@layoutit/polycss-core 0.2.0 → 0.2.1

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,135 +1,199 @@
1
- > **Status: pre-1.0. APIs may still change before a stable 1.0 release.**
1
+ # PolyCSS
2
2
 
3
- # @layoutit/polycss-core
3
+ A CSS polygon mesh library. A 3D engine for the DOM. Renders OBJ/MTL, 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
- Framework-agnostic math, parsers, and helpers for CSS polygon-mesh rendering. Zero browser globals: runs in Node, workers, or any JS environment.
5
+ Visit [polycss.com](https://polycss.com) for docs and model examples.
6
6
 
7
- This package contains the entire non-rendering side of polycss: OBJ / glTF / GLB / MagicaVoxel parsers, polygon normalization, coplanar merge, Lambert lighting, isometric camera state, and all shared TypeScript types.
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
- ## When to use directly
9
+ ## Installation
10
10
 
11
- Most users install `@layoutit/polycss-react`, `@layoutit/polycss-vue`, or `@layoutit/polycss` (vanilla). Those packages include `@layoutit/polycss-core` as a transitive runtime dependency and re-export its public types and functions, so you never need to write `import ... from "@layoutit/polycss-core"` in application code.
11
+ ```bash
12
12
 
13
- Install `@layoutit/polycss-core` directly when you:
13
+ # Vanilla
14
+ npm install @layoutit/polycss
14
15
 
15
- - Build custom rendering outside React / Vue / vanilla (e.g., a Svelte wrapper, a server-side OBJ validator, a CLI mesh processor).
16
- - Want only the parsers / math without any rendering layer.
17
- - Are writing a polycss plugin or tooling that must remain framework-neutral.
16
+ # React
17
+ npm install @layoutit/polycss-react
18
+
19
+ # Vue
20
+ npm install @layoutit/polycss-vue
18
21
 
19
- ```bash
20
- npm install @layoutit/polycss-core
21
22
  ```
22
23
 
23
- ## Public surface
24
24
 
25
- ### Types
25
+ You can also load PolyCSS directly from a CDN. Here is a minimal custom-element scene:
26
26
 
27
- | Type | Description |
28
- |---|---|
29
- | `Vec2` | `[number, number]`: 2D point or UV coordinate |
30
- | `Vec3` | `[number, number, number]`: 3D point or direction |
31
- | `Polygon` | Single renderable polygon: `vertices`, optional `color`, `texture`, `uvs`, `data` |
32
- | `PolyDirectionalLight` | Directional light: `direction`, optional `color`, optional `intensity` |
33
- | `PolyAmbientLight` | Ambient fill light: optional `color`, optional `intensity` |
34
- | `ParseResult` | Unified parser return: `polygons`, `objectUrls`, `dispose()`, `warnings` |
35
- | `ObjParseOptions` | Options for `parseObj` |
36
- | `GltfParseOptions` | Options for `parseGltf` |
37
- | `VoxParseOptions` | Options for `parseVox` |
38
- | `MtlParseResult` | `{ colors, textures }` from `parseMtl` |
39
- | `NormalizeResult` | `{ polygons, warnings }` from `normalizePolygons` |
40
- | `CameraState` | Camera target, angles, zoom, and dolly distance |
41
- | `CameraHandle` | Mutable camera object from `createIsometricCamera` |
42
- | `AutoRotateOption` | `boolean | number | { axis, speed, pauseOnInteraction }` |
43
- | `BoxPolygonsOptions` | Options for `boxPolygons`: size/center or min/max bounds, materials, face overrides |
44
-
45
- ### Functions
46
-
47
- | Function | Description |
48
- |---|---|
49
- | `normalizePolygons(input)` | Validates polygons. Drops degenerate ones, auto-triangulates non-coplanar N-gons, strips mismatched UVs. Returns `{ polygons, warnings }`. |
50
- | `mergePolygons(polygons)` | Coplanar same-material adjacent merge. Reduces DOM element count on flat surfaces. |
51
- | `optimizeMeshPolygons(polygons, options?)` | Applies lossless or lossy mesh-resolution optimization and chooses the smallest accepted candidate; defaults to `meshResolution: "lossy"`. |
52
- | `computeSceneBbox(polygons)` | Computes min/max bounds across all polygon vertices. |
53
- | `createIsometricCamera(initial?)` | Creates a mutable camera handle with `state`, `update(partial)`, and `getStyle()`. |
54
- | `boxPolygons(options?)` | Creates six quad `Polygon`s for an axis-aligned box/cuboid. Supports per-face material/data overrides and omitted faces. |
55
- | `parseObj(text, options?)` | Parses OBJ text into `ParseResult`. Supports UV (`vt`), materials, `map_Kd` textures. |
56
- | `parseMtl(text)` | Parses MTL text into `{ colors, textures }`. |
57
- | `parseGltf(buffer, options?)` | Parses GLB or glTF `ArrayBuffer` into `ParseResult`. Extracts embedded textures as blob URLs. |
58
- | `parseVox(buffer, options?)` | Parses MagicaVoxel `.vox` `ArrayBuffer` into `ParseResult`. Face-culls interior voxel faces and emits exposed quads. `targetSize` snaps to integer voxel CSS cells for the fast-path renderer. |
59
- | `loadMesh(url, options?)` | Fetches a URL, dispatches to the right parser by extension (`.obj`, `.glb`, `.gltf`, `.vox`). Returns `Promise<ParseResult>` and defaults to `meshResolution: "lossy"`. |
60
- | `parseColor(input)` | Parse any CSS color string to `{ r, g, b, a }`. |
61
- | `shadeColor(input, lambert, ...)` | Apply Lambert shading factor to a color. |
62
- | `computeShapeLighting(normal, baseColor, light?)` | Compute shaded color for a polygon face given a directional light and surface normal. |
63
-
64
- ## Examples
65
-
66
- ### Parse an OBJ file
27
+ ```html
28
+ <script type="module" src="https://esm.sh/@layoutit/polycss/elements"></script>
67
29
 
68
- ```ts
69
- import { parseObj } from "@layoutit/polycss-core";
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
+ ```
70
37
 
71
- const text = await fetch("/cottage.obj").then(r => r.text());
72
- const { polygons, warnings, dispose } = parseObj(text, {
73
- targetSize: 40,
74
- defaultColor: "#cccccc",
75
- });
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
76
41
 
77
- console.log(polygons.length, "polygons");
78
- warnings.forEach(w => console.warn(w));
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.
79
43
 
80
- // Revoke any blob URLs created during parse (OBJ rarely creates them,
81
- // but always call dispose() for correctness):
82
- dispose();
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
+ }
83
57
  ```
84
58
 
85
- ### Normalize a polygon list
59
+ ## API Reference
86
60
 
87
- ```ts
88
- import { normalizePolygons } from "@layoutit/polycss-core";
89
- import type { Polygon } from "@layoutit/polycss-core";
61
+ ### PolyCamera
90
62
 
91
- const raw: Polygon[] = [
92
- { vertices: [[0,0,0], [1,0,0], [0,1,0]], color: "#f00" },
93
- { vertices: [[0,0,0], [0,0,0], [0,0,0]] }, // degenerate: will be dropped
94
- { vertices: [[0,0,0], [1,0,0], [0.5,1,0], [0.5,1,0.1]] }, // non-coplanar quad → triangulated
95
- ];
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.
96
68
 
97
- const { polygons, warnings } = normalizePolygons(raw);
98
- console.log(polygons.length); // 2 (degenerate dropped; quad fan-triangulated into 2)
99
- warnings.forEach(w => console.warn(w));
100
- ```
69
+ ### PolyScene
101
70
 
102
- ### Merge coplanar polygons
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
+ - Solid seam bleed is automatic on detected shared solid edges.
76
+ - `strategies` can disable selected render strategies for diagnostics.
77
+ - `autoCenter` rotates around the rendered mesh bounds instead of world origin.
103
78
 
104
- ```ts
105
- import { parseGltf, mergePolygons } from "@layoutit/polycss-core";
79
+ ### PolyMesh
106
80
 
107
- const buf = await fetch("/cottage.glb").then(r => r.arrayBuffer());
108
- const { polygons, dispose } = parseGltf(buf, { targetSize: 60 });
81
+ - `src` loads `.obj`, `.gltf`, `.glb`, or `.vox` files.
82
+ - `mtl` loads companion OBJ materials.
83
+ - `polygons` accepts pre-parsed geometry.
84
+ - `position`, `scale`, and `rotation` transform the mesh wrapper.
85
+ - `autoCenter` shifts the mesh bbox center to local origin.
86
+ - `meshResolution` chooses `"lossy"` (default) or `"lossless"` optimization.
87
+ - `castShadow` emits CSS-projected shadows in dynamic lighting mode.
109
88
 
110
- // Merge coplanar same-material triangles to reduce DOM element count
111
- const merged = mergePolygons(polygons);
112
- console.log(`${polygons.length} triangles → ${merged.length} merged polygons`);
89
+ ### Controls
113
90
 
114
- dispose(); // always revoke GLB blob URLs when done
115
- ```
91
+ - `<PolyOrbitControls>` adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate.
92
+ - `<PolyMapControls>` uses pan-first map-style input.
93
+ - `<PolyFirstPersonControls>` provides keyboard and pointer-look navigation.
94
+ - `<PolyTransformControls>` adds translate/rotate gizmos for selected mesh handles.
116
95
 
117
- ### Create a box shape
96
+ ### Polygon Data Model
97
+
98
+ Each polygon describes one renderable face:
118
99
 
119
100
  ```ts
120
- import { boxPolygons } from "@layoutit/polycss-core";
121
-
122
- const polygons = boxPolygons({
123
- min: [0, 0, 0],
124
- max: [2, 1, 0.5],
125
- color: "#d8d2c7",
126
- data: { tileId: "tile-1" },
127
- faces: {
128
- top: {
129
- texture: "/tile.png",
130
- data: { face: "top" },
131
- },
132
- bottom: false,
101
+ const polygons = [
102
+ {
103
+ vertices: [[0, 0, 0], [60, 0, 0], [0, 60, 0]],
104
+ color: "#f97316",
105
+ },
106
+ {
107
+ vertices: [[0, 0, 0], [60, 0, 0], [60, 60, 0], [0, 60, 0]],
108
+ texture: "/texture.png",
109
+ uvs: [[0, 0], [1, 0], [1, 1], [0, 1]],
133
110
  },
111
+ ];
112
+ ```
113
+
114
+ Render polygons directly when you need per-face DOM events or custom styling:
115
+
116
+ ```tsx
117
+ <PolyCamera>
118
+ <PolyScene>
119
+ {polygons.map((polygon, index) => (
120
+ <Poly
121
+ key={index}
122
+ {...polygon}
123
+ onClick={() => console.log("clicked polygon", index)}
124
+ className="my-polygon"
125
+ />
126
+ ))}
127
+ </PolyScene>
128
+ </PolyCamera>
129
+ ```
130
+
131
+ ## Loading Mesh Files
132
+
133
+ Use `loadMesh()` to parse supported model formats:
134
+
135
+ ```ts
136
+ import { createPolyCamera, createPolyScene, loadMesh } from "@layoutit/polycss";
137
+
138
+ const host = document.getElementById("polycss")!;
139
+ const camera = createPolyCamera({ rotX: 65, rotY: 45 });
140
+ const scene = createPolyScene(host, { camera });
141
+
142
+ const mesh = await loadMesh("https://polycss.com/gallery/obj/cottage.obj", {
143
+ mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl",
134
144
  });
145
+
146
+ scene.add(mesh);
135
147
  ```
148
+
149
+ Supported formats:
150
+
151
+ - OBJ + MTL, including `map_Kd` textures and UV coordinates.
152
+ - glTF / GLB, including embedded images and `TEXCOORD_0`.
153
+ - MagicaVoxel `.vox`, with direct voxel fast paths when eligible.
154
+ - Generated primitives: box, plane, ring, sphere, torus, cylinder, cone, and Platonic solids.
155
+
156
+ ## Performance
157
+
158
+ PolyCSS renders in the DOM, so performance is mostly determined by how many polygons are mounted and how much texture atlas area they consume. The renderer uses several CSS strategies so simple surfaces stay cheap and textured or irregular surfaces fall back to atlas slices.
159
+
160
+ - One visible polygon becomes one leaf DOM element.
161
+ - Flat rectangles and stable quads use solid CSS leaves.
162
+ - Textured polygons are packed into generated texture atlases.
163
+ - Dynamic lighting runs through CSS custom properties instead of per-frame JavaScript.
164
+ - Voxel-shaped meshes mount only camera-facing leaves when the mesh is eligible.
165
+ - `meshResolution: "lossy"` merges compatible polygons, then may spend a small split budget to repair high-risk seams.
166
+
167
+ Renderer internals:
168
+
169
+ 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.
170
+
171
+ - `<b>` uses `background: currentColor` on a fixed box for solid rectangles and stable quads.
172
+ - `<u>` uses `corner-shape` for stable triangles and beveled-corner solids, with a `border-width` triangle fallback when needed.
173
+ - `<i>` clips solid polygons with `border-shape: polygon(...)` when the browser supports it.
174
+ - `<s>` maps a packed texture-atlas slice with `background-image`, and is the fallback for textured or unsupported shapes.
175
+
176
+ ## Packages
177
+
178
+ | Package | Description |
179
+ |---|---|
180
+ | `@layoutit/polycss-core` | Pure math, parsers, lighting, camera helpers, mesh optimization. Zero browser globals. |
181
+ | `@layoutit/polycss` | Vanilla custom elements and imperative `createPolyScene` API. |
182
+ | `@layoutit/polycss-react` | React components, hooks, controls, and core re-exports. |
183
+ | `@layoutit/polycss-vue` | Vue 3 components, composables, controls, and core re-exports. |
184
+
185
+ ## Made with PolyCSS
186
+
187
+ [Layoutit Voxels](https://voxels.layoutit.com)
188
+ -> A CSS Voxel editor
189
+
190
+ <img width="1000" height="600" alt="layoutit-voxels" src="https://polycss.com/layoutit-voxels.png" />
191
+
192
+ [Layoutit Terra](https://terra.layoutit.com)
193
+ -> A CSS Terrain Generator
194
+
195
+ <img width="1000" height="601" alt="layoutit-terra" src="https://polycss.com/layoutit-terra.png" />
196
+
197
+ ## License
198
+
199
+ MIT.