diff --git a/README.md b/README.md index 88db2db8..36aeea06 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ Updates: * Updated display/fullscreen example to reflect new full screen change. * Loads of updates to the TypeScript definitions files - games fully compile now and lots of missing classes added :) * Removed 'null parent' check from Group constructor. Will parent to game.world only if parent value is undefined. +* The tutorials have now been translated into Spanish - thanks feiss :) +* separateY updated to re-implement the 'riding platforms' special condition (thanks cocoademon) Bug Fixes: diff --git a/examples/wip/moveToPointer.js b/examples/wip/moveToPointer.js new file mode 100644 index 00000000..865825c9 --- /dev/null +++ b/examples/wip/moveToPointer.js @@ -0,0 +1,38 @@ + +var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create }); + +function preload() { + + game.load.image('arrow', 'assets/sprites/arrow.png'); + +} + +var sprite; +var tween; + +function create() { + + sprite = game.add.sprite(32, 32, 'arrow'); + + sprite.anchor.setTo(0.5, 0.5); + + game.input.onDown.add(moveSprite, this); + +} + +function moveSprite (pointer) { + + if (tween && tween.isRunning) + { + tween.stop(); + } + + sprite.rotation = game.physics.angleToPointer(sprite, pointer); + + // 300 = 300 pixels per second = the speed the sprite will move at, regardless of the distance it has to travel + var duration = (game.physics.distanceToPointer(sprite, pointer) / 300) * 1000; + + tween = game.add.tween(sprite).to({ x: pointer.x, y: pointer.y }, duration, Phaser.Easing.Linear.None, true); + +} + diff --git a/src/physics/advanced/collision/Broadphase.js b/src/physics/advanced/collision/Broadphase.js index b0d6129c..2628e45b 100644 --- a/src/physics/advanced/collision/Broadphase.js +++ b/src/physics/advanced/collision/Broadphase.js @@ -1,6 +1,4 @@ var vec2 = require('../math/vec2') -, Nearphase = require('./Nearphase') -, Shape = require('./../shapes/Shape') module.exports = Broadphase; @@ -10,6 +8,12 @@ module.exports = Broadphase; * @constructor */ function Broadphase(){ + + /** + * The resulting overlapping pairs. Will be filled with results during .getCollisionPairs(). + * @property result + * @type {Array} + */ this.result = []; }; @@ -23,10 +27,7 @@ Broadphase.prototype.getCollisionPairs = function(world){ throw new Error("getCollisionPairs must be implemented in a subclass!"); }; -// Temp things -var dist = vec2.create(), - worldNormal = vec2.create(), - yAxis = vec2.fromValues(0,1); +var dist = vec2.create(); /** * Check whether the bounding radius of two bodies overlap. diff --git a/src/physics/advanced/collision/GridBroadphase.js b/src/physics/advanced/collision/GridBroadphase.js index 747d78d6..d0a7172b 100644 --- a/src/physics/advanced/collision/GridBroadphase.js +++ b/src/physics/advanced/collision/GridBroadphase.js @@ -104,7 +104,7 @@ GridBroadphase.prototype.getCollisionPairs = function(world){ } } else if(si instanceof Plane){ // Put in all bins for now - if(bi.angle === 0){ + if(bi.angle == 0){ var y = bi.position[1]; for(var j=0; j!==Nbins && ymin+binsizeY*(j-1) id2){ + var tmp = id1; + id1 = id2; + id2 = tmp; + } + return !!this.collidingBodiesLastStep[id1 + " " + id2]; }; /** * Throws away the old equatons and gets ready to create new * @method reset */ -Nearphase.prototype.reset = function(){ +Narrowphase.prototype.reset = function(){ + + // Save the colliding bodies data + for(var key in this.collidingBodiesLastStep) + delete this.collidingBodiesLastStep[key]; + for(var i=0; i!==this.contactEquations.length; i++){ + var eq = this.contactEquations[i], + id1 = eq.bi.id, + id2 = eq.bj.id; + if(id1 > id2){ + var tmp = id1; + id1 = id2; + id2 = tmp; + } + this.collidingBodiesLastStep[id1 + " " + id2] = true; + } + if(this.reuseObjects){ var ce = this.contactEquations, fe = this.frictionEquations, @@ -68,10 +146,14 @@ Nearphase.prototype.reset = function(){ * @param {Body} bodyB * @return {ContactEquation} */ -Nearphase.prototype.createContactEquation = function(bodyA,bodyB){ +Narrowphase.prototype.createContactEquation = function(bodyA,bodyB,shapeA,shapeB){ var c = this.reusableContactEquations.length ? this.reusableContactEquations.pop() : new ContactEquation(bodyA,bodyB); c.bi = bodyA; c.bj = bodyB; + c.shapeA = shapeA; + c.shapeB = shapeB; + c.restitution = this.restitution; + c.firstImpact = !this.collidedLastStep(bodyA,bodyB); return c; }; @@ -82,11 +164,14 @@ Nearphase.prototype.createContactEquation = function(bodyA,bodyB){ * @param {Body} bodyB * @return {FrictionEquation} */ -Nearphase.prototype.createFrictionEquation = function(bodyA,bodyB){ +Narrowphase.prototype.createFrictionEquation = function(bodyA,bodyB,shapeA,shapeB){ var c = this.reusableFrictionEquations.length ? this.reusableFrictionEquations.pop() : new FrictionEquation(bodyA,bodyB); c.bi = bodyA; c.bj = bodyB; + c.shapeA = shapeA; + c.shapeB = shapeB; c.setSlipForce(this.slipForce); + c.frictionCoefficient = this.frictionCoefficient; return c; }; @@ -96,16 +181,17 @@ Nearphase.prototype.createFrictionEquation = function(bodyA,bodyB){ * @param {ContactEquation} contactEquation * @return {FrictionEquation} */ -Nearphase.prototype.createFrictionFromContact = function(c){ +Narrowphase.prototype.createFrictionFromContact = function(c){ var eq = this.createFrictionEquation(c.bi,c.bj); vec2.copy(eq.ri, c.ri); vec2.copy(eq.rj, c.rj); vec2.rotate(eq.t, c.ni, -Math.PI / 2); + eq.contactEquation = c; return eq; } /** - * Plane/line nearphase + * Plane/line Narrowphase * @method planeLine * @param {Body} bi * @param {Plane} si @@ -116,7 +202,8 @@ Nearphase.prototype.createFrictionFromContact = function(c){ * @param {Array} xj * @param {Number} aj */ -Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){ +Narrowphase.prototype[Shape.PLANE | Shape.LINE] = +Narrowphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){ var lineShape = sj, lineAngle = aj, lineBody = bj, @@ -170,7 +257,7 @@ Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){ if(d < 0){ - var c = this.createContactEquation(planeBody,lineBody); + var c = this.createContactEquation(planeBody,lineBody,si,sj); vec2.copy(c.ni, worldNormal); vec2.normalize(c.ni,c.ni); @@ -197,12 +284,13 @@ Nearphase.prototype.planeLine = function(bi,si,xi,ai, bj,sj,xj,aj){ } }; -Nearphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ +Narrowphase.prototype[Shape.PARTICLE | Shape.CAPSULE] = +Narrowphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ return this.circleLine(bi,si,xi,ai, bj,sj,xj,aj, justTest, sj.radius, 0); }; /** - * Circle/line nearphase + * Circle/line Narrowphase * @method circleLine * @param {Body} bi * @param {Circle} si @@ -216,7 +304,8 @@ Nearphase.prototype.particleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTes * @param {Number} lineRadius Radius to add to the line. Can be used to test Capsules. * @param {Number} circleRadius If set, this value overrides the circle shape radius. */ -Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, lineRadius, circleRadius){ +Narrowphase.prototype[Shape.CIRCLE | Shape.LINE] = +Narrowphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, lineRadius, circleRadius){ var lineShape = sj, lineAngle = aj, lineBody = bj, @@ -293,7 +382,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li if(justTest) return true; - var c = this.createContactEquation(circleBody,lineBody); + var c = this.createContactEquation(circleBody,lineBody,si,sj); vec2.scale(c.ni, orthoDist, -1); vec2.normalize(c.ni, c.ni); @@ -328,7 +417,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li if(justTest) return true; - var c = this.createContactEquation(circleBody,lineBody); + var c = this.createContactEquation(circleBody,lineBody,si,sj); vec2.copy(c.ni, dist); vec2.normalize(c.ni,c.ni); @@ -358,7 +447,7 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li }; /** - * Circle/capsule nearphase + * Circle/capsule Narrowphase * @method circleCapsule * @param {Body} bi * @param {Circle} si @@ -369,12 +458,13 @@ Nearphase.prototype.circleLine = function(bi,si,xi,ai, bj,sj,xj,aj, justTest, li * @param {Array} xj * @param {Number} aj */ -Nearphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ +Narrowphase.prototype[Shape.CIRCLE | Shape.CAPSULE] = +Narrowphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest){ return this.circleLine(bi,si,xi,ai, bj,sj,xj,aj, justTest, sj.radius); }; /** - * Circle/convex nearphase + * Circle/convex Narrowphase * @method circleConvex * @param {Body} bi * @param {Circle} si @@ -385,14 +475,16 @@ Nearphase.prototype.circleCapsule = function(bi,si,xi,ai, bj,sj,xj,aj, justTest) * @param {Array} xj * @param {Number} aj */ -Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){ +Narrowphase.prototype[Shape.CIRCLE | Shape.CONVEX] = +Narrowphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTest, circleRadius){ var convexShape = sj, convexAngle = aj, convexBody = bj, convexOffset = xj, circleOffset = xi, circleBody = bi, - circleShape = si; + circleShape = si, + circleRadius = typeof(circleRadius)=="number" ? circleRadius : circleShape.radius; var worldVertex0 = tmp1, worldVertex1 = tmp2, @@ -404,14 +496,31 @@ Nearphase.prototype.circleConvex = function( bi,si,xi,ai, bj,sj,xj,aj){ orthoDist = tmp8, projectedPoint = tmp9, dist = tmp10, - worldVertex = tmp11; + worldVertex = tmp11, + + closestEdge = -1, + closestEdgeDistance = null, + closestEdgeOrthoDist = tmp12, + closestEdgeProjectedPoint = tmp13, + candidate = tmp14, + candidateDist = tmp15, + minCandidate = tmp16, + + found = false, + minCandidateDistance = Number.MAX_VALUE; var numReported = 0; + // New algorithm: + // 1. Check so center of circle is not inside the polygon. If it is, this wont work... + // 2. For each edge + // 2. 1. Get point on circle that is closest to the edge (scale normal with -radius) + // 2. 2. Check if point is inside. + verts = convexShape.vertices; // Check all edges first - for(var i=0; i 0){ + vec2.sub(candidateDist,worldVertex0,candidate); + var candidateDistance = Math.abs(vec2.dot(candidateDist,worldTangent)); - // Now project the circle onto the edge - vec2.scale(orthoDist, worldTangent, d); - sub(projectedPoint, circleOffset, orthoDist); + /* + // Check distance from the plane spanned by the edge vs the circle + sub(dist, circleOffset, worldVertex0); + var d = dot(dist, worldTangent); + sub(centerDist, worldVertex0, convexOffset); - // Check if the point is within the edge span - var pos = dot(worldEdgeUnit, projectedPoint); - var pos0 = dot(worldEdgeUnit, worldVertex0); - var pos1 = dot(worldEdgeUnit, worldVertex1); + sub(convexToCircle, circleOffset, convexOffset); - if(pos > pos0 && pos < pos1){ - // We got contact! + if(d < circleRadius && dot(centerDist,convexToCircle) > 0){ - var c = this.createContactEquation(circleBody,convexBody); + // Now project the circle onto the edge + vec2.scale(orthoDist, worldTangent, d); + sub(projectedPoint, circleOffset, orthoDist); - vec2.scale(c.ni, orthoDist, -1); - vec2.normalize(c.ni, c.ni); - vec2.scale( c.ri, c.ni, circleShape.radius); + // Check if the point is within the edge span + var pos = dot(worldEdgeUnit, projectedPoint); + var pos0 = dot(worldEdgeUnit, worldVertex0); + var pos1 = dot(worldEdgeUnit, worldVertex1); + + if(pos > pos0 && pos < pos1){ + // We got contact! + + if(justTest) return true; + + if(closestEdgeDistance === null || d*d 0){ + for(var i=0; i= 0){ // Now project the particle onto the edge @@ -594,18 +816,32 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe } } } + */ + + vec2.sub(candidateDist,worldVertex0,particleOffset); + var candidateDistance = Math.abs(vec2.dot(candidateDist,worldTangent)); + + if(candidateDistance < minCandidateDistance){ + minCandidateDistance = candidateDistance; + vec2.scale(closestEdgeProjectedPoint,worldTangent,candidateDistance); + vec2.add(closestEdgeProjectedPoint,closestEdgeProjectedPoint,particleOffset); + vec2.copy(minEdgeNormal,worldTangent); + found = true; + } } - if(closestEdge != -1){ - var c = this.createContactEquation(particleBody,convexBody); + if(found){ + var c = this.createContactEquation(particleBody,convexBody,si,sj); - vec2.copy(c.ni, closestEdgeOrthoDist); + vec2.scale(c.ni, minEdgeNormal, -1); vec2.normalize(c.ni, c.ni); + // Particle has no extent to the contact point vec2.set(c.ri, 0, 0); add(c.ri, c.ri, particleOffset); sub(c.ri, c.ri, particleBody.position); + // From convex center to point sub(c.rj, closestEdgeProjectedPoint, convexOffset); add(c.rj, c.rj, convexOffset); sub(c.rj, c.rj, convexBody.position); @@ -623,7 +859,7 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe }; /** - * Circle/circle nearphase + * Circle/circle Narrowphase * @method circleCircle * @param {Body} bi * @param {Circle} si @@ -634,7 +870,8 @@ Nearphase.prototype.particleConvex = function( bi,si,xi,ai, bj,sj,xj,aj, justTe * @param {Array} xj * @param {Number} aj */ -Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest){ +Narrowphase.prototype[Shape.CIRCLE] = +Narrowphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest){ var bodyA = bi, shapeA = si, offsetA = xi, @@ -651,7 +888,7 @@ Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest if(justTest) return true; - var c = this.createContactEquation(bodyA,bodyB); + var c = this.createContactEquation(bodyA,bodyB,si,sj); sub(c.ni, offsetB, offsetA); vec2.normalize(c.ni,c.ni); @@ -673,26 +910,27 @@ Nearphase.prototype.circleCircle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest }; /** - * Convex/Plane nearphase - * @method convexPlane + * Plane/Convex Narrowphase + * @method planeConvex * @param {Body} bi - * @param {Convex} si + * @param {Plane} si * @param {Array} xi * @param {Number} ai * @param {Body} bj - * @param {Plane} sj + * @param {Convex} sj * @param {Array} xj * @param {Number} aj */ -Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ - var convexBody = bi, - convexOffset = xi, - convexShape = si, - convexAngle = ai, - planeBody = bj, - planeShape = sj, - planeOffset = xj, - planeAngle = aj; +Narrowphase.prototype[Shape.PLANE | Shape.CONVEX] = +Narrowphase.prototype.planeConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){ + var convexBody = bj, + convexOffset = xj, + convexShape = sj, + convexAngle = aj, + planeBody = bi, + planeShape = si, + planeOffset = xi, + planeAngle = ai; var worldVertex = tmp1, worldNormal = tmp2, @@ -701,8 +939,8 @@ Nearphase.prototype.convexPlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ var numReported = 0; vec2.rotate(worldNormal, yAxis, planeAngle); - for(var i=0; i 0) return false; if(justTest) return true; - var c = this.createContactEquation(planeBody,particleBody); + var c = this.createContactEquation(planeBody,particleBody,sj,si); vec2.copy(c.ni, worldNormal); vec2.scale( dist, c.ni, d ); @@ -801,7 +1049,7 @@ Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest }; /** - * Circle/Particle nearphase + * Circle/Particle Narrowphase * @method circleParticle * @param {Body} bi * @param {Circle} si @@ -812,7 +1060,8 @@ Nearphase.prototype.particlePlane = function( bi,si,xi,ai, bj,sj,xj,aj, justTest * @param {Array} xj * @param {Number} aj */ -Nearphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ +Narrowphase.prototype[Shape.CIRCLE | Shape.PARTICLE] = +Narrowphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justTest ){ var circleBody = bi, circleShape = si, circleOffset = xi, @@ -825,7 +1074,7 @@ Nearphase.prototype.circleParticle = function( bi,si,xi,ai, bj,sj,xj,aj, justT if(vec2.squaredLength(dist) > circleShape.radius*circleShape.radius) return false; if(justTest) return true; - var c = this.createContactEquation(circleBody,particleBody); + var c = this.createContactEquation(circleBody,particleBody,si,sj); vec2.copy(c.ni, dist); vec2.normalize(c.ni,c.ni); @@ -850,28 +1099,39 @@ var capsulePlane_tmpCircle = new Circle(1), capsulePlane_tmp1 = vec2.create(), capsulePlane_tmp2 = vec2.create(), capsulePlane_tmp3 = vec2.create(); -Nearphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + +Narrowphase.prototype[Shape.PLANE | Shape.CAPSULE] = +Narrowphase.prototype.planeCapsule = function( bi,si,xi,ai, bj,sj,xj,aj ){ var end1 = capsulePlane_tmp1, end2 = capsulePlane_tmp2, circle = capsulePlane_tmpCircle, dst = capsulePlane_tmp3; // Compute world end positions - vec2.set(end1, -si.length/2, 0); - vec2.rotate(end1,end1,ai); - add(end1,end1,xi); + vec2.set(end1, -sj.length/2, 0); + vec2.rotate(end1,end1,aj); + add(end1,end1,xj); - vec2.set(end2, si.length/2, 0); - vec2.rotate(end2,end2,ai); - add(end2,end2,xi); + vec2.set(end2, sj.length/2, 0); + vec2.rotate(end2,end2,aj); + add(end2,end2,xj); - circle.radius = si.radius; + circle.radius = sj.radius; - // Do nearphase as two circles - this.circlePlane(bi,circle,end1,0, bj,sj,xj,aj); - this.circlePlane(bi,circle,end2,0, bj,sj,xj,aj); + // Do Narrowphase as two circles + this.circlePlane(bj,circle,end1,0, bi,si,xi,ai); + this.circlePlane(bj,circle,end2,0, bi,si,xi,ai); }; +/** + * @method capsulePlane + * @deprecated Use .planeCapsule() instead! + */ +Narrowphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ + console.warn("Narrowphase.prototype.capsulePlane() is deprecated. Use .planeCapsule() instead!"); + return this.planeCapsule( bj,sj,xj,aj, bi,si,xi,ai ); +} + /** * Creates ContactEquations and FrictionEquations for a collision. * @method circlePlane @@ -883,7 +1143,8 @@ Nearphase.prototype.capsulePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ * @param {Array} xj Extra offset for the plane shape. * @param {Number} aj Extra angle to apply to the plane */ -Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ +Narrowphase.prototype[Shape.CIRCLE | Shape.PLANE] = +Narrowphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ var circleBody = bi, circleShape = si, circleOffset = xi, // Offset from body center, rotated! @@ -910,7 +1171,7 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ if(d > circleShape.radius) return false; // No overlap. Abort. // Create contact - var contact = this.createContactEquation(planeBody,circleBody); + var contact = this.createContactEquation(planeBody,circleBody,sj,si); // ni is the plane world normal vec2.copy(contact.ni, worldNormal); @@ -937,7 +1198,7 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ /** - * Convex/convex nearphase.See this article for more info. + * Convex/convex Narrowphase.See this article for more info. * @method convexConvex * @param {Body} bi * @param {Convex} si @@ -948,7 +1209,8 @@ Nearphase.prototype.circlePlane = function( bi,si,xi,ai, bj,sj,xj,aj ){ * @param {Array} xj * @param {Number} aj */ -Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){ +Narrowphase.prototype[Shape.CONVEX] = +Narrowphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){ var sepAxis = tmp1, worldPoint = tmp2, worldPoint0 = tmp3, @@ -959,7 +1221,7 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){ dist = tmp8, worldNormal = tmp9; - var found = Nearphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); + var found = Narrowphase.findSeparatingAxis(si,xi,ai,sj,xj,aj,sepAxis); if(!found) return false; // Make sure the separating axis is directed from shape i to shape j @@ -969,8 +1231,8 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){ } // Find edges with normals closest to the separating axis - var closestEdge1 = Nearphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis - closestEdge2 = Nearphase.getClosestEdge(sj,aj,sepAxis); + var closestEdge1 = Narrowphase.getClosestEdge(si,ai,sepAxis,true), // Flipped axis + closestEdge2 = Narrowphase.getClosestEdge(sj,aj,sepAxis); if(closestEdge1==-1 || closestEdge2==-1) return false; @@ -1036,7 +1298,7 @@ Nearphase.prototype.convexConvex = function( bi,si,xi,ai, bj,sj,xj,aj ){ // Project it to the center edge and use the projection direction as normal // Create contact - var c = this.createContactEquation(bodyA,bodyB); + var c = this.createContactEquation(bodyA,bodyB,si,sj); // Get center edge from body A var v0 = shapeA.vertices[(closestEdgeA) % shapeA.vertices.length], @@ -1090,7 +1352,7 @@ var pcoa_tmp1 = vec2.fromValues(0,0); * @param {Array} worldAxis * @param {Array} result */ -Nearphase.projectConvexOntoAxis = function(convexShape, convexOffset, convexAngle, worldAxis, result){ +Narrowphase.projectConvexOntoAxis = function(convexShape, convexOffset, convexAngle, worldAxis, result){ var max=null, min=null, v, @@ -1141,7 +1403,7 @@ var fsa_tmp1 = vec2.fromValues(0,0) * @param {Array} sepAxis The resulting axis * @return {Boolean} Whether the axis could be found. */ -Nearphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){ +Narrowphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepAxis){ var maxDist = null, overlap = false, found = false, @@ -1172,8 +1434,8 @@ Nearphase.findSeparatingAxis = function(c1,offset1,angle1,c2,offset2,angle2,sepA vec2.normalize(normal,normal); // Project hulls onto that normal - Nearphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1); - Nearphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2); + Narrowphase.projectConvexOntoAxis(c1,offset1,angle1,normal,span1); + Narrowphase.projectConvexOntoAxis(c2,offset2,angle2,normal,span2); // Order by span position var a=span1, @@ -1215,7 +1477,7 @@ var gce_tmp1 = vec2.fromValues(0,0) * @param {Boolean} flip * @return {Number} Index of the edge that is closest. This index and the next spans the resulting edge. Returns -1 if failed. */ -Nearphase.getClosestEdge = function(c,angle,axis,flip){ +Narrowphase.getClosestEdge = function(c,angle,axis,flip){ var localAxis = gce_tmp1, edge = gce_tmp2, normal = gce_tmp3; diff --git a/src/physics/advanced/collision/QuadTree.js b/src/physics/advanced/collision/QuadTree.js index 1cf5c58b..b0a48a73 100644 --- a/src/physics/advanced/collision/QuadTree.js +++ b/src/physics/advanced/collision/QuadTree.js @@ -27,10 +27,10 @@ function QuadTree(bounds, pointQuad, maxDepth, maxChildren){ } /** - * The root node of the QuadTree which covers the entire area being segmented. - * @property root - * @type Node - */ + * The root node of the QuadTree which covers the entire area being segmented. + * @property root + * @type Node + */ this.root = node; } @@ -299,7 +299,7 @@ BoundsNode.prototype.insert = function(item){ console.log("radius:",item.boundingRadius); console.log("item x:",item.position[0] - item.boundingRadius,"x range:",node.bounds.x,node.bounds.x+node.bounds.width); console.log("item y:",item.position[1] - item.boundingRadius,"y range:",node.bounds.y,node.bounds.y+node.bounds.height); - */ + */ //todo: make _bounds bounds if( !(item instanceof Plane) && // Plane is infinite.. Make it a "stuck" child diff --git a/src/physics/advanced/collision/SAP1DBroadphase.js b/src/physics/advanced/collision/SAP1DBroadphase.js index 7aa14c7c..f0aaf4d2 100644 --- a/src/physics/advanced/collision/SAP1DBroadphase.js +++ b/src/physics/advanced/collision/SAP1DBroadphase.js @@ -19,24 +19,24 @@ function SAP1DBroadphase(world){ Broadphase.apply(this); /** - * List of bodies currently in the broadphase. - * @property axisList - * @type {Array} - */ + * List of bodies currently in the broadphase. + * @property axisList + * @type {Array} + */ this.axisList = world.bodies.slice(0); /** - * The world to search in. - * @property world - * @type {World} - */ + * The world to search in. + * @property world + * @type {World} + */ this.world = world; /** - * Axis to sort the bodies along. Set to 0 for x axis, and 1 for y axis. For best performance, choose an axis that the bodies are spread out more on. - * @property axisIndex - * @type {Number} - */ + * Axis to sort the bodies along. Set to 0 for x axis, and 1 for y axis. For best performance, choose an axis that the bodies are spread out more on. + * @property axisIndex + * @type {Number} + */ this.axisIndex = 0; // Add listeners to update the list of bodies. @@ -92,23 +92,13 @@ SAP1DBroadphase.prototype.getCollisionPairs = function(world){ // Look through the list for(i=0, N=bodies.length; i!==N; i++){ - var bi = bodies[i], - biPos = bi.position[axisIndex], - ri = bi.boundingRadius; + var bi = bodies[i]; for(j=i+1; j boundA2 ){ + if(!SAP1DBroadphase.checkBounds(bi,bj,axisIndex)) break; - } // If we got overlap, add pair if(Broadphase.boundingRadiusCheck(bi,bj)) @@ -118,3 +108,25 @@ SAP1DBroadphase.prototype.getCollisionPairs = function(world){ return result; }; + +/** + * Check if the bounds of two bodies overlap, along the given SAP axis. + * @static + * @method checkBounds + * @param {Body} bi + * @param {Body} bj + * @param {Number} axisIndex + * @return {Boolean} + */ +SAP1DBroadphase.checkBounds = function(bi,bj,axisIndex){ + var biPos = bi.position[axisIndex], + ri = bi.boundingRadius, + bjPos = bj.position[axisIndex], + rj = bj.boundingRadius, + boundA1 = biPos-ri, + boundA2 = biPos+ri, + boundB1 = bjPos-rj, + boundB2 = bjPos+rj; + + return boundB1 < boundA2; +}; diff --git a/src/physics/advanced/constraints/Constraint.js b/src/physics/advanced/constraints/Constraint.js index b73c0376..df838cb1 100644 --- a/src/physics/advanced/constraints/Constraint.js +++ b/src/physics/advanced/constraints/Constraint.js @@ -12,24 +12,24 @@ module.exports = Constraint; function Constraint(bodyA,bodyB){ /** - * Equations to be solved in this constraint - * @property equations - * @type {Array} - */ + * Equations to be solved in this constraint + * @property equations + * @type {Array} + */ this.equations = []; /** - * First body participating in the constraint. - * @property bodyA - * @type {Body} - */ + * First body participating in the constraint. + * @property bodyA + * @type {Body} + */ this.bodyA = bodyA; /** - * Second body participating in the constraint. - * @property bodyB - * @type {Body} - */ + * Second body participating in the constraint. + * @property bodyB + * @type {Body} + */ this.bodyB = bodyB; }; diff --git a/src/physics/advanced/constraints/ContactEquation.js b/src/physics/advanced/constraints/ContactEquation.js index d54ca921..490c8cae 100644 --- a/src/physics/advanced/constraints/ContactEquation.js +++ b/src/physics/advanced/constraints/ContactEquation.js @@ -5,7 +5,7 @@ var Equation = require("./Equation"), module.exports = ContactEquation; /** - * Non-penetration constraint equation. + * Non-penetration constraint equation. Tries to make the ri and rj vectors the same point. * * @class ContactEquation * @constructor @@ -14,13 +14,57 @@ module.exports = ContactEquation; * @param {Body} bj */ function ContactEquation(bi,bj){ - Equation.call(this,bi,bj,0,1e6); + Equation.call(this,bi,bj,0,Number.MAX_VALUE); + + /** + * Vector from body i center of mass to the contact point. + * @property ri + * @type {Array} + */ this.ri = vec2.create(); this.penetrationVec = vec2.create(); + + /** + * Vector from body j center of mass to the contact point. + * @property rj + * @type {Array} + */ this.rj = vec2.create(); + + /** + * The normal vector, pointing out of body i + * @property ni + * @type {Array} + */ this.ni = vec2.create(); - this.rixn = 0; - this.rjxn = 0; + + /** + * The restitution to use. 0=no bounciness, 1=max bounciness. + * @property restitution + * @type {Number} + */ + this.restitution = 0; + + /** + * Set to true if this is the first impact between the bodies (not persistant contact). + * @property firstImpact + * @type {Boolean} + */ + this.firstImpact = false; + + /** + * The shape in body i that triggered this contact. + * @property shapeA + * @type {Shape} + */ + this.shapeA = null; + + /** + * The shape in body j that triggered this contact. + * @property shapeB + * @type {Shape} + */ + this.shapeB = null; }; ContactEquation.prototype = new Equation(); ContactEquation.prototype.constructor = ContactEquation; @@ -32,105 +76,39 @@ ContactEquation.prototype.computeB = function(a,b,h){ xi = bi.position, xj = bj.position; - var vi = bi.velocity, - wi = bi.angularVelocity, - fi = bi.force, - taui = bi.angularForce; - - var vj = bj.velocity, - wj = bj.angularVelocity, - fj = bj.force, - tauj = bj.angularForce; - var penetrationVec = this.penetrationVec, - invMassi = bi.invMass, - invMassj = bj.invMass, - invIi = bi.invInertia, - invIj = bj.invInertia, - n = this.ni; + n = this.ni, + G = this.G; // Caluclate cross products - this.rixn = vec2.crossLength(ri,n); - this.rjxn = vec2.crossLength(rj,n); + var rixn = vec2.crossLength(ri,n), + rjxn = vec2.crossLength(rj,n); + + // G = [-n -rixn n rjxn] + G[0] = -n[0]; + G[1] = -n[1]; + G[2] = -rixn; + G[3] = n[0]; + G[4] = n[1]; + G[5] = rjxn; // Calculate q = xj+rj -(xi+ri) i.e. the penetration vector vec2.add(penetrationVec,xj,rj); vec2.sub(penetrationVec,penetrationVec,xi); vec2.sub(penetrationVec,penetrationVec,ri); - var Gq = vec2.dot(n,penetrationVec); - // Compute iteration - var GW = vec2.dot(vj,n) - vec2.dot(vi,n) + wj * this.rjxn - wi * this.rixn; - var GiMf = vec2.dot(fj,n)*invMassj - vec2.dot(fi,n)*invMassi + invIj*tauj*this.rjxn - invIi*taui*this.rixn; + var GW, Gq; + if(this.firstImpact && this.restitution !== 0){ + Gq = 0; + GW = (1/b)*(1+this.restitution) * this.computeGW(); + } else { + GW = this.computeGW(); + Gq = vec2.dot(n,penetrationVec); + } + var GiMf = this.computeGiMf(); var B = - Gq * a - GW * b - h*GiMf; return B; }; - -// Compute C = GMG+eps in the SPOOK equation -var computeC_tmp1 = vec2.create(), - tmpMat1 = mat2.create(), - tmpMat2 = mat2.create(); -ContactEquation.prototype.computeC = function(eps){ - var bi = this.bi, - bj = this.bj, - n = this.ni, - rixn = this.rixn, - rjxn = this.rjxn, - tmp = computeC_tmp1, - imMat1 = tmpMat1, - imMat2 = tmpMat2; - - mat2.identity(imMat1); - mat2.identity(imMat2); - imMat1[0] = imMat1[3] = bi.invMass; - imMat2[0] = imMat2[3] = bj.invMass; - - var C = vec2.dot(n,vec2.transformMat2(tmp,n,imMat1)) + vec2.dot(n,vec2.transformMat2(tmp,n,imMat2)) + eps; - //var C = bi.invMass + bj.invMass + eps; - - C += bi.invInertia * this.rixn * this.rixn; - C += bj.invInertia * this.rjxn * this.rjxn; - - return C; -}; - -ContactEquation.prototype.computeGWlambda = function(){ - var bi = this.bi, - bj = this.bj, - n = this.ni, - dot = vec2.dot; - - return dot(n, bj.vlambda) + bj.wlambda * this.rjxn - dot(n, bi.vlambda) - bi.wlambda * this.rixn; -}; - -var addToWlambda_temp = vec2.create(); -ContactEquation.prototype.addToWlambda = function(deltalambda){ - var bi = this.bi, - bj = this.bj, - n = this.ni, - temp = addToWlambda_temp, - imMat1 = tmpMat1, - imMat2 = tmpMat2; - - mat2.identity(imMat1); - mat2.identity(imMat2); - imMat1[0] = imMat1[3] = bi.invMass; - imMat2[0] = imMat2[3] = bj.invMass; - - // Add to linear velocity - //vec2.scale(temp,n,-bi.invMass*deltalambda); - vec2.scale(temp,vec2.transformMat2(temp,n,imMat1),-deltalambda); - vec2.add( bi.vlambda,bi.vlambda, temp ); - - //vec2.scale(temp,n,bj.invMass*deltalambda); - vec2.scale(temp,vec2.transformMat2(temp,n,imMat2),deltalambda); - vec2.add( bj.vlambda,bj.vlambda, temp); - - // Add to angular velocity - bi.wlambda -= bi.invInertia * this.rixn * deltalambda; - bj.wlambda += bj.invInertia * this.rjxn * deltalambda; -}; - diff --git a/src/physics/advanced/constraints/DistanceConstraint.js b/src/physics/advanced/constraints/DistanceConstraint.js index ccdb6e56..4594a918 100644 --- a/src/physics/advanced/constraints/DistanceConstraint.js +++ b/src/physics/advanced/constraints/DistanceConstraint.js @@ -1,5 +1,5 @@ var Constraint = require('./Constraint') -, ContactEquation = require('./ContactEquation') +, Equation = require('./Equation') , vec2 = require('../math/vec2') module.exports = DistanceConstraint; @@ -19,16 +19,25 @@ module.exports = DistanceConstraint; function DistanceConstraint(bodyA,bodyB,distance,maxForce){ Constraint.call(this,bodyA,bodyB); + /** + * The distance to keep. + * @property distance + * @type {Number} + */ this.distance = distance; - if(typeof(maxForce)==="undefined" ) { - maxForce = 1e6; - } - - var normal = new ContactEquation(bodyA,bodyB); // Just in the normal direction + if(typeof(maxForce)==="undefined" ) + maxForce = Number.MAX_VALUE; + var normal = new Equation(bodyA,bodyB,-maxForce,maxForce); // Just in the normal direction this.equations = [ normal ]; + var r = vec2.create(); + normal.computeGq = function(){ + vec2.sub(r, bodyB.position, bodyA.position); + return vec2.length(r)-distance; + }; + // Make the contact constraint bilateral this.setMaxForce(maxForce); } @@ -38,24 +47,38 @@ DistanceConstraint.prototype = new Constraint(); * Update the constraint equations. Should be done if any of the bodies changed position, before solving. * @method update */ +var n = vec2.create(); DistanceConstraint.prototype.update = function(){ var normal = this.equations[0], bodyA = this.bodyA, bodyB = this.bodyB, - distance = this.distance; + distance = this.distance, + G = normal.G; - vec2.sub(normal.ni, bodyB.position, bodyA.position); - vec2.normalize(normal.ni,normal.ni); - vec2.scale(normal.ri, normal.ni, distance*0.5); - vec2.scale(normal.rj, normal.ni, -distance*0.5); + vec2.sub(n, bodyB.position, bodyA.position); + vec2.normalize(n,n); + G[0] = -n[0]; + G[1] = -n[1]; + G[3] = n[0]; + G[4] = n[1]; }; +/** + * Set the max force to be used + * @method setMaxForce + * @param {Number} f + */ DistanceConstraint.prototype.setMaxForce = function(f){ var normal = this.equations[0]; normal.minForce = -f; normal.maxForce = f; }; +/** + * Get the max force + * @method getMaxForce + * @return {Number} + */ DistanceConstraint.prototype.getMaxForce = function(f){ var normal = this.equations[0]; return normal.maxForce; diff --git a/src/physics/advanced/constraints/Equation.js b/src/physics/advanced/constraints/Equation.js index 2bbd564b..64527232 100644 --- a/src/physics/advanced/constraints/Equation.js +++ b/src/physics/advanced/constraints/Equation.js @@ -1,5 +1,9 @@ module.exports = Equation; +var vec2 = require('../math/vec2'), + mat2 = require('../math/mat2'), + Utils = require('../utils/Utils'); + /** * Base class for constraint equations. * @class Equation @@ -12,52 +16,75 @@ module.exports = Equation; function Equation(bi,bj,minForce,maxForce){ /** - * Minimum force to apply when solving - * @property minForce - * @type {Number} - */ + * Minimum force to apply when solving + * @property minForce + * @type {Number} + */ this.minForce = typeof(minForce)=="undefined" ? -1e6 : minForce; /** - * Max force to apply when solving - * @property maxForce - * @type {Number} - */ + * Max force to apply when solving + * @property maxForce + * @type {Number} + */ this.maxForce = typeof(maxForce)=="undefined" ? 1e6 : maxForce; /** - * First body participating in the constraint - * @property bi - * @type {Body} - */ + * First body participating in the constraint + * @property bi + * @type {Body} + */ this.bi = bi; /** - * Second body participating in the constraint - * @property bj - * @type {Body} - */ + * Second body participating in the constraint + * @property bj + * @type {Body} + */ this.bj = bj; /** - * The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation. - * @property stiffness - * @type {Number} - */ + * The stiffness of this equation. Typically chosen to a large number (~1e7), but can be chosen somewhat freely to get a stable simulation. + * @property stiffness + * @type {Number} + */ this.stiffness = 1e6; /** - * The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps. - * @property relaxation - * @type {Number} - */ + * The number of time steps needed to stabilize the constraint equation. Typically between 3 and 5 time steps. + * @property relaxation + * @type {Number} + */ this.relaxation = 4; + /** + * The Jacobian entry of this equation. 6 numbers, 3 per body (x,y,angle). + * @property G + * @type {Array} + */ + this.G = new Utils.ARRAY_TYPE(6); + + // Constraint frames for body i and j + /* + this.xi = vec2.create(); + this.xj = vec2.create(); + this.ai = 0; + this.aj = 0; + */ + this.offset = 0; + this.a = 0; this.b = 0; this.eps = 0; this.h = 0; this.updateSpookParams(1/60); + + /** + * The resulting constraint multiplier from the last solve. This is mostly equivalent to the force produced by the constraint. + * @property multiplier + * @type {Number} + */ + this.multiplier = 0; }; Equation.prototype.constructor = Equation; @@ -75,3 +102,215 @@ Equation.prototype.updateSpookParams = function(timeStep){ this.eps = 4.0 / (h * h * k * (1 + 4 * d)); this.h = timeStep; }; + +function Gmult(G,vi,wi,vj,wj){ + return G[0] * vi[0] + + G[1] * vi[1] + + G[2] * wi + + G[3] * vj[0] + + G[4] * vj[1] + + G[5] * wj; +} + +/** + * Computes the RHS of the SPOOK equation + * @method computeB + * @return {Number} + */ +Equation.prototype.computeB = function(a,b,h){ + var GW = this.computeGW(); + var Gq = this.computeGq(); + var GiMf = this.computeGiMf(); + return - Gq * a - GW * b - GiMf*h; +}; + +/** + * Computes G*q, where q are the generalized body coordinates + * @method computeGq + * @return {Number} + */ +var qi = vec2.create(), + qj = vec2.create(); +Equation.prototype.computeGq = function(){ + var G = this.G, + bi = this.bi, + bj = this.bj, + xi = bi.position, + xj = bj.position, + ai = bi.angle, + aj = bj.angle; + + // Transform to the given body frames + /* + vec2.rotate(qi,this.xi,ai); + vec2.rotate(qj,this.xj,aj); + vec2.add(qi,qi,xi); + vec2.add(qj,qj,xj); + */ + + return Gmult(G, qi, ai, qj, aj) + this.offset; +}; + +var tmp_i = vec2.create(), + tmp_j = vec2.create(); +Equation.prototype.transformedGmult = function(G,vi,wi,vj,wj){ + // Transform velocity to the given body frames + // v_p = v + w x r + /* + vec2.rotate(tmp_i,this.xi,Math.PI / 2 + this.bi.angle); // Get r, and rotate 90 degrees. We get the "x r" part + vec2.rotate(tmp_j,this.xj,Math.PI / 2 + this.bj.angle); + vec2.scale(tmp_i,tmp_i,wi); // Temp vectors are now (w x r) + vec2.scale(tmp_j,tmp_j,wj); + vec2.add(tmp_i,tmp_i,vi); + vec2.add(tmp_j,tmp_j,vj); + */ + + // Note: angular velocity is same + return Gmult(G,vi,wi,vj,wj); +}; + +/** + * Computes G*W, where W are the body velocities + * @method computeGW + * @return {Number} + */ +Equation.prototype.computeGW = function(){ + var G = this.G, + bi = this.bi, + bj = this.bj, + vi = bi.velocity, + vj = bj.velocity, + wi = bi.angularVelocity, + wj = bj.angularVelocity; + return this.transformedGmult(G,vi,wi,vj,wj); +}; + +/** + * Computes G*Wlambda, where W are the body velocities + * @method computeGWlambda + * @return {Number} + */ +Equation.prototype.computeGWlambda = function(){ + var G = this.G, + bi = this.bi, + bj = this.bj, + vi = bi.vlambda, + vj = bj.vlambda, + wi = bi.wlambda, + wj = bj.wlambda; + return this.transformedGmult(G,vi,wi,vj,wj); +}; + +/** + * Computes G*inv(M)*f, where M is the mass matrix with diagonal blocks for each body, and f are the forces on the bodies. + * @method computeGiMf + * @return {Number} + */ +var iMfi = vec2.create(), + iMfj = vec2.create(); +Equation.prototype.computeGiMf = function(){ + var bi = this.bi, + bj = this.bj, + fi = bi.force, + ti = bi.angularForce, + fj = bj.force, + tj = bj.angularForce, + invMassi = bi.invMass, + invMassj = bj.invMass, + invIi = bi.invInertia, + invIj = bj.invInertia, + G = this.G; + + vec2.scale(iMfi, fi,invMassi); + vec2.scale(iMfj, fj,invMassj); + + return this.transformedGmult(G,iMfi,ti*invIi,iMfj,tj*invIj); +}; + +/** + * Computes G*inv(M)*G' + * @method computeGiMGt + * @return {Number} + */ +Equation.prototype.computeGiMGt = function(){ + var bi = this.bi, + bj = this.bj, + invMassi = bi.invMass, + invMassj = bj.invMass, + invIi = bi.invInertia, + invIj = bj.invInertia, + G = this.G; + + return G[0] * G[0] * invMassi + + G[1] * G[1] * invMassi + + G[2] * G[2] * invIi + + G[3] * G[3] * invMassj + + G[4] * G[4] * invMassj + + G[5] * G[5] * invIj; +}; + +var addToWlambda_temp = vec2.create(), + addToWlambda_Gi = vec2.create(), + addToWlambda_Gj = vec2.create(), + addToWlambda_ri = vec2.create(), + addToWlambda_rj = vec2.create(); +var tmpMat1 = mat2.create(), + tmpMat2 = mat2.create(); + +/** + * Add constraint velocity to the bodies. + * @method addToWlambda + * @param {Number} deltalambda + */ +Equation.prototype.addToWlambda = function(deltalambda){ + var bi = this.bi, + bj = this.bj, + temp = addToWlambda_temp, + imMat1 = tmpMat1, + imMat2 = tmpMat2, + Gi = addToWlambda_Gi, + Gj = addToWlambda_Gj, + ri = addToWlambda_ri, + rj = addToWlambda_rj, + G = this.G; + + Gi[0] = G[0]; + Gi[1] = G[1]; + Gj[0] = G[3]; + Gj[1] = G[4]; + + mat2.identity(imMat1); + mat2.identity(imMat2); + imMat1[0] = imMat1[3] = bi.invMass; + imMat2[0] = imMat2[3] = bj.invMass; + + /* + vec2.rotate(ri,this.xi,bi.angle); + vec2.rotate(rj,this.xj,bj.angle); + */ + + // Add to linear velocity + vec2.scale(temp,vec2.transformMat2(temp,Gi,imMat1),deltalambda); + vec2.add( bi.vlambda, bi.vlambda, temp); + // This impulse is in the offset frame + // Also add contribution to angular + //bi.wlambda -= vec2.crossLength(temp,ri); + + vec2.scale(temp,vec2.transformMat2(temp,Gj,imMat2),deltalambda); + vec2.add( bj.vlambda, bj.vlambda, temp); + //bj.wlambda -= vec2.crossLength(temp,rj); + + // Add to angular velocity + bi.wlambda += bi.invInertia * G[2] * deltalambda; + bj.wlambda += bj.invInertia * G[5] * deltalambda; +}; + +/** + * Compute the denominator part of the SPOOK equation: C = G*inv(M)*G' + eps + * @method computeC + * @param {Number} eps + * @return {Number} + */ +Equation.prototype.computeC = function(eps){ + return this.computeGiMGt() + eps; +}; diff --git a/src/physics/advanced/constraints/FrictionEquation.js b/src/physics/advanced/constraints/FrictionEquation.js index d0a3e812..8dea7970 100644 --- a/src/physics/advanced/constraints/FrictionEquation.js +++ b/src/physics/advanced/constraints/FrictionEquation.js @@ -1,22 +1,10 @@ var mat2 = require('../math/mat2') , vec2 = require('../math/vec2') , Equation = require('./Equation') +, Utils = require('../utils/Utils') module.exports = FrictionEquation; -// 3D cross product from glmatrix, until we get this to work... -function cross(out, a, b) { - var ax = a[0], ay = a[1], az = a[2], - bx = b[0], by = b[1], bz = b[2]; - - out[0] = ay * bz - az * by; - out[1] = az * bx - ax * bz; - out[2] = ax * by - ay * bx; - return out; -}; - -var dot = vec2.dot; - /** * Constrains the slipping in a contact along a tangent * @@ -31,28 +19,53 @@ function FrictionEquation(bi,bj,slipForce){ Equation.call(this,bi,bj,-slipForce,slipForce); /** - * Relative vector from center of body i to the contact point, in world coords. - * @property ri - * @type {Float32Array} - */ + * Relative vector from center of body i to the contact point, in world coords. + * @property ri + * @type {Float32Array} + */ this.ri = vec2.create(); /** - * Relative vector from center of body j to the contact point, in world coords. - * @property rj - * @type {Float32Array} - */ + * Relative vector from center of body j to the contact point, in world coords. + * @property rj + * @type {Float32Array} + */ this.rj = vec2.create(); /** - * Tangent vector that the friction force will act along, in world coords. - * @property t - * @type {Float32Array} - */ + * Tangent vector that the friction force will act along, in world coords. + * @property t + * @type {Float32Array} + */ this.t = vec2.create(); - this.rixt = 0; - this.rjxt = 0; + /** + * A ContactEquation connected to this friction. The contact equation can be used to rescale the max force for the friction. + * @property contactEquation + * @type {ContactEquation} + */ + this.contactEquation = null; + + /** + * The shape in body i that triggered this friction. + * @property shapeA + * @type {Shape} + */ + this.shapeA = null; + + /** + * The shape in body j that triggered this friction. + * @property shapeB + * @type {Shape} + */ + this.shapeB = null; + + /** + * The friction coefficient to use. + * @property frictionCoefficient + * @type {Number} + */ + this.frictionCoefficient = 0.3; }; FrictionEquation.prototype = new Equation(); FrictionEquation.prototype.constructor = FrictionEquation; @@ -62,119 +75,34 @@ FrictionEquation.prototype.constructor = FrictionEquation; * larger than this value. * @method setSlipForce * @param {Number} slipForce + * @deprecated Use .frictionCoefficient instead */ FrictionEquation.prototype.setSlipForce = function(slipForce){ this.maxForce = slipForce; this.minForce = -slipForce; }; -var rixtVec = [0,0,0]; -var rjxtVec = [0,0,0]; -var ri3 = [0,0,0]; -var rj3 = [0,0,0]; -var t3 = [0,0,0]; FrictionEquation.prototype.computeB = function(a,b,h){ - var a = this.a, - b = this.b, - bi = this.bi, + var bi = this.bi, bj = this.bj, ri = this.ri, rj = this.rj, - t = this.t; + t = this.t, + G = this.G; - // Caluclate cross products - ri3[0] = ri[0]; - ri3[1] = ri[1]; - rj3[0] = rj[0]; - rj3[1] = rj[1]; - t3[0] = t[0]; - t3[1] = t[1]; - cross(rixtVec, ri3, t3);//ri.cross(t,rixt); - cross(rjxtVec, rj3, t3);//rj.cross(t,rjxt); - this.rixt = rixtVec[2]; - this.rjxt = rjxtVec[2]; + // G = [-t -rixt t rjxt] + // And remember, this is a pure velocity constraint, g is always zero! + G[0] = -t[0]; + G[1] = -t[1]; + G[2] = -vec2.crossLength(ri,t); + G[3] = t[0]; + G[4] = t[1]; + G[5] = vec2.crossLength(rj,t); - var GW = -dot(bi.velocity,t) + dot(bj.velocity,t) - this.rixt*bi.angularVelocity + this.rjxt*bj.angularVelocity; // eq. 40 - var GiMf = -dot(bi.force,t)*bi.invMass +dot(bj.force,t)*bj.invMass -this.rixt*bi.invInertia*bi.angularForce + this.rjxt*bj.invInertia*bj.angularForce; + var GW = this.computeGW(); + var GiMf = this.computeGiMf(); - var B = /* - Gq * a */ - GW * b - h*GiMf; + var B = /* - g * a */ - GW * b - h*GiMf; return B; }; - -// Compute C = G * iM * G' + eps -// -// G*iM*G' = -// -// [ iM1 ] [-t ] -// [-t (-ri x t) t (rj x t)] * [ iI1 ] [-ri x t] -// [ iM2 ] [t ] -// [ iI2 ] [rj x t ] -// -// = (-t)*iM1*(-t) + (-ri x t)*iI1*(-ri x t) + t*iM2*t + (rj x t)*iI2*(rj x t) -// -// = t*iM1*t + (ri x t)*iI1*(ri x t) + t*iM2*t + (rj x t)*iI2*(rj x t) -// -var computeC_tmp1 = vec2.create(), - tmpMat1 = mat2.create(), - tmpMat2 = mat2.create(); -FrictionEquation.prototype.computeC = function(eps){ - var bi = this.bi, - bj = this.bj, - t = this.t, - C = 0.0, - tmp = computeC_tmp1, - imMat1 = tmpMat1, - imMat2 = tmpMat2, - dot = vec2.dot; - - mat2.identity(imMat1); - mat2.identity(imMat2); - - imMat1[0] = imMat1[3] = bi.invMass; - imMat2[0] = imMat2[3] = bj.invMass; - - C = dot(t,vec2.transformMat2(tmp,t,imMat1)) + dot(t,vec2.transformMat2(tmp,t,imMat2)) + eps; - - //C = bi.invMass + bj.invMass + eps; - - C += bi.invInertia * this.rixt * this.rixt; - C += bj.invInertia * this.rjxt * this.rjxt; - - return C; -}; - -FrictionEquation.prototype.computeGWlambda = function(){ - var bi = this.bi, - bj = this.bj, - t = this.t, - dot = vec2.dot; - - return dot(t, bj.vlambda) + bj.wlambda * this.rjxt - bi.wlambda * this.rixt - dot(t, bi.vlambda); -}; - -var FrictionEquation_addToWlambda_tmp = vec2.create(); -FrictionEquation.prototype.addToWlambda = function(deltalambda){ - var bi = this.bi, - bj = this.bj, - t = this.t, - tmp = FrictionEquation_addToWlambda_tmp, - imMat1 = tmpMat1, - imMat2 = tmpMat2; - - mat2.identity(imMat1); - mat2.identity(imMat2); - imMat1[0] = imMat1[3] = bi.invMass; - imMat2[0] = imMat2[3] = bj.invMass; - - vec2.scale(tmp,vec2.transformMat2(tmp,t,imMat1),-deltalambda); - //vec2.scale(tmp, t, -bi.invMass * deltalambda); //t.mult(invMassi * deltalambda, tmp); - vec2.add(bi.vlambda, bi.vlambda, tmp); //bi.vlambda.vsub(tmp,bi.vlambda); - - vec2.scale(tmp,vec2.transformMat2(tmp,t,imMat2),deltalambda); - //vec2.scale(tmp, t, bj.invMass * deltalambda); //t.mult(invMassj * deltalambda, tmp); - vec2.add(bj.vlambda, bj.vlambda, tmp); //bj.vlambda.vadd(tmp,bj.vlambda); - - bi.wlambda -= bi.invInertia * this.rixt * deltalambda; - bj.wlambda += bj.invInertia * this.rjxt * deltalambda; -}; diff --git a/src/physics/advanced/constraints/LockConstraint.js b/src/physics/advanced/constraints/LockConstraint.js new file mode 100644 index 00000000..8c7539a9 --- /dev/null +++ b/src/physics/advanced/constraints/LockConstraint.js @@ -0,0 +1,123 @@ +var Constraint = require('./Constraint') +, vec2 = require('../math/vec2') +, Equation = require('./Equation') + +module.exports = LockConstraint; + +/** + * Locks the relative position between two bodies. + * + * @class LockConstraint + * @constructor + * @author schteppe + * @param {Body} bodyA + * @param {Body} bodyB + * @param {Object} [options] + * @param {Array} [options.localOffsetB] The offset of bodyB in bodyA's frame. + * @param {number} [options.localAngleB] The angle of bodyB in bodyA's frame. + * @param {number} [options.maxForce] + * @extends {Constraint} + */ +function LockConstraint(bodyA,bodyB,options){ + Constraint.call(this,bodyA,bodyB); + var maxForce = ( typeof(options.maxForce)=="undefined" ? Number.MAX_VALUE : options.maxForce ); + var localOffsetB = options.localOffsetB || vec2.fromValues(0,0); + localOffsetB = vec2.fromValues(localOffsetB[0],localOffsetB[1]); + + var localAngleB = options.localAngleB || 0; + + // Use 3 equations: + // gx = (xj - xi - l) * xhat = 0 + // gy = (xj - xi - l) * yhat = 0 + // gr = (xi - xj + r) * that = 0 + // + // ...where: + // l is the localOffsetB vector rotated to world in bodyA frame + // r is the same vector but reversed and rotated from bodyB frame + // xhat, yhat are world axis vectors + // that is the tangent of r + // + // For the first two constraints, we get + // G*W = (vj - vi - ldot ) * xhat + // = (vj - vi - wi x l) * xhat + // + // Since (wi x l) * xhat = (l x xhat) * wi, we get + // G*W = [ -1 0 (-l x xhat) 1 0 0] * [vi wi vj wj] + // + // The last constraint gives + // GW = (vi - vj + wj x r) * that + // = [ that 0 -that (r x t) ] + + var x = new Equation(bodyA,bodyB,-maxForce,maxForce), + y = new Equation(bodyA,bodyB,-maxForce,maxForce), + rot = new Equation(bodyA,bodyB,-maxForce,maxForce); + + var l = vec2.create(), + g = vec2.create(); + x.computeGq = function(){ + vec2.rotate(l,localOffsetB,bodyA.angle); + vec2.sub(g,bodyB.position,bodyA.position); + vec2.sub(g,g,l); + return g[0]; + } + y.computeGq = function(){ + vec2.rotate(l,localOffsetB,bodyA.angle); + vec2.sub(g,bodyB.position,bodyA.position); + vec2.sub(g,g,l); + return g[1]; + }; + var r = vec2.create(), + t = vec2.create(); + rot.computeGq = function(){ + vec2.rotate(r,localOffsetB,bodyB.angle - localAngleB); + vec2.scale(r,r,-1); + vec2.sub(g,bodyA.position,bodyB.position); + vec2.add(g,g,r); + vec2.rotate(t,r,-Math.PI/2); + vec2.normalize(t,t); + return vec2.dot(g,t); + }; + + this.localOffsetB = localOffsetB; + this.localAngleB = localAngleB; + this.maxForce = maxForce; + + var eqs = this.equations = [ x, y, rot ]; +} +LockConstraint.prototype = new Constraint(); + +var l = vec2.create(); +var r = vec2.create(); +var t = vec2.create(); +var xAxis = vec2.fromValues(1,0); +var yAxis = vec2.fromValues(0,1); +LockConstraint.prototype.update = function(){ + var x = this.equations[0], + y = this.equations[1], + rot = this.equations[2], + bodyA = this.bodyA, + bodyB = this.bodyB; + + vec2.rotate(l,this.localOffsetB,bodyA.angle); + vec2.rotate(r,this.localOffsetB,bodyB.angle - this.localAngleB); + vec2.scale(r,r,-1); + + vec2.rotate(t,r,Math.PI/2); + vec2.normalize(t,t); + + x.G[0] = -1; + x.G[1] = 0; + x.G[2] = -vec2.crossLength(l,xAxis); + x.G[3] = 1; + + y.G[0] = 0; + y.G[1] = -1; + y.G[2] = -vec2.crossLength(l,yAxis); + y.G[4] = 1; + + rot.G[0] = -t[0]; + rot.G[1] = -t[1]; + rot.G[3] = t[0]; + rot.G[4] = t[1]; + rot.G[5] = vec2.crossLength(r,t); +}; diff --git a/src/physics/advanced/constraints/PointToPointConstraint.js b/src/physics/advanced/constraints/PointToPointConstraint.js deleted file mode 100644 index 6fd343a1..00000000 --- a/src/physics/advanced/constraints/PointToPointConstraint.js +++ /dev/null @@ -1,94 +0,0 @@ -var Constraint = require('./Constraint') -, ContactEquation = require('./ContactEquation') -, RotationalVelocityEquation = require('./RotationalVelocityEquation') -, vec2 = require('../math/vec2') - -module.exports = PointToPointConstraint; - -/** - * Connects two bodies at given offset points - * @class PointToPointConstraint - * @constructor - * @author schteppe - * @param {Body} bodyA - * @param {Float32Array} pivotA The point relative to the center of mass of bodyA which bodyA is constrained to. - * @param {Body} bodyB Body that will be constrained in a similar way to the same point as bodyA. We will therefore get sort of a link between bodyA and bodyB. If not specified, bodyA will be constrained to a static point. - * @param {Float32Array} pivotB See pivotA. - * @param {Number} maxForce The maximum force that should be applied to constrain the bodies. - * @extends {Constraint} - * @todo Ability to specify world points - */ -function PointToPointConstraint(bodyA, pivotA, bodyB, pivotB, maxForce){ - Constraint.call(this,bodyA,bodyB); - - maxForce = typeof(maxForce)!="undefined" ? maxForce : 1e7; - - this.pivotA = pivotA; - this.pivotB = pivotB; - - // Equations to be fed to the solver - var eqs = this.equations = [ - new ContactEquation(bodyA,bodyB), // Normal - new ContactEquation(bodyA,bodyB), // Tangent - ]; - - var normal = eqs[0]; - var tangent = eqs[1]; - - tangent.minForce = normal.minForce = -maxForce; - tangent.maxForce = normal.maxForce = maxForce; - - this.motorEquation = null; -} -PointToPointConstraint.prototype = new Constraint(); - -PointToPointConstraint.prototype.update = function(){ - var bodyA = this.bodyA, - bodyB = this.bodyB, - pivotA = this.pivotA, - pivotB = this.pivotB, - eqs = this.equations, - normal = eqs[0], - tangent= eqs[1]; - - vec2.subtract(normal.ni, bodyB.position, bodyA.position); - vec2.normalize(normal.ni,normal.ni); - vec2.rotate(normal.ri, pivotA, bodyA.angle); - vec2.rotate(normal.rj, pivotB, bodyB.angle); - - vec2.rotate(tangent.ni, normal.ni, Math.PI / 2); - vec2.copy(tangent.ri, normal.ri); - vec2.copy(tangent.rj, normal.rj); -}; - -/** - * Enable the rotational motor - * @method enableMotor - */ -PointToPointConstraint.prototype.enableMotor = function(){ - if(this.motorEquation) return; - this.motorEquation = new RotationalVelocityEquation(this.bodyA,this.bodyB); - this.equations.push(this.motorEquation); -}; - -/** - * Disable the rotational motor - * @method disableMotor - */ -PointToPointConstraint.prototype.disableMotor = function(){ - if(!this.motorEquation) return; - var i = this.equations.indexOf(this.motorEquation); - this.motorEquation = null; - this.equations.splice(i,1); -}; - -/** - * Set the speed of the rotational constraint motor - * @method setMotorSpeed - * @param {Number} speed - */ -PointToPointConstraint.prototype.setMotorSpeed = function(speed){ - if(!this.motorEquation) return; - var i = this.equations.indexOf(this.motorEquation); - this.equations[i].relativeVelocity = speed; -}; diff --git a/src/physics/advanced/constraints/PrismaticConstraint.js b/src/physics/advanced/constraints/PrismaticConstraint.js index ff03a767..43cec8a7 100644 --- a/src/physics/advanced/constraints/PrismaticConstraint.js +++ b/src/physics/advanced/constraints/PrismaticConstraint.js @@ -1,58 +1,107 @@ var Constraint = require('./Constraint') , ContactEquation = require('./ContactEquation') +, Equation = require('./Equation') , vec2 = require('../math/vec2') +, RotationalLockEquation = require('./RotationalLockEquation') module.exports = PrismaticConstraint; /** - * Constraint that only allows translation along a line between the bodies, no rotation + * Constraint that only allows bodies to move along a line, relative to each other. See this tutorial. * * @class PrismaticConstraint * @constructor + * @extends {Constraint} * @author schteppe * @param {Body} bodyA * @param {Body} bodyB * @param {Object} options - * @param {Number} options.maxForce - * @param {Array} options.worldAxis - * @param {Array} options.localAxisA - * @param {Array} options.localAxisB - * @extends {Constraint} + * @param {Number} options.maxForce Max force to be applied by the constraint + * @param {Array} options.localAnchorA Body A's anchor point, defined in its own local frame. + * @param {Array} options.localAnchorB Body B's anchor point, defined in its own local frame. + * @param {Array} options.localAxisA An axis, defined in body A frame, that body B's anchor point may slide along. */ function PrismaticConstraint(bodyA,bodyB,options){ options = options || {}; Constraint.call(this,bodyA,bodyB); - var maxForce = this.maxForce = typeof(options.maxForce)==="undefined" ? options.maxForce : 1e6; + // Get anchors + var localAnchorA = vec2.fromValues(0,0), + localAxisA = vec2.fromValues(1,0), + localAnchorB = vec2.fromValues(0,0); + if(options.localAnchorA) vec2.copy(localAnchorA, options.localAnchorA); + if(options.localAxisA) vec2.copy(localAxisA, options.localAxisA); + if(options.localAnchorB) vec2.copy(localAnchorB, options.localAnchorB); - // Equations to be fed to the solver - var eqs = this.equations = [ - new ContactEquation(bodyA,bodyB), // Tangent for bodyA - new ContactEquation(bodyB,bodyA), // Tangent for bodyB - ]; + /** + * @property localAnchorA + * @type {Array} + */ + this.localAnchorA = localAnchorA; - var tangentA = eqs[0], - tangentB = eqs[1]; + /** + * @property localAnchorB + * @type {Array} + */ + this.localAnchorB = localAnchorB; - tangentA.minForce = tangentB.minForce = -maxForce; - tangentA.maxForce = tangentB.maxForce = maxForce; + /** + * @property localAxisA + * @type {Array} + */ + this.localAxisA = localAxisA; - var worldAxis = vec2.create(); - if(options.worldAxis){ - vec2.copy(worldAxis, options.worldAxis); - } else { - vec2.sub(worldAxis, bodyB.position, bodyA.position); + /* + + The constraint violation for the common axis point is + + g = ( xj + rj - xi - ri ) * t := gg*t + + where r are body-local anchor points, and t is a tangent to the constraint axis defined in body i frame. + + gdot = ( vj + wj x rj - vi - wi x ri ) * t + ( xj + rj - xi - ri ) * ( wi x t ) + + Note the use of the chain rule. Now we identify the jacobian + + G*W = [ -t -ri x t + t x gg t rj x t ] * [vi wi vj wj] + + The rotational part is just a rotation lock. + + */ + + var maxForce = this.maxForce = typeof(options.maxForce)==="undefined" ? options.maxForce : Number.MAX_VALUE; + + // Translational part + var trans = new Equation(bodyA,bodyB,-maxForce,maxForce); + var ri = new vec2.create(), + rj = new vec2.create(), + gg = new vec2.create(), + t = new vec2.create(); + trans.computeGq = function(){ + // g = ( xj + rj - xi - ri ) * t + return vec2.dot(gg,t); + }; + trans.update = function(){ + var G = this.G, + xi = bodyA.position, + xj = bodyB.position; + vec2.rotate(ri,localAnchorA,bodyA.angle); + vec2.rotate(rj,localAnchorB,bodyB.angle); + vec2.add(gg,xj,rj); + vec2.sub(gg,gg,xi); + vec2.sub(gg,gg,ri); + vec2.rotate(t,localAxisA,bodyA.angle+Math.PI/2); + + G[0] = -t[0]; + G[1] = -t[1]; + G[2] = -vec2.crossLength(ri,t) + vec2.crossLength(t,gg); + G[3] = t[0]; + G[4] = t[1]; + G[5] = vec2.crossLength(rj,t); } - vec2.normalize(worldAxis,worldAxis); + var rot = new RotationalLockEquation(bodyA,bodyB,-maxForce,maxForce); - // Axis that is local in each body - this.localAxisA = vec2.create(); - this.localAxisB = vec2.create(); - if(options.localAxisA) vec2.copy(this.localAxisA, options.localAxisA); - else vec2.rotate(this.localAxisA, worldAxis, -bodyA.angle); - - if(options.localAxisB) vec2.copy(this.localAxisB, options.localAxisB); - else vec2.rotate(this.localAxisB, worldAxis, -bodyB.angle); + this.equations.push(trans,rot); } PrismaticConstraint.prototype = new Constraint(); @@ -62,22 +111,7 @@ PrismaticConstraint.prototype = new Constraint(); * @method update */ PrismaticConstraint.prototype.update = function(){ - var tangentA = this.equations[0], - tangentB = this.equations[1], - bodyA = this.bodyA, - bodyB = this.bodyB; - - // Get tangent directions - vec2.rotate(tangentA.ni, this.localAxisA, bodyA.angle - Math.PI/2); - vec2.rotate(tangentB.ni, this.localAxisB, bodyB.angle + Math.PI/2); - - // Get distance vector - var dist = vec2.create(); - vec2.sub(dist, bodyB.position, bodyA.position); - vec2.scale(tangentA.ri, tangentA.ni, -vec2.dot(tangentA.ni, dist)); - vec2.scale(tangentB.ri, tangentB.ni, vec2.dot(tangentB.ni, dist)); - vec2.add(tangentA.rj, tangentA.ri, dist); - vec2.sub(tangentB.rj, tangentB.ri, dist); - vec2.set(tangentA.ri, 0, 0); - vec2.set(tangentB.ri, 0, 0); + var eqs = this.equations, + trans = eqs[0]; + trans.update(); }; diff --git a/src/physics/advanced/constraints/RevoluteConstraint.js b/src/physics/advanced/constraints/RevoluteConstraint.js new file mode 100644 index 00000000..14abbab8 --- /dev/null +++ b/src/physics/advanced/constraints/RevoluteConstraint.js @@ -0,0 +1,208 @@ +var Constraint = require('./Constraint') +, Equation = require('./Equation') +, RotationalVelocityEquation = require('./RotationalVelocityEquation') +, RotationalLockEquation = require('./RotationalLockEquation') +, vec2 = require('../math/vec2') + +module.exports = RevoluteConstraint; + +var worldPivotA = vec2.create(), + worldPivotB = vec2.create(), + xAxis = vec2.fromValues(1,0), + yAxis = vec2.fromValues(0,1), + g = vec2.create(); + +/** + * Connects two bodies at given offset points, letting them rotate relative to each other around this point. + * @class RevoluteConstraint + * @constructor + * @author schteppe + * @param {Body} bodyA + * @param {Float32Array} pivotA The point relative to the center of mass of bodyA which bodyA is constrained to. + * @param {Body} bodyB Body that will be constrained in a similar way to the same point as bodyA. We will therefore get sort of a link between bodyA and bodyB. If not specified, bodyA will be constrained to a static point. + * @param {Float32Array} pivotB See pivotA. + * @param {Number} maxForce The maximum force that should be applied to constrain the bodies. + * @extends {Constraint} + * @todo Ability to specify world points + */ +function RevoluteConstraint(bodyA, pivotA, bodyB, pivotB, maxForce){ + Constraint.call(this,bodyA,bodyB); + + maxForce = typeof(maxForce)!="undefined" ? maxForce : Number.MAX_VALUE; + + this.pivotA = pivotA; + this.pivotB = pivotB; + + // Equations to be fed to the solver + var eqs = this.equations = [ + new Equation(bodyA,bodyB,-maxForce,maxForce), + new Equation(bodyA,bodyB,-maxForce,maxForce), + ]; + + var x = eqs[0]; + var y = eqs[1]; + + x.computeGq = function(){ + vec2.rotate(worldPivotA, pivotA, bodyA.angle); + vec2.rotate(worldPivotB, pivotB, bodyB.angle); + vec2.add(g, bodyB.position, worldPivotB); + vec2.sub(g, g, bodyA.position); + vec2.sub(g, g, worldPivotA); + return vec2.dot(g,xAxis); + }; + + y.computeGq = function(){ + vec2.rotate(worldPivotA, pivotA, bodyA.angle); + vec2.rotate(worldPivotB, pivotB, bodyB.angle); + vec2.add(g, bodyB.position, worldPivotB); + vec2.sub(g, g, bodyA.position); + vec2.sub(g, g, worldPivotA); + return vec2.dot(g,yAxis); + }; + + y.minForce = x.minForce = -maxForce; + y.maxForce = x.maxForce = maxForce; + + this.motorEquation = new RotationalVelocityEquation(bodyA,bodyB); + this.motorEnabled = false; + + // Angle limits + this.lowerLimitEnabled = false; + this.upperLimitEnabled = false; + this.lowerLimit = 0; + this.upperLimit = 0; + this.upperLimitEquation = new RotationalLockEquation(bodyA,bodyB); + this.lowerLimitEquation = new RotationalLockEquation(bodyA,bodyB); + this.upperLimitEquation.minForce = 0; + this.lowerLimitEquation.maxForce = 0; +} +RevoluteConstraint.prototype = new Constraint(); + +RevoluteConstraint.prototype.update = function(){ + var bodyA = this.bodyA, + bodyB = this.bodyB, + pivotA = this.pivotA, + pivotB = this.pivotB, + eqs = this.equations, + normal = eqs[0], + tangent= eqs[1], + x = eqs[0], + y = eqs[1], + upperLimit = this.upperLimit, + lowerLimit = this.lowerLimit, + upperLimitEquation = this.upperLimitEquation, + lowerLimitEquation = this.lowerLimitEquation; + + var relAngle = this.angle = bodyB.angle - bodyA.angle; + + if(this.upperLimitEnabled && relAngle > upperLimit){ + upperLimitEquation.angle = upperLimit; + if(eqs.indexOf(upperLimitEquation)==-1) + eqs.push(upperLimitEquation); + } else { + var idx = eqs.indexOf(upperLimitEquation); + if(idx != -1) eqs.splice(idx,1); + } + + if(this.lowerLimitEnabled && relAngle < lowerLimit){ + lowerLimitEquation.angle = lowerLimit; + if(eqs.indexOf(lowerLimitEquation)==-1) + eqs.push(lowerLimitEquation); + } else { + var idx = eqs.indexOf(lowerLimitEquation); + if(idx != -1) eqs.splice(idx,1); + } + + /* + + The constraint violation is + + g = xj + rj - xi - ri + + ...where xi and xj are the body positions and ri and rj world-oriented offset vectors. Differentiate: + + gdot = vj + wj x rj - vi - wi x ri + + We split this into x and y directions. (let x and y be unit vectors along the respective axes) + + gdot * x = ( vj + wj x rj - vi - wi x ri ) * x + = ( vj*x + (wj x rj)*x -vi*x -(wi x ri)*x + = ( vj*x + (rj x x)*wj -vi*x -(ri x x)*wi + = [ -x -(ri x x) x (rj x x)] * [vi wi vj wj] + = G*W + + ...and similar for y. We have then identified the jacobian entries for x and y directions: + + Gx = [ x (rj x x) -x -(ri x x)] + Gy = [ y (rj x y) -y -(ri x y)] + + */ + + vec2.rotate(worldPivotA, pivotA, bodyA.angle); + vec2.rotate(worldPivotB, pivotB, bodyB.angle); + + x.G[0] = -1; + x.G[1] = 0; + x.G[2] = -vec2.crossLength(worldPivotA,xAxis); + x.G[3] = 1; + x.G[4] = 0; + x.G[5] = vec2.crossLength(worldPivotB,xAxis); + + y.G[0] = 0; + y.G[1] = -1; + y.G[2] = -vec2.crossLength(worldPivotA,yAxis); + y.G[3] = 0; + y.G[4] = 1; + y.G[5] = vec2.crossLength(worldPivotB,yAxis); +}; + +/** + * Enable the rotational motor + * @method enableMotor + */ +RevoluteConstraint.prototype.enableMotor = function(){ + if(this.motorEnabled) return; + this.equations.push(this.motorEquation); + this.motorEnabled = true; +}; + +/** + * Disable the rotational motor + * @method disableMotor + */ +RevoluteConstraint.prototype.disableMotor = function(){ + if(!this.motorEnabled) return; + var i = this.equations.indexOf(this.motorEquation); + this.equations.splice(i,1); + this.motorEnabled = false; +}; + +/** + * Check if the motor is enabled. + * @method motorIsEnabled + * @return {Boolean} + */ +RevoluteConstraint.prototype.motorIsEnabled = function(){ + return !!this.motorEnabled; +}; + +/** + * Set the speed of the rotational constraint motor + * @method setMotorSpeed + * @param {Number} speed + */ +RevoluteConstraint.prototype.setMotorSpeed = function(speed){ + if(!this.motorEnabled) return; + var i = this.equations.indexOf(this.motorEquation); + this.equations[i].relativeVelocity = speed; +}; + +/** + * Get the speed of the rotational constraint motor + * @method getMotorSpeed + * @return {Number} The current speed, or false if the motor is not enabled. + */ +RevoluteConstraint.prototype.getMotorSpeed = function(){ + if(!this.motorEnabled) return false; + return this.motorEquation.relativeVelocity; +}; diff --git a/src/physics/advanced/constraints/RotationalLockEquation.js b/src/physics/advanced/constraints/RotationalLockEquation.js new file mode 100644 index 00000000..bcbd66a1 --- /dev/null +++ b/src/physics/advanced/constraints/RotationalLockEquation.js @@ -0,0 +1,37 @@ +var Equation = require("./Equation"), + vec2 = require('../math/vec2'); + +module.exports = RotationalLockEquation; + +/** + * Locks the relative angle between two bodies. The constraint tries to keep the dot product between two vectors, local in each body, to zero. The local angle in body i is a parameter. + * + * @class RotationalLockEquation + * @constructor + * @extends Equation + * @param {Body} bi + * @param {Body} bj + * @param {Object} options + * @param {Number} options.angle Angle to add to the local vector in body i. + */ +function RotationalLockEquation(bi,bj,options){ + options = options || {}; + Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE); + this.angle = options.angle || 0; + + var G = this.G; + G[2] = 1; + G[5] = -1; +}; +RotationalLockEquation.prototype = new Equation(); +RotationalLockEquation.prototype.constructor = RotationalLockEquation; + +var worldVectorA = vec2.create(), + worldVectorB = vec2.create(), + xAxis = vec2.fromValues(1,0), + yAxis = vec2.fromValues(0,1); +RotationalLockEquation.prototype.computeGq = function(){ + vec2.rotate(worldVectorA,xAxis,this.bi.angle+this.angle); + vec2.rotate(worldVectorB,yAxis,this.bj.angle); + return vec2.dot(worldVectorA,worldVectorB); +}; diff --git a/src/physics/advanced/constraints/RotationalVelocityEquation.js b/src/physics/advanced/constraints/RotationalVelocityEquation.js index 346d5a69..6b6f6514 100644 --- a/src/physics/advanced/constraints/RotationalVelocityEquation.js +++ b/src/physics/advanced/constraints/RotationalVelocityEquation.js @@ -13,58 +13,20 @@ module.exports = RotationalVelocityEquation; * @param {Body} bj */ function RotationalVelocityEquation(bi,bj){ - Equation.call(this,bi,bj,-1e6,1e6); + Equation.call(this,bi,bj,-Number.MAX_VALUE,Number.MAX_VALUE); this.relativeVelocity = 1; this.ratio = 1; }; RotationalVelocityEquation.prototype = new Equation(); RotationalVelocityEquation.prototype.constructor = RotationalVelocityEquation; RotationalVelocityEquation.prototype.computeB = function(a,b,h){ - var bi = this.bi, - bj = this.bj, - vi = bi.velocity, - wi = bi.angularVelocity, - taui = bi.angularForce, - vj = bj.velocity, - wj = bj.angularVelocity, - tauj = bj.angularForce, - invIi = bi.invInertia, - invIj = bj.invInertia, - Gq = 0, - GW = this.ratio * wj - wi + this.relativeVelocity, - GiMf = invIj*tauj - invIi*taui; + var G = this.G; + G[2] = -1; + G[5] = this.ratio; - var B = - Gq * a - GW * b - h*GiMf; + var GiMf = this.computeGiMf(); + var GW = this.computeGW() + this.relativeVelocity; + var B = - GW * b - h*GiMf; return B; }; - -// Compute C = GMG+eps in the SPOOK equation -RotationalVelocityEquation.prototype.computeC = function(eps){ - var bi = this.bi, - bj = this.bj; - - var C = bi.invInertia + bj.invInertia + eps; - - return C; -}; -var computeGWlambda_ulambda = vec2.create(); -RotationalVelocityEquation.prototype.computeGWlambda = function(){ - var bi = this.bi, - bj = this.bj; - - var GWlambda = bj.wlambda - bi.wlambda; - - return GWlambda; -}; - -var addToWlambda_temp = vec2.create(); -RotationalVelocityEquation.prototype.addToWlambda = function(deltalambda){ - var bi = this.bi, - bj = this.bj; - - // Add to angular velocity - bi.wlambda -= bi.invInertia * deltalambda; - bj.wlambda += bj.invInertia * deltalambda; -}; - diff --git a/src/physics/advanced/events/EventEmitter.js b/src/physics/advanced/events/EventEmitter.js index 35bfbc7d..576d2024 100644 --- a/src/physics/advanced/events/EventEmitter.js +++ b/src/physics/advanced/events/EventEmitter.js @@ -11,12 +11,12 @@ EventEmitter.prototype = { constructor: EventEmitter, /** - * Add an event listener - * @method on - * @param {String} type - * @param {Function} listener - * @return {EventEmitter} The self object, for chainability. - */ + * Add an event listener + * @method on + * @param {String} type + * @param {Function} listener + * @return {EventEmitter} The self object, for chainability. + */ on: function ( type, listener ) { if ( this._listeners === undefined ) this._listeners = {}; var listeners = this._listeners; @@ -30,12 +30,12 @@ EventEmitter.prototype = { }, /** - * Check if an event listener is added - * @method has - * @param {String} type - * @param {Function} listener - * @return {Boolean} - */ + * Check if an event listener is added + * @method has + * @param {String} type + * @param {Function} listener + * @return {Boolean} + */ has: function ( type, listener ) { if ( this._listeners === undefined ) return false; var listeners = this._listeners; @@ -46,12 +46,12 @@ EventEmitter.prototype = { }, /** - * Remove an event listener - * @method off - * @param {String} type - * @param {Function} listener - * @return {EventEmitter} The self object, for chainability. - */ + * Remove an event listener + * @method off + * @param {String} type + * @param {Function} listener + * @return {EventEmitter} The self object, for chainability. + */ off: function ( type, listener ) { if ( this._listeners === undefined ) return; var listeners = this._listeners; @@ -63,12 +63,12 @@ EventEmitter.prototype = { }, /** - * Emit an event. - * @method emit - * @param {Object} event - * @param {String} event.type - * @return {EventEmitter} The self object, for chainability. - */ + * Emit an event. + * @method emit + * @param {Object} event + * @param {String} event.type + * @return {EventEmitter} The self object, for chainability. + */ emit: function ( event ) { if ( this._listeners === undefined ) return; var listeners = this._listeners; diff --git a/src/physics/advanced/material/ContactMaterial.js b/src/physics/advanced/material/ContactMaterial.js index 0577e608..5824646c 100644 --- a/src/physics/advanced/material/ContactMaterial.js +++ b/src/physics/advanced/material/ContactMaterial.js @@ -17,65 +17,65 @@ function ContactMaterial(materialA, materialB, options){ options = options || {}; /** - * The contact material identifier - * @property id - * @type {Number} - */ + * The contact material identifier + * @property id + * @type {Number} + */ this.id = idCounter++; /** - * First material participating in the contact material - * @property materialA - * @type {Material} - */ + * First material participating in the contact material + * @property materialA + * @type {Material} + */ this.materialA = materialA; /** - * Second material participating in the contact material - * @property materialB - * @type {Material} - */ + * Second material participating in the contact material + * @property materialB + * @type {Material} + */ this.materialB = materialB; /** - * Friction to use in the contact of these two materials - * @property friction - * @type {Number} - */ + * Friction to use in the contact of these two materials + * @property friction + * @type {Number} + */ this.friction = typeof(options.friction) !== "undefined" ? Number(options.friction) : 0.3; /** - * Restitution to use in the contact of these two materials - * @property restitution - * @type {Number} - */ - this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.3; + * Restitution to use in the contact of these two materials + * @property restitution + * @type {Number} + */ + this.restitution = typeof(options.restitution) !== "undefined" ? Number(options.restitution) : 0.0; /** - * Stiffness of the resulting ContactEquation that this ContactMaterial generate - * @property stiffness - * @type {Number} - */ + * Stiffness of the resulting ContactEquation that this ContactMaterial generate + * @property stiffness + * @type {Number} + */ this.stiffness = typeof(options.stiffness) !== "undefined" ? Number(options.stiffness) : 1e7; /** - * Relaxation of the resulting ContactEquation that this ContactMaterial generate - * @property relaxation - * @type {Number} - */ + * Relaxation of the resulting ContactEquation that this ContactMaterial generate + * @property relaxation + * @type {Number} + */ this.relaxation = typeof(options.relaxation) !== "undefined" ? Number(options.relaxation) : 3; /** - * Stiffness of the resulting FrictionEquation that this ContactMaterial generate - * @property frictionStiffness - * @type {Number} - */ + * Stiffness of the resulting FrictionEquation that this ContactMaterial generate + * @property frictionStiffness + * @type {Number} + */ this.frictionStiffness = typeof(options.frictionStiffness) !== "undefined" ? Number(options.frictionStiffness) : 1e7; /** - * Relaxation of the resulting FrictionEquation that this ContactMaterial generate - * @property frictionRelaxation - * @type {Number} - */ + * Relaxation of the resulting FrictionEquation that this ContactMaterial generate + * @property frictionRelaxation + * @type {Number} + */ this.frictionRelaxation = typeof(options.frictionRelaxation) !== "undefined" ? Number(options.frictionRelaxation) : 3; }; diff --git a/src/physics/advanced/material/Material.js b/src/physics/advanced/material/Material.js index 7ae21ea2..7c878dd9 100644 --- a/src/physics/advanced/material/Material.js +++ b/src/physics/advanced/material/Material.js @@ -11,9 +11,9 @@ var idCounter = 0; */ function Material(){ /** - * The material identifier - * @property id - * @type {Number} - */ + * The material identifier + * @property id + * @type {Number} + */ this.id = idCounter++; }; diff --git a/src/physics/advanced/math/polyk.js b/src/physics/advanced/math/polyk.js index 6f4b4d5f..1431d909 100644 --- a/src/physics/advanced/math/polyk.js +++ b/src/physics/advanced/math/polyk.js @@ -194,7 +194,7 @@ i++; } } - if(iscs.length === 0) return [p.slice(0)]; + if(iscs.length == 0) return [p.slice(0)]; var comp = function(u,v) {return PolyK._P.dist(a,u) - PolyK._P.dist(a,v); } iscs.sort(comp); @@ -226,7 +226,7 @@ ps = PolyK._getPoints(ps, ind1, ind0); i0.flag = i1.flag = false; iscs.splice(0,2); - if(iscs.length === 0) pgs.push(ps); + if(iscs.length == 0) pgs.push(ps); } else { dir++; iscs.reverse(); } if(dir>1) break; @@ -402,7 +402,7 @@ var day = (a1.y-a2.y), dby = (b1.y-b2.y); var Den = dax*dby - day*dbx; - if (Den === 0) return null; // parallel + if (Den == 0) return null; // parallel var A = (a1.x * a2.y - a1.y * a2.x); var B = (b1.x * b2.y - b1.y * b2.x); @@ -424,7 +424,7 @@ var day = (a1.y-a2.y), dby = (b1.y-b2.y); var Den = dax*dby - day*dbx; - if (Den === 0) return null; // parallel + if (Den == 0) return null; // parallel var A = (a1.x * a2.y - a1.y * a2.x); var B = (b1.x * b2.y - b1.y * b2.x); @@ -472,6 +472,6 @@ PolyK._tp = []; for(var i=0; i<10; i++) PolyK._tp.push(new PolyK._P(0,0)); - */ + */ module.exports = PolyK; diff --git a/src/physics/advanced/objects/Body.js b/src/physics/advanced/objects/Body.js index 701df13d..e24c40cb 100644 --- a/src/physics/advanced/objects/Body.js +++ b/src/physics/advanced/objects/Body.js @@ -1,4 +1,6 @@ -var vec2 = require('../math/vec2'); +var vec2 = require('../math/vec2') +, decomp = require('poly-decomp') +, Convex = require('../shapes/Convex') module.exports = Body; @@ -25,156 +27,176 @@ function Body(options){ options = options || {}; /** - * The body identifyer - * @property id - * @type {Number} - */ + * The body identifyer + * @property id + * @type {Number} + */ this.id = ++Body._idCounter; /** - * The shapes of the body. The local transform of the shape in .shapes[i] is - * defined by .shapeOffsets[i] and .shapeAngles[i]. - * - * @property shapes - * @type {Array} - */ + * The shapes of the body. The local transform of the shape in .shapes[i] is + * defined by .shapeOffsets[i] and .shapeAngles[i]. + * + * @property shapes + * @type {Array} + */ this.shapes = []; /** - * The local shape offsets, relative to the body center of mass. This is an - * array of Float32Array. - * @property shapeOffsets - * @type {Array} - */ + * The local shape offsets, relative to the body center of mass. This is an + * array of Float32Array. + * @property shapeOffsets + * @type {Array} + */ this.shapeOffsets = []; /** - * The body-local shape angle transforms. This is an array of numbers (angles). - * @property shapeAngles - * @type {Array} - */ + * The body-local shape angle transforms. This is an array of numbers (angles). + * @property shapeAngles + * @type {Array} + */ this.shapeAngles = []; /** - * The mass of the body. - * @property mass - * @type {number} - */ + * The mass of the body. + * @property mass + * @type {number} + */ this.mass = options.mass || 0; /** - * The inverse mass of the body. - * @property invMass - * @type {number} - */ + * The inverse mass of the body. + * @property invMass + * @type {number} + */ this.invMass = 0; /** - * The inertia of the body around the Z axis. - * @property inertia - * @type {number} - */ + * The inertia of the body around the Z axis. + * @property inertia + * @type {number} + */ this.inertia = 0; /** - * The inverse inertia of the body. - * @property invInertia - * @type {number} - */ + * The inverse inertia of the body. + * @property invInertia + * @type {number} + */ this.invInertia = 0; this.updateMassProperties(); /** - * The position of the body - * @property position - * @type {Float32Array} - */ + * The position of the body + * @property position + * @type {Float32Array} + */ this.position = vec2.fromValues(0,0); if(options.position) vec2.copy(this.position, options.position); /** - * The velocity of the body - * @property velocity - * @type {Float32Array} - */ + * The velocity of the body + * @property velocity + * @type {Float32Array} + */ this.velocity = vec2.fromValues(0,0); if(options.velocity) vec2.copy(this.velocity, options.velocity); /** - * Constraint velocity that was added to the body during the last step. - * @property vlambda - * @type {Float32Array} - */ + * Constraint velocity that was added to the body during the last step. + * @property vlambda + * @type {Float32Array} + */ this.vlambda = vec2.fromValues(0,0); /** - * Angular constraint velocity that was added to the body during last step. - * @property wlambda - * @type {Float32Array} - */ + * Angular constraint velocity that was added to the body during last step. + * @property wlambda + * @type {Float32Array} + */ this.wlambda = 0; /** - * The angle of the body - * @property angle - * @type {number} - */ + * The angle of the body + * @property angle + * @type {number} + */ this.angle = options.angle || 0; /** - * The angular velocity of the body - * @property angularVelocity - * @type {number} - */ + * The angular velocity of the body + * @property angularVelocity + * @type {number} + */ this.angularVelocity = options.angularVelocity || 0; /** - * The force acting on the body - * @property force - * @type {Float32Array} - */ + * The force acting on the body + * @property force + * @type {Float32Array} + */ this.force = vec2.create(); if(options.force) vec2.copy(this.force, options.force); /** - * The angular force acting on the body - * @property angularForce - * @type {number} - */ + * The angular force acting on the body + * @property angularForce + * @type {number} + */ this.angularForce = options.angularForce || 0; /** - * The type of motion this body has. Should be one of: Body.STATIC (the body - * does not move), Body.DYNAMIC (body can move and respond to collisions) - * and Body.KINEMATIC (only moves according to its .velocity). - * - * @property motionState - * @type {number} - * - * @example - * // This body will move and interact with other bodies - * var dynamicBody = new Body(); - * dynamicBody.motionState = Body.DYNAMIC; - * - * @example - * // This body will not move at all - * var staticBody = new Body(); - * staticBody.motionState = Body.STATIC; - * - * @example - * // This body will only move if you change its velocity - * var kinematicBody = new Body(); - * kinematicBody.motionState = Body.KINEMATIC; - */ - this.motionState = this.mass === 0 ? Body.STATIC : Body.DYNAMIC; + * The linear damping acting on the body in the velocity direction + * @property damping + * @type {Number} + */ + this.damping = typeof(options.damping)=="number" ? options.damping : 0.1; /** - * Bounding circle radius - * @property boundingRadius - * @type {Number} - */ + * The angular force acting on the body + * @property angularDamping + * @type {Number} + */ + this.angularDamping = typeof(options.angularDamping)=="number" ? options.angularDamping : 0.1; + + /** + * The type of motion this body has. Should be one of: Body.STATIC (the body + * does not move), Body.DYNAMIC (body can move and respond to collisions) + * and Body.KINEMATIC (only moves according to its .velocity). + * + * @property motionState + * @type {number} + * + * @example + * // This body will move and interact with other bodies + * var dynamicBody = new Body(); + * dynamicBody.motionState = Body.DYNAMIC; + * + * @example + * // This body will not move at all + * var staticBody = new Body(); + * staticBody.motionState = Body.STATIC; + * + * @example + * // This body will only move if you change its velocity + * var kinematicBody = new Body(); + * kinematicBody.motionState = Body.KINEMATIC; + */ + this.motionState = this.mass == 0 ? Body.STATIC : Body.DYNAMIC; + + /** + * Bounding circle radius + * @property boundingRadius + * @type {Number} + */ this.boundingRadius = 0; + + this.concavePath = null; + + this.lastDampingScale = 1; + this.lastAngularDampingScale = 1; + this.lastDampingTimeStep = -1; }; Body._idCounter = 0; @@ -225,6 +247,15 @@ Body.prototype.updateBoundingRadius = function(){ * body.addShape(shape,[0,1],Math.PI/2); */ Body.prototype.addShape = function(shape,offset,angle){ + angle = angle || 0.0; + + // Copy the offset vector + if(offset){ + offset = vec2.fromValues(offset[0],offset[1]); + } else { + offset = vec2.fromValues(0,0); + } + this.shapes .push(shape); this.shapeOffsets.push(offset); this.shapeAngles .push(angle); @@ -232,6 +263,24 @@ Body.prototype.addShape = function(shape,offset,angle){ this.updateBoundingRadius(); }; +/** + * Remove a shape + * @method removeShape + * @param {Shape} shape + * @return {Boolean} True if the shape was found and removed, else false. + */ +Body.prototype.removeShape = function(shape){ + var idx = this.shapes.indexOf(shape); + + if(idx != -1){ + this.shapes.splice(idx,1); + this.shapeOffsets.splice(idx,1); + this.shapeAngles.splice(idx,1); + return true; + } else + return false; +}; + /** * Updates .inertia, .invMass, .invInertia for this Body. Should be called when * changing the structure or mass of the Body. @@ -305,6 +354,175 @@ Body.prototype.toWorldFrame = function(out, localPoint){ vec2.toGlobalFrame(out, localPoint, this.position, this.angle); }; +/** + * Reads a polygon shape path, and assembles convex shapes from that and puts them at proper offset points. + * @method fromPolygon + * @param {Array} path An array of 2d vectors, e.g. [[0,0],[0,1],...] that resembles a concave or convex polygon. The shape must be simple and without holes. + * @param {Object} [options] + * @param {Boolean} [options.optimalDecomp=false] Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices. + * @param {Boolean} [options.skipSimpleCheck=false] Set to true if you already know that the path is not intersecting itself. + * @param {Boolean|Number} [options.removeCollinearPoints=false] Set to a number (angle threshold value) to remove collinear points, or false to keep all points. + * @return {Boolean} True on success, else false. + */ +Body.prototype.fromPolygon = function(path,options){ + options = options || {}; + + // Remove all shapes + for(var i=this.shapes.length; i>=0; --i) + this.removeShape(this.shapes[i]); + + var p = new decomp.Polygon(); + p.vertices = path; + + // Make it counter-clockwise + p.makeCCW(); + + if(typeof(options.removeCollinearPoints)=="number"){ + p.removeCollinearPoints(options.removeCollinearPoints); + } + + // Check if any line segment intersects the path itself + if(typeof(options.skipSimpleCheck) == "undefined"){ + if(!p.isSimple()) return false; + } + + // Save this path for later + this.concavePath = p.vertices.slice(0); + for(var i=0; ithis for details. + * @method applyDamping + * @param {number} dt Current time step + */ +Body.prototype.applyDamping = function(dt){ + if(this.motionState & Body.DYNAMIC){ // Only for dynamic bodies + + // Since Math.pow generates garbage we check if we can reuse the scaling coefficient from last step + if(dt != this.lastDampingTimeStep){ + this.lastDampingScale = Math.pow(1.0 - this.damping,dt); + this.lastAngularDampingScale = Math.pow(1.0 - this.angularDamping,dt); + this.lastDampingTimeStep = dt; + } + + var v = this.velocity; + vec2.scale(v,v,this.lastDampingScale); + this.angularVelocity *= this.lastAngularDampingScale; + } +}; + /** * Dynamic body. * @property DYNAMIC diff --git a/src/physics/advanced/objects/Spring.js b/src/physics/advanced/objects/Spring.js index 8105a9c5..44f7131c 100644 --- a/src/physics/advanced/objects/Spring.js +++ b/src/physics/advanced/objects/Spring.js @@ -22,52 +22,52 @@ function Spring(bodyA,bodyB,options){ options = options || {}; /** - * Rest length of the spring. - * @property restLength - * @type {number} - */ + * Rest length of the spring. + * @property restLength + * @type {number} + */ this.restLength = typeof(options.restLength)=="number" ? options.restLength : 1; /** - * Stiffness of the spring. - * @property stiffness - * @type {number} - */ + * Stiffness of the spring. + * @property stiffness + * @type {number} + */ this.stiffness = options.stiffness || 100; /** - * Damping of the spring. - * @property damping - * @type {number} - */ + * Damping of the spring. + * @property damping + * @type {number} + */ this.damping = options.damping || 1; /** - * First connected body. - * @property bodyA - * @type {Body} - */ + * First connected body. + * @property bodyA + * @type {Body} + */ this.bodyA = bodyA; /** - * Second connected body. - * @property bodyB - * @type {Body} - */ + * Second connected body. + * @property bodyB + * @type {Body} + */ this.bodyB = bodyB; /** - * Anchor for bodyA in local bodyA coordinates. - * @property localAnchorA - * @type {Array} - */ + * Anchor for bodyA in local bodyA coordinates. + * @property localAnchorA + * @type {Array} + */ this.localAnchorA = vec2.fromValues(0,0); /** - * Anchor for bodyB in local bodyB coordinates. - * @property localAnchorB - * @type {Array} - */ + * Anchor for bodyB in local bodyB coordinates. + * @property localAnchorB + * @type {Array} + */ this.localAnchorB = vec2.fromValues(0,0); if(options.localAnchorA) vec2.copy(this.localAnchorA, options.localAnchorA); diff --git a/src/physics/advanced/p2.js b/src/physics/advanced/p2.js index 10be36da..00b27f83 100644 --- a/src/physics/advanced/p2.js +++ b/src/physics/advanced/p2.js @@ -17,11 +17,12 @@ module.exports = { Island : require('./solver/IslandSolver'), IslandSolver : require('./solver/IslandSolver'), Line : require('./shapes/Line'), + LockConstraint : require('./constraints/LockConstraint'), Material : require('./material/Material'), NaiveBroadphase : require('./collision/NaiveBroadphase'), Particle : require('./shapes/Particle'), Plane : require('./shapes/Plane'), - PointToPointConstraint : require('./constraints/PointToPointConstraint'), + RevoluteConstraint : require('./constraints/RevoluteConstraint'), PrismaticConstraint : require('./constraints/PrismaticConstraint'), Rectangle : require('./shapes/Rectangle'), RotationalVelocityEquation : require('./constraints/RotationalVelocityEquation'), diff --git a/src/physics/advanced/serializer/Serializer.js b/src/physics/advanced/serializer/Serializer.js new file mode 100644 index 00000000..81f80e23 --- /dev/null +++ b/src/physics/advanced/serializer/Serializer.js @@ -0,0 +1,146 @@ +var World = require('../world/World') + +var num = { type:"number", required:true }; + +/* + * Serialize a World instance to JSON + * @method serialize + * @param {World} world + * @return {Object} + */ +exports.serialize = function(world){ + return {}; +}; + +/* + * Load a World instance from JSON + * @param {Object} json + * @return {World} + */ +exports.deserialize = function(json){ + var world = new World(); + return world; +}; + +var schemas = exports.schemas = {}; + +schemas['0.3.0'] = { + type: "object", + additionalProperties:false, + properties: { + gravity: { $ref:"vec2" }, + p2: { type:"string", pattern:"^0.3$", required:true }, + solver: { type:"object", required:true }, + broadphase: { type:"object", required:true }, + bodies: { + type:"array", + required:true, + additionalItems:false, + items:{ + type:"object", + additionalProperties:false, + properties:{ + id : num, + mass : num, + angle : num, + position : { $ref:"vec2" }, + velocity : { $ref:"vec2" }, + angularVelocity : num, + force : { $ref:"vec2" }, + shapes : { required:true, type:"array" }, + concavePath : { required:true, type:["array","null"] }, + }, + } + }, + springs: { + type:"array", + required:true, + additionalItems:false, + items:{ + type:"object", + additionalProperties:false, + properties:{ + bodyA : num, + bodyB : num, + stiffness : num, + damping : num, + restLength : num, + localAnchorA : { $ref:"vec2" }, + localAnchorB : { $ref:"vec2" }, + }, + }, + }, + constraints: { + type:"array", + required:true, + items:[{ + type:"object", + additionalProperties:false, + properties:{ + bodyA: num, + bodyB: num, + type: { type:"string", match:"^DistanceConstraint$" }, + distance: num, + maxForce: num, + }, + },{ + type:"object", + additionalProperties:false, + properties:{ + bodyA: num, + bodyB: num, + type: { type:"string", match:"^PrismaticConstraint$" }, + localAxisA: { $ref:"vec2" }, + localAxisB: { $ref:"vec2" }, + maxForce: num, + }, + },{ + type:"object", + additionalProperties:false, + properties:{ + bodyA: num, + bodyB: num, + type: { type:"string", match:'^RevoluteConstraint$' }, + pivotA: { $ref:"vec2" }, + pivotB: { $ref:"vec2" }, + maxForce: num, + motorSpeed: { type:["number","boolean"] }, + lowerLimit: num, + lowerLimitEnabled: { type:"boolean" }, + upperLimit: num, + upperLimitEnabled: { type:"boolean" }, + }, + }], + }, + contactMaterials: { + type:"array", + required:true, + additionalItems:false, + items: { + properties : { + id: num, + materialA: num, + materialB: num, + friction: num, + restitution: num, + stiffness: num, + relaxation: num, + frictionStiffness: num, + frictionRelaxation: num, + } + } + }, + } +}; + +exports.vec2 = { + id: "/vec2", + type:"array", + maxItems:2, + minItems:2, + items:{ + type:"number", + }, + additionalItems:false, + required:true, +}; diff --git a/src/physics/advanced/shapes/Circle.js b/src/physics/advanced/shapes/Circle.js index 9c2a1aeb..736ddda6 100644 --- a/src/physics/advanced/shapes/Circle.js +++ b/src/physics/advanced/shapes/Circle.js @@ -7,15 +7,15 @@ module.exports = Circle; * @class Circle * @extends {Shape} * @constructor - * @param {number} radius + * @param {number} radius The radius of this circle */ function Circle(radius){ /** - * The radius of the circle. - * @property radius - * @type {number} - */ + * The radius of the circle. + * @property radius + * @type {number} + */ this.radius = radius || 1; Shape.call(this,Shape.CIRCLE); diff --git a/src/physics/advanced/shapes/Convex.js b/src/physics/advanced/shapes/Convex.js index 61c4912d..8a591542 100644 --- a/src/physics/advanced/shapes/Convex.js +++ b/src/physics/advanced/shapes/Convex.js @@ -1,6 +1,7 @@ var Shape = require('./Shape') , vec2 = require('../math/vec2') , polyk = require('../math/polyk') +, decomp = require('poly-decomp') module.exports = Convex; @@ -14,24 +15,31 @@ module.exports = Convex; function Convex(vertices){ /** - * Vertices defined in the local frame. - * @property vertices - * @type {Array} - */ + * Vertices defined in the local frame. + * @property vertices + * @type {Array} + */ this.vertices = vertices || []; + // Copy the verts + for(var i=0; i h=2*a/b - // Get inertia for this triangle: http://answers.yahoo.com/question/index?qid=20080721030038AA3oE1m - var I_triangle = (base * (Math.pow(height,3))) / 36; - // Get mass for the triangle - var m = base*height/2 * density; + var m = area_triangle * density; + + // Get inertia for this triangle: http://answers.yahoo.com/question/index?qid=20080721030038AA3oE1m + var I_triangle = m*(base * (Math.pow(height,3))) / 36; // Add to total inertia using parallel axis theorem var r2 = vec2.squaredLength(centroid); @@ -211,3 +230,26 @@ Convex.prototype.updateBoundingRadius = function(){ this.boundingRadius = Math.sqrt(r2); }; +/** + * Update the .area + * @method updateArea + */ +Convex.prototype.updateArea = function(){ + this.updateTriangles(); + this.area = 0; + + var triangles = this.triangles, + verts = this.vertices; + for(var i=0; i!==triangles.length; i++){ + var t = triangles[i], + a = verts[t[0]], + b = verts[t[1]], + c = verts[t[2]]; + + // Get mass for the triangle (density=1 in this case) + // http://math.stackexchange.com/questions/80198/area-of-triangle-via-vectors + var m = decomp.Point.area(a,b,c) + this.area += m; + } +}; + diff --git a/src/physics/advanced/shapes/Line.js b/src/physics/advanced/shapes/Line.js index 630523eb..5a19c33e 100644 --- a/src/physics/advanced/shapes/Line.js +++ b/src/physics/advanced/shapes/Line.js @@ -5,16 +5,17 @@ module.exports = Line; /** * Line shape class. The line shape is along the x direction, and stretches from [-length/2, 0] to [length/2,0]. * @class Line + * @param {Number} length The total length of the line * @extends {Shape} * @constructor */ function Line(length){ /** - * Length of this line - * @property length - * @type {Number} - */ + * Length of this line + * @property length + * @type {Number} + */ this.length = length; Shape.call(this,Shape.LINE); diff --git a/src/physics/advanced/shapes/Plane.js b/src/physics/advanced/shapes/Plane.js index 187adcc7..7433c4c9 100644 --- a/src/physics/advanced/shapes/Plane.js +++ b/src/physics/advanced/shapes/Plane.js @@ -12,10 +12,19 @@ function Plane(){ Shape.call(this,Shape.PLANE); }; Plane.prototype = new Shape(); + +/** + * Compute moment of inertia + * @method computeMomentOfInertia + */ Plane.prototype.computeMomentOfInertia = function(mass){ return 0; // Plane is infinite. The inertia should therefore be infinty but by convention we set 0 here }; +/** + * Update the bounding radius + * @method updateBoundingRadius + */ Plane.prototype.updateBoundingRadius = function(){ this.boundingRadius = Number.MAX_VALUE; }; diff --git a/src/physics/advanced/shapes/Rectangle.js b/src/physics/advanced/shapes/Rectangle.js index 6c4c4507..35e20572 100644 --- a/src/physics/advanced/shapes/Rectangle.js +++ b/src/physics/advanced/shapes/Rectangle.js @@ -8,6 +8,8 @@ module.exports = Rectangle; * Rectangle shape class. * @class Rectangle * @constructor + * @param {Number} w Width + * @param {Number} h Height * @extends {Convex} */ function Rectangle(w,h){ @@ -16,7 +18,18 @@ function Rectangle(w,h){ vec2.fromValues( w/2, h/2), vec2.fromValues(-w/2, h/2)]; + /** + * Total width of the rectangle + * @property width + * @type {Number} + */ this.width = w; + + /** + * Total height of the rectangle + * @property height + * @type {Number} + */ this.height = h; Convex.call(this,verts); @@ -35,6 +48,10 @@ Rectangle.prototype.computeMomentOfInertia = function(mass){ return mass * (h*h + w*w) / 12; }; +/** + * Update the bounding radius + * @method updateBoundingRadius + */ Rectangle.prototype.updateBoundingRadius = function(){ var w = this.width, h = this.height; diff --git a/src/physics/advanced/shapes/Shape.js b/src/physics/advanced/shapes/Shape.js index b0a9d708..2f79f19a 100644 --- a/src/physics/advanced/shapes/Shape.js +++ b/src/physics/advanced/shapes/Shape.js @@ -9,57 +9,66 @@ function Shape(type){ this.type = type; /** - * Bounding circle radius of this shape - * @property boundingRadius - * @type {Number} - */ + * Bounding circle radius of this shape + * @property boundingRadius + * @type {Number} + */ this.boundingRadius = 0; /** - * Collision group that this shape belongs to (bit mask). See this tutorial. - * @property collisionGroup - * @type {Number} - * @example - * // Setup bits for each available group - * var PLAYER = Math.pow(2,0), - * ENEMY = Math.pow(2,1), - * GROUND = Math.pow(2,2) - * - * // Put shapes into their groups - * player1Shape.collisionGroup = PLAYER; - * player2Shape.collisionGroup = PLAYER; - * enemyShape .collisionGroup = ENEMY; - * groundShape .collisionGroup = GROUND; - * - * // Assign groups that each shape collide with. - * // Note that the players can collide with ground and enemies, but not with other players. - * player1Shape.collisionMask = ENEMY | GROUND; - * player2Shape.collisionMask = ENEMY | GROUND; - * enemyShape .collisionMask = PLAYER | GROUND; - * groundShape .collisionMask = PLAYER | ENEMY; - * - * @example - * // How collision check is done - * if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){ - * // The shapes will collide - * } - */ + * Collision group that this shape belongs to (bit mask). See this tutorial. + * @property collisionGroup + * @type {Number} + * @example + * // Setup bits for each available group + * var PLAYER = Math.pow(2,0), + * ENEMY = Math.pow(2,1), + * GROUND = Math.pow(2,2) + * + * // Put shapes into their groups + * player1Shape.collisionGroup = PLAYER; + * player2Shape.collisionGroup = PLAYER; + * enemyShape .collisionGroup = ENEMY; + * groundShape .collisionGroup = GROUND; + * + * // Assign groups that each shape collide with. + * // Note that the players can collide with ground and enemies, but not with other players. + * player1Shape.collisionMask = ENEMY | GROUND; + * player2Shape.collisionMask = ENEMY | GROUND; + * enemyShape .collisionMask = PLAYER | GROUND; + * groundShape .collisionMask = PLAYER | ENEMY; + * + * @example + * // How collision check is done + * if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){ + * // The shapes will collide + * } + */ this.collisionGroup = 1; /** - * Collision mask of this shape. See .collisionGroup. - * @property collisionMask - * @type {Number} - */ + * Collision mask of this shape. See .collisionGroup. + * @property collisionMask + * @type {Number} + */ this.collisionMask = 1; if(type) this.updateBoundingRadius(); /** - * Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead. - * @property material - * @type {Material} - */ + * Material to use in collisions for this Shape. If this is set to null, the world will use default material properties instead. + * @property material + * @type {Material} + */ this.material = null; + + /** + * Area of this shape. + * @property area + * @type {Number} + */ + this.area = 0; + + this.updateArea(); }; Shape.CIRCLE = 1; @@ -88,3 +97,11 @@ Shape.prototype.computeMomentOfInertia = function(mass){ Shape.prototype.updateBoundingRadius = function(){ throw new Error("Shape.updateBoundingRadius is not implemented in this Shape..."); }; + +/** + * Update the .area property of the shape. + * @method updateArea + */ +Shape.prototype.updateArea = function(){ + // To be implemented in all subclasses +}; diff --git a/src/physics/advanced/solver/GSSolver.js b/src/physics/advanced/solver/GSSolver.js index b5f6e761..5925d7c5 100644 --- a/src/physics/advanced/solver/GSSolver.js +++ b/src/physics/advanced/solver/GSSolver.js @@ -1,10 +1,10 @@ -var vec2 = require('../math/vec2'), - Solver = require('./Solver'); +var vec2 = require('../math/vec2') +, Solver = require('./Solver') +, Utils = require('../utils/Utils') +, FrictionEquation = require('../constraints/FrictionEquation') module.exports = GSSolver; -var ARRAY_TYPE = Float32Array || Array; - /** * Iterative Gauss-Seidel constraint equation solver. * @@ -19,59 +19,66 @@ var ARRAY_TYPE = Float32Array || Array; * @param {Number} options.tolerance */ function GSSolver(options){ - Solver.call(this); + Solver.call(this,options); options = options || {}; - this.iterations = options.iterations || 10; - this.tolerance = options.tolerance || 0; - this.debug = options.debug || false; - this.arrayStep = 30; - this.lambda = new ARRAY_TYPE(this.arrayStep); - this.Bs = new ARRAY_TYPE(this.arrayStep); - this.invCs = new ARRAY_TYPE(this.arrayStep); /** - * Whether to use .stiffness and .relaxation parameters from the Solver instead of each Equation individually. - * @type {Boolean} - * @property useGlobalEquationParameters - */ + * The number of iterations to do when solving. More gives better results, but is more expensive. + * @property iterations + * @type {Number} + */ + this.iterations = options.iterations || 10; + + /** + * The error tolerance. If the total error is below this limit, the solver will stop. Set to zero for as good solution as possible. + * @property tolerance + * @type {Number} + */ + this.tolerance = options.tolerance || 0; + + this.debug = options.debug || false; + this.arrayStep = 30; + this.lambda = new Utils.ARRAY_TYPE(this.arrayStep); + this.Bs = new Utils.ARRAY_TYPE(this.arrayStep); + this.invCs = new Utils.ARRAY_TYPE(this.arrayStep); + + /** + * Whether to use .stiffness and .relaxation parameters from the Solver instead of each Equation individually. + * @type {Boolean} + * @property useGlobalEquationParameters + */ this.useGlobalEquationParameters = true; /** - * Global equation stiffness. - * @property stiffness - * @type {Number} - */ + * Global equation stiffness. Larger number gives harder contacts, etc, but may also be more expensive to compute, or it will make your simulation explode. + * @property stiffness + * @type {Number} + */ this.stiffness = 1e6; /** - * Global equation relaxation. - * @property relaxation - * @type {Number} - */ + * Global equation relaxation. This is the number of timesteps required for a constraint to be resolved. Larger number will give softer contacts. Set to around 3 or 4 for good enough results. + * @property relaxation + * @type {Number} + */ this.relaxation = 4; /** - * Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications. - * @property useZeroRHS - * @type {Boolean} - */ + * Set to true to set all right hand side terms to zero when solving. Can be handy for a few applications. + * @property useZeroRHS + * @type {Boolean} + */ this.useZeroRHS = false; + + /** + * Number of friction iterations to skip. If .skipFrictionIterations=2, then no FrictionEquations will be iterated until the third iteration. + * @property skipFrictionIterations + * @type {Number} + */ + this.skipFrictionIterations = 2; }; GSSolver.prototype = new Solver(); -/** - * Set stiffness parameters - * - * @method setSpookParams - * @param {number} k - * @param {number} d - * @deprecated - */ -GSSolver.prototype.setSpookParams = function(k,d){ - this.stiffness = k; - this.relaxation = d; -}; - /** * Solve the system of equations * @method solve @@ -79,8 +86,12 @@ GSSolver.prototype.setSpookParams = function(k,d){ * @param {World} world World to solve */ GSSolver.prototype.solve = function(dt,world){ + + this.sortEquations(); + var iter = 0, maxIter = this.iterations, + skipFrictionIter = this.skipFrictionIterations, tolSquared = this.tolerance*this.tolerance, equations = this.equations, Neq = equations.length, @@ -99,9 +110,9 @@ GSSolver.prototype.solve = function(dt,world){ // Things that does not change during iteration can be computed once if(this.lambda.length < Neq){ - this.lambda = new ARRAY_TYPE(Neq + this.arrayStep); - this.Bs = new ARRAY_TYPE(Neq + this.arrayStep); - this.invCs = new ARRAY_TYPE(Neq + this.arrayStep); + this.lambda = new Utils.ARRAY_TYPE(Neq + this.arrayStep); + this.Bs = new Utils.ARRAY_TYPE(Neq + this.arrayStep); + this.invCs = new Utils.ARRAY_TYPE(Neq + this.arrayStep); } var invCs = this.invCs, Bs = this.Bs, @@ -130,9 +141,7 @@ GSSolver.prototype.solve = function(dt,world){ // Reset vlambda for(i=0; i!==Nbodies; i++){ - var b=bodies[i], vlambda=b.vlambda; - set(vlambda,0,0); - b.wlambda = 0; + bodies[i].resetConstraintVelocity(); } // Iterate over equations @@ -142,49 +151,59 @@ GSSolver.prototype.solve = function(dt,world){ deltalambdaTot = 0.0; for(j=0; j!==Neq; j++){ - c = equations[j]; + if(c instanceof FrictionEquation && iter < skipFrictionIter) + continue; + var _eps = useGlobalParams ? eps : c.eps; - // Compute iteration - maxForce = c.maxForce; - minForce = c.minForce; - - B = Bs[j]; - invC = invCs[j]; - lambdaj = lambda[j]; - GWlambda = c.computeGWlambda(_eps); - - if(useZeroRHS) B = 0; - - deltalambda = invC * ( B - GWlambda - _eps * lambdaj ); - - // Clamp if we are not within the min/max interval - lambdaj_plus_deltalambda = lambdaj + deltalambda; - if(lambdaj_plus_deltalambda < minForce){ - deltalambda = minForce - lambdaj; - } else if(lambdaj_plus_deltalambda > maxForce){ - deltalambda = maxForce - lambdaj; - } - lambda[j] += deltalambda; - - deltalambdaTot += Math.abs(deltalambda); - - c.addToWlambda(deltalambda); + var deltalambda = GSSolver.iterateEquation(j,c,_eps,Bs,invCs,lambda,useZeroRHS,dt); + if(tolSquared !== 0) deltalambdaTot += Math.abs(deltalambda); } // If the total error is small enough - stop iterate - if(deltalambdaTot*deltalambdaTot <= tolSquared) break; + if(tolSquared !== 0 && deltalambdaTot*deltalambdaTot <= tolSquared) break; } // Add result to velocity for(i=0; i!==Nbodies; i++){ - var b=bodies[i], v=b.velocity; - add( v, v, b.vlambda); - b.angularVelocity += b.wlambda; + bodies[i].addConstraintVelocity(); } } errorTot = deltalambdaTot; }; +GSSolver.iterateEquation = function(j,eq,eps,Bs,invCs,lambda,useZeroRHS,dt){ + // Compute iteration + var B = Bs[j], + invC = invCs[j], + lambdaj = lambda[j], + GWlambda = eq.computeGWlambda(eps); + + if(eq instanceof FrictionEquation){ + // Rescale the max friction force according to the normal force + eq.maxForce = eq.contactEquation.multiplier * eq.frictionCoefficient * dt; + eq.minForce = -eq.contactEquation.multiplier * eq.frictionCoefficient * dt; + } + + var maxForce = eq.maxForce, + minForce = eq.minForce; + + if(useZeroRHS) B = 0; + + var deltalambda = invC * ( B - GWlambda - eps * lambdaj ); + + // Clamp if we are not within the min/max interval + var lambdaj_plus_deltalambda = lambdaj + deltalambda; + if(lambdaj_plus_deltalambda < minForce){ + deltalambda = minForce - lambdaj; + } else if(lambdaj_plus_deltalambda > maxForce){ + deltalambda = maxForce - lambdaj; + } + lambda[j] += deltalambda; + eq.multiplier = lambda[j] / dt; + eq.addToWlambda(deltalambda); + + return deltalambda; +}; diff --git a/src/physics/advanced/solver/Island.js b/src/physics/advanced/solver/Island.js index ee5261ca..b3c8635d 100644 --- a/src/physics/advanced/solver/Island.js +++ b/src/physics/advanced/solver/Island.js @@ -8,17 +8,17 @@ module.exports = Island; function Island(){ /** - * Current equations in this island. - * @property equations - * @type {Array} - */ + * Current equations in this island. + * @property equations + * @type {Array} + */ this.equations = []; /** - * Current bodies in this island. - * @property bodies - * @type {Array} - */ + * Current bodies in this island. + * @property bodies + * @type {Array} + */ this.bodies = []; } diff --git a/src/physics/advanced/solver/IslandSolver.js b/src/physics/advanced/solver/IslandSolver.js index 73c5b79c..a32dd9e7 100644 --- a/src/physics/advanced/solver/IslandSolver.js +++ b/src/physics/advanced/solver/IslandSolver.js @@ -12,30 +12,41 @@ module.exports = IslandSolver; * @class IslandSolver * @constructor * @param {Solver} subsolver + * @param {Object} options * @extends Solver */ -function IslandSolver(subsolver){ - Solver.call(this); +function IslandSolver(subsolver,options){ + Solver.call(this,options); var that = this; /** - * The solver used in the workers. - * @property subsolver - * @type {Solver} - */ + * The solver used in the workers. + * @property subsolver + * @type {Solver} + */ this.subsolver = subsolver; /** - * Number of islands - * @property numIslands - * @type {number} - */ + * Number of islands. Read only. + * @property numIslands + * @type {number} + */ this.numIslands = 0; // Pooling of node objects saves some GC load this._nodePool = []; + + /** + * Fires before an island is solved. + * @event beforeSolveIsland + * @param {Island} island + */ + this.beforeSolveIslandEvent = { + type : "beforeSolveIsland", + island : null, + }; }; -IslandSolver.prototype = new Object(Solver.prototype); +IslandSolver.prototype = new Solver(); function getUnvisitedNode(nodes){ var Nnodes = nodes.length; @@ -153,7 +164,11 @@ IslandSolver.prototype.solve = function(dt,world){ this.numIslands = n; // Solve islands + var e = this.beforeSolveIslandEvent; for(var i=0; i 0) - reducedMass = 1/reducedMass; - - var mu = this.defaultFriction; + var mu = this.defaultFriction, + restitution = 0.0; if(si.material && sj.material){ var cm = this.getContactMaterial(si.material,sj.material); if(cm){ mu = cm.friction; + restitution = cm.restitution; } } - var mug = mu * glen * reducedMass, - doFriction = mu > 0; - - // Get world position and angle of each shape - rotate(xiw, xi, bi.angle); - rotate(xjw, xj, bj.angle); - add(xiw, xiw, bi.position); - add(xjw, xjw, bj.position); - var aiw = ai + bi.angle; - var ajw = aj + bj.angle; - - // Run nearphase - np.enableFriction = mu > 0; - np.slipForce = mug; - if(si instanceof Circle){ - if(sj instanceof Circle) np.circleCircle (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Particle) np.circleParticle(bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Plane) np.circlePlane (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Rectangle) np.circleConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Convex) np.circleConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Line) np.circleLine (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Capsule) np.circleCapsule (bi,si,xiw,aiw, bj,sj,xjw,ajw); - - } else if(si instanceof Particle){ - if(sj instanceof Circle) np.circleParticle (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Plane) np.particlePlane (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Rectangle) np.particleConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Convex) np.particleConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Capsule) np.particleCapsule (bi,si,xiw,aiw, bj,sj,xjw,ajw); - - } else if(si instanceof Plane){ - if(sj instanceof Circle) np.circlePlane (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Particle) np.particlePlane (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Rectangle) np.convexPlane (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Convex) np.convexPlane (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Line) np.planeLine (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Capsule) np.capsulePlane (bj,sj,xjw,ajw, bi,si,xiw,aiw); - - } else if(si instanceof Rectangle){ - if(sj instanceof Plane) np.convexPlane (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Circle) np.circleConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Rectangle) np.convexConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Convex) np.convexConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Particle) np.particleConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw); - - } else if(si instanceof Convex){ - if(sj instanceof Plane) np.convexPlane (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Circle) np.circleConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Rectangle) np.convexConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Convex) np.convexConvex (bi,si,xiw,aiw, bj,sj,xjw,ajw); - else if(sj instanceof Particle) np.particleConvex (bj,sj,xjw,ajw, bi,si,xiw,aiw); - - } else if(si instanceof Line){ - if(sj instanceof Circle) np.circleLine (bj,sj,xjw,ajw, bi,si,xiw,aiw); - else if(sj instanceof Plane) np.planeLine (bj,sj,xjw,ajw, bi,si,xiw,aiw); - - } else if(si instanceof Capsule){ - if(sj instanceof Plane) np.capsulePlane (bi,si,xiw,aiw, bj,sj,xjw,ajw); - if(sj instanceof Circle) np.circleCapsule (bj,sj,xjw,ajw, bi,si,xiw,aiw); - if(sj instanceof Particle) np.particleCapsule(bj,sj,xjw,ajw, bi,si,xiw,aiw); - - } + World.runNarrowphase(np,bi,si,xi,ai,bj,sj,xj,aj,mu,glen,restitution); } } } @@ -413,28 +430,13 @@ World.prototype.step = function(dt){ var body = bodies[i]; if(body.mass>0){ - var minv = body.invMass, - f = body.force, - pos = body.position, - velo = body.velocity; - - // Angular step - body.angularVelocity += body.angularForce * body.invInertia * dt; - body.angle += body.angularVelocity * dt; - - // Linear step - scale(fhMinv,f,dt*minv); - add(velo,fhMinv,velo); - scale(velodt,velo,dt); - add(pos,pos,velodt); + World.integrateBody(body,dt); } } // Reset force for(var i=0; i!==Nbodies; i++){ - var bi = bodies[i]; - vec2.set(bi.force,0.0,0.0); - bi.angularForce = 0.0; + bodies[i].setZeroForce(); } if(doProfiling){ @@ -442,9 +444,104 @@ World.prototype.step = function(dt){ that.lastStepTime = t1-t0; } + // Emit impact event + if(this.emitImpactEvent){ + var ev = this.impactEvent; + for(var i=0; i!==np.contactEquations.length; i++){ + var eq = np.contactEquations[i]; + if(eq.firstImpact){ + ev.bodyA = eq.bi; + ev.bodyB = eq.bj; + this.emit(ev); + } + } + } + + // Increment time + this.time += dt; + this.emit(this.postStepEvent); }; +var ib_fhMinv = vec2.create(); +var ib_velodt = vec2.create(); + +/** + * Move a body forward in time. + * @static + * @method integrateBody + * @param {Body} body + * @param {Number} dt + */ +World.integrateBody = function(body,dt){ + var minv = body.invMass, + f = body.force, + pos = body.position, + velo = body.velocity; + + // Angular step + body.angularVelocity += body.angularForce * body.invInertia * dt; + body.angle += body.angularVelocity * dt; + + // Linear step + vec2.scale(ib_fhMinv,f,dt*minv); + vec2.add(velo,ib_fhMinv,velo); + vec2.scale(ib_velodt,velo,dt); + vec2.add(pos,pos,ib_velodt); +}; + +/** + * Runs narrowphase for the shape pair i and j. + * @static + * @method runNarrowphase + * @param {Narrowphase} np + * @param {Body} bi + * @param {Shape} si + * @param {Array} xi + * @param {Number} ai + * @param {Body} bj + * @param {Shape} sj + * @param {Array} xj + * @param {Number} aj + * @param {Number} mu + * @param {Number} glen + */ +World.runNarrowphase = function(np,bi,si,xi,ai,bj,sj,xj,aj,mu,glen,restitution){ + + if(!((si.collisionGroup & sj.collisionMask) !== 0 && (sj.collisionGroup & si.collisionMask) !== 0)) + return; + + var reducedMass = bi.invMass + bj.invMass; + if(reducedMass > 0) + reducedMass = 1/reducedMass; + + var mug = mu * glen * reducedMass, + doFriction = mu > 0; + + // Get world position and angle of each shape + vec2.rotate(xiw, xi, bi.angle); + vec2.rotate(xjw, xj, bj.angle); + vec2.add(xiw, xiw, bi.position); + vec2.add(xjw, xjw, bj.position); + var aiw = ai + bi.angle; + var ajw = aj + bj.angle; + + // Run narrowphase + np.enableFriction = mu > 0; + np.slipForce = mug; + np.frictionCoefficient = mu; + np.restitution = restitution; + + var resolver = np[si.type | sj.type]; + if (resolver) { + if (si.type < sj.type) { + resolver.call(np, bi,si,xiw,aiw, bj,sj,xjw,ajw); + } else { + resolver.call(np, bj,sj,xjw,ajw, bi,si,xiw,aiw); + } + } +}; + /** * Add a spring to the simulation * @@ -498,10 +595,26 @@ World.prototype.removeBody = function(body){ if(idx!==-1){ this.bodies.splice(idx,1); this.removeBodyEvent.body = body; + body.resetConstraintVelocity(); this.emit(this.removeBodyEvent); } }; +/** + * Get a body by its id. + * @method getBodyById + * @return {Body|Boolean} The body, or false if it was not found. + */ +World.prototype.getBodyById = function(id){ + var bodies = this.bodies; + for(var i=0; i=0; i--){ @@ -801,6 +998,12 @@ World.prototype.clear = function(){ for(var i=springs.length-1; i>=0; i--){ this.removeSpring(springs[i]); } + + // Remove all contact materials + var cms = this.contactMaterials; + for(var i=cms.length-1; i>=0; i--){ + this.removeContactMaterial(cms[i]); + } }; /** @@ -839,7 +1042,7 @@ World.prototype.hitTest = function(worldPoint,bodies,precision){ tmp = hitTest_tmp2; pb.addShape(ps); - var n = this.nearphase, + var n = this.narrowphase, result = []; // Check bodies diff --git a/src/physics/arcade/ArcadePhysics.js b/src/physics/arcade/ArcadePhysics.js index 7dd28e68..7c21b646 100644 --- a/src/physics/arcade/ArcadePhysics.js +++ b/src/physics/arcade/ArcadePhysics.js @@ -200,7 +200,7 @@ Phaser.Physics.Arcade.prototype = { * @param {number} velocity - Any component of velocity (e.g. 20). * @param {number} acceleration - Rate at which the velocity is changing. * @param {number} drag - Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set. - * @param {number} mMax - An absolute value cap for the velocity. + * @param {number} [max=10000] - An absolute value cap for the velocity. * @return {number} The altered Velocity value. */ computeVelocity: function (axis, body, velocity, acceleration, drag, max) { diff --git a/src/physics/arcade/Body.js b/src/physics/arcade/Body.js index a7dd54ac..145f293d 100644 --- a/src/physics/arcade/Body.js +++ b/src/physics/arcade/Body.js @@ -392,11 +392,10 @@ Phaser.Physics.Arcade.Body.prototype = { this.checkWorldBounds(); } - this.hull.setTo(this.preX, this.preY, this.width, this.height); - + // this.hull.setTo(this.preX, this.preY, this.width, this.height); // this.hullX.setTo(this.x, this.preY, this.width, this.height); // this.hullY.setTo(this.preX, this.y, this.width, this.height); - this.updateHulls(true); + // this.updateHulls(true); } if (this.skipQuadTree === false && this.allowCollision.none === false && this.sprite.visible && this.sprite.alive) diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part1.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part1.png new file mode 100644 index 00000000..3816a1cb Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part1.png differ diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part2.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part2.png new file mode 100644 index 00000000..cd0d79e5 Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part2.png differ diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part3.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part3.png new file mode 100644 index 00000000..25517ce0 Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part3.png differ diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part4.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part4.png new file mode 100644 index 00000000..c158fd97 Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part4.png differ diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part5.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part5.png new file mode 100644 index 00000000..29c6cde4 Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part5.png differ diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part6.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part6.png new file mode 100644 index 00000000..bbfe652e Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part6.png differ diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part7.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part7.png new file mode 100644 index 00000000..78f2683f Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part7.png differ diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part8.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part8.png new file mode 100644 index 00000000..3db5a59c Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part8.png differ diff --git a/tutorials/03 Using Phaser with TypeScript/screen shots/part9.png b/tutorials/03 Using Phaser with TypeScript/screen shots/part9.png new file mode 100644 index 00000000..7e92308b Binary files /dev/null and b/tutorials/03 Using Phaser with TypeScript/screen shots/part9.png differ