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.

297 lines 13.2 kB
import { __decorate } from "../../../tslib.es6.js"; import { NodeGeometryBlock } from "../nodeGeometryBlock.js"; import { RegisterClass } from "../../../Misc/typeStore.js"; import { NodeGeometryBlockConnectionPointTypes } from "../Enums/nodeGeometryConnectionPointTypes.js"; import { editableInPropertyPage } from "../../../Decorators/nodeDecorator.js"; import { Vector3 } from "../../../Maths/math.vector.js"; import { VertexData } from "../../mesh.vertexData.js"; /** * Cap mode for the extrusion */ export var ExtrudeGeometryCap; (function (ExtrudeGeometryCap) { /** No caps — only the extruded side walls are generated */ ExtrudeGeometryCap[ExtrudeGeometryCap["NoCap"] = 0] = "NoCap"; /** Cap the bottom face (the original input geometry face) */ ExtrudeGeometryCap[ExtrudeGeometryCap["CapStart"] = 1] = "CapStart"; /** Cap the top face (the offset/extruded geometry face) */ ExtrudeGeometryCap[ExtrudeGeometryCap["CapEnd"] = 2] = "CapEnd"; /** Cap both the bottom and top faces (default). Creates a solid */ ExtrudeGeometryCap[ExtrudeGeometryCap["CapAll"] = 3] = "CapAll"; })(ExtrudeGeometryCap || (ExtrudeGeometryCap = {})); /** * Block used to extrude a geometry along its face normals */ export class ExtrudeGeometryBlock extends NodeGeometryBlock { /** * Create a new ExtrudeGeometryBlock * @param name defines the block name */ constructor(name) { super(name); /** * Gets or sets a boolean indicating that this block can evaluate context * Build performance is improved when this value is set to false as the system will cache values instead of reevaluating everything per context change */ this.evaluateContext = false; /** * Gets or sets the cap mode for the extrusion */ this.cap = ExtrudeGeometryCap.CapAll; this.registerInput("geometry", NodeGeometryBlockConnectionPointTypes.Geometry); this.registerInput("depth", NodeGeometryBlockConnectionPointTypes.Float, true, 1.0); this.registerOutput("output", NodeGeometryBlockConnectionPointTypes.Geometry); } /** * Gets the current class name * @returns the class name */ getClassName() { return "ExtrudeGeometryBlock"; } /** * Gets the geometry input component */ get geometry() { return this._inputs[0]; } /** * Gets the depth input component */ get depth() { return this._inputs[1]; } /** * Gets the geometry output component */ get output() { return this._outputs[0]; } _buildBlock(state) { const func = (state) => { const inputGeometry = this.geometry.getConnectedValue(state); if (!inputGeometry || !inputGeometry.positions || !inputGeometry.indices) { return null; } const vertexData = inputGeometry.clone(); const positions = vertexData.positions; const indices = vertexData.indices; const depthValue = this.depth.getConnectedValue(state) ?? 1.0; if (depthValue === 0) { return vertexData; } // Step 2: Compute average face normal const normal = new Vector3(0, 0, 0); const v0 = new Vector3(); const v1 = new Vector3(); const v2 = new Vector3(); const edge1 = new Vector3(); const edge2 = new Vector3(); const faceNormal = new Vector3(); for (let i = 0; i < indices.length; i += 3) { const i0 = indices[i]; const i1 = indices[i + 1]; const i2 = indices[i + 2]; v0.set(positions[i0 * 3], positions[i0 * 3 + 1], positions[i0 * 3 + 2]); v1.set(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]); v2.set(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]); v1.subtractToRef(v0, edge1); v2.subtractToRef(v0, edge2); Vector3.CrossToRef(edge1, edge2, faceNormal); normal.addInPlace(faceNormal); } if (normal.lengthSquared() < 1e-10) { normal.set(0, 1, 0); } else { normal.normalize(); } // Step 3: Create offset positions const offset = normal.scale(depthValue); const vertexCount = positions.length / 3; const bottomPositions = new Float32Array(positions); const topPositions = new Float32Array(vertexCount * 3); for (let i = 0; i < vertexCount; i++) { topPositions[i * 3] = positions[i * 3] + offset.x; topPositions[i * 3 + 1] = positions[i * 3 + 1] + offset.y; topPositions[i * 3 + 2] = positions[i * 3 + 2] + offset.z; } // Step 4: Detect boundary edges const edgeCount = new Map(); for (let i = 0; i < indices.length; i += 3) { const triIdx = i; const triVerts = [indices[i], indices[i + 1], indices[i + 2]]; for (let e = 0; e < 3; e++) { const a = triVerts[e]; const b = triVerts[(e + 1) % 3]; const key = Math.min(a, b) + "_" + Math.max(a, b); const existing = edgeCount.get(key); if (existing) { existing.count++; } else { edgeCount.set(key, { count: 1, triIndex: triIdx }); } } } // Collect boundary edges with winding info const boundaryEdges = []; for (const [key, value] of Array.from(edgeCount)) { if (value.count === 1) { const parts = key.split("_"); const minV = parseInt(parts[0]); const maxV = parseInt(parts[1]); // Check original winding order in the triangle const ti = value.triIndex; const t0 = indices[ti]; const t1 = indices[ti + 1]; const t2 = indices[ti + 2]; // Determine if edge (minV, maxV) appears in order or reversed in the triangle let inOrder = false; if ((t0 === minV && t1 === maxV) || (t1 === minV && t2 === maxV) || (t2 === minV && t0 === maxV)) { inOrder = true; } boundaryEdges.push({ a: minV, b: maxV, orderedAb: inOrder }); } } // Step 5: Generate side wall faces const sideIndices = []; for (const edge of boundaryEdges) { const a = edge.a; const b = edge.b; const aTop = a + vertexCount; const bTop = b + vertexCount; if (edge.orderedAb) { // Edge appears as (a, b) in triangle — outward normals sideIndices.push(a, b, bTop); sideIndices.push(a, bTop, aTop); } else { // Edge appears as (b, a) in triangle — reverse winding sideIndices.push(b, a, aTop); sideIndices.push(b, aTop, bTop); } } // Step 6: Assemble final VertexData const capStart = (this.cap & ExtrudeGeometryCap.CapStart) !== 0; const capEnd = (this.cap & ExtrudeGeometryCap.CapEnd) !== 0; // Build indices const finalIndices = []; // Bottom cap: original indices if (capStart) { for (let i = 0; i < indices.length; i++) { finalIndices.push(indices[i]); } } // Top cap: original indices offset by vertexCount, reversed winding if (capEnd) { for (let i = 0; i < indices.length; i += 3) { finalIndices.push(indices[i] + vertexCount); finalIndices.push(indices[i + 2] + vertexCount); finalIndices.push(indices[i + 1] + vertexCount); } } // Side walls for (let i = 0; i < sideIndices.length; i++) { finalIndices.push(sideIndices[i]); } // Build positions: [bottom, top] const finalPositions = new Float32Array(vertexCount * 2 * 3); finalPositions.set(bottomPositions, 0); finalPositions.set(topPositions, vertexCount * 3); // Build the result VertexData const result = new VertexData(); result.positions = Array.from(finalPositions); result.indices = finalIndices; // Duplicate UVs if present if (vertexData.uvs) { const uvs = vertexData.uvs; const finalUVs = new Float32Array(vertexCount * 2 * 2); finalUVs.set(new Float32Array(uvs), 0); finalUVs.set(new Float32Array(uvs), vertexCount * 2); result.uvs = Array.from(finalUVs); } // Duplicate colors if present if (vertexData.colors) { const colors = vertexData.colors; const finalColors = new Float32Array(vertexCount * 2 * 4); finalColors.set(new Float32Array(colors), 0); finalColors.set(new Float32Array(colors), vertexCount * 4); result.colors = Array.from(finalColors); } // Duplicate tangents if present if (vertexData.tangents) { const tangents = vertexData.tangents; const finalTangents = new Float32Array(vertexCount * 2 * 4); finalTangents.set(new Float32Array(tangents), 0); finalTangents.set(new Float32Array(tangents), vertexCount * 4); result.tangents = Array.from(finalTangents); } // Duplicate UV2-UV6 if present const uvSets = ["uvs2", "uvs3", "uvs4", "uvs5", "uvs6"]; for (const uvSet of uvSets) { const uvData = vertexData[uvSet]; if (uvData) { const finalUV = new Float32Array(vertexCount * 2 * 2); finalUV.set(new Float32Array(uvData), 0); finalUV.set(new Float32Array(uvData), vertexCount * 2); result[uvSet] = Array.from(finalUV); } } // Compute normals using the simplified approach const normals = []; VertexData.ComputeNormals(result.positions, result.indices, normals); result.normals = normals; return result; }; if (this.evaluateContext) { this.output._storedFunction = func; } else { this.output._storedFunction = null; this.output._storedValue = func(state); } } _dumpPropertiesCode() { let codeString = super._dumpPropertiesCode() + `${this._codeVariableName}.evaluateContext = ${this.evaluateContext ? "true" : "false"};\n`; codeString += `${this._codeVariableName}.cap = BABYLON.ExtrudeGeometryCap.${ExtrudeGeometryCap[this.cap]};\n`; return codeString; } /** * Serializes this block in a JSON representation * @returns the serialized block object */ serialize() { const serializationObject = super.serialize(); serializationObject.evaluateContext = this.evaluateContext; serializationObject.cap = this.cap; return serializationObject; } /** @internal */ _deserialize(serializationObject) { super._deserialize(serializationObject); if (serializationObject.evaluateContext !== undefined) { this.evaluateContext = serializationObject.evaluateContext; } if (serializationObject.cap !== undefined) { this.cap = serializationObject.cap; } } } __decorate([ editableInPropertyPage("Evaluate context", 0 /* PropertyTypeForEdition.Boolean */, "ADVANCED", { embedded: true, notifiers: { rebuild: true } }) ], ExtrudeGeometryBlock.prototype, "evaluateContext", void 0); __decorate([ editableInPropertyPage("Cap", 5 /* PropertyTypeForEdition.List */, "ADVANCED", { notifiers: { rebuild: true }, embedded: true, options: [ { label: "No Cap", value: ExtrudeGeometryCap.NoCap }, { label: "Cap Start", value: ExtrudeGeometryCap.CapStart }, { label: "Cap End", value: ExtrudeGeometryCap.CapEnd }, { label: "Cap All", value: ExtrudeGeometryCap.CapAll }, ], }) ], ExtrudeGeometryBlock.prototype, "cap", void 0); RegisterClass("BABYLON.ExtrudeGeometryBlock", ExtrudeGeometryBlock); //# sourceMappingURL=extrudeGeometryBlock.js.map