diff --git a/docs/architecture.md b/docs/architecture.md index 124a686..40ba457 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -46,7 +46,8 @@ apps/web ──► apps/proxy ──► packages/tts ──► packages/shared ### Edges - **`apps/web` → `apps/proxy`** — calls `/search`, `/items/:id`, - `/items/:id/file`, and `/asset` (CORS-safe asset proxy) over HTTP. + `/items/:id/file`, `/asset` (CORS-safe asset proxy), and `/trace` + (image → vector shape) over HTTP. - **`apps/web` → `packages/extract`** — uses `buildTree` / `collectRefs` to analyze a loaded `TTSMod` in the browser (tree sidebar + asset refs). - **`apps/proxy` → `packages/tts`** — calls `fetchMod` / `getFileName` to serve @@ -80,6 +81,9 @@ apps/web ──► apps/proxy ──► packages/tts ──► packages/shared | `@hono/node-server`| Node adapter for Hono | `apps/proxy` | | `@hono/cors` | CORS middleware | `apps/proxy` | | `bson` | BSON deserialization of TTS save files | `packages/tts` | +| `@visioncortex/vtracer` | Raster-to-SVG vectorization (wasm) | `apps/proxy` | +| `sharp` | Image decoding to RGBA | `apps/proxy` | +| `svgpath` | SVG path parsing for traced shapes | `apps/proxy` | | `cheerio` | Workshop browse page scraping | `apps/proxy` | | `zod` | Runtime validation | `apps/proxy`, `packages/shared` | | `three` | 3D rendering | `apps/web` | @@ -91,7 +95,11 @@ apps/web ──► apps/proxy ──► packages/tts ──► packages/shared - **`cheerio` is backend-only** — it never appears in `packages/extract`, which must stay isomorphic. -- **`bson` is Node-only** — used by the fetcher, not the analysis layer. +- **`bson` is Node-only** — used by the fetcher and the trace route, not the + analysis layer. +- **`@visioncortex/vtracer`, `sharp`, `svgpath` are backend-only** — the trace + route lives in `apps/proxy`; they never appear in `packages/extract`, which + must stay isomorphic. - **`packages/extract` has zero external runtime deps** — it relies only on platform `fetch` / `Blob`, keeping it portable to a future frontend. @@ -107,7 +115,7 @@ apps/web ──► apps/proxy ──► packages/tts ──► packages/shared - The proxy runs as a single Node process via `@hono/node-server`. - `apps/web` is a static Vite build served separately; during development it - proxies `/search`, `/items`, `/health`, and `/asset` to the proxy. + proxies `/search`, `/items`, `/health`, `/asset`, and `/trace` to the proxy. - `packages/extract` is published/consumed as a plain ESM module usable from a browser bundle or Node. - No shared state between requests; each request fetches fresh. \ No newline at end of file diff --git a/docs/decisions.md b/docs/decisions.md index 46838bc..3cf169e 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -157,4 +157,25 @@ headers, which would block three.js `TextureLoader` / `GLTFLoader` in the browser. Routing asset fetches through the proxy makes them loadable. **Alternatives considered:** Loading assets directly in the browser. Rejected — -CORS failures on common Workshop hosts would break the viewers. \ No newline at end of file +CORS failures on common Workshop hosts would break the viewers. + +## D13 — Server-side image tracing to a mesh-ready shape + +**Decision:** The proxy exposes `GET /trace?url=...`, which fetches an image, +traces it into a vector shape, and returns the result BSON-encoded. Tracing is +configurable: `mode` (`alpha` default, `bw`, `color`) selects how the region is +derived, and `format` (`shape` default, `svg`) selects the response. The +`shape` format returns a parsed `{ outline, holes }` polygon matching +`@tts/mesh`'s `Shape` interface. + +**Context:** The user wants to build a mesh from an image (e.g. a token or tile +art) using `@tts/mesh`. vtracer only returns SVG, but the mesh package consumes +`{ outline, holes }`. Tracing on the server keeps the SVG-to-shape parsing out +of the browser and lets the BSON response carry a ready-to-extrude shape. +`sharp` decodes the image to RGBA so alpha/luminance masks can be built before +feeding vtracer's `convertPixels`. + +**Alternatives considered:** Returning only the raw SVG and parsing in the web +app near `@tts/mesh`; tracing by color only (no alpha). Rejected — server-side +parsing yields a shape the mesh package can consume directly, and alpha-based +tracing (the default) is the common case for token/tile art. \ No newline at end of file diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 8d6a16f..0fd9ccd 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -63,6 +63,8 @@ tts-workshop/ │ │ │ ├── search.ts # GET /search?q=...&page=1 │ │ │ ├── items.ts # GET /items/:id, /items/:id/file │ │ │ ├── asset.ts # GET /asset?url=... (CORS-safe asset proxy) +│ │ │ ├── trace.ts # GET /trace?url=... (image → vector shape) +│ │ │ ├── svgShape.ts # parse vtracer SVG into { outline, holes } │ │ │ └── health.ts # GET /health │ │ └── env.ts # zod env validation │ └── web/ @@ -202,6 +204,17 @@ Hono server exposing search + fetch. it back with a `Content-Type` header. Workshop hosts often omit CORS headers, which would block three.js loaders in the browser; routing through the proxy makes those assets loadable. Only `http(s)` URLs are allowed. +- `routes/trace.ts` + - `GET /trace?url=...&mode=alpha&threshold=128&format=shape` — fetch an + image, trace it into a vector shape, and return the result BSON-encoded. + `mode` is `alpha` (default), `bw`, or `color`; `format` is `shape` + (default) or `svg`. `shape` returns a parsed `{ outline, holes }` polygon + matching `@tts/mesh`'s `Shape` interface, ready to extrude. +- `routes/svgShape.ts` + - `parseSvgShape(svg)` — parse a vtracer SVG into a `TracedShape` + (`{ outline, holes }`): flatten beziers to polylines, split subpaths into + rings, classify by winding (CCW outline / CW hole), and assign holes to + their containing outline. - `routes/health.ts` - `GET /health` — liveness. - `env.ts` — zod validation of `STEAM_API_KEY`, `PORT`. @@ -259,6 +272,7 @@ proxy API and `packages/extract` directly for analysis. | GET | `/items/:id` | Full parsed `TTSMod` (`?fileUrl=` skips key) | key* | | GET | `/items/:id/file` | Raw save bytes, filename from header | key* | | GET | `/asset?url=` | CORS-safe proxy for external assets | — | +| GET | `/trace?url=&mode=&format=` | Trace an image into a vector shape (BSON) | — | \* `STEAM_API_KEY` is optional; `/items/*` works without it when a `fileUrl` query param is supplied. @@ -275,12 +289,17 @@ query param is supplied. download BSON → parse → TTSMod ↓ packages/extract → flatten objects / extract refs / download assets + ↓ +/trace?url=... → sharp decode → vtracer → parse SVG → BSON shape ``` ## Dependencies - `hono`, `@hono/node-server`, `@hono/cors` — server. -- `bson` — BSON deserialization. +- `bson` — BSON deserialization (save files + trace results). +- `@visioncortex/vtracer` — raster-to-SVG vectorization (wasm). +- `sharp` — image decoding to RGBA for tracing. +- `svgpath` — SVG path parsing for traced shapes. - `cheerio` — Workshop browse page scraping (backend only). - `zod` — validation. - `react`, `react-dom`, `react-router-dom`, `zustand` — frontend.