forked from mirror/DotRecast
1368 lines
51 KiB
C#
1368 lines
51 KiB
C#
/*
|
|
Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
|
recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
|
|
DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
using System;
|
|
using DotRecast.Core;
|
|
|
|
namespace DotRecast.Recast
|
|
{
|
|
using static RcConstants;
|
|
|
|
public static class RecastMesh
|
|
{
|
|
public const int MAX_MESH_VERTS_POLY = 0xffff;
|
|
public const int VERTEX_BUCKET_COUNT = (1 << 12);
|
|
|
|
|
|
private static void BuildMeshAdjacency(int[] polys, int npolys, int nverts, int vertsPerPoly)
|
|
{
|
|
// Based on code by Eric Lengyel from:
|
|
// http://www.terathon.com/code/edges.php
|
|
|
|
int maxEdgeCount = npolys * vertsPerPoly;
|
|
int[] firstEdge = new int[nverts + maxEdgeCount];
|
|
int nextEdge = nverts;
|
|
int edgeCount = 0;
|
|
|
|
RcEdge[] edges = new RcEdge[maxEdgeCount];
|
|
|
|
for (int i = 0; i < nverts; i++)
|
|
firstEdge[i] = RC_MESH_NULL_IDX;
|
|
|
|
for (int i = 0; i < npolys; ++i)
|
|
{
|
|
int t = i * vertsPerPoly * 2;
|
|
for (int j = 0; j < vertsPerPoly; ++j)
|
|
{
|
|
if (polys[t + j] == RC_MESH_NULL_IDX)
|
|
break;
|
|
int v0 = polys[t + j];
|
|
int v1 = (j + 1 >= vertsPerPoly || polys[t + j + 1] == RC_MESH_NULL_IDX)
|
|
? polys[t + 0]
|
|
: polys[t + j + 1];
|
|
if (v0 < v1)
|
|
{
|
|
RcEdge edge = new RcEdge();
|
|
edges[edgeCount] = edge;
|
|
edge.vert[0] = v0;
|
|
edge.vert[1] = v1;
|
|
edge.poly[0] = i;
|
|
edge.polyEdge[0] = j;
|
|
edge.poly[1] = i;
|
|
edge.polyEdge[1] = 0;
|
|
// Insert edge
|
|
firstEdge[nextEdge + edgeCount] = firstEdge[v0];
|
|
firstEdge[v0] = edgeCount;
|
|
edgeCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < npolys; ++i)
|
|
{
|
|
int t = i * vertsPerPoly * 2;
|
|
for (int j = 0; j < vertsPerPoly; ++j)
|
|
{
|
|
if (polys[t + j] == RC_MESH_NULL_IDX)
|
|
break;
|
|
int v0 = polys[t + j];
|
|
int v1 = (j + 1 >= vertsPerPoly || polys[t + j + 1] == RC_MESH_NULL_IDX)
|
|
? polys[t + 0]
|
|
: polys[t + j + 1];
|
|
if (v0 > v1)
|
|
{
|
|
for (int e = firstEdge[v1]; e != RC_MESH_NULL_IDX; e = firstEdge[nextEdge + e])
|
|
{
|
|
RcEdge edge = edges[e];
|
|
if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1])
|
|
{
|
|
edge.poly[1] = i;
|
|
edge.polyEdge[1] = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store adjacency
|
|
for (int i = 0; i < edgeCount; ++i)
|
|
{
|
|
RcEdge e = edges[i];
|
|
if (e.poly[0] != e.poly[1])
|
|
{
|
|
int p0 = e.poly[0] * vertsPerPoly * 2;
|
|
int p1 = e.poly[1] * vertsPerPoly * 2;
|
|
polys[p0 + vertsPerPoly + e.polyEdge[0]] = e.poly[1];
|
|
polys[p1 + vertsPerPoly + e.polyEdge[1]] = e.poly[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
private static int ComputeVertexHash(int x, int y, int z)
|
|
{
|
|
uint h1 = 0x8da6b343; // Large multiplicative constants;
|
|
uint h2 = 0xd8163841; // here arbitrarily chosen primes
|
|
uint h3 = 0xcb1ab31f;
|
|
|
|
uint n = h1 * (uint)x + h2 * (uint)y + h3 * (uint)z;
|
|
return (int)(n & (VERTEX_BUCKET_COUNT - 1));
|
|
}
|
|
|
|
private static int AddVertex(int x, int y, int z, int[] verts, int[] firstVert, int[] nextVert, ref int nv)
|
|
{
|
|
int bucket = ComputeVertexHash(x, 0, z);
|
|
int i = firstVert[bucket];
|
|
|
|
while (i != -1)
|
|
{
|
|
int v = i * 3;
|
|
if (verts[v + 0] == x && (Math.Abs(verts[v + 1] - y) <= 2) && verts[v + 2] == z)
|
|
return i;
|
|
i = nextVert[i]; // next
|
|
}
|
|
|
|
// Could not find, create new.
|
|
i = nv;
|
|
nv++;
|
|
int v2 = i * 3;
|
|
verts[v2 + 0] = x;
|
|
verts[v2 + 1] = y;
|
|
verts[v2 + 2] = z;
|
|
nextVert[i] = firstVert[bucket];
|
|
firstVert[bucket] = i;
|
|
|
|
return i;
|
|
}
|
|
|
|
public static int Prev(int i, int n)
|
|
{
|
|
return i - 1 >= 0 ? i - 1 : n - 1;
|
|
}
|
|
|
|
public static int Next(int i, int n)
|
|
{
|
|
return i + 1 < n ? i + 1 : 0;
|
|
}
|
|
|
|
private static int Area2(int[] verts, int a, int b, int c)
|
|
{
|
|
return (verts[b + 0] - verts[a + 0]) * (verts[c + 2] - verts[a + 2])
|
|
- (verts[c + 0] - verts[a + 0]) * (verts[b + 2] - verts[a + 2]);
|
|
}
|
|
|
|
// Returns true iff c is strictly to the left of the directed
|
|
// line through a to b.
|
|
public static bool Left(int[] verts, int a, int b, int c)
|
|
{
|
|
return Area2(verts, a, b, c) < 0;
|
|
}
|
|
|
|
public static bool LeftOn(int[] verts, int a, int b, int c)
|
|
{
|
|
return Area2(verts, a, b, c) <= 0;
|
|
}
|
|
|
|
private static bool Collinear(int[] verts, int a, int b, int c)
|
|
{
|
|
return Area2(verts, a, b, c) == 0;
|
|
}
|
|
|
|
// Returns true iff ab properly intersects cd: they share
|
|
// a point interior to both segments. The properness of the
|
|
// intersection is ensured by using strict leftness.
|
|
private static bool IntersectProp(int[] verts, int a, int b, int c, int d)
|
|
{
|
|
// Eliminate improper cases.
|
|
if (Collinear(verts, a, b, c) || Collinear(verts, a, b, d) || Collinear(verts, c, d, a)
|
|
|| Collinear(verts, c, d, b))
|
|
return false;
|
|
|
|
return (Left(verts, a, b, c) ^ Left(verts, a, b, d)) && (Left(verts, c, d, a) ^ Left(verts, c, d, b));
|
|
}
|
|
|
|
// Returns T iff (a,b,c) are collinear and point c lies
|
|
// on the closed segement ab.
|
|
private static bool Between(int[] verts, int a, int b, int c)
|
|
{
|
|
if (!Collinear(verts, a, b, c))
|
|
return false;
|
|
// If ab not vertical, check betweenness on x; else on y.
|
|
if (verts[a + 0] != verts[b + 0])
|
|
return ((verts[a + 0] <= verts[c + 0]) && (verts[c + 0] <= verts[b + 0]))
|
|
|| ((verts[a + 0] >= verts[c + 0]) && (verts[c + 0] >= verts[b + 0]));
|
|
else
|
|
return ((verts[a + 2] <= verts[c + 2]) && (verts[c + 2] <= verts[b + 2]))
|
|
|| ((verts[a + 2] >= verts[c + 2]) && (verts[c + 2] >= verts[b + 2]));
|
|
}
|
|
|
|
// Returns true iff segments ab and cd intersect, properly or improperly.
|
|
public static bool Intersect(int[] verts, int a, int b, int c, int d)
|
|
{
|
|
if (IntersectProp(verts, a, b, c, d))
|
|
return true;
|
|
else if (Between(verts, a, b, c) || Between(verts, a, b, d) || Between(verts, c, d, a)
|
|
|| Between(verts, c, d, b))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
public static bool Vequal(int[] verts, int a, int b)
|
|
{
|
|
return verts[a + 0] == verts[b + 0] && verts[a + 2] == verts[b + 2];
|
|
}
|
|
|
|
// Returns T iff (v_i, v_j) is a proper internal *or* external
|
|
// diagonal of P, *ignoring edges incident to v_i and v_j*.
|
|
private static bool Diagonalie(int i, int j, int n, int[] verts, int[] indices)
|
|
{
|
|
int d0 = (indices[i] & 0x0fffffff) * 4;
|
|
int d1 = (indices[j] & 0x0fffffff) * 4;
|
|
|
|
// For each edge (k,k+1) of P
|
|
for (int k = 0; k < n; k++)
|
|
{
|
|
int k1 = Next(k, n);
|
|
// Skip edges incident to i or j
|
|
if (!((k == i) || (k1 == i) || (k == j) || (k1 == j)))
|
|
{
|
|
int p0 = (indices[k] & 0x0fffffff) * 4;
|
|
int p1 = (indices[k1] & 0x0fffffff) * 4;
|
|
|
|
if (Vequal(verts, d0, p0) || Vequal(verts, d1, p0) || Vequal(verts, d0, p1) || Vequal(verts, d1, p1))
|
|
continue;
|
|
|
|
if (Intersect(verts, d0, d1, p0, p1))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Returns true iff the diagonal (i,j) is strictly internal to the
|
|
// polygon P in the neighborhood of the i endpoint.
|
|
private static bool InCone(int i, int j, int n, int[] verts, int[] indices)
|
|
{
|
|
int pi = (indices[i] & 0x0fffffff) * 4;
|
|
int pj = (indices[j] & 0x0fffffff) * 4;
|
|
int pi1 = (indices[Next(i, n)] & 0x0fffffff) * 4;
|
|
int pin1 = (indices[Prev(i, n)] & 0x0fffffff) * 4;
|
|
// If P[i] is a convex vertex [ i+1 left or on (i-1,i) ].
|
|
if (LeftOn(verts, pin1, pi, pi1))
|
|
{
|
|
return Left(verts, pi, pj, pin1) && Left(verts, pj, pi, pi1);
|
|
}
|
|
|
|
// Assume (i-1,i,i+1) not collinear.
|
|
// else P[i] is reflex.
|
|
return !(LeftOn(verts, pi, pj, pi1) && LeftOn(verts, pj, pi, pin1));
|
|
}
|
|
|
|
// Returns T iff (v_i, v_j) is a proper internal
|
|
// diagonal of P.
|
|
private static bool Diagonal(int i, int j, int n, int[] verts, int[] indices)
|
|
{
|
|
return InCone(i, j, n, verts, indices) && Diagonalie(i, j, n, verts, indices);
|
|
}
|
|
|
|
private static bool DiagonalieLoose(int i, int j, int n, int[] verts, int[] indices)
|
|
{
|
|
int d0 = (indices[i] & 0x0fffffff) * 4;
|
|
int d1 = (indices[j] & 0x0fffffff) * 4;
|
|
|
|
// For each edge (k,k+1) of P
|
|
for (int k = 0; k < n; k++)
|
|
{
|
|
int k1 = Next(k, n);
|
|
// Skip edges incident to i or j
|
|
if (!((k == i) || (k1 == i) || (k == j) || (k1 == j)))
|
|
{
|
|
int p0 = (indices[k] & 0x0fffffff) * 4;
|
|
int p1 = (indices[k1] & 0x0fffffff) * 4;
|
|
|
|
if (Vequal(verts, d0, p0) || Vequal(verts, d1, p0) || Vequal(verts, d0, p1) || Vequal(verts, d1, p1))
|
|
continue;
|
|
|
|
if (IntersectProp(verts, d0, d1, p0, p1))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool InConeLoose(int i, int j, int n, int[] verts, int[] indices)
|
|
{
|
|
int pi = (indices[i] & 0x0fffffff) * 4;
|
|
int pj = (indices[j] & 0x0fffffff) * 4;
|
|
int pi1 = (indices[Next(i, n)] & 0x0fffffff) * 4;
|
|
int pin1 = (indices[Prev(i, n)] & 0x0fffffff) * 4;
|
|
|
|
// If P[i] is a convex vertex [ i+1 left or on (i-1,i) ].
|
|
if (LeftOn(verts, pin1, pi, pi1))
|
|
return LeftOn(verts, pi, pj, pin1) && LeftOn(verts, pj, pi, pi1);
|
|
// Assume (i-1,i,i+1) not collinear.
|
|
// else P[i] is reflex.
|
|
return !(LeftOn(verts, pi, pj, pi1) && LeftOn(verts, pj, pi, pin1));
|
|
}
|
|
|
|
private static bool DiagonalLoose(int i, int j, int n, int[] verts, int[] indices)
|
|
{
|
|
return InConeLoose(i, j, n, verts, indices) && DiagonalieLoose(i, j, n, verts, indices);
|
|
}
|
|
|
|
private static int Triangulate(int n, int[] verts, int[] indices, int[] tris)
|
|
{
|
|
int ntris = 0;
|
|
|
|
// The last bit of the index is used to indicate if the vertex can be removed.
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
int i1 = Next(i, n);
|
|
int i2 = Next(i1, n);
|
|
if (Diagonal(i, i2, n, verts, indices))
|
|
{
|
|
indices[i1] |= int.MinValue; // TODO : 체크 필요
|
|
}
|
|
}
|
|
|
|
while (n > 3)
|
|
{
|
|
int minLen = -1;
|
|
int mini = -1;
|
|
for (int minIdx = 0; minIdx < n; minIdx++)
|
|
{
|
|
int nextIdx1 = Next(minIdx, n);
|
|
if ((indices[nextIdx1] & 0x80000000) != 0)
|
|
{
|
|
int p0 = (indices[minIdx] & 0x0fffffff) * 4;
|
|
int p2 = (indices[Next(nextIdx1, n)] & 0x0fffffff) * 4;
|
|
|
|
int dx = verts[p2 + 0] - verts[p0 + 0];
|
|
int dy = verts[p2 + 2] - verts[p0 + 2];
|
|
int len = dx * dx + dy * dy;
|
|
|
|
if (minLen < 0 || len < minLen)
|
|
{
|
|
minLen = len;
|
|
mini = minIdx;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mini == -1)
|
|
{
|
|
// We might get here because the contour has overlapping segments, like this:
|
|
//
|
|
// A o-o=====o---o B
|
|
// / |C D| \
|
|
// o o o o
|
|
// : : : :
|
|
// We'll try to recover by loosing up the inCone test a bit so that a diagonal
|
|
// like A-B or C-D can be found and we can continue.
|
|
minLen = -1;
|
|
mini = -1;
|
|
for (int minIdx = 0; minIdx < n; minIdx++)
|
|
{
|
|
int nextIdx1 = Next(minIdx, n);
|
|
int nextIdx2 = Next(nextIdx1, n);
|
|
if (DiagonalLoose(minIdx, nextIdx2, n, verts, indices))
|
|
{
|
|
int p0 = (indices[minIdx] & 0x0fffffff) * 4;
|
|
int p2 = (indices[Next(nextIdx2, n)] & 0x0fffffff) * 4;
|
|
int dx = verts[p2 + 0] - verts[p0 + 0];
|
|
int dy = verts[p2 + 2] - verts[p0 + 2];
|
|
int len = dx * dx + dy * dy;
|
|
|
|
if (minLen < 0 || len < minLen)
|
|
{
|
|
minLen = len;
|
|
mini = minIdx;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mini == -1)
|
|
{
|
|
// The contour is messed up. This sometimes happens
|
|
// if the contour simplification is too aggressive.
|
|
return -ntris;
|
|
}
|
|
}
|
|
|
|
int i = mini;
|
|
int i1 = Next(i, n);
|
|
int i2 = Next(i1, n);
|
|
|
|
tris[ntris * 3] = indices[i] & 0x0fffffff;
|
|
tris[ntris * 3 + 1] = indices[i1] & 0x0fffffff;
|
|
tris[ntris * 3 + 2] = indices[i2] & 0x0fffffff;
|
|
ntris++;
|
|
|
|
// Removes P[i1] by copying P[i+1]...P[n-1] left one index.
|
|
n--;
|
|
for (int k = i1; k < n; k++)
|
|
indices[k] = indices[k + 1];
|
|
|
|
if (i1 >= n)
|
|
i1 = 0;
|
|
i = Prev(i1, n);
|
|
// Update diagonal flags.
|
|
if (Diagonal(Prev(i, n), i1, n, verts, indices))
|
|
indices[i] |= int.MinValue;
|
|
else
|
|
indices[i] &= 0x0fffffff;
|
|
|
|
if (Diagonal(i, Next(i1, n), n, verts, indices))
|
|
indices[i1] |= int.MinValue;
|
|
else
|
|
indices[i1] &= 0x0fffffff;
|
|
}
|
|
|
|
// Append the remaining triangle.
|
|
tris[ntris * 3] = indices[0] & 0x0fffffff;
|
|
tris[ntris * 3 + 1] = indices[1] & 0x0fffffff;
|
|
tris[ntris * 3 + 2] = indices[2] & 0x0fffffff;
|
|
ntris++;
|
|
|
|
return ntris;
|
|
}
|
|
|
|
private static int CountPolyVerts(int[] p, int j, int nvp)
|
|
{
|
|
for (int i = 0; i < nvp; ++i)
|
|
if (p[i + j] == RC_MESH_NULL_IDX)
|
|
return i;
|
|
return nvp;
|
|
}
|
|
|
|
private static bool Uleft(int[] verts, int a, int b, int c)
|
|
{
|
|
return (verts[b + 0] - verts[a + 0]) * (verts[c + 2] - verts[a + 2])
|
|
- (verts[c + 0] - verts[a + 0]) * (verts[b + 2] - verts[a + 2]) < 0;
|
|
}
|
|
|
|
private static int GetPolyMergeValue(int[] polys, int pa, int pb, int[] verts, out int ea, out int eb, int nvp)
|
|
{
|
|
ea = 0;
|
|
eb = 0;
|
|
|
|
int na = CountPolyVerts(polys, pa, nvp);
|
|
int nb = CountPolyVerts(polys, pb, nvp);
|
|
|
|
// If the merged polygon would be too big, do not merge.
|
|
if (na + nb - 2 > nvp)
|
|
return -1;
|
|
|
|
ea = -1;
|
|
eb = -1;
|
|
|
|
// Check if the polygons share an edge.
|
|
|
|
for (int i = 0; i < na; ++i)
|
|
{
|
|
int va0 = polys[pa + i];
|
|
int va1 = polys[pa + (i + 1) % na];
|
|
if (va0 > va1)
|
|
{
|
|
(va0, va1) = (va1, va0);
|
|
}
|
|
|
|
for (int j = 0; j < nb; ++j)
|
|
{
|
|
int vb0 = polys[pb + j];
|
|
int vb1 = polys[pb + (j + 1) % nb];
|
|
if (vb0 > vb1)
|
|
{
|
|
(vb0, vb1) = (vb1, vb0);
|
|
}
|
|
|
|
if (va0 == vb0 && va1 == vb1)
|
|
{
|
|
ea = i;
|
|
eb = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No common edge, cannot merge.
|
|
if (ea == -1 || eb == -1)
|
|
return -1;
|
|
|
|
// Check to see if the merged polygon would be convex.
|
|
int va, vb, vc;
|
|
|
|
va = polys[pa + (ea + na - 1) % na];
|
|
vb = polys[pa + ea];
|
|
vc = polys[pb + (eb + 2) % nb];
|
|
if (!Uleft(verts, va * 3, vb * 3, vc * 3))
|
|
return -1;
|
|
|
|
va = polys[pb + (eb + nb - 1) % nb];
|
|
vb = polys[pb + eb];
|
|
vc = polys[pa + (ea + 2) % na];
|
|
if (!Uleft(verts, va * 3, vb * 3, vc * 3))
|
|
return -1;
|
|
|
|
va = polys[pa + ea];
|
|
vb = polys[pa + (ea + 1) % na];
|
|
|
|
int dx = verts[va * 3 + 0] - verts[vb * 3 + 0];
|
|
int dy = verts[va * 3 + 2] - verts[vb * 3 + 2];
|
|
|
|
return (dx * dx) + (dy * dy);
|
|
}
|
|
|
|
private static void MergePolyVerts(int[] polys, int pa, int pb, int ea, int eb, int tmp, int nvp)
|
|
{
|
|
int na = CountPolyVerts(polys, pa, nvp);
|
|
int nb = CountPolyVerts(polys, pb, nvp);
|
|
|
|
// Merge polygons.
|
|
Array.Fill(polys, RC_MESH_NULL_IDX, tmp, (tmp + nvp) - (tmp));
|
|
int n = 0;
|
|
// Add pa
|
|
for (int i = 0; i < na - 1; ++i)
|
|
{
|
|
polys[tmp + n] = polys[pa + (ea + 1 + i) % na];
|
|
n++;
|
|
}
|
|
|
|
// Add pb
|
|
for (int i = 0; i < nb - 1; ++i)
|
|
{
|
|
polys[tmp + n] = polys[pb + (eb + 1 + i) % nb];
|
|
n++;
|
|
}
|
|
|
|
Array.Copy(polys, tmp, polys, pa, nvp);
|
|
}
|
|
|
|
private static int PushFront(int v, int[] arr, int an)
|
|
{
|
|
an++;
|
|
for (int i = an - 1; i > 0; --i)
|
|
arr[i] = arr[i - 1];
|
|
arr[0] = v;
|
|
return an;
|
|
}
|
|
|
|
private static int PushBack(int v, int[] arr, int an)
|
|
{
|
|
arr[an] = v;
|
|
an++;
|
|
return an;
|
|
}
|
|
|
|
private static bool CanRemoveVertex(RcTelemetry ctx, RcPolyMesh mesh, int rem)
|
|
{
|
|
int nvp = mesh.nvp;
|
|
|
|
// Count number of polygons to remove.
|
|
int numTouchedVerts = 0;
|
|
int numRemainingEdges = 0;
|
|
for (int i = 0; i < mesh.npolys; ++i)
|
|
{
|
|
int p = i * nvp * 2;
|
|
int nv = CountPolyVerts(mesh.polys, p, nvp);
|
|
int numRemoved = 0;
|
|
int numVerts = 0;
|
|
for (int j = 0; j < nv; ++j)
|
|
{
|
|
if (mesh.polys[p + j] == rem)
|
|
{
|
|
numTouchedVerts++;
|
|
numRemoved++;
|
|
}
|
|
|
|
numVerts++;
|
|
}
|
|
|
|
if (numRemoved != 0)
|
|
{
|
|
numRemainingEdges += numVerts - (numRemoved + 1);
|
|
}
|
|
}
|
|
|
|
// There would be too few edges remaining to create a polygon.
|
|
// This can happen for example when a tip of a triangle is marked
|
|
// as deletion, but there are no other polys that share the vertex.
|
|
// In this case, the vertex should not be removed.
|
|
if (numRemainingEdges <= 2)
|
|
return false;
|
|
|
|
// Find edges which share the removed vertex.
|
|
int maxEdges = numTouchedVerts * 2;
|
|
int nedges = 0;
|
|
int[] edges = new int[maxEdges * 3];
|
|
|
|
for (int i = 0; i < mesh.npolys; ++i)
|
|
{
|
|
int p = i * nvp * 2;
|
|
int nv = CountPolyVerts(mesh.polys, p, nvp);
|
|
|
|
// Collect edges which touches the removed vertex.
|
|
for (int j = 0, k = nv - 1; j < nv; k = j++)
|
|
{
|
|
if (mesh.polys[p + j] == rem || mesh.polys[p + k] == rem)
|
|
{
|
|
// Arrange edge so that a=rem.
|
|
int a = mesh.polys[p + j], b = mesh.polys[p + k];
|
|
if (b == rem)
|
|
{
|
|
int temp = a;
|
|
a = b;
|
|
b = temp;
|
|
}
|
|
|
|
// Check if the edge exists
|
|
bool exists = false;
|
|
for (int m = 0; m < nedges; ++m)
|
|
{
|
|
int e = m * 3;
|
|
if (edges[e + 1] == b)
|
|
{
|
|
// Exists, increment vertex share count.
|
|
edges[e + 2]++;
|
|
exists = true;
|
|
}
|
|
}
|
|
|
|
// Add new edge.
|
|
if (!exists)
|
|
{
|
|
int e = nedges * 3;
|
|
edges[e + 0] = a;
|
|
edges[e + 1] = b;
|
|
edges[e + 2] = 1;
|
|
nedges++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// There should be no more than 2 open edges.
|
|
// This catches the case that two non-adjacent polygons
|
|
// share the removed vertex. In that case, do not remove the vertex.
|
|
int numOpenEdges = 0;
|
|
for (int i = 0; i < nedges; ++i)
|
|
{
|
|
if (edges[i * 3 + 2] < 2)
|
|
numOpenEdges++;
|
|
}
|
|
|
|
if (numOpenEdges > 2)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void RemoveVertex(RcTelemetry ctx, RcPolyMesh mesh, int rem, int maxTris)
|
|
{
|
|
int nvp = mesh.nvp;
|
|
|
|
// Count number of polygons to remove.
|
|
int numRemovedVerts = 0;
|
|
for (int i = 0; i < mesh.npolys; ++i)
|
|
{
|
|
int p = i * nvp * 2;
|
|
int nv = CountPolyVerts(mesh.polys, p, nvp);
|
|
for (int j = 0; j < nv; ++j)
|
|
{
|
|
if (mesh.polys[p + j] == rem)
|
|
numRemovedVerts++;
|
|
}
|
|
}
|
|
|
|
int nedges = 0;
|
|
int[] edges = new int[numRemovedVerts * nvp * 4];
|
|
|
|
int nhole = 0;
|
|
int[] hole = new int[numRemovedVerts * nvp];
|
|
|
|
int nhreg = 0;
|
|
int[] hreg = new int[numRemovedVerts * nvp];
|
|
|
|
int nharea = 0;
|
|
int[] harea = new int[numRemovedVerts * nvp];
|
|
|
|
for (int i = 0; i < mesh.npolys; ++i)
|
|
{
|
|
int p = i * nvp * 2;
|
|
int nv = CountPolyVerts(mesh.polys, p, nvp);
|
|
bool hasRem = false;
|
|
for (int j = 0; j < nv; ++j)
|
|
if (mesh.polys[p + j] == rem)
|
|
hasRem = true;
|
|
if (hasRem)
|
|
{
|
|
// Collect edges which does not touch the removed vertex.
|
|
for (int j = 0, k = nv - 1; j < nv; k = j++)
|
|
{
|
|
if (mesh.polys[p + j] != rem && mesh.polys[p + k] != rem)
|
|
{
|
|
int e = nedges * 4;
|
|
edges[e + 0] = mesh.polys[p + k];
|
|
edges[e + 1] = mesh.polys[p + j];
|
|
edges[e + 2] = mesh.regs[i];
|
|
edges[e + 3] = mesh.areas[i];
|
|
nedges++;
|
|
}
|
|
}
|
|
|
|
// Remove the polygon.
|
|
int p2 = (mesh.npolys - 1) * nvp * 2;
|
|
if (p != p2)
|
|
{
|
|
Array.Copy(mesh.polys, p2, mesh.polys, p, nvp);
|
|
}
|
|
|
|
Array.Fill(mesh.polys, RC_MESH_NULL_IDX, p + nvp, (p + nvp + nvp) - (p + nvp));
|
|
mesh.regs[i] = mesh.regs[mesh.npolys - 1];
|
|
mesh.areas[i] = mesh.areas[mesh.npolys - 1];
|
|
mesh.npolys--;
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Remove vertex.
|
|
for (int i = rem; i < mesh.nverts - 1; ++i)
|
|
{
|
|
mesh.verts[i * 3 + 0] = mesh.verts[(i + 1) * 3 + 0];
|
|
mesh.verts[i * 3 + 1] = mesh.verts[(i + 1) * 3 + 1];
|
|
mesh.verts[i * 3 + 2] = mesh.verts[(i + 1) * 3 + 2];
|
|
}
|
|
|
|
mesh.nverts--;
|
|
|
|
// Adjust indices to match the removed vertex layout.
|
|
for (int i = 0; i < mesh.npolys; ++i)
|
|
{
|
|
int p = i * nvp * 2;
|
|
int nv = CountPolyVerts(mesh.polys, p, nvp);
|
|
for (int j = 0; j < nv; ++j)
|
|
if (mesh.polys[p + j] > rem)
|
|
mesh.polys[p + j]--;
|
|
}
|
|
|
|
for (int i = 0; i < nedges; ++i)
|
|
{
|
|
if (edges[i * 4 + 0] > rem)
|
|
edges[i * 4 + 0]--;
|
|
if (edges[i * 4 + 1] > rem)
|
|
edges[i * 4 + 1]--;
|
|
}
|
|
|
|
if (nedges == 0)
|
|
return;
|
|
|
|
// Start with one vertex, keep appending connected
|
|
// segments to the start and end of the hole.
|
|
nhole = PushBack(edges[0], hole, nhole);
|
|
nhreg = PushBack(edges[2], hreg, nhreg);
|
|
nharea = PushBack(edges[3], harea, nharea);
|
|
|
|
while (nedges != 0)
|
|
{
|
|
bool match = false;
|
|
|
|
for (int i = 0; i < nedges; ++i)
|
|
{
|
|
int ea = edges[i * 4 + 0];
|
|
int eb = edges[i * 4 + 1];
|
|
int r = edges[i * 4 + 2];
|
|
int a = edges[i * 4 + 3];
|
|
bool add = false;
|
|
if (hole[0] == eb)
|
|
{
|
|
// The segment matches the beginning of the hole boundary.
|
|
nhole = PushFront(ea, hole, nhole);
|
|
nhreg = PushFront(r, hreg, nhreg);
|
|
nharea = PushFront(a, harea, nharea);
|
|
add = true;
|
|
}
|
|
else if (hole[nhole - 1] == ea)
|
|
{
|
|
// The segment matches the end of the hole boundary.
|
|
nhole = PushBack(eb, hole, nhole);
|
|
nhreg = PushBack(r, hreg, nhreg);
|
|
nharea = PushBack(a, harea, nharea);
|
|
add = true;
|
|
}
|
|
|
|
if (add)
|
|
{
|
|
// The edge segment was added, remove it.
|
|
edges[i * 4 + 0] = edges[(nedges - 1) * 4 + 0];
|
|
edges[i * 4 + 1] = edges[(nedges - 1) * 4 + 1];
|
|
edges[i * 4 + 2] = edges[(nedges - 1) * 4 + 2];
|
|
edges[i * 4 + 3] = edges[(nedges - 1) * 4 + 3];
|
|
--nedges;
|
|
match = true;
|
|
--i;
|
|
}
|
|
}
|
|
|
|
if (!match)
|
|
break;
|
|
}
|
|
|
|
int[] tris = new int[nhole * 3];
|
|
|
|
int[] tverts = new int[nhole * 4];
|
|
|
|
int[] thole = new int[nhole];
|
|
|
|
// Generate temp vertex array for triangulation.
|
|
for (int i = 0; i < nhole; ++i)
|
|
{
|
|
int pi = hole[i];
|
|
tverts[i * 4 + 0] = mesh.verts[pi * 3 + 0];
|
|
tverts[i * 4 + 1] = mesh.verts[pi * 3 + 1];
|
|
tverts[i * 4 + 2] = mesh.verts[pi * 3 + 2];
|
|
tverts[i * 4 + 3] = 0;
|
|
thole[i] = i;
|
|
}
|
|
|
|
// Triangulate the hole.
|
|
int ntris = Triangulate(nhole, tverts, thole, tris);
|
|
if (ntris < 0)
|
|
{
|
|
ntris = -ntris;
|
|
ctx.Warn("removeVertex: Triangulate() returned bad results.");
|
|
}
|
|
|
|
// Merge the hole triangles back to polygons.
|
|
int[] polys = new int[(ntris + 1) * nvp];
|
|
int[] pregs = new int[ntris];
|
|
int[] pareas = new int[ntris];
|
|
|
|
int tmpPoly = ntris * nvp;
|
|
|
|
// Build initial polygons.
|
|
int npolys = 0;
|
|
Array.Fill(polys, RC_MESH_NULL_IDX, 0, (ntris * nvp) - (0));
|
|
for (int j = 0; j < ntris; ++j)
|
|
{
|
|
int t = j * 3;
|
|
if (tris[t + 0] != tris[t + 1] && tris[t + 0] != tris[t + 2] && tris[t + 1] != tris[t + 2])
|
|
{
|
|
polys[npolys * nvp + 0] = hole[tris[t + 0]];
|
|
polys[npolys * nvp + 1] = hole[tris[t + 1]];
|
|
polys[npolys * nvp + 2] = hole[tris[t + 2]];
|
|
|
|
// If this polygon covers multiple region types then
|
|
// mark it as such
|
|
if (hreg[tris[t + 0]] != hreg[tris[t + 1]] || hreg[tris[t + 1]] != hreg[tris[t + 2]])
|
|
pregs[npolys] = RC_MULTIPLE_REGS;
|
|
else
|
|
pregs[npolys] = hreg[tris[t + 0]];
|
|
|
|
pareas[npolys] = harea[tris[t + 0]];
|
|
npolys++;
|
|
}
|
|
}
|
|
|
|
if (npolys == 0)
|
|
return;
|
|
|
|
// Merge polygons.
|
|
if (nvp > 3)
|
|
{
|
|
for (;;)
|
|
{
|
|
// Find best polygons to merge.
|
|
int bestMergeVal = 0;
|
|
int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
|
|
|
|
for (int j = 0; j < npolys - 1; ++j)
|
|
{
|
|
int pj = j * nvp;
|
|
for (int k = j + 1; k < npolys; ++k)
|
|
{
|
|
int pk = k * nvp;
|
|
var v = GetPolyMergeValue(polys, pj, pk, mesh.verts, out var ea, out var eb, nvp);
|
|
if (v > bestMergeVal)
|
|
{
|
|
bestMergeVal = v;
|
|
bestPa = j;
|
|
bestPb = k;
|
|
bestEa = ea;
|
|
bestEb = eb;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestMergeVal > 0)
|
|
{
|
|
// Found best, merge.
|
|
int pa = bestPa * nvp;
|
|
int pb = bestPb * nvp;
|
|
MergePolyVerts(polys, pa, pb, bestEa, bestEb, tmpPoly, nvp);
|
|
if (pregs[bestPa] != pregs[bestPb])
|
|
pregs[bestPa] = RC_MULTIPLE_REGS;
|
|
int last = (npolys - 1) * nvp;
|
|
if (pb != last)
|
|
{
|
|
Array.Copy(polys, last, polys, pb, nvp);
|
|
}
|
|
|
|
pregs[bestPb] = pregs[npolys - 1];
|
|
pareas[bestPb] = pareas[npolys - 1];
|
|
npolys--;
|
|
}
|
|
else
|
|
{
|
|
// Could not merge any polygons, stop.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store polygons.
|
|
for (int i = 0; i < npolys; ++i)
|
|
{
|
|
if (mesh.npolys >= maxTris)
|
|
break;
|
|
int p = mesh.npolys * nvp * 2;
|
|
Array.Fill(mesh.polys, RC_MESH_NULL_IDX, p, (p + nvp * 2) - (p));
|
|
for (int j = 0; j < nvp; ++j)
|
|
mesh.polys[p + j] = polys[i * nvp + j];
|
|
mesh.regs[mesh.npolys] = pregs[i];
|
|
mesh.areas[mesh.npolys] = pareas[i];
|
|
mesh.npolys++;
|
|
if (mesh.npolys > maxTris)
|
|
{
|
|
throw new Exception("removeVertex: Too many polygons " + mesh.npolys + " (max:" + maxTris + ".");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @par
|
|
///
|
|
/// @note If the mesh data is to be used to construct a Detour navigation mesh, then the upper
|
|
/// limit must be retricted to <= #DT_VERTS_PER_POLYGON.
|
|
///
|
|
/// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig
|
|
public static RcPolyMesh BuildPolyMesh(RcTelemetry ctx, RcContourSet cset, int nvp)
|
|
{
|
|
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_BUILD_POLYMESH);
|
|
|
|
RcPolyMesh mesh = new RcPolyMesh();
|
|
mesh.bmin = cset.bmin;
|
|
mesh.bmax = cset.bmax;
|
|
mesh.cs = cset.cs;
|
|
mesh.ch = cset.ch;
|
|
mesh.borderSize = cset.borderSize;
|
|
mesh.maxEdgeError = cset.maxError;
|
|
|
|
int maxVertices = 0;
|
|
int maxTris = 0;
|
|
int maxVertsPerCont = 0;
|
|
for (int i = 0; i < cset.conts.Count; ++i)
|
|
{
|
|
// Skip null contours.
|
|
if (cset.conts[i].nverts < 3)
|
|
continue;
|
|
maxVertices += cset.conts[i].nverts;
|
|
maxTris += cset.conts[i].nverts - 2;
|
|
maxVertsPerCont = Math.Max(maxVertsPerCont, cset.conts[i].nverts);
|
|
}
|
|
|
|
if (maxVertices >= 0xfffe)
|
|
{
|
|
throw new Exception("rcBuildPolyMesh: Too many vertices " + maxVertices);
|
|
}
|
|
|
|
int[] vflags = new int[maxVertices];
|
|
|
|
mesh.verts = new int[maxVertices * 3];
|
|
mesh.polys = new int[maxTris * nvp * 2];
|
|
Array.Fill(mesh.polys, RC_MESH_NULL_IDX);
|
|
mesh.regs = new int[maxTris];
|
|
mesh.areas = new int[maxTris];
|
|
|
|
mesh.nverts = 0;
|
|
mesh.npolys = 0;
|
|
mesh.nvp = nvp;
|
|
mesh.maxpolys = maxTris;
|
|
|
|
int[] nextVert = new int[maxVertices];
|
|
|
|
int[] firstVert = new int[VERTEX_BUCKET_COUNT];
|
|
for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i)
|
|
firstVert[i] = -1;
|
|
|
|
int[] indices = new int[maxVertsPerCont];
|
|
int[] tris = new int[maxVertsPerCont * 3];
|
|
int[] polys = new int[(maxVertsPerCont + 1) * nvp];
|
|
|
|
int tmpPoly = maxVertsPerCont * nvp;
|
|
|
|
for (int i = 0; i < cset.conts.Count; ++i)
|
|
{
|
|
RcContour cont = cset.conts[i];
|
|
|
|
// Skip null contours.
|
|
if (cont.nverts < 3)
|
|
continue;
|
|
|
|
// Triangulate contour
|
|
for (int j = 0; j < cont.nverts; ++j)
|
|
indices[j] = j;
|
|
int ntris = Triangulate(cont.nverts, cont.verts, indices, tris);
|
|
if (ntris <= 0)
|
|
{
|
|
// Bad triangulation, should not happen.
|
|
ctx.Warn("buildPolyMesh: Bad triangulation Contour " + i + ".");
|
|
ntris = -ntris;
|
|
}
|
|
|
|
// Add and merge vertices.
|
|
for (int j = 0; j < cont.nverts; ++j)
|
|
{
|
|
int v = j * 4;
|
|
indices[j] = AddVertex(cont.verts[v + 0], cont.verts[v + 1], cont.verts[v + 2],
|
|
mesh.verts, firstVert, nextVert, ref mesh.nverts);
|
|
|
|
if ((cont.verts[v + 3] & RC_BORDER_VERTEX) != 0)
|
|
{
|
|
// This vertex should be removed.
|
|
vflags[indices[j]] = 1;
|
|
}
|
|
}
|
|
|
|
// Build initial polygons.
|
|
int npolys = 0;
|
|
Array.Fill(polys, RC_MESH_NULL_IDX);
|
|
for (int j = 0; j < ntris; ++j)
|
|
{
|
|
int t = j * 3;
|
|
if (tris[t + 0] != tris[t + 1] && tris[t + 0] != tris[t + 2] && tris[t + 1] != tris[t + 2])
|
|
{
|
|
polys[npolys * nvp + 0] = indices[tris[t + 0]];
|
|
polys[npolys * nvp + 1] = indices[tris[t + 1]];
|
|
polys[npolys * nvp + 2] = indices[tris[t + 2]];
|
|
npolys++;
|
|
}
|
|
}
|
|
|
|
if (npolys == 0)
|
|
continue;
|
|
|
|
// Merge polygons.
|
|
if (nvp > 3)
|
|
{
|
|
for (;;)
|
|
{
|
|
// Find best polygons to merge.
|
|
int bestMergeVal = 0;
|
|
int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
|
|
|
|
for (int j = 0; j < npolys - 1; ++j)
|
|
{
|
|
int pj = j * nvp;
|
|
for (int k = j + 1; k < npolys; ++k)
|
|
{
|
|
int pk = k * nvp;
|
|
var v = GetPolyMergeValue(polys, pj, pk, mesh.verts, out var ea, out var eb, nvp);
|
|
if (v > bestMergeVal)
|
|
{
|
|
bestMergeVal = v;
|
|
bestPa = j;
|
|
bestPb = k;
|
|
bestEa = ea;
|
|
bestEb = eb;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestMergeVal > 0)
|
|
{
|
|
// Found best, merge.
|
|
int pa = bestPa * nvp;
|
|
int pb = bestPb * nvp;
|
|
MergePolyVerts(polys, pa, pb, bestEa, bestEb, tmpPoly, nvp);
|
|
int lastPoly = (npolys - 1) * nvp;
|
|
if (pb != lastPoly)
|
|
{
|
|
Array.Copy(polys, lastPoly, polys, pb, nvp);
|
|
}
|
|
|
|
npolys--;
|
|
}
|
|
else
|
|
{
|
|
// Could not merge any polygons, stop.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store polygons.
|
|
for (int j = 0; j < npolys; ++j)
|
|
{
|
|
int p = mesh.npolys * nvp * 2;
|
|
int q = j * nvp;
|
|
for (int k = 0; k < nvp; ++k)
|
|
mesh.polys[p + k] = polys[q + k];
|
|
mesh.regs[mesh.npolys] = cont.reg;
|
|
mesh.areas[mesh.npolys] = cont.area;
|
|
mesh.npolys++;
|
|
if (mesh.npolys > maxTris)
|
|
{
|
|
throw new Exception(
|
|
"rcBuildPolyMesh: Too many polygons " + mesh.npolys + " (max:" + maxTris + ").");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove edge vertices.
|
|
for (int i = 0; i < mesh.nverts; ++i)
|
|
{
|
|
if (vflags[i] != 0)
|
|
{
|
|
if (!CanRemoveVertex(ctx, mesh, i))
|
|
continue;
|
|
RemoveVertex(ctx, mesh, i, maxTris);
|
|
// Remove vertex
|
|
// Note: mesh.nverts is already decremented inside RemoveVertex()!
|
|
// Fixup vertex flags
|
|
for (int j = i; j < mesh.nverts; ++j)
|
|
vflags[j] = vflags[j + 1];
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Calculate adjacency.
|
|
BuildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, nvp);
|
|
|
|
// Find portal edges
|
|
if (mesh.borderSize > 0)
|
|
{
|
|
int w = cset.width;
|
|
int h = cset.height;
|
|
for (int i = 0; i < mesh.npolys; ++i)
|
|
{
|
|
int p = i * 2 * nvp;
|
|
for (int j = 0; j < nvp; ++j)
|
|
{
|
|
if (mesh.polys[p + j] == RC_MESH_NULL_IDX)
|
|
break;
|
|
// Skip connected edges.
|
|
if (mesh.polys[p + nvp + j] != RC_MESH_NULL_IDX)
|
|
continue;
|
|
int nj = j + 1;
|
|
if (nj >= nvp || mesh.polys[p + nj] == RC_MESH_NULL_IDX)
|
|
nj = 0;
|
|
int va = mesh.polys[p + j] * 3;
|
|
int vb = mesh.polys[p + nj] * 3;
|
|
|
|
if (mesh.verts[va + 0] == 0 && mesh.verts[vb + 0] == 0)
|
|
mesh.polys[p + nvp + j] = 0x8000 | 0;
|
|
else if (mesh.verts[va + 2] == h && mesh.verts[vb + 2] == h)
|
|
mesh.polys[p + nvp + j] = 0x8000 | 1;
|
|
else if (mesh.verts[va + 0] == w && mesh.verts[vb + 0] == w)
|
|
mesh.polys[p + nvp + j] = 0x8000 | 2;
|
|
else if (mesh.verts[va + 2] == 0 && mesh.verts[vb + 2] == 0)
|
|
mesh.polys[p + nvp + j] = 0x8000 | 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Just allocate the mesh flags array. The user is resposible to fill it.
|
|
mesh.flags = new int[mesh.npolys];
|
|
|
|
if (mesh.nverts > MAX_MESH_VERTS_POLY)
|
|
{
|
|
throw new Exception("rcBuildPolyMesh: The resulting mesh has too many vertices " + mesh.nverts
|
|
+ " (max " + MAX_MESH_VERTS_POLY + "). Data can be corrupted.");
|
|
}
|
|
|
|
if (mesh.npolys > MAX_MESH_VERTS_POLY)
|
|
{
|
|
throw new Exception("rcBuildPolyMesh: The resulting mesh has too many polygons " + mesh.npolys
|
|
+ " (max " + MAX_MESH_VERTS_POLY + "). Data can be corrupted.");
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
|
|
/// @see rcAllocPolyMesh, rcPolyMesh
|
|
public static RcPolyMesh MergePolyMeshes(RcTelemetry ctx, RcPolyMesh[] meshes, int nmeshes)
|
|
{
|
|
if (nmeshes == 0 || meshes == null)
|
|
return null;
|
|
|
|
using var timer = ctx.ScopedTimer(RcTimerLabel.RC_TIMER_MERGE_POLYMESH);
|
|
|
|
RcPolyMesh mesh = new RcPolyMesh();
|
|
mesh.nvp = meshes[0].nvp;
|
|
mesh.cs = meshes[0].cs;
|
|
mesh.ch = meshes[0].ch;
|
|
mesh.bmin = meshes[0].bmin;
|
|
mesh.bmax = meshes[0].bmax;
|
|
|
|
int maxVerts = 0;
|
|
int maxPolys = 0;
|
|
int maxVertsPerMesh = 0;
|
|
for (int i = 0; i < nmeshes; ++i)
|
|
{
|
|
mesh.bmin.Min(meshes[i].bmin);
|
|
mesh.bmax.Max(meshes[i].bmax);
|
|
maxVertsPerMesh = Math.Max(maxVertsPerMesh, meshes[i].nverts);
|
|
maxVerts += meshes[i].nverts;
|
|
maxPolys += meshes[i].npolys;
|
|
}
|
|
|
|
mesh.nverts = 0;
|
|
mesh.verts = new int[maxVerts * 3];
|
|
|
|
mesh.npolys = 0;
|
|
mesh.polys = new int[maxPolys * 2 * mesh.nvp];
|
|
Array.Fill(mesh.polys, RC_MESH_NULL_IDX, 0, (mesh.polys.Length) - (0));
|
|
mesh.regs = new int[maxPolys];
|
|
mesh.areas = new int[maxPolys];
|
|
mesh.flags = new int[maxPolys];
|
|
|
|
int[] nextVert = new int[maxVerts];
|
|
|
|
int[] firstVert = new int[VERTEX_BUCKET_COUNT];
|
|
for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i)
|
|
firstVert[i] = -1;
|
|
|
|
int[] vremap = new int[maxVertsPerMesh];
|
|
|
|
for (int i = 0; i < nmeshes; ++i)
|
|
{
|
|
RcPolyMesh pmesh = meshes[i];
|
|
|
|
int ox = (int)Math.Floor((pmesh.bmin.x - mesh.bmin.x) / mesh.cs + 0.5f);
|
|
int oz = (int)Math.Floor((pmesh.bmin.z - mesh.bmin.z) / mesh.cs + 0.5f);
|
|
|
|
bool isMinX = (ox == 0);
|
|
bool isMinZ = (oz == 0);
|
|
bool isMaxX = (Math.Floor((mesh.bmax.x - pmesh.bmax.x) / mesh.cs + 0.5f)) == 0;
|
|
bool isMaxZ = (Math.Floor((mesh.bmax.z - pmesh.bmax.z) / mesh.cs + 0.5f)) == 0;
|
|
bool isOnBorder = (isMinX || isMinZ || isMaxX || isMaxZ);
|
|
|
|
for (int j = 0; j < pmesh.nverts; ++j)
|
|
{
|
|
int v = j * 3;
|
|
vremap[j] = AddVertex(pmesh.verts[v + 0] + ox, pmesh.verts[v + 1], pmesh.verts[v + 2] + oz,
|
|
mesh.verts, firstVert, nextVert, ref mesh.nverts);
|
|
}
|
|
|
|
for (int j = 0; j < pmesh.npolys; ++j)
|
|
{
|
|
int tgt = mesh.npolys * 2 * mesh.nvp;
|
|
int src = j * 2 * mesh.nvp;
|
|
mesh.regs[mesh.npolys] = pmesh.regs[j];
|
|
mesh.areas[mesh.npolys] = pmesh.areas[j];
|
|
mesh.flags[mesh.npolys] = pmesh.flags[j];
|
|
mesh.npolys++;
|
|
for (int k = 0; k < mesh.nvp; ++k)
|
|
{
|
|
if (pmesh.polys[src + k] == RC_MESH_NULL_IDX)
|
|
break;
|
|
mesh.polys[tgt + k] = vremap[pmesh.polys[src + k]];
|
|
}
|
|
|
|
if (isOnBorder)
|
|
{
|
|
for (int k = mesh.nvp; k < mesh.nvp * 2; ++k)
|
|
{
|
|
if ((pmesh.polys[src + k] & 0x8000) != 0 && pmesh.polys[src + k] != 0xffff)
|
|
{
|
|
int dir = pmesh.polys[src + k] & 0xf;
|
|
switch (dir)
|
|
{
|
|
case 0: // Portal x-
|
|
if (isMinX)
|
|
mesh.polys[tgt + k] = pmesh.polys[src + k];
|
|
break;
|
|
case 1: // Portal z+
|
|
if (isMaxZ)
|
|
mesh.polys[tgt + k] = pmesh.polys[src + k];
|
|
break;
|
|
case 2: // Portal x+
|
|
if (isMaxX)
|
|
mesh.polys[tgt + k] = pmesh.polys[src + k];
|
|
break;
|
|
case 3: // Portal z-
|
|
if (isMinZ)
|
|
mesh.polys[tgt + k] = pmesh.polys[src + k];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate adjacency.
|
|
BuildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp);
|
|
if (mesh.nverts > MAX_MESH_VERTS_POLY)
|
|
{
|
|
throw new Exception("rcBuildPolyMesh: The resulting mesh has too many vertices " + mesh.nverts
|
|
+ " (max " + MAX_MESH_VERTS_POLY + "). Data can be corrupted.");
|
|
}
|
|
|
|
if (mesh.npolys > MAX_MESH_VERTS_POLY)
|
|
{
|
|
throw new Exception("rcBuildPolyMesh: The resulting mesh has too many polygons " + mesh.npolys
|
|
+ " (max " + MAX_MESH_VERTS_POLY + "). Data can be corrupted.");
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
|
|
public static RcPolyMesh CopyPolyMesh(RcTelemetry ctx, RcPolyMesh src)
|
|
{
|
|
RcPolyMesh dst = new RcPolyMesh();
|
|
|
|
dst.nverts = src.nverts;
|
|
dst.npolys = src.npolys;
|
|
dst.maxpolys = src.npolys;
|
|
dst.nvp = src.nvp;
|
|
dst.bmin = src.bmin;
|
|
dst.bmax = src.bmax;
|
|
dst.cs = src.cs;
|
|
dst.ch = src.ch;
|
|
dst.borderSize = src.borderSize;
|
|
dst.maxEdgeError = src.maxEdgeError;
|
|
|
|
dst.verts = new int[src.nverts * 3];
|
|
Array.Copy(src.verts, 0, dst.verts, 0, dst.verts.Length);
|
|
dst.polys = new int[src.npolys * 2 * src.nvp];
|
|
Array.Copy(src.polys, 0, dst.polys, 0, dst.polys.Length);
|
|
dst.regs = new int[src.npolys];
|
|
Array.Copy(src.regs, 0, dst.regs, 0, dst.regs.Length);
|
|
dst.areas = new int[src.npolys];
|
|
Array.Copy(src.areas, 0, dst.areas, 0, dst.areas.Length);
|
|
dst.flags = new int[src.npolys];
|
|
Array.Copy(src.flags, 0, dst.flags, 0, dst.flags.Length);
|
|
return dst;
|
|
}
|
|
}
|
|
} |