Core Concepts
This page covers the mental model behind PolyCSS. Each section describes a building block and how it composes with the others.
A single block
Section titled “A single block”Where voxcss used to render a single voxel cube, PolyCSS renders any regular polyhedron the same way: each face becomes one DOM element. The demo starts on an icosahedron; flip the shape selector to see how the same renderer handles every Platonic solid, including a dodecahedron with 12 native pentagons.
Three building blocks
Section titled “Three building blocks”PolyCSS exposes three composable concepts. Each one ships as a custom element (vanilla) and as a React / Vue component:
- Scene (
<poly-scene>/PolyScene): the render tree root. Always nested inside a camera element. Sets up lighting and fills its parent element. - Mesh (
<poly-mesh>/PolyMesh): loads a mesh from a URL (OBJ / STL / glTF / GLB / VOX). Internally expands to one polygon child per face. Convenience wrapper around the parser + renderer. - Polygon (
<poly-polygon>/Poly): one polygon. The atomic primitive. Renders as one internal DOM leaf withtransform: matrix3d(...). Accepts standard DOM event handlers, classes, and styles: this is what makes PolyCSS “DOM-native 3D” rather than “3D inside a black-box canvas”.
A mesh element is internally polygons.map(p => <poly-polygon …>), so any rendered mesh can be inspected, styled, or handled per-polygon.
Camera
Section titled “Camera”The camera element (<poly-camera> / PolyCamera) is always the outer node. <poly-scene> / PolyScene is nested inside it. Camera attributes (rot-x, rot-y, zoom, distance) belong on the camera element, never on the scene. PolyCamera is orthographic by default; use PolyPerspectiveCamera for depth foreshortening.
<!-- Vanilla --><poly-camera rot-x="65" rot-y="45"> <poly-scene directional-light='{"direction":[0.5,-0.7,0.6],"color":"#ffe4a8"}' ambient-light='{"intensity":0.4}'> <poly-octahedron size="100" color="#7dd3fc"></poly-octahedron> </poly-scene></poly-camera>// React<PolyCamera rotX={65} rotY={45}> <PolyScene directionalLight={{ direction: [0.5, -0.7, 0.6], color: "#ffe4a8", }} ambientLight={{ intensity: 0.4 }} > <PolyOctahedron size={100} color="#7dd3fc" /> </PolyScene></PolyCamera>See PolyCamera for the full prop table, defaults, and usage patterns.
Polygon Data Model
Section titled “Polygon Data Model”Each polygon is a plain object. The only required field is vertices (three or more [x, y, z] points in world space):
interface Polygon { vertices: [number, number, number][]; // Required: 3+ [x, y, z] points in world space color?: string; // CSS color ("#f97316", "tomato") texture?: string; // Image URL for UV-mapped face material?: PolyMaterial; // Shared texture material uvs?: [number, number][]; // UV coordinates (one per vertex) data?: Record<string, string | number | boolean>; // Reflected as data-* DOM attributes}Because polygons are plain objects, you can generate them from loops, load them from parsers, or compute them from any data source.
World coordinate convention
Section titled “World coordinate convention”PolyCSS world space: +X right, +Y forward (into screen), +Z up. The camera’s default rotX=65, rotY=45 gives a classic isometric angle. Parsers (parseObj, parseGltf, parseStl) normalize imported coordinates to this convention.
(0,0,0) origin and autoCenter
Section titled “(0,0,0) origin and autoCenter”Scene content renders relative to the (0,0,0) origin. Most mesh files are authored with the model at an arbitrary offset. Use autoCenter (vanilla: auto-center) on the mesh element to shift the mesh’s bounding-box center to the origin before applying your position offset:
<!-- Vanilla --><poly-mesh src="/model.glb" auto-center position="[0,0,0]"></poly-mesh>// React<PolyMesh src="/model.glb" autoCenter position={[0, 0, 0]} />Rendering Pipeline
Section titled “Rendering Pipeline”PolyCSS is structured in three layers:
- Core (
@layoutit/polycss-core): Pure math and parsing. Handles OBJ / STL / glTF / GLB / VOX parsing, UV decoding, lighting math, and polygon normalization. No DOM dependency. - DOM renderer: Takes parsed polygons and produces one leaf DOM element per visible polygon. The renderer prefers CSS primitives for solid quads, triangles, and clipped solids, then falls back to atlas slices for textures or unsupported shapes. Atlas canvas work is one-shot; camera, mesh, and dynamic-light updates use transforms and CSS custom properties.
- Entry points: The vanilla
@layoutit/polycsspackage exposes custom elements (<poly-camera>,<poly-scene>,<poly-mesh>,<poly-polygon>, controls, helpers, shapes) plus imperative APIs such ascreatePolyCamera,createPolyScene,createSelect, andcreateTransformControls. React (@layoutit/polycss-react) and Vue (@layoutit/polycss-vue) bindings mirror that surface with framework-native reactivity, lifecycle, and prop updates.
Render Strategies
Section titled “Render Strategies”The internal leaf tag is a strategy, not public API:
| Tag | Strategy | Typical use |
|---|---|---|
<b> | Solid quad | Axis-aligned rectangles and stable projective quads. |
<u> | Stable triangle / corner-shape solid | Solid triangles and exact beveled-corner solids. |
<i> | Border-shape clipped solid | Solid non-rect polygons on browsers with border-shape. |
<s> | Atlas slice | Textured polygons and fallback solids. |
You normally do not target these tags directly; use Poly, PolyMesh, classes, data attributes, or render stats. Cast shadows are separate SVG shadow surfaces, not render-strategy leaves; meshes with castShadow project onto the scene ground or receiver surfaces in both lighting modes.
Automatic Polygon Merge
Section titled “Automatic Polygon Merge”Before rendering, PolyCSS automatically optimizes loaded meshes. meshResolution: "lossless" keeps exact planar candidates only; the default "lossy" mode also bakes solid texture swatches, merges visually redundant baked swatch colors, tries static triangle simplification for eligible non-animated imports, and can merge near-coplanar candidates within a bounded displacement budget. Candidates are accepted only when the final DOM win is meaningful and whole-mesh seam diagnostics do not regress. STL imports use the conservative lossless path and skip ray-based interior culling in both modes because public CAD/STL files often contain shell, winding, or topology quirks. This keeps DOM element counts low for flat surfaces without changing the intended rendered shape.
Per-polygon DOM identity is preserved for polygons that cannot merge; polygons inside a merged flat region become one rendered element.
Related
Section titled “Related”- Quickstart: Install + first scene walkthrough.
- PolyCamera: Camera props reference.
- PolyScene: Scene props and polygon data reference.
- Performance: DOM tuning and parser options.