Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4629a7d
Add Solid Particle System (SPS) blocks to Node Particle Editor
Oct 10, 2025
c5d8db6
Enhance Node Particle System with Solid Particle Blocks
Oct 14, 2025
fc2df4b
Refactor Solid Particle System initialization and cleanup
Oct 14, 2025
0be8117
Update NodeParticleEditor to use setToDefault method for NodeParticle…
Oct 14, 2025
6d72319
Enhance Solid Particle System with isStarted flag and update NodePart…
Oct 17, 2025
5a6e5b9
Enhance NodeParticleConnectionPoint to support multiple connections
Oct 17, 2025
b31248e
Refactor Solid Particle System and Node Particle Editor for improved …
Oct 20, 2025
3f7d61f
Refactor SPSSystemBlock to streamline particle initialization and upd…
Oct 20, 2025
7539783
Enhance SolidParticleSystem and NodeParticleSystemSet with improved d…
Oct 20, 2025
4ff96c9
Refactor NodeParticleBlock and NodeParticleConnectionPoint to simplif…
Oct 21, 2025
2d7e11c
Add unregisterInput method to NodeParticleBlock for dynamic input man…
Oct 21, 2025
396662b
Remove optional `allowMultipleConnections` property from IPortData in…
Oct 21, 2025
72c885b
Refactor SolidParticleSystem and related blocks for improved initiali…
Oct 28, 2025
014681f
Refactor SolidParticleSystem blocks to introduce SPSParticleConfigBlo…
Oct 29, 2025
9c25aa6
Refactor SPSCreateBlock and SPSSystemBlock to improve lifecycle manag…
Oct 30, 2025
6da7359
Enhance ParticleSystemSet to support SolidParticleSystem integration
Oct 31, 2025
e37f61f
Enhance NodeParticleBuildState and NodeParticleSystemSet for SolidPar…
Nov 14, 2025
dc4db46
Refactor SPSInitBlock and SPSUpdateBlock to streamline value retrieval
Nov 14, 2025
d116da5
Refactor NodeParticleSystemSet to streamline random position and rota…
Nov 14, 2025
69a803a
Remove commented-out steps in NodeParticleSystemSet for improved code…
Nov 14, 2025
50115b2
Add ParticlePropsSetBlock and ParticlePropsGetBlock for dynamic prope…
Nov 14, 2025
3289658
Enhance particle property management with String type support and dis…
Nov 14, 2025
8833c50
Add Solid Particle System (SPS) blocks to Node Particle Editor
Oct 10, 2025
3430d2f
Enhance Node Particle System with Solid Particle Blocks
Oct 14, 2025
be2185e
Refactor Solid Particle System initialization and cleanup
Oct 14, 2025
5b797bc
Update NodeParticleEditor to use setToDefault method for NodeParticle…
Oct 14, 2025
b63f06e
Enhance Solid Particle System with isStarted flag and update NodePart…
Oct 17, 2025
5ee2820
Enhance NodeParticleConnectionPoint to support multiple connections
Oct 17, 2025
1617792
Refactor Solid Particle System and Node Particle Editor for improved …
Oct 20, 2025
5dc1e1d
Refactor SPSSystemBlock to streamline particle initialization and upd…
Oct 20, 2025
eaeecf4
Enhance SolidParticleSystem and NodeParticleSystemSet with improved d…
Oct 20, 2025
4994414
Refactor NodeParticleBlock and NodeParticleConnectionPoint to simplif…
Oct 21, 2025
140a45e
Add unregisterInput method to NodeParticleBlock for dynamic input man…
Oct 21, 2025
62c7143
Remove optional `allowMultipleConnections` property from IPortData in…
Oct 21, 2025
9791892
Refactor SolidParticleSystem and related blocks for improved initiali…
Oct 28, 2025
dfe540f
Refactor SolidParticleSystem blocks to introduce SPSParticleConfigBlo…
Oct 29, 2025
a4207e3
Refactor SPSCreateBlock and SPSSystemBlock to improve lifecycle manag…
Oct 30, 2025
6ac1d15
Enhance ParticleSystemSet to support SolidParticleSystem integration
Oct 31, 2025
cc9bbe0
Enhance NodeParticleBuildState and NodeParticleSystemSet for SolidPar…
Nov 14, 2025
1eb3110
Refactor SPSInitBlock and SPSUpdateBlock to streamline value retrieval
Nov 14, 2025
b9a365b
Refactor NodeParticleSystemSet to streamline random position and rota…
Nov 14, 2025
bf82fdc
Remove commented-out steps in NodeParticleSystemSet for improved code…
Nov 14, 2025
2085aa6
Add ParticlePropsSetBlock and ParticlePropsGetBlock for dynamic prope…
Nov 14, 2025
c0c3d1d
Enhance particle property management with String type support and dis…
Nov 14, 2025
da65fb7
Merge branch 'feat/solid-particles-to-node-particle-editor' of https:…
Nov 14, 2025
e03fa4b
Remove String property from PropertyTypeForEdition enum and refactor …
Nov 14, 2025
499386d
Refactor particle property blocks to enhance naming consistency and t…
Nov 17, 2025
c3bf211
Merge remote-tracking branch 'upstream/master'
Nov 17, 2025
c3fa640
Merge branch 'master' into feat/solid-particles-to-node-particle-editor
Nov 17, 2025
28912f4
Implement SPSParticlePropsSetBlock and SPSParticlePropsGetBlock for d…
Nov 17, 2025
98d1dec
Add lifetime and disposeOnEnd properties to SPSSystemBlock and SolidP…
Nov 20, 2025
a0c36ab
Enhance particle system functionality with new mesh and material blocks
Nov 21, 2025
e1d549e
Refactor and enhance Solid Particle System blocks for improved functi…
Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/dev/core/src/Decorators/nodeDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ 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 */
TextureFormat,
/** property (int) should be edited as a combo box with a list of texture types */
TextureType,
/** property is a string */
String,
/** property is a matrix */
Matrix,
/** property is a viewport */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Vector3 } from "core/Maths/math.vector";
import type { Color4 } from "core/Maths/math.color";
import type { Material } from "core/Materials/material";
import type { VertexData } from "core/Meshes/mesh.vertexData";

export interface ISpsMeshSourceData {
customMeshName?: string;
vertexData?: VertexData;
}

/**
* 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 {
meshData: ISpsMeshSourceData | null;
count: number;
material?: Material;
initBlock?: ISpsUpdateData;
updateBlock?: ISpsUpdateData;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/* eslint-disable @typescript-eslint/naming-convention */

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 { Mesh } from "core/Meshes/mesh";
import type { SolidParticle } from "../../../solidParticle";
import type { Observer } from "core/Misc/observable";

/**
* Block used to create SolidParticleSystem and collect all Create blocks
*/
export class SPSCreateBlock extends NodeParticleBlock {
private _connectionObservers = new Map<number, Observer<NodeParticleConnectionPoint>>();
private _disconnectionObservers = new Map<number, Observer<NodeParticleConnectionPoint>>();

public constructor(name: string) {
super(name);
this.registerInput(`config-${this._entryCount - 1}`, NodeParticleBlockConnectionPointTypes.SolidParticleConfig);
this.registerOutput("solidParticle", NodeParticleBlockConnectionPointTypes.SolidParticle);

this._manageExtendedInputs(0);
}

public override getClassName() {
return "SPSCreateBlock";
}

private _entryCount = 1;

private _extend() {
this._entryCount++;
this.registerInput(`config-${this._entryCount - 1}`, NodeParticleBlockConnectionPointTypes.SolidParticleConfig, true);
this._manageExtendedInputs(this._entryCount - 1);
}

private _shrink() {
if (this._entryCount > 1) {
this._unmanageExtendedInputs(this._entryCount - 1);
this._entryCount--;
this.unregisterInput(`config-${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 config(): NodeParticleConnectionPoint {
return this._inputs[this._entryCount - 1];
}

public get solidParticle(): 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<number, ISpsParticleConfigData>();
for (let i = 0; i < this._inputs.length; i++) {
const creatData = this._inputs[i].getConnectedValue(state) as ISpsParticleConfigData;
if (!this._inputs[i].isConnected || !creatData || !creatData.meshData || !creatData.count) {
continue;
}

if (!creatData.meshData.vertexData) {
continue;
}

const mesh = new Mesh(`${this.name}_shape_${i}`, state.scene);
creatData.meshData.vertexData.applyToMesh(mesh, true);
mesh.isVisible = false;
if (creatData.material) {
mesh.material = creatData.material;
}

const shapeId = sps.addShape(mesh, creatData.count);
createBlocks.set(shapeId, creatData);
mesh.dispose();
}

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.solidParticle._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);
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/* eslint-disable @typescript-eslint/naming-convention */

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);
Loading