/* 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 */ var camera, scene, renderer, controls = null; var s_oRender; 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 = 0xaaaaaa; var solidMaterial = new THREE.MeshPhongMaterial({color: materialColor, specular: 0x111111, shininess: 50}); //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.MeshPhongMaterial({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, 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 = 1024; var SHADOW_MAP_HEIGHT = 1024; var MARGIN = 0; var SCREEN_WIDTH = s_iCanvasResizeWidth + s_iCanvasOffsetWidth; var SCREEN_HEIGHT = s_iCanvasResizeHeight + s_iCanvasOffsetHeight; var container; var windowHalfX = SCREEN_WIDTH / 2; var windowHalfY = SCREEN_HEIGHT / 2; init(); animate(); function init() { container = document.createElement('div'); document.body.appendChild(container); // Camera if (CAMERA_TEST_TRACKBALL) { NEAR = 5; camera = new THREE.PerspectiveCamera(45, SCREEN_WIDTH / SCREEN_HEIGHT, NEAR, FAR); camera.lookAt(new THREE.Vector3(CAMERA_TEST_LOOK_AT.x, CAMERA_TEST_LOOK_AT.y, CAMERA_TEST_LOOK_AT.z)); camera.position.set(0, 500, 500); camera.up.set(0, 0, 1); } else { camera = createOrthoGraphicCamera(); } // SCENE scene = that.scene = new THREE.Scene(); scene.fog = new THREE.Fog(0x7ec0ee, FAR * 0.5, FAR); // LIGHTS ambient = new THREE.AmbientLight(0x444444); scene.add(ambient); light = new THREE.DirectionalLight(0xffffcc, 1); light.position.set(180, 0, 180); light.target.position.set(0, 0, 0); light.castShadow = true; light.shadow.camera.near = 10; light.shadow.camera.far = 100;//camera.far; light.shadow.camera.fov = 30; light.shadowMapBias = 0.0139; light.shadowMapDarkness = 0.1; light.shadow.mapSize.width = SHADOW_MAP_WIDTH; light.shadow.mapSize.height = SHADOW_MAP_HEIGHT; // light.shadowCameraVisible = true; // new THREE.CameraHelper(light.shadow.camera); scene.add(light); scene.add(camera); // add plane for raycasting //var oPlaneGeometry = new THREE.PlaneBufferGeometry(500, 500, 50, 50); // s_oRayCasterMesh = new THREE.Mesh(oPlaneGeometry, new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff})); // scene.add( s_oRayCasterMesh ); // RENDERER if (SHOW_3D_RENDER) { renderer = new THREE.WebGLRenderer({clearColor: 0x000000, clearAlpha: 0.5, antialias: true, alpha: true}); } else { renderer = new THREE.CanvasRenderer({clearColor: 0x000000, clearAlpha: 0.5, antialias: false, alpha: true}); } renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT); renderer.domElement.style.position = "relative"; renderer.domElement.style.top = MARGIN + 'px'; renderer.domElement.style.opacity = OPACITY_INTENSITY_3D; 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 = 'cannon.js - javascript 3d physics'; container.appendChild(info); document.addEventListener('mousemove', onDocumentMouseMove); window.addEventListener('resize', onWindowResize); renderer.setClearColor(scene.fog.color, 1); renderer.autoClear = false; // renderer.shadowMap.enabled = 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 if (CAMERA_TEST_TRACKBALL) { 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 = s_iCanvasResizeWidth + s_iCanvasOffsetWidth * 2; SCREEN_HEIGHT = s_iCanvasResizeHeight + s_iCanvasOffsetHeight * 2; if (CAMERA_TEST_TRACKBALL) { controls.screen.width = SCREEN_WIDTH; controls.screen.height = SCREEN_HEIGHT; } } function render() { if (CAMERA_TEST_TRACKBALL || CAMERA_TEST_TRANSFORM && controls !== null) { controls.update(); } renderer.clear(); renderer.render(that.scene, camera); } s_oRender = render; 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.createTransformControl = function (oMesh, oBody) { controls = new THREE.TransformControls(camera, renderer.domElement); // controls.addEventListener('change', s_oRender); scene.add(oMesh); controls.attach(oMesh, oBody); scene.add(controls); console.log("CREATE"); window.addEventListener('keydown', function (event) { switch (event.keyCode) { case 81: // Q controls.setSpace(controls.space === "local" ? "world" : "local"); break; case 17: // Ctrl controls.setTranslationSnap(100); controls.setRotationSnap(THREE.Math.degToRad(15)); break; case 87: // W controls.setMode("translate"); break; case 69: // E controls.setMode("rotate"); break; case 82: // R controls.setMode("scale"); break; case 187: case 107: // +, =, num+ controls.setSize(controls.size + 0.1); break; case 189: case 109: // -, _, num- controls.setSize(Math.max(controls.size - 0.1, 0.1)); break; } }); window.addEventListener('keyup', function (event) { switch (event.keyCode) { case 17: // Ctrl controls.setTranslationSnap(null); controls.setRotationSnap(null); break; } }); }; CANNON.Demo.prototype.getWorld = function () { return this.world; }; CANNON.Demo.prototype.addVisual = function (body, material) { var s = this.settings; // What geometry should be used? var mesh; if (body instanceof CANNON.Body) { mesh = this.shape2mesh(body, material); } 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); } return 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, material) { 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); if (material === undefined) { mesh = new THREE.Mesh(sphere_geometry, this.currentMaterial); } else { mesh = new THREE.Mesh(sphere_geometry, material); } mesh.castShadow = true; 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; if (material === undefined) { ground = new THREE.Mesh(geometry, this.currentMaterial); } else { ground = new THREE.Mesh(geometry, material); } ground.scale.set(100, 100, 100); submesh.add(ground); ground.castShadow = false; 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); if (material === undefined) { mesh = new THREE.Mesh(box_geometry, this.currentMaterial); } else { mesh = new THREE.Mesh(box_geometry, material); } 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(); if (material === undefined) { mesh = new THREE.Mesh(geo, this.currentMaterial); } else { mesh = new THREE.Mesh(geo, material); } 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(); if (material === undefined) { mesh = new THREE.Mesh(geometry, this.currentMaterial); } else { mesh = new THREE.Mesh(geometry, material); } 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(); if (material === undefined) { mesh = new THREE.Mesh(geometry, this.currentMaterial); } else { mesh = new THREE.Mesh(geometry, material); } 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); } this.camera = function () { return camera; }; this.getScene = function () { return scene; }; return obj; };