|
@@ -61,7 +61,7 @@ public class EllipseHelper
|
|
|
/// Splits the ellipse into a bunch of horizontal lines.
|
|
|
/// The resulting list contains consecutive pairs of <see cref="VecI"/>s, each pair has one for the start of the line and one for the end.
|
|
|
/// </summary>
|
|
|
- public static List<VecI> SplitEllipseIntoLines(List<VecI> ellipse)
|
|
|
+ public static List<VecI> SplitEllipseIntoLines(HashSet<VecI> ellipse)
|
|
|
{
|
|
|
List<VecI> lines = new();
|
|
|
var sorted = ellipse.OrderBy(
|
|
@@ -97,7 +97,7 @@ public class EllipseHelper
|
|
|
return lines;
|
|
|
}
|
|
|
|
|
|
- public static List<VecI> GenerateEllipseFromRect(RectI rect, double rotationRad = 0)
|
|
|
+ public static HashSet<VecI> GenerateEllipseFromRect(RectI rect, double rotationRad = 0)
|
|
|
{
|
|
|
if (rect.IsZeroOrNegativeArea)
|
|
|
return new();
|
|
@@ -120,14 +120,14 @@ public class EllipseHelper
|
|
|
/// Center is at (2; 2). It's a place where 4 pixels meet
|
|
|
/// Both radii are 1.5. Making them 2 would make the ellipse touch the edges of pixels, whereas we want it to stay in the middle
|
|
|
/// </summary>
|
|
|
- public static List<VecI> GenerateMidpointEllipse(
|
|
|
+ public static HashSet<VecI> GenerateMidpointEllipse(
|
|
|
double halfWidth,
|
|
|
double halfHeight,
|
|
|
double centerX,
|
|
|
double centerY,
|
|
|
- List<VecI>? listToFill = null)
|
|
|
+ HashSet<VecI>? listToFill = null)
|
|
|
{
|
|
|
- listToFill ??= new List<VecI>();
|
|
|
+ listToFill ??= new HashSet<VecI>();
|
|
|
if (halfWidth < 1 || halfHeight < 1)
|
|
|
{
|
|
|
AddFallbackRectangle(halfWidth, halfHeight, centerX, centerY, listToFill);
|
|
@@ -181,74 +181,118 @@ public class EllipseHelper
|
|
|
return listToFill;
|
|
|
}
|
|
|
|
|
|
- private static List<VecI> GenerateMidpointEllipse(double halfWidth, double halfHeight, double centerX,
|
|
|
+ private static HashSet<VecI> GenerateMidpointEllipse(double halfWidth, double halfHeight, double centerX,
|
|
|
double centerY, double rotationRad)
|
|
|
{
|
|
|
- var listToFill = new List<VecI>();
|
|
|
- if (halfWidth < 1 || halfHeight < 1)
|
|
|
- {
|
|
|
- AddFallbackRectangle(halfWidth, halfHeight, centerX, centerY, listToFill);
|
|
|
- return listToFill;
|
|
|
- }
|
|
|
-
|
|
|
+ var listToFill = new HashSet<VecI>();
|
|
|
+
|
|
|
// formula ((x - h)cos(tetha) + (y - k)sin(tetha))^2 / a^2 + (-(x-h)sin(tetha)+(y-k)cos(tetha))^2 / b^2 = 1
|
|
|
|
|
|
//double topMostTetha = GetTopMostAlpha(halfWidth, halfHeight, rotationRad);
|
|
|
|
|
|
//VecD possiblyTopmostPoint = GetTethaPoint(topMostTetha, halfWidth, halfHeight, rotationRad);
|
|
|
//VecD possiblyMinPoint = GetTethaPoint(topMostTetha + Math.PI, halfWidth, halfHeight, rotationRad);
|
|
|
-
|
|
|
+
|
|
|
// less than, because y grows downwards
|
|
|
//VecD actualTopmost = possiblyTopmostPoint.Y < possiblyMinPoint.Y ? possiblyTopmostPoint : possiblyMinPoint;
|
|
|
+
|
|
|
+ //rotationRad = double.Round(rotationRad, 1);
|
|
|
|
|
|
- double currentTetha = 0;
|
|
|
+ double currentTetha = 0;
|
|
|
+
|
|
|
+ double tethaStep = 0.001;
|
|
|
+
|
|
|
+ VecI[] lastPoints = new VecI[2];
|
|
|
|
|
|
do
|
|
|
{
|
|
|
VecD point = GetTethaPoint(currentTetha, halfWidth, halfHeight, rotationRad);
|
|
|
- listToFill.Add(new VecI((int)Math.Round(point.X + centerX), (int)Math.Round(point.Y + centerY)));
|
|
|
+ VecI floored = new((int)Math.Floor(point.X + centerX), (int)Math.Floor(point.Y + centerY));
|
|
|
+
|
|
|
+ AddPoint(listToFill, floored, lastPoints);
|
|
|
|
|
|
- currentTetha += 0.001;
|
|
|
+ currentTetha += tethaStep;
|
|
|
} while (currentTetha < Math.PI * 2);
|
|
|
|
|
|
return listToFill;
|
|
|
}
|
|
|
-
|
|
|
- private static bool IsInsideEllipse(double x, double y, double centerX, double centerY, double halfWidth, double halfHeight, double rotationRad)
|
|
|
+
|
|
|
+ private static void AddPoint(HashSet<VecI> listToFill, VecI floored, VecI[] lastPoints)
|
|
|
+ {
|
|
|
+ if(!listToFill.Add(floored)) return;
|
|
|
+
|
|
|
+ if (lastPoints[0] == default)
|
|
|
+ {
|
|
|
+ lastPoints[0] = floored;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (lastPoints[1] == default)
|
|
|
+ {
|
|
|
+ lastPoints[1] = floored;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (IsLShape(lastPoints, floored))
|
|
|
+ {
|
|
|
+ listToFill.Remove(lastPoints[1]);
|
|
|
+
|
|
|
+ lastPoints[0] = floored;
|
|
|
+ lastPoints[1] = default;
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ lastPoints[0] = lastPoints[1];
|
|
|
+ lastPoints[1] = floored;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool IsLShape(VecI[] points, VecI third)
|
|
|
+ {
|
|
|
+ VecI first = points[0];
|
|
|
+ VecI second = points[1];
|
|
|
+ return first.X != third.X && first.Y != third.Y && (second - first).TaxicabLength == 1 &&
|
|
|
+ (second - third).TaxicabLength == 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool IsInsideEllipse(double x, double y, double centerX, double centerY, double halfWidth,
|
|
|
+ double halfHeight, double rotationRad)
|
|
|
{
|
|
|
double lhs = Math.Pow(x * Math.Cos(rotationRad) + y * Math.Sin(rotationRad), 2) / Math.Pow(halfWidth, 2);
|
|
|
double rhs = Math.Pow(-x * Math.Sin(rotationRad) + y * Math.Cos(rotationRad), 2) / Math.Pow(halfHeight, 2);
|
|
|
-
|
|
|
+
|
|
|
return lhs + rhs <= 1;
|
|
|
}
|
|
|
-
|
|
|
- private static VecD GetDerivative(double x, double halfWidth, double halfHeight, double rotationRad, double tetha)
|
|
|
+
|
|
|
+ private static VecD GetDerivative(double x, double halfWidth, double halfHeight, double rotationRad, double tetha)
|
|
|
{
|
|
|
- double xDerivative = halfWidth * Math.Cos(tetha) * Math.Cos(rotationRad) - halfHeight * Math.Sin(tetha) * Math.Sin(rotationRad);
|
|
|
- double yDerivative = halfWidth * Math.Cos(tetha) * Math.Sin(rotationRad) + halfHeight * Math.Sin(tetha) * Math.Cos(rotationRad);
|
|
|
-
|
|
|
+ double xDerivative = halfWidth * Math.Cos(tetha) * Math.Cos(rotationRad) -
|
|
|
+ halfHeight * Math.Sin(tetha) * Math.Sin(rotationRad);
|
|
|
+ double yDerivative = halfWidth * Math.Cos(tetha) * Math.Sin(rotationRad) +
|
|
|
+ halfHeight * Math.Sin(tetha) * Math.Cos(rotationRad);
|
|
|
+
|
|
|
return new VecD(xDerivative, yDerivative);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
private static VecD GetTethaPoint(double alpha, double halfWidth, double halfHeight, double rotation)
|
|
|
{
|
|
|
double x =
|
|
|
(halfWidth * Math.Cos(alpha) * Math.Cos(rotation) - halfHeight * Math.Sin(alpha) * Math.Sin(rotation));
|
|
|
double y = halfWidth * Math.Cos(alpha) * Math.Sin(rotation) + halfHeight * Math.Sin(alpha) * Math.Cos(rotation);
|
|
|
-
|
|
|
+
|
|
|
return new VecD(x, y);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
private static double GetTopMostAlpha(double halfWidth, double halfHeight, double rotationRad)
|
|
|
{
|
|
|
- if(rotationRad == 0)
|
|
|
+ if (rotationRad == 0)
|
|
|
return 0;
|
|
|
double tethaRot = Math.Cos(rotationRad) / Math.Sin(rotationRad);
|
|
|
return Math.Atan((halfHeight * tethaRot) / halfWidth);
|
|
|
}
|
|
|
|
|
|
private static void AddFallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY,
|
|
|
- List<VecI> coordinates)
|
|
|
+ HashSet<VecI> coordinates)
|
|
|
{
|
|
|
int left = (int)Math.Floor(centerX - halfWidth);
|
|
|
int top = (int)Math.Floor(centerY - halfHeight);
|
|
@@ -270,7 +314,7 @@ public class EllipseHelper
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static void AddRegionPoints(List<VecI> coordinates, double x, double xc, double y, double yc)
|
|
|
+ private static void AddRegionPoints(HashSet<VecI> coordinates, double x, double xc, double y, double yc)
|
|
|
{
|
|
|
int xFloor = (int)Math.Floor(x);
|
|
|
int yFloor = (int)Math.Floor(y);
|