Javascript Wall Collision On Convex Shapes, Getting Stuck At Corner
Solution 1:
I would say just check that you are not in the corner - discard cases when percentOfWall
is either exactly 0
or exactly 1
EDIT: to address the corners mentioned in the comments I have to explain why your implementation got stuck. It calculated penetration with all the walls and decreased position change by this penetration amount. In the corner, the object collided with both edges at once, and being repulsed from both at once stopped moving.
As you rightly noticed, in the corners your square gets inside the obstacle for one frame and then is pushed out on the consequent frame.
Alternative solutions, however, are more complicated and harder to debug, but here is a sketch for a couple of options:
- Limit the collisions to repulse from only one wall at a time and early exit your
forEach
loop. This is a simple solution which will work in this particular example but won't work in the general case, for example in the corner when you need to collide with 2 walls preventing you to go in both directions. - Add a tiny circle in each of the corners and collide with it to avoid going inside. The normal is along the line between the center of the circle and the point of contact. That smooths the normal discontinuity you have in the corners and there is always a single normal along which the object is repulsed, continuously changing from one segment to the other.
Directing the "push" to outside each segment of the obstacle (which is what you are asking) wouldn't prevent stopping as on the corner (which is exactly the point you are concerned with), both walls will collide with your object and "outside" will be in the opposite directions. So it'll get stuck the same way as before and for exact same reason - normals will be not continuous.
I hope that helps
var aD =[]
var r
functionstart() {
r = newCanvasRenderer(can),
my = newscene();
window.my = my
eventHandler();
my.add(newmesh({
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++
}
}));
var g = false
my.add(newmesh({
primitive:"rect",
name: "player",
scale: {
x: 50,
y:50
},
position: {
x: 311,
y:75
},
origin: {
x:0.5,
y:0.5
},
onupdate(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)
me.position.x += xDir * forward * speed
me.position.y += yDir * forward * speed
for(var i = 0; i < my.objects.length; i++) {
let cur = my.objects[i];
if(cur.name !== me.name) {
cur.lineSegments.forEach(l => {
var col = checkCollision(
me.position.x,
me.position.y,
me.scale.x/2,
l
)
if(col) {
me.position.y=col.y
me.position.x = col.x
}
});
}
}
}
}));
let i = setInterval(() =>render(r, my), 16);
r.on("resize", () =>render(r, my));
}
functioncheckCollision(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 && percentOfWall > 0 && percentOfWall < 1) {
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
}
}
}
functiondot(x1, y1, x2, y2) {
return (
x1 * x2 + y1 * y2
)
}
functiondistance2(x1, y1, x2, y2) {
let dx = (x1 - x2), dy = (y1 - y2);
return (
dx * dx + dy * dy
);
}
functionrender(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;
functioneventHandler() {
window.keys = {};
addEventListener("keyup" , e=> {
keys[e.keyCode] = false;
});
addEventListener("keydown" , e=> {
keys[e.keyCode] = true;
});
}
functionCanvasRenderer(dom) {
if(!dom) dom = document.createElement("canvas");
var events = {}, self = this;
functionrsz() {
dom.width = dom.clientWidth;
dom.height = dom.clientHeight;
self.dispatchEvent("resize");
}
window.addEventListener("resize", rsz);
let ctx = dom.getContext("2d");
functionrender(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;
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.fillStyle = o.color || "blue";
ctx.lineWidth = 1;
ctx.fill()
ctx.stroke();
ctx.closePath();
}
}
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();
}
functionscene() {
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();
}
});
}
}
});
}
functionmesh(data={}) {
let verts = [],
self = this,
holder = {
position:{},
scale: {
},
rotation: {},
origin:{}
},
actual = {
},
position = {},
scale = {},
rotation = {},
origin = {},
color,
name,
primitive,
eventNames = "update",
events = {},
drawPrimitive = {
circle(ctx) {
ctx.beginPath();
ctx.arc(
self.position.x,
self.position.y,
5,
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;
functionupdateRealVerts() {
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();
}
functionupdateLineSegments() {
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
}
},
name: {
get: ()=>name,
set(v) {
name = v
}
},
primitive: {
get: ()=>primitive,
set(v) {
primitive = v;
let newVerts = primitiveToVerts[v];
if(newVerts) {
this.verts = newVerts();
}
}
},
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;
}
},
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();
}
canvas{
width:100%;
height:100%;
position:absolute;
top:0;
left:0px
}
.wow{
float:right;
z-index:1298737198
}
<metacharset="utf-8"><buttononclick="start()"class=wow>ok</button><canvasid=can></canvas>
Post a Comment for "Javascript Wall Collision On Convex Shapes, Getting Stuck At Corner"