# Vectojs Three

> Use when embedding VectoJS in Three.js or WebXR with @vectojs/three, ThreeAdapter, canvas textures, raycaster input, UV-to-scene event routing, hover/wheel behavior, or disposal.

- Skill: `vectojs/vectojs-three` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add vectojs/vectojs-three`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vectojs/vectojs-three/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: vectojs (https://skillmd.com/u/vectojs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vectojs/vectojs-three

---


# VectoJS Three

Use this skill to place a live VectoJS 2D interface onto a Three.js texture and route 3D pointer input back into the VectoJS scene.

## Integration workflow

1. Install `@vectojs/core`, `@vectojs/three`, `three`, and `@vectojs/ui` if using UI components.
2. Create one `ThreeAdapter` per VectoJS panel texture.
3. Add VectoJS entities to `adapter.vectoScene`, then start the inner scene.
4. Add `adapter.mesh` or the adapter texture/material to the host Three.js scene.
5. In the host pointer/wheel loop, raycast against the adapter mesh and call `adapter.updateIntersection(...)`.
6. Call `adapter.dispose()` when removing the panel.

Read `references/three-recipes.md` for snippets.

## Constraints

- The default output is a flat textured plane, not DOM rendered in 3D.
- VectoJS logical hit-testing remains 2D even when the mesh is transformed in world space.
- The host application owns the camera, renderer, controls, XR session, raycaster, and occlusion rules.
- Raycast UVs should map to the adapter’s logical `width`/`height`; do not use backing-store size for layout math.
- Texture resolution affects sharpness and upload cost. Choose dimensions for the viewing distance.

## Common mistakes

| Mistake                                       | Correction                                                                       |
| --------------------------------------------- | -------------------------------------------------------------------------------- |
| Adding an offscreen adapter canvas to the DOM | Add `adapter.mesh` to Three.js; the canvas backs the texture.                    |
| Forgetting to call `updateIntersection`       | Route host raycaster events into the adapter each pointer/wheel event.           |
| Dispatching by screen coordinates             | Use raycast UVs through `ThreeAdapter`.                                          |
| Keeping disposed textures alive               | Call `adapter.dispose()` and remove host references.                             |
| Expecting full DOM a11y inside XR             | Treat the 3D panel as canvas texture; provide host-level semantics where needed. |

## Programmatic input and panel focus

The adapter can drive input without a raycaster or DOM — the entry point for
tests and automation:

- `dispatchPointer(type, x, y, init?)` synthesizes pointer events at **logical
  scene coordinates** through the same downstream path as
  `updateIntersection` (hover transitions, entity dispatch,
  pointerdown-driven focus, texture-dirty scheduling). Returns whether the
  point hit an entity. Wheel is deliberately not covered — route real
  `WheelEvent`s through `updateIntersection`.
- `dispatchKey(key, mods?, phase?)` synthesizes keyboard events
  (`phase` `'press'` is the default full keydown+keyup pair; `'keydown'`/
  `'keyup'` model held keys). With panel focus, the event hits the focused
  entity's projected mirror so core's own listeners run (Enter/Space
  activation included); otherwise — or after an unprevented pass-through — it
  reaches `window`, where the scene-level channel applies its ownership gates
  (`defaultPrevented`, auto-repeat, `ownsKeyboard`). A focused textbox-like
  role owns its keys exclusively; nothing leaks to the page.
- Panel focus mirrors DOM tabbability: `focus(entity)` / `blur()` /
  `focusedEntity` / `isFocusable(entity)` (projected `tabindex`,
  natively-focusable tags, interactive roles). A pointerdown focuses the
  nearest focusable ancestor of the hit; clicking empty background blurs.
  Focus flips are delivered as synthetic `focus`/`blur` events so caret and
  highlight visuals repaint immediately in `onDemand` mode.

## Version and backend gotchas (source-verified)

- **three ≤ 0.1.3**: `ThreeRenderer.flush()` performed a full GL render, and
  the Scene flushes around every non-batched node — frame cost grew O(N²) in
  entity count. three 0.1.4 renders once per frame via the `present()` hook;
  upgrade before profiling anything else.
- **Native input inside a texture is limited.** The adapter's canvas is
  offscreen, so the Scene's projected a11y elements are never connected to the
  document; `updateIntersection` falls back to VectoJS's own event dispatch.
  Buttons/hover/wheel work; full native IME/text editing does not — keep text
  entry outside the 3D panel or accept simplified input.
- **`stroke()` line width is effectively 1px** on most platforms
  (`LineBasicMaterial.linewidth` is a known WebGL limitation). Draw thick
  lines as filled shapes instead.
- **Texture caches (three 0.1.7+)**: both `fillText` and `drawImage` textures
  are cached with a 256-entry LRU (text keyed by font|color|text). Before 0.1.7
  `drawImage` allocated a texture per call per frame, so reusing a source canvas
  mattered more; it is still good practice for very large images.
- **GPU context loss + runtime DPR (three 0.1.7+)**: `ThreeRenderer` recovers on
  its own. `webglcontextlost` is `preventDefault()`-ed (required, or the browser
  never fires the restore event) and flips `isContextLost()`, which makes
  `present()` a no-op while lost; `webglcontextrestored` re-applies pixel ratio
  and size (a restore can land on a different display) and forces a repaint. A
  `(resolution: Ndppx)` media query re-applies `setPixelRatio` when DPR changes
  at runtime (monitor move, browser zoom) and re-arms itself. All of it is
  guarded for SSR/OffscreenCanvas and torn down in `dispose()`. Do not add your
  own context-loss listener — you would fight the built-in recovery.

