Browse Source

very basic rotated ellipse is working

flabbet 11 tháng trước cách đây
mục cha
commit
f8d8640c9c

+ 53 - 6
src/ChunkyImageLib/Operations/EllipseHelper.cs

@@ -103,8 +103,7 @@ public class EllipseHelper
             return new();
         float radiusX = (rect.Width - 1) / 2.0f;
         float radiusY = (rect.Height - 1) / 2.0f;
-        //TODO: Implement rotation
-        return GenerateMidpointEllipse(radiusX, radiusY, rect.Center.X, rect.Center.Y);
+        return GenerateMidpointEllipse(radiusX, radiusY, rect.Center.X, rect.Center.Y, rotationRad);
     }
 
     /// <summary>
@@ -182,7 +181,7 @@ public class EllipseHelper
         return listToFill;
     }
 
-    /*private static List<VecI> GenerateMidpointEllipseRotated(double halfWidth, double halfHeight, double centerX,
+    private static List<VecI> GenerateMidpointEllipse(double halfWidth, double halfHeight, double centerX,
         double centerY, double rotationRad)
     {
         var listToFill = new List<VecI>();
@@ -193,12 +192,60 @@ public class EllipseHelper
         }
         
         // 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);
         
-        double cos = Math.Cos(rotationRad);
-        double sin = Math.Sin(rotationRad);
+        // less than, because y grows downwards
+        //VecD actualTopmost = possiblyTopmostPoint.Y < possiblyMinPoint.Y ? possiblyTopmostPoint : possiblyMinPoint;
+
+        double currentTetha = 0; 
+
+        do
+        {
+            VecD point = GetTethaPoint(currentTetha, halfWidth, halfHeight, rotationRad);
+            listToFill.Add(new VecI((int)Math.Round(point.X + centerX), (int)Math.Round(point.Y + centerY)));
+
+            currentTetha += 0.001;
+        } 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)
+    {
+        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)
+    {
+        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)
+            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)

+ 18 - 8
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -40,11 +40,12 @@ internal class EllipseOperation : IMirroredDrawOperation
         if (strokeWidth == 1)
         {
             var ellipseList = EllipseHelper.GenerateEllipseFromRect(location, rotation);
-            ellipse = ellipseList.Select(a => new Point(a)).ToArray();
+
+            ellipse = ellipseList.Select(a => new Point(a)).Distinct().ToArray();
             if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
             {
-                (var fill, ellipseFillRect) = EllipseHelper.SplitEllipseFillIntoRegions(ellipseList, location);
-                ellipseFill = fill.Select(a => new Point(a)).ToArray();
+                /*(var fill, ellipseFillRect) = EllipseHelper.SplitEllipseFillIntoRegions(ellipseList, location);
+                ellipseFill = fill.Select(a => new Point(a)).ToArray();*/
             }
         }
         else
@@ -71,9 +72,9 @@ internal class EllipseOperation : IMirroredDrawOperation
         {
             if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
             {
-                paint.Color = fillColor;
+                /*paint.Color = fillColor;
                 surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
-                surf.Canvas.DrawRect(ellipseFillRect!.Value, paint);
+                surf.Canvas.DrawRect(ellipseFillRect!.Value, paint);*/
             }
             paint.Color = strokeColor;
             surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
@@ -98,14 +99,23 @@ internal class EllipseOperation : IMirroredDrawOperation
 
     public AffectedArea FindAffectedArea(VecI imageSize)
     {
-        var chunks = OperationHelper.FindChunksTouchingEllipse
-            (location.Center, location.Width / 2.0, location.Height / 2.0, ChunkyImage.FullChunkSize);
+        ShapeCorners corners = new((RectD)location);
+        corners = corners.AsRotated(rotation, (VecD)location.Center);
+        RectI bounds = (RectI)corners.AABBBounds;
+        
+        /*VecI shift = new VecI(Math.Max(0, -aabb.X), Math.Max(0, -aabb.Y));
+        aabb = aabb.Offset(shift);*/
+        
+        var chunks = OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
         if (fillColor.A == 0)
         {
+            // TODO: Implement ellipse fill optimization for rotated ellipses
+            /*
             chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
                 (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2, ChunkyImage.FullChunkSize));
+        */
         }
-        return new AffectedArea(chunks, location);
+        return new AffectedArea(chunks, bounds);
     }
 
     public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)

+ 14 - 7
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs

@@ -12,7 +12,7 @@ public class EllipseVectorData : ShapeVectorData
     } 
 
     public override RectD AABB =>
-        new ShapeCorners(Position, Size)
+        new ShapeCorners(Position, Size).AsRotated(RotationRadians, Position)
             .AABBBounds;
     
     public EllipseVectorData(VecD center, VecD radius)
@@ -23,17 +23,24 @@ public class EllipseVectorData : ShapeVectorData
 
     public override void Rasterize(DrawingSurface drawingSurface)
     {
-        var imageSize = (VecI)Size; 
+        var imageSize = (VecI)Size;
+        
+        using ChunkyImage img = new ChunkyImage((VecI)AABB.Size);
+
+        RectD rotated = new ShapeCorners(RectD.FromTwoPoints(VecD.Zero, imageSize))
+            .AsRotated(RotationRadians, imageSize / 2f).AABBBounds;
 
-        using ChunkyImage img = new ChunkyImage(imageSize);
-        RectI rect = new RectI(0, 0, imageSize.X, imageSize.Y); 
+        VecI shift = new VecI(-(int)rotated.Left, -(int)rotated.Top);
+        RectI drawRect = new(shift, imageSize);
         
-        img.EnqueueDrawEllipse(rect, StrokeColor, FillColor, StrokeWidth, RotationRadians);
+        img.EnqueueDrawEllipse(drawRect, StrokeColor, FillColor, StrokeWidth, RotationRadians);
         img.CommitChanges();
+
+        VecI topLeft = new VecI((int)(Position.X - Radius.X), (int)(Position.Y - Radius.Y)) - shift;
         
-        VecI topLeft = new VecI((int)(Position.X - Radius.X), (int)(Position.Y - Radius.Y));
+        RectI region = new(VecI.Zero, (VecI)AABB.Size);
 
-        img.DrawMostUpToDateRegionOn(rect, ChunkResolution.Full, drawingSurface, topLeft);
+        img.DrawMostUpToDateRegionOn(region, ChunkResolution.Full, drawingSurface, topLeft);
     }
 
     public override bool IsValid()