#

API Docs for: 0.6.1
Show: Inherited Protected Private Deprecated

# File: src/demo/Demo.js

    /* 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;
    };