5 using System.Collections.Generic;
13 private const int randomPointCount = 30;
18 private const float largeValue = 10000;
19 private const float smallValue = -largeValue;
23 private const float minimumHeightGain = 0.01f;
25 private float DegreesToRadians(
double deg) {
return (
float)(Math.PI / 180 * deg); }
28 private Vector2 center =
new Vector2(
float.NegativeInfinity,
float.NegativeInfinity);
53 FindInscribedRectangle(edges, randomSeed, out center, out angle, out width, out height);
59 public bool IsRectangleValid
68 public void GetRectangleParams(out Vector2 centerOut, out
float angleOut, out
float widthOut, out
float heightOut)
70 if (!IsRectangleValid)
72 throw new InvalidOperationException();
86 if (!IsRectangleValid)
88 throw new InvalidOperationException();
91 var points =
new Vector2[4];
93 float x = width / 2.0f;
94 float y = height / 2.0f;
95 points =
new Vector2[]
103 for (
int i = 0; i < points.Length; ++i)
105 points[i] = RotatePoint(Vector2.zero, DegreesToRadians(angle), points[i]);
117 if (!IsRectangleValid)
119 throw new InvalidOperationException();
123 point = RotatePoint(Vector2.zero, DegreesToRadians(-angle), point);
124 return (Math.Abs(point.x) <= (width / 2.0f)) && (Math.Abs(point.y) <= height / 2.0f);
130 private Vector2 RotatePoint(Vector2 origin,
float angleRad, Vector2 point)
132 Vector2 retval = point;
133 if (0.0f == angleRad)
139 retval.x -= origin.x;
140 retval.y -= origin.y;
143 float s = (float)Math.Sin(angleRad);
144 float c = (float)Math.Cos(angleRad);
145 float rotatedX = retval.x * c - retval.y * s;
146 float rotatedY = retval.x * s + retval.y * c;
149 retval.x = rotatedX + origin.x;
150 retval.y = rotatedY + origin.y;
159 private bool FindSurroundingCollisionPoints(
161 Vector2 point,
float angleRad, out Vector2 topCollisionPoint,
162 out Vector2 bottomCollisionPoint, out Vector2 leftCollisionPoint,
163 out Vector2 rightCollisionPoint)
177 var topEndpoint =
new Vector2(point.x, largeValue);
178 var bottomEndpoint =
new Vector2(point.x, smallValue);
179 topEndpoint = RotatePoint(point, angleRad, topEndpoint);
180 bottomEndpoint = RotatePoint(point, angleRad, bottomEndpoint);
181 var verticalLine =
new Edge(topEndpoint, bottomEndpoint);
183 var rightEndpoint =
new Vector2(largeValue, point.y);
184 var leftEndpoint =
new Vector2(smallValue, point.y);
185 rightEndpoint = RotatePoint(point, angleRad, rightEndpoint);
186 leftEndpoint = RotatePoint(point, angleRad, leftEndpoint);
187 var horizontalLine =
new Edge(rightEndpoint, leftEndpoint);
190 foreach (var edge
in edges)
196 bool isAbove = RotatePoint(point, -angleRad, verticalIntersection).y > point.y;
201 Vector2.SqrMagnitude(point - verticalIntersection) < Vector2.SqrMagnitude(point - topCollisionPoint))
203 topCollisionPoint = verticalIntersection;
209 Vector2.SqrMagnitude(point - verticalIntersection) < Vector2.SqrMagnitude(point - bottomCollisionPoint))
211 bottomCollisionPoint = verticalIntersection;
220 bool isLeft = RotatePoint(point, -angleRad, horizontalIntersection).x < point.x;
225 Vector2.SqrMagnitude(point - horizontalIntersection) < Vector2.SqrMagnitude(point - leftCollisionPoint))
227 leftCollisionPoint = horizontalIntersection;
234 Vector2.SqrMagnitude(point - horizontalIntersection) < Vector2.SqrMagnitude(point - rightCollisionPoint))
236 rightCollisionPoint = horizontalIntersection;
258 private bool TryFitMaximumRectangleAtAngle(
260 Vector2 center,
float angleRad,
float minArea,
261 out
float aspectRatio, out
float width, out
float height)
267 float[] aspectRatios = {1, 1.5f, 2, 2.5f, 3, 3.5f, 4, 4.5f, 5, 5.5f, 6, 6.5f, 7, 7.5f,
268 8, 8.5f, 9, 9.5f, 10, 10.5f, 11, 11.5f, 12, 12.5f, 13, 13.5f, 14, 14.5f, 15};
272 Vector2 topCollisionPoint;
273 Vector2 bottomCollisionPoint;
274 Vector2 leftCollisionPoint;
275 Vector2 rightCollisionPoint;
276 if (!FindSurroundingCollisionPoints(edges, center, angleRad,
277 out topCollisionPoint, out bottomCollisionPoint, out leftCollisionPoint, out rightCollisionPoint))
282 float verticalMinDistanceToEdge =
283 Math.Min(Vector2.Distance(center, topCollisionPoint), Vector2.Distance(center, bottomCollisionPoint));
284 float horizontalMinDistanceToEdge =
285 Math.Min(Vector2.Distance(center, leftCollisionPoint), Vector2.Distance(center, rightCollisionPoint));
288 float maxWidth = Math.Max(verticalMinDistanceToEdge, horizontalMinDistanceToEdge) * 2.0f;
289 float maxHeight = Math.Min(verticalMinDistanceToEdge, horizontalMinDistanceToEdge) * 2.0f;
292 foreach (var candidateAspectRatio
in aspectRatios)
295 float searchHeightUpperBound = Math.Max(maxHeight, maxWidth / candidateAspectRatio);
299 float searchHeightLowerBound = (float)Math.Sqrt(Math.Max((width * height), minArea) / candidateAspectRatio);
302 if (searchHeightLowerBound > searchHeightUpperBound || searchHeightLowerBound * candidateAspectRatio > maxWidth)
307 float currentTestingHeight = Math.Max(searchHeightLowerBound, maxHeight / 2);
312 if (WillRectangleFit(edges, center, angleRad, candidateAspectRatio * currentTestingHeight, currentTestingHeight))
317 searchHeightLowerBound = currentTestingHeight;
319 width = currentTestingHeight * candidateAspectRatio;
320 height = currentTestingHeight;
321 aspectRatio = candidateAspectRatio;
322 currentTestingHeight = (searchHeightUpperBound + currentTestingHeight) / 2;
327 searchHeightUpperBound = currentTestingHeight;
328 currentTestingHeight = (currentTestingHeight + searchHeightLowerBound) / 2;
331 while ((searchHeightUpperBound - searchHeightLowerBound) > minimumHeightGain);
334 return aspectRatio > 0;
341 private bool WillRectangleFit(
Edge[] edges, Vector2 center,
float angleRad,
float width,
float height)
343 float halfWidth = width / 2;
344 float halfHeight = height / 2;
346 var topLeft =
new Vector2(center.x - halfWidth, center.y + halfHeight);
347 var topRight =
new Vector2(center.x + halfWidth, center.y + halfHeight);
348 var bottomLeft =
new Vector2(center.x - halfWidth, center.y - halfHeight);
349 var bottomRight =
new Vector2(center.x + halfWidth, center.y - halfHeight);
351 topLeft = RotatePoint(center, angleRad, topLeft);
352 topRight = RotatePoint(center, angleRad, topRight);
353 bottomLeft = RotatePoint(center, angleRad, bottomLeft);
354 bottomRight = RotatePoint(center, angleRad, bottomRight);
356 var top =
new Edge(topLeft, topRight);
357 var right =
new Edge(topRight, bottomRight);
358 var bottom =
new Edge(bottomLeft, bottomRight);
359 var left =
new Edge(topLeft, bottomLeft);
362 foreach (var edge
in edges)
385 private void FindInscribedRectangle(
Edge[] edges,
int randomSeed, out Vector2 center, out
float angle, out
float width, out
float height)
388 angle = width = height = 0;
393 float minX = largeValue;
394 float minY = largeValue;
395 float maxX = smallValue;
396 float maxY = smallValue;
398 foreach (var edge
in edges)
400 if (edge.Ax < minX || edge.Bx < minX)
402 minX = Math.Min(edge.Ax, edge.Bx);
404 if (edge.Ay < minY || edge.By < minY)
406 minY = Math.Min(edge.Ay, edge.By);
408 if (edge.Ax > maxX || edge.Bx > maxX)
410 maxX = Math.Max(edge.Ax, edge.Bx);
412 if (edge.Ay > maxY || edge.By > maxY)
414 maxY = Math.Max(edge.Ay, edge.By);
419 Vector2[] startingPoints =
new Vector2[randomPointCount];
421 var random =
new System.Random(randomSeed);
423 for (
int pointIndex = 0; pointIndex < randomPointCount; ++pointIndex)
425 Vector2 candidatePoint;
429 candidatePoint.x = ((float)random.NextDouble() * (maxX - minX)) + minX;
430 candidatePoint.y = ((float)random.NextDouble() * (maxY - minY)) + minY;
434 startingPoints[pointIndex] = candidatePoint;
438 float[] angles = { 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165 };
440 foreach (var candidateAngle
in angles)
443 foreach (var startingPoint
in startingPoints)
448 Vector2 topCollisionPoint;
449 Vector2 bottomCollisionPoint;
450 Vector2 leftCollisionPoint;
451 Vector2 rightCollisionPoint;
452 FindSurroundingCollisionPoints(edges, startingPoint, DegreesToRadians(candidateAngle),
453 out topCollisionPoint, out bottomCollisionPoint, out leftCollisionPoint, out rightCollisionPoint);
458 var verticalMidpoint =
new Vector2((topCollisionPoint.x + bottomCollisionPoint.x) / 2,
459 (topCollisionPoint.y + bottomCollisionPoint.y) / 2);
463 if (TryFitMaximumRectangleAtAngle(edges, verticalMidpoint, DegreesToRadians(candidateAngle), width * height, out aspectRatio, out w, out h))
465 center = verticalMidpoint;
466 angle = candidateAngle;
474 var horizontalMidpoint =
new Vector2((leftCollisionPoint.x + rightCollisionPoint.x) / 2,
475 (leftCollisionPoint.y + rightCollisionPoint.y) / 2);
479 if (TryFitMaximumRectangleAtAngle(edges, horizontalMidpoint, DegreesToRadians(candidateAngle), width * height, out aspectRatio, out w, out h))
481 center = horizontalMidpoint;
482 angle = candidateAngle;