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.

309 lines 13.5 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 { Vector3 } from "../../../../Maths/math.vector.js"; import { editableInPropertyPage } from "../../../../Decorators/nodeDecorator.js"; import { Ray } from "../../../../Culling/ray.js"; import { extractMinAndMax } from "../../../../Maths/math.functions.js"; /** * Block used to instance geometry inside a geometry */ export class InstantiateOnVolumeBlock extends NodeGeometryBlock { /** * Create a new InstantiateOnVolumeBlock * @param name defines the block name */ constructor(name) { super(name); this._currentPosition = new Vector3(); this._vertex0 = new Vector3(); this._vertex1 = new Vector3(); this._vertex2 = new Vector3(); /** * 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 = true; /** * Gets or sets a boolean indicating that a grid pattern should be used */ this.gridMode = false; this.registerInput("geometry", NodeGeometryBlockConnectionPointTypes.Geometry); this.registerInput("instance", NodeGeometryBlockConnectionPointTypes.Geometry, true); this.registerInput("count", NodeGeometryBlockConnectionPointTypes.Int, true, 256); this.registerInput("matrix", NodeGeometryBlockConnectionPointTypes.Matrix, true); this.registerInput("offset", NodeGeometryBlockConnectionPointTypes.Vector3, true, Vector3.Zero()); this.registerInput("rotation", NodeGeometryBlockConnectionPointTypes.Vector3, true, Vector3.Zero()); this.registerInput("scaling", NodeGeometryBlockConnectionPointTypes.Vector3, true, Vector3.One()); this.registerInput("gridSize", NodeGeometryBlockConnectionPointTypes.Int, true, 10); this.scaling.acceptedConnectionPointTypes.push(NodeGeometryBlockConnectionPointTypes.Float); this.registerOutput("output", NodeGeometryBlockConnectionPointTypes.Geometry); } /** * Gets the current instance index in the current flow * @returns the current index */ getInstanceIndex() { return this._currentLoopIndex; } /** * Gets the current index in the current flow * @returns the current index */ getExecutionIndex() { return 0; } /** * Gets the current face index in the current flow * @returns the current face index */ getExecutionFaceIndex() { return 0; } /** * Gets the current loop index in the current flow * @returns the current loop index */ getExecutionLoopIndex() { return this._currentLoopIndex; } /** * Gets the value associated with a contextual positions * @returns the value associated with the source */ getOverridePositionsContextualValue() { return this._currentPosition; } /** * Gets the current class name * @returns the class name */ getClassName() { return "InstantiateOnVolumeBlock"; } /** * Gets the geometry input component */ get geometry() { return this._inputs[0]; } /** * Gets the instance input component */ get instance() { return this._inputs[1]; } /** * Gets the count input component */ get count() { return this._inputs[2]; } /** * Gets the matrix input component */ get matrix() { return this._inputs[3]; } /** * Gets the offset input component */ get offset() { return this._inputs[4]; } /** * Gets the rotation input component */ get rotation() { return this._inputs[5]; } /** * Gets the scaling input component */ get scaling() { return this._inputs[6]; } /** * Gets the grid size input component */ get gridSize() { return this._inputs[6]; } /** * Gets the geometry output component */ get output() { return this._outputs[0]; } _getValueOnGrid(step, size, min, max) { const cellSize = (max - min) / size; return min + cellSize / 2 + step * cellSize; } _getIndexinGrid(x, y, z, size) { return x + y * size + z * size * size; } _buildBlock(state) { const func = (state) => { state.pushExecutionContext(this); state.pushInstancingContext(this); this._vertexData = this.geometry.getConnectedValue(state); state.pushGeometryContext(this._vertexData); if (!this._vertexData || !this._vertexData.positions || !this._vertexData.indices || !this.instance.isConnected) { state.restoreExecutionContext(); state.restoreInstancingContext(); state.restoreGeometryContext(); this.output._storedValue = null; return; } // Processing let instanceGeometry = null; const instanceCount = this.count.getConnectedValue(state); const additionalVertexData = []; const boundingInfo = extractMinAndMax(this._vertexData.positions, 0, this._vertexData.positions.length / 3); const min = boundingInfo.minimum; const max = boundingInfo.maximum; const direction = new Vector3(0.5, 0.8, 0.2); const faceCount = this._vertexData.indices.length / 3; const gridSize = this.gridSize.getConnectedValue(state); this._currentLoopIndex = 0; let candidatesCells; if (this.gridMode) { candidatesCells = []; // Generates the list of candidates cells for (let index = 0; index < gridSize * gridSize * gridSize; index++) { candidatesCells[index] = false; } } for (let index = 0; index < instanceCount; index++) { if (this.gridMode) { // Get a random cell let cellX = Math.floor(Math.random() * gridSize); let cellY = Math.floor(Math.random() * gridSize); let cellZ = Math.floor(Math.random() * gridSize); let cellIndex = this._getIndexinGrid(cellX, cellY, cellZ, gridSize); if (candidatesCells[cellIndex]) { // Find the first one that is free let found = false; for (let candidateIndex = 0; candidateIndex < gridSize * gridSize * gridSize; candidateIndex++) { if (!candidatesCells[candidateIndex]) { cellZ = Math.floor(candidateIndex / (gridSize * gridSize)); cellY = Math.floor((candidateIndex - cellZ * gridSize * gridSize) / gridSize); cellX = candidateIndex - cellZ * gridSize * gridSize - cellY * gridSize; cellIndex = this._getIndexinGrid(cellX, cellY, cellZ, gridSize); found = true; break; } } if (!found) { // No more free cells break; } } if (!candidatesCells[cellIndex]) { // Cell is free const x = this._getValueOnGrid(cellX, gridSize, min.x, max.x); const y = this._getValueOnGrid(cellY, gridSize, min.y, max.y); const z = this._getValueOnGrid(cellZ, gridSize, min.z, max.z); this._currentPosition.set(x, y, z); candidatesCells[cellIndex] = true; } } else { this._currentPosition.set(Math.random() * (max.x - min.x) + min.x, Math.random() * (max.y - min.y) + min.y, Math.random() * (max.z - min.z) + min.z); } // Cast a ray from the random point in an arbitrary direction const ray = new Ray(this._currentPosition, direction); let intersectionCount = 0; for (let currentFaceIndex = 0; currentFaceIndex < faceCount; currentFaceIndex++) { // Extract face vertices this._vertex0.fromArray(this._vertexData.positions, this._vertexData.indices[currentFaceIndex * 3] * 3); this._vertex1.fromArray(this._vertexData.positions, this._vertexData.indices[currentFaceIndex * 3 + 1] * 3); this._vertex2.fromArray(this._vertexData.positions, this._vertexData.indices[currentFaceIndex * 3 + 2] * 3); const currentIntersectInfo = ray.intersectsTriangle(this._vertex0, this._vertex1, this._vertex2); if (currentIntersectInfo && currentIntersectInfo.distance > 0) { intersectionCount++; } } if (intersectionCount % 2 === 0) { // We are outside, try again index--; continue; } // Clone the instance instanceGeometry = this.instance.getConnectedValue(state); if (!instanceGeometry || !instanceGeometry.positions || instanceGeometry.positions.length === 0) { continue; } const clone = instanceGeometry.clone(); if (this.matrix.isConnected) { const transform = this.matrix.getConnectedValue(state); state._instantiateWithPositionAndMatrix(clone, this._currentPosition, transform, additionalVertexData); } else { const offset = state.adaptInput(this.offset, NodeGeometryBlockConnectionPointTypes.Vector3, Vector3.ZeroReadOnly); const scaling = state.adaptInput(this.scaling, NodeGeometryBlockConnectionPointTypes.Vector3, Vector3.OneReadOnly); const rotation = this.rotation.getConnectedValue(state) || Vector3.ZeroReadOnly; this._currentPosition.addInPlace(offset); state._instantiate(clone, this._currentPosition, rotation, scaling, additionalVertexData); } this._currentLoopIndex++; } // Merge if (additionalVertexData.length) { if (additionalVertexData.length === 1) { this._vertexData = additionalVertexData[0]; } else { // We do not merge the main one as user can use a merge node if wanted const main = additionalVertexData.splice(0, 1)[0]; this._vertexData = main.merge(additionalVertexData, true, false, true, true); } } state.restoreGeometryContext(); state.restoreExecutionContext(); state.restoreInstancingContext(); return this._vertexData; }; // Storage 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}.gridMode = ${this.gridMode ? "true" : "false"};\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.gridMode = this.gridMode; return serializationObject; } _deserialize(serializationObject) { super._deserialize(serializationObject); if (serializationObject.evaluateContext !== undefined) { this.evaluateContext = serializationObject.evaluateContext; } if (serializationObject.gridMode !== undefined) { this.gridMode = serializationObject.gridMode; } } } __decorate([ editableInPropertyPage("Evaluate context", 0 /* PropertyTypeForEdition.Boolean */, "ADVANCED", { notifiers: { rebuild: true } }) ], InstantiateOnVolumeBlock.prototype, "evaluateContext", void 0); __decorate([ editableInPropertyPage("Grid mode", 0 /* PropertyTypeForEdition.Boolean */, "MODES", { notifiers: { rebuild: true } }) ], InstantiateOnVolumeBlock.prototype, "gridMode", void 0); RegisterClass("BABYLON.InstantiateOnVolumeBlock", InstantiateOnVolumeBlock); //# sourceMappingURL=instantiateOnVolumeBlock.js.map