removed multiray hittest helper

This commit is contained in:
azykov@mail.ru 2026-05-23 08:51:53 +03:00
parent b583eaee29
commit cf45752848
No known key found for this signature in database
7 changed files with 39 additions and 110 deletions

View File

@ -7,10 +7,10 @@ export const HitTestView = observer(function () {
<div id="hit-test"> <div id="hit-test">
<pre> <pre>
{ {
state.hitTest.objects.map((obj) => state.hitTest.hits.map((hit) =>
<div key={obj.object.uuid}> <div key={hit.object.uuid}>
{JSON.stringify(obj.point.toArray())} {JSON.stringify(hit.point.toArray())}
{JSON.stringify(obj.object.userData)} {JSON.stringify(hit.object.userData)}
</div> </div>
) )
} }

View File

@ -2,9 +2,9 @@ import { useEffect, useRef } from "react";
import * as THREE from 'three'; import * as THREE from 'three';
import { useInteraction, type InteractionMouseEventArgs } from "../helpers/hooks/useInteration"; import { useInteraction, type InteractionMouseEventArgs } from "../helpers/hooks/useInteration";
import { db } from "../backend/db"; import { db } from "../backend/db";
import { HitTestFactory, type HitTest } from "../helpers/hitTest";
import { model } from "../model/model"; import { model } from "../model/model";
import { SceneHelper } from "../helpers/sceneHelper"; import { SceneHelper } from "../helpers/sceneHelper";
import type { HitResults } from "../helpers/circularFrustumIntersect";
export type ThreeViewEventArgs = { export type ThreeViewEventArgs = {
camera: THREE.Camera, camera: THREE.Camera,
@ -17,7 +17,7 @@ export type ThreeViewTickEventArgs = ThreeViewEventArgs & {
} }
export type ThreeViewMouseEventArgs = ThreeViewEventArgs & { export type ThreeViewMouseEventArgs = ThreeViewEventArgs & {
hitTest: HitTest, hitResults: HitResults,
} }
export type ThreeViewProps = { export type ThreeViewProps = {
@ -102,38 +102,27 @@ export const ThreeView = function (props: ThreeViewProps) {
window.addEventListener("resize", handleWindowResize); window.addEventListener("resize", handleWindowResize);
handleHover = (e: InteractionMouseEventArgs) => { handleHover = (e: InteractionMouseEventArgs) => {
const hitResults = props.sceneHelper.hitTest(
const ht = props.sceneHelper.hitTest(
e.position, e.position,
e.screenSize, 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?.({ props.onMouseMove?.({
camera, camera,
scene, scene,
hitTest, hitResults,
}); });
}; };
handleClick = (e: InteractionMouseEventArgs) => { handleClick = (e: InteractionMouseEventArgs) => {
const hitTest = HitTestFactory.hitTest( const hitResults = props.sceneHelper.hitTest(
props.sceneHelper, e.position,
new THREE.Vector2(e.position.x, e.position.y), e.screenSize,
camera,
{ tolerancePixels: 3, cameraPixelSize: e.pixelSize }
); );
props.onClick?.({ props.onClick?.({
camera, camera,
scene, scene,
hitTest, hitResults,
}) });
}; };
// --- Animation loop --- // --- Animation loop ---

View File

@ -7,12 +7,12 @@ export const Viewport = function () {
const sceneHelper = useSceneHelper(); const sceneHelper = useSceneHelper();
function handleMouseMove(e: ThreeViewMouseEventArgs) { function handleMouseMove(e: ThreeViewMouseEventArgs) {
state.setHitTest(e.hitTest); state.setHitTest(e.hitResults);
sceneHelper.clear(); sceneHelper.clear();
if (e.hitTest.objects.length) { if (e.hitResults.hits.length) {
e.hitTest.objects.forEach((obj) => { e.hitResults.hits.forEach((hit) => {
sceneHelper.showPoint(obj.object.uuid, obj.point); sceneHelper.showPoint(hit.object.uuid, hit.point);
}) })
// console.log(e.position); // console.log(e.position);
// console.log(e.hitTest.objects.map((o) => o)); // 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); // raycaster.setFromCamera(new THREE.Vector2(e.x, e.y), camera);
// const hits = raycaster.intersectObjects(sync.meshes); // 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) // if (hoveredFaceIds.length)
// console.log(hoveredFaceIds); // console.log(hoveredFaceIds);

View File

@ -2,7 +2,11 @@ import * as THREE from 'three';
import { CONTAINED, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED } from 'three-mesh-bvh'; import { CONTAINED, ExtendedTriangle, INTERSECTED, NOT_INTERSECTED } from 'three-mesh-bvh';
import { CircularFrustum } from './circularFrustum'; import { CircularFrustum } from './circularFrustum';
export type FrustumHitResult = { export type HitResults = {
hits: HitResult[];
}
export type HitResult = {
object: THREE.Object3D; object: THREE.Object3D;
point: THREE.Vector3; // world-space closest hit point point: THREE.Vector3; // world-space closest hit point
depth: number; // depth along frustum axis depth: number; // depth along frustum axis
@ -119,7 +123,7 @@ export class CircularFrustumIntersection {
public intersectMesh( public intersectMesh(
mesh: THREE.Mesh, mesh: THREE.Mesh,
findAll: boolean, findAll: boolean,
): FrustumHitResult[] { ): HitResult[] {
const geometry = mesh.geometry; const geometry = mesh.geometry;
if (!geometry) if (!geometry)
return []; return [];
@ -136,7 +140,7 @@ export class CircularFrustumIntersection {
if (this.insersectsSphere(boundingSphere) === 'NOT_INTERSECTED') if (this.insersectsSphere(boundingSphere) === 'NOT_INTERSECTED')
return []; return [];
const results: FrustumHitResult[] = []; const results: HitResult[] = [];
if (!geometry.boundsTree) if (!geometry.boundsTree)
geometry.computeBoundsTree(); geometry.computeBoundsTree();
@ -240,8 +244,8 @@ export class CircularFrustumIntersection {
public intersectObject( public intersectObject(
obj: THREE.Object3D, obj: THREE.Object3D,
options: CircularFrustumIntersectionOptions = {}, options: CircularFrustumIntersectionOptions = {},
): FrustumHitResult[] { ): HitResult[] {
const results: FrustumHitResult[] = []; const results: HitResult[] = [];
obj.traverseVisible((object) => { obj.traverseVisible((object) => {
if (options.filter && !options.filter(object)) if (options.filter && !options.filter(object))

View File

@ -1,67 +0,0 @@
import * as THREE from 'three';
import type { SceneHelper } from './sceneHelper';
import './bvh';
export type HitTest = {
objects: THREE.Intersection<THREE.Object3D>[];
}
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<string, THREE.Intersection<THREE.Object3D>> = {};
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),
}
}
}

View File

@ -2,7 +2,7 @@ import type { ColorRepresentation, Object3D, Object3DEventMap, OrthographicCamer
import { SceneSync } from "../layers/sceneSync"; import { SceneSync } from "../layers/sceneSync";
import { GeometryCache } from "../layers/geometryCache"; import { GeometryCache } from "../layers/geometryCache";
import type { Id } from "../types"; import type { Id } from "../types";
import { CircularFrustumIntersection, type FrustumHitResult } from "./circularFrustumIntersect"; import { CircularFrustumIntersection, type HitResult, type HitResults } from "./circularFrustumIntersect";
import { CircularFrustum } from "./circularFrustum"; import { CircularFrustum } from "./circularFrustum";
import './bvh'; import './bvh';
import { VolatileGeometryHelper, type VolatileGeometryOptions } from "./volatileGeometryHelper"; import { VolatileGeometryHelper, type VolatileGeometryOptions } from "./volatileGeometryHelper";
@ -30,7 +30,7 @@ export class SceneHelper {
public buildMouseFrustum( public buildMouseFrustum(
mouseNormalized: Vector2Like, mouseNormalized: Vector2Like,
screenSize: Vector2Like, screenSize: Vector2Like,
radius: number = 15, radius: number = 5,
): void { ): void {
if (!this.camera) if (!this.camera)
throw new Error('Camera is not initialized'); throw new Error('Camera is not initialized');
@ -46,13 +46,16 @@ export class SceneHelper {
public hitTest( public hitTest(
mouseNormalized: Vector2Like, mouseNormalized: Vector2Like,
screenSize: Vector2Like, screenSize: Vector2Like,
): FrustumHitResult[] { ): HitResults {
this.buildMouseFrustum(mouseNormalized, screenSize); this.buildMouseFrustum(mouseNormalized, screenSize);
const result: FrustumHitResult[] = []; const hits: HitResult[] = [];
for (const object of this.objects) for (const object of this.objects)
result.push(...this.mouseFrustum.intersectObject(object)); hits.push(...this.mouseFrustum.intersectObject(object));
return result;
return {
hits,
};
} }
public setSelection(faceIds: Id[]) { public setSelection(faceIds: Id[]) {

View File

@ -1,10 +1,10 @@
import { makeAutoObservable } from "mobx"; import { makeAutoObservable } from "mobx";
import type { Id } from "../types"; import type { Id } from "../types";
import type { HitTest } from "../helpers/hitTest"; import type { HitResults } from "../helpers/circularFrustumIntersect";
export class Root { export class Root {
public selectedPrimitiveIds: Id[] = []; public selectedPrimitiveIds: Id[] = [];
public hitTest: HitTest = { objects: [] }; public hitTest: HitResults = { hits: [] };
constructor() { constructor() {
makeAutoObservable(this); makeAutoObservable(this);
@ -14,7 +14,7 @@ export class Root {
this.selectedPrimitiveIds = value; this.selectedPrimitiveIds = value;
} }
public setHitTest(value: HitTest) { public setHitTest(value: HitResults) {
this.hitTest = value; this.hitTest = value;
} }
} }