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)); } }