PolyScene
The scene is the root of every PolyCSS render tree. It applies scene-level lighting and atlas options, then renders its children (typically meshes or individual polygons) in 3D space. PolyScene (React/Vue) must be nested inside a camera component (PolyCamera, PolyPerspectiveCamera, or PolyOrthographicCamera) — it throws otherwise — and createPolyScene() takes a required camera handle in its options. The <poly-scene> custom element prefers an ancestor camera element, but can stand alone: without one it builds an implicit camera from its own perspective, rot-x, rot-y, zoom, distance, and target attributes.
It’s available as a custom element (<poly-scene>), via the imperative createPolyScene(host, opts) API, and as React / Vue components (<PolyScene>).
Scene props / attributes
Section titled “Scene props / attributes”(React / Vue prop names use camelCase; the <poly-scene> custom element accepts the kebab-case form, e.g. textureQuality → texture-quality.)
The React/Vue components and createPolyScene() support the full table except rows marked otherwise (polygons, children, and centerPolygons are framework-only; shadow.dragDefinition — and the imperative-only debugShadowAttrs option, not listed here — are vanilla-only). The <poly-scene> custom element supports directional-*, ambient-*, texture-lighting, texture-quality, texture-leaf-sizing, texture-image-rendering, texture-backend, texture-projection, auto-center, and — when no ancestor camera element is present — the implicit camera attributes perspective, rot-x, rot-y, zoom, distance, and target. Only perspective, rot-x, rot-y, and zoom are observed: mutating distance or target alone does not update the implicit camera — their current values are read at connect time and re-applied only when a rot-x, rot-y, or zoom mutation next fires. (perspective likewise only selects the camera type at connect.) Use the imperative API for options such as shadow, seamBleed, and strategies in vanilla.
| Prop | Type | Default | Description |
|---|---|---|---|
directionalLight | PolyDirectionalLight | None | Directional light source. |
pointLights | PolyPointLight[] | None | Positional lights (direction-only, no falloff). Baked mode only; set castShadow: true per light for radial shadows. |
ambientLight | PolyAmbientLight | None | Ambient fill light. |
textureLighting | "baked" | "dynamic" | "baked" | Whether texture lighting is rasterized into atlases or computed with CSS variables. |
textureQuality | number | "auto" | "auto" | Atlas bitmap budget and compositor sprite size. Auto caps large runtime bitmaps and uses a larger desktop sprite to avoid Safari/Firefox flattening artifacts; lower numeric values reduce texture memory and detail. |
textureLeafSizing | "canonical" | "local" | "raster" | "canonical" | Texture leaf CSS primitive sizing. |
textureImageRendering | "auto" | "pixelated" | "auto" | Default image filtering for atlas and direct-image texture leaves. |
textureBackend | "auto" | "atlas" | "image" | "auto" | Default texture backend request per polygon. "auto" currently always resolves to the atlas — direct image leaves must be requested explicitly with "image". |
textureProjection | "affine" | "projective" | "affine" | Default texture projection request for textured quads. |
seamBleed | number | "auto" | 1.5 | Overscan on detected shared solid seam edges. Every request is additionally fitted per edge to what the polygon plan can safely absorb; 0 disables it. Semantics currently differ by renderer. Numbers: React/Vue treat the number as the requested CSS-pixel amount, while vanilla createPolyScene clamps it to 0..1 and multiplies the built-in 1.5 px default (so vanilla values above 1 behave like 1, capping at 1.5 px). "auto": vanilla resolves it to the full 1.5 px default, but React/Vue forward it into atlas planning, which accepts numeric amounts only — so an explicit "auto" in React/Vue produces no shared-edge overscan (per-strategy primitive bleeds are unaffected). Only the numeric default 1.5 behaves identically everywhere. |
strategies | { disable?: ("b" | "i" | "u")[] } | None | Diagnostic override for render strategy selection. Disabled solid strategies fall through to <s> atlas slices; <s> cannot be disabled. |
autoCenter | boolean | false | Rotate around the content bbox center instead of world origin. Polygon data is not mutated. |
centerPolygons | Polygon[] | None | (Framework only.) Bbox source for autoCenter when renderable polygons live inside child meshes. |
shadow | { color?, opacity?, lift?, maxExtend?, parametric?, definition?, style?, dragDefinition?, followAnimation? } | { color:"#000000", opacity:0.25, lift:0.05, maxExtend:2000 } | Appearance + SVG extent cap for cast shadows. parametric swaps to a cheap low-res silhouette (definition = detail, style: "vector" | "pixel"); dragDefinition (vanilla) and followAnimation cover light-drag and animated meshes. See Parametric shadows. |
polygons | Polygon[] | None | (Framework only.) Flat array of polygon objects rendered as direct children. Composes with JSX/slot children. |
children | None | None | Meshes, polygons, controls, helpers, selection wrappers, and transform controls. |
Camera state and input are normally set on the wrapping camera element (<poly-camera> / PolyCamera): rot-x, rot-y, zoom, distance. Without an ancestor camera element, <poly-scene> falls back to an implicit camera driven by its own perspective, rot-x, rot-y, zoom, distance, and target attributes (distance and target are read at connect and on the next rot-x / rot-y / zoom change — they are not live-observed on their own). Add a child <poly-orbit-controls> / <PolyOrbitControls> to enable drag, wheel, or autorotate: see PolyOrbitControls.
Mesh props / attributes
Section titled “Mesh props / attributes”React/Vue <PolyMesh> supports the full table. The <poly-mesh> custom element supports src, mtl, mesh-resolution, position, scale, rotation, auto-center, cast-shadow, receive-shadow, plus the OBJ parse attributes target-size, default-color, palette, include-objects, and exclude-objects (these five affect .obj sources only). position, scale, rotation, cast-shadow, and receive-shadow update live; changing src, mtl, mesh-resolution, or any of the OBJ parse attributes tears the mesh down and reloads it; auto-center is read at load only, so changing it after mount does nothing. Use scene.add(result, opts) for vanilla options the element doesn’t expose, such as merge, stableDom, and shadowDefinition.
| Prop | Type | Description |
|---|---|---|
id | string | Stable mesh identifier. Reflected as data-poly-mesh-id and exposed on mesh handles for selection / transform tools. |
src | string | URL to .obj, .stl, .glb, .gltf, or .vox. |
polygons | Polygon[] | Pre-parsed polygons (alternative to src). Framework only. |
position | Vec3 | [x, y, z] offset in scene space. |
scale | number | Vec3 | Uniform or per-axis scale. |
rotation | Vec3 | Euler rotation in degrees [x, y, z]. |
textureLighting | "baked" | "dynamic" | Per-mesh lighting mode override. React / Vue only; vanilla meshes inherit the scene value. |
textureQuality | number | "auto" | Atlas bitmap budget and compositor sprite size. React / Vue only; vanilla meshes inherit the scene’s texture-quality. |
textureLeafSizing | "canonical" | "local" | "raster" | Per-mesh override of the scene default. React / Vue only; vanilla meshes inherit the scene value. |
textureImageRendering | "auto" | "pixelated" | Per-mesh override of the scene default. React / Vue only; vanilla meshes inherit the scene value. |
textureBackend | "auto" | "atlas" | "image" | Per-mesh override of the scene default. React / Vue only; vanilla meshes inherit the scene value. |
textureProjection | "affine" | "projective" | Per-mesh override of the scene default. React / Vue only; vanilla meshes inherit the scene value. |
seamBleed | number | "auto" | Per-mesh solid seam overscan. React / Vue only; vanilla meshes inherit the scene setting. |
atomicAtlas | boolean | Hold the previous atlas frame until the next frame is decoded, then swap atomically. React / Vue only. |
onFrameReady | () => void | Fires when an atomic atlas frame swaps to a ready one. React / Vue only. |
autoCenter | boolean | Shift the loaded mesh so its bounding-box center sits at the local origin before applying position. Useful when assets aren’t centered in their file coordinates. |
mtl | string | Companion .mtl URL for OBJ models. |
parseOptions | UseMeshOptions | Parser options forwarded to loadMesh; meshResolution defaults to "lossy". |
meshResolution | "lossless" | "lossy" | Top-level optimizer intent. Wins over parseOptions.meshResolution; defaults to "lossy". |
castShadow | boolean | Emit SVG cast shadows in both lighting modes; projections update when light, ground, or mesh geometry changes. |
receiveShadow | boolean | Casters project per-coplanar-face SVG shadows onto this mesh’s visible surfaces (Three.js mesh.receiveShadow semantics). Defaults to false. In React/Vue, any receiver in the scene disables the casters’ ground-shadow fallback; vanilla has no such fallback, so a receiver is required for any shadow to appear. |
shadowDefinition | number | Per-mesh parametric-shadow detail, overriding the scene’s shadow.definition (only when shadow.parametric). |
merge | boolean | Run the polygon optimizer (dedupe, interior cull, coplanar/lossy merge). Defaults to true. Set false to render the polygon array entering the renderer exactly as given. It cannot restore source-file geometry: with src, loadMesh has already optimized the parse result before merge is consulted. |
fallback | ReactNode | Rendered while src is loading. React prop / Vue #fallback slot. |
errorFallback | (error: Error) => ReactNode | Rendered if parse fails. React prop / Vue #error slot. |
children | (polygon, index) => ReactNode | Per-polygon render prop / scoped slot. (React / Vue only.) |
Ground Props
Section titled “Ground Props”PolyGround is a React/Vue convenience component for a flat shadow-receiving plane.
| Prop | Type | Default | Description |
|---|---|---|---|
size | number | 6 | Side length in world units. |
z | number | 0 | World-space floor height. |
center | [number, number] | [0,0] | Ground center in world X/Y. |
color | string | "#7d848e" | Ground fill color. |
className / class | string | None | Additional CSS class. |
PolyDirectionalLight
Section titled “PolyDirectionalLight”interface PolyDirectionalLight { direction: [number, number, number]; // Surface-to-light source direction color?: string; // Light color (default: "#ffffff") intensity?: number; // Directional intensity (default: 1)}
interface PolyAmbientLight { color?: string; // Ambient tint (default: "#ffffff") intensity?: number; // Ambient intensity (default: 0.4)}Scene Helpers
Section titled “Scene Helpers”Helpers render as ordinary scene children and are available in vanilla custom elements plus React/Vue components.
| Helper | Props | Description |
|---|---|---|
<poly-axes-helper> / PolyAxesHelper | size, thickness, negative, xColor, yColor, zColor | Draws red/green/blue world axes from the origin. |
<poly-directional-light-helper> / PolyDirectionalLightHelper | React/Vue: light, target, distance, size, color. Vanilla: direction, target, distance, size, color. | Draws a small marker along a directional light vector. |
Basic Scene
Section titled “Basic Scene”A scene with a dodecahedron at default camera angle.
<script type="module" src="https://esm.sh/@layoutit/polycss/elements"></script>
<poly-camera rot-x="65" rot-y="45"> <poly-scene> <poly-dodecahedron size="100" color="#a78bfa"></poly-dodecahedron> </poly-scene></poly-camera>import { PolyCamera, PolyScene, PolyDodecahedron } from "@layoutit/polycss-react";
export function App() { return ( <PolyCamera rotX={65} rotY={45}> <PolyScene> <PolyDodecahedron size={100} color="#a78bfa" /> </PolyScene> </PolyCamera> );}<template> <PolyCamera :rot-x="65" :rot-y="45"> <PolyScene> <PolyDodecahedron :size="100" color="#a78bfa" /> </PolyScene> </PolyCamera></template>
<script setup lang="ts">import { PolyCamera, PolyScene, PolyDodecahedron } from "@layoutit/polycss-vue";</script>Scene with Camera and Lighting
Section titled “Scene with Camera and Lighting”import { PolyPerspectiveCamera, PolyScene, PolyTorus, PolyBox } from "@layoutit/polycss-react";
<PolyPerspectiveCamera perspective={1000} rotX={65} rotY={45}> <PolyScene directionalLight={{ direction: [0.5, -0.7, 0.6], color: "#ffe4a8" }} ambientLight={{ intensity: 0.4 }} > <PolyTorus color="#4ecdc4" position={[0, 0, 0]} /> <PolyBox size={60} color="#ffd166" position={[8, 0, 0]} /> </PolyScene></PolyPerspectiveCamera>Cast Shadows
Section titled “Cast Shadows”Shadows are SVG-projected surfaces that reproject when the light or scene geometry changes. Directional-light shadows work in both lighting modes. Point-light shadows are baked mode only — like point-light shading, they’re omitted in dynamic mode (a colored point shadow over a floor those lights never lit would look broken), so dynamic-mode shadows are directional-only. Where multiple lights overlap on a face, the shadows composite to the correct both-blocked color (each light shows the others’ color where it alone is blocked).
import { PolyCamera, PolyScene, PolyGround, PolyMesh } from "@layoutit/polycss-react";
<PolyCamera rotX={65} rotY={45}> <PolyScene textureLighting="dynamic" directionalLight={{ direction: [0.4, -0.6, 1], intensity: 1 }} shadow={{ opacity: 0.28, lift: 0.04 }} > <PolyGround size={8} color="#d8d2c7" /> <PolyMesh src="/model.glb" castShadow /> </PolyScene></PolyCamera>Strategy Diagnostics
Section titled “Strategy Diagnostics”Use strategies.disable when you need to compare paths or isolate browser rendering issues.
<PolyScene strategies={{ disable: ["b", "i", "u"] }}> <PolyMesh src="/model.glb" /></PolyScene>Multiple Meshes
Section titled “Multiple Meshes”<PolyCamera rotX={65} rotY={45}> <PolyScene> <PolyIcosahedron size={80} color="#ff6644" position={[0, 0, 0]} /> <PolyBox size={60} color="#7dd3fc" position={[10, 0, 0]} /> </PolyScene></PolyCamera>Flat Polygon Array
Section titled “Flat Polygon Array”Pass a polygons array directly to render static geometry without a file loader.
const polygons: Polygon[] = [ { vertices: [[0,0,0],[1,0,0],[0,1,0]], color: "#f00" }, { vertices: [[2,0,0],[3,0,0],[2,1,0]], color: "#00f" },];
<PolyCamera> <PolyScene polygons={polygons} /></PolyCamera>Related
Section titled “Related”- PolyCamera: Camera state and projection.
- Loading Meshes: OBJ, STL, glTF, GLB, VOX, MTL loading.
- Per-polygon Interaction: Using
Polyfor interactive per-polygon control. - Performance: Merge modes and DOM tuning.