I've been looking through a lot of answers here:
- Resolving Circle-Circle collision
- Circle to Circle collision, checking each circle against all others
- Nestling into contact with a group of physics objects without exerting forces on them
- 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
- Sliding character inside the circle
- 2D Circle detection algorithm partially working
- Box2d too much for Circle/Circle collision detection?
- 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
I 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 implementing things from other answers here but whenever the character moves around circle, for some reason it just goes inside it (I was able to get it to spin around edges at one point but was unpredictable)
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]
}
}
canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0px
}
.wow {
float: right;
z-index: 1298737198
}
<br> game test 2d minimal<br>
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can></canvas>
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
}
I 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