From 6c3f277d5f37f38b6fbde5f7e62f69674d79455d Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Sun, 9 Aug 2026 17:11:08 +0200 Subject: [PATCH] build(readme): sync only shared blocks so packages keep their own READMEs --- .github/scripts/sync-package-readmes.mjs | 107 +++++-- .github/workflows/ci.yml | 3 + AGENTS.md | 24 ++ README.md | 11 +- package.json | 1 + packages/core/README.md | 331 +++++++--------------- packages/polycss/README.md | 136 +++++---- packages/react/README.md | 264 +++++++++--------- packages/vue/README.md | 338 ++++++++++++----------- 9 files changed, 612 insertions(+), 603 deletions(-) diff --git a/.github/scripts/sync-package-readmes.mjs b/.github/scripts/sync-package-readmes.mjs index f6ec7a3e..9fc3e125 100644 --- a/.github/scripts/sync-package-readmes.mjs +++ b/.github/scripts/sync-package-readmes.mjs @@ -1,40 +1,111 @@ -import { copyFileSync } from "node:fs"; +/** + * Syncs the SHARED blocks of the root README into each package README. + * + * Each package README is a real, hand-written, committed file — what you read + * in the repo is what publishes to npm. This script only refreshes the regions + * delimited by: + * + * + * + * Everything between those blocks is package-specific and never touched. A + * package opts in per block simply by containing the matching markers; a + * package with no markers (or a subset) is left alone accordingly. + * + * Runs as `prepack` in every publishable package, so a stale block can never + * reach npm. Run with `--check` in CI to fail on drift instead of writing. + */ +import { readFileSync, writeFileSync } from "node:fs"; import { dirname, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", ".."); const source = resolve(repoRoot, "README.md"); + const targets = [ "packages/core/README.md", "packages/polycss/README.md", "packages/react/README.md", "packages/vue/README.md", -]; -const packageSpecificTargets = [ "packages/fonts/README.md", "packages/morph/README.md", ]; -const invokedFrom = relative(repoRoot, process.cwd()); -const invokedFromPackageReadme = invokedFrom.startsWith("packages/") - ? `${invokedFrom}/README.md` - : undefined; +const checkOnly = process.argv.includes("--check"); -if ( - invokedFromPackageReadme !== undefined - && packageSpecificTargets.includes(invokedFromPackageReadme) -) { - console.log(`[sync-package-readmes] preserved ${invokedFromPackageReadme}`); - process.exit(0); +const blockRe = (name) => + new RegExp( + `[\\s\\S]*?`, + ); + +/** Every block name the root README publishes, in document order. */ +function sharedBlockNames(text) { + return [...text.matchAll(//g)].map( + (m) => m[1], + ); } -if (invokedFromPackageReadme !== undefined && !targets.includes(invokedFromPackageReadme)) { - console.log(`[sync-package-readmes] skipped for ${invokedFromPackageReadme}`); - process.exit(0); +const rootText = readFileSync(source, "utf8"); +const names = sharedBlockNames(rootText); + +if (names.length === 0) { + console.error( + "[sync-package-readmes] no shared blocks found in the root README — refusing to run", + ); + process.exit(1); +} + +const blocks = new Map(); +for (const name of names) { + const match = rootText.match(blockRe(name)); + if (!match) { + console.error( + `[sync-package-readmes] block "${name}" has a start marker but no end marker`, + ); + process.exit(1); + } + blocks.set(name, match[0]); } +const drifted = []; +let updated = 0; + for (const target of targets) { - copyFileSync(source, resolve(repoRoot, target)); + const path = resolve(repoRoot, target); + let text; + try { + text = readFileSync(path, "utf8"); + } catch { + continue; + } + + let next = text; + for (const [name, block] of blocks) { + const re = blockRe(name); + if (re.test(next)) next = next.replace(re, block); + } + + if (next === text) continue; + if (checkOnly) { + drifted.push(target); + continue; + } + writeFileSync(path, next); + updated += 1; + console.log(`[sync-package-readmes] updated ${relative(repoRoot, path)}`); +} + +if (checkOnly) { + if (drifted.length > 0) { + console.error( + `[sync-package-readmes] shared blocks are stale in:\n ${drifted.join("\n ")}\n` + + "Edit the block in the root README, then run `pnpm sync:readmes`.", + ); + process.exit(1); + } + console.log("[sync-package-readmes] shared blocks are up to date"); + process.exit(0); } -console.log(`[sync-package-readmes] copied README.md to ${targets.length} package READMEs`); +console.log( + `[sync-package-readmes] ${updated} README${updated === 1 ? "" : "s"} updated, ${names.length} shared block${names.length === 1 ? "" : "s"}`, +); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e651c019..c7724eb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Check README shared blocks + run: pnpm check:readmes + - name: Run tests run: pnpm test diff --git a/AGENTS.md b/AGENTS.md index b4226375..c30f7c6f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -189,6 +189,7 @@ Before opening a PR: - [ ] If I touched the canvas atlas pipeline (`rasterise.ts` / `buildAtlasPages.ts`), browser-feature detection, or direct voxel renderer in ONE renderer, the same fix lands in the other two renderers (`polycss` + react + vue) in this PR. - [ ] If I touched any of the three `styles.ts` (`packages/polycss/src/styles/styles.ts`, `packages/react/src/styles/styles.ts`, `packages/vue/src/styles/styles.ts`), the other two are consistent — CSS rules cover every emitted tag for both lighting modes, and shared properties like `will-change: transform` on `.polycss-scene` exist in all three. - [ ] Website docs (`website/src/content/docs/**`) and READMEs reflect any user-visible change. +- [ ] If I edited a `` block, I edited it in the ROOT `README.md` and ran `pnpm sync:readmes` (see "Package READMEs" below). - [ ] If I changed a render strategy, lighting mode, naming convention, or the JS-in-render-loop rules, `AGENTS.md` reflects the new state in this same PR. ## Iterating on the system @@ -200,6 +201,29 @@ The rendering model, tag table, lighting modes, and naming conventions described - **Same-PR sync.** Any PR that adds, removes, or materially changes a render strategy, lighting mode, naming rule, or cross-package contract must update `AGENTS.md` in the same PR. An API change that lands without an AGENTS.md update is an incomplete change. - **Don't append-only.** Prune content that no longer reflects the codebase. If a strategy is dropped, remove its row from the tag table — don't leave a "deprecated" note. If a hook is renamed, update the naming section in place — don't list the old name "for reference". +## Package READMEs + +Each `packages/*/README.md` is a real, hand-written, committed file that is +published to npm **as-is**. What you read in the repo is what ships — there is +no generated README. + +Regions wrapped in `` / +`` are the exception: they are owned by the +root `README.md` and mirrored into every package README that contains the +matching markers. Current blocks are `links`, `packages`, `showcase`, and +`license`. + +- **Edit a shared block in the root `README.md`, never in a package README.** + Then run `pnpm sync:readmes`. +- Everything outside the markers is package-specific. Write it in the root + README's voice, but say what that package actually does — `core` documents + core, `vue` shows Vue code. +- `.github/scripts/sync-package-readmes.mjs` runs as `prepack` in every + publishable package, so a stale shared block cannot reach npm. +- CI runs `pnpm check:readmes`, which fails on drift instead of writing. +- A package opts in per block simply by containing the markers. `fonts` and + `morph` carry none today and are left entirely alone. + ## Backward compatibility - **No BC shims.** Clean breaks only. No re-export aliases for renamed symbols. No `@deprecated` wrappers. If the API changes, callers update. diff --git a/README.md b/README.md index df5ac333..b48a30f2 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,13 @@ A CSS polygon mesh library. A 3D engine for the DOM. Renders OBJ/MTL, STL, glTF/GLB, and VOX as real HTML elements transformed with CSS `matrix3d(...)`. Supports colors, textures, lighting, shadows, shapes and animations. Works with React, Vue or plain JavaScript. + Visit [polycss.com](https://polycss.com) for docs and model examples. -Join [chat.polycss.com](https://chat,polycss.com) for support and community discussions. +Join [chat.polycss.com](https://chat.polycss.com) for support and community discussions. PolyCSS primitives banner + ## Installation @@ -233,6 +235,7 @@ Each visible polygon is emitted as one leaf element; the renderer chooses the le - `` clips solid polygons with `border-shape: polygon(...)` when the browser supports it. - `` maps a packed texture-atlas slice with `background-image`, and is the fallback for textured or unsupported shapes. + ## Packages | Package | Description | @@ -241,8 +244,11 @@ Each visible polygon is emitted as one leaf element; the renderer chooses the le | `@layoutit/polycss` | Vanilla custom elements and imperative `createPolyScene` API. | | `@layoutit/polycss-react` | React components, hooks, controls, and core re-exports. | | `@layoutit/polycss-vue` | Vue 3 components, composables, controls, and core re-exports. | +| `@layoutit/polycss-fonts` | Fonts + text → extruded 3D `Polygon[]`. Framework-agnostic. | | `@layoutit/polycss-morph` | Prepared-model loading, retained DOM animation, morph targets, skinning, and playback. | + + ## Made with PolyCSS [cssQuake](https://cssquake.com) @@ -255,7 +261,10 @@ Each visible polygon is emitted as one leaf element; the renderer chooses the le -> A CSS Terrain Generator layoutit-terra + + ## License MIT. + diff --git a/package.json b/package.json index 8e437054..1f89b541 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test": "pnpm --filter './packages/*' -r --if-present test", "test:coverage": "pnpm --filter './packages/*' -r --if-present test:coverage", "sync:readmes": "node .github/scripts/sync-package-readmes.mjs", + "check:readmes": "node .github/scripts/sync-package-readmes.mjs --check", "publish:all": "pnpm sync:readmes && pnpm --filter './packages/*' -r publish --access public", "dev:website": "pnpm --filter @layoutit/polycss-website dev", "build:website": "pnpm --filter @layoutit/polycss-website build", diff --git a/packages/core/README.md b/packages/core/README.md index 9e379187..fc3550e8 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,250 +1,123 @@ -# PolyCSS +# @layoutit/polycss-core -A CSS polygon mesh library. A 3D engine for the DOM. Renders OBJ/MTL, STL, glTF/GLB, and VOX as real HTML elements transformed with CSS `matrix3d(...)`. Supports colors, textures, lighting, shadows, shapes and animations. Works with React, Vue or plain JavaScript. +The pure-math core of [PolyCSS](https://polycss.com). Vec3/polygon math, scene +and camera math, mesh parsers, mesh optimization, lighting, shadow projection, +and texture-atlas planning — with **zero browser globals** (built against +`lib: ES2020` only). -Visit [polycss.com](https://polycss.com) for docs and model examples. - -PolyCSS primitives banner - -## Installation - -```bash - -# Vanilla -npm install @layoutit/polycss - -# React -npm install @layoutit/polycss-react - -# Vue -npm install @layoutit/polycss-vue - -``` - -You can also load PolyCSS directly from a CDN. Here is a minimal custom-element scene: - -```html - - - - - - - - -``` - -PolyCSS intro - -## Framework Components - -React and Vue expose the same component model. `` owns the viewpoint, `` owns lighting and options, and `` loads or receives polygon data. - -```tsx -import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react"; - -export default function App() { - return ( - - - - - - - ); -} -``` - -## Three.js Parity API - -When porting Three.js scenes or generating code with an agent, use the explicit -`*/three` subpaths: - -- `@layoutit/polycss-core/three` -- `@layoutit/polycss/three` -- `@layoutit/polycss-react/three` -- `@layoutit/polycss-vue/three` - -They expose Three-like `PerspectiveCamera`, `OrthographicCamera`, `Object3D`, -`Vector3`, `DirectionalLight`, `PointLight`, `AmbientLight`, radians for object -rotations, Y-up authoring coordinates, and `camera.position` + `camera.lookAt(...)` -framing. The adapters convert into native PolyCSS coordinates with a right-handed -axis map, so the apparent object size, projection, orientation, depth ordering, -and light direction line up with Three.js scene math while still rendering -through the DOM. - -```tsx -import { PolyScene } from "@layoutit/polycss-react"; -import { - DirectionalLight, - PolyThreeMesh, - PolyThreePerspectiveCamera, -} from "@layoutit/polycss-react/three"; - -const sun = new DirectionalLight("#ffffff", 1); -sun.position.set(3, 5, 4); -sun.target.position.set(0, 0, 0); - -export function App() { - return ( - - - - - - ); -} -``` - -Full reference: [polycss.com/api/three-parity](https://polycss.com/api/three-parity). - -## API Reference - -### PolyCamera - -- `rotX`, `rotY` control the orbit angle in degrees. -- `zoom` scales the projected scene. -- `target` pans the camera target in world coordinates. -- `distance` adds dolly pull-back. -- `PolyCamera` is the orthographic default. Use `PolyPerspectiveCamera` when you want perspective depth. - -### PolyScene - -- `polygons` renders a static `Polygon[]` directly. -- `directionalLight`, `pointLights` (direction-only, baked mode; optional per-light `castShadow`), and `ambientLight` control scene lighting. -- `textureLighting` chooses `"baked"` or `"dynamic"`. -- `textureQuality` controls atlas raster budget. -- `strategies` can disable selected render strategies for diagnostics. -- `autoCenter` rotates around the rendered mesh bounds instead of world origin. - -### PolyMesh +This package does not render anything. It has no DOM access, emits no elements, +and injects no CSS. If you want to draw a scene, install a renderer instead: -- `src` loads `.obj`, `.gltf`, `.glb`, or `.vox` files. -- `mtl` loads companion OBJ materials. -- `polygons` accepts pre-parsed geometry. -- `position`, `scale`, and `rotation` transform the mesh wrapper. -- `autoCenter` shifts the mesh bbox center to local origin. -- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. STL imports use the conservative lossless path in both modes. -- `castShadow` emits CSS-projected shadows in dynamic lighting mode. - -### Controls - -- `` adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate. -- `` uses pan-first map-style input. -- `` provides keyboard and pointer-look navigation. -- `` adds translate/rotate gizmos for selected mesh handles. - -### Snapshot Export - -The vanilla package exports `exportPolySceneSnapshot(target)`. It clones the current rendered `.polycss-camera` / `.polycss-scene` DOM, injects only the PolyCSS CSS needed by that snapshot, inlines CSS `url(...)` image assets as `data:image/...;base64,...`, strips scripts and inline event handlers, and returns a standalone HTML document string with no PolyCSS runtime import. It works with rendered React/Vue scenes too; import it from `@layoutit/polycss` and pass the rendered camera or scene element. - -```ts -import { exportPolySceneSnapshot } from "@layoutit/polycss"; +| Package | Use it for | +|---|---| +| [`@layoutit/polycss`](https://www.npmjs.com/package/@layoutit/polycss) | Vanilla JS renderer + custom elements (``) | +| [`@layoutit/polycss-react`](https://www.npmjs.com/package/@layoutit/polycss-react) | React components and hooks | +| [`@layoutit/polycss-vue`](https://www.npmjs.com/package/@layoutit/polycss-vue) | Vue 3 components and composables | -const html = await exportPolySceneSnapshot(scene.host); -``` +All three renderers depend on this package and re-export most of its surface, so +you rarely install it directly. Reach for it when you need PolyCSS geometry work +**outside a browser** — a Node build step, a worker, a test, a server-side mesh +pipeline, or your own renderer. -If any referenced asset cannot be inlined, the function throws `PolySceneSnapshotError` with `code: "ASSET_INLINE_FAILED"`. + +Visit [polycss.com](https://polycss.com) for docs and model examples. -### Polygon Data Model +Join [chat.polycss.com](https://chat.polycss.com) for support and community discussions. -Each polygon describes one renderable face: +PolyCSS primitives banner + -```ts -const polygons = [ - { - vertices: [[0, 0, 0], [60, 0, 0], [0, 60, 0]], - color: "#f97316", - }, - { - vertices: [[0, 0, 0], [60, 0, 0], [60, 60, 0], [0, 60, 0]], - texture: "/texture.png", - uvs: [[0, 0], [1, 0], [1, 1], [0, 1]], - }, -]; -``` +## Installation -Render polygons directly when you need per-face DOM events or custom styling: - -```tsx - - - {polygons.map((polygon, index) => ( - console.log("clicked polygon", index)} - className="my-polygon" - /> - ))} - - +```bash +npm install @layoutit/polycss-core ``` -## Loading Mesh Files +## Parsing a mesh without a browser -Use `loadMesh()` to parse supported model formats: +The parsers (`parseObj`, `parseStl`, `parseGltf`, `parseVox`, `parseMtl`) are +synchronous functions over already-loaded bytes and strings, so they run under +Node. `parseGltf` has two caveats: `.gltf` files with external `.bin` buffers +need an `options.resolveBuffer` callback returning the bytes as a `Uint8Array` +**synchronously** (returning a Promise throws — read the buffers first), and +embedded images mint blob object URLs, so callers must call `result.dispose()` +when done with the mesh. `dispose()` is idempotent, and a no-op for the other +parsers. `loadMesh` is the convenience wrapper on top: it fetches a URL and +dispatches by extension, so it needs `fetch` and is not pure. ```ts -import { createPolyCamera, createPolyScene, loadMesh } from "@layoutit/polycss"; - -const host = document.getElementById("polycss")!; -const camera = createPolyCamera({ rotX: 65, rotY: 45 }); -const scene = createPolyScene(host, { camera }); +import { readFile } from "node:fs/promises"; +import { parseObj, optimizeMeshPolygons } from "@layoutit/polycss-core"; -const mesh = await loadMesh("https://polycss.com/gallery/obj/cottage.obj", { - mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl", -}); +const result = parseObj(await readFile("cottage.obj", "utf8")); +const optimized = optimizeMeshPolygons(result.polygons, { meshResolution: "lossy" }); -scene.add(mesh); +console.log(result.polygons.length, "→", optimized.length); ``` -Supported formats: - -- OBJ + MTL, including `map_Kd` textures and UV coordinates. -- STL triangle meshes, including binary Magics face colors. STL has no standard units, textures, UVs, or hierarchy, so imports skip lossy simplification and ray-based interior culling. -- glTF / GLB, including embedded images and `TEXCOORD_0`. -- MagicaVoxel `.vox`, with direct voxel fast paths when eligible. -- Generated primitives: box, plane, ring, sphere, torus, cylinder, cone, and Platonic solids. - -## Performance - -PolyCSS renders through the DOM, so performance is mostly shaped by two things: the number of mounted leaves, and the amount of texture atlas area the browser has to paint. The renderer tries to keep the common cases cheap. Simple surfaces stay as solid CSS elements, while textured, irregular, or high-detail geometry falls back to atlas-backed slices only when needed. - -Each visible polygon is emitted as one leaf element; the renderer chooses the least expensive CSS primitive that can represent the polygon, then uses `matrix3d(...)` to place that primitive in 3D space. - -- `` uses `background: currentColor` on a fixed box for solid rectangles and stable quads. -- `` uses `corner-shape` for stable triangles and beveled-corner solids, with a `border-width` triangle fallback when needed. -- `` clips solid polygons with `border-shape: polygon(...)` when the browser supports it. -- `` maps a packed texture-atlas slice with `background-image`, and is the fallback for textured or unsupported shapes. - -## Packages - -| Package | Description | -|---|---| -| `@layoutit/polycss-core` | Pure math, parsers, lighting, camera helpers, mesh optimization. Zero browser globals. | -| `@layoutit/polycss` | Vanilla custom elements and imperative `createPolyScene` API. | -| `@layoutit/polycss-react` | React components, hooks, controls, and core re-exports. | -| `@layoutit/polycss-vue` | Vue 3 components, composables, controls, and core re-exports. | - -## Made with PolyCSS - -[cssQuake](https://cssquake.com) --> A CSS port of Quake (1996) - -quake - - -[Layoutit Terra](https://terra.layoutit.com) --> A CSS Terrain Generator - -layoutit-terra - +Supported formats: OBJ (+ MTL), STL (ASCII and binary, including Magics face +colors), glTF / GLB (embedded images, `TEXCOORD_0`), and MagicaVoxel `.vox`. +`loadMesh` fetches and dispatches by extension; the `parse*` functions take +already-loaded input. + +## What's in here + +- **Types** — `Polygon`, `PolyMaterial`, `Vec2`, `Vec3`, the `PolyTexture*` + presentation types, `PolyDirectionalLight`, `PolyPointLight`, + `PolyAmbientLight`, `PolyTextureLightingMode`, `MeshResolution`. +- **Scene + camera math** — `buildSceneContext`, `computeSceneBbox`, + `normalizePolygons`, `createIsometricCamera`, `buildPolyCameraSceneTransform`, + `capturePolyCameraSnapshot`, `screenToWorldRay`, `screenToWorldOnSphere`, + `BASE_TILE`. +- **Transforms** — `buildPolyMeshTransform`, `buildPolySceneTransform`, + rotation and quaternion helpers. +- **Color + lighting** — `parseColor`, `parsePureColor`, `shadeColor`, + `computeShapeLighting`. +- **Primitives** — `boxPolygons`, `planePolygons`, `spherePolygons`, + `cylinderPolygons`, `conePolygons`, `torusPolygons`, `ringPolygons`, + `axesHelperPolygons`, and the Platonic solids. +- **Mesh optimization** — `optimizeMeshPolygons`, `mergePolygons`, + `dedupeOverlappingPolygons`, `cullInteriorPolygons`, + `simplifyTriangleMeshPolygons`, `repairMeshSeams`. +- **Culling** — `polygonFacesCamera`, `polygonCssSurfaceNormal`, + `cameraCullNormalGroups`, and the voxel camera-cull helpers. +- **Shadow projection** — `buildParametricCasterOverride`, + `computeParametricShadowSilhouette`, `computeCoverageShadowSilhouette`, + `projectCssVertexToGround`, `convexHull2D`. +- **Atlas planning** — the pure-math half of the texture atlas pipeline. Canvas + rasterisation itself lives in each renderer, because it needs the DOM. + +The package has two public entry points: the root (`@layoutit/polycss-core`, +exported from `src/index.ts`) and the Three.js parity subpath +(`@layoutit/polycss-core/three`, described below). Everything they export is the +supported surface; anything else is implementation detail. + +## Authoring polygons directly + +If you build `Polygon[]` by hand, read +[Authoring Polygons](https://polycss.com/core-concepts#authoring-polygons) +first. Three constraints bite immediately: + +- **Winding is CCW seen from the outside.** Vertex order sets the face normal + via the right-hand rule, and PolyCSS backface-culls, so a reversed face is + invisible. +- **`color` accepts hex and `rgb()`/`rgba()` only** — not CSS named colors. +- **Non-triangular polygons must be coplanar.** Only the React/Vue + `` entry point runs `normalizePolygons` for you + (fan-triangulating non-coplanar n-gons); `scene.add(...)`, + ``, ``, and `` do not. + +## Three.js parity + +`@layoutit/polycss-core/three` exposes Three-like math wrappers (`Vector3`, +`Euler`, `Object3D`, `PerspectiveCamera`, `OrthographicCamera`, +`DirectionalLight`, `PointLight`, `AmbientLight`) plus +`transformPolygonsToPoly` for converting Y-up authoring geometry into native +PolyCSS coordinates. See +[polycss.com/api/three-parity](https://polycss.com/api/three-parity). + + ## License MIT. + diff --git a/packages/polycss/README.md b/packages/polycss/README.md index 9e379187..b6d2fe5e 100644 --- a/packages/polycss/README.md +++ b/packages/polycss/README.md @@ -2,9 +2,13 @@ A CSS polygon mesh library. A 3D engine for the DOM. Renders OBJ/MTL, STL, glTF/GLB, and VOX as real HTML elements transformed with CSS `matrix3d(...)`. Supports colors, textures, lighting, shadows, shapes and animations. Works with React, Vue or plain JavaScript. + Visit [polycss.com](https://polycss.com) for docs and model examples. +Join [chat.polycss.com](https://chat.polycss.com) for support and community discussions. + PolyCSS primitives banner + ## Installation @@ -36,25 +40,33 @@ You can also load PolyCSS directly from a CDN. Here is a minimal custom-element PolyCSS intro -## Framework Components +## Imperative API -React and Vue expose the same component model. `` owns the viewpoint, `` owns lighting and options, and `` loads or receives polygon data. +`createPolyCamera` owns the viewpoint, `createPolyScene` owns lighting and +options, and meshes are added to the scene: -```tsx -import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react"; +```ts +import { + createPolyBox, + createPolyCamera, + createPolyOrbitControls, + createPolyScene, +} from "@layoutit/polycss"; -export default function App() { - return ( - - - - - - - ); -} +const host = document.getElementById("polycss")!; +const camera = createPolyCamera({ rotX: 65, rotY: 45 }); +const scene = createPolyScene(host, { camera, textureLighting: "dynamic" }); + +createPolyOrbitControls(scene, { drag: true, wheel: true }); + +scene.add(createPolyBox({ size: 100, color: "#ffd166" })); ``` +Using React or Vue instead? Install +[`@layoutit/polycss-react`](https://www.npmjs.com/package/@layoutit/polycss-react) +or [`@layoutit/polycss-vue`](https://www.npmjs.com/package/@layoutit/polycss-vue), +which expose the same model as components. + ## Three.js Parity API When porting Three.js scenes or generating code with an agent, use the explicit @@ -113,31 +125,49 @@ Full reference: [polycss.com/api/three-parity](https://polycss.com/api/three-par - `distance` adds dolly pull-back. - `PolyCamera` is the orthographic default. Use `PolyPerspectiveCamera` when you want perspective depth. -### PolyScene +### Scene options (`createPolyScene`) -- `polygons` renders a static `Polygon[]` directly. +- Geometry enters through `scene.add(parseResult, transform)` — there is **no** `polygons` option. In markup, use `` or `` children. - `directionalLight`, `pointLights` (direction-only, baked mode; optional per-light `castShadow`), and `ambientLight` control scene lighting. - `textureLighting` chooses `"baked"` or `"dynamic"`. -- `textureQuality` controls atlas raster budget. +- `textureQuality` controls atlas raster budget; `textureLeafSizing`, `textureImageRendering`, `textureBackend`, and `textureProjection` set per-polygon texture defaults. - `strategies` can disable selected render strategies for diagnostics. -- `autoCenter` rotates around the rendered mesh bounds instead of world origin. +- `autoCenter` rotates around the union bbox of all added meshes instead of world origin, updating as meshes are added or removed. Individual meshes opt out with `excludeFromAutoCenter`. + +### Mesh options -### PolyMesh +`` attributes: `src` (loads `.obj`, `.stl`, `.gltf`, `.glb`, or `.vox`), `mtl`, `position`, `scale`, `rotation`, `auto-center`, `mesh-resolution`, `cast-shadow`, `receive-shadow`, `target-size`, `default-color`, `palette`, `include-objects`, `exclude-objects`. There is **no** `polygons` attribute — pass pre-parsed geometry to `scene.add(...)`, or use `` for inline one-off polygons. -- `src` loads `.obj`, `.gltf`, `.glb`, or `.vox` files. -- `mtl` loads companion OBJ materials. -- `polygons` accepts pre-parsed geometry. -- `position`, `scale`, and `rotation` transform the mesh wrapper. -- `autoCenter` shifts the mesh bbox center to local origin. -- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. STL imports use the conservative lossless path in both modes. -- `castShadow` emits CSS-projected shadows in dynamic lighting mode. +`scene.add(result, transform)` additionally accepts `merge`, `meshResolution`, `stableDom`, `shadowDefinition`, `excludeFromAutoCenter`, and `id`. These are imperative-only — they are not `` attributes. + +- `cast-shadow` / `receive-shadow` emit CPU-projected SVG shadows. They work in both `"baked"` and `"dynamic"` lighting modes; dynamic-mode shadows are directional-only. +- `mesh-resolution` chooses `"lossy"` (default) or `"lossless"`. Note it threads into the **parse** only; the element's own `scene.add` call always renders at the default resolution. Use the imperative API when you need to control both passes. ### Controls -- `` adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate. -- `` uses pan-first map-style input. -- `` provides keyboard and pointer-look navigation. -- `` adds translate/rotate gizmos for selected mesh handles. +- `createPolyOrbitControls(scene, opts)` adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate. +- `createPolyMapControls(scene, opts)` uses pan-first map-style input. +- `createPolyFirstPersonControls(scene, opts)` provides keyboard and pointer-look navigation. +- `createTransformControls(scene, opts)` adds translate/rotate gizmos for selected mesh handles. +- `createSelect(scene, opts)` adds pointer picking over mesh handles. + +### PolyIframe + +The `` custom element renders a live document as a flat quad inside +the scene, using the same `position` / `rotation` / `scale` conventions as a +mesh. Its content is centered on the wrapper's local origin, so rotation and +scale pivot at the visible center. React and Vue expose it as ``. + +`width` and `height` are **world units**, not pixels — the mounted document is +`width × 50` by `height × 50` CSS px (`BASE_TILE`), so `16 × 9` yields an +800 × 450 px page. +`position` is world units too. + +```html + + + +``` ### Snapshot Export @@ -169,23 +199,22 @@ const polygons = [ ]; ``` -Render polygons directly when you need per-face DOM events or custom styling: +Geometry enters the scene through `scene.add()`, which takes a `ParseResult`. +There is no `polygons` scene option — wrap a raw `Polygon[]` yourself: -```tsx - - - {polygons.map((polygon, index) => ( - console.log("clicked polygon", index)} - className="my-polygon" - /> - ))} - - +```ts +scene.add({ polygons, objectUrls: [], warnings: [], dispose: () => {} }); ``` +`scene.add` runs the mesh optimizer by default. Pass `{ merge: false }` as the +second argument to render authored polygons exactly as given. + +Authoring `Polygon[]` by hand has real constraints — winding decides visibility, +`color` does not accept CSS named colors, and non-triangular polygons must be +coplanar. Read +[Authoring Polygons](https://polycss.com/core-concepts#authoring-polygons) +before you generate geometry. + ## Loading Mesh Files Use `loadMesh()` to parse supported model formats: @@ -223,6 +252,7 @@ Each visible polygon is emitted as one leaf element; the renderer chooses the le - `` clips solid polygons with `border-shape: polygon(...)` when the browser supports it. - `` maps a packed texture-atlas slice with `background-image`, and is the fallback for textured or unsupported shapes. + ## Packages | Package | Description | @@ -231,20 +261,12 @@ Each visible polygon is emitted as one leaf element; the renderer chooses the le | `@layoutit/polycss` | Vanilla custom elements and imperative `createPolyScene` API. | | `@layoutit/polycss-react` | React components, hooks, controls, and core re-exports. | | `@layoutit/polycss-vue` | Vue 3 components, composables, controls, and core re-exports. | +| `@layoutit/polycss-fonts` | Fonts + text → extruded 3D `Polygon[]`. Framework-agnostic. | +| `@layoutit/polycss-morph` | Prepared-model loading, retained DOM animation, morph targets, skinning, and playback. | + -## Made with PolyCSS - -[cssQuake](https://cssquake.com) --> A CSS port of Quake (1996) - -quake - - -[Layoutit Terra](https://terra.layoutit.com) --> A CSS Terrain Generator - -layoutit-terra - + ## License MIT. + diff --git a/packages/react/README.md b/packages/react/README.md index 9e379187..97e46a61 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -1,44 +1,27 @@ -# PolyCSS +# @layoutit/polycss-react -A CSS polygon mesh library. A 3D engine for the DOM. Renders OBJ/MTL, STL, glTF/GLB, and VOX as real HTML elements transformed with CSS `matrix3d(...)`. Supports colors, textures, lighting, shadows, shapes and animations. Works with React, Vue or plain JavaScript. +React bindings for [PolyCSS](https://polycss.com) — a 3D engine for the DOM. +Renders OBJ/MTL, STL, glTF/GLB, and VOX meshes as real HTML elements +transformed with CSS `matrix3d(...)`. No WebGL, no canvas-per-frame. + Visit [polycss.com](https://polycss.com) for docs and model examples. +Join [chat.polycss.com](https://chat.polycss.com) for support and community discussions. + PolyCSS primitives banner + ## Installation ```bash - -# Vanilla -npm install @layoutit/polycss - -# React npm install @layoutit/polycss-react - -# Vue -npm install @layoutit/polycss-vue - ``` -You can also load PolyCSS directly from a CDN. Here is a minimal custom-element scene: - -```html - - - - - - - - -``` - -PolyCSS intro +## Quick start -## Framework Components - -React and Vue expose the same component model. `` owns the viewpoint, `` owns lighting and options, and `` loads or receives polygon data. +`` owns the viewpoint, `` owns lighting and options, and +`` loads or receives polygon data. ```tsx import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react"; @@ -55,103 +38,106 @@ export default function App() { } ``` -## Three.js Parity API - -When porting Three.js scenes or generating code with an agent, use the explicit -`*/three` subpaths: - -- `@layoutit/polycss-core/three` -- `@layoutit/polycss/three` -- `@layoutit/polycss-react/three` -- `@layoutit/polycss-vue/three` - -They expose Three-like `PerspectiveCamera`, `OrthographicCamera`, `Object3D`, -`Vector3`, `DirectionalLight`, `PointLight`, `AmbientLight`, radians for object -rotations, Y-up authoring coordinates, and `camera.position` + `camera.lookAt(...)` -framing. The adapters convert into native PolyCSS coordinates with a right-handed -axis map, so the apparent object size, projection, orientation, depth ordering, -and light direction line up with Three.js scene math while still rendering -through the DOM. - -```tsx -import { PolyScene } from "@layoutit/polycss-react"; -import { - DirectionalLight, - PolyThreeMesh, - PolyThreePerspectiveCamera, -} from "@layoutit/polycss-react/three"; - -const sun = new DirectionalLight("#ffffff", 1); -sun.position.set(3, 5, 4); -sun.target.position.set(0, 0, 0); - -export function App() { - return ( - - - - - - ); -} -``` - -Full reference: [polycss.com/api/three-parity](https://polycss.com/api/three-parity). +PolyCSS intro -## API Reference +## Components -### PolyCamera +### `` - `rotX`, `rotY` control the orbit angle in degrees. -- `zoom` scales the projected scene. +- `zoom` is on-screen CSS pixels per world unit (Three.js `OrthographicCamera.zoom` style). - `target` pans the camera target in world coordinates. - `distance` adds dolly pull-back. -- `PolyCamera` is the orthographic default. Use `PolyPerspectiveCamera` when you want perspective depth. +- `PolyCamera` is the orthographic default. Use `` for + perspective depth, or `` for the explicit name. -### PolyScene +### `` - `polygons` renders a static `Polygon[]` directly. -- `directionalLight`, `pointLights` (direction-only, baked mode; optional per-light `castShadow`), and `ambientLight` control scene lighting. +- `directionalLight`, `pointLights` (direction-only, baked mode; optional + per-light `castShadow`), and `ambientLight` control scene lighting. - `textureLighting` chooses `"baked"` or `"dynamic"`. - `textureQuality` controls atlas raster budget. +- `shadow` configures cast-shadow color, opacity, and the parametric shadow + knobs (`parametric`, `definition`, `style`, `followAnimation`). - `strategies` can disable selected render strategies for diagnostics. -- `autoCenter` rotates around the rendered mesh bounds instead of world origin. +- `autoCenter` rotates around the bbox of the scene's own `polygons` prop (or + `centerPolygons` when given) instead of world origin. It does **not** see + geometry inside child `` components — with + `` the bbox is empty and + nothing shifts. Pass the mesh's polygons as `centerPolygons`, or use + `` to recenter the mesh itself. (Vanilla + `createPolyScene` differs — it unions every added mesh.) + +Unlike the vanilla renderer, React re-renders on prop change, so a light change +**auto-rebakes** the lit surface in baked mode. For live or animated lights, +prefer `textureLighting="dynamic"`. -### PolyMesh +### `` -- `src` loads `.obj`, `.gltf`, `.glb`, or `.vox` files. +- `src` loads `.obj`, `.stl`, `.gltf`, `.glb`, or `.vox` files. - `mtl` loads companion OBJ materials. - `polygons` accepts pre-parsed geometry. - `position`, `scale`, and `rotation` transform the mesh wrapper. -- `autoCenter` shifts the mesh bbox center to local origin. -- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. STL imports use the conservative lossless path in both modes. -- `castShadow` emits CSS-projected shadows in dynamic lighting mode. +- `autoCenter` shifts the mesh bbox center to local origin. Note this rewrites + vertex data, so `getPolygons()` returns the shifted coordinates. +- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. + `.stl` **parsing** is conservative — the loader always uses the lossless + optimizer and skips interior culling — but rendering re-optimizes the loaded + polygons: the second pass follows `meshResolution` and interior-culls even at + `"lossless"`. +- `merge` (default `true`) runs the polygon optimizer. Set `false` to render the + polygons you passed exactly as given. It cannot undo `loadMesh`'s own + parse-time optimization, so `src`-loaded geometry is already optimized before + this switch is consulted. +- `castShadow` / `receiveShadow` opt the mesh into CPU-projected SVG shadows. +- `shadowDefinition` overrides the scene parametric shadow resolution for this + mesh. + +### `` + +Renders a live document as a flat quad in the scene, with the same +`position` / `rotation` / `scale` conventions as a mesh. Content is centered on +the wrapper's local origin, so rotation and scale pivot at the visible center. + +`width` and `height` are **world units**, not pixels — the mounted document is +`width × 50` by `height × 50` CSS px (`BASE_TILE`), so `16 × 9` yields an +800 × 450 px page. + +```tsx + +``` ### Controls -- `` adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate. +- `` adds drag orbit, shift-drag pan, wheel zoom, and + optional auto-rotate. - `` uses pan-first map-style input. - `` provides keyboard and pointer-look navigation. -- `` adds translate/rotate gizmos for selected mesh handles. +- `` adds translate/rotate gizmos for selected mesh + handles. +- `` adds pointer picking; pair with `usePolySelect` / + `usePolySelectionApi`. -### Snapshot Export +### Helpers -The vanilla package exports `exportPolySceneSnapshot(target)`. It clones the current rendered `.polycss-camera` / `.polycss-scene` DOM, injects only the PolyCSS CSS needed by that snapshot, inlines CSS `url(...)` image assets as `data:image/...;base64,...`, strips scripts and inline event handlers, and returns a standalone HTML document string with no PolyCSS runtime import. It works with rendered React/Vue scenes too; import it from `@layoutit/polycss` and pass the rendered camera or scene element. +``, ``, ``, and the +`` shape component for one-off polygons. -```ts -import { exportPolySceneSnapshot } from "@layoutit/polycss"; +## Hooks -const html = await exportPolySceneSnapshot(scene.host); -``` +- `usePolyCamera(options)` — create and drive the scene camera store from camera + options; returns `{ store, cameraRef, sceneElRef, cameraElRef, + applyTransformDirect }`. +- `usePolySceneContext(polygons, { directionalLight })` — run the scene pipeline + (normalize + merge) over a polygon list; returns `{ polygons, sceneBbox }`. +- `usePolyMesh` — load a mesh imperatively; returns `{ polygons, voxelSource, + loading, error, warnings, dispose }`, where `error` is an `Error | null`. +- `usePolyMaterial` — resolve material state for a mesh. +- `usePolySelect`, `usePolySelectionApi` — selection state and imperative API. +- `usePolyAnimation` — drive imported skeletal/morph clips. -If any referenced asset cannot be inlined, the function throws `PolySceneSnapshotError` with `code: "ASSET_INLINE_FAILED"`. - -### Polygon Data Model +## Polygon data model Each polygon describes one renderable face: @@ -186,43 +172,57 @@ Render polygons directly when you need per-face DOM events or custom styling: ``` -## Loading Mesh Files +Authoring `Polygon[]` by hand has real constraints — vertex winding decides +whether a face is visible at all, `color` does not accept CSS named colors, and +non-triangular polygons must be coplanar. Read +[Authoring Polygons](https://polycss.com/core-concepts#authoring-polygons) +before generating geometry. -Use `loadMesh()` to parse supported model formats: +## Three.js parity API -```ts -import { createPolyCamera, createPolyScene, loadMesh } from "@layoutit/polycss"; +When porting Three.js scenes or generating code with an agent, use the explicit +`@layoutit/polycss-react/three` subpath. It exposes `PolyThreePerspectiveCamera`, +`PolyThreeOrthographicCamera`, `PolyThreeMesh`, and the Three-like light classes, +with radians for object rotations and Y-up authoring coordinates. -const host = document.getElementById("polycss")!; -const camera = createPolyCamera({ rotX: 65, rotY: 45 }); -const scene = createPolyScene(host, { camera }); +```tsx +import { PolyScene } from "@layoutit/polycss-react"; +import { + DirectionalLight, + PolyThreeMesh, + PolyThreePerspectiveCamera, +} from "@layoutit/polycss-react/three"; -const mesh = await loadMesh("https://polycss.com/gallery/obj/cottage.obj", { - mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl", -}); +const sun = new DirectionalLight("#ffffff", 1); +sun.position.set(3, 5, 4); +sun.target.position.set(0, 0, 0); -scene.add(mesh); +export function App() { + return ( + + + + + + ); +} ``` -Supported formats: - -- OBJ + MTL, including `map_Kd` textures and UV coordinates. -- STL triangle meshes, including binary Magics face colors. STL has no standard units, textures, UVs, or hierarchy, so imports skip lossy simplification and ray-based interior culling. -- glTF / GLB, including embedded images and `TEXCOORD_0`. -- MagicaVoxel `.vox`, with direct voxel fast paths when eligible. -- Generated primitives: box, plane, ring, sphere, torus, cylinder, cone, and Platonic solids. +Full reference: [polycss.com/api/three-parity](https://polycss.com/api/three-parity). ## Performance -PolyCSS renders through the DOM, so performance is mostly shaped by two things: the number of mounted leaves, and the amount of texture atlas area the browser has to paint. The renderer tries to keep the common cases cheap. Simple surfaces stay as solid CSS elements, while textured, irregular, or high-detail geometry falls back to atlas-backed slices only when needed. - -Each visible polygon is emitted as one leaf element; the renderer chooses the least expensive CSS primitive that can represent the polygon, then uses `matrix3d(...)` to place that primitive in 3D space. +Each visible polygon is emitted as one leaf element; the renderer chooses the +least expensive CSS primitive that can represent it, then uses `matrix3d(...)` +to place that primitive in 3D space. Polygon count is the dominant cost. -- `` uses `background: currentColor` on a fixed box for solid rectangles and stable quads. -- `` uses `corner-shape` for stable triangles and beveled-corner solids, with a `border-width` triangle fallback when needed. -- `` clips solid polygons with `border-shape: polygon(...)` when the browser supports it. -- `` maps a packed texture-atlas slice with `background-image`, and is the fallback for textured or unsupported shapes. +- `` uses `background: currentColor` for solid rectangles and stable quads. +- `` uses `corner-shape` for stable triangles and beveled-corner solids. +- `` clips solid polygons with `border-shape: polygon(...)` where supported. +- `` maps a packed texture-atlas slice, and is the fallback for textured or + unsupported shapes. + ## Packages | Package | Description | @@ -231,20 +231,12 @@ Each visible polygon is emitted as one leaf element; the renderer chooses the le | `@layoutit/polycss` | Vanilla custom elements and imperative `createPolyScene` API. | | `@layoutit/polycss-react` | React components, hooks, controls, and core re-exports. | | `@layoutit/polycss-vue` | Vue 3 components, composables, controls, and core re-exports. | +| `@layoutit/polycss-fonts` | Fonts + text → extruded 3D `Polygon[]`. Framework-agnostic. | +| `@layoutit/polycss-morph` | Prepared-model loading, retained DOM animation, morph targets, skinning, and playback. | + -## Made with PolyCSS - -[cssQuake](https://cssquake.com) --> A CSS port of Quake (1996) - -quake - - -[Layoutit Terra](https://terra.layoutit.com) --> A CSS Terrain Generator - -layoutit-terra - + ## License MIT. + diff --git a/packages/vue/README.md b/packages/vue/README.md index 9e379187..44b3dc88 100644 --- a/packages/vue/README.md +++ b/packages/vue/README.md @@ -1,157 +1,159 @@ -# PolyCSS +# @layoutit/polycss-vue -A CSS polygon mesh library. A 3D engine for the DOM. Renders OBJ/MTL, STL, glTF/GLB, and VOX as real HTML elements transformed with CSS `matrix3d(...)`. Supports colors, textures, lighting, shadows, shapes and animations. Works with React, Vue or plain JavaScript. +Vue 3 bindings for [PolyCSS](https://polycss.com) — a 3D engine for the DOM. +Renders OBJ/MTL, STL, glTF/GLB, and VOX meshes as real HTML elements +transformed with CSS `matrix3d(...)`. No WebGL, no canvas-per-frame. + Visit [polycss.com](https://polycss.com) for docs and model examples. +Join [chat.polycss.com](https://chat.polycss.com) for support and community discussions. + PolyCSS primitives banner + ## Installation ```bash - -# Vanilla -npm install @layoutit/polycss - -# React -npm install @layoutit/polycss-react - -# Vue npm install @layoutit/polycss-vue - -``` - -You can also load PolyCSS directly from a CDN. Here is a minimal custom-element scene: - -```html - - - - - - - - ``` -PolyCSS intro - -## Framework Components - -React and Vue expose the same component model. `` owns the viewpoint, `` owns lighting and options, and `` loads or receives polygon data. - -```tsx -import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react"; - -export default function App() { - return ( - - - - +## Quick start + +`` owns the viewpoint, `` owns lighting and options, and +`` loads or receives polygon data. + +```vue + -When porting Three.js scenes or generating code with an agent, use the explicit -`*/three` subpaths: - -- `@layoutit/polycss-core/three` -- `@layoutit/polycss/three` -- `@layoutit/polycss-react/three` -- `@layoutit/polycss-vue/three` - -They expose Three-like `PerspectiveCamera`, `OrthographicCamera`, `Object3D`, -`Vector3`, `DirectionalLight`, `PointLight`, `AmbientLight`, radians for object -rotations, Y-up authoring coordinates, and `camera.position` + `camera.lookAt(...)` -framing. The adapters convert into native PolyCSS coordinates with a right-handed -axis map, so the apparent object size, projection, orientation, depth ordering, -and light direction line up with Three.js scene math while still rendering -through the DOM. - -```tsx -import { PolyScene } from "@layoutit/polycss-react"; -import { - DirectionalLight, - PolyThreeMesh, - PolyThreePerspectiveCamera, -} from "@layoutit/polycss-react/three"; + -const sun = new DirectionalLight("#ffffff", 1); -sun.position.set(3, 5, 4); -sun.target.position.set(0, 0, 0); - -export function App() { - return ( - - - - - - ); -} + ``` -Full reference: [polycss.com/api/three-parity](https://polycss.com/api/three-parity). +PolyCSS intro + +## Components -## API Reference +Props are listed in their template (kebab-case) form. -### PolyCamera +### `` -- `rotX`, `rotY` control the orbit angle in degrees. -- `zoom` scales the projected scene. +- `rot-x`, `rot-y` control the orbit angle in degrees. +- `zoom` is on-screen CSS pixels per world unit (Three.js `OrthographicCamera.zoom` style). - `target` pans the camera target in world coordinates. - `distance` adds dolly pull-back. -- `PolyCamera` is the orthographic default. Use `PolyPerspectiveCamera` when you want perspective depth. +- `PolyCamera` is the orthographic default. Use `` for + perspective depth, or `` for the explicit name. -### PolyScene +### `` - `polygons` renders a static `Polygon[]` directly. -- `directionalLight`, `pointLights` (direction-only, baked mode; optional per-light `castShadow`), and `ambientLight` control scene lighting. -- `textureLighting` chooses `"baked"` or `"dynamic"`. -- `textureQuality` controls atlas raster budget. +- `directional-light`, `point-lights` (direction-only, baked mode; optional + per-light `castShadow`), and `ambient-light` control scene lighting. +- `texture-lighting` chooses `"baked"` or `"dynamic"`. +- `texture-quality` controls atlas raster budget. +- `shadow` configures cast-shadow color, opacity, and the parametric shadow + knobs (`parametric`, `definition`, `style`, `followAnimation`). - `strategies` can disable selected render strategies for diagnostics. -- `autoCenter` rotates around the rendered mesh bounds instead of world origin. +- `auto-center` rotates around the bbox of the scene's own `polygons` prop (or + `center-polygons` when given) instead of world origin. It does **not** see + geometry inside child `` components — with + `` the bbox is empty + and nothing shifts. Pass the mesh's polygons as `center-polygons`, or use + `` to recenter the mesh itself. (Vanilla + `createPolyScene` differs — it unions every added mesh.) -### PolyMesh +Unlike the vanilla renderer, Vue re-renders on prop change, so a light change +**auto-rebakes** the lit surface in baked mode. For live or animated lights, +prefer `texture-lighting="dynamic"`. -- `src` loads `.obj`, `.gltf`, `.glb`, or `.vox` files. +### `` + +- `src` loads `.obj`, `.stl`, `.gltf`, `.glb`, or `.vox` files. - `mtl` loads companion OBJ materials. - `polygons` accepts pre-parsed geometry. - `position`, `scale`, and `rotation` transform the mesh wrapper. -- `autoCenter` shifts the mesh bbox center to local origin. -- `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. STL imports use the conservative lossless path in both modes. -- `castShadow` emits CSS-projected shadows in dynamic lighting mode. +- `auto-center` shifts the mesh bbox center to local origin. Note this rewrites + vertex data, so `getPolygons()` returns the shifted coordinates. +- `mesh-resolution` chooses `"lossy"` (default) or `"lossless"` optimization. + `.stl` **parsing** is conservative — the loader always uses the lossless + optimizer and skips interior culling — but rendering re-optimizes the loaded + polygons: the second pass follows `mesh-resolution` and interior-culls even at + `"lossless"`. +- `merge` (default `true`) runs the polygon optimizer. Set `false` to render the + polygons you passed exactly as given. It cannot undo `loadMesh`'s own + parse-time optimization, so `src`-loaded geometry is already optimized before + this switch is consulted. +- `cast-shadow` / `receive-shadow` opt the mesh into CPU-projected SVG shadows. +- `shadow-definition` overrides the scene parametric shadow resolution for this + mesh. + +### `` + +Renders a live document as a flat quad in the scene, with the same +`position` / `rotation` / `scale` conventions as a mesh. Content is centered on +the wrapper's local origin, so rotation and scale pivot at the visible center. + +`width` and `height` are **world units**, not pixels — the mounted document is +`width × 50` by `height × 50` CSS px (`BASE_TILE`), so `16 × 9` yields an +800 × 450 px page. + +```vue + +``` ### Controls -- `` adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate. +- `` adds drag orbit, shift-drag pan, wheel zoom, and + optional auto-rotate. - `` uses pan-first map-style input. - `` provides keyboard and pointer-look navigation. -- `` adds translate/rotate gizmos for selected mesh handles. +- `` adds translate/rotate gizmos for selected mesh + handles. +- `` adds pointer picking; pair with `usePolySelect` / + `usePolySelectionApi`. -### Snapshot Export +### Helpers -The vanilla package exports `exportPolySceneSnapshot(target)`. It clones the current rendered `.polycss-camera` / `.polycss-scene` DOM, injects only the PolyCSS CSS needed by that snapshot, inlines CSS `url(...)` image assets as `data:image/...;base64,...`, strips scripts and inline event handlers, and returns a standalone HTML document string with no PolyCSS runtime import. It works with rendered React/Vue scenes too; import it from `@layoutit/polycss` and pass the rendered camera or scene element. +``, ``, ``, and the +`` shape component for one-off polygons. -```ts -import { exportPolySceneSnapshot } from "@layoutit/polycss"; +## Composables -const html = await exportPolySceneSnapshot(scene.host); -``` +- `usePolyCamera(options)` — create and drive the scene camera store from camera + options; returns the camera store plus scene/camera element refs. +- `usePolySceneContext(polygons, options)` — run the scene pipeline (normalize + + merge) over a reactive polygon list; returns a ref of + `{ polygons, sceneBbox }`. +- `usePolyMesh` — load a mesh imperatively; exposes polygons, voxel source, + loading, error, and warnings as reactive state. +- `usePolyMaterial` — resolve material state for a mesh. +- `usePolySelect`, `usePolySelectionApi` — selection state and imperative API. +- `usePolyAnimation` — drive imported skeletal/morph clips. -If any referenced asset cannot be inlined, the function throws `PolySceneSnapshotError` with `code: "ASSET_INLINE_FAILED"`. +Injection keys (`PolyCameraContextKey`, `PolySelectionContextKey`) are exported +for components that provide their own context. -### Polygon Data Model +## Polygon data model Each polygon describes one renderable face: @@ -171,58 +173,78 @@ const polygons = [ Render polygons directly when you need per-face DOM events or custom styling: -```tsx - - - {polygons.map((polygon, index) => ( +```vue + ``` -## Loading Mesh Files +Authoring `Polygon[]` by hand has real constraints — vertex winding decides +whether a face is visible at all, `color` does not accept CSS named colors, and +non-triangular polygons must be coplanar. Read +[Authoring Polygons](https://polycss.com/core-concepts#authoring-polygons) +before generating geometry. -Use `loadMesh()` to parse supported model formats: +## Three.js parity API -```ts -import { createPolyCamera, createPolyScene, loadMesh } from "@layoutit/polycss"; - -const host = document.getElementById("polycss")!; -const camera = createPolyCamera({ rotX: 65, rotY: 45 }); -const scene = createPolyScene(host, { camera }); - -const mesh = await loadMesh("https://polycss.com/gallery/obj/cottage.obj", { - mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl", -}); +When porting Three.js scenes or generating code with an agent, use the explicit +`@layoutit/polycss-vue/three` subpath. It exposes `PolyThreePerspectiveCamera`, +`PolyThreeOrthographicCamera`, `PolyThreeMesh`, and the Three-like light classes, +with radians for object rotations and Y-up authoring coordinates. + +```vue + + + ``` -Supported formats: - -- OBJ + MTL, including `map_Kd` textures and UV coordinates. -- STL triangle meshes, including binary Magics face colors. STL has no standard units, textures, UVs, or hierarchy, so imports skip lossy simplification and ray-based interior culling. -- glTF / GLB, including embedded images and `TEXCOORD_0`. -- MagicaVoxel `.vox`, with direct voxel fast paths when eligible. -- Generated primitives: box, plane, ring, sphere, torus, cylinder, cone, and Platonic solids. +Full reference: [polycss.com/api/three-parity](https://polycss.com/api/three-parity). ## Performance -PolyCSS renders through the DOM, so performance is mostly shaped by two things: the number of mounted leaves, and the amount of texture atlas area the browser has to paint. The renderer tries to keep the common cases cheap. Simple surfaces stay as solid CSS elements, while textured, irregular, or high-detail geometry falls back to atlas-backed slices only when needed. - -Each visible polygon is emitted as one leaf element; the renderer chooses the least expensive CSS primitive that can represent the polygon, then uses `matrix3d(...)` to place that primitive in 3D space. +Each visible polygon is emitted as one leaf element; the renderer chooses the +least expensive CSS primitive that can represent it, then uses `matrix3d(...)` +to place that primitive in 3D space. Polygon count is the dominant cost. -- `` uses `background: currentColor` on a fixed box for solid rectangles and stable quads. -- `` uses `corner-shape` for stable triangles and beveled-corner solids, with a `border-width` triangle fallback when needed. -- `` clips solid polygons with `border-shape: polygon(...)` when the browser supports it. -- `` maps a packed texture-atlas slice with `background-image`, and is the fallback for textured or unsupported shapes. +- `` uses `background: currentColor` for solid rectangles and stable quads. +- `` uses `corner-shape` for stable triangles and beveled-corner solids. +- `` clips solid polygons with `border-shape: polygon(...)` where supported. +- `` maps a packed texture-atlas slice, and is the fallback for textured or + unsupported shapes. + ## Packages | Package | Description | @@ -231,20 +253,12 @@ Each visible polygon is emitted as one leaf element; the renderer chooses the le | `@layoutit/polycss` | Vanilla custom elements and imperative `createPolyScene` API. | | `@layoutit/polycss-react` | React components, hooks, controls, and core re-exports. | | `@layoutit/polycss-vue` | Vue 3 components, composables, controls, and core re-exports. | +| `@layoutit/polycss-fonts` | Fonts + text → extruded 3D `Polygon[]`. Framework-agnostic. | +| `@layoutit/polycss-morph` | Prepared-model loading, retained DOM animation, morph targets, skinning, and playback. | + -## Made with PolyCSS - -[cssQuake](https://cssquake.com) --> A CSS port of Quake (1996) - -quake - - -[Layoutit Terra](https://terra.layoutit.com) --> A CSS Terrain Generator - -layoutit-terra - + ## License MIT. +