rimworld-animation-studio/Assets/Scripts/AdvancedPolygonCollider/AdvancedPolygonColliderUtilities.cs
2022-09-20 01:03:55 -05:00

1919 lines
No EOL
71 KiB
C#

/*
Advanced Polygon Collider (c) 2015 Digital Ruby, LLC
http://www.digitalruby.com
Source code may not be redistributed. Use in apps and games is fine.
*/
// This source code has been modified with great care from the farseer physics engine
/* ORIGINAL LICENSE COMPLIANCE REQUIRES THE FOLLOWING BE POSTED:
*
* Farseer Physics Engine:
* Copyright (c) 2012 Ian Qvist
*
* Original source Box2D:
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
*
* 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 System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
namespace DigitalRuby.AdvancedPolygonCollider
{
public sealed class TextureConverter
{
private const int closePixelsLength = 8;
private static int[,] closePixels = new int[closePixelsLength, 2] { { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } };
private const float hullTolerance = 0.9f;
private byte[] solids;
private int solidsLength;
private int width;
private int height;
public List<Vertices> DetectVertices(UnityEngine.Color[] colors, int width, int alphaTolerance)
{
this.width = width;
this.height = colors.Length / width;
int n, s;
solids = new byte[colors.Length];
// calculate solids once, this makes tolerance lookups much faster
for (int i = 0; i < solids.Length; i++)
{
// Diff will be negative for visitable value, resulting in a sign multiply and value of 0.
// Sign multiply will be -1 for positive visitable values, resulting in non-zero value.
n = alphaTolerance - (int)(colors[i].a * 255.0f);
s = ((int)((n & 0x80000000) >> 31)) - 1; // sign multiplier is -1 for positive numbers and 0 for negative numbers
n = n * s * s; // multiply n by sign squared to get 0 for negative numbers (solid) and > zero for positive numbers (pass through)
solids[i] = (byte)n; // assign value back
}
solidsLength = colors.Length;
List<Vertices> detectedVerticesList = DetectVertices();
List<Vertices> result = new List<Vertices>();
for (int i = 0; i < detectedVerticesList.Count; i++)
{
result.Add(detectedVerticesList[i]);
}
return result;
}
public List<Vertices> DetectVertices()
{
List<Vertices> detectedPolygons = new List<Vertices>();
Vector2? holeEntrance = null;
Vector2? polygonEntrance = null;
List<Vector2> blackList = new List<Vector2>();
bool searchOn;
do
{
Vertices polygon;
if (detectedPolygons.Count == 0)
{
// First pass / single polygon
polygon = new Vertices(CreateSimplePolygon(Vector2.zero, Vector2.zero));
if (polygon.Count > 2)
{
polygonEntrance = GetTopMostVertex(polygon);
}
}
else if (polygonEntrance.HasValue)
{
// Multi pass / multiple polygons
polygon = new Vertices(CreateSimplePolygon(polygonEntrance.Value, new Vector2(polygonEntrance.Value.x - 1f, polygonEntrance.Value.y)));
}
else
{
break;
}
searchOn = false;
if (polygon.Count > 2)
{
do
{
holeEntrance = SearchHoleEntrance(polygon, holeEntrance);
if (holeEntrance.HasValue)
{
if (!blackList.Contains(holeEntrance.Value))
{
blackList.Add(holeEntrance.Value);
Vertices holePolygon = CreateSimplePolygon(holeEntrance.Value, new Vector2(holeEntrance.Value.x + 1, holeEntrance.Value.y));
if (holePolygon != null && holePolygon.Count > 2)
{
// Add first hole polygon vertex to close the hole polygon.
holePolygon.Add(holePolygon[0]);
int vertex1Index, vertex2Index;
if (SplitPolygonEdge(polygon, holeEntrance.Value, out vertex1Index, out vertex2Index))
{
polygon.InsertRange(vertex2Index, holePolygon);
}
break;
}
}
else
{
break;
}
}
else
{
break;
}
}
while (true);
detectedPolygons.Add(polygon);
}
if (polygonEntrance.HasValue && SearchNextHullEntrance(detectedPolygons, polygonEntrance.Value, out polygonEntrance))
{
searchOn = true;
}
}
while (searchOn);
if (detectedPolygons == null || (detectedPolygons != null && detectedPolygons.Count == 0))
{
throw new Exception("Couldn't detect any vertices.");
}
return detectedPolygons;
}
private void ApplyTriangulationCompatibleWinding(ref List<Vertices> detectedPolygons)
{
for (int i = 0; i < detectedPolygons.Count; i++)
{
detectedPolygons[i].Reverse();
}
}
public bool IsSolid(ref Vector2 v)
{
return IsSolid((int)v.x + ((int)v.y * width));
}
public bool IsSolid(int x, int y)
{
return IsSolid(x + (y * width));
}
public bool IsSolid(int index)
{
return (index >= 0 && index < solids.Length && solids[index] == 0);
}
public bool InBounds(ref Vector2 coord)
{
return (coord.x >= 0f && coord.x < width && coord.y >= 0f && coord.y < height);
}
/// <summary>
/// Function to search for an entrance point of a hole in a polygon. It searches the polygon from top to bottom between the polygon edges.
/// </summary>
/// <param name="polygon">The polygon to search in.</param>
/// <param name="lastHoleEntrance">The last entrance point.</param>
/// <returns>The next holes entrance point. Null if ther are no holes.</returns>
private Vector2? SearchHoleEntrance(Vertices polygon, Vector2? lastHoleEntrance)
{
if (polygon == null)
{
throw new ArgumentNullException("'polygon' can't be null.");
}
else if (polygon.Count < 3)
{
throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
}
List<float> xCoords;
Vector2? entrance;
int startY;
int endY;
int lastSolid = 0;
bool foundSolid;
bool foundTransparent;
// Set start y coordinate.
if (lastHoleEntrance.HasValue)
{
// We need the y coordinate only.
startY = (int)lastHoleEntrance.Value.y;
}
else
{
// Start from the top of the polygon if last entrance == null.
startY = (int)GetTopMostCoord(polygon);
}
// Set the end y coordinate.
endY = (int)GetBottomMostCoord(polygon);
if (startY >= 0 && startY < height && endY > 0 && endY < height)
{
// go from top to bottom of the polygon
for (int y = startY; y <= endY; y++)
{
// get x-coord of every polygon edge which crosses y
xCoords = SearchCrossingEdges(polygon, y);
// We need an even number of crossing edges.
// It's always a pair of start and end edge: nothing | polygon | hole | polygon | nothing ...
// If it's not then don't bother, it's probably a peak ...
// ...which should be filtered out by SearchCrossingEdges() anyway.
if (xCoords.Count > 1 && xCoords.Count % 2 == 0)
{
// Ok, this is short, but probably a little bit confusing.
// This part searches from left to right between the edges inside the polygon.
// The problem: We are using the polygon data to search in the texture data.
// That's simply not accurate, but necessary because of performance.
for (int i = 0; i < xCoords.Count; i += 2)
{
foundSolid = false;
foundTransparent = false;
// We search between the edges inside the polygon.
for (int x = (int)xCoords[i]; x <= (int)xCoords[i + 1]; x++)
{
// First pass: IsSolid might return false.
// In that case the polygon edge doesn't lie on the texture's solid pixel, because of the hull tolearance.
// If the edge lies before the first solid pixel then we need to skip our transparent pixel finds.
// The algorithm starts to search for a relevant transparent pixel (which indicates a possible hole)
// after it has found a solid pixel.
// After we've found a solid and a transparent pixel (a hole's left edge)
// we search for a solid pixel again (a hole's right edge).
// When found the distance of that coodrinate has to be greater then the hull tolerance.
if (IsSolid(x, y))
{
if (!foundTransparent)
{
foundSolid = true;
lastSolid = x;
}
if (foundSolid && foundTransparent)
{
entrance = new Vector2(lastSolid, y);
if (DistanceToHullAcceptable(polygon, entrance.Value, true))
return entrance;
entrance = null;
break;
}
}
else
{
if (foundSolid)
foundTransparent = true;
}
}
}
}
else
{
if (xCoords.Count % 2 == 0)
System.Diagnostics.Debug.WriteLine("SearchCrossingEdges() % 2 != 0");
}
}
}
return null;
}
private bool DistanceToHullAcceptableHoles(Vertices polygon, Vector2 point, bool higherDetail)
{
if (polygon == null)
throw new ArgumentNullException("polygon", "'polygon' can't be null.");
if (polygon.Count < 3)
throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
// Check the distance to main polygon.
if (DistanceToHullAcceptable(polygon, point, higherDetail))
{
// All distances are larger then _hullTolerance.
return true;
}
// Default to false.
return false;
}
private bool DistanceToHullAcceptable(Vertices polygon, Vector2 point, bool higherDetail)
{
if (polygon == null)
throw new ArgumentNullException("polygon", "'polygon' can't be null.");
if (polygon.Count < 3)
throw new ArgumentException("'polygon.Count' can't be less then 3.");
Vector2 edgeVertex2 = polygon[polygon.Count - 1];
Vector2 edgeVertex1;
if (higherDetail)
{
for (int i = 0; i < polygon.Count; i++)
{
edgeVertex1 = polygon[i];
if (LineTools.DistanceBetweenPointAndLineSegment(ref point, ref edgeVertex1, ref edgeVertex2) <= hullTolerance || Vector2.Distance(point, edgeVertex1) <= hullTolerance)
return false;
edgeVertex2 = polygon[i];
}
return true;
}
else
{
for (int i = 0; i < polygon.Count; i++)
{
edgeVertex1 = polygon[i];
if (LineTools.DistanceBetweenPointAndLineSegment(ref point, ref edgeVertex1, ref edgeVertex2) <= hullTolerance)
return false;
edgeVertex2 = polygon[i];
}
return true;
}
}
private bool InPolygon(Vertices polygon, Vector2 point)
{
bool inPolygon = !DistanceToHullAcceptableHoles(polygon, point, true);
if (!inPolygon)
{
List<float> xCoords = SearchCrossingEdgesHoles(polygon, (int)point.y);
if (xCoords.Count > 0 && xCoords.Count % 2 == 0)
{
for (int i = 0; i < xCoords.Count; i += 2)
{
if (xCoords[i] <= point.x && xCoords[i + 1] >= point.x)
return true;
}
}
return false;
}
return true;
}
private Vector2? GetTopMostVertex(Vertices vertices)
{
float topMostValue = float.MaxValue;
Vector2? topMost = null;
for (int i = 0; i < vertices.Count; i++)
{
if (topMostValue > vertices[i].y)
{
topMostValue = vertices[i].y;
topMost = vertices[i];
}
}
return topMost;
}
private float GetTopMostCoord(Vertices vertices)
{
float returnValue = float.MaxValue;
for (int i = 0; i < vertices.Count; i++)
{
if (returnValue > vertices[i].y)
{
returnValue = vertices[i].y;
}
}
return returnValue;
}
private float GetBottomMostCoord(Vertices vertices)
{
float returnValue = float.MinValue;
for (int i = 0; i < vertices.Count; i++)
{
if (returnValue < vertices[i].y)
{
returnValue = vertices[i].y;
}
}
return returnValue;
}
private List<float> SearchCrossingEdgesHoles(Vertices polygon, int y)
{
if (polygon == null)
throw new ArgumentNullException("polygon", "'polygon' can't be null.");
if (polygon.Count < 3)
throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
List<float> result = SearchCrossingEdges(polygon, y);
result.Sort();
return result;
}
/// <summary>
/// Searches the polygon for the x coordinates of the edges that cross the specified y coordinate.
/// </summary>
/// <param name="polygon">Polygon to search in.</param>
/// <param name="y">Y coordinate to check for edges.</param>
/// <returns>Descending sorted list of x coordinates of edges that cross the specified y coordinate.</returns>
private List<float> SearchCrossingEdges(Vertices polygon, int y)
{
// sick-o-note:
// Used to search the x coordinates of edges in the polygon for a specific y coordinate.
// (Usualy comming from the texture data, that's why it's an int and not a float.)
List<float> edges = new List<float>();
// current edge
Vector2 slope;
Vector2 vertex1; // i
Vector2 vertex2; // i - 1
// next edge
Vector2 nextSlope;
Vector2 nextVertex; // i + 1
bool addFind;
if (polygon.Count > 2)
{
// There is a gap between the last and the first vertex in the vertex list.
// We will bridge that by setting the last vertex (vertex2) to the last
// vertex in the list.
vertex2 = polygon[polygon.Count - 1];
// We are moving along the polygon edges.
for (int i = 0; i < polygon.Count; i++)
{
vertex1 = polygon[i];
// Approx. check if the edge crosses our y coord.
if ((vertex1.y >= y && vertex2.y <= y) ||
(vertex1.y <= y && vertex2.y >= y))
{
// Ignore edges that are parallel to y.
if (vertex1.y != vertex2.y)
{
addFind = true;
slope = vertex2 - vertex1;
// Special threatment for edges that end at the y coord.
if (vertex1.y == y)
{
// Create preview of the next edge.
nextVertex = polygon[(i + 1) % polygon.Count];
nextSlope = vertex1 - nextVertex;
// Ignore peaks.
// If thwo edges are aligned like this: /\ and the y coordinate lies on the top,
// then we get the same x coord twice and we don't need that.
if (slope.y > 0)
addFind = (nextSlope.y <= 0);
else
addFind = (nextSlope.y >= 0);
}
if (addFind)
edges.Add((y - vertex1.y) / slope.y * slope.x + vertex1.x); // Calculate and add the x coord.
}
}
// vertex1 becomes vertex2 :).
vertex2 = vertex1;
}
}
edges.Sort();
return edges;
}
private bool SplitPolygonEdge(Vertices polygon, Vector2 coordInsideThePolygon, out int vertex1Index, out int vertex2Index)
{
Vector2 slope;
int nearestEdgeVertex1Index = 0;
int nearestEdgeVertex2Index = 0;
bool edgeFound = false;
float shortestDistance = float.MaxValue;
bool edgeCoordFound = false;
Vector2 foundEdgeCoord = Vector2.zero;
List<float> xCoords = SearchCrossingEdges(polygon, (int)coordInsideThePolygon.y);
vertex1Index = 0;
vertex2Index = 0;
foundEdgeCoord.y = coordInsideThePolygon.y;
if (xCoords != null && xCoords.Count > 1 && xCoords.Count % 2 == 0)
{
float distance;
for (int i = 0; i < xCoords.Count; i++)
{
if (xCoords[i] < coordInsideThePolygon.x)
{
distance = coordInsideThePolygon.x - xCoords[i];
if (distance < shortestDistance)
{
shortestDistance = distance;
foundEdgeCoord.x = xCoords[i];
edgeCoordFound = true;
}
}
}
if (edgeCoordFound)
{
shortestDistance = float.MaxValue;
int edgeVertex2Index = polygon.Count - 1;
int edgeVertex1Index;
for (edgeVertex1Index = 0; edgeVertex1Index < polygon.Count; edgeVertex1Index++)
{
Vector2 tempVector1 = polygon[edgeVertex1Index];
Vector2 tempVector2 = polygon[edgeVertex2Index];
distance = LineTools.DistanceBetweenPointAndLineSegment(ref foundEdgeCoord,
ref tempVector1, ref tempVector2);
if (distance < shortestDistance)
{
shortestDistance = distance;
nearestEdgeVertex1Index = edgeVertex1Index;
nearestEdgeVertex2Index = edgeVertex2Index;
edgeFound = true;
}
edgeVertex2Index = edgeVertex1Index;
}
if (edgeFound)
{
slope = polygon[nearestEdgeVertex2Index] - polygon[nearestEdgeVertex1Index];
slope.Normalize();
Vector2 tempVector = polygon[nearestEdgeVertex1Index];
distance = Vector2.Distance(tempVector, foundEdgeCoord);
vertex1Index = nearestEdgeVertex1Index;
vertex2Index = nearestEdgeVertex1Index + 1;
polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex1Index]);
polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex2Index]);
return true;
}
}
}
return false;
}
/// <summary>
///
/// </summary>
/// <param name="entrance"></param>
/// <param name="last"></param>
/// <returns></returns>
private Vertices CreateSimplePolygon(Vector2 entrance, Vector2 last)
{
bool entranceFound = false;
bool endOfHull = false;
Vertices polygon = new Vertices(32);
Vertices hullArea = new Vertices(32);
Vertices endOfHullArea = new Vertices(32);
Vector2 current = Vector2.zero;
#region Entrance check
// Get the entrance point.
if (entrance == Vector2.zero || !InBounds(ref entrance))
{
entranceFound = SearchHullEntrance(out entrance);
if (entranceFound)
{
current = new Vector2(entrance.x - 1f, entrance.y);
}
}
else
{
if (IsSolid(ref entrance))
{
if (IsNearPixel(ref entrance, ref last))
{
current = last;
entranceFound = true;
}
else
{
Vector2 temp;
if (SearchNearPixels(false, ref entrance, out temp))
{
current = temp;
entranceFound = true;
}
else
{
entranceFound = false;
}
}
}
}
#endregion
if (entranceFound)
{
polygon.Add(entrance);
hullArea.Add(entrance);
Vector2 next = entrance;
do
{
// Search in the pre vision list for an outstanding point.
Vector2 outstanding;
if (SearchForOutstandingVertex(hullArea, out outstanding))
{
if (endOfHull)
{
// We have found the next pixel, but is it on the last bit of the hull?
if (endOfHullArea.Contains(outstanding))
{
// Indeed.
polygon.Add(outstanding);
}
// That's enough, quit.
break;
}
// Add it and remove all vertices that don't matter anymore
// (all the vertices before the outstanding).
polygon.Add(outstanding);
hullArea.RemoveRange(0, hullArea.IndexOf(outstanding));
}
// Last point gets current and current gets next. Our little spider is moving forward on the hull ;).
last = current;
current = next;
// Get the next point on hull.
// TODO: Fix infinite loop here sometimes...
if (GetNextHullPoint(ref last, ref current, out next))
{
// Add the vertex to a hull pre vision list.
hullArea.Add(next);
}
else
{
// Quit
break;
}
if (next == entrance && !endOfHull)
{
// It's the last bit of the hull, search on and exit at next found vertex.
endOfHull = true;
endOfHullArea.AddRange(hullArea);
// We don't want the last vertex to be the same as the first one, because it causes the triangulation code to crash.
if (endOfHullArea.Contains(entrance))
endOfHullArea.Remove(entrance);
}
} while (true);
}
return polygon;
}
private bool SearchNearPixels(bool searchingForSolidPixel, ref Vector2 current, out Vector2 foundPixel)
{
for (int i = 0; i < closePixelsLength; i++)
{
int x = (int)current.x + closePixels[i, 0];
int y = (int)current.y + closePixels[i, 1];
if (!searchingForSolidPixel ^ IsSolid(x, y))
{
foundPixel = new Vector2(x, y);
return true;
}
}
// Nothing found.
foundPixel = Vector2.zero;
return false;
}
private bool IsNearPixel(ref Vector2 current, ref Vector2 near)
{
for (int i = 0; i < closePixelsLength; i++)
{
int x = (int)current.x + closePixels[i, 0];
int y = (int)current.y + closePixels[i, 1];
if (x >= 0 && x <= width && y >= 0 && y <= height)
{
if (x == (int)near.x && y == (int)near.y)
{
return true;
}
}
}
return false;
}
private bool SearchHullEntrance(out Vector2 entrance)
{
// Search for first solid pixel.
for (int y = 0; y <= height; y++)
{
for (int x = 0; x <= width; x++)
{
if (IsSolid(x, y))
{
entrance = new Vector2(x, y);
return true;
}
}
}
// If there are no solid pixels.
entrance = Vector2.zero;
return false;
}
/// <summary>
/// Searches for the next shape.
/// </summary>
/// <param name="detectedPolygons">Already detected polygons.</param>
/// <param name="start">Search start coordinate.</param>
/// <param name="entrance">Returns the found entrance coordinate. Null if no other shapes found.</param>
/// <returns>True if a new shape was found.</returns>
private bool SearchNextHullEntrance(List<Vertices> detectedPolygons, Vector2 start, out Vector2? entrance)
{
int x;
bool foundTransparent = false;
bool inPolygon = false;
for (int i = (int)start.x + (int)start.y * width; i <= solidsLength; i++)
{
if (IsSolid(i))
{
if (foundTransparent)
{
x = i % width;
entrance = new Vector2(x, (i - x) / (float)width);
inPolygon = false;
for (int polygonIdx = 0; polygonIdx < detectedPolygons.Count; polygonIdx++)
{
if (InPolygon(detectedPolygons[polygonIdx], entrance.Value))
{
inPolygon = true;
break;
}
}
if (inPolygon)
foundTransparent = false;
else
return true;
}
}
else
foundTransparent = true;
}
entrance = null;
return false;
}
private bool GetNextHullPoint(ref Vector2 last, ref Vector2 current, out Vector2 next)
{
int x;
int y;
int indexOfFirstPixelToCheck = GetIndexOfFirstPixelToCheck(ref last, ref current);
int indexOfPixelToCheck;
for (int i = 0; i < closePixelsLength; i++)
{
indexOfPixelToCheck = (indexOfFirstPixelToCheck + i) % closePixelsLength;
x = (int)current.x + closePixels[indexOfPixelToCheck, 0];
y = (int)current.y + closePixels[indexOfPixelToCheck, 1];
if (x >= 0 && x < width && y >= 0 && y <= height)
{
if (IsSolid(x, y))
{
next = new Vector2(x, y);
return true;
}
}
}
next = Vector2.zero;
return false;
}
private bool SearchForOutstandingVertex(Vertices hullArea, out Vector2 outstanding)
{
Vector2 outstandingResult = Vector2.zero;
bool found = false;
if (hullArea.Count > 2)
{
int hullAreaLastPoint = hullArea.Count - 1;
Vector2 tempVector1;
Vector2 tempVector2 = hullArea[0];
Vector2 tempVector3 = hullArea[hullAreaLastPoint];
// Search between the first and last hull point.
for (int i = 1; i < hullAreaLastPoint; i++)
{
tempVector1 = hullArea[i];
// Check if the distance is over the one that's tolerable.
if (LineTools.DistanceBetweenPointAndLineSegment(ref tempVector1, ref tempVector2, ref tempVector3) >= hullTolerance)
{
outstandingResult = hullArea[i];
found = true;
break;
}
}
}
outstanding = outstandingResult;
return found;
}
private int GetIndexOfFirstPixelToCheck(ref Vector2 last, ref Vector2 current)
{
// .: pixel
// l: last position
// c: current position
// f: first pixel for next search
// f . .
// l c .
// . . .
//Calculate in which direction the last move went and decide over the next pixel to check.
switch ((int)(current.x - last.x))
{
case 1:
switch ((int)(current.y - last.y))
{
case 1:
return 1;
case 0:
return 0;
case -1:
return 7;
}
break;
case 0:
switch ((int)(current.y - last.y))
{
case 1:
return 2;
case -1:
return 6;
}
break;
case -1:
switch ((int)(current.y - last.y))
{
case 1:
return 3;
case 0:
return 4;
case -1:
return 5;
}
break;
}
return 0;
}
}
public class Vertices : List<Vector2>
{
public Vertices() { }
public Vertices(int capacity) : base(capacity) { }
public Vertices(IEnumerable<Vector2> vertices)
{
AddRange(vertices);
}
/// <summary>
/// Gets the next index. Used for iterating all the edges with wrap-around.
/// </summary>
/// <param name="index">The current index</param>
public int NextIndex(int index)
{
return (index + 1 > Count - 1) ? 0 : index + 1;
}
/// <summary>
/// Gets the next vertex. Used for iterating all the edges with wrap-around.
/// </summary>
/// <param name="index">The current index</param>
public Vector2 NextVertex(int index)
{
return this[NextIndex(index)];
}
/// <summary>
/// Gets the previous index. Used for iterating all the edges with wrap-around.
/// </summary>
/// <param name="index">The current index</param>
public int PreviousIndex(int index)
{
return index - 1 < 0 ? Count - 1 : index - 1;
}
/// <summary>
/// Gets the previous vertex. Used for iterating all the edges with wrap-around.
/// </summary>
/// <param name="index">The current index</param>
public Vector2 PreviousVertex(int index)
{
return this[PreviousIndex(index)];
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
for (int i = 0; i < Count; i++)
{
builder.Append(this[i].ToString());
if (i < Count - 1)
{
builder.Append(" ");
}
}
return builder.ToString();
}
}
/// <summary>
/// Collection of helper methods for misc collisions.
/// Does float tolerance and line collisions with lines and AABBs.
/// </summary>
public static class LineTools
{
public static float DistanceBetweenPointAndLineSegment(ref Vector2 point, ref Vector2 start, ref Vector2 end)
{
if (start == end)
return Vector2.Distance(point, start);
Vector2 v = end - start;
Vector2 w = point - start;
float c1 = Vector2.Dot(w, v);
if (c1 <= 0) return Vector2.Distance(point, start);
float c2 = Vector2.Dot(v, v);
if (c2 <= c1) return Vector2.Distance(point, end);
float b = c1 / c2;
Vector2 pointOnLine = (start + (v * b));
return Vector2.Distance(point, pointOnLine);
}
// From Eric Jordan's convex decomposition library
/// <summary>
///Check if the lines a0->a1 and b0->b1 cross.
///If they do, intersectionPoint will be filled
///with the point of crossing.
///
///Grazing lines should not return true.
///
/// </summary>
public static bool LineIntersect2(ref Vector2 a0, ref Vector2 a1, ref Vector2 b0, ref Vector2 b1, out Vector2 intersectionPoint)
{
intersectionPoint = Vector2.zero;
if (a0 == b0 || a0 == b1 || a1 == b0 || a1 == b1)
return false;
float x1 = a0.x;
float y1 = a0.y;
float x2 = a1.x;
float y2 = a1.y;
float x3 = b0.x;
float y3 = b0.y;
float x4 = b1.x;
float y4 = b1.y;
//AABB early exit
if (Math.Max(x1, x2) < Math.Min(x3, x4) || Math.Max(x3, x4) < Math.Min(x1, x2))
return false;
if (Math.Max(y1, y2) < Math.Min(y3, y4) || Math.Max(y3, y4) < Math.Min(y1, y2))
return false;
float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3));
float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3));
float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (Math.Abs(denom) < Mathf.Epsilon)
{
//Lines are too close to parallel to call
return false;
}
ua /= denom;
ub /= denom;
if ((0 < ua) && (ua < 1) && (0 < ub) && (ub < 1))
{
intersectionPoint.x = (x1 + ua * (x2 - x1));
intersectionPoint.y = (y1 + ua * (y2 - y1));
return true;
}
return false;
}
//From Mark Bayazit's convex decomposition algorithm
public static Vector2 LineIntersect(Vector2 p1, Vector2 p2, Vector2 q1, Vector2 q2)
{
Vector2 i = Vector2.zero;
float a1 = p2.y - p1.y;
float b1 = p1.x - p2.x;
float c1 = a1 * p1.x + b1 * p1.y;
float a2 = q2.y - q1.y;
float b2 = q1.x - q2.x;
float c2 = a2 * q1.x + b2 * q1.y;
float det = a1 * b2 - a2 * b1;
if (Mathf.Abs(det) > Mathf.Epsilon)
{
// lines are not parallel
i.x = (b2 * c1 - b1 * c2) / det;
i.y = (a1 * c2 - a2 * c1) / det;
}
return i;
}
/// <summary>
/// This method detects if two line segments (or lines) intersect,
/// and, if so, the point of intersection. Use the <paramref name="firstIsSegment"/> and
/// <paramref name="secondIsSegment"/> parameters to set whether the intersection point
/// must be on the first and second line segments. Setting these
/// both to true means you are doing a line-segment to line-segment
/// intersection. Setting one of them to true means you are doing a
/// line to line-segment intersection test, and so on.
/// Note: If two line segments are coincident, then
/// no intersection is detected (there are actually
/// infinite intersection points).
/// Author: Jeremy Bell
/// </summary>
/// <param name="point1">The first point of the first line segment.</param>
/// <param name="point2">The second point of the first line segment.</param>
/// <param name="point3">The first point of the second line segment.</param>
/// <param name="point4">The second point of the second line segment.</param>
/// <param name="point">This is set to the intersection
/// point if an intersection is detected.</param>
/// <param name="firstIsSegment">Set this to true to require that the
/// intersection point be on the first line segment.</param>
/// <param name="secondIsSegment">Set this to true to require that the
/// intersection point be on the second line segment.</param>
/// <returns>True if an intersection is detected, false otherwise.</returns>
public static bool LineIntersect(ref Vector2 point1, ref Vector2 point2, ref Vector2 point3, ref Vector2 point4, bool firstIsSegment, bool secondIsSegment, out Vector2 point)
{
point = new Vector2();
// these are reused later.
// each lettered sub-calculation is used twice, except
// for b and d, which are used 3 times
float a = point4.y - point3.y;
float b = point2.x - point1.x;
float c = point4.x - point3.x;
float d = point2.y - point1.y;
// denominator to solution of linear system
float denom = (a * b) - (c * d);
// if denominator is 0, then lines are parallel
if (Mathf.Abs(denom) > Mathf.Epsilon)
{
float e = point1.y - point3.y;
float f = point1.x - point3.x;
float oneOverDenom = 1.0f / denom;
// numerator of first equation
float ua = (c * e) - (a * f);
ua *= oneOverDenom;
// check if intersection point of the two lines is on line segment 1
if (!firstIsSegment || ua >= 0.0f && ua <= 1.0f)
{
// numerator of second equation
float ub = (b * e) - (d * f);
ub *= oneOverDenom;
// check if intersection point of the two lines is on line segment 2
// means the line segments intersect, since we know it is on
// segment 1 as well.
if (!secondIsSegment || ub >= 0.0f && ub <= 1.0f)
{
// check if they are coincident (no collision in this case)
if (ua != 0f || ub != 0f)
{
//There is an intersection
point.x = point1.x + ua * b;
point.y = point1.y + ua * d;
return true;
}
}
}
}
return false;
}
/// <summary>
/// This method detects if two line segments (or lines) intersect,
/// and, if so, the point of intersection. Use the <paramref name="firstIsSegment"/> and
/// <paramref name="secondIsSegment"/> parameters to set whether the intersection point
/// must be on the first and second line segments. Setting these
/// both to true means you are doing a line-segment to line-segment
/// intersection. Setting one of them to true means you are doing a
/// line to line-segment intersection test, and so on.
/// Note: If two line segments are coincident, then
/// no intersection is detected (there are actually
/// infinite intersection points).
/// Author: Jeremy Bell
/// </summary>
/// <param name="point1">The first point of the first line segment.</param>
/// <param name="point2">The second point of the first line segment.</param>
/// <param name="point3">The first point of the second line segment.</param>
/// <param name="point4">The second point of the second line segment.</param>
/// <param name="intersectionPoint">This is set to the intersection
/// point if an intersection is detected.</param>
/// <param name="firstIsSegment">Set this to true to require that the
/// intersection point be on the first line segment.</param>
/// <param name="secondIsSegment">Set this to true to require that the
/// intersection point be on the second line segment.</param>
/// <returns>True if an intersection is detected, false otherwise.</returns>
public static bool LineIntersect(Vector2 point1, Vector2 point2, Vector2 point3, Vector2 point4, bool firstIsSegment, bool secondIsSegment, out Vector2 intersectionPoint)
{
return LineIntersect(ref point1, ref point2, ref point3, ref point4, firstIsSegment, secondIsSegment, out intersectionPoint);
}
/// <summary>
/// This method detects if two line segments intersect,
/// and, if so, the point of intersection.
/// Note: If two line segments are coincident, then
/// no intersection is detected (there are actually
/// infinite intersection points).
/// </summary>
/// <param name="point1">The first point of the first line segment.</param>
/// <param name="point2">The second point of the first line segment.</param>
/// <param name="point3">The first point of the second line segment.</param>
/// <param name="point4">The second point of the second line segment.</param>
/// <param name="intersectionPoint">This is set to the intersection
/// point if an intersection is detected.</param>
/// <returns>True if an intersection is detected, false otherwise.</returns>
public static bool LineIntersect(ref Vector2 point1, ref Vector2 point2, ref Vector2 point3, ref Vector2 point4, out Vector2 intersectionPoint)
{
return LineIntersect(ref point1, ref point2, ref point3, ref point4, true, true, out intersectionPoint);
}
/// <summary>
/// This method detects if two line segments intersect,
/// and, if so, the point of intersection.
/// Note: If two line segments are coincident, then
/// no intersection is detected (there are actually
/// infinite intersection points).
/// </summary>
/// <param name="point1">The first point of the first line segment.</param>
/// <param name="point2">The second point of the first line segment.</param>
/// <param name="point3">The first point of the second line segment.</param>
/// <param name="point4">The second point of the second line segment.</param>
/// <param name="intersectionPoint">This is set to the intersection
/// point if an intersection is detected.</param>
/// <returns>True if an intersection is detected, false otherwise.</returns>
public static bool LineIntersect(Vector2 point1, Vector2 point2, Vector2 point3, Vector2 point4, out Vector2 intersectionPoint)
{
return LineIntersect(ref point1, ref point2, ref point3, ref point4, true, true, out intersectionPoint);
}
/// <summary>
/// Get all intersections between a line segment and a list of vertices
/// representing a polygon. The vertices reuse adjacent points, so for example
/// edges one and two are between the first and second vertices and between the
/// second and third vertices. The last edge is between vertex vertices.Count - 1
/// and verts0. (ie, vertices from a Geometry or AABB)
/// </summary>
/// <param name="point1">The first point of the line segment to test</param>
/// <param name="point2">The second point of the line segment to test.</param>
/// <param name="vertices">The vertices, as described above</param>
public static Vertices LineSegmentVerticesIntersect(ref Vector2 point1, ref Vector2 point2, Vertices vertices)
{
Vertices intersectionPoints = new Vertices();
for (int i = 0; i < vertices.Count; i++)
{
Vector2 point;
if (LineIntersect(vertices[i], vertices[vertices.NextIndex(i)], point1, point2, true, true, out point))
{
intersectionPoints.Add(point);
}
}
return intersectionPoints;
}
}
/// <summary>
/// Convex decomposition algorithm created by Mark Bayazit (http://mnbayazit.com/)
/// For more information about this algorithm, see http://mnbayazit.com/406/bayazit
/// </summary>
public static class BayazitDecomposer
{
private static Vector2 At(int i, List<Vector2> vertices)
{
int s = vertices.Count;
return s == 1 ? vertices[0] : vertices[i < 0 ? s - (- i % s) : i % s];
}
private static List<Vector2> Copy(int i, int j, List<Vector2> vertices)
{
List<Vector2> p = new List<Vector2>();
while (j < i) j += vertices.Count;
//p.reserve(j - i + 1);
for (; i <= j; ++i)
{
p.Add(At(i, vertices));
}
return p;
}
/// <summary>
/// Decompose the polygon into several smaller non-concave polygon.
/// If the polygon is already convex, it will return the original polygon, unless it is over Settings.MaxPolygonVertices.
/// Precondition: Counter Clockwise polygon
/// </summary>
/// <param name="vertices"></param>
/// <returns></returns>
public static List<List<Vector2>> ConvexPartition(List<Vector2> vertices)
{
//We force it to CCW as it is a precondition in this algorithm.
ForceCounterClockWise(vertices);
List<List<Vector2>> list = new List<List<Vector2>>();
float d, lowerDist, upperDist;
Vector2 p;
Vector2 lowerInt = new Vector2();
Vector2 upperInt = new Vector2(); // intersection points
int lowerIndex = 0, upperIndex = 0;
List<Vector2> lowerPoly, upperPoly;
for (int i = 0; i < vertices.Count; ++i)
{
if (Reflex(i, vertices))
{
lowerDist = upperDist = float.MaxValue; // std::numeric_limits<qreal>::max();
for (int j = 0; j < vertices.Count; ++j)
{
// if line intersects with an edge
if (Left(At(i - 1, vertices), At(i, vertices), At(j, vertices)) &&
RightOn(At(i - 1, vertices), At(i, vertices), At(j - 1, vertices)))
{
// find the point of intersection
p = LineIntersect(At(i - 1, vertices), At(i, vertices), At(j, vertices),
At(j - 1, vertices));
if (Right(At(i + 1, vertices), At(i, vertices), p))
{
// make sure it's inside the poly
d = SquareDist(At(i, vertices), p);
if (d < lowerDist)
{
// keep only the closest intersection
lowerDist = d;
lowerInt = p;
lowerIndex = j;
}
}
}
if (Left(At(i + 1, vertices), At(i, vertices), At(j + 1, vertices)) &&
RightOn(At(i + 1, vertices), At(i, vertices), At(j, vertices)))
{
p = LineIntersect(At(i + 1, vertices), At(i, vertices), At(j, vertices),
At(j + 1, vertices));
if (Left(At(i - 1, vertices), At(i, vertices), p))
{
d = SquareDist(At(i, vertices), p);
if (d < upperDist)
{
upperDist = d;
upperIndex = j;
upperInt = p;
}
}
}
}
// if there are no vertices to connect to, choose a point in the middle
if (lowerIndex == (upperIndex + 1) % vertices.Count)
{
Vector2 sp = ((lowerInt + upperInt) / 2);
lowerPoly = Copy(i, upperIndex, vertices);
lowerPoly.Add(sp);
upperPoly = Copy(lowerIndex, i, vertices);
upperPoly.Add(sp);
}
else
{
double highestScore = 0, bestIndex = lowerIndex;
while (upperIndex < lowerIndex) upperIndex += vertices.Count;
for (int j = lowerIndex; j <= upperIndex; ++j)
{
if (CanSee(i, j, vertices))
{
double score = 1 / (SquareDist(At(i, vertices), At(j, vertices)) + 1);
if (Reflex(j, vertices))
{
if (RightOn(At(j - 1, vertices), At(j, vertices), At(i, vertices)) &&
LeftOn(At(j + 1, vertices), At(j, vertices), At(i, vertices)))
{
score += 3;
}
else
{
score += 2;
}
}
else
{
score += 1;
}
if (score > highestScore)
{
bestIndex = j;
highestScore = score;
}
}
}
lowerPoly = Copy(i, (int)bestIndex, vertices);
upperPoly = Copy((int)bestIndex, i, vertices);
}
list.AddRange(ConvexPartition(lowerPoly));
list.AddRange(ConvexPartition(upperPoly));
return list;
}
}
// polygon is already convex
list.Add(vertices);
//The polygons are not guaranteed to be without collinear points. We remove
//them to be sure.
for (int i = 0; i < list.Count; i++)
{
//list[i] = SimplifyTools.CollinearSimplify(list[i], 0);
}
//Remove empty vertice collections
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i].Count == 0)
list.RemoveAt(i);
}
return list;
}
private static bool CanSee(int i, int j, List<Vector2> vertices)
{
if (Reflex(i, vertices))
{
if (LeftOn(At(i, vertices), At(i - 1, vertices), At(j, vertices)) &&
RightOn(At(i, vertices), At(i + 1, vertices), At(j, vertices)))
return false;
}
else
{
if (RightOn(At(i, vertices), At(i + 1, vertices), At(j, vertices)) ||
LeftOn(At(i, vertices), At(i - 1, vertices), At(j, vertices)))
return false;
}
if (Reflex(j, vertices))
{
if (LeftOn(At(j, vertices), At(j - 1, vertices), At(i, vertices)) &&
RightOn(At(j, vertices), At(j + 1, vertices), At(i, vertices)))
return false;
}
else
{
if (RightOn(At(j, vertices), At(j + 1, vertices), At(i, vertices)) ||
LeftOn(At(j, vertices), At(j - 1, vertices), At(i, vertices)))
return false;
}
for (int k = 0; k < vertices.Count; ++k)
{
if ((k + 1) % vertices.Count == i || k == i || (k + 1) % vertices.Count == j || k == j)
{
continue; // ignore incident edges
}
Vector2 intersectionPoint;
if (LineIntersect2(At(i, vertices), At(j, vertices), At(k, vertices), At(k + 1, vertices), out intersectionPoint))
{
return false;
}
}
return true;
}
// precondition: ccw
private static bool Reflex(int i, List<Vector2> vertices)
{
return Right(i, vertices);
}
private static bool Right(int i, List<Vector2> vertices)
{
return Right(At(i - 1, vertices), At(i, vertices), At(i + 1, vertices));
}
private static bool Left(Vector2 a, Vector2 b, Vector2 c)
{
return Area(ref a, ref b, ref c) > 0;
}
private static bool LeftOn(Vector2 a, Vector2 b, Vector2 c)
{
return Area(ref a, ref b, ref c) >= 0;
}
private static bool Right(Vector2 a, Vector2 b, Vector2 c)
{
return Area(ref a, ref b, ref c) < 0;
}
private static bool RightOn(Vector2 a, Vector2 b, Vector2 c)
{
return Area(ref a, ref b, ref c) <= 0;
}
private static float SquareDist(Vector2 a, Vector2 b)
{
float dx = b.x - a.x;
float dy = b.y - a.y;
return dx * dx + dy * dy;
}
//forces counter clock wise order.
private static void ForceCounterClockWise(List<Vector2> vertices)
{
if (!IsCounterClockWise(vertices))
{
vertices.Reverse();
}
}
private static bool IsCounterClockWise(List<Vector2> vertices)
{
//We just return true for lines
if (vertices.Count < 3)
return true;
return (GetSignedArea(vertices) > 0.0f);
}
//gets the signed area.
private static float GetSignedArea(List<Vector2> vertices)
{
int i;
float area = 0;
for (i = 0; i < vertices.Count; i++)
{
int j = (i + 1) % vertices.Count;
area += vertices[i].x * vertices[j].y;
area -= vertices[i].y * vertices[j].x;
}
area /= 2.0f;
return area;
}
//From Mark Bayazit's convex decomposition algorithm
private static Vector2 LineIntersect(Vector2 p1, Vector2 p2, Vector2 q1, Vector2 q2)
{
Vector2 i = Vector2.zero;
float a1 = p2.y - p1.y;
float b1 = p1.x - p2.x;
float c1 = a1 * p1.x + b1 * p1.y;
float a2 = q2.y - q1.y;
float b2 = q1.x - q2.x;
float c2 = a2 * q1.x + b2 * q1.y;
float det = a1 * b2 - a2 * b1;
if (!FloatEquals(det, 0))
{
// lines are not parallel
i.x = (b2 * c1 - b1 * c2) / det;
i.y = (a1 * c2 - a2 * c1) / det;
}
return i;
}
//from Eric Jordan's convex decomposition library, it checks if the lines a0->a1 and b0->b1 cross.
//if they do, intersectionPoint will be filled with the point of crossing. Grazing lines should not return true.
private static bool LineIntersect2(Vector2 a0, Vector2 a1, Vector2 b0, Vector2 b1, out Vector2 intersectionPoint)
{
intersectionPoint = Vector2.zero;
if (a0 == b0 || a0 == b1 || a1 == b0 || a1 == b1)
return false;
float x1 = a0.x;
float y1 = a0.y;
float x2 = a1.x;
float y2 = a1.y;
float x3 = b0.x;
float y3 = b0.y;
float x4 = b1.x;
float y4 = b1.y;
//AABB early exit
if (Math.Max(x1, x2) < Math.Min(x3, x4) || Math.Max(x3, x4) < Math.Min(x1, x2))
return false;
if (Math.Max(y1, y2) < Math.Min(y3, y4) || Math.Max(y3, y4) < Math.Min(y1, y2))
return false;
float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3));
float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3));
float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (Math.Abs(denom) < Mathf.Epsilon)
{
//Lines are too close to parallel to call
return false;
}
ua /= denom;
ub /= denom;
if ((0 < ua) && (ua < 1) && (0 < ub) && (ub < 1))
{
intersectionPoint.x = (x1 + ua * (x2 - x1));
intersectionPoint.y = (y1 + ua * (y2 - y1));
return true;
}
return false;
}
private static bool FloatEquals(float value1, float value2)
{
return Math.Abs(value1 - value2) <= Mathf.Epsilon;
}
//returns a positive number if c is to the left of the line going from a to b. Positive number if point is left, negative if point is right, and 0 if points are collinear.</returns>
private static float Area(ref Vector2 a, ref Vector2 b, ref Vector2 c)
{
return a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y);
}
}
/// <summary>
/// Provides a set of tools to simplify polygons in various ways.
/// </summary>
public static class SimplifyTools
{
/// <summary>
/// Ramer-Douglas-Peucker polygon simplification algorithm. This is the general recursive version that does not use the
/// speed-up technique by using the Melkman convex hull.
///
/// If you pass in 0, it will remove all collinear points.
/// </summary>
/// <returns>The simplified polygon</returns>
public static Vertices DouglasPeuckerSimplify(Vertices vertices, float distanceTolerance)
{
if (vertices.Count <= 3)
return vertices;
bool[] usePoint = new bool[vertices.Count];
for (int i = 0; i < vertices.Count; i++)
usePoint[i] = true;
SimplifySection(vertices, 0, vertices.Count - 1, usePoint, distanceTolerance);
Vertices simplified = new Vertices(vertices.Count);
for (int i = 0; i < vertices.Count; i++)
{
if (usePoint[i])
simplified.Add(vertices[i]);
}
return simplified;
}
private static void SimplifySection(Vertices vertices, int i, int j, bool[] usePoint, float distanceTolerance)
{
if ((i + 1) == j)
return;
Vector2 a = vertices[i];
Vector2 b = vertices[j];
double maxDistance = -1.0;
int maxIndex = i;
for (int k = i + 1; k < j; k++)
{
Vector2 point = vertices[k];
double distance = LineTools.DistanceBetweenPointAndLineSegment(ref point, ref a, ref b);
if (distance > maxDistance)
{
maxDistance = distance;
maxIndex = k;
}
}
if (maxDistance <= distanceTolerance)
{
for (int k = i + 1; k < j; k++)
{
usePoint[k] = false;
}
}
else
{
SimplifySection(vertices, i, maxIndex, usePoint, distanceTolerance);
SimplifySection(vertices, maxIndex, j, usePoint, distanceTolerance);
}
}
private static float Cross(ref Vector2 a, ref Vector2 b)
{
return a.x * b.y - a.y * b.x;
}
/// <summary>
/// Merges all parallel edges in the list of vertices
/// </summary>
/// <param name="vertices">The vertices.</param>
/// <param name="tolerance">The tolerance.</param>
public static Vertices MergeParallelEdges(Vertices vertices, float tolerance)
{
//From Eric Jordan's convex decomposition library
if (vertices.Count <= 3)
return vertices; //Can't do anything useful here to a triangle
bool[] mergeMe = new bool[vertices.Count];
int newNVertices = vertices.Count;
//Gather points to process
for (int i = 0; i < vertices.Count; ++i)
{
int lower = (i == 0) ? (vertices.Count - 1) : (i - 1);
int middle = i;
int upper = (i == vertices.Count - 1) ? (0) : (i + 1);
float dx0 = vertices[middle].x - vertices[lower].x;
float dy0 = vertices[middle].y - vertices[lower].y;
float dx1 = vertices[upper].y - vertices[middle].x;
float dy1 = vertices[upper].y - vertices[middle].y;
float norm0 = Mathf.Sqrt(dx0 * dx0 + dy0 * dy0);
float norm1 = Mathf.Sqrt(dx1 * dx1 + dy1 * dy1);
if (!(norm0 > 0.0f && norm1 > 0.0f) && newNVertices > 3)
{
//Merge identical points
mergeMe[i] = true;
--newNVertices;
}
dx0 /= norm0;
dy0 /= norm0;
dx1 /= norm1;
dy1 /= norm1;
float cross = dx0 * dy1 - dx1 * dy0;
float dot = dx0 * dx1 + dy0 * dy1;
if (Math.Abs(cross) < tolerance && dot > 0 && newNVertices > 3)
{
mergeMe[i] = true;
--newNVertices;
}
else
mergeMe[i] = false;
}
if (newNVertices == vertices.Count || newNVertices == 0)
return vertices;
int currIndex = 0;
//Copy the vertices to a new list and clear the old
Vertices newVertices = new Vertices(newNVertices);
for (int i = 0; i < vertices.Count; ++i)
{
if (mergeMe[i] || newNVertices == 0 || currIndex == newNVertices)
continue;
System.Diagnostics.Debug.Assert(currIndex < newNVertices);
newVertices.Add(vertices[i]);
++currIndex;
}
return newVertices;
}
/// <summary>
/// Merges the identical points in the polygon.
/// </summary>
/// <param name="vertices">The vertices.</param>
public static Vertices MergeIdenticalPoints(Vertices vertices)
{
HashSet<Vector2> unique = new HashSet<Vector2>();
foreach (Vector2 vertex in vertices)
{
unique.Add(vertex);
}
return new Vertices(unique);
}
/// <summary>
/// Reduces the polygon by distance.
/// </summary>
/// <param name="vertices">The vertices.</param>
/// <param name="distance">The distance between points. Points closer than this will be removed.</param>
public static Vertices ReduceByDistance(Vertices vertices, float distance)
{
if (vertices.Count <= 3)
return vertices;
float distance2 = distance * distance;
Vertices simplified = new Vertices(vertices.Count);
for (int i = 0; i < vertices.Count; i++)
{
Vector2 current = vertices[i];
Vector2 next = vertices.NextVertex(i);
//If they are closer than the distance, continue
if ((next - current).sqrMagnitude <= distance2)
continue;
simplified.Add(current);
}
return simplified;
}
/// <summary>
/// Reduces the polygon by removing the Nth vertex in the vertices list.
/// </summary>
/// <param name="vertices">The vertices.</param>
/// <param name="nth">The Nth point to remove. Example: 5.</param>
/// <returns></returns>
public static Vertices ReduceByNth(Vertices vertices, int nth)
{
if (vertices.Count <= 3)
return vertices;
if (nth == 0)
return vertices;
Vertices simplified = new Vertices(vertices.Count);
for (int i = 0; i < vertices.Count; i++)
{
if (i % nth == 0)
continue;
simplified.Add(vertices[i]);
}
return simplified;
}
/// <summary>
/// Simplify the polygon by removing all points that in pairs of 3 have an area less than the tolerance.
///
/// Pass in 0 as tolerance, and it will only remove collinear points.
/// </summary>
/// <param name="vertices"></param>
/// <param name="areaTolerance"></param>
/// <returns></returns>
public static Vertices ReduceByArea(Vertices vertices, float areaTolerance)
{
//From physics2d.net
if (vertices.Count <= 3)
return vertices;
if (areaTolerance < 0)
throw new ArgumentOutOfRangeException("areaTolerance", "must be equal to or greater than zero.");
Vertices simplified = new Vertices(vertices.Count);
Vector2 v3;
Vector2 v1 = vertices[vertices.Count - 2];
Vector2 v2 = vertices[vertices.Count - 1];
areaTolerance *= 2;
for (int i = 0; i < vertices.Count; ++i, v2 = v3)
{
v3 = i == vertices.Count - 1 ? simplified[0] : vertices[i];
float old1 = Cross(ref v1, ref v2);
float old2 = Cross(ref v2, ref v3);
float new1 = Cross(ref v1, ref v3);
if (Math.Abs(new1 - (old1 + old2)) > areaTolerance)
{
simplified.Add(v2);
v1 = v2;
}
}
return simplified;
}
}
}