// Copyright (c) 2013-2014 Robert Rouhani 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 { /// /// A contour is formed from a region. /// public class Contour { //simplified vertices have much less edges private ContourVertex[] vertices; private RegionId regionId; private Area area; /// /// Initializes a new instance of the class. /// /// The raw vertices of the contour. /// The region ID of the contour. /// The area ID of the contour. /// The size of the border. public Contour(List 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; } } } /// /// Gets the simplified vertices of the contour. /// public ContourVertex[] Vertices { get { return vertices; } } /// /// Gets a value indicating whether the contour is "null" (has less than 3 vertices). /// public bool IsNull { get { //TODO operator overload == and != with null? if (vertices == null || vertices.Length < 3) return true; return false; } } /// /// Gets the area ID of the contour. /// public Area Area { get { return area; } } /// /// Gets the region ID of the contour. /// public RegionId RegionId { get { return regionId; } } /// /// Gets the 2D area of the contour. A positive area means the contour is going forwards, a negative /// area maens it is going backwards. /// 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; } } /// /// Merges another contour into this instance. /// /// The contour to merge. 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(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; } /// /// Finds the closest indices between two contours. Useful for merging contours. /// /// A contour. /// Another contour. /// The nearest index on contour A. /// The nearest index on contour B. 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; } } } } } /// /// Simplify the contours by reducing the number of edges /// /// Initial vertices /// New and simplified vertices /// Maximum error allowed /// The maximum edge length allowed /// Flags determines how to split the long edges public static void Simplify(List rawVerts, List 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; } } /// /// Removes degenerate segments from a simplified contour. /// /// The simplified contour. public static void RemoveDegenerateSegments(List 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--; } } } } }