Skip to content

Commit

Permalink
feat: mirroring and rotating vectors
Browse files Browse the repository at this point in the history
  • Loading branch information
jirikostiha committed Oct 31, 2023
1 parent 835f25d commit 4e6a02b
Show file tree
Hide file tree
Showing 7 changed files with 466 additions and 22 deletions.
127 changes: 124 additions & 3 deletions src/GodotSharpSome.Drawing2D/Multiline.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace GodotSharpSome.Drawing2D
{
using System;
using System.Diagnostics;
using System.Linq;
using static Godot.Mathf;
Expand Down Expand Up @@ -58,8 +59,6 @@ public static Multiline FourLineTypes(SolidLine solid, DottedLine dotted, Dashed

private string? _penKey;

public Vector2[] Points() => _points.ToArray();

public Multiline()
: this(new List<Vector2>())
{ }
Expand Down Expand Up @@ -129,13 +128,15 @@ public Multiline(IList<Vector2> points, IList<(string Key, IStraightLineAppender
/// </summary>
public string? PenKey => _penKey;

/// <summary>
/// <summary>
/// Number of line segments.
/// Note: For example, a dashed line is a single line consisting of n segments.
/// </summary>
public int Segments => _points.Count / 2;

public Vector2[] Points() => _points.ToArray();

/// <summary>
/// Set custom line type.
/// </summary>
public Multiline SetPen(IStraightLineAppender pen, string? penKey = null)
Expand Down Expand Up @@ -169,6 +170,19 @@ public Multiline SetPen(int index)
return this;
}

/// <summary>
/// Append other multiline.
/// </summary>
public Multiline Append(Multiline other)
{
Debug.Assert(other._points.Count % 2 == 0, "Multiline has odd number of points. It must have even.");

for (int i = 0; i < other._points.Count; i++)
_points.Add(other._points[i]);

return this;
}

/// <summary>
/// Append single dot to position vector.
/// </summary>
Expand Down Expand Up @@ -478,6 +492,111 @@ public Multiline AppendRegularConvexPolygon(Vector2 center, float radius, int ve
return this;
}

/// <summary>
/// Mirror all points to line parallel to x-axis.
/// </summary>
/// <param name="y"> Y coordinate of a mirror line. </param>
public Multiline MirrorX(float y)
{
var count = _points.Count;
for (int i = 0; i < count; i++)
_points.Add(_points[i].MirrorX(y));

return this;
}

/// <summary>
/// Mirror all points to line parallel to y-axis.
/// </summary>
/// <param name="x"> X coordinate of a mirror line. </param>
public Multiline MirrorY(float x)
{
var count = _points.Count;
for (int i = 0; i < count; i++)
_points.Add(_points[i].MirrorY(x));

return this;
}

/// <summary>
/// Mirror all points to line determined by one point and direction vector.
/// </summary>
/// <param name="mirrorPoint"> Point of mirror line. </param>
/// <param name="mirrorLineDir"> Direction vector of mirror line. </param>
public Multiline MirrorByDirection(Vector2 mirrorPoint, Vector2 mirrorLineDir)
{
var count = _points.Count;
for (int i = 0; i < count; i++)
_points.Add(_points[i].MirrorByDirection(mirrorPoint, mirrorLineDir));

return this;
}

/// <summary>
/// Mirror all points to line determined by one point and direction vector.
/// </summary>
/// <param name="mirrorPointA"> First point of mirror line. </param>
/// <param name="mirrorPointB"> Second point of mirror line. </param>
public Multiline MirrorByPoints(Vector2 mirrorPointA, Vector2 mirrorPointB)
{
var count = _points.Count;
for (int i = 0; i < count; i++)
_points.Add(_points[i].MirrorByPoints(mirrorPointA, mirrorPointB));

return this;
}

/// <summary>
/// Rotate all points around given center.
/// </summary>
/// <param name="center"> Rotation center. </param>
/// <param name="angle"> Rotation angle. </param>
public Multiline Rotate(Vector2 center, float angle)
{
var count = _points.Count;
for (int i = 0; i < count; i++)
_points.Add(_points[i].Rotated(center, angle));

return this;
}

/// <summary>
/// Rotate all points around given center and angles.
/// </summary>
/// <param name="center"> Rotation center. </param>
/// <param name="angle"> Rotation angle. </param>
/// <param name="repeatCount"> Number of a rotation repetition. </param>
public Multiline Rotate(Vector2 center, float angle, int repeatCount)
{
var count = _points.Count;
var currentAngle = 0f;
for (int j = 0; j < repeatCount; j++)
{
currentAngle += angle;
for (int i = 0; i < count; i++)
_points.Add(_points[i].Rotated(center, currentAngle));
}

return this;
}

/// <summary>
/// Rotate all points around given center and angles.
/// </summary>
/// <param name="center"> Rotation center. </param>
/// <param name="angles"> Rotation angles. </param>
public Multiline Rotate(Vector2 center, params float[] angles)
{
var count = _points.Count;
for (int angle = 0; angle < angles.Length; angle++)
{
for (int i = 0; i < count; i++)
_points.Add(_points[i].Rotated(center, angle));
}

return this;
}

/// <summary>
/// Remove last line (two points) from collection of points.
/// </summary>
Expand Down Expand Up @@ -514,5 +633,7 @@ public static IEnumerable<Vector2> RegularConvexPolygonVertices(Vector2 center,
yield return new(radius * Cos(angle) + center.X, radius * Sin(angle) + center.Y);
}
}

public static Multiline operator +(Multiline a, Multiline b) => a.Append(b);
}
}
92 changes: 79 additions & 13 deletions src/GodotSharpSome.Drawing2D/Vector2DExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace GodotSharpSome.Drawing2D;
using System.Drawing;

namespace GodotSharpSome.Drawing2D;

public static class Vector2DExtension
{
Expand All @@ -13,25 +15,89 @@ public static class Vector2DExtension
public static Vector2 Normal2(this Vector2 vector) => new(vector.Y, -vector.X);

/// <summary>
/// Rotate vectors around center [0,0].
/// Rotate vector around given center.
/// </summary>
/// <param name="vectors"> Vectors. </param>
/// <param name="vector"> Vector to rotate. </param>
/// <param name="center"> Rotation center. </param>
/// <param name="angle"> Rotation angle. </param>
public static IEnumerable<Vector2> Rotate(this IEnumerable<Vector2> vectors, float angle)
/// <returns> Vector with rotated coordinates. </returns>
public static Vector2 Rotated(this Vector2 vector, Vector2 center, float angle) =>
(vector - center).Rotated(angle) + center;

/// <summary>
/// Mirror vector to a line parallel to x-axis.
/// </summary>
/// <param name="vector"> Vector to mirror. </param>
/// <param name="y"> Y coordinate of a mirror line. </param>
/// <returns> Vector with mirrored coordinates. </returns>
public static Vector2 MirrorX(this Vector2 vector, float y) =>
new(vector.X, vector.Y - 2 * (vector.Y - y));

/// <summary>
/// Mirror vector to a line parallel to y-axis.
/// </summary>
/// <param name="vector"> Vector to mirror. </param>
/// <param name="x"> X coordinate of a mirror line. </param>
/// <returns> Vector with mirrored coordinates. </returns>
public static Vector2 MirrorY(this Vector2 vector, float x) =>
new(vector.X - 2 * (vector.X - x), vector.Y);

/// <summary>
/// Mirror vector to line determined by origin and direction vector.
/// </summary>
/// <param name="vector"> Vector to mirror. </param>
/// <param name="directionVector"> Direction vector of mirror line. </param>
/// <returns> Vector with mirrored coordinates. </returns>
public static Vector2 MirrorByDirection(this Vector2 vector, Vector2 directionVector)
{
foreach (var vector in vectors)
yield return vector.Rotated(angle);
var normalizedDir = directionVector.Normalized();
float dotProduct = vector.X * normalizedDir.X + vector.Y * normalizedDir.Y;

return new(
2 * dotProduct * normalizedDir.X - vector.X,
2 * dotProduct * normalizedDir.Y - vector.Y);
}

/// <summary>
/// Rotate vectors around given center.
/// Mirror vector to line determined by one point and direction vector.
/// </summary>
/// <param name="vectors"> Vectors. </param>
/// <param name="center"> Rotation center. </param>
/// <param name="angle"> Rotation angle. </param>
public static IEnumerable<Vector2> Rotate(this IEnumerable<Vector2> vectors, Vector2 center, float angle)
/// <param name="vector"> Vector to mirror. </param>
/// <param name="mirrorPoint"> Point of mirror line. </param>
/// <param name="directionVector"> Direction vector of mirror line. </param>
/// <returns> Vector with mirrored coordinates. </returns>
public static Vector2 MirrorByDirection(this Vector2 vector, Vector2 mirrorPoint, Vector2 directionVector)
{
foreach (var vector in vectors)
yield return (vector + center).Rotated(angle) - center;
var normalizedDir = directionVector.Normalized();
float dotProduct = (vector.X - mirrorPoint.X) * normalizedDir.X + (vector.Y - mirrorPoint.Y) * normalizedDir.Y;

return new (
2 * (mirrorPoint.X + dotProduct * normalizedDir.X) - vector.X,
2 * (mirrorPoint.Y + dotProduct * normalizedDir.Y) - vector.Y);
}

/// <summary>
/// Mirror vector to line determined by two points.
/// </summary>
/// <param name="vector"> Vector to mirror.</param>
/// <param name="mirrorPointA"> First point of mirror line. </param>
/// <param name="mirrorPointB"> Second point of mirror line. </param>
/// <returns> Vector with mirrored coordinates. </returns>
public static Vector2 MirrorByPoints(this Vector2 vector, Vector2 mirrorPointA, Vector2 mirrorPointB)
{
var dx = mirrorPointB.X - mirrorPointA.X;
var dy = mirrorPointB.Y - mirrorPointA.Y;

if (dx == 0 && dy == 0)
{
return new(2 * mirrorPointA.X - vector.X, 2 * mirrorPointA.Y - vector.Y);
}
else
{
float t = ((vector.X - mirrorPointA.X) * dx + (vector.Y - mirrorPointA.Y) * dy) / (dx * dx + dy * dy);

return new(
2 * (mirrorPointA.X + t * dx) - vector.X,
2 * (mirrorPointA.Y + t * dy) - vector.Y);
}
}
}
2 changes: 1 addition & 1 deletion src/quality/GodotSharpSome.Drawing2D.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static void Main(string[] args)
string outputFolder = @"./../../../benchmarks";
if (!Directory.GetCurrentDirectory().EndsWith(typeof(Program).Assembly.GetName().Name ?? string.Empty))
{
// workaround for executing from VS because the output path of binaries is in different folder than usual
//HACK: workaround for executing from VS because the output path of binaries is in different folder than usual
Directory.SetCurrentDirectory(Path.GetDirectoryName(GetSourceFilePathName()) ?? string.Empty);
Console.WriteLine("changed to: " + Directory.GetCurrentDirectory());
}
Expand Down
103 changes: 103 additions & 0 deletions src/quality/GodotSharpSome.Drawing2D.Tests/Vector2DExtensionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
namespace GodotSharpSome.Drawing2D.Tests;

public class Vector2DExtensionTest
{
[Fact]
public void MirrorX()
{
var mirrored = new Vector2(3, 1).MirrorX(1f);

Assert.Equal(3f, mirrored.X, 6);
Assert.Equal(1f, mirrored.Y, 6);
}

[Fact]
public void MirrorY()
{
var mirrored = new Vector2(1, 3).MirrorY(1f);

Assert.Equal(1f, mirrored.X, 6);
Assert.Equal(3f, mirrored.Y, 6);
}

[Fact]
public void MirrorByDirection_ToVectorParallelToXAxis()
{
var mirrored = new Vector2(1, 1).MirrorByDirection(Vector2.Right);

Assert.Equal(1f, mirrored.X, 6);
Assert.Equal(-1f, mirrored.Y, 6);
}

[Fact]
public void MirrorByDirection_ToVectorParallelToYAxis()
{
var mirrored = new Vector2(1, 1).MirrorByDirection(Vector2.Down);

Assert.Equal(-1f, mirrored.X, 6);
Assert.Equal(1f, mirrored.Y, 6);
}

[Fact]
public void MirrorByDirection_ToXEqualYLine()
{
var mirrored = new Vector2(1, 0).MirrorByDirection(Vector2.One * 3);

Assert.Equal(0, mirrored.X, 6);
Assert.Equal(1, mirrored.Y, 6);
}

[Fact]
public void MirrorByDirection_ToVectorParallelToXAxisWithOffset()
{
var mirrored = new Vector2(2, 2).MirrorByDirection(Vector2.One, Vector2.Right);

Assert.Equal(2f, mirrored.X, 6);
Assert.Equal(0f, mirrored.Y, 6);
}

[Fact]
public void MirrorByDirection_ToVectorParallelToYAxisWithOffset()
{
var mirrored = new Vector2(2, 2).MirrorByDirection(Vector2.One, Vector2.Down);

Assert.Equal(0f, mirrored.X, 6);
Assert.Equal(2f, mirrored.Y, 6);
}

[Fact]
public void MirrorByPoints_ToLineParallelToX()
{
var mirrored = new Vector2(2, 2).MirrorByPoints(Vector2.One, Vector2.One + Vector2.Right);

Assert.Equal(2f, mirrored.X, 6);
Assert.Equal(0f, mirrored.Y, 6);
}

[Fact]
public void MirrorByPoints_ToLineParallelToY()
{
var mirrored = new Vector2(2, 2).MirrorByPoints(Vector2.One, Vector2.One + Vector2.Down);

Assert.Equal(0f, mirrored.X, 6);
Assert.Equal(2f, mirrored.Y, 6);
}

[Fact]
public void Rotated_AroundOrigin()
{
var mirrored = Vector2.Right.Rotated(Mathf.Pi);

Assert.Equal(-1f, mirrored.X, 6);
Assert.Equal(0f, mirrored.Y, 6);
}

[Fact]
public void Rotated_AroundPoint()
{
var mirrored = Vector2.One.Rotated(Vector2.Right, Mathf.Pi);

Assert.Equal(1f, mirrored.X, 6);
Assert.Equal(-1f, mirrored.Y, 6);
}
}
Loading

0 comments on commit 4e6a02b

Please sign in to comment.