1 /*{# Copyright (c) 2013 Turbulenz Limited #}*/ 2 /* 3 * @title: GPU ParticleSystem 4 * @description: 5 * This sample demonstrates the capabilities and usage of the GPU ParticleSystem. 6 */ 7 /*{{ javascript("jslib/observer.js") }}*/ 8 /*{{ javascript("jslib/requesthandler.js") }}*/ 9 /*{{ javascript("jslib/utilities.js") }}*/ 10 /*{{ javascript("jslib/floor.js") }}*/ 11 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/ 12 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/ 13 /*{{ javascript("jslib/services/gamesession.js") }}*/ 14 /*{{ javascript("jslib/services/mappingtable.js") }}*/ 15 /*{{ javascript("jslib/camera.js") }}*/ 16 /*{{ javascript("jslib/aabbtree.js") }}*/ 17 /*{{ javascript("jslib/texturemanager.js") }}*/ 18 /*{{ javascript("jslib/shadermanager.js") }}*/ 19 /*{{ javascript("jslib/effectmanager.js") }}*/ 20 /*{{ javascript("jslib/material.js") }}*/ 21 /*{{ javascript("jslib/scenenode.js") }}*/ 22 /*{{ javascript("jslib/scene.js") }}*/ 23 /*{{ javascript("jslib/scenedebugging.js") }}*/ 24 /*{{ javascript("jslib/renderingcommon.js") }}*/ 25 /*{{ javascript("jslib/forwardrendering.js") }}*/ 26 /*{{ javascript("jslib/particlesystem.js") }}*/ 27 /*{{ javascript("jslib/fontmanager.js") }}*/ 28 /*{{ javascript("jslib/canvas.js") }}*/ 29 /*{{ javascript("scripts/htmlcontrols.js") }}*/ 30 /*global CameraController: false */ 31 /*global Camera: false */ 32 /*global Canvas: false */ 33 /*global EffectManager: false */ 34 /*global Floor: false */ 35 /*global FontManager: false */ 36 /*global ForwardRendering: false */ 37 /*global HTMLControls: false */ 38 /*global ParticleBuilder: false */ 39 /*global ParticleSystem: false */ 40 /*global ParticleView: false */ 41 /*global RequestHandler: false*/ 42 /*global Scene: false */ 43 /*global SceneNode: false */ 44 /*global ShaderManager: false */ 45 /*global TextureManager: false */ 46 /*global TurbulenzEngine: true */ 47 /*global TurbulenzServices: false */ 48 /*global window: false */ 49 TurbulenzEngine.onload = function onloadFn() { 50 var errorCallback = function errorCallback(msg) { 51 window.alert(msg); 52 }; 53 54 //========================================================================== 55 // Turbulenz Initialization 56 //========================================================================== 57 var graphicsDevice = TurbulenzEngine.createGraphicsDevice({}); 58 if (graphicsDevice.maxSupported("VERTEX_TEXTURE_UNITS") === 0) { 59 errorCallback("Device does not support sampling of textures from vertex shaders " + "required by GPU particle system"); 60 return; 61 } 62 63 var mathDevice = TurbulenzEngine.createMathDevice({}); 64 var inputDevice = TurbulenzEngine.createInputDevice({}); 65 66 var requestHandler = RequestHandler.create({}); 67 var textureManager = TextureManager.create(graphicsDevice, requestHandler, null, errorCallback); 68 var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback); 69 var effectManager = EffectManager.create(); 70 var fontManager = FontManager.create(graphicsDevice, requestHandler, null, errorCallback); 71 72 // region of world where systems will be spawned. 73 var sceneWidth = 1000; 74 var sceneHeight = 1000; 75 76 // speed of generation. 77 var generationSpeed = 50; 78 var lastGen = 0; 79 80 // speed of simulation (log 1.3) 81 var simulationSpeed = 0; 82 83 var camera = Camera.create(mathDevice); 84 var halfFOV = Math.tan(30 * Math.PI / 180); 85 camera.recipViewWindowX = 1 / halfFOV; 86 camera.recipViewWindowY = 1 / halfFOV; 87 camera.lookAt(mathDevice.v3Build(0, 0, 0), mathDevice.v3BuildYAxis(), mathDevice.v3Build(sceneWidth / 2, 30, sceneHeight / 2)); 88 camera.updateProjectionMatrix(); 89 camera.updateViewMatrix(); 90 var cameraController = CameraController.create(graphicsDevice, inputDevice, camera); 91 var maxCameraSpeed = 200; 92 93 var renderer; 94 var clearColor = mathDevice.v4Build(0, 0, 0, 1); 95 var scene = Scene.create(mathDevice); 96 97 var floor = Floor.create(graphicsDevice, mathDevice); 98 floor.color = mathDevice.v4Build(0, 0, 0.6, 1); 99 floor.fadeToColor = clearColor; 100 101 var drawRenderableExtents = false; 102 function extraDrawCallback() { 103 floor.render(graphicsDevice, camera); 104 if (drawRenderableExtents) { 105 (scene).drawVisibleRenderablesExtents(graphicsDevice, shaderManager, camera, false, true); 106 } 107 } 108 109 // Create canvas object for minimap. 110 var canvas = Canvas.create(graphicsDevice); 111 var ctx = canvas.getContext('2d'); 112 113 // Scaling to use when drawing to minimap, targetting a size of (150,150) for minimap 114 var scaleX = 150 / sceneWidth; 115 var scaleY = 150 / sceneHeight; 116 ctx.lineWidth = 0.1; 117 118 var fontTechnique; 119 var fontTechniqueParameters; 120 121 var fpsElement = document.getElementById("fps"); 122 var fpsText = ""; 123 function displayFPS() { 124 if (!fpsElement) { 125 return; 126 } 127 128 var text = graphicsDevice.fps.toFixed(2) + " fps"; 129 if (text !== fpsText) { 130 fpsText = text; 131 fpsElement.innerHTML = fpsText; 132 } 133 } 134 135 //========================================================================== 136 // Particle Systems 137 //========================================================================== 138 var particleManager = ParticleManager.create(graphicsDevice, textureManager, shaderManager); 139 140 particleManager.registerParticleAnimation({ 141 name: "fire", 142 // Define a texture-size to normalize uv-coordinates with. 143 // This avoids needing to use fractional values, especially if texture 144 // may be changed in future. 145 // 146 // In this case the actual texture is 512x512, but we map the particle animation 147 // to the top-half, so can pretend it is really 512x256. 148 // 149 // To simplify the uv-coordinates further, we can 'pretend' it is really 4x2 as 150 // after normalization the resulting uv-coordinates would be equivalent. 151 "texture0-size": [4, 2], 152 texture0: [ 153 [0, 0, 1, 1], 154 [1, 0, 1, 1], 155 [2, 0, 1, 1], 156 [3, 0, 1, 1], 157 [0, 1, 1, 1], 158 [1, 1, 1, 1], 159 [2, 1, 1, 1], 160 [3, 1, 1, 1] 161 ], 162 animation: [ 163 { 164 frame: 0 165 }, 166 { 167 // after 0.6 seconds, ensure colour is still [1,1,1,1] 168 time: 0.6, 169 color: [1, 1, 1, 1] 170 }, 171 { 172 // after another 0.1 seconds 173 time: 0.1, 174 // want to be 'just past' the last frame. 175 // so all frames of animation have equal screen presence. 176 frame: 8, 177 color: [1, 1, 1, 0] 178 } 179 ] 180 }); 181 182 particleManager.registerParticleAnimation({ 183 name: "smoke", 184 // smoke is similarly mapped as "fire" particle above, but to bottom of packed texture. 185 "texture0-size": [4, 2], 186 texture0: [ 187 [0, 0, 1, 1], 188 [1, 0, 1, 1], 189 [2, 0, 1, 1], 190 [3, 0, 1, 1], 191 [0, 1, 1, 1], 192 [1, 1, 1, 1], 193 [2, 1, 1, 1], 194 [3, 1, 1, 1] 195 ], 196 animation: [ 197 { 198 // these are values applied by default to the first snapshot in animation 199 // we could omit them here if we wished. 200 frame: 0, 201 "frame-interpolation": "linear", 202 color: [1, 1, 1, 1], 203 "color-interpolation": "linear" 204 }, 205 { 206 // after 0.8 seconds 207 time: 0.8, 208 color: [1, 0.5, 0.5, 1] 209 }, 210 { 211 // after another 0.5 seconds, we fade out. 212 time: 0.5, 213 // want to be 'just past' the last frame. 214 // so all frames of animation have equal screen presence. 215 frame: 8, 216 color: [0, 0, 0, 0] 217 } 218 ] 219 }); 220 221 particleManager.registerParticleAnimation({ 222 name: "portal", 223 animation: [ 224 { 225 "scale-interpolation": "catmull", 226 color: [0, 1, 0, 1] 227 }, 228 { 229 // after 0.3 seconds 230 time: 0.3, 231 scale: [2, 2], 232 color: [1, 1, 1, 1] 233 }, 234 { 235 // after another 0.7 seconds 236 time: 0.7, 237 scale: [0.5, 0.5], 238 color: [1, 0, 0, 0] 239 } 240 ] 241 }); 242 243 var description1 = { 244 system: { 245 // define local system extents, particles will be clamped against these extents when reached. 246 // 247 // We make extents a little larger than necessary so that in movement of system 248 // particles will not push up against the edges of extents so easily. 249 center: [0, 6, 0], 250 halfExtents: [7, 6, 7] 251 }, 252 updater: { 253 // set noise texture to use for randomization, and allow acceleration (when enabled) 254 // to be randomized to up to the given amounts. 255 noiseTexture: "textures/noise.dds", 256 randomizedAcceleration: [10, 10, 10] 257 }, 258 renderer: { 259 // use default renderer with additive blend mode 260 name: "additive", 261 // set noise texture to use for randomizations. 262 noiseTexture: "textures/noise.dds", 263 // for particles that enable these options, we're going to allow particle alphas 264 // if enabled on particles, allow particle orientation to be randomized up to these 265 // spherical amounts (+/-), in this case, to rotate around y-axis by +/- 0.3*Math.PI 266 // specify this variation should change over time 267 randomizedOrientation: [0, 0.3 * Math.PI], 268 animatedOrientation: true, 269 // if enabled on particles, allow particle scale to be randomized up to these 270 // amounts (+/-), and define that this variation should not change over time. 271 randomizedScale: [3, 3], 272 animatedScale: false 273 }, 274 // All particles make use of this single texture. 275 packedTexture: "textures/flamesmokesequence.png", 276 particles: { 277 fire: { 278 animation: "fire", 279 // select sub-set of packed texture this particles animation should be mapped to. 280 "texture-uv": [0, 0, 1, 0.5], 281 // apply animation tweaks to increase size of animation (x5) 282 tweaks: { 283 "scale-scale": [5, 5] 284 } 285 }, 286 ember: { 287 animation: "fire", 288 "texture-uv": [0, 0.0, 1, 0.5], 289 // apply animation tweaks so that only the second half of flip-book is used. 290 // and double the size. 291 tweaks: { 292 "scale-scale": [2, 2], 293 // The animation we're using has 8 frames, we want to use the second 294 // half of the flip-book animation, so we scale by 0.5 and offset by 4. 295 "frame-scale": 0.5, 296 "frame-offset": 4 297 } 298 }, 299 smoke: { 300 animation: "smoke", 301 // select sub-set of packed texture this particles animation should be mapped to. 302 "texture-uv": [0, 0.5, 1, 0.5], 303 // apply animation tweaks to increase size of animation (x3) 304 tweaks: { 305 "scale-scale": [3, 3] 306 } 307 } 308 }, 309 emitters: [ 310 { 311 particle: { 312 name: "fire", 313 // let life time of particle vary between 0.6 and 1.2 of animation life time. 314 lifeTimeScaleMin: 0.6, 315 lifeTimeScaleMax: 1.2, 316 // set userData so that its orientation will be randomized, and will have a 317 // also define scale should be randomized. 318 renderUserData: { 319 facing: "billboard", 320 randomizeOrientation: true, 321 randomizeScale: true 322 } 323 }, 324 emittance: { 325 // emit particles 10 times per second. With 0 - 2 particles emitted each time. 326 rate: 10, 327 burstMin: 0, 328 burstMax: 2 329 }, 330 position: { 331 // position 2 units above system position 332 position: [0, 2, 0], 333 // and with a randomized radius in disc of up to 1 unit 334 // with a normal (gaussian) distribution to focus on centre. 335 radiusMax: 1, 336 radiusDistribution: "normal" 337 }, 338 velocity: { 339 // spherical angles defining direction to emit particles in. 340 // the default 0, 0 means to emit particles straight up the y-axis. 341 theta: 0, 342 phi: 0 343 } 344 }, 345 { 346 particle: { 347 name: "ember", 348 // override animation life times. 349 lifeTimeMin: 0.2, 350 lifeTimeMax: 0.6, 351 // set userData so that acceleration will be randomized and also orientation. 352 updateUserData: { 353 randomizeAcceleration: true 354 }, 355 renderUserData: { 356 randomizeOrientation: true 357 } 358 }, 359 emittance: { 360 // emit particles 3 times per second. With 0 - 15 particles emitted each time. 361 rate: 3, 362 burstMin: 0, 363 burstMax: 15, 364 // only start emitting after 0.25 seconds 365 delay: 0.25 366 }, 367 velocity: { 368 // set velocity to a random direction in conical spread 369 conicalSpread: Math.PI * 0.25, 370 // and with speeds between these values. 371 speedMin: 1, 372 speedMax: 3 373 }, 374 position: { 375 // position 3 units above system position 376 position: [0, 3, 0], 377 // and in a random radius of this position in a sphere. 378 spherical: true, 379 radiusMin: 1, 380 radiusMax: 2.5 381 } 382 }, 383 { 384 particle: { 385 name: "smoke", 386 // set userData so that acceleration will be randomized. 387 updateUserData: { 388 randomizeAcceleration: true 389 } 390 }, 391 emittance: { 392 // emit particles 20 times per second, with 0 - 3 every time. 393 rate: 20, 394 burstMin: 0, 395 burstMax: 3 396 }, 397 velocity: { 398 // set velocity to a random direction in conical spread 399 conicalSpread: Math.PI * 0.25, 400 // and with speeds between these values. 401 speedMin: 2, 402 speedMax: 6 403 }, 404 position: { 405 // position 2.5 units above system position 406 position: [0, 2.5, 0], 407 // and in a random radius of this position in a sphere. 408 spherical: true, 409 radiusMin: 0.5, 410 radiusMax: 2.0 411 } 412 } 413 ] 414 }; 415 416 var description2 = { 417 system: { 418 // define local system extents 419 // as with first system these are defined to be a bit larger to account for 420 // movements of the system. 421 center: [0, 6, 0], 422 halfExtents: [12, 6, 12] 423 }, 424 renderer: { 425 // we're going to use the default renderer with the "additive" blend mode. 426 name: "additive", 427 // set noise texture to use for randomizations 428 noiseTexture: "textures/noise.dds", 429 // for particles that enable these options, we're going to allow particle alphas 430 // to vary +/- 0.5, and this alpha variation will change over time. 431 randomizedAlpha: 1.0, 432 animatedAlpha: true, 433 // for particles that enable these options, we're going to allow particle orientations 434 // to vary by the given spherical angles (+/-), and this variation will change over time. 435 randomizedOrientation: [Math.PI * 0.25, Math.PI * 0.25], 436 animatedOrientation: true, 437 // for particles that enable these options, we're going to allow particle rotations 438 // to vary by the given angle (+/-), and this variation will change over time. 439 randomizedRotation: Math.PI * 2, 440 animatedRotation: true 441 }, 442 updater: { 443 // In the absense of acceleration, set drag so that particles will come to a stop after 444 // 1 second of simulation. 445 drag: 1, 446 // for particles that enable these options, we're going to allow acceleration applied to 447 // particles to vary according to the noise texture, up to a defined maximum in each 448 // coordinate (+/-) 449 noiseTexture: "textures/noise.dds", 450 randomizedAcceleration: [10, 0, 10] 451 }, 452 particles: { 453 // Define two particles to be used in this system. 454 // As these define their own textures, textures will be packed at runtime by the particleManager. 455 spark: { 456 animation: "portal", 457 // define animation tweaks to be applied for this particle. 458 tweaks: { 459 // this defines that we're going to half the animated scale of the particle. 460 // In effect, we're making this particle half the size the animation said it should be. 461 "scale-scale": [0.5, 0.5] 462 }, 463 texture: "textures/particle_spark.png" 464 }, 465 smoke: { 466 animation: "portal", 467 tweaks: { 468 // The effect of these parameters will be to invert the RGB colours of the particle as 469 // defined by the animation, and particle texture. 470 "color-scale": [-1, -1, -1, 1], 471 "color-offset": [1, 1, 1, 0] 472 }, 473 texture: "textures/smoke.dds" 474 } 475 }, 476 emitters: [ 477 { 478 emittance: { 479 // After 1 second from the start of the effect, we're going to emit particles 80 times per second. 480 delay: 1, 481 rate: 80, 482 // Whenever we emit particles, we will emit exactly 4 particles. 483 burstMin: 4, 484 burstMax: 4 485 }, 486 particle: { 487 name: "spark", 488 // Here we access functions of the updater and renderer that will be used, to set the userData 489 // that will be applied to each particle emitted. 490 // We define that we want particles emitted by this emitter to have their acceleration randomize 491 // and also their alpha, orientation and rotation. We specify particle quad should be aligned 492 // with the particles velocity vector. 493 updateUserData: { 494 randomizeAcceleration: true 495 }, 496 renderUserData: { 497 facing: "velocity", 498 randomizeAlpha: true, 499 randomizeOrientation: true, 500 randomizeRotation: true 501 } 502 }, 503 velocity: { 504 // Particles will be emitted with local speeds between these values. 505 speedMin: 3, 506 speedMax: 20, 507 // And with a conical spread of the given angle about the default direction (y-axis). 508 conicalSpread: Math.PI / 10 509 }, 510 position: { 511 // Particles will be generated at radii between these values. 512 radiusMin: 4, 513 radiusMax: 5, 514 // And the distribution of the radius selected will be according to a normal (Gaussian) distribution 515 // with the given sigma parameter. 516 radiusDistribution: "normal", 517 radiusSigma: 0.125 518 } 519 }, 520 { 521 emittance: { 522 // We will emit particles 20 times per second. 523 rate: 20, 524 // And whenever we emit particles, we'll emit between 0 and 6 particles. 525 burstMin: 0, 526 burstMax: 6 527 }, 528 particle: { 529 name: "smoke", 530 // Particles of this emitter will have their quads billboarded to face camera. 531 renderUserData: { 532 facing: "billboard" 533 }, 534 // Particles will live for between these amounts of time in seconds. 535 useAnimationLifeTime: false, 536 lifeTimeMin: 0.5, 537 lifeTimeMax: 1.5 538 }, 539 velocity: { 540 speedMin: 5, 541 speedMax: 15 542 }, 543 position: { 544 spherical: false, 545 radiusMin: 0, 546 radiusMax: 2 547 } 548 } 549 ] 550 }; 551 552 // Produce ParticleArchetype objects based on these descriptions. 553 // These calls will verify the input descriptions for correctness, and fill in all missing parameters 554 // with the default values defined by the individual components of a particle system. 555 var archetype1 = particleManager.parseArchetype(description1); 556 var archetype2 = particleManager.parseArchetype(description2); 557 558 //========================================================================== 559 // Main loop 560 //========================================================================= 561 var previousFrameTime; 562 function init() { 563 fontTechnique = shaderManager.get("shaders/font.cgfx").getTechnique('font'); 564 fontTechniqueParameters = graphicsDevice.createTechniqueParameters({ 565 clipSpace: mathDevice.v4BuildZero(), 566 alphaRef: 0.01, 567 color: mathDevice.v4BuildOne() 568 }); 569 570 renderer = ForwardRendering.create(graphicsDevice, mathDevice, shaderManager, effectManager, {}); 571 572 // particleManager is initialized with the Scene to be worked with. 573 // and the transparent pass index of the renderer, so that particle systems 574 // created will be sorted with other transparent renderable elements of the Scene. 575 particleManager.initialize(scene, renderer.passIndex.transparent); 576 577 previousFrameTime = TurbulenzEngine.time; 578 } 579 580 // All systems are added as children of this node so we can shuffle them around 581 // in space, demonstrating trails. 582 var particleNode = SceneNode.create({ 583 name: "particleNode", 584 dynamic: true 585 }); 586 scene.addRootNode(particleNode); 587 588 var moveSystems = false; 589 var movementTime = 0; 590 591 // movement radius of particleNode. 592 var radius = 50; 593 594 function mainLoop() { 595 var currentTime = TurbulenzEngine.time; 596 var deltaTime = (currentTime - previousFrameTime); 597 previousFrameTime = currentTime; 598 displayFPS(); 599 600 inputDevice.update(); 601 602 cameraController.maxSpeed = (deltaTime * maxCameraSpeed); 603 cameraController.update(); 604 605 // Update the aspect ratio of the camera in case of window resizes 606 var aspectRatio = (graphicsDevice.width / graphicsDevice.height); 607 if (aspectRatio !== camera.aspectRatio) { 608 camera.aspectRatio = aspectRatio; 609 camera.updateProjectionMatrix(); 610 } 611 camera.updateViewProjectionMatrix(); 612 613 // alter deltaTime for simulation speed after camera maxSpeed was set to avoid 614 // slowing down the camera movement. 615 deltaTime *= Math.pow(1.3, simulationSpeed); 616 617 // Update ParticleManager object with elapsed time. 618 // This will add the deltaTime to the managers internal clock used by systems when synchronizing 619 // and will also remove any expired ParticleInstance objects created in the particleManager. 620 particleManager.update(deltaTime); 621 622 // Create new ParticleInstances in particleManager. 623 lastGen += deltaTime; 624 var limit = 0; 625 while (lastGen > 1 / generationSpeed && limit < 100) { 626 limit += 1; 627 lastGen -= 1 / generationSpeed; 628 629 var instance, x, z, s, timeout; 630 timeout = 2 + 2 * Math.random(); 631 instance = particleManager.createInstance(archetype1, timeout); 632 x = Math.random() * (sceneWidth - radius * 2) + radius; 633 z = Math.random() * (sceneHeight - radius * 2) + radius; 634 s = 1 + Math.random() * 2; 635 636 // this local transform will be applied to the entire system 637 // allowing us to re-use the same particle archetype around the 638 // scene at different positions and scales. 639 instance.renderable.setLocalTransform(mathDevice.m43Build(s, 0, 0, 0, s, 0, 0, 0, s, x, 0, z)); 640 particleManager.addInstanceToScene(instance, particleNode); 641 642 timeout = 2 + 2 * Math.random(); 643 instance = particleManager.createInstance(archetype2, timeout); 644 x = Math.random() * (sceneWidth - radius * 2) + radius; 645 z = Math.random() * (sceneHeight - radius * 2) + radius; 646 s = 1 + Math.random() * 2; 647 instance.renderable.setLocalTransform(mathDevice.m43Build(s, 0, 0, 0, s, 0, 0, 0, s, x, 0, z)); 648 particleManager.addInstanceToScene(instance, particleNode); 649 } 650 lastGen %= (1 / generationSpeed); 651 652 if (moveSystems) { 653 movementTime += deltaTime; 654 var time = movementTime / 5; 655 var rad = radius * Math.sin(time); 656 var transform = mathDevice.m43BuildTranslation(Math.sin(time) * rad, 0, Math.cos(time) * rad); 657 particleNode.setLocalTransform(transform); 658 } 659 660 // Update scene 661 scene.update(); 662 663 if (!graphicsDevice.beginFrame()) { 664 return; 665 } 666 667 // Update renderer, this will as a side-effect of particle instances becoming visible to the camera 668 // cause particle systems if required to be lazily created along with any views onto a particle system 669 // the low-level particle system will deal with this itself the way it is used by the particleManager. 670 renderer.update(graphicsDevice, camera, scene, currentTime); 671 672 // Render scene including all particle systems. 673 renderer.draw(graphicsDevice, clearColor, extraDrawCallback); 674 675 // Gather metrics about object usages in the particleManager, and display on the screen. 676 graphicsDevice.setTechnique(fontTechnique); 677 mathDevice.v4Build(2 / graphicsDevice.width, -2 / graphicsDevice.height, -1, 1, fontTechniqueParameters.clipSpace); 678 graphicsDevice.setTechniqueParameters(fontTechniqueParameters); 679 680 var metrics = particleManager.gatherMetrics(); 681 var text = "ParticleManager Metrics:\n"; 682 for (var f in metrics) { 683 if (metrics.hasOwnProperty(f)) { 684 text += f + ": " + metrics[f] + "\n"; 685 } 686 } 687 688 var font = fontManager.get("fonts/hero.fnt"); 689 var fontScale = 0.5; 690 var dimensions = font.calculateTextDimensions(text, fontScale, 0); 691 font.drawTextRect(text, { 692 rect: mathDevice.v4Build(0, 0, dimensions.width, dimensions.height), 693 scale: fontScale, 694 alignment: 0 695 }); 696 697 if (canvas.width !== graphicsDevice.width) { 698 canvas.width = graphicsDevice.width; 699 } 700 if (canvas.height !== graphicsDevice.height) { 701 canvas.height = graphicsDevice.height; 702 } 703 704 var width = sceneWidth * scaleX; 705 var height = sceneHeight * scaleY; 706 var viewport = mathDevice.v4Build(canvas.width - width - 2, canvas.height - 2, width, height); 707 viewport = null; 708 ctx.beginFrame(null, viewport); 709 ctx.setTransform(1, 0, 0, 1, 0, 0); 710 ctx.translate(canvas.width - sceneWidth * scaleX - 2, 2); 711 712 ctx.strokeStyle = "#ffffff"; 713 ctx.strokeRect(0, 0, sceneWidth * scaleX, sceneHeight * scaleY); 714 715 var instanceMetrics = particleManager.gatherInstanceMetrics(); 716 var count = instanceMetrics.length; 717 var i; 718 for (i = 0; i < count; i += 1) { 719 var metric = instanceMetrics[i]; 720 var extents = metric.instance.renderable.getWorldExtents(); 721 x = (extents[0] + extents[3]) / 2 * scaleX; 722 z = (extents[2] + extents[5]) / 2 * scaleY; 723 ctx.strokeStyle = metric.active ? "#00ff00" : metric.allocated ? "#ffff00" : "#ff0000"; 724 ctx.strokeRect(x - 0.5, z - 0.5, 1, 1); 725 } 726 727 // Display camera (xz) position on minimap also. 728 var pos = mathDevice.m43Pos(mathDevice.m43Inverse(camera.viewMatrix)); 729 if (pos[0] >= 0 && pos[2] >= 0 && pos[0] <= sceneWidth && pos[2] <= sceneHeight) { 730 ctx.strokeStyle = "#ffffff"; 731 ctx.strokeRect(pos[0] * scaleX - 1.5, pos[2] * scaleY - 1.5, 3, 3); 732 } 733 734 ctx.endFrame(); 735 graphicsDevice.endFrame(); 736 } 737 738 //========================================================================== 739 // Asset and Mapping table loading 740 //========================================================================= 741 var intervalID; 742 function loadingLoop() { 743 if (graphicsDevice.beginFrame()) { 744 graphicsDevice.clear(clearColor); 745 graphicsDevice.endFrame(); 746 } 747 748 if (textureManager.getNumPendingTextures() === 0 && shaderManager.getNumPendingShaders() === 0) { 749 TurbulenzEngine.clearInterval(intervalID); 750 init(); 751 intervalID = TurbulenzEngine.setInterval(mainLoop, 1000 / 60); 752 } 753 } 754 function loadAssets() { 755 // Load assets required to render renderable extents. 756 shaderManager.load("shaders/debug.cgfx"); 757 758 // Load assets required to render the fonts on screen. 759 shaderManager.load("shaders/font.cgfx"); 760 fontManager.load('fonts/hero.fnt'); 761 762 // Load all assets required to create and work with the particle system archetypes we're using. 763 particleManager.loadArchetype(archetype1); 764 particleManager.loadArchetype(archetype2); 765 766 intervalID = TurbulenzEngine.setInterval(loadingLoop, 10); 767 } 768 function mappingTableReceived(table) { 769 textureManager.setPathRemapping(table.urlMapping, table.assetPrefix); 770 shaderManager.setPathRemapping(table.urlMapping, table.assetPrefix); 771 fontManager.setPathRemapping(table.urlMapping, table.assetPrefix); 772 773 loadAssets(); 774 } 775 function sessionCreated(gameSession) { 776 TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived); 777 } 778 var gameSession = TurbulenzServices.createGameSession(requestHandler, sessionCreated); 779 780 //========================================================================== 781 // Sample tear-down 782 //========================================================================= 783 TurbulenzEngine.onunload = function unloadFn() { 784 TurbulenzEngine.clearInterval(intervalID); 785 786 if (gameSession) { 787 gameSession.destroy(); 788 gameSession = null; 789 } 790 if (shaderManager) { 791 shaderManager.destroy(); 792 shaderManager = null; 793 } 794 if (textureManager) { 795 textureManager.destroy(); 796 textureManager = null; 797 } 798 if (fontManager) { 799 fontManager.destroy(); 800 fontManager = null; 801 } 802 if (renderer) { 803 renderer.destroy(); 804 renderer = null; 805 } 806 if (particleManager) { 807 particleManager.destroy(); 808 particleManager = null; 809 } 810 811 effectManager = null; 812 requestHandler = null; 813 cameraController = null; 814 camera = null; 815 floor = null; 816 817 TurbulenzEngine.flush(); 818 819 inputDevice = null; 820 graphicsDevice = null; 821 mathDevice = null; 822 }; 823 824 //========================================================================= 825 // HTML Controls 826 //========================================================================= 827 var htmlControls = HTMLControls.create(); 828 829 htmlControls.addSliderControl({ 830 id: "speedSlider", 831 value: (simulationSpeed), 832 max: 6, 833 min: -10, 834 step: 1, 835 fn: function () { 836 simulationSpeed = this.value; 837 htmlControls.updateSlider("speedSlider", simulationSpeed); 838 } 839 }); 840 841 htmlControls.addSliderControl({ 842 id: "instanceSlider", 843 value: (generationSpeed), 844 max: 200, 845 min: 20, 846 step: 20, 847 fn: function () { 848 generationSpeed = this.value; 849 htmlControls.updateSlider("instanceSlider", generationSpeed); 850 } 851 }); 852 853 var scaleOffset = 0; 854 function refreshArchetype(description, archetype, scale) { 855 var emitters = description.emitters; 856 var count = emitters.length; 857 var i; 858 for (i = 0; i < count; i += 1) { 859 var emitter = emitters[i]; 860 emitter.emittance.burstMin *= scale; 861 emitter.emittance.burstMax *= scale; 862 } 863 864 // build new archetype from modified description. 865 // replacing all instances of old with new. 866 // and destroying the old. 867 var newArchetype = particleManager.parseArchetype(description); 868 particleManager.replaceArchetype(archetype, newArchetype); 869 particleManager.destroyArchetype(archetype); 870 return newArchetype; 871 } 872 htmlControls.addButtonControl({ 873 id: "button-decrease-particles", 874 value: "-", 875 fn: function () { 876 if (scaleOffset > -5) { 877 scaleOffset -= 1; 878 archetype1 = refreshArchetype(description1, archetype1, 1 / 1.5); 879 archetype2 = refreshArchetype(description2, archetype2, 1 / 1.5); 880 } 881 } 882 }); 883 htmlControls.addButtonControl({ 884 id: "button-increase-particles", 885 value: "+", 886 fn: function () { 887 if (scaleOffset < 4) { 888 scaleOffset += 1; 889 archetype1 = refreshArchetype(description1, archetype1, 1.5); 890 archetype2 = refreshArchetype(description2, archetype2, 1.5); 891 } 892 } 893 }); 894 895 htmlControls.addCheckboxControl({ 896 id: "move-systems", 897 value: "moveSystems", 898 isSelected: moveSystems, 899 fn: function () { 900 moveSystems = !moveSystems; 901 return moveSystems; 902 } 903 }); 904 905 htmlControls.addCheckboxControl({ 906 id: "draw-extents", 907 value: "drawRenderableExtents", 908 isSelected: drawRenderableExtents, 909 fn: function () { 910 drawRenderableExtents = !drawRenderableExtents; 911 return drawRenderableExtents; 912 } 913 }); 914 915 htmlControls.addButtonControl({ 916 id: "button-clear", 917 value: "Clear", 918 fn: function () { 919 // remove all instances of both archetypes, retaining other state like object 920 // pools and allocated memory on gpu. 921 particleManager.clear(archetype1); 922 particleManager.clear(archetype2); 923 } 924 }); 925 926 htmlControls.addButtonControl({ 927 id: "button-destroy-1", 928 value: "Destroy 1", 929 fn: function () { 930 // destroy all state and instances associated with archetype1 (complete reset) 931 particleManager.destroyArchetype(archetype1); 932 } 933 }); 934 htmlControls.addButtonControl({ 935 id: "button-destroy-2", 936 value: "Destroy 2", 937 fn: function () { 938 // destroy all state and instances associated with archetype2 (complete reset) 939 particleManager.destroyArchetype(archetype2); 940 } 941 }); 942 943 htmlControls.addButtonControl({ 944 id: "button-replace-1-2", 945 value: "Replace 1 to 2", 946 fn: function () { 947 // replace all instances of archetype1 with ones of archetype2 in-place. 948 particleManager.replaceArchetype(archetype1, archetype2); 949 } 950 }); 951 htmlControls.addButtonControl({ 952 id: "button-replace-2-1", 953 value: "Replace 2 to 1", 954 fn: function () { 955 // replace all instances of archetype2 with ones of archetype1 in-place. 956 particleManager.replaceArchetype(archetype2, archetype1); 957 } 958 }); 959 960 htmlControls.register(); 961 };