pages235/renderer/viewer/three/threeJsParticles.ts
Vitaly Turovsky 14ad1c5934 sync fork: add a bunch of not needed side core features like translation
AND fix critical performance regression (& ram)
2025-04-23 05:55:59 +03:00

160 lines
5.4 KiB
TypeScript

import * as THREE from 'three'
interface ParticleMesh extends THREE.Mesh {
velocity: THREE.Vector3;
}
interface ParticleConfig {
fountainHeight: number;
resetHeight: number;
xVelocityRange: number;
zVelocityRange: number;
particleCount: number;
particleRadiusRange: { min: number; max: number };
yVelocityRange: { min: number; max: number };
}
export interface FountainOptions {
position?: { x: number, y: number, z: number }
particleConfig?: Partial<ParticleConfig>;
}
export class Fountain {
private readonly particles: ParticleMesh[] = []
private readonly config: { particleConfig: ParticleConfig }
private readonly position: THREE.Vector3
container: THREE.Object3D | undefined
constructor (public sectionId: string, options: FountainOptions = {}) {
this.position = options.position ? new THREE.Vector3(options.position.x, options.position.y, options.position.z) : new THREE.Vector3(0, 0, 0)
this.config = this.createConfig(options.particleConfig)
}
private createConfig (
particleConfigOverride?: Partial<ParticleConfig>
): { particleConfig: ParticleConfig } {
const particleConfig: ParticleConfig = {
fountainHeight: 10,
resetHeight: 0,
xVelocityRange: 0.4,
zVelocityRange: 0.4,
particleCount: 400,
particleRadiusRange: { min: 0.1, max: 0.6 },
yVelocityRange: { min: 0.1, max: 2 },
...particleConfigOverride
}
return { particleConfig }
}
createParticles (container: THREE.Object3D): void {
this.container = container
const colorStart = new THREE.Color(0xff_ff_00)
const colorEnd = new THREE.Color(0xff_a5_00)
for (let i = 0; i < this.config.particleConfig.particleCount; i++) {
const radius = Math.random() *
(this.config.particleConfig.particleRadiusRange.max - this.config.particleConfig.particleRadiusRange.min) +
this.config.particleConfig.particleRadiusRange.min
const geometry = new THREE.SphereGeometry(radius)
const material = new THREE.MeshBasicMaterial({
color: colorStart.clone().lerp(colorEnd, Math.random())
})
const mesh = new THREE.Mesh(geometry, material)
const particle = mesh as unknown as ParticleMesh
particle.position.set(
this.position.x + (Math.random() - 0.5) * this.config.particleConfig.xVelocityRange * 2,
this.position.y + this.config.particleConfig.fountainHeight,
this.position.z + (Math.random() - 0.5) * this.config.particleConfig.zVelocityRange * 2
)
particle.velocity = new THREE.Vector3(
(Math.random() - 0.5) * this.config.particleConfig.xVelocityRange,
-Math.random() * this.config.particleConfig.yVelocityRange.max,
(Math.random() - 0.5) * this.config.particleConfig.zVelocityRange
)
this.particles.push(particle)
this.container.add(particle)
// this.container.onBeforeRender = () => {
// this.render()
// }
}
}
render (): void {
for (const particle of this.particles) {
particle.velocity.y -= 0.01 + Math.random() * 0.1
particle.position.add(particle.velocity)
if (particle.position.y < this.position.y + this.config.particleConfig.resetHeight) {
particle.position.set(
this.position.x + (Math.random() - 0.5) * this.config.particleConfig.xVelocityRange * 2,
this.position.y + this.config.particleConfig.fountainHeight,
this.position.z + (Math.random() - 0.5) * this.config.particleConfig.zVelocityRange * 2
)
particle.velocity.set(
(Math.random() - 0.5) * this.config.particleConfig.xVelocityRange,
-Math.random() * this.config.particleConfig.yVelocityRange.max,
(Math.random() - 0.5) * this.config.particleConfig.zVelocityRange
)
}
}
}
private updateParticleCount (newCount: number): void {
if (newCount !== this.config.particleConfig.particleCount) {
this.config.particleConfig.particleCount = newCount
const currentCount = this.particles.length
if (newCount > currentCount) {
this.addParticles(newCount - currentCount)
} else if (newCount < currentCount) {
this.removeParticles(currentCount - newCount)
}
}
}
private addParticles (count: number): void {
const geometry = new THREE.SphereGeometry(0.1)
const material = new THREE.MeshBasicMaterial({ color: 0x00_ff_00 })
for (let i = 0; i < count; i++) {
const mesh = new THREE.Mesh(geometry, material)
const particle = mesh as unknown as ParticleMesh
particle.position.copy(this.position)
particle.velocity = new THREE.Vector3(
Math.random() * this.config.particleConfig.xVelocityRange -
this.config.particleConfig.xVelocityRange / 2,
Math.random() * 2,
Math.random() * this.config.particleConfig.zVelocityRange -
this.config.particleConfig.zVelocityRange / 2
)
this.particles.push(particle)
this.container!.add(particle)
}
}
private removeParticles (count: number): void {
for (let i = 0; i < count; i++) {
const particle = this.particles.pop()
if (particle) {
this.container!.remove(particle)
}
}
}
public dispose (): void {
for (const particle of this.particles) {
particle.geometry.dispose()
if (Array.isArray(particle.material)) {
for (const material of particle.material) material.dispose()
} else {
particle.material.dispose()
}
}
}
}