physics_collisionmesh.js
  1 /*{# Copyright (c) 2011-2012 Turbulenz Limited #}*/
  2 /*
  3 * @title: 3D Physics collision meshes
  4 * @description:
  5 * This sample shows how to create rigid body physics nodes with each of the collision mesh types (boxes, spheres,
  6 * cones, cylinders, capsules and convex hulls) and a static triangle mesh.
  7 *
  8 * Rigid body cubes with an initial velocity can be fired into the scene from first person perspective by pressing
  9 * space. Click on the rendering window to move and rotate the camera around.
 10 */
 11 /*{{ javascript("jslib/aabbtree.js") }}*/
 12 /*{{ javascript("jslib/camera.js") }}*/
 13 /*{{ javascript("jslib/floor.js") }}*/
 14 /*{{ javascript("jslib/geometry.js") }}*/
 15 /*{{ javascript("jslib/material.js") }}*/
 16 /*{{ javascript("jslib/light.js") }}*/
 17 /*{{ javascript("jslib/scenenode.js") }}*/
 18 /*{{ javascript("jslib/scene.js") }}*/
 19 /*{{ javascript("jslib/vmath.js") }}*/
 20 /*{{ javascript("jslib/effectmanager.js") }}*/
 21 /*{{ javascript("jslib/shadermanager.js") }}*/
 22 /*{{ javascript("jslib/texturemanager.js") }}*/
 23 /*{{ javascript("jslib/renderingcommon.js") }}*/
 24 /*{{ javascript("jslib/defaultrendering.js") }}*/
 25 /*{{ javascript("jslib/resourceloader.js") }}*/
 26 /*{{ javascript("jslib/scenedebugging.js") }}*/
 27 /*{{ javascript("jslib/observer.js") }}*/
 28 /*{{ javascript("jslib/physicsmanager.js") }}*/
 29 /*{{ javascript("jslib/utilities.js") }}*/
 30 /*{{ javascript("jslib/vertexbuffermanager.js") }}*/
 31 /*{{ javascript("jslib/indexbuffermanager.js") }}*/
 32 /*{{ javascript("jslib/mouseforces.js") }}*/
 33 /*{{ javascript("jslib/utilities.js") }}*/
 34 /*{{ javascript("jslib/requesthandler.js") }}*/
 35 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/
 36 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/
 37 /*{{ javascript("jslib/services/gamesession.js") }}*/
 38 /*{{ javascript("jslib/services/mappingtable.js") }}*/
 39 /*{{ javascript("scripts/htmlcontrols.js") }}*/
 40 /*{{ javascript("scripts/sceneloader.js") }}*/
 41 /*global TurbulenzEngine: true */
 42 /*global DefaultRendering: false */
 43 /*global RequestHandler: false */
 44 /*global SceneLoader: false */
 45 /*global SceneNode: false */
 46 /*global TurbulenzServices: false */
 47 /*global TextureManager: false */
 48 /*global ShaderManager: false */
 49 /*global EffectManager: false */
 50 /*global Scene: false */
 51 /*global Camera: false */
 52 /*global CameraController: false */
 53 /*global Floor: false */
 54 /*global MouseForces: false */
 55 /*global PhysicsManager: false */
 56 /*global HTMLControls: false */
 57 TurbulenzEngine.onload = function onloadFn() {
 58     var errorCallback = function errorCallback(msg) {
 59         window.alert(msg);
 60     };
 61     TurbulenzEngine.onerror = errorCallback;
 62 
 63     var warningCallback = function warningCallback(msg) {
 64         window.alert(msg);
 65     };
 66     TurbulenzEngine.onwarning = warningCallback;
 67 
 68     var mathDeviceParameters = {};
 69     var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters);
 70 
 71     var graphicsDeviceParameters = {};
 72     var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters);
 73 
 74     var physicsDeviceParameters = {};
 75     var physicsDevice = TurbulenzEngine.createPhysicsDevice(physicsDeviceParameters);
 76 
 77     var dynamicsWorldParameters = {};
 78     var dynamicsWorld = physicsDevice.createDynamicsWorld(dynamicsWorldParameters);
 79 
 80     var inputDeviceParameters = {};
 81     var inputDevice = TurbulenzEngine.createInputDevice(inputDeviceParameters);
 82 
 83     var requestHandlerParameters = {};
 84     var requestHandler = RequestHandler.create(requestHandlerParameters);
 85 
 86     var textureManager = TextureManager.create(graphicsDevice, requestHandler, null, errorCallback);
 87     var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback);
 88     var effectManager = EffectManager.create();
 89     var physicsManager = PhysicsManager.create(mathDevice, physicsDevice, dynamicsWorld);
 90 
 91     var mappingTable;
 92     var debugMode = true;
 93 
 94     // Renderer and assets for the scene.
 95     var renderer;
 96     var scene = Scene.create(mathDevice);
 97     var sceneLoader = SceneLoader.create();
 98     var duckMesh;
 99 
100     // Setup world space
101     var clearColor = mathDevice.v4Build(0.95, 0.95, 1.0, 1.0);
102     var loadingClearColor = mathDevice.v4Build(0.8, 0.8, 0.8, 1.0);
103     var worldUp = mathDevice.v3BuildYAxis();
104 
105     // Setup a camera to view a close-up object
106     var camera = Camera.create(mathDevice);
107     camera.nearPlane = 0.05;
108     var cameraDefaultPos = mathDevice.v3Build(14.5, 8.0, 18.1);
109     var cameraDefaultLook = mathDevice.v3Build(14.5, -(camera.farPlane / 2), -camera.farPlane);
110 
111     // The objects needed to draw the crosshair
112     var technique2d;
113     var shader2d;
114     var techniqueParameters2d;
115     var chSemantics = graphicsDevice.createSemantics(['POSITION']);
116     var chFormats = [graphicsDevice.VERTEXFORMAT_FLOAT3];
117 
118     // The objects needed to draw the contact callbacks
119     var contactsTechnique;
120     var contactsShader;
121     var contactsTechniqueParameters;
122     var contactsSemantics = graphicsDevice.createSemantics(['POSITION']);
123     var contactsFormats = [graphicsDevice.VERTEXFORMAT_FLOAT3];
124     var contactWorldTransform = mathDevice.m43BuildIdentity();
125     var contactWorldPoint = mathDevice.v3BuildZero();
126     var contacts = [];
127     var numContacts = 0;
128 
129     // Setup world floor
130     var floor = Floor.create(graphicsDevice, mathDevice);
131     var cameraController = CameraController.create(graphicsDevice, inputDevice, camera);
132 
133     // Mouse forces
134     var dragMin = mathDevice.v3Build(-50, -50, -50);
135     var dragMax = mathDevice.v3Build(50, 50, 50);
136     var mouseForces = MouseForces.create(graphicsDevice, inputDevice, mathDevice, physicsDevice, dragMin, dragMax);
137     mouseForces.clamp = 400;
138 
139     // Control codes
140     var keyCodes = inputDevice.keyCodes;
141     var mouseCodes = inputDevice.mouseCodes;
142 
143     // Setup the box firing objects, including the inertia
144     var boxes = [];
145     var numBoxes = 12;
146     var fireCount = 0;
147     var cubeExtents = mathDevice.v3Build(0.5, 0.5, 0.5);
148     var boxShape = physicsDevice.createBoxShape({
149         halfExtents: cubeExtents,
150         margin: 0.001
151     });
152 
153     var inertia = mathDevice.v3Copy(boxShape.inertia);
154     inertia = mathDevice.v3ScalarMul(inertia, 1.0);
155 
156     function reset() {
157         var halfPI = Math.PI / 2;
158         var halfExtents = duckMesh.localHalfExtents;
159         var yhalfExtent = halfExtents[1];
160         var j = 1;
161 
162         function resetTransform(node, rotationMatrix) {
163             var body = node.physicsNodes[0].body;
164             dynamicsWorld.removeRigidBody(body);
165             body.transform = mathDevice.m43BuildTranslation(j * 5, yhalfExtent, 0, body.transform);
166             if (rotationMatrix) {
167                 body.transform = mathDevice.m43Mul(rotationMatrix, body.transform, body.transform);
168             }
169             body.linearVelocity = mathDevice.v3BuildZero();
170             body.angularVelocity = mathDevice.v3BuildZero();
171             body.active = true;
172             dynamicsWorld.addRigidBody(body);
173             j += 1;
174         }
175         ;
176 
177         // Reset ducks
178         var rootNode = scene.findNode("DuckBoxPhys");
179         resetTransform(rootNode);
180         rootNode = scene.findNode("DuckConePhys");
181         resetTransform(rootNode);
182         rootNode = scene.findNode("DuckCylinderPhys");
183         resetTransform(rootNode);
184         rootNode = scene.findNode("DuckSpherePhys");
185         resetTransform(rootNode);
186         rootNode = scene.findNode("DuckCapsulePhys");
187         var rot = mathDevice.m43FromAxisRotation(mathDevice.v3BuildXAxis(), halfPI);
188         mathDevice.m43SetAxisRotation(rot, mathDevice.v3BuildZAxis(), halfPI);
189         resetTransform(rootNode, rot);
190         rootNode = scene.findNode("DuckConvexHullPhys");
191         resetTransform(rootNode);
192 
193         // Reset boxes
194         var count = 0;
195         if (fireCount > 0 && fireCount < numBoxes) {
196             count = fireCount;
197         } else if (fireCount >= numBoxes) {
198             count = numBoxes;
199         }
200 
201         for (var i = 0; i < count; i += 1) {
202             var box = boxes[i];
203             var node = box.target;
204             physicsManager.deletePhysicsNode(box);
205             physicsManager.enableNode(node, false);
206         }
207         fireCount = 0;
208 
209         // Reset camera
210         camera.lookAt(cameraDefaultLook, worldUp, cameraDefaultPos);
211         camera.updateViewMatrix();
212     }
213 
214     function fireBox() {
215         mouseForces.mouseX = 0.5;
216         mouseForces.mouseY = 0.5;
217         mouseForces.mouseZ = 0.0;
218         mouseForces.generatePickRay(camera.matrix, 1.0 / camera.recipViewWindowX, 1.0 / camera.recipViewWindowY, camera.aspectRatio, camera.farPlane);
219 
220         var tr = mathDevice.m43BuildTranslation(mouseForces.pickRayFrom[0], mouseForces.pickRayFrom[1], mouseForces.pickRayFrom[2]);
221 
222         var linVel = mathDevice.v3Build(mouseForces.pickRayTo[0] - mouseForces.pickRayFrom[0], mouseForces.pickRayTo[1] - mouseForces.pickRayFrom[1], mouseForces.pickRayTo[2] - mouseForces.pickRayFrom[2]);
223         mathDevice.v3Normalize(linVel, linVel);
224         mathDevice.v3ScalarMul(linVel, 50.0, linVel);
225 
226         var box = boxes[fireCount % numBoxes];
227         physicsManager.deletePhysicsNode(box);
228         var node = box.target;
229         var body = box.body;
230         if (fireCount > numBoxes - 1) {
231             physicsManager.enableNode(node, false);
232         }
233         body.transform = tr;
234         body.angularVelocity = mathDevice.v3BuildZero();
235         body.linearVelocity = linVel;
236         body.active = true;
237 
238         physicsManager.physicsNodes.push(box);
239         physicsManager.dynamicPhysicsNodes.push(box);
240         physicsManager.enableNode(node, true);
241 
242         fireCount += 1;
243     }
244 
245     var onMouseDown = function (button) {
246         if (mouseCodes.BUTTON_0 === button || mouseCodes.BUTTON_1 === button) {
247             mouseForces.onmousedown();
248         }
249     };
250 
251     var onMouseUp = function (button) {
252         if (mouseCodes.BUTTON_0 === button || mouseCodes.BUTTON_1 === button) {
253             mouseForces.onmouseup();
254         }
255         if (mouseCodes.BUTTON_2 === button) {
256             mouseForces.onmouseup();
257             fireBox();
258         }
259     };
260 
261     var onKeyUp = function physicsOnkeyupFn(keynum) {
262         if (keynum === keyCodes.R) {
263             reset();
264         }
265         if (keynum === keyCodes.SPACE) {
266             fireBox();
267         } else {
268             cameraController.onkeyup(keynum);
269         }
270     };
271 
272     // Add event listeners
273     inputDevice.addEventListener("keyup", onKeyUp);
274     inputDevice.addEventListener("mousedown", onMouseDown);
275     inputDevice.addEventListener("mouseup", onMouseUp);
276 
277     // Controls
278     var htmlControls = HTMLControls.create();
279     htmlControls.addCheckboxControl({
280         id: "checkbox01",
281         value: "debugMode",
282         isSelected: debugMode,
283         fn: function () {
284             debugMode = !debugMode;
285             duckMesh.setDisabled(debugMode);
286             return debugMode;
287         }
288     });
289     htmlControls.register();
290 
291     function drawCrosshair() {
292         if (!mouseForces.pickedBody) {
293             graphicsDevice.setTechnique(technique2d);
294 
295             var screenWidth = graphicsDevice.width;
296             var screenHeight = graphicsDevice.height;
297             techniqueParameters2d.clipSpace = mathDevice.v4Build(2.0 / screenWidth, -2.0 / screenHeight, -1.0, 1.0);
298             graphicsDevice.setTechniqueParameters(techniqueParameters2d);
299 
300             var writer = graphicsDevice.beginDraw(graphicsDevice.PRIMITIVE_LINES, 4, chFormats, chSemantics);
301 
302             if (writer) {
303                 var halfWidth = screenWidth * 0.5;
304                 var halfHeight = screenHeight * 0.5;
305                 writer([halfWidth - 10, halfHeight]);
306                 writer([halfWidth + 10, halfHeight]);
307                 writer([halfWidth, halfHeight - 10]);
308                 writer([halfWidth, halfHeight + 10]);
309 
310                 graphicsDevice.endDraw(writer);
311             }
312         }
313     }
314 
315     //function addContact(objectA, objectB, pairContact)
316     //{
317     //    if (debugMode)
318     //    {
319     //        objectB.calculateTransform(contactWorldTransform);
320     //        mathDevice.m43TransformPoint(contactWorldTransform, pairContact.localPointOnB, contactWorldPoint);
321     //        var contactNormal = pairContact.worldNormalOnB;
322     //        if (numContacts >= contacts.length)
323     //        {
324     //            contacts[contacts.length] = new Float32Array(6);
325     //        }
326     //        var contact = contacts[numContacts];
327     //        contact[0] = contactWorldPoint[0];
328     //        contact[1] = contactWorldPoint[1];
329     //        contact[2] = contactWorldPoint[2];
330     //        contact[3] = contactWorldPoint[0] - contactNormal[0];
331     //        contact[4] = contactWorldPoint[1] - contactNormal[1];
332     //        contact[5] = contactWorldPoint[2] - contactNormal[2];
333     //        numContacts += 1;
334     //    }
335     //}
336     function addContacts(objectA, objectB, pairContacts) {
337         if (debugMode) {
338             var numPairContacts = pairContacts.length;
339             var n;
340             objectB.calculateTransform(contactWorldTransform);
341             for (n = 0; n < numPairContacts; n += 1) {
342                 var pairContact = pairContacts[n];
343                 mathDevice.m43TransformPoint(contactWorldTransform, pairContact.localPointOnB, contactWorldPoint);
344                 var contactNormal = pairContact.worldNormalOnB;
345                 if (numContacts >= contacts.length) {
346                     contacts[contacts.length] = new Float32Array(6);
347                 }
348                 var contact = contacts[numContacts];
349                 contact[0] = contactWorldPoint[0];
350                 contact[1] = contactWorldPoint[1];
351                 contact[2] = contactWorldPoint[2];
352                 contact[3] = contactWorldPoint[0] - contactNormal[0];
353                 contact[4] = contactWorldPoint[1] - contactNormal[1];
354                 contact[5] = contactWorldPoint[2] - contactNormal[2];
355                 numContacts += 1;
356             }
357         }
358     }
359 
360     function drawContacts() {
361         if (numContacts) {
362             graphicsDevice.setTechnique(contactsTechnique);
363 
364             contactsTechniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
365             graphicsDevice.setTechniqueParameters(contactsTechniqueParameters);
366 
367             var writer = graphicsDevice.beginDraw(graphicsDevice.PRIMITIVE_LINES, numContacts * 2, contactsFormats, contactsSemantics);
368 
369             if (writer) {
370                 var n;
371                 for (n = 0; n < numContacts; n += 1) {
372                     var contact = contacts[n];
373                     writer(contact[0], contact[1], contact[2]);
374                     writer(contact[3], contact[4], contact[5]);
375                 }
376 
377                 graphicsDevice.endDraw(writer);
378             }
379         }
380     }
381 
382     var renderFrame = function renderFrameFn() {
383         var currentTime = TurbulenzEngine.time;
384 
385         // Update input and camera
386         inputDevice.update();
387 
388         if (mouseForces.pickedBody) {
389             // If we're dragging a body don't apply the movement to the camera
390             cameraController.pitch = 0;
391             cameraController.turn = 0;
392             cameraController.step = 0;
393         }
394 
395         cameraController.update();
396 
397         var deviceWidth = graphicsDevice.width;
398         var deviceHeight = graphicsDevice.height;
399         var aspectRatio = (deviceWidth / deviceHeight);
400         if (aspectRatio !== camera.aspectRatio) {
401             camera.aspectRatio = aspectRatio;
402             camera.updateProjectionMatrix();
403         }
404         camera.updateViewProjectionMatrix();
405 
406         numContacts = 0;
407 
408         // Update the physics
409         mouseForces.update(dynamicsWorld, camera, 0.1);
410         dynamicsWorld.update();
411 
412         physicsManager.update();
413         scene.update();
414 
415         renderer.update(graphicsDevice, camera, scene, currentTime);
416 
417         if (graphicsDevice.beginFrame()) {
418             if (renderer.updateBuffers(graphicsDevice, deviceWidth, deviceHeight)) {
419                 renderer.draw(graphicsDevice, clearColor);
420                 floor.render(graphicsDevice, camera);
421                 if (debugMode) {
422                     scene.drawPhysicsNodes(graphicsDevice, shaderManager, camera, physicsManager);
423                     scene.drawPhysicsGeometry(graphicsDevice, shaderManager, camera, physicsManager);
424                     drawContacts();
425                 }
426             }
427 
428             drawCrosshair();
429 
430             graphicsDevice.endFrame();
431         }
432     };
433 
434     var intervalID;
435     var loadingLoop = function loadingLoopFn() {
436         if (graphicsDevice.beginFrame()) {
437             graphicsDevice.clear(loadingClearColor);
438             graphicsDevice.endFrame();
439         }
440 
441         if (sceneLoader.complete()) {
442             TurbulenzEngine.clearInterval(intervalID);
443 
444             camera.lookAt(cameraDefaultLook, worldUp, cameraDefaultPos);
445             camera.updateViewMatrix();
446 
447             renderer.updateShader(shaderManager);
448 
449             shader2d = shaderManager.get("shaders/generic2D.cgfx");
450             technique2d = shader2d.getTechnique("constantColor2D");
451             techniqueParameters2d = graphicsDevice.createTechniqueParameters({
452                 clipSpace: null,
453                 constantColor: mathDevice.v4Build(0, 0, 0, 1)
454             });
455 
456             contactsShader = shaderManager.get("shaders/debug.cgfx");
457             contactsTechnique = contactsShader.getTechnique("debug_lines_constant");
458             contactsTechniqueParameters = graphicsDevice.createTechniqueParameters({
459                 worldViewProjection: null,
460                 constantColor: mathDevice.v4Build(1, 0, 0, 1)
461             });
462 
463             if (physicsManager.physicsNodes.length >= 0) {
464                 // Floor is represented by a plane shape
465                 var floorShape = physicsDevice.createPlaneShape({
466                     normal: mathDevice.v3Build(0, 1, 0),
467                     distance: 0,
468                     margin: 0.001
469                 });
470 
471                 var floorObject = physicsDevice.createCollisionObject({
472                     shape: floorShape,
473                     transform: mathDevice.m43BuildIdentity(),
474                     friction: 0.8,
475                     restitution: 0.1,
476                     group: physicsDevice.FILTER_STATIC,
477                     mask: physicsDevice.FILTER_ALL,
478                     //onPreSolveContact : addContact,
479                     //onAddedContacts : addContacts
480                     onProcessedContacts: addContacts
481                 });
482 
483                 // Adds the floor collision object to the world
484                 dynamicsWorld.addCollisionObject(floorObject);
485             }
486             intervalID = TurbulenzEngine.setInterval(renderFrame, 1000 / 60);
487         }
488     };
489     intervalID = TurbulenzEngine.setInterval(loadingLoop, 1000 / 10);
490 
491     // Change the clear color before we start loading assets
492     loadingLoop();
493 
494     var postLoad = function postLoadFn() {
495         var mass = 10.0;
496         var margin = 0.001;
497         duckMesh = scene.findNode("DuckMesh");
498         var halfExtents = duckMesh.localHalfExtents;
499         var xhalfExtent = halfExtents[0];
500         var yhalfExtent = halfExtents[1];
501         var halfPI = Math.PI / 2;
502         var xAxis = mathDevice.v3BuildXAxis();
503         var zAxis = mathDevice.v3BuildZAxis();
504 
505         function newPhysicsNode(name, shape, offsetTransform, pos) {
506             var duckGeom = duckMesh.clone(name + "Geom");
507             physicsManager.deletePhysicsNode(duckGeom.physicsNodes[0]);
508             duckGeom.physicsNodes = [];
509             duckGeom.setLocalTransform(offsetTransform);
510 
511             var duckPhys = SceneNode.create({
512                 name: name + "Phys",
513                 local: pos,
514                 dynamic: true,
515                 disabled: false
516             });
517 
518             var rigidBody = physicsDevice.createRigidBody({
519                 shape: shape,
520                 mass: mass,
521                 inertia: mathDevice.v3ScalarMul(shape.inertia, mass),
522                 transform: pos,
523                 friction: 0.7,
524                 restitution: 0.2,
525                 angularDamping: 0.4
526             });
527 
528             var physicsNode = {
529                 body: rigidBody,
530                 target: duckPhys,
531                 dynamic: true
532             };
533 
534             scene.addRootNode(duckPhys);
535             duckPhys.addChild(duckGeom);
536             duckPhys.physicsNodes = [physicsNode];
537             duckPhys.setDynamic();
538 
539             physicsManager.physicsNodes.push(physicsNode);
540             physicsManager.dynamicPhysicsNodes.push(physicsNode);
541             physicsManager.enableHierarchy(duckPhys, true);
542         }
543 
544         // Build a box duck
545         var shape = physicsDevice.createBoxShape({
546             halfExtents: halfExtents,
547             margin: margin
548         });
549 
550         var position = mathDevice.m43BuildTranslation(5, yhalfExtent, 0);
551         newPhysicsNode("DuckBox", shape, mathDevice.m43BuildIdentity(), position);
552 
553         // Build a cone duck
554         shape = physicsDevice.createConeShape({
555             height: yhalfExtent * 2,
556             radius: xhalfExtent,
557             margin: margin
558         });
559 
560         mathDevice.m43BuildTranslation(10, yhalfExtent, 0, position);
561         newPhysicsNode("DuckCone", shape, mathDevice.m43BuildIdentity(), position);
562 
563         // Build a cylinder duck
564         shape = physicsDevice.createCylinderShape({
565             halfExtents: [xhalfExtent, yhalfExtent, xhalfExtent],
566             margin: margin
567         });
568 
569         mathDevice.m43BuildTranslation(15, yhalfExtent, 0, position);
570         newPhysicsNode("DuckCylinder", shape, mathDevice.m43BuildIdentity(), position);
571 
572         // Build a sphere duck
573         shape = physicsDevice.createSphereShape({
574             radius: xhalfExtent,
575             margin: margin
576         });
577 
578         mathDevice.m43BuildTranslation(20, yhalfExtent, 0, position);
579         newPhysicsNode("DuckSphere", shape, mathDevice.m43BuildIdentity(), position);
580 
581         // Build a capsule duck
582         shape = physicsDevice.createCapsuleShape({
583             radius: xhalfExtent,
584             height: yhalfExtent * 2,
585             margin: margin
586         });
587 
588         // Capsules always take their height in the Y-axis
589         // Rotate the capsule so it is flat against the floor
590         // Rotate the duck so it is facing the correct direction
591         mathDevice.m43BuildTranslation(25, yhalfExtent, 0, position);
592         mathDevice.m43SetAxisRotation(position, xAxis, halfPI);
593         mathDevice.m43SetAxisRotation(position, zAxis, halfPI);
594         newPhysicsNode("DuckCapsule", shape, mathDevice.m43FromAxisRotation(zAxis, -halfPI), position);
595 
596         // Build a convex hull duck
597         shape = physicsDevice.createConvexHullShape({
598             points: duckMesh.physicsNodes[0].triangleArray.vertices,
599             margin: margin,
600             minExtent: mathDevice.v3Neg(halfExtents),
601             maxExtent: halfExtents
602         });
603 
604         mathDevice.m43BuildTranslation(30, yhalfExtent, 0, position);
605         newPhysicsNode("DuckConvexHull", shape, mathDevice.m43BuildIdentity(), position);
606 
607         // Set DuckMesh to disabled when debug rendering is enabled
608         // This is to prevent Z-fighting between the geometry of the triangle mesh and asset
609         duckMesh.setDisabled(true);
610 
611         // Create a pool of boxes
612         var identity = mathDevice.m43BuildIdentity();
613         for (var i = 0; i < numBoxes; i += 1) {
614             var box = physicsDevice.createRigidBody({
615                 shape: boxShape,
616                 mass: 1.0,
617                 inertia: boxShape.inertia,
618                 transform: identity,
619                 friction: 0.9,
620                 restitution: 0.1
621             });
622 
623             var newBox = SceneNode.create({
624                 name: "box" + i,
625                 local: identity,
626                 dynamic: true,
627                 disabled: false
628             });
629             var physicsNode = {
630                 body: box,
631                 target: newBox,
632                 dynamic: true
633             };
634             newBox.physicsNodes = [physicsNode];
635             scene.addRootNode(newBox);
636             boxes[i] = physicsNode;
637         }
638     };
639 
640     var loadAssets = function loadAssetsFn() {
641         // Renderer for the scene.
642         renderer = DefaultRendering.create(graphicsDevice, mathDevice, shaderManager, effectManager);
643 
644         renderer.setGlobalLightPosition(mathDevice.v3Build(0.5, 100.0, 0.5));
645         renderer.setAmbientColor(mathDevice.v3Build(0.3, 0.3, 0.4));
646 
647         shaderManager.load("shaders/generic2D.cgfx");
648 
649         // Load mesh duck
650         sceneLoader.load({
651             scene: scene,
652             assetPath: "models/duck_trianglemesh.dae",
653             graphicsDevice: graphicsDevice,
654             textureManager: textureManager,
655             effectManager: effectManager,
656             shaderManager: shaderManager,
657             physicsManager: physicsManager,
658             requestHandler: requestHandler,
659             baseMatrix: mathDevice.m43BuildTranslation(0, 0.77, 0),
660             append: true,
661             postSceneLoadFn: postLoad,
662             dynamic: false
663         });
664     };
665 
666     var mappingTableReceived = function mappingTableReceivedFn(mappingTable) {
667         textureManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
668         shaderManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
669         sceneLoader.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
670 
671         loadAssets();
672     };
673 
674     var gameSessionCreated = function gameSessionCreatedFn(gameSession) {
675         mappingTable = TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived);
676     };
677 
678     var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated);
679 
680     // Create a scene destroy callback to run when the window is closed
681     function destroyScene() {
682         gameSession.destroy();
683 
684         TurbulenzEngine.clearInterval(intervalID);
685         clearColor = null;
686 
687         if (scene) {
688             scene.destroy();
689             scene = null;
690         }
691         requestHandler = null;
692 
693         if (renderer) {
694             renderer.destroy();
695             renderer = null;
696         }
697 
698         camera = null;
699 
700         if (textureManager) {
701             textureManager.destroy();
702             textureManager = null;
703         }
704 
705         if (shaderManager) {
706             shaderManager.destroy();
707             shaderManager = null;
708         }
709 
710         effectManager = null;
711 
712         TurbulenzEngine.flush();
713         graphicsDevice = null;
714         mathDevice = null;
715         physicsDevice = null;
716         physicsManager = null;
717         dynamicsWorld = null;
718         mouseCodes = null;
719         keyCodes = null;
720         inputDevice = null;
721         cameraController = null;
722         floor = null;
723     }
724 
725     TurbulenzEngine.onunload = destroyScene;
726 };