diff --git a/client/src/components/ThreeVIew.tsx b/client/src/components/ThreeVIew.tsx index a8b2531..e765c8e 100644 --- a/client/src/components/ThreeVIew.tsx +++ b/client/src/components/ThreeVIew.tsx @@ -1,10 +1,9 @@ import { useEffect, useRef } from "react"; import * as THREE from 'three'; -import { useInteraction, type InteractionMouseMoveEventArgs } from "../helpers/hooks/useInteration"; +import { useInteraction, type InteractionMouseEventArgs } from "../helpers/hooks/useInteration"; import { db } from "../backend/db"; import { HitTestFactory, type HitTest } from "../helpers/hitTest"; import { model } from "../model/model"; -import { Point3dHelper } from "../helpers/point3dHelper"; import { SceneHelper } from "../helpers/sceneHelper"; export type ThreeViewEventArgs = { @@ -17,14 +16,15 @@ export type ThreeViewTickEventArgs = ThreeViewEventArgs & { absoluteTime: number, } -export type ThreeViewMouseMoveEventArgs = ThreeViewEventArgs & { +export type ThreeViewMouseEventArgs = ThreeViewEventArgs & { hitTest: HitTest, } export type ThreeViewProps = { sceneHelper: SceneHelper, onTick?: (event: ThreeViewTickEventArgs) => void, - onMouseMove?: (event: ThreeViewMouseMoveEventArgs) => void, + onClick?: (event: ThreeViewMouseEventArgs) => void, + onMouseMove?: (event: ThreeViewMouseEventArgs) => void, onDispose: (event: ThreeViewEventArgs) => void, } @@ -67,7 +67,8 @@ export const ThreeView = function (props: ThreeViewProps) { const canvasRef = useRef(null); const cameraRef = useRef(null); - let handleHover: (e: InteractionMouseMoveEventArgs) => void; + let handleClick: (e: InteractionMouseEventArgs) => void; + let handleHover: (e: InteractionMouseEventArgs) => void; useEffect(() => { @@ -100,7 +101,7 @@ export const ThreeView = function (props: ThreeViewProps) { }; window.addEventListener("resize", handleWindowResize); - handleHover = (e: InteractionMouseMoveEventArgs) => { + handleHover = (e: InteractionMouseEventArgs) => { const hitTest = HitTestFactory.hitTest( props.sceneHelper, new THREE.Vector2(e.position.x, e.position.y), @@ -114,6 +115,20 @@ export const ThreeView = function (props: ThreeViewProps) { }); }; + handleClick = (e: InteractionMouseEventArgs) => { + const hitTest = HitTestFactory.hitTest( + props.sceneHelper, + new THREE.Vector2(e.position.x, e.position.y), + camera, + { tolerancePixels: 3, cameraPixelSize: e.pixelSize } + ); + props.onClick?.({ + camera, + scene, + hitTest, + }) + }; + // --- Animation loop --- let lastTime = performance.now(); let animId: number; @@ -153,6 +168,7 @@ export const ThreeView = function (props: ThreeViewProps) { useInteraction(canvasRef, cameraRef, { onMouseMove: (e) => handleHover?.(e), + onMouseClick: (e) => handleClick?.(e), }); return ( diff --git a/client/src/components/Viewport.tsx b/client/src/components/Viewport.tsx index 32548bb..b8de551 100644 --- a/client/src/components/Viewport.tsx +++ b/client/src/components/Viewport.tsx @@ -1,14 +1,12 @@ -import { useState } from "react"; import { useSceneHelper } from "../helpers/hooks/useSceneHelper"; -import { Point3dHelper } from "../helpers/point3dHelper"; import { state } from "../state/root"; -import { ThreeView, type ThreeViewEventArgs, type ThreeViewMouseMoveEventArgs } from "./ThreeVIew"; +import { ThreeView, type ThreeViewEventArgs, type ThreeViewMouseEventArgs } from "./ThreeVIew"; export const Viewport = function () { const sceneHelper = useSceneHelper(); - function handleMouseMove(e: ThreeViewMouseMoveEventArgs) { + function handleMouseMove(e: ThreeViewMouseEventArgs) { state.setHitTest(e.hitTest); sceneHelper.clearPoints(); @@ -33,10 +31,15 @@ export const Viewport = function () { // throw new Error("Function not implemented."); } + function handleClick(e: ThreeViewMouseEventArgs): void { + console.log(e); + } + return (<> ); diff --git a/client/src/helpers/hooks/useInteration.ts b/client/src/helpers/hooks/useInteration.ts index 3c1b44f..77f0d77 100644 --- a/client/src/helpers/hooks/useInteration.ts +++ b/client/src/helpers/hooks/useInteration.ts @@ -1,13 +1,16 @@ import { useEffect, type RefObject } from "react"; import * as THREE from "three"; -export type InteractionMouseMoveEventArgs = { +const CLICK_THRESHOLD = 2; // px + +export type InteractionMouseEventArgs = { position: { x: number, y: number }, pixelSize: { x: number, y: number }, }; export type UseInteractionOptions = { - onMouseMove?: (e: InteractionMouseMoveEventArgs) => void, + onMouseClick?: (e: InteractionMouseEventArgs) => void, + onMouseMove?: (e: InteractionMouseEventArgs) => void, } export function useInteraction( @@ -29,6 +32,7 @@ export function useInteraction( // --- Orbit Controls (manual implementation, no OrbitControls import needed) --- let isDragging = false; let isRightDrag = false; + let startX = 0, startY = 0; let lastX = 0, lastY = 0; let theta = 0.8, phi = 0.9, radius = 8; let targetX = 0, targetY = 0; @@ -44,12 +48,24 @@ export function useInteraction( const onMouseDown = (e: MouseEvent) => { isDragging = true; isRightDrag = e.button === 2; + startX = e.clientX; + startY = e.clientY; lastX = e.clientX; lastY = e.clientY; e.preventDefault(); }; - const onMouseUp = () => { isDragging = false; }; + const onMouseUp = (e: MouseEvent) => { + isDragging = false; + if ((e.clientX - startX < CLICK_THRESHOLD) && (e.clientY - startY < CLICK_THRESHOLD)) { + const rect = target.getBoundingClientRect(); + const position = { + x: ((e.clientX - rect.left) / rect.width) * 2 - 1, + y: -((e.clientY - rect.top) / rect.height) * 2 + 1, + }; + options.onMouseClick?.({ position, pixelSize: { x: 2 / rect.width, y: 2 / rect.height } }); + } + }; const onMouseMove = (e: MouseEvent) => { if (!isDragging) return;