Implemented dedicated Angular services (e.g., RendererService, InputHandlerService, PlayerModelService) to modularize and simplify Three.js integration within the `ParticlesComponent`. Refactored component logic to delegate rendering, input handling, and model creation to respective services. Updated particle data models and removed redundant logic from the component.
106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import { Injectable, ElementRef } from '@angular/core';
|
|
import * as THREE from 'three';
|
|
import { RendererService } from './renderer.service';
|
|
import { IntersectionPlaneService } from './intersection-plane.service';
|
|
import { ParticleManagerService } from './particle-manager.service';
|
|
|
|
/**
|
|
* Service responsible for handling user input interactions
|
|
*/
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class InputHandlerService {
|
|
private raycaster = new THREE.Raycaster();
|
|
private mouse = new THREE.Vector2();
|
|
private isDragging = false;
|
|
private mouseDownTime = 0;
|
|
|
|
constructor(
|
|
private rendererService: RendererService,
|
|
private intersectionPlaneService: IntersectionPlaneService,
|
|
private particleManagerService: ParticleManagerService
|
|
) {}
|
|
|
|
/**
|
|
* Initializes input event listeners
|
|
*/
|
|
initializeInputHandlers(rendererElement: HTMLElement): void {
|
|
rendererElement.addEventListener('mousedown', this.onMouseDown.bind(this));
|
|
rendererElement.addEventListener('mouseup', this.onMouseUp.bind(this));
|
|
rendererElement.addEventListener('mousemove', this.onMouseMove.bind(this));
|
|
window.addEventListener('resize', this.onWindowResize.bind(this));
|
|
}
|
|
|
|
/**
|
|
* Handles mouse down event
|
|
*/
|
|
private onMouseDown(event: MouseEvent): void {
|
|
this.isDragging = false;
|
|
this.mouseDownTime = Date.now();
|
|
}
|
|
|
|
/**
|
|
* Handles mouse up event
|
|
*/
|
|
private onMouseUp(event: MouseEvent): void {
|
|
// If mouse was down for less than 200ms and didn't move much, consider it a click, not a drag
|
|
if (Date.now() - this.mouseDownTime < 200 && !this.isDragging) {
|
|
this.handlePlaneClick(event);
|
|
}
|
|
this.isDragging = false;
|
|
}
|
|
|
|
/**
|
|
* Handles mouse move event
|
|
*/
|
|
private onMouseMove(event: MouseEvent): void {
|
|
// If mouse moves while button is pressed, it's a drag
|
|
if (event.buttons > 0) {
|
|
this.isDragging = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles mouse click on the plane
|
|
*/
|
|
private handlePlaneClick(event: MouseEvent): void {
|
|
// Calculate mouse position in normalized device coordinates
|
|
const rect = this.rendererService.renderer.domElement.getBoundingClientRect();
|
|
this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
|
|
// Update the picking ray with the camera and mouse position
|
|
this.raycaster.setFromCamera(this.mouse, this.rendererService.camera);
|
|
|
|
// Calculate objects intersecting the picking ray
|
|
const intersects = this.raycaster.intersectObject(this.intersectionPlaneService.getIntersectionPlane());
|
|
|
|
if (intersects.length > 0) {
|
|
const point = intersects[0].point;
|
|
this.particleManagerService.addParticle(point.x, point.y, point.z);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles window resize event
|
|
*/
|
|
private onWindowResize(): void {
|
|
// This is delegated to the renderer service
|
|
const container = this.rendererService.renderer.domElement.parentElement;
|
|
if (container) {
|
|
this.rendererService.onWindowResize(new ElementRef(container));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes event listeners
|
|
*/
|
|
cleanup(rendererElement: HTMLElement): void {
|
|
rendererElement.removeEventListener('mousedown', this.onMouseDown.bind(this));
|
|
rendererElement.removeEventListener('mouseup', this.onMouseUp.bind(this));
|
|
rendererElement.removeEventListener('mousemove', this.onMouseMove.bind(this));
|
|
window.removeEventListener('resize', this.onWindowResize.bind(this));
|
|
}
|
|
}
|