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