webgl_animation_multiple.html 10 KB

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