/* global CANNON,THREE,Detector */
CANNON = CANNON || {};
/**
* Demo framework class. If you want to learn how to connect Cannon.js with Three.js, please look at the examples/ instead.
* @class Demo
* @constructor
* @param {Object} options
*/
CANNON.Demo = function(options){
var that = this;
// API
this.addScene = addScene;
this.restartCurrentScene = restartCurrentScene;
this.changeScene = changeScene;
this.start = start;
var sceneFolder;
// Global settings
var settings = this.settings = {
stepFrequency: 60,
quatNormalizeSkip: 2,
quatNormalizeFast: true,
gx: 0,
gy: 0,
gz: 0,
iterations: 3,
tolerance: 0.0001,
k: 1e6,
d: 3,
scene: 0,
paused: false,
rendermode: "solid",
constraints: false,
contacts: false, // Contact points
cm2contact: false, // center of mass to contact points
normals: false, // contact normals
axes: false, // "local" frame axes
particleSize: 0.1,
shadows: false,
aabbs: false,
profiling: false,
maxSubSteps:3
};
// Extend settings with options
options = options || {};
for(var key in options){
if(key in settings){
settings[key] = options[key];
}
}
if(settings.stepFrequency % 60 !== 0){
throw new Error("stepFrequency must be a multiple of 60.");
}
var bodies = this.bodies = [];
var visuals = this.visuals = [];
var scenes = [];
var gui = null;
var smoothie = null;
var smoothieCanvas = null;
var scenePicker = {};
var three_contactpoint_geo = new THREE.SphereGeometry( 0.1, 6, 6);
var particleGeo = this.particleGeo = new THREE.SphereGeometry( 1, 16, 8 );
// Material
var materialColor = 0xdddddd;
var solidMaterial = new THREE.MeshLambertMaterial( { color: materialColor } );
//THREE.ColorUtils.adjustHSV( solidMaterial.color, 0, 0, 0.9 );
var wireframeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, wireframe:true } );
this.currentMaterial = solidMaterial;
var contactDotMaterial = new THREE.MeshLambertMaterial( { color: 0xff0000 } );
var particleMaterial = this.particleMaterial = new THREE.MeshLambertMaterial( { color: 0xff0000 } );
// Geometry caches
var contactMeshCache = new GeometryCache(function(){
return new THREE.Mesh( three_contactpoint_geo, contactDotMaterial );
});
var cm2contactMeshCache = new GeometryCache(function(){
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0,0,0));
geometry.vertices.push(new THREE.Vector3(1,1,1));
return new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0xff0000 } ) );
});
var bboxGeometry = new THREE.BoxGeometry(1,1,1);
var bboxMaterial = new THREE.MeshBasicMaterial({
color: materialColor,
wireframe:true
});
var bboxMeshCache = new GeometryCache(function(){
return new THREE.Mesh(bboxGeometry,bboxMaterial);
});
var distanceConstraintMeshCache = new GeometryCache(function(){
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0,0,0));
geometry.vertices.push(new THREE.Vector3(1,1,1));
return new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0xff0000 } ) );
});
var p2pConstraintMeshCache = new GeometryCache(function(){
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0,0,0));
geometry.vertices.push(new THREE.Vector3(1,1,1));
return new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0xff0000 } ) );
});
var normalMeshCache = new GeometryCache(function(){
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(0,0,0));
geometry.vertices.push(new THREE.Vector3(1,1,1));
return new THREE.Line( geometry, new THREE.LineBasicMaterial({color:0x00ff00}));
});
var axesMeshCache = new GeometryCache(function(){
var mesh = new THREE.Object3D();
//mesh.useQuaternion = true;
var origin = new THREE.Vector3(0,0,0);
var gX = new THREE.Geometry();
var gY = new THREE.Geometry();
var gZ = new THREE.Geometry();
gX.vertices.push(origin);
gY.vertices.push(origin);
gZ.vertices.push(origin);
gX.vertices.push(new THREE.Vector3(1,0,0));
gY.vertices.push(new THREE.Vector3(0,1,0));
gZ.vertices.push(new THREE.Vector3(0,0,1));
var lineX = new THREE.Line( gX, new THREE.LineBasicMaterial({color:0xff0000}));
var lineY = new THREE.Line( gY, new THREE.LineBasicMaterial({color:0x00ff00}));
var lineZ = new THREE.Line( gZ, new THREE.LineBasicMaterial({color:0x0000ff}));
mesh.add(lineX);
mesh.add(lineY);
mesh.add(lineZ);
return mesh;
});
function restartGeometryCaches(){
contactMeshCache.restart();
contactMeshCache.hideCached();
cm2contactMeshCache.restart();
cm2contactMeshCache.hideCached();
distanceConstraintMeshCache.restart();
distanceConstraintMeshCache.hideCached();
normalMeshCache.restart();
normalMeshCache.hideCached();
}
// Create physics world
var world = this.world = new CANNON.World();
world.broadphase = new CANNON.NaiveBroadphase();
var renderModes = ["solid","wireframe"];
function updategui(){
if(gui){
// First level
for (var i in gui.__controllers){
gui.__controllers[i].updateDisplay();
}
// Second level
for (var f in gui.__folders){
for (var i in gui.__folders[f].__controllers){
gui.__folders[f].__controllers[i].updateDisplay();
}
}
}
}
var light, scene, ambient, stats, info;
function setRenderMode(mode){
if(renderModes.indexOf(mode) === -1){
throw new Error("Render mode "+mode+" not found!");
}
switch(mode){
case "solid":
that.currentMaterial = solidMaterial;
light.intensity = 1;
ambient.color.setHex(0x222222);
break;
case "wireframe":
that.currentMaterial = wireframeMaterial;
light.intensity = 0;
ambient.color.setHex(0xffffff);
break;
}
function setMaterial(node,mat){
if(node.material){
node.material = mat;
}
for(var i=0; i<node.children.length; i++){
setMaterial(node.children[i],mat);
}
}
for(var i=0; i<visuals.length; i++){
setMaterial(visuals[i],that.currentMaterial);
}
settings.rendermode = mode;
}
/**
* Add a scene to the demo app
* @method addScene
* @param {String} title Title of the scene
* @param {Function} initfunc A function that takes one argument, app, and initializes a physics scene. The function runs app.setWorld(body), app.addVisual(body), app.removeVisual(body) etc.
*/
function addScene(title,initfunc){
if(typeof(title) !== "string"){
throw new Error("1st argument of Demo.addScene(title,initfunc) must be a string!");
}
if(typeof(initfunc)!=="function"){
throw new Error("2nd argument of Demo.addScene(title,initfunc) must be a function!");
}
scenes.push(initfunc);
var idx = scenes.length-1;
scenePicker[title] = function(){
changeScene(idx);
};
sceneFolder.add(scenePicker,title);
}
/**
* Restarts the current scene
* @method restartCurrentScene
*/
function restartCurrentScene(){
var N = bodies.length;
for(var i=0; i<N; i++){
var b = bodies[i];
b.position.copy(b.initPosition);
b.velocity.copy(b.initVelocity);
if(b.initAngularVelocity){
b.angularVelocity.copy(b.initAngularVelocity);
b.quaternion.copy(b.initQuaternion);
}
}
}
function makeSureNotZero(vec){
if(vec.x===0.0){
vec.x = 1e-6;
}
if(vec.y===0.0){
vec.y = 1e-6;
}
if(vec.z===0.0){
vec.z = 1e-6;
}
}
function updateVisuals(){
var N = bodies.length;
// Read position data into visuals
for(var i=0; i<N; i++){
var b = bodies[i], visual = visuals[i];
visual.position.copy(b.position);
if(b.quaternion){
visual.quaternion.copy(b.quaternion);
}
}
// Render contacts
contactMeshCache.restart();
if(settings.contacts){
// if ci is even - use body i, else j
for(var ci=0; ci < world.contacts.length; ci++){
for(var ij=0; ij < 2; ij++){
var mesh = contactMeshCache.request(),
c = world.contacts[ci],
b = ij===0 ? c.bi : c.bj,
r = ij===0 ? c.ri : c.rj;
mesh.position.set( b.position.x + r.x , b.position.y + r.y , b.position.z + r.z );
}
}
}
contactMeshCache.hideCached();
// Lines from center of mass to contact point
cm2contactMeshCache.restart();
if(settings.cm2contact){
for(var ci=0; ci<world.contacts.length; ci++){
for(var ij=0; ij < 2; ij++){
var line = cm2contactMeshCache.request(),
c = world.contacts[ci],
b = ij===0 ? c.bi : c.bj,
r = ij===0 ? c.ri : c.rj;
line.scale.set( r.x, r.y, r.z);
makeSureNotZero(line.scale);
line.position.copy(b.position);
}
}
}
cm2contactMeshCache.hideCached();
distanceConstraintMeshCache.restart();
p2pConstraintMeshCache.restart();
if(settings.constraints){
// Lines for distance constraints
for(var ci=0; ci<world.constraints.length; ci++){
var c = world.constraints[ci];
if(!(c instanceof CANNON.DistanceConstraint)){
continue;
}
var nc = c.equations.normal;
var bi=nc.bi, bj=nc.bj, line = distanceConstraintMeshCache.request();
var i=bi.id, j=bj.id;
// Remember, bj is either a Vec3 or a Body.
var v;
if(bj.position){
v = bj.position;
} else {
v = bj;
}
line.scale.set( v.x-bi.position.x,
v.y-bi.position.y,
v.z-bi.position.z );
makeSureNotZero(line.scale);
line.position.copy(bi.position);
}
// Lines for distance constraints
for(var ci=0; ci<world.constraints.length; ci++){
var c = world.constraints[ci];
if(!(c instanceof CANNON.PointToPointConstraint)){
continue;
}
var n = c.equations.normal;
var bi=n.bi, bj=n.bj, relLine1 = p2pConstraintMeshCache.request(), relLine2 = p2pConstraintMeshCache.request(), diffLine = p2pConstraintMeshCache.request();
var i=bi.id, j=bj.id;
relLine1.scale.set( n.ri.x, n.ri.y, n.ri.z );
relLine2.scale.set( n.rj.x, n.rj.y, n.rj.z );
diffLine.scale.set( -n.penetrationVec.x, -n.penetrationVec.y, -n.penetrationVec.z );
makeSureNotZero(relLine1.scale);
makeSureNotZero(relLine2.scale);
makeSureNotZero(diffLine.scale);
relLine1.position.copy(bi.position);
relLine2.position.copy(bj.position);
n.bj.position.vadd(n.rj,diffLine.position);
}
}
p2pConstraintMeshCache.hideCached();
distanceConstraintMeshCache.hideCached();
// Normal lines
normalMeshCache.restart();
if(settings.normals){
for(var ci=0; ci<world.contacts.length; ci++){
var c = world.contacts[ci];
var bi=c.bi, bj=c.bj, line=normalMeshCache.request();
var i=bi.id, j=bj.id;
var n = c.ni;
var b = bi;
line.scale.set(n.x,n.y,n.z);
makeSureNotZero(line.scale);
line.position.copy(b.position);
c.ri.vadd(line.position,line.position);
}
}
normalMeshCache.hideCached();
// Frame axes for each body
axesMeshCache.restart();
if(settings.axes){
for(var bi=0; bi<bodies.length; bi++){
var b = bodies[bi], mesh=axesMeshCache.request();
mesh.position.copy(b.position);
if(b.quaternion){
mesh.quaternion.copy(b.quaternion);
}
}
}
axesMeshCache.hideCached();
// AABBs
bboxMeshCache.restart();
if(settings.aabbs){
for(var i=0; i<bodies.length; i++){
var b = bodies[i];
if(b.computeAABB){
if(b.aabbNeedsUpdate){
b.computeAABB();
}
// Todo: cap the infinite AABB to scene AABB, for now just dont render
if( isFinite(b.aabb.lowerBound.x) &&
isFinite(b.aabb.lowerBound.y) &&
isFinite(b.aabb.lowerBound.z) &&
isFinite(b.aabb.upperBound.x) &&
isFinite(b.aabb.upperBound.y) &&
isFinite(b.aabb.upperBound.z) &&
b.aabb.lowerBound.x - b.aabb.upperBound.x != 0 &&
b.aabb.lowerBound.y - b.aabb.upperBound.y != 0 &&
b.aabb.lowerBound.z - b.aabb.upperBound.z != 0){
var mesh = bboxMeshCache.request();
mesh.scale.set( b.aabb.lowerBound.x - b.aabb.upperBound.x,
b.aabb.lowerBound.y - b.aabb.upperBound.y,
b.aabb.lowerBound.z - b.aabb.upperBound.z);
mesh.position.set( (b.aabb.lowerBound.x + b.aabb.upperBound.x)*0.5,
(b.aabb.lowerBound.y + b.aabb.upperBound.y)*0.5,
(b.aabb.lowerBound.z + b.aabb.upperBound.z)*0.5);
}
}
}
}
bboxMeshCache.hideCached();
}
if (!Detector.webgl){
Detector.addGetWebGLMessage();
}
var SHADOW_MAP_WIDTH = 512;
var SHADOW_MAP_HEIGHT = 512;
var MARGIN = 0;
var SCREEN_WIDTH = window.innerWidth;
var SCREEN_HEIGHT = window.innerHeight - 2 * MARGIN;
var camera, controls, renderer;
var container;
var NEAR = 5, FAR = 2000;
var sceneHUD, cameraOrtho, hudMaterial;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
// Camera
camera = new THREE.PerspectiveCamera( 24, SCREEN_WIDTH / SCREEN_HEIGHT, NEAR, FAR );
camera.up.set(0,0,1);
camera.position.set(0,30,20);
// SCENE
scene = that.scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0x222222, 1000, FAR );
// LIGHTS
ambient = new THREE.AmbientLight( 0x222222 );
scene.add( ambient );
light = new THREE.SpotLight( 0xffffff );
light.position.set( 30, 30, 40 );
light.target.position.set( 0, 0, 0 );
light.castShadow = true;
light.shadowCameraNear = 10;
light.shadowCameraFar = 100;//camera.far;
light.shadowCameraFov = 30;
light.shadowMapBias = 0.0039;
light.shadowMapDarkness = 0.5;
light.shadowMapWidth = SHADOW_MAP_WIDTH;
light.shadowMapHeight = SHADOW_MAP_HEIGHT;
//light.shadowCameraVisible = true;
scene.add( light );
scene.add( camera );
// RENDERER
renderer = new THREE.WebGLRenderer( { clearColor: 0x000000, clearAlpha: 1, antialias: false } );
renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
renderer.domElement.style.position = "relative";
renderer.domElement.style.top = MARGIN + 'px';
container.appendChild( renderer.domElement );
// Add info
info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.innerHTML = '<a href="http://github.com/schteppe/cannon.js">cannon.js</a> - javascript 3d physics';
container.appendChild( info );
document.addEventListener('mousemove',onDocumentMouseMove);
window.addEventListener('resize',onWindowResize);
renderer.setClearColor( scene.fog.color, 1 );
renderer.autoClear = false;
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
// Smoothie
smoothieCanvas = document.createElement("canvas");
smoothieCanvas.width = SCREEN_WIDTH;
smoothieCanvas.height = SCREEN_HEIGHT;
smoothieCanvas.style.opacity = 0.5;
smoothieCanvas.style.position = 'absolute';
smoothieCanvas.style.top = '0px';
smoothieCanvas.style.zIndex = 90;
container.appendChild( smoothieCanvas );
smoothie = new SmoothieChart({
labelOffsetY:50,
maxDataSetLength:100,
millisPerPixel:2,
grid: {
strokeStyle:'none',
fillStyle:'none',
lineWidth: 1,
millisPerLine: 250,
verticalSections: 6
},
labels: {
fillStyle:'rgb(180, 180, 180)'
}
});
smoothie.streamTo(smoothieCanvas);
// Create time series for each profile label
var lines = {};
var colors = [[255, 0, 0],[0, 255, 0],[0, 0, 255],[255,255,0],[255,0,255],[0,255,255]];
var i=0;
for(var label in world.profile){
var c = colors[i%colors.length];
lines[label] = new TimeSeries({
label : label,
fillStyle : "rgb("+c[0]+","+c[1]+","+c[2]+")",
maxDataLength : 500,
});
i++;
}
// Add a random value to each line every second
world.addEventListener("postStep",function(evt) {
for(var label in world.profile)
lines[label].append(world.time * 1000, world.profile[label]);
});
// Add to SmoothieChart
var i=0;
for(var label in world.profile){
var c = colors[i%colors.length];
smoothie.addTimeSeries(lines[label],{
strokeStyle : "rgb("+c[0]+","+c[1]+","+c[2]+")",
//fillStyle:"rgba("+c[0]+","+c[1]+","+c[2]+",0.3)",
lineWidth:2
});
i++;
}
world.doProfiling = false;
smoothie.stop();
smoothieCanvas.style.display = "none";
// STATS
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
stats.domElement.style.zIndex = 100;
container.appendChild( stats.domElement );
if(window.dat!=undefined){
gui = new dat.GUI();
gui.domElement.parentNode.style.zIndex=120;
// Render mode
var rf = gui.addFolder('Rendering');
rf.add(settings,'rendermode',{Solid:"solid",Wireframe:"wireframe"}).onChange(function(mode){
setRenderMode(mode);
});
rf.add(settings,'contacts');
rf.add(settings,'cm2contact');
rf.add(settings,'normals');
rf.add(settings,'constraints');
rf.add(settings,'axes');
rf.add(settings,'particleSize').min(0).max(1).onChange(function(size){
for(var i=0; i<visuals.length; i++){
if(bodies[i] instanceof CANNON.Particle)
visuals[i].scale.set(size,size,size);
}
});
rf.add(settings,'shadows').onChange(function(shadows){
if(shadows){
renderer.shadowMapAutoUpdate = true;
} else {
renderer.shadowMapAutoUpdate = false;
renderer.clearTarget( light.shadowMap );
}
});
rf.add(settings,'aabbs');
rf.add(settings,'profiling').onChange(function(profiling){
if(profiling){
world.doProfiling = true;
smoothie.start();
smoothieCanvas.style.display = "block";
} else {
world.doProfiling = false;
smoothie.stop();
smoothieCanvas.style.display = "none";
}
});
// World folder
var wf = gui.addFolder('World');
// Pause
wf.add(settings, 'paused').onChange(function(p){
/*if(p){
smoothie.stop();
} else {
smoothie.start();
}*/
});
wf.add(settings, 'stepFrequency',60,60*10).step(60);
var maxg = 100;
wf.add(settings, 'gx',-maxg,maxg).onChange(function(gx){
if(!isNaN(gx)){
world.gravity.set(gx,settings.gy,settings.gz);
}
});
wf.add(settings, 'gy',-maxg,maxg).onChange(function(gy){
if(!isNaN(gy))
world.gravity.set(settings.gx,gy,settings.gz);
});
wf.add(settings, 'gz',-maxg,maxg).onChange(function(gz){
if(!isNaN(gz))
world.gravity.set(settings.gx,settings.gy,gz);
});
wf.add(settings, 'quatNormalizeSkip',0,50).step(1).onChange(function(skip){
if(!isNaN(skip)){
world.quatNormalizeSkip = skip;
}
});
wf.add(settings, 'quatNormalizeFast').onChange(function(fast){
world.quatNormalizeFast = !!fast;
});
// Solver folder
var sf = gui.addFolder('Solver');
sf.add(settings, 'iterations',1,50).step(1).onChange(function(it){
world.solver.iterations = it;
});
sf.add(settings, 'k',10,10000000).onChange(function(k){
that.setGlobalSpookParams(settings.k,settings.d,1/settings.stepFrequency);
});
sf.add(settings, 'd',0,20).step(0.1).onChange(function(d){
that.setGlobalSpookParams(settings.k,settings.d,1/settings.stepFrequency);
});
sf.add(settings, 'tolerance',0.0,10.0).step(0.01).onChange(function(t){
world.solver.tolerance = t;
});
// Scene picker
sceneFolder = gui.addFolder('Scenes');
sceneFolder.open();
}
// Trackball controls
controls = new THREE.TrackballControls( camera, renderer.domElement );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.2;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = false;
controls.dynamicDampingFactor = 0.3;
var radius = 100;
controls.minDistance = 0.0;
controls.maxDistance = radius * 1000;
//controls.keys = [ 65, 83, 68 ]; // [ rotateKey, zoomKey, panKey ]
controls.screen.width = SCREEN_WIDTH;
controls.screen.height = SCREEN_HEIGHT;
}
var t = 0, newTime, delta;
function animate(){
requestAnimationFrame( animate );
if(!settings.paused){
updateVisuals();
updatePhysics();
}
render();
stats.update();
}
var lastCallTime = 0;
function updatePhysics(){
// Step world
var timeStep = 1 / settings.stepFrequency;
var now = Date.now() / 1000;
if(!lastCallTime){
// last call time not saved, cant guess elapsed time. Take a simple step.
world.step(timeStep);
lastCallTime = now;
return;
}
var timeSinceLastCall = now - lastCallTime;
world.step(timeStep, timeSinceLastCall, settings.maxSubSteps);
lastCallTime = now;
}
function onDocumentMouseMove( event ) {
mouseX = ( event.clientX - windowHalfX );
mouseY = ( event.clientY - windowHalfY );
}
function onWindowResize( event ) {
SCREEN_WIDTH = window.innerWidth;
SCREEN_HEIGHT = window.innerHeight;
renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
camera.updateProjectionMatrix();
controls.screen.width = SCREEN_WIDTH;
controls.screen.height = SCREEN_HEIGHT;
camera.radius = ( SCREEN_WIDTH + SCREEN_HEIGHT ) / 4;
}
function render(){
controls.update();
renderer.clear();
renderer.render( that.scene, camera );
}
document.addEventListener('keypress',function(e){
if(e.keyCode){
switch(e.keyCode){
case 32: // Space - restart
restartCurrentScene();
break;
case 104: // h - toggle widgets
if(stats.domElement.style.display=="none"){
stats.domElement.style.display = "block";
info.style.display = "block";
} else {
stats.domElement.style.display = "none";
info.style.display = "none";
}
break;
case 97: // a - AABBs
settings.aabbs = !settings.aabbs;
updategui();
break;
case 99: // c - constraints
settings.constraints = !settings.constraints;
updategui();
break;
case 112: // p
settings.paused = !settings.paused;
updategui();
break;
case 115: // s
var timeStep = 1 / settings.stepFrequency;
world.step(timeStep);
updateVisuals();
break;
case 109: // m - toggle materials
var idx = renderModes.indexOf(settings.rendermode);
idx++;
idx = idx % renderModes.length; // begin at 0 if we exceeded number of modes
setRenderMode(renderModes[idx]);
updategui();
break;
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// Change scene
// Only for numbers 1-9 and if no input field is active
if(scenes.length > e.keyCode-49 && !document.activeElement.localName.match(/input/)){
changeScene(e.keyCode-49);
}
break;
}
}
});
function changeScene(n){
that.dispatchEvent({ type: 'destroy' });
settings.paused = false;
updategui();
buildScene(n);
}
function start(){
buildScene(0);
}
function buildScene(n){
// Remove current bodies and visuals
var num = visuals.length;
for(var i=0; i<num; i++){
world.remove(bodies.pop());
var mesh = visuals.pop();
that.scene.remove(mesh);
}
// Remove all constraints
while(world.constraints.length){
world.removeConstraint(world.constraints[0]);
}
// Run the user defined "build scene" function
scenes[n]();
// Read the newly set data to the gui
settings.iterations = world.solver.iterations;
settings.gx = world.gravity.x+0.0;
settings.gy = world.gravity.y+0.0;
settings.gz = world.gravity.z+0.0;
settings.quatNormalizeSkip = world.quatNormalizeSkip;
settings.quatNormalizeFast = world.quatNormalizeFast;
updategui();
restartGeometryCaches();
}
function GeometryCache(createFunc){
var that=this, geometries=[], gone=[];
this.request = function(){
if(geometries.length){
geo = geometries.pop();
} else{
geo = createFunc();
}
scene.add(geo);
gone.push(geo);
return geo;
};
this.restart = function(){
while(gone.length){
geometries.push(gone.pop());
}
};
this.hideCached = function(){
for(var i=0; i<geometries.length; i++){
scene.remove(geometries[i]);
}
};
}
};
CANNON.Demo.prototype = new CANNON.EventTarget();
CANNON.Demo.constructor = CANNON.Demo;
CANNON.Demo.prototype.setGlobalSpookParams = function(k,d,h){
var world = this.world;
// Set for all constraints
for(var i=0; i<world.constraints.length; i++){
var c = world.constraints[i];
for(var j=0; j<c.equations.length; j++){
var eq = c.equations[j];
eq.setSpookParams(k,d,h);
}
}
// Set for all contact materals
for(var i=0; i<world.contactmaterials.length; i++){
var cm = world.contactmaterials[i];
cm.contactEquationStiffness = k;
cm.frictionEquationStiffness = k;
cm.contactEquationRelaxation = d;
cm.frictionEquationRelaxation = d;
}
world.defaultContactMaterial.contactEquationStiffness = k;
world.defaultContactMaterial.frictionEquationStiffness = k;
world.defaultContactMaterial.contactEquationRelaxation = d;
world.defaultContactMaterial.frictionEquationRelaxation = d;
};
CANNON.Demo.prototype.getWorld = function(){
return this.world;
};
CANNON.Demo.prototype.addVisual = function(body){
var s = this.settings;
// What geometry should be used?
var mesh;
if(body instanceof CANNON.Body){
mesh = this.shape2mesh(body);
}
if(mesh) {
// Add body
this.bodies.push(body);
this.visuals.push(mesh);
body.visualref = mesh;
body.visualref.visualId = this.bodies.length - 1;
//mesh.useQuaternion = true;
this.scene.add(mesh);
}
};
CANNON.Demo.prototype.addVisuals = function(bodies){
for (var i = 0; i < bodies.length; i++) {
this.addVisual(bodies[i]);
}
};
CANNON.Demo.prototype.removeVisual = function(body){
if(body.visualref){
var bodies = this.bodies,
visuals = this.visuals,
old_b = [],
old_v = [],
n = bodies.length;
for(var i=0; i<n; i++){
old_b.unshift(bodies.pop());
old_v.unshift(visuals.pop());
}
var id = body.visualref.visualId;
for(var j=0; j<old_b.length; j++){
if(j !== id){
var i = j>id ? j-1 : j;
bodies[i] = old_b[j];
visuals[i] = old_v[j];
bodies[i].visualref = old_b[j].visualref;
bodies[i].visualref.visualId = i;
}
}
body.visualref.visualId = null;
this.scene.remove(body.visualref);
body.visualref = null;
}
};
CANNON.Demo.prototype.removeAllVisuals = function(){
while(this.bodies.length) {
this.removeVisual(this.bodies[0]);
}
};
CANNON.Demo.prototype.shape2mesh = function(body){
var wireframe = this.settings.renderMode === "wireframe";
var obj = new THREE.Object3D();
for (var l = 0; l < body.shapes.length; l++) {
var shape = body.shapes[l];
var mesh;
switch(shape.type){
case CANNON.Shape.types.SPHERE:
var sphere_geometry = new THREE.SphereGeometry( shape.radius, 8, 8);
mesh = new THREE.Mesh( sphere_geometry, this.currentMaterial );
break;
case CANNON.Shape.types.PARTICLE:
mesh = new THREE.Mesh( this.particleGeo, this.particleMaterial );
var s = this.settings;
mesh.scale.set(s.particleSize,s.particleSize,s.particleSize);
break;
case CANNON.Shape.types.PLANE:
var geometry = new THREE.PlaneGeometry(10, 10, 4, 4);
mesh = new THREE.Object3D();
var submesh = new THREE.Object3D();
var ground = new THREE.Mesh( geometry, this.currentMaterial );
ground.scale.set(100, 100, 100);
submesh.add(ground);
ground.castShadow = true;
ground.receiveShadow = true;
mesh.add(submesh);
break;
case CANNON.Shape.types.BOX:
var box_geometry = new THREE.BoxGeometry( shape.halfExtents.x*2,
shape.halfExtents.y*2,
shape.halfExtents.z*2 );
mesh = new THREE.Mesh( box_geometry, this.currentMaterial );
break;
case CANNON.Shape.types.CONVEXPOLYHEDRON:
var geo = new THREE.Geometry();
// Add vertices
for (var i = 0; i < shape.vertices.length; i++) {
var v = shape.vertices[i];
geo.vertices.push(new THREE.Vector3(v.x, v.y, v.z));
}
for(var i=0; i < shape.faces.length; i++){
var face = shape.faces[i];
// add triangles
var a = face[0];
for (var j = 1; j < face.length - 1; j++) {
var b = face[j];
var c = face[j + 1];
geo.faces.push(new THREE.Face3(a, b, c));
}
}
geo.computeBoundingSphere();
geo.computeFaceNormals();
mesh = new THREE.Mesh( geo, this.currentMaterial );
break;
case CANNON.Shape.types.HEIGHTFIELD:
var geometry = new THREE.Geometry();
var v0 = new CANNON.Vec3();
var v1 = new CANNON.Vec3();
var v2 = new CANNON.Vec3();
for (var xi = 0; xi < shape.data.length - 1; xi++) {
for (var yi = 0; yi < shape.data[xi].length - 1; yi++) {
for (var k = 0; k < 2; k++) {
shape.getConvexTrianglePillar(xi, yi, k===0);
v0.copy(shape.pillarConvex.vertices[0]);
v1.copy(shape.pillarConvex.vertices[1]);
v2.copy(shape.pillarConvex.vertices[2]);
v0.vadd(shape.pillarOffset, v0);
v1.vadd(shape.pillarOffset, v1);
v2.vadd(shape.pillarOffset, v2);
geometry.vertices.push(
new THREE.Vector3(v0.x, v0.y, v0.z),
new THREE.Vector3(v1.x, v1.y, v1.z),
new THREE.Vector3(v2.x, v2.y, v2.z)
);
var i = geometry.vertices.length - 3;
geometry.faces.push(new THREE.Face3(i, i+1, i+2));
}
}
}
geometry.computeBoundingSphere();
geometry.computeFaceNormals();
mesh = new THREE.Mesh(geometry, this.currentMaterial);
break;
case CANNON.Shape.types.TRIMESH:
var geometry = new THREE.Geometry();
var v0 = new CANNON.Vec3();
var v1 = new CANNON.Vec3();
var v2 = new CANNON.Vec3();
for (var i = 0; i < shape.indices.length / 3; i++) {
shape.getTriangleVertices(i, v0, v1, v2);
geometry.vertices.push(
new THREE.Vector3(v0.x, v0.y, v0.z),
new THREE.Vector3(v1.x, v1.y, v1.z),
new THREE.Vector3(v2.x, v2.y, v2.z)
);
var j = geometry.vertices.length - 3;
geometry.faces.push(new THREE.Face3(j, j+1, j+2));
}
geometry.computeBoundingSphere();
geometry.computeFaceNormals();
mesh = new THREE.Mesh(geometry, this.currentMaterial);
break;
default:
throw "Visual type not recognized: "+shape.type;
}
mesh.receiveShadow = true;
mesh.castShadow = true;
if(mesh.children){
for(var i=0; i<mesh.children.length; i++){
mesh.children[i].castShadow = true;
mesh.children[i].receiveShadow = true;
if(mesh.children[i]){
for(var j=0; j<mesh.children[i].length; j++){
mesh.children[i].children[j].castShadow = true;
mesh.children[i].children[j].receiveShadow = true;
}
}
}
}
var o = body.shapeOffsets[l];
var q = body.shapeOrientations[l];
mesh.position.set(o.x, o.y, o.z);
mesh.quaternion.set(q.x, q.y, q.z, q.w);
obj.add(mesh);
}
return obj;
};