frustum hit test now works with faces also

This commit is contained in:
azykov@mail.ru 2026-05-21 18:43:16 +03:00
parent c8fdeafe3f
commit b583eaee29
No known key found for this signature in database
1 changed files with 52 additions and 23 deletions

View File

@ -147,42 +147,71 @@ export class CircularFrustumIntersection {
intersectsBounds: (box: THREE.Box3) => intersectionResultToBvh(CircularFrustumIntersection.intersectsBox(box, localFrustum)),
intersectsTriangle: (tri: ExtendedTriangle, _index: number, contained: boolean) => {
// If the whole node was CONTAINED, every triangle is inside — fast path
if (contained) {
const worldPoint = tri.a.clone().applyMatrix4(mesh.matrixWorld);
const depth = worldFrustum.axisNormalized.dot(worldPoint.clone().sub(worldFrustum.apex));
results.push({ object: mesh, point: worldPoint, depth, triangle: tri });
return !findAll; // stop if we only need first hit
return !findAll;
}
// Test all three vertices; take the closest that's inside
let bestDepth = Infinity;
let bestLocal: THREE.Vector3 | null = null;
let bestLocal: THREE.Vector3 | undefined = undefined;
for (const v of [tri.a, tri.b, tri.c] as THREE.Vector3[]) {
const tryPoint = (v: THREE.Vector3) => {
const d = CircularFrustumIntersection.pointAxialDepth(v, localFrustum);
if (d !== 'NOT_INTERSECTED') {
if (d < bestDepth) {
bestDepth = d;
bestLocal = v;
}
if (d !== 'NOT_INTERSECTED' && (d as number) < bestDepth) {
bestDepth = d as number;
bestLocal = v.clone();
}
};
// 1. Test vertices
tryPoint(tri.a);
tryPoint(tri.b);
tryPoint(tri.c);
// 2. For each edge, find the point closest to the frustum axis ray,
// and also the point closest to the apex.
// This catches triangles that straddle the cone surface.
const edges: [THREE.Vector3, THREE.Vector3][] = [
[tri.a, tri.b],
[tri.b, tri.c],
[tri.c, tri.a],
];
for (const [a, b] of edges) {
const edge = b.clone().sub(a);
const toA = a.clone().sub(localFrustum.apex);
// Closest point on edge segment to the axis ray
const edgeDir = edge.clone().normalize();
const axisDotEdge = localFrustum.axisNormalized.dot(edgeDir);
const denom = 1 - axisDotEdge * axisDotEdge;
if (Math.abs(denom) > 1e-10) {
const t = (
localFrustum.axisNormalized.dot(toA) * axisDotEdge
- toA.dot(edgeDir)
) / denom;
const edgeLen = edge.length();
const tClamped = Math.max(0, Math.min(edgeLen, t));
const pointOnEdge = a.clone().addScaledVector(edgeDir, tClamped);
tryPoint(pointOnEdge);
}
// Closest point on edge to the apex itself
const tApex = Math.max(0, Math.min(1, -toA.dot(edge) / edge.lengthSq()));
tryPoint(a.clone().addScaledVector(edge, tApex));
}
// Also test closest point on triangle to the frustum axis ray
const ray = new THREE.Ray(localFrustum.apex, localFrustum.axisNormalized);
const closest = new THREE.Vector3();
tri.closestPointToPoint(ray.origin, closest); // ExtendedTriangle has this
const d = CircularFrustumIntersection.pointAxialDepth(closest, localFrustum);
if (d !== 'NOT_INTERSECTED') {
if (d < bestDepth) {
bestDepth = d;
bestLocal = closest;
}
}
// 3. Closest point on the triangle face to the apex
const closestOnFace = new THREE.Vector3();
tri.closestPointToPoint(localFrustum.apex, closestOnFace);
if (!isNaN(closestOnFace.x))
tryPoint(closestOnFace);
if (bestLocal) {
const worldPoint = bestLocal.clone().applyMatrix4(mesh.matrixWorld);
if (bestLocal !== undefined) {
const worldPoint = (bestLocal as THREE.Vector3).clone().applyMatrix4(mesh.matrixWorld);
const worldDepth = worldFrustum.axisNormalized.dot(worldPoint.clone().sub(worldFrustum.apex));
results.push({ object: mesh, point: worldPoint, depth: worldDepth, triangle: tri });
return !findAll;