forked from mrdoob/three.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwebgl_animation_multiple.html
408 lines (294 loc) · 10.7 KB
/
webgl_animation_multiple.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
<!DOCTYPE html>
<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
//////////////////////////////
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>