summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelContour.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelContour.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelContour.cs710
1 files changed, 710 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelContour.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelContour.cs
new file mode 100644
index 0000000..39b49db
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/VoxelContour.cs
@@ -0,0 +1,710 @@
+using UnityEngine;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Burst;
+using Pathfinding.Util;
+
+namespace Pathfinding.Graphs.Navmesh.Voxelization.Burst {
+ /// <summary>VoxelContour used for recast graphs.</summary>
+ public struct VoxelContour {
+ public int nverts;
+
+ /// <summary>Vertex coordinates, each vertex contains 4 components.</summary>
+ public int vertexStartIndex;
+
+ /// <summary>Region ID of the contour</summary>
+ public int reg;
+
+ /// <summary>Area ID of the contour.</summary>
+ public int area;
+ }
+
+ [BurstCompile(CompileSynchronously = true)]
+ public struct JobBuildContours : IJob {
+ public CompactVoxelField field;
+ public float maxError;
+ public float maxEdgeLength;
+ public int buildFlags;
+ public float cellSize;
+ public NativeList<VoxelContour> outputContours;
+ public NativeList<int> outputVerts;
+
+ public void Execute () {
+ outputContours.Clear();
+ outputVerts.Clear();
+
+ int w = field.width;
+ int d = field.depth;
+ int wd = w*d;
+
+ const ushort BorderReg = VoxelUtilityBurst.BorderReg;
+
+ // NOTE: This array may contain uninitialized data, but since we explicitly set all data in it before we use it, it's OK.
+ var flags = new NativeArray<ushort>(field.spans.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+
+ // Mark boundaries. (@?)
+ for (int z = 0; z < wd; z += field.width) {
+ for (int x = 0; x < field.width; x++) {
+ CompactVoxelCell c = field.cells[x+z];
+
+ for (int i = (int)c.index, ci = (int)(c.index+c.count); i < ci; i++) {
+ ushort res = 0;
+ CompactVoxelSpan s = field.spans[i];
+
+ if (s.reg == 0 || (s.reg & BorderReg) == BorderReg) {
+ flags[i] = 0;
+ continue;
+ }
+
+ for (int dir = 0; dir < 4; dir++) {
+ int r = 0;
+
+ if (s.GetConnection(dir) != CompactVoxelField.NotConnected) {
+ int ni = field.cells[field.GetNeighbourIndex(x+z, dir)].index + s.GetConnection(dir);
+ r = field.spans[ni].reg;
+ }
+
+ //@TODO - Why isn't this inside the previous IF
+ if (r == s.reg) {
+ res |= (ushort)(1 << dir);
+ }
+ }
+
+ //Inverse, mark non connected edges.
+ flags[i] = (ushort)(res ^ 0xf);
+ }
+ }
+ }
+
+
+ NativeList<int> verts = new NativeList<int>(256, Allocator.Temp);
+ NativeList<int> simplified = new NativeList<int>(64, Allocator.Temp);
+
+ for (int z = 0; z < wd; z += field.width) {
+ for (int x = 0; x < field.width; x++) {
+ CompactVoxelCell c = field.cells[x+z];
+
+ for (int i = c.index, ci = c.index+c.count; i < ci; i++) {
+ if (flags[i] == 0 || flags[i] == 0xf) {
+ flags[i] = 0;
+ continue;
+ }
+
+ int reg = field.spans[i].reg;
+
+ if (reg == 0 || (reg & BorderReg) == BorderReg) {
+ continue;
+ }
+
+ int area = field.areaTypes[i];
+
+ verts.Clear();
+ simplified.Clear();
+
+ WalkContour(x, z, i, flags, verts);
+
+ SimplifyContour(verts, simplified, maxError, buildFlags);
+ RemoveDegenerateSegments(simplified);
+
+ VoxelContour contour = new VoxelContour {
+ vertexStartIndex = outputVerts.Length,
+ nverts = simplified.Length/4,
+ reg = reg,
+ area = area,
+ };
+
+ outputVerts.AddRange(simplified.AsArray());
+
+ outputContours.Add(contour);
+ }
+ }
+ }
+
+ verts.Dispose();
+ simplified.Dispose();
+
+
+
+ // Check and merge droppings.
+ // Sometimes the previous algorithms can fail and create several outputContours
+ // per area. This pass will try to merge the holes into the main region.
+ for (int i = 0; i < outputContours.Length; i++) {
+ VoxelContour cont = outputContours[i];
+ // Check if the contour is would backwards.
+ var outputVertsArr = outputVerts.AsArray();
+ if (CalcAreaOfPolygon2D(outputVertsArr, cont.vertexStartIndex, cont.nverts) < 0) {
+ // Find another contour which has the same region ID.
+ int mergeIdx = -1;
+ for (int j = 0; j < outputContours.Length; j++) {
+ if (i == j) continue;
+ if (outputContours[j].nverts > 0 && outputContours[j].reg == cont.reg) {
+ // Make sure the polygon is correctly oriented.
+ if (CalcAreaOfPolygon2D(outputVertsArr, outputContours[j].vertexStartIndex, outputContours[j].nverts) > 0) {
+ mergeIdx = j;
+ break;
+ }
+ }
+ }
+ if (mergeIdx == -1) {
+ // Debug.LogError("rcBuildContours: Could not find merge target for bad contour "+i+".");
+ } else {
+ // Debugging
+ // Debug.LogWarning ("Fixing contour");
+
+ VoxelContour mcont = outputContours[mergeIdx];
+ // Merge by closest points.
+ GetClosestIndices(outputVertsArr, mcont.vertexStartIndex, mcont.nverts, cont.vertexStartIndex, cont.nverts, out var ia, out var ib);
+
+ if (ia == -1 || ib == -1) {
+ // Debug.LogWarning("rcBuildContours: Failed to find merge points for "+i+" and "+mergeIdx+".");
+ continue;
+ }
+
+
+ if (!MergeContours(outputVerts, ref mcont, ref cont, ia, ib)) {
+ //Debug.LogWarning("rcBuildContours: Failed to merge contours "+i+" and "+mergeIdx+".");
+ continue;
+ }
+
+ outputContours[mergeIdx] = mcont;
+ outputContours[i] = cont;
+ }
+ }
+ }
+ }
+
+ void GetClosestIndices (NativeArray<int> verts, int vertexStartIndexA, int nvertsa,
+ int vertexStartIndexB, int nvertsb,
+ out int ia, out int ib) {
+ int closestDist = 0xfffffff;
+
+ ia = -1;
+ ib = -1;
+ for (int i = 0; i < nvertsa; i++) {
+ //in is a keyword in C#, so I can't use that as a variable name
+ int in2 = (i+1) % nvertsa;
+ int ip = (i+nvertsa-1) % nvertsa;
+ int va = vertexStartIndexA + i*4;
+ int van = vertexStartIndexA + in2*4;
+ int vap = vertexStartIndexA + ip*4;
+
+ for (int j = 0; j < nvertsb; ++j) {
+ int vb = vertexStartIndexB + j*4;
+ // vb must be "infront" of va.
+ if (Ileft(verts, vap, va, vb) && Ileft(verts, va, van, vb)) {
+ int dx = verts[vb+0] - verts[va+0];
+ int dz = (verts[vb+2]/field.width) - (verts[va+2]/field.width);
+ int d = dx*dx + dz*dz;
+ if (d < closestDist) {
+ ia = i;
+ ib = j;
+ closestDist = d;
+ }
+ }
+ }
+ }
+ }
+
+ public static bool MergeContours (NativeList<int> verts, ref VoxelContour ca, ref VoxelContour cb, int ia, int ib) {
+ // Note: this will essentially leave junk data in the verts array where the contours were previously.
+ // This shouldn't be a big problem because MergeContours is normally not called for that many contours (usually none).
+ int nv = 0;
+ var startIndex = verts.Length;
+
+ // Copy contour A.
+ for (int i = 0; i <= ca.nverts; i++) {
+ int src = ca.vertexStartIndex + ((ia+i) % ca.nverts)*4;
+ verts.Add(verts[src+0]);
+ verts.Add(verts[src+1]);
+ verts.Add(verts[src+2]);
+ verts.Add(verts[src+3]);
+ nv++;
+ }
+
+ // Copy contour B
+ for (int i = 0; i <= cb.nverts; i++) {
+ int src = cb.vertexStartIndex + ((ib+i) % cb.nverts)*4;
+ verts.Add(verts[src+0]);
+ verts.Add(verts[src+1]);
+ verts.Add(verts[src+2]);
+ verts.Add(verts[src+3]);
+ nv++;
+ }
+
+ ca.vertexStartIndex = startIndex;
+ ca.nverts = nv;
+
+ cb.vertexStartIndex = 0;
+ cb.nverts = 0;
+
+ return true;
+ }
+
+ public void SimplifyContour (NativeList<int> verts, NativeList<int> simplified, float maxError, int buildFlags) {
+ // Add initial points.
+ bool hasConnections = false;
+
+ for (int i = 0; i < verts.Length; i += 4) {
+ if ((verts[i+3] & VoxelUtilityBurst.ContourRegMask) != 0) {
+ hasConnections = true;
+ break;
+ }
+ }
+
+ if (hasConnections) {
+ // The contour has some portals to other regions.
+ // Add a new point to every location where the region changes.
+ for (int i = 0, ni = verts.Length/4; i < ni; i++) {
+ int ii = (i+1) % ni;
+ bool differentRegs = (verts[i*4+3] & VoxelUtilityBurst.ContourRegMask) != (verts[ii*4+3] & VoxelUtilityBurst.ContourRegMask);
+ bool areaBorders = (verts[i*4+3] & VoxelUtilityBurst.RC_AREA_BORDER) != (verts[ii*4+3] & VoxelUtilityBurst.RC_AREA_BORDER);
+
+ if (differentRegs || areaBorders) {
+ simplified.Add(verts[i*4+0]);
+ simplified.Add(verts[i*4+1]);
+ simplified.Add(verts[i*4+2]);
+ simplified.Add(i);
+ }
+ }
+ }
+
+
+ if (simplified.Length == 0) {
+ // If there is no connections at all,
+ // create some initial points for the simplification process.
+ // Find lower-left and upper-right vertices of the contour.
+ int llx = verts[0];
+ int lly = verts[1];
+ int llz = verts[2];
+ int lli = 0;
+ int urx = verts[0];
+ int ury = verts[1];
+ int urz = verts[2];
+ int uri = 0;
+
+ for (int i = 0; i < verts.Length; i += 4) {
+ int x = verts[i+0];
+ int y = verts[i+1];
+ int z = verts[i+2];
+ if (x < llx || (x == llx && z < llz)) {
+ llx = x;
+ lly = y;
+ llz = z;
+ lli = i/4;
+ }
+ if (x > urx || (x == urx && z > urz)) {
+ urx = x;
+ ury = y;
+ urz = z;
+ uri = i/4;
+ }
+ }
+
+ simplified.Add(llx);
+ simplified.Add(lly);
+ simplified.Add(llz);
+ simplified.Add(lli);
+
+ simplified.Add(urx);
+ simplified.Add(ury);
+ simplified.Add(urz);
+ simplified.Add(uri);
+ }
+
+ // Add points until all raw points are within
+ // error tolerance to the simplified shape.
+ // This uses the Douglas-Peucker algorithm.
+ int pn = verts.Length/4;
+
+ //Use the max squared error instead
+ maxError *= maxError;
+
+ for (int i = 0; i < simplified.Length/4;) {
+ int ii = (i+1) % (simplified.Length/4);
+
+ int ax = simplified[i*4+0];
+ int ay = simplified[i*4+1];
+ int az = simplified[i*4+2];
+ int ai = simplified[i*4+3];
+
+ int bx = simplified[ii*4+0];
+ int by = simplified[ii*4+1];
+ int bz = simplified[ii*4+2];
+ int bi = simplified[ii*4+3];
+
+ // Find maximum deviation from the segment.
+ float maxd = 0;
+ int maxi = -1;
+ int ci, cinc, endi;
+
+ // Traverse the segment in lexilogical order so that the
+ // max deviation is calculated similarly when traversing
+ // opposite segments.
+ if (bx > ax || (bx == ax && bz > az)) {
+ cinc = 1;
+ ci = (ai+cinc) % pn;
+ endi = bi;
+ } else {
+ cinc = pn-1;
+ ci = (bi+cinc) % pn;
+ endi = ai;
+ Memory.Swap(ref ax, ref bx);
+ Memory.Swap(ref az, ref bz);
+ }
+
+ // Tessellate only outer edges or edges between areas.
+ if ((verts[ci*4+3] & VoxelUtilityBurst.ContourRegMask) == 0 ||
+ (verts[ci*4+3] & VoxelUtilityBurst.RC_AREA_BORDER) == VoxelUtilityBurst.RC_AREA_BORDER) {
+ while (ci != endi) {
+ float d2 = VectorMath.SqrDistancePointSegmentApproximate(verts[ci*4+0], verts[ci*4+2]/field.width, ax, az/field.width, bx, bz/field.width);
+
+ if (d2 > maxd) {
+ maxd = d2;
+ maxi = ci;
+ }
+ ci = (ci+cinc) % pn;
+ }
+ }
+
+ // If the max deviation is larger than accepted error,
+ // add new point, else continue to next segment.
+ if (maxi != -1 && maxd > maxError) {
+ // Add space for the new point.
+ simplified.ResizeUninitialized(simplified.Length + 4);
+
+ // Move all points after this one, to leave space to insert the new point
+ simplified.AsUnsafeSpan().Move((i+1)*4, (i+2)*4, simplified.Length-(i+2)*4);
+
+ // Add the point.
+ simplified[(i+1)*4+0] = verts[maxi*4+0];
+ simplified[(i+1)*4+1] = verts[maxi*4+1];
+ simplified[(i+1)*4+2] = verts[maxi*4+2];
+ simplified[(i+1)*4+3] = maxi;
+ } else {
+ i++;
+ }
+ }
+
+ // Split too long edges
+
+ float maxEdgeLen = maxEdgeLength / cellSize;
+
+ if (maxEdgeLen > 0 && (buildFlags & (VoxelUtilityBurst.RC_CONTOUR_TESS_WALL_EDGES|VoxelUtilityBurst.RC_CONTOUR_TESS_AREA_EDGES|VoxelUtilityBurst.RC_CONTOUR_TESS_TILE_EDGES)) != 0) {
+ for (int i = 0; i < simplified.Length/4;) {
+ if (simplified.Length/4 > 200) {
+ break;
+ }
+
+ int ii = (i+1) % (simplified.Length/4);
+
+ int ax = simplified[i*4+0];
+ int az = simplified[i*4+2];
+ int ai = simplified[i*4+3];
+
+ int bx = simplified[ii*4+0];
+ int bz = simplified[ii*4+2];
+ int bi = simplified[ii*4+3];
+
+ // Find maximum deviation from the segment.
+ int maxi = -1;
+ int ci = (ai+1) % pn;
+
+ // Tessellate only outer edges or edges between areas.
+ bool tess = false;
+
+ // Wall edges.
+ if ((buildFlags & VoxelUtilityBurst.RC_CONTOUR_TESS_WALL_EDGES) != 0 && (verts[ci*4+3] & VoxelUtilityBurst.ContourRegMask) == 0)
+ tess = true;
+
+ // Edges between areas.
+ if ((buildFlags & VoxelUtilityBurst.RC_CONTOUR_TESS_AREA_EDGES) != 0 && (verts[ci*4+3] & VoxelUtilityBurst.RC_AREA_BORDER) == VoxelUtilityBurst.RC_AREA_BORDER)
+ tess = true;
+
+ // Border of tile
+ if ((buildFlags & VoxelUtilityBurst.RC_CONTOUR_TESS_TILE_EDGES) != 0 && (verts[ci*4+3] & VoxelUtilityBurst.BorderReg) == VoxelUtilityBurst.BorderReg)
+ tess = true;
+
+ if (tess) {
+ int dx = bx - ax;
+ int dz = (bz/field.width) - (az/field.width);
+ if (dx*dx + dz*dz > maxEdgeLen*maxEdgeLen) {
+ // Round based on the segments in lexilogical order so that the
+ // max tesselation is consistent regardles in which direction
+ // segments are traversed.
+ int n = bi < ai ? (bi+pn - ai) : (bi - ai);
+ if (n > 1) {
+ if (bx > ax || (bx == ax && bz > az)) {
+ maxi = (ai + n/2) % pn;
+ } else {
+ maxi = (ai + (n+1)/2) % pn;
+ }
+ }
+ }
+ }
+
+ // If the max deviation is larger than accepted error,
+ // add new point, else continue to next segment.
+ if (maxi != -1) {
+ // Add space for the new point.
+ //simplified.resize(simplified.size()+4);
+ simplified.Resize(simplified.Length + 4, NativeArrayOptions.UninitializedMemory);
+
+ simplified.AsUnsafeSpan().Move((i+1)*4, (i+2)*4, simplified.Length-(i+2)*4);
+
+ // Add the point.
+ simplified[(i+1)*4+0] = verts[maxi*4+0];
+ simplified[(i+1)*4+1] = verts[maxi*4+1];
+ simplified[(i+1)*4+2] = verts[maxi*4+2];
+ simplified[(i+1)*4+3] = maxi;
+ } else {
+ ++i;
+ }
+ }
+ }
+
+ for (int i = 0; i < simplified.Length/4; i++) {
+ // The edge vertex flag is take from the current raw point,
+ // and the neighbour region is take from the next raw point.
+ int ai = (simplified[i*4+3]+1) % pn;
+ int bi = simplified[i*4+3];
+ simplified[i*4+3] = (verts[ai*4+3] & VoxelUtilityBurst.ContourRegMask) | (verts[bi*4+3] & VoxelUtilityBurst.RC_BORDER_VERTEX);
+ }
+ }
+
+ public void WalkContour (int x, int z, int i, NativeArray<ushort> flags, NativeList<int> verts) {
+ // Choose the first non-connected edge
+ int dir = 0;
+
+ while ((flags[i] & (ushort)(1 << dir)) == 0) {
+ dir++;
+ }
+
+ int startDir = dir;
+ int startI = i;
+
+ int area = field.areaTypes[i];
+
+ int iter = 0;
+
+ while (iter++ < 40000) {
+ // Are we facing a region edge
+ if ((flags[i] & (ushort)(1 << dir)) != 0) {
+ // Choose the edge corner
+ bool isBorderVertex = false;
+ bool isAreaBorder = false;
+
+ int px = x;
+ int py = GetCornerHeight(x, z, i, dir, ref isBorderVertex);
+ int pz = z;
+
+ // Offset the vertex to land on the corner of the span.
+ // The resulting coordinates have an implicit 1/2 voxel offset because all corners
+ // are in the middle between two adjacent integer voxel coordinates.
+ switch (dir) {
+ case 0: pz += field.width; break;
+ case 1: px++; pz += field.width; break;
+ case 2: px++; break;
+ }
+
+ int r = 0;
+ CompactVoxelSpan s = field.spans[i];
+
+ if (s.GetConnection(dir) != CompactVoxelField.NotConnected) {
+ int ni = (int)field.cells[field.GetNeighbourIndex(x+z, dir)].index + s.GetConnection(dir);
+ r = (int)field.spans[ni].reg;
+
+ if (area != field.areaTypes[ni]) {
+ isAreaBorder = true;
+ }
+ }
+
+ if (isBorderVertex) {
+ r |= VoxelUtilityBurst.RC_BORDER_VERTEX;
+ }
+ if (isAreaBorder) {
+ r |= VoxelUtilityBurst.RC_AREA_BORDER;
+ }
+
+ verts.Add(px);
+ verts.Add(py);
+ verts.Add(pz);
+ verts.Add(r);
+
+ flags[i] = (ushort)(flags[i] & ~(1 << dir)); // Remove visited edges
+
+ // & 0x3 is the same as % 4 (for positive numbers)
+ dir = (dir+1) & 0x3; // Rotate CW
+ } else {
+ int ni = -1;
+ int nx = x + VoxelUtilityBurst.DX[dir];
+ int nz = z + VoxelUtilityBurst.DZ[dir]*field.width;
+
+ CompactVoxelSpan s = field.spans[i];
+
+ if (s.GetConnection(dir) != CompactVoxelField.NotConnected) {
+ CompactVoxelCell nc = field.cells[nx+nz];
+ ni = (int)nc.index + s.GetConnection(dir);
+ }
+
+ if (ni == -1) {
+ Debug.LogWarning("Degenerate triangles might have been generated.\n" +
+ "Usually this is not a problem, but if you have a static level, try to modify the graph settings slightly to avoid this edge case.");
+ return;
+ }
+ x = nx;
+ z = nz;
+ i = ni;
+
+ // & 0x3 is the same as % 4 (modulo 4)
+ dir = (dir+3) & 0x3; // Rotate CCW
+ }
+
+ if (startI == i && startDir == dir) {
+ break;
+ }
+ }
+ }
+
+ public int GetCornerHeight (int x, int z, int i, int dir, ref bool isBorderVertex) {
+ CompactVoxelSpan s = field.spans[i];
+
+ int cornerHeight = (int)s.y;
+
+ // dir + 1 step in the clockwise direction
+ int dirp = (dir+1) & 0x3;
+
+ unsafe {
+ // We need a small buffer to hold regions for each axis aligned neighbour.
+ // This requires unsafe, though. In future C# versions we can use Span<T>.
+ //
+ // dir
+ // X---->
+ // dirp |
+ // v
+ //
+ //
+ // The regs array will contain the regions for the following spans,
+ // where the 0th span is the current span.
+ // 'x' signifies the position of the corner we are interested in.
+ // This is the shared vertex corner the four spans.
+ // It is conceptually at the current span's position + 0.5*dir + 0.5*dirp
+ //
+ //
+ // 0 --------- 1 -> dir
+ // | |
+ // | x |
+ // | |
+ // 3 --------- 2
+ //
+ // | dirp
+ // v
+ //
+ var regs = stackalloc uint[] { 0, 0, 0, 0 };
+
+ regs[0] = (uint)field.spans[i].reg | ((uint)field.areaTypes[i] << 16);
+
+ if (s.GetConnection(dir) != CompactVoxelField.NotConnected) {
+ int neighbourCell = field.GetNeighbourIndex(x+z, dir);
+ int ni = (int)field.cells[neighbourCell].index + s.GetConnection(dir);
+
+ CompactVoxelSpan ns = field.spans[ni];
+
+ cornerHeight = System.Math.Max(cornerHeight, (int)ns.y);
+ regs[1] = (uint)ns.reg | ((uint)field.areaTypes[ni] << 16);
+
+ if (ns.GetConnection(dirp) != CompactVoxelField.NotConnected) {
+ int neighbourCell2 = field.GetNeighbourIndex(neighbourCell, dirp);
+ int ni2 = (int)field.cells[neighbourCell2].index + ns.GetConnection(dirp);
+
+ CompactVoxelSpan ns2 = field.spans[ni2];
+
+ cornerHeight = System.Math.Max(cornerHeight, (int)ns2.y);
+ regs[2] = (uint)ns2.reg | ((uint)field.areaTypes[ni2] << 16);
+ }
+ }
+
+ if (s.GetConnection(dirp) != CompactVoxelField.NotConnected) {
+ int neighbourCell = field.GetNeighbourIndex(x+z, dirp);
+ int ni = (int)field.cells[neighbourCell].index + s.GetConnection(dirp);
+
+ CompactVoxelSpan ns = field.spans[ni];
+
+ cornerHeight = System.Math.Max(cornerHeight, (int)ns.y);
+ regs[3] = (uint)ns.reg | ((uint)field.areaTypes[ni] << 16);
+
+ if (ns.GetConnection(dir) != CompactVoxelField.NotConnected) {
+ int neighbourCell2 = field.GetNeighbourIndex(neighbourCell, dir);
+ int ni2 = (int)field.cells[neighbourCell2].index + ns.GetConnection(dir);
+
+ CompactVoxelSpan ns2 = field.spans[ni2];
+
+ cornerHeight = System.Math.Max(cornerHeight, (int)ns2.y);
+ regs[2] = (uint)ns2.reg | ((uint)field.areaTypes[ni2] << 16);
+ }
+ }
+
+ // Zeroes show up when there are no connections to some spans. E.g. if the current span is on a ledge.
+ bool noZeros = regs[0] != 0 && regs[1] != 0 && regs[2] != 0 && regs[3] != 0;
+
+ // Check if the vertex is special edge vertex, these vertices will be removed later.
+ for (int j = 0; j < 4; ++j) {
+ int a = j;
+ int b = (j+1) & 0x3;
+ int c = (j+2) & 0x3;
+ int d = (j+3) & 0x3;
+
+ // The vertex is a border vertex there are two same exterior cells in a row,
+ // followed by two interior cells and none of the regions are out of bounds.
+ bool twoSameExts = (regs[a] & regs[b] & VoxelUtilityBurst.BorderReg) != 0 && regs[a] == regs[b];
+ bool twoInts = ((regs[c] | regs[d]) & VoxelUtilityBurst.BorderReg) == 0;
+ bool intsSameArea = (regs[c]>>16) == (regs[d]>>16);
+ if (twoSameExts && twoInts && intsSameArea && noZeros) {
+ isBorderVertex = true;
+ break;
+ }
+ }
+ }
+
+ return cornerHeight;
+ }
+
+ static void RemoveRange (NativeList<int> arr, int index, int count) {
+ for (int i = index; i < arr.Length - count; i++) {
+ arr[i] = arr[i+count];
+ }
+ arr.Resize(arr.Length - count, NativeArrayOptions.UninitializedMemory);
+ }
+
+ static void RemoveDegenerateSegments (NativeList<int> simplified) {
+ // Remove adjacent vertices which are equal on xz-plane,
+ // or else the triangulator will get confused
+ for (int i = 0; i < simplified.Length/4; i++) {
+ int ni = i+1;
+ if (ni >= (simplified.Length/4))
+ ni = 0;
+
+ if (simplified[i*4+0] == simplified[ni*4+0] &&
+ simplified[i*4+2] == simplified[ni*4+2]) {
+ // Degenerate segment, remove.
+ RemoveRange(simplified, i, 4);
+ }
+ }
+ }
+
+ int CalcAreaOfPolygon2D (NativeArray<int> verts, int vertexStartIndex, int nverts) {
+ int area = 0;
+
+ for (int i = 0, j = nverts-1; i < nverts; j = i++) {
+ int vi = vertexStartIndex + i*4;
+ int vj = vertexStartIndex + j*4;
+ area += verts[vi+0] * (verts[vj+2]/field.width) - verts[vj+0] * (verts[vi+2]/field.width);
+ }
+
+ return (area+1) / 2;
+ }
+
+ static bool Ileft (NativeArray<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;
+ }
+ }
+}