forward_rendering.js
  1 /*{# Copyright (c) 2010-2012 Turbulenz Limited #}*/
  2 /*
  3 * @title: Forward rendering
  4 * @description:
  5 * This sample shows a scene with dynamic shadows cast by point or spot lights rendered using the Turbulenz forward renderer.
  6 * You can enable multiple colored lights to see the impact on performance.
  7 * Click on the rendering window to move and rotate the camera around.
  8 */
  9 /*{{ javascript("jslib/camera.js") }}*/
 10 /*{{ javascript("jslib/aabbtree.js") }}*/
 11 /*{{ javascript("jslib/effectmanager.js") }}*/
 12 /*{{ javascript("jslib/shadermanager.js") }}*/
 13 /*{{ javascript("jslib/texturemanager.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/renderingcommon.js") }}*/
 20 /*{{ javascript("jslib/forwardrendering.js") }}*/
 21 /*{{ javascript("jslib/shadowmapping.js") }}*/
 22 /*{{ javascript("jslib/posteffects.js") }}*/
 23 /*{{ javascript("jslib/resourceloader.js") }}*/
 24 /*{{ javascript("jslib/observer.js") }}*/
 25 /*{{ javascript("jslib/utilities.js") }}*/
 26 /*{{ javascript("jslib/requesthandler.js") }}*/
 27 /*{{ javascript("jslib/vertexbuffermanager.js") }}*/
 28 /*{{ javascript("jslib/indexbuffermanager.js") }}*/
 29 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/
 30 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/
 31 /*{{ javascript("jslib/services/gamesession.js") }}*/
 32 /*{{ javascript("jslib/services/mappingtable.js") }}*/
 33 /*{{ javascript("scripts/sceneloader.js") }}*/
 34 /*{{ javascript("scripts/htmlcontrols.js") }}*/
 35 /*global TurbulenzEngine: true */
 36 /*global PostEffects: false */
 37 /*global ForwardRendering: false */
 38 /*global TurbulenzServices: false */
 39 /*global Camera: false */
 40 /*global CameraController: false */
 41 /*global TextureManager: false */
 42 /*global ShaderManager: false */
 43 /*global EffectManager: false */
 44 /*global Scene: false */
 45 /*global SceneNode: false */
 46 /*global SceneLoader: false */
 47 /*global LightInstance: false */
 48 /*global Light: false */
 49 /*global HTMLControls: false */
 50 /*global RequestHandler: false*/
 51 /*global window: false */
 52 TurbulenzEngine.onload = function onloadFn() {
 53     var errorCallback = function errorCallback(msg) {
 54         window.alert(msg);
 55     };
 56 
 57     // Create the engine devices objects
 58     var graphicsDeviceParameters = {};
 59     var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters);
 60 
 61     if (!graphicsDevice.shadingLanguageVersion) {
 62         errorCallback("No shading language support detected.\nPlease check your graphics drivers are up to date.");
 63         graphicsDevice = null;
 64         return;
 65     }
 66 
 67     // Clear the background color of the engine window
 68     var clearColor = [0.5, 0.5, 0.5, 1.0];
 69     if (graphicsDevice.beginFrame()) {
 70         graphicsDevice.clear(clearColor);
 71         graphicsDevice.endFrame();
 72     }
 73 
 74     var mathDeviceParameters = {};
 75     var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters);
 76 
 77     var inputDeviceParameters = {};
 78     var inputDevice = TurbulenzEngine.createInputDevice(inputDeviceParameters);
 79 
 80     // Create a camera with a 60 degree FOV
 81     var camera = Camera.create(mathDevice);
 82     var halfFov = Math.tan(30 * Math.PI / 180);
 83     camera.recipViewWindowX = 1.0 / halfFov;
 84     camera.recipViewWindowY = 1.0 / halfFov;
 85     camera.updateProjectionMatrix();
 86     var worldUp = mathDevice.v3BuildYAxis();
 87     camera.lookAt(worldUp, worldUp, mathDevice.v3Build(0.0, 50.0, 200.0));
 88     camera.updateViewMatrix();
 89 
 90     var cameraController = CameraController.create(graphicsDevice, inputDevice, camera);
 91 
 92     var requestHandlerParameters = {};
 93     var requestHandler = RequestHandler.create(requestHandlerParameters);
 94 
 95     var textureManager = TextureManager.create(graphicsDevice, requestHandler, null, errorCallback);
 96     var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback);
 97     var effectManager = EffectManager.create();
 98 
 99     var scene = Scene.create(mathDevice);
100     var sceneLoader = SceneLoader.create();
101 
102     var maxSpeed = cameraController.maxSpeed;
103 
104     var postEffects;
105     var renderer;
106 
107     // Create some rendering callbacks to pass to the forward rendering renderer
108     function drawExtraDecalsFn() {
109     }
110 
111     function drawExtraTransparentFn() {
112     }
113 
114     function drawDebugFn() {
115     }
116 
117     var coloredLights = false;
118     var pointLights = true;
119     var spotLights = false;
120     var directionalLights = false;
121     var enablePostEffects = false;
122     var pointLightCenter = [-3.5, 15, -0.5];
123     var spotLightCenter = [-3.5, 25, -0.5];
124     var yAxis = mathDevice.v3BuildYAxis();
125 
126     var setDisabled = function setDisabledFn(colored, spot, directional) {
127         var disabled = !colored || spot || directional;
128         scene.findNode("redLight-node").setDisabled(disabled);
129         scene.findNode("greenLight-node").setDisabled(disabled);
130         scene.findNode("blueLight-node").setDisabled(disabled);
131         scene.findNode("whiteLight-node").setDisabled(colored || spot || directional);
132         disabled = !colored || !spot;
133         scene.findNode("redSpotLight-node").setDisabled(disabled);
134         scene.findNode("greenSpotLight-node").setDisabled(disabled);
135         scene.findNode("blueSpotLight-node").setDisabled(disabled);
136         scene.findNode("whiteSpotLight-node").setDisabled(colored || !spot);
137         disabled = !colored || !directional;
138         scene.findNode("redDirectionalLight-node").setDisabled(disabled);
139         scene.findNode("greenDirectionalLight-node").setDisabled(disabled);
140         scene.findNode("blueDirectionalLight-node").setDisabled(disabled);
141         scene.findNode("whiteDirectionalLight-node").setDisabled(colored || !directional);
142     };
143 
144     // Controls
145     var htmlControls = HTMLControls.create();
146 
147     htmlControls.addRadioControl({
148         id: "radio-point",
149         groupName: "lightType",
150         radioIndex: 0,
151         value: "pointLights",
152         fn: function () {
153             pointLights = true;
154             spotLights = false;
155             directionalLights = false;
156             if (sceneLoader.complete()) {
157                 setDisabled(coloredLights, spotLights, directionalLights);
158             }
159         },
160         isDefault: true
161     });
162     htmlControls.addRadioControl({
163         id: "radio-spot",
164         groupName: "lightType",
165         radioIndex: 1,
166         value: "spotLights",
167         fn: function () {
168             pointLights = false;
169             spotLights = true;
170             directionalLights = false;
171             if (sceneLoader.complete()) {
172                 setDisabled(coloredLights, spotLights, directionalLights);
173             }
174         },
175         isDefault: false
176     });
177     htmlControls.addRadioControl({
178         id: "radio-directional",
179         groupName: "lightType",
180         radioIndex: 2,
181         value: "directionalLights",
182         fn: function () {
183             pointLights = false;
184             spotLights = false;
185             directionalLights = true;
186             if (sceneLoader.complete()) {
187                 setDisabled(coloredLights, spotLights, directionalLights);
188             }
189         },
190         isDefault: false
191     });
192 
193     htmlControls.addCheckboxControl({
194         id: "checkbox-colored",
195         value: "coloredLights",
196         isSelected: coloredLights,
197         fn: function () {
198             if (sceneLoader.complete()) {
199                 coloredLights = !coloredLights;
200                 setDisabled(coloredLights, spotLights, directionalLights);
201             }
202             return coloredLights;
203         }
204     });
205 
206     htmlControls.addCheckboxControl({
207         id: "checkbox-post",
208         value: "enablePostEffects",
209         isSelected: enablePostEffects,
210         fn: function () {
211             enablePostEffects = !enablePostEffects;
212             return enablePostEffects;
213         }
214     });
215 
216     htmlControls.register();
217 
218     var v3Build = mathDevice.v3Build;
219     var m43BuildTranslation = mathDevice.m43BuildTranslation;
220 
221     // Create a callback for post scene load to add some lights and update the camera position
222     var loadSceneFinished = function loadSceneFinishedFn(scene) {
223         var addLight = function addLightFn(light, enabled, lightCenter) {
224             scene.addLight(light);
225 
226             var lightMatrix = m43BuildTranslation.call(mathDevice, v3Build.apply(mathDevice, lightCenter));
227 
228             var lightNode = SceneNode.create({
229                 name: light.name + "-node",
230                 local: lightMatrix,
231                 dynamic: true,
232                 disabled: !enabled
233             });
234 
235             lightNode.addLightInstance(LightInstance.create(light));
236 
237             scene.addRootNode(lightNode);
238         };
239 
240         var createPointLight = function createPointLightFn(lightName, lightMaterial, color) {
241             return Light.create({
242                 name: lightName,
243                 color: color,
244                 point: true,
245                 shadows: true,
246                 halfExtents: mathDevice.v3Build(40, 40, 40),
247                 origin: mathDevice.v3Build(0, 10, 0),
248                 material: lightMaterial
249             });
250         };
251 
252         var createSpotLight = function createSpotLightFn(lightName, lightMaterial, color) {
253             return Light.create({
254                 name: lightName,
255                 color: mathDevice.v3Build(color[0], color[1], color[2]),
256                 spot: true,
257                 shadows: true,
258                 material: lightMaterial,
259                 target: mathDevice.v3Build(0, -40, 0),
260                 right: mathDevice.v3Build(0, 0, 40),
261                 up: mathDevice.v3Build(40, 0, 0)
262             });
263         };
264 
265         var createDirectionalLight = function createDirectionalLightFn(lightName, lightMaterial, color, direction) {
266             return Light.create({
267                 name: lightName,
268                 color: mathDevice.v3Build(color[0], color[1], color[2]),
269                 directional: true,
270                 shadows: true,
271                 material: lightMaterial,
272                 halfExtents: mathDevice.v3Build(60, 60, 60),
273                 direction: mathDevice.v3Build(direction[0], direction[1], direction[2])
274             });
275         };
276 
277         var lightMaterialData = {
278             effect: "lambert",
279             parameters: {
280                 lightfalloff: "textures/default_light.png",
281                 lightprojection: "textures/default_light.png"
282             }
283         };
284         scene.loadMaterial(graphicsDevice, textureManager, effectManager, "defaultLightMaterial", lightMaterialData);
285         var lightMaterial = scene.getMaterial("defaultLightMaterial");
286 
287         // Add some lights into the scene
288         addLight(createPointLight("whiteLight", lightMaterial, [1, 1, 1]), true, pointLightCenter);
289         addLight(createPointLight("redLight", lightMaterial, [0.9, 0.1, 0.1]), false, pointLightCenter);
290         addLight(createPointLight("greenLight", lightMaterial, [0.1, 0.9, 0.1]), false, pointLightCenter);
291         addLight(createPointLight("blueLight", lightMaterial, [0.1, 0.1, 0.9]), false, pointLightCenter);
292 
293         addLight(createSpotLight("whiteSpotLight", lightMaterial, [1, 1, 1]), false, spotLightCenter);
294         addLight(createSpotLight("redSpotLight", lightMaterial, [0.9, 0.1, 0.1]), false, spotLightCenter);
295         addLight(createSpotLight("greenSpotLight", lightMaterial, [0.1, 0.9, 0.1]), false, spotLightCenter);
296         addLight(createSpotLight("blueSpotLight", lightMaterial, [0.1, 0.1, 0.9]), false, spotLightCenter);
297 
298         addLight(createDirectionalLight("whiteDirectionalLight", lightMaterial, [1, 1, 1], [-0.707107, -0.707107, 0]), false, pointLightCenter);
299         addLight(createDirectionalLight("redDirectionalLight", lightMaterial, [0.9, 0.1, 0.1], [0.707107, -0.707107, 0]), false, pointLightCenter);
300         addLight(createDirectionalLight("greenDirectionalLight", lightMaterial, [0.1, 0.9, 0.1], [0, -0.707107, 0.707107]), false, pointLightCenter);
301         addLight(createDirectionalLight("blueDirectionalLight", lightMaterial, [0.1, 0.1, 0.9], [0, -0.707107, -0.707107]), false, pointLightCenter);
302 
303         scene.addLight(Light.create({
304             name: "ambient",
305             ambient: true,
306             color: [0.1, 0.1, 0.1]
307         }));
308 
309         var sceneExtents = scene.getExtents();
310         var sceneMinExtent = mathDevice.v3Build(sceneExtents[0], sceneExtents[1], sceneExtents[2]);
311         var sceneMaxExtent = mathDevice.v3Build(sceneExtents[3], sceneExtents[4], sceneExtents[5]);
312         var c = mathDevice.v3ScalarMul(mathDevice.v3Add(sceneMaxExtent, sceneMinExtent), 0.5);
313         var e = mathDevice.v3Sub(c, sceneMinExtent);
314 
315         camera.lookAt(mathDevice.v3Build(-3.5, 5, -0.5), mathDevice.v3Build(0.0, 1.0, 0.0), mathDevice.v3Build(-51, 16, 8));
316         camera.updateViewMatrix();
317 
318         var len = mathDevice.v3Length(e);
319         if (len < 4.0) {
320             camera.nearPlane = len * 0.25;
321         } else {
322             camera.nearPlane = 1.0;
323         }
324         camera.farPlane = Math.ceil(len) * 100.0;
325         camera.updateProjectionMatrix();
326 
327         maxSpeed = len;
328     };
329 
330     var previousFrameTime = TurbulenzEngine.time;
331     var doUpdate = true;
332     var intervalID;
333     var fpsElement = document.getElementById("fpscounter");
334     var lastFPS = '';
335 
336     var renderFrame = function renderFrameFn() {
337         var currentTime = TurbulenzEngine.time;
338         var deltaTime = (currentTime - previousFrameTime);
339         if (deltaTime > 0.1) {
340             deltaTime = 0.1;
341         }
342         cameraController.maxSpeed = (deltaTime * maxSpeed);
343 
344         // Update the input device
345         inputDevice.update();
346 
347         cameraController.update();
348 
349         // Update the aspect ratio of the camera in case of window resizes
350         var aspectRatio = (graphicsDevice.width / graphicsDevice.height);
351         if (aspectRatio !== camera.aspectRatio) {
352             camera.aspectRatio = aspectRatio;
353             camera.updateProjectionMatrix();
354         }
355         camera.updateViewProjectionMatrix();
356 
357         if (doUpdate) {
358             // Orbit the lights above the scene
359             var updateLight = function updateLightFn(lightName, orbitCycle, orbitOffset, orbitRadius, forward, lightCenter) {
360                 var orbit = (currentTime - orbitOffset) % orbitCycle;
361                 if (!forward) {
362                     orbit *= -1;
363                 }
364                 var omega = (orbit / orbitCycle) * 2 * Math.PI;
365                 var x = orbitRadius * Math.cos(omega);
366                 var y = orbitRadius * Math.sin(omega);
367                 var lightNode = scene.findNode(lightName + "-node");
368                 var matrix = lightNode.getLocalTransform();
369                 matrix[9] = (lightCenter[0] + x);
370                 matrix[11] = (lightCenter[2] + y);
371                 lightNode.setLocalTransform(matrix);
372             };
373 
374             var updateDirectionalLight = function updateDirectionalLightFn(lightName, orbitCycle, orbitOffset, forward, axis) {
375                 var orbit = (currentTime - orbitOffset) % orbitCycle;
376                 if (!forward) {
377                     orbit *= -1;
378                 }
379                 var omega = (orbit / orbitCycle) * 2 * Math.PI;
380                 var lightNode = scene.findNode(lightName + "-node");
381                 var matrix = lightNode.getLocalTransform();
382                 mathDevice.m43FromAxisRotation(axis, omega, matrix);
383                 lightNode.setLocalTransform(matrix);
384             };
385 
386             updateLight("whiteLight", 10, 0, 10, true, pointLightCenter);
387             updateLight("redLight", 5, 2, 10, true, pointLightCenter);
388             updateLight("greenLight", 7, 4.5, 10, true, pointLightCenter);
389             updateLight("blueLight", 8, 6, 10, false, pointLightCenter);
390 
391             updateLight("whiteSpotLight", 10, 0, 10, true, spotLightCenter);
392             updateLight("redSpotLight", 5, 2, 10, true, spotLightCenter);
393             updateLight("greenSpotLight", 7, 4.5, 10, true, spotLightCenter);
394             updateLight("blueSpotLight", 8, 6, 10, false, spotLightCenter);
395 
396             updateDirectionalLight("whiteDirectionalLight", 10, 0, true, yAxis);
397             updateDirectionalLight("redDirectionalLight", 5, 2, true, yAxis);
398             updateDirectionalLight("greenDirectionalLight", 7, 4.5, true, yAxis);
399             updateDirectionalLight("blueDirectionalLight", 8, 6, false, yAxis);
400 
401             scene.update();
402 
403             renderer.update(graphicsDevice, camera, scene, currentTime);
404         }
405 
406         if (graphicsDevice.beginFrame()) {
407             var clearColor = [0.0, 0.0, 0.0, 1.0];
408 
409             // Render the scene with the forward renderer without a posteffect
410             renderer.draw(graphicsDevice, clearColor, drawExtraDecalsFn, drawExtraTransparentFn, drawDebugFn, (postEffects && enablePostEffects ? postEffects.getEffectSetupCB("bicolor") : null));
411 
412             graphicsDevice.endFrame();
413         }
414 
415         previousFrameTime = currentTime;
416 
417         if (fpsElement) {
418             var fps = (graphicsDevice.fps).toFixed(2);
419             if (lastFPS !== fps) {
420                 lastFPS = fps;
421 
422                 // Execute any code that interacts with the DOM in a separate callback
423                 TurbulenzEngine.setTimeout(function () {
424                     fpsElement.innerHTML = fps + ' fps';
425                 }, 1);
426             }
427         }
428     };
429 
430     // Create a loop to run on an interval whilst loading
431     var loadingLoop = function loadingLoopFn() {
432         if (graphicsDevice.beginFrame()) {
433             graphicsDevice.clear(clearColor);
434             graphicsDevice.endFrame();
435         }
436 
437         if (sceneLoader.complete()) {
438             // Loading has completed, update the shader and effects systems
439             TurbulenzEngine.clearInterval(intervalID);
440 
441             renderer.updateShader(shaderManager);
442 
443             if (postEffects) {
444                 postEffects.updateShader(shaderManager);
445             }
446 
447             // Register the rendering callback as the new interval
448             intervalID = TurbulenzEngine.setInterval(renderFrame, 1000 / 60);
449         }
450     };
451 
452     intervalID = TurbulenzEngine.setInterval(loadingLoop, 1000 / 10);
453 
454     var loadAssets = function loadAssets() {
455         renderer = ForwardRendering.create(graphicsDevice, mathDevice, shaderManager, effectManager, { shadowRendering: true });
456 
457         if (renderer.updateBuffers(graphicsDevice, 1024, 1024)) {
458             postEffects = PostEffects.create(graphicsDevice, shaderManager);
459         } else {
460             var buttonPostEffects = document.getElementById("checkbox-post");
461             if (buttonPostEffects) {
462                 buttonPostEffects.disabled = true;
463             }
464         }
465 
466         sceneLoader.load({
467             scene: scene,
468             append: true,
469             assetPath: "models/diningroom.dae",
470             keepLights: true,
471             graphicsDevice: graphicsDevice,
472             mathDevice: mathDevice,
473             textureManager: textureManager,
474             shaderManager: shaderManager,
475             effectManager: effectManager,
476             requestHandler: requestHandler,
477             postSceneLoadFn: loadSceneFinished
478         });
479     };
480 
481     var mappingTableReceived = function mappingTableReceivedFn(mappingTable) {
482         textureManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
483         shaderManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
484         sceneLoader.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
485 
486         loadAssets();
487     };
488 
489     var gameSessionCreated = function gameSessionCreatedFn(gameSession) {
490         TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived);
491     };
492     var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated);
493 
494     // Create a scene destroy callback to run when the window is closed
495     TurbulenzEngine.onunload = function destroyScene() {
496         TurbulenzEngine.clearInterval(intervalID);
497 
498         if (gameSession) {
499             gameSession.destroy();
500             gameSession = null;
501         }
502 
503         requestHandler = null;
504         sceneLoader = null;
505 
506         if (scene) {
507             scene.destroy();
508             scene = null;
509         }
510 
511         effectManager = null;
512 
513         if (shaderManager) {
514             shaderManager.destroy();
515             shaderManager = null;
516         }
517 
518         if (textureManager) {
519             textureManager.destroy();
520             textureManager = null;
521         }
522 
523         cameraController = null;
524         camera = null;
525 
526         if (postEffects) {
527             postEffects.destroy();
528             postEffects = null;
529         }
530 
531         if (renderer) {
532             renderer.destroy();
533             renderer = null;
534         }
535 
536         TurbulenzEngine.flush();
537 
538         inputDevice = null;
539         graphicsDevice = null;
540         mathDevice = null;
541 
542         fpsElement = null;
543     };
544 };