@@ -402,6 +402,49 @@ export const PathwaysNiivueCanvas = () => (
402402 setIsCrosshair ( crosshairState ) ;
403403 } ;
404404
405+ // Animate, looping continuously through the coronal slices
406+ const [ isPlaying , setIsPlaying ] = useState ( false ) ;
407+ const playRafRef = React . useRef ( null ) ;
408+ const playLastTimeRef = React . useRef ( 0 ) ;
409+ const SLICES_PER_SECOND = 12 ;
410+
411+ const animateCoronalSlice = ( now ) => {
412+ const nv = niivue_slice . current ;
413+ if ( ! nv || ! nv . volumes . length ) {
414+ playRafRef . current = requestAnimationFrame ( animateCoronalSlice ) ;
415+ return ;
416+ }
417+ if ( ! playLastTimeRef . current ) playLastTimeRef . current = now ;
418+ const elapsed = ( now - playLastTimeRef . current ) / 1000 ; // seconds
419+ playLastTimeRef . current = now ;
420+
421+ const numSlices = nv . volumes [ 0 ] . dims [ 2 ] ;
422+ // Advance the crosshair fraction by the elapsed time, looping at the volume edge
423+ let y = nv . scene . crosshairPos [ 1 ] + ( SLICES_PER_SECOND / numSlices ) * elapsed ;
424+ y -= Math . floor ( y ) ; // wrap into [0, 1) to loop continuously
425+ nv . scene . crosshairPos [ 1 ] = y ;
426+ nv . drawScene ( ) ;
427+
428+ playRafRef . current = requestAnimationFrame ( animateCoronalSlice ) ;
429+ } ;
430+
431+ const handlePlayToggle = ( ) => {
432+ if ( isPlaying ) {
433+ cancelAnimationFrame ( playRafRef . current ) ;
434+ playRafRef . current = null ;
435+ setIsPlaying ( false ) ;
436+ } else {
437+ playLastTimeRef . current = 0 ;
438+ playRafRef . current = requestAnimationFrame ( animateCoronalSlice ) ;
439+ setIsPlaying ( true ) ;
440+ }
441+ } ;
442+
443+ // Stop the animation when the component unmounts
444+ React . useEffect ( ( ) => ( ) => {
445+ if ( playRafRef . current ) cancelAnimationFrame ( playRafRef . current ) ;
446+ } , [ ] ) ;
447+
405448 const [ isOrientCube , setIsOrientCube ] = useState ( true ) ;
406449
407450 const handleOrientCubeChange = ( event ) => {
@@ -411,6 +454,34 @@ export const PathwaysNiivueCanvas = () => (
411454 setIsOrientCube ( orientCubeState ) ;
412455 } ;
413456
457+ // Continuously rotate the 3D render view
458+ const [ isRotating , setIsRotating ] = useState ( false ) ;
459+ const rotateIntervalRef = React . useRef ( null ) ;
460+
461+ const stepRotation = ( ) => {
462+ const nv = niivue_render . current ;
463+ if ( ! nv ) return ;
464+ // Increment the azimuth to spin the camera around the volume, wrapping at 360
465+ const azimuth = ( nv . scene . renderAzimuth + 2 ) % 360 ;
466+ nv . setRenderAzimuthElevation ( azimuth , nv . scene . renderElevation ) ;
467+ } ;
468+
469+ const handleRotateToggle = ( ) => {
470+ if ( isRotating ) {
471+ clearInterval ( rotateIntervalRef . current ) ;
472+ rotateIntervalRef . current = null ;
473+ setIsRotating ( false ) ;
474+ } else {
475+ rotateIntervalRef . current = setInterval ( stepRotation , 50 ) ;
476+ setIsRotating ( true ) ;
477+ }
478+ } ;
479+
480+ // Stop the rotation when the component unmounts
481+ React . useEffect ( ( ) => ( ) => {
482+ if ( rotateIntervalRef . current ) clearInterval ( rotateIntervalRef . current ) ;
483+ } , [ ] ) ;
484+
414485 const [ tractVisibility , setTractVisibility ] = useState (
415486 trackList . map ( ( ) => true )
416487 ) ;
@@ -495,6 +566,15 @@ export const PathwaysNiivueCanvas = () => (
495566 Crosshair
496567 </ label >
497568 </ div >
569+ < div style = { { marginTop : "8px" } } >
570+ < button
571+ type = "button"
572+ className = "button button--sm button--primary"
573+ onClick = { handlePlayToggle }
574+ >
575+ { isPlaying ? "⏸ Pause slices" : "▶ Play slices" }
576+ </ button >
577+ </ div >
498578
499579 < hr />
500580 < h4 > 3D View</ h4 >
@@ -509,6 +589,15 @@ export const PathwaysNiivueCanvas = () => (
509589 Orientation cube
510590 </ label >
511591 </ div >
592+ < div style = { { marginTop : "8px" } } >
593+ < button
594+ type = "button"
595+ className = "button button--sm button--primary"
596+ onClick = { handleRotateToggle }
597+ >
598+ { isRotating ? "⏸ Pause rotation" : "▶ Rotate view" }
599+ </ button >
600+ </ div >
512601
513602 < hr />
514603 < h4 > Nuclei</ h4 >
0 commit comments