Skip to content

Particle attractors #16541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 43 additions & 0 deletions packages/dev/core/src/Particles/Queue/executionQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Nullable } from "core/types";
import type { Particle } from "../particle";
import type { ThinParticleSystem } from "../thinParticleSystem";

/** @internal */
export interface _IExecutionQueueItem {
/** @internal */
process: (particle: Particle, system: ThinParticleSystem) => void;
/** @internal */
previousItem: Nullable<_IExecutionQueueItem>;
/** @internal */
nextItem: Nullable<_IExecutionQueueItem>;
}

/** @internal */
export function _ConnectBefore(newOne: _IExecutionQueueItem, activeOne: _IExecutionQueueItem) {
newOne.previousItem = activeOne.previousItem;
newOne.nextItem = activeOne;
if (activeOne.previousItem) {
activeOne.previousItem.nextItem = newOne;
}
activeOne.previousItem = newOne;
}

/** @internal */
export function _ConnectAfter(newOne: _IExecutionQueueItem, activeOne: _IExecutionQueueItem) {
newOne.previousItem = activeOne;
newOne.nextItem = activeOne.nextItem;
if (activeOne.nextItem) {
activeOne.nextItem.previousItem = newOne;
}
activeOne.nextItem = newOne;
}

/** @internal */
export function _RemoveFromQueue(item: _IExecutionQueueItem) {
if (item.previousItem) {
item.previousItem.nextItem = item.nextItem;
}
if (item.nextItem) {
item.nextItem.previousItem = item.previousItem;
}
}
35 changes: 35 additions & 0 deletions packages/dev/core/src/Particles/attractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Vector3 } from "core/Maths/math.vector";
import type { Particle } from "./particle";
import type { ThinParticleSystem } from "./thinParticleSystem";

const toAttractor: Vector3 = Vector3.Zero();
const force: Vector3 = Vector3.Zero();
const scaledForce: Vector3 = Vector3.Zero();

/**
* Class representing an attractor in a particle system.
* #DEZ79M#31
*/
export class Attractor {
/**
* Gets or sets the strength of the attractor.
* A positive value attracts particles, while a negative value repels them.
*/
public strength = 0.0;

/**
* Gets or sets the position of the attractor in 3D space.
*/
public position = Vector3.Zero();

/** @internal */
public _processParticle(particle: Particle, system: ThinParticleSystem) {
this.position.subtractToRef(particle.position, toAttractor);
const distanceSquared = toAttractor.lengthSquared() + 1; // Avoid going under 1.0
toAttractor.normalize().scaleToRef(this.strength / distanceSquared, force);

force.scaleToRef(system._tempScaledUpdateSpeed, scaledForce);

particle.direction.addInPlace(scaledForce); // Update particle velocity
}
}
1 change: 1 addition & 0 deletions packages/dev/core/src/Particles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from "./solidParticleSystem";
export * from "./cloudPoint";
export * from "./pointsCloudSystem";
export * from "./subEmitter";
export * from "./attractor";

export * from "../Shaders/particles.fragment";
export * from "../Shaders/particles.vertex";
Expand Down
50 changes: 50 additions & 0 deletions packages/dev/core/src/Particles/particleSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ import {
CreatePointEmitter,
CreateSphereEmitter,
} from "./particleSystem.functions";
import type { Attractor } from "./attractor";
import type { _IExecutionQueueItem } from "./Queue/executionQueue";
import { _ConnectAfter, _RemoveFromQueue } from "./Queue/executionQueue";

/**
* This represents a particle system in Babylon.
Expand Down Expand Up @@ -88,6 +91,53 @@ export class ParticleSystem extends ThinParticleSystem {
return particleEmitter;
}

private _attractors: Attractor[] = [];
private _attractorUpdate: Nullable<_IExecutionQueueItem> = null;

/**
* The list of attractors used to change the direction of the particles in the system.
* Please note that this is a copy of the internal array. If you want to modify it, please use the addAttractor and removeAttractor methods.
*/
public get attractors(): Attractor[] {
return this._attractors.slice(0);
}

/**
* Add an attractor to the particle system. Attractors are used to change the direction of the particles in the system.
* @param attractor The attractor to add to the particle system
*/
public addAttractor(attractor: Attractor): void {
this._attractors.push(attractor);

if (this._attractors.length === 1) {
this._attractorUpdate = {
process: (particle: Particle) => {
for (const attractor of this._attractors) {
attractor._processParticle(particle, this);
}
},
previousItem: null,
nextItem: null,
};
_ConnectAfter(this._attractorUpdate, this._directionProcessing!);
}
}

/**
* Removes an attractor from the particle system. Attractors are used to change the direction of the particles in the system.
* @param attractor The attractor to remove from the particle system
*/
public removeAttractor(attractor: Attractor): void {
const index = this._attractors.indexOf(attractor);
if (index !== -1) {
this._attractors.splice(index, 1);
}

if (this._attractors.length === 0) {
_RemoveFromQueue(this._attractorUpdate!);
}
}

/**
* Creates a Hemisphere Emitter for the particle system (emits along the hemisphere radius)
* @param radius The radius of the hemisphere to emit from
Expand Down
Loading