particles.js
  1 /*{# Copyright (c) 2010-2012 Turbulenz Limited #}*/
  2 /*
  3 * @title: Particles
  4 * @description:
  5 * This sample demonstrates how to animate a simple particle system from a moving emitter using the CPU.
  6 * You can select different kinds of particles to render and you can also enable the rendering of the
  7 * dynamically calculated bounding box of the particle system.
  8 */
  9 /*{{ javascript("jslib/camera.js") }}*/
 10 /*{{ javascript("jslib/aabbtree.js") }}*/
 11 /*{{ javascript("jslib/shadermanager.js") }}*/
 12 /*{{ javascript("jslib/texturemanager.js") }}*/
 13 /*{{ javascript("jslib/effectmanager.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/scenedebugging.js") }}*/
 20 /*{{ javascript("jslib/renderingcommon.js") }}*/
 21 /*{{ javascript("jslib/defaultrendering.js") }}*/
 22 /*{{ javascript("jslib/resourceloader.js") }}*/
 23 /*{{ javascript("jslib/observer.js") }}*/
 24 /*{{ javascript("jslib/utilities.js") }}*/
 25 /*{{ javascript("jslib/requesthandler.js") }}*/
 26 /*{{ javascript("jslib/vertexbuffermanager.js") }}*/
 27 /*{{ javascript("jslib/indexbuffermanager.js") }}*/
 28 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/
 29 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/
 30 /*{{ javascript("jslib/services/gamesession.js") }}*/
 31 /*{{ javascript("jslib/services/mappingtable.js") }}*/
 32 /*{{ javascript("scripts/sceneloader.js") }}*/
 33 /*{{ javascript("scripts/htmlcontrols.js") }}*/
 34 /*{{ javascript("scripts/emitter.js") }}*/
 35 /*{{ javascript("scripts/motion.js") }}*/
 36 /*global RequestHandler: false */
 37 /*global TurbulenzEngine: true */
 38 /*global DefaultRendering: false */
 39 /*global TurbulenzServices: false */
 40 /*global Camera: false */
 41 /*global TextureManager: false */
 42 /*global ShaderManager: false */
 43 /*global EffectManager: false */
 44 /*global Scene: false */
 45 /*global SceneLoader: false */
 46 /*global Motion: false */
 47 /*global Effect: false */
 48 /*global Emitter: false */
 49 /*global HTMLControls: false */
 50 /*global window: false */
 51 TurbulenzEngine.onload = function onloadFn() {
 52     var errorCallback = function errorCallback(msg) {
 53         window.alert(msg);
 54     };
 55 
 56     var graphicsDeviceParameters = {};
 57     var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters);
 58 
 59     if (!graphicsDevice.shadingLanguageVersion) {
 60         errorCallback("No shading language support detected.\nPlease check your graphics drivers are up to date.");
 61         graphicsDevice = null;
 62         return;
 63     }
 64 
 65     // Clear the background color of the engine window
 66     var clearColor = [0.5, 0.5, 0.5, 1.0];
 67     if (graphicsDevice.beginFrame()) {
 68         graphicsDevice.clear(clearColor);
 69         graphicsDevice.endFrame();
 70     }
 71 
 72     var mathDeviceParameters = {};
 73     var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters);
 74 
 75     var requestHandlerParameters = {};
 76     var requestHandler = RequestHandler.create(requestHandlerParameters);
 77 
 78     var textureManager = TextureManager.create(graphicsDevice, requestHandler, null, errorCallback);
 79     var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback);
 80     var effectManager = EffectManager.create();
 81 
 82     var random = Math.random;
 83     var renderer;
 84 
 85     // Setup camera & controller
 86     var camera = Camera.create(mathDevice);
 87     var worldUp = mathDevice.v3BuildYAxis();
 88     camera.lookAt(mathDevice.v3Build(0.0, 2.0, 0.0), worldUp, mathDevice.v3Build(7.0, 3.0, 0.0));
 89     camera.updateViewMatrix();
 90     camera.nearPlane = 0.05;
 91 
 92     // The node on which the particle system is attached
 93     var particleNode = null;
 94 
 95     // The positions for the node to move between
 96     var positions = [
 97         [0, 0, -3],
 98         [0, 0, 3]
 99     ];
100     var lastPositionIndex = 0;
101 
102     // Emitter object, created when loading is complete
103     var emitter = null;
104 
105     // Emitter movement
106     var emitterMotion = Motion.create(mathDevice, "EmitterMotion");
107     var movementRate = 3.0;
108     emitterMotion.setRailMovement(positions[lastPositionIndex], movementRate);
109 
110     // The material list
111     var materials = {
112         particle1: {
113             effect: "add_particle_world",
114             meta: {
115                 transparent: true
116             },
117             parameters: {
118                 diffuse: "textures/default_light.png"
119             }
120         },
121         particle2: {
122             effect: "add_particle_world",
123             meta: {
124                 transparent: true
125             },
126             parameters: {
127                 diffuse: "textures/particle_star.png"
128             }
129         },
130         particle3: {
131             effect: "add_particle_world",
132             meta: {
133                 transparent: true
134             },
135             parameters: {
136                 diffuse: "textures/particle_spark.png"
137             }
138         }
139     };
140 
141     // The color list
142     var colors = {
143         particle1: [mathDevice.v4Build(1.0, 1.0, 1.0, 1.0)],
144         particle2: [mathDevice.v4Build(1.0, 1.0, 1.0, 1.0)],
145         particle3: [
146             mathDevice.v4Build(0.898, 0.807, 0.474, 1.0),
147             mathDevice.v4Build(0.878, 0.874, 0.345, 1.0),
148             mathDevice.v4Build(0.933, 0.811, 0.223, 1.0),
149             mathDevice.v4Build(0.772, 0.494, 0.294, 1.0),
150             mathDevice.v4Build(0.913, 0.909, 0.866, 1.0)
151         ]
152     };
153 
154     // Generate random colors
155     var randomColorCount = 14;
156     var colorList = colors.particle1;
157     for (var i = 0; i < randomColorCount; i += 1) {
158         colorList[colorList.length] = mathDevice.v4Build(random(), random(), random(), 1.0);
159     }
160 
161     var scene = Scene.create(mathDevice);
162     var sceneLoader = SceneLoader.create();
163 
164     var drawBoundingBox = false;
165 
166     function drawDebugFn() {
167         if (drawBoundingBox) {
168             scene.drawTransparentNodesExtents(graphicsDevice, shaderManager, camera);
169         }
170     }
171 
172     // Controls
173     var htmlControls = HTMLControls.create();
174 
175     htmlControls.addRadioControl({
176         id: "radio01",
177         groupName: "particleType",
178         radioIndex: 0,
179         value: "coloured",
180         fn: function () {
181             if (scene && emitter) {
182                 emitter.setParticleColors(colors.particle1);
183                 var material = scene.getMaterial("particle1");
184                 if (material) {
185                     // Set the material to use for the particle
186                     emitter.setMaterial(material);
187                 }
188             }
189         },
190         isDefault: true
191     });
192 
193     htmlControls.addRadioControl({
194         id: "radio02",
195         groupName: "particleType",
196         radioIndex: 1,
197         value: "constant",
198         fn: function () {
199             if (scene && emitter) {
200                 emitter.setParticleColors(colors.particle2);
201                 var material = scene.getMaterial("particle2");
202                 if (material) {
203                     // Set the material to use for the particle
204                     emitter.setMaterial(material);
205                 }
206             }
207         },
208         isDefault: false
209     });
210 
211     htmlControls.addRadioControl({
212         id: "radio03",
213         groupName: "particleType",
214         radioIndex: 2,
215         value: "sparks",
216         fn: function () {
217             if (scene && emitter) {
218                 emitter.setParticleColors(colors.particle3);
219                 var material = scene.getMaterial("particle3");
220                 if (material) {
221                     // Set the material to use for the particle
222                     emitter.setMaterial(material);
223                 }
224             }
225         },
226         isDefault: false
227     });
228 
229     htmlControls.addCheckboxControl({
230         id: "checkbox01",
231         value: "drawBoundingBox",
232         isSelected: drawBoundingBox,
233         fn: function () {
234             drawBoundingBox = !drawBoundingBox;
235             return drawBoundingBox;
236         }
237     });
238 
239     htmlControls.register();
240 
241     // Initialize the previous frame time
242     var previousFrameTime;
243     var intervalID;
244 
245     var lastFPS = '';
246     var numActiveParticles = 0;
247 
248     var numActiveParticlesElement = document.getElementById("numActiveParticles");
249     var fpsElement = document.getElementById("fpscounter");
250     var lastNumActiveParticles;
251 
252     //
253     // Update
254     //
255     var update = function updateFn() {
256         var currentTime = TurbulenzEngine.time;
257         var deltaTime = (currentTime - previousFrameTime);
258         if (deltaTime > 0.1) {
259             deltaTime = 0.1;
260         }
261 
262         var gdWidth = graphicsDevice.width;
263         var gdHeight = graphicsDevice.height;
264 
265         var aspectRatio = (gdWidth / gdHeight);
266         if (aspectRatio !== camera.aspectRatio) {
267             camera.aspectRatio = aspectRatio;
268             camera.updateProjectionMatrix();
269         }
270         camera.updateViewProjectionMatrix();
271 
272         emitterMotion.update(deltaTime);
273 
274         if (emitterMotion.atTarget) {
275             lastPositionIndex += 1;
276             lastPositionIndex %= positions.length;
277             emitterMotion.setRailMovement(positions[lastPositionIndex], movementRate);
278         }
279 
280         if (particleNode) {
281             particleNode.setLocalTransform(emitterMotion.matrix);
282         }
283 
284         if (fpsElement) {
285             var fps = (graphicsDevice.fps).toFixed(2);
286             if (lastFPS !== fps) {
287                 lastFPS = fps;
288 
289                 // Execute any code that interacts with the DOM in a separate callback
290                 TurbulenzEngine.setTimeout(function () {
291                     fpsElement.innerHTML = fps + ' fps';
292                 }, 1);
293             }
294         }
295 
296         scene.update();
297 
298         if (emitter) {
299             emitter.update(currentTime, deltaTime, camera);
300 
301             numActiveParticles = emitter.getNumActiveParticles();
302             if (numActiveParticlesElement && (numActiveParticles !== lastNumActiveParticles)) {
303                 numActiveParticlesElement.innerHTML = numActiveParticles.toString();
304                 lastNumActiveParticles = numActiveParticles;
305             }
306         }
307 
308         renderer.update(graphicsDevice, camera, scene, currentTime);
309 
310         if (graphicsDevice.beginFrame()) {
311             if (renderer.updateBuffers(graphicsDevice, gdWidth, gdHeight)) {
312                 renderer.draw(graphicsDevice, clearColor, null, null, drawDebugFn);
313             }
314             graphicsDevice.endFrame();
315         }
316 
317         previousFrameTime = currentTime;
318     };
319 
320     // Callback for when the scene is loaded to prepare particles
321     function loadSceneFinished(scene) {
322         particleNode = scene.findNode("cube");
323         if (particleNode) {
324             particleNode.setDynamic(true);
325 
326             for (var m in materials) {
327                 if (materials.hasOwnProperty(m)) {
328                     if (scene.loadMaterial(graphicsDevice, textureManager, effectManager, m, materials[m])) {
329                         materials[m].loaded = true;
330                     } else {
331                         errorCallback("Failed to load material: " + m);
332                     }
333                 }
334             }
335         }
336     }
337 
338     // Create a loop to run on an interval whilst loading
339     var loadingLoop = function loadingLoopFn() {
340         if (sceneLoader.complete()) {
341             // Register the update() loop as the new interval and make it call the function at 60Hz
342             TurbulenzEngine.clearInterval(intervalID);
343 
344             var material = scene.getMaterial("particle1");
345             if (material) {
346                 var particleSystemParameters = {
347                     minSpawnTime: 0.005,
348                     maxSpawnTime: 0.01,
349                     minLifetime: 1,
350                     maxLifetime: 2,
351                     gravity: 9.81,
352                     size: 0.1,
353                     growRate: -0.02
354                 };
355                 emitter = Emitter.create(graphicsDevice, mathDevice, material, particleNode, particleSystemParameters);
356                 emitter.setParticleColors(colors.particle1);
357             }
358 
359             for (var m in materials) {
360                 if (materials.hasOwnProperty(m)) {
361                     material = scene.getMaterial(m);
362                     if (material) {
363                         // Add additional references to materials, to avoid them being removed when not in use
364                         material.reference.add();
365                     }
366                 }
367             }
368 
369             // Loading has completed, update the shader system
370             renderer.updateShader(shaderManager);
371 
372             sceneLoader = null;
373 
374             previousFrameTime = TurbulenzEngine.time;
375 
376             intervalID = TurbulenzEngine.setInterval(update, 1000 / 60);
377         }
378     };
379     intervalID = TurbulenzEngine.setInterval(loadingLoop, 1000 / 10);
380 
381     function updateEffectFn(camera) {
382         // Custom update effect to project particles in world space
383         var techniqueParameters = this.techniqueParameters;
384         techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
385 
386         // As we update the geometry we need to propagate the changes to the drawParameters.
387         this.drawParameters[0].firstIndex = this.surface.first;
388         this.drawParameters[0].count = this.surface.numIndices;
389     }
390 
391     function loadTechniques(shaderManager) {
392         var that = this;
393 
394         var callback = function shaderLoadedCallbackFn(shader) {
395             that.shader = shader;
396             that.technique = shader.getTechnique(that.techniqueName);
397             that.techniqueIndex = that.technique.id;
398         };
399         shaderManager.load(this.shaderName, callback);
400     }
401 
402     var loadAssets = function loadAssetsFn() {
403         // Renderer for the scene (requires shader assets).
404         renderer = DefaultRendering.create(graphicsDevice, mathDevice, shaderManager, effectManager);
405         renderer.setGlobalLightPosition(mathDevice.v3Build(100, 100, 100));
406         renderer.setAmbientColor(mathDevice.v3Build(0.3, 0.3, 0.4));
407         renderer.setDefaultTexture(textureManager.get("default"));
408 
409         //
410         // add_particle_world
411         //
412         var effect = Effect.create("add_particle_world");
413         effectManager.add(effect);
414 
415         var effectTypeData = {
416             prepare: DefaultRendering.defaultPrepareFn,
417             shaderName: "shaders/defaultrendering.cgfx",
418             techniqueName: "add_particle",
419             update: updateEffectFn,
420             loadTechniques: loadTechniques
421         };
422         effectTypeData.loadTechniques(shaderManager);
423         effect.add("rigid", effectTypeData);
424 
425         //Start the loading
426         sceneLoader.load({
427             scene: scene,
428             append: false,
429             assetPath: "models/cube.dae",
430             keepLights: false,
431             graphicsDevice: graphicsDevice,
432             mathDevice: mathDevice,
433             textureManager: textureManager,
434             shaderManager: shaderManager,
435             effectManager: effectManager,
436             requestHandler: requestHandler,
437             postSceneLoadFn: loadSceneFinished
438         });
439     };
440 
441     var mappingTableReceived = function mappingTableReceivedFn(mappingTable) {
442         textureManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
443         shaderManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
444         sceneLoader.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
445 
446         loadAssets();
447     };
448 
449     var gameSessionCreated = function gameSessionCreatedFn(gameSession) {
450         TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived);
451     };
452     var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated);
453 
454     TurbulenzEngine.onunload = function destroyScene() {
455         TurbulenzEngine.clearInterval(intervalID);
456 
457         if (gameSession) {
458             gameSession.destroy();
459             gameSession = null;
460         }
461 
462         var material = null;
463         for (var m in materials) {
464             if (materials.hasOwnProperty(m)) {
465                 material = scene.getMaterial(m);
466                 if (material) {
467                     // Remove additional references to materials, we no longer need them
468                     material.reference.remove();
469                 }
470             }
471         }
472 
473         if (emitter) {
474             emitter.destroy();
475             emitter = null;
476         }
477 
478         if (scene) {
479             scene.destroy();
480             scene = null;
481         }
482 
483         requestHandler = null;
484 
485         if (renderer) {
486             renderer.destroy();
487             renderer = null;
488         }
489 
490         htmlControls = null;
491         numActiveParticlesElement = null;
492         fpsElement = null;
493 
494         particleNode = null;
495 
496         camera = null;
497 
498         colors = null;
499         materials = null;
500         clearColor = null;
501 
502         if (textureManager) {
503             textureManager.destroy();
504             textureManager = null;
505         }
506 
507         if (shaderManager) {
508             shaderManager.destroy();
509             shaderManager = null;
510         }
511 
512         effectManager = null;
513 
514         TurbulenzEngine.flush();
515         graphicsDevice = null;
516         mathDevice = null;
517     };
518 };