cannon
Version:
A lightweight 3D physics engine written in JavaScript.
294 lines (248 loc) • 9.72 kB
JavaScript
var Vec3 = require("../src/math/Vec3");
var Mat3 = require("../src/math/Mat3");
var Quaternion = require("../src/math/Quaternion");
var Box = require('../src/shapes/Box');
var Sphere = require('../src/shapes/Sphere');
var Trimesh = require('../src/shapes/Trimesh');
var Plane = require('../src/shapes/Plane');
var Ray = require('../src/collision/Ray');
var Body = require('../src/objects/Body');
var RaycastResult = require('../src/collision/RaycastResult');
var Heightfield = require('../src/shapes/Heightfield');
module.exports = {
construct : function(test) {
test.expect(0);
var r = new Ray(new Vec3(), new Vec3(1,0,0));
test.done();
},
intersectBody : function(test) {
var r = new Ray(new Vec3(5,0,0), new Vec3(-5, 0, 0));
r.skipBackfaces = true;
var shape = createPolyhedron(0.5);
var body = new Body({ mass: 1 });
body.addShape(shape);
var result = new RaycastResult();
r.intersectBody(body, result);
test.ok(result.hasHit);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0.5, 0, 0)));
// test rotating the body first
result.reset();
body.quaternion.setFromAxisAngle(new Vec3(1,0,0), Math.PI);
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0.5,0,0)));
// test shooting from other direction
result.reset();
r.to.set(0,0,-5);
r.from.set(0,0,5);
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0,0,0.5)));
// test miss
result.reset();
var r = new Ray(new Vec3(5, 1, 0), new Vec3(-5, 1, 0));
r.intersectBody(body, result);
test.equals(result.hasHit, false);
test.done();
},
intersectBodies : function(test) {
var r = new Ray(new Vec3(5,0,0), new Vec3(-5,0,0));
r.skipBackfaces = true;
var shape = createPolyhedron(0.5);
var body1 = new Body({ mass: 1 });
body1.addShape(shape);
var body2 = new Body({ mass: 1 });
body2.addShape(shape);
body2.position.x = -2;
var result = new RaycastResult();
r.intersectBodies([body1, body2], result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0.5,0,0)));
test.done();
},
box: function(test){
var r = new Ray(new Vec3(5,0,0),new Vec3(-5, 0, 0));
r.skipBackfaces = true;
var shape = new Box(new Vec3(0.5,0.5,0.5));
var body = new Body({ mass: 1 });
body.addShape(shape);
var result = new RaycastResult();
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0.5,0,0)));
result.reset();
body.quaternion.setFromAxisAngle(new Vec3(1,0,0), Math.PI / 2);
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0.5,0,0)));
result.reset();
body.quaternion.setFromAxisAngle(new Vec3(1,0,0), Math.PI);
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0.5,0,0)));
result.reset();
body.quaternion.setFromAxisAngle(new Vec3(1,0,0), 3 * Math.PI / 2);
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0.5,0,0)));
test.done();
},
sphere: function(test){
var r = new Ray(new Vec3(5,0,0), new Vec3(-5, 0, 0));
r.skipBackfaces = true;
var shape = new Sphere(1);
var body = new Body({ mass: 1 });
body.addShape(shape);
var result = new RaycastResult();
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(1,0,0)));
result.reset();
body.position.set(1, 0, 0);
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(2,0,0)));
result.reset();
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(2,0,0)));
result.reset();
var shape2 = new Sphere(1);
var body2 = new Body({ mass: 1 });
body2.addShape(shape2, new Vec3(1, 0, 0));
r.intersectBody(body2, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(2,0,0)));
test.done();
},
heightfield: function(test){
var r = new Ray(new Vec3(0, 0, 10), new Vec3(0, 0, -10));
r.skipBackfaces = true;
var data = [
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]
];
var shape = new Heightfield(data, {
elementSize: 1
});
var body = new Body({ mass: 1 }, new Vec3(-1.5, -1.5, 0));
body.addShape(shape);
// Hit
var result = new RaycastResult();
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.deepEqual(result.hitPointWorld, new Vec3(0, 0, 1));
// Miss
var result = new RaycastResult();
r.from.set(-100, -100, 10);
r.to.set(-100, -100, -10);
r.intersectBody(body, result);
test.equals(result.hasHit, false);
// Hit all triangles!
var result = new RaycastResult();
for(var i = 0; i < data.length - 1; i++){ // 3x3 data points will have 2x2 rectangles in the field
for(var j = 0; j < data[i].length - 1; j++){
for(var k = 0; k < 2; k++){
result.reset();
r.from.set(i + 0.25, j + 0.25, 10);
r.to.set(i + 0.25, j + 0.25, -10);
if (k) {
r.from.x += 0.5;
r.from.y += 0.5;
r.to.x += 0.5;
r.to.y += 0.5;
}
r.intersectBody(body, result);
test.ok(result.hasHit, 'missed triangle ' + [i, j].join(','));
}
}
}
test.done();
},
plane: function(test){
var r = new Ray(new Vec3(0,0,5), new Vec3(0, 0, -5));
r.skipBackfaces = true;
var shape = new Plane();
var body = new Body({ mass: 1 });
body.addShape(shape);
var result = new RaycastResult();
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0,0,0)));
test.equal(result.distance, 5);
result.reset();
var body2 = new Body({ mass: 1 });
body2.addShape(shape, new Vec3(0, 0, 1), new Quaternion());
r.intersectBody(body2, result);
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0,0,1)));
result.reset();
var body3 = new Body({ mass: 1 });
var quat = new Quaternion();
quat.setFromAxisAngle(new Vec3(1, 0, 0), Math.PI / 2);
body3.addShape(shape, new Vec3(), quat);
r.intersectBody(body3, result);
test.equals(result.hasHit, false);
result.reset();
var body4 = new Body({ mass: 1 });
body4.addShape(shape);
var r = new Ray(new Vec3(1, 1, 5), new Vec3(1, 1, -5));
r.intersectBody(body4, result);
test.equals(result.hasHit, true);
test.deepEqual(result.hitPointWorld, new Vec3(1, 1, 0));
test.equal(result.distance, 5);
var result = new RaycastResult();
r.from.set(0, 1, 1);
r.to.set(0, -1, -1);
body.position.set(0, 0, 0);
r.intersectBody(body, result);
var distance1 = result.distance;
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0,0,0)));
var result = new RaycastResult();
r.from.set(0, 1 - 5, 1);
r.to.set(0, -1 - 5, -1);
body.position.set(0, 0, 0);
r.intersectBody(body, result);
var distance2 = result.distance;
test.equals(result.hasHit, true);
test.ok(result.hitPointWorld.almostEquals(new Vec3(0,-5,0)));
test.equal(distance1, distance2);
test.done();
},
trimesh: function(test){
var r = new Ray(new Vec3(0.5, 0.5, 10), new Vec3(0.5, 0.5, -10));
r.skipBackfaces = true;
var vertices = [
0, 0, 0,
1, 0, 0,
0, 1, 0
];
var indices = [
0, 1, 2
];
var body = new Body({
mass: 1,
shape: new Trimesh(vertices, indices)
});
// Hit
var result = new RaycastResult();
r.intersectBody(body, result);
test.equals(result.hasHit, true);
test.deepEqual(result.hitPointWorld, new Vec3(0.5, 0.5, 0));
// Miss
result = new RaycastResult();
r.from.set(-100, -100, 10);
r.to.set(-100, -100, -10);
r.intersectBody(body, result);
test.equals(result.hasHit, false);
test.done();
},
};
function createPolyhedron(size){
size = (size===undefined ? 0.5 : size);
var box = new Box(new Vec3(size,size,size));
box.updateConvexPolyhedronRepresentation();
return box.convexPolyhedronRepresentation;
}