apresmoi/glyphcss

ASCII polygon mesh renderer for the DOM. Forked from LayoutitStudio/polycss.

TypeScript

212

433 commits

updated Sep 13, 2026

See the code

README

glyphcss

ASCII polygon-mesh renderer for the DOM — projects 3D meshes into a monospace character grid in a single <pre>. No WebGL, no canvas, no per-polygon DOM.

glyphcss — ETOPO1 world topography rasterised to ASCII

Loads OBJ, glTF, GLB, STL, and MagicaVoxel .vox files. Supports wireframe, solid, voxel, and ink render modes with swappable glyph palettes.

Full documentation: glyphcss.com — guides, component references, and the features not covered in this README.

Forked from polycss — the mesh math, parsers (OBJ / glTF / GLB / VOX), scene composition tree, camera math, and input controls carried over intact. The paint backend is rewritten: instead of emitting one CSS-transformed DOM leaf per polygon, the rasteriser walks all polygons, fills a cols × rows character grid, and writes a single string to <pre>.textContent per render.

Installation

# Vanilla / custom elements
npm install glyphcss

# React
npm install @glyphcss/react

# Vue 3
npm install @glyphcss/vue

# Reusable surface/scene effects (optional)
npm install @glyphcss/effects

You can also load glyphcss directly from a CDN. Here is a minimal custom-element scene:

<script type="module" src="https://esm.sh/glyphcss/elements"></script>

<glyph-camera rot-x="65" rot-y="45">
  <glyph-scene>
    <glyph-orbit-controls></glyph-orbit-controls>
    <glyph-mesh src="/cottage.glb"></glyph-mesh>
  </glyph-scene>
</glyph-camera>

Framework Components

React and Vue expose the same component model. <GlyphCamera> owns the viewpoint, <GlyphScene> owns the rasteriser options and lighting, and <GlyphMesh> loads or receives polygon data.

React

import {
  GlyphCamera,
  GlyphScene,
  GlyphOrbitControls,
  GlyphMesh,
} from "@glyphcss/react";

export default function App() {
  return (
    <GlyphCamera rotX={65} rotY={45}>
      <GlyphScene mode="solid" glyphPalette="default">
        <GlyphOrbitControls drag wheel />
        <GlyphMesh src="/gallery/obj/cottage.obj" />
      </GlyphScene>
    </GlyphCamera>
  );
}

Vue

<template>
  <GlyphCamera :rot-x="65" :rot-y="45">
    <GlyphScene mode="solid" glyph-palette="default">
      <GlyphOrbitControls drag wheel />
      <GlyphMesh src="/gallery/obj/cottage.obj" />
    </GlyphScene>
  </GlyphCamera>
</template>

<script setup lang="ts">
import {
  GlyphCamera,
  GlyphScene,
  GlyphOrbitControls,
  GlyphMesh,
} from "@glyphcss/vue";
</script>

Render Modes

Each render pass fills a cols × rows character grid and writes the result as a single string assignment to <pre>.textContent (or innerHTML when color spans are enabled).

ModeHow cells are filled
wireframePolygon edges rasterised as ASCII rules; glyph weight scales with edge prominence
solidFilled polygons; glyph picked from the palette's solid ramp by Lambert-shaded intensity
voxelCube-aligned geometry; face normals drive glyph selection
inkSilhouette + crease outline only; oriented glyphs trace the contour, interior stays empty

Glyph Palettes

The glyphPalette option selects a named character set used for both wireframe tiers and solid shading ramps. Built-in palettes:

default, ascii, dots, lines, blocks, solid, detail, stars, arrows, braille, runes, math, binary, hex

<GlyphScene mode="wireframe" glyphPalette="braille">
  <GlyphMesh src="/model.glb" />
</GlyphScene>

API Reference

GlyphCamera

GlyphCamera is the ergonomic default — it resolves to GlyphOrthographicCamera. Use GlyphPerspectiveCamera for perspective depth.

PropTypeDefaultDescription
rotXnumber65Tilt angle in degrees
rotYnumber45Spin angle in degrees
zoomnumber0.65Absolute scale in CSS pixels per world unit: zoom=50 → one world unit = 50 px. Not a viewport fraction.
center[number, number][0.5, 0.5]Projection center in normalized grid coordinates

GlyphPerspectiveCamera adds:

PropTypeDefaultDescription
distancenumber0Camera pull-back in CSS pixels under the default CSS-perspective projection. Larger = flatter.
stretchnumber1.0Extra horizontal scale on top of cellAspect

rotX=65, rotY=45 is the classic isometric-ish viewpoint. Rotation values are in degrees throughout (XYZ Euler) — there are no radians in the public API.

GlyphScene

Must be placed inside a camera component.

PropTypeDefaultDescription
mode"wireframe" | "solid" | "voxel" | "ink""solid"Render mode
glyphPalettestring"default"Named glyph character set
useColorsbooleantrueEmit <span> color elements inside the <pre>
colsnumber80Character grid width
rowsnumber24Character grid height
cellAspectnumber2.0Cell height / width ratio
directionalLightGlyphDirectionalLight{ direction: [0.5, 0.7, 0.5], intensity: 1 }Key light. direction points from the surface toward the light source.
ambientLightGlyphAmbientLight{ intensity: 0.4 }Fill light
smoothShadingbooleanfalseGouraud shading — interpolates Lambert intensity across vertices. Off by default (faceted ASCII look is intentional).
creaseAnglenumber60Degrees — edges sharper than this stay flat even with smoothShading on
autoSizebooleanfalseAuto-measure the host element and adapt cols/rows to fill it via ResizeObserver
shadowGlyphShadowOptionsundefinedEnable shadow mapping (see Shadows section)

GlyphMesh

PropTypeDefaultDescription
polygonsPolygon[]Pre-parsed geometry. Takes precedence over src and geometry.
srcstringURL of an OBJ, glTF, GLB, STL, or VOX file — fetched and parsed automatically
geometryGlyphGeometryNameBuilt-in geometry shortcut (e.g. "sphere", "cube")
sizenumber1Uniform size passed to resolveGeometry
colorstringFill color passed to resolveGeometry
positionVec3World-space translation
rotationVec3XYZ Euler rotation in degrees
scalenumber | Vec3Uniform or per-axis scale
castShadowbooleanfalseThis mesh casts shadows onto receiveShadow surfaces
receiveShadowbooleanfalseThis mesh receives (displays) shadows

A mesh that is both castShadow and receiveShadow self-shadows.

GlyphEffectLayer

Effects are ordered appearance programs mounted over the retained glyph frame. Parameter-only animation does not re-project the mesh, and the stable params object can be targeted directly by Anime.js or updated by a custom clock.

import { GlyphEffectLayer } from "@glyphcss/react";
import { GlyphEffects } from "@glyphcss/effects";

<GlyphEffectLayer
  effect={GlyphEffects.matrixRain}
  blend="replace"
  params={{ glyphs: "HOLA", speedMin: 5, speedMax: 12 }}
/>

Vanilla scenes use the same definition and handle:

const rain = scene.addEffectLayer({
  effect: GlyphEffects.matrixRain,
  blend: "replace",
});

function tick(now: number) {
  rain.params.time = now / 1000;
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

The catalog also includes flow text, scan, wipe, scramble, glitch, noise dissolve, ripple, and field synth. Flow text and scan default to space: "auto" (authored UVs when available, generated surface mapping otherwise); matrix rain defaults to space: "object", a volumetric field in the mesh's own local space. Matrix rain can keep the model's original surface colors or tint every strand with one monochrome color while preserving lighting and shape.

See the effects guide for the full parameter reference and mapping details.

GlyphGround

Convenience ground plane — a horizontal planePolygons registered as a mesh.

PropTypeDefaultDescription
sizenumber5Half-extent in world units
colorstring"#444444"Fill color
positionVec3[0, -0.5, 0]World-space position
castShadowbooleanfalse
receiveShadowbooleantrueGround planes are the primary shadow receivers

Controls

ComponentBehaviour
GlyphOrbitControlsDrag orbit, shift-drag pan, wheel zoom, optional auto-rotate
GlyphMapControlsPan-first map-style input
GlyphFirstPersonControlsKeyboard and pointer-look navigation

GlyphOrbitControls and GlyphMapControls accept drag, wheel, invert, and animate; orbit also accepts clampPitch.

Hotspots

GlyphHotspot is a 3D anchor that produces an absolutely-positioned DOM overlay tracking a world-space point. The rasteriser projects the at coordinate to a grid cell; glyphcss positions a <div> over that cell. Children are portalled inside that div.

<GlyphHotspot id="label-a" at={[0, 2, 0]}>
  <span className="label">Summit</span>
</GlyphHotspot>
PropTypeDescription
idstringStable identifier
atVec3World-space anchor
size[number, number]Hitbox size in character cells. Default [1, 1].

Polygon Data Model

Each polygon describes one renderable face:

import type { Polygon } from "@glyphcss/core";

const polygons: Polygon[] = [
  {
    vertices: [[0, 0, 0], [1, 0, 0], [0, 1, 0]],
    color: "#f97316",
  },
  {
    vertices: [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]],
    color: "#3b82f6",
  },
];

Polygons can also carry UV coordinates and TextureTriangle data for texture-mapped meshes (loaded via OBJ/MTL or glTF).

Pass polygons directly to a GlyphScene (imperative API) or a GlyphMesh component:

<GlyphCamera rotX={65} rotY={45}>
  <GlyphScene>
    <GlyphMesh polygons={polygons} />
  </GlyphScene>
</GlyphCamera>

Shadows

Shadows are opt-in. Enable them by setting shadow on <GlyphScene>, then flag individual meshes with castShadow and/or receiveShadow. glyphcss uses a shadow-map technique — renders depth from the light direction, then compares per cell — rather than an analytic projection.

<GlyphScene
  shadow={{ color: "#000000", opacity: 0.35, lift: 0.05, maxExtend: 2000 }}
>
  <GlyphMesh src="/tree.glb" castShadow />
  <GlyphGround receiveShadow />
</GlyphScene>
OptionTypeDefaultDescription
shadow.colorstring"#000000"Shadow tint hex color
shadow.opacitynumber0.25Darkness 0–1 toward color
shadow.liftnumber0.05Depth bias — prevents self-shadow acne on flat lit surfaces
shadow.maxExtendnumber2000Half-extent of the light-space projection volume

Loading Mesh Files

Use loadMesh() from @glyphcss/core to parse supported formats imperatively:

import {
  createGlyphScene,
  createGlyphOrthographicCamera,
  loadMesh,
} from "glyphcss";

const host = document.getElementById("scene")!;
const camera = createGlyphOrthographicCamera({ rotX: 65, rotY: 45 });
const scene = createGlyphScene(host, { camera });

const mesh = await loadMesh("/gallery/obj/cottage.obj");
scene.add(mesh.polygons);

In custom element HTML, set the src attribute directly — <glyph-mesh> fetches and parses the file automatically:

<glyph-mesh src="/model.glb"></glyph-mesh>
<glyph-mesh src="/model.obj"></glyph-mesh>
<glyph-mesh src="/model.vox"></glyph-mesh>

In React and Vue, use loadMesh (from @glyphcss/react or @glyphcss/vue) and pass the parsed polygons to <GlyphMesh>:

const { polygons } = await loadMesh("/model.glb");
<GlyphMesh polygons={polygons} />

Supported formats:

  • OBJ + MTL, including map_Kd textures and UV coordinates
  • glTF / GLB, including embedded images and TEXCOORD_0
  • STL (binary and ASCII)
  • MagicaVoxel .vox, with face-culling and default or custom palettes

Performance

glyphcss renders through a single <pre> element. The performance envelope is shaped by two things: the number of polygons walked per render and the size of the character grid written to the DOM.

Rendering is change-driven, not a fixed loop: a render is scheduled only when the camera or scene state actually changes, and multiple changes in the same tick are coalesced into a single pass. A static, un-interacted scene performs zero redraws. (Continuous animation — auto-rotate, inertia — does write every frame, but each frame is a genuinely different image.)

On every render pass:

  1. All mounted meshes are walked in scene order.
  2. Polygon vertices are transformed through the camera matrix to 2D projected positions.
  3. A cols × rows character grid is filled: polygons are depth-tested, each cell picks a glyph from the active palette.
  4. All cells are joined and written to <pre>.textContent (or .innerHTML for color mode) exactly once.

There are no per-polygon DOM elements and no CSS matrix3d. Hotspot overlays update via a single el.style.left/top assignment per hotspot per render — not a DOM rebuild.

autoSize uses a ResizeObserver to re-fit the grid whenever the host element resizes, keeping the character density constant regardless of viewport size.

Packages

Packagenpm nameDescription
@glyphcss/core@glyphcss/corePure math: Vec3, Polygon, scene, camera, mesh ops, parsers. Zero browser globals.
glyphcssglyphcssASCII rasteriser + vanilla custom elements + imperative createGlyphScene API.
@glyphcss/react@glyphcss/reactReact components, hooks, and controls.
@glyphcss/vue@glyphcss/vueVue 3 mirror of the React package.
@glyphcss/effects@glyphcss/effectsReusable spatial effect definitions; framework-agnostic and clock-free.
@glyphcss/fonts@glyphcss/fontsFont/text to extruded polygon-mesh generation.
@glyphcss/compile@glyphcss/compileStatic compiler, CLI, Vite plugin, and Node API.
@glyphcss/maps@glyphcss/mapsGeographic data → glyphcss: projections, elevation tiles, a relief mesh, and the interactive createGlyphMap widget with a MapLibre-shaped layer vocabulary.

License

MIT.

3d
3d-engine
ascii
ascii-art
css
dom
gltf
glyphcss
mesh
obj
react
renderer
typescript
voxel
vue

Contributors

alowpoly

225 commits

apresmoi

208 commits

apresmoi/glyphcss

ASCII polygon mesh renderer for the DOM. Forked from LayoutitStudio/polycss.

TypeScript

212

433 commits

updated Sep 13, 2026

See the code

README

glyphcss

ASCII polygon-mesh renderer for the DOM — projects 3D meshes into a monospace character grid in a single <pre>. No WebGL, no canvas, no per-polygon DOM.

glyphcss — ETOPO1 world topography rasterised to ASCII

Loads OBJ, glTF, GLB, STL, and MagicaVoxel .vox files. Supports wireframe, solid, voxel, and ink render modes with swappable glyph palettes.

Full documentation: glyphcss.com — guides, component references, and the features not covered in this README.

Forked from polycss — the mesh math, parsers (OBJ / glTF / GLB / VOX), scene composition tree, camera math, and input controls carried over intact. The paint backend is rewritten: instead of emitting one CSS-transformed DOM leaf per polygon, the rasteriser walks all polygons, fills a cols × rows character grid, and writes a single string to <pre>.textContent per render.

Installation

# Vanilla / custom elements
npm install glyphcss

# React
npm install @glyphcss/react

# Vue 3
npm install @glyphcss/vue

# Reusable surface/scene effects (optional)
npm install @glyphcss/effects

You can also load glyphcss directly from a CDN. Here is a minimal custom-element scene:

<script type="module" src="https://esm.sh/glyphcss/elements"></script>

<glyph-camera rot-x="65" rot-y="45">
  <glyph-scene>
    <glyph-orbit-controls></glyph-orbit-controls>
    <glyph-mesh src="/cottage.glb"></glyph-mesh>
  </glyph-scene>
</glyph-camera>

Framework Components

React and Vue expose the same component model. <GlyphCamera> owns the viewpoint, <GlyphScene> owns the rasteriser options and lighting, and <GlyphMesh> loads or receives polygon data.

React

import {
  GlyphCamera,
  GlyphScene,
  GlyphOrbitControls,
  GlyphMesh,
} from "@glyphcss/react";

export default function App() {
  return (
    <GlyphCamera rotX={65} rotY={45}>
      <GlyphScene mode="solid" glyphPalette="default">
        <GlyphOrbitControls drag wheel />
        <GlyphMesh src="/gallery/obj/cottage.obj" />
      </GlyphScene>
    </GlyphCamera>
  );
}

Vue

<template>
  <GlyphCamera :rot-x="65" :rot-y="45">
    <GlyphScene mode="solid" glyph-palette="default">
      <GlyphOrbitControls drag wheel />
      <GlyphMesh src="/gallery/obj/cottage.obj" />
    </GlyphScene>
  </GlyphCamera>
</template>

<script setup lang="ts">
import {
  GlyphCamera,
  GlyphScene,
  GlyphOrbitControls,
  GlyphMesh,
} from "@glyphcss/vue";
</script>

Render Modes

Each render pass fills a cols × rows character grid and writes the result as a single string assignment to <pre>.textContent (or innerHTML when color spans are enabled).

ModeHow cells are filled
wireframePolygon edges rasterised as ASCII rules; glyph weight scales with edge prominence
solidFilled polygons; glyph picked from the palette's solid ramp by Lambert-shaded intensity
voxelCube-aligned geometry; face normals drive glyph selection
inkSilhouette + crease outline only; oriented glyphs trace the contour, interior stays empty

Glyph Palettes

The glyphPalette option selects a named character set used for both wireframe tiers and solid shading ramps. Built-in palettes:

default, ascii, dots, lines, blocks, solid, detail, stars, arrows, braille, runes, math, binary, hex

<GlyphScene mode="wireframe" glyphPalette="braille">
  <GlyphMesh src="/model.glb" />
</GlyphScene>

API Reference

GlyphCamera

GlyphCamera is the ergonomic default — it resolves to GlyphOrthographicCamera. Use GlyphPerspectiveCamera for perspective depth.

PropTypeDefaultDescription
rotXnumber65Tilt angle in degrees
rotYnumber45Spin angle in degrees
zoomnumber0.65Absolute scale in CSS pixels per world unit: zoom=50 → one world unit = 50 px. Not a viewport fraction.
center[number, number][0.5, 0.5]Projection center in normalized grid coordinates

GlyphPerspectiveCamera adds:

PropTypeDefaultDescription
distancenumber0Camera pull-back in CSS pixels under the default CSS-perspective projection. Larger = flatter.
stretchnumber1.0Extra horizontal scale on top of cellAspect

rotX=65, rotY=45 is the classic isometric-ish viewpoint. Rotation values are in degrees throughout (XYZ Euler) — there are no radians in the public API.

GlyphScene

Must be placed inside a camera component.

PropTypeDefaultDescription
mode"wireframe" | "solid" | "voxel" | "ink""solid"Render mode
glyphPalettestring"default"Named glyph character set
useColorsbooleantrueEmit <span> color elements inside the <pre>
colsnumber80Character grid width
rowsnumber24Character grid height
cellAspectnumber2.0Cell height / width ratio
directionalLightGlyphDirectionalLight{ direction: [0.5, 0.7, 0.5], intensity: 1 }Key light. direction points from the surface toward the light source.
ambientLightGlyphAmbientLight{ intensity: 0.4 }Fill light
smoothShadingbooleanfalseGouraud shading — interpolates Lambert intensity across vertices. Off by default (faceted ASCII look is intentional).
creaseAnglenumber60Degrees — edges sharper than this stay flat even with smoothShading on
autoSizebooleanfalseAuto-measure the host element and adapt cols/rows to fill it via ResizeObserver
shadowGlyphShadowOptionsundefinedEnable shadow mapping (see Shadows section)

GlyphMesh

PropTypeDefaultDescription
polygonsPolygon[]Pre-parsed geometry. Takes precedence over src and geometry.
srcstringURL of an OBJ, glTF, GLB, STL, or VOX file — fetched and parsed automatically
geometryGlyphGeometryNameBuilt-in geometry shortcut (e.g. "sphere", "cube")
sizenumber1Uniform size passed to resolveGeometry
colorstringFill color passed to resolveGeometry
positionVec3World-space translation
rotationVec3XYZ Euler rotation in degrees
scalenumber | Vec3Uniform or per-axis scale
castShadowbooleanfalseThis mesh casts shadows onto receiveShadow surfaces
receiveShadowbooleanfalseThis mesh receives (displays) shadows

A mesh that is both castShadow and receiveShadow self-shadows.

GlyphEffectLayer

Effects are ordered appearance programs mounted over the retained glyph frame. Parameter-only animation does not re-project the mesh, and the stable params object can be targeted directly by Anime.js or updated by a custom clock.

import { GlyphEffectLayer } from "@glyphcss/react";
import { GlyphEffects } from "@glyphcss/effects";

<GlyphEffectLayer
  effect={GlyphEffects.matrixRain}
  blend="replace"
  params={{ glyphs: "HOLA", speedMin: 5, speedMax: 12 }}
/>

Vanilla scenes use the same definition and handle:

const rain = scene.addEffectLayer({
  effect: GlyphEffects.matrixRain,
  blend: "replace",
});

function tick(now: number) {
  rain.params.time = now / 1000;
  requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

The catalog also includes flow text, scan, wipe, scramble, glitch, noise dissolve, ripple, and field synth. Flow text and scan default to space: "auto" (authored UVs when available, generated surface mapping otherwise); matrix rain defaults to space: "object", a volumetric field in the mesh's own local space. Matrix rain can keep the model's original surface colors or tint every strand with one monochrome color while preserving lighting and shape.

See the effects guide for the full parameter reference and mapping details.

GlyphGround

Convenience ground plane — a horizontal planePolygons registered as a mesh.

PropTypeDefaultDescription
sizenumber5Half-extent in world units
colorstring"#444444"Fill color
positionVec3[0, -0.5, 0]World-space position
castShadowbooleanfalse
receiveShadowbooleantrueGround planes are the primary shadow receivers

Controls

ComponentBehaviour
GlyphOrbitControlsDrag orbit, shift-drag pan, wheel zoom, optional auto-rotate
GlyphMapControlsPan-first map-style input
GlyphFirstPersonControlsKeyboard and pointer-look navigation

GlyphOrbitControls and GlyphMapControls accept drag, wheel, invert, and animate; orbit also accepts clampPitch.

Hotspots

GlyphHotspot is a 3D anchor that produces an absolutely-positioned DOM overlay tracking a world-space point. The rasteriser projects the at coordinate to a grid cell; glyphcss positions a <div> over that cell. Children are portalled inside that div.

<GlyphHotspot id="label-a" at={[0, 2, 0]}>
  <span className="label">Summit</span>
</GlyphHotspot>
PropTypeDescription
idstringStable identifier
atVec3World-space anchor
size[number, number]Hitbox size in character cells. Default [1, 1].

Polygon Data Model

Each polygon describes one renderable face:

import type { Polygon } from "@glyphcss/core";

const polygons: Polygon[] = [
  {
    vertices: [[0, 0, 0], [1, 0, 0], [0, 1, 0]],
    color: "#f97316",
  },
  {
    vertices: [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]],
    color: "#3b82f6",
  },
];

Polygons can also carry UV coordinates and TextureTriangle data for texture-mapped meshes (loaded via OBJ/MTL or glTF).

Pass polygons directly to a GlyphScene (imperative API) or a GlyphMesh component:

<GlyphCamera rotX={65} rotY={45}>
  <GlyphScene>
    <GlyphMesh polygons={polygons} />
  </GlyphScene>
</GlyphCamera>

Shadows

Shadows are opt-in. Enable them by setting shadow on <GlyphScene>, then flag individual meshes with castShadow and/or receiveShadow. glyphcss uses a shadow-map technique — renders depth from the light direction, then compares per cell — rather than an analytic projection.

<GlyphScene
  shadow={{ color: "#000000", opacity: 0.35, lift: 0.05, maxExtend: 2000 }}
>
  <GlyphMesh src="/tree.glb" castShadow />
  <GlyphGround receiveShadow />
</GlyphScene>
OptionTypeDefaultDescription
shadow.colorstring"#000000"Shadow tint hex color
shadow.opacitynumber0.25Darkness 0–1 toward color
shadow.liftnumber0.05Depth bias — prevents self-shadow acne on flat lit surfaces
shadow.maxExtendnumber2000Half-extent of the light-space projection volume

Loading Mesh Files

Use loadMesh() from @glyphcss/core to parse supported formats imperatively:

import {
  createGlyphScene,
  createGlyphOrthographicCamera,
  loadMesh,
} from "glyphcss";

const host = document.getElementById("scene")!;
const camera = createGlyphOrthographicCamera({ rotX: 65, rotY: 45 });
const scene = createGlyphScene(host, { camera });

const mesh = await loadMesh("/gallery/obj/cottage.obj");
scene.add(mesh.polygons);

In custom element HTML, set the src attribute directly — <glyph-mesh> fetches and parses the file automatically:

<glyph-mesh src="/model.glb"></glyph-mesh>
<glyph-mesh src="/model.obj"></glyph-mesh>
<glyph-mesh src="/model.vox"></glyph-mesh>

In React and Vue, use loadMesh (from @glyphcss/react or @glyphcss/vue) and pass the parsed polygons to <GlyphMesh>:

const { polygons } = await loadMesh("/model.glb");
<GlyphMesh polygons={polygons} />

Supported formats:

  • OBJ + MTL, including map_Kd textures and UV coordinates
  • glTF / GLB, including embedded images and TEXCOORD_0
  • STL (binary and ASCII)
  • MagicaVoxel .vox, with face-culling and default or custom palettes

Performance

glyphcss renders through a single <pre> element. The performance envelope is shaped by two things: the number of polygons walked per render and the size of the character grid written to the DOM.

Rendering is change-driven, not a fixed loop: a render is scheduled only when the camera or scene state actually changes, and multiple changes in the same tick are coalesced into a single pass. A static, un-interacted scene performs zero redraws. (Continuous animation — auto-rotate, inertia — does write every frame, but each frame is a genuinely different image.)

On every render pass:

  1. All mounted meshes are walked in scene order.
  2. Polygon vertices are transformed through the camera matrix to 2D projected positions.
  3. A cols × rows character grid is filled: polygons are depth-tested, each cell picks a glyph from the active palette.
  4. All cells are joined and written to <pre>.textContent (or .innerHTML for color mode) exactly once.

There are no per-polygon DOM elements and no CSS matrix3d. Hotspot overlays update via a single el.style.left/top assignment per hotspot per render — not a DOM rebuild.

autoSize uses a ResizeObserver to re-fit the grid whenever the host element resizes, keeping the character density constant regardless of viewport size.

Packages

Packagenpm nameDescription
@glyphcss/core@glyphcss/corePure math: Vec3, Polygon, scene, camera, mesh ops, parsers. Zero browser globals.
glyphcssglyphcssASCII rasteriser + vanilla custom elements + imperative createGlyphScene API.
@glyphcss/react@glyphcss/reactReact components, hooks, and controls.
@glyphcss/vue@glyphcss/vueVue 3 mirror of the React package.
@glyphcss/effects@glyphcss/effectsReusable spatial effect definitions; framework-agnostic and clock-free.
@glyphcss/fonts@glyphcss/fontsFont/text to extruded polygon-mesh generation.
@glyphcss/compile@glyphcss/compileStatic compiler, CLI, Vite plugin, and Node API.
@glyphcss/maps@glyphcss/mapsGeographic data → glyphcss: projections, elevation tiles, a relief mesh, and the interactive createGlyphMap widget with a MapLibre-shaped layer vocabulary.

License

MIT.

3d
3d-engine
ascii
ascii-art
css
dom
gltf
glyphcss
mesh
obj
react
renderer
typescript
voxel
vue

Contributors

alowpoly

225 commits

apresmoi

208 commits

Languages

TypeScript

88.5%

Astro

3.4%

JavaScript

3.2%

MDX

2.7%

CSS

1.9%