• the warehouse
  • As hard as I try to clear some space, those crates keep piling up on me.
  • Sources

  • Main html file: dhtml/box2D.html
    ge1doot mini library: /library/ge1doot.js
    Physics Engine: /library/PHY2D.js
    Vector Lib: /library/V2.js
    Tutorial: Speculative contact
  • CSS

  • 
    html {
    	overflow: hidden;
    	-ms-touch-action: none;
    	-ms-content-zooming: none;
    }
    body {
    	position: absolute;
    	margin: 0;
    	padding: 0;
    	background: #000;
    	width: 100%;
    	height: 100%;
    }
    #screen {
    	position: absolute;
    	width: 100%;
    	height: 100%;
    }
    #textures, .textures {
    	visibility:hidden;
    }
    
  • HTML

  • 
    <canvas id="screen">HTML5 CANVAS box 2D demo</canvas>
    <div id="textures">
    	<img src="../images/caisse_bois_frag3.jpg" alt="">
    	<img src="../images/caisse_explosive_f2.jpg" alt="">
    	<img src="../images/caisse_export.jpg" alt="">
    	<img src="../images/caisse_bois_frag3.jpg" alt="">
    	<img src="../images/caisse_jurancon_face1.jpg" alt="">
    	<img src="../images/caisse_jurancon_face2.jpg" alt="">
    	<img src="../images/miam-fruit2.jpg" alt="">
    	<img src="../images/caisse_orange_face1.jpg" alt="">
    </div>
    <img id="blade" class="textures" src="../images/caisse_coke_f2.jpg" alt="">
    <img id="triangle" class="textures" src="../images/triangle-2.png" alt="">
    
  • JS

  • 
    /* =======================================================
     *  ---- HTML5 CANVAS box2D demo ----
     * script: Gerard Ferrandez - 28 April 2013
     * Adapted from a C# demo by Paul Firth
     * http://www.wildbunny.co.uk/blog/2011/03/25/speculative-contacts-an-continuous-collision-engine-approach-part-1/
     * ------------------------------------------------------
     * JavaScript code released under the MIT license
     * http://www.dhteumeuleu.com/LICENSE.html
     * ======================================================= */
     
     "use strict";
    
    (function () {
    	
    	// private variables
    	var scr, ctx, pointer, boxes, dropc = 0, frames = 0, PHY2D, V2, zoom;
    	
    	var clean = function () {
    		
    		frames++;
    		if (frames / 20 === Math.round(frames / 20)) {
    			var k = PHY2D.objects.length;
    			while (k--) {
    				if (PHY2D.objects[k].pos[1] > scr.height) {
    					PHY2D.deleteObject(k);
    				}
    			}
    			if (PHY2D.objects.length < 8 - dropc) drop();
    		}
    		
    	}
    	
    	var newBox = function (x, y) {
    		
    		if (!x) var x = pointer.X, y = -Math.random() * 512;
    		
    		if (Math.random() > 0.7) {
    			var img = document.getElementById("triangle");
    			PHY2D.triangle(img, x, y - img.height * 2, img.width * 0.5 * zoom, img.height * 0.7 * zoom, (img.width * img.height), Math.random() * 3 - 1.5)
    		} else {
    			var img = boxes[Math.floor(Math.random() * boxes.length)];
    			PHY2D.rectangle(img, x, y - img.height * 2, img.width * zoom, img.height * zoom, (img.width * img.height), Math.random() * 3 - 1.5)
    		}
    		
    	}
    	
    	var resize = function () {
    		
    		zoom = Math.max(0.5, Math.min(scr.width / 1366, 1));
    		PHY2D.deleteStatic();
    		var img = document.getElementById("blade");
    		PHY2D.rectangle(img, scr.width * 0.9, scr.height * 0.9, img.width * zoom, img.height * zoom, 0, 1, Math.PI/2); // blade
    		PHY2D.rectangle(img, scr.width * 0.9, scr.height * 0.9, img.width * zoom, img.height * zoom, 0, 1, 0); // blade
    		PHY2D.rectangle(img, scr.width * 0.1, scr.height * 0.9, img.width * zoom, img.height * zoom, 0, -1, Math.PI/2); // blade
    		PHY2D.rectangle(img, scr.width * 0.1, scr.height * 0.9, img.width * zoom, img.height * zoom, 0, -1, 0); // blade
    		PHY2D.rectangle(false, scr.width * 0.5, scr.height, scr.width * 0.7, 4, 0, 0, 0); // floor
    		
    	}
    	
    	var drop = function () {
    		
    		for (var i = 0; i < 10; i++) {
    			dropc++;
    			setTimeout(function() {
    				dropc--;
    				newBox(128 + (Math.random() * (scr.width - 256)), 0);
    			}, i * 1000);
    		}
    		
    	}
    	
    	// ==== init script ====
    	
    	var init = function (data) {
    		
    		// screen
    		scr = new ge1doot.Screen({
    			container: "screen",
    			resize: function () {
    				resize();
    			}
    		});
    		ctx = scr.ctx;
    
    		boxes = document.getElementById("textures").getElementsByTagName('img');
    		
    		// pointer events
    		pointer = new ge1doot.Pointer({
    			down: function() {
    				newBox();
    			},
    			
    			up: function () {
    				PHY2D.drag(false);
    			}
    		});
    		
    		// load physics engine
    		PHY2D = ge1doot.PHY2D();
    		PHY2D.init(data, ctx, pointer);
    		
    		scr.resize();
    		run();
    	}
    	
    	// ======== main loop ========
    	
    	var run = function () {
    
    		requestAnimFrame(run);
    		ctx.clearRect(0, 0, scr.width, scr.height);
    		clean();
    		PHY2D.render();
    		
    	}
    	return {
    		
    		// ---- onload event ----
    		load : function (data) {
    			window.addEventListener('load', function () {
    				init(data);
    			}, false);
    		}
    		
    	}
    })().load({
    	numIterations: 10,
    	kTimeStep: 1/60,
    	kGravity: 20,
    	kFriction: 0.3
    });
    
  • Library

  • 
    /* =======================================================
     *  ---- Rigid bodies physics engine ----
     * script: Gerard Ferrandez - 28 April 2013
     * last update: Nov 3, 2013
     * ------------------------------------------------------
     * Adapted from a C# demo by Paul Firth
     * http://www.wildbunny.co.uk/blog/2011/03/25/speculative-contacts-an-continuous-collision-engine-approach-part-1/
     * ------------------------------------------------------
     * JavaScript code released under the MIT license
     * http://www.dhteumeuleu.com/LICENSE.html
     * ======================================================= */
    
    "use strict";
    
    var ge1doot = ge1doot || {};
    
    ge1doot.PHY2D = function() {
    	
    	// closure variables
    	
    	var 
    	ctx, pointer, drag,
    	objects   = [],
    	contacts  = [],
    	contactsI = 0, numIterations, kTimeStep, kGravity, kFriction,
    	DEBUG = false, EPSILON = 0.0001;
    
    	// instantiate Vector library
    	
    	var V2 = ge1doot.V2();
    	
    	// ==== BroadPhase 'Seep and Prune' 1D Constructor ====
    	
    	var SAP = {
    	
    		axis: [],
    		pairs: [],
    		
    		// sweep point constructor
    		
    		SweepPoint: function (body, index) {
    			this.body    = body;
    			this.AABB    = body.motionBounds;
    			this.axis    = body.motionBounds.axis;
    			this.index   = index;
    			this.invMass = body.invMass;
    		},
    		
    		// pair Constructor
    		
    		Pair : function (A, B) {
    			this.A    = A;
    			this.B    = B;			
    		},
    		
    		// add left/right AABB's vectors
    		
    		add: function (rb) {
    			this.axis.push(new this.SweepPoint(rb, 0));
    			this.axis.push(new this.SweepPoint(rb, 1));
    		},
    		
    		// remove object from SAP
    		
    		del: function (rb) {
    			
    			// clean up axis
    			
    			var k = this.axis.length;
    			while (k--) {
    				var p = this.axis[k];
    				if (p.body === rb) {
    					this.axis.splice(k, 1);
    				}
    			}
    			
    			// clean up broad pairs
    			
    			var k = this.pairs.length;
    			while (k--) {
    				var p = this.pairs[k];
    				if (p.A === rb.motionBounds || p.B === rb.motionBounds) {
    					this.pairs.splice(k, 1);
    				}
    			}
    		},
    		
    		// delete A-B pair
    		
    		deletePair: function (A, B) {
    			
    			var k = this.pairs.length;
    			while (k--) {
    				var p = this.pairs[k];
    				if ((p.A === A && p.B === B) || (p.A === B && p.B === A)) {
    					this.pairs.splice(k, 1);
    					break;
    				}
    			}
    			
    		},
    		
    		// 1D Sweep And Prune
    		
    		collide: function () {
    			
    			var i, j, keyelement, key, swapper;
    			
    			// persistent insertion sort
    			
    			for (var j = 1, l = this.axis.length; j < l; j++) {
    				
    				keyelement = this.axis[j];
    				key = keyelement.axis[keyelement.index];
    				i = j - 1;
    				swapper = this.axis[i];
    				
    				while (i >= 0 && swapper.axis[swapper.index] > key) {
    					
    					// start X overlap
    					
    					if (keyelement.index === 0 && swapper.index === 1) {
    						if (swapper.invMass !== 0 || keyelement.invMass !== 0) {
    							this.pairs.push(
    								new this.Pair(swapper.AABB, keyelement.AABB)
    							);
    						}
    					}
    					
    					// stop X overlap
    					
    					if (keyelement.index === 1 && swapper.index === 0) {
    						this.deletePair(swapper.AABB, keyelement.AABB);
    					}
    					
    					this.axis[i + 1] = swapper;
    					swapper = this.axis[--i];
    				}
    				
    				this.axis[i + 1] = keyelement;
    			}
    			
    			// AABB overlaps
    			
    			contactsI = 0;
    			var k = this.pairs.length;
    			while (k--) {
    				var p = this.pairs[k], A = p.A, B = p.B;
    				if ( A.overlap(B) ) {
    					A.body.contact(B.body);
    				}
    			}
    		}
    		
    	};
    	
    	// ==== AABB Constructor ====
    	
    	var AABB = function (body) {
    		this.body        = body;
    		this.center      = V2.vector();
    		this.halfExtends = V2.vector();
    		this.axis        = V2.vector();
    	}
    
    	AABB.prototype.min = V2.vector();
    	AABB.prototype.max = V2.vector();
    
    	AABB.prototype.reset = function () {
    		this.min[0] =  100000;
    		this.min[1] =  100000;
    		this.max[0] = -100000;
    		this.max[1] = -100000;
    	}
    
    	AABB.prototype.minmax = function (a, b) {
    		this.min.min(a, b);
    		this.max.max(a, b);
    	}
    
    	AABB.prototype.save = function () {
    		this.center.add(this.min, this.max).scaleSelf(0.5);
    		this.halfExtends.sub(this.max, this.min).scaleSelf(0.5);
    		this.axis[0] = Math.floor(this.center[0] - this.halfExtends[0]);
    		this.axis[1] = Math.floor(this.center[0] + this.halfExtends[0]);
    	
    		if (DEBUG) {
    			ctx.beginPath();
    			ctx.moveTo(this.center[0] - this.halfExtends[0], this.center[1] - this.halfExtends[1]);
    			ctx.lineTo(this.center[0] + this.halfExtends[0], this.center[1] - this.halfExtends[1]);
    			ctx.lineTo(this.center[0] + this.halfExtends[0], this.center[1] + this.halfExtends[1]);
    			ctx.lineTo(this.center[0] - this.halfExtends[0], this.center[1] + this.halfExtends[1]);
    			ctx.closePath();
    			ctx.fillStyle = "rgba(255,0,0,0.5)";
    			ctx.fill();
    		}
    	
    	}
    
    	AABB.prototype.overlap = function(that) {
    	
    		// Y axis only (X axis handled during SAP phase)
    		
    		return (
    			Math.abs(
    				that.center[1] - this.center[1]
    			) - (
    				this.halfExtends[1] + that.halfExtends[1]
    			) < 0
    		);
    	}
    	
    	// ==== Feature Pair Constructor ====
    	
    	var FeaturePair = function () {
    		this.dist       = 0.0;
    		this.closestI   = 0.0;
    		this.edge       = 0.0;
    		this.fpc        = 0.0;
    		//this.centreDist = 0.0;
    	}
    
    	FeaturePair.prototype.set = function (dist, closestI, edge, fpc/*, centreDist*/) {
    		this.dist       = dist;
    		this.closestI   = closestI;
    		this.edge       = edge;
    		this.fpc        = fpc;
    	}
    	
    	// ==== Rigid Body Constructor ====
    	
    	var RigidBody = function (img, x, y, w, h, vertrices, invMass, angularVel, angle) {
    		
    		this.img             = img;
    		this.w               = w;
    		this.h               = h;
    		this.vel             = V2.vector();
    		this.pos             = V2.vector(x, y);
    		this.next            = V2.vector();
    		this.angularVel      = angularVel || 0;
    		this.invMass         = invMass || 0;
    		this.angle           = angle || 0;
    		this.matrix          = V2.matrix();
    		this.matrixNextFrame = V2.matrix();
    		this.motionBounds    = new AABB(this);
    		this.drag            = false;
    		this.vCount          = Math.floor(vertrices.length / 2);
    		this.static          = false;
    		
    		// add object to SAP axis
    		SAP.add(this);
    		
    		// form local space points
    		this.localSpacePoints = V2.vectorsArray(this.vCount, vertrices, w * 0.5, h * 0.5);
    		
    		// and local space normals
    		this.localSpaceNormals = V2.vectorsArray(this.vCount);
    		for (var i = 0; i < this.vCount; i++ ) {
    			this.localSpaceNormals[i].normal(
    				this.localSpacePoints[(i + 1) % this.vCount],
    				this.localSpacePoints[i]
    			);	
    		}
    		
    		// world points
    		this.worldSpaceNormals = V2.vectorsArray(this.vCount);
    		this.worldSpacePoints  = V2.vectorsArray(this.vCount);
    		
    		// calculate inverse inertia tensor
    		this.invI = (invMass > 0) ? 1 / ((1 / invMass) * (w * w + h * h) / 3) : 0
    		
    		// contact points
    		this.c1 = V2.vector();
    		this.c0 = V2.vector();
    
    	}
    
    	RigidBody.prototype.empty           = V2.vector();
    	RigidBody.prototype.mostSeparated   = new FeaturePair();
    	RigidBody.prototype.mostPenetrating = new FeaturePair();
    	
    	RigidBody.prototype.getSupportVertices = function (that, wsN) {
    		
    		// rotate into RigidBody space
    		var closestI = -1, closestD = -100000,
    		v = V2.rotateIntoSpaceOf(that.matrix, wsN);
    		
    		// support
    		for (var i = 0; i < that.vCount; i++) {
    			var d = v.dot(that.localSpacePoints[i]);
    			if (d > closestD) {
    				closestD = d;
    				closestI = i;
    			}
    		}
    		return closestI;
    		
    	}
    	
    	RigidBody.prototype.featurePairJudgement = function (that, fpc) {
    		
    		var wsN, closestI, closest, mfp0, mfp1, dist, centreDist;
    		
    		for (var edge = 0; edge < this.vCount; edge++) {
    			
    			// get support vertices
    			wsN = this.worldSpaceNormals[edge];
    			closestI = this.getSupportVertices(that, wsN);
    			    
    			// form point on plane of minkowski face
    			closest = that.worldSpacePoints[closestI];
    			mfp0 = V2.sub(closest, this.worldSpacePoints[edge]); 			
    			    
    			// distance from origin to face	
    			dist = mfp0.dot(wsN);
    			
    			if (dist > 0) {
    				
    				// separating axis, but we want to track closest points anyway
    				
    				// recompute distance to clamped edge
    				mfp1 = V2.sub(closest, this.worldSpacePoints[(edge + 1) % this.vCount]);
    				this.projectPointOntoEdge(this.empty, false, mfp0, mfp1);
    				
    				// recompute distance
    				dist = this.c0.lenSqr();
    				
    				if (dist < this.mostSeparated.dist) {
    					this.mostSeparated.set(dist, closestI, edge, fpc);
    				}
    				
    			} else {
    				
    				// penetration
    				if (dist > this.mostPenetrating.dist) {
    					this.mostPenetrating.set(dist, closestI, edge, fpc);
    				}
    				
    			}
    		}
    	}
    
    	RigidBody.prototype.projectPointOntoEdge = function (p0, p1, e0, e1) {
    		
    		var e10, l, t;
    		
    		e10 = V2.sub(e1, e0);
    		l = e10.lenSqr() + EPSILON;
    		
    		// time along edge - c0
    		t = V2.clamp(e10.dot(V2.sub(p0, e0)) / l, 0, 1);
    		
    		// form point
    		this.c0.scale(e10, t).addSelf(e0);
    		
    		if (p1) {
    			// time along edge - c1
    			t = V2.clamp(e10.dot(V2.sub(p1, e0)) / l, 0, 1);
    			// form point
    			this.c1.scale(e10, t).addSelf(e0);
    		}
    		
    	}
    	
    	RigidBody.prototype.motionAABB = function () {
    		
    		this.motionBounds.reset();
    		
    		// loop through all points
    		for (var i = 0; i < this.vCount; i++ ) {
    			this.motionBounds.minmax(
    				this.worldSpacePoints[i].transformBy(this.matrix, this.localSpacePoints[i]),
    				this.next.transformBy(this.matrixNextFrame, this.localSpacePoints[i])
    			);
    			// world normals
    			this.worldSpaceNormals[i].rotateBy(this.matrix, this.localSpaceNormals[i]);
    		}
    		
    		// save bounding box
    		this.motionBounds.save();
    	}
    
    	RigidBody.prototype.integrate = function () {
    
    		if (this.drag) {
    			// dragging object
    			this.vel[0] = (pointer.X - this.pos[0]) * 10;
    			this.vel[1] = (pointer.Y - this.pos[1]) * 10;
    		} else {
    			// gravity
    			if (this.invMass > 0) this.vel[1] += kGravity;
    		}
    		
    		// update position
    		var v = V2.scale(this.vel, kTimeStep);
    		this.pos.addSelf(v);
    		this.angle += this.angularVel * kTimeStep;
    		
    		// set transform matrix
    		this.matrix.setMatrix(this.angle, this.pos);
    		this.matrixNextFrame.setMatrix(
    			this.angle + this.angularVel * kTimeStep, 
    			v.addSelf(this.pos)
    		);
    		
    		// compute motion AABB
    		if (!this.static) this.motionAABB();
    		else {
    			if (this.invMass === 0) {
    				this.static = true;
    				this.motionAABB();
    			}
    		}
    		
    	}
    	
    	RigidBody.prototype.contact = function (that) {
    
    		var face, vertex, vertexRect, faceRect, fp, va, vb, vc, n0, n1, wsN, wdV0, wdV1, wsV0, wsV1;
    		
    		// generate contacts for this pair
    		this.mostSeparated.set(100000, -1, -1, 0, 100000);
    		this.mostPenetrating.set(-100000, -1, -1, 0, 100000);
    		
    		// face of A, vertices of B
    		this.featurePairJudgement(that, 2);
    		
    		// faces of B, vertices of A
    		that.featurePairJudgement(this, 1);
    		
    		if (this.mostSeparated.dist > 0 && this.mostSeparated.fpc !== 0) {
    			
    			// objects are separated
    			face = this.mostSeparated.edge;
    			vertex = this.mostSeparated.closestI;
    			fp = this.mostSeparated.fpc;
    			
    		} else if (this.mostPenetrating.dist <= 0) {
    			
    			// objects are penetrating
    			face = this.mostPenetrating.edge;
    			vertex = this.mostPenetrating.closestI;
    			fp = this.mostPenetrating.fpc;
    		}
    		
    		if (fp === 1) vertexRect = this, faceRect = that;
    		else vertexRect = that, faceRect = this;
    			
    		// world space vertex
    		wsN = faceRect.worldSpaceNormals[face];
    		
    		// other vertex adjacent which makes most parallel normal with the collision normal
    		va = vertexRect.worldSpacePoints[(vertex - 1 + vertexRect.vCount) % vertexRect.vCount];
    		vb = vertexRect.worldSpacePoints[vertex];
    		vc = vertexRect.worldSpacePoints[(vertex + 1) % vertexRect.vCount];
    		
    		if (
    			V2.normal(vb, va).dot(wsN) < V2.normal(vc, vb).dot(wsN)
    		) {
    			wdV0 = va;
    			wdV1 = vb;
    		} else {
    			wdV0 = vb;
    			wdV1 = vc;
    		}
    		
    		// world space edge
    		wsV0 = faceRect.worldSpacePoints[face];
    		wsV1 = faceRect.worldSpacePoints[(face + 1) % faceRect.vCount];
    		
    		
    		// form contact
    		if (fp === 1) {
    		
    			// project vertex onto edge
    			this.projectPointOntoEdge(wsV0, wsV1, wdV0, wdV1);
    			that.projectPointOntoEdge(wdV1, wdV0, wsV0, wsV1);
    			n0 = -wsN[0];
    			n1 = -wsN[1];
    			
    		} else {
    		
    			this.projectPointOntoEdge(wdV1, wdV0, wsV0, wsV1);
    			that.projectPointOntoEdge(wsV0, wsV1, wdV0, wdV1);
    			n0 = wsN[0];
    			n1 = wsN[1];
    			
    		}
    		
    		// first contact
    		if (!contacts[contactsI]) contacts[contactsI] = new Contact();
    		contacts[contactsI++].set(this, that, this.c0, that.c0, n0, n1);
    		
    		// second contact
    		if (!contacts[contactsI]) contacts[contactsI] = new Contact();
    		contacts[contactsI++].set(this, that, this.c1, that.c1, n0, n1);
    	
    		if (DEBUG) {
    			ctx.strokeStyle = "rgb(0,255,0)";
    			ctx.beginPath();
    			ctx.moveTo(this.c0[0], this.c0[1]);
    			ctx.lineTo(that.c0[0], that.c0[1]);
    			ctx.stroke();
    			
    			ctx.strokeStyle = "rgb(0,0,255)";
    			ctx.beginPath();
    			ctx.moveTo(this.c1[0], this.c1[1]);
    			ctx.lineTo(that.c1[0], that.c1[1]);
    			ctx.stroke();
    		}
    	
    	}
    	
    	RigidBody.prototype.del = function () {
    		var k = objects.length;
    		while (k--) {
    			if (objects[k] === this) {
    				SAP.del(this);
    				objects.splice(k, 1);
    				break;
    			}
    		}
    	}
    	
    	RigidBody.prototype.draw = function () {
    		
    		if (this.img) {
    		
    			// draw image
    			ctx.save();
    			ctx.translate(this.pos[0], this.pos[1]);
    			ctx.rotate(this.angle);
    			ctx.drawImage(this.img, -this.w * 0.5, -this.h * 0.5, this.w, this.h);
    			ctx.restore();
    			
    			if (pointer.isDown && !drag && this.invMass) {
    				
    				// poly path
    				ctx.beginPath();
    				for (var j = 0; j < this.vCount; j++ ) {
    					var a = this.worldSpacePoints[j];
    					ctx.lineTo(a[0], a[1]);
    				}
    				ctx.closePath();
    				
    				// test point in poly
    				if (ctx.isPointInPath(pointer.X, pointer.Y)) {
    					this.drag = true;
    					drag = true;
    				}
    				
    			}
    		}
    	}
    	
    	// ==== Contact Constructor ====
    	var Contact = function () {
    		
    		this.a           = {};
    		this.b           = {};
    		this.normal      = V2.vector();
    		this.ra          = V2.vector();
    		this.rb          = V2.vector();
    		this.dist        = 0;
    		this.impulseN    = 0;
    		this.impulseT    = 0;
    		this.invDenom    = 0;
    		this.invDenomTan = 0;
    		
    	}
    	
    	Contact.prototype.set = function (A, B, pa, pb, n0, n1) {
    		
    		var ran, rbn, pn;
    		
    		this.a = A;
    		this.b = B;
    		this.normal[0] = n0;
    		this.normal[1] = n1;
    		this.dist = V2.sub(pb, pa).dot(this.normal);
    		this.impulseN = 0;
    		this.impulseT = 0;
    		
    		// calculate radius arms
    		this.ra.sub(pa, A.pos).perpSelf();
    		this.rb.sub(pb, B.pos).perpSelf();
    		
    		// compute denominator in impulse equation
    		ran = this.ra.dot(this.normal);
    		rbn = this.rb.dot(this.normal);
    		pn  = V2.perp(this.normal);
    		this.invDenom  = 1 / (A.invMass + B.invMass + (ran * ran * A.invI) + (rbn * rbn * B.invI));
    		ran = this.ra.dot(pn);
    		rbn = this.rb.dot(pn);
    		this.invDenomTan = 1 / (A.invMass + B.invMass + (ran * ran * A.invI) + (rbn * rbn * B.invI));
    	}
    	
    	Contact.prototype.applyImpulse = function (imp) {
    		
    		// linear
    		this.a.vel.addMultSelf(imp, this.a.invMass);
    		this.b.vel.subMultSelf(imp, this.b.invMass);
    		
    		// angular
    		this.a.angularVel += imp.dot(this.ra) * this.a.invI;
    		this.b.angularVel -= imp.dot(this.rb) * this.b.invI;
    	}
    
    	Contact.prototype.solve = function () {
    		
    		var p, dv, newImpulse, absMag;
    		
    		// get all of relative normal velocity
    		dv = V2.sub(
    			V2.scale(this.rb, this.b.angularVel).addSelf(this.b.vel),
    			V2.scale(this.ra, this.a.angularVel).addSelf(this.a.vel)
    		);
    		
    		// accumulated impulse
    		newImpulse = (dv.dot(this.normal) + this.dist / kTimeStep) * this.invDenom + this.impulseN;
    		
    		// push only
    		if (newImpulse > 0) newImpulse = 0;
    		
    		// apply impulse
    		this.applyImpulse(V2.scale(this.normal, newImpulse - this.impulseN));
    		this.impulseN = newImpulse;
    		
    		// friction
    		absMag = Math.abs( this.impulseN ) * kFriction;
    		p = V2.perp(this.normal);
    		newImpulse = V2.clamp(dv.dot(p) * this.invDenomTan + this.impulseT, -absMag, absMag);
    		
    		// apply friction impulse
    		this.applyImpulse(p.scaleSelf(newImpulse - this.impulseT));
    		this.impulseT = newImpulse;
    		
    	}
    	
    	
    	// ======== API ========
    	
    	
    	this.init = function (data, c, p) {
    		ctx = c;
    		pointer = p;
    		numIterations = data.numIterations || 4;
    		kTimeStep = data.kTimeStep || 1/60;
    		kGravity = data.kGravity || 0;
    		kFriction = data.kFriction || 0;
    	}
    	
    	
    	// ---- rendering loop ----
    	
    	this.render = function () {
    		
    		// collisions
    		SAP.collide();
    		
    		// solve
    		for (var j = 0; j < numIterations; j++) {
    			var k = contactsI;
    			while (k--) contacts[k].solve();
    		}
    		
    		// integrate
    		var k = objects.length;
    		while (k--) {
    			var rb = objects[k];
    			rb.integrate();
    			rb.draw();
    		}
    	}
    	
    	// ---- delete all static objects ----
    	
    	this.deleteStatic = function () {
    		var k = objects.length;
    		while (k--) {
    			var p = objects[k];
    			if (!p.invMass) {
    				SAP.del(p);
    				objects.splice(k, 1);
    			}
    		}
    	}
    	
    	// ---- create new rectangle ----
    	
    	this.rectangle = function (img, x, y, w, h, mass, angularVel, angle) {
    		var invMass = mass ? 1 / mass : 0;
    		var rb = new RigidBody(img, x, y, w, h, [1,-1,-1,-1,-1,1,1,1], invMass, angularVel, angle);
    		objects.push(rb);
    		return rb;
    	}
    	
    	// ---- create new triangle ----
    	
    	this.triangle = function (img, x, y, w, h, mass, angularVel, angle) {
    		var invMass = mass ? 1 / mass : 0;
    		var rb = new RigidBody(img, x, y, w, h, [0,-1,-1,1,1,1], invMass, angularVel, angle);
    		objects.push(rb);
    		return rb;
    	}
    	
    	// ---- create new line ----
    	
    	this.line = function (img, x, y, w, h, mass, angularVel, angle) {
    		var invMass = mass ? 1 / mass : 0;
    		var rb = new RigidBody(img, x, y, w, h, [1,0,-1,0], invMass, angularVel, angle);
    		objects.push(rb);
    		return rb;
    	}
    	
    	// ---- objects ----
    	
    	this.objects = objects;
    
    	this.drag = function (d) {
    		var k = objects.length;
    		while (k--) objects[k].drag = d;
    		drag = d;
    	}
    	
    	// delete object
    	
    	this.deleteObject = function (index) {
    		
    		var rb = objects[index];
    		
    		if (rb) {
    	
    			// clean SAP
    			SAP.del(rb);
    			
    			// delete object
    			objects.splice(index, 1);
    			
    		}
    	}
    	
    	// ---- return namespace reference ----
    	
    	return this;
    	
    }
  • Library

  • 
    ////////////////////////////////////////////////////////////
    // // ==== Simple 2D Vector Class ====
    // @author Gerard Ferrandez / http://www.dhteumeuleu.com/
    // last update: Oct 24, 2013
    // Released under the MIT license
    // http://www.dhteumeuleu.com/LICENSE.html
    ////////////////////////////////////////////////////////////
    
    "use strict";
    
    var ge1doot = ge1doot || {};
    
    ge1doot.V2 = function () {
    
    /*
          ##,                                   ,##
          '##,                                 ,##'
           '##                                 ##'
            ##               __,               ##
            ##          __.-'   \              ##
            ##    ___.-'__.--'\ |              ##,
            ## .-' .-, (      | |        _     '##
            ##/ / /""=\ \     | |       / \     ##,
            '#| |_\   / /     | |      /   \    '##
            / `-` 0 0 '-'`\   | |      | |  \   ,##
            \_,   (__)  ,_/  / /       |  \  \  ##'
             / /    \   \\  / /        |  |\  \ ## __
            | /`.__.-'-._)|/ /         |  | \  \##`__)
            \        ^    / /          |  |  | v## '--.
             '._    '-'_.' / _.----.   |  |  l ,##  (_,'
              '##'-,  ` `"""/       `'/|  | / ,##--,  )
               '#/`        `         '    |'  ##'   `"
                |                         /\_/#'
                |              __.  .-,_.;###`
               _|___/_..---'''`   _/  (###'
           .-'`   ____,...---""```     `._
          (   --''        __,.,---.    ',_)
           `.,___,..---'``  / /    \     '._
                |  |       ( (      `.  '-._)
                |  /        \ \      \'-._)
                | |          \ \      `"`
                | |           \ \
                | |    .-,     ) |
                | |   ( (     / /
                | |    \ '---' /
                /  \    `-----`
               | , /
               |(_/\-,
               \  ,_`)
                `-._)
    	*/
    
    
    	var FloatArray = Float32Array || Array;
    
    	if (FloatArray == Array) {
    	
    		this.vector = function (x, y) {
    			return [x || 0, y || 0];
    		}
    		
    		this.matrix = function () {
    			return [
    				0, 0, // row0
    				0, 0, // row1
    				0, 0  // pos
    			];
    		}
    		
    		FloatArray.prototype.set = function (v) {
    			for (var i = 0, l = v.length; i < l; i++) this[i] = v[i];
    			return this;
    		}
    		
    	} else {
    	
    		this.vector = function (x, y) {
    			return new FloatArray([x || 0, y || 0]);
    		}
    		
    		this.matrix = function () {
    			return new FloatArray([
    				0, 0, // row0
    				0, 0, // row1
    				0, 0  // pos
    			]);
    		}
    	}
    	
    	// create an array of vector arrays
    	
    	this.vectorsArray = function (n, values, scaleX, scaleY) {
    		var buffer = new Array(n);
    		for (var i = 0; i < n; i++) {
    			buffer[i] = this.vector(
    				values != undefined ? (values[i * 2] * (scaleX || 1)) : 0,
    				values != undefined ? (values[i * 2 + 1] * (scaleY || 1)) : 0
    			);
    		}
    		return buffer;
    	}
    	
    	// V2 stacked results
    	
    	this.result = this.vectorsArray(16);
    	this.rid = 0;
    	
    	// inverse
    	
    	this.inv = function (a) {
    		var result = this.result[this.rid++ % 16];
    		result[0] = -a[0];
    		result[1] = -a[1];
    		return result;
    	};
    
    	// multiply scalar
    	
    	this.scale = function (a, s) {
    		var result = this.result[this.rid++ % 16];
    		result[0] = a[0] * s;
    		result[1] = a[1] * s;
    		return result;
    	}
    	
    	
    	// normal vector
    	
    	this.normal = function (a, b) {
    		var result = this.result[this.rid++ % 16];
    		var x = a[0] - b[0],
    			y = a[1] - b[1],
    			len = Math.sqrt(x * x + y * y);
    		result[0] = -y / len;
    		result[1] = x / len;
    		return result;
    	}
    	
    	
    	// perpendicular
    	
    	this.perp = function (a) {
    		var result = this.result[this.rid++ % 16];
    		result[0] = -a[1];
    		result[1] = a[0];
    		return result;
    	}
    	
    	
    	// subtraction
    	
    	this.sub = function (a, b) {
    		var result = this.result[this.rid++ % 16];
    		result[0] = a[0] - b[0];
    		result[1] = a[1] - b[1];
    		return result;
    	}
    	
    	
    	// set matrix
    	
    	FloatArray.prototype.setMatrix = function (a, p) {
    		this[0] = Math.cos(a);
    		this[1] = Math.sin(a);
    		this[2] = -this[1];
    		this[3] =  this[0];
    		this[4] = p[0];
    		this[5] = p[1];
    		return this;
    	}
    	
    	// clamp
    	
    	this.clamp = function (v, min, max) {
    		if (v > max) v = max; else if (v < min) v = min;
    		return v;
    	}
    	
    	// rotate into space of
    	
    	this.rotateIntoSpaceOf = function (m, a) {
    		var dx = -a[0], dy = -a[1],
    		result = this.result[this.rid++ % 16];
    		result[0] = dx * m[0] + dy * m[1];
    		result[1] = dx * m[2] + dy * m[3];
    		return result;
    	}
    	
    	// ---- Array prototype Monkey patching (for chaining..) ----
    	
    	FloatArray.prototype.copy = function (a) {
    		this[0] = a[0];
    		this[1] = a[1];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.normal = function (a, b) {
    		var x = a[0] - b[0],
    			y = a[1] - b[1],
    			len = Math.sqrt(x * x + y * y);
    		this[0] = -y / len;
    		this[1] = x / len;
    		return this;
    	}
    	
    	
    	FloatArray.prototype.inv = function (a) {
    		this[0] = -a[0];
    		this[1] = -a[1];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.invSelf = function () {
    		this[0] = -this[0];
    		this[1] = -this[1];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.dot = function (a) {
    		return this[0] * a[0] + this[1] * a[1];
    	}
    	
    	
    	FloatArray.prototype.add = function (a, b) {
    		this[0] = a[0] + b[0];
    		this[1] = a[1] + b[1];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.addSelf = function (a) {
    		this[0] += a[0];
    		this[1] += a[1];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.sub = function (a, b) {
    		this[0] = a[0] - b[0];
    		this[1] = a[1] - b[1];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.subSelf = function (a) {
    		this[0] -= a[0];
    		this[1] -= a[1];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.subMultSelf = function (a, s) {
    		this[0] -= a[0] * s;
    		this[1] -= a[1] * s;
    		return this;
    	}
    	
    	
    	FloatArray.prototype.scale = function (a, s) {
    		this[0] = a[0] * s;
    		this[1] = a[1] * s;
    		return this;
    	}
    	
    	
    	FloatArray.prototype.addMultSelf = function (a, s) {
    		this[0] += a[0] * s;
    		this[1] += a[1] * s;
    		return this;
    	}
    	
    	
    	FloatArray.prototype.scaleSelf = function (s) {
    		this[0] *= s;
    		this[1] *= s;
    		return this;
    	}
    	
    	
    	FloatArray.prototype.perp = function (a) {
    		this[0] = -a[1];
    		this[1] = a[0];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.perpSelf = function () {
    		var a = this[0];
    		this[0] = -this[1];
    		this[1] = a;
    		return this;
    	}
    	
    	
    	FloatArray.prototype.lenSqr = function () {
    		return this[0] * this[0] + this[1] * this[1];
    	}
    	
    	
    	FloatArray.prototype.len = function () {
    		return Math.sqrt(this[0] * this[0] + this[1] * this[1]);
    	}
    	
    	
    	FloatArray.prototype.rotateIntoSpaceOf = function (m, a) {
    		var dx = -a[0], dy = -a[1];
    		this[0] = dx * m[0] + dy * m[1];
    		this[1] = dx * m[2] + dy * m[3];
    		return this;
    	}
    	
    	
    	FloatArray.prototype.rotateBy = function (m, a) {
    		var dx = a[0], dy = a[1];
    		this[0] = m[0] * dx + m[2] * dy;
    		this[1] = m[1] * dx + m[3] * dy;
    		return this;
    	}
    	
    	
    	FloatArray.prototype.transformBy = function (m, a) {
    		var dx = a[0], dy = a[1];
    		this[0] = m[0] * dx + m[2] * dy + m[4];
    		this[1] = m[1] * dx + m[3] * dy + m[5];
    		return this;
    	}
    
    	
    	FloatArray.prototype.min = function (a, b) {
    		var ax = a[0], ay = a[1], bx = b[0], by = b[1];
    		if (ax < this[0]) this[0] = ax;
    		if (ay < this[1]) this[1] = ay;
    		if (bx < this[0]) this[0] = bx;
    		if (by < this[1]) this[1] = by;
    		return this;
    	}
    	
    	
    	FloatArray.prototype.max = function (a, b) {
    		var ax = a[0], ay = a[1], bx = b[0], by = b[1];
    		if (ax > this[0]) this[0] = ax;
    		if (ay > this[1]) this[1] = ay;
    		if (bx > this[0]) this[0] = bx;
    		if (by > this[1]) this[1] = by;
    		return this;
    	}
    	
    	return this;
    	
    }
    
    © www.dhteumeuleu.com 2004-2015