From 81d91b94f3ce24cb53e9b17f866556080ca58a6e Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 17 Oct 2023 11:25:58 +0200 Subject: [PATCH] Example: Add shared skeleton setup to `webgl_animation_multiple`. (#26995) --- examples/webgl_animation_multiple.html | 162 +++++++++++++++++++++---- 1 file changed, 137 insertions(+), 25 deletions(-) diff --git a/examples/webgl_animation_multiple.html b/examples/webgl_animation_multiple.html index cd62fc6237c395..325467994f0d96 100644 --- a/examples/webgl_animation_multiple.html +++ b/examples/webgl_animation_multiple.html @@ -8,7 +8,7 @@
- This demo shows how to clone a skinned mesh using SkeletonUtils.clone()
+ This demo shows the usage of SkeletonUtils.clone() and how to setup a shared skeleton.
Soldier model from https://www.mixamo.com.
@@ -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() {