this is a follow up to this other question: How do I handle player collision with corners of a wall
In inspiration of the code given in its answer, I tried to write some new code.
Basically, in the original, the wall sliding works very well on the inside of the walls, but I wanted to make it work on the outside as well, so I made a new basic code engine, based on his technique:
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++
}
}));
var g = false
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) {
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));
}
function checkCollision(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;
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();
}
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 = {},
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;
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
}
},
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
}
<meta charset="utf-8">
<button onclick="start()" class=wow>ok</button>
<canvas id=can>
</canvas>
See line 71 for the collision detection implementation call (and the return value of the function there).
The problem is, as you can hopefully see (just fullscreen it and use arrow keys to move, try colliding with the orange mesh at the corners) that it slides fine, but when it gets to the corners, it gets stuck at them.
Any ideas how to fix this -- without using any kind of external libraries etc. (only what's available in the snippet)?