@@ -327,7 +327,7 @@ class CameraController {
327327 if ( this . keys [ 'k' ] ) euler . x -= this . rotationStep ;
328328 if ( this . keys [ 'j' ] ) euler . y += this . rotationStep ;
329329 if ( this . keys [ 'l' ] ) euler . y -= this . rotationStep ;
330- if ( this . keys [ 'o ' ] ) euler . z -= this . rotationStep ;
330+ if ( this . keys [ 'u ' ] ) euler . z -= this . rotationStep ;
331331 if ( this . keys [ 'm' ] ) euler . z += this . rotationStep ;
332332
333333 // Prevent flipping over the top/bottom
@@ -486,6 +486,9 @@ class ImageViewer {
486486 // Setup keyframe controls
487487 this . initKeyframeControls ( ) ;
488488
489+ // Setup near-point jump controls
490+ this . initNearPointControls ( ) ;
491+
489492 // Setup color mode dropdown
490493 this . initColorModeControl ( ) ;
491494 this . initColorIntensityControls ( ) ;
@@ -1382,6 +1385,63 @@ class ImageViewer {
13821385 }
13831386 }
13841387
1388+ getNearestSpriteInfo ( preferInFront = true ) {
1389+ if ( this . imageSprites . length === 0 ) {
1390+ return null ;
1391+ }
1392+
1393+ const cameraForward = new THREE . Vector3 ( ) ;
1394+ this . camera . getWorldDirection ( cameraForward ) ;
1395+
1396+ const distances = this . imageSprites . map ( ( sprite , index ) => {
1397+ const distance = this . camera . position . distanceTo ( sprite . position ) ;
1398+ const toSprite = new THREE . Vector3 ( ) . subVectors ( sprite . position , this . camera . position ) . normalize ( ) ;
1399+ const dotProduct = cameraForward . dot ( toSprite ) ;
1400+
1401+ return {
1402+ sprite,
1403+ distance,
1404+ index,
1405+ inFront : dotProduct > this . fovConeThreshold
1406+ } ;
1407+ } ) ;
1408+
1409+ const inFrontSprites = distances
1410+ . filter ( item => item . inFront )
1411+ . sort ( ( a , b ) => a . distance - b . distance ) ;
1412+
1413+ if ( preferInFront && inFrontSprites . length > 0 ) {
1414+ return inFrontSprites [ 0 ] ;
1415+ }
1416+
1417+ distances . sort ( ( a , b ) => a . distance - b . distance ) ;
1418+ return distances [ 0 ] || null ;
1419+ }
1420+
1421+ jumpNearNearestPoint ( ) {
1422+ const nearest = this . getNearestSpriteInfo ( true ) ;
1423+ if ( ! nearest ?. sprite ) {
1424+ return ;
1425+ }
1426+
1427+ const nearPointPosition = nearest . sprite . position . clone ( ) ;
1428+ const offsetDirection = new THREE . Vector3 ( ) . subVectors ( this . camera . position , nearPointPosition ) ;
1429+
1430+ if ( offsetDirection . lengthSq ( ) < 1e-10 ) {
1431+ this . camera . getWorldDirection ( offsetDirection ) ;
1432+ offsetDirection . multiplyScalar ( - 1 ) ;
1433+ }
1434+
1435+ offsetDirection . normalize ( ) ;
1436+
1437+ // Keep the camera extremely close to the near point, but not exactly at the same location.
1438+ const offsetDistance = Math . max ( 0.1 , Math . min ( 0.001 , this . imageSize * 0.1 ) ) ;
1439+ const targetPosition = nearPointPosition . add ( offsetDirection . multiplyScalar ( offsetDistance ) ) ;
1440+
1441+ this . camera . position . copy ( targetPosition ) ;
1442+ this . camera . lookAt ( nearest . sprite . position ) ;
1443+ }
1444+
13851445 updateNearestPoint ( ) {
13861446 if ( this . imageSprites . length === 0 ) return ;
13871447
@@ -1738,12 +1798,39 @@ class ImageViewer {
17381798 } ) ;
17391799 }
17401800
1801+ initNearPointControls ( ) {
1802+ const nearBtn = document . getElementById ( 'btn-near' ) ;
1803+ if ( ! nearBtn ) return ;
1804+
1805+ nearBtn . addEventListener ( 'click' , ( e ) => {
1806+ e . preventDefault ( ) ;
1807+ this . jumpNearNearestPoint ( ) ;
1808+ } ) ;
1809+
1810+ nearBtn . addEventListener ( 'touchstart' , ( e ) => {
1811+ e . preventDefault ( ) ;
1812+ this . jumpNearNearestPoint ( ) ;
1813+ } , { passive : false } ) ;
1814+ }
1815+
17411816 initGlobalKeyboardShortcuts ( ) {
17421817 document . addEventListener ( 'keydown' , ( e ) => {
17431818 // Space key adds a keyframe
17441819 if ( e . key === ' ' || e . code === 'Space' ) {
17451820 e . preventDefault ( ) ; // Prevent default scrolling behavior
17461821 this . addKeyframe ( ) ;
1822+ return ;
1823+ }
1824+
1825+ // Jump very near the current nearest point.
1826+ if ( ! e . ctrlKey && ! e . metaKey && ! e . altKey && e . key . toLowerCase ( ) === 'o' ) {
1827+ e . preventDefault ( ) ;
1828+ if ( this . controller ?. keys ) {
1829+ this . controller . keys [ 'o' ] = false ;
1830+ }
1831+ if ( ! e . repeat ) {
1832+ this . jumpNearNearestPoint ( ) ;
1833+ }
17471834 }
17481835 } ) ;
17491836 }
0 commit comments