physics_benchmark.js
  1 /*{# Copyright (c) 2012 Turbulenz Limited #}*/
  2 /*
  3 * @title: 3D Physics benchmark
  4 * @description:
  5 * This sample is a benchmark for rigid body physics simulation with randomly generated boxes, spheres, cones,
  6 * cylinders, capsules and convex hulls.
  7 * The rigid bodies fall into a procedurally generated triangle mesh bowl that can be animated.
  8 * The sample also shows the time spent on the different physics simulation phases.
  9 * Disabling the debug rendering will show its impact on the framerate, the physics simulation will continue but
 10 * without any graphics update.
 11 */
 12 /*{{ javascript("jslib/aabbtree.js") }}*/
 13 /*{{ javascript("jslib/camera.js") }}*/
 14 /*{{ javascript("jslib/floor.js") }}*/
 15 /*{{ javascript("jslib/geometry.js") }}*/
 16 /*{{ javascript("jslib/material.js") }}*/
 17 /*{{ javascript("jslib/light.js") }}*/
 18 /*{{ javascript("jslib/scenenode.js") }}*/
 19 /*{{ javascript("jslib/scene.js") }}*/
 20 /*{{ javascript("jslib/vmath.js") }}*/
 21 /*{{ javascript("jslib/shadermanager.js") }}*/
 22 /*{{ javascript("jslib/renderingcommon.js") }}*/
 23 /*{{ javascript("jslib/resourceloader.js") }}*/
 24 /*{{ javascript("jslib/scenedebugging.js") }}*/
 25 /*{{ javascript("jslib/observer.js") }}*/
 26 /*{{ javascript("jslib/physicsmanager.js") }}*/
 27 /*{{ javascript("jslib/utilities.js") }}*/
 28 /*{{ javascript("jslib/vertexbuffermanager.js") }}*/
 29 /*{{ javascript("jslib/indexbuffermanager.js") }}*/
 30 /*{{ javascript("jslib/mouseforces.js") }}*/
 31 /*{{ javascript("jslib/utilities.js") }}*/
 32 /*{{ javascript("jslib/requesthandler.js") }}*/
 33 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/
 34 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/
 35 /*{{ javascript("jslib/services/gamesession.js") }}*/
 36 /*{{ javascript("jslib/services/mappingtable.js") }}*/
 37 /*{{ javascript("scripts/htmlcontrols.js") }}*/
 38 /*{{ javascript("scripts/sceneloader.js") }}*/
 39 /*global TurbulenzEngine: true */
 40 /*global RequestHandler: false */
 41 /*global SceneLoader: false */
 42 /*global SceneNode: false */
 43 /*global TurbulenzServices: false */
 44 /*global ShaderManager: false */
 45 /*global Scene: false */
 46 /*global Camera: false */
 47 /*global CameraController: false */
 48 /*global Floor: false */
 49 /*global MouseForces: false */
 50 /*global PhysicsManager: false */
 51 /*global HTMLControls: false */
 52 TurbulenzEngine.onload = function onloadFn() {
 53     var errorCallback = function errorCallback(msg) {
 54         window.alert(msg);
 55     };
 56     TurbulenzEngine.onerror = errorCallback;
 57 
 58     var warningCallback = function warningCallback(msg) {
 59         window.alert(msg);
 60     };
 61     TurbulenzEngine.onwarning = warningCallback;
 62 
 63     var mathDeviceParameters = {};
 64     var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters);
 65 
 66     var graphicsDeviceParameters = {};
 67     var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters);
 68 
 69     var physicsDeviceParameters = {};
 70     var physicsDevice = TurbulenzEngine.createPhysicsDevice(physicsDeviceParameters);
 71 
 72     var dynamicsWorldParameters = {
 73         variableTimeSteps: true,
 74         maxSubSteps: 2
 75     };
 76     var dynamicsWorld = physicsDevice.createDynamicsWorld(dynamicsWorldParameters);
 77 
 78     var inputDeviceParameters = {};
 79     var inputDevice = TurbulenzEngine.createInputDevice(inputDeviceParameters);
 80 
 81     var requestHandlerParameters = {};
 82     var requestHandler = RequestHandler.create(requestHandlerParameters);
 83 
 84     var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback);
 85     var physicsManager = PhysicsManager.create(mathDevice, physicsDevice, dynamicsWorld);
 86 
 87     var debugMode = true;
 88 
 89     // Renderer and assets for the scene.
 90     var scene = Scene.create(mathDevice);
 91     var sceneLoader = SceneLoader.create();
 92 
 93     // Setup world space
 94     var clearColor = mathDevice.v4Build(0.95, 0.95, 1.0, 1.0);
 95     var loadingClearColor = mathDevice.v4Build(0.8, 0.8, 0.8, 1.0);
 96     var worldUp = mathDevice.v3BuildYAxis();
 97 
 98     // Setup a camera to view a close-up object
 99     var camera = Camera.create(mathDevice);
100     camera.nearPlane = 0.05;
101     var cameraDefaultPos = mathDevice.v3Build(0, 8.0, 18.1);
102     var cameraDefaultLook = mathDevice.v3Build(0, -(camera.farPlane / 4), -camera.farPlane);
103 
104     // The objects needed to draw the crosshair
105     var technique2d;
106     var shader2d;
107     var techniqueParameters2d;
108     var chSemantics = graphicsDevice.createSemantics(['POSITION']);
109     var chFormats = [graphicsDevice.VERTEXFORMAT_FLOAT3];
110 
111     // Setup world floor
112     var floor = Floor.create(graphicsDevice, mathDevice);
113     var cameraController = CameraController.create(graphicsDevice, inputDevice, camera);
114 
115     // Mouse forces
116     var dragMin = mathDevice.v3Build(-50, -50, -50);
117     var dragMax = mathDevice.v3Build(50, 50, 50);
118     var mouseForces = MouseForces.create(graphicsDevice, inputDevice, mathDevice, physicsDevice, dragMin, dragMax);
119     mouseForces.clamp = 400;
120 
121     // Control codes
122     var keyCodes = inputDevice.keyCodes;
123     var mouseCodes = inputDevice.mouseCodes;
124 
125     // Dynamic physics objects
126     var physicsObjects = [];
127     var bowlObject;
128 
129     // Configuration of demo.
130     // Bowl radius and height
131     var bowlRadius = 9;
132     var bowlHeight = 5;
133 
134     // Number of radial points, and planes in bowl.
135     var radialN = 30;
136     var depthN = 10;
137 
138     // Control approximate size of objects
139     var objectSize = 0.5;
140 
141     // Radius to place objects at in spiral
142     // y-displacement between each object.
143     // And start y position.
144     var genRadius = bowlRadius - 4;
145     var genDeltaY = 1;
146     var genStartY = 50;
147     var genStartSpeed = 60;
148 
149     // Number of objects
150     var genCount = 100;
151 
152     // Determine a suitable angular displacement between each object.
153     var genTheta = Math.asin(0.5 * Math.sqrt(100 * objectSize * objectSize + genDeltaY * genDeltaY) / genRadius);
154 
155     // Whether bowl is animated.
156     var animateBowl = false;
157     var animateBowlTime = 0;
158     var prevAnimationTime = 0;
159     var animatedBowlAxis = mathDevice.v3Build(0, 0, 1);
160     var animatedBowlTransform = mathDevice.m43BuildIdentity();
161 
162     function reset() {
163         // Reset camera
164         camera.lookAt(cameraDefaultLook, worldUp, cameraDefaultPos);
165         camera.updateViewMatrix();
166 
167         // Reset physics object positions to new random values.
168         // We keep the physics objects that already exist to simplify things.
169         var n;
170         var maxN = physicsObjects.length;
171         for (n = 0; n < maxN; n += 1) {
172             var body = physicsObjects[n];
173             dynamicsWorld.removeRigidBody(body);
174             var position = mathDevice.m43BuildTranslation(genRadius * Math.cos(n * genTheta), genStartY + (genDeltaY * n) * 3, genRadius * Math.sin(n * genTheta));
175             body.transform = position;
176             body.linearVelocity = mathDevice.v3Build(0, -genStartSpeed, 0);
177             body.angularVelocity = mathDevice.v3BuildZero();
178             body.active = true;
179             dynamicsWorld.addRigidBody(body);
180         }
181     }
182 
183     var onMouseDown = function (button) {
184         if (mouseCodes.BUTTON_0 === button || mouseCodes.BUTTON_1 === button) {
185             mouseForces.onmousedown();
186         }
187     };
188 
189     var onMouseUp = function (button) {
190         if (mouseCodes.BUTTON_0 === button || mouseCodes.BUTTON_1 === button) {
191             mouseForces.onmouseup();
192         }
193     };
194 
195     var onKeyUp = function physicsOnkeyupFn(keynum) {
196         if (keynum === keyCodes.R) {
197             reset();
198         } else {
199             cameraController.onkeyup(keynum);
200         }
201     };
202 
203     // Add event listeners
204     inputDevice.addEventListener("keyup", onKeyUp);
205     inputDevice.addEventListener("mousedown", onMouseDown);
206     inputDevice.addEventListener("mouseup", onMouseUp);
207 
208     // Controls
209     var htmlControls = HTMLControls.create();
210     htmlControls.addCheckboxControl({
211         id: "checkbox01",
212         value: "debugMode",
213         isSelected: debugMode,
214         fn: function () {
215             debugMode = !debugMode;
216             return debugMode;
217         }
218     });
219     htmlControls.addCheckboxControl({
220         id: "checkbox02",
221         value: "animate",
222         isSelected: animateBowl,
223         fn: function () {
224             animateBowl = !animateBowl;
225             prevAnimationTime = TurbulenzEngine.time;
226             return animateBowl;
227         }
228     });
229     htmlControls.register();
230 
231     function drawCrosshair() {
232         if (!mouseForces.pickedBody) {
233             graphicsDevice.setTechnique(technique2d);
234 
235             var screenWidth = graphicsDevice.width;
236             var screenHeight = graphicsDevice.height;
237             techniqueParameters2d.clipSpace = mathDevice.v4Build(2.0 / screenWidth, -2.0 / screenHeight, -1.0, 1.0, techniqueParameters2d.clipSpace);
238             graphicsDevice.setTechniqueParameters(techniqueParameters2d);
239 
240             var writer = graphicsDevice.beginDraw(graphicsDevice.PRIMITIVE_LINES, 4, chFormats, chSemantics);
241 
242             if (writer) {
243                 var halfWidth = screenWidth * 0.5;
244                 var halfHeight = screenHeight * 0.5;
245                 writer(halfWidth - 10, halfHeight);
246                 writer(halfWidth + 10, halfHeight);
247                 writer(halfWidth, halfHeight - 10);
248                 writer(halfWidth, halfHeight + 10);
249 
250                 graphicsDevice.endDraw(writer);
251             }
252         }
253     }
254 
255     var nextUpdate = 0;
256 
257     var fpsElement = document.getElementById("fpscounter");
258     var lastFPS = "";
259 
260     var discreteElement = document.getElementById("discrete");
261     var preComputationsElement = document.getElementById("precomputations");
262     var physicsIterationsElement = document.getElementById("physicsiterations");
263     var continuousElement = document.getElementById("continuous");
264 
265     var lastDiscreteText = "";
266     var lastPreComputationsText = "";
267     var lastPhysicsIterationsText = "";
268     var lastContinuousText = "";
269 
270     var discreteVal = -1;
271     var preComputationsVal = -1;
272     var physicsIterationsVal = -1;
273     var continuousVal = -1;
274 
275     var displayPerformance = function displayPerformanceFn() {
276         if (TurbulenzEngine.canvas) {
277             var data = dynamicsWorld.performanceData;
278             var preval = data.sleepComputation + data.prestepContacts + data.prestepConstraints + data.integrateVelocities + data.warmstartContacts + data.warmstartConstraints;
279             var contval = data.integratePositions + data.continuous;
280 
281             if (discreteVal === -1) {
282                 discreteVal = data.discrete;
283                 preComputationsVal = preval;
284                 physicsIterationsVal = data.physicsIterations;
285                 continuousVal = contval;
286             } else {
287                 discreteVal = (0.95 * discreteVal) + (0.05 * data.discrete);
288                 preComputationsVal = (0.95 * preComputationsVal) + (0.05 * preval);
289                 physicsIterationsVal = (0.95 * physicsIterationsVal) + (0.05 * data.physicsIterations);
290                 continuousVal = (0.95 * continuousVal) + (0.05 * contval);
291             }
292         }
293 
294         var currentTime = TurbulenzEngine.time;
295         if (nextUpdate < currentTime) {
296             nextUpdate = (currentTime + 0.1);
297 
298             if (fpsElement) {
299                 var fpsText = (graphicsDevice.fps).toFixed(2);
300                 if (lastFPS !== fpsText) {
301                     lastFPS = fpsText;
302 
303                     fpsElement.innerHTML = fpsText + " fps";
304                 }
305             }
306 
307             if (TurbulenzEngine.canvas) {
308                 var discreteText = (1e3 * discreteVal).toFixed(2);
309                 var preComputationsText = (1e3 * preComputationsVal).toFixed(2);
310                 var physicsIterationsText = (1e3 * physicsIterationsVal).toFixed(2);
311                 var continuousText = (1e3 * continuousVal).toFixed(2);
312 
313                 if (discreteElement && lastDiscreteText !== discreteText) {
314                     lastDiscreteText = discreteText;
315                     discreteElement.innerHTML = discreteText + " ms";
316                 }
317 
318                 if (preComputationsElement && lastPreComputationsText !== preComputationsText) {
319                     lastPreComputationsText = preComputationsText;
320                     preComputationsElement.innerHTML = preComputationsText + " ms";
321                 }
322 
323                 if (physicsIterationsElement && lastPhysicsIterationsText !== physicsIterationsText) {
324                     lastPhysicsIterationsText = physicsIterationsText;
325                     physicsIterationsElement.innerHTML = physicsIterationsText + " ms";
326                 }
327 
328                 if (continuousElement && lastContinuousText !== continuousText) {
329                     lastContinuousText = continuousText;
330                     continuousElement.innerHTML = continuousText + " ms";
331                 }
332 
333                 discreteVal = -1;
334                 preComputationsVal = -1;
335                 physicsIterationsVal = -1;
336                 continuousVal = -1;
337             }
338         }
339     };
340 
341     // Functions to generate a physics object of a particular type.
342     var factories = [
343         // Create a random box primitive
344         function boxFactoryFn() {
345             var width = objectSize + (Math.random() * objectSize);
346             var height = objectSize + (Math.random() * objectSize);
347             var depth = objectSize + (Math.random() * objectSize);
348 
349             return physicsDevice.createBoxShape({
350                 halfExtents: mathDevice.v3Build(width, height, depth),
351                 margin: 0.001
352             });
353         },
354         // Create a random convex hull primitive
355         function convexHullFactoryFn() {
356             var radius0 = (objectSize + (Math.random() * objectSize)) * 2.0;
357             var radius1 = (objectSize + (Math.random() * objectSize)) * 2.0;
358             var radius2 = (objectSize + (Math.random() * objectSize)) * 2.0;
359             var numPoints = Math.floor(5 + Math.random() * 30);
360 
361             var positionsData = [];
362             var i;
363             for (i = 0; i < (numPoints * 3); i += 3) {
364                 var azimuth = Math.random() * Math.PI * 2;
365                 var elevation = (Math.random() - 0.5) * Math.PI;
366                 positionsData[i] = Math.sin(azimuth) * Math.cos(elevation) * radius0;
367                 positionsData[i + 1] = Math.cos(azimuth) * radius2;
368                 positionsData[i + 2] = Math.sin(azimuth) * Math.sin(elevation) * radius1;
369             }
370 
371             return physicsDevice.createConvexHullShape({
372                 points: positionsData,
373                 margin: 0.001
374             });
375         },
376         // Create a random sphere primitive
377         function sphereFactoryFn() {
378             return physicsDevice.createSphereShape({
379                 radius: (objectSize + (Math.random() * objectSize)) * 1.5,
380                 margin: 0.0
381             });
382         },
383         // Create a random capsule primitive
384         function capsuleFactoryFn() {
385             return physicsDevice.createCapsuleShape({
386                 radius: (objectSize + (Math.random() * objectSize)),
387                 height: (objectSize + (Math.random() * objectSize)) * 2,
388                 margin: 0.001
389             });
390         },
391         // Create a random cylinder primitive
392         function cylinderFactoryFn() {
393             var radius = (objectSize + (Math.random() * objectSize));
394             var height = (objectSize + (Math.random() * objectSize));
395 
396             return physicsDevice.createCylinderShape({
397                 halfExtents: mathDevice.v3Build(radius, height, radius),
398                 margin: 0.001
399             });
400         },
401         // Create a random cone primitive
402         function coneFactoryFn() {
403             return physicsDevice.createConeShape({
404                 radius: (objectSize + (Math.random() * objectSize)) * 1.5,
405                 height: (objectSize + (Math.random() * objectSize)) * 3,
406                 margin: 0.001
407             });
408         }
409     ];
410 
411     var skipFrame = true;
412 
413     var deferredObjectCreation = function deferredObjectCreationFn() {
414         var i = physicsObjects.length;
415         var shape = factories[Math.floor(factories.length * Math.random())]();
416         var position = mathDevice.m43BuildTranslation(genRadius * Math.cos(i * genTheta), genStartY + (genDeltaY * i), genRadius * Math.sin(i * genTheta));
417 
418         var sceneNode = SceneNode.create({
419             name: "Phys" + i,
420             local: position,
421             dynamic: true,
422             disabled: false
423         });
424 
425         var rigidBody = physicsDevice.createRigidBody({
426             shape: shape,
427             mass: 10.0,
428             inertia: mathDevice.v3ScalarMul(shape.inertia, 10.0),
429             transform: position,
430             friction: 0.8,
431             restitution: 0.2,
432             angularDamping: 0.4,
433             linearVelocity: mathDevice.v3Build(0, -genStartSpeed, 0)
434         });
435 
436         scene.addRootNode(sceneNode);
437 
438         physicsManager.addNode(sceneNode, rigidBody);
439 
440         physicsObjects.push(rigidBody);
441     };
442 
443     var renderFrame = function renderFrameFn() {
444         // Update input and camera
445         inputDevice.update();
446 
447         if (mouseForces.pickedBody) {
448             // If we're dragging a body don't apply the movement to the camera
449             cameraController.pitch = 0;
450             cameraController.turn = 0;
451             cameraController.step = 0;
452         }
453 
454         cameraController.update();
455 
456         var deviceWidth = graphicsDevice.width;
457         var deviceHeight = graphicsDevice.height;
458         var aspectRatio = (deviceWidth / deviceHeight);
459         if (aspectRatio !== camera.aspectRatio) {
460             camera.aspectRatio = aspectRatio;
461             camera.updateProjectionMatrix();
462         }
463         camera.updateViewProjectionMatrix();
464 
465         if (physicsObjects.length !== genCount && !skipFrame) {
466             deferredObjectCreation();
467         }
468         skipFrame = !skipFrame;
469 
470         if (animateBowl) {
471             animateBowlTime += (TurbulenzEngine.time - prevAnimationTime);
472             prevAnimationTime = TurbulenzEngine.time;
473 
474             mathDevice.m43FromAxisRotation(animatedBowlAxis, 0.5 * Math.sin(animateBowlTime), animatedBowlTransform);
475             animatedBowlTransform[10] = Math.abs(7 * 0.5 * Math.sin(Math.sin(animateBowlTime)));
476             bowlObject.transform = animatedBowlTransform;
477         }
478 
479         // Update the physics
480         mouseForces.update(dynamicsWorld, camera, 0.1);
481         dynamicsWorld.update();
482 
483         physicsManager.update();
484         scene.update();
485 
486         scene.updateVisibleNodes(camera);
487 
488         if (graphicsDevice.beginFrame()) {
489             graphicsDevice.clear(clearColor, 1.0, 0);
490 
491             floor.render(graphicsDevice, camera);
492 
493             if (debugMode) {
494                 scene.drawPhysicsGeometry(graphicsDevice, shaderManager, camera, physicsManager);
495             }
496 
497             drawCrosshair();
498 
499             graphicsDevice.endFrame();
500         }
501 
502         displayPerformance();
503     };
504 
505     var intervalID;
506     var loadingCompleted = false;
507     var loadingLoop = function loadingLoopFn() {
508         if (graphicsDevice.beginFrame()) {
509             graphicsDevice.clear(loadingClearColor);
510             graphicsDevice.endFrame();
511         }
512 
513         if (loadingCompleted) {
514             TurbulenzEngine.clearInterval(intervalID);
515 
516             camera.lookAt(cameraDefaultLook, worldUp, cameraDefaultPos);
517             camera.updateViewMatrix();
518 
519             shader2d = shaderManager.get("shaders/generic2D.cgfx");
520             technique2d = shader2d.getTechnique("constantColor2D");
521             techniqueParameters2d = graphicsDevice.createTechniqueParameters({
522                 clipSpace: mathDevice.v4BuildOne(),
523                 constantColor: mathDevice.v4Build(0, 0, 0, 1)
524             });
525 
526             intervalID = TurbulenzEngine.setInterval(renderFrame, 1000 / 60);
527         }
528     };
529     intervalID = TurbulenzEngine.setInterval(loadingLoop, 1000 / 10);
530 
531     // Change the clear color before we start loading assets
532     loadingLoop();
533 
534     var postLoad = function postLoadFn() {
535         // Floor is represented by a plane shape
536         var floorShape = physicsDevice.createPlaneShape({
537             normal: mathDevice.v3Build(0, 1, 0),
538             distance: 0,
539             margin: 0.001
540         });
541 
542         var floorObject = physicsDevice.createCollisionObject({
543             shape: floorShape,
544             transform: mathDevice.m43BuildIdentity(),
545             friction: 0.8,
546             restitution: 0.1,
547             group: physicsDevice.FILTER_STATIC,
548             mask: physicsDevice.FILTER_ALL
549         });
550 
551         // Adds the floor collision object to the world
552         dynamicsWorld.addCollisionObject(floorObject);
553 
554         // Bowl is represented by a triangle mesh shape.
555         // We create the triangle mesh simply and manually.
556         var positionsData = [];
557         var indicesData = [];
558 
559         // Compute bowl vertices.
560         var i, j, offset;
561         for (i = 0; i < depthN; i += 1) {
562             var elevation = (Math.PI * 0.75) * ((i + 1) / (depthN + 2));
563             for (j = 0; j < radialN; j += 1) {
564                 var azimuth = (Math.PI * 2) * (j / radialN);
565 
566                 offset = ((i * radialN) + j) * 3;
567                 positionsData[offset] = Math.sin(elevation) * Math.cos(azimuth) * bowlRadius;
568                 positionsData[offset + 1] = (1 - Math.cos(elevation)) * bowlHeight;
569                 positionsData[offset + 2] = Math.sin(elevation) * Math.sin(azimuth) * bowlRadius;
570             }
571         }
572 
573         offset = (depthN * radialN) * 3;
574         positionsData[offset] = 0;
575         positionsData[offset + 1] = 0;
576         positionsData[offset + 2] = 0;
577 
578         for (i = 0; i < (depthN - 1); i += 1) {
579             for (j = 0; j < radialN; j += 1) {
580                 offset = ((i * radialN) + j) * 3 * 2;
581                 indicesData[offset] = (i * radialN) + j;
582                 indicesData[offset + 1] = (i * radialN) + ((j + 1) % radialN);
583                 indicesData[offset + 2] = ((i + 1) * radialN) + j;
584 
585                 indicesData[offset + 3] = ((i + 1) * radialN) + j;
586                 indicesData[offset + 4] = (i * radialN) + ((j + 1) % radialN);
587                 indicesData[offset + 5] = ((i + 1) * radialN) + ((j + 1) % radialN);
588             }
589         }
590 
591         for (i = 0; i < radialN; i += 1) {
592             offset = (((depthN - 1) * radialN) * 3 * 2) + (i * 3);
593             indicesData[offset] = i;
594             indicesData[offset + 1] = (depthN * radialN);
595             indicesData[offset + 2] = ((i + 1) % radialN);
596         }
597 
598         // Create triangle array for bowl
599         var bowlTriangleArray = physicsDevice.createTriangleArray({
600             vertices: positionsData,
601             indices: indicesData
602         });
603 
604         // Create bowl physics shape and object.
605         var bowlShape = physicsDevice.createTriangleMeshShape({
606             triangleArray: bowlTriangleArray,
607             margin: 0.001
608         });
609 
610         bowlObject = physicsDevice.createCollisionObject({
611             shape: bowlShape,
612             transform: mathDevice.m43BuildIdentity(),
613             friction: 0.8,
614             restitution: 0.1,
615             group: physicsDevice.FILTER_STATIC,
616             mask: physicsDevice.FILTER_ALL,
617             kinematic: true
618         });
619 
620         // Create SceneNode for bowl, and add to scene.
621         var bowlSceneNode = SceneNode.create({
622             name: "Bowl",
623             local: bowlObject.transform,
624             dynamic: true,
625             disabled: false
626         });
627 
628         scene.addRootNode(bowlSceneNode);
629 
630         physicsManager.addNode(bowlSceneNode, bowlObject, null, bowlTriangleArray);
631     };
632 
633     var numShadersToLoad = 2;
634 
635     var shadersLoaded = function shadersLoadedFn(/* shader */ ) {
636         numShadersToLoad -= 1;
637         if (0 === numShadersToLoad) {
638             postLoad();
639             loadingCompleted = true;
640         }
641     };
642 
643     var loadAssets = function loadAssetsFn() {
644         shaderManager.load("shaders/debug.cgfx", shadersLoaded);
645         shaderManager.load("shaders/generic2D.cgfx", shadersLoaded);
646     };
647 
648     var mappingTableReceived = function mappingTableReceivedFn(mappingTable) {
649         shaderManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
650         sceneLoader.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
651 
652         loadAssets();
653     };
654 
655     var gameSessionCreated = function gameSessionCreatedFn(gameSession) {
656         TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived);
657     };
658 
659     var gameSessionFailed = function gameSessionFailedFn(reason) {
660         gameSessionCreated(null);
661     };
662 
663     var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated);
664 
665     // Create a scene destroy callback to run when the window is closed
666     function destroyScene() {
667         gameSession.destroy();
668 
669         TurbulenzEngine.clearInterval(intervalID);
670         clearColor = null;
671 
672         if (physicsManager) {
673             physicsManager.clear();
674             physicsManager = null;
675         }
676 
677         if (scene) {
678             scene.destroy();
679             scene = null;
680         }
681 
682         requestHandler = null;
683 
684         camera = null;
685 
686         techniqueParameters2d = null;
687         technique2d = null;
688         shader2d = null;
689         chSemantics = null;
690         chFormats = null;
691 
692         if (shaderManager) {
693             shaderManager.destroy();
694             shaderManager = null;
695         }
696 
697         TurbulenzEngine.flush();
698         graphicsDevice = null;
699         mathDevice = null;
700         physicsDevice = null;
701         dynamicsWorld = null;
702         mouseCodes = null;
703         keyCodes = null;
704         inputDevice = null;
705         cameraController = null;
706         floor = null;
707     }
708 
709     TurbulenzEngine.onunload = destroyScene;
710 };