303 lines
9.6 KiB
JavaScript
303 lines
9.6 KiB
JavaScript
import * as THREE from 'three';
|
|
|
|
export class ParticleSystem {
|
|
constructor(game) {
|
|
this.game = game;
|
|
this.systems = [];
|
|
}
|
|
|
|
createFire(position, opts = {}) {
|
|
const count = opts.count || 60;
|
|
const geo = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(count * 3);
|
|
const velocities = new Float32Array(count * 3);
|
|
const lifetimes = new Float32Array(count);
|
|
const sizes = new Float32Array(count);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
this.resetFireParticle(positions, velocities, lifetimes, sizes, i, position);
|
|
}
|
|
|
|
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
geo.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
|
|
|
|
const mat = new THREE.PointsMaterial({
|
|
color: 0xff6622,
|
|
size: 0.25,
|
|
transparent: true,
|
|
opacity: 0.8,
|
|
blending: THREE.AdditiveBlending,
|
|
depthWrite: false,
|
|
sizeAttenuation: true
|
|
});
|
|
|
|
const mesh = new THREE.Points(geo, mat);
|
|
this.game.scene.add(mesh);
|
|
|
|
this.systems.push({
|
|
type: 'fire',
|
|
mesh, geo, positions, velocities, lifetimes, sizes,
|
|
count, origin: position.clone(),
|
|
resetFn: (i) => this.resetFireParticle(positions, velocities, lifetimes, sizes, i, position)
|
|
});
|
|
|
|
return mesh;
|
|
}
|
|
|
|
resetFireParticle(positions, velocities, lifetimes, sizes, i, origin) {
|
|
const i3 = i * 3;
|
|
positions[i3] = origin.x + (Math.random() - 0.5) * 0.4;
|
|
positions[i3 + 1] = origin.y + Math.random() * 0.2;
|
|
positions[i3 + 2] = origin.z + (Math.random() - 0.5) * 0.4;
|
|
velocities[i3] = (Math.random() - 0.5) * 0.3;
|
|
velocities[i3 + 1] = 1.0 + Math.random() * 2.0;
|
|
velocities[i3 + 2] = (Math.random() - 0.5) * 0.3;
|
|
lifetimes[i] = Math.random() * 1.5;
|
|
sizes[i] = 0.15 + Math.random() * 0.2;
|
|
}
|
|
|
|
createRain() {
|
|
const count = 3000;
|
|
const geo = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(count * 3);
|
|
const velocities = new Float32Array(count * 3);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const i3 = i * 3;
|
|
positions[i3] = (Math.random() - 0.5) * 120;
|
|
positions[i3 + 1] = Math.random() * 40;
|
|
positions[i3 + 2] = (Math.random() - 0.5) * 120;
|
|
velocities[i3] = -0.5;
|
|
velocities[i3 + 1] = -15 - Math.random() * 10;
|
|
velocities[i3 + 2] = -1;
|
|
}
|
|
|
|
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
const mat = new THREE.PointsMaterial({
|
|
color: 0xaaccff,
|
|
size: 0.08,
|
|
transparent: true,
|
|
opacity: 0.5,
|
|
depthWrite: false
|
|
});
|
|
|
|
const mesh = new THREE.Points(geo, mat);
|
|
mesh.visible = false;
|
|
this.game.scene.add(mesh);
|
|
|
|
this.systems.push({
|
|
type: 'rain',
|
|
mesh, geo, positions, velocities, count
|
|
});
|
|
|
|
return mesh;
|
|
}
|
|
|
|
createSnow() {
|
|
const count = 2000;
|
|
const geo = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(count * 3);
|
|
const velocities = new Float32Array(count * 3);
|
|
const phases = new Float32Array(count);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const i3 = i * 3;
|
|
positions[i3] = (Math.random() - 0.5) * 120;
|
|
positions[i3 + 1] = Math.random() * 40;
|
|
positions[i3 + 2] = (Math.random() - 0.5) * 120;
|
|
velocities[i3] = 0;
|
|
velocities[i3 + 1] = -1.5 - Math.random() * 1.5;
|
|
velocities[i3 + 2] = 0;
|
|
phases[i] = Math.random() * Math.PI * 2;
|
|
}
|
|
|
|
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
const mat = new THREE.PointsMaterial({
|
|
color: 0xffffff,
|
|
size: 0.15,
|
|
transparent: true,
|
|
opacity: 0.8,
|
|
depthWrite: false
|
|
});
|
|
|
|
const mesh = new THREE.Points(geo, mat);
|
|
mesh.visible = false;
|
|
this.game.scene.add(mesh);
|
|
|
|
this.systems.push({
|
|
type: 'snow',
|
|
mesh, geo, positions, velocities, count, phases,
|
|
time: 0
|
|
});
|
|
|
|
return mesh;
|
|
}
|
|
|
|
createSparks(position) {
|
|
const count = 20;
|
|
const geo = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(count * 3);
|
|
const velocities = new Float32Array(count * 3);
|
|
const lifetimes = new Float32Array(count);
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
const i3 = i * 3;
|
|
positions[i3] = position.x;
|
|
positions[i3 + 1] = position.y + 0.5;
|
|
positions[i3 + 2] = position.z;
|
|
const angle = Math.random() * Math.PI * 2;
|
|
const speed = 1 + Math.random() * 3;
|
|
velocities[i3] = Math.cos(angle) * speed * 0.3;
|
|
velocities[i3 + 1] = 2 + Math.random() * 4;
|
|
velocities[i3 + 2] = Math.sin(angle) * speed * 0.3;
|
|
lifetimes[i] = 0.5 + Math.random() * 1.0;
|
|
}
|
|
|
|
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
const mat = new THREE.PointsMaterial({
|
|
color: 0xffaa00,
|
|
size: 0.08,
|
|
transparent: true,
|
|
opacity: 1,
|
|
blending: THREE.AdditiveBlending,
|
|
depthWrite: false
|
|
});
|
|
|
|
const mesh = new THREE.Points(geo, mat);
|
|
this.game.scene.add(mesh);
|
|
|
|
this.systems.push({
|
|
type: 'sparks',
|
|
mesh, geo, positions, velocities, lifetimes, count,
|
|
origin: position.clone(), age: 0, maxAge: 1.5
|
|
});
|
|
}
|
|
|
|
update(dt) {
|
|
const playerPos = this.game.player.position;
|
|
|
|
for (let s = this.systems.length - 1; s >= 0; s--) {
|
|
const sys = this.systems[s];
|
|
|
|
if (sys.type === 'fire') {
|
|
this.updateFire(sys, dt);
|
|
} else if (sys.type === 'rain') {
|
|
this.updateWeatherParticles(sys, dt, playerPos);
|
|
} else if (sys.type === 'snow') {
|
|
this.updateSnow(sys, dt, playerPos);
|
|
} else if (sys.type === 'sparks') {
|
|
sys.age += dt;
|
|
if (sys.age > sys.maxAge) {
|
|
this.game.scene.remove(sys.mesh);
|
|
sys.geo.dispose();
|
|
sys.mesh.material.dispose();
|
|
this.systems.splice(s, 1);
|
|
continue;
|
|
}
|
|
this.updateSparks(sys, dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
updateFire(sys, dt) {
|
|
const pos = sys.positions;
|
|
const vel = sys.velocities;
|
|
const life = sys.lifetimes;
|
|
|
|
for (let i = 0; i < sys.count; i++) {
|
|
const i3 = i * 3;
|
|
life[i] -= dt;
|
|
|
|
if (life[i] <= 0) {
|
|
sys.resetFn(i);
|
|
continue;
|
|
}
|
|
|
|
pos[i3] += vel[i3] * dt;
|
|
pos[i3 + 1] += vel[i3 + 1] * dt;
|
|
pos[i3 + 2] += vel[i3 + 2] * dt;
|
|
|
|
vel[i3 + 1] += dt * 0.5; // подъём
|
|
}
|
|
|
|
sys.geo.attributes.position.needsUpdate = true;
|
|
sys.mesh.material.opacity = 0.5 + Math.sin(Date.now() * 0.01) * 0.3;
|
|
}
|
|
|
|
updateWeatherParticles(sys, dt, playerPos) {
|
|
const pos = sys.positions;
|
|
const vel = sys.velocities;
|
|
|
|
for (let i = 0; i < sys.count; i++) {
|
|
const i3 = i * 3;
|
|
pos[i3] += vel[i3] * dt;
|
|
pos[i3 + 1] += vel[i3 + 1] * dt;
|
|
pos[i3 + 2] += vel[i3 + 2] * dt;
|
|
|
|
if (pos[i3 + 1] < 0) {
|
|
pos[i3] = playerPos.x + (Math.random() - 0.5) * 120;
|
|
pos[i3 + 1] = 30 + Math.random() * 10;
|
|
pos[i3 + 2] = playerPos.z + (Math.random() - 0.5) * 120;
|
|
}
|
|
}
|
|
|
|
sys.geo.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
updateSnow(sys, dt, playerPos) {
|
|
const pos = sys.positions;
|
|
const vel = sys.velocities;
|
|
sys.time += dt;
|
|
|
|
for (let i = 0; i < sys.count; i++) {
|
|
const i3 = i * 3;
|
|
pos[i3] += vel[i3] * dt + Math.sin(sys.time * 2 + sys.phases[i]) * 0.3 * dt;
|
|
pos[i3 + 1] += vel[i3 + 1] * dt;
|
|
pos[i3 + 2] += vel[i3 + 2] * dt + Math.cos(sys.time * 1.5 + sys.phases[i]) * 0.3 * dt;
|
|
|
|
if (pos[i3 + 1] < 0) {
|
|
pos[i3] = playerPos.x + (Math.random() - 0.5) * 120;
|
|
pos[i3 + 1] = 30 + Math.random() * 10;
|
|
pos[i3 + 2] = playerPos.z + (Math.random() - 0.5) * 120;
|
|
}
|
|
}
|
|
|
|
sys.geo.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
updateSparks(sys, dt) {
|
|
const pos = sys.positions;
|
|
const vel = sys.velocities;
|
|
|
|
for (let i = 0; i < sys.count; i++) {
|
|
const i3 = i * 3;
|
|
pos[i3] += vel[i3] * dt;
|
|
pos[i3 + 1] += vel[i3 + 1] * dt;
|
|
pos[i3 + 2] += vel[i3 + 2] * dt;
|
|
vel[i3 + 1] -= 9.8 * dt; // гравитация
|
|
}
|
|
|
|
sys.geo.attributes.position.needsUpdate = true;
|
|
sys.mesh.material.opacity = 1 - (sys.age / sys.maxAge);
|
|
}
|
|
|
|
setWeather(type) {
|
|
this.systems.forEach(sys => {
|
|
if (sys.type === 'rain') sys.mesh.visible = (type === 'rain');
|
|
if (sys.type === 'snow') sys.mesh.visible = (type === 'snow');
|
|
});
|
|
}
|
|
|
|
clear() {
|
|
this.systems.forEach(sys => {
|
|
this.game.scene.remove(sys.mesh);
|
|
sys.geo.dispose();
|
|
sys.mesh.material.dispose();
|
|
});
|
|
this.systems = [];
|
|
}
|
|
}
|