diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 28708c2e..00b9961a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,7 @@ - Added angle conversion utilities for ranges - Added Ray3.Transformed overloads - Added Ray3.Normalized +- Added Vec.AngleBetweenSigned for 2D vectors ### 5.3.5 - [Base] added IsEmpty/IsEmptyOrNull overloads for Array/ICollection with efficient implementation diff --git a/src/Aardvark.Base/Geometry/Types/Ray/Ray2_auto.cs b/src/Aardvark.Base/Geometry/Types/Ray/Ray2_auto.cs index 587a95b2..ad2403b4 100644 --- a/src/Aardvark.Base/Geometry/Types/Ray/Ray2_auto.cs +++ b/src/Aardvark.Base/Geometry/Types/Ray/Ray2_auto.cs @@ -123,6 +123,11 @@ public readonly Ray2f Reversed get => new Ray2f(Origin, -Direction); } + /// + /// Returns the ray with its directional normalized. + /// + public readonly Ray2f Normalized => new(Origin, Direction.Normalized); + #endregion #region Ray Arithmetics @@ -217,6 +222,14 @@ public readonly float AngleBetweenFast(Ray2f r) public readonly float AngleBetween(Ray2f r) => Direction.AngleBetween(r.Direction); + /// + /// Returns the signed angle between this and the given in radians. + /// The direction vectors of the input rays have to be normalized. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly float AngleBetweenSigned(Ray2f r) + => Direction.AngleBetweenSigned(r.Direction); + /// /// Returns a signed value where left is negative and right positive. /// The magnitude is equal to the float size of the triangle the ray + direction and p. @@ -650,6 +663,11 @@ public readonly Ray2d Reversed get => new Ray2d(Origin, -Direction); } + /// + /// Returns the ray with its directional normalized. + /// + public readonly Ray2d Normalized => new(Origin, Direction.Normalized); + #endregion #region Ray Arithmetics @@ -744,6 +762,14 @@ public readonly double AngleBetweenFast(Ray2d r) public readonly double AngleBetween(Ray2d r) => Direction.AngleBetween(r.Direction); + /// + /// Returns the signed angle between this and the given in radians. + /// The direction vectors of the input rays have to be normalized. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly double AngleBetweenSigned(Ray2d r) + => Direction.AngleBetweenSigned(r.Direction); + /// /// Returns a signed value where left is negative and right positive. /// The magnitude is equal to the double size of the triangle the ray + direction and p. diff --git a/src/Aardvark.Base/Geometry/Types/Ray/Ray2_template.cs b/src/Aardvark.Base/Geometry/Types/Ray/Ray2_template.cs index faea8d8f..0bcca925 100644 --- a/src/Aardvark.Base/Geometry/Types/Ray/Ray2_template.cs +++ b/src/Aardvark.Base/Geometry/Types/Ray/Ray2_template.cs @@ -139,6 +139,11 @@ public readonly __ray2t__ Reversed get => new __ray2t__(Origin, -Direction); } + /// + /// Returns the ray with its directional normalized. + /// + public readonly __ray2t__ Normalized => new(Origin, Direction.Normalized); + #endregion #region Ray Arithmetics @@ -233,6 +238,14 @@ public readonly __ftype__ AngleBetweenFast(__ray2t__ r) public readonly __ftype__ AngleBetween(__ray2t__ r) => Direction.AngleBetween(r.Direction); + /// + /// Returns the signed angle between this and the given in radians. + /// The direction vectors of the input rays have to be normalized. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly __ftype__ AngleBetweenSigned(__ray2t__ r) + => Direction.AngleBetweenSigned(r.Direction); + /// /// Returns a signed value where left is negative and right positive. /// The magnitude is equal to the __ftype__ size of the triangle the ray + direction and p. diff --git a/src/Aardvark.Base/Math/Vectors/Vector_auto.cs b/src/Aardvark.Base/Math/Vectors/Vector_auto.cs index 57c4dacd..b632b8fb 100644 --- a/src/Aardvark.Base/Math/Vectors/Vector_auto.cs +++ b/src/Aardvark.Base/Math/Vectors/Vector_auto.cs @@ -20277,6 +20277,17 @@ public static float AngleBetween(this V2f x, V2f y) return 2 * Fun.Atan2(b.Length, a.Length); } + /// + /// Computes the signed angle between two given vectors in radians. The input vectors have to be normalized. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float AngleBetweenSigned(this V2f x, V2f y) + { + var a = x.X * y.Y - x.Y * y.X; + var b = x.X * y.X + x.Y * y.Y; + return Fun.Atan2(a, b); + } + #endregion #region AnyTiny, AllTiny @@ -26481,6 +26492,17 @@ public static double AngleBetween(this V2d x, V2d y) return 2 * Fun.Atan2(b.Length, a.Length); } + /// + /// Computes the signed angle between two given vectors in radians. The input vectors have to be normalized. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double AngleBetweenSigned(this V2d x, V2d y) + { + var a = x.X * y.Y - x.Y * y.X; + var b = x.X * y.X + x.Y * y.Y; + return Fun.Atan2(a, b); + } + #endregion #region AnyTiny, AllTiny diff --git a/src/Aardvark.Base/Math/Vectors/Vector_template.cs b/src/Aardvark.Base/Math/Vectors/Vector_template.cs index 323f8e0f..4edf540c 100644 --- a/src/Aardvark.Base/Math/Vectors/Vector_template.cs +++ b/src/Aardvark.Base/Math/Vectors/Vector_template.cs @@ -2376,6 +2376,19 @@ public static __ctype__ AngleBetween(this __vtype__ x, __vtype__ y) return 2 * Fun.Atan2(b.Length, a.Length); } + //# if (d == 2) { + /// + /// Computes the signed angle between two given vectors in radians. The input vectors have to be normalized. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static __ctype__ AngleBetweenSigned(this __vtype__ x, __vtype__ y) + { + var a = x.X * y.Y - x.Y * y.X; + var b = x.X * y.X + x.Y * y.Y; + return Fun.Atan2(a, b); + } + + //# } #endregion //# } diff --git a/src/Tests/Aardvark.Base.FSharp.Tests/Aardvark.Base.FSharp.Tests.fsproj b/src/Tests/Aardvark.Base.FSharp.Tests/Aardvark.Base.FSharp.Tests.fsproj index 94770f8d..6153fe97 100644 --- a/src/Tests/Aardvark.Base.FSharp.Tests/Aardvark.Base.FSharp.Tests.fsproj +++ b/src/Tests/Aardvark.Base.FSharp.Tests/Aardvark.Base.FSharp.Tests.fsproj @@ -36,6 +36,7 @@ + diff --git a/src/Tests/Aardvark.Base.FSharp.Tests/Math/MathTests.fs b/src/Tests/Aardvark.Base.FSharp.Tests/Math/MathTests.fs index a7c1b6fd..75f02de5 100644 --- a/src/Tests/Aardvark.Base.FSharp.Tests/Math/MathTests.fs +++ b/src/Tests/Aardvark.Base.FSharp.Tests/Math/MathTests.fs @@ -4,9 +4,48 @@ open Aardvark.Base open NUnit.Framework open FsUnit +open FsCheck +open FsCheck.NUnit +open System module MathTests = + let equalWithin x eps = (new NUnit.Framework.Constraints.EqualConstraint(x)).Within(eps) + + type RotationTestCase2D = + { + Src: V2d + Dst: V2d + Angle: float + } + + module Gen = + let floatUnit = + gen { + let! n = Arb.generate + return float n / float UInt32.MaxValue + } + + let direction2d = + gen { + let! t = floatUnit + return Rot2d(t * Constant.PiTimesTwo) * V2d.XAxis + } + + type Generator private () = + static member AngleTestCase = + gen { + let! t = Gen.floatUnit + let angle = (t - 0.5) * Constant.PiTimesTwo + + let! src = Gen.direction2d + let rot = Rot2d angle + let dst = rot * src + + return { Src = src; Dst = dst; Angle = angle } + } + |> Arb.fromGen + [] let ``[Math] lerp`` () = // Just making sure the parameter order is correct :S @@ -50,4 +89,19 @@ module MathTests = lerp (V2d(50)) (V2d(100)) 0.5 |> should equal (Fun.Lerp(0.5, V2d(50), V2d(100))) lerp (V2d(50)) (V2d(100)) (V2d(0.5)) |> should equal (Fun.Lerp(V2d(0.5), V2d(50), V2d(100))) - lerp (C4d(50.0)) (C4d(100.0)) 0.5 |> should equal (Fun.Lerp(0.5, C4d(50.0), C4d(100.0))) \ No newline at end of file + lerp (C4d(50.0)) (C4d(100.0)) 0.5 |> should equal (Fun.Lerp(0.5, C4d(50.0), C4d(100.0))) + + [ |])>] + let ``[Math] AngleBetween 2D`` (input: RotationTestCase2D) = + let result = Vec.AngleBetween(input.Src, input.Dst) + result |> should be (equalWithin (abs input.Angle) 0.00001) + + [ |])>] + let ``[Math] AngleBetweenFast 2D`` (input: RotationTestCase2D) = + let result = Vec.AngleBetweenFast(input.Src, input.Dst) + result |> should be (equalWithin (abs input.Angle) 0.00001) + + [ |])>] + let ``[Math] AngleBetweenSigned 2D`` (input: RotationTestCase2D) = + let result = Vec.AngleBetweenSigned(input.Src, input.Dst) + result |> should be (equalWithin input.Angle 0.00001) \ No newline at end of file