Generate meshes from signed distance functions and constructive solid geometry operations. This library is heavily based upon Inigo Quilez's 3D SDFs article.
import { Box, Sphere, CappedCylinder } from "sdf-csg";
const sdf = new Sphere(1)
.setUserData([0, 0, 1])
.smoothIntersect(new Box(0.75, 0.75, 0.75).setUserData([1, 0, 0]), 0.1)
.smoothSubtract(
new CappedCylinder(1, 0.5)
.smoothUnion(new CappedCylinder(1, 0.5).rotateX(Math.PI / 2), 0.1)
.smoothUnion(new CappedCylinder(1, 0.5).rotateZ(Math.PI / 2), 0.1)
.setUserData([0, 1, 0]),
0.1
);
const mesh = sdf.generateMesh([64, 64, 64], 0.2);
Note: this project is very much a work in progress. There may be significant changes to the API until the 1.0.0 semantic version is released.
All of these return a new SDF.
const sdf = new Box(width: number, height: number, depth: number)
const sdf = new Sphere(radius: number)
const sdf = new BoxFrame(width: number, height: number, depth: number, edge: number)
const sdf = new Torus(majorRadius: number, minorRadius: number)
const sdf = new CappedTorus(majorRadius: number, minorRadius: number, angle: number)
const sdf = new Link(majorRadius: number, minorRadius: number, length: number)
const sdf = new Cone(angle: number, height: number)
const sdf = new HexagonalPrism(radius: number, length: number)
const sdf = new Capsule(pointA: number[], pointB: number[], radius: number)
const sdf = new CappedCylinder(length: number, radius: number)
const sdf = new CappedCone(length: number, radius1: number, radius2: number)
const sdf = new SolidAngle(angle: number, radius: number)
All of these return a new SDF.
sdf.union(sdf: SDF)
sdf.subtract(sdf: SDF)
sdf.intersect(sdf: SDF)
sdf.smoothUnion(sdf: SDF, smoothness: number)
sdf.smoothSubtract(sdf: SDF, smoothness: number)
sdf.smoothIntersect(sdf: SDF, smoothness: number)
sdf.translate(x: number, y: number, z: number)
sdf.rotate(quat: number[])
sdf.rotateX(radians: number)
sdf.rotateY(radians: number)
sdf.rotateZ(radians: number)
sdf.scale(amount: number)
sdf.round(amount: number)
const sdf = new Sphere(1)
.translate(-0.5, 0, 0)
.smoothUnion(new Sphere(1).translate(0.5, 0, 0), 0.1)
.setUserData([1, 0.5, 1]);
const sdf = new Sphere(1)
.translate(-0.5, 0, 0)
.smoothSubtract(new Sphere(1).translate(0.5, 0, 0), 0.2)
.setUserData([1, 0.5, 1]);
const sdf = new Sphere(1)
.translate(-0.5, 0, 0)
.smoothIntersect(new Sphere(1).translate(0.5, 0, 0), 0.2)
.setUserData([1, 0.5, 1]);
const sdf = new HexagonalPrism(0.25, 1)
.translate(-0.75, 0, 0)
.union(new HexagonalPrism(0.25, 1).translate(0.75, 0, 0).round(0.1))
.setUserData([1, 0.5, 1]);
You can assign "user data" - an array of numbers - to components of your SDF. These are interpolated across the SDF and returned by the generateMesh
function. Here's an example using them to interpolate color across the surface of the mesh, but you could imagine using them for other purposes, such as values for use with physically based rendering, etc.
const sdf = new Sphere(1)
.setUserData([1, 0.5, 0.5])
.translate(-0.75, 0, 0)
.smoothUnion(new Sphere(1).setUserData([0.5, 0.5, 1]).translate(0.75, 0, 0), 0.5);
const mesh = sdf.generateMesh(resolution: [number, number, number], padding: number);
Returns a Mesh
:
interface Mesh {
positions: number[][];
normals: number[][];
cells: number[][];
userdata: number[][] | null;
}
- The current isosurface extractor generates poor results for edges and corners. Write or use a different one for better results.
- Isosurface extraction is slow. Speed it up (perhaps the SDF data could be used to skip large chunks of the grid?).
- The bounds of the SDF are determined analytically, and it's a little buggy (e.g., when using the "smooth" operators), requiring the use of padding when generating the mesh. Some options:
- Fix the bugs.
- Determine the bounds numerically, using the value of the SDF to accelerate it.
- Add more primitives.
- Add more operators.
This library is heavily based upon Inigo Quilez's 3D SDFs article.