@@ -17,11 +17,10 @@ import { 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" ;
1919import { solveCCD , type JointLimits } from "./ik.js" ;
20- import { buildProps , syncPropAttachments , type PropScene } from "./props.js" ;
20+ import { buildProps , type PropScene } from "./props.js" ;
2121import { loadCharacter , type Character } from "./character.js" ;
2222import {
2323 loadClipSource ,
24- selectMotionClip ,
2524 retargetMocapClip ,
2625 createClipLayer ,
2726 type ClipLayer ,
@@ -83,14 +82,6 @@ export interface ViewerOptions {
8382 * skeleton, rebuilt to the character's exact proportions (see character.ts).
8483 */
8584 characterUrl ?: string ;
86- /**
87- * Show the procedural figure while `characterUrl` loads. Defaults to true
88- * for embeds and offline-friendly consumers. Set false when a brief empty
89- * stage is preferable to flashing the procedural figure before the skinned
90- * character appears. The procedural figure is still revealed if loading
91- * fails, so the viewer never remains permanently blank.
92- */
93- showProceduralWhileLoading ?: boolean ;
9485 /**
9586 * Mocap clip library: clip name (as written in a document's `clip "<name>"`
9687 * directive) → FBX/GLB asset URL. When a loaded document names a clip found
@@ -100,6 +91,13 @@ export interface ViewerOptions {
10091 * the procedural keyframes as always, so clips can never blank a movement.
10192 */
10293 clips ?: Record < string , string > ;
94+ /**
95+ * Keep the procedural figure visible while a skinned `characterUrl` loads.
96+ * This viewer always shows the procedural figure during load (and on load
97+ * failure), so the flag is accepted for API compatibility; the default
98+ * behavior already matches `true`.
99+ */
100+ showProceduralWhileLoading ?: boolean ;
103101}
104102
105103export function createViewer (
@@ -182,9 +180,6 @@ export function createViewer(
182180
183181 let mannequin : Mannequin = buildMannequin ( ) ;
184182 enableShadows ( mannequin . root ) ;
185- if ( opts . characterUrl && opts . showProceduralWhileLoading === false ) {
186- setMannequinMeshesVisible ( mannequin . root , false ) ;
187- }
188183 scene . add ( mannequin . root ) ;
189184
190185 // Skinned character layer (optional). While loading (and on failure) the
@@ -380,17 +375,6 @@ export function createViewer(
380375 foot_right : "ankle_right" ,
381376 } ;
382377
383- function contactBoneIds ( groundLock : readonly string [ ] , pins : readonly PinTarget [ ] ) : string [ ] {
384- const ids = new Set < string > ( ) ;
385- for ( const group of groundLock ) {
386- if ( group === "hands" ) { ids . add ( "wrist_left" ) ; ids . add ( "wrist_right" ) ; }
387- if ( group === "forearms" ) { ids . add ( "elbow_left" ) ; ids . add ( "elbow_right" ) ; }
388- if ( group === "feet" ) { ids . add ( "ankle_left" ) ; ids . add ( "ankle_right" ) ; }
389- }
390- for ( const pin of pins ) ids . add ( EFFECTOR_BONE [ pin . effector ] ?? pin . effector ) ;
391- return [ ...ids ] ;
392- }
393-
394378 /**
395379 * The rotatable joint chain (proximal → distal) that moves an effector, with
396380 * each joint's ROM expressed as local Euler limits for the constrained solve.
@@ -455,13 +439,7 @@ export function createViewer(
455439 p . y = Number . isFinite ( box . min . y ) ? Math . max ( 0 , p . y - box . min . y ) : 0 ;
456440 return p ;
457441 }
458- const side = effector . name . endsWith ( "_left" )
459- ? "left"
460- : effector . name . endsWith ( "_right" )
461- ? "right"
462- : null ;
463- const anchor = ( side ? propAnchors . get ( `${ target } .${ side } ` ) : undefined )
464- ?? propAnchors . get ( target ) ;
442+ const anchor = propAnchors . get ( target ) ;
465443 if ( anchor ) return anchor . clone ( ) ;
466444 const bone = mannequin . bones . get ( target ) ;
467445 if ( bone ) return bone . getWorldPosition ( new THREE . Vector3 ( ) ) ;
@@ -588,10 +566,8 @@ export function createViewer(
588566 }
589567
590568 function frame ( ) : void {
591- let activeContactBones : string [ ] = [ ] ;
592569 if ( timeline ) {
593570 const info = timeline . sample ( time , mannequin . bones ) ;
594- activeContactBones = contactBoneIds ( info . groundLock , info . pins ) ;
595571 // Life layer rides on wall-clock time (not timeline time) so the figure
596572 // keeps breathing and blinking while paused or scrubbing.
597573 applyLife ( performance . now ( ) / 1000 ) ;
@@ -613,7 +589,6 @@ export function createViewer(
613589 // Self-collision: nudge limbs out of the body BEFORE contact solving so
614590 // ground-lock and pins see the corrected pose (same order as load()).
615591 depenetrate ( mannequin ) ;
616- alignFloorSoles ( mannequin , info . groundLock , info . reaches , info . pins ) ;
617592 applyGroundLockTo ( mannequin , info . groundLock , frameAnchors ( info . rootYaw , info . rootOffset ) ) ;
618593 applyPins ( info . pins ) ;
619594 applyGrips ( info . grips ) ;
@@ -664,15 +639,8 @@ export function createViewer(
664639 const gap = clipTargetWeight - clipWeight ;
665640 clipWeight += Math . sign ( gap ) * Math . min ( Math . abs ( gap ) , step ) ;
666641 clipLayer . apply ( time , clipWeight ) ;
667- if ( clipWeight > 0 ) {
668- character . group . updateMatrixWorld ( true ) ;
669- // Mocap is deliberately layered after procedural posing. Re-apply the
670- // final contact positions/orientations so the visible mesh cannot skate
671- // away from feet/hands the driver already solved.
672- character . correctContacts ( mannequin , activeContactBones ) ;
673- }
642+ if ( clipWeight > 0 ) character . group . updateMatrixWorld ( true ) ;
674643 }
675- if ( propScene ?. attachments . length ) syncPropAttachments ( propScene , mannequin . bones ) ;
676644 frameDt = 0 ;
677645 if ( easeCamera ) {
678646 controls . target . lerp ( desiredTarget , 0.07 ) ;
@@ -841,9 +809,8 @@ export function createViewer(
841809 else char . sync ( mannequin ) ;
842810 } )
843811 . catch ( ( ) => {
844- // Reveal the fallback if it was hidden during loading. Deliberately
845- // silent: an offline embed or blocked CDN should degrade, not error.
846- setMannequinMeshesVisible ( mannequin . root , true ) ;
812+ // Keep the procedural figure. Deliberately silent: an offline embed
813+ // or a blocked CDN should degrade, not error.
847814 } ) ;
848815 }
849816
@@ -874,12 +841,6 @@ function enableShadows(root: THREE.Object3D): void {
874841 } ) ;
875842}
876843
877- function setMannequinMeshesVisible ( root : THREE . Object3D , visible : boolean ) : void {
878- root . traverse ( ( obj ) => {
879- if ( ( obj as THREE . Mesh ) . isMesh ) obj . visible = visible ;
880- } ) ;
881- }
882-
883844/** Free GPU resources for a discarded subtree (prop set swapped on reload). */
884845function disposeTree ( root : THREE . Object3D ) : void {
885846 root . traverse ( ( obj ) => {
@@ -898,16 +859,15 @@ export { applyGroundLock, groundFigure } from "./groundlock.js";
898859export type { Mannequin , Proportions , CollisionRadii } from "./mannequin.js" ;
899860export { buildTimeline } from "./timeline.js" ;
900861export { solveCCD , type IkChain , type JointLimits } from "./ik.js" ;
901- export { buildProps , syncPropAttachments , type PropScene , type PropAttachment } from "./props.js" ;
862+ export { buildProps , type PropScene } from "./props.js" ;
902863export { loadCharacter , rigCharacter , type Character } from "./character.js" ;
903864export {
904865 loadClipSource ,
905- selectMotionClip ,
906866 retargetMocapClip ,
907867 createClipLayer ,
908868 type ClipLayer ,
909869 type ClipSource ,
910870} from "./clips.js" ;
911871export { depenetrate } from "./depenetrate.js" ;
912- export { alignBarGrips , alignFloorPalms , alignFloorSoles } from "./contacts.js" ;
872+ export { alignFloorPalms } from "./contacts.js" ;
913873export type { PhaseSegment } from "./timeline.js" ;
0 commit comments