@@ -351,6 +351,9 @@ <h1>Anima<span>Sync</span></h1>
351351let idleActionV2 = null , speakingActionV2 = null ;
352352let isSpeaking = false ;
353353let crossFadeProgress = 0 ;
354+ let useVrmMode = false ;
355+
356+ const VRM_EXPRESSIONS = [ 'aa' , 'ih' , 'ou' , 'ee' , 'oh' , 'happy' , 'angry' , 'sad' , 'relaxed' , 'surprised' , 'neutral' , 'blinkLeft' , 'blinkRight' , 'lookUp' , 'lookDown' , 'lookLeft' , 'lookRight' , 'surprised' ] ;
354357
355358async function loadVRMAFromBytes ( bytes ) {
356359 const blob = new Blob ( [ bytes ] , { type : 'application/octet-stream' } ) ;
@@ -381,6 +384,17 @@ <h1>Anima<span>Sync</span></h1>
381384 }
382385 } catch ( e ) { console . warn ( 'VRMA skip:' , e . message ) ; }
383386
387+ // Auto-detect VRM expression mode (first load only)
388+ if ( ! useVrmMode && vrm . expressionManager ) {
389+ const exprNames = Object . keys ( vrm . expressionManager . expressionMap || { } ) ;
390+ const arkitNames = [ 'jawOpen' , 'mouthFunnel' , 'mouthPucker' , 'eyeBlinkLeft' , 'eyeBlinkRight' ] ;
391+ const hasArkit = arkitNames . filter ( n => exprNames . includes ( n ) ) . length >= 3 ;
392+ const vrmPresetNames = [ 'aa' , 'ih' , 'ou' , 'ee' , 'oh' , 'happy' , 'angry' , 'sad' , 'relaxed' , 'surprised' ] ;
393+ const hasVrmPreset = vrmPresetNames . filter ( n => exprNames . includes ( n ) ) . length >= 3 ;
394+ if ( ! hasArkit && hasVrmPreset ) useVrmMode = true ;
395+ console . log ( `[VRM] Mode: ${ useVrmMode ? 'VRM 18-dim' : 'ARKit 52-dim' } (arkit=${ hasArkit } , vrm=${ hasVrmPreset } )` ) ;
396+ }
397+
384398 return { vrm, mixer, idleAction : idleAct , speakingAction : speakAct } ;
385399}
386400
@@ -403,7 +417,8 @@ <h1>Anima<span>Sync</span></h1>
403417
404418function resetVRM ( vrm ) {
405419 if ( ! vrm ?. expressionManager ) return ;
406- for ( const name of ARKIT_52 ) vrm . expressionManager . setValue ( name , 0 ) ;
420+ const names = useVrmMode ? VRM_EXPRESSIONS : ARKIT_52 ;
421+ for ( const name of names ) vrm . expressionManager . setValue ( name , 0 ) ;
407422}
408423
409424// VRM drop handler (drops onto either pane, loads into both)
@@ -522,10 +537,10 @@ <h1>Anima<span>Sync</span></h1>
522537 document . getElementById ( 's-v2-wasm' ) . textContent = ( e2 / 1000 ) . toFixed ( 2 ) + 's' ;
523538 document . getElementById ( 's-v2-rtf' ) . textContent = ( e2 / 1000 / dur2 ) . toFixed ( 2 ) + 'x' ;
524539
525- // Queue frames
540+ // Queue frames (arkit + vrm for each)
526541 queueV1 . length = 0 ; queueV2 . length = 0 ;
527- for ( let i = 0 ; i < r1 . frame_count ; i ++ ) queueV1 . push ( lsV1 . getFrame ( r1 , i ) ) ;
528- for ( let i = 0 ; i < r2 . frame_count ; i ++ ) queueV2 . push ( lsV2 . getFrame ( r2 , i ) ) ;
542+ for ( let i = 0 ; i < r1 . frame_count ; i ++ ) queueV1 . push ( { arkit : lsV1 . getFrame ( r1 , i ) , vrm : lsV1 . getVrmFrame ?. ( r1 , i ) } ) ;
543+ for ( let i = 0 ; i < r2 . frame_count ; i ++ ) queueV2 . push ( { arkit : lsV2 . getFrame ( r2 , i ) , vrm : lsV2 . getVrmFrame ?. ( r2 , i ) } ) ;
529544
530545 transitionToSpeaking ( ) ;
531546
@@ -596,8 +611,8 @@ <h1>Anima<span>Sync</span></h1>
596611 lsV1 . processAudioChunk ( chunk ) ,
597612 lsV2 . processAudioChunk ( chunk ) ,
598613 ] ) ;
599- if ( r1 ) for ( let i = 0 ; i < r1 . frame_count ; i ++ ) queueV1 . push ( lsV1 . getFrame ( r1 , i ) ) ;
600- if ( r2 ) for ( let i = 0 ; i < r2 . frame_count ; i ++ ) queueV2 . push ( lsV2 . getFrame ( r2 , i ) ) ;
614+ if ( r1 ) for ( let i = 0 ; i < r1 . frame_count ; i ++ ) queueV1 . push ( { arkit : lsV1 . getFrame ( r1 , i ) , vrm : lsV1 . getVrmFrame ?. ( r1 , i ) } ) ;
615+ if ( r2 ) for ( let i = 0 ; i < r2 . frame_count ; i ++ ) queueV2 . push ( { arkit : lsV2 . getFrame ( r2 , i ) , vrm : lsV2 . getVrmFrame ?. ( r2 , i ) } ) ;
601616 } ;
602617
603618 micActive = true ;
@@ -608,10 +623,16 @@ <h1>Anima<span>Sync</span></h1>
608623// ================================================================
609624// Apply blendshapes
610625// ================================================================
611- function applyToVRM ( vrm , frame ) {
626+ function applyToVRM ( vrm , frameData ) {
612627 if ( ! vrm ?. expressionManager ) return ;
613- for ( let i = 0 ; i < Math . min ( frame . length , ARKIT_52 . length ) ; i ++ ) {
614- vrm . expressionManager . setValue ( ARKIT_52 [ i ] , frame [ i ] ) ;
628+ if ( useVrmMode && frameData . vrm ) {
629+ for ( let i = 0 ; i < VRM_EXPRESSIONS . length ; i ++ ) {
630+ vrm . expressionManager . setValue ( VRM_EXPRESSIONS [ i ] , frameData . vrm [ i ] || 0 ) ;
631+ }
632+ } else {
633+ for ( let i = 0 ; i < Math . min ( frameData . arkit . length , ARKIT_52 . length ) ; i ++ ) {
634+ vrm . expressionManager . setValue ( ARKIT_52 [ i ] , frameData . arkit [ i ] ) ;
635+ }
615636 }
616637}
617638
@@ -628,8 +649,8 @@ <h1>Anima<span>Sync</span></h1>
628649 ft += dt ;
629650
630651 if ( ft >= FI ) {
631- if ( queueV1 . length > 0 ) { const f = queueV1 . shift ( ) ; applyToVRM ( vrmV1 , f ) ; updateBars ( barsV1 , f ) ; }
632- if ( queueV2 . length > 0 ) { const f = queueV2 . shift ( ) ; applyToVRM ( vrmV2 , f ) ; updateBars ( barsV2 , f ) ; }
652+ if ( queueV1 . length > 0 ) { const f = queueV1 . shift ( ) ; applyToVRM ( vrmV1 , f ) ; updateBars ( barsV1 , f . arkit ) ; }
653+ if ( queueV2 . length > 0 ) { const f = queueV2 . shift ( ) ; applyToVRM ( vrmV2 , f ) ; updateBars ( barsV2 , f . arkit ) ; }
633654 ft = 0 ;
634655 }
635656
0 commit comments