@@ -22,10 +22,14 @@ Rotations are applied onto a flat neutral hand pose.
2222Rotations are applied through forward kinematics.
2323Format: {"joint-name":[x,y,z]} where x/y/z are euler angle radians.
2424For long fingers:
25- x: flexion/extension. Negative curls toward palm, positive extends away.
26- y: abduction/adduction. Negative spreads toward thumb, positive away.
27- z: twist. Negative twists away from thumb, positive toward thumb.
28- Prefer not to change the thumb metacarpal joint.
25+ x: flexion/extension. Positive flexes toward palm, negative extends away.
26+ y: abduction/adduction. Positive spreads away from the middle-finger axis, negative adducts toward it.
27+ z: axial roll. Positive rolls toward thumb, negative rolls away from thumb.
28+ For the middle finger, y is radial/ulnar deviation: positive moves toward index/thumb, negative toward ring/pinky.
29+ For the thumb:
30+ x: positive flexes across palm, negative extends/repositions.
31+ y: positive abducts away from palm, negative adducts back toward palm.
32+ z: positive rolls into opposition/internal rotation, negative repositions/external rotation away.
2933Include every non-tip WebXR joint listed below. Use [0,0,0] for neutral joints:
3034${ ROTATION_JOINT_NAMES . join ( ', ' ) }
3135` ;
@@ -73,6 +77,15 @@ function cleanRotationsForJson(rotations) {
7377 return cleanRotations ;
7478}
7579
80+ function copyRotations ( target , source ) {
81+ for ( const jointName of ROTATION_JOINT_NAMES ) {
82+ const rotation = source [ jointName ] ?? [ 0 , 0 , 0 ] ;
83+ target [ jointName ] [ 0 ] = rotation [ 0 ] ;
84+ target [ jointName ] [ 1 ] = rotation [ 1 ] ;
85+ target [ jointName ] [ 2 ] = rotation [ 2 ] ;
86+ }
87+ }
88+
7689function cleanJointsForJson ( joints ) {
7790 return joints . map ( ( joint ) => ( {
7891 t : joint . t . map ( toFixedNumber ) ,
@@ -380,6 +393,17 @@ class ManualSimHandScene extends xb.Script {
380393 }
381394}
382395
396+ class DisplayedPoseSync extends xb . Script {
397+ constructor ( syncDisplayedPose ) {
398+ super ( ) ;
399+ this . _syncDisplayedPose = syncDisplayedPose ;
400+ }
401+
402+ update ( ) {
403+ this . _syncDisplayedPose ( ) ;
404+ }
405+ }
406+
383407class GestureHUD extends xb . Script {
384408 init ( ) {
385409 this . _active = {
@@ -498,19 +522,35 @@ async function start() {
498522
499523 let updateJsonViews = ( ) => { } ;
500524
525+ const getActiveHandRotations = ( ) => {
526+ const activeHandIndex =
527+ xb . core . simulator . controls . simulatorControllerState
528+ . currentControllerIndex ;
529+ return activeHandIndex === 0
530+ ? xb . core . simulator . hands . leftHandCurrentRotations
531+ : xb . core . simulator . hands . rightHandCurrentRotations ;
532+ } ;
533+
534+ const getDisplayedJoints = ( bones ) =>
535+ bones . map ( ( bone ) => ( {
536+ t : bone . position . toArray ( ) ,
537+ r : bone . quaternion . toArray ( ) ,
538+ s : bone . scale . toArray ( ) ,
539+ } ) ) ;
540+
501541 const applyHandRotations = ( ) => {
502542 xb . core . simulator . hands . setLeftHandRotations ( handRotations ) ;
503543 xb . core . simulator . hands . setRightHandRotations ( handRotations ) ;
504544 updateJsonViews ( ) ;
505545 } ;
506546
507- const syncControlsToRotations = ( ) => {
547+ const syncControlsToRotations = ( rotations = getActiveHandRotations ( ) ) => {
508548 for ( const input of document . querySelectorAll (
509549 '.manual-sim-hand-slider input[type="range"]'
510550 ) ) {
511551 const axisIndex = [ 'x' , 'y' , 'z' ] . indexOf ( input . dataset . axis ) ;
512552 const degrees = Math . round (
513- handRotations [ input . dataset . joint ] [ axisIndex ] / DEG_TO_RAD
553+ ( rotations [ input . dataset . joint ] ?. [ axisIndex ] ?? 0 ) / DEG_TO_RAD
514554 ) ;
515555 input . value = String ( degrees ) ;
516556 input . nextElementSibling . value = String ( degrees ) ;
@@ -519,27 +559,36 @@ async function start() {
519559
520560 updateJsonViews = createSidebar (
521561 ( jointName , axis , value ) => {
562+ copyRotations ( handRotations , getActiveHandRotations ( ) ) ;
522563 handRotations [ jointName ] [ [ 'x' , 'y' , 'z' ] . indexOf ( axis ) ] = value ;
523564 applyHandRotations ( ) ;
524565 } ,
525566 ( ) => {
526567 for ( const rotation of Object . values ( handRotations ) ) {
527568 rotation . fill ( 0 ) ;
528569 }
529- syncControlsToRotations ( ) ;
570+ syncControlsToRotations ( handRotations ) ;
530571 applyHandRotations ( ) ;
531572 } ,
532573 ( ) => ( {
533574 raw : {
534- left : cleanJointsForJson ( xb . core . simulator . hands . leftHandTargetJoints ) ,
575+ left : cleanJointsForJson (
576+ getDisplayedJoints ( xb . core . simulator . hands . leftHandBones )
577+ ) ,
535578 right : cleanJointsForJson (
536- xb . core . simulator . hands . rightHandTargetJoints
579+ getDisplayedJoints ( xb . core . simulator . hands . rightHandBones )
537580 ) ,
538581 } ,
539- rotations : cleanRotationsForJson ( handRotations ) ,
582+ rotations : cleanRotationsForJson ( getActiveHandRotations ( ) ) ,
540583 } )
541584 ) ;
542585
586+ const syncDisplayedPose = ( ) => {
587+ syncControlsToRotations ( ) ;
588+ updateJsonViews ( ) ;
589+ } ;
590+ xb . add ( new DisplayedPoseSync ( syncDisplayedPose ) ) ;
591+
543592 createPromptBubble ( async ( description ) => {
544593 xb . core . options . ai . gemini . config = {
545594 responseMimeType : 'application/json' ,
@@ -558,7 +607,7 @@ async function start() {
558607 handRotations [ jointName ] [ 1 ] = generatedRotation [ 1 ] ;
559608 handRotations [ jointName ] [ 2 ] = generatedRotation [ 2 ] ;
560609 }
561- syncControlsToRotations ( ) ;
610+ syncControlsToRotations ( handRotations ) ;
562611 applyHandRotations ( ) ;
563612 } ) ;
564613}
0 commit comments