@@ -279,6 +279,7 @@ const createCoordinateSystem = (
279279 const xSpan = Math . abs ( range . xMax - range . xMin ) ;
280280 const ySpan = Math . abs ( range . yMax - range . yMin ) ;
281281
282+ const viewSpan = Math . min ( xSpan , ySpan ) ;
282283 const xTickStep = calculateNiceStep ( xSpan ) ;
283284 const yTickStep = calculateNiceStep ( ySpan ) ;
284285
@@ -352,30 +353,38 @@ const createCoordinateSystem = (
352353 const material = new THREE . SpriteMaterial ( { map : texture , transparent : true , depthTest : false } ) ;
353354 const sprite = new THREE . Sprite ( material ) ;
354355 sprite . position . copy ( position ) ;
355- sprite . scale . set ( fontSize * 2 , fontSize , 1 ) ;
356+ sprite . scale . set ( fontSize * 2.2 , fontSize , 1 ) ;
356357 sprite . userData . isGrid = true ;
357358 return sprite ;
358359 } ;
359360
360- const numXTicks = Math . ceil ( limit / xTickStep ) ;
361- for ( let i = - numXTicks ; i <= numXTicks ; i ++ ) {
361+ const labelFontSize = Math . min ( 0.12 , Math . max ( 0.004 , viewSpan * 0.028 ) ) ;
362+ const xLabelOffsetY = - Math . min ( 0.14 , Math . max ( 0.005 , ySpan * 0.035 ) ) ;
363+ const yLabelOffsetX = - Math . min ( 0.14 , Math . max ( 0.005 , xSpan * 0.035 ) ) ;
364+
365+ // Only place tick labels in the active visible view
366+ const minXTick = Math . floor ( ( range . xMin - xTickStep ) / xTickStep ) ;
367+ const maxXTick = Math . ceil ( ( range . xMax + xTickStep ) / xTickStep ) ;
368+ for ( let i = minXTick ; i <= maxXTick ; i ++ ) {
362369 const x = Number ( ( i * xTickStep ) . toFixed ( 6 ) ) ;
363- if ( Math . abs ( x ) > 0.001 && Math . abs ( x ) <= limit ) {
364- gridGroup . add ( createTextSprite ( formatTickNumber ( x ) , new THREE . Vector3 ( x , - 0.14 , 0 ) , 0.12 ) ) ;
370+ if ( Math . abs ( x ) > 0.0001 && Math . abs ( x ) <= limit ) {
371+ gridGroup . add ( createTextSprite ( formatTickNumber ( x ) , new THREE . Vector3 ( x , xLabelOffsetY , 0 ) , labelFontSize ) ) ;
365372 }
366373 }
367374
368- const numYTicks = Math . ceil ( limit / yTickStep ) ;
369- for ( let i = - numYTicks ; i <= numYTicks ; i ++ ) {
375+ const minYTick = Math . floor ( ( range . yMin - yTickStep ) / yTickStep ) ;
376+ const maxYTick = Math . ceil ( ( range . yMax + yTickStep ) / yTickStep ) ;
377+ for ( let i = minYTick ; i <= maxYTick ; i ++ ) {
370378 const y = Number ( ( i * yTickStep ) . toFixed ( 6 ) ) ;
371- if ( Math . abs ( y ) > 0.001 && Math . abs ( y ) <= limit ) {
372- gridGroup . add ( createTextSprite ( formatTickNumber ( y ) , new THREE . Vector3 ( - 0.14 , y , 0 ) , 0.12 ) ) ;
379+ if ( Math . abs ( y ) > 0.0001 && Math . abs ( y ) <= limit ) {
380+ gridGroup . add ( createTextSprite ( formatTickNumber ( y ) , new THREE . Vector3 ( yLabelOffsetX , y , 0 ) , labelFontSize ) ) ;
373381 }
374382 }
375383
376- gridGroup . add ( createTextSprite ( 'x' , new THREE . Vector3 ( range . xMax - 0.18 , 0.12 , 0 ) , 0.18 ) ) ;
377- gridGroup . add ( createTextSprite ( 'y' , new THREE . Vector3 ( 0.12 , range . yMax - 0.14 , 0 ) , 0.18 ) ) ;
378- gridGroup . add ( createTextSprite ( '0' , new THREE . Vector3 ( - 0.12 , - 0.12 , 0 ) , 0.1 ) ) ;
384+ const axisLabelSize = labelFontSize * 1.3 ;
385+ gridGroup . add ( createTextSprite ( 'x' , new THREE . Vector3 ( range . xMax - xSpan * 0.04 , ySpan * 0.03 , 0 ) , axisLabelSize ) ) ;
386+ gridGroup . add ( createTextSprite ( 'y' , new THREE . Vector3 ( xSpan * 0.03 , range . yMax - ySpan * 0.04 , 0 ) , axisLabelSize ) ) ;
387+ gridGroup . add ( createTextSprite ( '0' , new THREE . Vector3 ( yLabelOffsetX , xLabelOffsetY , 0 ) , labelFontSize * 0.9 ) ) ;
379388
380389 scene . add ( gridGroup ) ;
381390 return gridGroup ;
@@ -2775,6 +2784,12 @@ const SetValuedViz = () => {
27752784 scene . add ( line ) ;
27762785 }
27772786
2787+ const ySpan = Math . abs ( viewportRange . yMax - viewportRange . yMin ) ;
2788+ const { height : vpHeight } = readViewportSize ( ) ;
2789+ const frustumH = Math . max ( 1e-6 , ySpan + 0.24 ) ;
2790+ const pxPerUnit = vpHeight / frustumH ;
2791+ const radius = Math . max ( 1e-6 , 6.0 / pxPerUnit ) ;
2792+
27782793 manifoldState . fixedPoints . forEach ( fp => {
27792794 const stabLower = ( fp . stability || '' ) . toLowerCase ( ) ;
27802795 const isAttractor = stabLower === 'attractor' || stabLower === 'stable' ;
@@ -2785,11 +2800,37 @@ const SetValuedViz = () => {
27852800 isDualRepeller ? ORBIT_COLORS . dualRepeller :
27862801 isRepeller ? ORBIT_COLORS . repeller :
27872802 isSaddle ? ORBIT_COLORS . saddlePoint : ORBIT_COLORS . periodicBlue ;
2788- const radius = isAttractor ? 0.03 : ( isRepeller || isDualRepeller ) ? 0.028 : 0.025 ;
2789- const geom = new THREE . SphereGeometry ( radius , 12 , 12 ) ;
2790- const mat = new THREE . MeshBasicMaterial ( { color : new THREE . Color ( color ) } ) ;
2803+
2804+ const fpRadius = radius * ( isAttractor ? 1.15 : ( isRepeller || isDualRepeller ) ? 1.1 : 1.0 ) ;
2805+
2806+ // Dark high-contrast outer ring so fixed point is clearly separated from manifold lines
2807+ const ringGeom = new THREE . RingGeometry ( fpRadius * 0.9 , fpRadius * 1.35 , 32 ) ;
2808+ const ringMat = new THREE . MeshBasicMaterial ( {
2809+ color : new THREE . Color ( '#000000' ) ,
2810+ transparent : true ,
2811+ opacity : 0.95 ,
2812+ depthTest : false ,
2813+ depthWrite : false ,
2814+ side : THREE . DoubleSide
2815+ } ) ;
2816+ const ring = new THREE . Mesh ( ringGeom , ringMat ) ;
2817+ ring . position . set ( fp . x , fp . y , 0.49 ) ;
2818+ ring . renderOrder = 99 ;
2819+ ring . userData = { type : 'fixedPoint' } ;
2820+ scene . add ( ring ) ;
2821+
2822+ const geom = new THREE . CircleGeometry ( fpRadius , 32 ) ;
2823+ const mat = new THREE . MeshBasicMaterial ( {
2824+ color : new THREE . Color ( color ) ,
2825+ transparent : true ,
2826+ opacity : 1.0 ,
2827+ depthTest : false ,
2828+ depthWrite : false ,
2829+ side : THREE . DoubleSide
2830+ } ) ;
27912831 const sphere = new THREE . Mesh ( geom , mat ) ;
2792- sphere . position . set ( fp . x , fp . y , 0.2 ) ;
2832+ sphere . position . set ( fp . x , fp . y , 0.5 ) ;
2833+ sphere . renderOrder = 100 ;
27932834 sphere . userData = {
27942835 type : 'fixedPoint' ,
27952836 period : 1 ,
@@ -2814,35 +2855,60 @@ const SetValuedViz = () => {
28142855 } else {
28152856 manifoldState . trajectoryPoints . forEach ( ( point , idx ) => {
28162857 const normalizedIdx = idx / manifoldState . trajectoryPoints . length ;
2817- const size = 0.022 * ( 0.4 + 0.6 * normalizedIdx ) ;
2818- const geom = new THREE . SphereGeometry ( size , 8 , 8 ) ;
2819- const mat = new THREE . MeshBasicMaterial ( { color : new THREE . Color ( ORBIT_COLORS . trajectory ) , opacity : 0.4 + 0.6 * normalizedIdx , transparent : true } ) ;
2858+ const size = Math . max ( 1e-6 , ( 4.5 * ( 0.4 + 0.6 * normalizedIdx ) ) / pxPerUnit ) ;
2859+ const geom = new THREE . CircleGeometry ( size , 16 ) ;
2860+ const mat = new THREE . MeshBasicMaterial ( {
2861+ color : new THREE . Color ( ORBIT_COLORS . trajectory ) ,
2862+ opacity : 0.4 + 0.6 * normalizedIdx ,
2863+ transparent : true ,
2864+ depthTest : false ,
2865+ depthWrite : false ,
2866+ side : THREE . DoubleSide
2867+ } ) ;
28202868 const sphere = new THREE . Mesh ( geom , mat ) ;
2821- sphere . position . set ( point . x , point . y , 0.25 ) ;
2869+ sphere . position . set ( point . x , point . y , 0.45 ) ;
2870+ sphere . renderOrder = 40 ;
28222871 sphere . userData . type = 'trajectory' ;
28232872 scene . add ( sphere ) ;
28242873 } ) ;
28252874 }
28262875 }
28272876
28282877 if ( manifoldState . hasStarted && manifoldState . currentPoint ) {
2829- const glowGeom = new THREE . RingGeometry ( 0.05 , 0.05 , 20 ) ;
2878+ const glowRadius = Math . max ( 1e-6 , 7.5 / pxPerUnit ) ;
2879+ const glowGeom = new THREE . RingGeometry ( glowRadius * 0.8 , glowRadius * 1.2 , 32 ) ;
28302880 const currentColor = dynamicSystem === 'duffing_ode' ? ORBIT_COLORS . manifold : ORBIT_COLORS . trajectory ;
2831- const glowMat = new THREE . MeshBasicMaterial ( { color : new THREE . Color ( currentColor ) , opacity : 0.6 , transparent : true , side : THREE . DoubleSide } ) ;
2881+ const glowMat = new THREE . MeshBasicMaterial ( {
2882+ color : new THREE . Color ( currentColor ) ,
2883+ opacity : 0.6 ,
2884+ transparent : true ,
2885+ side : THREE . DoubleSide ,
2886+ depthTest : false ,
2887+ depthWrite : false
2888+ } ) ;
28322889 const glowRing = new THREE . Mesh ( glowGeom , glowMat ) ;
2833- glowRing . position . set ( manifoldState . currentPoint . x , manifoldState . currentPoint . y , 0.3 ) ;
2890+ glowRing . position . set ( manifoldState . currentPoint . x , manifoldState . currentPoint . y , 0.51 ) ;
2891+ glowRing . renderOrder = 101 ;
28342892 glowRing . userData . type = 'trajectory' ;
28352893 scene . add ( glowRing ) ;
28362894
2837- const geom = new THREE . SphereGeometry ( 0.02 , 16 , 16 ) ;
2838- const mat = new THREE . MeshBasicMaterial ( { color : new THREE . Color ( '#ffffff' ) } ) ;
2895+ const geom = new THREE . CircleGeometry ( Math . max ( 1e-6 , 5.0 / pxPerUnit ) , 32 ) ;
2896+ const mat = new THREE . MeshBasicMaterial ( {
2897+ color : new THREE . Color ( '#ffffff' ) ,
2898+ transparent : true ,
2899+ opacity : 1.0 ,
2900+ depthTest : false ,
2901+ depthWrite : false ,
2902+ side : THREE . DoubleSide
2903+ } ) ;
28392904 const sphere = new THREE . Mesh ( geom , mat ) ;
2840- sphere . position . set ( manifoldState . currentPoint . x , manifoldState . currentPoint . y , 0.3 ) ;
2905+ sphere . position . set ( manifoldState . currentPoint . x , manifoldState . currentPoint . y , 0.52 ) ;
2906+ sphere . renderOrder = 102 ;
28412907 sphere . userData . type = 'trajectory' ;
28422908 scene . add ( sphere ) ;
28432909 }
28442910
2845- } , [ manifoldState , geometricOffsetState , bdeState , dynamicSystem , type , viewRange , readViewportSize , geometricOffsetBoundaryPoints , boundaryLayers , hasBoundarySamples , hasVerifiedBoundaryCycles , params . epsilon ] ) ;
2911+ } , [ manifoldState , geometricOffsetState , bdeState , dynamicSystem , type , viewRange , viewportRange , readViewportSize , geometricOffsetBoundaryPoints , boundaryLayers , hasBoundarySamples , hasVerifiedBoundaryCycles , params . epsilon ] ) ;
28462912
28472913 useEffect ( ( ) => {
28482914 if ( ! sceneRef . current ) return ;
@@ -2890,17 +2956,45 @@ const SetValuedViz = () => {
28902956 return ORBIT_COLORS . periodicBlue ;
28912957 } ;
28922958
2959+ const ySpan = Math . abs ( viewportRange . yMax - viewportRange . yMin ) ;
2960+ const { height : vpHeight } = readViewportSize ( ) ;
2961+ const frustumH = Math . max ( 1e-6 , ySpan + 0.24 ) ;
2962+ const pxPerUnit = vpHeight / frustumH ;
2963+ const orbitSphereRadius = Math . max ( 1e-6 , 5.5 / pxPerUnit ) ;
2964+
28932965 if ( manifoldState . showOrbits ) {
28942966 periodicState . orbits . filter ( isVisible ) . forEach ( ( orbit , orbitIndex ) => {
28952967 const orbitId = `orbit-${ orbit . period } -${ orbitIndex } ` ;
28962968 const pointColor = colorForOrbit ( orbit ) ;
28972969 orbitExtendedStates ( orbit ) . forEach ( ( extendedPoint , pointIndex ) => {
2898- const geometry = new THREE . SphereGeometry ( 0.02 , 10 , 10 ) ;
2970+ // Dark high-contrast outer ring so orbit point is clearly visible on top of manifold lines
2971+ const ringGeom = new THREE . RingGeometry ( orbitSphereRadius * 0.9 , orbitSphereRadius * 1.35 , 32 ) ;
2972+ const ringMat = new THREE . MeshBasicMaterial ( {
2973+ color : new THREE . Color ( '#000000' ) ,
2974+ transparent : true ,
2975+ opacity : 0.95 ,
2976+ depthTest : false ,
2977+ depthWrite : false ,
2978+ side : THREE . DoubleSide
2979+ } ) ;
2980+ const ring = new THREE . Mesh ( ringGeom , ringMat ) ;
2981+ ring . position . set ( extendedPoint . x , extendedPoint . y , 0.49 ) ;
2982+ ring . renderOrder = 99 ;
2983+ ring . userData = { type : 'orbit' } ;
2984+ scene . add ( ring ) ;
2985+
2986+ const geometry = new THREE . CircleGeometry ( orbitSphereRadius , 32 ) ;
28992987 const material = new THREE . MeshBasicMaterial ( {
2900- color : new THREE . Color ( pointColor )
2988+ color : new THREE . Color ( pointColor ) ,
2989+ transparent : true ,
2990+ opacity : 1.0 ,
2991+ depthTest : false ,
2992+ depthWrite : false ,
2993+ side : THREE . DoubleSide
29012994 } ) ;
29022995 const sphere = new THREE . Mesh ( geometry , material ) ;
2903- sphere . position . set ( extendedPoint . x , extendedPoint . y , 0.05 ) ;
2996+ sphere . position . set ( extendedPoint . x , extendedPoint . y , 0.5 ) ;
2997+ sphere . renderOrder = 100 ;
29042998 sphere . userData = {
29052999 type : 'orbit' ,
29063000 resultRevision,
@@ -2961,7 +3055,9 @@ const SetValuedViz = () => {
29613055 manifoldState . showOrbits ,
29623056 periodicState . isReady ,
29633057 periodicState . orbits ,
2964- periodicState . resultRevision
3058+ periodicState . resultRevision ,
3059+ readViewportSize ,
3060+ viewportRange
29653061 ] ) ;
29663062
29673063 const stepForwardManifold = useCallback ( ( ) => {
0 commit comments