@@ -12,14 +12,7 @@ export function alignFloorPalms(
1212 reaches : readonly ReachTarget [ ] ,
1313 pins : readonly PinTarget [ ] ,
1414) : void {
15- const sides = new Set < "left" | "right" > ( ) ;
16- const collect = ( effector : string , target : string ) => {
17- if ( target !== "floor" ) return ;
18- if ( effector === "hands" || effector === "hand_left" ) sides . add ( "left" ) ;
19- if ( effector === "hands" || effector === "hand_right" ) sides . add ( "right" ) ;
20- } ;
21- reaches . forEach ( ( r ) => collect ( r . effector , r . target ) ) ;
22- pins . forEach ( ( p ) => collect ( p . effector , p . anchor ) ) ;
15+ const sides = contactSides ( reaches , pins , ( target ) => target === "floor" , "hand" ) ;
2316
2417 for ( const side of sides ) {
2518 const wrist = m . bones . get ( `wrist_${ side } ` ) ;
@@ -31,8 +24,68 @@ export function alignFloorPalms(
3124 const current = localNormal . applyQuaternion ( world ) . normalize ( ) ;
3225 const correction = new THREE . Quaternion ( ) . setFromUnitVectors ( current , DOWN ) ;
3326 const desiredWorld = correction . multiply ( world ) ;
34- const parentWorld = wrist . parent . getWorldQuaternion ( new THREE . Quaternion ( ) ) ;
35- wrist . quaternion . copy ( parentWorld . invert ( ) . multiply ( desiredWorld ) ) ;
27+ setWorldQuaternion ( wrist , desiredWorld ) ;
28+ }
29+ if ( sides . size > 0 ) m . root . updateMatrixWorld ( true ) ;
30+ }
31+
32+ /**
33+ * Keep contacting feet flat and facing with the body. The ankle joint remains
34+ * in place, so hip/knee motion and weight shift are preserved; only the sole's
35+ * terminal orientation is corrected.
36+ */
37+ export function alignFloorSoles (
38+ m : Mannequin ,
39+ groundLock : readonly string [ ] ,
40+ reaches : readonly ReachTarget [ ] = [ ] ,
41+ pins : readonly PinTarget [ ] = [ ] ,
42+ ) : void {
43+ const sides = contactSides ( reaches , pins , ( target ) => target === "floor" , "foot" ) ;
44+ if ( groundLock . includes ( "feet" ) ) {
45+ sides . add ( "left" ) ;
46+ sides . add ( "right" ) ;
47+ }
48+ if ( sides . size === 0 ) return ;
49+
50+ const rootForward = FORWARD . clone ( ) . applyQuaternion (
51+ m . root . getWorldQuaternion ( new THREE . Quaternion ( ) ) ,
52+ ) ;
53+ rootForward . y = 0 ;
54+ if ( rootForward . lengthSq ( ) < 1e-8 ) rootForward . copy ( FORWARD ) ;
55+ rootForward . normalize ( ) ;
56+ const worldX = UP . clone ( ) . cross ( rootForward ) . normalize ( ) ;
57+ const desiredWorld = new THREE . Quaternion ( ) . setFromRotationMatrix (
58+ new THREE . Matrix4 ( ) . makeBasis ( worldX , UP , rootForward ) ,
59+ ) ;
60+ for ( const side of sides ) {
61+ const ankle = m . bones . get ( `ankle_${ side } ` ) ;
62+ if ( ankle ) setWorldQuaternion ( ankle , desiredWorld ) ;
63+ }
64+ m . root . updateMatrixWorld ( true ) ;
65+ }
66+
67+ /**
68+ * Orient bar-contacting wrists as an overhand grip: fingers point up toward
69+ * the bar and the palm faces away from the body. Finger curl remains authored
70+ * independently, so this composes with grip strength / release animation.
71+ */
72+ export function alignBarGrips (
73+ m : Mannequin ,
74+ reaches : readonly ReachTarget [ ] ,
75+ pins : readonly PinTarget [ ] ,
76+ ) : void {
77+ const sides = contactSides ( reaches , pins , ( target ) => target === "bar" , "hand" ) ;
78+ for ( const side of sides ) {
79+ const wrist = m . bones . get ( `wrist_${ side } ` ) ;
80+ if ( ! wrist ) continue ;
81+ // Local palm normal is mirrored X; local -Y follows wrist→fingers.
82+ const worldX = side === "left" ? FORWARD . clone ( ) : FORWARD . clone ( ) . negate ( ) ;
83+ const worldY = UP . clone ( ) . negate ( ) ;
84+ const worldZ = worldX . clone ( ) . cross ( worldY ) . normalize ( ) ;
85+ const desiredWorld = new THREE . Quaternion ( ) . setFromRotationMatrix (
86+ new THREE . Matrix4 ( ) . makeBasis ( worldX , worldY , worldZ ) ,
87+ ) ;
88+ setWorldQuaternion ( wrist , desiredWorld ) ;
3689 }
3790 if ( sides . size > 0 ) m . root . updateMatrixWorld ( true ) ;
3891}
0 commit comments