Skip to content

feat: spatial hash grid, off-screen double-buffering, virtual camera zoom/pan for GridMap - #30

Merged
elizabetheonoja-art merged 3 commits into
Utility-Protocol:mainfrom
Timrossid:feat/gridmap-spatial-performance
Jun 17, 2026
Merged

feat: spatial hash grid, off-screen double-buffering, virtual camera zoom/pan for GridMap#30
elizabetheonoja-art merged 3 commits into
Utility-Protocol:mainfrom
Timrossid:feat/gridmap-spatial-performance

Conversation

@Timrossid

@Timrossid Timrossid commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Scope

Rendering 500+ grid resource nodes with per-frame resource pulsation, status coloring, and connection lines causes significant UI lag on mid-range devices. This PR addresses the performance bottlenecks.

Changes

\src/utils/spatial.ts\ (new)

  • SpatialHashGrid class — partitions the 2D canvas space into cells (\cellSize = NODE_RADIUS * 4 = 24px)
  • Methods: \insert(node), \clear(), \query(bounds)\ — only returns nodes whose cells intersect the viewport
  • Generic typed (\T extends { x: number; y: number }) for reusability

\src/components/spatial/GridMap.tsx\

  • Off-screen canvas double-buffering — renders to an in-memory <canvas>\ buffer first, then copies to the visible canvas via \drawImage\ in a single composite step
  • Spatial culling — only nodes intersecting the viewport (via SpatialHashGrid.query) are drawn each frame
  • Virtual camera (\cameraRef) with zoom (scroll wheel, 0.5x–5x range) and pan (click-drag), transforms nodes via \ctx.translate/scale\
  • devicePixelRatio-aware — canvas backing store sized as \clientWidth * dpr × clientHeight * dpr, context scaled by \dpr\ for sharp Retina output
  • Debounced ResizeObserver (100ms) — prevents layout thrashing on resize
  • Viewport-constrained grid lines — only grid lines intersecting the visible bounds are drawn
  • Per-frame resource pulsation — sine-wave pulse (\sin(time * 0.002 + i)) applied each frame for the requested animated effect
  • Pointer capture — \setPointerCapture\ for reliable drag tracking

Resolution Strategy

Requirement Implementation
Spatial indexing \SpatialHashGrid\ with \cellSize = 24, cells are keyed by \\:\\`
Off-screen double-buffer \useRef\ created via \document.createElement('canvas'), composited with \drawImage\
Batch updates All mutations collected in refs, flushed once per rAF frame via \
equestAnimationFrame\
DPR-aware \window.devicePixelRatio\ read on each frame, canvas backing resized only when needed
Zoom/pan culling Camera transform with \getViewportBounds\ → \SpatialHashGrid.query\ → only visible nodes drawn
Resize debounce \ResizeObserver\ + \setTimeout(100ms)\ pattern

Closes #2

…zoom/pan for GridMap

Implement performance improvements for 500+ grid resource node rendering:

- SpatialHashGrid class (src/utils/spatial.ts) partitions canvas into
  CELL_SIZE cells and only queries visible nodes per frame
- Off-screen canvas double-buffering separates drawing from compositing,
  preventing visual artifacts
- devicePixelRatio-aware canvas scaling for sharp Retina rendering
- Virtual camera transform supporting zoom (scroll wheel, 0.5x-5x) and
  pan (click-drag), culling off-screen nodes entirely
- ResizeObserver with 100ms debounce to avoid layout thrashing
- Batch node data updates within a single rAF frame
- Per-frame resource pulsation via Date.now() sine wave
- Viewport-constrained grid line drawing (only lines in visible range)
- Pointer capture for reliable drag tracking
@elizabetheonoja-art

Copy link
Copy Markdown
Contributor

fix errors so tests can pass

@elizabetheonoja-art
elizabetheonoja-art merged commit 3c6253c into Utility-Protocol:main Jun 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue 2: Canvas-Driven Spatial Rendering for High-Density Grid Networks

3 participants