AnswerBun.com

Wall sliding in JavaScript; works, but gets stuck at corners

Game Development Asked by bluejayke on December 31, 2020

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)?

One Answer

I don't know much about javascript, but it seems like you are moving from pt1 to pt2 when you are colliding a wall. Notice that it depends what direction you are going. If you go on a wall and slide and block to the corner, when you go on the other wall sharing the same corner, you can slide and it will exit the wall, where on the other wall it was blocking. Now, if I get this right, you are sliding on the wall using pt1 to pt2, and it seems like depending on which corner is pt1 and which is pt2, you won't block on one of them. That means the order of the wall points changes something and somewhere you are saying that pt1 won't block and pt2 will block, or vice versa. If you understand any of this script, you should be able to fix this. Else, like the others said, you should try to understand it first, play around with it, than try to fix the problem.

Answered by Samuel Fyckes on December 31, 2020

Add your own answers!

Related Questions

My GameObject keeps spawning when it shouldn’t

2  Asked on November 9, 2021 by michael-hampshire

     

Texture blends into one single color

1  Asked on November 9, 2021 by andrey-k

       

Player jitters when Jumping at object edges

1  Asked on November 9, 2021 by cgaudet

   

My bullets won’t bounce in Unity?

1  Asked on November 6, 2021 by kayra-yorulmaz

     

How to time draw images to the window in d3d8

0  Asked on November 2, 2021 by kite

 

Tank / turret animations

0  Asked on November 2, 2021 by lilkrit

   

Problems with recursive backtracking algorithm for a 3D maze

0  Asked on November 2, 2021 by janis-taranda

   

Add a sound to all the buttons in a project

3  Asked on November 2, 2021 by hamza-memon

   

Editing terrain in Blender

0  Asked on November 2, 2021

   

Ask a Question

Get help from others!

© 2022 AnswerBun.com. All rights reserved. Sites we Love: PCI Database, MenuIva, UKBizDB, Menu Kuliner, Sharing RPP, SolveDir