@@ -19,13 +19,13 @@ import { useRef, useEffect, useCallback, useState, useMemo } from 'react';
1919import { useShallow } from 'zustand/react/shallow' ;
2020import { useAirfoilStore , pauseHistory , resumeHistory } from '../stores/airfoilStore' ;
2121import { useVisualizationStore } from '../stores/visualizationStore' ;
22+ import { useRouteUiStore } from '../stores/routeUiStore' ;
2223import { useTheme } from '../contexts/ThemeContext' ;
2324import { useLayout } from '../contexts/LayoutContext' ;
2425import type { Point , ViewportState , AirfoilPoint } from '../types' ;
25- import { computeStreamlines , computePsiGrid , createSmokeSystem , isWasmReady , WasmSmokeSystem , analyzeAirfoil , computeGamma , getBLVisualizationData , type BLVisualizationData } from '../lib/wasm' ;
26+ import { computeStreamlines , computePsiGrid , createSmokeSystem , isWasmReady , analyzeAirfoil , computeGamma , getBLVisualizationData , type BLVisualizationData , type WasmSmokeSystem } from '../lib/wasm' ;
2627import { useMorphingAnimation , getCpColor , computeForceVectors } from '../hooks/useMorphingAnimation' ;
2728import { generateCamberSplineCurve } from '../lib/airfoilGeometry' ;
28- import { syncToUrl , getCompleteUrlState } from '../lib/urlState' ;
2929import { WebGPURenderer , checkWebGPUSupport } from '../lib/webgpu' ;
3030// Removed d3-contour - using efficient cell-based rendering instead
3131
@@ -462,11 +462,7 @@ const CONTROL_RADIUS = 6;
462462const HIT_RADIUS = 10 ;
463463const TOUCH_HIT_RADIUS = 24 ;
464464
465- interface AirfoilCanvasProps {
466- initialViewport ?: { centerX : number ; centerY : number ; zoom : number } | null ;
467- }
468-
469- export function AirfoilCanvas ( { initialViewport } : AirfoilCanvasProps ) {
465+ export function AirfoilCanvas ( ) {
470466 const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
471467 const smokeCanvasRef = useRef < HTMLCanvasElement > ( null ) ; // Overlay canvas for smoke (redraws every frame)
472468 const gpuCanvasRef = useRef < HTMLCanvasElement > ( null ) ; // WebGPU canvas
@@ -524,36 +520,51 @@ export function AirfoilCanvas({ initialViewport }: AirfoilCanvasProps) {
524520 } ) )
525521 ) ;
526522
527- // Viewport state (initialize from URL if provided)
523+ const routeViewport = useRouteUiStore ( ( state ) => state . viewport ) ;
524+ const viewportRevision = useRouteUiStore ( ( state ) => state . viewportRevision ) ;
525+ const setRouteViewport = useRouteUiStore ( ( state ) => state . setViewport ) ;
526+
527+ // Viewport state (initialize from route state)
528528 const [ viewport , setViewport ] = useState < ViewportState > ( ( ) => ( {
529529 center : {
530- x : initialViewport ? .centerX ?? 0.5 ,
531- y : initialViewport ? .centerY ?? 0
530+ x : routeViewport . centerX ?? 0.5 ,
531+ y : routeViewport . centerY ?? 0 ,
532532 } ,
533- zoom : initialViewport ? .zoom ?? 400 ,
533+ zoom : routeViewport . zoom ?? 400 ,
534534 width : 800 ,
535535 height : 600 ,
536536 } ) ) ;
537-
538- // Get airfoil state for URL sync (use shallow equality)
539- const airfoilState = useAirfoilStore (
540- useShallow ( ( state ) => ( {
541- name : state . name ,
542- nPanels : state . nPanels ,
543- spacingKnots : state . spacingKnots ,
544- controlMode : state . controlMode ,
545- displayAlpha : state . displayAlpha ,
546- thicknessScale : state . thicknessScale ,
547- camberScale : state . camberScale ,
548- curvatureWeight : state . curvatureWeight ,
549- spacingPanelMode : state . spacingPanelMode ,
550- sspInterpolation : state . sspInterpolation ,
551- sspVisualization : state . sspVisualization ,
552- } ) )
553- ) ;
554537
555- // Visualization settings from store (use shallow equality to prevent infinite loops)
556- const visualizationState = useVisualizationStore (
538+ const {
539+ showGrid,
540+ showCurve,
541+ showPanels,
542+ showPoints,
543+ showControls,
544+ showStreamlines,
545+ showSmoke,
546+ showPsiContours,
547+ showCp,
548+ showForces,
549+ showBoundaryLayer,
550+ showWake,
551+ showDisplacementThickness,
552+ blThicknessScale,
553+ enableMorphing,
554+ morphDuration,
555+ streamlineDensity,
556+ adaptiveStreamlines,
557+ smokeDensity,
558+ smokeParticlesPerBlob,
559+ smokeWaveSpacing,
560+ smokeResetCounter,
561+ flowSpeed,
562+ cpDisplayMode,
563+ cpBarScale,
564+ forceScale,
565+ useGPU,
566+ gpuAvailable : _gpuAvailable , // Used for conditional WebGPU setup
567+ } = useVisualizationStore (
557568 useShallow ( ( state ) => ( {
558569 showGrid : state . showGrid ,
559570 showCurve : state . showCurve ,
@@ -585,61 +596,40 @@ export function AirfoilCanvas({ initialViewport }: AirfoilCanvasProps) {
585596 gpuAvailable : state . gpuAvailable ,
586597 } ) )
587598 ) ;
588-
589- const {
590- showGrid,
591- showCurve,
592- showPanels,
593- showPoints,
594- showControls,
595- showStreamlines,
596- showSmoke,
597- showPsiContours,
598- showCp,
599- showForces,
600- showBoundaryLayer,
601- showWake,
602- showDisplacementThickness,
603- blThicknessScale,
604- enableMorphing,
605- morphDuration,
606- streamlineDensity,
607- adaptiveStreamlines,
608- smokeDensity,
609- smokeParticlesPerBlob,
610- smokeWaveSpacing,
611- smokeResetCounter,
612- flowSpeed,
613- cpDisplayMode,
614- cpBarScale,
615- forceScale,
616- useGPU,
617- gpuAvailable : _gpuAvailable , // Used for conditional WebGPU setup
618- } = visualizationState ;
619599 void _gpuAvailable ; // Suppress unused warning (used implicitly in GPU init)
620600
621601 // Get store actions separately (not needed for URL sync)
622602 const setSmokeDensity = useVisualizationStore ( ( state ) => state . setSmokeDensity ) ;
623603 const setGPUAvailable = useVisualizationStore ( ( state ) => state . setGPUAvailable ) ;
624604 const updatePerfMetrics = useVisualizationStore ( ( state ) => state . updatePerfMetrics ) ;
625-
626- // Sync all state to URL (debounced)
605+ const appliedViewportRevisionRef = useRef ( viewportRevision ) ;
606+
607+ useEffect ( ( ) => {
608+ if ( viewportRevision === appliedViewportRevisionRef . current ) {
609+ return ;
610+ }
611+ appliedViewportRevisionRef . current = viewportRevision ;
612+ setViewport ( ( current ) => ( {
613+ ...current ,
614+ center : {
615+ x : routeViewport . centerX ,
616+ y : routeViewport . centerY ,
617+ } ,
618+ zoom : routeViewport . zoom ,
619+ } ) ) ;
620+ } , [ routeViewport . centerX , routeViewport . centerY , routeViewport . zoom , viewportRevision ] ) ;
621+
627622 useEffect ( ( ) => {
628623 const timeoutId = setTimeout ( ( ) => {
629- const urlState = getCompleteUrlState (
630- airfoilState ,
631- visualizationState ,
632- {
633- centerX : viewport . center . x ,
634- centerY : viewport . center . y ,
635- zoom : viewport . zoom ,
636- }
637- ) ;
638- syncToUrl ( urlState ) ;
639- } , 500 ) ; // Debounce 500ms
640-
624+ setRouteViewport ( {
625+ centerX : viewport . center . x ,
626+ centerY : viewport . center . y ,
627+ zoom : viewport . zoom ,
628+ } ) ;
629+ } , 200 ) ;
630+
641631 return ( ) => clearTimeout ( timeoutId ) ;
642- } , [ airfoilState , visualizationState , viewport ] ) ;
632+ } , [ setRouteViewport , viewport . center . x , viewport . center . y , viewport . zoom ] ) ;
643633
644634 // Streamlines cache
645635 const [ streamlines , setStreamlines ] = useState < [ number , number ] [ ] [ ] > ( [ ] ) ;
0 commit comments