@layoutit/polycss 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,200 +1,202 @@
1
- > **Status: pre-1.0. APIs may still change before a stable 1.0 release.**
1
+ # PolyCSS
2
2
 
3
- # polycss
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
- Vanilla JS / custom elements package 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
- Two entry points:
7
+ <img width="1600" height="300" alt="PolyCSS primitives banner" src="https://github.com/user-attachments/assets/b05e2204-9323-4f83-8d1b-01ea0dd000db" />
8
8
 
9
- - **`polycss`**: imperative `createPolyScene` API + custom element classes (without auto-registering them).
10
- - **`polycss/elements`**: side-effect import that registers the scene, mesh, polygon, controls, camera, helper, select, and transform-control custom elements.
11
-
12
- ## Install
9
+ ## Installation
13
10
 
14
11
  ```bash
12
+
13
+ # Vanilla
15
14
  npm install @layoutit/polycss
16
- ```
17
15
 
18
- Or via CDN (no build step):
16
+ # React
17
+ npm install @layoutit/polycss-react
18
+
19
+ # Vue
20
+ npm install @layoutit/polycss-vue
19
21
 
20
- ```html
21
- <script type="module" src="https://esm.sh/@layoutit/polycss/elements"></script>
22
22
  ```
23
23
 
24
- ## Custom elements (declarative, primary path)
25
24
 
26
- Register elements with the side-effect import:
25
+ You can also load PolyCSS directly from a CDN. Here is a minimal custom-element scene:
27
26
 
28
27
  ```html
29
28
  <script type="module" src="https://esm.sh/@layoutit/polycss/elements"></script>
30
29
 
31
- <poly-scene perspective="1000" rot-x="65" rot-y="45">
32
- <poly-mesh src="/cottage.glb"></poly-mesh>
33
- </poly-scene>
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>
34
36
  ```
35
37
 
36
- With per-polygon elements:
38
+ <img width="2500" height="1145" alt="PolyCSS intro" src="https://github.com/user-attachments/assets/0e5df0d8-04a8-4e50-8e3a-1097a96ce42f" />
37
39
 
38
- ```html
39
- <poly-scene perspective="1000" rot-x="65" rot-y="45">
40
- <poly-polygon
41
- vertices="[[0,0,0],[1,0,0],[0,1,0]]"
42
- color="#ff0000"
43
- ></poly-polygon>
44
- <poly-polygon
45
- vertices="[[2,0,0],[3,0,0],[2,1,0]]"
46
- color="#0000ff"
47
- ></poly-polygon>
48
- </poly-scene>
49
- ```
40
+ ## Framework Components
50
41
 
51
- Custom elements accept standard DOM events: no framework needed:
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.
52
43
 
53
- ```html
54
- <poly-scene id="scene" perspective="1000" rot-x="65" rot-y="45"></poly-scene>
44
+ ```tsx
45
+ import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react";
46
+
47
+ export default function App() {
48
+ return (
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" />
53
+ </PolyScene>
54
+ </PolyCamera>
55
+ );
56
+ }
57
+ ```
55
58
 
56
- <script type="module">
57
- import "https://esm.sh/@layoutit/polycss/elements";
59
+ ## API Reference
58
60
 
59
- const scene = document.querySelector("#scene");
61
+ ### PolyCamera
60
62
 
61
- const polygons = [
62
- { vertices: [[0,0,0],[1,0,0],[0,1,0]], color: "#f00", id: "a" },
63
- { vertices: [[2,0,0],[3,0,0],[2,1,0]], color: "#00f", id: "b" },
64
- ];
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.
65
68
 
66
- polygons.forEach(p => {
67
- const el = document.createElement("poly-polygon");
68
- el.setAttribute("vertices", JSON.stringify(p.vertices));
69
- el.setAttribute("color", p.color);
70
- el.addEventListener("click", () => console.log("clicked", p.id));
71
- el.addEventListener("mouseenter", () => el.classList.add("hover"));
72
- el.addEventListener("mouseleave", () => el.classList.remove("hover"));
73
- scene.appendChild(el);
74
- });
75
- </script>
69
+ ### PolyScene
76
70
 
77
- <style>
78
- poly-polygon.hover { filter: brightness(1.5); }
79
- </style>
80
- ```
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.
81
77
 
82
- ### Custom element attributes
78
+ ### PolyMesh
83
79
 
84
- **`<poly-scene>`**
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.
85
87
 
86
- | Attribute | Description |
87
- |---|---|
88
- | `perspective` | CSS perspective distance in pixels |
89
- | `rot-x` | Camera X-axis rotation in degrees |
90
- | `rot-y` | Camera Y-axis rotation in degrees |
91
- | `zoom` | Scale factor |
92
- | `directional-direction` | Comma-separated `x, y, z` e.g. `"0.5, -0.7, 0.6"` |
93
- | `directional-color` | Directional light color hex |
94
- | `directional-intensity` | Directional light intensity |
95
- | `ambient-intensity` | Ambient light intensity |
96
- | `ambient-color` | Ambient light color hex |
97
- | `texture-lighting` | `"baked"` or `"dynamic"` |
98
- | `atlas-scale` | Atlas bitmap budget and compositor sprite size; lower numeric values reduce memory/detail |
99
-
100
- For pointer drag, wheel zoom, and autorotate, drop a `<poly-orbit-controls>` child inside the scene (or wire `createPolyOrbitControls(scene, ...)` against the imperative API). For pan-first map-style input use `<poly-map-controls>` / `createPolyMapControls` instead. Mirrors Three.js's split between camera state (`<poly-scene>`) and camera input.
101
-
102
- **`<poly-mesh>`**
103
-
104
- | Attribute | Description |
105
- |---|---|
106
- | `src` | URL to `.obj`, `.glb`, `.gltf`, or `.vox` |
107
- | `position` | Comma-separated `x, y, z` |
108
- | `scale` | Uniform scale factor |
109
- | `rotation` | Comma-separated euler degrees `x, y, z` |
110
- | `auto-center` | Boolean: shift mesh bbox center to origin |
88
+ ### Controls
111
89
 
112
- **`<poly-polygon>`**
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.
113
94
 
114
- | Attribute | Description |
115
- |---|---|
116
- | `vertices` | JSON array of `[x,y,z]` arrays |
117
- | `color` | CSS color |
118
- | `texture` | Image URL |
119
- | `uvs` | JSON array of `[u,v]` pairs |
120
- | `position` | Comma-separated `x, y, z` |
121
- | `scale` | Uniform scale factor |
122
- | `rotation` | Comma-separated euler degrees `x, y, z` |
123
-
124
- ## Imperative API (escape hatch)
125
-
126
- For programmatic control without custom elements:
127
-
128
- ```js
129
- import { createPolyScene, loadMesh } from "@layoutit/polycss";
130
-
131
- const scene = createPolyScene(document.querySelector("#scene"), {
132
- perspective: 1000,
133
- rotX: 65,
134
- rotY: 45,
135
- directionalLight: { direction: [0.5, -0.7, 0.6] },
136
- });
95
+ ### Snapshot Export
137
96
 
138
- const mesh = await loadMesh("/cottage.glb", {
139
- gltfOptions: { targetSize: 60 },
140
- });
141
- const handle = scene.add(mesh, { position: [0, 0, 0] });
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.
98
+
99
+ ```ts
100
+ import { exportPolySceneSnapshot } from "@layoutit/polycss";
142
101
 
143
- // Later:
144
- handle.setTransform({ position: [5, 0, 0] });
145
- handle.remove();
146
- mesh.dispose();
102
+ const html = await exportPolySceneSnapshot(scene.host);
147
103
  ```
148
104
 
149
- ### Imperative API reference
105
+ If any referenced asset cannot be inlined, the function throws `PolySceneSnapshotError` with `code: "ASSET_INLINE_FAILED"`.
150
106
 
151
- **`createPolyScene(host, options)`**
107
+ ### Polygon Data Model
152
108
 
153
- | Option | Type | Description |
154
- |---|---|---|
155
- | `perspective` | `number` | CSS perspective distance |
156
- | `rotX` | `number` | Camera X rotation in degrees |
157
- | `rotY` | `number` | Camera Y rotation in degrees |
158
- | `zoom` | `number` | Camera zoom scale |
159
- | `distance` | `number` | Camera dolly pull-back in CSS pixels |
160
- | `target` | `Vec3` | World-coordinate camera target |
161
- | `directionalLight` | `PolyDirectionalLight` | Directional light config |
162
- | `ambientLight` | `PolyAmbientLight` | Ambient light config |
163
- | `textureLighting` | `"baked" \| "dynamic"` | Texture lighting mode |
164
- | `textureQuality` | `number \| "auto"` | Atlas bitmap budget and compositor sprite size |
165
- | `autoCenter` | `boolean` | Rotate around the union bbox center of added meshes |
109
+ Each polygon describes one renderable face:
166
110
 
167
- Returns a `PolySceneHandle`:
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
+ ```
124
+
125
+ Render polygons directly when you need per-face DOM events or custom styling:
126
+
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
+ ```
141
+
142
+ ## Loading Mesh Files
143
+
144
+ Use `loadMesh()` to parse supported model formats:
168
145
 
169
146
  ```ts
170
- interface PolySceneHandle {
171
- add(mesh: ParseResult, opts?: { position?: Vec3; scale?: number | Vec3; rotation?: Vec3 }): PolyMeshHandle;
172
- setOptions(partial: Partial<PolySceneOptions>): void;
173
- destroy(): void;
174
- }
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);
175
158
  ```
176
159
 
177
- **`loadMesh(url, options?)`**
160
+ Supported formats:
161
+
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.
167
+
168
+ ## Performance
169
+
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.
178
171
 
179
- Fetches and parses a mesh by URL (dispatches by extension: `.obj`, `.glb`, `.gltf`, `.vox`). Returns `Promise<ParseResult>`.
180
- Mesh optimization defaults to `meshResolution: "lossy"`; pass `"lossless"` for exact planar candidates only.
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.
181
173
 
182
- ## Subpath imports
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.
183
178
 
184
- | Import | Effect |
179
+ ## Packages
180
+
181
+ | Package | Description |
185
182
  |---|---|
186
- | `import { createPolyScene } from "@layoutit/polycss"` | Imperative API + custom element classes (no auto-registration) |
187
- | `import "@layoutit/polycss/elements"` | Side-effect: registers the polycss custom elements |
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. |
188
187
 
189
- ## Re-exports from `@layoutit/polycss-core`
188
+ ## Made with PolyCSS
190
189
 
191
- All `@layoutit/polycss-core` exports are re-exported from `@layoutit/polycss`, so vanilla users install one package:
190
+ [Layoutit Voxels](https://voxels.layoutit.com)
191
+ -> A CSS Voxel editor
192
192
 
193
- ```ts
194
- import { parseObj, parseGltf, parseVox, loadMesh, normalizePolygons } from "@layoutit/polycss";
195
- import type { Polygon, Vec3, ParseResult } from "@layoutit/polycss";
196
- ```
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" />
197
199
 
198
- ## Docs
200
+ ## License
199
201
 
200
- Full documentation at [polycss.com](https://polycss.com).
202
+ MIT.