@@ -98,6 +98,7 @@ <h1>Probe Transform Debugger</h1>
9898 applyExtrinsicRotation ,
9999 applyTranslation ,
100100} from './src/lib/coord-systems.js' ;
101+ import { createOrbitControls } from './src/lib/orbit-controls.js' ;
101102
102103// ── Probe examples ────────────────────────────────────────────────────────
103104const EXAMPLES = [
@@ -190,23 +191,74 @@ <h1>Probe Transform Debugger</h1>
190191 { type : 'Translation' , translation : [ 0 , 0 , 5 , 0 ] , label : 'T=[0,0,5] — 5mm +S' } ,
191192 ] ,
192193 } ,
193- // ── Real probe (disabled by default) ────────────────────────────────
194+ // ── Depth (4th component) axis tests ────────────────────────────────
194195 {
195- name : 'Probe 46121 (real)' ,
196+ name : '⑫ depth=+5 at rest — should move +A' ,
197+ color : '#ff44ff' ,
198+ target : 'expect: probe at rest points +A; depth=+5 moves tip 5mm AWAY from +A (−A / posterior)' ,
199+ transforms : [
200+ { type : 'Translation' , translation : [ 0 , 0 , 0 , 5 ] , label : 'depth=+5mm along dir (+A at rest)' } ,
201+ ] ,
202+ } ,
203+ {
204+ name : '⑬ depth=−5 at rest — should move −A' ,
205+ color : '#aa00aa' ,
206+ target : 'expect: depth=−5 moves tip 5mm TOWARD +A arrow (anterior)' ,
207+ transforms : [
208+ { type : 'Translation' , translation : [ 0 , 0 , 0 , - 5 ] , label : 'depth=−5mm along dir (−A at rest)' } ,
209+ ] ,
210+ } ,
211+ {
212+ name : '⑭ Rx(+90) then depth=+5 — should move +S' ,
213+ color : '#ff88ff' ,
214+ target : 'expect: Rx pitches probe to +S; depth=+5 moves tip AWAY from +S (−S / inferior)' ,
215+ transforms : [
216+ { type : 'Rotation' , angles : [ 90 , 0 , 0 ] , label : 'Rx(+90°) — pitch probe to +S' } ,
217+ { type : 'Translation' , translation : [ 0 , 0 , 0 , 5 ] , label : 'depth=+5mm along dir (now +S)' } ,
218+ ] ,
219+ } ,
220+ // ── Real probes from example_metadata.json ──────────────────────────
221+ {
222+ name : 'Probe 46121 → PAL (Pallidum)' ,
196223 color : '#FF6B6B' ,
197- target : 'Pallidum (PAL)' ,
224+ target : 'target: Pallidum (PAL)' ,
198225 transforms : [
199- { type : 'Rotation' , angles : [ 90 , 0 , - 90 ] , label : 'R1 — initial reorientation' } ,
200- { type : 'Rotation' , angles : [ 0 , 0 , 0 ] , label : 'R2 — identity (no-op)' } ,
201- { type : 'Translation' , translation : [ 7.502 , 5.333 , 11.386 , 1 ] , label : 'T1 — manipulator position (mm)' } ,
202- { type : 'Rotation' , angles : [ 14.963 , - 4.141 , - 90 ] , label : 'R3 — fine-tune direction' } ,
203- { type : 'Translation' , translation : [ 3.962 , - 12.102 , - 4.041 , 3.611 ] , label : 'T2 — tip position (mm)' } ,
226+ { type : 'Rotation' , angles : [ 90 , 0 , - 90 ] , label : 'R1 — initial reorientation' } ,
227+ { type : 'Rotation' , angles : [ 0 , 0 , 0 ] , label : 'R2 — identity (no-op)' } ,
228+ { type : 'Translation' , translation : [ 7.502 , 5.333 , 11.386 , 1 ] , label : 'T1 — manipulator position (mm)' , intrinsic : true } ,
229+ { type : 'Rotation' , angles : [ 14.96260569495023 , - 4.140622262893658 , - 90 ] , label : 'R3 — rotate around bregma' } ,
230+ { type : 'Translation' , translation : [ 3.9618575437063566 , - 12.101629723550062 , - 4.040916212626378 , 3.611 ] , label : 'T2 — tip position (mm)' } ,
231+ ] ,
232+ } ,
233+ {
234+ name : 'Probe 46105 → MD (Mediodorsal thalamus)' ,
235+ color : '#4ECDC4' ,
236+ target : 'target: Mediodorsal nucleus of thalamus (MD)' ,
237+ transforms : [
238+ { type : 'Rotation' , angles : [ 90 , 0 , - 90 ] , label : 'R1 — initial reorientation' } ,
239+ { type : 'Rotation' , angles : [ 0 , 0 , 0 ] , label : 'R2 — identity (no-op)' } ,
240+ { type : 'Translation' , translation : [ 8.754 , 4.721 , 11.133 , 1 ] , label : 'T1 — manipulator position (mm)' , intrinsic : true } ,
241+ { type : 'Rotation' , angles : [ 4.371791013219473 , - 29.092718819262377 , - 90 ] , label : 'R3 — rotate around bregma' } ,
242+ { type : 'Translation' , translation : [ - 3.2039830376214535 , - 9.58046494924959 , - 8.495415766802356 , 3.862 ] , label : 'T2 — tip position (mm)' } ,
243+ ] ,
244+ } ,
245+ {
246+ name : 'Probe 46811 → PIR (Piriform area)' ,
247+ color : '#45B7D1' ,
248+ target : 'target: Piriform area (PIR)' ,
249+ transforms : [
250+ { type : 'Rotation' , angles : [ 90 , 0 , - 90 ] , label : 'R1 — initial reorientation' } ,
251+ { type : 'Rotation' , angles : [ 0 , 0 , 0 ] , label : 'R2 — identity (no-op)' } ,
252+ { type : 'Translation' , translation : [ 7.925 , 0.227 , 10.681 , 1 ] , label : 'T1 — manipulator position (mm)' , intrinsic : true } ,
253+ { type : 'Rotation' , angles : [ - 4.987789352538776 , - 4.015229468646872 , - 90 ] , label : 'R3 — rotate around bregma' } ,
254+ { type : 'Translation' , translation : [ 1.7128800425290023 , - 7.173946069818659 , - 7.053089943226251 , 3.46 ] , label : 'T2 — tip position (mm)' } ,
204255 ] ,
205256 } ,
206257] ;
207258
208259// ── State: probeEnabled[pi], enabled[pi][ti] ─────────────────────────────
209- const probeEnabled = EXAMPLES . map ( ( ) => false ) ;
260+ // Enable the last 3 entries (real probes from example_metadata.json) by default.
261+ const probeEnabled = EXAMPLES . map ( ( _ , i ) => i >= EXAMPLES . length - 3 ) ;
210262const enabled = EXAMPLES . map ( e => e . transforms . map ( ( ) => true ) ) ;
211263
212264// ── Coordinate basis from coord-systems.js ────────────────────────────────
@@ -239,8 +291,32 @@ <h1>Probe Transform Debugger</h1>
239291 if ( t . type === 'Rotation' ) {
240292 dir = applyExtrinsicRotation ( dir , t . angles , BASIS ) ;
241293 wid = applyExtrinsicRotation ( wid , t . angles , BASIS ) ;
294+ if ( ! t . intrinsic ) {
295+ // All rotations pivot around bregma (world origin) by default.
296+ // Set intrinsic: true on a rotation to skip pivoting pos.
297+ pos = applyExtrinsicRotation ( pos , t . angles , BASIS ) ;
298+ }
242299 } else if ( t . type === 'Translation' ) {
243- pos = applyTranslation ( pos , t . translation ?? [ ] , BASIS ) ;
300+ if ( t . intrinsic ) {
301+ // Apply translation in the probe's current local frame.
302+ // Local axes: wid = axis-0 (R), dir = axis-1 (A), wid×dir = axis-2 (S).
303+ const localS = [
304+ wid [ 1 ] * dir [ 2 ] - wid [ 2 ] * dir [ 1 ] ,
305+ wid [ 2 ] * dir [ 0 ] - wid [ 0 ] * dir [ 2 ] ,
306+ wid [ 0 ] * dir [ 1 ] - wid [ 1 ] * dir [ 0 ] ,
307+ ] ;
308+ pos = applyTranslation ( pos , t . translation ?? [ ] , [ wid , dir , localS ] ) ;
309+ } else {
310+ pos = applyTranslation ( pos , t . translation ?? [ ] , BASIS ) ;
311+ }
312+ // 4th component = depth: move tip along the probe's current long axis (dir).
313+ // Negative sign: positive depth moves the tip in the −dir direction (insertion).
314+ const depth = ( t . translation ?? [ ] ) [ 3 ] ;
315+ if ( depth != null && depth !== 0 ) {
316+ pos [ 0 ] -= depth * dir [ 0 ] ;
317+ pos [ 1 ] -= depth * dir [ 1 ] ;
318+ pos [ 2 ] -= depth * dir [ 2 ] ;
319+ }
244320 }
245321 }
246322 steps . push ( { pos : [ ...pos ] , dir : norm ( dir ) , wid : norm ( wid ) , type : t . type , label : t . label , active } ) ;
@@ -520,38 +596,10 @@ <h1>Probe Transform Debugger</h1>
520596
521597// ── Orbit controls ────────────────────────────────────────────────────────
522598const TARGET = new THREE . Vector3 ( 0 , - 3.668 , - 1.2 ) ;
523- const SPEED = 0.007 ;
524- const axZ = new THREE . Vector3 ( 0 , 0 , 1 ) ;
525- const axX = new THREE . Vector3 ( 1 , 0 , 0 ) ;
526- let dragging = false , sx = 0 , sy = 0 ;
527- const camOff = new THREE . Vector3 ( ) , camUp = new THREE . Vector3 ( ) ;
528-
529- function startDrag ( x , y ) {
530- dragging = true ; sx = x ; sy = y ;
531- camOff . copy ( camera . position ) . sub ( TARGET ) ;
532- camUp . copy ( camera . up ) ;
533- }
534- function stopDrag ( ) { dragging = false ; }
535- function doDrag ( x , y ) {
536- if ( ! dragging ) return ;
537- const q = new THREE . Quaternion ( )
538- . setFromAxisAngle ( axZ , - ( x - sx ) * SPEED )
539- . multiply ( new THREE . Quaternion ( ) . setFromAxisAngle ( axX , ( y - sy ) * SPEED ) ) ;
540- camera . position . copy ( TARGET ) . add ( camOff . clone ( ) . applyQuaternion ( q ) ) ;
541- camera . up . copy ( camUp ) . applyQuaternion ( q ) ;
542- camera . lookAt ( TARGET ) ;
543- }
544-
545- renderer . domElement . addEventListener ( 'mousedown' , e => startDrag ( e . clientX , e . clientY ) ) ;
546- window . addEventListener ( 'mouseup' , stopDrag ) ;
547- window . addEventListener ( 'mousemove' , e => doDrag ( e . clientX , e . clientY ) ) ;
548- renderer . domElement . addEventListener ( 'wheel' , e => {
549- e . preventDefault ( ) ;
550- const dir = camera . position . clone ( ) . sub ( TARGET ) . normalize ( ) ;
551- const d = Math . max ( 3 , Math . min ( 80 , camera . position . distanceTo ( TARGET ) + e . deltaY * 0.03 ) ) ;
552- camera . position . copy ( TARGET ) . addScaledVector ( dir , d ) ;
553- camera . lookAt ( TARGET ) ;
554- } , { passive : false } ) ;
599+ const initCamUp = camera . up . clone ( ) ;
600+ const orbitControls = createOrbitControls ( camera , TARGET , initCamUp , renderer . domElement , {
601+ rotateSpeed : 0.007 ,
602+ } ) ;
555603
556604// Resize
557605new ResizeObserver ( ( ) => {
0 commit comments