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