forked from mirror/DotRecast
[Upstream] Improved variable naming in RecastArea.cpp (recastnavigation #636)
* Cleanup compact heightfield functions in RecastArea.cpp * More improved variable names for RecastArea.cpp * Improved variable names and documentation in rcOffsetPoly * Don't normalize the miter bisector in rcOffsetPoly since this needs to be proportional to both segment normals. * Moved vector normalization out of rcOffsetPoly into a helper function rcVsafeNormalize * Rename local variables in rcOffsetPoly * Rename BMiterX/Z to cornerMIterX/Z * Also fixed some comment descriptions * Added docstring for rcVsafeNormalize * Improved clarity of a few comments
This commit is contained in:
parent
a57b8f26d6
commit
27250908d7
|
@ -230,6 +230,24 @@ namespace DotRecast.Core
|
|||
}
|
||||
}
|
||||
|
||||
public const float EPSILON = 1e-6f;
|
||||
|
||||
/// Normalizes the vector if the length is greater than zero.
|
||||
/// If the magnitude is zero, the vector is unchanged.
|
||||
/// @param[in,out] v The vector to normalize. [(x, y, z)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SafeNormalize()
|
||||
{
|
||||
float sqMag = RcMath.Sqr(x) + RcMath.Sqr(y) + RcMath.Sqr(z);
|
||||
if (sqMag > EPSILON)
|
||||
{
|
||||
float inverseMag = 1.0f / (float)Math.Sqrt(sqMag);
|
||||
x *= inverseMag;
|
||||
y *= inverseMag;
|
||||
z *= inverseMag;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Min(float[] @in, int i)
|
||||
{
|
||||
|
@ -262,7 +280,7 @@ namespace DotRecast.Core
|
|||
y = Math.Max(y, @in[i + 1]);
|
||||
z = Math.Max(z, @in[i + 2]);
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{x}, {y}, {z}";
|
||||
|
|
|
@ -25,106 +25,163 @@ namespace DotRecast.Recast
|
|||
{
|
||||
public static class PolyUtils
|
||||
{
|
||||
public static bool PointInPoly(float[] verts, RcVec3f p)
|
||||
// public static bool PointInPoly(float[] verts, RcVec3f p)
|
||||
// {
|
||||
// bool c = false;
|
||||
// int i, j;
|
||||
// for (i = 0, j = verts.Length - 3; i < verts.Length; j = i, i += 3)
|
||||
// {
|
||||
// int vi = i;
|
||||
// int vj = j;
|
||||
// if (((verts[vi + 2] > p.z) != (verts[vj + 2] > p.z))
|
||||
// && (p.x < (verts[vj] - verts[vi]) * (p.z - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2])
|
||||
// + verts[vi]))
|
||||
// c = !c;
|
||||
// }
|
||||
//
|
||||
// return c;
|
||||
// }
|
||||
|
||||
// TODO (graham): This is duplicated in the ConvexVolumeTool in RecastDemo
|
||||
/// Checks if a point is contained within a polygon
|
||||
///
|
||||
/// @param[in] numVerts Number of vertices in the polygon
|
||||
/// @param[in] verts The polygon vertices
|
||||
/// @param[in] point The point to check
|
||||
/// @returns true if the point lies within the polygon, false otherwise.
|
||||
public static bool PointInPoly(float[] verts, RcVec3f point)
|
||||
{
|
||||
int i, j;
|
||||
bool c = false;
|
||||
for (i = 0, j = verts.Length / 3 - 1; i < verts.Length / 3; j = i++)
|
||||
bool inPoly = false;
|
||||
for (int i = 0, j = verts.Length / 3 - 1; i < verts.Length / 3; j = i++)
|
||||
{
|
||||
RcVec3f vi = RcVec3f.Of(verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2]);
|
||||
RcVec3f vj = RcVec3f.Of(verts[j * 3], verts[j * 3 + 1], verts[j * 3 + 2]);
|
||||
if (((vi.z > p.z) != (vj.z > p.z))
|
||||
&& (p.x < (vj.x - vi.x) * (p.z - vi.z) / (vj.z - vi.z) + vi.x))
|
||||
if (vi.z > point.z == vj.z > point.z)
|
||||
{
|
||||
c = !c;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (point.x >= (vj.x - vi.x) * (point.z - vi.z) / (vj.z - vi.z) + vi.x)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
inPoly = !inPoly;
|
||||
}
|
||||
|
||||
return c;
|
||||
return inPoly;
|
||||
}
|
||||
|
||||
public static int OffsetPoly(float[] verts, int nverts, float offset, float[] outVerts, int maxOutVerts)
|
||||
/// Expands a convex polygon along its vertex normals by the given offset amount.
|
||||
/// Inserts extra vertices to bevel sharp corners.
|
||||
///
|
||||
/// Helper function to offset convex polygons for rcMarkConvexPolyArea.
|
||||
///
|
||||
/// @ingroup recast
|
||||
///
|
||||
/// @param[in] verts The vertices of the polygon [Form: (x, y, z) * @p numVerts]
|
||||
/// @param[in] numVerts The number of vertices in the polygon.
|
||||
/// @param[in] offset How much to offset the polygon by. [Units: wu]
|
||||
/// @param[out] outVerts The offset vertices (should hold up to 2 * @p numVerts) [Form: (x, y, z) * return value]
|
||||
/// @param[in] maxOutVerts The max number of vertices that can be stored to @p outVerts.
|
||||
/// @returns Number of vertices in the offset polygon or 0 if too few vertices in @p outVerts.
|
||||
public static int OffsetPoly(float[] verts, int numVerts, float offset, float[] outVerts, int maxOutVerts)
|
||||
{
|
||||
// Defines the limit at which a miter becomes a bevel.
|
||||
// Similar in behavior to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit
|
||||
const float MITER_LIMIT = 1.20f;
|
||||
|
||||
int n = 0;
|
||||
int numOutVerts = 0;
|
||||
|
||||
for (int i = 0; i < nverts; i++)
|
||||
for (int vertIndex = 0; vertIndex < numVerts; vertIndex++)
|
||||
{
|
||||
int a = (i + nverts - 1) % nverts;
|
||||
int b = i;
|
||||
int c = (i + 1) % nverts;
|
||||
int va = a * 3;
|
||||
int vb = b * 3;
|
||||
int vc = c * 3;
|
||||
float dx0 = verts[vb] - verts[va];
|
||||
float dy0 = verts[vb + 2] - verts[va + 2];
|
||||
float d0 = dx0 * dx0 + dy0 * dy0;
|
||||
if (d0 > 1e-6f)
|
||||
{
|
||||
d0 = (float)(1.0f / Math.Sqrt(d0));
|
||||
dx0 *= d0;
|
||||
dy0 *= d0;
|
||||
}
|
||||
int vertIndexA = (vertIndex + numVerts - 1) % numVerts;
|
||||
int vertIndexB = vertIndex;
|
||||
int vertIndexC = (vertIndex + 1) % numVerts;
|
||||
|
||||
RcVec3f vertA = RcVec3f.Of(verts, vertIndexA * 3);
|
||||
RcVec3f vertB = RcVec3f.Of(verts, vertIndexB * 3);
|
||||
RcVec3f vertC = RcVec3f.Of(verts, vertIndexC * 3);
|
||||
|
||||
// From A to B on the x/z plane
|
||||
RcVec3f prevSegmentDir = vertB.Subtract(vertA);
|
||||
prevSegmentDir.y = 0; // Squash onto x/z plane
|
||||
prevSegmentDir.SafeNormalize();
|
||||
|
||||
// From B to C on the x/z plane
|
||||
RcVec3f currSegmentDir = vertC.Subtract(vertB);
|
||||
currSegmentDir.y = 0; // Squash onto x/z plane
|
||||
currSegmentDir.SafeNormalize();
|
||||
|
||||
// The y component of the cross product of the two normalized segment directions.
|
||||
// The X and Z components of the cross product are both zero because the two
|
||||
// segment direction vectors fall within the x/z plane.
|
||||
float cross = currSegmentDir.x * prevSegmentDir.z - prevSegmentDir.x * currSegmentDir.z;
|
||||
|
||||
float dx1 = verts[vc] - verts[vb];
|
||||
float dy1 = verts[vc + 2] - verts[vb + 2];
|
||||
float d1 = dx1 * dx1 + dy1 * dy1;
|
||||
if (d1 > 1e-6f)
|
||||
{
|
||||
d1 = (float)(1.0f / Math.Sqrt(d1));
|
||||
dx1 *= d1;
|
||||
dy1 *= d1;
|
||||
}
|
||||
// CCW perpendicular vector to AB. The segment normal.
|
||||
float prevSegmentNormX = -prevSegmentDir.z;
|
||||
float prevSegmentNormZ = prevSegmentDir.x;
|
||||
|
||||
float dlx0 = -dy0;
|
||||
float dly0 = dx0;
|
||||
float dlx1 = -dy1;
|
||||
float dly1 = dx1;
|
||||
float cross = dx1 * dy0 - dx0 * dy1;
|
||||
float dmx = (dlx0 + dlx1) * 0.5f;
|
||||
float dmy = (dly0 + dly1) * 0.5f;
|
||||
float dmr2 = dmx * dmx + dmy * dmy;
|
||||
bool bevel = dmr2 * MITER_LIMIT * MITER_LIMIT < 1.0f;
|
||||
if (dmr2 > 1e-6f)
|
||||
{
|
||||
float scale = 1.0f / dmr2;
|
||||
dmx *= scale;
|
||||
dmy *= scale;
|
||||
}
|
||||
// CCW perpendicular vector to BC. The segment normal.
|
||||
float currSegmentNormX = -currSegmentDir.z;
|
||||
float currSegmentNormZ = currSegmentDir.x;
|
||||
|
||||
if (bevel && cross < 0.0f)
|
||||
// Average the two segment normals to get the proportional miter offset for B.
|
||||
// This isn't normalized because it's defining the distance and direction the corner will need to be
|
||||
// adjusted proportionally to the edge offsets to properly miter the adjoining edges.
|
||||
float cornerMiterX = (prevSegmentNormX + currSegmentNormX) * 0.5f;
|
||||
float cornerMiterZ = (prevSegmentNormZ + currSegmentNormZ) * 0.5f;
|
||||
float cornerMiterSqMag = RcMath.Sqr(cornerMiterX) + RcMath.Sqr(cornerMiterZ);
|
||||
|
||||
// If the magnitude of the segment normal average is less than about .69444,
|
||||
// the corner is an acute enough angle that the result should be beveled.
|
||||
bool bevel = cornerMiterSqMag * MITER_LIMIT * MITER_LIMIT < 1.0f;
|
||||
|
||||
// Scale the corner miter so it's proportional to how much the corner should be offset compared to the edges.
|
||||
if (cornerMiterSqMag > RcVec3f.EPSILON)
|
||||
{
|
||||
if (n + 2 > maxOutVerts)
|
||||
float scale = 1.0f / cornerMiterSqMag;
|
||||
cornerMiterX *= scale;
|
||||
cornerMiterZ *= scale;
|
||||
}
|
||||
|
||||
if (bevel && cross < 0.0f) // If the corner is convex and an acute enough angle, generate a bevel.
|
||||
{
|
||||
if (numOutVerts + 2 > maxOutVerts)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
float d = (1.0f - (dx0 * dx1 + dy0 * dy1)) * 0.5f;
|
||||
outVerts[n * 3 + 0] = verts[vb] + (-dlx0 + dx0 * d) * offset;
|
||||
outVerts[n * 3 + 1] = verts[vb + 1];
|
||||
outVerts[n * 3 + 2] = verts[vb + 2] + (-dly0 + dy0 * d) * offset;
|
||||
n++;
|
||||
outVerts[n * 3 + 0] = verts[vb] + (-dlx1 - dx1 * d) * offset;
|
||||
outVerts[n * 3 + 1] = verts[vb + 1];
|
||||
outVerts[n * 3 + 2] = verts[vb + 2] + (-dly1 - dy1 * d) * offset;
|
||||
n++;
|
||||
// Generate two bevel vertices at a distances from B proportional to the angle between the two segments.
|
||||
// Move each bevel vertex out proportional to the given offset.
|
||||
float d = (1.0f - (prevSegmentDir.x * currSegmentDir.x + prevSegmentDir.z * currSegmentDir.z)) * 0.5f;
|
||||
|
||||
outVerts[numOutVerts * 3 + 0] = vertB.x + (-prevSegmentNormX + prevSegmentDir.x * d) * offset;
|
||||
outVerts[numOutVerts * 3 + 1] = vertB.y;
|
||||
outVerts[numOutVerts * 3 + 2] = vertB.z + (-prevSegmentNormZ + prevSegmentDir.z * d) * offset;
|
||||
numOutVerts++;
|
||||
|
||||
outVerts[numOutVerts * 3 + 0] = vertB.x + (-currSegmentNormX - currSegmentDir.x * d) * offset;
|
||||
outVerts[numOutVerts * 3 + 1] = vertB.y;
|
||||
outVerts[numOutVerts * 3 + 2] = vertB.z + (-currSegmentNormZ - currSegmentDir.z * d) * offset;
|
||||
numOutVerts++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (n + 1 > maxOutVerts)
|
||||
if (numOutVerts + 1 > maxOutVerts)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
outVerts[n * 3 + 0] = verts[vb] - dmx * offset;
|
||||
outVerts[n * 3 + 1] = verts[vb + 1];
|
||||
outVerts[n * 3 + 2] = verts[vb + 2] - dmy * offset;
|
||||
n++;
|
||||
// Move B along the miter direction by the specified offset.
|
||||
outVerts[numOutVerts * 3 + 0] = vertB.x - cornerMiterX * offset;
|
||||
outVerts[numOutVerts * 3 + 1] = vertB.y;
|
||||
outVerts[numOutVerts * 3 + 2] = vertB.z - cornerMiterZ * offset;
|
||||
numOutVerts++;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
return numOutVerts;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,115 +27,142 @@ namespace DotRecast.Recast
|
|||
|
||||
public static class RecastArea
|
||||
{
|
||||
/// @par
|
||||
///
|
||||
/// Basically, any spans that are closer to a boundary or obstruction than the specified radius
|
||||
/// are marked as unwalkable.
|
||||
/// Erodes the walkable area within the heightfield by the specified radius.
|
||||
///
|
||||
/// Basically, any spans that are closer to a boundary or obstruction than the specified radius
|
||||
/// are marked as un-walkable.
|
||||
///
|
||||
/// This method is usually called immediately after the heightfield has been built.
|
||||
///
|
||||
///
|
||||
/// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius
|
||||
public static void ErodeWalkableArea(RcTelemetry ctx, int radius, RcCompactHeightfield chf)
|
||||
/// @ingroup recast
|
||||
///
|
||||
/// @param[in,out] context The build context to use during the operation.
|
||||
/// @param[in] erosionRadius The radius of erosion. [Limits: 0 < value < 255] [Units: vx]
|
||||
/// @param[in,out] compactHeightfield The populated compact heightfield to erode.
|
||||
/// @returns True if the operation completed successfully.
|
||||
public static void ErodeWalkableArea(RcTelemetry context, int erosionRadius, RcCompactHeightfield compactHeightfield)
|
||||
{
|
||||
int w = chf.width;
|
||||
int h = chf.height;
|
||||
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_ERODE_AREA);
|
||||
int xSize = compactHeightfield.width;
|
||||
int zSize = compactHeightfield.height;
|
||||
int zStride = xSize; // For readability
|
||||
|
||||
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_ERODE_AREA);
|
||||
|
||||
int[] distanceToBoundary = new int[compactHeightfield.spanCount];
|
||||
Array.Fill(distanceToBoundary, 255);
|
||||
|
||||
int[] dist = new int[chf.spanCount];
|
||||
Array.Fill(dist, 255);
|
||||
// Mark boundary cells.
|
||||
for (int y = 0; y < h; ++y)
|
||||
for (int z = 0; z < zSize; ++z)
|
||||
{
|
||||
for (int x = 0; x < w; ++x)
|
||||
for (int x = 0; x < xSize; ++x)
|
||||
{
|
||||
RcCompactCell c = chf.cells[x + y * w];
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
|
||||
for (int spanIndex = cell.index, maxSpanIndex = cell.index + cell.count; spanIndex < maxSpanIndex; ++spanIndex)
|
||||
{
|
||||
if (chf.areas[i] == RC_NULL_AREA)
|
||||
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
|
||||
{
|
||||
dist[i] = 0;
|
||||
distanceToBoundary[spanIndex] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
RcCompactSpan s = chf.spans[i];
|
||||
int nc = 0;
|
||||
for (int dir = 0; dir < 4; ++dir)
|
||||
RcCompactSpan span = compactHeightfield.spans[spanIndex];
|
||||
|
||||
// Check that there is a non-null adjacent span in each of the 4 cardinal directions.
|
||||
int neighborCount = 0;
|
||||
for (int direction = 0; direction < 4; ++direction)
|
||||
{
|
||||
if (RecastCommon.GetCon(s, dir) != RC_NOT_CONNECTED)
|
||||
int neighborConnection = RecastCommon.GetCon(span, direction);
|
||||
if (neighborConnection == RC_NOT_CONNECTED)
|
||||
{
|
||||
int nx = x + RecastCommon.GetDirOffsetX(dir);
|
||||
int ny = y + RecastCommon.GetDirOffsetY(dir);
|
||||
int nidx = chf.cells[nx + ny * w].index + RecastCommon.GetCon(s, dir);
|
||||
if (chf.areas[nidx] != RC_NULL_AREA)
|
||||
{
|
||||
nc++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int neighborX = x + RecastCommon.GetDirOffsetX(direction);
|
||||
int neighborZ = z + RecastCommon.GetDirOffsetY(direction);
|
||||
int neighborSpanIndex = compactHeightfield.cells[neighborX + neighborZ * zStride].index + RecastCommon.GetCon(span, direction);
|
||||
if (compactHeightfield.areas[neighborSpanIndex] == RC_NULL_AREA)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
neighborCount++;
|
||||
}
|
||||
|
||||
// At least one missing neighbour.
|
||||
if (nc != 4)
|
||||
dist[i] = 0;
|
||||
// At least one missing neighbour, so this is a boundary cell.
|
||||
if (neighborCount != 4)
|
||||
{
|
||||
distanceToBoundary[spanIndex] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int nd;
|
||||
int newDistance;
|
||||
|
||||
// Pass 1
|
||||
for (int y = 0; y < h; ++y)
|
||||
for (int z = 0; z < zSize; ++z)
|
||||
{
|
||||
for (int x = 0; x < w; ++x)
|
||||
for (int x = 0; x < xSize; ++x)
|
||||
{
|
||||
RcCompactCell c = chf.cells[x + y * w];
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
|
||||
int maxSpanIndex = cell.index + cell.count;
|
||||
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
|
||||
{
|
||||
RcCompactSpan s = chf.spans[i];
|
||||
RcCompactSpan span = compactHeightfield.spans[spanIndex];
|
||||
|
||||
if (RecastCommon.GetCon(s, 0) != RC_NOT_CONNECTED)
|
||||
if (RecastCommon.GetCon(span, 0) != RC_NOT_CONNECTED)
|
||||
{
|
||||
// (-1,0)
|
||||
int ax = x + RecastCommon.GetDirOffsetX(0);
|
||||
int ay = y + RecastCommon.GetDirOffsetY(0);
|
||||
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 0);
|
||||
RcCompactSpan @as = chf.spans[ai];
|
||||
nd = Math.Min(dist[ai] + 2, 255);
|
||||
if (nd < dist[i])
|
||||
dist[i] = nd;
|
||||
int aX = x + RecastCommon.GetDirOffsetX(0);
|
||||
int aY = z + RecastCommon.GetDirOffsetY(0);
|
||||
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + RecastCommon.GetCon(span, 0);
|
||||
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
|
||||
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
|
||||
if (newDistance < distanceToBoundary[spanIndex])
|
||||
{
|
||||
distanceToBoundary[spanIndex] = newDistance;
|
||||
}
|
||||
|
||||
// (-1,-1)
|
||||
if (RecastCommon.GetCon(@as, 3) != RC_NOT_CONNECTED)
|
||||
if (RecastCommon.GetCon(aSpan, 3) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int aax = ax + RecastCommon.GetDirOffsetX(3);
|
||||
int aay = ay + RecastCommon.GetDirOffsetY(3);
|
||||
int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 3);
|
||||
nd = Math.Min(dist[aai] + 3, 255);
|
||||
if (nd < dist[i])
|
||||
dist[i] = nd;
|
||||
int bX = aX + RecastCommon.GetDirOffsetX(3);
|
||||
int bY = aY + RecastCommon.GetDirOffsetY(3);
|
||||
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + RecastCommon.GetCon(aSpan, 3);
|
||||
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
|
||||
if (newDistance < distanceToBoundary[spanIndex])
|
||||
{
|
||||
distanceToBoundary[spanIndex] = newDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (RecastCommon.GetCon(s, 3) != RC_NOT_CONNECTED)
|
||||
if (RecastCommon.GetCon(span, 3) != RC_NOT_CONNECTED)
|
||||
{
|
||||
// (0,-1)
|
||||
int ax = x + RecastCommon.GetDirOffsetX(3);
|
||||
int ay = y + RecastCommon.GetDirOffsetY(3);
|
||||
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 3);
|
||||
RcCompactSpan @as = chf.spans[ai];
|
||||
nd = Math.Min(dist[ai] + 2, 255);
|
||||
if (nd < dist[i])
|
||||
dist[i] = nd;
|
||||
int aX = x + RecastCommon.GetDirOffsetX(3);
|
||||
int aY = z + RecastCommon.GetDirOffsetY(3);
|
||||
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + RecastCommon.GetCon(span, 3);
|
||||
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
|
||||
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
|
||||
if (newDistance < distanceToBoundary[spanIndex])
|
||||
{
|
||||
distanceToBoundary[spanIndex] = newDistance;
|
||||
}
|
||||
|
||||
// (1,-1)
|
||||
if (RecastCommon.GetCon(@as, 2) != RC_NOT_CONNECTED)
|
||||
if (RecastCommon.GetCon(aSpan, 2) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int aax = ax + RecastCommon.GetDirOffsetX(2);
|
||||
int aay = ay + RecastCommon.GetDirOffsetY(2);
|
||||
int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 2);
|
||||
nd = Math.Min(dist[aai] + 3, 255);
|
||||
if (nd < dist[i])
|
||||
dist[i] = nd;
|
||||
int bX = aX + RecastCommon.GetDirOffsetX(2);
|
||||
int bY = aY + RecastCommon.GetDirOffsetY(2);
|
||||
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + RecastCommon.GetCon(aSpan, 2);
|
||||
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
|
||||
if (newDistance < distanceToBoundary[spanIndex])
|
||||
{
|
||||
distanceToBoundary[spanIndex] = newDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,219 +170,289 @@ namespace DotRecast.Recast
|
|||
}
|
||||
|
||||
// Pass 2
|
||||
for (int y = h - 1; y >= 0; --y)
|
||||
for (int z = zSize - 1; z >= 0; --z)
|
||||
{
|
||||
for (int x = w - 1; x >= 0; --x)
|
||||
for (int x = xSize - 1; x >= 0; --x)
|
||||
{
|
||||
RcCompactCell c = chf.cells[x + y * w];
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
|
||||
int maxSpanIndex = cell.index + cell.count;
|
||||
for (int i = cell.index; i < maxSpanIndex; ++i)
|
||||
{
|
||||
RcCompactSpan s = chf.spans[i];
|
||||
RcCompactSpan span = compactHeightfield.spans[i];
|
||||
|
||||
if (RecastCommon.GetCon(s, 2) != RC_NOT_CONNECTED)
|
||||
if (RecastCommon.GetCon(span, 2) != RC_NOT_CONNECTED)
|
||||
{
|
||||
// (1,0)
|
||||
int ax = x + RecastCommon.GetDirOffsetX(2);
|
||||
int ay = y + RecastCommon.GetDirOffsetY(2);
|
||||
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 2);
|
||||
RcCompactSpan @as = chf.spans[ai];
|
||||
nd = Math.Min(dist[ai] + 2, 255);
|
||||
if (nd < dist[i])
|
||||
dist[i] = nd;
|
||||
int aX = x + RecastCommon.GetDirOffsetX(2);
|
||||
int aY = z + RecastCommon.GetDirOffsetY(2);
|
||||
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + RecastCommon.GetCon(span, 2);
|
||||
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
|
||||
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
|
||||
if (newDistance < distanceToBoundary[i])
|
||||
{
|
||||
distanceToBoundary[i] = newDistance;
|
||||
}
|
||||
|
||||
// (1,1)
|
||||
if (RecastCommon.GetCon(@as, 1) != RC_NOT_CONNECTED)
|
||||
if (RecastCommon.GetCon(aSpan, 1) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int aax = ax + RecastCommon.GetDirOffsetX(1);
|
||||
int aay = ay + RecastCommon.GetDirOffsetY(1);
|
||||
int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 1);
|
||||
nd = Math.Min(dist[aai] + 3, 255);
|
||||
if (nd < dist[i])
|
||||
dist[i] = nd;
|
||||
}
|
||||
}
|
||||
|
||||
if (RecastCommon.GetCon(s, 1) != RC_NOT_CONNECTED)
|
||||
{
|
||||
// (0,1)
|
||||
int ax = x + RecastCommon.GetDirOffsetX(1);
|
||||
int ay = y + RecastCommon.GetDirOffsetY(1);
|
||||
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, 1);
|
||||
RcCompactSpan @as = chf.spans[ai];
|
||||
nd = Math.Min(dist[ai] + 2, 255);
|
||||
if (nd < dist[i])
|
||||
dist[i] = nd;
|
||||
|
||||
// (-1,1)
|
||||
if (RecastCommon.GetCon(@as, 0) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int aax = ax + RecastCommon.GetDirOffsetX(0);
|
||||
int aay = ay + RecastCommon.GetDirOffsetY(0);
|
||||
int aai = chf.cells[aax + aay * w].index + RecastCommon.GetCon(@as, 0);
|
||||
nd = Math.Min(dist[aai] + 3, 255);
|
||||
if (nd < dist[i])
|
||||
dist[i] = nd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int thr = radius * 2;
|
||||
for (int i = 0; i < chf.spanCount; ++i)
|
||||
if (dist[i] < thr)
|
||||
chf.areas[i] = RC_NULL_AREA;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// This filter is usually applied after applying area id's using functions
|
||||
/// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea.
|
||||
///
|
||||
/// @see rcCompactHeightfield
|
||||
public static bool MedianFilterWalkableArea(RcTelemetry ctx, RcCompactHeightfield chf)
|
||||
{
|
||||
int w = chf.width;
|
||||
int h = chf.height;
|
||||
|
||||
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_MEDIAN_AREA);
|
||||
|
||||
int[] areas = new int[chf.spanCount];
|
||||
|
||||
for (int y = 0; y < h; ++y)
|
||||
{
|
||||
for (int x = 0; x < w; ++x)
|
||||
{
|
||||
RcCompactCell c = chf.cells[x + y * w];
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
{
|
||||
RcCompactSpan s = chf.spans[i];
|
||||
if (chf.areas[i] == RC_NULL_AREA)
|
||||
{
|
||||
areas[i] = chf.areas[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] nei = new int[9];
|
||||
for (int j = 0; j < 9; ++j)
|
||||
nei[j] = chf.areas[i];
|
||||
|
||||
for (int dir = 0; dir < 4; ++dir)
|
||||
{
|
||||
if (RecastCommon.GetCon(s, dir) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int ax = x + RecastCommon.GetDirOffsetX(dir);
|
||||
int ay = y + RecastCommon.GetDirOffsetY(dir);
|
||||
int ai = chf.cells[ax + ay * w].index + RecastCommon.GetCon(s, dir);
|
||||
if (chf.areas[ai] != RC_NULL_AREA)
|
||||
nei[dir * 2 + 0] = chf.areas[ai];
|
||||
|
||||
RcCompactSpan @as = chf.spans[ai];
|
||||
int dir2 = (dir + 1) & 0x3;
|
||||
if (RecastCommon.GetCon(@as, dir2) != RC_NOT_CONNECTED)
|
||||
int bX = aX + RecastCommon.GetDirOffsetX(1);
|
||||
int bY = aY + RecastCommon.GetDirOffsetY(1);
|
||||
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + RecastCommon.GetCon(aSpan, 1);
|
||||
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
|
||||
if (newDistance < distanceToBoundary[i])
|
||||
{
|
||||
int ax2 = ax + RecastCommon.GetDirOffsetX(dir2);
|
||||
int ay2 = ay + RecastCommon.GetDirOffsetY(dir2);
|
||||
int ai2 = chf.cells[ax2 + ay2 * w].index + RecastCommon.GetCon(@as, dir2);
|
||||
if (chf.areas[ai2] != RC_NULL_AREA)
|
||||
nei[dir * 2 + 1] = chf.areas[ai2];
|
||||
distanceToBoundary[i] = newDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array.Sort(nei);
|
||||
areas[i] = nei[4];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chf.areas = areas;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// The value of spacial parameters are in world units.
|
||||
///
|
||||
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
|
||||
public static void MarkBoxArea(RcTelemetry ctx, float[] bmin, float[] bmax, RcAreaModification areaMod, RcCompactHeightfield chf)
|
||||
{
|
||||
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_BOX_AREA);
|
||||
|
||||
int minx = (int)((bmin[0] - chf.bmin.x) / chf.cs);
|
||||
int miny = (int)((bmin[1] - chf.bmin.y) / chf.ch);
|
||||
int minz = (int)((bmin[2] - chf.bmin.z) / chf.cs);
|
||||
int maxx = (int)((bmax[0] - chf.bmin.x) / chf.cs);
|
||||
int maxy = (int)((bmax[1] - chf.bmin.y) / chf.ch);
|
||||
int maxz = (int)((bmax[2] - chf.bmin.z) / chf.cs);
|
||||
|
||||
if (maxx < 0)
|
||||
return;
|
||||
if (minx >= chf.width)
|
||||
return;
|
||||
if (maxz < 0)
|
||||
return;
|
||||
if (minz >= chf.height)
|
||||
return;
|
||||
|
||||
if (minx < 0)
|
||||
minx = 0;
|
||||
if (maxx >= chf.width)
|
||||
maxx = chf.width - 1;
|
||||
if (minz < 0)
|
||||
minz = 0;
|
||||
if (maxz >= chf.height)
|
||||
maxz = chf.height - 1;
|
||||
|
||||
for (int z = minz; z <= maxz; ++z)
|
||||
{
|
||||
for (int x = minx; x <= maxx; ++x)
|
||||
{
|
||||
RcCompactCell c = chf.cells[x + z * chf.width];
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
{
|
||||
RcCompactSpan s = chf.spans[i];
|
||||
if (s.y >= miny && s.y <= maxy)
|
||||
if (RecastCommon.GetCon(span, 1) != RC_NOT_CONNECTED)
|
||||
{
|
||||
if (chf.areas[i] != RC_NULL_AREA)
|
||||
chf.areas[i] = areaMod.Apply(chf.areas[i]);
|
||||
// (0,1)
|
||||
int aX = x + RecastCommon.GetDirOffsetX(1);
|
||||
int aY = z + RecastCommon.GetDirOffsetY(1);
|
||||
int aIndex = compactHeightfield.cells[aX + aY * xSize].index + RecastCommon.GetCon(span, 1);
|
||||
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
|
||||
newDistance = Math.Min(distanceToBoundary[aIndex] + 2, 255);
|
||||
if (newDistance < distanceToBoundary[i])
|
||||
{
|
||||
distanceToBoundary[i] = newDistance;
|
||||
}
|
||||
|
||||
// (-1,1)
|
||||
if (RecastCommon.GetCon(aSpan, 0) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int bX = aX + RecastCommon.GetDirOffsetX(0);
|
||||
int bY = aY + RecastCommon.GetDirOffsetY(0);
|
||||
int bIndex = compactHeightfield.cells[bX + bY * xSize].index + RecastCommon.GetCon(aSpan, 0);
|
||||
newDistance = Math.Min(distanceToBoundary[bIndex] + 3, 255);
|
||||
if (newDistance < distanceToBoundary[i])
|
||||
{
|
||||
distanceToBoundary[i] = newDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int minBoundaryDistance = erosionRadius * 2;
|
||||
for (int spanIndex = 0; spanIndex < compactHeightfield.spanCount; ++spanIndex)
|
||||
{
|
||||
if (distanceToBoundary[spanIndex] < minBoundaryDistance)
|
||||
{
|
||||
compactHeightfield.areas[spanIndex] = RC_NULL_AREA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool PointInPoly(float[] verts, RcVec3f p)
|
||||
/// Applies a median filter to walkable area types (based on area id), removing noise.
|
||||
///
|
||||
/// This filter is usually applied after applying area id's using functions
|
||||
/// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea.
|
||||
///
|
||||
/// @see rcCompactHeightfield
|
||||
/// @ingroup recast
|
||||
///
|
||||
/// @param[in,out] context The build context to use during the operation.
|
||||
/// @param[in,out] compactHeightfield A populated compact heightfield.
|
||||
/// @returns True if the operation completed successfully.
|
||||
public static bool MedianFilterWalkableArea(RcTelemetry context, RcCompactHeightfield compactHeightfield)
|
||||
{
|
||||
bool c = false;
|
||||
int i, j;
|
||||
for (i = 0, j = verts.Length - 3; i < verts.Length; j = i, i += 3)
|
||||
int xSize = compactHeightfield.width;
|
||||
int zSize = compactHeightfield.height;
|
||||
int zStride = xSize; // For readability
|
||||
|
||||
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MEDIAN_AREA);
|
||||
|
||||
int[] areas = new int[compactHeightfield.spanCount];
|
||||
|
||||
for (int z = 0; z < zSize; ++z)
|
||||
{
|
||||
int vi = i;
|
||||
int vj = j;
|
||||
if (((verts[vi + 2] > p.z) != (verts[vj + 2] > p.z))
|
||||
&& (p.x < (verts[vj] - verts[vi]) * (p.z - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2])
|
||||
+ verts[vi]))
|
||||
c = !c;
|
||||
for (int x = 0; x < xSize; ++x)
|
||||
{
|
||||
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
|
||||
int maxSpanIndex = cell.index + cell.count;
|
||||
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
|
||||
{
|
||||
RcCompactSpan span = compactHeightfield.spans[spanIndex];
|
||||
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
|
||||
{
|
||||
areas[spanIndex] = compactHeightfield.areas[spanIndex];
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] neighborAreas = new int[9];
|
||||
for (int neighborIndex = 0; neighborIndex < 9; ++neighborIndex)
|
||||
{
|
||||
neighborAreas[neighborIndex] = compactHeightfield.areas[spanIndex];
|
||||
}
|
||||
|
||||
for (int dir = 0; dir < 4; ++dir)
|
||||
{
|
||||
if (RecastCommon.GetCon(span, dir) == RC_NOT_CONNECTED)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int aX = x + RecastCommon.GetDirOffsetX(dir);
|
||||
int aZ = z + RecastCommon.GetDirOffsetY(dir);
|
||||
int aIndex = compactHeightfield.cells[aX + aZ * zStride].index + RecastCommon.GetCon(span, dir);
|
||||
if (compactHeightfield.areas[aIndex] != RC_NULL_AREA)
|
||||
{
|
||||
neighborAreas[dir * 2 + 0] = compactHeightfield.areas[aIndex];
|
||||
}
|
||||
|
||||
RcCompactSpan aSpan = compactHeightfield.spans[aIndex];
|
||||
int dir2 = (dir + 1) & 0x3;
|
||||
int neighborConnection2 = RecastCommon.GetCon(aSpan, dir2);
|
||||
if (neighborConnection2 != RC_NOT_CONNECTED)
|
||||
{
|
||||
int bX = aX + RecastCommon.GetDirOffsetX(dir2);
|
||||
int bZ = aZ + RecastCommon.GetDirOffsetY(dir2);
|
||||
int bIndex = compactHeightfield.cells[bX + bZ * zStride].index + RecastCommon.GetCon(aSpan, dir2);
|
||||
if (compactHeightfield.areas[bIndex] != RC_NULL_AREA)
|
||||
{
|
||||
neighborAreas[dir * 2 + 1] = compactHeightfield.areas[bIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array.Sort(neighborAreas);
|
||||
areas[spanIndex] = neighborAreas[4];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
compactHeightfield.areas = areas;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @par
|
||||
/// Applies an area id to all spans within the specified bounding box. (AABB)
|
||||
///
|
||||
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
|
||||
/// @ingroup recast
|
||||
///
|
||||
/// @param[in,out] context The build context to use during the operation.
|
||||
/// @param[in] boxMinBounds The minimum extents of the bounding box. [(x, y, z)] [Units: wu]
|
||||
/// @param[in] boxMaxBounds The maximum extents of the bounding box. [(x, y, z)] [Units: wu]
|
||||
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
|
||||
/// @param[in,out] compactHeightfield A populated compact heightfield.
|
||||
public static void MarkBoxArea(RcTelemetry context, float[] boxMinBounds, float[] boxMaxBounds, RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
|
||||
{
|
||||
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_BOX_AREA);
|
||||
|
||||
int xSize = compactHeightfield.width;
|
||||
int zSize = compactHeightfield.height;
|
||||
int zStride = xSize; // For readability
|
||||
|
||||
// Find the footprint of the box area in grid cell coordinates.
|
||||
int minX = (int)((boxMinBounds[0] - compactHeightfield.bmin.x) / compactHeightfield.cs);
|
||||
int minY = (int)((boxMinBounds[1] - compactHeightfield.bmin.y) / compactHeightfield.ch);
|
||||
int minZ = (int)((boxMinBounds[2] - compactHeightfield.bmin.z) / compactHeightfield.cs);
|
||||
int maxX = (int)((boxMaxBounds[0] - compactHeightfield.bmin.x) / compactHeightfield.cs);
|
||||
int maxY = (int)((boxMaxBounds[1] - compactHeightfield.bmin.y) / compactHeightfield.ch);
|
||||
int maxZ = (int)((boxMaxBounds[2] - compactHeightfield.bmin.z) / compactHeightfield.cs);
|
||||
|
||||
if (maxX < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (minX >= xSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxZ < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (minZ >= zSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (minX < 0)
|
||||
{
|
||||
minX = 0;
|
||||
}
|
||||
|
||||
if (maxX >= xSize)
|
||||
{
|
||||
maxX = xSize - 1;
|
||||
}
|
||||
|
||||
if (minZ < 0)
|
||||
{
|
||||
minZ = 0;
|
||||
}
|
||||
|
||||
if (maxZ >= zSize)
|
||||
{
|
||||
maxZ = zSize - 1;
|
||||
}
|
||||
|
||||
for (int z = minZ; z <= maxZ; ++z)
|
||||
{
|
||||
for (int x = minX; x <= maxX; ++x)
|
||||
{
|
||||
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
|
||||
int maxSpanIndex = cell.index + cell.count;
|
||||
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
|
||||
{
|
||||
RcCompactSpan span = compactHeightfield.spans[spanIndex];
|
||||
|
||||
// Skip if the span is outside the box extents.
|
||||
if (span.y < minY || span.y > maxY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if the span has been removed.
|
||||
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mark the span.
|
||||
compactHeightfield.areas[spanIndex] = areaId.Apply(compactHeightfield.areas[spanIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the area id to the all spans within the specified convex polygon.
|
||||
///
|
||||
/// The value of spacial parameters are in world units.
|
||||
///
|
||||
/// The y-values of the polygon vertices are ignored. So the polygon is effectively
|
||||
/// projected onto the xz-plane at @p hmin, then extruded to @p hmax.
|
||||
///
|
||||
///
|
||||
/// The y-values of the polygon vertices are ignored. So the polygon is effectively
|
||||
/// projected onto the xz-plane, translated to @p minY, and extruded to @p maxY.
|
||||
///
|
||||
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
|
||||
public static void MarkConvexPolyArea(RcTelemetry ctx, float[] verts, float hmin, float hmax, RcAreaModification areaMod,
|
||||
RcCompactHeightfield chf)
|
||||
/// @ingroup recast
|
||||
///
|
||||
/// @param[in,out] context The build context to use during the operation.
|
||||
/// @param[in] verts The vertices of the polygon [For: (x, y, z) * @p numVerts]
|
||||
/// @param[in] numVerts The number of vertices in the polygon.
|
||||
/// @param[in] minY The height of the base of the polygon. [Units: wu]
|
||||
/// @param[in] maxY The height of the top of the polygon. [Units: wu]
|
||||
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
|
||||
/// @param[in,out] compactHeightfield A populated compact heightfield.
|
||||
public static void MarkConvexPolyArea(RcTelemetry context, float[] verts,
|
||||
float minY, float maxY, RcAreaModification areaId,
|
||||
RcCompactHeightfield compactHeightfield)
|
||||
{
|
||||
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA);
|
||||
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CONVEXPOLY_AREA);
|
||||
|
||||
int xSize = compactHeightfield.width;
|
||||
int zSize = compactHeightfield.height;
|
||||
int zStride = xSize; // For readability
|
||||
|
||||
// Compute the bounding box of the polygon
|
||||
RcVec3f bmin = new RcVec3f();
|
||||
RcVec3f bmax = new RcVec3f();
|
||||
RcVec3f.Copy(ref bmin, verts, 0);
|
||||
|
@ -366,129 +463,214 @@ namespace DotRecast.Recast
|
|||
bmax.Max(verts, i);
|
||||
}
|
||||
|
||||
bmin.y = hmin;
|
||||
bmax.y = hmax;
|
||||
bmin.y = minY;
|
||||
bmax.y = maxY;
|
||||
|
||||
int minx = (int)((bmin.x - chf.bmin.x) / chf.cs);
|
||||
int miny = (int)((bmin.y - chf.bmin.y) / chf.ch);
|
||||
int minz = (int)((bmin.z - chf.bmin.z) / chf.cs);
|
||||
int maxx = (int)((bmax.x - chf.bmin.x) / chf.cs);
|
||||
int maxy = (int)((bmax.y - chf.bmin.y) / chf.ch);
|
||||
int maxz = (int)((bmax.z - chf.bmin.z) / chf.cs);
|
||||
// Compute the grid footprint of the polygon
|
||||
int minx = (int)((bmin.x - compactHeightfield.bmin.x) / compactHeightfield.cs);
|
||||
int miny = (int)((bmin.y - compactHeightfield.bmin.y) / compactHeightfield.ch);
|
||||
int minz = (int)((bmin.z - compactHeightfield.bmin.z) / compactHeightfield.cs);
|
||||
int maxx = (int)((bmax.x - compactHeightfield.bmin.x) / compactHeightfield.cs);
|
||||
int maxy = (int)((bmax.y - compactHeightfield.bmin.y) / compactHeightfield.ch);
|
||||
int maxz = (int)((bmax.z - compactHeightfield.bmin.z) / compactHeightfield.cs);
|
||||
|
||||
// Early-out if the polygon lies entirely outside the grid.
|
||||
if (maxx < 0)
|
||||
{
|
||||
return;
|
||||
if (minx >= chf.width)
|
||||
return;
|
||||
if (maxz < 0)
|
||||
return;
|
||||
if (minz >= chf.height)
|
||||
return;
|
||||
}
|
||||
|
||||
if (minx >= xSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxz < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (minz >= zSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp the polygon footprint to the grid
|
||||
if (minx < 0)
|
||||
{
|
||||
minx = 0;
|
||||
if (maxx >= chf.width)
|
||||
maxx = chf.width - 1;
|
||||
}
|
||||
|
||||
if (maxx >= xSize)
|
||||
{
|
||||
maxx = xSize - 1;
|
||||
}
|
||||
|
||||
if (minz < 0)
|
||||
{
|
||||
minz = 0;
|
||||
if (maxz >= chf.height)
|
||||
maxz = chf.height - 1;
|
||||
}
|
||||
|
||||
if (maxz >= zSize)
|
||||
{
|
||||
maxz = zSize - 1;
|
||||
}
|
||||
|
||||
// TODO: Optimize.
|
||||
for (int z = minz; z <= maxz; ++z)
|
||||
{
|
||||
for (int x = minx; x <= maxx; ++x)
|
||||
{
|
||||
RcCompactCell c = chf.cells[x + z * chf.width];
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
|
||||
int maxSpanIndex = cell.index + cell.count;
|
||||
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
|
||||
{
|
||||
RcCompactSpan s = chf.spans[i];
|
||||
if (chf.areas[i] == RC_NULL_AREA)
|
||||
continue;
|
||||
if (s.y >= miny && s.y <= maxy)
|
||||
{
|
||||
RcVec3f p = new RcVec3f();
|
||||
p.x = chf.bmin.x + (x + 0.5f) * chf.cs;
|
||||
p.y = 0;
|
||||
p.z = chf.bmin.z + (z + 0.5f) * chf.cs;
|
||||
RcCompactSpan span = compactHeightfield.spans[spanIndex];
|
||||
|
||||
if (PointInPoly(verts, p))
|
||||
{
|
||||
chf.areas[i] = areaMod.Apply(chf.areas[i]);
|
||||
}
|
||||
// Skip if span is removed.
|
||||
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
|
||||
continue;
|
||||
|
||||
// Skip if y extents don't overlap.
|
||||
if (span.y < miny || span.y > maxy)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RcVec3f point = new RcVec3f(
|
||||
compactHeightfield.bmin.x + (x + 0.5f) * compactHeightfield.cs,
|
||||
0,
|
||||
compactHeightfield.bmin.z + (z + 0.5f) * compactHeightfield.cs
|
||||
);
|
||||
|
||||
if (PolyUtils.PointInPoly(verts, point))
|
||||
{
|
||||
compactHeightfield.areas[spanIndex] = areaId.Apply(compactHeightfield.areas[spanIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// The value of spacial parameters are in world units.
|
||||
///
|
||||
|
||||
/// Applies the area id to all spans within the specified y-axis-aligned cylinder.
|
||||
///
|
||||
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
|
||||
public static void MarkCylinderArea(RcTelemetry ctx, float[] pos, float r, float h, RcAreaModification areaMod, RcCompactHeightfield chf)
|
||||
///
|
||||
/// @ingroup recast
|
||||
///
|
||||
/// @param[in,out] context The build context to use during the operation.
|
||||
/// @param[in] position The center of the base of the cylinder. [Form: (x, y, z)] [Units: wu]
|
||||
/// @param[in] radius The radius of the cylinder. [Units: wu] [Limit: > 0]
|
||||
/// @param[in] height The height of the cylinder. [Units: wu] [Limit: > 0]
|
||||
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
|
||||
/// @param[in,out] compactHeightfield A populated compact heightfield.
|
||||
public static void MarkCylinderArea(RcTelemetry context, float[] position, float radius, float height,
|
||||
RcAreaModification areaId, RcCompactHeightfield compactHeightfield)
|
||||
{
|
||||
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA);
|
||||
using var timer = context.ScopedTimer(RcTimerLabel.RC_TIMER_MARK_CYLINDER_AREA);
|
||||
|
||||
RcVec3f bmin = new RcVec3f();
|
||||
RcVec3f bmax = new RcVec3f();
|
||||
bmin.x = pos[0] - r;
|
||||
bmin.y = pos[1];
|
||||
bmin.z = pos[2] - r;
|
||||
bmax.x = pos[0] + r;
|
||||
bmax.y = pos[1] + h;
|
||||
bmax.z = pos[2] + r;
|
||||
float r2 = r * r;
|
||||
int xSize = compactHeightfield.width;
|
||||
int zSize = compactHeightfield.height;
|
||||
int zStride = xSize; // For readability
|
||||
|
||||
int minx = (int)((bmin.x - chf.bmin.x) / chf.cs);
|
||||
int miny = (int)((bmin.y - chf.bmin.y) / chf.ch);
|
||||
int minz = (int)((bmin.z - chf.bmin.z) / chf.cs);
|
||||
int maxx = (int)((bmax.x - chf.bmin.x) / chf.cs);
|
||||
int maxy = (int)((bmax.y - chf.bmin.y) / chf.ch);
|
||||
int maxz = (int)((bmax.z - chf.bmin.z) / chf.cs);
|
||||
// Compute the bounding box of the cylinder
|
||||
RcVec3f cylinderBBMin = new RcVec3f(
|
||||
position[0] - radius,
|
||||
position[1],
|
||||
position[2] - radius
|
||||
);
|
||||
|
||||
RcVec3f cylinderBBMax = new RcVec3f(
|
||||
position[0] + radius,
|
||||
position[1] + height,
|
||||
position[2] + radius
|
||||
);
|
||||
|
||||
// Compute the grid footprint of the cylinder
|
||||
int minx = (int)((cylinderBBMin.x - compactHeightfield.bmin.x) / compactHeightfield.cs);
|
||||
int miny = (int)((cylinderBBMin.y - compactHeightfield.bmin.y) / compactHeightfield.ch);
|
||||
int minz = (int)((cylinderBBMin.z - compactHeightfield.bmin.z) / compactHeightfield.cs);
|
||||
int maxx = (int)((cylinderBBMax.x - compactHeightfield.bmin.x) / compactHeightfield.cs);
|
||||
int maxy = (int)((cylinderBBMax.y - compactHeightfield.bmin.y) / compactHeightfield.ch);
|
||||
int maxz = (int)((cylinderBBMax.z - compactHeightfield.bmin.z) / compactHeightfield.cs);
|
||||
|
||||
// Early-out if the cylinder is completely outside the grid bounds.
|
||||
if (maxx < 0)
|
||||
{
|
||||
return;
|
||||
if (minx >= chf.width)
|
||||
}
|
||||
|
||||
if (minx >= xSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxz < 0)
|
||||
{
|
||||
return;
|
||||
if (minz >= chf.height)
|
||||
return;
|
||||
}
|
||||
|
||||
if (minz >= zSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp the cylinder bounds to the grid.
|
||||
if (minx < 0)
|
||||
{
|
||||
minx = 0;
|
||||
if (maxx >= chf.width)
|
||||
maxx = chf.width - 1;
|
||||
if (minz < 0)
|
||||
minz = 0;
|
||||
if (maxz >= chf.height)
|
||||
maxz = chf.height - 1;
|
||||
}
|
||||
|
||||
if (maxx >= xSize)
|
||||
{
|
||||
maxx = xSize - 1;
|
||||
}
|
||||
|
||||
if (minz < 0)
|
||||
{
|
||||
minz = 0;
|
||||
}
|
||||
|
||||
if (maxz >= zSize)
|
||||
{
|
||||
maxz = zSize - 1;
|
||||
}
|
||||
|
||||
float radiusSq = radius * radius;
|
||||
for (int z = minz; z <= maxz; ++z)
|
||||
{
|
||||
for (int x = minx; x <= maxx; ++x)
|
||||
{
|
||||
RcCompactCell c = chf.cells[x + z * chf.width];
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
RcCompactCell cell = compactHeightfield.cells[x + z * zStride];
|
||||
int maxSpanIndex = cell.index + cell.count;
|
||||
|
||||
float cellX = compactHeightfield.bmin[0] + ((float)x + 0.5f) * compactHeightfield.cs;
|
||||
float cellZ = compactHeightfield.bmin[2] + ((float)z + 0.5f) * compactHeightfield.cs;
|
||||
float deltaX = cellX - position[0];
|
||||
float deltaZ = cellZ - position[2];
|
||||
|
||||
// Skip this column if it's too far from the center point of the cylinder.
|
||||
if (RcMath.Sqr(deltaX) + RcMath.Sqr(deltaZ) >= radiusSq)
|
||||
{
|
||||
RcCompactSpan s = chf.spans[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chf.areas[i] == RC_NULL_AREA)
|
||||
continue;
|
||||
// Mark all overlapping spans
|
||||
for (int spanIndex = cell.index; spanIndex < maxSpanIndex; ++spanIndex)
|
||||
{
|
||||
RcCompactSpan span = compactHeightfield.spans[spanIndex];
|
||||
|
||||
if (s.y >= miny && s.y <= maxy)
|
||||
// Skip if span is removed.
|
||||
if (compactHeightfield.areas[spanIndex] == RC_NULL_AREA)
|
||||
{
|
||||
float sx = chf.bmin.x + (x + 0.5f) * chf.cs;
|
||||
float sz = chf.bmin.z + (z + 0.5f) * chf.cs;
|
||||
float dx = sx - pos[0];
|
||||
float dz = sz - pos[2];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dx * dx + dz * dz < r2)
|
||||
{
|
||||
chf.areas[i] = areaMod.Apply(chf.areas[i]);
|
||||
}
|
||||
// Mark if y extents overlap.
|
||||
if (span.y >= miny && span.y <= maxy)
|
||||
{
|
||||
compactHeightfield.areas[spanIndex] = areaId.Apply(compactHeightfield.areas[spanIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,6 +110,7 @@ public class RecastSoloMeshTest
|
|||
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
|
||||
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
|
||||
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, bmin, bmax);
|
||||
|
||||
//
|
||||
// Step 2. Rasterize input polygon soup.
|
||||
//
|
||||
|
@ -125,22 +126,20 @@ public class RecastSoloMeshTest
|
|||
|
||||
// Allocate array that can hold triangle area types.
|
||||
// If you have multiple meshes you need to process, allocate
|
||||
// and array which can hold the max number of triangles you need to
|
||||
// process.
|
||||
// and array which can hold the max number of triangles you need to process.
|
||||
|
||||
// Find triangles which are walkable based on their slope and rasterize
|
||||
// them.
|
||||
// If your input data is multiple meshes, you can transform them here,
|
||||
// calculate
|
||||
// Find triangles which are walkable based on their slope and rasterize them.
|
||||
// If your input data is multiple meshes, you can transform them here, calculate
|
||||
// the are type for each of the meshes and rasterize them.
|
||||
int[] m_triareas = Recast.MarkWalkableTriangles(m_ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
|
||||
cfg.walkableAreaMod);
|
||||
int[] m_triareas = Recast.MarkWalkableTriangles(m_ctx, cfg.walkableSlopeAngle, verts, tris, ntris, cfg.walkableAreaMod);
|
||||
RecastRasterization.RasterizeTriangles(m_solid, verts, tris, m_triareas, ntris, cfg.walkableClimb, m_ctx);
|
||||
//
|
||||
// Step 3. Filter walkables surfaces.
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// Step 3. Filter walkable surfaces.
|
||||
//
|
||||
|
||||
// Once all geometry is rasterized, we do initial pass of filtering to
|
||||
// remove unwanted overhangs caused by the conservative rasterization
|
||||
// as well as filter spans where the character cannot possibly stand.
|
||||
|
|
Loading…
Reference in New Issue