physics.js
  1 /*{# Copyright (c) 2010-2013 Turbulenz Limited #}*/
  2 /*
  3 * @title: 3D Physics
  4 * @description:
  5 * This sample shows how to create and use the Turbulenz physics device.
  6 * The sample creates a floor plane and multiple cube shaped rigid bodies attached to nodes in the rendering scene.
  7 * Click on the rendering window to move and rotate the camera around,
  8 * click again to pick up and move the rigid bodies using the mouse forces object.
  9 */
 10 /*{{ javascript("jslib/camera.js") }}*/
 11 /*{{ javascript("jslib/floor.js") }}*/
 12 /*{{ javascript("jslib/mouseforces.js") }}*/
 13 /*{{ javascript("jslib/observer.js") }}*/
 14 /*{{ javascript("jslib/utilities.js") }}*/
 15 /*{{ javascript("jslib/requesthandler.js") }}*/
 16 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/
 17 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/
 18 /*{{ javascript("jslib/services/gamesession.js") }}*/
 19 /*{{ javascript("jslib/services/mappingtable.js") }}*/
 20 /*{{ javascript("scripts/htmlcontrols.js") }}*/
 21 /*global TurbulenzEngine: true */
 22 /*global RequestHandler: false */
 23 /*global window: false */
 24 /*global Camera: false */
 25 /*global CameraController: false */
 26 /*global Floor: false */
 27 /*global RequestHandler: false */
 28 /*global HTMLControls: false */
 29 /*global MouseForces: false */
 30 /*global TurbulenzServices: false */
 31 TurbulenzEngine.onload = function onloadFn() {
 32     var errorCallback = function errorCallback(msg) {
 33         window.alert(msg);
 34     };
 35 
 36     var graphicsDeviceParameters = {};
 37     var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters);
 38 
 39     if (!graphicsDevice.shadingLanguageVersion) {
 40         errorCallback("No shading language support detected.\nPlease check your graphics drivers are up to date.");
 41         graphicsDevice = null;
 42         return;
 43     }
 44 
 45     var mathDeviceParameters = {};
 46     var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters);
 47 
 48     var inputDeviceParameters = {};
 49     var inputDevice = TurbulenzEngine.createInputDevice(inputDeviceParameters);
 50 
 51     var assetsToLoad = 3;
 52 
 53     // Setup camera & controller
 54     var camera = Camera.create(mathDevice);
 55     var cameraInitialize = function cameraInitializeFn() {
 56         var worldUp = mathDevice.v3BuildYAxis();
 57         camera.lookAt(worldUp, worldUp, mathDevice.v3Build(0.0, 3.0, -15.0));
 58         camera.updateViewMatrix();
 59     };
 60     cameraInitialize();
 61 
 62     var cameraController = CameraController.create(graphicsDevice, inputDevice, camera);
 63 
 64     var floor = Floor.create(graphicsDevice, mathDevice);
 65 
 66     var requestHandlerParameters = {};
 67     var requestHandler = RequestHandler.create(requestHandlerParameters);
 68 
 69     var shader3d = null;
 70     var technique3d = null;
 71 
 72     var shader3dLoaded = function shader3dLoadedFn(shaderText) {
 73         if (shaderText) {
 74             var shaderParameters = JSON.parse(shaderText);
 75             shader3d = graphicsDevice.createShader(shaderParameters);
 76             if (shader3d) {
 77                 technique3d = shader3d.getTechnique("textured3D");
 78                 assetsToLoad -= 1;
 79             }
 80         }
 81     };
 82 
 83     var sharedTechniqueParameters = graphicsDevice.createTechniqueParameters({
 84         diffuse: null
 85     });
 86 
 87     /*jshint white: false*/
 88     // Vertex buffer parameters for crate
 89     var vertexbufferParameters = {
 90         numVertices: 24,
 91         attributes: ['FLOAT3', 'SHORT2'],
 92         dynamic: false,
 93         data: [
 94             -0.5,
 95             -0.5,
 96             0.5,
 97             0,
 98             0,
 99             0.5,
100             -0.5,
101             0.5,
102             1,
103             0,
104             0.5,
105             0.5,
106             0.5,
107             1,
108             1,
109             -0.5,
110             0.5,
111             0.5,
112             0,
113             1,
114             -0.5,
115             0.5,
116             0.5,
117             0,
118             0,
119             0.5,
120             0.5,
121             0.5,
122             1,
123             0,
124             0.5,
125             0.5,
126             -0.5,
127             1,
128             1,
129             -0.5,
130             0.5,
131             -0.5,
132             0,
133             1,
134             -0.5,
135             0.5,
136             -0.5,
137             1,
138             1,
139             0.5,
140             0.5,
141             -0.5,
142             0,
143             1,
144             0.5,
145             -0.5,
146             -0.5,
147             0,
148             0,
149             -0.5,
150             -0.5,
151             -0.5,
152             1,
153             0,
154             -0.5,
155             -0.5,
156             -0.5,
157             0,
158             0,
159             0.5,
160             -0.5,
161             -0.5,
162             1,
163             0,
164             0.5,
165             -0.5,
166             0.5,
167             1,
168             1,
169             -0.5,
170             -0.5,
171             0.5,
172             0,
173             1,
174             0.5,
175             -0.5,
176             0.5,
177             0,
178             0,
179             0.5,
180             -0.5,
181             -0.5,
182             1,
183             0,
184             0.5,
185             0.5,
186             -0.5,
187             1,
188             1,
189             0.5,
190             0.5,
191             0.5,
192             0,
193             1,
194             -0.5,
195             -0.5,
196             -0.5,
197             0,
198             0,
199             -0.5,
200             -0.5,
201             0.5,
202             1,
203             0,
204             -0.5,
205             0.5,
206             0.5,
207             1,
208             1,
209             -0.5,
210             0.5,
211             -0.5,
212             0,
213             1
214         ]
215     };
216 
217     /*jshint white: true*/
218     var vertexbuffer = graphicsDevice.createVertexBuffer(vertexbufferParameters);
219 
220     var semantics = graphicsDevice.createSemantics([graphicsDevice.SEMANTIC_POSITION, graphicsDevice.SEMANTIC_TEXCOORD]);
221 
222     /*jshint white: false*/
223     var indexbufferParameters = {
224         numIndices: 36,
225         format: 'USHORT',
226         dynamic: false,
227         data: [
228             2,
229             0,
230             1,
231             3,
232             0,
233             2,
234             6,
235             4,
236             5,
237             7,
238             4,
239             6,
240             10,
241             8,
242             9,
243             11,
244             8,
245             10,
246             14,
247             12,
248             13,
249             15,
250             12,
251             14,
252             18,
253             16,
254             17,
255             19,
256             16,
257             18,
258             22,
259             20,
260             21,
261             23,
262             20,
263             22
264         ]
265     };
266 
267     /*jshint white: true*/
268     var indexbuffer = graphicsDevice.createIndexBuffer(indexbufferParameters);
269 
270     var primitive = graphicsDevice.PRIMITIVE_TRIANGLES;
271     var numIndices = 36;
272 
273     // Cache mathDevice functions
274     var m43MulM44 = mathDevice.m43MulM44;
275     var isVisibleBoxOrigin = mathDevice.isVisibleBoxOrigin;
276     var v4Build = mathDevice.v4Build;
277     var m43BuildTranslation = mathDevice.m43BuildTranslation;
278 
279     var shader2d = null;
280     var technique2d = null;
281 
282     var shader2dLoaded = function shader2dLoadedFn(shaderText) {
283         if (shaderText) {
284             var shaderParameters = JSON.parse(shaderText);
285             shader2d = graphicsDevice.createShader(shaderParameters);
286             if (shader2d) {
287                 technique2d = shader2d.getTechnique("constantColor2D");
288                 assetsToLoad -= 1;
289             }
290         }
291     };
292 
293     var techniqueParameters2d = graphicsDevice.createTechniqueParameters({
294         clipSpace: null,
295         constantColor: mathDevice.v4Build(0, 0, 0, 1)
296     });
297 
298     var linePrim = graphicsDevice.PRIMITIVE_LINES;
299     var cursorFormat = [graphicsDevice.VERTEXFORMAT_FLOAT3];
300     var cursorSemantic = graphicsDevice.createSemantics([graphicsDevice.SEMANTIC_POSITION]);
301 
302     var clearColor = mathDevice.v4Build(0.95, 0.95, 1.0, 1.0);
303 
304     //
305     // Physics
306     //
307     var physicsDeviceParameters = {};
308     var physicsDevice = TurbulenzEngine.createPhysicsDevice(physicsDeviceParameters);
309 
310     var dynamicsWorldParameters = {};
311     var dynamicsWorld = physicsDevice.createDynamicsWorld(dynamicsWorldParameters);
312 
313     // Specify the generic settings for the collision objects
314     var collisionMargin = 0.005;
315     var mass = 20.0;
316     var numCubes = 200;
317     var cubeExtents = mathDevice.v3Build(0.5, 0.5, 0.5);
318 
319     // Floor is represented by a plane
320     var floorShape = physicsDevice.createPlaneShape({
321         normal: mathDevice.v3Build(0, 1, 0),
322         distance: 0,
323         margin: collisionMargin
324     });
325 
326     var floorObject = physicsDevice.createCollisionObject({
327         shape: floorShape,
328         transform: mathDevice.m43BuildIdentity(),
329         friction: 0.5,
330         restitution: 0.3,
331         group: physicsDevice.FILTER_STATIC,
332         mask: physicsDevice.FILTER_ALL
333     });
334 
335     // Adds the floor collision object to the physicsDevice
336     dynamicsWorld.addCollisionObject(floorObject);
337 
338     var boxShape = physicsDevice.createBoxShape({
339         halfExtents: cubeExtents,
340         margin: collisionMargin
341     });
342 
343     var inertia = boxShape.inertia;
344     inertia = mathDevice.v3ScalarMul(inertia, mass);
345 
346     var boxBodies = [];
347 
348     // Initial box is created as a rigid body
349     boxBodies[0] = physicsDevice.createRigidBody({
350         shape: boxShape,
351         mass: mass,
352         inertia: inertia,
353         transform: mathDevice.m43BuildTranslation(0.0, 1.0, 0.0),
354         friction: 0.5,
355         restitution: 0.3,
356         angularDamping: 0.9,
357         frozen: false
358     });
359     dynamicsWorld.addRigidBody(boxBodies[0]);
360 
361     for (var n = 1; n < numCubes; n += 1) {
362         // Each additional box is cloned from the original
363         boxBodies[n] = boxBodies[0].clone();
364         boxBodies[n].transform = m43BuildTranslation.call(mathDevice, 0.0, 1.0 + 1.5 * n, 0.0);
365 
366         dynamicsWorld.addRigidBody(boxBodies[n]);
367     }
368 
369     var doReset = false;
370     var reset = function resetFn() {
371         var transform = mathDevice.m43BuildIdentity();
372         var v3Zero = mathDevice.v3BuildZero();
373 
374         for (n = 0; n < numCubes; n += 1) {
375             var body = boxBodies[n];
376             body.transform = mathDevice.m43BuildTranslation(0.0, 1.0 + 1.5 * n, 0.0, transform);
377             body.linearVelocity = v3Zero;
378             body.angularVelocity = v3Zero;
379             body.active = true;
380         }
381 
382         cameraInitialize();
383     };
384 
385     // Controls
386     var htmlControls = HTMLControls.create();
387 
388     htmlControls.addButtonControl({
389         id: "button01",
390         value: "Reset",
391         fn: function schedulResetFn() {
392             doReset = true;
393         }
394     });
395     htmlControls.register();
396 
397     var keyCodes = inputDevice.keyCodes;
398     var mouseCodes = inputDevice.mouseCodes;
399 
400     var onKeyUp = function physicsOnkeyupFn(keynum) {
401         if (keynum === keyCodes.R) {
402             reset();
403         }
404     };
405 
406     // Mouse forces
407     var dragMin = mathDevice.v3Build(-1000, 0, -1000);
408     var dragMax = mathDevice.v3Build(1000, 1000, 1000);
409     var mouseForces = MouseForces.create(graphicsDevice, inputDevice, mathDevice, physicsDevice, dragMin, dragMax);
410 
411     var onMouseDown = function (button) {
412         if (mouseCodes.BUTTON_0 === button || mouseCodes.BUTTON_1 === button) {
413             mouseForces.onmousedown();
414         }
415     };
416 
417     var onMouseUp = function (button) {
418         if (mouseCodes.BUTTON_0 === button || mouseCodes.BUTTON_1 === button) {
419             mouseForces.onmouseup();
420         }
421     };
422 
423     // Add event listeners
424     inputDevice.addEventListener("keyup", onKeyUp);
425     inputDevice.addEventListener("mousedown", onMouseDown);
426     inputDevice.addEventListener("mouseup", onMouseUp);
427 
428     //
429     // Update
430     //
431     var update = function updateFn() {
432         inputDevice.update();
433 
434         if (doReset) {
435             reset();
436             doReset = false;
437         }
438 
439         if (mouseForces.pickedBody) {
440             // If we're dragging a body don't apply the movement to the camera
441             cameraController.pitch = 0;
442             cameraController.turn = 0;
443             cameraController.step = 0;
444         }
445 
446         cameraController.update();
447 
448         var aspectRatio = (graphicsDevice.width / graphicsDevice.height);
449         if (aspectRatio !== camera.aspectRatio) {
450             camera.aspectRatio = aspectRatio;
451             camera.updateProjectionMatrix();
452         }
453         camera.updateViewProjectionMatrix();
454 
455         if (0 >= assetsToLoad) {
456             mouseForces.update(dynamicsWorld, camera, 0.1);
457 
458             dynamicsWorld.update();
459         }
460 
461         var vp = camera.viewProjectionMatrix;
462         var transform = mathDevice.m43BuildIdentity();
463         var wvp;
464 
465         if (graphicsDevice.beginFrame()) {
466             graphicsDevice.clear(clearColor, 1.0, 0);
467 
468             floor.render(graphicsDevice, camera);
469 
470             if (0 >= assetsToLoad) {
471                 graphicsDevice.setStream(vertexbuffer, semantics);
472 
473                 graphicsDevice.setIndexBuffer(indexbuffer);
474 
475                 graphicsDevice.setTechnique(technique3d);
476 
477                 graphicsDevice.setTechniqueParameters(sharedTechniqueParameters);
478 
479                 for (n = 0; n < numCubes; n += 1) {
480                     boxBodies[n].calculateTransform(transform);
481                     wvp = m43MulM44.call(mathDevice, transform, vp, wvp);
482                     if (isVisibleBoxOrigin.call(mathDevice, cubeExtents, wvp)) {
483                         // Use directly the active technique when just a single property changes
484                         technique3d.worldViewProjection = wvp;
485                         graphicsDevice.drawIndexed(primitive, numIndices);
486                     }
487                 }
488 
489                 if (!mouseForces.pickedBody) {
490                     graphicsDevice.setTechnique(technique2d);
491 
492                     var screenWidth = graphicsDevice.width;
493                     var screenHeight = graphicsDevice.height;
494                     techniqueParameters2d['clipSpace'] = v4Build.call(mathDevice, 2.0 / screenWidth, -2.0 / screenHeight, -1.0, 1.0);
495                     graphicsDevice.setTechniqueParameters(techniqueParameters2d);
496 
497                     var writer = graphicsDevice.beginDraw(linePrim, 4, cursorFormat, cursorSemantic);
498 
499                     if (writer) {
500                         var halfWidth = screenWidth * 0.5;
501                         var halfHeight = screenHeight * 0.5;
502                         writer([halfWidth - 10, halfHeight]);
503                         writer([halfWidth + 10, halfHeight]);
504                         writer([halfWidth, halfHeight - 10]);
505                         writer([halfWidth, halfHeight + 10]);
506 
507                         graphicsDevice.endDraw(writer);
508                     }
509                 }
510             }
511 
512             graphicsDevice.endFrame();
513         }
514     };
515     var intervalID = TurbulenzEngine.setInterval(update, 1000 / 60);
516 
517     var mappingTableReceived = function mappingTableReceivedFn(mappingTable) {
518         var textureParameters = {
519             src: mappingTable.getURL("textures/crate.jpg"),
520             mipmaps: true,
521             onload: function (texture) {
522                 sharedTechniqueParameters['diffuse'] = texture;
523                 assetsToLoad -= 1;
524             }
525         };
526 
527         graphicsDevice.createTexture(textureParameters);
528 
529         requestHandler.request({
530             src: mappingTable.getURL("shaders/generic3D.cgfx"),
531             onload: shader3dLoaded
532         });
533         requestHandler.request({
534             src: mappingTable.getURL("shaders/generic2D.cgfx"),
535             onload: shader2dLoaded
536         });
537     };
538 
539     var gameSessionCreated = function gameSessionCreatedFn(gameSession) {
540         TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived);
541     };
542     var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated);
543 
544     TurbulenzEngine.onunload = function destroyScene() {
545         TurbulenzEngine.clearInterval(intervalID);
546 
547         if (gameSession) {
548             gameSession.destroy();
549             gameSession = null;
550         }
551 
552         requestHandler = null;
553         indexbuffer = null;
554         vertexbuffer = null;
555         semantics = null;
556         cursorSemantic = null;
557         primitive = null;
558         linePrim = null;
559         technique3d = null;
560         shader3d = null;
561         technique2d = null;
562         shader2d = null;
563         techniqueParameters2d = null;
564         sharedTechniqueParameters = null;
565         mouseForces = null;
566         floor = null;
567         cameraController = null;
568         camera = null;
569         boxBodies = null;
570         inertia = null;
571         clearColor = null;
572         m43MulM44 = null;
573         m43BuildTranslation = null;
574         isVisibleBoxOrigin = null;
575         v4Build = null;
576         dragMax = null;
577         dragMin = null;
578         mouseCodes = null;
579         keyCodes = null;
580 
581         TurbulenzEngine.flush();
582         physicsDevice = null;
583         inputDevice = null;
584         graphicsDevice = null;
585         mathDevice = null;
586     };
587 };