Spaces & cameras
Each Layer declares a coordinate space that decides
how its geometry is transformed to the screen. The renderer keeps one camera slot
per space, and groups add extra slots on demand.
space:"ui" overlay stays pinned to the canvas. LayerSpace
Section titled “LayerSpace”type LayerSpace = "world" | "ui";| Space | Coordinates | Camera applied? |
| ------------------- | ----------- | ---------------------------------------------------------- |
| "world" (default) | World units | Yes — the live camera (pan / zoom / rotate). |
| "ui" | CSS pixels | No — maps CSS px directly to NDC (overlays, text, chrome). |
A layer’s space selects which view-projection its draws use. world content
moves with the camera; ui content is screen-fixed (and is never
frustum-culled).
Camera slots
Section titled “Camera slots”The renderer packs one Camera uniform per space into a single GPU buffer at
aligned offsets, with one bind group per slot. The two base slots are fixed:
| Slot | Space |
| ---- | ------- |
| 0 | world |
| 1 | ui |
Slots beyond the base two are a growable pool reused frame-to-frame for the
distinct groups present that frame. A group-tagged draw routes to a dedicated
per-(space × group) slot whose uploaded matrix is spaceVP ∘ resolveGroupTransform(group)
— so a group transform applies live with no repack. Because each group needs its
own slot, a group-identity change is a DrawCommand
merge boundary.
Camera state & fitting
Section titled “Camera state & fitting”CameraState is the live camera the renderer transforms world content through;
all fields are optional and default to { x: 0, y: 0, zoom: 1, rotation: 0 }.
| Field | Default | Notes |
| ---------- | ------- | ----------------------------------------------- |
| x / y | 0 | World coordinate shown at the viewport center. |
| zoom | 1 | Zoom around the viewport center. |
| rotation | 0 | Rotation in radians around the viewport center. |
cameraStateForBounds(bounds, viewportWidth, viewportHeight, options?) computes
the camera that fits a Bounds2D into the viewport.
import { cameraStateForBounds } from "insomni";
const cam = cameraStateForBounds( { minX: 0, minY: 0, maxX: 800, maxY: 600 }, canvas.clientWidth, canvas.clientHeight, { padding: 0.9 }, // leave 10% slack; 1 fills the viewport);renderer.setCamera(cam);Bounds2D is { minX, minY, maxX, maxY }. FitCameraToBoundsOptions carries
the optional padding factor in (0, 1]. The function throws on non-finite or
inverted bounds.
View matrices
Section titled “View matrices”| Helper | Signature | Returns |
| ---------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------- |
| viewFromCamera(cam, viewportWidth, viewportHeight) | camera + viewport | Mat3 mapping world → CSS-pixel screen, anchored at the viewport center. |
| viewFromCameraInRect(cam, rx, ry, rw, rh) | camera + arbitrary rect | Mat3 mapping world → screen within that rect (world (cam.x, cam.y) lands at the rect center). |
| cameraViewportRect(viewport) | CameraViewport | The viewport’s absoluteFrame as a FrameRect. |
viewFromCamera is the special case of viewFromCameraInRect covering the full
target. The renderer uploads exactly this matrix to the world camera slot, and
the damage project hook below reuses it so projection stays byte-consistent.
Projecting AABBs: project
Section titled “Projecting AABBs: project”project converts an authoring-space AABB into a CSS-pixel FrameRect for
use as a damage / scissor region. It is the bridge between a changed layer’s
bounds and the damage system.
import { project } from "insomni";
const rect = project(minX, minY, maxX, maxY, "world", { camera: { x: 0, y: 0, zoom: 1, rotation: 0 }, group: null, dpr: window.devicePixelRatio, viewportWidth: canvas.clientWidth, viewportHeight: canvas.clientHeight,});ProjectOptions:
| Field | Type | Notes |
| ---------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| camera | ResolvedCameraState | Read only for space: "world". |
| group | Group \| null | Compounded transform applied before the camera (world only). |
| dpr | number | Read only by the world branch: device dims are recovered as viewportWidth/Height × dpr to build the camera projection over the device-px baseline. |
| viewportWidth / viewportHeight | number | Viewport extent in CSS px. |
Per-space behavior:
"ui"— returned verbatim in CSS px (camera + dpr ignored)."world"— resolve the group transform, transform all four AABB corners through it and the camera view (world → CSS px via the device-px baseline), take the screen-space min/max (correct for any rotation).
Viewport world bounds: viewportAabb
Section titled “Viewport world bounds: viewportAabb”viewportAabb(viewport, rect?) returns the world-space AABB of a
CameraViewport (the world rectangle currently visible), inflated by an AA
epsilon to absorb SDF edge falloff. Pass a rect to query a sub-rect of the
viewport; omit it for the full viewport.
import { viewportAabb } from "insomni";
const visibleWorld = viewportAabb(viewport); // { minX, minY, maxX, maxY }Rotation is ignored — under a rotated camera the result is a conservative over-approximation of what is on screen.