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 };