import {AfterViewInit, Component, ElementRef, OnDestroy, ViewChild} from '@angular/core'; import {CommonModule} from '@angular/common'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatButtonModule} from '@angular/material/button'; import {MatInputModule} from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatSelectModule} from '@angular/material/select'; import {MatSliderModule} from '@angular/material/slider'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatTabsModule} from '@angular/material/tabs'; import {MatCardModule} from '@angular/material/card'; import {MatIconModule} from '@angular/material/icon'; import {HeaderComponent} from '../header/header.component'; // Services import {RendererService} from './services/renderer.service'; import {PlayerModelService} from './services/player-model.service'; import {IntersectionPlaneService} from './services/intersection-plane.service'; import {ParticleManagerService} from './services/particle-manager.service'; import {InputHandlerService} from './services/input-handler.service'; // Models import {PropertiesComponent} from './components/properties/properties.component'; import {ParticleComponent} from './components/particle/particle.component'; import {FramesComponent} from './components/frames/frames.component'; import {MatSnackBar} from '@angular/material/snack-bar'; @Component({ selector: 'app-particles', standalone: true, imports: [ CommonModule, FormsModule, ReactiveFormsModule, MatButtonModule, MatInputModule, MatFormFieldModule, MatSelectModule, MatSliderModule, MatCheckboxModule, MatTabsModule, MatCardModule, MatIconModule, HeaderComponent, PropertiesComponent, ParticleComponent, FramesComponent, ], templateUrl: './particles.component.html', styleUrl: './particles.component.scss' }) export class ParticlesComponent implements AfterViewInit, OnDestroy { @ViewChild('rendererContainer') rendererContainer!: ElementRef; @ViewChild('planeSlider') planeSlider!: ElementRef; constructor( private rendererService: RendererService, private playerModelService: PlayerModelService, private intersectionPlaneService: IntersectionPlaneService, private particleManagerService: ParticleManagerService, private inputHandlerService: InputHandlerService, private matSnackBar: MatSnackBar, ) { } /** * Initialize Three.js scene after view is initialized */ ngAfterViewInit(): void { this.initializeScene(); this.animate(); } /** * Clean up resources when component is destroyed */ ngOnDestroy(): void { if (this.rendererService.renderer) { this.inputHandlerService.cleanup(this.rendererService.renderer.domElement); } } /** * Initialize the 3D scene and all related components */ private initializeScene(): void { this.rendererService.initializeRenderer(this.rendererContainer); this.playerModelService.createPlayerModel(); this.intersectionPlaneService.createIntersectionPlane(); this.inputHandlerService.initializeInputHandlers(this.rendererService.renderer.domElement); } /** * Update plane position based on slider */ public updatePlanePosition(event: Event): void { const slider = event.target as HTMLInputElement; const value = Number(slider.value); this.intersectionPlaneService.updatePlanePosition(value); } /** * Get the current plane position */ public get planePosition(): number { return this.intersectionPlaneService.getPlanePosition(); } public set planePosition(newPlanePosition: number) { this.intersectionPlaneService.updatePlanePosition(newPlanePosition); } public get maxOffset(): number { return this.intersectionPlaneService.getMaxOffset(); } public get minOffset(): number { return this.intersectionPlaneService.getMinOffset(); } /** * Animation loop */ private animate(): void { requestAnimationFrame(this.animate.bind(this)); this.intersectionPlaneService.updatePlaneOrientation(this.rendererService.camera); this.rendererService.render(); } /** * Generate JSON output */ public generateJson(): string { return this.particleManagerService.generateJson(); } public copyJson() { navigator.clipboard.writeText(this.generateJson()).then(() => { this.matSnackBar.open('Copied to clipboard', '', {duration: 2000}) }); } }