From 4746b2a346ab9728de444fb158c8dc590913a4bb Mon Sep 17 00:00:00 2001 From: David Catuhe Date: Thu, 10 Oct 2024 15:22:38 -0700 Subject: [PATCH] Add lattice support for NGE (#15690) * Fix #15678 * Fixed! * no need --- .../Meshes/Node/Blocks/Set/latticeBlock.ts | 227 ++++++++++++++++++ .../Meshes/Node/Blocks/geometryInputBlock.ts | 2 + .../Enums/nodeGeometryContextualSources.ts | 4 + packages/dev/core/src/Meshes/Node/index.ts | 1 + .../src/Meshes/Node/nodeGeometryBuildState.ts | 12 + packages/dev/core/src/Meshes/lattice.ts | 2 +- .../nodeGeometryEditor/src/blockTools.ts | 13 + .../components/nodeList/nodeListComponent.tsx | 6 + .../display/inputDisplayManager.ts | 6 + .../genericNodePropertyComponent.tsx | 44 +++- .../properties/inputNodePropertyComponent.tsx | 2 + 11 files changed, 305 insertions(+), 14 deletions(-) create mode 100644 packages/dev/core/src/Meshes/Node/Blocks/Set/latticeBlock.ts diff --git a/packages/dev/core/src/Meshes/Node/Blocks/Set/latticeBlock.ts b/packages/dev/core/src/Meshes/Node/Blocks/Set/latticeBlock.ts new file mode 100644 index 00000000000..0a811914872 --- /dev/null +++ b/packages/dev/core/src/Meshes/Node/Blocks/Set/latticeBlock.ts @@ -0,0 +1,227 @@ +import { NodeGeometryBlock } from "../../nodeGeometryBlock"; +import type { NodeGeometryConnectionPoint } from "../../nodeGeometryBlockConnectionPoint"; +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeGeometryBlockConnectionPointTypes } from "../../Enums/nodeGeometryConnectionPointTypes"; +import type { NodeGeometryBuildState } from "../../nodeGeometryBuildState"; +import type { INodeGeometryExecutionContext } from "../../Interfaces/nodeGeometryExecutionContext"; +import type { VertexData } from "../../../mesh.vertexData"; +import { Vector3 } from "../../../../Maths/math.vector"; +import { PropertyTypeForEdition, editableInPropertyPage } from "../../../../Decorators/nodeDecorator"; +import { Lattice } from "core/Meshes/lattice"; +import { extractMinAndMax } from "core/Maths/math.functions"; + +/** + * Block used to apply Lattice on geometry + */ +export class LatticeBlock extends NodeGeometryBlock implements INodeGeometryExecutionContext { + private _vertexData: VertexData; + private _currentIndexX: number; + private _currentIndexY: number; + private _currentIndexZ: number; + private _lattice: Lattice; + private _indexVector3 = new Vector3(); + private _currentControl = 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 + */ + @editableInPropertyPage("Evaluate context", PropertyTypeForEdition.Boolean, "ADVANCED", { notifiers: { rebuild: true } }) + public evaluateContext = true; + + /** + * Resolution on x axis + */ + @editableInPropertyPage("resolutionX", PropertyTypeForEdition.Int, "ADVANCED", { notifiers: { rebuild: true }, min: 1, max: 10 }) + public resolutionX = 3; + + /** + * Resolution on y axis + */ + @editableInPropertyPage("resolutionY", PropertyTypeForEdition.Int, "ADVANCED", { notifiers: { rebuild: true }, min: 1, max: 10 }) + public resolutionY = 3; + + /** + * Resolution on z axis + */ + @editableInPropertyPage("resolutionZ", PropertyTypeForEdition.Int, "ADVANCED", { notifiers: { rebuild: true }, min: 1, max: 10 }) + public resolutionZ = 3; + + /** + * Create a new LatticeBlock + * @param name defines the block name + */ + public constructor(name: string) { + super(name); + + this.registerInput("geometry", NodeGeometryBlockConnectionPointTypes.Geometry); + this.registerInput("controls", NodeGeometryBlockConnectionPointTypes.Vector3); + + this.registerOutput("output", NodeGeometryBlockConnectionPointTypes.Geometry); + } + + /** + * Gets the current index in the current flow + * @returns the current index + */ + public getExecutionIndex(): number { + return this._currentIndexX + this.resolutionX * (this._currentIndexY + this.resolutionY * this._currentIndexZ); + } + + /** + * Gets the current loop index in the current flow + * @returns the current loop index + */ + public getExecutionLoopIndex(): number { + return this.getExecutionIndex(); + } + + /** + * Gets the current face index in the current flow + * @returns the current face index + */ + public getExecutionFaceIndex(): number { + return 0; + } + + /** + * Gets the current class name + * @returns the class name + */ + public override getClassName() { + return "LatticeBlock"; + } + + /** + * Gets the geometry input component + */ + public get geometry(): NodeGeometryConnectionPoint { + return this._inputs[0]; + } + + /** + * Gets the controls input component + */ + public get controls(): NodeGeometryConnectionPoint { + return this._inputs[1]; + } + + /** + * Gets the geometry output component + */ + public get output(): NodeGeometryConnectionPoint { + return this._outputs[0]; + } + + /** + * Gets the value associated with a contextual positions + * In this case it will be the current position in the lattice + * @returns the current position in the lattice + */ + public getOverridePositionsContextualValue() { + return this._indexVector3; + } + + /** + * Gets the value associated with a contextual normals + * In this case it will be the current control point being processed + * @returns the current control point being processed + */ + public getOverrideNormalsContextualValue() { + return this._currentControl; + } + + protected override _buildBlock(state: NodeGeometryBuildState) { + const func = (state: NodeGeometryBuildState) => { + state.pushExecutionContext(this); + + this._vertexData = this.geometry.getConnectedValue(state); + + if (this._vertexData) { + this._vertexData = this._vertexData.clone(); // Preserve source data + } + + if (!this._vertexData || !this._vertexData.positions) { + state.restoreExecutionContext(); + this.output._storedValue = null; + return; + } + const positions = this._vertexData.positions; + const boundingInfo = extractMinAndMax(positions!, 0, positions!.length / 3); + + // Building the lattice + this._lattice = new Lattice({ + resolutionX: this.resolutionX, + resolutionY: this.resolutionY, + resolutionZ: this.resolutionZ, + size: boundingInfo.maximum.subtract(boundingInfo.minimum), + }); + + for (this._currentIndexX = 0; this._currentIndexX < this.resolutionX; this._currentIndexX++) { + for (this._currentIndexY = 0; this._currentIndexY < this.resolutionY; this._currentIndexY++) { + for (this._currentIndexZ = 0; this._currentIndexZ < this.resolutionZ; this._currentIndexZ++) { + this._indexVector3.set(this._currentIndexX, this._currentIndexY, this._currentIndexZ); + const latticeControl = this._lattice.data[this._currentIndexX][this._currentIndexY][this._currentIndexZ]; + this._currentControl.copyFrom(latticeControl); + const tempVector3 = this.controls.getConnectedValue(state) as Vector3; + + if (tempVector3) { + latticeControl.set(tempVector3.x, tempVector3.y, tempVector3.z); + } + } + } + } + + // Apply lattice + this._lattice.deform(positions); + + // Storage + state.restoreExecutionContext(); + return this._vertexData; + }; + + if (this.evaluateContext) { + this.output._storedFunction = func; + } else { + this.output._storedFunction = null; + this.output._storedValue = func(state); + } + } + + protected override _dumpPropertiesCode() { + let codeString = super._dumpPropertiesCode() + `${this._codeVariableName}.evaluateContext = ${this.evaluateContext ? "true" : "false"};\n`; + + codeString += `${this._codeVariableName}.resolutionX = ${this.resolutionX};\n`; + codeString += `${this._codeVariableName}.resolutionY = ${this.resolutionY};\n`; + codeString += `${this._codeVariableName}.resolutionZ = ${this.resolutionZ};\n`; + return codeString; + } + + /** + * Serializes this block in a JSON representation + * @returns the serialized block object + */ + public override serialize(): any { + const serializationObject = super.serialize(); + + serializationObject.evaluateContext = this.evaluateContext; + serializationObject.resolutionX = this.resolutionX; + serializationObject.resolutionY = this.resolutionY; + serializationObject.resolutionZ = this.resolutionZ; + + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + + if (serializationObject.evaluateContext !== undefined) { + this.evaluateContext = serializationObject.evaluateContext; + this.resolutionX = serializationObject.resolutionX; + this.resolutionY = serializationObject.resolutionY; + this.resolutionZ = serializationObject.resolutionZ; + } + } +} + +RegisterClass("BABYLON.LatticeBlock", LatticeBlock); diff --git a/packages/dev/core/src/Meshes/Node/Blocks/geometryInputBlock.ts b/packages/dev/core/src/Meshes/Node/Blocks/geometryInputBlock.ts index adcdf3d7afc..714f81bdd25 100644 --- a/packages/dev/core/src/Meshes/Node/Blocks/geometryInputBlock.ts +++ b/packages/dev/core/src/Meshes/Node/Blocks/geometryInputBlock.ts @@ -79,6 +79,8 @@ export class GeometryInputBlock extends NodeGeometryBlock { switch (value) { case NodeGeometryContextualSources.Positions: case NodeGeometryContextualSources.Normals: + case NodeGeometryContextualSources.LatticeID: + case NodeGeometryContextualSources.LatticeControl: this._type = NodeGeometryBlockConnectionPointTypes.Vector3; break; case NodeGeometryContextualSources.Colors: diff --git a/packages/dev/core/src/Meshes/Node/Enums/nodeGeometryContextualSources.ts b/packages/dev/core/src/Meshes/Node/Enums/nodeGeometryContextualSources.ts index 05b0b66cf24..402134c3720 100644 --- a/packages/dev/core/src/Meshes/Node/Enums/nodeGeometryContextualSources.ts +++ b/packages/dev/core/src/Meshes/Node/Enums/nodeGeometryContextualSources.ts @@ -36,4 +36,8 @@ export enum NodeGeometryContextualSources { LoopID = 0x000f, /** InstanceID */ InstanceID = 0x0010, + /** LatticeID */ + LatticeID = 0x0011, + /** LatticeControl */ + LatticeControl = 0x0012, } diff --git a/packages/dev/core/src/Meshes/Node/index.ts b/packages/dev/core/src/Meshes/Node/index.ts index 3df9910a4f2..9ec1b8092b9 100644 --- a/packages/dev/core/src/Meshes/Node/index.ts +++ b/packages/dev/core/src/Meshes/Node/index.ts @@ -36,6 +36,7 @@ export * from "./Blocks/computeNormalsBlock"; export * from "./Blocks/vectorConverterBlock"; export * from "./Blocks/normalizeVectorBlock"; export * from "./Blocks/Set/setMaterialIDBlock"; +export * from "./Blocks/Set/latticeBlock"; export * from "./Blocks/geometryTrigonometryBlock"; export * from "./Blocks/geometryTransformBlock"; export * from "./Blocks/Matrices/rotationXBlock"; diff --git a/packages/dev/core/src/Meshes/Node/nodeGeometryBuildState.ts b/packages/dev/core/src/Meshes/Node/nodeGeometryBuildState.ts index 0660b7cbfa0..aa4e2e24136 100644 --- a/packages/dev/core/src/Meshes/Node/nodeGeometryBuildState.ts +++ b/packages/dev/core/src/Meshes/Node/nodeGeometryBuildState.ts @@ -195,6 +195,18 @@ export class NodeGeometryBuildState { } return this.geometryContext.metadata.collectionId || 0; } + case NodeGeometryContextualSources.LatticeID: { + if (this.executionContext.getOverridePositionsContextualValue) { + return this.executionContext.getOverridePositionsContextualValue(); + } + return Vector3.Zero(); + } + case NodeGeometryContextualSources.LatticeControl: { + if (this.executionContext.getOverrideNormalsContextualValue) { + return this.executionContext.getOverrideNormalsContextualValue(); + } + return Vector3.Zero(); + } } return null; diff --git a/packages/dev/core/src/Meshes/lattice.ts b/packages/dev/core/src/Meshes/lattice.ts index 4654f03d136..4d7282b36fa 100644 --- a/packages/dev/core/src/Meshes/lattice.ts +++ b/packages/dev/core/src/Meshes/lattice.ts @@ -102,7 +102,7 @@ export class Lattice { * Creates a new Lattice * @param options options for creating */ - public constructor(options: Partial) { + public constructor(options?: Partial) { const localOptions: ILatticeOptions = { resolutionX: 3, resolutionY: 3, diff --git a/packages/tools/nodeGeometryEditor/src/blockTools.ts b/packages/tools/nodeGeometryEditor/src/blockTools.ts index 93c71a01315..4f81de11268 100644 --- a/packages/tools/nodeGeometryEditor/src/blockTools.ts +++ b/packages/tools/nodeGeometryEditor/src/blockTools.ts @@ -74,6 +74,7 @@ import { GeometryReplaceColorBlock } from "core/Meshes/Node/Blocks/geometryRepla import { GeometryRotate2dBlock } from "core/Meshes/Node/Blocks/geometryRotate2dBlock"; import { GeometryLengthBlock } from "core/Meshes/Node/Blocks/geometryLengthBlock"; import { GeometryInterceptorBlock } from "core/Meshes/Node/Blocks/geometryInterceptorBlock"; +import { LatticeBlock } from "core/Meshes/Node/Blocks/Set/latticeBlock"; /** * Static class for BlockTools @@ -81,6 +82,8 @@ import { GeometryInterceptorBlock } from "core/Meshes/Node/Blocks/geometryInterc export class BlockTools { public static GetBlockFromString(data: string) { switch (data) { + case "LatticeBlock": + return new LatticeBlock("Lattice"); case "InterceptorBlock": return new GeometryInterceptorBlock("Interceptor"); case "Rotate2dBlock": @@ -315,6 +318,16 @@ export class BlockTools { block.contextualValue = NodeGeometryContextualSources.FaceID; return block; } + case "LatticeIDBlock": { + const block = new GeometryInputBlock("Lattice ID"); + block.contextualValue = NodeGeometryContextualSources.LatticeID; + return block; + } + case "LatticeControlBlock": { + const block = new GeometryInputBlock("Lattice Control"); + block.contextualValue = NodeGeometryContextualSources.LatticeControl; + return block; + } case "AddBlock": { const block = new MathBlock("Add"); block.operation = MathBlockOperations.Add; diff --git a/packages/tools/nodeGeometryEditor/src/components/nodeList/nodeListComponent.tsx b/packages/tools/nodeGeometryEditor/src/components/nodeList/nodeListComponent.tsx index 10b17977220..f92c3de94ea 100644 --- a/packages/tools/nodeGeometryEditor/src/components/nodeList/nodeListComponent.tsx +++ b/packages/tools/nodeGeometryEditor/src/components/nodeList/nodeListComponent.tsx @@ -48,6 +48,8 @@ export class NodeListComponent extends React.Component this.forceRebuild(propertyName, options.notifiers)} - /> - ); + const cantDisplaySlider = isNaN(options.min as number) || isNaN(options.max as number) || options.min === options.max; + if (cantDisplaySlider) { + components.push( + this.forceRebuild(propertyName, options.notifiers)} + /> + ); + } else { + components.push( + this.forceRebuild(propertyName, options.notifiers)} + /> + ); + } break; } case PropertyTypeForEdition.Vector2: { diff --git a/packages/tools/nodeGeometryEditor/src/graphSystem/properties/inputNodePropertyComponent.tsx b/packages/tools/nodeGeometryEditor/src/graphSystem/properties/inputNodePropertyComponent.tsx index 104cfda6b78..9c88a0281d0 100644 --- a/packages/tools/nodeGeometryEditor/src/graphSystem/properties/inputNodePropertyComponent.tsx +++ b/packages/tools/nodeGeometryEditor/src/graphSystem/properties/inputNodePropertyComponent.tsx @@ -141,6 +141,8 @@ export class InputPropertyTabComponent extends React.Component