@layoutit/polycss-react 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,174 +1,202 @@
1
- > **Status: pre-1.0. APIs may still change before a stable 1.0 release.**
1
+ # PolyCSS
2
2
 
3
- # @layoutit/polycss-react
3
+ 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.
4
4
 
5
- Declarative React components for CSS-based polygon mesh rendering. Loads OBJ, glTF, GLB, and MagicaVoxel `.vox` files; renders each polygon as a real DOM element (atlas-backed `<i>` for both textured and flat-color faces) positioned with `transform: matrix3d(...)`. No WebGL, no canvas-as-scene.
5
+ Visit [polycss.com](https://polycss.com) for docs and model examples.
6
6
 
7
- ## Install
7
+ <img width="1600" height="300" alt="PolyCSS primitives banner" src="https://github.com/user-attachments/assets/b05e2204-9323-4f83-8d1b-01ea0dd000db" />
8
+
9
+ ## Installation
8
10
 
9
11
  ```bash
12
+
13
+ # Vanilla
14
+ npm install @layoutit/polycss
15
+
16
+ # React
10
17
  npm install @layoutit/polycss-react
18
+
19
+ # Vue
20
+ npm install @layoutit/polycss-vue
21
+
11
22
  ```
12
23
 
13
- Requires React 18 or 19 as a peer dependency.
14
24
 
15
- ## Quickstart
25
+ You can also load PolyCSS directly from a CDN. Here is a minimal custom-element scene:
26
+
27
+ ```html
28
+ <script type="module" src="https://esm.sh/@layoutit/polycss/elements"></script>
29
+
30
+ <poly-camera rot-x="65" rot-y="45">
31
+ <poly-scene>
32
+ <poly-orbit-controls drag wheel></poly-orbit-controls>
33
+ <poly-box size="100" color="#ffd166"></poly-box>
34
+ </poly-scene>
35
+ </poly-camera>
36
+ ```
37
+
38
+ <img width="2500" height="1145" alt="PolyCSS intro" src="https://github.com/user-attachments/assets/0e5df0d8-04a8-4e50-8e3a-1097a96ce42f" />
39
+
40
+ ## Framework Components
41
+
42
+ React and Vue expose the same component model. `<PolyCamera>` owns the viewpoint, `<PolyScene>` owns lighting and atlas options, and `<PolyMesh>` loads or receives polygon data.
16
43
 
17
44
  ```tsx
18
- import { PolyCamera, PolyScene, PolyMesh } from "@layoutit/polycss-react";
45
+ import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react";
19
46
 
20
- export function App() {
47
+ export default function App() {
21
48
  return (
22
- <PolyCamera rotX={65} rotY={45} perspective={1000}>
23
- <PolyScene>
24
- <PolyMesh src="/cottage.glb" />
49
+ <PolyCamera rotX={65} rotY={45}>
50
+ <PolyScene textureLighting="dynamic">
51
+ <PolyOrbitControls drag wheel />
52
+ <PolyMesh src="/gallery/obj/cottage.obj" mtl="/gallery/obj/cottage.mtl" />
25
53
  </PolyScene>
26
54
  </PolyCamera>
27
55
  );
28
56
  }
29
57
  ```
30
58
 
31
- Every polygon in the mesh is a real DOM element: inspect it in DevTools, style it with CSS, attach event handlers.
59
+ ## API Reference
32
60
 
33
- ## Component reference
61
+ ### PolyCamera
34
62
 
35
- ### `<PolyScene>`
63
+ - `rotX`, `rotY` control the orbit angle in degrees.
64
+ - `zoom` scales the projected scene.
65
+ - `target` pans the camera target in world coordinates.
66
+ - `distance` adds dolly pull-back.
67
+ - `PolyCamera` is the orthographic default. Use `PolyPerspectiveCamera` when you want perspective depth.
36
68
 
37
- Root of every React polycss render tree. Renders polygons and meshes inside a `<PolyCamera>` context, and owns scene-level lighting and atlas options.
69
+ ### PolyScene
38
70
 
39
- | Prop | Type | Default | Description |
40
- |---|---|---|---|
41
- | `directionalLight` | `PolyDirectionalLight` | None | Directional light config |
42
- | `ambientLight` | `PolyAmbientLight` | None | Ambient light config |
43
- | `textureLighting` | `"baked" \| "dynamic"` | `"baked"` | Texture lighting mode |
44
- | `textureQuality` | `number \| "auto"` | `"auto"` | Atlas bitmap budget and compositor sprite size |
45
- | `polygons` | `Polygon[]` | None | Static polygon array (composes with `children`) |
46
- | `children` | `ReactNode` | None | `<PolyMesh>`, `<Poly>`, and/or `<PolyOrbitControls>` |
71
+ - `polygons` renders a static `Polygon[]` directly.
72
+ - `directionalLight` and `ambientLight` control scene lighting.
73
+ - `textureLighting` chooses `"baked"` or `"dynamic"`.
74
+ - `textureQuality` controls atlas raster budget.
75
+ - `strategies` can disable selected render strategies for diagnostics.
76
+ - `autoCenter` rotates around the rendered mesh bounds instead of world origin.
47
77
 
48
- For pointer drag, wheel zoom, and autorotate, mount `<PolyOrbitControls>` (or `<PolyMapControls>` for pan-first map-style input) inside `<PolyCamera>`: it receives the camera context. Mirrors Three.js's split between camera state and input.
78
+ ### PolyMesh
49
79
 
50
- ### `<PolyMesh>`
80
+ - `src` loads `.obj`, `.gltf`, `.glb`, or `.vox` files.
81
+ - `mtl` loads companion OBJ materials.
82
+ - `polygons` accepts pre-parsed geometry.
83
+ - `position`, `scale`, and `rotation` transform the mesh wrapper.
84
+ - `autoCenter` shifts the mesh bbox center to local origin.
85
+ - `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization. STL imports use the conservative lossless path in both modes.
86
+ - `castShadow` emits CSS-projected shadows in dynamic lighting mode.
51
87
 
52
- Loads a mesh from a URL and renders its polygons. Manages blob-URL lifecycle automatically.
88
+ ### Controls
53
89
 
54
- | Prop | Type | Description |
55
- |---|---|---|
56
- | `src` | `string` | URL to `.obj`, `.glb`, `.gltf`, or `.vox` |
57
- | `polygons` | `Polygon[]` | Pre-parsed polygons (alternative to `src`) |
58
- | `position` | `Vec3` | `[x, y, z]` offset in scene space |
59
- | `scale` | `number \| Vec3` | Uniform or per-axis scale |
60
- | `rotation` | `Vec3` | Euler angles in degrees `[x, y, z]` |
61
- | `textureQuality` | `number \| "auto"` | Atlas bitmap budget and compositor sprite size |
62
- | `autoCenter` | `boolean` | Shift mesh so its bbox center is at origin |
63
- | `mtl` | `string` | Companion `.mtl` URL for OBJ models |
64
- | `parseOptions` | `UseMeshOptions` | Forwarded to `loadMesh`; `meshResolution` defaults to `"lossy"` |
65
- | `fallback` | `ReactNode` | Rendered while loading |
66
- | `errorFallback` | `(error: Error) => ReactNode` | Rendered on parse failure |
67
- | `children` | `((polygon, index) => ReactNode) \| ReactNode` | Per-polygon render prop override, or static children mounted inside the mesh wrapper |
90
+ - `<PolyOrbitControls>` adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate.
91
+ - `<PolyMapControls>` uses pan-first map-style input.
92
+ - `<PolyFirstPersonControls>` provides keyboard and pointer-look navigation.
93
+ - `<PolyTransformControls>` adds translate/rotate gizmos for selected mesh handles.
68
94
 
69
- ### `<Poly>`
95
+ ### Snapshot Export
70
96
 
71
- Single polygon. The atomic primitive: renders one atlas-backed `<i>` for UV-textured and flat-color faces. Forwards all standard DOM props.
97
+ 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.
72
98
 
73
- | Prop | Type | Description |
74
- |---|---|---|
75
- | `vertices` | `Vec3[]` | Required: 3+ `[x, y, z]` points |
76
- | `color` | `string` | CSS color; used when no texture is set |
77
- | `texture` | `string` | Image URL for UV-mapped rendering |
78
- | `uvs` | `Vec2[]` | UV coordinates, one per vertex |
79
- | `data` | `Record<string, string \| number \| boolean>` | Reflected as `data-*` DOM attributes |
80
- | `position` | `Vec3` | Local offset |
81
- | `scale` | `number \| Vec3` | Scale |
82
- | `rotation` | `Vec3` | Euler rotation in degrees |
83
- | `textureQuality` | `number \| "auto"` | Atlas bitmap budget and compositor sprite size |
84
- | `onClick` | `MouseEventHandler` | Standard DOM event handler |
85
- | `onMouseEnter` | `MouseEventHandler` | |
86
- | `className` | `string` | CSS class |
87
- | `style` | `CSSProperties` | Inline style |
88
- | `aria-label` | `string` | ARIA label |
99
+ ```ts
100
+ import { exportPolySceneSnapshot } from "@layoutit/polycss";
89
101
 
90
- ### `<PolyCamera>`
102
+ const html = await exportPolySceneSnapshot(scene.host);
103
+ ```
91
104
 
92
- Camera wrapper for perspective, rotation, zoom, target, and dolly distance. React scenes must render inside `<PolyCamera>` (or `<PolyPerspectiveCamera>` / `<PolyOrthographicCamera>`) so controls and scenes share camera state.
105
+ If any referenced asset cannot be inlined, the function throws `PolySceneSnapshotError` with `code: "ASSET_INLINE_FAILED"`.
93
106
 
94
- ### Hooks
107
+ ### Polygon Data Model
95
108
 
96
- | Hook | Description |
97
- |---|---|
98
- | `usePolyCamera(options)` | Internal camera integration hook (used by `<PolyCamera>`) |
99
- | `usePolySceneContext(polygons, options)` | Lower-level hook for building custom scene wrappers |
100
- | `usePolyMesh(src, options?)` | Fetch + parse a mesh. Returns `{ polygons, loading, error, warnings, dispose }`. Manages blob-URL lifecycle: safe across rapid src changes and unmounts. |
109
+ Each polygon describes one renderable face:
101
110
 
102
- ### Utility
111
+ ```ts
112
+ const polygons = [
113
+ {
114
+ vertices: [[0, 0, 0], [60, 0, 0], [0, 60, 0]],
115
+ color: "#f97316",
116
+ },
117
+ {
118
+ vertices: [[0, 0, 0], [60, 0, 0], [60, 60, 0], [0, 60, 0]],
119
+ texture: "/texture.png",
120
+ uvs: [[0, 0], [1, 0], [1, 1], [0, 1]],
121
+ },
122
+ ];
123
+ ```
103
124
 
104
- | Export | Description |
105
- |---|---|
106
- | `injectPolyBaseStyles(doc?)` | Inject polycss base CSS into the document. Idempotent. Called automatically by `<PolyScene>`; manual call only needed for custom scene hosts. Polygon defaults are scoped to `.polycss-scene`. |
125
+ Render polygons directly when you need per-face DOM events or custom styling:
107
126
 
108
- ## Re-exports from `@layoutit/polycss-core`
127
+ ```tsx
128
+ <PolyCamera>
129
+ <PolyScene>
130
+ {polygons.map((polygon, index) => (
131
+ <Poly
132
+ key={index}
133
+ {...polygon}
134
+ onClick={() => console.log("clicked polygon", index)}
135
+ className="my-polygon"
136
+ />
137
+ ))}
138
+ </PolyScene>
139
+ </PolyCamera>
140
+ ```
109
141
 
110
- All types and core functions are re-exported for convenience, so you never need to add `@layoutit/polycss-core` to your dependencies:
142
+ ## Loading Mesh Files
143
+
144
+ Use `loadMesh()` to parse supported model formats:
111
145
 
112
146
  ```ts
113
- import type { Polygon, Vec2, Vec3, PolyDirectionalLight, PolyAmbientLight, ParseResult } from "@layoutit/polycss-react";
114
- import { parseObj, parseGltf, parseVox, loadMesh, normalizePolygons, mergePolygons } from "@layoutit/polycss-react";
147
+ import { createPolyCamera, createPolyScene, loadMesh } from "@layoutit/polycss";
148
+
149
+ const host = document.getElementById("polycss")!;
150
+ const camera = createPolyCamera({ rotX: 65, rotY: 45 });
151
+ const scene = createPolyScene(host, { camera });
152
+
153
+ const mesh = await loadMesh("https://polycss.com/gallery/obj/cottage.obj", {
154
+ mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl",
155
+ });
156
+
157
+ scene.add(mesh);
115
158
  ```
116
159
 
117
- ## Per-polygon interactivity example
160
+ Supported formats:
118
161
 
119
- ```tsx
120
- import { useState } from "react";
121
- import { PolyCamera, PolyScene, Poly } from "@layoutit/polycss-react";
122
- import type { Polygon } from "@layoutit/polycss-react";
162
+ - OBJ + MTL, including `map_Kd` textures and UV coordinates.
163
+ - 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.
164
+ - glTF / GLB, including embedded images and `TEXCOORD_0`.
165
+ - MagicaVoxel `.vox`, with direct voxel fast paths when eligible.
166
+ - Generated primitives: box, plane, ring, sphere, torus, cylinder, cone, and Platonic solids.
123
167
 
124
- export function InteractiveMesh({ polygons }: { polygons: Polygon[] }) {
125
- const [hoveredId, setHoveredId] = useState<number | null>(null);
168
+ ## Performance
126
169
 
127
- return (
128
- <PolyCamera rotX={65} rotY={45}>
129
- <PolyScene>
130
- {polygons.map((p, i) => (
131
- <Poly
132
- key={i}
133
- {...p}
134
- onClick={() => alert(`clicked polygon ${i}`)}
135
- onMouseEnter={() => setHoveredId(i)}
136
- onMouseLeave={() => setHoveredId(null)}
137
- className={hoveredId === i ? "highlight" : ""}
138
- style={{ transition: "filter 0.2s" }}
139
- />
140
- ))}
141
- </PolyScene>
142
- </PolyCamera>
143
- );
144
- }
145
- ```
170
+ 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.
146
171
 
147
- ```css
148
- .highlight { filter: brightness(1.5); }
149
- ```
172
+ 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.
150
173
 
151
- ## `usePolyMesh`: imperative loading
174
+ - `<b>` uses `background: currentColor` on a fixed box for solid rectangles and stable quads.
175
+ - `<u>` uses `corner-shape` for stable triangles and beveled-corner solids, with a `border-width` triangle fallback when needed.
176
+ - `<i>` clips solid polygons with `border-shape: polygon(...)` when the browser supports it.
177
+ - `<s>` maps a packed texture-atlas slice with `background-image`, and is the fallback for textured or unsupported shapes.
152
178
 
153
- ```tsx
154
- import { PolyCamera, PolyScene, Poly, usePolyMesh } from "@layoutit/polycss-react";
179
+ ## Packages
155
180
 
156
- function Viewer() {
157
- const { polygons, loading, error } = usePolyMesh("/cottage.glb");
181
+ | Package | Description |
182
+ |---|---|
183
+ | `@layoutit/polycss-core` | Pure math, parsers, lighting, camera helpers, mesh optimization. Zero browser globals. |
184
+ | `@layoutit/polycss` | Vanilla custom elements and imperative `createPolyScene` API. |
185
+ | `@layoutit/polycss-react` | React components, hooks, controls, and core re-exports. |
186
+ | `@layoutit/polycss-vue` | Vue 3 components, composables, controls, and core re-exports. |
158
187
 
159
- if (loading) return <div>Loading…</div>;
160
- if (error) return <div>Error: {error.message}</div>;
188
+ ## Made with PolyCSS
161
189
 
162
- return (
163
- <PolyCamera>
164
- <PolyScene>
165
- {polygons.map((p, i) => <Poly key={i} {...p} />)}
166
- </PolyScene>
167
- </PolyCamera>
168
- );
169
- }
170
- ```
190
+ [Layoutit Voxels](https://voxels.layoutit.com)
191
+ -> A CSS Voxel editor
192
+
193
+ <img width="1000" height="600" alt="layoutit-voxels" src="https://polycss.com/layoutit-voxels.png" />
194
+
195
+ [Layoutit Terra](https://terra.layoutit.com)
196
+ -> A CSS Terrain Generator
197
+
198
+ <img width="1000" height="601" alt="layoutit-terra" src="https://polycss.com/layoutit-terra.png" />
171
199
 
172
- ## Docs
200
+ ## License
173
201
 
174
- Full documentation at [polycss.com](https://polycss.com).
202
+ MIT.