Constructive Solid Geometry (CSG) library for use with three.js
The OctreeCSG library is using the Octree data structure to store the geometry data for the CSG operations
All the code examples below can be tested live in 3dd.dev
- Usage
- Basic Operations
- Advanced Operations
- Array Operations
- Asynchronous Operations
- OctreeCSG Flags
- Examples
- Resources
OctreeCSG comes as a Javascript Module and can be imported with the following command:
import OctreeCSG from './OctreeCSG/OctreeCSG.js';
OctreeCSG provides basic boolean operations (union, subtract and intersect) for ease of use. The basic operations expects the same type of parameters:
Parameter | Description |
---|---|
mesh1 | First mesh |
mesh2 | Second mesh |
targetMaterial | (Optional) The material to use for the final mesh, can be a single material or an array of two materials. Default: A clone of the material of the first mesh |
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const material2 = new THREE.MeshStandardMaterial({ color: 0x0000ff });
const mesh1 = new THREE.Mesh(geometry, material1);
const mesh2 = new THREE.Mesh(geometry.clone(), material2);
mesh2.position.set(5, -5, 5);
const resultMesh = OctreeCSG.meshUnion(mesh1, mesh2);
scene.add(resultMesh);
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const material2 = new THREE.MeshStandardMaterial({ color: 0x0000ff });
const mesh1 = new THREE.Mesh(geometry, material1);
const mesh2 = new THREE.Mesh(geometry.clone(), material2);
mesh2.position.set(5, -5, 5);
const resultMesh = OctreeCSG.meshSubtract(mesh1, mesh2);
scene.add(resultMesh);
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const material2 = new THREE.MeshStandardMaterial({ color: 0x0000ff });
const mesh1 = new THREE.Mesh(geometry, material1);
const mesh2 = new THREE.Mesh(geometry.clone(), material2);
mesh2.position.set(5, -5, 5);
const resultMesh = OctreeCSG.meshIntersect(mesh1, mesh2);
scene.add(resultMesh);
Converts a three.js mesh to an Octree
Parameter | Description |
---|---|
obj | three.js mesh |
objectIndex | (Optional) Used for specifying the geometry group index in the result mesh. Default: Input mesh's groups if there are any |
octree | (Optional) Target octree to use. Default: new Octree |
buildTargetOctree | (Optional) Specifies if to build the target Octree tree or return a flat Octree (true / flase). Default: true |
Converts an Octree to a three.js mesh
Parameter | Description |
---|---|
octree | Octree object |
material | Material object or an array of materials to use for the new three.js mesh |
Merges two Octrees (octreeA and octreeB) to one Octree
Parameter | Description |
---|---|
octreeA | First octree object |
octreeB | Second octree object |
buildTargetOctree | (Optional) Specifies if to build the target Octree tree or return a flat Octree (true / flase). Default: true |
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const material2 = new THREE.MeshStandardMaterial({ color: 0x0000ff });
const mesh1 = new THREE.Mesh(geometry, material1);
const mesh2 = new THREE.Mesh(geometry.clone(), material2);
mesh2.position.set(5, -5, 5);
const octreeA = OctreeCSG.fromMesh(mesh1);
const octreeB = OctreeCSG.fromMesh(mesh2);
const resultOctree = OctreeCSG.union(octreeA, octreeB);
const resultMesh = OctreeCSG.toMesh(resultOctree, mesh1.material.clone());
scene.add(resultMesh);
Subtracts octreeB from octreeA and returns the result Octree
Parameter | Description |
---|---|
octreeA | First octree object |
octreeB | Second octree object |
buildTargetOctree | (Optional) Specifies if to build the target Octree tree or return a flat Octree (true / flase). Default: true |
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const material2 = new THREE.MeshStandardMaterial({ color: 0x0000ff });
const mesh1 = new THREE.Mesh(geometry, material1);
const mesh2 = new THREE.Mesh(geometry.clone(), material2);
mesh2.position.set(5, -5, 5);
const octreeA = OctreeCSG.fromMesh(mesh1);
const octreeB = OctreeCSG.fromMesh(mesh2);
const resultOctree = OctreeCSG.subtract(octreeA, octreeB);
const resultMesh = OctreeCSG.toMesh(resultOctree, mesh1.material.clone());
scene.add(resultMesh);
Returns the intersection of octreeA and octreeB
Parameter | Description |
---|---|
octreeA | First octree object |
octreeB | Second octree object |
buildTargetOctree | (Optional) Specifies if to build the target Octree tree or return a flat Octree (true / flase). Default: true |
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material1 = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const material2 = new THREE.MeshStandardMaterial({ color: 0x0000ff });
const mesh1 = new THREE.Mesh(geometry, material1);
const mesh2 = new THREE.Mesh(geometry.clone(), material2);
mesh2.position.set(5, -5, 5);
const octreeA = OctreeCSG.fromMesh(mesh1);
const octreeB = OctreeCSG.fromMesh(mesh2);
const resultOctree = OctreeCSG.intersect(octreeA, octreeB);
const resultMesh = OctreeCSG.toMesh(resultOctree, mesh1.material.clone());
scene.add(resultMesh);
CSG Hierarchy of Operations (syntax may change), provides a simple method to combine several CSG operations into one
Parameter | Description |
---|---|
obj | Input object with the CSG hierarchy |
returnOctrees | (Optional) Specifies whether to return the Octrees as part of the result or not (true / false). Default: false |
Input object structure:
Key | Expected Value |
---|---|
op | Type of operation to perform as string, options: union, subtract and intersect |
material | (Optional) Used only in the root level of the object, if a material is provided the returned object will be a three.js mesh instead of an Octree. Value can be a single material or an array of materials |
objA | First object, can be a three.js mesh, Octree or a sub-structure of the CSG operation |
objB | Second object, can be a three.js mesh, Octree or a sub-structure of the CSG operation |
let baseMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });
let cubeGeometry = new THREE.BoxGeometry(10, 10, 10);
let sphereGeometry = new THREE.SphereGeometry(6.5, 64, 32);
let baseCylinderGeometry = new THREE.CylinderGeometry(3, 3, 20, 64);
let cubeMesh = new THREE.Mesh(cubeGeometry, baseMaterial.clone());
let sphereMesh = new THREE.Mesh(sphereGeometry, baseMaterial.clone());
let cylinderMesh1 = new THREE.Mesh(baseCylinderGeometry.clone(), baseMaterial.clone());
let cylinderMesh2 = new THREE.Mesh(baseCylinderGeometry.clone(), baseMaterial.clone());
let cylinderMesh3 = new THREE.Mesh(baseCylinderGeometry.clone(), baseMaterial.clone());
cubeMesh.material.color.set(0xff0000);
sphereMesh.material.color.set(0x0000ff);
cylinderMesh1.material.color.set(0x00ff00);
cylinderMesh2.material.color.set(0x00ff00);
cylinderMesh3.material.color.set(0x00ff00);
cylinderMesh2.rotation.set(0, 0, THREE.MathUtils.degToRad(90));
cylinderMesh3.rotation.set(THREE.MathUtils.degToRad(90), 0, 0);
let result = OctreeCSG.operation({
op: "subtract",
material: [cubeMesh.material, sphereMesh.material, cylinderMesh1.material, cylinderMesh2.material, cylinderMesh3.material],
objA: {
op: "intersect",
objA: cubeMesh,
objB: sphereMesh
},
objB: {
op: "union",
objA: {
op: "union",
objA: cylinderMesh1,
objB: cylinderMesh2,
},
objB: cylinderMesh3
}
});
scene.add(result);
OctreeCSG provides 3 methods to perform CSG operations on an array of meshes / octrees
Parameter | Description |
---|---|
objArr | An array of meshes or octrees to perform the CSG operation on |
materialIndexMax | (Optional) Can be used to specify the maximum number of groups in the result Octree. Default: Infinity |
List of Methods:
- OctreeCSG.unionArray - Union operation on an array of meshes
- OctreeCSG.subtractArray - Subtract operation on an array of meshes
- OctreeCSG.intersectArray - Intersect operation on an array of meshes
OctreeCSG provides asynchronous CSG methods for all the advanced CSG operations.
List of Methods:
- OctreeCSG.async.union
- OctreeCSG.async.subtract
- OctreeCSG.async.intersect
- OctreeCSG.async.operation
- OctreeCSG.async.unionArray
- OctreeCSG.async.subtractArray
- OctreeCSG.async.intersectArray
The following flags and variables control how OctreeCSG operates.
Flag / Variable | Default Value | Description |
---|---|---|
OctreeCSG.useOctreeRay | true | Determines if to use OctreeCSG's ray intersection logic or use three.js's intersection logic (Raycaster.intersectObject). Options: true, false |
OctreeCSG.rayIntersectTriangleType | MollerTrumbore | Determines which ray-triangle intersection algorithm to use. three.js's ray-triangle intersection algorithm proved to be not accurate enough for CSG operations during testing so the Möller–Trumbore ray-triangle intersection algorithm was implemented. Options: MollerTrumbore, regular (uses three.js's Ray.intersectTriangle) |
OctreeCSG.useWindingNumber | false | Determines if to use the ray-triangle intersection algorithm or the Winding number algorithm. The Winding number alogirthm can be more accurate than the ray-triangle algorithm on some occasions at the cost of performance. Options: true, false |
OctreeCSG.maxLevel | 16 | Maximum number of sub-Octree levels in the tree |
OctreeCSG.polygonsPerTree | 100 | Minimum number of polygons (triangles) in a sub-Octree before a split is needed |
- CSG Operations on basic geometries
- Real-Time CSG - Demonstrating the use of async CSG operations (OctreeCSG.async.operation & OctreeCSG.async.unionArray) in real-time CSG
More examples coming soon.
- The Polygon, Vertex and Plane classes were adapted from THREE-CSGMesh
- The Winding number algorithm is based on this code
- The Möller–Trumbore ray-triangle intersection algorithm is based on this code
- The triangle-triangle intersection logic and algorithm is based on this code