@layoutit/polycss-react 0.2.7 → 0.2.8

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
@@ -21,7 +21,6 @@ npm install @layoutit/polycss-vue
21
21
 
22
22
  ```
23
23
 
24
-
25
24
  You can also load PolyCSS directly from a CDN. Here is a minimal custom-element scene:
26
25
 
27
26
  ```html
@@ -39,7 +38,7 @@ You can also load PolyCSS directly from a CDN. Here is a minimal custom-element
39
38
 
40
39
  ## Framework Components
41
40
 
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.
41
+ React and Vue expose the same component model. `<PolyCamera>` owns the viewpoint, `<PolyScene>` owns lighting and options, and `<PolyMesh>` loads or receives polygon data.
43
42
 
44
43
  ```tsx
45
44
  import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react";
@@ -56,6 +55,54 @@ export default function App() {
56
55
  }
57
56
  ```
58
57
 
58
+ ## Three.js Parity API
59
+
60
+ When porting Three.js scenes or generating code with an agent, use the explicit
61
+ `*/three` subpaths:
62
+
63
+ - `@layoutit/polycss-core/three`
64
+ - `@layoutit/polycss/three`
65
+ - `@layoutit/polycss-react/three`
66
+ - `@layoutit/polycss-vue/three`
67
+
68
+ They expose Three-like `PerspectiveCamera`, `OrthographicCamera`, `Object3D`,
69
+ `Vector3`, `DirectionalLight`, `PointLight`, `AmbientLight`, radians for object
70
+ rotations, Y-up authoring coordinates, and `camera.position` + `camera.lookAt(...)`
71
+ framing. The adapters convert into native PolyCSS coordinates with a right-handed
72
+ axis map, so the apparent object size, projection, orientation, depth ordering,
73
+ and light direction line up with Three.js scene math while still rendering
74
+ through the DOM.
75
+
76
+ ```tsx
77
+ import { PolyScene } from "@layoutit/polycss-react";
78
+ import {
79
+ DirectionalLight,
80
+ PolyThreeMesh,
81
+ PolyThreePerspectiveCamera,
82
+ } from "@layoutit/polycss-react/three";
83
+
84
+ const sun = new DirectionalLight("#ffffff", 1);
85
+ sun.position.set(3, 5, 4);
86
+ sun.target.position.set(0, 0, 0);
87
+
88
+ export function App() {
89
+ return (
90
+ <PolyThreePerspectiveCamera
91
+ fov={50}
92
+ aspect={16 / 9}
93
+ position={[3, 2, 5]}
94
+ lookAt={[0, 0, 0]}
95
+ >
96
+ <PolyScene directionalLight={sun.toPolyDirectionalLight()}>
97
+ <PolyThreeMesh src="/models/cube.glb" rotation={[0, Math.PI / 4, 0]} />
98
+ </PolyScene>
99
+ </PolyThreePerspectiveCamera>
100
+ );
101
+ }
102
+ ```
103
+
104
+ Full reference: [polycss.com/api/three-parity](https://polycss.com/api/three-parity).
105
+
59
106
  ## API Reference
60
107
 
61
108
  ### PolyCamera
@@ -187,10 +234,11 @@ Each visible polygon is emitted as one leaf element; the renderer chooses the le
187
234
 
188
235
  ## Made with PolyCSS
189
236
 
190
- [Layoutit Voxels](https://voxels.layoutit.com)
191
- -> A CSS Voxel editor
237
+ [cssQuake](https://cssquake.com)
238
+ -> A CSS port of Quake (1996)
239
+
240
+ <img width="1280" height="720" alt="quake" src="https://github.com/user-attachments/assets/6d9d809c-857a-4a39-b5cf-733ead2661ec" />
192
241
 
193
- <img width="1000" height="600" alt="layoutit-voxels" src="https://polycss.com/layoutit-voxels.png" />
194
242
 
195
243
  [Layoutit Terra](https://terra.layoutit.com)
196
244
  -> A CSS Terrain Generator
@@ -0,0 +1,343 @@
1
+ import * as React from 'react';
2
+ import { CSSProperties, MouseEventHandler, PointerEventHandler, FocusEventHandler, KeyboardEventHandler, ReactNode } from 'react';
3
+ import * as _layoutit_polycss_core from '@layoutit/polycss-core';
4
+ import { Vec3, PolyTextureImageSource, PolyTexturePresentation, Vec2, PolyMaterial, PolyDirectionalLight, PolyTextureLightingMode, TextureQuality, PolyTextureLeafSizing, PolyTextureImageRendering, PolyTextureBackend, PolyTextureProjection, LoadMeshOptions, Polygon, ParseResult, PolySeamBleed, MeshResolution } from '@layoutit/polycss-core';
5
+
6
+ /** Three.js-style transform props accepted by every PolyCSS component. */
7
+ interface TransformProps {
8
+ position?: Vec3;
9
+ scale?: number | Vec3;
10
+ rotation?: Vec3;
11
+ }
12
+ /**
13
+ * DOM event handlers, ARIA, and style props forwarded to the rendered
14
+ * element by every Poly component.
15
+ *
16
+ * This is the DOM-native pitch: polygons are real DOM nodes you can
17
+ * target with CSS, attach event handlers to, and inspect in DevTools.
18
+ */
19
+ interface DOMPassthroughProps {
20
+ className?: string;
21
+ style?: CSSProperties;
22
+ id?: string;
23
+ onClick?: MouseEventHandler<HTMLElement>;
24
+ onDoubleClick?: MouseEventHandler<HTMLElement>;
25
+ onMouseEnter?: MouseEventHandler<HTMLElement>;
26
+ onMouseLeave?: MouseEventHandler<HTMLElement>;
27
+ onMouseMove?: MouseEventHandler<HTMLElement>;
28
+ onPointerDown?: PointerEventHandler<HTMLElement>;
29
+ onPointerUp?: PointerEventHandler<HTMLElement>;
30
+ onPointerEnter?: PointerEventHandler<HTMLElement>;
31
+ onPointerLeave?: PointerEventHandler<HTMLElement>;
32
+ onFocus?: FocusEventHandler<HTMLElement>;
33
+ onBlur?: FocusEventHandler<HTMLElement>;
34
+ onKeyDown?: KeyboardEventHandler<HTMLElement>;
35
+ tabIndex?: number;
36
+ role?: string;
37
+ "aria-label"?: string;
38
+ "aria-hidden"?: boolean;
39
+ pointerEvents?: "auto" | "none";
40
+ [dataAttr: `data-${string}`]: string | number | boolean | undefined;
41
+ }
42
+ /**
43
+ * Props for the `<Poly>` component — the atomic polygon primitive.
44
+ *
45
+ * Extends TransformProps + DOMPassthroughProps with the polygon's own fields.
46
+ * This is the canonical Poly component API.
47
+ */
48
+ interface PolyProps extends TransformProps, DOMPassthroughProps {
49
+ vertices: Vec3[];
50
+ color?: string;
51
+ texture?: string;
52
+ textureImageSource?: PolyTextureImageSource;
53
+ texturePresentation?: PolyTexturePresentation;
54
+ uvs?: Vec2[];
55
+ data?: Record<string, string | number | boolean>;
56
+ doubleSided?: boolean;
57
+ /** Shared material. When set AND the polygon's UVs form an axis-aligned
58
+ * rectangle, renders via `background-image` directly — no per-polygon
59
+ * canvas rasterization. Falls back to the atlas path otherwise. */
60
+ material?: PolyMaterial;
61
+ context?: {
62
+ tileSize?: number;
63
+ layerElevation?: number;
64
+ directionalLight?: PolyDirectionalLight;
65
+ textureLighting?: PolyTextureLightingMode;
66
+ textureQuality?: TextureQuality;
67
+ textureLeafSizing?: PolyTextureLeafSizing;
68
+ textureImageRendering?: PolyTextureImageRendering;
69
+ textureBackend?: PolyTextureBackend;
70
+ textureProjection?: PolyTextureProjection;
71
+ debugShowBackfaces?: boolean;
72
+ [key: string]: unknown;
73
+ };
74
+ /** Textured polygon lighting mode. Defaults to scene context, then "baked". */
75
+ textureLighting?: PolyTextureLightingMode;
76
+ /** Atlas bitmap budget and CSS sprite size. `"auto"` (default) uses a
77
+ * device-appropriate memory budget and desktop/mobile sprite sizing. */
78
+ textureQuality?: TextureQuality;
79
+ /** Atlas leaf CSS primitive sizing. Defaults to scene context, then canonical. */
80
+ textureLeafSizing?: PolyTextureLeafSizing;
81
+ /** Default image filtering for atlas and direct image texture leaves. */
82
+ textureImageRendering?: PolyTextureImageRendering;
83
+ /** Default texture backend request. Defaults to scene context, then "auto". */
84
+ textureBackend?: PolyTextureBackend;
85
+ /** Default texture projection request. Defaults to scene context, then "affine". */
86
+ textureProjection?: PolyTextureProjection;
87
+ /** Pre-computed shaded base color from the parent (optional override). */
88
+ baseColor?: string;
89
+ }
90
+
91
+ type UseMeshOptions = LoadMeshOptions;
92
+ interface UseMeshResult {
93
+ polygons: Polygon[];
94
+ voxelSource: ParseResult["voxelSource"];
95
+ loading: boolean;
96
+ error: Error | null;
97
+ warnings: string[];
98
+ /** Manually trigger cleanup (also called on unmount automatically). */
99
+ dispose: () => void;
100
+ }
101
+ declare function usePolyMesh(src: string, options?: UseMeshOptions): UseMeshResult;
102
+
103
+ /**
104
+ * Pointer event API for <PolyMesh>. Mirrors @react-three/fiber's mesh
105
+ * event surface (handler names + payload shape) so devs migrating from
106
+ * three.js use the same mental model. PolyCSS is DOM-native so we get
107
+ * native pointer events for free — no raycaster, no canvas event
108
+ * synthesis.
109
+ *
110
+ * Diverges from r3f in two intentional ways:
111
+ * 1. Bubbling matches DOM (pointer hits the front element only). r3f
112
+ * replays events to occluded objects behind the front hit; doing
113
+ * that here would require running our own raycaster, which defeats
114
+ * the point of being DOM-native.
115
+ * 2. `face`, `uv`, `uv1`, `instanceId` are omitted — they're three.js
116
+ * BufferGeometry / InstancedMesh concepts with no polycss analogue.
117
+ * The polycss-native equivalent of `face` is the hit polygon's
118
+ * index, exposed via `polygon` (set when the underlying DOM target
119
+ * is an `<i>` polygon element).
120
+ */
121
+
122
+ /**
123
+ * Imperative handle exposed by `<PolyMesh ref>`. Read-only view of the
124
+ * mesh's element + current transform + polygons. Mutation flows through
125
+ * controlled props (parent owns transform state) — matches three.js
126
+ * editor's pattern of keeping `selected` external to the object.
127
+ */
128
+ interface PolyMeshHandle {
129
+ /** The `.polycss-mesh` wrapper div (null until mounted). */
130
+ readonly element: HTMLDivElement | null;
131
+ /** Identifier passed via the `id` prop, if any. */
132
+ readonly id?: string;
133
+ /** Current `position` prop value. */
134
+ getPosition(): Vec3 | undefined;
135
+ /** Current `rotation` prop value (Euler degrees). */
136
+ getRotation(): Vec3 | undefined;
137
+ /** Current `scale` prop value. */
138
+ getScale(): number | Vec3 | undefined;
139
+ /** Polygons currently being rendered (post-autoCenter). */
140
+ getPolygons(): Polygon[];
141
+ /**
142
+ * Replace the mesh polygons imperatively. Animated solid-triangle meshes use
143
+ * a stable DOM update path that mutates mounted leaves directly; unsupported
144
+ * topology falls back to the normal framework render path.
145
+ */
146
+ setPolygons(polygons: Polygon[]): void;
147
+ /**
148
+ * Snapshot the current `rotation` prop as the new "baked rotation" and
149
+ * trigger an atlas re-rasterization with the directional light
150
+ * inverse-rotated into the mesh's local frame.
151
+ *
152
+ * Call this after a rotate-mode drag ends (i.e. on pointer release) —
153
+ * **not** on every pointermove during the drag. The visual wrapper already
154
+ * follows the live `rotation` prop smoothly; the atlas only needs to
155
+ * update once per committed rotation so it doesn't re-bake every frame.
156
+ *
157
+ * Math rationale: baked atlas tiles encode `baseColor × Lambert(worldNormal,
158
+ * worldLight)`. When the mesh wrapper rotates via CSS the world-space normal
159
+ * changes but the baked color does not, causing stale shading. Calling
160
+ * `rebakeAtlas()` inverse-rotates the world light into the mesh-local frame
161
+ * before re-running the atlas baker, so `dot(localNormal, localLight) ===
162
+ * dot(worldNormal, worldLight)` and the shading is correct again.
163
+ *
164
+ * In dynamic (`textureLighting="dynamic"`) mode this call is a no-op for
165
+ * shading purposes (dynamic mode re-evaluates per frame), but it is still
166
+ * safe to call.
167
+ */
168
+ rebakeAtlas(): void;
169
+ /** Resolves when this mesh's current texture generation has usable backgrounds. */
170
+ whenTexturesReady(): Promise<void>;
171
+ /**
172
+ * Mutate a single polygon in place and re-render the mesh. `target` is
173
+ * either a polygon reference (as returned by `getPolygons()`) or its index.
174
+ * `partial` fields are merged onto the polygon via `Object.assign`. Skips
175
+ * the merge pass — cheaper than a full prop change for targeted edits like
176
+ * color picker updates from an inspector UI. Silently no-ops when `target`
177
+ * is not found or the index is out of range.
178
+ */
179
+ updatePolygon(target: Polygon | number, partial: Partial<Polygon>): void;
180
+ }
181
+ /**
182
+ * Pointer event payload delivered to <PolyMesh> handlers. Mirrors r3f's
183
+ * shape, minus raycaster-specific fields. See module docstring for the
184
+ * intentional divergences.
185
+ */
186
+ interface PolyPointerEvent<E extends Event = PointerEvent> {
187
+ /** The mesh originally under the pointer (deepest hit). */
188
+ object: PolyMeshHandle;
189
+ /** The mesh whose handler is being invoked. Equal to `object` until
190
+ * ancestor bubbling is added (out of scope for v1). */
191
+ eventObject: PolyMeshHandle;
192
+ /** All meshes stacked under the pointer this moment, front-to-back.
193
+ * Computed via `document.elementsFromPoint` then filtered to
194
+ * registered `.polycss-mesh` ancestors. */
195
+ intersections: Array<{
196
+ object: PolyMeshHandle;
197
+ }>;
198
+ /** Pointer position in normalized device coords [-1, 1] relative to
199
+ * the camera viewport. (0,0) = viewport center. Falls back to (0,0)
200
+ * when the mesh is rendered outside a `<PolyCamera>`. */
201
+ pointer: {
202
+ x: number;
203
+ y: number;
204
+ };
205
+ /** Pixel distance from the most recent `pointerdown` to this event.
206
+ * 0 on pointerdown itself. Use to discriminate click-vs-drag. */
207
+ delta: number;
208
+ /** The underlying DOM event. */
209
+ nativeEvent: E;
210
+ /** Stops native bubbling. (Equivalent to `nativeEvent.stopPropagation()`
211
+ * today; reserved for future r3f-style bubbling above the wrapper.) */
212
+ stopPropagation(): void;
213
+ }
214
+ type PolyMouseEvent = PolyPointerEvent<MouseEvent>;
215
+ type PolyWheelEvent = PolyPointerEvent<WheelEvent>;
216
+ type PolyEventHandler<E extends Event = PointerEvent> = (event: PolyPointerEvent<E>) => void;
217
+ /**
218
+ * Pointer / mouse / wheel handlers accepted by `<PolyMesh>`. Names mirror
219
+ * r3f exactly. Provide any handler to opt the mesh into receiving events;
220
+ * absent handlers add zero overhead.
221
+ */
222
+ interface InteractionProps {
223
+ onClick?: PolyEventHandler<MouseEvent>;
224
+ onContextMenu?: PolyEventHandler<MouseEvent>;
225
+ onDoubleClick?: PolyEventHandler<MouseEvent>;
226
+ onWheel?: PolyEventHandler<WheelEvent>;
227
+ onPointerDown?: PolyEventHandler<PointerEvent>;
228
+ onPointerUp?: PolyEventHandler<PointerEvent>;
229
+ onPointerMove?: PolyEventHandler<PointerEvent>;
230
+ onPointerOver?: PolyEventHandler<PointerEvent>;
231
+ onPointerOut?: PolyEventHandler<PointerEvent>;
232
+ onPointerEnter?: PolyEventHandler<PointerEvent>;
233
+ onPointerLeave?: PolyEventHandler<PointerEvent>;
234
+ onPointerCancel?: PolyEventHandler<PointerEvent>;
235
+ }
236
+ /** Walk up from `el` looking for the nearest registered mesh wrapper. */
237
+ declare function findPolyMeshHandle(el: Element | null): PolyMeshHandle | null;
238
+ /** Test whether `(clientX, clientY)` falls inside any polygon leaf
239
+ * child of `meshEl`'s post-3D bounding rect. Skips zero-area rects
240
+ * (happy-dom and pre-layout SSR return those). */
241
+ declare function pointInMeshElement(meshEl: HTMLElement, clientX: number, clientY: number): boolean;
242
+ /** Walk every registered `.polycss-mesh` in the document and return
243
+ * the first whose polygon bounding-rects contain `(clientX, clientY)`.
244
+ * An optional `filter` skips matched mesh elements (e.g. gizmos). */
245
+ declare function findMeshUnderPoint(clientX: number, clientY: number, filter?: (meshEl: HTMLElement) => boolean): PolyMeshHandle | null;
246
+
247
+ interface PolyMeshProps extends TransformProps, InteractionProps {
248
+ /** Stable identifier — exposed on the mesh handle and reflected as
249
+ * `data-poly-mesh-id` on the wrapper div. Use for selection lookups. */
250
+ id?: string;
251
+ /** URL to .obj / .glb / .gltf. Mutually exclusive with `polygons`. */
252
+ src?: string;
253
+ /**
254
+ * Companion `.mtl` URL for OBJ models. When set, materials defined in
255
+ * the mtl (Kd colors, map_Kd textures) are applied to the loaded mesh.
256
+ * Ignored for GLB/GLTF (they carry materials inline).
257
+ */
258
+ mtl?: string;
259
+ /** Pre-parsed polygons. Mutually exclusive with `src`. */
260
+ polygons?: Polygon[];
261
+ /** Optional `parseResult.voxelSource` companion for `.vox` meshes. When
262
+ * set alongside `polygons`, the direct voxel renderer fast path activates
263
+ * — emitting one `<b>` per visible voxel quad inside `.polycss-voxel-face`
264
+ * wrappers (matches vanilla's `scene.add(parseResult)` behaviour).
265
+ * Callers fetching via core's `loadMesh()` pass `parseResult.voxelSource`
266
+ * here so the fast path engages; callers fetching via PolyMesh's `src`
267
+ * prop get the same data wired through `useMesh` automatically. */
268
+ voxelSource?: _layoutit_polycss_core.ParseResult["voxelSource"];
269
+ /** Translate so mesh's bbox center is at local origin before applying `position`. */
270
+ autoCenter?: boolean;
271
+ /** Textured polygon lighting mode. Defaults to "baked". */
272
+ textureLighting?: PolyTextureLightingMode;
273
+ /** Atlas bitmap budget and CSS sprite size. `"auto"` (default) uses a
274
+ * device-appropriate memory budget (~4 MB mobile / ~16 MB desktop) and
275
+ * desktop/mobile sprite sizing. Numeric values 0.1..1 force an explicit
276
+ * raster scale and the 64px sprite. */
277
+ textureQuality?: TextureQuality;
278
+ /** Atlas leaf CSS primitive sizing. Defaults to scene context, then canonical. */
279
+ textureLeafSizing?: PolyTextureLeafSizing;
280
+ /** Default image filtering for atlas and direct image texture leaves. */
281
+ textureImageRendering?: PolyTextureImageRendering;
282
+ /** Default texture backend request. Defaults to scene context, then "auto". */
283
+ textureBackend?: PolyTextureBackend;
284
+ /** Default texture projection request. Defaults to scene context, then "affine". */
285
+ textureProjection?: PolyTextureProjection;
286
+ /** Solid seam overscan. `"auto"` computes a fitted per-edge amount from the polygon plan. */
287
+ seamBleed?: PolySeamBleed;
288
+ /**
289
+ * Hold the whole previous frame (geometry + texture) until the next atlas is
290
+ * decoded, then swap atomically — so a geometry edit never shows geometry
291
+ * before its texture. Best when edits arrive as discrete commits (no
292
+ * continuous drag). Defaults to false (bitmap streams in over live geometry).
293
+ */
294
+ atomicAtlas?: boolean;
295
+ /** Fires when the displayed atlas frame swaps to a ready one (atomic mode). */
296
+ onFrameReady?: () => void;
297
+ /** Per-polygon override render, or static children mounted inside the mesh wrapper. */
298
+ children?: ((polygon: Polygon, index: number) => ReactNode) | ReactNode;
299
+ /** Loading slot — rendered while `src` is being fetched/parsed. */
300
+ fallback?: ReactNode;
301
+ /** Error slot — rendered if parse fails. Receives the Error. */
302
+ errorFallback?: (error: Error) => ReactNode;
303
+ /** Parser options forwarded to parseObj/parseGltf. */
304
+ parseOptions?: UseMeshOptions;
305
+ /** Mesh optimization intent. Defaults to "lossy"; set "lossless" to keep
306
+ * authored surface fidelity. Top-level prop wins over `parseOptions.meshResolution`
307
+ * when both are present. */
308
+ meshResolution?: MeshResolution;
309
+ /**
310
+ * When `true`, emits a per-mesh SVG shadow path in both lighting modes.
311
+ * Each casting polygon projects onto the scene ground plane along the
312
+ * directional light; overlapping outlines are merged into one silhouette.
313
+ * Defaults to `false`.
314
+ */
315
+ castShadow?: boolean;
316
+ /**
317
+ * When `true`, this mesh acts as a shadow receiver. The scene's caster
318
+ * meshes (those with `castShadow=true`) project per-coplanar-face SVG
319
+ * shadows onto each visible surface of this mesh, matching Three.js's
320
+ * `mesh.receiveShadow` semantics. Disables the ground-shadow fallback
321
+ * for caster meshes — receivers handle shadow display. Defaults to `false`.
322
+ */
323
+ receiveShadow?: boolean;
324
+ /**
325
+ * Per-mesh parametric-shadow detail, overriding the scene's
326
+ * `shadow.definition` for this mesh's cast/self shadow. Only used when the
327
+ * scene's `shadow.parametric` is true. Unset → inherit the scene definition.
328
+ */
329
+ shadowDefinition?: number;
330
+ /**
331
+ * Apply mesh optimization (coplanar merge + interior cull) before
332
+ * rendering. Defaults to `true` — matches vanilla `scene.add`'s default.
333
+ * Set `false` for helper meshes (axes, light markers) whose geometry
334
+ * shouldn't be merged, or when the imperative `updatePolygon` API needs
335
+ * polygon refs to survive across updates.
336
+ */
337
+ merge?: boolean;
338
+ className?: string;
339
+ style?: CSSProperties;
340
+ }
341
+ declare const PolyMesh: React.ForwardRefExoticComponent<PolyMeshProps & React.RefAttributes<PolyMeshHandle>>;
342
+
343
+ export { type DOMPassthroughProps as D, type InteractionProps as I, type PolyProps as P, type TransformProps as T, type UseMeshOptions as U, type PolyMeshProps as a, type PolyMeshHandle as b, type PolyEventHandler as c, PolyMesh as d, type PolyMouseEvent as e, type PolyPointerEvent as f, type PolyWheelEvent as g, type UseMeshResult as h, findMeshUnderPoint as i, findPolyMeshHandle as j, pointInMeshElement as p, usePolyMesh as u };