BH
HI 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
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
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)
BH
<br>
game test 2d<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();
window.my = my
eventHandler();
my.add(new mesh({
verts: [
0, 0,
100, 15,
115, 60,
50, 100,
20, 75,2,8
],
position: {
x: 100,
y:100
},
scale: {
x:4,y:5
},
color:"orange",
onupdate(me) {
// me.position.x++
}
}));
my.add(new mesh({
position:{
x:700,
y:200
},
radius:70,
color:"yellow",
primitive:"circle",
collision:"circle"
}))
var g = false
corn=false
nv = [0,0]
my.add(new mesh({
primitive:"rect",
name: "player",
scale: {
x: 50,
y:50
},
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
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
if(nv[0]||nv[1]){
//xvl = -nv[0]
//yvl = -nv[1]
}
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 lastdify = 0;
var lastdifx = 0;
var dify = 0
var difx = 0
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=[]
//console.log("COL",col.type,col.name)
ob.active=false
ob.wowow=292
var op=[0,0]
//corn=false
// if(col.type=="mesh") console.log("wow")
// console.log("K",col.x,col.y,me.cols.length,isColliding.length,isme)
if(isColliding.length==1){
if(me.obs.length>0){
console.log("undid")
me.obs.forEach(ob=>{
//if(ob.active) {
ob.lastPoint = null
me.obs.splice(me.obs.indexOf(ob),1)
//}
})
}
var backlash = col.x
var backlashy = col.y
if(col.type=="circle"){
let vecBetweenX = me.position.x - ob.position.x;
let vecBetweenY = me.position.y - ob.position.y;
let heading = Math.atan2(vecBetweenY, vecBetweenX);
var rt= me.rotation.x+heading
var ax = Math.cos(rt)*speed*forward
var ay = Math.sin(rt)*speed*forward
difx = -ax
dify = -ay
me.position.x +=difx
me.position.y += dify
// return
} else {
difx += col.x-next.x
dify += col.y-next.y
}
}else {
me.eq=false
me.curc=null
ob.active=true
if(!col.line) return;
var xd = col.line.end[0]-col.line.start[0]
var slope1=slope(col.line)
var b = findb(col.x,col.y,slope1)
var amy = slope1
var amx = Math.abs(xd)
var y;
if(!ob.lastPoint) {
y = col.y+amy
} else {
y = ob.lastPoint.y+amy;
ob.lastPoint.y+=amy;
}
var x = xatywithslope(y,slope1,b)
me.tst={
b,x,y,slope1,amx,amy
}
if(col.percentOfWall == 0 || col.percentOfWall == 1){
me.eq=true
difx += x-next.x
dify += y-next.y
if(!ob.lastPoint){
ob.lastPoint = {
x:col.x+amx,
y:col.y+amy
}
me.obs.push(ob)
}
} else {
me.curc=col;
difx += col.x-next.x
dify += col.y-next.y
lastdify = dify
lastdifx = difx
if(difx>0)
nv[0] = difx
if(dify>0)
nv[1]=dify
}
}
dify+=op[1]
difx+=op[0]
}
if(!noad) {
//xvl+=difx
//yvl+=dify
me.position.x+=difx+xvl
me.position.y+=dify+yvl
} else {
// xvl+=nv[0]//*forward
// yvl-=nv[1]//*forward
}
if(noad) {
} else {
}
}
}));
let i = setInterval(() => render(r, my), 16);
r.on("resize", () => render(r, my));
}
function angleOf2points(p1,p2) {
return Math.atan2(p2[1] - p1[1], p2[0] - p1[0])
//* 180 / Math.PI
}
function xatywithslope(y,slope,b){
/*y=mx+b
mx=y-b
x=(y-b)/m
*/
return (y-b)/slope
}
function findb(x,y,s){
/*y=mx+b
y-mx=b
b=y-mx
*/
return y-s*x
}
function slope(line){
var p1=line.start
var p2=line.end
return (
(p1[1]-p2[1]) /
(p1[0]-p2[0])
)
}
function midSlope(slope1,slope2) {
return -Math.tan(
(
Math.atan(slope1) +
Math.atan(slope2)
) / 2
)
}
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)
}
} else {
// console.log("cecking",rad,ob)
ob.lineSegments.forEach(l => {
var dist = distance2(
l.start[0],
l.start[1],
l.end[0],
l.end[1]
),
vec1 = [
x1 - l.start[0],
y1 - l.start[1]
],
vec2 = [
l.end[0] - l.start[0],
l.end[1] - l.start[1]
],
percentOfWall = (
Math.max(
0,
Math.min(
1,
dot(
vec1[0],
vec1[1],
vec2[0],
vec2[1]
) / dist
)
)
),
projection = [
l.start[0] + percentOfWall * vec2[0],
l.start[1] + percentOfWall * vec2[1],
],
acDist = Math.sqrt(distance2(
x1, y1,
projection[0], projection[1]
))
aD.push( () => {
r.ctx.beginPath()
r.ctx.fillStyle="green"
r.ctx.arc(projection[0], projection[1], 5, 0, Math.PI*2);
r.ctx.fill()
r.ctx.closePath();
})
if(acDist < rad) {
aD.push(() => {
r.ctx.beginPath()
r.ctx.fillStyle="orange"
r.ctx.arc(projection[0] +
rad * (normal[1] ), projection[1] +
rad* (-normal[0] ), 5, 0, Math.PI*2);
r.ctx.fill()
r.ctx.closePath()
})
var mag = Math.sqrt(dist),
delt = [
l.end[0] - l.start[0],
l.end[1] - l.start[1]
],
normal = [
delt[0] / mag,
delt[1] / mag
]
colls.push({
percentOfWall,
type:"mesh",
x: projection[0] +
rad * (normal[1] ),
y:projection[1] +
rad* (-normal[0] ),
projection,
normal,
line:l
})
}
})
}
return colls;
}
function checkCollision1(x1, y1, rad,l) {
var dist = distance2(
l.start[0],
l.start[1],
l.end[0],
l.end[1]
),
vec1 = [
x1 - l.start[0],
y1 - l.start[1]
],
vec2 = [
l.end[0] - l.start[0],
l.end[1] - l.start[1]
],
percentOfWall = (
Math.max(
0,
Math.min(
1,
dot(
vec1[0],
vec1[1],
vec2[0],
vec2[1]
) / dist
)
)
),
projection = [
l.start[0] + percentOfWall * vec2[0],
l.start[1] + percentOfWall * vec2[1],
],
acDist = Math.sqrt(distance2(
x1, y1,
projection[0], projection[1]
))
aD.push( () => {
r.ctx.beginPath()
r.ctx.fillStyle="green"
r.ctx.arc(projection[0], projection[1], 5, 0, Math.PI*2);
r.ctx.fill()
r.ctx.closePath();
})
if(acDist < rad) {
var mag = Math.sqrt(dist),
delt = [
l.end[0] - l.start[0],
l.end[1] - l.start[1]
],
normal = [
delt[0] / mag,
delt[1] / mag
]
return {
x: projection[0] +
rad * (normal[1] ),
y:projection[1] +
rad* (-normal[0] ),
projection,
normal
}
}
}
function dot(x1, y1, x2, y2) {
return (
x1 * x2 + y1 * y2
)
}
function distance2(x1, y1, x2, y2) {
let dx = (x1 - x2), dy = (y1 - y2);
return (
dx * dx + dy * dy
);
}
function render(r,s) {
//r.ctx.clearRect(0,0,r.ctx.canvas.width,r.ctx.canvas.height)
s.update();
r.render(s)
aD.forEach(x=>x());
aD = []
}
onload = start;
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)
} else {
ctx.beginPath();
ctx.moveTo(
verts[0] ,
verts[1]
);
verts.forEach((v, i, ar) => {
let y = i;
ctx.lineTo(
v[0] ,
v[1]
);
});
ctx.lineTo(
verts[0],
verts[1]
);
ctx.closePath();
}
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 verts = [],
self = this,
holder = {
position:{},
scale: {
},
rotation: {},
origin:{}
},
actual = {
},
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();
},
rect(ctx) {
ctx.strokeRect(
self.position.x,
self.position.y,
30, 30
);
}
},
width = 1,
height = 1,
primitiveToVerts = {
rect: () => [
0, 0,
width , 0,
width, height,
0, height
]
},
realVerts = verts,
lineSegments = [],
o = this;
function updateRealVerts() {
let actualVerts = [],
originedVerts = [],
adjustedVerts = [],
rotatedVerts = [],
stepSize = o.step || 2,
curVerts = [];
o.verts.forEach((v, i) => {
curVerts.push(v);
if(
(i - 1) % stepSize === 0 &&
i !== 0
) {
actualVerts.push(curVerts);
curVerts = [];
}
});
actualVerts = actualVerts.filter(x => x.length == stepSize);
originedVerts = actualVerts.map(v => [
v[0] - o.origin.x,
v[1] - o.origin.y,
v[2] - o.origin.z
]);
rotatedVerts = originedVerts.map(v =>
[
v[0] * Math.cos(o.rotation.x) -
v[1] * Math.sin(o.rotation.x),
v[0] * Math.sin(o.rotation.x) +
v[1] *Math.cos(o.rotation.x),
v[2]
]
);
adjustedVerts = rotatedVerts.map(v =>
[
v[0] *
o.scale.x +
o.position.x,
v[1] *
o.scale.y +
o.position.y,
v[2] *
o.scale.z +
o.position.z,
]
);
realVerts = adjustedVerts;
updateLineSegments();
}
function updateLineSegments() {
let lines = [];
for(let i = 0, a = realVerts; i < a.length;i++) {
let start = [], end = []
if(i < a.length - 1) {
start = a[i];
end = a[i + 1];
} else {
start = a[i];
end = a[0];
}
lines.push({
start, end
})
}
lineSegments = lines;
}
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(origin, {
x: {
get: () => holder.origin.x || 0,
set: v => holder.origin.x = v
},
y: {
get: () => holder.origin.y || 0,
set: v => holder.origin.y = v
},
z: {
get: () => holder.origin.z || 0,
set: v => holder.origin.z = v
}
});
Object.defineProperties(this, {
verts: {
get: ()=>verts,
set(v) {
verts = v
}
},
shape: {
get: ()=>shape,
set(v) {
shape = v
}
},
name: {
get: ()=>name,
set(v) {
name = v
}
},
primitive: {
get: ()=>primitive,
set(v) {
primitive = v;
let newVerts = primitiveToVerts[v];
if(newVerts) {
this.verts = newVerts();
}
}
},
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;
}
},
origin: {
get: () => origin,
set: v => {
origin.x = v.x || 0;
origin.y = v.y || 0;
origin.z = v.z || 0;
}
},
color: {
get: () => color,
set: v => {
color = v;
}
},
realVerts: {
get: () => realVerts
},
lineSegments: {
get: () => lineSegments
},
update: {
get: () => () => {
if(events["update"]) {
events.update.forEach(x => {
updateRealVerts();
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]
}
updateRealVerts();
}
</script>
just arrow keys to move
relevant part for circle sliding:
if(col.type=="circle"){
let vecBetweenX = me.position.x - ob.position.x;
let vecBetweenY = me.position.y - ob.position.y;
let heading = Math.atan2(vecBetweenY, vecBetweenX);
var rt= me.rotation.x+heading
var ax = Math.cos(rt)*speed*forward
var ay = Math.sin(rt)*speed*forward
difx = -ax
dify = -ay
me.position.x +=difx
me.position.y += dify
// return
}
ive tried following the complex math questions here, but my mind can only understand code