npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

heerich

v0.14.0

Published

A tiny engine for 3D voxel scenes rendered to SVG

Readme

heerich.js

A tiny engine for 3D voxel scenes rendered to SVG. Build shapes with CSG-like boolean operations, style individual faces, and output crisp vector graphics — no WebGL, no canvas, just <svg>.

Named after Erwin Heerich, the German sculptor known for geometric cardboard sculptures.

Install

npm install heerich
import { Heerich } from 'heerich'

Or use the UMD build via a <script> tag — the global Heerich will be available.

Quick Start

import { Heerich } from 'heerich'

const h = new Heerich({
  tile: 40,
  camera: { type: 'oblique', angle: 45, distance: 15 },
})

// A simple house
h.applyGeometry({ type: 'box', position: [0, 0, 0], size: [5, 4, 5], style: {
  default: { fill: '#e8d4b8', stroke: '#333' },
  top:     { fill: '#c94c3a' },
}})

// Carve out a door
h.removeGeometry({
  type: 'box',
  position: [2, 1, 0],
  size: [1, 3, 1]
})

document.body.innerHTML = h.toSVG()

Camera

The engine exposes four projection types:

  • oblique (default): Parallel projection where depth recedes at a configurable angle. Classic pixel-art / cabinet-projection look.
  • perspective: Single-vanishing-point projection with an explicit camera position.
  • orthographic: True 3D parallel projection with configurable pan (angle) and tilt (pitch). Use this for dimetric, trimetric, or any custom orthographic view.
  • isometric: Orthographic preset with pitch locked to 35.264°. For the classic isometric diamond grid.
// Oblique (parallel)
new Heerich({ camera: { type: 'oblique', angle: 45, distance: 15 } })

// Perspective (1-point)
new Heerich({ camera: { type: 'perspective', position: [5, 5], distance: 10 } })

// Orthographic (parallel 3D)
new Heerich({ camera: { type: 'orthographic', angle: 45, pitch: 35.264 } })

// Isometric (orthographic with fixed pitch)
new Heerich({ camera: { type: 'isometric', angle: 45 } })

// Update camera at any time
h.setCamera({ angle: 30, distance: 20 })

The angle parameter

The angle parameter is shared across camera types for easy switching, but its meaning differs:

| Type | angle controls | Default | |------|-----------------|---------| | oblique | Direction the depth (Z) axis recedes | 45° | | orthographic | Horizontal rotation (pan) around the scene | 45° | | isometric | Horizontal rotation — recommended values: 45°, 135°, 225°, 315° | 45° | | perspective | (Mapped to camera X position) | — |

For isometric, any angle value works, but 45°, 135°, 225°, and 315° produce the standard isometric orientations where edges align to the pixel grid.

Note: orthographic and isometric use parallel projection — distance has no effect in these modes.

Shapes

All shape methods accept a common set of options:

| Option | Type | Description | |-----------|------|-------------| | mode | 'union' | 'subtract' | 'intersect' | 'exclude' | Boolean operation (default: 'union') | | style | object or function | Per-face styles (see Styling) | | content | string | Raw SVG content to render instead of polygon faces | | opaque | boolean | Whether this voxel occludes neighbors (default: true) | | meta | object | Key/value pairs emitted as data-* attributes on SVG polygons | | rotate | object | Rotate coordinates before placement (see Rotation) | | scale | [x, y, z] or (x, y, z) => [sx, sy, sz] | Per-axis scale 0–1 (auto-sets opaque: false) | | scaleOrigin | [x, y, z] or (x, y, z) => [ox, oy, oz] | Scale anchor within the voxel cell (default: [0.5, 0, 0.5]) | | gap | number | Gap between voxels, 0–0.5 (overrides constructor default) |

Convenience methods

  • addGeometry(opts) — shortcut for applyGeometry({ ...opts, mode: 'union' })
  • removeGeometry(opts) — shortcut for applyGeometry({ ...opts, mode: 'subtract' })

Uniform positioning

Box, sphere, and fill all accept both position (min-corner) and center (geometric center) — the engine converts between them automatically based on the shape's size:

// These are equivalent for a 5×5×5 box:
h.applyGeometry({ type: 'box', position: [0, 0, 0], size: 5 })
h.applyGeometry({ type: 'box', center: [2, 2, 2], size: 5 })

// These are equivalent for a sphere with radius 3:
h.applyGeometry({ type: 'sphere', center: [3, 3, 3], radius: 3 })
h.applyGeometry({ type: 'sphere', position: [0, 0, 0], radius: 3 })
h.applyGeometry({ type: 'sphere', center: [3, 3, 3], size: 7 })

Fill also accepts position/center + size as an alternative to bounds.

Box

h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: [3, 2, 4]
})
h.removeGeometry({
  type: 'box',
  position: [1, 0, 1],
  size: 1
})

// Style the carved walls (optional)
h.removeGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: 1,
  style: { default: { fill: '#222' } }
})

Sphere

h.applyGeometry({
  type: 'sphere',
  center: [5, 5, 5],
  radius: 3
})
h.removeGeometry({
  type: 'sphere',
  center: [5, 5, 5],
  radius: 1.5
})

// Style the carved walls (optional)
h.removeGeometry({
  type: 'sphere',
  center: [5, 5, 5],
  radius: 1,
  style: { default: { fill: '#222' } }
})

Line

Lines are the only shape that uses different positioning — from/to instead of position/center + size:

h.applyGeometry({
  type: 'line',
  from: [0, 0, 0],
  to: [10, 5, 0]
})

// Thick rounded line
h.applyGeometry({
  type: 'line',
  from: [0, 0, 0],
  to: [10, 0, 0],
  radius: 2,
  shape: 'rounded'
})

// Thick square line
h.applyGeometry({
  type: 'line',
  from: [0, 0, 0],
  to: [0, 10, 0],
  radius: 1,
  shape: 'square'
})

h.removeGeometry({
  type: 'line',
  from: [3, 0, 0],
  to: [7, 0, 0]
})

Custom Shapes

applyGeometry with type: 'fill' is the general-purpose shape primitive — define any shape as a function of (x, y, z). Boxes, spheres, and lines are just convenience wrappers around this pattern.

// Hollow sphere
h.applyGeometry({
  type: 'fill',
  bounds: [[-6, -6, -6], [6, 6, 6]],
  test: (x, y, z) => {
    const d = x*x + y*y + z*z
    return d <= 25 && d >= 16
  }
})

// Torus
h.applyGeometry({
  type: 'fill',
  bounds: [[-8, -3, -8], [8, 3, 8]],
  test: (x, y, z) => {
    const R = 6, r = 2
    const q = Math.sqrt(x*x + z*z) - R
    return q*q + y*y <= r*r
  }
})

h.removeGeometry({
  type: 'fill',
  bounds: [[0, -6, -6], [6, 6, 6]],
  test: () => true
})

Combine with functional scale and style for fully procedural shapes — closest thing to a voxel shader.

Boolean Operations

All shape methods support a mode option for CSG-like operations:

// Union (default) — add voxels
h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: 5
})

// Subtract — carve out voxels
h.applyGeometry({
  type: 'sphere',
  center: [2, 2, 2],
  radius: 2,
  mode: 'subtract'
})

// Intersect — keep only the overlap
h.applyGeometry({
  type: 'box',
  position: [1, 1, 1],
  size: 3,
  mode: 'intersect'
})

// Exclude — XOR: add where empty, remove where occupied
h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: 5,
  mode: 'exclude'
})

Styling carved faces

When removing voxels, you can pass a style to color the newly exposed faces of neighboring voxels — the "walls" of the carved hole:

h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: 10
})

// Carve a hole with dark walls
h.removeGeometry({
  type: 'box',
  position: [3, 3, 0],
  size: [4, 4, 5],
  style: { default: { fill: '#222', stroke: '#111' } }
})

This works on removeGeometry (with any type) and on applyGeometry with mode: 'subtract'. Without a style, subtract behaves as before — just deleting voxels.

Styling

Styles are set per face name: default, top, bottom, left, right, front, back. Each face style is an object with SVG presentation attributes (fill, stroke, strokeWidth, etc.).

h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: 3,
  style: {
    default: { fill: '#6699cc', stroke: '#234' },
    top:     { fill: '#88bbee' },
    front:   { fill: '#557799' },
  }
})

Dynamic styles

Style values can be functions of (x, y, z):

h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: 8,
  style: {
    default: (x, y, z) => ({
      fill: `hsl(${x * 40}, 60%, ${50 + z * 5}%)`,
      stroke: '#222',
    })
  }
})

Restyling

Restyle existing voxels without adding or removing them:

h.applyStyle({
  type: 'box',
  position: [0, 0, 0],
  size: 3,
  style: { top: { fill: 'red' } }
})
h.applyStyle({
  type: 'sphere',
  center: [5, 5, 5],
  radius: 2,
  style: { default: { fill: 'gold' } }
})
h.applyStyle({
  type: 'line',
  from: [0, 0, 0],
  to: [10, 0, 0],
  radius: 1,
  style: { default: { fill: 'blue' } }
})

Voxel Scaling

Shrink individual voxels along any axis. Scaled voxels automatically become non-opaque, revealing neighbors behind them.

// Static — same scale for every voxel
h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: 1,
  scale: [1, 0.5, 1],
  scaleOrigin: [0.5, 1, 0.5]
})

// Functional — scale varies by position
h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: 4,
  scale: (x, y, z) => [1, 1 - y * 0.2, 1],
  scaleOrigin: [0.5, 1, 0.5]
})

The scaleOrigin sets where scaling anchors within the voxel cell (0–1 per axis). [0.5, 1, 0.5] pins to the bottom-center (floor), [0.5, 0, 0.5] pins to the top-center (ceiling). Both scale and scaleOrigin accept functions of (x, y, z) for per-voxel control. Return null from a scale function to leave that voxel at full size.

Gap

Add uniform spacing between voxels. Set a global default in the constructor, or override per-geometry.

// Global gap — applies to all voxels
const h = new Heerich({ tile: 40, gap: 0.05 })

// Per-geometry override
h.addGeometry({ type: 'box', position: [0, 0, 0], size: 3, gap: 0.1 })
h.addGeometry({ type: 'box', position: [4, 0, 0], size: 3, gap: 0 }) // no gap

Gap shrinks each voxel toward its center by the specified amount on each side. A gap of 0.05 means each voxel is 10% smaller overall. Gap is applied after scale and is independent of it — a scaled voxel with a gap will shrink first, then have spacing applied.

Rotation

Rotate coordinates by 90-degree increments before or after placement:

// Rotate a shape before placing it
h.applyGeometry({
  type: 'box',
  position: [0, 0, 0],
  size: [5, 1, 3],
  rotate: { axis: 'z', turns: 1 }
})

// Rotate all existing voxels in place
h.rotate({ axis: 'y', turns: 2 })

// With explicit center
h.rotate({ axis: 'x', turns: 1, center: [5, 5, 5] })

Rendering

toSVG(options?)

Render the scene to an SVG string:

const svg = h.toSVG()
const svg = h.toSVG({ padding: 40 })
const svg = h.toSVG({ viewBox: [0, 0, 800, 600] })

Options:

| Option | Type | Description | |--------|------|-------------| | padding | number | ViewBox padding in px (default: 20) | | faces | Face[] | Pre-computed faces (skips internal rendering) | | viewBox | [x,y,w,h] | Custom viewBox override | | offset | [x,y] | Translate all geometry | | prepend | string | Raw SVG inserted before faces | | append | string | Raw SVG inserted after faces | | faceAttributes | function | Per-face attribute callback | | occlusion | boolean | Enable built-in occlusion culling (no external dependency) | | resolveOcclusion | function | Custom occlusion resolver (overrides built-in). Providing this implicitly enables occlusion. Input (subjectCoords, overlappingCoords[]), return pathString or null |

Occlusion Culling for Pen Plotters

By default, the engine relies on the browser's Paint Algorithm (back-to-front rendering). For zero overlapping vectors (perfect for plotters), enable built-in occlusion culling:

const svg = h.toSVG({ occlusion: true });

The built-in clipper assumes convex occluders, which works well for oblique projection but may produce minor artifacts with perspective (where projected quads can become non-convex). For exact clipping, drop in polygon-clipping:

<script src="https://unpkg.com/[email protected]/dist/polygon-clipping.umd.js"></script>
const svg = h.toSVG({
  resolveOcclusion: (subject, occluders) => {
    try {
      const result = polygonClipping.difference([subject], ...occluders.map(o => [o]));
      if (!result || result.length === 0) return null; // Fully occluded

      let d = "";
      for (const polygon of result) {
        for (const ring of polygon) {
          ring.forEach((pt, i) => { d += (i === 0 ? `M ${pt[0]} ${pt[1]} ` : `L ${pt[0]} ${pt[1]} `); });
          d += "Z ";
        }
      }
      return d.trim();
    } catch { return null; }
  }
});

Use prepend and append to inject SVG filters for effects like cel-shaded outlines:

const svg = h.toSVG({
  prepend: `<defs><filter id="cel">
    <feMorphology in="SourceAlpha" operator="dilate" radius="2" result="thick"/>
    <feFlood flood-color="#000"/>
    <feComposite in2="thick" operator="in" result="border"/>
    <feMerge><feMergeNode in="border"/><feMergeNode in="SourceGraphic"/></feMerge>
  </filter></defs><g filter="url(#cel)">`,
  append: `</g>`,
})

Every polygon gets data attributes for interactivity:

<... data-voxel="x,y,z"  data-x="x"  data-y="y"  data-z="z"  data-face="top" ../>

Voxels with a meta object get additional data-* attributes.

getFaces() / renderTest(opts)

Get the projected 2D face array directly (for custom renderers or Canvas output):

// From stored voxels
const faces = h.getFaces()

// Raw 3D faces (no projection or backface culling) — for GPU renderers
const raw = h.getFaces({ raw: true })

// Stateless — from a test function, no voxels stored
const faces = h.renderTest({
  bounds: [[-10, -10, -10], [10, 10, 10]],
  test: (x, y, z) => x*x + y*y + z*z <= 100,
  style: (x, y, z, faceName) => ({ fill: faceName === 'top' ? '#fff' : '#ccc' })
})

// Render pre-computed faces
const svg = h.toSVG({ faces })

Pass { raw: true } to get all neighbour-exposed 3D faces without camera-dependent culling or projection. Raw faces keep their original 3D coordinates — useful for GPU renderers that handle their own backface culling and projection.

Custom Renderers

getFaces() returns everything you need to build your own renderer. Each projected face has:

  • face.points — projected 2D coordinates (flat array via face.points.data: [x0, y0, x1, y1, ...])
  • face.style — resolved style object (fill, stroke, strokeWidth, etc.)
  • face.type — face direction ('top', 'front', 'right', etc.) or 'content'
  • face.voxel — source voxel with x, y, z, and optional meta
  • face.depth — depth value (array is already sorted back-to-front)
const faces = h.getFaces()

for (const face of faces) {
  if (face.type === 'content') continue
  const d = face.points.data
  // d = [x0, y0, x1, y1, x2, y2, x3, y3] — four corners of a quad
  ctx.beginPath()
  ctx.moveTo(d[0], d[1])
  ctx.lineTo(d[2], d[3])
  ctx.lineTo(d[4], d[5])
  ctx.lineTo(d[6], d[7])
  ctx.closePath()
  ctx.fillStyle = face.style.fill
  ctx.fill()
}

getBounds(padding?, faces?)

Compute the 2D bounding box of the rendered geometry:

const { x, y, w, h } = h.getBounds()
const padded = h.getBounds(30)

Content Voxels

Embed arbitrary SVG at a voxel position (depth-sorted with the rest of the scene):

h.applyGeometry({
  type: 'box',
  position: [3, 0, 3],
  size: 1,
  content: '<text font-size="12" text-anchor="middle">Hi</text>',
  opaque: false,
})

Content voxels receive CSS custom properties --x, --y, --z, --scale, --tile for positioning.

Decals

Stamp SVG paths onto voxel faces. Define decals as <path> elements in a 0–1 unit coordinate space, then reference them by name in any face style. The engine warps every path coordinate via bilinear interpolation onto the projected face quad — perspective-correct, no affine approximation.

// Register a decal — one or more <path> elements in 0–1 unit space
h.defineDecal('circle', {
  content: '<path d="M0.5 0 A0.5 0.5 0 1 1 0.5 1 A0.5 0.5 0 1 1 0.5 0 Z" fill="none" stroke="#333" stroke-width="1" vector-effect="non-scaling-stroke"/>'
})

// Shorthand — just the SVG string
h.defineDecal('cross', '<path d="M0 0 L1 1 M1 0 L0 1" stroke="#333" stroke-width="1" vector-effect="non-scaling-stroke" fill="none"/>')

// Reference by name in any face style
h.addGeometry({
  type: 'box', position: [0, 0, 0], size: 3,
  style: {
    top:   { fill: '#fff', decal: 'circle' },
    front: { fill: '#ccc', decal: 'cross' },
  }
})

// Per-use style overrides
h.addGeometry({
  type: 'box', position: [4, 0, 0], size: 1,
  style: {
    top: { fill: '#fff', decal: { name: 'circle', style: { opacity: 0.5 } } }
  }
})

All SVG path commands are supported, both absolute (M, L, H, V, C, S, Q, T, A, Z) and relative (m, l, h, v, c, s, q, t, a, z). Arcs are automatically converted to cubic beziers before warping. Use vector-effect="non-scaling-stroke" for uniform stroke widths across faces. Decals are included in toJSON()/fromJSON() serialization.

Limitations:

  • Only <path> elements are currently supported. Other SVG shapes (<circle>, <rect>, <line>, etc.) must be converted to <path> before use.
  • Decals are not clipped by occlusion culling. Partially occluded faces will render their decals at full size — the painter's algorithm (back-to-front rendering) hides the overflow in most cases, but it may be visible with transparent occluders.

Querying

h.getVoxel([2, 3, 1])       // voxel data or null
h.hasVoxel([2, 3, 1])       // boolean
h.getNeighbors([2, 3, 1])   // { top, bottom, left, right, front, back }
h.clear()                    // remove all voxels
h.epoch                      // mutation counter (increments on every add/remove/style change)
for (const voxel of h) { /* voxel.x, voxel.y, voxel.z, voxel.styles, ... */ }

The instance is iterable — for...of, [...h], and Array.from(h) all yield every voxel in the scene.

findVoxels(predicate)

Returns all voxels matching a predicate function. The predicate receives each voxel object and should return true to include it.

// Find by meta ID (set at add time via the meta option)
h.addGeometry({ type: 'box', position: [0, 0, 0], size: 3, meta: { id: 'tower' } })
const tower = h.findVoxels(v => v.meta?.id === 'tower')

// Find all voxels at ground level
const ground = h.findVoxels(v => v.y === 0)

// Find voxels in a coordinate range
const slab = h.findVoxels(v => v.x >= 2 && v.x <= 5 && v.z >= 2 && v.z <= 5)

// Find by style property
const redVoxels = h.findVoxels(v => v.styles?.default?.fill === '#ff0000')

Returns a plain array of voxel objects. Each voxel has x, y, z, styles, and optionally meta, scale, scaleOrigin, content, and opaque. The returned references are live — mutations to them affect the scene (call h.batch(() => {}) or trigger applyStyle to rerender after manual mutations).

getVoxelInfo(posOrVoxel)

Returns projected position and size data for a single voxel. Accepts either a [x, y, z] coordinate array or a voxel object reference (from getVoxel(), findVoxels(), or face.voxel).

const info = h.getVoxelInfo([2, 0, 3])
const info = h.getVoxelInfo(someVoxelRef)

// {
//   voxel              — the raw voxel object (null if not found)
//   center3D           — [x+0.5, y+0.5, z+0.5] in voxel space
//   center2D           — { x, y } projected centroid in pixel space
//   bounds2D           — { x, y, w, h } pixel bounding box of visible faces
//   normalizedCenter2D — { x, y } center as 0–1 fraction of scene bounds
//   normalizedSize2D   — { w, h } size as 0–1 fraction of scene bounds
// }

All 2D values are in the same coordinate space as getFaces() and getBounds() — before any SVG viewBox offset or padding. bounds2D is null when the voxel exists but all its faces are culled (e.g. fully surrounded).

// Combine with findVoxels to query a group
const voxels = h.findVoxels(v => v.meta?.id === 'tower')
const infos = voxels.map(v => h.getVoxelInfo(v))
const avgX = infos.reduce((s, i) => s + i.center2D.x, 0) / infos.length

findByPosition([x, y], options?)

Finds the frontmost voxel at a 2D screen-space position. Iterates projected faces front-to-back and returns the first hit, or null if the position is over empty space.

const hit = h.findByPosition([px, py])
// { voxel, face } | null
//   voxel — the hit voxel object
//   face  — the specific projected face that was hit (includes face.type, face.style, etc.)

Coordinates are in the same raw pixel space as getFaces(). When working from mouse events on a rendered SVG, use the SVG coordinate transform to convert client coordinates:

svgEl.addEventListener('mousemove', e => {
  const pt = svgEl.createSVGPoint()
  pt.x = e.clientX
  pt.y = e.clientY
  const { x, y } = pt.matrixTransform(svgEl.getScreenCTM().inverse())

  const hit = h.findByPosition([x, y])
  if (hit) {
    console.log(hit.voxel.x, hit.voxel.y, hit.voxel.z)
    console.log(hit.face.type) // 'top', 'front', 'left', …
  }
})

If you have mouse coordinates relative to the SVG element (not the viewport), pass the viewBox origin as an offset:

const bounds = h.getBounds(padding)
const hit = h.findByPosition([elX, elY], { offset: [bounds.x, bounds.y] })

Combine with getVoxelInfo for the full interactivity loop — hit test, then retrieve projected position:

const hit = h.findByPosition([x, y])
if (hit) {
  const { center2D, bounds2D, normalizedCenter2D } = h.getVoxelInfo(hit.voxel)
}

Serialization

const data = h.toJSON()
const json = JSON.stringify(data)

const h2 = Heerich.fromJSON(JSON.parse(json))

Note: functional styles (callbacks) cannot be serialized and will be omitted with a console warning.

Coordinate System

  • X — horizontal (left/right)
  • Y — vertical (up/down). Note: Y increases downward, originating from SVG/DOM screen space.
  • Z — depth (front/back).

Common 3D "Gotchas"

Because the engine outputs standard SVG graphics and relies on Oblique projections, its grid behaves slightly differently than classic WebGL or mathematical 3D setups:

  1. Y Pointing Down: Setting a voxel at y: -4 places it above the origin, and y: 4 places it below the origin in standard rendering.
  2. Oblique Z-Offset: At the default angle of 45° (pointing up and left visually), the Z-axis projects horizontally and vertically on screen.
  3. The "Front" Quadrant: Due to this isometric-style camera offset and Painter's Algorithm sorting, the closest visual corner pointing toward the camera is the [-X, -Y, -Z] (Negative) octant, not [+X, +Y, +Z] (Positive) as one might expect. Carving out the "front" of a block to expose the inside means subtracting negative values.

Valid voxel coordinate bounds range from -512 to 511 on each axis.

Acknowledgements

Shape calculations for lines and spheres are based on the excellent guides by Red Blob Games:

License

MIT © 2026 David Aerne