diff --git a/client/src/helpers/circularFrustumIntersect.ts b/client/src/helpers/circularFrustumIntersect.ts index 94a3f47..2c66f5d 100644 --- a/client/src/helpers/circularFrustumIntersect.ts +++ b/client/src/helpers/circularFrustumIntersect.ts @@ -110,12 +110,7 @@ export class CircularFrustumIntersection { : 'NOT_INTERSECTED'; } - // ─── Local frustum construction ────────────────────────────────────────────── - - /** - * Transform a world-space CircularFrustum into an object's local space. - * Note: halfAngle is only preserved exactly under uniform scale. - */ + //world-space to an object's local space private toObjectLocalSpace(invWorldMatrix: THREE.Matrix4): CircularFrustum { return this.frustum.transform(invWorldMatrix); } @@ -140,6 +135,8 @@ export class CircularFrustumIntersection { if (this.insersectsSphere(boundingSphere) === 'NOT_INTERSECTED') return []; + const axisRay = new THREE.Ray(localFrustum.apex, localFrustum.axisNormalized); + const results: HitResult[] = []; if (!geometry.boundsTree) @@ -208,12 +205,19 @@ export class CircularFrustumIntersection { tryPoint(a.clone().addScaledVector(edge, tApex)); } - // 3. Closest point on the triangle face to the apex + // 3. Closest point on triangle face to the apex (using THREE.Triangle, not ExtendedTriangle) + const threeTri = new THREE.Triangle(tri.a, tri.b, tri.c); const closestOnFace = new THREE.Vector3(); - tri.closestPointToPoint(localFrustum.apex, closestOnFace); + threeTri.closestPointToPoint(localFrustum.apex, closestOnFace); if (!isNaN(closestOnFace.x)) tryPoint(closestOnFace); + // 4. Axis ray through the face — catches large faces the cone passes through + const faceHit = new THREE.Vector3(); + if (axisRay.intersectTriangle(tri.a, tri.b, tri.c, false, faceHit)) { + tryPoint(faceHit); + } + if (bestLocal !== undefined) { const worldPoint = (bestLocal as THREE.Vector3).clone().applyMatrix4(mesh.matrixWorld); const worldDepth = worldFrustum.axisNormalized.dot(worldPoint.clone().sub(worldFrustum.apex));