Loading Meshes
PolyCSS loads 3D mesh files (OBJ, STL, glTF, GLB, and MagicaVoxel VOX) and renders them as DOM elements. UV textures are extracted from OBJ/glTF/GLB files and packed into generated atlas pages; STL imports are triangle meshes with optional binary Magics face colors, but no standard units, textures, UVs, or hierarchy.
Live demo: UV-textured OBJ
Section titled “Live demo: UV-textured OBJ”The broken stone slab below uses stone.obj with a companion stone.mtl file that points at stone_low_Material_Diffuse.png. Each UV-mapped face is a DOM sprite whose background is a generated atlas page.
Declarative loading: the mesh element
Section titled “Declarative loading: the mesh element”The simplest way to render a mesh is the mesh element with a src. It fetches the file, parses it, and renders one polygon element per polygon automatically.
<script type="module" src="https://esm.sh/@layoutit/polycss/elements"></script>
<poly-camera rot-x="65" rot-y="45"> <poly-scene> <poly-mesh src="https://polycss.com/gallery/obj/cottage.obj" mtl="https://polycss.com/gallery/obj/cottage.mtl" ></poly-mesh> </poly-scene></poly-camera>import { PolyCamera, PolyScene, PolyMesh } from "@layoutit/polycss-react";
export function App() { return ( <PolyCamera rotX={65} rotY={45}> <PolyScene> <PolyMesh src="https://polycss.com/gallery/obj/cottage.obj" mtl="https://polycss.com/gallery/obj/cottage.mtl" /> </PolyScene> </PolyCamera> );}<template> <PolyCamera :rot-x="65" :rot-y="45"> <PolyScene> <PolyMesh src="https://polycss.com/gallery/obj/cottage.obj" mtl="https://polycss.com/gallery/obj/cottage.mtl" /> </PolyScene> </PolyCamera></template>
<script setup lang="ts">import { PolyCamera, PolyScene, PolyMesh } from "@layoutit/polycss-vue";</script>Supported formats
Section titled “Supported formats”| Format | Extension | Notes |
|---|---|---|
| OBJ + MTL | .obj + .mtl | Text format. UV maps via vt. Material textures from map_Kd. |
| STL | .stl | ASCII or binary triangle mesh. Supports binary Magics face colors; STL has no standard units, textures, UVs, or hierarchy. |
| glTF | .gltf | JSON format. Embedded or external buffers. TEXCOORD_0 UVs. |
| GLB | .glb | Binary glTF. Embedded textures extracted as blob URLs. |
| MagicaVoxel | .vox | Voxel format. Exposed faces become colored polygon quads; eligible vanilla, React, and Vue meshes use a baked direct-voxel fast path. |
OBJ with MTL
Section titled “OBJ with MTL”When your OBJ has a companion MTL file with textures, PolyCSS reads map_Kd entries and applies them as UV-mapped textures. Pass the mtl attribute / prop:
<!-- Vanilla --><poly-mesh src="/rock.obj" mtl="/rock.mtl"></poly-mesh>// React<PolyMesh src="/rock.obj" mtl="/rock.mtl" parseOptions={{ objOptions: { targetSize: 40 } }}/>Material overrides
Section titled “Material overrides”Override material colors or textures without modifying the source files (React / Vue prop form):
<PolyMesh src="/character.obj" parseOptions={{ objOptions: { materialColors: { Skin: "#f4c2a1" }, materialTextures: { Body: "/body-diffuse.png" }, includeObjects: ["Body", "Head"], // only these objects }, }}/>For glTF/GLB files that preserved UVs but lost an external image reference, use the same material-name texture override under gltfOptions:
<PolyMesh src="/character.glb" parseOptions={{ gltfOptions: { materialTextures: { Texture: "/character-atlas.png" }, }, }}/>Imperative loading
Section titled “Imperative loading”For programmatic loading with explicit lifecycle, use loadMesh from the core parser. This is the universal vanilla path; React adds a usePolyMesh hook on top:
// Vanilla: works anywhere, no frameworkimport { createPolyCamera, loadMesh, createPolyScene } from "@layoutit/polycss";
const result = await loadMesh("https://polycss.com/gallery/obj/cottage.obj", { mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl", gltfOptions: { targetSize: 60 },});const camera = createPolyCamera({ rotX: 65, rotY: 45 });const scene = createPolyScene(document.getElementById("host")!, { camera });scene.add(result);// later:scene.destroy(); // removes the scene and disposes registered meshes// React: usePolyMesh wraps loadMesh + dispose() on unmountimport { PolyCamera, PolyScene, Poly, usePolyMesh } from "@layoutit/polycss-react";
function Viewer() { const { polygons, loading, error } = usePolyMesh("https://polycss.com/gallery/obj/cottage.obj", { mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl", });
if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>;
return ( <PolyCamera> <PolyScene> {polygons.map((p, i) => <Poly key={i} {...p} />)} </PolyScene> </PolyCamera> );}Atlas pipeline
Section titled “Atlas pipeline”For textured polygons, PolyCSS runs a one-time atlas canvas pass at mount. Flat-color polygons can bypass the atlas when they can render as CSS solids or border-shape polygons.
- Extract the texture image from the file (or fetch it by URL) when the polygon has a texture.
- Solve a 6-DOF affine transform from the polygon’s UV coordinates to its 2D screen footprint when UVs are available.
- Pack polygon footprints into one or more atlas pages.
- Clip, draw texture pixels or shaded color fills, and export atlas pages to blob URLs via
canvas.toBlob().textureQuality="auto"can rasterize these pages below full CSS resolution when packed pages would create oversized runtime bitmaps, and also selects the atlas leaf sprite size used for CSS compositing. - Repair antialiased atlas pixels along shared textured edges, then render each polygon as an
<s>withbackground-image,background-size, andbackground-position.
Generated atlas blob URLs are revoked on unmount (call dispose() or let PolyMesh / usePolyMesh handle it).
targetSize: scale the model so its longest axis fits this many world units (default:60)..voxmodels snap to the nearest integer voxel CSS cell size, so the final size may differ slightly to keep voxel fast-path coordinates integral.paletteMergeDistance/colorRegionMergeDistance: for.voxfiles with noisy palettes, fold nearby opaque, hue-compatible colors before greedy meshing and optionally clean up small local color islands/streaks. These are lossy and change authored colors, but can reduce material count and split-quad output. In the gallery and builder, the Mesh resolution control applies them only inLossymode;Losslesskeeps the authored palette exact.solidTextureSamples: when enabled throughloadMesh, texture-backed faces whose sampled UV region is effectively one color are converted to solid-color polygons before optimization. This avoids atlas slices for assets that use texture images as color swatches.- In the gallery and builder, Mesh resolution
Lossyalso collapses nearby colors produced by solid texture sampling on OBJ/GLB assets before mesh optimization.Losslesskeeps those sampled colors exact. textureQuality: leave at"auto"for workload-based bitmap caps and browser/device sprite sizing, or set a numeric raster scale for explicit quality.0.5uses about one quarter of the atlas bitmap memory of1.- Shared textured edges are repaired automatically during atlas generation. Geometry stays unchanged; only low-alpha atlas pixels at those shared edges are filled from nearby opaque texels.
baseUrl: for OBJ/glTF files with external texture paths, pass the file’s URL so relative paths resolve correctly.- For large meshes: blob URLs for embedded textures and generated atlases are revoked when
dispose()is called. Always let PolyCSS manage this: don’t hold references to blob URLs across remounts.
Related
Section titled “Related”- PolyScene: Scene props.
- Per-polygon Interaction: Click handlers and hover states on individual polygons.
- Performance: Merge modes and DOM tuning.
- Headless API:
loadMesh,parseObj,parseGltf,parseVox, andparseStlsignatures.