void OnDrawGizmos()
{
Bounds boundingBox = CalculateBoundingBox(originalPolygonVertices);
DrawBoundingBox(boundingBox);
DrawPolygon(originalPolygonVertices, Color.green);
DrawPolygon(OrthPolygon(originalPolygonVertices, maxAngleChange, skewTolerance), Color.magenta);
// Debug.LogError(.Count);
// Handle the returned values as needed
/*Debug.Log("Original Angles: " + string.Join(", ", orgAngles));
Debug.Log("Correction Angles: " + string.Join(", ", corAngles));
Debug.Log("Directional Angles: " + string.Join(", ", dirAngles));*/
}
public List<Vector2> OrthPolygon(List<Vector2> polygon, float maxAngleChange = 15f, float skewTolerance = 15f)
{
List<Vector2> poly = new List<Vector2>();
List<float> orgAngles;
List<float> corAngles;
List<int> dirAngles;
(orgAngles, corAngles, dirAngles) = CalculateSegmentAngles(originalPolygonVertices);
float medAngle = 0;
if (StandardDeviation(corAngles) < 30)
{
medAngle = Median(corAngles);
}
else
{
medAngle = 45;
}
List<Vector2> polySimpleR = RotatePolygon(polygon, medAngle);
(orgAngles, corAngles, dirAngles) = CalculateSegmentAngles(polySimpleR, maxAngleChange);
List<float> rotatedX = new List<float>();
List<float> rotatedY = new List<float>();
foreach (Vector2 vertex in polySimpleR)
{
rotatedX.Add(vertex.x);
rotatedY.Add(vertex.y);
}
// Scan backwards to check if starting segment is a continuation of straight region in the same direction
int shift = 0;
for (int i = 1; i < dirAngles.Count; i++)
{
if (dirAngles[0] == dirAngles[^i])
{
shift = i;
}
else
{
break;
}
}
// If the first segment is part of continuing straight region then reset the index to its beginning
if (shift != 0)
{
dirAngles = dirAngles.GetRange(dirAngles.Count - shift, shift)
.Concat(dirAngles.GetRange(0, dirAngles.Count - shift)).ToList();
orgAngles = orgAngles.GetRange(orgAngles.Count - shift, shift)
.Concat(orgAngles.GetRange(0, orgAngles.Count - shift)).ToList();
rotatedX = rotatedX.GetRange(rotatedX.Count - shift - 1, shift)
.Concat(rotatedX.GetRange(0, rotatedX.Count - shift)).ToList();
rotatedY = rotatedY.GetRange(rotatedY.Count - shift - 1, shift)
.Concat(rotatedY.GetRange(0, rotatedY.Count - shift)).ToList();
}
// Fix 180 degree turns (N->S, S->N, E->W, W->E)
// Subtract two adjacent directions and if the difference is 2, which means we have 180˚ turn (0,1,3 are OK) then use the direction of the previous segment
List<int> dirAngleRoll = dirAngles.GetRange(1, dirAngles.Count - 1)
.Concat(dirAngles.GetRange(0, 1)).ToList();
dirAngles = dirAngles.Select((value, index) => MathF.Abs(value - dirAngleRoll[index]) == 2 ? dirAngles[index - 1] : value).ToList();
// Assuming dirAngle, orgAngle, rotatedX, rotatedY, skewTolerance, and shift are defined elsewhere in the code
dirAngles.Add(dirAngles[0]); // Append dummy value
orgAngles.Add(orgAngles[0]); // Append dummy value
List<int> segmentBuffer = new List<int>(); // Buffer for determining which segments are part of one large straight line
for (int i = 0; i < dirAngles.Count - 1; i++)
{
// Preserving skewed walls: Leave walls that are obviously meant to be skewed 45˚+/- tolerance˚ (e.g.angle 30-60 degrees) off main walls as untouched
if (orgAngles[i] % 90 > (45 - skewTolerance) && orgAngles[i] % 90 < (45 + skewTolerance))
{ continue;
}
// Dealing with adjacent segments following the same direction
segmentBuffer.Add(i);
if (dirAngles[i] ==
dirAngles
[i + 1]) // If next segment is of same orientation, we need 180 deg angle for straight line. Keep checking.
{
if (orgAngles[i + 1] % 90 > (45 - skewTolerance) && orgAngles[i + 1] % 90 < (45 + skewTolerance))
{
continue;
}
}
if (dirAngles[i] == 0 || dirAngles[i] == 2) // for N,S segments average x coordinate
{
float tempX = rotatedX.GetRange(segmentBuffer[0], segmentBuffer.Count).Average();
// Update with new coordinates
for (int j = segmentBuffer[0]; j <= segmentBuffer[segmentBuffer.Count - 1]; j++)
{
rotatedX[j] = tempX;
}
}
else if (dirAngles[i] == 1 || dirAngles[i] == 3) // for E,W segments average y coordinate
{
float tempY = rotatedY.GetRange(segmentBuffer[0], segmentBuffer.Count).Average();
// Update with new coordinates
for (int j = segmentBuffer[0]; j <= segmentBuffer[segmentBuffer.Count - 1]; j++)
{
rotatedY[j] = tempY;
}
}
if (segmentBuffer.Contains(0)) // Copy change in first point to its last point so we don't lose it during Reverse shift
{
rotatedX[rotatedX.Count - 1] = rotatedX[0];
rotatedY[rotatedY.Count - 1] = rotatedY[0];
}
segmentBuffer.Clear();
}
for (int i = 0; i < rotatedX.Count; i++)
{
poly.Add(new Vector2(rotatedX[i], rotatedY[i]));
}
return RotatePolygon(poly, -medAngle);
}
/** :Returns:
- orgAngle: Segments bearing
- corAngle: Segments angles to closest cardinal direction
- dirAngle: Segments direction [N, E, S, W] as [0, 1, 2, 3]*/
public ( List<float>, List<float>, List<int>) CalculateSegmentAngles(List<Vector2> polySimple,
float maxAngleChange = 45f)
{
if (polySimple == null || polySimple.Count < 2)
{
Debug.LogError("Polygon should contain at least two points.");
return (null, null, null);
}
List<float> orgAngle = new List<float>();
List<float> corAngle = new List<float>();
List<int> dirAngle = new List<int>();
float[] limit = new float[4];
maxAngleChange = 45 - maxAngleChange;
for (int i = 0; i < 4; i++)
{
limit[i] = 0;
}
// Calculate angles between consecutive segments
for (int i = 0; i < polySimple.Count; i++)
{
Vector2 point1 = polySimple[i];
Vector2 point2 = polySimple[(i + 1) % polySimple.Count];
float angle = CalculateInitialCompassBearing(point1, point2);
if (angle > 45 + limit[1] && angle <= 135 - limit[1])
{
orgAngle.Add(angle);
corAngle.Add(angle - 90);
dirAngle.Add(1);
}
else if (angle > 135 + limit[2] && angle <= 225 - limit[2])
{
orgAngle.Add(angle);
corAngle.Add(angle - 180);
dirAngle.Add(2);
}
else if (angle > (225 + limit[3]) && angle <= (315 - limit[3]))
{
orgAngle.Add(angle);
corAngle.Add(angle - 270);
dirAngle.Add(3);
}
else if (angle > (315 + limit[0]) && angle <= 360)
{
orgAngle.Add(angle);
corAngle.Add(angle - 360);
dirAngle.Add(0);
}
else if (angle >= 0 && angle <= (45 - limit[0]))
{
orgAngle.Add(angle);
corAngle.Add(angle);
dirAngle.Add(0);
}
for (int b = 0; b < 4; b++)
{
limit[b] = 0;
}
limit[dirAngle[i]] = maxAngleChange;
limit[(dirAngle[i] + 1) % 4] = -maxAngleChange;
limit[(dirAngle[i] - 1 + 4) % 4] = -maxAngleChange;
}
return (orgAngle, corAngle, dirAngle);
}
public List<Vector2> RotatePolygon(List<Vector2> polygon, float angle)
{
Vector2 centroid = CalculateCentroid(polygon);
List<Vector2> rotatedPolygon = new List<Vector2>();
foreach (Vector2 vertex in polygon)
{
Vector2 rotatedVertex = RotatePoint(vertex, centroid, angle);
rotatedPolygon.Add(rotatedVertex);
}
return rotatedPolygon;
}
private Vector2 RotatePoint(Vector2 point, Vector2 pivot, float angle)
{
float angleInRadians = angle * Mathf.Deg2Rad;
float cosTheta = Mathf.Cos(angleInRadians);
float sinTheta = Mathf.Sin(angleInRadians);
float newX = cosTheta * (point.x - pivot.x) - sinTheta * (point.y - pivot.y) + pivot.x;
float newY = sinTheta * (point.x - pivot.x) + cosTheta * (point.y - pivot.y) + pivot.y;
return new Vector2(newX, newY);
}
private Vector2 CalculateCentroid(List<Vector2> polygon)
{
Vector2 centroid = Vector2.zero;
foreach (Vector2 vertex in polygon)
{
centroid += vertex;
}
centroid /= polygon.Count;
return centroid;
}
public float CalculateInitialCompassBearing(Vector2 pointA, Vector2 pointB)
{
// Check if the inputs are vectors
if (!(pointA is Vector2) || !(pointB is Vector2))
{
throw new ArgumentException("Only vectors are supported as arguments");
}
// Convert latitude and longitude from degrees to radians
float lat1 = Mathf.Deg2Rad * pointA.x;
float lat2 = Mathf.Deg2Rad * pointB.x;
float diffLong = Mathf.Deg2Rad * (pointB.y - pointA.y);
float x = Mathf.Sin(diffLong) * Mathf.Cos(lat2);
float y = Mathf.Cos(lat1) * Mathf.Sin(lat2) - (Mathf.Sin(lat1) * Mathf.Cos(lat2) * Mathf.Cos(diffLong));
// Calculate the initial bearing
float initialBearing = Mathf.Atan2(x, y);
// Normalize the initial bearing to be between 0 and 360 degrees
float compassBearing = (Mathf.Rad2Deg * initialBearing + 360) % 360;
return compassBearing;
}
void DrawBoundingBox(Bounds boundingBox)
{
Vector3 min = boundingBox.min;
Vector3 max = boundingBox.max;
Vector3[] corners = new Vector3[]
{
new Vector3(min.x, 0, min.y),
new Vector3(min.x, 0, max.y),
new Vector3(max.x, 0, max.y),
new Vector3(max.x, 0, min.y),
new Vector3(min.x, 0, min.y)
};
for (int i = 0; i < corners.Length - 1; i++)
{
Gizmos.color = new Color(1f, 0.69f, 0.16f);
Gizmos.DrawLine(corners[i], corners[i + 1]);
}
}
void DrawPolygon(List<Vector2> vertices, Color color)
{
for (int i = 0; i < vertices.Count; i++)
{
Vector2 start = vertices[i];
Vector2 end = vertices[(i + 1) % vertices.Count];
Gizmos.color = color;
Gizmos.DrawLine(new Vector3(start.x, 0, start.y), new Vector3(end.x, 0, end.y));
Debug.LogError(start);
}
}
Bounds CalculateBoundingBox(List<Vector2> vertices)
{
Vector2 min = vertices[0];
Vector2 max = vertices[0];
foreach (Vector2 vertex in vertices)
{
min = Vector2.Min(min, vertex);
max = Vector2.Max(max, vertex);
}
return new Bounds((min + max) / 2, max - min);
}
public static float Median(IEnumerable<float> source)
{
var sorted = source.OrderBy(x => x).ToList();
var count = sorted.Count;
if (count % 2 == 0)
{
return (sorted[count / 2 - 1] + sorted[count / 2]) / 2;
}
else
{
return sorted[count / 2];
}
}
public static float StandardDeviation(IEnumerable<float> source)
{
var values = source.ToList();
var count = values.Count;
var mean = values.Average();
var sumOfSquares = values.Sum(x => Mathf.Pow(x - mean, 2));
return Mathf.Sqrt(sumOfSquares / count);
}