UNPKG

three-csg-ts

Version:
242 lines (241 loc) 8.97 kB
import { BufferAttribute, BufferGeometry, Matrix3, Matrix4, Mesh, Vector3, } from 'three'; import { NBuf2, NBuf3 } from './NBuf'; import { Node } from './Node'; import { Polygon } from './Polygon'; import { Vector } from './Vector'; import { Vertex } from './Vertex'; /** * Holds a binary space partition tree representing a 3D solid. Two solids can * be combined using the `union()`, `subtract()`, and `intersect()` methods. */ export class CSG { constructor() { this.polygons = []; } static fromPolygons(polygons) { const csg = new CSG(); csg.polygons = polygons; return csg; } static fromGeometry(geom, objectIndex) { let polys = []; const posattr = geom.attributes.position; const normalattr = geom.attributes.normal; const uvattr = geom.attributes.uv; const colorattr = geom.attributes.color; const grps = geom.groups; let index; if (geom.index) { index = geom.index.array; } else { index = new Array((posattr.array.length / posattr.itemSize) | 0); for (let i = 0; i < index.length; i++) index[i] = i; } const triCount = (index.length / 3) | 0; polys = new Array(triCount); for (let i = 0, pli = 0, l = index.length; i < l; i += 3, pli++) { const vertices = new Array(3); for (let j = 0; j < 3; j++) { const vi = index[i + j]; const vp = vi * 3; const vt = vi * 2; const x = posattr.array[vp]; const y = posattr.array[vp + 1]; const z = posattr.array[vp + 2]; const nx = normalattr.array[vp]; const ny = normalattr.array[vp + 1]; const nz = normalattr.array[vp + 2]; const u = uvattr === null || uvattr === void 0 ? void 0 : uvattr.array[vt]; const v = uvattr === null || uvattr === void 0 ? void 0 : uvattr.array[vt + 1]; vertices[j] = new Vertex(new Vector(x, y, z), new Vector(nx, ny, nz), new Vector(u, v, 0), colorattr && new Vector(colorattr.array[vp], colorattr.array[vp + 1], colorattr.array[vp + 2])); } if (objectIndex === undefined && grps && grps.length > 0) { for (const grp of grps) { if (i >= grp.start && i < grp.start + grp.count) { polys[pli] = new Polygon(vertices, grp.materialIndex); } } } else { polys[pli] = new Polygon(vertices, objectIndex); } } return CSG.fromPolygons(polys.filter((p) => !Number.isNaN(p.plane.normal.x))); } static toGeometry(csg, toMatrix) { let triCount = 0; const ps = csg.polygons; for (const p of ps) { triCount += p.vertices.length - 2; } const geom = new BufferGeometry(); const vertices = new NBuf3(triCount * 3 * 3); const normals = new NBuf3(triCount * 3 * 3); const uvs = new NBuf2(triCount * 2 * 3); let colors; const grps = []; const dgrp = []; for (const p of ps) { const pvs = p.vertices; const pvlen = pvs.length; if (p.shared !== undefined) { if (!grps[p.shared]) grps[p.shared] = []; } if (pvlen && pvs[0].color !== undefined) { if (!colors) colors = new NBuf3(triCount * 3 * 3); } for (let j = 3; j <= pvlen; j++) { const grp = p.shared === undefined ? dgrp : grps[p.shared]; grp.push(vertices.top / 3, vertices.top / 3 + 1, vertices.top / 3 + 2); vertices.write(pvs[0].pos); vertices.write(pvs[j - 2].pos); vertices.write(pvs[j - 1].pos); normals.write(pvs[0].normal); normals.write(pvs[j - 2].normal); normals.write(pvs[j - 1].normal); if (uvs) { uvs.write(pvs[0].uv); uvs.write(pvs[j - 2].uv); uvs.write(pvs[j - 1].uv); } if (colors) { colors.write(pvs[0].color); colors.write(pvs[j - 2].color); colors.write(pvs[j - 1].color); } } } geom.setAttribute('position', new BufferAttribute(vertices.array, 3)); geom.setAttribute('normal', new BufferAttribute(normals.array, 3)); uvs && geom.setAttribute('uv', new BufferAttribute(uvs.array, 2)); colors && geom.setAttribute('color', new BufferAttribute(colors.array, 3)); for (let gi = 0; gi < grps.length; gi++) { if (grps[gi] === undefined) { grps[gi] = []; } } if (grps.length) { let index = []; let gbase = 0; for (let gi = 0; gi < grps.length; gi++) { geom.addGroup(gbase, grps[gi].length, gi); gbase += grps[gi].length; index = index.concat(grps[gi]); } geom.addGroup(gbase, dgrp.length, grps.length); index = index.concat(dgrp); geom.setIndex(index); } const inv = new Matrix4().copy(toMatrix).invert(); geom.applyMatrix4(inv); geom.computeBoundingSphere(); geom.computeBoundingBox(); return geom; } static fromMesh(mesh, objectIndex) { const csg = CSG.fromGeometry(mesh.geometry, objectIndex); const ttvv0 = new Vector3(); const tmpm3 = new Matrix3(); tmpm3.getNormalMatrix(mesh.matrix); for (let i = 0; i < csg.polygons.length; i++) { const p = csg.polygons[i]; for (let j = 0; j < p.vertices.length; j++) { const v = p.vertices[j]; v.pos.copy(ttvv0.copy(v.pos.toVector3()).applyMatrix4(mesh.matrix)); v.normal.copy(ttvv0.copy(v.normal.toVector3()).applyMatrix3(tmpm3)); } } return csg; } static toMesh(csg, toMatrix, toMaterial) { const geom = CSG.toGeometry(csg, toMatrix); const m = new Mesh(geom, toMaterial); m.matrix.copy(toMatrix); m.matrix.decompose(m.position, m.quaternion, m.scale); m.rotation.setFromQuaternion(m.quaternion); m.updateMatrixWorld(); m.castShadow = m.receiveShadow = true; return m; } static union(meshA, meshB) { const csgA = CSG.fromMesh(meshA); const csgB = CSG.fromMesh(meshB); return CSG.toMesh(csgA.union(csgB), meshA.matrix, meshA.material); } static subtract(meshA, meshB) { const csgA = CSG.fromMesh(meshA); const csgB = CSG.fromMesh(meshB); return CSG.toMesh(csgA.subtract(csgB), meshA.matrix, meshA.material); } static intersect(meshA, meshB) { const csgA = CSG.fromMesh(meshA); const csgB = CSG.fromMesh(meshB); return CSG.toMesh(csgA.intersect(csgB), meshA.matrix, meshA.material); } clone() { const csg = new CSG(); csg.polygons = this.polygons .map((p) => p.clone()) .filter((p) => Number.isFinite(p.plane.w)); return csg; } toPolygons() { return this.polygons; } union(csg) { const a = new Node(this.clone().polygons); const b = new Node(csg.clone().polygons); a.clipTo(b); b.clipTo(a); b.invert(); b.clipTo(a); b.invert(); a.build(b.allPolygons()); return CSG.fromPolygons(a.allPolygons()); } subtract(csg) { const a = new Node(this.clone().polygons); const b = new Node(csg.clone().polygons); a.invert(); a.clipTo(b); b.clipTo(a); b.invert(); b.clipTo(a); b.invert(); a.build(b.allPolygons()); a.invert(); return CSG.fromPolygons(a.allPolygons()); } intersect(csg) { const a = new Node(this.clone().polygons); const b = new Node(csg.clone().polygons); a.invert(); b.clipTo(a); b.invert(); a.clipTo(b); b.clipTo(a); a.build(b.allPolygons()); a.invert(); return CSG.fromPolygons(a.allPolygons()); } // Return a new CSG solid with solid and empty space switched. This solid is // not modified. inverse() { const csg = this.clone(); for (const p of csg.polygons) { p.flip(); } return csg; } toMesh(toMatrix, toMaterial) { return CSG.toMesh(this, toMatrix, toMaterial); } toGeometry(toMatrix) { return CSG.toGeometry(this, toMatrix); } }