Recast: rewrite triangle intersect code and increase chunk index buffer

There was a bug where some rays went through triangles, albeit rare. The issue was that overlapping chunk indices were truncated if the number exceedes the maximum of the stack buffer. The intersection code was originally also written to ignore back faces, but we need back faces, the intersection code has been rewritten with a known robust algorithm and the index buffer has been increased and made static. The rays no longer go through triangles.
This commit is contained in:
Kawe Mazidjatari 2024-11-06 21:27:18 +01:00
parent cd78e5afa9
commit ce0fa2028f

View File

@ -27,59 +27,47 @@
#include "NavEditor/Include/Editor.h"
#include <naveditor/include/GameUtils.h>
// note(amos): based on the MöllerTrumbore algorithm, see:
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
static inline bool intersectSegmentTriangle(const float* sp, const float* sq,
const float* a, const float* b, const float* c,
float &t, const bool delayedDiv)
const float* a, const float* b, const float* c, float& t)
{
float ab[3], ac[3], qp[3], ap[3], norm[3], e[3];
float ab[3], ac[3], qp[3];
rdVsub(ab, b, a);
rdVsub(ac, c, a);
rdVsub(qp, sp, sq);
// Compute triangle normal. Can be precalculated or cached if
// intersecting multiple segments against the same triangle
rdVcross(norm, ab, ac);
// Compute denominator d. If abs(d) < EPS, segment is parallel to or points
// away from triangle, so exit early
const float d = rdVdot(qp, norm);
if (rdMathFabsf(d) < RD_EPS) return false;
// Compute intersection t value of pq with plane of triangle. A ray
// intersects if 0 <= t. Segment intersects if 0 <= t <= 1. Delay
// dividing by d until intersection has been found to pierce triangle
rdVsub(ap, sp, a);
t = rdVdot(ap, norm);
rdVsub(qp, sq, sp);
const bool frontFace = (d > 0.0f);
float h[3];
rdVcross(h, qp, ac);
// For segment; exclude the 't > d' and 't < d' code line for a ray test
if (rdLikely(frontFace))
{ if (t < 0.0f || t > d) return false; }
else
{ if (t > 0.0f || t < d) return false; }
const float d = rdVdot(ab, h);
// Compute barycentric coordinate components and test if within bounds
rdVcross(e, qp, ap);
const float v = rdVdot(ac, e);
if (rdLikely(frontFace))
{ if (v < 0.0f || v > d) return false; }
else
{ if (v > 0.0f || v < d) return false; }
if (d > -RD_EPS && d < RD_EPS)
return false; // Ray is parallel to the triangle plane
const float w = -rdVdot(ab, e);
float s[3];
rdVsub(s, sp, a);
if (rdLikely(frontFace))
{ if (w < 0.0f || v + w > d) return false; }
else
{ if (w > 0.0f || v + w < d) return false; }
const float id = 1.0f/d;
const float u = rdVdot(s, h)*id;
if (delayedDiv)
{
// Segment/ray intersects triangle. Perform delayed division
t /= d;
}
if (u < 0.0f || u > 1.0f)
return false;
float q[3];
rdVcross(q, s, ab);
const float v = rdVdot(qp, q)*id;
if (v < 0.0f || u+v > 1.0f)
return false;
t = rdVdot(ac, q)*id;
if (t < 0.0f || t > 1.0f)
return false;
// Segment/ray intersects triangle
return true;
}
@ -505,6 +493,9 @@ bool InputGeom::saveGeomSet(const BuildSettings* settings)
return true;
}
static const int MAX_CHUNK_INDICES = 0xffff;
static int s_chunkIndices[MAX_CHUNK_INDICES];
bool InputGeom::raycastMesh(const float* src, const float* dst, const unsigned int mask, int* vidx, float* tmin) const
{
if (vidx)
@ -592,8 +583,7 @@ bool InputGeom::raycastMesh(const float* src, const float* dst, const unsigned i
q[0] = src[0] + (dst[0]-src[0]) * btmax;
q[1] = src[1] + (dst[1]-src[1]) * btmax;
int cid[512];
const int ncid = rcGetChunksOverlappingSegment(m_chunkyMesh, p, q, cid, 512);
const int ncid = rcGetChunksOverlappingSegment(m_chunkyMesh, p, q, s_chunkIndices, MAX_CHUNK_INDICES);
if (!ncid)
return false;
@ -601,7 +591,7 @@ bool InputGeom::raycastMesh(const float* src, const float* dst, const unsigned i
for (int i = 0; i < ncid; ++i)
{
const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[cid[i]];
const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[s_chunkIndices[i]];
const int* tris = &m_chunkyMesh->tris[node.i*3];
const int ntris = node.n;
@ -611,7 +601,7 @@ bool InputGeom::raycastMesh(const float* src, const float* dst, const unsigned i
if (intersectSegmentTriangle(src, dst,
&verts[tris[j]*3],
&verts[tris[j+1]*3],
&verts[tris[j+2]*3], t, tmin != nullptr))
&verts[tris[j+2]*3], t))
{
// Caller isn't interested in finding the closest intersection; return out.
if (!tmin)