|
@@ -1,322 +1,408 @@
|
|
<!DOCTYPE html>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<html lang="en">
|
|
-<head>
|
|
|
|
- <title>Multiple animated objects</title>
|
|
|
|
- <meta charset="utf-8">
|
|
|
|
- <meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport">
|
|
|
|
- <style>
|
|
|
|
- body {
|
|
|
|
- overflow: hidden;
|
|
|
|
- }
|
|
|
|
- </style>
|
|
|
|
-</head>
|
|
|
|
-<body>
|
|
|
|
-<div id="container"></div>
|
|
|
|
-
|
|
|
|
-<div id="info"
|
|
|
|
- style="position: absolute; left: 0; top: 0; width: 100%; background-color: white; border: 1px solid black; margin: 10px; padding: 10px;">
|
|
|
|
- This demo shows how to load several instances of the same 3D model (same .GLTF file) into the
|
|
|
|
- scene, position them at different locations and launch different animations for them.
|
|
|
|
- To do it, some tricky cloning of SkinnedMesh, Skeleton and Bone objects is necessary (done by SkeletonUtils.clone().
|
|
|
|
- Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>.
|
|
|
|
-</div>
|
|
|
|
-
|
|
|
|
-<script src="../build/three.js"></script>
|
|
|
|
-<script src="js/WebGL.js"></script>
|
|
|
|
-<script src="js/loaders/GLTFLoader.js"></script>
|
|
|
|
-<script src="js/utils/SkeletonUtils.js"></script>
|
|
|
|
-
|
|
|
|
-<script>
|
|
|
|
-
|
|
|
|
- if (WEBGL.isWebGLAvailable() === false) {
|
|
|
|
- document.body.appendChild(WEBGL.getWebGLErrorMessage());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- //////////////////////////////
|
|
|
|
- // Global objects
|
|
|
|
- //////////////////////////////
|
|
|
|
- let worldScene = null; // THREE.Scene where it all will be rendered
|
|
|
|
- let renderer = null;
|
|
|
|
- let camera = null;
|
|
|
|
- let mixers = []; // All the AnimationMixer objects for all the animations in the scene
|
|
|
|
- //////////////////////////////
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- //////////////////////////////
|
|
|
|
- // Information about our 3D models and units
|
|
|
|
- //////////////////////////////
|
|
|
|
-
|
|
|
|
- // The names of the 3D models to load. One-per file.
|
|
|
|
- // A model may have multiple SkinnedMesh objects as well as several rigs (armatures). Units will define which
|
|
|
|
- // meshes, armatures and animations to use. We will load the whole scene for each object and clone it for each unit.
|
|
|
|
- // Models are from https://www.mixamo.com/
|
|
|
|
- const MODELS = [
|
|
|
|
- {name: "Soldier"},
|
|
|
|
- {name: "Parrot"},
|
|
|
|
- // {name: "RiflePunch"},
|
|
|
|
- ];
|
|
|
|
-
|
|
|
|
- // Here we define instances of the models that we want to place in the scene, their position, scale and the animations
|
|
|
|
- // that must be played.
|
|
|
|
- const UNITS = [
|
|
|
|
- {
|
|
|
|
- modelName: "Soldier", // Will use the 3D model from file models/gltf/Soldier.glb
|
|
|
|
- meshName: "vanguard_Mesh", // Name of the main mesh to animate
|
|
|
|
- position: {x: 0, y: 0, z: 0}, // Where to put the unit in the scene
|
|
|
|
- scale: 1, // Scaling of the unit. 1.0 means: use original size, 0.1 means "10 times smaller", etc.
|
|
|
|
- animationName: "Idle" // Name of animation to run
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- modelName: "Soldier",
|
|
|
|
- meshName: "vanguard_Mesh",
|
|
|
|
- position: {x: 3, y: 0, z: 0},
|
|
|
|
- scale: 2,
|
|
|
|
- animationName: "Walk"
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- modelName: "Soldier",
|
|
|
|
- meshName: "vanguard_Mesh",
|
|
|
|
- position: {x: 1, y: 0, z: 0},
|
|
|
|
- scale: 1,
|
|
|
|
- animationName: "Run"
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- modelName: "Parrot",
|
|
|
|
- meshName: "mesh_0",
|
|
|
|
- position: {x: -4, y: 0, z: 0},
|
|
|
|
- rotation: {x: 0, y: Math.PI, z: 0},
|
|
|
|
- scale: 0.01,
|
|
|
|
- animationName: "parrot_A_"
|
|
|
|
- },
|
|
|
|
- {
|
|
|
|
- modelName: "Parrot",
|
|
|
|
- meshName: "mesh_0",
|
|
|
|
- position: {x: -2, y: 0, z: 0},
|
|
|
|
- rotation: {x: 0, y: Math.PI / 2, z: 0},
|
|
|
|
- scale: 0.02,
|
|
|
|
- animationName: null
|
|
|
|
- },
|
|
|
|
- ];
|
|
|
|
-
|
|
|
|
- //////////////////////////////
|
|
|
|
- // The main setup happens here
|
|
|
|
- //////////////////////////////
|
|
|
|
- let numLoadedModels = 0;
|
|
|
|
- initScene();
|
|
|
|
- initRenderer();
|
|
|
|
- loadModels();
|
|
|
|
- animate();
|
|
|
|
- //////////////////////////////
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- //////////////////////////////
|
|
|
|
- // Function implementations
|
|
|
|
- //////////////////////////////
|
|
|
|
- /**
|
|
|
|
- * Function that starts loading process for the next model in the queue. The loading process is
|
|
|
|
- * asynchronous: it happens "in the background". Therefore we don't load all the models at once. We load one,
|
|
|
|
- * wait until it is done, then load the next one. When all models are loaded, we call loadUnits().
|
|
|
|
- */
|
|
|
|
- function loadModels() {
|
|
|
|
- for (let i = 0; i < MODELS.length; ++i) {
|
|
|
|
- const m = MODELS[i];
|
|
|
|
- loadGltfModel(m, function (model) {
|
|
|
|
- console.log("Done loading model", MODELS[i].name);
|
|
|
|
- ++numLoadedModels;
|
|
|
|
- if (numLoadedModels === MODELS.length) {
|
|
|
|
- console.log("All models loaded, time to instantiate units...");
|
|
|
|
- instantiateUnits();
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Look at UNITS configuration, clone necessary 3D model scenes, place the armatures and meshes in the scene and
|
|
|
|
- * launch necessary animations
|
|
|
|
- */
|
|
|
|
- function instantiateUnits() {
|
|
|
|
- let numSuccess = 0;
|
|
|
|
- for (let i = 0; i < UNITS.length; ++i) {
|
|
|
|
- const u = UNITS[i];
|
|
|
|
- const model = getModelByName(u.modelName);
|
|
|
|
- if (model) {
|
|
|
|
- const clonedScene = THREE.SkeletonUtils.clone(model.scene);
|
|
|
|
- if (clonedScene) {
|
|
|
|
- // Scene is cloned properly, let's find one mesh and launch animation for it
|
|
|
|
- const clonedMesh = clonedScene.getObjectByName(u.meshName);
|
|
|
|
- if (clonedMesh) {
|
|
|
|
- const mixer = startAnimation(clonedMesh, model.animations, u.animationName);
|
|
|
|
- if (mixer) {
|
|
|
|
- // Save the animation mixer in the list, will need it in the animation loop
|
|
|
|
- mixers.push(mixer);
|
|
|
|
- numSuccess++;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Different models can have different configurations of armatures and meshes. Therefore,
|
|
|
|
- // We can't set position, scale or rotation to individual mesh objects. Instead we set
|
|
|
|
- // it to the whole cloned scene and then add the whole scene to the game world
|
|
|
|
- // Note: this may have weird effects if you have lights or other items in the GLTF file's scene!
|
|
|
|
- worldScene.add(clonedScene);
|
|
|
|
- if (u.position) {
|
|
|
|
- clonedScene.position.set(u.position.x, u.position.y, u.position.z);
|
|
|
|
- }
|
|
|
|
- if (u.scale) {
|
|
|
|
- clonedScene.scale.set(u.scale, u.scale, u.scale);
|
|
|
|
- }
|
|
|
|
- if (u.rotation) {
|
|
|
|
- clonedScene.rotation.x = u.rotation.x;
|
|
|
|
- clonedScene.rotation.y = u.rotation.y;
|
|
|
|
- clonedScene.rotation.z = u.rotation.z;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- console.error("Can not find model", u.modelName);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- console.log(`Successfully instantiated ${numSuccess} units`);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Start animation for a specific mesh object. Find the animation by name in the 3D model's animation array
|
|
|
|
- * @param skinnedMesh {THREE.SkinnedMesh} The mesh to animate
|
|
|
|
- * @param animations {Array} Array containing all the animations for this model
|
|
|
|
- * @param animationName {string} Name of the animation to launch
|
|
|
|
- * @return {THREE.AnimationMixer} Mixer to be used in the render loop
|
|
|
|
- */
|
|
|
|
- function startAnimation(skinnedMesh, animations, animationName) {
|
|
|
|
- let mixer = new THREE.AnimationMixer(skinnedMesh);
|
|
|
|
- const clip = THREE.AnimationClip.findByName(animations, animationName);
|
|
|
|
- if (clip) {
|
|
|
|
- const action = mixer.clipAction(clip);
|
|
|
|
- action.play();
|
|
|
|
- }
|
|
|
|
- return mixer;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Find a model object by name
|
|
|
|
- * @param name
|
|
|
|
- * @returns {object|null}
|
|
|
|
- */
|
|
|
|
- function getModelByName(name) {
|
|
|
|
- for (let i = 0; i < MODELS.length; ++i) {
|
|
|
|
- if (MODELS[i].name === name) {
|
|
|
|
- return MODELS[i];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Load a 3D model from a GLTF file. Use the GLTFLoader.
|
|
|
|
- * @param model {object} Model config, one item from the MODELS array. It will be updated inside the function!
|
|
|
|
- * @param onLoaded {function} A callback function that will be called when the model is loaded
|
|
|
|
- */
|
|
|
|
- function loadGltfModel(model, onLoaded) {
|
|
|
|
- const loader = new THREE.GLTFLoader();
|
|
|
|
- const modelName = "models/gltf/" + model.name + ".glb";
|
|
|
|
- loader.load(modelName, function (gltf) {
|
|
|
|
- const scene = gltf.scene;
|
|
|
|
- model.animations = gltf.animations;
|
|
|
|
- model.scene = scene;
|
|
|
|
- // Enable Shadows
|
|
|
|
- gltf.scene.traverse(function (object) {
|
|
|
|
- if (object.isMesh) {
|
|
|
|
- object.castShadow = true;
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- onLoaded(model);
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Render loop. Renders the next frame of all animations
|
|
|
|
- */
|
|
|
|
- function animate() {
|
|
|
|
- requestAnimationFrame(animate);
|
|
|
|
- // Get the time elapsed since the last frame
|
|
|
|
- const mixerUpdateDelta = clock.getDelta();
|
|
|
|
- // Update all the animation frames
|
|
|
|
- for (let i = 0; i < mixers.length; ++i) {
|
|
|
|
- mixers[i].update(mixerUpdateDelta);
|
|
|
|
- }
|
|
|
|
- renderer.render(worldScene, camera);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- //////////////////////////////
|
|
|
|
- // General Three.JS stuff
|
|
|
|
- //////////////////////////////
|
|
|
|
- // This part is not anyhow related to the cloning of models, it's just setting up the scene.
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Initialize ThreeJS scene renderer
|
|
|
|
- */
|
|
|
|
- function initRenderer() {
|
|
|
|
- const container = document.getElementById('container');
|
|
|
|
- renderer = new THREE.WebGLRenderer({antialias: true});
|
|
|
|
- renderer.setPixelRatio(window.devicePixelRatio);
|
|
|
|
- renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
|
- renderer.gammaOutput = true;
|
|
|
|
- renderer.gammaFactor = 2.2;
|
|
|
|
- renderer.shadowMap.enabled = true;
|
|
|
|
- renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
|
|
- container.appendChild(renderer.domElement);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Initialize ThreeJS Scene
|
|
|
|
- */
|
|
|
|
- function initScene() {
|
|
|
|
- camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
|
|
|
|
- camera.position.set(3, 6, -10);
|
|
|
|
- camera.lookAt(0, 1, 0);
|
|
|
|
-
|
|
|
|
- clock = new THREE.Clock();
|
|
|
|
-
|
|
|
|
- worldScene = new THREE.Scene();
|
|
|
|
- worldScene.background = new THREE.Color(0xa0a0a0);
|
|
|
|
- worldScene.fog = new THREE.Fog(0xa0a0a0, 10, 22);
|
|
|
|
-
|
|
|
|
- const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
|
|
|
|
- hemiLight.position.set(0, 20, 0);
|
|
|
|
- worldScene.add(hemiLight);
|
|
|
|
-
|
|
|
|
- const dirLight = new THREE.DirectionalLight(0xffffff);
|
|
|
|
- dirLight.position.set(-3, 10, -10);
|
|
|
|
- dirLight.castShadow = true;
|
|
|
|
- dirLight.shadow.camera.top = 10;
|
|
|
|
- dirLight.shadow.camera.bottom = -10;
|
|
|
|
- dirLight.shadow.camera.left = -10;
|
|
|
|
- dirLight.shadow.camera.right = 10;
|
|
|
|
- dirLight.shadow.camera.near = 0.1;
|
|
|
|
- dirLight.shadow.camera.far = 40;
|
|
|
|
- worldScene.add(dirLight);
|
|
|
|
-
|
|
|
|
- // ground
|
|
|
|
- const groundMesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(40, 40), new THREE.MeshPhongMaterial({
|
|
|
|
- color: 0x999999,
|
|
|
|
- depthWrite: false
|
|
|
|
- }));
|
|
|
|
- groundMesh.rotation.x = -Math.PI / 2;
|
|
|
|
- groundMesh.receiveShadow = true;
|
|
|
|
- worldScene.add(groundMesh);
|
|
|
|
- window.addEventListener('resize', onWindowResize, false);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * A callback that will be called whenever the browser window is resized.
|
|
|
|
- */
|
|
|
|
- function onWindowResize() {
|
|
|
|
- camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
|
- camera.updateProjectionMatrix();
|
|
|
|
- renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-</script>
|
|
|
|
-
|
|
|
|
-</body>
|
|
|
|
|
|
+ <head>
|
|
|
|
+ <title>Multiple animated objects</title>
|
|
|
|
+ <meta charset="utf-8">
|
|
|
|
+ <meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport">
|
|
|
|
+ <style>
|
|
|
|
+ body {
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ }
|
|
|
|
+ </style>
|
|
|
|
+ </head>
|
|
|
|
+ <body>
|
|
|
|
+ <div id="container"></div>
|
|
|
|
+
|
|
|
|
+ <div id="info"
|
|
|
|
+ style="position: absolute; left: 0; top: 0; width: 100%; background-color: white; border: 1px solid black; margin: 10px; padding: 10px;">
|
|
|
|
+ This demo shows how to load several instances of the same 3D model (same .GLTF file) into the
|
|
|
|
+ scene, position them at different locations and launch different animations for them.
|
|
|
|
+ To do it, some tricky cloning of SkinnedMesh, Skeleton and Bone objects is necessary (done by SkeletonUtils.clone().
|
|
|
|
+ Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>.
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <script src="../build/three.js"></script>
|
|
|
|
+ <script src="js/WebGL.js"></script>
|
|
|
|
+ <script src="js/loaders/GLTFLoader.js"></script>
|
|
|
|
+ <script src="js/utils/SkeletonUtils.js"></script>
|
|
|
|
+
|
|
|
|
+ <script>
|
|
|
|
+
|
|
|
|
+ if ( WEBGL.isWebGLAvailable() === false ) {
|
|
|
|
+
|
|
|
|
+ document.body.appendChild( WEBGL.getWebGLErrorMessage() );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ // Global objects
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ var worldScene = null; // THREE.Scene where it all will be rendered
|
|
|
|
+ var renderer = null;
|
|
|
|
+ var camera = null;
|
|
|
|
+ var mixers = []; // All the AnimationMixer objects for all the animations in the scene
|
|
|
|
+ //////////////////////////////
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ // Information about our 3D models and units
|
|
|
|
+ //////////////////////////////
|
|
|
|
+
|
|
|
|
+ // The names of the 3D models to load. One-per file.
|
|
|
|
+ // A model may have multiple SkinnedMesh objects as well as several rigs (armatures). Units will define which
|
|
|
|
+ // meshes, armatures and animations to use. We will load the whole scene for each object and clone it for each unit.
|
|
|
|
+ // Models are from https://www.mixamo.com/
|
|
|
|
+ var MODELS = [
|
|
|
|
+ { name: "Soldier" },
|
|
|
|
+ { name: "Parrot" },
|
|
|
|
+ // { name: "RiflePunch" },
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ // Here we define instances of the models that we want to place in the scene, their position, scale and the animations
|
|
|
|
+ // that must be played.
|
|
|
|
+ var UNITS = [
|
|
|
|
+ {
|
|
|
|
+ modelName: "Soldier", // Will use the 3D model from file models/gltf/Soldier.glb
|
|
|
|
+ meshName: "vanguard_Mesh", // Name of the main mesh to animate
|
|
|
|
+ position: { x: 0, y: 0, z: 0 }, // Where to put the unit in the scene
|
|
|
|
+ scale: 1, // Scaling of the unit. 1.0 means: use original size, 0.1 means "10 times smaller", etc.
|
|
|
|
+ animationName: "Idle" // Name of animation to run
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ modelName: "Soldier",
|
|
|
|
+ meshName: "vanguard_Mesh",
|
|
|
|
+ position: { x: 3, y: 0, z: 0 },
|
|
|
|
+ scale: 2,
|
|
|
|
+ animationName: "Walk"
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ modelName: "Soldier",
|
|
|
|
+ meshName: "vanguard_Mesh",
|
|
|
|
+ position: { x: 1, y: 0, z: 0 },
|
|
|
|
+ scale: 1,
|
|
|
|
+ animationName: "Run"
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ modelName: "Parrot",
|
|
|
|
+ meshName: "mesh_0",
|
|
|
|
+ position: { x: - 4, y: 0, z: 0 },
|
|
|
|
+ rotation: { x: 0, y: Math.PI, z: 0 },
|
|
|
|
+ scale: 0.01,
|
|
|
|
+ animationName: "parrot_A_"
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ modelName: "Parrot",
|
|
|
|
+ meshName: "mesh_0",
|
|
|
|
+ position: { x: - 2, y: 0, z: 0 },
|
|
|
|
+ rotation: { x: 0, y: Math.PI / 2, z: 0 },
|
|
|
|
+ scale: 0.02,
|
|
|
|
+ animationName: null
|
|
|
|
+ },
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ // The main setup happens here
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ var numLoadedModels = 0;
|
|
|
|
+ initScene();
|
|
|
|
+ initRenderer();
|
|
|
|
+ loadModels();
|
|
|
|
+ animate();
|
|
|
|
+ //////////////////////////////
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ // Function implementations
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ /**
|
|
|
|
+ * Function that starts loading process for the next model in the queue. The loading process is
|
|
|
|
+ * asynchronous: it happens "in the background". Therefore we don't load all the models at once. We load one,
|
|
|
|
+ * wait until it is done, then load the next one. When all models are loaded, we call loadUnits().
|
|
|
|
+ */
|
|
|
|
+ function loadModels() {
|
|
|
|
+
|
|
|
|
+ for ( var i = 0; i < MODELS.length; ++ i ) {
|
|
|
|
+
|
|
|
|
+ var m = MODELS[ i ];
|
|
|
|
+
|
|
|
|
+ loadGltfModel( m, function ( model ) {
|
|
|
|
+
|
|
|
|
+ ++ numLoadedModels;
|
|
|
|
+
|
|
|
|
+ if ( numLoadedModels === MODELS.length ) {
|
|
|
|
+
|
|
|
|
+ console.log( "All models loaded, time to instantiate units..." );
|
|
|
|
+ instantiateUnits();
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Look at UNITS configuration, clone necessary 3D model scenes, place the armatures and meshes in the scene and
|
|
|
|
+ * launch necessary animations
|
|
|
|
+ */
|
|
|
|
+ function instantiateUnits() {
|
|
|
|
+
|
|
|
|
+ var numSuccess = 0;
|
|
|
|
+
|
|
|
|
+ for ( var i = 0; i < UNITS.length; ++ i ) {
|
|
|
|
+
|
|
|
|
+ var u = UNITS[ i ];
|
|
|
|
+ var model = getModelByName( u.modelName );
|
|
|
|
+
|
|
|
|
+ if ( model ) {
|
|
|
|
+
|
|
|
|
+ var clonedScene = THREE.SkeletonUtils.clone( model.scene );
|
|
|
|
+
|
|
|
|
+ if ( clonedScene ) {
|
|
|
|
+
|
|
|
|
+ // Scene is cloned properly, let's find one mesh and launch animation for it
|
|
|
|
+ var clonedMesh = clonedScene.getObjectByName( u.meshName );
|
|
|
|
+
|
|
|
|
+ if ( clonedMesh ) {
|
|
|
|
+
|
|
|
|
+ var mixer = startAnimation( clonedMesh, model.animations, u.animationName );
|
|
|
|
+
|
|
|
|
+ if ( mixer ) {
|
|
|
|
+
|
|
|
|
+ // Save the animation mixer in the list, will need it in the animation loop
|
|
|
|
+ mixers.push( mixer );
|
|
|
|
+ numSuccess ++;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Different models can have different configurations of armatures and meshes. Therefore,
|
|
|
|
+ // We can't set position, scale or rotation to individual mesh objects. Instead we set
|
|
|
|
+ // it to the whole cloned scene and then add the whole scene to the game world
|
|
|
|
+ // Note: this may have weird effects if you have lights or other items in the GLTF file's scene!
|
|
|
|
+ worldScene.add( clonedScene );
|
|
|
|
+
|
|
|
|
+ if ( u.position ) {
|
|
|
|
+
|
|
|
|
+ clonedScene.position.set( u.position.x, u.position.y, u.position.z );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ( u.scale ) {
|
|
|
|
+
|
|
|
|
+ clonedScene.scale.set( u.scale, u.scale, u.scale );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ( u.rotation ) {
|
|
|
|
+
|
|
|
|
+ clonedScene.rotation.x = u.rotation.x;
|
|
|
|
+ clonedScene.rotation.y = u.rotation.y;
|
|
|
|
+ clonedScene.rotation.z = u.rotation.z;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ console.error( "Can not find model", u.modelName );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ console.log( `Successfully instantiated ${numSuccess} units` );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Start animation for a specific mesh object. Find the animation by name in the 3D model's animation array
|
|
|
|
+ * @param skinnedMesh {THREE.SkinnedMesh} The mesh to animate
|
|
|
|
+ * @param animations {Array} Array containing all the animations for this model
|
|
|
|
+ * @param animationName {string} Name of the animation to launch
|
|
|
|
+ * @return {THREE.AnimationMixer} Mixer to be used in the render loop
|
|
|
|
+ */
|
|
|
|
+ function startAnimation( skinnedMesh, animations, animationName ) {
|
|
|
|
+
|
|
|
|
+ var mixer = new THREE.AnimationMixer( skinnedMesh );
|
|
|
|
+ var clip = THREE.AnimationClip.findByName( animations, animationName );
|
|
|
|
+
|
|
|
|
+ if ( clip ) {
|
|
|
|
+
|
|
|
|
+ var action = mixer.clipAction( clip );
|
|
|
|
+ action.play();
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return mixer;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Find a model object by name
|
|
|
|
+ * @param name
|
|
|
|
+ * @returns {object|null}
|
|
|
|
+ */
|
|
|
|
+ function getModelByName( name ) {
|
|
|
|
+
|
|
|
|
+ for ( var i = 0; i < MODELS.length; ++ i ) {
|
|
|
|
+
|
|
|
|
+ if ( MODELS[ i ].name === name ) {
|
|
|
|
+
|
|
|
|
+ return MODELS[ i ];
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return null;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Load a 3D model from a GLTF file. Use the GLTFLoader.
|
|
|
|
+ * @param model {object} Model config, one item from the MODELS array. It will be updated inside the function!
|
|
|
|
+ * @param onLoaded {function} A callback function that will be called when the model is loaded
|
|
|
|
+ */
|
|
|
|
+ function loadGltfModel( model, onLoaded ) {
|
|
|
|
+
|
|
|
|
+ var loader = new THREE.GLTFLoader();
|
|
|
|
+ var modelName = "models/gltf/" + model.name + ".glb";
|
|
|
|
+
|
|
|
|
+ loader.load( modelName, function ( gltf ) {
|
|
|
|
+
|
|
|
|
+ var scene = gltf.scene;
|
|
|
|
+
|
|
|
|
+ model.animations = gltf.animations;
|
|
|
|
+ model.scene = scene;
|
|
|
|
+
|
|
|
|
+ // Enable Shadows
|
|
|
|
+
|
|
|
|
+ gltf.scene.traverse( function ( object ) {
|
|
|
|
+
|
|
|
|
+ if ( object.isMesh ) {
|
|
|
|
+
|
|
|
|
+ object.castShadow = true;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ console.log( "Done loading model", model.name );
|
|
|
|
+
|
|
|
|
+ onLoaded( model );
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Render loop. Renders the next frame of all animations
|
|
|
|
+ */
|
|
|
|
+ function animate() {
|
|
|
|
+
|
|
|
|
+ requestAnimationFrame( animate );
|
|
|
|
+
|
|
|
|
+ // Get the time elapsed since the last frame
|
|
|
|
+
|
|
|
|
+ var mixerUpdateDelta = clock.getDelta();
|
|
|
|
+
|
|
|
|
+ // Update all the animation frames
|
|
|
|
+
|
|
|
|
+ for ( var i = 0; i < mixers.length; ++ i ) {
|
|
|
|
+
|
|
|
|
+ mixers[ i ].update( mixerUpdateDelta );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ renderer.render( worldScene, camera );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ // General Three.JS stuff
|
|
|
|
+ //////////////////////////////
|
|
|
|
+ // This part is not anyhow related to the cloning of models, it's just setting up the scene.
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Initialize ThreeJS scene renderer
|
|
|
|
+ */
|
|
|
|
+ function initRenderer() {
|
|
|
|
+
|
|
|
|
+ var container = document.getElementById( 'container' );
|
|
|
|
+ renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
|
|
+ renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
+ renderer.gammaOutput = true;
|
|
|
|
+ renderer.gammaFactor = 2.2;
|
|
|
|
+ renderer.shadowMap.enabled = true;
|
|
|
|
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
|
|
+ container.appendChild( renderer.domElement );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Initialize ThreeJS Scene
|
|
|
|
+ */
|
|
|
|
+ function initScene() {
|
|
|
|
+
|
|
|
|
+ camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
|
|
|
|
+ camera.position.set( 3, 6, - 10 );
|
|
|
|
+ camera.lookAt( 0, 1, 0 );
|
|
|
|
+
|
|
|
|
+ clock = new THREE.Clock();
|
|
|
|
+
|
|
|
|
+ worldScene = new THREE.Scene();
|
|
|
|
+ worldScene.background = new THREE.Color( 0xa0a0a0 );
|
|
|
|
+ worldScene.fog = new THREE.Fog( 0xa0a0a0, 10, 22 );
|
|
|
|
+
|
|
|
|
+ var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 );
|
|
|
|
+ hemiLight.position.set( 0, 20, 0 );
|
|
|
|
+ worldScene.add( hemiLight );
|
|
|
|
+
|
|
|
|
+ var dirLight = new THREE.DirectionalLight( 0xffffff );
|
|
|
|
+ dirLight.position.set( - 3, 10, - 10 );
|
|
|
|
+ dirLight.castShadow = true;
|
|
|
|
+ dirLight.shadow.camera.top = 10;
|
|
|
|
+ dirLight.shadow.camera.bottom = - 10;
|
|
|
|
+ dirLight.shadow.camera.left = - 10;
|
|
|
|
+ dirLight.shadow.camera.right = 10;
|
|
|
|
+ dirLight.shadow.camera.near = 0.1;
|
|
|
|
+ dirLight.shadow.camera.far = 40;
|
|
|
|
+ worldScene.add( dirLight );
|
|
|
|
+
|
|
|
|
+ // ground
|
|
|
|
+ var groundMesh = new THREE.Mesh(
|
|
|
|
+ new THREE.PlaneBufferGeometry( 40, 40 ),
|
|
|
|
+ new THREE.MeshPhongMaterial( {
|
|
|
|
+ color: 0x999999,
|
|
|
|
+ depthWrite: false
|
|
|
|
+ } )
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ groundMesh.rotation.x = - Math.PI / 2;
|
|
|
|
+ groundMesh.receiveShadow = true;
|
|
|
|
+ worldScene.add( groundMesh );
|
|
|
|
+ window.addEventListener( 'resize', onWindowResize, false );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * A callback that will be called whenever the browser window is resized.
|
|
|
|
+ */
|
|
|
|
+ function onWindowResize() {
|
|
|
|
+
|
|
|
|
+ camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ </script>
|
|
|
|
+
|
|
|
|
+ </body>
|
|
|
|
+
|
|
</html>
|
|
</html>
|