diff --git a/packages/dev/core/src/Decorators/nodeDecorator.ts b/packages/dev/core/src/Decorators/nodeDecorator.ts index b0ad690caca..44754fe852d 100644 --- a/packages/dev/core/src/Decorators/nodeDecorator.ts +++ b/packages/dev/core/src/Decorators/nodeDecorator.ts @@ -21,6 +21,8 @@ export const enum PropertyTypeForEdition { Color3, /** property is a Color4 */ Color4, + /** property is a string */ + String, /** property (int) should be edited as a combo box with a list of sampling modes */ SamplingMode, /** property (int) should be edited as a combo box with a list of texture formats */ diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ISPSData.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ISPSData.ts new file mode 100644 index 00000000000..8d0e2f0227f --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ISPSData.ts @@ -0,0 +1,26 @@ +import { Vector3 } from "core/Maths/math.vector"; +import { Color4 } from "core/Maths/math.color"; +import type { Mesh } from "core/Meshes/mesh"; +import type { Material } from "core/Materials/material"; + +/** + * Interface for SPS update block data + */ +export interface ISPSUpdateData { + position?: () => Vector3; + velocity?: () => Vector3; + color?: () => Color4; + scaling?: () => Vector3; + rotation?: () => Vector3; +} + +/** + * Interface for SPS create block data + */ +export interface ISPSParticleConfigData { + mesh: Mesh; + count: number; + material?: Material; + initBlock?: ISPSUpdateData; + updateBlock?: ISPSUpdateData; +} diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ParticlePropsGetBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ParticlePropsGetBlock.ts new file mode 100644 index 00000000000..c0fc904c3de --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ParticlePropsGetBlock.ts @@ -0,0 +1,129 @@ +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; +import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; +import { SolidParticle } from "../../../solidParticle"; +import { editableInPropertyPage, PropertyTypeForEdition } from "../../../../Decorators/nodeDecorator"; +import { serialize } from "../../../../Misc/decorators"; + +/** + * Block used to get custom properties from particle.props + * Works similar to contextual blocks but for dynamic property names + */ +export class ParticlePropsGetBlock extends NodeParticleBlock { + /** + * Gets or sets the property name to read from particle.props + */ + @serialize("propertyName") + @editableInPropertyPage("Property Name", PropertyTypeForEdition.String, "PROPERTIES", { + embedded: false, + notifiers: { rebuild: true }, + }) + public propertyName: string = "value"; + + /** + * Gets or sets the connection point type (default float) + */ + private _type: NodeParticleBlockConnectionPointTypes = NodeParticleBlockConnectionPointTypes.Float; + + /** + * Gets the value to display (returns propertyName as string) + */ + public get displayValue(): string { + return this.propertyName || "value"; + } + + public constructor(name: string) { + super(name); + + this.registerOutput("output", NodeParticleBlockConnectionPointTypes.AutoDetect); + // Set default type + (this._outputs[0] as any)._defaultConnectionPointType = this._type; + } + + public override getClassName() { + return "ParticlePropsGetBlock"; + } + + /** + * Gets or sets the connection point type + */ + public get type(): NodeParticleBlockConnectionPointTypes { + return this._type; + } + + public set type(value: NodeParticleBlockConnectionPointTypes) { + if (this._type !== value) { + this._type = value; + // Update output type + (this._outputs[0] as any)._type = value; + (this._outputs[0] as any)._defaultConnectionPointType = value; + } + } + + public get output(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + public override _build(state: NodeParticleBuildState) { + // Validate property name + if (!this.propertyName || this.propertyName.trim() === "") { + this.output._storedFunction = null; + this.output._storedValue = null; + return; + } + + // Validate type + if (this._type === NodeParticleBlockConnectionPointTypes.Undefined || this._type === NodeParticleBlockConnectionPointTypes.AutoDetect) { + this._type = NodeParticleBlockConnectionPointTypes.Float; + (this._outputs[0] as any)._type = this._type; + (this._outputs[0] as any)._defaultConnectionPointType = this._type; + } + + const propertyName = this.propertyName; + + const func = (state: NodeParticleBuildState) => { + if (!state.particleContext) { + return null; + } + + const particle = state.particleContext as SolidParticle; + + if (!particle.props) { + return null; + } + + const value = particle.props[propertyName]; + + if (value === undefined) { + return null; + } + + return value; + }; + + if (this.output.isConnected) { + this.output._storedFunction = func; + } else { + this.output._storedValue = func(state); + } + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.propertyName = this.propertyName; + serializationObject.type = this._type; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.propertyName = serializationObject.propertyName || "value"; + this._type = serializationObject.type || NodeParticleBlockConnectionPointTypes.Float; + (this._outputs[0] as any)._type = this._type; + (this._outputs[0] as any)._defaultConnectionPointType = this._type; + } +} + +RegisterClass("BABYLON.ParticlePropsGetBlock", ParticlePropsGetBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ParticlePropsSetBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ParticlePropsSetBlock.ts new file mode 100644 index 00000000000..6313d5a7c58 --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/ParticlePropsSetBlock.ts @@ -0,0 +1,133 @@ +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; +import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; +import { SolidParticle } from "../../../solidParticle"; +import { editableInPropertyPage, PropertyTypeForEdition } from "../../../../Decorators/nodeDecorator"; +import { serialize } from "../../../../Misc/decorators"; + +/** + * Block used to set custom properties in particle.props + * Works as a side-effect block that stores values and passes them through + */ +export class ParticlePropsSetBlock extends NodeParticleBlock { + /** + * Gets or sets the property name to store in particle.props + */ + @serialize("propertyName") + @editableInPropertyPage("Property Name", PropertyTypeForEdition.String, "PROPERTIES", { + embedded: false, + notifiers: { rebuild: true }, + }) + public propertyName: string = "value"; + + /** + * Gets or sets the connection point type (default float) + */ + private _type: NodeParticleBlockConnectionPointTypes = NodeParticleBlockConnectionPointTypes.Float; + + public constructor(name: string) { + super(name); + + this.registerInput("value", NodeParticleBlockConnectionPointTypes.AutoDetect, true); + this.registerOutput("output", NodeParticleBlockConnectionPointTypes.BasedOnInput); + + // Link output type to input type + this._outputs[0]._typeConnectionSource = this._inputs[0]; + // Set default type for when input is not connected + (this._outputs[0] as any)._defaultConnectionPointType = this._type; + } + + public override getClassName() { + return "ParticlePropsSetBlock"; + } + + public get value(): NodeParticleConnectionPoint { + return this._inputs[0]; + } + + /** + * Gets the value to display (returns propertyName as string) + * This shadows the connection point name for display purposes + */ + public get displayValue(): string { + return this.propertyName || "value"; + } + + public get output(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + /** + * Gets or sets the connection point type + */ + public get type(): NodeParticleBlockConnectionPointTypes { + return this._type; + } + + public set type(value: NodeParticleBlockConnectionPointTypes) { + if (this._type !== value) { + this._type = value; + // Update default type (used when input is not connected) + (this._outputs[0] as any)._defaultConnectionPointType = value; + } + } + + public override _build(state: NodeParticleBuildState) { + // Validate property name + if (!this.propertyName || this.propertyName.trim() === "") { + this.output._storedFunction = null; + this.output._storedValue = null; + return; + } + + if (!this.value.isConnected) { + this.output._storedFunction = null; + this.output._storedValue = null; + return; + } + + const propertyName = this.propertyName; + + const func = (state: NodeParticleBuildState) => { + if (!state.particleContext) { + return null; + } + + const particle = state.particleContext as SolidParticle; + + const value = this.value.getConnectedValue(state); + + if (!particle.props) { + particle.props = {}; + } + + particle.props[propertyName] = value; + + return value; + }; + + if (this.output.isConnected) { + this.output._storedFunction = func; + } else { + this.output._storedValue = func(state); + } + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.propertyName = this.propertyName; + serializationObject.type = this._type; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.propertyName = serializationObject.propertyName || "value"; + this._type = serializationObject.type || NodeParticleBlockConnectionPointTypes.Float; + (this._outputs[0] as any)._defaultConnectionPointType = this._type; + } +} + +RegisterClass("BABYLON.ParticlePropsSetBlock", ParticlePropsSetBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSCreateBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSCreateBlock.ts new file mode 100644 index 00000000000..d0798fe21c0 --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSCreateBlock.ts @@ -0,0 +1,213 @@ +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; +import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; +import { SolidParticleSystem } from "core/Particles/solidParticleSystem"; +import type { ISPSParticleConfigData } from "./ISPSData"; +import { SolidParticle } from "../../../solidParticle"; +import { Observer } from "../../../../Misc"; + +/** + * Block used to create SolidParticleSystem and collect all Create blocks + */ +export class SPSCreateBlock extends NodeParticleBlock { + private _connectionObservers = new Map>(); + private _disconnectionObservers = new Map>(); + + public constructor(name: string) { + super(name); + this.registerInput(`particleConfig-${this._entryCount - 1}`, NodeParticleBlockConnectionPointTypes.SolidParticle); + this.registerOutput("solidParticleSystem", NodeParticleBlockConnectionPointTypes.SolidParticleSystem); + + this._manageExtendedInputs(0); + } + + public override getClassName() { + return "SPSCreateBlock"; + } + + private _entryCount = 1; + + private _extend() { + this._entryCount++; + this.registerInput(`particleConfig-${this._entryCount - 1}`, NodeParticleBlockConnectionPointTypes.SolidParticle, true); + this._manageExtendedInputs(this._entryCount - 1); + } + + private _shrink() { + if (this._entryCount > 1) { + this._unmanageExtendedInputs(this._entryCount - 1); + this._entryCount--; + this.unregisterInput(`particleConfig-${this._entryCount}`); + } + } + + private _manageExtendedInputs(index: number) { + const connectionObserver = this._inputs[index].onConnectionObservable.add(() => { + if (this._entryCount - 1 > index) { + return; + } + this._extend(); + }); + + const disconnectionObserver = this._inputs[index].onDisconnectionObservable.add(() => { + if (this._entryCount - 1 > index) { + return; + } + this._shrink(); + }); + + // Store observers for later removal + this._connectionObservers.set(index, connectionObserver); + this._disconnectionObservers.set(index, disconnectionObserver); + } + + private _unmanageExtendedInputs(index: number) { + const connectionObserver = this._connectionObservers.get(index); + const disconnectionObserver = this._disconnectionObservers.get(index); + + if (connectionObserver) { + this._inputs[index].onConnectionObservable.remove(connectionObserver); + this._connectionObservers.delete(index); + } + + if (disconnectionObserver) { + this._inputs[index].onDisconnectionObservable.remove(disconnectionObserver); + this._disconnectionObservers.delete(index); + } + } + + public get particleConfig(): NodeParticleConnectionPoint { + return this._inputs[this._entryCount - 1]; + } + + public get solidParticleSystem(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + public override _build(state: NodeParticleBuildState) { + if (!state.scene) { + throw new Error("Scene is not initialized in NodeParticleBuildState"); + } + + const sps = new SolidParticleSystem(this.name, state.scene, { + useModelMaterial: true, + }); + + const createBlocks = new Map(); + for (let i = 0; i < this._inputs.length; i++) { + const creatData = this._inputs[i].getConnectedValue(state) as ISPSParticleConfigData; + if (this._inputs[i].isConnected && creatData) { + if (creatData.mesh && creatData.count) { + const shapeId = sps.addShape(creatData.mesh, creatData.count); + createBlocks.set(shapeId, creatData); + creatData.mesh.isVisible = false; + } + } + } + + sps.initParticles = () => { + if (!sps) { + return; + } + + const originalContext = state.particleContext; + const originalSystemContext = state.systemContext; + + try { + for (let p = 0; p < sps.nbParticles; p++) { + const particle = sps.particles[p]; + const particleCreateData = createBlocks.get(particle.shapeId); + const initBlock = particleCreateData?.initBlock; + if (!initBlock) { + continue; + } + + state.particleContext = particle; + state.systemContext = sps; + + if (initBlock.position) { + particle.position.copyFrom(initBlock.position()); + } + if (initBlock.velocity) { + particle.velocity.copyFrom(initBlock.velocity()); + } + if (initBlock.color) { + particle.color?.copyFrom(initBlock.color()); + } + if (initBlock.scaling) { + particle.scaling.copyFrom(initBlock.scaling()); + } + if (initBlock.rotation) { + particle.rotation.copyFrom(initBlock.rotation()); + } + } + } finally { + state.particleContext = originalContext; + state.systemContext = originalSystemContext; + } + }; + + sps.updateParticle = (particle: SolidParticle) => { + if (!sps) { + return particle; + } + + const particleCreateData = createBlocks.get(particle.shapeId); + const updateBlock = particleCreateData?.updateBlock; + if (!updateBlock) { + return particle; + } + // Set particle context in state for PerParticle lock mode + const originalContext = state.particleContext; + const originalSystemContext = state.systemContext; + + // Temporarily set particle context for PerParticle lock mode + state.particleContext = particle; + state.systemContext = sps; + + try { + if (updateBlock.position) { + particle.position.copyFrom(updateBlock.position()); + } + if (updateBlock.velocity) { + particle.velocity.copyFrom(updateBlock.velocity()); + } + if (updateBlock.color) { + particle.color?.copyFrom(updateBlock.color()); + } + if (updateBlock.scaling) { + particle.scaling.copyFrom(updateBlock.scaling()); + } + if (updateBlock.rotation) { + particle.rotation.copyFrom(updateBlock.rotation()); + } + } finally { + // Restore original context + state.particleContext = originalContext; + state.systemContext = originalSystemContext; + } + return particle; + }; + + this.solidParticleSystem._storedValue = sps; + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject._entryCount = this._entryCount; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + if (serializationObject._entryCount && serializationObject._entryCount > 1) { + for (let i = 1; i < serializationObject._entryCount; i++) { + this._extend(); + } + } + } +} + +RegisterClass("BABYLON.SPSCreateBlock", SPSCreateBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSInitBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSInitBlock.ts new file mode 100644 index 00000000000..94fa69bb512 --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSInitBlock.ts @@ -0,0 +1,93 @@ +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; +import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; +import type { ISPSUpdateData } from "./ISPSData"; + +/** + * Block used to generate initialization function for SPS particles + */ +export class SPSInitBlock extends NodeParticleBlock { + public constructor(name: string) { + super(name); + + this.registerInput("position", NodeParticleBlockConnectionPointTypes.Vector3, true); + this.registerInput("velocity", NodeParticleBlockConnectionPointTypes.Vector3, true); + this.registerInput("color", NodeParticleBlockConnectionPointTypes.Color4, true); + this.registerInput("scaling", NodeParticleBlockConnectionPointTypes.Vector3, true); + this.registerInput("rotation", NodeParticleBlockConnectionPointTypes.Vector3, true); + + this.registerOutput("initData", NodeParticleBlockConnectionPointTypes.System); + } + + public override getClassName() { + return "SPSInitBlock"; + } + + public get initData(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + public get position(): NodeParticleConnectionPoint { + return this._inputs[0]; + } + + public get velocity(): NodeParticleConnectionPoint { + return this._inputs[1]; + } + + public get color(): NodeParticleConnectionPoint { + return this._inputs[2]; + } + + public get scaling(): NodeParticleConnectionPoint { + return this._inputs[3]; + } + + public get rotation(): NodeParticleConnectionPoint { + return this._inputs[4]; + } + + public override _build(state: NodeParticleBuildState) { + const initData = {} as ISPSUpdateData; + if (this.position.isConnected) { + initData.position = () => { + return this.position.getConnectedValue(state); + }; + } + if (this.velocity.isConnected) { + initData.velocity = () => { + return this.velocity.getConnectedValue(state); + }; + } + if (this.color.isConnected) { + initData.color = () => { + return this.color.getConnectedValue(state); + }; + } + if (this.scaling.isConnected) { + initData.scaling = () => { + return this.scaling.getConnectedValue(state); + }; + } + if (this.rotation.isConnected) { + initData.rotation = () => { + return this.rotation.getConnectedValue(state); + }; + } + + this.initData._storedValue = initData; + } + + public override serialize(): any { + const serializationObject = super.serialize(); + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + } +} + +RegisterClass("BABYLON.SPSInitBlock", SPSInitBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSMeshShapeType.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSMeshShapeType.ts new file mode 100644 index 00000000000..242d6e22f2e --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSMeshShapeType.ts @@ -0,0 +1,10 @@ +/** + * Mesh shape types for SPS + */ +export enum SPSMeshShapeType { + Box = 0, + Sphere = 1, + Cylinder = 2, + Plane = 3, + Custom = 4, +} diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSMeshSourceBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSMeshSourceBlock.ts new file mode 100644 index 00000000000..fd4e56d9a5e --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSMeshSourceBlock.ts @@ -0,0 +1,130 @@ +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; +import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; +import { editableInPropertyPage, PropertyTypeForEdition } from "core/Decorators/nodeDecorator"; +import { CreateBox } from "core/Meshes/Builders/boxBuilder"; +import { CreateSphere } from "core/Meshes/Builders/sphereBuilder"; +import { CreateCylinder } from "core/Meshes/Builders/cylinderBuilder"; +import { CreatePlane } from "core/Meshes/Builders/planeBuilder"; +import { SPSMeshShapeType } from "./SPSMeshShapeType"; +import { Mesh } from "../../../../Meshes"; + +/** + * Block used to provide mesh source for SPS + */ +export class SPSMeshSourceBlock extends NodeParticleBlock { + private _mesh: Mesh | null = null; + private _disposeHandlerAdded = false; + + @editableInPropertyPage("Shape Type", PropertyTypeForEdition.List, "ADVANCED", { + notifiers: { rebuild: true }, + embedded: true, + options: [ + { label: "Box", value: SPSMeshShapeType.Box }, + { label: "Sphere", value: SPSMeshShapeType.Sphere }, + { label: "Cylinder", value: SPSMeshShapeType.Cylinder }, + { label: "Plane", value: SPSMeshShapeType.Plane }, + { label: "Custom", value: SPSMeshShapeType.Custom }, + ], + }) + public shapeType = SPSMeshShapeType.Box; + + @editableInPropertyPage("Size", PropertyTypeForEdition.Float, "ADVANCED", { + embedded: true, + min: 0.01, + }) + public size = 1; + + @editableInPropertyPage("Segments", PropertyTypeForEdition.Int, "ADVANCED", { + embedded: true, + min: 1, + }) + public segments = 16; + + public constructor(name: string) { + super(name); + + this.registerInput("customMesh", NodeParticleBlockConnectionPointTypes.Mesh, true); + this.registerOutput("mesh", NodeParticleBlockConnectionPointTypes.Mesh); + } + + public override getClassName() { + return "SPSMeshSourceBlock"; + } + + public get customMesh(): NodeParticleConnectionPoint { + return this._inputs[0]; + } + + public get mesh(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + public override _build(state: NodeParticleBuildState) { + if (this._mesh) { + this._mesh.dispose(); + this._mesh = null; + } + if (this.shapeType === SPSMeshShapeType.Custom) { + if (this.customMesh.isConnected) { + const customMesh = this.customMesh.getConnectedValue(state); + if (customMesh) { + this._mesh = customMesh; + } else { + this._mesh = CreateBox("sps_mesh_source", { size: this.size }, state.scene); + } + } else { + this._mesh = CreateBox("sps_mesh_source", { size: this.size }, state.scene); + } + } else { + switch (this.shapeType) { + case SPSMeshShapeType.Box: + this._mesh = CreateBox("sps_mesh_source", { size: this.size }, state.scene); + break; + case SPSMeshShapeType.Sphere: + this._mesh = CreateSphere("sps_mesh_source", { diameter: this.size, segments: this.segments }, state.scene); + break; + case SPSMeshShapeType.Cylinder: + this._mesh = CreateCylinder("sps_mesh_source", { height: this.size, diameter: this.size, tessellation: this.segments }, state.scene); + break; + case SPSMeshShapeType.Plane: + this._mesh = CreatePlane("sps_mesh_source", { size: this.size }, state.scene); + break; + default: + this._mesh = CreateBox("sps_mesh_source", { size: this.size }, state.scene); + break; + } + } + if (this._mesh) { + this._mesh.isVisible = false; + } + + if (!this._disposeHandlerAdded) { + this.onDisposeObservable.addOnce(() => { + this._mesh?.dispose(); + this._mesh = null; + }); + this._disposeHandlerAdded = true; + } + this.mesh._storedValue = this._mesh; + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.shapeType = this.shapeType; + serializationObject.size = this.size; + serializationObject.segments = this.segments; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.shapeType = serializationObject.shapeType || SPSMeshShapeType.Box; + this.size = serializationObject.size || 1; + this.segments = serializationObject.segments || 16; + } +} + +RegisterClass("BABYLON.SPSMeshSourceBlock", SPSMeshSourceBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSParticleConfigBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSParticleConfigBlock.ts new file mode 100644 index 00000000000..d585cbd1258 --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSParticleConfigBlock.ts @@ -0,0 +1,72 @@ +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; +import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; +import type { ISPSParticleConfigData } from "./ISPSData"; + +/** + * Block used to configure SPS particle parameters (mesh, count, material, initBlock, updateBlock) + */ +export class SPSParticleConfigBlock extends NodeParticleBlock { + public constructor(name: string) { + super(name); + + this.registerInput("mesh", NodeParticleBlockConnectionPointTypes.Mesh); + this.registerInput("count", NodeParticleBlockConnectionPointTypes.Int, true, 1); + this.registerInput("material", NodeParticleBlockConnectionPointTypes.Material, true); + this.registerInput("initBlock", NodeParticleBlockConnectionPointTypes.System, true); + this.registerInput("updateBlock", NodeParticleBlockConnectionPointTypes.System, true); + + this.registerOutput("particleConfig", NodeParticleBlockConnectionPointTypes.SolidParticle); + } + + public override getClassName() { + return "SPSParticleConfigBlock"; + } + + public get mesh(): NodeParticleConnectionPoint { + return this._inputs[0]; + } + + public get count(): NodeParticleConnectionPoint { + return this._inputs[1]; + } + + public get material(): NodeParticleConnectionPoint { + return this._inputs[2]; + } + + public get initBlock(): NodeParticleConnectionPoint { + return this._inputs[3]; + } + + public get updateBlock(): NodeParticleConnectionPoint { + return this._inputs[4]; + } + + public get particleConfig(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + public override _build(state: NodeParticleBuildState) { + const mesh = this.mesh.getConnectedValue(state); + const count = (this.count.getConnectedValue(state) as number) || 1; + const material = this.material.getConnectedValue(state); + + const initBlock = this.initBlock.isConnected ? this.initBlock.getConnectedValue(state) : null; + const updateBlock = this.updateBlock.isConnected ? this.updateBlock.getConnectedValue(state) : null; + + const particleConfig: ISPSParticleConfigData = { + mesh, + count, + material, + initBlock, + updateBlock, + }; + + this.particleConfig._storedValue = particleConfig; + } +} + +RegisterClass("BABYLON.SPSParticleConfigBlock", SPSParticleConfigBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSSystemBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSSystemBlock.ts new file mode 100644 index 00000000000..4d0a8e4340d --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSSystemBlock.ts @@ -0,0 +1,74 @@ +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; +import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; +import { editableInPropertyPage, PropertyTypeForEdition } from "core/Decorators/nodeDecorator"; +import { SolidParticleSystem } from "core/Particles/solidParticleSystem"; + +/** + * Block used to create SolidParticleSystem and collect all Create blocks + */ +export class SPSSystemBlock extends NodeParticleBlock { + private static _IdCounter = 0; + + @editableInPropertyPage("Billboard", PropertyTypeForEdition.Boolean, "ADVANCED", { + embedded: true, + notifiers: { rebuild: true }, + }) + public billboard = false; + + public _internalId = SPSSystemBlock._IdCounter++; + + public constructor(name: string) { + super(name); + this._isSystem = true; + this.registerInput("solidParticleSystem", NodeParticleBlockConnectionPointTypes.SolidParticleSystem); + this.registerOutput("solidParticleSystem", NodeParticleBlockConnectionPointTypes.SolidParticleSystem); + } + + public override getClassName() { + return "SPSSystemBlock"; + } + + public get solidParticleSystem(): NodeParticleConnectionPoint { + return this._inputs[0]; + } + + public get system(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + public createSystem(state: NodeParticleBuildState): SolidParticleSystem { + state.buildId = ++this._buildId; + + this.build(state); + + const solidParticleSystem = this.solidParticleSystem.getConnectedValue(state) as SolidParticleSystem; + + if (!solidParticleSystem) { + throw new Error("No SolidParticleSystem connected to SPSSystemBlock"); + } + + solidParticleSystem.billboard = this.billboard; + solidParticleSystem.name = this.name; + + this.onDisposeObservable.addOnce(() => { + solidParticleSystem.dispose(); + }); + return solidParticleSystem; + } + + public override serialize(): any { + const serializationObject = super.serialize(); + serializationObject.billboard = this.billboard; + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + this.billboard = !!serializationObject.billboard; + } +} + +RegisterClass("BABYLON.SPSSystemBlock", SPSSystemBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSUpdateBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSUpdateBlock.ts new file mode 100644 index 00000000000..4fac6e6af07 --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/SPSUpdateBlock.ts @@ -0,0 +1,92 @@ +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; +import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; +import type { ISPSUpdateData } from "./ISPSData"; + +/** + * Block used to generate update function for SPS particles + */ +export class SPSUpdateBlock extends NodeParticleBlock { + public constructor(name: string) { + super(name); + + this.registerInput("position", NodeParticleBlockConnectionPointTypes.Vector3, true); + this.registerInput("velocity", NodeParticleBlockConnectionPointTypes.Vector3, true); + this.registerInput("color", NodeParticleBlockConnectionPointTypes.Color4, true); + this.registerInput("scaling", NodeParticleBlockConnectionPointTypes.Vector3, true); + this.registerInput("rotation", NodeParticleBlockConnectionPointTypes.Vector3, true); + + this.registerOutput("updateData", NodeParticleBlockConnectionPointTypes.System); + } + + public override getClassName() { + return "SPSUpdateBlock"; + } + + public get position(): NodeParticleConnectionPoint { + return this._inputs[0]; + } + + public get velocity(): NodeParticleConnectionPoint { + return this._inputs[1]; + } + + public get color(): NodeParticleConnectionPoint { + return this._inputs[2]; + } + + public get scaling(): NodeParticleConnectionPoint { + return this._inputs[3]; + } + + public get rotation(): NodeParticleConnectionPoint { + return this._inputs[4]; + } + + public get updateData(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + public override _build(state: NodeParticleBuildState) { + const updateData: ISPSUpdateData = {} as ISPSUpdateData; + if (this.position.isConnected) { + updateData.position = () => { + return this.position.getConnectedValue(state); + }; + } + if (this.velocity.isConnected) { + updateData.velocity = () => { + return this.velocity.getConnectedValue(state); + }; + } + if (this.color.isConnected) { + updateData.color = () => { + return this.color.getConnectedValue(state); + }; + } + if (this.scaling.isConnected) { + updateData.scaling = () => { + return this.scaling.getConnectedValue(state); + }; + } + if (this.rotation.isConnected) { + updateData.rotation = () => { + return this.rotation.getConnectedValue(state); + }; + } + this.updateData._storedValue = updateData; + } + + public override serialize(): any { + const serializationObject = super.serialize(); + return serializationObject; + } + + public override _deserialize(serializationObject: any) { + super._deserialize(serializationObject); + } +} + +RegisterClass("BABYLON.SPSUpdateBlock", SPSUpdateBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/index.ts b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/index.ts new file mode 100644 index 00000000000..66eb47cd817 --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/SolidParticle/index.ts @@ -0,0 +1,10 @@ +export * from "./ISPSData"; +export * from "./SPSMeshShapeType"; +export * from "./SPSMeshSourceBlock"; +export * from "./SPSParticleConfigBlock"; +export * from "./SPSSystemBlock"; +export * from "./SPSUpdateBlock"; +export * from "./SPSInitBlock"; +export * from "./SPSCreateBlock"; +export * from "./ParticlePropsSetBlock"; +export * from "./ParticlePropsGetBlock"; diff --git a/packages/dev/core/src/Particles/Node/Blocks/index.ts b/packages/dev/core/src/Particles/Node/Blocks/index.ts index 3e086086f35..c691fcccbf1 100644 --- a/packages/dev/core/src/Particles/Node/Blocks/index.ts +++ b/packages/dev/core/src/Particles/Node/Blocks/index.ts @@ -32,3 +32,4 @@ export * from "./Triggers/particleTriggerBlock"; export * from "./particleLocalVariableBlock"; export * from "./particleVectorLengthBlock"; export * from "./particleFresnelBlock"; +export * from "./SolidParticle"; diff --git a/packages/dev/core/src/Particles/Node/Enums/nodeParticleBlockConnectionPointTypes.ts b/packages/dev/core/src/Particles/Node/Enums/nodeParticleBlockConnectionPointTypes.ts index 87d59a00192..88d31930cd9 100644 --- a/packages/dev/core/src/Particles/Node/Enums/nodeParticleBlockConnectionPointTypes.ts +++ b/packages/dev/core/src/Particles/Node/Enums/nodeParticleBlockConnectionPointTypes.ts @@ -28,12 +28,28 @@ export enum NodeParticleBlockConnectionPointTypes { Color4Gradient = 0x0800, /** System */ System = 0x1000, + /** SPS - Solid Particle System */ + SolidParticleSystem = 0x2000, + /** SolidParticle */ + SolidParticle = 0x4000, + /** Mesh */ + Mesh = 0x8000, + /** Material */ + Material = 0x10000, + /** Camera */ + Camera = 0x20000, + /** Function */ + Function = 0x40000, + /** Vector4 */ + Vector4 = 0x80000, + /** Boolean */ + Boolean = 0x100000, /** Detect type based on connection */ - AutoDetect = 0x2000, + AutoDetect = 0x200000, /** Output type that will be defined by input type */ - BasedOnInput = 0x4000, + BasedOnInput = 0x400000, /** Undefined */ - Undefined = 0x8000, + Undefined = 0x800000, /** Bitmask of all types */ - All = 0xffff, + All = 0xffffff, } diff --git a/packages/dev/core/src/Particles/Node/nodeParticleBlock.ts b/packages/dev/core/src/Particles/Node/nodeParticleBlock.ts index 719e58614b9..d4d24c45b8b 100644 --- a/packages/dev/core/src/Particles/Node/nodeParticleBlock.ts +++ b/packages/dev/core/src/Particles/Node/nodeParticleBlock.ts @@ -236,6 +236,28 @@ export class NodeParticleBlock { return this; } + /** + * Unregister an input. Used for dynamic input management + * @param name defines the connection point name to remove + * @returns the current block + */ + public unregisterInput(name: string) { + const index = this._inputs.findIndex((input) => input.name === name); + if (index !== -1) { + const point = this._inputs[index]; + + if (point.isConnected) { + point.disconnectFrom(point.connectedPoint!); + } + + this._inputs.splice(index, 1); + + this.onInputChangedObservable.notifyObservers(point); + } + + return this; + } + /** * Register a new output. Must be called inside a block constructor * @param name defines the connection point name diff --git a/packages/dev/core/src/Particles/Node/nodeParticleBuildState.ts b/packages/dev/core/src/Particles/Node/nodeParticleBuildState.ts index 88b49990200..fbcc0b43375 100644 --- a/packages/dev/core/src/Particles/Node/nodeParticleBuildState.ts +++ b/packages/dev/core/src/Particles/Node/nodeParticleBuildState.ts @@ -1,14 +1,16 @@ import type { Scene } from "core/scene"; import type { NodeParticleConnectionPoint } from "./nodeParticleBlockConnectionPoint"; import { NodeParticleContextualSources } from "./Enums/nodeParticleContextualSources"; -import type { Particle } from "../particle"; +import { Particle } from "../particle"; import type { Nullable } from "core/types"; import { NodeParticleBlockConnectionPointTypes } from "./Enums/nodeParticleBlockConnectionPointTypes"; import { Vector2, Vector3 } from "core/Maths/math.vector"; -import type { ThinParticleSystem } from "../thinParticleSystem"; +import { SolidParticle } from "../solidParticle"; +import { ThinParticleSystem } from "../thinParticleSystem"; import { Color4 } from "core/Maths/math.color"; import { NodeParticleSystemSources } from "./Enums/nodeParticleSystemSources"; import type { AbstractMesh } from "core/Meshes/abstractMesh"; +import { SolidParticleSystem } from "../solidParticleSystem"; /** * Class used to store node based geometry build state @@ -36,12 +38,18 @@ export class NodeParticleBuildState { /** * Gets or sets the particle context for contextual data */ - public particleContext: Nullable = null; + public particleContext: Nullable = null; /** * Gets or sets the system context for contextual data + * Can be either ThinParticleSystem or SolidParticleSystem */ - public systemContext: Nullable = null; + public systemContext: Nullable = null; + + /** + * Gets or sets the delta time for physics calculations + */ + public deltaTime: number = 0.016; // 60 FPS default /** * Gets or sets the index of the gradient to use @@ -99,58 +107,164 @@ export class NodeParticleBuildState { return null; } + /** + * Type guard to check if particle context is a Particle and system context is ThinParticleSystem + */ + private isParticleWithThinSystem(): this is this & { particleContext: Particle; systemContext: ThinParticleSystem } { + return this.particleContext instanceof Particle && this.systemContext instanceof ThinParticleSystem; + } + + /** + * Type guard to check if particle context is a Particle + */ + private isParticle(): this is this & { particleContext: Particle } { + return this.particleContext instanceof Particle; + } + + /** + * Type guard to check if particle context is a SolidParticle + */ + private isSolidParticle(): this is this & { particleContext: SolidParticle } { + return this.particleContext instanceof SolidParticle; + } + + /** + * Type guard to check if system context is a ThinParticleSystem + */ + private isThinParticleSystem(): this is this & { systemContext: ThinParticleSystem } { + return this.systemContext instanceof ThinParticleSystem; + } + + /** + * Type guard to check if system context is a SolidParticleSystem + */ + private isSolidParticleSystem(): this is this & { systemContext: SolidParticleSystem } { + return this.systemContext instanceof SolidParticleSystem; + } + /** * Gets the value associated with a contextual source * @param source Source of the contextual value * @returns the value associated with the source */ public getContextualValue(source: NodeParticleContextualSources) { - if (!this.particleContext || !this.systemContext) { + if (!this.particleContext) { return null; } switch (source) { + // Common properties available on both Particle and SolidParticle case NodeParticleContextualSources.Position: return this.particleContext.position; + case NodeParticleContextualSources.Color: + return this.particleContext.color; + case NodeParticleContextualSources.Scale: + if (this.isParticle()) { + return this.particleContext.scale; + } + if (this.isSolidParticle()) { + // Convert Vector3 scaling to Vector2 for compatibility + const scaling = this.particleContext.scaling; + return new Vector2(scaling.x, scaling.y); + } + return null; + case NodeParticleContextualSources.Direction: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.direction; + case NodeParticleContextualSources.ScaledDirection: + if (!this.isParticleWithThinSystem()) { + return null; + } this.particleContext.direction.scaleToRef(this.systemContext._directionScale, this.systemContext._scaledDirection); return this.systemContext._scaledDirection; - case NodeParticleContextualSources.Color: - return this.particleContext.color; + case NodeParticleContextualSources.InitialColor: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.initialColor; + case NodeParticleContextualSources.ColorDead: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.colorDead; + case NodeParticleContextualSources.Age: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.age; + case NodeParticleContextualSources.Lifetime: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.lifeTime; + case NodeParticleContextualSources.Angle: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.angle; - case NodeParticleContextualSources.Scale: - return this.particleContext.scale; + case NodeParticleContextualSources.AgeGradient: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.age / this.particleContext.lifeTime; - case NodeParticleContextualSources.SpriteCellEnd: - return this.systemContext.endSpriteCellID; + case NodeParticleContextualSources.SpriteCellIndex: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.cellIndex; - case NodeParticleContextualSources.SpriteCellStart: - return this.systemContext.startSpriteCellID; + case NodeParticleContextualSources.InitialDirection: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext._initialDirection; + case NodeParticleContextualSources.ColorStep: + if (!this.isParticleWithThinSystem()) { + return null; + } return this.particleContext.colorStep; + case NodeParticleContextualSources.ScaledColorStep: + if (!this.isParticleWithThinSystem()) { + return null; + } this.particleContext.colorStep.scaleToRef(this.systemContext._scaledUpdateSpeed, this.systemContext._scaledColorStep); return this.systemContext._scaledColorStep; + case NodeParticleContextualSources.LocalPositionUpdated: + if (!this.isParticleWithThinSystem()) { + return null; + } this.particleContext.direction.scaleToRef(this.systemContext._directionScale, this.systemContext._scaledDirection); - this.particleContext._localPosition!.addInPlace(this.systemContext._scaledDirection); - Vector3.TransformCoordinatesToRef(this.particleContext._localPosition!, this.systemContext._emitterWorldMatrix, this.particleContext.position); + if (this.particleContext._localPosition) { + this.particleContext._localPosition.addInPlace(this.systemContext._scaledDirection); + Vector3.TransformCoordinatesToRef(this.particleContext._localPosition, this.systemContext._emitterWorldMatrix, this.particleContext.position); + } return this.particleContext.position; + + case NodeParticleContextualSources.SpriteCellEnd: + if (!this.isThinParticleSystem()) { + return null; + } + return this.systemContext.endSpriteCellID; + + case NodeParticleContextualSources.SpriteCellStart: + if (!this.isThinParticleSystem()) { + return null; + } + return this.systemContext.startSpriteCellID; } return null; @@ -160,7 +274,7 @@ export class NodeParticleBuildState { * Gets the emitter world matrix */ public get emitterWorldMatrix() { - if (!this.systemContext) { + if (!this.isThinParticleSystem()) { return null; } return this.systemContext._emitterWorldMatrix; @@ -170,7 +284,7 @@ export class NodeParticleBuildState { * Gets the emitter inverse world matrix */ public get emitterInverseWorldMatrix() { - if (!this.systemContext) { + if (!this.isThinParticleSystem()) { return null; } return this.systemContext._emitterInverseWorldMatrix; @@ -184,6 +298,14 @@ export class NodeParticleBuildState { return null; } + if (this.isSolidParticleSystem()) { + return this.systemContext.mesh?.absolutePosition || Vector3.Zero(); + } + + if (!this.isThinParticleSystem()) { + return null; + } + if (!this.systemContext.emitter) { return null; } @@ -192,7 +314,27 @@ export class NodeParticleBuildState { return this.systemContext.emitter; } - return (this.systemContext.emitter).absolutePosition; + return (this.systemContext.emitter as AbstractMesh).absolutePosition; + } + + /** + * Gets the actual frame number + */ + public get actualFrame() { + if (this.isThinParticleSystem()) { + return this.systemContext._actualFrame; + } + return this.scene.getFrameId() || 0; + } + + /** + * Gets the delta time + */ + public get delta() { + if (this.isThinParticleSystem()) { + return this.systemContext._scaledUpdateSpeed; + } + return this.scene.getEngine().getDeltaTime() || this.deltaTime; } /** @@ -207,9 +349,9 @@ export class NodeParticleBuildState { switch (source) { case NodeParticleSystemSources.Time: - return this.systemContext._actualFrame; + return this.actualFrame; case NodeParticleSystemSources.Delta: - return this.systemContext._scaledUpdateSpeed; + return this.delta; case NodeParticleSystemSources.Emitter: return this.emitterPosition; case NodeParticleSystemSources.CameraPosition: diff --git a/packages/dev/core/src/Particles/Node/nodeParticleSystemSet.ts b/packages/dev/core/src/Particles/Node/nodeParticleSystemSet.ts index eb05f23fc1b..5ae8dcfd847 100644 --- a/packages/dev/core/src/Particles/Node/nodeParticleSystemSet.ts +++ b/packages/dev/core/src/Particles/Node/nodeParticleSystemSet.ts @@ -20,8 +20,26 @@ import type { ParticleTeleportOutBlock } from "./Blocks/Teleport/particleTelepor import type { ParticleTeleportInBlock } from "./Blocks/Teleport/particleTeleportInBlock"; import { BoxShapeBlock } from "./Blocks/Emitters/boxShapeBlock"; import { CreateParticleBlock } from "./Blocks/Emitters/createParticleBlock"; -import type { Color4 } from "core/Maths/math.color"; import type { Nullable } from "../../types"; +import { Color4 } from "core/Maths/math.color"; +import { Vector2, Vector3 } from "core/Maths/math.vector"; +import { + SPSParticleConfigBlock, + SPSInitBlock, + SPSMeshShapeType, + SPSMeshSourceBlock, + SPSSystemBlock, + SPSCreateBlock, + SPSUpdateBlock, + ParticlePropsSetBlock, + ParticlePropsGetBlock, +} from "./Blocks"; +import { ParticleSystem } from ".."; +import { ParticleRandomBlock, ParticleRandomBlockLocks } from "./Blocks/particleRandomBlock"; +import { ParticleConverterBlock } from "./Blocks/particleConverterBlock"; +import { ParticleTrigonometryBlock, ParticleTrigonometryBlockOperations } from "./Blocks/particleTrigonometryBlock"; +import { NodeParticleSystemSources } from "./Enums/nodeParticleSystemSources"; +import { NodeParticleBlockConnectionPointTypes } from "./Enums/nodeParticleBlockConnectionPointTypes"; // declare NODEPARTICLEEDITOR namespace for compilation issue declare let NODEPARTICLEEDITOR: any; @@ -45,7 +63,7 @@ export interface INodeParticleEditorOptions { * PG: #ZT509U#1 */ export class NodeParticleSystemSet { - private _systemBlocks: SystemBlock[] = []; + private _systemBlocks: (SystemBlock | SPSSystemBlock)[] = []; private _buildId: number = 0; /** Define the Url to load node editor script */ @@ -90,7 +108,7 @@ export class NodeParticleSystemSet { /** * Gets the system blocks */ - public get systemBlocks(): SystemBlock[] { + public get systemBlocks(): (SystemBlock | SPSSystemBlock)[] { return this._systemBlocks; } @@ -269,13 +287,13 @@ export class NodeParticleSystemSet { state.verbose = verbose; const system = block.createSystem(state); - system._source = this; - system._blockReference = block._internalId; - + if (system instanceof ParticleSystem) { + system._source = this; + system._blockReference = block._internalId; + } + output.systems.push(system); // Errors state.emitErrors(); - - output.systems.push(system); } this.onBuildObservable.notifyObservers(this); @@ -336,6 +354,193 @@ export class NodeParticleSystemSet { this._systemBlocks.push(system); } + public setToDefaultSPS() { + this.clear(); + this.editorData = null; + + const spsSystem = new SPSSystemBlock("SPS System"); + spsSystem.billboard = false; + + const spsCreateBlock = new SPSCreateBlock("Create Particles System"); + spsCreateBlock.solidParticleSystem.connectTo(spsSystem.solidParticleSystem); + + const spsCreateTetra = new SPSParticleConfigBlock("Create Tetrahedron Particles"); + spsCreateTetra.count.value = 2000; + spsCreateTetra.particleConfig.connectTo(spsCreateBlock.particleConfig); + + const meshSourceTetra = new SPSMeshSourceBlock("Tetrahedron Mesh"); + meshSourceTetra.shapeType = SPSMeshShapeType.Box; + meshSourceTetra.size = 0.1; + meshSourceTetra.mesh.connectTo(spsCreateTetra.mesh); + + const spsInitTetra = new SPSInitBlock("Initialize Tetrahedron Particles"); + spsInitTetra.initData.connectTo(spsCreateTetra.initBlock); + + const randomXZMin = new ParticleInputBlock("Random XZ Min"); + randomXZMin.value = new Vector2(-10, -10); + const randomXZMax = new ParticleInputBlock("Random XZ Max"); + randomXZMax.value = new Vector2(10, 10); + const randomXZ = new ParticleRandomBlock("Random XZ"); + randomXZ.lockMode = ParticleRandomBlockLocks.PerParticle; + randomXZMin.output.connectTo(randomXZ.min); + randomXZMax.output.connectTo(randomXZ.max); + + const randomAngleMin = new ParticleInputBlock("Random Angle Min"); + randomAngleMin.value = -Math.PI; + const randomAngleMax = new ParticleInputBlock("Random Angle Max"); + randomAngleMax.value = Math.PI; + const randomAngle = new ParticleRandomBlock("Random Angle"); + randomAngle.lockMode = ParticleRandomBlockLocks.PerParticle; + randomAngleMin.output.connectTo(randomAngle.min); + randomAngleMax.output.connectTo(randomAngle.max); + + const randomRangeMin = new ParticleInputBlock("Random Range Min"); + randomRangeMin.value = 1; + const randomRangeMax = new ParticleInputBlock("Random Range Max"); + randomRangeMax.value = 5; + const randomRange = new ParticleRandomBlock("Random Range"); + randomRange.lockMode = ParticleRandomBlockLocks.PerParticle; + randomRangeMin.output.connectTo(randomRange.min); + randomRangeMax.output.connectTo(randomRange.max); + + const one = new ParticleInputBlock("One"); + one.value = 1; + const cosAngle = new ParticleTrigonometryBlock("Cos Angle"); + cosAngle.operation = ParticleTrigonometryBlockOperations.Cos; + // Store angle in props so we can reuse during update + const setAnglePropInit = new ParticlePropsSetBlock("Set Angle Prop Init"); + setAnglePropInit.propertyName = "angle"; + randomAngle.output.connectTo(setAnglePropInit.value); + setAnglePropInit.output.connectTo(cosAngle.input); + const addOne = new ParticleMathBlock("Add One"); + addOne.operation = ParticleMathBlockOperations.Add; + one.output.connectTo(addOne.left); + cosAngle.output.connectTo(addOne.right); + const multiplyRange = new ParticleMathBlock("Multiply Range"); + multiplyRange.operation = ParticleMathBlockOperations.Multiply; + const setRangePropInit = new ParticlePropsSetBlock("Set Range Prop Init"); + setRangePropInit.propertyName = "range"; + randomRange.output.connectTo(setRangePropInit.value); + setRangePropInit.output.connectTo(multiplyRange.left); + addOne.output.connectTo(multiplyRange.right); + + const extractXZ = new ParticleConverterBlock("Extract XZ"); + randomXZ.output.connectTo(extractXZ.xyIn); + const positionConverter = new ParticleConverterBlock("Position Converter"); + extractXZ.xOut.connectTo(positionConverter.xIn); + multiplyRange.output.connectTo(positionConverter.yIn); + extractXZ.yOut.connectTo(positionConverter.zIn); + positionConverter.xyzOut.connectTo(spsInitTetra.position); + + const randomRotMin = new ParticleInputBlock("Random Rot Min"); + randomRotMin.value = new Vector3(-Math.PI, -Math.PI, -Math.PI); + const randomRotMax = new ParticleInputBlock("Random Rot Max"); + randomRotMax.value = new Vector3(Math.PI, Math.PI, Math.PI); + const randomRot = new ParticleRandomBlock("Random Rotation"); + randomRot.lockMode = ParticleRandomBlockLocks.PerParticle; + randomRotMin.output.connectTo(randomRot.min); + randomRotMax.output.connectTo(randomRot.max); + randomRot.output.connectTo(spsInitTetra.rotation); + + const randomColorMin = new ParticleInputBlock("Random Color Min"); + randomColorMin.value = new Vector3(0, 0, 0); + const randomColorMax = new ParticleInputBlock("Random Color Max"); + randomColorMax.value = new Vector3(1, 1, 1); + const randomColorRGB = new ParticleRandomBlock("Random Color RGB"); + randomColorRGB.lockMode = ParticleRandomBlockLocks.PerParticle; + randomColorMin.output.connectTo(randomColorRGB.min); + randomColorMax.output.connectTo(randomColorRGB.max); + const colorAlpha = new ParticleInputBlock("Color Alpha"); + colorAlpha.value = 1; + const colorConverter = new ParticleConverterBlock("Color Converter"); + randomColorRGB.output.connectTo(colorConverter.xyzIn); + colorAlpha.output.connectTo(colorConverter.wIn); + colorConverter.colorOut.connectTo(spsInitTetra.color); + + // Create update block + const spsUpdateTetra = new SPSUpdateBlock("Update Tetrahedron Particles"); + spsUpdateTetra.updateData.connectTo(spsCreateTetra.updateBlock); + + // Get current position (X, Z stay the same, Y updates) + const currentPosition = new ParticleInputBlock("Current Position"); + currentPosition.contextualValue = NodeParticleContextualSources.Position; + + // Extract X and Z from current position + const extractPosition = new ParticleConverterBlock("Extract Position"); + currentPosition.output.connectTo(extractPosition.xyzIn); + + // Retrieve stored properties + const getAngleProp = new ParticlePropsGetBlock("Get Angle Prop"); + getAngleProp.propertyName = "angle"; + getAngleProp.type = NodeParticleBlockConnectionPointTypes.Float; + + const getRangeProp = new ParticlePropsGetBlock("Get Range Prop"); + getRangeProp.propertyName = "range"; + getRangeProp.type = NodeParticleBlockConnectionPointTypes.Float; + + // Accumulate angle using delta time to avoid relying on absolute frame id + const deltaBlock = new ParticleInputBlock("Delta Time"); + deltaBlock.systemSource = NodeParticleSystemSources.Delta; + + const milliToSecond = new ParticleInputBlock("Milli To Second"); + milliToSecond.value = 0.001; + + const deltaSeconds = new ParticleMathBlock("Delta Seconds"); + deltaSeconds.operation = ParticleMathBlockOperations.Multiply; + deltaBlock.output.connectTo(deltaSeconds.left); + milliToSecond.output.connectTo(deltaSeconds.right); + + const targetFps = new ParticleInputBlock("Target FPS"); + targetFps.value = 60; + + const normalizedDelta = new ParticleMathBlock("Normalized Delta"); + normalizedDelta.operation = ParticleMathBlockOperations.Multiply; + deltaSeconds.output.connectTo(normalizedDelta.left); + targetFps.output.connectTo(normalizedDelta.right); + + const speedPerFrame = new ParticleInputBlock("Speed Per Frame"); + speedPerFrame.value = Math.PI / 100; + + const scaledIncrement = new ParticleMathBlock("Scaled Increment"); + scaledIncrement.operation = ParticleMathBlockOperations.Multiply; + speedPerFrame.output.connectTo(scaledIncrement.left); + normalizedDelta.output.connectTo(scaledIncrement.right); + + const accumulateAngle = new ParticleMathBlock("Accumulate Angle"); + accumulateAngle.operation = ParticleMathBlockOperations.Add; + getAngleProp.output.connectTo(accumulateAngle.left); + scaledIncrement.output.connectTo(accumulateAngle.right); + + const setAnglePropUpdate = new ParticlePropsSetBlock("Set Angle Prop Update"); + setAnglePropUpdate.propertyName = "angle"; + setAnglePropUpdate.type = NodeParticleBlockConnectionPointTypes.Float; + accumulateAngle.output.connectTo(setAnglePropUpdate.value); + + // Calculate new Y position: range * (1 + cos(angle)) + const oneUpdate = new ParticleInputBlock("One Update"); + oneUpdate.value = 1; + const cosUpdatedAngle = new ParticleTrigonometryBlock("Cos Updated Angle"); + cosUpdatedAngle.operation = ParticleTrigonometryBlockOperations.Cos; + setAnglePropUpdate.output.connectTo(cosUpdatedAngle.input); + const addOneUpdate = new ParticleMathBlock("Add One Update"); + addOneUpdate.operation = ParticleMathBlockOperations.Add; + oneUpdate.output.connectTo(addOneUpdate.left); + cosUpdatedAngle.output.connectTo(addOneUpdate.right); + const multiplyRangeUpdate = new ParticleMathBlock("Multiply Range Update"); + multiplyRangeUpdate.operation = ParticleMathBlockOperations.Multiply; + getRangeProp.output.connectTo(multiplyRangeUpdate.left); + addOneUpdate.output.connectTo(multiplyRangeUpdate.right); + + // Combine X (from current position), Y (new), Z (from current position) + const updatePositionConverter = new ParticleConverterBlock("Update Position Converter"); + extractPosition.xOut.connectTo(updatePositionConverter.xIn); + multiplyRangeUpdate.output.connectTo(updatePositionConverter.yIn); + extractPosition.zOut.connectTo(updatePositionConverter.zIn); + updatePositionConverter.xyzOut.connectTo(spsUpdateTetra.position); + + this._systemBlocks.push(spsSystem); + } + /** * Remove a block from the current system set * @param block defines the block to remove diff --git a/packages/dev/core/src/Particles/particleSystemSet.ts b/packages/dev/core/src/Particles/particleSystemSet.ts index 9537fdbbc51..75b52830ad7 100644 --- a/packages/dev/core/src/Particles/particleSystemSet.ts +++ b/packages/dev/core/src/Particles/particleSystemSet.ts @@ -9,6 +9,7 @@ import { ParticleSystem } from "../Particles/particleSystem"; import type { Scene, IDisposable } from "../scene"; import { StandardMaterial } from "../Materials/standardMaterial"; import type { Vector3 } from "../Maths/math.vector"; +import type { SolidParticleSystem } from "./solidParticleSystem"; /** Internal class used to store shapes for emitters */ class ParticleSystemSetEmitterCreationOptions { @@ -34,7 +35,7 @@ export class ParticleSystemSet implements IDisposable { /** * Gets the particle system list */ - public systems: IParticleSystem[] = []; + public systems: (IParticleSystem | SolidParticleSystem)[] = []; /** * Gets or sets the emitter node used with this set @@ -52,7 +53,9 @@ export class ParticleSystemSet implements IDisposable { } for (const system of this.systems) { - system.emitter = value; + if (system instanceof ParticleSystem) { + system.emitter = value; + } } this._emitterNode = value; @@ -90,7 +93,9 @@ export class ParticleSystemSet implements IDisposable { emitterMesh.material = material; for (const system of this.systems) { - system.emitter = emitterMesh; + if (system instanceof ParticleSystem) { + system.emitter = emitterMesh; + } } this._emitterNode = emitterMesh; @@ -103,7 +108,9 @@ export class ParticleSystemSet implements IDisposable { public start(emitter?: AbstractMesh): void { for (const system of this.systems) { if (emitter) { - system.emitter = emitter; + if (system instanceof ParticleSystem) { + system.emitter = emitter; + } } system.start(); } @@ -137,7 +144,7 @@ export class ParticleSystemSet implements IDisposable { result.systems = []; for (const system of this.systems) { - if (!system.doNotSerialize) { + if (system instanceof ParticleSystem && !system.doNotSerialize) { result.systems.push(system.serialize(serializeTexture)); } } diff --git a/packages/dev/core/src/Particles/solidParticleSystem.ts b/packages/dev/core/src/Particles/solidParticleSystem.ts index fa9a0a8a10b..0b002d34fbe 100644 --- a/packages/dev/core/src/Particles/solidParticleSystem.ts +++ b/packages/dev/core/src/Particles/solidParticleSystem.ts @@ -17,6 +17,7 @@ import { StandardMaterial } from "../Materials/standardMaterial"; import { MultiMaterial } from "../Materials/multiMaterial"; import type { PickingInfo } from "../Collisions/pickingInfo"; import type { PBRMaterial } from "../Materials/PBR/pbrMaterial"; +import type { Observer } from "../Misc/observable"; /** * The SPS is a single updatable mesh. The solid particles are simply separate parts or faces of this big mesh. @@ -152,7 +153,7 @@ export class SolidParticleSystem implements IDisposable { protected _autoUpdateSubMeshes: boolean = false; protected _tmpVertex: SolidParticleVertex; protected _recomputeInvisibles: boolean = false; - + protected _onBeforeRenderObserver: Nullable> = null; /** * Creates a SPS (Solid Particle System) object. * @param name (String) is the SPS name, this will be the underlying mesh name. @@ -1538,7 +1539,13 @@ export class SolidParticleSystem implements IDisposable { * Disposes the SPS. */ public dispose(): void { - this.mesh.dispose(); + if (this._onBeforeRenderObserver) { + this._scene.onBeforeRenderObservable.remove(this._onBeforeRenderObserver); + this._onBeforeRenderObserver = null; + } + if (this.mesh) { + this.mesh.dispose(); + } this.vars = null; // drop references to internal big arrays for the GC (this._positions) = null; @@ -1998,6 +2005,15 @@ export class SolidParticleSystem implements IDisposable { */ public initParticles(): void {} + public start(): void { + this.buildMesh(); + this.initParticles(); + this.setParticles(); + this._onBeforeRenderObserver = this._scene.onBeforeRenderObservable.add(() => { + this.setParticles(); + }); + } + /** * This function does nothing. It may be overwritten to recycle a particle. * The SPS doesn't call this function, you may have to call it by your own. diff --git a/packages/tools/nodeParticleEditor/src/blockTools.ts b/packages/tools/nodeParticleEditor/src/blockTools.ts index 735902ecff8..6325534a0b4 100644 --- a/packages/tools/nodeParticleEditor/src/blockTools.ts +++ b/packages/tools/nodeParticleEditor/src/blockTools.ts @@ -42,6 +42,7 @@ import { BasicColorUpdateBlock } from "core/Particles/Node/Blocks/Update/basicCo import { ParticleLocalVariableBlock } from "core/Particles/Node/Blocks/particleLocalVariableBlock"; import { ParticleVectorLengthBlock } from "core/Particles/Node/Blocks/particleVectorLengthBlock"; import { ParticleFresnelBlock } from "core/Particles/Node/Blocks/particleFresnelBlock"; +import { SPSMeshSourceBlock, SPSSystemBlock, SPSCreateBlock, SPSInitBlock, SPSUpdateBlock, SPSParticleConfigBlock } from "core/Particles/Node/Blocks"; /** * Static class for BlockTools @@ -150,6 +151,18 @@ export class BlockTools { return new UpdateAttractorBlock("Update attractor"); case "SystemBlock": return new SystemBlock("System"); + case "SPSMeshSourceBlock": + return new SPSMeshSourceBlock("SPS Mesh Source"); + case "SPSParticleConfigBlock": + return new SPSParticleConfigBlock("SPS Particle Config"); + case "SPSSystemBlock": + return new SPSSystemBlock("SPS System"); + case "SPSCreateBlock": + return new SPSCreateBlock("SPS Create"); + case "SPSInitBlock": + return new SPSInitBlock("SPS Init"); + case "SPSUpdateBlock": + return new SPSUpdateBlock("SPS Update"); case "TextureBlock": return new ParticleTextureSourceBlock("Texture"); case "BoxShapeBlock": @@ -453,6 +466,24 @@ export class BlockTools { case NodeParticleBlockConnectionPointTypes.System: color = "#f20a2e"; break; + case NodeParticleBlockConnectionPointTypes.SolidParticleSystem: + color = "#8b4513"; + break; + case NodeParticleBlockConnectionPointTypes.SolidParticle: + color = "#2e8b57"; + break; + case NodeParticleBlockConnectionPointTypes.Mesh: + color = "#4682b4"; + break; + case NodeParticleBlockConnectionPointTypes.Material: + color = "#daa520"; + break; + case NodeParticleBlockConnectionPointTypes.Camera: + color = "#9370db"; + break; + case NodeParticleBlockConnectionPointTypes.Function: + color = "#ff6347"; + break; } return color; @@ -472,6 +503,18 @@ export class BlockTools { return NodeParticleBlockConnectionPointTypes.Color4; case "Matrix": return NodeParticleBlockConnectionPointTypes.Matrix; + case "SolidParticleSystem": + return NodeParticleBlockConnectionPointTypes.SolidParticleSystem; + case "SolidParticle": + return NodeParticleBlockConnectionPointTypes.SolidParticle; + case "Mesh": + return NodeParticleBlockConnectionPointTypes.Mesh; + case "Material": + return NodeParticleBlockConnectionPointTypes.Material; + case "Camera": + return NodeParticleBlockConnectionPointTypes.Camera; + case "Function": + return NodeParticleBlockConnectionPointTypes.Function; } return NodeParticleBlockConnectionPointTypes.AutoDetect; @@ -491,6 +534,18 @@ export class BlockTools { return "Color4"; case NodeParticleBlockConnectionPointTypes.Matrix: return "Matrix"; + case NodeParticleBlockConnectionPointTypes.SolidParticleSystem: + return "SolidParticleSystem"; + case NodeParticleBlockConnectionPointTypes.SolidParticle: + return "SolidParticle"; + case NodeParticleBlockConnectionPointTypes.Mesh: + return "Mesh"; + case NodeParticleBlockConnectionPointTypes.Material: + return "Material"; + case NodeParticleBlockConnectionPointTypes.Camera: + return "Camera"; + case NodeParticleBlockConnectionPointTypes.Function: + return "Function"; } return ""; diff --git a/packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx b/packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx index 61452afcc06..f17df445a13 100644 --- a/packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx +++ b/packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx @@ -23,6 +23,14 @@ export class NodeListComponent extends React.Component + this.changeMode(value as NodeParticleModes)} + /> void; customSave?: { label: string; action: (data: string) => Promise }; + mode: NodeParticleModes = NodeParticleModes.Standard; public constructor() { this.stateManager = new StateManager(); diff --git a/packages/tools/nodeParticleEditor/src/graphSystem/display/inputDisplayManager.ts b/packages/tools/nodeParticleEditor/src/graphSystem/display/inputDisplayManager.ts index 9050c1eb679..aa3522bb2b5 100644 --- a/packages/tools/nodeParticleEditor/src/graphSystem/display/inputDisplayManager.ts +++ b/packages/tools/nodeParticleEditor/src/graphSystem/display/inputDisplayManager.ts @@ -130,28 +130,47 @@ export class InputDisplayManager implements IDisplayManager { break; } } else { + const block = nodeData.data as any; + const blockValue = block.displayValue !== undefined ? block.displayValue : (inputBlock as any).value; + const isStringValue = typeof blockValue === "string"; + switch (inputBlock.type) { case NodeParticleBlockConnectionPointTypes.Int: - value = inputBlock.value.toFixed(0); + value = isStringValue ? blockValue : inputBlock.value.toFixed(0); break; case NodeParticleBlockConnectionPointTypes.Float: - value = inputBlock.value.toFixed(4); + value = isStringValue ? blockValue : inputBlock.value.toFixed(4); break; case NodeParticleBlockConnectionPointTypes.Vector2: { - const vec2Value = inputBlock.value as Vector2; - value = `(${vec2Value.x.toFixed(2)}, ${vec2Value.y.toFixed(2)})`; + if (isStringValue) { + value = blockValue; + } else { + const vec2Value = inputBlock.value as Vector2; + value = `(${vec2Value.x.toFixed(2)}, ${vec2Value.y.toFixed(2)})`; + } break; } case NodeParticleBlockConnectionPointTypes.Vector3: { - const vec3Value = inputBlock.value as Vector3; - value = `(${vec3Value.x.toFixed(2)}, ${vec3Value.y.toFixed(2)}, ${vec3Value.z.toFixed(2)})`; + if (isStringValue) { + value = blockValue; + } else { + const vec3Value = inputBlock.value as Vector3; + value = `(${vec3Value.x.toFixed(2)}, ${vec3Value.y.toFixed(2)}, ${vec3Value.z.toFixed(2)})`; + } break; } case NodeParticleBlockConnectionPointTypes.Color4: { - const col4Value = inputBlock.value as Color4; - value = `(${col4Value.r.toFixed(2)}, ${col4Value.g.toFixed(2)}, ${col4Value.b.toFixed(2)}, ${col4Value.a.toFixed(2)})`; + if (isStringValue) { + value = blockValue; + } else { + const col4Value = inputBlock.value as Color4; + value = `(${col4Value.r.toFixed(2)}, ${col4Value.g.toFixed(2)}, ${col4Value.b.toFixed(2)}, ${col4Value.a.toFixed(2)})`; + } break; } + default: + value = isStringValue ? blockValue : String(blockValue); + break; } } contentArea.innerHTML = value; diff --git a/packages/tools/nodeParticleEditor/src/graphSystem/properties/genericNodePropertyComponent.tsx b/packages/tools/nodeParticleEditor/src/graphSystem/properties/genericNodePropertyComponent.tsx index 343dff680f5..edee94902df 100644 --- a/packages/tools/nodeParticleEditor/src/graphSystem/properties/genericNodePropertyComponent.tsx +++ b/packages/tools/nodeParticleEditor/src/graphSystem/properties/genericNodePropertyComponent.tsx @@ -186,6 +186,20 @@ export class GenericPropertyTabComponent extends React.Component ForceRebuild(block, this.props.stateManager, propertyName, options.notifiers)} + throttlePropertyChangedNotification={true} + /> + ); + break; + } case PropertyTypeForEdition.Float: { const cantDisplaySlider = isNaN(options.min as number) || isNaN(options.max as number) || options.min === options.max; if (cantDisplaySlider) { diff --git a/packages/tools/nodeParticleEditor/src/graphSystem/registerToDisplayLedger.ts b/packages/tools/nodeParticleEditor/src/graphSystem/registerToDisplayLedger.ts index 09311bdc7e5..ad51f51f8f9 100644 --- a/packages/tools/nodeParticleEditor/src/graphSystem/registerToDisplayLedger.ts +++ b/packages/tools/nodeParticleEditor/src/graphSystem/registerToDisplayLedger.ts @@ -36,10 +36,20 @@ export const RegisterToDisplayManagers = () => { DisplayLedger.RegisteredControls["BasicColorUpdateBlock"] = UpdateDisplayManager; DisplayLedger.RegisteredControls["UpdateFlowMapBlock"] = UpdateDisplayManager; DisplayLedger.RegisteredControls["SystemBlock"] = SystemDisplayManager; + + DisplayLedger.RegisteredControls["SPSMeshSourceBlock"] = EmitterDisplayManager; + DisplayLedger.RegisteredControls["SPSParticleConfigBlock"] = EmitterDisplayManager; + DisplayLedger.RegisteredControls["SPSCreateBlock"] = EmitterDisplayManager; + DisplayLedger.RegisteredControls["SPSSystemBlock"] = SystemDisplayManager; + DisplayLedger.RegisteredControls["SPSInitBlock"] = UpdateDisplayManager; + DisplayLedger.RegisteredControls["SPSUpdateBlock"] = UpdateDisplayManager; + DisplayLedger.RegisteredControls["ParticleDebugBlock"] = DebugDisplayManager; DisplayLedger.RegisteredControls["ParticleElbowBlock"] = ElbowDisplayManager; DisplayLedger.RegisteredControls["ParticleTeleportInBlock"] = TeleportInDisplayManager; DisplayLedger.RegisteredControls["ParticleTeleportOutBlock"] = TeleportOutDisplayManager; DisplayLedger.RegisteredControls["BasicConditionBlock"] = ConditionDisplayManager; DisplayLedger.RegisteredControls["ParticleTriggerBlock"] = TriggerDisplayManager; + DisplayLedger.RegisteredControls["ParticlePropsGetBlock"] = InputDisplayManager; + DisplayLedger.RegisteredControls["ParticlePropsSetBlock"] = InputDisplayManager; }; diff --git a/packages/tools/nodeParticleEditor/src/graphSystem/registerToPropertyLedger.ts b/packages/tools/nodeParticleEditor/src/graphSystem/registerToPropertyLedger.ts index 0290e5e97d5..57075b01779 100644 --- a/packages/tools/nodeParticleEditor/src/graphSystem/registerToPropertyLedger.ts +++ b/packages/tools/nodeParticleEditor/src/graphSystem/registerToPropertyLedger.ts @@ -13,4 +13,11 @@ export const RegisterToPropertyTabManagers = () => { PropertyLedger.RegisteredControls["ParticleDebugBlock"] = DebugPropertyTabComponent; PropertyLedger.RegisteredControls["ParticleTeleportOutBlock"] = TeleportOutPropertyTabComponent; PropertyLedger.RegisteredControls["MeshShapeBlock"] = MeshShapePropertyTabComponent; + + PropertyLedger.RegisteredControls["SPSMeshSourceBlock"] = GenericPropertyComponent; + PropertyLedger.RegisteredControls["SPSParticleConfigBlock"] = GenericPropertyComponent; + PropertyLedger.RegisteredControls["SPSCreateBlock"] = GenericPropertyComponent; + PropertyLedger.RegisteredControls["SPSSystemBlock"] = GenericPropertyComponent; + PropertyLedger.RegisteredControls["SPSInitBlock"] = GenericPropertyComponent; + PropertyLedger.RegisteredControls["SPSUpdateBlock"] = GenericPropertyComponent; }; diff --git a/packages/tools/nodeParticleEditor/src/nodeParticleModes.ts b/packages/tools/nodeParticleEditor/src/nodeParticleModes.ts new file mode 100644 index 00000000000..a5c6f90ea6a --- /dev/null +++ b/packages/tools/nodeParticleEditor/src/nodeParticleModes.ts @@ -0,0 +1,9 @@ +/** + * Enum used to define the different modes for NodeParticleEditor + */ +export enum NodeParticleModes { + /** Standard particle system */ + Standard = 0, + /** SPS (Solid Particle System) */ + SPS = 1, +}