113 lines
3.2 KiB
JavaScript
113 lines
3.2 KiB
JavaScript
import * as THREE from 'three';
|
|
|
|
export class InputManager {
|
|
constructor(camera, domElement, scene, controls) {
|
|
this.camera = camera;
|
|
this.domElement = domElement;
|
|
this.scene = scene;
|
|
this.controls = controls;
|
|
|
|
this.raycaster = new THREE.Raycaster();
|
|
this.mouse = new THREE.Vector2();
|
|
|
|
// Interaction State
|
|
this.downPosition = new THREE.Vector2();
|
|
this.dragObject = null;
|
|
this.isPanning = false;
|
|
|
|
// Callbacks
|
|
this.onClick = null; // (point, object) -> void
|
|
this.onDrag = null; // (object, newPoint) -> void
|
|
this.onDragEnd = null; // () -> void
|
|
this.onHover = null; // (point) -> void <-- NEW
|
|
}
|
|
|
|
init() {
|
|
this.domElement.addEventListener('pointerdown', this.onPointerDown.bind(this));
|
|
this.domElement.addEventListener('pointermove', this.onPointerMove.bind(this));
|
|
this.domElement.addEventListener('pointerup', this.onPointerUp.bind(this));
|
|
}
|
|
|
|
onPointerDown(event) {
|
|
if (event.button !== 0) return;
|
|
|
|
this.downPosition.set(event.clientX, event.clientY);
|
|
this.isPanning = false;
|
|
|
|
const hit = this.raycast(event);
|
|
|
|
if (hit) {
|
|
if (hit.object.userData.isMarker) {
|
|
this.dragObject = hit.object;
|
|
this.controls.enabled = false;
|
|
this.domElement.style.cursor = 'grabbing';
|
|
}
|
|
}
|
|
}
|
|
|
|
onPointerMove(event) {
|
|
// Case A: Dragging a Marker
|
|
if (this.dragObject) {
|
|
const hit = this.raycastGround(event);
|
|
if (hit && this.onDrag) {
|
|
this.onDrag(this.dragObject, hit.point);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Case B: Hovering (Ghost Marker Logic) <-- NEW
|
|
// We only care about hovering the ground for placing new nodes
|
|
const hit = this.raycastGround(event);
|
|
if (hit && this.onHover) {
|
|
this.onHover(hit.point);
|
|
}
|
|
}
|
|
|
|
onPointerUp(event) {
|
|
if (event.button !== 0) return;
|
|
|
|
if (this.dragObject) {
|
|
this.dragObject = null;
|
|
this.controls.enabled = true;
|
|
this.domElement.style.cursor = 'auto';
|
|
if (this.onDragEnd) this.onDragEnd();
|
|
return;
|
|
}
|
|
|
|
const upPosition = new THREE.Vector2(event.clientX, event.clientY);
|
|
if (this.downPosition.distanceTo(upPosition) > 3) {
|
|
return;
|
|
}
|
|
|
|
const hit = this.raycast(event);
|
|
if (hit && hit.object.name === "GROUND" && this.onClick) {
|
|
this.onClick(hit.point, hit.object);
|
|
}
|
|
}
|
|
|
|
// --- Helpers ---
|
|
|
|
getMouse(event) {
|
|
const r = this.domElement.getBoundingClientRect();
|
|
const x = ((event.clientX - r.left) / r.width) * 2 - 1;
|
|
const y = -((event.clientY - r.top) / r.height) * 2 + 1;
|
|
return new THREE.Vector2(x, y);
|
|
}
|
|
|
|
raycast(event) {
|
|
this.raycaster.setFromCamera(this.getMouse(event), this.camera);
|
|
// Ignore Ghost Marker in standard raycast interaction
|
|
const intersects = this.raycaster.intersectObjects(this.scene.children, true);
|
|
return intersects.find(obj =>
|
|
(obj.object.name === "GROUND" || obj.object.userData.isMarker) &&
|
|
obj.object.name !== "GHOST_MARKER"
|
|
);
|
|
}
|
|
|
|
raycastGround(event) {
|
|
this.raycaster.setFromCamera(this.getMouse(event), this.camera);
|
|
const intersects = this.raycaster.intersectObjects(this.scene.children, true);
|
|
return intersects.find(obj => obj.object.name === "GROUND");
|
|
}
|
|
}
|