WebXR extension / Hands Animation
Video Overview
Introduction
A good practice is to show a pre-recorded hand animation to familiarize users with the correct way to perform each gesture. Pre-recorded gesture animations can also be useful for tutorials.
Capturing
For capture, use the capabilities of the HandTracking wrapper.
export interface IHandTrackingXRFeatEnv extends IXRFeatEnv<WebXRHandTracking> {
captureHandsAnimation( frames:number, mode:TRecordMode, delayMS?:number):Promise<THandsAnimationData | undefined>; // ... other functions}Example of capturing:
// 1. Create service// 2. Init service// 3. Go immersive
// 4. Extract feature Envconst ftHandEnv = xrService.extractFeatureEnv('HAND_TRACKING');
// Recording will start after a 2s pause at 360 frames.const animation = await ftHandEnv(360, 'both', 2000);!!! Using binary packaging for the captured data is a great idea, but it is not an extension feature. Even a short recording can take up 200-300 KB, so it is important to be mindful of animation file sizes.
Playback
For playback, there is a special class called AnimatedHand:
export interface IAnimatedHand { readonly isReady:boolean; readonly isPaused:boolean; readonly isPlayed:boolean; readonly root:TransformNode; readonly mesh:AbstractMesh; init(handDesc:Partial<TAnimatedHandDescription>):void; loadModelFromFile(url:string):Promise<boolean>; setPosition(position:Vector3):void; setRotation(rotation:Quaternion):void; setScaling(scaling:number):void; setEnabled(val:boolean):void; play( data:THandsAnimationData, loop?:boolean, onFrame?:(frameNumber:number)=>void, onStarted?:()=>void, onEnded?:()=>void ):Promise<void>; material:Nullable<Material>; stop():void; goto(frame:number):void; pause(val:boolean):void; readonly isDisposed:boolean; dispose(disposeMaterialAndTextures?:boolean):void;}This class allows you to load the required hand model, assign a material, and set its position and orientation in space. You can also implement all this logic yourself with a class specifically designed for playback. We'll demonstrate both approaches:
Example 1: Playback with AnimatedHand
// 1. Upload a data file or get the data in another wayconst animationData = animationDataProvider.get('conduct_an_orchestra');
// 2. Create an AnimatedHand instance (or two if we have animation for both hands)const handR = new AnimatedHand('right', scene);
// 3. Here setup the instance (set material, mesh, positions, etc)
// 4. Start playback// It possible use it in async-await or in callback styleawait handR.play( animationData, true, showProgressFunction)Example 2: Playing the animation at a lower level
export interface IAnimatedHandsPlayer { init(scene:Scene):void; readonly isPlayed: boolean; readonly isPaused: boolean; play( hand:XRHandedness, positions:number[][], rotations:number[][], skeleton:Skeleton, loop?:boolean, onFrame?:(frameNumber:number)=>void ):Promise<void>; goto(frame:number):void; stop():void; pause(val:boolean):void; dispose(): void;}
// 1. Upload a data file or get the data in another wayconst animationData = animationDataProvider.get('conduct_an_orchestra');
// Parsing animation data here
// 2. Init playerconst player = new AnimatedHandsPlayer();player.init(scene);
// 3. Start playbackawait player.play( 'right', // Bones positions from animationData positions, // Bones quaternions rotations, // Skeleton, skeleton, false);* Warning: To use this file as JSON, you need to add a 0 before numbers starting with '.' or '-.'