ZeroVR/ZeroPacientVR/Assets/CodeRespawn/DungeonArchitect/ThirdParty/SharpNav/Contour.cs

432 lines
12 KiB
C#

// Copyright (c) 2013-2014 Robert Rouhani <robert.rouhani@gmail.com> and other contributors (see CONTRIBUTORS file).
// Licensed under the MIT License - https://raw.github.com/Robmaister/SharpNav/master/LICENSE
using System.Collections.Generic;
using SharpNav.Geometry;
namespace SharpNav
{
/// <summary>
/// A contour is formed from a region.
/// </summary>
public class Contour
{
//simplified vertices have much less edges
private ContourVertex[] vertices;
private RegionId regionId;
private Area area;
/// <summary>
/// Initializes a new instance of the <see cref="Contour"/> class.
/// </summary>
/// <param name="verts">The raw vertices of the contour.</param>
/// <param name="region">The region ID of the contour.</param>
/// <param name="area">The area ID of the contour.</param>
/// <param name="borderSize">The size of the border.</param>
public Contour(List<ContourVertex> verts, RegionId region, Area area, int borderSize)
{
this.vertices = verts.ToArray();
this.regionId = region;
this.area = area;
//remove offset
if (borderSize > 0)
{
for (int j = 0; j < vertices.Length; j++)
{
vertices[j].X -= borderSize;
vertices[j].Z -= borderSize;
}
}
}
/// <summary>
/// Gets the simplified vertices of the contour.
/// </summary>
public ContourVertex[] Vertices
{
get
{
return vertices;
}
}
/// <summary>
/// Gets a value indicating whether the contour is "null" (has less than 3 vertices).
/// </summary>
public bool IsNull
{
get
{
//TODO operator overload == and != with null?
if (vertices == null || vertices.Length < 3)
return true;
return false;
}
}
/// <summary>
/// Gets the area ID of the contour.
/// </summary>
public Area Area
{
get
{
return area;
}
}
/// <summary>
/// Gets the region ID of the contour.
/// </summary>
public RegionId RegionId
{
get
{
return regionId;
}
}
/// <summary>
/// Gets the 2D area of the contour. A positive area means the contour is going forwards, a negative
/// area maens it is going backwards.
/// </summary>
public int Area2D
{
get
{
int area = 0;
for (int i = 0, j = vertices.Length - 1; i < vertices.Length; j = i++)
{
ContourVertex vi = vertices[i], vj = vertices[j];
area += vi.X * vj.Z - vj.X * vi.Z;
}
return (area + 1) / 2;
}
}
/// <summary>
/// Merges another contour into this instance.
/// </summary>
/// <param name="contour">The contour to merge.</param>
public void MergeWith(Contour contour)
{
int lengthA = vertices.Length;
int lengthB = contour.vertices.Length;
int ia, ib;
GetClosestIndices(this, contour, out ia, out ib);
//create a list with the capacity set to the max number of possible verts to avoid expanding the list.
var newVerts = new List<ContourVertex>(vertices.Length + contour.vertices.Length + 2);
//copy contour A
for (int i = 0; i <= lengthA; i++)
newVerts.Add(vertices[(ia + i) % lengthA]);
//add contour B (other contour) to contour A (this contour)
for (int i = 0; i <= lengthB; i++)
newVerts.Add(contour.vertices[(ib + i) % lengthB]);
vertices = newVerts.ToArray();
//delete the other contour
contour.vertices = null;
}
/// <summary>
/// Finds the closest indices between two contours. Useful for merging contours.
/// </summary>
/// <param name="a">A contour.</param>
/// <param name="b">Another contour.</param>
/// <param name="indexA">The nearest index on contour A.</param>
/// <param name="indexB">The nearest index on contour B.</param>
private static void GetClosestIndices(Contour a, Contour b, out int indexA, out int indexB)
{
int closestDistance = int.MaxValue;
int lengthA = a.vertices.Length;
int lengthB = b.vertices.Length;
indexA = -1;
indexB = -1;
for (int i = 0; i < lengthA; i++)
{
int vertA = i;
int vertANext = (i + 1) % lengthA;
int vertAPrev = (i + lengthA - 1) % lengthA;
for (int j = 0; j < lengthB; j++)
{
int vertB = j;
//vertB must be infront of vertA
if (ContourVertex.IsLeft(ref a.vertices[vertAPrev], ref a.vertices[vertA], ref b.vertices[vertB]) &&
ContourVertex.IsLeft(ref a.vertices[vertA], ref a.vertices[vertANext], ref b.vertices[vertB]))
{
int dx = b.vertices[vertB].X - a.vertices[vertA].X;
int dz = b.vertices[vertB].Z - a.vertices[vertA].Z;
int tempDist = dx * dx + dz * dz;
if (tempDist < closestDistance)
{
indexA = i;
indexB = j;
closestDistance = tempDist;
}
}
}
}
}
/// <summary>
/// Simplify the contours by reducing the number of edges
/// </summary>
/// <param name="rawVerts">Initial vertices</param>
/// <param name="simplified">New and simplified vertices</param>
/// <param name="maxError">Maximum error allowed</param>
/// <param name="maxEdgeLen">The maximum edge length allowed</param>
/// <param name="buildFlags">Flags determines how to split the long edges</param>
public static void Simplify(List<ContourVertex> rawVerts, List<ContourVertex> simplified, float maxError, int maxEdgeLen, ContourBuildFlags buildFlags)
{
bool tesselateWallEdges = (buildFlags & ContourBuildFlags.TessellateWallEdges) == ContourBuildFlags.TessellateWallEdges;
bool tesselateAreaEdges = (buildFlags & ContourBuildFlags.TessellateAreaEdges) == ContourBuildFlags.TessellateAreaEdges;
//add initial points
bool hasConnections = false;
for (int i = 0; i < rawVerts.Count; i++)
{
if (rawVerts[i].RegionId.Id != 0)
{
hasConnections = true;
break;
}
}
if (hasConnections)
{
//contour has some portals to other regions
//add new point to every location where region changes
for (int i = 0, end = rawVerts.Count; i < end; i++)
{
int ii = (i + 1) % end;
bool differentRegions = rawVerts[i].RegionId.Id != rawVerts[ii].RegionId.Id;
bool areaBorders = RegionId.HasFlags(rawVerts[i].RegionId, RegionFlags.AreaBorder) != RegionId.HasFlags(rawVerts[ii].RegionId, RegionFlags.AreaBorder);
if (differentRegions || areaBorders)
{
simplified.Add(new ContourVertex(rawVerts[i], i));
}
}
}
//add some points if thhere are no connections
if (simplified.Count == 0)
{
//find lower-left and upper-right vertices of contour
int lowerLeftX = rawVerts[0].X;
int lowerLeftY = rawVerts[0].Y;
int lowerLeftZ = rawVerts[0].Z;
RegionId lowerLeftI = RegionId.Null;
int upperRightX = rawVerts[0].X;
int upperRightY = rawVerts[0].Y;
int upperRightZ = rawVerts[0].Z;
RegionId upperRightI = RegionId.Null;
//iterate through points
for (int i = 0; i < rawVerts.Count; i++)
{
int x = rawVerts[i].X;
int y = rawVerts[i].Y;
int z = rawVerts[i].Z;
if (x < lowerLeftX || (x == lowerLeftX && z < lowerLeftZ))
{
lowerLeftX = x;
lowerLeftY = y;
lowerLeftZ = z;
lowerLeftI = new RegionId(i);
}
if (x > upperRightX || (x == upperRightX && z > upperRightZ))
{
upperRightX = x;
upperRightY = y;
upperRightZ = z;
upperRightI = new RegionId(i);
}
}
//save the points
simplified.Add(new ContourVertex(lowerLeftX, lowerLeftY, lowerLeftZ, lowerLeftI));
simplified.Add(new ContourVertex(upperRightX, upperRightY, upperRightZ, upperRightI));
}
//add points until all points are within error tolerance of simplified slope
int numPoints = rawVerts.Count;
for (int i = 0; i < simplified.Count;)
{
int ii = (i + 1) % simplified.Count;
//obtain (x, z) coordinates, along with region id
int ax = simplified[i].X;
int az = simplified[i].Z;
int ai = (int)simplified[i].RegionId;
int bx = simplified[ii].X;
int bz = simplified[ii].Z;
int bi = (int)simplified[ii].RegionId;
float maxDeviation = 0;
int maxi = -1;
int ci, countIncrement, endi;
//traverse segment in lexilogical order (try to go from smallest to largest coordinates?)
if (bx > ax || (bx == ax && bz > az))
{
countIncrement = 1;
ci = (int)(ai + countIncrement) % numPoints;
endi = (int)bi;
}
else
{
countIncrement = numPoints - 1;
ci = (int)(bi + countIncrement) % numPoints;
endi = (int)ai;
}
//tessellate only outer edges or edges between areas
if (rawVerts[ci].RegionId.Id == 0 || RegionId.HasFlags(rawVerts[ci].RegionId, RegionFlags.AreaBorder))
{
//find the maximum deviation
while (ci != endi)
{
float deviation = Distance.PointToSegment2DSquared(rawVerts[ci].X, rawVerts[ci].Z, ax, az, bx, bz);
if (deviation > maxDeviation)
{
maxDeviation = deviation;
maxi = ci;
}
ci = (ci + countIncrement) % numPoints;
}
}
//If max deviation is larger than accepted error, add new point
if (maxi != -1 && maxDeviation > (maxError * maxError))
{
simplified.Insert(i + 1, new ContourVertex(rawVerts[maxi], maxi));
}
else
{
i++;
}
}
//split too long edges
if (maxEdgeLen > 0 && (tesselateAreaEdges || tesselateWallEdges))
{
for (int i = 0; i < simplified.Count;)
{
int ii = (i + 1) % simplified.Count;
//get (x, z) coordinates along with region id
int ax = simplified[i].X;
int az = simplified[i].Z;
int ai = (int)simplified[i].RegionId;
int bx = simplified[ii].X;
int bz = simplified[ii].Z;
int bi = (int)simplified[ii].RegionId;
//find maximum deviation from segment
int maxi = -1;
int ci = (int)(ai + 1) % numPoints;
//tessellate only outer edges or edges between areas
bool tess = false;
//wall edges
if (tesselateWallEdges && rawVerts[ci].RegionId.Id == 0)
tess = true;
//edges between areas
if (tesselateAreaEdges && RegionId.HasFlags(rawVerts[ci].RegionId, RegionFlags.AreaBorder))
tess = true;
if (tess)
{
int dx = bx - ax;
int dz = bz - az;
if (dx * dx + dz * dz > maxEdgeLen * maxEdgeLen)
{
//round based on lexilogical direction (smallest to largest cooridinates, first by x.
//if x coordinates are equal, then compare z coordinates)
int n = bi < ai ? (bi + numPoints - ai) : (bi - ai);
if (n > 1)
{
if (bx > ax || (bx == ax && bz > az))
maxi = (int)(ai + n / 2) % numPoints;
else
maxi = (int)(ai + (n + 1) / 2) % numPoints;
}
}
}
//add new point
if (maxi != -1)
{
simplified.Insert(i + 1, new ContourVertex(rawVerts[maxi], maxi));
}
else
{
i++;
}
}
}
for (int i = 0; i < simplified.Count; i++)
{
ContourVertex sv = simplified[i];
//take edge vertex flag from current raw point and neighbor region from next raw point
int ai = ((int)sv.RegionId + 1) % numPoints;
RegionId bi = sv.RegionId;
//save new region id
sv.RegionId = RegionId.FromRawBits(((int)rawVerts[ai].RegionId & (RegionId.MaskId | (int)RegionFlags.AreaBorder)) | ((int)rawVerts[(int)bi].RegionId & (int)RegionFlags.VertexBorder));
simplified[i] = sv;
}
}
/// <summary>
/// Removes degenerate segments from a simplified contour.
/// </summary>
/// <param name="simplified">The simplified contour.</param>
public static void RemoveDegenerateSegments(List<ContourVertex> simplified)
{
//remove adjacent vertices which are equal on the xz-plane
for (int i = 0; i < simplified.Count; i++)
{
int ni = i + 1;
if (ni >= simplified.Count)
ni = 0;
if (simplified[i].X == simplified[ni].X &&
simplified[i].Z == simplified[ni].Z)
{
//remove degenerate segment
simplified.RemoveAt(i);
i--;
}
}
}
}
}