Skip to content

The infinite-zoom engine

packages/engine is the heart of the product. It is a zero-dependency TypeScript module, framework-agnostic, with unit tests covering the precision-critical math.

The problem

A naive “world coordinates + camera scale” approach collapses when the zoom range exceeds float64 precision: after ~2⁵⁰ of zoom, adjacent points become indistinguishable and strokes jitter or corrupt.

The solution: a cell tree (“deep zoom” portals)

The world is a quadtree of coordinate frames:

  • The root cell covers the whole world; its local space is [-1, 1]².
  • Each cell may have 4 children. A child’s local space is also [-1, 1]² and maps into its parent as parent = offset(quadrant) + 0.5 · local.
  • Cell ids are paths of quadrant digits ("013" = NW → NE → SE).

Consequences:

  1. Every coordinate is bounded. A stroke always lives inside roughly [-1.5, 1.5] of its cell — at any depth — so float64 precision is constant regardless of how deep you zoom.
  2. Composition is exact. Each level is a power-of-two scale plus a small offset; composing N levels multiplies by exact powers of two, which float64 represents without error. 512 levels ≈ 2⁵¹² zoom range.
  3. The camera keeps a focus cell plus a local view (pan, scale kept in [0.5, 2), rotation). Zooming in descends into the child containing the viewport center; zooming out ascends. All per-frame math stays bounded.

Rendering

Strokes are vector, but rendering is raster-cached per (cell, layer):

  • Each (cell, layer) with content has a small offscreen canvas (512 px across the cell’s extent) that replays its elements.
  • The visible frame composites only the tiles intersecting the viewport — O(screen), not O(document) — so canvases with millions of strokes pan at 60+ fps and zoom-in never upscales a cache (the camera descends cells before upscaling would be visible).
  • A bounded LRU evicts stale tiles; evicted tiles simply re-render on demand.
  • The in-flight stroke is drawn vector-straight to the screen each frame (live preview), then committed into its tile.
  • Eraser strokes are elements too: they replay with destination-out inside the tile, so vector erasing, undo, and per-layer erasing all work.

Element model

ElementDescription
strokepolyline with per-point pressure; rendered as a variable-width ribbon outline (left/right offset curves with round caps)
shaperect / ellipse with stroke style
fillsolid polygon from the fill (paint-bucket) tool; drawn under strokes/shapes
textvector text with font size (in cell units)
imageembedded raster (≤ 3 MB), positioned in cell space
bookmarka saved (cell, point) for navigation

Layers are world-level; each element belongs to a layer, and tiles are keyed by (cell, layer), which is what makes per-layer blend modes cheap.

Exports share one geometry path

The same ribbon outline builder feeds the canvas tiles, the SVG exporter (strokes become filled paths), and PNG. SVG export maps the cell tree onto nested <g transform="translate(…) scale(0.5)"> groups — a literal deep-zoom SVG — with eraser strokes expressed as masks, so exports match the screen exactly.

Tests

packages/engine/src/__tests__/ covers cell-tree transforms (including 60-level round-trips with < 1e-12 error), camera zoom/pan invariants (anchor stays fixed across depth transitions), serialization, history, and stroke geometry.