|
@@ -8,7 +8,7 @@
|
|
|
</head>
|
|
|
<body>
|
|
|
<div id="info">
|
|
|
- This demo shows how to clone a skinned mesh using <strong>SkeletonUtils.clone()</strong><br/>
|
|
|
+ This demo shows the usage of <strong>SkeletonUtils.clone()</strong> and how to setup a shared skeleton.<br/>
|
|
|
Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>.
|
|
|
</div>
|
|
|
|
|
@@ -27,11 +27,16 @@
|
|
|
|
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
|
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
|
|
|
+ import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
|
|
|
|
|
- let camera, scene, renderer;
|
|
|
- let clock;
|
|
|
+ let camera, scene, renderer, clock;
|
|
|
+ let model, animations;
|
|
|
|
|
|
- const mixers = [];
|
|
|
+ const mixers = [], objects = [];
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ sharedSkeleton: false
|
|
|
+ };
|
|
|
|
|
|
init();
|
|
|
animate();
|
|
@@ -75,32 +80,16 @@
|
|
|
const loader = new GLTFLoader();
|
|
|
loader.load( 'models/gltf/Soldier.glb', function ( gltf ) {
|
|
|
|
|
|
- gltf.scene.traverse( function ( object ) {
|
|
|
+ model = gltf.scene;
|
|
|
+ animations = gltf.animations;
|
|
|
+
|
|
|
+ model.traverse( function ( object ) {
|
|
|
|
|
|
if ( object.isMesh ) object.castShadow = true;
|
|
|
|
|
|
} );
|
|
|
|
|
|
- const model1 = SkeletonUtils.clone( gltf.scene );
|
|
|
- const model2 = SkeletonUtils.clone( gltf.scene );
|
|
|
- const model3 = SkeletonUtils.clone( gltf.scene );
|
|
|
-
|
|
|
- const mixer1 = new THREE.AnimationMixer( model1 );
|
|
|
- const mixer2 = new THREE.AnimationMixer( model2 );
|
|
|
- const mixer3 = new THREE.AnimationMixer( model3 );
|
|
|
-
|
|
|
- mixer1.clipAction( gltf.animations[ 0 ] ).play(); // idle
|
|
|
- mixer2.clipAction( gltf.animations[ 1 ] ).play(); // run
|
|
|
- mixer3.clipAction( gltf.animations[ 3 ] ).play(); // walk
|
|
|
-
|
|
|
- model1.position.x = - 2;
|
|
|
- model2.position.x = 0;
|
|
|
- model3.position.x = 2;
|
|
|
-
|
|
|
- scene.add( model1, model2, model3 );
|
|
|
- mixers.push( mixer1, mixer2, mixer3 );
|
|
|
-
|
|
|
- animate();
|
|
|
+ setupDefaultScene();
|
|
|
|
|
|
} );
|
|
|
|
|
@@ -112,6 +101,129 @@
|
|
|
|
|
|
window.addEventListener( 'resize', onWindowResize );
|
|
|
|
|
|
+ const gui = new GUI();
|
|
|
+
|
|
|
+ gui.add( params, 'sharedSkeleton' ).onChange( function () {
|
|
|
+
|
|
|
+ clearScene();
|
|
|
+
|
|
|
+ if ( params.sharedSkeleton === true ) {
|
|
|
+
|
|
|
+ setupSharedSkeletonScene();
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ setupDefaultScene();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } );
|
|
|
+ gui.open();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function clearScene() {
|
|
|
+
|
|
|
+ for ( const mixer of mixers ) {
|
|
|
+
|
|
|
+ mixer.stopAllAction();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ mixers.length = 0;
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ for ( const object of objects ) {
|
|
|
+
|
|
|
+ scene.remove( object );
|
|
|
+
|
|
|
+ scene.traverse( function ( child ) {
|
|
|
+
|
|
|
+ if ( child.isSkinnedMesh ) child.skeleton.dispose();
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function setupDefaultScene() {
|
|
|
+
|
|
|
+ // three cloned models with independent skeletons.
|
|
|
+ // each model can have its own animation state
|
|
|
+
|
|
|
+ const model1 = SkeletonUtils.clone( model );
|
|
|
+ const model2 = SkeletonUtils.clone( model );
|
|
|
+ const model3 = SkeletonUtils.clone( model );
|
|
|
+
|
|
|
+ model1.position.x = - 2;
|
|
|
+ model2.position.x = 0;
|
|
|
+ model3.position.x = 2;
|
|
|
+
|
|
|
+ const mixer1 = new THREE.AnimationMixer( model1 );
|
|
|
+ const mixer2 = new THREE.AnimationMixer( model2 );
|
|
|
+ const mixer3 = new THREE.AnimationMixer( model3 );
|
|
|
+
|
|
|
+ mixer1.clipAction( animations[ 0 ] ).play(); // idle
|
|
|
+ mixer2.clipAction( animations[ 1 ] ).play(); // run
|
|
|
+ mixer3.clipAction( animations[ 3 ] ).play(); // walk
|
|
|
+
|
|
|
+ scene.add( model1, model2, model3 );
|
|
|
+
|
|
|
+ objects.push( model1, model2, model3 );
|
|
|
+ mixers.push( mixer1, mixer2, mixer3 );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function setupSharedSkeletonScene() {
|
|
|
+
|
|
|
+ // three cloned models with a single shared skeleton.
|
|
|
+ // all models share the same animation state
|
|
|
+
|
|
|
+ const sharedModel = SkeletonUtils.clone( model );
|
|
|
+ const shareSkinnedMesh = sharedModel.getObjectByName( 'vanguard_Mesh' );
|
|
|
+ const sharedSkeleton = shareSkinnedMesh.skeleton;
|
|
|
+ const sharedParentBone = sharedModel.getObjectByName( 'mixamorigHips' );
|
|
|
+ scene.add( sharedParentBone ); // the bones need to be in the scene for the animation to work
|
|
|
+
|
|
|
+ const model1 = shareSkinnedMesh.clone();
|
|
|
+ const model2 = shareSkinnedMesh.clone();
|
|
|
+ const model3 = shareSkinnedMesh.clone();
|
|
|
+
|
|
|
+ model1.bindMode = THREE.DetachedBindMode;
|
|
|
+ model2.bindMode = THREE.DetachedBindMode;
|
|
|
+ model3.bindMode = THREE.DetachedBindMode;
|
|
|
+
|
|
|
+ const identity = new THREE.Matrix4();
|
|
|
+
|
|
|
+ model1.bind( sharedSkeleton, identity );
|
|
|
+ model2.bind( sharedSkeleton, identity );
|
|
|
+ model3.bind( sharedSkeleton, identity );
|
|
|
+
|
|
|
+ model1.position.x = - 2;
|
|
|
+ model2.position.x = 0;
|
|
|
+ model3.position.x = 2;
|
|
|
+
|
|
|
+ // apply transformation from the glTF asset
|
|
|
+
|
|
|
+ model1.scale.setScalar( 0.01 );
|
|
|
+ model1.rotation.x = - Math.PI * 0.5;
|
|
|
+ model2.scale.setScalar( 0.01 );
|
|
|
+ model2.rotation.x = - Math.PI * 0.5;
|
|
|
+ model3.scale.setScalar( 0.01 );
|
|
|
+ model3.rotation.x = - Math.PI * 0.5;
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ const mixer = new THREE.AnimationMixer( sharedParentBone );
|
|
|
+ mixer.clipAction( animations[ 1 ] ).play();
|
|
|
+
|
|
|
+ scene.add( sharedParentBone, model1, model2, model3 );
|
|
|
+
|
|
|
+ objects.push( sharedParentBone, model1, model2, model3 );
|
|
|
+ mixers.push( mixer );
|
|
|
+
|
|
|
}
|
|
|
|
|
|
function onWindowResize() {
|