diff --git a/client/src/components/HitTestView.tsx b/client/src/components/HitTestView.tsx index ff328de..6182f10 100644 --- a/client/src/components/HitTestView.tsx +++ b/client/src/components/HitTestView.tsx @@ -7,10 +7,10 @@ export const HitTestView = observer(function () {
                 {
-                    state.hitTest.objects.map((obj) =>
-                        
- {JSON.stringify(obj.point.toArray())} - {JSON.stringify(obj.object.userData)} + state.hitTest.hits.map((hit) => +
+ {JSON.stringify(hit.point.toArray())} + {JSON.stringify(hit.object.userData)}
) } diff --git a/client/src/components/ThreeVIew.tsx b/client/src/components/ThreeVIew.tsx index c1bc179..ad9b217 100644 --- a/client/src/components/ThreeVIew.tsx +++ b/client/src/components/ThreeVIew.tsx @@ -2,9 +2,9 @@ import { useEffect, useRef } from "react"; import * as THREE from 'three'; 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 { SceneHelper } from "../helpers/sceneHelper"; +import type { HitResults } from "../helpers/circularFrustumIntersect"; export type ThreeViewEventArgs = { camera: THREE.Camera, @@ -17,7 +17,7 @@ export type ThreeViewTickEventArgs = ThreeViewEventArgs & { } export type ThreeViewMouseEventArgs = ThreeViewEventArgs & { - hitTest: HitTest, + hitResults: HitResults, } export type ThreeViewProps = { @@ -102,38 +102,27 @@ export const ThreeView = function (props: ThreeViewProps) { window.addEventListener("resize", handleWindowResize); handleHover = (e: InteractionMouseEventArgs) => { - - const ht = props.sceneHelper.hitTest( + const hitResults = props.sceneHelper.hitTest( e.position, e.screenSize, ); - console.log(JSON.stringify(ht.map((h) => h.object.userData))); - - const hitTest = HitTestFactory.hitTest( - props.sceneHelper, - new THREE.Vector2(e.position.x, e.position.y), - camera, - { tolerancePixels: 3, cameraPixelSize: e.pixelSize } - ); props.onMouseMove?.({ camera, scene, - hitTest, + hitResults, }); }; handleClick = (e: InteractionMouseEventArgs) => { - const hitTest = HitTestFactory.hitTest( - props.sceneHelper, - new THREE.Vector2(e.position.x, e.position.y), - camera, - { tolerancePixels: 3, cameraPixelSize: e.pixelSize } + const hitResults = props.sceneHelper.hitTest( + e.position, + e.screenSize, ); props.onClick?.({ camera, scene, - hitTest, - }) + hitResults, + }); }; // --- Animation loop --- diff --git a/client/src/components/Viewport.tsx b/client/src/components/Viewport.tsx index f35f356..87cc596 100644 --- a/client/src/components/Viewport.tsx +++ b/client/src/components/Viewport.tsx @@ -7,12 +7,12 @@ export const Viewport = function () { const sceneHelper = useSceneHelper(); function handleMouseMove(e: ThreeViewMouseEventArgs) { - state.setHitTest(e.hitTest); + state.setHitTest(e.hitResults); sceneHelper.clear(); - if (e.hitTest.objects.length) { - e.hitTest.objects.forEach((obj) => { - sceneHelper.showPoint(obj.object.uuid, obj.point); + if (e.hitResults.hits.length) { + e.hitResults.hits.forEach((hit) => { + sceneHelper.showPoint(hit.object.uuid, hit.point); }) // console.log(e.position); // console.log(e.hitTest.objects.map((o) => o)); @@ -20,7 +20,7 @@ export const Viewport = function () { } // raycaster.setFromCamera(new THREE.Vector2(e.x, e.y), camera); // const hits = raycaster.intersectObjects(sync.meshes); - const hoveredFaceIds = e.hitTest.objects.map(hit => hit.object.userData.faceId); + const hoveredFaceIds = e.hitResults.hits.map((hit) => hit.object.userData.faceId); // if (hoveredFaceIds.length) // console.log(hoveredFaceIds); diff --git a/client/src/helpers/circularFrustumIntersect.ts b/client/src/helpers/circularFrustumIntersect.ts index 9d1134f..94a3f47 100644 --- a/client/src/helpers/circularFrustumIntersect.ts +++ b/client/src/helpers/circularFrustumIntersect.ts @@ -2,7 +2,11 @@ import * as THREE from 'three'; import { CONTAINED, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED } from 'three-mesh-bvh'; import { CircularFrustum } from './circularFrustum'; -export type FrustumHitResult = { +export type HitResults = { + hits: HitResult[]; +} + +export type HitResult = { object: THREE.Object3D; point: THREE.Vector3; // world-space closest hit point depth: number; // depth along frustum axis @@ -119,7 +123,7 @@ export class CircularFrustumIntersection { public intersectMesh( mesh: THREE.Mesh, findAll: boolean, - ): FrustumHitResult[] { + ): HitResult[] { const geometry = mesh.geometry; if (!geometry) return []; @@ -136,7 +140,7 @@ export class CircularFrustumIntersection { if (this.insersectsSphere(boundingSphere) === 'NOT_INTERSECTED') return []; - const results: FrustumHitResult[] = []; + const results: HitResult[] = []; if (!geometry.boundsTree) geometry.computeBoundsTree(); @@ -240,8 +244,8 @@ export class CircularFrustumIntersection { public intersectObject( obj: THREE.Object3D, options: CircularFrustumIntersectionOptions = {}, - ): FrustumHitResult[] { - const results: FrustumHitResult[] = []; + ): HitResult[] { + const results: HitResult[] = []; obj.traverseVisible((object) => { if (options.filter && !options.filter(object)) diff --git a/client/src/helpers/hitTest.ts b/client/src/helpers/hitTest.ts deleted file mode 100644 index c72b72b..0000000 --- a/client/src/helpers/hitTest.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as THREE from 'three'; -import type { SceneHelper } from './sceneHelper'; -import './bvh'; - -export type HitTest = { - objects: THREE.Intersection[]; -} - -export type HitTestRaycasterOptions = { - cameraPixelSize: THREE.Vector2Like; - tolerancePixels: number; -} - -export type HitTestOptions = HitTestRaycasterOptions & { -} - -export class HitTestFactory { - - private static raycasters: [THREE.Vector2, THREE.Raycaster][] = Array(9).fill(0).map(() => [new THREE.Vector2(), new THREE.Raycaster()]); - - private static setupRaycasters(cursor: THREE.Vector2, camera: THREE.PerspectiveCamera, options: HitTestRaycasterOptions) { - - this.raycasters[0][0].copy(cursor); - this.raycasters[0][1].setFromCamera(cursor, camera); - - const count = HitTestFactory.raycasters.length - 1; - const step = Math.PI * 2 / count; - - for (let angle = 0, idx = 0; idx < count; angle += step, idx++) { - const pos = { - x: Math.cos(angle) * options.tolerancePixels * options.cameraPixelSize.x, - y: Math.sin(angle) * options.tolerancePixels * options.cameraPixelSize.y, - }; - const v = HitTestFactory.raycasters[idx + 1][0]; - v.copy(cursor).add(pos); - HitTestFactory.raycasters[idx + 1][1].setFromCamera(v, camera); - } - } - - public static getRaycasterPosition(index: number): THREE.Vector2 { - return HitTestFactory.raycasters[index][0]; - } - - public static get raycasterCount(): number { - return HitTestFactory.raycasters.length; - } - - public static hitTest(scene: SceneHelper, cursor: THREE.Vector2, camera: THREE.PerspectiveCamera, options: HitTestOptions): HitTest { - - HitTestFactory.setupRaycasters(cursor, camera, options); - - const objects: THREE.Object3D[] = scene.objects; - - const hitTest: Record> = {}; - - HitTestFactory.raycasters.forEach((raycaster) => { - const hits = raycaster[1].intersectObjects(objects); - for (const hit of hits) { - hitTest[hit.object.uuid] = hit; - } - }); - - return { - objects: Object.values(hitTest), - } - } -} diff --git a/client/src/helpers/sceneHelper.ts b/client/src/helpers/sceneHelper.ts index 361b311..ca1fcb7 100644 --- a/client/src/helpers/sceneHelper.ts +++ b/client/src/helpers/sceneHelper.ts @@ -2,7 +2,7 @@ import type { ColorRepresentation, Object3D, Object3DEventMap, OrthographicCamer import { SceneSync } from "../layers/sceneSync"; import { GeometryCache } from "../layers/geometryCache"; import type { Id } from "../types"; -import { CircularFrustumIntersection, type FrustumHitResult } from "./circularFrustumIntersect"; +import { CircularFrustumIntersection, type HitResult, type HitResults } from "./circularFrustumIntersect"; import { CircularFrustum } from "./circularFrustum"; import './bvh'; import { VolatileGeometryHelper, type VolatileGeometryOptions } from "./volatileGeometryHelper"; @@ -30,7 +30,7 @@ export class SceneHelper { public buildMouseFrustum( mouseNormalized: Vector2Like, screenSize: Vector2Like, - radius: number = 15, + radius: number = 5, ): void { if (!this.camera) throw new Error('Camera is not initialized'); @@ -46,13 +46,16 @@ export class SceneHelper { public hitTest( mouseNormalized: Vector2Like, screenSize: Vector2Like, - ): FrustumHitResult[] { + ): HitResults { this.buildMouseFrustum(mouseNormalized, screenSize); - const result: FrustumHitResult[] = []; + const hits: HitResult[] = []; for (const object of this.objects) - result.push(...this.mouseFrustum.intersectObject(object)); - return result; + hits.push(...this.mouseFrustum.intersectObject(object)); + + return { + hits, + }; } public setSelection(faceIds: Id[]) { diff --git a/client/src/state/root.ts b/client/src/state/root.ts index cc8697d..1abe70a 100644 --- a/client/src/state/root.ts +++ b/client/src/state/root.ts @@ -1,10 +1,10 @@ import { makeAutoObservable } from "mobx"; import type { Id } from "../types"; -import type { HitTest } from "../helpers/hitTest"; +import type { HitResults } from "../helpers/circularFrustumIntersect"; export class Root { public selectedPrimitiveIds: Id[] = []; - public hitTest: HitTest = { objects: [] }; + public hitTest: HitResults = { hits: [] }; constructor() { makeAutoObservable(this); @@ -14,7 +14,7 @@ export class Root { this.selectedPrimitiveIds = value; } - public setHitTest(value: HitTest) { + public setHitTest(value: HitResults) { this.hitTest = value; } }