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">
<pre>
{
state.hitTest.objects.map((obj) =>
<div key={obj.object.uuid}>
{JSON.stringify(obj.point.toArray())}
{JSON.stringify(obj.object.userData)}
state.hitTest.hits.map((hit) =>
<div key={hit.object.uuid}>
{JSON.stringify(hit.point.toArray())}
{JSON.stringify(hit.object.userData)}
</div>
)
}

View File

@ -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 ---

View File

@ -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);

View File

@ -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))

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 { 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[]) {

View File

@ -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;
}
}