rudimentary collision resolve (physix wip3)

In this example below you will see how to do a rudimentary collision resolve (physix wip3) with some HTML / CSS and Javascript

https://github.com/marcusstenbeck/physix

Thumbnail
This awesome code was written by marcusstenbeck, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright marcusstenbeck ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>rudimentary collision resolve (physix wip3)</title>
  
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">

  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  
<canvas id="c"></canvas>
<button id="restart">Restart</button>
  
  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/marcusstenbeck/rudimentary-collision-resolve-physix-wip3-qENbod */
#c {
			position: absolute;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			background-color: #000;
			width: 100%;
			height: 100%;
		}
#restart {
  position: absolute;
  top: 10px;
  left: 10px;
  background-color: #f00;
  border-color: #900;
}

/*Downloaded from https://www.codeseek.co/marcusstenbeck/rudimentary-collision-resolve-physix-wip3-qENbod */
function Vec2(x,y) {
		this.x = isNaN(x) ? 0 : x;
		this.y = isNaN(y) ? 0 : y;
	}

	Vec2.prototype.getLength = function() {
		return Math.sqrt(this.x*this.x + this.y*this.y);
	};

	Vec2.prototype.normalize = function() {
		var mag = this.getLength();
		this.x /= mag;
		this.y /= mag;
	};

	function World() {
		this.bodies = [];
		this.forceFields = [];
		this.gravity = new Vec2();
	}

	World.prototype.update = function(timestep) {
		if(!timestep) {
			console.warn('Bad timestep value', timestep);
			return;
		}

		// Make sure timestep never exceeds 10 ms
		for(var dt = 10, timeleft = timestep; timeleft > 0; timeleft -= dt) {
			var step = timeleft < dt ? timeleft : dt;
			this._updateFixedTimeStep(step);
		}
	};

	World.prototype._updateFixedTimeStep = function(timestep) {
		/**
		 *  Update positions, velocities, accelerations
		 */
		this._integrate(timestep);

		/**
		 *  Check for collisions
		 */
		var collisions = this._detectCollisions(this.bodies);

		/**
		 *  Resolve collisions
		 */
		this._resolveCollisions(collisions);
	};

	World.prototype._integrate = function(timestep) {
		var body;

		for(var i = 0; i < this.bodies.length; i++) {
			body = this.bodies[i];

			/* TODO: Remove this?
			for(var j = 0; j < this.forceFields.length; j++) {
				var ff = this.forceFields[j];

				var dist = new Vec2(ff.pos.x - body.pos.x, ff.pos.y - body.pos.y);
				dist.normalize();

				body.accumulatedForce.x += dist.x * ff.magnitude / body.mass;
				body.accumulatedForce.y += dist.y * ff.magnitude / body.mass;
			}
			*/

			// Calculate acceleration
			switch(body.type) {
				case Body.DYNAMIC:
					body.acc.x = (this.gravity.x + body.accumulatedForce.x) / body.mass;
					body.acc.y = (this.gravity.y + body.accumulatedForce.y) / body.mass;
					break;
				case Body.KINEMATIC:
					body.acc.x = body.accumulatedForce.x / body.mass;
					body.acc.y = body.accumulatedForce.y / body.mass;
					break;
			}

			// Zero out accumulated force
			body.accumulatedForce.x = 0;
			body.accumulatedForce.y = 0;

			// Calculate velocity
			body.vel.x += body.acc.x * timestep;
			body.vel.y += body.acc.y * timestep;

			// Calculate position
			body.pos.x += body.vel.x * timestep;
			body.pos.y += body.vel.y * timestep;
		}
	};

	World.prototype._detectCollisions = function(bodies) {
		var collisions = [];

		for(var i = 0; i < bodies.length; i++) {
			var ba = bodies[i].getBounds();

			for(var j = i+1; j < bodies.length; j++) {
				var bb = bodies[j].getBounds();

				var l1 = ba.left;
				var r1 = ba.right;
				var t1 = ba.top;
				var b1 = ba.bottom;

				var l2 = bb.left;
				var r2 = bb.right;
				var t2 = bb.top;
				var b2 = bb.bottom;

				if(l1 > r2 || r1 < l2 || t1 < b2 || b1 > t2) continue;

				// If we've come here, there has to be a collision
				collisions.push([bodies[i], bodies[j]]);
			}
		}

		return collisions;
	};

	World.prototype._resolveCollisions = function(collisions) {
		if(collisions.length == 0) return;
		
		var col;

		while(col = collisions.shift()) {
			// Resolve collision
			
			if(col[0].type == Body.DYNAMIC) {
				
				// absolute distance
				var dist = new Vec2(Math.abs(col[0].pos.x - col[1].pos.x), Math.abs(col[0].pos.y - col[1].pos.y));

				switch(col[1].type) {
					case Body.DYNAMIC:
						// dynamic - dynamic
						if(dist.x > dist.y) {

							/**
							 * Move along horizontal axis (the boxes are closest horizontally)
							 */
/*								
							// find out sign
							var sign = (col[0].pos.x - col[0].pos.x) < 0 ? -1 : 1;
							
							// calc relative position to move
							var moveDiff = 0.5 * dist.x * sign / 2;

							// move the bodies
							col[0].pos.x += moveDiff;
							col[1].pos.x -= moveDiff;

							// reverse their velocities
							col[0].vel.x *= -1;
							col[1].vel.x *= -1;

*/							} else if(dist.x < dist.y) {
							/**
							 *  Move along vertical axis (the boxes are closest vertically)
							 */
							
							// find out sign
							var sign = (col[0].pos.y - col[0].pos.y) < 0 ? -1 : 1;
							
							// calc relative position to move
							var moveDiff = sign*(0.5*(col[0].shape.height + col[1].shape.height) - dist.y + 0.000000001);

							// move the bodies
							col[0].pos.y += moveDiff;
							col[1].pos.y -= moveDiff;

							// reverse their velocities
							col[0].vel.y *= -.98;
							col[1].vel.y *= -.98;
						} else {
							// corner collision
						}
						break;

					case Body.KINEMATIC:
						// dynamic - kinematic
						col[0].pos.y += 0.5*(col[0].shape.height + col[1].shape.height) - dist.y + 0.000000001;
						col[0].vel.y *= -.98;
						break;
				}
			} else if(col[0].type == Body.KINEMATIC) {
				switch(col[1].type) {
					case Body.DYNAMIC:
						// kinematic - dynamic
						col[1].pos.y += 0.5*(col[0].shape.height + col[1].shape.height) - dist.y + 0.000000001;
						col[1].vel.y *= -.98;
						break;

					case Body.KINEMATIC:
						// kinematic - kinematic
						break;
				}
			}
		}
		return this._resolveCollisions(this._detectCollisions(this.bodies));
	};

	function Body(params) {
		if(!params) params = {};

		this.mass = 1;
		this.pos = new Vec2();
		this.vel = new Vec2();
		this.acc = new Vec2();
		this.accumulatedForce = new Vec2();

		this.type = params.type || Body.DYNAMIC;

		// TODO: Refactor out of here. Shape class?
		this.shape = {
			name: 'rectangle',
			width: 10,
			height: 10
		};
	}

	Body.prototype.getBounds = function() {
		return {
			left: this.pos.x - this.shape.width/2,
			right: this.pos.x + this.shape.width/2,
			top: this.pos.y + this.shape.height/2,
			bottom: this.pos.y - this.shape.height/2
		};
	};

	Body.prototype.applyForce = function(vecForce) {
		this.accumulatedForce.x += vecForce.x;
		this.accumulatedForce.y += vecForce.y;
	};

	Body.DYNAMIC = 'dynamic';
	Body.KINEMATIC = 'kinematic';

var w;

	// TODO: Move this someplace better
	window.requestAnimFrame =
	    window.requestAnimationFrame ||
	    window.webkitRequestAnimationFrame ||
	    window.mozRequestAnimationFrame ||
	    window.oRequestAnimationFrame ||
	    window.msRequestAnimationFrame ||
	    function(callback) {
	        window.setTimeout(callback, 1000 / 60);
	};

	var canvas = document.getElementById('c');
	var ctx = canvas.getContext('2d');

	function resize() {
		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight;
	}

	var dt = 0, lastTime = 0;
	function update(time) {
		requestAnimFrame(update);

		ctx.clearRect(0, 0, canvas.width, canvas.height);

		dt = Math.min(60, time - lastTime);

		w.update(dt);
		var bodies = w.bodies;
		for(var i = 0; i < bodies.length; i++) {
			var bound = bodies[i].getBounds();
			ctx.beginPath();
			ctx.strokeStyle = "magenta";
			ctx.strokeRect(bound.left, canvas.height - bound.top, bodies[i].shape.width, bodies[i].shape.height);
			ctx.restore();
		}

		lastTime = time;
	}

	function init() {
		resize();
		window.addEventListener('resize', resize);

		w = new World();
		w.gravity.y = -0.001;

		var b;
		for(var i = 0; i < 3; i++) {
			b = new Body();
			b.pos.x = 0.5*canvas.width;// + i*2;
			b.pos.y = 0.45*canvas.height + 0.3*i*canvas.height/2;
			w.bodies.push(b);
		}

		b = new Body({
			type: Body.KINEMATIC
		});
		b.pos.x = 0.5*canvas.width;
		b.pos.y = 0.4*canvas.height;
		w.bodies.push(b);

		update();
	}

	init();


var btnRestart = document.getElementById('restart');
btnRestart.onclick = function() {
  init();
};

Comments