Skip to content

Loading Meshes

polycss loads 3D mesh files (OBJ, GLB, glTF) and renders them as DOM elements. UV textures are extracted from the file and packed into generated atlas pages; each textured polygon is an atlas-backed <i> that uses CSS background positioning.

The tree below uses tree.glb, which has embedded UV textures. Each textured triangle is a DOM sprite whose background is a generated atlas page.

Loading…

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/polycss/elements"></script>
<poly-scene rot-x="65" rot-y="45">
<poly-mesh src="/cottage.glb"></poly-mesh>
</poly-scene>
FormatExtensionNotes
OBJ + MTL.obj + .mtlText format. UV maps via vt. Material textures from map_Kd.
glTF.gltfJSON format. Embedded or external buffers. TEXCOORD_0 UVs.
GLB.glbBinary glTF. Embedded textures extracted as blob URLs.

When your OBJ has a companion MTL file with textures, polycss reads map_Kd entries and applies them as UV-mapped textures. Pass mtlUrl (vanilla attribute) or mtlUrl prop:

<!-- Vanilla -->
<poly-mesh src="/rock.obj" mtl-url="/rock.mtl" target-size="40"></poly-mesh>
// React / Vue
<PolyMesh
src="/rock.obj"
mtlUrl="/rock.mtl"
options={{ targetSize: 40 }}
/>

Override material colors or textures without modifying the source files (React / Vue prop form):

<PolyMesh
src="/character.obj"
options={{
materialColors: { Skin: "#f4c2a1" },
materialTextures: { Body: "/body-diffuse.png" },
includeObjects: ["Body", "Head"], // only these objects
}}
/>

For programmatic loading with explicit lifecycle, use loadMesh from the core parser. This is the universal vanilla path; React adds a useMesh hook on top:

// Vanilla — works anywhere, no framework
import { loadMesh, createPolyScene } from "polycss";
const result = await loadMesh("/cottage.glb", { targetSize: 60 });
const scene = createPolyScene(document.getElementById("host")!);
scene.add(result);
// later:
result.dispose(); // revoke blob URLs
scene.remove();
// React — useMesh wraps loadMesh + dispose() on unmount
import { PolyScene, Poly, useMesh } from "@polycss/react";
function Viewer() {
const { polygons, loading, error } = useMesh("/cottage.glb");
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<PolyScene>
{polygons.map((p, i) => <Poly key={i} {...p} />)}
</PolyScene>
);
}

For both textured and flat-color polygons, polycss runs a one-time atlas canvas pass at mount:

  1. Extract the texture image from the file (or fetch it by URL) when the polygon has a texture.
  2. Solve a 6-DOF affine transform from the polygon’s UV coordinates to its 2D screen footprint when UVs are available.
  3. Pack polygon footprints into one or more atlas pages.
  4. Clip, draw texture pixels or shaded color fills, and export atlas pages to blob URLs via canvas.toBlob(). atlasScale can rasterize these pages below full CSS resolution to reduce memory.
  5. Render each polygon as an <i> with background-image, background-size, and background-position.

Generated atlas blob URLs are revoked on unmount (call dispose() or let PolyMesh / useMesh handle it).

  • targetSize — scale the model so its longest axis fits this many world units (default: no scaling). Set it to roughly match the scale of your other scene content.
  • atlasScale — lower the generated atlas bitmap scale for dense or distant textured assets. 0.5 uses about one quarter of the atlas bitmap memory of 1.
  • 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.