From 65504cae2b2f8a27f5e25e5df344f79eedb26e7e Mon Sep 17 00:00:00 2001 From: minsung Date: Wed, 1 Apr 2026 15:22:56 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=A4=91=EC=8B=AC=EC=84=A0=20=EC=8A=A4?= =?UTF-8?q?=EB=AC=B4=EB=94=A9=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EB=94=94?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=ED=8C=A8=EB=84=90=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - renderCacheRef useEffect에서 CL 계산 시 smoothFrame 적용 (텍스트와 동일한 smooth 슬라이더 값 공유) - currentFrameIdxRef 추가로 smoothFrame 인덱스 전달 - 디버그 패널(frame/frac/mapSize) 캔버스에서 제거 Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/overlay/StationOverlay.tsx | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/client/src/components/overlay/StationOverlay.tsx b/client/src/components/overlay/StationOverlay.tsx index e462578..e89faed 100644 --- a/client/src/components/overlay/StationOverlay.tsx +++ b/client/src/components/overlay/StationOverlay.tsx @@ -114,6 +114,7 @@ export default function StationOverlay({ currentFrame, currentTime, fps, visible // 현재 상태 ref const currentDroneFrameRef = useRef(null); const currentFrameNumRef = useRef(0); // RAF에서 Map 조회용 + const currentFrameIdxRef = useRef(0); // smoothFrame용 배열 인덱스 const currentTimeSecRef = useRef(0); // 마지막으로 알려진 재생 시간 const timeUpdateWallRef = useRef(performance.now()); // currentTime 갱신된 시각 const paramsRef = useRef(DEFAULT_CAMERA_PARAMS); @@ -298,13 +299,14 @@ export default function StationOverlay({ currentFrame, currentTime, fps, visible if (!visible || !droneFramesLoaded) return; const frames = allDroneFramesRef.current; if (!frames.length) return; - let best = frames[0], bestD = Math.abs((best.frame ?? 0) / VIDEO_FPS - currentTime); - for (const f of frames) { - const d = Math.abs(f.frame / VIDEO_FPS - currentTime); - if (d < bestD) { bestD = d; best = f; } + let best = frames[0], bestIdx = 0, bestD = Math.abs((best.frame ?? 0) / VIDEO_FPS - currentTime); + for (let i = 0; i < frames.length; i++) { + const d = Math.abs(frames[i].frame / VIDEO_FPS - currentTime); + if (d < bestD) { bestD = d; best = frames[i]; bestIdx = i; } if (bestD < 1 / VIDEO_FPS / 2) break; } currentFrameNumRef.current = best.frame; + currentFrameIdxRef.current = bestIdx; currentTimeSecRef.current = currentTime; timeUpdateWallRef.current = performance.now(); if (currentDroneFrameRef.current?.frame !== best.frame) { @@ -315,10 +317,14 @@ export default function StationOverlay({ currentFrame, currentTime, fps, visible // 중심선 + 나침반 캐시 빌드 (per-frame, 텍스트 계산 없음) useEffect(() => { - const drone = currentDroneFrameRef.current; - if (!drone || !visible) { renderCacheRef.current = null; return; } + if (!currentDroneFrameRef.current || !visible) { renderCacheRef.current = null; return; } const t0 = performance.now(); + // 중심선도 smoothFrame 적용 (텍스트와 동일한 스무딩) + const frames = allDroneFramesRef.current; + const drone = frames.length + ? smoothFrame(frames, currentFrameIdxRef.current, smoothHalfRef.current) + : currentDroneFrameRef.current; const params = paramsRef.current; const worldOrigin = worldOriginRef.current; const allCL = allCenterlinePointsRef.current; @@ -426,28 +432,12 @@ export default function StationOverlay({ currentFrame, currentTime, fps, visible const elapsed = (performance.now() - timeUpdateWallRef.current) / 1000; const estTime = currentTimeSecRef.current + elapsed; const frac = Math.min(0.999, (estTime * VIDEO_FPS) - frameNum); // 0~0.999 - const mapSize = labelMapRef.current.size; const labelsA = labelMapRef.current.get(frameNum); const labelsB = labelMapRef.current.get(frameNum + 1); // 두 프레임 사이 픽셀 좌표 보간 const interpY = (a: number, b: number | undefined) => b !== undefined ? a + (b - a) * frac : a; - // 디버그 표시 (좌하단) - ctx.font = '11px monospace'; - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - ctx.fillStyle = 'rgba(0,0,0,0.6)'; - ctx.fillRect(6, H - 54, 300, 48); - ctx.fillStyle = '#0f0'; - ctx.fillText(`frame: ${frameNum} frac: ${frac.toFixed(2)} mapSize: ${mapSize}`, 10, H - 50); - const firstSt = labelsA?.stationLabels[0]; - const firstStB = labelsB?.stationLabels[0]; - const dispY = firstSt ? interpY(firstSt.sy, firstStB?.sy) * H : 0; - ctx.fillText(`labels: ${labelsA?.stationLabels.length ?? '-'} firstY: ${firstSt ? dispY.toFixed(1) : '-'}px`, 10, H - 36); - ctx.fillText(`smooth: ±${smoothHalfRef.current}fr interp: ${frac.toFixed(2)}`, 10, H - 22); - ctx.textBaseline = 'alphabetic'; - if (labelsA) { const α = emaAlphaRef.current;