UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

375 lines 15.6 kB
import { Mesh } from "./mesh.js"; import { VertexData } from "./mesh.vertexData.js"; import { VertexBuffer } from "../Buffers/buffer.js"; import { Logger } from "../Misc/logger.js"; import { MultiMaterial } from "../Materials/multiMaterial.js"; import { SubMesh } from "./subMesh.js"; import { _LoadScriptModuleAsync } from "../Misc/tools.internals.js"; import { Vector3 } from "../Maths/math.vector.js"; /** * Main manifold library */ // eslint-disable-next-line @typescript-eslint/naming-convention let Manifold; /** * Promise to wait for the manifold library to be ready */ // eslint-disable-next-line @typescript-eslint/naming-convention let ManifoldPromise; /** * Manifold mesh */ // eslint-disable-next-line @typescript-eslint/naming-convention let ManifoldMesh; /** * First ID to use for materials indexing */ // eslint-disable-next-line @typescript-eslint/naming-convention let FirstID; /** * Wrapper around the Manifold library * https://manifoldcad.org/ * Use this class to perform fast boolean operations on meshes * @see [basic operations](https://playground.babylonjs.com/#IW43EB#15) * @see [skull vs box](https://playground.babylonjs.com/#JUKXQD#6218) * @see [skull vs vertex data](https://playground.babylonjs.com/#JUKXQD#6219) */ export class CSG2 { /** * Return the size of a vertex (at least 3 for the position) */ get numProp() { return this._numProp; } constructor(manifold, numProp, vertexStructure) { this._manifold = manifold; this._numProp = numProp; this._vertexStructure = vertexStructure; } _process(operation, csg) { if (this.numProp !== csg.numProp) { throw new Error("CSG must be used with geometries having the same number of properties"); } return new CSG2(Manifold[operation](this._manifold, csg._manifold), this.numProp, this._vertexStructure); } /** * Run a difference operation between two CSG * @param csg defines the CSG to use to create the difference * @returns a new csg */ subtract(csg) { return this._process("difference", csg); } /** * Run an intersection operation between two CSG * @param csg defines the CSG to use to create the intersection * @returns a new csg */ intersect(csg) { return this._process("intersection", csg); } /** * Run an union operation between two CSG * @param csg defines the CSG to use to create the union * @returns a new csg */ add(csg) { return this._process("union", csg); } /** * Print debug information about the CSG */ printDebug() { Logger.Log("Genus:" + this._manifold.genus()); const properties = this._manifold.getProperties(); Logger.Log("Volume:" + properties.volume); Logger.Log("surface area:" + properties.surfaceArea); } /** * Generate a vertex data from the CSG * @param options defines the options to use to rebuild the vertex data * @returns a new vertex data */ toVertexData(options) { const localOptions = { rebuildNormals: false, ...options, }; const vertexData = new VertexData(); const normalComponent = this._vertexStructure.find((c) => c.kind === VertexBuffer.NormalKind); const manifoldMesh = this._manifold.getMesh(localOptions.rebuildNormals && normalComponent ? [3, 4, 5] : undefined); vertexData.indices = manifoldMesh.triVerts.length > 65535 ? new Uint32Array(manifoldMesh.triVerts) : new Uint16Array(manifoldMesh.triVerts); for (let i = 0; i < manifoldMesh.triVerts.length; i += 3) { vertexData.indices[i] = manifoldMesh.triVerts[i + 2]; vertexData.indices[i + 1] = manifoldMesh.triVerts[i + 1]; vertexData.indices[i + 2] = manifoldMesh.triVerts[i]; } const vertexCount = manifoldMesh.vertProperties.length / manifoldMesh.numProp; // Attributes let offset = 0; for (let componentIndex = 0; componentIndex < this._vertexStructure.length; componentIndex++) { const component = this._vertexStructure[componentIndex]; const data = new Float32Array(vertexCount * component.stride); for (let i = 0; i < vertexCount; i++) { for (let strideIndex = 0; strideIndex < component.stride; strideIndex++) { data[i * component.stride + strideIndex] = manifoldMesh.vertProperties[i * manifoldMesh.numProp + offset + strideIndex]; } } vertexData.set(data, component.kind); offset += component.stride; } // Rebuild mesh from vertex data return vertexData; } /** * Generate a mesh from the CSG * @param name defines the name of the mesh * @param scene defines the scene to use to create the mesh * @param options defines the options to use to rebuild the mesh * @returns a new Mesh */ toMesh(name, scene, options) { const localOptions = { rebuildNormals: false, centerMesh: true, ...options, }; const vertexData = this.toVertexData({ rebuildNormals: localOptions.rebuildNormals }); const normalComponent = this._vertexStructure.find((c) => c.kind === VertexBuffer.NormalKind); const manifoldMesh = this._manifold.getMesh(localOptions.rebuildNormals && normalComponent ? [3, 4, 5] : undefined); const vertexCount = manifoldMesh.vertProperties.length / manifoldMesh.numProp; // Rebuild mesh from vertex data const output = new Mesh(name, scene); vertexData.applyToMesh(output); // Center mesh if (localOptions.centerMesh) { const extents = output.getBoundingInfo().boundingSphere.center; output.position.set(-extents.x, -extents.y, -extents.z); output.bakeCurrentTransformIntoVertices(); } // Submeshes let id = manifoldMesh.runOriginalID[0]; let start = manifoldMesh.runIndex[0]; let materialIndex = 0; const materials = []; scene = output.getScene(); for (let run = 0; run < manifoldMesh.numRun; ++run) { const nextID = manifoldMesh.runOriginalID[run + 1]; if (nextID !== id) { const end = manifoldMesh.runIndex[run + 1]; new SubMesh(materialIndex, 0, vertexCount, start, end - start, output); materials.push(scene.getMaterialByUniqueID(id - FirstID) || scene.defaultMaterial); id = nextID; start = end; materialIndex++; } } if (localOptions.materialToUse) { output.material = localOptions.materialToUse; } else { if (materials.length > 1) { const multiMaterial = new MultiMaterial(name, scene); multiMaterial.subMaterials = materials; output.material = multiMaterial; } else { if (output.subMeshes.length > 1) { // Remove the submeshes as they are not needed output._createGlobalSubMesh(true); } output.material = materials[0]; } } return output; } /** * Dispose the CSG resources */ dispose() { if (this._manifold) { this._manifold.delete(); this._manifold = null; } } static _ProcessData(vertexCount, triVerts, structure, numProp, runIndex, runOriginalID) { const vertProperties = new Float32Array(vertexCount * structure.reduce((acc, cur) => acc + cur.stride, 0)); for (let i = 0; i < vertexCount; i++) { let offset = 0; for (let idx = 0; idx < structure.length; idx++) { const component = structure[idx]; for (let strideIndex = 0; strideIndex < component.stride; strideIndex++) { vertProperties[i * numProp + offset + strideIndex] = component.data[i * component.stride + strideIndex]; } offset += component.stride; } } // eslint-disable-next-line @typescript-eslint/naming-convention const manifoldMesh = new ManifoldMesh({ numProp: numProp, vertProperties, triVerts, runIndex, runOriginalID }); manifoldMesh.merge(); let returnValue; try { returnValue = new CSG2(new Manifold(manifoldMesh), numProp, structure); } catch (e) { throw new Error("Error while creating the CSG: " + e.message); } return returnValue; } static _Construct(data, worldMatrix, runIndex, runOriginalID) { // Create the MeshGL for I/O with Manifold library. const triVerts = new Uint32Array(data.indices.length); // Revert order for (let i = 0; i < data.indices.length; i += 3) { triVerts[i] = data.indices[i + 2]; triVerts[i + 1] = data.indices[i + 1]; triVerts[i + 2] = data.indices[i]; } const tempVector3 = new Vector3(); let numProp = 3; const structure = [{ stride: 3, kind: VertexBuffer.PositionKind }]; if (!worldMatrix) { structure[0].data = data.positions; } else { const positions = new Float32Array(data.positions.length); for (let i = 0; i < data.positions.length; i += 3) { Vector3.TransformCoordinatesFromFloatsToRef(data.positions[i], data.positions[i + 1], data.positions[i + 2], worldMatrix, tempVector3); tempVector3.toArray(positions, i); } structure[0].data = positions; } // Normals const sourceNormals = data.normals; if (sourceNormals) { numProp += 3; structure.push({ stride: 3, kind: VertexBuffer.NormalKind }); if (!worldMatrix) { structure[1].data = sourceNormals; } else { const normals = new Float32Array(sourceNormals.length); for (let i = 0; i < sourceNormals.length; i += 3) { Vector3.TransformNormalFromFloatsToRef(sourceNormals[i], sourceNormals[i + 1], sourceNormals[i + 2], worldMatrix, tempVector3); tempVector3.toArray(normals, i); } structure[1].data = normals; } } // UVs for (const kind of [VertexBuffer.UVKind, VertexBuffer.UV2Kind, VertexBuffer.UV3Kind, VertexBuffer.UV4Kind, VertexBuffer.UV5Kind, VertexBuffer.UV6Kind]) { const sourceUV = data[kind === VertexBuffer.UVKind ? "uvs" : kind]; if (sourceUV) { numProp += 2; structure.push({ stride: 2, kind: kind, data: sourceUV }); } } // Colors const sourceColors = data.colors; if (sourceColors) { numProp += 4; structure.push({ stride: 4, kind: VertexBuffer.ColorKind, data: sourceColors }); } return this._ProcessData(data.positions.length / 3, triVerts, structure, numProp, runIndex, runOriginalID); } /** * Create a new Constructive Solid Geometry from a vertexData * @param vertexData defines the vertexData to use to create the CSG * @returns a new CSG2 class */ static FromVertexData(vertexData) { const sourceVertices = vertexData.positions; const sourceIndices = vertexData.indices; if (!sourceVertices || !sourceIndices) { throw new Error("The vertexData must at least have positions and indices"); } return this._Construct(vertexData, null); } /** * Create a new Constructive Solid Geometry from a mesh * @param mesh defines the mesh to use to create the CSG * @param ignoreWorldMatrix defines if the world matrix should be ignored * @returns a new CSG2 class */ static FromMesh(mesh, ignoreWorldMatrix = false) { const sourceVertices = mesh.getVerticesData(VertexBuffer.PositionKind); const sourceIndices = mesh.getIndices(); const worldMatrix = mesh.computeWorldMatrix(true); if (!sourceVertices || !sourceIndices) { throw new Error("The mesh must at least have positions and indices"); } // Create a triangle run for each submesh (material) const starts = [...Array(mesh.subMeshes.length)].map((_, idx) => mesh.subMeshes[idx].indexStart); // Map the materials to ID. const sourceMaterial = mesh.material || mesh.getScene().defaultMaterial; const isMultiMaterial = sourceMaterial.getClassName() === "MultiMaterial"; const originalIDs = [...Array(mesh.subMeshes.length)].map((_, idx) => { if (isMultiMaterial) { return FirstID + sourceMaterial.subMaterials[mesh.subMeshes[idx].materialIndex].uniqueId; } return FirstID + sourceMaterial.uniqueId; }); // List the runs in sequence. const indices = Array.from(starts.keys()); indices.sort((a, b) => starts[a] - starts[b]); const runIndex = new Uint32Array(indices.map((i) => starts[i])); const runOriginalID = new Uint32Array(indices.map((i) => originalIDs[i])); // Process const data = { positions: sourceVertices, indices: sourceIndices, normals: mesh.getVerticesData(VertexBuffer.NormalKind), colors: mesh.getVerticesData(VertexBuffer.ColorKind), uvs: mesh.getVerticesData(VertexBuffer.UVKind), uvs2: mesh.getVerticesData(VertexBuffer.UV2Kind), uvs3: mesh.getVerticesData(VertexBuffer.UV3Kind), uvs4: mesh.getVerticesData(VertexBuffer.UV4Kind), uvs5: mesh.getVerticesData(VertexBuffer.UV5Kind), uvs6: mesh.getVerticesData(VertexBuffer.UV6Kind), }; return this._Construct(data, ignoreWorldMatrix ? null : worldMatrix, runIndex, runOriginalID); } } /** * Checks if the Manifold library is ready * @returns true if the Manifold library is ready */ export function IsCSG2Ready() { return Manifold !== undefined; } /** * Initialize the Manifold library * @param options defines the options to use to initialize the library */ export async function InitializeCSG2Async(options) { const localOptions = { manifoldUrl: "https://unpkg.com/manifold-3d@3.2.1", ...options, }; if (Manifold) { return; // Already initialized } if (ManifoldPromise) { await ManifoldPromise; return; } if (localOptions.manifoldInstance) { Manifold = localOptions.manifoldInstance; ManifoldMesh = localOptions.manifoldMeshInstance; } else { ManifoldPromise = _LoadScriptModuleAsync(` import Module from '${localOptions.manifoldUrl}/manifold.js'; const wasm = await Module(); wasm.setup(); const {Manifold, Mesh} = wasm; const returnedValue = {Manifold, Mesh}; `); const result = await ManifoldPromise; Manifold = result.Manifold; ManifoldMesh = result.Mesh; } // Reserve IDs for materials (we consider that there will be no more than 65536 materials) FirstID = Manifold.reserveIDs(65536); } //# sourceMappingURL=csg2.js.map