Browse Source

Move to using vectors

Equbuxu 3 years ago
parent
commit
b26c73a75d

+ 4 - 2
src/ChangeableDocument/ChangeInfos/LayerImageChunks_ChangeInfo.cs

@@ -1,8 +1,10 @@
-namespace ChangeableDocument.ChangeInfos
+using ChunkyImageLib.DataHolders;
+
+namespace ChangeableDocument.ChangeInfos
 {
     public record struct LayerImageChunks_ChangeInfo : IChangeInfo
     {
         public Guid LayerGuid { get; init; }
-        public HashSet<(int, int)>? Chunks { get; init; }
+        public HashSet<Vector2i>? Chunks { get; init; }
     }
 }

+ 4 - 2
src/ChunkyImageLib/ChunkPool.cs

@@ -1,9 +1,11 @@
-namespace ChunkyImageLib
+using ChunkyImageLib.DataHolders;
+
+namespace ChunkyImageLib
 {
     internal class ChunkPool
     {
         public const int ChunkSize = 32;
-
+        public static Vector2i ChunkSizeVec => new(ChunkSize, ChunkSize);
         // not thread-safe!
         private static ChunkPool? instance;
         public static ChunkPool Instance => instance ??= new ChunkPool();

+ 13 - 11
src/ChunkyImageLib/ChunkStorage.cs

@@ -1,22 +1,24 @@
-namespace ChunkyImageLib
+using ChunkyImageLib.DataHolders;
+
+namespace ChunkyImageLib
 {
     public class ChunkStorage : IDisposable
     {
         private bool disposed = false;
-        private List<(int, int, Chunk?)> savedChunks = new();
-        public ChunkStorage(ChunkyImage image, HashSet<(int, int)> commitedChunksToSave)
+        private List<(Vector2i, Chunk?)> savedChunks = new();
+        public ChunkStorage(ChunkyImage image, HashSet<Vector2i> commitedChunksToSave)
         {
-            foreach (var (x, y) in commitedChunksToSave)
+            foreach (var chunkPos in commitedChunksToSave)
             {
-                Chunk? chunk = image.GetCommitedChunk(x, y);
+                Chunk? chunk = image.GetCommitedChunk(chunkPos);
                 if (chunk == null)
                 {
-                    savedChunks.Add((x, y, null));
+                    savedChunks.Add((chunkPos, null));
                     continue;
                 }
                 Chunk copy = ChunkPool.Instance.BorrowChunk();
                 chunk.Surface.CopyTo(copy.Surface);
-                savedChunks.Add((x, y, copy));
+                savedChunks.Add((chunkPos, copy));
             }
         }
 
@@ -24,12 +26,12 @@
         {
             if (disposed)
                 throw new Exception("This instance has been disposed");
-            foreach (var (x, y, chunk) in savedChunks)
+            foreach (var (pos, chunk) in savedChunks)
             {
                 if (chunk == null)
-                    image.DrawImage(x * ChunkPool.ChunkSize, y * ChunkPool.ChunkSize, ChunkPool.Instance.TransparentChunk.Surface);
+                    image.DrawImage(pos * ChunkPool.ChunkSize, ChunkPool.Instance.TransparentChunk.Surface);
                 else
-                    image.DrawImage(x * ChunkPool.ChunkSize, y * ChunkPool.ChunkSize, chunk.Surface);
+                    image.DrawImage(pos * ChunkPool.ChunkSize, chunk.Surface);
             }
         }
 
@@ -37,7 +39,7 @@
         {
             if (disposed)
                 return;
-            foreach (var (x, y, chunk) in savedChunks)
+            foreach (var (pos, chunk) in savedChunks)
             {
                 if (chunk != null)
                     ChunkPool.Instance.ReturnChunk(chunk);

+ 27 - 27
src/ChunkyImageLib/ChunkyImage.cs

@@ -9,27 +9,27 @@ namespace ChunkyImageLib
     {
         private bool locked = false; //todo implement locking
 
-        private Queue<(IOperation, HashSet<(int, int)>)> queuedOperations = new();
+        private Queue<(IOperation, HashSet<Vector2i>)> queuedOperations = new();
 
-        private Dictionary<(int, int), Chunk> chunks = new();
-        private Dictionary<(int, int), Chunk> uncommitedChunks = new();
+        private Dictionary<Vector2i, Chunk> chunks = new();
+        private Dictionary<Vector2i, Chunk> uncommitedChunks = new();
 
         public static int ChunkSize => ChunkPool.ChunkSize;
 
-        public Chunk? GetChunk(int x, int y)
+        public Chunk? GetChunk(Vector2i pos)
         {
             if (queuedOperations.Count == 0)
-                return MaybeGetChunk(x, y, chunks);
-            ProcessQueue(x, y);
-            return MaybeGetChunk(x, y, uncommitedChunks) ?? MaybeGetChunk(x, y, chunks);
+                return MaybeGetChunk(pos, chunks);
+            ProcessQueue(pos);
+            return MaybeGetChunk(pos, uncommitedChunks) ?? MaybeGetChunk(pos, chunks);
         }
 
-        public Chunk? GetCommitedChunk(int x, int y)
+        public Chunk? GetCommitedChunk(Vector2i pos)
         {
-            return MaybeGetChunk(x, y, chunks);
+            return MaybeGetChunk(pos, chunks);
         }
 
-        private Chunk? MaybeGetChunk(int x, int y, Dictionary<(int, int), Chunk> from) => from.ContainsKey((x, y)) ? from[(x, y)] : null;
+        private Chunk? MaybeGetChunk(Vector2i pos, Dictionary<Vector2i, Chunk> from) => from.ContainsKey(pos) ? from[pos] : null;
 
         public void DrawRectangle(ShapeData rect)
         {
@@ -37,9 +37,9 @@ namespace ChunkyImageLib
             queuedOperations.Enqueue((operation, operation.FindAffectedChunks()));
         }
 
-        internal void DrawImage(int x, int y, Surface image)
+        internal void DrawImage(Vector2i pos, Surface image)
         {
-            ImageOperation operation = new(x, y, image);
+            ImageOperation operation = new(pos, image);
             queuedOperations.Enqueue((operation, operation.FindAffectedChunks()));
         }
 
@@ -61,7 +61,7 @@ namespace ChunkyImageLib
             ProcessQueueFinal();
         }
 
-        public HashSet<(int, int)> FindAffectedChunks()
+        public HashSet<Vector2i> FindAffectedChunks()
         {
             var chunks = uncommitedChunks.Select(chunk => chunk.Key).ToHashSet();
             foreach (var (operation, opChunks) in queuedOperations)
@@ -75,9 +75,9 @@ namespace ChunkyImageLib
         {
             foreach (var (operation, operChunks) in queuedOperations)
             {
-                foreach (var (x, y) in operChunks)
+                foreach (var pos in operChunks)
                 {
-                    operation.DrawOnChunk(GetOrCreateCommitedChunk(x, y), x, y);
+                    operation.DrawOnChunk(GetOrCreateCommitedChunk(pos), pos);
                 }
                 operation.Dispose();
             }
@@ -99,48 +99,48 @@ namespace ChunkyImageLib
             uncommitedChunks.Clear();
         }
 
-        private void ProcessQueue(int chunkX, int chunkY)
+        private void ProcessQueue(Vector2i chunkPos)
         {
             Chunk? targetChunk = null;
             foreach (var (operation, operChunks) in queuedOperations)
             {
-                if (!operChunks.Contains((chunkX, chunkY)))
+                if (!operChunks.Contains(chunkPos))
                     continue;
-                operChunks.Remove((chunkX, chunkY));
+                operChunks.Remove(chunkPos);
 
                 if (targetChunk == null)
-                    targetChunk = GetOrCreateUncommitedChunk(chunkX, chunkY);
+                    targetChunk = GetOrCreateUncommitedChunk(chunkPos);
 
-                operation.DrawOnChunk(targetChunk, chunkX, chunkY);
+                operation.DrawOnChunk(targetChunk, chunkPos);
             }
         }
 
-        private Chunk GetOrCreateCommitedChunk(int chunkX, int chunkY)
+        private Chunk GetOrCreateCommitedChunk(Vector2i chunkPos)
         {
-            Chunk? targetChunk = MaybeGetChunk(chunkX, chunkY, chunks);
+            Chunk? targetChunk = MaybeGetChunk(chunkPos, chunks);
             if (targetChunk != null)
                 return targetChunk;
             var newChunk = ChunkPool.Instance.BorrowChunk();
             newChunk.Surface.SkiaSurface.Canvas.Clear();
-            chunks[(chunkX, chunkY)] = newChunk;
+            chunks[chunkPos] = newChunk;
             return newChunk;
         }
 
-        private Chunk GetOrCreateUncommitedChunk(int chunkX, int chunkY)
+        private Chunk GetOrCreateUncommitedChunk(Vector2i chunkPos)
         {
             Chunk? targetChunk;
-            targetChunk = MaybeGetChunk(chunkX, chunkY, uncommitedChunks);
+            targetChunk = MaybeGetChunk(chunkPos, uncommitedChunks);
             if (targetChunk == null)
             {
                 targetChunk = ChunkPool.Instance.BorrowChunk();
-                var maybeCommitedChunk = MaybeGetChunk(chunkX, chunkY, chunks);
+                var maybeCommitedChunk = MaybeGetChunk(chunkPos, chunks);
 
                 if (maybeCommitedChunk != null)
                     maybeCommitedChunk.Surface.CopyTo(targetChunk.Surface);
                 else
                     targetChunk.Surface.SkiaSurface.Canvas.Clear();
 
-                uncommitedChunks[(chunkX, chunkY)] = targetChunk;
+                uncommitedChunks[chunkPos] = targetChunk;
             }
             return targetChunk;
         }

+ 24 - 0
src/ChunkyImageLib/DataHolders/RotatedShapeData.cs

@@ -0,0 +1,24 @@
+using SkiaSharp;
+
+namespace ChunkyImageLib.DataHolders
+{
+    public record struct RotatedShapeData
+    {
+        public RotatedShapeData(Vector2d center, Vector2d size, float angle, int strokeWidth, SKColor strokeColor, SKColor fillColor)
+        {
+            Center = center;
+            Size = size;
+            Angle = angle;
+            StrokeColor = strokeColor;
+            FillColor = fillColor;
+            StrokeWidth = strokeWidth;
+        }
+
+        public Vector2d Center { get; }
+        public float Angle { get; }
+        public Vector2d Size { get; }
+        public SKColor StrokeColor { get; }
+        public SKColor FillColor { get; }
+        public int StrokeWidth { get; }
+    }
+}

+ 7 - 11
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -4,25 +4,21 @@ namespace ChunkyImageLib.DataHolders
 {
     public record struct ShapeData
     {
-        public ShapeData(int x, int y, int width, int height, int strokeWidth, SKColor strokeColor, SKColor fillColor)
+        public ShapeData(Vector2i pos, Vector2i size, int strokeWidth, SKColor strokeColor, SKColor fillColor)
         {
-            X = x;
-            Y = y;
-            Width = width;
-            Height = height;
+            Pos = pos;
+            MaxPos = new(pos.X + size.X - 1, pos.Y + size.Y - 1);
+            Size = size;
             StrokeColor = strokeColor;
             FillColor = fillColor;
             StrokeWidth = strokeWidth;
         }
 
-        public int X { get; }
-        public int Y { get; }
-        public int Width { get; }
-        public int Height { get; }
+        public Vector2i Pos { get; }
+        public Vector2i MaxPos { get; }
+        public Vector2i Size { get; }
         public SKColor StrokeColor { get; }
         public SKColor FillColor { get; }
         public int StrokeWidth { get; }
-        public int MaxX => X + Width - 1;
-        public int MaxY => Y + Height - 1;
     }
 }

+ 144 - 0
src/ChunkyImageLib/DataHolders/Vector2d.cs

@@ -0,0 +1,144 @@
+using SkiaSharp;
+
+namespace ChunkyImageLib.DataHolders
+{
+    public struct Vector2d
+    {
+        public double X { set; get; }
+        public double Y { set; get; }
+
+        public double TaxicabLength => Math.Abs(X) + Math.Abs(Y);
+        public double Length => Math.Sqrt(LengthSquared);
+        public double LengthSquared => X * X + Y * Y;
+        public double Angle => Y < 0 ? -AngleTo(new Vector2d(1, 0)) : AngleTo(new Vector2d(1, 0));
+
+        public Vector2d(double x, double y)
+        {
+            X = x;
+            Y = y;
+        }
+        public static Vector2d FromAngleAndLength(double angle, double length)
+        {
+            return new Vector2d(Math.Cos(angle) * length, Math.Sin(angle) * length);
+        }
+
+        public Vector2d Rotate(double angle)
+        {
+            Vector2d result = new Vector2d();
+            result.X = X * Math.Cos(angle) - Y * Math.Sin(angle);
+            result.Y = X * Math.Sin(angle) + Y * Math.Cos(angle);
+            return result;
+        }
+        public double DistanceToLineSegment(Vector2d pos1, Vector2d pos2)
+        {
+            Vector2d segment = pos2 - pos1;
+            if ((this - pos1).AngleTo(segment) > Math.PI / 2)
+                return (this - pos1).Length;
+            if ((this - pos2).AngleTo(-segment) > Math.PI / 2)
+                return (this - pos2).Length;
+            return DistanceToLine(pos1, pos2);
+        }
+        public double DistanceToLine(Vector2d pos1, Vector2d pos2)
+        {
+            double a = (pos1 - pos2).Length;
+            double b = (this - pos1).Length;
+            double c = (this - pos2).Length;
+
+            double p = (a + b + c) / 2;
+            double triangleArea = Math.Sqrt(p * (p - a) * (p - b) * (p - c));
+
+            return triangleArea / a * 2;
+        }
+        public double AngleTo(Vector2d other)
+        {
+            return Math.Acos((this * other) / Length / other.Length);
+        }
+        public Vector2d Normalize()
+        {
+            return new Vector2d(X / Length, Y / Length);
+        }
+        public Vector2d Signs()
+        {
+            return new Vector2d(X >= 0 ? 1 : -1, Y >= 0 ? 1 : -1);
+        }
+        public Vector2d Multiply(Vector2d other)
+        {
+            return new Vector2d(X * other.X, Y * other.Y);
+        }
+        public static Vector2d operator +(Vector2d a, Vector2d b)
+        {
+            return new Vector2d(a.X + b.X, a.Y + b.Y);
+        }
+        public static Vector2d operator -(Vector2d a, Vector2d b)
+        {
+            return new Vector2d(a.X - b.X, a.Y - b.Y);
+        }
+        public static Vector2d operator -(Vector2d a)
+        {
+            return new Vector2d(-a.X, -a.Y);
+        }
+        public static Vector2d operator *(double b, Vector2d a)
+        {
+            return new Vector2d(a.X * b, a.Y * b);
+        }
+        public static double operator *(Vector2d a, Vector2d b)
+        {
+            return a.X * b.X + a.Y * b.Y;
+        }
+        public static Vector2d operator *(Vector2d a, double b)
+        {
+            return new Vector2d(a.X * b, a.Y * b);
+        }
+        public static Vector2d operator /(Vector2d a, double b)
+        {
+            return new Vector2d(a.X / b, a.Y / b);
+        }
+        public static bool operator ==(Vector2d a, Vector2d b)
+        {
+            return a.X == b.X && a.Y == b.Y;
+        }
+        public static bool operator !=(Vector2d a, Vector2d b)
+        {
+            return !(a.X == b.X && a.Y == b.Y);
+        }
+
+        public static explicit operator Vector2i(Vector2d vec)
+        {
+            return new Vector2i((int)vec.X, (int)vec.Y);
+        }
+        public static explicit operator SKPointI(Vector2d vec)
+        {
+            return new SKPointI((int)vec.X, (int)vec.Y);
+        }
+        public static explicit operator SKPoint(Vector2d vec)
+        {
+            return new SKPoint((float)vec.X, (float)vec.Y);
+        }
+        public static explicit operator SKSizeI(Vector2d vec)
+        {
+            return new SKSizeI((int)vec.X, (int)vec.Y);
+        }
+        public static explicit operator SKSize(Vector2d vec)
+        {
+            return new SKSize((float)vec.X, (float)vec.Y);
+        }
+
+        public override string ToString()
+        {
+            return $"({X},{Y})";
+        }
+
+        public override bool Equals(object? obj)
+        {
+            var item = obj as Vector2d?;
+            if (item == null)
+                return false;
+            return this == item;
+        }
+
+        public override int GetHashCode()
+        {
+            return HashCode.Combine(X, Y);
+        }
+    }
+}

+ 100 - 0
src/ChunkyImageLib/DataHolders/Vector2i.cs

@@ -0,0 +1,100 @@
+using SkiaSharp;
+
+namespace ChunkyImageLib.DataHolders
+{
+    public struct Vector2i
+    {
+        public int X { set; get; }
+        public int Y { set; get; }
+
+        public int TaxicabLength => Math.Abs(X) + Math.Abs(Y);
+        public double Length => Math.Sqrt(LengthSquared);
+        public int LengthSquared => X * X + Y * Y;
+
+        public Vector2i(int x, int y)
+        {
+            X = x;
+            Y = y;
+        }
+
+        public Vector2i Signs()
+        {
+            return new Vector2i(X >= 0 ? 1 : -1, Y >= 0 ? 1 : -1);
+        }
+        public Vector2i Multiply(Vector2i other)
+        {
+            return new Vector2i(X * other.X, Y * other.Y);
+        }
+        public static Vector2i operator +(Vector2i a, Vector2i b)
+        {
+            return new Vector2i(a.X + b.X, a.Y + b.Y);
+        }
+        public static Vector2i operator -(Vector2i a, Vector2i b)
+        {
+            return new Vector2i(a.X - b.X, a.Y - b.Y);
+        }
+        public static Vector2i operator -(Vector2i a)
+        {
+            return new Vector2i(-a.X, -a.Y);
+        }
+        public static Vector2i operator *(int b, Vector2i a)
+        {
+            return new Vector2i(a.X * b, a.Y * b);
+        }
+        public static int operator *(Vector2i a, Vector2i b)
+        {
+            return a.X * b.X + a.Y * b.Y;
+        }
+        public static Vector2i operator *(Vector2i a, int b)
+        {
+            return new Vector2i(a.X * b, a.Y * b);
+        }
+        public static bool operator ==(Vector2i a, Vector2i b)
+        {
+            return a.X == b.X && a.Y == b.Y;
+        }
+        public static bool operator !=(Vector2i a, Vector2i b)
+        {
+            return !(a.X == b.X && a.Y == b.Y);
+        }
+
+        public static implicit operator Vector2d(Vector2i vec)
+        {
+            return new Vector2d(vec.X, vec.Y);
+        }
+        public static implicit operator SKPointI(Vector2i vec)
+        {
+            return new SKPointI(vec.X, vec.Y);
+        }
+        public static implicit operator SKPoint(Vector2i vec)
+        {
+            return new SKPoint(vec.X, vec.Y);
+        }
+        public static implicit operator SKSizeI(Vector2i vec)
+        {
+            return new SKSizeI(vec.X, vec.Y);
+        }
+        public static implicit operator SKSize(Vector2i vec)
+        {
+            return new SKSize(vec.X, vec.Y);
+        }
+
+        public override string ToString()
+        {
+            return $"({X},{Y})";
+        }
+
+        public override bool Equals(object? obj)
+        {
+            var item = obj as Vector2i?;
+            if (item == null)
+                return false;
+            return this == item;
+        }
+
+        public override int GetHashCode()
+        {
+            return HashCode.Combine(X, Y);
+        }
+    }
+}

+ 5 - 3
src/ChunkyImageLib/Operations/IOperation.cs

@@ -1,8 +1,10 @@
-namespace ChunkyImageLib.Operations
+using ChunkyImageLib.DataHolders;
+
+namespace ChunkyImageLib.Operations
 {
     internal interface IOperation : IDisposable
     {
-        void DrawOnChunk(Chunk chunk, int chunkX, int chunkY);
-        HashSet<(int, int)> FindAffectedChunks();
+        void DrawOnChunk(Chunk chunk, Vector2i chunkPos);
+        HashSet<Vector2i> FindAffectedChunks();
     }
 }

+ 14 - 15
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -1,35 +1,34 @@
-using SkiaSharp;
+using ChunkyImageLib.DataHolders;
+using SkiaSharp;
 
 namespace ChunkyImageLib.Operations
 {
     internal record class ImageOperation : IOperation
     {
-        private int x;
-        private int y;
+        private Vector2i pos;
         private Surface toPaint;
         private static SKPaint ReplacingPaint = new() { BlendMode = SKBlendMode.Src };
-        public ImageOperation(int x, int y, Surface image)
+        public ImageOperation(Vector2i pos, Surface image)
         {
-            this.x = x;
-            this.y = y;
+            this.pos = pos;
             toPaint = new Surface(image);
         }
 
-        public void DrawOnChunk(Chunk chunk, int chunkX, int chunkY)
+        public void DrawOnChunk(Chunk chunk, Vector2i chunkPos)
         {
-            chunk.Surface.SkiaSurface.Canvas.DrawSurface(toPaint.SkiaSurface, x - chunkX * ChunkPool.ChunkSize, y - chunkY * ChunkPool.ChunkSize, ReplacingPaint);
+            chunk.Surface.SkiaSurface.Canvas.DrawSurface(toPaint.SkiaSurface, pos - chunkPos * ChunkPool.ChunkSize, ReplacingPaint);
         }
 
-        public HashSet<(int, int)> FindAffectedChunks()
+        public HashSet<Vector2i> FindAffectedChunks()
         {
-            var (startX, startY) = OperationHelper.GetChunkPos(x, y, ChunkPool.ChunkSize);
-            var (endX, endY) = OperationHelper.GetChunkPos(x + toPaint.Width - 1, y + toPaint.Height - 1, ChunkPool.ChunkSize);
-            HashSet<(int, int)> output = new();
-            for (int cx = startX; cx <= endX; cx++)
+            Vector2i start = OperationHelper.GetChunkPos(pos, ChunkPool.ChunkSize);
+            Vector2i end = OperationHelper.GetChunkPos(new(pos.X + toPaint.Width - 1, pos.Y + toPaint.Height - 1), ChunkPool.ChunkSize);
+            HashSet<Vector2i> output = new();
+            for (int cx = start.X; cx <= end.X; cx++)
             {
-                for (int cy = startY; cy <= endY; cy++)
+                for (int cy = start.Y; cy <= end.Y; cy++)
                 {
-                    output.Add((cx, cy));
+                    output.Add(new(cx, cy));
                 }
             }
             return output;

+ 37 - 5
src/ChunkyImageLib/Operations/OperationHelper.cs

@@ -1,12 +1,44 @@
-namespace ChunkyImageLib.Operations
+using ChunkyImageLib.DataHolders;
+
+namespace ChunkyImageLib.Operations
 {
     public static class OperationHelper
     {
-        public static (int, int) GetChunkPos(int pixelX, int pixelY, int chunkSize)
+        public static Vector2i GetChunkPos(Vector2i pixelPos, int chunkSize)
         {
-            int x = (int)Math.Floor(pixelX / (float)chunkSize);
-            int y = (int)Math.Floor(pixelY / (float)chunkSize);
-            return (x, y);
+            return new Vector2i()
+            {
+                X = (int)MathF.Floor(pixelPos.X / (float)chunkSize),
+                Y = (int)MathF.Floor(pixelPos.Y / (float)chunkSize)
+            };
+        }
+
+        /// <summary>
+        /// Finds chunks that at least partially lie inside of a rectangle
+        /// </summary>
+        public static HashSet<Vector2i> FindChunksTouchingRectangle(Vector2d center, Vector2d size, float angle, int chunkSize)
+        {
+            var corners = FindRectangleCorners(center, size, angle);
+            double minX = Math.Min(Math.Min(corners.Item1.X, corners.Item2.X), Math.Min(corners.Item3.X, corners.Item4.X));
+            double maxX = Math.Max(Math.Max(corners.Item1.X, corners.Item2.X), Math.Max(corners.Item3.X, corners.Item4.X));
+            double minY = Math.Min(Math.Min(corners.Item1.Y, corners.Item2.Y), Math.Min(corners.Item3.Y, corners.Item4.Y));
+            double maxY = Math.Max(Math.Max(corners.Item1.Y, corners.Item2.Y), Math.Max(corners.Item3.Y, corners.Item4.Y));
+
+            //(int leftChunkX, int leftChunkY) = GetChunkPos(minX, )
+            throw new NotImplementedException();
+        }
+
+
+        private static (Vector2d, Vector2d, Vector2d, Vector2d) FindRectangleCorners(Vector2d center, Vector2d size, float angle)
+        {
+            Vector2d right = Vector2d.FromAngleAndLength(angle, size.X / 2);
+            Vector2d up = Vector2d.FromAngleAndLength(angle + Math.PI / 2, size.Y / 2);
+            return (
+                center + right + up,
+                center - right + up,
+                center + right - up,
+                center - right - up
+                );
         }
     }
 }

+ 26 - 26
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -12,12 +12,12 @@ namespace ChunkyImageLib.Operations
 
         public ShapeData Data { get; }
 
-        public void DrawOnChunk(Chunk chunk, int chunkX, int chunkY)
+        public void DrawOnChunk(Chunk chunk, Vector2i chunkPos)
         {
             var skiaSurf = chunk.Surface.SkiaSurface;
             // use a clipping rectangle with 2x stroke width to make sure stroke doesn't stick outside rect bounds
             skiaSurf.Canvas.Save();
-            var rect = SKRect.Create(Data.X - chunkX * ChunkPool.ChunkSize, Data.Y - chunkY * ChunkPool.ChunkSize, Data.Width, Data.Height);
+            var rect = SKRect.Create(Data.Pos - chunkPos * ChunkPool.ChunkSize, Data.Size);
             skiaSurf.Canvas.ClipRect(rect);
 
             // draw fill
@@ -41,11 +41,11 @@ namespace ChunkyImageLib.Operations
             skiaSurf.Canvas.Restore();
         }
 
-        public HashSet<(int, int)> FindAffectedChunks()
+        public HashSet<Vector2i> FindAffectedChunks()
         {
-            if (Data.Width < 1 || Data.Height < 1 || Data.StrokeColor.Alpha == 0 && Data.FillColor.Alpha == 0)
+            if (Data.Size.X < 1 || Data.Size.Y < 1 || Data.StrokeColor.Alpha == 0 && Data.FillColor.Alpha == 0)
                 return new();
-            if (Data.FillColor.Alpha != 0 || Data.Width == 1 || Data.Height == 1)
+            if (Data.FillColor.Alpha != 0 || Data.Size.X == 1 || Data.Size.Y == 1)
                 return GetChunksForFilled(ChunkPool.ChunkSize);
             return GetChunksForStroke(ChunkPool.ChunkSize);
         }
@@ -60,46 +60,46 @@ namespace ChunkyImageLib.Operations
             return (insetMin, insetMax);
         }
 
-        private HashSet<(int, int)> GetChunksForStroke(int chunkSize)
+        private HashSet<Vector2i> GetChunksForStroke(int chunkSize)
         {
             //we need to account for wide strokes covering multiple chunks
             //find inner stroke boudaries in pixel coords
-            var xInset = Inset(Data.X, Data.MaxX, Data.StrokeWidth);
-            var yInset = Inset(Data.Y, Data.MaxY, Data.StrokeWidth);
+            var xInset = Inset(Data.Pos.X, Data.MaxPos.X, Data.StrokeWidth);
+            var yInset = Inset(Data.Pos.Y, Data.MaxPos.Y, Data.StrokeWidth);
             if (xInset == null || yInset == null)
                 return GetChunksForFilled(chunkSize);
 
             //find two chunk rectanges, outer and inner
-            var (minX, minY) = OperationHelper.GetChunkPos(Data.X, Data.Y, chunkSize);
-            var (maxX, maxY) = OperationHelper.GetChunkPos(Data.MaxX, Data.MaxY, chunkSize);
-            var (minInsetX, minInsetY) = OperationHelper.GetChunkPos(xInset.Value.Item1, yInset.Value.Item1, chunkSize);
-            var (maxInsetX, maxInsetY) = OperationHelper.GetChunkPos(xInset.Value.Item2, yInset.Value.Item2, chunkSize);
+            Vector2i min = OperationHelper.GetChunkPos(Data.Pos, chunkSize);
+            Vector2i max = OperationHelper.GetChunkPos(Data.MaxPos, chunkSize);
+            Vector2i minInset = OperationHelper.GetChunkPos(new(xInset.Value.Item1, yInset.Value.Item1), chunkSize);
+            Vector2i maxInset = OperationHelper.GetChunkPos(new(xInset.Value.Item2, yInset.Value.Item2), chunkSize);
 
             //fill in sides
-            HashSet<(int, int)> chunks = new();
-            AddRectangle(minX, minY, maxX, minInsetY, chunks); //top
-            AddRectangle(minX, minInsetY + 1, minInsetX, maxInsetY - 1, chunks); //left
-            AddRectangle(maxInsetX, minInsetY + 1, maxX, maxInsetY - 1, chunks); //right
-            AddRectangle(minX, maxInsetY, maxX, maxY, chunks); //bottom
+            HashSet<Vector2i> chunks = new();
+            AddRectangle(min, new(max.X, minInset.Y), chunks); //top
+            AddRectangle(new(min.X, minInset.Y + 1), new(minInset.X, maxInset.Y - 1), chunks); //left
+            AddRectangle(new(maxInset.X, minInset.Y + 1), new(max.X, maxInset.Y - 1), chunks); //right
+            AddRectangle(new(min.X, maxInset.Y), max, chunks); //bottom
             return chunks;
         }
 
-        private HashSet<(int, int)> GetChunksForFilled(int chunkSize)
+        private HashSet<Vector2i> GetChunksForFilled(int chunkSize)
         {
-            var (minX, minY) = OperationHelper.GetChunkPos(Data.X, Data.Y, chunkSize);
-            var (maxX, maxY) = OperationHelper.GetChunkPos(Data.MaxX, Data.MaxY, chunkSize);
-            HashSet<(int, int)> output = new();
-            AddRectangle(minX, minY, maxX, maxY, output);
+            Vector2i min = OperationHelper.GetChunkPos(Data.Pos, chunkSize);
+            Vector2i max = OperationHelper.GetChunkPos(Data.MaxPos, chunkSize);
+            HashSet<Vector2i> output = new();
+            AddRectangle(min, max, output);
             return output;
         }
 
-        private static void AddRectangle(int minX, int minY, int maxX, int maxY, HashSet<(int, int)> set)
+        private static void AddRectangle(Vector2i min, Vector2i max, HashSet<Vector2i> set)
         {
-            for (int x = minX; x <= maxX; x++)
+            for (int x = min.X; x <= max.X; x++)
             {
-                for (int y = minY; y <= maxY; y++)
+                for (int y = min.Y; y <= max.Y; y++)
                 {
-                    set.Add((x, y));
+                    set.Add(new(x, y));
                 }
             }
         }

+ 4 - 3
src/ChunkyImageLibTest/OperationHelperTests.cs

@@ -1,3 +1,4 @@
+using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using Xunit;
 
@@ -13,9 +14,9 @@ namespace ChunkyImageLibTest
         [InlineData(-33, -33, -2, -2)]
         public void GetChunkPos_32ChunkSize_ReturnsCorrectValues(int x, int y, int expX, int expY)
         {
-            var (actX, actY) = OperationHelper.GetChunkPos(x, y, 32);
-            Assert.Equal(expX, actX);
-            Assert.Equal(expY, actY);
+            Vector2i act = OperationHelper.GetChunkPos(new(x, y), 32);
+            Assert.Equal(expX, act.X);
+            Assert.Equal(expY, act.Y);
         }
     }
 }

+ 29 - 28
src/ChunkyImageLibTest/RectangleOperationTests.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using SkiaSharp;
 using System.Collections.Generic;
@@ -15,9 +16,9 @@ namespace ChunkyImageLibTest
         public void FindAffectedChunks_SmallStrokeOnly_FindsCorrectChunks()
         {
             var (x, y, w, h) = (0, 0, chunkSize, chunkSize);
-            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent));
+            RectangleOperation operation = new(new(new(x, y), new(w, h), 1, SKColors.Black, SKColors.Transparent));
 
-            HashSet<(int, int)> expected = new() { (0, 0) };
+            HashSet<Vector2i> expected = new() { new(0, 0) };
             var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
@@ -27,9 +28,9 @@ namespace ChunkyImageLibTest
         public void FindAffectedChunks_2by2StrokeOnly_FindsCorrectChunks()
         {
             var (x, y, w, h) = (-chunkSize, -chunkSize, chunkSize * 2, chunkSize * 2);
-            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent));
+            RectangleOperation operation = new(new(new(x, y), new(w, h), 1, SKColors.Black, SKColors.Transparent));
 
-            HashSet<(int, int)> expected = new() { (-1, -1), (0, -1), (-1, 0), (0, 0) };
+            HashSet<Vector2i> expected = new() { new(-1, -1), new(0, -1), new(-1, 0), new(0, 0) };
             var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);
@@ -39,13 +40,13 @@ namespace ChunkyImageLibTest
         public void FindAffectedChunks_3x3PositiveStrokeOnly_FindsCorrectChunks()
         {
             var (x, y, w, h) = (chunkSize + chunkSize / 2, chunkSize + chunkSize / 2, chunkSize * 2, chunkSize * 2);
-            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent));
+            RectangleOperation operation = new(new(new(x, y), new(w, h), 1, SKColors.Black, SKColors.Transparent));
 
-            HashSet<(int, int)> expected = new()
+            HashSet<Vector2i> expected = new()
             {
-                (1, 1), (2, 1), (3, 1), 
-                (1, 2),         (3, 2), 
-                (1, 3), (2, 3), (3, 3),
+                new(1, 1), new(2, 1), new(3, 1),
+                new(1, 2),            new(3, 2),
+                new(1, 3), new(2, 3), new(3, 3),
             };
             var actual = operation.FindAffectedChunks();
 
@@ -56,13 +57,13 @@ namespace ChunkyImageLibTest
         public void FindAffectedChunks_3x3NegativeStrokeOnly_FindsCorrectChunks()
         {
             var (x, y, w, h) = (-chunkSize * 3 + chunkSize / 2, -chunkSize * 3 + chunkSize / 2, chunkSize * 2, chunkSize * 2);
-            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.Transparent));
+            RectangleOperation operation = new(new(new(x, y), new(w, h), 1, SKColors.Black, SKColors.Transparent));
 
-            HashSet<(int, int)> expected = new()
+            HashSet<Vector2i> expected = new()
             {
-                (-4, -4), (-3, -4), (-2, -4),
-                (-4, -3),           (-2, -3),
-                (-4, -2), (-3, -2), (-2, -2),
+                new(-4, -4), new(-3, -4), new(-2, -4),
+                new(-4, -3),              new(-2, -3),
+                new(-4, -2), new(-3, -2), new(-2, -2),
             };
             var actual = operation.FindAffectedChunks();
 
@@ -73,13 +74,13 @@ namespace ChunkyImageLibTest
         public void FindAffectedChunks_3x3PositiveFilled_FindsCorrectChunks()
         {
             var (x, y, w, h) = (chunkSize + chunkSize / 2, chunkSize + chunkSize / 2, chunkSize * 2, chunkSize * 2);
-            RectangleOperation operation = new(new(x, y, w, h, 1, SKColors.Black, SKColors.White));
+            RectangleOperation operation = new(new(new(x, y), new(w, h), 1, SKColors.Black, SKColors.White));
 
-            HashSet<(int, int)> expected = new()
+            HashSet<Vector2i> expected = new()
             {
-                (1, 1), (2, 1), (3, 1), 
-                (1, 2), (2, 2), (3, 2),
-                (1, 3), (2, 3), (3, 3),
+                new(1, 1), new(2, 1), new(3, 1), 
+                new(1, 2), new(2, 2), new(3, 2),
+                new(1, 3), new(2, 3), new(3, 3),
             };
             var actual = operation.FindAffectedChunks();
 
@@ -90,15 +91,15 @@ namespace ChunkyImageLibTest
         public void FindAffectedChunks_ThickPositiveStroke_FindsCorrectChunks()
         {
             var (x, y, w, h) = (chunkSize / 2, chunkSize / 2, chunkSize * 4, chunkSize * 4);
-            RectangleOperation operation = new(new(x, y, w, h, 32, SKColors.Black, SKColors.Transparent));
+            RectangleOperation operation = new(new(new(x, y), new(w, h), 32, SKColors.Black, SKColors.Transparent));
 
-            HashSet<(int, int)> expected = new()
+            HashSet<Vector2i> expected = new()
             {
-                (0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
-                (0, 1), (1, 1), (2, 1), (3, 1), (4, 1),
-                (0, 2), (1, 2),         (3, 2), (4, 2),
-                (0, 3), (1, 3), (2, 3), (3, 3), (4, 3),
-                (0, 4), (1, 4), (2, 4), (3, 4), (4, 4),
+                new(0, 0), new(1, 0), new(2, 0), new(3, 0), new(4, 0),
+                new(0, 1), new(1, 1), new(2, 1), new(3, 1), new(4, 1),
+                new(0, 2), new(1, 2),            new(3, 2), new(4, 2),
+                new(0, 3), new(1, 3), new(2, 3), new(3, 3), new(4, 3),
+                new(0, 4), new(1, 4), new(2, 4), new(3, 4), new(4, 4),
             };
             var actual = operation.FindAffectedChunks();
 
@@ -109,9 +110,9 @@ namespace ChunkyImageLibTest
         public void FindAffectedChunks_SmallButThick_FindsCorrectChunks()
         {
             var (x, y, w, h) = (chunkSize / 2, chunkSize / 2, 1, 1);
-            RectangleOperation operation = new(new(x, y, w, h, 256, SKColors.Black, SKColors.White));
+            RectangleOperation operation = new(new(new(x, y), new(w, h), 256, SKColors.Black, SKColors.White));
 
-            HashSet<(int, int)> expected = new() { (0, 0) };
+            HashSet<Vector2i> expected = new() { new(0, 0) };
             var actual = operation.FindAffectedChunks();
 
             Assert.Equal(expected, actual);

+ 12 - 4
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -108,10 +108,18 @@ namespace PixiEditorPrototype.ViewModels
             var pos = args.GetPosition(source);
             int curX = (int)(pos.X / source.Width * FinalBitmap.PixelHeight);
             int curY = (int)(pos.Y / source.Height * FinalBitmap.PixelHeight);
-            ActionAccumulator.AddAction(
-                new DrawRectangle_Action(
-                    SelectedStructureMember!.GuidValue,
-                    new(mouseDownX, mouseDownY, curX - mouseDownX, curY - mouseDownY, 1, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A), SKColors.Transparent)));
+            ActionAccumulator.AddAction
+                (
+                    new DrawRectangle_Action
+                    (
+                        SelectedStructureMember!.GuidValue,
+                        new(new(mouseDownX, mouseDownY),
+                        new(curX - mouseDownX, curY - mouseDownY),
+                        1,
+                        new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A),
+                        SKColors.Transparent)
+                    )
+                );
         }
 
         public void MouseUp(object? param)

+ 13 - 12
src/StructureRenderer/Renderer.cs

@@ -2,6 +2,7 @@
 using ChangeableDocument.Changeables.Interfaces;
 using ChangeableDocument.ChangeInfos;
 using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
 using SkiaSharp;
 using StructureRenderer.RenderInfos;
 
@@ -23,9 +24,9 @@ namespace StructureRenderer
             return await Task.Run(() => Render(changes, screenSurface, screenW, screenH)).ConfigureAwait(true);
         }
 
-        private HashSet<(int, int)> FindChunksToRerender(IReadOnlyList<IChangeInfo> changes)
+        private HashSet<Vector2i> FindChunksToRerender(IReadOnlyList<IChangeInfo> changes)
         {
-            HashSet<(int, int)> chunks = new();
+            HashSet<Vector2i> chunks = new();
             foreach (var change in changes)
             {
                 if (change is LayerImageChunks_ChangeInfo layerImageChunks)
@@ -57,13 +58,13 @@ namespace StructureRenderer
             }
             else
             {
-                HashSet<(int, int)> chunks = FindChunksToRerender(changes);
+                HashSet<Vector2i> chunks = FindChunksToRerender(changes);
                 var (minX, minY, maxX, maxY) = (int.MaxValue, int.MaxValue, int.MinValue, int.MinValue);
-                foreach (var (x, y) in chunks)
+                foreach (var chunkPos in chunks)
                 {
-                    RenderChunk(x, y, screenSurface);
-                    (minX, minY) = (Math.Min(x, minX), Math.Min(y, minY));
-                    (maxX, maxY) = (Math.Max(x, maxX), Math.Max(y, maxY));
+                    RenderChunk(chunkPos, screenSurface);
+                    (minX, minY) = (Math.Min(chunkPos.X, minX), Math.Min(chunkPos.Y, minY));
+                    (maxX, maxY) = (Math.Max(chunkPos.X, maxX), Math.Max(chunkPos.Y, maxY));
                 }
                 if (minX != int.MaxValue)
                 {
@@ -89,21 +90,21 @@ namespace StructureRenderer
             {
                 for (int y = 0; y < chunksHeight; y++)
                 {
-                    RenderChunk(x, y, screenSurface);
+                    RenderChunk(new(x, y), screenSurface);
                 }
             }
         }
 
-        private void RenderChunk(int chunkX, int chunkY, SKSurface screenSurface)
+        private void RenderChunk(Vector2i chunkPos, SKSurface screenSurface)
         {
-            screenSurface.Canvas.DrawRect(chunkX * ChunkyImage.ChunkSize, chunkY * ChunkyImage.ChunkSize, ChunkyImage.ChunkSize, ChunkyImage.ChunkSize, ClearPaint);
+            screenSurface.Canvas.DrawRect(SKRect.Create(chunkPos * ChunkyImage.ChunkSize, new(ChunkyImage.ChunkSize, ChunkyImage.ChunkSize)), ClearPaint);
             ForEachLayer((layer) =>
             {
-                var chunk = layer.LayerImage.GetChunk(chunkX, chunkY);
+                var chunk = layer.LayerImage.GetChunk(chunkPos);
                 if (chunk == null)
                     return;
                 using var snapshot = chunk.Snapshot();
-                screenSurface.Canvas.DrawImage(snapshot, chunkX * ChunkyImage.ChunkSize, chunkY * ChunkyImage.ChunkSize, BlendingPaint);
+                screenSurface.Canvas.DrawImage(snapshot, chunkPos * ChunkyImage.ChunkSize, BlendingPaint);
             }, tracker.Document.ReadOnlyStructureRoot);
         }