From ce7142fab1aa8de95d96d91eb7161c207b8b75d4 Mon Sep 17 00:00:00 2001 From: lantos1618 Date: Wed, 20 May 2026 19:58:07 +0000 Subject: [PATCH] fix(map): use flipY=true convention for avatar overlay projection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OrthographicView defaults to flipY: true, so positive world Y maps to positive screen Y. The HTML avatar overlay used `-` on the Y term, which mirrored avatars vertically relative to the ScatterplotLayer rings beneath them — visually obvious as avatars drifting away from their dot as you panned vertically. Co-Authored-By: Claude Opus 4.7 --- src/components/discover/SemanticMap.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/discover/SemanticMap.tsx b/src/components/discover/SemanticMap.tsx index b7293d7..40497a9 100644 --- a/src/components/discover/SemanticMap.tsx +++ b/src/components/discover/SemanticMap.tsx @@ -122,10 +122,14 @@ export function SemanticMap({ setViewState({ target: [bounds.cx, bounds.cy, 0], zoom: bounds.zoom }); }, [viewState, size, points.length, bounds.cx, bounds.cy, bounds.zoom]); - // Project each visible point's world coords to canvas pixel coords using - // OrthographicView's simple projection: pixelsPerWorldUnit = 2^zoom, and - // y is flipped vs world y. Matches deck.gl's internal projection so the - // HTML overlay stays aligned with the ScatterplotLayer rings underneath. + // Project each visible point's world coords to canvas pixel coords. + // OrthographicView defaults to `flipY: true`, meaning positive world Y points + // *down* in screen space (screen convention) — so both X and Y use `+`. A + // previous version had `-` on the Y term, which mirrored avatars vertically + // relative to the ScatterplotLayer rings underneath. + // ppu = pixelsPerWorldUnit = 2^zoom + // screen_x = canvas_w/2 + (world_x - target_x) * ppu + // screen_y = canvas_h/2 + (world_y - target_y) * ppu const screenPositions = useMemo(() => { if (!size || !viewState) return [] as Array<{ p: SemanticMapPoint; sx: number; sy: number; px: number }>; const ppu = Math.pow(2, viewState.zoom); @@ -134,7 +138,7 @@ export function SemanticMap({ return visible.map((p) => ({ p, sx: size.width / 2 + (p.x - cx) * ppu, - sy: size.height / 2 - (p.y - cy) * ppu, + sy: size.height / 2 + (p.y - cy) * ppu, px: avatarSizeFor(p.confidence), })); }, [visible, viewState, size]);