@@ -12,7 +12,7 @@ import * as THREE from "three";
1212import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js" ;
1313import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js" ;
1414import { eulerRomFor } from "posecode-parser" ;
15- import type { PosecodeIR , ReachTarget , PinTarget } from "posecode-parser" ;
15+ import type { PosecodeIR , ReachTarget , PinTarget , GripTarget } from "posecode-parser" ;
1616import { buildMannequin , type Mannequin } from "./mannequin.js" ;
1717import { applyGroundLock as applyGroundLockTo , groundFigure as groundFigureOf } from "./groundlock.js" ;
1818import { buildTimeline , type BuiltTimeline , type PhaseSegment } from "./timeline.js" ;
@@ -27,7 +27,7 @@ import {
2727 type ClipSource ,
2828} from "./clips.js" ;
2929import { depenetrate } from "./depenetrate.js" ;
30- import { alignFloorPalms , levelPlantedFeet } from "./contacts.js" ;
30+ import { alignFloorPalms , levelPlantedFeet , wrapGrip } from "./contacts.js" ;
3131
3232const DEG = Math . PI / 180 ;
3333
@@ -482,6 +482,50 @@ export function createViewer(
482482 }
483483 }
484484
485+ /**
486+ * Bar grips: unlike a pin (body translate only), a grip makes each hand hold
487+ * the bar. (1) Translate the body by the average wrist→anchor delta — the
488+ * authored elbow flex raises the wrists, so the body rises: the pull-up. (2)
489+ * Per-hand arm IK drives each wrist exactly onto its own two-point anchor
490+ * (`bar_left`/`bar_right`), so the hands grip shoulder-width and the arms angle
491+ * naturally instead of pointing straight up. (3) Wrap the fingers round the bar.
492+ */
493+ function applyGrips ( grips : GripTarget [ ] ) : void {
494+ if ( grips . length === 0 ) return ;
495+ const resolveGrip = ( anchor : string , effector : THREE . Object3D ) : THREE . Vector3 | null =>
496+ resolveReachTarget ( anchor , effector ) ??
497+ resolveReachTarget ( anchor . replace ( / _ ( l e f t | r i g h t ) $ / , "" ) , effector ) ;
498+ // 1. Body translate (the vertical pull).
499+ const delta = new THREE . Vector3 ( ) ;
500+ let n = 0 ;
501+ for ( const g of grips ) {
502+ const effectorBone = EFFECTOR_BONE [ g . effector ] ?? g . effector ;
503+ const effector = mannequin . bones . get ( effectorBone ) ;
504+ if ( ! effector ) continue ;
505+ const target = resolveGrip ( g . anchor , effector ) ;
506+ if ( ! target ) continue ;
507+ delta . add ( target . clone ( ) . sub ( effector . getWorldPosition ( new THREE . Vector3 ( ) ) ) ) ;
508+ n ++ ;
509+ }
510+ if ( n > 0 ) {
511+ mannequin . root . position . add ( delta . multiplyScalar ( 1 / n ) ) ;
512+ mannequin . root . updateMatrixWorld ( true ) ;
513+ }
514+ // 2. Per-hand arm IK onto each grip point (ROM-clamped via reachChain).
515+ for ( const g of grips ) {
516+ const effectorBone = EFFECTOR_BONE [ g . effector ] ?? g . effector ;
517+ const effector = mannequin . bones . get ( effectorBone ) ;
518+ if ( ! effector ) continue ;
519+ const target = resolveGrip ( g . anchor , effector ) ;
520+ if ( ! target ) continue ;
521+ const { joints, limits } = reachChain ( effectorBone ) ;
522+ if ( joints . length === 0 ) continue ;
523+ solveCCD ( { joints, limits, effector, target } , 12 ) ;
524+ }
525+ // 3. Finger wrap.
526+ wrapGrip ( mannequin , grips ) ;
527+ }
528+
485529 function frameCamera ( ) : void {
486530 // Auto-frame the figure: fit its bounding box, keep a pleasant angle.
487531 // Include any scene prop too: a pull-up bar sits well above the figure's
@@ -535,6 +579,7 @@ export function createViewer(
535579 depenetrate ( mannequin ) ;
536580 applyGroundLockTo ( mannequin , info . groundLock , frameAnchors ( info . rootYaw , info . rootOffset ) ) ;
537581 applyPins ( info . pins ) ;
582+ applyGrips ( info . grips ) ;
538583 // Reach-IK BEFORE the floor safety clamp. When authored FK pushes a
539584 // reaching limb through the floor (cobra: prone + shoulders flex 50),
540585 // the limb must bend to meet the floor. Running reaches after the clamp
0 commit comments