p2s
Version:
A JavaScript 2D physics engine.
409 lines (353 loc) • 12.2 kB
JavaScript
var World = require(__dirname + '/../../src/world/World')
, Body = require(__dirname + '/../../src/objects/Body')
, LinearSpring = require(__dirname + '/../../src/objects/LinearSpring')
, DistanceConstraint = require(__dirname + '/../../src/constraints/DistanceConstraint')
, Circle = require(__dirname + '/../../src/shapes/Circle')
, Plane = require(__dirname + '/../../src/shapes/Plane')
, Ray = require(__dirname + '/../../src/collision/Ray')
, RaycastResult = require(__dirname + '/../../src/collision/RaycastResult')
, LinearSpring = require(__dirname + '/../../src/objects/LinearSpring')
, Convex = require(__dirname + '/../../src/shapes/Convex');
exports.construct = function(test){
var world = new World();
var options = {
gravity: [123,456]
};
world = new World(options);
test.equal(world.gravity[0], options.gravity[0]);
test.equal(world.gravity[1], options.gravity[1]);
test.done();
};
exports.raycast = function(test){
var world = new World();
var shape = new Plane();
var body = new Body();
body.addShape(shape, [0,0], Math.PI / 2);
world.addBody(body);
var result = new RaycastResult();
var ray = new Ray({
from: [0, -10],
to: [0, 10]
});
world.raycast(result, ray);
test.ok(result.hasHit());
var hitPointWorld = [1,1];
result.getHitPoint(hitPointWorld, ray);
test.equal(hitPointWorld[0], 0);
test.equal(hitPointWorld[1], 0);
test.equal(result.getHitDistance(ray), 10);
test.done();
};
exports.addBody = {
duringStep: function(test){
var world = new World();
var body = new Body();
world.on('postBroadphase', function(){
test.throws(function(){
world.addBody(body);
}, 'should throw on removing bodies during step');
test.done();
});
world.step(1);
},
twice: function(test){
var world = new World();
var body = new Body();
world.addBody(body);
test.throws(function(){
world.addBody(body);
}, 'should throw on adding body twice');
test.done();
},
};
exports.addConstraint = {
withoutAddedBodies: function(test){
var bodyA = new Body();
var bodyB = new Body();
var constraint = new DistanceConstraint(bodyA, bodyB);
var world = new World();
test.throws(function(){
world.addConstraint(constraint);
},'Should throw error if a constraint is added to the world but not its bodies.');
world.addBody(bodyA);
world.addBody(bodyB);
world.addConstraint(constraint);
test.done();
},
duringStep: function(test){
var bodyA = new Body();
var bodyB = new Body();
var constraint = new DistanceConstraint(bodyA, bodyB);
var world = new World();
world.on('postBroadphase', function(){
test.throws(function(){
world.addConstraint(constraint);
}, 'should throw on adding constraints during step');
test.done();
});
world.step(1);
}
};
exports.addContactMaterial = function(test){
// STUB
test.done();
};
exports.addSpring = {
duringStep: function(test){
var bodyA = new Body();
var bodyB = new Body();
var spring = new LinearSpring(bodyA, bodyB);
var world = new World();
world.on('postBroadphase', function(){
test.throws(function(){
world.addSpring(spring);
}, 'should throw on adding springs during step');
test.done();
});
world.step(1);
}
};
exports.clear = function(test){
var world = new World();
var bodyA = new Body();
var bodyB = new Body();
world.addBody(bodyA);
world.addBody(bodyB);
var spring = new LinearSpring(bodyA, bodyB);
world.addSpring(spring);
var constraint = new DistanceConstraint(bodyA, bodyB);
world.addConstraint(constraint);
world.clear();
test.deepEqual(world.bodies, []);
test.deepEqual(world.springs, []);
test.deepEqual(world.constraints, []);
test.deepEqual(world.contactMaterials, []);
test.done();
};
exports.clone = function(test){
// STUB
test.done();
};
exports.disableBodyCollision = function(test){
var bodyA = new Body({ mass:1 }),
bodyB = new Body({ mass:1 }),
world = new World();
bodyA.addShape(new Circle({ radius: 1 }));
bodyB.addShape(new Circle({ radius: 1 }));
world.addBody(bodyA);
world.addBody(bodyB);
world.disableBodyCollision(bodyA,bodyB);
world.step(1/60);
test.equal(world.narrowphase.contactEquations.length,0);
world.enableBodyCollision(bodyA,bodyB);
world.step(1/60);
test.equal(world.narrowphase.contactEquations.length,1);
test.done();
};
exports.getBodyById = function(test){
// STUB
test.done();
};
exports.getContactMaterial = function(test){
// STUB
test.done();
};
exports.hitTest = function(test){
var b = new Body(),
world = new World();
world.addBody(b);
test.deepEqual(world.hitTest([0,0],[b]) , [], "Should miss bodies without shapes");
b.addShape(new Circle({ radius: 1 }));
test.deepEqual(world.hitTest([0,0],[b]) , [b], "Should hit Circle");
test.deepEqual(world.hitTest([1.1,0],[b]) , [], "Should miss Circle");
b = new Body();
b.addShape(new Convex({ vertices: [ [-1,-1],
[ 1,-1],
[ 1, 1],
[-1, 1]] }));
test.deepEqual(world.hitTest([0,0], [b]) , [b], "Should hit Convex");
test.deepEqual(world.hitTest([1.1,0],[b]) , [], "Should miss Convex");
test.done();
};
exports.integrateBody = function(test){
// STUB
test.done();
};
// TODO test other aspects of removeBody
exports.removeBody = {
duringStep: function(test){
var world = new World();
var body = new Body();
world.addBody(body);
world.on('postBroadphase', function(){
test.throws(function(){
world.removeBody(body);
}, 'should throw on adding bodies during step');
test.done();
});
world.step(1);
},
"removes relevant pairs from disabledBodyCollisionPairs": function(test){
var body1 = new Body({id: 1}),
body2 = new Body({id: 2}),
body3 = new Body({id: 3}),
world = new World();
world.addBody(body1);
world.addBody(body2);
world.addBody(body3);
world.disableBodyCollision(body1, body2);
world.disableBodyCollision(body2, body1);
world.disableBodyCollision(body3, body3);
world.removeBody(body1);
test.equal(world.disabledBodyCollisionPairs.length, 2);
test.equal(world.disabledBodyCollisionPairs[0].id, body3.id);
test.equal(world.disabledBodyCollisionPairs[1].id, body3.id);
test.done();
}
};
exports.removeConstraint = {
duringStep: function(test){
var world = new World();
var bodyA = new Body();
world.addBody(bodyA);
var bodyB = new Body();
world.addBody(bodyB);
var constraint = new DistanceConstraint(bodyA, bodyB);
world.addConstraint(constraint);
world.on('postBroadphase', function(){
test.throws(function(){
world.removeConstraint(constraint);
}, 'should throw on removing constraints during step');
test.done();
});
world.step(1);
}
};
exports.removeContactMaterial = function(test){
// STUB
test.done();
};
exports.removeSpring = {
duringStep: function(test){
var bodyA = new Body();
var bodyB = new Body();
var spring = new LinearSpring(bodyA, bodyB);
var world = new World();
world.addSpring(spring);
world.on('postBroadphase', function(){
test.throws(function(){
world.removeSpring(spring);
}, 'should throw on removing springs during step');
test.done();
});
world.step(1);
}
};
exports.runNarrowphase = function(test){
// STUB
test.done();
};
exports.setGlobalStiffness = function(test){
var world = new World();
world.setGlobalStiffness(123);
test.equal(world.defaultContactMaterial.stiffness, 123);
// TODO: check constraints etc
test.done();
};
exports.setGlobalRelaxation = function(test){
var world = new World();
world.setGlobalRelaxation(123);
test.equal(world.defaultContactMaterial.relaxation, 123);
// TODO: check constraints etc
test.done();
};
exports.step = function(test){
// STUB
test.done();
};
exports.events = {
beginContact : function(test){
var world = new World(),
bodyA = new Body({ mass:1 }),
bodyB = new Body({ mass:1 });
world.addBody(bodyA);
world.addBody(bodyB);
var shapeA = new Circle({ radius: 1 }),
shapeB = new Circle({ radius: 1 });
bodyA.addShape(shapeA);
bodyB.addShape(shapeB);
var beginContactHits = 0,
endContactHits = 0;
world.on("beginContact",function(evt){
test.ok( evt.shapeA.id === shapeA.id || evt.shapeA.id === shapeB.id );
test.ok( evt.shapeB.id === shapeA.id || evt.shapeB.id === shapeB.id );
test.ok( evt.bodyA.id === bodyA.id || evt.bodyA.id === bodyB.id );
test.ok( evt.bodyB.id === bodyA.id || evt.bodyB.id === bodyB.id );
beginContactHits++;
});
world.on("endContact",function(evt){
test.ok( evt.shapeA.id === shapeA.id || evt.shapeA.id === shapeB.id );
test.ok( evt.shapeB.id === shapeA.id || evt.shapeB.id === shapeB.id );
test.ok( evt.bodyA.id === bodyA.id || evt.bodyA.id === bodyB.id );
test.ok( evt.bodyB.id === bodyA.id || evt.bodyB.id === bodyB.id );
endContactHits++;
});
// First overlap - one new beginContact
world.step(1/60);
test.equal(beginContactHits, 1);
test.equal(endContactHits, 0);
// Still overlapping - should maintain
world.step(1/60);
test.equal(beginContactHits, 1);
test.equal(endContactHits, 0);
// End the overlap
bodyA.position[0] = 10;
world.step(1/60);
test.equal(beginContactHits, 1);
test.equal(endContactHits,1);
test.done();
},
beginContact2 : function(test){
var world = new World(),
// 3 circles, A overlaps B which overlaps C
bodyA = new Body({ mass:1, position:[-1.1,0] }),
bodyB = new Body({ mass:1, position:[0,0] }),
bodyC = new Body({ mass:1, position:[1.1,0] });
world.addBody(bodyA);
world.addBody(bodyB);
world.addBody(bodyC);
var shapeA = new Circle({ radius: 1 }),
shapeB = new Circle({ radius: 1 }),
shapeC = new Circle({ radius: 1 });
bodyA.addShape(shapeA);
bodyB.addShape(shapeB);
bodyC.addShape(shapeC);
var beginContactHits = 0,
endContactHits = 0;
world.on("beginContact",function(evt){
beginContactHits++;
});
world.on("endContact",function(evt){
endContactHits++;
});
// First overlap - two new beginContact
world.step(1/60);
test.equal(beginContactHits, 2);
test.equal(endContactHits, 0);
// Still overlapping - should not report anything
world.step(1/60);
test.equal(beginContactHits, 2);
test.equal(endContactHits, 0);
// End one overlap
bodyA.position[1] = 10;
world.step(1/60);
test.equal(beginContactHits, 2);
test.equal(endContactHits,1);
// End another overlap
bodyB.position[1] = -10;
world.step(1/60);
test.equal(beginContactHits, 2);
test.equal(endContactHits,2);
test.done();
},
};