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_0002. 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 solid3. 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 countOmit 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
- Guide: Create and measure solids — primitives, transforms, measurement.
- Guide: Tessellate for display — deflection semantics and the full channel layout.
- Guide: Rendering with three.js — updates, disposal, entity streams.
- Quickstart: parametric models — the same result driven by a
ModelJSONdocument.