BH


HI
I've been looking through a lot of answers here:

https://gamedev.stackexchange.com/questions/158825/resolving-circle-circle-collision

https://gamedev.stackexchange.com/questions/26439/circle-to-circle-collision-checking-each-circle-against-all-others

https://gamedev.stackexchange.com/questions/136520/nestling-into-contact-with-a-group-of-physics-objects-without-exerting-forces-on/136595#136595

https://stackoverflow.com/questions/345838/ball-to-ball-collision-detection-and-handling

https://stackoverflow.com/questions/53010947/detect-collision-between-two-circles-and-sliding-them-on-each-other

https://gamedev.stackexchange.com/questions/199274/sliding-character-inside-the-circle

https://gamedev.stackexchange.com/questions/147493/2d-circle-detection-algorithm-partially-working

https://gamedev.stackexchange.com/questions/40547/box2d-too-much-for-circle-circle-collision-detection

https://gamedev.stackexchange.com/questions/103278/circle-collision-resolution-and-angular-momentum

https://flatredball.com/documentation/tutorials/math/circle-collision/

https://stackoverflow.com/questions/61161303/how-do-i-simulate-ball-ball-collision-without-having-the-balls-bounce --doesnt show sliding

https://stackoverflow.com/questions/26216173/accelerometer-rolling-ball-in-ball -- not sure how to implement in my case for angle `a`

Couldn't find exactly the same thing I was looking for, which is proper top-down character sliding of circle to circle movement.

I tried implemmenting things from other answers here but whenever the character moves around circle, for some reason it just goes inside it (was able to get it to spin around edges at one point but was unpredictable)

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-html -->

    BH

    <br>
    game test 2d minimal<br>
    <meta charset="utf-8">
    <button onclick="start()" class=wow>ok</button>
    <canvas id=can>

    </canvas>
    <style>
    	canvas{
    		width:100%;
    		height:100%;
    		position:absolute;
    		top:0;	
    		left:0px
    	}

    	.wow{
    		float:right;
    		z-index:1298737198
    	}

    </style>
    <script>
    var aD =[]
    var r
    function start() {
    	r = new CanvasRenderer(can)
    	my = new scene();
    	eventHandler();
    	
    	my.add(new mesh({
    		position:{
    			x:700,
    			y:200
    		},
    		radius:70,
    		color:"yellow",
    		primitive:"circle",
    		collision:"circle"
    	}))
    	
    	my.add(new mesh({
    		primitive:"circle",
    		
    		collision:"circle",
    		color:"blue",
    		name: "player",
    		radius:40,
    		position: {
    			x: 311,
    			y:75
    		},
    		origin: {
    			x:0.5,
    			y:0.5
    		},
    		onupdate(me) {
    			

    			aD.push( () => {
    				r.ctx.beginPath()
    				r.ctx.fillStyle="red"
    				r.ctx.arc(me.position.x, me.position.y, 5, 0, Math.PI*2);
    				r.ctx.fill()
    				r.ctx.closePath();
    			})
    			
    			if(!window.pl)pl=me
    			var upKey = keys[38],
    				downKey = keys[40],
    				rightKey = keys[39],
    				leftKey = keys[37],
    				drx  = 0,
    				dx = 0,
    				speed = 5,
    				turningSpeed = 3
    			//console.log(upKey)
    			drx = leftKey ? -1 : rightKey ? 1 : 0
    			forward = upKey ? 1 : downKey ? -1 : 0

    			me.rotation.x += (
    				(drx * Math.PI / 180 * turningSpeed )
    			)
    			me.rotation.y = 1;

    			var xDir = Math.cos(me.rotation.x)
    			var yDir = Math.sin(me.rotation.x)
    			
    			var xvl = xDir  * forward * speed
    			var yvl = yDir * forward * speed
    			me.position.y//=col.y
    			me.position.x// = col.x
    			me.xvl=xvl
    			me.yvl=yvl
    			var next = {
    				x:me.position.x+xvl,
    				y:me.position.y+yvl
    			}
    			me.next=next
    			var isColliding = []
    			var coll;
    			var curColls = []
    			me.cols=[]

    			me.speed=speed
    			me.yDir=yDir
    			for(var i = 0; i < my.objects.length; i++) {
    				let cur = my.objects[i];
    				if(cur.name == me.name || cur == me) {
    					break;	
    				}
    				curColls = checkCollision(
    					next.x,
    					next.y,
    					me.radius||me.scale.x/2,
    					cur
    				)
    				
    				
    				curColls.forEach(coll=>{
    					if(!coll) return;
    					if(coll==me) {
    					//	console.log("WH")
    						return	
    					}
    					
    					
    					cur.isCol=true
    					isColliding.push({coll,object:cur})	
    					//	console.log(2222,coll.line)

    				})
    			}
    			
    			Array.from(me.cols)
    				.forEach(q=>{
    					var w=isColliding.indexOf(q)
    					if(w==-1||q.object==me){
    						me.cols.splice(me.cols.indexOf(q),1)
    					}
    					

    			})
    			var isme=me.cols.findIndex(q=>q.object==me)
    			if(isme>-1){
    				me.cols.splice(isme,1)
    				var ok=isColliding.findIndex(q=>q.object==me)
    				if(ok>-1){
    					isColliding.splice(ok,1)	
    				}
    			}
    			if(!me.obs)
    				me.obs=[]
    			var noad=false
    			if(isColliding.length > 0) {
    			
    				isColliding.forEach(q=>{
    					var w = me.cols.indexOf(q)
    					if(w==-1){
    						me.cols.push(q)	
    					}
    				})
    				
    				
    				
    				
    				
    				var col =me.cols[0].coll
    				var ob=me.cols[0].object
    				if(!me.obs)
    					me.obs=[]

    				if(col.type=="circle"){
    					let vecBetweenX = next.x - ob.position.x;
    					let vecBetweenY = next.y - ob.position.y;

    					let heading = Math.atan2(vecBetweenY, vecBetweenX);

    					var dst = Math.sqrt(
    						vecBetweenX*vecBetweenX
    						+
    						vecBetweenY*vecBetweenY
    					)
    					var dirX=vecBetweenX/dst
    					var dirY=vecBetweenY/dst

    					var rt= heading
    					
    					var ris = ob.radius + me.radius

    					var ax = Math.cos(rt)*ris
    					var ay = Math.sin(rt)*ris

    					/*
    					var nx=ob.position.x+ax
    					var ny=ob.position.y+ay
    					*/
    					
    					var nx=ob.position.x+dirX*ris
    					var ny=ob.position.y+dirY+ris
    					
    					me.position.x = nx
    					me.position.y = ny
    					return


    				}
    			}
    			
    			me.position.x += xvl
    			
    			me.position.y += yvl
    		}

    	}));
    	let i = setInterval(() => render(r, my), 16);
    	r.on("resize", () => render(r, my));

    }
    	
    	
    function render(r,s) {
    	
    	s.update();
    	r.render(s)
    	aD.forEach(x=>x());
    	aD = []
    }

    onload = start;


    function checkCollision(x1, y1,rad,ob) {
    	var colls = [];
    	if(ob.collision == "circle") {
    		/*
    			Ball 1:  center: p1=(x1,y1)  radius: r1
    			Ball 2:  center: p2=(x2,y2)  radius: r2

    			collision distance: R= r1 + r2
    			actual distance:    r12= sqrt( (x2-x1)^2 + (y2-y1)^2 )

    			collision vector: d12= (x2-x1,y2-y1) = (dx,dy)
    			actual distance:  r12= sqrt( dx*dx + dy*dy )
    		*/
    		
    		var r1 = rad;
    		var r2 = ob.radius;
    		
    		var p1 = [x1,y1]
    		var p2 = [ob.position.x,ob.position.y]
    		
    		var R = r1 + r2;
    		var R2 = R * R;
    		
    		var dx = p2[0] - p1[0]
    		var dy = p2[1] - p1[1]
    		
    		var r12 = (
    			dx * dx + dy * dy
    		)
    		
    		var r12s = Math.sqrt(r12)
    		
    		var isCollision = r12s < R
    		
    		var colPoint = {
    			x:(x1*r2 + p2[0]*r1) / 
    				(r1+r2),
    			y:(y1*r2+p2[1]*r1) /
    				(r1+r2),
    			type:"circle"
    		};

    		aD.push( () => {
    			r.ctx.beginPath()
    			r.ctx.fillStyle="salmon"
    			r.ctx.arc(colPoint.x, colPoint.y, 5, 0, Math.PI*2);
    			r.ctx.fill()
    			r.ctx.closePath();
    		})

    		if(isCollision) {
    			colls.push(colPoint)
    		}
    	}
    	
    	return colls;

    					
    }

    	
    function dot(x1, y1, x2, y2) {
    	return (
    		x1 * x2 + y1 * y2
    	)
    }
    	

    function eventHandler() {
    	window.keys = {};
    	addEventListener("keyup" , e=> {
    		keys[e.keyCode] = false;
    			
    	});

    	addEventListener("keydown" , e=> {
    		keys[e.keyCode] = true;
    	});
    }

    function CanvasRenderer(dom) {
    	if(!dom) dom = document.createElement("canvas");
    	
    	var events = {}, self = this;
    	function rsz() {
    		dom.width = dom.clientWidth;
    		dom.height = dom.clientHeight;
    		self.dispatchEvent("resize");
    	}
    	
    	window.addEventListener("resize", rsz);	

    	let ctx = dom.getContext("2d");

    	function render(scene) {
    		ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
    		for(let i = 0; i < scene.objects.length; i++) {
    			let o = scene.objects[i],
    				verts = o.realVerts;

    			
    			if(o.primitive == "circle") {
    				var fnc = o.drawPrimitive[o.primitive]
    				if(typeof(fnc)=="function")
    					fnc(ctx)
    			}
    			ctx.fillStyle = o.color || "blue";
    			ctx.lineWidth = 1;
    			ctx.fill()
    			ctx.stroke();
    		}
    	}

    	Object.defineProperties(this, {
    		domElement: {
    			get: () => dom
    		},	
    		ctx: {
    			get: () => ctx
    		},
    		render: {
    			get: () => render
    		},
    		on: {
    			get: () => (nm, cb) => {
    				if(!events[nm]) {
    					events[nm] = [];
    				}
    				events[nm].push(data => {
    					if(typeof cb == "function") {
    						cb(data);
    					}
    				});
    			}		
    		},
    		dispatchEvent: {
    			get: () => (name, data) => {
    				if(events[name]) {
    					events[name].forEach(x => {
    						x(data);
    					});
    				}
    			}
    		}
    	});
    	
    	rsz();

    }

    function scene() {
    	let objects = [];
    	Object.defineProperties(this, {
    		add: {
    			get: () => obj => {
    				objects.push(obj);
    			}
    		},
    		objects: {
    			get: () => objects
    		},
    		update: {
    			get: () => () => {				
    				objects.forEach(x => {
    					if(typeof x.update == "function") {
    						x.update();
    					}
    				});
    				
    			}
    		}
    	});
    }
    	
    function mesh(data={}) {
    	let self = this,
    		holder = {
    			position:{},
    			scale: {
    				
    			},
    			rotation: {},
    			origin:{}
    		},
    		position = {},
    		scale = {},
    		rotation = {},
    		collision="mesh",
    		shape = null,
    		origin = {},
    		color,
    		radius=0,
    		name,
    		primitive,
    		
    		eventNames = "update",
    		events = {},
    		drawPrimitive = {
    			circle(ctx) {
    				ctx.beginPath();
    				ctx.arc(
    					self.position.x,
    					self.position.y,
    					radius, 
    					0,
    					360 * Math.PI / 180
    				);
    				ctx.closePath();
    			}
    		},
    		width = 1,
    		height = 1;

    	Object.defineProperties(position, {
    		x: {
    			get: () => holder.position.x || 0,
    			set: v => holder.position.x = v
    		},
    		y: {
    			get: () => holder.position.y || 0,
    			set: v => holder.position.y = v
    		},
    		z: {
    			get: () => holder.position.z || 0,
    			set: v => holder.position.z = v
    		}
    	});

    	Object.defineProperties(scale, {
    		x: {
    			get: () => holder.scale.x || 1,
    			set: v => holder.scale.x = v
    		},
    		y: {
    			get: () => holder.scale.y || 1,
    			set: v => holder.scale.y = v
    		},
    		z: {
    			get: () => holder.scale.z || 1,
    			set: v => holder.scale.z = v
    		}
    	});

    	Object.defineProperties(rotation, {
    		x: {
    			get: () => holder.rotation.x || 0,
    			set: v => holder.rotation.x = v
    		},
    		y: {
    			get: () => holder.rotation.y || 0,
    			set: v => holder.rotation.y = v
    		},
    		z: {
    			get: () => holder.rotation.z || 0,
    			set: v => holder.rotation.z = v
    		}
    	});


    	Object.defineProperties(this, {

    		name: {
    			get: ()=>name,
    			set(v) {
    				name = v
    			}
    		},
    		primitive: {
    			get: ()=>primitive,
    			set(v) {
    				primitive = v;
    				
    			}
    		},
    		
    		drawPrimitive: {
    			get: ()=>drawPrimitive
    		},
    		width: {
    			get: ()=>width,
    			set(v) {
    				width = v
    			}
    		},
    		height: {
    			get: ()=>height,
    			set(v) {
    				height = v
    			}
    		},
    		position: {
    			get: () => position,
    			set: v => {
    				position.x = v.x || 0;
    				position.y = v.y || 0;
    				position.z = v.z || 0;
    			}
    		},
    		
    		radius: {
    			get: () => radius||(width*scale.x)/2,
    			set: v => {
    				radius = v;
    				this.width = radius/2
    				this.height = radius/2
    			}
    		},
    		scale: {
    			get: () => scale,
    			set: v => {
    				scale.x = v.x || v.x === 0 ? v.x : 1;
    				scale.y = v.y  || v.y === 0 ? v.y : 1;
    				scale.z = v.z  || v.z === 0 ? v.z : 1;
    				
    					
    			}
    		},
    		rotation: {
    			get: () => rotation,
    			set: v => {
    				rotation.x = v.x || 0;
    				rotation.y = v.y || 0;
    				rotation.z = v.z || 0;
    			}
    		},
    		color: {
    			get: () => color,
    			set: v => {
    				color = v;
    			}
    		},
    		update: {
    			get: () => () => {
    				if(events["update"]) {
    					events.update.forEach(x => {
    						
    						x(this);
    					});
    				}
    			}
    		},
    		on: {
    			get: () => (nm, fnc) => {
    				if(!events[nm]) events[nm] = [];
    				events[nm].push(stuff => {
    					if(typeof fnc == "function") {
    						fnc(stuff);
    					}
    				});
    			}
    		}
    	});

    	
    	
    	eventNames.split(" ").forEach(x => {
    		var name = "on" + x;
    		if(!this.hasOwnProperty(name)) {
    			Object.defineProperty(this, name, {
    				get: () => events[name],
    				set(v) {
    					events[x] = [
    						data => {
    							typeof v == "function" && v(data)
    						}
    					];
    				}
    			});
    		}
    	});

    	for(let k in data) {
    		this[k] = data[k]
    	}


    }
    </script>

<!-- end snippet -->

just arrow keys to move

relevant part for circle sliding (contains code from different examples that was used at different times but nothing works):

```

				if(col.type=="circle"){
					let vecBetweenX = next.x - ob.position.x;
					let vecBetweenY = next.y - ob.position.y;

					let heading = Math.atan2(vecBetweenY, vecBetweenX);

					var dst = Math.sqrt(
						vecBetweenX*vecBetweenX
						+
						vecBetweenY*vecBetweenY
					)
					var dirX=vecBetweenX/dst
					var dirY=vecBetweenY/dst

					var rt= heading
					
					var ris = ob.radius + me.radius

					var ax = Math.cos(rt)*ris
					var ay = Math.sin(rt)*ris

					/*
					var nx=ob.position.x+ax
					var ny=ob.position.y+ay
					*/
					
					var nx=ob.position.x+dirX*ris
					var ny=ob.position.y+dirY+ris
					
					me.position.x = nx
					me.position.y = ny
					return


				}
			}
			
```
[Edit] tried using method suggested in answer, but player is still getting stuck at circle, while the ideal intended behavior is for the player to slide along the edges