webgl_animation_multiple.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>Multiple animated objects</title>
  5. <meta charset="utf-8">
  6. <meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport">
  7. <style>
  8. body {
  9. overflow: hidden;
  10. }
  11. </style>
  12. </head>
  13. <body>
  14. <div id="container"></div>
  15. <div id="info"
  16. style="position: absolute; left: 0; top: 0; width: 100%; background-color: white; border: 1px solid black; margin: 10px; padding: 10px;">
  17. This demo shows how to load several instances of the same 3D model (same .GLTF file) into the
  18. scene, position them at different locations and launch different animations for them.
  19. To do it, some tricky cloning of SkinnedMesh, Skeleton and Bone objects is necessary (done by SkeletonUtils.clone().
  20. Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>.
  21. </div>
  22. <script src="../build/three.js"></script>
  23. <script src="js/WebGL.js"></script>
  24. <script src="js/loaders/GLTFLoader.js"></script>
  25. <script src="js/utils/SkeletonUtils.js"></script>
  26. <script>
  27. if ( WEBGL.isWebGLAvailable() === false ) {
  28. document.body.appendChild( WEBGL.getWebGLErrorMessage() );
  29. }
  30. //////////////////////////////
  31. // Global objects
  32. //////////////////////////////
  33. var worldScene = null; // THREE.Scene where it all will be rendered
  34. var renderer = null;
  35. var camera = null;
  36. var mixers = []; // All the AnimationMixer objects for all the animations in the scene
  37. //////////////////////////////
  38. //////////////////////////////
  39. // Information about our 3D models and units
  40. //////////////////////////////
  41. // The names of the 3D models to load. One-per file.
  42. // A model may have multiple SkinnedMesh objects as well as several rigs (armatures). Units will define which
  43. // meshes, armatures and animations to use. We will load the whole scene for each object and clone it for each unit.
  44. // Models are from https://www.mixamo.com/
  45. var MODELS = [
  46. { name: "Soldier" },
  47. { name: "Parrot" },
  48. // { name: "RiflePunch" },
  49. ];
  50. // Here we define instances of the models that we want to place in the scene, their position, scale and the animations
  51. // that must be played.
  52. var UNITS = [
  53. {
  54. modelName: "Soldier", // Will use the 3D model from file models/gltf/Soldier.glb
  55. meshName: "vanguard_Mesh", // Name of the main mesh to animate
  56. position: { x: 0, y: 0, z: 0 }, // Where to put the unit in the scene
  57. scale: 1, // Scaling of the unit. 1.0 means: use original size, 0.1 means "10 times smaller", etc.
  58. animationName: "Idle" // Name of animation to run
  59. },
  60. {
  61. modelName: "Soldier",
  62. meshName: "vanguard_Mesh",
  63. position: { x: 3, y: 0, z: 0 },
  64. scale: 2,
  65. animationName: "Walk"
  66. },
  67. {
  68. modelName: "Soldier",
  69. meshName: "vanguard_Mesh",
  70. position: { x: 1, y: 0, z: 0 },
  71. scale: 1,
  72. animationName: "Run"
  73. },
  74. {
  75. modelName: "Parrot",
  76. meshName: "mesh_0",
  77. position: { x: - 4, y: 0, z: 0 },
  78. rotation: { x: 0, y: Math.PI, z: 0 },
  79. scale: 0.01,
  80. animationName: "parrot_A_"
  81. },
  82. {
  83. modelName: "Parrot",
  84. meshName: "mesh_0",
  85. position: { x: - 2, y: 0, z: 0 },
  86. rotation: { x: 0, y: Math.PI / 2, z: 0 },
  87. scale: 0.02,
  88. animationName: null
  89. },
  90. ];
  91. //////////////////////////////
  92. // The main setup happens here
  93. //////////////////////////////
  94. var numLoadedModels = 0;
  95. initScene();
  96. initRenderer();
  97. loadModels();
  98. animate();
  99. //////////////////////////////
  100. //////////////////////////////
  101. // Function implementations
  102. //////////////////////////////
  103. /**
  104. * Function that starts loading process for the next model in the queue. The loading process is
  105. * asynchronous: it happens "in the background". Therefore we don't load all the models at once. We load one,
  106. * wait until it is done, then load the next one. When all models are loaded, we call loadUnits().
  107. */
  108. function loadModels() {
  109. for ( var i = 0; i < MODELS.length; ++ i ) {
  110. var m = MODELS[ i ];
  111. loadGltfModel( m, function ( model ) {
  112. ++ numLoadedModels;
  113. if ( numLoadedModels === MODELS.length ) {
  114. console.log( "All models loaded, time to instantiate units..." );
  115. instantiateUnits();
  116. }
  117. } );
  118. }
  119. }
  120. /**
  121. * Look at UNITS configuration, clone necessary 3D model scenes, place the armatures and meshes in the scene and
  122. * launch necessary animations
  123. */
  124. function instantiateUnits() {
  125. var numSuccess = 0;
  126. for ( var i = 0; i < UNITS.length; ++ i ) {
  127. var u = UNITS[ i ];
  128. var model = getModelByName( u.modelName );
  129. if ( model ) {
  130. var clonedScene = THREE.SkeletonUtils.clone( model.scene );
  131. if ( clonedScene ) {
  132. // Scene is cloned properly, let's find one mesh and launch animation for it
  133. var clonedMesh = clonedScene.getObjectByName( u.meshName );
  134. if ( clonedMesh ) {
  135. var mixer = startAnimation( clonedMesh, model.animations, u.animationName );
  136. if ( mixer ) {
  137. // Save the animation mixer in the list, will need it in the animation loop
  138. mixers.push( mixer );
  139. numSuccess ++;
  140. }
  141. }
  142. // Different models can have different configurations of armatures and meshes. Therefore,
  143. // We can't set position, scale or rotation to individual mesh objects. Instead we set
  144. // it to the whole cloned scene and then add the whole scene to the game world
  145. // Note: this may have weird effects if you have lights or other items in the GLTF file's scene!
  146. worldScene.add( clonedScene );
  147. if ( u.position ) {
  148. clonedScene.position.set( u.position.x, u.position.y, u.position.z );
  149. }
  150. if ( u.scale ) {
  151. clonedScene.scale.set( u.scale, u.scale, u.scale );
  152. }
  153. if ( u.rotation ) {
  154. clonedScene.rotation.x = u.rotation.x;
  155. clonedScene.rotation.y = u.rotation.y;
  156. clonedScene.rotation.z = u.rotation.z;
  157. }
  158. }
  159. } else {
  160. console.error( "Can not find model", u.modelName );
  161. }
  162. }
  163. console.log( `Successfully instantiated ${numSuccess} units` );
  164. }
  165. /**
  166. * Start animation for a specific mesh object. Find the animation by name in the 3D model's animation array
  167. * @param skinnedMesh {THREE.SkinnedMesh} The mesh to animate
  168. * @param animations {Array} Array containing all the animations for this model
  169. * @param animationName {string} Name of the animation to launch
  170. * @return {THREE.AnimationMixer} Mixer to be used in the render loop
  171. */
  172. function startAnimation( skinnedMesh, animations, animationName ) {
  173. var mixer = new THREE.AnimationMixer( skinnedMesh );
  174. var clip = THREE.AnimationClip.findByName( animations, animationName );
  175. if ( clip ) {
  176. var action = mixer.clipAction( clip );
  177. action.play();
  178. }
  179. return mixer;
  180. }
  181. /**
  182. * Find a model object by name
  183. * @param name
  184. * @returns {object|null}
  185. */
  186. function getModelByName( name ) {
  187. for ( var i = 0; i < MODELS.length; ++ i ) {
  188. if ( MODELS[ i ].name === name ) {
  189. return MODELS[ i ];
  190. }
  191. }
  192. return null;
  193. }
  194. /**
  195. * Load a 3D model from a GLTF file. Use the GLTFLoader.
  196. * @param model {object} Model config, one item from the MODELS array. It will be updated inside the function!
  197. * @param onLoaded {function} A callback function that will be called when the model is loaded
  198. */
  199. function loadGltfModel( model, onLoaded ) {
  200. var loader = new THREE.GLTFLoader();
  201. var modelName = "models/gltf/" + model.name + ".glb";
  202. loader.load( modelName, function ( gltf ) {
  203. var scene = gltf.scene;
  204. model.animations = gltf.animations;
  205. model.scene = scene;
  206. // Enable Shadows
  207. gltf.scene.traverse( function ( object ) {
  208. if ( object.isMesh ) {
  209. object.castShadow = true;
  210. }
  211. } );
  212. console.log( "Done loading model", model.name );
  213. onLoaded( model );
  214. } );
  215. }
  216. /**
  217. * Render loop. Renders the next frame of all animations
  218. */
  219. function animate() {
  220. requestAnimationFrame( animate );
  221. // Get the time elapsed since the last frame
  222. var mixerUpdateDelta = clock.getDelta();
  223. // Update all the animation frames
  224. for ( var i = 0; i < mixers.length; ++ i ) {
  225. mixers[ i ].update( mixerUpdateDelta );
  226. }
  227. renderer.render( worldScene, camera );
  228. }
  229. //////////////////////////////
  230. // General Three.JS stuff
  231. //////////////////////////////
  232. // This part is not anyhow related to the cloning of models, it's just setting up the scene.
  233. /**
  234. * Initialize ThreeJS scene renderer
  235. */
  236. function initRenderer() {
  237. var container = document.getElementById( 'container' );
  238. renderer = new THREE.WebGLRenderer( { antialias: true } );
  239. renderer.setPixelRatio( window.devicePixelRatio );
  240. renderer.setSize( window.innerWidth, window.innerHeight );
  241. renderer.gammaOutput = true;
  242. renderer.gammaFactor = 2.2;
  243. renderer.shadowMap.enabled = true;
  244. renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  245. container.appendChild( renderer.domElement );
  246. }
  247. /**
  248. * Initialize ThreeJS Scene
  249. */
  250. function initScene() {
  251. camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
  252. camera.position.set( 3, 6, - 10 );
  253. camera.lookAt( 0, 1, 0 );
  254. clock = new THREE.Clock();
  255. worldScene = new THREE.Scene();
  256. worldScene.background = new THREE.Color( 0xa0a0a0 );
  257. worldScene.fog = new THREE.Fog( 0xa0a0a0, 10, 22 );
  258. var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 );
  259. hemiLight.position.set( 0, 20, 0 );
  260. worldScene.add( hemiLight );
  261. var dirLight = new THREE.DirectionalLight( 0xffffff );
  262. dirLight.position.set( - 3, 10, - 10 );
  263. dirLight.castShadow = true;
  264. dirLight.shadow.camera.top = 10;
  265. dirLight.shadow.camera.bottom = - 10;
  266. dirLight.shadow.camera.left = - 10;
  267. dirLight.shadow.camera.right = 10;
  268. dirLight.shadow.camera.near = 0.1;
  269. dirLight.shadow.camera.far = 40;
  270. worldScene.add( dirLight );
  271. // ground
  272. var groundMesh = new THREE.Mesh(
  273. new THREE.PlaneBufferGeometry( 40, 40 ),
  274. new THREE.MeshPhongMaterial( {
  275. color: 0x999999,
  276. depthWrite: false
  277. } )
  278. );
  279. groundMesh.rotation.x = - Math.PI / 2;
  280. groundMesh.receiveShadow = true;
  281. worldScene.add( groundMesh );
  282. window.addEventListener( 'resize', onWindowResize, false );
  283. }
  284. /**
  285. * A callback that will be called whenever the browser window is resized.
  286. */
  287. function onWindowResize() {
  288. camera.aspect = window.innerWidth / window.innerHeight;
  289. camera.updateProjectionMatrix();
  290. renderer.setSize( window.innerWidth, window.innerHeight );
  291. }
  292. </script>
  293. </body>
  294. </html>