233 lines
6.3 KiB
TypeScript
233 lines
6.3 KiB
TypeScript
import {Injectable} from '@angular/core';
|
|
import * as THREE from 'three';
|
|
import {RendererService} from './renderer.service';
|
|
import {Particle, ParticleData, ParticleInfo, ParticleType} from '../models/particle.model';
|
|
|
|
/**
|
|
* Service responsible for managing particles in the scene
|
|
*/
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class ParticleManagerService {
|
|
private particles: THREE.Mesh[] = [];
|
|
private particleData: ParticleData = {
|
|
particle_name: '',
|
|
display_name: '',
|
|
particle_type: ParticleType.TRAIL,
|
|
lore: '',
|
|
display_item: 'DIRT',
|
|
permission: '',
|
|
package_permission: '',
|
|
frame_delay: 1,
|
|
repeat: 1,
|
|
repeat_delay: 0,
|
|
random_offset: 0,
|
|
stationary: true,
|
|
frames: {
|
|
'frame1': []
|
|
}
|
|
};
|
|
private currentFrame: string = 'frame1';
|
|
private frames: string[] = ['frame1'];
|
|
private selectedColor: string = '#ff0000';
|
|
private selectedParticle: Particle = Particle.DUST;
|
|
private selectedSize: number = 1;
|
|
|
|
constructor(private rendererService: RendererService) {
|
|
}
|
|
|
|
/**
|
|
* Adds a particle at the specified position
|
|
*/
|
|
addParticle(x: number, y: number, z: number): void {
|
|
// Create a visual representation of the particle
|
|
const particleGeometry = new THREE.SphereGeometry(0.03, 16, 16);
|
|
const particleMaterial = new THREE.MeshBasicMaterial({color: this.selectedColor});
|
|
const particleMesh = new THREE.Mesh(particleGeometry, particleMaterial);
|
|
|
|
particleMesh.position.set(x, y, z);
|
|
this.rendererService.scene.add(particleMesh);
|
|
this.particles.push(particleMesh);
|
|
|
|
// Add to particle data
|
|
const hexColor = this.selectedColor.replace('#', '');
|
|
|
|
//TODO make this work for more than just type DUST
|
|
const particleInfo: ParticleInfo = {
|
|
particle_type: this.selectedParticle,
|
|
x: x,
|
|
y: y,
|
|
z: z,
|
|
color: hexColor,
|
|
// color_gradient_end: hexColor2,
|
|
extra: 1,
|
|
size: this.selectedSize
|
|
};
|
|
|
|
if (!this.particleData.frames[this.currentFrame]) {
|
|
this.particleData.frames[this.currentFrame] = [];
|
|
}
|
|
|
|
this.particleData.frames[this.currentFrame].push(particleInfo);
|
|
}
|
|
|
|
/**
|
|
* Clears all particle visuals from the scene
|
|
*/
|
|
clearParticleVisuals(): void {
|
|
for (const particle of this.particles) {
|
|
this.rendererService.scene.remove(particle);
|
|
}
|
|
this.particles = [];
|
|
}
|
|
|
|
/**
|
|
* Renders particles for a specific frame
|
|
*/
|
|
renderFrameParticles(frameId: string): void {
|
|
if (!this.particleData.frames[frameId]) return;
|
|
|
|
for (const particleInfo of this.particleData.frames[frameId]) {
|
|
const particleGeometry = new THREE.SphereGeometry(0.03, 16, 16);
|
|
|
|
const color = this.getColor(particleInfo);
|
|
const particleMaterial = new THREE.MeshBasicMaterial({color});
|
|
const particleMesh = new THREE.Mesh(particleGeometry, particleMaterial);
|
|
|
|
particleMesh.position.set(particleInfo.x, particleInfo.y, particleInfo.z);
|
|
this.rendererService.scene.add(particleMesh);
|
|
this.particles.push(particleMesh);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a particle from a specific frame
|
|
*/
|
|
removeParticle(frameId: string, index: number): void {
|
|
if (this.particleData.frames[frameId] && this.particleData.frames[frameId].length > index) {
|
|
this.particleData.frames[frameId].splice(index, 1);
|
|
|
|
// Update visuals if this is the current frame
|
|
if (frameId === this.currentFrame) {
|
|
this.clearParticleVisuals();
|
|
this.renderFrameParticles(frameId);
|
|
}
|
|
}
|
|
}
|
|
|
|
highlightParticle(frameId: string, index: number): void {
|
|
if (!(this.particleData.frames[frameId] && this.particleData.frames[frameId].length > index)) {
|
|
return;
|
|
}
|
|
const particleInfo = this.particleData.frames[frameId][index];
|
|
const color = this.getColor(particleInfo);
|
|
const particleMaterial = new THREE.MeshBasicMaterial({color});
|
|
const particleGeometry = new THREE.SphereGeometry(0.03, 16, 16);
|
|
const particleMesh = new THREE.Mesh(particleGeometry, particleMaterial);
|
|
particleMesh.position.set(particleInfo.x, particleInfo.y, particleInfo.z);
|
|
this.rendererService.scene.add(particleMesh);
|
|
this.particles.push(particleMesh);
|
|
this.animatePulse(particleMesh, 3, () => {
|
|
this.rendererService.scene.remove(particleMesh);
|
|
this.clearParticleVisuals();
|
|
this.renderFrameParticles(this.currentFrame);
|
|
});
|
|
}
|
|
|
|
private getColor(particleInfo: ParticleInfo) {
|
|
if (particleInfo.color) {
|
|
const r = parseInt(particleInfo.color.substring(0, 2), 16) / 255;
|
|
const g = parseInt(particleInfo.color.substring(2, 4), 16) / 255;
|
|
const b = parseInt(particleInfo.color.substring(4, 6), 16) / 255;
|
|
return new THREE.Color(r, g, b);
|
|
} else {
|
|
return new THREE.Color(255, 0, 0);
|
|
}
|
|
}
|
|
|
|
private animatePulse(mesh: THREE.Mesh, cycles: number, onComplete: () => void): void {
|
|
const duration = 300;
|
|
const maxScale = 0.08 / 0.03;
|
|
const startTime = performance.now();
|
|
|
|
const animate = (time: number) => {
|
|
const elapsed = (time - startTime) % duration;
|
|
const t = elapsed / (duration / 2);
|
|
const scaleFactor = t <= 1 ? 1 + (maxScale - 1) * t : maxScale - (maxScale - 1) * (t - 1);
|
|
mesh.scale.setScalar(scaleFactor);
|
|
|
|
if (time - startTime >= duration * cycles) {
|
|
onComplete();
|
|
} else {
|
|
requestAnimationFrame(animate);
|
|
}
|
|
};
|
|
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
/**
|
|
* Sets the selected color for new particles
|
|
*/
|
|
setSelectedColor(color: string): void {
|
|
this.selectedColor = color;
|
|
}
|
|
|
|
/**
|
|
* Gets the selected color
|
|
*/
|
|
getSelectedColor(): string {
|
|
return this.selectedColor;
|
|
}
|
|
|
|
/**
|
|
* Gets the particle data
|
|
*/
|
|
getParticleData(): ParticleData {
|
|
return this.particleData;
|
|
}
|
|
|
|
/**
|
|
* Sets the particle data
|
|
*/
|
|
setParticleData(data: ParticleData): void {
|
|
this.particleData = data;
|
|
}
|
|
|
|
/**
|
|
* Gets the current frame
|
|
*/
|
|
getCurrentFrame(): string {
|
|
return this.currentFrame;
|
|
}
|
|
|
|
/**
|
|
* Sets the current frame
|
|
*/
|
|
setCurrentFrame(frameId: string): void {
|
|
this.currentFrame = frameId;
|
|
}
|
|
|
|
/**
|
|
* Gets all frames
|
|
*/
|
|
getFrames(): string[] {
|
|
return this.frames;
|
|
}
|
|
|
|
/**
|
|
* Sets all frames
|
|
*/
|
|
setFrames(frames: string[]): void {
|
|
this.frames = frames;
|
|
}
|
|
|
|
/**
|
|
* Generates JSON output of the particle data
|
|
*/
|
|
generateJson(): string {
|
|
return JSON.stringify(this.particleData, null, 2);
|
|
}
|
|
}
|