multiple_animations.js
  1 /*{# Copyright (c) 2010-2013 Turbulenz Limited #}*/
  2 /*
  3  * @title: Multiple animations
  4  * @description:
  5  * This sample demonstrates how the Turbulenz engine can render a high number of animated skinned characters each one with their own separate animation.
  6  * You can use the slider to increase or decrease the number of character to animate and render at the same time.
  7 */
  8 var __extends = this.__extends || function (d, b) {
  9     for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
 10     function __() { this.constructor = d; }
 11     __.prototype = b.prototype;
 12     d.prototype = new __();
 13 };
 14 /*{{ javascript("jslib/aabbtree.js") }}*/
 15 /*{{ javascript("jslib/camera.js") }}*/
 16 /*{{ javascript("jslib/geometry.js") }}*/
 17 /*{{ javascript("jslib/material.js") }}*/
 18 /*{{ javascript("jslib/light.js") }}*/
 19 /*{{ javascript("jslib/scenenode.js") }}*/
 20 /*{{ javascript("jslib/scene.js") }}*/
 21 /*{{ javascript("jslib/vmath.js") }}*/
 22 /*{{ javascript("jslib/effectmanager.js") }}*/
 23 /*{{ javascript("jslib/shadermanager.js") }}*/
 24 /*{{ javascript("jslib/texturemanager.js") }}*/
 25 /*{{ javascript("jslib/renderingcommon.js") }}*/
 26 /*{{ javascript("jslib/defaultrendering.js") }}*/
 27 /*{{ javascript("jslib/resourceloader.js") }}*/
 28 /*{{ javascript("jslib/animationmanager.js") }}*/
 29 /*{{ javascript("jslib/animation.js") }}*/
 30 /*{{ javascript("jslib/observer.js") }}*/
 31 /*{{ javascript("jslib/utilities.js") }}*/
 32 /*{{ javascript("jslib/requesthandler.js") }}*/
 33 /*{{ javascript("jslib/vertexbuffermanager.js") }}*/
 34 /*{{ javascript("jslib/indexbuffermanager.js") }}*/
 35 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/
 36 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/
 37 /*{{ javascript("jslib/services/gamesession.js") }}*/
 38 /*{{ javascript("jslib/services/mappingtable.js") }}*/
 39 /*{{ javascript("scripts/sceneloader.js") }}*/
 40 /*{{ javascript("scripts/motion.js") }}*/
 41 /*{{ javascript("scripts/htmlcontrols.js") }}*/
 42 /*global TurbulenzEngine: false */
 43 /*global RequestHandler: false */
 44 /*global DefaultRendering: false */
 45 /*global TurbulenzServices: false */
 46 /*global Camera: false */
 47 /*global TextureManager: false */
 48 /*global ShaderManager: false */
 49 /*global EffectManager: false */
 50 /*global AnimationManager: false */
 51 /*global Scene: false */
 52 /*global SkinnedNode: false */
 53 /*global GPUSkinController: false */
 54 /*global SceneLoader: false */
 55 /*global ResourceLoader: false */
 56 /*global InterpolatorController: false */
 57 /*global VMath: false */
 58 /*global HTMLControls: false */
 59 /*global window: false */
 60 // HACK: TypeScript 0.9.0 removes the above comments without this
 61 // (since the comments get associated with the declare, which
 62 // generates no code).
 63 void 0;
 64 
 65 TurbulenzEngine.onload = function onloadFn() {
 66     var errorCallback = function errorCallback(msg) {
 67         window.alert(msg);
 68     };
 69 
 70     var graphicsDeviceParameters = {};
 71     var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters);
 72 
 73     if (!graphicsDevice.shadingLanguageVersion) {
 74         errorCallback("No shading language support detected.\nPlease check your graphics drivers are up to date.");
 75         graphicsDevice = null;
 76         return;
 77     }
 78 
 79     // Clear the background color of the engine window
 80     var clearColor = [0.5, 0.5, 0.5, 1.0];
 81     if (graphicsDevice.beginFrame()) {
 82         graphicsDevice.clear(clearColor);
 83         graphicsDevice.endFrame();
 84     }
 85 
 86     var mathDeviceParameters = {};
 87     var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters);
 88 
 89     var requestHandlerParameters = {};
 90     var requestHandler = RequestHandler.create(requestHandlerParameters);
 91 
 92     var textureManager = TextureManager.create(graphicsDevice, requestHandler, null, errorCallback);
 93     var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback);
 94     var effectManager = EffectManager.create();
 95     var animationManager = AnimationManager.create(errorCallback);
 96 
 97     // The model used for this sample only has 20 bones so we optimize for it.
 98     // Ideally you will modify the source shader code instead of patching at runtime
 99     // but this is done here for convenience.
100     shaderManager.setAutomaticParameterResize("skinBones", 20 * 3);
101 
102     var resourceLoader = ResourceLoader.create();
103     var scene = (Scene.create(mathDevice));
104     var sceneLoader = SceneLoader.create();
105     var mappingTable;
106     var renderer;
107 
108     // Setup world space
109     var worldUp = mathDevice.v3BuildYAxis();
110 
111     // Setup a camera to view a close-up object
112     var camera = Camera.create(mathDevice);
113     camera.nearPlane = 0.05;
114     camera.updateViewMatrix();
115 
116     var animMinExtent, animMaxExtent;
117 
118     var cameraDistanceFactor = 1.2;
119     var cameraDir = [-1, 1, -1];
120 
121     // Settings for the animation
122     var settings = {
123         animScale: 1,
124         defaultRate: 1,
125         drawDebug: false,
126         drawInterpolators: false,
127         drawWireframe: false,
128         loopAnimation: true
129     };
130 
131     // This is our base asset that includes a character and animations
132     var assetToLoad = "models/Seymour.dae";
133 
134     var character = {
135         count: 64,
136         max: 400,
137         min: 1,
138         lastCount: 1,
139         startPos: [0.0, 0.0, 0.0],
140         gridSpace: 0.1,
141         baseAngle: 135,
142         varAngle: 270,
143         height: 0.1
144     };
145 
146     var deg2Rad = (Math.PI / 180);
147 
148     // Materials to use for grid characters
149     var materials = {
150         seymour10: {
151             parameters: {
152                 diffuse: "textures/boy_10.png"
153             },
154             effect: "lambert"
155         },
156         seymour20: {
157             parameters: {
158                 diffuse: "textures/boy_20.png"
159             },
160             effect: "lambert"
161         },
162         seymour30: {
163             parameters: {
164                 diffuse: "textures/boy_30.png"
165             },
166             effect: "lambert"
167         },
168         seymour40: {
169             parameters: {
170                 diffuse: "textures/boy_40.png"
171             },
172             effect: "lambert"
173         },
174         seymour50: {
175             parameters: {
176                 diffuse: "textures/boy_50.png"
177             },
178             effect: "lambert"
179         }
180     };
181 
182     // Helper function for setting material on skeleton hierarchy
183     var setMaterialHierarchy = function setMaterialHierarchyFn(scene, node, materialName) {
184         var renderables = node.renderables;
185         if (renderables) {
186             var material = scene.getMaterial(materialName);
187             var numRenderables = renderables.length;
188             for (var i = 0; i < numRenderables; i += 1) {
189                 renderables[i].setMaterial(material);
190             }
191         }
192         var children = node.children;
193         if (children) {
194             var numChildren = children.length;
195             for (var c = 0; c < numChildren; c += 1) {
196                 var child = children[c];
197                 setMaterialHierarchy(scene, child, materialName);
198             }
199         }
200     };
201 
202     // The list of animations to load pre-scene load (by reference)
203     // This is only for animations that are not included in the default scene
204     // e.g. "animations/default_walking.anim"
205     var addAnimations = ["models/Seymour_anim2_rot90_anim_only.dae"];
206 
207     // The list of animations to be removed from the scene data pre-load
208     // This is for undesired animations that are packed in the scene
209     // All these animations are not required for this sample
210     var removeAnimations = [
211         "default_astroboy_w_skel02_gog_polySurface5",
212         "default_astroboy_w_skel02_polySurface5",
213         "default_astroboy_w_skel02c_gog_polySurface5",
214         "default_astroboy_w_skel02c_polySurface5",
215         "default_gog_polySurface5",
216         "default_polySurface5"
217     ];
218 
219     var animationsLoaded;
220 
221     // When the JSON is loaded, add a prefix to uniquely identify that set of animation data
222     var animationsLoadedCallback = function animationsLoadedCallbackFn(jsonData) {
223         var addAnimNum = (addAnimations.length - animationsLoaded);
224         animationManager.loadData(jsonData, "AnimExtra" + addAnimNum + "-");
225         animationsLoaded -= 1;
226     };
227 
228     var addAnimationsToScene = function addAnimationsToSceneFn() {
229         // Attach additional animations to the scene (specify by path)
230         // The animations are added by reference and the scene will attempt to load them using request
231         animationsLoaded = addAnimations.length;
232         for (var i = 0; i < addAnimations.length; i += 1) {
233             var path = addAnimations[i];
234             resourceLoader.load(mappingTable.getURL(path), {
235                 append: true,
236                 onload: animationsLoadedCallback,
237                 requestHandler: requestHandler
238             });
239         }
240     };
241 
242     var removeAnimationsFromScene = function removeAnimationsFromSceneFn(sceneData) {
243         // Remove unrequired animations from scene, if they exist before load
244         var anims = sceneData.animations;
245         var animationRef;
246 
247         if (anims) {
248             for (var i = 0; i < removeAnimations.length; i += 1) {
249                 animationRef = removeAnimations[i];
250                 if (anims[animationRef]) {
251                     delete anims[animationRef];
252                 }
253             }
254         }
255     };
256 
257     // Calculates a position for the camera to lookAt
258     var resetCamera = function resetCameraFn(camera) {
259         var ceil = Math.ceil;
260         var ex = character.gridSpace * ceil(Math.sqrt(character.count));
261         animMinExtent = mathDevice.v3BuildZero();
262         animMaxExtent = mathDevice.v3Build(ex, character.height, ex);
263 
264         // Update the camera to scale to the size of the scene
265         var center = mathDevice.v3ScalarMul(mathDevice.v3Add(animMaxExtent, animMinExtent), 0.5);
266         var extent = mathDevice.v3Sub(center, animMinExtent);
267 
268         camera.lookAt(center, worldUp, mathDevice.v3Build(center[0] + extent[0] * cameraDistanceFactor * cameraDir[0], center[1] + extent[1] * cameraDistanceFactor * cameraDir[1] * 4, center[2] + extent[2] * cameraDistanceFactor * cameraDir[2]));
269         camera.updateViewMatrix();
270 
271         // Calculates the appropriate nearPlane for the animation extents
272         var len = VMath.v3Length(extent);
273         if (len < 4.0) {
274             camera.nearPlane = len * 0.1;
275         } else {
276             camera.nearPlane = 1.0;
277         }
278         camera.farPlane = ceil(len) * 100.0;
279         camera.updateProjectionMatrix();
280     };
281 
282     // Controls
283     var htmlControls = HTMLControls.create();
284 
285     var slider01ID = "slider1";
286 
287     htmlControls.addSliderControl({
288         id: slider01ID,
289         value: character.count,
290         max: character.max,
291         min: character.min,
292         step: 1,
293         fn: function () {
294             var val = this.value;
295             character.count = val;
296             htmlControls.updateSlider(slider01ID, val);
297         }
298     });
299 
300     htmlControls.register();
301 
302     // Initialise character animations
303     var initCharacters = function initCharactersFn() {
304         var n, i, j, k, x, y, node, characterAngle;
305 
306         var nodeHasSkeleton = animationManager.nodeHasSkeleton;
307         var sceneNodes = scene.rootNodes;
308         var numNodes = sceneNodes.length;
309         var random = Math.random;
310         var floor = Math.floor;
311         var ceil = Math.ceil;
312 
313         // We want to create resources for the maximum grid elements we want to use
314         var gridDim = ceil(Math.sqrt(character.max));
315         var gridSpace = character.gridSpace;
316         var startX = character.startPos[0];
317         var startY = character.startPos[2];
318         var varAngle = character.varAngle;
319         var halfVarAngle = varAngle / 2;
320         var baseAngle = character.baseAngle;
321         var currentNodeIndex = 0;
322 
323         var newNodes = [];
324 
325         // These will be the reference node hierarchies for each material we use
326         // Instead of cloning the base node in the scene we will clone the reference node, because the material is already set
327         var referenceNodes = [];
328         referenceNodes.push("seymour10");
329         referenceNodes.push("seymour20");
330         referenceNodes.push("seymour30");
331         referenceNodes.push("seymour40");
332         referenceNodes.push("seymour50");
333 
334         var refLength = referenceNodes.length;
335 
336         var nodePos, nodeMaterialID, refNode, newNode, nodeName, matrix;
337         var v3Build = mathDevice.v3Build;
338         var m43FromAxisRotation = mathDevice.m43FromAxisRotation;
339         var m43SetPos = mathDevice.m43SetPos;
340 
341         for (n = 0; n < numNodes; n += 1) {
342             node = sceneNodes[n];
343             var skeleton = nodeHasSkeleton(node);
344             if (skeleton) {
345                 for (i = 0; i < gridDim; i += 1) {
346                     for (j = 0; j < gridDim; j += 1) {
347                         if ((i === 0) && (j === 0)) {
348                             // Use the orginal node as the basis, so don't create one
349                             node.gridNum = 1;
350                             continue;
351                         }
352                         nodeMaterialID = floor(refLength * random()) % refLength;
353                         refNode = referenceNodes[nodeMaterialID];
354                         if (typeof refNode === 'string') {
355                             // Create the first instance of the node, from the material name 'refNode'
356                             nodeName = node.name + "_" + refNode;
357                             newNode = scene.cloneRootNode(node, nodeName);
358 
359                             // Sets the material on this node and its children
360                             setMaterialHierarchy(scene, newNode, refNode);
361 
362                             // Assign the new hierarchy as a reference
363                             referenceNodes[nodeMaterialID] = newNode;
364                         } else {
365                             // Clone the reference node
366                             newNode = scene.cloneRootNode(refNode, refNode.name + currentNodeIndex);
367                         }
368 
369                         // CharacterAngle is the random angle of rotation
370                         characterAngle = baseAngle + (halfVarAngle - (varAngle * random()));
371 
372                         // x and y are the top down grid positions
373                         x = (i * gridSpace) + startX;
374                         y = (j * gridSpace) + startY;
375                         nodePos = v3Build.call(mathDevice, x, 0, y);
376 
377                         // Create a matrix from the angle
378                         matrix = m43FromAxisRotation.call(mathDevice, worldUp, characterAngle * deg2Rad);
379                         m43SetPos.call(mathDevice, matrix, nodePos);
380                         newNode.setLocalTransform(matrix);
381 
382                         // Set the grid position identifiers for our nodes
383                         // We can find the node grid position later
384                         newNode.gridX = i;
385                         newNode.gridY = j;
386 
387                         if (i > j) {
388                             k = (i + 1);
389                             k *= k;
390                             newNode.gridNum = k - (2 * i) + j;
391                         } else {
392                             k = (j + 1);
393                             k *= k;
394                             newNode.gridNum = k - j + i;
395                         }
396 
397                         newNodes.push(newNode);
398                         currentNodeIndex += 1;
399                     }
400                 }
401             }
402         }
403     };
404 
405     // Initialise all animations with InterpolatorControllers set to start time
406     var initAnimations = function initAnimationsFn(scene) {
407         var a, n, interp, skinnedNode, node;
408         var nodeHasSkeleton = animationManager.nodeHasSkeleton;
409         var sceneNodes = scene.rootNodes;
410         var sceneAnimations = animationManager.getAll();
411         var numNodes = sceneNodes.length;
412 
413         var random = Math.random;
414         var floor = Math.floor;
415 
416         var animations = [];
417         var animationsLength = 0;
418 
419         for (a in sceneAnimations) {
420             if (sceneAnimations.hasOwnProperty(a)) {
421                 animations.push(sceneAnimations[a]);
422             }
423         }
424         animationsLength = animations.length;
425 
426         scene.skinnedNodes = [];
427 
428         var randomIndex = 0;
429 
430         for (n = 0; n < numNodes; n += 1) {
431             node = sceneNodes[n];
432             var skeleton = nodeHasSkeleton(node);
433             if (skeleton) {
434                 // Randomly select an animation
435                 randomIndex = (floor(random() * 100) + randomIndex) % animationsLength;
436                 var animation = animations[randomIndex];
437 
438                 // Create an interpolation controller
439                 interp = InterpolatorController.create(animation.hierarchy);
440                 interp.setAnimation(animation, settings.loopAnimation);
441 
442                 // Set a different start time for each looping animation (for variation)
443                 interp.setTime(animation.length * random());
444                 interp.setRate(settings.defaultRate);
445 
446                 // Create a skinnedNode
447                 skinnedNode = SkinnedNode.create(graphicsDevice, mathDevice, node, skeleton, interp);
448                 skinnedNode.active = false;
449                 scene.skinnedNodes.push(skinnedNode);
450             }
451         }
452 
453         return true;
454     };
455 
456     // Initialize the previous frame time
457     var previousFrameTime = 0;
458     var nextGridUpdateTime = 0;
459     var fpsElement = document.getElementById("fpscounter");
460     var lastFPS = '';
461 
462     var updateFPSCounter = function updateFPSCounterFn() {
463         fpsElement.innerHTML = (lastFPS + ' fps');
464     };
465 
466     var renderFrame = function renderFrameFn() {
467         var skinnedNodes, numSkins, skinnedNode, sceneNode, skin;
468         var currentCharacterCount = character.count;
469         var resetGrid = false;
470 
471         var currentTime = TurbulenzEngine.getTime();
472         var deltaTime = (currentTime - previousFrameTime) * 0.001;
473         if (deltaTime > 0.1) {
474             deltaTime = 0.1;
475         }
476 
477         if (currentTime >= nextGridUpdateTime) {
478             nextGridUpdateTime = (currentTime + 0.5);
479 
480             resetGrid = (currentCharacterCount !== character.lastCount);
481             character.lastCount = currentCharacterCount;
482 
483             if (fpsElement) {
484                 var currentFPS = (graphicsDevice.fps).toFixed(2);
485                 if (lastFPS !== currentFPS) {
486                     lastFPS = currentFPS;
487 
488                     // Execute any code that interacts with the DOM in a separate callback
489                     TurbulenzEngine.setTimeout(updateFPSCounter, 1);
490                 }
491             }
492         }
493 
494         if (resetGrid) {
495             resetCamera(camera);
496         }
497 
498         var deviceWidth = graphicsDevice.width;
499         var deviceHeight = graphicsDevice.height;
500         var aspectRatio = (deviceWidth / deviceHeight);
501         if (aspectRatio !== camera.aspectRatio) {
502             camera.aspectRatio = aspectRatio;
503             camera.updateProjectionMatrix();
504         }
505         camera.updateViewProjectionMatrix();
506 
507         skinnedNodes = scene.skinnedNodes;
508         numSkins = skinnedNodes.length;
509 
510         for (skin = 0; skin < numSkins; skin += 1) {
511             skinnedNode = skinnedNodes[skin];
512             sceneNode = skinnedNode.node;
513 
514             if (resetGrid) {
515                 var gridNum = sceneNode.gridNum;
516                 if (gridNum !== undefined) {
517                     skinnedNode.active = (gridNum <= currentCharacterCount);
518                 } else {
519                     // Default model or not in the grid, so set active
520                     skinnedNode.active = true;
521                 }
522             }
523 
524             if (skinnedNode.active) {
525                 if (sceneNode.getDisabled()) {
526                     sceneNode.enableHierarchy(true);
527                 }
528 
529                 // The skinned node will peform the update
530                 skinnedNode.addTime(deltaTime);
531                 skinnedNode.update(true);
532             } else {
533                 if (!sceneNode.getDisabled()) {
534                     sceneNode.enableHierarchy(false);
535                 }
536             }
537         }
538 
539         scene.update();
540 
541         renderer.update(graphicsDevice, camera, scene, currentTime);
542 
543         if (graphicsDevice.beginFrame()) {
544             if (renderer.updateBuffers(graphicsDevice, deviceWidth, deviceHeight)) {
545                 renderer.draw(graphicsDevice, clearColor);
546             }
547 
548             graphicsDevice.endFrame();
549         }
550 
551         previousFrameTime = currentTime;
552     };
553 
554     var intervalID;
555     var loadingLoop = function loadingLoopFn() {
556         if (sceneLoader.complete() && animationsLoaded === 0) {
557             TurbulenzEngine.clearInterval(intervalID);
558 
559             // Adds character nodes to the scene
560             initCharacters();
561 
562             // Init the animations from the scene
563             initAnimations(scene);
564 
565             // Intial reset of the camera
566             resetCamera(camera);
567 
568             renderer.updateShader(shaderManager);
569 
570             // Update the slider
571             htmlControls.updateSlider(slider01ID, undefined);
572 
573             intervalID = TurbulenzEngine.setInterval(renderFrame, 1000 / 60);
574 
575             previousFrameTime = TurbulenzEngine.getTime();
576         }
577     };
578     intervalID = TurbulenzEngine.setInterval(loadingLoop, 1000 / 10);
579 
580     var loadAssets = function loadAssetsFn() {
581         // Renderer for the scene (requires shader assets).
582         renderer = DefaultRendering.create(graphicsDevice, mathDevice, shaderManager, effectManager);
583 
584         renderer.setGlobalLightPosition(mathDevice.v3Build(0.5, 100.0, 0.5));
585         renderer.setAmbientColor(mathDevice.v3Build(0.3, 0.3, 0.4));
586         renderer.setDefaultTexture(textureManager.get("default"));
587 
588         for (var m in materials) {
589             if (materials.hasOwnProperty(m)) {
590                 var material = materials[m];
591 
592                 if (!material.loaded) {
593                     material.loaded = scene.hasMaterial(m);
594                 }
595 
596                 if (!material.loaded) {
597                     material.loaded = scene.loadMaterial(graphicsDevice, textureManager, effectManager, m, material);
598                 }
599             }
600         }
601 
602         // Create object using scene loader
603         sceneLoader.load({
604             scene: scene,
605             assetPath: assetToLoad,
606             graphicsDevice: graphicsDevice,
607             mathDevice: mathDevice,
608             textureManager: textureManager,
609             effectManager: effectManager,
610             shaderManager: shaderManager,
611             animationManager: animationManager,
612             requestHandler: requestHandler,
613             preSceneLoadFn: function (sceneData) {
614                 // Apply the modifications to the sceneData from assetPath
615                 addAnimationsToScene();
616                 removeAnimationsFromScene(sceneData);
617             },
618             keepLights: true,
619             append: true,
620             vertexFormatMap: {
621                 "BLENDINDICES": "UBYTE4",
622                 "NORMAL": "BYTE4N",
623                 "BLENDWEIGHT": "UBYTE4N"
624             }
625         });
626     };
627 
628     var mappingTableReceived = function mappingTableReceivedFn(mappingTable) {
629         textureManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
630         shaderManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
631         sceneLoader.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
632 
633         loadAssets();
634     };
635 
636     var gameSessionCreated = function gameSessionCreatedFn(gameSession) {
637         mappingTable = TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived);
638     };
639     var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated);
640 
641     // Create a scene destroy callback to run when the window is closed
642     TurbulenzEngine.onunload = function destroyScene() {
643         TurbulenzEngine.clearInterval(intervalID);
644 
645         if (gameSession) {
646             gameSession.destroy();
647             gameSession = null;
648         }
649         mappingTable = null;
650 
651         if (scene) {
652             scene.destroy();
653             scene = null;
654         }
655         requestHandler = null;
656         sceneLoader = null;
657 
658         htmlControls = null;
659 
660         if (renderer) {
661             renderer.destroy();
662             renderer = null;
663         }
664 
665         addAnimations = null;
666         removeAnimations = null;
667 
668         materials = null;
669 
670         settings = null;
671         character = null;
672 
673         camera = null;
674         cameraDir = null;
675 
676         animMinExtent = [];
677         animMaxExtent = [];
678 
679         clearColor = [];
680         worldUp = [];
681 
682         if (textureManager) {
683             textureManager.destroy();
684             textureManager = null;
685         }
686 
687         if (shaderManager) {
688             shaderManager.destroy();
689             shaderManager = null;
690         }
691 
692         effectManager = null;
693 
694         fpsElement = null;
695 
696         TurbulenzEngine.flush();
697         graphicsDevice = null;
698         mathDevice = null;
699     };
700 };