Skip to content
Shapemetry

Quickstart: Your First Solid

Five minutes, ending in pixels. You will create a B-Rep box, measure its volume, tessellate it into triangles, and draw it with three.js. (Install first: Installation.)

1. Create and measure

B-Rep solids in shapemetry are JSON documents (strings), not objects. Every Brep.* door takes a document and returns a fresh one — nothing to free.

import { Brep } from '@huukhanhnguyen/geometry'

const box = Brep.box(100, 100, 100)

const eps = Brep.measurementDefaultEps() // kernel default, relative 1e-6
console.log(Brep.volume(box, eps))       // 1_000_000
console.log(Brep.surfaceArea(box, eps))  // 60_000

2. Edit it

Booleans and transforms return new documents; the inputs are untouched.

const hole = Brep.translate(Brep.cylinder(25, 200), 50, 50, -50)
const part = Brep.subtract(box, hole)

console.log(Brep.volume(part, eps)) // 1_000_000 − π·25²·100
console.log(Brep.isClosed(part))    // true — still a sealed solid

3. Tessellate for display

Brep.tessellate converts the document into a TriangleMesh — flat number[] channels ready for any renderer. The deflection argument is the max sag between the true surface and its triangles: smaller means finer.

const mesh = Brep.tessellate(part, 0.5)
// mesh.positions — flat [x,y,z,…] vertices
// mesh.normals   — flat [x,y,z,…] per-vertex normals
// mesh.indices   — triangle corners, 3 per triangle
// mesh.uvs       — flat [u,v,…] texture coordinates
// mesh.edges     — display edges (smooth / boundary polylines)
console.log(mesh.indices.length / 3) // triangle count

Omit deflection and the kernel picks the face-measured default (Brep.measurementDeflection(part)).

4. Render it (three.js)

The channels drop straight into THREE.BufferGeometry — plain number[] is accepted everywhere; three copies into typed arrays for you.

import * as THREE from 'three'
import { Brep } from '@huukhanhnguyen/geometry'

const part = Brep.subtract(
  Brep.box(100, 100, 100),
  Brep.translate(Brep.cylinder(25, 200), 50, 50, -50),
)
const tess = Brep.tessellate(part, 0.5)

const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.Float32BufferAttribute(tess.positions, 3))
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(tess.normals, 3))
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(tess.uvs, 2))
geometry.setIndex(tess.indices)

const material = new THREE.MeshStandardMaterial({
  color: 0x8ab4f8, roughness: 0.7, metalness: 0.0, side: THREE.DoubleSide,
})
const mesh = new THREE.Mesh(geometry, material)

// Scene skeleton
const scene = new THREE.Scene()
scene.add(mesh)
scene.add(new THREE.DirectionalLight(0xffffff, 2).translateZ(300).translateX(150))
scene.add(new THREE.AmbientLight(0xffffff, 0.4))

const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 5000)
camera.position.set(180, -220, 160)
camera.up.set(0, 0, 1)            // shapemetry is Z-up
camera.lookAt(50, 50, 50)

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(innerWidth, innerHeight)
document.body.appendChild(renderer.domElement)
renderer.render(scene, camera)

You now have a drilled box on screen. Everything after this is the same loop: build documents with Brep.*, tessellate, hand channels to your renderer.

Where next

Last updated: 📖 1 min readEdit on GitHub