@@ -111,6 +111,11 @@ export interface Viewer {
111111 getConstraintDiagnostics ( ) : readonly ConstraintDiagnostic [ ] ;
112112 /** Precise visible world bounds; intended for audits and deterministic export. */
113113 getVisibleBounds ( ) : THREE . Box3 ;
114+ /**
115+ * Highlight canonical bone ids at their live joint positions. Unknown ids
116+ * are ignored; pass an empty list to clear the selection.
117+ */
118+ selectBones ( boneIds : readonly string [ ] ) : void ;
114119 getMannequin ( ) : any ;
115120 getCharacter ( ) : any ;
116121 /**
@@ -179,6 +184,46 @@ export function createViewer(
179184 scene . background = new THREE . Color ( 0x0c0f15 ) ;
180185 scene . fog = new THREE . Fog ( 0x0c0f15 , 9 , 18 ) ;
181186
187+ // Text-editor selection overlay. Markers live outside the mannequin and
188+ // character trees so they never affect grounding, camera framing, bounds,
189+ // exports, or contact diagnostics.
190+ const boneSelection = new THREE . Group ( ) ;
191+ boneSelection . name = "posecode-bone-selection" ;
192+ scene . add ( boneSelection ) ;
193+ const selectionDotGeometry = new THREE . SphereGeometry ( 0.018 , 16 , 12 ) ;
194+ const selectionRingGeometry = new THREE . TorusGeometry ( 0.055 , 0.006 , 8 , 32 ) ;
195+ const selectionDotMaterial = new THREE . MeshBasicMaterial ( {
196+ color : 0xd4ff3f ,
197+ transparent : true ,
198+ opacity : 0.96 ,
199+ depthTest : false ,
200+ depthWrite : false ,
201+ } ) ;
202+ const selectionRingMaterial = new THREE . MeshBasicMaterial ( {
203+ color : 0xd4ff3f ,
204+ transparent : true ,
205+ opacity : 0.82 ,
206+ depthTest : false ,
207+ depthWrite : false ,
208+ } ) ;
209+ let selectedBoneIds : string [ ] = [ ] ;
210+ let selectionMarkers : THREE . Group [ ] = [ ] ;
211+
212+ function rebuildBoneSelection ( ) : void {
213+ boneSelection . clear ( ) ;
214+ selectionMarkers = selectedBoneIds . map ( ( ) => {
215+ const marker = new THREE . Group ( ) ;
216+ marker . renderOrder = 1000 ;
217+ const dot = new THREE . Mesh ( selectionDotGeometry , selectionDotMaterial ) ;
218+ dot . renderOrder = 1000 ;
219+ const ring = new THREE . Mesh ( selectionRingGeometry , selectionRingMaterial ) ;
220+ ring . renderOrder = 1000 ;
221+ marker . add ( dot , ring ) ;
222+ boneSelection . add ( marker ) ;
223+ return marker ;
224+ } ) ;
225+ }
226+
182227 // Image-based environment light: soft bounced light that gives the matte
183228 // figure materials realistic shading gradients instead of flat CG plastic.
184229 const pmrem = new THREE . PMREMGenerator ( renderer ) ;
@@ -911,6 +956,23 @@ export function createViewer(
911956 // geometry; a segmented skin can have a different lowest point as limbs
912957 // rotate. Reconcile the actual skinned surface after every animation layer.
913958 if ( character && solvedInfo && isFloorBound ( solvedInfo ) ) character . reconcileFloor ( ) ;
959+ // Follow the visible rig (including mocap and its final floor correction)
960+ // when available; the congruent procedural driver is the fallback.
961+ for ( let i = 0 ; i < selectedBoneIds . length ; i ++ ) {
962+ const boneId = selectedBoneIds [ i ] ! ;
963+ const marker = selectionMarkers [ i ] ! ;
964+ const position =
965+ character ?. getJointWorldPosition ( boneId ) ??
966+ mannequin . bones . get ( boneId ) ?. getWorldPosition ( new THREE . Vector3 ( ) ) ??
967+ null ;
968+ marker . visible = position !== null ;
969+ if ( position ) {
970+ marker . position . copy ( position ) ;
971+ // The torus is a screen-facing selection ring; the centre dot remains
972+ // spherical, so copying the camera frame works for both children.
973+ marker . quaternion . copy ( camera . quaternion ) ;
974+ }
975+ }
914976 frameDt = 0 ;
915977 if ( easeCamera ) {
916978 controls . target . lerp ( desiredTarget , 0.07 ) ;
@@ -1242,6 +1304,12 @@ export function createViewer(
12421304 getVisibleBounds ( ) {
12431305 return character ?. getBounds ( ) ?? new THREE . Box3 ( ) . setFromObject ( mannequin . root ) ;
12441306 } ,
1307+ selectBones ( boneIds ) {
1308+ selectedBoneIds = [ ...new Set ( boneIds ) ] . filter ( ( id ) =>
1309+ mannequin . bones . has ( id ) ,
1310+ ) ;
1311+ rebuildBoneSelection ( ) ;
1312+ } ,
12451313 getMannequin ( ) {
12461314 return mannequin ;
12471315 } ,
@@ -1267,6 +1335,10 @@ export function createViewer(
12671335 clipLayer ?. dispose ( ) ;
12681336 character ?. dispose ( ) ;
12691337 floorGuide ?. dispose ( ) ;
1338+ selectionDotGeometry . dispose ( ) ;
1339+ selectionRingGeometry . dispose ( ) ;
1340+ selectionDotMaterial . dispose ( ) ;
1341+ selectionRingMaterial . dispose ( ) ;
12701342 renderer . dispose ( ) ;
12711343 } ,
12721344 } ;
0 commit comments