From ac29cd00e656e73db235965977e6db9f9d2a9f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Mar 2022 19:11:14 +0100 Subject: [PATCH 001/301] fixed Vector2.XY() not being correct --- Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extensions.cs b/Extensions.cs index 5262558..a288473 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -56,7 +56,7 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { #region Swizzling /// Returns X and Y as a Vector2, equivalent to new Vector2(v.x,v.y) - [MethodImpl( INLINE )] public static Vector2 XY( this Vector2 v ) => new Vector2( v.y, v.x ); + [MethodImpl( INLINE )] public static Vector2 XY( this Vector2 v ) => new Vector2( v.x, v.y ); /// Returns Y and X as a Vector2, equivalent to new Vector2(v.y,v.x) [MethodImpl( INLINE )] public static Vector2 YX( this Vector2 v ) => new Vector2( v.y, v.x ); From 53b634e8ec25fd163dbc4f1eac8bfed0b41cbe27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 29 Mar 2022 21:11:26 +0200 Subject: [PATCH 002/301] made extension remaps use base mathfs remap --- Extensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Extensions.cs b/Extensions.cs index a288473..7032a3b 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -467,7 +467,7 @@ public static Rect Encapsulate( this Rect r, Vector2 p ) { [MethodImpl( INLINE )] public static float Remap( this float value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, value ); /// - [MethodImpl( INLINE )] public static float Remap( this int value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.Lerp( oMin, oMax, Mathfs.InverseLerp( iMin, iMax, value ) ); + [MethodImpl( INLINE )] public static float Remap( this int value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, value ); /// [MethodImpl( INLINE )] public static Vector2 Remap( this Vector2 v, Vector2 iMin, Vector2 iMax, Vector2 oMin, Vector2 oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, v ); @@ -476,7 +476,7 @@ public static Rect Encapsulate( this Rect r, Vector2 p ) { [MethodImpl( INLINE )] public static Vector3 Remap( this Vector3 v, Vector3 iMin, Vector3 iMax, Vector3 oMin, Vector3 oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, v ); /// - [MethodImpl( INLINE )] public static Vector4 Remap( this Vector4 v, Vector4 iMin, Vector4 iMax, Vector4 oMin, Vector4 oMax ) => Mathfs.Lerp( oMin, oMax, Mathfs.InverseLerp( iMin, iMax, v ) ); + [MethodImpl( INLINE )] public static Vector4 Remap( this Vector4 v, Vector4 iMin, Vector4 iMax, Vector4 oMin, Vector4 oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, v ); /// [MethodImpl( INLINE )] public static Vector2 Remap( this Vector2 iPos, Rect iRect, Rect oRect ) => Mathfs.Remap( iRect.min, iRect.max, oRect.min, oRect.max, iPos ); From 151ed83f13f772215b11b3735fe4b1a780c48ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 29 Mar 2022 21:12:32 +0200 Subject: [PATCH 003/301] added int remap clamped extension --- Extensions.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Extensions.cs b/Extensions.cs index 7032a3b..f1b217d 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -466,9 +466,14 @@ public static Rect Encapsulate( this Rect r, Vector2 p ) { /// [MethodImpl( INLINE )] public static float Remap( this float value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, value ); + /// + [MethodImpl( INLINE )] public static float RemapClamped( this float value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.RemapClamped( iMin, iMax, oMin, oMax, value ); + /// [MethodImpl( INLINE )] public static float Remap( this int value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, value ); + /// + [MethodImpl( INLINE )] public static float RemapClamped( this int value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.RemapClamped( iMin, iMax, oMin, oMax, value ); /// [MethodImpl( INLINE )] public static Vector2 Remap( this Vector2 v, Vector2 iMin, Vector2 iMax, Vector2 oMin, Vector2 oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, v ); @@ -484,9 +489,6 @@ public static Rect Encapsulate( this Rect r, Vector2 p ) { /// [MethodImpl( INLINE )] public static Vector3 Remap( this Vector3 iPos, Bounds iBounds, Bounds oBounds ) => Mathfs.Remap( iBounds.min, iBounds.max, oBounds.min, oBounds.max, iPos ); - /// - [MethodImpl( INLINE )] public static float RemapClamped( this float value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.Lerp( oMin, oMax, Mathfs.InverseLerpClamped( iMin, iMax, value ) ); - #endregion #region Vector Math From ffdc91e364ccceba7f885da4bffdd54903016b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 29 Mar 2022 21:12:49 +0200 Subject: [PATCH 004/301] added lerp and inverse lerp float ext methods --- Extensions.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index f1b217d..2277b7f 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -474,6 +474,19 @@ public static Rect Encapsulate( this Rect r, Vector2 p ) { /// [MethodImpl( INLINE )] public static float RemapClamped( this int value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.RemapClamped( iMin, iMax, oMin, oMax, value ); + + /// + [MethodImpl( INLINE )] public static float Lerp( this float t, float a, float b ) => Mathfs.Lerp( a, b, t ); + + /// + [MethodImpl( INLINE )] public static float InverseLerp( this float value, float a, float b ) => Mathfs.InverseLerp( a, b, value ); + + /// + [MethodImpl( INLINE )] public static float LerpClamped( this float t, float a, float b ) => Mathfs.LerpClamped( a, b, t ); + + /// + [MethodImpl( INLINE )] public static float InverseLerpClamped( this float value, float a, float b ) => Mathfs.InverseLerpClamped( a, b, value ); + /// [MethodImpl( INLINE )] public static Vector2 Remap( this Vector2 v, Vector2 iMin, Vector2 iMax, Vector2 oMin, Vector2 oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, v ); From 7ee28f8b163a96afdb18322bc6a6b0bddfb8b217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 1 Apr 2022 09:42:31 +0200 Subject: [PATCH 005/301] moved splines to a spline folder --- {Curves => Splines}/BSpline2D.cs | 0 {Curves => Splines}/Nurbs2D.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {Curves => Splines}/BSpline2D.cs (100%) rename {Curves => Splines}/Nurbs2D.cs (100%) diff --git a/Curves/BSpline2D.cs b/Splines/BSpline2D.cs similarity index 100% rename from Curves/BSpline2D.cs rename to Splines/BSpline2D.cs diff --git a/Curves/Nurbs2D.cs b/Splines/Nurbs2D.cs similarity index 100% rename from Curves/Nurbs2D.cs rename to Splines/Nurbs2D.cs From 7d4d13406bef0e518840811c74412377fd4422d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 1 Apr 2022 09:43:35 +0200 Subject: [PATCH 006/301] formatting and comment cleanup --- Curves/CatRom2D.cs | 1 - Curves/CatRom3D.cs | 1 - Curves/CatRomType.cs | 1 - Curves/SplineUtils.cs | 4 ++-- Curves/Trajectory2D.cs | 2 +- Curves/UBSCubic2D.cs | 3 +-- 6 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Curves/CatRom2D.cs b/Curves/CatRom2D.cs index e76b03b..f97a09f 100644 --- a/Curves/CatRom2D.cs +++ b/Curves/CatRom2D.cs @@ -1,5 +1,4 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -// a lot of stuff here made possible by this excellent writeup on bezier curves: https://pomax.github.io/bezierinfo/ using System; using System.Runtime.CompilerServices; diff --git a/Curves/CatRom3D.cs b/Curves/CatRom3D.cs index 6f34c70..9c8f3ea 100644 --- a/Curves/CatRom3D.cs +++ b/Curves/CatRom3D.cs @@ -1,5 +1,4 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -// a lot of stuff here made possible by this excellent writeup on bezier curves: https://pomax.github.io/bezierinfo/ using System; using System.Runtime.CompilerServices; diff --git a/Curves/CatRomType.cs b/Curves/CatRomType.cs index 32a407b..351ce13 100644 --- a/Curves/CatRomType.cs +++ b/Curves/CatRomType.cs @@ -1,5 +1,4 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -// a lot of stuff here made possible by this excellent writeup on bezier curves: https://pomax.github.io/bezierinfo/ namespace Freya { diff --git a/Curves/SplineUtils.cs b/Curves/SplineUtils.cs index ffd4e15..1ae546a 100644 --- a/Curves/SplineUtils.cs +++ b/Curves/SplineUtils.cs @@ -73,7 +73,7 @@ public static Vector4 GetBernsteinPolynomialWeightsDerivative( float t ) { public static Vector4 GetBernsteinPolynomialWeightsSecondDerivative( float t ) { return new Vector4( 6 - 6 * t, 18 * t - 12, 6 - 18 * t, 6 * t ); } - + /// Samples a bernstein polynomial bézier basis function /// The degree of the bézier curve /// The basis function index @@ -81,7 +81,7 @@ public static Vector4 GetBernsteinPolynomialWeightsSecondDerivative( float t ) { public static float SampleBasisFunction( int degree, int i, float t ) { ulong bc = Mathfs.BinomialCoef( (uint)degree, (uint)i ); double scale = Math.Pow( 1f - t, degree - i ) * Math.Pow( t, i ); - return (float)(bc * scale); + return (float)( bc * scale ); } public static float[] GenerateUniformKnots( int degree, int pCount, bool open ) { diff --git a/Curves/Trajectory2D.cs b/Curves/Trajectory2D.cs index 2a1c141..b72ce36 100644 --- a/Curves/Trajectory2D.cs +++ b/Curves/Trajectory2D.cs @@ -1,4 +1,4 @@ -// collected and expended upon by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using UnityEngine; using static Freya.Mathfs; diff --git a/Curves/UBSCubic2D.cs b/Curves/UBSCubic2D.cs index e2887d4..98fe509 100644 --- a/Curves/UBSCubic2D.cs +++ b/Curves/UBSCubic2D.cs @@ -95,12 +95,11 @@ public Vector2 this[ int i ] { c2.x = 0.5f * ( p0.x - 2 * p1.x + p2.x ); c1.x = 0.5f * ( -p0.x + p2.x ); c0.x = _6th * ( p0.x + 4 * p1.x + p2.x ); - + c3.y = _6th * ( -p0.y + 3 * ( p1.y - p2.y ) + p3.y ); c2.y = 0.5f * ( p0.y - 2 * p1.y + p2.y ); c1.y = 0.5f * ( -p0.y + p2.y ); c0.y = _6th * ( p0.y + 4 * p1.y + p2.y ); - } /// The constant coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 From e77916ba95ccc8dc0900197ddb9dd8275e466247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 9 May 2022 16:17:31 +0200 Subject: [PATCH 007/301] added polynomial scaling operator overloads --- Curves/Polynomial.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index 72d8635..424ebab 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -206,6 +206,10 @@ static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { #endregion + public static Polynomial operator /( Polynomial p, float v ) => new(p.fCubic / v, p.fQuadratic / v, p.fLinear / v, p.fConstant / v); + public static Polynomial operator /( float v, Polynomial p ) => new(v / p.fCubic, v / p.fQuadratic, v / p.fLinear, v / p.fConstant); + public static Polynomial operator *( Polynomial p, float v ) => new(p.fCubic * v, p.fQuadratic * v, p.fLinear * v, p.fConstant * v); + public static Polynomial operator *( float v, Polynomial p ) => p * v; } From 789534ce1995f96154ee8f38ad1dc15ed856e1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 9 May 2022 16:17:53 +0200 Subject: [PATCH 008/301] added polynomial/polynomial linear interpolation --- Curves/Polynomial.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index 424ebab..bd7030e 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -128,6 +128,19 @@ public static ResultsMax2 GetQuadraticRoots( float a, float b, float c ) return -b / a; } + + /// Linearly interpolates between two polynomials + /// The first polynomial to blend from + /// The second polynomial to blend to + /// The blend value, typically from 0 to 1 + public static Polynomial Lerp( Polynomial a, Polynomial b, float t ) => + new( + t.Lerp( a.fCubic, b.fCubic ), + t.Lerp( a.fQuadratic, b.fQuadratic ), + t.Lerp( a.fLinear, b.fLinear ), + t.Lerp( a.fConstant, b.fConstant ) + ); + #region Internal root solvers // These functions lack safety checks (division by zero etc.) for lower degree equivalency - they presume "a" is always nonzero. From cbb51d90a08039c5703157408928d899089892ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 9 May 2022 16:19:30 +0200 Subject: [PATCH 009/301] added GetBasisFunctions to uniform cubic bspline --- Curves/UBSCubic2D.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Curves/UBSCubic2D.cs b/Curves/UBSCubic2D.cs index 98fe509..48557df 100644 --- a/Curves/UBSCubic2D.cs +++ b/Curves/UBSCubic2D.cs @@ -203,6 +203,18 @@ public BezierCubic2D ToBezier() { ); } + /// Get the basis function for the given point, by index + /// The index of the point (0, 1, 2 or 3) + public static Polynomial GetBasisFunction( int i ) { + return i switch { + 0 => new Polynomial( -1, 3, -3, 1 ) / 6f, + 1 => new Polynomial( 3, -6, 0, 4 ) / 6f, + 2 => new Polynomial( -3, 3, 3, 1 ) / 6f, + 3 => new Polynomial( 1, 0, 0, 0 ) / 6f, + _ => throw new IndexOutOfRangeException( "Cubic B-Spline index needs to be between 0 and 3" ) + }; + } + } } \ No newline at end of file From 799451f402e0ae38fa72d8b82866b867291ff29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 11 May 2022 20:51:09 +0200 Subject: [PATCH 010/301] polynomial terminology changes --- Curves/Polynomial.cs | 84 ++++++++++++------- ...{PolynomialType.cs => PolynomialDegree.cs} | 2 +- 2 files changed, 53 insertions(+), 33 deletions(-) rename Curves/{PolynomialType.cs => PolynomialDegree.cs} (93%) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index bd7030e..c113ede 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -12,22 +12,44 @@ [Serializable] public struct Polynomial { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// The cubic factor. [fCubed]x³+bx²+cx+d + /// The cubic coefficient. [fCubic]x³+bx²+cx+d public float fCubic; - /// The quadratic factor. [fQuadratic]x²+cx+d + /// The quadratic coefficient. [fQuadratic]x²+cx+d public float fQuadratic; - /// The linear factor. [fLinear]x+d + /// The linear coefficient. [fLinear]x+d public float fLinear; - /// The constant factor. ax+[fConstant] + /// The constant coefficient. ax+[fConstant] public float fConstant; - /// The type of polynomial - public PolynomialType Type => GetPolynomialType( fCubic, fQuadratic, fLinear, fConstant ); + /// Get or set the coefficient of the given degree + /// The degree of the coefficient you want to get/set. For example, 0 will return the constant coefficient, 3 will return the cubic coefficient + public float this[ int degree ] { + get => + degree switch { + 0 => fConstant, + 1 => fLinear, + 2 => fQuadratic, + 3 => fCubic, + _ => throw new IndexOutOfRangeException( "Polynomial factor degree has to be between 0 and 3" ) + }; + set { + _ = degree switch { + 0 => fConstant = value, + 1 => fLinear = value, + 2 => fQuadratic = value, + 3 => fCubic = value, + _ => throw new IndexOutOfRangeException( "Polynomial factor degree has to be between 0 and 3" ) + }; + } + } - /// Creates a polynomial of the form ax+b + /// The degree of the polynomial + public PolynomialDegree Degree => GetPolynomialDegree( fCubic, fQuadratic, fLinear, fConstant ); + + /// Creates a linear polynomial of the form ax+b /// The linear factor a in ax+b /// The constant factor b in ax+b public Polynomial( float a, float b ) { @@ -36,7 +58,7 @@ public Polynomial( float a, float b ) { fConstant = b; } - /// Creates a polynomial of the form ax²+bx+c + /// Creates a quadratic polynomial of the form ax²+bx+c /// The quadratic factor a in ax²+bx+c /// The linear factor b in ax²+bx+c /// The constant factor c in ax²+bx+c @@ -47,7 +69,7 @@ public Polynomial( float a, float b, float c ) { fConstant = c; } - /// Creates a polynomial of the form ax³+bx²+cx+d + /// Creates a cubic polynomial of the form ax³+bx²+cx+d /// The cubic factor a in ax³+bx²+cx+d /// The quadratic factor b in ax³+bx²+cx+d /// The linear factor c in ax³+bx²+cx+d @@ -78,52 +100,50 @@ public Polynomial( float a, float b, float c, float d ) { /// The quadratic factor b in ax³+bx²+cx+d /// The linear factor c in ax³+bx²+cx+d /// The constant factor d in ax³+bx²+cx+d - [MethodImpl( INLINE )] public static PolynomialType GetPolynomialType( float a, float b, float c, float d ) => FactorAlmost0( a ) ? GetPolynomialType( b, c, d ) : PolynomialType.Cubic; + [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float a, float b, float c, float d ) => FactorAlmost0( a ) ? GetPolynomialDegree( b, c, d ) : PolynomialDegree.Cubic; - /// Given ax²+bx+c, returns the net polynomial type/degree, accounting for values very close to 0 + /// Given ax²+bx+c, returns the net polynomial degree, accounting for values very close to 0 /// The quadratic factor a in ax²+bx+c /// The linear factor b in ax²+bx+c /// The constant factor c in ax²+bx+c - [MethodImpl( INLINE )] public static PolynomialType GetPolynomialType( float a, float b, float c ) => FactorAlmost0( a ) ? GetPolynomialType( b, c ) : PolynomialType.Quadratic; + [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float a, float b, float c ) => FactorAlmost0( a ) ? GetPolynomialDegree( b, c ) : PolynomialDegree.Quadratic; - /// Given ax+b, returns the net polynomial type/degree, accounting for values very close to 0 + /// Given ax+b, returns the net polynomial degree, accounting for values very close to 0 /// The linear factor a in ax+b /// The constant factor b in ax+b - [MethodImpl( INLINE )] public static PolynomialType GetPolynomialType( float a, float b ) => FactorAlmost0( a ) ? PolynomialType.Constant : PolynomialType.Linear; + [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float a, float b ) => FactorAlmost0( a ) ? PolynomialDegree.Constant : PolynomialDegree.Linear; /// Returns the roots/solutions of ax³+bx²+cx+d = 0. There's either 0, 1, 2 or 3 roots, filled in left to right among the return values /// The cubic factor a in ax³+bx²+cx+d /// The quadratic factor b in ax³+bx²+cx+d /// The linear factor c in ax³+bx²+cx+d /// The constant factor d in ax³+bx²+cx+d - public static ResultsMax3 GetCubicRoots( float a, float b, float c, float d ) { - switch( GetPolynomialType( a, b, c, d ) ) { - case PolynomialType.Constant: return default; // either no roots or infinite roots if c == 0 - case PolynomialType.Linear: return new ResultsMax3( SolveLinearRoot( c, d ) ); - case PolynomialType.Quadratic: return SolveQuadraticRoots( b, c, d ); - case PolynomialType.Cubic: return SolveCubicRoots( a, b, c, d ); - default: throw new InvalidEnumArgumentException(); - } - } + public static ResultsMax3 GetCubicRoots( float a, float b, float c, float d ) => + GetPolynomialDegree( a, b, c, d ) switch { + PolynomialDegree.Constant => default, // either no roots or infinite roots if c == 0 + PolynomialDegree.Linear => new ResultsMax3( SolveLinearRoot( c, d ) ), + PolynomialDegree.Quadratic => SolveQuadraticRoots( b, c, d ), + PolynomialDegree.Cubic => SolveCubicRoots( a, b, c, d ), + _ => throw new InvalidEnumArgumentException() + }; /// Returns the roots/solutions of ax²+bx+c = 0. There's either 0, 1 or 2 roots, filled in left to right among the return values /// The quadratic factor a in ax²+bx+c /// The linear factor b in ax²+bx+c /// The constant factor c in ax²+bx+c - public static ResultsMax2 GetQuadraticRoots( float a, float b, float c ) { - switch( GetPolynomialType( a, b, c ) ) { - case PolynomialType.Constant: return default; // either no roots or infinite roots if c == 0 - case PolynomialType.Linear: return new ResultsMax2( SolveLinearRoot( b, c ) ); - case PolynomialType.Quadratic: return SolveQuadraticRoots( a, b, c ); - default: throw new InvalidEnumArgumentException(); - } - } + public static ResultsMax2 GetQuadraticRoots( float a, float b, float c ) => + GetPolynomialDegree( a, b, c ) switch { + PolynomialDegree.Constant => default, // either no roots or infinite roots if c == 0 + PolynomialDegree.Linear => new ResultsMax2( SolveLinearRoot( b, c ) ), + PolynomialDegree.Quadratic => SolveQuadraticRoots( a, b, c ), + _ => throw new InvalidEnumArgumentException() + }; /// Returns the root/solution of ax+b = 0. Returns null if there is no root /// The linear factor a in ax+b /// The constant factor b in ax+b public static float? GetLinearRoots( float a, float b ) { - if( GetPolynomialType( a, b ) == PolynomialType.Constant ) + if( GetPolynomialDegree( a, b ) == PolynomialDegree.Constant ) return null; return -b / a; } diff --git a/Curves/PolynomialType.cs b/Curves/PolynomialDegree.cs similarity index 93% rename from Curves/PolynomialType.cs rename to Curves/PolynomialDegree.cs index c794b3b..47e75be 100644 --- a/Curves/PolynomialType.cs +++ b/Curves/PolynomialDegree.cs @@ -3,7 +3,7 @@ namespace Freya { /// The type/degree of a polynomial - public enum PolynomialType { + public enum PolynomialDegree { /// A polynomial that is just a, straight up constant value Constant, From 1b2f89c69ac3830bea0350910c05bd4fcadca14f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 12 May 2022 14:29:51 +0200 Subject: [PATCH 011/301] added FloatRange --- FloatRange.cs | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 FloatRange.cs diff --git a/FloatRange.cs b/FloatRange.cs new file mode 100644 index 0000000..6d756f5 --- /dev/null +++ b/FloatRange.cs @@ -0,0 +1,74 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using UnityEngine; + +namespace Freya { + + /// A value range between two values a and b + public readonly struct FloatRange { + + /// The start of this range + public readonly float a; + + /// The end of this range + public readonly float b; + + /// Creates a new value range + /// The start of the range + /// The end of the range + public FloatRange( float a, float b ) => ( this.a, this.b ) = ( a, b ); + + /// The value at the center of this value range + public float Center => ( a + b ) / 2; + + /// The length/span of this value range + public float Length => Mathf.Abs( b - a ); + + /// The minimum value of this range + public float Min => Mathf.Min( a, b ); + + /// The maximum value of this range + public float Max => Mathf.Max( a, b ); + + /// The direction of this value range. Returns -1 if b is greater than a, otherwise returns 1 + public int Sign => b > a ? 1 : -1; + + /// Interpolates a value from a to b, based on a parameter t + /// The normalized interpolant from a to b. A value of 0 returns a, a value of 1 returns b + public float Lerp( float t ) => Mathfs.Lerp( a, b, t ); + + /// Returns the normalized position of the input value v within this range + /// The value to get the normalized position of + public float InverseLerp( float v ) => Mathfs.InverseLerp( a, b, v ); + + /// Returns whether or not this range contains the value v + /// The value to see if it's inside + public bool Contains( float v ) => v >= Min && v <= Max; + + /// Remaps the input value from the input range to the output range + /// The value to remap + /// The input range + /// The output range + public static float Remap( float value, FloatRange input, FloatRange output ) => output.Lerp( input.InverseLerp( value ) ); + + /// Returns whether or not this range overlaps another range + /// The other range to test overlap with + public bool Overlaps( FloatRange other ) { + float separation = Mathfs.Abs( other.Center - Center ); + float rTotal = ( other.Length + Length ) / 2; + return separation < rTotal; + } + + /// Expands the minimum or maximum value to contain the given value + /// The value to include + public FloatRange Encapsulate( float value ) => + Sign switch { + 1 => ( Mathf.Min( a, value ), Mathf.Max( b, value ) ), // forward - a is min, b is max + _ => ( Mathf.Min( b, value ), Mathf.Max( a, value ) ) // reversed - b is min, a is max + }; + + public static implicit operator FloatRange( (float a, float b) tuple ) => new FloatRange( tuple.a, tuple.b ); + + } + +} \ No newline at end of file From e143098a3d69fd07a5a007f96bdaafcc4198eb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 12 May 2022 14:32:13 +0200 Subject: [PATCH 012/301] minor cleanup --- FloatRange.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/FloatRange.cs b/FloatRange.cs index 6d756f5..7bb930b 100644 --- a/FloatRange.cs +++ b/FloatRange.cs @@ -1,7 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -using UnityEngine; - namespace Freya { /// A value range between two values a and b @@ -22,13 +20,13 @@ public readonly struct FloatRange { public float Center => ( a + b ) / 2; /// The length/span of this value range - public float Length => Mathf.Abs( b - a ); + public float Length => Mathfs.Abs( b - a ); /// The minimum value of this range - public float Min => Mathf.Min( a, b ); + public float Min => Mathfs.Min( a, b ); /// The maximum value of this range - public float Max => Mathf.Max( a, b ); + public float Max => Mathfs.Max( a, b ); /// The direction of this value range. Returns -1 if b is greater than a, otherwise returns 1 public int Sign => b > a ? 1 : -1; @@ -63,8 +61,8 @@ public bool Overlaps( FloatRange other ) { /// The value to include public FloatRange Encapsulate( float value ) => Sign switch { - 1 => ( Mathf.Min( a, value ), Mathf.Max( b, value ) ), // forward - a is min, b is max - _ => ( Mathf.Min( b, value ), Mathf.Max( a, value ) ) // reversed - b is min, a is max + 1 => ( Mathfs.Min( a, value ), Mathfs.Max( b, value ) ), // forward - a is min, b is max + _ => ( Mathfs.Min( b, value ), Mathfs.Max( a, value ) ) // reversed - b is min, a is max }; public static implicit operator FloatRange( (float a, float b) tuple ) => new FloatRange( tuple.a, tuple.b ); From 24f80fa0c2331be47861a7d8e1f724cd56f5393e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 12 May 2022 14:32:40 +0200 Subject: [PATCH 013/301] renamed sign to direction --- FloatRange.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FloatRange.cs b/FloatRange.cs index 7bb930b..66bbab0 100644 --- a/FloatRange.cs +++ b/FloatRange.cs @@ -29,7 +29,7 @@ public readonly struct FloatRange { public float Max => Mathfs.Max( a, b ); /// The direction of this value range. Returns -1 if b is greater than a, otherwise returns 1 - public int Sign => b > a ? 1 : -1; + public int Direction => b > a ? 1 : -1; /// Interpolates a value from a to b, based on a parameter t /// The normalized interpolant from a to b. A value of 0 returns a, a value of 1 returns b @@ -60,7 +60,7 @@ public bool Overlaps( FloatRange other ) { /// Expands the minimum or maximum value to contain the given value /// The value to include public FloatRange Encapsulate( float value ) => - Sign switch { + Direction switch { 1 => ( Mathfs.Min( a, value ), Mathfs.Max( b, value ) ), // forward - a is min, b is max _ => ( Mathfs.Min( b, value ), Mathfs.Max( a, value ) ) // reversed - b is min, a is max }; From 6e57d8506b1d193d3d90d2ba9ebd92f5c0e56f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 14 May 2022 18:19:09 +0200 Subject: [PATCH 014/301] started massive refactor of splines uniform cubics now have a property called Curve, and lots of functions have been lifted out into Polynomial types instead --- Curves/Bezier2D.cs | 4 +- Curves/Bezier3D.cs | 2 +- Curves/BezierCubic2D.cs | 476 +++------------------------------------- Curves/BezierCubic3D.cs | 390 +------------------------------- Curves/BezierQuad2D.cs | 6 +- Curves/BezierQuad3D.cs | 6 +- Curves/BezierSampler.cs | 4 +- Curves/CharMatrix.cs | 68 ++++++ Curves/Hermite2D.cs | 8 +- Curves/IParamCurve.cs | 105 ++++----- Curves/Polynomial.cs | 140 +++++++----- Curves/Polynomial2D.cs | 254 +++++++++++++++++++++ Curves/Polynomial3D.cs | 124 +++++++++++ Curves/SplineUtils.cs | 70 +----- Curves/UBSCubic2D.cs | 8 +- FloatRange.cs | 17 ++ UtilityTypes.cs | 13 ++ 17 files changed, 681 insertions(+), 1014 deletions(-) create mode 100644 Curves/CharMatrix.cs create mode 100644 Curves/Polynomial2D.cs create mode 100644 Curves/Polynomial3D.cs diff --git a/Curves/Bezier2D.cs b/Curves/Bezier2D.cs index 7b541a2..63ae859 100644 --- a/Curves/Bezier2D.cs +++ b/Curves/Bezier2D.cs @@ -46,7 +46,7 @@ public int Degree { [MethodImpl( INLINE )] get => points.Length - 1; } - public Vector2 GetPoint( float t ) { + public Vector2 Eval( float t ) { float n = Count - 1; for( int i = 0; i < n; i++ ) ptEvalBuffer[i] = Vector2.LerpUnclamped( points[i], points[i + 1], t ); @@ -73,7 +73,7 @@ Vector2 B( int k, int i ) { public float GetPointWeight( int i, float t ) { if(i < 0 || i >= Count) throw new IndexOutOfRangeException($"GetPointWeight index {i} is out of range. Valid range is 0 to {Count-1}"); - return SplineUtils.SampleBasisFunction( Degree, i, t ); + return SplineUtils.SampleBernsteinBasisFunction( Degree, i, t ); } /// Returns the derivative bezier curve if possible, otherwise returns null diff --git a/Curves/Bezier3D.cs b/Curves/Bezier3D.cs index 8db6c89..b85f7e6 100644 --- a/Curves/Bezier3D.cs +++ b/Curves/Bezier3D.cs @@ -38,7 +38,7 @@ public int Degree { [MethodImpl( INLINE )] get => points.Length - 1; } - public Vector3 GetPoint( float t ) { + public Vector3 Eval( float t ) { return B( Degree, 0 ); Vector3 B( int k, int i ) { diff --git a/Curves/BezierCubic2D.cs b/Curves/BezierCubic2D.cs index 0b259ec..b6838fe 100644 --- a/Curves/BezierCubic2D.cs +++ b/Curves/BezierCubic2D.cs @@ -4,16 +4,11 @@ using System; using System.Runtime.CompilerServices; using UnityEngine; -using static Freya.Mathfs; namespace Freya { - // Bezier math - // A lot of the following code is unrolled into floats and components for performance reasons. - // It's much faster than keeping the more readable function calls and vector types unfortunately - /// An optimized 2D cubic bezier curve, with 4 control points - [Serializable] public struct BezierCubic2D : IParamCurve3Diff { + [Serializable] public struct BezierCubic2D : IParamCubicSplineSegment2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -25,7 +20,15 @@ namespace Freya { public BezierCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); validCoefficients = false; - c3 = c2 = c1 = default; + curve = default; + } + + Polynomial2D curve; + public Polynomial2D Curve { + get { + ReadyCoefficients(); + return curve; + } } #region Control Points @@ -91,60 +94,17 @@ public Vector2 this[ int i ] { #region Coefficients [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) - [NonSerialized] Vector2 c3, c2, c1; // cached coefficients for fast evaluation. c0 = p0 // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update validCoefficients = true; - c3.x = 3 * ( p1.x - p2.x ) + ( p3.x - p0.x ); - c2.x = 3 * ( p0.x - p1.x + p2.x - p1.x ); - c1.x = 3 * ( p1.x - p0.x ); - c3.y = 3 * ( p1.y - p2.y ) + ( p3.y - p0.y ); - c2.y = 3 * ( p0.y - p1.y + p2.y - p1.y ); - c1.y = 3 * ( p1.y - p0.y ); - } - - /// The constant coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C0 { - [MethodImpl( INLINE )] get => p0; - } - - /// The linear coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C1 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c1; - } - } - - /// The quadratic coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C2 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c2; - } - } - - /// The cubic coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C3 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c3; - } - } - - /// The polynomial coefficients in the form c3*t³ + c2*t² + c1*t + c0 - [MethodImpl( INLINE )] public (Vector2 c3, Vector2 c2, Vector2 c1, Vector2 c0) GetCoefficients() { - ReadyCoefficients(); - return ( c3, c2, c1, p0 ); + curve = CharMatrix.cubicBezier.GetCurve( p0, p1, p2, p3 ); } #endregion - // Object comparison stuff - #region Object Comparison & ToString public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; @@ -176,78 +136,6 @@ public static explicit operator BezierCubic3D( BezierCubic2D bezierCubic2D ) { #endregion - // Base properties - Points, Derivatives & Tangents - - #region Core IParamCurve Implementations - - public int Degree { - [MethodImpl( INLINE )] get => 3; - } - public int Count { - [MethodImpl( INLINE )] get => 4; - } - - [MethodImpl( INLINE )] public Vector2 GetStartPoint() => p0; - [MethodImpl( INLINE )] public Vector2 GetEndPoint() => p3; - - [MethodImpl( INLINE )] public Vector2 GetPoint( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return new Vector2( t3 * c3.x + t2 * c2.x + t * c1.x + p0.x, t3 * c3.y + t2 * c2.y + t * c1.y + p0.y ); - } - - [MethodImpl( INLINE )] public Vector2 GetDerivative( float t ) { - ReadyCoefficients(); - float t2 = t * t; - return new Vector2( 3 * t2 * c3.x + 2 * t * c2.x + c1.x, 3 * t2 * c3.y + 2 * t * c2.y + c1.y ); - } - - [MethodImpl( INLINE )] public Vector2 GetSecondDerivative( float t ) { - ReadyCoefficients(); - return new Vector2( 6 * t * c3.x + 2 * c2.x, 6 * t * c3.y + 2 * c2.y ); - } - - [MethodImpl( INLINE )] public Vector2 GetThirdDerivative( float t = 0 ) { - ReadyCoefficients(); - return new Vector2( 6 * c3.x, 6 * c3.y ); - } - - #endregion - - #region Point Components - - /// Returns the X coordinate at the given t-value on the curve - /// The t-value along the curve to sample - [MethodImpl( INLINE )] public float GetPointX( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return t3 * c3.x + t2 * c2.x + t * c1.x + p0.x; - } - - /// Returns the Y coordinate at the given t-value on the curve - /// The t-value along the curve to sample - [MethodImpl( INLINE )] public float GetPointY( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return t3 * c3.y + t2 * c2.y + t * c1.y + p0.y; - } - - /// Returns a component of the coordinate at the given t-value on the curve - /// Which component of the coordinate to return. 0 is X, 1 is Y - /// The t-value along the curve to sample - public float GetPointComponent( int component, float t ) { - switch( component ) { - case 0: return GetPointX( t ); - case 1: return GetPointY( t ); - default: throw new ArgumentOutOfRangeException( nameof(component), "component has to be either 0 or 1" ); - } - } - - #endregion - // Whole-curve properties & functions #region Interpolation @@ -311,338 +199,34 @@ public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { #endregion - #region Bounds - /// Returns the tight axis-aligned bounds of the curve - public Rect GetBounds() { - // first and last points are always included - Vector2 min = Vector2.Min( P0, P3 ); - Vector2 max = Vector2.Max( P0, P3 ); + #region Conversion - void Encapsulate( int axis, float value ) { - min[axis] = Min( min[axis], value ); - max[axis] = Max( max[axis], value ); - } - - for( int i = 0; i < 2; i++ ) { - ResultsMax2 extrema = GetLocalExtremaPoints( i ); - for( int j = 0; j < extrema.count; j++ ) - Encapsulate( i, extrema[j] ); - } - - return new Rect( min.x, min.y, max.x - min.x, max.y - min.y ); - } - - #endregion - - #region Project Point - - /// Returns the (approximate) point on the curve closest to the input point - /// The point to project against the curve - /// Recommended range: [8-32]. More subdivisions will be more accurate, but more expensive. - /// This is how many subdivisions to split the curve into, to find candidates for the closest point. - /// If your curves are complex, you might need to use around 16 subdivisions. - /// If they are usually very simple, then around 8 subdivisions is likely fine - /// Recommended range: [3-6]. More iterations will be more accurate, but more expensive. - /// This is how many times to refine the initial guesses, using Newton's method. This converges rapidly, so high numbers are generally not necessary - public Vector2 ProjectPoint( Vector2 point, int initialSubdivisions = 16, int refinementIterations = 4 ) => ProjectPoint( point, out _, initialSubdivisions, refinementIterations ); - - struct PointProjectSample { - public float t; - public float distDeltaSq; - public Vector2 f; - public Vector2 fp; - } - - static PointProjectSample[] pointProjectGuesses = { default, default, default }; - - /// Returns the (approximate) point on the curve closest to the input point - /// The point to project against the curve - /// The t-value at the projected point on the curve - /// Recommended range: [8-32]. More subdivisions will be more accurate, but more expensive. - /// This is how many subdivisions to split the curve into, to find candidates for the closest point. - /// If your curves are complex, you might need to use around 16 subdivisions. - /// If they are usually very simple, then around 8 subdivisions is likely fine - /// Recommended range: [3-6]. More iterations will be more accurate, but more expensive. - /// This is how many times to refine the initial guesses, using Newton's method. This converges rapidly, so high numbers are generally not necessary - public Vector2 ProjectPoint( Vector2 point, out float t, int initialSubdivisions = 16, int refinementIterations = 4 ) { - // define a bezier relative to the test point - BezierCubic2D bez = new BezierCubic2D( P0 - point, P1 - point, P2 - point, P3 - point ); - - PointProjectSample SampleDistSqDelta( float tSmp ) { - PointProjectSample s = new PointProjectSample { t = tSmp }; - s.f = bez.GetPoint( tSmp ); - s.fp = bez.GetDerivative( tSmp ); - s.distDeltaSq = Vector2.Dot( s.f, s.fp ); - return s; - } - - // find initial candidates - int candidatesFound = 0; - PointProjectSample prevSmp = SampleDistSqDelta( 0 ); - - for( int i = 1; i < initialSubdivisions; i++ ) { - float ti = i / ( initialSubdivisions - 1f ); - PointProjectSample smp = SampleDistSqDelta( ti ); - if( SignAsInt( smp.distDeltaSq ) != SignAsInt( prevSmp.distDeltaSq ) ) { - pointProjectGuesses[candidatesFound++] = SampleDistSqDelta( ( prevSmp.t + smp.t ) / 2 ); - if( candidatesFound == 3 ) break; // no more than three possible candidates because of the polynomial degree - } - - prevSmp = smp; - } - - // refine each guess w. Newton-Raphson iterations - void Refine( ref PointProjectSample smp ) { - Vector2 fpp = bez.GetSecondDerivative( smp.t ); - float tNew = smp.t - Vector2.Dot( smp.f, smp.fp ) / ( Vector2.Dot( smp.f, fpp ) + Vector2.Dot( smp.fp, smp.fp ) ); - smp = SampleDistSqDelta( tNew ); - } - - for( int p = 0; p < candidatesFound; p++ ) - for( int i = 0; i < refinementIterations; i++ ) - Refine( ref pointProjectGuesses[p] ); - - // Now find closest. First include the endpoints - float sqDist0 = bez.P0.sqrMagnitude; // include endpoints - float sqDist1 = bez.P3.sqrMagnitude; - bool firstClosest = sqDist0 < sqDist1; - float tClosest = firstClosest ? 0 : 1; - Vector2 ptClosest = firstClosest ? P0 : P3; - float distSqClosest = firstClosest ? sqDist0 : sqDist1; - - // then check internal roots - for( int i = 0; i < candidatesFound; i++ ) { - float pSqmag = pointProjectGuesses[i].f.sqrMagnitude; - if( pSqmag < distSqClosest ) { - distSqClosest = pSqmag; - tClosest = pointProjectGuesses[i].t; - ptClosest = pointProjectGuesses[i].f + point; - } - } - - t = tClosest; - return ptClosest; - } - - #endregion - - #region Intersection Tests - - // Internal - used by all other intersections - private ResultsMax3 Intersect( Vector2 origin, Vector2 direction, bool rangeLimited = false, float minRayT = float.NaN, float maxRayT = float.NaN ) { - Vector2 p0rel = this.P0 - origin; - Vector2 p1rel = this.P1 - origin; - Vector2 p2rel = this.P2 - origin; - Vector2 p3rel = this.P3 - origin; - float y0 = Determinant( p0rel, direction ); // transform bezier point components into the line space y components - float y1 = Determinant( p1rel, direction ); - float y2 = Determinant( p2rel, direction ); - float y3 = Determinant( p3rel, direction ); - Polynomial polynomY = SplineUtils.GetCubicPolynomial( y0, y1, y2, y3 ); - ResultsMax3 roots = polynomY.Roots; // t values of the function - - - Polynomial polynomX = default; - if( rangeLimited ) { - // if we're range limited, we need to verify position along the ray/line/lineSegment - // and if we do, we need to be able to go from t -> x coord - float x0 = Vector2.Dot( p0rel, direction ); // transform bezier point components into the line space x components - float x1 = Vector2.Dot( p1rel, direction ); - float x2 = Vector2.Dot( p2rel, direction ); - float x3 = Vector2.Dot( p3rel, direction ); - polynomX = SplineUtils.GetCubicPolynomial( x0, x1, x2, x3 ); - } - - float CurveTtoRayT( float t ) => polynomX.Sample( t ); - - ResultsMax3 returnVals = default; - - for( int i = 0; i < roots.count; i++ ) { - if( roots[i].Between( 0, 1 ) && ( rangeLimited == false || CurveTtoRayT( roots[i] ).Within( minRayT, maxRayT ) ) ) - returnVals = returnVals.Add( roots[i] ); - } - - return returnVals; - } - - // Internal - to unpack from curve t values to points - private ResultsMax3 TtoPoints( ResultsMax3 tVals ) { - ResultsMax3 pts = default; - for( int i = 0; i < tVals.count; i++ ) - pts = pts.Add( GetPoint( tVals[i] ) ); - return pts; - } - - /// Returns the t-values at which the given line intersects with the curve - /// The line to test intersection against - public ResultsMax3 Intersect( Line2D line ) => Intersect( line.origin, line.dir ); - - /// Returns the t-values at which the given ray intersects with the curve - /// The ray to test intersection against - public ResultsMax3 Intersect( Ray2D ray ) => Intersect( ray.origin, ray.dir, rangeLimited: true, 0, float.MaxValue ); - - /// Returns the t-values at which the given line segment intersects with the curve - /// The line segment to test intersection against - public ResultsMax3 Intersect( LineSegment2D lineSegment ) => Intersect( lineSegment.start, lineSegment.end - lineSegment.start, rangeLimited: true, 0, lineSegment.LengthSquared ); - - /// Returns the points at which the given line intersects with the curve - /// The line to test intersection against - public ResultsMax3 IntersectionPoints( Line2D line ) => TtoPoints( Intersect( line.origin, line.dir ) ); - - /// Returns the points at which the given ray intersects with the curve - /// The ray to test intersection against - public ResultsMax3 IntersectionPoints( Ray2D ray ) => TtoPoints( Intersect( ray.origin, ray.dir, rangeLimited: true, 0, float.MaxValue ) ); - - /// Returns the points at which the given line segment intersects with the curve - /// The line segment to test intersection against - public ResultsMax3 IntersectionPoints( LineSegment2D lineSegment ) => TtoPoints( Intersect( lineSegment.start, lineSegment.end - lineSegment.start, rangeLimited: true, 0, lineSegment.LengthSquared ) ); - - /// Raycasts and returns whether or not it hit, along with the closest hit point - /// The ray to use when raycasting - /// The closest point on the curve the ray hit - /// The maximum length of the ray - public bool Raycast( Ray2D ray, out Vector2 hitPoint, float maxDist = float.MaxValue ) => Raycast( ray, out hitPoint, out _, maxDist ); - - /// Raycasts and returns whether or not it hit, along with the closest hit point and the t-value on the curve - /// The ray to use when raycasting - /// The closest point on the curve the ray hit - /// The t-value of the curve at the point the ray hit - /// The maximum length of the ray - public bool Raycast( Ray2D ray, out Vector2 hitPoint, out float t, float maxDist = float.MaxValue ) { - float closestDist = float.MaxValue; - ResultsMax3 tPts = Intersect( ray ); - ResultsMax3 pts = TtoPoints( tPts ); - - // find closest point - bool didHit = false; - hitPoint = default; - t = default; - for( int i = 0; i < pts.count; i++ ) { - Vector2 pt = pts[i]; - float dist = Vector2.Dot( ray.dir, pt - ray.origin ); - if( dist < closestDist && dist <= maxDist ) { - closestDist = dist; - hitPoint = pt; - t = tPts[i]; - didHit = true; - } - } - - return didHit; + public UBSCubic2D ToUniformCubicBSpline() { + // todo: channel split for performance + return new UBSCubic2D( + 6 * p0 - 7 * p1 + 2 * p2, + 2 * p1 - p2, + -p1 + 2 * p2, + 2 * p1 - 7 * p2 + 6 * p3 ); } - #endregion - - // Esoteric math stuff - - #region Polynomial Factors - - /// Returns the factors of the derivative polynomials, per-component, in the form at²+bt+c - public (Vector2 a, Vector2 b, Vector2 c) GetDerivativeFactors() { - ReadyCoefficients(); - return ( new Vector2( 3 * c3.x, 3 * c3.y ), new Vector2( 2 * c2.x, 2 * c2.y ), c1 ); - } - - /// Returns the factors of the second derivative polynomials, per-component, in the form at+b - public (Vector2 a, Vector2 b) GetSecondDerivativeFactors() { - ReadyCoefficients(); - return ( new Vector2( 6 * c3.x, 6 * c3.y ), new Vector2( 2 * c2.x, 2 * c2.y ) ); - } - - #endregion - - #region Local Extrema - - /// Returns the t values of extrema (local minima/maxima) on a given axis in the 0 < t < 1 range - /// Either 0 (X) or 1 (Y) - public ResultsMax2 GetLocalExtrema( int axis ) { - if( axis < 0 || axis > 1 ) - throw new ArgumentOutOfRangeException( nameof(axis), "axis has to be either 0 or 1" ); - Polynomial polynom; - if( axis == 0 ) // a little silly but the vec[] indexers are kinda expensive - polynom = SplineUtils.GetCubicPolynomialDerivative( P0.x, P1.x, P2.x, P3.x ); - else - polynom = SplineUtils.GetCubicPolynomialDerivative( P0.y, P1.y, P2.y, P3.y ); - ResultsMax3 roots = polynom.Roots; - ResultsMax2 outPts = default; - for( int i = 0; i < roots.count; i++ ) { - float t = roots[i]; - if( t.Between( 0, 1 ) ) - outPts = outPts.Add( t ); - } - - return outPts; + public CatRom2D ToUniformCubicCatRom() { + // todo: channel split for performance + return new CatRom2D( + 6 * p0 - 6 * p1 + p3, + p0, + p3, + p0 - 6 * p2 + 6 * p3 ); } - /// Returns the extrema points (local minima/maxima points) on a given axis in the 0 < t < 1 range - /// Either 0 (X) or 1 (Y) - public ResultsMax2 GetLocalExtremaPoints( int axis ) { - ResultsMax2 t = GetLocalExtrema( axis ); - ResultsMax2 pts = default; - for( int i = 0; i < t.count; i++ ) - pts = pts.Add( GetPointComponent( axis, t[i] ) ); - return pts; + public Hermite2D ToHermite() { + // todo: channel split for performance + return new Hermite2D( p0, ( p1 - p0 ) * 3, p3, ( p3 - p2 ) * 3 ); } #endregion } -} - -// code graveyard - kept just in case I want to bring any of it back: -/* -/// Returns the point and the derivative at the given t-value on the curve. This is more performant than calling GetPoint and GetDerivative separately -/// The t-value along the curve to sample -public (Vector2, Vector2) GetPointAndDerivative( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float tx2 = t * 2; - float t2x3 = t2 * 3; - float t3 = t2 * t; - return ( - new Vector2( t3 * c3.x + t2 * c2.x + t * c1.x + p0.x, t3 * c3.y + t2 * c2.y + t * c1.y + p0.y ), - new Vector2( t2x3 * c3.x + tx2 * c2.x + c1.x, t2x3 * c3.y + tx2 * c2.y + c1.y ) - ); -} - -/// Returns all three derivatives at the given t-value on the curve -/// The t-value along the curve to sample -public (Vector2, Vector2, Vector2) GetAllThreeDerivatives( float t ) { - ReadyCoefficients(); - float t2x3 = 3 * t * t; - float tx2 = 2 * t; - float tx6 = 6 * t; - return ( - new Vector2( t2x3 * c3.x + tx2 * c2.x + c1.x, t2x3 * c3.y + tx2 * c2.y + c1.y ), - new Vector2( tx6 * c3.x + 2 * c2.x, tx6 * c3.y + 2 * c2.y ), - new Vector2( 6 * c3.x, 6 * c3.y ) - ); -} - -[MethodImpl( INLINE )] public (Vector2, Vector2) GetFirstTwoDerivatives( float t ) { - ReadyCoefficients(); - float t2x3 = 3 * t * t; - float tx2 = 2 * t; - float tx6 = 6 * t; - return ( - new Vector2( t2x3 * c3.x + tx2 * c2.x + c1.x, t2x3 * c3.y + tx2 * c2.y + c1.y ), - new Vector2( tx6 * c3.x + 2 * c2.x, tx6 * c3.y + 2 * c2.y ) - ); -} - -public (Vector2, Vector2, Vector2) GetPointAndFirstTwoDerivatives( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float tx2 = t * 2; - float tx6 = t * 6; - float t2x3 = t2 * 3; - float t3 = t2 * t; - return ( - new Vector2( t3 * c3.x + t2 * c2.x + t * c1.x + p0.x, t3 * c3.y + t2 * c2.y + t * c1.y + p0.y ), - new Vector2( t2x3 * c3.x + tx2 * c2.x + c1.x, t2x3 * c3.y + tx2 * c2.y + c1.y ), - new Vector2( tx6 * c3.x + 2 * c2.x, tx6 * c3.y + 2 * c2.y ) - ); -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/Curves/BezierCubic3D.cs b/Curves/BezierCubic3D.cs index 4235bb8..634c36c 100644 --- a/Curves/BezierCubic3D.cs +++ b/Curves/BezierCubic3D.cs @@ -4,16 +4,11 @@ using System; using System.Runtime.CompilerServices; using UnityEngine; -using static Freya.Mathfs; namespace Freya { - // Bezier math - // A lot of the following code is unrolled into floats and components for performance reasons. - // It's much faster than keeping the more readable function calls and vector types unfortunately - /// An optimized 3D cubic bezier curve, with 4 control points - [Serializable] public struct BezierCubic3D : IParamCurve3Diff { + [Serializable] public struct BezierCubic3D : IParamCubicSplineSegment3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -21,7 +16,15 @@ namespace Freya { public BezierCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); validCoefficients = false; - c3 = c2 = c1 = default; + curve = default; + } + + Polynomial3D curve; + public Polynomial3D Curve { + get { + ReadyCoefficients(); + return curve; + } } #region Control Points @@ -87,63 +90,17 @@ public Vector3 this[ int i ] { #region Coefficients [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) - [NonSerialized] Vector3 c3, c2, c1; // cached coefficients for fast evaluation. c0 = p0 // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update validCoefficients = true; - c3.x = 3 * ( p1.x - p2.x ) + ( p3.x - p0.x ); - c2.x = 3 * ( p0.x - p1.x + p2.x - p1.x ); - c1.x = 3 * ( p1.x - p0.x ); - c3.y = 3 * ( p1.y - p2.y ) + ( p3.y - p0.y ); - c2.y = 3 * ( p0.y - p1.y + p2.y - p1.y ); - c1.y = 3 * ( p1.y - p0.y ); - c3.z = 3 * ( p1.z - p2.z ) + ( p3.z - p0.z ); - c2.z = 3 * ( p0.z - p1.z + p2.z - p1.z ); - c1.z = 3 * ( p1.z - p0.z ); - } - - /// - public Vector3 C0 { - [MethodImpl( INLINE )] get => p0; - } - - /// - public Vector3 C1 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c1; - } - } - - /// - public Vector3 C2 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c2; - } - } - - /// - public Vector3 C3 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c3; - } - } - - /// - [MethodImpl( INLINE )] public (Vector3 c3, Vector3 c2, Vector3 c1, Vector3 c0) GetCoefficients() { - ReadyCoefficients(); - return ( c3, c2, c1, p0 ); + curve = CharMatrix.cubicBezier.GetCurve( p0, p1, p2, p3 ); } #endregion - // Object comparison stuff - #region Object Comparison & ToString public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; @@ -175,88 +132,6 @@ public static explicit operator BezierCubic2D( BezierCubic3D bezierCubic3D ) { #endregion - // Base properties - Points, Derivatives & Tangents - - #region Core IParamCurve Implementations - - public int Degree { - [MethodImpl( INLINE )] get => 3; - } - public int Count { - [MethodImpl( INLINE )] get => 4; - } - - [MethodImpl( INLINE )] public Vector3 GetStartPoint() => p0; - [MethodImpl( INLINE )] public Vector3 GetEndPoint() => p3; - - [MethodImpl( INLINE )] public Vector3 GetPoint( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return new Vector3( t3 * c3.x + t2 * c2.x + t * c1.x + p0.x, t3 * c3.y + t2 * c2.y + t * c1.y + p0.y, t3 * c3.z + t2 * c2.z + t * c1.z + p0.z ); - } - - [MethodImpl( INLINE )] public Vector3 GetDerivative( float t ) { - ReadyCoefficients(); - float t2 = t * t; - return new Vector3( 3 * t2 * c3.x + 2 * t * c2.x + c1.x, 3 * t2 * c3.y + 2 * t * c2.y + c1.y, 3 * t2 * c3.z + 2 * t * c2.z + c1.z ); - } - - [MethodImpl( INLINE )] public Vector3 GetSecondDerivative( float t ) { - ReadyCoefficients(); - return new Vector3( 6 * t * c3.x + 2 * c2.x, 6 * t * c3.y + 2 * c2.y, 6 * t * c3.z + 2 * c2.z ); - } - - [MethodImpl( INLINE )] public Vector3 GetThirdDerivative( float t = 0 ) { - ReadyCoefficients(); - return new Vector3( 6 * c3.x, 6 * c3.y, 6 * c3.z ); - } - - #endregion - - #region Point Components - - /// - [MethodImpl( INLINE )] public float GetPointX( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return t3 * c3.x + t2 * c2.x + t * c1.x + p0.x; - } - - /// - [MethodImpl( INLINE )] public float GetPointY( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return t3 * c3.y + t2 * c2.y + t * c1.y + p0.y; - } - - /// Returns the Z coordinate at the given t-value on the curve - /// The t-value along the curve to sample - [MethodImpl( INLINE )] public float GetPointZ( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return t3 * c3.z + t2 * c2.z + t * c1.z + p0.z; - } - - /// Returns a component of the coordinate at the given t-value on the curve - /// Which component of the coordinate to return. 0 is X, 1 is Y, 2 is Z - /// The t-value along the curve to sample - public float GetPointComponent( int component, float t ) { - switch( component ) { - case 0: return GetPointX( t ); - case 1: return GetPointY( t ); - case 2: return GetPointZ( t ); - default: throw new ArgumentOutOfRangeException( nameof(component), "component has to be either 0, 1 or 2" ); - } - } - - #endregion - - // Whole-curve properties & functions - #region Interpolation /// @@ -315,247 +190,6 @@ public static BezierCubic3D Slerp( BezierCubic3D a, BezierCubic3D b, float t ) { #endregion - #region Bounds - - /// - public Bounds GetBounds() { - // first and last points are always included - Vector3 min = Vector3.Min( P0, P3 ); - Vector3 max = Vector3.Max( P0, P3 ); - - void Encapsulate( int axis, float value ) { - min[axis] = Min( min[axis], value ); - max[axis] = Max( max[axis], value ); - } - - for( int i = 0; i < 3; i++ ) { - ResultsMax2 extrema = GetLocalExtremaPoints( i ); - for( int j = 0; j < extrema.count; j++ ) - Encapsulate( i, extrema[j] ); - } - - return new Bounds( ( max + min ) * 0.5f, max - min ); - } - - #endregion - - #region Project Point - - /// - public Vector3 ProjectPoint( Vector3 point, int initialSubdivisions = 16, int refinementIterations = 4 ) => ProjectPoint( point, out _, initialSubdivisions, refinementIterations ); - - struct PointProjectSample { - public float t; - public float distDeltaSq; - public Vector3 f; - public Vector3 fp; - } - - static PointProjectSample[] pointProjectGuesses = { default, default, default }; - - /// - public Vector3 ProjectPoint( Vector3 point, out float t, int initialSubdivisions = 16, int refinementIterations = 4 ) { - // define a bezier relative to the test point - BezierCubic3D bez = new BezierCubic3D( P0 - point, P1 - point, P2 - point, P3 - point ); - - PointProjectSample SampleDistSqDelta( float tSmp ) { - PointProjectSample s = new PointProjectSample { t = tSmp }; - ( s.f, s.fp ) = ( bez.GetPoint( tSmp ), bez.GetDerivative( tSmp ) ); - s.distDeltaSq = Vector3.Dot( s.f, s.fp ); - return s; - } - - // find initial candidates - int candidatesFound = 0; - PointProjectSample prevSmp = SampleDistSqDelta( 0 ); - - for( int i = 1; i < initialSubdivisions; i++ ) { - float ti = i / ( initialSubdivisions - 1f ); - PointProjectSample smp = SampleDistSqDelta( ti ); - if( SignAsInt( smp.distDeltaSq ) != SignAsInt( prevSmp.distDeltaSq ) ) { - pointProjectGuesses[candidatesFound++] = SampleDistSqDelta( ( prevSmp.t + smp.t ) / 2 ); - if( candidatesFound == 3 ) break; // no more than three possible candidates because of the polynomial degree - } - - prevSmp = smp; - } - - // refine each guess w. Newton-Raphson iterations - void Refine( ref PointProjectSample smp ) { - Vector3 fpp = bez.GetSecondDerivative( smp.t ); - float tNew = smp.t - Vector3.Dot( smp.f, smp.fp ) / ( Vector3.Dot( smp.f, fpp ) + Vector3.Dot( smp.fp, smp.fp ) ); - smp = SampleDistSqDelta( tNew ); - } - - for( int p = 0; p < candidatesFound; p++ ) - for( int i = 0; i < refinementIterations; i++ ) - Refine( ref pointProjectGuesses[p] ); - - // Now find closest. First include the endpoints - float sqDist0 = bez.P0.sqrMagnitude; // include endpoints - float sqDist1 = bez.P3.sqrMagnitude; - bool firstClosest = sqDist0 < sqDist1; - float tClosest = firstClosest ? 0 : 1; - Vector3 ptClosest = firstClosest ? P0 : P3; - float distSqClosest = firstClosest ? sqDist0 : sqDist1; - - // then check internal roots - for( int i = 0; i < candidatesFound; i++ ) { - float pSqmag = pointProjectGuesses[i].f.sqrMagnitude; - if( pSqmag < distSqClosest ) { - distSqClosest = pSqmag; - tClosest = pointProjectGuesses[i].t; - ptClosest = pointProjectGuesses[i].f + point; - } - } - - t = tClosest; - return ptClosest; - } - - #endregion - - // Esoteric math stuff - - #region Polynomial Factors - - /// - public (Vector3 a, Vector3 b, Vector3 c) GetDerivativeFactors() { - ReadyCoefficients(); - return ( new Vector3( 3 * c3.x, 3 * c3.y, 3 * c3.z ), new Vector3( 2 * c2.x, 2 * c2.y, 2 * c2.z ), c1 ); - } - - /// - public (Vector3 a, Vector3 b) GetSecondDerivativeFactors() { - ReadyCoefficients(); - return ( new Vector3( 6 * c3.x, 6 * c3.y, 6 * c3.z ), new Vector3( 2 * c2.x, 2 * c2.y, 2 * c2.z ) ); - } - - #endregion - - #region Local Extrema - - /// Returns the t values of extrema (local minima/maxima) on a given axis in the 0 < t < 1 range - /// Either 0 (X), 1 (Y) or 2 (Z) - public ResultsMax2 GetLocalExtrema( int axis ) { - if( axis < 0 || axis > 2 ) - throw new ArgumentOutOfRangeException( nameof(axis), "axis has to be either 0, 1 or 2" ); - Polynomial polynom; - if( axis == 0 ) // a little silly but the vec[] indexers are kinda expensive - polynom = SplineUtils.GetCubicPolynomialDerivative( P0.x, P1.x, P2.x, P3.x ); - else if( axis == 1 ) - polynom = SplineUtils.GetCubicPolynomialDerivative( P0.y, P1.y, P2.y, P3.y ); - else - polynom = SplineUtils.GetCubicPolynomialDerivative( P0.z, P1.z, P2.z, P3.z ); - ResultsMax3 roots = polynom.Roots; - ResultsMax2 outPts = default; - for( int i = 0; i < roots.count; i++ ) { - float t = roots[i]; - if( t.Between( 0, 1 ) ) - outPts = outPts.Add( t ); - } - - return outPts; - } - - /// Returns the extrema points (local minima/maxima points) on a given axis in the 0 < t < 1 range - /// Either 0 (X), 1 (Y) or 2 (Z) - public ResultsMax2 GetLocalExtremaPoints( int axis ) { - ResultsMax2 t = GetLocalExtrema( axis ); - ResultsMax2 pts = default; - for( int i = 0; i < t.count; i++ ) - pts = pts.Add( GetPointComponent( axis, t[i] ) ); - return pts; - } - - #endregion - } -} - -// Code graveyard below - saving just in case -/* -#region Point & Derivative combos - -/// -public (Vector3, Vector3) GetPointAndTangent( float t ) { - ( Vector3 p, Vector3 d ) = GetPointAndDerivative( t ); - return ( p, d.normalized ); -} - -/// -public (Vector3, Vector3) GetPointAndDerivative( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float tx2 = t * 2; - float t2x3 = t2 * 3; - float t3 = t2 * t; - return ( - new Vector3( - t3 * c3.x + t2 * c2.x + t * c1.x + p0.x, - t3 * c3.y + t2 * c2.y + t * c1.y + p0.y, - t3 * c3.z + t2 * c2.z + t * c1.z + p0.z - ), - new Vector3( - t2x3 * c3.x + tx2 * c2.x + c1.x, - t2x3 * c3.y + tx2 * c2.y + c1.y, - t2x3 * c3.z + tx2 * c2.z + c1.z - ) - ); -} - -/// -public (Vector3, Vector3) GetFirstTwoDerivatives( float t ) { - ReadyCoefficients(); - float t2x3 = 3 * t * t; - float tx2 = 2 * t; - float tx6 = 6 * t; - return ( - new Vector3( t2x3 * c3.x + tx2 * c2.x + c1.x, t2x3 * c3.y + tx2 * c2.y + c1.y, t2x3 * c3.z + tx2 * c2.z + c1.z ), - new Vector3( tx6 * c3.x + 2 * c2.x, tx6 * c3.y + 2 * c2.y, tx6 * c3.z + 2 * c2.z ) - ); -} - -/// -public (Vector3, Vector3, Vector3) GetAllThreeDerivatives( float t ) { - ReadyCoefficients(); - float t2x3 = 3 * t * t; - float tx2 = 2 * t; - float tx6 = 6 * t; - return ( - new Vector3( t2x3 * c3.x + tx2 * c2.x + c1.x, t2x3 * c3.y + tx2 * c2.y + c1.y, t2x3 * c3.z + tx2 * c2.z + c1.z ), - new Vector3( tx6 * c3.x + 2 * c2.x, tx6 * c3.y + 2 * c2.y, tx6 * c3.z + 2 * c2.z ), - new Vector3( 6 * c3.x, 6 * c3.y, 6 * c3.z ) - ); -} - -/// -public (Vector3, Vector3, Vector3) GetPointAndFirstTwoDerivatives( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float tx2 = t * 2; - float tx6 = t * 6; - float t2x3 = t2 * 3; - float t3 = t2 * t; - return ( - new Vector3( - t3 * c3.x + t2 * c2.x + t * c1.x + p0.x, - t3 * c3.y + t2 * c2.y + t * c1.y + p0.y, - t3 * c3.z + t2 * c2.z + t * c1.z + p0.z - ), - new Vector3( - t2x3 * c3.x + tx2 * c2.x + c1.x, - t2x3 * c3.y + tx2 * c2.y + c1.y, - t2x3 * c3.z + tx2 * c2.z + c1.z - ), - new Vector3( - tx6 * c3.x + 2 * c2.x, - tx6 * c3.y + 2 * c2.y, - tx6 * c3.z + 2 * c2.z - ) - ); -} - -#endregion -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/Curves/BezierQuad2D.cs b/Curves/BezierQuad2D.cs index baa98c0..0bd5e86 100644 --- a/Curves/BezierQuad2D.cs +++ b/Curves/BezierQuad2D.cs @@ -133,19 +133,19 @@ public int Count { [MethodImpl( INLINE )] public Vector2 GetStartPoint() => p0; [MethodImpl( INLINE )] public Vector2 GetEndPoint() => p2; - public Vector2 GetPoint( float t ) { + public Vector2 Eval( float t ) { ReadyCoefficients(); float tt = t * t; return new Vector2( c2.x * tt + c1.x * t + p0.x, c2.y * tt + c1.y * t + p0.y ); } - public Vector2 GetDerivative( float t ) { + public Vector2 EvalDerivative( float t ) { ReadyCoefficients(); float tx2 = 2 * t; return new Vector2( tx2 * c2.x + c1.x, tx2 * c2.y + c1.y ); } - public Vector2 GetSecondDerivative( float t = 0 ) { + public Vector2 EvalSecondDerivative( float t = 0 ) { ReadyCoefficients(); return new Vector2( 2 * c2.x, 2 * c2.y ); } diff --git a/Curves/BezierQuad3D.cs b/Curves/BezierQuad3D.cs index 6ad7f48..6cc61e3 100644 --- a/Curves/BezierQuad3D.cs +++ b/Curves/BezierQuad3D.cs @@ -135,19 +135,19 @@ public int Count { [MethodImpl( INLINE )] public Vector3 GetStartPoint() => p0; [MethodImpl( INLINE )] public Vector3 GetEndPoint() => p2; - public Vector3 GetPoint( float t ) { + public Vector3 Eval( float t ) { ReadyCoefficients(); float tt = t * t; return new Vector3( c2.x * tt + c1.x * t + p0.x, c2.y * tt + c1.y * t + p0.y, c2.z * tt + c1.z * t + p0.z ); } - public Vector3 GetDerivative( float t ) { + public Vector3 EvalDerivative( float t ) { ReadyCoefficients(); float tx2 = 2 * t; return new Vector3( tx2 * c2.x + c1.x, tx2 * c2.y + c1.y, tx2 * c2.z + c1.z ); } - public Vector3 GetSecondDerivative( float t = 0 ) { + public Vector3 EvalSecondDerivative( float t = 0 ) { ReadyCoefficients(); return new Vector3( 2 * c2.x, 2 * c2.y, 2 * c2.z ); } diff --git a/Curves/BezierSampler.cs b/Curves/BezierSampler.cs index f415791..845dca5 100644 --- a/Curves/BezierSampler.cs +++ b/Curves/BezierSampler.cs @@ -55,7 +55,7 @@ public void Recalculate( BezierCubic2D bezier ) { Vector2 prevPt = bezier.P0; cumulativeDistances[0] = 0; for( int i = 1; i < resolution; i++ ) { // todo: could optimize by moving all points so that p0 = (0,0) - Vector2 pt = bezier.GetPoint( i / ( resolution - 1f ) ); + Vector2 pt = bezier.Curve.Eval( i / ( resolution - 1f ) ); cumulativeLength += Vector2.Distance( prevPt, pt ); cumulativeDistances[i] = cumulativeLength; prevPt = pt; @@ -70,7 +70,7 @@ public void Recalculate( BezierCubic3D bezier ) { Vector3 prevPt = bezier.P0; cumulativeDistances[0] = 0; for( int i = 1; i < resolution; i++ ) { // todo: could optimize by moving all points so that p0 = (0,0) - Vector3 pt = bezier.GetPoint( i / ( resolution - 1f ) ); + Vector3 pt = bezier.Curve.Eval( i / ( resolution - 1f ) ); cumulativeLength += Vector3.Distance( prevPt, pt ); cumulativeDistances[i] = cumulativeLength; prevPt = pt; diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs new file mode 100644 index 0000000..45de47b --- /dev/null +++ b/Curves/CharMatrix.cs @@ -0,0 +1,68 @@ +using System; +using UnityEngine; + +namespace Freya { + + /// Data structure representing a characteristic up to a cubic. Used for spline evaluation + public readonly struct CharMatrix { + + public readonly float m00, m01, m02, m03; + public readonly float m10, m11, m12, m13; + public readonly float m20, m21, m22, m23; + public readonly float m30, m31, m32, m33; + + public CharMatrix( float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33 ) { + ( this.m00, this.m01, this.m02, this.m03 ) = ( m00, m01, m02, m03 ); + ( this.m10, this.m11, this.m12, this.m13 ) = ( m10, m11, m12, m13 ); + ( this.m20, this.m21, this.m22, this.m23 ) = ( m20, m21, m22, m23 ); + ( this.m30, this.m31, this.m32, this.m33 ) = ( m30, m31, m32, m33 ); + } + + public static readonly CharMatrix cubicBezier = new( + 1, 0, 0, 0, + -3, 3, 0, 0, + 3, -6, 3, 0, + -1, 3, -3, 1 + ); + + public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => + new( + GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), + GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ) + ); + public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => + new( + GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), + GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ), + GetEvalPolynomial( p0.z, p1.z, p2.z, p3.z ) + ); + + /// Returns the basis function (weight) for the given point by index i, + /// equal to the t-matrix multiplied by the characteristic matrix + /// The point index to get the basis function of + public Polynomial GetBasisFunction( int i ) { + return i switch { + 0 => new Polynomial( m30, m20, m10, m00 ), + 1 => new Polynomial( m31, m21, m11, m01 ), + 2 => new Polynomial( m32, m22, m12, m02 ), + 3 => new Polynomial( m33, m23, m13, m03 ), + _ => throw new IndexOutOfRangeException( "Bézier basis index needs to be between 0 and 3" ) + }; + } + + /// Returns the polynomial representing the charateristic matrix + /// multiplied by the input points, on a single axis + /// The value of the first point + /// The value of the second point + /// The value of the third point + /// The value of the fourth point + public Polynomial GetEvalPolynomial( float p0, float p1, float p2, float p3 ) => + new( + p0 * m30 + p1 * m31 + p2 * m32 + p3 * m33, + p0 * m20 + p1 * m21 + p2 * m22 + p3 * m23, + p0 * m10 + p1 * m11 + p2 * m12 + p3 * m13, + p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03); + + } + +} \ No newline at end of file diff --git a/Curves/Hermite2D.cs b/Curves/Hermite2D.cs index f04515c..c9f0f3f 100644 --- a/Curves/Hermite2D.cs +++ b/Curves/Hermite2D.cs @@ -114,25 +114,25 @@ public int Count { [MethodImpl( INLINE )] public Vector2 GetStartPoint() => p0; [MethodImpl( INLINE )] public Vector2 GetEndPoint() => p1; - [MethodImpl( INLINE )] public Vector2 GetPoint( float t ) { + [MethodImpl( INLINE )] public Vector2 Eval( float t ) { ReadyCoefficients(); float t2 = t * t; float t3 = t2 * t; return new Vector2( t3 * c3.x + t2 * c2.x + t * m0.x + p0.x, t3 * c3.y + t2 * c2.y + t * m0.y + p0.y ); } - [MethodImpl( INLINE )] public Vector2 GetDerivative( float t ) { + [MethodImpl( INLINE )] public Vector2 EvalDerivative( float t ) { ReadyCoefficients(); float t2 = t * t; return new Vector2( 3 * t2 * c3.x + 2 * t * c2.x + m0.x, 3 * t2 * c3.y + 2 * t * c2.y + m0.y ); } - [MethodImpl( INLINE )] public Vector2 GetSecondDerivative( float t ) { + [MethodImpl( INLINE )] public Vector2 EvalSecondDerivative( float t ) { ReadyCoefficients(); return new Vector2( 6 * t * c3.x + 2 * c2.x, 6 * t * c3.y + 2 * c2.y ); } - [MethodImpl( INLINE )] public Vector2 GetThirdDerivative( float t = 0 ) { + [MethodImpl( INLINE )] public Vector2 EvalThirdDerivative( float t = 0 ) { ReadyCoefficients(); return new Vector2( 6 * c3.x, 6 * c3.y ); } diff --git a/Curves/IParamCurve.cs b/Curves/IParamCurve.cs index 1e077b9..af22104 100644 --- a/Curves/IParamCurve.cs +++ b/Curves/IParamCurve.cs @@ -1,9 +1,21 @@ -using System.Runtime.CompilerServices; +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System.Runtime.CompilerServices; using UnityEngine; using static Freya.Mathfs; namespace Freya { + public interface IParamCubicSplineSegment2D { + /// The curve generated by the control points + Polynomial2D Curve { get; } + } + + public interface IParamCubicSplineSegment3D { + /// + Polynomial3D Curve { get; } + } + /// An interface representing a parametric curve /// The vector type of the curve public interface IParamCurve where V : struct { @@ -11,18 +23,9 @@ public interface IParamCurve where V : struct { /// Returns the degree of this curve. Quadratic = 2, Cubic = 3, etc int Degree { get; } - /// The number of control points in this curve - int Count { get; } - /// Returns the point at the given t-value on the curve /// The t-value along the curve to sample - V GetPoint( float t ); - - /// Returns the starting point of this curve, where t = 0 - V GetStartPoint(); - - /// Returns the end point of this curve, where t = 1 - V GetEndPoint(); + V Eval( float t ); } @@ -31,7 +34,7 @@ public interface IParamCurve where V : struct { public interface IParamCurve1Diff : IParamCurve where V : struct { /// Returns the derivative at the given t-value on the curve. Loosely analogous to "velocity" of the point along the curve /// The t-value along the curve to sample - V GetDerivative( float t ); + V EvalDerivative( float t ); } /// An interface representing a parametric curve of degree 2 or higher @@ -39,14 +42,14 @@ public interface IParamCurve1Diff : IParamCurve where V : struct { public interface IParamCurve2Diff : IParamCurve1Diff where V : struct { /// Returns the second derivative at the given t-value on the curve. Loosely analogous to "acceleration" of the point along the curve /// The t-value along the curve to sample - V GetSecondDerivative( float t ); + V EvalSecondDerivative( float t ); } /// An interface representing a parametric curve of degree 3 or higher /// The vector type of the curve public interface IParamCurve3Diff : IParamCurve2Diff where V : struct { /// Returns the third derivative of the curve. Loosely analogous to "jerk/jolt" (rate of change of acceleration) of the point along the curve - V GetThirdDerivative( float t ); + V EvalThirdDerivative( float t ); } /// Shared functionality for all 2D parametric curves @@ -57,14 +60,16 @@ public static class IParamCurveExt2D { /// Returns the approximate length of the curve /// The number of subdivisions to approximate the length with. Higher values are more accurate, but more expensive to calculate public static float GetLength( this T curve, int accuracy = 8 ) where T : IParamCurve { + Vector2 start = curve.Eval( 0 ); + Vector2 end = curve.Eval( 1 ); if( accuracy <= 2 ) - return ( curve.GetStartPoint() - curve.GetEndPoint() ).magnitude; + return ( start - end ).magnitude; float totalDist = 0; - Vector2 prev = curve.GetStartPoint(); + Vector2 prev = start; for( int i = 1; i < accuracy; i++ ) { float t = i / ( accuracy - 1f ); - Vector2 p = curve.GetPoint( t ); + Vector2 p = curve.Eval( t ); float dx = p.x - prev.x; float dy = p.y - prev.y; totalDist += Mathf.Sqrt( dx * dx + dy * dy ); @@ -84,14 +89,16 @@ public static class IParamCurveExt3D { /// Returns the approximate length of the curve /// The number of subdivisions to approximate the length with. Higher values are more accurate, but more expensive to calculate public static float GetLength( this T curve, int accuracy = 8 ) where T : IParamCurve { + Vector3 start = curve.Eval( 0 ); + Vector3 end = curve.Eval( 1 ); if( accuracy <= 2 ) - return ( curve.GetStartPoint() - curve.GetEndPoint() ).magnitude; + return ( start - end ).magnitude; float totalDist = 0; - Vector3 prev = curve.GetStartPoint(); + Vector3 prev = start; for( int i = 1; i < accuracy; i++ ) { float t = i / ( accuracy - 1f ); - Vector3 p = curve.GetPoint( t ); + Vector3 p = curve.Eval( t ); float dx = p.x - prev.x; float dy = p.y - prev.y; totalDist += Mathf.Sqrt( dx * dx + dy * dy ); @@ -110,28 +117,28 @@ public static class IParamCurve1DiffExt2D { /// Returns the normalized tangent direction at the given t-value on the curve /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Vector2 GetTangent( this T curve, float t ) where T : IParamCurve1Diff => curve.GetDerivative( t ).normalized; + [MethodImpl( INLINE )] public static Vector2 EvalTangent( this T curve, float t ) where T : IParamCurve1Diff => curve.EvalDerivative( t ).normalized; /// Returns the normal direction at the given t-value on the curve. /// This normal will point to the inner arc of the current curvature /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Vector2 GetNormal( this T curve, float t ) where T : IParamCurve1Diff => curve.GetTangent( t ).Rotate90CCW(); + [MethodImpl( INLINE )] public static Vector2 EvalNormal( this T curve, float t ) where T : IParamCurve1Diff => curve.EvalTangent( t ).Rotate90CCW(); /// Returns the 2D angle of the direction of the curve at the given point, in radians /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static float GetAngle( this T curve, float t ) where T : IParamCurve1Diff => DirToAng( curve.GetDerivative( t ) ); + [MethodImpl( INLINE )] public static float EvalAngle( this T curve, float t ) where T : IParamCurve1Diff => DirToAng( curve.EvalDerivative( t ) ); /// Returns the orientation at the given point t, where the X axis is tangent to the curve /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Quaternion GetOrientation( this T curve, float t ) where T : IParamCurve1Diff => DirToOrientation( curve.GetDerivative( t ) ); + [MethodImpl( INLINE )] public static Quaternion EvalOrientation( this T curve, float t ) where T : IParamCurve1Diff => DirToOrientation( curve.EvalDerivative( t ) ); /// Returns the position and orientation at the given t-value on the curve /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Pose GetPose( this T curve, float t ) where T : IParamCurve1Diff => PointDirToPose( curve.GetPoint( t ), curve.GetTangent( t ) ); + [MethodImpl( INLINE )] public static Pose EvalPose( this T curve, float t ) where T : IParamCurve1Diff => PointDirToPose( curve.Eval( t ), curve.EvalTangent( t ) ); /// Returns the position and orientation at the given t-value on the curve, expressed as a matrix /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Matrix4x4 GetMatrix( this T curve, float t ) where T : IParamCurve1Diff => GetMatrixFrom2DPointDir( curve.GetPoint( t ), curve.GetTangent( t ) ); + [MethodImpl( INLINE )] public static Matrix4x4 EvalMatrix( this T curve, float t ) where T : IParamCurve1Diff => GetMatrixFrom2DPointDir( curve.Eval( t ), curve.EvalTangent( t ) ); } @@ -139,40 +146,40 @@ public static class IParamCurve1DiffExt2D { public static class IParamCurve1DiffExt3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// - [MethodImpl( INLINE )] public static Vector3 GetTangent( this T curve, float t ) where T : IParamCurve1Diff => curve.GetDerivative( t ).normalized; + /// + [MethodImpl( INLINE )] public static Vector3 EvalTangent( this T curve, float t ) where T : IParamCurve1Diff => curve.EvalDerivative( t ).normalized; /// Returns a normal of the curve given a reference up vector and t-value on the curve. /// The normal will be perpendicular to both the supplied up vector and the curve /// The t-value along the curve to sample /// The reference up vector. The normal will be perpendicular to both the supplied up vector and the curve - [MethodImpl( INLINE )] public static Vector3 GetNormal( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff => GetNormalFromLookTangent( curve.GetDerivative( t ), up ); + [MethodImpl( INLINE )] public static Vector3 EvalNormal( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff => GetNormalFromLookTangent( curve.EvalDerivative( t ), up ); /// Returns the binormal of the curve given a reference up vector and t-value on the curve. /// The binormal will attempt to be as aligned with the reference vector as possible, /// while still being perpendicular to the curve /// The t-value along the curve to sample /// The reference up vector. The binormal will attempt to be as aligned with the reference vector as possible, while still being perpendicular to the curve - [MethodImpl( INLINE )] public static Vector3 GetBinormal( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff => GetBinormalFromLookTangent( curve.GetDerivative( t ), up ); + [MethodImpl( INLINE )] public static Vector3 EvalBinormal( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff => GetBinormalFromLookTangent( curve.EvalDerivative( t ), up ); /// Returns the orientation at the given point t, where the Z direction is tangent to the curve. /// The Y axis will attempt to align with the supplied up vector /// The t-value along the curve to sample /// The reference up vector. The Y axis will attempt to be as aligned with this vector as much as possible - [MethodImpl( INLINE )] public static Quaternion GetOrientation( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff => Quaternion.LookRotation( curve.GetDerivative( t ), up ); + [MethodImpl( INLINE )] public static Quaternion EvalOrientation( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff => Quaternion.LookRotation( curve.EvalDerivative( t ), up ); /// Returns the position and orientation of curve at the given point t, where the Z direction is tangent to the curve. /// The Y axis will attempt to align with the supplied up vector /// The t-value along the curve to sample /// The reference up vector. The Y axis will attempt to be as aligned with this vector as much as possible - [MethodImpl( INLINE )] public static Pose GetPose( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff => new Pose( curve.GetPoint( t ), Quaternion.LookRotation( curve.GetDerivative( t ), up ) ); + [MethodImpl( INLINE )] public static Pose EvalPose( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff => new Pose( curve.Eval( t ), Quaternion.LookRotation( curve.EvalDerivative( t ), up ) ); /// Returns the position and orientation of curve at the given point t, expressed as a matrix, where the Z direction is tangent to the curve. /// The Y axis will attempt to align with the supplied up vector /// The t-value along the curve to sample /// The reference up vector. The Y axis will attempt to be as aligned with this vector as much as possible - public static Matrix4x4 GetMatrix( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff { - ( Vector3 Pt, Vector3 Tn ) = ( curve.GetPoint( t ), curve.GetTangent( t ) ); + public static Matrix4x4 EvalMatrix( this T curve, float t, Vector3 up ) where T : IParamCurve1Diff { + ( Vector3 Pt, Vector3 Tn ) = ( curve.Eval( t ), curve.EvalTangent( t ) ); Vector3 Nm = Vector3.Cross( up, Tn ).normalized; // X axis Vector3 Bn = Vector3.Cross( Tn, Nm ); // Y axis return new Matrix4x4( @@ -191,11 +198,11 @@ public static class IParamCurve2DiffExt2D { /// Returns the signed curvature at the given t-value on the curve, in radians per distance unit (equivalent to the reciprocal radius of the osculating circle) /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static float GetCurvature( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetCurvature( curve.GetDerivative( t ), curve.GetSecondDerivative( t ) ); + [MethodImpl( INLINE )] public static float EvalCurvature( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetCurvature( curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); /// Returns the osculating circle at the given t-value in the curve, if possible. Osculating circles are defined everywhere except on inflection points, where curvature is 0 /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Circle2D GetOsculatingCircle( this T curve, float t ) where T : IParamCurve2Diff => Circle2D.GetOsculatingCircle( curve.GetPoint( t ), curve.GetDerivative( t ), curve.GetSecondDerivative( t ) ); + [MethodImpl( INLINE )] public static Circle2D EvalOsculatingCircle( this T curve, float t ) where T : IParamCurve2Diff => Circle2D.GetOsculatingCircle( curve.Eval( t ), curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); } @@ -205,29 +212,29 @@ public static class IParamCurve2DiffExt3D { /// Returns a pseudovector at the given t-value on the curve, where the magnitude is the curvature in radians per distance unit, and the direction is the axis of curvature /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Vector3 GetCurvature( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetCurvature( curve.GetDerivative( t ), curve.GetSecondDerivative( t ) ); + [MethodImpl( INLINE )] public static Vector3 EvalCurvature( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetCurvature( curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); - /// - [MethodImpl( INLINE )] public static Circle3D GetOsculatingCircle( this T curve, float t ) where T : IParamCurve2Diff => Circle3D.GetOsculatingCircle( curve.GetPoint( t ), curve.GetDerivative( t ), curve.GetSecondDerivative( t ) ); + /// + [MethodImpl( INLINE )] public static Circle3D EvalOsculatingCircle( this T curve, float t ) where T : IParamCurve2Diff => Circle3D.GetOsculatingCircle( curve.Eval( t ), curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); /// Returns the frenet-serret (curvature-based) normal direction at the given t-value on the curve /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Vector3 GetArcNormal( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetArcNormal( curve.GetDerivative( t ), curve.GetSecondDerivative( t ) ); + [MethodImpl( INLINE )] public static Vector3 EvalArcNormal( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetArcNormal( curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); /// Returns the frenet-serret (curvature-based) binormal direction at the given t-value on the curve /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Vector3 GetArcBinormal( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetArcBinormal( curve.GetDerivative( t ), curve.GetSecondDerivative( t ) ); + [MethodImpl( INLINE )] public static Vector3 EvalArcBinormal( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetArcBinormal( curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); /// Returns the frenet-serret (curvature-based) orientation of curve at the given point t, where the Z direction is tangent to the curve. /// The X axis will point to the inner arc of the current curvature /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Quaternion GetArcOrientation( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetArcOrientation( curve.GetDerivative( t ), curve.GetSecondDerivative( t ) ); + [MethodImpl( INLINE )] public static Quaternion EvalArcOrientation( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetArcOrientation( curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); /// Returns the position and the frenet-serret (curvature-based) orientation of curve at the given point t, where the Z direction is tangent to the curve. /// The X axis will point to the inner arc of the current curvature /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Pose GetArcPose( this T curve, float t ) where T : IParamCurve2Diff { - ( Vector3 pt, Vector3 vel, Vector3 acc ) = ( curve.GetPoint( t ), curve.GetDerivative( t ), curve.GetSecondDerivative( t ) ); + [MethodImpl( INLINE )] public static Pose EvalArcPose( this T curve, float t ) where T : IParamCurve2Diff { + ( Vector3 pt, Vector3 vel, Vector3 acc ) = ( curve.Eval( t ), curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); Vector3 binormal = Vector3.Cross( vel, acc ); return new Pose( pt, Quaternion.LookRotation( vel, binormal ) ); } @@ -235,10 +242,10 @@ public static class IParamCurve2DiffExt3D { /// Returns the position and the frenet-serret (curvature-based) orientation of curve at the given point t, expressed as a matrix, where the Z direction is tangent to the curve. /// The X axis will point to the inner arc of the current curvature /// The t-value along the curve to sample - public static Matrix4x4 GetArcMatrix( this T curve, float t ) where T : IParamCurve2Diff { - Vector3 P = curve.GetPoint( t ); - Vector3 vel = curve.GetDerivative( t ); - Vector3 acc = curve.GetSecondDerivative( t ); + public static Matrix4x4 EvalArcMatrix( this T curve, float t ) where T : IParamCurve2Diff { + Vector3 P = curve.Eval( t ); + Vector3 vel = curve.EvalDerivative( t ); + Vector3 acc = curve.EvalSecondDerivative( t ); Vector3 Tn = vel.normalized; Vector3 B = Vector3.Cross( vel, acc ).normalized; Vector3 N = Vector3.Cross( B, Tn ); @@ -259,7 +266,7 @@ public static class IParamCurve3DiffExt3D { /// Returns the torsion at the given t-value on the curve, in radians per distance unit /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static float GetTorsion( this T curve, float t ) where T : IParamCurve3Diff => Mathfs.GetTorsion( curve.GetDerivative( t ), curve.GetSecondDerivative( t ), curve.GetThirdDerivative( t ) ); + [MethodImpl( INLINE )] public static float EvalTorsion( this T curve, float t ) where T : IParamCurve3Diff => Mathfs.GetTorsion( curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ), curve.EvalThirdDerivative( t ) ); } diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index c113ede..f21ca87 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; using UnityEngine; +using UnityEngine.Serialization; namespace Freya { @@ -12,86 +13,119 @@ [Serializable] public struct Polynomial { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// The cubic coefficient. [fCubic]x³+bx²+cx+d - public float fCubic; + /// A polynomial with all 0 coefficients. f(x) = 0 + public static readonly Polynomial zero = new Polynomial( 0, 0, 0, 0 ); - /// The quadratic coefficient. [fQuadratic]x²+cx+d - public float fQuadratic; + /// The cubic coefficient + [FormerlySerializedAs( "fCubic" )] public float c3; - /// The linear coefficient. [fLinear]x+d - public float fLinear; + /// The quadratic coefficient + [FormerlySerializedAs( "fQuadratic" )] public float c2; - /// The constant coefficient. ax+[fConstant] - public float fConstant; + /// The linear coefficient + [FormerlySerializedAs( "fLinear" )] public float c1; + + /// The constant coefficient + [FormerlySerializedAs( "fConstant" )] public float c0; /// Get or set the coefficient of the given degree /// The degree of the coefficient you want to get/set. For example, 0 will return the constant coefficient, 3 will return the cubic coefficient public float this[ int degree ] { get => degree switch { - 0 => fConstant, - 1 => fLinear, - 2 => fQuadratic, - 3 => fCubic, + 0 => c0, + 1 => c1, + 2 => c2, + 3 => c3, _ => throw new IndexOutOfRangeException( "Polynomial factor degree has to be between 0 and 3" ) }; set { _ = degree switch { - 0 => fConstant = value, - 1 => fLinear = value, - 2 => fQuadratic = value, - 3 => fCubic = value, + 0 => c0 = value, + 1 => c1 = value, + 2 => c2 = value, + 3 => c3 = value, _ => throw new IndexOutOfRangeException( "Polynomial factor degree has to be between 0 and 3" ) }; } } /// The degree of the polynomial - public PolynomialDegree Degree => GetPolynomialDegree( fCubic, fQuadratic, fLinear, fConstant ); + public PolynomialDegree Degree => GetPolynomialDegree( c3, c2, c1, c0 ); + + /// + public Polynomial( float a, float b, float c, float d ) => ( c3, c2, c1, c0 ) = ( a, b, c, d ); + + /// Evaluates the polynomial at the given value t + /// The value to sample at + public float Eval( float t ) => c3 * ( t * t * t ) + c2 * ( t * t ) + c1 * t + c0; + + /// Differentiates this function, returning the n-th derivative of this polynomial + /// The number of times to differentiate this function. 0 returns the function itself, 1 returns the first derivative + public Polynomial Differentiate( int n = 1 ) { + return n switch { + 0 => this, + 1 => new Polynomial( 0, 3 * c3, 2 * c2, c1 ), + 2 => new Polynomial( 0, 0, 6 * c3, 2 * c2 ), + 3 => new Polynomial( 0, 0, 0, 6 * c3 ), + _ => n > 3 ? zero : throw new IndexOutOfRangeException( "Cannot differentiate a negative amount of times" ) + }; + } + + /// Calculates the roots (values where this polynomial = 0) + public ResultsMax3 Roots => GetCubicRoots( c3, c2, c1, c0 ); + + /// Calculates the local extrema of this polynomial + public ResultsMax2 LocalExtrema => (ResultsMax2)Differentiate().Roots; + + /// Calculates the local extrema of this polynomial in the unit interval + public ResultsMax2 LocalExtrema01 { + get { + ResultsMax2 all = LocalExtrema; + ResultsMax2 valids = new ResultsMax2(); + for( int i = 0; i < all.count; i++ ) { + float t = all[i]; + if( t.Within( 0, 1 ) ) + valids = valids.Add( all[i] ); + } + + return valids; + } + } + + /// Returns the output value range within the unit interval + public FloatRange OutputRange01 { + get { + FloatRange range = ( Eval( 0 ), Eval( 1 ) ); + foreach( float t in LocalExtrema01 ) + range = range.Encapsulate( Eval( t ) ); + return range; + } + } + + #region Statics + + /// Creates a constant polynomial + /// The constant factor + public static Polynomial Constant( float constant ) => new Polynomial( 0, 0, 0, constant ); /// Creates a linear polynomial of the form ax+b /// The linear factor a in ax+b /// The constant factor b in ax+b - public Polynomial( float a, float b ) { - fCubic = fQuadratic = 0; - fLinear = a; - fConstant = b; - } + public static Polynomial Linear( float a, float b ) => new Polynomial( 0, 0, a, b ); /// Creates a quadratic polynomial of the form ax²+bx+c /// The quadratic factor a in ax²+bx+c /// The linear factor b in ax²+bx+c /// The constant factor c in ax²+bx+c - public Polynomial( float a, float b, float c ) { - fCubic = 0; - fQuadratic = a; - fLinear = b; - fConstant = c; - } + public static Polynomial Quadratic( float a, float b, float c ) => new Polynomial( 0, a, b, c ); /// Creates a cubic polynomial of the form ax³+bx²+cx+d /// The cubic factor a in ax³+bx²+cx+d /// The quadratic factor b in ax³+bx²+cx+d /// The linear factor c in ax³+bx²+cx+d /// The constant factor d in ax³+bx²+cx+d - public Polynomial( float a, float b, float c, float d ) { - fCubic = a; - fQuadratic = b; - fLinear = c; - fConstant = d; - } - - /// Calculates the derivative (rate of change) of this polynomial - public Polynomial Derivative => new Polynomial( 3 * fCubic, 2 * fQuadratic, fLinear ); - - /// Calculates the roots (values where this polynomial = 0) - public ResultsMax3 Roots => GetCubicRoots( fCubic, fQuadratic, fLinear, fConstant ); - - /// Samples the polynomial at a given x value - /// The value to sample at - public float Sample( float x ) => fCubic * ( x * x * x ) + fQuadratic * ( x * x ) + fLinear * x + fConstant; - - #region Statics + public static Polynomial Cubic( float a, float b, float c, float d ) => new Polynomial( a, b, c, d ); static bool FactorAlmost0( float v ) => v.Abs() < 0.00001f; @@ -155,10 +189,10 @@ public static ResultsMax2 GetQuadraticRoots( float a, float b, float c ) /// The blend value, typically from 0 to 1 public static Polynomial Lerp( Polynomial a, Polynomial b, float t ) => new( - t.Lerp( a.fCubic, b.fCubic ), - t.Lerp( a.fQuadratic, b.fQuadratic ), - t.Lerp( a.fLinear, b.fLinear ), - t.Lerp( a.fConstant, b.fConstant ) + t.Lerp( a.c3, b.c3 ), + t.Lerp( a.c2, b.c2 ), + t.Lerp( a.c1, b.c1 ), + t.Lerp( a.c0, b.c0 ) ); #region Internal root solvers @@ -239,9 +273,9 @@ static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { #endregion - public static Polynomial operator /( Polynomial p, float v ) => new(p.fCubic / v, p.fQuadratic / v, p.fLinear / v, p.fConstant / v); - public static Polynomial operator /( float v, Polynomial p ) => new(v / p.fCubic, v / p.fQuadratic, v / p.fLinear, v / p.fConstant); - public static Polynomial operator *( Polynomial p, float v ) => new(p.fCubic * v, p.fQuadratic * v, p.fLinear * v, p.fConstant * v); + public static Polynomial operator /( Polynomial p, float v ) => new(p.c3 / v, p.c2 / v, p.c1 / v, p.c0 / v); + public static Polynomial operator /( float v, Polynomial p ) => new(v / p.c3, v / p.c2, v / p.c1, v / p.c0); + public static Polynomial operator *( Polynomial p, float v ) => new(p.c3 * v, p.c2 * v, p.c1 * v, p.c0 * v); public static Polynomial operator *( float v, Polynomial p ) => p * v; } diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs new file mode 100644 index 0000000..04ec037 --- /dev/null +++ b/Curves/Polynomial2D.cs @@ -0,0 +1,254 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + public struct Polynomial2D : IParamCurve3Diff { + + public Polynomial x; + public Polynomial y; + + public Vector2 C0 => new(x.c0, y.c0); + public Vector2 C1 => new(x.c1, y.c1); + public Vector2 C2 => new(x.c2, y.c2); + public Vector2 C3 => new(x.c3, y.c3); + + public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( "Polynomial2D component index has to be either 0 or 1" ) }; + + public Polynomial2D( Polynomial x, Polynomial y ) => ( this.x, this.y ) = ( x, y ); + + /// + public Vector2 Eval( float t ) => new(x.Eval( t ), y.Eval( t )); + + /// + public Polynomial2D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n )); + + /// Returns the tight axis-aligned bounds of the curve in the unit interval + public Rect GetBounds01() => FloatRange.ToRect( x.OutputRange01, y.OutputRange01 ); + + public Hermite2D ToHermiteCurve() { + Polynomial2D d = Differentiate(); + return new Hermite2D( Eval( 0 ), d.Eval( 0 ), Eval( 1 ), d.Eval( 1 ) ); + } + + #region IParamCurve3Diff interface implementations + + public int Degree => Mathf.Max( (int)x.Degree, (int)y.Degree ); + public Vector2 EvalDerivative( float t ) => Differentiate().Eval( t ); + public Vector2 EvalSecondDerivative( float t ) => Differentiate( 2 ).Eval( t ); + public Vector2 EvalThirdDerivative( float t = 0 ) => Differentiate( 3 ).Eval( 0 ); + + #endregion + + #region Project Point + + /// Returns the (approximate) point on the curve closest to the input point + /// The point to project against the curve + /// Recommended range: [8-32]. More subdivisions will be more accurate, but more expensive. + /// This is how many subdivisions to split the curve into, to find candidates for the closest point. + /// If your curves are complex, you might need to use around 16 subdivisions. + /// If they are usually very simple, then around 8 subdivisions is likely fine + /// Recommended range: [3-6]. More iterations will be more accurate, but more expensive. + /// This is how many times to refine the initial guesses, using Newton's method. This converges rapidly, so high numbers are generally not necessary + public Vector2 ProjectPoint( Vector2 point, int initialSubdivisions = 16, int refinementIterations = 4 ) => ProjectPoint( point, out _, initialSubdivisions, refinementIterations ); + + struct PointProjectSample { + public float t; + public float distDeltaSq; + public Vector2 f; + public Vector2 fp; + } + + static PointProjectSample[] pointProjectGuesses = { default, default, default }; + + /// Returns the (approximate) point on the curve closest to the input point + /// The point to project against the curve + /// The t-value at the projected point on the curve + /// Recommended range: [8-32]. More subdivisions will be more accurate, but more expensive. + /// This is how many subdivisions to split the curve into, to find candidates for the closest point. + /// If your curves are complex, you might need to use around 16 subdivisions. + /// If they are usually very simple, then around 8 subdivisions is likely fine + /// Recommended range: [3-6]. More iterations will be more accurate, but more expensive. + /// This is how many times to refine the initial guesses, using Newton's method. This converges rapidly, so high numbers are generally not necessary + public Vector2 ProjectPoint( Vector2 point, out float t, int initialSubdivisions = 16, int refinementIterations = 4 ) { + // define a curve relative to the test point + Polynomial2D curve = this; + curve.x.c0 -= point.x; // constant coefficient defines the start position + curve.y.c0 -= point.y; + Polynomial2D vel = curve.Differentiate(); + Polynomial2D acc = vel.Differentiate(); + Vector2 curveStart = curve.Eval( 0 ); + Vector2 curveEnd = curve.Eval( 1 ); + + PointProjectSample SampleDistSqDelta( float tSmp ) { + PointProjectSample s = new PointProjectSample { + t = tSmp, + f = curve.Eval( tSmp ), + fp = vel.Eval( tSmp ) + }; + s.distDeltaSq = Vector2.Dot( s.f, s.fp ); + return s; + } + + // find initial candidates + int candidatesFound = 0; + PointProjectSample prevSmp = SampleDistSqDelta( 0 ); + + for( int i = 1; i < initialSubdivisions; i++ ) { + float ti = i / ( initialSubdivisions - 1f ); + PointProjectSample smp = SampleDistSqDelta( ti ); + if( Mathfs.SignAsInt( smp.distDeltaSq ) != Mathfs.SignAsInt( prevSmp.distDeltaSq ) ) { + pointProjectGuesses[candidatesFound++] = SampleDistSqDelta( ( prevSmp.t + smp.t ) / 2 ); + if( candidatesFound == 3 ) break; // no more than three possible candidates because of the polynomial degree + } + + prevSmp = smp; + } + + // refine each guess w. Newton-Raphson iterations + void Refine( ref PointProjectSample smp ) { + Vector2 fpp = acc.Eval( smp.t ); + float tNew = smp.t - Vector2.Dot( smp.f, smp.fp ) / ( Vector2.Dot( smp.f, fpp ) + Vector2.Dot( smp.fp, smp.fp ) ); + smp = SampleDistSqDelta( tNew ); + } + + for( int p = 0; p < candidatesFound; p++ ) + for( int i = 0; i < refinementIterations; i++ ) + Refine( ref pointProjectGuesses[p] ); + + // Now find closest. First include the endpoints + float sqDist0 = curveStart.sqrMagnitude; // include endpoints + float sqDist1 = curveEnd.sqrMagnitude; + bool firstClosest = sqDist0 < sqDist1; + float tClosest = firstClosest ? 0 : 1; + Vector2 ptClosest = ( firstClosest ? curveStart : curveEnd ) + point; + float distSqClosest = firstClosest ? sqDist0 : sqDist1; + + // then check internal roots + for( int i = 0; i < candidatesFound; i++ ) { + float pSqmag = pointProjectGuesses[i].f.sqrMagnitude; + if( pSqmag < distSqClosest ) { + distSqClosest = pSqmag; + tClosest = pointProjectGuesses[i].t; + ptClosest = pointProjectGuesses[i].f + point; + } + } + + t = tClosest; + return ptClosest; + } + + #endregion + + #region Intersection Tests + + // Internal - used by all other intersections + private ResultsMax3 Intersect( Vector2 origin, Vector2 direction, bool rangeLimited = false, float minRayT = float.NaN, float maxRayT = float.NaN ) { + BezierCubic2D bez = this.ToHermiteCurve().ToBezier(); // todo: hack + Vector2 p0rel = bez.P0 - origin; + Vector2 p1rel = bez.P1 - origin; + Vector2 p2rel = bez.P2 - origin; + Vector2 p3rel = bez.P3 - origin; + float y0 = Mathfs.Determinant( p0rel, direction ); // transform bezier point components into the line space y components + float y1 = Mathfs.Determinant( p1rel, direction ); + float y2 = Mathfs.Determinant( p2rel, direction ); + float y3 = Mathfs.Determinant( p3rel, direction ); + Polynomial polynomY = CharMatrix.cubicBezier.GetEvalPolynomial( y0, y1, y2, y3 ); + ResultsMax3 roots = polynomY.Roots; // t values of the function + + Polynomial polynomX = default; + if( rangeLimited ) { + // if we're range limited, we need to verify position along the ray/line/lineSegment + // and if we do, we need to be able to go from t -> x coord + float x0 = Vector2.Dot( p0rel, direction ); // transform bezier point components into the line space x components + float x1 = Vector2.Dot( p1rel, direction ); + float x2 = Vector2.Dot( p2rel, direction ); + float x3 = Vector2.Dot( p3rel, direction ); + polynomX = CharMatrix.cubicBezier.GetEvalPolynomial( x0, x1, x2, x3 ); + } + + float CurveTtoRayT( float t ) => polynomX.Eval( t ); + + ResultsMax3 returnVals = default; + + for( int i = 0; i < roots.count; i++ ) { + if( roots[i].Between( 0, 1 ) && ( rangeLimited == false || CurveTtoRayT( roots[i] ).Within( minRayT, maxRayT ) ) ) + returnVals = returnVals.Add( roots[i] ); + } + + return returnVals; + } + + // Internal - to unpack from curve t values to points + private ResultsMax3 TtoPoints( ResultsMax3 tVals ) { + ResultsMax3 pts = default; + for( int i = 0; i < tVals.count; i++ ) + pts = pts.Add( Eval( tVals[i] ) ); + return pts; + } + + /// Returns the t-values at which the given line intersects with the curve + /// The line to test intersection against + public ResultsMax3 Intersect( Line2D line ) => Intersect( line.origin, line.dir ); + + /// Returns the t-values at which the given ray intersects with the curve + /// The ray to test intersection against + public ResultsMax3 Intersect( Ray2D ray ) => Intersect( ray.origin, ray.dir, rangeLimited: true, 0, float.MaxValue ); + + /// Returns the t-values at which the given line segment intersects with the curve + /// The line segment to test intersection against + public ResultsMax3 Intersect( LineSegment2D lineSegment ) => Intersect( lineSegment.start, lineSegment.end - lineSegment.start, rangeLimited: true, 0, lineSegment.LengthSquared ); + + /// Returns the points at which the given line intersects with the curve + /// The line to test intersection against + public ResultsMax3 IntersectionPoints( Line2D line ) => TtoPoints( Intersect( line.origin, line.dir ) ); + + /// Returns the points at which the given ray intersects with the curve + /// The ray to test intersection against + public ResultsMax3 IntersectionPoints( Ray2D ray ) => TtoPoints( Intersect( ray.origin, ray.dir, rangeLimited: true, 0, float.MaxValue ) ); + + /// Returns the points at which the given line segment intersects with the curve + /// The line segment to test intersection against + public ResultsMax3 IntersectionPoints( LineSegment2D lineSegment ) => TtoPoints( Intersect( lineSegment.start, lineSegment.end - lineSegment.start, rangeLimited: true, 0, lineSegment.LengthSquared ) ); + + /// Raycasts and returns whether or not it hit, along with the closest hit point + /// The ray to use when raycasting + /// The closest point on the curve the ray hit + /// The maximum length of the ray + public bool Raycast( Ray2D ray, out Vector2 hitPoint, float maxDist = float.MaxValue ) => Raycast( ray, out hitPoint, out _, maxDist ); + + /// Raycasts and returns whether or not it hit, along with the closest hit point and the t-value on the curve + /// The ray to use when raycasting + /// The closest point on the curve the ray hit + /// The t-value of the curve at the point the ray hit + /// The maximum length of the ray + public bool Raycast( Ray2D ray, out Vector2 hitPoint, out float t, float maxDist = float.MaxValue ) { + float closestDist = float.MaxValue; + ResultsMax3 tPts = Intersect( ray ); + ResultsMax3 pts = TtoPoints( tPts ); + + // find closest point + bool didHit = false; + hitPoint = default; + t = default; + for( int i = 0; i < pts.count; i++ ) { + Vector2 pt = pts[i]; + float dist = Vector2.Dot( ray.dir, pt - ray.origin ); + if( dist < closestDist && dist <= maxDist ) { + closestDist = dist; + hitPoint = pt; + t = tPts[i]; + didHit = true; + } + } + + return didHit; + } + + #endregion + + } + +} \ No newline at end of file diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs new file mode 100644 index 0000000..18f5270 --- /dev/null +++ b/Curves/Polynomial3D.cs @@ -0,0 +1,124 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + public struct Polynomial3D : IParamCurve3Diff { + + public Polynomial x; + public Polynomial y; + public Polynomial z; + + public Vector3 C0 => new(x.c0, y.c0, z.c0); + public Vector3 C1 => new(x.c1, y.c1, z.c1); + public Vector3 C2 => new(x.c2, y.c2, z.c2); + public Vector3 C3 => new(x.c3, y.c3, z.c3); + + public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, _ => throw new IndexOutOfRangeException( "Polynomial3D component index has to be either 0, 1, or 2" ) }; + + public Polynomial3D( Polynomial x, Polynomial y, Polynomial z ) => ( this.x, this.y, this.z ) = ( x, y, z ); + + /// + public Vector3 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t )); + + /// + public Polynomial3D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n )); + + /// + public Bounds GetBounds01() => FloatRange.ToBounds( x.OutputRange01, y.OutputRange01, z.OutputRange01 ); + + #region IParamCurve3Diff interface implementations + + public int Degree => Mathf.Max( (int)x.Degree, (int)y.Degree, (int)z.Degree ); + public Vector3 EvalDerivative( float t ) => Differentiate().Eval( t ); + public Vector3 EvalSecondDerivative( float t ) => Differentiate( 2 ).Eval( t ); + public Vector3 EvalThirdDerivative( float t = 0 ) => Differentiate( 3 ).Eval( 0 ); + + #endregion + + #region Project Point + + /// + public Vector3 ProjectPoint( Vector3 point, int initialSubdivisions = 16, int refinementIterations = 4 ) => ProjectPoint( point, out _, initialSubdivisions, refinementIterations ); + + struct PointProjectSample { + public float t; + public float distDeltaSq; + public Vector3 f; + public Vector3 fp; + } + + static PointProjectSample[] pointProjectGuesses = { default, default, default }; + + /// + public Vector3 ProjectPoint( Vector3 point, out float t, int initialSubdivisions = 16, int refinementIterations = 4 ) { + // define a bezier relative to the test point + Polynomial3D curve = this; + curve.x.c0 -= point.x; // constant coefficient defines the start position + curve.y.c0 -= point.y; + curve.z.c0 -= point.z; + Vector3 curveStart = curve.Eval( 0 ); + Vector3 curveEnd = curve.Eval( 1 ); + + PointProjectSample SampleDistSqDelta( float tSmp ) { + PointProjectSample s = new PointProjectSample { t = tSmp }; + ( s.f, s.fp ) = ( curve.Eval( tSmp ), curve.EvalDerivative( tSmp ) ); + s.distDeltaSq = Vector3.Dot( s.f, s.fp ); + return s; + } + + // find initial candidates + int candidatesFound = 0; + PointProjectSample prevSmp = SampleDistSqDelta( 0 ); + + for( int i = 1; i < initialSubdivisions; i++ ) { + float ti = i / ( initialSubdivisions - 1f ); + PointProjectSample smp = SampleDistSqDelta( ti ); + if( Mathfs.SignAsInt( smp.distDeltaSq ) != Mathfs.SignAsInt( prevSmp.distDeltaSq ) ) { + pointProjectGuesses[candidatesFound++] = SampleDistSqDelta( ( prevSmp.t + smp.t ) / 2 ); + if( candidatesFound == 3 ) break; // no more than three possible candidates because of the polynomial degree + } + + prevSmp = smp; + } + + // refine each guess w. Newton-Raphson iterations + void Refine( ref PointProjectSample smp ) { + Vector3 fpp = curve.EvalSecondDerivative( smp.t ); + float tNew = smp.t - Vector3.Dot( smp.f, smp.fp ) / ( Vector3.Dot( smp.f, fpp ) + Vector3.Dot( smp.fp, smp.fp ) ); + smp = SampleDistSqDelta( tNew ); + } + + for( int p = 0; p < candidatesFound; p++ ) + for( int i = 0; i < refinementIterations; i++ ) + Refine( ref pointProjectGuesses[p] ); + + // Now find closest. First include the endpoints + float sqDist0 = curveStart.sqrMagnitude; // include endpoints + float sqDist1 = curveEnd.sqrMagnitude; + bool firstClosest = sqDist0 < sqDist1; + float tClosest = firstClosest ? 0 : 1; + Vector3 ptClosest = ( firstClosest ? curveStart : curveEnd ) + point; + float distSqClosest = firstClosest ? sqDist0 : sqDist1; + + // then check internal roots + for( int i = 0; i < candidatesFound; i++ ) { + float pSqmag = pointProjectGuesses[i].f.sqrMagnitude; + if( pSqmag < distSqClosest ) { + distSqClosest = pSqmag; + tClosest = pointProjectGuesses[i].t; + ptClosest = pointProjectGuesses[i].f + point; + } + } + + t = tClosest; + return ptClosest; + } + + #endregion + + } + +} \ No newline at end of file diff --git a/Curves/SplineUtils.cs b/Curves/SplineUtils.cs index 1ae546a..9c26ac5 100644 --- a/Curves/SplineUtils.cs +++ b/Curves/SplineUtils.cs @@ -6,79 +6,11 @@ namespace Freya { /// Various utility functions for splines public static class SplineUtils { - /// Returns the cubic factors of the polynomials, of a single component, in the form at³+bt²+ct+d - /// The starting point of the curve - /// The second control point of the curve, sometimes called the start tangent point - /// The third control point of the curve, sometimes called the end tangent point - /// The end point of the curve - public static Polynomial GetCubicPolynomial( float p0, float p1, float p2, float p3 ) => - new Polynomial( - -p0 + 3 * ( p1 - p2 ) + p3, - 3 * ( p0 - 2 * p1 + p2 ), - 3 * ( -p0 + p1 ), - p0 ); - - - /// Returns the cubic factors of the derivative polynomials, of a single component, in the form at²+bt+c - /// The starting point of the curve - /// The second control point of the curve, sometimes called the start tangent point - /// The third control point of the curve, sometimes called the end tangent point - /// The end point of the curve - public static Polynomial GetCubicPolynomialDerivative( float p0, float p1, float p2, float p3 ) => - new Polynomial( - 3 * ( -p0 + 3 * ( p1 - p2 ) + p3 ), - 6 * ( p0 - 2 * p1 + p2 ), - 3 * ( -p0 + p1 ) ); - - /// Returns the cubic factors of the second derivative polynomials, of a single component, in the form at+b - /// The starting point of the curve - /// The second control point of the curve, sometimes called the start tangent point - /// The third control point of the curve, sometimes called the end tangent point - /// The end point of the curve - public static Polynomial GetCubicPolynomialSecondDerivative( float p0, float p1, float p2, float p3 ) => - new Polynomial( - 6 * ( -p0 + 3 * ( p1 - p2 ) + p3 ), - 6 * ( p0 - 2 * p1 + p2 ) ); - - /// Returns the bernstein polynomial weights for positions in the curve at the given point t - /// The t-value along the curve to sample - public static Vector4 GetBernsteinPolynomialWeights( float t ) { - float omt = 1f - t; - float omt2 = omt * omt; - float t2 = t * t; - return new Vector4( - omt2 * omt, // (1-t)³ - 3f * omt2 * t, // 3(1-t)²t - 3f * omt * t2, // 3(1-t)t² - t2 * t // t³ - ); - } - - /// Returns the bernstein polynomial weights for the derivative of the curve at the given point t - /// The t-value along the curve to sample - public static Vector4 GetBernsteinPolynomialWeightsDerivative( float t ) { - float omt = 1f - t; - float omt2 = omt * omt; - float t2 = t * t; - return new Vector4( - -3 * omt2, // -3(1-t)² - 9 * t2 - 12 * t + 3, // 9t²-12t+3 - 6 * t - 9 * t2, // 6t-9t² - 3 * t2 // 3t² - ); - } - - /// Returns the bernstein polynomial weights for the second derivative of the curve at the given point t - /// The t-value along the curve to sample - public static Vector4 GetBernsteinPolynomialWeightsSecondDerivative( float t ) { - return new Vector4( 6 - 6 * t, 18 * t - 12, 6 - 18 * t, 6 * t ); - } - /// Samples a bernstein polynomial bézier basis function /// The degree of the bézier curve /// The basis function index /// The value to sample at - public static float SampleBasisFunction( int degree, int i, float t ) { + public static float SampleBernsteinBasisFunction( int degree, int i, float t ) { ulong bc = Mathfs.BinomialCoef( (uint)degree, (uint)i ); double scale = Math.Pow( 1f - t, degree - i ) * Math.Pow( t, i ); return (float)( bc * scale ); diff --git a/Curves/UBSCubic2D.cs b/Curves/UBSCubic2D.cs index 48557df..a132c25 100644 --- a/Curves/UBSCubic2D.cs +++ b/Curves/UBSCubic2D.cs @@ -160,25 +160,25 @@ public int Count { return new Vector2( x, y ); } - [MethodImpl( INLINE )] public Vector2 GetPoint( float t ) { + [MethodImpl( INLINE )] public Vector2 Eval( float t ) { ReadyCoefficients(); float t2 = t * t; float t3 = t2 * t; return new Vector2( t3 * c3.x + t2 * c2.x + t * c1.x + c0.x, t3 * c3.y + t2 * c2.y + t * c1.y + c0.y ); } - [MethodImpl( INLINE )] public Vector2 GetDerivative( float t ) { + [MethodImpl( INLINE )] public Vector2 EvalDerivative( float t ) { ReadyCoefficients(); float t2 = t * t; return new Vector2( 3 * t2 * c3.x + 2 * t * c2.x + c1.x, 3 * t2 * c3.y + 2 * t * c2.y + c1.y ); } - [MethodImpl( INLINE )] public Vector2 GetSecondDerivative( float t ) { + [MethodImpl( INLINE )] public Vector2 EvalSecondDerivative( float t ) { ReadyCoefficients(); return new Vector2( 6 * t * c3.x + 2 * c2.x, 6 * t * c3.y + 2 * c2.y ); } - [MethodImpl( INLINE )] public Vector2 GetThirdDerivative( float t = 0 ) { + [MethodImpl( INLINE )] public Vector2 EvalThirdDerivative( float t = 0 ) { ReadyCoefficients(); return new Vector2( 6 * c3.x, 6 * c3.y ); } diff --git a/FloatRange.cs b/FloatRange.cs index 66bbab0..02152dd 100644 --- a/FloatRange.cs +++ b/FloatRange.cs @@ -1,5 +1,7 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using UnityEngine; + namespace Freya { /// A value range between two values a and b @@ -65,6 +67,21 @@ public FloatRange Encapsulate( float value ) => _ => ( Mathfs.Min( b, value ), Mathfs.Max( a, value ) ) // reversed - b is min, a is max }; + /// Returns the rectangle encapsulating the region defined by a range per axis. Note: The direction of each range is ignored + /// The range of the X axis + /// The range of the Y axis + public static Rect ToRect( FloatRange rangeX, FloatRange rangeY ) => new Rect( rangeX.Min, rangeY.Min, rangeX.Length, rangeY.Length ); + + /// Returns the bounding box encapsulating the region defined by a range per axis. Note: The direction of each range is ignored + /// The range of the X axis + /// The range of the Y axis + /// The range of the Z axis + public static Bounds ToBounds( FloatRange rangeX, FloatRange rangeY, FloatRange rangeZ ) { + Vector3 center = new ( rangeX.Center, rangeY.Center, rangeZ.Center ); + Vector3 size = new ( rangeX.Length, rangeY.Length, rangeZ.Length ); + return new Bounds( center, size ); + } + public static implicit operator FloatRange( (float a, float b) tuple ) => new FloatRange( tuple.a, tuple.b ); } diff --git a/UtilityTypes.cs b/UtilityTypes.cs index 763ecc8..4f5081b 100644 --- a/UtilityTypes.cs +++ b/UtilityTypes.cs @@ -109,6 +109,19 @@ public static implicit operator ResultsMax3( ResultsMax2 m2 ) { throw new InvalidCastException( "Failed to cast ResultsMax2 to ResultsMax3" ); } + + /// Explicitly casts ResultsMax3 to ResultsMax2 + /// The results to cast + public static explicit operator ResultsMax2( ResultsMax3 m3 ) { + switch( m3.count ) { + case 0: return default; + case 1: return new ResultsMax2( m3.a ); + case 2: return new ResultsMax2( m3.a, m3.b ); + case 3: throw new IndexOutOfRangeException( "Attempt to cast ResultsMax3 to ResultsMax2 when it had 3 results" ); + } + + throw new InvalidCastException( "Failed to cast ResultsMax2 to ResultsMax3" ); + } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); From b5d6c4b010bb80e876bdd28512cc54ebd30cce9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 14 May 2022 18:38:06 +0200 Subject: [PATCH 015/301] updated general beziers mostly removed things and made them more similar to the updated beziers --- Curves/Bezier2D.cs | 29 ++++++----------------------- Curves/Bezier3D.cs | 40 ++++++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/Curves/Bezier2D.cs b/Curves/Bezier2D.cs index 63ae859..a88d147 100644 --- a/Curves/Bezier2D.cs +++ b/Curves/Bezier2D.cs @@ -17,6 +17,11 @@ namespace Freya { readonly Vector2[] ptEvalBuffer; + /// The number of control points in this curve + public int Count { + [MethodImpl( INLINE )] get => points.Length; + } + /// Creates a general bezier curve, from any number of control points /// The control points of this curve public Bezier2D( params Vector2[] points ) { @@ -34,13 +39,6 @@ public Vector2 this[ int i ] { #region Core IParamCurve Implementations - public Vector2 GetStartPoint() => points[0]; - public Vector2 GetEndPoint() => points[Count - 1]; - - public int Count { - [MethodImpl( INLINE )] get => points.Length; - } - /// The degree of the curve, equal to the number of control points minus 1. 2 points = degree 1 (linear), 3 points = degree 2 (quadratic), 4 points = degree 3 (cubic) public int Degree { [MethodImpl( INLINE )] get => points.Length - 1; @@ -57,27 +55,12 @@ public Vector2 Eval( float t ) { } return ptEvalBuffer[0]; - /* // pretty but slow recursive implementation: - return B( Degree, 0 ); - Vector2 B( int k, int i ) { - if( k == 0 ) return points[i]; - return Vector2.LerpUnclamped( B( k - 1, i ), B( k - 1, i + 1 ), t ); - }*/ } #endregion - /// Calculates the weight (influence) of a given point at the given t-value - /// The point to get the weight of - /// The t-value where you want sample the weight value - public float GetPointWeight( int i, float t ) { - if(i < 0 || i >= Count) - throw new IndexOutOfRangeException($"GetPointWeight index {i} is out of range. Valid range is 0 to {Count-1}"); - return SplineUtils.SampleBernsteinBasisFunction( Degree, i, t ); - } - /// Returns the derivative bezier curve if possible, otherwise returns null - public Bezier2D GetDerivative() { + public Bezier2D Differentiate() { int n = Count - 1; if( n == 0 ) return null; // no derivative diff --git a/Curves/Bezier3D.cs b/Curves/Bezier3D.cs index b85f7e6..ab3a3a1 100644 --- a/Curves/Bezier3D.cs +++ b/Curves/Bezier3D.cs @@ -14,9 +14,20 @@ namespace Freya { /// public readonly Vector3[] points; + readonly Vector3[] ptEvalBuffer; + + /// + public int Count { + [MethodImpl( INLINE )] get => points.Length; + } /// - public Bezier3D( Vector3[] points ) => this.points = points; + public Bezier3D( Vector3[] points ) { + this.points = points; + if( points == null || points.Length <= 1 ) + throw new ArgumentException( "Bézier curves require at least two points" ); + ptEvalBuffer = new Vector3[points.Length - 1]; + } /// public Vector3 this[ int i ] { @@ -26,31 +37,28 @@ public Vector3 this[ int i ] { #region Core IParamCurve Implementations - public Vector3 GetStartPoint() => points[0]; - public Vector3 GetEndPoint() => points[Count - 1]; - - public int Count { - [MethodImpl( INLINE )] get => points.Length; - } - - /// The degree of the curve, equal to the number of control points minus 1. 2 points = degree 1 (linear), 3 points = degree 2 (quadratic), 4 points = degree 3 (cubic) + /// public int Degree { [MethodImpl( INLINE )] get => points.Length - 1; } public Vector3 Eval( float t ) { - return B( Degree, 0 ); - - Vector3 B( int k, int i ) { - if( k == 0 ) return points[i]; - return Vector3.LerpUnclamped( B( k - 1, i ), B( k - 1, i + 1 ), t ); // todo: optimize + float n = Count - 1; + for( int i = 0; i < n; i++ ) + ptEvalBuffer[i] = Vector3.LerpUnclamped( points[i], points[i + 1], t ); + while( n > 1 ) { + n--; + for( int i = 0; i < n; i++ ) + ptEvalBuffer[i] = Vector3.LerpUnclamped( ptEvalBuffer[i], ptEvalBuffer[i + 1], t ); } + + return ptEvalBuffer[0]; } #endregion - /// - public Bezier3D GetDerivative() { + /// + public Bezier3D Differentiate() { int n = Count - 1; if( n == 0 ) return null; // no derivative From 5b6ef71f7049b255d315d82ecc9e93321853c237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 14 May 2022 20:30:25 +0200 Subject: [PATCH 016/301] moved cubic beziers into a folder --- Curves/{ => Uniform Spline Segments}/BezierCubic2D.cs | 0 Curves/{ => Uniform Spline Segments}/BezierCubic3D.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Curves/{ => Uniform Spline Segments}/BezierCubic2D.cs (100%) rename Curves/{ => Uniform Spline Segments}/BezierCubic3D.cs (100%) diff --git a/Curves/BezierCubic2D.cs b/Curves/Uniform Spline Segments/BezierCubic2D.cs similarity index 100% rename from Curves/BezierCubic2D.cs rename to Curves/Uniform Spline Segments/BezierCubic2D.cs diff --git a/Curves/BezierCubic3D.cs b/Curves/Uniform Spline Segments/BezierCubic3D.cs similarity index 100% rename from Curves/BezierCubic3D.cs rename to Curves/Uniform Spline Segments/BezierCubic3D.cs From e3edecb70b72c21ecf2d5a321c2aafa1f8c9ecfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 14 May 2022 20:31:06 +0200 Subject: [PATCH 017/301] updated quadratic beziers to use the new interface structure --- .../BezierQuad2D.cs | 95 +++--------------- .../BezierQuad3D.cs | 97 +++---------------- 2 files changed, 22 insertions(+), 170 deletions(-) rename Curves/{ => Uniform Spline Segments}/BezierQuad2D.cs (58%) rename Curves/{ => Uniform Spline Segments}/BezierQuad3D.cs (57%) diff --git a/Curves/BezierQuad2D.cs b/Curves/Uniform Spline Segments/BezierQuad2D.cs similarity index 58% rename from Curves/BezierQuad2D.cs rename to Curves/Uniform Spline Segments/BezierQuad2D.cs index 0bd5e86..81d7c20 100644 --- a/Curves/BezierQuad2D.cs +++ b/Curves/Uniform Spline Segments/BezierQuad2D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized 2D quadratic bezier curve, with 3 control points - [Serializable] public struct BezierQuad2D : IParamCurve2Diff { + [Serializable] public struct BezierQuad2D : IParamCubicSplineSegment2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -19,7 +19,15 @@ namespace Freya { public BezierQuad2D( Vector2 p0, Vector2 p1, Vector2 p2 ) { ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); validCoefficients = false; - c2 = c1 = default; + curve = default; + } + + Polynomial2D curve; + public Polynomial2D Curve { + get { + ReadyCoefficients(); + return curve; + } } #region Control Points @@ -75,85 +83,17 @@ public Vector2 this[ int i ] { #region Coefficients [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) - [NonSerialized] Vector2 c2, c1; // cached coefficients for fast evaluation. c0 = p0 // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update validCoefficients = true; - c2.x = p0.x - 2 * p1.x + p2.x; - c1.x = 2 * ( p1.x - p0.x ); - c2.y = p0.y - 2 * p1.y + p2.y; - c1.y = 2 * ( p1.y - p0.y ); - } - - /// The constant coefficient when evaluating this curve in the form C2*t² + C1*t + C0 - public Vector2 C0 { - [MethodImpl( INLINE )] get => p0; - } - - /// The linear coefficient when evaluating this curve in the form C2*t² + C1*t + C0 - public Vector2 C1 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c1; - } - } - - /// The quadratic coefficient when evaluating this curve in the form C2*t² + C1*t + C0 - public Vector2 C2 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c2; - } - } - - /// The polynomial coefficients in the form c2*t² + c1*t + c0 - [MethodImpl( INLINE )] public (Vector2 c2, Vector2 c1, Vector2 c0) GetCoefficients() { - ReadyCoefficients(); - return ( c2, c1, p0 ); + curve = CharMatrix.quadraticBezier.GetCurve( p0, p1, p2 ); } #endregion - // todo: Object Comparison & ToString - - // todo: Type Casting - - #region Core IParamCurve Implementations - - public int Degree { - [MethodImpl( INLINE )] get => 2; - } - public int Count { - [MethodImpl( INLINE )] get => 3; - } - - [MethodImpl( INLINE )] public Vector2 GetStartPoint() => p0; - [MethodImpl( INLINE )] public Vector2 GetEndPoint() => p2; - - public Vector2 Eval( float t ) { - ReadyCoefficients(); - float tt = t * t; - return new Vector2( c2.x * tt + c1.x * t + p0.x, c2.y * tt + c1.y * t + p0.y ); - } - - public Vector2 EvalDerivative( float t ) { - ReadyCoefficients(); - float tx2 = 2 * t; - return new Vector2( tx2 * c2.x + c1.x, tx2 * c2.y + c1.y ); - } - - public Vector2 EvalSecondDerivative( float t = 0 ) { - ReadyCoefficients(); - return new Vector2( 2 * c2.x, 2 * c2.y ); - } - - #endregion - - // todo: Point Components - /// public BezierQuad2D Split( float t ) { Vector2 mid = Vector2.LerpUnclamped( p0, p1, t ); @@ -162,19 +102,6 @@ public BezierQuad2D Split( float t ) { return new BezierQuad2D( p0, mid, end ); } - // todo: Bounds - - // todo: exact length - - // todo: Project Point - - // todo: Intersection Tests - - // todo: Polynomial Factors - - // todo: Local Extrema - - } } \ No newline at end of file diff --git a/Curves/BezierQuad3D.cs b/Curves/Uniform Spline Segments/BezierQuad3D.cs similarity index 57% rename from Curves/BezierQuad3D.cs rename to Curves/Uniform Spline Segments/BezierQuad3D.cs index 6cc61e3..e71bc4b 100644 --- a/Curves/BezierQuad3D.cs +++ b/Curves/Uniform Spline Segments/BezierQuad3D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized 3D quadratic bezier curve, with 3 control points - [Serializable] public struct BezierQuad3D : IParamCurve2Diff { + [Serializable] public struct BezierQuad3D : IParamCubicSplineSegment3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -19,7 +19,15 @@ namespace Freya { public BezierQuad3D( Vector3 p0, Vector3 p1, Vector3 p2 ) { ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); validCoefficients = false; - c2 = c1 = default; + curve = default; + } + + Polynomial3D curve; + public Polynomial3D Curve { + get { + ReadyCoefficients(); + return curve; + } } #region Control Points @@ -75,87 +83,17 @@ public Vector3 this[ int i ] { #region Coefficients [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) - [NonSerialized] Vector3 c2, c1; // cached coefficients for fast evaluation. c0 = p0 // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update validCoefficients = true; - c2.x = p0.x - 2 * p1.x + p2.x; - c2.y = p0.y - 2 * p1.y + p2.y; - c2.z = p0.z - 2 * p1.z + p2.z; - c1.x = 2 * ( p1.x - p0.x ); - c1.y = 2 * ( p1.y - p0.y ); - c1.z = 2 * ( p1.z - p0.z ); - } - - /// The constant coefficient when evaluating this curve in the form C2*t² + C1*t + C0 - public Vector3 C0 { - [MethodImpl( INLINE )] get => p0; - } - - /// The linear coefficient when evaluating this curve in the form C2*t² + C1*t + C0 - public Vector3 C1 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c1; - } - } - - /// The quadratic coefficient when evaluating this curve in the form C2*t² + C1*t + C0 - public Vector3 C2 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c2; - } - } - - /// The polynomial coefficients in the form c2*t² + c1*t + c0 - [MethodImpl( INLINE )] public (Vector3 c2, Vector3 c1, Vector3 c0) GetCoefficients() { - ReadyCoefficients(); - return ( c2, c1, p0 ); + curve = CharMatrix.quadraticBezier.GetCurve( p0, p1, p2 ); } #endregion - // todo: Object Comparison & ToString - - // todo: Type Casting - - #region Core IParamCurve Implementations - - public int Degree { - [MethodImpl( INLINE )] get => 2; - } - public int Count { - [MethodImpl( INLINE )] get => 3; - } - - [MethodImpl( INLINE )] public Vector3 GetStartPoint() => p0; - [MethodImpl( INLINE )] public Vector3 GetEndPoint() => p2; - - public Vector3 Eval( float t ) { - ReadyCoefficients(); - float tt = t * t; - return new Vector3( c2.x * tt + c1.x * t + p0.x, c2.y * tt + c1.y * t + p0.y, c2.z * tt + c1.z * t + p0.z ); - } - - public Vector3 EvalDerivative( float t ) { - ReadyCoefficients(); - float tx2 = 2 * t; - return new Vector3( tx2 * c2.x + c1.x, tx2 * c2.y + c1.y, tx2 * c2.z + c1.z ); - } - - public Vector3 EvalSecondDerivative( float t = 0 ) { - ReadyCoefficients(); - return new Vector3( 2 * c2.x, 2 * c2.y, 2 * c2.z ); - } - - #endregion - - // todo: Point Components - /// public BezierQuad3D Split( float t ) { Vector3 mid = Vector3.LerpUnclamped( p0, p1, t ); @@ -164,19 +102,6 @@ public BezierQuad3D Split( float t ) { return new BezierQuad3D( p0, mid, end ); } - // todo: Bounds - - // todo: exact length - - // todo: Project Point - - // todo: Intersection Tests - - // todo: Polynomial Factors - - // todo: Local Extrema - - } } \ No newline at end of file From e6dbdaa493186913ff7dc95ff4cf50729f726ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 14 May 2022 21:02:21 +0200 Subject: [PATCH 018/301] updated uniform cubic B-spline to match new api --- Curves/UBSCubic2D.cs | 137 +++++++------------------------------------ 1 file changed, 22 insertions(+), 115 deletions(-) diff --git a/Curves/UBSCubic2D.cs b/Curves/UBSCubic2D.cs index a132c25..cf4ec4d 100644 --- a/Curves/UBSCubic2D.cs +++ b/Curves/UBSCubic2D.cs @@ -5,7 +5,7 @@ namespace Freya { /// An optimized 2D uniform B-spline segment - [Serializable] public struct UBSCubic2D : IParamCurve3Diff { + [Serializable] public struct UBSCubic2D : IParamCubicSplineSegment2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -17,7 +17,15 @@ namespace Freya { public UBSCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); validCoefficients = false; - c3 = c2 = c1 = c0 = default; + curve = default; + } + + Polynomial2D curve; + public Polynomial2D Curve { + get { + ReadyCoefficients(); + return curve; + } } #region Control Points @@ -83,118 +91,29 @@ public Vector2 this[ int i ] { #region Coefficients [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) - [NonSerialized] Vector2 c3, c2, c1, c0; // cached coefficients for fast evaluation // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update validCoefficients = true; - const float _6th = 1 / 6f; - c3.x = _6th * ( -p0.x + 3 * ( p1.x - p2.x ) + p3.x ); - c2.x = 0.5f * ( p0.x - 2 * p1.x + p2.x ); - c1.x = 0.5f * ( -p0.x + p2.x ); - c0.x = _6th * ( p0.x + 4 * p1.x + p2.x ); - - c3.y = _6th * ( -p0.y + 3 * ( p1.y - p2.y ) + p3.y ); - c2.y = 0.5f * ( p0.y - 2 * p1.y + p2.y ); - c1.y = 0.5f * ( -p0.y + p2.y ); - c0.y = _6th * ( p0.y + 4 * p1.y + p2.y ); - } - - /// The constant coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C0 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c0; - } - } - - /// The linear coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C1 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c1; - } - } - - /// The quadratic coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C2 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c2; - } - } - - /// The cubic coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C3 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c3; - } - } - - /// The polynomial coefficients in the form c3*t³ + c2*t² + c1*t + c0 - [MethodImpl( INLINE )] public (Vector2 c3, Vector2 c2, Vector2 c1, Vector2 c0) GetCoefficients() { - ReadyCoefficients(); - return ( c3, c2, c1, c0 ); + curve = CharMatrix.cubicUniformBspline.GetCurve( p0, p1, p2, p3 ); } #endregion - - #region Core IParamCurve Implementations - - public int Degree { - [MethodImpl( INLINE )] get => 3; - } - public int Count { - [MethodImpl( INLINE )] get => 4; - } - - [MethodImpl( INLINE )] public Vector2 GetStartPoint() => C0; - - [MethodImpl( INLINE )] public Vector2 GetEndPoint() { - ReadyCoefficients(); - float x = c0.x + c1.x + c2.x + c3.x; - float y = c0.y + c1.y + c2.y + c3.y; - return new Vector2( x, y ); - } - - [MethodImpl( INLINE )] public Vector2 Eval( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return new Vector2( t3 * c3.x + t2 * c2.x + t * c1.x + c0.x, t3 * c3.y + t2 * c2.y + t * c1.y + c0.y ); - } - - [MethodImpl( INLINE )] public Vector2 EvalDerivative( float t ) { - ReadyCoefficients(); - float t2 = t * t; - return new Vector2( 3 * t2 * c3.x + 2 * t * c2.x + c1.x, 3 * t2 * c3.y + 2 * t * c2.y + c1.y ); - } - - [MethodImpl( INLINE )] public Vector2 EvalSecondDerivative( float t ) { - ReadyCoefficients(); - return new Vector2( 6 * t * c3.x + 2 * c2.x, 6 * t * c3.y + 2 * c2.y ); - } - - [MethodImpl( INLINE )] public Vector2 EvalThirdDerivative( float t = 0 ) { - ReadyCoefficients(); - return new Vector2( 6 * c3.x, 6 * c3.y ); - } - - #endregion - + /// Returns the exact cubic bézier representation of this segment public BezierCubic2D ToBezier() { - float ax = p0.x + ( 2f / 3f ) * ( p1.x - p0.x ); - float bx = p1.x + ( 1f / 3f ) * ( p2.x - p1.x ); - float cx = p1.x + ( 2f / 3f ) * ( p2.x - p1.x ); - float dx = p2.x + ( 1f / 3f ) * ( p3.x - p2.x ); - float ay = p0.y + ( 2f / 3f ) * ( p1.y - p0.y ); - float by = p1.y + ( 1f / 3f ) * ( p2.y - p1.y ); - float cy = p1.y + ( 2f / 3f ) * ( p2.y - p1.y ); - float dy = p2.y + ( 1f / 3f ) * ( p3.y - p2.y ); + const float _13 = 1f / 3f; + const float _23 = 2f / 3f; + float ax = p0.x + _23 * ( p1.x - p0.x ); + float bx = p1.x + _13 * ( p2.x - p1.x ); + float cx = p1.x + _23 * ( p2.x - p1.x ); + float dx = p2.x + _13 * ( p3.x - p2.x ); + float ay = p0.y + _23 * ( p1.y - p0.y ); + float by = p1.y + _13 * ( p2.y - p1.y ); + float cy = p1.y + _23 * ( p2.y - p1.y ); + float dy = p2.y + _13 * ( p3.y - p2.y ); return new BezierCubic2D( new Vector2( 0.5f * ( ax + bx ), 0.5f * ( ay + by ) ), new Vector2( bx, by ), @@ -203,18 +122,6 @@ public BezierCubic2D ToBezier() { ); } - /// Get the basis function for the given point, by index - /// The index of the point (0, 1, 2 or 3) - public static Polynomial GetBasisFunction( int i ) { - return i switch { - 0 => new Polynomial( -1, 3, -3, 1 ) / 6f, - 1 => new Polynomial( 3, -6, 0, 4 ) / 6f, - 2 => new Polynomial( -3, 3, 3, 1 ) / 6f, - 3 => new Polynomial( 1, 0, 0, 0 ) / 6f, - _ => throw new IndexOutOfRangeException( "Cubic B-Spline index needs to be between 0 and 3" ) - }; - } - } } \ No newline at end of file From 4fca06f7a463d07c1f953edbfc2ccc8f272ae79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 14 May 2022 21:02:49 +0200 Subject: [PATCH 019/301] moved UBS cubic --- Curves/{ => Uniform Spline Segments}/UBSCubic2D.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Curves/{ => Uniform Spline Segments}/UBSCubic2D.cs (100%) diff --git a/Curves/UBSCubic2D.cs b/Curves/Uniform Spline Segments/UBSCubic2D.cs similarity index 100% rename from Curves/UBSCubic2D.cs rename to Curves/Uniform Spline Segments/UBSCubic2D.cs From 879b2b833d345c77a1f9e46926fd1b148468c662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 14 May 2022 21:29:05 +0200 Subject: [PATCH 020/301] updated characteristic matrices --- Curves/CharMatrix.cs | 128 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 22 deletions(-) diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs index 45de47b..3e25855 100644 --- a/Curves/CharMatrix.cs +++ b/Curves/CharMatrix.cs @@ -3,40 +3,49 @@ namespace Freya { - /// Data structure representing a characteristic up to a cubic. Used for spline evaluation + public readonly struct CharMatrix { + /// The characteristic matrix of a quadratic bézier curve + public static readonly CharMatrix3x3 quadraticBezier = new( + 1, 0, 0, + -2, 2, 0, + 1, -2, 1 + ); + + /// The characteristic matrix of a cubic bézier curve + public static readonly CharMatrix4x4 cubicBezier = new( + 1, 0, 0, 0, + -3, 3, 0, 0, + 3, -6, 3, 0, + -1, 3, -3, 1 + ); + + /// The characteristic matrix of a cubic uniform B-spline segment + public static readonly CharMatrix4x4 cubicUniformBspline = new CharMatrix4x4( + 1, 4, 1, 0, + -3, 0, 3, 0, + 3, -6, 3, 0, + -1, 3, -3, 1 + ) / 6; + + + } + + /// Data structure representing a cubic characteristic matrix with 4 points. Used for spline evaluation + public readonly struct CharMatrix4x4 { public readonly float m00, m01, m02, m03; public readonly float m10, m11, m12, m13; public readonly float m20, m21, m22, m23; public readonly float m30, m31, m32, m33; - public CharMatrix( float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33 ) { + public CharMatrix4x4( float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33 ) { ( this.m00, this.m01, this.m02, this.m03 ) = ( m00, m01, m02, m03 ); ( this.m10, this.m11, this.m12, this.m13 ) = ( m10, m11, m12, m13 ); ( this.m20, this.m21, this.m22, this.m23 ) = ( m20, m21, m22, m23 ); ( this.m30, this.m31, this.m32, this.m33 ) = ( m30, m31, m32, m33 ); } - public static readonly CharMatrix cubicBezier = new( - 1, 0, 0, 0, - -3, 3, 0, 0, - 3, -6, 3, 0, - -1, 3, -3, 1 - ); - - public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => - new( - GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), - GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ) - ); - public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => - new( - GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), - GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ), - GetEvalPolynomial( p0.z, p1.z, p2.z, p3.z ) - ); - /// Returns the basis function (weight) for the given point by index i, /// equal to the t-matrix multiplied by the characteristic matrix /// The point index to get the basis function of @@ -46,7 +55,7 @@ public Polynomial GetBasisFunction( int i ) { 1 => new Polynomial( m31, m21, m11, m01 ), 2 => new Polynomial( m32, m22, m12, m02 ), 3 => new Polynomial( m33, m23, m13, m03 ), - _ => throw new IndexOutOfRangeException( "Bézier basis index needs to be between 0 and 3" ) + _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) }; } @@ -63,6 +72,81 @@ public Polynomial GetEvalPolynomial( float p0, float p1, float p2, float p3 ) => p0 * m10 + p1 * m11 + p2 * m12 + p3 * m13, p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03); + /// Returns the curve this characteristic matrix represents, given 4 points + /// The first point + /// The second point + /// The third point + /// The fourth point + public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => + new( + GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), + GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ) + ); + + /// Returns the curve this characteristic matrix represents, given 4 points + /// The first point + /// The second point + /// The third point + /// The fourth point + public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => + new( + GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), + GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ), + GetEvalPolynomial( p0.z, p1.z, p2.z, p3.z ) + ); + + public static CharMatrix4x4 operator *( CharMatrix4x4 c, float v ) => + new(c.m00 * v, c.m01 * v, c.m02 * v, c.m03 * v, + c.m10 * v, c.m11 * v, c.m12 * v, c.m13 * v, + c.m20 * v, c.m21 * v, c.m22 * v, c.m23 * v, + c.m30 * v, c.m31 * v, c.m32 * v, c.m33 * v); + + public static CharMatrix4x4 operator /( CharMatrix4x4 c, float v ) => c * ( 1f / v ); + } + + /// Data structure representing a cubic characteristic matrix with 4 points. Used for spline evaluation + public readonly struct CharMatrix3x3 { + public readonly float m00, m01, m02; + public readonly float m10, m11, m12; + public readonly float m20, m21, m22; + + public CharMatrix3x3( float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22 ) { + ( this.m00, this.m01, this.m02 ) = ( m00, m01, m02 ); + ( this.m10, this.m11, this.m12 ) = ( m10, m11, m12 ); + ( this.m20, this.m21, this.m22 ) = ( m20, m21, m22 ); + } + + /// + public Polynomial GetBasisFunction( int i ) { + return i switch { + 0 => Polynomial.Quadratic( m20, m10, m00 ), + 1 => Polynomial.Quadratic( m21, m11, m01 ), + 2 => Polynomial.Quadratic( m22, m12, m02 ), + _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 2" ) + }; + } + + /// + public Polynomial GetEvalPolynomial( float p0, float p1, float p2 ) => + Polynomial.Quadratic( + p0 * m20 + p1 * m21 + p2 * m22, + p0 * m10 + p1 * m11 + p2 * m12, + p0 * m00 + p1 * m01 + p2 * m02 ); + + /// + public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2 ) => + new( + GetEvalPolynomial( p0.x, p1.x, p2.x ), + GetEvalPolynomial( p0.y, p1.y, p2.y ) + ); + + /// + public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2 ) => + new( + GetEvalPolynomial( p0.x, p1.x, p2.x ), + GetEvalPolynomial( p0.y, p1.y, p2.y ), + GetEvalPolynomial( p0.z, p1.z, p2.z ) + ); } } \ No newline at end of file From 2d71c670b8a2c0687a4f67be8830b6504249cac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 16 May 2022 13:30:42 +0200 Subject: [PATCH 021/301] updated Hermite2D, and renamed it to HermiteCubic2D --- Curves/CatRom2D.cs | 2 +- Curves/CharMatrix.cs | 11 +- Curves/Hermite2D.cs | 146 ------------------ Curves/HermiteCubic2D.cs | 83 ++++++++++ Curves/Polynomial2D.cs | 4 +- .../Uniform Spline Segments/BezierCubic2D.cs | 6 +- 6 files changed, 97 insertions(+), 155 deletions(-) delete mode 100644 Curves/Hermite2D.cs create mode 100644 Curves/HermiteCubic2D.cs diff --git a/Curves/CatRom2D.cs b/Curves/CatRom2D.cs index f97a09f..adc9fed 100644 --- a/Curves/CatRom2D.cs +++ b/Curves/CatRom2D.cs @@ -212,9 +212,9 @@ public BezierCubic2D ToBezier() { return new BezierCubic2D( p1, p1 + m1 / 3, p2 - m2 / 3, p2 ); } - public Hermite2D ToHermite() { ( Vector2 m1, Vector2 m2 ) = GetPointTangents(); return new Hermite2D( p1, m1, p2, m2 ); + public HermiteCubic2D ToHermite() { } } diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs index 3e25855..0c71717 100644 --- a/Curves/CharMatrix.cs +++ b/Curves/CharMatrix.cs @@ -21,7 +21,15 @@ public readonly struct CharMatrix { -1, 3, -3, 1 ); - /// The characteristic matrix of a cubic uniform B-spline segment + /// The characteristic matrix of a uniform cubic hermite curve + public static readonly CharMatrix4x4 cubicHermite = new( + 1, 0, 0, 0, + 0, 1, 0, 0, + -3, -2, 3, -1, + 2, 1, -2, 1 + ); + + /// The characteristic matrix of a uniform cubic B-spline segment public static readonly CharMatrix4x4 cubicUniformBspline = new CharMatrix4x4( 1, 4, 1, 0, -3, 0, 3, 0, @@ -29,7 +37,6 @@ public readonly struct CharMatrix { -1, 3, -3, 1 ) / 6; - } /// Data structure representing a cubic characteristic matrix with 4 points. Used for spline evaluation diff --git a/Curves/Hermite2D.cs b/Curves/Hermite2D.cs deleted file mode 100644 index c9f0f3f..0000000 --- a/Curves/Hermite2D.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using UnityEngine; - -namespace Freya { - - /// An optimized 2D cubic Hermite curve segment - [Serializable] public struct Hermite2D : IParamCurve3Diff { - - const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - - /// Creates a cubic Hermite curve, from two control points and two tangents - /// The starting point of the curve - /// The relative tangent vector of the start point - /// The end point of the curve - /// The relative tangent vector of the end point - public Hermite2D( Vector2 p0, Vector2 m0, Vector2 p1, Vector2 m1 ) { - ( this.p0, this.m0, this.p1, this.m1 ) = ( p0, m0, p1, m1 ); - validCoefficients = false; - c3 = c2 = default; - } - - #region Control Points - - [SerializeField] Vector2 p0, m0, p1, m1; // the points & tangents of the curve - - /// The starting point of the curve - public Vector2 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); - } - - /// The second control point of the curve, sometimes called the start tangent point - public Vector2 M0 { - [MethodImpl( INLINE )] get => m0; - [MethodImpl( INLINE )] set => _ = ( m0 = value, validCoefficients = false ); - } - - /// The third control point of the curve, sometimes called the end tangent point - public Vector2 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); - } - - /// The end point of the curve - public Vector2 M1 { - [MethodImpl( INLINE )] get => m1; - [MethodImpl( INLINE )] set => _ = ( m1 = value, validCoefficients = false ); - } - - #endregion - - #region Coefficients - - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) - [NonSerialized] Vector2 c3, c2; // cached coefficients for fast evaluation. c0 = p0. c1 = m0 - - // Coefficient Calculation - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - c3.x = ( 2 * ( p0.x - p1.x ) + m0.x + m1.x ); - c2.x = ( 3 * ( p1.x - p0.x ) - 2 * m0.x - m1.x ); - c3.y = ( 2 * ( p0.y - p1.y ) + m0.y + m1.y ); - c2.y = ( 3 * ( p1.y - p0.y ) - 2 * m0.y - m1.y ); - // c1 = m0 - // c0 = p0 - } - - /// The constant coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C0 { - [MethodImpl( INLINE )] get => p0; - } - - /// The linear coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C1 { - [MethodImpl( INLINE )] get => m0; - } - - /// The quadratic coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C2 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c2; - } - } - - /// The cubic coefficient when evaluating this curve in the form C3*t³ + C2*t² + C1*t + C0 - public Vector2 C3 { - [MethodImpl( INLINE )] get { - ReadyCoefficients(); - return c3; - } - } - - /// The polynomial coefficients in the form c3*t³ + c2*t² + c1*t + c0 - [MethodImpl( INLINE )] public (Vector2 c3, Vector2 c2, Vector2 c1, Vector2 c0) GetCoefficients() { - ReadyCoefficients(); - return ( c3, c2, m0, p0 ); - } - - #endregion - - #region Core IParamCurve Implementations - - public int Degree { - [MethodImpl( INLINE )] get => 3; - } - public int Count { - [MethodImpl( INLINE )] get => 4; - } - - [MethodImpl( INLINE )] public Vector2 GetStartPoint() => p0; - [MethodImpl( INLINE )] public Vector2 GetEndPoint() => p1; - - [MethodImpl( INLINE )] public Vector2 Eval( float t ) { - ReadyCoefficients(); - float t2 = t * t; - float t3 = t2 * t; - return new Vector2( t3 * c3.x + t2 * c2.x + t * m0.x + p0.x, t3 * c3.y + t2 * c2.y + t * m0.y + p0.y ); - } - - [MethodImpl( INLINE )] public Vector2 EvalDerivative( float t ) { - ReadyCoefficients(); - float t2 = t * t; - return new Vector2( 3 * t2 * c3.x + 2 * t * c2.x + m0.x, 3 * t2 * c3.y + 2 * t * c2.y + m0.y ); - } - - [MethodImpl( INLINE )] public Vector2 EvalSecondDerivative( float t ) { - ReadyCoefficients(); - return new Vector2( 6 * t * c3.x + 2 * c2.x, 6 * t * c3.y + 2 * c2.y ); - } - - [MethodImpl( INLINE )] public Vector2 EvalThirdDerivative( float t = 0 ) { - ReadyCoefficients(); - return new Vector2( 6 * c3.x, 6 * c3.y ); - } - - #endregion - - public BezierCubic2D ToBezier() => new BezierCubic2D( p0, p0 + m0 / 3, p1 - m1 / 3, p1 ); - - } - -} \ No newline at end of file diff --git a/Curves/HermiteCubic2D.cs b/Curves/HermiteCubic2D.cs new file mode 100644 index 0000000..346eadf --- /dev/null +++ b/Curves/HermiteCubic2D.cs @@ -0,0 +1,83 @@ +using System; +using System.Runtime.CompilerServices; +using UnityEngine; +using UnityEngine.Serialization; + +namespace Freya { + + /// An optimized 2D cubic Hermite curve segment + [Serializable] public struct HermiteCubic2D : IParamCubicSplineSegment2D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Creates a cubic Hermite curve, from two control points and two tangents + /// The starting point of the curve + /// The rate of change (velocity) at the start of the curve + /// The end point of the curve + /// The rate of change (velocity) at the end of the curve + public HermiteCubic2D( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) { + ( this.p0, this.v0, this.p1, this.v1 ) = ( p0, v0, p1, v1 ); + validCoefficients = false; + curve = default; + } + + Polynomial2D curve; + public Polynomial2D Curve { + get { + ReadyCoefficients(); + return curve; + } + } + + #region Control Points + + [SerializeField] Vector2 p0; + [FormerlySerializedAs( "m0" )] [SerializeField] Vector2 v0; + [SerializeField] Vector2 p1; + [FormerlySerializedAs( "m1" )] [SerializeField] Vector2 v1; + + /// The starting point of the curve + public Vector2 P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// The rate of change (velocity) at the start of the curve + public Vector2 V0 { + [MethodImpl( INLINE )] get => v0; + [MethodImpl( INLINE )] set => _ = ( v0 = value, validCoefficients = false ); + } + + /// The end point of the curve + public Vector2 P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// The rate of change (velocity) at the end of the curve + public Vector2 V1 { + [MethodImpl( INLINE )] get => v1; + [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); + } + + #endregion + + #region Coefficients + + [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + + // Coefficient Calculation + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicHermite.GetCurve( p0, v0, p1, v1 ); + } + + #endregion + + public BezierCubic2D ToBezier() => new BezierCubic2D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); + + } + +} \ No newline at end of file diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index 04ec037..f793f2c 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -28,9 +28,9 @@ public struct Polynomial2D : IParamCurve3Diff { /// Returns the tight axis-aligned bounds of the curve in the unit interval public Rect GetBounds01() => FloatRange.ToRect( x.OutputRange01, y.OutputRange01 ); - public Hermite2D ToHermiteCurve() { + public HermiteCubic2D ToHermiteCurve() { Polynomial2D d = Differentiate(); - return new Hermite2D( Eval( 0 ), d.Eval( 0 ), Eval( 1 ), d.Eval( 1 ) ); + return new HermiteCubic2D( Eval( 0 ), d.Eval( 0 ), Eval( 1 ), d.Eval( 1 ) ); } #region IParamCurve3Diff interface implementations diff --git a/Curves/Uniform Spline Segments/BezierCubic2D.cs b/Curves/Uniform Spline Segments/BezierCubic2D.cs index b6838fe..daebd57 100644 --- a/Curves/Uniform Spline Segments/BezierCubic2D.cs +++ b/Curves/Uniform Spline Segments/BezierCubic2D.cs @@ -136,8 +136,6 @@ public static explicit operator BezierCubic3D( BezierCubic2D bezierCubic2D ) { #endregion - // Whole-curve properties & functions - #region Interpolation /// Returns linear blend between two bézier curves @@ -220,9 +218,9 @@ public CatRom2D ToUniformCubicCatRom() { p0 - 6 * p2 + 6 * p3 ); } - public Hermite2D ToHermite() { + public HermiteCubic2D ToHermite() { // todo: channel split for performance - return new Hermite2D( p0, ( p1 - p0 ) * 3, p3, ( p3 - p2 ) * 3 ); + return new HermiteCubic2D( p0, ( p1 - p0 ) * 3, p3, ( p3 - p2 ) * 3 ); } #endregion From 2932502ade3b5e362ca8c1d9ee530a635fe5cbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 13:31:29 +0200 Subject: [PATCH 022/301] coefficient constructor/props for Polynomial2D --- Curves/Polynomial2D.cs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index f793f2c..b4117cf 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -5,20 +5,38 @@ namespace Freya { + [Serializable] public struct Polynomial2D : IParamCurve3Diff { public Polynomial x; public Polynomial y; - public Vector2 C0 => new(x.c0, y.c0); - public Vector2 C1 => new(x.c1, y.c1); - public Vector2 C2 => new(x.c2, y.c2); - public Vector2 C3 => new(x.c3, y.c3); + public Vector2 C0 { + get => new(x.c0, y.c0); + set => ( x.c0, y.c0 ) = ( value.x, value.y ); + } + public Vector2 C1 { + get => new(x.c1, y.c1); + set => ( x.c1, y.c1 ) = ( value.x, value.y ); + } + public Vector2 C2 { + get => new(x.c2, y.c2); + set => ( x.c2, y.c2 ) = ( value.x, value.y ); + } + public Vector2 C3 { + get => new(x.c3, y.c3); + set => ( x.c3, y.c3 ) = ( value.x, value.y ); + } public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( "Polynomial2D component index has to be either 0 or 1" ) }; public Polynomial2D( Polynomial x, Polynomial y ) => ( this.x, this.y ) = ( x, y ); + public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2, Vector2 c3 ) { + this.x = new Polynomial( c3.x, c2.x, c1.x, c0.x ); + this.y = new Polynomial( c3.y, c2.y, c1.y, c0.y ); + } + /// public Vector2 Eval( float t ) => new(x.Eval( t ), y.Eval( t )); From f3250100926b2de39f0d75dfea78590a46b6c776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 13:33:38 +0200 Subject: [PATCH 023/301] added CatRomCubic2D --- Curves/CharMatrix.cs | 7 + .../Uniform Spline Segments/BezierCubic2D.cs | 5 +- .../Uniform Spline Segments/CatRomCubic2D.cs | 174 ++++++++++++++++++ 3 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 Curves/Uniform Spline Segments/CatRomCubic2D.cs diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs index 0c71717..e608e9f 100644 --- a/Curves/CharMatrix.cs +++ b/Curves/CharMatrix.cs @@ -29,6 +29,13 @@ public readonly struct CharMatrix { 2, 1, -2, 1 ); + /// The characteristic matrix of a uniform cubic catmull-rom curve + public static readonly CharMatrix4x4 cubicCatmullRom = new CharMatrix4x4( + 0, 2, 0, 0, + -1, 0, 1, 0, + 2, -5, 4, -1, + -1, 3, -3, 1 + ) / 2; /// The characteristic matrix of a uniform cubic B-spline segment public static readonly CharMatrix4x4 cubicUniformBspline = new CharMatrix4x4( 1, 4, 1, 0, diff --git a/Curves/Uniform Spline Segments/BezierCubic2D.cs b/Curves/Uniform Spline Segments/BezierCubic2D.cs index daebd57..63d7edc 100644 --- a/Curves/Uniform Spline Segments/BezierCubic2D.cs +++ b/Curves/Uniform Spline Segments/BezierCubic2D.cs @@ -197,7 +197,6 @@ public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { #endregion - #region Conversion public UBSCubic2D ToUniformCubicBSpline() { @@ -209,9 +208,9 @@ public UBSCubic2D ToUniformCubicBSpline() { 2 * p1 - 7 * p2 + 6 * p3 ); } - public CatRom2D ToUniformCubicCatRom() { + public CatRomCubic2D ToUniformCubicCatRom() { // todo: channel split for performance - return new CatRom2D( + return new CatRomCubic2D( 6 * p0 - 6 * p1 + p3, p0, p3, diff --git a/Curves/Uniform Spline Segments/CatRomCubic2D.cs b/Curves/Uniform Spline Segments/CatRomCubic2D.cs new file mode 100644 index 0000000..de01bbf --- /dev/null +++ b/Curves/Uniform Spline Segments/CatRomCubic2D.cs @@ -0,0 +1,174 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform cubic catmull-rom 2D curve, with 4 control points + [Serializable] public struct CatRomCubic2D : IParamCubicSplineSegment2D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Creates a uniform cubic catmull-rom curve, from 4 control points + /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The second control point, and the start of the catmull-rom curve + /// The third control point, and the end of the catmull-rom curve + /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public CatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + validCoefficients = false; + curve = default; + } + + Polynomial2D curve; + public Polynomial2D Curve { + get { + ReadyCoefficients(); + return curve; + } + } + + #region Control Points + + [SerializeField] Vector2 p0, p1, p2, p3; // the points of the curve + + /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public Vector2 P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// The second control point, and the start of the catmull-rom curve + public Vector2 P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// The third control point, and the end of the catmull-rom curve + public Vector2 P2 { + [MethodImpl( INLINE )] get => p2; + [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + } + + /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public Vector2 P3 { + [MethodImpl( INLINE )] get => p3; + [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + } + + /// Get or set a control point position by index. Valid indices: 0, 1, 2 or 3 + public Vector2 this[ int i ] { + get { + switch( i ) { + case 0: return P0; + case 1: return P1; + case 2: return P2; + case 3: return P3; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + P1 = value; + break; + case 2: + P2 = value; + break; + case 3: + P3 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + + #endregion + + #region Coefficients + + [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + + // Coefficient Calculation + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicCatmullRom.GetCurve( p0, p1, p2, p3 ); + } + + #endregion + + #region Object Comparison & ToString + + public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); + public bool Equals( CatRomCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is CatRomCubic2D other && Equals( other ); + + public override int GetHashCode() { + unchecked { + int hashCode = P0.GetHashCode(); + hashCode = ( hashCode * 397 ) ^ P1.GetHashCode(); + hashCode = ( hashCode * 397 ) ^ P2.GetHashCode(); + hashCode = ( hashCode * 397 ) ^ P3.GetHashCode(); + return hashCode; + } + } + + public override string ToString() => $"{P0}, {P1}, {P2}, {P3}"; + + #endregion + + #region Interpolation + + /// Returns a linear blend between two catmull-rom curves + /// The first curve + /// The second curve + /// A value from 0 to 1 to blend between a and b + public static CatRomCubic2D Lerp( CatRomCubic2D a, CatRomCubic2D b, float t ) { + return new CatRomCubic2D( + Vector2.LerpUnclamped( a.p0, b.p0, t ), + Vector2.LerpUnclamped( a.p1, b.p1, t ), + Vector2.LerpUnclamped( a.p2, b.p2, t ), + Vector2.LerpUnclamped( a.p3, b.p3, t ) + ); + } + + #endregion + + // todo: this is untested + public BezierCubic2D ToBezier() => + new BezierCubic2D( + p1, + p1 + ( p2 - p0 ) / 6f, + p2 + ( p1 - p3 ) / 6f, + p2 + ); + + // todo: this is untested + public HermiteCubic2D ToHermite() => + new HermiteCubic2D( + p1, + ( p2 - p0 ) / 2f, + p2, + ( p3 - p1 ) / 2f + ); + + // todo: this is untested + public UBSCubic2D ToBSpline() => + new UBSCubic2D( + ( 7 * p0 - 4 * p1 + 5 * p2 - 2 * p3 ) / 6, + ( -2 * p0 + 11 * p1 - 4 * p2 + p3 ) / 6, + ( p0 - 4 * p1 + 11 * p2 - 2 * p3 ) / 6, + ( -2 * p0 + 5 * p1 - 4 * p2 + 7 * p3 ) / 6 + ); + + } + +} \ No newline at end of file From 2120a8720bac8dbbccfd458e2b42bbdebeafae39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 13:37:33 +0200 Subject: [PATCH 024/301] added NUCatRomCubic2D, replacing CatRom2D --- Curves/CatRom2D.cs | 222 ------------------ .../NUCatRomCubic2D.cs | 164 +++++++++++++ Curves/SplineUtils.cs | 85 +++++++ 3 files changed, 249 insertions(+), 222 deletions(-) delete mode 100644 Curves/CatRom2D.cs create mode 100644 Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs diff --git a/Curves/CatRom2D.cs b/Curves/CatRom2D.cs deleted file mode 100644 index adc9fed..0000000 --- a/Curves/CatRom2D.cs +++ /dev/null @@ -1,222 +0,0 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) - -using System; -using System.Runtime.CompilerServices; -using UnityEngine; - -namespace Freya { - - /// A 2D cubic catmull-rom curve - [Serializable] public struct CatRom2D { - - const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - - // serialized data - [SerializeField] Vector2 p0, p1, p2, p3; - [SerializeField] float k0, k1, k2, k3; // knots - [SerializeField] [Range( 0, 1 )] float alpha; - [SerializeField] [Range( 0, 1 )] float tension; - [SerializeField] bool manualKnots; - - // cached data to accelerate calculations - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) - [NonSerialized] Vector2 c3, c2, c1, c0; // cached coefficients for fast evaluation - - #region Properties - - /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it - public Vector2 P0 { - [MethodImpl( INLINE )] get => p0; - set => _ = ( p0 = value, validCoefficients = false ); - } - /// The second control point, and the start of the catrom curve - public Vector2 P1 { - [MethodImpl( INLINE )] get => p1; - set => _ = ( p1 = value, validCoefficients = false ); - } - /// The third control point, and the end of the catrom curve - public Vector2 P2 { - [MethodImpl( INLINE )] get => p2; - set => _ = ( p2 = value, validCoefficients = false ); - } - /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it - public Vector2 P3 { - [MethodImpl( INLINE )] get => p3; - set => _ = ( p3 = value, validCoefficients = false ); - } - - /// The alpha parameter, which controls how much the length of each segment should influence the shape of the curve. - /// A value of 0 is called a uniform catrom, and is fast to evaluate but has a tendency to overshoot. - /// A value of 0.5 is a centripetal catrom, which follows points very tightly, and prevents cusps and loops. - /// A value of 1 is a chordal catrom, which follows the points very smoothly with wide arcs - public float Alpha { - [MethodImpl( INLINE )] get => alpha; - set => _ = ( alpha = value, validCoefficients = false ); - } - - /// Controls tension of the curve, where a value of 0 is a standard smooth catrom curve, while a value of 1 flattens the curve to a straight line segment - public float Tension { - [MethodImpl( INLINE )] get => tension; - set => _ = ( tension = value, validCoefficients = false ); - } - - #endregion - - #region Constructors - - /// Creates a cubic catmull-rom curve, from 4 control points with explicit alpha parameter to define its type - /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it - /// The second control point, and the start of the catrom curve - /// The third control point, and the end of the catrom curve - /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it - /// The alpha parameter controls how much the length of each segment should influence the shape of the curve. - /// A value of 0 is called a uniform catrom, and is fast to evaluate but has a tendency to overshoot. - /// A value of 0.5 is a centripetal catrom, which follows points very tightly, and prevents cusps and loops. - /// A value of 1 is a chordal catrom, which follows the points very smoothly with wide arcs - /// Controls tension of the curve, where a value of 0 is a standard smooth catrom curve, while a value of 1 flattens the curve to a straight line segment - public CatRom2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float alpha = 0.5f, float tension = 0 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); - this.alpha = alpha; - this.tension = tension; - this.manualKnots = false; - validCoefficients = false; - c0 = c1 = c2 = c3 = default; - k0 = k1 = k2 = k3 = default; - } - - /// Creates a cubic catmull-rom curve, from 4 control points with explicit alpha parameter to define its type - /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it - /// The second control point, and the start of the catrom curve - /// The third control point, and the end of the catrom curve - /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it - /// The type of catrom curve to use. This will internally determine the value of the alpha parameter - /// Controls tension of the curve, where a value of 0 is a standard smooth catrom curve, while a value of 1 flattens the curve to a straight line segment - public CatRom2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, CatRomType type, float tension = 0 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); - this.alpha = type.AlphaValue(); - this.tension = tension; - this.manualKnots = false; - validCoefficients = false; - c0 = c1 = c2 = c3 = default; - k0 = k1 = k2 = k3 = default; - } - - /// Creates a catmull-rom curve with 4 control points and manually assigned knot values - /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it - /// The second control point, and the start of the catrom curve - /// The third control point, and the end of the catrom curve - /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it - /// Controls tension of the curve, where a value of 0 is a standard smooth catrom curve, while a value of 1 flattens the curve to a straight line segment - /// The first knot value - /// The second knot value - /// The third knot value - /// The fourth knot value - public CatRom2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3, float tension = 0 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); - ( this.k0, this.k1, this.k2, this.k3 ) = ( k0, k1, k2, k3 ); - this.alpha = 0; // unused when using manual vectors - this.tension = tension; - this.manualKnots = true; - validCoefficients = false; - c0 = c1 = c2 = c3 = default; - } - - #endregion - - #region Internal Functions - - /// Returns the internal knot vector of this curve - public (float, float, float, float) GetKnots() { - if( manualKnots ) - return ( k0, k1, k2, k3 ); - if( alpha == 0 ) // uniform catrom - return ( 0, 1, 2, 3 ); - const float ak0 = 0; - float ak1 = Vector2.SqrMagnitude( p0 - p1 ).Pow( 0.5f * alpha ) + ak0; - float ak2 = Vector2.SqrMagnitude( p1 - p2 ).Pow( 0.5f * alpha ) + ak1; - float ak3 = Vector2.SqrMagnitude( p2 - p3 ).Pow( 0.5f * alpha ) + ak2; - return ( ak0, ak1, ak2, ak3 ); - } - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - if( Mathfs.Approximately( tension, 1f ) ) { // linear segment - c3 = default; - c2 = default; - c1 = p2 - p1; - c0 = p1; - } else { - ( float kn0, float kn1, float kn2, float kn3 ) = GetKnots(); - Vector2 m1 = ( 1 - tension ) * ( kn2 - kn1 ) * ( ( p1 - p0 ) / ( kn1 - kn0 ) - ( p2 - p0 ) / ( kn2 - kn0 ) + ( p2 - p1 ) / ( kn2 - kn1 ) ); - Vector2 m2 = ( 1 - tension ) * ( kn2 - kn1 ) * ( ( p2 - p1 ) / ( kn2 - kn1 ) - ( p3 - p1 ) / ( kn3 - kn1 ) + ( p3 - p2 ) / ( kn3 - kn2 ) ); - Vector2 p2p1 = p1 - p2; - c3 = 2 * p2p1 + m1 + m2; - c2 = -3 * p2p1 - 2 * m1 - m2; - c1 = m1; - c0 = p1; - } - } - - #endregion - - #region Points & Derivatives - - /// - [MethodImpl( INLINE )] public Vector2 GetPoint( float t ) { - ReadyCoefficients(); - return c3 * t * t * t + c2 * t * t + c1 * t + c0; - } - - /// - [MethodImpl( INLINE )] public Vector2 GetDerivative( float t ) { - ReadyCoefficients(); - return 3 * c3 * t * t + 2 * c2 * t + c1; - } - - /// - [MethodImpl( INLINE )] public Vector2 GetSecondDerivative( float t ) { - ReadyCoefficients(); - return 6 * c3 * t + 2 * c2; - } - - /// - [MethodImpl( INLINE )] public Vector2 GetThirdDerivative() { - ReadyCoefficients(); - return 6 * c3; - } - - /* Alternate method to calculate the point - this is slower but it's mathematically kinda pretty isn't it? - public Vector2 GetPoint( float t, float alpha ) { - ( float k0, float k1, float k2, float k3 ) = GetKnots( alpha ); - float v = Mathfs.Lerp( k1, k2, t ); // remap from 0-1 to k1-k2 - Vector2 A1 = Remap( v, k0, k1, p0, p1 ); - Vector2 A2 = Remap( v, k1, k2, p1, p2 ); - Vector2 A3 = Remap( v, k2, k3, p2, p3 ); - Vector2 B1 = Remap( v, k0, k2, A1, A2 ); - Vector2 B2 = Remap( v, k1, k3, A2, A3 ); - return C = Remap( v, k1, k2, B1, B2 ); - } - Vector2 Remap( float value, float t0, float t1, Vector2 a, Vector2 b ) { - float t = Mathfs.InverseLerp( t0, t1, value ); - return Vector2.LerpUnclamped( a, b, t ); - }*/ - - #endregion - - public (Vector2 m1, Vector2 m2) GetPointTangents() => ( GetDerivative( 0 ), GetDerivative( 1 ) ); - - public BezierCubic2D ToBezier() { - ( Vector2 m1, Vector2 m2 ) = GetPointTangents(); - return new BezierCubic2D( p1, p1 + m1 / 3, p2 - m2 / 3, p2 ); - } - - ( Vector2 m1, Vector2 m2 ) = GetPointTangents(); - return new Hermite2D( p1, m1, p2, m2 ); - public HermiteCubic2D ToHermite() { - } - - } - -} \ No newline at end of file diff --git a/Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs b/Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs new file mode 100644 index 0000000..2124946 --- /dev/null +++ b/Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs @@ -0,0 +1,164 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// A non-uniform cubic catmull-rom 2D curve + [Serializable] public struct NUCatRomCubic2D : IParamCubicSplineSegment2D { + + public enum KnotCalcMode { + Manual, + Auto, + AutoUnitInterval + } + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + #region Constructors + + /// Creates a cubic catmull-rom curve, from 4 control points and their corresponding knot values + /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The second control point, and the start of the catrom curve + /// The third control point, and the end of the catrom curve + /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The first knot value + /// The second knot value + /// The third knot value + /// The fourth knot value + public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + ( this.k0, this.k1, this.k2, this.k3 ) = ( k0, k1, k2, k3 ); + validCoefficients = false; + curve = default; + knotCalcMode = KnotCalcMode.Manual; + alpha = default; // unused when using manual knots + } + + /// Creates a uniform cubic catmull-rom curve, from 4 control points + /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The second control point, and the start of the catrom curve + /// The third control point, and the end of the catrom curve + /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) : this( p0, p1, p2, p3, -1, 0, 1, 2 ) { + } + + /// Creates a cubic catmull-rom curve, from 4 control points with explicit type for auto-generating its knot values + /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The second control point, and the start of the catrom curve + /// The third control point, and the end of the catrom curve + /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The type of catrom curve to use. This will internally determine the value of the alpha parameter + /// If true, the knot generation will ensure k1 = 0 and k2 = 1, + /// making it span the unit interval of 0 to 1, instead of using the raw knot values generated by the alpha parameterization + public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, CatRomType type, bool parameterizeToUnitInterval = true ) + : this( p0, p1, p2, p3, type.AlphaValue(), parameterizeToUnitInterval ) { + } + + /// Creates a cubic catmull-rom curve, from 4 control points with explicit alpha parameter to define its type + /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The second control point, and the start of the catrom curve + /// The third control point, and the end of the catrom curve + /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The alpha parameter controls how much the length of each segment should influence the knot values, which in turn influence the shape of the curve. + /// A value of 0 is called a uniform catrom, and is fast to evaluate but has a tendency to overshoot. + /// A value of 0.5 is a centripetal catrom, which follows points very tightly, and prevents cusps and loops. + /// A value of 1 is a chordal catrom, which follows the points very smoothly with wide arcs + /// If true, the knot generation will ensure k1 = 0 and k2 = 1, + /// making it span the unit interval of 0 to 1 instead of using the raw knot values generated by the alpha parameterization + public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float alpha, bool parameterizeToUnitInterval = true ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + validCoefficients = false; + curve = default; + k0 = k1 = k2 = k3 = default; + knotCalcMode = parameterizeToUnitInterval ? KnotCalcMode.AutoUnitInterval : KnotCalcMode.Auto; + this.alpha = alpha; + } + + #endregion + + // serialized data + [SerializeField] Vector2 p0, p1, p2, p3; + [SerializeField] float k0, k1, k2, k3; // knot vector + + // knot auto-calculation fields + [SerializeField] KnotCalcMode knotCalcMode; // knot recalculation mode + [SerializeField] float alpha; // alpha parameterization + + Polynomial2D curve; + public Polynomial2D Curve { + get { + ReadyCoefficients(); + return curve; + } + } + + #region Properties + + /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + public Vector2 P0 { + [MethodImpl( INLINE )] get => p0; + set => _ = ( p0 = value, validCoefficients = false ); + } + /// The second control point, and the start of the catrom curve + public Vector2 P1 { + [MethodImpl( INLINE )] get => p1; + set => _ = ( p1 = value, validCoefficients = false ); + } + /// The third control point, and the end of the catrom curve + public Vector2 P2 { + [MethodImpl( INLINE )] get => p2; + set => _ = ( p2 = value, validCoefficients = false ); + } + /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it + public Vector2 P3 { + [MethodImpl( INLINE )] get => p3; + set => _ = ( p3 = value, validCoefficients = false ); + } + + /// The alpha parameter, which controls how much the length of each segment should influence the shape of the curve. + /// A value of 0 is called a uniform catrom, and is fast to evaluate but has a tendency to overshoot. + /// A value of 0.5 is a centripetal catrom, which follows points very tightly, and prevents cusps and loops. + /// A value of 1 is a chordal catrom, which follows the points very smoothly with wide arcs + public float Alpha { + [MethodImpl( INLINE )] get => alpha; + set => _ = ( alpha = value, validCoefficients = false ); + } + + #endregion + + // cached data to accelerate calculations + [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + if( knotCalcMode != KnotCalcMode.Manual ) + ( k0, k1, k2, k3 ) = SplineUtils.CalcCatRomKnots( p0, p1, p2, p3, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); + curve = SplineUtils.CalculateCatRomCurve( p0, p1, p2, p3, k0, k1, k2, k3 ); + } + + /// Returns the weight of the given control point at the given parameter value + /// The point to get the weight of + /// The parameter value at which to sample the weight + public float GetPointWeightAtKnotValue( int i, float u ) { + float a = Mathfs.InverseLerp( k0, k1, u ); + float b = Mathfs.InverseLerp( k1, k2, u ); + float c = Mathfs.InverseLerp( k2, k3, u ); + float d = Mathfs.InverseLerp( k0, k2, u ); + float g = Mathfs.InverseLerp( k1, k3, u ); + switch( i ) { + case 0: return -( a - 1 ) * ( b - 1 ) * ( d - 1 ); + case 1: return ( b - 1 ) * ( a * d - a + b * ( d + g - 1 ) - d ); + case 2: return -b * ( b * ( d + g - 1 ) + g * ( c - 1 ) - d ); + case 3: return b * c * g; + default: throw new IndexOutOfRangeException( $"Catrom point has to be either 0, 1, 2 or 3. Got: {i}" ); + } + } + + } + +} \ No newline at end of file diff --git a/Curves/SplineUtils.cs b/Curves/SplineUtils.cs index 9c26ac5..72af107 100644 --- a/Curves/SplineUtils.cs +++ b/Curves/SplineUtils.cs @@ -28,6 +28,91 @@ public static float[] GenerateUniformKnots( int degree, int pCount, bool open ) internal static int BSplineKnotCount( int pointCount, int degree ) => degree + pointCount + 1; + public static float CalcCatRomKnot( float kPrev, float alpha, float sqDist ) { + return kPrev + sqDist.Pow( 0.5f * alpha ).AtLeast( 0.00001f ); // ensure there are no duplicate knots + } + + public static (float, float, float, float) CalcCatRomKnots( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float alpha, bool unitInterval ) { + if( alpha == 0 ) // uniform catrom + return ( -1, 0, 1, 2 ); + float i01 = Vector2.SqrMagnitude( p0 - p1 ).Pow( 0.5f * alpha ); + float i12 = Vector2.SqrMagnitude( p1 - p2 ).Pow( 0.5f * alpha ); + float i23 = Vector2.SqrMagnitude( p2 - p3 ).Pow( 0.5f * alpha ); + + float k0, k1, k2, k3; + if( unitInterval ) { + return ( -i01 / i12, 0, 1, 1 + i23 / i12 ); + } else { + k0 = 0; + k1 = k0 + i01; + k2 = k1 + i12; + k3 = k2 + i23; + } + + return ( k0, k1, k2, k3 ); + } + + internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { + float k1k1 = k1 * k1; + float k2k2 = k2 * k2; + float k0k1 = k0 * k1; + float _2k0k1 = 2 * k0k1; + float k0k2 = k0 * k2; + float k1k2 = k1 * k2; + float _2k1k2 = 2 * k1k2; + float k1k3 = k1 * k3; + float k2k3 = k2 * k3; + float _2k2k3 = 2 * k2k3; + float k0k1k2 = k0k1 * k2; + float k0k1k3 = k0k1 * k3; + float k1k1k2 = k1k1 * k2; + float k0k2k3 = k0k2 * k3; + float k1k1k3 = k1k1 * k3; + float k1k2k2 = k1k2 * k2; + float k1k2k3 = k1k2 * k3; + float k0k1k1 = k0k1 * k1; + float k0k2k2 = k0k2 * k2; + + // CHAR matrix COLUMN 0: + float p0u0 = -k1k2k2; + float p0u1 = _2k1k2 + k2k2; + float p0u2 = -k1 - 2 * k2; + const float p0u3 = 1; + // CHAR matrix COLUMN 1: + float p1u0 = k2 * ( k0k1k2 + k0k1k3 - k0k2k3 - k1k1k3 ); + float p1u1 = -3 * k0k1k2 - k0k1k3 + k0k2k3 + k1k1k3 + k1k1k2 - k1k2k2 + k1k2k3 + k2k2 * k3; + float p1u2 = _2k0k1 + k0k2 - k1k1 + k1k2 - k1k3 - _2k2k3; + float p1u3 = -k0 + k3; + // CHAR matrix COLUMN 2: + float common = k0k1k3 + k0k2k2 - k0k2k3; + float p2u0 = -k1 * ( common - k1k2k3 ); + float p2u1 = k0k1k1 + k0k1k2 + common - k1k1k2 + k1k2k2 - 3 * k1k2k3; + float p2u2 = -_2k0k1 - k0k2 + k1k2 + k1k3 - k2k2 + _2k2k3; + float p2u3 = k0 - k3; + // CHAR matrix COLUMN 3: + float p3u0 = k1k1k2; + float p3u1 = -k1k1 - _2k1k2; + float p3u2 = 2 * k1 + k2; + const float p3u3 = -1; + + float i01 = k0 - k1; + float i02 = k0 - k2; + float i12 = k1 - k2; + float i12sq = i12 * i12; + float i13 = k1 - k3; + float i23 = k2 - k3; + Vector2 scaledP0 = p0 / ( i01 * i02 * i12 ); + Vector2 scaledP1 = p1 / ( i01 * i12sq * i13 ); + Vector2 scaledP2 = p2 / ( i02 * i12sq * i23 ); + Vector2 scaledP3 = p3 / ( i12 * i13 * i23 ); + return new Polynomial2D( + p0u0 * scaledP0 + p1u0 * scaledP1 + p2u0 * scaledP2 + p3u0 * scaledP3, + p0u1 * scaledP0 + p1u1 * scaledP1 + p2u1 * scaledP2 + p3u1 * scaledP3, + p0u2 * scaledP0 + p1u2 * scaledP1 + p2u2 * scaledP2 + p3u2 * scaledP3, + p0u3 * scaledP0 + p1u3 * scaledP1 + p2u3 * scaledP2 + p3u3 * scaledP3 + ); + } + } } \ No newline at end of file From 49c4fe79f95c6e010f2567b0073fb6ef6d80d6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 13:38:25 +0200 Subject: [PATCH 025/301] added inverse characteristic matrices --- Curves/CharMatrix.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs index e608e9f..8780248 100644 --- a/Curves/CharMatrix.cs +++ b/Curves/CharMatrix.cs @@ -21,6 +21,13 @@ public readonly struct CharMatrix { -1, 3, -3, 1 ); + public static readonly CharMatrix4x4 cubicBezierInverse = new CharMatrix4x4( + 3, 0, 0, 0, + 3, 1, 0, 0, + 3, 2, 1, 0, + 3, 3, 3, 3 + ) / 3; + /// The characteristic matrix of a uniform cubic hermite curve public static readonly CharMatrix4x4 cubicHermite = new( 1, 0, 0, 0, @@ -29,6 +36,13 @@ public readonly struct CharMatrix { 2, 1, -2, 1 ); + public static readonly CharMatrix4x4 cubicHermiteInverse = new( + 1, 0, 0, 0, + 0, 1, 0, 0, + 1, 1, 1, 1, + 0, 1, 2, 3 + ); + /// The characteristic matrix of a uniform cubic catmull-rom curve public static readonly CharMatrix4x4 cubicCatmullRom = new CharMatrix4x4( 0, 2, 0, 0, @@ -36,6 +50,14 @@ public readonly struct CharMatrix { 2, -5, 4, -1, -1, 3, -3, 1 ) / 2; + + public static readonly CharMatrix4x4 cubicCatmullRomInverse = new CharMatrix4x4( + 1, -1, 1, 1, + 1, 0, 0, 0, + 1, 1, 1, 1, + 1, 2, 4, 6 + ); + /// The characteristic matrix of a uniform cubic B-spline segment public static readonly CharMatrix4x4 cubicUniformBspline = new CharMatrix4x4( 1, 4, 1, 0, @@ -44,6 +66,13 @@ public readonly struct CharMatrix { -1, 3, -3, 1 ) / 6; + public static readonly CharMatrix4x4 cubicUniformBsplineInverse = new CharMatrix4x4( + 3, -3, 2, 0, + 3, 0, -1, 0, + 3, 3, 2, 0, + 3, 6, 11, 18 + ) / 3; + } /// Data structure representing a cubic characteristic matrix with 4 points. Used for spline evaluation From dda032574bda952ae3b7d3b9c912f564636917d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 13:38:58 +0200 Subject: [PATCH 026/301] added char matrix column multiply and ToString() --- Curves/CharMatrix.cs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs index 8780248..7a9ecd6 100644 --- a/Curves/CharMatrix.cs +++ b/Curves/CharMatrix.cs @@ -138,6 +138,31 @@ public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) = GetEvalPolynomial( p0.z, p1.z, p2.z, p3.z ) ); + /// Multiplies this characteristic matrix C by a column matrix: C*[p0,p1,p2,p3]^T + /// The first entry of the column matrix + /// The second entry of the column matrix + /// The third entry of the column matrix + /// The fourth entry of the column matrix + public (float, float, float, float) MultiplyColumnVec( float p0, float p1, float p2, float p3 ) => + ( + p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03, + p0 * m10 + p1 * m11 + p2 * m12 + p3 * m13, + p0 * m20 + p1 * m21 + p2 * m22 + p3 * m23, + p0 * m30 + p1 * m31 + p2 * m32 + p3 * m33 + ); + + /// + public (Vector2, Vector2, Vector2, Vector2) MultiplyColumnVec( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { + ( float x0, float x1, float x2, float x3 ) = MultiplyColumnVec( p0.x, p1.x, p2.x, p3.x ); + ( float y0, float y1, float y2, float y3 ) = MultiplyColumnVec( p0.y, p1.y, p2.y, p3.y ); + return ( + new Vector2( x0, y0 ), + new Vector2( x1, y1 ), + new Vector2( x2, y2 ), + new Vector2( x3, y3 ) + ); + } + public static CharMatrix4x4 operator *( CharMatrix4x4 c, float v ) => new(c.m00 * v, c.m01 * v, c.m02 * v, c.m03 * v, c.m10 * v, c.m11 * v, c.m12 * v, c.m13 * v, @@ -145,6 +170,9 @@ public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) = c.m30 * v, c.m31 * v, c.m32 * v, c.m33 * v); public static CharMatrix4x4 operator /( CharMatrix4x4 c, float v ) => c * ( 1f / v ); + + public override string ToString() => $"{m00},\t{m01},\t{m02},\t{m03}\n{m10},\t{m11},\t{m12},\t{m13}\n{m20},\t{m21},\t{m22},\t{m23}\n{m30},\t{m31},\t{m32},\t{m33}\n"; + } /// Data structure representing a cubic characteristic matrix with 4 points. Used for spline evaluation From 8c728482bf5f3a1620e6d606dd98cda2e4971cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 13:40:29 +0200 Subject: [PATCH 027/301] optimized Polynomial2D intersection tests --- Curves/Polynomial2D.cs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index b4117cf..7897831 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -164,27 +164,24 @@ void Refine( ref PointProjectSample smp ) { // Internal - used by all other intersections private ResultsMax3 Intersect( Vector2 origin, Vector2 direction, bool rangeLimited = false, float minRayT = float.NaN, float maxRayT = float.NaN ) { - BezierCubic2D bez = this.ToHermiteCurve().ToBezier(); // todo: hack - Vector2 p0rel = bez.P0 - origin; - Vector2 p1rel = bez.P1 - origin; - Vector2 p2rel = bez.P2 - origin; - Vector2 p3rel = bez.P3 - origin; - float y0 = Mathfs.Determinant( p0rel, direction ); // transform bezier point components into the line space y components - float y1 = Mathfs.Determinant( p1rel, direction ); - float y2 = Mathfs.Determinant( p2rel, direction ); - float y3 = Mathfs.Determinant( p3rel, direction ); - Polynomial polynomY = CharMatrix.cubicBezier.GetEvalPolynomial( y0, y1, y2, y3 ); + Polynomial2D rel = this; + rel.C0 -= origin; + float y0 = Mathfs.Determinant( rel.C0, direction ); // transform polynomial into the line space y components + float y1 = Mathfs.Determinant( rel.C1, direction ); + float y2 = Mathfs.Determinant( rel.C2, direction ); + float y3 = Mathfs.Determinant( rel.C3, direction ); + Polynomial polynomY = new Polynomial( y3, y2, y1, y0 ); ResultsMax3 roots = polynomY.Roots; // t values of the function Polynomial polynomX = default; if( rangeLimited ) { // if we're range limited, we need to verify position along the ray/line/lineSegment // and if we do, we need to be able to go from t -> x coord - float x0 = Vector2.Dot( p0rel, direction ); // transform bezier point components into the line space x components - float x1 = Vector2.Dot( p1rel, direction ); - float x2 = Vector2.Dot( p2rel, direction ); - float x3 = Vector2.Dot( p3rel, direction ); - polynomX = CharMatrix.cubicBezier.GetEvalPolynomial( x0, x1, x2, x3 ); + float x0 = Vector2.Dot( rel.C0, direction ); // transform into the line space x components + float x1 = Vector2.Dot( rel.C1, direction ); + float x2 = Vector2.Dot( rel.C2, direction ); + float x3 = Vector2.Dot( rel.C3, direction ); + polynomX = new Polynomial( x3, x2, x1, x0 ); } float CurveTtoRayT( float t ) => polynomX.Eval( t ); @@ -267,6 +264,14 @@ public bool Raycast( Ray2D ray, out Vector2 hitPoint, out float t, float maxDist #endregion + public static Polynomial2D Rotate( Polynomial2D poly, float a ) { + return new Polynomial2D( + poly.C0.Rotate( a ), + poly.C1.Rotate( a ), + poly.C2.Rotate( a ), + poly.C3.Rotate( a ) + ); + } } } \ No newline at end of file From f0d751772d8ee3807244d7f3d3bd1a79498e848d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 13:42:16 +0200 Subject: [PATCH 028/301] moved HermiteCubic2D into the correct folder --- Curves/{ => Uniform Spline Segments}/HermiteCubic2D.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Curves/{ => Uniform Spline Segments}/HermiteCubic2D.cs (100%) diff --git a/Curves/HermiteCubic2D.cs b/Curves/Uniform Spline Segments/HermiteCubic2D.cs similarity index 100% rename from Curves/HermiteCubic2D.cs rename to Curves/Uniform Spline Segments/HermiteCubic2D.cs From 3faaba006c7f64fa3e61f39e972567fd926f485b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 14:02:21 +0200 Subject: [PATCH 029/301] added HermiteCubic3D --- .../Uniform Spline Segments/HermiteCubic2D.cs | 4 +- .../Uniform Spline Segments/HermiteCubic3D.cs | 80 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 Curves/Uniform Spline Segments/HermiteCubic3D.cs diff --git a/Curves/Uniform Spline Segments/HermiteCubic2D.cs b/Curves/Uniform Spline Segments/HermiteCubic2D.cs index 346eadf..859bae5 100644 --- a/Curves/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Curves/Uniform Spline Segments/HermiteCubic2D.cs @@ -1,4 +1,6 @@ -using System; +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Serialization; diff --git a/Curves/Uniform Spline Segments/HermiteCubic3D.cs b/Curves/Uniform Spline Segments/HermiteCubic3D.cs new file mode 100644 index 0000000..dda4595 --- /dev/null +++ b/Curves/Uniform Spline Segments/HermiteCubic3D.cs @@ -0,0 +1,80 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized 3D cubic Hermite curve segment + [Serializable] public struct HermiteCubic3D : IParamCubicSplineSegment3D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// + public HermiteCubic3D( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) { + ( this.p0, this.v0, this.p1, this.v1 ) = ( p0, v0, p1, v1 ); + validCoefficients = false; + curve = default; + } + + Polynomial3D curve; + public Polynomial3D Curve { + get { + ReadyCoefficients(); + return curve; + } + } + + #region Control Points + + [SerializeField] Vector3 p0; + [SerializeField] Vector3 v0; + [SerializeField] Vector3 p1; + [SerializeField] Vector3 v1; + + /// + public Vector3 P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// + public Vector3 V0 { + [MethodImpl( INLINE )] get => v0; + [MethodImpl( INLINE )] set => _ = ( v0 = value, validCoefficients = false ); + } + + /// + public Vector3 P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// + public Vector3 V1 { + [MethodImpl( INLINE )] get => v1; + [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); + } + + #endregion + + #region Coefficients + + [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + + // Coefficient Calculation + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicHermite.GetCurve( p0, v0, p1, v1 ); + } + + #endregion + + public BezierCubic3D ToBezier() => new BezierCubic3D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); + + } + +} \ No newline at end of file From 619038f600edf2170de8bdde2642b5f99c75e860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 14:04:54 +0200 Subject: [PATCH 030/301] inline doc fixes for BezierQuad3D --- Curves/Uniform Spline Segments/BezierQuad3D.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Curves/Uniform Spline Segments/BezierQuad3D.cs b/Curves/Uniform Spline Segments/BezierQuad3D.cs index e71bc4b..4b33d4e 100644 --- a/Curves/Uniform Spline Segments/BezierQuad3D.cs +++ b/Curves/Uniform Spline Segments/BezierQuad3D.cs @@ -12,10 +12,7 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Creates a quadratic bezier curve, from 3 control points - /// The starting point of the curve - /// The second control point of the curve, sometimes called the start tangent point - /// The end point of the curve, sometimes called the end tangent point + /// public BezierQuad3D( Vector3 p0, Vector3 p1, Vector3 p2 ) { ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); validCoefficients = false; @@ -34,25 +31,25 @@ public Polynomial3D Curve { [SerializeField] Vector3 p0, p1, p2; // the points of the curve - /// The starting point of the curve + /// public Vector3 P0 { [MethodImpl( INLINE )] get => p0; [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); } - /// The middle control point of the curve + /// public Vector3 P1 { [MethodImpl( INLINE )] get => p1; [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); } - /// The end point of the curve + /// public Vector3 P2 { [MethodImpl( INLINE )] get => p2; [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); } - /// Get or set a control point position by index. Valid indices: 0, 1, 2 or 3 + /// public Vector3 this[ int i ] { get { switch( i ) { From 4630f64b4a7d1e750820c63781d557f51edd4123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 14:16:10 +0200 Subject: [PATCH 031/301] added CatRomCubic3D --- .../Uniform Spline Segments/CatRomCubic2D.cs | 6 +- .../Uniform Spline Segments/CatRomCubic3D.cs | 167 ++++++++++++++++++ 2 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 Curves/Uniform Spline Segments/CatRomCubic3D.cs diff --git a/Curves/Uniform Spline Segments/CatRomCubic2D.cs b/Curves/Uniform Spline Segments/CatRomCubic2D.cs index de01bbf..1f2a1b2 100644 --- a/Curves/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Curves/Uniform Spline Segments/CatRomCubic2D.cs @@ -142,7 +142,7 @@ public static CatRomCubic2D Lerp( CatRomCubic2D a, CatRomCubic2D b, float t ) { #endregion - // todo: this is untested + /// Returns the bezier representation of the same curve public BezierCubic2D ToBezier() => new BezierCubic2D( p1, @@ -151,7 +151,7 @@ public BezierCubic2D ToBezier() => p2 ); - // todo: this is untested + /// Returns the hermite representation of the same curve public HermiteCubic2D ToHermite() => new HermiteCubic2D( p1, @@ -160,7 +160,7 @@ public HermiteCubic2D ToHermite() => ( p3 - p1 ) / 2f ); - // todo: this is untested + /// Returns the bspline representation of the same curve public UBSCubic2D ToBSpline() => new UBSCubic2D( ( 7 * p0 - 4 * p1 + 5 * p2 - 2 * p3 ) / 6, diff --git a/Curves/Uniform Spline Segments/CatRomCubic3D.cs b/Curves/Uniform Spline Segments/CatRomCubic3D.cs new file mode 100644 index 0000000..2f347a8 --- /dev/null +++ b/Curves/Uniform Spline Segments/CatRomCubic3D.cs @@ -0,0 +1,167 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform cubic catmull-rom 3D curve, with 4 control points + [Serializable] public struct CatRomCubic3D : IParamCubicSplineSegment3D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// + public CatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + validCoefficients = false; + curve = default; + } + + Polynomial3D curve; + public Polynomial3D Curve { + get { + ReadyCoefficients(); + return curve; + } + } + + #region Control Points + + [SerializeField] Vector3 p0, p1, p2, p3; // the points of the curve + + /// + public Vector3 P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// + public Vector3 P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// + public Vector3 P2 { + [MethodImpl( INLINE )] get => p2; + [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + } + + /// + public Vector3 P3 { + [MethodImpl( INLINE )] get => p3; + [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + } + + /// + public Vector3 this[ int i ] { + get { + switch( i ) { + case 0: return P0; + case 1: return P1; + case 2: return P2; + case 3: return P3; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + P1 = value; + break; + case 2: + P2 = value; + break; + case 3: + P3 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + + #endregion + + #region Coefficients + + [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + + // Coefficient Calculation + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicCatmullRom.GetCurve( p0, p1, p2, p3 ); + } + + #endregion + + #region Object Comparison & ToString + + public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator !=( CatRomCubic3D a, CatRomCubic3D b ) => !( a == b ); + public bool Equals( CatRomCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is CatRomCubic3D other && Equals( other ); + + public override int GetHashCode() { + unchecked { + int hashCode = P0.GetHashCode(); + hashCode = ( hashCode * 397 ) ^ P1.GetHashCode(); + hashCode = ( hashCode * 397 ) ^ P2.GetHashCode(); + hashCode = ( hashCode * 397 ) ^ P3.GetHashCode(); + return hashCode; + } + } + + public override string ToString() => $"{P0}, {P1}, {P2}, {P3}"; + + #endregion + + #region Interpolation + + /// + public static CatRomCubic3D Lerp( CatRomCubic3D a, CatRomCubic3D b, float t ) { + return new CatRomCubic3D( + Vector3.LerpUnclamped( a.p0, b.p0, t ), + Vector3.LerpUnclamped( a.p1, b.p1, t ), + Vector3.LerpUnclamped( a.p2, b.p2, t ), + Vector3.LerpUnclamped( a.p3, b.p3, t ) + ); + } + + #endregion + + /// + public BezierCubic3D ToBezier() => + new BezierCubic3D( + p1, + p1 + ( p2 - p0 ) / 6f, + p2 + ( p1 - p3 ) / 6f, + p2 + ); + + /// + public HermiteCubic3D ToHermite() => + new HermiteCubic3D( + p1, + ( p2 - p0 ) / 2f, + p2, + ( p3 - p1 ) / 2f + ); + + /// + public UBSCubic3D ToBSpline() => + new UBSCubic3D( + ( 7 * p0 - 4 * p1 + 5 * p2 - 2 * p3 ) / 6, + ( -2 * p0 + 11 * p1 - 4 * p2 + p3 ) / 6, + ( p0 - 4 * p1 + 11 * p2 - 2 * p3 ) / 6, + ( -2 * p0 + 5 * p1 - 4 * p2 + 7 * p3 ) / 6 + ); + + } + +} \ No newline at end of file From eb29bd305f2712898f77d67634268a6a6c9044e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 14:16:19 +0200 Subject: [PATCH 032/301] added UBSCubic3D --- Curves/Uniform Spline Segments/UBSCubic2D.cs | 8 +- Curves/Uniform Spline Segments/UBSCubic3D.cs | 129 +++++++++++++++++++ 2 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 Curves/Uniform Spline Segments/UBSCubic3D.cs diff --git a/Curves/Uniform Spline Segments/UBSCubic2D.cs b/Curves/Uniform Spline Segments/UBSCubic2D.cs index cf4ec4d..15206ae 100644 --- a/Curves/Uniform Spline Segments/UBSCubic2D.cs +++ b/Curves/Uniform Spline Segments/UBSCubic2D.cs @@ -1,4 +1,6 @@ -using System; +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; using System.Runtime.CompilerServices; using UnityEngine; @@ -19,7 +21,7 @@ public UBSCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { validCoefficients = false; curve = default; } - + Polynomial2D curve; public Polynomial2D Curve { get { @@ -101,7 +103,7 @@ public Vector2 this[ int i ] { } #endregion - + /// Returns the exact cubic bézier representation of this segment public BezierCubic2D ToBezier() { const float _13 = 1f / 3f; diff --git a/Curves/Uniform Spline Segments/UBSCubic3D.cs b/Curves/Uniform Spline Segments/UBSCubic3D.cs new file mode 100644 index 0000000..58345a0 --- /dev/null +++ b/Curves/Uniform Spline Segments/UBSCubic3D.cs @@ -0,0 +1,129 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized 3D uniform B-spline segment + [Serializable] public struct UBSCubic3D : IParamCubicSplineSegment3D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Creates a uniform cubic B-spline segment, given 4 control points + /// The first point of the B-spline hull + /// The second point of the B-spline hull + /// The third point of the B-spline hull + /// The fourth point of the B-spline hull + public UBSCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + validCoefficients = false; + curve = default; + } + + Polynomial3D curve; + public Polynomial3D Curve { + get { + ReadyCoefficients(); + return curve; + } + } + + #region Control Points + + [SerializeField] Vector3 p0, p1, p2, p3; // the points of the B-spline hull + + /// + public Vector3 P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// + public Vector3 P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// + public Vector3 P2 { + [MethodImpl( INLINE )] get => p2; + [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + } + + /// + public Vector3 P3 { + [MethodImpl( INLINE )] get => p3; + [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + } + + /// + public Vector3 this[ int i ] { + get { + switch( i ) { + case 0: return P0; + case 1: return P1; + case 2: return P2; + case 3: return P3; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + P1 = value; + break; + case 2: + P2 = value; + break; + case 3: + P3 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + + #endregion + + #region Coefficients + + [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + + // Coefficient Calculation + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicUniformBspline.GetCurve( p0, p1, p2, p3 ); + } + + #endregion + + /// + public BezierCubic3D ToBezier() { + const float _13 = 1f / 3f; + const float _23 = 2f / 3f; + float ax = p0.x + _23 * ( p1.x - p0.x ); + float bx = p1.x + _13 * ( p2.x - p1.x ); + float cx = p1.x + _23 * ( p2.x - p1.x ); + float dx = p2.x + _13 * ( p3.x - p2.x ); + float ay = p0.y + _23 * ( p1.y - p0.y ); + float by = p1.y + _13 * ( p2.y - p1.y ); + float cy = p1.y + _23 * ( p2.y - p1.y ); + float dy = p2.y + _13 * ( p3.y - p2.y ); + return new BezierCubic3D( + new Vector3( 0.5f * ( ax + bx ), 0.5f * ( ay + by ) ), + new Vector3( bx, by ), + new Vector3( cx, cy ), + new Vector3( 0.5f * ( cx + dx ), 0.5f * ( cy + dy ) ) + ); + } + + } + +} \ No newline at end of file From 0847133f40bfafd37207bcbc2f0f57529fa69068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 18:29:05 +0200 Subject: [PATCH 033/301] doc typo --- Curves/CharMatrix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs index 7a9ecd6..b20abe6 100644 --- a/Curves/CharMatrix.cs +++ b/Curves/CharMatrix.cs @@ -175,7 +175,7 @@ public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) = } - /// Data structure representing a cubic characteristic matrix with 4 points. Used for spline evaluation + /// Data structure representing a quadratic characteristic matrix with 3 points. Used for spline evaluation public readonly struct CharMatrix3x3 { public readonly float m00, m01, m02; public readonly float m10, m11, m12; From e321a66bcdd8e50ee5c09c082d8f1e14b84ec2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 18:29:52 +0200 Subject: [PATCH 034/301] optimized NUCatRom char matrix calculations --- Curves/SplineUtils.cs | 74 ++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/Curves/SplineUtils.cs b/Curves/SplineUtils.cs index 72af107..deda5e0 100644 --- a/Curves/SplineUtils.cs +++ b/Curves/SplineUtils.cs @@ -52,7 +52,9 @@ public static (float, float, float, float) CalcCatRomKnots( Vector2 p0, Vector2 return ( k0, k1, k2, k3 ); } - internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { + static CharMatrix4x4 GetNUCatRomCharMatrix( float k0, float k1, float k2, float k3 ) { + if( k1 == 0f && k2 == 1f ) + return GetNUCatRomCharMatrixUnitInterval( k0, k3 ); float k1k1 = k1 * k1; float k2k2 = k2 * k2; float k0k1 = k0 * k1; @@ -73,6 +75,11 @@ internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vecto float k0k1k1 = k0k1 * k1; float k0k2k2 = k0k2 * k2; + float common = _2k0k1 + k0k2 - k1k3 - _2k2k3; + float common2 = k0k1k3 + k0k2k2 - k0k2k3; + float common3 = k1k1k2 - k1k2k2; + float common4 = k0 - k3; + // CHAR matrix COLUMN 0: float p0u0 = -k1k2k2; float p0u1 = _2k1k2 + k2k2; @@ -80,15 +87,14 @@ internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vecto const float p0u3 = 1; // CHAR matrix COLUMN 1: float p1u0 = k2 * ( k0k1k2 + k0k1k3 - k0k2k3 - k1k1k3 ); - float p1u1 = -3 * k0k1k2 - k0k1k3 + k0k2k3 + k1k1k3 + k1k1k2 - k1k2k2 + k1k2k3 + k2k2 * k3; - float p1u2 = _2k0k1 + k0k2 - k1k1 + k1k2 - k1k3 - _2k2k3; - float p1u3 = -k0 + k3; + float p1u1 = -3 * k0k1k2 - k0k1k3 + k0k2k3 + k1k1k3 + common3 + k1k2k3 + k2k2 * k3; + float p1u2 = common - k1k1 + k1k2; + float p1u3 = -common4; // CHAR matrix COLUMN 2: - float common = k0k1k3 + k0k2k2 - k0k2k3; - float p2u0 = -k1 * ( common - k1k2k3 ); - float p2u1 = k0k1k1 + k0k1k2 + common - k1k1k2 + k1k2k2 - 3 * k1k2k3; - float p2u2 = -_2k0k1 - k0k2 + k1k2 + k1k3 - k2k2 + _2k2k3; - float p2u3 = k0 - k3; + float p2u0 = -k1 * ( common2 - k1k2k3 ); + float p2u1 = k0k1k1 + k0k1k2 + common2 - common3 - 3 * k1k2k3; + float p2u2 = -common - k2k2 + k1k2; + float p2u3 = common4; // CHAR matrix COLUMN 3: float p3u0 = k1k1k2; float p3u1 = -k1k1 - _2k1k2; @@ -101,18 +107,50 @@ internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vecto float i12sq = i12 * i12; float i13 = k1 - k3; float i23 = k2 - k3; - Vector2 scaledP0 = p0 / ( i01 * i02 * i12 ); - Vector2 scaledP1 = p1 / ( i01 * i12sq * i13 ); - Vector2 scaledP2 = p2 / ( i02 * i12sq * i23 ); - Vector2 scaledP3 = p3 / ( i12 * i13 * i23 ); - return new Polynomial2D( - p0u0 * scaledP0 + p1u0 * scaledP1 + p2u0 * scaledP2 + p3u0 * scaledP3, - p0u1 * scaledP0 + p1u1 * scaledP1 + p2u1 * scaledP2 + p3u1 * scaledP3, - p0u2 * scaledP0 + p1u2 * scaledP1 + p2u2 * scaledP2 + p3u2 * scaledP3, - p0u3 * scaledP0 + p1u3 * scaledP1 + p2u3 * scaledP2 + p3u3 * scaledP3 + float p0sc = 1f / ( i01 * i02 * i12 ); + float p1sc = 1f / ( i01 * i12sq * i13 ); + float p2sc = 1f / ( i02 * i12sq * i23 ); + float p3sc = 1f / ( i12 * i13 * i23 ); + return new CharMatrix4x4( + p0sc * p0u0, p1sc * p1u0, p2sc * p2u0, p3sc * p3u0, + p0sc * p0u1, p1sc * p1u1, p2sc * p2u1, p3sc * p3u1, + p0sc * p0u2, p1sc * p1u2, p2sc * p2u2, p3sc * p3u2, + p0sc * p0u3, p1sc * p1u3, p2sc * p2u3, p3sc * p3u3 + ); + } + + static CharMatrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { + float k0mk3 = k0 - k3; + float k0m2k3 = k0mk3 - k3; + float k0k3 = k0 * k3; + + // CHAR matrix COLUMN 1: + float p1u1 = k0k3 + k3; + float p1u2 = k0m2k3; + float p1u3 = -k0mk3; + // CHAR matrix COLUMN 2: + float p2u1 = k0 - k0k3; + float p2u2 = -k0m2k3 - 1; + float p2u3 = k0mk3; + + float i02 = k0 - 1; + float i23 = 1 - k3; + float p0sc = 1f / ( -k0 * i02 ); + float p1sc = 1f / ( -k0k3 ); + float p2sc = 1f / ( i02 * i23 ); + float p3sc = 1f / ( k3 * i23 ); + + return new CharMatrix4x4( + 0, 1, 0, 0, + p0sc, p1sc * p1u1, p2sc * p2u1, 0, + p0sc * -2, p1sc * p1u2, p2sc * p2u2, p3sc, + p0sc, p1sc * p1u3, p2sc * p2u3, -p3sc ); } + internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { + return GetNUCatRomCharMatrix( k0, k1, k2, k3 ).GetCurve( p0, p1, p2, p3 ); + } } } \ No newline at end of file From f3ee164a9b95a45aac12e8400968ae103ba5b6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 18:30:15 +0200 Subject: [PATCH 035/301] added NUCatRomCubic3D --- Curves/CatRom3D.cs | 164 ------------------ .../NUCatRomCubic3D.cs | 130 ++++++++++++++ Curves/SplineUtils.cs | 5 + 3 files changed, 135 insertions(+), 164 deletions(-) delete mode 100644 Curves/CatRom3D.cs create mode 100644 Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs diff --git a/Curves/CatRom3D.cs b/Curves/CatRom3D.cs deleted file mode 100644 index 9c8f3ea..0000000 --- a/Curves/CatRom3D.cs +++ /dev/null @@ -1,164 +0,0 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) - -using System; -using System.Runtime.CompilerServices; -using UnityEngine; - -namespace Freya { - - /// A 3D cubic catmull-rom curve - [Serializable] public struct CatRom3D { - - const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - - // serialized data - [SerializeField] Vector3 p0, p1, p2, p3; - [SerializeField] [Range( 0, 1 )] float alpha; - [SerializeField] [Range( 0, 1 )] float tension; - - // cached data to accelerate calculations - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) - [NonSerialized] Vector3 c3, c2, c1, c0; // cached coefficients for fast evaluation - - #region Properties - - /// - public Vector3 P0 { - [MethodImpl( INLINE )] get => p0; - set => _ = ( p0 = value, validCoefficients = false ); - } - /// - public Vector3 P1 { - [MethodImpl( INLINE )] get => p1; - set => _ = ( p1 = value, validCoefficients = false ); - } - /// - public Vector3 P2 { - [MethodImpl( INLINE )] get => p2; - set => _ = ( p2 = value, validCoefficients = false ); - } - /// - public Vector3 P3 { - [MethodImpl( INLINE )] get => p3; - set => _ = ( p3 = value, validCoefficients = false ); - } - - /// - public float Alpha { - [MethodImpl( INLINE )] get => alpha; - set => _ = ( alpha = value, validCoefficients = false ); - } - - /// - public float Tension { - [MethodImpl( INLINE )] get => tension; - set => _ = ( tension = value, validCoefficients = false ); - } - - #endregion - - #region Constructors - - /// - public CatRom3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float alpha = 0.5f, float tension = 0 ) { - _ = ( this.p0 = p0, this.p1 = p1, this.p2 = p2, this.p3 = p3 ); - this.alpha = alpha; - this.tension = tension; - validCoefficients = false; - c0 = c1 = c2 = c3 = default; - } - - /// - public CatRom3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, CatRomType type, float tension = 0 ) { - _ = ( this.p0 = p0, this.p1 = p1, this.p2 = p2, this.p3 = p3 ); - this.alpha = type.AlphaValue(); - this.tension = tension; - validCoefficients = false; - c0 = c1 = c2 = c3 = default; - } - - #endregion - - #region Internal Functions - - /// - public (float, float, float, float) GetKnots() { - if( alpha == 0 ) // uniform catrom - return ( 0, 1, 2, 3 ); - const float k0 = 0; - float k1 = Vector3.SqrMagnitude( p0 - p1 ).Pow( 0.5f * alpha ) + k0; - float k2 = Vector3.SqrMagnitude( p1 - p2 ).Pow( 0.5f * alpha ) + k1; - float k3 = Vector3.SqrMagnitude( p2 - p3 ).Pow( 0.5f * alpha ) + k2; - return ( k0, k1, k2, k3 ); - } - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - if( Mathfs.Approximately( tension, 1f ) ) { // linear segment - c3 = default; - c2 = default; - c1 = p2 - p1; - c0 = p1; - } else { - ( float k0, float k1, float k2, float k3 ) = GetKnots(); - Vector3 m1 = ( 1 - tension ) * ( k2 - k1 ) * ( ( p1 - p0 ) / ( k1 - k0 ) - ( p2 - p0 ) / ( k2 - k0 ) + ( p2 - p1 ) / ( k2 - k1 ) ); - Vector3 m2 = ( 1 - tension ) * ( k2 - k1 ) * ( ( p2 - p1 ) / ( k2 - k1 ) - ( p3 - p1 ) / ( k3 - k1 ) + ( p3 - p2 ) / ( k3 - k2 ) ); - Vector3 p2p1 = p1 - p2; - c3 = 2 * p2p1 + m1 + m2; - c2 = -3 * p2p1 - 2 * m1 - m2; - c1 = m1; - c0 = p1; - } - } - - #endregion - - #region Points & Derivatives - - /// - [MethodImpl( INLINE )] public Vector3 GetPoint( float t ) { - ReadyCoefficients(); - return c3 * t * t * t + c2 * t * t + c1 * t + c0; - } - - /// - [MethodImpl( INLINE )] public Vector3 GetDerivative( float t ) { - ReadyCoefficients(); - return 3 * c3 * t * t + 2 * c2 * t + c1; - } - - /// - [MethodImpl( INLINE )] public Vector3 GetSecondDerivative( float t ) { - ReadyCoefficients(); - return 6 * c3 * t + 2 * c2; - } - - /// - [MethodImpl( INLINE )] public Vector3 GetThirdDerivative() { - ReadyCoefficients(); - return 6 * c3; - } - - /* Alternate method to calculate the point - this is slower but it's mathematically kinda pretty isn't it? - public Vector3 GetPoint( float t, float alpha ) { - ( float k0, float k1, float k2, float k3 ) = GetKnots( alpha ); - float v = Mathfs.Lerp( k1, k2, t ); // remap from 0-1 to k1-k2 - Vector3 a = Remap( v, k0, k1, p0, p1 ); - Vector3 b = Remap( v, k1, k2, p1, p2 ); - Vector3 c = Remap( v, k2, k3, p2, p3 ); - Vector3 d = Remap( v, k0, k2, a, b ); - Vector3 e = Remap( v, k1, k3, b, c ); - return Remap( v, k1, k2, d, e ); - } - Vector3 Remap( float value, float t0, float t1, Vector3 a, Vector3 b ) { - float t = Mathfs.InverseLerp( t0, t1, value ); - return Vector3.LerpUnclamped( a, b, t ); - }*/ - - #endregion - - } - -} \ No newline at end of file diff --git a/Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs b/Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs new file mode 100644 index 0000000..12bfa12 --- /dev/null +++ b/Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs @@ -0,0 +1,130 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// A non-uniform cubic catmull-rom 3D curve + [Serializable] public struct NUCatRomCubic3D : IParamCubicSplineSegment3D { + + public enum KnotCalcMode { + Manual, + Auto, + AutoUnitInterval + } + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + #region Constructors + + /// + public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float k0, float k1, float k2, float k3 ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + ( this.k0, this.k1, this.k2, this.k3 ) = ( k0, k1, k2, k3 ); + validCoefficients = false; + curve = default; + knotCalcMode = KnotCalcMode.Manual; + alpha = default; // unused when using manual knots + } + + /// + public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) : this( p0, p1, p2, p3, -1, 0, 1, 2 ) { + } + + /// + public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, CatRomType type, bool parameterizeToUnitInterval = true ) + : this( p0, p1, p2, p3, type.AlphaValue(), parameterizeToUnitInterval ) { + } + + /// + public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float alpha, bool parameterizeToUnitInterval = true ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + validCoefficients = false; + curve = default; + k0 = k1 = k2 = k3 = default; + knotCalcMode = parameterizeToUnitInterval ? KnotCalcMode.AutoUnitInterval : KnotCalcMode.Auto; + this.alpha = alpha; + } + + #endregion + + // serialized data + [SerializeField] Vector3 p0, p1, p2, p3; + [SerializeField] float k0, k1, k2, k3; // knot vector + + // knot auto-calculation fields + [SerializeField] KnotCalcMode knotCalcMode; // knot recalculation mode + [SerializeField] float alpha; // alpha parameterization + + Polynomial3D curve; + public Polynomial3D Curve { + get { + ReadyCoefficients(); + return curve; + } + } + + #region Properties + + /// + public Vector3 P0 { + [MethodImpl( INLINE )] get => p0; + set => _ = ( p0 = value, validCoefficients = false ); + } + /// + public Vector3 P1 { + [MethodImpl( INLINE )] get => p1; + set => _ = ( p1 = value, validCoefficients = false ); + } + /// + public Vector3 P2 { + [MethodImpl( INLINE )] get => p2; + set => _ = ( p2 = value, validCoefficients = false ); + } + /// + public Vector3 P3 { + [MethodImpl( INLINE )] get => p3; + set => _ = ( p3 = value, validCoefficients = false ); + } + + /// + public float Alpha { + [MethodImpl( INLINE )] get => alpha; + set => _ = ( alpha = value, validCoefficients = false ); + } + + #endregion + + // cached data to accelerate calculations + [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + if( knotCalcMode != KnotCalcMode.Manual ) + ( k0, k1, k2, k3 ) = SplineUtils.CalcCatRomKnots( p0, p1, p2, p3, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); + curve = SplineUtils.CalculateCatRomCurve( p0, p1, p2, p3, k0, k1, k2, k3 ); + } + + /// + public float GetPointWeightAtKnotValue( int i, float u ) { + float a = Mathfs.InverseLerp( k0, k1, u ); + float b = Mathfs.InverseLerp( k1, k2, u ); + float c = Mathfs.InverseLerp( k2, k3, u ); + float d = Mathfs.InverseLerp( k0, k2, u ); + float g = Mathfs.InverseLerp( k1, k3, u ); + switch( i ) { + case 0: return -( a - 1 ) * ( b - 1 ) * ( d - 1 ); + case 1: return ( b - 1 ) * ( a * d - a + b * ( d + g - 1 ) - d ); + case 2: return -b * ( b * ( d + g - 1 ) + g * ( c - 1 ) - d ); + case 3: return b * c * g; + default: throw new IndexOutOfRangeException( $"Catrom point has to be either 0, 1, 2 or 3. Got: {i}" ); + } + } + + } + +} \ No newline at end of file diff --git a/Curves/SplineUtils.cs b/Curves/SplineUtils.cs index deda5e0..22bc662 100644 --- a/Curves/SplineUtils.cs +++ b/Curves/SplineUtils.cs @@ -151,6 +151,11 @@ static CharMatrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { return GetNUCatRomCharMatrix( k0, k1, k2, k3 ).GetCurve( p0, p1, p2, p3 ); } + + internal static Polynomial3D CalculateCatRomCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float k0, float k1, float k2, float k3 ) { + return GetNUCatRomCharMatrix( k0, k1, k2, k3 ).GetCurve( p0, p1, p2, p3 ); + } + } } \ No newline at end of file From 79e46dfeb5268ec9dfc52c76800bf4f31d89a1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 28 May 2022 19:01:33 +0200 Subject: [PATCH 036/301] added converters from Polynomial to all UC-Splines --- Curves/Polynomial2D.cs | 29 ++++++++++++++++++++++++++--- Curves/Polynomial3D.cs | 28 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index 7897831..c804af9 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -46,11 +46,34 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2, Vector2 c3 ) { /// Returns the tight axis-aligned bounds of the curve in the unit interval public Rect GetBounds01() => FloatRange.ToRect( x.OutputRange01, y.OutputRange01 ); - public HermiteCubic2D ToHermiteCurve() { - Polynomial2D d = Differentiate(); - return new HermiteCubic2D( Eval( 0 ), d.Eval( 0 ), Eval( 1 ), d.Eval( 1 ) ); + #region Polynomial to spline converters + + /// Returns the cubic bezier control points for the unit interval of this curve + public BezierCubic2D ToBezier() { + ( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) = CharMatrix.cubicBezierInverse.MultiplyColumnVec( C0, C1, C2, C3 ); + return new BezierCubic2D( p0, p1, p2, p3 ); + } + + /// Returns the cubic catmull-rom control points for the unit interval of this curve + public CatRomCubic2D ToCatmullRom() { + ( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) = CharMatrix.cubicCatmullRomInverse.MultiplyColumnVec( C0, C1, C2, C3 ); + return new CatRomCubic2D( p0, p1, p2, p3 ); + } + + /// Returns the cubic hermite control points for the unit interval of this curve + public HermiteCubic2D ToHermite() { + ( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) = CharMatrix.cubicHermiteInverse.MultiplyColumnVec( C0, C1, C2, C3 ); + return new HermiteCubic2D( p0, v0, p1, v1 ); } + /// Returns the cubic b-spline control points for the unit interval of this curve + public UBSCubic2D ToBSpline() { + ( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) = CharMatrix.cubicUniformBsplineInverse.MultiplyColumnVec( C0, C1, C2, C3 ); + return new UBSCubic2D( p0, v0, p1, v1 ); + } + + #endregion + #region IParamCurve3Diff interface implementations public int Degree => Mathf.Max( (int)x.Degree, (int)y.Degree ); diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs index 18f5270..cfbc875 100644 --- a/Curves/Polynomial3D.cs +++ b/Curves/Polynomial3D.cs @@ -29,6 +29,34 @@ public struct Polynomial3D : IParamCurve3Diff { /// public Bounds GetBounds01() => FloatRange.ToBounds( x.OutputRange01, y.OutputRange01, z.OutputRange01 ); + #region Polynomial to spline converters + + /// + public BezierCubic3D ToBezier() { + ( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) = CharMatrix.cubicBezierInverse.MultiplyColumnVec( C0, C1, C2, C3 ); + return new BezierCubic3D( p0, p1, p2, p3 ); + } + + /// + public CatRomCubic3D ToCatmullRom() { + ( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) = CharMatrix.cubicCatmullRomInverse.MultiplyColumnVec( C0, C1, C2, C3 ); + return new CatRomCubic3D( p0, p1, p2, p3 ); + } + + /// + public HermiteCubic3D ToHermite() { + ( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) = CharMatrix.cubicHermiteInverse.MultiplyColumnVec( C0, C1, C2, C3 ); + return new HermiteCubic3D( p0, v0, p1, v1 ); + } + + /// + public UBSCubic3D ToBSpline() { + ( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) = CharMatrix.cubicUniformBsplineInverse.MultiplyColumnVec( C0, C1, C2, C3 ); + return new UBSCubic3D( p0, v0, p1, v1 ); + } + + #endregion + #region IParamCurve3Diff interface implementations public int Degree => Mathf.Max( (int)x.Degree, (int)y.Degree, (int)z.Degree ); From cf503cbeb6f89199dac9c36b37fa2858cd35f66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 11:09:02 +0200 Subject: [PATCH 037/301] made polynomial argument names/order consistent also renamed a bunch of things --- Curves/CharMatrix.cs | 26 ++++--- Curves/Polynomial.cs | 167 +++++++++++++++++++++-------------------- Curves/Polynomial2D.cs | 8 +- 3 files changed, 103 insertions(+), 98 deletions(-) diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs index b20abe6..518a528 100644 --- a/Curves/CharMatrix.cs +++ b/Curves/CharMatrix.cs @@ -94,10 +94,10 @@ public CharMatrix4x4( float m00, float m01, float m02, float m03, float m10, flo /// The point index to get the basis function of public Polynomial GetBasisFunction( int i ) { return i switch { - 0 => new Polynomial( m30, m20, m10, m00 ), - 1 => new Polynomial( m31, m21, m11, m01 ), - 2 => new Polynomial( m32, m22, m12, m02 ), - 3 => new Polynomial( m33, m23, m13, m03 ), + 0 => new Polynomial( m00, m10, m20, m30 ), + 1 => new Polynomial( m01, m11, m21, m31 ), + 2 => new Polynomial( m02, m12, m22, m32 ), + 3 => new Polynomial( m03, m13, m23, m33 ), _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) }; } @@ -110,10 +110,11 @@ public Polynomial GetBasisFunction( int i ) { /// The value of the fourth point public Polynomial GetEvalPolynomial( float p0, float p1, float p2, float p3 ) => new( - p0 * m30 + p1 * m31 + p2 * m32 + p3 * m33, - p0 * m20 + p1 * m21 + p2 * m22 + p3 * m23, + p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03, p0 * m10 + p1 * m11 + p2 * m12 + p3 * m13, - p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03); + p0 * m20 + p1 * m21 + p2 * m22 + p3 * m23, + p0 * m30 + p1 * m31 + p2 * m32 + p3 * m33 + ); /// Returns the curve this characteristic matrix represents, given 4 points /// The first point @@ -190,9 +191,9 @@ public CharMatrix3x3( float m00, float m01, float m02, float m10, float m11, flo /// public Polynomial GetBasisFunction( int i ) { return i switch { - 0 => Polynomial.Quadratic( m20, m10, m00 ), - 1 => Polynomial.Quadratic( m21, m11, m01 ), - 2 => Polynomial.Quadratic( m22, m12, m02 ), + 0 => Polynomial.Quadratic( m00, m10, m20 ), + 1 => Polynomial.Quadratic( m01, m11, m21 ), + 2 => Polynomial.Quadratic( m02, m12, m22 ), _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 2" ) }; } @@ -200,9 +201,10 @@ public Polynomial GetBasisFunction( int i ) { /// public Polynomial GetEvalPolynomial( float p0, float p1, float p2 ) => Polynomial.Quadratic( - p0 * m20 + p1 * m21 + p2 * m22, + p0 * m00 + p1 * m01 + p2 * m02, p0 * m10 + p1 * m11 + p2 * m12, - p0 * m00 + p1 * m01 + p2 * m02 ); + p0 * m20 + p1 * m21 + p2 * m22 + ); /// public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2 ) => diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index f21ca87..62529e1 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -37,7 +37,7 @@ public float this[ int degree ] { 1 => c1, 2 => c2, 3 => c3, - _ => throw new IndexOutOfRangeException( "Polynomial factor degree has to be between 0 and 3" ) + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) }; set { _ = degree switch { @@ -45,35 +45,39 @@ public float this[ int degree ] { 1 => c1 = value, 2 => c2 = value, 3 => c3 = value, - _ => throw new IndexOutOfRangeException( "Polynomial factor degree has to be between 0 and 3" ) + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) }; } } /// The degree of the polynomial - public PolynomialDegree Degree => GetPolynomialDegree( c3, c2, c1, c0 ); + public PolynomialDegree Degree => GetPolynomialDegree( c0, c1, c2, c3 ); - /// - public Polynomial( float a, float b, float c, float d ) => ( c3, c2, c1, c0 ) = ( a, b, c, d ); + /// Creates a polynomial up to a cubic + /// The constant coefficient + /// The linear coefficient + /// The quadratic coefficient + /// The cubic coefficient + public Polynomial( float c0, float c1, float c2, float c3 ) => ( this.c0, this.c1, this.c2, this.c3 ) = ( c0, c1, c2, c3 ); /// Evaluates the polynomial at the given value t /// The value to sample at public float Eval( float t ) => c3 * ( t * t * t ) + c2 * ( t * t ) + c1 * t + c0; - + /// Differentiates this function, returning the n-th derivative of this polynomial /// The number of times to differentiate this function. 0 returns the function itself, 1 returns the first derivative public Polynomial Differentiate( int n = 1 ) { return n switch { 0 => this, - 1 => new Polynomial( 0, 3 * c3, 2 * c2, c1 ), - 2 => new Polynomial( 0, 0, 6 * c3, 2 * c2 ), - 3 => new Polynomial( 0, 0, 0, 6 * c3 ), + 1 => new Polynomial( c1, 2 * c2, 3 * c3, 0 ), + 2 => new Polynomial( 2 * c2, 6 * c3, 0, 0 ), + 3 => new Polynomial( 6 * c3, 0, 0, 0 ), _ => n > 3 ? zero : throw new IndexOutOfRangeException( "Cannot differentiate a negative amount of times" ) }; } /// Calculates the roots (values where this polynomial = 0) - public ResultsMax3 Roots => GetCubicRoots( c3, c2, c1, c0 ); + public ResultsMax3 Roots => GetCubicRoots( c0, c1, c2, c3 ); /// Calculates the local extrema of this polynomial public ResultsMax2 LocalExtrema => (ResultsMax2)Differentiate().Roots; @@ -106,93 +110,92 @@ public FloatRange OutputRange01 { #region Statics /// Creates a constant polynomial - /// The constant factor - public static Polynomial Constant( float constant ) => new Polynomial( 0, 0, 0, constant ); + /// The constant coefficient + public static Polynomial Constant( float constant ) => new Polynomial( constant, 0, 0, 0 ); /// Creates a linear polynomial of the form ax+b - /// The linear factor a in ax+b - /// The constant factor b in ax+b - public static Polynomial Linear( float a, float b ) => new Polynomial( 0, 0, a, b ); - - /// Creates a quadratic polynomial of the form ax²+bx+c - /// The quadratic factor a in ax²+bx+c - /// The linear factor b in ax²+bx+c - /// The constant factor c in ax²+bx+c - public static Polynomial Quadratic( float a, float b, float c ) => new Polynomial( 0, a, b, c ); - - /// Creates a cubic polynomial of the form ax³+bx²+cx+d - /// The cubic factor a in ax³+bx²+cx+d - /// The quadratic factor b in ax³+bx²+cx+d - /// The linear factor c in ax³+bx²+cx+d - /// The constant factor d in ax³+bx²+cx+d - public static Polynomial Cubic( float a, float b, float c, float d ) => new Polynomial( a, b, c, d ); - - static bool FactorAlmost0( float v ) => v.Abs() < 0.00001f; - - /// Given ax³+bx²+cx+d, returns the net polynomial type/degree, accounting for values very close to 0 - /// The cubic factor a in ax³+bx²+cx+d - /// The quadratic factor b in ax³+bx²+cx+d - /// The linear factor c in ax³+bx²+cx+d - /// The constant factor d in ax³+bx²+cx+d - [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float a, float b, float c, float d ) => FactorAlmost0( a ) ? GetPolynomialDegree( b, c, d ) : PolynomialDegree.Cubic; - - /// Given ax²+bx+c, returns the net polynomial degree, accounting for values very close to 0 - /// The quadratic factor a in ax²+bx+c - /// The linear factor b in ax²+bx+c - /// The constant factor c in ax²+bx+c - [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float a, float b, float c ) => FactorAlmost0( a ) ? GetPolynomialDegree( b, c ) : PolynomialDegree.Quadratic; - - /// Given ax+b, returns the net polynomial degree, accounting for values very close to 0 - /// The linear factor a in ax+b - /// The constant factor b in ax+b - [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float a, float b ) => FactorAlmost0( a ) ? PolynomialDegree.Constant : PolynomialDegree.Linear; - - /// Returns the roots/solutions of ax³+bx²+cx+d = 0. There's either 0, 1, 2 or 3 roots, filled in left to right among the return values - /// The cubic factor a in ax³+bx²+cx+d - /// The quadratic factor b in ax³+bx²+cx+d - /// The linear factor c in ax³+bx²+cx+d - /// The constant factor d in ax³+bx²+cx+d - public static ResultsMax3 GetCubicRoots( float a, float b, float c, float d ) => - GetPolynomialDegree( a, b, c, d ) switch { + /// The constant coefficient b in ax+b + /// The linear coefficient a in ax+b + public static Polynomial Linear( float c0, float c1 ) => new Polynomial( c0, c1, 0, 0 ); + + /// Creates a quadratic polynomial + /// The constant coefficient + /// The linear coefficient + /// The quadratic coefficient + public static Polynomial Quadratic( float c0, float c1, float c2 ) => new Polynomial( c0, c1, c2, 0 ); + + /// Creates a cubic polynomial + /// The constant coefficient + /// The linear coefficient + /// The quadratic coefficient + /// The cubic coefficient + public static Polynomial Cubic( float c0, float c1, float c2, float c3 ) => new Polynomial( c0, c1, c2, c3 ); + + static bool ValueAlmost0( float v ) => v.Abs() < 0.00001f; + + /// Given the coefficients for a cubic polynomial, returns the net polynomial type/degree, accounting for values very close to 0 + /// The constant coefficient + /// The linear coefficient + /// The quadratic coefficient + /// The cubic coefficient + [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float c0, float c1, float c2, float c3 ) => ValueAlmost0( c3 ) ? GetPolynomialDegree( c0, c1, c2 ) : PolynomialDegree.Cubic; + + /// Given the coefficients for a quadratic polynomial, returns the net polynomial degree, accounting for values very close to 0 + /// The constant coefficient + /// The linear coefficient + /// The quadratic coefficient + [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float c0, float c1, float c2 ) => ValueAlmost0( c2 ) ? GetPolynomialDegree( c0, c1 ) : PolynomialDegree.Quadratic; + + /// Given the coefficients for a linear polynomial, returns the net polynomial degree, accounting for values very close to 0 + /// The constant coefficient + /// The linear coefficient + [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float c0, float c1 ) => ValueAlmost0( c1 ) ? PolynomialDegree.Constant : PolynomialDegree.Linear; + + /// Returns the roots/solutions/x-values where this polynomial equals 0. There's either 0, 1, 2 or 3 roots, filled in left to right among the return values + /// The constant coefficient + /// The linear coefficient + /// The quadratic coefficient + /// The cubic coefficient + public static ResultsMax3 GetCubicRoots( float c0, float c1, float c2, float c3 ) => + GetPolynomialDegree( c0, c1, c2, c3 ) switch { PolynomialDegree.Constant => default, // either no roots or infinite roots if c == 0 - PolynomialDegree.Linear => new ResultsMax3( SolveLinearRoot( c, d ) ), - PolynomialDegree.Quadratic => SolveQuadraticRoots( b, c, d ), - PolynomialDegree.Cubic => SolveCubicRoots( a, b, c, d ), + PolynomialDegree.Linear => new ResultsMax3( SolveLinearRoot( c1, c0 ) ), + PolynomialDegree.Quadratic => SolveQuadraticRoots( c2, c1, c0 ), + PolynomialDegree.Cubic => SolveCubicRoots( c3, c2, c1, c0 ), _ => throw new InvalidEnumArgumentException() }; - /// Returns the roots/solutions of ax²+bx+c = 0. There's either 0, 1 or 2 roots, filled in left to right among the return values - /// The quadratic factor a in ax²+bx+c - /// The linear factor b in ax²+bx+c - /// The constant factor c in ax²+bx+c - public static ResultsMax2 GetQuadraticRoots( float a, float b, float c ) => - GetPolynomialDegree( a, b, c ) switch { + /// Returns the roots/solutions/x-values where this polynomial equals 0. There's either 0, 1 or 2 roots, filled in left to right among the return values + /// The constant coefficient + /// The linear coefficient + /// The quadratic coefficient + public static ResultsMax2 GetQuadraticRoots( float c0, float c1, float c2 ) => + GetPolynomialDegree( c0, c1, c2 ) switch { PolynomialDegree.Constant => default, // either no roots or infinite roots if c == 0 - PolynomialDegree.Linear => new ResultsMax2( SolveLinearRoot( b, c ) ), - PolynomialDegree.Quadratic => SolveQuadraticRoots( a, b, c ), + PolynomialDegree.Linear => new ResultsMax2( SolveLinearRoot( c1, c0 ) ), + PolynomialDegree.Quadratic => SolveQuadraticRoots( c2, c1, c0 ), _ => throw new InvalidEnumArgumentException() }; - /// Returns the root/solution of ax+b = 0. Returns null if there is no root - /// The linear factor a in ax+b - /// The constant factor b in ax+b - public static float? GetLinearRoots( float a, float b ) { - if( GetPolynomialDegree( a, b ) == PolynomialDegree.Constant ) + /// Returns the roots/solutions/x-values where this polynomial equals 0. Returns null if there is no root + /// The constant coefficient + /// The linear coefficient + public static float? GetLinearRoots( float c0, float c1 ) { + if( GetPolynomialDegree( c0, c1 ) == PolynomialDegree.Constant ) return null; - return -b / a; + return -c0 / c1; } - /// Linearly interpolates between two polynomials /// The first polynomial to blend from /// The second polynomial to blend to /// The blend value, typically from 0 to 1 public static Polynomial Lerp( Polynomial a, Polynomial b, float t ) => new( - t.Lerp( a.c3, b.c3 ), - t.Lerp( a.c2, b.c2 ), + t.Lerp( a.c0, b.c0 ), t.Lerp( a.c1, b.c1 ), - t.Lerp( a.c0, b.c0 ) + t.Lerp( a.c2, b.c2 ), + t.Lerp( a.c3, b.c3 ) ); #region Internal root solvers @@ -204,7 +207,7 @@ public static Polynomial Lerp( Polynomial a, Polynomial b, float t ) => static ResultsMax2 SolveQuadraticRoots( float a, float b, float c ) { float rootContent = b * b - 4 * a * c; - if( FactorAlmost0( rootContent ) ) + if( ValueAlmost0( rootContent ) ) return new ResultsMax2( -b / ( 2 * a ) ); // two equivalent solutions at one point if( rootContent >= 0 ) { @@ -236,7 +239,7 @@ static ResultsMax3 SolveCubicRoots( float a, float b, float c, float d ) // t³+pt+q = 0 static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { - if( FactorAlmost0( p ) ) // triple root - one solution. solve x³+q = 0 => x = cr(-q) + if( ValueAlmost0( p ) ) // triple root - one solution. solve x³+q = 0 => x = cr(-q) return new ResultsMax3( Mathfs.Cbrt( -q ) ); float discriminant = 4 * p * p * p + 27 * q * q; if( discriminant < 0.00001 ) { // two or three roots guaranteed, use trig solution @@ -273,9 +276,9 @@ static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { #endregion - public static Polynomial operator /( Polynomial p, float v ) => new(p.c3 / v, p.c2 / v, p.c1 / v, p.c0 / v); - public static Polynomial operator /( float v, Polynomial p ) => new(v / p.c3, v / p.c2, v / p.c1, v / p.c0); - public static Polynomial operator *( Polynomial p, float v ) => new(p.c3 * v, p.c2 * v, p.c1 * v, p.c0 * v); + public static Polynomial operator /( Polynomial p, float v ) => new(p.c0 / v, p.c1 / v, p.c2 / v, p.c3 / v); + public static Polynomial operator /( float v, Polynomial p ) => new(v / p.c0, v / p.c1, v / p.c2, v / p.c3); + public static Polynomial operator *( Polynomial p, float v ) => new(p.c0 * v, p.c1 * v, p.c2 * v, p.c3 * v); public static Polynomial operator *( float v, Polynomial p ) => p * v; } diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index c804af9..0850f1a 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -33,8 +33,8 @@ public Vector2 C3 { public Polynomial2D( Polynomial x, Polynomial y ) => ( this.x, this.y ) = ( x, y ); public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2, Vector2 c3 ) { - this.x = new Polynomial( c3.x, c2.x, c1.x, c0.x ); - this.y = new Polynomial( c3.y, c2.y, c1.y, c0.y ); + this.x = new Polynomial( c0.x, c1.x, c2.x, c3.x ); + this.y = new Polynomial( c0.y, c1.y, c2.y, c3.y ); } /// @@ -193,7 +193,7 @@ private ResultsMax3 Intersect( Vector2 origin, Vector2 direction, bool ra float y1 = Mathfs.Determinant( rel.C1, direction ); float y2 = Mathfs.Determinant( rel.C2, direction ); float y3 = Mathfs.Determinant( rel.C3, direction ); - Polynomial polynomY = new Polynomial( y3, y2, y1, y0 ); + Polynomial polynomY = new Polynomial( y0, y1, y2, y3 ); ResultsMax3 roots = polynomY.Roots; // t values of the function Polynomial polynomX = default; @@ -204,7 +204,7 @@ private ResultsMax3 Intersect( Vector2 origin, Vector2 direction, bool ra float x1 = Vector2.Dot( rel.C1, direction ); float x2 = Vector2.Dot( rel.C2, direction ); float x3 = Vector2.Dot( rel.C3, direction ); - polynomX = new Polynomial( x3, x2, x1, x0 ); + polynomX = new Polynomial( x0, x1, x2, x3 ); } float CurveTtoRayT( float t ) => polynomX.Eval( t ); From 64ed198da1d37d76d7b03278197457a95b1c3055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 11:12:17 +0200 Subject: [PATCH 038/301] replaced PolynomialDegree enum with just an int easier that way --- Curves/Polynomial.cs | 31 +++++++++++++++---------------- Curves/PolynomialDegree.cs | 21 --------------------- 2 files changed, 15 insertions(+), 37 deletions(-) delete mode 100644 Curves/PolynomialDegree.cs diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index 62529e1..8742350 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -1,7 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; -using System.ComponentModel; using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Serialization; @@ -51,7 +50,7 @@ public float this[ int degree ] { } /// The degree of the polynomial - public PolynomialDegree Degree => GetPolynomialDegree( c0, c1, c2, c3 ); + public int Degree => GetPolynomialDegree( c0, c1, c2, c3 ); /// Creates a polynomial up to a cubic /// The constant coefficient @@ -131,25 +130,25 @@ public FloatRange OutputRange01 { /// The cubic coefficient public static Polynomial Cubic( float c0, float c1, float c2, float c3 ) => new Polynomial( c0, c1, c2, c3 ); - static bool ValueAlmost0( float v ) => v.Abs() < 0.00001f; + static bool ValueAlmost0( float v ) => Mathfs.Approximately( v, 0 ); /// Given the coefficients for a cubic polynomial, returns the net polynomial type/degree, accounting for values very close to 0 /// The constant coefficient /// The linear coefficient /// The quadratic coefficient /// The cubic coefficient - [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float c0, float c1, float c2, float c3 ) => ValueAlmost0( c3 ) ? GetPolynomialDegree( c0, c1, c2 ) : PolynomialDegree.Cubic; + [MethodImpl( INLINE )] public static int GetPolynomialDegree( float c0, float c1, float c2, float c3 ) => ValueAlmost0( c3 ) ? GetPolynomialDegree( c0, c1, c2 ) : 3; /// Given the coefficients for a quadratic polynomial, returns the net polynomial degree, accounting for values very close to 0 /// The constant coefficient /// The linear coefficient /// The quadratic coefficient - [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float c0, float c1, float c2 ) => ValueAlmost0( c2 ) ? GetPolynomialDegree( c0, c1 ) : PolynomialDegree.Quadratic; + [MethodImpl( INLINE )] public static int GetPolynomialDegree( float c0, float c1, float c2 ) => ValueAlmost0( c2 ) ? GetPolynomialDegree( c0, c1 ) : 2; /// Given the coefficients for a linear polynomial, returns the net polynomial degree, accounting for values very close to 0 /// The constant coefficient /// The linear coefficient - [MethodImpl( INLINE )] public static PolynomialDegree GetPolynomialDegree( float c0, float c1 ) => ValueAlmost0( c1 ) ? PolynomialDegree.Constant : PolynomialDegree.Linear; + [MethodImpl( INLINE )] public static int GetPolynomialDegree( float c0, float c1 ) => ValueAlmost0( c1 ) ? 0 : 1; /// Returns the roots/solutions/x-values where this polynomial equals 0. There's either 0, 1, 2 or 3 roots, filled in left to right among the return values /// The constant coefficient @@ -158,11 +157,11 @@ public FloatRange OutputRange01 { /// The cubic coefficient public static ResultsMax3 GetCubicRoots( float c0, float c1, float c2, float c3 ) => GetPolynomialDegree( c0, c1, c2, c3 ) switch { - PolynomialDegree.Constant => default, // either no roots or infinite roots if c == 0 - PolynomialDegree.Linear => new ResultsMax3( SolveLinearRoot( c1, c0 ) ), - PolynomialDegree.Quadratic => SolveQuadraticRoots( c2, c1, c0 ), - PolynomialDegree.Cubic => SolveCubicRoots( c3, c2, c1, c0 ), - _ => throw new InvalidEnumArgumentException() + 0 => default, // either no roots or infinite roots if c == 0 + 1 => new ResultsMax3( SolveLinearRoot( c1, c0 ) ), + 2 => SolveQuadraticRoots( c2, c1, c0 ), + 3 => SolveCubicRoots( c3, c2, c1, c0 ), + _ => throw new IndexOutOfRangeException() }; /// Returns the roots/solutions/x-values where this polynomial equals 0. There's either 0, 1 or 2 roots, filled in left to right among the return values @@ -171,17 +170,17 @@ public static ResultsMax3 GetCubicRoots( float c0, float c1, float c2, fl /// The quadratic coefficient public static ResultsMax2 GetQuadraticRoots( float c0, float c1, float c2 ) => GetPolynomialDegree( c0, c1, c2 ) switch { - PolynomialDegree.Constant => default, // either no roots or infinite roots if c == 0 - PolynomialDegree.Linear => new ResultsMax2( SolveLinearRoot( c1, c0 ) ), - PolynomialDegree.Quadratic => SolveQuadraticRoots( c2, c1, c0 ), - _ => throw new InvalidEnumArgumentException() + 0 => default, // either no roots or infinite roots if c == 0 + 1 => new ResultsMax2( SolveLinearRoot( c1, c0 ) ), + 2 => SolveQuadraticRoots( c2, c1, c0 ), + _ => throw new IndexOutOfRangeException() }; /// Returns the roots/solutions/x-values where this polynomial equals 0. Returns null if there is no root /// The constant coefficient /// The linear coefficient public static float? GetLinearRoots( float c0, float c1 ) { - if( GetPolynomialDegree( c0, c1 ) == PolynomialDegree.Constant ) + if( GetPolynomialDegree( c0, c1 ) == 0 ) return null; return -c0 / c1; } diff --git a/Curves/PolynomialDegree.cs b/Curves/PolynomialDegree.cs deleted file mode 100644 index 47e75be..0000000 --- a/Curves/PolynomialDegree.cs +++ /dev/null @@ -1,21 +0,0 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) - -namespace Freya { - - /// The type/degree of a polynomial - public enum PolynomialDegree { - - /// A polynomial that is just a, straight up constant value - Constant, - - /// A polynomial of the form ax+b - Linear, - - /// A polynomial of the form ax²+bx+c - Quadratic, - - /// A polynomial of the form ax³+bx²+cx+d - Cubic - } - -} \ No newline at end of file From 2c159afabd40f33e5c167218630972784c5fc5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 11:14:22 +0200 Subject: [PATCH 039/301] tiny optimization of the cubic root finder --- Curves/Polynomial.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index 8742350..af60f61 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -221,8 +221,11 @@ static ResultsMax2 SolveQuadraticRoots( float a, float b, float c ) { static ResultsMax3 SolveCubicRoots( float a, float b, float c, float d ) { // first, depress the cubic to make it easier to solve - float p = ( 3 * a * c - b * b ) / ( 3 * a * a ); - float q = ( 2 * b * b * b - 9 * a * b * c + 27 * a * a * d ) / ( 27 * a * a * a ); + float aa = a * a; + float ac = a * c; + float bb = b * b; + float p = ( 3 * ac - bb ) / ( 3 * aa ); + float q = ( 2 * bb * b - 9 * ac * b + 27 * aa * d ) / ( 27 * aa * a ); ResultsMax3 dpr = SolveDepressedCubicRoots( p, q ); From e5fdd55fa0f84a782ac24eb4295942d2760d296e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 11:19:30 +0200 Subject: [PATCH 040/301] fixed and generalized catrom knot calcs it was using the 2D calc for the 3D spline, which, is not good~ --- Curves/SplineUtils.cs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Curves/SplineUtils.cs b/Curves/SplineUtils.cs index 22bc662..a21c78c 100644 --- a/Curves/SplineUtils.cs +++ b/Curves/SplineUtils.cs @@ -32,13 +32,33 @@ public static float CalcCatRomKnot( float kPrev, float alpha, float sqDist ) { return kPrev + sqDist.Pow( 0.5f * alpha ).AtLeast( 0.00001f ); // ensure there are no duplicate knots } + static (float, float, float, float) GetUniformKnots( bool unitInterval ) => unitInterval ? ( -1, 0, 1, 2 ) : ( 0, 1, 2, 3 ); + public static (float, float, float, float) CalcCatRomKnots( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float alpha, bool unitInterval ) { if( alpha == 0 ) // uniform catrom - return ( -1, 0, 1, 2 ); - float i01 = Vector2.SqrMagnitude( p0 - p1 ).Pow( 0.5f * alpha ); - float i12 = Vector2.SqrMagnitude( p1 - p2 ).Pow( 0.5f * alpha ); - float i23 = Vector2.SqrMagnitude( p2 - p3 ).Pow( 0.5f * alpha ); + return GetUniformKnots( unitInterval ); + float sqMag01 = Vector2.SqrMagnitude( p0 - p1 ); + float sqMag12 = Vector2.SqrMagnitude( p1 - p2 ); + float sqMag23 = Vector2.SqrMagnitude( p2 - p3 ); + return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval ); + } + + public static (float, float, float, float) CalcCatRomKnots( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float alpha, bool unitInterval ) { + if( alpha == 0 ) // uniform catrom + return GetUniformKnots( unitInterval ); + float sqMag01 = Vector3.SqrMagnitude( p0 - p1 ); + float sqMag12 = Vector3.SqrMagnitude( p1 - p2 ); + float sqMag23 = Vector3.SqrMagnitude( p2 - p3 ); + return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval ); + } + static (float, float, float, float) CalcCatRomKnots( float sqMag01, float sqMag12, float sqMag23, float alpha, bool unitInterval ) { + ( float i01, float i12, float i23 ) = alpha switch { + 0 => ( 1, 1, 1 ), // uniform + 1 => ( sqMag01.Sqrt(), sqMag12.Sqrt(), sqMag23.Sqrt() ), // chordal + 2 => ( sqMag01, sqMag12, sqMag23 ), + _ => ( sqMag01.Pow( 0.5f * alpha ), sqMag12.Pow( 0.5f * alpha ), sqMag23.Pow( 0.5f * alpha ) ) + }; float k0, k1, k2, k3; if( unitInterval ) { return ( -i01 / i12, 0, 1, 1 + i23 / i12 ); From bc2bb4e0e43434659c75c65e3c06eab9d4f85748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 11:30:24 +0200 Subject: [PATCH 041/301] credit lines --- Curves/CharMatrix.cs | 2 ++ Curves/SplineUtils.cs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Curves/CharMatrix.cs b/Curves/CharMatrix.cs index 518a528..b3ccf0f 100644 --- a/Curves/CharMatrix.cs +++ b/Curves/CharMatrix.cs @@ -1,3 +1,5 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + using System; using UnityEngine; diff --git a/Curves/SplineUtils.cs b/Curves/SplineUtils.cs index a21c78c..fa8c6b7 100644 --- a/Curves/SplineUtils.cs +++ b/Curves/SplineUtils.cs @@ -1,4 +1,6 @@ -using System; +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; using UnityEngine; namespace Freya { From 5e5043f972bae47322177b91473fa8a28b3ab9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 11:30:38 +0200 Subject: [PATCH 042/301] moved n-degree beziers into uniform folder --- Curves/{ => Uniform Spline Segments}/Bezier2D.cs | 0 Curves/{ => Uniform Spline Segments}/Bezier3D.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Curves/{ => Uniform Spline Segments}/Bezier2D.cs (100%) rename Curves/{ => Uniform Spline Segments}/Bezier3D.cs (100%) diff --git a/Curves/Bezier2D.cs b/Curves/Uniform Spline Segments/Bezier2D.cs similarity index 100% rename from Curves/Bezier2D.cs rename to Curves/Uniform Spline Segments/Bezier2D.cs diff --git a/Curves/Bezier3D.cs b/Curves/Uniform Spline Segments/Bezier3D.cs similarity index 100% rename from Curves/Bezier3D.cs rename to Curves/Uniform Spline Segments/Bezier3D.cs From d5228c3e4ffecdade3561bf26852b0752f58e4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 11:30:49 +0200 Subject: [PATCH 043/301] Polynomial3D coefficient setters --- Curves/Polynomial3D.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs index cfbc875..a8bd7e9 100644 --- a/Curves/Polynomial3D.cs +++ b/Curves/Polynomial3D.cs @@ -11,10 +11,23 @@ public struct Polynomial3D : IParamCurve3Diff { public Polynomial y; public Polynomial z; - public Vector3 C0 => new(x.c0, y.c0, z.c0); - public Vector3 C1 => new(x.c1, y.c1, z.c1); - public Vector3 C2 => new(x.c2, y.c2, z.c2); - public Vector3 C3 => new(x.c3, y.c3, z.c3); + public Vector3 C0 { + get => new(x.c0, y.c0, z.c0); + set => ( x.c0, y.c0, z.c0 ) = ( value.x, value.y, value.z ); + } + public Vector3 C1 { + get => new(x.c1, y.c1, z.c1); + set => ( x.c1, y.c1, z.c1 ) = ( value.x, value.y, value.z ); + } + public Vector3 C2 { + get => new(x.c2, y.c2, z.c2); + set => ( x.c2, y.c2, z.c2 ) = ( value.x, value.y, value.z ); + } + public Vector3 C3 { + get => new(x.c3, y.c3, z.c3); + set => ( x.c3, y.c3, z.c3 ) = ( value.x, value.y, value.z ); + } + public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, _ => throw new IndexOutOfRangeException( "Polynomial3D component index has to be either 0, 1, or 2" ) }; From 760ab986886df104666b16196d0bf4dc1164ab43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 17:10:31 +0200 Subject: [PATCH 044/301] overhauled BezierSampler into UniformCurveSampler --- Curves/BezierSampler.cs | 112 ---------------------------- Curves/UniformCurveSampler.cs | 134 ++++++++++++++++++++++++++++++++++ FloatRange.cs | 3 + 3 files changed, 137 insertions(+), 112 deletions(-) delete mode 100644 Curves/BezierSampler.cs create mode 100644 Curves/UniformCurveSampler.cs diff --git a/Curves/BezierSampler.cs b/Curves/BezierSampler.cs deleted file mode 100644 index 845dca5..0000000 --- a/Curves/BezierSampler.cs +++ /dev/null @@ -1,112 +0,0 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) - -using UnityEngine; - -namespace Freya { - - /// A helper class to let you sample a bezier curve by uniform T-values or by distance - public class BezierSampler { - - /// The number of distance samples used when calculating the cumulative distance table - public readonly int resolution; - - /// Cumulative distance samples, where the first element is 0 and the last element is the total length of the curve - public readonly float[] cumulativeDistances; - - /// Returns the approximate length of this curve. This property doesn't have to recalculate length, since it's already stored in the cumulative distances array - public float CurveLength => cumulativeDistances[resolution - 1]; - - // The way the point recalculation works right now is pretty naive, and doesn't handle extreme acceleration very well. - // Right now you need around 30 samples to properly sample, which, is a lot - - #region Constructors & Point recalc - - /// Creates a sampler that can be used to sample a bezier curve by distance or by uniform t-values. - /// You'll need to call sampler.Recalculate(bezier) to recalculate if the curve changes shape after this points. - /// Recommended resolution for animation: [8-16] - /// Recommended resolution for even point spacing: [16-50] - /// The curve to use when sampling - /// The accuracy of this sampler. - /// Higher values are more accurate, but are more costly to calculate for every new bezier shape - public BezierSampler( BezierCubic2D bezier, int resolution = 12 ) { - this.resolution = resolution; - cumulativeDistances = new float[resolution]; - Recalculate( bezier ); - } - - /// Creates a sampler that can be used to sample a bezier curve by distance or by uniform t-values. - /// You'll need to call sampler.Recalculate(bezier) to recalculate if the curve changes shape after this points. - /// Recommended resolution for animation: [8-16] - /// Recommended resolution for even point spacing: [16-50] - /// The curve to use when sampling - /// The accuracy of this sampler. - /// Higher values are more accurate, but are more costly to calculate for every new bezier shape - public BezierSampler( BezierCubic3D bezier, int resolution = 12 ) { - this.resolution = resolution; - cumulativeDistances = new float[resolution]; - Recalculate( bezier ); - } - - /// Recalculates the internal lookup table so that the bezier can be sampled by distance or by uniform t-values. - /// Only call this before sampling a different curve, or if the curve has changed shape since last time it was calculated - /// The curve to use when sampling - public void Recalculate( BezierCubic2D bezier ) { - float cumulativeLength = 0; - Vector2 prevPt = bezier.P0; - cumulativeDistances[0] = 0; - for( int i = 1; i < resolution; i++ ) { // todo: could optimize by moving all points so that p0 = (0,0) - Vector2 pt = bezier.Curve.Eval( i / ( resolution - 1f ) ); - cumulativeLength += Vector2.Distance( prevPt, pt ); - cumulativeDistances[i] = cumulativeLength; - prevPt = pt; - } - } - - /// Recalculates the internal lookup table so that the bezier can be sampled by distance or by uniform t-values. - /// Only call this before sampling a different curve, or if the curve has changed shape since last time it was calculated - /// The curve to use when sampling - public void Recalculate( BezierCubic3D bezier ) { - float cumulativeLength = 0; - Vector3 prevPt = bezier.P0; - cumulativeDistances[0] = 0; - for( int i = 1; i < resolution; i++ ) { // todo: could optimize by moving all points so that p0 = (0,0) - Vector3 pt = bezier.Curve.Eval( i / ( resolution - 1f ) ); - cumulativeLength += Vector3.Distance( prevPt, pt ); - cumulativeDistances[i] = cumulativeLength; - prevPt = pt; - } - } - - #endregion - - /// Converts a uniform t-value to a t-value. Useful to uniformly sample a bezier curve - /// A value from 0 to 1 representing uniform distance along the spline - public float UniformToT( float tUniform ) => DistanceToT( tUniform * CurveLength ); - - /// Converts a distance value to a t-value. Useful to sample a bezier curve by distance - /// The distance along the bezier curve at which you'd like to get the t-value for - public float DistanceToT( float distance ) { - // check if the value is within the length of the curve - if( distance.Between( 0, CurveLength ) ) { - // find which two distance values our input distance lies between - for( int i = 0; i < resolution - 1; i++ ) { - float distPrev = cumulativeDistances[i]; - float distNext = cumulativeDistances[i + 1]; - if( distance.Within( distPrev, distNext ) ) { // check if our input distance lies between the two distances - // get t-values at the samples - float tPrev = i / ( resolution - 1f ); - float tNext = ( i + 1 ) / ( resolution - 1f ); - // remap the distance range to the t-value range - return distance.Remap( distPrev, distNext, tPrev, tNext ); - } - } - } - - // distance is outside the length of the curve - extrapolate values outside - return distance / CurveLength; - } - - - } - -} \ No newline at end of file diff --git a/Curves/UniformCurveSampler.cs b/Curves/UniformCurveSampler.cs new file mode 100644 index 0000000..71ca064 --- /dev/null +++ b/Curves/UniformCurveSampler.cs @@ -0,0 +1,134 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using UnityEngine; + +namespace Freya { + + /// A helper class to let you sample a curve by uniform parameter values, t-values or by distance + public class UniformCurveSampler { + + /// The number of distance samples used when calculating the cumulative distance table + public readonly int resolution; + + /// Cumulative distance samples, where the first element is 0 and the last element is the total length of the curve + public readonly float[] cumulativeDistances; + + /// Returns the approximate length of this curve. This property doesn't have to recalculate length, since it's already stored in the cumulative distances array + public float CurveIntervalLength => cumulativeDistances[resolution - 1]; + + FloatRange paramInterval; + + /// The t-value range in which we've calculated the uniform sampler for + public FloatRange ParamInterval => paramInterval; + + // The way the point recalculation works right now is pretty naive, and doesn't handle extreme acceleration very well. + // Right now you need around 30 samples to properly sample, which, is a lot + + #region Constructors & Point recalc + + /// Creates a sampler that can be used to sample a curve by distance or by uniform t-values. + /// You'll need to call sampler.Recalculate(curve) to recalculate if the curve changes shape after this points. + /// Recommended resolution for animation: [8-16] + /// Recommended resolution for even point spacing: [16-50] + /// The curve to use when sampling + /// The interval you want to uniformly sample within + /// The accuracy of this sampler. + /// Higher values are more accurate, but are more costly to calculate for every new curve shape + public UniformCurveSampler( Polynomial2D curve, FloatRange interval, int resolution = 12 ) { + this.resolution = resolution; + cumulativeDistances = new float[resolution]; + Recalculate( curve, interval ); + } + + /// + public UniformCurveSampler( Polynomial2D curve, int resolution = 12 ) : this( curve, FloatRange.unit, resolution ) { + } + + /// + public UniformCurveSampler( Polynomial3D curve, FloatRange interval, int resolution = 12 ) { + this.resolution = resolution; + cumulativeDistances = new float[resolution]; + Recalculate( curve, interval ); + } + + /// + public UniformCurveSampler( Polynomial3D curve, int resolution = 12 ) : this( curve, FloatRange.unit, resolution ) { + } + + /// Recalculates the internal lookup table so that the curve can be sampled by distance or by uniform t-values. + /// Only call this before sampling a different curve, or if the curve has changed shape since last time it was calculated + /// The curve to use when sampling + /// The interval you want to uniformly sample within + public void Recalculate( Polynomial2D curve, FloatRange interval ) { + this.paramInterval = interval; + float cumulativeLength = 0; + Vector2 prevPt = curve.Eval( paramInterval.a ); + cumulativeDistances[0] = 0; + for( int i = 1; i < resolution; i++ ) { + Vector2 pt = curve.Eval( paramInterval.Lerp( i / ( resolution - 1f ) ) ); + cumulativeLength += Vector2.Distance( prevPt, pt ); + cumulativeDistances[i] = cumulativeLength; + prevPt = pt; + } + } + + /// + public void Recalculate( Polynomial2D curve ) => Recalculate( curve, FloatRange.unit ); + + /// + public void Recalculate( Polynomial3D curve, FloatRange interval ) { + this.paramInterval = interval; + float cumulativeLength = 0; + Vector3 prevPt = curve.Eval( paramInterval.a ); + cumulativeDistances[0] = 0; + for( int i = 1; i < resolution; i++ ) { + Vector3 pt = curve.Eval( paramInterval.Lerp( i / ( resolution - 1f ) ) ); + cumulativeLength += Vector3.Distance( prevPt, pt ); + cumulativeDistances[i] = cumulativeLength; + prevPt = pt; + } + } + + /// + public void Recalculate( Polynomial3D curve ) => Recalculate( curve, FloatRange.unit ); + + #endregion + + float GetParamAtSegmentTValue( float t ) => paramInterval.InverseLerp( t ); + + /// Converts a t-value along the segment to a raw parameter value. Useful to uniformly sample a curve + /// A value from 0 to 1 representing uniform position along the curve interval + public float UniformTToParam( float t ) => DistanceToParam( t * CurveIntervalLength ); + + /// Converts a uniform parameter value to a raw parameter value. Useful to uniformly sample a curve + /// The input parameter for uniform position along the curve + public float UniformParamToParam( float u ) => DistanceToParam( paramInterval.InverseLerp( u ) * CurveIntervalLength ); + + /// Converts a distance value (relative to the start of the interval) to a parameter value. Useful to sample a curve by distance + /// The distance along the curve segment parameter interval at which you'd like to get the parameter value for + public float DistanceToParam( float distance ) { + // check if the value is within the length of the curve + if( distance.Between( 0, CurveIntervalLength ) ) { + // find which two distance values our input distance lies between + for( int i = 0; i < resolution - 1; i++ ) { + float distPrev = cumulativeDistances[i]; + float distNext = cumulativeDistances[i + 1]; + if( distance.Within( distPrev, distNext ) ) { // check if our input distance lies between the two distances + // get t-values at the samples + float tPrev = i / ( resolution - 1f ); + float tNext = ( i + 1 ) / ( resolution - 1f ); + // remap the distance range to the t-value range + float tLocal = distance.Remap( distPrev, distNext, tPrev, tNext ); + return paramInterval.Lerp( tLocal ); + } + } + } + + // distance is outside the length of the curve - extrapolate values outside + return paramInterval.Lerp( distance / CurveIntervalLength ); + } + + + } + +} \ No newline at end of file diff --git a/FloatRange.cs b/FloatRange.cs index 02152dd..051208a 100644 --- a/FloatRange.cs +++ b/FloatRange.cs @@ -7,6 +7,9 @@ namespace Freya { /// A value range between two values a and b public readonly struct FloatRange { + /// The unit interval of 0 to 1 + public static readonly FloatRange unit = new FloatRange( 0, 1 ); + /// The start of this range public readonly float a; From 5376339a103a4bc33bc898f4024cda8e426e16cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 17:10:40 +0200 Subject: [PATCH 045/301] formatting stuff --- Curves/Uniform Spline Segments/Bezier3D.cs | 3 ++- FloatRange.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Curves/Uniform Spline Segments/Bezier3D.cs b/Curves/Uniform Spline Segments/Bezier3D.cs index ab3a3a1..90c1f73 100644 --- a/Curves/Uniform Spline Segments/Bezier3D.cs +++ b/Curves/Uniform Spline Segments/Bezier3D.cs @@ -14,8 +14,9 @@ namespace Freya { /// public readonly Vector3[] points; + readonly Vector3[] ptEvalBuffer; - + /// public int Count { [MethodImpl( INLINE )] get => points.Length; diff --git a/FloatRange.cs b/FloatRange.cs index 051208a..61e3966 100644 --- a/FloatRange.cs +++ b/FloatRange.cs @@ -74,14 +74,14 @@ public FloatRange Encapsulate( float value ) => /// The range of the X axis /// The range of the Y axis public static Rect ToRect( FloatRange rangeX, FloatRange rangeY ) => new Rect( rangeX.Min, rangeY.Min, rangeX.Length, rangeY.Length ); - + /// Returns the bounding box encapsulating the region defined by a range per axis. Note: The direction of each range is ignored /// The range of the X axis /// The range of the Y axis /// The range of the Z axis public static Bounds ToBounds( FloatRange rangeX, FloatRange rangeY, FloatRange rangeZ ) { - Vector3 center = new ( rangeX.Center, rangeY.Center, rangeZ.Center ); - Vector3 size = new ( rangeX.Length, rangeY.Length, rangeZ.Length ); + Vector3 center = new(rangeX.Center, rangeY.Center, rangeZ.Center); + Vector3 size = new(rangeX.Length, rangeY.Length, rangeZ.Length); return new Bounds( center, size ); } From 4e956cc19bed8670a426131c6613a665a8431c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 17:11:09 +0200 Subject: [PATCH 046/301] added knot properties to NUCatRomCubic --- .../NUCatRomCubic2D.cs | 37 +++++++++++++++++++ .../NUCatRomCubic3D.cs | 37 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs b/Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs index 2124946..a35ed70 100644 --- a/Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs +++ b/Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs @@ -118,6 +118,43 @@ public Vector2 P3 { set => _ = ( p3 = value, validCoefficients = false ); } + /// The knot value of the first control point of the catrom curve + public float K0 { + [MethodImpl( INLINE )] get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return k0; + } + set => _ = ( k0 = value, validCoefficients = false ); + } + /// The knot value of the second control point, and the start of the catrom curve + public float K1 { + [MethodImpl( INLINE )] get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return k1; + } + set => _ = ( k1 = value, validCoefficients = false ); + } + /// The knot value of the third control point, and the end of the catrom curve + public float K2 { + [MethodImpl( INLINE )] get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return k2; + } + set => _ = ( k2 = value, validCoefficients = false ); + } + /// The knot value of the last control point of the catrom curve + public float K3 { + [MethodImpl( INLINE )] get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return k3; + } + set => _ = ( k3 = value, validCoefficients = false ); + } + /// The alpha parameter, which controls how much the length of each segment should influence the shape of the curve. /// A value of 0 is called a uniform catrom, and is fast to evaluate but has a tendency to overshoot. /// A value of 0.5 is a centripetal catrom, which follows points very tightly, and prevents cusps and loops. diff --git a/Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs b/Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs index 12bfa12..4de8cf9 100644 --- a/Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs +++ b/Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs @@ -89,6 +89,43 @@ public Vector3 P3 { set => _ = ( p3 = value, validCoefficients = false ); } + /// + public float K0 { + [MethodImpl( INLINE )] get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return k0; + } + set => _ = ( k0 = value, validCoefficients = false ); + } + /// + public float K1 { + [MethodImpl( INLINE )] get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return k1; + } + set => _ = ( k1 = value, validCoefficients = false ); + } + /// + public float K2 { + [MethodImpl( INLINE )] get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return k2; + } + set => _ = ( k2 = value, validCoefficients = false ); + } + /// + public float K3 { + [MethodImpl( INLINE )] get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return k3; + } + set => _ = ( k3 = value, validCoefficients = false ); + } + /// public float Alpha { [MethodImpl( INLINE )] get => alpha; From 916fd4a4f1940db1e3ee6dfafa16940e2048dfb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 17:14:18 +0200 Subject: [PATCH 047/301] terminology update --- Splines/BSpline2D.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Splines/BSpline2D.cs b/Splines/BSpline2D.cs index 65b8d2d..60f2f05 100644 --- a/Splines/BSpline2D.cs +++ b/Splines/BSpline2D.cs @@ -84,10 +84,10 @@ public bool Open { #endregion - #region Derivative + #region Differentiation /// Returns the derivative of this B-spline, which is a B-spline in and of itself - public BSpline2D GetDerivative() { + public BSpline2D Differentiate() { // knots are the same except we remove the two outermost ones float[] dKnots = new float[KnotCount - 2]; for( int i = 0; i < dKnots.Length; i++ ) From f4e5cb27010d7bf09bbce4dfd88100ed154d145c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 29 May 2022 22:38:55 +0200 Subject: [PATCH 048/301] moved lots of files around and renamed things for consistency --- {Curves => Splines}/CatRomType.cs | 0 {Curves => Splines}/CharMatrix.cs | 0 Splines/{ => Multi-Segment Splines}/BSpline2D.cs | 0 Splines/{Nurbs2D.cs => Multi-Segment Splines/NURBS2D.cs} | 8 ++++---- .../Non-Uniform Spline Segments/NUCatRomCubic2D.cs | 0 .../Non-Uniform Spline Segments/NUCatRomCubic3D.cs | 0 {Curves => Splines}/SplineUtils.cs | 0 {Curves => Splines}/Trajectory2D.cs | 0 {Curves => Splines}/Uniform Spline Segments/Bezier2D.cs | 0 {Curves => Splines}/Uniform Spline Segments/Bezier3D.cs | 0 .../Uniform Spline Segments/BezierCubic2D.cs | 0 .../Uniform Spline Segments/BezierCubic3D.cs | 0 .../Uniform Spline Segments/BezierQuad2D.cs | 0 .../Uniform Spline Segments/BezierQuad3D.cs | 0 .../Uniform Spline Segments/CatRomCubic2D.cs | 0 .../Uniform Spline Segments/CatRomCubic3D.cs | 0 .../Uniform Spline Segments/HermiteCubic2D.cs | 0 .../Uniform Spline Segments/HermiteCubic3D.cs | 0 {Curves => Splines}/Uniform Spline Segments/UBSCubic2D.cs | 0 {Curves => Splines}/Uniform Spline Segments/UBSCubic3D.cs | 0 {Curves => Splines}/UniformCurveSampler.cs | 0 21 files changed, 4 insertions(+), 4 deletions(-) rename {Curves => Splines}/CatRomType.cs (100%) rename {Curves => Splines}/CharMatrix.cs (100%) rename Splines/{ => Multi-Segment Splines}/BSpline2D.cs (100%) rename Splines/{Nurbs2D.cs => Multi-Segment Splines/NURBS2D.cs} (92%) rename {Curves => Splines}/Non-Uniform Spline Segments/NUCatRomCubic2D.cs (100%) rename {Curves => Splines}/Non-Uniform Spline Segments/NUCatRomCubic3D.cs (100%) rename {Curves => Splines}/SplineUtils.cs (100%) rename {Curves => Splines}/Trajectory2D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/Bezier2D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/Bezier3D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/BezierCubic2D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/BezierCubic3D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/BezierQuad2D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/BezierQuad3D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/CatRomCubic2D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/CatRomCubic3D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/HermiteCubic2D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/HermiteCubic3D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/UBSCubic2D.cs (100%) rename {Curves => Splines}/Uniform Spline Segments/UBSCubic3D.cs (100%) rename {Curves => Splines}/UniformCurveSampler.cs (100%) diff --git a/Curves/CatRomType.cs b/Splines/CatRomType.cs similarity index 100% rename from Curves/CatRomType.cs rename to Splines/CatRomType.cs diff --git a/Curves/CharMatrix.cs b/Splines/CharMatrix.cs similarity index 100% rename from Curves/CharMatrix.cs rename to Splines/CharMatrix.cs diff --git a/Splines/BSpline2D.cs b/Splines/Multi-Segment Splines/BSpline2D.cs similarity index 100% rename from Splines/BSpline2D.cs rename to Splines/Multi-Segment Splines/BSpline2D.cs diff --git a/Splines/Nurbs2D.cs b/Splines/Multi-Segment Splines/NURBS2D.cs similarity index 92% rename from Splines/Nurbs2D.cs rename to Splines/Multi-Segment Splines/NURBS2D.cs index 697b1fa..59dfcba 100644 --- a/Splines/Nurbs2D.cs +++ b/Splines/Multi-Segment Splines/NURBS2D.cs @@ -3,7 +3,7 @@ namespace Freya { - public class Nurbs2D { + public class NURBS2D { public Vector2[] points; public float[] knots; @@ -17,10 +17,10 @@ public class Nurbs2D { public int KnotCount => degree + PointCount + 1; public int SegmentCount => KnotCount - degree * 2 - 1; - public static Nurbs2D GetUniformBSpline( Vector2[] points, int degree = 3, bool open = true ) { + public static NURBS2D GetUniformBSpline( Vector2[] points, int degree = 3, bool open = true ) { int ptCount = points.Length; float[] knots = SplineUtils.GenerateUniformKnots( degree, ptCount, open ); - return new Nurbs2D( points, knots, null, degree ); + return new NURBS2D( points, knots, null, degree ); } public static float[] GetUnweightedWeights( int count ) { @@ -31,7 +31,7 @@ public static float[] GetUnweightedWeights( int count ) { } - public Nurbs2D( Vector2[] points, float[] knots, float[] weights, int degree = 3 ) { + public NURBS2D( Vector2[] points, float[] knots, float[] weights, int degree = 3 ) { this.points = points; this.knots = knots; this.degree = degree; diff --git a/Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs b/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs similarity index 100% rename from Curves/Non-Uniform Spline Segments/NUCatRomCubic2D.cs rename to Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs diff --git a/Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs b/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs similarity index 100% rename from Curves/Non-Uniform Spline Segments/NUCatRomCubic3D.cs rename to Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs diff --git a/Curves/SplineUtils.cs b/Splines/SplineUtils.cs similarity index 100% rename from Curves/SplineUtils.cs rename to Splines/SplineUtils.cs diff --git a/Curves/Trajectory2D.cs b/Splines/Trajectory2D.cs similarity index 100% rename from Curves/Trajectory2D.cs rename to Splines/Trajectory2D.cs diff --git a/Curves/Uniform Spline Segments/Bezier2D.cs b/Splines/Uniform Spline Segments/Bezier2D.cs similarity index 100% rename from Curves/Uniform Spline Segments/Bezier2D.cs rename to Splines/Uniform Spline Segments/Bezier2D.cs diff --git a/Curves/Uniform Spline Segments/Bezier3D.cs b/Splines/Uniform Spline Segments/Bezier3D.cs similarity index 100% rename from Curves/Uniform Spline Segments/Bezier3D.cs rename to Splines/Uniform Spline Segments/Bezier3D.cs diff --git a/Curves/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs similarity index 100% rename from Curves/Uniform Spline Segments/BezierCubic2D.cs rename to Splines/Uniform Spline Segments/BezierCubic2D.cs diff --git a/Curves/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs similarity index 100% rename from Curves/Uniform Spline Segments/BezierCubic3D.cs rename to Splines/Uniform Spline Segments/BezierCubic3D.cs diff --git a/Curves/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs similarity index 100% rename from Curves/Uniform Spline Segments/BezierQuad2D.cs rename to Splines/Uniform Spline Segments/BezierQuad2D.cs diff --git a/Curves/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs similarity index 100% rename from Curves/Uniform Spline Segments/BezierQuad3D.cs rename to Splines/Uniform Spline Segments/BezierQuad3D.cs diff --git a/Curves/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs similarity index 100% rename from Curves/Uniform Spline Segments/CatRomCubic2D.cs rename to Splines/Uniform Spline Segments/CatRomCubic2D.cs diff --git a/Curves/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs similarity index 100% rename from Curves/Uniform Spline Segments/CatRomCubic3D.cs rename to Splines/Uniform Spline Segments/CatRomCubic3D.cs diff --git a/Curves/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs similarity index 100% rename from Curves/Uniform Spline Segments/HermiteCubic2D.cs rename to Splines/Uniform Spline Segments/HermiteCubic2D.cs diff --git a/Curves/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs similarity index 100% rename from Curves/Uniform Spline Segments/HermiteCubic3D.cs rename to Splines/Uniform Spline Segments/HermiteCubic3D.cs diff --git a/Curves/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs similarity index 100% rename from Curves/Uniform Spline Segments/UBSCubic2D.cs rename to Splines/Uniform Spline Segments/UBSCubic2D.cs diff --git a/Curves/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs similarity index 100% rename from Curves/Uniform Spline Segments/UBSCubic3D.cs rename to Splines/Uniform Spline Segments/UBSCubic3D.cs diff --git a/Curves/UniformCurveSampler.cs b/Splines/UniformCurveSampler.cs similarity index 100% rename from Curves/UniformCurveSampler.cs rename to Splines/UniformCurveSampler.cs From cf663e6122994377cb1886702ec9fd9e3c653dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 30 May 2022 12:13:01 +0200 Subject: [PATCH 049/301] FloatRange equality operators --- FloatRange.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/FloatRange.cs b/FloatRange.cs index 61e3966..6e1b33c 100644 --- a/FloatRange.cs +++ b/FloatRange.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using UnityEngine; namespace Freya { @@ -86,6 +87,11 @@ public static Bounds ToBounds( FloatRange rangeX, FloatRange rangeY, FloatRange } public static implicit operator FloatRange( (float a, float b) tuple ) => new FloatRange( tuple.a, tuple.b ); + public static bool operator ==( FloatRange a, FloatRange b ) => a.a == b.a && a.b == b.b; + public static bool operator !=( FloatRange a, FloatRange b ) => a.a != b.a || a.b != b.b; + public bool Equals( FloatRange other ) => a.Equals( other.a ) && b.Equals( other.b ); + public override bool Equals( object obj ) => obj is FloatRange other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( a, b ); } From feb93c9b8a0c9b27af47374dc54bd7144b593ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 30 May 2022 12:22:45 +0200 Subject: [PATCH 050/301] optimized and added interval to GetArcLength --- Curves/IParamCurve.cs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Curves/IParamCurve.cs b/Curves/IParamCurve.cs index af22104..aa88a1e 100644 --- a/Curves/IParamCurve.cs +++ b/Curves/IParamCurve.cs @@ -57,19 +57,21 @@ public static class IParamCurveExt2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Returns the approximate length of the curve + /// Returns the approximate length of the curve in the 0 to 1 interval /// The number of subdivisions to approximate the length with. Higher values are more accurate, but more expensive to calculate - public static float GetLength( this T curve, int accuracy = 8 ) where T : IParamCurve { - Vector2 start = curve.Eval( 0 ); - Vector2 end = curve.Eval( 1 ); - if( accuracy <= 2 ) - return ( start - end ).magnitude; + [MethodImpl( INLINE )] public static float GetArcLength( this T curve, int accuracy = 8 ) where T : IParamCurve => curve.GetArcLength( FloatRange.unit, accuracy ); + /// Returns the approximate length of the curve in the given interval + /// The parameter interval of the curve to get the length of + /// The number of subdivisions to approximate the length with. Higher values are more accurate, but more expensive to calculate + public static float GetArcLength( this T curve, FloatRange interval, int accuracy = 8 ) where T : IParamCurve { + accuracy = accuracy.AtLeast( 2 ); + bool unit = interval == FloatRange.unit; float totalDist = 0; - Vector2 prev = start; + Vector2 prev = curve.Eval( interval.a ); for( int i = 1; i < accuracy; i++ ) { float t = i / ( accuracy - 1f ); - Vector2 p = curve.Eval( t ); + Vector2 p = curve.Eval( unit ? t : interval.Lerp( t ) ); float dx = p.x - prev.x; float dy = p.y - prev.y; totalDist += Mathf.Sqrt( dx * dx + dy * dy ); @@ -78,7 +80,6 @@ public static float GetLength( this T curve, int accuracy = 8 ) where T : IPa return totalDist; } - } /// Shared functionality for all 3D parametric curves @@ -86,28 +87,27 @@ public static class IParamCurveExt3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Returns the approximate length of the curve - /// The number of subdivisions to approximate the length with. Higher values are more accurate, but more expensive to calculate - public static float GetLength( this T curve, int accuracy = 8 ) where T : IParamCurve { - Vector3 start = curve.Eval( 0 ); - Vector3 end = curve.Eval( 1 ); - if( accuracy <= 2 ) - return ( start - end ).magnitude; + /// + [MethodImpl( INLINE )] public static float GetArcLength( this T curve, int accuracy = 8 ) where T : IParamCurve => curve.GetArcLength( FloatRange.unit, accuracy ); + /// + public static float GetArcLength( this T curve, FloatRange interval, int accuracy = 8 ) where T : IParamCurve { + accuracy = accuracy.AtLeast( 2 ); + bool unit = interval == FloatRange.unit; float totalDist = 0; - Vector3 prev = start; + Vector3 prev = curve.Eval( interval.a ); for( int i = 1; i < accuracy; i++ ) { float t = i / ( accuracy - 1f ); - Vector3 p = curve.Eval( t ); + Vector3 p = curve.Eval( unit ? t : interval.Lerp( t ) ); float dx = p.x - prev.x; float dy = p.y - prev.y; - totalDist += Mathf.Sqrt( dx * dx + dy * dy ); + float dz = p.z - prev.z; + totalDist += Mathf.Sqrt( dx * dx + dy * dy + dz * dz ); prev = p; } return totalDist; } - } /// Shared functionality for 2D parametric curves of degree 1 or higher From c3119d697fda0ad86e7cdea302bdb487efcc5f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 30 May 2022 12:28:42 +0200 Subject: [PATCH 051/301] added a linear Polynomial.Compose --- Curves/Polynomial.cs | 19 +++++++++++++++++++ Curves/Polynomial2D.cs | 3 +++ Curves/Polynomial3D.cs | 3 +++ 3 files changed, 25 insertions(+) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index af60f61..c70d11f 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -75,6 +75,25 @@ public Polynomial Differentiate( int n = 1 ) { }; } + /// Given an inner function g(x), returns f(g(x)) + /// The constant coefficient of the inner function g(x) + /// The linear coefficient of the inner function g(x) + public Polynomial Compose( float g0, float g1 ) { + float ss = g1 * g1; + float sss = ss * g1; + float oo = g0 * g0; + float ooo = oo * g0; + float _3c3 = 3 * c3; + float c2g0 = c2 * g0; + + return new Polynomial( + c3 * ooo + c2 * oo + c2g0 + c0, + g1 * ( _3c3 * oo + 2 * c2g0 + c1 ), + ss * ( _3c3 * g0 + c2 ), + sss * c3 + ); + } + /// Calculates the roots (values where this polynomial = 0) public ResultsMax3 Roots => GetCubicRoots( c0, c1, c2, c3 ); diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index 0850f1a..c775d8c 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -43,6 +43,9 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2, Vector2 c3 ) { /// public Polynomial2D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n )); + /// + public Polynomial2D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 )); + /// Returns the tight axis-aligned bounds of the curve in the unit interval public Rect GetBounds01() => FloatRange.ToRect( x.OutputRange01, y.OutputRange01 ); diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs index a8bd7e9..35a684b 100644 --- a/Curves/Polynomial3D.cs +++ b/Curves/Polynomial3D.cs @@ -39,6 +39,9 @@ public Vector3 C3 { /// public Polynomial3D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n )); + /// + public Polynomial3D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 )); + /// public Bounds GetBounds01() => FloatRange.ToBounds( x.OutputRange01, y.OutputRange01, z.OutputRange01 ); From 795f04570d583556746c8631f5296ef58947cdaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 30 May 2022 14:32:45 +0200 Subject: [PATCH 052/301] Polynomial2D rotate doc update --- Curves/Polynomial2D.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index c775d8c..c84a0d6 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -290,14 +290,16 @@ public bool Raycast( Ray2D ray, out Vector2 hitPoint, out float t, float maxDist #endregion - public static Polynomial2D Rotate( Polynomial2D poly, float a ) { - return new Polynomial2D( - poly.C0.Rotate( a ), - poly.C1.Rotate( a ), - poly.C2.Rotate( a ), - poly.C3.Rotate( a ) + /// Returns the polynomial, rotated around the origin + /// The polynomial to rotate + /// The angle to rotate by (in radians) + public static Polynomial2D Rotate( Polynomial2D poly, float angle ) => + new( + poly.C0.Rotate( angle ), + poly.C1.Rotate( angle ), + poly.C2.Rotate( angle ), + poly.C3.Rotate( angle ) ); - } } } \ No newline at end of file From 17a179996bc16a72434ad89f75190e9ac9738cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 4 Jun 2022 20:41:04 +0200 Subject: [PATCH 053/301] doc & formatting fix --- .../Uniform Spline Segments/BezierQuad2D.cs | 2 +- .../Uniform Spline Segments/CatRomCubic2D.cs | 38 +++++++------------ 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index 81d7c20..3bae269 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -15,7 +15,7 @@ namespace Freya { /// Creates a quadratic bezier curve, from 3 control points /// The starting point of the curve /// The second control point of the curve, sometimes called the start tangent point - /// The end point of the curve, sometimes called the end tangent point + /// The end point of the curve public BezierQuad2D( Vector2 p0, Vector2 p1, Vector2 p2 ) { ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); validCoefficients = false; diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 1f2a1b2..2d1f7a3 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -60,15 +60,14 @@ public Vector2 P3 { /// Get or set a control point position by index. Valid indices: 0, 1, 2 or 3 public Vector2 this[ int i ] { - get { - switch( i ) { - case 0: return P0; - case 1: return P1; - case 2: return P2; - case 3: return P3; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; set { switch( i ) { case 0: @@ -106,22 +105,14 @@ public Vector2 this[ int i ] { #region Object Comparison & ToString - public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.p0 == b.p0 && a.p1 == b.p1 && a.p2 == b.p2 && a.p3 == b.p3; public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); - public bool Equals( CatRomCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public bool Equals( CatRomCubic2D other ) => p0.Equals( other.p0 ) && p1.Equals( other.p1 ) && p2.Equals( other.p2 ) && p3.Equals( other.p3 ); public override bool Equals( object obj ) => obj is CatRomCubic2D other && Equals( other ); - public override int GetHashCode() { - unchecked { - int hashCode = P0.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P1.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P2.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P3.GetHashCode(); - return hashCode; - } - } + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); - public override string ToString() => $"{P0}, {P1}, {P2}, {P3}"; + public override string ToString() => $"{p0}, {p1}, {p2}, {p3}"; #endregion @@ -131,14 +122,13 @@ public override int GetHashCode() { /// The first curve /// The second curve /// A value from 0 to 1 to blend between a and b - public static CatRomCubic2D Lerp( CatRomCubic2D a, CatRomCubic2D b, float t ) { - return new CatRomCubic2D( + public static CatRomCubic2D Lerp( CatRomCubic2D a, CatRomCubic2D b, float t ) => + new( Vector2.LerpUnclamped( a.p0, b.p0, t ), Vector2.LerpUnclamped( a.p1, b.p1, t ), Vector2.LerpUnclamped( a.p2, b.p2, t ), Vector2.LerpUnclamped( a.p3, b.p3, t ) ); - } #endregion From 396a60d9f4c65d0004876257a4f5ee8cea3f8569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 4 Jun 2022 23:07:15 +0200 Subject: [PATCH 054/301] added polynomial splitting --- Curves/Polynomial.cs | 19 +++++++++++++++++++ Curves/Polynomial2D.cs | 7 +++++++ Curves/Polynomial3D.cs | 8 ++++++++ 3 files changed, 34 insertions(+) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index c70d11f..9926abd 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -93,6 +93,25 @@ public Polynomial Compose( float g0, float g1 ) { sss * c3 ); } + + /// Splits the 0-1 range into two distinct polynomials at the given parameter value u, where both new curves cover the same total range with their individual 0-1 ranges + /// The parameter value to split at + public (Polynomial pre, Polynomial post) Split01( float u ) { + float d = 1f - u; + float dd = d * d; + float ddd = d * d * d; + float uu = u * u; + float uuu = u * u * u; + + Polynomial pre = new Polynomial( c0, c1 * u, c2 * uu, c3 * uuu ); + Polynomial post = new Polynomial( + Eval( u ), + d * Differentiate( 1 ).Eval( u ), + ( dd / 2 ) * Differentiate( 2 ).Eval( u ), + ( ddd / 6 ) * Differentiate( 3 ).Eval( u ) + ); + return ( pre, post ); + } /// Calculates the roots (values where this polynomial = 0) public ResultsMax3 Roots => GetCubicRoots( c0, c1, c2, c3 ); diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index c84a0d6..44f4fea 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -49,6 +49,13 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2, Vector2 c3 ) { /// Returns the tight axis-aligned bounds of the curve in the unit interval public Rect GetBounds01() => FloatRange.ToRect( x.OutputRange01, y.OutputRange01 ); + /// + public (Polynomial2D pre, Polynomial2D post) Split01( float u ) { + ( Polynomial xPre, Polynomial xPost ) = x.Split01( u ); + ( Polynomial yPre, Polynomial yPost ) = y.Split01( u ); + return ( new Polynomial2D( xPre, yPre ), new Polynomial2D( xPost, yPost ) ); + } + #region Polynomial to spline converters /// Returns the cubic bezier control points for the unit interval of this curve diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs index 35a684b..0e7f593 100644 --- a/Curves/Polynomial3D.cs +++ b/Curves/Polynomial3D.cs @@ -45,6 +45,14 @@ public Vector3 C3 { /// public Bounds GetBounds01() => FloatRange.ToBounds( x.OutputRange01, y.OutputRange01, z.OutputRange01 ); + /// + public (Polynomial3D pre, Polynomial3D post) Split01( float u ) { + ( Polynomial xPre, Polynomial xPost ) = x.Split01( u ); + ( Polynomial yPre, Polynomial yPost ) = y.Split01( u ); + ( Polynomial zPre, Polynomial zPost ) = z.Split01( u ); + return ( new Polynomial3D( xPre, yPre, zPre ), new Polynomial3D( xPost, yPost, zPost ) ); + } + #region Polynomial to spline converters /// From 178a134007f76b2fe1add8455cb0ad5bf4f5084c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 4 Jun 2022 23:07:38 +0200 Subject: [PATCH 055/301] added IParamCubicSplineSegment1D --- Curves/IParamCurve.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Curves/IParamCurve.cs b/Curves/IParamCurve.cs index aa88a1e..68aa63e 100644 --- a/Curves/IParamCurve.cs +++ b/Curves/IParamCurve.cs @@ -6,13 +6,18 @@ namespace Freya { - public interface IParamCubicSplineSegment2D { + public interface IParamCubicSplineSegment1D { /// The curve generated by the control points + Polynomial Curve { get; } + } + + public interface IParamCubicSplineSegment2D { + /// Polynomial2D Curve { get; } } public interface IParamCubicSplineSegment3D { - /// + /// Polynomial3D Curve { get; } } From d4e0a37c601c4ce8de51ea842fe6e1a595e15dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 4 Jun 2022 23:07:43 +0200 Subject: [PATCH 056/301] doc cleanup --- Splines/CharMatrix.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Splines/CharMatrix.cs b/Splines/CharMatrix.cs index b3ccf0f..0e43c32 100644 --- a/Splines/CharMatrix.cs +++ b/Splines/CharMatrix.cs @@ -129,11 +129,7 @@ public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) = GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ) ); - /// Returns the curve this characteristic matrix represents, given 4 points - /// The first point - /// The second point - /// The third point - /// The fourth point + /// public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => new( GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), From 16a645263f0a4df6757a8f4d4f2eecb8a28d93ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 4 Jun 2022 23:31:26 +0200 Subject: [PATCH 057/301] codegen consistency preparation --- .../Uniform Spline Segments/BezierCubic2D.cs | 65 +++++++--------- .../Uniform Spline Segments/BezierCubic3D.cs | 75 +++++++++---------- .../Uniform Spline Segments/BezierQuad2D.cs | 34 ++++----- .../Uniform Spline Segments/BezierQuad3D.cs | 39 +++++----- .../Uniform Spline Segments/CatRomCubic2D.cs | 27 +++---- .../Uniform Spline Segments/CatRomCubic3D.cs | 68 ++++++++--------- .../Uniform Spline Segments/HermiteCubic2D.cs | 16 ++-- .../Uniform Spline Segments/HermiteCubic3D.cs | 27 +++---- Splines/Uniform Spline Segments/UBSCubic2D.cs | 31 ++++---- Splines/Uniform Spline Segments/UBSCubic3D.cs | 39 +++++----- 10 files changed, 200 insertions(+), 221 deletions(-) diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index 63d7edc..d8838ee 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -1,5 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -// a lot of stuff here made possible by this excellent writeup on bezier curves: https://pomax.github.io/bezierinfo/ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -7,12 +6,12 @@ namespace Freya { - /// An optimized 2D cubic bezier curve, with 4 control points + /// An optimized uniform 2D Cubic bézier segment, with 4 control points [Serializable] public struct BezierCubic2D : IParamCubicSplineSegment2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Creates a cubic bezier curve, from 4 control points + /// Creates a uniform 2D Cubic bézier segment, from 4 control points /// The starting point of the curve /// The second control point of the curve, sometimes called the start tangent point /// The third control point of the curve, sometimes called the end tangent point @@ -33,7 +32,7 @@ public Polynomial2D Curve { #region Control Points - [SerializeField] Vector2 p0, p1, p2, p3; // the points of the curve + [SerializeField] Vector2 p0, p1, p2, p3; /// The starting point of the curve public Vector2 P0 { @@ -59,17 +58,16 @@ public Vector2 P3 { [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); } - /// Get or set a control point position by index. Valid indices: 0, 1, 2 or 3 + /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector2 this[ int i ] { - get { - switch( i ) { - case 0: return P0; - case 1: return P1; - case 2: return P2; - case 3: return P3; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; set { switch( i ) { case 0: @@ -91,11 +89,11 @@ public Vector2 this[ int i ] { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update @@ -105,24 +103,16 @@ public Vector2 this[ int i ] { #endregion + #region Object Comparison & ToString public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic2D a, BezierCubic2D b ) => !( a == b ); public bool Equals( BezierCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); public override bool Equals( object obj ) => obj is BezierCubic2D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); - public override int GetHashCode() { - unchecked { - int hashCode = P0.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P1.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P2.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P3.GetHashCode(); - return hashCode; - } - } - - public override string ToString() => $"{P0}, {P1}, {P2}, {P3}"; + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; #endregion @@ -138,24 +128,21 @@ public static explicit operator BezierCubic3D( BezierCubic2D bezierCubic2D ) { #region Interpolation - /// Returns linear blend between two bézier curves - /// The first curve - /// The second curve + /// Returns a linear blend between two bézier curves + /// The first spline segment + /// The second spline segment /// A value from 0 to 1 to blend between a and b - public static BezierCubic2D Lerp( BezierCubic2D a, BezierCubic2D b, float t ) { - return new BezierCubic2D( + public static BezierCubic2D Lerp( BezierCubic2D a, BezierCubic2D b, float t ) => + new( Vector2.LerpUnclamped( a.p0, b.p0, t ), Vector2.LerpUnclamped( a.p1, b.p1, t ), Vector2.LerpUnclamped( a.p2, b.p2, t ), Vector2.LerpUnclamped( a.p3, b.p3, t ) ); - } - /// Returns blend between two bézier curves, - /// where the endpoints are linearly interpolated, - /// while the tangents are spherically interpolated relative to their corresponding endpoint - /// The first curve - /// The second curve + /// Returns a linear blend between two bézier curves, where the tangent directions are spherically interpolated + /// The first spline segment + /// The second spline segment /// A value from 0 to 1 to blend between a and b public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { Vector2 p0 = Vector2.LerpUnclamped( a.p0, b.p0, t ); diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index 634c36c..1f75da0 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -1,5 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -// a lot of stuff here made possible by this excellent writeup on bezier curves: https://pomax.github.io/bezierinfo/ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -7,12 +6,16 @@ namespace Freya { - /// An optimized 3D cubic bezier curve, with 4 control points + /// An optimized uniform 3D Cubic bézier segment, with 4 control points [Serializable] public struct BezierCubic3D : IParamCubicSplineSegment3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// + /// Creates a uniform 3D Cubic bézier segment, from 4 control points + /// The starting point of the curve + /// The second control point of the curve, sometimes called the start tangent point + /// The third control point of the curve, sometimes called the end tangent point + /// The end point of the curve public BezierCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); validCoefficients = false; @@ -29,43 +32,42 @@ public Polynomial3D Curve { #region Control Points - [SerializeField] Vector3 p0, p1, p2, p3; // the points of the curve + [SerializeField] Vector3 p0, p1, p2, p3; - /// + /// The starting point of the curve public Vector3 P0 { [MethodImpl( INLINE )] get => p0; [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); } - /// + /// The second control point of the curve, sometimes called the start tangent point public Vector3 P1 { [MethodImpl( INLINE )] get => p1; [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); } - /// + /// The third control point of the curve, sometimes called the end tangent point public Vector3 P2 { [MethodImpl( INLINE )] get => p2; [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); } - /// + /// The end point of the curve public Vector3 P3 { [MethodImpl( INLINE )] get => p3; [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); } - /// + /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector3 this[ int i ] { - get { - switch( i ) { - case 0: return P0; - case 1: return P1; - case 2: return P2; - case 3: return P3; - default: throw new IndexOutOfRangeException(); - } - } + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; set { switch( i ) { case 0: @@ -80,18 +82,18 @@ public Vector3 this[ int i ] { case 3: P3 = value; break; - default: throw new IndexOutOfRangeException(); + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); } } } #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update @@ -101,24 +103,16 @@ public Vector3 this[ int i ] { #endregion + #region Object Comparison & ToString public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic3D a, BezierCubic3D b ) => !( a == b ); public bool Equals( BezierCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); public override bool Equals( object obj ) => obj is BezierCubic3D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); - public override int GetHashCode() { - unchecked { - int hashCode = P0.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P1.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P2.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P3.GetHashCode(); - return hashCode; - } - } - - public override string ToString() => $"{P0}, {P1}, {P2}, {P3}"; + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; #endregion @@ -134,17 +128,22 @@ public static explicit operator BezierCubic2D( BezierCubic3D bezierCubic3D ) { #region Interpolation - /// - public static BezierCubic3D Lerp( BezierCubic3D a, BezierCubic3D b, float t ) { - return new BezierCubic3D( + /// Returns a linear blend between two bézier curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static BezierCubic3D Lerp( BezierCubic3D a, BezierCubic3D b, float t ) => + new( Vector3.LerpUnclamped( a.p0, b.p0, t ), Vector3.LerpUnclamped( a.p1, b.p1, t ), Vector3.LerpUnclamped( a.p2, b.p2, t ), Vector3.LerpUnclamped( a.p3, b.p3, t ) ); - } - /// + /// Returns a linear blend between two bézier curves, where the tangent directions are spherically interpolated + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b public static BezierCubic3D Slerp( BezierCubic3D a, BezierCubic3D b, float t ) { Vector3 p0 = Vector3.LerpUnclamped( a.p0, b.p0, t ); Vector3 p3 = Vector3.LerpUnclamped( a.p3, b.p3, t ); diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index 3bae269..b28bb5e 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -1,5 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -// a lot of stuff here made possible by this excellent writeup on bezier curves: https://pomax.github.io/bezierinfo/ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -7,14 +6,14 @@ namespace Freya { - /// An optimized 2D quadratic bezier curve, with 3 control points + /// An optimized uniform 2D Quadratic bézier segment, with 3 control points [Serializable] public struct BezierQuad2D : IParamCubicSplineSegment2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Creates a quadratic bezier curve, from 3 control points + /// Creates a uniform 2D Quadratic bézier segment, from 3 control points /// The starting point of the curve - /// The second control point of the curve, sometimes called the start tangent point + /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve public BezierQuad2D( Vector2 p0, Vector2 p1, Vector2 p2 ) { ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); @@ -32,7 +31,7 @@ public Polynomial2D Curve { #region Control Points - [SerializeField] Vector2 p0, p1, p2; // the points of the curve + [SerializeField] Vector2 p0, p1, p2; /// The starting point of the curve public Vector2 P0 { @@ -40,7 +39,7 @@ public Vector2 P0 { [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); } - /// The middle control point of the curve + /// The middle control point of the curve, sometimes called a tangent point public Vector2 P1 { [MethodImpl( INLINE )] get => p1; [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); @@ -52,16 +51,15 @@ public Vector2 P2 { [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); } - /// Get or set a control point position by index. Valid indices: 0, 1, 2 or 3 + /// Get or set a control point position by index. Valid indices from 0 to 2 public Vector2 this[ int i ] { - get { - switch( i ) { - case 0: return P0; - case 1: return P1; - case 2: return P2; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); - } - } + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) + }; set { switch( i ) { case 0: @@ -80,11 +78,11 @@ public Vector2 this[ int i ] { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index 4b33d4e..6fc7a36 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -1,5 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -// a lot of stuff here made possible by this excellent writeup on bezier curves: https://pomax.github.io/bezierinfo/ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -7,12 +6,15 @@ namespace Freya { - /// An optimized 3D quadratic bezier curve, with 3 control points + /// An optimized uniform 3D Quadratic bézier segment, with 3 control points [Serializable] public struct BezierQuad3D : IParamCubicSplineSegment3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// + /// Creates a uniform 3D Quadratic bézier segment, from 3 control points + /// The starting point of the curve + /// The middle control point of the curve, sometimes called a tangent point + /// The end point of the curve public BezierQuad3D( Vector3 p0, Vector3 p1, Vector3 p2 ) { ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); validCoefficients = false; @@ -29,36 +31,35 @@ public Polynomial3D Curve { #region Control Points - [SerializeField] Vector3 p0, p1, p2; // the points of the curve + [SerializeField] Vector3 p0, p1, p2; - /// + /// The starting point of the curve public Vector3 P0 { [MethodImpl( INLINE )] get => p0; [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); } - /// + /// The middle control point of the curve, sometimes called a tangent point public Vector3 P1 { [MethodImpl( INLINE )] get => p1; [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); } - /// + /// The end point of the curve public Vector3 P2 { [MethodImpl( INLINE )] get => p2; [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); } - /// + /// Get or set a control point position by index. Valid indices from 0 to 2 public Vector3 this[ int i ] { - get { - switch( i ) { - case 0: return P0; - case 1: return P1; - case 2: return P2; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); - } - } + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) + }; set { switch( i ) { case 0: @@ -77,11 +78,11 @@ public Vector3 this[ int i ] { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 2d1f7a3..c41fe87 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -1,4 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -6,12 +6,12 @@ namespace Freya { - /// An optimized uniform cubic catmull-rom 2D curve, with 4 control points + /// An optimized uniform 2D Cubic catmull-rom segment, with 4 control points [Serializable] public struct CatRomCubic2D : IParamCubicSplineSegment2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Creates a uniform cubic catmull-rom curve, from 4 control points + /// Creates a uniform 2D Cubic catmull-rom segment, from 4 control points /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it /// The second control point, and the start of the catmull-rom curve /// The third control point, and the end of the catmull-rom curve @@ -32,7 +32,7 @@ public Polynomial2D Curve { #region Control Points - [SerializeField] Vector2 p0, p1, p2, p3; // the points of the curve + [SerializeField] Vector2 p0, p1, p2, p3; /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P0 { @@ -58,7 +58,7 @@ public Vector2 P3 { [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); } - /// Get or set a control point position by index. Valid indices: 0, 1, 2 or 3 + /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector2 this[ int i ] { get => i switch { @@ -89,11 +89,11 @@ public Vector2 this[ int i ] { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update @@ -103,24 +103,25 @@ public Vector2 this[ int i ] { #endregion + #region Object Comparison & ToString - public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.p0 == b.p0 && a.p1 == b.p1 && a.p2 == b.p2 && a.p3 == b.p3; + public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); - public bool Equals( CatRomCubic2D other ) => p0.Equals( other.p0 ) && p1.Equals( other.p1 ) && p2.Equals( other.p2 ) && p3.Equals( other.p3 ); + public bool Equals( CatRomCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); public override bool Equals( object obj ) => obj is CatRomCubic2D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); - public override string ToString() => $"{p0}, {p1}, {p2}, {p3}"; + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; #endregion + #region Interpolation /// Returns a linear blend between two catmull-rom curves - /// The first curve - /// The second curve + /// The first spline segment + /// The second spline segment /// A value from 0 to 1 to blend between a and b public static CatRomCubic2D Lerp( CatRomCubic2D a, CatRomCubic2D b, float t ) => new( diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 2f347a8..17b594e 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -1,4 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -6,12 +6,16 @@ namespace Freya { - /// An optimized uniform cubic catmull-rom 3D curve, with 4 control points + /// An optimized uniform 3D Cubic catmull-rom segment, with 4 control points [Serializable] public struct CatRomCubic3D : IParamCubicSplineSegment3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// + /// Creates a uniform 3D Cubic catmull-rom segment, from 4 control points + /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The second control point, and the start of the catmull-rom curve + /// The third control point, and the end of the catmull-rom curve + /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public CatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); validCoefficients = false; @@ -28,43 +32,42 @@ public Polynomial3D Curve { #region Control Points - [SerializeField] Vector3 p0, p1, p2, p3; // the points of the curve + [SerializeField] Vector3 p0, p1, p2, p3; - /// + /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector3 P0 { [MethodImpl( INLINE )] get => p0; [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); } - /// + /// The second control point, and the start of the catmull-rom curve public Vector3 P1 { [MethodImpl( INLINE )] get => p1; [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); } - /// + /// The third control point, and the end of the catmull-rom curve public Vector3 P2 { [MethodImpl( INLINE )] get => p2; [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); } - /// + /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector3 P3 { [MethodImpl( INLINE )] get => p3; [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); } - /// + /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector3 this[ int i ] { - get { - switch( i ) { - case 0: return P0; - case 1: return P1; - case 2: return P2; - case 3: return P3; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; set { switch( i ) { case 0: @@ -86,11 +89,11 @@ public Vector3 this[ int i ] { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update @@ -100,38 +103,33 @@ public Vector3 this[ int i ] { #endregion + #region Object Comparison & ToString public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic3D a, CatRomCubic3D b ) => !( a == b ); public bool Equals( CatRomCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); public override bool Equals( object obj ) => obj is CatRomCubic3D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); - public override int GetHashCode() { - unchecked { - int hashCode = P0.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P1.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P2.GetHashCode(); - hashCode = ( hashCode * 397 ) ^ P3.GetHashCode(); - return hashCode; - } - } - - public override string ToString() => $"{P0}, {P1}, {P2}, {P3}"; + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; #endregion + #region Interpolation - /// - public static CatRomCubic3D Lerp( CatRomCubic3D a, CatRomCubic3D b, float t ) { - return new CatRomCubic3D( + /// Returns a linear blend between two catmull-rom curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static CatRomCubic3D Lerp( CatRomCubic3D a, CatRomCubic3D b, float t ) => + new( Vector3.LerpUnclamped( a.p0, b.p0, t ), Vector3.LerpUnclamped( a.p1, b.p1, t ), Vector3.LerpUnclamped( a.p2, b.p2, t ), Vector3.LerpUnclamped( a.p3, b.p3, t ) ); - } #endregion diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index 859bae5..f9bfa93 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -1,18 +1,17 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; using UnityEngine; -using UnityEngine.Serialization; namespace Freya { - /// An optimized 2D cubic Hermite curve segment + /// An optimized uniform 2D Cubic hermite segment, with 4 control points [Serializable] public struct HermiteCubic2D : IParamCubicSplineSegment2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Creates a cubic Hermite curve, from two control points and two tangents + /// Creates a uniform 2D Cubic hermite segment, from 4 control points /// The starting point of the curve /// The rate of change (velocity) at the start of the curve /// The end point of the curve @@ -33,10 +32,7 @@ public Polynomial2D Curve { #region Control Points - [SerializeField] Vector2 p0; - [FormerlySerializedAs( "m0" )] [SerializeField] Vector2 v0; - [SerializeField] Vector2 p1; - [FormerlySerializedAs( "m1" )] [SerializeField] Vector2 v1; + [SerializeField] Vector2 p0, v0, p1, v1; /// The starting point of the curve public Vector2 P0 { @@ -64,11 +60,11 @@ public Vector2 V1 { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index dda4595..46bc835 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -1,4 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -6,12 +6,16 @@ namespace Freya { - /// An optimized 3D cubic Hermite curve segment + /// An optimized uniform 3D Cubic hermite segment, with 4 control points [Serializable] public struct HermiteCubic3D : IParamCubicSplineSegment3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// + /// Creates a uniform 3D Cubic hermite segment, from 4 control points + /// The starting point of the curve + /// The rate of change (velocity) at the start of the curve + /// The end point of the curve + /// The rate of change (velocity) at the end of the curve public HermiteCubic3D( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) { ( this.p0, this.v0, this.p1, this.v1 ) = ( p0, v0, p1, v1 ); validCoefficients = false; @@ -28,30 +32,27 @@ public Polynomial3D Curve { #region Control Points - [SerializeField] Vector3 p0; - [SerializeField] Vector3 v0; - [SerializeField] Vector3 p1; - [SerializeField] Vector3 v1; + [SerializeField] Vector3 p0, v0, p1, v1; - /// + /// The starting point of the curve public Vector3 P0 { [MethodImpl( INLINE )] get => p0; [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); } - /// + /// The rate of change (velocity) at the start of the curve public Vector3 V0 { [MethodImpl( INLINE )] get => v0; [MethodImpl( INLINE )] set => _ = ( v0 = value, validCoefficients = false ); } - /// + /// The end point of the curve public Vector3 P1 { [MethodImpl( INLINE )] get => p1; [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); } - /// + /// The rate of change (velocity) at the end of the curve public Vector3 V1 { [MethodImpl( INLINE )] get => v1; [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); @@ -59,11 +60,11 @@ public Vector3 V1 { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index 15206ae..fd5752b 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -1,4 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -6,12 +6,12 @@ namespace Freya { - /// An optimized 2D uniform B-spline segment + /// An optimized uniform 2D Cubic b-spline segment, with 4 control points [Serializable] public struct UBSCubic2D : IParamCubicSplineSegment2D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Creates a uniform cubic B-spline segment, given 4 control points + /// Creates a uniform 2D Cubic b-spline segment, from 4 control points /// The first point of the B-spline hull /// The second point of the B-spline hull /// The third point of the B-spline hull @@ -32,7 +32,7 @@ public Polynomial2D Curve { #region Control Points - [SerializeField] Vector2 p0, p1, p2, p3; // the points of the B-spline hull + [SerializeField] Vector2 p0, p1, p2, p3; /// The first point of the B-spline hull public Vector2 P0 { @@ -58,17 +58,16 @@ public Vector2 P3 { [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); } - /// Get or set a control point position by index. Valid indices: 0, 1, 2 or 3 + /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector2 this[ int i ] { - get { - switch( i ) { - case 0: return P0; - case 1: return P1; - case 2: return P2; - case 3: return P3; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; set { switch( i ) { case 0: @@ -90,11 +89,11 @@ public Vector2 this[ int i ] { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index 58345a0..be1d203 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -1,4 +1,4 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; using System.Runtime.CompilerServices; @@ -6,12 +6,12 @@ namespace Freya { - /// An optimized 3D uniform B-spline segment + /// An optimized uniform 3D Cubic b-spline segment, with 4 control points [Serializable] public struct UBSCubic3D : IParamCubicSplineSegment3D { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - /// Creates a uniform cubic B-spline segment, given 4 control points + /// Creates a uniform 3D Cubic b-spline segment, from 4 control points /// The first point of the B-spline hull /// The second point of the B-spline hull /// The third point of the B-spline hull @@ -32,43 +32,42 @@ public Polynomial3D Curve { #region Control Points - [SerializeField] Vector3 p0, p1, p2, p3; // the points of the B-spline hull + [SerializeField] Vector3 p0, p1, p2, p3; - /// + /// The first point of the B-spline hull public Vector3 P0 { [MethodImpl( INLINE )] get => p0; [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); } - /// + /// The second point of the B-spline hull public Vector3 P1 { [MethodImpl( INLINE )] get => p1; [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); } - /// + /// The third point of the B-spline hull public Vector3 P2 { [MethodImpl( INLINE )] get => p2; [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); } - /// + /// The fourth point of the B-spline hull public Vector3 P3 { [MethodImpl( INLINE )] get => p3; [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); } - /// + /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector3 this[ int i ] { - get { - switch( i ) { - case 0: return P0; - case 1: return P1; - case 2: return P2; - case 3: return P3; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; set { switch( i ) { case 0: @@ -90,11 +89,11 @@ public Vector3 this[ int i ] { #endregion + #region Coefficients - [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + [NonSerialized] bool validCoefficients; - // Coefficient Calculation [MethodImpl( INLINE )] void ReadyCoefficients() { if( validCoefficients ) return; // no need to update From 37df088e5f7c3c1299f74746faa870b9dd57c292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 00:03:05 +0200 Subject: [PATCH 058/301] started work on codegen for simple spline segments --- Codegen/Editor/CodeGenerator.cs | 66 ++++++++ Codegen/Editor/MathfsCodegen.cs | 287 ++++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 Codegen/Editor/CodeGenerator.cs create mode 100644 Codegen/Editor/MathfsCodegen.cs diff --git a/Codegen/Editor/CodeGenerator.cs b/Codegen/Editor/CodeGenerator.cs new file mode 100644 index 0000000..de4a7bb --- /dev/null +++ b/Codegen/Editor/CodeGenerator.cs @@ -0,0 +1,66 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Collections.Generic; + +namespace Freya { + + public class CodeGenerator { + + int scope = 0; + public List content = new List(); + + public void Append( string s ) { + content.Add( $"{new string( '\t', scope )}{s}" ); + } + + public void Comment( string s ) => Append( $"// {s}" ); + public void Using( string s ) => Append( $"using {s};" ); + public void Summary( string s ) => Append( $"/// {s}" ); + public void Param( string param, string desc ) => Append( $"/// {desc}" ); + + public void LineBreak() => content.Add( "" ); + + public CodeScope BracketScope( string s ) => new CodeScope( this, s, true ); + public CodeScope Scope( string s ) => new CodeScope( this, s, false ); + public RegionScope ScopeRegion( string s ) => new RegionScope( this, s ); + + public readonly struct CodeScope : IDisposable { + + readonly CodeGenerator gen; + readonly bool includeBrackets; + + public CodeScope( CodeGenerator gen, string s, bool includeBrackets = true ) { + this.gen = gen; + this.includeBrackets = includeBrackets; + gen.Append( includeBrackets ? $"{s} {{" : s ); + gen.scope++; + } + + public void Dispose() { + gen.scope--; + if( includeBrackets ) + gen.Append( "}" ); + } + } + + public readonly struct RegionScope : IDisposable { + + readonly CodeGenerator gen; + + public RegionScope( CodeGenerator gen, string s ) { + this.gen = gen; + gen.LineBreak(); + gen.Append( $"#region {s}" ); + gen.LineBreak(); + } + + public void Dispose() { + gen.LineBreak(); + gen.Append( "#endregion" ); + gen.LineBreak(); + } + } + } + +} \ No newline at end of file diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs new file mode 100644 index 0000000..c469470 --- /dev/null +++ b/Codegen/Editor/MathfsCodegen.cs @@ -0,0 +1,287 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.IO; +using System.Linq; +using UnityEditor; + +namespace Freya { + + public static class MathfsCodegen { + + class SplineType { + public int degree; + public string className; + public string prettyName; + public string prettyNameLower; + public string[] paramNames; + public string[] paramDescs; + public string matrixName; + + public SplineType( int degree, string className, string prettyName, string matrixName, string[] paramNames, string[] paramDescs, string[] paramDescsQuad = null ) { + this.degree = degree; + this.className = className; + this.prettyName = prettyName; + this.prettyNameLower = prettyName.ToLowerInvariant(); + this.paramDescs = paramDescs; + this.matrixName = matrixName; + this.paramNames = paramNames; + } + + public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { + gen.Param( paramNames[i], paramDescs[i] ); + } + } + + #region Type Definitions + + static SplineType typeBezier = new SplineType( 3, "Bezier", "Bézier", "cubicBezier", + new[] { "p0", "p1", "p2", "p3" }, + new[] { + "The starting point of the curve", + "The second control point of the curve, sometimes called the start tangent point", + "The third control point of the curve, sometimes called the end tangent point", + "The end point of the curve" + } + ); + + static SplineType typeBezierQuad = new SplineType( 2, "Bezier", "Bézier", "quadraticBezier", + new[] { "p0", "p1", "p2" }, + new[] { + "The starting point of the curve", + "The middle control point of the curve, sometimes called a tangent point", + "The end point of the curve" + } + ); + + static SplineType typeHermite = new SplineType( 3, "Hermite", "Hermite", "cubicHermite", + new[] { "p0", "v0", "p1", "v1" }, + new[] { + "The starting point of the curve", + "The rate of change (velocity) at the start of the curve", + "The end point of the curve", + "The rate of change (velocity) at the end of the curve" + } + ); + + static SplineType typeBspline = new SplineType( 3, "UBS", "B-Spline", "cubicUniformBspline", + new[] { "p0", "p1", "p2", "p3" }, + new[] { + "The first point of the B-spline hull", + "The second point of the B-spline hull", + "The third point of the B-spline hull", + "The fourth point of the B-spline hull" + } + ); + + static SplineType typeCatRom = new SplineType( 3, "CatRom", "Catmull-Rom", "cubicCatmullRom", + new[] { "p0", "p1", "p2", "p3" }, + new[] { + "The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it", + "The second control point, and the start of the catmull-rom curve", + "The third control point, and the end of the catmull-rom curve", + "The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it" + } + ); + + #endregion + + [MenuItem( "Assets/Run Mathfs Codegen" )] + public static void Regenerate() { + for( int dim = 1; dim < 4; dim++ ) { // 1D, 2D, 3D + GenerateType( typeBezier, dim ); + GenerateType( typeBezierQuad, dim ); + GenerateType( typeHermite, dim ); + GenerateType( typeBspline, dim ); + GenerateType( typeCatRom, dim ); + } + } + + public static string GetLerpName( int dim ) { + return dim switch { + 1 => "Mathfs.Lerp", + 2 => "Vector2.LerpUnclamped", + 3 => "Vector3.LerpUnclamped", + 4 => "Vector4.LerpUnclamped", + _ => throw new IndexOutOfRangeException() + }; + } + + static void GenerateType( SplineType type, int dim ) { + int degree = type.degree; + string dataType = dim == 1 ? "float" : $"Vector{dim}"; + string polynomType = dim == 1 ? "Polynomial" : $"Polynomial{dim}D"; + int ptCount = degree + 1; + string degFullLower = GetDegreeName( degree, false ); + string degShortCapital = GetDegreeName( degree, true ); + string structName = $"{type.className}{degShortCapital}{dim}D"; + string[] points = type.paramNames; + string[] pointDescs = type.paramDescs; + string lerpName = GetLerpName( dim ); + string curveFunc = dim == 1 ? "GetEvalPolynomial" : "GetCurve"; + + CodeGenerator code = new CodeGenerator(); + code.Comment( "by Freya Holmér (https://github.com/FreyaHolmer/Mathfs)" ); + code.LineBreak(); + code.Using( "System" ); + code.Using( "System.Runtime.CompilerServices" ); + code.Using( "UnityEngine" ); + code.LineBreak(); + + using( code.BracketScope( "namespace Freya" ) ) { + code.LineBreak(); + + // type definition + code.Summary( $"An optimized uniform {dim}D {degFullLower} {type.prettyNameLower} segment, with {ptCount} control points" ); + using( code.BracketScope( $"[Serializable] public struct {structName} : IParamCubicSplineSegment{dim}D" ) ) { // intentionally always Cubic right now + code.LineBreak(); + code.Append( "const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining;" ); + code.LineBreak(); + + // constructor + code.Summary( $"Creates a uniform {dim}D {degFullLower} {type.prettyNameLower} segment, from {ptCount} control points" ); + for( int i = 0; i < ptCount; i++ ) + type.AppendParamStrings( code, degree, i ); + using( code.BracketScope( $"public {structName}( {string.Join( ", ", points.Select( p => $"{dataType} {p}" ) )} )" ) ) { + code.Append( $"( {string.Join( ", ", points.Select( p => $"this.{p}" ) )} ) = ( {string.Join( ", ", points )} );" ); + code.Append( "validCoefficients = false;" ); + code.Append( "curve = default;" ); + } + + code.LineBreak(); + + // Curve + code.Append( $"{polynomType} curve;" ); + using( code.BracketScope( $"public {polynomType} Curve" ) ) { + using( code.BracketScope( $"get" ) ) { + code.Append( "ReadyCoefficients();" ); + code.Append( "return curve;" ); + } + } + + // control point properties + using( code.ScopeRegion( "Control Points" ) ) { + code.Append( $"[SerializeField] {dataType} {string.Join( ", ", points )};" ); + code.LineBreak(); + for( int i = 0; i < ptCount; i++ ) { + code.Summary( pointDescs[i] ); + using( code.BracketScope( $"public {dataType} {points[i].ToUpperInvariant()}" ) ) { + code.Append( $"[MethodImpl( INLINE )] get => {points[i]};" ); + code.Append( $"[MethodImpl( INLINE )] set => _ = ( {points[i]} = value, validCoefficients = false );" ); + } + + code.LineBreak(); + } + + code.Summary( $"Get or set a control point position by index. Valid indices from 0 to {degree}" ); + using( code.BracketScope( $"public {dataType} this[ int i ]" ) ) { + using( code.Scope( "get =>" ) ) { + using( code.Scope( "i switch {" ) ) { + for( int i = 0; i < ptCount; i++ ) + code.Append( $"{i} => {points[i].ToUpperInvariant()}," ); + code.Append( $"_ => throw new ArgumentOutOfRangeException( nameof(i), $\"Index has to be in the 0 to {degree} range, and I think {{i}} is outside that range you know\" )" ); + } + + code.Append( "};" ); + } + + using( code.BracketScope( "set" ) ) { + using( code.BracketScope( "switch( i )" ) ) { + for( int i = 0; i < ptCount; i++ ) { + using( code.Scope( $"case {i}:" ) ) { + code.Append( $"{points[i].ToUpperInvariant()} = value;" ); + code.Append( "break;" ); + } + } + + code.Append( $"default: throw new ArgumentOutOfRangeException( nameof(i), $\"Index has to be in the 0 to {degree} range, and I think {{i}} is outside that range you know\" );" ); + } + } + } + } + + // Coefficients + using( code.ScopeRegion( "Coefficients" ) ) { + code.Append( "[NonSerialized] bool validCoefficients;" ); + code.LineBreak(); + using( code.BracketScope( "[MethodImpl( INLINE )] void ReadyCoefficients()" ) ) { + using( code.Scope( "if( validCoefficients )" ) ) + code.Append( "return; // no need to update" ); + code.Append( "validCoefficients = true;" ); + code.Append( $"curve = CharMatrix.{type.matrixName}.{curveFunc}( {string.Join( ", ", points )} );" ); + } + } + + // equality checks + using( code.ScopeRegion( "Object Comparison & ToString" ) ) { + code.Append( $"public static bool operator ==( {structName} a, {structName} b ) => {string.Join( " && ", points.Select( p => $"a.{p.ToUpperInvariant()} == b.{p.ToUpperInvariant()}" ) )};" ); + code.Append( $"public static bool operator !=( {structName} a, {structName} b ) => !( a == b );" ); + code.Append( $"public bool Equals( {structName} other ) => {string.Join( " && ", points.Select( p => $"{p.ToUpperInvariant()}.Equals( other.{p.ToUpperInvariant()} )" ) )};" ); + code.Append( $"public override bool Equals( object obj ) => obj is {structName} other && Equals( other );" ); + code.Append( $"public override int GetHashCode() => HashCode.Combine( {string.Join( ", ", points )} );" ); + code.LineBreak(); + code.Append( $"public override string ToString() => $\"({string.Join( ", ", points.Select( p => $"{{{p}}}" ) )})\";" ); + } + + // Interpolation + using( code.ScopeRegion( "Interpolation" ) ) { + code.Summary( $"Returns a linear blend between two {type.prettyNameLower} curves" ); + code.Param( "a", "The first spline segment" ); + code.Param( "b", "The second spline segment" ); + code.Param( "t", "A value from 0 to 1 to blend between a and b" ); + using( code.Scope( $"public static {structName} Lerp( {structName} a, {structName} b, float t ) =>" ) ) { + using( code.Scope( "new(" ) ) { + for( int i = 0; i < ptCount; i++ ) { + code.Append( $"{lerpName}( a.{points[i]}, b.{points[i]}, t )" + ( i == ptCount - 1 ? "" : "," ) ); + } + } + + code.Append( ");" ); + } + } + + // special case slerps for cubic beziers in 2D and 3D + if( dim > 1 && degree is 2 or 3 && type == typeBezier ) { + string slerpCast = dim == 2 ? "(Vector2)" : ""; + code.LineBreak(); + code.Summary( $"Returns a linear blend between two {type.prettyNameLower} curves, where the tangent directions are spherically interpolated" ); + code.Param( "a", "The first spline segment" ); + code.Param( "b", "The second spline segment" ); + code.Param( "t", "A value from 0 to 1 to blend between a and b" ); + using( code.BracketScope( $"public static {structName} Slerp( {structName} a, {structName} b, float t )" ) ) { + code.Append( $"{dataType} p0 = {lerpName}( a.p0, b.p0, t );" ); + code.Append( $"{dataType} p3 = {lerpName}( a.p3, b.p3, t );" ); + using( code.Scope( $"return new {structName}(" ) ) { + code.Append( $"p0," ); + code.Append( $"p0 + {slerpCast}Vector3.SlerpUnclamped( a.p1 - a.p0, b.p1 - b.p0, t )," ); + code.Append( $"p3 + {slerpCast}Vector3.SlerpUnclamped( a.p2 - a.p3, b.p2 - b.p3, t )," ); + code.Append( $"p3" ); + } + + code.Append( ");" ); + } + } + + + // todo: conversion to other spline types + } + } + + string path = $"Assets/Mathfs/Splines/Uniform Spline Segments/{structName}.cs"; + File.WriteAllLines( path, code.content ); + } + + public static string GetDegreeName( int d, bool shortName ) { + return d switch { + 1 => "Linear", + 2 => shortName ? "Quad" : "Quadratic", + 3 => "Cubic", + 4 => "Quartic", + 5 => "Quintic", + _ => throw new IndexOutOfRangeException() + }; + } + + } + +} \ No newline at end of file From 97d75768bc138636e9529b93a7fe8231a5494ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 01:02:57 +0200 Subject: [PATCH 059/301] formatting changes from codegen --- Curves/Polynomial.cs | 6 +++--- Splines/Uniform Spline Segments/BezierCubic2D.cs | 9 +++++---- Splines/Uniform Spline Segments/BezierCubic3D.cs | 9 +++++---- Splines/Uniform Spline Segments/BezierQuad2D.cs | 1 + Splines/Uniform Spline Segments/BezierQuad3D.cs | 1 + Splines/Uniform Spline Segments/CatRomCubic2D.cs | 1 + Splines/Uniform Spline Segments/CatRomCubic3D.cs | 1 + Splines/Uniform Spline Segments/HermiteCubic2D.cs | 1 + Splines/Uniform Spline Segments/HermiteCubic3D.cs | 1 + Splines/Uniform Spline Segments/UBSCubic2D.cs | 1 + Splines/Uniform Spline Segments/UBSCubic3D.cs | 1 + 11 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index 9926abd..5854268 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -93,7 +93,7 @@ public Polynomial Compose( float g0, float g1 ) { sss * c3 ); } - + /// Splits the 0-1 range into two distinct polynomials at the given parameter value u, where both new curves cover the same total range with their individual 0-1 ranges /// The parameter value to split at public (Polynomial pre, Polynomial post) Split01( float u ) { @@ -107,8 +107,8 @@ public Polynomial Compose( float g0, float g1 ) { Polynomial post = new Polynomial( Eval( u ), d * Differentiate( 1 ).Eval( u ), - ( dd / 2 ) * Differentiate( 2 ).Eval( u ), - ( ddd / 6 ) * Differentiate( 3 ).Eval( u ) + dd / 2 * Differentiate( 2 ).Eval( u ), + ddd / 6 * Differentiate( 3 ).Eval( u ) ); return ( pre, post ); } diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index d8838ee..00118e0 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; @@ -118,10 +119,10 @@ public Vector2 this[ int i ] { #region Type Casting - /// Returns this bezier curve in 3D, where z = 0 - /// The 2D curve to cast - public static explicit operator BezierCubic3D( BezierCubic2D bezierCubic2D ) { - return new BezierCubic3D( bezierCubic2D.P0, bezierCubic2D.P1, bezierCubic2D.P2, bezierCubic2D.P3 ); + /// Returns this spline segment in 3D, where z = 0 + /// The 2D curve to cast to 3D + public static explicit operator BezierCubic3D( BezierCubic2D curve2D ) { + return new BezierCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); } #endregion diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index 1f75da0..1be3d84 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; @@ -118,10 +119,10 @@ public Vector3 this[ int i ] { #region Type Casting - /// Returns this bezier curve flattened to the Z plane, effectively setting z to 0 - /// The 3D curve to cast and flatten on the Z plane - public static explicit operator BezierCubic2D( BezierCubic3D bezierCubic3D ) { - return new BezierCubic2D( bezierCubic3D.P0, bezierCubic3D.P1, bezierCubic3D.P2, bezierCubic3D.P3 ); + /// Returns this curve flattened to 2D. Effectively setting z = 0 + /// The 3D curve to flatten to the Z plane + public static explicit operator BezierCubic2D( BezierCubic3D curve3D ) { + return new BezierCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); } #endregion diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index b28bb5e..38a22a9 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index 6fc7a36..8a34b47 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index c41fe87..00c71db 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 17b594e..140ba91 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index f9bfa93..b1bf595 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index 46bc835..0aab8dd 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index fd5752b..4d025be 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index be1d203..583c6b7 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -1,4 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using System.Runtime.CompilerServices; From ecc892ccbe62a7f356208a01e1751e9e02926ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 01:03:35 +0200 Subject: [PATCH 060/301] codegen top comment and 2D/3D projections --- Codegen/Editor/MathfsCodegen.cs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index c469470..d024c01 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -122,6 +122,7 @@ static void GenerateType( SplineType type, int dim ) { CodeGenerator code = new CodeGenerator(); code.Comment( "by Freya Holmér (https://github.com/FreyaHolmer/Mathfs)" ); + code.Comment( $"Do not manually edit - this file is generated by {nameof(MathfsCodegen)}.cs" ); code.LineBreak(); code.Using( "System" ); code.Using( "System.Runtime.CompilerServices" ); @@ -223,6 +224,32 @@ static void GenerateType( SplineType type, int dim ) { code.Append( $"public override string ToString() => $\"({string.Join( ", ", points.Select( p => $"{{{p}}}" ) )})\";" ); } + // typecasting + if( dim is 2 or 3 && degree is 3 ) + using( code.ScopeRegion( "Type Casting" ) ) { + if( dim == 2 ) { + // Typecast to 3D where z = 0 + string structName3D = $"{type.className}{degShortCapital}3D"; + code.Summary( "Returns this spline segment in 3D, where z = 0" ); + code.Param( "curve2D", "The 2D curve to cast to 3D" ); + using( code.BracketScope( $"public static explicit operator {structName3D}( {structName} curve2D )" ) ) { + code.Append( $"return new {structName3D}( {string.Join( ", ", points.Select( p => $"curve2D.{p}" ) )} );" ); + } + } + + if( dim == 3 ) { + // typecast to 2D where z is omitted + string structName2D = $"{type.className}{degShortCapital}2D"; + code.Summary( "Returns this curve flattened to 2D. Effectively setting z = 0" ); + code.Param( "curve3D", "The 3D curve to flatten to the Z plane" ); + using( code.BracketScope( $"public static explicit operator {structName2D}( {structName} curve3D )" ) ) { + code.Append( $"return new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p}" ) )} );" ); + } + + // todo: conversion to other cubic splines + } + } + // Interpolation using( code.ScopeRegion( "Interpolation" ) ) { code.Summary( $"Returns a linear blend between two {type.prettyNameLower} curves" ); @@ -261,9 +288,6 @@ static void GenerateType( SplineType type, int dim ) { code.Append( ");" ); } } - - - // todo: conversion to other spline types } } From 3ca36aa5f9e462ed2b4e9538db1589652b681057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 01:22:37 +0200 Subject: [PATCH 061/301] codegen formatting preparation --- .../Uniform Spline Segments/BezierCubic2D.cs | 35 +------------------ .../Uniform Spline Segments/BezierCubic3D.cs | 30 +--------------- .../Uniform Spline Segments/BezierQuad2D.cs | 10 +----- .../Uniform Spline Segments/BezierQuad3D.cs | 10 +----- .../Uniform Spline Segments/CatRomCubic2D.cs | 21 +---------- .../Uniform Spline Segments/CatRomCubic3D.cs | 21 +---------- .../Uniform Spline Segments/HermiteCubic2D.cs | 7 +--- .../Uniform Spline Segments/HermiteCubic3D.cs | 8 +---- Splines/Uniform Spline Segments/UBSCubic2D.cs | 10 +----- Splines/Uniform Spline Segments/UBSCubic3D.cs | 10 +----- 10 files changed, 10 insertions(+), 152 deletions(-) diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index 00118e0..b622776 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -30,7 +30,6 @@ public Polynomial2D Curve { return curve; } } - #region Control Points [SerializeField] Vector2 p0, p1, p2, p3; @@ -89,10 +88,6 @@ public Vector2 this[ int i ] { } #endregion - - - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -101,12 +96,6 @@ public Vector2 this[ int i ] { validCoefficients = true; curve = CharMatrix.cubicBezier.GetCurve( p0, p1, p2, p3 ); } - - #endregion - - - #region Object Comparison & ToString - public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic2D a, BezierCubic2D b ) => !( a == b ); public bool Equals( BezierCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); @@ -114,21 +103,11 @@ public Vector2 this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - - #endregion - - #region Type Casting - /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D public static explicit operator BezierCubic3D( BezierCubic2D curve2D ) { return new BezierCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); } - - #endregion - - #region Interpolation - /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment @@ -156,10 +135,6 @@ public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { ); } - #endregion - - #region Splitting - /// Splits this curve at the given t-value, into two curves of the exact same shape /// The t-value along the curve to sample public (BezierCubic2D pre, BezierCubic2D post) Split( float t ) { @@ -183,10 +158,6 @@ public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { return ( new BezierCubic2D( P0, a, d, p ), new BezierCubic2D( p, e, c, P3 ) ); } - #endregion - - #region Conversion - public UBSCubic2D ToUniformCubicBSpline() { // todo: channel split for performance return new UBSCubic2D( @@ -209,9 +180,5 @@ public HermiteCubic2D ToHermite() { // todo: channel split for performance return new HermiteCubic2D( p0, ( p1 - p0 ) * 3, p3, ( p3 - p2 ) * 3 ); } - - #endregion - } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index 1be3d84..3949b55 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -30,7 +30,6 @@ public Polynomial3D Curve { return curve; } } - #region Control Points [SerializeField] Vector3 p0, p1, p2, p3; @@ -89,10 +88,6 @@ public Vector3 this[ int i ] { } #endregion - - - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -101,12 +96,6 @@ public Vector3 this[ int i ] { validCoefficients = true; curve = CharMatrix.cubicBezier.GetCurve( p0, p1, p2, p3 ); } - - #endregion - - - #region Object Comparison & ToString - public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic3D a, BezierCubic3D b ) => !( a == b ); public bool Equals( BezierCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); @@ -114,21 +103,11 @@ public Vector3 this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - - #endregion - - #region Type Casting - /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane public static explicit operator BezierCubic2D( BezierCubic3D curve3D ) { return new BezierCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); } - - #endregion - - #region Interpolation - /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment @@ -156,10 +135,6 @@ public static BezierCubic3D Slerp( BezierCubic3D a, BezierCubic3D b, float t ) { ); } - #endregion - - #region Splitting - /// public (BezierCubic3D pre, BezierCubic3D post) Split( float t ) { Vector3 a = new Vector3( @@ -188,8 +163,5 @@ public static BezierCubic3D Slerp( BezierCubic3D a, BezierCubic3D b, float t ) { return ( new BezierCubic3D( P0, a, d, p ), new BezierCubic3D( p, e, c, P3 ) ); } - #endregion - } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index 38a22a9..284ab6f 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -29,7 +29,6 @@ public Polynomial2D Curve { return curve; } } - #region Control Points [SerializeField] Vector2 p0, p1, p2; @@ -78,10 +77,6 @@ public Vector2 this[ int i ] { } #endregion - - - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -91,8 +86,6 @@ public Vector2 this[ int i ] { curve = CharMatrix.quadraticBezier.GetCurve( p0, p1, p2 ); } - #endregion - /// public BezierQuad2D Split( float t ) { Vector2 mid = Vector2.LerpUnclamped( p0, p1, t ); @@ -102,5 +95,4 @@ public BezierQuad2D Split( float t ) { } } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index 8a34b47..847a109 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -29,7 +29,6 @@ public Polynomial3D Curve { return curve; } } - #region Control Points [SerializeField] Vector3 p0, p1, p2; @@ -78,10 +77,6 @@ public Vector3 this[ int i ] { } #endregion - - - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -91,8 +86,6 @@ public Vector3 this[ int i ] { curve = CharMatrix.quadraticBezier.GetCurve( p0, p1, p2 ); } - #endregion - /// public BezierQuad3D Split( float t ) { Vector3 mid = Vector3.LerpUnclamped( p0, p1, t ); @@ -102,5 +95,4 @@ public BezierQuad3D Split( float t ) { } } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 00c71db..0596fa9 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -30,7 +30,6 @@ public Polynomial2D Curve { return curve; } } - #region Control Points [SerializeField] Vector2 p0, p1, p2, p3; @@ -89,10 +88,6 @@ public Vector2 this[ int i ] { } #endregion - - - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -101,12 +96,6 @@ public Vector2 this[ int i ] { validCoefficients = true; curve = CharMatrix.cubicCatmullRom.GetCurve( p0, p1, p2, p3 ); } - - #endregion - - - #region Object Comparison & ToString - public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); public bool Equals( CatRomCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); @@ -115,11 +104,6 @@ public Vector2 this[ int i ] { public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - #endregion - - - #region Interpolation - /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment @@ -132,8 +116,6 @@ public static CatRomCubic2D Lerp( CatRomCubic2D a, CatRomCubic2D b, float t ) => Vector2.LerpUnclamped( a.p3, b.p3, t ) ); - #endregion - /// Returns the bezier representation of the same curve public BezierCubic2D ToBezier() => new BezierCubic2D( @@ -162,5 +144,4 @@ public UBSCubic2D ToBSpline() => ); } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 140ba91..09e99dd 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -30,7 +30,6 @@ public Polynomial3D Curve { return curve; } } - #region Control Points [SerializeField] Vector3 p0, p1, p2, p3; @@ -89,10 +88,6 @@ public Vector3 this[ int i ] { } #endregion - - - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -101,12 +96,6 @@ public Vector3 this[ int i ] { validCoefficients = true; curve = CharMatrix.cubicCatmullRom.GetCurve( p0, p1, p2, p3 ); } - - #endregion - - - #region Object Comparison & ToString - public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic3D a, CatRomCubic3D b ) => !( a == b ); public bool Equals( CatRomCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); @@ -115,11 +104,6 @@ public Vector3 this[ int i ] { public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - #endregion - - - #region Interpolation - /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment @@ -132,8 +116,6 @@ public static CatRomCubic3D Lerp( CatRomCubic3D a, CatRomCubic3D b, float t ) => Vector3.LerpUnclamped( a.p3, b.p3, t ) ); - #endregion - /// public BezierCubic3D ToBezier() => new BezierCubic3D( @@ -162,5 +144,4 @@ public UBSCubic3D ToBSpline() => ); } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index b1bf595..e8c54dc 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -62,8 +62,6 @@ public Vector2 V1 { #endregion - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -73,10 +71,7 @@ public Vector2 V1 { curve = CharMatrix.cubicHermite.GetCurve( p0, v0, p1, v1 ); } - #endregion - public BezierCubic2D ToBezier() => new BezierCubic2D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index 0aab8dd..89c7e88 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -30,7 +30,6 @@ public Polynomial3D Curve { return curve; } } - #region Control Points [SerializeField] Vector3 p0, v0, p1, v1; @@ -62,8 +61,6 @@ public Vector3 V1 { #endregion - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -73,10 +70,7 @@ public Vector3 V1 { curve = CharMatrix.cubicHermite.GetCurve( p0, v0, p1, v1 ); } - #endregion - public BezierCubic3D ToBezier() => new BezierCubic3D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index 4d025be..1814fad 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -30,7 +30,6 @@ public Polynomial2D Curve { return curve; } } - #region Control Points [SerializeField] Vector2 p0, p1, p2, p3; @@ -89,10 +88,6 @@ public Vector2 this[ int i ] { } #endregion - - - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -102,8 +97,6 @@ public Vector2 this[ int i ] { curve = CharMatrix.cubicUniformBspline.GetCurve( p0, p1, p2, p3 ); } - #endregion - /// Returns the exact cubic bézier representation of this segment public BezierCubic2D ToBezier() { const float _13 = 1f / 3f; @@ -125,5 +118,4 @@ public BezierCubic2D ToBezier() { } } - -} \ No newline at end of file +} diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index 583c6b7..c04a561 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -30,7 +30,6 @@ public Polynomial3D Curve { return curve; } } - #region Control Points [SerializeField] Vector3 p0, p1, p2, p3; @@ -89,10 +88,6 @@ public Vector3 this[ int i ] { } #endregion - - - #region Coefficients - [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -102,8 +97,6 @@ public Vector3 this[ int i ] { curve = CharMatrix.cubicUniformBspline.GetCurve( p0, p1, p2, p3 ); } - #endregion - /// public BezierCubic3D ToBezier() { const float _13 = 1f / 3f; @@ -125,5 +118,4 @@ public BezierCubic3D ToBezier() { } } - -} \ No newline at end of file +} From 887890a28d42230685b69a9f3d4225b7784802e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 01:38:12 +0200 Subject: [PATCH 062/301] lerp, projection & eq comp. for all curves and ToString and index accessors for all of them! yay consistency from codegen etc --- .../Uniform Spline Segments/BezierQuad2D.cs | 16 ++++++ .../Uniform Spline Segments/BezierQuad3D.cs | 16 ++++++ .../Uniform Spline Segments/CatRomCubic2D.cs | 6 +- .../Uniform Spline Segments/CatRomCubic3D.cs | 6 +- .../Uniform Spline Segments/HermiteCubic2D.cs | 56 +++++++++++++++++-- .../Uniform Spline Segments/HermiteCubic3D.cs | 53 +++++++++++++++++- Splines/Uniform Spline Segments/UBSCubic2D.cs | 24 +++++++- Splines/Uniform Spline Segments/UBSCubic3D.cs | 23 +++++++- 8 files changed, 190 insertions(+), 10 deletions(-) diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index 284ab6f..f2ba633 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -85,6 +85,11 @@ public Vector2 this[ int i ] { validCoefficients = true; curve = CharMatrix.quadraticBezier.GetCurve( p0, p1, p2 ); } + public static bool operator ==( BezierQuad2D a, BezierQuad2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; + public static bool operator !=( BezierQuad2D a, BezierQuad2D b ) => !( a == b ); + public bool Equals( BezierQuad2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ); + public override bool Equals( object obj ) => obj is BezierQuad2D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2 ); /// public BezierQuad2D Split( float t ) { @@ -94,5 +99,16 @@ public BezierQuad2D Split( float t ) { return new BezierQuad2D( p0, mid, end ); } + public override string ToString() => $"({p0}, {p1}, {p2})"; + /// Returns a linear blend between two bézier curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static BezierQuad2D Lerp( BezierQuad2D a, BezierQuad2D b, float t ) => + new( + Vector2.LerpUnclamped( a.p0, b.p0, t ), + Vector2.LerpUnclamped( a.p1, b.p1, t ), + Vector2.LerpUnclamped( a.p2, b.p2, t ) + ); } } diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index 847a109..51abf7b 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -85,6 +85,11 @@ public Vector3 this[ int i ] { validCoefficients = true; curve = CharMatrix.quadraticBezier.GetCurve( p0, p1, p2 ); } + public static bool operator ==( BezierQuad3D a, BezierQuad3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; + public static bool operator !=( BezierQuad3D a, BezierQuad3D b ) => !( a == b ); + public bool Equals( BezierQuad3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ); + public override bool Equals( object obj ) => obj is BezierQuad3D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2 ); /// public BezierQuad3D Split( float t ) { @@ -94,5 +99,16 @@ public BezierQuad3D Split( float t ) { return new BezierQuad3D( p0, mid, end ); } + public override string ToString() => $"({p0}, {p1}, {p2})"; + /// Returns a linear blend between two bézier curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static BezierQuad3D Lerp( BezierQuad3D a, BezierQuad3D b, float t ) => + new( + Vector3.LerpUnclamped( a.p0, b.p0, t ), + Vector3.LerpUnclamped( a.p1, b.p1, t ), + Vector3.LerpUnclamped( a.p2, b.p2, t ) + ); } } diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 0596fa9..238e8b5 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -103,7 +103,11 @@ public Vector2 this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - + /// Returns this spline segment in 3D, where z = 0 + /// The 2D curve to cast to 3D + public static explicit operator CatRomCubic3D( CatRomCubic2D curve2D ) { + return new CatRomCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); + } /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 09e99dd..817f0ef 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -103,7 +103,11 @@ public Vector3 this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - + /// Returns this curve flattened to 2D. Effectively setting z = 0 + /// The 3D curve to flatten to the Z plane + public static explicit operator CatRomCubic2D( CatRomCubic3D curve3D ) { + return new CatRomCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); + } /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index e8c54dc..d8e2d12 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -30,7 +30,6 @@ public Polynomial2D Curve { return curve; } } - #region Control Points [SerializeField] Vector2 p0, v0, p1, v1; @@ -59,9 +58,36 @@ public Vector2 V1 { [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); } - #endregion - + /// Get or set a control point position by index. Valid indices from 0 to 3 + public Vector2 this[ int i ] { + get => + i switch { + 0 => P0, + 1 => V0, + 2 => P1, + 3 => V1, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + V0 = value; + break; + case 2: + P1 = value; + break; + case 3: + V1 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + #endregion [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -72,6 +98,28 @@ public Vector2 V1 { } public BezierCubic2D ToBezier() => new BezierCubic2D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); - + public static bool operator ==( HermiteCubic2D a, HermiteCubic2D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; + public static bool operator !=( HermiteCubic2D a, HermiteCubic2D b ) => !( a == b ); + public bool Equals( HermiteCubic2D other ) => P0.Equals( other.P0 ) && V0.Equals( other.V0 ) && P1.Equals( other.P1 ) && V1.Equals( other.V1 ); + public override bool Equals( object obj ) => obj is HermiteCubic2D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); + + public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; + /// Returns this spline segment in 3D, where z = 0 + /// The 2D curve to cast to 3D + public static explicit operator HermiteCubic3D( HermiteCubic2D curve2D ) { + return new HermiteCubic3D( curve2D.p0, curve2D.v0, curve2D.p1, curve2D.v1 ); + } + /// Returns a linear blend between two hermite curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static HermiteCubic2D Lerp( HermiteCubic2D a, HermiteCubic2D b, float t ) => + new( + Vector2.LerpUnclamped( a.p0, b.p0, t ), + Vector2.LerpUnclamped( a.v0, b.v0, t ), + Vector2.LerpUnclamped( a.p1, b.p1, t ), + Vector2.LerpUnclamped( a.v1, b.v1, t ) + ); } } diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index 89c7e88..a3f0cb1 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -58,9 +58,36 @@ public Vector3 V1 { [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); } - #endregion - + /// Get or set a control point position by index. Valid indices from 0 to 3 + public Vector3 this[ int i ] { + get => + i switch { + 0 => P0, + 1 => V0, + 2 => P1, + 3 => V1, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + V0 = value; + break; + case 2: + P1 = value; + break; + case 3: + V1 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + #endregion [NonSerialized] bool validCoefficients; [MethodImpl( INLINE )] void ReadyCoefficients() { @@ -69,8 +96,30 @@ public Vector3 V1 { validCoefficients = true; curve = CharMatrix.cubicHermite.GetCurve( p0, v0, p1, v1 ); } + public static bool operator ==( HermiteCubic3D a, HermiteCubic3D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; + public static bool operator !=( HermiteCubic3D a, HermiteCubic3D b ) => !( a == b ); + public bool Equals( HermiteCubic3D other ) => P0.Equals( other.P0 ) && V0.Equals( other.V0 ) && P1.Equals( other.P1 ) && V1.Equals( other.V1 ); + public override bool Equals( object obj ) => obj is HermiteCubic3D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); public BezierCubic3D ToBezier() => new BezierCubic3D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); + public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; + /// Returns this curve flattened to 2D. Effectively setting z = 0 + /// The 3D curve to flatten to the Z plane + public static explicit operator HermiteCubic2D( HermiteCubic3D curve3D ) { + return new HermiteCubic2D( curve3D.p0, curve3D.v0, curve3D.p1, curve3D.v1 ); + } + /// Returns a linear blend between two hermite curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static HermiteCubic3D Lerp( HermiteCubic3D a, HermiteCubic3D b, float t ) => + new( + Vector3.LerpUnclamped( a.p0, b.p0, t ), + Vector3.LerpUnclamped( a.v0, b.v0, t ), + Vector3.LerpUnclamped( a.p1, b.p1, t ), + Vector3.LerpUnclamped( a.v1, b.v1, t ) + ); } } diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index 1814fad..dca474d 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -116,6 +116,28 @@ public BezierCubic2D ToBezier() { new Vector2( 0.5f * ( cx + dx ), 0.5f * ( cy + dy ) ) ); } - + public static bool operator ==( UBSCubic2D a, UBSCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator !=( UBSCubic2D a, UBSCubic2D b ) => !( a == b ); + public bool Equals( UBSCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is UBSCubic2D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; + /// Returns this spline segment in 3D, where z = 0 + /// The 2D curve to cast to 3D + public static explicit operator UBSCubic3D( UBSCubic2D curve2D ) { + return new UBSCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); + } + /// Returns a linear blend between two b-spline curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static UBSCubic2D Lerp( UBSCubic2D a, UBSCubic2D b, float t ) => + new( + Vector2.LerpUnclamped( a.p0, b.p0, t ), + Vector2.LerpUnclamped( a.p1, b.p1, t ), + Vector2.LerpUnclamped( a.p2, b.p2, t ), + Vector2.LerpUnclamped( a.p3, b.p3, t ) + ); } } diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index c04a561..5234873 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -96,6 +96,11 @@ public Vector3 this[ int i ] { validCoefficients = true; curve = CharMatrix.cubicUniformBspline.GetCurve( p0, p1, p2, p3 ); } + public static bool operator ==( UBSCubic3D a, UBSCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator !=( UBSCubic3D a, UBSCubic3D b ) => !( a == b ); + public bool Equals( UBSCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is UBSCubic3D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); /// public BezierCubic3D ToBezier() { @@ -116,6 +121,22 @@ public BezierCubic3D ToBezier() { new Vector3( 0.5f * ( cx + dx ), 0.5f * ( cy + dy ) ) ); } - + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; + /// Returns this curve flattened to 2D. Effectively setting z = 0 + /// The 3D curve to flatten to the Z plane + public static explicit operator UBSCubic2D( UBSCubic3D curve3D ) { + return new UBSCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); + } + /// Returns a linear blend between two b-spline curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static UBSCubic3D Lerp( UBSCubic3D a, UBSCubic3D b, float t ) => + new( + Vector3.LerpUnclamped( a.p0, b.p0, t ), + Vector3.LerpUnclamped( a.p1, b.p1, t ), + Vector3.LerpUnclamped( a.p2, b.p2, t ), + Vector3.LerpUnclamped( a.p3, b.p3, t ) + ); } } From ed4956d0ec5bf8ab5b32107a38dafe62edeb78c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 11:38:05 +0200 Subject: [PATCH 063/301] codegen bezier splitting (smol API change) --- Codegen/Editor/MathfsCodegen.cs | 42 +++++++++++++++++++ .../Uniform Spline Segments/BezierCubic2D.cs | 28 ++++++------- .../Uniform Spline Segments/BezierCubic3D.cs | 38 ++++++++--------- .../Uniform Spline Segments/BezierQuad2D.cs | 22 ++++++---- .../Uniform Spline Segments/BezierQuad3D.cs | 25 +++++++---- 5 files changed, 106 insertions(+), 49 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index d024c01..16ae8a4 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -288,6 +288,16 @@ static void GenerateType( SplineType type, int dim ) { code.Append( ");" ); } } + + // special case splits + bool hasSplit = type == typeBezier || type == typeBezierQuad; + if( hasSplit ) { + code.Summary( "Splits this curve at the given t-value, into two curves that together form the exact same shape" ); + code.Param( "t", "The t-value to split at" ); + using( code.BracketScope( $"public ({structName} pre, {structName} post) Split( float t )" ) ) { + AppendBezierSplit( code, structName, dataType, degree, dim ); + } + } } } @@ -306,6 +316,38 @@ public static string GetDegreeName( int d, bool shortName ) { }; } + static readonly string[] comp = { "x", "y", "z" }; + + public static void AppendBezierSplit( CodeGenerator code, string structName, string dataType, int degree, int dim ) { + string LerpStr( string A, string B, int c ) => $"{A}.{comp[c]} + ( {B}.{comp[c]} - {A}.{comp[c]} ) * t"; + + void AppendLerps( string varName, string A, string B ) { + if( dim > 1 ) { + using( code.Scope( $"{dataType} {varName} = new {dataType}(" ) ) { + for( int c = 0; c < dim; c++ ) { + string end = c == dim - 1 ? " );" : ","; + code.Append( $"{LerpStr( A, B, c )}{end}" ); + } + } + } else { // floats + code.Append( $"{dataType} {varName} = {A} + ( {B} - {A} ) * t;" ); + } + } + + AppendLerps( "a", "p0", "p1" ); + AppendLerps( "b", "p1", "p2" ); // this could be unrolled/optimized for the cubic case, as b is never used for the output + if( degree == 3 ) { + AppendLerps( "c", "p2", "p3" ); + AppendLerps( "d", "a", "b" ); + AppendLerps( "e", "b", "c" ); + AppendLerps( "p", "d", "e" ); + code.Append( $"return ( new {structName}( p0, a, d, p ), new {structName}( p, e, c, p3 ) );" ); + } else if( degree == 2 ) { + AppendLerps( "p", "a", "b" ); + code.Append( $"return ( new {structName}( p0, a, p ), new {structName}( p, b, p2 ) );" ); + } + } + } } \ No newline at end of file diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index b622776..c6c2d76 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -134,28 +134,27 @@ public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { p3 ); } - - /// Splits this curve at the given t-value, into two curves of the exact same shape - /// The t-value along the curve to sample + /// Splits this curve at the given t-value, into two curves that together form the exact same shape + /// The t-value to split at public (BezierCubic2D pre, BezierCubic2D post) Split( float t ) { Vector2 a = new Vector2( - P0.x + ( P1.x - P0.x ) * t, - P0.y + ( P1.y - P0.y ) * t ); - float bx = P1.x + ( P2.x - P1.x ) * t; - float by = P1.y + ( P2.y - P1.y ) * t; + p0.x + ( p1.x - p0.x ) * t, + p0.y + ( p1.y - p0.y ) * t ); + Vector2 b = new Vector2( + p1.x + ( p2.x - p1.x ) * t, + p1.y + ( p2.y - p1.y ) * t ); Vector2 c = new Vector2( - P2.x + ( P3.x - P2.x ) * t, - P2.y + ( P3.y - P2.y ) * t ); + p2.x + ( p3.x - p2.x ) * t, + p2.y + ( p3.y - p2.y ) * t ); Vector2 d = new Vector2( - a.x + ( bx - a.x ) * t, - a.y + ( by - a.y ) * t ); + a.x + ( b.x - a.x ) * t, + a.y + ( b.y - a.y ) * t ); Vector2 e = new Vector2( - bx + ( c.x - bx ) * t, - by + ( c.y - by ) * t ); + b.x + ( c.x - b.x ) * t, + b.y + ( c.y - b.y ) * t ); Vector2 p = new Vector2( d.x + ( e.x - d.x ) * t, d.y + ( e.y - d.y ) * t ); - return ( new BezierCubic2D( P0, a, d, p ), new BezierCubic2D( p, e, c, P3 ) ); } public UBSCubic2D ToUniformCubicBSpline() { @@ -179,6 +178,7 @@ public CatRomCubic2D ToUniformCubicCatRom() { public HermiteCubic2D ToHermite() { // todo: channel split for performance return new HermiteCubic2D( p0, ( p1 - p0 ) * 3, p3, ( p3 - p2 ) * 3 ); + return ( new BezierCubic2D( p0, a, d, p ), new BezierCubic2D( p, e, c, p3 ) ); } } } diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index 3949b55..d7f01f3 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -134,34 +134,34 @@ public static BezierCubic3D Slerp( BezierCubic3D a, BezierCubic3D b, float t ) { p3 ); } - - /// + /// Splits this curve at the given t-value, into two curves that together form the exact same shape + /// The t-value to split at public (BezierCubic3D pre, BezierCubic3D post) Split( float t ) { Vector3 a = new Vector3( - P0.x + ( P1.x - P0.x ) * t, - P0.y + ( P1.y - P0.y ) * t, - P0.z + ( P1.z - P0.z ) * t ); - float bx = P1.x + ( P2.x - P1.x ) * t; - float by = P1.y + ( P2.y - P1.y ) * t; - float bz = P1.z + ( P2.z - P1.z ) * t; + p0.x + ( p1.x - p0.x ) * t, + p0.y + ( p1.y - p0.y ) * t, + p0.z + ( p1.z - p0.z ) * t ); + Vector3 b = new Vector3( + p1.x + ( p2.x - p1.x ) * t, + p1.y + ( p2.y - p1.y ) * t, + p1.z + ( p2.z - p1.z ) * t ); Vector3 c = new Vector3( - P2.x + ( P3.x - P2.x ) * t, - P2.y + ( P3.y - P2.y ) * t, - P2.z + ( P3.z - P2.z ) * t ); + p2.x + ( p3.x - p2.x ) * t, + p2.y + ( p3.y - p2.y ) * t, + p2.z + ( p3.z - p2.z ) * t ); Vector3 d = new Vector3( - a.x + ( bx - a.x ) * t, - a.y + ( by - a.y ) * t, - a.z + ( bz - a.z ) * t ); + a.x + ( b.x - a.x ) * t, + a.y + ( b.y - a.y ) * t, + a.z + ( b.z - a.z ) * t ); Vector3 e = new Vector3( - bx + ( c.x - bx ) * t, - by + ( c.y - by ) * t, - bz + ( c.z - bz ) * t ); + b.x + ( c.x - b.x ) * t, + b.y + ( c.y - b.y ) * t, + b.z + ( c.z - b.z ) * t ); Vector3 p = new Vector3( d.x + ( e.x - d.x ) * t, d.y + ( e.y - d.y ) * t, d.z + ( e.z - d.z ) * t ); - return ( new BezierCubic3D( P0, a, d, p ), new BezierCubic3D( p, e, c, P3 ) ); + return ( new BezierCubic3D( p0, a, d, p ), new BezierCubic3D( p, e, c, p3 ) ); } - } } diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index f2ba633..bade0f0 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -91,14 +91,6 @@ public Vector2 this[ int i ] { public override bool Equals( object obj ) => obj is BezierQuad2D other && Equals( other ); public override int GetHashCode() => HashCode.Combine( p0, p1, p2 ); - /// - public BezierQuad2D Split( float t ) { - Vector2 mid = Vector2.LerpUnclamped( p0, p1, t ); - Vector2 b = Vector2.LerpUnclamped( p1, p2, t ); - Vector2 end = Vector2.LerpUnclamped( mid, b, t ); - return new BezierQuad2D( p0, mid, end ); - } - public override string ToString() => $"({p0}, {p1}, {p2})"; /// Returns a linear blend between two bézier curves /// The first spline segment @@ -110,5 +102,19 @@ public static BezierQuad2D Lerp( BezierQuad2D a, BezierQuad2D b, float t ) => Vector2.LerpUnclamped( a.p1, b.p1, t ), Vector2.LerpUnclamped( a.p2, b.p2, t ) ); + /// Splits this curve at the given t-value, into two curves that together form the exact same shape + /// The t-value to split at + public (BezierQuad2D pre, BezierQuad2D post) Split( float t ) { + Vector2 a = new Vector2( + p0.x + ( p1.x - p0.x ) * t, + p0.y + ( p1.y - p0.y ) * t ); + Vector2 b = new Vector2( + p1.x + ( p2.x - p1.x ) * t, + p1.y + ( p2.y - p1.y ) * t ); + Vector2 p = new Vector2( + a.x + ( b.x - a.x ) * t, + a.y + ( b.y - a.y ) * t ); + return ( new BezierQuad2D( p0, a, p ), new BezierQuad2D( p, b, p2 ) ); + } } } diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index 51abf7b..ef2b30f 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -91,14 +91,6 @@ public Vector3 this[ int i ] { public override bool Equals( object obj ) => obj is BezierQuad3D other && Equals( other ); public override int GetHashCode() => HashCode.Combine( p0, p1, p2 ); - /// - public BezierQuad3D Split( float t ) { - Vector3 mid = Vector3.LerpUnclamped( p0, p1, t ); - Vector3 b = Vector3.LerpUnclamped( p1, p2, t ); - Vector3 end = Vector3.LerpUnclamped( mid, b, t ); - return new BezierQuad3D( p0, mid, end ); - } - public override string ToString() => $"({p0}, {p1}, {p2})"; /// Returns a linear blend between two bézier curves /// The first spline segment @@ -110,5 +102,22 @@ public static BezierQuad3D Lerp( BezierQuad3D a, BezierQuad3D b, float t ) => Vector3.LerpUnclamped( a.p1, b.p1, t ), Vector3.LerpUnclamped( a.p2, b.p2, t ) ); + /// Splits this curve at the given t-value, into two curves that together form the exact same shape + /// The t-value to split at + public (BezierQuad3D pre, BezierQuad3D post) Split( float t ) { + Vector3 a = new Vector3( + p0.x + ( p1.x - p0.x ) * t, + p0.y + ( p1.y - p0.y ) * t, + p0.z + ( p1.z - p0.z ) * t ); + Vector3 b = new Vector3( + p1.x + ( p2.x - p1.x ) * t, + p1.y + ( p2.y - p1.y ) * t, + p1.z + ( p2.z - p1.z ) * t ); + Vector3 p = new Vector3( + a.x + ( b.x - a.x ) * t, + a.y + ( b.y - a.y ) * t, + a.z + ( b.z - a.z ) * t ); + return ( new BezierQuad3D( p0, a, p ), new BezierQuad3D( p, b, p2 ) ); + } } } From f6b27c667bc0637664fe7689e746f1fbbe72909a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 11:40:45 +0200 Subject: [PATCH 064/301] codegen 1D uniform spline segments --- .../Uniform Spline Segments/BezierCubic1D.cs | 129 ++++++++++++++++++ .../Uniform Spline Segments/BezierQuad1D.cs | 114 ++++++++++++++++ .../Uniform Spline Segments/CatRomCubic1D.cs | 118 ++++++++++++++++ .../Uniform Spline Segments/HermiteCubic1D.cs | 118 ++++++++++++++++ Splines/Uniform Spline Segments/UBSCubic1D.cs | 118 ++++++++++++++++ 5 files changed, 597 insertions(+) create mode 100644 Splines/Uniform Spline Segments/BezierCubic1D.cs create mode 100644 Splines/Uniform Spline Segments/BezierQuad1D.cs create mode 100644 Splines/Uniform Spline Segments/CatRomCubic1D.cs create mode 100644 Splines/Uniform Spline Segments/HermiteCubic1D.cs create mode 100644 Splines/Uniform Spline Segments/UBSCubic1D.cs diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs new file mode 100644 index 0000000..efe93f6 --- /dev/null +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -0,0 +1,129 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 1D Cubic bézier segment, with 4 control points + [Serializable] public struct BezierCubic1D : IParamCubicSplineSegment1D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Creates a uniform 1D Cubic bézier segment, from 4 control points + /// The starting point of the curve + /// The second control point of the curve, sometimes called the start tangent point + /// The third control point of the curve, sometimes called the end tangent point + /// The end point of the curve + public BezierCubic1D( float p0, float p1, float p2, float p3 ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + validCoefficients = false; + curve = default; + } + + Polynomial curve; + public Polynomial Curve { + get { + ReadyCoefficients(); + return curve; + } + } + #region Control Points + + [SerializeField] float p0, p1, p2, p3; + + /// The starting point of the curve + public float P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// The second control point of the curve, sometimes called the start tangent point + public float P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// The third control point of the curve, sometimes called the end tangent point + public float P2 { + [MethodImpl( INLINE )] get => p2; + [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + } + + /// The end point of the curve + public float P3 { + [MethodImpl( INLINE )] get => p3; + [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + } + + /// Get or set a control point position by index. Valid indices from 0 to 3 + public float this[ int i ] { + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + P1 = value; + break; + case 2: + P2 = value; + break; + case 3: + P3 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + + #endregion + [NonSerialized] bool validCoefficients; + + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicBezier.GetEvalPolynomial( p0, p1, p2, p3 ); + } + public static bool operator ==( BezierCubic1D a, BezierCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator !=( BezierCubic1D a, BezierCubic1D b ) => !( a == b ); + public bool Equals( BezierCubic1D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is BezierCubic1D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; + /// Returns a linear blend between two bézier curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static BezierCubic1D Lerp( BezierCubic1D a, BezierCubic1D b, float t ) => + new( + Mathfs.Lerp( a.p0, b.p0, t ), + Mathfs.Lerp( a.p1, b.p1, t ), + Mathfs.Lerp( a.p2, b.p2, t ), + Mathfs.Lerp( a.p3, b.p3, t ) + ); + /// Splits this curve at the given t-value, into two curves that together form the exact same shape + /// The t-value to split at + public (BezierCubic1D pre, BezierCubic1D post) Split( float t ) { + float a = p0 + ( p1 - p0 ) * t; + float b = p1 + ( p2 - p1 ) * t; + float c = p2 + ( p3 - p2 ) * t; + float d = a + ( b - a ) * t; + float e = b + ( c - b ) * t; + float p = d + ( e - d ) * t; + return ( new BezierCubic1D( p0, a, d, p ), new BezierCubic1D( p, e, c, p3 ) ); + } + } +} diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Splines/Uniform Spline Segments/BezierQuad1D.cs new file mode 100644 index 0000000..fd46ff8 --- /dev/null +++ b/Splines/Uniform Spline Segments/BezierQuad1D.cs @@ -0,0 +1,114 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 1D Quadratic bézier segment, with 3 control points + [Serializable] public struct BezierQuad1D : IParamCubicSplineSegment1D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Creates a uniform 1D Quadratic bézier segment, from 3 control points + /// The starting point of the curve + /// The middle control point of the curve, sometimes called a tangent point + /// The end point of the curve + public BezierQuad1D( float p0, float p1, float p2 ) { + ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); + validCoefficients = false; + curve = default; + } + + Polynomial curve; + public Polynomial Curve { + get { + ReadyCoefficients(); + return curve; + } + } + #region Control Points + + [SerializeField] float p0, p1, p2; + + /// The starting point of the curve + public float P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// The middle control point of the curve, sometimes called a tangent point + public float P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// The end point of the curve + public float P2 { + [MethodImpl( INLINE )] get => p2; + [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + } + + /// Get or set a control point position by index. Valid indices from 0 to 2 + public float this[ int i ] { + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) + }; + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + P1 = value; + break; + case 2: + P2 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); + } + } + } + + #endregion + [NonSerialized] bool validCoefficients; + + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.quadraticBezier.GetEvalPolynomial( p0, p1, p2 ); + } + public static bool operator ==( BezierQuad1D a, BezierQuad1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; + public static bool operator !=( BezierQuad1D a, BezierQuad1D b ) => !( a == b ); + public bool Equals( BezierQuad1D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ); + public override bool Equals( object obj ) => obj is BezierQuad1D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2 ); + + public override string ToString() => $"({p0}, {p1}, {p2})"; + /// Returns a linear blend between two bézier curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static BezierQuad1D Lerp( BezierQuad1D a, BezierQuad1D b, float t ) => + new( + Mathfs.Lerp( a.p0, b.p0, t ), + Mathfs.Lerp( a.p1, b.p1, t ), + Mathfs.Lerp( a.p2, b.p2, t ) + ); + /// Splits this curve at the given t-value, into two curves that together form the exact same shape + /// The t-value to split at + public (BezierQuad1D pre, BezierQuad1D post) Split( float t ) { + float a = p0 + ( p1 - p0 ) * t; + float b = p1 + ( p2 - p1 ) * t; + float p = a + ( b - a ) * t; + return ( new BezierQuad1D( p0, a, p ), new BezierQuad1D( p, b, p2 ) ); + } + } +} diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs new file mode 100644 index 0000000..e63f946 --- /dev/null +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -0,0 +1,118 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 1D Cubic catmull-rom segment, with 4 control points + [Serializable] public struct CatRomCubic1D : IParamCubicSplineSegment1D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Creates a uniform 1D Cubic catmull-rom segment, from 4 control points + /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The second control point, and the start of the catmull-rom curve + /// The third control point, and the end of the catmull-rom curve + /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public CatRomCubic1D( float p0, float p1, float p2, float p3 ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + validCoefficients = false; + curve = default; + } + + Polynomial curve; + public Polynomial Curve { + get { + ReadyCoefficients(); + return curve; + } + } + #region Control Points + + [SerializeField] float p0, p1, p2, p3; + + /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public float P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// The second control point, and the start of the catmull-rom curve + public float P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// The third control point, and the end of the catmull-rom curve + public float P2 { + [MethodImpl( INLINE )] get => p2; + [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + } + + /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public float P3 { + [MethodImpl( INLINE )] get => p3; + [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + } + + /// Get or set a control point position by index. Valid indices from 0 to 3 + public float this[ int i ] { + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + P1 = value; + break; + case 2: + P2 = value; + break; + case 3: + P3 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + + #endregion + [NonSerialized] bool validCoefficients; + + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicCatmullRom.GetEvalPolynomial( p0, p1, p2, p3 ); + } + public static bool operator ==( CatRomCubic1D a, CatRomCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator !=( CatRomCubic1D a, CatRomCubic1D b ) => !( a == b ); + public bool Equals( CatRomCubic1D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is CatRomCubic1D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; + /// Returns a linear blend between two catmull-rom curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static CatRomCubic1D Lerp( CatRomCubic1D a, CatRomCubic1D b, float t ) => + new( + Mathfs.Lerp( a.p0, b.p0, t ), + Mathfs.Lerp( a.p1, b.p1, t ), + Mathfs.Lerp( a.p2, b.p2, t ), + Mathfs.Lerp( a.p3, b.p3, t ) + ); + } +} diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs new file mode 100644 index 0000000..3a3989e --- /dev/null +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -0,0 +1,118 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 1D Cubic hermite segment, with 4 control points + [Serializable] public struct HermiteCubic1D : IParamCubicSplineSegment1D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Creates a uniform 1D Cubic hermite segment, from 4 control points + /// The starting point of the curve + /// The rate of change (velocity) at the start of the curve + /// The end point of the curve + /// The rate of change (velocity) at the end of the curve + public HermiteCubic1D( float p0, float v0, float p1, float v1 ) { + ( this.p0, this.v0, this.p1, this.v1 ) = ( p0, v0, p1, v1 ); + validCoefficients = false; + curve = default; + } + + Polynomial curve; + public Polynomial Curve { + get { + ReadyCoefficients(); + return curve; + } + } + #region Control Points + + [SerializeField] float p0, v0, p1, v1; + + /// The starting point of the curve + public float P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// The rate of change (velocity) at the start of the curve + public float V0 { + [MethodImpl( INLINE )] get => v0; + [MethodImpl( INLINE )] set => _ = ( v0 = value, validCoefficients = false ); + } + + /// The end point of the curve + public float P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// The rate of change (velocity) at the end of the curve + public float V1 { + [MethodImpl( INLINE )] get => v1; + [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); + } + + /// Get or set a control point position by index. Valid indices from 0 to 3 + public float this[ int i ] { + get => + i switch { + 0 => P0, + 1 => V0, + 2 => P1, + 3 => V1, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + V0 = value; + break; + case 2: + P1 = value; + break; + case 3: + V1 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + + #endregion + [NonSerialized] bool validCoefficients; + + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicHermite.GetEvalPolynomial( p0, v0, p1, v1 ); + } + public static bool operator ==( HermiteCubic1D a, HermiteCubic1D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; + public static bool operator !=( HermiteCubic1D a, HermiteCubic1D b ) => !( a == b ); + public bool Equals( HermiteCubic1D other ) => P0.Equals( other.P0 ) && V0.Equals( other.V0 ) && P1.Equals( other.P1 ) && V1.Equals( other.V1 ); + public override bool Equals( object obj ) => obj is HermiteCubic1D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); + + public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; + /// Returns a linear blend between two hermite curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static HermiteCubic1D Lerp( HermiteCubic1D a, HermiteCubic1D b, float t ) => + new( + Mathfs.Lerp( a.p0, b.p0, t ), + Mathfs.Lerp( a.v0, b.v0, t ), + Mathfs.Lerp( a.p1, b.p1, t ), + Mathfs.Lerp( a.v1, b.v1, t ) + ); + } +} diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs new file mode 100644 index 0000000..a6ba373 --- /dev/null +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -0,0 +1,118 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 1D Cubic b-spline segment, with 4 control points + [Serializable] public struct UBSCubic1D : IParamCubicSplineSegment1D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Creates a uniform 1D Cubic b-spline segment, from 4 control points + /// The first point of the B-spline hull + /// The second point of the B-spline hull + /// The third point of the B-spline hull + /// The fourth point of the B-spline hull + public UBSCubic1D( float p0, float p1, float p2, float p3 ) { + ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + validCoefficients = false; + curve = default; + } + + Polynomial curve; + public Polynomial Curve { + get { + ReadyCoefficients(); + return curve; + } + } + #region Control Points + + [SerializeField] float p0, p1, p2, p3; + + /// The first point of the B-spline hull + public float P0 { + [MethodImpl( INLINE )] get => p0; + [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + } + + /// The second point of the B-spline hull + public float P1 { + [MethodImpl( INLINE )] get => p1; + [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + } + + /// The third point of the B-spline hull + public float P2 { + [MethodImpl( INLINE )] get => p2; + [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + } + + /// The fourth point of the B-spline hull + public float P3 { + [MethodImpl( INLINE )] get => p3; + [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + } + + /// Get or set a control point position by index. Valid indices from 0 to 3 + public float this[ int i ] { + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + 3 => P3, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) + }; + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + P1 = value; + break; + case 2: + P2 = value; + break; + case 3: + P3 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); + } + } + } + + #endregion + [NonSerialized] bool validCoefficients; + + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = CharMatrix.cubicUniformBspline.GetEvalPolynomial( p0, p1, p2, p3 ); + } + public static bool operator ==( UBSCubic1D a, UBSCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator !=( UBSCubic1D a, UBSCubic1D b ) => !( a == b ); + public bool Equals( UBSCubic1D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is UBSCubic1D other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + + public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; + /// Returns a linear blend between two b-spline curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static UBSCubic1D Lerp( UBSCubic1D a, UBSCubic1D b, float t ) => + new( + Mathfs.Lerp( a.p0, b.p0, t ), + Mathfs.Lerp( a.p1, b.p1, t ), + Mathfs.Lerp( a.p2, b.p2, t ), + Mathfs.Lerp( a.p3, b.p3, t ) + ); + } +} From b8d968bd277c4349d0a31f05812d45e70176fa98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 11:41:05 +0200 Subject: [PATCH 065/301] codegen formatting tweaks --- Codegen/Editor/CodeGenerator.cs | 2 - Codegen/Editor/MathfsCodegen.cs | 96 ++++++++++++++++----------------- 2 files changed, 46 insertions(+), 52 deletions(-) diff --git a/Codegen/Editor/CodeGenerator.cs b/Codegen/Editor/CodeGenerator.cs index de4a7bb..9715d50 100644 --- a/Codegen/Editor/CodeGenerator.cs +++ b/Codegen/Editor/CodeGenerator.cs @@ -50,7 +50,6 @@ public void Dispose() { public RegionScope( CodeGenerator gen, string s ) { this.gen = gen; - gen.LineBreak(); gen.Append( $"#region {s}" ); gen.LineBreak(); } @@ -58,7 +57,6 @@ public RegionScope( CodeGenerator gen, string s ) { public void Dispose() { gen.LineBreak(); gen.Append( "#endregion" ); - gen.LineBreak(); } } } diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 16ae8a4..230a257 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -202,73 +202,69 @@ static void GenerateType( SplineType type, int dim ) { } // Coefficients - using( code.ScopeRegion( "Coefficients" ) ) { - code.Append( "[NonSerialized] bool validCoefficients;" ); - code.LineBreak(); - using( code.BracketScope( "[MethodImpl( INLINE )] void ReadyCoefficients()" ) ) { - using( code.Scope( "if( validCoefficients )" ) ) - code.Append( "return; // no need to update" ); - code.Append( "validCoefficients = true;" ); - code.Append( $"curve = CharMatrix.{type.matrixName}.{curveFunc}( {string.Join( ", ", points )} );" ); - } + code.Append( "[NonSerialized] bool validCoefficients;" ); + code.LineBreak(); + using( code.BracketScope( "[MethodImpl( INLINE )] void ReadyCoefficients()" ) ) { + using( code.Scope( "if( validCoefficients )" ) ) + code.Append( "return; // no need to update" ); + code.Append( "validCoefficients = true;" ); + // todo: unroll matrix multiply for performance + code.Append( $"curve = CharMatrix.{type.matrixName}.{curveFunc}( {string.Join( ", ", points )} );" ); } // equality checks - using( code.ScopeRegion( "Object Comparison & ToString" ) ) { - code.Append( $"public static bool operator ==( {structName} a, {structName} b ) => {string.Join( " && ", points.Select( p => $"a.{p.ToUpperInvariant()} == b.{p.ToUpperInvariant()}" ) )};" ); - code.Append( $"public static bool operator !=( {structName} a, {structName} b ) => !( a == b );" ); - code.Append( $"public bool Equals( {structName} other ) => {string.Join( " && ", points.Select( p => $"{p.ToUpperInvariant()}.Equals( other.{p.ToUpperInvariant()} )" ) )};" ); - code.Append( $"public override bool Equals( object obj ) => obj is {structName} other && Equals( other );" ); - code.Append( $"public override int GetHashCode() => HashCode.Combine( {string.Join( ", ", points )} );" ); - code.LineBreak(); - code.Append( $"public override string ToString() => $\"({string.Join( ", ", points.Select( p => $"{{{p}}}" ) )})\";" ); - } + code.Append( $"public static bool operator ==( {structName} a, {structName} b ) => {string.Join( " && ", points.Select( p => $"a.{p.ToUpperInvariant()} == b.{p.ToUpperInvariant()}" ) )};" ); + code.Append( $"public static bool operator !=( {structName} a, {structName} b ) => !( a == b );" ); + code.Append( $"public bool Equals( {structName} other ) => {string.Join( " && ", points.Select( p => $"{p.ToUpperInvariant()}.Equals( other.{p.ToUpperInvariant()} )" ) )};" ); + code.Append( $"public override bool Equals( object obj ) => obj is {structName} other && Equals( other );" ); + code.Append( $"public override int GetHashCode() => HashCode.Combine( {string.Join( ", ", points )} );" ); + code.LineBreak(); + code.Append( $"public override string ToString() => $\"({string.Join( ", ", points.Select( p => $"{{{p}}}" ) )})\";" ); // typecasting - if( dim is 2 or 3 && degree is 3 ) - using( code.ScopeRegion( "Type Casting" ) ) { - if( dim == 2 ) { - // Typecast to 3D where z = 0 - string structName3D = $"{type.className}{degShortCapital}3D"; - code.Summary( "Returns this spline segment in 3D, where z = 0" ); - code.Param( "curve2D", "The 2D curve to cast to 3D" ); - using( code.BracketScope( $"public static explicit operator {structName3D}( {structName} curve2D )" ) ) { - code.Append( $"return new {structName3D}( {string.Join( ", ", points.Select( p => $"curve2D.{p}" ) )} );" ); - } + if( dim is 2 or 3 && degree is 3 ) { + if( dim == 2 ) { + // Typecast to 3D where z = 0 + string structName3D = $"{type.className}{degShortCapital}3D"; + code.Summary( "Returns this spline segment in 3D, where z = 0" ); + code.Param( "curve2D", "The 2D curve to cast to 3D" ); + using( code.BracketScope( $"public static explicit operator {structName3D}( {structName} curve2D )" ) ) { + code.Append( $"return new {structName3D}( {string.Join( ", ", points.Select( p => $"curve2D.{p}" ) )} );" ); } + } - if( dim == 3 ) { - // typecast to 2D where z is omitted - string structName2D = $"{type.className}{degShortCapital}2D"; - code.Summary( "Returns this curve flattened to 2D. Effectively setting z = 0" ); - code.Param( "curve3D", "The 3D curve to flatten to the Z plane" ); - using( code.BracketScope( $"public static explicit operator {structName2D}( {structName} curve3D )" ) ) { - code.Append( $"return new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p}" ) )} );" ); - } - - // todo: conversion to other cubic splines + if( dim == 3 ) { + // typecast to 2D where z is omitted + string structName2D = $"{type.className}{degShortCapital}2D"; + code.Summary( "Returns this curve flattened to 2D. Effectively setting z = 0" ); + code.Param( "curve3D", "The 3D curve to flatten to the Z plane" ); + using( code.BracketScope( $"public static explicit operator {structName2D}( {structName} curve3D )" ) ) { + code.Append( $"return new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p}" ) )} );" ); } + + // todo: conversion to other cubic splines } + } // Interpolation - using( code.ScopeRegion( "Interpolation" ) ) { - code.Summary( $"Returns a linear blend between two {type.prettyNameLower} curves" ); - code.Param( "a", "The first spline segment" ); - code.Param( "b", "The second spline segment" ); - code.Param( "t", "A value from 0 to 1 to blend between a and b" ); - using( code.Scope( $"public static {structName} Lerp( {structName} a, {structName} b, float t ) =>" ) ) { - using( code.Scope( "new(" ) ) { - for( int i = 0; i < ptCount; i++ ) { - code.Append( $"{lerpName}( a.{points[i]}, b.{points[i]}, t )" + ( i == ptCount - 1 ? "" : "," ) ); - } + code.Summary( $"Returns a linear blend between two {type.prettyNameLower} curves" ); + code.Param( "a", "The first spline segment" ); + code.Param( "b", "The second spline segment" ); + code.Param( "t", "A value from 0 to 1 to blend between a and b" ); + using( code.Scope( $"public static {structName} Lerp( {structName} a, {structName} b, float t ) =>" ) ) { + using( code.Scope( "new(" ) ) { + for( int i = 0; i < ptCount; i++ ) { + code.Append( $"{lerpName}( a.{points[i]}, b.{points[i]}, t )" + ( i == ptCount - 1 ? "" : "," ) ); } - - code.Append( ");" ); } + + code.Append( ");" ); } + // special case slerps for cubic beziers in 2D and 3D if( dim > 1 && degree is 2 or 3 && type == typeBezier ) { + // todo: hermite slerp string slerpCast = dim == 2 ? "(Vector2)" : ""; code.LineBreak(); code.Summary( $"Returns a linear blend between two {type.prettyNameLower} curves, where the tangent directions are spherically interpolated" ); From 7ae6dc851355a13eea70fbef9bfc332599cf0a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 15:23:34 +0200 Subject: [PATCH 066/301] added Rational number type --- Rational.cs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Rational.cs diff --git a/Rational.cs b/Rational.cs new file mode 100644 index 0000000..89001a3 --- /dev/null +++ b/Rational.cs @@ -0,0 +1,92 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; + +namespace Freya { + + /// A struct representing exact rational numbers + [Serializable] public struct Rational : IComparable { + + public static readonly Rational Zero = new(0, 1); + public static readonly Rational One = new(1, 1); + public static readonly Rational MaxValue = new(int.MaxValue, 1); + public static readonly Rational MinValue = new(int.MinValue, 1); + + /// The numerator of this number + public readonly int n; + + /// The denominator of this number + public readonly int d; + + /// Creates an exact representation of a rational number + /// The numerator of this number + /// The denominator of this number + public Rational( int num, int den ) { + int sign = Mathfs.Sign( den ); // used to ensure only the numerator carries the sign + int gcd = Mathfs.Gcd( num, den ); // used to simplify the expression + n = sign * num / gcd; + d = sign * den / gcd; + } + + /// Returns the reciprocal of this number + public Rational Reciprocal => new(d, n); + + /// Returns this number to the power of another integer pow + /// The power to raise this number by + public Rational Pow( int pow ) => + pow switch { + <= -2 => Reciprocal.Pow( -pow ), + -1 => Reciprocal, + 0 => 1, + 1 => this, + >= 2 => new Rational( n.Pow( pow ), d.Pow( pow ) ) + }; + + public override string ToString() => d == 1 ? n.ToString() : $"{n}/{d}"; + + // type casting + public static implicit operator Rational( int n ) => new(n, 1); + public static explicit operator float( Rational r ) => (float)r.n / r.d; + public static explicit operator double( Rational r ) => (double)r.n / r.d; + + // unary operations + public static Rational operator -( Rational r ) => checked( new(-r.n, r.d) ); + public static Rational operator +( Rational r ) => r; + + // addition + public static Rational operator +( Rational a, Rational b ) => checked( new(a.n * b.d + a.d * b.n, a.d * b.d) ); + public static Rational operator +( Rational a, int b ) => checked( new(a.n + a.d * b, a.d) ); + public static Rational operator +( int a, Rational b ) => checked( new(a * b.d + b.n, b.d) ); + + // subtraction + public static Rational operator -( Rational a, Rational b ) => checked( new(a.n * b.d - a.d * b.n, a.d * b.d) ); + public static Rational operator -( Rational a, int b ) => checked( new(a.n - a.d * b, a.d) ); + public static Rational operator -( int a, Rational b ) => checked( new(a * b.d - b.n, b.d) ); + + // multiplication + public static Rational operator *( Rational a, Rational b ) => checked( new(a.n * b.n, a.d * b.d) ); + public static Rational operator *( Rational a, int b ) => checked( new(a.n * b, a.d) ); + public static Rational operator *( int a, Rational b ) => checked( new(b.n * a, b.d) ); + + // division + public static Rational operator /( Rational a, Rational b ) => checked( new(a.n * b.d, a.d * b.n) ); + public static Rational operator /( Rational a, int b ) => checked( new(a.n, a.d * b) ); + public static Rational operator /( int a, Rational b ) => checked( new(a * b.d, b.n) ); + + // comparison operators + public static bool operator ==( Rational a, Rational b ) => a.CompareTo( b ) == 0; + public static bool operator !=( Rational a, Rational b ) => a.CompareTo( b ) != 0; + public static bool operator <( Rational a, Rational b ) => a.CompareTo( b ) < 0; + public static bool operator >( Rational a, Rational b ) => a.CompareTo( b ) > 0; + public static bool operator <=( Rational a, Rational b ) => a.CompareTo( b ) <= 0; + public static bool operator >=( Rational a, Rational b ) => a.CompareTo( b ) >= 0; + + // comparison functions + public int CompareTo( Rational other ) => checked( ( n * other.d ).CompareTo( d * other.n ) ); + public bool Equals( Rational other ) => n == other.n && d == other.d; + public override bool Equals( object obj ) => obj is Rational other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( n, d ); + + } + +} \ No newline at end of file From 9bebc06d5a89f281f14108056ae9edc84bbe1924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 15:26:43 +0200 Subject: [PATCH 067/301] added Gcd --- Mathfs.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Mathfs.cs b/Mathfs.cs index dbb2be4..3b126b6 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -601,6 +601,14 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => return 1f - Abs( 2 * ( x - Floor( x ) ) - 1 ); } + /// Returns the greatest common divisor of the two numbers + public static int Gcd( int a, int b ) { + ( a, b ) = ( Mathf.Abs( a ), Mathf.Abs( b ) ); + while( a != 0 && b != 0 ) + _ = a > b ? a %= b : b %= a; + return a | b; + } + #endregion #region Smoothing & Easing Curves From da8213b1f607f397394cee2fbce89b32c382931d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Jun 2022 15:26:58 +0200 Subject: [PATCH 068/301] int.Pow(int) extension method --- Extensions.cs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Extensions.cs b/Extensions.cs index 2277b7f..3efcf13 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -1,9 +1,9 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using System.Runtime.CompilerServices; using UnityEngine; - namespace Freya { /// Various extensions for floats, vectors and colors @@ -225,6 +225,35 @@ public static Rect Encapsulate( this Rect r, Vector2 p ) { /// [MethodImpl( INLINE )] public static float Pow( this float value, float exponent ) => Mathfs.Pow( value, exponent ); + /// Calculates exact positive integer powers + /// + /// A positive integer power + [MethodImpl( INLINE )] public static int Pow( this int value, int pow ) { + if( pow < 0 ) + throw new ArithmeticException( "int.Pow(int) doesn't support negative powers" ); + checked { + switch( pow ) { + case 0: return 1; + case 1: return value; + case 2: return value * value; + case 3: return value * value * value; + default: + if( value == 2 ) + return 1 << pow; + // from: https://stackoverflow.com/questions/383587/how-do-you-do-integer-exponentiation-in-c + int ret = 1; + while( pow != 0 ) { + if( ( pow & 1 ) == 1 ) + ret *= value; + value *= value; + pow >>= 1; + } + + return ret; + } + } + } + #endregion #region Absolute Values From c94030399cc853df63c0c367696ff2cda00342b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 6 Jun 2022 18:08:02 +0200 Subject: [PATCH 069/301] made Mathfs.Gcd work with int.MinValue --- Mathfs.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Mathfs.cs b/Mathfs.cs index 3b126b6..a22b052 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -603,6 +603,16 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => /// Returns the greatest common divisor of the two numbers public static int Gcd( int a, int b ) { + // special case bc we can't negate int.MinValue + if( a == int.MinValue || b == int.MinValue ) { + if( a == int.MinValue && b == int.MinValue ) + return int.MinValue; // the only negative return value, bc we can't negate this number + int v = Mathf.Max( a, b ).Abs(); + return v & -v; + } + + if( a == b ) + return a.Abs(); ( a, b ) = ( Mathf.Abs( a ), Mathf.Abs( b ) ); while( a != 0 && b != 0 ) _ = a > b ? a %= b : b %= a; From 23ea596ac139b91c55ea8bd6a1db6c2a3a64fd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 6 Jun 2022 18:08:18 +0200 Subject: [PATCH 070/301] Rational constructor formatting --- Rational.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Rational.cs b/Rational.cs index 89001a3..ef1fb0d 100644 --- a/Rational.cs +++ b/Rational.cs @@ -22,10 +22,14 @@ namespace Freya { /// The numerator of this number /// The denominator of this number public Rational( int num, int den ) { - int sign = Mathfs.Sign( den ); // used to ensure only the numerator carries the sign - int gcd = Mathfs.Gcd( num, den ); // used to simplify the expression - n = sign * num / gcd; - d = sign * den / gcd; + // ensure only the numerator carries the sign + int sign = Mathfs.Sign( den ); + n = sign * num; + d = sign * den; + // simplify the expression + int gcd = Mathfs.Gcd( num, den ); + n /= gcd; + d /= gcd; } /// Returns the reciprocal of this number From 514cb0f08a9465ebf13b8c18d05e75c4dad88574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 09:47:22 +0200 Subject: [PATCH 071/301] min/max/abs/lerp/invlerp for Rational --- Rational.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Rational.cs b/Rational.cs index ef1fb0d..5221aba 100644 --- a/Rational.cs +++ b/Rational.cs @@ -35,6 +35,9 @@ public Rational( int num, int den ) { /// Returns the reciprocal of this number public Rational Reciprocal => new(d, n); + /// Returns the absolute value of this number + public Rational Abs() => new(n.Abs(), d); + /// Returns this number to the power of another integer pow /// The power to raise this number by public Rational Pow( int pow ) => @@ -47,6 +50,12 @@ public Rational Pow( int pow ) => }; public override string ToString() => d == 1 ? n.ToString() : $"{n}/{d}"; + + // statics + public static Rational Min( Rational a, Rational b ) => a < b ? a : b; + public static Rational Max( Rational a, Rational b ) => a > b ? a : b; + public static Rational Lerp( Rational a, Rational b, Rational t ) => a + t * ( b - a ); + public static Rational InverseLerp( Rational a, Rational b, Rational v ) => ( v - a ) / ( b - a ); // type casting public static implicit operator Rational( int n ) => new(n, 1); From 17b50637e6272d09790218da9e852ae521c53487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 09:47:38 +0200 Subject: [PATCH 072/301] Rational constructor optimizations --- Rational.cs | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/Rational.cs b/Rational.cs index 5221aba..14ebe48 100644 --- a/Rational.cs +++ b/Rational.cs @@ -22,14 +22,29 @@ namespace Freya { /// The numerator of this number /// The denominator of this number public Rational( int num, int den ) { - // ensure only the numerator carries the sign - int sign = Mathfs.Sign( den ); - n = sign * num; - d = sign * den; - // simplify the expression - int gcd = Mathfs.Gcd( num, den ); - n /= gcd; - d /= gcd; + switch( den ) { + case -1: + ( n, d ) = ( -num, -den ); + break; + case 0: throw new DivideByZeroException( "The denominator can't be 0" ); + case 1: + ( n, d ) = ( num, den ); + break; + default: + // ensure only the numerator carries the sign + int sign = Mathfs.Sign( den ); + n = sign * num; + d = sign * den; + + if( n is -1 or 1 ) + break; // no reduction needed + + // in this case, we have to try simplifying the expression + int gcd = Mathfs.Gcd( num, den ); + n /= gcd; + d /= gcd; + break; + } } /// Returns the reciprocal of this number From feec4e3e7327c8cb6a2d8572e54b5ab448941308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 09:47:57 +0200 Subject: [PATCH 073/301] rational float mult & div operators --- Rational.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Rational.cs b/Rational.cs index 14ebe48..d7d79ef 100644 --- a/Rational.cs +++ b/Rational.cs @@ -95,11 +95,15 @@ public Rational Pow( int pow ) => public static Rational operator *( Rational a, Rational b ) => checked( new(a.n * b.n, a.d * b.d) ); public static Rational operator *( Rational a, int b ) => checked( new(a.n * b, a.d) ); public static Rational operator *( int a, Rational b ) => checked( new(b.n * a, b.d) ); + public static float operator *( Rational a, float b ) => ( a.n * b ) / a.d; + public static float operator *( float a, Rational b ) => ( b.n * a ) / b.d; // division public static Rational operator /( Rational a, Rational b ) => checked( new(a.n * b.d, a.d * b.n) ); public static Rational operator /( Rational a, int b ) => checked( new(a.n, a.d * b) ); public static Rational operator /( int a, Rational b ) => checked( new(a * b.d, b.n) ); + public static float operator /( Rational a, float b ) => a.n / ( a.d * b ); + public static float operator /( float a, Rational b ) => ( a * b.d ) / b.n; // comparison operators public static bool operator ==( Rational a, Rational b ) => a.CompareTo( b ) == 0; From 98010566792939e9e00352e6f3299667fbf00c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 09:54:37 +0200 Subject: [PATCH 074/301] matrix refactors CharMatrix types replaced with static functions and RationalMatrix4x4 etc --- Codegen/Editor/MathfsCodegen.cs | 2 +- Curves/Polynomial.cs | 8 + Extensions.cs | 42 +++ RationalMatrix3x3.cs | 160 ++++++++++++ RationalMatrix4x4.cs | 241 ++++++++++++++++++ Splines/CharMatrix.cs | 209 +++------------ Splines/SplineUtils.cs | 21 +- .../Uniform Spline Segments/BezierCubic1D.cs | 2 +- .../Uniform Spline Segments/BezierCubic2D.cs | 4 +- .../Uniform Spline Segments/BezierCubic3D.cs | 2 +- .../Uniform Spline Segments/CatRomCubic1D.cs | 2 +- .../Uniform Spline Segments/CatRomCubic2D.cs | 2 +- .../Uniform Spline Segments/CatRomCubic3D.cs | 2 +- .../Uniform Spline Segments/HermiteCubic1D.cs | 2 +- .../Uniform Spline Segments/HermiteCubic2D.cs | 2 +- .../Uniform Spline Segments/HermiteCubic3D.cs | 2 +- Splines/Uniform Spline Segments/UBSCubic1D.cs | 2 +- Splines/Uniform Spline Segments/UBSCubic2D.cs | 2 +- Splines/Uniform Spline Segments/UBSCubic3D.cs | 2 +- 19 files changed, 522 insertions(+), 187 deletions(-) create mode 100644 RationalMatrix3x3.cs create mode 100644 RationalMatrix4x4.cs diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 230a257..eee166f 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -209,7 +209,7 @@ static void GenerateType( SplineType type, int dim ) { code.Append( "return; // no need to update" ); code.Append( "validCoefficients = true;" ); // todo: unroll matrix multiply for performance - code.Append( $"curve = CharMatrix.{type.matrixName}.{curveFunc}( {string.Join( ", ", points )} );" ); + code.Append( $"curve = CharMatrix.GetSplinePolynomial( CharMatrix.{type.matrixName}, {string.Join( ", ", points )} );" ); } // equality checks diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index 5854268..fe51e23 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -59,6 +59,14 @@ public float this[ int degree ] { /// The cubic coefficient public Polynomial( float c0, float c1, float c2, float c3 ) => ( this.c0, this.c1, this.c2, this.c3 ) = ( c0, c1, c2, c3 ); + /// Creates a polynomial up to a cubic + /// The coefficients to use (x = constant, y = linear, z = quadratic, w = cubic) + public Polynomial( Vector4 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.x, coefficients.y, coefficients.z, coefficients.w ); + + /// Creates a polynomial up to a cubic + /// The coefficients to use (c0 = constant, c1 = linear, c2 = quadratic, c3 = cubic) + public Polynomial( (float c0, float c1, float c2, float c3) coefficients ) => ( c0, c1, c2, c3 ) = coefficients; + /// Evaluates the polynomial at the given value t /// The value to sample at public float Eval( float t ) => c3 * ( t * t * t ) + c2 * ( t * t ) + c1 * t + c0; diff --git a/Extensions.cs b/Extensions.cs index 3efcf13..aebeae2 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -203,6 +203,48 @@ public static Rect Encapsulate( this Rect r, Vector2 p ) { #endregion + #region String extensions + + public static string ToValueTableString( this string[,] m ) { + int rowCount = m.GetLength( 0 ); + int colCount = m.GetLength( 1 ); + string[] r = new string[rowCount]; + for( int i = 0; i < rowCount; i++ ) + r[i] = ""; + + for( int c = 0; c < colCount; c++ ) { + string endBit = c == colCount - 1 ? "" : ", "; + + int colWidth = 4; // min width + string[] columnEntries = new string[rowCount]; + for( int row = 0; row < rowCount; row++ ) { + string s = m[row, c].StartsWith( '-' ) ? "" : " "; + columnEntries[row] = $"{s}{m[row, c]}{endBit}"; + colWidth = Mathfs.Max( colWidth, columnEntries[row].Length ); + } + + for( int row = 0; row < rowCount; row++ ) { + r[row] += columnEntries[row].PadRight( colWidth, ' ' ); + } + } + + return string.Join( '\n', r ); + } + + #endregion + + #region Matrix extensions + + public static Vector4 MultiplyColumnVector( this Matrix4x4 m, Vector4 v ) => + new Vector4( + Vector4.Dot( m.GetRow( 0 ), v ), + Vector4.Dot( m.GetRow( 1 ), v ), + Vector4.Dot( m.GetRow( 2 ), v ), + Vector4.Dot( m.GetRow( 3 ), v ) + ); + + #endregion + #region Extension method counterparts of the static Mathfs functions - lots of boilerplate in here #region Math operations diff --git a/RationalMatrix3x3.cs b/RationalMatrix3x3.cs new file mode 100644 index 0000000..54bf569 --- /dev/null +++ b/RationalMatrix3x3.cs @@ -0,0 +1,160 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + /// A 4x4 matrix using exact rational number representation + public struct RationalMatrix3x3 { + + public Rational m00, m01, m02; + public Rational m10, m11, m12; + public Rational m20, m21, m22; + + public RationalMatrix3x3( Rational m00, Rational m01, Rational m02, Rational m10, Rational m11, Rational m12, Rational m20, Rational m21, Rational m22 ) { + ( this.m00, this.m01, this.m02 ) = ( m00, m01, m02 ); + ( this.m10, this.m11, this.m12 ) = ( m10, m11, m12 ); + ( this.m20, this.m21, this.m22 ) = ( m20, m21, m22 ); + } + + public Rational this[ int row, int column ] { + get { + return ( row, column ) switch { + (0, 0) => m00, + (0, 1) => m01, + (0, 2) => m02, + (1, 0) => m10, + (1, 1) => m11, + (1, 2) => m12, + (2, 0) => m20, + (2, 1) => m21, + (2, 2) => m22, + _ => throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 3, got: ({row},{column})" ) + }; + } + set { + switch( ( row, column ) ) { + case (0, 0): + m00 = value; + break; + case (0, 1): + m01 = value; + break; + case (0, 2): + m02 = value; + break; + case (1, 0): + m10 = value; + break; + case (1, 1): + m11 = value; + break; + case (1, 2): + m12 = value; + break; + case (2, 0): + m20 = value; + break; + case (2, 1): + m21 = value; + break; + case (2, 2): + m22 = value; + break; + default: throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 3, got: ({row},{column})" ); + } + } + } + + /// Returns the inverse of this matrix. Throws a division by zero exception if it's not invertible + public RationalMatrix3x3 Inverse { + get { + Rational A1212 = m11 * m22 - m12 * m21; + Rational A0212 = m10 * m22 - m12 * m20; + Rational A0112 = m10 * m21 - m11 * m20; + Rational det = m00 * A1212 - m01 * A0212 + m02 * A0112; + + if( det == Rational.Zero ) + throw new DivideByZeroException( "The matrix is not invertible - its determinant is 0" ); + + return new RationalMatrix3x3( + A1212, m02 * m21 - m01 * m22, m01 * m12 - m02 * m11, + -A0212, m00 * m22 - m02 * m20, m10 * m02 - m00 * m12, + A0112, m20 * m01 - m00 * m21, m00 * m11 - m10 * m01 + ) / det; + } + } + + /// Returns the determinant of this matrix + public Rational Determinant { + get { + Rational A1212 = m11 * m22 - m12 * m21; + Rational A0212 = m10 * m22 - m12 * m20; + Rational A0112 = m10 * m21 - m11 * m20; + return m00 * A1212 - m01 * A0212 + m02 * A0112; + } + } + + public override string ToString() => ToStringMatrix().ToValueTableString(); + + public string[,] ToStringMatrix() { + return new[,] { + { m00.ToString(), m01.ToString(), m02.ToString() }, + { m10.ToString(), m11.ToString(), m12.ToString() }, + { m20.ToString(), m21.ToString(), m22.ToString() } + }; + } + + public static RationalMatrix3x3 operator *( RationalMatrix3x3 c, Rational v ) => + new(c.m00 * v, c.m01 * v, c.m02 * v, + c.m10 * v, c.m11 * v, c.m12 * v, + c.m20 * v, c.m21 * v, c.m22 * v); + + public static RationalMatrix3x3 operator /( RationalMatrix3x3 c, Rational v ) => c * v.Reciprocal; + + public static RationalMatrix3x3 operator *( RationalMatrix3x3 a, RationalMatrix3x3 b ) { + Rational GetEntry( int r, int c ) => a[r, 0] * b[0, c] + a[r, 1] * b[1, c] + a[r, 2] * b[2, c] + a[r, 3] * b[3, c]; + + return new RationalMatrix3x3( + GetEntry( 0, 0 ), GetEntry( 0, 1 ), GetEntry( 0, 2 ), + GetEntry( 1, 0 ), GetEntry( 1, 1 ), GetEntry( 1, 2 ), + GetEntry( 2, 0 ), GetEntry( 2, 1 ), GetEntry( 2, 2 ) + ); + } + + /// + public Polynomial GetEvalPolynomial( float p0, float p1, float p2 ) => + Polynomial.Quadratic( + p0 * m00 + p1 * m01 + p2 * m02, + p0 * m10 + p1 * m11 + p2 * m12, + p0 * m20 + p1 * m21 + p2 * m22 + ); + + /// + public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2 ) => + new( + GetEvalPolynomial( p0.x, p1.x, p2.x ), + GetEvalPolynomial( p0.y, p1.y, p2.y ) + ); + + /// + public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2 ) => + new( + GetEvalPolynomial( p0.x, p1.x, p2.x ), + GetEvalPolynomial( p0.y, p1.y, p2.y ), + GetEvalPolynomial( p0.z, p1.z, p2.z ) + ); + + /// + public Polynomial GetBasisFunction( int i ) { + return i switch { + 0 => Polynomial.Quadratic( (float)m00, (float)m10, (float)m20 ), + 1 => Polynomial.Quadratic( (float)m01, (float)m11, (float)m21 ), + 2 => Polynomial.Quadratic( (float)m02, (float)m12, (float)m22 ), + _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 2" ) + }; + } + } + +} \ No newline at end of file diff --git a/RationalMatrix4x4.cs b/RationalMatrix4x4.cs new file mode 100644 index 0000000..a4129fe --- /dev/null +++ b/RationalMatrix4x4.cs @@ -0,0 +1,241 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + /// A 4x4 matrix using exact rational number representation + public struct RationalMatrix4x4 { + + public Rational m00, m01, m02, m03; + public Rational m10, m11, m12, m13; + public Rational m20, m21, m22, m23; + public Rational m30, m31, m32, m33; + + public RationalMatrix4x4( Rational m00, Rational m01, Rational m02, Rational m03, Rational m10, Rational m11, Rational m12, Rational m13, Rational m20, Rational m21, Rational m22, Rational m23, Rational m30, Rational m31, Rational m32, Rational m33 ) { + ( this.m00, this.m01, this.m02, this.m03 ) = ( m00, m01, m02, m03 ); + ( this.m10, this.m11, this.m12, this.m13 ) = ( m10, m11, m12, m13 ); + ( this.m20, this.m21, this.m22, this.m23 ) = ( m20, m21, m22, m23 ); + ( this.m30, this.m31, this.m32, this.m33 ) = ( m30, m31, m32, m33 ); + } + + public Rational this[ int row, int column ] { + get { + return ( row, column ) switch { + (0, 0) => m00, + (0, 1) => m01, + (0, 2) => m02, + (0, 3) => m03, + (1, 0) => m10, + (1, 1) => m11, + (1, 2) => m12, + (1, 3) => m13, + (2, 0) => m20, + (2, 1) => m21, + (2, 2) => m22, + (2, 3) => m23, + (3, 0) => m30, + (3, 1) => m31, + (3, 2) => m32, + (3, 3) => m33, + _ => throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 3, got: ({row},{column})" ) + }; + } + set { + switch( ( row, column ) ) { + case (0, 0): + m00 = value; + break; + case (0, 1): + m01 = value; + break; + case (0, 2): + m02 = value; + break; + case (0, 3): + m03 = value; + break; + case (1, 0): + m10 = value; + break; + case (1, 1): + m11 = value; + break; + case (1, 2): + m12 = value; + break; + case (1, 3): + m13 = value; + break; + case (2, 0): + m20 = value; + break; + case (2, 1): + m21 = value; + break; + case (2, 2): + m22 = value; + break; + case (2, 3): + m23 = value; + break; + case (3, 0): + m30 = value; + break; + case (3, 1): + m31 = value; + break; + case (3, 2): + m32 = value; + break; + case (3, 3): + m33 = value; + break; + default: throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 3, got: ({row},{column})" ); + } + } + } + + /// Returns the inverse of this matrix. Throws a division by zero exception if it's not invertible + public RationalMatrix4x4 Inverse { + get { + // source: https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix + Rational A2323 = m22 * m33 - m23 * m32; + Rational A1323 = m21 * m33 - m23 * m31; + Rational A1223 = m21 * m32 - m22 * m31; + Rational A0323 = m20 * m33 - m23 * m30; + Rational A0223 = m20 * m32 - m22 * m30; + Rational A0123 = m20 * m31 - m21 * m30; + Rational det = m00 * ( m11 * A2323 - m12 * A1323 + m13 * A1223 ) + - m01 * ( m10 * A2323 - m12 * A0323 + m13 * A0223 ) + + m02 * ( m10 * A1323 - m11 * A0323 + m13 * A0123 ) + - m03 * ( m10 * A1223 - m11 * A0223 + m12 * A0123 ); + + if( det == Rational.Zero ) + throw new DivideByZeroException( "The matrix is not invertible - its determinant is 0" ); + + Rational A2313 = m12 * m33 - m13 * m32; + Rational A1313 = m11 * m33 - m13 * m31; + Rational A1213 = m11 * m32 - m12 * m31; + Rational A2312 = m12 * m23 - m13 * m22; + Rational A1312 = m11 * m23 - m13 * m21; + Rational A1212 = m11 * m22 - m12 * m21; + Rational A0313 = m10 * m33 - m13 * m30; + Rational A0213 = m10 * m32 - m12 * m30; + Rational A0312 = m10 * m23 - m13 * m20; + Rational A0212 = m10 * m22 - m12 * m20; + Rational A0113 = m10 * m31 - m11 * m30; + Rational A0112 = m10 * m21 - m11 * m20; + + return new RationalMatrix4x4( + ( m11 * A2323 - m12 * A1323 + m13 * A1223 ), -( m01 * A2323 - m02 * A1323 + m03 * A1223 ), ( m01 * A2313 - m02 * A1313 + m03 * A1213 ), -( m01 * A2312 - m02 * A1312 + m03 * A1212 ), + -( m10 * A2323 - m12 * A0323 + m13 * A0223 ), ( m00 * A2323 - m02 * A0323 + m03 * A0223 ), -( m00 * A2313 - m02 * A0313 + m03 * A0213 ), ( m00 * A2312 - m02 * A0312 + m03 * A0212 ), + ( m10 * A1323 - m11 * A0323 + m13 * A0123 ), -( m00 * A1323 - m01 * A0323 + m03 * A0123 ), ( m00 * A1313 - m01 * A0313 + m03 * A0113 ), -( m00 * A1312 - m01 * A0312 + m03 * A0112 ), + -( m10 * A1223 - m11 * A0223 + m12 * A0123 ), ( m00 * A1223 - m01 * A0223 + m02 * A0123 ), -( m00 * A1213 - m01 * A0213 + m02 * A0113 ), ( m00 * A1212 - m01 * A0212 + m02 * A0112 ) + ) / det; + } + } + + /// Returns the determinant of this matrix + public Rational Determinant { + get { + // source: https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix + Rational A2323 = m22 * m33 - m23 * m32; + Rational A1323 = m21 * m33 - m23 * m31; + Rational A1223 = m21 * m32 - m22 * m31; + Rational A0323 = m20 * m33 - m23 * m30; + Rational A0223 = m20 * m32 - m22 * m30; + Rational A0123 = m20 * m31 - m21 * m30; + return m00 * ( m11 * A2323 - m12 * A1323 + m13 * A1223 ) + - m01 * ( m10 * A2323 - m12 * A0323 + m13 * A0223 ) + + m02 * ( m10 * A1323 - m11 * A0323 + m13 * A0123 ) + - m03 * ( m10 * A1223 - m11 * A0223 + m12 * A0123 ); + } + } + + public override string ToString() => ToStringMatrix().ToValueTableString(); + + public string[,] ToStringMatrix() { + return new[,] { + { m00.ToString(), m01.ToString(), m02.ToString(), m03.ToString() }, + { m10.ToString(), m11.ToString(), m12.ToString(), m13.ToString() }, + { m20.ToString(), m21.ToString(), m22.ToString(), m23.ToString() }, + { m30.ToString(), m31.ToString(), m32.ToString(), m33.ToString() } + }; + } + + public static RationalMatrix4x4 operator *( RationalMatrix4x4 c, Rational v ) => + new(c.m00 * v, c.m01 * v, c.m02 * v, c.m03 * v, + c.m10 * v, c.m11 * v, c.m12 * v, c.m13 * v, + c.m20 * v, c.m21 * v, c.m22 * v, c.m23 * v, + c.m30 * v, c.m31 * v, c.m32 * v, c.m33 * v); + + + public static RationalMatrix4x4 operator /( RationalMatrix4x4 c, Rational v ) => c * v.Reciprocal; + + public static RationalMatrix4x4 operator *( RationalMatrix4x4 a, RationalMatrix4x4 b ) { + Rational GetEntry( int r, int c ) => a[r, 0] * b[0, c] + a[r, 1] * b[1, c] + a[r, 2] * b[2, c] + a[r, 3] * b[3, c]; + + return new RationalMatrix4x4( + GetEntry( 0, 0 ), GetEntry( 0, 1 ), GetEntry( 0, 2 ), GetEntry( 0, 3 ), + GetEntry( 1, 0 ), GetEntry( 1, 1 ), GetEntry( 1, 2 ), GetEntry( 1, 3 ), + GetEntry( 2, 0 ), GetEntry( 2, 1 ), GetEntry( 2, 2 ), GetEntry( 2, 3 ), + GetEntry( 3, 0 ), GetEntry( 3, 1 ), GetEntry( 3, 2 ), GetEntry( 3, 3 ) + ); + } + + /// Multiplies this characteristic matrix C by a column matrix: C*[p0,p1,p2,p3]^T + /// The first entry of the column matrix + /// The second entry of the column matrix + /// The third entry of the column matrix + /// The fourth entry of the column matrix + public (float, float, float, float) MultiplyColumnVec( float p0, float p1, float p2, float p3 ) => + ( + p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03, + p0 * m10 + p1 * m11 + p2 * m12 + p3 * m13, + p0 * m20 + p1 * m21 + p2 * m22 + p3 * m23, + p0 * m30 + p1 * m31 + p2 * m32 + p3 * m33 + ); + + /// + public (Vector2, Vector2, Vector2, Vector2) MultiplyColumnVec( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { + ( float x0, float x1, float x2, float x3 ) = MultiplyColumnVec( p0.x, p1.x, p2.x, p3.x ); + ( float y0, float y1, float y2, float y3 ) = MultiplyColumnVec( p0.y, p1.y, p2.y, p3.y ); + return ( + new Vector2( x0, y0 ), + new Vector2( x1, y1 ), + new Vector2( x2, y2 ), + new Vector2( x3, y3 ) + ); + } + + /// + public (Vector3, Vector3, Vector3, Vector3) MultiplyColumnVec( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { + ( float x0, float x1, float x2, float x3 ) = MultiplyColumnVec( p0.x, p1.x, p2.x, p3.x ); + ( float y0, float y1, float y2, float y3 ) = MultiplyColumnVec( p0.y, p1.y, p2.y, p3.y ); + ( float z0, float z1, float z2, float z3 ) = MultiplyColumnVec( p0.z, p1.z, p2.z, p3.z ); + return ( + new Vector3( x0, y0, z0 ), + new Vector3( x1, y1, z1 ), + new Vector3( x2, y2, z2 ), + new Vector3( x3, y3, z3 ) + ); + } + + /// Returns the basis function (weight) for the given point by index i, + /// equal to the t-matrix multiplied by the characteristic matrix + /// The point index to get the basis function of + public Polynomial GetBasisFunction( int i ) { + return i switch { + 0 => new Polynomial( (float)m00, (float)m10, (float)m20, (float)m30 ), + 1 => new Polynomial( (float)m01, (float)m11, (float)m21, (float)m31 ), + 2 => new Polynomial( (float)m02, (float)m12, (float)m22, (float)m32 ), + 3 => new Polynomial( (float)m03, (float)m13, (float)m23, (float)m33 ), + _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) + }; + } + + } + +} \ No newline at end of file diff --git a/Splines/CharMatrix.cs b/Splines/CharMatrix.cs index 0e43c32..23f35c6 100644 --- a/Splines/CharMatrix.cs +++ b/Splines/CharMatrix.cs @@ -1,223 +1,98 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -using System; using UnityEngine; namespace Freya { - - public readonly struct CharMatrix { + public static class CharMatrix { /// The characteristic matrix of a quadratic bézier curve - public static readonly CharMatrix3x3 quadraticBezier = new( + public static readonly RationalMatrix3x3 quadraticBezier = new( 1, 0, 0, -2, 2, 0, 1, -2, 1 ); /// The characteristic matrix of a cubic bézier curve - public static readonly CharMatrix4x4 cubicBezier = new( + public static readonly RationalMatrix4x4 cubicBezier = new( 1, 0, 0, 0, -3, 3, 0, 0, 3, -6, 3, 0, -1, 3, -3, 1 ); - public static readonly CharMatrix4x4 cubicBezierInverse = new CharMatrix4x4( - 3, 0, 0, 0, - 3, 1, 0, 0, - 3, 2, 1, 0, - 3, 3, 3, 3 - ) / 3; - /// The characteristic matrix of a uniform cubic hermite curve - public static readonly CharMatrix4x4 cubicHermite = new( + public static readonly RationalMatrix4x4 cubicHermite = new( 1, 0, 0, 0, 0, 1, 0, 0, -3, -2, 3, -1, 2, 1, -2, 1 ); - public static readonly CharMatrix4x4 cubicHermiteInverse = new( - 1, 0, 0, 0, - 0, 1, 0, 0, - 1, 1, 1, 1, - 0, 1, 2, 3 - ); - /// The characteristic matrix of a uniform cubic catmull-rom curve - public static readonly CharMatrix4x4 cubicCatmullRom = new CharMatrix4x4( + public static readonly RationalMatrix4x4 cubicCatmullRom = new RationalMatrix4x4( 0, 2, 0, 0, -1, 0, 1, 0, 2, -5, 4, -1, -1, 3, -3, 1 ) / 2; - public static readonly CharMatrix4x4 cubicCatmullRomInverse = new CharMatrix4x4( - 1, -1, 1, 1, - 1, 0, 0, 0, - 1, 1, 1, 1, - 1, 2, 4, 6 - ); - - /// The characteristic matrix of a uniform cubic B-spline segment - public static readonly CharMatrix4x4 cubicUniformBspline = new CharMatrix4x4( + /// The characteristic matrix of a uniform cubic B-spline curve + public static readonly RationalMatrix4x4 cubicUniformBspline = new RationalMatrix4x4( 1, 4, 1, 0, -3, 0, 3, 0, 3, -6, 3, 0, -1, 3, -3, 1 ) / 6; - public static readonly CharMatrix4x4 cubicUniformBsplineInverse = new CharMatrix4x4( - 3, -3, 2, 0, - 3, 0, -1, 0, - 3, 3, 2, 0, - 3, 6, 11, 18 - ) / 3; - - } + /// The inverse characteristic matrix of a cubic bézier curve + public static readonly RationalMatrix4x4 cubicBezierInverse = cubicBezier.Inverse; - /// Data structure representing a cubic characteristic matrix with 4 points. Used for spline evaluation - public readonly struct CharMatrix4x4 { - public readonly float m00, m01, m02, m03; - public readonly float m10, m11, m12, m13; - public readonly float m20, m21, m22, m23; - public readonly float m30, m31, m32, m33; - - public CharMatrix4x4( float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33 ) { - ( this.m00, this.m01, this.m02, this.m03 ) = ( m00, m01, m02, m03 ); - ( this.m10, this.m11, this.m12, this.m13 ) = ( m10, m11, m12, m13 ); - ( this.m20, this.m21, this.m22, this.m23 ) = ( m20, m21, m22, m23 ); - ( this.m30, this.m31, this.m32, this.m33 ) = ( m30, m31, m32, m33 ); - } - - /// Returns the basis function (weight) for the given point by index i, - /// equal to the t-matrix multiplied by the characteristic matrix - /// The point index to get the basis function of - public Polynomial GetBasisFunction( int i ) { - return i switch { - 0 => new Polynomial( m00, m10, m20, m30 ), - 1 => new Polynomial( m01, m11, m21, m31 ), - 2 => new Polynomial( m02, m12, m22, m32 ), - 3 => new Polynomial( m03, m13, m23, m33 ), - _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) - }; - } - - /// Returns the polynomial representing the charateristic matrix - /// multiplied by the input points, on a single axis - /// The value of the first point - /// The value of the second point - /// The value of the third point - /// The value of the fourth point - public Polynomial GetEvalPolynomial( float p0, float p1, float p2, float p3 ) => - new( - p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03, - p0 * m10 + p1 * m11 + p2 * m12 + p3 * m13, - p0 * m20 + p1 * m21 + p2 * m22 + p3 * m23, - p0 * m30 + p1 * m31 + p2 * m32 + p3 * m33 - ); + /// The characteristic matrix of a uniform cubic hermite curve + public static readonly RationalMatrix4x4 cubicHermiteInverse = cubicHermite.Inverse; - /// Returns the curve this characteristic matrix represents, given 4 points - /// The first point - /// The second point - /// The third point - /// The fourth point - public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => - new( - GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), - GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ) - ); + /// The characteristic matrix of a uniform cubic catmull-rom curve + public static readonly RationalMatrix4x4 cubicCatmullRomInverse = cubicCatmullRom.Inverse; - /// - public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => - new( - GetEvalPolynomial( p0.x, p1.x, p2.x, p3.x ), - GetEvalPolynomial( p0.y, p1.y, p2.y, p3.y ), - GetEvalPolynomial( p0.z, p1.z, p2.z, p3.z ) - ); + /// The characteristic matrix of a uniform cubic B-spline curve + public static readonly RationalMatrix4x4 cubicUniformBsplineInverse = cubicUniformBspline.Inverse; - /// Multiplies this characteristic matrix C by a column matrix: C*[p0,p1,p2,p3]^T - /// The first entry of the column matrix - /// The second entry of the column matrix - /// The third entry of the column matrix - /// The fourth entry of the column matrix - public (float, float, float, float) MultiplyColumnVec( float p0, float p1, float p2, float p3 ) => - ( - p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03, - p0 * m10 + p1 * m11 + p2 * m12 + p3 * m13, - p0 * m20 + p1 * m21 + p2 * m22 + p3 * m23, - p0 * m30 + p1 * m31 + p2 * m32 + p3 * m33 - ); + /// Returns the matrix to convert control points from one cubic spline to another, keeping the same curve intact + /// The characteristic matrix of the spline to convert from + /// The characteristic matrix of the spline to convert from + public static RationalMatrix4x4 GetConversionMatrix( RationalMatrix4x4 from, RationalMatrix4x4 to ) => to.Inverse * from; - /// - public (Vector2, Vector2, Vector2, Vector2) MultiplyColumnVec( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { - ( float x0, float x1, float x2, float x3 ) = MultiplyColumnVec( p0.x, p1.x, p2.x, p3.x ); - ( float y0, float y1, float y2, float y3 ) = MultiplyColumnVec( p0.y, p1.y, p2.y, p3.y ); - return ( - new Vector2( x0, y0 ), - new Vector2( x1, y1 ), - new Vector2( x2, y2 ), - new Vector2( x3, y3 ) + public static Matrix4x4 Create( float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33 ) => + new( + new Vector4( m00, m10, m20, m30 ), + new Vector4( m01, m11, m21, m31 ), + new Vector4( m02, m12, m22, m32 ), + new Vector4( m03, m13, m23, m33 ) ); - } - public static CharMatrix4x4 operator *( CharMatrix4x4 c, float v ) => - new(c.m00 * v, c.m01 * v, c.m02 * v, c.m03 * v, - c.m10 * v, c.m11 * v, c.m12 * v, c.m13 * v, - c.m20 * v, c.m21 * v, c.m22 * v, c.m23 * v, - c.m30 * v, c.m31 * v, c.m32 * v, c.m33 * v); + /// Returns the polynomial representing the cubic curve of a given characteristic matrix of a spline, given 4 control points + /// The characteristic matrix to use + /// The value of the first control point + /// The value of the second control point + /// The value of the third control point + /// The value of the fourth control point + public static Polynomial GetSplinePolynomial( RationalMatrix4x4 c, float p0, float p1, float p2, float p3 ) => new Polynomial( c.MultiplyColumnVec( p0, p1, p2, p3 ) ); - public static CharMatrix4x4 operator /( CharMatrix4x4 c, float v ) => c * ( 1f / v ); - - public override string ToString() => $"{m00},\t{m01},\t{m02},\t{m03}\n{m10},\t{m11},\t{m12},\t{m13}\n{m20},\t{m21},\t{m22},\t{m23}\n{m30},\t{m31},\t{m32},\t{m33}\n"; - - } - - /// Data structure representing a quadratic characteristic matrix with 3 points. Used for spline evaluation - public readonly struct CharMatrix3x3 { - public readonly float m00, m01, m02; - public readonly float m10, m11, m12; - public readonly float m20, m21, m22; - - public CharMatrix3x3( float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22 ) { - ( this.m00, this.m01, this.m02 ) = ( m00, m01, m02 ); - ( this.m10, this.m11, this.m12 ) = ( m10, m11, m12 ); - ( this.m20, this.m21, this.m22 ) = ( m20, m21, m22 ); - } - - /// - public Polynomial GetBasisFunction( int i ) { - return i switch { - 0 => Polynomial.Quadratic( m00, m10, m20 ), - 1 => Polynomial.Quadratic( m01, m11, m21 ), - 2 => Polynomial.Quadratic( m02, m12, m22 ), - _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 2" ) - }; - } - - /// - public Polynomial GetEvalPolynomial( float p0, float p1, float p2 ) => - Polynomial.Quadratic( - p0 * m00 + p1 * m01 + p2 * m02, - p0 * m10 + p1 * m11 + p2 * m12, - p0 * m20 + p1 * m21 + p2 * m22 - ); - - /// - public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2 ) => + /// + public static Polynomial2D GetSplinePolynomial( RationalMatrix4x4 c, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => new( - GetEvalPolynomial( p0.x, p1.x, p2.x ), - GetEvalPolynomial( p0.y, p1.y, p2.y ) + GetSplinePolynomial( c, p0.x, p1.x, p2.x, p3.x ), + GetSplinePolynomial( c, p0.y, p1.y, p2.y, p3.y ) ); - /// - public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2 ) => + /// + public static Polynomial3D GetSplinePolynomial( RationalMatrix4x4 c, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => new( - GetEvalPolynomial( p0.x, p1.x, p2.x ), - GetEvalPolynomial( p0.y, p1.y, p2.y ), - GetEvalPolynomial( p0.z, p1.z, p2.z ) + GetSplinePolynomial( c, p0.x, p1.x, p2.x, p3.x ), + GetSplinePolynomial( c, p0.y, p1.y, p2.y, p3.y ), + GetSplinePolynomial( c, p0.z, p1.z, p2.z, p3.z ) ); + } } \ No newline at end of file diff --git a/Splines/SplineUtils.cs b/Splines/SplineUtils.cs index fa8c6b7..83fbbc6 100644 --- a/Splines/SplineUtils.cs +++ b/Splines/SplineUtils.cs @@ -74,7 +74,7 @@ public static (float, float, float, float) CalcCatRomKnots( Vector3 p0, Vector3 return ( k0, k1, k2, k3 ); } - static CharMatrix4x4 GetNUCatRomCharMatrix( float k0, float k1, float k2, float k3 ) { + static Matrix4x4 GetNUCatRomCharMatrix( float k0, float k1, float k2, float k3 ) { if( k1 == 0f && k2 == 1f ) return GetNUCatRomCharMatrixUnitInterval( k0, k3 ); float k1k1 = k1 * k1; @@ -133,7 +133,7 @@ static CharMatrix4x4 GetNUCatRomCharMatrix( float k0, float k1, float k2, float float p1sc = 1f / ( i01 * i12sq * i13 ); float p2sc = 1f / ( i02 * i12sq * i23 ); float p3sc = 1f / ( i12 * i13 * i23 ); - return new CharMatrix4x4( + return CharMatrix.Create( p0sc * p0u0, p1sc * p1u0, p2sc * p2u0, p3sc * p3u0, p0sc * p0u1, p1sc * p1u1, p2sc * p2u1, p3sc * p3u1, p0sc * p0u2, p1sc * p1u2, p2sc * p2u2, p3sc * p3u2, @@ -141,7 +141,7 @@ static CharMatrix4x4 GetNUCatRomCharMatrix( float k0, float k1, float k2, float ); } - static CharMatrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { + static Matrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { float k0mk3 = k0 - k3; float k0m2k3 = k0mk3 - k3; float k0k3 = k0 * k3; @@ -162,7 +162,7 @@ static CharMatrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { float p2sc = 1f / ( i02 * i23 ); float p3sc = 1f / ( k3 * i23 ); - return new CharMatrix4x4( + return CharMatrix.Create( 0, 1, 0, 0, p0sc, p1sc * p1u1, p2sc * p2u1, 0, p0sc * -2, p1sc * p1u2, p2sc * p2u2, p3sc, @@ -171,11 +171,20 @@ static CharMatrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { } internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { - return GetNUCatRomCharMatrix( k0, k1, k2, k3 ).GetCurve( p0, p1, p2, p3 ); + Matrix4x4 m = GetNUCatRomCharMatrix( k0, k1, k2, k3 ); + return new Polynomial2D( + new Polynomial( m.MultiplyColumnVector( new Vector4( p0.x, p1.x, p2.x, p3.x ) ) ), + new Polynomial( m.MultiplyColumnVector( new Vector4( p0.y, p1.y, p2.y, p3.y ) ) ) + ); } internal static Polynomial3D CalculateCatRomCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float k0, float k1, float k2, float k3 ) { - return GetNUCatRomCharMatrix( k0, k1, k2, k3 ).GetCurve( p0, p1, p2, p3 ); + Matrix4x4 m = GetNUCatRomCharMatrix( k0, k1, k2, k3 ); + return new Polynomial3D( + new Polynomial( m.MultiplyColumnVector( new Vector4( p0.x, p1.x, p2.x, p3.x ) ) ), + new Polynomial( m.MultiplyColumnVector( new Vector4( p0.y, p1.y, p2.y, p3.y ) ) ), + new Polynomial( m.MultiplyColumnVector( new Vector4( p0.z, p1.z, p2.z, p3.z ) ) ) + ); } } diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index efe93f6..ab26894 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -94,7 +94,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicBezier.GetEvalPolynomial( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicBezier, p0, p1, p2, p3 ); } public static bool operator ==( BezierCubic1D a, BezierCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic1D a, BezierCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index c6c2d76..6aaec78 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -94,7 +94,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicBezier.GetCurve( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicBezier, p0, p1, p2, p3 ); } public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic2D a, BezierCubic2D b ) => !( a == b ); @@ -155,6 +155,7 @@ public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { Vector2 p = new Vector2( d.x + ( e.x - d.x ) * t, d.y + ( e.y - d.y ) * t ); + return ( new BezierCubic2D( p0, a, d, p ), new BezierCubic2D( p, e, c, p3 ) ); } public UBSCubic2D ToUniformCubicBSpline() { @@ -178,7 +179,6 @@ public CatRomCubic2D ToUniformCubicCatRom() { public HermiteCubic2D ToHermite() { // todo: channel split for performance return new HermiteCubic2D( p0, ( p1 - p0 ) * 3, p3, ( p3 - p2 ) * 3 ); - return ( new BezierCubic2D( p0, a, d, p ), new BezierCubic2D( p, e, c, p3 ) ); } } } diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index d7f01f3..863b061 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -94,7 +94,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicBezier.GetCurve( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicBezier, p0, p1, p2, p3 ); } public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic3D a, BezierCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index e63f946..4cc94d9 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -94,7 +94,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicCatmullRom.GetEvalPolynomial( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicCatmullRom, p0, p1, p2, p3 ); } public static bool operator ==( CatRomCubic1D a, CatRomCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic1D a, CatRomCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 238e8b5..9401dd3 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -94,7 +94,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicCatmullRom.GetCurve( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicCatmullRom, p0, p1, p2, p3 ); } public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 817f0ef..1e9c190 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -94,7 +94,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicCatmullRom.GetCurve( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicCatmullRom, p0, p1, p2, p3 ); } public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic3D a, CatRomCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index 3a3989e..69e3211 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -94,7 +94,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicHermite.GetEvalPolynomial( p0, v0, p1, v1 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicHermite, p0, v0, p1, v1 ); } public static bool operator ==( HermiteCubic1D a, HermiteCubic1D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; public static bool operator !=( HermiteCubic1D a, HermiteCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index d8e2d12..5e06e00 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -94,7 +94,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicHermite.GetCurve( p0, v0, p1, v1 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicHermite, p0, v0, p1, v1 ); } public BezierCubic2D ToBezier() => new BezierCubic2D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index a3f0cb1..18c863b 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -94,7 +94,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicHermite.GetCurve( p0, v0, p1, v1 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicHermite, p0, v0, p1, v1 ); } public static bool operator ==( HermiteCubic3D a, HermiteCubic3D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; public static bool operator !=( HermiteCubic3D a, HermiteCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index a6ba373..6b21b87 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -94,7 +94,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicUniformBspline.GetEvalPolynomial( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicUniformBspline, p0, p1, p2, p3 ); } public static bool operator ==( UBSCubic1D a, UBSCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( UBSCubic1D a, UBSCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index dca474d..6fd2085 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -94,7 +94,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicUniformBspline.GetCurve( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicUniformBspline, p0, p1, p2, p3 ); } /// Returns the exact cubic bézier representation of this segment diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index 5234873..4f69cd3 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -94,7 +94,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.cubicUniformBspline.GetCurve( p0, p1, p2, p3 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicUniformBspline, p0, p1, p2, p3 ); } public static bool operator ==( UBSCubic3D a, UBSCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( UBSCubic3D a, UBSCubic3D b ) => !( a == b ); From 33f7ffad67deee0e0c245785cb367450a23c51d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 10:16:20 +0200 Subject: [PATCH 075/301] made quadratic splines consistent w. the cubics --- Curves/Polynomial.cs | 4 ++++ RationalMatrix3x3.cs | 10 +++++++++ Splines/CharMatrix.cs | 21 ++++++++++++++++++- .../Uniform Spline Segments/BezierQuad1D.cs | 2 +- .../Uniform Spline Segments/BezierQuad2D.cs | 2 +- .../Uniform Spline Segments/BezierQuad3D.cs | 2 +- 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index fe51e23..a60b2f7 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -67,6 +67,10 @@ public float this[ int degree ] { /// The coefficients to use (c0 = constant, c1 = linear, c2 = quadratic, c3 = cubic) public Polynomial( (float c0, float c1, float c2, float c3) coefficients ) => ( c0, c1, c2, c3 ) = coefficients; + /// Creates a polynomial up to a quadratic + /// The coefficients to use (c0 = constant, c1 = linear, c2 = quadratic) + public Polynomial( (float c0, float c1, float c2) coefficients ) => _ = ( ( c0, c1, c2 ) = coefficients, c3 = 0 ); + /// Evaluates the polynomial at the given value t /// The value to sample at public float Eval( float t ) => c3 * ( t * t * t ) + c2 * ( t * t ) + c1 * t + c0; diff --git a/RationalMatrix3x3.cs b/RationalMatrix3x3.cs index 54bf569..de25f59 100644 --- a/RationalMatrix3x3.cs +++ b/RationalMatrix3x3.cs @@ -145,6 +145,16 @@ public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2 ) => GetEvalPolynomial( p0.y, p1.y, p2.y ), GetEvalPolynomial( p0.z, p1.z, p2.z ) ); + /// Multiplies this characteristic matrix C by a column matrix: C*[p0,p1,p2]^T + /// The first entry of the column matrix + /// The second entry of the column matrix + /// The third entry of the column matrix + public (float, float, float) MultiplyColumnVec( float p0, float p1, float p2 ) => + ( + p0 * m00 + p1 * m01 + p2 * m02, + p0 * m10 + p1 * m11 + p2 * m12, + p0 * m20 + p1 * m21 + p2 * m22 + ); /// public Polynomial GetBasisFunction( int i ) { diff --git a/Splines/CharMatrix.cs b/Splines/CharMatrix.cs index 23f35c6..62e9fbe 100644 --- a/Splines/CharMatrix.cs +++ b/Splines/CharMatrix.cs @@ -70,7 +70,7 @@ public static Matrix4x4 Create( float m00, float m01, float m02, float m03, floa new Vector4( m03, m13, m23, m33 ) ); - /// Returns the polynomial representing the cubic curve of a given characteristic matrix of a spline, given 4 control points + /// Returns the polynomial representing the curve of a given characteristic matrix of a spline, given 4 control points /// The characteristic matrix to use /// The value of the first control point /// The value of the second control point @@ -93,6 +93,25 @@ public static Polynomial3D GetSplinePolynomial( RationalMatrix4x4 c, Vector3 p0, GetSplinePolynomial( c, p0.z, p1.z, p2.z, p3.z ) ); + /// + public static Polynomial GetSplinePolynomial( RationalMatrix3x3 c, float p0, float p1, float p2 ) => new Polynomial( c.MultiplyColumnVec( p0, p1, p2 ) ); + + /// + public static Polynomial2D GetSplinePolynomial( RationalMatrix3x3 c, Vector2 p0, Vector2 p1, Vector2 p2 ) => + new( + GetSplinePolynomial( c, p0.x, p1.x, p2.x ), + GetSplinePolynomial( c, p0.y, p1.y, p2.y ) + ); + + /// + public static Polynomial3D GetSplinePolynomial( RationalMatrix3x3 c, Vector3 p0, Vector3 p1, Vector3 p2 ) => + new( + GetSplinePolynomial( c, p0.x, p1.x, p2.x ), + GetSplinePolynomial( c, p0.y, p1.y, p2.y ), + GetSplinePolynomial( c, p0.z, p1.z, p2.z ) + ); + + } } \ No newline at end of file diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Splines/Uniform Spline Segments/BezierQuad1D.cs index fd46ff8..86d9adf 100644 --- a/Splines/Uniform Spline Segments/BezierQuad1D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad1D.cs @@ -83,7 +83,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.quadraticBezier.GetEvalPolynomial( p0, p1, p2 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.quadraticBezier, p0, p1, p2 ); } public static bool operator ==( BezierQuad1D a, BezierQuad1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad1D a, BezierQuad1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index bade0f0..7dd53e1 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -83,7 +83,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.quadraticBezier.GetCurve( p0, p1, p2 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.quadraticBezier, p0, p1, p2 ); } public static bool operator ==( BezierQuad2D a, BezierQuad2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad2D a, BezierQuad2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index ef2b30f..0685633 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -83,7 +83,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.quadraticBezier.GetCurve( p0, p1, p2 ); + curve = CharMatrix.GetSplinePolynomial( CharMatrix.quadraticBezier, p0, p1, p2 ); } public static bool operator ==( BezierQuad3D a, BezierQuad3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad3D a, BezierQuad3D b ) => !( a == b ); From 777fedc97945043e8e359862044f938afe001039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 10:55:10 +0200 Subject: [PATCH 076/301] moved numeric types into their own folder --- FloatRange.cs => Numerics/FloatRange.cs | 0 Rational.cs => Numerics/Rational.cs | 0 RationalMatrix3x3.cs => Numerics/RationalMatrix3x3.cs | 0 RationalMatrix4x4.cs => Numerics/RationalMatrix4x4.cs | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename FloatRange.cs => Numerics/FloatRange.cs (100%) rename Rational.cs => Numerics/Rational.cs (100%) rename RationalMatrix3x3.cs => Numerics/RationalMatrix3x3.cs (100%) rename RationalMatrix4x4.cs => Numerics/RationalMatrix4x4.cs (100%) diff --git a/FloatRange.cs b/Numerics/FloatRange.cs similarity index 100% rename from FloatRange.cs rename to Numerics/FloatRange.cs diff --git a/Rational.cs b/Numerics/Rational.cs similarity index 100% rename from Rational.cs rename to Numerics/Rational.cs diff --git a/RationalMatrix3x3.cs b/Numerics/RationalMatrix3x3.cs similarity index 100% rename from RationalMatrix3x3.cs rename to Numerics/RationalMatrix3x3.cs diff --git a/RationalMatrix4x4.cs b/Numerics/RationalMatrix4x4.cs similarity index 100% rename from RationalMatrix4x4.cs rename to Numerics/RationalMatrix4x4.cs From 5dc7f5c46cc388dffe6afdfcbcc0587e9cf10a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 12:36:17 +0200 Subject: [PATCH 077/301] added 4x1 and 3x1 matrix types --- Numerics/Matrix3x1.cs | 18 ++++++++++++++++++ Numerics/Matrix4x1.cs | 18 ++++++++++++++++++ Numerics/Vector2Matrix3x1.cs | 32 ++++++++++++++++++++++++++++++++ Numerics/Vector2Matrix4x1.cs | 33 +++++++++++++++++++++++++++++++++ Numerics/Vector3Matrix3x1.cs | 33 +++++++++++++++++++++++++++++++++ Numerics/Vector3Matrix4x1.cs | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 168 insertions(+) create mode 100644 Numerics/Matrix3x1.cs create mode 100644 Numerics/Matrix4x1.cs create mode 100644 Numerics/Vector2Matrix3x1.cs create mode 100644 Numerics/Vector2Matrix4x1.cs create mode 100644 Numerics/Vector3Matrix3x1.cs create mode 100644 Numerics/Vector3Matrix4x1.cs diff --git a/Numerics/Matrix3x1.cs b/Numerics/Matrix3x1.cs new file mode 100644 index 0000000..a2fc891 --- /dev/null +++ b/Numerics/Matrix3x1.cs @@ -0,0 +1,18 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; + +/// A 3x1 column matrix with float values +public readonly struct Matrix3x1 { + + public readonly float m0, m1, m2; + + public Matrix3x1( float m0, float m1, float m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); + + public float this[ int column ] => + column switch { + 0 => m0, 1 => m1, 2 => m2, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) + }; + +} \ No newline at end of file diff --git a/Numerics/Matrix4x1.cs b/Numerics/Matrix4x1.cs new file mode 100644 index 0000000..4e48a53 --- /dev/null +++ b/Numerics/Matrix4x1.cs @@ -0,0 +1,18 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; + +/// A 4x1 column matrix with float values +public readonly struct Matrix4x1 { + + public readonly float m0, m1, m2, m3; + + public Matrix4x1( float m0, float m1, float m2, float m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); + + public float this[ int column ] => + column switch { + 0 => m0, 1 => m1, 2 => m2, 3 => m3, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) + }; + +} \ No newline at end of file diff --git a/Numerics/Vector2Matrix3x1.cs b/Numerics/Vector2Matrix3x1.cs new file mode 100644 index 0000000..11ac685 --- /dev/null +++ b/Numerics/Vector2Matrix3x1.cs @@ -0,0 +1,32 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + /// A 3x1 column matrix with Vector2 values + public readonly struct Vector2Matrix3x1 { + + public readonly Vector2 m0, m1, m2; + + public Vector2Matrix3x1( Vector2 m0, Vector2 m1, Vector2 m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); + + public Vector2Matrix3x1( Matrix3x1 x, Matrix3x1 y ) { + m0 = new Vector2( x.m0, y.m0 ); + m1 = new Vector2( x.m1, y.m1 ); + m2 = new Vector2( x.m2, y.m2 ); + } + + public Vector2 this[ int column ] => + column switch { + 0 => m0, 1 => m1, 2 => m2, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) + }; + + public Matrix3x1 X => new(m0.x, m1.x, m2.x); + public Matrix3x1 Y => new(m0.y, m1.y, m2.y); + + } + +} \ No newline at end of file diff --git a/Numerics/Vector2Matrix4x1.cs b/Numerics/Vector2Matrix4x1.cs new file mode 100644 index 0000000..0993cfd --- /dev/null +++ b/Numerics/Vector2Matrix4x1.cs @@ -0,0 +1,33 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + /// A 4x1 column matrix with Vector2 values + public readonly struct Vector2Matrix4x1 { + + public readonly Vector2 m0, m1, m2, m3; + + public Vector2Matrix4x1( Vector2 m0, Vector2 m1, Vector2 m2, Vector2 m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); + + public Vector2Matrix4x1( Matrix4x1 x, Matrix4x1 y ) { + m0 = new Vector2( x.m0, y.m0 ); + m1 = new Vector2( x.m1, y.m1 ); + m2 = new Vector2( x.m2, y.m2 ); + m3 = new Vector2( x.m3, y.m3 ); + } + + public Vector2 this[ int column ] => + column switch { + 0 => m0, 1 => m1, 2 => m2, 3 => m3, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) + }; + + public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); + public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); + + } + +} \ No newline at end of file diff --git a/Numerics/Vector3Matrix3x1.cs b/Numerics/Vector3Matrix3x1.cs new file mode 100644 index 0000000..0a9d838 --- /dev/null +++ b/Numerics/Vector3Matrix3x1.cs @@ -0,0 +1,33 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + /// A 3x1 column matrix with Vector3 values + public readonly struct Vector3Matrix3x1 { + + public readonly Vector3 m0, m1, m2; + + public Vector3Matrix3x1( Vector3 m0, Vector3 m1, Vector3 m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); + + public Vector3Matrix3x1( Matrix3x1 x, Matrix3x1 y, Matrix3x1 z ) { + m0 = new Vector3( x.m0, y.m0, z.m0 ); + m1 = new Vector3( x.m1, y.m1, z.m1 ); + m2 = new Vector3( x.m2, y.m2, z.m2 ); + } + + public Vector3 this[ int column ] => + column switch { + 0 => m0, 1 => m1, 2 => m2, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) + }; + + public Matrix3x1 X => new(m0.x, m1.x, m2.x); + public Matrix3x1 Y => new(m0.y, m1.y, m2.y); + public Matrix3x1 Z => new(m0.z, m1.z, m2.z); + + } + +} \ No newline at end of file diff --git a/Numerics/Vector3Matrix4x1.cs b/Numerics/Vector3Matrix4x1.cs new file mode 100644 index 0000000..c405f60 --- /dev/null +++ b/Numerics/Vector3Matrix4x1.cs @@ -0,0 +1,34 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + /// A 4x1 column matrix with Vector3 values + public readonly struct Vector3Matrix4x1 { + + public readonly Vector3 m0, m1, m2, m3; + + public Vector3Matrix4x1( Vector3 m0, Vector3 m1, Vector3 m2, Vector3 m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); + + public Vector3Matrix4x1( Matrix4x1 x, Matrix4x1 y, Matrix4x1 z ) { + m0 = new Vector3( x.m0, y.m0, z.m0 ); + m1 = new Vector3( x.m1, y.m1, z.m1 ); + m2 = new Vector3( x.m2, y.m2, z.m2 ); + m3 = new Vector3( x.m3, y.m3, z.m3 ); + } + + public Vector3 this[ int column ] => + column switch { + 0 => m0, 1 => m1, 2 => m2, 3 => m3, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) + }; + + public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); + public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); + public Matrix4x1 Z => new(m0.z, m1.z, m2.z, m3.z); + + } + +} \ No newline at end of file From a55b38438ee3184f3205f20c11f5ac1016b75a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 12:37:33 +0200 Subject: [PATCH 078/301] refactored dealing with characteristic matrices now it's more, mathematical, rather than special cased --- Codegen/Editor/MathfsCodegen.cs | 3 +- Curves/Polynomial.cs | 10 +- Curves/Polynomial2D.cs | 22 ++-- Curves/Polynomial3D.cs | 22 ++-- Numerics/RationalMatrix3x3.cs | 90 ++----------- Numerics/RationalMatrix4x4.cs | 123 +++--------------- Splines/CharMatrix.cs | 54 ++------ .../Uniform Spline Segments/BezierCubic1D.cs | 3 +- .../Uniform Spline Segments/BezierCubic2D.cs | 3 +- .../Uniform Spline Segments/BezierCubic3D.cs | 3 +- .../Uniform Spline Segments/BezierQuad1D.cs | 3 +- .../Uniform Spline Segments/BezierQuad2D.cs | 3 +- .../Uniform Spline Segments/BezierQuad3D.cs | 3 +- .../Uniform Spline Segments/CatRomCubic1D.cs | 3 +- .../Uniform Spline Segments/CatRomCubic2D.cs | 3 +- .../Uniform Spline Segments/CatRomCubic3D.cs | 3 +- .../Uniform Spline Segments/HermiteCubic1D.cs | 3 +- .../Uniform Spline Segments/HermiteCubic2D.cs | 3 +- .../Uniform Spline Segments/HermiteCubic3D.cs | 3 +- Splines/Uniform Spline Segments/UBSCubic1D.cs | 3 +- Splines/Uniform Spline Segments/UBSCubic2D.cs | 3 +- Splines/Uniform Spline Segments/UBSCubic3D.cs | 3 +- 22 files changed, 114 insertions(+), 255 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index eee166f..4b36cb6 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -163,6 +163,7 @@ static void GenerateType( SplineType type, int dim ) { // control point properties using( code.ScopeRegion( "Control Points" ) ) { code.Append( $"[SerializeField] {dataType} {string.Join( ", ", points )};" ); + code.Append( $"public {( dim == 1 ? "" : dataType )}Matrix{ptCount}x1 PointMatrix => new({string.Join( ", ", points )});" ); code.LineBreak(); for( int i = 0; i < ptCount; i++ ) { code.Summary( pointDescs[i] ); @@ -209,7 +210,7 @@ static void GenerateType( SplineType type, int dim ) { code.Append( "return; // no need to update" ); code.Append( "validCoefficients = true;" ); // todo: unroll matrix multiply for performance - code.Append( $"curve = CharMatrix.GetSplinePolynomial( CharMatrix.{type.matrixName}, {string.Join( ", ", points )} );" ); + code.Append( $"curve = new {polynomType}( CharMatrix.{type.matrixName} * PointMatrix );" ); } // equality checks diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index a60b2f7..d289efd 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -63,13 +63,21 @@ public float this[ int degree ] { /// The coefficients to use (x = constant, y = linear, z = quadratic, w = cubic) public Polynomial( Vector4 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.x, coefficients.y, coefficients.z, coefficients.w ); + /// Creates a polynomial up to a cubic + /// The coefficients to use + public Polynomial( Matrix4x1 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.m0, coefficients.m1, coefficients.m2, coefficients.m3 ); + + /// Creates a polynomial up to a quadratic + /// The coefficients to use + public Polynomial( Matrix3x1 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.m0, coefficients.m1, coefficients.m2, 0 ); + /// Creates a polynomial up to a cubic /// The coefficients to use (c0 = constant, c1 = linear, c2 = quadratic, c3 = cubic) public Polynomial( (float c0, float c1, float c2, float c3) coefficients ) => ( c0, c1, c2, c3 ) = coefficients; /// Creates a polynomial up to a quadratic /// The coefficients to use (c0 = constant, c1 = linear, c2 = quadratic) - public Polynomial( (float c0, float c1, float c2) coefficients ) => _ = ( ( c0, c1, c2 ) = coefficients, c3 = 0 ); + public Polynomial( (float c0, float c1, float c2) coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.c0, coefficients.c1, coefficients.c2, 0 ); /// Evaluates the polynomial at the given value t /// The value to sample at diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index 44f4fea..4c9c0c3 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -37,6 +37,12 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2, Vector2 c3 ) { this.y = new Polynomial( c0.y, c1.y, c2.y, c3.y ); } + /// + public Polynomial2D( Vector2Matrix4x1 coefficients ) => ( x, y ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ) ); + + /// + public Polynomial2D( Vector2Matrix3x1 coefficients ) => ( x, y ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ) ); + /// public Vector2 Eval( float t ) => new(x.Eval( t ), y.Eval( t )); @@ -60,26 +66,26 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2, Vector2 c3 ) { /// Returns the cubic bezier control points for the unit interval of this curve public BezierCubic2D ToBezier() { - ( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) = CharMatrix.cubicBezierInverse.MultiplyColumnVec( C0, C1, C2, C3 ); - return new BezierCubic2D( p0, p1, p2, p3 ); + Vector2Matrix4x1 p = CharMatrix.cubicBezierInverse * new Vector2Matrix4x1( C0, C1, C2, C3 ); + return new BezierCubic2D( p.m0, p.m1, p.m2, p.m3 ); } /// Returns the cubic catmull-rom control points for the unit interval of this curve public CatRomCubic2D ToCatmullRom() { - ( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) = CharMatrix.cubicCatmullRomInverse.MultiplyColumnVec( C0, C1, C2, C3 ); - return new CatRomCubic2D( p0, p1, p2, p3 ); + Vector2Matrix4x1 p = CharMatrix.cubicCatmullRomInverse * new Vector2Matrix4x1( C0, C1, C2, C3 ); + return new CatRomCubic2D( p.m0, p.m1, p.m2, p.m3 ); } /// Returns the cubic hermite control points for the unit interval of this curve public HermiteCubic2D ToHermite() { - ( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) = CharMatrix.cubicHermiteInverse.MultiplyColumnVec( C0, C1, C2, C3 ); - return new HermiteCubic2D( p0, v0, p1, v1 ); + Vector2Matrix4x1 p = CharMatrix.cubicHermiteInverse * new Vector2Matrix4x1( C0, C1, C2, C3 ); + return new HermiteCubic2D( p.m0, p.m1, p.m2, p.m3 ); } /// Returns the cubic b-spline control points for the unit interval of this curve public UBSCubic2D ToBSpline() { - ( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) = CharMatrix.cubicUniformBsplineInverse.MultiplyColumnVec( C0, C1, C2, C3 ); - return new UBSCubic2D( p0, v0, p1, v1 ); + Vector2Matrix4x1 p = CharMatrix.cubicUniformBsplineInverse * new Vector2Matrix4x1( C0, C1, C2, C3 ); + return new UBSCubic2D( p.m0, p.m1, p.m2, p.m3 ); } #endregion diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs index 0e7f593..1bf5900 100644 --- a/Curves/Polynomial3D.cs +++ b/Curves/Polynomial3D.cs @@ -33,6 +33,12 @@ public Vector3 C3 { public Polynomial3D( Polynomial x, Polynomial y, Polynomial z ) => ( this.x, this.y, this.z ) = ( x, y, z ); + /// + public Polynomial3D( Vector3Matrix4x1 coefficients ) => ( x, y, z ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ) ); + + /// + public Polynomial3D( Vector3Matrix3x1 coefficients ) => ( x, y, z ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ) ); + /// public Vector3 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t )); @@ -57,26 +63,26 @@ public Vector3 C3 { /// public BezierCubic3D ToBezier() { - ( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) = CharMatrix.cubicBezierInverse.MultiplyColumnVec( C0, C1, C2, C3 ); - return new BezierCubic3D( p0, p1, p2, p3 ); + Vector3Matrix4x1 p = CharMatrix.cubicBezierInverse * new Vector3Matrix4x1( C0, C1, C2, C3 ); + return new BezierCubic3D( p.m0, p.m1, p.m2, p.m3 ); } /// public CatRomCubic3D ToCatmullRom() { - ( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) = CharMatrix.cubicCatmullRomInverse.MultiplyColumnVec( C0, C1, C2, C3 ); - return new CatRomCubic3D( p0, p1, p2, p3 ); + Vector3Matrix4x1 p = CharMatrix.cubicCatmullRomInverse * new Vector3Matrix4x1( C0, C1, C2, C3 ); + return new CatRomCubic3D( p.m0, p.m1, p.m2, p.m3 ); } /// public HermiteCubic3D ToHermite() { - ( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) = CharMatrix.cubicHermiteInverse.MultiplyColumnVec( C0, C1, C2, C3 ); - return new HermiteCubic3D( p0, v0, p1, v1 ); + Vector3Matrix4x1 p = CharMatrix.cubicHermiteInverse * new Vector3Matrix4x1( C0, C1, C2, C3 ); + return new HermiteCubic3D( p.m0, p.m1, p.m2, p.m3 ); } /// public UBSCubic3D ToBSpline() { - ( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) = CharMatrix.cubicUniformBsplineInverse.MultiplyColumnVec( C0, C1, C2, C3 ); - return new UBSCubic3D( p0, v0, p1, v1 ); + Vector3Matrix4x1 p = CharMatrix.cubicUniformBsplineInverse * new Vector3Matrix4x1( C0, C1, C2, C3 ); + return new UBSCubic3D( p.m0, p.m1, p.m2, p.m3 ); } #endregion diff --git a/Numerics/RationalMatrix3x3.cs b/Numerics/RationalMatrix3x3.cs index de25f59..8521285 100644 --- a/Numerics/RationalMatrix3x3.cs +++ b/Numerics/RationalMatrix3x3.cs @@ -6,11 +6,11 @@ namespace Freya { /// A 4x4 matrix using exact rational number representation - public struct RationalMatrix3x3 { + public readonly struct RationalMatrix3x3 { - public Rational m00, m01, m02; - public Rational m10, m11, m12; - public Rational m20, m21, m22; + public readonly Rational m00, m01, m02; + public readonly Rational m10, m11, m12; + public readonly Rational m20, m21, m22; public RationalMatrix3x3( Rational m00, Rational m01, Rational m02, Rational m10, Rational m11, Rational m12, Rational m20, Rational m21, Rational m22 ) { ( this.m00, this.m01, this.m02 ) = ( m00, m01, m02 ); @@ -33,38 +33,6 @@ public RationalMatrix3x3( Rational m00, Rational m01, Rational m02, Rational m10 _ => throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 3, got: ({row},{column})" ) }; } - set { - switch( ( row, column ) ) { - case (0, 0): - m00 = value; - break; - case (0, 1): - m01 = value; - break; - case (0, 2): - m02 = value; - break; - case (1, 0): - m10 = value; - break; - case (1, 1): - m11 = value; - break; - case (1, 2): - m12 = value; - break; - case (2, 0): - m20 = value; - break; - case (2, 1): - m21 = value; - break; - case (2, 2): - m22 = value; - break; - default: throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 3, got: ({row},{column})" ); - } - } } /// Returns the inverse of this matrix. Throws a division by zero exception if it's not invertible @@ -123,48 +91,18 @@ public Rational Determinant { ); } - /// - public Polynomial GetEvalPolynomial( float p0, float p1, float p2 ) => - Polynomial.Quadratic( - p0 * m00 + p1 * m01 + p2 * m02, - p0 * m10 + p1 * m11 + p2 * m12, - p0 * m20 + p1 * m21 + p2 * m22 - ); + /// + public static Matrix3x1 operator *( RationalMatrix3x3 c, Matrix3x1 m ) => + new(m.m0 * c.m00 + m.m1 * c.m01 + m.m2 * c.m02, + m.m0 * c.m10 + m.m1 * c.m11 + m.m2 * c.m12, + m.m0 * c.m20 + m.m1 * c.m21 + m.m2 * c.m22); - /// - public Polynomial2D GetCurve( Vector2 p0, Vector2 p1, Vector2 p2 ) => - new( - GetEvalPolynomial( p0.x, p1.x, p2.x ), - GetEvalPolynomial( p0.y, p1.y, p2.y ) - ); + /// + public static Vector2Matrix3x1 operator *( RationalMatrix3x3 c, Vector2Matrix3x1 m ) => new(c * m.X, c * m.Y); - /// - public Polynomial3D GetCurve( Vector3 p0, Vector3 p1, Vector3 p2 ) => - new( - GetEvalPolynomial( p0.x, p1.x, p2.x ), - GetEvalPolynomial( p0.y, p1.y, p2.y ), - GetEvalPolynomial( p0.z, p1.z, p2.z ) - ); - /// Multiplies this characteristic matrix C by a column matrix: C*[p0,p1,p2]^T - /// The first entry of the column matrix - /// The second entry of the column matrix - /// The third entry of the column matrix - public (float, float, float) MultiplyColumnVec( float p0, float p1, float p2 ) => - ( - p0 * m00 + p1 * m01 + p2 * m02, - p0 * m10 + p1 * m11 + p2 * m12, - p0 * m20 + p1 * m21 + p2 * m22 - ); - - /// - public Polynomial GetBasisFunction( int i ) { - return i switch { - 0 => Polynomial.Quadratic( (float)m00, (float)m10, (float)m20 ), - 1 => Polynomial.Quadratic( (float)m01, (float)m11, (float)m21 ), - 2 => Polynomial.Quadratic( (float)m02, (float)m12, (float)m22 ), - _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 2" ) - }; - } + /// + public static Vector3Matrix3x1 operator *( RationalMatrix3x3 c, Vector3Matrix3x1 m ) => new(c * m.X, c * m.Y, c * m.Z); + } } \ No newline at end of file diff --git a/Numerics/RationalMatrix4x4.cs b/Numerics/RationalMatrix4x4.cs index a4129fe..f89813f 100644 --- a/Numerics/RationalMatrix4x4.cs +++ b/Numerics/RationalMatrix4x4.cs @@ -6,12 +6,12 @@ namespace Freya { /// A 4x4 matrix using exact rational number representation - public struct RationalMatrix4x4 { + public readonly struct RationalMatrix4x4 { - public Rational m00, m01, m02, m03; - public Rational m10, m11, m12, m13; - public Rational m20, m21, m22, m23; - public Rational m30, m31, m32, m33; + public readonly Rational m00, m01, m02, m03; + public readonly Rational m10, m11, m12, m13; + public readonly Rational m20, m21, m22, m23; + public readonly Rational m30, m31, m32, m33; public RationalMatrix4x4( Rational m00, Rational m01, Rational m02, Rational m03, Rational m10, Rational m11, Rational m12, Rational m13, Rational m20, Rational m21, Rational m22, Rational m23, Rational m30, Rational m31, Rational m32, Rational m33 ) { ( this.m00, this.m01, this.m02, this.m03 ) = ( m00, m01, m02, m03 ); @@ -42,59 +42,6 @@ public RationalMatrix4x4( Rational m00, Rational m01, Rational m02, Rational m03 _ => throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 3, got: ({row},{column})" ) }; } - set { - switch( ( row, column ) ) { - case (0, 0): - m00 = value; - break; - case (0, 1): - m01 = value; - break; - case (0, 2): - m02 = value; - break; - case (0, 3): - m03 = value; - break; - case (1, 0): - m10 = value; - break; - case (1, 1): - m11 = value; - break; - case (1, 2): - m12 = value; - break; - case (1, 3): - m13 = value; - break; - case (2, 0): - m20 = value; - break; - case (2, 1): - m21 = value; - break; - case (2, 2): - m22 = value; - break; - case (2, 3): - m23 = value; - break; - case (3, 0): - m30 = value; - break; - case (3, 1): - m31 = value; - break; - case (3, 2): - m32 = value; - break; - case (3, 3): - m33 = value; - break; - default: throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 3, got: ({row},{column})" ); - } - } } /// Returns the inverse of this matrix. Throws a division by zero exception if it's not invertible @@ -185,56 +132,20 @@ public Rational Determinant { ); } - /// Multiplies this characteristic matrix C by a column matrix: C*[p0,p1,p2,p3]^T - /// The first entry of the column matrix - /// The second entry of the column matrix - /// The third entry of the column matrix - /// The fourth entry of the column matrix - public (float, float, float, float) MultiplyColumnVec( float p0, float p1, float p2, float p3 ) => - ( - p0 * m00 + p1 * m01 + p2 * m02 + p3 * m03, - p0 * m10 + p1 * m11 + p2 * m12 + p3 * m13, - p0 * m20 + p1 * m21 + p2 * m22 + p3 * m23, - p0 * m30 + p1 * m31 + p2 * m32 + p3 * m33 - ); - - /// - public (Vector2, Vector2, Vector2, Vector2) MultiplyColumnVec( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { - ( float x0, float x1, float x2, float x3 ) = MultiplyColumnVec( p0.x, p1.x, p2.x, p3.x ); - ( float y0, float y1, float y2, float y3 ) = MultiplyColumnVec( p0.y, p1.y, p2.y, p3.y ); - return ( - new Vector2( x0, y0 ), - new Vector2( x1, y1 ), - new Vector2( x2, y2 ), - new Vector2( x3, y3 ) - ); - } + /// Multiplies this matrix C by a column matrix M + /// The left hand side 4x4 matrix + /// The right hand side 4x1 matrix + public static Matrix4x1 operator *( RationalMatrix4x4 c, Matrix4x1 m ) => + new(m.m0 * c.m00 + m.m1 * c.m01 + m.m2 * c.m02 + m.m3 * c.m03, + m.m0 * c.m10 + m.m1 * c.m11 + m.m2 * c.m12 + m.m3 * c.m13, + m.m0 * c.m20 + m.m1 * c.m21 + m.m2 * c.m22 + m.m3 * c.m23, + m.m0 * c.m30 + m.m1 * c.m31 + m.m2 * c.m32 + m.m3 * c.m33); - /// - public (Vector3, Vector3, Vector3, Vector3) MultiplyColumnVec( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { - ( float x0, float x1, float x2, float x3 ) = MultiplyColumnVec( p0.x, p1.x, p2.x, p3.x ); - ( float y0, float y1, float y2, float y3 ) = MultiplyColumnVec( p0.y, p1.y, p2.y, p3.y ); - ( float z0, float z1, float z2, float z3 ) = MultiplyColumnVec( p0.z, p1.z, p2.z, p3.z ); - return ( - new Vector3( x0, y0, z0 ), - new Vector3( x1, y1, z1 ), - new Vector3( x2, y2, z2 ), - new Vector3( x3, y3, z3 ) - ); - } + /// + public static Vector2Matrix4x1 operator *( RationalMatrix4x4 c, Vector2Matrix4x1 m ) => new(c * m.X, c * m.Y); - /// Returns the basis function (weight) for the given point by index i, - /// equal to the t-matrix multiplied by the characteristic matrix - /// The point index to get the basis function of - public Polynomial GetBasisFunction( int i ) { - return i switch { - 0 => new Polynomial( (float)m00, (float)m10, (float)m20, (float)m30 ), - 1 => new Polynomial( (float)m01, (float)m11, (float)m21, (float)m31 ), - 2 => new Polynomial( (float)m02, (float)m12, (float)m22, (float)m32 ), - 3 => new Polynomial( (float)m03, (float)m13, (float)m23, (float)m33 ), - _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) - }; - } + /// + public static Vector3Matrix4x1 operator *( RationalMatrix4x4 c, Vector3Matrix4x1 m ) => new(c * m.X, c * m.Y, c * m.Z); } diff --git a/Splines/CharMatrix.cs b/Splines/CharMatrix.cs index 62e9fbe..abf6fba 100644 --- a/Splines/CharMatrix.cs +++ b/Splines/CharMatrix.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using UnityEngine; namespace Freya { @@ -70,46 +71,19 @@ public static Matrix4x4 Create( float m00, float m01, float m02, float m03, floa new Vector4( m03, m13, m23, m33 ) ); - /// Returns the polynomial representing the curve of a given characteristic matrix of a spline, given 4 control points - /// The characteristic matrix to use - /// The value of the first control point - /// The value of the second control point - /// The value of the third control point - /// The value of the fourth control point - public static Polynomial GetSplinePolynomial( RationalMatrix4x4 c, float p0, float p1, float p2, float p3 ) => new Polynomial( c.MultiplyColumnVec( p0, p1, p2, p3 ) ); - - /// - public static Polynomial2D GetSplinePolynomial( RationalMatrix4x4 c, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => - new( - GetSplinePolynomial( c, p0.x, p1.x, p2.x, p3.x ), - GetSplinePolynomial( c, p0.y, p1.y, p2.y, p3.y ) - ); - - /// - public static Polynomial3D GetSplinePolynomial( RationalMatrix4x4 c, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => - new( - GetSplinePolynomial( c, p0.x, p1.x, p2.x, p3.x ), - GetSplinePolynomial( c, p0.y, p1.y, p2.y, p3.y ), - GetSplinePolynomial( c, p0.z, p1.z, p2.z, p3.z ) - ); - - /// - public static Polynomial GetSplinePolynomial( RationalMatrix3x3 c, float p0, float p1, float p2 ) => new Polynomial( c.MultiplyColumnVec( p0, p1, p2 ) ); - - /// - public static Polynomial2D GetSplinePolynomial( RationalMatrix3x3 c, Vector2 p0, Vector2 p1, Vector2 p2 ) => - new( - GetSplinePolynomial( c, p0.x, p1.x, p2.x ), - GetSplinePolynomial( c, p0.y, p1.y, p2.y ) - ); - - /// - public static Polynomial3D GetSplinePolynomial( RationalMatrix3x3 c, Vector3 p0, Vector3 p1, Vector3 p2 ) => - new( - GetSplinePolynomial( c, p0.x, p1.x, p2.x ), - GetSplinePolynomial( c, p0.y, p1.y, p2.y ), - GetSplinePolynomial( c, p0.z, p1.z, p2.z ) - ); + /// Returns the basis function (weight) for the given spline points by index i, + /// equal to the t-matrix multiplied by the characteristic matrix + /// The characteristic matrix to get the basis functions of + /// The point index to get the basis function of + public static Polynomial GetBasisFunction( RationalMatrix4x4 c, int i ) { + return i switch { + 0 => new Polynomial( (float)c.m00, (float)c.m10, (float)c.m20, (float)c.m30 ), + 1 => new Polynomial( (float)c.m01, (float)c.m11, (float)c.m21, (float)c.m31 ), + 2 => new Polynomial( (float)c.m02, (float)c.m12, (float)c.m22, (float)c.m32 ), + 3 => new Polynomial( (float)c.m03, (float)c.m13, (float)c.m23, (float)c.m33 ), + _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) + }; + } } diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index ab26894..77a054e 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -33,6 +33,7 @@ public Polynomial Curve { #region Control Points [SerializeField] float p0, p1, p2, p3; + public Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The starting point of the curve public float P0 { @@ -94,7 +95,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicBezier, p0, p1, p2, p3 ); + curve = new Polynomial( CharMatrix.cubicBezier * PointMatrix ); } public static bool operator ==( BezierCubic1D a, BezierCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic1D a, BezierCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index 6aaec78..7330c17 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -33,6 +33,7 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2 p0, p1, p2, p3; + public Vector2Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The starting point of the curve public Vector2 P0 { @@ -94,7 +95,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicBezier, p0, p1, p2, p3 ); + curve = new Polynomial2D( CharMatrix.cubicBezier * PointMatrix ); } public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic2D a, BezierCubic2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index 863b061..a6454a4 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -33,6 +33,7 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3 p0, p1, p2, p3; + public Vector3Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The starting point of the curve public Vector3 P0 { @@ -94,7 +95,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicBezier, p0, p1, p2, p3 ); + curve = new Polynomial3D( CharMatrix.cubicBezier * PointMatrix ); } public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic3D a, BezierCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Splines/Uniform Spline Segments/BezierQuad1D.cs index 86d9adf..98ae2fb 100644 --- a/Splines/Uniform Spline Segments/BezierQuad1D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad1D.cs @@ -32,6 +32,7 @@ public Polynomial Curve { #region Control Points [SerializeField] float p0, p1, p2; + public Matrix3x1 PointMatrix => new(p0, p1, p2); /// The starting point of the curve public float P0 { @@ -83,7 +84,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.quadraticBezier, p0, p1, p2 ); + curve = new Polynomial( CharMatrix.quadraticBezier * PointMatrix ); } public static bool operator ==( BezierQuad1D a, BezierQuad1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad1D a, BezierQuad1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index 7dd53e1..ebf1cfc 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -32,6 +32,7 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2 p0, p1, p2; + public Vector2Matrix3x1 PointMatrix => new(p0, p1, p2); /// The starting point of the curve public Vector2 P0 { @@ -83,7 +84,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.quadraticBezier, p0, p1, p2 ); + curve = new Polynomial2D( CharMatrix.quadraticBezier * PointMatrix ); } public static bool operator ==( BezierQuad2D a, BezierQuad2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad2D a, BezierQuad2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index 0685633..ac05f09 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -32,6 +32,7 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3 p0, p1, p2; + public Vector3Matrix3x1 PointMatrix => new(p0, p1, p2); /// The starting point of the curve public Vector3 P0 { @@ -83,7 +84,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.quadraticBezier, p0, p1, p2 ); + curve = new Polynomial3D( CharMatrix.quadraticBezier * PointMatrix ); } public static bool operator ==( BezierQuad3D a, BezierQuad3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad3D a, BezierQuad3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index 4cc94d9..727dece 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -33,6 +33,7 @@ public Polynomial Curve { #region Control Points [SerializeField] float p0, p1, p2, p3; + public Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public float P0 { @@ -94,7 +95,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicCatmullRom, p0, p1, p2, p3 ); + curve = new Polynomial( CharMatrix.cubicCatmullRom * PointMatrix ); } public static bool operator ==( CatRomCubic1D a, CatRomCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic1D a, CatRomCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 9401dd3..bff7afb 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -33,6 +33,7 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2 p0, p1, p2, p3; + public Vector2Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P0 { @@ -94,7 +95,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicCatmullRom, p0, p1, p2, p3 ); + curve = new Polynomial2D( CharMatrix.cubicCatmullRom * PointMatrix ); } public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 1e9c190..a5074cc 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -33,6 +33,7 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3 p0, p1, p2, p3; + public Vector3Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector3 P0 { @@ -94,7 +95,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicCatmullRom, p0, p1, p2, p3 ); + curve = new Polynomial3D( CharMatrix.cubicCatmullRom * PointMatrix ); } public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic3D a, CatRomCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index 69e3211..743f4ee 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -33,6 +33,7 @@ public Polynomial Curve { #region Control Points [SerializeField] float p0, v0, p1, v1; + public Matrix4x1 PointMatrix => new(p0, v0, p1, v1); /// The starting point of the curve public float P0 { @@ -94,7 +95,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicHermite, p0, v0, p1, v1 ); + curve = new Polynomial( CharMatrix.cubicHermite * PointMatrix ); } public static bool operator ==( HermiteCubic1D a, HermiteCubic1D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; public static bool operator !=( HermiteCubic1D a, HermiteCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index 5e06e00..d95fef5 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -33,6 +33,7 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2 p0, v0, p1, v1; + public Vector2Matrix4x1 PointMatrix => new(p0, v0, p1, v1); /// The starting point of the curve public Vector2 P0 { @@ -94,7 +95,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicHermite, p0, v0, p1, v1 ); + curve = new Polynomial2D( CharMatrix.cubicHermite * PointMatrix ); } public BezierCubic2D ToBezier() => new BezierCubic2D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index 18c863b..71f49dd 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -33,6 +33,7 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3 p0, v0, p1, v1; + public Vector3Matrix4x1 PointMatrix => new(p0, v0, p1, v1); /// The starting point of the curve public Vector3 P0 { @@ -94,7 +95,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicHermite, p0, v0, p1, v1 ); + curve = new Polynomial3D( CharMatrix.cubicHermite * PointMatrix ); } public static bool operator ==( HermiteCubic3D a, HermiteCubic3D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; public static bool operator !=( HermiteCubic3D a, HermiteCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index 6b21b87..026f54d 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -33,6 +33,7 @@ public Polynomial Curve { #region Control Points [SerializeField] float p0, p1, p2, p3; + public Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The first point of the B-spline hull public float P0 { @@ -94,7 +95,7 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicUniformBspline, p0, p1, p2, p3 ); + curve = new Polynomial( CharMatrix.cubicUniformBspline * PointMatrix ); } public static bool operator ==( UBSCubic1D a, UBSCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( UBSCubic1D a, UBSCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index 6fd2085..c604211 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -33,6 +33,7 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2 p0, p1, p2, p3; + public Vector2Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The first point of the B-spline hull public Vector2 P0 { @@ -94,7 +95,7 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicUniformBspline, p0, p1, p2, p3 ); + curve = new Polynomial2D( CharMatrix.cubicUniformBspline * PointMatrix ); } /// Returns the exact cubic bézier representation of this segment diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index 4f69cd3..a23a9e0 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -33,6 +33,7 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3 p0, p1, p2, p3; + public Vector3Matrix4x1 PointMatrix => new(p0, p1, p2, p3); /// The first point of the B-spline hull public Vector3 P0 { @@ -94,7 +95,7 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = CharMatrix.GetSplinePolynomial( CharMatrix.cubicUniformBspline, p0, p1, p2, p3 ); + curve = new Polynomial3D( CharMatrix.cubicUniformBspline * PointMatrix ); } public static bool operator ==( UBSCubic3D a, UBSCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( UBSCubic3D a, UBSCubic3D b ) => !( a == b ); From 9becc8438ce5118723187d8491d2ae90b47ddcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 12:45:13 +0200 Subject: [PATCH 079/301] polynomial constructor doc cleanup --- Curves/Polynomial.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index d289efd..89c6600 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -59,24 +59,20 @@ public float this[ int degree ] { /// The cubic coefficient public Polynomial( float c0, float c1, float c2, float c3 ) => ( this.c0, this.c1, this.c2, this.c3 ) = ( c0, c1, c2, c3 ); - /// Creates a polynomial up to a cubic - /// The coefficients to use (x = constant, y = linear, z = quadratic, w = cubic) + /// Creates a polynomial + /// The coefficients to use public Polynomial( Vector4 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.x, coefficients.y, coefficients.z, coefficients.w ); - /// Creates a polynomial up to a cubic - /// The coefficients to use + /// public Polynomial( Matrix4x1 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.m0, coefficients.m1, coefficients.m2, coefficients.m3 ); - /// Creates a polynomial up to a quadratic - /// The coefficients to use + /// public Polynomial( Matrix3x1 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.m0, coefficients.m1, coefficients.m2, 0 ); - /// Creates a polynomial up to a cubic - /// The coefficients to use (c0 = constant, c1 = linear, c2 = quadratic, c3 = cubic) + /// public Polynomial( (float c0, float c1, float c2, float c3) coefficients ) => ( c0, c1, c2, c3 ) = coefficients; - /// Creates a polynomial up to a quadratic - /// The coefficients to use (c0 = constant, c1 = linear, c2 = quadratic) + /// public Polynomial( (float c0, float c1, float c2) coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.c0, coefficients.c1, coefficients.c2, 0 ); /// Evaluates the polynomial at the given value t From 47470d08ffe6213010a6ebb604c93862c32dcdf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 13:34:01 +0200 Subject: [PATCH 080/301] added explicit cubic spline conversion operators need to optimize later --- Splines/Uniform Spline Segments/BezierCubic1D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/BezierCubic2D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/BezierCubic3D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/CatRomCubic1D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/CatRomCubic2D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/CatRomCubic3D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/HermiteCubic1D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/HermiteCubic2D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/HermiteCubic3D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/UBSCubic1D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/UBSCubic2D.cs | 12 ++++++++++++ Splines/Uniform Spline Segments/UBSCubic3D.cs | 12 ++++++++++++ 12 files changed, 144 insertions(+) diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index 77a054e..aed9512 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -104,6 +104,18 @@ public float this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; + public static explicit operator HermiteCubic1D( BezierCubic1D bezier ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicHermite ) * bezier.PointMatrix; + return new HermiteCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic1D( BezierCubic1D bezier ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicCatmullRom ) * bezier.PointMatrix; + return new CatRomCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic1D( BezierCubic1D bezier ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicUniformBspline ) * bezier.PointMatrix; + return new UBSCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index 7330c17..a28878b 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -109,6 +109,18 @@ public Vector2 this[ int i ] { public static explicit operator BezierCubic3D( BezierCubic2D curve2D ) { return new BezierCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); } + public static explicit operator HermiteCubic2D( BezierCubic2D bezier ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicHermite ) * bezier.PointMatrix; + return new HermiteCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic2D( BezierCubic2D bezier ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicCatmullRom ) * bezier.PointMatrix; + return new CatRomCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic2D( BezierCubic2D bezier ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicUniformBspline ) * bezier.PointMatrix; + return new UBSCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index a6454a4..7089e7b 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -109,6 +109,18 @@ public Vector3 this[ int i ] { public static explicit operator BezierCubic2D( BezierCubic3D curve3D ) { return new BezierCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); } + public static explicit operator HermiteCubic3D( BezierCubic3D bezier ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicHermite ) * bezier.PointMatrix; + return new HermiteCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic3D( BezierCubic3D bezier ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicCatmullRom ) * bezier.PointMatrix; + return new CatRomCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic3D( BezierCubic3D bezier ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicUniformBspline ) * bezier.PointMatrix; + return new UBSCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index 727dece..5c2405b 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -104,6 +104,18 @@ public float this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; + public static explicit operator BezierCubic1D( CatRomCubic1D catrom ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicBezier ) * catrom.PointMatrix; + return new BezierCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator HermiteCubic1D( CatRomCubic1D catrom ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicHermite ) * catrom.PointMatrix; + return new HermiteCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic1D( CatRomCubic1D catrom ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicUniformBspline ) * catrom.PointMatrix; + return new UBSCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index bff7afb..795f8a6 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -109,6 +109,18 @@ public Vector2 this[ int i ] { public static explicit operator CatRomCubic3D( CatRomCubic2D curve2D ) { return new CatRomCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); } + public static explicit operator BezierCubic2D( CatRomCubic2D catrom ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicBezier ) * catrom.PointMatrix; + return new BezierCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator HermiteCubic2D( CatRomCubic2D catrom ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicHermite ) * catrom.PointMatrix; + return new HermiteCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic2D( CatRomCubic2D catrom ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicUniformBspline ) * catrom.PointMatrix; + return new UBSCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index a5074cc..2a77df8 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -109,6 +109,18 @@ public Vector3 this[ int i ] { public static explicit operator CatRomCubic2D( CatRomCubic3D curve3D ) { return new CatRomCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); } + public static explicit operator BezierCubic3D( CatRomCubic3D catrom ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicBezier ) * catrom.PointMatrix; + return new BezierCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator HermiteCubic3D( CatRomCubic3D catrom ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicHermite ) * catrom.PointMatrix; + return new HermiteCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic3D( CatRomCubic3D catrom ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicUniformBspline ) * catrom.PointMatrix; + return new UBSCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index 743f4ee..c70a7bc 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -104,6 +104,18 @@ public float this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; + public static explicit operator BezierCubic1D( HermiteCubic1D hermite ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicBezier ) * hermite.PointMatrix; + return new BezierCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic1D( HermiteCubic1D hermite ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicCatmullRom ) * hermite.PointMatrix; + return new CatRomCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic1D( HermiteCubic1D hermite ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicUniformBspline ) * hermite.PointMatrix; + return new UBSCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two hermite curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index d95fef5..b34a636 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -111,6 +111,18 @@ public Vector2 this[ int i ] { public static explicit operator HermiteCubic3D( HermiteCubic2D curve2D ) { return new HermiteCubic3D( curve2D.p0, curve2D.v0, curve2D.p1, curve2D.v1 ); } + public static explicit operator BezierCubic2D( HermiteCubic2D hermite ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicBezier ) * hermite.PointMatrix; + return new BezierCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic2D( HermiteCubic2D hermite ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicCatmullRom ) * hermite.PointMatrix; + return new CatRomCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic2D( HermiteCubic2D hermite ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicUniformBspline ) * hermite.PointMatrix; + return new UBSCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two hermite curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index 71f49dd..8122e6c 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -111,6 +111,18 @@ public Vector3 this[ int i ] { public static explicit operator HermiteCubic2D( HermiteCubic3D curve3D ) { return new HermiteCubic2D( curve3D.p0, curve3D.v0, curve3D.p1, curve3D.v1 ); } + public static explicit operator BezierCubic3D( HermiteCubic3D hermite ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicBezier ) * hermite.PointMatrix; + return new BezierCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic3D( HermiteCubic3D hermite ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicCatmullRom ) * hermite.PointMatrix; + return new CatRomCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator UBSCubic3D( HermiteCubic3D hermite ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicUniformBspline ) * hermite.PointMatrix; + return new UBSCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two hermite curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index 026f54d..5f68e24 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -104,6 +104,18 @@ public float this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; + public static explicit operator BezierCubic1D( UBSCubic1D ubs ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicBezier ) * ubs.PointMatrix; + return new BezierCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator HermiteCubic1D( UBSCubic1D ubs ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicHermite ) * ubs.PointMatrix; + return new HermiteCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic1D( UBSCubic1D ubs ) { + Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicCatmullRom ) * ubs.PointMatrix; + return new CatRomCubic1D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two b-spline curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index c604211..35c54a0 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -129,6 +129,18 @@ public BezierCubic2D ToBezier() { public static explicit operator UBSCubic3D( UBSCubic2D curve2D ) { return new UBSCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); } + public static explicit operator BezierCubic2D( UBSCubic2D ubs ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicBezier ) * ubs.PointMatrix; + return new BezierCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator HermiteCubic2D( UBSCubic2D ubs ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicHermite ) * ubs.PointMatrix; + return new HermiteCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic2D( UBSCubic2D ubs ) { + Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicCatmullRom ) * ubs.PointMatrix; + return new CatRomCubic2D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two b-spline curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index a23a9e0..1a8ae54 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -128,6 +128,18 @@ public BezierCubic3D ToBezier() { public static explicit operator UBSCubic2D( UBSCubic3D curve3D ) { return new UBSCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); } + public static explicit operator BezierCubic3D( UBSCubic3D ubs ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicBezier ) * ubs.PointMatrix; + return new BezierCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator HermiteCubic3D( UBSCubic3D ubs ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicHermite ) * ubs.PointMatrix; + return new HermiteCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } + public static explicit operator CatRomCubic3D( UBSCubic3D ubs ) { + Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicCatmullRom ) * ubs.PointMatrix; + return new CatRomCubic3D( p.m0, p.m1, p.m2, p.m3 ); + } /// Returns a linear blend between two b-spline curves /// The first spline segment /// The second spline segment From fcd7f7c11e1d7e5963430e604cbbada82520079a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 13:34:25 +0200 Subject: [PATCH 081/301] cubic spline conversion codegen --- Codegen/Editor/MathfsCodegen.cs | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 4b36cb6..07f004b 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -118,7 +118,7 @@ static void GenerateType( SplineType type, int dim ) { string[] points = type.paramNames; string[] pointDescs = type.paramDescs; string lerpName = GetLerpName( dim ); - string curveFunc = dim == 1 ? "GetEvalPolynomial" : "GetCurve"; + string pointMatrixType = $"{( dim == 1 ? "" : dataType )}Matrix{ptCount}x1"; CodeGenerator code = new CodeGenerator(); code.Comment( "by Freya Holmér (https://github.com/FreyaHolmer/Mathfs)" ); @@ -163,7 +163,7 @@ static void GenerateType( SplineType type, int dim ) { // control point properties using( code.ScopeRegion( "Control Points" ) ) { code.Append( $"[SerializeField] {dataType} {string.Join( ", ", points )};" ); - code.Append( $"public {( dim == 1 ? "" : dataType )}Matrix{ptCount}x1 PointMatrix => new({string.Join( ", ", points )});" ); + code.Append( $"public {pointMatrixType} PointMatrix => new({string.Join( ", ", points )});" ); code.LineBreak(); for( int i = 0; i < ptCount; i++ ) { code.Summary( pointDescs[i] ); @@ -242,8 +242,35 @@ static void GenerateType( SplineType type, int dim ) { using( code.BracketScope( $"public static explicit operator {structName2D}( {structName} curve3D )" ) ) { code.Append( $"return new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p}" ) )} );" ); } + } + } - // todo: conversion to other cubic splines + // converting between spline types + if( degree == 3 ) { + string[] cubicSplineTypeNames = { + nameof(BezierCubic1D).Replace( "1D", $"{dim}D" ), + nameof(HermiteCubic1D).Replace( "1D", $"{dim}D" ), + nameof(CatRomCubic1D).Replace( "1D", $"{dim}D" ), + nameof(UBSCubic1D).Replace( "1D", $"{dim}D" ) + }; + string[] typeMatrices = { + nameof(CharMatrix.cubicBezier), + nameof(CharMatrix.cubicHermite), + nameof(CharMatrix.cubicCatmullRom), + nameof(CharMatrix.cubicUniformBspline) + }; + + // Conversion to other cubic splines + for( int i = 0; i < 4; i++ ) { + string targetType = cubicSplineTypeNames[i]; + if( targetType == structName ) + continue; // don't convert to self + string v = type.className.ToLowerInvariant(); // var name + using( code.BracketScope( $"public static explicit operator {targetType}( {structName} {v} )" ) ) { + code.Append( $"{pointMatrixType} p = CharMatrix.GetConversionMatrix( CharMatrix.{type.matrixName}, CharMatrix.{typeMatrices[i]} ) * {v}.PointMatrix;" ); + int[] range4 = { 0, 1, 2, 3 }; + code.Append( $"return new {targetType}( {string.Join( ", ", range4.Select( j => $"p.m{j}" ) )} );" ); + } } } From d9007b78c6efaf5cd2c7cd5bd2169e692b6c0060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 13:42:43 +0200 Subject: [PATCH 082/301] minor formatting thing --- Codegen/Editor/CodeGenerator.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Codegen/Editor/CodeGenerator.cs b/Codegen/Editor/CodeGenerator.cs index 9715d50..fba1ecd 100644 --- a/Codegen/Editor/CodeGenerator.cs +++ b/Codegen/Editor/CodeGenerator.cs @@ -10,10 +10,7 @@ public class CodeGenerator { int scope = 0; public List content = new List(); - public void Append( string s ) { - content.Add( $"{new string( '\t', scope )}{s}" ); - } - + public void Append( string s ) => content.Add( $"{new string( '\t', scope )}{s}" ); public void Comment( string s ) => Append( $"// {s}" ); public void Using( string s ) => Append( $"using {s};" ); public void Summary( string s ) => Append( $"/// {s}" ); From 548349ea971422c300c26f9a80615e1f69938dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 14:36:07 +0200 Subject: [PATCH 083/301] Rational.IsInteger --- Numerics/Rational.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Numerics/Rational.cs b/Numerics/Rational.cs index d7d79ef..e012a9a 100644 --- a/Numerics/Rational.cs +++ b/Numerics/Rational.cs @@ -50,6 +50,8 @@ public Rational( int num, int den ) { /// Returns the reciprocal of this number public Rational Reciprocal => new(d, n); + public bool IsInteger => d == 1; + /// Returns the absolute value of this number public Rational Abs() => new(n.Abs(), d); @@ -65,7 +67,7 @@ public Rational Pow( int pow ) => }; public override string ToString() => d == 1 ? n.ToString() : $"{n}/{d}"; - + // statics public static Rational Min( Rational a, Rational b ) => a < b ? a : b; public static Rational Max( Rational a, Rational b ) => a > b ? a : b; From df71723ab9eed470422d72f95e65a9e9fb51210b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 14:36:21 +0200 Subject: [PATCH 084/301] Rational zero constructor optimization --- Numerics/Rational.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Numerics/Rational.cs b/Numerics/Rational.cs index e012a9a..575abed 100644 --- a/Numerics/Rational.cs +++ b/Numerics/Rational.cs @@ -31,6 +31,11 @@ public Rational( int num, int den ) { ( n, d ) = ( num, den ); break; default: + if( num == 0 ) { + ( n, d ) = ( 0, 1 ); + break; + } + // ensure only the numerator carries the sign int sign = Mathfs.Sign( den ); n = sign * num; From d46029fc5dad3fe02542b9aa76ca529fd7e8e056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Jun 2022 14:37:28 +0200 Subject: [PATCH 085/301] optimized/unrolled codegen typecasting --- Codegen/Editor/MathfsCodegen.cs | 84 ++++++++++++++----- .../Uniform Spline Segments/BezierCubic1D.cs | 33 +++++--- .../Uniform Spline Segments/BezierCubic2D.cs | 60 +++++-------- .../Uniform Spline Segments/BezierCubic3D.cs | 37 ++++---- .../Uniform Spline Segments/CatRomCubic1D.cs | 33 +++++--- .../Uniform Spline Segments/CatRomCubic2D.cs | 65 +++++--------- .../Uniform Spline Segments/CatRomCubic3D.cs | 65 +++++--------- .../Uniform Spline Segments/HermiteCubic1D.cs | 33 +++++--- .../Uniform Spline Segments/HermiteCubic2D.cs | 39 +++++---- .../Uniform Spline Segments/HermiteCubic3D.cs | 39 +++++---- Splines/Uniform Spline Segments/UBSCubic1D.cs | 33 +++++--- Splines/Uniform Spline Segments/UBSCubic2D.cs | 57 +++++-------- Splines/Uniform Spline Segments/UBSCubic3D.cs | 56 +++++-------- 13 files changed, 322 insertions(+), 312 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 07f004b..f654a8f 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -17,8 +17,9 @@ class SplineType { public string[] paramNames; public string[] paramDescs; public string matrixName; + public RationalMatrix4x4 charMatrix; - public SplineType( int degree, string className, string prettyName, string matrixName, string[] paramNames, string[] paramDescs, string[] paramDescsQuad = null ) { + public SplineType( int degree, string className, string prettyName, string matrixName, RationalMatrix4x4 charMatrix, string[] paramNames, string[] paramDescs, string[] paramDescsQuad = null ) { this.degree = degree; this.className = className; this.prettyName = prettyName; @@ -26,6 +27,7 @@ public SplineType( int degree, string className, string prettyName, string matri this.paramDescs = paramDescs; this.matrixName = matrixName; this.paramNames = paramNames; + this.charMatrix = charMatrix; } public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { @@ -35,7 +37,7 @@ public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { #region Type Definitions - static SplineType typeBezier = new SplineType( 3, "Bezier", "Bézier", "cubicBezier", + static SplineType typeBezier = new SplineType( 3, "Bezier", "Bézier", "cubicBezier", CharMatrix.cubicBezier, new[] { "p0", "p1", "p2", "p3" }, new[] { "The starting point of the curve", @@ -45,7 +47,7 @@ public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { } ); - static SplineType typeBezierQuad = new SplineType( 2, "Bezier", "Bézier", "quadraticBezier", + static SplineType typeBezierQuad = new SplineType( 2, "Bezier", "Bézier", "quadraticBezier", default, new[] { "p0", "p1", "p2" }, new[] { "The starting point of the curve", @@ -54,7 +56,7 @@ public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { } ); - static SplineType typeHermite = new SplineType( 3, "Hermite", "Hermite", "cubicHermite", + static SplineType typeHermite = new SplineType( 3, "Hermite", "Hermite", "cubicHermite", CharMatrix.cubicHermite, new[] { "p0", "v0", "p1", "v1" }, new[] { "The starting point of the curve", @@ -64,7 +66,7 @@ public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { } ); - static SplineType typeBspline = new SplineType( 3, "UBS", "B-Spline", "cubicUniformBspline", + static SplineType typeBspline = new SplineType( 3, "UBS", "B-Spline", "cubicUniformBspline", CharMatrix.cubicUniformBspline, new[] { "p0", "p1", "p2", "p3" }, new[] { "The first point of the B-spline hull", @@ -74,7 +76,7 @@ public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { } ); - static SplineType typeCatRom = new SplineType( 3, "CatRom", "Catmull-Rom", "cubicCatmullRom", + static SplineType typeCatRom = new SplineType( 3, "CatRom", "Catmull-Rom", "cubicCatmullRom", CharMatrix.cubicCatmullRom, new[] { "p0", "p1", "p2", "p3" }, new[] { "The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it", @@ -209,6 +211,14 @@ static void GenerateType( SplineType type, int dim ) { using( code.Scope( "if( validCoefficients )" ) ) code.Append( "return; // no need to update" ); code.Append( "validCoefficients = true;" ); + + + // string line = "curve = "; + // for( int i = 0; i < dim; i++ ) { + // + // } + // code.Append( line ); + // todo: unroll matrix multiply for performance code.Append( $"curve = new {polynomType}( CharMatrix.{type.matrixName} * PointMatrix );" ); } @@ -229,9 +239,7 @@ static void GenerateType( SplineType type, int dim ) { string structName3D = $"{type.className}{degShortCapital}3D"; code.Summary( "Returns this spline segment in 3D, where z = 0" ); code.Param( "curve2D", "The 2D curve to cast to 3D" ); - using( code.BracketScope( $"public static explicit operator {structName3D}( {structName} curve2D )" ) ) { - code.Append( $"return new {structName3D}( {string.Join( ", ", points.Select( p => $"curve2D.{p}" ) )} );" ); - } + code.Append( $"public static explicit operator {structName3D}( {structName} curve2D ) => new {structName3D}( {string.Join( ", ", points.Select( p => $"curve2D.{p}" ) )} );" ); } if( dim == 3 ) { @@ -239,9 +247,7 @@ static void GenerateType( SplineType type, int dim ) { string structName2D = $"{type.className}{degShortCapital}2D"; code.Summary( "Returns this curve flattened to 2D. Effectively setting z = 0" ); code.Param( "curve3D", "The 3D curve to flatten to the Z plane" ); - using( code.BracketScope( $"public static explicit operator {structName2D}( {structName} curve3D )" ) ) { - code.Append( $"return new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p}" ) )} );" ); - } + code.Append( $"public static explicit operator {structName2D}( {structName} curve3D ) => new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p}" ) )} );" ); } } @@ -253,11 +259,11 @@ static void GenerateType( SplineType type, int dim ) { nameof(CatRomCubic1D).Replace( "1D", $"{dim}D" ), nameof(UBSCubic1D).Replace( "1D", $"{dim}D" ) }; - string[] typeMatrices = { - nameof(CharMatrix.cubicBezier), - nameof(CharMatrix.cubicHermite), - nameof(CharMatrix.cubicCatmullRom), - nameof(CharMatrix.cubicUniformBspline) + RationalMatrix4x4[] typeMatrices = { + CharMatrix.cubicBezier, + CharMatrix.cubicHermite, + CharMatrix.cubicCatmullRom, + CharMatrix.cubicUniformBspline }; // Conversion to other cubic splines @@ -265,11 +271,45 @@ static void GenerateType( SplineType type, int dim ) { string targetType = cubicSplineTypeNames[i]; if( targetType == structName ) continue; // don't convert to self - string v = type.className.ToLowerInvariant(); // var name - using( code.BracketScope( $"public static explicit operator {targetType}( {structName} {v} )" ) ) { - code.Append( $"{pointMatrixType} p = CharMatrix.GetConversionMatrix( CharMatrix.{type.matrixName}, CharMatrix.{typeMatrices[i]} ) * {v}.PointMatrix;" ); - int[] range4 = { 0, 1, 2, 3 }; - code.Append( $"return new {targetType}( {string.Join( ", ", range4.Select( j => $"p.m{j}" ) )} );" ); + RationalMatrix4x4 C = CharMatrix.GetConversionMatrix( type.charMatrix, typeMatrices[i] ); + + using( code.Scope( $"public static explicit operator {targetType}( {structName} s ) =>" ) ) { + using( code.Scope( $"new {targetType}(" ) ) { + for( int oPt = 0; oPt < 4; oPt++ ) { + string line = ""; + int entries = 0; + for( int iPt = 0; iPt < 4; iPt++ ) { + Rational value = C[oPt, iPt]; + if( value == 0 ) + continue; + + string FormatStr( Rational v ) => v.IsInteger ? $"{v.n}*" : $"({v}f)*"; + + string sign = entries > 0 && value >= 0 ? "+" : ""; + string valueStr; + if( value == Rational.One ) + valueStr = ""; + else if( value == -Rational.One ) + valueStr = "-"; + else if( value > 0 ) + valueStr = FormatStr( value ); + else { // value < 0 + valueStr = FormatStr( -value ); + sign = "-"; + } + + line += $"{sign}{valueStr}s.{type.paramNames[iPt]}"; + entries++; + } + + code.Append( $"{line}{( oPt < 3 ? "," : "" )}" ); + } + } + + code.Append( ");" ); + // code.Append( $"{pointMatrixType} p = CharMatrix.GetConversionMatrix( CharMatrix.{type.matrixName}, CharMatrix.{typeMatrices[i]} ) * {v}.PointMatrix;" ); + // int[] range4 = { 0, 1, 2, 3 }; + // code.Append( $"return new {targetType}( {string.Join( ", ", range4.Select( j => $"p.m{j}" ) )} );" ); } } } diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index aed9512..8b389bd 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -104,18 +104,27 @@ public float this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - public static explicit operator HermiteCubic1D( BezierCubic1D bezier ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicHermite ) * bezier.PointMatrix; - return new HermiteCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic1D( BezierCubic1D bezier ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicCatmullRom ) * bezier.PointMatrix; - return new CatRomCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic1D( BezierCubic1D bezier ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicUniformBspline ) * bezier.PointMatrix; - return new UBSCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator HermiteCubic1D( BezierCubic1D s ) => + new HermiteCubic1D( + s.p0, + -3*s.p0+3*s.p1, + s.p3, + -3*s.p2+3*s.p3 + ); + public static explicit operator CatRomCubic1D( BezierCubic1D s ) => + new CatRomCubic1D( + 6*s.p0-6*s.p1+s.p3, + s.p0, + s.p3, + s.p0-6*s.p2+6*s.p3 + ); + public static explicit operator UBSCubic1D( BezierCubic1D s ) => + new UBSCubic1D( + 6*s.p0-7*s.p1+2*s.p2, + 2*s.p1-s.p2, + -s.p1+2*s.p2, + 2*s.p1-7*s.p2+6*s.p3 + ); /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index a28878b..9ccd35b 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -106,21 +106,28 @@ public Vector2 this[ int i ] { public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D - public static explicit operator BezierCubic3D( BezierCubic2D curve2D ) { - return new BezierCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); - } - public static explicit operator HermiteCubic2D( BezierCubic2D bezier ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicHermite ) * bezier.PointMatrix; - return new HermiteCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic2D( BezierCubic2D bezier ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicCatmullRom ) * bezier.PointMatrix; - return new CatRomCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic2D( BezierCubic2D bezier ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicUniformBspline ) * bezier.PointMatrix; - return new UBSCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator BezierCubic3D( BezierCubic2D curve2D ) => new BezierCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); + public static explicit operator HermiteCubic2D( BezierCubic2D s ) => + new HermiteCubic2D( + s.p0, + -3*s.p0+3*s.p1, + s.p3, + -3*s.p2+3*s.p3 + ); + public static explicit operator CatRomCubic2D( BezierCubic2D s ) => + new CatRomCubic2D( + 6*s.p0-6*s.p1+s.p3, + s.p0, + s.p3, + s.p0-6*s.p2+6*s.p3 + ); + public static explicit operator UBSCubic2D( BezierCubic2D s ) => + new UBSCubic2D( + 6*s.p0-7*s.p1+2*s.p2, + 2*s.p1-s.p2, + -s.p1+2*s.p2, + 2*s.p1-7*s.p2+6*s.p3 + ); /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment @@ -170,28 +177,5 @@ public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { d.y + ( e.y - d.y ) * t ); return ( new BezierCubic2D( p0, a, d, p ), new BezierCubic2D( p, e, c, p3 ) ); } - - public UBSCubic2D ToUniformCubicBSpline() { - // todo: channel split for performance - return new UBSCubic2D( - 6 * p0 - 7 * p1 + 2 * p2, - 2 * p1 - p2, - -p1 + 2 * p2, - 2 * p1 - 7 * p2 + 6 * p3 ); - } - - public CatRomCubic2D ToUniformCubicCatRom() { - // todo: channel split for performance - return new CatRomCubic2D( - 6 * p0 - 6 * p1 + p3, - p0, - p3, - p0 - 6 * p2 + 6 * p3 ); - } - - public HermiteCubic2D ToHermite() { - // todo: channel split for performance - return new HermiteCubic2D( p0, ( p1 - p0 ) * 3, p3, ( p3 - p2 ) * 3 ); - } } } diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index 7089e7b..882c46d 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -106,21 +106,28 @@ public Vector3 this[ int i ] { public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane - public static explicit operator BezierCubic2D( BezierCubic3D curve3D ) { - return new BezierCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); - } - public static explicit operator HermiteCubic3D( BezierCubic3D bezier ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicHermite ) * bezier.PointMatrix; - return new HermiteCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic3D( BezierCubic3D bezier ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicCatmullRom ) * bezier.PointMatrix; - return new CatRomCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic3D( BezierCubic3D bezier ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicBezier, CharMatrix.cubicUniformBspline ) * bezier.PointMatrix; - return new UBSCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator BezierCubic2D( BezierCubic3D curve3D ) => new BezierCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); + public static explicit operator HermiteCubic3D( BezierCubic3D s ) => + new HermiteCubic3D( + s.p0, + -3*s.p0+3*s.p1, + s.p3, + -3*s.p2+3*s.p3 + ); + public static explicit operator CatRomCubic3D( BezierCubic3D s ) => + new CatRomCubic3D( + 6*s.p0-6*s.p1+s.p3, + s.p0, + s.p3, + s.p0-6*s.p2+6*s.p3 + ); + public static explicit operator UBSCubic3D( BezierCubic3D s ) => + new UBSCubic3D( + 6*s.p0-7*s.p1+2*s.p2, + 2*s.p1-s.p2, + -s.p1+2*s.p2, + 2*s.p1-7*s.p2+6*s.p3 + ); /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index 5c2405b..60b5340 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -104,18 +104,27 @@ public float this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - public static explicit operator BezierCubic1D( CatRomCubic1D catrom ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicBezier ) * catrom.PointMatrix; - return new BezierCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator HermiteCubic1D( CatRomCubic1D catrom ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicHermite ) * catrom.PointMatrix; - return new HermiteCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic1D( CatRomCubic1D catrom ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicUniformBspline ) * catrom.PointMatrix; - return new UBSCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator BezierCubic1D( CatRomCubic1D s ) => + new BezierCubic1D( + s.p1, + -(1/6f)*s.p0+s.p1+(1/6f)*s.p2, + (1/6f)*s.p1+s.p2-(1/6f)*s.p3, + s.p2 + ); + public static explicit operator HermiteCubic1D( CatRomCubic1D s ) => + new HermiteCubic1D( + s.p1, + -(1/2f)*s.p0+(1/2f)*s.p2, + s.p2, + -(1/2f)*s.p1+(1/2f)*s.p3 + ); + public static explicit operator UBSCubic1D( CatRomCubic1D s ) => + new UBSCubic1D( + (7/6f)*s.p0-(2/3f)*s.p1+(5/6f)*s.p2-(1/3f)*s.p3, + -(1/3f)*s.p0+(11/6f)*s.p1-(2/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0-(2/3f)*s.p1+(11/6f)*s.p2-(1/3f)*s.p3, + -(1/3f)*s.p0+(5/6f)*s.p1-(2/3f)*s.p2+(7/6f)*s.p3 + ); /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 795f8a6..0467aeb 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -106,21 +106,28 @@ public Vector2 this[ int i ] { public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D - public static explicit operator CatRomCubic3D( CatRomCubic2D curve2D ) { - return new CatRomCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); - } - public static explicit operator BezierCubic2D( CatRomCubic2D catrom ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicBezier ) * catrom.PointMatrix; - return new BezierCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator HermiteCubic2D( CatRomCubic2D catrom ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicHermite ) * catrom.PointMatrix; - return new HermiteCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic2D( CatRomCubic2D catrom ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicUniformBspline ) * catrom.PointMatrix; - return new UBSCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator CatRomCubic3D( CatRomCubic2D curve2D ) => new CatRomCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); + public static explicit operator BezierCubic2D( CatRomCubic2D s ) => + new BezierCubic2D( + s.p1, + -(1/6f)*s.p0+s.p1+(1/6f)*s.p2, + (1/6f)*s.p1+s.p2-(1/6f)*s.p3, + s.p2 + ); + public static explicit operator HermiteCubic2D( CatRomCubic2D s ) => + new HermiteCubic2D( + s.p1, + -(1/2f)*s.p0+(1/2f)*s.p2, + s.p2, + -(1/2f)*s.p1+(1/2f)*s.p3 + ); + public static explicit operator UBSCubic2D( CatRomCubic2D s ) => + new UBSCubic2D( + (7/6f)*s.p0-(2/3f)*s.p1+(5/6f)*s.p2-(1/3f)*s.p3, + -(1/3f)*s.p0+(11/6f)*s.p1-(2/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0-(2/3f)*s.p1+(11/6f)*s.p2-(1/3f)*s.p3, + -(1/3f)*s.p0+(5/6f)*s.p1-(2/3f)*s.p2+(7/6f)*s.p3 + ); /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment @@ -132,33 +139,5 @@ public static CatRomCubic2D Lerp( CatRomCubic2D a, CatRomCubic2D b, float t ) => Vector2.LerpUnclamped( a.p2, b.p2, t ), Vector2.LerpUnclamped( a.p3, b.p3, t ) ); - - /// Returns the bezier representation of the same curve - public BezierCubic2D ToBezier() => - new BezierCubic2D( - p1, - p1 + ( p2 - p0 ) / 6f, - p2 + ( p1 - p3 ) / 6f, - p2 - ); - - /// Returns the hermite representation of the same curve - public HermiteCubic2D ToHermite() => - new HermiteCubic2D( - p1, - ( p2 - p0 ) / 2f, - p2, - ( p3 - p1 ) / 2f - ); - - /// Returns the bspline representation of the same curve - public UBSCubic2D ToBSpline() => - new UBSCubic2D( - ( 7 * p0 - 4 * p1 + 5 * p2 - 2 * p3 ) / 6, - ( -2 * p0 + 11 * p1 - 4 * p2 + p3 ) / 6, - ( p0 - 4 * p1 + 11 * p2 - 2 * p3 ) / 6, - ( -2 * p0 + 5 * p1 - 4 * p2 + 7 * p3 ) / 6 - ); - } } diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 2a77df8..9604deb 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -106,21 +106,28 @@ public Vector3 this[ int i ] { public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane - public static explicit operator CatRomCubic2D( CatRomCubic3D curve3D ) { - return new CatRomCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); - } - public static explicit operator BezierCubic3D( CatRomCubic3D catrom ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicBezier ) * catrom.PointMatrix; - return new BezierCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator HermiteCubic3D( CatRomCubic3D catrom ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicHermite ) * catrom.PointMatrix; - return new HermiteCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic3D( CatRomCubic3D catrom ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicCatmullRom, CharMatrix.cubicUniformBspline ) * catrom.PointMatrix; - return new UBSCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator CatRomCubic2D( CatRomCubic3D curve3D ) => new CatRomCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); + public static explicit operator BezierCubic3D( CatRomCubic3D s ) => + new BezierCubic3D( + s.p1, + -(1/6f)*s.p0+s.p1+(1/6f)*s.p2, + (1/6f)*s.p1+s.p2-(1/6f)*s.p3, + s.p2 + ); + public static explicit operator HermiteCubic3D( CatRomCubic3D s ) => + new HermiteCubic3D( + s.p1, + -(1/2f)*s.p0+(1/2f)*s.p2, + s.p2, + -(1/2f)*s.p1+(1/2f)*s.p3 + ); + public static explicit operator UBSCubic3D( CatRomCubic3D s ) => + new UBSCubic3D( + (7/6f)*s.p0-(2/3f)*s.p1+(5/6f)*s.p2-(1/3f)*s.p3, + -(1/3f)*s.p0+(11/6f)*s.p1-(2/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0-(2/3f)*s.p1+(11/6f)*s.p2-(1/3f)*s.p3, + -(1/3f)*s.p0+(5/6f)*s.p1-(2/3f)*s.p2+(7/6f)*s.p3 + ); /// Returns a linear blend between two catmull-rom curves /// The first spline segment /// The second spline segment @@ -132,33 +139,5 @@ public static CatRomCubic3D Lerp( CatRomCubic3D a, CatRomCubic3D b, float t ) => Vector3.LerpUnclamped( a.p2, b.p2, t ), Vector3.LerpUnclamped( a.p3, b.p3, t ) ); - - /// - public BezierCubic3D ToBezier() => - new BezierCubic3D( - p1, - p1 + ( p2 - p0 ) / 6f, - p2 + ( p1 - p3 ) / 6f, - p2 - ); - - /// - public HermiteCubic3D ToHermite() => - new HermiteCubic3D( - p1, - ( p2 - p0 ) / 2f, - p2, - ( p3 - p1 ) / 2f - ); - - /// - public UBSCubic3D ToBSpline() => - new UBSCubic3D( - ( 7 * p0 - 4 * p1 + 5 * p2 - 2 * p3 ) / 6, - ( -2 * p0 + 11 * p1 - 4 * p2 + p3 ) / 6, - ( p0 - 4 * p1 + 11 * p2 - 2 * p3 ) / 6, - ( -2 * p0 + 5 * p1 - 4 * p2 + 7 * p3 ) / 6 - ); - } } diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index c70a7bc..f6af87e 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -104,18 +104,27 @@ public float this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; - public static explicit operator BezierCubic1D( HermiteCubic1D hermite ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicBezier ) * hermite.PointMatrix; - return new BezierCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic1D( HermiteCubic1D hermite ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicCatmullRom ) * hermite.PointMatrix; - return new CatRomCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic1D( HermiteCubic1D hermite ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicUniformBspline ) * hermite.PointMatrix; - return new UBSCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator BezierCubic1D( HermiteCubic1D s ) => + new BezierCubic1D( + s.p0, + s.p0+(1/3f)*s.v0, + s.p1-(1/3f)*s.v1, + s.p1 + ); + public static explicit operator CatRomCubic1D( HermiteCubic1D s ) => + new CatRomCubic1D( + -2*s.v0+s.p1, + s.p0, + s.p1, + s.p0+2*s.v1 + ); + public static explicit operator UBSCubic1D( HermiteCubic1D s ) => + new UBSCubic1D( + -s.p0-(7/3f)*s.v0+2*s.p1-(2/3f)*s.v1, + 2*s.p0+(2/3f)*s.v0-s.p1+(1/3f)*s.v1, + -s.p0-(1/3f)*s.v0+2*s.p1-(2/3f)*s.v1, + 2*s.p0+(2/3f)*s.v0-s.p1+(7/3f)*s.v1 + ); /// Returns a linear blend between two hermite curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index b34a636..20baab2 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -97,8 +97,6 @@ public Vector2 this[ int i ] { validCoefficients = true; curve = new Polynomial2D( CharMatrix.cubicHermite * PointMatrix ); } - - public BezierCubic2D ToBezier() => new BezierCubic2D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); public static bool operator ==( HermiteCubic2D a, HermiteCubic2D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; public static bool operator !=( HermiteCubic2D a, HermiteCubic2D b ) => !( a == b ); public bool Equals( HermiteCubic2D other ) => P0.Equals( other.P0 ) && V0.Equals( other.V0 ) && P1.Equals( other.P1 ) && V1.Equals( other.V1 ); @@ -108,21 +106,28 @@ public Vector2 this[ int i ] { public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D - public static explicit operator HermiteCubic3D( HermiteCubic2D curve2D ) { - return new HermiteCubic3D( curve2D.p0, curve2D.v0, curve2D.p1, curve2D.v1 ); - } - public static explicit operator BezierCubic2D( HermiteCubic2D hermite ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicBezier ) * hermite.PointMatrix; - return new BezierCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic2D( HermiteCubic2D hermite ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicCatmullRom ) * hermite.PointMatrix; - return new CatRomCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic2D( HermiteCubic2D hermite ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicUniformBspline ) * hermite.PointMatrix; - return new UBSCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator HermiteCubic3D( HermiteCubic2D curve2D ) => new HermiteCubic3D( curve2D.p0, curve2D.v0, curve2D.p1, curve2D.v1 ); + public static explicit operator BezierCubic2D( HermiteCubic2D s ) => + new BezierCubic2D( + s.p0, + s.p0+(1/3f)*s.v0, + s.p1-(1/3f)*s.v1, + s.p1 + ); + public static explicit operator CatRomCubic2D( HermiteCubic2D s ) => + new CatRomCubic2D( + -2*s.v0+s.p1, + s.p0, + s.p1, + s.p0+2*s.v1 + ); + public static explicit operator UBSCubic2D( HermiteCubic2D s ) => + new UBSCubic2D( + -s.p0-(7/3f)*s.v0+2*s.p1-(2/3f)*s.v1, + 2*s.p0+(2/3f)*s.v0-s.p1+(1/3f)*s.v1, + -s.p0-(1/3f)*s.v0+2*s.p1-(2/3f)*s.v1, + 2*s.p0+(2/3f)*s.v0-s.p1+(7/3f)*s.v1 + ); /// Returns a linear blend between two hermite curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index 8122e6c..a682d1c 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -103,26 +103,31 @@ public Vector3 this[ int i ] { public override bool Equals( object obj ) => obj is HermiteCubic3D other && Equals( other ); public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); - public BezierCubic3D ToBezier() => new BezierCubic3D( p0, p0 + v0 / 3, p1 - v1 / 3, p1 ); - public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane - public static explicit operator HermiteCubic2D( HermiteCubic3D curve3D ) { - return new HermiteCubic2D( curve3D.p0, curve3D.v0, curve3D.p1, curve3D.v1 ); - } - public static explicit operator BezierCubic3D( HermiteCubic3D hermite ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicBezier ) * hermite.PointMatrix; - return new BezierCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic3D( HermiteCubic3D hermite ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicCatmullRom ) * hermite.PointMatrix; - return new CatRomCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator UBSCubic3D( HermiteCubic3D hermite ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicHermite, CharMatrix.cubicUniformBspline ) * hermite.PointMatrix; - return new UBSCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator HermiteCubic2D( HermiteCubic3D curve3D ) => new HermiteCubic2D( curve3D.p0, curve3D.v0, curve3D.p1, curve3D.v1 ); + public static explicit operator BezierCubic3D( HermiteCubic3D s ) => + new BezierCubic3D( + s.p0, + s.p0+(1/3f)*s.v0, + s.p1-(1/3f)*s.v1, + s.p1 + ); + public static explicit operator CatRomCubic3D( HermiteCubic3D s ) => + new CatRomCubic3D( + -2*s.v0+s.p1, + s.p0, + s.p1, + s.p0+2*s.v1 + ); + public static explicit operator UBSCubic3D( HermiteCubic3D s ) => + new UBSCubic3D( + -s.p0-(7/3f)*s.v0+2*s.p1-(2/3f)*s.v1, + 2*s.p0+(2/3f)*s.v0-s.p1+(1/3f)*s.v1, + -s.p0-(1/3f)*s.v0+2*s.p1-(2/3f)*s.v1, + 2*s.p0+(2/3f)*s.v0-s.p1+(7/3f)*s.v1 + ); /// Returns a linear blend between two hermite curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index 5f68e24..83e680c 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -104,18 +104,27 @@ public float this[ int i ] { public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; - public static explicit operator BezierCubic1D( UBSCubic1D ubs ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicBezier ) * ubs.PointMatrix; - return new BezierCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator HermiteCubic1D( UBSCubic1D ubs ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicHermite ) * ubs.PointMatrix; - return new HermiteCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic1D( UBSCubic1D ubs ) { - Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicCatmullRom ) * ubs.PointMatrix; - return new CatRomCubic1D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator BezierCubic1D( UBSCubic1D s ) => + new BezierCubic1D( + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + (2/3f)*s.p1+(1/3f)*s.p2, + (1/3f)*s.p1+(2/3f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3 + ); + public static explicit operator HermiteCubic1D( UBSCubic1D s ) => + new HermiteCubic1D( + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + -(1/2f)*s.p0+(1/2f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, + -(1/2f)*s.p1+(1/2f)*s.p3 + ); + public static explicit operator CatRomCubic1D( UBSCubic1D s ) => + new CatRomCubic1D( + s.p0+(1/6f)*s.p1-(1/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0-(1/3f)*s.p1+(1/6f)*s.p2+s.p3 + ); /// Returns a linear blend between two b-spline curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index 35c54a0..7f0833c 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -97,26 +97,6 @@ public Vector2 this[ int i ] { validCoefficients = true; curve = new Polynomial2D( CharMatrix.cubicUniformBspline * PointMatrix ); } - - /// Returns the exact cubic bézier representation of this segment - public BezierCubic2D ToBezier() { - const float _13 = 1f / 3f; - const float _23 = 2f / 3f; - float ax = p0.x + _23 * ( p1.x - p0.x ); - float bx = p1.x + _13 * ( p2.x - p1.x ); - float cx = p1.x + _23 * ( p2.x - p1.x ); - float dx = p2.x + _13 * ( p3.x - p2.x ); - float ay = p0.y + _23 * ( p1.y - p0.y ); - float by = p1.y + _13 * ( p2.y - p1.y ); - float cy = p1.y + _23 * ( p2.y - p1.y ); - float dy = p2.y + _13 * ( p3.y - p2.y ); - return new BezierCubic2D( - new Vector2( 0.5f * ( ax + bx ), 0.5f * ( ay + by ) ), - new Vector2( bx, by ), - new Vector2( cx, cy ), - new Vector2( 0.5f * ( cx + dx ), 0.5f * ( cy + dy ) ) - ); - } public static bool operator ==( UBSCubic2D a, UBSCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( UBSCubic2D a, UBSCubic2D b ) => !( a == b ); public bool Equals( UBSCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); @@ -126,21 +106,28 @@ public BezierCubic2D ToBezier() { public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D - public static explicit operator UBSCubic3D( UBSCubic2D curve2D ) { - return new UBSCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); - } - public static explicit operator BezierCubic2D( UBSCubic2D ubs ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicBezier ) * ubs.PointMatrix; - return new BezierCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator HermiteCubic2D( UBSCubic2D ubs ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicHermite ) * ubs.PointMatrix; - return new HermiteCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic2D( UBSCubic2D ubs ) { - Vector2Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicCatmullRom ) * ubs.PointMatrix; - return new CatRomCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator UBSCubic3D( UBSCubic2D curve2D ) => new UBSCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); + public static explicit operator BezierCubic2D( UBSCubic2D s ) => + new BezierCubic2D( + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + (2/3f)*s.p1+(1/3f)*s.p2, + (1/3f)*s.p1+(2/3f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3 + ); + public static explicit operator HermiteCubic2D( UBSCubic2D s ) => + new HermiteCubic2D( + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + -(1/2f)*s.p0+(1/2f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, + -(1/2f)*s.p1+(1/2f)*s.p3 + ); + public static explicit operator CatRomCubic2D( UBSCubic2D s ) => + new CatRomCubic2D( + s.p0+(1/6f)*s.p1-(1/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0-(1/3f)*s.p1+(1/6f)*s.p2+s.p3 + ); /// Returns a linear blend between two b-spline curves /// The first spline segment /// The second spline segment diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index 1a8ae54..8c0610b 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -103,43 +103,31 @@ public Vector3 this[ int i ] { public override bool Equals( object obj ) => obj is UBSCubic3D other && Equals( other ); public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); - /// - public BezierCubic3D ToBezier() { - const float _13 = 1f / 3f; - const float _23 = 2f / 3f; - float ax = p0.x + _23 * ( p1.x - p0.x ); - float bx = p1.x + _13 * ( p2.x - p1.x ); - float cx = p1.x + _23 * ( p2.x - p1.x ); - float dx = p2.x + _13 * ( p3.x - p2.x ); - float ay = p0.y + _23 * ( p1.y - p0.y ); - float by = p1.y + _13 * ( p2.y - p1.y ); - float cy = p1.y + _23 * ( p2.y - p1.y ); - float dy = p2.y + _13 * ( p3.y - p2.y ); - return new BezierCubic3D( - new Vector3( 0.5f * ( ax + bx ), 0.5f * ( ay + by ) ), - new Vector3( bx, by ), - new Vector3( cx, cy ), - new Vector3( 0.5f * ( cx + dx ), 0.5f * ( cy + dy ) ) - ); - } public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane - public static explicit operator UBSCubic2D( UBSCubic3D curve3D ) { - return new UBSCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); - } - public static explicit operator BezierCubic3D( UBSCubic3D ubs ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicBezier ) * ubs.PointMatrix; - return new BezierCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator HermiteCubic3D( UBSCubic3D ubs ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicHermite ) * ubs.PointMatrix; - return new HermiteCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - public static explicit operator CatRomCubic3D( UBSCubic3D ubs ) { - Vector3Matrix4x1 p = CharMatrix.GetConversionMatrix( CharMatrix.cubicUniformBspline, CharMatrix.cubicCatmullRom ) * ubs.PointMatrix; - return new CatRomCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } + public static explicit operator UBSCubic2D( UBSCubic3D curve3D ) => new UBSCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); + public static explicit operator BezierCubic3D( UBSCubic3D s ) => + new BezierCubic3D( + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + (2/3f)*s.p1+(1/3f)*s.p2, + (1/3f)*s.p1+(2/3f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3 + ); + public static explicit operator HermiteCubic3D( UBSCubic3D s ) => + new HermiteCubic3D( + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + -(1/2f)*s.p0+(1/2f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, + -(1/2f)*s.p1+(1/2f)*s.p3 + ); + public static explicit operator CatRomCubic3D( UBSCubic3D s ) => + new CatRomCubic3D( + s.p0+(1/6f)*s.p1-(1/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, + (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, + (1/6f)*s.p0-(1/3f)*s.p1+(1/6f)*s.p2+s.p3 + ); /// Returns a linear blend between two b-spline curves /// The first spline segment /// The second spline segment From f1eba3cb328f63d26786e88bc51d100190971247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 10 Jun 2022 23:07:35 +0200 Subject: [PATCH 086/301] MathSum utility class for codegen --- Codegen/Editor/MathfsCodegen.cs | 75 ++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index f654a8f..f54be90 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -1,6 +1,7 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; +using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; @@ -276,40 +277,14 @@ static void GenerateType( SplineType type, int dim ) { using( code.Scope( $"public static explicit operator {targetType}( {structName} s ) =>" ) ) { using( code.Scope( $"new {targetType}(" ) ) { for( int oPt = 0; oPt < 4; oPt++ ) { - string line = ""; - int entries = 0; - for( int iPt = 0; iPt < 4; iPt++ ) { - Rational value = C[oPt, iPt]; - if( value == 0 ) - continue; - - string FormatStr( Rational v ) => v.IsInteger ? $"{v.n}*" : $"({v}f)*"; - - string sign = entries > 0 && value >= 0 ? "+" : ""; - string valueStr; - if( value == Rational.One ) - valueStr = ""; - else if( value == -Rational.One ) - valueStr = "-"; - else if( value > 0 ) - valueStr = FormatStr( value ); - else { // value < 0 - valueStr = FormatStr( -value ); - sign = "-"; - } - - line += $"{sign}{valueStr}s.{type.paramNames[iPt]}"; - entries++; - } - - code.Append( $"{line}{( oPt < 3 ? "," : "" )}" ); + MathSum sum = new(); + for( int iPt = 0; iPt < 4; iPt++ ) + sum.AddTerm( C[oPt, iPt], $"s.{type.paramNames[iPt]}" ); + code.Append( $"{sum}{( oPt < 3 ? "," : "" )}" ); } } code.Append( ");" ); - // code.Append( $"{pointMatrixType} p = CharMatrix.GetConversionMatrix( CharMatrix.{type.matrixName}, CharMatrix.{typeMatrices[i]} ) * {v}.PointMatrix;" ); - // int[] range4 = { 0, 1, 2, 3 }; - // code.Append( $"return new {targetType}( {string.Join( ", ", range4.Select( j => $"p.m{j}" ) )} );" ); } } } @@ -369,6 +344,46 @@ static void GenerateType( SplineType type, int dim ) { File.WriteAllLines( path, code.content ); } + class MathSum { + List<(Rational coeff, string var)> terms = new List<(Rational coeff, string var)>(); + + public void AddTerm( Rational coeff, string var ) { + if( coeff != 0 ) + terms.Add( ( coeff, var ) ); + } + + public override string ToString() { + if( terms.Count == 0 ) + return "0"; + + string line = ""; + string FormatStr( Rational v ) => v.IsInteger ? $"{v.n}" : $"({v}f)"; + + for( int i = 0; i < terms.Count; i++ ) { + Rational value = terms[i].coeff; + string sign = i > 0 && value >= 0 ? "+" : ""; + string valueStr; + string op = ""; + if( value == Rational.One ) + valueStr = ""; + else if( value == -Rational.One ) + valueStr = "-"; + else if( value > 0 ) { + valueStr = FormatStr( value ); + op = "*"; + } else { // value < 0 + valueStr = FormatStr( -value ); + sign = "-"; + op = "*"; + } + + line += $"{sign}{valueStr}{op}{terms[i].var}"; + } + + return line; + } + } + public static string GetDegreeName( int d, bool shortName ) { return d switch { 1 => "Linear", From 60a3332ad75510f5ab9fb3c0290eecb09b4727c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 10 Jun 2022 23:50:53 +0200 Subject: [PATCH 087/301] codegen optimized spline coefficient calcs also the spline conversion is now factored a little bit better --- Codegen/Editor/MathfsCodegen.cs | 89 +++++++++++++------ Curves/Polynomial.cs | 6 ++ Curves/Polynomial2D.cs | 7 ++ Curves/Polynomial3D.cs | 15 +++- Numerics/RationalMatrix4x4.cs | 6 ++ .../Uniform Spline Segments/BezierCubic1D.cs | 11 ++- .../Uniform Spline Segments/BezierCubic2D.cs | 11 ++- .../Uniform Spline Segments/BezierCubic3D.cs | 11 ++- .../Uniform Spline Segments/BezierQuad1D.cs | 6 +- .../Uniform Spline Segments/BezierQuad2D.cs | 6 +- .../Uniform Spline Segments/BezierQuad3D.cs | 6 +- .../Uniform Spline Segments/CatRomCubic1D.cs | 11 ++- .../Uniform Spline Segments/CatRomCubic2D.cs | 11 ++- .../Uniform Spline Segments/CatRomCubic3D.cs | 11 ++- .../Uniform Spline Segments/HermiteCubic1D.cs | 7 +- .../Uniform Spline Segments/HermiteCubic2D.cs | 7 +- .../Uniform Spline Segments/HermiteCubic3D.cs | 7 +- Splines/Uniform Spline Segments/UBSCubic1D.cs | 11 ++- Splines/Uniform Spline Segments/UBSCubic2D.cs | 11 ++- Splines/Uniform Spline Segments/UBSCubic3D.cs | 11 ++- 20 files changed, 200 insertions(+), 61 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index f54be90..0d31cdb 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -48,7 +48,7 @@ public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { } ); - static SplineType typeBezierQuad = new SplineType( 2, "Bezier", "Bézier", "quadraticBezier", default, + static SplineType typeBezierQuad = new SplineType( 2, "Bezier", "Bézier", "quadraticBezier", (RationalMatrix4x4)CharMatrix.quadraticBezier, new[] { "p0", "p1", "p2" }, new[] { "The starting point of the curve", @@ -214,14 +214,19 @@ static void GenerateType( SplineType type, int dim ) { code.Append( "validCoefficients = true;" ); - // string line = "curve = "; - // for( int i = 0; i < dim; i++ ) { - // - // } - // code.Append( line ); + using( code.Scope( $"curve = new {polynomType}(" ) ) { + for( int icRow = 0; icRow < ptCount; icRow++ ) { + MathSum sum = new MathSum(); + for( int ip = 0; ip < ptCount; ip++ ) + sum.AddTerm( type.charMatrix[icRow, ip], $"{type.paramNames[ip]}" ); + code.Append( $"{sum}{( icRow < ptCount - 1 ? "," : "" )}" ); + } + } + + code.Append( ");" ); // todo: unroll matrix multiply for performance - code.Append( $"curve = new {polynomType}( CharMatrix.{type.matrixName} * PointMatrix );" ); + // code.Append( $"curve = new {polynomType}( CharMatrix.{type.matrixName} * PointMatrix );" ); } // equality checks @@ -345,6 +350,8 @@ static void GenerateType( SplineType type, int dim ) { } class MathSum { + + Rational globalScale = Rational.One; List<(Rational coeff, string var)> terms = new List<(Rational coeff, string var)>(); public void AddTerm( Rational coeff, string var ) { @@ -352,36 +359,64 @@ public void AddTerm( Rational coeff, string var ) { terms.Add( ( coeff, var ) ); } + void TryOptimize() { + if( terms.Count < 2 ) + return; // can't optimize 0 or 1 terms + + Rational coeff0 = terms[0].coeff.Abs(); + if( terms.TrueForAll( t => t.coeff.Abs() == coeff0 ) ) { + globalScale = coeff0; + for( int i = 0; i < terms.Count; i++ ) + terms[i] = ( terms[i].coeff / coeff0, terms[i].var ); + } + } + public override string ToString() { if( terms.Count == 0 ) return "0"; + TryOptimize(); + string line = ""; - string FormatStr( Rational v ) => v.IsInteger ? $"{v.n}" : $"({v}f)"; - - for( int i = 0; i < terms.Count; i++ ) { - Rational value = terms[i].coeff; - string sign = i > 0 && value >= 0 ? "+" : ""; - string valueStr; - string op = ""; - if( value == Rational.One ) - valueStr = ""; - else if( value == -Rational.One ) - valueStr = "-"; - else if( value > 0 ) { - valueStr = FormatStr( value ); - op = "*"; - } else { // value < 0 - valueStr = FormatStr( -value ); - sign = "-"; - op = "*"; - } + for( int i = 0; i < terms.Count; i++ ) + line += FormatTerm( i ); + + if( globalScale != 1 ) { - line += $"{sign}{valueStr}{op}{terms[i].var}"; + if( globalScale.n == 1 ) { + line = $"({line})/{globalScale.d}"; + } else { + line = $"{FormatRational( globalScale )}*({line})"; + } + } return line; } + + string FormatRational( Rational v ) => v.IsInteger ? $"{v.n}" : $"({v}f)"; + + string FormatTerm( int i ) { + Rational value = terms[i].coeff; + string sign = i > 0 && value >= 0 ? "+" : ""; + string valueStr; + string op = ""; + if( value == Rational.One ) + valueStr = ""; + else if( value == -Rational.One ) + valueStr = "-"; + else if( value > 0 ) { + valueStr = FormatRational( value ); + op = "*"; + } else { // value < 0 + valueStr = FormatRational( -value ); + sign = "-"; + op = "*"; + } + + return $"{sign}{valueStr}{op}{terms[i].var}"; + } + } public static string GetDegreeName( int d, bool shortName ) { diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index 89c6600..3cb9ff7 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -59,6 +59,12 @@ public float this[ int degree ] { /// The cubic coefficient public Polynomial( float c0, float c1, float c2, float c3 ) => ( this.c0, this.c1, this.c2, this.c3 ) = ( c0, c1, c2, c3 ); + /// Creates a polynomial up to a quadratic + /// The constant coefficient + /// The linear coefficient + /// The quadratic coefficient + public Polynomial( float c0, float c1, float c2 ) => ( this.c0, this.c1, this.c2, this.c3 ) = ( c0, c1, c2, 0 ); + /// Creates a polynomial /// The coefficients to use public Polynomial( Vector4 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.x, coefficients.y, coefficients.z, coefficients.w ); diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index 4c9c0c3..9342b75 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -32,11 +32,18 @@ public Vector2 C3 { public Polynomial2D( Polynomial x, Polynomial y ) => ( this.x, this.y ) = ( x, y ); + /// public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2, Vector2 c3 ) { this.x = new Polynomial( c0.x, c1.x, c2.x, c3.x ); this.y = new Polynomial( c0.y, c1.y, c2.y, c3.y ); } + /// + public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2 ) { + this.x = new Polynomial( c0.x, c1.x, c2.x ); + this.y = new Polynomial( c0.y, c1.y, c2.y ); + } + /// public Polynomial2D( Vector2Matrix4x1 coefficients ) => ( x, y ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ) ); diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs index 1bf5900..bc14193 100644 --- a/Curves/Polynomial3D.cs +++ b/Curves/Polynomial3D.cs @@ -28,11 +28,24 @@ public Vector3 C3 { set => ( x.c3, y.c3, z.c3 ) = ( value.x, value.y, value.z ); } - public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, _ => throw new IndexOutOfRangeException( "Polynomial3D component index has to be either 0, 1, or 2" ) }; public Polynomial3D( Polynomial x, Polynomial y, Polynomial z ) => ( this.x, this.y, this.z ) = ( x, y, z ); + /// + public Polynomial3D( Vector3 c0, Vector3 c1, Vector3 c2, Vector3 c3 ) { + this.x = new Polynomial( c0.x, c1.x, c2.x, c3.x ); + this.y = new Polynomial( c0.y, c1.y, c2.y, c3.y ); + this.z = new Polynomial( c0.z, c1.z, c2.z, c3.z ); + } + + /// + public Polynomial3D( Vector3 c0, Vector3 c1, Vector3 c2 ) { + this.x = new Polynomial( c0.x, c1.x, c2.x, 0 ); + this.y = new Polynomial( c0.y, c1.y, c2.y, 0 ); + this.z = new Polynomial( c0.z, c1.z, c2.z, 0 ); + } + /// public Polynomial3D( Vector3Matrix4x1 coefficients ) => ( x, y, z ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ) ); diff --git a/Numerics/RationalMatrix4x4.cs b/Numerics/RationalMatrix4x4.cs index f89813f..ea23651 100644 --- a/Numerics/RationalMatrix4x4.cs +++ b/Numerics/RationalMatrix4x4.cs @@ -118,6 +118,12 @@ public Rational Determinant { c.m20 * v, c.m21 * v, c.m22 * v, c.m23 * v, c.m30 * v, c.m31 * v, c.m32 * v, c.m33 * v); + public static explicit operator RationalMatrix4x4( RationalMatrix3x3 c ) => + new(c.m00, c.m01, c.m02, 0, + c.m10, c.m11, c.m12, 0, + c.m20, c.m21, c.m22, 0, + 0, 0, 0, 1); + public static RationalMatrix4x4 operator /( RationalMatrix4x4 c, Rational v ) => c * v.Reciprocal; diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index 8b389bd..f21ac44 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -95,7 +95,12 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial( CharMatrix.cubicBezier * PointMatrix ); + curve = new Polynomial( + p0, + 3*(-p0+p1), + 3*p0-6*p1+3*p2, + -p0+3*p1-3*p2+p3 + ); } public static bool operator ==( BezierCubic1D a, BezierCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic1D a, BezierCubic1D b ) => !( a == b ); @@ -107,9 +112,9 @@ public float this[ int i ] { public static explicit operator HermiteCubic1D( BezierCubic1D s ) => new HermiteCubic1D( s.p0, - -3*s.p0+3*s.p1, + 3*(-s.p0+s.p1), s.p3, - -3*s.p2+3*s.p3 + 3*(-s.p2+s.p3) ); public static explicit operator CatRomCubic1D( BezierCubic1D s ) => new CatRomCubic1D( diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index 9ccd35b..72de4c0 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -95,7 +95,12 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial2D( CharMatrix.cubicBezier * PointMatrix ); + curve = new Polynomial2D( + p0, + 3*(-p0+p1), + 3*p0-6*p1+3*p2, + -p0+3*p1-3*p2+p3 + ); } public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic2D a, BezierCubic2D b ) => !( a == b ); @@ -110,9 +115,9 @@ public Vector2 this[ int i ] { public static explicit operator HermiteCubic2D( BezierCubic2D s ) => new HermiteCubic2D( s.p0, - -3*s.p0+3*s.p1, + 3*(-s.p0+s.p1), s.p3, - -3*s.p2+3*s.p3 + 3*(-s.p2+s.p3) ); public static explicit operator CatRomCubic2D( BezierCubic2D s ) => new CatRomCubic2D( diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index 882c46d..d4f87d3 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -95,7 +95,12 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial3D( CharMatrix.cubicBezier * PointMatrix ); + curve = new Polynomial3D( + p0, + 3*(-p0+p1), + 3*p0-6*p1+3*p2, + -p0+3*p1-3*p2+p3 + ); } public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( BezierCubic3D a, BezierCubic3D b ) => !( a == b ); @@ -110,9 +115,9 @@ public Vector3 this[ int i ] { public static explicit operator HermiteCubic3D( BezierCubic3D s ) => new HermiteCubic3D( s.p0, - -3*s.p0+3*s.p1, + 3*(-s.p0+s.p1), s.p3, - -3*s.p2+3*s.p3 + 3*(-s.p2+s.p3) ); public static explicit operator CatRomCubic3D( BezierCubic3D s ) => new CatRomCubic3D( diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Splines/Uniform Spline Segments/BezierQuad1D.cs index 98ae2fb..6e2a066 100644 --- a/Splines/Uniform Spline Segments/BezierQuad1D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad1D.cs @@ -84,7 +84,11 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial( CharMatrix.quadraticBezier * PointMatrix ); + curve = new Polynomial( + p0, + 2*(-p0+p1), + p0-2*p1+p2 + ); } public static bool operator ==( BezierQuad1D a, BezierQuad1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad1D a, BezierQuad1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index ebf1cfc..07ad412 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -84,7 +84,11 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial2D( CharMatrix.quadraticBezier * PointMatrix ); + curve = new Polynomial2D( + p0, + 2*(-p0+p1), + p0-2*p1+p2 + ); } public static bool operator ==( BezierQuad2D a, BezierQuad2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad2D a, BezierQuad2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index ac05f09..fa45f13 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -84,7 +84,11 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial3D( CharMatrix.quadraticBezier * PointMatrix ); + curve = new Polynomial3D( + p0, + 2*(-p0+p1), + p0-2*p1+p2 + ); } public static bool operator ==( BezierQuad3D a, BezierQuad3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; public static bool operator !=( BezierQuad3D a, BezierQuad3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index 60b5340..e041593 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -95,7 +95,12 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial( CharMatrix.cubicCatmullRom * PointMatrix ); + curve = new Polynomial( + p1, + (-p0+p2)/2, + p0-(5/2f)*p1+2*p2-(1/2f)*p3, + -(1/2f)*p0+(3/2f)*p1-(3/2f)*p2+(1/2f)*p3 + ); } public static bool operator ==( CatRomCubic1D a, CatRomCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic1D a, CatRomCubic1D b ) => !( a == b ); @@ -114,9 +119,9 @@ public static explicit operator BezierCubic1D( CatRomCubic1D s ) => public static explicit operator HermiteCubic1D( CatRomCubic1D s ) => new HermiteCubic1D( s.p1, - -(1/2f)*s.p0+(1/2f)*s.p2, + (-s.p0+s.p2)/2, s.p2, - -(1/2f)*s.p1+(1/2f)*s.p3 + (-s.p1+s.p3)/2 ); public static explicit operator UBSCubic1D( CatRomCubic1D s ) => new UBSCubic1D( diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 0467aeb..271f1af 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -95,7 +95,12 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial2D( CharMatrix.cubicCatmullRom * PointMatrix ); + curve = new Polynomial2D( + p1, + (-p0+p2)/2, + p0-(5/2f)*p1+2*p2-(1/2f)*p3, + -(1/2f)*p0+(3/2f)*p1-(3/2f)*p2+(1/2f)*p3 + ); } public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); @@ -117,9 +122,9 @@ public static explicit operator BezierCubic2D( CatRomCubic2D s ) => public static explicit operator HermiteCubic2D( CatRomCubic2D s ) => new HermiteCubic2D( s.p1, - -(1/2f)*s.p0+(1/2f)*s.p2, + (-s.p0+s.p2)/2, s.p2, - -(1/2f)*s.p1+(1/2f)*s.p3 + (-s.p1+s.p3)/2 ); public static explicit operator UBSCubic2D( CatRomCubic2D s ) => new UBSCubic2D( diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 9604deb..44492cd 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -95,7 +95,12 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial3D( CharMatrix.cubicCatmullRom * PointMatrix ); + curve = new Polynomial3D( + p1, + (-p0+p2)/2, + p0-(5/2f)*p1+2*p2-(1/2f)*p3, + -(1/2f)*p0+(3/2f)*p1-(3/2f)*p2+(1/2f)*p3 + ); } public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( CatRomCubic3D a, CatRomCubic3D b ) => !( a == b ); @@ -117,9 +122,9 @@ public static explicit operator BezierCubic3D( CatRomCubic3D s ) => public static explicit operator HermiteCubic3D( CatRomCubic3D s ) => new HermiteCubic3D( s.p1, - -(1/2f)*s.p0+(1/2f)*s.p2, + (-s.p0+s.p2)/2, s.p2, - -(1/2f)*s.p1+(1/2f)*s.p3 + (-s.p1+s.p3)/2 ); public static explicit operator UBSCubic3D( CatRomCubic3D s ) => new UBSCubic3D( diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index f6af87e..c15a5fe 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -95,7 +95,12 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial( CharMatrix.cubicHermite * PointMatrix ); + curve = new Polynomial( + p0, + v0, + -3*p0-2*v0+3*p1-v1, + 2*p0+v0-2*p1+v1 + ); } public static bool operator ==( HermiteCubic1D a, HermiteCubic1D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; public static bool operator !=( HermiteCubic1D a, HermiteCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index 20baab2..91a52bd 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -95,7 +95,12 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial2D( CharMatrix.cubicHermite * PointMatrix ); + curve = new Polynomial2D( + p0, + v0, + -3*p0-2*v0+3*p1-v1, + 2*p0+v0-2*p1+v1 + ); } public static bool operator ==( HermiteCubic2D a, HermiteCubic2D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; public static bool operator !=( HermiteCubic2D a, HermiteCubic2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index a682d1c..bb6b717 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -95,7 +95,12 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial3D( CharMatrix.cubicHermite * PointMatrix ); + curve = new Polynomial3D( + p0, + v0, + -3*p0-2*v0+3*p1-v1, + 2*p0+v0-2*p1+v1 + ); } public static bool operator ==( HermiteCubic3D a, HermiteCubic3D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; public static bool operator !=( HermiteCubic3D a, HermiteCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index 83e680c..371062c 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -95,7 +95,12 @@ public float this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial( CharMatrix.cubicUniformBspline * PointMatrix ); + curve = new Polynomial( + (1/6f)*p0+(2/3f)*p1+(1/6f)*p2, + (-p0+p2)/2, + (1/2f)*p0-p1+(1/2f)*p2, + -(1/6f)*p0+(1/2f)*p1-(1/2f)*p2+(1/6f)*p3 + ); } public static bool operator ==( UBSCubic1D a, UBSCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( UBSCubic1D a, UBSCubic1D b ) => !( a == b ); @@ -114,9 +119,9 @@ public static explicit operator BezierCubic1D( UBSCubic1D s ) => public static explicit operator HermiteCubic1D( UBSCubic1D s ) => new HermiteCubic1D( (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - -(1/2f)*s.p0+(1/2f)*s.p2, + (-s.p0+s.p2)/2, (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - -(1/2f)*s.p1+(1/2f)*s.p3 + (-s.p1+s.p3)/2 ); public static explicit operator CatRomCubic1D( UBSCubic1D s ) => new CatRomCubic1D( diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index 7f0833c..5dfe22f 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -95,7 +95,12 @@ public Vector2 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial2D( CharMatrix.cubicUniformBspline * PointMatrix ); + curve = new Polynomial2D( + (1/6f)*p0+(2/3f)*p1+(1/6f)*p2, + (-p0+p2)/2, + (1/2f)*p0-p1+(1/2f)*p2, + -(1/6f)*p0+(1/2f)*p1-(1/2f)*p2+(1/6f)*p3 + ); } public static bool operator ==( UBSCubic2D a, UBSCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( UBSCubic2D a, UBSCubic2D b ) => !( a == b ); @@ -117,9 +122,9 @@ public static explicit operator BezierCubic2D( UBSCubic2D s ) => public static explicit operator HermiteCubic2D( UBSCubic2D s ) => new HermiteCubic2D( (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - -(1/2f)*s.p0+(1/2f)*s.p2, + (-s.p0+s.p2)/2, (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - -(1/2f)*s.p1+(1/2f)*s.p3 + (-s.p1+s.p3)/2 ); public static explicit operator CatRomCubic2D( UBSCubic2D s ) => new CatRomCubic2D( diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index 8c0610b..b2430db 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -95,7 +95,12 @@ public Vector3 this[ int i ] { if( validCoefficients ) return; // no need to update validCoefficients = true; - curve = new Polynomial3D( CharMatrix.cubicUniformBspline * PointMatrix ); + curve = new Polynomial3D( + (1/6f)*p0+(2/3f)*p1+(1/6f)*p2, + (-p0+p2)/2, + (1/2f)*p0-p1+(1/2f)*p2, + -(1/6f)*p0+(1/2f)*p1-(1/2f)*p2+(1/6f)*p3 + ); } public static bool operator ==( UBSCubic3D a, UBSCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; public static bool operator !=( UBSCubic3D a, UBSCubic3D b ) => !( a == b ); @@ -117,9 +122,9 @@ public static explicit operator BezierCubic3D( UBSCubic3D s ) => public static explicit operator HermiteCubic3D( UBSCubic3D s ) => new HermiteCubic3D( (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - -(1/2f)*s.p0+(1/2f)*s.p2, + (-s.p0+s.p2)/2, (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - -(1/2f)*s.p1+(1/2f)*s.p3 + (-s.p1+s.p3)/2 ); public static explicit operator CatRomCubic3D( UBSCubic3D s ) => new CatRomCubic3D( From 73c1b9b847c0ee4d1ca1d003947f00ada1c41636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 13:29:07 +0200 Subject: [PATCH 088/301] matrix comparison/equality operators --- Numerics/Matrix3x1.cs | 8 ++++++++ Numerics/Matrix4x1.cs | 7 +++++++ Numerics/Vector2Matrix3x1.cs | 6 ++++++ Numerics/Vector2Matrix4x1.cs | 6 ++++++ Numerics/Vector3Matrix3x1.cs | 6 ++++++ Numerics/Vector3Matrix4x1.cs | 6 ++++++ 6 files changed, 39 insertions(+) diff --git a/Numerics/Matrix3x1.cs b/Numerics/Matrix3x1.cs index a2fc891..20c100a 100644 --- a/Numerics/Matrix3x1.cs +++ b/Numerics/Matrix3x1.cs @@ -15,4 +15,12 @@ public readonly struct Matrix3x1 { _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) }; + public static bool operator ==( Matrix3x1 a, Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; + public static bool operator !=( Matrix3x1 a, Matrix3x1 b ) => !( a == b ); + public bool Equals( Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); + public override bool Equals( object obj ) => obj is Matrix3x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); + + } + } \ No newline at end of file diff --git a/Numerics/Matrix4x1.cs b/Numerics/Matrix4x1.cs index 4e48a53..8bdf0a6 100644 --- a/Numerics/Matrix4x1.cs +++ b/Numerics/Matrix4x1.cs @@ -14,5 +14,12 @@ public readonly struct Matrix4x1 { 0 => m0, 1 => m1, 2 => m2, 3 => m3, _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) }; + public static bool operator ==( Matrix4x1 a, Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; + public static bool operator !=( Matrix4x1 a, Matrix4x1 b ) => !( a == b ); + public bool Equals( Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); + public override bool Equals( object obj ) => obj is Matrix4x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); + + } } \ No newline at end of file diff --git a/Numerics/Vector2Matrix3x1.cs b/Numerics/Vector2Matrix3x1.cs index 11ac685..19377e2 100644 --- a/Numerics/Vector2Matrix3x1.cs +++ b/Numerics/Vector2Matrix3x1.cs @@ -27,6 +27,12 @@ public Vector2Matrix3x1( Matrix3x1 x, Matrix3x1 y ) { public Matrix3x1 X => new(m0.x, m1.x, m2.x); public Matrix3x1 Y => new(m0.y, m1.y, m2.y); + public static bool operator ==( Vector2Matrix3x1 a, Vector2Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; + public static bool operator !=( Vector2Matrix3x1 a, Vector2Matrix3x1 b ) => !( a == b ); + public bool Equals( Vector2Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); + public override bool Equals( object obj ) => obj is Vector2Matrix3x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); + } } \ No newline at end of file diff --git a/Numerics/Vector2Matrix4x1.cs b/Numerics/Vector2Matrix4x1.cs index 0993cfd..018a724 100644 --- a/Numerics/Vector2Matrix4x1.cs +++ b/Numerics/Vector2Matrix4x1.cs @@ -27,6 +27,12 @@ public Vector2Matrix4x1( Matrix4x1 x, Matrix4x1 y ) { public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); + + public static bool operator ==( Vector2Matrix4x1 a, Vector2Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; + public static bool operator !=( Vector2Matrix4x1 a, Vector2Matrix4x1 b ) => !( a == b ); + public bool Equals( Vector2Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); + public override bool Equals( object obj ) => obj is Vector2Matrix4x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); } diff --git a/Numerics/Vector3Matrix3x1.cs b/Numerics/Vector3Matrix3x1.cs index 0a9d838..d6d1df9 100644 --- a/Numerics/Vector3Matrix3x1.cs +++ b/Numerics/Vector3Matrix3x1.cs @@ -28,6 +28,12 @@ public Vector3Matrix3x1( Matrix3x1 x, Matrix3x1 y, Matrix3x1 z ) { public Matrix3x1 Y => new(m0.y, m1.y, m2.y); public Matrix3x1 Z => new(m0.z, m1.z, m2.z); + public static bool operator ==( Vector3Matrix3x1 a, Vector3Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; + public static bool operator !=( Vector3Matrix3x1 a, Vector3Matrix3x1 b ) => !( a == b ); + public bool Equals( Vector3Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); + public override bool Equals( object obj ) => obj is Vector3Matrix3x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); + } } \ No newline at end of file diff --git a/Numerics/Vector3Matrix4x1.cs b/Numerics/Vector3Matrix4x1.cs index c405f60..5df0a19 100644 --- a/Numerics/Vector3Matrix4x1.cs +++ b/Numerics/Vector3Matrix4x1.cs @@ -28,6 +28,12 @@ public Vector3Matrix4x1( Matrix4x1 x, Matrix4x1 y, Matrix4x1 z ) { public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); public Matrix4x1 Z => new(m0.z, m1.z, m2.z, m3.z); + + public static bool operator ==( Vector3Matrix4x1 a, Vector3Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; + public static bool operator !=( Vector3Matrix4x1 a, Vector3Matrix4x1 b ) => !( a == b ); + public bool Equals( Vector3Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); + public override bool Equals( object obj ) => obj is Vector3Matrix4x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); } From ede1fcca91a9847a506b5682713fa400b77fdb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 13:30:04 +0200 Subject: [PATCH 089/301] matrix no longer readonly --- Numerics/Matrix3x1.cs | 42 +++++++++++++++++++++++++----------- Numerics/Matrix4x1.cs | 36 +++++++++++++++++++++++-------- Numerics/Vector2Matrix3x1.cs | 30 ++++++++++++++++++++------ Numerics/Vector2Matrix4x1.cs | 4 ++-- Numerics/Vector3Matrix3x1.cs | 30 ++++++++++++++++++++------ Numerics/Vector3Matrix4x1.cs | 4 ++-- 6 files changed, 107 insertions(+), 39 deletions(-) diff --git a/Numerics/Matrix3x1.cs b/Numerics/Matrix3x1.cs index 20c100a..ad2b78b 100644 --- a/Numerics/Matrix3x1.cs +++ b/Numerics/Matrix3x1.cs @@ -2,18 +2,36 @@ using System; -/// A 3x1 column matrix with float values -public readonly struct Matrix3x1 { - - public readonly float m0, m1, m2; - - public Matrix3x1( float m0, float m1, float m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); - - public float this[ int column ] => - column switch { - 0 => m0, 1 => m1, 2 => m2, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) - }; +namespace Freya { + + /// A 3x1 column matrix with float values + [Serializable] public struct Matrix3x1 { + + public float m0, m1, m2; + + public Matrix3x1( float m0, float m1, float m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); + + public float this[ int column ] { + get => + column switch { + 0 => m0, 1 => m1, 2 => m2, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ) + }; + set { + switch( column ) { + case 0: + m0 = value; + break; + case 1: + m1 = value; + break; + case 2: + m2 = value; + break; + default: throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ); + } + } + } public static bool operator ==( Matrix3x1 a, Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; public static bool operator !=( Matrix3x1 a, Matrix3x1 b ) => !( a == b ); diff --git a/Numerics/Matrix4x1.cs b/Numerics/Matrix4x1.cs index 8bdf0a6..42fb33d 100644 --- a/Numerics/Matrix4x1.cs +++ b/Numerics/Matrix4x1.cs @@ -2,18 +2,36 @@ using System; -/// A 4x1 column matrix with float values -public readonly struct Matrix4x1 { +namespace Freya { - public readonly float m0, m1, m2, m3; + /// A 4x1 column matrix with float values + [Serializable] public struct Matrix4x1 { - public Matrix4x1( float m0, float m1, float m2, float m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); + public float m0, m1, m2, m3; + + public Matrix4x1( float m0, float m1, float m2, float m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); + + public float this[ int column ] { + get => column switch { 0 => m0, 1 => m1, 2 => m2, 3 => m3, _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) }; + set { + switch( column ) { + case 0: + m0 = value; + break; + case 1: + m1 = value; + break; + case 2: + m2 = value; + break; + case 3: + m3 = value; + break; + default: throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ); + } + } + } - public float this[ int column ] => - column switch { - 0 => m0, 1 => m1, 2 => m2, 3 => m3, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) - }; public static bool operator ==( Matrix4x1 a, Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; public static bool operator !=( Matrix4x1 a, Matrix4x1 b ) => !( a == b ); public bool Equals( Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); diff --git a/Numerics/Vector2Matrix3x1.cs b/Numerics/Vector2Matrix3x1.cs index 19377e2..44a29cc 100644 --- a/Numerics/Vector2Matrix3x1.cs +++ b/Numerics/Vector2Matrix3x1.cs @@ -6,9 +6,9 @@ namespace Freya { /// A 3x1 column matrix with Vector2 values - public readonly struct Vector2Matrix3x1 { + [Serializable] public struct Vector2Matrix3x1 { - public readonly Vector2 m0, m1, m2; + public Vector2 m0, m1, m2; public Vector2Matrix3x1( Vector2 m0, Vector2 m1, Vector2 m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); @@ -18,11 +18,27 @@ public Vector2Matrix3x1( Matrix3x1 x, Matrix3x1 y ) { m2 = new Vector2( x.m2, y.m2 ); } - public Vector2 this[ int column ] => - column switch { - 0 => m0, 1 => m1, 2 => m2, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) - }; + public Vector2 this[ int column ] { + get => + column switch { + 0 => m0, 1 => m1, 2 => m2, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ) + }; + set { + switch( column ) { + case 0: + m0 = value; + break; + case 1: + m1 = value; + break; + case 2: + m2 = value; + break; + default: throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ); + } + } + } public Matrix3x1 X => new(m0.x, m1.x, m2.x); public Matrix3x1 Y => new(m0.y, m1.y, m2.y); diff --git a/Numerics/Vector2Matrix4x1.cs b/Numerics/Vector2Matrix4x1.cs index 018a724..bc96a2c 100644 --- a/Numerics/Vector2Matrix4x1.cs +++ b/Numerics/Vector2Matrix4x1.cs @@ -6,9 +6,9 @@ namespace Freya { /// A 4x1 column matrix with Vector2 values - public readonly struct Vector2Matrix4x1 { + [Serializable] public struct Vector2Matrix4x1 { - public readonly Vector2 m0, m1, m2, m3; + public Vector2 m0, m1, m2, m3; public Vector2Matrix4x1( Vector2 m0, Vector2 m1, Vector2 m2, Vector2 m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); diff --git a/Numerics/Vector3Matrix3x1.cs b/Numerics/Vector3Matrix3x1.cs index d6d1df9..66bee79 100644 --- a/Numerics/Vector3Matrix3x1.cs +++ b/Numerics/Vector3Matrix3x1.cs @@ -6,9 +6,9 @@ namespace Freya { /// A 3x1 column matrix with Vector3 values - public readonly struct Vector3Matrix3x1 { + [Serializable] public struct Vector3Matrix3x1 { - public readonly Vector3 m0, m1, m2; + public Vector3 m0, m1, m2; public Vector3Matrix3x1( Vector3 m0, Vector3 m1, Vector3 m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); @@ -18,11 +18,27 @@ public Vector3Matrix3x1( Matrix3x1 x, Matrix3x1 y, Matrix3x1 z ) { m2 = new Vector3( x.m2, y.m2, z.m2 ); } - public Vector3 this[ int column ] => - column switch { - 0 => m0, 1 => m1, 2 => m2, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) - }; + public Vector3 this[ int column ] { + get => + column switch { + 0 => m0, 1 => m1, 2 => m2, + _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ) + }; + set { + switch( column ) { + case 0: + m0 = value; + break; + case 1: + m1 = value; + break; + case 2: + m2 = value; + break; + default: throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ); + } + } + } public Matrix3x1 X => new(m0.x, m1.x, m2.x); public Matrix3x1 Y => new(m0.y, m1.y, m2.y); diff --git a/Numerics/Vector3Matrix4x1.cs b/Numerics/Vector3Matrix4x1.cs index 5df0a19..317d9ce 100644 --- a/Numerics/Vector3Matrix4x1.cs +++ b/Numerics/Vector3Matrix4x1.cs @@ -6,9 +6,9 @@ namespace Freya { /// A 4x1 column matrix with Vector3 values - public readonly struct Vector3Matrix4x1 { + [Serializable] public struct Vector3Matrix4x1 { - public readonly Vector3 m0, m1, m2, m3; + public Vector3 m0, m1, m2, m3; public Vector3Matrix4x1( Vector3 m0, Vector3 m1, Vector3 m2, Vector3 m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); From 37341fd31cc93b577fb48ff42a77c1f52dcaa5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 13:33:09 +0200 Subject: [PATCH 090/301] BREAKING CHANGE: splines now serialize as matrices --- Codegen/Editor/CodeGenerator.cs | 7 +- Codegen/Editor/MathfsCodegen.cs | 61 +++++----- .../Uniform Spline Segments/BezierCubic1D.cs | 78 ++++++------- .../Uniform Spline Segments/BezierCubic2D.cs | 98 ++++++++--------- .../Uniform Spline Segments/BezierCubic3D.cs | 104 +++++++++--------- .../Uniform Spline Segments/BezierQuad1D.cs | 44 ++++---- .../Uniform Spline Segments/BezierQuad2D.cs | 71 +++++------- .../Uniform Spline Segments/BezierQuad3D.cs | 52 ++++----- .../Uniform Spline Segments/CatRomCubic1D.cs | 70 ++++++------ .../Uniform Spline Segments/CatRomCubic2D.cs | 72 ++++++------ .../Uniform Spline Segments/CatRomCubic3D.cs | 72 ++++++------ .../Uniform Spline Segments/HermiteCubic1D.cs | 70 ++++++------ .../Uniform Spline Segments/HermiteCubic2D.cs | 72 ++++++------ .../Uniform Spline Segments/HermiteCubic3D.cs | 72 ++++++------ Splines/Uniform Spline Segments/UBSCubic1D.cs | 70 ++++++------ Splines/Uniform Spline Segments/UBSCubic2D.cs | 72 ++++++------ Splines/Uniform Spline Segments/UBSCubic3D.cs | 72 ++++++------ 17 files changed, 568 insertions(+), 589 deletions(-) diff --git a/Codegen/Editor/CodeGenerator.cs b/Codegen/Editor/CodeGenerator.cs index fba1ecd..3bda325 100644 --- a/Codegen/Editor/CodeGenerator.cs +++ b/Codegen/Editor/CodeGenerator.cs @@ -15,9 +15,14 @@ public class CodeGenerator { public void Using( string s ) => Append( $"using {s};" ); public void Summary( string s ) => Append( $"/// {s}" ); public void Param( string param, string desc ) => Append( $"/// {desc}" ); - public void LineBreak() => content.Add( "" ); + public void AppendHeader() { + Comment( "by Freya Holmér (https://github.com/FreyaHolmer/Mathfs)" ); + Comment( $"Do not manually edit - this file is generated by {nameof(MathfsCodegen)}.cs" ); + LineBreak(); + } + public CodeScope BracketScope( string s ) => new CodeScope( this, s, true ); public CodeScope Scope( string s ) => new CodeScope( this, s, false ); public RegionScope ScopeRegion( string s ) => new RegionScope( this, s ); diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 0d31cdb..c6bf72b 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -119,14 +119,13 @@ static void GenerateType( SplineType type, int dim ) { string degShortCapital = GetDegreeName( degree, true ); string structName = $"{type.className}{degShortCapital}{dim}D"; string[] points = type.paramNames; + int[] ptRange = ptCount == 3 ? new[] { 0, 1, 2 } : new[] { 0, 1, 2, 3 }; string[] pointDescs = type.paramDescs; string lerpName = GetLerpName( dim ); string pointMatrixType = $"{( dim == 1 ? "" : dataType )}Matrix{ptCount}x1"; CodeGenerator code = new CodeGenerator(); - code.Comment( "by Freya Holmér (https://github.com/FreyaHolmer/Mathfs)" ); - code.Comment( $"Do not manually edit - this file is generated by {nameof(MathfsCodegen)}.cs" ); - code.LineBreak(); + code.AppendHeader(); code.Using( "System" ); code.Using( "System.Runtime.CompilerServices" ); code.Using( "UnityEngine" ); @@ -147,7 +146,7 @@ static void GenerateType( SplineType type, int dim ) { for( int i = 0; i < ptCount; i++ ) type.AppendParamStrings( code, degree, i ); using( code.BracketScope( $"public {structName}( {string.Join( ", ", points.Select( p => $"{dataType} {p}" ) )} )" ) ) { - code.Append( $"( {string.Join( ", ", points.Select( p => $"this.{p}" ) )} ) = ( {string.Join( ", ", points )} );" ); + code.Append( $"pointMatrix = new {pointMatrixType}( {string.Join( ", ", points )} );" ); code.Append( "validCoefficients = false;" ); code.Append( "curve = default;" ); } @@ -165,14 +164,14 @@ static void GenerateType( SplineType type, int dim ) { // control point properties using( code.ScopeRegion( "Control Points" ) ) { - code.Append( $"[SerializeField] {dataType} {string.Join( ", ", points )};" ); - code.Append( $"public {pointMatrixType} PointMatrix => new({string.Join( ", ", points )});" ); + code.Append( $"[SerializeField] {pointMatrixType} pointMatrix;" ); + code.Append( $"public {pointMatrixType} PointMatrix => pointMatrix;" ); code.LineBreak(); for( int i = 0; i < ptCount; i++ ) { code.Summary( pointDescs[i] ); using( code.BracketScope( $"public {dataType} {points[i].ToUpperInvariant()}" ) ) { - code.Append( $"[MethodImpl( INLINE )] get => {points[i]};" ); - code.Append( $"[MethodImpl( INLINE )] set => _ = ( {points[i]} = value, validCoefficients = false );" ); + code.Append( $"[MethodImpl( INLINE )] get => pointMatrix.m{i};" ); + code.Append( $"[MethodImpl( INLINE )] set => _ = ( pointMatrix.m{i} = value, validCoefficients = false );" ); } code.LineBreak(); @@ -213,30 +212,26 @@ static void GenerateType( SplineType type, int dim ) { code.Append( "return; // no need to update" ); code.Append( "validCoefficients = true;" ); - using( code.Scope( $"curve = new {polynomType}(" ) ) { for( int icRow = 0; icRow < ptCount; icRow++ ) { MathSum sum = new MathSum(); for( int ip = 0; ip < ptCount; ip++ ) - sum.AddTerm( type.charMatrix[icRow, ip], $"{type.paramNames[ip]}" ); + sum.AddTerm( type.charMatrix[icRow, ip], $"{type.paramNames[ip].ToUpperInvariant()}" ); code.Append( $"{sum}{( icRow < ptCount - 1 ? "," : "" )}" ); } } code.Append( ");" ); - - // todo: unroll matrix multiply for performance - // code.Append( $"curve = new {polynomType}( CharMatrix.{type.matrixName} * PointMatrix );" ); } // equality checks - code.Append( $"public static bool operator ==( {structName} a, {structName} b ) => {string.Join( " && ", points.Select( p => $"a.{p.ToUpperInvariant()} == b.{p.ToUpperInvariant()}" ) )};" ); + code.Append( $"public static bool operator ==( {structName} a, {structName} b ) => a.pointMatrix == b.pointMatrix;" ); code.Append( $"public static bool operator !=( {structName} a, {structName} b ) => !( a == b );" ); code.Append( $"public bool Equals( {structName} other ) => {string.Join( " && ", points.Select( p => $"{p.ToUpperInvariant()}.Equals( other.{p.ToUpperInvariant()} )" ) )};" ); - code.Append( $"public override bool Equals( object obj ) => obj is {structName} other && Equals( other );" ); - code.Append( $"public override int GetHashCode() => HashCode.Combine( {string.Join( ", ", points )} );" ); + code.Append( $"public override bool Equals( object obj ) => obj is {structName} other && pointMatrix.Equals( other.pointMatrix );" ); + code.Append( $"public override int GetHashCode() => pointMatrix.GetHashCode();" ); + code.Append( $"public override string ToString() => $\"({string.Join( ", ", ptRange.Select( i => $"{{pointMatrix.m{i}}}" ) )})\";" ); code.LineBreak(); - code.Append( $"public override string ToString() => $\"({string.Join( ", ", points.Select( p => $"{{{p}}}" ) )})\";" ); // typecasting if( dim is 2 or 3 && degree is 3 ) { @@ -245,7 +240,7 @@ static void GenerateType( SplineType type, int dim ) { string structName3D = $"{type.className}{degShortCapital}3D"; code.Summary( "Returns this spline segment in 3D, where z = 0" ); code.Param( "curve2D", "The 2D curve to cast to 3D" ); - code.Append( $"public static explicit operator {structName3D}( {structName} curve2D ) => new {structName3D}( {string.Join( ", ", points.Select( p => $"curve2D.{p}" ) )} );" ); + code.Append( $"public static explicit operator {structName3D}( {structName} curve2D ) => new {structName3D}( {string.Join( ", ", points.Select( p => $"curve2D.{p.ToUpperInvariant()}" ) )} );" ); } if( dim == 3 ) { @@ -253,7 +248,7 @@ static void GenerateType( SplineType type, int dim ) { string structName2D = $"{type.className}{degShortCapital}2D"; code.Summary( "Returns this curve flattened to 2D. Effectively setting z = 0" ); code.Param( "curve3D", "The 3D curve to flatten to the Z plane" ); - code.Append( $"public static explicit operator {structName2D}( {structName} curve3D ) => new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p}" ) )} );" ); + code.Append( $"public static explicit operator {structName2D}( {structName} curve3D ) => new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p.ToUpperInvariant()}" ) )} );" ); } } @@ -284,7 +279,7 @@ static void GenerateType( SplineType type, int dim ) { for( int oPt = 0; oPt < 4; oPt++ ) { MathSum sum = new(); for( int iPt = 0; iPt < 4; iPt++ ) - sum.AddTerm( C[oPt, iPt], $"s.{type.paramNames[iPt]}" ); + sum.AddTerm( C[oPt, iPt], $"s.{type.paramNames[iPt].ToUpperInvariant()}" ); code.Append( $"{sum}{( oPt < 3 ? "," : "" )}" ); } } @@ -302,7 +297,7 @@ static void GenerateType( SplineType type, int dim ) { using( code.Scope( $"public static {structName} Lerp( {structName} a, {structName} b, float t ) =>" ) ) { using( code.Scope( "new(" ) ) { for( int i = 0; i < ptCount; i++ ) { - code.Append( $"{lerpName}( a.{points[i]}, b.{points[i]}, t )" + ( i == ptCount - 1 ? "" : "," ) ); + code.Append( $"{lerpName}( a.{points[i].ToUpperInvariant()}, b.{points[i].ToUpperInvariant()}, t )" + ( i == ptCount - 1 ? "" : "," ) ); } } @@ -320,13 +315,13 @@ static void GenerateType( SplineType type, int dim ) { code.Param( "b", "The second spline segment" ); code.Param( "t", "A value from 0 to 1 to blend between a and b" ); using( code.BracketScope( $"public static {structName} Slerp( {structName} a, {structName} b, float t )" ) ) { - code.Append( $"{dataType} p0 = {lerpName}( a.p0, b.p0, t );" ); - code.Append( $"{dataType} p3 = {lerpName}( a.p3, b.p3, t );" ); + code.Append( $"{dataType} P0 = {lerpName}( a.P0, b.P0, t );" ); + code.Append( $"{dataType} P3 = {lerpName}( a.P3, b.P3, t );" ); using( code.Scope( $"return new {structName}(" ) ) { - code.Append( $"p0," ); - code.Append( $"p0 + {slerpCast}Vector3.SlerpUnclamped( a.p1 - a.p0, b.p1 - b.p0, t )," ); - code.Append( $"p3 + {slerpCast}Vector3.SlerpUnclamped( a.p2 - a.p3, b.p2 - b.p3, t )," ); - code.Append( $"p3" ); + code.Append( $"P0," ); + code.Append( $"P0 + {slerpCast}Vector3.SlerpUnclamped( a.P1 - a.P0, b.P1 - b.P0, t )," ); + code.Append( $"P3 + {slerpCast}Vector3.SlerpUnclamped( a.P2 - a.P3, b.P2 - b.P3, t )," ); + code.Append( $"P3" ); } code.Append( ");" ); @@ -382,13 +377,11 @@ public override string ToString() { line += FormatTerm( i ); if( globalScale != 1 ) { - if( globalScale.n == 1 ) { line = $"({line})/{globalScale.d}"; } else { line = $"{FormatRational( globalScale )}*({line})"; } - } return line; @@ -448,17 +441,17 @@ void AppendLerps( string varName, string A, string B ) { } } - AppendLerps( "a", "p0", "p1" ); - AppendLerps( "b", "p1", "p2" ); // this could be unrolled/optimized for the cubic case, as b is never used for the output + AppendLerps( "a", "P0", "P1" ); + AppendLerps( "b", "P1", "P2" ); // this could be unrolled/optimized for the cubic case, as b is never used for the output if( degree == 3 ) { - AppendLerps( "c", "p2", "p3" ); + AppendLerps( "c", "P2", "P3" ); AppendLerps( "d", "a", "b" ); AppendLerps( "e", "b", "c" ); AppendLerps( "p", "d", "e" ); - code.Append( $"return ( new {structName}( p0, a, d, p ), new {structName}( p, e, c, p3 ) );" ); + code.Append( $"return ( new {structName}( P0, a, d, p ), new {structName}( p, e, c, P3 ) );" ); } else if( degree == 2 ) { AppendLerps( "p", "a", "b" ); - code.Append( $"return ( new {structName}( p0, a, p ), new {structName}( p, b, p2 ) );" ); + code.Append( $"return ( new {structName}( P0, a, p ), new {structName}( p, b, P2 ) );" ); } } diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index f21ac44..2090511 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve public BezierCubic1D( float p0, float p1, float p2, float p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial Curve { } #region Control Points - [SerializeField] float p0, p1, p2, p3; - public Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Matrix4x1 pointMatrix; + public Matrix4x1 PointMatrix => pointMatrix; /// The starting point of the curve public float P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point of the curve, sometimes called the start tangent point public float P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point of the curve, sometimes called the end tangent point public float P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The end point of the curve public float P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,39 +96,39 @@ public float this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial( - p0, - 3*(-p0+p1), - 3*p0-6*p1+3*p2, - -p0+3*p1-3*p2+p3 + P0, + 3*(-P0+P1), + 3*P0-6*P1+3*P2, + -P0+3*P1-3*P2+P3 ); } - public static bool operator ==( BezierCubic1D a, BezierCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( BezierCubic1D a, BezierCubic1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierCubic1D a, BezierCubic1D b ) => !( a == b ); public bool Equals( BezierCubic1D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is BezierCubic1D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is BezierCubic1D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; public static explicit operator HermiteCubic1D( BezierCubic1D s ) => new HermiteCubic1D( - s.p0, - 3*(-s.p0+s.p1), - s.p3, - 3*(-s.p2+s.p3) + s.P0, + 3*(-s.P0+s.P1), + s.P3, + 3*(-s.P2+s.P3) ); public static explicit operator CatRomCubic1D( BezierCubic1D s ) => new CatRomCubic1D( - 6*s.p0-6*s.p1+s.p3, - s.p0, - s.p3, - s.p0-6*s.p2+6*s.p3 + 6*s.P0-6*s.P1+s.P3, + s.P0, + s.P3, + s.P0-6*s.P2+6*s.P3 ); public static explicit operator UBSCubic1D( BezierCubic1D s ) => new UBSCubic1D( - 6*s.p0-7*s.p1+2*s.p2, - 2*s.p1-s.p2, - -s.p1+2*s.p2, - 2*s.p1-7*s.p2+6*s.p3 + 6*s.P0-7*s.P1+2*s.P2, + 2*s.P1-s.P2, + -s.P1+2*s.P2, + 2*s.P1-7*s.P2+6*s.P3 ); /// Returns a linear blend between two bézier curves /// The first spline segment @@ -136,21 +136,21 @@ public static explicit operator UBSCubic1D( BezierCubic1D s ) => /// A value from 0 to 1 to blend between a and b public static BezierCubic1D Lerp( BezierCubic1D a, BezierCubic1D b, float t ) => new( - Mathfs.Lerp( a.p0, b.p0, t ), - Mathfs.Lerp( a.p1, b.p1, t ), - Mathfs.Lerp( a.p2, b.p2, t ), - Mathfs.Lerp( a.p3, b.p3, t ) + Mathfs.Lerp( a.P0, b.P0, t ), + Mathfs.Lerp( a.P1, b.P1, t ), + Mathfs.Lerp( a.P2, b.P2, t ), + Mathfs.Lerp( a.P3, b.P3, t ) ); /// Splits this curve at the given t-value, into two curves that together form the exact same shape /// The t-value to split at public (BezierCubic1D pre, BezierCubic1D post) Split( float t ) { - float a = p0 + ( p1 - p0 ) * t; - float b = p1 + ( p2 - p1 ) * t; - float c = p2 + ( p3 - p2 ) * t; + float a = P0 + ( P1 - P0 ) * t; + float b = P1 + ( P2 - P1 ) * t; + float c = P2 + ( P3 - P2 ) * t; float d = a + ( b - a ) * t; float e = b + ( c - b ) * t; float p = d + ( e - d ) * t; - return ( new BezierCubic1D( p0, a, d, p ), new BezierCubic1D( p, e, c, p3 ) ); + return ( new BezierCubic1D( P0, a, d, p ), new BezierCubic1D( p, e, c, P3 ) ); } } } diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index 72de4c0..dd7c45e 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve public BezierCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial2D Curve { } #region Control Points - [SerializeField] Vector2 p0, p1, p2, p3; - public Vector2Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Vector2Matrix4x1 pointMatrix; + public Vector2Matrix4x1 PointMatrix => pointMatrix; /// The starting point of the curve public Vector2 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point of the curve, sometimes called the start tangent point public Vector2 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point of the curve, sometimes called the end tangent point public Vector2 P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The end point of the curve public Vector2 P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,42 +96,42 @@ public Vector2 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial2D( - p0, - 3*(-p0+p1), - 3*p0-6*p1+3*p2, - -p0+3*p1-3*p2+p3 + P0, + 3*(-P0+P1), + 3*P0-6*P1+3*P2, + -P0+3*P1-3*P2+P3 ); } - public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierCubic2D a, BezierCubic2D b ) => !( a == b ); public bool Equals( BezierCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is BezierCubic2D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is BezierCubic2D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D - public static explicit operator BezierCubic3D( BezierCubic2D curve2D ) => new BezierCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); + public static explicit operator BezierCubic3D( BezierCubic2D curve2D ) => new BezierCubic3D( curve2D.P0, curve2D.P1, curve2D.P2, curve2D.P3 ); public static explicit operator HermiteCubic2D( BezierCubic2D s ) => new HermiteCubic2D( - s.p0, - 3*(-s.p0+s.p1), - s.p3, - 3*(-s.p2+s.p3) + s.P0, + 3*(-s.P0+s.P1), + s.P3, + 3*(-s.P2+s.P3) ); public static explicit operator CatRomCubic2D( BezierCubic2D s ) => new CatRomCubic2D( - 6*s.p0-6*s.p1+s.p3, - s.p0, - s.p3, - s.p0-6*s.p2+6*s.p3 + 6*s.P0-6*s.P1+s.P3, + s.P0, + s.P3, + s.P0-6*s.P2+6*s.P3 ); public static explicit operator UBSCubic2D( BezierCubic2D s ) => new UBSCubic2D( - 6*s.p0-7*s.p1+2*s.p2, - 2*s.p1-s.p2, - -s.p1+2*s.p2, - 2*s.p1-7*s.p2+6*s.p3 + 6*s.P0-7*s.P1+2*s.P2, + 2*s.P1-s.P2, + -s.P1+2*s.P2, + 2*s.P1-7*s.P2+6*s.P3 ); /// Returns a linear blend between two bézier curves /// The first spline segment @@ -139,10 +139,10 @@ public static explicit operator UBSCubic2D( BezierCubic2D s ) => /// A value from 0 to 1 to blend between a and b public static BezierCubic2D Lerp( BezierCubic2D a, BezierCubic2D b, float t ) => new( - Vector2.LerpUnclamped( a.p0, b.p0, t ), - Vector2.LerpUnclamped( a.p1, b.p1, t ), - Vector2.LerpUnclamped( a.p2, b.p2, t ), - Vector2.LerpUnclamped( a.p3, b.p3, t ) + Vector2.LerpUnclamped( a.P0, b.P0, t ), + Vector2.LerpUnclamped( a.P1, b.P1, t ), + Vector2.LerpUnclamped( a.P2, b.P2, t ), + Vector2.LerpUnclamped( a.P3, b.P3, t ) ); /// Returns a linear blend between two bézier curves, where the tangent directions are spherically interpolated @@ -150,27 +150,27 @@ public static BezierCubic2D Lerp( BezierCubic2D a, BezierCubic2D b, float t ) => /// The second spline segment /// A value from 0 to 1 to blend between a and b public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { - Vector2 p0 = Vector2.LerpUnclamped( a.p0, b.p0, t ); - Vector2 p3 = Vector2.LerpUnclamped( a.p3, b.p3, t ); + Vector2 P0 = Vector2.LerpUnclamped( a.P0, b.P0, t ); + Vector2 P3 = Vector2.LerpUnclamped( a.P3, b.P3, t ); return new BezierCubic2D( - p0, - p0 + (Vector2)Vector3.SlerpUnclamped( a.p1 - a.p0, b.p1 - b.p0, t ), - p3 + (Vector2)Vector3.SlerpUnclamped( a.p2 - a.p3, b.p2 - b.p3, t ), - p3 + P0, + P0 + (Vector2)Vector3.SlerpUnclamped( a.P1 - a.P0, b.P1 - b.P0, t ), + P3 + (Vector2)Vector3.SlerpUnclamped( a.P2 - a.P3, b.P2 - b.P3, t ), + P3 ); } /// Splits this curve at the given t-value, into two curves that together form the exact same shape /// The t-value to split at public (BezierCubic2D pre, BezierCubic2D post) Split( float t ) { Vector2 a = new Vector2( - p0.x + ( p1.x - p0.x ) * t, - p0.y + ( p1.y - p0.y ) * t ); + P0.x + ( P1.x - P0.x ) * t, + P0.y + ( P1.y - P0.y ) * t ); Vector2 b = new Vector2( - p1.x + ( p2.x - p1.x ) * t, - p1.y + ( p2.y - p1.y ) * t ); + P1.x + ( P2.x - P1.x ) * t, + P1.y + ( P2.y - P1.y ) * t ); Vector2 c = new Vector2( - p2.x + ( p3.x - p2.x ) * t, - p2.y + ( p3.y - p2.y ) * t ); + P2.x + ( P3.x - P2.x ) * t, + P2.y + ( P3.y - P2.y ) * t ); Vector2 d = new Vector2( a.x + ( b.x - a.x ) * t, a.y + ( b.y - a.y ) * t ); @@ -180,7 +180,7 @@ public static BezierCubic2D Slerp( BezierCubic2D a, BezierCubic2D b, float t ) { Vector2 p = new Vector2( d.x + ( e.x - d.x ) * t, d.y + ( e.y - d.y ) * t ); - return ( new BezierCubic2D( p0, a, d, p ), new BezierCubic2D( p, e, c, p3 ) ); + return ( new BezierCubic2D( P0, a, d, p ), new BezierCubic2D( p, e, c, P3 ) ); } } } diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index d4f87d3..de2b1ea 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve public BezierCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial3D Curve { } #region Control Points - [SerializeField] Vector3 p0, p1, p2, p3; - public Vector3Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Vector3Matrix4x1 pointMatrix; + public Vector3Matrix4x1 PointMatrix => pointMatrix; /// The starting point of the curve public Vector3 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point of the curve, sometimes called the start tangent point public Vector3 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point of the curve, sometimes called the end tangent point public Vector3 P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The end point of the curve public Vector3 P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,42 +96,42 @@ public Vector3 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial3D( - p0, - 3*(-p0+p1), - 3*p0-6*p1+3*p2, - -p0+3*p1-3*p2+p3 + P0, + 3*(-P0+P1), + 3*P0-6*P1+3*P2, + -P0+3*P1-3*P2+P3 ); } - public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierCubic3D a, BezierCubic3D b ) => !( a == b ); public bool Equals( BezierCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is BezierCubic3D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is BezierCubic3D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane - public static explicit operator BezierCubic2D( BezierCubic3D curve3D ) => new BezierCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); + public static explicit operator BezierCubic2D( BezierCubic3D curve3D ) => new BezierCubic2D( curve3D.P0, curve3D.P1, curve3D.P2, curve3D.P3 ); public static explicit operator HermiteCubic3D( BezierCubic3D s ) => new HermiteCubic3D( - s.p0, - 3*(-s.p0+s.p1), - s.p3, - 3*(-s.p2+s.p3) + s.P0, + 3*(-s.P0+s.P1), + s.P3, + 3*(-s.P2+s.P3) ); public static explicit operator CatRomCubic3D( BezierCubic3D s ) => new CatRomCubic3D( - 6*s.p0-6*s.p1+s.p3, - s.p0, - s.p3, - s.p0-6*s.p2+6*s.p3 + 6*s.P0-6*s.P1+s.P3, + s.P0, + s.P3, + s.P0-6*s.P2+6*s.P3 ); public static explicit operator UBSCubic3D( BezierCubic3D s ) => new UBSCubic3D( - 6*s.p0-7*s.p1+2*s.p2, - 2*s.p1-s.p2, - -s.p1+2*s.p2, - 2*s.p1-7*s.p2+6*s.p3 + 6*s.P0-7*s.P1+2*s.P2, + 2*s.P1-s.P2, + -s.P1+2*s.P2, + 2*s.P1-7*s.P2+6*s.P3 ); /// Returns a linear blend between two bézier curves /// The first spline segment @@ -139,10 +139,10 @@ public static explicit operator UBSCubic3D( BezierCubic3D s ) => /// A value from 0 to 1 to blend between a and b public static BezierCubic3D Lerp( BezierCubic3D a, BezierCubic3D b, float t ) => new( - Vector3.LerpUnclamped( a.p0, b.p0, t ), - Vector3.LerpUnclamped( a.p1, b.p1, t ), - Vector3.LerpUnclamped( a.p2, b.p2, t ), - Vector3.LerpUnclamped( a.p3, b.p3, t ) + Vector3.LerpUnclamped( a.P0, b.P0, t ), + Vector3.LerpUnclamped( a.P1, b.P1, t ), + Vector3.LerpUnclamped( a.P2, b.P2, t ), + Vector3.LerpUnclamped( a.P3, b.P3, t ) ); /// Returns a linear blend between two bézier curves, where the tangent directions are spherically interpolated @@ -150,30 +150,30 @@ public static BezierCubic3D Lerp( BezierCubic3D a, BezierCubic3D b, float t ) => /// The second spline segment /// A value from 0 to 1 to blend between a and b public static BezierCubic3D Slerp( BezierCubic3D a, BezierCubic3D b, float t ) { - Vector3 p0 = Vector3.LerpUnclamped( a.p0, b.p0, t ); - Vector3 p3 = Vector3.LerpUnclamped( a.p3, b.p3, t ); + Vector3 P0 = Vector3.LerpUnclamped( a.P0, b.P0, t ); + Vector3 P3 = Vector3.LerpUnclamped( a.P3, b.P3, t ); return new BezierCubic3D( - p0, - p0 + Vector3.SlerpUnclamped( a.p1 - a.p0, b.p1 - b.p0, t ), - p3 + Vector3.SlerpUnclamped( a.p2 - a.p3, b.p2 - b.p3, t ), - p3 + P0, + P0 + Vector3.SlerpUnclamped( a.P1 - a.P0, b.P1 - b.P0, t ), + P3 + Vector3.SlerpUnclamped( a.P2 - a.P3, b.P2 - b.P3, t ), + P3 ); } /// Splits this curve at the given t-value, into two curves that together form the exact same shape /// The t-value to split at public (BezierCubic3D pre, BezierCubic3D post) Split( float t ) { Vector3 a = new Vector3( - p0.x + ( p1.x - p0.x ) * t, - p0.y + ( p1.y - p0.y ) * t, - p0.z + ( p1.z - p0.z ) * t ); + P0.x + ( P1.x - P0.x ) * t, + P0.y + ( P1.y - P0.y ) * t, + P0.z + ( P1.z - P0.z ) * t ); Vector3 b = new Vector3( - p1.x + ( p2.x - p1.x ) * t, - p1.y + ( p2.y - p1.y ) * t, - p1.z + ( p2.z - p1.z ) * t ); + P1.x + ( P2.x - P1.x ) * t, + P1.y + ( P2.y - P1.y ) * t, + P1.z + ( P2.z - P1.z ) * t ); Vector3 c = new Vector3( - p2.x + ( p3.x - p2.x ) * t, - p2.y + ( p3.y - p2.y ) * t, - p2.z + ( p3.z - p2.z ) * t ); + P2.x + ( P3.x - P2.x ) * t, + P2.y + ( P3.y - P2.y ) * t, + P2.z + ( P3.z - P2.z ) * t ); Vector3 d = new Vector3( a.x + ( b.x - a.x ) * t, a.y + ( b.y - a.y ) * t, @@ -186,7 +186,7 @@ public static BezierCubic3D Slerp( BezierCubic3D a, BezierCubic3D b, float t ) { d.x + ( e.x - d.x ) * t, d.y + ( e.y - d.y ) * t, d.z + ( e.z - d.z ) * t ); - return ( new BezierCubic3D( p0, a, d, p ), new BezierCubic3D( p, e, c, p3 ) ); + return ( new BezierCubic3D( P0, a, d, p ), new BezierCubic3D( p, e, c, P3 ) ); } } } diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Splines/Uniform Spline Segments/BezierQuad1D.cs index 6e2a066..a512686 100644 --- a/Splines/Uniform Spline Segments/BezierQuad1D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad1D.cs @@ -17,7 +17,7 @@ namespace Freya { /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve public BezierQuad1D( float p0, float p1, float p2 ) { - ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); + pointMatrix = new Matrix3x1( p0, p1, p2 ); validCoefficients = false; curve = default; } @@ -31,25 +31,25 @@ public Polynomial Curve { } #region Control Points - [SerializeField] float p0, p1, p2; - public Matrix3x1 PointMatrix => new(p0, p1, p2); + [SerializeField] Matrix3x1 pointMatrix; + public Matrix3x1 PointMatrix => pointMatrix; /// The starting point of the curve public float P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The middle control point of the curve, sometimes called a tangent point public float P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve public float P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 2 @@ -85,35 +85,35 @@ public float this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial( - p0, - 2*(-p0+p1), - p0-2*p1+p2 + P0, + 2*(-P0+P1), + P0-2*P1+P2 ); } - public static bool operator ==( BezierQuad1D a, BezierQuad1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; + public static bool operator ==( BezierQuad1D a, BezierQuad1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierQuad1D a, BezierQuad1D b ) => !( a == b ); public bool Equals( BezierQuad1D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ); - public override bool Equals( object obj ) => obj is BezierQuad1D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2 ); + public override bool Equals( object obj ) => obj is BezierQuad1D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2})"; - public override string ToString() => $"({p0}, {p1}, {p2})"; /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment /// A value from 0 to 1 to blend between a and b public static BezierQuad1D Lerp( BezierQuad1D a, BezierQuad1D b, float t ) => new( - Mathfs.Lerp( a.p0, b.p0, t ), - Mathfs.Lerp( a.p1, b.p1, t ), - Mathfs.Lerp( a.p2, b.p2, t ) + Mathfs.Lerp( a.P0, b.P0, t ), + Mathfs.Lerp( a.P1, b.P1, t ), + Mathfs.Lerp( a.P2, b.P2, t ) ); /// Splits this curve at the given t-value, into two curves that together form the exact same shape /// The t-value to split at public (BezierQuad1D pre, BezierQuad1D post) Split( float t ) { - float a = p0 + ( p1 - p0 ) * t; - float b = p1 + ( p2 - p1 ) * t; + float a = P0 + ( P1 - P0 ) * t; + float b = P1 + ( P2 - P1 ) * t; float p = a + ( b - a ) * t; - return ( new BezierQuad1D( p0, a, p ), new BezierQuad1D( p, b, p2 ) ); + return ( new BezierQuad1D( P0, a, p ), new BezierQuad1D( p, b, P2 ) ); } } } diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index 07ad412..8ce84ef 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -17,7 +17,7 @@ namespace Freya { /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve public BezierQuad2D( Vector2 p0, Vector2 p1, Vector2 p2 ) { - ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); + pointMatrix = new Vector2Matrix3x1( p0, p1, p2 ); validCoefficients = false; curve = default; } @@ -31,50 +31,31 @@ public Polynomial2D Curve { } #region Control Points - [SerializeField] Vector2 p0, p1, p2; - public Vector2Matrix3x1 PointMatrix => new(p0, p1, p2); + [SerializeField] Vector2Matrix3x1 pointMatrix; + public Vector2Matrix3x1 PointMatrix => pointMatrix; /// The starting point of the curve public Vector2 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The middle control point of the curve, sometimes called a tangent point public Vector2 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve public Vector2 P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 2 public Vector2 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); - } - } + [MethodImpl( INLINE )] get => pointMatrix[i]; + [MethodImpl( INLINE )] set => _ = ( pointMatrix[i] = value, validCoefficients = false ); } #endregion @@ -85,41 +66,41 @@ public Vector2 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial2D( - p0, - 2*(-p0+p1), - p0-2*p1+p2 + P0, + 2*(-P0+P1), + P0-2*P1+P2 ); } - public static bool operator ==( BezierQuad2D a, BezierQuad2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; + public static bool operator ==( BezierQuad2D a, BezierQuad2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierQuad2D a, BezierQuad2D b ) => !( a == b ); public bool Equals( BezierQuad2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ); - public override bool Equals( object obj ) => obj is BezierQuad2D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2 ); + public override bool Equals( object obj ) => obj is BezierQuad2D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2})"; - public override string ToString() => $"({p0}, {p1}, {p2})"; /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment /// A value from 0 to 1 to blend between a and b public static BezierQuad2D Lerp( BezierQuad2D a, BezierQuad2D b, float t ) => new( - Vector2.LerpUnclamped( a.p0, b.p0, t ), - Vector2.LerpUnclamped( a.p1, b.p1, t ), - Vector2.LerpUnclamped( a.p2, b.p2, t ) + Vector2.LerpUnclamped( a.P0, b.P0, t ), + Vector2.LerpUnclamped( a.P1, b.P1, t ), + Vector2.LerpUnclamped( a.P2, b.P2, t ) ); /// Splits this curve at the given t-value, into two curves that together form the exact same shape /// The t-value to split at public (BezierQuad2D pre, BezierQuad2D post) Split( float t ) { Vector2 a = new Vector2( - p0.x + ( p1.x - p0.x ) * t, - p0.y + ( p1.y - p0.y ) * t ); + P0.x + ( P1.x - P0.x ) * t, + P0.y + ( P1.y - P0.y ) * t ); Vector2 b = new Vector2( - p1.x + ( p2.x - p1.x ) * t, - p1.y + ( p2.y - p1.y ) * t ); + P1.x + ( P2.x - P1.x ) * t, + P1.y + ( P2.y - P1.y ) * t ); Vector2 p = new Vector2( a.x + ( b.x - a.x ) * t, a.y + ( b.y - a.y ) * t ); - return ( new BezierQuad2D( p0, a, p ), new BezierQuad2D( p, b, p2 ) ); + return ( new BezierQuad2D( P0, a, p ), new BezierQuad2D( p, b, P2 ) ); } } } diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index fa45f13..e9087f7 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -17,7 +17,7 @@ namespace Freya { /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve public BezierQuad3D( Vector3 p0, Vector3 p1, Vector3 p2 ) { - ( this.p0, this.p1, this.p2 ) = ( p0, p1, p2 ); + pointMatrix = new Vector3Matrix3x1( p0, p1, p2 ); validCoefficients = false; curve = default; } @@ -31,25 +31,25 @@ public Polynomial3D Curve { } #region Control Points - [SerializeField] Vector3 p0, p1, p2; - public Vector3Matrix3x1 PointMatrix => new(p0, p1, p2); + [SerializeField] Vector3Matrix3x1 pointMatrix; + public Vector3Matrix3x1 PointMatrix => pointMatrix; /// The starting point of the curve public Vector3 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The middle control point of the curve, sometimes called a tangent point public Vector3 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve public Vector3 P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 2 @@ -85,44 +85,44 @@ public Vector3 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial3D( - p0, - 2*(-p0+p1), - p0-2*p1+p2 + P0, + 2*(-P0+P1), + P0-2*P1+P2 ); } - public static bool operator ==( BezierQuad3D a, BezierQuad3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2; + public static bool operator ==( BezierQuad3D a, BezierQuad3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierQuad3D a, BezierQuad3D b ) => !( a == b ); public bool Equals( BezierQuad3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ); - public override bool Equals( object obj ) => obj is BezierQuad3D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2 ); + public override bool Equals( object obj ) => obj is BezierQuad3D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2})"; - public override string ToString() => $"({p0}, {p1}, {p2})"; /// Returns a linear blend between two bézier curves /// The first spline segment /// The second spline segment /// A value from 0 to 1 to blend between a and b public static BezierQuad3D Lerp( BezierQuad3D a, BezierQuad3D b, float t ) => new( - Vector3.LerpUnclamped( a.p0, b.p0, t ), - Vector3.LerpUnclamped( a.p1, b.p1, t ), - Vector3.LerpUnclamped( a.p2, b.p2, t ) + Vector3.LerpUnclamped( a.P0, b.P0, t ), + Vector3.LerpUnclamped( a.P1, b.P1, t ), + Vector3.LerpUnclamped( a.P2, b.P2, t ) ); /// Splits this curve at the given t-value, into two curves that together form the exact same shape /// The t-value to split at public (BezierQuad3D pre, BezierQuad3D post) Split( float t ) { Vector3 a = new Vector3( - p0.x + ( p1.x - p0.x ) * t, - p0.y + ( p1.y - p0.y ) * t, - p0.z + ( p1.z - p0.z ) * t ); + P0.x + ( P1.x - P0.x ) * t, + P0.y + ( P1.y - P0.y ) * t, + P0.z + ( P1.z - P0.z ) * t ); Vector3 b = new Vector3( - p1.x + ( p2.x - p1.x ) * t, - p1.y + ( p2.y - p1.y ) * t, - p1.z + ( p2.z - p1.z ) * t ); + P1.x + ( P2.x - P1.x ) * t, + P1.y + ( P2.y - P1.y ) * t, + P1.z + ( P2.z - P1.z ) * t ); Vector3 p = new Vector3( a.x + ( b.x - a.x ) * t, a.y + ( b.y - a.y ) * t, a.z + ( b.z - a.z ) * t ); - return ( new BezierQuad3D( p0, a, p ), new BezierQuad3D( p, b, p2 ) ); + return ( new BezierQuad3D( P0, a, p ), new BezierQuad3D( p, b, P2 ) ); } } } diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index e041593..5c038c8 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public CatRomCubic1D( float p0, float p1, float p2, float p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial Curve { } #region Control Points - [SerializeField] float p0, p1, p2, p3; - public Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Matrix4x1 pointMatrix; + public Matrix4x1 PointMatrix => pointMatrix; /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public float P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point, and the start of the catmull-rom curve public float P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point, and the end of the catmull-rom curve public float P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public float P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,39 +96,39 @@ public float this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial( - p1, - (-p0+p2)/2, - p0-(5/2f)*p1+2*p2-(1/2f)*p3, - -(1/2f)*p0+(3/2f)*p1-(3/2f)*p2+(1/2f)*p3 + P1, + (-P0+P2)/2, + P0-(5/2f)*P1+2*P2-(1/2f)*P3, + -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 ); } - public static bool operator ==( CatRomCubic1D a, CatRomCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( CatRomCubic1D a, CatRomCubic1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( CatRomCubic1D a, CatRomCubic1D b ) => !( a == b ); public bool Equals( CatRomCubic1D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is CatRomCubic1D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is CatRomCubic1D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; public static explicit operator BezierCubic1D( CatRomCubic1D s ) => new BezierCubic1D( - s.p1, - -(1/6f)*s.p0+s.p1+(1/6f)*s.p2, - (1/6f)*s.p1+s.p2-(1/6f)*s.p3, - s.p2 + s.P1, + -(1/6f)*s.P0+s.P1+(1/6f)*s.P2, + (1/6f)*s.P1+s.P2-(1/6f)*s.P3, + s.P2 ); public static explicit operator HermiteCubic1D( CatRomCubic1D s ) => new HermiteCubic1D( - s.p1, - (-s.p0+s.p2)/2, - s.p2, - (-s.p1+s.p3)/2 + s.P1, + (-s.P0+s.P2)/2, + s.P2, + (-s.P1+s.P3)/2 ); public static explicit operator UBSCubic1D( CatRomCubic1D s ) => new UBSCubic1D( - (7/6f)*s.p0-(2/3f)*s.p1+(5/6f)*s.p2-(1/3f)*s.p3, - -(1/3f)*s.p0+(11/6f)*s.p1-(2/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0-(2/3f)*s.p1+(11/6f)*s.p2-(1/3f)*s.p3, - -(1/3f)*s.p0+(5/6f)*s.p1-(2/3f)*s.p2+(7/6f)*s.p3 + (7/6f)*s.P0-(2/3f)*s.P1+(5/6f)*s.P2-(1/3f)*s.P3, + -(1/3f)*s.P0+(11/6f)*s.P1-(2/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0-(2/3f)*s.P1+(11/6f)*s.P2-(1/3f)*s.P3, + -(1/3f)*s.P0+(5/6f)*s.P1-(2/3f)*s.P2+(7/6f)*s.P3 ); /// Returns a linear blend between two catmull-rom curves /// The first spline segment @@ -136,10 +136,10 @@ public static explicit operator UBSCubic1D( CatRomCubic1D s ) => /// A value from 0 to 1 to blend between a and b public static CatRomCubic1D Lerp( CatRomCubic1D a, CatRomCubic1D b, float t ) => new( - Mathfs.Lerp( a.p0, b.p0, t ), - Mathfs.Lerp( a.p1, b.p1, t ), - Mathfs.Lerp( a.p2, b.p2, t ), - Mathfs.Lerp( a.p3, b.p3, t ) + Mathfs.Lerp( a.P0, b.P0, t ), + Mathfs.Lerp( a.P1, b.P1, t ), + Mathfs.Lerp( a.P2, b.P2, t ), + Mathfs.Lerp( a.P3, b.P3, t ) ); } } diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 271f1af..65536a5 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public CatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial2D Curve { } #region Control Points - [SerializeField] Vector2 p0, p1, p2, p3; - public Vector2Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Vector2Matrix4x1 pointMatrix; + public Vector2Matrix4x1 PointMatrix => pointMatrix; /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point, and the start of the catmull-rom curve public Vector2 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point, and the end of the catmull-rom curve public Vector2 P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,42 +96,42 @@ public Vector2 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial2D( - p1, - (-p0+p2)/2, - p0-(5/2f)*p1+2*p2-(1/2f)*p3, - -(1/2f)*p0+(3/2f)*p1-(3/2f)*p2+(1/2f)*p3 + P1, + (-P0+P2)/2, + P0-(5/2f)*P1+2*P2-(1/2f)*P3, + -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 ); } - public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); public bool Equals( CatRomCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is CatRomCubic2D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is CatRomCubic2D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D - public static explicit operator CatRomCubic3D( CatRomCubic2D curve2D ) => new CatRomCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); + public static explicit operator CatRomCubic3D( CatRomCubic2D curve2D ) => new CatRomCubic3D( curve2D.P0, curve2D.P1, curve2D.P2, curve2D.P3 ); public static explicit operator BezierCubic2D( CatRomCubic2D s ) => new BezierCubic2D( - s.p1, - -(1/6f)*s.p0+s.p1+(1/6f)*s.p2, - (1/6f)*s.p1+s.p2-(1/6f)*s.p3, - s.p2 + s.P1, + -(1/6f)*s.P0+s.P1+(1/6f)*s.P2, + (1/6f)*s.P1+s.P2-(1/6f)*s.P3, + s.P2 ); public static explicit operator HermiteCubic2D( CatRomCubic2D s ) => new HermiteCubic2D( - s.p1, - (-s.p0+s.p2)/2, - s.p2, - (-s.p1+s.p3)/2 + s.P1, + (-s.P0+s.P2)/2, + s.P2, + (-s.P1+s.P3)/2 ); public static explicit operator UBSCubic2D( CatRomCubic2D s ) => new UBSCubic2D( - (7/6f)*s.p0-(2/3f)*s.p1+(5/6f)*s.p2-(1/3f)*s.p3, - -(1/3f)*s.p0+(11/6f)*s.p1-(2/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0-(2/3f)*s.p1+(11/6f)*s.p2-(1/3f)*s.p3, - -(1/3f)*s.p0+(5/6f)*s.p1-(2/3f)*s.p2+(7/6f)*s.p3 + (7/6f)*s.P0-(2/3f)*s.P1+(5/6f)*s.P2-(1/3f)*s.P3, + -(1/3f)*s.P0+(11/6f)*s.P1-(2/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0-(2/3f)*s.P1+(11/6f)*s.P2-(1/3f)*s.P3, + -(1/3f)*s.P0+(5/6f)*s.P1-(2/3f)*s.P2+(7/6f)*s.P3 ); /// Returns a linear blend between two catmull-rom curves /// The first spline segment @@ -139,10 +139,10 @@ public static explicit operator UBSCubic2D( CatRomCubic2D s ) => /// A value from 0 to 1 to blend between a and b public static CatRomCubic2D Lerp( CatRomCubic2D a, CatRomCubic2D b, float t ) => new( - Vector2.LerpUnclamped( a.p0, b.p0, t ), - Vector2.LerpUnclamped( a.p1, b.p1, t ), - Vector2.LerpUnclamped( a.p2, b.p2, t ), - Vector2.LerpUnclamped( a.p3, b.p3, t ) + Vector2.LerpUnclamped( a.P0, b.P0, t ), + Vector2.LerpUnclamped( a.P1, b.P1, t ), + Vector2.LerpUnclamped( a.P2, b.P2, t ), + Vector2.LerpUnclamped( a.P3, b.P3, t ) ); } } diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 44492cd..2e4d1e4 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public CatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial3D Curve { } #region Control Points - [SerializeField] Vector3 p0, p1, p2, p3; - public Vector3Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Vector3Matrix4x1 pointMatrix; + public Vector3Matrix4x1 PointMatrix => pointMatrix; /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector3 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point, and the start of the catmull-rom curve public Vector3 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point, and the end of the catmull-rom curve public Vector3 P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector3 P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,42 +96,42 @@ public Vector3 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial3D( - p1, - (-p0+p2)/2, - p0-(5/2f)*p1+2*p2-(1/2f)*p3, - -(1/2f)*p0+(3/2f)*p1-(3/2f)*p2+(1/2f)*p3 + P1, + (-P0+P2)/2, + P0-(5/2f)*P1+2*P2-(1/2f)*P3, + -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 ); } - public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( CatRomCubic3D a, CatRomCubic3D b ) => !( a == b ); public bool Equals( CatRomCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is CatRomCubic3D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is CatRomCubic3D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane - public static explicit operator CatRomCubic2D( CatRomCubic3D curve3D ) => new CatRomCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); + public static explicit operator CatRomCubic2D( CatRomCubic3D curve3D ) => new CatRomCubic2D( curve3D.P0, curve3D.P1, curve3D.P2, curve3D.P3 ); public static explicit operator BezierCubic3D( CatRomCubic3D s ) => new BezierCubic3D( - s.p1, - -(1/6f)*s.p0+s.p1+(1/6f)*s.p2, - (1/6f)*s.p1+s.p2-(1/6f)*s.p3, - s.p2 + s.P1, + -(1/6f)*s.P0+s.P1+(1/6f)*s.P2, + (1/6f)*s.P1+s.P2-(1/6f)*s.P3, + s.P2 ); public static explicit operator HermiteCubic3D( CatRomCubic3D s ) => new HermiteCubic3D( - s.p1, - (-s.p0+s.p2)/2, - s.p2, - (-s.p1+s.p3)/2 + s.P1, + (-s.P0+s.P2)/2, + s.P2, + (-s.P1+s.P3)/2 ); public static explicit operator UBSCubic3D( CatRomCubic3D s ) => new UBSCubic3D( - (7/6f)*s.p0-(2/3f)*s.p1+(5/6f)*s.p2-(1/3f)*s.p3, - -(1/3f)*s.p0+(11/6f)*s.p1-(2/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0-(2/3f)*s.p1+(11/6f)*s.p2-(1/3f)*s.p3, - -(1/3f)*s.p0+(5/6f)*s.p1-(2/3f)*s.p2+(7/6f)*s.p3 + (7/6f)*s.P0-(2/3f)*s.P1+(5/6f)*s.P2-(1/3f)*s.P3, + -(1/3f)*s.P0+(11/6f)*s.P1-(2/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0-(2/3f)*s.P1+(11/6f)*s.P2-(1/3f)*s.P3, + -(1/3f)*s.P0+(5/6f)*s.P1-(2/3f)*s.P2+(7/6f)*s.P3 ); /// Returns a linear blend between two catmull-rom curves /// The first spline segment @@ -139,10 +139,10 @@ public static explicit operator UBSCubic3D( CatRomCubic3D s ) => /// A value from 0 to 1 to blend between a and b public static CatRomCubic3D Lerp( CatRomCubic3D a, CatRomCubic3D b, float t ) => new( - Vector3.LerpUnclamped( a.p0, b.p0, t ), - Vector3.LerpUnclamped( a.p1, b.p1, t ), - Vector3.LerpUnclamped( a.p2, b.p2, t ), - Vector3.LerpUnclamped( a.p3, b.p3, t ) + Vector3.LerpUnclamped( a.P0, b.P0, t ), + Vector3.LerpUnclamped( a.P1, b.P1, t ), + Vector3.LerpUnclamped( a.P2, b.P2, t ), + Vector3.LerpUnclamped( a.P3, b.P3, t ) ); } } diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index c15a5fe..b686d4a 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The end point of the curve /// The rate of change (velocity) at the end of the curve public HermiteCubic1D( float p0, float v0, float p1, float v1 ) { - ( this.p0, this.v0, this.p1, this.v1 ) = ( p0, v0, p1, v1 ); + pointMatrix = new Matrix4x1( p0, v0, p1, v1 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial Curve { } #region Control Points - [SerializeField] float p0, v0, p1, v1; - public Matrix4x1 PointMatrix => new(p0, v0, p1, v1); + [SerializeField] Matrix4x1 pointMatrix; + public Matrix4x1 PointMatrix => pointMatrix; /// The starting point of the curve public float P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The rate of change (velocity) at the start of the curve public float V0 { - [MethodImpl( INLINE )] get => v0; - [MethodImpl( INLINE )] set => _ = ( v0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve public float P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The rate of change (velocity) at the end of the curve public float V1 { - [MethodImpl( INLINE )] get => v1; - [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,39 +96,39 @@ public float this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial( - p0, - v0, - -3*p0-2*v0+3*p1-v1, - 2*p0+v0-2*p1+v1 + P0, + V0, + -3*P0-2*V0+3*P1-V1, + 2*P0+V0-2*P1+V1 ); } - public static bool operator ==( HermiteCubic1D a, HermiteCubic1D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; + public static bool operator ==( HermiteCubic1D a, HermiteCubic1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( HermiteCubic1D a, HermiteCubic1D b ) => !( a == b ); public bool Equals( HermiteCubic1D other ) => P0.Equals( other.P0 ) && V0.Equals( other.V0 ) && P1.Equals( other.P1 ) && V1.Equals( other.V1 ); - public override bool Equals( object obj ) => obj is HermiteCubic1D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); + public override bool Equals( object obj ) => obj is HermiteCubic1D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; public static explicit operator BezierCubic1D( HermiteCubic1D s ) => new BezierCubic1D( - s.p0, - s.p0+(1/3f)*s.v0, - s.p1-(1/3f)*s.v1, - s.p1 + s.P0, + s.P0+(1/3f)*s.V0, + s.P1-(1/3f)*s.V1, + s.P1 ); public static explicit operator CatRomCubic1D( HermiteCubic1D s ) => new CatRomCubic1D( - -2*s.v0+s.p1, - s.p0, - s.p1, - s.p0+2*s.v1 + -2*s.V0+s.P1, + s.P0, + s.P1, + s.P0+2*s.V1 ); public static explicit operator UBSCubic1D( HermiteCubic1D s ) => new UBSCubic1D( - -s.p0-(7/3f)*s.v0+2*s.p1-(2/3f)*s.v1, - 2*s.p0+(2/3f)*s.v0-s.p1+(1/3f)*s.v1, - -s.p0-(1/3f)*s.v0+2*s.p1-(2/3f)*s.v1, - 2*s.p0+(2/3f)*s.v0-s.p1+(7/3f)*s.v1 + -s.P0-(7/3f)*s.V0+2*s.P1-(2/3f)*s.V1, + 2*s.P0+(2/3f)*s.V0-s.P1+(1/3f)*s.V1, + -s.P0-(1/3f)*s.V0+2*s.P1-(2/3f)*s.V1, + 2*s.P0+(2/3f)*s.V0-s.P1+(7/3f)*s.V1 ); /// Returns a linear blend between two hermite curves /// The first spline segment @@ -136,10 +136,10 @@ public static explicit operator UBSCubic1D( HermiteCubic1D s ) => /// A value from 0 to 1 to blend between a and b public static HermiteCubic1D Lerp( HermiteCubic1D a, HermiteCubic1D b, float t ) => new( - Mathfs.Lerp( a.p0, b.p0, t ), - Mathfs.Lerp( a.v0, b.v0, t ), - Mathfs.Lerp( a.p1, b.p1, t ), - Mathfs.Lerp( a.v1, b.v1, t ) + Mathfs.Lerp( a.P0, b.P0, t ), + Mathfs.Lerp( a.V0, b.V0, t ), + Mathfs.Lerp( a.P1, b.P1, t ), + Mathfs.Lerp( a.V1, b.V1, t ) ); } } diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index 91a52bd..365632b 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The end point of the curve /// The rate of change (velocity) at the end of the curve public HermiteCubic2D( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) { - ( this.p0, this.v0, this.p1, this.v1 ) = ( p0, v0, p1, v1 ); + pointMatrix = new Vector2Matrix4x1( p0, v0, p1, v1 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial2D Curve { } #region Control Points - [SerializeField] Vector2 p0, v0, p1, v1; - public Vector2Matrix4x1 PointMatrix => new(p0, v0, p1, v1); + [SerializeField] Vector2Matrix4x1 pointMatrix; + public Vector2Matrix4x1 PointMatrix => pointMatrix; /// The starting point of the curve public Vector2 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The rate of change (velocity) at the start of the curve public Vector2 V0 { - [MethodImpl( INLINE )] get => v0; - [MethodImpl( INLINE )] set => _ = ( v0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve public Vector2 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The rate of change (velocity) at the end of the curve public Vector2 V1 { - [MethodImpl( INLINE )] get => v1; - [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,42 +96,42 @@ public Vector2 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial2D( - p0, - v0, - -3*p0-2*v0+3*p1-v1, - 2*p0+v0-2*p1+v1 + P0, + V0, + -3*P0-2*V0+3*P1-V1, + 2*P0+V0-2*P1+V1 ); } - public static bool operator ==( HermiteCubic2D a, HermiteCubic2D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; + public static bool operator ==( HermiteCubic2D a, HermiteCubic2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( HermiteCubic2D a, HermiteCubic2D b ) => !( a == b ); public bool Equals( HermiteCubic2D other ) => P0.Equals( other.P0 ) && V0.Equals( other.V0 ) && P1.Equals( other.P1 ) && V1.Equals( other.V1 ); - public override bool Equals( object obj ) => obj is HermiteCubic2D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); + public override bool Equals( object obj ) => obj is HermiteCubic2D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D - public static explicit operator HermiteCubic3D( HermiteCubic2D curve2D ) => new HermiteCubic3D( curve2D.p0, curve2D.v0, curve2D.p1, curve2D.v1 ); + public static explicit operator HermiteCubic3D( HermiteCubic2D curve2D ) => new HermiteCubic3D( curve2D.P0, curve2D.V0, curve2D.P1, curve2D.V1 ); public static explicit operator BezierCubic2D( HermiteCubic2D s ) => new BezierCubic2D( - s.p0, - s.p0+(1/3f)*s.v0, - s.p1-(1/3f)*s.v1, - s.p1 + s.P0, + s.P0+(1/3f)*s.V0, + s.P1-(1/3f)*s.V1, + s.P1 ); public static explicit operator CatRomCubic2D( HermiteCubic2D s ) => new CatRomCubic2D( - -2*s.v0+s.p1, - s.p0, - s.p1, - s.p0+2*s.v1 + -2*s.V0+s.P1, + s.P0, + s.P1, + s.P0+2*s.V1 ); public static explicit operator UBSCubic2D( HermiteCubic2D s ) => new UBSCubic2D( - -s.p0-(7/3f)*s.v0+2*s.p1-(2/3f)*s.v1, - 2*s.p0+(2/3f)*s.v0-s.p1+(1/3f)*s.v1, - -s.p0-(1/3f)*s.v0+2*s.p1-(2/3f)*s.v1, - 2*s.p0+(2/3f)*s.v0-s.p1+(7/3f)*s.v1 + -s.P0-(7/3f)*s.V0+2*s.P1-(2/3f)*s.V1, + 2*s.P0+(2/3f)*s.V0-s.P1+(1/3f)*s.V1, + -s.P0-(1/3f)*s.V0+2*s.P1-(2/3f)*s.V1, + 2*s.P0+(2/3f)*s.V0-s.P1+(7/3f)*s.V1 ); /// Returns a linear blend between two hermite curves /// The first spline segment @@ -139,10 +139,10 @@ public static explicit operator UBSCubic2D( HermiteCubic2D s ) => /// A value from 0 to 1 to blend between a and b public static HermiteCubic2D Lerp( HermiteCubic2D a, HermiteCubic2D b, float t ) => new( - Vector2.LerpUnclamped( a.p0, b.p0, t ), - Vector2.LerpUnclamped( a.v0, b.v0, t ), - Vector2.LerpUnclamped( a.p1, b.p1, t ), - Vector2.LerpUnclamped( a.v1, b.v1, t ) + Vector2.LerpUnclamped( a.P0, b.P0, t ), + Vector2.LerpUnclamped( a.V0, b.V0, t ), + Vector2.LerpUnclamped( a.P1, b.P1, t ), + Vector2.LerpUnclamped( a.V1, b.V1, t ) ); } } diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index bb6b717..7e09e56 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The end point of the curve /// The rate of change (velocity) at the end of the curve public HermiteCubic3D( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) { - ( this.p0, this.v0, this.p1, this.v1 ) = ( p0, v0, p1, v1 ); + pointMatrix = new Vector3Matrix4x1( p0, v0, p1, v1 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial3D Curve { } #region Control Points - [SerializeField] Vector3 p0, v0, p1, v1; - public Vector3Matrix4x1 PointMatrix => new(p0, v0, p1, v1); + [SerializeField] Vector3Matrix4x1 pointMatrix; + public Vector3Matrix4x1 PointMatrix => pointMatrix; /// The starting point of the curve public Vector3 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The rate of change (velocity) at the start of the curve public Vector3 V0 { - [MethodImpl( INLINE )] get => v0; - [MethodImpl( INLINE )] set => _ = ( v0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve public Vector3 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The rate of change (velocity) at the end of the curve public Vector3 V1 { - [MethodImpl( INLINE )] get => v1; - [MethodImpl( INLINE )] set => _ = ( v1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,42 +96,42 @@ public Vector3 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial3D( - p0, - v0, - -3*p0-2*v0+3*p1-v1, - 2*p0+v0-2*p1+v1 + P0, + V0, + -3*P0-2*V0+3*P1-V1, + 2*P0+V0-2*P1+V1 ); } - public static bool operator ==( HermiteCubic3D a, HermiteCubic3D b ) => a.P0 == b.P0 && a.V0 == b.V0 && a.P1 == b.P1 && a.V1 == b.V1; + public static bool operator ==( HermiteCubic3D a, HermiteCubic3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( HermiteCubic3D a, HermiteCubic3D b ) => !( a == b ); public bool Equals( HermiteCubic3D other ) => P0.Equals( other.P0 ) && V0.Equals( other.V0 ) && P1.Equals( other.P1 ) && V1.Equals( other.V1 ); - public override bool Equals( object obj ) => obj is HermiteCubic3D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, v0, p1, v1 ); + public override bool Equals( object obj ) => obj is HermiteCubic3D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {v0}, {p1}, {v1})"; /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane - public static explicit operator HermiteCubic2D( HermiteCubic3D curve3D ) => new HermiteCubic2D( curve3D.p0, curve3D.v0, curve3D.p1, curve3D.v1 ); + public static explicit operator HermiteCubic2D( HermiteCubic3D curve3D ) => new HermiteCubic2D( curve3D.P0, curve3D.V0, curve3D.P1, curve3D.V1 ); public static explicit operator BezierCubic3D( HermiteCubic3D s ) => new BezierCubic3D( - s.p0, - s.p0+(1/3f)*s.v0, - s.p1-(1/3f)*s.v1, - s.p1 + s.P0, + s.P0+(1/3f)*s.V0, + s.P1-(1/3f)*s.V1, + s.P1 ); public static explicit operator CatRomCubic3D( HermiteCubic3D s ) => new CatRomCubic3D( - -2*s.v0+s.p1, - s.p0, - s.p1, - s.p0+2*s.v1 + -2*s.V0+s.P1, + s.P0, + s.P1, + s.P0+2*s.V1 ); public static explicit operator UBSCubic3D( HermiteCubic3D s ) => new UBSCubic3D( - -s.p0-(7/3f)*s.v0+2*s.p1-(2/3f)*s.v1, - 2*s.p0+(2/3f)*s.v0-s.p1+(1/3f)*s.v1, - -s.p0-(1/3f)*s.v0+2*s.p1-(2/3f)*s.v1, - 2*s.p0+(2/3f)*s.v0-s.p1+(7/3f)*s.v1 + -s.P0-(7/3f)*s.V0+2*s.P1-(2/3f)*s.V1, + 2*s.P0+(2/3f)*s.V0-s.P1+(1/3f)*s.V1, + -s.P0-(1/3f)*s.V0+2*s.P1-(2/3f)*s.V1, + 2*s.P0+(2/3f)*s.V0-s.P1+(7/3f)*s.V1 ); /// Returns a linear blend between two hermite curves /// The first spline segment @@ -139,10 +139,10 @@ public static explicit operator UBSCubic3D( HermiteCubic3D s ) => /// A value from 0 to 1 to blend between a and b public static HermiteCubic3D Lerp( HermiteCubic3D a, HermiteCubic3D b, float t ) => new( - Vector3.LerpUnclamped( a.p0, b.p0, t ), - Vector3.LerpUnclamped( a.v0, b.v0, t ), - Vector3.LerpUnclamped( a.p1, b.p1, t ), - Vector3.LerpUnclamped( a.v1, b.v1, t ) + Vector3.LerpUnclamped( a.P0, b.P0, t ), + Vector3.LerpUnclamped( a.V0, b.V0, t ), + Vector3.LerpUnclamped( a.P1, b.P1, t ), + Vector3.LerpUnclamped( a.V1, b.V1, t ) ); } } diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index 371062c..aadfd27 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third point of the B-spline hull /// The fourth point of the B-spline hull public UBSCubic1D( float p0, float p1, float p2, float p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial Curve { } #region Control Points - [SerializeField] float p0, p1, p2, p3; - public Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Matrix4x1 pointMatrix; + public Matrix4x1 PointMatrix => pointMatrix; /// The first point of the B-spline hull public float P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second point of the B-spline hull public float P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third point of the B-spline hull public float P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The fourth point of the B-spline hull public float P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,39 +96,39 @@ public float this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial( - (1/6f)*p0+(2/3f)*p1+(1/6f)*p2, - (-p0+p2)/2, - (1/2f)*p0-p1+(1/2f)*p2, - -(1/6f)*p0+(1/2f)*p1-(1/2f)*p2+(1/6f)*p3 + (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, + (-P0+P2)/2, + (1/2f)*P0-P1+(1/2f)*P2, + -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 ); } - public static bool operator ==( UBSCubic1D a, UBSCubic1D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( UBSCubic1D a, UBSCubic1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( UBSCubic1D a, UBSCubic1D b ) => !( a == b ); public bool Equals( UBSCubic1D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is UBSCubic1D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is UBSCubic1D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; public static explicit operator BezierCubic1D( UBSCubic1D s ) => new BezierCubic1D( - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (2/3f)*s.p1+(1/3f)*s.p2, - (1/3f)*s.p1+(2/3f)*s.p2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3 + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (2/3f)*s.P1+(1/3f)*s.P2, + (1/3f)*s.P1+(2/3f)*s.P2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3 ); public static explicit operator HermiteCubic1D( UBSCubic1D s ) => new HermiteCubic1D( - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (-s.p0+s.p2)/2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - (-s.p1+s.p3)/2 + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (-s.P0+s.P2)/2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3, + (-s.P1+s.P3)/2 ); public static explicit operator CatRomCubic1D( UBSCubic1D s ) => new CatRomCubic1D( - s.p0+(1/6f)*s.p1-(1/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0-(1/3f)*s.p1+(1/6f)*s.p2+s.p3 + s.P0+(1/6f)*s.P1-(1/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0-(1/3f)*s.P1+(1/6f)*s.P2+s.P3 ); /// Returns a linear blend between two b-spline curves /// The first spline segment @@ -136,10 +136,10 @@ public static explicit operator CatRomCubic1D( UBSCubic1D s ) => /// A value from 0 to 1 to blend between a and b public static UBSCubic1D Lerp( UBSCubic1D a, UBSCubic1D b, float t ) => new( - Mathfs.Lerp( a.p0, b.p0, t ), - Mathfs.Lerp( a.p1, b.p1, t ), - Mathfs.Lerp( a.p2, b.p2, t ), - Mathfs.Lerp( a.p3, b.p3, t ) + Mathfs.Lerp( a.P0, b.P0, t ), + Mathfs.Lerp( a.P1, b.P1, t ), + Mathfs.Lerp( a.P2, b.P2, t ), + Mathfs.Lerp( a.P3, b.P3, t ) ); } } diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index 5dfe22f..f1ba438 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third point of the B-spline hull /// The fourth point of the B-spline hull public UBSCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial2D Curve { } #region Control Points - [SerializeField] Vector2 p0, p1, p2, p3; - public Vector2Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Vector2Matrix4x1 pointMatrix; + public Vector2Matrix4x1 PointMatrix => pointMatrix; /// The first point of the B-spline hull public Vector2 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second point of the B-spline hull public Vector2 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third point of the B-spline hull public Vector2 P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The fourth point of the B-spline hull public Vector2 P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,42 +96,42 @@ public Vector2 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial2D( - (1/6f)*p0+(2/3f)*p1+(1/6f)*p2, - (-p0+p2)/2, - (1/2f)*p0-p1+(1/2f)*p2, - -(1/6f)*p0+(1/2f)*p1-(1/2f)*p2+(1/6f)*p3 + (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, + (-P0+P2)/2, + (1/2f)*P0-P1+(1/2f)*P2, + -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 ); } - public static bool operator ==( UBSCubic2D a, UBSCubic2D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( UBSCubic2D a, UBSCubic2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( UBSCubic2D a, UBSCubic2D b ) => !( a == b ); public bool Equals( UBSCubic2D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is UBSCubic2D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is UBSCubic2D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this spline segment in 3D, where z = 0 /// The 2D curve to cast to 3D - public static explicit operator UBSCubic3D( UBSCubic2D curve2D ) => new UBSCubic3D( curve2D.p0, curve2D.p1, curve2D.p2, curve2D.p3 ); + public static explicit operator UBSCubic3D( UBSCubic2D curve2D ) => new UBSCubic3D( curve2D.P0, curve2D.P1, curve2D.P2, curve2D.P3 ); public static explicit operator BezierCubic2D( UBSCubic2D s ) => new BezierCubic2D( - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (2/3f)*s.p1+(1/3f)*s.p2, - (1/3f)*s.p1+(2/3f)*s.p2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3 + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (2/3f)*s.P1+(1/3f)*s.P2, + (1/3f)*s.P1+(2/3f)*s.P2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3 ); public static explicit operator HermiteCubic2D( UBSCubic2D s ) => new HermiteCubic2D( - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (-s.p0+s.p2)/2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - (-s.p1+s.p3)/2 + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (-s.P0+s.P2)/2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3, + (-s.P1+s.P3)/2 ); public static explicit operator CatRomCubic2D( UBSCubic2D s ) => new CatRomCubic2D( - s.p0+(1/6f)*s.p1-(1/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0-(1/3f)*s.p1+(1/6f)*s.p2+s.p3 + s.P0+(1/6f)*s.P1-(1/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0-(1/3f)*s.P1+(1/6f)*s.P2+s.P3 ); /// Returns a linear blend between two b-spline curves /// The first spline segment @@ -139,10 +139,10 @@ public static explicit operator CatRomCubic2D( UBSCubic2D s ) => /// A value from 0 to 1 to blend between a and b public static UBSCubic2D Lerp( UBSCubic2D a, UBSCubic2D b, float t ) => new( - Vector2.LerpUnclamped( a.p0, b.p0, t ), - Vector2.LerpUnclamped( a.p1, b.p1, t ), - Vector2.LerpUnclamped( a.p2, b.p2, t ), - Vector2.LerpUnclamped( a.p3, b.p3, t ) + Vector2.LerpUnclamped( a.P0, b.P0, t ), + Vector2.LerpUnclamped( a.P1, b.P1, t ), + Vector2.LerpUnclamped( a.P2, b.P2, t ), + Vector2.LerpUnclamped( a.P3, b.P3, t ) ); } } diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index b2430db..80cf8cd 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -18,7 +18,7 @@ namespace Freya { /// The third point of the B-spline hull /// The fourth point of the B-spline hull public UBSCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; } @@ -32,31 +32,31 @@ public Polynomial3D Curve { } #region Control Points - [SerializeField] Vector3 p0, p1, p2, p3; - public Vector3Matrix4x1 PointMatrix => new(p0, p1, p2, p3); + [SerializeField] Vector3Matrix4x1 pointMatrix; + public Vector3Matrix4x1 PointMatrix => pointMatrix; /// The first point of the B-spline hull public Vector3 P0 { - [MethodImpl( INLINE )] get => p0; - [MethodImpl( INLINE )] set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second point of the B-spline hull public Vector3 P1 { - [MethodImpl( INLINE )] get => p1; - [MethodImpl( INLINE )] set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third point of the B-spline hull public Vector3 P2 { - [MethodImpl( INLINE )] get => p2; - [MethodImpl( INLINE )] set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The fourth point of the B-spline hull public Vector3 P3 { - [MethodImpl( INLINE )] get => p3; - [MethodImpl( INLINE )] set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 @@ -96,42 +96,42 @@ public Vector3 this[ int i ] { return; // no need to update validCoefficients = true; curve = new Polynomial3D( - (1/6f)*p0+(2/3f)*p1+(1/6f)*p2, - (-p0+p2)/2, - (1/2f)*p0-p1+(1/2f)*p2, - -(1/6f)*p0+(1/2f)*p1-(1/2f)*p2+(1/6f)*p3 + (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, + (-P0+P2)/2, + (1/2f)*P0-P1+(1/2f)*P2, + -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 ); } - public static bool operator ==( UBSCubic3D a, UBSCubic3D b ) => a.P0 == b.P0 && a.P1 == b.P1 && a.P2 == b.P2 && a.P3 == b.P3; + public static bool operator ==( UBSCubic3D a, UBSCubic3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( UBSCubic3D a, UBSCubic3D b ) => !( a == b ); public bool Equals( UBSCubic3D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); - public override bool Equals( object obj ) => obj is UBSCubic3D other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( p0, p1, p2, p3 ); + public override bool Equals( object obj ) => obj is UBSCubic3D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; - public override string ToString() => $"({p0}, {p1}, {p2}, {p3})"; /// Returns this curve flattened to 2D. Effectively setting z = 0 /// The 3D curve to flatten to the Z plane - public static explicit operator UBSCubic2D( UBSCubic3D curve3D ) => new UBSCubic2D( curve3D.p0, curve3D.p1, curve3D.p2, curve3D.p3 ); + public static explicit operator UBSCubic2D( UBSCubic3D curve3D ) => new UBSCubic2D( curve3D.P0, curve3D.P1, curve3D.P2, curve3D.P3 ); public static explicit operator BezierCubic3D( UBSCubic3D s ) => new BezierCubic3D( - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (2/3f)*s.p1+(1/3f)*s.p2, - (1/3f)*s.p1+(2/3f)*s.p2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3 + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (2/3f)*s.P1+(1/3f)*s.P2, + (1/3f)*s.P1+(2/3f)*s.P2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3 ); public static explicit operator HermiteCubic3D( UBSCubic3D s ) => new HermiteCubic3D( - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (-s.p0+s.p2)/2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - (-s.p1+s.p3)/2 + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (-s.P0+s.P2)/2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3, + (-s.P1+s.P3)/2 ); public static explicit operator CatRomCubic3D( UBSCubic3D s ) => new CatRomCubic3D( - s.p0+(1/6f)*s.p1-(1/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0+(2/3f)*s.p1+(1/6f)*s.p2, - (1/6f)*s.p1+(2/3f)*s.p2+(1/6f)*s.p3, - (1/6f)*s.p0-(1/3f)*s.p1+(1/6f)*s.p2+s.p3 + s.P0+(1/6f)*s.P1-(1/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0-(1/3f)*s.P1+(1/6f)*s.P2+s.P3 ); /// Returns a linear blend between two b-spline curves /// The first spline segment @@ -139,10 +139,10 @@ public static explicit operator CatRomCubic3D( UBSCubic3D s ) => /// A value from 0 to 1 to blend between a and b public static UBSCubic3D Lerp( UBSCubic3D a, UBSCubic3D b, float t ) => new( - Vector3.LerpUnclamped( a.p0, b.p0, t ), - Vector3.LerpUnclamped( a.p1, b.p1, t ), - Vector3.LerpUnclamped( a.p2, b.p2, t ), - Vector3.LerpUnclamped( a.p3, b.p3, t ) + Vector3.LerpUnclamped( a.P0, b.P0, t ), + Vector3.LerpUnclamped( a.P1, b.P1, t ), + Vector3.LerpUnclamped( a.P2, b.P2, t ), + Vector3.LerpUnclamped( a.P3, b.P3, t ) ); } } From 335708fed0d01c4a825faf6da82f89bb99a68b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 13:35:17 +0200 Subject: [PATCH 091/301] added upgrade scene spline serialization function --- Codegen/Editor/MathfsCodegen.cs | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index c6bf72b..61aec37 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -5,6 +5,9 @@ using System.IO; using System.Linq; using UnityEditor; +using UnityEngine; +using UnityEngine.SceneManagement; +using Debug = UnityEngine.Debug; namespace Freya { @@ -87,8 +90,67 @@ public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { } ); + static SplineType[] allSplineTypes = { typeBezier, typeBezierQuad, typeHermite, typeBspline, typeCatRom }; + #endregion + // [MenuItem( "Assets/Port Spline Data" )] + public static void PortSplineData() { + string replacements = ""; + int replacementCount = 0; + GameObject[] gos = SceneManager.GetActiveScene().GetRootGameObjects(); + foreach( GameObject go in gos ) { + foreach( Component c in go.GetComponentsInChildren( true ) ) { + SerializedObject so = new SerializedObject( c ); // can actually find null components?? + so.Update(); + bool madeChanges = false; + SerializedProperty prop = so.GetIterator(); + while( prop.Next( true ) ) { + if( prop.isArray == false && IsSplineType( prop.type, out SplineType type, out int dim ) ) { + SerializedProperty ptMtx = prop.FindPropertyRelative( "pointMatrix" ); + try { + if( dim == 1 ) + for( int i = 0; i < type.paramNames.Length; i++ ) + ptMtx.FindPropertyRelative( $"m{i}" ).floatValue = prop.FindPropertyRelative( type.paramNames[i] ).floatValue; + else if( dim == 2 ) + for( int i = 0; i < type.paramNames.Length; i++ ) + ptMtx.FindPropertyRelative( $"m{i}" ).vector2Value = prop.FindPropertyRelative( type.paramNames[i] ).vector2Value; + else if( dim == 3 ) + for( int i = 0; i < type.paramNames.Length; i++ ) + ptMtx.FindPropertyRelative( $"m{i}" ).vector3Value = prop.FindPropertyRelative( type.paramNames[i] ).vector3Value; + } catch { + Debug.LogError( $"Null thing in {go.name}/{c.GetType().Name}/{prop.propertyPath} of type {type.className} mtx: {type.matrixName}" ); + } + + madeChanges = true; + replacements += $"Replaced: {go.name}/{c.GetType().Name}: {prop.displayName}\n"; + replacementCount++; + } + } + + if( madeChanges ) + so.ApplyModifiedProperties(); + } + } + + Debug.Log( $"{replacementCount} replacements:\n{replacements}" ); + } + + static bool IsSplineType( string name, out SplineType type, out int dim ) { + foreach( SplineType spline in allSplineTypes ) { + for( int d = 1; d <= 3; d++ ) { + if( name == $"{spline.className}{GetDegreeName( spline.degree, true )}{d}D" ) { + dim = d; + type = spline; + return true; + } + } + } + + ( type, dim ) = ( default, default ); + return false; + } + [MenuItem( "Assets/Run Mathfs Codegen" )] public static void Regenerate() { for( int dim = 1; dim < 4; dim++ ) { // 1D, 2D, 3D From 5e872b4048708e439b8f2d58b0d41f07caa4e0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 13:45:34 +0200 Subject: [PATCH 092/301] martix types are now generated in code --- Codegen/Editor/MathfsCodegen.cs | 79 +++++++++++++++++++++++++++++++++ Numerics/Matrix3x1.cs | 35 ++++----------- Numerics/Matrix4x1.cs | 34 ++++---------- Numerics/Vector2Matrix3x1.cs | 43 ++++-------------- Numerics/Vector2Matrix4x1.cs | 34 +++++--------- Numerics/Vector3Matrix3x1.cs | 43 ++++-------------- Numerics/Vector3Matrix4x1.cs | 34 +++++--------- 7 files changed, 137 insertions(+), 165 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 61aec37..1dda5a7 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -159,6 +159,8 @@ public static void Regenerate() { GenerateType( typeHermite, dim ); GenerateType( typeBspline, dim ); GenerateType( typeCatRom, dim ); + GenerateMatrix( 3, dim ); + GenerateMatrix( 4, dim ); } } @@ -172,6 +174,83 @@ public static string GetLerpName( int dim ) { }; } + + static void GenerateMatrix( int count, int dim ) { + const string vCompStr = "xyz"; + const string vCompStrUp = "XYZ"; + int[] elemRange = Enumerable.Range( 0, count ).ToArray(); + int[] compRange = Enumerable.Range( 0, dim ).ToArray(); + string[] compRangeStr = compRange.Select( c => vCompStr[c].ToString() ).ToArray(); + string JoinRange( string separator, Func elem ) => string.Join( separator, elemRange.Select( elem ) ); + string typePrefix = dim switch { 2 => "Vector2", 3 => "Vector3", _ => "" }; + string elemType = dim switch { 1 => "float", 2 => "Vector2", 3 => "Vector3", _ => throw new Exception( "Invalid type" ) }; + + string typeName = $"{typePrefix}Matrix{count}x1"; + string csParams = JoinRange( ", ", i => $"m{i}" ); + string csParamsThis = JoinRange( ", ", i => $"this.m{i}" ); + string ctorParams = JoinRange( ", ", i => $"{elemType} m{i}" ); + string indexerException = $"throw new IndexOutOfRangeException( $\"Matrix row index has to be from 0 to {count - 1}, got: {{row}}\" )"; + string indexerGetterCases = JoinRange( ", ", i => $"{i} => m{i}" ) + $", _ => {indexerException}"; + string equalsCompare = JoinRange( " && ", i => $"m{i}.Equals( other.m{i} )" ); + string equalsOpCompare = JoinRange( " && ", i => $"a.m{i} == b.m{i}" ); + + + // generate content + CodeGenerator code = new CodeGenerator(); + code.AppendHeader(); + code.Append( "using System;" ); + if( dim > 1 ) // for Vector2/3 + code.Append( "using UnityEngine;" ); + + using( code.BracketScope( "namespace Freya" ) ) { + code.Summary( $"A {count}x1 column matrix with {elemType} values" ); + using( code.BracketScope( $"[Serializable] public struct {typeName}" ) ) { + // fields + code.Append( $"public {elemType} {csParams};" ); + + // constructors + code.Append( $"public {typeName}({ctorParams}) => ({csParamsThis}) = ({csParams});" ); + if( dim > 1 ) { // compose from float matrices + string s = $"public {typeName}({string.Join( ", ", compRangeStr.Select( c => $"Matrix{count}x1 {c}" ) )}) => "; + s += $"({csParams}) = ({JoinRange( ", ", i => $"new {elemType}({string.Join( ", ", compRangeStr.Select( c => $"{c}.m{i}" ) )})" )});"; + code.Append( s ); + } + + // indexer + using( code.BracketScope( $"public {elemType} this[int row]" ) ) { + code.Append( $"get => row switch{{{indexerGetterCases}}};" ); + using( code.BracketScope( "set" ) ) { + using( code.BracketScope( "switch(row)" ) ) { + code.Append( JoinRange( " ", i => $"case {i}: m{i} = value; break;" ) ); + code.Append( $"default: {indexerException};" ); + } + } + } + + // component extraction for vector-valued matrices + if( dim > 1 ) { + for( int c = 0; c < dim; c++ ) { + int cc = c; + string parameters = JoinRange( ", ", i => $"m{i}.{vCompStr[cc]}" ); + code.Append( $"public Matrix{count}x1 {vCompStrUp[c]} => new({parameters});" ); + } + } + + // comparison/operators + code.Append( $"public static bool operator ==( {typeName} a, {typeName} b ) => {equalsOpCompare};" ); + code.Append( $"public static bool operator !=( {typeName} a, {typeName} b ) => !( a == b );" ); + code.Append( $"public bool Equals( {typeName} other ) => {equalsCompare};" ); + code.Append( $"public override bool Equals( object obj ) => obj is {typeName} other && Equals( other );" ); + code.Append( $"public override int GetHashCode() => HashCode.Combine( {csParams} );" ); + } + } + + + // save/finalize + string path = $"Assets/Mathfs/Numerics/{typeName}.cs"; + File.WriteAllLines( path, code.content ); + } + static void GenerateType( SplineType type, int dim ) { int degree = type.degree; string dataType = dim == 1 ? "float" : $"Vector{dim}"; diff --git a/Numerics/Matrix3x1.cs b/Numerics/Matrix3x1.cs index ad2b78b..8e65644 100644 --- a/Numerics/Matrix3x1.cs +++ b/Numerics/Matrix3x1.cs @@ -1,44 +1,25 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; - namespace Freya { - /// A 3x1 column matrix with float values [Serializable] public struct Matrix3x1 { - public float m0, m1, m2; - - public Matrix3x1( float m0, float m1, float m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); - - public float this[ int column ] { - get => - column switch { - 0 => m0, 1 => m1, 2 => m2, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ) - }; + public Matrix3x1(float m0, float m1, float m2) => (this.m0, this.m1, this.m2) = (m0, m1, m2); + public float this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 2, got: {row}" )}; set { - switch( column ) { - case 0: - m0 = value; - break; - case 1: - m1 = value; - break; - case 2: - m2 = value; - break; - default: throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ); + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 2, got: {row}" ); } } } - public static bool operator ==( Matrix3x1 a, Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; public static bool operator !=( Matrix3x1 a, Matrix3x1 b ) => !( a == b ); public bool Equals( Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); public override bool Equals( object obj ) => obj is Matrix3x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); - } - -} \ No newline at end of file +} diff --git a/Numerics/Matrix4x1.cs b/Numerics/Matrix4x1.cs index 42fb33d..a256154 100644 --- a/Numerics/Matrix4x1.cs +++ b/Numerics/Matrix4x1.cs @@ -1,43 +1,25 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; - namespace Freya { - /// A 4x1 column matrix with float values [Serializable] public struct Matrix4x1 { - public float m0, m1, m2, m3; - - public Matrix4x1( float m0, float m1, float m2, float m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); - - public float this[ int column ] { - get => column switch { 0 => m0, 1 => m1, 2 => m2, 3 => m3, _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) }; + public Matrix4x1(float m0, float m1, float m2, float m3) => (this.m0, this.m1, this.m2, this.m3) = (m0, m1, m2, m3); + public float this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, 3 => m3, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" )}; set { - switch( column ) { - case 0: - m0 = value; - break; - case 1: - m1 = value; - break; - case 2: - m2 = value; - break; - case 3: - m3 = value; - break; - default: throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ); + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; case 3: m3 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" ); } } } - public static bool operator ==( Matrix4x1 a, Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; public static bool operator !=( Matrix4x1 a, Matrix4x1 b ) => !( a == b ); public bool Equals( Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); public override bool Equals( object obj ) => obj is Matrix4x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); - } - -} \ No newline at end of file +} diff --git a/Numerics/Vector2Matrix3x1.cs b/Numerics/Vector2Matrix3x1.cs index 44a29cc..3c00dc8 100644 --- a/Numerics/Vector2Matrix3x1.cs +++ b/Numerics/Vector2Matrix3x1.cs @@ -1,54 +1,29 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using UnityEngine; - namespace Freya { - /// A 3x1 column matrix with Vector2 values [Serializable] public struct Vector2Matrix3x1 { - public Vector2 m0, m1, m2; - - public Vector2Matrix3x1( Vector2 m0, Vector2 m1, Vector2 m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); - - public Vector2Matrix3x1( Matrix3x1 x, Matrix3x1 y ) { - m0 = new Vector2( x.m0, y.m0 ); - m1 = new Vector2( x.m1, y.m1 ); - m2 = new Vector2( x.m2, y.m2 ); - } - - public Vector2 this[ int column ] { - get => - column switch { - 0 => m0, 1 => m1, 2 => m2, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ) - }; + public Vector2Matrix3x1(Vector2 m0, Vector2 m1, Vector2 m2) => (this.m0, this.m1, this.m2) = (m0, m1, m2); + public Vector2Matrix3x1(Matrix3x1 x, Matrix3x1 y) => (m0, m1, m2) = (new Vector2(x.m0, y.m0), new Vector2(x.m1, y.m1), new Vector2(x.m2, y.m2)); + public Vector2 this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 2, got: {row}" )}; set { - switch( column ) { - case 0: - m0 = value; - break; - case 1: - m1 = value; - break; - case 2: - m2 = value; - break; - default: throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ); + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 2, got: {row}" ); } } } - public Matrix3x1 X => new(m0.x, m1.x, m2.x); public Matrix3x1 Y => new(m0.y, m1.y, m2.y); - public static bool operator ==( Vector2Matrix3x1 a, Vector2Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; public static bool operator !=( Vector2Matrix3x1 a, Vector2Matrix3x1 b ) => !( a == b ); public bool Equals( Vector2Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); public override bool Equals( object obj ) => obj is Vector2Matrix3x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); - } - -} \ No newline at end of file +} diff --git a/Numerics/Vector2Matrix4x1.cs b/Numerics/Vector2Matrix4x1.cs index bc96a2c..7374a96 100644 --- a/Numerics/Vector2Matrix4x1.cs +++ b/Numerics/Vector2Matrix4x1.cs @@ -1,39 +1,29 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using UnityEngine; - namespace Freya { - /// A 4x1 column matrix with Vector2 values [Serializable] public struct Vector2Matrix4x1 { - public Vector2 m0, m1, m2, m3; - - public Vector2Matrix4x1( Vector2 m0, Vector2 m1, Vector2 m2, Vector2 m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); - - public Vector2Matrix4x1( Matrix4x1 x, Matrix4x1 y ) { - m0 = new Vector2( x.m0, y.m0 ); - m1 = new Vector2( x.m1, y.m1 ); - m2 = new Vector2( x.m2, y.m2 ); - m3 = new Vector2( x.m3, y.m3 ); + public Vector2Matrix4x1(Vector2 m0, Vector2 m1, Vector2 m2, Vector2 m3) => (this.m0, this.m1, this.m2, this.m3) = (m0, m1, m2, m3); + public Vector2Matrix4x1(Matrix4x1 x, Matrix4x1 y) => (m0, m1, m2, m3) = (new Vector2(x.m0, y.m0), new Vector2(x.m1, y.m1), new Vector2(x.m2, y.m2), new Vector2(x.m3, y.m3)); + public Vector2 this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, 3 => m3, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" )}; + set { + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; case 3: m3 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" ); + } + } } - - public Vector2 this[ int column ] => - column switch { - 0 => m0, 1 => m1, 2 => m2, 3 => m3, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) - }; - public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); - public static bool operator ==( Vector2Matrix4x1 a, Vector2Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; public static bool operator !=( Vector2Matrix4x1 a, Vector2Matrix4x1 b ) => !( a == b ); public bool Equals( Vector2Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); public override bool Equals( object obj ) => obj is Vector2Matrix4x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); - } - -} \ No newline at end of file +} diff --git a/Numerics/Vector3Matrix3x1.cs b/Numerics/Vector3Matrix3x1.cs index 66bee79..41f7b31 100644 --- a/Numerics/Vector3Matrix3x1.cs +++ b/Numerics/Vector3Matrix3x1.cs @@ -1,55 +1,30 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using UnityEngine; - namespace Freya { - /// A 3x1 column matrix with Vector3 values [Serializable] public struct Vector3Matrix3x1 { - public Vector3 m0, m1, m2; - - public Vector3Matrix3x1( Vector3 m0, Vector3 m1, Vector3 m2 ) => ( this.m0, this.m1, this.m2 ) = ( m0, m1, m2 ); - - public Vector3Matrix3x1( Matrix3x1 x, Matrix3x1 y, Matrix3x1 z ) { - m0 = new Vector3( x.m0, y.m0, z.m0 ); - m1 = new Vector3( x.m1, y.m1, z.m1 ); - m2 = new Vector3( x.m2, y.m2, z.m2 ); - } - - public Vector3 this[ int column ] { - get => - column switch { - 0 => m0, 1 => m1, 2 => m2, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ) - }; + public Vector3Matrix3x1(Vector3 m0, Vector3 m1, Vector3 m2) => (this.m0, this.m1, this.m2) = (m0, m1, m2); + public Vector3Matrix3x1(Matrix3x1 x, Matrix3x1 y, Matrix3x1 z) => (m0, m1, m2) = (new Vector3(x.m0, y.m0, z.m0), new Vector3(x.m1, y.m1, z.m1), new Vector3(x.m2, y.m2, z.m2)); + public Vector3 this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 2, got: {row}" )}; set { - switch( column ) { - case 0: - m0 = value; - break; - case 1: - m1 = value; - break; - case 2: - m2 = value; - break; - default: throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 2, got: {column}" ); + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 2, got: {row}" ); } } } - public Matrix3x1 X => new(m0.x, m1.x, m2.x); public Matrix3x1 Y => new(m0.y, m1.y, m2.y); public Matrix3x1 Z => new(m0.z, m1.z, m2.z); - public static bool operator ==( Vector3Matrix3x1 a, Vector3Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; public static bool operator !=( Vector3Matrix3x1 a, Vector3Matrix3x1 b ) => !( a == b ); public bool Equals( Vector3Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); public override bool Equals( object obj ) => obj is Vector3Matrix3x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); - } - -} \ No newline at end of file +} diff --git a/Numerics/Vector3Matrix4x1.cs b/Numerics/Vector3Matrix4x1.cs index 317d9ce..b33400d 100644 --- a/Numerics/Vector3Matrix4x1.cs +++ b/Numerics/Vector3Matrix4x1.cs @@ -1,40 +1,30 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs using System; using UnityEngine; - namespace Freya { - /// A 4x1 column matrix with Vector3 values [Serializable] public struct Vector3Matrix4x1 { - public Vector3 m0, m1, m2, m3; - - public Vector3Matrix4x1( Vector3 m0, Vector3 m1, Vector3 m2, Vector3 m3 ) => ( this.m0, this.m1, this.m2, this.m3 ) = ( m0, m1, m2, m3 ); - - public Vector3Matrix4x1( Matrix4x1 x, Matrix4x1 y, Matrix4x1 z ) { - m0 = new Vector3( x.m0, y.m0, z.m0 ); - m1 = new Vector3( x.m1, y.m1, z.m1 ); - m2 = new Vector3( x.m2, y.m2, z.m2 ); - m3 = new Vector3( x.m3, y.m3, z.m3 ); + public Vector3Matrix4x1(Vector3 m0, Vector3 m1, Vector3 m2, Vector3 m3) => (this.m0, this.m1, this.m2, this.m3) = (m0, m1, m2, m3); + public Vector3Matrix4x1(Matrix4x1 x, Matrix4x1 y, Matrix4x1 z) => (m0, m1, m2, m3) = (new Vector3(x.m0, y.m0, z.m0), new Vector3(x.m1, y.m1, z.m1), new Vector3(x.m2, y.m2, z.m2), new Vector3(x.m3, y.m3, z.m3)); + public Vector3 this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, 3 => m3, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" )}; + set { + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; case 3: m3 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" ); + } + } } - - public Vector3 this[ int column ] => - column switch { - 0 => m0, 1 => m1, 2 => m2, 3 => m3, - _ => throw new IndexOutOfRangeException( $"Matrix column index has to be from 0 to 3, got: {column}" ) - }; - public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); public Matrix4x1 Z => new(m0.z, m1.z, m2.z, m3.z); - public static bool operator ==( Vector3Matrix4x1 a, Vector3Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; public static bool operator !=( Vector3Matrix4x1 a, Vector3Matrix4x1 b ) => !( a == b ); public bool Equals( Vector3Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); public override bool Equals( object obj ) => obj is Vector3Matrix4x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); - } - -} \ No newline at end of file +} From 2a5399428878ee55d64c906ecfe88968709c3686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 13:49:02 +0200 Subject: [PATCH 093/301] FloatRange Contains(FloatRange) --- Numerics/FloatRange.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Numerics/FloatRange.cs b/Numerics/FloatRange.cs index 6e1b33c..4227fec 100644 --- a/Numerics/FloatRange.cs +++ b/Numerics/FloatRange.cs @@ -48,6 +48,10 @@ public readonly struct FloatRange { /// Returns whether or not this range contains the value v /// The value to see if it's inside public bool Contains( float v ) => v >= Min && v <= Max; + + /// Returns whether or not this range contains the range r + /// The range to see if it's inside + public bool Contains( FloatRange r ) => r.Min >= Min && r.Max <= Max; /// Remaps the input value from the input range to the output range /// The value to remap From bf054dd9b5c303308c568522d6650e63d903ae08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 13:49:32 +0200 Subject: [PATCH 094/301] Line2D.SignedDistance --- Geometric Shapes/Line2D.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Geometric Shapes/Line2D.cs b/Geometric Shapes/Line2D.cs index deca79d..4634352 100644 --- a/Geometric Shapes/Line2D.cs +++ b/Geometric Shapes/Line2D.cs @@ -27,6 +27,10 @@ namespace Freya { /// The direction of the line. It does not have to be normalized, but if it is, the t-value when sampling will correspond to distance along the ray public Line2D( Vector2 origin, Vector2 dir ) => ( this.origin, this.dir ) = ( origin, dir ); + /// The signed distance from this line to a point. Points to the left of this line are positive + /// The point to check the signed distance to + [MethodImpl( INLINE )] public float SignedDistance( Vector2 point ) => Determinant( dir.normalized, point - origin ); + #region Interface stuff for generic line tests [MethodImpl( INLINE )] bool ILinear2D.IsValidTValue( float t ) => true; // just always valid uwu From de0f5e4bd5d212e98208a85d96e25d6a03f15a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 16:34:56 +0200 Subject: [PATCH 095/301] refactored spline segment interfaces and some matrix multiplication stuff --- Codegen/Editor/MathfsCodegen.cs | 10 ++++-- Curves/IParamCurve.cs | 15 +++------ Extensions.cs | 15 +++++---- .../NUCatRomCubic2D.cs | 32 ++++++++++-------- .../NUCatRomCubic3D.cs | 32 ++++++++++-------- Splines/SplineUtils.cs | 33 +++++++------------ .../Uniform Spline Segments/BezierCubic1D.cs | 7 ++-- .../Uniform Spline Segments/BezierCubic2D.cs | 7 ++-- .../Uniform Spline Segments/BezierCubic3D.cs | 7 ++-- .../Uniform Spline Segments/BezierQuad1D.cs | 7 ++-- .../Uniform Spline Segments/BezierQuad2D.cs | 30 ++++++++++++++--- .../Uniform Spline Segments/BezierQuad3D.cs | 7 ++-- .../Uniform Spline Segments/CatRomCubic1D.cs | 7 ++-- .../Uniform Spline Segments/CatRomCubic2D.cs | 7 ++-- .../Uniform Spline Segments/CatRomCubic3D.cs | 7 ++-- .../Uniform Spline Segments/HermiteCubic1D.cs | 7 ++-- .../Uniform Spline Segments/HermiteCubic2D.cs | 7 ++-- .../Uniform Spline Segments/HermiteCubic3D.cs | 7 ++-- Splines/Uniform Spline Segments/UBSCubic1D.cs | 7 ++-- Splines/Uniform Spline Segments/UBSCubic2D.cs | 7 ++-- Splines/Uniform Spline Segments/UBSCubic3D.cs | 7 ++-- 21 files changed, 164 insertions(+), 101 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 1dda5a7..e43fefa 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -160,7 +160,7 @@ public static void Regenerate() { GenerateType( typeBspline, dim ); GenerateType( typeCatRom, dim ); GenerateMatrix( 3, dim ); - GenerateMatrix( 4, dim ); + GenerateMatrix( 4, dim ); } } @@ -277,7 +277,7 @@ static void GenerateType( SplineType type, int dim ) { // type definition code.Summary( $"An optimized uniform {dim}D {degFullLower} {type.prettyNameLower} segment, with {ptCount} control points" ); - using( code.BracketScope( $"[Serializable] public struct {structName} : IParamCubicSplineSegment{dim}D" ) ) { // intentionally always Cubic right now + using( code.BracketScope( $"[Serializable] public struct {structName} : IParamSplineSegment<{polynomType},{pointMatrixType}>" ) ) { // intentionally always Cubic right now code.LineBreak(); code.Append( "const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining;" ); code.LineBreak(); @@ -306,7 +306,11 @@ static void GenerateType( SplineType type, int dim ) { // control point properties using( code.ScopeRegion( "Control Points" ) ) { code.Append( $"[SerializeField] {pointMatrixType} pointMatrix;" ); - code.Append( $"public {pointMatrixType} PointMatrix => pointMatrix;" ); + using( code.BracketScope( $"public {pointMatrixType} PointMatrix" ) ) { + code.Append( "get => pointMatrix;" ); + code.Append( "set => _ = ( pointMatrix = value, validCoefficients = false );" ); + } + code.LineBreak(); for( int i = 0; i < ptCount; i++ ) { code.Summary( pointDescs[i] ); diff --git a/Curves/IParamCurve.cs b/Curves/IParamCurve.cs index 68aa63e..f54cd6c 100644 --- a/Curves/IParamCurve.cs +++ b/Curves/IParamCurve.cs @@ -6,19 +6,12 @@ namespace Freya { - public interface IParamCubicSplineSegment1D { + public interface IParamSplineSegment { /// The curve generated by the control points - Polynomial Curve { get; } - } - - public interface IParamCubicSplineSegment2D { - /// - Polynomial2D Curve { get; } - } + P Curve { get; } - public interface IParamCubicSplineSegment3D { - /// - Polynomial3D Curve { get; } + /// The point matrix of this spline segment + M PointMatrix { get; set; } } /// An interface representing a parametric curve diff --git a/Extensions.cs b/Extensions.cs index aebeae2..f9be435 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -235,14 +235,17 @@ public static string ToValueTableString( this string[,] m ) { #region Matrix extensions - public static Vector4 MultiplyColumnVector( this Matrix4x4 m, Vector4 v ) => - new Vector4( - Vector4.Dot( m.GetRow( 0 ), v ), - Vector4.Dot( m.GetRow( 1 ), v ), - Vector4.Dot( m.GetRow( 2 ), v ), - Vector4.Dot( m.GetRow( 3 ), v ) + public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => + new Matrix4x1( + m.m00 * v.m0 + m.m01 * v.m1 + m.m02 * v.m2 + m.m03 * v.m3, + m.m10 * v.m0 + m.m11 * v.m1 + m.m12 * v.m2 + m.m13 * v.m3, + m.m20 * v.m0 + m.m21 * v.m1 + m.m22 * v.m2 + m.m23 * v.m3, + m.m30 * v.m0 + m.m31 * v.m1 + m.m32 * v.m2 + m.m33 * v.m3 ); + public static Vector2Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Vector2Matrix4x1 v ) => new(m.MultiplyColumnVector( v.X ), m.MultiplyColumnVector( v.Y )); + public static Vector3Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Vector3Matrix4x1 v ) => new(m.MultiplyColumnVector( v.X ), m.MultiplyColumnVector( v.Y ), m.MultiplyColumnVector( v.Z )); + #endregion #region Extension method counterparts of the static Mathfs functions - lots of boilerplate in here diff --git a/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs b/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs index a35ed70..f32d532 100644 --- a/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs +++ b/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs @@ -7,7 +7,7 @@ namespace Freya { /// A non-uniform cubic catmull-rom 2D curve - [Serializable] public struct NUCatRomCubic2D : IParamCubicSplineSegment2D { + [Serializable] public struct NUCatRomCubic2D : IParamSplineSegment { public enum KnotCalcMode { Manual, @@ -29,7 +29,7 @@ public enum KnotCalcMode { /// The third knot value /// The fourth knot value public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); ( this.k0, this.k1, this.k2, this.k3 ) = ( k0, k1, k2, k3 ); validCoefficients = false; curve = default; @@ -69,7 +69,7 @@ public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, CatRomTy /// If true, the knot generation will ensure k1 = 0 and k2 = 1, /// making it span the unit interval of 0 to 1 instead of using the raw knot values generated by the alpha parameterization public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float alpha, bool parameterizeToUnitInterval = true ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; k0 = k1 = k2 = k3 = default; @@ -80,7 +80,11 @@ public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float al #endregion // serialized data - [SerializeField] Vector2 p0, p1, p2, p3; + [SerializeField] Vector2Matrix4x1 pointMatrix; + public Vector2Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } [SerializeField] float k0, k1, k2, k3; // knot vector // knot auto-calculation fields @@ -99,23 +103,23 @@ public Polynomial2D Curve { /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P0 { - [MethodImpl( INLINE )] get => p0; - set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point, and the start of the catrom curve public Vector2 P1 { - [MethodImpl( INLINE )] get => p1; - set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point, and the end of the catrom curve public Vector2 P2 { - [MethodImpl( INLINE )] get => p2; - set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P3 { - [MethodImpl( INLINE )] get => p3; - set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// The knot value of the first control point of the catrom curve @@ -174,8 +178,8 @@ public float Alpha { return; // no need to update validCoefficients = true; if( knotCalcMode != KnotCalcMode.Manual ) - ( k0, k1, k2, k3 ) = SplineUtils.CalcCatRomKnots( p0, p1, p2, p3, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); - curve = SplineUtils.CalculateCatRomCurve( p0, p1, p2, p3, k0, k1, k2, k3 ); + ( k0, k1, k2, k3 ) = SplineUtils.CalcCatRomKnots( pointMatrix, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); + curve = SplineUtils.CalculateCatRomCurve( pointMatrix, k0, k1, k2, k3 ); } /// Returns the weight of the given control point at the given parameter value diff --git a/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs b/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs index 4de8cf9..634bc45 100644 --- a/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs +++ b/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs @@ -7,7 +7,7 @@ namespace Freya { /// A non-uniform cubic catmull-rom 3D curve - [Serializable] public struct NUCatRomCubic3D : IParamCubicSplineSegment3D { + [Serializable] public struct NUCatRomCubic3D : IParamSplineSegment { public enum KnotCalcMode { Manual, @@ -21,7 +21,7 @@ public enum KnotCalcMode { /// public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float k0, float k1, float k2, float k3 ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); ( this.k0, this.k1, this.k2, this.k3 ) = ( k0, k1, k2, k3 ); validCoefficients = false; curve = default; @@ -40,7 +40,7 @@ public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, CatRomTy /// public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float alpha, bool parameterizeToUnitInterval = true ) { - ( this.p0, this.p1, this.p2, this.p3 ) = ( p0, p1, p2, p3 ); + pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; k0 = k1 = k2 = k3 = default; @@ -51,7 +51,11 @@ public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float al #endregion // serialized data - [SerializeField] Vector3 p0, p1, p2, p3; + [SerializeField] Vector3Matrix4x1 pointMatrix; + public Vector3Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } [SerializeField] float k0, k1, k2, k3; // knot vector // knot auto-calculation fields @@ -70,23 +74,23 @@ public Polynomial3D Curve { /// public Vector3 P0 { - [MethodImpl( INLINE )] get => p0; - set => _ = ( p0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m0; + set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// public Vector3 P1 { - [MethodImpl( INLINE )] get => p1; - set => _ = ( p1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m1; + set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// public Vector3 P2 { - [MethodImpl( INLINE )] get => p2; - set => _ = ( p2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m2; + set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// public Vector3 P3 { - [MethodImpl( INLINE )] get => p3; - set => _ = ( p3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => pointMatrix.m3; + set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// @@ -142,8 +146,8 @@ public float Alpha { return; // no need to update validCoefficients = true; if( knotCalcMode != KnotCalcMode.Manual ) - ( k0, k1, k2, k3 ) = SplineUtils.CalcCatRomKnots( p0, p1, p2, p3, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); - curve = SplineUtils.CalculateCatRomCurve( p0, p1, p2, p3, k0, k1, k2, k3 ); + ( k0, k1, k2, k3 ) = SplineUtils.CalcCatRomKnots( pointMatrix, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); + curve = SplineUtils.CalculateCatRomCurve( pointMatrix, k0, k1, k2, k3 ); } /// diff --git a/Splines/SplineUtils.cs b/Splines/SplineUtils.cs index 83fbbc6..33f4f6d 100644 --- a/Splines/SplineUtils.cs +++ b/Splines/SplineUtils.cs @@ -36,21 +36,21 @@ public static float CalcCatRomKnot( float kPrev, float alpha, float sqDist ) { static (float, float, float, float) GetUniformKnots( bool unitInterval ) => unitInterval ? ( -1, 0, 1, 2 ) : ( 0, 1, 2, 3 ); - public static (float, float, float, float) CalcCatRomKnots( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float alpha, bool unitInterval ) { + public static (float, float, float, float) CalcCatRomKnots( Vector2Matrix4x1 m, float alpha, bool unitInterval ) { if( alpha == 0 ) // uniform catrom return GetUniformKnots( unitInterval ); - float sqMag01 = Vector2.SqrMagnitude( p0 - p1 ); - float sqMag12 = Vector2.SqrMagnitude( p1 - p2 ); - float sqMag23 = Vector2.SqrMagnitude( p2 - p3 ); + float sqMag01 = Vector2.SqrMagnitude( m.m0 - m.m1 ); + float sqMag12 = Vector2.SqrMagnitude( m.m1 - m.m2 ); + float sqMag23 = Vector2.SqrMagnitude( m.m2 - m.m3 ); return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval ); } - public static (float, float, float, float) CalcCatRomKnots( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float alpha, bool unitInterval ) { + public static (float, float, float, float) CalcCatRomKnots( Vector3Matrix4x1 m, float alpha, bool unitInterval ) { if( alpha == 0 ) // uniform catrom return GetUniformKnots( unitInterval ); - float sqMag01 = Vector3.SqrMagnitude( p0 - p1 ); - float sqMag12 = Vector3.SqrMagnitude( p1 - p2 ); - float sqMag23 = Vector3.SqrMagnitude( p2 - p3 ); + float sqMag01 = Vector3.SqrMagnitude( m.m0 - m.m1 ); + float sqMag12 = Vector3.SqrMagnitude( m.m1 - m.m2 ); + float sqMag23 = Vector3.SqrMagnitude( m.m2 - m.m3 ); return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval ); } @@ -170,21 +170,12 @@ static Matrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { ); } - internal static Polynomial2D CalculateCatRomCurve( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { - Matrix4x4 m = GetNUCatRomCharMatrix( k0, k1, k2, k3 ); - return new Polynomial2D( - new Polynomial( m.MultiplyColumnVector( new Vector4( p0.x, p1.x, p2.x, p3.x ) ) ), - new Polynomial( m.MultiplyColumnVector( new Vector4( p0.y, p1.y, p2.y, p3.y ) ) ) - ); + internal static Polynomial2D CalculateCatRomCurve( Vector2Matrix4x1 m, float k0, float k1, float k2, float k3 ) { + return new Polynomial2D( GetNUCatRomCharMatrix( k0, k1, k2, k3 ).MultiplyColumnVector( m ) ); } - internal static Polynomial3D CalculateCatRomCurve( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float k0, float k1, float k2, float k3 ) { - Matrix4x4 m = GetNUCatRomCharMatrix( k0, k1, k2, k3 ); - return new Polynomial3D( - new Polynomial( m.MultiplyColumnVector( new Vector4( p0.x, p1.x, p2.x, p3.x ) ) ), - new Polynomial( m.MultiplyColumnVector( new Vector4( p0.y, p1.y, p2.y, p3.y ) ) ), - new Polynomial( m.MultiplyColumnVector( new Vector4( p0.z, p1.z, p2.z, p3.z ) ) ) - ); + internal static Polynomial3D CalculateCatRomCurve( Vector3Matrix4x1 m, float k0, float k1, float k2, float k3 ) { + return new Polynomial3D( GetNUCatRomCharMatrix( k0, k1, k2, k3 ).MultiplyColumnVector( m ) ); } } diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index 2090511..96f19d3 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 1D Cubic bézier segment, with 4 control points - [Serializable] public struct BezierCubic1D : IParamCubicSplineSegment1D { + [Serializable] public struct BezierCubic1D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial Curve { #region Control Points [SerializeField] Matrix4x1 pointMatrix; - public Matrix4x1 PointMatrix => pointMatrix; + public Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public float P0 { diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index dd7c45e..44637f1 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 2D Cubic bézier segment, with 4 control points - [Serializable] public struct BezierCubic2D : IParamCubicSplineSegment2D { + [Serializable] public struct BezierCubic2D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2Matrix4x1 pointMatrix; - public Vector2Matrix4x1 PointMatrix => pointMatrix; + public Vector2Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public Vector2 P0 { diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index de2b1ea..fb97429 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 3D Cubic bézier segment, with 4 control points - [Serializable] public struct BezierCubic3D : IParamCubicSplineSegment3D { + [Serializable] public struct BezierCubic3D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3Matrix4x1 pointMatrix; - public Vector3Matrix4x1 PointMatrix => pointMatrix; + public Vector3Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public Vector3 P0 { diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Splines/Uniform Spline Segments/BezierQuad1D.cs index a512686..c3797fd 100644 --- a/Splines/Uniform Spline Segments/BezierQuad1D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad1D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 1D Quadratic bézier segment, with 3 control points - [Serializable] public struct BezierQuad1D : IParamCubicSplineSegment1D { + [Serializable] public struct BezierQuad1D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -32,7 +32,10 @@ public Polynomial Curve { #region Control Points [SerializeField] Matrix3x1 pointMatrix; - public Matrix3x1 PointMatrix => pointMatrix; + public Matrix3x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public float P0 { diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index 8ce84ef..b1e0213 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 2D Quadratic bézier segment, with 3 control points - [Serializable] public struct BezierQuad2D : IParamCubicSplineSegment2D { + [Serializable] public struct BezierQuad2D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -32,7 +32,10 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2Matrix3x1 pointMatrix; - public Vector2Matrix3x1 PointMatrix => pointMatrix; + public Vector2Matrix3x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public Vector2 P0 { @@ -54,8 +57,27 @@ public Vector2 P2 { /// Get or set a control point position by index. Valid indices from 0 to 2 public Vector2 this[ int i ] { - [MethodImpl( INLINE )] get => pointMatrix[i]; - [MethodImpl( INLINE )] set => _ = ( pointMatrix[i] = value, validCoefficients = false ); + get => + i switch { + 0 => P0, + 1 => P1, + 2 => P2, + _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) + }; + set { + switch( i ) { + case 0: + P0 = value; + break; + case 1: + P1 = value; + break; + case 2: + P2 = value; + break; + default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); + } + } } #endregion diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index e9087f7..46a33db 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 3D Quadratic bézier segment, with 3 control points - [Serializable] public struct BezierQuad3D : IParamCubicSplineSegment3D { + [Serializable] public struct BezierQuad3D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -32,7 +32,10 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3Matrix3x1 pointMatrix; - public Vector3Matrix3x1 PointMatrix => pointMatrix; + public Vector3Matrix3x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public Vector3 P0 { diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index 5c038c8..f9d989c 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 1D Cubic catmull-rom segment, with 4 control points - [Serializable] public struct CatRomCubic1D : IParamCubicSplineSegment1D { + [Serializable] public struct CatRomCubic1D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial Curve { #region Control Points [SerializeField] Matrix4x1 pointMatrix; - public Matrix4x1 PointMatrix => pointMatrix; + public Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public float P0 { diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 65536a5..ce4d27f 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 2D Cubic catmull-rom segment, with 4 control points - [Serializable] public struct CatRomCubic2D : IParamCubicSplineSegment2D { + [Serializable] public struct CatRomCubic2D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2Matrix4x1 pointMatrix; - public Vector2Matrix4x1 PointMatrix => pointMatrix; + public Vector2Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P0 { diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 2e4d1e4..18a5cea 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 3D Cubic catmull-rom segment, with 4 control points - [Serializable] public struct CatRomCubic3D : IParamCubicSplineSegment3D { + [Serializable] public struct CatRomCubic3D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3Matrix4x1 pointMatrix; - public Vector3Matrix4x1 PointMatrix => pointMatrix; + public Vector3Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector3 P0 { diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index b686d4a..87a7f87 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 1D Cubic hermite segment, with 4 control points - [Serializable] public struct HermiteCubic1D : IParamCubicSplineSegment1D { + [Serializable] public struct HermiteCubic1D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial Curve { #region Control Points [SerializeField] Matrix4x1 pointMatrix; - public Matrix4x1 PointMatrix => pointMatrix; + public Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public float P0 { diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index 365632b..ad098a3 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 2D Cubic hermite segment, with 4 control points - [Serializable] public struct HermiteCubic2D : IParamCubicSplineSegment2D { + [Serializable] public struct HermiteCubic2D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2Matrix4x1 pointMatrix; - public Vector2Matrix4x1 PointMatrix => pointMatrix; + public Vector2Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public Vector2 P0 { diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index 7e09e56..a98c4bc 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 3D Cubic hermite segment, with 4 control points - [Serializable] public struct HermiteCubic3D : IParamCubicSplineSegment3D { + [Serializable] public struct HermiteCubic3D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3Matrix4x1 pointMatrix; - public Vector3Matrix4x1 PointMatrix => pointMatrix; + public Vector3Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The starting point of the curve public Vector3 P0 { diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index aadfd27..7c23c9e 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 1D Cubic b-spline segment, with 4 control points - [Serializable] public struct UBSCubic1D : IParamCubicSplineSegment1D { + [Serializable] public struct UBSCubic1D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial Curve { #region Control Points [SerializeField] Matrix4x1 pointMatrix; - public Matrix4x1 PointMatrix => pointMatrix; + public Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The first point of the B-spline hull public float P0 { diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index f1ba438..eb0404f 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 2D Cubic b-spline segment, with 4 control points - [Serializable] public struct UBSCubic2D : IParamCubicSplineSegment2D { + [Serializable] public struct UBSCubic2D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial2D Curve { #region Control Points [SerializeField] Vector2Matrix4x1 pointMatrix; - public Vector2Matrix4x1 PointMatrix => pointMatrix; + public Vector2Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The first point of the B-spline hull public Vector2 P0 { diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index 80cf8cd..bcb3c81 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -8,7 +8,7 @@ namespace Freya { /// An optimized uniform 3D Cubic b-spline segment, with 4 control points - [Serializable] public struct UBSCubic3D : IParamCubicSplineSegment3D { + [Serializable] public struct UBSCubic3D : IParamSplineSegment { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; @@ -33,7 +33,10 @@ public Polynomial3D Curve { #region Control Points [SerializeField] Vector3Matrix4x1 pointMatrix; - public Vector3Matrix4x1 PointMatrix => pointMatrix; + public Vector3Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } /// The first point of the B-spline hull public Vector3 P0 { From 5af6c5c42736964c52bb2c457be4f5df6c3d8e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 18:01:35 +0200 Subject: [PATCH 096/301] cleaned up codegen a little --- Codegen/Editor/MathfsCodegen.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index e43fefa..65ca33c 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -260,11 +260,16 @@ static void GenerateType( SplineType type, int dim ) { string degShortCapital = GetDegreeName( degree, true ); string structName = $"{type.className}{degShortCapital}{dim}D"; string[] points = type.paramNames; - int[] ptRange = ptCount == 3 ? new[] { 0, 1, 2 } : new[] { 0, 1, 2, 3 }; + int[] ptRange = Enumerable.Range( 0, ptCount ).ToArray(); string[] pointDescs = type.paramDescs; string lerpName = GetLerpName( dim ); string pointMatrixType = $"{( dim == 1 ? "" : dataType )}Matrix{ptCount}x1"; + string JoinRange( string separator, Func elem ) => string.Join( separator, ptRange.Select( elem ) ); + string JoinRangeStr( string separator, Func elem ) => string.Join( separator, points.Select( elem ) ); + + string ctorParams = JoinRange( ", ", i => $"{dataType} {type.paramNames[i]}" ); + string csPoints = JoinRange( ", ", i => $"{type.paramNames[i]}" ); CodeGenerator code = new CodeGenerator(); code.AppendHeader(); code.Using( "System" ); @@ -286,8 +291,8 @@ static void GenerateType( SplineType type, int dim ) { code.Summary( $"Creates a uniform {dim}D {degFullLower} {type.prettyNameLower} segment, from {ptCount} control points" ); for( int i = 0; i < ptCount; i++ ) type.AppendParamStrings( code, degree, i ); - using( code.BracketScope( $"public {structName}( {string.Join( ", ", points.Select( p => $"{dataType} {p}" ) )} )" ) ) { - code.Append( $"pointMatrix = new {pointMatrixType}( {string.Join( ", ", points )} );" ); + using( code.BracketScope( $"public {structName}( {ctorParams} )" ) ) { + code.Append( $"pointMatrix = new {pointMatrixType}( {csPoints} );" ); code.Append( "validCoefficients = false;" ); code.Append( "curve = default;" ); } @@ -370,12 +375,14 @@ static void GenerateType( SplineType type, int dim ) { } // equality checks + string compEquals = JoinRangeStr( " && ", p => $"{p.ToUpperInvariant()}.Equals( other.{p.ToUpperInvariant()} )" ); + string toStringParams = JoinRange( ", ", i => $"{{pointMatrix.m{i}}}" ); code.Append( $"public static bool operator ==( {structName} a, {structName} b ) => a.pointMatrix == b.pointMatrix;" ); code.Append( $"public static bool operator !=( {structName} a, {structName} b ) => !( a == b );" ); - code.Append( $"public bool Equals( {structName} other ) => {string.Join( " && ", points.Select( p => $"{p.ToUpperInvariant()}.Equals( other.{p.ToUpperInvariant()} )" ) )};" ); + code.Append( $"public bool Equals( {structName} other ) => {compEquals};" ); code.Append( $"public override bool Equals( object obj ) => obj is {structName} other && pointMatrix.Equals( other.pointMatrix );" ); code.Append( $"public override int GetHashCode() => pointMatrix.GetHashCode();" ); - code.Append( $"public override string ToString() => $\"({string.Join( ", ", ptRange.Select( i => $"{{pointMatrix.m{i}}}" ) )})\";" ); + code.Append( $"public override string ToString() => $\"({toStringParams})\";" ); code.LineBreak(); // typecasting @@ -385,7 +392,8 @@ static void GenerateType( SplineType type, int dim ) { string structName3D = $"{type.className}{degShortCapital}3D"; code.Summary( "Returns this spline segment in 3D, where z = 0" ); code.Param( "curve2D", "The 2D curve to cast to 3D" ); - code.Append( $"public static explicit operator {structName3D}( {structName} curve2D ) => new {structName3D}( {string.Join( ", ", points.Select( p => $"curve2D.{p.ToUpperInvariant()}" ) )} );" ); + string inParams = JoinRangeStr( ", ", p => $"curve2D.{p.ToUpperInvariant()}" ); + code.Append( $"public static explicit operator {structName3D}( {structName} curve2D ) => new {structName3D}( {inParams} );" ); } if( dim == 3 ) { @@ -393,7 +401,8 @@ static void GenerateType( SplineType type, int dim ) { string structName2D = $"{type.className}{degShortCapital}2D"; code.Summary( "Returns this curve flattened to 2D. Effectively setting z = 0" ); code.Param( "curve3D", "The 3D curve to flatten to the Z plane" ); - code.Append( $"public static explicit operator {structName2D}( {structName} curve3D ) => new {structName2D}( {string.Join( ", ", points.Select( p => $"curve3D.{p.ToUpperInvariant()}" ) )} );" ); + string inParams = JoinRangeStr( ", ", p => $"curve3D.{p.ToUpperInvariant()}" ); + code.Append( $"public static explicit operator {structName2D}( {structName} curve3D ) => new {structName2D}( {inParams} );" ); } } From a12a568511166e0b46def16acdaf8ab662507046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Jun 2022 18:55:46 +0200 Subject: [PATCH 097/301] reorganized/compressed spline segment codegen --- Codegen/Editor/MathfsCodegen.cs | 99 ++++++------------- Curves/IParamCurve.cs | 3 +- .../Uniform Spline Segments/BezierCubic1D.cs | 97 ++++-------------- .../Uniform Spline Segments/BezierCubic2D.cs | 97 ++++-------------- .../Uniform Spline Segments/BezierCubic3D.cs | 97 ++++-------------- .../Uniform Spline Segments/BezierQuad1D.cs | 85 ++++------------ .../Uniform Spline Segments/BezierQuad2D.cs | 85 ++++------------ .../Uniform Spline Segments/BezierQuad3D.cs | 85 ++++------------ .../Uniform Spline Segments/CatRomCubic1D.cs | 97 ++++-------------- .../Uniform Spline Segments/CatRomCubic2D.cs | 97 ++++-------------- .../Uniform Spline Segments/CatRomCubic3D.cs | 97 ++++-------------- .../Uniform Spline Segments/HermiteCubic1D.cs | 97 ++++-------------- .../Uniform Spline Segments/HermiteCubic2D.cs | 97 ++++-------------- .../Uniform Spline Segments/HermiteCubic3D.cs | 97 ++++-------------- Splines/Uniform Spline Segments/UBSCubic1D.cs | 97 ++++-------------- Splines/Uniform Spline Segments/UBSCubic2D.cs | 97 ++++-------------- Splines/Uniform Spline Segments/UBSCubic3D.cs | 97 ++++-------------- 17 files changed, 341 insertions(+), 1180 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 65ca33c..fe5d777 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -270,6 +270,7 @@ static void GenerateType( SplineType type, int dim ) { string ctorParams = JoinRange( ", ", i => $"{dataType} {type.paramNames[i]}" ); string csPoints = JoinRange( ", ", i => $"{type.paramNames[i]}" ); + CodeGenerator code = new CodeGenerator(); code.AppendHeader(); code.Using( "System" ); @@ -287,91 +288,51 @@ static void GenerateType( SplineType type, int dim ) { code.Append( "const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining;" ); code.LineBreak(); + // fields + code.Append( $"[SerializeField] {pointMatrixType} pointMatrix;" ); + code.Append( $"[NonSerialized] {polynomType} curve;" ); + code.Append( "[NonSerialized] bool validCoefficients;" ); + code.LineBreak(); + // constructor code.Summary( $"Creates a uniform {dim}D {degFullLower} {type.prettyNameLower} segment, from {ptCount} control points" ); for( int i = 0; i < ptCount; i++ ) type.AppendParamStrings( code, degree, i ); - using( code.BracketScope( $"public {structName}( {ctorParams} )" ) ) { - code.Append( $"pointMatrix = new {pointMatrixType}( {csPoints} );" ); - code.Append( "validCoefficients = false;" ); - code.Append( "curve = default;" ); - } + code.Append( $"public {structName}( {ctorParams} ) => (pointMatrix,curve,validCoefficients) = (new {pointMatrixType}({csPoints}),default,false);" ); code.LineBreak(); - // Curve - code.Append( $"{polynomType} curve;" ); + // properties using( code.BracketScope( $"public {polynomType} Curve" ) ) { using( code.BracketScope( $"get" ) ) { - code.Append( "ReadyCoefficients();" ); - code.Append( "return curve;" ); - } - } - - // control point properties - using( code.ScopeRegion( "Control Points" ) ) { - code.Append( $"[SerializeField] {pointMatrixType} pointMatrix;" ); - using( code.BracketScope( $"public {pointMatrixType} PointMatrix" ) ) { - code.Append( "get => pointMatrix;" ); - code.Append( "set => _ = ( pointMatrix = value, validCoefficients = false );" ); - } - - code.LineBreak(); - for( int i = 0; i < ptCount; i++ ) { - code.Summary( pointDescs[i] ); - using( code.BracketScope( $"public {dataType} {points[i].ToUpperInvariant()}" ) ) { - code.Append( $"[MethodImpl( INLINE )] get => pointMatrix.m{i};" ); - code.Append( $"[MethodImpl( INLINE )] set => _ = ( pointMatrix.m{i} = value, validCoefficients = false );" ); - } - - code.LineBreak(); - } - - code.Summary( $"Get or set a control point position by index. Valid indices from 0 to {degree}" ); - using( code.BracketScope( $"public {dataType} this[ int i ]" ) ) { - using( code.Scope( "get =>" ) ) { - using( code.Scope( "i switch {" ) ) { - for( int i = 0; i < ptCount; i++ ) - code.Append( $"{i} => {points[i].ToUpperInvariant()}," ); - code.Append( $"_ => throw new ArgumentOutOfRangeException( nameof(i), $\"Index has to be in the 0 to {degree} range, and I think {{i}} is outside that range you know\" )" ); + using( code.Scope( "if( validCoefficients )" ) ) + code.Append( "return curve; // no need to update" ); + code.Append( "validCoefficients = true;" ); + using( code.Scope( $"return curve = new {polynomType}(" ) ) { + for( int icRow = 0; icRow < ptCount; icRow++ ) { + MathSum sum = new MathSum(); + for( int ip = 0; ip < ptCount; ip++ ) + sum.AddTerm( type.charMatrix[icRow, ip], $"{type.paramNames[ip].ToUpperInvariant()}" ); + code.Append( $"{sum}{( icRow < ptCount - 1 ? "," : "" )}" ); } - - code.Append( "};" ); } - using( code.BracketScope( "set" ) ) { - using( code.BracketScope( "switch( i )" ) ) { - for( int i = 0; i < ptCount; i++ ) { - using( code.Scope( $"case {i}:" ) ) { - code.Append( $"{points[i].ToUpperInvariant()} = value;" ); - code.Append( "break;" ); - } - } - - code.Append( $"default: throw new ArgumentOutOfRangeException( nameof(i), $\"Index has to be in the 0 to {degree} range, and I think {{i}} is outside that range you know\" );" ); - } - } + code.Append( ");" ); } + // todo: set would be possible! setting the points based on a curve } - // Coefficients - code.Append( "[NonSerialized] bool validCoefficients;" ); - code.LineBreak(); - using( code.BracketScope( "[MethodImpl( INLINE )] void ReadyCoefficients()" ) ) { - using( code.Scope( "if( validCoefficients )" ) ) - code.Append( "return; // no need to update" ); - code.Append( "validCoefficients = true;" ); - - using( code.Scope( $"curve = new {polynomType}(" ) ) { - for( int icRow = 0; icRow < ptCount; icRow++ ) { - MathSum sum = new MathSum(); - for( int ip = 0; ip < ptCount; ip++ ) - sum.AddTerm( type.charMatrix[icRow, ip], $"{type.paramNames[ip].ToUpperInvariant()}" ); - code.Append( $"{sum}{( icRow < ptCount - 1 ? "," : "" )}" ); - } - } + code.Append( $"public {pointMatrixType} PointMatrix {{[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); }}" ); + for( int i = 0; i < ptCount; i++ ) { + code.Summary( pointDescs[i] ); + code.Append( $"public {dataType} {points[i].ToUpperInvariant()}{{ [MethodImpl( INLINE )] get => pointMatrix.m{i}; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m{i} = value, validCoefficients = false ); }}" ); + } - code.Append( ");" ); + code.Summary( $"Get or set a control point position by index. Valid indices from 0 to {degree}" ); + using( code.BracketScope( $"public {dataType} this[ int i ]" ) ) { + string indexException = $"throw new ArgumentOutOfRangeException( nameof(i), $\"Index has to be in the 0 to {degree} range, and I think {{i}} is outside that range you know\" )"; + code.Append( $"get => i switch {{ {JoinRange( ", ", i => $"{i} => {points[i].ToUpperInvariant()}" )}, _ => {indexException} }};" ); + code.Append( $"set {{ switch( i ){{ {JoinRange( " ", i => $"case {i}: {points[i].ToUpperInvariant()} = value; break;" )} default: {indexException}; }}}}" ); } // equality checks diff --git a/Curves/IParamCurve.cs b/Curves/IParamCurve.cs index f54cd6c..4e1691e 100644 --- a/Curves/IParamCurve.cs +++ b/Curves/IParamCurve.cs @@ -10,8 +10,9 @@ public interface IParamSplineSegment { /// The curve generated by the control points P Curve { get; } - /// The point matrix of this spline segment + /// The matrix containing the control points of this spline segment M PointMatrix { get; set; } + } /// An interface representing a parametric curve diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index 96f19d3..c84671d 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Matrix4x1 pointMatrix; + [NonSerialized] Polynomial curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 1D Cubic bézier segment, from 4 control points /// The starting point of the curve /// The second control point of the curve, sometimes called the start tangent point /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve - public BezierCubic1D( float p0, float p1, float p2, float p3 ) { - pointMatrix = new Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public BezierCubic1D( float p0, float p1, float p2, float p3 ) => (pointMatrix,curve,validCoefficients) = (new Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial curve; public Polynomial Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial( + P0, + 3*(-P0+P1), + 3*P0-6*P1+3*P2, + -P0+3*P1-3*P2+P3 + ); } } - #region Control Points - - [SerializeField] Matrix4x1 pointMatrix; - public Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public float P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public float P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point of the curve, sometimes called the start tangent point - public float P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public float P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point of the curve, sometimes called the end tangent point - public float P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public float P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The end point of the curve - public float P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public float P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public float this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial( - P0, - 3*(-P0+P1), - 3*P0-6*P1+3*P2, - -P0+3*P1-3*P2+P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( BezierCubic1D a, BezierCubic1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierCubic1D a, BezierCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index 44637f1..5ee2143 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector2Matrix4x1 pointMatrix; + [NonSerialized] Polynomial2D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 2D Cubic bézier segment, from 4 control points /// The starting point of the curve /// The second control point of the curve, sometimes called the start tangent point /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve - public BezierCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { - pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public BezierCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial2D curve; public Polynomial2D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial2D( + P0, + 3*(-P0+P1), + 3*P0-6*P1+3*P2, + -P0+3*P1-3*P2+P3 + ); } } - #region Control Points - - [SerializeField] Vector2Matrix4x1 pointMatrix; - public Vector2Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector2Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public Vector2 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector2 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point of the curve, sometimes called the start tangent point - public Vector2 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector2 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point of the curve, sometimes called the end tangent point - public Vector2 P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector2 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The end point of the curve - public Vector2 P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public Vector2 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector2 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial2D( - P0, - 3*(-P0+P1), - 3*P0-6*P1+3*P2, - -P0+3*P1-3*P2+P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( BezierCubic2D a, BezierCubic2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierCubic2D a, BezierCubic2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index fb97429..ae24f51 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector3Matrix4x1 pointMatrix; + [NonSerialized] Polynomial3D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 3D Cubic bézier segment, from 4 control points /// The starting point of the curve /// The second control point of the curve, sometimes called the start tangent point /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve - public BezierCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { - pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public BezierCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial3D curve; public Polynomial3D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial3D( + P0, + 3*(-P0+P1), + 3*P0-6*P1+3*P2, + -P0+3*P1-3*P2+P3 + ); } } - #region Control Points - - [SerializeField] Vector3Matrix4x1 pointMatrix; - public Vector3Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector3Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public Vector3 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector3 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point of the curve, sometimes called the start tangent point - public Vector3 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector3 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point of the curve, sometimes called the end tangent point - public Vector3 P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector3 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The end point of the curve - public Vector3 P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public Vector3 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector3 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial3D( - P0, - 3*(-P0+P1), - 3*P0-6*P1+3*P2, - -P0+3*P1-3*P2+P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( BezierCubic3D a, BezierCubic3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierCubic3D a, BezierCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Splines/Uniform Spline Segments/BezierQuad1D.cs index c3797fd..7bc14cc 100644 --- a/Splines/Uniform Spline Segments/BezierQuad1D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad1D.cs @@ -12,86 +12,39 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Matrix3x1 pointMatrix; + [NonSerialized] Polynomial curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 1D Quadratic bézier segment, from 3 control points /// The starting point of the curve /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve - public BezierQuad1D( float p0, float p1, float p2 ) { - pointMatrix = new Matrix3x1( p0, p1, p2 ); - validCoefficients = false; - curve = default; - } + public BezierQuad1D( float p0, float p1, float p2 ) => (pointMatrix,curve,validCoefficients) = (new Matrix3x1(p0, p1, p2),default,false); - Polynomial curve; public Polynomial Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial( + P0, + 2*(-P0+P1), + P0-2*P1+P2 + ); } } - #region Control Points - - [SerializeField] Matrix3x1 pointMatrix; - public Matrix3x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Matrix3x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public float P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public float P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The middle control point of the curve, sometimes called a tangent point - public float P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public float P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve - public float P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public float P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 2 public float this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial( - P0, - 2*(-P0+P1), - P0-2*P1+P2 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( BezierQuad1D a, BezierQuad1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierQuad1D a, BezierQuad1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index b1e0213..cf6576c 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -12,86 +12,39 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector2Matrix3x1 pointMatrix; + [NonSerialized] Polynomial2D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 2D Quadratic bézier segment, from 3 control points /// The starting point of the curve /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve - public BezierQuad2D( Vector2 p0, Vector2 p1, Vector2 p2 ) { - pointMatrix = new Vector2Matrix3x1( p0, p1, p2 ); - validCoefficients = false; - curve = default; - } + public BezierQuad2D( Vector2 p0, Vector2 p1, Vector2 p2 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix3x1(p0, p1, p2),default,false); - Polynomial2D curve; public Polynomial2D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial2D( + P0, + 2*(-P0+P1), + P0-2*P1+P2 + ); } } - #region Control Points - - [SerializeField] Vector2Matrix3x1 pointMatrix; - public Vector2Matrix3x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector2Matrix3x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public Vector2 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector2 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The middle control point of the curve, sometimes called a tangent point - public Vector2 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector2 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve - public Vector2 P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector2 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 2 public Vector2 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial2D( - P0, - 2*(-P0+P1), - P0-2*P1+P2 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( BezierQuad2D a, BezierQuad2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierQuad2D a, BezierQuad2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index 46a33db..78e61ff 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -12,86 +12,39 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector3Matrix3x1 pointMatrix; + [NonSerialized] Polynomial3D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 3D Quadratic bézier segment, from 3 control points /// The starting point of the curve /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve - public BezierQuad3D( Vector3 p0, Vector3 p1, Vector3 p2 ) { - pointMatrix = new Vector3Matrix3x1( p0, p1, p2 ); - validCoefficients = false; - curve = default; - } + public BezierQuad3D( Vector3 p0, Vector3 p1, Vector3 p2 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix3x1(p0, p1, p2),default,false); - Polynomial3D curve; public Polynomial3D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial3D( + P0, + 2*(-P0+P1), + P0-2*P1+P2 + ); } } - #region Control Points - - [SerializeField] Vector3Matrix3x1 pointMatrix; - public Vector3Matrix3x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector3Matrix3x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public Vector3 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector3 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The middle control point of the curve, sometimes called a tangent point - public Vector3 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector3 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve - public Vector3 P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector3 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 2 public Vector3 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial3D( - P0, - 2*(-P0+P1), - P0-2*P1+P2 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( BezierQuad3D a, BezierQuad3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( BezierQuad3D a, BezierQuad3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index f9d989c..22cf9f7 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Matrix4x1 pointMatrix; + [NonSerialized] Polynomial curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 1D Cubic catmull-rom segment, from 4 control points /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it /// The second control point, and the start of the catmull-rom curve /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public CatRomCubic1D( float p0, float p1, float p2, float p3 ) { - pointMatrix = new Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public CatRomCubic1D( float p0, float p1, float p2, float p3 ) => (pointMatrix,curve,validCoefficients) = (new Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial curve; public Polynomial Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial( + P1, + (-P0+P2)/2, + P0-(5/2f)*P1+2*P2-(1/2f)*P3, + -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 + ); } } - #region Control Points - - [SerializeField] Matrix4x1 pointMatrix; - public Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public float P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public float P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point, and the start of the catmull-rom curve - public float P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public float P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point, and the end of the catmull-rom curve - public float P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public float P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public float P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public float P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public float this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial( - P1, - (-P0+P2)/2, - P0-(5/2f)*P1+2*P2-(1/2f)*P3, - -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( CatRomCubic1D a, CatRomCubic1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( CatRomCubic1D a, CatRomCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index ce4d27f..588a85a 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector2Matrix4x1 pointMatrix; + [NonSerialized] Polynomial2D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 2D Cubic catmull-rom segment, from 4 control points /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it /// The second control point, and the start of the catmull-rom curve /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public CatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { - pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public CatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial2D curve; public Polynomial2D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial2D( + P1, + (-P0+P2)/2, + P0-(5/2f)*P1+2*P2-(1/2f)*P3, + -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 + ); } } - #region Control Points - - [SerializeField] Vector2Matrix4x1 pointMatrix; - public Vector2Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector2Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public Vector2 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector2 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point, and the start of the catmull-rom curve - public Vector2 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector2 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point, and the end of the catmull-rom curve - public Vector2 P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector2 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public Vector2 P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public Vector2 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector2 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial2D( - P1, - (-P0+P2)/2, - P0-(5/2f)*P1+2*P2-(1/2f)*P3, - -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( CatRomCubic2D a, CatRomCubic2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( CatRomCubic2D a, CatRomCubic2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index 18a5cea..a38a27a 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector3Matrix4x1 pointMatrix; + [NonSerialized] Polynomial3D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 3D Cubic catmull-rom segment, from 4 control points /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it /// The second control point, and the start of the catmull-rom curve /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public CatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { - pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public CatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial3D curve; public Polynomial3D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial3D( + P1, + (-P0+P2)/2, + P0-(5/2f)*P1+2*P2-(1/2f)*P3, + -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 + ); } } - #region Control Points - - [SerializeField] Vector3Matrix4x1 pointMatrix; - public Vector3Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector3Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public Vector3 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector3 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point, and the start of the catmull-rom curve - public Vector3 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector3 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point, and the end of the catmull-rom curve - public Vector3 P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector3 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public Vector3 P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public Vector3 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector3 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial3D( - P1, - (-P0+P2)/2, - P0-(5/2f)*P1+2*P2-(1/2f)*P3, - -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( CatRomCubic3D a, CatRomCubic3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( CatRomCubic3D a, CatRomCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index 87a7f87..7d4f4de 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Matrix4x1 pointMatrix; + [NonSerialized] Polynomial curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 1D Cubic hermite segment, from 4 control points /// The starting point of the curve /// The rate of change (velocity) at the start of the curve /// The end point of the curve /// The rate of change (velocity) at the end of the curve - public HermiteCubic1D( float p0, float v0, float p1, float v1 ) { - pointMatrix = new Matrix4x1( p0, v0, p1, v1 ); - validCoefficients = false; - curve = default; - } + public HermiteCubic1D( float p0, float v0, float p1, float v1 ) => (pointMatrix,curve,validCoefficients) = (new Matrix4x1(p0, v0, p1, v1),default,false); - Polynomial curve; public Polynomial Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial( + P0, + V0, + -3*P0-2*V0+3*P1-V1, + 2*P0+V0-2*P1+V1 + ); } } - #region Control Points - - [SerializeField] Matrix4x1 pointMatrix; - public Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public float P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public float P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The rate of change (velocity) at the start of the curve - public float V0 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public float V0{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve - public float P1 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public float P1{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The rate of change (velocity) at the end of the curve - public float V1 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public float V1{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public float this[ int i ] { - get => - i switch { - 0 => P0, - 1 => V0, - 2 => P1, - 3 => V1, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - V0 = value; - break; - case 2: - P1 = value; - break; - case 3: - V1 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial( - P0, - V0, - -3*P0-2*V0+3*P1-V1, - 2*P0+V0-2*P1+V1 - ); + get => i switch { 0 => P0, 1 => V0, 2 => P1, 3 => V1, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: V0 = value; break; case 2: P1 = value; break; case 3: V1 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( HermiteCubic1D a, HermiteCubic1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( HermiteCubic1D a, HermiteCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index ad098a3..9f6b781 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector2Matrix4x1 pointMatrix; + [NonSerialized] Polynomial2D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 2D Cubic hermite segment, from 4 control points /// The starting point of the curve /// The rate of change (velocity) at the start of the curve /// The end point of the curve /// The rate of change (velocity) at the end of the curve - public HermiteCubic2D( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) { - pointMatrix = new Vector2Matrix4x1( p0, v0, p1, v1 ); - validCoefficients = false; - curve = default; - } + public HermiteCubic2D( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix4x1(p0, v0, p1, v1),default,false); - Polynomial2D curve; public Polynomial2D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial2D( + P0, + V0, + -3*P0-2*V0+3*P1-V1, + 2*P0+V0-2*P1+V1 + ); } } - #region Control Points - - [SerializeField] Vector2Matrix4x1 pointMatrix; - public Vector2Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector2Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public Vector2 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector2 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The rate of change (velocity) at the start of the curve - public Vector2 V0 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector2 V0{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve - public Vector2 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector2 P1{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The rate of change (velocity) at the end of the curve - public Vector2 V1 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public Vector2 V1{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector2 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => V0, - 2 => P1, - 3 => V1, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - V0 = value; - break; - case 2: - P1 = value; - break; - case 3: - V1 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial2D( - P0, - V0, - -3*P0-2*V0+3*P1-V1, - 2*P0+V0-2*P1+V1 - ); + get => i switch { 0 => P0, 1 => V0, 2 => P1, 3 => V1, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: V0 = value; break; case 2: P1 = value; break; case 3: V1 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( HermiteCubic2D a, HermiteCubic2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( HermiteCubic2D a, HermiteCubic2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index a98c4bc..d3a5de4 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector3Matrix4x1 pointMatrix; + [NonSerialized] Polynomial3D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 3D Cubic hermite segment, from 4 control points /// The starting point of the curve /// The rate of change (velocity) at the start of the curve /// The end point of the curve /// The rate of change (velocity) at the end of the curve - public HermiteCubic3D( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) { - pointMatrix = new Vector3Matrix4x1( p0, v0, p1, v1 ); - validCoefficients = false; - curve = default; - } + public HermiteCubic3D( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix4x1(p0, v0, p1, v1),default,false); - Polynomial3D curve; public Polynomial3D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial3D( + P0, + V0, + -3*P0-2*V0+3*P1-V1, + 2*P0+V0-2*P1+V1 + ); } } - #region Control Points - - [SerializeField] Vector3Matrix4x1 pointMatrix; - public Vector3Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector3Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The starting point of the curve - public Vector3 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector3 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The rate of change (velocity) at the start of the curve - public Vector3 V0 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector3 V0{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The end point of the curve - public Vector3 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector3 P1{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The rate of change (velocity) at the end of the curve - public Vector3 V1 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public Vector3 V1{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector3 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => V0, - 2 => P1, - 3 => V1, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - V0 = value; - break; - case 2: - P1 = value; - break; - case 3: - V1 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial3D( - P0, - V0, - -3*P0-2*V0+3*P1-V1, - 2*P0+V0-2*P1+V1 - ); + get => i switch { 0 => P0, 1 => V0, 2 => P1, 3 => V1, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: V0 = value; break; case 2: P1 = value; break; case 3: V1 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( HermiteCubic3D a, HermiteCubic3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( HermiteCubic3D a, HermiteCubic3D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index 7c23c9e..d66f789 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Matrix4x1 pointMatrix; + [NonSerialized] Polynomial curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 1D Cubic b-spline segment, from 4 control points /// The first point of the B-spline hull /// The second point of the B-spline hull /// The third point of the B-spline hull /// The fourth point of the B-spline hull - public UBSCubic1D( float p0, float p1, float p2, float p3 ) { - pointMatrix = new Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public UBSCubic1D( float p0, float p1, float p2, float p3 ) => (pointMatrix,curve,validCoefficients) = (new Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial curve; public Polynomial Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial( + (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, + (-P0+P2)/2, + (1/2f)*P0-P1+(1/2f)*P2, + -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 + ); } } - #region Control Points - - [SerializeField] Matrix4x1 pointMatrix; - public Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The first point of the B-spline hull - public float P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public float P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second point of the B-spline hull - public float P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public float P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third point of the B-spline hull - public float P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public float P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The fourth point of the B-spline hull - public float P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public float P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public float this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial( - (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, - (-P0+P2)/2, - (1/2f)*P0-P1+(1/2f)*P2, - -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( UBSCubic1D a, UBSCubic1D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( UBSCubic1D a, UBSCubic1D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index eb0404f..068aa2b 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector2Matrix4x1 pointMatrix; + [NonSerialized] Polynomial2D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 2D Cubic b-spline segment, from 4 control points /// The first point of the B-spline hull /// The second point of the B-spline hull /// The third point of the B-spline hull /// The fourth point of the B-spline hull - public UBSCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) { - pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public UBSCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial2D curve; public Polynomial2D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial2D( + (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, + (-P0+P2)/2, + (1/2f)*P0-P1+(1/2f)*P2, + -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 + ); } } - #region Control Points - - [SerializeField] Vector2Matrix4x1 pointMatrix; - public Vector2Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector2Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The first point of the B-spline hull - public Vector2 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector2 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second point of the B-spline hull - public Vector2 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector2 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third point of the B-spline hull - public Vector2 P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector2 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The fourth point of the B-spline hull - public Vector2 P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public Vector2 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector2 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial2D( - (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, - (-P0+P2)/2, - (1/2f)*P0-P1+(1/2f)*P2, - -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( UBSCubic2D a, UBSCubic2D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( UBSCubic2D a, UBSCubic2D b ) => !( a == b ); diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index bcb3c81..410c517 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -12,98 +12,43 @@ namespace Freya { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [SerializeField] Vector3Matrix4x1 pointMatrix; + [NonSerialized] Polynomial3D curve; + [NonSerialized] bool validCoefficients; + /// Creates a uniform 3D Cubic b-spline segment, from 4 control points /// The first point of the B-spline hull /// The second point of the B-spline hull /// The third point of the B-spline hull /// The fourth point of the B-spline hull - public UBSCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { - pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); - validCoefficients = false; - curve = default; - } + public UBSCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix4x1(p0, p1, p2, p3),default,false); - Polynomial3D curve; public Polynomial3D Curve { get { - ReadyCoefficients(); - return curve; + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial3D( + (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, + (-P0+P2)/2, + (1/2f)*P0-P1+(1/2f)*P2, + -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 + ); } } - #region Control Points - - [SerializeField] Vector3Matrix4x1 pointMatrix; - public Vector3Matrix4x1 PointMatrix { - get => pointMatrix; - set => _ = ( pointMatrix = value, validCoefficients = false ); - } - + public Vector3Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } /// The first point of the B-spline hull - public Vector3 P0 { - [MethodImpl( INLINE )] get => pointMatrix.m0; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); - } - + public Vector3 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second point of the B-spline hull - public Vector3 P1 { - [MethodImpl( INLINE )] get => pointMatrix.m1; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); - } - + public Vector3 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third point of the B-spline hull - public Vector3 P2 { - [MethodImpl( INLINE )] get => pointMatrix.m2; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); - } - + public Vector3 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The fourth point of the B-spline hull - public Vector3 P3 { - [MethodImpl( INLINE )] get => pointMatrix.m3; - [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); - } - + public Vector3 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// Get or set a control point position by index. Valid indices from 0 to 3 public Vector3 this[ int i ] { - get => - i switch { - 0 => P0, - 1 => P1, - 2 => P2, - 3 => P3, - _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) - }; - set { - switch( i ) { - case 0: - P0 = value; - break; - case 1: - P1 = value; - break; - case 2: - P2 = value; - break; - case 3: - P3 = value; - break; - default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); - } - } - } - - #endregion - [NonSerialized] bool validCoefficients; - - [MethodImpl( INLINE )] void ReadyCoefficients() { - if( validCoefficients ) - return; // no need to update - validCoefficients = true; - curve = new Polynomial3D( - (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, - (-P0+P2)/2, - (1/2f)*P0-P1+(1/2f)*P2, - -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 - ); + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} } public static bool operator ==( UBSCubic3D a, UBSCubic3D b ) => a.pointMatrix == b.pointMatrix; public static bool operator !=( UBSCubic3D a, UBSCubic3D b ) => !( a == b ); From 870d1eada2d21de28bb560f1d9de0c6928b3f9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 15 Jun 2022 10:17:08 +0200 Subject: [PATCH 098/301] added rational matrix identity & zero --- Numerics/RationalMatrix3x3.cs | 5 ++++- Numerics/RationalMatrix4x4.cs | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Numerics/RationalMatrix3x3.cs b/Numerics/RationalMatrix3x3.cs index 8521285..066b112 100644 --- a/Numerics/RationalMatrix3x3.cs +++ b/Numerics/RationalMatrix3x3.cs @@ -8,6 +8,9 @@ namespace Freya { /// A 4x4 matrix using exact rational number representation public readonly struct RationalMatrix3x3 { + public static readonly RationalMatrix3x3 Identity = new RationalMatrix3x3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); + public static readonly RationalMatrix3x3 Zero = new RationalMatrix3x3( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + public readonly Rational m00, m01, m02; public readonly Rational m10, m11, m12; public readonly Rational m20, m21, m22; @@ -102,7 +105,7 @@ public Rational Determinant { /// public static Vector3Matrix3x1 operator *( RationalMatrix3x3 c, Vector3Matrix3x1 m ) => new(c * m.X, c * m.Y, c * m.Z); - + } } \ No newline at end of file diff --git a/Numerics/RationalMatrix4x4.cs b/Numerics/RationalMatrix4x4.cs index ea23651..841bd63 100644 --- a/Numerics/RationalMatrix4x4.cs +++ b/Numerics/RationalMatrix4x4.cs @@ -8,6 +8,9 @@ namespace Freya { /// A 4x4 matrix using exact rational number representation public readonly struct RationalMatrix4x4 { + public static readonly RationalMatrix4x4 Identity = new RationalMatrix4x4( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ); + public static readonly RationalMatrix4x4 Zero = new RationalMatrix4x4( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + public readonly Rational m00, m01, m02, m03; public readonly Rational m10, m11, m12, m13; public readonly Rational m20, m21, m22, m23; From 5f1f689dac386ea8bae4ec7dd925fc9d9aba10d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 15 Jun 2022 10:53:52 +0200 Subject: [PATCH 099/301] 4D spline segments added to codegen --- Codegen/Editor/MathfsCodegen.cs | 17 +- Curves/Polynomial4D.cs | 199 ++++++++++++++++++ Numerics/RationalMatrix4x4.cs | 3 + Numerics/Vector4Matrix3x1.cs | 31 +++ Numerics/Vector4Matrix4x1.cs | 31 +++ .../Uniform Spline Segments/BezierCubic4D.cs | 128 +++++++++++ .../Uniform Spline Segments/BezierQuad4D.cs | 87 ++++++++ .../Uniform Spline Segments/CatRomCubic4D.cs | 93 ++++++++ .../Uniform Spline Segments/HermiteCubic4D.cs | 93 ++++++++ Splines/Uniform Spline Segments/UBSCubic4D.cs | 93 ++++++++ 10 files changed, 768 insertions(+), 7 deletions(-) create mode 100644 Curves/Polynomial4D.cs create mode 100644 Numerics/Vector4Matrix3x1.cs create mode 100644 Numerics/Vector4Matrix4x1.cs create mode 100644 Splines/Uniform Spline Segments/BezierCubic4D.cs create mode 100644 Splines/Uniform Spline Segments/BezierQuad4D.cs create mode 100644 Splines/Uniform Spline Segments/CatRomCubic4D.cs create mode 100644 Splines/Uniform Spline Segments/HermiteCubic4D.cs create mode 100644 Splines/Uniform Spline Segments/UBSCubic4D.cs diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index fe5d777..0e1da3e 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -118,6 +118,9 @@ public static void PortSplineData() { else if( dim == 3 ) for( int i = 0; i < type.paramNames.Length; i++ ) ptMtx.FindPropertyRelative( $"m{i}" ).vector3Value = prop.FindPropertyRelative( type.paramNames[i] ).vector3Value; + else if( dim == 4 ) + for( int i = 0; i < type.paramNames.Length; i++ ) + ptMtx.FindPropertyRelative( $"m{i}" ).vector4Value = prop.FindPropertyRelative( type.paramNames[i] ).vector4Value; } catch { Debug.LogError( $"Null thing in {go.name}/{c.GetType().Name}/{prop.propertyPath} of type {type.className} mtx: {type.matrixName}" ); } @@ -153,7 +156,7 @@ static bool IsSplineType( string name, out SplineType type, out int dim ) { [MenuItem( "Assets/Run Mathfs Codegen" )] public static void Regenerate() { - for( int dim = 1; dim < 4; dim++ ) { // 1D, 2D, 3D + for( int dim = 1; dim < 5; dim++ ) { // 1D, 2D, 3D, 4D GenerateType( typeBezier, dim ); GenerateType( typeBezierQuad, dim ); GenerateType( typeHermite, dim ); @@ -176,14 +179,14 @@ public static string GetLerpName( int dim ) { static void GenerateMatrix( int count, int dim ) { - const string vCompStr = "xyz"; - const string vCompStrUp = "XYZ"; + const string vCompStr = "xyzw"; + const string vCompStrUp = "XYZW"; int[] elemRange = Enumerable.Range( 0, count ).ToArray(); int[] compRange = Enumerable.Range( 0, dim ).ToArray(); string[] compRangeStr = compRange.Select( c => vCompStr[c].ToString() ).ToArray(); string JoinRange( string separator, Func elem ) => string.Join( separator, elemRange.Select( elem ) ); - string typePrefix = dim switch { 2 => "Vector2", 3 => "Vector3", _ => "" }; - string elemType = dim switch { 1 => "float", 2 => "Vector2", 3 => "Vector3", _ => throw new Exception( "Invalid type" ) }; + string typePrefix = dim switch { > 1 => $"Vector{dim}", _ => "" }; + string elemType = dim switch { 1 => "float", > 1 => $"Vector{dim}", _ => throw new Exception( "Invalid type" ) }; string typeName = $"{typePrefix}Matrix{count}x1"; string csParams = JoinRange( ", ", i => $"m{i}" ); @@ -421,7 +424,7 @@ static void GenerateType( SplineType type, int dim ) { // special case slerps for cubic beziers in 2D and 3D - if( dim > 1 && degree is 2 or 3 && type == typeBezier ) { + if( dim is 2 or 3 && type == typeBezier ) { // todo: hermite slerp string slerpCast = dim == 2 ? "(Vector2)" : ""; code.LineBreak(); @@ -538,7 +541,7 @@ public static string GetDegreeName( int d, bool shortName ) { }; } - static readonly string[] comp = { "x", "y", "z" }; + static readonly string[] comp = { "x", "y", "z", "w" }; public static void AppendBezierSplit( CodeGenerator code, string structName, string dataType, int degree, int dim ) { string LerpStr( string A, string B, int c ) => $"{A}.{comp[c]} + ( {B}.{comp[c]} - {A}.{comp[c]} ) * t"; diff --git a/Curves/Polynomial4D.cs b/Curves/Polynomial4D.cs new file mode 100644 index 0000000..195144f --- /dev/null +++ b/Curves/Polynomial4D.cs @@ -0,0 +1,199 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + public struct Polynomial4D : IParamCurve3Diff { + + public Polynomial x; + public Polynomial y; + public Polynomial z; + public Polynomial w; + + public Vector4 C0 { + get => new(x.c0, y.c0, z.c0); + set => ( x.c0, y.c0, z.c0 ) = ( value.x, value.y, value.z ); + } + public Vector4 C1 { + get => new(x.c1, y.c1, z.c1); + set => ( x.c1, y.c1, z.c1 ) = ( value.x, value.y, value.z ); + } + public Vector4 C2 { + get => new(x.c2, y.c2, z.c2); + set => ( x.c2, y.c2, z.c2 ) = ( value.x, value.y, value.z ); + } + public Vector4 C3 { + get => new(x.c3, y.c3, z.c3); + set => ( x.c3, y.c3, z.c3 ) = ( value.x, value.y, value.z ); + } + + public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, 4 => z, _ => throw new IndexOutOfRangeException( "Polynomial4D component index has to be either 0, 1, 2, or 3" ) }; + + public Polynomial4D( Polynomial x, Polynomial y, Polynomial z, Polynomial w ) => ( this.x, this.y, this.z, this.w ) = ( x, y, z, w ); + + /// + public Polynomial4D( Vector4 c0, Vector4 c1, Vector4 c2, Vector4 c3 ) { + this.x = new Polynomial( c0.x, c1.x, c2.x, c3.x ); + this.y = new Polynomial( c0.y, c1.y, c2.y, c3.y ); + this.z = new Polynomial( c0.z, c1.z, c2.z, c3.z ); + this.w = new Polynomial( c0.w, c1.w, c2.w, c3.w ); + } + + /// + public Polynomial4D( Vector4 c0, Vector4 c1, Vector4 c2 ) { + this.x = new Polynomial( c0.x, c1.x, c2.x, 0 ); + this.y = new Polynomial( c0.y, c1.y, c2.y, 0 ); + this.z = new Polynomial( c0.z, c1.z, c2.z, 0 ); + this.w = new Polynomial( c0.w, c1.w, c2.w, 0 ); + } + + /// + public Polynomial4D( Vector4Matrix4x1 coefficients ) => ( x, y, z, w ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ), new Polynomial( coefficients.W ) ); + + /// + public Polynomial4D( Vector4Matrix3x1 coefficients ) => ( x, y, z, w ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ), new Polynomial( coefficients.W ) ); + + /// + public Vector4 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t )); + + /// + public Polynomial4D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n ), w.Differentiate( n )); + + /// + public Polynomial4D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 ), w.Compose( g0, g1 )); + + /// + public (FloatRange x, FloatRange y, FloatRange z, FloatRange w) GetBounds01() => ( x.OutputRange01, y.OutputRange01, z.OutputRange01, w.OutputRange01 ); + + /// + public (Polynomial4D pre, Polynomial4D post) Split01( float u ) { + ( Polynomial xPre, Polynomial xPost ) = x.Split01( u ); + ( Polynomial yPre, Polynomial yPost ) = y.Split01( u ); + ( Polynomial zPre, Polynomial zPost ) = z.Split01( u ); + ( Polynomial wPre, Polynomial wPost ) = w.Split01( u ); + return ( new Polynomial4D( xPre, yPre, zPre, wPre ), new Polynomial4D( xPost, yPost, zPost, wPost ) ); + } + + #region Polynomial to spline converters + + /// + public BezierCubic4D ToBezier() { + Vector4Matrix4x1 p = CharMatrix.cubicBezierInverse * new Vector4Matrix4x1( C0, C1, C2, C3 ); + return new BezierCubic4D( p.m0, p.m1, p.m2, p.m3 ); + } + + /// + public CatRomCubic4D ToCatmullRom() { + Vector4Matrix4x1 p = CharMatrix.cubicCatmullRomInverse * new Vector4Matrix4x1( C0, C1, C2, C3 ); + return new CatRomCubic4D( p.m0, p.m1, p.m2, p.m3 ); + } + + /// + public HermiteCubic4D ToHermite() { + Vector4Matrix4x1 p = CharMatrix.cubicHermiteInverse * new Vector4Matrix4x1( C0, C1, C2, C3 ); + return new HermiteCubic4D( p.m0, p.m1, p.m2, p.m3 ); + } + + /// + public UBSCubic4D ToBSpline() { + Vector4Matrix4x1 p = CharMatrix.cubicUniformBsplineInverse * new Vector4Matrix4x1( C0, C1, C2, C3 ); + return new UBSCubic4D( p.m0, p.m1, p.m2, p.m3 ); + } + + #endregion + + #region IParamCurve3Diff interface implementations + + public int Degree => Mathf.Max( x.Degree, y.Degree, z.Degree, w.Degree ); + public Vector4 EvalDerivative( float t ) => Differentiate().Eval( t ); + public Vector4 EvalSecondDerivative( float t ) => Differentiate( 2 ).Eval( t ); + public Vector4 EvalThirdDerivative( float t = 0 ) => Differentiate( 3 ).Eval( 0 ); + + #endregion + + #region Project Point + + /// + public Vector4 ProjectPoint( Vector4 point, int initialSubdivisions = 16, int refinementIterations = 4 ) => ProjectPoint( point, out _, initialSubdivisions, refinementIterations ); + + struct PointProjectSample { + public float t; + public float distDeltaSq; + public Vector4 f; + public Vector4 fp; + } + + static PointProjectSample[] pointProjectGuesses = { default, default, default }; + + /// + public Vector4 ProjectPoint( Vector4 point, out float t, int initialSubdivisions = 16, int refinementIterations = 4 ) { + // define a bezier relative to the test point + Polynomial4D curve = this; + curve.x.c0 -= point.x; // constant coefficient defines the start position + curve.y.c0 -= point.y; + curve.z.c0 -= point.z; + Vector4 curveStart = curve.Eval( 0 ); + Vector4 curveEnd = curve.Eval( 1 ); + + PointProjectSample SampleDistSqDelta( float tSmp ) { + PointProjectSample s = new PointProjectSample { t = tSmp }; + ( s.f, s.fp ) = ( curve.Eval( tSmp ), curve.EvalDerivative( tSmp ) ); + s.distDeltaSq = Vector4.Dot( s.f, s.fp ); + return s; + } + + // find initial candidates + int candidatesFound = 0; + PointProjectSample prevSmp = SampleDistSqDelta( 0 ); + + for( int i = 1; i < initialSubdivisions; i++ ) { + float ti = i / ( initialSubdivisions - 1f ); + PointProjectSample smp = SampleDistSqDelta( ti ); + if( Mathfs.SignAsInt( smp.distDeltaSq ) != Mathfs.SignAsInt( prevSmp.distDeltaSq ) ) { + pointProjectGuesses[candidatesFound++] = SampleDistSqDelta( ( prevSmp.t + smp.t ) / 2 ); + if( candidatesFound == 3 ) break; // no more than three possible candidates because of the polynomial degree + } + + prevSmp = smp; + } + + // refine each guess w. Newton-Raphson iterations + void Refine( ref PointProjectSample smp ) { + Vector4 fpp = curve.EvalSecondDerivative( smp.t ); + float tNew = smp.t - Vector4.Dot( smp.f, smp.fp ) / ( Vector4.Dot( smp.f, fpp ) + Vector4.Dot( smp.fp, smp.fp ) ); + smp = SampleDistSqDelta( tNew ); + } + + for( int p = 0; p < candidatesFound; p++ ) + for( int i = 0; i < refinementIterations; i++ ) + Refine( ref pointProjectGuesses[p] ); + + // Now find closest. First include the endpoints + float sqDist0 = curveStart.sqrMagnitude; // include endpoints + float sqDist1 = curveEnd.sqrMagnitude; + bool firstClosest = sqDist0 < sqDist1; + float tClosest = firstClosest ? 0 : 1; + Vector4 ptClosest = ( firstClosest ? curveStart : curveEnd ) + point; + float distSqClosest = firstClosest ? sqDist0 : sqDist1; + + // then check internal roots + for( int i = 0; i < candidatesFound; i++ ) { + float pSqmag = pointProjectGuesses[i].f.sqrMagnitude; + if( pSqmag < distSqClosest ) { + distSqClosest = pSqmag; + tClosest = pointProjectGuesses[i].t; + ptClosest = pointProjectGuesses[i].f + point; + } + } + + t = tClosest; + return ptClosest; + } + + #endregion + + } + +} \ No newline at end of file diff --git a/Numerics/RationalMatrix4x4.cs b/Numerics/RationalMatrix4x4.cs index 841bd63..d65b638 100644 --- a/Numerics/RationalMatrix4x4.cs +++ b/Numerics/RationalMatrix4x4.cs @@ -156,6 +156,9 @@ public static explicit operator RationalMatrix4x4( RationalMatrix3x3 c ) => /// public static Vector3Matrix4x1 operator *( RationalMatrix4x4 c, Vector3Matrix4x1 m ) => new(c * m.X, c * m.Y, c * m.Z); + /// + public static Vector4Matrix4x1 operator *( RationalMatrix4x4 c, Vector4Matrix4x1 m ) => new(c * m.X, c * m.Y, c * m.Z, c * m.W); + } } \ No newline at end of file diff --git a/Numerics/Vector4Matrix3x1.cs b/Numerics/Vector4Matrix3x1.cs new file mode 100644 index 0000000..7470660 --- /dev/null +++ b/Numerics/Vector4Matrix3x1.cs @@ -0,0 +1,31 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using UnityEngine; +namespace Freya { + /// A 3x1 column matrix with Vector4 values + [Serializable] public struct Vector4Matrix3x1 { + public Vector4 m0, m1, m2; + public Vector4Matrix3x1(Vector4 m0, Vector4 m1, Vector4 m2) => (this.m0, this.m1, this.m2) = (m0, m1, m2); + public Vector4Matrix3x1(Matrix3x1 x, Matrix3x1 y, Matrix3x1 z, Matrix3x1 w) => (m0, m1, m2) = (new Vector4(x.m0, y.m0, z.m0, w.m0), new Vector4(x.m1, y.m1, z.m1, w.m1), new Vector4(x.m2, y.m2, z.m2, w.m2)); + public Vector4 this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 2, got: {row}" )}; + set { + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 2, got: {row}" ); + } + } + } + public Matrix3x1 X => new(m0.x, m1.x, m2.x); + public Matrix3x1 Y => new(m0.y, m1.y, m2.y); + public Matrix3x1 Z => new(m0.z, m1.z, m2.z); + public Matrix3x1 W => new(m0.w, m1.w, m2.w); + public static bool operator ==( Vector4Matrix3x1 a, Vector4Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; + public static bool operator !=( Vector4Matrix3x1 a, Vector4Matrix3x1 b ) => !( a == b ); + public bool Equals( Vector4Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); + public override bool Equals( object obj ) => obj is Vector4Matrix3x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); + } +} diff --git a/Numerics/Vector4Matrix4x1.cs b/Numerics/Vector4Matrix4x1.cs new file mode 100644 index 0000000..a93815e --- /dev/null +++ b/Numerics/Vector4Matrix4x1.cs @@ -0,0 +1,31 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using UnityEngine; +namespace Freya { + /// A 4x1 column matrix with Vector4 values + [Serializable] public struct Vector4Matrix4x1 { + public Vector4 m0, m1, m2, m3; + public Vector4Matrix4x1(Vector4 m0, Vector4 m1, Vector4 m2, Vector4 m3) => (this.m0, this.m1, this.m2, this.m3) = (m0, m1, m2, m3); + public Vector4Matrix4x1(Matrix4x1 x, Matrix4x1 y, Matrix4x1 z, Matrix4x1 w) => (m0, m1, m2, m3) = (new Vector4(x.m0, y.m0, z.m0, w.m0), new Vector4(x.m1, y.m1, z.m1, w.m1), new Vector4(x.m2, y.m2, z.m2, w.m2), new Vector4(x.m3, y.m3, z.m3, w.m3)); + public Vector4 this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, 3 => m3, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" )}; + set { + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; case 3: m3 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" ); + } + } + } + public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); + public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); + public Matrix4x1 Z => new(m0.z, m1.z, m2.z, m3.z); + public Matrix4x1 W => new(m0.w, m1.w, m2.w, m3.w); + public static bool operator ==( Vector4Matrix4x1 a, Vector4Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; + public static bool operator !=( Vector4Matrix4x1 a, Vector4Matrix4x1 b ) => !( a == b ); + public bool Equals( Vector4Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); + public override bool Equals( object obj ) => obj is Vector4Matrix4x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); + } +} diff --git a/Splines/Uniform Spline Segments/BezierCubic4D.cs b/Splines/Uniform Spline Segments/BezierCubic4D.cs new file mode 100644 index 0000000..96b1528 --- /dev/null +++ b/Splines/Uniform Spline Segments/BezierCubic4D.cs @@ -0,0 +1,128 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 4D Cubic bézier segment, with 4 control points + [Serializable] public struct BezierCubic4D : IParamSplineSegment { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + [SerializeField] Vector4Matrix4x1 pointMatrix; + [NonSerialized] Polynomial4D curve; + [NonSerialized] bool validCoefficients; + + /// Creates a uniform 4D Cubic bézier segment, from 4 control points + /// The starting point of the curve + /// The second control point of the curve, sometimes called the start tangent point + /// The third control point of the curve, sometimes called the end tangent point + /// The end point of the curve + public BezierCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix4x1(p0, p1, p2, p3),default,false); + + public Polynomial4D Curve { + get { + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial4D( + P0, + 3*(-P0+P1), + 3*P0-6*P1+3*P2, + -P0+3*P1-3*P2+P3 + ); + } + } + public Vector4Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } + /// The starting point of the curve + public Vector4 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } + /// The second control point of the curve, sometimes called the start tangent point + public Vector4 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } + /// The third control point of the curve, sometimes called the end tangent point + public Vector4 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } + /// The end point of the curve + public Vector4 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } + /// Get or set a control point position by index. Valid indices from 0 to 3 + public Vector4 this[ int i ] { + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} + } + public static bool operator ==( BezierCubic4D a, BezierCubic4D b ) => a.pointMatrix == b.pointMatrix; + public static bool operator !=( BezierCubic4D a, BezierCubic4D b ) => !( a == b ); + public bool Equals( BezierCubic4D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is BezierCubic4D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; + + public static explicit operator HermiteCubic4D( BezierCubic4D s ) => + new HermiteCubic4D( + s.P0, + 3*(-s.P0+s.P1), + s.P3, + 3*(-s.P2+s.P3) + ); + public static explicit operator CatRomCubic4D( BezierCubic4D s ) => + new CatRomCubic4D( + 6*s.P0-6*s.P1+s.P3, + s.P0, + s.P3, + s.P0-6*s.P2+6*s.P3 + ); + public static explicit operator UBSCubic4D( BezierCubic4D s ) => + new UBSCubic4D( + 6*s.P0-7*s.P1+2*s.P2, + 2*s.P1-s.P2, + -s.P1+2*s.P2, + 2*s.P1-7*s.P2+6*s.P3 + ); + /// Returns a linear blend between two bézier curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static BezierCubic4D Lerp( BezierCubic4D a, BezierCubic4D b, float t ) => + new( + Vector4.LerpUnclamped( a.P0, b.P0, t ), + Vector4.LerpUnclamped( a.P1, b.P1, t ), + Vector4.LerpUnclamped( a.P2, b.P2, t ), + Vector4.LerpUnclamped( a.P3, b.P3, t ) + ); + /// Splits this curve at the given t-value, into two curves that together form the exact same shape + /// The t-value to split at + public (BezierCubic4D pre, BezierCubic4D post) Split( float t ) { + Vector4 a = new Vector4( + P0.x + ( P1.x - P0.x ) * t, + P0.y + ( P1.y - P0.y ) * t, + P0.z + ( P1.z - P0.z ) * t, + P0.w + ( P1.w - P0.w ) * t ); + Vector4 b = new Vector4( + P1.x + ( P2.x - P1.x ) * t, + P1.y + ( P2.y - P1.y ) * t, + P1.z + ( P2.z - P1.z ) * t, + P1.w + ( P2.w - P1.w ) * t ); + Vector4 c = new Vector4( + P2.x + ( P3.x - P2.x ) * t, + P2.y + ( P3.y - P2.y ) * t, + P2.z + ( P3.z - P2.z ) * t, + P2.w + ( P3.w - P2.w ) * t ); + Vector4 d = new Vector4( + a.x + ( b.x - a.x ) * t, + a.y + ( b.y - a.y ) * t, + a.z + ( b.z - a.z ) * t, + a.w + ( b.w - a.w ) * t ); + Vector4 e = new Vector4( + b.x + ( c.x - b.x ) * t, + b.y + ( c.y - b.y ) * t, + b.z + ( c.z - b.z ) * t, + b.w + ( c.w - b.w ) * t ); + Vector4 p = new Vector4( + d.x + ( e.x - d.x ) * t, + d.y + ( e.y - d.y ) * t, + d.z + ( e.z - d.z ) * t, + d.w + ( e.w - d.w ) * t ); + return ( new BezierCubic4D( P0, a, d, p ), new BezierCubic4D( p, e, c, P3 ) ); + } + } +} diff --git a/Splines/Uniform Spline Segments/BezierQuad4D.cs b/Splines/Uniform Spline Segments/BezierQuad4D.cs new file mode 100644 index 0000000..f7b7c23 --- /dev/null +++ b/Splines/Uniform Spline Segments/BezierQuad4D.cs @@ -0,0 +1,87 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 4D Quadratic bézier segment, with 3 control points + [Serializable] public struct BezierQuad4D : IParamSplineSegment { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + [SerializeField] Vector4Matrix3x1 pointMatrix; + [NonSerialized] Polynomial4D curve; + [NonSerialized] bool validCoefficients; + + /// Creates a uniform 4D Quadratic bézier segment, from 3 control points + /// The starting point of the curve + /// The middle control point of the curve, sometimes called a tangent point + /// The end point of the curve + public BezierQuad4D( Vector4 p0, Vector4 p1, Vector4 p2 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix3x1(p0, p1, p2),default,false); + + public Polynomial4D Curve { + get { + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial4D( + P0, + 2*(-P0+P1), + P0-2*P1+P2 + ); + } + } + public Vector4Matrix3x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } + /// The starting point of the curve + public Vector4 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } + /// The middle control point of the curve, sometimes called a tangent point + public Vector4 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } + /// The end point of the curve + public Vector4 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } + /// Get or set a control point position by index. Valid indices from 0 to 2 + public Vector4 this[ int i ] { + get => i switch { 0 => P0, 1 => P1, 2 => P2, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 2 range, and I think {i} is outside that range you know" ); }} + } + public static bool operator ==( BezierQuad4D a, BezierQuad4D b ) => a.pointMatrix == b.pointMatrix; + public static bool operator !=( BezierQuad4D a, BezierQuad4D b ) => !( a == b ); + public bool Equals( BezierQuad4D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ); + public override bool Equals( object obj ) => obj is BezierQuad4D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2})"; + + /// Returns a linear blend between two bézier curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static BezierQuad4D Lerp( BezierQuad4D a, BezierQuad4D b, float t ) => + new( + Vector4.LerpUnclamped( a.P0, b.P0, t ), + Vector4.LerpUnclamped( a.P1, b.P1, t ), + Vector4.LerpUnclamped( a.P2, b.P2, t ) + ); + /// Splits this curve at the given t-value, into two curves that together form the exact same shape + /// The t-value to split at + public (BezierQuad4D pre, BezierQuad4D post) Split( float t ) { + Vector4 a = new Vector4( + P0.x + ( P1.x - P0.x ) * t, + P0.y + ( P1.y - P0.y ) * t, + P0.z + ( P1.z - P0.z ) * t, + P0.w + ( P1.w - P0.w ) * t ); + Vector4 b = new Vector4( + P1.x + ( P2.x - P1.x ) * t, + P1.y + ( P2.y - P1.y ) * t, + P1.z + ( P2.z - P1.z ) * t, + P1.w + ( P2.w - P1.w ) * t ); + Vector4 p = new Vector4( + a.x + ( b.x - a.x ) * t, + a.y + ( b.y - a.y ) * t, + a.z + ( b.z - a.z ) * t, + a.w + ( b.w - a.w ) * t ); + return ( new BezierQuad4D( P0, a, p ), new BezierQuad4D( p, b, P2 ) ); + } + } +} diff --git a/Splines/Uniform Spline Segments/CatRomCubic4D.cs b/Splines/Uniform Spline Segments/CatRomCubic4D.cs new file mode 100644 index 0000000..4c32ac1 --- /dev/null +++ b/Splines/Uniform Spline Segments/CatRomCubic4D.cs @@ -0,0 +1,93 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 4D Cubic catmull-rom segment, with 4 control points + [Serializable] public struct CatRomCubic4D : IParamSplineSegment { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + [SerializeField] Vector4Matrix4x1 pointMatrix; + [NonSerialized] Polynomial4D curve; + [NonSerialized] bool validCoefficients; + + /// Creates a uniform 4D Cubic catmull-rom segment, from 4 control points + /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + /// The second control point, and the start of the catmull-rom curve + /// The third control point, and the end of the catmull-rom curve + /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public CatRomCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix4x1(p0, p1, p2, p3),default,false); + + public Polynomial4D Curve { + get { + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial4D( + P1, + (-P0+P2)/2, + P0-(5/2f)*P1+2*P2-(1/2f)*P3, + -(1/2f)*P0+(3/2f)*P1-(3/2f)*P2+(1/2f)*P3 + ); + } + } + public Vector4Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } + /// The first control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public Vector4 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } + /// The second control point, and the start of the catmull-rom curve + public Vector4 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } + /// The third control point, and the end of the catmull-rom curve + public Vector4 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } + /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it + public Vector4 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } + /// Get or set a control point position by index. Valid indices from 0 to 3 + public Vector4 this[ int i ] { + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} + } + public static bool operator ==( CatRomCubic4D a, CatRomCubic4D b ) => a.pointMatrix == b.pointMatrix; + public static bool operator !=( CatRomCubic4D a, CatRomCubic4D b ) => !( a == b ); + public bool Equals( CatRomCubic4D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is CatRomCubic4D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; + + public static explicit operator BezierCubic4D( CatRomCubic4D s ) => + new BezierCubic4D( + s.P1, + -(1/6f)*s.P0+s.P1+(1/6f)*s.P2, + (1/6f)*s.P1+s.P2-(1/6f)*s.P3, + s.P2 + ); + public static explicit operator HermiteCubic4D( CatRomCubic4D s ) => + new HermiteCubic4D( + s.P1, + (-s.P0+s.P2)/2, + s.P2, + (-s.P1+s.P3)/2 + ); + public static explicit operator UBSCubic4D( CatRomCubic4D s ) => + new UBSCubic4D( + (7/6f)*s.P0-(2/3f)*s.P1+(5/6f)*s.P2-(1/3f)*s.P3, + -(1/3f)*s.P0+(11/6f)*s.P1-(2/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0-(2/3f)*s.P1+(11/6f)*s.P2-(1/3f)*s.P3, + -(1/3f)*s.P0+(5/6f)*s.P1-(2/3f)*s.P2+(7/6f)*s.P3 + ); + /// Returns a linear blend between two catmull-rom curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static CatRomCubic4D Lerp( CatRomCubic4D a, CatRomCubic4D b, float t ) => + new( + Vector4.LerpUnclamped( a.P0, b.P0, t ), + Vector4.LerpUnclamped( a.P1, b.P1, t ), + Vector4.LerpUnclamped( a.P2, b.P2, t ), + Vector4.LerpUnclamped( a.P3, b.P3, t ) + ); + } +} diff --git a/Splines/Uniform Spline Segments/HermiteCubic4D.cs b/Splines/Uniform Spline Segments/HermiteCubic4D.cs new file mode 100644 index 0000000..1ed976d --- /dev/null +++ b/Splines/Uniform Spline Segments/HermiteCubic4D.cs @@ -0,0 +1,93 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 4D Cubic hermite segment, with 4 control points + [Serializable] public struct HermiteCubic4D : IParamSplineSegment { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + [SerializeField] Vector4Matrix4x1 pointMatrix; + [NonSerialized] Polynomial4D curve; + [NonSerialized] bool validCoefficients; + + /// Creates a uniform 4D Cubic hermite segment, from 4 control points + /// The starting point of the curve + /// The rate of change (velocity) at the start of the curve + /// The end point of the curve + /// The rate of change (velocity) at the end of the curve + public HermiteCubic4D( Vector4 p0, Vector4 v0, Vector4 p1, Vector4 v1 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix4x1(p0, v0, p1, v1),default,false); + + public Polynomial4D Curve { + get { + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial4D( + P0, + V0, + -3*P0-2*V0+3*P1-V1, + 2*P0+V0-2*P1+V1 + ); + } + } + public Vector4Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } + /// The starting point of the curve + public Vector4 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } + /// The rate of change (velocity) at the start of the curve + public Vector4 V0{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } + /// The end point of the curve + public Vector4 P1{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } + /// The rate of change (velocity) at the end of the curve + public Vector4 V1{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } + /// Get or set a control point position by index. Valid indices from 0 to 3 + public Vector4 this[ int i ] { + get => i switch { 0 => P0, 1 => V0, 2 => P1, 3 => V1, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: V0 = value; break; case 2: P1 = value; break; case 3: V1 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} + } + public static bool operator ==( HermiteCubic4D a, HermiteCubic4D b ) => a.pointMatrix == b.pointMatrix; + public static bool operator !=( HermiteCubic4D a, HermiteCubic4D b ) => !( a == b ); + public bool Equals( HermiteCubic4D other ) => P0.Equals( other.P0 ) && V0.Equals( other.V0 ) && P1.Equals( other.P1 ) && V1.Equals( other.V1 ); + public override bool Equals( object obj ) => obj is HermiteCubic4D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; + + public static explicit operator BezierCubic4D( HermiteCubic4D s ) => + new BezierCubic4D( + s.P0, + s.P0+(1/3f)*s.V0, + s.P1-(1/3f)*s.V1, + s.P1 + ); + public static explicit operator CatRomCubic4D( HermiteCubic4D s ) => + new CatRomCubic4D( + -2*s.V0+s.P1, + s.P0, + s.P1, + s.P0+2*s.V1 + ); + public static explicit operator UBSCubic4D( HermiteCubic4D s ) => + new UBSCubic4D( + -s.P0-(7/3f)*s.V0+2*s.P1-(2/3f)*s.V1, + 2*s.P0+(2/3f)*s.V0-s.P1+(1/3f)*s.V1, + -s.P0-(1/3f)*s.V0+2*s.P1-(2/3f)*s.V1, + 2*s.P0+(2/3f)*s.V0-s.P1+(7/3f)*s.V1 + ); + /// Returns a linear blend between two hermite curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static HermiteCubic4D Lerp( HermiteCubic4D a, HermiteCubic4D b, float t ) => + new( + Vector4.LerpUnclamped( a.P0, b.P0, t ), + Vector4.LerpUnclamped( a.V0, b.V0, t ), + Vector4.LerpUnclamped( a.P1, b.P1, t ), + Vector4.LerpUnclamped( a.V1, b.V1, t ) + ); + } +} diff --git a/Splines/Uniform Spline Segments/UBSCubic4D.cs b/Splines/Uniform Spline Segments/UBSCubic4D.cs new file mode 100644 index 0000000..96c2929 --- /dev/null +++ b/Splines/Uniform Spline Segments/UBSCubic4D.cs @@ -0,0 +1,93 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// An optimized uniform 4D Cubic b-spline segment, with 4 control points + [Serializable] public struct UBSCubic4D : IParamSplineSegment { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + [SerializeField] Vector4Matrix4x1 pointMatrix; + [NonSerialized] Polynomial4D curve; + [NonSerialized] bool validCoefficients; + + /// Creates a uniform 4D Cubic b-spline segment, from 4 control points + /// The first point of the B-spline hull + /// The second point of the B-spline hull + /// The third point of the B-spline hull + /// The fourth point of the B-spline hull + public UBSCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix4x1(p0, p1, p2, p3),default,false); + + public Polynomial4D Curve { + get { + if( validCoefficients ) + return curve; // no need to update + validCoefficients = true; + return curve = new Polynomial4D( + (1/6f)*P0+(2/3f)*P1+(1/6f)*P2, + (-P0+P2)/2, + (1/2f)*P0-P1+(1/2f)*P2, + -(1/6f)*P0+(1/2f)*P1-(1/2f)*P2+(1/6f)*P3 + ); + } + } + public Vector4Matrix4x1 PointMatrix {[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); } + /// The first point of the B-spline hull + public Vector4 P0{ [MethodImpl( INLINE )] get => pointMatrix.m0; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } + /// The second point of the B-spline hull + public Vector4 P1{ [MethodImpl( INLINE )] get => pointMatrix.m1; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } + /// The third point of the B-spline hull + public Vector4 P2{ [MethodImpl( INLINE )] get => pointMatrix.m2; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } + /// The fourth point of the B-spline hull + public Vector4 P3{ [MethodImpl( INLINE )] get => pointMatrix.m3; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } + /// Get or set a control point position by index. Valid indices from 0 to 3 + public Vector4 this[ int i ] { + get => i switch { 0 => P0, 1 => P1, 2 => P2, 3 => P3, _ => throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ) }; + set { switch( i ){ case 0: P0 = value; break; case 1: P1 = value; break; case 2: P2 = value; break; case 3: P3 = value; break; default: throw new ArgumentOutOfRangeException( nameof(i), $"Index has to be in the 0 to 3 range, and I think {i} is outside that range you know" ); }} + } + public static bool operator ==( UBSCubic4D a, UBSCubic4D b ) => a.pointMatrix == b.pointMatrix; + public static bool operator !=( UBSCubic4D a, UBSCubic4D b ) => !( a == b ); + public bool Equals( UBSCubic4D other ) => P0.Equals( other.P0 ) && P1.Equals( other.P1 ) && P2.Equals( other.P2 ) && P3.Equals( other.P3 ); + public override bool Equals( object obj ) => obj is UBSCubic4D other && pointMatrix.Equals( other.pointMatrix ); + public override int GetHashCode() => pointMatrix.GetHashCode(); + public override string ToString() => $"({pointMatrix.m0}, {pointMatrix.m1}, {pointMatrix.m2}, {pointMatrix.m3})"; + + public static explicit operator BezierCubic4D( UBSCubic4D s ) => + new BezierCubic4D( + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (2/3f)*s.P1+(1/3f)*s.P2, + (1/3f)*s.P1+(2/3f)*s.P2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3 + ); + public static explicit operator HermiteCubic4D( UBSCubic4D s ) => + new HermiteCubic4D( + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (-s.P0+s.P2)/2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3, + (-s.P1+s.P3)/2 + ); + public static explicit operator CatRomCubic4D( UBSCubic4D s ) => + new CatRomCubic4D( + s.P0+(1/6f)*s.P1-(1/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0+(2/3f)*s.P1+(1/6f)*s.P2, + (1/6f)*s.P1+(2/3f)*s.P2+(1/6f)*s.P3, + (1/6f)*s.P0-(1/3f)*s.P1+(1/6f)*s.P2+s.P3 + ); + /// Returns a linear blend between two b-spline curves + /// The first spline segment + /// The second spline segment + /// A value from 0 to 1 to blend between a and b + public static UBSCubic4D Lerp( UBSCubic4D a, UBSCubic4D b, float t ) => + new( + Vector4.LerpUnclamped( a.P0, b.P0, t ), + Vector4.LerpUnclamped( a.P1, b.P1, t ), + Vector4.LerpUnclamped( a.P2, b.P2, t ), + Vector4.LerpUnclamped( a.P3, b.P3, t ) + ); + } +} From 1658d8e5136d68bf5254d6ad8b14dfcc601aceef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 15 Jun 2022 22:17:08 +0200 Subject: [PATCH 100/301] fixed typo in Polynomial4D --- Curves/Polynomial4D.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Curves/Polynomial4D.cs b/Curves/Polynomial4D.cs index 195144f..58a5663 100644 --- a/Curves/Polynomial4D.cs +++ b/Curves/Polynomial4D.cs @@ -29,7 +29,7 @@ public Vector4 C3 { set => ( x.c3, y.c3, z.c3 ) = ( value.x, value.y, value.z ); } - public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, 4 => z, _ => throw new IndexOutOfRangeException( "Polynomial4D component index has to be either 0, 1, 2, or 3" ) }; + public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, 4 => w, _ => throw new IndexOutOfRangeException( "Polynomial4D component index has to be either 0, 1, 2, or 3" ) }; public Polynomial4D( Polynomial x, Polynomial y, Polynomial z, Polynomial w ) => ( this.x, this.y, this.z, this.w ) = ( x, y, z, w ); From 9036bbcb41c171c6e9cdba45ac164a02713ae3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 16 Jun 2022 09:32:08 +0200 Subject: [PATCH 101/301] added uniform spline segment matrix constructors --- Codegen/Editor/MathfsCodegen.cs | 23 +++++++++++-------- .../Uniform Spline Segments/BezierCubic1D.cs | 5 +++- .../Uniform Spline Segments/BezierCubic2D.cs | 5 +++- .../Uniform Spline Segments/BezierCubic3D.cs | 5 +++- .../Uniform Spline Segments/BezierCubic4D.cs | 5 +++- .../Uniform Spline Segments/BezierQuad1D.cs | 5 +++- .../Uniform Spline Segments/BezierQuad2D.cs | 5 +++- .../Uniform Spline Segments/BezierQuad3D.cs | 5 +++- .../Uniform Spline Segments/BezierQuad4D.cs | 5 +++- .../Uniform Spline Segments/CatRomCubic1D.cs | 5 +++- .../Uniform Spline Segments/CatRomCubic2D.cs | 5 +++- .../Uniform Spline Segments/CatRomCubic3D.cs | 5 +++- .../Uniform Spline Segments/CatRomCubic4D.cs | 5 +++- .../Uniform Spline Segments/HermiteCubic1D.cs | 5 +++- .../Uniform Spline Segments/HermiteCubic2D.cs | 5 +++- .../Uniform Spline Segments/HermiteCubic3D.cs | 5 +++- .../Uniform Spline Segments/HermiteCubic4D.cs | 5 +++- Splines/Uniform Spline Segments/UBSCubic1D.cs | 5 +++- Splines/Uniform Spline Segments/UBSCubic2D.cs | 5 +++- Splines/Uniform Spline Segments/UBSCubic3D.cs | 5 +++- Splines/Uniform Spline Segments/UBSCubic4D.cs | 5 +++- 21 files changed, 94 insertions(+), 29 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 0e1da3e..18a8dec 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -157,11 +157,11 @@ static bool IsSplineType( string name, out SplineType type, out int dim ) { [MenuItem( "Assets/Run Mathfs Codegen" )] public static void Regenerate() { for( int dim = 1; dim < 5; dim++ ) { // 1D, 2D, 3D, 4D - GenerateType( typeBezier, dim ); - GenerateType( typeBezierQuad, dim ); - GenerateType( typeHermite, dim ); - GenerateType( typeBspline, dim ); - GenerateType( typeCatRom, dim ); + GenerateUniformSplineType( typeBezier, dim ); + GenerateUniformSplineType( typeBezierQuad, dim ); + GenerateUniformSplineType( typeHermite, dim ); + GenerateUniformSplineType( typeBspline, dim ); + GenerateUniformSplineType( typeCatRom, dim ); GenerateMatrix( 3, dim ); GenerateMatrix( 4, dim ); } @@ -254,7 +254,7 @@ static void GenerateMatrix( int count, int dim ) { File.WriteAllLines( path, code.content ); } - static void GenerateType( SplineType type, int dim ) { + static void GenerateUniformSplineType( SplineType type, int dim ) { int degree = type.degree; string dataType = dim == 1 ? "float" : $"Vector{dim}"; string polynomType = dim == 1 ? "Polynomial" : $"Polynomial{dim}D"; @@ -297,11 +297,16 @@ static void GenerateType( SplineType type, int dim ) { code.Append( "[NonSerialized] bool validCoefficients;" ); code.LineBreak(); - // constructor - code.Summary( $"Creates a uniform {dim}D {degFullLower} {type.prettyNameLower} segment, from {ptCount} control points" ); + // constructors + string ctorSummary = $"Creates a uniform {dim}D {degFullLower} {type.prettyNameLower} segment, from {ptCount} control points"; + code.Summary( ctorSummary ); for( int i = 0; i < ptCount; i++ ) type.AppendParamStrings( code, degree, i ); - code.Append( $"public {structName}( {ctorParams} ) => (pointMatrix,curve,validCoefficients) = (new {pointMatrixType}({csPoints}),default,false);" ); + code.Append( $"public {structName}( {ctorParams} ) : this(new {pointMatrixType}({csPoints})){{}}" ); + + code.Summary( ctorSummary ); + code.Param( "pointMatrix", "The matrix containing the control points of this spline" ); + code.Append( $"public {structName}( {pointMatrixType} pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false);" ); code.LineBreak(); diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Splines/Uniform Spline Segments/BezierCubic1D.cs index c84671d..97f6959 100644 --- a/Splines/Uniform Spline Segments/BezierCubic1D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic1D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second control point of the curve, sometimes called the start tangent point /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve - public BezierCubic1D( float p0, float p1, float p2, float p3 ) => (pointMatrix,curve,validCoefficients) = (new Matrix4x1(p0, p1, p2, p3),default,false); + public BezierCubic1D( float p0, float p1, float p2, float p3 ) : this(new Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 1D Cubic bézier segment, from 4 control points + /// The matrix containing the control points of this spline + public BezierCubic1D( Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial Curve { get { diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Splines/Uniform Spline Segments/BezierCubic2D.cs index 5ee2143..116f842 100644 --- a/Splines/Uniform Spline Segments/BezierCubic2D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic2D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second control point of the curve, sometimes called the start tangent point /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve - public BezierCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix4x1(p0, p1, p2, p3),default,false); + public BezierCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) : this(new Vector2Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 2D Cubic bézier segment, from 4 control points + /// The matrix containing the control points of this spline + public BezierCubic2D( Vector2Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial2D Curve { get { diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Splines/Uniform Spline Segments/BezierCubic3D.cs index ae24f51..8ec0cd1 100644 --- a/Splines/Uniform Spline Segments/BezierCubic3D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic3D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second control point of the curve, sometimes called the start tangent point /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve - public BezierCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix4x1(p0, p1, p2, p3),default,false); + public BezierCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) : this(new Vector3Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 3D Cubic bézier segment, from 4 control points + /// The matrix containing the control points of this spline + public BezierCubic3D( Vector3Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial3D Curve { get { diff --git a/Splines/Uniform Spline Segments/BezierCubic4D.cs b/Splines/Uniform Spline Segments/BezierCubic4D.cs index 96b1528..bfafe42 100644 --- a/Splines/Uniform Spline Segments/BezierCubic4D.cs +++ b/Splines/Uniform Spline Segments/BezierCubic4D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second control point of the curve, sometimes called the start tangent point /// The third control point of the curve, sometimes called the end tangent point /// The end point of the curve - public BezierCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix4x1(p0, p1, p2, p3),default,false); + public BezierCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) : this(new Vector4Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 4D Cubic bézier segment, from 4 control points + /// The matrix containing the control points of this spline + public BezierCubic4D( Vector4Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial4D Curve { get { diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Splines/Uniform Spline Segments/BezierQuad1D.cs index 7bc14cc..9e8ca9e 100644 --- a/Splines/Uniform Spline Segments/BezierQuad1D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad1D.cs @@ -20,7 +20,10 @@ namespace Freya { /// The starting point of the curve /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve - public BezierQuad1D( float p0, float p1, float p2 ) => (pointMatrix,curve,validCoefficients) = (new Matrix3x1(p0, p1, p2),default,false); + public BezierQuad1D( float p0, float p1, float p2 ) : this(new Matrix3x1(p0, p1, p2)){} + /// Creates a uniform 1D Quadratic bézier segment, from 3 control points + /// The matrix containing the control points of this spline + public BezierQuad1D( Matrix3x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial Curve { get { diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Splines/Uniform Spline Segments/BezierQuad2D.cs index cf6576c..b844b65 100644 --- a/Splines/Uniform Spline Segments/BezierQuad2D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad2D.cs @@ -20,7 +20,10 @@ namespace Freya { /// The starting point of the curve /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve - public BezierQuad2D( Vector2 p0, Vector2 p1, Vector2 p2 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix3x1(p0, p1, p2),default,false); + public BezierQuad2D( Vector2 p0, Vector2 p1, Vector2 p2 ) : this(new Vector2Matrix3x1(p0, p1, p2)){} + /// Creates a uniform 2D Quadratic bézier segment, from 3 control points + /// The matrix containing the control points of this spline + public BezierQuad2D( Vector2Matrix3x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial2D Curve { get { diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Splines/Uniform Spline Segments/BezierQuad3D.cs index 78e61ff..f617a09 100644 --- a/Splines/Uniform Spline Segments/BezierQuad3D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad3D.cs @@ -20,7 +20,10 @@ namespace Freya { /// The starting point of the curve /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve - public BezierQuad3D( Vector3 p0, Vector3 p1, Vector3 p2 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix3x1(p0, p1, p2),default,false); + public BezierQuad3D( Vector3 p0, Vector3 p1, Vector3 p2 ) : this(new Vector3Matrix3x1(p0, p1, p2)){} + /// Creates a uniform 3D Quadratic bézier segment, from 3 control points + /// The matrix containing the control points of this spline + public BezierQuad3D( Vector3Matrix3x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial3D Curve { get { diff --git a/Splines/Uniform Spline Segments/BezierQuad4D.cs b/Splines/Uniform Spline Segments/BezierQuad4D.cs index f7b7c23..e36e998 100644 --- a/Splines/Uniform Spline Segments/BezierQuad4D.cs +++ b/Splines/Uniform Spline Segments/BezierQuad4D.cs @@ -20,7 +20,10 @@ namespace Freya { /// The starting point of the curve /// The middle control point of the curve, sometimes called a tangent point /// The end point of the curve - public BezierQuad4D( Vector4 p0, Vector4 p1, Vector4 p2 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix3x1(p0, p1, p2),default,false); + public BezierQuad4D( Vector4 p0, Vector4 p1, Vector4 p2 ) : this(new Vector4Matrix3x1(p0, p1, p2)){} + /// Creates a uniform 4D Quadratic bézier segment, from 3 control points + /// The matrix containing the control points of this spline + public BezierQuad4D( Vector4Matrix3x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial4D Curve { get { diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Splines/Uniform Spline Segments/CatRomCubic1D.cs index 22cf9f7..0935785 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic1D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic1D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second control point, and the start of the catmull-rom curve /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public CatRomCubic1D( float p0, float p1, float p2, float p3 ) => (pointMatrix,curve,validCoefficients) = (new Matrix4x1(p0, p1, p2, p3),default,false); + public CatRomCubic1D( float p0, float p1, float p2, float p3 ) : this(new Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 1D Cubic catmull-rom segment, from 4 control points + /// The matrix containing the control points of this spline + public CatRomCubic1D( Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial Curve { get { diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Splines/Uniform Spline Segments/CatRomCubic2D.cs index 588a85a..cfbfc49 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic2D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic2D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second control point, and the start of the catmull-rom curve /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public CatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix4x1(p0, p1, p2, p3),default,false); + public CatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) : this(new Vector2Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 2D Cubic catmull-rom segment, from 4 control points + /// The matrix containing the control points of this spline + public CatRomCubic2D( Vector2Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial2D Curve { get { diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Splines/Uniform Spline Segments/CatRomCubic3D.cs index a38a27a..3d9d724 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic3D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic3D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second control point, and the start of the catmull-rom curve /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public CatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix4x1(p0, p1, p2, p3),default,false); + public CatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) : this(new Vector3Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 3D Cubic catmull-rom segment, from 4 control points + /// The matrix containing the control points of this spline + public CatRomCubic3D( Vector3Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial3D Curve { get { diff --git a/Splines/Uniform Spline Segments/CatRomCubic4D.cs b/Splines/Uniform Spline Segments/CatRomCubic4D.cs index 4c32ac1..0b768dd 100644 --- a/Splines/Uniform Spline Segments/CatRomCubic4D.cs +++ b/Splines/Uniform Spline Segments/CatRomCubic4D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second control point, and the start of the catmull-rom curve /// The third control point, and the end of the catmull-rom curve /// The last control point of the catmull-rom curve. Note that this point is not included in the curve itself, and only helps to shape it - public CatRomCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix4x1(p0, p1, p2, p3),default,false); + public CatRomCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) : this(new Vector4Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 4D Cubic catmull-rom segment, from 4 control points + /// The matrix containing the control points of this spline + public CatRomCubic4D( Vector4Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial4D Curve { get { diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Splines/Uniform Spline Segments/HermiteCubic1D.cs index 7d4f4de..59be8b1 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic1D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic1D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The rate of change (velocity) at the start of the curve /// The end point of the curve /// The rate of change (velocity) at the end of the curve - public HermiteCubic1D( float p0, float v0, float p1, float v1 ) => (pointMatrix,curve,validCoefficients) = (new Matrix4x1(p0, v0, p1, v1),default,false); + public HermiteCubic1D( float p0, float v0, float p1, float v1 ) : this(new Matrix4x1(p0, v0, p1, v1)){} + /// Creates a uniform 1D Cubic hermite segment, from 4 control points + /// The matrix containing the control points of this spline + public HermiteCubic1D( Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial Curve { get { diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Splines/Uniform Spline Segments/HermiteCubic2D.cs index 9f6b781..4f7e15a 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic2D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic2D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The rate of change (velocity) at the start of the curve /// The end point of the curve /// The rate of change (velocity) at the end of the curve - public HermiteCubic2D( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix4x1(p0, v0, p1, v1),default,false); + public HermiteCubic2D( Vector2 p0, Vector2 v0, Vector2 p1, Vector2 v1 ) : this(new Vector2Matrix4x1(p0, v0, p1, v1)){} + /// Creates a uniform 2D Cubic hermite segment, from 4 control points + /// The matrix containing the control points of this spline + public HermiteCubic2D( Vector2Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial2D Curve { get { diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Splines/Uniform Spline Segments/HermiteCubic3D.cs index d3a5de4..c703060 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic3D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic3D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The rate of change (velocity) at the start of the curve /// The end point of the curve /// The rate of change (velocity) at the end of the curve - public HermiteCubic3D( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix4x1(p0, v0, p1, v1),default,false); + public HermiteCubic3D( Vector3 p0, Vector3 v0, Vector3 p1, Vector3 v1 ) : this(new Vector3Matrix4x1(p0, v0, p1, v1)){} + /// Creates a uniform 3D Cubic hermite segment, from 4 control points + /// The matrix containing the control points of this spline + public HermiteCubic3D( Vector3Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial3D Curve { get { diff --git a/Splines/Uniform Spline Segments/HermiteCubic4D.cs b/Splines/Uniform Spline Segments/HermiteCubic4D.cs index 1ed976d..3db1280 100644 --- a/Splines/Uniform Spline Segments/HermiteCubic4D.cs +++ b/Splines/Uniform Spline Segments/HermiteCubic4D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The rate of change (velocity) at the start of the curve /// The end point of the curve /// The rate of change (velocity) at the end of the curve - public HermiteCubic4D( Vector4 p0, Vector4 v0, Vector4 p1, Vector4 v1 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix4x1(p0, v0, p1, v1),default,false); + public HermiteCubic4D( Vector4 p0, Vector4 v0, Vector4 p1, Vector4 v1 ) : this(new Vector4Matrix4x1(p0, v0, p1, v1)){} + /// Creates a uniform 4D Cubic hermite segment, from 4 control points + /// The matrix containing the control points of this spline + public HermiteCubic4D( Vector4Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial4D Curve { get { diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Splines/Uniform Spline Segments/UBSCubic1D.cs index d66f789..53f3e1a 100644 --- a/Splines/Uniform Spline Segments/UBSCubic1D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic1D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second point of the B-spline hull /// The third point of the B-spline hull /// The fourth point of the B-spline hull - public UBSCubic1D( float p0, float p1, float p2, float p3 ) => (pointMatrix,curve,validCoefficients) = (new Matrix4x1(p0, p1, p2, p3),default,false); + public UBSCubic1D( float p0, float p1, float p2, float p3 ) : this(new Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 1D Cubic b-spline segment, from 4 control points + /// The matrix containing the control points of this spline + public UBSCubic1D( Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial Curve { get { diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Splines/Uniform Spline Segments/UBSCubic2D.cs index 068aa2b..22583c7 100644 --- a/Splines/Uniform Spline Segments/UBSCubic2D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic2D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second point of the B-spline hull /// The third point of the B-spline hull /// The fourth point of the B-spline hull - public UBSCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector2Matrix4x1(p0, p1, p2, p3),default,false); + public UBSCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3 ) : this(new Vector2Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 2D Cubic b-spline segment, from 4 control points + /// The matrix containing the control points of this spline + public UBSCubic2D( Vector2Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial2D Curve { get { diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Splines/Uniform Spline Segments/UBSCubic3D.cs index 410c517..c653996 100644 --- a/Splines/Uniform Spline Segments/UBSCubic3D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic3D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second point of the B-spline hull /// The third point of the B-spline hull /// The fourth point of the B-spline hull - public UBSCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector3Matrix4x1(p0, p1, p2, p3),default,false); + public UBSCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) : this(new Vector3Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 3D Cubic b-spline segment, from 4 control points + /// The matrix containing the control points of this spline + public UBSCubic3D( Vector3Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial3D Curve { get { diff --git a/Splines/Uniform Spline Segments/UBSCubic4D.cs b/Splines/Uniform Spline Segments/UBSCubic4D.cs index 96c2929..3dc3ea5 100644 --- a/Splines/Uniform Spline Segments/UBSCubic4D.cs +++ b/Splines/Uniform Spline Segments/UBSCubic4D.cs @@ -21,7 +21,10 @@ namespace Freya { /// The second point of the B-spline hull /// The third point of the B-spline hull /// The fourth point of the B-spline hull - public UBSCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) => (pointMatrix,curve,validCoefficients) = (new Vector4Matrix4x1(p0, p1, p2, p3),default,false); + public UBSCubic4D( Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3 ) : this(new Vector4Matrix4x1(p0, p1, p2, p3)){} + /// Creates a uniform 4D Cubic b-spline segment, from 4 control points + /// The matrix containing the control points of this spline + public UBSCubic4D( Vector4Matrix4x1 pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false); public Polynomial4D Curve { get { From 013258502a07ba9b91019c15f697b692dd1b3d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 16 Jun 2022 10:26:28 +0200 Subject: [PATCH 102/301] forgot a Polynomial4D thing --- Curves/Polynomial4D.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Curves/Polynomial4D.cs b/Curves/Polynomial4D.cs index 58a5663..757e88e 100644 --- a/Curves/Polynomial4D.cs +++ b/Curves/Polynomial4D.cs @@ -13,20 +13,20 @@ public struct Polynomial4D : IParamCurve3Diff { public Polynomial w; public Vector4 C0 { - get => new(x.c0, y.c0, z.c0); - set => ( x.c0, y.c0, z.c0 ) = ( value.x, value.y, value.z ); + get => new(x.c0, y.c0, z.c0, w.c0); + set => ( x.c0, y.c0, z.c0, w.c0 ) = ( value.x, value.y, value.z, value.w ); } public Vector4 C1 { - get => new(x.c1, y.c1, z.c1); - set => ( x.c1, y.c1, z.c1 ) = ( value.x, value.y, value.z ); + get => new(x.c1, y.c1, z.c1, w.c1); + set => ( x.c1, y.c1, z.c1, w.c1 ) = ( value.x, value.y, value.z, value.w ); } public Vector4 C2 { - get => new(x.c2, y.c2, z.c2); - set => ( x.c2, y.c2, z.c2 ) = ( value.x, value.y, value.z ); + get => new(x.c2, y.c2, z.c2, w.c2); + set => ( x.c2, y.c2, z.c2, w.c2 ) = ( value.x, value.y, value.z, value.w ); } public Vector4 C3 { - get => new(x.c3, y.c3, z.c3); - set => ( x.c3, y.c3, z.c3 ) = ( value.x, value.y, value.z ); + get => new(x.c3, y.c3, z.c3, w.c3); + set => ( x.c3, y.c3, z.c3, w.c3 ) = ( value.x, value.y, value.z, value.w ); } public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, 4 => w, _ => throw new IndexOutOfRangeException( "Polynomial4D component index has to be either 0, 1, 2, or 3" ) }; From 854a702eae1d04123362ce18aa3b93ea2d87060d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 16 Jun 2022 10:27:05 +0200 Subject: [PATCH 103/301] polynomial typecasting and operators cleaned up --- Curves/Polynomial.cs | 12 +++++++++++ Curves/Polynomial2D.cs | 45 ++++++++++++++++-------------------------- Curves/Polynomial3D.cs | 44 +++++++++++++++-------------------------- Curves/Polynomial4D.cs | 44 +++++++++++++++-------------------------- 4 files changed, 61 insertions(+), 84 deletions(-) diff --git a/Curves/Polynomial.cs b/Curves/Polynomial.cs index 3cb9ff7..83949e6 100644 --- a/Curves/Polynomial.cs +++ b/Curves/Polynomial.cs @@ -338,11 +338,23 @@ static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { #endregion + #region Typecasting & Operators + public static Polynomial operator /( Polynomial p, float v ) => new(p.c0 / v, p.c1 / v, p.c2 / v, p.c3 / v); public static Polynomial operator /( float v, Polynomial p ) => new(v / p.c0, v / p.c1, v / p.c2, v / p.c3); public static Polynomial operator *( Polynomial p, float v ) => new(p.c0 * v, p.c1 * v, p.c2 * v, p.c3 * v); public static Polynomial operator *( float v, Polynomial p ) => p * v; + public static explicit operator Matrix3x1( Polynomial poly ) => new(poly.c0, poly.c1, poly.c2); + public static explicit operator Matrix4x1( Polynomial poly ) => new(poly.c0, poly.c1, poly.c2, poly.c3); + public static explicit operator BezierQuad1D( Polynomial poly ) => poly.Degree < 3 ? new BezierQuad1D( CharMatrix.quadraticBezierInverse * (Matrix3x1)poly ) : throw new InvalidCastException( "Cannot cast a cubic polynomial to a quadratic curve" ); + public static explicit operator BezierCubic1D( Polynomial poly ) => new(CharMatrix.cubicBezierInverse * (Matrix4x1)poly); + public static explicit operator CatRomCubic1D( Polynomial poly ) => new(CharMatrix.cubicCatmullRomInverse * (Matrix4x1)poly); + public static explicit operator HermiteCubic1D( Polynomial poly ) => new(CharMatrix.cubicHermiteInverse * (Matrix4x1)poly); + public static explicit operator UBSCubic1D( Polynomial poly ) => new(CharMatrix.cubicUniformBsplineInverse * (Matrix4x1)poly); + + #endregion + } } \ No newline at end of file diff --git a/Curves/Polynomial2D.cs b/Curves/Polynomial2D.cs index 9342b75..abca150 100644 --- a/Curves/Polynomial2D.cs +++ b/Curves/Polynomial2D.cs @@ -69,34 +69,6 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2 ) { return ( new Polynomial2D( xPre, yPre ), new Polynomial2D( xPost, yPost ) ); } - #region Polynomial to spline converters - - /// Returns the cubic bezier control points for the unit interval of this curve - public BezierCubic2D ToBezier() { - Vector2Matrix4x1 p = CharMatrix.cubicBezierInverse * new Vector2Matrix4x1( C0, C1, C2, C3 ); - return new BezierCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// Returns the cubic catmull-rom control points for the unit interval of this curve - public CatRomCubic2D ToCatmullRom() { - Vector2Matrix4x1 p = CharMatrix.cubicCatmullRomInverse * new Vector2Matrix4x1( C0, C1, C2, C3 ); - return new CatRomCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// Returns the cubic hermite control points for the unit interval of this curve - public HermiteCubic2D ToHermite() { - Vector2Matrix4x1 p = CharMatrix.cubicHermiteInverse * new Vector2Matrix4x1( C0, C1, C2, C3 ); - return new HermiteCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// Returns the cubic b-spline control points for the unit interval of this curve - public UBSCubic2D ToBSpline() { - Vector2Matrix4x1 p = CharMatrix.cubicUniformBsplineInverse * new Vector2Matrix4x1( C0, C1, C2, C3 ); - return new UBSCubic2D( p.m0, p.m1, p.m2, p.m3 ); - } - - #endregion - #region IParamCurve3Diff interface implementations public int Degree => Mathf.Max( (int)x.Degree, (int)y.Degree ); @@ -320,6 +292,23 @@ public static Polynomial2D Rotate( Polynomial2D poly, float angle ) => poly.C2.Rotate( angle ), poly.C3.Rotate( angle ) ); + + #region Typecasting & Operators + + public static Polynomial2D operator /( Polynomial2D p, float v ) => new(p.C0 / v, p.C1 / v, p.C2 / v, p.C3 / v); + public static Polynomial2D operator *( Polynomial2D p, float v ) => new(p.C0 * v, p.C1 * v, p.C2 * v, p.C3 * v); + public static Polynomial2D operator *( float v, Polynomial2D p ) => p * v; + + public static explicit operator Vector2Matrix3x1( Polynomial2D poly ) => new(poly.C0, poly.C1, poly.C2); + public static explicit operator Vector2Matrix4x1( Polynomial2D poly ) => new(poly.C0, poly.C1, poly.C2, poly.C3); + public static explicit operator BezierQuad2D( Polynomial2D poly ) => poly.Degree < 3 ? new BezierQuad2D( CharMatrix.quadraticBezierInverse * (Vector2Matrix3x1)poly ) : throw new InvalidCastException( "Cannot cast a cubic polynomial to a quadratic curve" ); + public static explicit operator BezierCubic2D( Polynomial2D poly ) => new(CharMatrix.cubicBezierInverse * (Vector2Matrix4x1)poly); + public static explicit operator CatRomCubic2D( Polynomial2D poly ) => new(CharMatrix.cubicCatmullRomInverse * (Vector2Matrix4x1)poly); + public static explicit operator HermiteCubic2D( Polynomial2D poly ) => new(CharMatrix.cubicHermiteInverse * (Vector2Matrix4x1)poly); + public static explicit operator UBSCubic2D( Polynomial2D poly ) => new(CharMatrix.cubicUniformBsplineInverse * (Vector2Matrix4x1)poly); + + #endregion + } } \ No newline at end of file diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs index bc14193..30eb9a2 100644 --- a/Curves/Polynomial3D.cs +++ b/Curves/Polynomial3D.cs @@ -72,34 +72,6 @@ public Polynomial3D( Vector3 c0, Vector3 c1, Vector3 c2 ) { return ( new Polynomial3D( xPre, yPre, zPre ), new Polynomial3D( xPost, yPost, zPost ) ); } - #region Polynomial to spline converters - - /// - public BezierCubic3D ToBezier() { - Vector3Matrix4x1 p = CharMatrix.cubicBezierInverse * new Vector3Matrix4x1( C0, C1, C2, C3 ); - return new BezierCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// - public CatRomCubic3D ToCatmullRom() { - Vector3Matrix4x1 p = CharMatrix.cubicCatmullRomInverse * new Vector3Matrix4x1( C0, C1, C2, C3 ); - return new CatRomCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// - public HermiteCubic3D ToHermite() { - Vector3Matrix4x1 p = CharMatrix.cubicHermiteInverse * new Vector3Matrix4x1( C0, C1, C2, C3 ); - return new HermiteCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// - public UBSCubic3D ToBSpline() { - Vector3Matrix4x1 p = CharMatrix.cubicUniformBsplineInverse * new Vector3Matrix4x1( C0, C1, C2, C3 ); - return new UBSCubic3D( p.m0, p.m1, p.m2, p.m3 ); - } - - #endregion - #region IParamCurve3Diff interface implementations public int Degree => Mathf.Max( (int)x.Degree, (int)y.Degree, (int)z.Degree ); @@ -190,6 +162,22 @@ void Refine( ref PointProjectSample smp ) { #endregion + #region Typecasting & Operators + + public static Polynomial3D operator /( Polynomial3D p, float v ) => new(p.C0 / v, p.C1 / v, p.C2 / v, p.C3 / v); + public static Polynomial3D operator *( Polynomial3D p, float v ) => new(p.C0 * v, p.C1 * v, p.C2 * v, p.C3 * v); + public static Polynomial3D operator *( float v, Polynomial3D p ) => p * v; + + public static explicit operator Vector3Matrix3x1( Polynomial3D poly ) => new(poly.C0, poly.C1, poly.C2); + public static explicit operator Vector3Matrix4x1( Polynomial3D poly ) => new(poly.C0, poly.C1, poly.C2, poly.C3); + public static explicit operator BezierQuad3D( Polynomial3D poly ) => poly.Degree < 3 ? new BezierQuad3D( CharMatrix.quadraticBezierInverse * (Vector3Matrix3x1)poly ) : throw new InvalidCastException( "Cannot cast a cubic polynomial to a quadratic curve" ); + public static explicit operator BezierCubic3D( Polynomial3D poly ) => new(CharMatrix.cubicBezierInverse * (Vector3Matrix4x1)poly); + public static explicit operator CatRomCubic3D( Polynomial3D poly ) => new(CharMatrix.cubicCatmullRomInverse * (Vector3Matrix4x1)poly); + public static explicit operator HermiteCubic3D( Polynomial3D poly ) => new(CharMatrix.cubicHermiteInverse * (Vector3Matrix4x1)poly); + public static explicit operator UBSCubic3D( Polynomial3D poly ) => new(CharMatrix.cubicUniformBsplineInverse * (Vector3Matrix4x1)poly); + + #endregion + } } \ No newline at end of file diff --git a/Curves/Polynomial4D.cs b/Curves/Polynomial4D.cs index 757e88e..7a4f1f5 100644 --- a/Curves/Polynomial4D.cs +++ b/Curves/Polynomial4D.cs @@ -76,34 +76,6 @@ public Polynomial4D( Vector4 c0, Vector4 c1, Vector4 c2 ) { return ( new Polynomial4D( xPre, yPre, zPre, wPre ), new Polynomial4D( xPost, yPost, zPost, wPost ) ); } - #region Polynomial to spline converters - - /// - public BezierCubic4D ToBezier() { - Vector4Matrix4x1 p = CharMatrix.cubicBezierInverse * new Vector4Matrix4x1( C0, C1, C2, C3 ); - return new BezierCubic4D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// - public CatRomCubic4D ToCatmullRom() { - Vector4Matrix4x1 p = CharMatrix.cubicCatmullRomInverse * new Vector4Matrix4x1( C0, C1, C2, C3 ); - return new CatRomCubic4D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// - public HermiteCubic4D ToHermite() { - Vector4Matrix4x1 p = CharMatrix.cubicHermiteInverse * new Vector4Matrix4x1( C0, C1, C2, C3 ); - return new HermiteCubic4D( p.m0, p.m1, p.m2, p.m3 ); - } - - /// - public UBSCubic4D ToBSpline() { - Vector4Matrix4x1 p = CharMatrix.cubicUniformBsplineInverse * new Vector4Matrix4x1( C0, C1, C2, C3 ); - return new UBSCubic4D( p.m0, p.m1, p.m2, p.m3 ); - } - - #endregion - #region IParamCurve3Diff interface implementations public int Degree => Mathf.Max( x.Degree, y.Degree, z.Degree, w.Degree ); @@ -194,6 +166,22 @@ void Refine( ref PointProjectSample smp ) { #endregion + #region Typecasting & Operators + + public static Polynomial4D operator /( Polynomial4D p, float v ) => new(p.C0 / v, p.C1 / v, p.C2 / v, p.C3 / v); + public static Polynomial4D operator *( Polynomial4D p, float v ) => new(p.C0 * v, p.C1 * v, p.C2 * v, p.C3 * v); + public static Polynomial4D operator *( float v, Polynomial4D p ) => p * v; + + public static explicit operator Vector4Matrix3x1( Polynomial4D poly ) => new(poly.C0, poly.C1, poly.C2); + public static explicit operator Vector4Matrix4x1( Polynomial4D poly ) => new(poly.C0, poly.C1, poly.C2, poly.C3); + public static explicit operator BezierQuad4D( Polynomial4D poly ) => poly.Degree < 3 ? new BezierQuad4D( CharMatrix.quadraticBezierInverse * (Vector4Matrix3x1)poly ) : throw new InvalidCastException( "Cannot cast a cubic polynomial to a quadratic curve" ); + public static explicit operator BezierCubic4D( Polynomial4D poly ) => new(CharMatrix.cubicBezierInverse * (Vector4Matrix4x1)poly); + public static explicit operator CatRomCubic4D( Polynomial4D poly ) => new(CharMatrix.cubicCatmullRomInverse * (Vector4Matrix4x1)poly); + public static explicit operator HermiteCubic4D( Polynomial4D poly ) => new(CharMatrix.cubicHermiteInverse * (Vector4Matrix4x1)poly); + public static explicit operator UBSCubic4D( Polynomial4D poly ) => new(CharMatrix.cubicUniformBsplineInverse * (Vector4Matrix4x1)poly); + + #endregion + } } \ No newline at end of file From 486a7bcb084f8c2c2e8b2d1075273ae18548c575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 16 Jun 2022 13:21:46 +0200 Subject: [PATCH 104/301] quad bezier char matrix inverse --- Splines/CharMatrix.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Splines/CharMatrix.cs b/Splines/CharMatrix.cs index abf6fba..578804b 100644 --- a/Splines/CharMatrix.cs +++ b/Splines/CharMatrix.cs @@ -46,6 +46,9 @@ public static class CharMatrix { -1, 3, -3, 1 ) / 6; + /// The inverse characteristic matrix of a quadratic bézier curve + public static readonly RationalMatrix3x3 quadraticBezierInverse = quadraticBezier.Inverse; + /// The inverse characteristic matrix of a cubic bézier curve public static readonly RationalMatrix4x4 cubicBezierInverse = cubicBezier.Inverse; From 1f74ee3c3351784f72cc9c157f501e99bd23e811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 16 Jun 2022 13:22:48 +0200 Subject: [PATCH 105/301] ratmat3x3 op * vec4mat3x1 --- Numerics/RationalMatrix3x3.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Numerics/RationalMatrix3x3.cs b/Numerics/RationalMatrix3x3.cs index 066b112..4308f70 100644 --- a/Numerics/RationalMatrix3x3.cs +++ b/Numerics/RationalMatrix3x3.cs @@ -105,6 +105,9 @@ public Rational Determinant { /// public static Vector3Matrix3x1 operator *( RationalMatrix3x3 c, Vector3Matrix3x1 m ) => new(c * m.X, c * m.Y, c * m.Z); + + /// + public static Vector4Matrix3x1 operator *( RationalMatrix3x3 c, Vector4Matrix3x1 m ) => new(c * m.X, c * m.Y, c * m.Z, c * m.W); } From 5d6505b369c8c49edb1c6b46a5c7676aeb6d63f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 23 Jun 2022 02:01:17 +0200 Subject: [PATCH 106/301] MirrorAround vector extension methods --- Extensions.cs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index f9be435..29f742a 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -115,6 +115,34 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// Equivalent to (target-v).normalized or v.To(target).normalized [MethodImpl( INLINE )] public static Vector3 DirTo( this Vector3 v, Vector3 target ) => ( target - v ).normalized; + /// Mirrors this vector around another point. Equivalent to rotating the vector 180° around the point + /// The point to mirror + /// The point to mirror around + [MethodImpl( INLINE )] public static Vector2 MirrorAround( this Vector2 p, Vector2 pivot ) => new(2 * pivot.x - p.x, 2 * pivot.y - p.y); + + /// Mirrors this vector around an x coordinate + /// The point to mirror + /// The x coordinate to mirror around + [MethodImpl( INLINE )] public static Vector2 MirrorAroundX( this Vector2 p, float xPivot ) => new(2 * xPivot - p.x, p.y ); + + /// Mirrors this vector around a y coordinate + /// The point to mirror + /// The y coordinate to mirror around + [MethodImpl( INLINE )] public static Vector2 MirrorAroundY( this Vector2 p, float yPivot ) => new(p.x, 2 * yPivot - p.y ); + + /// + [MethodImpl( INLINE )] public static Vector3 MirrorAroundX( this Vector3 p, float xPivot ) => new(2 * xPivot - p.x, p.y, p.z ); + + /// + [MethodImpl( INLINE )] public static Vector3 MirrorAroundY( this Vector3 p, float yPivot ) => new(p.x, 2 * yPivot - p.y, p.z ); + + /// Mirrors this vector around a y coordinate + /// The point to mirror + /// The z coordinate to mirror around + [MethodImpl( INLINE )] public static Vector3 MirrorAroundZ( this Vector3 p, float zPivot ) => new(p.x, p.y, 2 * zPivot - p.z ); + + /// + [MethodImpl( INLINE )] public static Vector3 MirrorAround( this Vector3 p, Vector3 pivot ) => new(2 * pivot.x - p.x, 2 * pivot.y - p.y, 2 * pivot.z - p.z); #endregion #region Color manipulation From a22c6b5231ab6aa9c975683ad25243e25827b714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 23 Jun 2022 02:01:28 +0200 Subject: [PATCH 107/301] eerp/inverseEerp float extensions --- Extensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index 29f742a..04fc95c 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -604,6 +604,12 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => /// [MethodImpl( INLINE )] public static Vector3 Remap( this Vector3 iPos, Bounds iBounds, Bounds oBounds ) => Mathfs.Remap( iBounds.min, iBounds.max, oBounds.min, oBounds.max, iPos ); + /// + [MethodImpl( INLINE )] public static float Eerp( this float t, float a, float b ) => Mathfs.Eerp( a, b, t ); + + /// + [MethodImpl( INLINE )] public static float InverseEerp( this float v, float a, float b ) => Mathfs.InverseEerp( a, b, v ); + #endregion #region Vector Math From 14d2c77c54a17c5bf0c24e6487842179ebdfd2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 30 Jun 2022 17:42:55 +0200 Subject: [PATCH 108/301] added Rational to int explicit cast --- Numerics/Rational.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Numerics/Rational.cs b/Numerics/Rational.cs index 575abed..f370ea3 100644 --- a/Numerics/Rational.cs +++ b/Numerics/Rational.cs @@ -35,7 +35,7 @@ public Rational( int num, int den ) { ( n, d ) = ( 0, 1 ); break; } - + // ensure only the numerator carries the sign int sign = Mathfs.Sign( den ); n = sign * num; @@ -81,6 +81,7 @@ public Rational Pow( int pow ) => // type casting public static implicit operator Rational( int n ) => new(n, 1); + public static explicit operator int( Rational r ) => r.IsInteger ? r.n : throw new ArithmeticException( $"Rational value {r} can't be cast to an integer" ); public static explicit operator float( Rational r ) => (float)r.n / r.d; public static explicit operator double( Rational r ) => (double)r.n / r.d; From 28856835ec5c0d3e80519f74ff375f46766bd853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 9 Jul 2022 20:38:58 +0200 Subject: [PATCH 109/301] Mathfs.bools array --- Mathfs.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mathfs.cs b/Mathfs.cs index a22b052..14f08c8 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -17,6 +17,8 @@ public static class Mathfs { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + public static readonly bool[] bools = { false, true }; + #region Constants /// The circle constant. Defined as the circumference of a circle divided by its radius. Equivalent to 2*pi From 3d5b2dc58a9aaf2d1932a44b0e853c4313fd331e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 9 Jul 2022 20:50:07 +0200 Subject: [PATCH 110/301] added midpoint rounding parameter to Round functions --- Mathfs.cs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Mathfs.cs b/Mathfs.cs index 14f08c8..6cdd92e 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -540,37 +540,37 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => [MethodImpl( INLINE )] public static Vector3Int CeilToInt( Vector3 value ) => new Vector3Int( (int)Math.Ceiling( value.x ), (int)Math.Ceiling( value.y ), (int)Math.Ceiling( value.z ) ); /// Rounds the value to the nearest integer - [MethodImpl( INLINE )] public static float Round( float value ) => (float)Math.Round( value ); + [MethodImpl( INLINE )] public static float Round( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)MathF.Round( value, midpointRounding ); /// Rounds the vector components to the nearest integer - [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value ) => new Vector2( (float)Math.Round( value.x ), (float)Math.Round( value.y ) ); + [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value ) => new Vector3( (float)Math.Round( value.x ), (float)Math.Round( value.y ), (float)Math.Round( value.z ) ); + /// + [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ), (float)Math.Round( value.z, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value ) => new Vector4( (float)Math.Round( value.x ), (float)Math.Round( value.y ), (float)Math.Round( value.z ), (float)Math.Round( value.w ) ); + /// + [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ), (float)Math.Round( value.z, midpointRounding ), (float)Math.Round( value.w, midpointRounding ) ); /// Rounds the value to the nearest value, snapped to the given interval size - [MethodImpl( INLINE )] public static float Round( float value, float snapInterval ) => Mathf.Round( value / snapInterval ) * snapInterval; + [MethodImpl( INLINE )] public static float Round( float value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)Math.Round( value / snapInterval, midpointRounding ) * snapInterval; /// Rounds the vector components to the nearest value, snapped to the given interval size - [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, float snapInterval ) => new Vector2( Round( value.x, snapInterval ), Round( value.y, snapInterval ) ); + [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, float snapInterval ) => new Vector3( Round( value.x, snapInterval ), Round( value.y, snapInterval ), Round( value.z, snapInterval ) ); + /// + [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, float snapInterval ) => new Vector4( Round( value.x, snapInterval ), Round( value.y, snapInterval ), Round( value.z, snapInterval ), Round( value.w, snapInterval ) ); + /// + [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ), Round( value.w, snapInterval, midpointRounding ) ); /// Rounds the value to the nearest integer, returning an int value - [MethodImpl( INLINE )] public static int RoundToInt( float value ) => (int)Math.Round( value ); + [MethodImpl( INLINE )] public static int RoundToInt( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (int)Math.Round( value, midpointRounding ); /// Rounds the vector components to the nearest integer, returning an integer vector - [MethodImpl( INLINE )] public static Vector2Int RoundToInt( Vector2 value ) => new Vector2Int( (int)Math.Round( value.x ), (int)Math.Round( value.y ) ); + [MethodImpl( INLINE )] public static Vector2Int RoundToInt( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector3Int RoundToInt( Vector3 value ) => new Vector3Int( (int)Math.Round( value.x ), (int)Math.Round( value.y ), (int)Math.Round( value.z ) ); + /// + [MethodImpl( INLINE )] public static Vector3Int RoundToInt( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ), (int)Math.Round( value.z, midpointRounding ) ); #endregion From 9bf78875c30688e69400edc987cb4c5639502ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 9 Jul 2022 20:50:14 +0200 Subject: [PATCH 111/301] optimized Eerp --- Extensions.cs | 44 ++++++++++++++++++++++---------------------- Mathfs.cs | 7 ++++++- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/Extensions.cs b/Extensions.cs index 04fc95c..3c256f2 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -493,38 +493,38 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => /// [MethodImpl( INLINE )] public static Vector3Int CeilToInt( this Vector3 value ) => Mathfs.CeilToInt( value ); - /// - [MethodImpl( INLINE )] public static float Round( this float value ) => Mathfs.Round( value ); + /// + [MethodImpl( INLINE )] public static float Round( this float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector2 Round( this Vector2 value ) => Mathfs.Round( value ); + /// + [MethodImpl( INLINE )] public static Vector2 Round( this Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector3 Round( this Vector3 value ) => Mathfs.Round( value ); + /// + [MethodImpl( INLINE )] public static Vector3 Round( this Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector4 Round( this Vector4 value ) => Mathfs.Round( value ); + /// + [MethodImpl( INLINE )] public static Vector4 Round( this Vector4 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static float Round( this float value, float snapInterval ) => Mathfs.Round( value, snapInterval ); + /// + [MethodImpl( INLINE )] public static float Round( this float value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector2 Round( this Vector2 value, float snapInterval ) => Mathfs.Round( value, snapInterval ); + /// + [MethodImpl( INLINE )] public static Vector2 Round( this Vector2 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector3 Round( this Vector3 value, float snapInterval ) => Mathfs.Round( value, snapInterval ); + /// + [MethodImpl( INLINE )] public static Vector3 Round( this Vector3 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector4 Round( this Vector4 value, float snapInterval ) => Mathfs.Round( value, snapInterval ); + /// + [MethodImpl( INLINE )] public static Vector4 Round( this Vector4 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); - /// - [MethodImpl( INLINE )] public static int RoundToInt( this float value ) => Mathfs.RoundToInt( value ); + /// + [MethodImpl( INLINE )] public static int RoundToInt( this float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector2Int RoundToInt( this Vector2 value ) => Mathfs.RoundToInt( value ); + /// + [MethodImpl( INLINE )] public static Vector2Int RoundToInt( this Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector3Int RoundToInt( this Vector3 value ) => Mathfs.RoundToInt( value ); + /// + [MethodImpl( INLINE )] public static Vector3Int RoundToInt( this Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); #endregion diff --git a/Mathfs.cs b/Mathfs.cs index 6cdd92e..6734441 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -784,7 +784,12 @@ public static Rect Lerp( Rect a, Rect b, float t ) { /// The start value /// The end value /// The t-value from 0 to 1 representing position along the eerp - [MethodImpl( INLINE )] public static float Eerp( float a, float b, float t ) => Mathf.Pow( a, 1 - t ) * Mathf.Pow( b, t ); + [MethodImpl( INLINE )] public static float Eerp( float a, float b, float t ) => + t switch { + 0f => a, + 1f => b, + _ => Mathf.Pow( a, 1 - t ) * Mathf.Pow( b, t ) + }; /// Inverse exponential interpolation, the multiplicative version of InverseLerp, useful for values such as scaling or zooming /// The start value From 4a0718576b9b6fbd6e5724a4a1d6a1f6c20c4eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 9 Jul 2022 20:51:31 +0200 Subject: [PATCH 112/301] formatting --- Extensions.cs | 20 ++++++++++---------- Numerics/FloatRange.cs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Extensions.cs b/Extensions.cs index 3c256f2..1c1b149 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -119,27 +119,27 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// The point to mirror /// The point to mirror around [MethodImpl( INLINE )] public static Vector2 MirrorAround( this Vector2 p, Vector2 pivot ) => new(2 * pivot.x - p.x, 2 * pivot.y - p.y); - + /// Mirrors this vector around an x coordinate /// The point to mirror /// The x coordinate to mirror around - [MethodImpl( INLINE )] public static Vector2 MirrorAroundX( this Vector2 p, float xPivot ) => new(2 * xPivot - p.x, p.y ); - + [MethodImpl( INLINE )] public static Vector2 MirrorAroundX( this Vector2 p, float xPivot ) => new(2 * xPivot - p.x, p.y); + /// Mirrors this vector around a y coordinate /// The point to mirror /// The y coordinate to mirror around - [MethodImpl( INLINE )] public static Vector2 MirrorAroundY( this Vector2 p, float yPivot ) => new(p.x, 2 * yPivot - p.y ); - + [MethodImpl( INLINE )] public static Vector2 MirrorAroundY( this Vector2 p, float yPivot ) => new(p.x, 2 * yPivot - p.y); + /// - [MethodImpl( INLINE )] public static Vector3 MirrorAroundX( this Vector3 p, float xPivot ) => new(2 * xPivot - p.x, p.y, p.z ); - + [MethodImpl( INLINE )] public static Vector3 MirrorAroundX( this Vector3 p, float xPivot ) => new(2 * xPivot - p.x, p.y, p.z); + /// - [MethodImpl( INLINE )] public static Vector3 MirrorAroundY( this Vector3 p, float yPivot ) => new(p.x, 2 * yPivot - p.y, p.z ); - + [MethodImpl( INLINE )] public static Vector3 MirrorAroundY( this Vector3 p, float yPivot ) => new(p.x, 2 * yPivot - p.y, p.z); + /// Mirrors this vector around a y coordinate /// The point to mirror /// The z coordinate to mirror around - [MethodImpl( INLINE )] public static Vector3 MirrorAroundZ( this Vector3 p, float zPivot ) => new(p.x, p.y, 2 * zPivot - p.z ); + [MethodImpl( INLINE )] public static Vector3 MirrorAroundZ( this Vector3 p, float zPivot ) => new(p.x, p.y, 2 * zPivot - p.z); /// [MethodImpl( INLINE )] public static Vector3 MirrorAround( this Vector3 p, Vector3 pivot ) => new(2 * pivot.x - p.x, 2 * pivot.y - p.y, 2 * pivot.z - p.z); diff --git a/Numerics/FloatRange.cs b/Numerics/FloatRange.cs index 4227fec..bb16dd3 100644 --- a/Numerics/FloatRange.cs +++ b/Numerics/FloatRange.cs @@ -48,7 +48,7 @@ public readonly struct FloatRange { /// Returns whether or not this range contains the value v /// The value to see if it's inside public bool Contains( float v ) => v >= Min && v <= Max; - + /// Returns whether or not this range contains the range r /// The range to see if it's inside public bool Contains( FloatRange r ) => r.Min >= Min && r.Max <= Max; From ec07cfb0d11156afd47926e8573da32b53c9dbe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 9 Jul 2022 20:52:34 +0200 Subject: [PATCH 113/301] added int Random.Range, upd float random doc --- Random.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Random.cs b/Random.cs index 93368a9..b2f05bf 100644 --- a/Random.cs +++ b/Random.cs @@ -20,9 +20,14 @@ public static class Random { public static float Direction1D => Sign; /// Randomly returns a value between min [inclusive] and max [inclusive] - /// The minimum value - /// The maximum value + /// The minimum value [inclusive] + /// The maximum value [inclusive] public static float Range( float min, float max ) => UnityRandom.Range( min, max ); + + /// Randomly returns a value between min [inclusive] and max [exclusive] + /// The minimum value [inclusive] + /// The maximum value [exclusive] + public static int Range( int min, int max ) => UnityRandom.Range( min, max ); // 2D /// Returns a random point on the unit circle From 47ba394d22e4d9e15bd07ce030e628b863af7d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 9 Jul 2022 20:53:06 +0200 Subject: [PATCH 114/301] added FloatRange.ScaleFromStart --- Numerics/FloatRange.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Numerics/FloatRange.cs b/Numerics/FloatRange.cs index bb16dd3..c5bae98 100644 --- a/Numerics/FloatRange.cs +++ b/Numerics/FloatRange.cs @@ -75,6 +75,10 @@ public FloatRange Encapsulate( float value ) => _ => ( Mathfs.Min( b, value ), Mathfs.Max( a, value ) ) // reversed - b is min, a is max }; + /// Returns a version of this range, scaled around its start value + /// The value to scale the range by + public FloatRange ScaleFromStart( float scale ) => new FloatRange( a, a + scale * ( b - a ) ); + /// Returns the rectangle encapsulating the region defined by a range per axis. Note: The direction of each range is ignored /// The range of the X axis /// The range of the Y axis From a9132f195ad84a0e2d821657f6064e158371aeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 9 Jul 2022 20:53:52 +0200 Subject: [PATCH 115/301] added Rect.Lerp(Vector2) --- Extensions.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index 1c1b149..b8e4f11 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -182,6 +182,15 @@ public static Rect Encapsulate( this Rect r, Vector2 p ) { return r; } + /// Interpolates a position within this rectangle, given a normalized position + /// The rectangle to get a position within + /// The normalized position within this rectangle + public static Vector2 Lerp( this Rect r, Vector2 tPos ) => + new( + Mathfs.Lerp( r.xMin, r.xMax, tPos.x ), + Mathfs.Lerp( r.yMin, r.yMax, tPos.y ) + ); + #endregion #region Simple float and int operations From 2395412fcd0b1353261dafb2399bbce771e3020b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 9 Jul 2022 20:56:19 +0200 Subject: [PATCH 116/301] added polygon clipping also updated polygon a little bit --- Geometric Shapes/Polygon.cs | 43 +++++++- Geometric Shapes/PolygonClipper.cs | 158 +++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 Geometric Shapes/PolygonClipper.cs diff --git a/Geometric Shapes/Polygon.cs b/Geometric Shapes/Polygon.cs index f7bbb26..7ec1ecd 100644 --- a/Geometric Shapes/Polygon.cs +++ b/Geometric Shapes/Polygon.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using System.Collections.Generic; using UnityEngine; using static Freya.Mathfs; @@ -7,7 +8,7 @@ namespace Freya { /// Polygon with various math functions to test if a point is inside, calculate area, etc. - public struct Polygon { + public class Polygon { /// The points in this polygon public IReadOnlyList points; @@ -16,6 +17,13 @@ public struct Polygon { /// The points in the polygon public Polygon( IReadOnlyList points ) => this.points = points; + /// Get a point by index. Indices cannot be out of range, as they will wrap/cycle in the polygon + /// The index of the point + public Vector2 this[ int i ] => points[i.Mod( Count )]; + + /// The number of points in this polygon + public int Count => points.Count; + /// Returns whether or not this polygon is defined clockwise public bool IsClockwise => SignedArea > 0; @@ -79,7 +87,7 @@ public Rect Bounds { // modified version of the code from here: // http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm // Copyright 2000 softSurfer, 2012 Dan Sunday. This code may be freely used and modified for any purpose providing that this copyright notice is included with it. SoftSurfer makes no warranty for this code, and cannot be held liable for any real or imagined damage resulting from its use. Users of this code must verify correctness for their application. - /// Returns the winding number for this polygon, given a point + /// Returns the winding number for this polygon, around a given point /// The point to check winding around public int WindingNumber( Vector2 point ) { int winding = 0; @@ -99,6 +107,37 @@ public int WindingNumber( Vector2 point ) { return winding; } + + /// Returns the resulting polygons when clipping this polygon by a line + /// The line/plane to clip by. Points on its left side will be kept + /// The resulting array of clipped polygons (if any) + public PolygonClipper.ResultState Clip( Line2D line, out List clippedPolygons ) => PolygonClipper.Clip( this, line, out clippedPolygons ); + + public Polygon GetMiterPolygon( float offset ) { + List miterPts = new List(); + + Line2D GetMiterLine( int i ) { + Vector2 tangent = ( this[i + 1] - this[i] ).normalized; + Vector2 normal = tangent.Rotate90CCW(); + return new Line2D( this[i] + normal * offset, tangent ); + } + + // Line2D prev = GetMiterLine( -1 ); + for( int i = 0; i < Count; i++ ) { + Line2D line = GetMiterLine( i ); + Line2D line2 = GetMiterLine( i + 1 ); + if( line.Intersect( line2, out Vector2 pt ) ) + miterPts.Add( pt ); + else { + Debug.LogError( $"{line.origin},{line.dir}\n{line2.origin},{line2.dir}\nPoints:{string.Join( '\n', points )}" ); + throw new Exception( "Line intersection failed" ); + } + // prev = line; + } + + return new Polygon( miterPts ); + } + } } \ No newline at end of file diff --git a/Geometric Shapes/PolygonClipper.cs b/Geometric Shapes/PolygonClipper.cs new file mode 100644 index 0000000..d9debb4 --- /dev/null +++ b/Geometric Shapes/PolygonClipper.cs @@ -0,0 +1,158 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Freya { + + /// Utility type to clip polygons + public static class PolygonClipper { + + enum PointSideState { + Discard = -1, + Edge = 0, + Keep = 1, + Handled = 2 + } + + public enum ResultState { + OriginalLeftIntact, + Clipped, + FullyDiscarded + } + + public class PolygonSection : IComparable { + public FloatRange tRange; + public List points; + public PolygonSection( FloatRange tRange, List points ) => ( this.tRange, this.points ) = ( tRange, points ); + public int CompareTo( PolygonSection other ) => tRange.Min.CompareTo( other.tRange.Min ); + } + + static List states = new List(); + + public static ResultState Clip( Polygon poly, Line2D line, out List clippedPolygons ) { + states.Clear(); + + // first, figure out which side all points are on + bool hasDiscards = false; + int startIndex = -1; + for( int i = 0; i < poly.Count; i++ ) { + float sd = line.SignedDistance( poly[i] ); + if( Mathfs.Approximately( sd, 0 ) ) + states.Add( PointSideState.Edge ); + else if( sd > 0 ) { + if( startIndex == -1 ) + startIndex = i; + states.Add( PointSideState.Keep ); + } else { + hasDiscards = true; + states.Add( PointSideState.Discard ); + } + } + + if( hasDiscards == false ) { + clippedPolygons = null; + return ResultState.OriginalLeftIntact; + } + + if( startIndex == -1 ) { + clippedPolygons = null; + return ResultState.FullyDiscarded; + } + + // find keep points, spread outwards until it's cut off from the rest + SortedSet sections = new SortedSet(); + for( int i = 0; i < poly.Count; i++ ) { + if( states[i] == PointSideState.Keep ) { + sections.Add( ExtractPolygonSection( poly, line, i ) ); + } + } + + // combine all clipped polygonal regions + clippedPolygons = new List(); + while( sections.Count > 0 ) { + // find solid polygon + PolygonSection solid = sections.First(); + sections.Remove( solid ); + int solidDir = solid.tRange.Direction; + + // find holes in that polygon + float referencePoint = solid.tRange.Min; + while( true ) { // should break early anyway + FloatRange checkRange = new FloatRange( referencePoint, solid.tRange.Max ); + PolygonSection hole = sections.FirstOrDefault( s => s.tRange.Direction != solidDir && checkRange.Contains( s.tRange ) ); + if( hole == null ) { + // nothing inside - we're done with this solid + clippedPolygons.Add( new Polygon( solid.points ) ); + break; + } else { + // append the hole polygon to the solid points + sections.Remove( hole ); + if( solidDir == 1 ) + solid.points.InsertRange( 0, hole.points ); + else + solid.points.AddRange( hole.points ); + + referencePoint = hole.tRange.Max; // skip everything inside the hole by shifting forward + } + } + } + + return ResultState.Clipped; + } + + static PolygonSection ExtractPolygonSection( Polygon poly, Line2D line, int sourceIndex ) { + List points = new List(); + + void AddBack( int i ) { + states[i.Mod( states.Count )] = PointSideState.Handled; + points.Insert( 0, poly[i] ); + } + + void AddFront( int i ) { + states[i.Mod( states.Count )] = PointSideState.Handled; + points.Add( poly[i] ); + } + + AddFront( sourceIndex ); + + float tStart = 0, tEnd = 0; + for( int dir = -1; dir <= 1; dir += 2 ) { + for( int i = 1; i < poly.Count; i++ ) { + int index = sourceIndex + dir * i; + if( states[index.Mod( states.Count )] is PointSideState.Discard or PointSideState.Edge ) { + // hit the front edge + Line2D edge = new Line2D( poly[index - dir], poly[index] - poly[index - dir] ); + if( IntersectionTest.LinearTValues( line, edge, out float tLine, out float tEdge ) ) { + Vector2 intPt = edge.GetPoint( tEdge ); + if( dir == 1 ) { + tEnd = tLine; + points.Add( intPt ); + } else { + tStart = tLine; + points.Insert( 0, intPt ); + } + + break; + } + + throw new Exception( "Polygon clipping failed due to line intersection not working as expected. You may have duplicate points or a degenerate polygon in general" ); + } + + // haven't hit the end yet, add current point + if( dir == 1 ) + AddFront( index ); + else + AddBack( index ); + } + } + + return new PolygonSection( ( tStart, tEnd ), points ); + } + + + } + +} \ No newline at end of file From 1e45113a413b60a36c593cfde50fd4ee7e1ce37e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 18 Jul 2022 01:59:50 +0200 Subject: [PATCH 117/301] poly3D to poly2D explicit cast --- Curves/Polynomial3D.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Curves/Polynomial3D.cs b/Curves/Polynomial3D.cs index 30eb9a2..0c81a5a 100644 --- a/Curves/Polynomial3D.cs +++ b/Curves/Polynomial3D.cs @@ -168,6 +168,7 @@ void Refine( ref PointProjectSample smp ) { public static Polynomial3D operator *( Polynomial3D p, float v ) => new(p.C0 * v, p.C1 * v, p.C2 * v, p.C3 * v); public static Polynomial3D operator *( float v, Polynomial3D p ) => p * v; + public static explicit operator Polynomial2D( Polynomial3D p ) => new(p.x, p.y); public static explicit operator Vector3Matrix3x1( Polynomial3D poly ) => new(poly.C0, poly.C1, poly.C2); public static explicit operator Vector3Matrix4x1( Polynomial3D poly ) => new(poly.C0, poly.C1, poly.C2, poly.C3); public static explicit operator BezierQuad3D( Polynomial3D poly ) => poly.Degree < 3 ? new BezierQuad3D( CharMatrix.quadraticBezierInverse * (Vector3Matrix3x1)poly ) : throw new InvalidCastException( "Cannot cast a cubic polynomial to a quadratic curve" ); @@ -177,7 +178,7 @@ void Refine( ref PointProjectSample smp ) { public static explicit operator UBSCubic3D( Polynomial3D poly ) => new(CharMatrix.cubicUniformBsplineInverse * (Vector3Matrix4x1)poly); #endregion - + } } \ No newline at end of file From 4f28750f1f63d7dd4f143c94ec130d492c3dfee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 18 Jul 2022 02:00:20 +0200 Subject: [PATCH 118/301] refactored catmull-rom splines & knot calcs --- .../NUCatRomCubic2D.cs | 93 +++++++++---------- .../NUCatRomCubic3D.cs | 75 +++++++-------- Splines/SplineUtils.cs | 50 ++++++---- 3 files changed, 113 insertions(+), 105 deletions(-) diff --git a/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs b/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs index f32d532..7e215b5 100644 --- a/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs +++ b/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs @@ -7,7 +7,7 @@ namespace Freya { /// A non-uniform cubic catmull-rom 2D curve - [Serializable] public struct NUCatRomCubic2D : IParamSplineSegment { + [Serializable] public struct NUCatRomCubic2D : IParamSplineSegment { public enum KnotCalcMode { Manual, @@ -19,6 +19,18 @@ public enum KnotCalcMode { #region Constructors + /// Creates a cubic catmull-rom curve, from 4 control points and their corresponding knot values + /// The control points of the curve + /// The knot vector of the curve + public NUCatRomCubic2D( Vector2Matrix4x1 pointMatrix, Matrix4x1 knotVector ) { + this.pointMatrix = pointMatrix; + this.knotVector = knotVector; + validCoefficients = false; + curve = default; + knotCalcMode = KnotCalcMode.Manual; + alpha = default; // unused when using manual knots + } + /// Creates a cubic catmull-rom curve, from 4 control points and their corresponding knot values /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it /// The second control point, and the start of the catrom curve @@ -28,13 +40,8 @@ public enum KnotCalcMode { /// The second knot value /// The third knot value /// The fourth knot value - public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) { - pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); - ( this.k0, this.k1, this.k2, this.k3 ) = ( k0, k1, k2, k3 ); - validCoefficients = false; - curve = default; - knotCalcMode = KnotCalcMode.Manual; - alpha = default; // unused when using manual knots + public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float k0, float k1, float k2, float k3 ) + : this( new Vector2Matrix4x1( p0, p1, p2, p3 ), new Matrix4x1( k0, k1, k2, k3 ) ) { } /// Creates a uniform cubic catmull-rom curve, from 4 control points @@ -70,9 +77,9 @@ public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, CatRomTy /// making it span the unit interval of 0 to 1 instead of using the raw knot values generated by the alpha parameterization public NUCatRomCubic2D( Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float alpha, bool parameterizeToUnitInterval = true ) { pointMatrix = new Vector2Matrix4x1( p0, p1, p2, p3 ); + knotVector = default; validCoefficients = false; curve = default; - k0 = k1 = k2 = k3 = default; knotCalcMode = parameterizeToUnitInterval ? KnotCalcMode.AutoUnitInterval : KnotCalcMode.Auto; this.alpha = alpha; } @@ -85,7 +92,15 @@ public Vector2Matrix4x1 PointMatrix { get => pointMatrix; set => _ = ( pointMatrix = value, validCoefficients = false ); } - [SerializeField] float k0, k1, k2, k3; // knot vector + [SerializeField] Matrix4x1 knotVector; + public Matrix4x1 KnotVector { + get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return knotVector; + } + set => _ = ( knotVector = value, validCoefficients = false ); + } // knot auto-calculation fields [SerializeField] KnotCalcMode knotCalcMode; // knot recalculation mode @@ -104,59 +119,43 @@ public Polynomial2D Curve { /// The first control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P0 { [MethodImpl( INLINE )] get => pointMatrix.m0; - set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m0 = value, validCoefficients = false ); } /// The second control point, and the start of the catrom curve public Vector2 P1 { [MethodImpl( INLINE )] get => pointMatrix.m1; - set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m1 = value, validCoefficients = false ); } /// The third control point, and the end of the catrom curve public Vector2 P2 { [MethodImpl( INLINE )] get => pointMatrix.m2; - set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m2 = value, validCoefficients = false ); } /// The last control point of the catrom curve. Note that this point is not included in the curve itself, and only helps to shape it public Vector2 P3 { [MethodImpl( INLINE )] get => pointMatrix.m3; - set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] set => _ = ( pointMatrix.m3 = value, validCoefficients = false ); } /// The knot value of the first control point of the catrom curve public float K0 { - [MethodImpl( INLINE )] get { - if( knotCalcMode != KnotCalcMode.Manual ) - ReadyCoefficients(); - return k0; - } - set => _ = ( k0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => KnotVector.m0; + [MethodImpl( INLINE )] set => _ = ( knotVector.m0 = value, validCoefficients = false ); } /// The knot value of the second control point, and the start of the catrom curve public float K1 { - [MethodImpl( INLINE )] get { - if( knotCalcMode != KnotCalcMode.Manual ) - ReadyCoefficients(); - return k1; - } - set => _ = ( k1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => KnotVector.m1; + [MethodImpl( INLINE )] set => _ = ( knotVector.m1 = value, validCoefficients = false ); } /// The knot value of the third control point, and the end of the catrom curve public float K2 { - [MethodImpl( INLINE )] get { - if( knotCalcMode != KnotCalcMode.Manual ) - ReadyCoefficients(); - return k2; - } - set => _ = ( k2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => KnotVector.m2; + [MethodImpl( INLINE )] set => _ = ( knotVector.m2 = value, validCoefficients = false ); } /// The knot value of the last control point of the catrom curve public float K3 { - [MethodImpl( INLINE )] get { - if( knotCalcMode != KnotCalcMode.Manual ) - ReadyCoefficients(); - return k3; - } - set => _ = ( k3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => KnotVector.m3; + [MethodImpl( INLINE )] set => _ = ( knotVector.m3 = value, validCoefficients = false ); } /// The alpha parameter, which controls how much the length of each segment should influence the shape of the curve. @@ -178,22 +177,22 @@ public float Alpha { return; // no need to update validCoefficients = true; if( knotCalcMode != KnotCalcMode.Manual ) - ( k0, k1, k2, k3 ) = SplineUtils.CalcCatRomKnots( pointMatrix, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); - curve = SplineUtils.CalculateCatRomCurve( pointMatrix, k0, k1, k2, k3 ); + KnotVector = SplineUtils.CalcCatRomKnots( pointMatrix, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); + curve = SplineUtils.CalculateCatRomCurve( pointMatrix, knotVector ); } /// Returns the weight of the given control point at the given parameter value /// The point to get the weight of /// The parameter value at which to sample the weight public float GetPointWeightAtKnotValue( int i, float u ) { - float a = Mathfs.InverseLerp( k0, k1, u ); - float b = Mathfs.InverseLerp( k1, k2, u ); - float c = Mathfs.InverseLerp( k2, k3, u ); - float d = Mathfs.InverseLerp( k0, k2, u ); - float g = Mathfs.InverseLerp( k1, k3, u ); + float a = Mathfs.InverseLerp( K0, K1, u ); + float b = Mathfs.InverseLerp( K1, K2, u ); + float c = Mathfs.InverseLerp( K2, K3, u ); + float d = Mathfs.InverseLerp( K0, K2, u ); + float g = Mathfs.InverseLerp( K1, K3, u ); switch( i ) { - case 0: return -( a - 1 ) * ( b - 1 ) * ( d - 1 ); - case 1: return ( b - 1 ) * ( a * d - a + b * ( d + g - 1 ) - d ); + case 0: return ( 1 - a ) * ( 1 - b ) * ( 1 - d ); + case 1: return ( 1 - b ) * ( a * ( 1 - d ) + b * ( 1 - d - g ) + d ); case 2: return -b * ( b * ( d + g - 1 ) + g * ( c - 1 ) - d ); case 3: return b * c * g; default: throw new IndexOutOfRangeException( $"Catrom point has to be either 0, 1, 2 or 3. Got: {i}" ); diff --git a/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs b/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs index 634bc45..f8dac96 100644 --- a/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs +++ b/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs @@ -7,7 +7,7 @@ namespace Freya { /// A non-uniform cubic catmull-rom 3D curve - [Serializable] public struct NUCatRomCubic3D : IParamSplineSegment { + [Serializable] public struct NUCatRomCubic3D : IParamSplineSegment { public enum KnotCalcMode { Manual, @@ -19,16 +19,21 @@ public enum KnotCalcMode { #region Constructors - /// - public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float k0, float k1, float k2, float k3 ) { - pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); - ( this.k0, this.k1, this.k2, this.k3 ) = ( k0, k1, k2, k3 ); + /// + public NUCatRomCubic3D( Vector3Matrix4x1 pointMatrix, Matrix4x1 knotVector ) { + this.pointMatrix = pointMatrix; + this.knotVector = knotVector; validCoefficients = false; curve = default; knotCalcMode = KnotCalcMode.Manual; alpha = default; // unused when using manual knots } + /// + public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float k0, float k1, float k2, float k3 ) + : this( new Vector3Matrix4x1( p0, p1, p2, p3 ), new Matrix4x1( k0, k1, k2, k3 ) ) { + } + /// public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) : this( p0, p1, p2, p3, -1, 0, 1, 2 ) { } @@ -43,7 +48,7 @@ public NUCatRomCubic3D( Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float al pointMatrix = new Vector3Matrix4x1( p0, p1, p2, p3 ); validCoefficients = false; curve = default; - k0 = k1 = k2 = k3 = default; + knotVector = default; knotCalcMode = parameterizeToUnitInterval ? KnotCalcMode.AutoUnitInterval : KnotCalcMode.Auto; this.alpha = alpha; } @@ -56,7 +61,15 @@ public Vector3Matrix4x1 PointMatrix { get => pointMatrix; set => _ = ( pointMatrix = value, validCoefficients = false ); } - [SerializeField] float k0, k1, k2, k3; // knot vector + [SerializeField] Matrix4x1 knotVector; + public Matrix4x1 KnotVector { + get { + if( knotCalcMode != KnotCalcMode.Manual ) + ReadyCoefficients(); + return knotVector; + } + set => _ = ( knotVector = value, validCoefficients = false ); + } // knot auto-calculation fields [SerializeField] KnotCalcMode knotCalcMode; // knot recalculation mode @@ -95,39 +108,23 @@ public Vector3 P3 { /// public float K0 { - [MethodImpl( INLINE )] get { - if( knotCalcMode != KnotCalcMode.Manual ) - ReadyCoefficients(); - return k0; - } - set => _ = ( k0 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => KnotVector.m0; + [MethodImpl( INLINE )] set => _ = ( knotVector.m0 = value, validCoefficients = false ); } /// public float K1 { - [MethodImpl( INLINE )] get { - if( knotCalcMode != KnotCalcMode.Manual ) - ReadyCoefficients(); - return k1; - } - set => _ = ( k1 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => KnotVector.m1; + [MethodImpl( INLINE )] set => _ = ( knotVector.m1 = value, validCoefficients = false ); } /// public float K2 { - [MethodImpl( INLINE )] get { - if( knotCalcMode != KnotCalcMode.Manual ) - ReadyCoefficients(); - return k2; - } - set => _ = ( k2 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => KnotVector.m2; + [MethodImpl( INLINE )] set => _ = ( knotVector.m2 = value, validCoefficients = false ); } /// public float K3 { - [MethodImpl( INLINE )] get { - if( knotCalcMode != KnotCalcMode.Manual ) - ReadyCoefficients(); - return k3; - } - set => _ = ( k3 = value, validCoefficients = false ); + [MethodImpl( INLINE )] get => KnotVector.m3; + [MethodImpl( INLINE )] set => _ = ( knotVector.m3 = value, validCoefficients = false ); } /// @@ -146,19 +143,19 @@ public float Alpha { return; // no need to update validCoefficients = true; if( knotCalcMode != KnotCalcMode.Manual ) - ( k0, k1, k2, k3 ) = SplineUtils.CalcCatRomKnots( pointMatrix, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); - curve = SplineUtils.CalculateCatRomCurve( pointMatrix, k0, k1, k2, k3 ); + KnotVector = SplineUtils.CalcCatRomKnots( pointMatrix, alpha, knotCalcMode == KnotCalcMode.AutoUnitInterval ); + curve = SplineUtils.CalculateCatRomCurve( pointMatrix, knotVector ); } /// public float GetPointWeightAtKnotValue( int i, float u ) { - float a = Mathfs.InverseLerp( k0, k1, u ); - float b = Mathfs.InverseLerp( k1, k2, u ); - float c = Mathfs.InverseLerp( k2, k3, u ); - float d = Mathfs.InverseLerp( k0, k2, u ); - float g = Mathfs.InverseLerp( k1, k3, u ); + float a = Mathfs.InverseLerp( K0, K1, u ); + float b = Mathfs.InverseLerp( K1, K2, u ); + float c = Mathfs.InverseLerp( K2, K3, u ); + float d = Mathfs.InverseLerp( K0, K2, u ); + float g = Mathfs.InverseLerp( K1, K3, u ); switch( i ) { - case 0: return -( a - 1 ) * ( b - 1 ) * ( d - 1 ); + case 0: return ( 1 - a ) * ( 1 - b ) * ( 1 - d ); case 1: return ( b - 1 ) * ( a * d - a + b * ( d + g - 1 ) - d ); case 2: return -b * ( b * ( d + g - 1 ) + g * ( c - 1 ) - d ); case 3: return b * c * g; diff --git a/Splines/SplineUtils.cs b/Splines/SplineUtils.cs index 33f4f6d..31867fc 100644 --- a/Splines/SplineUtils.cs +++ b/Splines/SplineUtils.cs @@ -30,13 +30,24 @@ public static float[] GenerateUniformKnots( int degree, int pCount, bool open ) internal static int BSplineKnotCount( int pointCount, int degree ) => degree + pointCount + 1; - public static float CalcCatRomKnot( float kPrev, float alpha, float sqDist ) { - return kPrev + sqDist.Pow( 0.5f * alpha ).AtLeast( 0.00001f ); // ensure there are no duplicate knots + public static float CalcCatRomKnot( float kPrev, float sqDist, float alpha ) { + return kPrev + CalcCatRomKnot( sqDist, alpha ).AtLeast( 0.00001f ); // ensure there are no duplicate knots } - static (float, float, float, float) GetUniformKnots( bool unitInterval ) => unitInterval ? ( -1, 0, 1, 2 ) : ( 0, 1, 2, 3 ); + public static float CalcCatRomKnot( float squaredDistance, float alpha ) => + alpha switch { + 0 => 1, // uniform + 1 => squaredDistance.Sqrt(), // chordal + 2 => squaredDistance, // centripetal + _ => squaredDistance.Pow( 0.5f * alpha ) + }; + + static readonly Matrix4x1 knotsUniformUnit = new(-1, 0, 1, 2); + static readonly Matrix4x1 knotsUniform = new(0, 1, 2, 3); - public static (float, float, float, float) CalcCatRomKnots( Vector2Matrix4x1 m, float alpha, bool unitInterval ) { + static Matrix4x1 GetUniformKnots( bool unitInterval ) => unitInterval ? knotsUniformUnit : knotsUniform; + + public static Matrix4x1 CalcCatRomKnots( Vector2Matrix4x1 m, float alpha, bool unitInterval ) { if( alpha == 0 ) // uniform catrom return GetUniformKnots( unitInterval ); float sqMag01 = Vector2.SqrMagnitude( m.m0 - m.m1 ); @@ -45,7 +56,7 @@ public static (float, float, float, float) CalcCatRomKnots( Vector2Matrix4x1 m, return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval ); } - public static (float, float, float, float) CalcCatRomKnots( Vector3Matrix4x1 m, float alpha, bool unitInterval ) { + public static Matrix4x1 CalcCatRomKnots( Vector3Matrix4x1 m, float alpha, bool unitInterval ) { if( alpha == 0 ) // uniform catrom return GetUniformKnots( unitInterval ); float sqMag01 = Vector3.SqrMagnitude( m.m0 - m.m1 ); @@ -54,16 +65,13 @@ public static (float, float, float, float) CalcCatRomKnots( Vector3Matrix4x1 m, return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval ); } - static (float, float, float, float) CalcCatRomKnots( float sqMag01, float sqMag12, float sqMag23, float alpha, bool unitInterval ) { - ( float i01, float i12, float i23 ) = alpha switch { - 0 => ( 1, 1, 1 ), // uniform - 1 => ( sqMag01.Sqrt(), sqMag12.Sqrt(), sqMag23.Sqrt() ), // chordal - 2 => ( sqMag01, sqMag12, sqMag23 ), - _ => ( sqMag01.Pow( 0.5f * alpha ), sqMag12.Pow( 0.5f * alpha ), sqMag23.Pow( 0.5f * alpha ) ) - }; + static Matrix4x1 CalcCatRomKnots( float sqMag01, float sqMag12, float sqMag23, float alpha, bool unitInterval ) { + float i01 = CalcCatRomKnot( sqMag01, alpha ); + float i12 = CalcCatRomKnot( sqMag12, alpha ); + float i23 = CalcCatRomKnot( sqMag23, alpha ); float k0, k1, k2, k3; if( unitInterval ) { - return ( -i01 / i12, 0, 1, 1 + i23 / i12 ); + return new(-i01 / i12, 0, 1, 1 + i23 / i12); } else { k0 = 0; k1 = k0 + i01; @@ -71,10 +79,14 @@ public static (float, float, float, float) CalcCatRomKnots( Vector3Matrix4x1 m, k3 = k2 + i23; } - return ( k0, k1, k2, k3 ); + return new(k0, k1, k2, k3); } - static Matrix4x4 GetNUCatRomCharMatrix( float k0, float k1, float k2, float k3 ) { + static Matrix4x4 GetNUCatRomCharMatrix( Matrix4x1 knots ) { + float k0 = knots.m0; + float k1 = knots.m1; + float k2 = knots.m2; + float k3 = knots.m3; if( k1 == 0f && k2 == 1f ) return GetNUCatRomCharMatrixUnitInterval( k0, k3 ); float k1k1 = k1 * k1; @@ -170,12 +182,12 @@ static Matrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { ); } - internal static Polynomial2D CalculateCatRomCurve( Vector2Matrix4x1 m, float k0, float k1, float k2, float k3 ) { - return new Polynomial2D( GetNUCatRomCharMatrix( k0, k1, k2, k3 ).MultiplyColumnVector( m ) ); + internal static Polynomial2D CalculateCatRomCurve( Vector2Matrix4x1 m, Matrix4x1 knots ) { + return new Polynomial2D( GetNUCatRomCharMatrix( knots ).MultiplyColumnVector( m ) ); } - internal static Polynomial3D CalculateCatRomCurve( Vector3Matrix4x1 m, float k0, float k1, float k2, float k3 ) { - return new Polynomial3D( GetNUCatRomCharMatrix( k0, k1, k2, k3 ).MultiplyColumnVector( m ) ); + internal static Polynomial3D CalculateCatRomCurve( Vector3Matrix4x1 m, Matrix4x1 knots ) { + return new Polynomial3D( GetNUCatRomCharMatrix( knots ).MultiplyColumnVector( m ) ); } } From f19f7befd17a539f7c842009673086e0c87d06c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 5 Aug 2022 23:12:23 +0200 Subject: [PATCH 119/301] added matrix lerps --- Codegen/Editor/MathfsCodegen.cs | 14 ++++++++++---- Numerics/Matrix3x1.cs | 3 +++ Numerics/Matrix4x1.cs | 3 +++ Numerics/Vector2Matrix3x1.cs | 3 +++ Numerics/Vector2Matrix4x1.cs | 3 +++ Numerics/Vector3Matrix3x1.cs | 3 +++ Numerics/Vector3Matrix4x1.cs | 3 +++ Numerics/Vector4Matrix3x1.cs | 3 +++ Numerics/Vector4Matrix4x1.cs | 3 +++ 9 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Codegen/Editor/MathfsCodegen.cs b/Codegen/Editor/MathfsCodegen.cs index 18a8dec..d673a5e 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Codegen/Editor/MathfsCodegen.cs @@ -186,8 +186,8 @@ static void GenerateMatrix( int count, int dim ) { string[] compRangeStr = compRange.Select( c => vCompStr[c].ToString() ).ToArray(); string JoinRange( string separator, Func elem ) => string.Join( separator, elemRange.Select( elem ) ); string typePrefix = dim switch { > 1 => $"Vector{dim}", _ => "" }; - string elemType = dim switch { 1 => "float", > 1 => $"Vector{dim}", _ => throw new Exception( "Invalid type" ) }; - + string lerpName = GetLerpName( dim ); + string elemType = dim switch { 1 => "float", > 1 => $"Vector{dim}", _ => throw new Exception( "Invalid type" ) }; string typeName = $"{typePrefix}Matrix{count}x1"; string csParams = JoinRange( ", ", i => $"m{i}" ); string csParamsThis = JoinRange( ", ", i => $"this.m{i}" ); @@ -196,6 +196,7 @@ static void GenerateMatrix( int count, int dim ) { string indexerGetterCases = JoinRange( ", ", i => $"{i} => m{i}" ) + $", _ => {indexerException}"; string equalsCompare = JoinRange( " && ", i => $"m{i}.Equals( other.m{i} )" ); string equalsOpCompare = JoinRange( " && ", i => $"a.m{i} == b.m{i}" ); + string lerpAtoB = JoinRange( ", ", i => $"{lerpName}( a.m{i}, b.m{i}, t )" ); // generate content @@ -239,6 +240,11 @@ static void GenerateMatrix( int count, int dim ) { } } + // interpolation + code.Summary( "Linearly interpolates between two matrices, based on a value t" ); + code.Param( "t", "The value to blend by" ); + code.Append( $"public static {typeName} Lerp( {typeName} a, {typeName} b, float t ) => new {typeName}({lerpAtoB});" ); + // comparison/operators code.Append( $"public static bool operator ==( {typeName} a, {typeName} b ) => {equalsOpCompare};" ); code.Append( $"public static bool operator !=( {typeName} a, {typeName} b ) => !( a == b );" ); @@ -298,12 +304,12 @@ static void GenerateUniformSplineType( SplineType type, int dim ) { code.LineBreak(); // constructors - string ctorSummary = $"Creates a uniform {dim}D {degFullLower} {type.prettyNameLower} segment, from {ptCount} control points"; + string ctorSummary = $"Creates a uniform {dim}D {degFullLower} {type.prettyNameLower} segment, from {ptCount} control points"; code.Summary( ctorSummary ); for( int i = 0; i < ptCount; i++ ) type.AppendParamStrings( code, degree, i ); code.Append( $"public {structName}( {ctorParams} ) : this(new {pointMatrixType}({csPoints})){{}}" ); - + code.Summary( ctorSummary ); code.Param( "pointMatrix", "The matrix containing the control points of this spline" ); code.Append( $"public {structName}( {pointMatrixType} pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false);" ); diff --git a/Numerics/Matrix3x1.cs b/Numerics/Matrix3x1.cs index 8e65644..77bea6f 100644 --- a/Numerics/Matrix3x1.cs +++ b/Numerics/Matrix3x1.cs @@ -16,6 +16,9 @@ public float this[int row] { } } } + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static Matrix3x1 Lerp( Matrix3x1 a, Matrix3x1 b, float t ) => new Matrix3x1(Mathfs.Lerp( a.m0, b.m0, t ), Mathfs.Lerp( a.m1, b.m1, t ), Mathfs.Lerp( a.m2, b.m2, t )); public static bool operator ==( Matrix3x1 a, Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; public static bool operator !=( Matrix3x1 a, Matrix3x1 b ) => !( a == b ); public bool Equals( Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); diff --git a/Numerics/Matrix4x1.cs b/Numerics/Matrix4x1.cs index a256154..0a5cab7 100644 --- a/Numerics/Matrix4x1.cs +++ b/Numerics/Matrix4x1.cs @@ -16,6 +16,9 @@ public float this[int row] { } } } + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static Matrix4x1 Lerp( Matrix4x1 a, Matrix4x1 b, float t ) => new Matrix4x1(Mathfs.Lerp( a.m0, b.m0, t ), Mathfs.Lerp( a.m1, b.m1, t ), Mathfs.Lerp( a.m2, b.m2, t ), Mathfs.Lerp( a.m3, b.m3, t )); public static bool operator ==( Matrix4x1 a, Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; public static bool operator !=( Matrix4x1 a, Matrix4x1 b ) => !( a == b ); public bool Equals( Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); diff --git a/Numerics/Vector2Matrix3x1.cs b/Numerics/Vector2Matrix3x1.cs index 3c00dc8..9c182d2 100644 --- a/Numerics/Vector2Matrix3x1.cs +++ b/Numerics/Vector2Matrix3x1.cs @@ -20,6 +20,9 @@ public Vector2 this[int row] { } public Matrix3x1 X => new(m0.x, m1.x, m2.x); public Matrix3x1 Y => new(m0.y, m1.y, m2.y); + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static Vector2Matrix3x1 Lerp( Vector2Matrix3x1 a, Vector2Matrix3x1 b, float t ) => new Vector2Matrix3x1(Vector2.LerpUnclamped( a.m0, b.m0, t ), Vector2.LerpUnclamped( a.m1, b.m1, t ), Vector2.LerpUnclamped( a.m2, b.m2, t )); public static bool operator ==( Vector2Matrix3x1 a, Vector2Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; public static bool operator !=( Vector2Matrix3x1 a, Vector2Matrix3x1 b ) => !( a == b ); public bool Equals( Vector2Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); diff --git a/Numerics/Vector2Matrix4x1.cs b/Numerics/Vector2Matrix4x1.cs index 7374a96..fd163c2 100644 --- a/Numerics/Vector2Matrix4x1.cs +++ b/Numerics/Vector2Matrix4x1.cs @@ -20,6 +20,9 @@ public Vector2 this[int row] { } public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static Vector2Matrix4x1 Lerp( Vector2Matrix4x1 a, Vector2Matrix4x1 b, float t ) => new Vector2Matrix4x1(Vector2.LerpUnclamped( a.m0, b.m0, t ), Vector2.LerpUnclamped( a.m1, b.m1, t ), Vector2.LerpUnclamped( a.m2, b.m2, t ), Vector2.LerpUnclamped( a.m3, b.m3, t )); public static bool operator ==( Vector2Matrix4x1 a, Vector2Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; public static bool operator !=( Vector2Matrix4x1 a, Vector2Matrix4x1 b ) => !( a == b ); public bool Equals( Vector2Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); diff --git a/Numerics/Vector3Matrix3x1.cs b/Numerics/Vector3Matrix3x1.cs index 41f7b31..6dfb6b1 100644 --- a/Numerics/Vector3Matrix3x1.cs +++ b/Numerics/Vector3Matrix3x1.cs @@ -21,6 +21,9 @@ public Vector3 this[int row] { public Matrix3x1 X => new(m0.x, m1.x, m2.x); public Matrix3x1 Y => new(m0.y, m1.y, m2.y); public Matrix3x1 Z => new(m0.z, m1.z, m2.z); + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static Vector3Matrix3x1 Lerp( Vector3Matrix3x1 a, Vector3Matrix3x1 b, float t ) => new Vector3Matrix3x1(Vector3.LerpUnclamped( a.m0, b.m0, t ), Vector3.LerpUnclamped( a.m1, b.m1, t ), Vector3.LerpUnclamped( a.m2, b.m2, t )); public static bool operator ==( Vector3Matrix3x1 a, Vector3Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; public static bool operator !=( Vector3Matrix3x1 a, Vector3Matrix3x1 b ) => !( a == b ); public bool Equals( Vector3Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); diff --git a/Numerics/Vector3Matrix4x1.cs b/Numerics/Vector3Matrix4x1.cs index b33400d..f9afa2a 100644 --- a/Numerics/Vector3Matrix4x1.cs +++ b/Numerics/Vector3Matrix4x1.cs @@ -21,6 +21,9 @@ public Vector3 this[int row] { public Matrix4x1 X => new(m0.x, m1.x, m2.x, m3.x); public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); public Matrix4x1 Z => new(m0.z, m1.z, m2.z, m3.z); + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static Vector3Matrix4x1 Lerp( Vector3Matrix4x1 a, Vector3Matrix4x1 b, float t ) => new Vector3Matrix4x1(Vector3.LerpUnclamped( a.m0, b.m0, t ), Vector3.LerpUnclamped( a.m1, b.m1, t ), Vector3.LerpUnclamped( a.m2, b.m2, t ), Vector3.LerpUnclamped( a.m3, b.m3, t )); public static bool operator ==( Vector3Matrix4x1 a, Vector3Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; public static bool operator !=( Vector3Matrix4x1 a, Vector3Matrix4x1 b ) => !( a == b ); public bool Equals( Vector3Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); diff --git a/Numerics/Vector4Matrix3x1.cs b/Numerics/Vector4Matrix3x1.cs index 7470660..a1b30a5 100644 --- a/Numerics/Vector4Matrix3x1.cs +++ b/Numerics/Vector4Matrix3x1.cs @@ -22,6 +22,9 @@ public Vector4 this[int row] { public Matrix3x1 Y => new(m0.y, m1.y, m2.y); public Matrix3x1 Z => new(m0.z, m1.z, m2.z); public Matrix3x1 W => new(m0.w, m1.w, m2.w); + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static Vector4Matrix3x1 Lerp( Vector4Matrix3x1 a, Vector4Matrix3x1 b, float t ) => new Vector4Matrix3x1(Vector4.LerpUnclamped( a.m0, b.m0, t ), Vector4.LerpUnclamped( a.m1, b.m1, t ), Vector4.LerpUnclamped( a.m2, b.m2, t )); public static bool operator ==( Vector4Matrix3x1 a, Vector4Matrix3x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2; public static bool operator !=( Vector4Matrix3x1 a, Vector4Matrix3x1 b ) => !( a == b ); public bool Equals( Vector4Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); diff --git a/Numerics/Vector4Matrix4x1.cs b/Numerics/Vector4Matrix4x1.cs index a93815e..4f0b77b 100644 --- a/Numerics/Vector4Matrix4x1.cs +++ b/Numerics/Vector4Matrix4x1.cs @@ -22,6 +22,9 @@ public Vector4 this[int row] { public Matrix4x1 Y => new(m0.y, m1.y, m2.y, m3.y); public Matrix4x1 Z => new(m0.z, m1.z, m2.z, m3.z); public Matrix4x1 W => new(m0.w, m1.w, m2.w, m3.w); + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static Vector4Matrix4x1 Lerp( Vector4Matrix4x1 a, Vector4Matrix4x1 b, float t ) => new Vector4Matrix4x1(Vector4.LerpUnclamped( a.m0, b.m0, t ), Vector4.LerpUnclamped( a.m1, b.m1, t ), Vector4.LerpUnclamped( a.m2, b.m2, t ), Vector4.LerpUnclamped( a.m3, b.m3, t )); public static bool operator ==( Vector4Matrix4x1 a, Vector4Matrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; public static bool operator !=( Vector4Matrix4x1 a, Vector4Matrix4x1 b ) => !( a == b ); public bool Equals( Vector4Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); From ad06210c3107ff49e578d2123b3c97819ad64b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 5 Aug 2022 23:12:51 +0200 Subject: [PATCH 120/301] made modulo faster for positive values --- Mathfs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mathfs.cs b/Mathfs.cs index 6734441..485f8e3 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -592,7 +592,7 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => [MethodImpl( INLINE )] public static float Repeat( float value, float length ) => Clamp( value - Floor( value / length ) * length, 0.0f, length ); /// Modulo, but, behaves the way you want with negative values, for stuff like array[(n+1)%length] etc. - [MethodImpl( INLINE )] public static int Mod( int value, int length ) => ( value % length + length ) % length; + [MethodImpl( INLINE )] public static int Mod( int value, int length ) => value >= 0 ? value % length : ( value % length + length ) % length; /// Repeats a value within a range, going back and forth [MethodImpl( INLINE )] public static float PingPong( float t, float length ) => length - Abs( Repeat( t, length * 2f ) - length ); From 7c9df1f63d14077fc9ff291835ebe148d66e6747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 5 Aug 2022 23:14:01 +0200 Subject: [PATCH 121/301] fixed Circle3D.FromThreePoints being broken --- Geometric Shapes/Circle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Geometric Shapes/Circle.cs b/Geometric Shapes/Circle.cs index 1d419f3..eff10aa 100644 --- a/Geometric Shapes/Circle.cs +++ b/Geometric Shapes/Circle.cs @@ -183,7 +183,7 @@ public static bool FromThreePoints( Vector3 a, Vector3 b, Vector3 c, out Circle3 if( Circle2D.FromThreePoints( default, b2D, c2D, out Circle2D circle2D ) ) { Vector3 origin = xAxis * circle2D.center.x + yAxis * circle2D.center.y; - circle = new Circle3D( origin, normal, circle2D.radius ); + circle = new Circle3D( a + origin, normal, circle2D.radius ); return true; } From 3fe835b371d1fa79f6cca2e2686acaaef23bf135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 5 Aug 2022 23:15:32 +0200 Subject: [PATCH 122/301] added FloatRange remap functions --- Extensions.cs | 6 ++++++ Mathfs.cs | 12 ++++++++++++ Numerics/FloatRange.cs | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index b8e4f11..9d2dce4 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -580,6 +580,12 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => /// [MethodImpl( INLINE )] public static float RemapClamped( this float value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.RemapClamped( iMin, iMax, oMin, oMax, value ); + /// + [MethodImpl( INLINE )] public static float Remap( this float value, FloatRange inRange, FloatRange outRange ) => Mathfs.Remap( inRange.a, inRange.b, outRange.a, outRange.b, value ); + + /// + [MethodImpl( INLINE )] public static float RemapClamped( this float value, FloatRange inRange, FloatRange outRange ) => Mathfs.RemapClamped( inRange.a, inRange.b, outRange.a, outRange.b, value ); + /// [MethodImpl( INLINE )] public static float Remap( this int value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.Remap( iMin, iMax, oMin, oMax, value ); diff --git a/Mathfs.cs b/Mathfs.cs index 485f8e3..1023fbd 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -780,6 +780,18 @@ public static Rect Lerp( Rect a, Rect b, float t ) { /// The input position in the input Bounds space [MethodImpl( INLINE )] public static Vector3 Remap( Bounds iBounds, Bounds oBounds, Vector3 iPos ) => Remap( iBounds.min, iBounds.max, oBounds.min, oBounds.max, iPos ); + /// Remaps a value from the input range to the output range + /// The input range + /// The output range + /// The value to remap from the input range + [MethodImpl( INLINE )] public static float Remap( FloatRange inRange, FloatRange outRange, float value ) => Remap( inRange.a, inRange.b, outRange.a, outRange.b, value ); + + /// Remaps a value from the input range to the output range, clamping to make sure it does not extrapolate. + /// The input range + /// The output range + /// The value to remap from the input range + [MethodImpl( INLINE )] public static float RemapClamped( FloatRange inRange, FloatRange outRange, float value ) => RemapClamped( inRange.a, inRange.b, outRange.a, outRange.b, value ); + /// Exponential interpolation, the multiplicative version of lerp, useful for values such as scaling or zooming /// The start value /// The end value diff --git a/Numerics/FloatRange.cs b/Numerics/FloatRange.cs index c5bae98..179e678 100644 --- a/Numerics/FloatRange.cs +++ b/Numerics/FloatRange.cs @@ -59,6 +59,12 @@ public readonly struct FloatRange { /// The output range public static float Remap( float value, FloatRange input, FloatRange output ) => output.Lerp( input.InverseLerp( value ) ); + /// Remaps a range from the input range to the output range + /// The range to remap + /// The input range + /// The output range + public static FloatRange Remap( FloatRange value, FloatRange input, FloatRange output ) => new(Remap( value.a, input, output ), Remap( value.b, input, output )); + /// Returns whether or not this range overlaps another range /// The other range to test overlap with public bool Overlaps( FloatRange other ) { From 03fea56b3c4cff40c6c7dca476aac3494f4f4d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 5 Aug 2022 23:16:14 +0200 Subject: [PATCH 123/301] FloatRange offset overloads --- Numerics/FloatRange.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Numerics/FloatRange.cs b/Numerics/FloatRange.cs index 179e678..c5ec761 100644 --- a/Numerics/FloatRange.cs +++ b/Numerics/FloatRange.cs @@ -100,6 +100,9 @@ public static Bounds ToBounds( FloatRange rangeX, FloatRange rangeY, FloatRange return new Bounds( center, size ); } + public static FloatRange operator -( FloatRange range, float v ) => new(range.a - v, range.b - v); + public static FloatRange operator +( FloatRange range, float v ) => new(range.a + v, range.b + v); + public static implicit operator FloatRange( (float a, float b) tuple ) => new FloatRange( tuple.a, tuple.b ); public static bool operator ==( FloatRange a, FloatRange b ) => a.a == b.a && a.b == b.b; public static bool operator !=( FloatRange a, FloatRange b ) => a.a != b.a || a.b != b.b; From 1b5f00c5bfc137974ecfa24e1413920363f62d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 5 Aug 2022 23:17:38 +0200 Subject: [PATCH 124/301] rect extensions for X and Y ranges --- Extensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index 9d2dce4..c102174 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -191,6 +191,14 @@ public static Vector2 Lerp( this Rect r, Vector2 tPos ) => Mathfs.Lerp( r.yMin, r.yMax, tPos.y ) ); + /// The x axis range of this rectangle + /// The rectangle to get the x range of + public static FloatRange RangeX( this Rect rect ) => ( rect.xMin, rect.xMax ); + + /// The y axis range of this rectangle + /// The rectangle to get the y range of + public static FloatRange RangeY( this Rect rect ) => ( rect.yMin, rect.yMax ); + #endregion #region Simple float and int operations From e70cadeb2e1dcffa0ba39168e6d0be037f7e55f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 5 Aug 2022 23:18:26 +0200 Subject: [PATCH 125/301] Matrix4x4 basis function extraction --- Splines/CharMatrix.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Splines/CharMatrix.cs b/Splines/CharMatrix.cs index 578804b..0f1ab21 100644 --- a/Splines/CharMatrix.cs +++ b/Splines/CharMatrix.cs @@ -87,6 +87,17 @@ public static Polynomial GetBasisFunction( RationalMatrix4x4 c, int i ) { _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) }; } + + /// + public static Polynomial GetBasisFunction( Matrix4x4 c, int i ) { + return i switch { + 0 => new Polynomial( c.m00, c.m10, c.m20, c.m30 ), + 1 => new Polynomial( c.m01, c.m11, c.m21, c.m31 ), + 2 => new Polynomial( c.m02, c.m12, c.m22, c.m32 ), + 3 => new Polynomial( c.m03, c.m13, c.m23, c.m33 ), + _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) + }; + } } From be9b4bf38e544459bdfb976c5250cda1d54cff54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:32:03 +0200 Subject: [PATCH 126/301] Added ScaleAround vector extension --- Extensions.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index c102174..caa3ad1 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -143,6 +143,15 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// [MethodImpl( INLINE )] public static Vector3 MirrorAround( this Vector3 p, Vector3 pivot ) => new(2 * pivot.x - p.x, 2 * pivot.y - p.y, 2 * pivot.z - p.z); + + /// Scale the point p around pivot by scale + /// The point to scale + /// The pivot to scale around + /// The scale to scale by + [MethodImpl( INLINE )] public static Vector2 ScaleAround( this Vector2 p, Vector2 pivot, Vector2 scale ) => new(pivot.x + ( p.x - pivot.x ) * scale.x, pivot.y + ( p.y - pivot.y ) * scale.y); + + /// + [MethodImpl( INLINE )] public static Vector3 ScaleAround( this Vector3 p, Vector3 pivot, Vector3 scale ) => new(pivot.x + ( p.x - pivot.x ) * scale.x, pivot.y + ( p.y - pivot.y ) * scale.y, pivot.z + ( p.z - pivot.z ) * scale.z); #endregion #region Color manipulation From 030cede0b3a54d143503be9bb905a35d0f2232d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:32:24 +0200 Subject: [PATCH 127/301] FloatRange.MirrorAround --- Numerics/FloatRange.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Numerics/FloatRange.cs b/Numerics/FloatRange.cs index c5ec761..dfd080b 100644 --- a/Numerics/FloatRange.cs +++ b/Numerics/FloatRange.cs @@ -85,6 +85,10 @@ public FloatRange Encapsulate( float value ) => /// The value to scale the range by public FloatRange ScaleFromStart( float scale ) => new FloatRange( a, a + scale * ( b - a ) ); + /// Returns this range mirrored around a given value + /// The value to mirror around + public FloatRange MirrorAround( float pivot ) => new FloatRange( 2 * pivot - a, 2 * pivot - b ); + /// Returns the rectangle encapsulating the region defined by a range per axis. Note: The direction of each range is ignored /// The range of the X axis /// The range of the Y axis From 380f26bdc4e963207315daba69d0e774ce2ea0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:32:50 +0200 Subject: [PATCH 128/301] FloatRange.ToString --- Numerics/FloatRange.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Numerics/FloatRange.cs b/Numerics/FloatRange.cs index dfd080b..563ef30 100644 --- a/Numerics/FloatRange.cs +++ b/Numerics/FloatRange.cs @@ -114,6 +114,8 @@ public static Bounds ToBounds( FloatRange rangeX, FloatRange rangeY, FloatRange public override bool Equals( object obj ) => obj is FloatRange other && Equals( other ); public override int GetHashCode() => HashCode.Combine( a, b ); + public override string ToString() => $"[{a},{b}]"; + } } \ No newline at end of file From 70199142396a6e6d38634ceb1dea98f8b5e690b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:34:01 +0200 Subject: [PATCH 129/301] Mathfs.Sinc --- Mathfs.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Mathfs.cs b/Mathfs.cs index 1023fbd..fa2925f 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -240,6 +240,26 @@ public static ulong BinomialCoef( uint n, uint k ) { /// Angle in radians [MethodImpl( INLINE )] public static float Crd( float angRad ) => 2 * (float)Math.Sin( angRad / 2 ); + const double SINC_W = 0.01; + const double SINC_P_C2 = -1 / 6.0; + const double SINC_P_C4 = 1 / 120.0; + + /// The unnormalized sinc function sin(x)/x, properly handling the removable singularity around x = 0 + /// The input value for the Sinc function + public static float Sinc( float x ) => (float)Sinc( (double)x ); + + /// + public static double Sinc( double x ) { + x = Math.Abs( x ); // sinc is symmetric + if( x < SINC_W ) { + // approximate the singularity w. a polynomial + double x2 = x * x; + double x4 = x2 * x2; + return 1 + SINC_P_C2 * x2 + SINC_P_C4 * x4; + } + + return Math.Sin( x ) / x; + } #endregion #region Hyperbolic Trigonometry From 98cdc3fca86f6fae0166af4aed2f6286ccf95b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:34:13 +0200 Subject: [PATCH 130/301] Mathfs.SincRcp --- Mathfs.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Mathfs.cs b/Mathfs.cs index fa2925f..e63143d 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -243,6 +243,8 @@ public static ulong BinomialCoef( uint n, uint k ) { const double SINC_W = 0.01; const double SINC_P_C2 = -1 / 6.0; const double SINC_P_C4 = 1 / 120.0; + const double SINCRCP_P_C2 = 1 / 6.0; + const double SINCRCP_P_C4 = 7 / 360.0; /// The unnormalized sinc function sin(x)/x, properly handling the removable singularity around x = 0 /// The input value for the Sinc function @@ -260,6 +262,24 @@ public static double Sinc( double x ) { return Math.Sin( x ) / x; } + + /// The unnormalized reciprocal sinc function x/sin(x), properly handling the removable singularity around x = 0 + /// The input value for the reciprocal Sinc function + public static float SincRcp( float x ) => (float)SincRcp( (double)x ); + + /// + public static double SincRcp( double x ) { + x = Math.Abs( x ); // sinc is symmetric + if( x < SINC_W ) { + // approximate the singularity w. a polynomial + double x2 = x * x; + double x4 = x2 * x2; + return 1 + SINCRCP_P_C2 * x2 + SINCRCP_P_C4 * x4; + } + + return x / Math.Sin( x ); + } + #endregion #region Hyperbolic Trigonometry From 5dae59b18cacb77c4e3b4b491be5ef037753000a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:37:42 +0200 Subject: [PATCH 131/301] changed GetArcNormal calculation --- Mathfs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mathfs.cs b/Mathfs.cs index e63143d..01a4719 100644 --- a/Mathfs.cs +++ b/Mathfs.cs @@ -1140,7 +1140,7 @@ public static Pose Lerp( Pose a, Pose b, float t ) => /// Returns the frenet-serret (curvature-based) normal direction at a given point in a curve /// The first derivative of the point in the curve /// The second derivative of the point in the curve - [MethodImpl( INLINE )] public static Vector3 GetArcNormal( Vector3 velocity, Vector3 acceleration ) => Vector3.Cross( velocity, Vector3.Cross( acceleration, velocity ) ).normalized; + [MethodImpl( INLINE )] public static Vector3 GetArcNormal( Vector3 velocity, Vector3 acceleration ) => Vector3.Cross( Vector3.Cross( velocity, acceleration ).normalized, velocity.normalized ); /// Returns the frenet-serret (curvature-based) binormal direction at a given point in a curve /// The first derivative of the point in the curve From 8852bc914ce7df5409bbbd26bca3b3e99d07f78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:39:11 +0200 Subject: [PATCH 132/301] Quaternion.Mul --- Extensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index caa3ad1..d8599c3 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -152,6 +152,12 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// [MethodImpl( INLINE )] public static Vector3 ScaleAround( this Vector3 p, Vector3 pivot, Vector3 scale ) => new(pivot.x + ( p.x - pivot.x ) * scale.x, pivot.y + ( p.y - pivot.y ) * scale.y, pivot.z + ( p.z - pivot.z ) * scale.z); + + + #region Quaternions + public static Quaternion Mul( this Quaternion q, float c ) => new Quaternion( c * q.x, c * q.y, c * q.z, c * q.w ); + + #endregion #endregion #region Color manipulation From 43505b469f578191edd163da0211bf4e14bdedd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:39:28 +0200 Subject: [PATCH 133/301] Quaternion.Log --- Extensions.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index d8599c3..72b2e5a 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -155,6 +155,20 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { #region Quaternions + + public static Quaternion Log( this Quaternion q ) { + double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; + double vMag = Math.Sqrt( vMagSq ); + double qMag = Math.Sqrt( vMagSq + (double)q.w * q.w ); + double theta = Math.Atan2( vMag, q.w ); + double scV = vMag < 0.01f ? SincRcp( theta ) / qMag : theta / vMag; + return new Quaternion( + (float)( scV * q.x ), + (float)( scV * q.y ), + (float)( scV * q.z ), + (float)Math.Log( qMag ) + ); + } public static Quaternion Mul( this Quaternion q, float c ) => new Quaternion( c * q.x, c * q.y, c * q.z, c * q.w ); #endregion From 025b316fb32fa57bba0d072200d2bafe5bea3bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 31 Aug 2022 13:40:09 +0200 Subject: [PATCH 134/301] Quaternion.Exp --- Extensions.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Extensions.cs b/Extensions.cs index 72b2e5a..e768ae9 100644 --- a/Extensions.cs +++ b/Extensions.cs @@ -169,6 +169,15 @@ public static Quaternion Log( this Quaternion q ) { (float)Math.Log( qMag ) ); } + + public static Quaternion Exp( this Quaternion q ) { + Vector3 v = new(q.x, q.y, q.z); + double vMag = Math.Sqrt( (double)v.x * v.x + (double)v.y * v.y + (double)v.z * v.z ); + double sc = Math.Exp( q.w ); + double scV = sc * Sinc( vMag ); + return new Quaternion( (float)( scV * v.x ), (float)( scV * v.y ), (float)( scV * v.z ), (float)( sc * Math.Cos( vMag ) ) ); + } + public static Quaternion Mul( this Quaternion q, float c ) => new Quaternion( c * q.x, c * q.y, c * q.z, c * q.w ); #endregion From 15187378f79e6d7598da2bd655d3518e8053c97d Mon Sep 17 00:00:00 2001 From: eborchers Date: Mon, 26 Sep 2022 12:00:47 -0400 Subject: [PATCH 135/301] added Installation instructions to readme.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7196817..ceeefdc 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,8 @@ Mathfs.cs **does not fully match Unity's Mathf.cs**, I've made a few changes: - LerpSmooth (which is how it was implemented) and - InverseLerpSmooth (which is how it is implemented everywhere but Unity's Mathf.cs) - Min/Max functions with arbitrary inputs/array input will throw on empty instead of returning 0 + +## Installation instructions +- Download or Git Clone the repository +- Place the downloaded files in a folder in your Unity project Assets/ folder +- Access the library in script by including namespace "using Freya" \ No newline at end of file From f1496b026069b96aa37749a0ded7cb39a268bd79 Mon Sep 17 00:00:00 2001 From: Andrei Andreev Date: Mon, 17 Oct 2022 21:32:07 +0200 Subject: [PATCH 136/301] Group code into Editor and Runtime folders --- {Codegen/Editor => Editor}/CodeGenerator.cs | 0 Editor/Mathfs.Editor.asmdef | 18 + {Codegen/Editor => Editor}/MathfsCodegen.cs | 4 +- {Curves => Runtime/Curves}/IParamCurve.cs | 0 {Curves => Runtime/Curves}/Polynomial.cs | 0 {Curves => Runtime/Curves}/Polynomial2D.cs | 0 {Curves => Runtime/Curves}/Polynomial3D.cs | 0 {Curves => Runtime/Curves}/Polynomial4D.cs | 0 Extensions.cs => Runtime/Extensions.cs | 4 +- .../Geometric Shapes}/Box.cs | 0 .../Geometric Shapes}/Circle.cs | 0 .../Geometric Shapes}/ILinear2D.cs | 0 .../Geometric Shapes}/Line2D.cs | 0 .../Geometric Shapes}/LineSegment2D.cs | 0 .../Geometric Shapes}/Polygon.cs | 0 .../Geometric Shapes}/PolygonClipper.cs | 0 .../Geometric Shapes}/Ray2D.cs | 0 .../Geometric Shapes}/Triangle.cs | 0 .../IntersectionTestCore.cs | 0 .../IntersectionTestWrappers.cs | 0 Mathfs.cs => Runtime/Mathfs.cs | 2582 ++++++++--------- .../MathfsAsmdef.asmdef | 0 {Numerics => Runtime/Numerics}/FloatRange.cs | 0 {Numerics => Runtime/Numerics}/Matrix3x1.cs | 0 {Numerics => Runtime/Numerics}/Matrix4x1.cs | 0 {Numerics => Runtime/Numerics}/Rational.cs | 0 .../Numerics}/RationalMatrix3x3.cs | 0 .../Numerics}/RationalMatrix4x4.cs | 0 .../Numerics}/Vector2Matrix3x1.cs | 0 .../Numerics}/Vector2Matrix4x1.cs | 0 .../Numerics}/Vector3Matrix3x1.cs | 0 .../Numerics}/Vector3Matrix4x1.cs | 0 .../Numerics}/Vector4Matrix3x1.cs | 0 .../Numerics}/Vector4Matrix4x1.cs | 0 Random.cs => Runtime/Random.cs | 0 {Splines => Runtime/Splines}/CatRomType.cs | 0 {Splines => Runtime/Splines}/CharMatrix.cs | 0 .../Multi-Segment Splines/BSpline2D.cs | 0 .../Splines}/Multi-Segment Splines/NURBS2D.cs | 0 .../NUCatRomCubic2D.cs | 0 .../NUCatRomCubic3D.cs | 0 {Splines => Runtime/Splines}/SplineUtils.cs | 0 {Splines => Runtime/Splines}/Trajectory2D.cs | 0 .../Uniform Spline Segments/Bezier2D.cs | 0 .../Uniform Spline Segments/Bezier3D.cs | 0 .../Uniform Spline Segments/BezierCubic1D.cs | 0 .../Uniform Spline Segments/BezierCubic2D.cs | 0 .../Uniform Spline Segments/BezierCubic3D.cs | 0 .../Uniform Spline Segments/BezierCubic4D.cs | 0 .../Uniform Spline Segments/BezierQuad1D.cs | 0 .../Uniform Spline Segments/BezierQuad2D.cs | 0 .../Uniform Spline Segments/BezierQuad3D.cs | 0 .../Uniform Spline Segments/BezierQuad4D.cs | 0 .../Uniform Spline Segments/CatRomCubic1D.cs | 0 .../Uniform Spline Segments/CatRomCubic2D.cs | 0 .../Uniform Spline Segments/CatRomCubic3D.cs | 0 .../Uniform Spline Segments/CatRomCubic4D.cs | 0 .../Uniform Spline Segments/HermiteCubic1D.cs | 0 .../Uniform Spline Segments/HermiteCubic2D.cs | 0 .../Uniform Spline Segments/HermiteCubic3D.cs | 0 .../Uniform Spline Segments/HermiteCubic4D.cs | 0 .../Uniform Spline Segments/UBSCubic1D.cs | 0 .../Uniform Spline Segments/UBSCubic2D.cs | 0 .../Uniform Spline Segments/UBSCubic3D.cs | 0 .../Uniform Spline Segments/UBSCubic4D.cs | 0 .../Splines}/UniformCurveSampler.cs | 0 UtilityTypes.cs => Runtime/UtilityTypes.cs | 0 67 files changed, 1313 insertions(+), 1295 deletions(-) rename {Codegen/Editor => Editor}/CodeGenerator.cs (100%) create mode 100644 Editor/Mathfs.Editor.asmdef rename {Codegen/Editor => Editor}/MathfsCodegen.cs (99%) rename {Curves => Runtime/Curves}/IParamCurve.cs (100%) rename {Curves => Runtime/Curves}/Polynomial.cs (100%) rename {Curves => Runtime/Curves}/Polynomial2D.cs (100%) rename {Curves => Runtime/Curves}/Polynomial3D.cs (100%) rename {Curves => Runtime/Curves}/Polynomial4D.cs (100%) rename Extensions.cs => Runtime/Extensions.cs (99%) rename {Geometric Shapes => Runtime/Geometric Shapes}/Box.cs (100%) rename {Geometric Shapes => Runtime/Geometric Shapes}/Circle.cs (100%) rename {Geometric Shapes => Runtime/Geometric Shapes}/ILinear2D.cs (100%) rename {Geometric Shapes => Runtime/Geometric Shapes}/Line2D.cs (100%) rename {Geometric Shapes => Runtime/Geometric Shapes}/LineSegment2D.cs (100%) rename {Geometric Shapes => Runtime/Geometric Shapes}/Polygon.cs (100%) rename {Geometric Shapes => Runtime/Geometric Shapes}/PolygonClipper.cs (100%) rename {Geometric Shapes => Runtime/Geometric Shapes}/Ray2D.cs (100%) rename {Geometric Shapes => Runtime/Geometric Shapes}/Triangle.cs (100%) rename IntersectionTestCore.cs => Runtime/IntersectionTestCore.cs (100%) rename IntersectionTestWrappers.cs => Runtime/IntersectionTestWrappers.cs (100%) rename Mathfs.cs => Runtime/Mathfs.cs (98%) rename MathfsAsmdef.asmdef => Runtime/MathfsAsmdef.asmdef (100%) rename {Numerics => Runtime/Numerics}/FloatRange.cs (100%) rename {Numerics => Runtime/Numerics}/Matrix3x1.cs (100%) rename {Numerics => Runtime/Numerics}/Matrix4x1.cs (100%) rename {Numerics => Runtime/Numerics}/Rational.cs (100%) rename {Numerics => Runtime/Numerics}/RationalMatrix3x3.cs (100%) rename {Numerics => Runtime/Numerics}/RationalMatrix4x4.cs (100%) rename {Numerics => Runtime/Numerics}/Vector2Matrix3x1.cs (100%) rename {Numerics => Runtime/Numerics}/Vector2Matrix4x1.cs (100%) rename {Numerics => Runtime/Numerics}/Vector3Matrix3x1.cs (100%) rename {Numerics => Runtime/Numerics}/Vector3Matrix4x1.cs (100%) rename {Numerics => Runtime/Numerics}/Vector4Matrix3x1.cs (100%) rename {Numerics => Runtime/Numerics}/Vector4Matrix4x1.cs (100%) rename Random.cs => Runtime/Random.cs (100%) rename {Splines => Runtime/Splines}/CatRomType.cs (100%) rename {Splines => Runtime/Splines}/CharMatrix.cs (100%) rename {Splines => Runtime/Splines}/Multi-Segment Splines/BSpline2D.cs (100%) rename {Splines => Runtime/Splines}/Multi-Segment Splines/NURBS2D.cs (100%) rename {Splines => Runtime/Splines}/Non-Uniform Spline Segments/NUCatRomCubic2D.cs (100%) rename {Splines => Runtime/Splines}/Non-Uniform Spline Segments/NUCatRomCubic3D.cs (100%) rename {Splines => Runtime/Splines}/SplineUtils.cs (100%) rename {Splines => Runtime/Splines}/Trajectory2D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/Bezier2D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/Bezier3D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/BezierCubic1D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/BezierCubic2D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/BezierCubic3D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/BezierCubic4D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/BezierQuad1D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/BezierQuad2D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/BezierQuad3D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/BezierQuad4D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/CatRomCubic1D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/CatRomCubic2D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/CatRomCubic3D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/CatRomCubic4D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/HermiteCubic1D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/HermiteCubic2D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/HermiteCubic3D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/HermiteCubic4D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/UBSCubic1D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/UBSCubic2D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/UBSCubic3D.cs (100%) rename {Splines => Runtime/Splines}/Uniform Spline Segments/UBSCubic4D.cs (100%) rename {Splines => Runtime/Splines}/UniformCurveSampler.cs (100%) rename UtilityTypes.cs => Runtime/UtilityTypes.cs (100%) diff --git a/Codegen/Editor/CodeGenerator.cs b/Editor/CodeGenerator.cs similarity index 100% rename from Codegen/Editor/CodeGenerator.cs rename to Editor/CodeGenerator.cs diff --git a/Editor/Mathfs.Editor.asmdef b/Editor/Mathfs.Editor.asmdef new file mode 100644 index 0000000..1356d96 --- /dev/null +++ b/Editor/Mathfs.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Mathfs.Editor", + "rootNamespace": "", + "references": [ + "GUID:6071c9f2ce0a4407c93af459fa416e54" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Codegen/Editor/MathfsCodegen.cs b/Editor/MathfsCodegen.cs similarity index 99% rename from Codegen/Editor/MathfsCodegen.cs rename to Editor/MathfsCodegen.cs index d673a5e..4204cfd 100644 --- a/Codegen/Editor/MathfsCodegen.cs +++ b/Editor/MathfsCodegen.cs @@ -256,7 +256,7 @@ static void GenerateMatrix( int count, int dim ) { // save/finalize - string path = $"Assets/Mathfs/Numerics/{typeName}.cs"; + string path = $"Assets/Mathfs/Runtime/Numerics/{typeName}.cs"; File.WriteAllLines( path, code.content ); } @@ -469,7 +469,7 @@ static void GenerateUniformSplineType( SplineType type, int dim ) { } } - string path = $"Assets/Mathfs/Splines/Uniform Spline Segments/{structName}.cs"; + string path = $"Assets/Mathfs/Runtime/Splines/Uniform Spline Segments/{structName}.cs"; File.WriteAllLines( path, code.content ); } diff --git a/Curves/IParamCurve.cs b/Runtime/Curves/IParamCurve.cs similarity index 100% rename from Curves/IParamCurve.cs rename to Runtime/Curves/IParamCurve.cs diff --git a/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs similarity index 100% rename from Curves/Polynomial.cs rename to Runtime/Curves/Polynomial.cs diff --git a/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs similarity index 100% rename from Curves/Polynomial2D.cs rename to Runtime/Curves/Polynomial2D.cs diff --git a/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs similarity index 100% rename from Curves/Polynomial3D.cs rename to Runtime/Curves/Polynomial3D.cs diff --git a/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs similarity index 100% rename from Curves/Polynomial4D.cs rename to Runtime/Curves/Polynomial4D.cs diff --git a/Extensions.cs b/Runtime/Extensions.cs similarity index 99% rename from Extensions.cs rename to Runtime/Extensions.cs index e768ae9..bea92ff 100644 --- a/Extensions.cs +++ b/Runtime/Extensions.cs @@ -161,7 +161,7 @@ public static Quaternion Log( this Quaternion q ) { double vMag = Math.Sqrt( vMagSq ); double qMag = Math.Sqrt( vMagSq + (double)q.w * q.w ); double theta = Math.Atan2( vMag, q.w ); - double scV = vMag < 0.01f ? SincRcp( theta ) / qMag : theta / vMag; + double scV = vMag < 0.01f ? Mathfs.SincRcp( theta ) / qMag : theta / vMag; return new Quaternion( (float)( scV * q.x ), (float)( scV * q.y ), @@ -174,7 +174,7 @@ public static Quaternion Exp( this Quaternion q ) { Vector3 v = new(q.x, q.y, q.z); double vMag = Math.Sqrt( (double)v.x * v.x + (double)v.y * v.y + (double)v.z * v.z ); double sc = Math.Exp( q.w ); - double scV = sc * Sinc( vMag ); + double scV = sc * Mathfs.Sinc( vMag ); return new Quaternion( (float)( scV * v.x ), (float)( scV * v.y ), (float)( scV * v.z ), (float)( sc * Math.Cos( vMag ) ) ); } diff --git a/Geometric Shapes/Box.cs b/Runtime/Geometric Shapes/Box.cs similarity index 100% rename from Geometric Shapes/Box.cs rename to Runtime/Geometric Shapes/Box.cs diff --git a/Geometric Shapes/Circle.cs b/Runtime/Geometric Shapes/Circle.cs similarity index 100% rename from Geometric Shapes/Circle.cs rename to Runtime/Geometric Shapes/Circle.cs diff --git a/Geometric Shapes/ILinear2D.cs b/Runtime/Geometric Shapes/ILinear2D.cs similarity index 100% rename from Geometric Shapes/ILinear2D.cs rename to Runtime/Geometric Shapes/ILinear2D.cs diff --git a/Geometric Shapes/Line2D.cs b/Runtime/Geometric Shapes/Line2D.cs similarity index 100% rename from Geometric Shapes/Line2D.cs rename to Runtime/Geometric Shapes/Line2D.cs diff --git a/Geometric Shapes/LineSegment2D.cs b/Runtime/Geometric Shapes/LineSegment2D.cs similarity index 100% rename from Geometric Shapes/LineSegment2D.cs rename to Runtime/Geometric Shapes/LineSegment2D.cs diff --git a/Geometric Shapes/Polygon.cs b/Runtime/Geometric Shapes/Polygon.cs similarity index 100% rename from Geometric Shapes/Polygon.cs rename to Runtime/Geometric Shapes/Polygon.cs diff --git a/Geometric Shapes/PolygonClipper.cs b/Runtime/Geometric Shapes/PolygonClipper.cs similarity index 100% rename from Geometric Shapes/PolygonClipper.cs rename to Runtime/Geometric Shapes/PolygonClipper.cs diff --git a/Geometric Shapes/Ray2D.cs b/Runtime/Geometric Shapes/Ray2D.cs similarity index 100% rename from Geometric Shapes/Ray2D.cs rename to Runtime/Geometric Shapes/Ray2D.cs diff --git a/Geometric Shapes/Triangle.cs b/Runtime/Geometric Shapes/Triangle.cs similarity index 100% rename from Geometric Shapes/Triangle.cs rename to Runtime/Geometric Shapes/Triangle.cs diff --git a/IntersectionTestCore.cs b/Runtime/IntersectionTestCore.cs similarity index 100% rename from IntersectionTestCore.cs rename to Runtime/IntersectionTestCore.cs diff --git a/IntersectionTestWrappers.cs b/Runtime/IntersectionTestWrappers.cs similarity index 100% rename from IntersectionTestWrappers.cs rename to Runtime/IntersectionTestWrappers.cs diff --git a/Mathfs.cs b/Runtime/Mathfs.cs similarity index 98% rename from Mathfs.cs rename to Runtime/Mathfs.cs index 01a4719..058fa0c 100644 --- a/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1,1292 +1,1292 @@ -// Some of this code is similar to Unity's original Mathf source to match functionality. -// The original Mathf.cs source https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Math/Mathf.cs -// ...and the trace amounts of it left in here is copyright (c) Unity Technologies with license: https://unity3d.com/legal/licenses/Unity_Reference_Only_License -// -// Collected and expanded upon to by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) - -using System; -using UnityEngine; -using Uei = UnityEngine.Internal; -using System.Linq; // used for arbitrary count min/max functions, so it's safe and won't allocate garbage don't worry~ -using System.Runtime.CompilerServices; - -namespace Freya { - - /// The core math helper class. It has functions mostly for single values, but also vector helpers - public static class Mathfs { - - const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - - public static readonly bool[] bools = { false, true }; - - #region Constants - - /// The circle constant. Defined as the circumference of a circle divided by its radius. Equivalent to 2*pi - public const float TAU = 6.28318530717959f; - - /// An obscure circle constant. Defined as the circumference of a circle divided by its diameter. Equivalent to 0.5*tau - public const float PI = 3.14159265359f; - - /// Euler's number. The base of the natural logarithm. f(x)=e^x is equal to its own derivative - public const float E = 2.71828182846f; - - /// The golden ratio. It is the value of a/b where a/b = (a+b)/a. It's the positive root of x^2-x-1 - public const float GOLDEN_RATIO = 1.61803398875f; - - /// The square root of two. The length of the vector (1,1) - public const float SQRT2 = 1.41421356237f; - - /// The reciprocal of the square root of two. The components of the vector (1,1) - public const float RSQRT2 = 1f / SQRT2; - - /// Multiply an angle in degrees by this, to convert it to radians - public const float Deg2Rad = TAU / 360f; - - /// Multiply an angle in radians by this, to convert it to degrees - public const float Rad2Deg = 360f / TAU; - - #endregion - - #region Math operations - - /// Returns the square root of the given value - [MethodImpl( INLINE )] public static float Sqrt( float value ) => (float)Math.Sqrt( value ); - - /// Returns the square root of each component - [MethodImpl( INLINE )] public static Vector2 Sqrt( Vector2 v ) => new Vector2( Sqrt( v.x ), Sqrt( v.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Sqrt( Vector3 v ) => new Vector3( Sqrt( v.x ), Sqrt( v.y ), Sqrt( v.z ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Sqrt( Vector4 v ) => new Vector4( Sqrt( v.x ), Sqrt( v.y ), Sqrt( v.z ), Sqrt( v.w ) ); - - /// Returns the cube root of the given value, properly handling negative values unlike Pow(v,1/3) - [MethodImpl( INLINE )] public static float Cbrt( float value ) => value < 0 ? -Pow( -value, 1f / 3f ) : Pow( value, 1f / 3f ); - - /// Returns value raised to the power of exponent - [MethodImpl( INLINE )] public static float Pow( float value, float exponent ) => (float)Math.Pow( value, exponent ); - - /// Returns e to the power of the given value - [MethodImpl( INLINE )] public static float Exp( float power ) => (float)Math.Exp( power ); - - /// Returns the logarithm of a value, with the given base - [MethodImpl( INLINE )] public static float Log( float value, float @base ) => (float)Math.Log( value, @base ); - - /// Returns the natural logarithm of the given value - [MethodImpl( INLINE )] public static float Log( float value ) => (float)Math.Log( value ); - - /// Returns the base 10 logarithm of the given value - [MethodImpl( INLINE )] public static float Log10( float value ) => (float)Math.Log10( value ); - - /// Returns the binomial coefficient n over k - public static ulong BinomialCoef( uint n, uint k ) { - // source: https://blog.plover.com/math/choose.html - ulong r = 1; - if( k > n ) return 0; - for( ulong d = 1; d <= k; d++ ) { - r *= n--; - r /= d; - } - - return r; - // mathematically clean but extremely prone to overflow - //return Factorial( n ) / ( Factorial( k ) * Factorial( n - k ) ); - } - - /// Returns the Factorial of a given value from 0 to 12 - /// A value between 0 and 12 (integers can't store the factorial of 13 or above) - [MethodImpl( INLINE )] public static int Factorial( uint value ) { - if( value <= 12 ) - return factorialInt[value]; - if( value <= 20 ) - throw new OverflowException( $"The Factorial of {value} is too big for integer representation, please use {nameof(FactorialLong)} instead" ); - throw new OverflowException( $"The Factorial of {value} is too big for integer representation" ); - } - - /// Returns the Factorial of a given value from 0 to 20 - /// A value between 0 and 20 (neither long nor ulong can store values large enough for the factorial of 21) - [MethodImpl( INLINE )] public static long FactorialLong( uint value ) { - if( value <= 20 ) - return factorialLong[value]; - throw new OverflowException( $"The Factorial of {value} is too big for integer representation, even unsigned longs, soooo, rip" ); - } - - static readonly long[] factorialLong = { - /*0*/ 1, - /*1*/ 1, - /*2*/ 2, - /*3*/ 6, - /*4*/ 24, - /*5*/ 120, - /*6*/ 720, - /*7*/ 5040, - /*8*/ 40320, - /*9*/ 362880, - /*10*/ 3628800, - /*11*/ 39916800, - /*12*/ 479001600, - /*13*/ 6227020800, - /*14*/ 87178291200, - /*15*/ 1307674368000, - /*16*/ 20922789888000, - /*17*/ 355687428096000, - /*18*/ 6402373705728000, - /*19*/ 121645100408832000, - /*20*/ 2432902008176640000 - }; - - static readonly int[] factorialInt = { - /*0*/ 1, - /*1*/ 1, - /*2*/ 2, - /*3*/ 6, - /*4*/ 24, - /*5*/ 120, - /*6*/ 720, - /*7*/ 5040, - /*8*/ 40320, - /*9*/ 362880, - /*10*/ 3628800, - /*11*/ 39916800, - /*12*/ 479001600 - }; - - #endregion - - #region Floating point shenanigans - - /// A very small value, used for various floating point inaccuracy thresholds - public static readonly float Epsilon = UnityEngineInternal.MathfInternal.IsFlushToZeroEnabled ? UnityEngineInternal.MathfInternal.FloatMinNormal : UnityEngineInternal.MathfInternal.FloatMinDenormal; - - /// float.PositiveInfinity - public const float Infinity = float.PositiveInfinity; - - /// float.NegativeInfinity - public const float NegativeInfinity = float.NegativeInfinity; - - /// Returns whether or not two values are approximately equal. - /// They are considered equal if they are within a Mathfs.Epsilon*8 or max(a,b)*0.000001f range of each other - /// The first value to compare - /// The second value to compare - [MethodImpl( INLINE )] public static bool Approximately( float a, float b ) => Abs( b - a ) < Max( 0.000001f * Max( Abs( a ), Abs( b ) ), Epsilon * 8 ); - - /// - [MethodImpl( INLINE )] public static bool Approximately( Vector2 a, Vector2 b ) => Approximately( a.x, b.x ) && Approximately( a.y, b.y ); - - /// - [MethodImpl( INLINE )] public static bool Approximately( Vector3 a, Vector3 b ) => Approximately( a.x, b.x ) && Approximately( a.y, b.y ) && Approximately( a.z, b.z ); - - /// - [MethodImpl( INLINE )] public static bool Approximately( Vector4 a, Vector4 b ) => Approximately( a.x, b.x ) && Approximately( a.y, b.y ) && Approximately( a.z, b.z ) && Approximately( a.w, b.w ); - - /// - [MethodImpl( INLINE )] public static bool Approximately( Color a, Color b ) => Approximately( a.r, b.r ) && Approximately( a.g, b.g ) && Approximately( a.b, b.b ) && Approximately( a.a, b.a ); - - #endregion - - #region Trigonometry - - /// Returns the cosine of the given angle. Equivalent to the x-component of a unit vector with the same angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Cos( float angRad ) => (float)Math.Cos( angRad ); - - /// Returns the sine of the given angle. Equivalent to the y-component of a unit vector with the same angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Sin( float angRad ) => (float)Math.Sin( angRad ); - - /// Returns the tangent of the given angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Tan( float angRad ) => (float)Math.Tan( angRad ); - - /// Returns the arc cosine of the given value, in radians - /// A value between -1 and 1 - [MethodImpl( INLINE )] public static float Acos( float value ) => (float)Math.Acos( value ); - - /// Returns the arc sine of the given value, in radians - /// A value between -1 and 1 - [MethodImpl( INLINE )] public static float Asin( float value ) => (float)Math.Asin( value ); - - /// Returns the arc tangent of the given value, in radians - /// A value between -1 and 1 - [MethodImpl( INLINE )] public static float Atan( float value ) => (float)Math.Atan( value ); - - /// Returns the angle of a vector. I don't recommend using this function, it's confusing~ Use Mathfs.DirToAng instead - /// The y component of the vector. They're flipped yeah I know but this is how everyone implements if for some godforsaken reason - /// The x component of the vector. They're flipped yeah I know but this is how everyone implements if for some godforsaken reason - [MethodImpl( INLINE )] public static float Atan2( float y, float x ) => (float)Math.Atan2( y, x ); - - /// Returns the cosecant of the given angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Csc( float angRad ) => 1f / (float)Math.Sin( angRad ); - - /// Returns the secant of the given angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Sec( float angRad ) => 1f / (float)Math.Cos( angRad ); - - /// Returns the cotangent of the given angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Cot( float angRad ) => 1f / (float)Math.Tan( angRad ); - - /// Returns the versine of the given angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Ver( float angRad ) => 1 - (float)Math.Cos( angRad ); - - /// Returns the coversine of the given angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Cvs( float angRad ) => 1 - (float)Math.Sin( angRad ); - - /// Returns the chord of the given angle - /// Angle in radians - [MethodImpl( INLINE )] public static float Crd( float angRad ) => 2 * (float)Math.Sin( angRad / 2 ); - - const double SINC_W = 0.01; - const double SINC_P_C2 = -1 / 6.0; - const double SINC_P_C4 = 1 / 120.0; - const double SINCRCP_P_C2 = 1 / 6.0; - const double SINCRCP_P_C4 = 7 / 360.0; - - /// The unnormalized sinc function sin(x)/x, properly handling the removable singularity around x = 0 - /// The input value for the Sinc function - public static float Sinc( float x ) => (float)Sinc( (double)x ); - - /// - public static double Sinc( double x ) { - x = Math.Abs( x ); // sinc is symmetric - if( x < SINC_W ) { - // approximate the singularity w. a polynomial - double x2 = x * x; - double x4 = x2 * x2; - return 1 + SINC_P_C2 * x2 + SINC_P_C4 * x4; - } - - return Math.Sin( x ) / x; - } - - /// The unnormalized reciprocal sinc function x/sin(x), properly handling the removable singularity around x = 0 - /// The input value for the reciprocal Sinc function - public static float SincRcp( float x ) => (float)SincRcp( (double)x ); - - /// - public static double SincRcp( double x ) { - x = Math.Abs( x ); // sinc is symmetric - if( x < SINC_W ) { - // approximate the singularity w. a polynomial - double x2 = x * x; - double x4 = x2 * x2; - return 1 + SINCRCP_P_C2 * x2 + SINCRCP_P_C4 * x4; - } - - return x / Math.Sin( x ); - } - - #endregion - - #region Hyperbolic Trigonometry - - /// Returns the hyperbolic cosine of the given hyperbolic angle - [MethodImpl( INLINE )] public static float Cosh( float x ) => (float)Math.Cosh( x ); - - /// Returns the hyperbolic sine of the given hyperbolic angle - [MethodImpl( INLINE )] public static float Sinh( float x ) => (float)Math.Sinh( x ); - - /// Returns the hyperbolic tangent of the given hyperbolic angle - [MethodImpl( INLINE )] public static float Tanh( float x ) => (float)Math.Tanh( x ); - - /// Returns the hyperbolic arc cosine of the given value - [MethodImpl( INLINE )] public static float Acosh( float x ) => (float)Math.Log( x + Mathf.Sqrt( x * x - 1 ) ); - - /// Returns the hyperbolic arc sine of the given value - [MethodImpl( INLINE )] public static float Asinh( float x ) => (float)Math.Log( x + Mathf.Sqrt( x * x + 1 ) ); - - /// Returns the hyperbolic arc tangent of the given value - [MethodImpl( INLINE )] public static float Atanh( float x ) => (float)( 0.5 * Math.Log( ( 1 + x ) / ( 1 - x ) ) ); - - #endregion - - #region Absolute Values - - /// Returns the absolute value. Basically makes negative numbers positive - [MethodImpl( INLINE )] public static float Abs( float value ) => Math.Abs( value ); - - /// - [MethodImpl( INLINE )] public static int Abs( int value ) => Math.Abs( value ); - - /// Returns the absolute value, per component. Basically makes negative numbers positive - [MethodImpl( INLINE )] public static Vector2 Abs( Vector2 v ) => new Vector2( Abs( v.x ), Abs( v.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Abs( Vector3 v ) => new Vector3( Abs( v.x ), Abs( v.y ), Abs( v.z ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Abs( Vector4 v ) => new Vector4( Abs( v.x ), Abs( v.y ), Abs( v.z ), Abs( v.w ) ); - - #endregion - - #region Clamping - - /// Returns the value clamped between min and max - /// The value to clamp - /// The minimum value - /// The maximum value - public static float Clamp( float value, float min, float max ) => value < min ? min : value > max ? max : value; - - /// Clamps each component between min and max - public static Vector2 Clamp( Vector2 v, Vector2 min, Vector2 max ) => - new Vector2( - v.x < min.x ? min.x : v.x > max.x ? max.x : v.x, - v.y < min.y ? min.y : v.y > max.y ? max.y : v.y - ); - - /// - public static Vector3 Clamp( Vector3 v, Vector3 min, Vector3 max ) => - new Vector3( - v.x < min.x ? min.x : v.x > max.x ? max.x : v.x, - v.y < min.y ? min.y : v.y > max.y ? max.y : v.y, - v.z < min.z ? min.z : v.z > max.z ? max.z : v.z - ); - - /// - public static Vector4 Clamp( Vector4 v, Vector4 min, Vector4 max ) => - new Vector4( - v.x < min.x ? min.x : v.x > max.x ? max.x : v.x, - v.y < min.y ? min.y : v.y > max.y ? max.y : v.y, - v.z < min.z ? min.z : v.z > max.z ? max.z : v.z, - v.w < min.w ? min.w : v.w > max.w ? max.w : v.w - ); - - /// - public static int Clamp( int value, int min, int max ) => value < min ? min : value > max ? max : value; - - /// Returns the value clamped between 0 and 1 - public static float Clamp01( float value ) => value < 0f ? 0f : value > 1f ? 1f : value; - - /// Clamps each component between 0 and 1 - public static Vector2 Clamp01( Vector2 v ) => - new Vector2( - v.x < 0f ? 0f : v.x > 1f ? 1f : v.x, - v.y < 0f ? 0f : v.y > 1f ? 1f : v.y - ); - - /// - public static Vector3 Clamp01( Vector3 v ) => - new Vector3( - v.x < 0f ? 0f : v.x > 1f ? 1f : v.x, - v.y < 0f ? 0f : v.y > 1f ? 1f : v.y, - v.z < 0f ? 0f : v.z > 1f ? 1f : v.z - ); - - /// - public static Vector4 Clamp01( Vector4 v ) => - new Vector4( - v.x < 0f ? 0f : v.x > 1f ? 1f : v.x, - v.y < 0f ? 0f : v.y > 1f ? 1f : v.y, - v.z < 0f ? 0f : v.z > 1f ? 1f : v.z, - v.w < 0f ? 0f : v.w > 1f ? 1f : v.w - ); - - /// Clamps the value between -1 and 1 - public static float ClampNeg1to1( float value ) => value < -1f ? -1f : value > 1f ? 1f : value; - - /// Clamps each component between -1 and 1 - public static Vector2 ClampNeg1to1( Vector2 v ) => - new Vector2( - v.x < -1f ? -1f : v.x > 1f ? 1f : v.x, - v.y < -1f ? -1f : v.y > 1f ? 1f : v.y - ); - - /// Clamps each component between -1 and 1 - public static Vector3 ClampNeg1to1( Vector3 v ) => - new Vector3( - v.x < -1f ? -1f : v.x > 1f ? 1f : v.x, - v.y < -1f ? -1f : v.y > 1f ? 1f : v.y, - v.z < -1f ? -1f : v.z > 1f ? 1f : v.z - ); - - /// Clamps each component between -1 and 1 - public static Vector4 ClampNeg1to1( Vector4 v ) => - new Vector4( - v.x < -1f ? -1f : v.x > 1f ? 1f : v.x, - v.y < -1f ? -1f : v.y > 1f ? 1f : v.y, - v.z < -1f ? -1f : v.z > 1f ? 1f : v.z, - v.w < -1f ? -1f : v.w > 1f ? 1f : v.w - ); - - #endregion - - #region Min & Max - - /// Returns the smallest of the two values - [MethodImpl( INLINE )] public static float Min( float a, float b ) => a < b ? a : b; - - /// Returns the smallest of the three values - [MethodImpl( INLINE )] public static float Min( float a, float b, float c ) => Min( Min( a, b ), c ); - - /// Returns the smallest of the four values - [MethodImpl( INLINE )] public static float Min( float a, float b, float c, float d ) => Min( Min( a, b ), Min( c, d ) ); - - /// Returns the largest of the two values - [MethodImpl( INLINE )] public static float Max( float a, float b ) => a > b ? a : b; - - /// Returns the largest of the three values - [MethodImpl( INLINE )] public static float Max( float a, float b, float c ) => Max( Max( a, b ), c ); - - /// Returns the largest of the four values - [MethodImpl( INLINE )] public static float Max( float a, float b, float c, float d ) => Max( Max( a, b ), Max( c, d ) ); - - /// Returns the smallest of the two values - [MethodImpl( INLINE )] public static int Min( int a, int b ) => a < b ? a : b; - - /// Returns the smallest of the three values - [MethodImpl( INLINE )] public static int Min( int a, int b, int c ) => Min( Min( a, b ), c ); - - /// Returns the smallest of the four values - [MethodImpl( INLINE )] public static int Min( int a, int b, int c, int d ) => Min( Min( a, b ), Min( c, d ) ); - - /// Returns the largest of the two values - [MethodImpl( INLINE )] public static int Max( int a, int b ) => a > b ? a : b; - - /// Returns the largest of the three values - [MethodImpl( INLINE )] public static int Max( int a, int b, int c ) => Max( Max( a, b ), c ); - - /// Returns the largest of the four values - [MethodImpl( INLINE )] public static int Max( int a, int b, int c, int d ) => Max( Max( a, b ), Max( c, d ) ); - - /// Returns the smallest of the given values - [MethodImpl( INLINE )] public static float Min( params float[] values ) => values.Min(); - - /// Returns the largest of the given values - [MethodImpl( INLINE )] public static float Max( params float[] values ) => values.Max(); - - /// Returns the smallest of the given values - [MethodImpl( INLINE )] public static int Min( params int[] values ) => values.Min(); - - /// Returns the largest of the given values - [MethodImpl( INLINE )] public static int Max( params int[] values ) => values.Max(); - - /// Returns the minimum value of all components in the vector - [MethodImpl( INLINE )] public static float Min( Vector2 v ) => Min( v.x, v.y ); - - /// - [MethodImpl( INLINE )] public static float Min( Vector3 v ) => Min( v.x, v.y, v.z ); - - /// - [MethodImpl( INLINE )] public static float Min( Vector4 v ) => Min( v.x, v.y, v.z, v.w ); - - /// Returns the maximum value of all components in the vector - [MethodImpl( INLINE )] public static float Max( Vector2 v ) => Max( v.x, v.y ); - - /// - [MethodImpl( INLINE )] public static float Max( Vector3 v ) => Max( v.x, v.y, v.z ); - - /// - [MethodImpl( INLINE )] public static float Max( Vector4 v ) => Max( v.x, v.y, v.z, v.w ); - - #endregion - - #region Signs & Rounding - - /// The sign of the value. Returns -1 if negative, returns 1 if greater than or equal to 0 - [MethodImpl( INLINE )] public static float Sign( float value ) => value >= 0f ? 1 : -1; - - /// The sign of each component. Returns -1 if negative, returns 1 if greater than or equal to 0 - [MethodImpl( INLINE )] public static Vector2 Sign( Vector2 value ) => new Vector2( value.x >= 0f ? 1 : -1, value.y >= 0f ? 1 : -1 ); - - /// - [MethodImpl( INLINE )] public static Vector3 Sign( Vector3 value ) => new Vector3( value.x >= 0f ? 1 : -1, value.y >= 0f ? 1 : -1, value.z >= 0f ? 1 : -1 ); - - /// - [MethodImpl( INLINE )] public static Vector4 Sign( Vector4 value ) => new Vector4( value.x >= 0f ? 1 : -1, value.y >= 0f ? 1 : -1, value.z >= 0f ? 1 : -1, value.w >= 0f ? 1 : -1 ); - - /// Returns the sign of the value, either -1 if negative, or 1 if positive or 0 - [MethodImpl( INLINE )] public static int Sign( int value ) => value >= 0 ? 1 : -1; - - /// The sign of the value as an integer. Returns -1 if negative, returns 1 if greater than or equal to 0 - [MethodImpl( INLINE )] public static int SignAsInt( float value ) => value >= 0f ? 1 : -1; - - /// The sign of the value. Returns -1 if negative, return 0 if zero (or within the given threshold), returns 1 if positive - [MethodImpl( INLINE )] public static float SignWithZero( float value, float zeroThreshold = 0.000001f ) => Abs( value ) < zeroThreshold ? 0 : Sign( value ); - - /// The sign of each component. Returns -1 if negative, return 0 if zero (or within the given threshold), returns 1 if positive - [MethodImpl( INLINE )] public static Vector2 SignWithZero( Vector2 value, float zeroThreshold = 0.000001f ) => - new Vector2( - Abs( value.x ) < zeroThreshold ? 0 : Sign( value.x ), - Abs( value.y ) < zeroThreshold ? 0 : Sign( value.y ) - ); - - /// - [MethodImpl( INLINE )] public static Vector3 SignWithZero( Vector3 value, float zeroThreshold = 0.000001f ) => - new Vector3( - Abs( value.x ) < zeroThreshold ? 0 : Sign( value.x ), - Abs( value.y ) < zeroThreshold ? 0 : Sign( value.y ), - Abs( value.z ) < zeroThreshold ? 0 : Sign( value.z ) - ); - - /// - [MethodImpl( INLINE )] public static Vector4 SignWithZero( Vector4 value, float zeroThreshold = 0.000001f ) => - new Vector4( - Abs( value.x ) < zeroThreshold ? 0 : Sign( value.x ), - Abs( value.y ) < zeroThreshold ? 0 : Sign( value.y ), - Abs( value.z ) < zeroThreshold ? 0 : Sign( value.z ), - Abs( value.w ) < zeroThreshold ? 0 : Sign( value.w ) - ); - - /// Returns the sign of the value, either -1 if negative, 0 if zero, 1 if positive - [MethodImpl( INLINE )] public static int SignWithZero( int value ) => value == 0 ? 0 : Sign( value ); - - /// The sign of the value. Returns -1 if negative, return 0 if zero (or within the given threshold), returns 1 if positive - [MethodImpl( INLINE )] public static int SignWithZeroAsInt( float value, float zeroThreshold = 0.000001f ) => Abs( value ) < zeroThreshold ? 0 : SignAsInt( value ); - - /// Rounds the value down to the nearest integer - [MethodImpl( INLINE )] public static float Floor( float value ) => (float)Math.Floor( value ); - - /// Rounds the vector components down to the nearest integer - [MethodImpl( INLINE )] public static Vector2 Floor( Vector2 value ) => new Vector2( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Floor( Vector3 value ) => new Vector3( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ), (float)Math.Floor( value.z ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Floor( Vector4 value ) => new Vector4( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ), (float)Math.Floor( value.z ), (float)Math.Floor( value.w ) ); - - /// Rounds the value down to the nearest integer, returning an int value - [MethodImpl( INLINE )] public static int FloorToInt( float value ) => (int)Math.Floor( value ); - - /// Rounds the vector components down to the nearest integer, returning an integer vector - [MethodImpl( INLINE )] public static Vector2Int FloorToInt( Vector2 value ) => new Vector2Int( (int)Math.Floor( value.x ), (int)Math.Floor( value.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3Int FloorToInt( Vector3 value ) => new Vector3Int( (int)Math.Floor( value.x ), (int)Math.Floor( value.y ), (int)Math.Floor( value.z ) ); - - /// Rounds the value up to the nearest integer - [MethodImpl( INLINE )] public static float Ceil( float value ) => (float)Math.Ceiling( value ); - - /// Rounds the vector components up to the nearest integer - [MethodImpl( INLINE )] public static Vector2 Ceil( Vector2 value ) => new Vector2( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Ceil( Vector3 value ) => new Vector3( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ), (float)Math.Ceiling( value.z ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Ceil( Vector4 value ) => new Vector4( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ), (float)Math.Ceiling( value.z ), (float)Math.Ceiling( value.w ) ); - - /// Rounds the value up to the nearest integer, returning an int value - [MethodImpl( INLINE )] public static int CeilToInt( float value ) => (int)Math.Ceiling( value ); - - /// Rounds the vector components up to the nearest integer, returning an integer vector - [MethodImpl( INLINE )] public static Vector2Int CeilToInt( Vector2 value ) => new Vector2Int( (int)Math.Ceiling( value.x ), (int)Math.Ceiling( value.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3Int CeilToInt( Vector3 value ) => new Vector3Int( (int)Math.Ceiling( value.x ), (int)Math.Ceiling( value.y ), (int)Math.Ceiling( value.z ) ); - - /// Rounds the value to the nearest integer - [MethodImpl( INLINE )] public static float Round( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)MathF.Round( value, midpointRounding ); - - /// Rounds the vector components to the nearest integer - [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ), (float)Math.Round( value.z, midpointRounding ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ), (float)Math.Round( value.z, midpointRounding ), (float)Math.Round( value.w, midpointRounding ) ); - - /// Rounds the value to the nearest value, snapped to the given interval size - [MethodImpl( INLINE )] public static float Round( float value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)Math.Round( value / snapInterval, midpointRounding ) * snapInterval; - - /// Rounds the vector components to the nearest value, snapped to the given interval size - [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ), Round( value.w, snapInterval, midpointRounding ) ); - - /// Rounds the value to the nearest integer, returning an int value - [MethodImpl( INLINE )] public static int RoundToInt( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (int)Math.Round( value, midpointRounding ); - - /// Rounds the vector components to the nearest integer, returning an integer vector - [MethodImpl( INLINE )] public static Vector2Int RoundToInt( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ) ); - - /// - [MethodImpl( INLINE )] public static Vector3Int RoundToInt( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ), (int)Math.Round( value.z, midpointRounding ) ); - - #endregion - - #region Range Repeating - - /// Returns the fractional part of the value. Equivalent to x - floor(x) - [MethodImpl( INLINE )] public static float Frac( float x ) => x - Floor( x ); - - /// Returns the fractional part of the value for each component. Equivalent to v - floor(v) - [MethodImpl( INLINE )] public static Vector2 Frac( Vector2 v ) => new Vector2( v.x - Floor( v.x ), v.y - Floor( v.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Frac( Vector3 v ) => new Vector3( v.x - Floor( v.x ), v.y - Floor( v.y ), v.z - Floor( v.z ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Frac( Vector4 v ) => new Vector4( v.x - Floor( v.x ), v.y - Floor( v.y ), v.z - Floor( v.z ), v.w - Floor( v.w ) ); - - /// Repeats the given value in the interval specified by length - [MethodImpl( INLINE )] public static float Repeat( float value, float length ) => Clamp( value - Floor( value / length ) * length, 0.0f, length ); - - /// Modulo, but, behaves the way you want with negative values, for stuff like array[(n+1)%length] etc. - [MethodImpl( INLINE )] public static int Mod( int value, int length ) => value >= 0 ? value % length : ( value % length + length ) % length; - - /// Repeats a value within a range, going back and forth - [MethodImpl( INLINE )] public static float PingPong( float t, float length ) => length - Abs( Repeat( t, length * 2f ) - length ); - - /// Returns the height of in a triangle wave at time t going from 0 to 1 and back to 0 within the the given period - [MethodImpl( INLINE )] public static float TriangleWave( float t, float period = 1f ) { - float x = t / period; - return 1f - Abs( 2 * ( x - Floor( x ) ) - 1 ); - } - - /// Returns the greatest common divisor of the two numbers - public static int Gcd( int a, int b ) { - // special case bc we can't negate int.MinValue - if( a == int.MinValue || b == int.MinValue ) { - if( a == int.MinValue && b == int.MinValue ) - return int.MinValue; // the only negative return value, bc we can't negate this number - int v = Mathf.Max( a, b ).Abs(); - return v & -v; - } - - if( a == b ) - return a.Abs(); - ( a, b ) = ( Mathf.Abs( a ), Mathf.Abs( b ) ); - while( a != 0 && b != 0 ) - _ = a > b ? a %= b : b %= a; - return a | b; - } - - #endregion - - #region Smoothing & Easing Curves - - /// Applies cubic smoothing to the 0-1 interval, also known as the smoothstep function. Similar to an EaseInOut operation - [MethodImpl( INLINE )] public static float Smooth01( float x ) => x * x * ( 3 - 2 * x ); - - /// Applies quintic smoothing to the 0-1 interval, also known as the smootherstep function. Similar to an EaseInOut operation - [MethodImpl( INLINE )] public static float Smoother01( float x ) => x * x * x * ( x * ( x * 6 - 15 ) + 10 ); - - /// Applies trigonometric smoothing to the 0-1 interval. Similar to an EaseInOut operation - [MethodImpl( INLINE )] public static float SmoothCos01( float x ) => Cos( x * PI ) * -0.5f + 0.5f; - - /// Applies a gamma curve or something idk I've never used this function before but it was part of Unity's original Mathfs.cs and it's undocumented - public static float Gamma( float value, float absmax, float gamma ) { - bool negative = value < 0F; - float absval = Abs( value ); - if( absval > absmax ) - return negative ? -absval : absval; - - float result = Pow( absval / absmax, gamma ) * absmax; - return negative ? -result : result; - } - - #endregion - - #region Value & Vector interpolation - - /// Blends between a and b, based on the t-value. When t = 0 it returns a, when t = 1 it returns b, and any values between are blended linearly - /// The start value, when t is 0 - /// The start value, when t is 1 - /// The t-value from 0 to 1 representing position along the lerp - [MethodImpl( INLINE )] public static float Lerp( float a, float b, float t ) => ( 1f - t ) * a + t * b; - - /// Blends between a and b of each component, based on the t-value of each component in the t-vector. When t = 0 it returns a, when t = 1 it returns b, and any values between are blended linearly - /// The start value, when t is 0 - /// The start value, when t is 1 - /// The t-values from 0 to 1 representing position along the lerp - [MethodImpl( INLINE )] public static Vector2 Lerp( Vector2 a, Vector2 b, Vector2 t ) => new Vector2( Lerp( a.x, b.x, t.x ), Lerp( a.y, b.y, t.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Lerp( Vector3 a, Vector3 b, Vector3 t ) => new Vector3( Lerp( a.x, b.x, t.x ), Lerp( a.y, b.y, t.y ), Lerp( a.z, b.z, t.z ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Lerp( Vector4 a, Vector4 b, Vector4 t ) => new Vector4( Lerp( a.x, b.x, t.x ), Lerp( a.y, b.y, t.y ), Lerp( a.z, b.z, t.z ), Lerp( a.w, b.w, t.w ) ); - - /// Linearly blends between two rectangles, moving and resizing from the center. Note: this lerp is unclamped - /// The start value, when t is 0 - /// The start value, when t is 1 - /// The t-values from 0 to 1 representing position along the lerp - public static Rect Lerp( Rect a, Rect b, float t ) { - Vector2 center = Vector2.LerpUnclamped( a.center, b.center, t ); - Vector2 size = Vector2.LerpUnclamped( a.size, b.size, t ); - return new Rect( default, size ) { center = center }; - } - - /// Blends between a and b, based on the t-value. When t = 0 it returns a, when t = 1 it returns b, and any values between are blended linearly - /// The start value, when t is 0 - /// The start value, when t is 1 - /// The t-value from 0 to 1 representing position along the lerp, clamped between 0 and 1 - [MethodImpl( INLINE )] public static float LerpClamped( float a, float b, float t ) => Lerp( a, b, Clamp01( t ) ); - - /// Lerps between a and b, applying cubic smoothing to the t-value - /// The start value, when t is 0 - /// The start value, when t is 1 - /// The t-value from 0 to 1 representing position along the lerp, clamped between 0 and 1 - [MethodImpl( INLINE )] public static float LerpSmooth( float a, float b, float t ) => Lerp( a, b, Smooth01( Clamp01( t ) ) ); - - /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1 - /// The start of the range, where it would return 0 - /// The end of the range, where it would return 1 - /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated - [MethodImpl( INLINE )] public static float InverseLerp( float a, float b, float value ) => ( value - a ) / ( b - a ); - - /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1. - /// This safe version returns 0 if a == b, instead of a division by zero - /// The start of the range, where it would return 0 - /// The end of the range, where it would return 1 - /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated - [MethodImpl( INLINE )] public static float InverseLerpSafe( float a, float b, float value ) { - float den = b - a; - if( den == 0 ) - return 0; - return ( value - a ) / den; - } - - /// Given values between a and b in each component, returns their normalized locations in the given ranges, as t-values (interpolants) from 0 to 1 - /// The start of the ranges, where it would return 0 - /// The end of the ranges, where it would return 1 - /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated - [MethodImpl( INLINE )] public static Vector2 InverseLerp( Vector2 a, Vector2 b, Vector2 v ) => new Vector2( ( v.x - a.x ) / ( b.x - a.x ), ( v.y - a.y ) / ( b.y - a.y ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 InverseLerp( Vector3 a, Vector3 b, Vector3 v ) => new Vector3( ( v.x - a.x ) / ( b.x - a.x ), ( v.y - a.y ) / ( b.y - a.y ), ( v.z - a.z ) / ( b.z - a.z ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 InverseLerp( Vector4 a, Vector4 b, Vector4 v ) => new Vector4( ( v.x - a.x ) / ( b.x - a.x ), ( v.y - a.y ) / ( b.y - a.y ), ( v.z - a.z ) / ( b.z - a.z ), ( v.w - a.w ) / ( b.w - a.w ) ); - - /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) clamped between 0 and 1 - /// The start of the range, where it would return 0 - /// The end of the range, where it would return 1 - /// A value between a and b - [MethodImpl( INLINE )] public static float InverseLerpClamped( float a, float b, float value ) => Clamp01( ( value - a ) / ( b - a ) ); - - /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1, with cubic smoothing applied. - /// Equivalent to "smoothstep" in shader code - /// The start of the range, where it would return 0 - /// The end of the range, where it would return 1 - /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated - [MethodImpl( INLINE )] public static float InverseLerpSmooth( float a, float b, float value ) => Smooth01( Clamp01( ( value - a ) / ( b - a ) ) ); - - /// Remaps a value from the input range [iMin to iMax] into the output range [oMin to oMax]. - /// Equivalent to Lerp(oMin,oMax,InverseLerp(iMin,iMax,value)) - /// The start value of the input range - /// The end value of the input range - /// The start value of the output range - /// The end value of the output range - /// The value to remap - [MethodImpl( INLINE )] public static float Remap( float iMin, float iMax, float oMin, float oMax, float value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); - - /// - [MethodImpl( INLINE )] public static float Remap( float iMin, float iMax, float oMin, float oMax, int value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); - - /// Remaps values from the input range [iMin to iMax] into the output range [oMin to oMax] on a per-component basis. - /// Equivalent to Lerp(oMin,oMax,InverseLerp(iMin,iMax,value)) - /// The start values of the input ranges - /// The end values of the input ranges - /// The start values of the output ranges - /// The end values of the output ranges - /// The values to remap - [MethodImpl( INLINE )] public static Vector2 Remap( Vector2 iMin, Vector2 iMax, Vector2 oMin, Vector2 oMax, Vector2 value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); - - /// - [MethodImpl( INLINE )] public static Vector3 Remap( Vector3 iMin, Vector3 iMax, Vector3 oMin, Vector3 oMax, Vector3 value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); - - /// - [MethodImpl( INLINE )] public static Vector4 Remap( Vector4 iMin, Vector4 iMax, Vector4 oMin, Vector4 oMax, Vector4 value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); - - /// Remaps a value from the input range [iMin to iMax] into the output range [oMin to oMax], clamping to make sure it does not extrapolate. - /// Equivalent to Lerp(oMin,oMax,InverseLerpClamped(iMin,iMax,value)) - /// The start value of the input range - /// The end value of the input range - /// The start value of the output range - /// The end value of the output range - /// The value to remap - [MethodImpl( INLINE )] public static float RemapClamped( float iMin, float iMax, float oMin, float oMax, float value ) => Lerp( oMin, oMax, InverseLerpClamped( iMin, iMax, value ) ); - - /// Remaps a value from the input Rect to the output Rect - /// The input Rect - /// The output Rect - /// The input position in the input Rect space - [MethodImpl( INLINE )] public static Vector2 Remap( Rect iRect, Rect oRect, Vector2 iPos ) => Remap( iRect.min, iRect.max, oRect.min, oRect.max, iPos ); - - /// Remaps a value from the input Bounds to the output Bounds - /// The input Bounds - /// The output Bounds - /// The input position in the input Bounds space - [MethodImpl( INLINE )] public static Vector3 Remap( Bounds iBounds, Bounds oBounds, Vector3 iPos ) => Remap( iBounds.min, iBounds.max, oBounds.min, oBounds.max, iPos ); - - /// Remaps a value from the input range to the output range - /// The input range - /// The output range - /// The value to remap from the input range - [MethodImpl( INLINE )] public static float Remap( FloatRange inRange, FloatRange outRange, float value ) => Remap( inRange.a, inRange.b, outRange.a, outRange.b, value ); - - /// Remaps a value from the input range to the output range, clamping to make sure it does not extrapolate. - /// The input range - /// The output range - /// The value to remap from the input range - [MethodImpl( INLINE )] public static float RemapClamped( FloatRange inRange, FloatRange outRange, float value ) => RemapClamped( inRange.a, inRange.b, outRange.a, outRange.b, value ); - - /// Exponential interpolation, the multiplicative version of lerp, useful for values such as scaling or zooming - /// The start value - /// The end value - /// The t-value from 0 to 1 representing position along the eerp - [MethodImpl( INLINE )] public static float Eerp( float a, float b, float t ) => - t switch { - 0f => a, - 1f => b, - _ => Mathf.Pow( a, 1 - t ) * Mathf.Pow( b, t ) - }; - - /// Inverse exponential interpolation, the multiplicative version of InverseLerp, useful for values such as scaling or zooming - /// The start value - /// The end value - /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated - [MethodImpl( INLINE )] public static float InverseEerp( float a, float b, float v ) => Mathf.Log( a / v ) / Mathf.Log( a / b ); - - #endregion - - #region Movement helpers - - /// Moves a value current towards target - /// The current value - /// The value to move towards - /// The maximum change that should be applied to the value - public static float MoveTowards( float current, float target, float maxDelta ) { - if( Mathf.Abs( target - current ) <= maxDelta ) - return target; - return current + Mathf.Sign( target - current ) * maxDelta; - } - - /// Gradually changes a value towards a desired goal over time. - /// The value is smoothed by some spring-damper like function, which will never overshoot. - /// The function can be used to smooth any kind of value, positions, colors, scalars - /// The current position - /// The position we are trying to reach - /// The current velocity, this value is modified by the function every time you call it - /// Approximately the time it will take to reach the target. A smaller value will reach the target faster - /// Optionally allows you to clamp the maximum speed - public static float SmoothDamp( float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = Infinity ) { - float deltaTime = Time.deltaTime; - return SmoothDamp( current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime ); - } - - /// Gradually changes a value towards a desired goal over time. - /// The value is smoothed by some spring-damper like function, which will never overshoot. - /// The function can be used to smooth any kind of value, positions, colors, scalars - /// The current position - /// The position we are trying to reach - /// The current velocity, this value is modified by the function every time you call it - /// Approximately the time it will take to reach the target. A smaller value will reach the target faster - /// Optionally allows you to clamp the maximum speed - /// The time since the last call to this function. By default Time.deltaTime - public static float SmoothDamp( float current, float target, ref float currentVelocity, float smoothTime, [Uei.DefaultValue( "Mathf.Infinity" )] float maxSpeed, [Uei.DefaultValue( "Time.deltaTime" )] float deltaTime ) { - // Based on Game Programming Gems 4 Chapter 1.10 - smoothTime = Mathf.Max( 0.0001F, smoothTime ); - float omega = 2F / smoothTime; - - float x = omega * deltaTime; - float exp = 1F / ( 1F + x + 0.48F * x * x + 0.235F * x * x * x ); - float change = current - target; - float originalTo = target; - - // Clamp maximum speed - float maxChange = maxSpeed * smoothTime; - change = Mathf.Clamp( change, -maxChange, maxChange ); - target = current - change; - - float temp = ( currentVelocity + omega * change ) * deltaTime; - currentVelocity = ( currentVelocity - omega * temp ) * exp; - float output = target + ( change + temp ) * exp; - - // Prevent overshooting - if( originalTo - current > 0.0F == output > originalTo ) { - output = originalTo; - currentVelocity = ( output - originalTo ) / deltaTime; - } - - return output; - } - - #endregion - - #region Weighted sums - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - [MethodImpl( INLINE )] public static float WeightedSum( Vector2 w, float a, float b ) => a * w.x + b * w.y; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - /// The third value, weighted by w.z - [MethodImpl( INLINE )] public static float WeightedSum( Vector3 w, float a, float b, float c ) => a * w.x + b * w.y + c * w.z; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - /// The third value, weighted by w.z - /// The fourth value, weighted by w.w - [MethodImpl( INLINE )] public static float WeightedSum( Vector4 w, float a, float b, float c, float d ) => a * w.x + b * w.y + c * w.z + d * w.w; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - [MethodImpl( INLINE )] public static Vector2 WeightedSum( Vector2 w, Vector2 a, Vector2 b ) => a * w.x + b * w.y; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - /// The third value, weighted by w.z - [MethodImpl( INLINE )] public static Vector2 WeightedSum( Vector3 w, Vector2 a, Vector2 b, Vector2 c ) => a * w.x + b * w.y + c * w.z; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - /// The third value, weighted by w.z - /// The fourth value, weighted by w.w - [MethodImpl( INLINE )] public static Vector2 WeightedSum( Vector4 w, Vector2 a, Vector2 b, Vector2 c, Vector2 d ) => a * w.x + b * w.y + c * w.z + d * w.w; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - [MethodImpl( INLINE )] public static Vector3 WeightedSum( Vector3 w, Vector3 a, Vector3 b ) => a * w.x + b * w.y; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - /// The third value, weighted by w.z - [MethodImpl( INLINE )] public static Vector3 WeightedSum( Vector3 w, Vector3 a, Vector3 b, Vector3 c ) => a * w.x + b * w.y + c * w.z; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - /// The third value, weighted by w.z - /// The fourth value, weighted by w.w - [MethodImpl( INLINE )] public static Vector3 WeightedSum( Vector4 w, Vector3 a, Vector3 b, Vector3 c, Vector3 d ) => a * w.x + b * w.y + c * w.z + d * w.w; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - [MethodImpl( INLINE )] public static Vector4 WeightedSum( Vector4 w, Vector4 a, Vector4 b ) => a * w.x + b * w.y; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - /// The third value, weighted by w.z - [MethodImpl( INLINE )] public static Vector4 WeightedSum( Vector4 w, Vector4 a, Vector4 b, Vector4 c ) => a * w.x + b * w.y + c * w.z; - - /// Multiplies each component of w by the input values, and returns their sum - /// The weights (per component) to apply to the rest of the values - /// The first value, weighted by w.x - /// The second value, weighted by w.y - /// The third value, weighted by w.z - /// The fourth value, weighted by w.w - [MethodImpl( INLINE )] public static Vector4 WeightedSum( Vector4 w, Vector4 a, Vector4 b, Vector4 c, Vector4 d ) => a * w.x + b * w.y + c * w.z + d * w.w; - - #endregion - - #region Vector math - - /// The determinant is equivalent to the dot product, but with one vector rotated 90 degrees. - /// Note that det(a,b) != det(b,a). It's equivalent to a.x * b.y - a.y * b.x. - /// It is also known as the 2D Cross Product, Wedge Product, Outer Product and Perpendicular Dot Product - public static float Determinant /*or Cross*/( Vector2 a, Vector2 b ) => a.x * b.y - a.y * b.x; // 2D "cross product" - - /// Returns the direction and magnitude of the vector. Cheaper than calculating length and normalizing it separately - public static (Vector2 dir, float magnitude ) GetDirAndMagnitude( Vector2 v ) { - float magnitude = v.magnitude; - return ( v / magnitude, magnitude ); - } - - /// - public static (Vector3 dir, float magnitude ) GetDirAndMagnitude( Vector3 v ) { - float magnitude = v.magnitude; - return ( v / magnitude, magnitude ); - } - - /// Clamps the length of the vector between min and max - /// The vector to clamp - /// Minimum length - /// Maximum length - public static Vector2 ClampMagnitude( Vector2 v, float min, float max ) { - float mag = v.magnitude; - return mag < min ? ( v / mag ) * min : mag > max ? ( v / mag ) * max : v; - } - - /// - public static Vector3 ClampMagnitude( Vector3 v, float min, float max ) { - float mag = v.magnitude; - return mag < min ? ( v / mag ) * min : mag > max ? ( v / mag ) * max : v; - } - - /// Returns the average/center of the two input vectors - [MethodImpl( INLINE )] public static Vector2 Average( Vector2 a, Vector2 b ) => ( a + b ) / 2f; - - /// Returns the average/center of the two input vectors - [MethodImpl( INLINE )] public static Vector3 Average( Vector3 a, Vector3 b ) => ( a + b ) / 2f; - - /// Returns the average/halfway direction between the two input direction vectors. Note that this presumes both aDir and bDir have the same length - [MethodImpl( INLINE )] public static Vector2 AverageDir( Vector2 aDir, Vector2 bDir ) => ( aDir + bDir ).normalized; - - /// Returns the average/halfway direction between the two input direction vectors. Note that this presumes both aDir and bDir have the same length - [MethodImpl( INLINE )] public static Vector3 AverageDir( Vector3 aDir, Vector3 bDir ) => ( aDir + bDir ).normalized; - - /// Returns the squared distance between two points. - /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter - [MethodImpl( INLINE )] public static float DistanceSquared( Vector2 a, Vector2 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square(); - - /// Returns the squared distance between two points. - /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter - [MethodImpl( INLINE )] public static float DistanceSquared( Vector3 a, Vector3 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square() + ( a.z - b.z ).Square(); - - /// Returns the squared distance between two points. - /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter - [MethodImpl( INLINE )] public static float DistanceSquared( Vector4 a, Vector4 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square() + ( a.z - b.z ).Square() + ( a.w - b.w ).Square(); - - #endregion - - #region Angles & Rotation - - /// Returns the direction of the input angle, as a normalized vector - /// The input angle, in radians - /// - [MethodImpl( INLINE )] public static Vector2 AngToDir( float aRad ) => new Vector2( Mathf.Cos( aRad ), Mathf.Sin( aRad ) ); - - /// Returns the angle of the input vector, in radians. You can also use myVector.Angle() - /// The vector to get the angle of. It does not have to be normalized - /// - [MethodImpl( INLINE )] public static float DirToAng( Vector2 vec ) => Mathf.Atan2( vec.y, vec.x ); - - /// Returns a 2D orientation from a vector, representing the X axis - /// The direction to create a 2D orientation from (does not have to be normalized) - [MethodImpl( INLINE )] public static Quaternion DirToOrientation( Vector2 v ) { - v.Normalize(); - v.x += 1; - v.Normalize(); - return new Quaternion( 0, 0, v.y, v.x ); - } - - /// Returns a 2D Pose from a point and a vector, representing the X axis - /// The location of the pose - /// The direction to create a 2D orientation from (does not have to be normalized) - [MethodImpl( INLINE )] public static Pose PointDirToPose( Vector2 pt, Vector2 v ) => new Pose( pt, DirToOrientation( v ) ); - - /// Linearly blends between two poses. The position will lerp, while the rotation will slerp. Note: this lerp is unclamped - /// Pose at t = 0 - /// Pose at t = 1 - /// The t-value to blend from a to b, from 0 to 1 (values outside will extrapolate) - public static Pose Lerp( Pose a, Pose b, float t ) => - new Pose( - Vector3.LerpUnclamped( a.position, b.position, t ), - Quaternion.SlerpUnclamped( a.rotation, b.rotation, t ) - ); - - /// Returns a matrix representing a 2D position and rotation - /// The location of the matrix - /// The direction of the X axis (has to be normalized) - [MethodImpl( INLINE )] public static Matrix4x4 GetMatrixFrom2DPointDir( Vector2 point, Vector2 tangent ) { - Vector2 N = tangent.Rotate90CCW(); - return new Matrix4x4( - new Vector4( tangent.x, tangent.y, 0, 0 ), - new Vector4( N.x, N.y, 0, 0 ), - new Vector4( 0, 0, 1, 0 ), - new Vector4( point.x, point.y, 0, 1 ) - ); - } - - /// Returns the signed curvature at a point in a curve, in radians per distance unit (equivalent to the reciprocal radius of the osculating circle) - /// The first derivative of the point in the curve - /// The second derivative of the point in the curve - [MethodImpl( INLINE )] public static float GetCurvature( Vector2 velocity, Vector2 acceleration ) { - float dMag = velocity.magnitude; - return Determinant( velocity, acceleration ) / ( dMag * dMag * dMag ); - } - - /// Returns a pseudovector of a point in a curve, where the magnitude is the curvature in radians per distance unit, and the direction is the axis of curvature - /// The first derivative of the point in the curve - /// The second derivative of the point in the curve - [MethodImpl( INLINE )] public static Vector3 GetCurvature( Vector3 velocity, Vector3 acceleration ) { - float dMag = velocity.magnitude; - return Vector3.Cross( velocity, acceleration ) / ( dMag * dMag * dMag ); - } - - /// Returns the torsion of a given point in a curve, in radians per distance unit - /// The first derivative of the point in the curve - /// The second derivative of the point in the curve - /// The third derivative of the point in the curve - [MethodImpl( INLINE )] public static float GetTorsion( Vector3 velocity, Vector3 acceleration, Vector3 jerk ) { - Vector3 cVector = Vector3.Cross( velocity, acceleration ); - return Vector3.Dot( cVector, jerk ) / cVector.sqrMagnitude; - } - - /// Returns the frenet-serret (curvature-based) normal direction at a given point in a curve - /// The first derivative of the point in the curve - /// The second derivative of the point in the curve - [MethodImpl( INLINE )] public static Vector3 GetArcNormal( Vector3 velocity, Vector3 acceleration ) => Vector3.Cross( Vector3.Cross( velocity, acceleration ).normalized, velocity.normalized ); - - /// Returns the frenet-serret (curvature-based) binormal direction at a given point in a curve - /// The first derivative of the point in the curve - /// The second derivative of the point in the curve - [MethodImpl( INLINE )] public static Vector3 GetArcBinormal( Vector3 velocity, Vector3 acceleration ) => Vector3.Cross( velocity, acceleration ).normalized; - - /// Returns a normal direction given a reference up vector and a tangent direction - /// The tangent direction (does not have to be normalized) - /// The reference up vector. The normal will be perpendicular to both the supplied up vector and the curve - [MethodImpl( INLINE )] public static Vector3 GetNormalFromLookTangent( Vector3 tangent, Vector3 up ) => Vector3.Cross( up, tangent ).normalized; - - /// Returns the binormal from a vector, given a reference up vector. - /// The binormal will attempt to be as aligned with the reference vector as possible, - /// while still being perpendicular to the tangent - /// The tangent direction (does not have to be normalized) - /// The reference up vector. The normal will be perpendicular to both the supplied up vector and the tangent - [MethodImpl( INLINE )] public static Vector3 GetBinormalFromLookTangent( Vector3 tangent, Vector3 up ) { - Vector3 normal = Vector3.Cross( up, tangent ).normalized; - return Vector3.Cross( tangent.normalized, normal ); - } - - /// Returns the frenet-serret (curvature-based) orientation of a point in a curve with the given velocity and acceleration values, where the Z direction is tangent to the curve. - /// The X axis will point to the inner arc of the current curvature - /// The first derivative of the point in the curve - /// The second derivative of the point in the curve - [MethodImpl( INLINE )] public static Quaternion GetArcOrientation( Vector3 velocity, Vector3 acceleration ) { - Vector3 binormal = Vector3.Cross( velocity, acceleration ); - return Quaternion.LookRotation( velocity, binormal ); - } - - /// Returns the signed angle between a and b, in the range -tau/2 to tau/2 (-pi to pi) - [MethodImpl( INLINE )] public static float SignedAngle( Vector2 a, Vector2 b ) => AngleBetween( a, b ) * Mathf.Sign( Determinant( a, b ) ); // -tau/2 to tau/2 - - /// Returns the shortest angle between a and b, in the range 0 to tau/2 (0 to pi) - [MethodImpl( INLINE )] public static float AngleBetween( Vector2 a, Vector2 b ) => Mathf.Acos( Vector2.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); - - /// - [MethodImpl( INLINE )] public static float AngleBetween( Vector3 a, Vector3 b ) => Mathf.Acos( Vector3.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); - - /// Returns the clockwise angle between from and to, in the range 0 to tau (0 to 2*pi) - [MethodImpl( INLINE )] public static float AngleFromToCW( Vector2 from, Vector2 to ) => Determinant( from, to ) < 0 ? AngleBetween( from, to ) : TAU - AngleBetween( from, to ); - - /// Returns the counterclockwise angle between from and to, in the range 0 to tau (0 to 2*pi) - [MethodImpl( INLINE )] public static float AngleFromToCCW( Vector2 from, Vector2 to ) => Determinant( from, to ) > 0 ? AngleBetween( from, to ) : TAU - AngleBetween( from, to ); - - /// Blends between the aRad and bRad angles, based on the input t-value between 0 and 1 - /// The start value, in radians - /// The end value, in radians - /// The t-value between 0 and 1 - public static float LerpAngle( float aRad, float bRad, float t ) { - float delta = Repeat( ( bRad - aRad ), TAU ); - if( delta > PI ) - delta -= TAU; - return aRad + delta * Clamp01( t ); - } - - /// Returns the shortest angle between the two input angles, in radians - [MethodImpl( INLINE )] public static float DeltaAngle( float a, float b ) => ( b - a + PI ).Repeat( TAU ) - PI; - - /// Given an angle between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1 - /// The start angle of the range (in radians), where it would return 0 - /// The end angle of the range (in radians), where it would return 1 - /// An angle between a and b - public static float InverseLerpAngle( float a, float b, float v ) { - float angBetween = DeltaAngle( a, b ); - b = a + angBetween; // removes any a->b discontinuity - float h = a + angBetween * 0.5f; // halfway angle - v = h + DeltaAngle( h, v ); // get offset from h, and offset by h - return InverseLerpClamped( a, b, v ); - } - - #endregion - - #region Angular movement helpers - - /// Same as MoveTowards but makes sure the angles interpolate correctly when they wrap around a full turn. - /// Variables current and target are assumed to be in radians. - /// For optimization reasons, negative values of maxDelta are not supported and may cause oscillation. - /// To push current away from a target angle, add 180 to that angle instead. - /// The current angle - /// The angle to move towards - /// The maximum change that should be applied to the value - public static float MoveTowardsAngle( float current, float target, float maxDelta ) { - float deltaAngle = DeltaAngle( current, target ); - if( -maxDelta < deltaAngle && deltaAngle < maxDelta ) - return target; - target = current + deltaAngle; - return MoveTowards( current, target, maxDelta ); - } - - /// Gradually changes an angle given in radians towards a desired goal angle over time. - /// The value is smoothed by some spring-damper like function. - /// The function can be used to smooth any kind of value, positions, colors, scalars. The most common use is for smoothing a follow camera. - /// The current angle - /// The angle we are trying to reach - /// The current angular velocity, this value is modified by the function every time you call it - /// Approximately the time it will take to reach the target. A smaller value will reach the target faster - /// Optionally allows you to clamp the maximum speed - public static float SmoothDampAngle( float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = Infinity ) { - float deltaTime = Time.deltaTime; - return SmoothDampAngle( current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime ); - } - - /// Gradually changes an angle given in radians towards a desired goal angle over time. - /// The value is smoothed by some spring-damper like function. - /// The function can be used to smooth any kind of value, positions, colors, scalars. The most common use is for smoothing a follow camera. - /// The current angle - /// The angle we are trying to reach - /// The current angular velocity, this value is modified by the function every time you call it - /// Approximately the time it will take to reach the target. A smaller value will reach the target faster - /// Optionally allows you to clamp the maximum speed - /// The time since the last call to this function. By default Time.deltaTime - public static float SmoothDampAngle( float current, float target, ref float currentVelocity, float smoothTime, [Uei.DefaultValue( "Mathf.Infinity" )] float maxSpeed, [Uei.DefaultValue( "Time.deltaTime" )] float deltaTime ) { - target = current + DeltaAngle( current, target ); - return SmoothDamp( current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime ); - } - - #endregion - - #region Shape coordinate remapping - - /// Given a position within a -1 to 1 square, remaps it to the unit circle - /// The input position inside the square - public static Vector2 SquareToDisc( Vector2 c ) { - c.x = c.x.ClampNeg1to1(); - c.y = c.y.ClampNeg1to1(); - float u = c.x * Sqrt( 1 - ( c.y * c.y ) / 2 ); - float v = c.y * Sqrt( 1 - ( c.x * c.x ) / 2 ); - return new Vector2( u, v ); - } - - /// Given a position within the unit circle, remaps it to a square in the -1 to 1 range - /// The input position inside the circle - public static Vector2 DiscToSquare( Vector2 c ) { - c = c.ClampMagnitude( 0, 1 ); - float u2 = c.x * c.x; - float v2 = c.y * c.y; - Vector2 n = new Vector2( 1, -1 ); - Vector2 p = new Vector2( 2, 2 ) + n * ( u2 - v2 ); - Vector2 q = 2 * SQRT2 * c; - Vector2 smolVec = Vector2.one * 0.0001f; - return 0.5f * ( Vector2.Max( smolVec, p + q ).Sqrt() - Vector2.Max( smolVec, p - q ).Sqrt() ); - } - - #endregion - - } - +// Some of this code is similar to Unity's original Mathf source to match functionality. +// The original Mathf.cs source https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Math/Mathf.cs +// ...and the trace amounts of it left in here is copyright (c) Unity Technologies with license: https://unity3d.com/legal/licenses/Unity_Reference_Only_License +// +// Collected and expanded upon to by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; +using Uei = UnityEngine.Internal; +using System.Linq; // used for arbitrary count min/max functions, so it's safe and won't allocate garbage don't worry~ +using System.Runtime.CompilerServices; + +namespace Freya { + + /// The core math helper class. It has functions mostly for single values, but also vector helpers + public static class Mathfs { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + public static readonly bool[] bools = { false, true }; + + #region Constants + + /// The circle constant. Defined as the circumference of a circle divided by its radius. Equivalent to 2*pi + public const float TAU = 6.28318530717959f; + + /// An obscure circle constant. Defined as the circumference of a circle divided by its diameter. Equivalent to 0.5*tau + public const float PI = 3.14159265359f; + + /// Euler's number. The base of the natural logarithm. f(x)=e^x is equal to its own derivative + public const float E = 2.71828182846f; + + /// The golden ratio. It is the value of a/b where a/b = (a+b)/a. It's the positive root of x^2-x-1 + public const float GOLDEN_RATIO = 1.61803398875f; + + /// The square root of two. The length of the vector (1,1) + public const float SQRT2 = 1.41421356237f; + + /// The reciprocal of the square root of two. The components of the vector (1,1) + public const float RSQRT2 = 1f / SQRT2; + + /// Multiply an angle in degrees by this, to convert it to radians + public const float Deg2Rad = TAU / 360f; + + /// Multiply an angle in radians by this, to convert it to degrees + public const float Rad2Deg = 360f / TAU; + + #endregion + + #region Math operations + + /// Returns the square root of the given value + [MethodImpl( INLINE )] public static float Sqrt( float value ) => (float)Math.Sqrt( value ); + + /// Returns the square root of each component + [MethodImpl( INLINE )] public static Vector2 Sqrt( Vector2 v ) => new Vector2( Sqrt( v.x ), Sqrt( v.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Sqrt( Vector3 v ) => new Vector3( Sqrt( v.x ), Sqrt( v.y ), Sqrt( v.z ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Sqrt( Vector4 v ) => new Vector4( Sqrt( v.x ), Sqrt( v.y ), Sqrt( v.z ), Sqrt( v.w ) ); + + /// Returns the cube root of the given value, properly handling negative values unlike Pow(v,1/3) + [MethodImpl( INLINE )] public static float Cbrt( float value ) => value < 0 ? -Pow( -value, 1f / 3f ) : Pow( value, 1f / 3f ); + + /// Returns value raised to the power of exponent + [MethodImpl( INLINE )] public static float Pow( float value, float exponent ) => (float)Math.Pow( value, exponent ); + + /// Returns e to the power of the given value + [MethodImpl( INLINE )] public static float Exp( float power ) => (float)Math.Exp( power ); + + /// Returns the logarithm of a value, with the given base + [MethodImpl( INLINE )] public static float Log( float value, float @base ) => (float)Math.Log( value, @base ); + + /// Returns the natural logarithm of the given value + [MethodImpl( INLINE )] public static float Log( float value ) => (float)Math.Log( value ); + + /// Returns the base 10 logarithm of the given value + [MethodImpl( INLINE )] public static float Log10( float value ) => (float)Math.Log10( value ); + + /// Returns the binomial coefficient n over k + public static ulong BinomialCoef( uint n, uint k ) { + // source: https://blog.plover.com/math/choose.html + ulong r = 1; + if( k > n ) return 0; + for( ulong d = 1; d <= k; d++ ) { + r *= n--; + r /= d; + } + + return r; + // mathematically clean but extremely prone to overflow + //return Factorial( n ) / ( Factorial( k ) * Factorial( n - k ) ); + } + + /// Returns the Factorial of a given value from 0 to 12 + /// A value between 0 and 12 (integers can't store the factorial of 13 or above) + [MethodImpl( INLINE )] public static int Factorial( uint value ) { + if( value <= 12 ) + return factorialInt[value]; + if( value <= 20 ) + throw new OverflowException( $"The Factorial of {value} is too big for integer representation, please use {nameof(FactorialLong)} instead" ); + throw new OverflowException( $"The Factorial of {value} is too big for integer representation" ); + } + + /// Returns the Factorial of a given value from 0 to 20 + /// A value between 0 and 20 (neither long nor ulong can store values large enough for the factorial of 21) + [MethodImpl( INLINE )] public static long FactorialLong( uint value ) { + if( value <= 20 ) + return factorialLong[value]; + throw new OverflowException( $"The Factorial of {value} is too big for integer representation, even unsigned longs, soooo, rip" ); + } + + static readonly long[] factorialLong = { + /*0*/ 1, + /*1*/ 1, + /*2*/ 2, + /*3*/ 6, + /*4*/ 24, + /*5*/ 120, + /*6*/ 720, + /*7*/ 5040, + /*8*/ 40320, + /*9*/ 362880, + /*10*/ 3628800, + /*11*/ 39916800, + /*12*/ 479001600, + /*13*/ 6227020800, + /*14*/ 87178291200, + /*15*/ 1307674368000, + /*16*/ 20922789888000, + /*17*/ 355687428096000, + /*18*/ 6402373705728000, + /*19*/ 121645100408832000, + /*20*/ 2432902008176640000 + }; + + static readonly int[] factorialInt = { + /*0*/ 1, + /*1*/ 1, + /*2*/ 2, + /*3*/ 6, + /*4*/ 24, + /*5*/ 120, + /*6*/ 720, + /*7*/ 5040, + /*8*/ 40320, + /*9*/ 362880, + /*10*/ 3628800, + /*11*/ 39916800, + /*12*/ 479001600 + }; + + #endregion + + #region Floating point shenanigans + + /// A very small value, used for various floating point inaccuracy thresholds + public static readonly float Epsilon = UnityEngineInternal.MathfInternal.IsFlushToZeroEnabled ? UnityEngineInternal.MathfInternal.FloatMinNormal : UnityEngineInternal.MathfInternal.FloatMinDenormal; + + /// float.PositiveInfinity + public const float Infinity = float.PositiveInfinity; + + /// float.NegativeInfinity + public const float NegativeInfinity = float.NegativeInfinity; + + /// Returns whether or not two values are approximately equal. + /// They are considered equal if they are within a Mathfs.Epsilon*8 or max(a,b)*0.000001f range of each other + /// The first value to compare + /// The second value to compare + [MethodImpl( INLINE )] public static bool Approximately( float a, float b ) => Abs( b - a ) < Max( 0.000001f * Max( Abs( a ), Abs( b ) ), Epsilon * 8 ); + + /// + [MethodImpl( INLINE )] public static bool Approximately( Vector2 a, Vector2 b ) => Approximately( a.x, b.x ) && Approximately( a.y, b.y ); + + /// + [MethodImpl( INLINE )] public static bool Approximately( Vector3 a, Vector3 b ) => Approximately( a.x, b.x ) && Approximately( a.y, b.y ) && Approximately( a.z, b.z ); + + /// + [MethodImpl( INLINE )] public static bool Approximately( Vector4 a, Vector4 b ) => Approximately( a.x, b.x ) && Approximately( a.y, b.y ) && Approximately( a.z, b.z ) && Approximately( a.w, b.w ); + + /// + [MethodImpl( INLINE )] public static bool Approximately( Color a, Color b ) => Approximately( a.r, b.r ) && Approximately( a.g, b.g ) && Approximately( a.b, b.b ) && Approximately( a.a, b.a ); + + #endregion + + #region Trigonometry + + /// Returns the cosine of the given angle. Equivalent to the x-component of a unit vector with the same angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Cos( float angRad ) => (float)Math.Cos( angRad ); + + /// Returns the sine of the given angle. Equivalent to the y-component of a unit vector with the same angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Sin( float angRad ) => (float)Math.Sin( angRad ); + + /// Returns the tangent of the given angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Tan( float angRad ) => (float)Math.Tan( angRad ); + + /// Returns the arc cosine of the given value, in radians + /// A value between -1 and 1 + [MethodImpl( INLINE )] public static float Acos( float value ) => (float)Math.Acos( value ); + + /// Returns the arc sine of the given value, in radians + /// A value between -1 and 1 + [MethodImpl( INLINE )] public static float Asin( float value ) => (float)Math.Asin( value ); + + /// Returns the arc tangent of the given value, in radians + /// A value between -1 and 1 + [MethodImpl( INLINE )] public static float Atan( float value ) => (float)Math.Atan( value ); + + /// Returns the angle of a vector. I don't recommend using this function, it's confusing~ Use Mathfs.DirToAng instead + /// The y component of the vector. They're flipped yeah I know but this is how everyone implements if for some godforsaken reason + /// The x component of the vector. They're flipped yeah I know but this is how everyone implements if for some godforsaken reason + [MethodImpl( INLINE )] public static float Atan2( float y, float x ) => (float)Math.Atan2( y, x ); + + /// Returns the cosecant of the given angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Csc( float angRad ) => 1f / (float)Math.Sin( angRad ); + + /// Returns the secant of the given angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Sec( float angRad ) => 1f / (float)Math.Cos( angRad ); + + /// Returns the cotangent of the given angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Cot( float angRad ) => 1f / (float)Math.Tan( angRad ); + + /// Returns the versine of the given angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Ver( float angRad ) => 1 - (float)Math.Cos( angRad ); + + /// Returns the coversine of the given angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Cvs( float angRad ) => 1 - (float)Math.Sin( angRad ); + + /// Returns the chord of the given angle + /// Angle in radians + [MethodImpl( INLINE )] public static float Crd( float angRad ) => 2 * (float)Math.Sin( angRad / 2 ); + + const double SINC_W = 0.01; + const double SINC_P_C2 = -1 / 6.0; + const double SINC_P_C4 = 1 / 120.0; + const double SINCRCP_P_C2 = 1 / 6.0; + const double SINCRCP_P_C4 = 7 / 360.0; + + /// The unnormalized sinc function sin(x)/x, properly handling the removable singularity around x = 0 + /// The input value for the Sinc function + public static float Sinc( float x ) => (float)Sinc( (double)x ); + + /// + public static double Sinc( double x ) { + x = Math.Abs( x ); // sinc is symmetric + if( x < SINC_W ) { + // approximate the singularity w. a polynomial + double x2 = x * x; + double x4 = x2 * x2; + return 1 + SINC_P_C2 * x2 + SINC_P_C4 * x4; + } + + return Math.Sin( x ) / x; + } + + /// The unnormalized reciprocal sinc function x/sin(x), properly handling the removable singularity around x = 0 + /// The input value for the reciprocal Sinc function + public static float SincRcp( float x ) => (float)SincRcp( (double)x ); + + /// + public static double SincRcp( double x ) { + x = Math.Abs( x ); // sinc is symmetric + if( x < SINC_W ) { + // approximate the singularity w. a polynomial + double x2 = x * x; + double x4 = x2 * x2; + return 1 + SINCRCP_P_C2 * x2 + SINCRCP_P_C4 * x4; + } + + return x / Math.Sin( x ); + } + + #endregion + + #region Hyperbolic Trigonometry + + /// Returns the hyperbolic cosine of the given hyperbolic angle + [MethodImpl( INLINE )] public static float Cosh( float x ) => (float)Math.Cosh( x ); + + /// Returns the hyperbolic sine of the given hyperbolic angle + [MethodImpl( INLINE )] public static float Sinh( float x ) => (float)Math.Sinh( x ); + + /// Returns the hyperbolic tangent of the given hyperbolic angle + [MethodImpl( INLINE )] public static float Tanh( float x ) => (float)Math.Tanh( x ); + + /// Returns the hyperbolic arc cosine of the given value + [MethodImpl( INLINE )] public static float Acosh( float x ) => (float)Math.Log( x + Mathf.Sqrt( x * x - 1 ) ); + + /// Returns the hyperbolic arc sine of the given value + [MethodImpl( INLINE )] public static float Asinh( float x ) => (float)Math.Log( x + Mathf.Sqrt( x * x + 1 ) ); + + /// Returns the hyperbolic arc tangent of the given value + [MethodImpl( INLINE )] public static float Atanh( float x ) => (float)( 0.5 * Math.Log( ( 1 + x ) / ( 1 - x ) ) ); + + #endregion + + #region Absolute Values + + /// Returns the absolute value. Basically makes negative numbers positive + [MethodImpl( INLINE )] public static float Abs( float value ) => Math.Abs( value ); + + /// + [MethodImpl( INLINE )] public static int Abs( int value ) => Math.Abs( value ); + + /// Returns the absolute value, per component. Basically makes negative numbers positive + [MethodImpl( INLINE )] public static Vector2 Abs( Vector2 v ) => new Vector2( Abs( v.x ), Abs( v.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Abs( Vector3 v ) => new Vector3( Abs( v.x ), Abs( v.y ), Abs( v.z ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Abs( Vector4 v ) => new Vector4( Abs( v.x ), Abs( v.y ), Abs( v.z ), Abs( v.w ) ); + + #endregion + + #region Clamping + + /// Returns the value clamped between min and max + /// The value to clamp + /// The minimum value + /// The maximum value + public static float Clamp( float value, float min, float max ) => value < min ? min : value > max ? max : value; + + /// Clamps each component between min and max + public static Vector2 Clamp( Vector2 v, Vector2 min, Vector2 max ) => + new Vector2( + v.x < min.x ? min.x : v.x > max.x ? max.x : v.x, + v.y < min.y ? min.y : v.y > max.y ? max.y : v.y + ); + + /// + public static Vector3 Clamp( Vector3 v, Vector3 min, Vector3 max ) => + new Vector3( + v.x < min.x ? min.x : v.x > max.x ? max.x : v.x, + v.y < min.y ? min.y : v.y > max.y ? max.y : v.y, + v.z < min.z ? min.z : v.z > max.z ? max.z : v.z + ); + + /// + public static Vector4 Clamp( Vector4 v, Vector4 min, Vector4 max ) => + new Vector4( + v.x < min.x ? min.x : v.x > max.x ? max.x : v.x, + v.y < min.y ? min.y : v.y > max.y ? max.y : v.y, + v.z < min.z ? min.z : v.z > max.z ? max.z : v.z, + v.w < min.w ? min.w : v.w > max.w ? max.w : v.w + ); + + /// + public static int Clamp( int value, int min, int max ) => value < min ? min : value > max ? max : value; + + /// Returns the value clamped between 0 and 1 + public static float Clamp01( float value ) => value < 0f ? 0f : value > 1f ? 1f : value; + + /// Clamps each component between 0 and 1 + public static Vector2 Clamp01( Vector2 v ) => + new Vector2( + v.x < 0f ? 0f : v.x > 1f ? 1f : v.x, + v.y < 0f ? 0f : v.y > 1f ? 1f : v.y + ); + + /// + public static Vector3 Clamp01( Vector3 v ) => + new Vector3( + v.x < 0f ? 0f : v.x > 1f ? 1f : v.x, + v.y < 0f ? 0f : v.y > 1f ? 1f : v.y, + v.z < 0f ? 0f : v.z > 1f ? 1f : v.z + ); + + /// + public static Vector4 Clamp01( Vector4 v ) => + new Vector4( + v.x < 0f ? 0f : v.x > 1f ? 1f : v.x, + v.y < 0f ? 0f : v.y > 1f ? 1f : v.y, + v.z < 0f ? 0f : v.z > 1f ? 1f : v.z, + v.w < 0f ? 0f : v.w > 1f ? 1f : v.w + ); + + /// Clamps the value between -1 and 1 + public static float ClampNeg1to1( float value ) => value < -1f ? -1f : value > 1f ? 1f : value; + + /// Clamps each component between -1 and 1 + public static Vector2 ClampNeg1to1( Vector2 v ) => + new Vector2( + v.x < -1f ? -1f : v.x > 1f ? 1f : v.x, + v.y < -1f ? -1f : v.y > 1f ? 1f : v.y + ); + + /// Clamps each component between -1 and 1 + public static Vector3 ClampNeg1to1( Vector3 v ) => + new Vector3( + v.x < -1f ? -1f : v.x > 1f ? 1f : v.x, + v.y < -1f ? -1f : v.y > 1f ? 1f : v.y, + v.z < -1f ? -1f : v.z > 1f ? 1f : v.z + ); + + /// Clamps each component between -1 and 1 + public static Vector4 ClampNeg1to1( Vector4 v ) => + new Vector4( + v.x < -1f ? -1f : v.x > 1f ? 1f : v.x, + v.y < -1f ? -1f : v.y > 1f ? 1f : v.y, + v.z < -1f ? -1f : v.z > 1f ? 1f : v.z, + v.w < -1f ? -1f : v.w > 1f ? 1f : v.w + ); + + #endregion + + #region Min & Max + + /// Returns the smallest of the two values + [MethodImpl( INLINE )] public static float Min( float a, float b ) => a < b ? a : b; + + /// Returns the smallest of the three values + [MethodImpl( INLINE )] public static float Min( float a, float b, float c ) => Min( Min( a, b ), c ); + + /// Returns the smallest of the four values + [MethodImpl( INLINE )] public static float Min( float a, float b, float c, float d ) => Min( Min( a, b ), Min( c, d ) ); + + /// Returns the largest of the two values + [MethodImpl( INLINE )] public static float Max( float a, float b ) => a > b ? a : b; + + /// Returns the largest of the three values + [MethodImpl( INLINE )] public static float Max( float a, float b, float c ) => Max( Max( a, b ), c ); + + /// Returns the largest of the four values + [MethodImpl( INLINE )] public static float Max( float a, float b, float c, float d ) => Max( Max( a, b ), Max( c, d ) ); + + /// Returns the smallest of the two values + [MethodImpl( INLINE )] public static int Min( int a, int b ) => a < b ? a : b; + + /// Returns the smallest of the three values + [MethodImpl( INLINE )] public static int Min( int a, int b, int c ) => Min( Min( a, b ), c ); + + /// Returns the smallest of the four values + [MethodImpl( INLINE )] public static int Min( int a, int b, int c, int d ) => Min( Min( a, b ), Min( c, d ) ); + + /// Returns the largest of the two values + [MethodImpl( INLINE )] public static int Max( int a, int b ) => a > b ? a : b; + + /// Returns the largest of the three values + [MethodImpl( INLINE )] public static int Max( int a, int b, int c ) => Max( Max( a, b ), c ); + + /// Returns the largest of the four values + [MethodImpl( INLINE )] public static int Max( int a, int b, int c, int d ) => Max( Max( a, b ), Max( c, d ) ); + + /// Returns the smallest of the given values + [MethodImpl( INLINE )] public static float Min( params float[] values ) => values.Min(); + + /// Returns the largest of the given values + [MethodImpl( INLINE )] public static float Max( params float[] values ) => values.Max(); + + /// Returns the smallest of the given values + [MethodImpl( INLINE )] public static int Min( params int[] values ) => values.Min(); + + /// Returns the largest of the given values + [MethodImpl( INLINE )] public static int Max( params int[] values ) => values.Max(); + + /// Returns the minimum value of all components in the vector + [MethodImpl( INLINE )] public static float Min( Vector2 v ) => Min( v.x, v.y ); + + /// + [MethodImpl( INLINE )] public static float Min( Vector3 v ) => Min( v.x, v.y, v.z ); + + /// + [MethodImpl( INLINE )] public static float Min( Vector4 v ) => Min( v.x, v.y, v.z, v.w ); + + /// Returns the maximum value of all components in the vector + [MethodImpl( INLINE )] public static float Max( Vector2 v ) => Max( v.x, v.y ); + + /// + [MethodImpl( INLINE )] public static float Max( Vector3 v ) => Max( v.x, v.y, v.z ); + + /// + [MethodImpl( INLINE )] public static float Max( Vector4 v ) => Max( v.x, v.y, v.z, v.w ); + + #endregion + + #region Signs & Rounding + + /// The sign of the value. Returns -1 if negative, returns 1 if greater than or equal to 0 + [MethodImpl( INLINE )] public static float Sign( float value ) => value >= 0f ? 1 : -1; + + /// The sign of each component. Returns -1 if negative, returns 1 if greater than or equal to 0 + [MethodImpl( INLINE )] public static Vector2 Sign( Vector2 value ) => new Vector2( value.x >= 0f ? 1 : -1, value.y >= 0f ? 1 : -1 ); + + /// + [MethodImpl( INLINE )] public static Vector3 Sign( Vector3 value ) => new Vector3( value.x >= 0f ? 1 : -1, value.y >= 0f ? 1 : -1, value.z >= 0f ? 1 : -1 ); + + /// + [MethodImpl( INLINE )] public static Vector4 Sign( Vector4 value ) => new Vector4( value.x >= 0f ? 1 : -1, value.y >= 0f ? 1 : -1, value.z >= 0f ? 1 : -1, value.w >= 0f ? 1 : -1 ); + + /// Returns the sign of the value, either -1 if negative, or 1 if positive or 0 + [MethodImpl( INLINE )] public static int Sign( int value ) => value >= 0 ? 1 : -1; + + /// The sign of the value as an integer. Returns -1 if negative, returns 1 if greater than or equal to 0 + [MethodImpl( INLINE )] public static int SignAsInt( float value ) => value >= 0f ? 1 : -1; + + /// The sign of the value. Returns -1 if negative, return 0 if zero (or within the given threshold), returns 1 if positive + [MethodImpl( INLINE )] public static float SignWithZero( float value, float zeroThreshold = 0.000001f ) => Abs( value ) < zeroThreshold ? 0 : Sign( value ); + + /// The sign of each component. Returns -1 if negative, return 0 if zero (or within the given threshold), returns 1 if positive + [MethodImpl( INLINE )] public static Vector2 SignWithZero( Vector2 value, float zeroThreshold = 0.000001f ) => + new Vector2( + Abs( value.x ) < zeroThreshold ? 0 : Sign( value.x ), + Abs( value.y ) < zeroThreshold ? 0 : Sign( value.y ) + ); + + /// + [MethodImpl( INLINE )] public static Vector3 SignWithZero( Vector3 value, float zeroThreshold = 0.000001f ) => + new Vector3( + Abs( value.x ) < zeroThreshold ? 0 : Sign( value.x ), + Abs( value.y ) < zeroThreshold ? 0 : Sign( value.y ), + Abs( value.z ) < zeroThreshold ? 0 : Sign( value.z ) + ); + + /// + [MethodImpl( INLINE )] public static Vector4 SignWithZero( Vector4 value, float zeroThreshold = 0.000001f ) => + new Vector4( + Abs( value.x ) < zeroThreshold ? 0 : Sign( value.x ), + Abs( value.y ) < zeroThreshold ? 0 : Sign( value.y ), + Abs( value.z ) < zeroThreshold ? 0 : Sign( value.z ), + Abs( value.w ) < zeroThreshold ? 0 : Sign( value.w ) + ); + + /// Returns the sign of the value, either -1 if negative, 0 if zero, 1 if positive + [MethodImpl( INLINE )] public static int SignWithZero( int value ) => value == 0 ? 0 : Sign( value ); + + /// The sign of the value. Returns -1 if negative, return 0 if zero (or within the given threshold), returns 1 if positive + [MethodImpl( INLINE )] public static int SignWithZeroAsInt( float value, float zeroThreshold = 0.000001f ) => Abs( value ) < zeroThreshold ? 0 : SignAsInt( value ); + + /// Rounds the value down to the nearest integer + [MethodImpl( INLINE )] public static float Floor( float value ) => (float)Math.Floor( value ); + + /// Rounds the vector components down to the nearest integer + [MethodImpl( INLINE )] public static Vector2 Floor( Vector2 value ) => new Vector2( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Floor( Vector3 value ) => new Vector3( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ), (float)Math.Floor( value.z ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Floor( Vector4 value ) => new Vector4( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ), (float)Math.Floor( value.z ), (float)Math.Floor( value.w ) ); + + /// Rounds the value down to the nearest integer, returning an int value + [MethodImpl( INLINE )] public static int FloorToInt( float value ) => (int)Math.Floor( value ); + + /// Rounds the vector components down to the nearest integer, returning an integer vector + [MethodImpl( INLINE )] public static Vector2Int FloorToInt( Vector2 value ) => new Vector2Int( (int)Math.Floor( value.x ), (int)Math.Floor( value.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3Int FloorToInt( Vector3 value ) => new Vector3Int( (int)Math.Floor( value.x ), (int)Math.Floor( value.y ), (int)Math.Floor( value.z ) ); + + /// Rounds the value up to the nearest integer + [MethodImpl( INLINE )] public static float Ceil( float value ) => (float)Math.Ceiling( value ); + + /// Rounds the vector components up to the nearest integer + [MethodImpl( INLINE )] public static Vector2 Ceil( Vector2 value ) => new Vector2( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Ceil( Vector3 value ) => new Vector3( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ), (float)Math.Ceiling( value.z ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Ceil( Vector4 value ) => new Vector4( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ), (float)Math.Ceiling( value.z ), (float)Math.Ceiling( value.w ) ); + + /// Rounds the value up to the nearest integer, returning an int value + [MethodImpl( INLINE )] public static int CeilToInt( float value ) => (int)Math.Ceiling( value ); + + /// Rounds the vector components up to the nearest integer, returning an integer vector + [MethodImpl( INLINE )] public static Vector2Int CeilToInt( Vector2 value ) => new Vector2Int( (int)Math.Ceiling( value.x ), (int)Math.Ceiling( value.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3Int CeilToInt( Vector3 value ) => new Vector3Int( (int)Math.Ceiling( value.x ), (int)Math.Ceiling( value.y ), (int)Math.Ceiling( value.z ) ); + + /// Rounds the value to the nearest integer + [MethodImpl( INLINE )] public static float Round( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)MathF.Round( value, midpointRounding ); + + /// Rounds the vector components to the nearest integer + [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ), (float)Math.Round( value.z, midpointRounding ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ), (float)Math.Round( value.z, midpointRounding ), (float)Math.Round( value.w, midpointRounding ) ); + + /// Rounds the value to the nearest value, snapped to the given interval size + [MethodImpl( INLINE )] public static float Round( float value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)Math.Round( value / snapInterval, midpointRounding ) * snapInterval; + + /// Rounds the vector components to the nearest value, snapped to the given interval size + [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ), Round( value.w, snapInterval, midpointRounding ) ); + + /// Rounds the value to the nearest integer, returning an int value + [MethodImpl( INLINE )] public static int RoundToInt( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (int)Math.Round( value, midpointRounding ); + + /// Rounds the vector components to the nearest integer, returning an integer vector + [MethodImpl( INLINE )] public static Vector2Int RoundToInt( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ) ); + + /// + [MethodImpl( INLINE )] public static Vector3Int RoundToInt( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ), (int)Math.Round( value.z, midpointRounding ) ); + + #endregion + + #region Range Repeating + + /// Returns the fractional part of the value. Equivalent to x - floor(x) + [MethodImpl( INLINE )] public static float Frac( float x ) => x - Floor( x ); + + /// Returns the fractional part of the value for each component. Equivalent to v - floor(v) + [MethodImpl( INLINE )] public static Vector2 Frac( Vector2 v ) => new Vector2( v.x - Floor( v.x ), v.y - Floor( v.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Frac( Vector3 v ) => new Vector3( v.x - Floor( v.x ), v.y - Floor( v.y ), v.z - Floor( v.z ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Frac( Vector4 v ) => new Vector4( v.x - Floor( v.x ), v.y - Floor( v.y ), v.z - Floor( v.z ), v.w - Floor( v.w ) ); + + /// Repeats the given value in the interval specified by length + [MethodImpl( INLINE )] public static float Repeat( float value, float length ) => Clamp( value - Floor( value / length ) * length, 0.0f, length ); + + /// Modulo, but, behaves the way you want with negative values, for stuff like array[(n+1)%length] etc. + [MethodImpl( INLINE )] public static int Mod( int value, int length ) => value >= 0 ? value % length : ( value % length + length ) % length; + + /// Repeats a value within a range, going back and forth + [MethodImpl( INLINE )] public static float PingPong( float t, float length ) => length - Abs( Repeat( t, length * 2f ) - length ); + + /// Returns the height of in a triangle wave at time t going from 0 to 1 and back to 0 within the the given period + [MethodImpl( INLINE )] public static float TriangleWave( float t, float period = 1f ) { + float x = t / period; + return 1f - Abs( 2 * ( x - Floor( x ) ) - 1 ); + } + + /// Returns the greatest common divisor of the two numbers + public static int Gcd( int a, int b ) { + // special case bc we can't negate int.MinValue + if( a == int.MinValue || b == int.MinValue ) { + if( a == int.MinValue && b == int.MinValue ) + return int.MinValue; // the only negative return value, bc we can't negate this number + int v = Mathf.Max( a, b ).Abs(); + return v & -v; + } + + if( a == b ) + return a.Abs(); + ( a, b ) = ( Mathf.Abs( a ), Mathf.Abs( b ) ); + while( a != 0 && b != 0 ) + _ = a > b ? a %= b : b %= a; + return a | b; + } + + #endregion + + #region Smoothing & Easing Curves + + /// Applies cubic smoothing to the 0-1 interval, also known as the smoothstep function. Similar to an EaseInOut operation + [MethodImpl( INLINE )] public static float Smooth01( float x ) => x * x * ( 3 - 2 * x ); + + /// Applies quintic smoothing to the 0-1 interval, also known as the smootherstep function. Similar to an EaseInOut operation + [MethodImpl( INLINE )] public static float Smoother01( float x ) => x * x * x * ( x * ( x * 6 - 15 ) + 10 ); + + /// Applies trigonometric smoothing to the 0-1 interval. Similar to an EaseInOut operation + [MethodImpl( INLINE )] public static float SmoothCos01( float x ) => Cos( x * PI ) * -0.5f + 0.5f; + + /// Applies a gamma curve or something idk I've never used this function before but it was part of Unity's original Mathfs.cs and it's undocumented + public static float Gamma( float value, float absmax, float gamma ) { + bool negative = value < 0F; + float absval = Abs( value ); + if( absval > absmax ) + return negative ? -absval : absval; + + float result = Pow( absval / absmax, gamma ) * absmax; + return negative ? -result : result; + } + + #endregion + + #region Value & Vector interpolation + + /// Blends between a and b, based on the t-value. When t = 0 it returns a, when t = 1 it returns b, and any values between are blended linearly + /// The start value, when t is 0 + /// The start value, when t is 1 + /// The t-value from 0 to 1 representing position along the lerp + [MethodImpl( INLINE )] public static float Lerp( float a, float b, float t ) => ( 1f - t ) * a + t * b; + + /// Blends between a and b of each component, based on the t-value of each component in the t-vector. When t = 0 it returns a, when t = 1 it returns b, and any values between are blended linearly + /// The start value, when t is 0 + /// The start value, when t is 1 + /// The t-values from 0 to 1 representing position along the lerp + [MethodImpl( INLINE )] public static Vector2 Lerp( Vector2 a, Vector2 b, Vector2 t ) => new Vector2( Lerp( a.x, b.x, t.x ), Lerp( a.y, b.y, t.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Lerp( Vector3 a, Vector3 b, Vector3 t ) => new Vector3( Lerp( a.x, b.x, t.x ), Lerp( a.y, b.y, t.y ), Lerp( a.z, b.z, t.z ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Lerp( Vector4 a, Vector4 b, Vector4 t ) => new Vector4( Lerp( a.x, b.x, t.x ), Lerp( a.y, b.y, t.y ), Lerp( a.z, b.z, t.z ), Lerp( a.w, b.w, t.w ) ); + + /// Linearly blends between two rectangles, moving and resizing from the center. Note: this lerp is unclamped + /// The start value, when t is 0 + /// The start value, when t is 1 + /// The t-values from 0 to 1 representing position along the lerp + public static Rect Lerp( Rect a, Rect b, float t ) { + Vector2 center = Vector2.LerpUnclamped( a.center, b.center, t ); + Vector2 size = Vector2.LerpUnclamped( a.size, b.size, t ); + return new Rect( default, size ) { center = center }; + } + + /// Blends between a and b, based on the t-value. When t = 0 it returns a, when t = 1 it returns b, and any values between are blended linearly + /// The start value, when t is 0 + /// The start value, when t is 1 + /// The t-value from 0 to 1 representing position along the lerp, clamped between 0 and 1 + [MethodImpl( INLINE )] public static float LerpClamped( float a, float b, float t ) => Lerp( a, b, Clamp01( t ) ); + + /// Lerps between a and b, applying cubic smoothing to the t-value + /// The start value, when t is 0 + /// The start value, when t is 1 + /// The t-value from 0 to 1 representing position along the lerp, clamped between 0 and 1 + [MethodImpl( INLINE )] public static float LerpSmooth( float a, float b, float t ) => Lerp( a, b, Smooth01( Clamp01( t ) ) ); + + /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1 + /// The start of the range, where it would return 0 + /// The end of the range, where it would return 1 + /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated + [MethodImpl( INLINE )] public static float InverseLerp( float a, float b, float value ) => ( value - a ) / ( b - a ); + + /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1. + /// This safe version returns 0 if a == b, instead of a division by zero + /// The start of the range, where it would return 0 + /// The end of the range, where it would return 1 + /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated + [MethodImpl( INLINE )] public static float InverseLerpSafe( float a, float b, float value ) { + float den = b - a; + if( den == 0 ) + return 0; + return ( value - a ) / den; + } + + /// Given values between a and b in each component, returns their normalized locations in the given ranges, as t-values (interpolants) from 0 to 1 + /// The start of the ranges, where it would return 0 + /// The end of the ranges, where it would return 1 + /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated + [MethodImpl( INLINE )] public static Vector2 InverseLerp( Vector2 a, Vector2 b, Vector2 v ) => new Vector2( ( v.x - a.x ) / ( b.x - a.x ), ( v.y - a.y ) / ( b.y - a.y ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 InverseLerp( Vector3 a, Vector3 b, Vector3 v ) => new Vector3( ( v.x - a.x ) / ( b.x - a.x ), ( v.y - a.y ) / ( b.y - a.y ), ( v.z - a.z ) / ( b.z - a.z ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 InverseLerp( Vector4 a, Vector4 b, Vector4 v ) => new Vector4( ( v.x - a.x ) / ( b.x - a.x ), ( v.y - a.y ) / ( b.y - a.y ), ( v.z - a.z ) / ( b.z - a.z ), ( v.w - a.w ) / ( b.w - a.w ) ); + + /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) clamped between 0 and 1 + /// The start of the range, where it would return 0 + /// The end of the range, where it would return 1 + /// A value between a and b + [MethodImpl( INLINE )] public static float InverseLerpClamped( float a, float b, float value ) => Clamp01( ( value - a ) / ( b - a ) ); + + /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1, with cubic smoothing applied. + /// Equivalent to "smoothstep" in shader code + /// The start of the range, where it would return 0 + /// The end of the range, where it would return 1 + /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated + [MethodImpl( INLINE )] public static float InverseLerpSmooth( float a, float b, float value ) => Smooth01( Clamp01( ( value - a ) / ( b - a ) ) ); + + /// Remaps a value from the input range [iMin to iMax] into the output range [oMin to oMax]. + /// Equivalent to Lerp(oMin,oMax,InverseLerp(iMin,iMax,value)) + /// The start value of the input range + /// The end value of the input range + /// The start value of the output range + /// The end value of the output range + /// The value to remap + [MethodImpl( INLINE )] public static float Remap( float iMin, float iMax, float oMin, float oMax, float value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); + + /// + [MethodImpl( INLINE )] public static float Remap( float iMin, float iMax, float oMin, float oMax, int value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); + + /// Remaps values from the input range [iMin to iMax] into the output range [oMin to oMax] on a per-component basis. + /// Equivalent to Lerp(oMin,oMax,InverseLerp(iMin,iMax,value)) + /// The start values of the input ranges + /// The end values of the input ranges + /// The start values of the output ranges + /// The end values of the output ranges + /// The values to remap + [MethodImpl( INLINE )] public static Vector2 Remap( Vector2 iMin, Vector2 iMax, Vector2 oMin, Vector2 oMax, Vector2 value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); + + /// + [MethodImpl( INLINE )] public static Vector3 Remap( Vector3 iMin, Vector3 iMax, Vector3 oMin, Vector3 oMax, Vector3 value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); + + /// + [MethodImpl( INLINE )] public static Vector4 Remap( Vector4 iMin, Vector4 iMax, Vector4 oMin, Vector4 oMax, Vector4 value ) => Lerp( oMin, oMax, InverseLerp( iMin, iMax, value ) ); + + /// Remaps a value from the input range [iMin to iMax] into the output range [oMin to oMax], clamping to make sure it does not extrapolate. + /// Equivalent to Lerp(oMin,oMax,InverseLerpClamped(iMin,iMax,value)) + /// The start value of the input range + /// The end value of the input range + /// The start value of the output range + /// The end value of the output range + /// The value to remap + [MethodImpl( INLINE )] public static float RemapClamped( float iMin, float iMax, float oMin, float oMax, float value ) => Lerp( oMin, oMax, InverseLerpClamped( iMin, iMax, value ) ); + + /// Remaps a value from the input Rect to the output Rect + /// The input Rect + /// The output Rect + /// The input position in the input Rect space + [MethodImpl( INLINE )] public static Vector2 Remap( Rect iRect, Rect oRect, Vector2 iPos ) => Remap( iRect.min, iRect.max, oRect.min, oRect.max, iPos ); + + /// Remaps a value from the input Bounds to the output Bounds + /// The input Bounds + /// The output Bounds + /// The input position in the input Bounds space + [MethodImpl( INLINE )] public static Vector3 Remap( Bounds iBounds, Bounds oBounds, Vector3 iPos ) => Remap( iBounds.min, iBounds.max, oBounds.min, oBounds.max, iPos ); + + /// Remaps a value from the input range to the output range + /// The input range + /// The output range + /// The value to remap from the input range + [MethodImpl( INLINE )] public static float Remap( FloatRange inRange, FloatRange outRange, float value ) => Remap( inRange.a, inRange.b, outRange.a, outRange.b, value ); + + /// Remaps a value from the input range to the output range, clamping to make sure it does not extrapolate. + /// The input range + /// The output range + /// The value to remap from the input range + [MethodImpl( INLINE )] public static float RemapClamped( FloatRange inRange, FloatRange outRange, float value ) => RemapClamped( inRange.a, inRange.b, outRange.a, outRange.b, value ); + + /// Exponential interpolation, the multiplicative version of lerp, useful for values such as scaling or zooming + /// The start value + /// The end value + /// The t-value from 0 to 1 representing position along the eerp + [MethodImpl( INLINE )] public static float Eerp( float a, float b, float t ) => + t switch { + 0f => a, + 1f => b, + _ => Mathf.Pow( a, 1 - t ) * Mathf.Pow( b, t ) + }; + + /// Inverse exponential interpolation, the multiplicative version of InverseLerp, useful for values such as scaling or zooming + /// The start value + /// The end value + /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated + [MethodImpl( INLINE )] public static float InverseEerp( float a, float b, float v ) => Mathf.Log( a / v ) / Mathf.Log( a / b ); + + #endregion + + #region Movement helpers + + /// Moves a value current towards target + /// The current value + /// The value to move towards + /// The maximum change that should be applied to the value + public static float MoveTowards( float current, float target, float maxDelta ) { + if( Mathf.Abs( target - current ) <= maxDelta ) + return target; + return current + Mathf.Sign( target - current ) * maxDelta; + } + + /// Gradually changes a value towards a desired goal over time. + /// The value is smoothed by some spring-damper like function, which will never overshoot. + /// The function can be used to smooth any kind of value, positions, colors, scalars + /// The current position + /// The position we are trying to reach + /// The current velocity, this value is modified by the function every time you call it + /// Approximately the time it will take to reach the target. A smaller value will reach the target faster + /// Optionally allows you to clamp the maximum speed + public static float SmoothDamp( float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = Infinity ) { + float deltaTime = Time.deltaTime; + return SmoothDamp( current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime ); + } + + /// Gradually changes a value towards a desired goal over time. + /// The value is smoothed by some spring-damper like function, which will never overshoot. + /// The function can be used to smooth any kind of value, positions, colors, scalars + /// The current position + /// The position we are trying to reach + /// The current velocity, this value is modified by the function every time you call it + /// Approximately the time it will take to reach the target. A smaller value will reach the target faster + /// Optionally allows you to clamp the maximum speed + /// The time since the last call to this function. By default Time.deltaTime + public static float SmoothDamp( float current, float target, ref float currentVelocity, float smoothTime, [Uei.DefaultValue( "Mathf.Infinity" )] float maxSpeed, [Uei.DefaultValue( "Time.deltaTime" )] float deltaTime ) { + // Based on Game Programming Gems 4 Chapter 1.10 + smoothTime = Mathf.Max( 0.0001F, smoothTime ); + float omega = 2F / smoothTime; + + float x = omega * deltaTime; + float exp = 1F / ( 1F + x + 0.48F * x * x + 0.235F * x * x * x ); + float change = current - target; + float originalTo = target; + + // Clamp maximum speed + float maxChange = maxSpeed * smoothTime; + change = Mathf.Clamp( change, -maxChange, maxChange ); + target = current - change; + + float temp = ( currentVelocity + omega * change ) * deltaTime; + currentVelocity = ( currentVelocity - omega * temp ) * exp; + float output = target + ( change + temp ) * exp; + + // Prevent overshooting + if( originalTo - current > 0.0F == output > originalTo ) { + output = originalTo; + currentVelocity = ( output - originalTo ) / deltaTime; + } + + return output; + } + + #endregion + + #region Weighted sums + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + [MethodImpl( INLINE )] public static float WeightedSum( Vector2 w, float a, float b ) => a * w.x + b * w.y; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + /// The third value, weighted by w.z + [MethodImpl( INLINE )] public static float WeightedSum( Vector3 w, float a, float b, float c ) => a * w.x + b * w.y + c * w.z; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + /// The third value, weighted by w.z + /// The fourth value, weighted by w.w + [MethodImpl( INLINE )] public static float WeightedSum( Vector4 w, float a, float b, float c, float d ) => a * w.x + b * w.y + c * w.z + d * w.w; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + [MethodImpl( INLINE )] public static Vector2 WeightedSum( Vector2 w, Vector2 a, Vector2 b ) => a * w.x + b * w.y; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + /// The third value, weighted by w.z + [MethodImpl( INLINE )] public static Vector2 WeightedSum( Vector3 w, Vector2 a, Vector2 b, Vector2 c ) => a * w.x + b * w.y + c * w.z; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + /// The third value, weighted by w.z + /// The fourth value, weighted by w.w + [MethodImpl( INLINE )] public static Vector2 WeightedSum( Vector4 w, Vector2 a, Vector2 b, Vector2 c, Vector2 d ) => a * w.x + b * w.y + c * w.z + d * w.w; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + [MethodImpl( INLINE )] public static Vector3 WeightedSum( Vector3 w, Vector3 a, Vector3 b ) => a * w.x + b * w.y; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + /// The third value, weighted by w.z + [MethodImpl( INLINE )] public static Vector3 WeightedSum( Vector3 w, Vector3 a, Vector3 b, Vector3 c ) => a * w.x + b * w.y + c * w.z; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + /// The third value, weighted by w.z + /// The fourth value, weighted by w.w + [MethodImpl( INLINE )] public static Vector3 WeightedSum( Vector4 w, Vector3 a, Vector3 b, Vector3 c, Vector3 d ) => a * w.x + b * w.y + c * w.z + d * w.w; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + [MethodImpl( INLINE )] public static Vector4 WeightedSum( Vector4 w, Vector4 a, Vector4 b ) => a * w.x + b * w.y; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + /// The third value, weighted by w.z + [MethodImpl( INLINE )] public static Vector4 WeightedSum( Vector4 w, Vector4 a, Vector4 b, Vector4 c ) => a * w.x + b * w.y + c * w.z; + + /// Multiplies each component of w by the input values, and returns their sum + /// The weights (per component) to apply to the rest of the values + /// The first value, weighted by w.x + /// The second value, weighted by w.y + /// The third value, weighted by w.z + /// The fourth value, weighted by w.w + [MethodImpl( INLINE )] public static Vector4 WeightedSum( Vector4 w, Vector4 a, Vector4 b, Vector4 c, Vector4 d ) => a * w.x + b * w.y + c * w.z + d * w.w; + + #endregion + + #region Vector math + + /// The determinant is equivalent to the dot product, but with one vector rotated 90 degrees. + /// Note that det(a,b) != det(b,a). It's equivalent to a.x * b.y - a.y * b.x. + /// It is also known as the 2D Cross Product, Wedge Product, Outer Product and Perpendicular Dot Product + public static float Determinant /*or Cross*/( Vector2 a, Vector2 b ) => a.x * b.y - a.y * b.x; // 2D "cross product" + + /// Returns the direction and magnitude of the vector. Cheaper than calculating length and normalizing it separately + public static (Vector2 dir, float magnitude ) GetDirAndMagnitude( Vector2 v ) { + float magnitude = v.magnitude; + return ( v / magnitude, magnitude ); + } + + /// + public static (Vector3 dir, float magnitude ) GetDirAndMagnitude( Vector3 v ) { + float magnitude = v.magnitude; + return ( v / magnitude, magnitude ); + } + + /// Clamps the length of the vector between min and max + /// The vector to clamp + /// Minimum length + /// Maximum length + public static Vector2 ClampMagnitude( Vector2 v, float min, float max ) { + float mag = v.magnitude; + return mag < min ? ( v / mag ) * min : mag > max ? ( v / mag ) * max : v; + } + + /// + public static Vector3 ClampMagnitude( Vector3 v, float min, float max ) { + float mag = v.magnitude; + return mag < min ? ( v / mag ) * min : mag > max ? ( v / mag ) * max : v; + } + + /// Returns the average/center of the two input vectors + [MethodImpl( INLINE )] public static Vector2 Average( Vector2 a, Vector2 b ) => ( a + b ) / 2f; + + /// Returns the average/center of the two input vectors + [MethodImpl( INLINE )] public static Vector3 Average( Vector3 a, Vector3 b ) => ( a + b ) / 2f; + + /// Returns the average/halfway direction between the two input direction vectors. Note that this presumes both aDir and bDir have the same length + [MethodImpl( INLINE )] public static Vector2 AverageDir( Vector2 aDir, Vector2 bDir ) => ( aDir + bDir ).normalized; + + /// Returns the average/halfway direction between the two input direction vectors. Note that this presumes both aDir and bDir have the same length + [MethodImpl( INLINE )] public static Vector3 AverageDir( Vector3 aDir, Vector3 bDir ) => ( aDir + bDir ).normalized; + + /// Returns the squared distance between two points. + /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter + [MethodImpl( INLINE )] public static float DistanceSquared( Vector2 a, Vector2 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square(); + + /// Returns the squared distance between two points. + /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter + [MethodImpl( INLINE )] public static float DistanceSquared( Vector3 a, Vector3 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square() + ( a.z - b.z ).Square(); + + /// Returns the squared distance between two points. + /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter + [MethodImpl( INLINE )] public static float DistanceSquared( Vector4 a, Vector4 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square() + ( a.z - b.z ).Square() + ( a.w - b.w ).Square(); + + #endregion + + #region Angles & Rotation + + /// Returns the direction of the input angle, as a normalized vector + /// The input angle, in radians + /// + [MethodImpl( INLINE )] public static Vector2 AngToDir( float aRad ) => new Vector2( Mathf.Cos( aRad ), Mathf.Sin( aRad ) ); + + /// Returns the angle of the input vector, in radians. You can also use myVector.Angle() + /// The vector to get the angle of. It does not have to be normalized + /// + [MethodImpl( INLINE )] public static float DirToAng( Vector2 vec ) => Mathf.Atan2( vec.y, vec.x ); + + /// Returns a 2D orientation from a vector, representing the X axis + /// The direction to create a 2D orientation from (does not have to be normalized) + [MethodImpl( INLINE )] public static Quaternion DirToOrientation( Vector2 v ) { + v.Normalize(); + v.x += 1; + v.Normalize(); + return new Quaternion( 0, 0, v.y, v.x ); + } + + /// Returns a 2D Pose from a point and a vector, representing the X axis + /// The location of the pose + /// The direction to create a 2D orientation from (does not have to be normalized) + [MethodImpl( INLINE )] public static Pose PointDirToPose( Vector2 pt, Vector2 v ) => new Pose( pt, DirToOrientation( v ) ); + + /// Linearly blends between two poses. The position will lerp, while the rotation will slerp. Note: this lerp is unclamped + /// Pose at t = 0 + /// Pose at t = 1 + /// The t-value to blend from a to b, from 0 to 1 (values outside will extrapolate) + public static Pose Lerp( Pose a, Pose b, float t ) => + new Pose( + Vector3.LerpUnclamped( a.position, b.position, t ), + Quaternion.SlerpUnclamped( a.rotation, b.rotation, t ) + ); + + /// Returns a matrix representing a 2D position and rotation + /// The location of the matrix + /// The direction of the X axis (has to be normalized) + [MethodImpl( INLINE )] public static Matrix4x4 GetMatrixFrom2DPointDir( Vector2 point, Vector2 tangent ) { + Vector2 N = tangent.Rotate90CCW(); + return new Matrix4x4( + new Vector4( tangent.x, tangent.y, 0, 0 ), + new Vector4( N.x, N.y, 0, 0 ), + new Vector4( 0, 0, 1, 0 ), + new Vector4( point.x, point.y, 0, 1 ) + ); + } + + /// Returns the signed curvature at a point in a curve, in radians per distance unit (equivalent to the reciprocal radius of the osculating circle) + /// The first derivative of the point in the curve + /// The second derivative of the point in the curve + [MethodImpl( INLINE )] public static float GetCurvature( Vector2 velocity, Vector2 acceleration ) { + float dMag = velocity.magnitude; + return Determinant( velocity, acceleration ) / ( dMag * dMag * dMag ); + } + + /// Returns a pseudovector of a point in a curve, where the magnitude is the curvature in radians per distance unit, and the direction is the axis of curvature + /// The first derivative of the point in the curve + /// The second derivative of the point in the curve + [MethodImpl( INLINE )] public static Vector3 GetCurvature( Vector3 velocity, Vector3 acceleration ) { + float dMag = velocity.magnitude; + return Vector3.Cross( velocity, acceleration ) / ( dMag * dMag * dMag ); + } + + /// Returns the torsion of a given point in a curve, in radians per distance unit + /// The first derivative of the point in the curve + /// The second derivative of the point in the curve + /// The third derivative of the point in the curve + [MethodImpl( INLINE )] public static float GetTorsion( Vector3 velocity, Vector3 acceleration, Vector3 jerk ) { + Vector3 cVector = Vector3.Cross( velocity, acceleration ); + return Vector3.Dot( cVector, jerk ) / cVector.sqrMagnitude; + } + + /// Returns the frenet-serret (curvature-based) normal direction at a given point in a curve + /// The first derivative of the point in the curve + /// The second derivative of the point in the curve + [MethodImpl( INLINE )] public static Vector3 GetArcNormal( Vector3 velocity, Vector3 acceleration ) => Vector3.Cross( Vector3.Cross( velocity, acceleration ).normalized, velocity.normalized ); + + /// Returns the frenet-serret (curvature-based) binormal direction at a given point in a curve + /// The first derivative of the point in the curve + /// The second derivative of the point in the curve + [MethodImpl( INLINE )] public static Vector3 GetArcBinormal( Vector3 velocity, Vector3 acceleration ) => Vector3.Cross( velocity, acceleration ).normalized; + + /// Returns a normal direction given a reference up vector and a tangent direction + /// The tangent direction (does not have to be normalized) + /// The reference up vector. The normal will be perpendicular to both the supplied up vector and the curve + [MethodImpl( INLINE )] public static Vector3 GetNormalFromLookTangent( Vector3 tangent, Vector3 up ) => Vector3.Cross( up, tangent ).normalized; + + /// Returns the binormal from a vector, given a reference up vector. + /// The binormal will attempt to be as aligned with the reference vector as possible, + /// while still being perpendicular to the tangent + /// The tangent direction (does not have to be normalized) + /// The reference up vector. The normal will be perpendicular to both the supplied up vector and the tangent + [MethodImpl( INLINE )] public static Vector3 GetBinormalFromLookTangent( Vector3 tangent, Vector3 up ) { + Vector3 normal = Vector3.Cross( up, tangent ).normalized; + return Vector3.Cross( tangent.normalized, normal ); + } + + /// Returns the frenet-serret (curvature-based) orientation of a point in a curve with the given velocity and acceleration values, where the Z direction is tangent to the curve. + /// The X axis will point to the inner arc of the current curvature + /// The first derivative of the point in the curve + /// The second derivative of the point in the curve + [MethodImpl( INLINE )] public static Quaternion GetArcOrientation( Vector3 velocity, Vector3 acceleration ) { + Vector3 binormal = Vector3.Cross( velocity, acceleration ); + return Quaternion.LookRotation( velocity, binormal ); + } + + /// Returns the signed angle between a and b, in the range -tau/2 to tau/2 (-pi to pi) + [MethodImpl( INLINE )] public static float SignedAngle( Vector2 a, Vector2 b ) => AngleBetween( a, b ) * Mathf.Sign( Determinant( a, b ) ); // -tau/2 to tau/2 + + /// Returns the shortest angle between a and b, in the range 0 to tau/2 (0 to pi) + [MethodImpl( INLINE )] public static float AngleBetween( Vector2 a, Vector2 b ) => Mathf.Acos( Vector2.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); + + /// + [MethodImpl( INLINE )] public static float AngleBetween( Vector3 a, Vector3 b ) => Mathf.Acos( Vector3.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); + + /// Returns the clockwise angle between from and to, in the range 0 to tau (0 to 2*pi) + [MethodImpl( INLINE )] public static float AngleFromToCW( Vector2 from, Vector2 to ) => Determinant( from, to ) < 0 ? AngleBetween( from, to ) : TAU - AngleBetween( from, to ); + + /// Returns the counterclockwise angle between from and to, in the range 0 to tau (0 to 2*pi) + [MethodImpl( INLINE )] public static float AngleFromToCCW( Vector2 from, Vector2 to ) => Determinant( from, to ) > 0 ? AngleBetween( from, to ) : TAU - AngleBetween( from, to ); + + /// Blends between the aRad and bRad angles, based on the input t-value between 0 and 1 + /// The start value, in radians + /// The end value, in radians + /// The t-value between 0 and 1 + public static float LerpAngle( float aRad, float bRad, float t ) { + float delta = Repeat( ( bRad - aRad ), TAU ); + if( delta > PI ) + delta -= TAU; + return aRad + delta * Clamp01( t ); + } + + /// Returns the shortest angle between the two input angles, in radians + [MethodImpl( INLINE )] public static float DeltaAngle( float a, float b ) => ( b - a + PI ).Repeat( TAU ) - PI; + + /// Given an angle between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1 + /// The start angle of the range (in radians), where it would return 0 + /// The end angle of the range (in radians), where it would return 1 + /// An angle between a and b + public static float InverseLerpAngle( float a, float b, float v ) { + float angBetween = DeltaAngle( a, b ); + b = a + angBetween; // removes any a->b discontinuity + float h = a + angBetween * 0.5f; // halfway angle + v = h + DeltaAngle( h, v ); // get offset from h, and offset by h + return InverseLerpClamped( a, b, v ); + } + + #endregion + + #region Angular movement helpers + + /// Same as MoveTowards but makes sure the angles interpolate correctly when they wrap around a full turn. + /// Variables current and target are assumed to be in radians. + /// For optimization reasons, negative values of maxDelta are not supported and may cause oscillation. + /// To push current away from a target angle, add 180 to that angle instead. + /// The current angle + /// The angle to move towards + /// The maximum change that should be applied to the value + public static float MoveTowardsAngle( float current, float target, float maxDelta ) { + float deltaAngle = DeltaAngle( current, target ); + if( -maxDelta < deltaAngle && deltaAngle < maxDelta ) + return target; + target = current + deltaAngle; + return MoveTowards( current, target, maxDelta ); + } + + /// Gradually changes an angle given in radians towards a desired goal angle over time. + /// The value is smoothed by some spring-damper like function. + /// The function can be used to smooth any kind of value, positions, colors, scalars. The most common use is for smoothing a follow camera. + /// The current angle + /// The angle we are trying to reach + /// The current angular velocity, this value is modified by the function every time you call it + /// Approximately the time it will take to reach the target. A smaller value will reach the target faster + /// Optionally allows you to clamp the maximum speed + public static float SmoothDampAngle( float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = Infinity ) { + float deltaTime = Time.deltaTime; + return SmoothDampAngle( current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime ); + } + + /// Gradually changes an angle given in radians towards a desired goal angle over time. + /// The value is smoothed by some spring-damper like function. + /// The function can be used to smooth any kind of value, positions, colors, scalars. The most common use is for smoothing a follow camera. + /// The current angle + /// The angle we are trying to reach + /// The current angular velocity, this value is modified by the function every time you call it + /// Approximately the time it will take to reach the target. A smaller value will reach the target faster + /// Optionally allows you to clamp the maximum speed + /// The time since the last call to this function. By default Time.deltaTime + public static float SmoothDampAngle( float current, float target, ref float currentVelocity, float smoothTime, [Uei.DefaultValue( "Mathf.Infinity" )] float maxSpeed, [Uei.DefaultValue( "Time.deltaTime" )] float deltaTime ) { + target = current + DeltaAngle( current, target ); + return SmoothDamp( current, target, ref currentVelocity, smoothTime, maxSpeed, deltaTime ); + } + + #endregion + + #region Shape coordinate remapping + + /// Given a position within a -1 to 1 square, remaps it to the unit circle + /// The input position inside the square + public static Vector2 SquareToDisc( Vector2 c ) { + c.x = c.x.ClampNeg1to1(); + c.y = c.y.ClampNeg1to1(); + float u = c.x * Sqrt( 1 - ( c.y * c.y ) / 2 ); + float v = c.y * Sqrt( 1 - ( c.x * c.x ) / 2 ); + return new Vector2( u, v ); + } + + /// Given a position within the unit circle, remaps it to a square in the -1 to 1 range + /// The input position inside the circle + public static Vector2 DiscToSquare( Vector2 c ) { + c = c.ClampMagnitude( 0, 1 ); + float u2 = c.x * c.x; + float v2 = c.y * c.y; + Vector2 n = new Vector2( 1, -1 ); + Vector2 p = new Vector2( 2, 2 ) + n * ( u2 - v2 ); + Vector2 q = 2 * SQRT2 * c; + Vector2 smolVec = Vector2.one * 0.0001f; + return 0.5f * ( Vector2.Max( smolVec, p + q ).Sqrt() - Vector2.Max( smolVec, p - q ).Sqrt() ); + } + + #endregion + + } + } \ No newline at end of file diff --git a/MathfsAsmdef.asmdef b/Runtime/MathfsAsmdef.asmdef similarity index 100% rename from MathfsAsmdef.asmdef rename to Runtime/MathfsAsmdef.asmdef diff --git a/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs similarity index 100% rename from Numerics/FloatRange.cs rename to Runtime/Numerics/FloatRange.cs diff --git a/Numerics/Matrix3x1.cs b/Runtime/Numerics/Matrix3x1.cs similarity index 100% rename from Numerics/Matrix3x1.cs rename to Runtime/Numerics/Matrix3x1.cs diff --git a/Numerics/Matrix4x1.cs b/Runtime/Numerics/Matrix4x1.cs similarity index 100% rename from Numerics/Matrix4x1.cs rename to Runtime/Numerics/Matrix4x1.cs diff --git a/Numerics/Rational.cs b/Runtime/Numerics/Rational.cs similarity index 100% rename from Numerics/Rational.cs rename to Runtime/Numerics/Rational.cs diff --git a/Numerics/RationalMatrix3x3.cs b/Runtime/Numerics/RationalMatrix3x3.cs similarity index 100% rename from Numerics/RationalMatrix3x3.cs rename to Runtime/Numerics/RationalMatrix3x3.cs diff --git a/Numerics/RationalMatrix4x4.cs b/Runtime/Numerics/RationalMatrix4x4.cs similarity index 100% rename from Numerics/RationalMatrix4x4.cs rename to Runtime/Numerics/RationalMatrix4x4.cs diff --git a/Numerics/Vector2Matrix3x1.cs b/Runtime/Numerics/Vector2Matrix3x1.cs similarity index 100% rename from Numerics/Vector2Matrix3x1.cs rename to Runtime/Numerics/Vector2Matrix3x1.cs diff --git a/Numerics/Vector2Matrix4x1.cs b/Runtime/Numerics/Vector2Matrix4x1.cs similarity index 100% rename from Numerics/Vector2Matrix4x1.cs rename to Runtime/Numerics/Vector2Matrix4x1.cs diff --git a/Numerics/Vector3Matrix3x1.cs b/Runtime/Numerics/Vector3Matrix3x1.cs similarity index 100% rename from Numerics/Vector3Matrix3x1.cs rename to Runtime/Numerics/Vector3Matrix3x1.cs diff --git a/Numerics/Vector3Matrix4x1.cs b/Runtime/Numerics/Vector3Matrix4x1.cs similarity index 100% rename from Numerics/Vector3Matrix4x1.cs rename to Runtime/Numerics/Vector3Matrix4x1.cs diff --git a/Numerics/Vector4Matrix3x1.cs b/Runtime/Numerics/Vector4Matrix3x1.cs similarity index 100% rename from Numerics/Vector4Matrix3x1.cs rename to Runtime/Numerics/Vector4Matrix3x1.cs diff --git a/Numerics/Vector4Matrix4x1.cs b/Runtime/Numerics/Vector4Matrix4x1.cs similarity index 100% rename from Numerics/Vector4Matrix4x1.cs rename to Runtime/Numerics/Vector4Matrix4x1.cs diff --git a/Random.cs b/Runtime/Random.cs similarity index 100% rename from Random.cs rename to Runtime/Random.cs diff --git a/Splines/CatRomType.cs b/Runtime/Splines/CatRomType.cs similarity index 100% rename from Splines/CatRomType.cs rename to Runtime/Splines/CatRomType.cs diff --git a/Splines/CharMatrix.cs b/Runtime/Splines/CharMatrix.cs similarity index 100% rename from Splines/CharMatrix.cs rename to Runtime/Splines/CharMatrix.cs diff --git a/Splines/Multi-Segment Splines/BSpline2D.cs b/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs similarity index 100% rename from Splines/Multi-Segment Splines/BSpline2D.cs rename to Runtime/Splines/Multi-Segment Splines/BSpline2D.cs diff --git a/Splines/Multi-Segment Splines/NURBS2D.cs b/Runtime/Splines/Multi-Segment Splines/NURBS2D.cs similarity index 100% rename from Splines/Multi-Segment Splines/NURBS2D.cs rename to Runtime/Splines/Multi-Segment Splines/NURBS2D.cs diff --git a/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs b/Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs similarity index 100% rename from Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs rename to Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs diff --git a/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs b/Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs similarity index 100% rename from Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs rename to Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs diff --git a/Splines/SplineUtils.cs b/Runtime/Splines/SplineUtils.cs similarity index 100% rename from Splines/SplineUtils.cs rename to Runtime/Splines/SplineUtils.cs diff --git a/Splines/Trajectory2D.cs b/Runtime/Splines/Trajectory2D.cs similarity index 100% rename from Splines/Trajectory2D.cs rename to Runtime/Splines/Trajectory2D.cs diff --git a/Splines/Uniform Spline Segments/Bezier2D.cs b/Runtime/Splines/Uniform Spline Segments/Bezier2D.cs similarity index 100% rename from Splines/Uniform Spline Segments/Bezier2D.cs rename to Runtime/Splines/Uniform Spline Segments/Bezier2D.cs diff --git a/Splines/Uniform Spline Segments/Bezier3D.cs b/Runtime/Splines/Uniform Spline Segments/Bezier3D.cs similarity index 100% rename from Splines/Uniform Spline Segments/Bezier3D.cs rename to Runtime/Splines/Uniform Spline Segments/Bezier3D.cs diff --git a/Splines/Uniform Spline Segments/BezierCubic1D.cs b/Runtime/Splines/Uniform Spline Segments/BezierCubic1D.cs similarity index 100% rename from Splines/Uniform Spline Segments/BezierCubic1D.cs rename to Runtime/Splines/Uniform Spline Segments/BezierCubic1D.cs diff --git a/Splines/Uniform Spline Segments/BezierCubic2D.cs b/Runtime/Splines/Uniform Spline Segments/BezierCubic2D.cs similarity index 100% rename from Splines/Uniform Spline Segments/BezierCubic2D.cs rename to Runtime/Splines/Uniform Spline Segments/BezierCubic2D.cs diff --git a/Splines/Uniform Spline Segments/BezierCubic3D.cs b/Runtime/Splines/Uniform Spline Segments/BezierCubic3D.cs similarity index 100% rename from Splines/Uniform Spline Segments/BezierCubic3D.cs rename to Runtime/Splines/Uniform Spline Segments/BezierCubic3D.cs diff --git a/Splines/Uniform Spline Segments/BezierCubic4D.cs b/Runtime/Splines/Uniform Spline Segments/BezierCubic4D.cs similarity index 100% rename from Splines/Uniform Spline Segments/BezierCubic4D.cs rename to Runtime/Splines/Uniform Spline Segments/BezierCubic4D.cs diff --git a/Splines/Uniform Spline Segments/BezierQuad1D.cs b/Runtime/Splines/Uniform Spline Segments/BezierQuad1D.cs similarity index 100% rename from Splines/Uniform Spline Segments/BezierQuad1D.cs rename to Runtime/Splines/Uniform Spline Segments/BezierQuad1D.cs diff --git a/Splines/Uniform Spline Segments/BezierQuad2D.cs b/Runtime/Splines/Uniform Spline Segments/BezierQuad2D.cs similarity index 100% rename from Splines/Uniform Spline Segments/BezierQuad2D.cs rename to Runtime/Splines/Uniform Spline Segments/BezierQuad2D.cs diff --git a/Splines/Uniform Spline Segments/BezierQuad3D.cs b/Runtime/Splines/Uniform Spline Segments/BezierQuad3D.cs similarity index 100% rename from Splines/Uniform Spline Segments/BezierQuad3D.cs rename to Runtime/Splines/Uniform Spline Segments/BezierQuad3D.cs diff --git a/Splines/Uniform Spline Segments/BezierQuad4D.cs b/Runtime/Splines/Uniform Spline Segments/BezierQuad4D.cs similarity index 100% rename from Splines/Uniform Spline Segments/BezierQuad4D.cs rename to Runtime/Splines/Uniform Spline Segments/BezierQuad4D.cs diff --git a/Splines/Uniform Spline Segments/CatRomCubic1D.cs b/Runtime/Splines/Uniform Spline Segments/CatRomCubic1D.cs similarity index 100% rename from Splines/Uniform Spline Segments/CatRomCubic1D.cs rename to Runtime/Splines/Uniform Spline Segments/CatRomCubic1D.cs diff --git a/Splines/Uniform Spline Segments/CatRomCubic2D.cs b/Runtime/Splines/Uniform Spline Segments/CatRomCubic2D.cs similarity index 100% rename from Splines/Uniform Spline Segments/CatRomCubic2D.cs rename to Runtime/Splines/Uniform Spline Segments/CatRomCubic2D.cs diff --git a/Splines/Uniform Spline Segments/CatRomCubic3D.cs b/Runtime/Splines/Uniform Spline Segments/CatRomCubic3D.cs similarity index 100% rename from Splines/Uniform Spline Segments/CatRomCubic3D.cs rename to Runtime/Splines/Uniform Spline Segments/CatRomCubic3D.cs diff --git a/Splines/Uniform Spline Segments/CatRomCubic4D.cs b/Runtime/Splines/Uniform Spline Segments/CatRomCubic4D.cs similarity index 100% rename from Splines/Uniform Spline Segments/CatRomCubic4D.cs rename to Runtime/Splines/Uniform Spline Segments/CatRomCubic4D.cs diff --git a/Splines/Uniform Spline Segments/HermiteCubic1D.cs b/Runtime/Splines/Uniform Spline Segments/HermiteCubic1D.cs similarity index 100% rename from Splines/Uniform Spline Segments/HermiteCubic1D.cs rename to Runtime/Splines/Uniform Spline Segments/HermiteCubic1D.cs diff --git a/Splines/Uniform Spline Segments/HermiteCubic2D.cs b/Runtime/Splines/Uniform Spline Segments/HermiteCubic2D.cs similarity index 100% rename from Splines/Uniform Spline Segments/HermiteCubic2D.cs rename to Runtime/Splines/Uniform Spline Segments/HermiteCubic2D.cs diff --git a/Splines/Uniform Spline Segments/HermiteCubic3D.cs b/Runtime/Splines/Uniform Spline Segments/HermiteCubic3D.cs similarity index 100% rename from Splines/Uniform Spline Segments/HermiteCubic3D.cs rename to Runtime/Splines/Uniform Spline Segments/HermiteCubic3D.cs diff --git a/Splines/Uniform Spline Segments/HermiteCubic4D.cs b/Runtime/Splines/Uniform Spline Segments/HermiteCubic4D.cs similarity index 100% rename from Splines/Uniform Spline Segments/HermiteCubic4D.cs rename to Runtime/Splines/Uniform Spline Segments/HermiteCubic4D.cs diff --git a/Splines/Uniform Spline Segments/UBSCubic1D.cs b/Runtime/Splines/Uniform Spline Segments/UBSCubic1D.cs similarity index 100% rename from Splines/Uniform Spline Segments/UBSCubic1D.cs rename to Runtime/Splines/Uniform Spline Segments/UBSCubic1D.cs diff --git a/Splines/Uniform Spline Segments/UBSCubic2D.cs b/Runtime/Splines/Uniform Spline Segments/UBSCubic2D.cs similarity index 100% rename from Splines/Uniform Spline Segments/UBSCubic2D.cs rename to Runtime/Splines/Uniform Spline Segments/UBSCubic2D.cs diff --git a/Splines/Uniform Spline Segments/UBSCubic3D.cs b/Runtime/Splines/Uniform Spline Segments/UBSCubic3D.cs similarity index 100% rename from Splines/Uniform Spline Segments/UBSCubic3D.cs rename to Runtime/Splines/Uniform Spline Segments/UBSCubic3D.cs diff --git a/Splines/Uniform Spline Segments/UBSCubic4D.cs b/Runtime/Splines/Uniform Spline Segments/UBSCubic4D.cs similarity index 100% rename from Splines/Uniform Spline Segments/UBSCubic4D.cs rename to Runtime/Splines/Uniform Spline Segments/UBSCubic4D.cs diff --git a/Splines/UniformCurveSampler.cs b/Runtime/Splines/UniformCurveSampler.cs similarity index 100% rename from Splines/UniformCurveSampler.cs rename to Runtime/Splines/UniformCurveSampler.cs diff --git a/UtilityTypes.cs b/Runtime/UtilityTypes.cs similarity index 100% rename from UtilityTypes.cs rename to Runtime/UtilityTypes.cs From f4b4667f5ddac3a919df2f9f519b1db15b8ca514 Mon Sep 17 00:00:00 2001 From: Andrei Andreev Date: Mon, 17 Oct 2022 21:33:52 +0200 Subject: [PATCH 137/301] Add package.json --- package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..95c2578 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "com.freyaholmer.mathfs", + "displayName": "Mathfs", + "description": "Expanded Math Functionality for Unity", + "author": "Freya Holmér", + "version": "1.0.0", + "unity": "2021.2", + "documentationUrl": "https://github.com/FreyaHolmer/Mathfs", + "licensesUrl": "https://github.com/FreyaHolmer/Mathfs/LICENSE.txt" +} From c8ed8da588a562182ff26819b12180291ddb432b Mon Sep 17 00:00:00 2001 From: Andrei Andreev Date: Mon, 17 Oct 2022 21:34:03 +0200 Subject: [PATCH 138/301] Add installation instructions --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 7196817..7d533e7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,18 @@ # Mathfs Expanded Math Functionality for Unity +## Installation instructions + +There are several ways to install this library into our project: + +- **Plain install**: Clone or [download](https://github.com/FreyaHolmer/Mathfs/archive/refs/heads/master.zip) this repository and put it somewhere in your Unity project +- **Unity Package Manager (UPM)**: Add the following line to *Packages/manifest.json*: + - `"com.freyaholmer.mathfs": "https://github.com/FreyaHolmer/Mathfs#1.0.0",` +- **[OpenUPM](https://openupm.com)**: After installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command: + - `openupm add com.freyaholmer.mathfs` + +After installation you will be able to access the library in script by including namespace `using Freya` + ## Features - 2D Intersection tests between all combinations of: - Ray From 0ebdb9f9f9cf1e7d75e5ed2d7b7930f3edd4beb6 Mon Sep 17 00:00:00 2001 From: Andrei Andreev Date: Mon, 17 Oct 2022 21:45:33 +0200 Subject: [PATCH 139/301] Remove .gitignore and allow .meta files --- .gitignore | 1 - Editor.meta | 8 ++++++++ Editor/CodeGenerator.cs.meta | 11 +++++++++++ Editor/Mathfs.Editor.asmdef.meta | 7 +++++++ Editor/MathfsCodegen.cs.meta | 11 +++++++++++ LICENSE.txt.meta | 7 +++++++ README.md.meta | 7 +++++++ Runtime.meta | 8 ++++++++ Runtime/Curves.meta | 8 ++++++++ Runtime/Curves/IParamCurve.cs.meta | 11 +++++++++++ Runtime/Curves/Polynomial.cs.meta | 11 +++++++++++ Runtime/Curves/Polynomial2D.cs.meta | 11 +++++++++++ Runtime/Curves/Polynomial3D.cs.meta | 11 +++++++++++ Runtime/Curves/Polynomial4D.cs.meta | 11 +++++++++++ Runtime/Extensions.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes.meta | 8 ++++++++ Runtime/Geometric Shapes/Box.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes/Circle.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes/ILinear2D.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes/Line2D.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes/LineSegment2D.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes/Polygon.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes/PolygonClipper.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes/Ray2D.cs.meta | 11 +++++++++++ Runtime/Geometric Shapes/Triangle.cs.meta | 11 +++++++++++ Runtime/IntersectionTestCore.cs.meta | 11 +++++++++++ Runtime/IntersectionTestWrappers.cs.meta | 11 +++++++++++ Runtime/Mathfs.cs.meta | 11 +++++++++++ Runtime/MathfsAsmdef.asmdef.meta | 7 +++++++ Runtime/Numerics.meta | 8 ++++++++ Runtime/Numerics/FloatRange.cs.meta | 11 +++++++++++ Runtime/Numerics/Matrix3x1.cs.meta | 11 +++++++++++ Runtime/Numerics/Matrix4x1.cs.meta | 11 +++++++++++ Runtime/Numerics/Rational.cs.meta | 11 +++++++++++ Runtime/Numerics/RationalMatrix3x3.cs.meta | 11 +++++++++++ Runtime/Numerics/RationalMatrix4x4.cs.meta | 11 +++++++++++ Runtime/Numerics/Vector2Matrix3x1.cs.meta | 11 +++++++++++ Runtime/Numerics/Vector2Matrix4x1.cs.meta | 11 +++++++++++ Runtime/Numerics/Vector3Matrix3x1.cs.meta | 11 +++++++++++ Runtime/Numerics/Vector3Matrix4x1.cs.meta | 11 +++++++++++ Runtime/Numerics/Vector4Matrix3x1.cs.meta | 11 +++++++++++ Runtime/Numerics/Vector4Matrix4x1.cs.meta | 11 +++++++++++ Runtime/Random.cs.meta | 11 +++++++++++ Runtime/Splines.meta | 8 ++++++++ Runtime/Splines/CatRomType.cs.meta | 11 +++++++++++ Runtime/Splines/CharMatrix.cs.meta | 11 +++++++++++ Runtime/Splines/Multi-Segment Splines.meta | 8 ++++++++ .../Splines/Multi-Segment Splines/BSpline2D.cs.meta | 11 +++++++++++ Runtime/Splines/Multi-Segment Splines/NURBS2D.cs.meta | 11 +++++++++++ Runtime/Splines/Non-Uniform Spline Segments.meta | 8 ++++++++ .../NUCatRomCubic2D.cs.meta | 11 +++++++++++ .../NUCatRomCubic3D.cs.meta | 11 +++++++++++ Runtime/Splines/SplineUtils.cs.meta | 11 +++++++++++ Runtime/Splines/Trajectory2D.cs.meta | 11 +++++++++++ Runtime/Splines/Uniform Spline Segments.meta | 8 ++++++++ .../Splines/Uniform Spline Segments/Bezier2D.cs.meta | 11 +++++++++++ .../Splines/Uniform Spline Segments/Bezier3D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/BezierCubic1D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/BezierCubic2D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/BezierCubic3D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/BezierCubic4D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/BezierQuad1D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/BezierQuad2D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/BezierQuad3D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/BezierQuad4D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/CatRomCubic1D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/CatRomCubic2D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/CatRomCubic3D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/CatRomCubic4D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/HermiteCubic1D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/HermiteCubic2D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/HermiteCubic3D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/HermiteCubic4D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/UBSCubic1D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/UBSCubic2D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/UBSCubic3D.cs.meta | 11 +++++++++++ .../Uniform Spline Segments/UBSCubic4D.cs.meta | 11 +++++++++++ Runtime/Splines/UniformCurveSampler.cs.meta | 11 +++++++++++ Runtime/UtilityTypes.cs.meta | 11 +++++++++++ 79 files changed, 815 insertions(+), 1 deletion(-) delete mode 100644 .gitignore create mode 100644 Editor.meta create mode 100644 Editor/CodeGenerator.cs.meta create mode 100644 Editor/Mathfs.Editor.asmdef.meta create mode 100644 Editor/MathfsCodegen.cs.meta create mode 100644 LICENSE.txt.meta create mode 100644 README.md.meta create mode 100644 Runtime.meta create mode 100644 Runtime/Curves.meta create mode 100644 Runtime/Curves/IParamCurve.cs.meta create mode 100644 Runtime/Curves/Polynomial.cs.meta create mode 100644 Runtime/Curves/Polynomial2D.cs.meta create mode 100644 Runtime/Curves/Polynomial3D.cs.meta create mode 100644 Runtime/Curves/Polynomial4D.cs.meta create mode 100644 Runtime/Extensions.cs.meta create mode 100644 Runtime/Geometric Shapes.meta create mode 100644 Runtime/Geometric Shapes/Box.cs.meta create mode 100644 Runtime/Geometric Shapes/Circle.cs.meta create mode 100644 Runtime/Geometric Shapes/ILinear2D.cs.meta create mode 100644 Runtime/Geometric Shapes/Line2D.cs.meta create mode 100644 Runtime/Geometric Shapes/LineSegment2D.cs.meta create mode 100644 Runtime/Geometric Shapes/Polygon.cs.meta create mode 100644 Runtime/Geometric Shapes/PolygonClipper.cs.meta create mode 100644 Runtime/Geometric Shapes/Ray2D.cs.meta create mode 100644 Runtime/Geometric Shapes/Triangle.cs.meta create mode 100644 Runtime/IntersectionTestCore.cs.meta create mode 100644 Runtime/IntersectionTestWrappers.cs.meta create mode 100644 Runtime/Mathfs.cs.meta create mode 100644 Runtime/MathfsAsmdef.asmdef.meta create mode 100644 Runtime/Numerics.meta create mode 100644 Runtime/Numerics/FloatRange.cs.meta create mode 100644 Runtime/Numerics/Matrix3x1.cs.meta create mode 100644 Runtime/Numerics/Matrix4x1.cs.meta create mode 100644 Runtime/Numerics/Rational.cs.meta create mode 100644 Runtime/Numerics/RationalMatrix3x3.cs.meta create mode 100644 Runtime/Numerics/RationalMatrix4x4.cs.meta create mode 100644 Runtime/Numerics/Vector2Matrix3x1.cs.meta create mode 100644 Runtime/Numerics/Vector2Matrix4x1.cs.meta create mode 100644 Runtime/Numerics/Vector3Matrix3x1.cs.meta create mode 100644 Runtime/Numerics/Vector3Matrix4x1.cs.meta create mode 100644 Runtime/Numerics/Vector4Matrix3x1.cs.meta create mode 100644 Runtime/Numerics/Vector4Matrix4x1.cs.meta create mode 100644 Runtime/Random.cs.meta create mode 100644 Runtime/Splines.meta create mode 100644 Runtime/Splines/CatRomType.cs.meta create mode 100644 Runtime/Splines/CharMatrix.cs.meta create mode 100644 Runtime/Splines/Multi-Segment Splines.meta create mode 100644 Runtime/Splines/Multi-Segment Splines/BSpline2D.cs.meta create mode 100644 Runtime/Splines/Multi-Segment Splines/NURBS2D.cs.meta create mode 100644 Runtime/Splines/Non-Uniform Spline Segments.meta create mode 100644 Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs.meta create mode 100644 Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs.meta create mode 100644 Runtime/Splines/SplineUtils.cs.meta create mode 100644 Runtime/Splines/Trajectory2D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/Bezier2D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/Bezier3D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/BezierCubic1D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/BezierCubic2D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/BezierCubic3D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/BezierCubic4D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/BezierQuad1D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/BezierQuad2D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/BezierQuad3D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/BezierQuad4D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/CatRomCubic1D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/CatRomCubic2D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/CatRomCubic3D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/CatRomCubic4D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/HermiteCubic1D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/HermiteCubic2D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/HermiteCubic3D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/HermiteCubic4D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/UBSCubic1D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/UBSCubic2D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/UBSCubic3D.cs.meta create mode 100644 Runtime/Splines/Uniform Spline Segments/UBSCubic4D.cs.meta create mode 100644 Runtime/Splines/UniformCurveSampler.cs.meta create mode 100644 Runtime/UtilityTypes.cs.meta diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 91f9a20..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.meta diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..3c43245 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 481a01d96fbac42fd97dcbab9d904dd7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/CodeGenerator.cs.meta b/Editor/CodeGenerator.cs.meta new file mode 100644 index 0000000..b4c709c --- /dev/null +++ b/Editor/CodeGenerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 809f3c22dc3524a428c8cea98678b5da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Mathfs.Editor.asmdef.meta b/Editor/Mathfs.Editor.asmdef.meta new file mode 100644 index 0000000..378efcc --- /dev/null +++ b/Editor/Mathfs.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e310b6bc7297442988d0ba2f3b166f3a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/MathfsCodegen.cs.meta b/Editor/MathfsCodegen.cs.meta new file mode 100644 index 0000000..c437f9b --- /dev/null +++ b/Editor/MathfsCodegen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c152a4285a384e82b731c40d3751c36 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE.txt.meta b/LICENSE.txt.meta new file mode 100644 index 0000000..be2250f --- /dev/null +++ b/LICENSE.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e2a7f2371f43d4faca3cdab60b7679a7 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..d4e58cb --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c12d18034743d4bd7b7992ffada185ec +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..31d92cb --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 19167c22abdb4380b3b9de2d78ac7819 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves.meta b/Runtime/Curves.meta new file mode 100644 index 0000000..4dbd77f --- /dev/null +++ b/Runtime/Curves.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 06a46a22856424f5184e3ca3d254c609 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves/IParamCurve.cs.meta b/Runtime/Curves/IParamCurve.cs.meta new file mode 100644 index 0000000..c3d9b5b --- /dev/null +++ b/Runtime/Curves/IParamCurve.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0d319ba2d8a94359ad22777db36c523 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves/Polynomial.cs.meta b/Runtime/Curves/Polynomial.cs.meta new file mode 100644 index 0000000..3f7dd11 --- /dev/null +++ b/Runtime/Curves/Polynomial.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d77cc56ff77094210bc83f74c421077c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves/Polynomial2D.cs.meta b/Runtime/Curves/Polynomial2D.cs.meta new file mode 100644 index 0000000..8ea9ff9 --- /dev/null +++ b/Runtime/Curves/Polynomial2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d8cebec39f574d0cb2d29824afc2220 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves/Polynomial3D.cs.meta b/Runtime/Curves/Polynomial3D.cs.meta new file mode 100644 index 0000000..d5b8bcf --- /dev/null +++ b/Runtime/Curves/Polynomial3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db7bebf9d29d7450b978e9e990eed482 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves/Polynomial4D.cs.meta b/Runtime/Curves/Polynomial4D.cs.meta new file mode 100644 index 0000000..9f90047 --- /dev/null +++ b/Runtime/Curves/Polynomial4D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4abb3aedb35e5495eb7a39471860fc50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Extensions.cs.meta b/Runtime/Extensions.cs.meta new file mode 100644 index 0000000..300ed17 --- /dev/null +++ b/Runtime/Extensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4e671a7c3c775403485f53cdb833fd46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes.meta b/Runtime/Geometric Shapes.meta new file mode 100644 index 0000000..e691a22 --- /dev/null +++ b/Runtime/Geometric Shapes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 38fb77b76c22f4e9da81e718f7f273ef +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Box.cs.meta b/Runtime/Geometric Shapes/Box.cs.meta new file mode 100644 index 0000000..9149437 --- /dev/null +++ b/Runtime/Geometric Shapes/Box.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53d1cb856f6234d75b97c79eaeeb9851 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Circle.cs.meta b/Runtime/Geometric Shapes/Circle.cs.meta new file mode 100644 index 0000000..376abac --- /dev/null +++ b/Runtime/Geometric Shapes/Circle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02ae58e19ebdf4a009bb3e77ee7873c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/ILinear2D.cs.meta b/Runtime/Geometric Shapes/ILinear2D.cs.meta new file mode 100644 index 0000000..7b490da --- /dev/null +++ b/Runtime/Geometric Shapes/ILinear2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 51f75430748c44d949919836cbc44e75 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Line2D.cs.meta b/Runtime/Geometric Shapes/Line2D.cs.meta new file mode 100644 index 0000000..1380d9d --- /dev/null +++ b/Runtime/Geometric Shapes/Line2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a212ccfc9b024c46bf134163234992a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/LineSegment2D.cs.meta b/Runtime/Geometric Shapes/LineSegment2D.cs.meta new file mode 100644 index 0000000..b4c2838 --- /dev/null +++ b/Runtime/Geometric Shapes/LineSegment2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1b97d865bcd2f448eae8e4a1089aa6ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Polygon.cs.meta b/Runtime/Geometric Shapes/Polygon.cs.meta new file mode 100644 index 0000000..0183321 --- /dev/null +++ b/Runtime/Geometric Shapes/Polygon.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 14a0a8a5c63dc4b35848977e18cb4a0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/PolygonClipper.cs.meta b/Runtime/Geometric Shapes/PolygonClipper.cs.meta new file mode 100644 index 0000000..daf7a4d --- /dev/null +++ b/Runtime/Geometric Shapes/PolygonClipper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 433af421925ac41db9300c8146938f22 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Ray2D.cs.meta b/Runtime/Geometric Shapes/Ray2D.cs.meta new file mode 100644 index 0000000..9f5524b --- /dev/null +++ b/Runtime/Geometric Shapes/Ray2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f7ef092194dc43aebdd9a3d63b6b1c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Triangle.cs.meta b/Runtime/Geometric Shapes/Triangle.cs.meta new file mode 100644 index 0000000..868f3ba --- /dev/null +++ b/Runtime/Geometric Shapes/Triangle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 524889b1a2a414d7e8c0bf355912df9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/IntersectionTestCore.cs.meta b/Runtime/IntersectionTestCore.cs.meta new file mode 100644 index 0000000..264f20c --- /dev/null +++ b/Runtime/IntersectionTestCore.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b59506ec891ae4eecb84750ddd5fe0ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/IntersectionTestWrappers.cs.meta b/Runtime/IntersectionTestWrappers.cs.meta new file mode 100644 index 0000000..78cfbef --- /dev/null +++ b/Runtime/IntersectionTestWrappers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fe72063fc6ef488fb5995d7a9043e5a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Mathfs.cs.meta b/Runtime/Mathfs.cs.meta new file mode 100644 index 0000000..9373f7e --- /dev/null +++ b/Runtime/Mathfs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2069a5561e158411e9e5f4a6e688b7e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/MathfsAsmdef.asmdef.meta b/Runtime/MathfsAsmdef.asmdef.meta new file mode 100644 index 0000000..954f77f --- /dev/null +++ b/Runtime/MathfsAsmdef.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6071c9f2ce0a4407c93af459fa416e54 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics.meta b/Runtime/Numerics.meta new file mode 100644 index 0000000..83f5893 --- /dev/null +++ b/Runtime/Numerics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 00f54cc78acf34c319c7a4961ce108c7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/FloatRange.cs.meta b/Runtime/Numerics/FloatRange.cs.meta new file mode 100644 index 0000000..f64ad87 --- /dev/null +++ b/Runtime/Numerics/FloatRange.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b99f89e16c9e64dc59e6b11cbf47e2d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Matrix3x1.cs.meta b/Runtime/Numerics/Matrix3x1.cs.meta new file mode 100644 index 0000000..72c0ac0 --- /dev/null +++ b/Runtime/Numerics/Matrix3x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 46eb5269471cf45fe92c8d3c3f846794 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Matrix4x1.cs.meta b/Runtime/Numerics/Matrix4x1.cs.meta new file mode 100644 index 0000000..4cd0a1f --- /dev/null +++ b/Runtime/Numerics/Matrix4x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62662aa0799434a159869af6ad28e0f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Rational.cs.meta b/Runtime/Numerics/Rational.cs.meta new file mode 100644 index 0000000..86624c2 --- /dev/null +++ b/Runtime/Numerics/Rational.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32002ef5527d54148944ed8a754cc7e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/RationalMatrix3x3.cs.meta b/Runtime/Numerics/RationalMatrix3x3.cs.meta new file mode 100644 index 0000000..7ca6c4f --- /dev/null +++ b/Runtime/Numerics/RationalMatrix3x3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c3f12f91d06c450ea67ecad997e7621 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/RationalMatrix4x4.cs.meta b/Runtime/Numerics/RationalMatrix4x4.cs.meta new file mode 100644 index 0000000..81c25f9 --- /dev/null +++ b/Runtime/Numerics/RationalMatrix4x4.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e23eb42353ba44267aad2d4f107fb5f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Vector2Matrix3x1.cs.meta b/Runtime/Numerics/Vector2Matrix3x1.cs.meta new file mode 100644 index 0000000..09179a7 --- /dev/null +++ b/Runtime/Numerics/Vector2Matrix3x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4896fd8a91aeb4827a2f369b30b5c846 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Vector2Matrix4x1.cs.meta b/Runtime/Numerics/Vector2Matrix4x1.cs.meta new file mode 100644 index 0000000..8eaa31f --- /dev/null +++ b/Runtime/Numerics/Vector2Matrix4x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c4f1faa6ec0d4a49af803a0d4893ac9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Vector3Matrix3x1.cs.meta b/Runtime/Numerics/Vector3Matrix3x1.cs.meta new file mode 100644 index 0000000..302d6ea --- /dev/null +++ b/Runtime/Numerics/Vector3Matrix3x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2d4626d12e4443a7813a4345b9efc21 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Vector3Matrix4x1.cs.meta b/Runtime/Numerics/Vector3Matrix4x1.cs.meta new file mode 100644 index 0000000..0cfa3e4 --- /dev/null +++ b/Runtime/Numerics/Vector3Matrix4x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f670854c9791746b08f464357e34ac53 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Vector4Matrix3x1.cs.meta b/Runtime/Numerics/Vector4Matrix3x1.cs.meta new file mode 100644 index 0000000..48f6885 --- /dev/null +++ b/Runtime/Numerics/Vector4Matrix3x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 067c7fbbdf8b242f099b313b03dc26f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Vector4Matrix4x1.cs.meta b/Runtime/Numerics/Vector4Matrix4x1.cs.meta new file mode 100644 index 0000000..58c2ee6 --- /dev/null +++ b/Runtime/Numerics/Vector4Matrix4x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 123ba71fee2e44862b0c1409d27df3ea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Random.cs.meta b/Runtime/Random.cs.meta new file mode 100644 index 0000000..f496c1d --- /dev/null +++ b/Runtime/Random.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8210027925ef4bb5807a0e22c44e047 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines.meta b/Runtime/Splines.meta new file mode 100644 index 0000000..dba3feb --- /dev/null +++ b/Runtime/Splines.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2fd831045bfb24ee9adeb289a9cee378 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/CatRomType.cs.meta b/Runtime/Splines/CatRomType.cs.meta new file mode 100644 index 0000000..0e983bc --- /dev/null +++ b/Runtime/Splines/CatRomType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f8d1051627b6a43a49fa2fd6f564d0b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/CharMatrix.cs.meta b/Runtime/Splines/CharMatrix.cs.meta new file mode 100644 index 0000000..44f592c --- /dev/null +++ b/Runtime/Splines/CharMatrix.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77b97aac785974286bde838188365fd8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Multi-Segment Splines.meta b/Runtime/Splines/Multi-Segment Splines.meta new file mode 100644 index 0000000..f0185b8 --- /dev/null +++ b/Runtime/Splines/Multi-Segment Splines.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 621407d19775a485d938187df7c297f9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs.meta b/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs.meta new file mode 100644 index 0000000..596f7e0 --- /dev/null +++ b/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6efd4bd1062de4b5ab2559215cd3d3c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Multi-Segment Splines/NURBS2D.cs.meta b/Runtime/Splines/Multi-Segment Splines/NURBS2D.cs.meta new file mode 100644 index 0000000..558ab8c --- /dev/null +++ b/Runtime/Splines/Multi-Segment Splines/NURBS2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca7fc048128b44d20a057f6436283f8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Non-Uniform Spline Segments.meta b/Runtime/Splines/Non-Uniform Spline Segments.meta new file mode 100644 index 0000000..05973ec --- /dev/null +++ b/Runtime/Splines/Non-Uniform Spline Segments.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b66eb786294404d91b8f267343232c7b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs.meta b/Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs.meta new file mode 100644 index 0000000..bd1fbc8 --- /dev/null +++ b/Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d8e47e3fa3e54659b61527bf0f41842 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs.meta b/Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs.meta new file mode 100644 index 0000000..a3064b0 --- /dev/null +++ b/Runtime/Splines/Non-Uniform Spline Segments/NUCatRomCubic3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e8fd379bcd8a540d39bfe61760455451 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/SplineUtils.cs.meta b/Runtime/Splines/SplineUtils.cs.meta new file mode 100644 index 0000000..4b69f67 --- /dev/null +++ b/Runtime/Splines/SplineUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c0c94e230385c4b4888e548c3fd650b2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Trajectory2D.cs.meta b/Runtime/Splines/Trajectory2D.cs.meta new file mode 100644 index 0000000..e9c84a9 --- /dev/null +++ b/Runtime/Splines/Trajectory2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77d647eaddaf740dfa0ede98c36474f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments.meta b/Runtime/Splines/Uniform Spline Segments.meta new file mode 100644 index 0000000..a45ee06 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1b1febd520dc5434c8e4bf061f9b6908 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/Bezier2D.cs.meta b/Runtime/Splines/Uniform Spline Segments/Bezier2D.cs.meta new file mode 100644 index 0000000..edd05c3 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/Bezier2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a62110a194614ce0b365b9d24ee54e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/Bezier3D.cs.meta b/Runtime/Splines/Uniform Spline Segments/Bezier3D.cs.meta new file mode 100644 index 0000000..c762531 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/Bezier3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b696f42e5a5c467ebd6ccd85bc70d91 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/BezierCubic1D.cs.meta b/Runtime/Splines/Uniform Spline Segments/BezierCubic1D.cs.meta new file mode 100644 index 0000000..e825429 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/BezierCubic1D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d4c99da6f5ba4f8180ad18ea3b40a53 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/BezierCubic2D.cs.meta b/Runtime/Splines/Uniform Spline Segments/BezierCubic2D.cs.meta new file mode 100644 index 0000000..fba95e4 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/BezierCubic2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7082194004e35467fab3dce11f04f195 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/BezierCubic3D.cs.meta b/Runtime/Splines/Uniform Spline Segments/BezierCubic3D.cs.meta new file mode 100644 index 0000000..636c325 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/BezierCubic3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ed432da1ee6545b09b08bfcf670a3b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/BezierCubic4D.cs.meta b/Runtime/Splines/Uniform Spline Segments/BezierCubic4D.cs.meta new file mode 100644 index 0000000..83ba311 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/BezierCubic4D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba539f1adb9aa4d5981b5adec2f3f8c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/BezierQuad1D.cs.meta b/Runtime/Splines/Uniform Spline Segments/BezierQuad1D.cs.meta new file mode 100644 index 0000000..af6e269 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/BezierQuad1D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a8260881189248708f453a096bd0b65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/BezierQuad2D.cs.meta b/Runtime/Splines/Uniform Spline Segments/BezierQuad2D.cs.meta new file mode 100644 index 0000000..f7f8587 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/BezierQuad2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6eb524f1bc4cd445ba5106f72fb87364 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/BezierQuad3D.cs.meta b/Runtime/Splines/Uniform Spline Segments/BezierQuad3D.cs.meta new file mode 100644 index 0000000..52b3c29 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/BezierQuad3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50fefeb9ca6674a69aa14218c38ee6ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/BezierQuad4D.cs.meta b/Runtime/Splines/Uniform Spline Segments/BezierQuad4D.cs.meta new file mode 100644 index 0000000..e29e23e --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/BezierQuad4D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95c73aefe92d749e0962fd79ccd9b59c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/CatRomCubic1D.cs.meta b/Runtime/Splines/Uniform Spline Segments/CatRomCubic1D.cs.meta new file mode 100644 index 0000000..91007bc --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/CatRomCubic1D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17d55b0e3881c4be18542f651d39605b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/CatRomCubic2D.cs.meta b/Runtime/Splines/Uniform Spline Segments/CatRomCubic2D.cs.meta new file mode 100644 index 0000000..b791225 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/CatRomCubic2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0c539b74c9a44d54b5675954d44ff7c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/CatRomCubic3D.cs.meta b/Runtime/Splines/Uniform Spline Segments/CatRomCubic3D.cs.meta new file mode 100644 index 0000000..3043b8b --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/CatRomCubic3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abce92a8a033c4b0da7f8fbb1828ffcd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/CatRomCubic4D.cs.meta b/Runtime/Splines/Uniform Spline Segments/CatRomCubic4D.cs.meta new file mode 100644 index 0000000..20237c8 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/CatRomCubic4D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fdd4e8e6d088547a195955dd98e51e27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/HermiteCubic1D.cs.meta b/Runtime/Splines/Uniform Spline Segments/HermiteCubic1D.cs.meta new file mode 100644 index 0000000..f074e16 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/HermiteCubic1D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f5df525ea1f3407992f8a28de6dd2c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/HermiteCubic2D.cs.meta b/Runtime/Splines/Uniform Spline Segments/HermiteCubic2D.cs.meta new file mode 100644 index 0000000..26b43a0 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/HermiteCubic2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c66bda3f5beff458998ca6127a3f911d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/HermiteCubic3D.cs.meta b/Runtime/Splines/Uniform Spline Segments/HermiteCubic3D.cs.meta new file mode 100644 index 0000000..18ec0b8 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/HermiteCubic3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 723633b56770946fd95419f8b49ce803 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/HermiteCubic4D.cs.meta b/Runtime/Splines/Uniform Spline Segments/HermiteCubic4D.cs.meta new file mode 100644 index 0000000..e64a753 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/HermiteCubic4D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b9289807d1c84363900e4adc25ab188 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/UBSCubic1D.cs.meta b/Runtime/Splines/Uniform Spline Segments/UBSCubic1D.cs.meta new file mode 100644 index 0000000..60ba3c8 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/UBSCubic1D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cc7e1a18c41064f9d905f02be89a10e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/UBSCubic2D.cs.meta b/Runtime/Splines/Uniform Spline Segments/UBSCubic2D.cs.meta new file mode 100644 index 0000000..97f1904 --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/UBSCubic2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e4308106ac0144929902c1245834058c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/UBSCubic3D.cs.meta b/Runtime/Splines/Uniform Spline Segments/UBSCubic3D.cs.meta new file mode 100644 index 0000000..2cb951a --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/UBSCubic3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bdb3a3a315c444d9b95c59bb5ccf692f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/Uniform Spline Segments/UBSCubic4D.cs.meta b/Runtime/Splines/Uniform Spline Segments/UBSCubic4D.cs.meta new file mode 100644 index 0000000..d07d94c --- /dev/null +++ b/Runtime/Splines/Uniform Spline Segments/UBSCubic4D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8323b54d3931942118674a6807c3aa40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Splines/UniformCurveSampler.cs.meta b/Runtime/Splines/UniformCurveSampler.cs.meta new file mode 100644 index 0000000..ce04353 --- /dev/null +++ b/Runtime/Splines/UniformCurveSampler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 776ce704c8cb64df1b019a96a3121d6e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UtilityTypes.cs.meta b/Runtime/UtilityTypes.cs.meta new file mode 100644 index 0000000..7557008 --- /dev/null +++ b/Runtime/UtilityTypes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 737098dc7b1184e1f91a1d3b18362028 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 1aec7990880265f1d7dbafc9e3b46364293faf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 15:40:38 +0200 Subject: [PATCH 140/301] updated package manifest --- package.json | 14 +++++++++----- package.json.meta | 7 +++++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 package.json.meta diff --git a/package.json b/package.json index 95c2578..8da567c 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,14 @@ { - "name": "com.freyaholmer.mathfs", + "name": "com.acegikmo.mathfs", + "version": "0.1.0", "displayName": "Mathfs", - "description": "Expanded Math Functionality for Unity", - "author": "Freya Holmér", - "version": "1.0.0", + "description": "Advanced math functionality for Unity", "unity": "2021.2", "documentationUrl": "https://github.com/FreyaHolmer/Mathfs", - "licensesUrl": "https://github.com/FreyaHolmer/Mathfs/LICENSE.txt" + "licensesUrl": "https://github.com/FreyaHolmer/Mathfs/LICENSE.txt", + "author": { + "name": "Freya Holmér", + "email": "acegikmo@gmail.com", + "url": "https://acegikmo.com/" + } } diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..9ba3ca1 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2b5953667ba52bc47bb20558274637f2 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 058e0a17dc24b96b217f288e1b906236dca811cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 15:55:33 +0200 Subject: [PATCH 141/301] Color.ToHexString --- Runtime/Extensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index bea92ff..2659b3d 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -205,6 +205,12 @@ public static Quaternion Exp( this Quaternion q ) { /// The multiplier for the alpha [MethodImpl( INLINE )] public static Color MultiplyA( this Color c, float m ) => new Color( c.r, c.g, c.b, c.a * m ); + /// Converts this color to the nearest 32 bit hex string, including the alpha channel. + /// A pure red color of (1,0,0,1) returns "FF0000FF" + /// The color to get the hex string of + /// + [MethodImpl( INLINE )] public static string ToHexString( this Color c ) => ColorUtility.ToHtmlStringRGBA( c ); + #endregion #region Rect From 45db88e1263cfebb89e86b53a5fb8cf7f84142a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 15:55:43 +0200 Subject: [PATCH 142/301] remap documentation fix --- Runtime/Extensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 2659b3d..201d2c0 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -632,10 +632,10 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => /// [MethodImpl( INLINE )] public static float RemapClamped( this float value, float iMin, float iMax, float oMin, float oMax ) => Mathfs.RemapClamped( iMin, iMax, oMin, oMax, value ); - /// + /// [MethodImpl( INLINE )] public static float Remap( this float value, FloatRange inRange, FloatRange outRange ) => Mathfs.Remap( inRange.a, inRange.b, outRange.a, outRange.b, value ); - /// + /// [MethodImpl( INLINE )] public static float RemapClamped( this float value, FloatRange inRange, FloatRange outRange ) => Mathfs.RemapClamped( inRange.a, inRange.b, outRange.a, outRange.b, value ); /// From fd8b186c2bd113270e44e0bff1711f4f7760c18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:02:17 +0200 Subject: [PATCH 143/301] Quaternion docs & quaternion.inverse --- Runtime/Extensions.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 201d2c0..2c7ba8e 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -156,6 +156,7 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { #region Quaternions + /// Returns the natural logarithm of a quaternion public static Quaternion Log( this Quaternion q ) { double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; double vMag = Math.Sqrt( vMagSq ); @@ -170,6 +171,7 @@ public static Quaternion Log( this Quaternion q ) { ); } + /// Returns the natural exponent of a quaternion public static Quaternion Exp( this Quaternion q ) { Vector3 v = new(q.x, q.y, q.z); double vMag = Math.Sqrt( (double)v.x * v.x + (double)v.y * v.y + (double)v.z * v.z ); @@ -178,8 +180,13 @@ public static Quaternion Exp( this Quaternion q ) { return new Quaternion( (float)( scV * v.x ), (float)( scV * v.y ), (float)( scV * v.z ), (float)( sc * Math.Cos( vMag ) ) ); } + /// Multiplies a quaternion by a scalar + /// The quaternion to multiply + /// The scalar value to multiply with public static Quaternion Mul( this Quaternion q, float c ) => new Quaternion( c * q.x, c * q.y, c * q.z, c * q.w ); + /// + public static Quaternion Inverse( this Quaternion q ) => Quaternion.Inverse( q ); #endregion #endregion From 037000d28d597301e93ac82de3b9d2b83862c3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:04:59 +0200 Subject: [PATCH 144/301] transform.TransformRotation extensions --- Runtime/Extensions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 2c7ba8e..53f8758 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -187,6 +187,21 @@ public static Quaternion Exp( this Quaternion q ) { /// public static Quaternion Inverse( this Quaternion q ) => Quaternion.Inverse( q ); + + #endregion + + #region Transform extensions + + /// Transforms a rotation from local space to world space + /// The transform to use + /// The local space rotation + public static Quaternion TransformRotation( this Transform tf, Quaternion quat ) => tf.rotation * quat; + + /// Transforms a rotation from world space to local space + /// The transform to use + /// The world space rotation + public static Quaternion InverseTransformRotation( this Transform tf, Quaternion quat ) => tf.rotation * quat; + #endregion #endregion From 25a595af9bfb1fcff8b709ef097d54d3244ac0e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:07:50 +0200 Subject: [PATCH 145/301] linear polynomial constructor --- Runtime/Curves/Polynomial.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index 83949e6..b5cae3d 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -65,6 +65,11 @@ public float this[ int degree ] { /// The quadratic coefficient public Polynomial( float c0, float c1, float c2 ) => ( this.c0, this.c1, this.c2, this.c3 ) = ( c0, c1, c2, 0 ); + /// Creates a polynomial up to a linear + /// The constant coefficient + /// The linear coefficient + public Polynomial( float c0, float c1 ) => ( this.c0, this.c1, this.c2, this.c3 ) = ( c0, c1, 0, 0 ); + /// Creates a polynomial /// The coefficients to use public Polynomial( Vector4 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.x, coefficients.y, coefficients.z, coefficients.w ); From 793f2cb9fdc48a85ee5768c555b7f38d10610338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:08:52 +0200 Subject: [PATCH 146/301] polynomial add/subtract --- Runtime/Curves/Polynomial.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index b5cae3d..2d24f3d 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -349,6 +349,8 @@ static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { public static Polynomial operator /( float v, Polynomial p ) => new(v / p.c0, v / p.c1, v / p.c2, v / p.c3); public static Polynomial operator *( Polynomial p, float v ) => new(p.c0 * v, p.c1 * v, p.c2 * v, p.c3 * v); public static Polynomial operator *( float v, Polynomial p ) => p * v; + public static Polynomial operator +( Polynomial a, Polynomial b ) => new(a.c0 + b.c0, a.c1 + b.c1, a.c2 + b.c2, a.c3 + b.c3); + public static Polynomial operator -( Polynomial a, Polynomial b ) => new(a.c0 - b.c0, a.c1 - b.c1, a.c2 - b.c2, a.c3 - b.c3); public static explicit operator Matrix3x1( Polynomial poly ) => new(poly.c0, poly.c1, poly.c2); public static explicit operator Matrix4x1( Polynomial poly ) => new(poly.c0, poly.c1, poly.c2, poly.c3); From 7870dae461e7f5c6c8b8503826379718174edb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:09:53 +0200 Subject: [PATCH 147/301] improved polynomial ToString --- Runtime/Curves/Polynomial.cs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index 2d24f3d..988cfcc 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -2,6 +2,7 @@ using System; using System.Runtime.CompilerServices; +using System.Text; using UnityEngine; using UnityEngine.Serialization; @@ -362,6 +363,37 @@ static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { #endregion + static StringBuilder strBuilder = new StringBuilder( 64 ); + static string[] tPowerSuffixStr = new[] { "", "x", "x²", "x³" }; + + public override string ToString() { + strBuilder.Clear(); + + bool hasAddedFirstTerm = false; + for( int c = 0; c < 4; c++ ) { + float value = this[c]; + if( value != 0 ) { + if( hasAddedFirstTerm == false ) { + hasAddedFirstTerm = true; + strBuilder.Append( this[c] ); + } else { + if( value > 0 ) + strBuilder.Append( "+" ); + strBuilder.Append( this[c] ); + if( c > 0 ) + strBuilder.Append( tPowerSuffixStr[c] ); + } + } + } + + if( hasAddedFirstTerm == false ) + return "0"; // no terms. constant 0 + + return strBuilder.ToString(); + } + + public string ToStringCoefficients() => $"({c0},{c1},{c2},{c3})"; + } } \ No newline at end of file From 38fb5fa4b34132cc8c1fd567c4f2e832c227aa17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:10:55 +0200 Subject: [PATCH 148/301] Polynomial3D linear constructor --- Runtime/Curves/Polynomial3D.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index 0c81a5a..71d4ae9 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -46,6 +46,13 @@ public Polynomial3D( Vector3 c0, Vector3 c1, Vector3 c2 ) { this.z = new Polynomial( c0.z, c1.z, c2.z, 0 ); } + /// + public Polynomial3D( Vector3 c0, Vector3 c1 ) { + this.x = new Polynomial( c0.x, c1.x, 0, 0 ); + this.y = new Polynomial( c0.y, c1.y, 0, 0 ); + this.z = new Polynomial( c0.z, c1.z, 0, 0 ); + } + /// public Polynomial3D( Vector3Matrix4x1 coefficients ) => ( x, y, z ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ) ); From 285d96da4209b05dd7df2f481b897dc8a42622bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:13:50 +0200 Subject: [PATCH 149/301] n-degree generic 2D trajectory --- Runtime/Curves/GenericTrajectory2D.cs | 23 ++++++++++++++++++++++ Runtime/Curves/GenericTrajectory2D.cs.meta | 11 +++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Runtime/Curves/GenericTrajectory2D.cs create mode 100644 Runtime/Curves/GenericTrajectory2D.cs.meta diff --git a/Runtime/Curves/GenericTrajectory2D.cs b/Runtime/Curves/GenericTrajectory2D.cs new file mode 100644 index 0000000..1d5f3f0 --- /dev/null +++ b/Runtime/Curves/GenericTrajectory2D.cs @@ -0,0 +1,23 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using Freya; +using UnityEngine; + +public class GenericTrajectory2D { + + public Vector2[] derivatives; + + public GenericTrajectory2D( params Vector2[] derivatives ) => this.derivatives = derivatives; + + public Vector2 GetPosition( float time ) { + Vector2 pt = derivatives[0]; + for( int i = 1; i < derivatives.Length; i++ ) { + float scale = Mathfs.Pow( time, i ) / Mathfs.Factorial( (uint)i ); + pt += scale * derivatives[i]; + } + + return pt; + } + + +} \ No newline at end of file diff --git a/Runtime/Curves/GenericTrajectory2D.cs.meta b/Runtime/Curves/GenericTrajectory2D.cs.meta new file mode 100644 index 0000000..236cec4 --- /dev/null +++ b/Runtime/Curves/GenericTrajectory2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39f2a44aef2c7834da92d8e743f7d335 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From dcb1d6043c82a5141b105bc01b976cb54527e6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:16:52 +0200 Subject: [PATCH 150/301] added LagrangePolynomial2D --- Runtime/Curves/LagrangePolynomial2D.cs | 39 +++++++++++++++++++++ Runtime/Curves/LagrangePolynomial2D.cs.meta | 11 ++++++ 2 files changed, 50 insertions(+) create mode 100644 Runtime/Curves/LagrangePolynomial2D.cs create mode 100644 Runtime/Curves/LagrangePolynomial2D.cs.meta diff --git a/Runtime/Curves/LagrangePolynomial2D.cs b/Runtime/Curves/LagrangePolynomial2D.cs new file mode 100644 index 0000000..86abd3f --- /dev/null +++ b/Runtime/Curves/LagrangePolynomial2D.cs @@ -0,0 +1,39 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System.Collections.Generic; +using UnityEngine; + +namespace Freya { + + public class LagrangePolynomial2D { + + public List points = new List(); + public List knots = null; + public bool Uniform => knots == null; + public FloatRange InternalKnotRange => Uniform ? ( 0, points.Count - 1 ) : ( knots[0], knots[^1] ); + + public Vector2 Eval( float u ) { + float l( int j ) { + float prod = 1; + for( int i = 0; i < points.Count; i++ ) { + if( i == j ) + continue; + if( Uniform ) + prod *= ( u - i ) / ( j - i ); + else + prod *= Mathfs.InverseLerp( knots[i], knots[j], u ); + } + + return prod; + } + + Vector2 sum = Vector2.zero; + for( int j = 0; j < points.Count; j++ ) + sum += points[j] * l( j ); + + return sum; + } + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/LagrangePolynomial2D.cs.meta b/Runtime/Curves/LagrangePolynomial2D.cs.meta new file mode 100644 index 0000000..33f9f81 --- /dev/null +++ b/Runtime/Curves/LagrangePolynomial2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: def04ed833bc2244996ce588be8fb734 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 4e086f95814f371cdbea0da2f25b703ad22ac78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:18:09 +0200 Subject: [PATCH 151/301] added FloatRange.Wrap/Clamp/Reverse --- Runtime/Numerics/FloatRange.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 563ef30..49e1d69 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -73,6 +73,14 @@ public bool Overlaps( FloatRange other ) { return separation < rTotal; } + /// Wraps/repeats the input value to stay within this range + /// The value to wrap/repeat in this interval + public float Wrap( float value ) => a + Mathfs.Repeat( value - a, b - a ); + + /// Clamps the input value to this range + /// The value to clamp to this interval + public float Clamp( float value ) => Mathfs.Clamp( value, Min, Max ); + /// Expands the minimum or maximum value to contain the given value /// The value to include public FloatRange Encapsulate( float value ) => @@ -89,6 +97,9 @@ public FloatRange Encapsulate( float value ) => /// The value to mirror around public FloatRange MirrorAround( float pivot ) => new FloatRange( 2 * pivot - a, 2 * pivot - b ); + /// Returns a reversed version of this range, where a and b is swapped + public FloatRange Reverse() => ( b, a ); + /// Returns the rectangle encapsulating the region defined by a range per axis. Note: The direction of each range is ignored /// The range of the X axis /// The range of the Y axis From 024bf663eadd141b6755bc9090e7fbbb643d3fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:44:55 +0200 Subject: [PATCH 152/301] updated readme --- README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 26208cc..a2005ea 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # Mathfs -Expanded Math Functionality for Unity +Expanded math functionality for Unity ## Installation instructions There are several ways to install this library into our project: -- **Plain install**: Clone or [download](https://github.com/FreyaHolmer/Mathfs/archive/refs/heads/master.zip) this repository and put it somewhere in your Unity project +- **Plain install**: Clone or [download](https://github.com/FreyaHolmer/Mathfs/archive/refs/heads/master.zip) this repository and put it somewhere in the Assets folder of your Unity project - **Unity Package Manager (UPM)**: Add the following line to *Packages/manifest.json*: - - `"com.freyaholmer.mathfs": "https://github.com/FreyaHolmer/Mathfs#1.0.0",` + - `"com.acegikmo.mathfs": "https://github.com/FreyaHolmer/Mathfs#1.0.0",` - **[OpenUPM](https://openupm.com)**: After installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command: - - `openupm add com.freyaholmer.mathfs` + - `openupm add com.acegikmo.mathfs` -After installation you will be able to access the library in script by including namespace `using Freya` +After installation you will be able to access the library in scripts by including the namespace `using Freya` ## Features - 2D Intersection tests between all combinations of: @@ -25,7 +25,7 @@ After installation you will be able to access the library in script by including - Catmull-Rom - B-Spline (Uniform Cubic & Generalized Non-Uniform) - NURBS (Non-Unifrom Rational B-Spline) - - Trajectory (Cubic) + - Trajectory (Cubic & Generalized) - Trajectory math - GetDisplacement (point in trajectory), given gravity, angle, speed & time - GetLaunchSpeed, given gravity, angle & lateral distance @@ -75,9 +75,4 @@ Mathfs.cs **does not fully match Unity's Mathf.cs**, I've made a few changes: - Smoothstep is removed in favor of the more explicit: - LerpSmooth (which is how it was implemented) and - InverseLerpSmooth (which is how it is implemented everywhere but Unity's Mathf.cs) - - Min/Max functions with arbitrary inputs/array input will throw on empty instead of returning 0 - -## Installation instructions -- Download or Git Clone the repository -- Place the downloaded files in a folder in your Unity project Assets/ folder -- Access the library in script by including namespace "using Freya" \ No newline at end of file + - Min/Max functions with arbitrary inputs/array input will throw on empty instead of returning 0 \ No newline at end of file From 05c3df4c8c7ab711735d33de606dee867fd11f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 16:47:13 +0200 Subject: [PATCH 153/301] renamed Mathfs runtime asmdef --- Runtime/{MathfsAsmdef.asmdef => Mathfs.asmdef} | 0 Runtime/{MathfsAsmdef.asmdef.meta => Mathfs.asmdef.meta} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Runtime/{MathfsAsmdef.asmdef => Mathfs.asmdef} (100%) rename Runtime/{MathfsAsmdef.asmdef.meta => Mathfs.asmdef.meta} (100%) diff --git a/Runtime/MathfsAsmdef.asmdef b/Runtime/Mathfs.asmdef similarity index 100% rename from Runtime/MathfsAsmdef.asmdef rename to Runtime/Mathfs.asmdef diff --git a/Runtime/MathfsAsmdef.asmdef.meta b/Runtime/Mathfs.asmdef.meta similarity index 100% rename from Runtime/MathfsAsmdef.asmdef.meta rename to Runtime/Mathfs.asmdef.meta From b45e727b81150c88d5925692cdf1d2eed5fc7c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 18 Oct 2022 17:17:40 +0200 Subject: [PATCH 154/301] Update README.md --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a2005ea..7423d3a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,23 @@ # Mathfs -Expanded math functionality for Unity +Freya's expanded math functionality for Unity! +- This is primarily a way for me to share the math functionality I write and use in my own personal projects +- I will recklessly edit and adapt things without too much thought into backwards compatibility +- Minimum Unity version is currently 2021.2 due to using newer C# version features. It may be possible to auto-downgrade through your IDE if necessary +- Commits with version tags should be relatively stable. Other commits may not be ## Installation instructions -There are several ways to install this library into our project: +There are several ways to install this library into your project: -- **Plain install**: Clone or [download](https://github.com/FreyaHolmer/Mathfs/archive/refs/heads/master.zip) this repository and put it somewhere in the Assets folder of your Unity project -- **Unity Package Manager (UPM)**: Add the following line to *Packages/manifest.json*: - - `"com.acegikmo.mathfs": "https://github.com/FreyaHolmer/Mathfs#1.0.0",` -- **[OpenUPM](https://openupm.com)**: After installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command: +- **Plain install** + - Clone or [download](https://github.com/FreyaHolmer/Mathfs/archive/refs/heads/master.zip) this repository and put it somewhere in the Assets folder of your Unity project +- **Unity Package Manager (UPM)**: + - Add either of the the following lines to *Packages/manifest.json*: + - `"com.acegikmo.mathfs": "https://github.com/FreyaHolmer/Mathfs#0.1.0",` if you want to target a specific version (recommended) + - `"com.acegikmo.mathfs": "https://github.com/FreyaHolmer/Mathfs",` if you want to pull the latest commit (potentially unstable) + - More information about UPM and git [here](https://docs.unity3d.com/Manual/upm-git.html) +- **[OpenUPM](https://openupm.com)** + - After installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command: - `openupm add com.acegikmo.mathfs` After installation you will be able to access the library in scripts by including the namespace `using Freya` @@ -75,4 +84,4 @@ Mathfs.cs **does not fully match Unity's Mathf.cs**, I've made a few changes: - Smoothstep is removed in favor of the more explicit: - LerpSmooth (which is how it was implemented) and - InverseLerpSmooth (which is how it is implemented everywhere but Unity's Mathf.cs) - - Min/Max functions with arbitrary inputs/array input will throw on empty instead of returning 0 \ No newline at end of file + - Min/Max functions with arbitrary inputs/array input will throw on empty instead of returning 0 From d5ebd09922678eccdef575bdd3fd4d2bd32aff33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Nov 2022 21:50:47 +0100 Subject: [PATCH 155/301] two new linear polynomial constructors --- Runtime/Curves/Polynomial.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index 988cfcc..1984e84 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -183,6 +183,21 @@ public FloatRange OutputRange01 { /// The linear coefficient a in ax+b public static Polynomial Linear( float c0, float c1 ) => new Polynomial( c0, c1, 0, 0 ); + /// Creates a linear polynomial of the form ax+b from two points a and b + /// The first point + /// The second point + public static Polynomial Linear( Vector2 a, Vector2 b ) => Linear( a.x, a.y, b.x, b.y ); + + /// Creates a linear polynomial of the form ax+b from two points + /// The coordinate of the first point + /// The value of the first point + /// The coordinate of the second point + /// The value of the second point + public static Polynomial Linear( float x0, float y0, float x1, float y1 ) { + float d = ( y1 - y0 ) / ( x1 - x0 ); + return new Polynomial( y0 - d * x0, d, 0, 0 ); + } + /// Creates a quadratic polynomial /// The constant coefficient /// The linear coefficient From 97a3930300e43b9e945676c9891a3a51a9f688af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Nov 2022 21:52:24 +0100 Subject: [PATCH 156/301] non-uniform hermite calculations --- Runtime/Splines/SplineUtils.cs | 53 ++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Runtime/Splines/SplineUtils.cs b/Runtime/Splines/SplineUtils.cs index 31867fc..d7623e5 100644 --- a/Runtime/Splines/SplineUtils.cs +++ b/Runtime/Splines/SplineUtils.cs @@ -190,6 +190,59 @@ internal static Polynomial3D CalculateCatRomCurve( Vector3Matrix4x1 m, Matrix4x1 return new Polynomial3D( GetNUCatRomCharMatrix( knots ).MultiplyColumnVector( m ) ); } + internal static Polynomial3D CalculateHermiteCurve( Vector3Matrix4x1 m, float k0, float k1 ) { + return new Polynomial3D( GetNUHermiteCharMatrix( k0, k1 ).MultiplyColumnVector( m ) ); + } + + internal static Polynomial2D CalculateHermiteCurve( Vector2Matrix4x1 m, float k0, float k1 ) { + return new Polynomial2D( GetNUHermiteCharMatrix( k0, k1 ).MultiplyColumnVector( m ) ); + } + + static Matrix4x4 GetNUHermiteCharMatrix( float k0, float k1 ) { + float d = k1 - k0; + float d2 = d * d; + float d3 = d * d * d; + float k0_2 = k0 * k0; + float k0_3 = k0 * k0 * k0; + + // row 0 + float m02 = ( 3 * k0_2 ) / d2 + ( 2 * k0_3 ) / d3; + float m00 = 1 - m02; + float _k02d = k0_2 / d; + float _k03d2 = k0_3 / d2; + float m03 = -_k02d - _k03d2; + float m01 = -k0 - 2 * _k02d - _k03d2; + + // row 1 + float _2k0d = 2 * k0 / d; + float _3k02d2 = 3 * k0_2 / d2; + float m13 = _2k0d + _3k02d2; + float m11 = 1 + 2 * _2k0d + _3k02d2; + float m10 = 6 * ( k0 / d2 + k0_2 / d3 ); + float m12 = -m10; + + // row 2 + float m22 = 3 / d2 + ( 6 * k0 ) / d3; + float m20 = -m22; + float _dRcp = 1 / d; + float _3k0d2 = 3 * k0 / d2; + float m23 = -_dRcp - _3k0d2; + float m21 = m23 - _dRcp; + + // row 3 + float m30 = 2 / d3; + float m31 = 1 / d2; + float m32 = -m30; + float m33 = m31; + + return CharMatrix.Create( + m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33 + ); + } + } } \ No newline at end of file From 68c20faccbe19495b39f9469d20ba66b2bc0c439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Nov 2022 21:52:50 +0100 Subject: [PATCH 157/301] non-uniform catrom tweaks --- Runtime/Splines/SplineUtils.cs | 73 ++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/Runtime/Splines/SplineUtils.cs b/Runtime/Splines/SplineUtils.cs index d7623e5..292af0f 100644 --- a/Runtime/Splines/SplineUtils.cs +++ b/Runtime/Splines/SplineUtils.cs @@ -82,7 +82,7 @@ static Matrix4x1 CalcCatRomKnots( float sqMag01, float sqMag12, float sqMag23, f return new(k0, k1, k2, k3); } - static Matrix4x4 GetNUCatRomCharMatrix( Matrix4x1 knots ) { + public static Matrix4x4 GetNUCatRomCharMatrix( Matrix4x1 knots ) { float k0 = knots.m0; float k1 = knots.m1; float k2 = knots.m2; @@ -141,18 +141,67 @@ static Matrix4x4 GetNUCatRomCharMatrix( Matrix4x1 knots ) { float i12sq = i12 * i12; float i13 = k1 - k3; float i23 = k2 - k3; - float p0sc = 1f / ( i01 * i02 * i12 ); - float p1sc = 1f / ( i01 * i12sq * i13 ); - float p2sc = 1f / ( i02 * i12sq * i23 ); - float p3sc = 1f / ( i12 * i13 * i23 ); + float p0sc = ( i01 * i02 * i12 ); + float p1sc = ( i01 * i12sq * i13 ); + float p2sc = ( i02 * i12sq * i23 ); + float p3sc = ( i12 * i13 * i23 ); return CharMatrix.Create( - p0sc * p0u0, p1sc * p1u0, p2sc * p2u0, p3sc * p3u0, - p0sc * p0u1, p1sc * p1u1, p2sc * p2u1, p3sc * p3u1, - p0sc * p0u2, p1sc * p1u2, p2sc * p2u2, p3sc * p3u2, - p0sc * p0u3, p1sc * p1u3, p2sc * p2u3, p3sc * p3u3 + p0u0 / p0sc, p1u0 / p1sc, p2u0 / p2sc, p3u0 / p3sc, + p0u1 / p0sc, p1u1 / p1sc, p2u1 / p2sc, p3u1 / p3sc, + p0u2 / p0sc, p1u2 / p1sc, p2u2 / p2sc, p3u2 / p3sc, + p0u3 / p0sc, p1u3 / p1sc, p2u3 / p2sc, p3u3 / p3sc ); } + public static Vector3 GetNUCatRomCharMatrixC2End( Matrix4x1 knots, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3 ) { + float k0 = knots.m0; + float k1 = knots.m1; + float k2 = knots.m2; + float k3 = knots.m3; + + float k1k1 = k1 * k1; + float k2k2 = k2 * k2; + float k0k1 = k0 * k1; + float _2k0k1 = 2 * k0k1; + float k0k2 = k0 * k2; + float k1k2 = k1 * k2; + float k1k3 = k1 * k3; + float k2k3 = k2 * k3; + float _2k2k3 = 2 * k2k3; + + float common = _2k0k1 + k0k2 - k1k3 - _2k2k3; + + // CHAR matrix COLUMN 0: + // CHAR matrix COLUMN 1: + // CHAR matrix COLUMN 2: + // CHAR matrix COLUMN 3: + + float i01 = k0 - k1; + float i02 = k0 - k2; + float i12 = k1 - k2; + float i12sq = i12 * i12; + float i13 = k1 - k3; + float i23 = k2 - k3; + float p0sc = ( i01 * i02 * i12 ); + float p1sc = ( i01 * i12sq * i13 ); + float p2sc = ( i02 * i12sq * i23 ); + float p3sc = ( i12 * i13 * i23 ); + + float m20 = (-k1 - 2 * k2) / p0sc; + float m21 = (common - k1k1 + k1k2) / p1sc; + float m22 = (-common - k2k2 + k1k2) / p2sc; + float m23 = (2 * k1 + k2) / p3sc; + float m30 = 1f / p0sc; + float m31 = (k3 - k0) / p1sc; + float m32 = (k0 - k3) / p2sc; + float m33 = -1f / p3sc; + + return p0 * ( ( m20 + 3 * m30 ) / m23 ) + + p1 * ( ( m21 + 3 * m31 - m20 ) / m23 ) + + p2 * ( ( m22 + 3 * m32 - m21 ) / m23 ) + + p3 * ( 1 + ( 3 * m33 - m22 ) / m23 ); + } + static Matrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { float k0mk3 = k0 - k3; float k0m2k3 = k0mk3 - k3; @@ -176,9 +225,9 @@ static Matrix4x4 GetNUCatRomCharMatrixUnitInterval( float k0, float k3 ) { return CharMatrix.Create( 0, 1, 0, 0, - p0sc, p1sc * p1u1, p2sc * p2u1, 0, - p0sc * -2, p1sc * p1u2, p2sc * p2u2, p3sc, - p0sc, p1sc * p1u3, p2sc * p2u3, -p3sc + p0sc, -p1u1 / k0k3, p2sc * p2u1, 0, + p0sc * -2, -p1u2 / k0k3, p2sc * p2u2, p3sc, + p0sc, -p1u3 / k0k3, p2sc * p2u3, -p3sc ); } From c403acf220e7dfe0e4ab32e51b985246420d5b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Nov 2022 21:53:44 +0100 Subject: [PATCH 158/301] cleaned up BSpline2D error --- Runtime/Splines/Multi-Segment Splines/BSpline2D.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs b/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs index 60f2f05..a7fb4b6 100644 --- a/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs +++ b/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs @@ -24,8 +24,9 @@ public BSpline2D( Vector2[] points, float[] knots, int degree = 3 ) { this.knots = knots; this.degree = degree; this.evalBuffer = new Vector2[degree + 1]; - if( knots.Length != SplineUtils.BSplineKnotCount( this.points.Length, this.degree ) ) - throw new ArgumentException( $"The knots array has to be of length (degree+pointCount+1). Got an array of {knots.Length} knots, expected ${KnotCount}", nameof(knots) ); + int expectedKnotCount = SplineUtils.BSplineKnotCount( this.points.Length, this.degree ); + if( knots.Length != expectedKnotCount ) + throw new ArgumentException( $"The knots array has to be of length (degree+pointCount+1). Got an array of {knots.Length} knots, expected {expectedKnotCount}", nameof(knots) ); } /// Creates a uniform B-spline of the given degree, automatically configuring the knot vector to be uniform From 37b8cbd2897b16e329f1574e03900d8495ebe096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Nov 2022 21:55:39 +0100 Subject: [PATCH 159/301] Added 3D line/plane functions --- Runtime/Geometric Shapes/ILinear2D.cs | 22 ++-- Runtime/Geometric Shapes/ILinear3D.cs | 75 +++++++++++ Runtime/Geometric Shapes/ILinear3D.cs.meta | 11 ++ Runtime/Geometric Shapes/Line3D.cs | 118 ++++++++++++++++++ Runtime/Geometric Shapes/Line3D.cs.meta | 11 ++ Runtime/Geometric Shapes/LineSegment3D.cs | 81 ++++++++++++ .../Geometric Shapes/LineSegment3D.cs.meta | 11 ++ Runtime/Geometric Shapes/Plane3D.cs | 35 ++++++ Runtime/Geometric Shapes/Plane3D.cs.meta | 11 ++ 9 files changed, 362 insertions(+), 13 deletions(-) create mode 100644 Runtime/Geometric Shapes/ILinear3D.cs create mode 100644 Runtime/Geometric Shapes/ILinear3D.cs.meta create mode 100644 Runtime/Geometric Shapes/Line3D.cs create mode 100644 Runtime/Geometric Shapes/Line3D.cs.meta create mode 100644 Runtime/Geometric Shapes/LineSegment3D.cs create mode 100644 Runtime/Geometric Shapes/LineSegment3D.cs.meta create mode 100644 Runtime/Geometric Shapes/Plane3D.cs create mode 100644 Runtime/Geometric Shapes/Plane3D.cs.meta diff --git a/Runtime/Geometric Shapes/ILinear2D.cs b/Runtime/Geometric Shapes/ILinear2D.cs index ad88058..0f4fc12 100644 --- a/Runtime/Geometric Shapes/ILinear2D.cs +++ b/Runtime/Geometric Shapes/ILinear2D.cs @@ -30,31 +30,27 @@ public static class ExtILinear2D { /// Gets a point along this line /// The linear object to get a point along (Ray2D, Line2D or LineSegment2D) /// The t-value along the ray to get the point of. If the ray direction is normalized, t is equivalent to distance - [MethodImpl( INLINE )] public static Vector2 GetPoint( this T linear, float t ) where T : ILinear2D { - return linear.Origin + linear.Dir * t; - } + [MethodImpl( INLINE )] public static Vector2 GetPoint( this T linear, float t ) where T : ILinear2D => linear.Origin + linear.Dir * t; /// Returns the t-value of a point projected onto this line /// The linear object to project onto (Ray2D, Line2D or LineSegment2D) /// The point to use when projecting - [MethodImpl( INLINE )] public static float ProjectPointTValue( this T linear, Vector2 point ) where T : ILinear2D { - float t = Line2D.ProjectPointToLineTValue( linear.Origin, linear.Dir, point ); - return linear.ClampTValue( t ); - } + [MethodImpl( INLINE )] public static float ProjectPointTValue( this T linear, Vector2 point ) where T : ILinear2D => linear.ClampTValue( Line2D.ProjectPointToLineTValue( linear.Origin, linear.Dir, point ) ); /// Projects a point onto this line /// The linear object to project onto (Ray2D, Line2D or LineSegment2D) /// The point to project - [MethodImpl( INLINE )] public static Vector2 ProjectPoint( this T linear, Vector2 point ) where T : ILinear2D { - return linear.GetPoint( linear.ProjectPointTValue( point ) ); - } + [MethodImpl( INLINE )] public static Vector2 ProjectPoint( this T linear, Vector2 point ) where T : ILinear2D => linear.GetPoint( linear.ProjectPointTValue( point ) ); /// The shortest distance from this line to a point /// The linear object to check distance from (Ray2D, Line2D or LineSegment2D) /// The point to check the distance to - [MethodImpl( INLINE )] public static float Distance( this T linear, Vector2 point ) where T : ILinear2D { - return Vector2.Distance( point, linear.ProjectPoint( point ) ); - } + [MethodImpl( INLINE )] public static float Distance( this T linear, Vector2 point ) where T : ILinear2D => Mathfs.Sqrt( DistanceSqr( linear, point ) ); + + /// The shortest squared distance from this line to a point + /// The linear object to check distance from (Ray2D, Line2D or LineSegment2D) + /// The point to check the distance to + [MethodImpl( INLINE )] public static float DistanceSqr( this T linear, Vector2 point ) where T : ILinear2D => ( point - linear.ProjectPoint( point ) ).sqrMagnitude; #region Intersection Tests diff --git a/Runtime/Geometric Shapes/ILinear3D.cs b/Runtime/Geometric Shapes/ILinear3D.cs new file mode 100644 index 0000000..626a7c5 --- /dev/null +++ b/Runtime/Geometric Shapes/ILinear3D.cs @@ -0,0 +1,75 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + /// A shared interface between Ray3D, Line3D and LineSegment3D + public interface ILinear3D { + + /// The origin of this linear 3D object (Ray3D, Line3D or LineSegment3D) + Vector3 Origin { get; } + + /// The direction of this linear 3D object (Ray3D, Line3D or LineSegment3D). Note: this vector may or may not be normalized + Vector3 Dir { get; } + + /// Returns whether or not this t-value is within this linear 3D object (Ray3D, Line3D or LineSegment3D) + /// The t-value along the linear 3D object + bool IsValidTValue( float t ); + + /// Clamps the value into the range of this linear 3D object (Ray3D, Line3D or LineSegment3D) + /// The t-value along the linear 3D object + float ClampTValue( float t ); + } + + public static class ExtILinear3D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// Gets a point along this line + /// The linear object to get a point along (Ray3D, Line3D or LineSegment3D) + /// The t-value along the ray to get the point of. If the ray direction is normalized, t is equivalent to distance + [MethodImpl( INLINE )] public static Vector3 GetPoint( this T linear, float t ) where T : ILinear3D => linear.Origin + linear.Dir * t; + + /// Returns the t-value of a point projected onto this line + /// The linear object to project onto (Ray3D, Line3D or LineSegment3D) + /// The point to use when projecting + [MethodImpl( INLINE )] public static float ProjectPointTValue( this T linear, Vector3 point ) where T : ILinear3D => linear.ClampTValue( Line3D.ProjectPointToLineTValue( linear.Origin, linear.Dir, point ) ); + + /// The t-values at the shortest squared distance between two linear objects + /// The linear object to check distance from (Ray3D, Line3D or LineSegment3D) + /// The other linear object to check the distance to + [MethodImpl( INLINE )] public static (float,float) LinearTValues( this A linear, B other ) where A : ILinear3D where B : ILinear3D { + ( float tA, float tB ) = Line3D.ClosestPointBetweenLinesTValues( linear.Origin, linear.Dir, other.Origin, other.Dir ); + tA = linear.ClampTValue( tA ); + tB = other.ClampTValue( tB ); + return ( tA, tB ); + } + + /// Projects a point onto this line + /// The linear object to project onto (Ray3D, Line3D or LineSegment3D) + /// The point to project + [MethodImpl( INLINE )] public static Vector3 ProjectPoint( this T linear, Vector3 point ) where T : ILinear3D => linear.GetPoint( linear.ProjectPointTValue( point ) ); + + /// The shortest distance from this line to a point + /// The linear object to check distance from (Ray3D, Line3D or LineSegment3D) + /// The point to check the distance to + [MethodImpl( INLINE )] public static float Distance( this T linear, Vector3 point ) where T : ILinear3D => Mathfs.Sqrt( DistanceSqr( linear, point ) ); + + /// The shortest squared distance from this line to a point + /// The linear object to check distance from (Ray3D, Line3D or LineSegment3D) + /// The point to check the distance to + [MethodImpl( INLINE )] public static float DistanceSqr( this T linear, Vector3 point ) where T : ILinear3D => ( point - linear.ProjectPoint( point ) ).sqrMagnitude; + + /// The shortest squared distance from this line to another line + /// The linear object to check distance from (Ray3D, Line3D or LineSegment3D) + /// The other linear object to check the distance to + [MethodImpl( INLINE )] public static float DistanceSqr( this A linear, B other ) where A : ILinear3D where B : ILinear3D { + ( float tA, float tB ) = LinearTValues( linear, other ); + return ( linear.GetPoint( tA ) - other.GetPoint( tB ) ).sqrMagnitude; + } + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Shapes/ILinear3D.cs.meta b/Runtime/Geometric Shapes/ILinear3D.cs.meta new file mode 100644 index 0000000..735a3a3 --- /dev/null +++ b/Runtime/Geometric Shapes/ILinear3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 35cc3b65d9cb6e84e99bed822ec6fe09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Line3D.cs b/Runtime/Geometric Shapes/Line3D.cs new file mode 100644 index 0000000..7b7aa12 --- /dev/null +++ b/Runtime/Geometric Shapes/Line3D.cs @@ -0,0 +1,118 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; +using static Freya.Mathfs; + +namespace Freya { + + // 3D line math + /// A structure representing an infinitely long 3D line + [Serializable] public struct Line3D : ILinear3D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// The origin of this line + public Vector3 origin; + + /// The direction of the ray. Note: Line3D allows non-normalized direction vectors + public Vector3 dir; + + /// Returns a normalized version of this line. Normalized lines ensure t-values correspond to distance + public Line3D Normalized => new Line3D( origin, dir ); + + /// Creates an infinitely long 3D line, given an origin and a direction + /// The origin of the line + /// The direction of the line. It does not have to be normalized, but if it is, the t-value when sampling will correspond to distance along the ray + public Line3D( Vector3 origin, Vector3 dir ) => ( this.origin, this.dir ) = ( origin, dir ); + + /// The signed distance from this line to a point. Points to the left of this line are positive + /// The point to check the signed distance to + [MethodImpl( INLINE )] public float SignedDistance( Vector3 point ) => Determinant( dir.normalized, point - origin ); + + #region Interface stuff for generic line tests + + [MethodImpl( INLINE )] bool ILinear3D.IsValidTValue( float t ) => true; // just always valid uwu + [MethodImpl( INLINE )] float ILinear3D.ClampTValue( float t ) => t; // :) + Vector3 ILinear3D.Origin { + [MethodImpl( INLINE )] get => origin; + } + Vector3 ILinear3D.Dir { + [MethodImpl( INLINE )] get => dir; + } + + #endregion + + #region Statics (general linear 3D methods) + + /// Projects a point onto an infinite line, returning the t-value along the line + /// Line origin + /// Line direction (does not have to be normalized) + /// The point to project onto the line + [MethodImpl( INLINE )] public static float ProjectPointToLineTValue( Vector3 lineOrigin, Vector3 lineDir, Vector3 point ) { + return Vector3.Dot( lineDir, point - lineOrigin ) / Vector3.Dot( lineDir, lineDir ); + } + + /// Gets the t-values of the closest point between two infinite lines, returning the two t-values along the line + /// Line A origin + /// Line A direction (does not have to be normalized) + /// Line B origin + /// Line B direction (does not have to be normalized) + [MethodImpl( INLINE )] public static (float tA, float tB) ClosestPointBetweenLinesTValues( Vector3 aOrigin, Vector3 aDir, Vector3 bOrigin, Vector3 bDir ) { + // source: https://math.stackexchange.com/questions/2213165/find-shortest-distance-between-lines-in-3d + Vector3 a = aOrigin; + Vector3 b = aDir; + Vector3 c = bOrigin; + Vector3 d = bDir; + Vector3 e = a - c; + float be = Vector3.Dot( b, e ); + float de = Vector3.Dot( d, e ); + float bd = Vector3.Dot( b, d ); + float b2 = Vector3.Dot( b, b ); + float d2 = Vector3.Dot( d, d ); + float A = -b2 * d2 + bd * bd; + + float s = ( -b2 * de + be * bd ) / A; + float t = ( d2 * be - de * bd ) / A; + + return ( t, s ); + + // Vector3 n = Vector3.Cross( aDir, bDir ); + // float nMag = n.magnitude; + // float dist = Vector3.Dot( n, aOrigin - bOrigin ) / nMag; + } + + /// Projects a point onto an infinite line + /// Line origin + /// Line direction (does not have to be normalized) + /// The point to project onto the line + [MethodImpl( INLINE )] public static Vector3 ProjectPointToLine( Vector3 lineOrigin, Vector3 lineDir, Vector3 point ) { + return lineOrigin + lineDir * ProjectPointToLineTValue( lineOrigin, lineDir, point ); + } + + /// Projects a point onto an infinite line + /// Line to project onto + /// The point to project onto the line + [MethodImpl( INLINE )] public static Vector3 ProjectPointToLine( Line3D line, Vector3 point ) => ProjectPointToLine( line.origin, line.dir, point ); + + /// Returns the signed distance to a 3D plane + /// Plane origin + /// Plane normal (has to be normalized for a true distance) + /// The point to use when checking distance to the plane + [MethodImpl( INLINE )] public static float PointToPlaneSignedDistance( Vector3 planeOrigin, Vector3 planeNormal, Vector3 point ) { + return Vector3.Dot( point - planeOrigin, planeNormal ); + } + + /// Returns the distance to a 3D plane + /// Plane origin + /// Plane normal (has to be normalized for a true distance) + /// The point to use when checking distance to the plane + [MethodImpl( INLINE )] public static float PointToPlaneDistance( Vector3 planeOrigin, Vector3 planeNormal, Vector3 point ) => Abs( PointToPlaneSignedDistance( planeOrigin, planeNormal, point ) ); + + #endregion + + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Shapes/Line3D.cs.meta b/Runtime/Geometric Shapes/Line3D.cs.meta new file mode 100644 index 0000000..8f0fd07 --- /dev/null +++ b/Runtime/Geometric Shapes/Line3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6a2c5d66a5446344b60a60f51987f49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/LineSegment3D.cs b/Runtime/Geometric Shapes/LineSegment3D.cs new file mode 100644 index 0000000..fd42510 --- /dev/null +++ b/Runtime/Geometric Shapes/LineSegment3D.cs @@ -0,0 +1,81 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Runtime.CompilerServices; +using UnityEngine; +using Plane = System.Numerics.Plane; + +namespace Freya { + + /// Represents a line segment, similar to a line but with a defined start and end + [Serializable] public struct LineSegment3D : ILinear3D { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// The start point of the line segment + public Vector3 start; + + /// The end point of the line segment + public Vector3 end; + + /// Creates a line segment with a defined start and end point + /// The start point of the line segment + /// The end point of the line segment + public LineSegment3D( Vector3 start, Vector3 end ) => ( this.start, this.end ) = ( start, end ); + + /// Returns the displacement vector from start to end of this line. Equivalent to end-start + public Vector3 Displacement { + [MethodImpl( INLINE )] get => end - start; + } + + /// Returns the normalized direction of this line. Equivalent to (end-start).normalized + public Vector3 Direction { + [MethodImpl( INLINE )] get => Displacement.normalized; + } + + /// Calculates the length of the line segment + public float Length { + [MethodImpl( INLINE )] get { + float dx = end.x - start.x; + float dy = end.y - start.y; + float dz = end.z - start.z; + return (float)Math.Sqrt( dx * dx + dy * dy + dz * dz ); + } + } + + /// Calculates the length squared (faster than calculating the actual length) + public float LengthSquared { + [MethodImpl( INLINE )] get { + float dx = end.x - start.x; + float dy = end.y - start.y; + float dz = end.z - start.z; + return dx * dx + dy * dy + dz * dz; + } + } + + /// Returns the perpendicular bisector. Note: the returned normal is not normalized to save performance. Use Bisector.Normalized if you want to make sure it is normalized + public Plane3D Bisector { + [MethodImpl( INLINE )] get => GetBisector( start, end ); + } + + /// Returns the perpendicular bisector of the input line segment + /// Starting point of the line segment + /// Endpoint of the line segment + [MethodImpl( INLINE )] public static Plane3D GetBisector( Vector3 startPoint, Vector3 endPoint ) => new Plane3D( ( endPoint - startPoint ).normalized, ( endPoint + startPoint ) / 2 ); + + #region Interface stuff for generic line tests + + [MethodImpl( INLINE )] bool ILinear3D.IsValidTValue( float t ) => t >= 0 && t <= 1; + [MethodImpl( INLINE )] float ILinear3D.ClampTValue( float t ) => t < 0 ? 0 : t > 1 ? 1 : t; + Vector3 ILinear3D.Origin { + [MethodImpl( INLINE )] get => start; + } + Vector3 ILinear3D.Dir { + [MethodImpl( INLINE )] get => end - start; + } + + #endregion + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Shapes/LineSegment3D.cs.meta b/Runtime/Geometric Shapes/LineSegment3D.cs.meta new file mode 100644 index 0000000..8c8eef6 --- /dev/null +++ b/Runtime/Geometric Shapes/LineSegment3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 906a5dc9fe0ac30479250ca70e09d037 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Plane3D.cs b/Runtime/Geometric Shapes/Plane3D.cs new file mode 100644 index 0000000..4ace889 --- /dev/null +++ b/Runtime/Geometric Shapes/Plane3D.cs @@ -0,0 +1,35 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using UnityEngine; + +namespace Freya { + + /// A mathematical plane in 3D space + public struct Plane3D { + + /// The normal of the plane. Note that this type lets you assign non-normalized vectors + public Vector3 normal; + + /// The signed distance from the world origin + public float distance; + + public Vector3 PointClosestToOrigin => normal * distance; + + public Plane3D( Vector3 normal, float distance ) { + this.normal = normal; + this.distance = distance; + } + + public Plane3D( Vector3 normal, Vector3 point ) { + this.normal = normal; + this.distance = Vector3.Dot( normal, point ); + } + + // public static Line3D Intersect( Plane3D a, Plane3D b ) { + // return default; + // } + + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Shapes/Plane3D.cs.meta b/Runtime/Geometric Shapes/Plane3D.cs.meta new file mode 100644 index 0000000..ed6e984 --- /dev/null +++ b/Runtime/Geometric Shapes/Plane3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 449dff60f3928394abea47bcd9791a76 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From dc09147bbde3a5ab70304831c3b397c5f51e6a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Nov 2022 23:30:36 +0100 Subject: [PATCH 160/301] simple hermite 3D spline --- .../NUHermiteCubic3D.cs | 51 +++++++++++++++++++ .../NUHermiteCubic3D.cs.meta | 11 ++++ 2 files changed, 62 insertions(+) create mode 100644 Runtime/Splines/Non-Uniform Spline Segments/NUHermiteCubic3D.cs create mode 100644 Runtime/Splines/Non-Uniform Spline Segments/NUHermiteCubic3D.cs.meta diff --git a/Runtime/Splines/Non-Uniform Spline Segments/NUHermiteCubic3D.cs b/Runtime/Splines/Non-Uniform Spline Segments/NUHermiteCubic3D.cs new file mode 100644 index 0000000..b90b922 --- /dev/null +++ b/Runtime/Splines/Non-Uniform Spline Segments/NUHermiteCubic3D.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + public class NUHermiteCubic3D : IParamSplineSegment { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + /// + public NUHermiteCubic3D( Vector3Matrix4x1 pointMatrix, (float k0, float k1) knotVector ) { + this.pointMatrix = pointMatrix; + this.KnotVector = knotVector; + validCoefficients = false; + curve = default; + } + + // serialized data + [SerializeField] Vector3Matrix4x1 pointMatrix; + public Vector3Matrix4x1 PointMatrix { + get => pointMatrix; + set => _ = ( pointMatrix = value, validCoefficients = false ); + } + [SerializeField] float k0, k1; + public (float k0, float k1) KnotVector { + get => ( k0, k1 ); + set => _ = ( ( k0, k1 ) = value, validCoefficients = false ); + } + + Polynomial3D curve; + public Polynomial3D Curve { + get { + ReadyCoefficients(); + return curve; + } + } + + // cached data to accelerate calculations + [NonSerialized] bool validCoefficients; // inverted isDirty flag (can't default to true in structs) + + [MethodImpl( INLINE )] void ReadyCoefficients() { + if( validCoefficients ) + return; // no need to update + validCoefficients = true; + curve = SplineUtils.CalculateHermiteCurve( pointMatrix, k0, k1 ); + } + + } + +} \ No newline at end of file diff --git a/Runtime/Splines/Non-Uniform Spline Segments/NUHermiteCubic3D.cs.meta b/Runtime/Splines/Non-Uniform Spline Segments/NUHermiteCubic3D.cs.meta new file mode 100644 index 0000000..59b05f7 --- /dev/null +++ b/Runtime/Splines/Non-Uniform Spline Segments/NUHermiteCubic3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96c8df53b570b63439afac85966a0084 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From d3d9ca82c39fbd0aaef575453a8bd1d8b8bde211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Nov 2022 23:31:00 +0100 Subject: [PATCH 161/301] 2D catmull rom --- .../Multi-Segment Splines/CatRom2DSpline.cs | 364 ++++++++++++++++++ .../CatRom2DSpline.cs.meta | 11 + 2 files changed, 375 insertions(+) create mode 100644 Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs create mode 100644 Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs.meta diff --git a/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs b/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs new file mode 100644 index 0000000..3dc5893 --- /dev/null +++ b/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + [Serializable] + public class CatRom2DSpline { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + + public enum EndpointMode { + None, + Extrapolate, + Collapse + } + + public struct Node { + public Vector2 pos; + public float knot; + public Polynomial2D curve; + + public Vector2 EvalPoint( float u ) { + u -= knot; // transform to local knot value + return curve.Eval( u ); + } + + public Vector2 EvalDerivative( float u ) { + u -= knot; // transform to local knot value + return curve.EvalDerivative( u ); + } + + public Vector2 EvalSecondDerivative( float u ) { + u -= knot; // transform to local knot value + return curve.EvalSecondDerivative( u ); + } + + public Vector2 EvalThirdDerivative() => curve.EvalThirdDerivative(); + } + + public List nodes; + [SerializeField] [Range( 0, 1 )] float alpha; + [SerializeField] bool autoCalculateKnots; + [SerializeField] public EndpointMode endpointMode; + [NonSerialized] bool isDirty; + + #region Properties + + bool IncludeEndpoints { + [MethodImpl( INLINE )] get => endpointMode != EndpointMode.None; + } + + /// + public float Alpha { + [MethodImpl( INLINE )] get => alpha; + [MethodImpl( INLINE )] set { + isDirty = true; + alpha = value; + } + } + + /// Whether or not to calculate knots based on the alpha value + public bool AutoCalculateKnots { + [MethodImpl( INLINE )] get => autoCalculateKnots; + [MethodImpl( INLINE )] set { + isDirty = true; + autoCalculateKnots = value; + } + } + + int IndexSplineStart { + [MethodImpl( INLINE )] get => IncludeEndpoints ? 0 : 1; + } + int IndexSplineEnd { + [MethodImpl( INLINE )] get => ControlPointCount - ( IncludeEndpoints ? 1 : 2 ); + } + + /// The number of control points in this spline + public int ControlPointCount { + [MethodImpl( INLINE )] get => nodes.Count; + } + + /// The number of curves in this spline + public int CurveCount { + [MethodImpl( INLINE )] get => ControlPointCount - ( IncludeEndpoints ? 1 : 3 ); + } + + /// The knot value at the start of the spline + public float KnotStart { + [MethodImpl( INLINE )] get => GetKnot( IndexSplineStart ); + } + + /// The knot value at the end of the spline + public float KnotEnd { + [MethodImpl( INLINE )] get => GetKnot( IndexSplineEnd ); + } + /// The knot range of the spline from start to end + public float KnotRange { + [MethodImpl( INLINE )] get => KnotEnd - KnotStart; + } + + /// The starting point of this spline + public Vector2 StartPoint { + [MethodImpl( INLINE )] get => nodes[IndexSplineStart].pos; + } + + /// The endpoint of this spline + public Vector2 EndPoint { + [MethodImpl( INLINE )] get => nodes[IndexSplineEnd].pos; + } + + #endregion + + #region Constructors + + /// Creates a cubic catmull-rom spline, given a set of control points + /// The control points of the spline + /// The knot values of the spline + /// Whether or not the spline should reach the endpoints, and how + public CatRom2DSpline( IReadOnlyCollection points, IReadOnlyCollection knots, EndpointMode endpointMode = EndpointMode.None ) { + if( points == null || knots == null ) + throw new NullReferenceException( $"{GetType().Name} requires non-null inputs" ); + if( points.Count != knots.Count ) + throw new Exception( $"{GetType().Name} points[{points.Count}] and knots[{knots.Count}] have to have the same count" ); + if( points.Count < 2 ) + throw new Exception( $"{GetType().Name} requires at least 2 points" ); + nodes = points.Zip( knots, ( p, k ) => new Node { pos = p, knot = k } ).ToList(); + isDirty = true; + autoCalculateKnots = false; + this.endpointMode = endpointMode; + } + + /// Creates a cubic catmull-rom spline, given a set of control points + /// The control points of the spline + /// The alpha parameter controls how much the length of each segment should influence the shape of the curve. + /// A value of 0 is called a uniform catrom, and is fast to evaluate but has a tendency to overshoot. + /// A value of 0.5 is a centripetal catrom, which follows points very tightly, and prevents cusps and loops. + /// A value of 1 is a chordal catrom, which follows the points very smoothly with wide arcs + /// Whether or not the spline should reach the endpoints, and how + public CatRom2DSpline( IReadOnlyCollection points, float alpha, EndpointMode endpointMode = EndpointMode.None ) { + if( points == null ) + throw new NullReferenceException( $"{GetType().Name} requires non-null points" ); + if( points.Count < 2 ) + throw new Exception( $"{GetType().Name} requires at least 2 points" ); + nodes = points.Select( p => new Node { pos = p } ).ToList(); + this.alpha = alpha; + this.isDirty = true; + this.autoCalculateKnots = true; + this.endpointMode = endpointMode; + } + + /// Creates a cubic catmull-rom spline, given a set of control points + /// The control points of the spline + /// The type of catrom curve to use. This will internally determine the value of the alpha parameter + /// Whether or not the spline should reach the endpoints, and how + public CatRom2DSpline( IReadOnlyCollection points, CatRomType type, EndpointMode endpointMode = EndpointMode.None ) { + if( points == null ) + throw new NullReferenceException( $"{GetType().Name} requires non-null points" ); + if( points.Count < 2 ) + throw new Exception( $"{GetType().Name} requires at least 2 points" ); + nodes = points.Select( p => new Node { pos = p } ).ToList(); + this.alpha = type.AlphaValue(); + this.isDirty = true; + this.autoCalculateKnots = true; + this.endpointMode = endpointMode; + } + + #endregion + + #region Points & Derivatives + + /// Returns the point at parameter value u + /// The parameter space position to sample the point at + [MethodImpl( INLINE )] public Vector2 GetPoint( float u ) { + u = ReadyAndClampU( u ); + return GetPointInternal( GetIntervalIndexForKnotValue( u ), u ); + } + + /// Returns the derivative with respect to u at the input parameter value + /// The parameter space position to sample the derivative at + [MethodImpl( INLINE )] public Vector2 GetDerivative( float u ) { + u = ReadyAndClampU( u ); + return GetDerivativeInternal( GetIntervalIndexForKnotValue( u ), u ); + } + + /// Returns the second derivative with respect to u at the input parameter value + /// The parameter space position to sample the second derivative at + [MethodImpl( INLINE )] public Vector2 GetSecondDerivative( float u ) { + u = ReadyAndClampU( u ); + return GetSecondDerivativeInternal( GetIntervalIndexForKnotValue( u ), u ); + } + + /// Returns the third derivative with respect to u at the input parameter value + /// The parameter space position to sample the third derivative at + [MethodImpl( INLINE )] public Vector2 GetThirdDerivative( float u ) { + return GetThirdDerivativeInternal( GetIntervalIndexForKnotValue( ReadyAndClampU( u ) ) ); + } + + [MethodImpl( INLINE )] float ReadyAndClampU( float u ) { + ReadyKnotsAndCoefficients(); + return ClampToKnotRange( u ); + } + + #endregion + + #region Recalculations + + /// Ensures the knot vector is ready (if autoCalculateKnots is on) and the coefficients are up to date + public void ReadyKnotsAndCoefficients() { + if( isDirty ) { + isDirty = false; + if( autoCalculateKnots ) + RecalculateKnots(); + for( int i = 0; i < ControlPointCount - 1; i++ ) { + Node n = nodes[i]; + // knot parameters are local to each spline's main (second) knot + Vector2Matrix4x1 pts = new(GetControlPoint( i - 1 ), n.pos, GetControlPoint( i + 1 ), GetControlPoint( i + 2 )); + Matrix4x1 knots = new(GetKnot( i - 1 ) - n.knot, 0, GetKnot( i + 1 ) - n.knot, GetKnot( i + 2 ) - n.knot); + n.curve = SplineUtils.CalculateCatRomCurve( pts, knots ); + nodes[i] = n; + } + } + } + + /// Recalculates the knot vector based on alpha and the point distances + public void RecalculateKnots() { + if( alpha == 0 ) { // uniform catrom + for( int i = 0; i < ControlPointCount; i++ ) + SetKnotInternal( i, i ); + } else { // non-uniform + SetKnotInternal( 0, 0 ); // first knot is 0 + // todo: it's possible to cache and optimize these distance checks + // todo: by caching distances and only recalculating the necessary ones + for( int i = 1; i < ControlPointCount; i++ ) { + float sqDist = Vector2.SqrMagnitude( nodes[i - 1].pos - nodes[i].pos ); + SetKnotInternal( i, SplineUtils.CalcCatRomKnot( nodes[i - 1].knot, sqDist, alpha ) ); + } + } + } + + #endregion + + #region Point/Knot getter/setters + + /// Get the position of a control point by index + /// The index of the point + public Vector2 GetControlPoint( int index ) { + if( endpointMode == EndpointMode.Collapse ) + index = index.Clamp( 0, ControlPointCount - 1 ); + + if( index == -1 ) // extrapolate at the ends + return Vector2.LerpUnclamped( nodes[1].pos, nodes[0].pos, 2 ); + if( index == ControlPointCount ) + return Vector2.LerpUnclamped( nodes[ControlPointCount - 2].pos, nodes[ControlPointCount - 1].pos, 2 ); + + return nodes[index].pos; + } + + /// Set the position of a control point by index + /// The index of the knot + /// The position to assign to the control point + public void SetControlPoint( int index, Vector2 position ) { + isDirty = true; + Node n = nodes[index]; + n.pos = position; + nodes[index] = n; + } + + /// Get the value of knot by index + /// The index of the knot + public float GetKnot( int index ) { + ReadyKnotsAndCoefficients(); + if( index == -1 ) // extrapolate at the ends + return Mathfs.Lerp( nodes[1].knot, nodes[0].knot, 2 ); + if( index == ControlPointCount ) + return Mathfs.Lerp( nodes[ControlPointCount - 2].knot, nodes[ControlPointCount - 1].knot, 2 ); + return nodes[index].knot; + } + + /// Get the knot value at a given t-value along the whole spline + /// The percentage along the spline from 0 to 1 + public float GetKnotValue( float t ) => Mathfs.LerpClamped( KnotStart, KnotEnd, t ); + + /// Sets the given knot to a specific value + /// The index of the knot to edit + /// The value to assign to the knot + [MethodImpl( INLINE )] public void SetKnot( int index, float value ) { + isDirty = true; + SetKnotInternal( index, value ); + } + + [MethodImpl( INLINE )] void SetKnotInternal( int index, float value ) { + Node n = nodes[index]; + n.knot = value; + nodes[index] = n; + } + + #endregion + + #region Get point/derivative by curve index + + /// Returns a point along a curve by index + /// The index of the curve to sample + /// The fraction along this segment from 0 to 1 + [MethodImpl( INLINE )] public Vector2 GetPoint( int curve, float t ) => GetPointInternal( curve, RangeCheckAndGetU( curve, t ) ); + + /// Returns the derivative with respect to u along a curve by index + /// The index of the curve to sample + /// The fraction along this segment from 0 to 1 + [MethodImpl( INLINE )] public Vector2 GetDerivative( int curve, float t ) => GetDerivativeInternal( curve, RangeCheckAndGetU( curve, t ) ); + + /// Returns the second derivative with respect to u along a curve by index + /// The index of the curve to sample + /// The fraction along this segment from 0 to 1 + [MethodImpl( INLINE )] public Vector2 GetSecondDerivative( int curve, float t ) => GetSecondDerivativeInternal( curve, RangeCheckAndGetU( curve, t ) ); + + /// Returns the third derivative with respect to u of a curve by index + /// The index of the curve to sample + [MethodImpl( INLINE )] public Vector2 GetThirdDerivative( int curve ) => GetThirdDerivativeInternal( curve ); + + float RangeCheckAndGetU( int curve, float t ) { + if( curve < 0 || curve >= CurveCount ) + throw new IndexOutOfRangeException( $"Curve index {curve} is out of the range 0 to {CurveCount - 1}" ); + ReadyKnotsAndCoefficients(); + return Mathfs.Lerp( nodes[curve].knot, nodes[curve + 1].knot, t ); + } + + [MethodImpl( INLINE )] Vector2 GetPointInternal( int curve, float u ) => nodes[curve].EvalPoint( u ); + [MethodImpl( INLINE )] Vector2 GetDerivativeInternal( int curve, float u ) => nodes[curve].EvalDerivative( u ); + [MethodImpl( INLINE )] Vector2 GetSecondDerivativeInternal( int curve, float u ) => nodes[curve].EvalSecondDerivative( u ); + [MethodImpl( INLINE )] Vector2 GetThirdDerivativeInternal( int curve ) => nodes[curve].EvalThirdDerivative(); + + #endregion + + #region Knot Utilities + + /// Clamps the input value u to the range of this spline + /// The parameter space value to clamp + public float ClampToKnotRange( float u ) => u.Clamp( KnotStart, KnotEnd ); + + /// Returns the index of the curve containing knot value u + /// The knot value to get the curve of + public int GetIntervalIndexForKnotValue( float u ) { + ReadyKnotsAndCoefficients(); + if( u <= KnotStart ) + return 0; + if( u >= GetKnot( ControlPointCount - 2 ) ) + return ControlPointCount - 2; // very last knot is never used as an interval (fencepost issue |-|-|) + // todo: linear search, but, might want to use binary search if more than ~30 nodes + for( int i = 0; i < ControlPointCount - 1; i++ ) { + if( u < GetKnot( i + 1 ) ) + return i; + } + + throw new Exception( $"Failed to get spline interval for knot value {u} in the range {KnotStart} to {KnotEnd}" ); + } + + #endregion + + } + +} \ No newline at end of file diff --git a/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs.meta b/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs.meta new file mode 100644 index 0000000..2db78cd --- /dev/null +++ b/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db0f72c5f03f3e74fb2ad9649a885e3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From b79257ad8d2d459a9945e868caa72ba31c5c4874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 17 Nov 2022 21:51:53 +0100 Subject: [PATCH 162/301] Add IntRange.cs --- Runtime/Numerics/IntRange.cs | 10 ++++++++++ Runtime/Numerics/IntRange.cs.meta | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100644 Runtime/Numerics/IntRange.cs create mode 100644 Runtime/Numerics/IntRange.cs.meta diff --git a/Runtime/Numerics/IntRange.cs b/Runtime/Numerics/IntRange.cs new file mode 100644 index 0000000..fae6830 --- /dev/null +++ b/Runtime/Numerics/IntRange.cs @@ -0,0 +1,10 @@ +public readonly struct IntRange { + public readonly int start; + public readonly int count; + + public IntRange( int start, int count ) { + this.start = start; + this.count = count; + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IntRange.cs.meta b/Runtime/Numerics/IntRange.cs.meta new file mode 100644 index 0000000..844ddc0 --- /dev/null +++ b/Runtime/Numerics/IntRange.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f48d1234c10c4a84cb575073effd06be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From d51cd821a689dd521a46730d932b9756ad16907b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 25 Nov 2022 15:56:24 +0100 Subject: [PATCH 163/301] added quaternion swizzling --- Runtime/Extensions.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 53f8758..69dd422 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -156,6 +156,25 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { #region Quaternions + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundWorldX( this Quaternion q ) => new(q.w, -q.z, q.y, -q.x); + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundWorldY( this Quaternion q ) => new(q.z, q.w, -q.x, -q.y); + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundWorldZ( this Quaternion q ) => new(-q.y, q.x, q.w, -q.z); + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundSelfX( this Quaternion q ) => new(q.w, q.z, -q.y, -q.x); + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundSelfY( this Quaternion q ) => new(-q.z, q.w, q.x, -q.y); + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundSelfZ( this Quaternion q ) => new(q.y, -q.x, q.w, -q.z); + + /// Returns an 180° rotated version of this quaternion around the given axis + /// The quaternion to rotate + /// The axis to rotate around + /// The space of the axis + public static Quaternion Rotate180Around( this Quaternion q, int axis, Space space ) { + return axis switch { + 0 => space == Space.Self ? Rotate180AroundSelfX( q ) : Rotate180AroundWorldX( q ), + 1 => space == Space.Self ? Rotate180AroundSelfY( q ) : Rotate180AroundWorldY( q ), + 2 => space == Space.Self ? Rotate180AroundSelfZ( q ) : Rotate180AroundWorldZ( q ), + _ => throw new ArgumentOutOfRangeException( nameof(axis), $"Invalid axis: {axis}. Expected 0, 1 or 2" ) + }; + } /// Returns the natural logarithm of a quaternion public static Quaternion Log( this Quaternion q ) { double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; From 446593cea11f7a25b8e73b8398d77f665039bcee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 25 Nov 2022 15:58:07 +0100 Subject: [PATCH 164/301] formatting --- Runtime/Extensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 69dd422..31e152d 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -175,6 +175,7 @@ public static Quaternion Rotate180Around( this Quaternion q, int axis, Space spa _ => throw new ArgumentOutOfRangeException( nameof(axis), $"Invalid axis: {axis}. Expected 0, 1 or 2" ) }; } + /// Returns the natural logarithm of a quaternion public static Quaternion Log( this Quaternion q ) { double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; From 47e4c55acd6bce6694004520f0692b172d2490b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 26 Nov 2022 00:24:12 +0100 Subject: [PATCH 165/301] quaternion axis rotation w. custom angle --- Runtime/Enums.meta | 8 ++++ Runtime/Enums/Axis.cs | 11 +++++ Runtime/Enums/Axis.cs.meta | 11 +++++ Runtime/Enums/RotationSpace.cs | 12 ++++++ Runtime/Enums/RotationSpace.cs.meta | 11 +++++ Runtime/Extensions.cs | 64 +++++++++++++++++++++++++---- 6 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 Runtime/Enums.meta create mode 100644 Runtime/Enums/Axis.cs create mode 100644 Runtime/Enums/Axis.cs.meta create mode 100644 Runtime/Enums/RotationSpace.cs create mode 100644 Runtime/Enums/RotationSpace.cs.meta diff --git a/Runtime/Enums.meta b/Runtime/Enums.meta new file mode 100644 index 0000000..7cd31b1 --- /dev/null +++ b/Runtime/Enums.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f78df51a080d0a0469ef09acab10afce +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Enums/Axis.cs b/Runtime/Enums/Axis.cs new file mode 100644 index 0000000..78275cb --- /dev/null +++ b/Runtime/Enums/Axis.cs @@ -0,0 +1,11 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +namespace Freya { + + public enum Axis { + X = 0, + Y = 1, + Z = 2 + } + +} \ No newline at end of file diff --git a/Runtime/Enums/Axis.cs.meta b/Runtime/Enums/Axis.cs.meta new file mode 100644 index 0000000..8e1dfaa --- /dev/null +++ b/Runtime/Enums/Axis.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c486966d4fb6a6f46b350341c26bb8a0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Enums/RotationSpace.cs b/Runtime/Enums/RotationSpace.cs new file mode 100644 index 0000000..a3370db --- /dev/null +++ b/Runtime/Enums/RotationSpace.cs @@ -0,0 +1,12 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +namespace Freya { + + public enum RotationSpace { + /// An intrinsic rotation around its own local axes, usually called "local" or "self" space. Equivalent to q*rotation + Self, + /// Rotation around its pre-rotation axes, usually "world" space. Equivalent to rotation*q + Extrinsic + } + +} \ No newline at end of file diff --git a/Runtime/Enums/RotationSpace.cs.meta b/Runtime/Enums/RotationSpace.cs.meta new file mode 100644 index 0000000..8177792 --- /dev/null +++ b/Runtime/Enums/RotationSpace.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9ea6b7f7cd84d743ad4651c7f27967a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 31e152d..250d07d 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -156,23 +156,69 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { #region Quaternions - [MethodImpl( INLINE )] public static Quaternion Rotate180AroundWorldX( this Quaternion q ) => new(q.w, -q.z, q.y, -q.x); - [MethodImpl( INLINE )] public static Quaternion Rotate180AroundWorldY( this Quaternion q ) => new(q.z, q.w, -q.x, -q.y); - [MethodImpl( INLINE )] public static Quaternion Rotate180AroundWorldZ( this Quaternion q ) => new(-q.y, q.x, q.w, -q.z); + /// Rotates 180° around the extrinsic pre-rotation X axis, sometimes this is interpreted as a world space rotation, as opposed to rotating around its own axes + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundExtrX( this Quaternion q ) => new(q.w, -q.z, q.y, -q.x); + + /// Rotates 180° around the extrinsic pre-rotation Y axis, sometimes this is interpreted as a world space rotation, as opposed to rotating around its own axes + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundExtrY( this Quaternion q ) => new(q.z, q.w, -q.x, -q.y); + + /// Rotates 180° around the extrinsic pre-rotation Z axis, sometimes this is interpreted as a world space rotation, as opposed to rotating around its own axes + [MethodImpl( INLINE )] public static Quaternion Rotate180AroundExtrZ( this Quaternion q ) => new(-q.y, q.x, q.w, -q.z); + + /// Rotates 180° around its local X axis [MethodImpl( INLINE )] public static Quaternion Rotate180AroundSelfX( this Quaternion q ) => new(q.w, q.z, -q.y, -q.x); + + /// Rotates 180° around its local Y axis [MethodImpl( INLINE )] public static Quaternion Rotate180AroundSelfY( this Quaternion q ) => new(-q.z, q.w, q.x, -q.y); + + /// Rotates 180° around its local Z axis [MethodImpl( INLINE )] public static Quaternion Rotate180AroundSelfZ( this Quaternion q ) => new(q.y, -q.x, q.w, -q.z); /// Returns an 180° rotated version of this quaternion around the given axis /// The quaternion to rotate /// The axis to rotate around - /// The space of the axis - public static Quaternion Rotate180Around( this Quaternion q, int axis, Space space ) { + /// The rotation space of the axis, if it should be intrinsic/self/local or extrinsic/"world" + public static Quaternion Rotate180Around( this Quaternion q, Axis axis, RotationSpace space = RotationSpace.Self ) { return axis switch { - 0 => space == Space.Self ? Rotate180AroundSelfX( q ) : Rotate180AroundWorldX( q ), - 1 => space == Space.Self ? Rotate180AroundSelfY( q ) : Rotate180AroundWorldY( q ), - 2 => space == Space.Self ? Rotate180AroundSelfZ( q ) : Rotate180AroundWorldZ( q ), - _ => throw new ArgumentOutOfRangeException( nameof(axis), $"Invalid axis: {axis}. Expected 0, 1 or 2" ) + Axis.X => space == RotationSpace.Self ? Rotate180AroundSelfX( q ) : Rotate180AroundExtrX( q ), + Axis.Y => space == RotationSpace.Self ? Rotate180AroundSelfY( q ) : Rotate180AroundExtrY( q ), + Axis.Z => space == RotationSpace.Self ? Rotate180AroundSelfZ( q ) : Rotate180AroundExtrZ( q ), + _ => throw new ArgumentOutOfRangeException( nameof(axis), $"Invalid axis: {axis}. Expected 0, 1 or 2" ) + }; + } + + /// Returns the quaternion rotated around the given axis by the given angle in radians + /// The quaternion to rotate + /// The axis to rotate around + /// The angle to rotate by (in radians) + /// The rotation space of the axis, if it should be intrinsic/self/local or extrinsic/"world" + public static Quaternion RotateAround( this Quaternion q, Axis axis, float angRad, RotationSpace space = RotationSpace.Self ) { + float aHalf = angRad / 2; + float c = Mathf.Cos( aHalf ); + float s = Mathf.Sin( aHalf ); + float xc = q.x * c; + float yc = q.y * c; + float zc = q.z * c; + float wc = q.w * c; + float xs = q.x * s; + float ys = q.y * s; + float zs = q.z * s; + float ws = q.w * s; + + return space switch { + RotationSpace.Self => axis switch { + Axis.X => new Quaternion( xc + ws, yc + zs, zc - ys, wc - xs ), + Axis.Y => new Quaternion( xc - zs, yc + ws, zc + xs, wc - ys ), + Axis.Z => new Quaternion( xc + ys, yc - xs, zc + ws, wc - zs ), + _ => throw new ArgumentOutOfRangeException( nameof(axis) ) + }, + RotationSpace.Extrinsic => axis switch { + Axis.X => new Quaternion( xc + ws, yc - zs, zc + ys, wc - xs ), + Axis.Y => new Quaternion( xc + zs, yc + ws, zc - xs, wc - ys ), + Axis.Z => new Quaternion( xc - ys, yc + xs, zc + ws, wc - zs ), + _ => throw new ArgumentOutOfRangeException( nameof(axis) ) + }, + _ => throw new ArgumentOutOfRangeException( nameof(space) ) }; } From 700e27cd9454143f3024fccacf1d47a02f1540d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 26 Nov 2022 16:26:49 +0100 Subject: [PATCH 166/301] added quaternion 90 degree rotation helpers --- Runtime/Extensions.cs | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 250d07d..ba1d56e 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -222,6 +222,62 @@ public static Quaternion RotateAround( this Quaternion q, Axis axis, float angRa }; } + /// Returns the quaternion rotated around the given axis by 90° + /// The quaternion to rotate + /// The axis to rotate around + /// The rotation space of the axis, if it should be intrinsic/self/local or extrinsic/"world" + public static Quaternion Rotate90Around( this Quaternion q, Axis axis, RotationSpace space = RotationSpace.Self ) { + const float v = Mathfs.SQRT2; // 2*cos(90°/2) = 2*sin(90°/2) + float x = q.x; + float y = q.y; + float z = q.z; + float w = q.w; + + return space switch { + RotationSpace.Self => axis switch { + Axis.X => new Quaternion( v * ( x + w ), v * ( y + z ), v * ( z - y ), v * ( w - x ) ), + Axis.Y => new Quaternion( v * ( x - z ), v * ( y + w ), v * ( z + x ), v * ( w - y ) ), + Axis.Z => new Quaternion( v * ( x + y ), v * ( y - x ), v * ( z + w ), v * ( w - z ) ), + _ => throw new ArgumentOutOfRangeException( nameof(axis) ) + }, + RotationSpace.Extrinsic => axis switch { + Axis.X => new Quaternion( v * ( x + w ), v * ( y - z ), v * ( z + y ), v * ( w - x ) ), + Axis.Y => new Quaternion( v * ( x + z ), v * ( y + w ), v * ( z - x ), v * ( w - y ) ), + Axis.Z => new Quaternion( v * ( x - y ), v * ( y + x ), v * ( z + w ), v * ( w - z ) ), + _ => throw new ArgumentOutOfRangeException( nameof(axis) ) + }, + _ => throw new ArgumentOutOfRangeException( nameof(space) ) + }; + } + + /// Returns the quaternion rotated around the given axis by -90° + /// The quaternion to rotate + /// The axis to rotate around + /// The rotation space of the axis, if it should be intrinsic/self/local or extrinsic/"world" + public static Quaternion RotateNeg90Around( this Quaternion q, Axis axis, RotationSpace space = RotationSpace.Self ) { + const float v = Mathfs.SQRT2; // 2*cos(90°/2) = 2*sin(90°/2) + float x = q.x; + float y = q.y; + float z = q.z; + float w = q.w; + + return space switch { + RotationSpace.Self => axis switch { + Axis.X => new Quaternion( v * ( x - w ), v * ( y - z ), v * ( z + y ), v * ( w + x ) ), + Axis.Y => new Quaternion( v * ( x + z ), v * ( y - w ), v * ( z - x ), v * ( w + y ) ), + Axis.Z => new Quaternion( v * ( x - y ), v * ( y + x ), v * ( z - w ), v * ( w + z ) ), + _ => throw new ArgumentOutOfRangeException( nameof(axis) ) + }, + RotationSpace.Extrinsic => axis switch { + Axis.X => new Quaternion( v * ( x - w ), v * ( y + z ), v * ( z - y ), v * ( w + x ) ), + Axis.Y => new Quaternion( v * ( x - z ), v * ( y - w ), v * ( z + x ), v * ( w + y ) ), + Axis.Z => new Quaternion( v * ( x + y ), v * ( y - x ), v * ( z - w ), v * ( w + z ) ), + _ => throw new ArgumentOutOfRangeException( nameof(axis) ) + }, + _ => throw new ArgumentOutOfRangeException( nameof(space) ) + }; + } + /// Returns the natural logarithm of a quaternion public static Quaternion Log( this Quaternion q ) { double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; From 0ecb4b8336381dccf360530a4fefec12f3235097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 26 Nov 2022 16:55:14 +0100 Subject: [PATCH 167/301] fixed 90 degree quat rotation normalization --- Runtime/Extensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index ba1d56e..2e383b7 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -227,7 +227,7 @@ public static Quaternion RotateAround( this Quaternion q, Axis axis, float angRa /// The axis to rotate around /// The rotation space of the axis, if it should be intrinsic/self/local or extrinsic/"world" public static Quaternion Rotate90Around( this Quaternion q, Axis axis, RotationSpace space = RotationSpace.Self ) { - const float v = Mathfs.SQRT2; // 2*cos(90°/2) = 2*sin(90°/2) + const float v = Mathfs.RSQRT2; // cos(90°/2) = sin(90°/2) float x = q.x; float y = q.y; float z = q.z; @@ -255,7 +255,7 @@ public static Quaternion Rotate90Around( this Quaternion q, Axis axis, RotationS /// The axis to rotate around /// The rotation space of the axis, if it should be intrinsic/self/local or extrinsic/"world" public static Quaternion RotateNeg90Around( this Quaternion q, Axis axis, RotationSpace space = RotationSpace.Self ) { - const float v = Mathfs.SQRT2; // 2*cos(90°/2) = 2*sin(90°/2) + const float v = Mathfs.RSQRT2; // cos(90°/2) = sin(90°/2) float x = q.x; float y = q.y; float z = q.z; From 8057bbc826234e4d9312269ade47d50940c400b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 27 Nov 2022 20:48:17 +0100 Subject: [PATCH 168/301] added Quaternion.Right/Up/Forward/ToMatrix --- Runtime/Extensions.cs | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 2e383b7..3a6f358 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -278,6 +278,53 @@ public static Quaternion RotateNeg90Around( this Quaternion q, Axis axis, Rotati }; } + /// Returns the given axis of this rotation (assumes this quaternion is normalized) + public static Vector3 GetAxis( this Quaternion q, Axis axis ) { + return axis switch { + Axis.X => q.Right(), + Axis.Y => q.Up(), + Axis.Z => q.Forward(), + _ => throw new ArgumentOutOfRangeException( nameof(axis) ) + }; + } + + /// Returns the X axis of this rotation (assumes this quaternion is normalized) + public static Vector3 Right( this Quaternion q ) => new(q.x * q.x - q.y * q.y - q.z * q.z + q.w * q.w, 2 * ( q.x * q.y + q.z * q.w ), 2 * ( q.x * q.z - q.y * q.w )); + + /// Returns the Y axis of this rotation (assumes this quaternion is normalized) + public static Vector3 Up( this Quaternion q ) => new(2 * ( q.x * q.y - q.z * q.w ), -q.x * q.x + q.y * q.y - q.z * q.z + q.w * q.w, 2 * ( q.x * q.w + q.y * q.z )); + + /// Returns the Z axis of this rotation (assumes this quaternion is normalized) + public static Vector3 Forward( this Quaternion q ) => new(2 * ( q.x * q.z + q.y * q.w ), 2 * ( q.y * q.z - q.x * q.w ), -q.x * q.x - q.y * q.y + q.z * q.z + q.w * q.w); + + /// Converts this quaternion to a rotation matrix + public static Matrix4x4 ToMatrix( this Quaternion q ) { + // you could just use Matrix4x4.Rotate( q ) but that's not as fun as doing this math myself + float xx = q.x * q.x; + float yy = q.y * q.y; + float zz = q.z * q.z; + float ww = q.w * q.w; + float xy = q.x * q.y; + float yz = q.y * q.z; + float zw = q.z * q.w; + float wx = q.w * q.x; + float xz = q.x * q.z; + float yw = q.y * q.w; + + return new Matrix4x4 { + m00 = xx - yy - zz + ww, // X + m10 = 2 * ( xy + zw ), + m20 = 2 * ( xz - yw ), + m01 = 2 * ( xy - zw ), // Y + m11 = -xx + yy - zz + ww, + m21 = 2 * ( wx + yz ), + m02 = 2 * ( xz + yw ), // Z + m12 = 2 * ( yz - wx ), + m22 = -xx - yy + zz + ww, + m33 = 1 + }; + } + /// Returns the natural logarithm of a quaternion public static Quaternion Log( this Quaternion q ) { double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; From 2255749502632f7cdd15f48c54ac273a53480e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 27 Nov 2022 20:50:34 +0100 Subject: [PATCH 169/301] quat axis inline hints --- Runtime/Extensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 3a6f358..a77c1f3 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -289,13 +289,13 @@ public static Vector3 GetAxis( this Quaternion q, Axis axis ) { } /// Returns the X axis of this rotation (assumes this quaternion is normalized) - public static Vector3 Right( this Quaternion q ) => new(q.x * q.x - q.y * q.y - q.z * q.z + q.w * q.w, 2 * ( q.x * q.y + q.z * q.w ), 2 * ( q.x * q.z - q.y * q.w )); + [MethodImpl( INLINE )] public static Vector3 Right( this Quaternion q ) => new(q.x * q.x - q.y * q.y - q.z * q.z + q.w * q.w, 2 * ( q.x * q.y + q.z * q.w ), 2 * ( q.x * q.z - q.y * q.w )); /// Returns the Y axis of this rotation (assumes this quaternion is normalized) - public static Vector3 Up( this Quaternion q ) => new(2 * ( q.x * q.y - q.z * q.w ), -q.x * q.x + q.y * q.y - q.z * q.z + q.w * q.w, 2 * ( q.x * q.w + q.y * q.z )); + [MethodImpl( INLINE )] public static Vector3 Up( this Quaternion q ) => new(2 * ( q.x * q.y - q.z * q.w ), -q.x * q.x + q.y * q.y - q.z * q.z + q.w * q.w, 2 * ( q.x * q.w + q.y * q.z )); /// Returns the Z axis of this rotation (assumes this quaternion is normalized) - public static Vector3 Forward( this Quaternion q ) => new(2 * ( q.x * q.z + q.y * q.w ), 2 * ( q.y * q.z - q.x * q.w ), -q.x * q.x - q.y * q.y + q.z * q.z + q.w * q.w); + [MethodImpl( INLINE )] public static Vector3 Forward( this Quaternion q ) => new(2 * ( q.x * q.z + q.y * q.w ), 2 * ( q.y * q.z - q.x * q.w ), -q.x * q.x - q.y * q.y + q.z * q.z + q.w * q.w); /// Converts this quaternion to a rotation matrix public static Matrix4x4 ToMatrix( this Quaternion q ) { From 1998a9ea8e551d363ac1e1a5710f37b78804f674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 14 Dec 2022 16:37:04 +0100 Subject: [PATCH 170/301] FloatRange & IntRange updates --- Runtime/Numerics/FloatRange.cs | 19 ++++++++++-- Runtime/Numerics/IntRange.cs | 53 ++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 49e1d69..731d561 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -6,16 +6,17 @@ namespace Freya { /// A value range between two values a and b - public readonly struct FloatRange { + [Serializable] + public struct FloatRange { /// The unit interval of 0 to 1 public static readonly FloatRange unit = new FloatRange( 0, 1 ); /// The start of this range - public readonly float a; + public float a; /// The end of this range - public readonly float b; + public float b; /// Creates a new value range /// The start of the range @@ -89,6 +90,14 @@ public FloatRange Encapsulate( float value ) => _ => ( Mathfs.Min( b, value ), Mathfs.Max( a, value ) ) // reversed - b is min, a is max }; + /// Expands the minimum or maximum value to contain the given range + /// The value range to include + public FloatRange Encapsulate( FloatRange range ) => + Direction switch { + 1 => ( Mathfs.Min( a, range.a ), Mathfs.Max( b, range.b ) ), // forward - a is min, b is max + _ => ( Mathfs.Min( b, range.b ), Mathfs.Max( a, range.a ) ) // reversed - b is min, a is max + }; + /// Returns a version of this range, scaled around its start value /// The value to scale the range by public FloatRange ScaleFromStart( float scale ) => new FloatRange( a, a + scale * ( b - a ) ); @@ -117,6 +126,10 @@ public static Bounds ToBounds( FloatRange rangeX, FloatRange rangeY, FloatRange public static FloatRange operator -( FloatRange range, float v ) => new(range.a - v, range.b - v); public static FloatRange operator +( FloatRange range, float v ) => new(range.a + v, range.b + v); + public static FloatRange operator /( FloatRange range, int v ) => new(range.a / v, range.b / v); + public static FloatRange operator /( FloatRange range, float v ) => new(range.a / v, range.b / v); + public static FloatRange operator *( FloatRange range, int v ) => new(range.a * v, range.b * v); + public static FloatRange operator *( FloatRange range, float v ) => new(range.a * v, range.b * v); public static implicit operator FloatRange( (float a, float b) tuple ) => new FloatRange( tuple.a, tuple.b ); public static bool operator ==( FloatRange a, FloatRange b ) => a.a == b.a && a.b == b.b; diff --git a/Runtime/Numerics/IntRange.cs b/Runtime/Numerics/IntRange.cs index fae6830..a20b11c 100644 --- a/Runtime/Numerics/IntRange.cs +++ b/Runtime/Numerics/IntRange.cs @@ -1,10 +1,51 @@ -public readonly struct IntRange { - public readonly int start; - public readonly int count; +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System.Text; + +namespace Freya { + + /// An integer range + public readonly struct IntRange { + public readonly int start; + public readonly int count; + + /// The last integer in the range + public int End => start + count - 1; + + /// The distance from first to last integer. Equivalent to count-1 + public int Distance => count - 1; + + /// Creates a new integer range, given a start integer and how many more integers to include + /// The first integer + /// How many integers to include in the range + public IntRange( int start, int count ) { + this.start = start; + this.count = count; + } + + /// Whether or not this range contains a given value (inclusive) + /// The value to check if it's inside, or equal to the start or end + public bool Contains( int value ) => value >= start && value <= End; + + /// Create an integer range from start to end (inclusive) + /// The first integer + /// The last integer (inclusive) + public static IntRange StartEnd( int start, int end ) => new IntRange( start, end - start + 1 ); + + static readonly StringBuilder toStrBuilder = new StringBuilder(); + public override string ToString() { + toStrBuilder.Clear(); + toStrBuilder.Append( "{ " ); + int last = End; + for( int i = start; i <= last; i++ ) { + toStrBuilder.Append( i ); + if( i != last ) + toStrBuilder.Append( ", " ); + } + toStrBuilder.Append( " }" ); + return toStrBuilder.ToString(); + } - public IntRange( int start, int count ) { - this.start = start; - this.count = count; } } \ No newline at end of file From a30c740404b9ab0d6ed7eb70a069b2c1d69e8e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 14 Dec 2022 18:16:56 +0100 Subject: [PATCH 171/301] Chebyshev & Taxicab distance norms --- Runtime/Extensions.cs | 12 ++++++++++++ Runtime/Mathfs.cs | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index a77c1f3..08342f6 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -91,6 +91,18 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { #region Vector directions & magnitudes + /// Returns the chebyshev magnitude of this vector + [MethodImpl( INLINE )] public static float ChebyshevMagnitude( this Vector3 v ) => Mathfs.Max( Abs( v.x ), Abs( v.y ), Abs( v.z ) ); + + /// Returns the taxicab/rectilinear magnitude of this vector + [MethodImpl( INLINE )] public static float TaxicabMagnitude( this Vector3 v ) => Abs( v.x ) + Abs( v.y ) + Abs( v.z ); + + /// + [MethodImpl( INLINE )] public static float ChebyshevMagnitude( this Vector2 v ) => Mathfs.Max( Abs( v.x ), Abs( v.y ) ); + + /// + [MethodImpl( INLINE )] public static float TaxicabMagnitude( this Vector2 v ) => Abs( v.x ) + Abs( v.y ); + /// Returns a vector with the same direction, but with the given magnitude. /// Equivalent to v.normalized*mag [MethodImpl( INLINE )] public static Vector2 WithMagnitude( this Vector2 v, float mag ) => v.normalized * mag; diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 058fa0c..fa3161a 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1037,6 +1037,18 @@ public static Vector3 ClampMagnitude( Vector3 v, float min, float max ) { return mag < min ? ( v / mag ) * min : mag > max ? ( v / mag ) * max : v; } + /// Returns the chebyshev distance between the two vectors + [MethodImpl( INLINE )] public static float ChebyshevDistance( Vector3 a, Vector3 b ) => Max( Abs( a.x - b.x ), Abs( a.y - b.y ), Abs( a.z - b.z ) ); + + /// Returns the taxicab/rectilinear distance between the two vectors + [MethodImpl( INLINE )] public static float TaxicabDistance( Vector3 a, Vector3 b ) => Abs( a.x - b.x ) + Abs( a.y - b.y ) + Abs( a.z - b.z ); + + /// + [MethodImpl( INLINE )] public static float ChebyshevDistance( Vector2 a, Vector2 b ) => Max( Abs( a.x - b.x ), Abs( a.y - b.y ) ); + + /// + [MethodImpl( INLINE )] public static float TaxicabDistance( Vector2 a, Vector2 b ) => Abs( a.x - b.x ) + Abs( a.y - b.y ); + /// Returns the average/center of the two input vectors [MethodImpl( INLINE )] public static Vector2 Average( Vector2 a, Vector2 b ) => ( a + b ) / 2f; From ec71c4e4a13bd1d903acb17f3256188f222e60dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 14 Dec 2022 18:19:31 +0100 Subject: [PATCH 172/301] formatting, docs, cleanup stuff --- Runtime/Extensions.cs | 25 +++++++++++++++---- Runtime/Mathfs.cs | 10 +++----- .../Multi-Segment Splines/BSpline2D.cs | 6 ++--- .../Splines/Multi-Segment Splines/NURBS2D.cs | 2 +- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 08342f6..c94ce46 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -96,7 +96,7 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// Returns the taxicab/rectilinear magnitude of this vector [MethodImpl( INLINE )] public static float TaxicabMagnitude( this Vector3 v ) => Abs( v.x ) + Abs( v.y ) + Abs( v.z ); - + /// [MethodImpl( INLINE )] public static float ChebyshevMagnitude( this Vector2 v ) => Mathfs.Max( Abs( v.x ), Abs( v.y ) ); @@ -165,6 +165,7 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// [MethodImpl( INLINE )] public static Vector3 ScaleAround( this Vector3 p, Vector3 pivot, Vector3 scale ) => new(pivot.x + ( p.x - pivot.x ) * scale.x, pivot.y + ( p.y - pivot.y ) * scale.y, pivot.z + ( p.z - pivot.z ) * scale.z); + #endregion #region Quaternions @@ -301,13 +302,28 @@ public static Vector3 GetAxis( this Quaternion q, Axis axis ) { } /// Returns the X axis of this rotation (assumes this quaternion is normalized) - [MethodImpl( INLINE )] public static Vector3 Right( this Quaternion q ) => new(q.x * q.x - q.y * q.y - q.z * q.z + q.w * q.w, 2 * ( q.x * q.y + q.z * q.w ), 2 * ( q.x * q.z - q.y * q.w )); + [MethodImpl( INLINE )] public static Vector3 Right( this Quaternion q ) => + new( + q.x * q.x - q.y * q.y - q.z * q.z + q.w * q.w, + 2 * ( q.x * q.y + q.z * q.w ), + 2 * ( q.x * q.z - q.y * q.w ) + ); /// Returns the Y axis of this rotation (assumes this quaternion is normalized) - [MethodImpl( INLINE )] public static Vector3 Up( this Quaternion q ) => new(2 * ( q.x * q.y - q.z * q.w ), -q.x * q.x + q.y * q.y - q.z * q.z + q.w * q.w, 2 * ( q.x * q.w + q.y * q.z )); + [MethodImpl( INLINE )] public static Vector3 Up( this Quaternion q ) => + new( + 2 * ( q.x * q.y - q.z * q.w ), + -q.x * q.x + q.y * q.y - q.z * q.z + q.w * q.w, + 2 * ( q.x * q.w + q.y * q.z ) + ); /// Returns the Z axis of this rotation (assumes this quaternion is normalized) - [MethodImpl( INLINE )] public static Vector3 Forward( this Quaternion q ) => new(2 * ( q.x * q.z + q.y * q.w ), 2 * ( q.y * q.z - q.x * q.w ), -q.x * q.x - q.y * q.y + q.z * q.z + q.w * q.w); + [MethodImpl( INLINE )] public static Vector3 Forward( this Quaternion q ) => + new( + 2 * ( q.x * q.z + q.y * q.w ), + 2 * ( q.y * q.z - q.x * q.w ), + -q.x * q.x - q.y * q.y + q.z * q.z + q.w * q.w + ); /// Converts this quaternion to a rotation matrix public static Matrix4x4 ToMatrix( this Quaternion q ) { @@ -383,7 +399,6 @@ public static Quaternion Exp( this Quaternion q ) { /// The world space rotation public static Quaternion InverseTransformRotation( this Transform tf, Quaternion quat ) => tf.rotation * quat; - #endregion #endregion #region Color manipulation diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index fa3161a..b767103 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1052,25 +1052,23 @@ public static Vector3 ClampMagnitude( Vector3 v, float min, float max ) { /// Returns the average/center of the two input vectors [MethodImpl( INLINE )] public static Vector2 Average( Vector2 a, Vector2 b ) => ( a + b ) / 2f; - /// Returns the average/center of the two input vectors + /// [MethodImpl( INLINE )] public static Vector3 Average( Vector3 a, Vector3 b ) => ( a + b ) / 2f; /// Returns the average/halfway direction between the two input direction vectors. Note that this presumes both aDir and bDir have the same length [MethodImpl( INLINE )] public static Vector2 AverageDir( Vector2 aDir, Vector2 bDir ) => ( aDir + bDir ).normalized; - /// Returns the average/halfway direction between the two input direction vectors. Note that this presumes both aDir and bDir have the same length + /// [MethodImpl( INLINE )] public static Vector3 AverageDir( Vector3 aDir, Vector3 bDir ) => ( aDir + bDir ).normalized; /// Returns the squared distance between two points. /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter [MethodImpl( INLINE )] public static float DistanceSquared( Vector2 a, Vector2 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square(); - /// Returns the squared distance between two points. - /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter + /// [MethodImpl( INLINE )] public static float DistanceSquared( Vector3 a, Vector3 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square() + ( a.z - b.z ).Square(); - /// Returns the squared distance between two points. - /// This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter + /// [MethodImpl( INLINE )] public static float DistanceSquared( Vector4 a, Vector4 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square() + ( a.z - b.z ).Square() + ( a.w - b.w ).Square(); #endregion diff --git a/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs b/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs index a7fb4b6..f579a25 100644 --- a/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs +++ b/Runtime/Splines/Multi-Segment Splines/BSpline2D.cs @@ -89,10 +89,8 @@ public bool Open { /// Returns the derivative of this B-spline, which is a B-spline in and of itself public BSpline2D Differentiate() { - // knots are the same except we remove the two outermost ones - float[] dKnots = new float[KnotCount - 2]; - for( int i = 0; i < dKnots.Length; i++ ) - dKnots[i] = knots[i + 1]; + float[] dKnots = new float[KnotCount]; + knots.CopyTo( dKnots, 0 ); // one point less Vector2[] dPts = new Vector2[PointCount - 1]; diff --git a/Runtime/Splines/Multi-Segment Splines/NURBS2D.cs b/Runtime/Splines/Multi-Segment Splines/NURBS2D.cs index 59dfcba..c984c15 100644 --- a/Runtime/Splines/Multi-Segment Splines/NURBS2D.cs +++ b/Runtime/Splines/Multi-Segment Splines/NURBS2D.cs @@ -48,7 +48,7 @@ public Vector2 GetPointByKnotValue( float t ) { Vector2 sum = default; float norm = 0; - for( int i = 0; i < PointCount; i++ ) { + for( int i = 0; i < PointCount; i++ ) { // todo: unnecessary, don't do all points float basis = Basis( i, Order, t ); if( weighted ) norm += ( basis *= weights[i] ); sum += points[i] * basis; From 6946ca0f2141b0501b2c461a17fd40201e3bb481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 15 Dec 2022 16:14:17 +0100 Subject: [PATCH 173/301] some geometric algebra stuff (3D VGA) --- Runtime/Geometric Algebra.meta | 8 ++ Runtime/Geometric Algebra/Bivector3.cs | 95 ++++++++++++++++ Runtime/Geometric Algebra/Bivector3.cs.meta | 11 ++ Runtime/Geometric Algebra/Multivector3.cs | 105 ++++++++++++++++++ .../Geometric Algebra/Multivector3.cs.meta | 11 ++ Runtime/Geometric Algebra/Rotor3.cs | 91 +++++++++++++++ Runtime/Geometric Algebra/Rotor3.cs.meta | 11 ++ Runtime/Geometric Algebra/Trivector3.cs | 22 ++++ Runtime/Geometric Algebra/Trivector3.cs.meta | 11 ++ Runtime/Mathfs.cs | 18 +++ 10 files changed, 383 insertions(+) create mode 100644 Runtime/Geometric Algebra.meta create mode 100644 Runtime/Geometric Algebra/Bivector3.cs create mode 100644 Runtime/Geometric Algebra/Bivector3.cs.meta create mode 100644 Runtime/Geometric Algebra/Multivector3.cs create mode 100644 Runtime/Geometric Algebra/Multivector3.cs.meta create mode 100644 Runtime/Geometric Algebra/Rotor3.cs create mode 100644 Runtime/Geometric Algebra/Rotor3.cs.meta create mode 100644 Runtime/Geometric Algebra/Trivector3.cs create mode 100644 Runtime/Geometric Algebra/Trivector3.cs.meta diff --git a/Runtime/Geometric Algebra.meta b/Runtime/Geometric Algebra.meta new file mode 100644 index 0000000..c8cbbd2 --- /dev/null +++ b/Runtime/Geometric Algebra.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 27c902a03e0fe7d45bd17d786cd48ddd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Algebra/Bivector3.cs b/Runtime/Geometric Algebra/Bivector3.cs new file mode 100644 index 0000000..772dd12 --- /dev/null +++ b/Runtime/Geometric Algebra/Bivector3.cs @@ -0,0 +1,95 @@ +using System; +using UnityEngine; + +namespace Freya { + + [Serializable] + public struct Bivector3 { + + public static readonly Bivector3 zero = new Bivector3( 0, 0, 0 ); + public float yz, zx, xy; + + public float this[ int i ] => i switch { 0 => yz, 1 => zx, 2 => xy, _ => throw new IndexOutOfRangeException() }; + + public Bivector3( float yz, float zx, float xy ) { + this.yz = yz; + this.zx = zx; + this.xy = xy; + } + + public Bivector3( Vector3 a, Vector3 b ) { + Bivector3 bv = Mathfs.Wedge( a, b ); + this.yz = bv.yz; + this.zx = bv.zx; + this.xy = bv.xy; + } + + public float Magnitude => Mathf.Sqrt( SqrMagnitude ); + public Bivector3 Normalized => new Bivector3( yz, zx, xy ) / Magnitude; + public Vector3 Normal => new Vector3( yz, zx, xy ) / Magnitude; + public float SqrMagnitude => yz * yz + zx * zx + xy * xy; + + /// + public float Dot( Bivector3 b ) => Dot( this, b ); + + /// + public Bivector3 Wedge( Bivector3 b ) => Wedge( this, b ); + + /// The real part when multiplying two bivectors + public static float Dot( Bivector3 a, Bivector3 b ) => -a.yz * b.yz - a.zx * b.zx - a.xy * b.xy; + + /// The bivector part when multiplying two bivectors + public static Bivector3 Wedge( Bivector3 a, Bivector3 b ) => + new Bivector3( + yz: a.xy * b.zx - a.zx * b.xy, + zx: a.yz * b.xy - a.xy * b.yz, + xy: a.zx * b.yz - a.yz * b.zx ); + + // Multiplication + public static Bivector3 operator -( Bivector3 b ) => new Bivector3( -b.yz, -b.zx, -b.xy ); + public static Bivector3 operator *( float a, Bivector3 b ) => b * a; + public static Bivector3 operator *( Bivector3 a, float b ) => new Bivector3( a.yz * b, a.zx * b, a.xy * b ); + + public static Rotor3 operator *( Bivector3 a, Bivector3 b ) => + new( + r: Dot( a, b ), + b: Wedge( a, b ) + ); + + public static Multivector3 operator *( Bivector3 a, Vector3 b ) { + return new Multivector3( + 0, // real + a.xy * b.y - a.zx * b.z, // vector + a.yz * b.z - a.xy * b.x, + a.zx * b.x - a.yz * b.y, + 0, 0, 0, // bivector + a.yz * b.x + a.zx * b.y + a.xy * b.z // trivector + ); + } + + public static Multivector3 operator *( Vector3 a, Bivector3 b ) { + return new Multivector3( + 0, // real + a.z * b.zx - a.y * b.xy, // vector + a.x * b.xy - a.z * b.yz, + a.y * b.yz - a.x * b.zx, + 0, 0, 0, // bivector + a.x * b.yz + a.y * b.zx + a.z * b.xy // trivector + ); + } + + // division + public static Bivector3 operator /( Bivector3 a, float b ) => new Bivector3( a.yz / b, a.zx / b, a.xy / b ); + + // addition + public static Bivector3 operator +( Bivector3 a, Bivector3 b ) => new Bivector3( a.yz * b.yz, a.zx * b.zx, a.xy * b.xy ); + public static Multivector3 operator +( Bivector3 a, Trivector3 b ) => new Multivector3( 0, Vector3.zero, a, b ); + public static Multivector3 operator +( Trivector3 a, Bivector3 b ) => new Multivector3( 0, Vector3.zero, b, a ); + + // casting + public static explicit operator Vector3( Bivector3 bv ) => new Vector3( bv.yz, bv.zx, bv.xy ); + public static explicit operator Bivector3( Vector3 v ) => new Bivector3( v.x, v.y, v.z ); + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Algebra/Bivector3.cs.meta b/Runtime/Geometric Algebra/Bivector3.cs.meta new file mode 100644 index 0000000..df2bab5 --- /dev/null +++ b/Runtime/Geometric Algebra/Bivector3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cc21a5a6fd4afbb4bb345d2004897f94 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Algebra/Multivector3.cs b/Runtime/Geometric Algebra/Multivector3.cs new file mode 100644 index 0000000..90ca614 --- /dev/null +++ b/Runtime/Geometric Algebra/Multivector3.cs @@ -0,0 +1,105 @@ +using UnityEngine; + +namespace Freya { + + public struct Multivector3 { + + public float r; + public Vector3 v; + public Bivector3 b; + public Trivector3 t; + + public float x { + get => v.x; + set => v.x = value; + } + public float y { + get => v.y; + set => v.y = value; + } + public float z { + get => v.z; + set => v.z = value; + } + public float yz { + get => b.yz; + set => b.yz = value; + } + public float zx { + get => b.zx; + set => b.zx = value; + } + public float xy { + get => b.xy; + set => b.xy = value; + } + public float xyz { + get => t.xyz; + set => t.xyz = value; + } + + public Multivector3( float r, float x, float y, float z, float yz, float zx, float xy, float xyz ) + : this( + r, + new Vector3( x, y, z ), + new Bivector3( yz, zx, xy ), + new Trivector3( xyz ) ) {} + + public Multivector3( float r, Vector3 v ) : this( r, v, Bivector3.zero, Trivector3.zero ) {} + + public Multivector3( float r, Vector3 v, Bivector3 b, Trivector3 t ) { + this.r = r; + this.v = v; + this.b = b; + this.t = t; + } + + // multiplication + public static Multivector3 operator *( Multivector3 a, Multivector3 b ) { + // 64 multiplications, 56 add/sub + // R: a_r*b_r +a_x*b_x +a_y*b_y +a_z*b_z +a_yz*b_yz +a_zx*b_zx +a_xy*b_xy +a_xyz*b_xyz + // X: a_r*b_x +a_x*b_r -a_y*b_xy +a_z*b_zx -a_yz*b_xyz -a_zx*b_z +a_xy*b_y -a_xyz*b_yz + // Y: a_r*b_y +a_x*b_xy +a_y*b_r -a_z*b_yz +a_yz*b_z -a_zx*b_xyz -a_xy*b_x -a_xyz*b_zx + // Z: a_r*b_z -a_x*b_zx +a_y*b_yz +a_z*b_r -a_yz*b_y +a_zx*b_x -a_xy*b_xyz -a_xyz*b_xy + // YZ: a_r*b_yz +a_x*b_xyz +a_y*b_z -a_z*b_y +a_yz*b_r -a_zx*b_xy +a_xy*b_zx +a_xyz*b_x + // ZX: a_r*b_zx -a_x*b_z +a_y*b_xyz +a_z*b_x +a_yz*b_xy +a_zx*b_r -a_xy*b_yz +a_xyz*b_y + // XY: a_r*b_xy +a_x*b_y -a_y*b_x +a_z*b_xyz -a_yz*b_zx +a_zx*b_yz +a_xy*b_r +a_xyz*b_z + // XYZ: a_r*b_xyz +a_x*byz +a_y*b_zx +a_z*xy +a_yz*b_x +a_zx*b_y +a_xy*b_z +a_xyz*b_r + return new Multivector3( + a.r * b.r + a.x * b.x + a.y * b.y + a.z * b.z + a.yz * b.yz + a.zx * b.zx + a.xy * b.xy + a.xyz * b.xyz, + a.r * b.x + a.x * b.r - a.y * b.xy + a.z * b.zx - a.yz * b.xyz - a.zx * b.z + a.xy * b.y - a.xyz * b.yz, + a.r * b.y + a.x * b.xy + a.y * b.r - a.z * b.yz + a.yz * b.z - a.zx * b.xyz - a.xy * b.x - a.xyz * b.zx, + a.r * b.z - a.x * b.zx + a.y * b.yz + a.z * b.r - a.yz * b.y + a.zx * b.x - a.xy * b.xyz - a.xyz * b.xy, + a.r * b.yz + a.x * b.xyz + a.y * b.z - a.z * b.y + a.yz * b.r - a.zx * b.xy + a.xy * b.zx + a.xyz * b.x, + a.r * b.zx - a.x * b.z + a.y * b.xyz + a.z * b.x + a.yz * b.xy + a.zx * b.r - a.xy * b.yz + a.xyz * b.y, + a.r * b.xy + a.x * b.y - a.y * b.x + a.z * b.xyz - a.yz * b.zx + a.zx * b.yz + a.xy * b.r + a.xyz * b.z, + a.r * b.xyz + a.x * b.yz + a.y * b.zx + a.z * b.xy + a.yz * b.x + a.zx * b.y + a.xy * b.z + a.xyz * b.r + ); + } + + public static Bivector3 Wedge( Multivector3 a, Multivector3 b ) => + new Bivector3( + a.r * b.yz + a.x * b.xyz + a.y * b.z - a.z * b.y + a.yz * b.r - a.zx * b.xy + a.xy * b.zx + a.xyz * b.x, + a.r * b.zx - a.x * b.z + a.y * b.xyz + a.z * b.x + a.yz * b.xy + a.zx * b.r - a.xy * b.yz + a.xyz * b.y, + a.r * b.xy + a.x * b.y - a.y * b.x + a.z * b.xyz - a.yz * b.zx + a.zx * b.yz + a.xy * b.r + a.xyz * b.z ); + + public static float Dot( Multivector3 a, Multivector3 b ) => a.r * b.r + a.x * b.x + a.y * b.y + a.z * b.z + a.yz * b.yz + a.zx * b.zx + a.xy * b.xy + a.xyz * b.xyz; + + // addition + public static Multivector3 operator +( Multivector3 a, float b ) => new(a.r + b, a.v, a.b, a.t); + public static Multivector3 operator +( float a, Multivector3 b ) => b + a; + public static Multivector3 operator +( Multivector3 a, Vector3 b ) => new(a.r, a.v + b, a.b, a.t); + public static Multivector3 operator +( Vector3 a, Multivector3 b ) => b + a; + public static Multivector3 operator +( Multivector3 a, Bivector3 b ) => new(a.r, a.v, a.b + b, a.t); + public static Multivector3 operator +( Bivector3 a, Multivector3 b ) => b + a; + public static Multivector3 operator +( Multivector3 a, Trivector3 b ) => new(a.r, a.v, a.b, a.t + b); + public static Multivector3 operator +( Trivector3 a, Multivector3 b ) => b + a; + public static Multivector3 operator +( Multivector3 a, Multivector3 b ) => new(a.r + b.r, a.v + b.v, a.b + b.b, a.t + b.t); + + // combined + public static Multivector3 operator +( Multivector3 a, Rotor3 b ) => new(a.r + b.r, a.v, a.b + b.b, a.t); + public static Multivector3 operator +( Rotor3 a, Multivector3 b ) => b + a; + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Algebra/Multivector3.cs.meta b/Runtime/Geometric Algebra/Multivector3.cs.meta new file mode 100644 index 0000000..6feb424 --- /dev/null +++ b/Runtime/Geometric Algebra/Multivector3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae2bcddcbde33d74a83fa78413d60774 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Algebra/Rotor3.cs b/Runtime/Geometric Algebra/Rotor3.cs new file mode 100644 index 0000000..6d4933a --- /dev/null +++ b/Runtime/Geometric Algebra/Rotor3.cs @@ -0,0 +1,91 @@ +using UnityEngine; + +namespace Freya { + + /// The even subalgebra of 3D VGA, isomorphic to Quaternions + public struct Rotor3 { + + public float r; + public Bivector3 b; + public float yz { + get => b.yz; + set => b.yz = value; + } + public float zx { + get => b.zx; + set => b.zx = value; + } + public float xy { + get => b.xy; + set => b.xy = value; + } + + public Rotor3( float r, float yz, float zx, float xy ) : this( r, new Bivector3( yz, zx, xy ) ) {} + + public Rotor3( float r, Bivector3 b ) { + this.r = r; + this.b = b; + } + + public float Magnitude => Mathf.Sqrt( SqrMagnitude ); + public float SqrMagnitude => r * r + b.SqrMagnitude; + + public Rotor3 Normalized() => this / Magnitude; + + /// Negates the bivector, which is equivalent to reversing the rotation, + /// if this is normalized an interpreted as a rotation + public Rotor3 Conjugate => new Rotor3( r, -b ); + + /// Sandwich product, equivalent to RvR* (where R* is the conjugate of R). + /// Commonly used to rotate vectors with unit rotors + /// The vector to multiply (or rotate) + public Vector3 SandwichConjugate( Vector3 v ) { + // todo: untested + float r2 = r * r; + float yz2 = yz * yz; + float zx2 = zx * zx; + float xy2 = xy * xy; + float yzzx = yz * zx; + float zxxy = zx * xy; + float xyyz = xy * yz; + float rxy = r * xy; + float rzx = r * zx; + float ryz = r * yz; + + return new Vector3( + v.x * ( r2 + yz2 - zx2 - xy2 ) + + 2 * v.y * ( yzzx + rxy ) + + 2 * v.z * ( xyyz - rzx ), + v.y * ( r2 - yz2 + zx2 - xy2 ) + + 2 * v.x * ( yzzx - rxy ) + + 2 * v.z * ( ryz + zxxy ), + v.z * ( r2 - yz2 - zx2 + xy2 ) + + 2 * v.x * ( rzx + xyyz ) + + 2 * v.y * ( zxxy - ryz ) + ); + } + + // multiplication + public static Rotor3 operator *( Rotor3 a, Rotor3 b ) { + return new Rotor3( + a.r * b.r - a.yz * b.yz - a.zx * b.zx - a.xy * b.xy, + a.r * b.yz + a.yz * b.r - a.zx * b.xy + a.xy * b.zx, + a.r * b.zx + a.yz * b.xy + a.zx * b.r - a.xy * b.yz, + a.r * b.xy - a.yz * b.zx + a.zx * b.yz + a.xy * b.r + ); + } + + public static Rotor3 operator /( Rotor3 a, float b ) { + return new Rotor3( a.r / b, a.yz / b, a.zx / b, a.xy / b ); + } + + // addition + public static Rotor3 operator +( Rotor3 a, float b ) => new Rotor3( a.r + b, a.b ); + public static Rotor3 operator +( float a, Rotor3 b ) => b + a; + public static Rotor3 operator +( Rotor3 a, Bivector3 b ) => new Rotor3( a.r, a.b + b ); + public static Rotor3 operator +( Bivector3 a, Rotor3 b ) => b + a; + public static Rotor3 operator +( Rotor3 a, Rotor3 b ) => new Rotor3( a.r + b.r, a.b + b.b ); + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Algebra/Rotor3.cs.meta b/Runtime/Geometric Algebra/Rotor3.cs.meta new file mode 100644 index 0000000..a93a430 --- /dev/null +++ b/Runtime/Geometric Algebra/Rotor3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eea02c7bcbb44ac4680152501f9ae6cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Algebra/Trivector3.cs b/Runtime/Geometric Algebra/Trivector3.cs new file mode 100644 index 0000000..09ce893 --- /dev/null +++ b/Runtime/Geometric Algebra/Trivector3.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +namespace Freya { + + public struct Trivector3 { + public static readonly Trivector3 zero = new Trivector3( 0 ); + public float xyz; + public Trivector3( float xyz ) => this.xyz = xyz; + public static Trivector3 operator +( Trivector3 a, Trivector3 b ) => new Trivector3( a.xyz + b.xyz ); + public static Trivector3 operator *( Trivector3 a, float b ) => new Trivector3( a.xyz * b ); + public static Trivector3 operator *( float a, Trivector3 b ) => b * a; + + public static Bivector3 operator *( Trivector3 a, Vector3 b ) => new Bivector3( a.xyz * b.x, a.xyz * b.y, a.xyz * b.z ); + public static Bivector3 operator *( Vector3 a, Trivector3 b ) => new Bivector3( a.x * b.xyz, a.y * b.xyz, a.z * b.xyz ); + public static Bivector3 operator *( Bivector3 a, Trivector3 b ) => new Bivector3( -a.yz * b.xyz, -a.zx * b.xyz, -a.xy * b.xyz ); + public static Bivector3 operator *( Trivector3 a, Bivector3 b ) => new Bivector3( -a.xyz * b.yz, -a.xyz * b.zx, -a.xyz * b.xy ); + + public static float operator *( Trivector3 a, Trivector3 b ) => -a.xyz * b.xyz; + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Algebra/Trivector3.cs.meta b/Runtime/Geometric Algebra/Trivector3.cs.meta new file mode 100644 index 0000000..519a45a --- /dev/null +++ b/Runtime/Geometric Algebra/Trivector3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2506c44a177d8b3418ba2b081ffefeff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index b767103..86ada09 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -304,6 +304,24 @@ public static double SincRcp( double x ) { #endregion + #region Geometric Algebra + + /// Returns the wedge product between two vectors + public static float Wedge( Vector2 a, Vector2 b ) => a.x * b.y - a.y * b.x; + + /// Returns the wedge product between two vectors + public static Bivector3 Wedge( Vector3 a, Vector3 b ) => + new Bivector3( + a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x + ); + + /// Returns the geometric product between two vectors + public static Rotor3 GeometricProduct( Vector3 a, Vector3 b ) => new Rotor3( Vector3.Dot( a, b ), Wedge( a, b ) ); + + #endregion + #region Absolute Values /// Returns the absolute value. Basically makes negative numbers positive From 1239a17292535a9c8225d9258477bb373c065c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 15 Dec 2022 16:33:04 +0100 Subject: [PATCH 174/301] probability utility --- Runtime/Numerics/Probability.cs | 53 ++++++++++++++++++++++++++++ Runtime/Numerics/Probability.cs.meta | 11 ++++++ 2 files changed, 64 insertions(+) create mode 100644 Runtime/Numerics/Probability.cs create mode 100644 Runtime/Numerics/Probability.cs.meta diff --git a/Runtime/Numerics/Probability.cs b/Runtime/Numerics/Probability.cs new file mode 100644 index 0000000..dcae1c0 --- /dev/null +++ b/Runtime/Numerics/Probability.cs @@ -0,0 +1,53 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; + +namespace Freya { + + /// A struct representing a probability (as a rational number) + [Serializable] public struct Probability : IComparable { + + public static readonly Rational Zero = new(0, 1); + public static readonly Rational One = new(1, 1); + + /// The value of this probability + public Rational value; + + /// Creates a representation of probability using a rational number + /// /// The probability value + public Probability( Rational value ) => this.value = value; + + /// Creates a representation of probability using a rational number + /// The numerator of this probability + /// The denominator of this probability + public Probability( int num, int den ) : this( new Rational( num, den ) ) { + } + + /// Randomly samples this probability, returning either true or false + public bool Sample => Random.Range( 0, value.d ) < value.n; + + public override string ToString() => $"{value} ({(float)value * 100:#.#####}%)"; + + // statics + public static Probability operator &( Probability a, Probability b ) => new(a.value * b.value); + public static Probability operator |( Probability a, Probability b ) => !( !a & !b ); + public static Probability operator +( Probability a, Probability b ) => new(a.value + b.value); + public static Probability operator !( Probability p ) => new(1 - p.value); + + // comparison operators + public static bool operator ==( Probability a, Probability b ) => a.value == b.value; + public static bool operator !=( Probability a, Probability b ) => a.value != b.value; + public static bool operator <( Probability a, Probability b ) => a.value < b.value; + public static bool operator >( Probability a, Probability b ) => a.value > b.value; + public static bool operator <=( Probability a, Probability b ) => a.value <= b.value; + public static bool operator >=( Probability a, Probability b ) => a.value >= b.value; + + // comparison functions + public int CompareTo( Probability other ) => value.CompareTo( other.value ); + public bool Equals( Probability other ) => value.Equals( other.value ); + public override bool Equals( object obj ) => obj is Probability other && Equals( other ); + public override int GetHashCode() => value.GetHashCode(); + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/Probability.cs.meta b/Runtime/Numerics/Probability.cs.meta new file mode 100644 index 0000000..c7e42ba --- /dev/null +++ b/Runtime/Numerics/Probability.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c81aa65965d057d488f9b20e1ae6aa95 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From cdeae8b4f89eb4c0307c069f15e1f5d381369fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 25 Dec 2022 18:02:40 +0100 Subject: [PATCH 175/301] int range tweaks --- Runtime/Numerics/IntRange.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Runtime/Numerics/IntRange.cs b/Runtime/Numerics/IntRange.cs index a20b11c..ebc2f51 100644 --- a/Runtime/Numerics/IntRange.cs +++ b/Runtime/Numerics/IntRange.cs @@ -10,14 +10,14 @@ public readonly struct IntRange { public readonly int count; /// The last integer in the range - public int End => start + count - 1; + public int Last => start + count - 1; /// The distance from first to last integer. Equivalent to count-1 public int Distance => count - 1; - /// Creates a new integer range, given a start integer and how many more integers to include + /// Creates a new integer range, given a start integer and how many integers to include in total /// The first integer - /// How many integers to include in the range + /// How many integers to include in the full range public IntRange( int start, int count ) { this.start = start; this.count = count; @@ -25,18 +25,19 @@ public IntRange( int start, int count ) { /// Whether or not this range contains a given value (inclusive) /// The value to check if it's inside, or equal to the start or end - public bool Contains( int value ) => value >= start && value <= End; + public bool Contains( int value ) => value >= start && value <= Last; /// Create an integer range from start to end (inclusive) - /// The first integer - /// The last integer (inclusive) - public static IntRange StartEnd( int start, int end ) => new IntRange( start, end - start + 1 ); - + /// The first integer + /// The last integer + public static IntRange FirstToLast( int first, int last ) => new IntRange( first, last - first + 1 ); + static readonly StringBuilder toStrBuilder = new StringBuilder(); + public override string ToString() { toStrBuilder.Clear(); toStrBuilder.Append( "{ " ); - int last = End; + int last = Last; for( int i = start; i <= last; i++ ) { toStrBuilder.Append( i ); if( i != last ) From a55feecce583e5582ebc19f69a89462e57eb5ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 25 Dec 2022 18:02:55 +0100 Subject: [PATCH 176/301] added bivector normal/area combo function --- Runtime/Geometric Algebra/Bivector3.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Runtime/Geometric Algebra/Bivector3.cs b/Runtime/Geometric Algebra/Bivector3.cs index 772dd12..8252f97 100644 --- a/Runtime/Geometric Algebra/Bivector3.cs +++ b/Runtime/Geometric Algebra/Bivector3.cs @@ -45,6 +45,9 @@ public static Bivector3 Wedge( Bivector3 a, Bivector3 b ) => zx: a.yz * b.xy - a.xy * b.yz, xy: a.zx * b.yz - a.yz * b.zx ); + /// Returns the normal of this bivector plane and its area + public (Vector3 normal, float area) GetNormalAndArea() => ( (Vector3)this ).GetDirAndMagnitude(); + // Multiplication public static Bivector3 operator -( Bivector3 b ) => new Bivector3( -b.yz, -b.zx, -b.xy ); public static Bivector3 operator *( float a, Bivector3 b ) => b * a; From e05698fcef0d4d7f93b99532928c142c09594da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 25 Dec 2022 18:03:06 +0100 Subject: [PATCH 177/301] added 2D catenary curve w. arc length solvers --- Runtime/Curves/Catenary2D.cs | 184 ++++++++++++++++++++++++++++++ Runtime/Curves/Catenary2D.cs.meta | 11 ++ 2 files changed, 195 insertions(+) create mode 100644 Runtime/Curves/Catenary2D.cs create mode 100644 Runtime/Curves/Catenary2D.cs.meta diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs new file mode 100644 index 0000000..f8ccb0c --- /dev/null +++ b/Runtime/Curves/Catenary2D.cs @@ -0,0 +1,184 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + /// A catenary curve passing through two points with a given an arc length + public struct Catenary2D { + + enum Evaluability { + Unknown = 0, + Catenary, + LinearVertical, + LineSegment + } + + const int INTERVAL_SEARCH_ITERATIONS = 12; + const int BISECT_REFINE_COUNT = 15; + + // input data + readonly Vector2 p0, p1; + readonly float s; + + // cached state + float a; + Vector2 delta; + Evaluability evaluability; + + /// Creates a catenary curve between two points, given an arc length s + /// The start of the curve + /// The end of the curve + /// The length of the curve. note: has to be equal or longer than the distance between the points + public Catenary2D( Vector2 p0, Vector2 p1, float s ) { + ( this.p0, this.p1, this.s ) = ( p0, p1, s ); + a = 0; + delta = default; + evaluability = Evaluability.Unknown; + } + + /// Evaluates a position on this catenary curve, given a t-value from 0 to 1 + /// A value from 0 to 1 along the whole curve + public Vector2 Eval( float t ) { + ReadyForEvaluation(); + + if( evaluability == Evaluability.LineSegment ) + return Vector3.LerpUnclamped( p0, p1, t ); // chain is almost completely linear + + ( Vector2 pLeft, Vector2 pRight ) = p0.x > p1.x ? ( p1, p0 ) : ( p0, p1 ); + Vector2 p = pRight - pLeft; + float x = Mathfs.Lerp( 0, p.x, t ); + + float y; + if( evaluability == Evaluability.LinearVertical ) { // chain is almost completely vertical, use a linear approximation + float ts = t * s; + float seg0 = ( s - p.y ) / 2; + y = ( ts < seg0 ) ? -ts : -2 * seg0 + ts; + } else { + y = EvalFrom0( x ); + } + + return new Vector2( x, y ) + pLeft; + } + + // Passing through (0,0) and point p + float EvalFrom0( float x ) => a * Mathfs.Cosh( ( x - delta.x ) / a ) + delta.y; + + bool IsFullyVertical( float dx ) => dx < 0.001f; + bool IsStraightLine() => s <= Vector2.Distance( p0, p1 ) * 1.00005f; + + void ReadyForEvaluation() { + if( evaluability != Evaluability.Unknown ) + return; + + // CASE 1: + // first, test if it's a line segment + if( IsStraightLine() ) { + Debug.Log( "line seg" ); + evaluability = Evaluability.LineSegment; + return; + } + + // relative to origin point p + Vector2 p = p1 - p0; + if( p.x < 0 ) + p = -p; // swap 0 and p + + // CASE 2: + // check if it's basically a fully vertical hanging chain + if( IsFullyVertical( p.x ) ) { + Debug.Log( "linear vertical" ); + evaluability = Evaluability.LinearVertical; + return; + } + + // Now we've got a possible catenary curve on our hands + + // set up function + float c = Mathf.Sqrt( s * s - p.y * p.y ); + float F( float a ) => 2 * a * Mathfs.Sinh( p.x / ( 2 * a ) ) - c; + + // find bounds of the root + float xRoot = ( p.x * p.x ) / ( 2 * s ); // intial guess based on freya's flawless heuristics + bool rootFound = FindRootRangeExponential( F, xRoot, out FloatRange xRange, out FloatRange yRange ); + + if( rootFound == false ) { // refine if it hasn't already been found + // CASE 4: + // it's possible we failed to find a valid root range + if( yRange.Contains( 0 ) == false ) { + evaluability = Evaluability.LinearVertical; + return; + } + + // CASE 5: + // Catenary seems valid, with roots inside! now refine this range + RootFindBisections( F, ref xRange, BISECT_REFINE_COUNT ); + } + + // set a to the middle of the latest range + a = xRange.Center; + + // cached delta, for faster evaluation: + delta.x = ( p.x - a * Mathf.Log( ( s + p.y ) / ( s - p.y ) ) ) / 2; + delta.y = -a * Mathfs.Cosh( -delta.x / a ); + evaluability = Evaluability.Catenary; + } + + static bool FindRootRangeExponential( Func F, float initialGuess, out FloatRange xRange, out FloatRange yRange ) { + float xRoot = initialGuess; + float yTest = F( xRoot ); + xRange = new FloatRange( xRoot, xRoot ); + yRange = new FloatRange( yTest, yTest ); + + // find which direction to search in + if( Mathf.Approximately( yTest, 0 ) ) { + // already on the root, no need to iterate + return true; + } else if( yTest > 0 ) { // search forwards for a negative value, set upper bound + for( int i = 0; i < INTERVAL_SEARCH_ITERATIONS; i++ ) { + xRoot *= 2; + float value = F( xRoot ); + if( value < 0 ) { + xRange.b = xRoot; // found negative value! + yRange.b = value; + break; + } else { + xRange.a = xRoot; // still positive, shift left bound + yRange.a = value; + } + } + } else { // search backwards for a positive value, set lower bound + for( int i = 0; i < INTERVAL_SEARCH_ITERATIONS; i++ ) { + xRoot *= 0.5f; + float value = F( xRoot ); + if( value > 0 ) { + xRange.a = xRoot; // found positive value! + yRange.a = value; + break; + } else { + xRange.b = xRoot; // still negative, shift right bound + yRange.b = value; + } + } + } + return false; // not found yet + } + + static void RootFindBisections( Func F, ref FloatRange xRange, int iterationCount ) { + for( int i = 0; i < iterationCount; i++ ) + RootFindBisection( F, ref xRange ); + } + + static void RootFindBisection( Func F, ref FloatRange xRange ) { + float xInter = xRange.Center; // bisection + float yInter = F( xInter ); + if( yInter > 0 ) + xRange.a = xInter; // adjust left bound + else + xRange.b = xInter; // adjust right bound + } + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/Catenary2D.cs.meta b/Runtime/Curves/Catenary2D.cs.meta new file mode 100644 index 0000000..4e495e2 --- /dev/null +++ b/Runtime/Curves/Catenary2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04762b34aa4b834489c20d76358b2d7e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From fccd0cd2211d587922a18cacf216593c824a00a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 25 Dec 2022 18:19:41 +0100 Subject: [PATCH 178/301] moved Trajectory2D to the correct folder --- Runtime/{Splines => Curves}/Trajectory2D.cs | 0 Runtime/{Splines => Curves}/Trajectory2D.cs.meta | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Runtime/{Splines => Curves}/Trajectory2D.cs (100%) rename Runtime/{Splines => Curves}/Trajectory2D.cs.meta (100%) diff --git a/Runtime/Splines/Trajectory2D.cs b/Runtime/Curves/Trajectory2D.cs similarity index 100% rename from Runtime/Splines/Trajectory2D.cs rename to Runtime/Curves/Trajectory2D.cs diff --git a/Runtime/Splines/Trajectory2D.cs.meta b/Runtime/Curves/Trajectory2D.cs.meta similarity index 100% rename from Runtime/Splines/Trajectory2D.cs.meta rename to Runtime/Curves/Trajectory2D.cs.meta From f957594ad613ef457e6ada0aa44ed2ffcf837495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 25 Dec 2022 18:51:24 +0100 Subject: [PATCH 179/301] removed some debug logs from Catenary2D --- Runtime/Curves/Catenary2D.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index f8ccb0c..9ed2242 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -75,7 +75,6 @@ void ReadyForEvaluation() { // CASE 1: // first, test if it's a line segment if( IsStraightLine() ) { - Debug.Log( "line seg" ); evaluability = Evaluability.LineSegment; return; } @@ -88,7 +87,6 @@ void ReadyForEvaluation() { // CASE 2: // check if it's basically a fully vertical hanging chain if( IsFullyVertical( p.x ) ) { - Debug.Log( "linear vertical" ); evaluability = Evaluability.LinearVertical; return; } From eeb908f2d167e1885f8d33280e65e22b12010da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 30 Dec 2022 23:14:06 +0100 Subject: [PATCH 180/301] cleaned up catenary code a bunch --- Runtime/Curves/Catenary2D.cs | 117 ++++++++++++++++------------------- 1 file changed, 52 insertions(+), 65 deletions(-) diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index 9ed2242..e3adad9 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -46,10 +46,8 @@ public Vector2 Eval( float t ) { if( evaluability == Evaluability.LineSegment ) return Vector3.LerpUnclamped( p0, p1, t ); // chain is almost completely linear - ( Vector2 pLeft, Vector2 pRight ) = p0.x > p1.x ? ( p1, p0 ) : ( p0, p1 ); - Vector2 p = pRight - pLeft; + Vector2 p = p1 - p0; float x = Mathfs.Lerp( 0, p.x, t ); - float y; if( evaluability == Evaluability.LinearVertical ) { // chain is almost completely vertical, use a linear approximation float ts = t * s; @@ -59,13 +57,13 @@ public Vector2 Eval( float t ) { y = EvalFrom0( x ); } - return new Vector2( x, y ) + pLeft; + return new Vector2( x, y ) + p0; } // Passing through (0,0) and point p float EvalFrom0( float x ) => a * Mathfs.Cosh( ( x - delta.x ) / a ) + delta.y; - bool IsFullyVertical( float dx ) => dx < 0.001f; + bool IsFullyVertical( float dx ) => Mathf.Abs( dx ) < 0.001f; bool IsStraightLine() => s <= Vector2.Distance( p0, p1 ) * 1.00005f; void ReadyForEvaluation() { @@ -81,8 +79,6 @@ void ReadyForEvaluation() { // relative to origin point p Vector2 p = p1 - p0; - if( p.x < 0 ) - p = -p; // swap 0 and p // CASE 2: // check if it's basically a fully vertical hanging chain @@ -91,76 +87,67 @@ void ReadyForEvaluation() { return; } - // Now we've got a possible catenary curve on our hands - - // set up function + // CASE 3: + // Now we've got a catenary on our hands unless something explodes. float c = Mathf.Sqrt( s * s - p.y * p.y ); - float F( float a ) => 2 * a * Mathfs.Sinh( p.x / ( 2 * a ) ) - c; + float pAbsX = p.x.Abs(); // solve only in x > 0 + float R( float a ) => 2 * a * Mathfs.Sinh( pAbsX / ( 2 * a ) ) - c; // set up root solve function // find bounds of the root float xRoot = ( p.x * p.x ) / ( 2 * s ); // intial guess based on freya's flawless heuristics - bool rootFound = FindRootRangeExponential( F, xRoot, out FloatRange xRange, out FloatRange yRange ); - - if( rootFound == false ) { // refine if it hasn't already been found + if( TryFindRootBounds( R, xRoot, out FloatRange xRange ) ) { + // refine range, if necessary (which is very likely) + if( Mathf.Approximately( xRange.Length, 0 ) == false ) + RootFindBisections( R, ref xRange, BISECT_REFINE_COUNT ); // Catenary seems valid, with roots inside, refine the range + a = xRange.Center; // set a to the middle of the latest range + delta = CalcCatenaryDelta( a, p ); // find delta to pass through both points + evaluability = Evaluability.Catenary; + } else { // CASE 4: - // it's possible we failed to find a valid root range - if( yRange.Contains( 0 ) == false ) { - evaluability = Evaluability.LinearVertical; - return; - } - - // CASE 5: - // Catenary seems valid, with roots inside! now refine this range - RootFindBisections( F, ref xRange, BISECT_REFINE_COUNT ); + // something exploded, couldn't find a range, so let's use a straight line as a fallback + evaluability = Evaluability.LineSegment; } - - // set a to the middle of the latest range - a = xRange.Center; - - // cached delta, for faster evaluation: - delta.x = ( p.x - a * Mathf.Log( ( s + p.y ) / ( s - p.y ) ) ) / 2; - delta.y = -a * Mathfs.Cosh( -delta.x / a ); - evaluability = Evaluability.Catenary; } - static bool FindRootRangeExponential( Func F, float initialGuess, out FloatRange xRange, out FloatRange yRange ) { - float xRoot = initialGuess; - float yTest = F( xRoot ); - xRange = new FloatRange( xRoot, xRoot ); - yRange = new FloatRange( yTest, yTest ); + // Calculates the required offset to make a catenary pass through the origin and a point p + static Vector2 CalcCatenaryDelta( float a, Vector2 p ) { + Vector2 d; + d.x = p.x / 2 - a * Mathfs.Asinh( p.y / ( 2 * a * Mathfs.Sinh( p.x / ( 2 * a ) ) ) ); + d.y = -a * Mathfs.Cosh( -d.x / a ); + return d; + } - // find which direction to search in - if( Mathf.Approximately( yTest, 0 ) ) { - // already on the root, no need to iterate + // presumes a decreasing function with one root in x > 0 + // g = initial guess + static bool TryFindRootBounds( Func R, float g, out FloatRange xRange ) { + float y = R( g ); + xRange = new FloatRange( g, g ); + if( Mathfs.Approximately( y, 0 ) ) // somehow landed *on* our root in our initial guess return true; - } else if( yTest > 0 ) { // search forwards for a negative value, set upper bound - for( int i = 0; i < INTERVAL_SEARCH_ITERATIONS; i++ ) { - xRoot *= 2; - float value = F( xRoot ); - if( value < 0 ) { - xRange.b = xRoot; // found negative value! - yRange.b = value; - break; - } else { - xRange.a = xRoot; // still positive, shift left bound - yRange.a = value; - } - } - } else { // search backwards for a positive value, set lower bound - for( int i = 0; i < INTERVAL_SEARCH_ITERATIONS; i++ ) { - xRoot *= 0.5f; - float value = F( xRoot ); - if( value > 0 ) { - xRange.a = xRoot; // found positive value! - yRange.a = value; - break; - } else { - xRange.b = xRoot; // still negative, shift right bound - yRange.b = value; - } + + bool findingUpper = y > 0; + + for( int n = 1; n <= INTERVAL_SEARCH_ITERATIONS; n++ ) { + if( findingUpper ) { + // It's positive - we found our lower bound + // exponentially search for upper bound + xRange.a = xRange.b; + xRange.b = g * Mathf.Pow( 2, n ); + y = R( xRange.b ); + if( y < 0 ) + return true; // upper bound found! + } else { + // It's negative - we found our upper bound + // exponentially search for lower bound + xRange.b = xRange.a; + xRange.a = g * Mathf.Pow( 2, -n ); + y = R( xRange.a ); + if( y > 0 ) + return true; // lower bound found! } } - return false; // not found yet + + return false; // no root found } static void RootFindBisections( Func F, ref FloatRange xRange, int iterationCount ) { From bd26e64b11bd08f0061ae3d93047364dc8905451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 1 Jan 2023 14:19:59 +0100 Subject: [PATCH 181/301] matched inverse hyperbolic trig functions to .NET --- Runtime/Mathfs.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 86ada09..38a9849 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -294,13 +294,13 @@ public static double SincRcp( double x ) { [MethodImpl( INLINE )] public static float Tanh( float x ) => (float)Math.Tanh( x ); /// Returns the hyperbolic arc cosine of the given value - [MethodImpl( INLINE )] public static float Acosh( float x ) => (float)Math.Log( x + Mathf.Sqrt( x * x - 1 ) ); + [MethodImpl( INLINE )] public static float Acosh( float x ) => (float)Math.Acosh( x ); /// Returns the hyperbolic arc sine of the given value - [MethodImpl( INLINE )] public static float Asinh( float x ) => (float)Math.Log( x + Mathf.Sqrt( x * x + 1 ) ); + [MethodImpl( INLINE )] public static float Asinh( float x ) => (float)Math.Asinh( x ); /// Returns the hyperbolic arc tangent of the given value - [MethodImpl( INLINE )] public static float Atanh( float x ) => (float)( 0.5 * Math.Log( ( 1 + x ) / ( 1 - x ) ) ); + [MethodImpl( INLINE )] public static float Atanh( float x ) => (float)Math.Atanh( x ); #endregion From eb6720be4a1cb19e4073e33ac7a85df8d14d5b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 1 Jan 2023 14:20:32 +0100 Subject: [PATCH 182/301] static catenary equations --- Runtime/Curves/Catenary2D.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index e3adad9..5038926 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -8,6 +8,27 @@ namespace Freya { /// A catenary curve passing through two points with a given an arc length public struct Catenary2D { + #region Standard catenary equations + + /// Returns the y coordinate of a catenary at the given x value + /// The x coordinate to evaluate at + /// The a-parameter of the catenary + public static float Eval( float x, float a ) => a * Mathfs.Cosh( x / a ); + + /// Evaluates the arc length from the apex of the catenary, to the given x coordinate. + /// Note that this is negative when x is less than 0 + /// The x coordinate to get the length to + /// The a-parameter of the catenary + public static float EvalArcLen( float x, float a ) => a * Mathfs.Sinh( x / a ); + + /// Evaluates the x coordinate at the given arc length relative to the apex of the catenary. + /// Note that the input arc length can be negative, to get the negative x coordinates + /// The arc length to get the x coordinate of + /// The a-parameter of the catenary + public static float EvalXByArcLength( float s, float a ) => a * Mathfs.Asinh( s / a ); + + #endregion + enum Evaluability { Unknown = 0, Catenary, From 3f5ad9cd577a75c98a0e728e00808a1aa3970e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 1 Jan 2023 14:21:47 +0100 Subject: [PATCH 183/301] lots of catenary2D cleanup, now arc len param. --- Runtime/Curves/Catenary2D.cs | 96 +++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index 5038926..0bda2f8 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -37,16 +37,34 @@ enum Evaluability { } const int INTERVAL_SEARCH_ITERATIONS = 12; - const int BISECT_REFINE_COUNT = 15; - - // input data - readonly Vector2 p0, p1; - readonly float s; + const int BISECT_REFINE_COUNT = 14; + // data + Vector2 p0, p1; + float s; + // cached state float a; + Vector2 p; Vector2 delta; + float arcLenSampleOffset; Evaluability evaluability; + + public float Length { + get => s; + set => ( s, evaluability ) = ( value, Evaluability.Unknown ); + } + public Vector2 P0 { + get => p0; + set => ( p0, evaluability ) = ( value, Evaluability.Unknown ); + } + public Vector2 P1 { + get => p1; + set => ( p1, evaluability ) = ( value, Evaluability.Unknown ); + } + + public bool IsVertical => Mathf.Abs( p.x ) < 0.001f; + public bool IsStraightLine => s <= Vector2.Distance( p0, p1 ) * 1.00005f; /// Creates a catenary curve between two points, given an arc length s /// The start of the curve @@ -55,55 +73,73 @@ enum Evaluability { public Catenary2D( Vector2 p0, Vector2 p1, float s ) { ( this.p0, this.p1, this.s ) = ( p0, p1, s ); a = 0; + p = default; delta = default; + arcLenSampleOffset = default; evaluability = Evaluability.Unknown; } /// Evaluates a position on this catenary curve, given a t-value from 0 to 1 /// A value from 0 to 1 along the whole curve - public Vector2 Eval( float t ) { + public Vector2 Eval( float t ) => EvalByArcLength( t * s ); + + /// Evaluates a position on this catenary curve at the given arc length of sEval + /// The arc length along the curve to sample, relative to the first point + public Vector2 EvalByArcLength( float sEval ) { ReadyForEvaluation(); + return evaluability switch { + Evaluability.Catenary => EvalCatPosByArcLength( sEval ), + Evaluability.LineSegment => EvalStraightLineByArcLength( sEval ), + Evaluability.LinearVertical => EvalVerticalLinearApproxByArcLength( sEval ), + Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) + }; + } - if( evaluability == Evaluability.LineSegment ) - return Vector3.LerpUnclamped( p0, p1, t ); // chain is almost completely linear + // straight line from p0 to p1 + Vector2 EvalStraightLineByArcLength( float sEval ) => Vector3.LerpUnclamped( p0, p1, sEval / s ); - Vector2 p = p1 - p0; - float x = Mathfs.Lerp( 0, p.x, t ); - float y; - if( evaluability == Evaluability.LinearVertical ) { // chain is almost completely vertical, use a linear approximation - float ts = t * s; - float seg0 = ( s - p.y ) / 2; - y = ( ts < seg0 ) ? -ts : -2 * seg0 + ts; - } else { - y = EvalFrom0( x ); - } + // almost completely vertical line when p0.x is approx. equal to p1.x + Vector2 EvalVerticalLinearApproxByArcLength( float sEval ) { + float x = Mathfs.Lerp( 0, p.x, sEval / s ); // just to make it not snap to x=0 + float b = ( p.y - s ) / 2; // bottom + float seg0 = -b; + float y = ( sEval < seg0 ) ? -sEval : -2 * seg0 + sEval; + return new Vector2( x, y ) + p0; + } + // evaluates the position of the catenary at the given arc length, relative to the first point + Vector2 EvalCatPosByArcLength( float sEval ) { + float x = EvalCatXByArcLengthPassingThrough0( sEval ); + float y = EvalPassingThrough0( x ); return new Vector2( x, y ) + p0; } - // Passing through (0,0) and point p - float EvalFrom0( float x ) => a * Mathfs.Cosh( ( x - delta.x ) / a ) + delta.y; + float EvalCatXByArcLengthPassingThrough0( float sEval ) { + sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x + return Catenary2D.EvalXByArcLength( sEval + arcLenSampleOffset, a ) + delta.x; + } - bool IsFullyVertical( float dx ) => Mathf.Abs( dx ) < 0.001f; - bool IsStraightLine() => s <= Vector2.Distance( p0, p1 ) * 1.00005f; + // Evaluate passing through the origin and p + float EvalPassingThrough0( float x ) => Catenary2D.Eval( x - delta.x, a ) + delta.y; + // calculates p, a, delta, arcLenSampleOffset, and which evaluation method to use void ReadyForEvaluation() { if( evaluability != Evaluability.Unknown ) return; + // cache p, ie: p1 relative to p0 + p = p1 - p0; + // CASE 1: // first, test if it's a line segment - if( IsStraightLine() ) { + if( IsStraightLine ) { evaluability = Evaluability.LineSegment; return; } - // relative to origin point p - Vector2 p = p1 - p0; - // CASE 2: // check if it's basically a fully vertical hanging chain - if( IsFullyVertical( p.x ) ) { + if( IsVertical ) { evaluability = Evaluability.LinearVertical; return; } @@ -122,6 +158,7 @@ void ReadyForEvaluation() { RootFindBisections( R, ref xRange, BISECT_REFINE_COUNT ); // Catenary seems valid, with roots inside, refine the range a = xRange.Center; // set a to the middle of the latest range delta = CalcCatenaryDelta( a, p ); // find delta to pass through both points + arcLenSampleOffset = CalcArcLenSampleOffset( delta.x, a ); evaluability = Evaluability.Catenary; } else { // CASE 4: @@ -130,11 +167,14 @@ void ReadyForEvaluation() { } } + // Calculates the arc length offset so that it's relative to the start of the chain when evaluating by arc length + static float CalcArcLenSampleOffset( float deltaX, float a ) => Catenary2D.EvalArcLen( -deltaX, a ); + // Calculates the required offset to make a catenary pass through the origin and a point p static Vector2 CalcCatenaryDelta( float a, Vector2 p ) { Vector2 d; d.x = p.x / 2 - a * Mathfs.Asinh( p.y / ( 2 * a * Mathfs.Sinh( p.x / ( 2 * a ) ) ) ); - d.y = -a * Mathfs.Cosh( -d.x / a ); + d.y = -Catenary2D.Eval( d.x, a ); // technically -d.x but because of symmetry d.x works too return d; } From 73710f551ba7ca6238dc1755194d4850bc957c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 1 Jan 2023 14:21:59 +0100 Subject: [PATCH 184/301] formatting --- Runtime/Curves/Catenary2D.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index 0bda2f8..66ff7e4 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -42,14 +42,14 @@ enum Evaluability { // data Vector2 p0, p1; float s; - + // cached state float a; Vector2 p; Vector2 delta; float arcLenSampleOffset; Evaluability evaluability; - + public float Length { get => s; set => ( s, evaluability ) = ( value, Evaluability.Unknown ); From 28087b4cdd6101fb26570f53ad74f268942ab8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 2 Jan 2023 13:11:43 +0100 Subject: [PATCH 185/301] fixed package manger git urls in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7423d3a..f848811 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ There are several ways to install this library into your project: - Clone or [download](https://github.com/FreyaHolmer/Mathfs/archive/refs/heads/master.zip) this repository and put it somewhere in the Assets folder of your Unity project - **Unity Package Manager (UPM)**: - Add either of the the following lines to *Packages/manifest.json*: - - `"com.acegikmo.mathfs": "https://github.com/FreyaHolmer/Mathfs#0.1.0",` if you want to target a specific version (recommended) - - `"com.acegikmo.mathfs": "https://github.com/FreyaHolmer/Mathfs",` if you want to pull the latest commit (potentially unstable) + - `"com.acegikmo.mathfs": "https://github.com/FreyaHolmer/Mathfs.git#0.1.0",` if you want to target a specific version (recommended) + - `"com.acegikmo.mathfs": "https://github.com/FreyaHolmer/Mathfs.git",` if you want to pull the latest commit (potentially unstable) - More information about UPM and git [here](https://docs.unity3d.com/Manual/upm-git.html) - **[OpenUPM](https://openupm.com)** - After installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command: From 4742b1fd77f59bccddcdc77b3316e8abe5d0ab4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 2 Jan 2023 20:12:36 +0100 Subject: [PATCH 186/301] replaced all Unity's Mathf calls with .NETs MathF Unity's Mathf is largely no longer used as of this commit. This might have performance improvements because it's no longer converting all calls from float to double, but it might have potential precision loss implications. Hopefully it won't be anything too bad --- Runtime/Curves/Catenary2D.cs | 10 +-- Runtime/Curves/GenericTrajectory2D.cs | 3 +- Runtime/Curves/IParamCurve.cs | 7 +- Runtime/Curves/Polynomial.cs | 18 ++--- Runtime/Curves/Polynomial2D.cs | 2 +- Runtime/Curves/Polynomial3D.cs | 2 +- Runtime/Curves/Polynomial4D.cs | 2 +- Runtime/Extensions.cs | 26 +++---- Runtime/Geometric Algebra/Bivector3.cs | 2 +- Runtime/Geometric Algebra/Rotor3.cs | 3 +- Runtime/Geometric Shapes/Circle.cs | 5 +- Runtime/Geometric Shapes/ILinear2D.cs | 3 +- Runtime/Geometric Shapes/ILinear3D.cs | 3 +- Runtime/Geometric Shapes/Polygon.cs | 12 +-- Runtime/Geometric Shapes/Triangle.cs | 10 +-- Runtime/IntersectionTestCore.cs | 5 +- Runtime/Mathfs.cs | 102 ++++++++++++------------- Runtime/Numerics/FloatRange.cs | 8 +- 18 files changed, 115 insertions(+), 108 deletions(-) diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index 66ff7e4..be3815a 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -63,7 +63,7 @@ public Vector2 P1 { set => ( p1, evaluability ) = ( value, Evaluability.Unknown ); } - public bool IsVertical => Mathf.Abs( p.x ) < 0.001f; + public bool IsVertical => MathF.Abs( p.x ) < 0.001f; public bool IsStraightLine => s <= Vector2.Distance( p0, p1 ) * 1.00005f; /// Creates a catenary curve between two points, given an arc length s @@ -146,7 +146,7 @@ void ReadyForEvaluation() { // CASE 3: // Now we've got a catenary on our hands unless something explodes. - float c = Mathf.Sqrt( s * s - p.y * p.y ); + float c = MathF.Sqrt( s * s - p.y * p.y ); float pAbsX = p.x.Abs(); // solve only in x > 0 float R( float a ) => 2 * a * Mathfs.Sinh( pAbsX / ( 2 * a ) ) - c; // set up root solve function @@ -154,7 +154,7 @@ void ReadyForEvaluation() { float xRoot = ( p.x * p.x ) / ( 2 * s ); // intial guess based on freya's flawless heuristics if( TryFindRootBounds( R, xRoot, out FloatRange xRange ) ) { // refine range, if necessary (which is very likely) - if( Mathf.Approximately( xRange.Length, 0 ) == false ) + if( Mathfs.Approximately( xRange.Length, 0 ) == false ) RootFindBisections( R, ref xRange, BISECT_REFINE_COUNT ); // Catenary seems valid, with roots inside, refine the range a = xRange.Center; // set a to the middle of the latest range delta = CalcCatenaryDelta( a, p ); // find delta to pass through both points @@ -193,7 +193,7 @@ static bool TryFindRootBounds( Func R, float g, out FloatRange xRa // It's positive - we found our lower bound // exponentially search for upper bound xRange.a = xRange.b; - xRange.b = g * Mathf.Pow( 2, n ); + xRange.b = g * MathF.Pow( 2, n ); y = R( xRange.b ); if( y < 0 ) return true; // upper bound found! @@ -201,7 +201,7 @@ static bool TryFindRootBounds( Func R, float g, out FloatRange xRa // It's negative - we found our upper bound // exponentially search for lower bound xRange.b = xRange.a; - xRange.a = g * Mathf.Pow( 2, -n ); + xRange.a = g * MathF.Pow( 2, -n ); y = R( xRange.a ); if( y > 0 ) return true; // lower bound found! diff --git a/Runtime/Curves/GenericTrajectory2D.cs b/Runtime/Curves/GenericTrajectory2D.cs index 1d5f3f0..3a9e42f 100644 --- a/Runtime/Curves/GenericTrajectory2D.cs +++ b/Runtime/Curves/GenericTrajectory2D.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using Freya; using UnityEngine; @@ -12,7 +13,7 @@ public class GenericTrajectory2D { public Vector2 GetPosition( float time ) { Vector2 pt = derivatives[0]; for( int i = 1; i < derivatives.Length; i++ ) { - float scale = Mathfs.Pow( time, i ) / Mathfs.Factorial( (uint)i ); + float scale = MathF.Pow( time, i ) / Mathfs.Factorial( (uint)i ); pt += scale * derivatives[i]; } diff --git a/Runtime/Curves/IParamCurve.cs b/Runtime/Curves/IParamCurve.cs index 4e1691e..320d5c7 100644 --- a/Runtime/Curves/IParamCurve.cs +++ b/Runtime/Curves/IParamCurve.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using System.Runtime.CompilerServices; using UnityEngine; using static Freya.Mathfs; @@ -73,7 +74,7 @@ public static float GetArcLength( this T curve, FloatRange interval, int accu Vector2 p = curve.Eval( unit ? t : interval.Lerp( t ) ); float dx = p.x - prev.x; float dy = p.y - prev.y; - totalDist += Mathf.Sqrt( dx * dx + dy * dy ); + totalDist += MathF.Sqrt( dx * dx + dy * dy ); prev = p; } @@ -101,7 +102,7 @@ public static float GetArcLength( this T curve, FloatRange interval, int accu float dx = p.x - prev.x; float dy = p.y - prev.y; float dz = p.z - prev.z; - totalDist += Mathf.Sqrt( dx * dx + dy * dy + dz * dz ); + totalDist += MathF.Sqrt( dx * dx + dy * dy + dz * dz ); prev = p; } @@ -211,7 +212,7 @@ public static class IParamCurve2DiffExt3D { /// Returns a pseudovector at the given t-value on the curve, where the magnitude is the curvature in radians per distance unit, and the direction is the axis of curvature /// The t-value along the curve to sample - [MethodImpl( INLINE )] public static Vector3 EvalCurvature( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetCurvature( curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); + [MethodImpl( INLINE )] public static Bivector3 EvalCurvature( this T curve, float t ) where T : IParamCurve2Diff => Mathfs.GetCurvature( curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); /// [MethodImpl( INLINE )] public static Circle3D EvalOsculatingCircle( this T curve, float t ) where T : IParamCurve2Diff => Circle3D.GetOsculatingCircle( curve.Eval( t ), curve.EvalDerivative( t ), curve.EvalSecondDerivative( t ) ); diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index 1984e84..c746216 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -291,10 +291,10 @@ static ResultsMax2 SolveQuadraticRoots( float a, float b, float c ) { return new ResultsMax2( -b / ( 2 * a ) ); // two equivalent solutions at one point if( rootContent >= 0 ) { - float root = Mathf.Sqrt( rootContent ); + float root = MathF.Sqrt( rootContent ); float r0 = ( -b - root ) / ( 2 * a ); // crosses at two points float r1 = ( -b + root ) / ( 2 * a ); - return new ResultsMax2( Mathf.Min( r0, r1 ), Mathf.Max( r0, r1 ) ); + return new ResultsMax2( MathF.Min( r0, r1 ), MathF.Max( r0, r1 ) ); } return default; // no roots @@ -326,10 +326,10 @@ static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { return new ResultsMax3( Mathfs.Cbrt( -q ) ); float discriminant = 4 * p * p * p + 27 * q * q; if( discriminant < 0.00001 ) { // two or three roots guaranteed, use trig solution - float pre = 2 * Mathf.Sqrt( -p / 3 ); - float acosInner = ( ( 3 * q ) / ( 2 * p ) ) * Mathf.Sqrt( -3 / p ); + float pre = 2 * MathF.Sqrt( -p / 3 ); + float acosInner = ( ( 3 * q ) / ( 2 * p ) ) * MathF.Sqrt( -3 / p ); - float GetRoot( int k ) => pre * Mathf.Cos( ( 1f / 3f ) * Mathfs.Acos( acosInner.ClampNeg1to1() ) - ( Mathfs.TAU / 3f ) * k ); + float GetRoot( int k ) => pre * MathF.Cos( ( 1f / 3f ) * Mathfs.Acos( acosInner.ClampNeg1to1() ) - ( Mathfs.TAU / 3f ) * k ); // if acos hits 0 or TAU/2, the offsets will have the same value, // which means we have a double root plus one regular root on our hands if( acosInner >= 0.9999f ) @@ -340,14 +340,14 @@ static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { } if( discriminant > 0 && p < 0 ) { // one root - float coshInner = ( 1f / 3f ) * Mathfs.Acosh( ( -3 * q.Abs() / ( 2 * p ) ) * Mathf.Sqrt( -3 / p ) ); - float r = -2 * Mathfs.Sign( q ) * Mathf.Sqrt( -p / 3 ) * Mathfs.Cosh( coshInner ); + float coshInner = ( 1f / 3f ) * Mathfs.Acosh( ( -3 * q.Abs() / ( 2 * p ) ) * MathF.Sqrt( -3 / p ) ); + float r = -2 * Mathfs.Sign( q ) * MathF.Sqrt( -p / 3 ) * Mathfs.Cosh( coshInner ); return new ResultsMax3( r ); } if( p > 0 ) { // one root - float sinhInner = ( 1f / 3f ) * Mathfs.Asinh( ( ( 3 * q ) / ( 2 * p ) ) * Mathf.Sqrt( 3 / p ) ); - float r = ( -2 * Mathf.Sqrt( p / 3 ) ) * Mathfs.Sinh( sinhInner ); + float sinhInner = ( 1f / 3f ) * Mathfs.Asinh( ( ( 3 * q ) / ( 2 * p ) ) * MathF.Sqrt( 3 / p ) ); + float r = ( -2 * MathF.Sqrt( p / 3 ) ) * Mathfs.Sinh( sinhInner ); return new ResultsMax3( r ); } diff --git a/Runtime/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs index abca150..00e50e5 100644 --- a/Runtime/Curves/Polynomial2D.cs +++ b/Runtime/Curves/Polynomial2D.cs @@ -71,7 +71,7 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2 ) { #region IParamCurve3Diff interface implementations - public int Degree => Mathf.Max( (int)x.Degree, (int)y.Degree ); + public int Degree => Mathfs.Max( x.Degree, y.Degree ); public Vector2 EvalDerivative( float t ) => Differentiate().Eval( t ); public Vector2 EvalSecondDerivative( float t ) => Differentiate( 2 ).Eval( t ); public Vector2 EvalThirdDerivative( float t = 0 ) => Differentiate( 3 ).Eval( 0 ); diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index 71d4ae9..3b82d79 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -81,7 +81,7 @@ public Polynomial3D( Vector3 c0, Vector3 c1 ) { #region IParamCurve3Diff interface implementations - public int Degree => Mathf.Max( (int)x.Degree, (int)y.Degree, (int)z.Degree ); + public int Degree => Mathfs.Max( x.Degree, y.Degree, z.Degree ); public Vector3 EvalDerivative( float t ) => Differentiate().Eval( t ); public Vector3 EvalSecondDerivative( float t ) => Differentiate( 2 ).Eval( t ); public Vector3 EvalThirdDerivative( float t = 0 ) => Differentiate( 3 ).Eval( 0 ); diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index 7a4f1f5..7d60f46 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -78,7 +78,7 @@ public Polynomial4D( Vector4 c0, Vector4 c1, Vector4 c2 ) { #region IParamCurve3Diff interface implementations - public int Degree => Mathf.Max( x.Degree, y.Degree, z.Degree, w.Degree ); + public int Degree => Mathfs.Max( x.Degree, y.Degree, z.Degree, w.Degree ); public Vector4 EvalDerivative( float t ) => Differentiate().Eval( t ); public Vector4 EvalSecondDerivative( float t ) => Differentiate( 2 ).Eval( t ); public Vector4 EvalThirdDerivative( float t = 0 ) => Differentiate( 3 ).Eval( 0 ); diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index c94ce46..3bbaa0e 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -16,7 +16,7 @@ public static class MathfsExtensions { /// Returns the angle of this vector, in radians /// The vector to get the angle of. It does not have to be normalized /// - [MethodImpl( INLINE )] public static float Angle( this Vector2 v ) => Mathf.Atan2( v.y, v.x ); + [MethodImpl( INLINE )] public static float Angle( this Vector2 v ) => MathF.Atan2( v.y, v.x ); /// Rotates the vector 90 degrees clockwise (negative Z axis rotation) [MethodImpl( INLINE )] public static Vector2 Rotate90CW( this Vector2 v ) => new Vector2( v.y, -v.x ); @@ -34,8 +34,8 @@ public static class MathfsExtensions { /// The vector to rotate /// The angle to rotate by, in radians public static Vector2 Rotate( this Vector2 v, float angRad ) { - float ca = Mathf.Cos( angRad ); - float sa = Mathf.Sin( angRad ); + float ca = MathF.Cos( angRad ); + float sa = MathF.Sin( angRad ); return new Vector2( ca * v.x - sa * v.y, sa * v.x + ca * v.y ); } @@ -207,8 +207,8 @@ public static Quaternion Rotate180Around( this Quaternion q, Axis axis, Rotation /// The rotation space of the axis, if it should be intrinsic/self/local or extrinsic/"world" public static Quaternion RotateAround( this Quaternion q, Axis axis, float angRad, RotationSpace space = RotationSpace.Self ) { float aHalf = angRad / 2; - float c = Mathf.Cos( aHalf ); - float s = Mathf.Sin( aHalf ); + float c = MathF.Cos( aHalf ); + float s = MathF.Sin( aHalf ); float xc = q.x * c; float yc = q.y * c; float zc = q.z * c; @@ -437,10 +437,10 @@ public static Quaternion Exp( this Quaternion q ) { /// The rectangle to expand /// The point to encapsulate public static Rect Encapsulate( this Rect r, Vector2 p ) { - r.xMax = Mathf.Max( r.xMax, p.x ); - r.xMin = Mathf.Min( r.xMin, p.x ); - r.yMax = Mathf.Max( r.yMax, p.y ); - r.yMin = Mathf.Min( r.yMin, p.y ); + r.xMax = MathF.Max( r.xMax, p.x ); + r.xMin = MathF.Min( r.xMin, p.x ); + r.yMax = MathF.Max( r.yMax, p.y ); + r.yMin = MathF.Min( r.yMin, p.y ); return r; } @@ -560,7 +560,7 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => #region Math operations /// - [MethodImpl( INLINE )] public static float Sqrt( this float value ) => Mathfs.Sqrt( value ); + [MethodImpl( INLINE )] public static float Sqrt( this float value ) => MathF.Sqrt( value ); /// [MethodImpl( INLINE )] public static Vector2 Sqrt( this Vector2 value ) => Mathfs.Sqrt( value ); @@ -572,10 +572,10 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => [MethodImpl( INLINE )] public static Vector4 Sqrt( this Vector4 value ) => Mathfs.Sqrt( value ); /// - [MethodImpl( INLINE )] public static float Cbrt( this float value ) => Mathfs.Cbrt( value ); + [MethodImpl( INLINE )] public static float Cbrt( this float value ) => MathF.Cbrt( value ); /// - [MethodImpl( INLINE )] public static float Pow( this float value, float exponent ) => Mathfs.Pow( value, exponent ); + [MethodImpl( INLINE )] public static float Pow( this float value, float exponent ) => MathF.Pow( value, exponent ); /// Calculates exact positive integer powers /// @@ -611,7 +611,7 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => #region Absolute Values /// - [MethodImpl( INLINE )] public static float Abs( this float value ) => Mathfs.Abs( value ); + [MethodImpl( INLINE )] public static float Abs( this float value ) => MathF.Abs( value ); /// [MethodImpl( INLINE )] public static int Abs( this int value ) => Mathfs.Abs( value ); diff --git a/Runtime/Geometric Algebra/Bivector3.cs b/Runtime/Geometric Algebra/Bivector3.cs index 8252f97..638b52d 100644 --- a/Runtime/Geometric Algebra/Bivector3.cs +++ b/Runtime/Geometric Algebra/Bivector3.cs @@ -24,7 +24,7 @@ public Bivector3( Vector3 a, Vector3 b ) { this.xy = bv.xy; } - public float Magnitude => Mathf.Sqrt( SqrMagnitude ); + public float Magnitude => MathF.Sqrt( SqrMagnitude ); public Bivector3 Normalized => new Bivector3( yz, zx, xy ) / Magnitude; public Vector3 Normal => new Vector3( yz, zx, xy ) / Magnitude; public float SqrMagnitude => yz * yz + zx * zx + xy * xy; diff --git a/Runtime/Geometric Algebra/Rotor3.cs b/Runtime/Geometric Algebra/Rotor3.cs index 6d4933a..ccc9b61 100644 --- a/Runtime/Geometric Algebra/Rotor3.cs +++ b/Runtime/Geometric Algebra/Rotor3.cs @@ -1,3 +1,4 @@ +using System; using UnityEngine; namespace Freya { @@ -27,7 +28,7 @@ public Rotor3( float r, Bivector3 b ) { this.b = b; } - public float Magnitude => Mathf.Sqrt( SqrMagnitude ); + public float Magnitude => MathF.Sqrt( SqrMagnitude ); public float SqrMagnitude => r * r + b.SqrMagnitude; public Rotor3 Normalized() => this / Magnitude; diff --git a/Runtime/Geometric Shapes/Circle.cs b/Runtime/Geometric Shapes/Circle.cs index eff10aa..b093bbc 100644 --- a/Runtime/Geometric Shapes/Circle.cs +++ b/Runtime/Geometric Shapes/Circle.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using System.Runtime.CompilerServices; using UnityEngine; using static Freya.Mathfs; @@ -226,10 +227,10 @@ public static bool FromPointTangentPoint( Vector3 startPt, Vector3 startTangent, if( Vector3.Dot( xAxis, startTangent ).Abs() < 0.9999f ) { float h = d / 2; float ang = AngleBetween( xAxis, startTangent ); - float fh = h * Mathf.Tan( ang + TAU / 4 ); + float fh = h * MathF.Tan( ang + TAU / 4 ); float x2D = h; float y2D = fh; - float r = Mathf.Sqrt( h * h + fh * fh ); + float r = MathF.Sqrt( h * h + fh * fh ); Vector3 normal = Vector3.Cross( xAxis, startTangent ).normalized; Vector3 yAxis = Vector3.Cross( normal, xAxis ); Vector3 center = startPt + xAxis * x2D + yAxis * y2D; diff --git a/Runtime/Geometric Shapes/ILinear2D.cs b/Runtime/Geometric Shapes/ILinear2D.cs index 0f4fc12..fea1811 100644 --- a/Runtime/Geometric Shapes/ILinear2D.cs +++ b/Runtime/Geometric Shapes/ILinear2D.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using System.Runtime.CompilerServices; using UnityEngine; @@ -45,7 +46,7 @@ public static class ExtILinear2D { /// The shortest distance from this line to a point /// The linear object to check distance from (Ray2D, Line2D or LineSegment2D) /// The point to check the distance to - [MethodImpl( INLINE )] public static float Distance( this T linear, Vector2 point ) where T : ILinear2D => Mathfs.Sqrt( DistanceSqr( linear, point ) ); + [MethodImpl( INLINE )] public static float Distance( this T linear, Vector2 point ) where T : ILinear2D => MathF.Sqrt( DistanceSqr( linear, point ) ); /// The shortest squared distance from this line to a point /// The linear object to check distance from (Ray2D, Line2D or LineSegment2D) diff --git a/Runtime/Geometric Shapes/ILinear3D.cs b/Runtime/Geometric Shapes/ILinear3D.cs index 626a7c5..d481f93 100644 --- a/Runtime/Geometric Shapes/ILinear3D.cs +++ b/Runtime/Geometric Shapes/ILinear3D.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using System.Runtime.CompilerServices; using UnityEngine; @@ -55,7 +56,7 @@ public static class ExtILinear3D { /// The shortest distance from this line to a point /// The linear object to check distance from (Ray3D, Line3D or LineSegment3D) /// The point to check the distance to - [MethodImpl( INLINE )] public static float Distance( this T linear, Vector3 point ) where T : ILinear3D => Mathfs.Sqrt( DistanceSqr( linear, point ) ); + [MethodImpl( INLINE )] public static float Distance( this T linear, Vector3 point ) where T : ILinear3D => MathF.Sqrt( DistanceSqr( linear, point ) ); /// The shortest squared distance from this line to a point /// The linear object to check distance from (Ray3D, Line3D or LineSegment3D) diff --git a/Runtime/Geometric Shapes/Polygon.cs b/Runtime/Geometric Shapes/Polygon.cs index 7ec1ecd..43839c4 100644 --- a/Runtime/Geometric Shapes/Polygon.cs +++ b/Runtime/Geometric Shapes/Polygon.cs @@ -28,7 +28,7 @@ public class Polygon { public bool IsClockwise => SignedArea > 0; /// Returns the area of this polygon - public float Area => Mathf.Abs( SignedArea ); + public float Area => MathF.Abs( SignedArea ); /// Returns the signed area of this polygon public float SignedArea { @@ -55,7 +55,7 @@ public float Perimeter { Vector2 b = points[( i + 1 ) % count]; float dx = a.x - b.x; float dy = a.y - b.y; - totalDist += Mathf.Sqrt( dx * dx + dy * dy ); // unrolled for speed + totalDist += MathF.Sqrt( dx * dx + dy * dy ); // unrolled for speed } return totalDist; @@ -70,10 +70,10 @@ public Rect Bounds { float xMin = p.x, xMax = p.x, yMin = p.y, yMax = p.y; for( int i = 1; i < count; i++ ) { p = points[i]; - xMin = Mathf.Min( xMin, p.x ); - xMax = Mathf.Max( xMax, p.x ); - yMin = Mathf.Min( yMin, p.y ); - yMax = Mathf.Max( yMax, p.y ); + xMin = MathF.Min( xMin, p.x ); + xMax = MathF.Max( xMax, p.x ); + yMin = MathF.Min( yMin, p.y ); + yMax = MathF.Max( yMax, p.y ); } return new Rect( xMin, yMin, xMax - xMin, yMax - yMin ); diff --git a/Runtime/Geometric Shapes/Triangle.cs b/Runtime/Geometric Shapes/Triangle.cs index 6e0c4c7..6fe3191 100644 --- a/Runtime/Geometric Shapes/Triangle.cs +++ b/Runtime/Geometric Shapes/Triangle.cs @@ -100,7 +100,7 @@ public Vector2 this[ int i ] { public partial struct Triangle2D { /// The area of the triangle - public float Area => Mathf.Abs( SignedArea ); + public float Area => MathF.Abs( SignedArea ); // todo: verify clockwise vs ccw /// The signed area of the triangle. When the triangle is defined clockwise, the area will be negative @@ -323,8 +323,8 @@ public float GetAngle( int index ) { Vector2 abDir = ( b - a ).normalized; Vector2 acDir = ( c - a ).normalized; Vector2 bcDir = ( c - b ).normalized; - float angA = Mathf.Acos( Vector2.Dot( abDir, acDir ).ClampNeg1to1() ); - float angB = Mathf.Acos( Vector2.Dot( -abDir, bcDir ).ClampNeg1to1() ); + float angA = MathF.Acos( Vector2.Dot( abDir, acDir ).ClampNeg1to1() ); + float angB = MathF.Acos( Vector2.Dot( -abDir, bcDir ).ClampNeg1to1() ); float angC = PI - angA - angB; return ( angA, angB, angC ); } @@ -375,8 +375,8 @@ public float GetAngle( int index ) { Vector2 abDir = ( b - a ).normalized; Vector2 acDir = ( c - a ).normalized; Vector2 bcDir = ( c - b ).normalized; - float angA = Mathf.Acos( Vector2.Dot( abDir, acDir ).ClampNeg1to1() ); - float angB = Mathf.Acos( Vector2.Dot( -abDir, bcDir ).ClampNeg1to1() ); + float angA = MathF.Acos( Vector2.Dot( abDir, acDir ).ClampNeg1to1() ); + float angB = MathF.Acos( Vector2.Dot( -abDir, bcDir ).ClampNeg1to1() ); float angC = PI - angA - angB; return ( angA, angB, angC ); } diff --git a/Runtime/IntersectionTestCore.cs b/Runtime/IntersectionTestCore.cs index bfabdd4..4437ff7 100644 --- a/Runtime/IntersectionTestCore.cs +++ b/Runtime/IntersectionTestCore.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using System.Runtime.CompilerServices; using UnityEngine; using static Freya.Mathfs; @@ -70,7 +71,7 @@ public static ResultsMax2 CirclesIntersectionPoints( Vector2 aPos, floa bool differentPosition = dist > 0.00001f; float maxRad = Max( aRadius, bRadius ); float minRad = Min( aRadius, bRadius ); - bool ringsTouching = Mathf.Abs( dist - maxRad ) < minRad; + bool ringsTouching = MathF.Abs( dist - maxRad ) < minRad; if( ringsTouching && differentPosition ) { float aRadSq = aRadius * aRadius; @@ -100,7 +101,7 @@ public static bool CirclesOverlap( Vector2 aPos, float aRadius, Vector2 bPos, fl float dist = Vector2.Distance( aPos, bPos ); float maxRad = Max( aRadius, bRadius ); float minRad = Min( aRadius, bRadius ); - return Mathf.Abs( dist - maxRad ) < minRad; + return MathF.Abs( dist - maxRad ) < minRad; } /// Returns whether or not a line passes through a box centered at (0,0) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 38a9849..91c143a 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -50,7 +50,7 @@ public static class Mathfs { #region Math operations /// Returns the square root of the given value - [MethodImpl( INLINE )] public static float Sqrt( float value ) => (float)Math.Sqrt( value ); + [MethodImpl( INLINE )] public static float Sqrt( float value ) => MathF.Sqrt( value ); /// Returns the square root of each component [MethodImpl( INLINE )] public static Vector2 Sqrt( Vector2 v ) => new Vector2( Sqrt( v.x ), Sqrt( v.y ) ); @@ -62,22 +62,22 @@ public static class Mathfs { [MethodImpl( INLINE )] public static Vector4 Sqrt( Vector4 v ) => new Vector4( Sqrt( v.x ), Sqrt( v.y ), Sqrt( v.z ), Sqrt( v.w ) ); /// Returns the cube root of the given value, properly handling negative values unlike Pow(v,1/3) - [MethodImpl( INLINE )] public static float Cbrt( float value ) => value < 0 ? -Pow( -value, 1f / 3f ) : Pow( value, 1f / 3f ); + [MethodImpl( INLINE )] public static float Cbrt( float value ) => MathF.Cbrt( value ); /// Returns value raised to the power of exponent - [MethodImpl( INLINE )] public static float Pow( float value, float exponent ) => (float)Math.Pow( value, exponent ); + [MethodImpl( INLINE )] public static float Pow( float value, float exponent ) => MathF.Pow( value, exponent ); /// Returns e to the power of the given value - [MethodImpl( INLINE )] public static float Exp( float power ) => (float)Math.Exp( power ); + [MethodImpl( INLINE )] public static float Exp( float power ) => MathF.Exp( power ); /// Returns the logarithm of a value, with the given base - [MethodImpl( INLINE )] public static float Log( float value, float @base ) => (float)Math.Log( value, @base ); + [MethodImpl( INLINE )] public static float Log( float value, float @base ) => MathF.Log( value, @base ); /// Returns the natural logarithm of the given value - [MethodImpl( INLINE )] public static float Log( float value ) => (float)Math.Log( value ); + [MethodImpl( INLINE )] public static float Log( float value ) => MathF.Log( value ); /// Returns the base 10 logarithm of the given value - [MethodImpl( INLINE )] public static float Log10( float value ) => (float)Math.Log10( value ); + [MethodImpl( INLINE )] public static float Log10( float value ) => MathF.Log10( value ); /// Returns the binomial coefficient n over k public static ulong BinomialCoef( uint n, uint k ) { @@ -189,56 +189,56 @@ public static ulong BinomialCoef( uint n, uint k ) { /// Returns the cosine of the given angle. Equivalent to the x-component of a unit vector with the same angle /// Angle in radians - [MethodImpl( INLINE )] public static float Cos( float angRad ) => (float)Math.Cos( angRad ); + [MethodImpl( INLINE )] public static float Cos( float angRad ) => MathF.Cos( angRad ); /// Returns the sine of the given angle. Equivalent to the y-component of a unit vector with the same angle /// Angle in radians - [MethodImpl( INLINE )] public static float Sin( float angRad ) => (float)Math.Sin( angRad ); + [MethodImpl( INLINE )] public static float Sin( float angRad ) => MathF.Sin( angRad ); /// Returns the tangent of the given angle /// Angle in radians - [MethodImpl( INLINE )] public static float Tan( float angRad ) => (float)Math.Tan( angRad ); + [MethodImpl( INLINE )] public static float Tan( float angRad ) => MathF.Tan( angRad ); /// Returns the arc cosine of the given value, in radians /// A value between -1 and 1 - [MethodImpl( INLINE )] public static float Acos( float value ) => (float)Math.Acos( value ); + [MethodImpl( INLINE )] public static float Acos( float value ) => MathF.Acos( value ); /// Returns the arc sine of the given value, in radians /// A value between -1 and 1 - [MethodImpl( INLINE )] public static float Asin( float value ) => (float)Math.Asin( value ); + [MethodImpl( INLINE )] public static float Asin( float value ) => MathF.Asin( value ); /// Returns the arc tangent of the given value, in radians /// A value between -1 and 1 - [MethodImpl( INLINE )] public static float Atan( float value ) => (float)Math.Atan( value ); + [MethodImpl( INLINE )] public static float Atan( float value ) => MathF.Atan( value ); /// Returns the angle of a vector. I don't recommend using this function, it's confusing~ Use Mathfs.DirToAng instead /// The y component of the vector. They're flipped yeah I know but this is how everyone implements if for some godforsaken reason /// The x component of the vector. They're flipped yeah I know but this is how everyone implements if for some godforsaken reason - [MethodImpl( INLINE )] public static float Atan2( float y, float x ) => (float)Math.Atan2( y, x ); + [MethodImpl( INLINE )] public static float Atan2( float y, float x ) => MathF.Atan2( y, x ); /// Returns the cosecant of the given angle /// Angle in radians - [MethodImpl( INLINE )] public static float Csc( float angRad ) => 1f / (float)Math.Sin( angRad ); + [MethodImpl( INLINE )] public static float Csc( float angRad ) => 1f / MathF.Sin( angRad ); /// Returns the secant of the given angle /// Angle in radians - [MethodImpl( INLINE )] public static float Sec( float angRad ) => 1f / (float)Math.Cos( angRad ); + [MethodImpl( INLINE )] public static float Sec( float angRad ) => 1f / MathF.Cos( angRad ); /// Returns the cotangent of the given angle /// Angle in radians - [MethodImpl( INLINE )] public static float Cot( float angRad ) => 1f / (float)Math.Tan( angRad ); + [MethodImpl( INLINE )] public static float Cot( float angRad ) => 1f / MathF.Tan( angRad ); /// Returns the versine of the given angle /// Angle in radians - [MethodImpl( INLINE )] public static float Ver( float angRad ) => 1 - (float)Math.Cos( angRad ); + [MethodImpl( INLINE )] public static float Ver( float angRad ) => 1 - MathF.Cos( angRad ); /// Returns the coversine of the given angle /// Angle in radians - [MethodImpl( INLINE )] public static float Cvs( float angRad ) => 1 - (float)Math.Sin( angRad ); + [MethodImpl( INLINE )] public static float Cvs( float angRad ) => 1 - MathF.Sin( angRad ); /// Returns the chord of the given angle /// Angle in radians - [MethodImpl( INLINE )] public static float Crd( float angRad ) => 2 * (float)Math.Sin( angRad / 2 ); + [MethodImpl( INLINE )] public static float Crd( float angRad ) => 2 * MathF.Sin( angRad / 2 ); const double SINC_W = 0.01; const double SINC_P_C2 = -1 / 6.0; @@ -285,22 +285,22 @@ public static double SincRcp( double x ) { #region Hyperbolic Trigonometry /// Returns the hyperbolic cosine of the given hyperbolic angle - [MethodImpl( INLINE )] public static float Cosh( float x ) => (float)Math.Cosh( x ); + [MethodImpl( INLINE )] public static float Cosh( float x ) => MathF.Cosh( x ); /// Returns the hyperbolic sine of the given hyperbolic angle - [MethodImpl( INLINE )] public static float Sinh( float x ) => (float)Math.Sinh( x ); + [MethodImpl( INLINE )] public static float Sinh( float x ) => MathF.Sinh( x ); /// Returns the hyperbolic tangent of the given hyperbolic angle - [MethodImpl( INLINE )] public static float Tanh( float x ) => (float)Math.Tanh( x ); + [MethodImpl( INLINE )] public static float Tanh( float x ) => MathF.Tanh( x ); /// Returns the hyperbolic arc cosine of the given value - [MethodImpl( INLINE )] public static float Acosh( float x ) => (float)Math.Acosh( x ); + [MethodImpl( INLINE )] public static float Acosh( float x ) => MathF.Acosh( x ); /// Returns the hyperbolic arc sine of the given value - [MethodImpl( INLINE )] public static float Asinh( float x ) => (float)Math.Asinh( x ); + [MethodImpl( INLINE )] public static float Asinh( float x ) => MathF.Asinh( x ); /// Returns the hyperbolic arc tangent of the given value - [MethodImpl( INLINE )] public static float Atanh( float x ) => (float)Math.Atanh( x ); + [MethodImpl( INLINE )] public static float Atanh( float x ) => MathF.Atanh( x ); #endregion @@ -556,16 +556,16 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => [MethodImpl( INLINE )] public static int SignWithZeroAsInt( float value, float zeroThreshold = 0.000001f ) => Abs( value ) < zeroThreshold ? 0 : SignAsInt( value ); /// Rounds the value down to the nearest integer - [MethodImpl( INLINE )] public static float Floor( float value ) => (float)Math.Floor( value ); + [MethodImpl( INLINE )] public static float Floor( float value ) => MathF.Floor( value ); /// Rounds the vector components down to the nearest integer - [MethodImpl( INLINE )] public static Vector2 Floor( Vector2 value ) => new Vector2( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ) ); + [MethodImpl( INLINE )] public static Vector2 Floor( Vector2 value ) => new Vector2( MathF.Floor( value.x ), MathF.Floor( value.y ) ); /// - [MethodImpl( INLINE )] public static Vector3 Floor( Vector3 value ) => new Vector3( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ), (float)Math.Floor( value.z ) ); + [MethodImpl( INLINE )] public static Vector3 Floor( Vector3 value ) => new Vector3( MathF.Floor( value.x ), MathF.Floor( value.y ), MathF.Floor( value.z ) ); /// - [MethodImpl( INLINE )] public static Vector4 Floor( Vector4 value ) => new Vector4( (float)Math.Floor( value.x ), (float)Math.Floor( value.y ), (float)Math.Floor( value.z ), (float)Math.Floor( value.w ) ); + [MethodImpl( INLINE )] public static Vector4 Floor( Vector4 value ) => new Vector4( MathF.Floor( value.x ), MathF.Floor( value.y ), MathF.Floor( value.z ), MathF.Floor( value.w ) ); /// Rounds the value down to the nearest integer, returning an int value [MethodImpl( INLINE )] public static int FloorToInt( float value ) => (int)Math.Floor( value ); @@ -577,16 +577,16 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => [MethodImpl( INLINE )] public static Vector3Int FloorToInt( Vector3 value ) => new Vector3Int( (int)Math.Floor( value.x ), (int)Math.Floor( value.y ), (int)Math.Floor( value.z ) ); /// Rounds the value up to the nearest integer - [MethodImpl( INLINE )] public static float Ceil( float value ) => (float)Math.Ceiling( value ); + [MethodImpl( INLINE )] public static float Ceil( float value ) => MathF.Ceiling( value ); /// Rounds the vector components up to the nearest integer - [MethodImpl( INLINE )] public static Vector2 Ceil( Vector2 value ) => new Vector2( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ) ); + [MethodImpl( INLINE )] public static Vector2 Ceil( Vector2 value ) => new Vector2( MathF.Ceiling( value.x ), MathF.Ceiling( value.y ) ); /// - [MethodImpl( INLINE )] public static Vector3 Ceil( Vector3 value ) => new Vector3( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ), (float)Math.Ceiling( value.z ) ); + [MethodImpl( INLINE )] public static Vector3 Ceil( Vector3 value ) => new Vector3( MathF.Ceiling( value.x ), MathF.Ceiling( value.y ), MathF.Ceiling( value.z ) ); /// - [MethodImpl( INLINE )] public static Vector4 Ceil( Vector4 value ) => new Vector4( (float)Math.Ceiling( value.x ), (float)Math.Ceiling( value.y ), (float)Math.Ceiling( value.z ), (float)Math.Ceiling( value.w ) ); + [MethodImpl( INLINE )] public static Vector4 Ceil( Vector4 value ) => new Vector4( MathF.Ceiling( value.x ), MathF.Ceiling( value.y ), MathF.Ceiling( value.z ), MathF.Ceiling( value.w ) ); /// Rounds the value up to the nearest integer, returning an int value [MethodImpl( INLINE )] public static int CeilToInt( float value ) => (int)Math.Ceiling( value ); @@ -601,16 +601,16 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => [MethodImpl( INLINE )] public static float Round( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)MathF.Round( value, midpointRounding ); /// Rounds the vector components to the nearest integer - [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ) ); + [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ) ); /// - [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ), (float)Math.Round( value.z, midpointRounding ) ); + [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ), MathF.Round( value.z, midpointRounding ) ); /// - [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( (float)Math.Round( value.x, midpointRounding ), (float)Math.Round( value.y, midpointRounding ), (float)Math.Round( value.z, midpointRounding ), (float)Math.Round( value.w, midpointRounding ) ); + [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ), MathF.Round( value.z, midpointRounding ), MathF.Round( value.w, midpointRounding ) ); /// Rounds the value to the nearest value, snapped to the given interval size - [MethodImpl( INLINE )] public static float Round( float value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)Math.Round( value / snapInterval, midpointRounding ) * snapInterval; + [MethodImpl( INLINE )] public static float Round( float value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => MathF.Round( value / snapInterval, midpointRounding ) * snapInterval; /// Rounds the vector components to the nearest value, snapped to the given interval size [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ) ); @@ -667,13 +667,13 @@ public static int Gcd( int a, int b ) { if( a == int.MinValue || b == int.MinValue ) { if( a == int.MinValue && b == int.MinValue ) return int.MinValue; // the only negative return value, bc we can't negate this number - int v = Mathf.Max( a, b ).Abs(); + int v = Max( a, b ).Abs(); return v & -v; } if( a == b ) return a.Abs(); - ( a, b ) = ( Mathf.Abs( a ), Mathf.Abs( b ) ); + ( a, b ) = ( Abs( a ), Abs( b ) ); while( a != 0 && b != 0 ) _ = a > b ? a %= b : b %= a; return a | b; @@ -858,14 +858,14 @@ public static Rect Lerp( Rect a, Rect b, float t ) { t switch { 0f => a, 1f => b, - _ => Mathf.Pow( a, 1 - t ) * Mathf.Pow( b, t ) + _ => MathF.Pow( a, 1 - t ) * MathF.Pow( b, t ) }; /// Inverse exponential interpolation, the multiplicative version of InverseLerp, useful for values such as scaling or zooming /// The start value /// The end value /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated - [MethodImpl( INLINE )] public static float InverseEerp( float a, float b, float v ) => Mathf.Log( a / v ) / Mathf.Log( a / b ); + [MethodImpl( INLINE )] public static float InverseEerp( float a, float b, float v ) => MathF.Log( a / v ) / MathF.Log( a / b ); #endregion @@ -876,9 +876,9 @@ public static Rect Lerp( Rect a, Rect b, float t ) { /// The value to move towards /// The maximum change that should be applied to the value public static float MoveTowards( float current, float target, float maxDelta ) { - if( Mathf.Abs( target - current ) <= maxDelta ) + if( MathF.Abs( target - current ) <= maxDelta ) return target; - return current + Mathf.Sign( target - current ) * maxDelta; + return current + MathF.Sign( target - current ) * maxDelta; } /// Gradually changes a value towards a desired goal over time. @@ -905,7 +905,7 @@ public static float SmoothDamp( float current, float target, ref float currentVe /// The time since the last call to this function. By default Time.deltaTime public static float SmoothDamp( float current, float target, ref float currentVelocity, float smoothTime, [Uei.DefaultValue( "Mathf.Infinity" )] float maxSpeed, [Uei.DefaultValue( "Time.deltaTime" )] float deltaTime ) { // Based on Game Programming Gems 4 Chapter 1.10 - smoothTime = Mathf.Max( 0.0001F, smoothTime ); + smoothTime = MathF.Max( 0.0001F, smoothTime ); float omega = 2F / smoothTime; float x = omega * deltaTime; @@ -915,7 +915,7 @@ public static float SmoothDamp( float current, float target, ref float currentVe // Clamp maximum speed float maxChange = maxSpeed * smoothTime; - change = Mathf.Clamp( change, -maxChange, maxChange ); + change = Clamp( change, -maxChange, maxChange ); target = current - change; float temp = ( currentVelocity + omega * change ) * deltaTime; @@ -1096,12 +1096,12 @@ public static Vector3 ClampMagnitude( Vector3 v, float min, float max ) { /// Returns the direction of the input angle, as a normalized vector /// The input angle, in radians /// - [MethodImpl( INLINE )] public static Vector2 AngToDir( float aRad ) => new Vector2( Mathf.Cos( aRad ), Mathf.Sin( aRad ) ); + [MethodImpl( INLINE )] public static Vector2 AngToDir( float aRad ) => new Vector2( MathF.Cos( aRad ), MathF.Sin( aRad ) ); /// Returns the angle of the input vector, in radians. You can also use myVector.Angle() /// The vector to get the angle of. It does not have to be normalized /// - [MethodImpl( INLINE )] public static float DirToAng( Vector2 vec ) => Mathf.Atan2( vec.y, vec.x ); + [MethodImpl( INLINE )] public static float DirToAng( Vector2 vec ) => MathF.Atan2( vec.y, vec.x ); /// Returns a 2D orientation from a vector, representing the X axis /// The direction to create a 2D orientation from (does not have to be normalized) @@ -1200,13 +1200,13 @@ public static Pose Lerp( Pose a, Pose b, float t ) => } /// Returns the signed angle between a and b, in the range -tau/2 to tau/2 (-pi to pi) - [MethodImpl( INLINE )] public static float SignedAngle( Vector2 a, Vector2 b ) => AngleBetween( a, b ) * Mathf.Sign( Determinant( a, b ) ); // -tau/2 to tau/2 + [MethodImpl( INLINE )] public static float SignedAngle( Vector2 a, Vector2 b ) => AngleBetween( a, b ) * MathF.Sign( Determinant( a, b ) ); // -tau/2 to tau/2 /// Returns the shortest angle between a and b, in the range 0 to tau/2 (0 to pi) - [MethodImpl( INLINE )] public static float AngleBetween( Vector2 a, Vector2 b ) => Mathf.Acos( Vector2.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); + [MethodImpl( INLINE )] public static float AngleBetween( Vector2 a, Vector2 b ) => MathF.Acos( Vector2.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); /// - [MethodImpl( INLINE )] public static float AngleBetween( Vector3 a, Vector3 b ) => Mathf.Acos( Vector3.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); + [MethodImpl( INLINE )] public static float AngleBetween( Vector3 a, Vector3 b ) => MathF.Acos( Vector3.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); /// Returns the clockwise angle between from and to, in the range 0 to tau (0 to 2*pi) [MethodImpl( INLINE )] public static float AngleFromToCW( Vector2 from, Vector2 to ) => Determinant( from, to ) < 0 ? AngleBetween( from, to ) : TAU - AngleBetween( from, to ); diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 731d561..838684a 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -27,13 +27,13 @@ public struct FloatRange { public float Center => ( a + b ) / 2; /// The length/span of this value range - public float Length => Mathfs.Abs( b - a ); + public float Length => MathF.Abs( b - a ); /// The minimum value of this range - public float Min => Mathfs.Min( a, b ); + public float Min => MathF.Min( a, b ); /// The maximum value of this range - public float Max => Mathfs.Max( a, b ); + public float Max => MathF.Max( a, b ); /// The direction of this value range. Returns -1 if b is greater than a, otherwise returns 1 public int Direction => b > a ? 1 : -1; @@ -69,7 +69,7 @@ public struct FloatRange { /// Returns whether or not this range overlaps another range /// The other range to test overlap with public bool Overlaps( FloatRange other ) { - float separation = Mathfs.Abs( other.Center - Center ); + float separation = MathF.Abs( other.Center - Center ); float rTotal = ( other.Length + Length ) / 2; return separation < rTotal; } From 5543be07934b2f1ffd3ef8d5cef2c0f2c363a33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 2 Jan 2023 20:13:51 +0100 Subject: [PATCH 187/301] GetCurvature now returns a Bivector3 you can still cast it to a Vector3 for the old behavior dw --- Runtime/Geometric Shapes/Circle.cs | 4 ++-- Runtime/Mathfs.cs | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Runtime/Geometric Shapes/Circle.cs b/Runtime/Geometric Shapes/Circle.cs index b093bbc..216ba53 100644 --- a/Runtime/Geometric Shapes/Circle.cs +++ b/Runtime/Geometric Shapes/Circle.cs @@ -315,8 +315,8 @@ public partial struct Circle3D { /// public static Circle3D GetOsculatingCircle( Vector3 point, Vector3 velocity, Vector3 acceleration ) { - Vector3 curvatureVector = GetCurvature( velocity, acceleration ); - ( Vector3 axis, float curvature ) = curvatureVector.GetDirAndMagnitude(); + Bivector3 curvatureBivector = GetCurvature( velocity, acceleration ); + ( Vector3 axis, float curvature ) = curvatureBivector.GetNormalAndArea(); Vector3 normal = Vector3.Cross( velocity, Vector3.Cross( acceleration, velocity ) ).normalized; float signedRadius = 1f / curvature; return new Circle3D( point + normal * signedRadius, axis, Abs( signedRadius ) ); diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 91c143a..ada8af3 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1148,12 +1148,14 @@ public static Pose Lerp( Pose a, Pose b, float t ) => return Determinant( velocity, acceleration ) / ( dMag * dMag * dMag ); } - /// Returns a pseudovector of a point in a curve, where the magnitude is the curvature in radians per distance unit, and the direction is the axis of curvature + /// Returns the curvature of a point in a 3D curve, as a Bivector. + /// The magnitude is the curvature in radians per distance unit, + /// casting it to a Vector3 gives you the axis of curvature /// The first derivative of the point in the curve /// The second derivative of the point in the curve - [MethodImpl( INLINE )] public static Vector3 GetCurvature( Vector3 velocity, Vector3 acceleration ) { + [MethodImpl( INLINE )] public static Bivector3 GetCurvature( Vector3 velocity, Vector3 acceleration ) { float dMag = velocity.magnitude; - return Vector3.Cross( velocity, acceleration ) / ( dMag * dMag * dMag ); + return Wedge( velocity, acceleration ) / ( dMag * dMag * dMag ); } /// Returns the torsion of a given point in a curve, in radians per distance unit From 71f33c0b551fafb98af39957109a1b1d8f89ea1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 2 Jan 2023 20:16:26 +0100 Subject: [PATCH 188/301] catrom spline fix --- Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs b/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs index 3dc5893..aea5d91 100644 --- a/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs +++ b/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs @@ -84,7 +84,7 @@ public int ControlPointCount { /// The number of curves in this spline public int CurveCount { - [MethodImpl( INLINE )] get => ControlPointCount - ( IncludeEndpoints ? 1 : 3 ); + [MethodImpl( INLINE )] get => ControlPointCount - ( IncludeEndpoints ? 1 : 2 ); } /// The knot value at the start of the spline From 933e645417b35c6c5b537c084e590baa9be541f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 6 Jan 2023 21:56:32 +0100 Subject: [PATCH 189/301] fixed Polynomial4D eval bug --- Runtime/Curves/Polynomial4D.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index 7d60f46..61299be 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -56,7 +56,7 @@ public Polynomial4D( Vector4 c0, Vector4 c1, Vector4 c2 ) { public Polynomial4D( Vector4Matrix3x1 coefficients ) => ( x, y, z, w ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ), new Polynomial( coefficients.W ) ); /// - public Vector4 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t )); + public Vector4 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t ), w.Eval( t )); /// public Polynomial4D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n ), w.Differentiate( n )); From cb1b9dda01b9c7006100067448f4519e5bb96fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 6 Jan 2023 21:56:42 +0100 Subject: [PATCH 190/301] added IntRange indexer --- Runtime/Numerics/IntRange.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Runtime/Numerics/IntRange.cs b/Runtime/Numerics/IntRange.cs index ebc2f51..dab1912 100644 --- a/Runtime/Numerics/IntRange.cs +++ b/Runtime/Numerics/IntRange.cs @@ -9,6 +9,8 @@ public readonly struct IntRange { public readonly int start; public readonly int count; + public int this[ int i ] => start + i; + /// The last integer in the range public int Last => start + count - 1; From 0e77d8921b4012db77cd3a06b31bd6e9aef70edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 6 Jan 2023 21:57:27 +0100 Subject: [PATCH 191/301] added Polynomial Eval nth derivative shorthand --- Runtime/Curves/Polynomial.cs | 5 +++++ Runtime/Curves/Polynomial2D.cs | 3 +++ Runtime/Curves/Polynomial3D.cs | 3 +++ Runtime/Curves/Polynomial4D.cs | 3 +++ 4 files changed, 14 insertions(+) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index c746216..fe04853 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -91,6 +91,11 @@ public float this[ int degree ] { /// The value to sample at public float Eval( float t ) => c3 * ( t * t * t ) + c2 * ( t * t ) + c1 * t + c0; + /// Evaluates the n:th derivative of the polynomial at the given value t + /// The value to sample at + /// The derivative to evaluate + public float Eval( float t, int n ) => Differentiate( n ).Eval( t ); + /// Differentiates this function, returning the n-th derivative of this polynomial /// The number of times to differentiate this function. 0 returns the function itself, 1 returns the first derivative public Polynomial Differentiate( int n = 1 ) { diff --git a/Runtime/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs index 00e50e5..d356bad 100644 --- a/Runtime/Curves/Polynomial2D.cs +++ b/Runtime/Curves/Polynomial2D.cs @@ -53,6 +53,9 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2 ) { /// public Vector2 Eval( float t ) => new(x.Eval( t ), y.Eval( t )); + /// + public Vector2 Eval( float t, int n ) => Differentiate( n ).Eval( t ); + /// public Polynomial2D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n )); diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index 3b82d79..4ab1073 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -62,6 +62,9 @@ public Polynomial3D( Vector3 c0, Vector3 c1 ) { /// public Vector3 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t )); + /// + public Vector3 Eval( float t, int n ) => Differentiate( n ).Eval( t ); + /// public Polynomial3D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n )); diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index 61299be..521bee2 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -58,6 +58,9 @@ public Polynomial4D( Vector4 c0, Vector4 c1, Vector4 c2 ) { /// public Vector4 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t ), w.Eval( t )); + /// + public Vector4 Eval( float t, int n ) => Differentiate( n ).Eval( t ); + /// public Polynomial4D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n ), w.Differentiate( n )); From 105934a579b5d1511a7c0b2d7ad1708a2f0fcd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 6 Jan 2023 21:58:42 +0100 Subject: [PATCH 192/301] added Catenary2D derivatives --- Runtime/Curves/Catenary2D.cs | 62 ++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index be3815a..c40d4b2 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -27,6 +27,41 @@ public struct Catenary2D { /// The a-parameter of the catenary public static float EvalXByArcLength( float s, float a ) => a * Mathfs.Asinh( s / a ); + /// Evaluates the n:th 2D derivative at the given arc length relative to the apex of the catenary. + /// Note that the input arc length can be negative, to get the tangents on the negative x side + /// The arc length coordinate to get the tangent of + /// The a-parameter of the catenary + public static Vector2 EvalDerivByArcLength( float s, float a, int n = 1 ) { + if( n == 0 ) { // position + float x = EvalXByArcLength( s, a ); + float y = Eval( x, a ); + return new Vector2( x, y ); + } + float xNum = default; + float yNum = default; + float aSq = a * a; + float sSq = s * s; + + if( n == 1 ) { // velocity + xNum = a; + yNum = s; + } else if( n == 2 ) { // acceleration + xNum = -a * s; + yNum = aSq; + } else if( n == 3 ) { // jerk/jolt + xNum = a * ( -aSq + 2 * sSq ); + yNum = 3 * aSq * s; + } else if( n == 4 ) { // 4th derivative + xNum = 3 * s * a * ( -3 * aSq + 2 * sSq ); + yNum = 3 * aSq * ( -aSq + 4 * sSq ); + } else { + throw new NotImplementedException( $"Derivative ({n}) of Catenaries are not implemented" ); + } + + float den = MathF.Pow( aSq + sSq, ( n * 2 - 1 ) / 2f ); + return new Vector2( xNum / den, yNum / den ); + } + #endregion enum Evaluability { @@ -95,6 +130,21 @@ public Vector2 EvalByArcLength( float sEval ) { }; } + /// Evaluates the tangent on this catenary curve at the given arc length of sEval + /// The arc length along the curve to sample, relative to the first point + /// The derivative to sample. 1 = first derivative, 2 = second derivative + public Vector2 EvalDerivativeByArcLength( float sEval, int n = 1 ) { + if( n == 0 ) // position + return EvalByArcLength( sEval ); + ReadyForEvaluation(); + return evaluability switch { + Evaluability.Catenary => EvalCatDerivByArcLength( sEval ), + Evaluability.LineSegment => n == 1 ? ( p1 - p0 ).normalized : Vector2.zero, + Evaluability.LinearVertical => new Vector2( 0, n == 1 ? ( sEval < -( p.y - s ) / 2 ? -1 : 1 ) : 0 ), + Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) + }; + } + // straight line from p0 to p1 Vector2 EvalStraightLineByArcLength( float sEval ) => Vector3.LerpUnclamped( p0, p1, sEval / s ); @@ -109,14 +159,20 @@ Vector2 EvalVerticalLinearApproxByArcLength( float sEval ) { // evaluates the position of the catenary at the given arc length, relative to the first point Vector2 EvalCatPosByArcLength( float sEval ) { - float x = EvalCatXByArcLengthPassingThrough0( sEval ); + sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x + float x = Catenary2D.EvalXByArcLength( sEval + arcLenSampleOffset, a ) + delta.x; float y = EvalPassingThrough0( x ); return new Vector2( x, y ) + p0; } - float EvalCatXByArcLengthPassingThrough0( float sEval ) { + /// /// Evaluates the n-th derivative of the catenary at the given arc length + /// The arc length, relative to the first point + /// The derivative to evaluate + public Vector2 EvalCatDerivByArcLength( float sEval, int n = 1 ) { + if( n == 0 ) + return EvalCatPosByArcLength( sEval ); sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x - return Catenary2D.EvalXByArcLength( sEval + arcLenSampleOffset, a ) + delta.x; + return Catenary2D.EvalDerivByArcLength( sEval + arcLenSampleOffset, a, n ); } // Evaluate passing through the origin and p From f37f77de5e04fd0a5fa82670b4254a06c7a29f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 7 Jan 2023 18:04:26 +0100 Subject: [PATCH 193/301] added IntRange.empty --- Runtime/Numerics/IntRange.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Runtime/Numerics/IntRange.cs b/Runtime/Numerics/IntRange.cs index dab1912..efe7e27 100644 --- a/Runtime/Numerics/IntRange.cs +++ b/Runtime/Numerics/IntRange.cs @@ -6,6 +6,9 @@ namespace Freya { /// An integer range public readonly struct IntRange { + + public static readonly IntRange empty = new IntRange( 0, 0 ); + public readonly int start; public readonly int count; From 39f4cd3a849212451fd63b8d75abb7624da5ea3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 10 Feb 2023 20:03:26 +0100 Subject: [PATCH 194/301] fixed broken Transform.InverseTransformRotation --- Runtime/Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 3bbaa0e..c6317db 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -397,7 +397,7 @@ public static Quaternion Exp( this Quaternion q ) { /// Transforms a rotation from world space to local space /// The transform to use /// The world space rotation - public static Quaternion InverseTransformRotation( this Transform tf, Quaternion quat ) => tf.rotation * quat; + public static Quaternion InverseTransformRotation( this Transform tf, Quaternion quat ) => Quaternion.Inverse( tf.rotation ) * quat; #endregion From b76be6cd42f22e845b88bab7ab843c23ce9afc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 17 Feb 2023 16:55:10 +0100 Subject: [PATCH 195/301] added cosinc(x) --- Runtime/Mathfs.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index ada8af3..9dd4c58 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -263,6 +263,17 @@ public static double Sinc( double x ) { return Math.Sin( x ) / x; } + /// The unnormalized cosinc or cosc function (1-cos(x))/x, properly handling the removable singularity around x = 0 + /// The input value for the Cosinc function + public static float Cosinc( float x ) => (float)Cosinc( (double)x ); + + /// + public static double Cosinc( double x ) { + if( Math.Abs( x ) < 0.01 ) + return x / 2 - ( x * x * x ) / 24; // approximate the singularity w. a polynomial, based on the taylor series expansion + return ( 1 - Math.Cos( x ) ) / x; + } + /// The unnormalized reciprocal sinc function x/sin(x), properly handling the removable singularity around x = 0 /// The input value for the reciprocal Sinc function public static float SincRcp( float x ) => (float)SincRcp( (double)x ); From 341f67285b990230f39d2e2d5b2041db7851462d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 22 Feb 2023 22:16:05 +0100 Subject: [PATCH 196/301] added missing constructor to Polynomial2D --- Runtime/Curves/Polynomial2D.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Runtime/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs index d356bad..a698977 100644 --- a/Runtime/Curves/Polynomial2D.cs +++ b/Runtime/Curves/Polynomial2D.cs @@ -43,6 +43,12 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2 ) { this.x = new Polynomial( c0.x, c1.x, c2.x ); this.y = new Polynomial( c0.y, c1.y, c2.y ); } + + /// + public Polynomial2D( Vector2 c0, Vector2 c1 ) { + this.x = new Polynomial( c0.x, c1.x, 0, 0 ); + this.y = new Polynomial( c0.y, c1.y, 0, 0 ); + } /// public Polynomial2D( Vector2Matrix4x1 coefficients ) => ( x, y ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ) ); From f36c160987f32fe1f2dcf96b78442ef8de6ede18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 22 Feb 2023 22:16:59 +0100 Subject: [PATCH 197/301] only recalc Cat2D when values change --- Runtime/Curves/Catenary2D.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index c40d4b2..303ac66 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -87,15 +87,24 @@ enum Evaluability { public float Length { get => s; - set => ( s, evaluability ) = ( value, Evaluability.Unknown ); + set { + if( value != s ) + ( s, evaluability ) = ( value, Evaluability.Unknown ); + } } public Vector2 P0 { get => p0; - set => ( p0, evaluability ) = ( value, Evaluability.Unknown ); + set { + if( value != p0 ) + ( p0, evaluability ) = ( value, Evaluability.Unknown ); + } } public Vector2 P1 { get => p1; - set => ( p1, evaluability ) = ( value, Evaluability.Unknown ); + set { + if( value != p1 ) + ( p1, evaluability ) = ( value, Evaluability.Unknown ); + } } public bool IsVertical => MathF.Abs( p.x ) < 0.001f; From 57c9aa878ebc8e0c6cc307cda0b7d9c7191d3a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 22 Feb 2023 22:19:00 +0100 Subject: [PATCH 198/301] added Arc2D --- Runtime/Curves/Arc2D.cs | 99 ++++++++++++++++++++++++++++++++++++ Runtime/Curves/Arc2D.cs.meta | 11 ++++ 2 files changed, 110 insertions(+) create mode 100644 Runtime/Curves/Arc2D.cs create mode 100644 Runtime/Curves/Arc2D.cs.meta diff --git a/Runtime/Curves/Arc2D.cs b/Runtime/Curves/Arc2D.cs new file mode 100644 index 0000000..e0df251 --- /dev/null +++ b/Runtime/Curves/Arc2D.cs @@ -0,0 +1,99 @@ +using System; +using UnityEngine; + +namespace Freya { + + /// a 2D arc with support for straight lines + public struct Arc2D { + + /// The starting point of the arc + public Vector2 startPoint; + /// The normalized tangent direction at the start of the arc + public Vector2 startTangent; + /// The signed curvature of the arc, equal to 1/radius (0 = straight line, 1 = turning left, -1 = turning right) + public float curvature; + /// The length of the arc + public float length; + + /// The radius of the circle traced by the arc. Returns infinity if this segment is linear, ie: if curvature is 0 + public float Radius => 1f / MathF.Abs( curvature ); + /// The center of the circle traced by the arc. Returns infinity if this segment is linear, ie: if curvature is 0 + public Vector2 CircleCenter => StartNormal / curvature; + /// The normal direction at the start of the arc + public Vector2 StartNormal => startTangent.Rotate90CCW(); + /// The normal direction at the end of the arc + public Vector2 EndNormal => GetNormal( length ); + /// The end point of the arc + public Vector2 EndPoint => GetPosition( length ); + /// The signed angular span covered across the arc. This returns 0 if this segment is linear, ie: if curvature is 0 + public float AngularSpan => length * curvature; // s = ra ▶ s = a/k ▶ sk = a + /// Whether or not this is a straight line rather than an arc, ie: if curvature is 0 + public bool IsStraight => Mathfs.Approximately( curvature, 0 ); + + /// Evaluates the position of this arc at the given arc length s + public Vector2 GetPosition( float s ) => Eval( s, nThDerivative: 0 ); + + /// Evaluates the tangent direction of this arc at the given arc length s + public Vector2 GetTangent( float s ) => s == 0 ? startTangent : Eval( s, nThDerivative: 1 ); // no need to normalize, it's already arc-length parameterized + + /// Evaluates the normal direction of this arc at the given arc length s + public Vector2 GetNormal( float s ) => Eval( s, nThDerivative: 1 ).Rotate90CCW(); // no need to normalize, it's already arc-length parameterized + + /// Evaluates the given derivative of this arc, by arc length s + public Vector2 Eval( float s, int nThDerivative = 0 ) { + float ang = s * curvature; + float x, y; + + switch( nThDerivative ) { + case 0: + x = s * Mathfs.Sinc( ang ); + y = s * Mathfs.Cosinc( ang ); + return new Vector2( + startPoint.x + startTangent.x * x + StartNormal.x * y, + startPoint.y + startTangent.y * x + StartNormal.y * y + ); + case 1: + x = MathF.Cos( ang ); + y = MathF.Sin( ang ); + break; + case 2: + x = -curvature * MathF.Sin( ang ); + y = +curvature * MathF.Cos( ang ); + break; + case 3: + float k2 = curvature * curvature; + x = -k2 * MathF.Cos( ang ); + y = -k2 * MathF.Sin( ang ); + break; + case 4: + float k3 = curvature * curvature * curvature; + x = +k3 * MathF.Sin( ang ); + y = -k3 * MathF.Cos( ang ); + break; + case 5: + float _k2 = curvature * curvature; + float k4 = _k2 * _k2; + x = k4 * MathF.Cos( ang ); + y = k4 * MathF.Sin( ang ); + break; + default: + // general form for n > 0 + float scale = MathF.Pow( curvature, nThDerivative - 1 ); + int xSgn = nThDerivative / 2 % 2 == 0 ? 1 : -1; + int ySgn = ( nThDerivative - 1 ) / 2 % 2 == 0 ? 1 : -1; + bool even = nThDerivative % 2 == 0; + x = xSgn * scale * ( even ? MathF.Sin( ang ) : MathF.Cos( ang ) ); + y = ySgn * scale * ( even ? MathF.Cos( ang ) : MathF.Sin( ang ) ); + break; + } + + // space transformation + return new Vector2( + startTangent.x * x + StartNormal.x * y, + startTangent.y * x + StartNormal.y * y + ); + } + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/Arc2D.cs.meta b/Runtime/Curves/Arc2D.cs.meta new file mode 100644 index 0000000..29658a3 --- /dev/null +++ b/Runtime/Curves/Arc2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 723d5270bbbe92b47b5152cbbc8928cb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 1c527241b5e8e6ed568cb491abdfd501c931fea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 22 Feb 2023 22:45:22 +0100 Subject: [PATCH 199/301] Catenary3D wip --- Runtime/Curves/Catenary3D.cs | 75 +++++++++++++++++++++++++++++++ Runtime/Curves/Catenary3D.cs.meta | 11 +++++ 2 files changed, 86 insertions(+) create mode 100644 Runtime/Curves/Catenary3D.cs create mode 100644 Runtime/Curves/Catenary3D.cs.meta diff --git a/Runtime/Curves/Catenary3D.cs b/Runtime/Curves/Catenary3D.cs new file mode 100644 index 0000000..dcbdeb8 --- /dev/null +++ b/Runtime/Curves/Catenary3D.cs @@ -0,0 +1,75 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya { + + /// A catenary curve passing through two points with a given an arc length + public struct Catenary3D { + + // data + Vector3 p0, p1; + + // cached state + Catenary2D cat2D; + + public float Length { + get => cat2D.Length; + set => cat2D.Length = value; + } + public Vector3 P0 { + get => p0; + set { + // todo + throw new NotImplementedException(); + } + } + public Vector3 P1 { + get => p1; + set { + // todo + throw new NotImplementedException(); + } + } + + /// + public Catenary3D( Vector3 p0, Vector3 p1, float s ) { + cat2D = new Catenary2D( default, default, s ); + ( this.p0, this.p1 ) = ( p0, p1 ); + } + + public Vector3 TransformPoint( Vector2 pt ) { + // 2D to 3D + // todo + throw new NotImplementedException(); + } + + public Vector3 TransformVector( Vector2 pt ) { + // 2D to 3D + // todo + throw new NotImplementedException(); + } + + /// + public Vector3 Eval( float t ) => EvalDerivativeByArcLength( t * Length, n: 0 ); + + /// + public Vector3 EvalByArcLength( float sEval ) => EvalDerivativeByArcLength( sEval, n: 0 ); + + /// + public Vector3 EvalDerivativeByArcLength( float sEval, int n = 1 ) { + return n switch { + 0 => TransformPoint( cat2D.EvalByArcLength( sEval ) ), + _ => TransformVector( cat2D.EvalCatDerivByArcLength( sEval ) ) + }; + } + + // calculates p, a, delta, arcLenSampleOffset, and which evaluation method to use + void ReadyForEvaluation() { + // todo: space transform cache? + } + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/Catenary3D.cs.meta b/Runtime/Curves/Catenary3D.cs.meta new file mode 100644 index 0000000..8fad120 --- /dev/null +++ b/Runtime/Curves/Catenary3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1b20ae836093c6c4891a5bd00c155a6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 6b1b91ba2eb3ca2d5409fcc4ebf85400d406a116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 23 Feb 2023 10:00:47 +0100 Subject: [PATCH 200/301] Updated (and broke) Catenary3D --- Runtime/Curves/Catenary3D.cs | 92 ++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/Runtime/Curves/Catenary3D.cs b/Runtime/Curves/Catenary3D.cs index dcbdeb8..9698869 100644 --- a/Runtime/Curves/Catenary3D.cs +++ b/Runtime/Curves/Catenary3D.cs @@ -8,48 +8,61 @@ namespace Freya { /// A catenary curve passing through two points with a given an arc length public struct Catenary3D { + enum Evaluability { + NotReady, + Ready + } + // data - Vector3 p0, p1; + Vector3 p1; - // cached state - Catenary2D cat2D; + // cached states + Catenary2D cat2D; // also data + Evaluability evaluability; + Plane2DIn3D plane; public float Length { get => cat2D.Length; - set => cat2D.Length = value; + set => cat2D.Length = value; // does not change evaluability of this type, since space hasn't changed } public Vector3 P0 { - get => p0; + get => plane.origin; set { - // todo - throw new NotImplementedException(); + if( value != plane.origin ) + ( plane.origin, evaluability ) = ( value, Evaluability.NotReady ); } } public Vector3 P1 { get => p1; set { - // todo - throw new NotImplementedException(); + if( value != p1 ) + ( p1, evaluability ) = ( value, Evaluability.NotReady ); } } - - /// - public Catenary3D( Vector3 p0, Vector3 p1, float s ) { - cat2D = new Catenary2D( default, default, s ); - ( this.p0, this.p1 ) = ( p0, p1 ); + public Vector3 SlackDirection { + get => -plane.axisY; + set { + if( value != SlackDirection ) + ( plane.axisY, evaluability ) = ( -value, Evaluability.NotReady ); + } } - public Vector3 TransformPoint( Vector2 pt ) { - // 2D to 3D - // todo - throw new NotImplementedException(); + /// Creates a catenary curve between two points, given an arc length s and a slack direction + /// The start of the curve + /// The end of the curve + /// The length of the curve. note: has to be equal or longer than the distance between the points + public Catenary3D( Vector3 p0, Vector3 p1, float length, Vector3 slackDirection ) { + cat2D = new Catenary2D( default, default, length ); + ( plane.origin, plane.axisY, this.p1 ) = ( p0, -slackDirection, p1 ); + evaluability = Evaluability.NotReady; + plane = default; } - public Vector3 TransformVector( Vector2 pt ) { - // 2D to 3D - // todo - throw new NotImplementedException(); - } + /// Creates a catenary curve between two points, given an arc length s, with slack/gravity direction pointing down + /// The start of the curve + /// The end of the curve + /// The length of the curve. note: has to be equal or longer than the distance between the points + public Catenary3D( Vector3 p0, Vector3 p1, float length ) : this( p0, p1, length, Vector3.down ) {} /// public Vector3 Eval( float t ) => EvalDerivativeByArcLength( t * Length, n: 0 ); @@ -59,17 +72,44 @@ public Vector3 TransformVector( Vector2 pt ) { /// public Vector3 EvalDerivativeByArcLength( float sEval, int n = 1 ) { + ReadyForEvaluation(); return n switch { - 0 => TransformPoint( cat2D.EvalByArcLength( sEval ) ), - _ => TransformVector( cat2D.EvalCatDerivByArcLength( sEval ) ) + 0 => plane.TransformPoint( cat2D.EvalByArcLength( sEval ) ), + _ => plane.TransformVector( cat2D.EvalCatDerivByArcLength( sEval ) ) }; } // calculates p, a, delta, arcLenSampleOffset, and which evaluation method to use void ReadyForEvaluation() { - // todo: space transform cache? + if( evaluability == Evaluability.Ready ) + return; + // ready the embedded plane of the catenary and assign the 2D endpoint + plane.RotateAroundYToInclude( P1, out Vector2 p1Local ); + cat2D.P1 = p1Local; + evaluability = Evaluability.Ready; } } +} + +/// An oriented 2D plane embedded in 3D space +struct Plane2DIn3D { + public Vector3 origin; + public Vector3 axisX, axisY; + + /// Rotates this plane around the Y axis, setting the X axis, + /// so that the given point p is in the plane where x > 0 + /// The point to include in the plane + /// The included point in the 2D local space + public void RotateAroundYToInclude( Vector3 p, out Vector2 pLocal ) { + Vector3 pRel = p - origin; + float yProj = Vector3.Dot( axisY, pRel ); + axisX = ( pRel - axisY * yProj ).normalized; + float xProj = Vector3.Dot( axisX, pRel ); + pLocal = new Vector2( xProj, yProj ); + } + + public Vector3 TransformPoint( Vector2 pt ) => origin + TransformVector( pt ); // todo: unroll + public Vector3 TransformVector( Vector2 pt ) => axisX * pt.x + axisY * pt.y; // todo: unroll } \ No newline at end of file From 60c673107aac99f16d68b3a6f4674c2730d80d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 23 Feb 2023 12:35:40 +0100 Subject: [PATCH 201/301] fixed Catenary3D and cleaned up caternary code --- Runtime/Curves/Catenary.cs | 63 +++++++++++ Runtime/Curves/Catenary.cs.meta | 11 ++ Runtime/Curves/Catenary2D.cs | 108 ++++--------------- Runtime/Curves/Catenary3D.cs | 38 ++----- Runtime/Geometric Shapes/Plane2DIn3D.cs | 75 +++++++++++++ Runtime/Geometric Shapes/Plane2DIn3D.cs.meta | 11 ++ 6 files changed, 191 insertions(+), 115 deletions(-) create mode 100644 Runtime/Curves/Catenary.cs create mode 100644 Runtime/Curves/Catenary.cs.meta create mode 100644 Runtime/Geometric Shapes/Plane2DIn3D.cs create mode 100644 Runtime/Geometric Shapes/Plane2DIn3D.cs.meta diff --git a/Runtime/Curves/Catenary.cs b/Runtime/Curves/Catenary.cs new file mode 100644 index 0000000..92a8136 --- /dev/null +++ b/Runtime/Curves/Catenary.cs @@ -0,0 +1,63 @@ +using System; +using UnityEngine; + +namespace Freya { + + /// Catenary math utility functions + public static class Catenary { + + /// Returns the y coordinate of a catenary at the given x value + /// The x coordinate to evaluate at + /// The a-parameter of the catenary + public static float Eval( float x, float a ) => a * Mathfs.Cosh( x / a ); + + /// Evaluates the arc length from the apex of the catenary, to the given x coordinate. + /// Note that this is negative when x is less than 0 + /// The x coordinate to get the length to + /// The a-parameter of the catenary + public static float EvalArcLen( float x, float a ) => a * Mathfs.Sinh( x / a ); + + /// Evaluates the x coordinate at the given arc length relative to the apex of the catenary. + /// Note that the input arc length can be negative, to get the negative x coordinates + /// The arc length to get the x coordinate of + /// The a-parameter of the catenary + public static float EvalXByArcLength( float s, float a ) => a * Mathfs.Asinh( s / a ); + + /// Evaluates the n:th 2D derivative at the given arc length relative to the apex of the catenary. + /// Note that the input arc length can be negative, to get the tangents on the negative x side + /// The arc length coordinate to get the tangent of + /// The a-parameter of the catenary + public static Vector2 EvalDerivByArcLength( float s, float a, int n = 1 ) { + if( n == 0 ) { // position + float x = EvalXByArcLength( s, a ); + float y = Eval( x, a ); + return new Vector2( x, y ); + } + float xNum = default; + float yNum = default; + float aSq = a * a; + float sSq = s * s; + + if( n == 1 ) { // velocity + xNum = a; + yNum = s; + } else if( n == 2 ) { // acceleration + xNum = -a * s; + yNum = aSq; + } else if( n == 3 ) { // jerk/jolt + xNum = a * ( -aSq + 2 * sSq ); + yNum = 3 * aSq * s; + } else if( n == 4 ) { // 4th derivative + xNum = 3 * s * a * ( -3 * aSq + 2 * sSq ); + yNum = 3 * aSq * ( -aSq + 4 * sSq ); + } else { + throw new NotImplementedException( $"Derivative ({n}) of Catenaries are not implemented" ); + } + + float den = MathF.Pow( aSq + sSq, ( n * 2 - 1 ) / 2f ); + return new Vector2( xNum / den, yNum / den ); + } + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/Catenary.cs.meta b/Runtime/Curves/Catenary.cs.meta new file mode 100644 index 0000000..053d6e7 --- /dev/null +++ b/Runtime/Curves/Catenary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b30c699ff7f0afe4aa0d122f4a3e313f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index 303ac66..9bd5a1c 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -8,62 +8,6 @@ namespace Freya { /// A catenary curve passing through two points with a given an arc length public struct Catenary2D { - #region Standard catenary equations - - /// Returns the y coordinate of a catenary at the given x value - /// The x coordinate to evaluate at - /// The a-parameter of the catenary - public static float Eval( float x, float a ) => a * Mathfs.Cosh( x / a ); - - /// Evaluates the arc length from the apex of the catenary, to the given x coordinate. - /// Note that this is negative when x is less than 0 - /// The x coordinate to get the length to - /// The a-parameter of the catenary - public static float EvalArcLen( float x, float a ) => a * Mathfs.Sinh( x / a ); - - /// Evaluates the x coordinate at the given arc length relative to the apex of the catenary. - /// Note that the input arc length can be negative, to get the negative x coordinates - /// The arc length to get the x coordinate of - /// The a-parameter of the catenary - public static float EvalXByArcLength( float s, float a ) => a * Mathfs.Asinh( s / a ); - - /// Evaluates the n:th 2D derivative at the given arc length relative to the apex of the catenary. - /// Note that the input arc length can be negative, to get the tangents on the negative x side - /// The arc length coordinate to get the tangent of - /// The a-parameter of the catenary - public static Vector2 EvalDerivByArcLength( float s, float a, int n = 1 ) { - if( n == 0 ) { // position - float x = EvalXByArcLength( s, a ); - float y = Eval( x, a ); - return new Vector2( x, y ); - } - float xNum = default; - float yNum = default; - float aSq = a * a; - float sSq = s * s; - - if( n == 1 ) { // velocity - xNum = a; - yNum = s; - } else if( n == 2 ) { // acceleration - xNum = -a * s; - yNum = aSq; - } else if( n == 3 ) { // jerk/jolt - xNum = a * ( -aSq + 2 * sSq ); - yNum = 3 * aSq * s; - } else if( n == 4 ) { // 4th derivative - xNum = 3 * s * a * ( -3 * aSq + 2 * sSq ); - yNum = 3 * aSq * ( -aSq + 4 * sSq ); - } else { - throw new NotImplementedException( $"Derivative ({n}) of Catenaries are not implemented" ); - } - - float den = MathF.Pow( aSq + sSq, ( n * 2 - 1 ) / 2f ); - return new Vector2( xNum / den, yNum / den ); - } - - #endregion - enum Evaluability { Unknown = 0, Catenary, @@ -125,32 +69,26 @@ public Catenary2D( Vector2 p0, Vector2 p1, float s ) { /// Evaluates a position on this catenary curve, given a t-value from 0 to 1 /// A value from 0 to 1 along the whole curve - public Vector2 Eval( float t ) => EvalByArcLength( t * s ); + public Vector2 EvalByTValue( float t ) => Eval( t * s ); /// Evaluates a position on this catenary curve at the given arc length of sEval /// The arc length along the curve to sample, relative to the first point - public Vector2 EvalByArcLength( float sEval ) { + /// The derivative to sample. 1 = first derivative, 2 = second derivative + public Vector2 Eval( float sEval, int nthDerivative = 0 ) { ReadyForEvaluation(); - return evaluability switch { - Evaluability.Catenary => EvalCatPosByArcLength( sEval ), - Evaluability.LineSegment => EvalStraightLineByArcLength( sEval ), - Evaluability.LinearVertical => EvalVerticalLinearApproxByArcLength( sEval ), - Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) - }; - } - - /// Evaluates the tangent on this catenary curve at the given arc length of sEval - /// The arc length along the curve to sample, relative to the first point - /// The derivative to sample. 1 = first derivative, 2 = second derivative - public Vector2 EvalDerivativeByArcLength( float sEval, int n = 1 ) { - if( n == 0 ) // position - return EvalByArcLength( sEval ); - ReadyForEvaluation(); - return evaluability switch { - Evaluability.Catenary => EvalCatDerivByArcLength( sEval ), - Evaluability.LineSegment => n == 1 ? ( p1 - p0 ).normalized : Vector2.zero, - Evaluability.LinearVertical => new Vector2( 0, n == 1 ? ( sEval < -( p.y - s ) / 2 ? -1 : 1 ) : 0 ), - Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) + return nthDerivative switch { + 0 => evaluability switch { + Evaluability.Catenary => EvalCatPosByArcLength( sEval ), + Evaluability.LineSegment => EvalStraightLineByArcLength( sEval ), + Evaluability.LinearVertical => EvalVerticalLinearApproxByArcLength( sEval ), + Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) + }, + _ => evaluability switch { + Evaluability.Catenary => EvalCatDerivByArcLength( sEval ), + Evaluability.LineSegment => nthDerivative == 1 ? ( p1 - p0 ).normalized : Vector2.zero, // todo: this is incorrect + Evaluability.LinearVertical => new Vector2( 0, nthDerivative == 1 ? ( sEval < -( p.y - s ) / 2 ? -1 : 1 ) : 0 ), // todo: this might also be incorrect + Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) + } }; } @@ -169,23 +107,23 @@ Vector2 EvalVerticalLinearApproxByArcLength( float sEval ) { // evaluates the position of the catenary at the given arc length, relative to the first point Vector2 EvalCatPosByArcLength( float sEval ) { sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x - float x = Catenary2D.EvalXByArcLength( sEval + arcLenSampleOffset, a ) + delta.x; + float x = Catenary.EvalXByArcLength( sEval + arcLenSampleOffset, a ) + delta.x; float y = EvalPassingThrough0( x ); return new Vector2( x, y ) + p0; } - /// /// Evaluates the n-th derivative of the catenary at the given arc length + /// Evaluates the n-th derivative of the catenary at the given arc length /// The arc length, relative to the first point /// The derivative to evaluate - public Vector2 EvalCatDerivByArcLength( float sEval, int n = 1 ) { + Vector2 EvalCatDerivByArcLength( float sEval, int n = 1 ) { if( n == 0 ) return EvalCatPosByArcLength( sEval ); sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x - return Catenary2D.EvalDerivByArcLength( sEval + arcLenSampleOffset, a, n ); + return Catenary.EvalDerivByArcLength( sEval + arcLenSampleOffset, a, n ); } // Evaluate passing through the origin and p - float EvalPassingThrough0( float x ) => Catenary2D.Eval( x - delta.x, a ) + delta.y; + float EvalPassingThrough0( float x ) => Catenary.Eval( x - delta.x, a ) + delta.y; // calculates p, a, delta, arcLenSampleOffset, and which evaluation method to use void ReadyForEvaluation() { @@ -233,13 +171,13 @@ void ReadyForEvaluation() { } // Calculates the arc length offset so that it's relative to the start of the chain when evaluating by arc length - static float CalcArcLenSampleOffset( float deltaX, float a ) => Catenary2D.EvalArcLen( -deltaX, a ); + static float CalcArcLenSampleOffset( float deltaX, float a ) => Catenary.EvalArcLen( -deltaX, a ); // Calculates the required offset to make a catenary pass through the origin and a point p static Vector2 CalcCatenaryDelta( float a, Vector2 p ) { Vector2 d; d.x = p.x / 2 - a * Mathfs.Asinh( p.y / ( 2 * a * Mathfs.Sinh( p.x / ( 2 * a ) ) ) ); - d.y = -Catenary2D.Eval( d.x, a ); // technically -d.x but because of symmetry d.x works too + d.y = -Catenary.Eval( d.x, a ); // technically -d.x but because of symmetry d.x works too return d; } diff --git a/Runtime/Curves/Catenary3D.cs b/Runtime/Curves/Catenary3D.cs index 9698869..5b396ed 100644 --- a/Runtime/Curves/Catenary3D.cs +++ b/Runtime/Curves/Catenary3D.cs @@ -15,11 +15,9 @@ enum Evaluability { // data Vector3 p1; - - // cached states - Catenary2D cat2D; // also data + Catenary2D cat2D; // also stores arc length + Plane2DIn3D plane; // also stores p0 Evaluability evaluability; - Plane2DIn3D plane; public float Length { get => cat2D.Length; @@ -51,6 +49,7 @@ public Vector3 SlackDirection { /// The start of the curve /// The end of the curve /// The length of the curve. note: has to be equal or longer than the distance between the points + /// The direction of "gravity" for the arc. (0,-1,0) would create a hanging chain like arc public Catenary3D( Vector3 p0, Vector3 p1, float length, Vector3 slackDirection ) { cat2D = new Catenary2D( default, default, length ); ( plane.origin, plane.axisY, this.p1 ) = ( p0, -slackDirection, p1 ); @@ -64,18 +63,18 @@ public Catenary3D( Vector3 p0, Vector3 p1, float length, Vector3 slackDirection /// The length of the curve. note: has to be equal or longer than the distance between the points public Catenary3D( Vector3 p0, Vector3 p1, float length ) : this( p0, p1, length, Vector3.down ) {} - /// + /// public Vector3 Eval( float t ) => EvalDerivativeByArcLength( t * Length, n: 0 ); - /// + /// public Vector3 EvalByArcLength( float sEval ) => EvalDerivativeByArcLength( sEval, n: 0 ); - /// + /// public Vector3 EvalDerivativeByArcLength( float sEval, int n = 1 ) { ReadyForEvaluation(); return n switch { - 0 => plane.TransformPoint( cat2D.EvalByArcLength( sEval ) ), - _ => plane.TransformVector( cat2D.EvalCatDerivByArcLength( sEval ) ) + 0 => plane.TransformPoint( cat2D.Eval( sEval, 0 ) ), + _ => plane.TransformVector( cat2D.Eval( sEval, n ) ) }; } @@ -91,25 +90,4 @@ void ReadyForEvaluation() { } -} - -/// An oriented 2D plane embedded in 3D space -struct Plane2DIn3D { - public Vector3 origin; - public Vector3 axisX, axisY; - - /// Rotates this plane around the Y axis, setting the X axis, - /// so that the given point p is in the plane where x > 0 - /// The point to include in the plane - /// The included point in the 2D local space - public void RotateAroundYToInclude( Vector3 p, out Vector2 pLocal ) { - Vector3 pRel = p - origin; - float yProj = Vector3.Dot( axisY, pRel ); - axisX = ( pRel - axisY * yProj ).normalized; - float xProj = Vector3.Dot( axisX, pRel ); - pLocal = new Vector2( xProj, yProj ); - } - - public Vector3 TransformPoint( Vector2 pt ) => origin + TransformVector( pt ); // todo: unroll - public Vector3 TransformVector( Vector2 pt ) => axisX * pt.x + axisY * pt.y; // todo: unroll } \ No newline at end of file diff --git a/Runtime/Geometric Shapes/Plane2DIn3D.cs b/Runtime/Geometric Shapes/Plane2DIn3D.cs new file mode 100644 index 0000000..7633af4 --- /dev/null +++ b/Runtime/Geometric Shapes/Plane2DIn3D.cs @@ -0,0 +1,75 @@ +using UnityEngine; + +namespace Freya { + + /// An oriented 2D plane embedded in 3D space + public struct Plane2DIn3D { + + public static readonly Plane2DIn3D XY = new(default, Vector3.right, Vector3.up); + public static readonly Plane2DIn3D YZ = new(default, Vector3.up, Vector3.forward); + public static readonly Plane2DIn3D ZX = new(default, Vector3.forward, Vector3.right); + + public Vector3 origin, axisX, axisY; + + /// Creates an oriented 2D plane embedded in 3D space + /// The origin of the plane + /// The x axis direction of the plane + /// The y axis direction of the plane + public Plane2DIn3D( Vector3 origin, Vector3 axisX, Vector3 axisY ) => ( this.origin, this.axisX, this.axisY ) = ( origin, axisX.normalized, axisY.normalized ); + + /// Rotates this plane around the Y axis, setting the X axis, + /// so that the given point p is in the plane where x > 0 + /// The point to include in the plane + /// The included point in the 2D local space + public void RotateAroundYToInclude( Vector3 p, out Vector2 pLocal ) { + Vector3 pRel = p - origin; + float yProj = Vector3.Dot( axisY, pRel ); + axisX = ( pRel - axisY * yProj ).normalized; + float xProj = Vector3.Dot( axisX, pRel ); + pLocal = new Vector2( xProj, yProj ); + } + + /// Transforms a local 2D point to a 3D world space point + /// The local space point to transform + public Vector3 TransformPoint( Vector2 pt ) { + return new( // unrolled for performance + origin.x + axisX.x * pt.x + axisY.x * pt.y, + origin.y + axisX.y * pt.x + axisY.y * pt.y, + origin.z + axisX.z * pt.x + axisY.z * pt.y + ); + } + + /// Transforms a local 2D vector to a 3D world space vector, not taking position into account + /// The local space vector to transform + public Vector3 TransformVector( Vector2 vec ) { + return new( // unrolled for performance + axisX.x * vec.x + axisY.x * vec.y, + axisX.y * vec.x + axisY.y * vec.y, + axisX.z * vec.x + axisY.z * vec.y + ); + } + + /// Transform a 3D world space point to a local 2D point + /// World space point + public Vector2 InverseTransformPoint( Vector3 pt ) { + float rx = pt.x - origin.x; + float ry = pt.y - origin.y; + float rz = pt.z - origin.z; + return new( + axisX.x * rx + axisX.y * ry + axisX.z * rz, + axisY.x * rx + axisY.y * ry + axisY.z * rz + ); + } + + /// Transform a 3D world space vector to a local 2D vector + /// World space vector + public Vector2 InverseTransformVector( Vector3 vec ) { + return new( + axisX.x * vec.x + axisX.y * vec.y + axisX.z * vec.z, + axisY.x * vec.x + axisY.y * vec.y + axisY.z * vec.z + ); + } + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Shapes/Plane2DIn3D.cs.meta b/Runtime/Geometric Shapes/Plane2DIn3D.cs.meta new file mode 100644 index 0000000..828d594 --- /dev/null +++ b/Runtime/Geometric Shapes/Plane2DIn3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f19b780d08262e46bd6fae1ce9f7275 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 7faf902a16a0d00a8a000cc2f1a9c8503623d5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 23 Feb 2023 16:08:00 +0100 Subject: [PATCH 202/301] cleaned/separated catenary code --- Runtime/Curves/Catenary2D.cs | 225 +++---------------- Runtime/Curves/Catenary3D.cs | 49 ++-- Runtime/Curves/CatenaryToPoint.cs | 213 ++++++++++++++++++ Runtime/Curves/CatenaryToPoint.cs.meta | 11 + Runtime/Geometric Shapes/Transform2D.cs | 74 ++++++ Runtime/Geometric Shapes/Transform2D.cs.meta | 11 + 6 files changed, 360 insertions(+), 223 deletions(-) create mode 100644 Runtime/Curves/CatenaryToPoint.cs create mode 100644 Runtime/Curves/CatenaryToPoint.cs.meta create mode 100644 Runtime/Geometric Shapes/Transform2D.cs create mode 100644 Runtime/Geometric Shapes/Transform2D.cs.meta diff --git a/Runtime/Curves/Catenary2D.cs b/Runtime/Curves/Catenary2D.cs index 9bd5a1c..a269e4b 100644 --- a/Runtime/Curves/Catenary2D.cs +++ b/Runtime/Curves/Catenary2D.cs @@ -1,6 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -using System; using UnityEngine; namespace Freya { @@ -9,223 +8,65 @@ namespace Freya { public struct Catenary2D { enum Evaluability { - Unknown = 0, - Catenary, - LinearVertical, - LineSegment + NotReady, + Ready } - const int INTERVAL_SEARCH_ITERATIONS = 12; - const int BISECT_REFINE_COUNT = 14; - // data - Vector2 p0, p1; - float s; - - // cached state - float a; - Vector2 p; - Vector2 delta; - float arcLenSampleOffset; + Vector2 p1; + CatenaryToPoint catenary; // stores arc length + Transform2D space; // stores p0 and slack direction Evaluability evaluability; public float Length { - get => s; - set { - if( value != s ) - ( s, evaluability ) = ( value, Evaluability.Unknown ); - } + get => catenary.Length; + set => catenary.Length = value; // does not change evaluability of this type, since space hasn't changed } public Vector2 P0 { - get => p0; + get => space.Origin; set { - if( value != p0 ) - ( p0, evaluability ) = ( value, Evaluability.Unknown ); + if( value != space.Origin ) + ( space.Origin, evaluability ) = ( value, Evaluability.NotReady ); } } public Vector2 P1 { get => p1; set { if( value != p1 ) - ( p1, evaluability ) = ( value, Evaluability.Unknown ); + ( p1, evaluability ) = ( value, Evaluability.NotReady ); } } - - public bool IsVertical => MathF.Abs( p.x ) < 0.001f; - public bool IsStraightLine => s <= Vector2.Distance( p0, p1 ) * 1.00005f; - - /// Creates a catenary curve between two points, given an arc length s - /// The start of the curve - /// The end of the curve - /// The length of the curve. note: has to be equal or longer than the distance between the points - public Catenary2D( Vector2 p0, Vector2 p1, float s ) { - ( this.p0, this.p1, this.s ) = ( p0, p1, s ); - a = 0; - p = default; - delta = default; - arcLenSampleOffset = default; - evaluability = Evaluability.Unknown; + public Vector2 SlackDirection { + get => -space.AxisY; + set { + if( value != SlackDirection ) + ( space.AxisY, evaluability ) = ( -value, Evaluability.NotReady ); + } } - /// Evaluates a position on this catenary curve, given a t-value from 0 to 1 - /// A value from 0 to 1 along the whole curve - public Vector2 EvalByTValue( float t ) => Eval( t * s ); + /// + public Catenary2D( Vector2 p0, Vector2 p1, float length, Vector2 slackDirection ) { + space = default; + catenary = new CatenaryToPoint( p1 - p0, length ); + ( space.Origin, this.p1 ) = ( p0, p1 ); + evaluability = Evaluability.NotReady; + } - /// Evaluates a position on this catenary curve at the given arc length of sEval - /// The arc length along the curve to sample, relative to the first point - /// The derivative to sample. 1 = first derivative, 2 = second derivative - public Vector2 Eval( float sEval, int nthDerivative = 0 ) { + /// + public Vector3 Eval( float sEval, int n = 1 ) { ReadyForEvaluation(); - return nthDerivative switch { - 0 => evaluability switch { - Evaluability.Catenary => EvalCatPosByArcLength( sEval ), - Evaluability.LineSegment => EvalStraightLineByArcLength( sEval ), - Evaluability.LinearVertical => EvalVerticalLinearApproxByArcLength( sEval ), - Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) - }, - _ => evaluability switch { - Evaluability.Catenary => EvalCatDerivByArcLength( sEval ), - Evaluability.LineSegment => nthDerivative == 1 ? ( p1 - p0 ).normalized : Vector2.zero, // todo: this is incorrect - Evaluability.LinearVertical => new Vector2( 0, nthDerivative == 1 ? ( sEval < -( p.y - s ) / 2 ? -1 : 1 ) : 0 ), // todo: this might also be incorrect - Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) - } + return n switch { + 0 => space.TransformPoint( catenary.Eval( sEval, 0 ) ), + _ => space.TransformVector( catenary.Eval( sEval, n ) ) }; } - // straight line from p0 to p1 - Vector2 EvalStraightLineByArcLength( float sEval ) => Vector3.LerpUnclamped( p0, p1, sEval / s ); - - // almost completely vertical line when p0.x is approx. equal to p1.x - Vector2 EvalVerticalLinearApproxByArcLength( float sEval ) { - float x = Mathfs.Lerp( 0, p.x, sEval / s ); // just to make it not snap to x=0 - float b = ( p.y - s ) / 2; // bottom - float seg0 = -b; - float y = ( sEval < seg0 ) ? -sEval : -2 * seg0 + sEval; - return new Vector2( x, y ) + p0; - } - - // evaluates the position of the catenary at the given arc length, relative to the first point - Vector2 EvalCatPosByArcLength( float sEval ) { - sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x - float x = Catenary.EvalXByArcLength( sEval + arcLenSampleOffset, a ) + delta.x; - float y = EvalPassingThrough0( x ); - return new Vector2( x, y ) + p0; - } - - /// Evaluates the n-th derivative of the catenary at the given arc length - /// The arc length, relative to the first point - /// The derivative to evaluate - Vector2 EvalCatDerivByArcLength( float sEval, int n = 1 ) { - if( n == 0 ) - return EvalCatPosByArcLength( sEval ); - sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x - return Catenary.EvalDerivByArcLength( sEval + arcLenSampleOffset, a, n ); - } - - // Evaluate passing through the origin and p - float EvalPassingThrough0( float x ) => Catenary.Eval( x - delta.x, a ) + delta.y; - - // calculates p, a, delta, arcLenSampleOffset, and which evaluation method to use + // ensures the space transformation is ready void ReadyForEvaluation() { - if( evaluability != Evaluability.Unknown ) - return; - - // cache p, ie: p1 relative to p0 - p = p1 - p0; - - // CASE 1: - // first, test if it's a line segment - if( IsStraightLine ) { - evaluability = Evaluability.LineSegment; + if( evaluability == Evaluability.Ready ) return; - } - - // CASE 2: - // check if it's basically a fully vertical hanging chain - if( IsVertical ) { - evaluability = Evaluability.LinearVertical; - return; - } - - // CASE 3: - // Now we've got a catenary on our hands unless something explodes. - float c = MathF.Sqrt( s * s - p.y * p.y ); - float pAbsX = p.x.Abs(); // solve only in x > 0 - float R( float a ) => 2 * a * Mathfs.Sinh( pAbsX / ( 2 * a ) ) - c; // set up root solve function - - // find bounds of the root - float xRoot = ( p.x * p.x ) / ( 2 * s ); // intial guess based on freya's flawless heuristics - if( TryFindRootBounds( R, xRoot, out FloatRange xRange ) ) { - // refine range, if necessary (which is very likely) - if( Mathfs.Approximately( xRange.Length, 0 ) == false ) - RootFindBisections( R, ref xRange, BISECT_REFINE_COUNT ); // Catenary seems valid, with roots inside, refine the range - a = xRange.Center; // set a to the middle of the latest range - delta = CalcCatenaryDelta( a, p ); // find delta to pass through both points - arcLenSampleOffset = CalcArcLenSampleOffset( delta.x, a ); - evaluability = Evaluability.Catenary; - } else { - // CASE 4: - // something exploded, couldn't find a range, so let's use a straight line as a fallback - evaluability = Evaluability.LineSegment; - } - } - - // Calculates the arc length offset so that it's relative to the start of the chain when evaluating by arc length - static float CalcArcLenSampleOffset( float deltaX, float a ) => Catenary.EvalArcLen( -deltaX, a ); - - // Calculates the required offset to make a catenary pass through the origin and a point p - static Vector2 CalcCatenaryDelta( float a, Vector2 p ) { - Vector2 d; - d.x = p.x / 2 - a * Mathfs.Asinh( p.y / ( 2 * a * Mathfs.Sinh( p.x / ( 2 * a ) ) ) ); - d.y = -Catenary.Eval( d.x, a ); // technically -d.x but because of symmetry d.x works too - return d; - } - - // presumes a decreasing function with one root in x > 0 - // g = initial guess - static bool TryFindRootBounds( Func R, float g, out FloatRange xRange ) { - float y = R( g ); - xRange = new FloatRange( g, g ); - if( Mathfs.Approximately( y, 0 ) ) // somehow landed *on* our root in our initial guess - return true; - - bool findingUpper = y > 0; - - for( int n = 1; n <= INTERVAL_SEARCH_ITERATIONS; n++ ) { - if( findingUpper ) { - // It's positive - we found our lower bound - // exponentially search for upper bound - xRange.a = xRange.b; - xRange.b = g * MathF.Pow( 2, n ); - y = R( xRange.b ); - if( y < 0 ) - return true; // upper bound found! - } else { - // It's negative - we found our upper bound - // exponentially search for lower bound - xRange.b = xRange.a; - xRange.a = g * MathF.Pow( 2, -n ); - y = R( xRange.a ); - if( y > 0 ) - return true; // lower bound found! - } - } - - return false; // no root found - } - - static void RootFindBisections( Func F, ref FloatRange xRange, int iterationCount ) { - for( int i = 0; i < iterationCount; i++ ) - RootFindBisection( F, ref xRange ); - } - - static void RootFindBisection( Func F, ref FloatRange xRange ) { - float xInter = xRange.Center; // bisection - float yInter = F( xInter ); - if( yInter > 0 ) - xRange.a = xInter; // adjust left bound - else - xRange.b = xInter; // adjust right bound + catenary.P = space.InverseTransformPoint( p1 ); + evaluability = Evaluability.Ready; } } diff --git a/Runtime/Curves/Catenary3D.cs b/Runtime/Curves/Catenary3D.cs index 5b396ed..5b8bc8a 100644 --- a/Runtime/Curves/Catenary3D.cs +++ b/Runtime/Curves/Catenary3D.cs @@ -1,6 +1,5 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -using System; using UnityEngine; namespace Freya { @@ -15,8 +14,8 @@ enum Evaluability { // data Vector3 p1; - Catenary2D cat2D; // also stores arc length - Plane2DIn3D plane; // also stores p0 + CatenaryToPoint cat2D; // also stores arc length + Plane2DIn3D space; // stores p0 and slack direction Evaluability evaluability; public float Length { @@ -24,10 +23,10 @@ public float Length { set => cat2D.Length = value; // does not change evaluability of this type, since space hasn't changed } public Vector3 P0 { - get => plane.origin; + get => space.origin; set { - if( value != plane.origin ) - ( plane.origin, evaluability ) = ( value, Evaluability.NotReady ); + if( value != space.origin ) + ( space.origin, evaluability ) = ( value, Evaluability.NotReady ); } } public Vector3 P1 { @@ -38,10 +37,10 @@ public Vector3 P1 { } } public Vector3 SlackDirection { - get => -plane.axisY; + get => -space.axisY; set { if( value != SlackDirection ) - ( plane.axisY, evaluability ) = ( -value, Evaluability.NotReady ); + ( space.axisY, evaluability ) = ( -value, Evaluability.NotReady ); } } @@ -49,42 +48,30 @@ public Vector3 SlackDirection { /// The start of the curve /// The end of the curve /// The length of the curve. note: has to be equal or longer than the distance between the points - /// The direction of "gravity" for the arc. (0,-1,0) would create a hanging chain like arc + /// The direction of "gravity" for the arc public Catenary3D( Vector3 p0, Vector3 p1, float length, Vector3 slackDirection ) { - cat2D = new Catenary2D( default, default, length ); - ( plane.origin, plane.axisY, this.p1 ) = ( p0, -slackDirection, p1 ); + cat2D = new CatenaryToPoint( p1 - p0, length ); + ( space.origin, space.axisY, this.p1 ) = ( p0, -slackDirection, p1 ); evaluability = Evaluability.NotReady; - plane = default; + space = default; } - /// Creates a catenary curve between two points, given an arc length s, with slack/gravity direction pointing down - /// The start of the curve - /// The end of the curve - /// The length of the curve. note: has to be equal or longer than the distance between the points - public Catenary3D( Vector3 p0, Vector3 p1, float length ) : this( p0, p1, length, Vector3.down ) {} - - /// - public Vector3 Eval( float t ) => EvalDerivativeByArcLength( t * Length, n: 0 ); - - /// - public Vector3 EvalByArcLength( float sEval ) => EvalDerivativeByArcLength( sEval, n: 0 ); - - /// - public Vector3 EvalDerivativeByArcLength( float sEval, int n = 1 ) { + /// + public Vector3 Eval( float sEval, int n = 1 ) { ReadyForEvaluation(); return n switch { - 0 => plane.TransformPoint( cat2D.Eval( sEval, 0 ) ), - _ => plane.TransformVector( cat2D.Eval( sEval, n ) ) + 0 => space.TransformPoint( cat2D.Eval( sEval, 0 ) ), + _ => space.TransformVector( cat2D.Eval( sEval, n ) ) }; } - // calculates p, a, delta, arcLenSampleOffset, and which evaluation method to use + // ensures the space transformation is ready void ReadyForEvaluation() { if( evaluability == Evaluability.Ready ) return; // ready the embedded plane of the catenary and assign the 2D endpoint - plane.RotateAroundYToInclude( P1, out Vector2 p1Local ); - cat2D.P1 = p1Local; + space.RotateAroundYToInclude( P1, out Vector2 p1Local ); + cat2D.P = p1Local; evaluability = Evaluability.Ready; } diff --git a/Runtime/Curves/CatenaryToPoint.cs b/Runtime/Curves/CatenaryToPoint.cs new file mode 100644 index 0000000..10a7335 --- /dev/null +++ b/Runtime/Curves/CatenaryToPoint.cs @@ -0,0 +1,213 @@ +using System; +using UnityEngine; + +namespace Freya { + + /// A catenary curve from the origin to a point P + public struct CatenaryToPoint { + + enum Evaluability { + Unknown = 0, + Catenary, + LinearVertical, + LineSegment + } + + const int INTERVAL_SEARCH_ITERATIONS = 12; + const int BISECT_REFINE_COUNT = 14; + + // data + Vector2 p; + float s; + + // cached state + Evaluability evaluability; + float a; + Vector2 delta; + float arcLenSampleOffset; + + public CatenaryToPoint( Vector2 p, float s ) { + ( this.p, this.s ) = ( p, s ); + a = default; + delta = default; + arcLenSampleOffset = default; + evaluability = Evaluability.Unknown; + } + + public Vector2 P { + get => p; + set { + if( value != p ) + ( p, evaluability ) = ( value, Evaluability.Unknown ); + } + } + + public float Length { + get => s; + set { + if( value != s ) + ( s, evaluability ) = ( value, Evaluability.Unknown ); + } + } + + public bool IsVertical => MathF.Abs( p.x ) < 0.001f; + public bool IsStraightLine => s <= p.magnitude * 1.00005f; + + /// Evaluates a position on this catenary curve at the given arc length of sEval + /// The arc length along the curve to sample, relative to the first point + /// The derivative to sample. 1 = first derivative, 2 = second derivative + public Vector2 Eval( float sEval, int nthDerivative = 0 ) { + ReadyForEvaluation(); + return nthDerivative switch { + 0 => evaluability switch { + Evaluability.Catenary => EvalCatPosByArcLength( sEval ), + Evaluability.LineSegment => EvalStraightLineByArcLength( sEval ), + Evaluability.LinearVertical => EvalVerticalLinearApproxByArcLength( sEval ), + Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) + }, + _ => evaluability switch { + Evaluability.Catenary => EvalCatDerivByArcLength( sEval ), + Evaluability.LineSegment => nthDerivative == 1 ? p.normalized : Vector2.zero, + Evaluability.LinearVertical => new Vector2( 0, nthDerivative == 1 ? ( sEval < -( p.y - s ) / 2 ? -1 : 1 ) : 0 ), + Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) + } + }; + } + + // straight line from p0 to p1 + Vector2 EvalStraightLineByArcLength( float sEval ) => p * ( sEval / s ); + + // almost completely vertical line when p0.x is approx. equal to p1.x + Vector2 EvalVerticalLinearApproxByArcLength( float sEval ) { + float x = Mathfs.Lerp( 0, p.x, sEval / s ); // just to make it not snap to x=0 + float b = ( p.y - s ) / 2; // bottom + float seg0 = -b; + float y = ( sEval < seg0 ) ? -sEval : -2 * seg0 + sEval; + return new Vector2( x, y ); + } + + // evaluates the position of the catenary at the given arc length, relative to the first point + Vector2 EvalCatPosByArcLength( float sEval ) { + sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x + float x = Catenary.EvalXByArcLength( sEval + arcLenSampleOffset, a ) + delta.x; + float y = EvalPassingThrough0( x ); + return new Vector2( x, y ); + } + + /// Evaluates the n-th derivative of the catenary at the given arc length + /// The arc length, relative to the first point + /// The derivative to evaluate + Vector2 EvalCatDerivByArcLength( float sEval, int n = 1 ) { + if( n == 0 ) + return EvalCatPosByArcLength( sEval ); + sEval *= p.x.Sign(); // since we go backwards when p0.x < p1.x + return Catenary.EvalDerivByArcLength( sEval + arcLenSampleOffset, a, n ); + } + + // Evaluate passing through the origin and p + float EvalPassingThrough0( float x ) => Catenary.Eval( x - delta.x, a ) + delta.y; + + // calculates p, a, delta, arcLenSampleOffset, and which evaluation method to use + void ReadyForEvaluation() { + if( evaluability != Evaluability.Unknown ) + return; + + // CASE 1: + // first, test if it's a line segment + if( IsStraightLine ) { + evaluability = Evaluability.LineSegment; + return; + } + + // CASE 2: + // check if it's basically a fully vertical hanging chain + if( IsVertical ) { + evaluability = Evaluability.LinearVertical; + return; + } + + // CASE 3: + // Now we've got a catenary on our hands unless something explodes. + float c = MathF.Sqrt( s * s - p.y * p.y ); + float pAbsX = p.x.Abs(); // solve only in x > 0 + float R( float a ) => 2 * a * Mathfs.Sinh( pAbsX / ( 2 * a ) ) - c; // set up root solve function + + // find bounds of the root + float xRoot = ( p.x * p.x ) / ( 2 * s ); // intial guess based on freya's flawless heuristics + if( TryFindRootBounds( R, xRoot, out FloatRange xRange ) ) { + // refine range, if necessary (which is very likely) + if( Mathfs.Approximately( xRange.Length, 0 ) == false ) + RootFindBisections( R, ref xRange, BISECT_REFINE_COUNT ); // Catenary seems valid, with roots inside, refine the range + a = xRange.Center; // set a to the middle of the latest range + delta = CalcCatenaryDelta( a, p ); // find delta to pass through both points + arcLenSampleOffset = CalcArcLenSampleOffset( delta.x, a ); + evaluability = Evaluability.Catenary; + } else { + // CASE 4: + // something exploded, couldn't find a range, so let's use a straight line as a fallback + evaluability = Evaluability.LineSegment; + } + } + + // Calculates the arc length offset so that it's relative to the start of the chain when evaluating by arc length + static float CalcArcLenSampleOffset( float deltaX, float a ) => Catenary.EvalArcLen( -deltaX, a ); + + // Calculates the required offset to make a catenary pass through the origin and a point p + static Vector2 CalcCatenaryDelta( float a, Vector2 p ) { + Vector2 d; + d.x = p.x / 2 - a * Mathfs.Asinh( p.y / ( 2 * a * Mathfs.Sinh( p.x / ( 2 * a ) ) ) ); + d.y = -Catenary.Eval( d.x, a ); // technically -d.x but because of symmetry d.x works too + return d; + } + + // presumes a decreasing function with one root in x > 0 + // g = initial guess + static bool TryFindRootBounds( Func R, float g, out FloatRange xRange ) { + float y = R( g ); + xRange = new FloatRange( g, g ); + if( Mathfs.Approximately( y, 0 ) ) // somehow landed *on* our root in our initial guess + return true; + + bool findingUpper = y > 0; + + for( int n = 1; n <= INTERVAL_SEARCH_ITERATIONS; n++ ) { + if( findingUpper ) { + // It's positive - we found our lower bound + // exponentially search for upper bound + xRange.a = xRange.b; + xRange.b = g * MathF.Pow( 2, n ); + y = R( xRange.b ); + if( y < 0 ) + return true; // upper bound found! + } else { + // It's negative - we found our upper bound + // exponentially search for lower bound + xRange.b = xRange.a; + xRange.a = g * MathF.Pow( 2, -n ); + y = R( xRange.a ); + if( y > 0 ) + return true; // lower bound found! + } + } + + return false; // no root found + } + + static void RootFindBisections( Func F, ref FloatRange xRange, int iterationCount ) { + for( int i = 0; i < iterationCount; i++ ) + RootFindBisection( F, ref xRange ); + } + + static void RootFindBisection( Func F, ref FloatRange xRange ) { + float xInter = xRange.Center; // bisection + float yInter = F( xInter ); + if( yInter > 0 ) + xRange.a = xInter; // adjust left bound + else + xRange.b = xInter; // adjust right bound + } + + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/CatenaryToPoint.cs.meta b/Runtime/Curves/CatenaryToPoint.cs.meta new file mode 100644 index 0000000..b7d8d6d --- /dev/null +++ b/Runtime/Curves/CatenaryToPoint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 725fcdb7e0f11c54eae09804333f8693 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Geometric Shapes/Transform2D.cs b/Runtime/Geometric Shapes/Transform2D.cs new file mode 100644 index 0000000..e9b4b96 --- /dev/null +++ b/Runtime/Geometric Shapes/Transform2D.cs @@ -0,0 +1,74 @@ +using UnityEngine; + +namespace Freya { + + /// An orthonormal affine 2D transformation + public struct Transform2D { + + public float origin_x, origin_y; + public float axisX_x, axisX_y; + + public Vector2 Origin { + get => new(origin_x, origin_y); + set => ( origin_x, origin_y ) = ( value.x, value.y ); + } + public Vector2 AxisX { + get => new(axisX_x, axisX_y); + set => ( axisX_x, axisX_y ) = ( value.x, value.y ); + } + public Vector2 AxisY { + get => new(AxisY_x, AxisY_y); + set => ( axisX_x, axisX_y ) = ( +value.y, -value.x ); + } + public float AxisY_x => -axisX_y; + public float AxisY_y => +axisX_x; + + /// Creates an orthonormal affine 2D transformation + public Transform2D( Vector2 origin, Vector2 axisX ) { + this.origin_x = origin.x; + this.origin_y = origin.y; + this.axisX_x = axisX.x; + this.axisX_y = axisX.y; + } + + /// Transforms a local point to a world space point + /// The local space point to transform + public Vector2 TransformPoint( Vector2 pt ) { + return new( // unrolled for performance + origin_x + axisX_x * pt.x + AxisY_x * pt.y, + origin_y + axisX_y * pt.x + AxisY_y * pt.y + ); + } + + /// Transforms a local vector to a world space vector, not taking position into account + /// The local space vector to transform + public Vector2 TransformVector( Vector2 vec ) { + return new( // unrolled for performance + axisX_x * vec.x + AxisY_x * vec.y, + axisX_y * vec.x + AxisY_y * vec.y + ); + } + + /// Transform a world space point to a local point + /// World space point + public Vector2 InverseTransformPoint( Vector2 pt ) { + float rx = pt.x - origin_x; + float ry = pt.y - origin_y; + return new( + axisX_x * rx + axisX_y * ry, + AxisY_x * rx + AxisY_y * ry + ); + } + + /// Transform a world space vector to a local vector + /// World space vector + public Vector2 InverseTransformVector( Vector2 vec ) { + return new( + axisX_x * vec.x + axisX_y * vec.y, + AxisY_x * vec.x + AxisY_y * vec.y + ); + } + + } + +} \ No newline at end of file diff --git a/Runtime/Geometric Shapes/Transform2D.cs.meta b/Runtime/Geometric Shapes/Transform2D.cs.meta new file mode 100644 index 0000000..1cc15ab --- /dev/null +++ b/Runtime/Geometric Shapes/Transform2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77cf644ac96832c4aa1be178553ec82f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From d053714ecebcb8e7c07f275f8324b8a8ceff80fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Mar 2023 16:22:16 +0200 Subject: [PATCH 203/301] fixed Catenary3D not initializing correctly, closes #10 --- Runtime/Curves/Catenary3D.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Curves/Catenary3D.cs b/Runtime/Curves/Catenary3D.cs index 5b8bc8a..9ea9d45 100644 --- a/Runtime/Curves/Catenary3D.cs +++ b/Runtime/Curves/Catenary3D.cs @@ -51,9 +51,9 @@ public Vector3 SlackDirection { /// The direction of "gravity" for the arc public Catenary3D( Vector3 p0, Vector3 p1, float length, Vector3 slackDirection ) { cat2D = new CatenaryToPoint( p1 - p0, length ); + space.axisX = default; // set on first evaluation by RotateAroundYToInclude ( space.origin, space.axisY, this.p1 ) = ( p0, -slackDirection, p1 ); evaluability = Evaluability.NotReady; - space = default; } /// From 3401762b4a09bae2b8f35cde7fb9cd91a5c628e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Mar 2023 16:31:19 +0200 Subject: [PATCH 204/301] added GetEnumerator to IntRange --- Runtime/Numerics/IntRange.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Runtime/Numerics/IntRange.cs b/Runtime/Numerics/IntRange.cs index efe7e27..1476d70 100644 --- a/Runtime/Numerics/IntRange.cs +++ b/Runtime/Numerics/IntRange.cs @@ -52,6 +52,16 @@ public override string ToString() { return toStrBuilder.ToString(); } + public IntRangeEnumerator GetEnumerator() => new IntRangeEnumerator( this ); + + public struct IntRangeEnumerator /*: IEnumerator*/ { + readonly IntRange intRange; + int currValue; + public IntRangeEnumerator( IntRange range ) => ( this.intRange, currValue ) = ( range, range.start - 1 ); + public bool MoveNext() => ++currValue <= intRange.Last; + public int Current => currValue; + } + } } \ No newline at end of file From fe1aa683a83f31df4e0bd66b30a18e6d2c2948a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 7 Jun 2023 10:48:57 +0200 Subject: [PATCH 205/301] fixed catenary derivatives beyond the first --- Runtime/Curves/CatenaryToPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Curves/CatenaryToPoint.cs b/Runtime/Curves/CatenaryToPoint.cs index 10a7335..68c1dee 100644 --- a/Runtime/Curves/CatenaryToPoint.cs +++ b/Runtime/Curves/CatenaryToPoint.cs @@ -66,7 +66,7 @@ public Vector2 Eval( float sEval, int nthDerivative = 0 ) { Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) }, _ => evaluability switch { - Evaluability.Catenary => EvalCatDerivByArcLength( sEval ), + Evaluability.Catenary => EvalCatDerivByArcLength( sEval, nthDerivative ), Evaluability.LineSegment => nthDerivative == 1 ? p.normalized : Vector2.zero, Evaluability.LinearVertical => new Vector2( 0, nthDerivative == 1 ? ( sEval < -( p.y - s ) / 2 ? -1 : 1 ) : 0 ), Evaluability.Unknown or _ => throw new Exception( "Failed to evaluate catenary, couldn't calculate evaluability" ) From 04b64bb396228800576897a17a3a2026debca3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 9 Jun 2023 10:15:03 +0200 Subject: [PATCH 206/301] additional polynomial 4D constructor --- Runtime/Curves/Polynomial4D.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index 521bee2..70153b2 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -49,6 +49,14 @@ public Polynomial4D( Vector4 c0, Vector4 c1, Vector4 c2 ) { this.w = new Polynomial( c0.w, c1.w, c2.w, 0 ); } + /// + public Polynomial4D( Vector4 c0, Vector4 c1 ) { + this.x = new Polynomial( c0.x, c1.x, 0, 0 ); + this.y = new Polynomial( c0.y, c1.y, 0, 0 ); + this.z = new Polynomial( c0.z, c1.z, 0, 0 ); + this.w = new Polynomial( c0.w, c1.w, 0, 0 ); + } + /// public Polynomial4D( Vector4Matrix4x1 coefficients ) => ( x, y, z, w ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ), new Polynomial( coefficients.W ) ); From 034998f4a8791fc8bd48577fb73e6e50720f0693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 11 Jun 2023 14:07:41 +0200 Subject: [PATCH 207/301] arc orientation, frenet-serret & curvature axes also added 2D variants, since they can be optimized a bunch --- Runtime/Mathfs.cs | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 9dd4c58..5ae63f8 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1204,7 +1204,7 @@ public static Pose Lerp( Pose a, Pose b, float t ) => } /// Returns the frenet-serret (curvature-based) orientation of a point in a curve with the given velocity and acceleration values, where the Z direction is tangent to the curve. - /// The X axis will point to the inner arc of the current curvature + /// The X axis will point to the inner arc of the current curvature, while Y is the axis of rotation /// The first derivative of the point in the curve /// The second derivative of the point in the curve [MethodImpl( INLINE )] public static Quaternion GetArcOrientation( Vector3 velocity, Vector3 acceleration ) { @@ -1212,6 +1212,47 @@ public static Pose Lerp( Pose a, Pose b, float t ) => return Quaternion.LookRotation( velocity, binormal ); } + /// + [MethodImpl( INLINE )] public static Quaternion GetArcOrientation( Vector2 velocity, Vector2 acceleration ) { + Vector3 binormal = new Vector3( 0, 0, Sign( Determinant( velocity, acceleration ) ) ); + return Quaternion.LookRotation( velocity, binormal ); + } + + /// Returns the frenet-serret (curvature-based) orientation of a point in a curve with the given velocity and acceleration values, where the X direction is tangent to the curve. + /// The Y axis (the normal) will point to the inner arc of the current curvature, while Z is the axis of rotation + /// The first derivative of the point in the curve + /// The second derivative of the point in the curve + [MethodImpl( INLINE )] public static Quaternion GetFrenetSerretOrientation( Vector3 velocity, Vector3 acceleration ) { + GetCurvatureOrientationAxes( velocity, acceleration, out _, out Vector3 N, out Vector3 B ); + return Quaternion.LookRotation( B, N ); + } + + /// + [MethodImpl( INLINE )] public static Quaternion GetFrenetSerretOrientation( Vector2 velocity, Vector2 acceleration ) { + GetCurvatureOrientationAxes( velocity, acceleration, out _, out Vector3 N, out Vector3 B ); + return Quaternion.LookRotation( B, N ); + } + + /// Returns the frenet-serret (curvature-based) orientation axes of a point in a curve with the given velocity and acceleration values + /// The first derivative of the point in the curve + /// The second derivative of the point in the curve + /// The axis pointing along the curve + /// The axis pointing to the inside of the curve + /// The axis of rotation of the curve + [MethodImpl( INLINE )] public static void GetCurvatureOrientationAxes( Vector3 velocity, Vector3 acceleration, out Vector3 tangent, out Vector3 normal, out Vector3 binormal ) { + tangent = velocity.normalized; + binormal = Vector3.Cross( velocity, acceleration ).normalized; + normal = Vector3.Cross( binormal, tangent ); + } + + /// + [MethodImpl( INLINE )] public static void GetCurvatureOrientationAxes( Vector2 velocity, Vector2 acceleration, out Vector3 tangent, out Vector3 normal, out Vector3 binormal ) { + tangent = velocity.normalized; + float sign = Sign( Determinant( velocity, acceleration ) ); + binormal = new Vector3( 0, 0, sign ); + normal = new Vector3( -sign * tangent.y, sign * tangent.x, 0 ); + } + /// Returns the signed angle between a and b, in the range -tau/2 to tau/2 (-pi to pi) [MethodImpl( INLINE )] public static float SignedAngle( Vector2 a, Vector2 b ) => AngleBetween( a, b ) * MathF.Sign( Determinant( a, b ) ); // -tau/2 to tau/2 From 63bcfc55d2205c89992b125cf6667986df019d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 26 Jun 2023 10:15:24 +0200 Subject: [PATCH 208/301] documentation typo fix --- Runtime/Numerics/RationalMatrix3x3.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Numerics/RationalMatrix3x3.cs b/Runtime/Numerics/RationalMatrix3x3.cs index 4308f70..3f3be21 100644 --- a/Runtime/Numerics/RationalMatrix3x3.cs +++ b/Runtime/Numerics/RationalMatrix3x3.cs @@ -5,7 +5,7 @@ namespace Freya { - /// A 4x4 matrix using exact rational number representation + /// A 3x3 matrix using exact rational number representation public readonly struct RationalMatrix3x3 { public static readonly RationalMatrix3x3 Identity = new RationalMatrix3x3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); From 6d9b8e5a375467bbcd50f175f1d96a23a40c0da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 26 Jun 2023 10:15:31 +0200 Subject: [PATCH 209/301] added Matrix3x3 --- Runtime/Numerics/Matrix3x3.cs | 156 +++++++++++++++++++++++++++++ Runtime/Numerics/Matrix3x3.cs.meta | 11 ++ 2 files changed, 167 insertions(+) create mode 100644 Runtime/Numerics/Matrix3x3.cs create mode 100644 Runtime/Numerics/Matrix3x3.cs.meta diff --git a/Runtime/Numerics/Matrix3x3.cs b/Runtime/Numerics/Matrix3x3.cs new file mode 100644 index 0000000..d246804 --- /dev/null +++ b/Runtime/Numerics/Matrix3x3.cs @@ -0,0 +1,156 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Globalization; +using UnityEngine; + +namespace Freya { + + /// A 3x3 matrix + public readonly struct Matrix3x3 { + + public static readonly Matrix3x3 Identity = new Matrix3x3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); + public static readonly Matrix3x3 Zero = new Matrix3x3( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + + public readonly float m00, m01, m02; + public readonly float m10, m11, m12; + public readonly float m20, m21, m22; + + public Matrix3x3( float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22 ) { + ( this.m00, this.m01, this.m02 ) = ( m00, m01, m02 ); + ( this.m10, this.m11, this.m12 ) = ( m10, m11, m12 ); + ( this.m20, this.m21, this.m22 ) = ( m20, m21, m22 ); + } + + public Matrix3x3( Matrix4x4 m ) { + ( this.m00, this.m01, this.m02 ) = ( m.m00, m.m01, m.m02 ); + ( this.m10, this.m11, this.m12 ) = ( m.m10, m.m11, m.m12 ); + ( this.m20, this.m21, this.m22 ) = ( m.m20, m.m21, m.m22 ); + } + + public Matrix3x3( Quaternion q ) { + Matrix4x4 m = Matrix4x4.Rotate( q ); + ( this.m00, this.m01, this.m02 ) = ( m.m00, m.m01, m.m02 ); + ( this.m10, this.m11, this.m12 ) = ( m.m10, m.m11, m.m12 ); + ( this.m20, this.m21, this.m22 ) = ( m.m20, m.m21, m.m22 ); + } + + public static Matrix3x3 Scale( Vector3 s ) { + return new Matrix3x3( + s.x, 0, 0, + 0, s.y, 0, + 0, 0, s.z + ); + } + + public Matrix3x3 NormalizeColumns() { + double lenX = Math.Sqrt( m00 * m00 + m10 * m10 + m20 * m20 ); + double lenY = Math.Sqrt( m01 * m01 + m11 * m11 + m21 * m21 ); + double lenZ = Math.Sqrt( m02 * m02 + m12 * m12 + m22 * m22 ); + return new( + (float)( m00 / lenX ), (float)( m01 / lenY ), (float)( m02 / lenZ ), + (float)( m10 / lenX ), (float)( m11 / lenY ), (float)( m12 / lenZ ), + (float)( m20 / lenX ), (float)( m21 / lenY ), (float)( m22 / lenZ )); + } + + public float this[ int row, int column ] { + get { + return ( row, column ) switch { + (0, 0) => m00, + (0, 1) => m01, + (0, 2) => m02, + (1, 0) => m10, + (1, 1) => m11, + (1, 2) => m12, + (2, 0) => m20, + (2, 1) => m21, + (2, 2) => m22, + _ => throw new IndexOutOfRangeException( $"Matrix row/column indices have to be from 0 to 2, got: ({row},{column})" ) + }; + } + } + + /// Returns the inverse of this matrix. Throws a division by zero exception if it's not invertible + public Matrix3x3 Inverse { + get { + float A1212 = m11 * m22 - m12 * m21; + float A0212 = m10 * m22 - m12 * m20; + float A0112 = m10 * m21 - m11 * m20; + float det = m00 * A1212 - m01 * A0212 + m02 * A0112; + + if( det == 0 ) + throw new DivideByZeroException( "The matrix is not invertible - its determinant is 0" ); + + return new Matrix3x3( + A1212, m02 * m21 - m01 * m22, m01 * m12 - m02 * m11, + -A0212, m00 * m22 - m02 * m20, m10 * m02 - m00 * m12, + A0112, m20 * m01 - m00 * m21, m00 * m11 - m10 * m01 + ) / det; + } + } + + /// Returns the determinant of this matrix + public float Determinant { + get { + float A1212 = m11 * m22 - m12 * m21; + float A0212 = m10 * m22 - m12 * m20; + float A0112 = m10 * m21 - m11 * m20; + return m00 * A1212 - m01 * A0212 + m02 * A0112; + } + } + public Matrix3x3 Transpose => + new(m00, m10, m20, + m01, m11, m21, + m02, m12, m22); + + public override string ToString() => ToStringMatrix().ToValueTableString(); + + public string[,] ToStringMatrix() { + return new[,] { + { m00.ToString( CultureInfo.InvariantCulture ), m01.ToString( CultureInfo.InvariantCulture ), m02.ToString( CultureInfo.InvariantCulture ) }, + { m10.ToString( CultureInfo.InvariantCulture ), m11.ToString( CultureInfo.InvariantCulture ), m12.ToString( CultureInfo.InvariantCulture ) }, + { m20.ToString( CultureInfo.InvariantCulture ), m21.ToString( CultureInfo.InvariantCulture ), m22.ToString( CultureInfo.InvariantCulture ) } + }; + } + + public static explicit operator Matrix3x3( Matrix4x4 m ) => new(m.m00, m.m01, m.m02, m.m10, m.m11, m.m12, m.m20, m.m21, m.m22); + + public static Matrix3x3 operator *( Matrix3x3 c, float v ) => + new(c.m00 * v, c.m01 * v, c.m02 * v, + c.m10 * v, c.m11 * v, c.m12 * v, + c.m20 * v, c.m21 * v, c.m22 * v); + + public static Matrix3x3 operator /( Matrix3x3 c, float v ) => c * ( 1f / v ); + + public static Matrix3x3 operator *( Matrix3x3 a, Matrix3x3 b ) { + float GetEntry( int r, int c ) => + a[r, 0] * b[0, c] + + a[r, 1] * b[1, c] + + a[r, 2] * b[2, c]; + + return new Matrix3x3( + GetEntry( 0, 0 ), GetEntry( 0, 1 ), GetEntry( 0, 2 ), + GetEntry( 1, 0 ), GetEntry( 1, 1 ), GetEntry( 1, 2 ), + GetEntry( 2, 0 ), GetEntry( 2, 1 ), GetEntry( 2, 2 ) + ); + } + + public static Matrix3x1 operator *( Matrix3x3 c, Matrix3x1 m ) => + new(m.m0 * c.m00 + m.m1 * c.m01 + m.m2 * c.m02, + m.m0 * c.m10 + m.m1 * c.m11 + m.m2 * c.m12, + m.m0 * c.m20 + m.m1 * c.m21 + m.m2 * c.m22); + + public static Vector3 operator *( Matrix3x3 c, Vector3 v ) => + new(v.x * c.m00 + v.y * c.m01 + v.z * c.m02, + v.x * c.m10 + v.y * c.m11 + v.z * c.m12, + v.x * c.m20 + v.y * c.m21 + v.z * c.m22); + + public static Vector2Matrix3x1 operator *( Matrix3x3 c, Vector2Matrix3x1 m ) => new(c * m.X, c * m.Y); + + public static Vector3Matrix3x1 operator *( Matrix3x3 c, Vector3Matrix3x1 m ) => new(c * m.X, c * m.Y, c * m.Z); + + public static Vector4Matrix3x1 operator *( Matrix3x3 c, Vector4Matrix3x1 m ) => new(c * m.X, c * m.Y, c * m.Z, c * m.W); + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/Matrix3x3.cs.meta b/Runtime/Numerics/Matrix3x3.cs.meta new file mode 100644 index 0000000..b1e5470 --- /dev/null +++ b/Runtime/Numerics/Matrix3x3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d17543dcc01204c4fa46d00e24181170 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 71057b2337e035d31e23afcb45fa830888bf81c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 26 Jun 2023 10:16:03 +0200 Subject: [PATCH 210/301] FloatRange.Wrap interval check --- Runtime/Numerics/FloatRange.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 838684a..1579c18 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -76,7 +76,11 @@ public bool Overlaps( FloatRange other ) { /// Wraps/repeats the input value to stay within this range /// The value to wrap/repeat in this interval - public float Wrap( float value ) => a + Mathfs.Repeat( value - a, b - a ); + public float Wrap( float value ) { + if( value >= a && value < b ) + return value; + return a + Mathfs.Repeat( value - a, b - a ); + } /// Clamps the input value to this range /// The value to clamp to this interval From 0ea2750b84d57aa781eb6b6ae7ff1b10188e22c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 26 Jun 2023 10:16:15 +0200 Subject: [PATCH 211/301] added GetLookRotation2D --- Runtime/Mathfs.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 5ae63f8..a33d5c4 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1253,6 +1253,19 @@ public static Pose Lerp( Pose a, Pose b, float t ) => normal = new Vector3( -sign * tangent.y, sign * tangent.x, 0 ); } + /// Returns a 2D look-orientation (X forward), ensuring the returned Y axis is upright with regards to the up vector + /// The forward direction of the rotation (X axis) + /// The reference up direction of the rotation to align to, usually pointing along world up + [MethodImpl( INLINE )] public static Quaternion GetLookRotation2D( Vector2 forward, Vector2 up ) { + int sign = Determinant( forward, up ) >= 0 ? 1 : -1; + Vector2 Y = new(-sign * forward.y, sign * forward.x); + Vector3 Z = new(0, 0, sign); + return Quaternion.LookRotation( Z, Y ); + } + + /// + [MethodImpl( INLINE )] public static Quaternion GetLookRotation2D( Vector2 forward ) => GetLookRotation2D( forward, Vector2.up ); + /// Returns the signed angle between a and b, in the range -tau/2 to tau/2 (-pi to pi) [MethodImpl( INLINE )] public static float SignedAngle( Vector2 a, Vector2 b ) => AngleBetween( a, b ) * MathF.Sign( Determinant( a, b ) ); // -tau/2 to tau/2 From 33d2640df353007194280b72058a4f0e05f10924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 26 Jun 2023 10:17:16 +0200 Subject: [PATCH 212/301] added ScaleParameterSpace to Polynomial types --- Runtime/Curves/Polynomial.cs | 13 +++++++++++++ Runtime/Curves/Polynomial2D.cs | 12 +++++++++++- Runtime/Curves/Polynomial3D.cs | 11 +++++++++++ Runtime/Curves/Polynomial4D.cs | 12 ++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index fe04853..bf3a866 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -127,6 +127,19 @@ public Polynomial Compose( float g0, float g1 ) { ); } + /// Scales the parameter space by a factor. For example, the output in the interval [0 to 1] will now be in the range [0 to factor] + /// The factor to scale the input parameters by + public Polynomial ScaleParameterSpace( float factor ) { + float factor2 = factor * factor; + float factor3 = factor2 * factor; + return new Polynomial( + c0, + c1 / factor, + c2 / factor2, + c3 / factor3 + ); + } + /// Splits the 0-1 range into two distinct polynomials at the given parameter value u, where both new curves cover the same total range with their individual 0-1 ranges /// The parameter value to split at public (Polynomial pre, Polynomial post) Split01( float u ) { diff --git a/Runtime/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs index a698977..9aa9cca 100644 --- a/Runtime/Curves/Polynomial2D.cs +++ b/Runtime/Curves/Polynomial2D.cs @@ -43,7 +43,7 @@ public Polynomial2D( Vector2 c0, Vector2 c1, Vector2 c2 ) { this.x = new Polynomial( c0.x, c1.x, c2.x ); this.y = new Polynomial( c0.y, c1.y, c2.y ); } - + /// public Polynomial2D( Vector2 c0, Vector2 c1 ) { this.x = new Polynomial( c0.x, c1.x, 0, 0 ); @@ -68,6 +68,16 @@ public Polynomial2D( Vector2 c0, Vector2 c1 ) { /// public Polynomial2D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 )); + /// + public Polynomial2D ScaleParameterSpace( float factor ) { + float factor2 = factor * factor; + float factor3 = factor2 * factor; + return new Polynomial2D( + new Polynomial( x.c0, x.c1 / factor, x.c2 / factor2, x.c3 / factor3 ), + new Polynomial( y.c0, y.c1 / factor, y.c2 / factor2, y.c3 / factor3 ) + ); + } + /// Returns the tight axis-aligned bounds of the curve in the unit interval public Rect GetBounds01() => FloatRange.ToRect( x.OutputRange01, y.OutputRange01 ); diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index 4ab1073..68f4eee 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -71,6 +71,17 @@ public Polynomial3D( Vector3 c0, Vector3 c1 ) { /// public Polynomial3D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 )); + /// + public Polynomial3D ScaleParameterSpace( float factor ) { + float factor2 = factor * factor; + float factor3 = factor2 * factor; + return new Polynomial3D( + new Polynomial( x.c0, x.c1 / factor, x.c2 / factor2, x.c3 / factor3 ), + new Polynomial( y.c0, y.c1 / factor, y.c2 / factor2, y.c3 / factor3 ), + new Polynomial( z.c0, z.c1 / factor, z.c2 / factor2, z.c3 / factor3 ) + ); + } + /// public Bounds GetBounds01() => FloatRange.ToBounds( x.OutputRange01, y.OutputRange01, z.OutputRange01 ); diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index 70153b2..e0070ef 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -74,6 +74,18 @@ public Polynomial4D( Vector4 c0, Vector4 c1 ) { /// public Polynomial4D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 ), w.Compose( g0, g1 )); + + /// + public Polynomial4D ScaleParameterSpace( float factor ) { + float factor2 = factor * factor; + float factor3 = factor2 * factor; + return new Polynomial4D( + new Polynomial( x.c0, x.c1 / factor, x.c2 / factor2, x.c3 / factor3 ), + new Polynomial( y.c0, y.c1 / factor, y.c2 / factor2, y.c3 / factor3 ), + new Polynomial( z.c0, z.c1 / factor, z.c2 / factor2, z.c3 / factor3 ), + new Polynomial( w.c0, w.c1 / factor, w.c2 / factor2, w.c3 / factor3 ) + ); + } /// public (FloatRange x, FloatRange y, FloatRange z, FloatRange w) GetBounds01() => ( x.OutputRange01, y.OutputRange01, z.OutputRange01, w.OutputRange01 ); From 06385c0b06cc4e0297747a78b55fd01c50a86701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 3 Jul 2023 10:26:38 +0200 Subject: [PATCH 213/301] fixed bug in box.Contains(), closes #12 --- Runtime/Geometric Shapes/Box.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Geometric Shapes/Box.cs b/Runtime/Geometric Shapes/Box.cs index 19a56fb..aa61841 100644 --- a/Runtime/Geometric Shapes/Box.cs +++ b/Runtime/Geometric Shapes/Box.cs @@ -184,12 +184,12 @@ public float SurfaceArea { public partial struct Box2D { /// Returns whether or not a point is inside this box /// The point to test if it's inside - [MethodImpl( INLINE )] public bool Contains( Vector2 point ) => Abs( point.x ) - extents.x <= 0 && Abs( point.y ) - extents.y <= 0; + [MethodImpl( INLINE )] public bool Contains( Vector2 point ) => Abs( point.x - center.x ) - extents.x <= 0 && Abs( point.y - center.y ) - extents.y <= 0; } public partial struct Box3D { /// - [MethodImpl( INLINE )] public bool Contains( Vector3 point ) => Abs( point.x ) - extents.x <= 0 && Abs( point.y ) - extents.y <= 0 && Abs( point.z ) - extents.z <= 0; + [MethodImpl( INLINE )] public bool Contains( Vector3 point ) => Abs( point.x - center.x ) - extents.x <= 0 && Abs( point.y - center.y ) - extents.y <= 0 && Abs( point.z - center.z ) - extents.z <= 0; } #endregion From 775979fb3544be2458f2e6d3d0af5debf0f05bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 12 Jul 2023 12:31:45 +0200 Subject: [PATCH 214/301] added scaling support to circles --- Runtime/Geometric Shapes/Circle.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Runtime/Geometric Shapes/Circle.cs b/Runtime/Geometric Shapes/Circle.cs index 216ba53..66cbcfe 100644 --- a/Runtime/Geometric Shapes/Circle.cs +++ b/Runtime/Geometric Shapes/Circle.cs @@ -309,6 +309,9 @@ public partial struct Circle2D { return new Circle2D( point + normal * signedRadius, Abs( signedRadius ) ); } + public static Circle2D operator *( Circle2D circle, float value ) => new(circle.center * value, circle.radius * value); + public static Circle2D operator *( float value, Circle2D circle ) => new(circle.center * value, circle.radius * value); + } public partial struct Circle3D { From 2c118fab441198f84887d4bafa11ccb88d8aedb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 12 Jul 2023 12:32:06 +0200 Subject: [PATCH 215/301] Matrix3x3 column vector constructor --- Runtime/Numerics/Matrix3x3.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Runtime/Numerics/Matrix3x3.cs b/Runtime/Numerics/Matrix3x3.cs index d246804..fcf421b 100644 --- a/Runtime/Numerics/Matrix3x3.cs +++ b/Runtime/Numerics/Matrix3x3.cs @@ -22,6 +22,18 @@ public Matrix3x3( float m00, float m01, float m02, float m10, float m11, float m ( this.m20, this.m21, this.m22 ) = ( m20, m21, m22 ); } + public Matrix3x3( Vector3 col0, Vector3 col1, Vector3 col2 ) { + m00 = col0.x; + m10 = col0.y; + m20 = col0.z; + m01 = col1.x; + m11 = col1.y; + m21 = col1.z; + m02 = col2.x; + m12 = col2.y; + m22 = col2.z; + } + public Matrix3x3( Matrix4x4 m ) { ( this.m00, this.m01, this.m02 ) = ( m.m00, m.m01, m.m02 ); ( this.m10, this.m11, this.m12 ) = ( m.m10, m.m11, m.m12 ); From 35a391d3fe132ef3aa7fe81fe83b227b7e0af0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 12 Jul 2023 12:32:14 +0200 Subject: [PATCH 216/301] Matrix3x3.AverageScale --- Runtime/Numerics/Matrix3x3.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Runtime/Numerics/Matrix3x3.cs b/Runtime/Numerics/Matrix3x3.cs index fcf421b..cc8226f 100644 --- a/Runtime/Numerics/Matrix3x3.cs +++ b/Runtime/Numerics/Matrix3x3.cs @@ -163,6 +163,14 @@ float GetEntry( int r, int c ) => public static Vector4Matrix3x1 operator *( Matrix3x3 c, Vector4Matrix3x1 m ) => new(c * m.X, c * m.Y, c * m.Z, c * m.W); + public float AverageScale() { + return ( + MathF.Sqrt( m00 * m00 + m10 * m10 + m20 * m20 ) + + MathF.Sqrt( m01 * m01 + m11 * m11 + m21 * m21 ) + + MathF.Sqrt( m02 * m02 + m12 * m12 + m22 * m22 ) + ) / 3; + } + } } \ No newline at end of file From 7ba8cd8286d9dfd8c146d2fedb646f2e513946c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 12 Jul 2023 12:32:32 +0200 Subject: [PATCH 217/301] Matrix4x4.AverageScale extension method --- Runtime/Extensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index c6317db..31cde0b 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -542,6 +542,14 @@ public static string ToValueTableString( this string[,] m ) { #region Matrix extensions + public static float AverageScale( this Matrix4x4 m ) { + return ( + ( (Vector3)m.GetColumn( 0 ) ).magnitude + + ( (Vector3)m.GetColumn( 1 ) ).magnitude + + ( (Vector3)m.GetColumn( 2 ) ).magnitude + ) / 3; + } + public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => new Matrix4x1( m.m00 * v.m0 + m.m01 * v.m1 + m.m02 * v.m2 + m.m03 * v.m3, From c223a03a993294d52c208897e9562aa734efc9ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 12 Jul 2023 12:33:17 +0200 Subject: [PATCH 218/301] quadratic root solve optimization --- Runtime/Curves/Polynomial.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index bf3a866..70f6704 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -308,10 +308,10 @@ static ResultsMax2 SolveQuadraticRoots( float a, float b, float c ) { if( ValueAlmost0( rootContent ) ) return new ResultsMax2( -b / ( 2 * a ) ); // two equivalent solutions at one point - if( rootContent >= 0 ) { - float root = MathF.Sqrt( rootContent ); - float r0 = ( -b - root ) / ( 2 * a ); // crosses at two points - float r1 = ( -b + root ) / ( 2 * a ); + if( rootContent >= 0 ) { // crosses at two points + float u = -b * -( b < 0 ? -1 : 1 ) * MathF.Sqrt( rootContent ); + float r0 = u / ( 2 * a ); + float r1 = ( 2 * c ) / u; return new ResultsMax2( MathF.Min( r0, r1 ), MathF.Max( r0, r1 ) ); } From 9e7e3a8b2c24dad157e008614711421d3343b05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 12 Jul 2023 12:33:52 +0200 Subject: [PATCH 219/301] made Transform2D serializable + more functions --- Runtime/Geometric Shapes/Transform2D.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Runtime/Geometric Shapes/Transform2D.cs b/Runtime/Geometric Shapes/Transform2D.cs index e9b4b96..9ce9981 100644 --- a/Runtime/Geometric Shapes/Transform2D.cs +++ b/Runtime/Geometric Shapes/Transform2D.cs @@ -1,8 +1,10 @@ +using System; using UnityEngine; namespace Freya { /// An orthonormal affine 2D transformation + [Serializable] public struct Transform2D { public float origin_x, origin_y; @@ -40,6 +42,14 @@ public Vector2 TransformPoint( Vector2 pt ) { ); } + /// + public Vector2 TransformPoint( float x, float y ) { + return new( // unrolled for performance + origin_x + axisX_x * x + AxisY_x * y, + origin_y + axisX_y * x + AxisY_y * y + ); + } + /// Transforms a local vector to a world space vector, not taking position into account /// The local space vector to transform public Vector2 TransformVector( Vector2 vec ) { @@ -49,6 +59,14 @@ public Vector2 TransformVector( Vector2 vec ) { ); } + /// + public Vector2 TransformVector( float x, float y ) { + return new( // unrolled for performance + axisX_x * x + AxisY_x * y, + axisX_y * x + AxisY_y * y + ); + } + /// Transform a world space point to a local point /// World space point public Vector2 InverseTransformPoint( Vector2 pt ) { @@ -69,6 +87,13 @@ public Vector2 InverseTransformVector( Vector2 vec ) { ); } + public static Transform2D operator *( Transform2D a, Transform2D b ) { + return new Transform2D( + a.TransformPoint( b.origin_x, b.origin_y ), + a.TransformVector( b.axisX_x, b.axisX_y ) + ); + } + } } \ No newline at end of file From 14115081cab77595dbe902d888c2de8f373ac3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 12 Jul 2023 12:34:17 +0200 Subject: [PATCH 220/301] Arc2D now uses a Transform2D for placement and is now serializable too bc why not --- Runtime/Curves/Arc2D.cs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Runtime/Curves/Arc2D.cs b/Runtime/Curves/Arc2D.cs index e0df251..e188c73 100644 --- a/Runtime/Curves/Arc2D.cs +++ b/Runtime/Curves/Arc2D.cs @@ -4,12 +4,11 @@ namespace Freya { /// a 2D arc with support for straight lines + [Serializable] public struct Arc2D { /// The starting point of the arc - public Vector2 startPoint; - /// The normalized tangent direction at the start of the arc - public Vector2 startTangent; + public Transform2D placement; /// The signed curvature of the arc, equal to 1/radius (0 = straight line, 1 = turning left, -1 = turning right) public float curvature; /// The length of the arc @@ -20,7 +19,9 @@ public struct Arc2D { /// The center of the circle traced by the arc. Returns infinity if this segment is linear, ie: if curvature is 0 public Vector2 CircleCenter => StartNormal / curvature; /// The normal direction at the start of the arc - public Vector2 StartNormal => startTangent.Rotate90CCW(); + public Vector2 StartNormal => placement.AxisY; + /// The tangent direction at the start of the arc + public Vector2 StartTangent => placement.AxisX; /// The normal direction at the end of the arc public Vector2 EndNormal => GetNormal( length ); /// The end point of the arc @@ -34,7 +35,7 @@ public struct Arc2D { public Vector2 GetPosition( float s ) => Eval( s, nThDerivative: 0 ); /// Evaluates the tangent direction of this arc at the given arc length s - public Vector2 GetTangent( float s ) => s == 0 ? startTangent : Eval( s, nThDerivative: 1 ); // no need to normalize, it's already arc-length parameterized + public Vector2 GetTangent( float s ) => s == 0 ? StartTangent : Eval( s, nThDerivative: 1 ); // no need to normalize, it's already arc-length parameterized /// Evaluates the normal direction of this arc at the given arc length s public Vector2 GetNormal( float s ) => Eval( s, nThDerivative: 1 ).Rotate90CCW(); // no need to normalize, it's already arc-length parameterized @@ -48,10 +49,7 @@ public Vector2 Eval( float s, int nThDerivative = 0 ) { case 0: x = s * Mathfs.Sinc( ang ); y = s * Mathfs.Cosinc( ang ); - return new Vector2( - startPoint.x + startTangent.x * x + StartNormal.x * y, - startPoint.y + startTangent.y * x + StartNormal.y * y - ); + return placement.TransformPoint( x, y ); case 1: x = MathF.Cos( ang ); y = MathF.Sin( ang ); @@ -88,10 +86,7 @@ public Vector2 Eval( float s, int nThDerivative = 0 ) { } // space transformation - return new Vector2( - startTangent.x * x + StartNormal.x * y, - startTangent.y * x + StartNormal.y * y - ); + return placement.TransformVector( x, y ); } } From 2f3e66378c70199d0832526b1f235c81eea5de48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:30:26 +0200 Subject: [PATCH 221/301] catrom calc fix + format --- Runtime/Splines/SplineUtils.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Runtime/Splines/SplineUtils.cs b/Runtime/Splines/SplineUtils.cs index 292af0f..bf39f4f 100644 --- a/Runtime/Splines/SplineUtils.cs +++ b/Runtime/Splines/SplineUtils.cs @@ -36,10 +36,10 @@ public static float CalcCatRomKnot( float kPrev, float sqDist, float alpha ) { public static float CalcCatRomKnot( float squaredDistance, float alpha ) => alpha switch { - 0 => 1, // uniform - 1 => squaredDistance.Sqrt(), // chordal - 2 => squaredDistance, // centripetal - _ => squaredDistance.Pow( 0.5f * alpha ) + 0 => 1, // uniform + 0.5f => squaredDistance, // centripetal + 1 => squaredDistance.Sqrt(), // chordal + _ => squaredDistance.Pow( 0.5f * alpha ) }; static readonly Matrix4x1 knotsUniformUnit = new(-1, 0, 1, 2); @@ -187,13 +187,13 @@ public static Vector3 GetNUCatRomCharMatrixC2End( Matrix4x1 knots, Vector3 p0, V float p2sc = ( i02 * i12sq * i23 ); float p3sc = ( i12 * i13 * i23 ); - float m20 = (-k1 - 2 * k2) / p0sc; - float m21 = (common - k1k1 + k1k2) / p1sc; - float m22 = (-common - k2k2 + k1k2) / p2sc; - float m23 = (2 * k1 + k2) / p3sc; + float m20 = ( -k1 - 2 * k2 ) / p0sc; + float m21 = ( common - k1k1 + k1k2 ) / p1sc; + float m22 = ( -common - k2k2 + k1k2 ) / p2sc; + float m23 = ( 2 * k1 + k2 ) / p3sc; float m30 = 1f / p0sc; - float m31 = (k3 - k0) / p1sc; - float m32 = (k0 - k3) / p2sc; + float m31 = ( k3 - k0 ) / p1sc; + float m32 = ( k0 - k3 ) / p2sc; float m33 = -1f / p3sc; return p0 * ( ( m20 + 3 * m30 ) / m23 ) + From 86b462257e3aaa37b28997cbdd1782cdf0fe7385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:33:33 +0200 Subject: [PATCH 222/301] matrix ToString overloads --- Editor/MathfsCodegen.cs | 2 ++ Runtime/Numerics/Matrix3x1.cs | 1 + Runtime/Numerics/Matrix4x1.cs | 1 + Runtime/Numerics/Vector2Matrix3x1.cs | 1 + Runtime/Numerics/Vector2Matrix4x1.cs | 1 + Runtime/Numerics/Vector3Matrix3x1.cs | 1 + Runtime/Numerics/Vector3Matrix4x1.cs | 1 + Runtime/Numerics/Vector4Matrix3x1.cs | 1 + Runtime/Numerics/Vector4Matrix4x1.cs | 1 + 9 files changed, 10 insertions(+) diff --git a/Editor/MathfsCodegen.cs b/Editor/MathfsCodegen.cs index 4204cfd..d04af6d 100644 --- a/Editor/MathfsCodegen.cs +++ b/Editor/MathfsCodegen.cs @@ -251,6 +251,8 @@ static void GenerateMatrix( int count, int dim ) { code.Append( $"public bool Equals( {typeName} other ) => {equalsCompare};" ); code.Append( $"public override bool Equals( object obj ) => obj is {typeName} other && Equals( other );" ); code.Append( $"public override int GetHashCode() => HashCode.Combine( {csParams} );" ); + string stringPrint = JoinRange( "\\n", i => $"[{{m{i}}}]" ); + code.Append( $"public override string ToString() => $\"{stringPrint}\";" ); } } diff --git a/Runtime/Numerics/Matrix3x1.cs b/Runtime/Numerics/Matrix3x1.cs index 77bea6f..67b1b56 100644 --- a/Runtime/Numerics/Matrix3x1.cs +++ b/Runtime/Numerics/Matrix3x1.cs @@ -24,5 +24,6 @@ public float this[int row] { public bool Equals( Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); public override bool Equals( object obj ) => obj is Matrix3x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]"; } } diff --git a/Runtime/Numerics/Matrix4x1.cs b/Runtime/Numerics/Matrix4x1.cs index 0a5cab7..9d5bd8b 100644 --- a/Runtime/Numerics/Matrix4x1.cs +++ b/Runtime/Numerics/Matrix4x1.cs @@ -24,5 +24,6 @@ public float this[int row] { public bool Equals( Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); public override bool Equals( object obj ) => obj is Matrix4x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]\n[{m3}]"; } } diff --git a/Runtime/Numerics/Vector2Matrix3x1.cs b/Runtime/Numerics/Vector2Matrix3x1.cs index 9c182d2..51c602e 100644 --- a/Runtime/Numerics/Vector2Matrix3x1.cs +++ b/Runtime/Numerics/Vector2Matrix3x1.cs @@ -28,5 +28,6 @@ public Vector2 this[int row] { public bool Equals( Vector2Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); public override bool Equals( object obj ) => obj is Vector2Matrix3x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]"; } } diff --git a/Runtime/Numerics/Vector2Matrix4x1.cs b/Runtime/Numerics/Vector2Matrix4x1.cs index fd163c2..a558041 100644 --- a/Runtime/Numerics/Vector2Matrix4x1.cs +++ b/Runtime/Numerics/Vector2Matrix4x1.cs @@ -28,5 +28,6 @@ public Vector2 this[int row] { public bool Equals( Vector2Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); public override bool Equals( object obj ) => obj is Vector2Matrix4x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]\n[{m3}]"; } } diff --git a/Runtime/Numerics/Vector3Matrix3x1.cs b/Runtime/Numerics/Vector3Matrix3x1.cs index 6dfb6b1..6e6dc20 100644 --- a/Runtime/Numerics/Vector3Matrix3x1.cs +++ b/Runtime/Numerics/Vector3Matrix3x1.cs @@ -29,5 +29,6 @@ public Vector3 this[int row] { public bool Equals( Vector3Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); public override bool Equals( object obj ) => obj is Vector3Matrix3x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]"; } } diff --git a/Runtime/Numerics/Vector3Matrix4x1.cs b/Runtime/Numerics/Vector3Matrix4x1.cs index f9afa2a..bf9943f 100644 --- a/Runtime/Numerics/Vector3Matrix4x1.cs +++ b/Runtime/Numerics/Vector3Matrix4x1.cs @@ -29,5 +29,6 @@ public Vector3 this[int row] { public bool Equals( Vector3Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); public override bool Equals( object obj ) => obj is Vector3Matrix4x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]\n[{m3}]"; } } diff --git a/Runtime/Numerics/Vector4Matrix3x1.cs b/Runtime/Numerics/Vector4Matrix3x1.cs index a1b30a5..e8f48b7 100644 --- a/Runtime/Numerics/Vector4Matrix3x1.cs +++ b/Runtime/Numerics/Vector4Matrix3x1.cs @@ -30,5 +30,6 @@ public Vector4 this[int row] { public bool Equals( Vector4Matrix3x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ); public override bool Equals( object obj ) => obj is Vector4Matrix3x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]"; } } diff --git a/Runtime/Numerics/Vector4Matrix4x1.cs b/Runtime/Numerics/Vector4Matrix4x1.cs index 4f0b77b..c7f6ced 100644 --- a/Runtime/Numerics/Vector4Matrix4x1.cs +++ b/Runtime/Numerics/Vector4Matrix4x1.cs @@ -30,5 +30,6 @@ public Vector4 this[int row] { public bool Equals( Vector4Matrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); public override bool Equals( object obj ) => obj is Vector4Matrix4x1 other && Equals( other ); public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]\n[{m3}]"; } } From 69c9a92c9a2368500569fe10f395f9da076a3f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:37:36 +0200 Subject: [PATCH 223/301] Added Polynomial.NaN --- Runtime/Curves/Polynomial.cs | 3 +++ Runtime/Curves/Polynomial2D.cs | 3 +++ Runtime/Curves/Polynomial3D.cs | 3 +++ Runtime/Curves/Polynomial4D.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index 70f6704..d1b5a70 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -15,6 +15,9 @@ [Serializable] public struct Polynomial { /// A polynomial with all 0 coefficients. f(x) = 0 public static readonly Polynomial zero = new Polynomial( 0, 0, 0, 0 ); + + /// A polynomial with all NaN coefficients + public static readonly Polynomial NaN = new Polynomial( float.NaN, float.NaN, float.NaN, float.NaN ); /// The cubic coefficient [FormerlySerializedAs( "fCubic" )] public float c3; diff --git a/Runtime/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs index 9aa9cca..7249761 100644 --- a/Runtime/Curves/Polynomial2D.cs +++ b/Runtime/Curves/Polynomial2D.cs @@ -8,6 +8,9 @@ namespace Freya { [Serializable] public struct Polynomial2D : IParamCurve3Diff { + /// + public static readonly Polynomial2D NaN = new Polynomial2D { x = Polynomial.NaN, y = Polynomial.NaN }; + public Polynomial x; public Polynomial y; diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index 68f4eee..f43e7bd 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -7,6 +7,9 @@ namespace Freya { public struct Polynomial3D : IParamCurve3Diff { + /// + public static readonly Polynomial3D NaN = new Polynomial3D { x = Polynomial.NaN, y = Polynomial.NaN, z = Polynomial.NaN }; + public Polynomial x; public Polynomial y; public Polynomial z; diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index e0070ef..91d2f64 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -7,6 +7,9 @@ namespace Freya { public struct Polynomial4D : IParamCurve3Diff { + /// + public static readonly Polynomial4D NaN = new Polynomial4D { x = Polynomial.NaN, y = Polynomial.NaN, z = Polynomial.NaN, w = Polynomial.NaN }; + public Polynomial x; public Polynomial y; public Polynomial z; From 4e822e8a99065bacdd5972be3af695dddbe54e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:38:37 +0200 Subject: [PATCH 224/301] Polynomial ScaleParameterSpace optimization --- Runtime/Curves/Polynomial.cs | 5 ++++- Runtime/Curves/Polynomial2D.cs | 3 +++ Runtime/Curves/Polynomial3D.cs | 3 +++ Runtime/Curves/Polynomial4D.cs | 5 ++++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index d1b5a70..3c65757 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -133,6 +133,9 @@ public Polynomial Compose( float g0, float g1 ) { /// Scales the parameter space by a factor. For example, the output in the interval [0 to 1] will now be in the range [0 to factor] /// The factor to scale the input parameters by public Polynomial ScaleParameterSpace( float factor ) { + // ReSharper disable once CompareOfFloatsByEqualityOperator + if( factor == 1f ) + return this; float factor2 = factor * factor; float factor3 = factor2 * factor; return new Polynomial( @@ -313,7 +316,7 @@ static ResultsMax2 SolveQuadraticRoots( float a, float b, float c ) { if( rootContent >= 0 ) { // crosses at two points float u = -b * -( b < 0 ? -1 : 1 ) * MathF.Sqrt( rootContent ); - float r0 = u / ( 2 * a ); + float r0 = u / ( 2 * a ); float r1 = ( 2 * c ) / u; return new ResultsMax2( MathF.Min( r0, r1 ), MathF.Max( r0, r1 ) ); } diff --git a/Runtime/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs index 7249761..4f4433e 100644 --- a/Runtime/Curves/Polynomial2D.cs +++ b/Runtime/Curves/Polynomial2D.cs @@ -73,6 +73,9 @@ public Polynomial2D( Vector2 c0, Vector2 c1 ) { /// public Polynomial2D ScaleParameterSpace( float factor ) { + // ReSharper disable once CompareOfFloatsByEqualityOperator + if( factor == 1f ) + return this; float factor2 = factor * factor; float factor3 = factor2 * factor; return new Polynomial2D( diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index f43e7bd..b9e8b6f 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -76,6 +76,9 @@ public Polynomial3D( Vector3 c0, Vector3 c1 ) { /// public Polynomial3D ScaleParameterSpace( float factor ) { + // ReSharper disable once CompareOfFloatsByEqualityOperator + if( factor == 1f ) + return this; float factor2 = factor * factor; float factor3 = factor2 * factor; return new Polynomial3D( diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index 91d2f64..5bae551 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -77,9 +77,12 @@ public Polynomial4D( Vector4 c0, Vector4 c1 ) { /// public Polynomial4D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 ), w.Compose( g0, g1 )); - + /// public Polynomial4D ScaleParameterSpace( float factor ) { + // ReSharper disable once CompareOfFloatsByEqualityOperator + if( factor == 1f ) + return this; float factor2 = factor * factor; float factor3 = factor2 * factor; return new Polynomial4D( From dc7c86ebf5f5175e40ed5562b02dc38cde9e3d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:40:16 +0200 Subject: [PATCH 225/301] Vector2/3 AddMagnitude extensions + doc fix --- Runtime/Extensions.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 31cde0b..019a34a 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -107,10 +107,15 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// Equivalent to v.normalized*mag [MethodImpl( INLINE )] public static Vector2 WithMagnitude( this Vector2 v, float mag ) => v.normalized * mag; - /// Returns a vector with the same direction, but with the given magnitude. - /// Equivalent to v.normalized*mag + /// [MethodImpl( INLINE )] public static Vector3 WithMagnitude( this Vector3 v, float mag ) => v.normalized * mag; + /// Returns a vector with the same direction, but extending the magnitude by the given amount + [MethodImpl( INLINE )] public static Vector2 AddMagnitude( this Vector2 v, float extraMagnitude ) => v * ( 1 + extraMagnitude / v.magnitude ); + + /// + [MethodImpl( INLINE )] public static Vector3 AddMagnitude( this Vector3 v, float extraMagnitude ) => v * ( 1 + extraMagnitude / v.magnitude ); + /// Returns the vector going from one position to another, also known as the displacement. /// Equivalent to target-v [MethodImpl( INLINE )] public static Vector2 To( this Vector2 v, Vector2 target ) => target - v; From 9197a5e5e094b38a16e9420d33393bc3e06fec61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:40:36 +0200 Subject: [PATCH 226/301] quaternion to rotation vector conversion --- Runtime/Extensions.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 019a34a..fed552a 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -51,6 +51,13 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// The quaternion to get the components of [MethodImpl( INLINE )] public static Vector4 ToVector4( this Quaternion q ) => new Vector4( q.x, q.y, q.z, q.w ); + /// Converts to a rotation vector (axis-angle where the angle is embedded in the magnitude, in radians) + /// The quaternion to get the rotation vector of + [MethodImpl( INLINE )] public static Vector3 ToRotationVector( this Quaternion q ) { + q.ToAngleAxis( out float angDeg, out Vector3 axis ); + return axis * ( angDeg * Mathf.Deg2Rad ); + } + #endregion #region Swizzling From 2b1860c2024b10c35048d0cd3c52c4d36ee0a4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:40:52 +0200 Subject: [PATCH 227/301] Vector4Matrix4x1.MultiplyColumnVector extension --- Runtime/Extensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index fed552a..e092a4c 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -572,6 +572,7 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => public static Vector2Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Vector2Matrix4x1 v ) => new(m.MultiplyColumnVector( v.X ), m.MultiplyColumnVector( v.Y )); public static Vector3Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Vector3Matrix4x1 v ) => new(m.MultiplyColumnVector( v.X ), m.MultiplyColumnVector( v.Y ), m.MultiplyColumnVector( v.Z )); + public static Vector4Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Vector4Matrix4x1 v ) => new(m.MultiplyColumnVector( v.X ), m.MultiplyColumnVector( v.Y ), m.MultiplyColumnVector( v.Z ), m.MultiplyColumnVector( v.W )); #endregion From bd5566ff1963a6b2863a365baf9a7f0aa941bad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:41:16 +0200 Subject: [PATCH 228/301] Polynomial curve fitting --- Runtime/Curves/Polynomial.cs | 72 ++++++++++++++++++++++++++++++++++ Runtime/Curves/Polynomial2D.cs | 43 ++++++++++++++++++++ Runtime/Curves/Polynomial3D.cs | 55 ++++++++++++++++++++++++++ Runtime/Curves/Polynomial4D.cs | 50 +++++++++++++++++++++++ 4 files changed, 220 insertions(+) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index 3c65757..26391b7 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -130,6 +130,78 @@ public Polynomial Compose( float g0, float g1 ) { ); } + /// Fits a cubic polynomial to pass through the given coordinates + public static Polynomial FitCubic( float x0, float x1, float x2, float x3, float y0, float y1, float y2, float y3 ) { + // precalcs + float i01 = x1 - x0; + float i02 = x2 - x0; + float i03 = x3 - x0; + float i12 = x2 - x1; + float i13 = x3 - x1; + float i23 = x3 - x2; + float x0x1 = x0 * x1; + float x0x2 = x0 * x2; + float x0x3 = x0 * x3; + float x1x2 = x1 * x2; + float x1x3 = x1 * x3; + float x2x3 = x2 * x3; + float x1x2x3 = x1 * x2x3; + float x0x2x3 = x0 * x2x3; + float x0x1x3 = x0 * x1x3; + float x0x1x2 = x0 * x1x2; + float x0plusx1 = x0 + x1; + float x0plusx1plusx2 = x0plusx1 + x2; + float x0plusx1plusx3 = x0plusx1 + x3; + float x2plusx3 = x2 + x3; + float x0plusx2plusx3 = x0 + x2plusx3; + float x1plusx2plusx3 = x1 + x2plusx3; + float x1x2plusx1x3plusx2x3 = ( x1x2 + x1x3 + x2x3 ); + float x0x2plusx0x3plusx2x3 = ( x0x2 + x0x3 + x2x3 ); + float x0x1plusx0x3plusx1x3 = ( x0x1 + x0x3 + x1x3 ); + float x0x1plusx0x2plusx1x2 = ( x0x1 + x0x2 + x1x2 ); + + // scale factors + float scl0 = -( y0 / ( i01 * i02 * i03 ) ); + float scl1 = +( y1 / ( i01 * i12 * i13 ) ); + float scl2 = -( y2 / ( i02 * i12 * i23 ) ); + float scl3 = +( y3 / ( i03 * i13 * i23 ) ); + + // polynomial form + float c0 = -( scl0 * x1x2x3 + scl1 * x0x2x3 + scl2 * x0x1x3 + scl3 * x0x1x2 ); + float c1 = scl0 * x1x2plusx1x3plusx2x3 + scl1 * x0x2plusx0x3plusx2x3 + scl2 * x0x1plusx0x3plusx1x3 + scl3 * x0x1plusx0x2plusx1x2; + float c2 = -( scl0 * x1plusx2plusx3 + scl1 * x0plusx2plusx3 + scl2 * x0plusx1plusx3 + scl3 * x0plusx1plusx2 ); + float c3 = scl0 + scl1 + scl2 + scl3; + + return new Polynomial( (float)c0, (float)c1, (float)c2, (float)c3 ); + } + + /// Fits a cubic polynomial to pass through the given coordinates, assuming x0 = 0 + public static Polynomial FitCubicFrom0( float x1, float x2, float x3, float y0, float y1, float y2, float y3 ) { + // precalcs + float i12 = x2 - x1; + float i13 = x3 - x1; + float i23 = x3 - x2; + float x1x2 = x1 * x2; + float x1x3 = x1 * x3; + float x2x3 = x2 * x3; + float x1x2x3 = x1 * x2x3; + float x2plusx3 = x2 + x3; + + // scale factors + float scl0 = -( y0 / ( x1 * x2 * x3 ) ); + float scl1 = +( y1 / ( x1 * i12 * i13 ) ); + float scl2 = -( y2 / ( x2 * i12 * i23 ) ); + float scl3 = +( y3 / ( x3 * i13 * i23 ) ); + + // polynomial form + float c0 = -( scl0 * x1x2x3 ); + float c1 = scl0 * ( x1x2 + x1x3 + x2x3 ) + scl1 * x2x3 + scl2 * x1x3 + scl3 * x1x2; + float c2 = -( scl0 * ( x2plusx3 + x1 ) + scl1 * ( x2plusx3 ) + scl2 * ( x1 + x3 ) + scl3 * ( x1 + x2 ) ); + float c3 = scl0 + scl1 + scl2 + scl3; + + return new Polynomial( c0, c1, c2, c3 ); + } + /// Scales the parameter space by a factor. For example, the output in the interval [0 to 1] will now be in the range [0 to factor] /// The factor to scale the input parameters by public Polynomial ScaleParameterSpace( float factor ) { diff --git a/Runtime/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs index 4f4433e..450e12b 100644 --- a/Runtime/Curves/Polynomial2D.cs +++ b/Runtime/Curves/Polynomial2D.cs @@ -71,6 +71,49 @@ public Polynomial2D( Vector2 c0, Vector2 c1 ) { /// public Polynomial2D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 )); + /// + public static Polynomial2D FitCubicFrom0( float x1, float x2, float x3, Vector2 y0, Vector2 y1, Vector2 y2, Vector2 y3 ) { + // precalcs + float i12 = x2 - x1; + float i13 = x3 - x1; + float i23 = x3 - x2; + float x1x2 = x1 * x2; + float x1x3 = x1 * x3; + float x2x3 = x2 * x3; + float x1x2x3 = x1 * x2x3; + float x0plusx1plusx2 = x1 + x2; + float x0plusx1plusx3 = x1 + x3; + float x2plusx3 = x2 + x3; + float x1plusx2plusx3 = x1 + x2plusx3; + float x1x2plusx1x3plusx2x3 = ( x1x2 + x1x3 + x2x3 ); + + // scale factors + Vector2 scl0 = y0 / -( x1 * x2 * x3 ); + Vector2 scl1 = y1 / +( x1 * i12 * i13 ); + Vector2 scl2 = y2 / -( x2 * i12 * i23 ); + Vector2 scl3 = y3 / +( x3 * i13 * i23 ); + + // polynomial form + Vector2 c0 = new( + -( scl0.x * x1x2x3 ), + -( scl0.y * x1x2x3 ) + ); + Vector2 c1 = new( + scl0.x * x1x2plusx1x3plusx2x3 + scl1.x * x2x3 + scl2.x * x1x3 + scl3.x * x1x2, + scl0.y * x1x2plusx1x3plusx2x3 + scl1.y * x2x3 + scl2.y * x1x3 + scl3.y * x1x2 + ); + Vector2 c2 = new( + -( scl0.x * x1plusx2plusx3 + scl1.x * x2plusx3 + scl2.x * x0plusx1plusx3 + scl3.x * x0plusx1plusx2 ), + -( scl0.y * x1plusx2plusx3 + scl1.y * x2plusx3 + scl2.y * x0plusx1plusx3 + scl3.y * x0plusx1plusx2 ) + ); + Vector2 c3 = new( + scl0.x + scl1.x + scl2.x + scl3.x, + scl0.y + scl1.y + scl2.y + scl3.y + ); + + return new Polynomial2D( c0, c1, c2, c3 ); + } + /// public Polynomial2D ScaleParameterSpace( float factor ) { // ReSharper disable once CompareOfFloatsByEqualityOperator diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index b9e8b6f..d07872f 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -74,6 +74,53 @@ public Polynomial3D( Vector3 c0, Vector3 c1 ) { /// public Polynomial3D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 )); + /// + public static Polynomial3D FitCubicFrom0( float x1, float x2, float x3, Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3 ) { + // precalcs + float i12 = x2 - x1; + float i13 = x3 - x1; + float i23 = x3 - x2; + float x1x2 = x1 * x2; + float x1x3 = x1 * x3; + float x2x3 = x2 * x3; + float x1x2x3 = x1 * x2x3; + float x0plusx1plusx2 = x1 + x2; + float x0plusx1plusx3 = x1 + x3; + float x2plusx3 = x2 + x3; + float x1plusx2plusx3 = x1 + x2plusx3; + float x1x2plusx1x3plusx2x3 = ( x1x2 + x1x3 + x2x3 ); + + // scale factors + Vector3 scl0 = y0 / -( x1 * x2 * x3 ); + Vector3 scl1 = y1 / +( x1 * i12 * i13 ); + Vector3 scl2 = y2 / -( x2 * i12 * i23 ); + Vector3 scl3 = y3 / +( x3 * i13 * i23 ); + + // polynomial form + Vector3 c0 = new( + -( scl0.x * x1x2x3 ), + -( scl0.y * x1x2x3 ), + -( scl0.z * x1x2x3 ) + ); + Vector3 c1 = new( + scl0.x * x1x2plusx1x3plusx2x3 + scl1.x * x2x3 + scl2.x * x1x3 + scl3.x * x1x2, + scl0.y * x1x2plusx1x3plusx2x3 + scl1.y * x2x3 + scl2.y * x1x3 + scl3.y * x1x2, + scl0.z * x1x2plusx1x3plusx2x3 + scl1.z * x2x3 + scl2.z * x1x3 + scl3.z * x1x2 + ); + Vector3 c2 = new( + -( scl0.x * x1plusx2plusx3 + scl1.x * x2plusx3 + scl2.x * x0plusx1plusx3 + scl3.x * x0plusx1plusx2 ), + -( scl0.y * x1plusx2plusx3 + scl1.y * x2plusx3 + scl2.y * x0plusx1plusx3 + scl3.y * x0plusx1plusx2 ), + -( scl0.z * x1plusx2plusx3 + scl1.z * x2plusx3 + scl2.z * x0plusx1plusx3 + scl3.z * x0plusx1plusx2 ) + ); + Vector3 c3 = new( + scl0.x + scl1.x + scl2.x + scl3.x, + scl0.y + scl1.y + scl2.y + scl3.y, + scl0.z + scl1.z + scl2.z + scl3.z + ); + + return new Polynomial3D( c0, c1, c2, c3 ); + } + /// public Polynomial3D ScaleParameterSpace( float factor ) { // ReSharper disable once CompareOfFloatsByEqualityOperator @@ -195,6 +242,14 @@ void Refine( ref PointProjectSample smp ) { public static Polynomial3D operator *( Polynomial3D p, float v ) => new(p.C0 * v, p.C1 * v, p.C2 * v, p.C3 * v); public static Polynomial3D operator *( float v, Polynomial3D p ) => p * v; + public override string ToString() { + string s = ""; + s += x + "\n"; + s += y + "\n"; + s += z; + return s; + } + public static explicit operator Polynomial2D( Polynomial3D p ) => new(p.x, p.y); public static explicit operator Vector3Matrix3x1( Polynomial3D poly ) => new(poly.C0, poly.C1, poly.C2); public static explicit operator Vector3Matrix4x1( Polynomial3D poly ) => new(poly.C0, poly.C1, poly.C2, poly.C3); diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index 5bae551..419a789 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -93,6 +93,56 @@ public Polynomial4D ScaleParameterSpace( float factor ) { ); } + public static Polynomial4D FitCubicFrom0( float x1, float x2, float x3, Vector4 y0, Vector4 y1, Vector4 y2, Vector4 y3 ) { + // precalcs + float i12 = x2 - x1; + float i13 = x3 - x1; + float i23 = x3 - x2; + float x1x2 = x1 * x2; + float x1x3 = x1 * x3; + float x2x3 = x2 * x3; + float x1x2x3 = x1 * x2x3; + float x0plusx1plusx2 = x1 + x2; + float x0plusx1plusx3 = x1 + x3; + float x2plusx3 = x2 + x3; + float x1plusx2plusx3 = x1 + x2plusx3; + float x1x2plusx1x3plusx2x3 = ( x1x2 + x1x3 + x2x3 ); + + // scale factors + Vector4 scl0 = y0 / -( x1 * x2 * x3 ); + Vector4 scl1 = y1 / +( x1 * i12 * i13 ); + Vector4 scl2 = y2 / -( x2 * i12 * i23 ); + Vector4 scl3 = y3 / +( x3 * i13 * i23 ); + + // polynomial form + Vector4 c0 = new( + -( scl0.x * x1x2x3 ), + -( scl0.y * x1x2x3 ), + -( scl0.z * x1x2x3 ), + -( scl0.w * x1x2x3 ) + ); + Vector4 c1 = new( + scl0.x * x1x2plusx1x3plusx2x3 + scl1.x * x2x3 + scl2.x * x1x3 + scl3.x * x1x2, + scl0.y * x1x2plusx1x3plusx2x3 + scl1.y * x2x3 + scl2.y * x1x3 + scl3.y * x1x2, + scl0.z * x1x2plusx1x3plusx2x3 + scl1.z * x2x3 + scl2.z * x1x3 + scl3.z * x1x2, + scl0.w * x1x2plusx1x3plusx2x3 + scl1.w * x2x3 + scl2.w * x1x3 + scl3.w * x1x2 + ); + Vector4 c2 = new( + -( scl0.x * x1plusx2plusx3 + scl1.x * x2plusx3 + scl2.x * x0plusx1plusx3 + scl3.x * x0plusx1plusx2 ), + -( scl0.y * x1plusx2plusx3 + scl1.y * x2plusx3 + scl2.y * x0plusx1plusx3 + scl3.y * x0plusx1plusx2 ), + -( scl0.z * x1plusx2plusx3 + scl1.z * x2plusx3 + scl2.z * x0plusx1plusx3 + scl3.z * x0plusx1plusx2 ), + -( scl0.w * x1plusx2plusx3 + scl1.w * x2plusx3 + scl2.w * x0plusx1plusx3 + scl3.w * x0plusx1plusx2 ) + ); + Vector4 c3 = new( + scl0.x + scl1.x + scl2.x + scl3.x, + scl0.y + scl1.y + scl2.y + scl3.y, + scl0.z + scl1.z + scl2.z + scl3.z, + scl0.w + scl1.w + scl2.w + scl3.w + ); + + return new Polynomial4D( c0, c1, c2, c3 ); + } + /// public (FloatRange x, FloatRange y, FloatRange z, FloatRange w) GetBounds01() => ( x.OutputRange01, y.OutputRange01, z.OutputRange01, w.OutputRange01 ); From 916b45ad26ff3a38ef4a146e1edf747759b1dbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:41:32 +0200 Subject: [PATCH 229/301] small fix on FloatRange.Contains --- Runtime/Numerics/FloatRange.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 1579c18..2aec5a5 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -46,9 +46,9 @@ public struct FloatRange { /// The value to get the normalized position of public float InverseLerp( float v ) => Mathfs.InverseLerp( a, b, v ); - /// Returns whether or not this range contains the value v + /// Returns whether or not this range contains the value v (inclusive) /// The value to see if it's inside - public bool Contains( float v ) => v >= Min && v <= Max; + public bool Contains( float v ) => v >= MathF.Min( a, b ) && v <= MathF.Max( a, b ); /// Returns whether or not this range contains the range r /// The range to see if it's inside From d0a566b3739681e154dbd1aa2450d260fba19d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 30 Jul 2023 21:41:56 +0200 Subject: [PATCH 230/301] Rotor3*Bivector3 operator --- Runtime/Geometric Algebra/Rotor3.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Runtime/Geometric Algebra/Rotor3.cs b/Runtime/Geometric Algebra/Rotor3.cs index ccc9b61..0614a5d 100644 --- a/Runtime/Geometric Algebra/Rotor3.cs +++ b/Runtime/Geometric Algebra/Rotor3.cs @@ -76,6 +76,16 @@ public Vector3 SandwichConjugate( Vector3 v ) { ); } + public static Rotor3 operator *( Rotor3 a, Bivector3 b ) { + return new Rotor3( + a.yz * b.yz + a.zx * b.zx + a.xy * b.xy, + a.r * b.yz - a.zx * b.xy + a.xy * b.zx, + a.r * b.zx + a.yz * b.xy + -a.xy * b.yz, + a.r * b.xy - a.yz * b.zx + a.zx * b.yz + ); + } + + public static Rotor3 operator /( Rotor3 a, float b ) { return new Rotor3( a.r / b, a.yz / b, a.zx / b, a.xy / b ); } From 1e3bdb0a0c75d254e4a9261a6f33723f062be8fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 31 Jul 2023 22:19:15 +0200 Subject: [PATCH 231/301] some GA rotor fixes --- Runtime/Geometric Algebra/Bivector3.cs | 2 +- Runtime/Geometric Algebra/Rotor3.cs | 61 ++++++++++++++++---------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/Runtime/Geometric Algebra/Bivector3.cs b/Runtime/Geometric Algebra/Bivector3.cs index 638b52d..46debd0 100644 --- a/Runtime/Geometric Algebra/Bivector3.cs +++ b/Runtime/Geometric Algebra/Bivector3.cs @@ -26,7 +26,7 @@ public Bivector3( Vector3 a, Vector3 b ) { public float Magnitude => MathF.Sqrt( SqrMagnitude ); public Bivector3 Normalized => new Bivector3( yz, zx, xy ) / Magnitude; - public Vector3 Normal => new Vector3( yz, zx, xy ) / Magnitude; + public Vector3 Normal => new Vector3( yz, zx, xy ) / Magnitude; // todo: rename to hodge dual? public float SqrMagnitude => yz * yz + zx * zx + xy * xy; /// diff --git a/Runtime/Geometric Algebra/Rotor3.cs b/Runtime/Geometric Algebra/Rotor3.cs index 0614a5d..98d1be4 100644 --- a/Runtime/Geometric Algebra/Rotor3.cs +++ b/Runtime/Geometric Algebra/Rotor3.cs @@ -28,42 +28,55 @@ public Rotor3( float r, Bivector3 b ) { this.b = b; } + public Rotor3( Vector3 a, Vector3 b ) { + // constructs a rotor by multiplying two vectors + this.r = Vector3.Dot( a, b ); + this.b = Mathfs.Wedge( a, b ); + } + public float Magnitude => MathF.Sqrt( SqrMagnitude ); public float SqrMagnitude => r * r + b.SqrMagnitude; public Rotor3 Normalized() => this / Magnitude; + public Quaternion ToQuaternion() => new(yz, zx, xy, r); + /// Negates the bivector, which is equivalent to reversing the rotation, /// if this is normalized an interpreted as a rotation public Rotor3 Conjugate => new Rotor3( r, -b ); - /// Sandwich product, equivalent to RvR* (where R* is the conjugate of R). + /// Sandwich product, equivalent to (R*v*R*)* (where R* is the conjugate of R and v* is the hodge dual of v). /// Commonly used to rotate vectors with unit rotors /// The vector to multiply (or rotate) public Vector3 SandwichConjugate( Vector3 v ) { - // todo: untested - float r2 = r * r; - float yz2 = yz * yz; - float zx2 = zx * zx; - float xy2 = xy * xy; - float yzzx = yz * zx; - float zxxy = zx * xy; - float xyyz = xy * yz; - float rxy = r * xy; - float rzx = r * zx; - float ryz = r * yz; - - return new Vector3( - v.x * ( r2 + yz2 - zx2 - xy2 ) - + 2 * v.y * ( yzzx + rxy ) - + 2 * v.z * ( xyyz - rzx ), - v.y * ( r2 - yz2 + zx2 - xy2 ) - + 2 * v.x * ( yzzx - rxy ) - + 2 * v.z * ( ryz + zxxy ), - v.z * ( r2 - yz2 - zx2 + xy2 ) - + 2 * v.x * ( rzx + xyyz ) - + 2 * v.y * ( zxxy - ryz ) - ); + + Bivector3 vHodge = new( v.x, v.y, v.z ); + Rotor3 bivecPost = this.Conjugate * vHodge * this; + return bivecPost.b.Normal; + + // // todo: does not work - this does R v* R* instead of R* v* R + // float r2 = r * r; + // float yz2 = yz * yz; + // float zx2 = zx * zx; + // float xy2 = xy * xy; + // float yzzx = yz * zx; + // float zxxy = zx * xy; + // float xyyz = xy * yz; + // float rxy = r * xy; + // float rzx = r * zx; + // float ryz = r * yz; + // + // return new Vector3( + // v.x * ( r2 + yz2 - zx2 - xy2 ) + // + 2 * v.y * ( yzzx + rxy ) + // + 2 * v.z * ( xyyz - rzx ), + // v.y * ( r2 - yz2 + zx2 - xy2 ) + // + 2 * v.x * ( yzzx - rxy ) + // + 2 * v.z * ( ryz + zxxy ), + // v.z * ( r2 - yz2 - zx2 + xy2 ) + // + 2 * v.x * ( rzx + xyyz ) + // + 2 * v.y * ( zxxy - ryz ) + // ); } // multiplication From 7143ed9ce5318bb63d72aa1c097ca8ddfea12ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 11 Aug 2023 15:11:37 +0200 Subject: [PATCH 232/301] more GA operator shenanigans --- Runtime/Geometric Algebra/Bivector3.cs | 13 ++++- Runtime/Geometric Algebra/Multivector3.cs | 13 +++++ Runtime/Geometric Algebra/Rotor3.cs | 67 +++++++++++++++++++---- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/Runtime/Geometric Algebra/Bivector3.cs b/Runtime/Geometric Algebra/Bivector3.cs index 46debd0..d131b13 100644 --- a/Runtime/Geometric Algebra/Bivector3.cs +++ b/Runtime/Geometric Algebra/Bivector3.cs @@ -26,7 +26,8 @@ public Bivector3( Vector3 a, Vector3 b ) { public float Magnitude => MathF.Sqrt( SqrMagnitude ); public Bivector3 Normalized => new Bivector3( yz, zx, xy ) / Magnitude; - public Vector3 Normal => new Vector3( yz, zx, xy ) / Magnitude; // todo: rename to hodge dual? + public Vector3 Normal => HodgeDual.normalized; + public Vector3 HodgeDual => new Vector3( yz, zx, xy ); public float SqrMagnitude => yz * yz + zx * zx + xy * xy; /// @@ -46,7 +47,7 @@ public static Bivector3 Wedge( Bivector3 a, Bivector3 b ) => xy: a.zx * b.yz - a.yz * b.zx ); /// Returns the normal of this bivector plane and its area - public (Vector3 normal, float area) GetNormalAndArea() => ( (Vector3)this ).GetDirAndMagnitude(); + public (Vector3 normal, float area) GetNormalAndArea() => HodgeDual.GetDirAndMagnitude(); // Multiplication public static Bivector3 operator -( Bivector3 b ) => new Bivector3( -b.yz, -b.zx, -b.xy ); @@ -59,6 +60,14 @@ public static Bivector3 Wedge( Bivector3 a, Bivector3 b ) => b: Wedge( a, b ) ); + public Rotor3 Square() => + new( + -yz * yz - zx * zx - xy * xy, + yz: xy * zx - zx * xy, + zx: yz * xy - xy * yz, + xy: zx * yz - yz * zx + ); + public static Multivector3 operator *( Bivector3 a, Vector3 b ) { return new Multivector3( 0, // real diff --git a/Runtime/Geometric Algebra/Multivector3.cs b/Runtime/Geometric Algebra/Multivector3.cs index 90ca614..5014b20 100644 --- a/Runtime/Geometric Algebra/Multivector3.cs +++ b/Runtime/Geometric Algebra/Multivector3.cs @@ -77,6 +77,19 @@ public Multivector3( float r, Vector3 v, Bivector3 b, Trivector3 t ) { ); } + public static Multivector3 operator *( Multivector3 m, Rotor3 r ) { + return new Multivector3( + m.r * r.r + m.yz * r.yz + m.zx * r.zx + m.xy * r.xy, + m.x * r.r - m.y * r.xy + m.z * r.zx - m.xyz * r.yz, + +m.x * r.xy + m.y * r.r - m.z * r.yz - m.xyz * r.zx, + -m.x * r.zx + m.y * r.yz + m.z * r.r - m.xyz * r.xy, + m.r * r.yz + m.yz * r.r - m.zx * r.xy + m.xy * r.zx, + m.r * r.zx + m.yz * r.xy + m.zx * r.r - m.xy * r.yz, + m.r * r.xy - m.yz * r.zx + m.zx * r.yz + m.xy * r.r, + m.x * r.yz + m.y * r.zx + m.z * r.xy + m.xyz * r.r + ); + } + public static Bivector3 Wedge( Multivector3 a, Multivector3 b ) => new Bivector3( a.r * b.yz + a.x * b.xyz + a.y * b.z - a.z * b.y + a.yz * b.r - a.zx * b.xy + a.xy * b.zx + a.xyz * b.x, diff --git a/Runtime/Geometric Algebra/Rotor3.cs b/Runtime/Geometric Algebra/Rotor3.cs index 98d1be4..ceddb53 100644 --- a/Runtime/Geometric Algebra/Rotor3.cs +++ b/Runtime/Geometric Algebra/Rotor3.cs @@ -28,10 +28,20 @@ public Rotor3( float r, Bivector3 b ) { this.b = b; } - public Rotor3( Vector3 a, Vector3 b ) { - // constructs a rotor by multiplying two vectors - this.r = Vector3.Dot( a, b ); - this.b = Mathfs.Wedge( a, b ); + /// Creates a rotation representing twice the angle from a to b. + /// This is equivalent to multiplying the two vectors a*b. + /// Note: Assumes both input vectors are normalized + public static Rotor3 FromToRotationDouble( Vector3 a, Vector3 b ) => new Rotor3( Vector3.Dot( a, b ), Mathfs.Wedge( a, b ) ); + + /// Creates a rotation from a to b. Note: Assumes both input vectors are normalized + public static Rotor3 FromToRotation( Vector3 a, Vector3 b ) => new Rotor3( Vector3.Dot( a, b ) + 1, Mathfs.Wedge( a, b ) ).Normalized(); + + /// Constructs a unit rotor representing a rotation + public Rotor3( float angle, Vector3 axis ) { + Bivector3 axisDual = new Bivector3( axis.x, axis.y, axis.z ); + float halfAngle = angle / 2; + r = MathF.Cos( halfAngle ); + b = axisDual * MathF.Sin( halfAngle ); } public float Magnitude => MathF.Sqrt( SqrMagnitude ); @@ -45,16 +55,14 @@ public Rotor3( Vector3 a, Vector3 b ) { /// if this is normalized an interpreted as a rotation public Rotor3 Conjugate => new Rotor3( r, -b ); - /// Sandwich product, equivalent to (R*v*R*)* (where R* is the conjugate of R and v* is the hodge dual of v). + /// Sandwich product, equivalent to ⭐(R* ⭐v R) (where R* is the conjugate of R and ⭐v is the hodge dual of v). /// Commonly used to rotate vectors with unit rotors /// The vector to multiply (or rotate) - public Vector3 SandwichConjugate( Vector3 v ) { - - Bivector3 vHodge = new( v.x, v.y, v.z ); - Rotor3 bivecPost = this.Conjugate * vHodge * this; - return bivecPost.b.Normal; - - // // todo: does not work - this does R v* R* instead of R* v* R + public Vector3 Rotate( Vector3 v ) { + // hodge variant + Bivector3 vHodge = new(v.x, v.y, v.z); + return ( this.Conjugate * vHodge * this ).b.HodgeDual; + // // todo: does not work - this does R ⭐v R* instead of R* ⭐v R // float r2 = r * r; // float yz2 = yz * yz; // float zx2 = zx * zx; @@ -89,6 +97,15 @@ public Vector3 SandwichConjugate( Vector3 v ) { ); } + public static Rotor3 operator *( Bivector3 b, Rotor3 r ) { + return new Rotor3( + -b.yz * r.yz - b.zx * r.zx - b.xy * r.xy, + +b.yz * r.r - b.zx * r.xy + b.xy * r.zx, + +b.yz * r.xy + b.zx * r.r - b.xy * r.yz, + -b.yz * r.zx + b.zx * r.yz + b.xy * r.r + ); + } + public static Rotor3 operator *( Rotor3 a, Bivector3 b ) { return new Rotor3( a.yz * b.yz + a.zx * b.zx + a.xy * b.xy, @@ -98,6 +115,32 @@ public Vector3 SandwichConjugate( Vector3 v ) { ); } + public static Multivector3 operator *( Rotor3 a, Vector3 b ) { + return new Multivector3( + 0, + a.r * b.x - a.zx * b.z + a.xy * b.y, + a.r * b.y + a.yz * b.z - a.xy * b.x, + a.r * b.z - a.yz * b.y + a.zx * b.x, + 0, + 0, + 0, + a.yz * b.x + a.zx * b.y + a.xy * b.z + ); + } + + public static Multivector3 operator *( Vector3 b, Rotor3 a ) { + return new Multivector3( + 0, + a.r * b.x - a.zx * b.z + a.xy * b.y, + a.r * b.y + a.yz * b.z - a.xy * b.x, + a.r * b.z - a.yz * b.y + a.zx * b.x, + 0, + 0, + 0, + a.yz * b.x + a.zx * b.y + a.xy * b.z + ); + } + public static Rotor3 operator /( Rotor3 a, float b ) { return new Rotor3( a.r / b, a.yz / b, a.zx / b, a.xy / b ); From d9c72acb1bb10f990c384c79e5e2ba3285e980a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 11 Aug 2023 15:11:54 +0200 Subject: [PATCH 233/301] double clamp -1 to 1 --- Runtime/Extensions.cs | 3 +++ Runtime/Mathfs.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index e092a4c..bbd46e1 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -680,6 +680,9 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => /// [MethodImpl( INLINE )] public static float ClampNeg1to1( this float value ) => Mathfs.ClampNeg1to1( value ); + /// + [MethodImpl( INLINE )] public static double ClampNeg1to1( this double value ) => Mathfs.ClampNeg1to1( value ); + /// [MethodImpl( INLINE )] public static Vector2 ClampNeg1to1( this Vector2 v ) => Mathfs.ClampNeg1to1( v ); diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index a33d5c4..720d2ef 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -414,6 +414,9 @@ public static Vector4 Clamp01( Vector4 v ) => v.w < 0f ? 0f : v.w > 1f ? 1f : v.w ); + /// Clamps the value between -1 and 1 + public static double ClampNeg1to1( double value ) => value < -1.0 ? -1.0 : value > 1.0 ? 1.0 : value; + /// Clamps the value between -1 and 1 public static float ClampNeg1to1( float value ) => value < -1f ? -1f : value > 1f ? 1f : value; From 0a10e9877a6411c1289955ea17ee5bffed7348ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 11 Aug 2023 15:12:19 +0200 Subject: [PATCH 234/301] quat.pow, add, conjugate, magnitude, sqMag also renamed log to ln --- Runtime/Extensions.cs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index bbd46e1..e2c6f21 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -366,7 +366,7 @@ public static Matrix4x4 ToMatrix( this Quaternion q ) { } /// Returns the natural logarithm of a quaternion - public static Quaternion Log( this Quaternion q ) { + public static Quaternion Ln( this Quaternion q ) { double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; double vMag = Math.Sqrt( vMagSq ); double qMag = Math.Sqrt( vMagSq + (double)q.w * q.w ); @@ -389,11 +389,41 @@ public static Quaternion Exp( this Quaternion q ) { return new Quaternion( (float)( scV * v.x ), (float)( scV * v.y ), (float)( scV * v.z ), (float)( sc * Math.Cos( vMag ) ) ); } + /// Returns the quaternion raised to a real power + public static Quaternion Pow( this Quaternion q, float x ) { + double vSqMag = q.x * q.x + q.y * q.y + q.z * q.z; + double rSqMag = q.w * q.w; + double vMag = Math.Sqrt( vSqMag ); + double qMag = Math.Sqrt( rSqMag + vSqMag ); + double nx = q.x / vMag; + double ny = q.y / vMag; + double nz = q.z / vMag; + double ang = Math.Acos( ( q.w / qMag ).ClampNeg1to1() ); + double theta = ang * x; + double magPow = Math.Pow( qMag, x ); + double cos = magPow * Math.Cos( theta ); + double sin = magPow * Math.Sin( theta ); + return new Quaternion( (float)( sin * nx ), (float)( sin * ny ), (float)( sin * nz ), (float)cos ); + } + + /// Returns the squared magnitude of this quaternion + public static float SqrMagnitude( this Quaternion q ) => (float)( (double)q.w * q.w + (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z ); + + /// Returns the magnitude of this quaternion + public static float Magnitude( this Quaternion q ) => MathF.Sqrt( q.SqrMagnitude() ); + /// Multiplies a quaternion by a scalar /// The quaternion to multiply /// The scalar value to multiply with public static Quaternion Mul( this Quaternion q, float c ) => new Quaternion( c * q.x, c * q.y, c * q.z, c * q.w ); + /// Adds a quaternion to an existing quaternion + public static Quaternion Add( this Quaternion a, Quaternion b ) => new Quaternion( a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w ); + + /// The conjugate of a quaternion + /// The quaternion to conjugate + public static Quaternion Conjugate( this Quaternion q ) => new Quaternion( -q.x, -q.y, -q.z, q.w ); + /// public static Quaternion Inverse( this Quaternion q ) => Quaternion.Inverse( q ); From b3955ab652edb06267bb9eff97803ac5f2737154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 13 Aug 2023 20:51:09 +0200 Subject: [PATCH 235/301] quaternion subtraction --- Runtime/Extensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index e2c6f21..7483432 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -420,6 +420,9 @@ public static Quaternion Pow( this Quaternion q, float x ) { /// Adds a quaternion to an existing quaternion public static Quaternion Add( this Quaternion a, Quaternion b ) => new Quaternion( a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w ); + /// Subtracts a quaternion from an existing quaternion + public static Quaternion Sub( this Quaternion a, Quaternion b ) => new Quaternion( a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w ); + /// The conjugate of a quaternion /// The quaternion to conjugate public static Quaternion Conjugate( this Quaternion q ) => new Quaternion( -q.x, -q.y, -q.z, q.w ); From c91af7505655b31e241254a264560ed7154cbc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 15 Aug 2023 12:49:11 +0200 Subject: [PATCH 236/301] added shared interface to polynomials & cleanup I think I also fixed the compose function --- Runtime/Curves/IPolynomialCubic.cs | 54 ++++++++ Runtime/Curves/IPolynomialCubic.cs.meta | 11 ++ Runtime/Curves/IPolynomialMath.cs | 39 ++++++ Runtime/Curves/IPolynomialMath.cs.meta | 11 ++ Runtime/Curves/Polynomial.cs | 158 +++++++++++++----------- Runtime/Curves/Polynomial2D.cs | 113 +++++++++++------ Runtime/Curves/Polynomial3D.cs | 117 +++++++++++------- Runtime/Curves/Polynomial4D.cs | 96 +++++++++----- 8 files changed, 410 insertions(+), 189 deletions(-) create mode 100644 Runtime/Curves/IPolynomialCubic.cs create mode 100644 Runtime/Curves/IPolynomialCubic.cs.meta create mode 100644 Runtime/Curves/IPolynomialMath.cs create mode 100644 Runtime/Curves/IPolynomialMath.cs.meta diff --git a/Runtime/Curves/IPolynomialCubic.cs b/Runtime/Curves/IPolynomialCubic.cs new file mode 100644 index 0000000..5d8186e --- /dev/null +++ b/Runtime/Curves/IPolynomialCubic.cs @@ -0,0 +1,54 @@ +namespace Freya { + + public interface IPolynomialCubic { + + /// The constant coefficient + public V C0 { get; set; } + /// The linear coefficient + public V C1 { get; set; } + /// The quadratic coefficient + public V C2 { get; set; } + /// The cubic coefficient + public V C3 { get; set; } + + /// The degree of the polynomial + public int Degree { get; } + + /// Returns the component polynomial of the given axis/dimension + /// Index of axis/dimension. 0 = x. 1 = y, etc + public Polynomial this[ int i ] { get; set; } + + /// Gets the coefficient of the given degree + /// The degree of the coefficient you want to get. For example, 0 will return the constant coefficient, 3 will return the cubic coefficient + V GetCoefficient( int degree ); + + /// Sets the coefficient of the given degree + /// The degree of the coefficient you want to set. For example, 0 will return the constant coefficient, 3 will return the cubic coefficient + /// The value to set it to + public void SetCoefficient( int degree, V value ); + + /// Evaluates the polynomial at the given value t + /// The value to sample at + public V Eval( float t ); + + /// Evaluates the n:th derivative of the polynomial at the given value t + /// The value to sample at + /// The derivative to evaluate + public V Eval( float t, int n ); + + /// Differentiates this function, returning the n-th derivative of this polynomial + /// The number of times to differentiate this function. 0 returns the function itself, 1 returns the first derivative + public P Differentiate( int n = 1 ); + + /// Scales the parameter space by a factor. For example, the output of the polynomial in the input interval [0 to 1] will now be in the range [0 to factor] + /// The factor to scale the input parameters by + public P ScaleParameterSpace( float factor ); + + /// Given an inner function g(x), returns f(g(x)) + /// The constant coefficient of the inner function g(x) + /// The linear coefficient of the inner function g(x) + public P Compose( float g0, float g1 ); + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/IPolynomialCubic.cs.meta b/Runtime/Curves/IPolynomialCubic.cs.meta new file mode 100644 index 0000000..6e2ecf4 --- /dev/null +++ b/Runtime/Curves/IPolynomialCubic.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d508c7c494c2a34f9d18768260e71e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves/IPolynomialMath.cs b/Runtime/Curves/IPolynomialMath.cs new file mode 100644 index 0000000..20efc91 --- /dev/null +++ b/Runtime/Curves/IPolynomialMath.cs @@ -0,0 +1,39 @@ +using UnityEngine; + +namespace Freya { + + public interface IPolynomialMath { + public P NaN { get; } + public P FitCubicFrom0( float x1, float x2, float x3, V y0, V y1, V y2, V y3 ); + } + + public struct PolynomialMath1D : IPolynomialMath { + public Polynomial NaN => Polynomial.NaN; + + /// + public Polynomial FitCubicFrom0( float x1, float x2, float x3, float y0, float y1, float y2, float y3 ) => Polynomial.FitCubicFrom0( x1, x2, x3, y0, y1, y2, y3 ); + } + + public struct PolynomialMath2D : IPolynomialMath { + public Polynomial2D NaN => Polynomial2D.NaN; + + /// + public Polynomial2D FitCubicFrom0( float x1, float x2, float x3, Vector2 y0, Vector2 y1, Vector2 y2, Vector2 y3 ) => Polynomial2D.FitCubicFrom0( x1, x2, x3, y0, y1, y2, y3 ); + } + + public struct PolynomialMath3D : IPolynomialMath { + public Polynomial3D NaN => Polynomial3D.NaN; + + /// + public Polynomial3D FitCubicFrom0( float x1, float x2, float x3, Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3 ) => Polynomial3D.FitCubicFrom0( x1, x2, x3, y0, y1, y2, y3 ); + } + + public struct PolynomialMath4D : IPolynomialMath { + public Polynomial4D NaN => Polynomial4D.NaN; + + /// + public Polynomial4D FitCubicFrom0( float x1, float x2, float x3, Vector4 y0, Vector4 y1, Vector4 y2, Vector4 y3 ) => Polynomial4D.FitCubicFrom0( x1, x2, x3, y0, y1, y2, y3 ); + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/IPolynomialMath.cs.meta b/Runtime/Curves/IPolynomialMath.cs.meta new file mode 100644 index 0000000..42cd070 --- /dev/null +++ b/Runtime/Curves/IPolynomialMath.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47c9d879bc3df8e4abdda485b1cc9254 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index 26391b7..56c5231 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -9,52 +9,27 @@ namespace Freya { /// A polynomial in the form ax³+bx²+cx+d, up to a cubic, with functions like derivatives, root finding, and more - [Serializable] public struct Polynomial { + [Serializable] public struct Polynomial : IPolynomialCubic { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; /// A polynomial with all 0 coefficients. f(x) = 0 public static readonly Polynomial zero = new Polynomial( 0, 0, 0, 0 ); - + /// A polynomial with all NaN coefficients public static readonly Polynomial NaN = new Polynomial( float.NaN, float.NaN, float.NaN, float.NaN ); - /// The cubic coefficient - [FormerlySerializedAs( "fCubic" )] public float c3; - - /// The quadratic coefficient - [FormerlySerializedAs( "fQuadratic" )] public float c2; + /// The constant coefficient + [FormerlySerializedAs( "fConstant" )] public float c0; /// The linear coefficient [FormerlySerializedAs( "fLinear" )] public float c1; - /// The constant coefficient - [FormerlySerializedAs( "fConstant" )] public float c0; - - /// Get or set the coefficient of the given degree - /// The degree of the coefficient you want to get/set. For example, 0 will return the constant coefficient, 3 will return the cubic coefficient - public float this[ int degree ] { - get => - degree switch { - 0 => c0, - 1 => c1, - 2 => c2, - 3 => c3, - _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) - }; - set { - _ = degree switch { - 0 => c0 = value, - 1 => c1 = value, - 2 => c2 = value, - 3 => c3 = value, - _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) - }; - } - } + /// The quadratic coefficient + [FormerlySerializedAs( "fQuadratic" )] public float c2; - /// The degree of the polynomial - public int Degree => GetPolynomialDegree( c0, c1, c2, c3 ); + /// The cubic coefficient + [FormerlySerializedAs( "fCubic" )] public float c3; /// Creates a polynomial up to a cubic /// The constant coefficient @@ -90,18 +65,59 @@ public float this[ int degree ] { /// public Polynomial( (float c0, float c1, float c2) coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.c0, coefficients.c1, coefficients.c2, 0 ); - /// Evaluates the polynomial at the given value t - /// The value to sample at - public float Eval( float t ) => c3 * ( t * t * t ) + c2 * ( t * t ) + c1 * t + c0; + #region IPolynomialCubic + + public float C0 { + [MethodImpl( INLINE )] get => c0; + [MethodImpl( INLINE )] set => c0 = value; + } + public float C1 { + [MethodImpl( INLINE )] get => c1; + [MethodImpl( INLINE )] set => c1 = value; + } + public float C2 { + [MethodImpl( INLINE )] get => c2; + [MethodImpl( INLINE )] set => c2 = value; + } + public float C3 { + [MethodImpl( INLINE )] get => c3; + [MethodImpl( INLINE )] set => c3 = value; + } + public Polynomial this[ int i ] { + [MethodImpl( INLINE )] get => i == 0 ? this : throw new IndexOutOfRangeException( "float polynomials don't have vector components" ); + [MethodImpl( INLINE )] set => this = value; + } + + public int Degree => GetPolynomialDegree( c0, c1, c2, c3 ); + + [MethodImpl( INLINE )] public float GetCoefficient( int degree ) => + degree switch { + 0 => c0, + 1 => c1, + 2 => c2, + 3 => c3, + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) + }; + + [MethodImpl( INLINE )] public void SetCoefficient( int degree, float value ) { + _ = degree switch { + 0 => c0 = value, + 1 => c1 = value, + 2 => c2 = value, + 3 => c3 = value, + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) + }; + } - /// Evaluates the n:th derivative of the polynomial at the given value t - /// The value to sample at - /// The derivative to evaluate - public float Eval( float t, int n ) => Differentiate( n ).Eval( t ); + public float Eval( float t ) { + float t2 = t * t; + float t3 = t * t2; + return c3 * t3 + c2 * t2 + c1 * t + c0; + } - /// Differentiates this function, returning the n-th derivative of this polynomial - /// The number of times to differentiate this function. 0 returns the function itself, 1 returns the first derivative - public Polynomial Differentiate( int n = 1 ) { + [MethodImpl( INLINE )] public float Eval( float t, int n ) => Differentiate( n ).Eval( t ); + + [MethodImpl( INLINE )] public Polynomial Differentiate( int n = 1 ) { return n switch { 0 => this, 1 => new Polynomial( c1, 2 * c2, 3 * c3, 0 ), @@ -111,25 +127,38 @@ public Polynomial Differentiate( int n = 1 ) { }; } + public Polynomial ScaleParameterSpace( float factor ) { + // ReSharper disable once CompareOfFloatsByEqualityOperator + if( factor == 1f ) + return this; + float factor2 = factor * factor; + float factor3 = factor2 * factor; + return new Polynomial( + c0, + c1 / factor, + c2 / factor2, + c3 / factor3 + ); + } + /// Given an inner function g(x), returns f(g(x)) /// The constant coefficient of the inner function g(x) /// The linear coefficient of the inner function g(x) public Polynomial Compose( float g0, float g1 ) { - float ss = g1 * g1; - float sss = ss * g1; - float oo = g0 * g0; - float ooo = oo * g0; - float _3c3 = 3 * c3; - float c2g0 = c2 * g0; - + float g0_2 = g0 * g0; + float g0_3 = g0 * g0_2; + float g1_2 = g1 * g1; + float g1_3 = g1 * g1_2; return new Polynomial( - c3 * ooo + c2 * oo + c2g0 + c0, - g1 * ( _3c3 * oo + 2 * c2g0 + c1 ), - ss * ( _3c3 * g0 + c2 ), - sss * c3 + c0 + c1 * g0 + c2 * g0_2 + c3 * g0_3, + c1 * g1 + c2 * 2 * g0 * g1 + c3 * 3 * g0_2 * g1, + c2 * g1_2 + c3 * 3 * g0 * g1_2, + c3 * g1_3 ); } + #endregion + /// Fits a cubic polynomial to pass through the given coordinates public static Polynomial FitCubic( float x0, float x1, float x2, float x3, float y0, float y1, float y2, float y3 ) { // precalcs @@ -202,21 +231,6 @@ public static Polynomial FitCubicFrom0( float x1, float x2, float x3, float y0, return new Polynomial( c0, c1, c2, c3 ); } - /// Scales the parameter space by a factor. For example, the output in the interval [0 to 1] will now be in the range [0 to factor] - /// The factor to scale the input parameters by - public Polynomial ScaleParameterSpace( float factor ) { - // ReSharper disable once CompareOfFloatsByEqualityOperator - if( factor == 1f ) - return this; - float factor2 = factor * factor; - float factor3 = factor2 * factor; - return new Polynomial( - c0, - c1 / factor, - c2 / factor2, - c3 / factor3 - ); - } /// Splits the 0-1 range into two distinct polynomials at the given parameter value u, where both new curves cover the same total range with their individual 0-1 ranges /// The parameter value to split at @@ -482,15 +496,15 @@ public override string ToString() { bool hasAddedFirstTerm = false; for( int c = 0; c < 4; c++ ) { - float value = this[c]; + float value = GetCoefficient( c ); if( value != 0 ) { if( hasAddedFirstTerm == false ) { hasAddedFirstTerm = true; - strBuilder.Append( this[c] ); + strBuilder.Append( GetCoefficient( c ) ); } else { if( value > 0 ) strBuilder.Append( "+" ); - strBuilder.Append( this[c] ); + strBuilder.Append( GetCoefficient( c ) ); if( c > 0 ) strBuilder.Append( tPowerSuffixStr[c] ); } diff --git a/Runtime/Curves/Polynomial2D.cs b/Runtime/Curves/Polynomial2D.cs index 450e12b..59eddca 100644 --- a/Runtime/Curves/Polynomial2D.cs +++ b/Runtime/Curves/Polynomial2D.cs @@ -1,37 +1,20 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; +using System.Runtime.CompilerServices; using UnityEngine; namespace Freya { [Serializable] - public struct Polynomial2D : IParamCurve3Diff { + public struct Polynomial2D : IPolynomialCubic, IParamCurve3Diff { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; /// public static readonly Polynomial2D NaN = new Polynomial2D { x = Polynomial.NaN, y = Polynomial.NaN }; - public Polynomial x; - public Polynomial y; - - public Vector2 C0 { - get => new(x.c0, y.c0); - set => ( x.c0, y.c0 ) = ( value.x, value.y ); - } - public Vector2 C1 { - get => new(x.c1, y.c1); - set => ( x.c1, y.c1 ) = ( value.x, value.y ); - } - public Vector2 C2 { - get => new(x.c2, y.c2); - set => ( x.c2, y.c2 ) = ( value.x, value.y ); - } - public Vector2 C3 { - get => new(x.c3, y.c3); - set => ( x.c3, y.c3 ) = ( value.x, value.y ); - } - - public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( "Polynomial2D component index has to be either 0 or 1" ) }; + public Polynomial x, y; public Polynomial2D( Polynomial x, Polynomial y ) => ( this.x, this.y ) = ( x, y ); @@ -59,18 +42,78 @@ public Polynomial2D( Vector2 c0, Vector2 c1 ) { /// public Polynomial2D( Vector2Matrix3x1 coefficients ) => ( x, y ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ) ); - /// - public Vector2 Eval( float t ) => new(x.Eval( t ), y.Eval( t )); + #region IPolynomialCubic + + public Vector2 C0 { + [MethodImpl( INLINE )] get => new(x.c0, y.c0); + [MethodImpl( INLINE )] set => ( x.c0, y.c0 ) = ( value.x, value.y ); + } + public Vector2 C1 { + [MethodImpl( INLINE )] get => new(x.c1, y.c1); + [MethodImpl( INLINE )] set => ( x.c1, y.c1 ) = ( value.x, value.y ); + } + public Vector2 C2 { + [MethodImpl( INLINE )] get => new(x.c2, y.c2); + [MethodImpl( INLINE )] set => ( x.c2, y.c2 ) = ( value.x, value.y ); + } + public Vector2 C3 { + [MethodImpl( INLINE )] get => new(x.c3, y.c3); + [MethodImpl( INLINE )] set => ( x.c3, y.c3 ) = ( value.x, value.y ); + } + + public Polynomial this[ int i ] { + get { return i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( "Polynomial2D component index has to be either 0 or 1" ) }; } + set => _ = i switch { 0 => x = value, 1 => y = value, _ => throw new IndexOutOfRangeException() }; + } + + [MethodImpl( INLINE )] public Vector2 GetCoefficient( int degree ) => + degree switch { + 0 => C0, + 1 => C1, + 2 => C2, + 3 => C3, + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) + }; + + [MethodImpl( INLINE )] public void SetCoefficient( int degree, Vector2 value ) { + _ = degree switch { + 0 => C0 = value, + 1 => C1 = value, + 2 => C2 = value, + 3 => C3 = value, + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) + }; + } + + public Vector2 Eval( float t ) { + float t2 = t * t; + float t3 = t2 * t; + return new Vector2( + x.c3 * t3 + x.c2 * t2 + x.c1 * t + x.c0, + y.c3 * t3 + y.c2 * t2 + y.c1 * t + y.c0 + ); + } + + [MethodImpl( INLINE )] public Vector2 Eval( float t, int n ) => Differentiate( n ).Eval( t ); - /// - public Vector2 Eval( float t, int n ) => Differentiate( n ).Eval( t ); + [MethodImpl( INLINE )] public Polynomial2D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n )); - /// - public Polynomial2D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n )); + public Polynomial2D ScaleParameterSpace( float factor ) { + // ReSharper disable once CompareOfFloatsByEqualityOperator + if( factor == 1f ) + return this; + float factor2 = factor * factor; + float factor3 = factor2 * factor; + return new Polynomial2D( + new Polynomial( x.c0, x.c1 / factor, x.c2 / factor2, x.c3 / factor3 ), + new Polynomial( y.c0, y.c1 / factor, y.c2 / factor2, y.c3 / factor3 ) + ); + } - /// public Polynomial2D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 )); + #endregion + /// public static Polynomial2D FitCubicFrom0( float x1, float x2, float x3, Vector2 y0, Vector2 y1, Vector2 y2, Vector2 y3 ) { // precalcs @@ -114,18 +157,6 @@ public static Polynomial2D FitCubicFrom0( float x1, float x2, float x3, Vector2 return new Polynomial2D( c0, c1, c2, c3 ); } - /// - public Polynomial2D ScaleParameterSpace( float factor ) { - // ReSharper disable once CompareOfFloatsByEqualityOperator - if( factor == 1f ) - return this; - float factor2 = factor * factor; - float factor3 = factor2 * factor; - return new Polynomial2D( - new Polynomial( x.c0, x.c1 / factor, x.c2 / factor2, x.c3 / factor3 ), - new Polynomial( y.c0, y.c1 / factor, y.c2 / factor2, y.c3 / factor3 ) - ); - } /// Returns the tight axis-aligned bounds of the curve in the unit interval public Rect GetBounds01() => FloatRange.ToRect( x.OutputRange01, y.OutputRange01 ); diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index d07872f..fb25537 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -1,37 +1,19 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; +using System.Runtime.CompilerServices; using UnityEngine; namespace Freya { - public struct Polynomial3D : IParamCurve3Diff { + public struct Polynomial3D : IPolynomialCubic, IParamCurve3Diff { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; /// public static readonly Polynomial3D NaN = new Polynomial3D { x = Polynomial.NaN, y = Polynomial.NaN, z = Polynomial.NaN }; - public Polynomial x; - public Polynomial y; - public Polynomial z; - - public Vector3 C0 { - get => new(x.c0, y.c0, z.c0); - set => ( x.c0, y.c0, z.c0 ) = ( value.x, value.y, value.z ); - } - public Vector3 C1 { - get => new(x.c1, y.c1, z.c1); - set => ( x.c1, y.c1, z.c1 ) = ( value.x, value.y, value.z ); - } - public Vector3 C2 { - get => new(x.c2, y.c2, z.c2); - set => ( x.c2, y.c2, z.c2 ) = ( value.x, value.y, value.z ); - } - public Vector3 C3 { - get => new(x.c3, y.c3, z.c3); - set => ( x.c3, y.c3, z.c3 ) = ( value.x, value.y, value.z ); - } - - public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, _ => throw new IndexOutOfRangeException( "Polynomial3D component index has to be either 0, 1, or 2" ) }; + public Polynomial x, y, z; public Polynomial3D( Polynomial x, Polynomial y, Polynomial z ) => ( this.x, this.y, this.z ) = ( x, y, z ); @@ -62,18 +44,80 @@ public Polynomial3D( Vector3 c0, Vector3 c1 ) { /// public Polynomial3D( Vector3Matrix3x1 coefficients ) => ( x, y, z ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ) ); - /// - public Vector3 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t )); + #region IPolynomialCubic + + public Vector3 C0 { + [MethodImpl( INLINE )] get => new(x.c0, y.c0, z.c0); + [MethodImpl( INLINE )] set => ( x.c0, y.c0, z.c0 ) = ( value.x, value.y, value.z ); + } + public Vector3 C1 { + [MethodImpl( INLINE )] get => new(x.c1, y.c1, z.c1); + [MethodImpl( INLINE )] set => ( x.c1, y.c1, z.c1 ) = ( value.x, value.y, value.z ); + } + public Vector3 C2 { + [MethodImpl( INLINE )] get => new(x.c2, y.c2, z.c2); + [MethodImpl( INLINE )] set => ( x.c2, y.c2, z.c2 ) = ( value.x, value.y, value.z ); + } + public Vector3 C3 { + [MethodImpl( INLINE )] get => new(x.c3, y.c3, z.c3); + [MethodImpl( INLINE )] set => ( x.c3, y.c3, z.c3 ) = ( value.x, value.y, value.z ); + } + + public Polynomial this[ int i ] { + get { return i switch { 0 => x, 1 => y, 2 => z, _ => throw new IndexOutOfRangeException( "Polynomial3D component index has to be either 0, 1, or 2" ) }; } + set => _ = i switch { 0 => x = value, 1 => y = value, 2 => z = value, _ => throw new IndexOutOfRangeException() }; + } + + [MethodImpl( INLINE )] public Vector3 GetCoefficient( int degree ) => + degree switch { + 0 => C0, + 1 => C1, + 2 => C2, + 3 => C3, + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) + }; + + [MethodImpl( INLINE )] public void SetCoefficient( int degree, Vector3 value ) { + _ = degree switch { + 0 => C0 = value, + 1 => C1 = value, + 2 => C2 = value, + 3 => C3 = value, + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) + }; + } + + public Vector3 Eval( float t ) { + float t2 = t * t; + float t3 = t2 * t; + return new Vector3( + x.c3 * t3 + x.c2 * t2 + x.c1 * t + x.c0, + y.c3 * t3 + y.c2 * t2 + y.c1 * t + y.c0, + z.c3 * t3 + z.c2 * t2 + z.c1 * t + z.c0 + ); + } + + [MethodImpl( INLINE )] public Vector3 Eval( float t, int n ) => Differentiate( n ).Eval( t ); - /// - public Vector3 Eval( float t, int n ) => Differentiate( n ).Eval( t ); + [MethodImpl( INLINE )] public Polynomial3D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n )); - /// - public Polynomial3D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n )); + public Polynomial3D ScaleParameterSpace( float factor ) { + // ReSharper disable once CompareOfFloatsByEqualityOperator + if( factor == 1f ) + return this; + float factor2 = factor * factor; + float factor3 = factor2 * factor; + return new Polynomial3D( + new Polynomial( x.c0, x.c1 / factor, x.c2 / factor2, x.c3 / factor3 ), + new Polynomial( y.c0, y.c1 / factor, y.c2 / factor2, y.c3 / factor3 ), + new Polynomial( z.c0, z.c1 / factor, z.c2 / factor2, z.c3 / factor3 ) + ); + } - /// public Polynomial3D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 )); + #endregion + /// public static Polynomial3D FitCubicFrom0( float x1, float x2, float x3, Vector3 y0, Vector3 y1, Vector3 y2, Vector3 y3 ) { // precalcs @@ -121,19 +165,6 @@ public static Polynomial3D FitCubicFrom0( float x1, float x2, float x3, Vector3 return new Polynomial3D( c0, c1, c2, c3 ); } - /// - public Polynomial3D ScaleParameterSpace( float factor ) { - // ReSharper disable once CompareOfFloatsByEqualityOperator - if( factor == 1f ) - return this; - float factor2 = factor * factor; - float factor3 = factor2 * factor; - return new Polynomial3D( - new Polynomial( x.c0, x.c1 / factor, x.c2 / factor2, x.c3 / factor3 ), - new Polynomial( y.c0, y.c1 / factor, y.c2 / factor2, y.c3 / factor3 ), - new Polynomial( z.c0, z.c1 / factor, z.c2 / factor2, z.c3 / factor3 ) - ); - } /// public Bounds GetBounds01() => FloatRange.ToBounds( x.OutputRange01, y.OutputRange01, z.OutputRange01 ); diff --git a/Runtime/Curves/Polynomial4D.cs b/Runtime/Curves/Polynomial4D.cs index 419a789..e0bb790 100644 --- a/Runtime/Curves/Polynomial4D.cs +++ b/Runtime/Curves/Polynomial4D.cs @@ -1,38 +1,19 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; +using System.Runtime.CompilerServices; using UnityEngine; namespace Freya { - public struct Polynomial4D : IParamCurve3Diff { + public struct Polynomial4D : IPolynomialCubic, IParamCurve3Diff { + + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; /// public static readonly Polynomial4D NaN = new Polynomial4D { x = Polynomial.NaN, y = Polynomial.NaN, z = Polynomial.NaN, w = Polynomial.NaN }; - public Polynomial x; - public Polynomial y; - public Polynomial z; - public Polynomial w; - - public Vector4 C0 { - get => new(x.c0, y.c0, z.c0, w.c0); - set => ( x.c0, y.c0, z.c0, w.c0 ) = ( value.x, value.y, value.z, value.w ); - } - public Vector4 C1 { - get => new(x.c1, y.c1, z.c1, w.c1); - set => ( x.c1, y.c1, z.c1, w.c1 ) = ( value.x, value.y, value.z, value.w ); - } - public Vector4 C2 { - get => new(x.c2, y.c2, z.c2, w.c2); - set => ( x.c2, y.c2, z.c2, w.c2 ) = ( value.x, value.y, value.z, value.w ); - } - public Vector4 C3 { - get => new(x.c3, y.c3, z.c3, w.c3); - set => ( x.c3, y.c3, z.c3, w.c3 ) = ( value.x, value.y, value.z, value.w ); - } - - public Polynomial this[ int i ] => i switch { 0 => x, 1 => y, 2 => z, 4 => w, _ => throw new IndexOutOfRangeException( "Polynomial4D component index has to be either 0, 1, 2, or 3" ) }; + public Polynomial x, y, z, w; public Polynomial4D( Polynomial x, Polynomial y, Polynomial z, Polynomial w ) => ( this.x, this.y, this.z, this.w ) = ( x, y, z, w ); @@ -66,19 +47,64 @@ public Polynomial4D( Vector4 c0, Vector4 c1 ) { /// public Polynomial4D( Vector4Matrix3x1 coefficients ) => ( x, y, z, w ) = ( new Polynomial( coefficients.X ), new Polynomial( coefficients.Y ), new Polynomial( coefficients.Z ), new Polynomial( coefficients.W ) ); - /// - public Vector4 Eval( float t ) => new(x.Eval( t ), y.Eval( t ), z.Eval( t ), w.Eval( t )); + #region IPolynomialCubic - /// - public Vector4 Eval( float t, int n ) => Differentiate( n ).Eval( t ); + public Vector4 C0 { + [MethodImpl( INLINE )] get => new(x.c0, y.c0, z.c0, w.c0); + [MethodImpl( INLINE )] set => ( x.c0, y.c0, z.c0, w.c0 ) = ( value.x, value.y, value.z, value.w ); + } + public Vector4 C1 { + [MethodImpl( INLINE )] get => new(x.c1, y.c1, z.c1, w.c1); + [MethodImpl( INLINE )] set => ( x.c1, y.c1, z.c1, w.c1 ) = ( value.x, value.y, value.z, value.w ); + } + public Vector4 C2 { + [MethodImpl( INLINE )] get => new(x.c2, y.c2, z.c2, w.c2); + [MethodImpl( INLINE )] set => ( x.c2, y.c2, z.c2, w.c2 ) = ( value.x, value.y, value.z, value.w ); + } + public Vector4 C3 { + [MethodImpl( INLINE )] get => new(x.c3, y.c3, z.c3, w.c3); + [MethodImpl( INLINE )] set => ( x.c3, y.c3, z.c3, w.c3 ) = ( value.x, value.y, value.z, value.w ); + } - /// - public Polynomial4D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n ), w.Differentiate( n )); + public Polynomial this[ int i ] { + get => i switch { 0 => x, 1 => y, 2 => z, 4 => w, _ => throw new IndexOutOfRangeException( "Polynomial4D component index has to be either 0, 1, 2, or 3" ) }; + set => _ = i switch { 0 => x = value, 1 => y = value, 2 => z = value, 3 => w = value, _ => throw new IndexOutOfRangeException() }; + } - /// - public Polynomial4D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 ), w.Compose( g0, g1 )); + [MethodImpl( INLINE )] public Vector4 GetCoefficient( int degree ) => + degree switch { + 0 => C0, + 1 => C1, + 2 => C2, + 3 => C3, + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) + }; + + [MethodImpl( INLINE )] public void SetCoefficient( int degree, Vector4 value ) { + _ = degree switch { + 0 => C0 = value, + 1 => C1 = value, + 2 => C2 = value, + 3 => C3 = value, + _ => throw new IndexOutOfRangeException( "Polynomial coefficient degree/index has to be between 0 and 3" ) + }; + } + + public Vector4 Eval( float t ) { + float t2 = t * t; + float t3 = t2 * t; + return new Vector4( + x.c3 * t3 + x.c2 * t2 + x.c1 * t + x.c0, + y.c3 * t3 + y.c2 * t2 + y.c1 * t + y.c0, + z.c3 * t3 + z.c2 * t2 + z.c1 * t + z.c0, + w.c3 * t3 + w.c2 * t2 + w.c1 * t + w.c0 + ); + } + + [MethodImpl( INLINE )] public Vector4 Eval( float t, int n ) => Differentiate( n ).Eval( t ); + + [MethodImpl( INLINE )] public Polynomial4D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n ), w.Differentiate( n )); - /// public Polynomial4D ScaleParameterSpace( float factor ) { // ReSharper disable once CompareOfFloatsByEqualityOperator if( factor == 1f ) @@ -93,6 +119,10 @@ public Polynomial4D ScaleParameterSpace( float factor ) { ); } + public Polynomial4D Compose( float g0, float g1 ) => new(x.Compose( g0, g1 ), y.Compose( g0, g1 ), z.Compose( g0, g1 ), w.Compose( g0, g1 )); + + #endregion + public static Polynomial4D FitCubicFrom0( float x1, float x2, float x3, Vector4 y0, Vector4 y1, Vector4 y2, Vector4 y3 ) { // precalcs float i12 = x2 - x1; From a0c233a981b13905d61457dee3a4c68b10ab26cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 15 Aug 2023 12:49:28 +0200 Subject: [PATCH 237/301] Generalized vector math utilities --- Runtime/Numerics/IVectorMath.cs | 121 +++++++++++++++++++++++++++ Runtime/Numerics/IVectorMath.cs.meta | 11 +++ 2 files changed, 132 insertions(+) create mode 100644 Runtime/Numerics/IVectorMath.cs create mode 100644 Runtime/Numerics/IVectorMath.cs.meta diff --git a/Runtime/Numerics/IVectorMath.cs b/Runtime/Numerics/IVectorMath.cs new file mode 100644 index 0000000..3cdd5aa --- /dev/null +++ b/Runtime/Numerics/IVectorMath.cs @@ -0,0 +1,121 @@ +using System; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + public interface IVectorMath { + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] V Add( V a, V b ); + [MethodImpl( INLINE )] V Sub( V a, V b ); + [MethodImpl( INLINE )] V Mul( V v, float c ); + [MethodImpl( INLINE )] V Div( V v, float c ); + [MethodImpl( INLINE )] float Dot( V a, V b ); + [MethodImpl( INLINE )] float Mag( V v ); + [MethodImpl( INLINE )] V Normalize( V v ); + + [MethodImpl( INLINE )] V Mul( float c, V v ) => Mul( v, c ); + [MethodImpl( INLINE )] float SqMag( V v ) => Dot( v, v ); + [MethodImpl( INLINE )] float SqDist( V a, V b ) => SqMag( Sub( b, a ) ); + [MethodImpl( INLINE )] float Dist( V a, V b ) => MathF.Sqrt( SqDist( b, a ) ); + [MethodImpl( INLINE )] V VecProject( V p, V to ) => Mul( to, BasisProject( p, to ) ); + [MethodImpl( INLINE )] V VecReject( V p, V to ) => Sub( p, VecProject( p, to ) ); + [MethodImpl( INLINE )] float BasisProject( V p, V to ) => Dot( p, to ) / Dot( to, to ); + [MethodImpl( INLINE )] V VecFromLineToPoint( V o, V n, V p ) => VecReject( Sub( p, o ), n ); + [MethodImpl( INLINE )] float SqDistFromPointToLine( V o, V n, V p ) => SqMag( VecFromLineToPoint( o, n, p ) ); + [MethodImpl( INLINE )] V GetPointAlongLine( V o, V n, float t ) => Add( o, Mul( n, t ) ); + [MethodImpl( INLINE )] float ProjPointToLineSegmentTValue( V a, V b, V p ) => Mathf.Clamp01( ProjPointToLineTValue( a, Sub( b, a ), p ) ); + [MethodImpl( INLINE )] float ProjPointToLineTValue( V o, V n, V p ) => BasisProject( Sub( p, o ), n ); + [MethodImpl( INLINE )] V ProjPointToLine( V o, V n, V p ) => Add( o, VecProject( Sub( p, o ), n ) ); + [MethodImpl( INLINE )] V Lerp( V a, V b, float t ) => Add( Mul( 1f - t, a ), Mul( t, b ) ); + + (float tA, float tB) ClosestPointBetweenLinesTValues( V aOrigin, V aDir, V bOrigin, V bDir ) { + // source: https://math.stackexchange.com/questions/2213165/find-shortest-distance-between-lines-in-3d + V e = Sub( aOrigin, bOrigin ); + float be = Dot( aDir, e ); + float de = Dot( bDir, e ); + float bd = Dot( aDir, bDir ); + float b2 = Dot( aDir, aDir ); + float d2 = Dot( bDir, bDir ); + float A = -b2 * d2 + bd * bd; + float s = ( -b2 * de + be * bd ) / A; + float t = ( d2 * be - de * bd ) / A; + return ( t, s ); + } + + public bool TryIntersectSphereAtOrigin( V o, V n, float r, out (float tMin, float tMax) tValues ) { + float nn = Dot( n, n ); + if( nn <= 0f ) { // vector has zero length, there's no direction + tValues = default; + return false; + } + float oo = Dot( o, o ); + float on = Dot( o, n ); + + // quadratic terms + double A = nn; + double B = 2 * on; + double C = oo - r * r; + + // try root solving + double discriminant = B * B - 4 * A * C; + if( discriminant < 0 ) { // no root, line is outside the circle + tValues = default; + return false; + } + int sign = B < 0 ? -1 : 1; + double u = -B - sign * Math.Sqrt( discriminant ); + + float tA = (float)( u / ( 2 * A ) ); + float tB = (float)( 2 * C / u ); + tValues = tA < tB ? ( tA, tB ) : ( tB, tA ); + return true; + } + + } + + public struct VectorMath1D : IVectorMath { + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public float Add( float a, float b ) => a + b; + [MethodImpl( INLINE )] public float Sub( float a, float b ) => a - b; + [MethodImpl( INLINE )] public float Mul( float v, float c ) => v * c; + [MethodImpl( INLINE )] public float Div( float v, float c ) => v / c; + [MethodImpl( INLINE )] public float Dot( float a, float b ) => a * b; + [MethodImpl( INLINE )] public float Mag( float v ) => MathF.Abs( v ); + [MethodImpl( INLINE )] public float Normalize( float v ) => v < 0 ? -1 : 1; + } + + public struct VectorMath2D : IVectorMath { + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public Vector2 Add( Vector2 a, Vector2 b ) => new(a.x + b.x, a.y + b.y); + [MethodImpl( INLINE )] public Vector2 Sub( Vector2 a, Vector2 b ) => new(a.x - b.x, a.y - b.y); + [MethodImpl( INLINE )] public Vector2 Mul( Vector2 v, float c ) => new(v.x * c, v.y * c); + [MethodImpl( INLINE )] public Vector2 Div( Vector2 v, float c ) => new(v.x / c, v.y / c); + [MethodImpl( INLINE )] public float Dot( Vector2 a, Vector2 b ) => a.x * b.x + a.y * b.y; + [MethodImpl( INLINE )] public float Mag( Vector2 v ) => MathF.Sqrt( Dot( v, v ) ); + [MethodImpl( INLINE )] public Vector2 Normalize( Vector2 v ) => Div( v, Mag( v ) ); + } + + public struct VectorMath3D : IVectorMath { + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public Vector3 Add( Vector3 a, Vector3 b ) => new(a.x + b.x, a.y + b.y, a.z + b.z); + [MethodImpl( INLINE )] public Vector3 Sub( Vector3 a, Vector3 b ) => new(a.x - b.x, a.y - b.y, a.z - b.z); + [MethodImpl( INLINE )] public Vector3 Mul( Vector3 v, float c ) => new(v.x * c, v.y * c, v.z * c); + [MethodImpl( INLINE )] public Vector3 Div( Vector3 v, float c ) => new(v.x / c, v.y / c, v.z / c); + [MethodImpl( INLINE )] public float Dot( Vector3 a, Vector3 b ) => a.x * b.x + a.y * b.y + a.z * b.z; + [MethodImpl( INLINE )] public float Mag( Vector3 v ) => MathF.Sqrt( Dot( v, v ) ); + [MethodImpl( INLINE )] public Vector3 Normalize( Vector3 v ) => Div( v, Mag( v ) ); + } + + public struct VectorMath4D : IVectorMath { + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public Vector4 Add( Vector4 a, Vector4 b ) => new(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); + [MethodImpl( INLINE )] public Vector4 Sub( Vector4 a, Vector4 b ) => new(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); + [MethodImpl( INLINE )] public Vector4 Mul( Vector4 v, float c ) => new(v.x * c, v.y * c, v.z * c, v.w * c); + [MethodImpl( INLINE )] public Vector4 Div( Vector4 v, float c ) => new(v.x / c, v.y / c, v.z / c, v.w / c); + [MethodImpl( INLINE )] public float Dot( Vector4 a, Vector4 b ) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + [MethodImpl( INLINE )] public float Mag( Vector4 v ) => MathF.Sqrt( Dot( v, v ) ); + [MethodImpl( INLINE )] public Vector4 Normalize( Vector4 v ) => Div( v, Mag( v ) ); + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IVectorMath.cs.meta b/Runtime/Numerics/IVectorMath.cs.meta new file mode 100644 index 0000000..699a8c2 --- /dev/null +++ b/Runtime/Numerics/IVectorMath.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 106146a67b5f75647bef03ff84a853bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From baf1c70a9a61c8d1a9c25ef73740772b5d4570d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 15 Aug 2023 14:22:12 +0200 Subject: [PATCH 238/301] added Rect.ByCenter --- Runtime/Extensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 7483432..44119a6 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -505,6 +505,10 @@ public static Vector2 Lerp( this Rect r, Vector2 tPos ) => /// The y axis range of this rectangle /// The rectangle to get the y range of public static FloatRange RangeY( this Rect rect ) => ( rect.yMin, rect.yMax ); + + /// Places the center of this rectangle at its position, + /// useful together with the constructor to define it by center instead of by corner + public static Rect ByCenter( this Rect r ) => new Rect( r ) { center = r.position }; #endregion From 945b358750e916a0ce4aa0c1c22fe4460f3df14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Aug 2023 16:14:25 +0200 Subject: [PATCH 239/301] made the generic vector math allocation free --- Runtime/Numerics/IVectorMath.cs | 97 +++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/Runtime/Numerics/IVectorMath.cs b/Runtime/Numerics/IVectorMath.cs index 3cdd5aa..6fa02dd 100644 --- a/Runtime/Numerics/IVectorMath.cs +++ b/Runtime/Numerics/IVectorMath.cs @@ -4,53 +4,44 @@ namespace Freya { - public interface IVectorMath { + public static class VectorMathExt { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; - [MethodImpl( INLINE )] V Add( V a, V b ); - [MethodImpl( INLINE )] V Sub( V a, V b ); - [MethodImpl( INLINE )] V Mul( V v, float c ); - [MethodImpl( INLINE )] V Div( V v, float c ); - [MethodImpl( INLINE )] float Dot( V a, V b ); - [MethodImpl( INLINE )] float Mag( V v ); - [MethodImpl( INLINE )] V Normalize( V v ); - [MethodImpl( INLINE )] V Mul( float c, V v ) => Mul( v, c ); - [MethodImpl( INLINE )] float SqMag( V v ) => Dot( v, v ); - [MethodImpl( INLINE )] float SqDist( V a, V b ) => SqMag( Sub( b, a ) ); - [MethodImpl( INLINE )] float Dist( V a, V b ) => MathF.Sqrt( SqDist( b, a ) ); - [MethodImpl( INLINE )] V VecProject( V p, V to ) => Mul( to, BasisProject( p, to ) ); - [MethodImpl( INLINE )] V VecReject( V p, V to ) => Sub( p, VecProject( p, to ) ); - [MethodImpl( INLINE )] float BasisProject( V p, V to ) => Dot( p, to ) / Dot( to, to ); - [MethodImpl( INLINE )] V VecFromLineToPoint( V o, V n, V p ) => VecReject( Sub( p, o ), n ); - [MethodImpl( INLINE )] float SqDistFromPointToLine( V o, V n, V p ) => SqMag( VecFromLineToPoint( o, n, p ) ); - [MethodImpl( INLINE )] V GetPointAlongLine( V o, V n, float t ) => Add( o, Mul( n, t ) ); - [MethodImpl( INLINE )] float ProjPointToLineSegmentTValue( V a, V b, V p ) => Mathf.Clamp01( ProjPointToLineTValue( a, Sub( b, a ), p ) ); - [MethodImpl( INLINE )] float ProjPointToLineTValue( V o, V n, V p ) => BasisProject( Sub( p, o ), n ); - [MethodImpl( INLINE )] V ProjPointToLine( V o, V n, V p ) => Add( o, VecProject( Sub( p, o ), n ) ); - [MethodImpl( INLINE )] V Lerp( V a, V b, float t ) => Add( Mul( 1f - t, a ), Mul( t, b ) ); - - (float tA, float tB) ClosestPointBetweenLinesTValues( V aOrigin, V aDir, V bOrigin, V bDir ) { + [MethodImpl( INLINE )] public static float SqMag( this VM vm, V v ) where VM : struct, IVectorMath => vm.Dot( v, v ); + [MethodImpl( INLINE )] public static float SqDist( this VM vm, V a, V b ) where VM : struct, IVectorMath => vm.SqMag( vm.Sub( b, a ) ); + [MethodImpl( INLINE )] public static float Dist( this VM vm, V a, V b ) where VM : struct, IVectorMath => MathF.Sqrt( vm.SqDist( b, a ) ); + [MethodImpl( INLINE )] public static V VecProject( this VM vm, V p, V to ) where VM : struct, IVectorMath => vm.Mul( to, vm.BasisProject( p, to ) ); + [MethodImpl( INLINE )] public static V VecReject( this VM vm, V p, V to ) where VM : struct, IVectorMath => vm.Sub( p, vm.VecProject( p, to ) ); + [MethodImpl( INLINE )] public static float BasisProject( this VM vm, V p, V to ) where VM : struct, IVectorMath => vm.Dot( p, to ) / vm.Dot( to, to ); + [MethodImpl( INLINE )] public static V VecFromLineToPoint( this VM vm, V o, V n, V p ) where VM : struct, IVectorMath => vm.VecReject( vm.Sub( p, o ), n ); + [MethodImpl( INLINE )] public static float SqDistFromPointToLine( this VM vm, V o, V n, V p ) where VM : struct, IVectorMath => vm.SqMag( vm.VecFromLineToPoint( o, n, p ) ); + [MethodImpl( INLINE )] public static V GetPointAlongLine( this VM vm, V o, V n, float t ) where VM : struct, IVectorMath => vm.Add( o, vm.Mul( n, t ) ); + [MethodImpl( INLINE )] public static float ProjPointToLineSegmentTValue( this VM vm, V a, V b, V p ) where VM : struct, IVectorMath => Mathf.Clamp01( vm.ProjPointToLineTValue( a, vm.Sub( b, a ), p ) ); + [MethodImpl( INLINE )] public static float ProjPointToLineTValue( this VM vm, V o, V n, V p ) where VM : struct, IVectorMath => vm.BasisProject( vm.Sub( p, o ), n ); + [MethodImpl( INLINE )] public static V ProjPointToLine( this VM vm, V o, V n, V p ) where VM : struct, IVectorMath => vm.Add( o, vm.VecProject( vm.Sub( p, o ), n ) ); + + public static (float tA, float tB) ClosestPointBetweenLinesTValues( this VM vm, V aOrigin, V aDir, V bOrigin, V bDir ) where VM : struct, IVectorMath { // source: https://math.stackexchange.com/questions/2213165/find-shortest-distance-between-lines-in-3d - V e = Sub( aOrigin, bOrigin ); - float be = Dot( aDir, e ); - float de = Dot( bDir, e ); - float bd = Dot( aDir, bDir ); - float b2 = Dot( aDir, aDir ); - float d2 = Dot( bDir, bDir ); + V e = vm.Sub( aOrigin, bOrigin ); + float be = vm.Dot( aDir, e ); + float de = vm.Dot( bDir, e ); + float bd = vm.Dot( aDir, bDir ); + float b2 = vm.Dot( aDir, aDir ); + float d2 = vm.Dot( bDir, bDir ); float A = -b2 * d2 + bd * bd; float s = ( -b2 * de + be * bd ) / A; float t = ( d2 * be - de * bd ) / A; return ( t, s ); } - public bool TryIntersectSphereAtOrigin( V o, V n, float r, out (float tMin, float tMax) tValues ) { - float nn = Dot( n, n ); + public static bool TryIntersectSphereAtOrigin( this VM vm, V o, V n, float r, out (float tMin, float tMax) tValues ) where VM : struct, IVectorMath { + float nn = vm.Dot( n, n ); if( nn <= 0f ) { // vector has zero length, there's no direction tValues = default; return false; } - float oo = Dot( o, o ); - float on = Dot( o, n ); + float oo = vm.Dot( o, o ); + float on = vm.Dot( o, n ); // quadratic terms double A = nn; @@ -74,6 +65,19 @@ public bool TryIntersectSphereAtOrigin( V o, V n, float r, out (float tMin, floa } + public interface IVectorMath { + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] V Add( V a, V b ); + [MethodImpl( INLINE )] V Sub( V a, V b ); + [MethodImpl( INLINE )] V Mul( V v, float c ); + [MethodImpl( INLINE )] V Mul( float c, V v ); + [MethodImpl( INLINE )] V Div( V v, float c ); + [MethodImpl( INLINE )] float Dot( V a, V b ); + [MethodImpl( INLINE )] float Mag( V v ); + [MethodImpl( INLINE )] V Normalize( V v ); + [MethodImpl( INLINE )] V Lerp( V a, V b, float t ); + } + public struct VectorMath1D : IVectorMath { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; [MethodImpl( INLINE )] public float Add( float a, float b ) => a + b; @@ -83,6 +87,9 @@ public struct VectorMath1D : IVectorMath { [MethodImpl( INLINE )] public float Dot( float a, float b ) => a * b; [MethodImpl( INLINE )] public float Mag( float v ) => MathF.Abs( v ); [MethodImpl( INLINE )] public float Normalize( float v ) => v < 0 ? -1 : 1; + + // shared implementations + [MethodImpl( INLINE )] public float Lerp( float a, float b, float t ) => ( 1f - t ) * a + t * b; } public struct VectorMath2D : IVectorMath { @@ -90,10 +97,18 @@ public struct VectorMath2D : IVectorMath { [MethodImpl( INLINE )] public Vector2 Add( Vector2 a, Vector2 b ) => new(a.x + b.x, a.y + b.y); [MethodImpl( INLINE )] public Vector2 Sub( Vector2 a, Vector2 b ) => new(a.x - b.x, a.y - b.y); [MethodImpl( INLINE )] public Vector2 Mul( Vector2 v, float c ) => new(v.x * c, v.y * c); + [MethodImpl( INLINE )] public Vector2 Mul( float c, Vector2 v ) => new(v.x * c, v.y * c); [MethodImpl( INLINE )] public Vector2 Div( Vector2 v, float c ) => new(v.x / c, v.y / c); [MethodImpl( INLINE )] public float Dot( Vector2 a, Vector2 b ) => a.x * b.x + a.y * b.y; [MethodImpl( INLINE )] public float Mag( Vector2 v ) => MathF.Sqrt( Dot( v, v ) ); [MethodImpl( INLINE )] public Vector2 Normalize( Vector2 v ) => Div( v, Mag( v ) ); + + // shared implementations + [MethodImpl( INLINE )] public Vector2 Lerp( Vector2 a, Vector2 b, float t ) { + float omt = 1f - t; + return new Vector2( omt * a.x + t * b.x, omt * a.y + t * b.y ); + } + } public struct VectorMath3D : IVectorMath { @@ -101,10 +116,17 @@ public struct VectorMath3D : IVectorMath { [MethodImpl( INLINE )] public Vector3 Add( Vector3 a, Vector3 b ) => new(a.x + b.x, a.y + b.y, a.z + b.z); [MethodImpl( INLINE )] public Vector3 Sub( Vector3 a, Vector3 b ) => new(a.x - b.x, a.y - b.y, a.z - b.z); [MethodImpl( INLINE )] public Vector3 Mul( Vector3 v, float c ) => new(v.x * c, v.y * c, v.z * c); + [MethodImpl( INLINE )] public Vector3 Mul( float c, Vector3 v ) => new(v.x * c, v.y * c, v.z * c); [MethodImpl( INLINE )] public Vector3 Div( Vector3 v, float c ) => new(v.x / c, v.y / c, v.z / c); [MethodImpl( INLINE )] public float Dot( Vector3 a, Vector3 b ) => a.x * b.x + a.y * b.y + a.z * b.z; [MethodImpl( INLINE )] public float Mag( Vector3 v ) => MathF.Sqrt( Dot( v, v ) ); [MethodImpl( INLINE )] public Vector3 Normalize( Vector3 v ) => Div( v, Mag( v ) ); + + // shared implementations + [MethodImpl( INLINE )] public Vector3 Lerp( Vector3 a, Vector3 b, float t ) { + float omt = 1f - t; + return new Vector3( omt * a.x + t * b.x, omt * a.y + t * b.y, omt * a.z + t * b.z ); + } } public struct VectorMath4D : IVectorMath { @@ -112,10 +134,17 @@ public struct VectorMath4D : IVectorMath { [MethodImpl( INLINE )] public Vector4 Add( Vector4 a, Vector4 b ) => new(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); [MethodImpl( INLINE )] public Vector4 Sub( Vector4 a, Vector4 b ) => new(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); [MethodImpl( INLINE )] public Vector4 Mul( Vector4 v, float c ) => new(v.x * c, v.y * c, v.z * c, v.w * c); + [MethodImpl( INLINE )] public Vector4 Mul( float c, Vector4 v ) => new(v.x * c, v.y * c, v.z * c, v.w * c); [MethodImpl( INLINE )] public Vector4 Div( Vector4 v, float c ) => new(v.x / c, v.y / c, v.z / c, v.w / c); [MethodImpl( INLINE )] public float Dot( Vector4 a, Vector4 b ) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; [MethodImpl( INLINE )] public float Mag( Vector4 v ) => MathF.Sqrt( Dot( v, v ) ); [MethodImpl( INLINE )] public Vector4 Normalize( Vector4 v ) => Div( v, Mag( v ) ); + + // shared implementations + [MethodImpl( INLINE )] public Vector4 Lerp( Vector4 a, Vector4 b, float t ) { + float omt = 1f - t; + return new Vector4( omt * a.x + t * b.x, omt * a.y + t * b.y, omt * a.z + t * b.z, omt * a.w + t * b.w ); + } } } \ No newline at end of file From 2aee4dbf77b68e74b986c8a92b49704b2b439719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Aug 2023 16:16:18 +0200 Subject: [PATCH 240/301] allocation free CatenaryToPoint --- Runtime/Curves/CatenaryToPoint.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Runtime/Curves/CatenaryToPoint.cs b/Runtime/Curves/CatenaryToPoint.cs index 68c1dee..8dfbd9d 100644 --- a/Runtime/Curves/CatenaryToPoint.cs +++ b/Runtime/Curves/CatenaryToPoint.cs @@ -130,14 +130,13 @@ void ReadyForEvaluation() { // Now we've got a catenary on our hands unless something explodes. float c = MathF.Sqrt( s * s - p.y * p.y ); float pAbsX = p.x.Abs(); // solve only in x > 0 - float R( float a ) => 2 * a * Mathfs.Sinh( pAbsX / ( 2 * a ) ) - c; // set up root solve function // find bounds of the root float xRoot = ( p.x * p.x ) / ( 2 * s ); // intial guess based on freya's flawless heuristics - if( TryFindRootBounds( R, xRoot, out FloatRange xRange ) ) { + if( TryFindRootBounds( pAbsX, c, xRoot, out FloatRange xRange ) ) { // refine range, if necessary (which is very likely) if( Mathfs.Approximately( xRange.Length, 0 ) == false ) - RootFindBisections( R, ref xRange, BISECT_REFINE_COUNT ); // Catenary seems valid, with roots inside, refine the range + RootFindBisections( pAbsX, c, ref xRange, BISECT_REFINE_COUNT ); // Catenary seems valid, with roots inside, refine the range a = xRange.Center; // set a to the middle of the latest range delta = CalcCatenaryDelta( a, p ); // find delta to pass through both points arcLenSampleOffset = CalcArcLenSampleOffset( delta.x, a ); @@ -149,6 +148,9 @@ void ReadyForEvaluation() { } } + // root solve function + static float R( float a, float pAbsX, float c ) => 2 * a * Mathfs.Sinh( pAbsX / ( 2 * a ) ) - c; + // Calculates the arc length offset so that it's relative to the start of the chain when evaluating by arc length static float CalcArcLenSampleOffset( float deltaX, float a ) => Catenary.EvalArcLen( -deltaX, a ); @@ -162,8 +164,8 @@ static Vector2 CalcCatenaryDelta( float a, Vector2 p ) { // presumes a decreasing function with one root in x > 0 // g = initial guess - static bool TryFindRootBounds( Func R, float g, out FloatRange xRange ) { - float y = R( g ); + static bool TryFindRootBounds( float pAbsX, float c, float g, out FloatRange xRange ) { + float y = R( pAbsX, c, g ); xRange = new FloatRange( g, g ); if( Mathfs.Approximately( y, 0 ) ) // somehow landed *on* our root in our initial guess return true; @@ -176,7 +178,7 @@ static bool TryFindRootBounds( Func R, float g, out FloatRange xRa // exponentially search for upper bound xRange.a = xRange.b; xRange.b = g * MathF.Pow( 2, n ); - y = R( xRange.b ); + y = R( xRange.b, pAbsX, c ); if( y < 0 ) return true; // upper bound found! } else { @@ -184,7 +186,7 @@ static bool TryFindRootBounds( Func R, float g, out FloatRange xRa // exponentially search for lower bound xRange.b = xRange.a; xRange.a = g * MathF.Pow( 2, -n ); - y = R( xRange.a ); + y = R( xRange.a, pAbsX, c ); if( y > 0 ) return true; // lower bound found! } @@ -193,14 +195,14 @@ static bool TryFindRootBounds( Func R, float g, out FloatRange xRa return false; // no root found } - static void RootFindBisections( Func F, ref FloatRange xRange, int iterationCount ) { + static void RootFindBisections( float pAbsX, float c, ref FloatRange xRange, int iterationCount ) { for( int i = 0; i < iterationCount; i++ ) - RootFindBisection( F, ref xRange ); + RootFindBisection( pAbsX, c, ref xRange ); } - static void RootFindBisection( Func F, ref FloatRange xRange ) { + static void RootFindBisection( float pAbsX, float c, ref FloatRange xRange ) { float xInter = xRange.Center; // bisection - float yInter = F( xInter ); + float yInter = R( xInter, pAbsX, c ); if( yInter > 0 ) xRange.a = xInter; // adjust left bound else From ed4cd378ab983fed815cca11ada230c6de6483f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Aug 2023 16:17:08 +0200 Subject: [PATCH 241/301] unit & pure im quaternion specific functions --- Runtime/Extensions.cs | 50 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 44119a6..14b6dd2 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -380,17 +380,41 @@ public static Quaternion Ln( this Quaternion q ) { ); } + /// Returns the natural logarithm of a unit quaternion + public static Quaternion LnUnit( this Quaternion q ) { + double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; + double vMag = Math.Sqrt( vMagSq ); + double theta = Math.Atan2( vMag, q.w ); + double scV = vMag < 0.01f ? Mathfs.SincRcp( theta ) : theta / vMag; + return new Quaternion( + (float)( scV * q.x ), + (float)( scV * q.y ), + (float)( scV * q.z ), + 0f + ); + } + /// Returns the natural exponent of a quaternion public static Quaternion Exp( this Quaternion q ) { - Vector3 v = new(q.x, q.y, q.z); - double vMag = Math.Sqrt( (double)v.x * v.x + (double)v.y * v.y + (double)v.z * v.z ); + double vMag = Math.Sqrt( (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z ); double sc = Math.Exp( q.w ); double scV = sc * Mathfs.Sinc( vMag ); - return new Quaternion( (float)( scV * v.x ), (float)( scV * v.y ), (float)( scV * v.z ), (float)( sc * Math.Cos( vMag ) ) ); + return new Quaternion( (float)( scV * q.x ), (float)( scV * q.y ), (float)( scV * q.z ), (float)( sc * Math.Cos( vMag ) ) ); + } + + /// Returns the natural exponent of a pure imaginary quaternion + public static Quaternion ExpPureIm( this Quaternion q ) { + double vMag = Math.Sqrt( (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z ); + double scV = Mathfs.Sinc( vMag ); + return new Quaternion( (float)( scV * q.x ), (float)( scV * q.y ), (float)( scV * q.z ), (float)Math.Cos( vMag ) ); } /// Returns the quaternion raised to a real power public static Quaternion Pow( this Quaternion q, float x ) { + switch( x ) { + case 0f: return new Quaternion( 0, 0, 0, 1 ); + case 1f: return q; + } double vSqMag = q.x * q.x + q.y * q.y + q.z * q.z; double rSqMag = q.w * q.w; double vMag = Math.Sqrt( vSqMag ); @@ -406,6 +430,24 @@ public static Quaternion Pow( this Quaternion q, float x ) { return new Quaternion( (float)( sin * nx ), (float)( sin * ny ), (float)( sin * nz ), (float)cos ); } + /// Returns the unit quaternion raised to a real power + public static Quaternion PowUnit( this Quaternion q, float x ) { + switch( x ) { + case 0f: return new Quaternion( 0, 0, 0, 1 ); + case 1f: return q; + } + double vSqMag = q.x * q.x + q.y * q.y + q.z * q.z; + double vMag = Math.Sqrt( vSqMag ); + double nx = q.x / vMag; + double ny = q.y / vMag; + double nz = q.z / vMag; + double ang = Math.Acos( q.w.ClampNeg1to1() ); + double theta = ang * x; + double cos = Math.Cos( theta ); + double sin = Math.Sin( theta ); + return new Quaternion( (float)( sin * nx ), (float)( sin * ny ), (float)( sin * nz ), (float)cos ); + } + /// Returns the squared magnitude of this quaternion public static float SqrMagnitude( this Quaternion q ) => (float)( (double)q.w * q.w + (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z ); @@ -505,7 +547,7 @@ public static Vector2 Lerp( this Rect r, Vector2 tPos ) => /// The y axis range of this rectangle /// The rectangle to get the y range of public static FloatRange RangeY( this Rect rect ) => ( rect.yMin, rect.yMax ); - + /// Places the center of this rectangle at its position, /// useful together with the constructor to define it by center instead of by corner public static Rect ByCenter( this Rect r ) => new Rect( r ) { center = r.position }; From a7cc5aec5d151a1d6dfc4b79008acc480a84b57f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 16 Aug 2023 16:17:40 +0200 Subject: [PATCH 242/301] QuaternionMatrix4x1 --- Editor/MathfsCodegen.cs | 62 ++++++++++++-------- Runtime/Numerics/QuaternionMatrix4x1.cs | 30 ++++++++++ Runtime/Numerics/QuaternionMatrix4x1.cs.meta | 11 ++++ 3 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 Runtime/Numerics/QuaternionMatrix4x1.cs create mode 100644 Runtime/Numerics/QuaternionMatrix4x1.cs.meta diff --git a/Editor/MathfsCodegen.cs b/Editor/MathfsCodegen.cs index d04af6d..e72f638 100644 --- a/Editor/MathfsCodegen.cs +++ b/Editor/MathfsCodegen.cs @@ -154,6 +154,16 @@ static bool IsSplineType( string name, out SplineType type, out int dim ) { return false; } + enum ElemType { + _1D = 1, + _2D, + _3D, + _4D, + Quat + } + + static ElemType GetVectorOfDim( int dim ) => (ElemType)dim; + [MenuItem( "Assets/Run Mathfs Codegen" )] public static void Regenerate() { for( int dim = 1; dim < 5; dim++ ) { // 1D, 2D, 3D, 4D @@ -162,32 +172,38 @@ public static void Regenerate() { GenerateUniformSplineType( typeHermite, dim ); GenerateUniformSplineType( typeBspline, dim ); GenerateUniformSplineType( typeCatRom, dim ); - GenerateMatrix( 3, dim ); - GenerateMatrix( 4, dim ); + GenerateMatrixNx1( 3, GetVectorOfDim( dim ) ); + GenerateMatrixNx1( 4, GetVectorOfDim( dim ) ); } + GenerateMatrixNx1( 4, ElemType.Quat ); } - public static string GetLerpName( int dim ) { + static string GetLerpName( ElemType dim ) { return dim switch { - 1 => "Mathfs.Lerp", - 2 => "Vector2.LerpUnclamped", - 3 => "Vector3.LerpUnclamped", - 4 => "Vector4.LerpUnclamped", - _ => throw new IndexOutOfRangeException() + ElemType._1D => "Mathfs.Lerp", + ElemType._2D => "Vector2.LerpUnclamped", + ElemType._3D => "Vector3.LerpUnclamped", + ElemType._4D => "Vector4.LerpUnclamped", + ElemType.Quat => "Quaternion.SlerpUnclamped", + _ => throw new IndexOutOfRangeException() }; } - - static void GenerateMatrix( int count, int dim ) { + static void GenerateMatrixNx1( int count, ElemType dim ) { const string vCompStr = "xyzw"; const string vCompStrUp = "XYZW"; + int elemCompCount = ( (int)dim ).AtMost( 4 ); // quats also have 4 int[] elemRange = Enumerable.Range( 0, count ).ToArray(); - int[] compRange = Enumerable.Range( 0, dim ).ToArray(); + int[] compRange = Enumerable.Range( 0, elemCompCount ).ToArray(); string[] compRangeStr = compRange.Select( c => vCompStr[c].ToString() ).ToArray(); string JoinRange( string separator, Func elem ) => string.Join( separator, elemRange.Select( elem ) ); - string typePrefix = dim switch { > 1 => $"Vector{dim}", _ => "" }; + string elemType = dim switch { + ElemType._1D => "float", + ElemType.Quat => "Quaternion", + _ => $"Vector{elemCompCount}" + }; + string typePrefix = dim == ElemType._1D ? "" : elemType; string lerpName = GetLerpName( dim ); - string elemType = dim switch { 1 => "float", > 1 => $"Vector{dim}", _ => throw new Exception( "Invalid type" ) }; string typeName = $"{typePrefix}Matrix{count}x1"; string csParams = JoinRange( ", ", i => $"m{i}" ); string csParamsThis = JoinRange( ", ", i => $"this.m{i}" ); @@ -197,13 +213,13 @@ static void GenerateMatrix( int count, int dim ) { string equalsCompare = JoinRange( " && ", i => $"m{i}.Equals( other.m{i} )" ); string equalsOpCompare = JoinRange( " && ", i => $"a.m{i} == b.m{i}" ); string lerpAtoB = JoinRange( ", ", i => $"{lerpName}( a.m{i}, b.m{i}, t )" ); - + bool isMultiComponentVector = dim != ElemType._1D && dim != ElemType.Quat; // generate content CodeGenerator code = new CodeGenerator(); code.AppendHeader(); code.Append( "using System;" ); - if( dim > 1 ) // for Vector2/3 + if( dim != ElemType._1D ) // for Vector2/3 code.Append( "using UnityEngine;" ); using( code.BracketScope( "namespace Freya" ) ) { @@ -214,7 +230,7 @@ static void GenerateMatrix( int count, int dim ) { // constructors code.Append( $"public {typeName}({ctorParams}) => ({csParamsThis}) = ({csParams});" ); - if( dim > 1 ) { // compose from float matrices + if( isMultiComponentVector ) { // compose from float matrices string s = $"public {typeName}({string.Join( ", ", compRangeStr.Select( c => $"Matrix{count}x1 {c}" ) )}) => "; s += $"({csParams}) = ({JoinRange( ", ", i => $"new {elemType}({string.Join( ", ", compRangeStr.Select( c => $"{c}.m{i}" ) )})" )});"; code.Append( s ); @@ -232,8 +248,8 @@ static void GenerateMatrix( int count, int dim ) { } // component extraction for vector-valued matrices - if( dim > 1 ) { - for( int c = 0; c < dim; c++ ) { + if( isMultiComponentVector ) { + for( int c = 0; c < elemCompCount; c++ ) { int cc = c; string parameters = JoinRange( ", ", i => $"m{i}.{vCompStr[cc]}" ); code.Append( $"public Matrix{count}x1 {vCompStrUp[c]} => new({parameters});" ); @@ -243,7 +259,8 @@ static void GenerateMatrix( int count, int dim ) { // interpolation code.Summary( "Linearly interpolates between two matrices, based on a value t" ); code.Param( "t", "The value to blend by" ); - code.Append( $"public static {typeName} Lerp( {typeName} a, {typeName} b, float t ) => new {typeName}({lerpAtoB});" ); + string interpName = dim == ElemType.Quat ? "Slerp" : "Lerp"; + code.Append( $"public static {typeName} {interpName}( {typeName} a, {typeName} b, float t ) => new {typeName}({lerpAtoB});" ); // comparison/operators code.Append( $"public static bool operator ==( {typeName} a, {typeName} b ) => {equalsOpCompare};" ); @@ -256,9 +273,8 @@ static void GenerateMatrix( int count, int dim ) { } } - // save/finalize - string path = $"Assets/Mathfs/Runtime/Numerics/{typeName}.cs"; + string path = $"Assets/Spline Plugin/Mathfs/Runtime/Numerics/{typeName}.cs"; File.WriteAllLines( path, code.content ); } @@ -273,7 +289,7 @@ static void GenerateUniformSplineType( SplineType type, int dim ) { string[] points = type.paramNames; int[] ptRange = Enumerable.Range( 0, ptCount ).ToArray(); string[] pointDescs = type.paramDescs; - string lerpName = GetLerpName( dim ); + string lerpName = GetLerpName( (ElemType)dim ); string pointMatrixType = $"{( dim == 1 ? "" : dataType )}Matrix{ptCount}x1"; string JoinRange( string separator, Func elem ) => string.Join( separator, ptRange.Select( elem ) ); @@ -471,7 +487,7 @@ static void GenerateUniformSplineType( SplineType type, int dim ) { } } - string path = $"Assets/Mathfs/Runtime/Splines/Uniform Spline Segments/{structName}.cs"; + string path = $"Assets/Spline Plugin/Mathfs/Runtime/Splines/Uniform Spline Segments/{structName}.cs"; File.WriteAllLines( path, code.content ); } diff --git a/Runtime/Numerics/QuaternionMatrix4x1.cs b/Runtime/Numerics/QuaternionMatrix4x1.cs new file mode 100644 index 0000000..3ec3bed --- /dev/null +++ b/Runtime/Numerics/QuaternionMatrix4x1.cs @@ -0,0 +1,30 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using UnityEngine; +namespace Freya { + /// A 4x1 column matrix with Quaternion values + [Serializable] public struct QuaternionMatrix4x1 { + public Quaternion m0, m1, m2, m3; + public QuaternionMatrix4x1(Quaternion m0, Quaternion m1, Quaternion m2, Quaternion m3) => (this.m0, this.m1, this.m2, this.m3) = (m0, m1, m2, m3); + public Quaternion this[int row] { + get => row switch{0 => m0, 1 => m1, 2 => m2, 3 => m3, _ => throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" )}; + set { + switch(row) { + case 0: m0 = value; break; case 1: m1 = value; break; case 2: m2 = value; break; case 3: m3 = value; break; + default: throw new IndexOutOfRangeException( $"Matrix row index has to be from 0 to 3, got: {row}" ); + } + } + } + /// Linearly interpolates between two matrices, based on a value t + /// The value to blend by + public static QuaternionMatrix4x1 Slerp( QuaternionMatrix4x1 a, QuaternionMatrix4x1 b, float t ) => new QuaternionMatrix4x1(Quaternion.SlerpUnclamped( a.m0, b.m0, t ), Quaternion.SlerpUnclamped( a.m1, b.m1, t ), Quaternion.SlerpUnclamped( a.m2, b.m2, t ), Quaternion.SlerpUnclamped( a.m3, b.m3, t )); + public static bool operator ==( QuaternionMatrix4x1 a, QuaternionMatrix4x1 b ) => a.m0 == b.m0 && a.m1 == b.m1 && a.m2 == b.m2 && a.m3 == b.m3; + public static bool operator !=( QuaternionMatrix4x1 a, QuaternionMatrix4x1 b ) => !( a == b ); + public bool Equals( QuaternionMatrix4x1 other ) => m0.Equals( other.m0 ) && m1.Equals( other.m1 ) && m2.Equals( other.m2 ) && m3.Equals( other.m3 ); + public override bool Equals( object obj ) => obj is QuaternionMatrix4x1 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( m0, m1, m2, m3 ); + public override string ToString() => $"[{m0}]\n[{m1}]\n[{m2}]\n[{m3}]"; + } +} diff --git a/Runtime/Numerics/QuaternionMatrix4x1.cs.meta b/Runtime/Numerics/QuaternionMatrix4x1.cs.meta new file mode 100644 index 0000000..7ace23a --- /dev/null +++ b/Runtime/Numerics/QuaternionMatrix4x1.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 526976a4e61ad714696fce96490bcd19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From b95468fcd9db6a38f4d3a1687b69e9a6ee3acb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 18 Aug 2023 16:29:26 +0200 Subject: [PATCH 243/301] explicit RationalMtx4x4 to Matrix4x4 op --- Runtime/Numerics/RationalMatrix4x4.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Runtime/Numerics/RationalMatrix4x4.cs b/Runtime/Numerics/RationalMatrix4x4.cs index d65b638..60ab014 100644 --- a/Runtime/Numerics/RationalMatrix4x4.cs +++ b/Runtime/Numerics/RationalMatrix4x4.cs @@ -115,6 +115,15 @@ public Rational Determinant { }; } + public static explicit operator Matrix4x4( RationalMatrix4x4 c ) { + return CharMatrix.Create( + (float)c.m00, (float)c.m01, (float)c.m02, (float)c.m03, + (float)c.m10, (float)c.m11, (float)c.m12, (float)c.m13, + (float)c.m20, (float)c.m21, (float)c.m22, (float)c.m23, + (float)c.m30, (float)c.m31, (float)c.m32, (float)c.m33 + ); + } + public static RationalMatrix4x4 operator *( RationalMatrix4x4 c, Rational v ) => new(c.m00 * v, c.m01 * v, c.m02 * v, c.m03 * v, c.m10 * v, c.m11 * v, c.m12 * v, c.m13 * v, From 84426c65ef6b7055db5f29c8f50bc366193c0ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 18 Aug 2023 16:29:48 +0200 Subject: [PATCH 244/301] optimized Matrix create method --- Runtime/Splines/CharMatrix.cs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/Runtime/Splines/CharMatrix.cs b/Runtime/Splines/CharMatrix.cs index 0f1ab21..bca0972 100644 --- a/Runtime/Splines/CharMatrix.cs +++ b/Runtime/Splines/CharMatrix.cs @@ -66,13 +66,26 @@ public static class CharMatrix { /// The characteristic matrix of the spline to convert from public static RationalMatrix4x4 GetConversionMatrix( RationalMatrix4x4 from, RationalMatrix4x4 to ) => to.Inverse * from; - public static Matrix4x4 Create( float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33 ) => - new( - new Vector4( m00, m10, m20, m30 ), - new Vector4( m01, m11, m21, m31 ), - new Vector4( m02, m12, m22, m32 ), - new Vector4( m03, m13, m23, m33 ) - ); + public static Matrix4x4 Create( float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33 ) { + Matrix4x4 m; + m.m00 = m00; + m.m10 = m10; + m.m20 = m20; + m.m30 = m30; + m.m01 = m01; + m.m11 = m11; + m.m21 = m21; + m.m31 = m31; + m.m02 = m02; + m.m12 = m12; + m.m22 = m22; + m.m32 = m32; + m.m03 = m03; + m.m13 = m13; + m.m23 = m23; + m.m33 = m33; + return m; + } /// Returns the basis function (weight) for the given spline points by index i, /// equal to the t-matrix multiplied by the characteristic matrix From 6513eadb1a1c3b871742fe1768089f6a18a48034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 18 Aug 2023 16:29:58 +0200 Subject: [PATCH 245/301] static uniform catrom basis functions --- Runtime/Splines/CharMatrix.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Runtime/Splines/CharMatrix.cs b/Runtime/Splines/CharMatrix.cs index bca0972..6930a48 100644 --- a/Runtime/Splines/CharMatrix.cs +++ b/Runtime/Splines/CharMatrix.cs @@ -38,6 +38,13 @@ public static class CharMatrix { -1, 3, -3, 1 ) / 2; + public static readonly Polynomial[] cubicCatmullRomBasisFunctions = { + GetBasisFunction( cubicCatmullRom, 0 ), + GetBasisFunction( cubicCatmullRom, 1 ), + GetBasisFunction( cubicCatmullRom, 2 ), + GetBasisFunction( cubicCatmullRom, 3 ) + }; + /// The characteristic matrix of a uniform cubic B-spline curve public static readonly RationalMatrix4x4 cubicUniformBspline = new RationalMatrix4x4( 1, 4, 1, 0, @@ -100,7 +107,7 @@ public static Polynomial GetBasisFunction( RationalMatrix4x4 c, int i ) { _ => throw new IndexOutOfRangeException( "Basis index needs to be between 0 and 3" ) }; } - + /// public static Polynomial GetBasisFunction( Matrix4x4 c, int i ) { return i switch { From 1b680096768b9b0c531a0ced8382d24c0a7ae874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 30 Aug 2023 16:33:53 +0200 Subject: [PATCH 246/301] fixed broken CatenaryToPoint --- Runtime/Curves/CatenaryToPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Curves/CatenaryToPoint.cs b/Runtime/Curves/CatenaryToPoint.cs index 8dfbd9d..212f897 100644 --- a/Runtime/Curves/CatenaryToPoint.cs +++ b/Runtime/Curves/CatenaryToPoint.cs @@ -165,7 +165,7 @@ static Vector2 CalcCatenaryDelta( float a, Vector2 p ) { // presumes a decreasing function with one root in x > 0 // g = initial guess static bool TryFindRootBounds( float pAbsX, float c, float g, out FloatRange xRange ) { - float y = R( pAbsX, c, g ); + float y = R( g, pAbsX, c ); xRange = new FloatRange( g, g ); if( Mathfs.Approximately( y, 0 ) ) // somehow landed *on* our root in our initial guess return true; From 33013a6fa1ab13710c7ee1688a95160257bca650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 30 Aug 2023 16:38:08 +0200 Subject: [PATCH 247/301] cleaned up & added some quaternion things --- Runtime/Extensions.cs | 62 ++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 14b6dd2..4565a06 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -371,7 +371,7 @@ public static Quaternion Ln( this Quaternion q ) { double vMag = Math.Sqrt( vMagSq ); double qMag = Math.Sqrt( vMagSq + (double)q.w * q.w ); double theta = Math.Atan2( vMag, q.w ); - double scV = vMag < 0.01f ? Mathfs.SincRcp( theta ) / qMag : theta / vMag; + double scV = vMag < 0.001 ? Mathfs.SincRcp( theta ) / qMag : theta / vMag; return new Quaternion( (float)( scV * q.x ), (float)( scV * q.y ), @@ -385,7 +385,7 @@ public static Quaternion LnUnit( this Quaternion q ) { double vMagSq = (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z; double vMag = Math.Sqrt( vMagSq ); double theta = Math.Atan2( vMag, q.w ); - double scV = vMag < 0.01f ? Mathfs.SincRcp( theta ) : theta / vMag; + double scV = vMag < 0.001 ? Mathfs.SincRcp( theta ) : theta / vMag; return new Quaternion( (float)( scV * q.x ), (float)( scV * q.y ), @@ -411,42 +411,24 @@ public static Quaternion ExpPureIm( this Quaternion q ) { /// Returns the quaternion raised to a real power public static Quaternion Pow( this Quaternion q, float x ) { - switch( x ) { - case 0f: return new Quaternion( 0, 0, 0, 1 ); - case 1f: return q; - } - double vSqMag = q.x * q.x + q.y * q.y + q.z * q.z; - double rSqMag = q.w * q.w; - double vMag = Math.Sqrt( vSqMag ); - double qMag = Math.Sqrt( rSqMag + vSqMag ); - double nx = q.x / vMag; - double ny = q.y / vMag; - double nz = q.z / vMag; - double ang = Math.Acos( ( q.w / qMag ).ClampNeg1to1() ); - double theta = ang * x; - double magPow = Math.Pow( qMag, x ); - double cos = magPow * Math.Cos( theta ); - double sin = magPow * Math.Sin( theta ); - return new Quaternion( (float)( sin * nx ), (float)( sin * ny ), (float)( sin * nz ), (float)cos ); + return x switch { + 0f => new Quaternion( 0, 0, 0, 1 ), + 1f => q, + _ => q.Ln().Mul( x ).Exp() + }; } /// Returns the unit quaternion raised to a real power public static Quaternion PowUnit( this Quaternion q, float x ) { - switch( x ) { - case 0f: return new Quaternion( 0, 0, 0, 1 ); - case 1f: return q; - } - double vSqMag = q.x * q.x + q.y * q.y + q.z * q.z; - double vMag = Math.Sqrt( vSqMag ); - double nx = q.x / vMag; - double ny = q.y / vMag; - double nz = q.z / vMag; - double ang = Math.Acos( q.w.ClampNeg1to1() ); - double theta = ang * x; - double cos = Math.Cos( theta ); - double sin = Math.Sin( theta ); - return new Quaternion( (float)( sin * nx ), (float)( sin * ny ), (float)( sin * nz ), (float)cos ); + return x switch { + 0f => new Quaternion( 0, 0, 0, 1 ), + 1f => q, + _ => q.LnUnit().Mul( x ).ExpPureIm() + }; } + + /// Returns the imaginary part of a quaternion as a vector + public static Vector3 Imag( this Quaternion q ) => new Vector3( q.x, q.y, q.z ); /// Returns the squared magnitude of this quaternion public static float SqrMagnitude( this Quaternion q ) => (float)( (double)q.w * q.w + (double)q.x * q.x + (double)q.y * q.y + (double)q.z * q.z ); @@ -472,6 +454,20 @@ public static Quaternion PowUnit( this Quaternion q, float x ) { /// public static Quaternion Inverse( this Quaternion q ) => Quaternion.Inverse( q ); + /// The inverse of a unit quaternion, equivalent to the quaternion conjugate + public static Quaternion InverseUnit( this Quaternion q ) { + return new Quaternion( -q.x, -q.y, -q.z, q.w ); + } + + /// The inverse of a pure imaginary unit quaternion, where w is assumed to be 0 + public static Quaternion InversePureIm( this Quaternion q ) { + float sqMag = q.x * q.x + q.y * q.y + q.z * q.z; + return new Quaternion( -q.x / sqMag, -q.y / sqMag, -q.z / sqMag, 0 ); + } + + /// Add to the magnitude of this quaternion + public static Quaternion AddMagnitude( this Quaternion q, float amount ) => amount == 0f ? q : q.Mul( 1 + amount / q.Magnitude() ); + #endregion #region Transform extensions From 385605e093f04ab0060ec23ad3dc3c1b129edab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 30 Aug 2023 17:29:10 +0200 Subject: [PATCH 248/301] sqDist support in catrom knot interval calcs --- .../Multi-Segment Splines/CatRom2DSpline.cs | 2 +- Runtime/Splines/SplineUtils.cs | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs b/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs index aea5d91..8b5383c 100644 --- a/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs +++ b/Runtime/Splines/Multi-Segment Splines/CatRom2DSpline.cs @@ -235,7 +235,7 @@ public void RecalculateKnots() { // todo: by caching distances and only recalculating the necessary ones for( int i = 1; i < ControlPointCount; i++ ) { float sqDist = Vector2.SqrMagnitude( nodes[i - 1].pos - nodes[i].pos ); - SetKnotInternal( i, SplineUtils.CalcCatRomKnot( nodes[i - 1].knot, sqDist, alpha ) ); + SetKnotInternal( i, SplineUtils.CalcCatRomKnot( nodes[i - 1].knot, sqDist, alpha, true ) ); } } } diff --git a/Runtime/Splines/SplineUtils.cs b/Runtime/Splines/SplineUtils.cs index bf39f4f..3af6256 100644 --- a/Runtime/Splines/SplineUtils.cs +++ b/Runtime/Splines/SplineUtils.cs @@ -30,16 +30,16 @@ public static float[] GenerateUniformKnots( int degree, int pCount, bool open ) internal static int BSplineKnotCount( int pointCount, int degree ) => degree + pointCount + 1; - public static float CalcCatRomKnot( float kPrev, float sqDist, float alpha ) { - return kPrev + CalcCatRomKnot( sqDist, alpha ).AtLeast( 0.00001f ); // ensure there are no duplicate knots + public static float CalcCatRomKnot( float kPrev, float sqDist, float alpha, bool isSquaredDist ) { + return kPrev + CalcCatRomKnot( sqDist, alpha, isSquaredDist ).AtLeast( 0.00001f ); // ensure there are no duplicate knots } - public static float CalcCatRomKnot( float squaredDistance, float alpha ) => + public static float CalcCatRomKnot( float dist, float alpha, bool isSquaredDist ) => alpha switch { 0 => 1, // uniform - 0.5f => squaredDistance, // centripetal - 1 => squaredDistance.Sqrt(), // chordal - _ => squaredDistance.Pow( 0.5f * alpha ) + 0.5f => isSquaredDist ? dist.Pow( 0.25f ) : Mathf.Sqrt( dist ), // centripetal + 1 => isSquaredDist ? Mathf.Sqrt( dist ) : dist, // chordal + _ => isSquaredDist ? dist.Pow( 0.5f * alpha ) : dist.Pow( alpha ) }; static readonly Matrix4x1 knotsUniformUnit = new(-1, 0, 1, 2); @@ -53,7 +53,7 @@ public static Matrix4x1 CalcCatRomKnots( Vector2Matrix4x1 m, float alpha, bool u float sqMag01 = Vector2.SqrMagnitude( m.m0 - m.m1 ); float sqMag12 = Vector2.SqrMagnitude( m.m1 - m.m2 ); float sqMag23 = Vector2.SqrMagnitude( m.m2 - m.m3 ); - return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval ); + return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval, isSquaredDist:true ); } public static Matrix4x1 CalcCatRomKnots( Vector3Matrix4x1 m, float alpha, bool unitInterval ) { @@ -62,13 +62,13 @@ public static Matrix4x1 CalcCatRomKnots( Vector3Matrix4x1 m, float alpha, bool u float sqMag01 = Vector3.SqrMagnitude( m.m0 - m.m1 ); float sqMag12 = Vector3.SqrMagnitude( m.m1 - m.m2 ); float sqMag23 = Vector3.SqrMagnitude( m.m2 - m.m3 ); - return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval ); + return CalcCatRomKnots( sqMag01, sqMag12, sqMag23, alpha, unitInterval, isSquaredDist:true ); } - static Matrix4x1 CalcCatRomKnots( float sqMag01, float sqMag12, float sqMag23, float alpha, bool unitInterval ) { - float i01 = CalcCatRomKnot( sqMag01, alpha ); - float i12 = CalcCatRomKnot( sqMag12, alpha ); - float i23 = CalcCatRomKnot( sqMag23, alpha ); + static Matrix4x1 CalcCatRomKnots( float dist01, float dist12, float dist23, float alpha, bool unitInterval, bool isSquaredDist ) { + float i01 = CalcCatRomKnot( dist01, alpha, isSquaredDist ); + float i12 = CalcCatRomKnot( dist12, alpha, isSquaredDist ); + float i23 = CalcCatRomKnot( dist23, alpha, isSquaredDist ); float k0, k1, k2, k3; if( unitInterval ) { return new(-i01 / i12, 0, 1, 1 + i23 / i12); From 8a3f043f6aec0635a79b40abf4403ac8d44ac692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 30 Aug 2023 17:29:25 +0200 Subject: [PATCH 249/301] uniform cubic hermite basis functions --- Runtime/Splines/CharMatrix.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Runtime/Splines/CharMatrix.cs b/Runtime/Splines/CharMatrix.cs index 6930a48..5e4fca2 100644 --- a/Runtime/Splines/CharMatrix.cs +++ b/Runtime/Splines/CharMatrix.cs @@ -29,6 +29,14 @@ public static class CharMatrix { -3, -2, 3, -1, 2, 1, -2, 1 ); + public static readonly Polynomial[] cubicHermitePositionBasisFunctions = { + GetBasisFunction( cubicHermite, 0 ), + GetBasisFunction( cubicHermite, 2 ) + }; + public static readonly Polynomial[] cubicHermiteVelocityBasisFunctions = { + GetBasisFunction( cubicHermite, 1 ), + GetBasisFunction( cubicHermite, 3 ) + }; /// The characteristic matrix of a uniform cubic catmull-rom curve public static readonly RationalMatrix4x4 cubicCatmullRom = new RationalMatrix4x4( From 0b10c00c6337a5b7efae8f5b9b9faf8cba3039e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 30 Aug 2023 21:17:41 +0200 Subject: [PATCH 250/301] added quaternion angle function in radians also added quaternions to IVectorMath, for better or worse --- Runtime/Mathfs.cs | 8 +++++++- Runtime/Numerics/IVectorMath.cs | 26 +++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 720d2ef..d7bc615 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -416,7 +416,7 @@ public static Vector4 Clamp01( Vector4 v ) => /// Clamps the value between -1 and 1 public static double ClampNeg1to1( double value ) => value < -1.0 ? -1.0 : value > 1.0 ? 1.0 : value; - + /// Clamps the value between -1 and 1 public static float ClampNeg1to1( float value ) => value < -1f ? -1f : value > 1f ? 1f : value; @@ -1126,6 +1126,12 @@ public static Vector3 ClampMagnitude( Vector3 v, float min, float max ) { return new Quaternion( 0, 0, v.y, v.x ); } + /// The angle between two quaternions, in radians + public static float Angle( Quaternion a, Quaternion b ) { + float num = Mathf.Min( Mathf.Abs( Quaternion.Dot( a, b ) ), 1f ); + return num > 0.999998986721039 ? 0.0f : (float)( MathF.Acos( num ) * 2.0 ); + } + /// Returns a 2D Pose from a point and a vector, representing the X axis /// The location of the pose /// The direction to create a 2D orientation from (does not have to be normalized) diff --git a/Runtime/Numerics/IVectorMath.cs b/Runtime/Numerics/IVectorMath.cs index 6fa02dd..c1514e5 100644 --- a/Runtime/Numerics/IVectorMath.cs +++ b/Runtime/Numerics/IVectorMath.cs @@ -9,7 +9,6 @@ public static class VectorMathExt { [MethodImpl( INLINE )] public static float SqMag( this VM vm, V v ) where VM : struct, IVectorMath => vm.Dot( v, v ); [MethodImpl( INLINE )] public static float SqDist( this VM vm, V a, V b ) where VM : struct, IVectorMath => vm.SqMag( vm.Sub( b, a ) ); - [MethodImpl( INLINE )] public static float Dist( this VM vm, V a, V b ) where VM : struct, IVectorMath => MathF.Sqrt( vm.SqDist( b, a ) ); [MethodImpl( INLINE )] public static V VecProject( this VM vm, V p, V to ) where VM : struct, IVectorMath => vm.Mul( to, vm.BasisProject( p, to ) ); [MethodImpl( INLINE )] public static V VecReject( this VM vm, V p, V to ) where VM : struct, IVectorMath => vm.Sub( p, vm.VecProject( p, to ) ); [MethodImpl( INLINE )] public static float BasisProject( this VM vm, V p, V to ) where VM : struct, IVectorMath => vm.Dot( p, to ) / vm.Dot( to, to ); @@ -74,6 +73,7 @@ public interface IVectorMath { [MethodImpl( INLINE )] V Div( V v, float c ); [MethodImpl( INLINE )] float Dot( V a, V b ); [MethodImpl( INLINE )] float Mag( V v ); + [MethodImpl( INLINE )] float Dist( V a, V b ); [MethodImpl( INLINE )] V Normalize( V v ); [MethodImpl( INLINE )] V Lerp( V a, V b, float t ); } @@ -86,6 +86,7 @@ public struct VectorMath1D : IVectorMath { [MethodImpl( INLINE )] public float Div( float v, float c ) => v / c; [MethodImpl( INLINE )] public float Dot( float a, float b ) => a * b; [MethodImpl( INLINE )] public float Mag( float v ) => MathF.Abs( v ); + [MethodImpl( INLINE )] public float Dist( float a, float b ) => MathF.Abs( b - a ); [MethodImpl( INLINE )] public float Normalize( float v ) => v < 0 ? -1 : 1; // shared implementations @@ -101,6 +102,7 @@ public struct VectorMath2D : IVectorMath { [MethodImpl( INLINE )] public Vector2 Div( Vector2 v, float c ) => new(v.x / c, v.y / c); [MethodImpl( INLINE )] public float Dot( Vector2 a, Vector2 b ) => a.x * b.x + a.y * b.y; [MethodImpl( INLINE )] public float Mag( Vector2 v ) => MathF.Sqrt( Dot( v, v ) ); + [MethodImpl( INLINE )] public float Dist( Vector2 a, Vector2 b ) => Mag( Sub( b, a ) ); [MethodImpl( INLINE )] public Vector2 Normalize( Vector2 v ) => Div( v, Mag( v ) ); // shared implementations @@ -120,6 +122,7 @@ public struct VectorMath3D : IVectorMath { [MethodImpl( INLINE )] public Vector3 Div( Vector3 v, float c ) => new(v.x / c, v.y / c, v.z / c); [MethodImpl( INLINE )] public float Dot( Vector3 a, Vector3 b ) => a.x * b.x + a.y * b.y + a.z * b.z; [MethodImpl( INLINE )] public float Mag( Vector3 v ) => MathF.Sqrt( Dot( v, v ) ); + [MethodImpl( INLINE )] public float Dist( Vector3 a, Vector3 b ) => Mag( Sub( b, a ) ); [MethodImpl( INLINE )] public Vector3 Normalize( Vector3 v ) => Div( v, Mag( v ) ); // shared implementations @@ -138,6 +141,7 @@ public struct VectorMath4D : IVectorMath { [MethodImpl( INLINE )] public Vector4 Div( Vector4 v, float c ) => new(v.x / c, v.y / c, v.z / c, v.w / c); [MethodImpl( INLINE )] public float Dot( Vector4 a, Vector4 b ) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; [MethodImpl( INLINE )] public float Mag( Vector4 v ) => MathF.Sqrt( Dot( v, v ) ); + [MethodImpl( INLINE )] public float Dist( Vector4 a, Vector4 b ) => Mag( Sub( b, a ) ); [MethodImpl( INLINE )] public Vector4 Normalize( Vector4 v ) => Div( v, Mag( v ) ); // shared implementations @@ -147,4 +151,24 @@ public struct VectorMath4D : IVectorMath { } } + // todo: quaternions, as a treat. this is untested and unported basically lol + public struct VectorMathQuat : IVectorMath { + const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public Quaternion Add( Quaternion a, Quaternion b ) => new(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); + [MethodImpl( INLINE )] public Quaternion Sub( Quaternion a, Quaternion b ) => new(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); + [MethodImpl( INLINE )] public Quaternion Mul( Quaternion v, float c ) => new(v.x * c, v.y * c, v.z * c, v.w * c); + [MethodImpl( INLINE )] public Quaternion Mul( float c, Quaternion v ) => new(v.x * c, v.y * c, v.z * c, v.w * c); + [MethodImpl( INLINE )] public Quaternion Div( Quaternion v, float c ) => new(v.x / c, v.y / c, v.z / c, v.w / c); + [MethodImpl( INLINE )] public float Dot( Quaternion a, Quaternion b ) => a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + [MethodImpl( INLINE )] public float Mag( Quaternion v ) => MathF.Sqrt( Dot( v, v ) ); + [MethodImpl( INLINE )] public float Dist( Quaternion a, Quaternion b ) => Mathfs.Angle( a, b ); + [MethodImpl( INLINE )] public Quaternion Normalize( Quaternion v ) => Div( v, Mag( v ) ); + + // shared implementations + [MethodImpl( INLINE )] public Quaternion Lerp( Quaternion a, Quaternion b, float t ) { + float omt = 1f - t; + return new Quaternion( omt * a.x + t * b.x, omt * a.y + t * b.y, omt * a.z + t * b.z, omt * a.w + t * b.w ); + } + } + } \ No newline at end of file From 595663a77489360ca8be1fe7069950d73fc20f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 31 Aug 2023 22:19:46 +0200 Subject: [PATCH 251/301] added clamped/restricted integer attributes --- Editor/Property Drawers.meta | 8 ++ .../Property Drawers/ClampedIntegerDrawers.cs | 81 +++++++++++++++++++ .../ClampedIntegerDrawers.cs.meta | 11 +++ Editor/Property Drawers/RationalDrawer.cs | 57 +++++++++++++ .../Property Drawers/RationalDrawer.cs.meta | 11 +++ Runtime/Numerics/Rational.cs | 5 +- Runtime/Property Drawers.meta | 8 ++ .../ClampedIntegerAttributes.cs | 20 +++++ .../ClampedIntegerAttributes.cs.meta | 11 +++ 9 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 Editor/Property Drawers.meta create mode 100644 Editor/Property Drawers/ClampedIntegerDrawers.cs create mode 100644 Editor/Property Drawers/ClampedIntegerDrawers.cs.meta create mode 100644 Editor/Property Drawers/RationalDrawer.cs create mode 100644 Editor/Property Drawers/RationalDrawer.cs.meta create mode 100644 Runtime/Property Drawers.meta create mode 100644 Runtime/Property Drawers/ClampedIntegerAttributes.cs create mode 100644 Runtime/Property Drawers/ClampedIntegerAttributes.cs.meta diff --git a/Editor/Property Drawers.meta b/Editor/Property Drawers.meta new file mode 100644 index 0000000..9e08f0b --- /dev/null +++ b/Editor/Property Drawers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0c50cca08ed628843a5c7077b998eef6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Property Drawers/ClampedIntegerDrawers.cs b/Editor/Property Drawers/ClampedIntegerDrawers.cs new file mode 100644 index 0000000..7f42d16 --- /dev/null +++ b/Editor/Property Drawers/ClampedIntegerDrawers.cs @@ -0,0 +1,81 @@ +namespace Freya { + + using UnityEngine; + using UnityEditor; + + public class ClampedIntegerDrawer : PropertyDrawer { + protected bool hasInitialized; // this flag is used to ensure it's valid when the inspector reveals it + } + + [CustomPropertyDrawer( typeof(PositiveIntegerAttribute) )] public class PositiveIntegerDrawer : ClampedIntegerDrawer { + public override void OnGUI( Rect rect, SerializedProperty property, GUIContent label ) => IntegerDrawerUtils.OnGUI( ref hasInitialized, rect, property, label, false, false, true ); + } + + [CustomPropertyDrawer( typeof(NegativeIntegerAttribute) )] public class NegativeIntegerDrawer : ClampedIntegerDrawer { + public override void OnGUI( Rect rect, SerializedProperty property, GUIContent label ) => IntegerDrawerUtils.OnGUI( ref hasInitialized, rect, property, label, true, false, false ); + } + + [CustomPropertyDrawer( typeof(NonNegativeIntegerAttribute) )] public class NonNegativeIntegerDrawer : ClampedIntegerDrawer { + public override void OnGUI( Rect rect, SerializedProperty property, GUIContent label ) => IntegerDrawerUtils.OnGUI( ref hasInitialized, rect, property, label, false, true, true ); + } + + [CustomPropertyDrawer( typeof(NonPositiveIntegerAttribute) )] public class NonPositiveIntegerDrawer : ClampedIntegerDrawer { + public override void OnGUI( Rect rect, SerializedProperty property, GUIContent label ) => IntegerDrawerUtils.OnGUI( ref hasInitialized, rect, property, label, true, true, false ); + } + + [CustomPropertyDrawer( typeof(NonZeroIntegerAttribute) )] public class NonZeroIntegerDrawer : ClampedIntegerDrawer { + public override void OnGUI( Rect rect, SerializedProperty property, GUIContent label ) => IntegerDrawerUtils.OnGUI( ref hasInitialized, rect, property, label, true, false, true ); + } + + static class IntegerDrawerUtils { + + public static void OnGUI( ref bool hasInitialized, Rect rect, SerializedProperty property, GUIContent label, bool allowNegative, bool allowZero, bool allowPositive ) { + EditorGUI.BeginProperty( rect, label, property ); + + if( property.propertyType == SerializedPropertyType.Integer ) { + // PositiveIntegerAttribute range = attribute as PositiveIntegerAttribute; + using( EditorGUI.ChangeCheckScope chChk = new() ) { + int prevValue = property.intValue; + + EditorGUI.PropertyField( rect, property, label ); + if( chChk.changed || hasInitialized == false ) { + hasInitialized = true; + int newValue = property.intValue; + // special case, make it "skip" 0 when scrubbing + if( allowNegative && allowZero == false && allowPositive && newValue == 0 ) { + property.intValue = prevValue > 0 ? -1 : 1; + } else { + // other cases can clamp + int min = GetRangeMin( allowNegative, allowZero, allowPositive ); + int max = GetRangeMax( allowNegative, allowZero, allowPositive ); + if( newValue < min || newValue > max ) + property.intValue = Mathf.Clamp( newValue, min, max ); + } + } + } + } else { + EditorGUI.LabelField( rect, label.text, $"PositiveInteger only works on integer fields. Field is: {property.propertyType.ToString()}" ); + } + + EditorGUI.EndProperty(); + } + + static int GetRangeMin( bool allowNegative, bool allowZero, bool allowPositive ) { + if( allowNegative ) + return int.MinValue; + if( allowZero ) + return 0; + return 1; + } + + static int GetRangeMax( bool allowNegative, bool allowZero, bool allowPositive ) { + if( allowPositive ) + return int.MaxValue; + if( allowZero ) + return 0; + return -1; + } + + } + +} \ No newline at end of file diff --git a/Editor/Property Drawers/ClampedIntegerDrawers.cs.meta b/Editor/Property Drawers/ClampedIntegerDrawers.cs.meta new file mode 100644 index 0000000..bfa43bf --- /dev/null +++ b/Editor/Property Drawers/ClampedIntegerDrawers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b35a7699a7b00a441a973209a563f913 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Property Drawers/RationalDrawer.cs b/Editor/Property Drawers/RationalDrawer.cs new file mode 100644 index 0000000..609aa83 --- /dev/null +++ b/Editor/Property Drawers/RationalDrawer.cs @@ -0,0 +1,57 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Freya { + + [CustomPropertyDrawer( typeof(Rational) )] + public class IngredientDrawer : PropertyDrawer { + + bool hasInitialized; + + // Draw the property inside the given rect + public override void OnGUI( Rect rect, SerializedProperty property, GUIContent label ) { + // Using BeginProperty / EndProperty on the parent property means that + // prefab override logic works on the entire property. + EditorGUI.BeginProperty( rect, label, property ); + + // Draw label + rect = EditorGUI.PrefixLabel( rect, GUIUtility.GetControlID( FocusType.Passive ), label ); + + // Don't make child fields be indented + int indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + // Calculate rects + int slashWidth = 10; + int inputBoxWidth = Mathfs.FloorToInt( ( rect.width - slashWidth ) / 2 ); + Rect rectNumerator = new Rect( rect.x, rect.y, inputBoxWidth, rect.height ); + Rect rectDivision = new Rect( rect.x + inputBoxWidth, rect.y, slashWidth, rect.height ); + Rect rectDenominator = new Rect( rect.x + inputBoxWidth + slashWidth, rect.y, inputBoxWidth, rect.height ); + + SerializedProperty numerator = property.FindPropertyRelative( "n" ); + SerializedProperty denominator = property.FindPropertyRelative( "d" ); + + using( var chChk = new EditorGUI.ChangeCheckScope() ) { + EditorGUI.DelayedIntField( rectNumerator, numerator, GUIContent.none ); + GUI.Label( rectDivision, "/" ); + EditorGUI.DelayedIntField( rectDenominator, denominator, GUIContent.none ); + if( chChk.changed || hasInitialized == false ) { + hasInitialized = true; + // validation, make sure we're not dividing by 0 + if( denominator.intValue == 0 ) { + denominator.intValue = 1; + Debug.LogWarning( "Rational numbers cannot divide by 0. Setting denominator to 1", property.serializedObject.targetObject ); + } + } + } + + // Set indent back to what it was + EditorGUI.indentLevel = indent; + + EditorGUI.EndProperty(); + } + } + +} \ No newline at end of file diff --git a/Editor/Property Drawers/RationalDrawer.cs.meta b/Editor/Property Drawers/RationalDrawer.cs.meta new file mode 100644 index 0000000..2dc1257 --- /dev/null +++ b/Editor/Property Drawers/RationalDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6c3402c7b9d5aa4b8518af9657a070c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Rational.cs b/Runtime/Numerics/Rational.cs index f370ea3..7dc7f09 100644 --- a/Runtime/Numerics/Rational.cs +++ b/Runtime/Numerics/Rational.cs @@ -1,6 +1,7 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; +using UnityEngine; namespace Freya { @@ -13,10 +14,10 @@ namespace Freya { public static readonly Rational MinValue = new(int.MinValue, 1); /// The numerator of this number - public readonly int n; + [SerializeField] public int n; /// The denominator of this number - public readonly int d; + [SerializeField] [NonZeroInteger] public int d; /// Creates an exact representation of a rational number /// The numerator of this number diff --git a/Runtime/Property Drawers.meta b/Runtime/Property Drawers.meta new file mode 100644 index 0000000..9c4e903 --- /dev/null +++ b/Runtime/Property Drawers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 15ddb36d8f130b147b402770205352f6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Property Drawers/ClampedIntegerAttributes.cs b/Runtime/Property Drawers/ClampedIntegerAttributes.cs new file mode 100644 index 0000000..0c987ba --- /dev/null +++ b/Runtime/Property Drawers/ClampedIntegerAttributes.cs @@ -0,0 +1,20 @@ +namespace Freya { + + using UnityEngine; + + /// Restricts this integer to a range of 1 and above + public class PositiveIntegerAttribute : PropertyAttribute {} + + /// Restricts this integer to a range of -1 and below + public class NegativeIntegerAttribute : PropertyAttribute {} + + /// Restricts this integer to a range of 0 and above + public class NonNegativeIntegerAttribute : PropertyAttribute {} + + /// Restricts this integer to a range of 0 and below + public class NonPositiveIntegerAttribute : PropertyAttribute {} + + /// Restricts this integer to be non-zero + public class NonZeroIntegerAttribute : PropertyAttribute {} + +} \ No newline at end of file diff --git a/Runtime/Property Drawers/ClampedIntegerAttributes.cs.meta b/Runtime/Property Drawers/ClampedIntegerAttributes.cs.meta new file mode 100644 index 0000000..6768c19 --- /dev/null +++ b/Runtime/Property Drawers/ClampedIntegerAttributes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 846a20ede34f7a448b88c3220ddd45d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 114f721ebf31c048ef4d689bca19bd7a252299a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 25 Sep 2023 21:30:31 +0200 Subject: [PATCH 252/301] added quaternion axis swap functions --- Runtime/Extensions.cs | 32 +++++++++++++++++++++++++++++++- Runtime/Mathfs.cs | 2 +- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 4565a06..1b3444c 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -199,6 +199,36 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// Rotates 180° around its local Z axis [MethodImpl( INLINE )] public static Quaternion Rotate180AroundSelfZ( this Quaternion q ) => new(q.y, -q.x, q.w, -q.z); + /// Rotates this quaternion 180° so that its Y and Z axes are swapped + [MethodImpl( INLINE )] public static Quaternion SwapYZ( this Quaternion q ) { + return new Quaternion( + Mathfs.RSQRT2 * ( q.y - q.z ), + Mathfs.RSQRT2 * ( q.w - q.x ), + Mathfs.RSQRT2 * ( q.w + q.x ), + Mathfs.RSQRT2 * ( -q.y - q.z ) + ); + } + + /// Rotates this quaternion 180° so that its Z and X axes are swapped + [MethodImpl( INLINE )] public static Quaternion SwapZX( this Quaternion q ) { + return new Quaternion( + Mathfs.RSQRT2 * ( q.w + q.y ), + Mathfs.RSQRT2 * ( q.z - q.x ), + Mathfs.RSQRT2 * ( q.w - q.y ), + Mathfs.RSQRT2 * ( -q.x - q.z ) + ); + } + + /// Rotates this quaternion 180° so that its X and Y axes are swapped + [MethodImpl( INLINE )] public static Quaternion SwapXY( this Quaternion q ) { + return new Quaternion( + Mathfs.RSQRT2 * ( q.w - q.z ), + Mathfs.RSQRT2 * ( q.w + q.z ), + Mathfs.RSQRT2 * ( q.x - q.y ), + Mathfs.RSQRT2 * ( -q.x - q.y ) + ); + } + /// Returns an 180° rotated version of this quaternion around the given axis /// The quaternion to rotate /// The axis to rotate around @@ -426,7 +456,7 @@ public static Quaternion PowUnit( this Quaternion q, float x ) { _ => q.LnUnit().Mul( x ).ExpPureIm() }; } - + /// Returns the imaginary part of a quaternion as a vector public static Vector3 Imag( this Quaternion q ) => new Vector3( q.x, q.y, q.z ); diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index d7bc615..084025b 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -36,7 +36,7 @@ public static class Mathfs { /// The square root of two. The length of the vector (1,1) public const float SQRT2 = 1.41421356237f; - /// The reciprocal of the square root of two. The components of the vector (1,1) + /// The reciprocal of the square root of two. The components of a normalized (1,1) vector public const float RSQRT2 = 1f / SQRT2; /// Multiply an angle in degrees by this, to convert it to radians From 94039180fba4844cf6fff4a4aa53207fe574518c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 26 Sep 2023 17:09:36 +0200 Subject: [PATCH 253/301] IVectorMath.Zero --- Runtime/Numerics/IVectorMath.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Runtime/Numerics/IVectorMath.cs b/Runtime/Numerics/IVectorMath.cs index c1514e5..7bdb990 100644 --- a/Runtime/Numerics/IVectorMath.cs +++ b/Runtime/Numerics/IVectorMath.cs @@ -66,6 +66,7 @@ public static bool TryIntersectSphereAtOrigin( this VM vm, V o, V n, floa public interface IVectorMath { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] V Zero(); [MethodImpl( INLINE )] V Add( V a, V b ); [MethodImpl( INLINE )] V Sub( V a, V b ); [MethodImpl( INLINE )] V Mul( V v, float c ); @@ -80,6 +81,7 @@ public interface IVectorMath { public struct VectorMath1D : IVectorMath { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public float Zero() => 0; [MethodImpl( INLINE )] public float Add( float a, float b ) => a + b; [MethodImpl( INLINE )] public float Sub( float a, float b ) => a - b; [MethodImpl( INLINE )] public float Mul( float v, float c ) => v * c; @@ -95,6 +97,7 @@ public struct VectorMath1D : IVectorMath { public struct VectorMath2D : IVectorMath { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public Vector2 Zero() => new(0, 0); [MethodImpl( INLINE )] public Vector2 Add( Vector2 a, Vector2 b ) => new(a.x + b.x, a.y + b.y); [MethodImpl( INLINE )] public Vector2 Sub( Vector2 a, Vector2 b ) => new(a.x - b.x, a.y - b.y); [MethodImpl( INLINE )] public Vector2 Mul( Vector2 v, float c ) => new(v.x * c, v.y * c); @@ -115,6 +118,7 @@ public struct VectorMath2D : IVectorMath { public struct VectorMath3D : IVectorMath { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public Vector3 Zero() => new(0, 0, 0); [MethodImpl( INLINE )] public Vector3 Add( Vector3 a, Vector3 b ) => new(a.x + b.x, a.y + b.y, a.z + b.z); [MethodImpl( INLINE )] public Vector3 Sub( Vector3 a, Vector3 b ) => new(a.x - b.x, a.y - b.y, a.z - b.z); [MethodImpl( INLINE )] public Vector3 Mul( Vector3 v, float c ) => new(v.x * c, v.y * c, v.z * c); @@ -134,6 +138,7 @@ public struct VectorMath3D : IVectorMath { public struct VectorMath4D : IVectorMath { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public Vector4 Zero() => new(0, 0, 0, 0); [MethodImpl( INLINE )] public Vector4 Add( Vector4 a, Vector4 b ) => new(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); [MethodImpl( INLINE )] public Vector4 Sub( Vector4 a, Vector4 b ) => new(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); [MethodImpl( INLINE )] public Vector4 Mul( Vector4 v, float c ) => new(v.x * c, v.y * c, v.z * c, v.w * c); @@ -154,6 +159,7 @@ public struct VectorMath4D : IVectorMath { // todo: quaternions, as a treat. this is untested and unported basically lol public struct VectorMathQuat : IVectorMath { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; + [MethodImpl( INLINE )] public Quaternion Zero() => new(0, 0, 0, 0); [MethodImpl( INLINE )] public Quaternion Add( Quaternion a, Quaternion b ) => new(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); [MethodImpl( INLINE )] public Quaternion Sub( Quaternion a, Quaternion b ) => new(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); [MethodImpl( INLINE )] public Quaternion Mul( Quaternion v, float c ) => new(v.x * c, v.y * c, v.z * c, v.w * c); From da7bc4da875598c828a8650f9d8636f7be22c564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 7 Oct 2023 20:41:15 +0200 Subject: [PATCH 254/301] IntRange.WithoutLast --- Runtime/Numerics/IntRange.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Runtime/Numerics/IntRange.cs b/Runtime/Numerics/IntRange.cs index 1476d70..2f24c51 100644 --- a/Runtime/Numerics/IntRange.cs +++ b/Runtime/Numerics/IntRange.cs @@ -32,6 +32,9 @@ public IntRange( int start, int count ) { /// The value to check if it's inside, or equal to the start or end public bool Contains( int value ) => value >= start && value <= Last; + /// Returns a copy of this range, without the last element (ie: count is reduced by 1) + public IntRange WithoutLast() => new(start, count - 1); + /// Create an integer range from start to end (inclusive) /// The first integer /// The last integer From 8097fe8695efa2f30dd213f367388de120748920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sat, 7 Oct 2023 20:41:27 +0200 Subject: [PATCH 255/301] RMF method --- Runtime/Mathfs.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 084025b..2e92073 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1103,6 +1103,25 @@ public static Vector3 ClampMagnitude( Vector3 v, float min, float max ) { /// [MethodImpl( INLINE )] public static float DistanceSquared( Vector4 a, Vector4 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square() + ( a.z - b.z ).Square() + ( a.w - b.w ).Square(); + /// Calculates a rotation minimizing normal direction, given start and end conditions. This is usually used when evaluating rotation minimizing frames on curves. + /// The start position + /// The start tangent direction + /// The start normal direction + /// The end position + /// The end tangent direction + public static Vector3 GetRotationMinimizingNormal( Vector3 posA, Vector3 tangentA, Vector3 normalA, Vector3 posB, Vector3 tangentB ) { + // source: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf + Vector3 v1 = posB - posA; + float v1_dot_v1_half = Vector3.Dot( v1, v1 ) / 2; + float r1 = Vector3.Dot( v1, normalA ) / v1_dot_v1_half; + float r2 = Vector3.Dot( v1, tangentA ) / v1_dot_v1_half; + Vector3 nL = normalA - r1 * v1; + Vector3 tL = tangentA - r2 * v1; + Vector3 v2 = tangentB - tL; + float r3 = Vector3.Dot( v2, nL ) / Vector3.Dot( v2, v2 ); + return ( nL - 2 * r3 * v2 ).normalized; + } + #endregion #region Angles & Rotation From 1c013d4b58fae2cac77cdcd79e95da8a81386413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Sun, 5 Nov 2023 19:31:23 +0100 Subject: [PATCH 256/301] Transform/Inverse transform ray extensions --- Runtime/Extensions.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 1b3444c..77f6603 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -512,6 +512,16 @@ public static Quaternion InversePureIm( this Quaternion q ) { /// The world space rotation public static Quaternion InverseTransformRotation( this Transform tf, Quaternion quat ) => Quaternion.Inverse( tf.rotation ) * quat; + /// Transforms a ray from world space to local space + /// The transform to use + /// The world space ray + public static Ray InverseTransformRay( this Transform tf, Ray ray ) => new(tf.InverseTransformPoint( ray.origin ), tf.InverseTransformDirection( ray.direction )); + + /// Transforms a ray from local space to world space + /// The transform to use + /// The local space ray + public static Ray TransformRay( this Transform tf, Ray ray ) => new(tf.TransformPoint( ray.origin ), tf.TransformDirection( ray.direction )); + #endregion #region Color manipulation From 243c545869be438b84340584e62093a60d725dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 13 Nov 2023 10:46:27 +0100 Subject: [PATCH 257/301] Wrap() now allows last value --- Runtime/Numerics/FloatRange.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 2aec5a5..0c981ef 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -77,7 +77,7 @@ public bool Overlaps( FloatRange other ) { /// Wraps/repeats the input value to stay within this range /// The value to wrap/repeat in this interval public float Wrap( float value ) { - if( value >= a && value < b ) + if( value >= a && value <= b ) return value; return a + Mathfs.Repeat( value - a, b - a ); } From b3b021a1195649b542bbfc6b68aedccf4603e9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 13 Nov 2023 22:52:34 +0100 Subject: [PATCH 258/301] serializable polynomial3D & IntRange --- Runtime/Curves/Polynomial3D.cs | 1 + Runtime/Numerics/IntRange.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index fb25537..fc8b93b 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -6,6 +6,7 @@ namespace Freya { + [Serializable] public struct Polynomial3D : IPolynomialCubic, IParamCurve3Diff { const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; diff --git a/Runtime/Numerics/IntRange.cs b/Runtime/Numerics/IntRange.cs index 2f24c51..12a4629 100644 --- a/Runtime/Numerics/IntRange.cs +++ b/Runtime/Numerics/IntRange.cs @@ -1,16 +1,17 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using System.Text; namespace Freya { /// An integer range - public readonly struct IntRange { + [Serializable] public struct IntRange { - public static readonly IntRange empty = new IntRange( 0, 0 ); + public static IntRange empty = new IntRange( 0, 0 ); - public readonly int start; - public readonly int count; + public int start; + public int count; public int this[ int i ] => start + i; @@ -40,7 +41,7 @@ public IntRange( int start, int count ) { /// The last integer public static IntRange FirstToLast( int first, int last ) => new IntRange( first, last - first + 1 ); - static readonly StringBuilder toStrBuilder = new StringBuilder(); + static StringBuilder toStrBuilder = new StringBuilder(); public override string ToString() { toStrBuilder.Clear(); @@ -58,7 +59,7 @@ public override string ToString() { public IntRangeEnumerator GetEnumerator() => new IntRangeEnumerator( this ); public struct IntRangeEnumerator /*: IEnumerator*/ { - readonly IntRange intRange; + IntRange intRange; int currValue; public IntRangeEnumerator( IntRange range ) => ( this.intRange, currValue ) = ( range, range.start - 1 ); public bool MoveNext() => ++currValue <= intRange.Last; From 2fed160d66bddb11f63893d8cc3c217b49443a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 13 Nov 2023 22:52:50 +0100 Subject: [PATCH 259/301] matrix*ray multiplication --- Runtime/Extensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 77f6603..402c4a9 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -689,6 +689,11 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => public static Vector3Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Vector3Matrix4x1 v ) => new(m.MultiplyColumnVector( v.X ), m.MultiplyColumnVector( v.Y ), m.MultiplyColumnVector( v.Z )); public static Vector4Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Vector4Matrix4x1 v ) => new(m.MultiplyColumnVector( v.X ), m.MultiplyColumnVector( v.Y ), m.MultiplyColumnVector( v.Z ), m.MultiplyColumnVector( v.W )); + /// Transforms a ray by this matrix + /// The matrix to use + /// The ray to transform + public static Ray MultiplyRay( this Matrix4x4 mtx, Ray ray ) => new(mtx.MultiplyPoint3x4( ray.origin ), mtx.MultiplyVector( ray.direction )); + #endregion #region Extension method counterparts of the static Mathfs functions - lots of boilerplate in here From 838ccb3350d672d9d85ed555a2ec0988343372f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 13 Nov 2023 22:52:56 +0100 Subject: [PATCH 260/301] Rational utilities --- Runtime/Numerics/Rational.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Runtime/Numerics/Rational.cs b/Runtime/Numerics/Rational.cs index 7dc7f09..9b3aac4 100644 --- a/Runtime/Numerics/Rational.cs +++ b/Runtime/Numerics/Rational.cs @@ -80,6 +80,22 @@ public Rational Pow( int pow ) => public static Rational Lerp( Rational a, Rational b, Rational t ) => a + t * ( b - a ); public static Rational InverseLerp( Rational a, Rational b, Rational v ) => ( v - a ) / ( b - a ); + public static Rational Floor( Rational r ) { + if( r.n < 0 ) + return ( r.n - r.d + 1 ) / r.d; + return r.n / r.d; + } + + public static Rational Ceil( Rational r ) { + if( r.n > 0 ) + return ( r.n + r.d - 1 ) / r.d; + return r.n / r.d; + } + + public static Rational Round( Rational r ) { + return r.n < 0 == r.d < 0 ? ( r.n + r.d / 2 ) / r.d : ( r.n - r.d / 2 ) / r.d; + } + // type casting public static implicit operator Rational( int n ) => new(n, 1); public static explicit operator int( Rational r ) => r.IsInteger ? r.n : throw new ArithmeticException( $"Rational value {r} can't be cast to an integer" ); From f49c82f4c59978ceaf8616ef9fb855387701d9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 13 Nov 2023 22:53:22 +0100 Subject: [PATCH 261/301] double variants of clamp01 and inverselerpclamped --- Runtime/Mathfs.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 2e92073..e56c278 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -388,7 +388,10 @@ public static Vector4 Clamp( Vector4 v, Vector4 min, Vector4 max ) => public static int Clamp( int value, int min, int max ) => value < min ? min : value > max ? max : value; /// Returns the value clamped between 0 and 1 - public static float Clamp01( float value ) => value < 0f ? 0f : value > 1f ? 1f : value; + public static float Clamp01( float value ) => value < 0 ? 0 : value > 1 ? 1 : value; + + /// + public static double Clamp01( double value ) => value < 0 ? 0 : value > 1 ? 1 : value; /// Clamps each component between 0 and 1 public static Vector2 Clamp01( Vector2 v ) => @@ -797,6 +800,9 @@ public static Rect Lerp( Rect a, Rect b, float t ) { /// A value between a and b [MethodImpl( INLINE )] public static float InverseLerpClamped( float a, float b, float value ) => Clamp01( ( value - a ) / ( b - a ) ); + /// + [MethodImpl( INLINE )] public static double InverseLerpClamped( double a, double b, double value ) => Clamp01( ( value - a ) / ( b - a ) ); + /// Given a value between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1, with cubic smoothing applied. /// Equivalent to "smoothstep" in shader code /// The start of the range, where it would return 0 From 4ac4fc65dc1f0db08db52db482357e576e54aab3 Mon Sep 17 00:00:00 2001 From: Colt Bauman Date: Sun, 26 Nov 2023 08:45:45 +0900 Subject: [PATCH 262/301] fix: bivector * trivector should return a vector --- Runtime/Geometric Algebra/Trivector3.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Geometric Algebra/Trivector3.cs b/Runtime/Geometric Algebra/Trivector3.cs index 09ce893..c731fee 100644 --- a/Runtime/Geometric Algebra/Trivector3.cs +++ b/Runtime/Geometric Algebra/Trivector3.cs @@ -12,8 +12,8 @@ public struct Trivector3 { public static Bivector3 operator *( Trivector3 a, Vector3 b ) => new Bivector3( a.xyz * b.x, a.xyz * b.y, a.xyz * b.z ); public static Bivector3 operator *( Vector3 a, Trivector3 b ) => new Bivector3( a.x * b.xyz, a.y * b.xyz, a.z * b.xyz ); - public static Bivector3 operator *( Bivector3 a, Trivector3 b ) => new Bivector3( -a.yz * b.xyz, -a.zx * b.xyz, -a.xy * b.xyz ); - public static Bivector3 operator *( Trivector3 a, Bivector3 b ) => new Bivector3( -a.xyz * b.yz, -a.xyz * b.zx, -a.xyz * b.xy ); + public static Vector3 operator *( Bivector3 a, Trivector3 b ) => new Vector3( -a.yz * b.xyz, -a.zx * b.xyz, -a.xy * b.xyz ); + public static Vector3 operator *( Trivector3 a, Bivector3 b ) => new Vector3( -a.xyz * b.yz, -a.xyz * b.zx, -a.xyz * b.xy ); public static float operator *( Trivector3 a, Trivector3 b ) => -a.xyz * b.xyz; From fe9385749d4922ab6461d764b075511f773211da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 26 Jan 2024 00:52:54 +0100 Subject: [PATCH 263/301] added polygon centroid and weighted edge center --- Runtime/Geometric Shapes/Polygon.cs | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Runtime/Geometric Shapes/Polygon.cs b/Runtime/Geometric Shapes/Polygon.cs index 43839c4..1b37823 100644 --- a/Runtime/Geometric Shapes/Polygon.cs +++ b/Runtime/Geometric Shapes/Polygon.cs @@ -138,6 +138,39 @@ Line2D GetMiterLine( int i ) { return new Polygon( miterPts ); } + // from: https://en.wikipedia.org/wiki/Centroid + /// The centroid of this polygon, also known as the center of mass + public Vector2 Centroid { + get { + Vector2 centroid = Vector2.zero; + float signedArea = 0; + for( int i = 0; i < Count; i++ ) { + Vector2 a = points[i]; + Vector2 b = points[( i + 1 ) % Count]; + float det = a.x * b.y - b.x * a.y; + signedArea += det; + centroid.x += ( a.x + b.x ) * det; + centroid.y += ( b.y + a.y ) * det; + } + return centroid / ( 3 * signedArea ); + } + } + + public Vector2 WeightedEdgeCenter { + get { + Vector2 eCenter = Vector2.zero; + float totalLength = 0; + for( int i = 0; i < Count; i++ ) { + Vector2 a = points[i]; + Vector2 b = points[( i + 1 ) % Count]; + float length = Vector2.Distance( a, b ); + totalLength += length; + eCenter += ( a + b ) * length; + } + return eCenter / ( 2 * totalLength ); + } + } + } } \ No newline at end of file From a36401508440b846739954d056ad0f6e7f34ad8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 1 May 2024 15:07:01 +0200 Subject: [PATCH 264/301] projection, angle & PointsInCircle stuff --- Runtime/Extensions.cs | 20 ++++++++++++++++++++ Runtime/Mathfs.cs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 402c4a9..fd080c5 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -177,6 +177,26 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// [MethodImpl( INLINE )] public static Vector3 ScaleAround( this Vector3 p, Vector3 pivot, Vector3 scale ) => new(pivot.x + ( p.x - pivot.x ) * scale.x, pivot.y + ( p.y - pivot.y ) * scale.y, pivot.z + ( p.z - pivot.z ) * scale.z); + /// Projects the vector perpendicularly onto the other vector B + /// The vector to project with + /// The vector to project perpendicularly against. The resulting vector is along this vector + public static Vector3 Project( this Vector3 a, Vector3 b ) { + float denom = Vector3.Dot( b, b ); + if( Mathfs.Approximately( denom, 0 ) ) + throw new DivideByZeroException( "Can't project to a vector with 0 length" ); + return b * ( Vector3.Dot( a, b ) / denom ); + } + + /// Projects the vector perpendicularly *from* the initial vector, onto the other vector B + /// The vector to project perpendicularly from + /// The vector to project against. The resulting vector is along this vector + public static Vector3 ProjectPerpFrom( this Vector3 a, Vector3 b ) { + float denom = Vector3.Dot( a, b ); + if( Mathfs.Approximately( denom, 0 ) ) + throw new DivideByZeroException( "Can't project to a vector with 0 length" ); + return b * ( Vector3.Dot( a, a ) / denom ); + } + #endregion #region Quaternions diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index e56c278..7ab82b3 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -5,6 +5,7 @@ // Collected and expanded upon to by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; +using System.Collections.Generic; using UnityEngine; using Uei = UnityEngine.Internal; using System.Linq; // used for arbitrary count min/max functions, so it's safe and won't allocate garbage don't worry~ @@ -1109,6 +1110,14 @@ public static Vector3 ClampMagnitude( Vector3 v, float min, float max ) { /// [MethodImpl( INLINE )] public static float DistanceSquared( Vector4 a, Vector4 b ) => ( a.x - b.x ).Square() + ( a.y - b.y ).Square() + ( a.z - b.z ).Square() + ( a.w - b.w ).Square(); + /// The t-value (fraction) where a projected along b would be + /// The vector to project + /// The vector to project onto + public static float ProjectionTValue( Vector3 a, Vector3 b ) => Vector3.Dot( a, b ) / Vector3.Dot( b, b ); + + /// + public static float ProjectionTValue( Vector2 a, Vector2 b ) => Vector2.Dot( a, b ) / Vector2.Dot( b, b ); + /// Calculates a rotation minimizing normal direction, given start and end conditions. This is usually used when evaluating rotation minimizing frames on curves. /// The start position /// The start tangent direction @@ -1306,9 +1315,15 @@ public static Pose Lerp( Pose a, Pose b, float t ) => /// Returns the shortest angle between a and b, in the range 0 to tau/2 (0 to pi) [MethodImpl( INLINE )] public static float AngleBetween( Vector2 a, Vector2 b ) => MathF.Acos( Vector2.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); + /// Returns the shortest angle between two normalized vectors a and b, in the range 0 to tau/2 (0 to pi) + [MethodImpl( INLINE )] public static float AngleBetweenPreNormalized( Vector2 a, Vector2 b ) => MathF.Acos( Vector2.Dot( a, b ).ClampNeg1to1() ); + /// [MethodImpl( INLINE )] public static float AngleBetween( Vector3 a, Vector3 b ) => MathF.Acos( Vector3.Dot( a.normalized, b.normalized ).ClampNeg1to1() ); + /// + [MethodImpl( INLINE )] public static float AngleBetweenPreNormalized( Vector3 a, Vector3 b ) => MathF.Acos( Vector3.Dot( a, b ).ClampNeg1to1() ); + /// Returns the clockwise angle between from and to, in the range 0 to tau (0 to 2*pi) [MethodImpl( INLINE )] public static float AngleFromToCW( Vector2 from, Vector2 to ) => Determinant( from, to ) < 0 ? AngleBetween( from, to ) : TAU - AngleBetween( from, to ); @@ -1341,6 +1356,25 @@ public static float InverseLerpAngle( float a, float b, float v ) { return InverseLerpClamped( a, b, v ); } + /// An enumerable sequence of count number of vectors + /// on a circle with the given radius, starting from the X axis + /// The number of vectors to arrange on the circle. + /// A negative count will enumerate in the negative direction + /// The radius of the circle + public static IEnumerable PointsInCircle( int count, float radius = 1 ) { + if( count == 0 ) + yield break; + yield return new Vector2( radius, 0 ); + int absCount = Math.Abs( count ); + for( int i = 1; i < absCount; i++ ) { + float angle = ( TAU * i ) / count; + yield return new Vector2( + MathF.Cos( angle ) * radius, + MathF.Sin( angle ) * radius + ); + } + } + #endregion #region Angular movement helpers From 1a6e6bc7548cdd7612daae60077611daa62fdcf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 1 May 2024 15:07:33 +0200 Subject: [PATCH 265/301] matrix 3x3 to 4x4 conversion --- Runtime/Numerics/Matrix3x3.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Runtime/Numerics/Matrix3x3.cs b/Runtime/Numerics/Matrix3x3.cs index cc8226f..c9dabe8 100644 --- a/Runtime/Numerics/Matrix3x3.cs +++ b/Runtime/Numerics/Matrix3x3.cs @@ -127,6 +127,20 @@ public float Determinant { public static explicit operator Matrix3x3( Matrix4x4 m ) => new(m.m00, m.m01, m.m02, m.m10, m.m11, m.m12, m.m20, m.m21, m.m22); + public static explicit operator Matrix4x4( Matrix3x3 m ) => + new() { + m00 = m.m00, + m01 = m.m01, + m02 = m.m02, + m10 = m.m10, + m11 = m.m11, + m12 = m.m12, + m20 = m.m20, + m21 = m.m21, + m22 = m.m22, + m33 = 1 // to match identity matrix + }; + public static Matrix3x3 operator *( Matrix3x3 c, float v ) => new(c.m00 * v, c.m01 * v, c.m02 * v, c.m10 * v, c.m11 * v, c.m12 * v, From fb11fee8693937b00e2992c2482d5e7e184b1d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 1 May 2024 17:54:48 +0200 Subject: [PATCH 266/301] Dual quaternion experiments --- Runtime/Numerics/DualQuaternion.cs | 42 +++++++++++++++++++++++++ Runtime/Numerics/DualQuaternion.cs.meta | 11 +++++++ 2 files changed, 53 insertions(+) create mode 100644 Runtime/Numerics/DualQuaternion.cs create mode 100644 Runtime/Numerics/DualQuaternion.cs.meta diff --git a/Runtime/Numerics/DualQuaternion.cs b/Runtime/Numerics/DualQuaternion.cs new file mode 100644 index 0000000..4c8916c --- /dev/null +++ b/Runtime/Numerics/DualQuaternion.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Freya { + + [Serializable] + public struct DualQuaternion { + + float r; + float i, j, k; + float e; + float ei, ej, ek; + + public DualQuaternion( float r, float i, float j, float k, float e, float ei, float ej, float ek ) { + this.r = r; + this.i = i; + this.j = j; + this.k = k; + this.e = e; + this.ei = ei; + this.ej = ej; + this.ek = ek; + } + + public static DualQuaternion operator *( DualQuaternion a, DualQuaternion b ) { + return new DualQuaternion( + r: a.r * b.r - a.i * b.i - a.j * b.j - a.k * b.k, + i: a.r * b.i + a.i * b.r + a.j * b.k - a.k * b.j, + j: a.r * b.j - a.i * b.k + a.j * b.r + a.k * b.i, + k: a.r * b.k + a.i * b.j - a.j * b.i + a.k * b.r, + e: a.r * b.e - a.i * b.ei - a.j * b.ej - a.k * b.ek + a.e * b.r - a.ei * b.i - a.ej * b.j - a.ek * b.k, + ei: a.r * b.ei + a.i * b.e + a.j * b.ek - a.k * b.ej + a.e * b.i + a.ei * b.r + a.ej * b.k - a.ek * b.j, + ej: a.r * b.ej - a.i * b.ek + a.j * b.e + a.k * b.ei + a.e * b.j - a.ei * b.k + a.ej * b.r + a.ek * b.i, + ek: a.r * b.ek + a.i * b.ej - a.j * b.ei + a.k * b.e + a.e + b.k + a.ei * b.j - a.ej * b.i + a.ek * b.r + ); + } + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/DualQuaternion.cs.meta b/Runtime/Numerics/DualQuaternion.cs.meta new file mode 100644 index 0000000..334be28 --- /dev/null +++ b/Runtime/Numerics/DualQuaternion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc3197f5010eddc4fa722b4c2a152812 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From f846d1c7708032a45490b3d77b39064f3bf7d866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 1 May 2024 17:54:57 +0200 Subject: [PATCH 267/301] elevator style easing --- .../Multi-Segment Splines/ElevatorEase.cs | 113 ++++++++++++++++++ .../ElevatorEase.cs.meta | 11 ++ 2 files changed, 124 insertions(+) create mode 100644 Runtime/Splines/Multi-Segment Splines/ElevatorEase.cs create mode 100644 Runtime/Splines/Multi-Segment Splines/ElevatorEase.cs.meta diff --git a/Runtime/Splines/Multi-Segment Splines/ElevatorEase.cs b/Runtime/Splines/Multi-Segment Splines/ElevatorEase.cs new file mode 100644 index 0000000..85ac965 --- /dev/null +++ b/Runtime/Splines/Multi-Segment Splines/ElevatorEase.cs @@ -0,0 +1,113 @@ +using System; +using Freya; + +public class ElevatorEase { + + public enum Mode { + FastStartPolynomial, + Trigonometric + } + + public readonly Mode mode; + public readonly float maxDistance; + public readonly float contractSpeed; + public readonly float accDuration; + + public readonly float t1; + public readonly float t2; + public readonly float tEnd; + public readonly float v; + + // linear section + float g_c0, g_c1; + + // polynomial coefficients used by Mode.FastStartPolynomial + float f_c3, f_c4, f_c5; // ease in + float h_c4, h_c5; // ease out (relative) + + // constants used by Mode.Trigonometric + float c_xsq, c_trigScale, c_trigInner; + + public ElevatorEase( Mode mode, float maxDistance, float contractSpeed, float accDuration ) { + this.mode = mode; + this.maxDistance = maxDistance; + this.contractSpeed = contractSpeed; + this.accDuration = accDuration; + + // derived things + t1 = accDuration; + t2 = maxDistance / contractSpeed; + tEnd = t1 + t2; + v = contractSpeed; + float tHalf = tEnd / 2; + if( accDuration >= tHalf ) { + // no linear section - adjust accordingly + t1 = t2 = tHalf; + v = 2 * ( maxDistance / tEnd ); + } + + // calculate coefficients + g_c1 = v; + if( mode == Mode.FastStartPolynomial ) { + g_c0 = -2 * v * t1 / 5; + float t1_2 = t1 * t1; + float t1_3 = t1_2 * t1; + float t1_4 = t1_2 * t1_2; + f_c3 = 2 * v / t1_2; + f_c4 = 2 * v / -t1_3; + f_c5 = 3 * v / ( 5 * t1_4 ); + h_c4 = v / -t1_3; + h_c5 = -3 * v / ( 5 * t1_4 ); + } else if( mode == Mode.Trigonometric ) { + g_c0 = -v * t1 / 2; + c_xsq = v / ( 2 * t1 ); + c_trigScale = v * t1 / ( Mathfs.TAU * Mathfs.TAU ); + c_trigInner = Mathfs.TAU / t1; + } + } + + float EaseIn( float t ) { + float x2 = t * t; + + if( mode == Mode.FastStartPolynomial ) { + float x3 = x2 * t; + float x4 = x2 * x2; + float x5 = x3 * x2; + return f_c3 * x3 + f_c4 * x4 + f_c5 * x5; + } else if( mode == Mode.Trigonometric ) { + return c_xsq * x2 + c_trigScale * ( MathF.Cos( c_trigInner * t ) - 1 ); + } + throw new NotImplementedException(); + } + + float Linear( float t ) => g_c0 + g_c1 * t; + + float EaseOut( float t ) { + if( mode == Mode.FastStartPolynomial ) { + // this one is asymmetric, and needs special handling + float x = t - tEnd; // remap to relative + float x2 = x * x; + float x4 = x2 * x2; + float x5 = x4 * x; + return h_c4 * x4 + h_c5 * x5 + maxDistance; + } + + // same as ease-in but reversed + return maxDistance - EaseIn( tEnd - t ); + } + + public float Eval( float t ) { + // check extremes: + if( t <= 0 ) + return 0; + if( t >= tEnd ) + return maxDistance; + // check functions: + if( t < t1 ) + return EaseIn( t ); + if( t > t2 ) + return EaseOut( t ); + return Linear( t ); + } + +} \ No newline at end of file diff --git a/Runtime/Splines/Multi-Segment Splines/ElevatorEase.cs.meta b/Runtime/Splines/Multi-Segment Splines/ElevatorEase.cs.meta new file mode 100644 index 0000000..94acd15 --- /dev/null +++ b/Runtime/Splines/Multi-Segment Splines/ElevatorEase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba96d62d0c3156b4fb6c1e76c2c5a3b2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 0095f839589ce5c9b12ef3c051abde7ee3d9885d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 15 Oct 2024 16:14:51 +0200 Subject: [PATCH 268/301] double multiply/divide operators for Rational --- Runtime/Numerics/Rational.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Runtime/Numerics/Rational.cs b/Runtime/Numerics/Rational.cs index 9b3aac4..6129837 100644 --- a/Runtime/Numerics/Rational.cs +++ b/Runtime/Numerics/Rational.cs @@ -122,6 +122,8 @@ public static Rational Round( Rational r ) { public static Rational operator *( int a, Rational b ) => checked( new(b.n * a, b.d) ); public static float operator *( Rational a, float b ) => ( a.n * b ) / a.d; public static float operator *( float a, Rational b ) => ( b.n * a ) / b.d; + public static double operator *( Rational a, double b ) => ( a.n * b ) / a.d; + public static double operator *( double a, Rational b ) => ( b.n * a ) / b.d; // division public static Rational operator /( Rational a, Rational b ) => checked( new(a.n * b.d, a.d * b.n) ); @@ -129,6 +131,8 @@ public static Rational Round( Rational r ) { public static Rational operator /( int a, Rational b ) => checked( new(a * b.d, b.n) ); public static float operator /( Rational a, float b ) => a.n / ( a.d * b ); public static float operator /( float a, Rational b ) => ( a * b.d ) / b.n; + public static double operator /( Rational a, double b ) => a.n / ( a.d * b ); + public static double operator /( double a, Rational b ) => ( a * b.d ) / b.n; // comparison operators public static bool operator ==( Rational a, Rational b ) => a.CompareTo( b ) == 0; From 4ea2b72b7ef7c7fdb39d016ec194dd62eafe39f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 1 Apr 2025 23:08:37 +0200 Subject: [PATCH 269/301] matrix3x3 and points in circle util --- Runtime/Mathfs.cs | 21 +++++++++++++++++---- Runtime/Numerics/Matrix3x3.cs | 6 ++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 7ab82b3..ef1f1bf 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1361,13 +1361,12 @@ public static float InverseLerpAngle( float a, float b, float v ) { /// The number of vectors to arrange on the circle. /// A negative count will enumerate in the negative direction /// The radius of the circle - public static IEnumerable PointsInCircle( int count, float radius = 1 ) { + public static IEnumerable PointsInCircle( int count, float radius = 1, float startAngle = 0f ) { if( count == 0 ) yield break; - yield return new Vector2( radius, 0 ); int absCount = Math.Abs( count ); - for( int i = 1; i < absCount; i++ ) { - float angle = ( TAU * i ) / count; + for( int i = 0; i < absCount; i++ ) { + float angle = ( TAU * i ) / count + startAngle; yield return new Vector2( MathF.Cos( angle ) * radius, MathF.Sin( angle ) * radius @@ -1375,6 +1374,20 @@ public static IEnumerable PointsInCircle( int count, float radius = 1 ) } } + /// + public static IEnumerable<(Vector2 p, int i)> PointsInCircleIdx( int count, float radius = 1, int startOffset = 0 ) { + if( count == 0 ) + yield break; + int absCount = Math.Abs( count ); + for( int i = 0; i < absCount; i++ ) { + float angle = ( TAU * ( i + startOffset ) ) / count; + yield return ( new Vector2( + MathF.Cos( angle ) * radius, + MathF.Sin( angle ) * radius + ), i ); + } + } + #endregion #region Angular movement helpers diff --git a/Runtime/Numerics/Matrix3x3.cs b/Runtime/Numerics/Matrix3x3.cs index c9dabe8..ce9a6cd 100644 --- a/Runtime/Numerics/Matrix3x3.cs +++ b/Runtime/Numerics/Matrix3x3.cs @@ -125,6 +125,12 @@ public float Determinant { }; } + public Matrix4x4 ToMatrix4x4( Vector3 pos, float m33 = 1 ) { + Matrix4x4 m4 = (Matrix4x4)this; + m4.SetColumn( 3, new Vector4( pos.x, pos.y, pos.z, m33 ) ); + return m4; + } + public static explicit operator Matrix3x3( Matrix4x4 m ) => new(m.m00, m.m01, m.m02, m.m10, m.m11, m.m12, m.m20, m.m21, m.m22); public static explicit operator Matrix4x4( Matrix3x3 m ) => From ba8612859ea5ff513285b336652b6ae399ce88cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 2 Apr 2025 00:25:15 +0200 Subject: [PATCH 270/301] Cofactor/Bivector math --- Runtime/Extensions.cs | 10 ++++++++++ Runtime/Numerics/Matrix3x3.cs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index fd080c5..d740c95 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -527,6 +527,16 @@ public static Quaternion InversePureIm( this Quaternion q ) { /// The local space rotation public static Quaternion TransformRotation( this Transform tf, Quaternion quat ) => tf.rotation * quat; + /// Transforms a bivector from local space to world space + /// The transform to use + /// The local space bivector + public static Bivector3 TransformBivector( this Transform tf, Bivector3 b ) => new Matrix3x3( tf.localToWorldMatrix ).CofactorMatrix * b; + + /// Transforms a bivector from local space to world space + /// The transform to use + /// The local space bivector + public static float TransformBivector( this Transform tf, float b ) => new Matrix3x3( tf.localToWorldMatrix ).Determinant * b; + /// Transforms a rotation from world space to local space /// The transform to use /// The world space rotation diff --git a/Runtime/Numerics/Matrix3x3.cs b/Runtime/Numerics/Matrix3x3.cs index c9dabe8..4c273e6 100644 --- a/Runtime/Numerics/Matrix3x3.cs +++ b/Runtime/Numerics/Matrix3x3.cs @@ -101,6 +101,32 @@ public Matrix3x3 Inverse { } } + public float Minor( int r, int c ) { + // forming a 2x2 by deleting row r and column c + int r0 = r == 0 ? 1 : 0; + int r1 = r == 2 ? 1 : 2; + int c0 = c == 0 ? 1 : 0; + int c1 = c == 2 ? 1 : 2; + float mn00 = this[r0, c0]; + float mn01 = this[r0, c1]; + float mn10 = this[r1, c0]; + float mn11 = this[r1, c1]; + return mn00 * mn11 - mn01 * mn10; // 2x2 determinant + } + + public float Cofactor( int r, int c ) { + // https://en.wikipedia.org/wiki/Minor_(linear_algebra) + int sign = ( r + c ) % 2 == 0 ? 1 : -1; // (-1)^(r+c) + return sign * Minor( r, c ); + } + + public Matrix3x3 CofactorMatrix => + new( + Cofactor( 0, 0 ), Cofactor( 0, 1 ), Cofactor( 0, 2 ), + Cofactor( 1, 0 ), Cofactor( 1, 1 ), Cofactor( 1, 2 ), + Cofactor( 2, 0 ), Cofactor( 2, 1 ), Cofactor( 2, 2 ) + ); + /// Returns the determinant of this matrix public float Determinant { get { @@ -171,6 +197,11 @@ float GetEntry( int r, int c ) => v.x * c.m10 + v.y * c.m11 + v.z * c.m12, v.x * c.m20 + v.y * c.m21 + v.z * c.m22); + public static Bivector3 operator *( Matrix3x3 c, Bivector3 v ) => + new(v.yz * c.m00 + v.zx * c.m01 + v.xy * c.m02, + v.yz * c.m10 + v.zx * c.m11 + v.xy * c.m12, + v.yz * c.m20 + v.zx * c.m21 + v.xy * c.m22); + public static Vector2Matrix3x1 operator *( Matrix3x3 c, Vector2Matrix3x1 m ) => new(c * m.X, c * m.Y); public static Vector3Matrix3x1 operator *( Matrix3x3 c, Vector3Matrix3x1 m ) => new(c * m.X, c * m.Y, c * m.Z); From d690f0ced7441a35243c66c4256ad84faf02ebb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Mon, 2 Mar 2026 23:31:42 +0100 Subject: [PATCH 271/301] exp2 remap stuff --- Runtime/Mathfs.cs | 25 +++++++++++++++++++++++-- Runtime/Numerics/FloatRange.cs | 6 ++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index ef1f1bf..ac2fe58 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -71,6 +71,9 @@ public static class Mathfs { /// Returns e to the power of the given value [MethodImpl( INLINE )] public static float Exp( float power ) => MathF.Exp( power ); + /// Returns 2 to the power of the given value + [MethodImpl( INLINE )] public static float Exp2( float power ) => MathF.Exp( power * 0.69314718056f ); + /// Returns the logarithm of a value, with the given base [MethodImpl( INLINE )] public static float Log( float value, float @base ) => MathF.Log( value, @base ); @@ -879,14 +882,32 @@ public static Rect Lerp( Rect a, Rect b, float t ) { t switch { 0f => a, 1f => b, - _ => MathF.Pow( a, 1 - t ) * MathF.Pow( b, t ) + _ => a * MathF.Exp( MathF.Log( b / a ) * t ) // same as exp( lerp(ln a, ln b, t) ), but without numeric issues! + }; + + /// + [MethodImpl( INLINE )] public static Vector3 Eerp( Vector3 a, Vector3 b, float t ) => + t switch { + 0f => a, + 1f => b, + _ => new Vector3( + Eerp( a.x, b.x, t ), + Eerp( a.y, b.y, t ), + Eerp( a.z, b.z, t ) + ) }; /// Inverse exponential interpolation, the multiplicative version of InverseLerp, useful for values such as scaling or zooming /// The start value /// The end value /// A value between a and b. Note: values outside this range are still valid, and will be extrapolated - [MethodImpl( INLINE )] public static float InverseEerp( float a, float b, float v ) => MathF.Log( a / v ) / MathF.Log( a / b ); + [MethodImpl( INLINE )] public static float InverseEerp( float a, float b, float v ) { + if( v == a ) + return 0f; + if( v == b ) + return 1f; + return MathF.Log( v / a ) / MathF.Log( b / a ); + } #endregion diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 0c981ef..812e984 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -60,6 +60,12 @@ public struct FloatRange { /// The output range public static float Remap( float value, FloatRange input, FloatRange output ) => output.Lerp( input.InverseLerp( value ) ); + /// Remaps the input value from the input range to the output range, and contains it within + /// The value to remap + /// The input range + /// The output range + public static float RemapClamped( float value, FloatRange input, FloatRange output ) => output.Lerp( input.InverseLerp( value ).Clamp01() ); + /// Remaps a range from the input range to the output range /// The range to remap /// The input range From 9fb2c99f1a18640aeb18131496ba953b92d51126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:26:13 +0200 Subject: [PATCH 272/301] the great unity.mathematics refactor begins --- Runtime/Mathfs.asmdef | 5 ++++- package.json | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Runtime/Mathfs.asmdef b/Runtime/Mathfs.asmdef index 68ca406..bafca79 100644 --- a/Runtime/Mathfs.asmdef +++ b/Runtime/Mathfs.asmdef @@ -1,6 +1,9 @@ { "name": "MathfsAsmdef", - "references": [], + "rootNamespace": "", + "references": [ + "GUID:d8b63aba1907145bea998dd612889d6b" + ], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, diff --git a/package.json b/package.json index 8da567c..5e5a1dc 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "com.acegikmo.mathfs", - "version": "0.1.0", + "version": "1.0.0", "displayName": "Mathfs", "description": "Advanced math functionality for Unity", - "unity": "2021.2", + "unity": "6000.0", "documentationUrl": "https://github.com/FreyaHolmer/Mathfs", "licensesUrl": "https://github.com/FreyaHolmer/Mathfs/LICENSE.txt", "author": { From 5708fa0f86ba1a6ae60ec17c7308ca3467f1ee6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:29:26 +0200 Subject: [PATCH 273/301] Shared interfaces for number types --- Runtime/Numerics/INumber.cs | 32 ++++++++++++++++++++++++++++++++ Runtime/Numerics/INumber.cs.meta | 3 +++ 2 files changed, 35 insertions(+) create mode 100644 Runtime/Numerics/INumber.cs create mode 100644 Runtime/Numerics/INumber.cs.meta diff --git a/Runtime/Numerics/INumber.cs b/Runtime/Numerics/INumber.cs new file mode 100644 index 0000000..5f82061 --- /dev/null +++ b/Runtime/Numerics/INumber.cs @@ -0,0 +1,32 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using Unity.Mathematics; +using UnityEngine; + +namespace Freya { + + public interface INumber { + public bool isInteger { get; } + } + + public interface ISignedNumber : INumber { + public R sign { get; } + } + + public interface INumber : INumber { + /// Returns the absolute value of this number + public N abs { get; } + public N max( N other ); + public N min( N other ); + + // I can't do this bc Unity uses older versions of C#: + // public static abstract R zero { get; } + // public static abstract R one { get; } + } + + public interface IHalfNumber { + /// Multiplies this by 2 and returns an integer value + public F times2 { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/INumber.cs.meta b/Runtime/Numerics/INumber.cs.meta new file mode 100644 index 0000000..a458364 --- /dev/null +++ b/Runtime/Numerics/INumber.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a60a1c63dc754d8b98dbec145cf5ed44 +timeCreated: 1774975260 \ No newline at end of file From 4fb9f804f944170b70f0a5c5ce80002d8d0c6653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:29:43 +0200 Subject: [PATCH 274/301] Interfaces for roundable types --- Runtime/Numerics/IRoundable.cs | 23 +++++++++++++++++++++++ Runtime/Numerics/IRoundable.cs.meta | 3 +++ 2 files changed, 26 insertions(+) create mode 100644 Runtime/Numerics/IRoundable.cs create mode 100644 Runtime/Numerics/IRoundable.cs.meta diff --git a/Runtime/Numerics/IRoundable.cs b/Runtime/Numerics/IRoundable.cs new file mode 100644 index 0000000..5121559 --- /dev/null +++ b/Runtime/Numerics/IRoundable.cs @@ -0,0 +1,23 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +namespace Freya { + + /// Basically the same as C#'s , but older .net versions don't have all the options + public enum RoundingDirection { + ToEven = 0, + AwayFromZero = 1, + ToZero = 2, + ToNegativeInfinity = 3, + ToPositiveInfinity = 4, + } + + /// Objects that can be rounded to nearby values + public interface IRoundable { + public R round( RoundingDirection rounding = RoundingDirection.ToEven ); + public R floorToward0 { get; } + public R ceilAwayFrom0 { get; } + public R floor { get; } + public R ceil { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IRoundable.cs.meta b/Runtime/Numerics/IRoundable.cs.meta new file mode 100644 index 0000000..8960a9c --- /dev/null +++ b/Runtime/Numerics/IRoundable.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 74aac72b52ce4bf68533409de752b6f8 +timeCreated: 1774975320 \ No newline at end of file From 2ce7f84d57c977c0d515322b563a235682aedbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:30:28 +0200 Subject: [PATCH 275/301] Interfaces for vector types --- Runtime/Numerics/IVec.cs | 111 ++++++++++++++++++++++++++++++++++ Runtime/Numerics/IVec.cs.meta | 3 + 2 files changed, 114 insertions(+) create mode 100644 Runtime/Numerics/IVec.cs create mode 100644 Runtime/Numerics/IVec.cs.meta diff --git a/Runtime/Numerics/IVec.cs b/Runtime/Numerics/IVec.cs new file mode 100644 index 0000000..8cdfdb4 --- /dev/null +++ b/Runtime/Numerics/IVec.cs @@ -0,0 +1,111 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using Unity.Mathematics; + +namespace Freya { + + public interface IVec : IDotProduct, IWedgeProduct { + /// Returns a component of this vector by index + public C this[ int i ] { get; } + /// Returns whether this lies flat along at least one axis + public bool isOrthogonal { get; } + /// Returns whether this vector is the zero vector + public bool isZero { get; } + + /// The vector from this point to the target. Equivalent to target - this + public V to( V target ); + + /// The squared magnitude of this vector + public D magSq { get; } + + /// The chebyshev magnitude of this vector. + /// In chebyshev distance, diagonal distances are treated the same as orthogonal distances. + /// This means the magnitude of (1,1) is 1, the magnitude of (2,2) is 2 + public C magChebyshev { get; } + /// The taxicab magnitude of this vector. + /// In taxicab distance, diagonal distances are treated as if you can only measure orthogonally. + /// This means the magnitude of (1,1) is 2, the magnitude of (2,2) is 4 + public C magTaxicab { get; } + + /// The minimum of the components of this vector + public C cmin { get; } + /// The maximum of the components of this vector + public C cmax { get; } + /// The sum of the components of this vector + public C csum { get; } + + /// Returns whether this point is in front of or behind a plane. + ///
    + ///
  • returns +1 when in front of the plane
  • + ///
  • returns 0 when inside the plane
  • + ///
  • returns -1 when behind the plane
  • + ///
+ ///
+ /// A point inside the plane + /// The normal direction of the plane + public int pointSideOfPlane( V planePos, V planeNormal ); + } + + /// Objects that implement a dot product + public interface IDotProduct { + /// The dot product between two vectors. This is the sum of the product of each respective component + public D dot( V other ); + } + + /// Objects that implement the wedge product + public interface IWedgeProduct { + /// The wedge product between two vectors. This is a generalized form of the cross product. + ///
  • In 2D, this returns a scalar, and is sometimes called the perpendicular dot product.
  • + ///
  • In 3D, this returns a vector, and is effectively the same as the cross product (technically it's a bivector but whatever)
  • + ///
+ public W wedge( V other ); + } + + /// Objects that reside within four quadrants in 2D + public interface IQuadrant2D { + /// The index of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, + /// increasing in the positive rotation direction/counter-clockwise. + /// Ambiguous positions pick the quadrant in the positive rotation direction. + public int quadrant { get; } + /// The signed of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, + /// increasing in the positive rotation direction/counter-clockwise. + /// Ambiguous positions pick the quadrant in the positive rotation direction. + public int signedQuadrant { get; } + /// The X-axis of the basis within the current quadrant. + /// Ambiguous positions pick the quadrant in the positive rotation direction. Zero-vectors return (1,0) + public int2 quadrantBasisX { get; } + /// Returns the two basis vectors of the quadrant that contains this position. + /// Ambiguous positions pick the quadrant in the positive rotation direction + public (int2 x, int2 y) quadrantBasis { get; } + } + + /// Objects that can be treated like complex numbers + public interface IComplex { + /// Multiplies as if they were complex numbers. The resulting vector is "rotated" by the other, and scaled by its magnitude. + /// Note that this operation does not use any trigonometry or square roots, it's very cheap to use! + public M complexMul( V other ); + + /// The complex conjugate of this vector, if treated as a complex number. Which, in english, just means it negates the y component + public V complexConj { get; } + } + + public interface IVec2 : IVec, IQuadrant2D, IComplex { + /// The X component of this vector + public C X { get; } + /// The Y component of this vector + public C Y { get; } + + + /// Rotates this vector in the positive rotation direction by 90 degrees. This is usually a counter-clockwise/left turn" + public V rot90 { get; } + /// Rotates this vector in the positive rotation direction by 90 degrees. This is usually a clockwise/right turn" + public V rotNeg90 { get; } + /// Rotates this vector by 180 degrees. Equivalent to negating this vector + public V rot180 { get; } + + + // public V rot45chebyshev { get; } + // public V FromVector2( Vector2 v ); // should only happen for coarse things like inthalf2 and int. rational ones are messy here + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IVec.cs.meta b/Runtime/Numerics/IVec.cs.meta new file mode 100644 index 0000000..d4de972 --- /dev/null +++ b/Runtime/Numerics/IVec.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 98afe4bf8b2b4756a3b7b17448d5f670 +timeCreated: 1775585084 \ No newline at end of file From 2fb04736ea462b430d7c6b0cd3822e99ab3ada26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:34:19 +0200 Subject: [PATCH 276/301] Rational is now called rat, with some updates --- Editor/MathfsCodegen.cs | 18 +- Editor/Property Drawers/RationalDrawer.cs | 2 +- Runtime/Numerics/Probability.cs | 10 +- Runtime/Numerics/Rational.cs | 153 --------------- Runtime/Numerics/RationalMatrix3x3.cs | 34 ++-- Runtime/Numerics/RationalMatrix4x4.cs | 72 +++---- Runtime/Numerics/rat.cs | 176 ++++++++++++++++++ .../{Rational.cs.meta => rat.cs.meta} | 0 8 files changed, 244 insertions(+), 221 deletions(-) delete mode 100644 Runtime/Numerics/Rational.cs create mode 100644 Runtime/Numerics/rat.cs rename Runtime/Numerics/{Rational.cs.meta => rat.cs.meta} (100%) diff --git a/Editor/MathfsCodegen.cs b/Editor/MathfsCodegen.cs index e72f638..2d03ef5 100644 --- a/Editor/MathfsCodegen.cs +++ b/Editor/MathfsCodegen.cs @@ -493,10 +493,10 @@ static void GenerateUniformSplineType( SplineType type, int dim ) { class MathSum { - Rational globalScale = Rational.One; - List<(Rational coeff, string var)> terms = new List<(Rational coeff, string var)>(); + rat globalScale = rat.one; + List<(rat coeff, string var)> terms = new List<(rat coeff, string var)>(); - public void AddTerm( Rational coeff, string var ) { + public void AddTerm( rat coeff, string var ) { if( coeff != 0 ) terms.Add( ( coeff, var ) ); } @@ -505,8 +505,8 @@ void TryOptimize() { if( terms.Count < 2 ) return; // can't optimize 0 or 1 terms - Rational coeff0 = terms[0].coeff.Abs(); - if( terms.TrueForAll( t => t.coeff.Abs() == coeff0 ) ) { + rat coeff0 = terms[0].coeff.abs; + if( terms.TrueForAll( t => t.coeff.abs == coeff0 ) ) { globalScale = coeff0; for( int i = 0; i < terms.Count; i++ ) terms[i] = ( terms[i].coeff / coeff0, terms[i].var ); @@ -534,16 +534,16 @@ public override string ToString() { return line; } - string FormatRational( Rational v ) => v.IsInteger ? $"{v.n}" : $"({v}f)"; + string FormatRational( rat v ) => v.isInteger ? $"{v.n}" : $"({v}f)"; string FormatTerm( int i ) { - Rational value = terms[i].coeff; + rat value = terms[i].coeff; string sign = i > 0 && value >= 0 ? "+" : ""; string valueStr; string op = ""; - if( value == Rational.One ) + if( value == rat.one ) valueStr = ""; - else if( value == -Rational.One ) + else if( value == -rat.one ) valueStr = "-"; else if( value > 0 ) { valueStr = FormatRational( value ); diff --git a/Editor/Property Drawers/RationalDrawer.cs b/Editor/Property Drawers/RationalDrawer.cs index 609aa83..49dfa9d 100644 --- a/Editor/Property Drawers/RationalDrawer.cs +++ b/Editor/Property Drawers/RationalDrawer.cs @@ -5,7 +5,7 @@ namespace Freya { - [CustomPropertyDrawer( typeof(Rational) )] + [CustomPropertyDrawer( typeof(rat) )] public class IngredientDrawer : PropertyDrawer { bool hasInitialized; diff --git a/Runtime/Numerics/Probability.cs b/Runtime/Numerics/Probability.cs index dcae1c0..d8b4a7d 100644 --- a/Runtime/Numerics/Probability.cs +++ b/Runtime/Numerics/Probability.cs @@ -7,20 +7,20 @@ namespace Freya { /// A struct representing a probability (as a rational number) [Serializable] public struct Probability : IComparable { - public static readonly Rational Zero = new(0, 1); - public static readonly Rational One = new(1, 1); + public static readonly rat Zero = new(0, 1); + public static readonly rat One = new(1, 1); /// The value of this probability - public Rational value; + public rat value; /// Creates a representation of probability using a rational number /// /// The probability value - public Probability( Rational value ) => this.value = value; + public Probability( rat value ) => this.value = value; /// Creates a representation of probability using a rational number /// The numerator of this probability /// The denominator of this probability - public Probability( int num, int den ) : this( new Rational( num, den ) ) { + public Probability( int num, int den ) : this( new rat( num, den ) ) { } /// Randomly samples this probability, returning either true or false diff --git a/Runtime/Numerics/Rational.cs b/Runtime/Numerics/Rational.cs deleted file mode 100644 index 6129837..0000000 --- a/Runtime/Numerics/Rational.cs +++ /dev/null @@ -1,153 +0,0 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) - -using System; -using UnityEngine; - -namespace Freya { - - /// A struct representing exact rational numbers - [Serializable] public struct Rational : IComparable { - - public static readonly Rational Zero = new(0, 1); - public static readonly Rational One = new(1, 1); - public static readonly Rational MaxValue = new(int.MaxValue, 1); - public static readonly Rational MinValue = new(int.MinValue, 1); - - /// The numerator of this number - [SerializeField] public int n; - - /// The denominator of this number - [SerializeField] [NonZeroInteger] public int d; - - /// Creates an exact representation of a rational number - /// The numerator of this number - /// The denominator of this number - public Rational( int num, int den ) { - switch( den ) { - case -1: - ( n, d ) = ( -num, -den ); - break; - case 0: throw new DivideByZeroException( "The denominator can't be 0" ); - case 1: - ( n, d ) = ( num, den ); - break; - default: - if( num == 0 ) { - ( n, d ) = ( 0, 1 ); - break; - } - - // ensure only the numerator carries the sign - int sign = Mathfs.Sign( den ); - n = sign * num; - d = sign * den; - - if( n is -1 or 1 ) - break; // no reduction needed - - // in this case, we have to try simplifying the expression - int gcd = Mathfs.Gcd( num, den ); - n /= gcd; - d /= gcd; - break; - } - } - - /// Returns the reciprocal of this number - public Rational Reciprocal => new(d, n); - - public bool IsInteger => d == 1; - - /// Returns the absolute value of this number - public Rational Abs() => new(n.Abs(), d); - - /// Returns this number to the power of another integer pow - /// The power to raise this number by - public Rational Pow( int pow ) => - pow switch { - <= -2 => Reciprocal.Pow( -pow ), - -1 => Reciprocal, - 0 => 1, - 1 => this, - >= 2 => new Rational( n.Pow( pow ), d.Pow( pow ) ) - }; - - public override string ToString() => d == 1 ? n.ToString() : $"{n}/{d}"; - - // statics - public static Rational Min( Rational a, Rational b ) => a < b ? a : b; - public static Rational Max( Rational a, Rational b ) => a > b ? a : b; - public static Rational Lerp( Rational a, Rational b, Rational t ) => a + t * ( b - a ); - public static Rational InverseLerp( Rational a, Rational b, Rational v ) => ( v - a ) / ( b - a ); - - public static Rational Floor( Rational r ) { - if( r.n < 0 ) - return ( r.n - r.d + 1 ) / r.d; - return r.n / r.d; - } - - public static Rational Ceil( Rational r ) { - if( r.n > 0 ) - return ( r.n + r.d - 1 ) / r.d; - return r.n / r.d; - } - - public static Rational Round( Rational r ) { - return r.n < 0 == r.d < 0 ? ( r.n + r.d / 2 ) / r.d : ( r.n - r.d / 2 ) / r.d; - } - - // type casting - public static implicit operator Rational( int n ) => new(n, 1); - public static explicit operator int( Rational r ) => r.IsInteger ? r.n : throw new ArithmeticException( $"Rational value {r} can't be cast to an integer" ); - public static explicit operator float( Rational r ) => (float)r.n / r.d; - public static explicit operator double( Rational r ) => (double)r.n / r.d; - - // unary operations - public static Rational operator -( Rational r ) => checked( new(-r.n, r.d) ); - public static Rational operator +( Rational r ) => r; - - // addition - public static Rational operator +( Rational a, Rational b ) => checked( new(a.n * b.d + a.d * b.n, a.d * b.d) ); - public static Rational operator +( Rational a, int b ) => checked( new(a.n + a.d * b, a.d) ); - public static Rational operator +( int a, Rational b ) => checked( new(a * b.d + b.n, b.d) ); - - // subtraction - public static Rational operator -( Rational a, Rational b ) => checked( new(a.n * b.d - a.d * b.n, a.d * b.d) ); - public static Rational operator -( Rational a, int b ) => checked( new(a.n - a.d * b, a.d) ); - public static Rational operator -( int a, Rational b ) => checked( new(a * b.d - b.n, b.d) ); - - // multiplication - public static Rational operator *( Rational a, Rational b ) => checked( new(a.n * b.n, a.d * b.d) ); - public static Rational operator *( Rational a, int b ) => checked( new(a.n * b, a.d) ); - public static Rational operator *( int a, Rational b ) => checked( new(b.n * a, b.d) ); - public static float operator *( Rational a, float b ) => ( a.n * b ) / a.d; - public static float operator *( float a, Rational b ) => ( b.n * a ) / b.d; - public static double operator *( Rational a, double b ) => ( a.n * b ) / a.d; - public static double operator *( double a, Rational b ) => ( b.n * a ) / b.d; - - // division - public static Rational operator /( Rational a, Rational b ) => checked( new(a.n * b.d, a.d * b.n) ); - public static Rational operator /( Rational a, int b ) => checked( new(a.n, a.d * b) ); - public static Rational operator /( int a, Rational b ) => checked( new(a * b.d, b.n) ); - public static float operator /( Rational a, float b ) => a.n / ( a.d * b ); - public static float operator /( float a, Rational b ) => ( a * b.d ) / b.n; - public static double operator /( Rational a, double b ) => a.n / ( a.d * b ); - public static double operator /( double a, Rational b ) => ( a * b.d ) / b.n; - - // comparison operators - public static bool operator ==( Rational a, Rational b ) => a.CompareTo( b ) == 0; - public static bool operator !=( Rational a, Rational b ) => a.CompareTo( b ) != 0; - public static bool operator <( Rational a, Rational b ) => a.CompareTo( b ) < 0; - public static bool operator >( Rational a, Rational b ) => a.CompareTo( b ) > 0; - public static bool operator <=( Rational a, Rational b ) => a.CompareTo( b ) <= 0; - public static bool operator >=( Rational a, Rational b ) => a.CompareTo( b ) >= 0; - - // comparison functions - public int CompareTo( Rational other ) => checked( ( n * other.d ).CompareTo( d * other.n ) ); - public bool Equals( Rational other ) => n == other.n && d == other.d; - public override bool Equals( object obj ) => obj is Rational other && Equals( other ); - public override int GetHashCode() => HashCode.Combine( n, d ); - - } - -} \ No newline at end of file diff --git a/Runtime/Numerics/RationalMatrix3x3.cs b/Runtime/Numerics/RationalMatrix3x3.cs index 3f3be21..9e9c164 100644 --- a/Runtime/Numerics/RationalMatrix3x3.cs +++ b/Runtime/Numerics/RationalMatrix3x3.cs @@ -11,17 +11,17 @@ public readonly struct RationalMatrix3x3 { public static readonly RationalMatrix3x3 Identity = new RationalMatrix3x3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); public static readonly RationalMatrix3x3 Zero = new RationalMatrix3x3( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - public readonly Rational m00, m01, m02; - public readonly Rational m10, m11, m12; - public readonly Rational m20, m21, m22; + public readonly rat m00, m01, m02; + public readonly rat m10, m11, m12; + public readonly rat m20, m21, m22; - public RationalMatrix3x3( Rational m00, Rational m01, Rational m02, Rational m10, Rational m11, Rational m12, Rational m20, Rational m21, Rational m22 ) { + public RationalMatrix3x3( rat m00, rat m01, rat m02, rat m10, rat m11, rat m12, rat m20, rat m21, rat m22 ) { ( this.m00, this.m01, this.m02 ) = ( m00, m01, m02 ); ( this.m10, this.m11, this.m12 ) = ( m10, m11, m12 ); ( this.m20, this.m21, this.m22 ) = ( m20, m21, m22 ); } - public Rational this[ int row, int column ] { + public rat this[ int row, int column ] { get { return ( row, column ) switch { (0, 0) => m00, @@ -41,12 +41,12 @@ public RationalMatrix3x3( Rational m00, Rational m01, Rational m02, Rational m10 /// Returns the inverse of this matrix. Throws a division by zero exception if it's not invertible public RationalMatrix3x3 Inverse { get { - Rational A1212 = m11 * m22 - m12 * m21; - Rational A0212 = m10 * m22 - m12 * m20; - Rational A0112 = m10 * m21 - m11 * m20; - Rational det = m00 * A1212 - m01 * A0212 + m02 * A0112; + rat A1212 = m11 * m22 - m12 * m21; + rat A0212 = m10 * m22 - m12 * m20; + rat A0112 = m10 * m21 - m11 * m20; + rat det = m00 * A1212 - m01 * A0212 + m02 * A0112; - if( det == Rational.Zero ) + if( det == rat.zero ) throw new DivideByZeroException( "The matrix is not invertible - its determinant is 0" ); return new RationalMatrix3x3( @@ -58,11 +58,11 @@ public RationalMatrix3x3 Inverse { } /// Returns the determinant of this matrix - public Rational Determinant { + public rat Determinant { get { - Rational A1212 = m11 * m22 - m12 * m21; - Rational A0212 = m10 * m22 - m12 * m20; - Rational A0112 = m10 * m21 - m11 * m20; + rat A1212 = m11 * m22 - m12 * m21; + rat A0212 = m10 * m22 - m12 * m20; + rat A0112 = m10 * m21 - m11 * m20; return m00 * A1212 - m01 * A0212 + m02 * A0112; } } @@ -77,15 +77,15 @@ public Rational Determinant { }; } - public static RationalMatrix3x3 operator *( RationalMatrix3x3 c, Rational v ) => + public static RationalMatrix3x3 operator *( RationalMatrix3x3 c, rat v ) => new(c.m00 * v, c.m01 * v, c.m02 * v, c.m10 * v, c.m11 * v, c.m12 * v, c.m20 * v, c.m21 * v, c.m22 * v); - public static RationalMatrix3x3 operator /( RationalMatrix3x3 c, Rational v ) => c * v.Reciprocal; + public static RationalMatrix3x3 operator /( RationalMatrix3x3 c, rat v ) => c * v.Reciprocal; public static RationalMatrix3x3 operator *( RationalMatrix3x3 a, RationalMatrix3x3 b ) { - Rational GetEntry( int r, int c ) => a[r, 0] * b[0, c] + a[r, 1] * b[1, c] + a[r, 2] * b[2, c] + a[r, 3] * b[3, c]; + rat GetEntry( int r, int c ) => a[r, 0] * b[0, c] + a[r, 1] * b[1, c] + a[r, 2] * b[2, c] + a[r, 3] * b[3, c]; return new RationalMatrix3x3( GetEntry( 0, 0 ), GetEntry( 0, 1 ), GetEntry( 0, 2 ), diff --git a/Runtime/Numerics/RationalMatrix4x4.cs b/Runtime/Numerics/RationalMatrix4x4.cs index 60ab014..59426e8 100644 --- a/Runtime/Numerics/RationalMatrix4x4.cs +++ b/Runtime/Numerics/RationalMatrix4x4.cs @@ -11,19 +11,19 @@ public readonly struct RationalMatrix4x4 { public static readonly RationalMatrix4x4 Identity = new RationalMatrix4x4( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ); public static readonly RationalMatrix4x4 Zero = new RationalMatrix4x4( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - public readonly Rational m00, m01, m02, m03; - public readonly Rational m10, m11, m12, m13; - public readonly Rational m20, m21, m22, m23; - public readonly Rational m30, m31, m32, m33; + public readonly rat m00, m01, m02, m03; + public readonly rat m10, m11, m12, m13; + public readonly rat m20, m21, m22, m23; + public readonly rat m30, m31, m32, m33; - public RationalMatrix4x4( Rational m00, Rational m01, Rational m02, Rational m03, Rational m10, Rational m11, Rational m12, Rational m13, Rational m20, Rational m21, Rational m22, Rational m23, Rational m30, Rational m31, Rational m32, Rational m33 ) { + public RationalMatrix4x4( rat m00, rat m01, rat m02, rat m03, rat m10, rat m11, rat m12, rat m13, rat m20, rat m21, rat m22, rat m23, rat m30, rat m31, rat m32, rat m33 ) { ( this.m00, this.m01, this.m02, this.m03 ) = ( m00, m01, m02, m03 ); ( this.m10, this.m11, this.m12, this.m13 ) = ( m10, m11, m12, m13 ); ( this.m20, this.m21, this.m22, this.m23 ) = ( m20, m21, m22, m23 ); ( this.m30, this.m31, this.m32, this.m33 ) = ( m30, m31, m32, m33 ); } - public Rational this[ int row, int column ] { + public rat this[ int row, int column ] { get { return ( row, column ) switch { (0, 0) => m00, @@ -51,32 +51,32 @@ public RationalMatrix4x4( Rational m00, Rational m01, Rational m02, Rational m03 public RationalMatrix4x4 Inverse { get { // source: https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix - Rational A2323 = m22 * m33 - m23 * m32; - Rational A1323 = m21 * m33 - m23 * m31; - Rational A1223 = m21 * m32 - m22 * m31; - Rational A0323 = m20 * m33 - m23 * m30; - Rational A0223 = m20 * m32 - m22 * m30; - Rational A0123 = m20 * m31 - m21 * m30; - Rational det = m00 * ( m11 * A2323 - m12 * A1323 + m13 * A1223 ) + rat A2323 = m22 * m33 - m23 * m32; + rat A1323 = m21 * m33 - m23 * m31; + rat A1223 = m21 * m32 - m22 * m31; + rat A0323 = m20 * m33 - m23 * m30; + rat A0223 = m20 * m32 - m22 * m30; + rat A0123 = m20 * m31 - m21 * m30; + rat det = m00 * ( m11 * A2323 - m12 * A1323 + m13 * A1223 ) - m01 * ( m10 * A2323 - m12 * A0323 + m13 * A0223 ) + m02 * ( m10 * A1323 - m11 * A0323 + m13 * A0123 ) - m03 * ( m10 * A1223 - m11 * A0223 + m12 * A0123 ); - if( det == Rational.Zero ) + if( det == rat.zero ) throw new DivideByZeroException( "The matrix is not invertible - its determinant is 0" ); - Rational A2313 = m12 * m33 - m13 * m32; - Rational A1313 = m11 * m33 - m13 * m31; - Rational A1213 = m11 * m32 - m12 * m31; - Rational A2312 = m12 * m23 - m13 * m22; - Rational A1312 = m11 * m23 - m13 * m21; - Rational A1212 = m11 * m22 - m12 * m21; - Rational A0313 = m10 * m33 - m13 * m30; - Rational A0213 = m10 * m32 - m12 * m30; - Rational A0312 = m10 * m23 - m13 * m20; - Rational A0212 = m10 * m22 - m12 * m20; - Rational A0113 = m10 * m31 - m11 * m30; - Rational A0112 = m10 * m21 - m11 * m20; + rat A2313 = m12 * m33 - m13 * m32; + rat A1313 = m11 * m33 - m13 * m31; + rat A1213 = m11 * m32 - m12 * m31; + rat A2312 = m12 * m23 - m13 * m22; + rat A1312 = m11 * m23 - m13 * m21; + rat A1212 = m11 * m22 - m12 * m21; + rat A0313 = m10 * m33 - m13 * m30; + rat A0213 = m10 * m32 - m12 * m30; + rat A0312 = m10 * m23 - m13 * m20; + rat A0212 = m10 * m22 - m12 * m20; + rat A0113 = m10 * m31 - m11 * m30; + rat A0112 = m10 * m21 - m11 * m20; return new RationalMatrix4x4( ( m11 * A2323 - m12 * A1323 + m13 * A1223 ), -( m01 * A2323 - m02 * A1323 + m03 * A1223 ), ( m01 * A2313 - m02 * A1313 + m03 * A1213 ), -( m01 * A2312 - m02 * A1312 + m03 * A1212 ), @@ -88,15 +88,15 @@ public RationalMatrix4x4 Inverse { } /// Returns the determinant of this matrix - public Rational Determinant { + public rat Determinant { get { // source: https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix - Rational A2323 = m22 * m33 - m23 * m32; - Rational A1323 = m21 * m33 - m23 * m31; - Rational A1223 = m21 * m32 - m22 * m31; - Rational A0323 = m20 * m33 - m23 * m30; - Rational A0223 = m20 * m32 - m22 * m30; - Rational A0123 = m20 * m31 - m21 * m30; + rat A2323 = m22 * m33 - m23 * m32; + rat A1323 = m21 * m33 - m23 * m31; + rat A1223 = m21 * m32 - m22 * m31; + rat A0323 = m20 * m33 - m23 * m30; + rat A0223 = m20 * m32 - m22 * m30; + rat A0123 = m20 * m31 - m21 * m30; return m00 * ( m11 * A2323 - m12 * A1323 + m13 * A1223 ) - m01 * ( m10 * A2323 - m12 * A0323 + m13 * A0223 ) + m02 * ( m10 * A1323 - m11 * A0323 + m13 * A0123 ) @@ -124,7 +124,7 @@ public static explicit operator Matrix4x4( RationalMatrix4x4 c ) { ); } - public static RationalMatrix4x4 operator *( RationalMatrix4x4 c, Rational v ) => + public static RationalMatrix4x4 operator *( RationalMatrix4x4 c, rat v ) => new(c.m00 * v, c.m01 * v, c.m02 * v, c.m03 * v, c.m10 * v, c.m11 * v, c.m12 * v, c.m13 * v, c.m20 * v, c.m21 * v, c.m22 * v, c.m23 * v, @@ -137,10 +137,10 @@ public static explicit operator RationalMatrix4x4( RationalMatrix3x3 c ) => 0, 0, 0, 1); - public static RationalMatrix4x4 operator /( RationalMatrix4x4 c, Rational v ) => c * v.Reciprocal; + public static RationalMatrix4x4 operator /( RationalMatrix4x4 c, rat v ) => c * v.Reciprocal; public static RationalMatrix4x4 operator *( RationalMatrix4x4 a, RationalMatrix4x4 b ) { - Rational GetEntry( int r, int c ) => a[r, 0] * b[0, c] + a[r, 1] * b[1, c] + a[r, 2] * b[2, c] + a[r, 3] * b[3, c]; + rat GetEntry( int r, int c ) => a[r, 0] * b[0, c] + a[r, 1] * b[1, c] + a[r, 2] * b[2, c] + a[r, 3] * b[3, c]; return new RationalMatrix4x4( GetEntry( 0, 0 ), GetEntry( 0, 1 ), GetEntry( 0, 2 ), GetEntry( 0, 3 ), diff --git a/Runtime/Numerics/rat.cs b/Runtime/Numerics/rat.cs new file mode 100644 index 0000000..0241efb --- /dev/null +++ b/Runtime/Numerics/rat.cs @@ -0,0 +1,176 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace Freya { + + /// A data type representing a rational number n/d, using a pair of integers. + [Serializable] public struct rat : + IComparable, + IEquatable, + INumber, + ISignedNumber, + IRoundable { + /// The numerator of this number. Note: Directly modifying this value will not reduce the fraction + [SerializeField] public int n; + + /// The denominator of this number. Note: Directly modifying this value will not reduce the fraction + [SerializeField] [NonZeroInteger] public int d; + + public static readonly rat zero = new(0, 1); + public static readonly rat one = new(1, 1); + public static readonly rat half = new(1, 2); + public static readonly rat MaxValue = new(int.MaxValue, 1); + public static readonly rat MinValue = new(int.MinValue, 1); + + + /// Creates an exact representation of a rational number + /// The numerator of this number + /// The denominator of this number + public rat( int num, int den ) { + switch( den ) { + case -1: + ( n, d ) = ( -num, -den ); + break; + case 0: throw new DivideByZeroException( "The denominator can't be 0" ); + case 1: + ( n, d ) = ( num, den ); + break; + default: + if( num == 0 ) { + ( n, d ) = ( 0, 1 ); + break; + } + + // ensure only the numerator carries the sign + int sign = Mathfs.Sign( den ); + n = sign * num; + d = sign * den; + + if( n is -1 or 1 ) + break; // no reduction needed + + // in this case, we have to try simplifying the expression + int gcd = Mathfs.Gcd( num, den ); + n /= gcd; + d /= gcd; + break; + } + } + + public static rat FromFloat( float v, int snapStepsPerUnit = 2 ) => new(Mathf.RoundToInt( v * snapStepsPerUnit ), snapStepsPerUnit); + + /// Returns the reciprocal of this number + public rat Reciprocal => new(d, n); + + public bool isInteger => d == 1; + + + /// Returns this number to the power of another integer pow + /// The power to raise this number by + public rat Pow( int pow ) => + pow switch { + <= -2 => Reciprocal.Pow( -pow ), + -1 => Reciprocal, + 0 => 1, + 1 => this, + >= 2 => new rat( n.Pow( pow ), d.Pow( pow ) ) + }; + + public bool TryCastToIntHalf( out inth ih ) { + switch( d ) { + case 1: + ih = n; + return true; + case 2: + ih = new inth { h = n }; + return true; + default: + ih = default; + return false; + } + } + + public override string ToString() => d == 1 ? n.ToString() : $"{n}/{d}"; + + // statics + public rat abs => new(n.Abs(), d); + public rat max( rat other ) => this > other ? this : other; + public rat min( rat other ) => this < other ? this : other; + public int sign => MathF.Sign( n ); + public int round( RoundingDirection rounding = RoundingDirection.ToEven ) => ( n < 0 == d < 0 ? ( n + d / 2 ) / d : ( n - d / 2 ) / d ); // todo: work out which rounding method this is + public int floorToward0 => n < 0 ? ceil : floor; + public int ceilAwayFrom0 => n < 0 ? floor : ceil; + public int floor => ( n < 0 ? n - d + 1 : n ) / d; + public int ceil => ( n > 0 ? n + d - 1 : n ) / d; + + // type casting + public static implicit operator rat( int n ) => new(n, 1); + public static explicit operator int( rat r ) => r.isInteger ? r.n : throw new ArithmeticException( $"Rational value {r} can't be cast to an integer" ); + + public static explicit operator inth( rat r ) => + r.d switch { + 1 => r.n, + 2 => new inth { h = r.n * 2 }, + _ => throw new ArithmeticException( $"Rational value {r} can't be cast to a half-step integer" ) + }; + + public static explicit operator float( rat r ) => (float)r.n / r.d; + public static explicit operator double( rat r ) => (double)r.n / r.d; + + // unary operations + public static rat operator -( rat r ) => checked( new(-r.n, r.d) ); + public static rat operator +( rat r ) => r; + + // binary operations + public static rat operator +( rat a, rat b ) => checked( new(a.n * b.d + a.d * b.n, a.d * b.d) ); + public static rat operator -( rat a, rat b ) => checked( new(a.n * b.d - a.d * b.n, a.d * b.d) ); + public static rat operator *( rat a, rat b ) => checked( new(a.n * b.n, a.d * b.d) ); + public static rat operator /( rat a, rat b ) => checked( new(a.n * b.d, a.d * b.n) ); + + // additional addition operations + public static rat operator +( rat a, int b ) => checked( new(a.n + a.d * b, a.d) ); + public static rat operator +( int a, rat b ) => checked( new(a * b.d + b.n, b.d) ); + + // additional subtraction operations + public static rat operator -( rat a, int b ) => checked( new(a.n - a.d * b, a.d) ); + public static rat operator -( int a, rat b ) => checked( new(a * b.d - b.n, b.d) ); + + // additional multiplication operations + public static rat operator *( rat a, int b ) => checked( new(a.n * b, a.d) ); + public static rat operator *( int a, rat b ) => checked( new(b.n * a, b.d) ); + public static float operator *( rat a, float b ) => ( a.n * b ) / a.d; + public static float operator *( float a, rat b ) => ( b.n * a ) / b.d; + public static double operator *( rat a, double b ) => ( a.n * b ) / a.d; + public static double operator *( double a, rat b ) => ( b.n * a ) / b.d; + public static rat2 operator *( int2 a, rat b ) => new(a.x * b, a.y * b); + public static rat2 operator *( rat a, int2 b ) => new(a * b.x, a * b.y); + + // additional division operations + public static rat operator /( rat a, int b ) => checked( new(a.n, a.d * b) ); + public static rat operator /( int a, rat b ) => checked( new(a * b.d, b.n) ); + public static float operator /( rat a, float b ) => a.n / ( a.d * b ); + public static float operator /( float a, rat b ) => ( a * b.d ) / b.n; + public static double operator /( rat a, double b ) => a.n / ( a.d * b ); + public static double operator /( double a, rat b ) => ( a * b.d ) / b.n; + + // comparison operators + public static bool operator ==( rat a, rat b ) => a.CompareTo( b ) == 0; + public static bool operator !=( rat a, rat b ) => a.CompareTo( b ) != 0; + public static bool operator <( rat a, rat b ) => a.CompareTo( b ) < 0; + public static bool operator >( rat a, rat b ) => a.CompareTo( b ) > 0; + public static bool operator <=( rat a, rat b ) => a.CompareTo( b ) <= 0; + public static bool operator >=( rat a, rat b ) => a.CompareTo( b ) >= 0; + + // comparison functions + public int CompareTo( rat other ) => checked( ( n * other.d ).CompareTo( d * other.n ) ); + public bool Equals( rat other ) => n == other.n && d == other.d; + public override bool Equals( object obj ) => obj is rat other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( n, d ); + + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/Rational.cs.meta b/Runtime/Numerics/rat.cs.meta similarity index 100% rename from Runtime/Numerics/Rational.cs.meta rename to Runtime/Numerics/rat.cs.meta From 8a348c62646cef3b8efbb3ab7415f0a0e4c7e3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:35:55 +0200 Subject: [PATCH 277/301] rat2 (rational 2D vectors) --- Runtime/Numerics/rat2.cs | 206 ++++++++++++++++++++++++++++++++++ Runtime/Numerics/rat2.cs.meta | 3 + 2 files changed, 209 insertions(+) create mode 100644 Runtime/Numerics/rat2.cs create mode 100644 Runtime/Numerics/rat2.cs.meta diff --git a/Runtime/Numerics/rat2.cs b/Runtime/Numerics/rat2.cs new file mode 100644 index 0000000..6f52118 --- /dev/null +++ b/Runtime/Numerics/rat2.cs @@ -0,0 +1,206 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Assertions; +using static Freya.mathfs; + +namespace Freya { + + /// A 2D vector with rational components (ℚ² instead of ℝ²) + [Serializable] public struct rat2 : IEquatable, + IVec2, + INumber, + ISignedNumber, + IRoundable { + [SerializeField] public rat x; + [SerializeField] public rat y; + public rat this[ int i ] => i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( i.ToString() ) }; + public rat X => x; + public rat Y => y; + // public Rational2 rot45chebyshev => throw new NotImplementedException(); + public static readonly rat2 zero = new(rat.zero, rat.zero); + public static readonly rat2 half = new(rat.half, rat.half); + public static readonly rat2 one = new(rat.one, rat.one); + /// The numerator, as an int2 vector + public int2 N => new(x.n, y.n); + /// The denominator, as an int2 vector + public int2 D => new(x.d, y.d); + public rat2( rat x, rat y ) => ( this.x, this.y ) = ( x, y ); + public rat2( int2 v ) => ( this.x, this.y ) = ( v.x, v.y ); + + public static rat2 FromVector2( Vector2 v, int snapStepsPerUnit = 2 ) { + return new rat2( rat.FromFloat( v.x, snapStepsPerUnit ), rat.FromFloat( v.y, snapStepsPerUnit ) ); + } + + public bool isZero => math.all( N == new int2( 0, 0 ) ); + public bool isInteger => math.all( D == new int2( 1, 1 ) ); + public bool isOrthogonal => abs.cmin == 0; + public bool IsDiagonal => x.abs == y.abs; + + // Chebyshev distances + public rat2 to( rat2 target ) => target - this; + public rat magSq => this.dot( this ); + public rat magChebyshev => this.abs.cmax; + public rat magTaxicab => this.abs.csum; + + public rat2 normalizedChebyshev => this / magChebyshev; + public static rat distChebyshev( rat2 a, rat2 b ) => ( b - a ).magChebyshev; + + public int quadrant => ceilAwayFrom0.quadrant(); + public int signedQuadrant => ceilAwayFrom0.signedQuadrant(); + public int2 quadrantBasisX => ceilAwayFrom0.quadrantBasisX(); + public (int2 x, int2 y) quadrantBasis => ceilAwayFrom0.quadrantBasis(); + public int pointSideOfPlane( rat2 planePos, rat2 planeNormal ) => ( this - planePos ).dot( planeNormal ).sign; + public rat2 complexMul( rat2 other ) => new(x * other.x - y * other.y, x * other.y + y * other.x); + public rat2 complexConj => new(x, -y); + + public rat2 normalizedTaxicab => this / magTaxicab; + + public static rat sqDist( rat2 a, rat2 b ) => ( b - a ).magSq; + public int2 normalized { + get { + Assert.IsTrue( isOrthogonal, $"Non-orthogonal {nameof(rat2)} vectors can't be normalized" ); + return this.sign; + } + } + /// Returns an int2 snapped to the nearest orthogonal normal. + /// Ambiguities along diagonals are resolved in the positive rotation direction + public int2 orthonormalized { + get { + Assert.IsFalse( isZero, "Can't orthonormalize a zero vector" ); + rat2 a = this.abs; + rat2 g = this / a.cmax; + if( IsDiagonal ) + return (int2)( ( g + g.rot90 ) / 2 ); + return a.x > a.y ? new int2( (int)g.x, 0 ) : new int2( 0, (int)g.y ); + } + } + /// Similar to orthonormalized, but also provides the "other" axis. + /// Note that this is the other axis in the same direction, it does not strictly follow handedness + public (int2 main, int2 secondary, rat mainLen, rat secondaryLen) decomposeOrthonormal { + get { + int2 m = orthonormalized; + int2 s = m.rot90(); + if( isOrthogonal ) + s *= wedge( m ).sign; + return ( m, s, projectionTValue( this, m ), projectionTValue( this, s ) ); + } + } + public rat2 rot90 => new(-y, x); + public rat2 rotNeg90 => new(y, -x); + public rat2 rot180 => -this; + public rat cmin => x.min( y ); + public rat cmax => x.max( y ); + public rat csum => x + y; + + public rat dot( rat2 other ) => x * other.x + y * other.y; + public rat dot( int2 other ) => x * other.x + y * other.y; + public rat wedge( rat2 other ) => x * other.y - y * other.x; + public rat wedge( int2 other ) => x * other.y - y * other.x; + + public rat2 abs => new(x.abs, y.abs); + public rat2 max( rat2 other ) => new(x.max( other.x ), y.max( other.y )); + public rat2 min( rat2 other ) => new(x.min( other.x ), y.min( other.y )); + public int2 sign => new(x.sign, y.sign); + public int2 round( RoundingDirection rounding = RoundingDirection.ToEven ) => new(x.round( rounding ), y.round( rounding )); + public int2 floorToward0 => new(x.floorToward0, y.floorToward0); + public int2 ceilAwayFrom0 => new(x.ceilAwayFrom0, y.ceilAwayFrom0); + public int2 floor => new(x.floor, y.floor); + public int2 ceil => new(x.ceil, y.ceil); + + public bool TryCastToIntHalf2( out inth2 ih ) { + if( x.TryCastToIntHalf( out inth ihx ) && y.TryCastToIntHalf( out inth ihy ) ) { + ih = new inth2( ihx, ihy ); + return true; + } + ih = default; + return false; + } + + public override string ToString() => $"( {x}, {y} )"; + + /// Returns the signed "distance" from the plane to a point + public static rat PointScaledDistFromPlane( rat2 pt, rat2 planePt, rat2 planeTangent ) => mathfs.wedge( pt - planePt, planeTangent ); + + public static rat LineIntersectionTValueAlongA( rat2 aOrigin, rat2 aDir, rat2 bOrigin, rat2 bDir ) { + rat2 beta = bDir.rot90; + rat2 c = projectToNormal( bOrigin - aOrigin, beta ); + return projectionTValuePerp( c, aDir ); + } + + public static rat2 IntersectLines( rat2 aOrigin, rat2 aDir, rat2 bOrigin, rat2 bDir ) { + if( mathfs.wedge( aDir, bDir ) == 0 ) // check if parallel + throw new Exception( "Cannot intersect parallel lines" ); + rat t = LineIntersectionTValueAlongA( aOrigin, aDir, bOrigin, bDir ); + return aOrigin + aDir * t; + // bOrigin -= aOrigin; + // Rational2 beta = bDir.rot90; + // Rational2 c = projectToNormal( bOrigin, beta ); + // Rational2 I = projectToNormalPerp( c, aDir ); + // return I + aOrigin; + } + + // type casting + public static implicit operator rat2( int2 n ) => new(n.x, n.y); + public static explicit operator int2( rat2 r ) => r.isInteger ? r.N : throw new ArithmeticException( $"Rational value {r} can't be cast to an integer" ); + public static explicit operator inth2( rat2 r ) => new((inth)r.x, (inth)r.y); + public static explicit operator float2( rat2 r ) => new((float)r.x, (float)r.y); + public static explicit operator double2( rat2 r ) => new((double)r.x, (double)r.y); + public static explicit operator Vector2( rat2 r ) => new((float)r.x, (float)r.y); + public static explicit operator Vector3( rat2 r ) => new((float)r.x, (float)r.y, 0); + public static explicit operator double3( rat2 r ) => new((double)r.x, (double)r.y, 0); + + // unary operations + public static rat2 operator -( rat2 r ) => new(-r.x, -r.y); + public static rat2 operator +( rat2 r ) => r; + + // addition + public static rat2 operator +( rat2 a, rat2 b ) => new(a.x + b.x, a.y + b.y); + public static rat2 operator +( rat2 a, int2 b ) => new(a.x + b.x, a.y + b.y); + public static rat2 operator +( int2 a, rat2 b ) => new(a.x + b.x, a.y + b.y); + + // subtraction + public static rat2 operator -( rat2 a, rat2 b ) => new(a.x - b.x, a.y - b.y); + public static rat2 operator -( rat2 a, int2 b ) => new(a.x - b.x, a.y - b.y); + public static rat2 operator -( int2 a, rat2 b ) => new(a.x - b.x, a.y - b.y); + + // multiplication + // public static Rational2 operator *( Rational2 a, Rational2 b ) => new(a.x * b.x, a.y * b.y); + public static rat2 operator *( rat2 a, rat b ) => new(a.x * b, a.y * b); + public static rat2 operator *( rat a, rat2 b ) => new(a * b.x, a * b.y); + public static rat2 operator *( rat2 a, int b ) => new(a.x * b, a.y * b); + public static rat2 operator *( int a, rat2 b ) => new(a * b.x, a * b.y); + public static float2 operator *( rat2 a, float b ) => new(a.x * b, a.y * b); + public static float2 operator *( float a, rat2 b ) => new(a * b.x, a * b.y); + public static double2 operator *( rat2 a, double b ) => new(a.x * b, a.y * b); + public static double2 operator *( double a, rat2 b ) => new(a * b.x, a * b.y); + // public static Rational2 operator *( Rational2 a, Rational2 b ) => checked( new(a.n * b.n, a.d * b.d) ); + + // division + public static rat2 operator /( rat2 a, rat b ) => new(a.x / b, a.y / b); + public static rat2 operator /( rat2 a, int b ) => new(a.x / b, a.y / b); + public static rat2 operator /( int a, rat2 b ) => new(a / b.x, a / b.y); + public static float2 operator /( rat2 a, float b ) => new(a.x / b, a.y / b); + public static double2 operator /( rat2 a, double b ) => new(a.x / b, a.y / b); + + // public static Rational2 operator /( Rational2 a, Rational2 b ) => checked( new(a.n * b.d, a.d * b.n) ); + // public static float operator /( float a, Rational2 b ) => ( a * b.d ) / b.n; + // public static double operator /( double a, Rational2 b ) => ( a * b.d ) / b.n; + + // comparison operators + public static bool2 operator ==( rat2 a, rat2 b ) => new(a.x == b.x, a.y == b.y); + public static bool2 operator !=( rat2 a, rat2 b ) => new(a.x != b.x, a.y != b.y); + public static bool2 operator <( rat2 a, rat2 b ) => new(a.x < b.x, a.y < b.y); + public static bool2 operator >( rat2 a, rat2 b ) => new(a.x > b.x, a.y > b.y); + public static bool2 operator <=( rat2 a, rat2 b ) => new(a.x <= b.x, a.y <= b.y); + public static bool2 operator >=( rat2 a, rat2 b ) => new(a.x >= b.x, a.y >= b.y); + public bool Equals( rat2 other ) => x.Equals( other.x ) && y.Equals( other.y ); + public override bool Equals( object obj ) => obj is rat2 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( x, y ); + + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/rat2.cs.meta b/Runtime/Numerics/rat2.cs.meta new file mode 100644 index 0000000..9567369 --- /dev/null +++ b/Runtime/Numerics/rat2.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e437079d92a54cdabe992f3570e08ec6 +timeCreated: 1774753386 \ No newline at end of file From 016f27ceac604731478c17206da13d57ea94e09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:36:32 +0200 Subject: [PATCH 278/301] inth (half-integers) --- Runtime/Numerics/inth.cs | 92 +++++++++++++++++++++++++++++++++++ Runtime/Numerics/inth.cs.meta | 3 ++ 2 files changed, 95 insertions(+) create mode 100644 Runtime/Numerics/inth.cs create mode 100644 Runtime/Numerics/inth.cs.meta diff --git a/Runtime/Numerics/inth.cs b/Runtime/Numerics/inth.cs new file mode 100644 index 0000000..d456ef0 --- /dev/null +++ b/Runtime/Numerics/inth.cs @@ -0,0 +1,92 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine; + +namespace Freya {} + +namespace Freya { + + /// A fixed precision data type for half-integers, using a single backing interger. For numbers like: 0, 0.5, 1, 1.5, 2, etc. + [Serializable] public struct inth : + IComparable, + IEquatable, + INumber, + ISignedNumber, + IHalfNumber, + IRoundable { + /// The number of halves + [SerializeField] public int h; + + // public intHalf( int halves ) => this.h = halves; + public inth fromInt( int intValue ) => this.h = intValue * 2; + + public bool isInteger => h % 2 == 0; + public int sign => Math.Sign( h ); + public inth abs => new() { h = Math.Abs( h ) }; + public inth max( inth other ) => this > other ? this : other; + public inth min( inth other ) => this < other ? this : other; + public static int zero => 0; + public static inth half => new() { h = 1 }; + public static int one => 1; + public int times2 => h; + + public static inth fromFloat( float v, RoundingDirection rounding = RoundingDirection.ToEven ) => new() { h = mathfs.round( v * 2, rounding ) }; + + public int round( RoundingDirection rounding = RoundingDirection.ToEven ) { + if( isInteger ) + return h / 2; + int rUp = ( h + 1 ) / 2; + return rounding switch { + RoundingDirection.AwayFromZero => h > 0 ? rUp : rUp - 1, + RoundingDirection.ToZero => h < 0 ? rUp : rUp - 1, + RoundingDirection.ToNegativeInfinity => rUp - 1, // floor(x) + RoundingDirection.ToPositiveInfinity => rUp, // ceil(x) + RoundingDirection.ToEven or _ => rUp % 2 == 0 ? rUp : rUp - 1, + }; + } + + public int floorToward0 => round( RoundingDirection.ToZero ); + public int ceilAwayFrom0 => round( RoundingDirection.AwayFromZero ); + public int floor => round( RoundingDirection.ToNegativeInfinity ); + public int ceil => round( RoundingDirection.ToPositiveInfinity ); + + public override string ToString() => isInteger ? $"{h / 2}" : $"{h / 2}.5"; + + // public static intHalf Lerp( intHalf a, intHalf b, intHalf t ) => a + t * ( b - a ); + // public static intHalf InverseLerp( intHalf a, intHalf b, intHalf v ) => ( v - a ) / ( b - a ); + + public static implicit operator rat( inth ih ) => new(ih.h, 2); + public static implicit operator inth( int i ) => new() { h = i * 2 }; + public static explicit operator float( inth i ) => i.h / 2f; // todo: this is a little wasteful prolly + + // unary operations + public static inth operator -( inth ih ) => new() { h = -ih.h }; + public static inth operator +( inth ih ) => ih; + + // binary operations + public static inth operator +( inth a, inth b ) => new() { h = a.h + b.h }; + public static inth operator +( inth a, int b ) => new() { h = a.h + b * 2 }; + public static inth operator +( int a, inth b ) => new() { h = a * 2 + b.h }; + public static inth operator -( inth a, inth b ) => new() { h = a.h - b.h }; + public static inth operator -( inth a, int b ) => new() { h = a.h - b * 2 }; + public static inth operator -( int a, inth b ) => new() { h = a * 2 - b.h }; + public static inth operator *( inth a, int b ) => new() { h = a.h * b }; + public static inth operator *( int a, inth b ) => new() { h = a * b.h }; + + // comparison operators + public static bool operator ==( inth a, inth b ) => a.CompareTo( b ) == 0; + public static bool operator !=( inth a, inth b ) => a.CompareTo( b ) != 0; + public static bool operator <( inth a, inth b ) => a.CompareTo( b ) < 0; + public static bool operator >( inth a, inth b ) => a.CompareTo( b ) > 0; + public static bool operator <=( inth a, inth b ) => a.CompareTo( b ) <= 0; + public static bool operator >=( inth a, inth b ) => a.CompareTo( b ) >= 0; + + // comparison functions + public int CompareTo( inth other ) => h.CompareTo( other.h ); + public bool Equals( inth other ) => h == other.h; + public override bool Equals( object obj ) => obj is inth other && Equals( other ); + public override int GetHashCode() => h.GetHashCode(); + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/inth.cs.meta b/Runtime/Numerics/inth.cs.meta new file mode 100644 index 0000000..8969ddd --- /dev/null +++ b/Runtime/Numerics/inth.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ad7fd47f9cf845ca991eb4563b706893 +timeCreated: 1774974007 \ No newline at end of file From 68ebfed20bb02118f1fd5fd840bd0b35aed1ae10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:36:54 +0200 Subject: [PATCH 279/301] inth2 (half-integer vectors, useful for grid coordinates!) --- Runtime/Numerics/inth2.cs | 120 +++++++++++++++++++++++++++++++++ Runtime/Numerics/inth2.cs.meta | 3 + 2 files changed, 123 insertions(+) create mode 100644 Runtime/Numerics/inth2.cs create mode 100644 Runtime/Numerics/inth2.cs.meta diff --git a/Runtime/Numerics/inth2.cs b/Runtime/Numerics/inth2.cs new file mode 100644 index 0000000..b6e6011 --- /dev/null +++ b/Runtime/Numerics/inth2.cs @@ -0,0 +1,120 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using Unity.Mathematics; +using UnityEngine; +using static Freya.mathfs; + +namespace Freya { + + /// A fixed precision data type for half-integers, using a single backing interger. For numbers like: 0, 0.5, 1, 1.5, 2, etc. + [Serializable] public struct inth2 : + IEquatable, + IVec2, + INumber, + ISignedNumber, + IHalfNumber, + IRoundable { + + /// The number of halves in the x axis + [SerializeField] public inth x; + /// The number of halves in the y axis + [SerializeField] public inth y; + + public inth X => x; + public inth Y => y; + public inth this[ int i ] => i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( i.ToString() ) }; + public bool isOrthogonal => abs.cmin == 0; + public bool isZero => x == 0 && y == 0; + + public inth2( inth x, inth y ) => ( this.x, this.y ) = ( x, y ); + + public bool isInteger => x.isInteger && y.isInteger; + + public inth2 rot90 => new(-y, x); + public inth2 rotNeg90 => new(y, -x); + public inth2 rot180 => -this; + + public inth2 FromVector2( Vector2 v, RoundingDirection rounding = RoundingDirection.ToEven ) => + new() { + x = inth.fromFloat( v.x, rounding ), + y = inth.fromFloat( v.y, rounding ) + }; + + public int2 times2 => new(x.times2, y.times2); + public inth cmin => x.min( y ); + public inth cmax => x.max( y ); + public inth csum => x + y; + public rat dot( inth2 other ) => ( (rat2)this ).dot( other ); + public rat wedge( inth2 other ) => ( (rat2)this ).wedge( other ); + public inth2 to( inth2 target ) => target - this; + public rat magSq => this.dot( this ); + public inth magChebyshev => this.abs.cmax; + public inth magTaxicab => this.abs.csum; + public inth2 abs => new(x.abs, y.abs); + public inth2 max( inth2 other ) => new(x.max( other.x ), y.max( other.y )); + public inth2 min( inth2 other ) => new(x.min( other.x ), y.min( other.y )); + public int2 sign => new(x.sign, y.sign); + public int2 round( RoundingDirection rounding = RoundingDirection.ToEven ) => new(x.round( rounding ), y.round( rounding )); + public int2 floorToward0 => new(x.floorToward0, y.floorToward0); + public int2 ceilAwayFrom0 => new(x.ceilAwayFrom0, y.ceilAwayFrom0); + public int2 floor => new(x.floor, y.floor); + public int2 ceil => new(x.ceil, y.ceil); + public int quadrant => ceilAwayFrom0.quadrant(); + public int signedQuadrant => ceilAwayFrom0.signedQuadrant(); + public int2 quadrantBasisX => ceilAwayFrom0.quadrantBasisX(); + public (int2 x, int2 y) quadrantBasis => ceilAwayFrom0.quadrantBasis(); + public int pointSideOfPlane( inth2 planePos, inth2 planeNormal ) => this.times2.pointSideOfPlane( planePos.times2, planeNormal.times2 ); + + public rat2 complexMul( inth2 other ) => ( (rat2)times2.complexMul( other.times2 ) ) / 4; + public inth2 complexConj => new(x, -y); + + public override string ToString() => $"( {x}, {y} )"; + + public static implicit operator rat2( inth2 ih ) => new(ih.x, ih.y); + public static implicit operator inth2( int2 i ) => new(i.x, i.y); + + public static explicit operator int2( inth2 ih ) => new((int)ih.x, (int)ih.y); + public static explicit operator float2( inth2 ih ) => new((float)ih.x, (float)ih.y); + public static explicit operator Vector2( inth2 ih ) => new((float)ih.x, (float)ih.y); + public static explicit operator double2( inth2 ih ) => new((double)ih.x, (double)ih.y); + public static explicit operator Vector3( inth2 ih ) => new((float)ih.x, (float)ih.y, 0); + public static explicit operator double3( inth2 ih ) => new((double)ih.x, (double)ih.y, 0); + + public static int2 zero => new(0, 0); + public static inth2 half => new(inth.half, inth.half); + public static int2 one => new(1, 1); + + // unary operations + public static inth2 operator -( inth2 ih ) => new(-ih.x, -ih.y); + public static inth2 operator +( inth2 ih ) => ih; + + // binary operations + public static inth2 operator +( inth2 a, inth2 b ) => new(a.x + b.x, a.y + b.y); + public static inth2 operator +( inth2 a, int2 b ) => new(a.x + b.x, a.y + b.y); + public static inth2 operator +( int2 a, inth2 b ) => new(a.x + b.x, a.y + b.y); + public static inth2 operator -( inth2 a, inth2 b ) => new(a.x - b.x, a.y - b.y); + public static inth2 operator -( inth2 a, int2 b ) => new(a.x - b.x, a.y - b.y); + public static inth2 operator -( int2 a, inth2 b ) => new(a.x - b.x, a.y - b.y); + public static inth2 operator *( inth2 a, int2 b ) => new(a.x * b.x, a.y * b.y); + public static inth2 operator *( int2 a, inth2 b ) => new(a.x * b.x, a.y * b.y); + public static rat2 operator *( inth2 a, rat b ) => new(a.x * b, a.y * b); + public static rat2 operator *( rat a, inth2 b ) => new(a * b.x, a * b.y); + + // comparison operators + public static bool2 operator ==( inth2 a, inth2 b ) => new(a.x == b.x, a.y == b.y); + public static bool2 operator !=( inth2 a, inth2 b ) => new(a.x != b.x, a.y != b.y); + public static bool2 operator <( inth2 a, inth2 b ) => new(a.x < b.x, a.y < b.y); + public static bool2 operator >( inth2 a, inth2 b ) => new(a.x > b.x, a.y > b.y); + public static bool2 operator <=( inth2 a, inth2 b ) => new(a.x <= b.x, a.y <= b.y); + public static bool2 operator >=( inth2 a, inth2 b ) => new(a.x >= b.x, a.y >= b.y); + + // comparison functions + public bool Equals( inth2 other ) => x == other.x && y == other.y; + public override bool Equals( object obj ) => obj is inth2 other && Equals( other ); + public override int GetHashCode() => HashCode.Combine( x, y ); + + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/inth2.cs.meta b/Runtime/Numerics/inth2.cs.meta new file mode 100644 index 0000000..25a8397 --- /dev/null +++ b/Runtime/Numerics/inth2.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4e0cbee7d7ff47279e6435584568257b +timeCreated: 1775060027 \ No newline at end of file From 9ef5cf212cb326446b21f9d1bb810b48beced504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 20:39:06 +0200 Subject: [PATCH 280/301] cleanup --- Runtime/Extensions.cs | 44 +++++++++++++++++++++---------------------- Runtime/Mathfs.cs | 35 ++++++++++++++++++---------------- Runtime/Random.cs | 2 +- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index d740c95..abd7ffe 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -946,38 +946,38 @@ public static Matrix4x1 MultiplyColumnVector( this Matrix4x4 m, Matrix4x1 v ) => /// [MethodImpl( INLINE )] public static Vector3Int CeilToInt( this Vector3 value ) => Mathfs.CeilToInt( value ); - /// - [MethodImpl( INLINE )] public static float Round( this float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); + /// + [MethodImpl( INLINE )] public static float Round( this float value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector2 Round( this Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); + /// + [MethodImpl( INLINE )] public static Vector2 Round( this Vector2 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector3 Round( this Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); + /// + [MethodImpl( INLINE )] public static Vector3 Round( this Vector3 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector4 Round( this Vector4 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); + /// + [MethodImpl( INLINE )] public static Vector4 Round( this Vector4 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.Round( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static float Round( this float value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); + /// + [MethodImpl( INLINE )] public static float Round( this float value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector2 Round( this Vector2 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); + /// + [MethodImpl( INLINE )] public static Vector2 Round( this Vector2 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector3 Round( this Vector3 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); + /// + [MethodImpl( INLINE )] public static Vector3 Round( this Vector3 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector4 Round( this Vector4 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); + /// + [MethodImpl( INLINE )] public static Vector4 Round( this Vector4 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.Round( value, snapInterval, midpointRounding ); - /// - [MethodImpl( INLINE )] public static int RoundToInt( this float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); + /// + [MethodImpl( INLINE )] public static int RoundToInt( this float value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector2Int RoundToInt( this Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); + /// + [MethodImpl( INLINE )] public static Vector2Int RoundToInt( this Vector2 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); - /// - [MethodImpl( INLINE )] public static Vector3Int RoundToInt( this Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); + /// + [MethodImpl( INLINE )] public static Vector3Int RoundToInt( this Vector3 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => Mathfs.RoundToInt( value, midpointRounding ); #endregion diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index ac2fe58..a922599 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -10,6 +10,9 @@ using Uei = UnityEngine.Internal; using System.Linq; // used for arbitrary count min/max functions, so it's safe and won't allocate garbage don't worry~ using System.Runtime.CompilerServices; +using Unity.Mathematics; + +using MidpointRounding = System.MidpointRounding; namespace Freya { @@ -619,37 +622,37 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => [MethodImpl( INLINE )] public static Vector3Int CeilToInt( Vector3 value ) => new Vector3Int( (int)Math.Ceiling( value.x ), (int)Math.Ceiling( value.y ), (int)Math.Ceiling( value.z ) ); /// Rounds the value to the nearest integer - [MethodImpl( INLINE )] public static float Round( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (float)MathF.Round( value, midpointRounding ); + [MethodImpl( INLINE )] public static float Round( float value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => (float)MathF.Round( value, midpointRounding ); /// Rounds the vector components to the nearest integer - [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ) ); + [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector2( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ), MathF.Round( value.z, midpointRounding ) ); + /// + [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector3( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ), MathF.Round( value.z, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ), MathF.Round( value.z, midpointRounding ), MathF.Round( value.w, midpointRounding ) ); + /// + [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector4( MathF.Round( value.x, midpointRounding ), MathF.Round( value.y, midpointRounding ), MathF.Round( value.z, midpointRounding ), MathF.Round( value.w, midpointRounding ) ); /// Rounds the value to the nearest value, snapped to the given interval size - [MethodImpl( INLINE )] public static float Round( float value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => MathF.Round( value / snapInterval, midpointRounding ) * snapInterval; + [MethodImpl( INLINE )] public static float Round( float value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => MathF.Round( value / snapInterval, midpointRounding ) * snapInterval; /// Rounds the vector components to the nearest value, snapped to the given interval size - [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ) ); + [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector2( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ) ); + /// + [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector3( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, float snapInterval, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector4( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ), Round( value.w, snapInterval, midpointRounding ) ); + /// + [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector4( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ), Round( value.w, snapInterval, midpointRounding ) ); /// Rounds the value to the nearest integer, returning an int value - [MethodImpl( INLINE )] public static int RoundToInt( float value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => (int)Math.Round( value, midpointRounding ); + [MethodImpl( INLINE )] public static int RoundToInt( float value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => (int)Math.Round( value, midpointRounding ); /// Rounds the vector components to the nearest integer, returning an integer vector - [MethodImpl( INLINE )] public static Vector2Int RoundToInt( Vector2 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector2Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ) ); + [MethodImpl( INLINE )] public static Vector2Int RoundToInt( Vector2 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector2Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ) ); - /// - [MethodImpl( INLINE )] public static Vector3Int RoundToInt( Vector3 value, MidpointRounding midpointRounding = MidpointRounding.ToEven ) => new Vector3Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ), (int)Math.Round( value.z, midpointRounding ) ); + /// + [MethodImpl( INLINE )] public static Vector3Int RoundToInt( Vector3 value, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector3Int( (int)Math.Round( value.x, midpointRounding ), (int)Math.Round( value.y, midpointRounding ), (int)Math.Round( value.z, midpointRounding ) ); #endregion diff --git a/Runtime/Random.cs b/Runtime/Random.cs index b2f05bf..da107d5 100644 --- a/Runtime/Random.cs +++ b/Runtime/Random.cs @@ -23,7 +23,7 @@ public static class Random { /// The minimum value [inclusive] /// The maximum value [inclusive] public static float Range( float min, float max ) => UnityRandom.Range( min, max ); - + /// Randomly returns a value between min [inclusive] and max [exclusive] /// The minimum value [inclusive] /// The maximum value [exclusive] From 4389bca0a21615d7c6e7b64c109dda6716207665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 21:03:32 +0200 Subject: [PATCH 281/301] Added FloatRange Union/Difference/Intersection --- Runtime/Numerics/FloatRange.cs | 100 ++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 812e984..7637742 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -1,13 +1,15 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Mathematics; using UnityEngine; namespace Freya { /// A value range between two values a and b - [Serializable] - public struct FloatRange { + [Serializable] public struct FloatRange : IEquatable { /// The unit interval of 0 to 1 public static readonly FloatRange unit = new FloatRange( 0, 1 ); @@ -108,6 +110,100 @@ public FloatRange Encapsulate( FloatRange range ) => _ => ( Mathfs.Min( b, range.b ), Mathfs.Max( a, range.a ) ) // reversed - b is min, a is max }; + /// Combines overlapping ranges and enumerates the results + public static IEnumerable Union( IEnumerable ranges ) { + int i = 0; + FloatRange range = default; + foreach( FloatRange r in ranges.OrderBy( r => r.a ) ) { + if( i == 0 ) { + range = r; + } else { + if( range.b < r.a ) { // r.a is guaranteed to be >= range.a + // the next range r is outside the current range, commit current range + yield return range; + // switch to next range + range = r; + } else { + range = range.Encapsulate( r ); // the next range starts inside the current one, combine them + } + } + i++; + } + if( i > 0 ) // if i == 0 there were no items provided + yield return range; + } + + /// Enumerates the remaining float ranges after this range has range b taken out of it. May return either 0, 1 or 2 ranges + /// The range to subtract with + public IEnumerable Difference( FloatRange remove ) { + if( this.Overlaps( remove ) == false ) { + yield return this; // nothing is subtracted + } else if( remove.a <= this.a && remove.b >= this.b ) { + // everything has been subtracted away + } else { + // now we know it's a partial cut, which leads to either one or two results + if( remove.a < this.b && remove.a > this.a ) + yield return new FloatRange( this.a, remove.a ); + if( remove.b < this.b && remove.b > this.a ) + yield return new FloatRange( remove.b, this.b ); + } + } + + /// Returns the range shared by this range and other, which is either one range, or no ranges at all + /// The range to intersect with + public IEnumerable Intersection( FloatRange other ) { + if( this.TryIntersect( other, out FloatRange intersection ) ) + yield return intersection; + } + + /// Tries to get the range shared by this range and other + /// The range to intersect with + /// The range common to both ranges + /// Returns true if there is a range shared by both + public bool TryIntersect( FloatRange other, out FloatRange intersection ) { + if( other.b <= this.a || other.a >= this.b ) { + intersection = default; // no overlap, return nothing + return false; + } + intersection = new FloatRange( math.max( other.a, this.a ), math.min( other.b, this.b ) ); + return true; + } + + /// Enumerates the remaining float ranges after range a has range b taken out of it + /// The ranges to subtract from + /// The range to subtract with + public static IEnumerable Difference( IEnumerable solid, FloatRange remove ) { + return solid.SelectMany( x => x.Difference( remove ) ); + } + + /// Splits the range if it crosses a modulo boundary. For example, the range [-20,10] mod 360 will return [0,10] and [340,360] + /// The value at the discontinuity. For example, if the ranges represent degrees, mod should be 360 + public IEnumerable ModuloSplit( float mod ) { + float len = Length; + if( len >= mod ) { // this means the range necessarily covers the entire range + yield return new FloatRange( 0, mod ); + } else { + float aCanon = Mathfs.Repeat( a, mod ); + + float spaceBeforeDiscontinuity = mod - aCanon; + if( len <= spaceBeforeDiscontinuity ) { + // this means it's not crossing the discontinuity + yield return new FloatRange( aCanon, aCanon + len ); + } else { + // this means it needs to be split up into two around the discontinuity + yield return new FloatRange( aCanon, mod ); + yield return new FloatRange( 0, len - spaceBeforeDiscontinuity ); + } + } + } + + /// Combines overlapping ranges with a specified modulo + /// The ranges to combine + /// The value at the discontinuity. For example, if the ranges represent degrees, mod should be 360 + public static IEnumerable UnionModulo( IEnumerable ranges, float mod ) { + return Union( ranges.SelectMany( r => r.ModuloSplit( mod ) ) ); + } + /// Returns a version of this range, scaled around its start value /// The value to scale the range by public FloatRange ScaleFromStart( float scale ) => new FloatRange( a, a + scale * ( b - a ) ); From 5b667898ae19b0025fb8d926ba33a626ad47dae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 21:04:37 +0200 Subject: [PATCH 282/301] Harmonic interpolation (herp) --- Runtime/Mathfs.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index a922599..6513cd7 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -888,6 +888,17 @@ public static Rect Lerp( Rect a, Rect b, float t ) { _ => a * MathF.Exp( MathF.Log( b / a ) * t ) // same as exp( lerp(ln a, ln b, t) ), but without numeric issues! }; + /// Harmonic interpolation + /// The start value + /// The end value + /// The t-value from 0 to 1 representing position along the eerp + [MethodImpl( INLINE )] public static float Herp( float a, float b, float t ) => + t switch { + 0f => a, + 1f => b, + _ => ( a * b ) / ( t * ( a - b ) + b ) + }; + /// [MethodImpl( INLINE )] public static Vector3 Eerp( Vector3 a, Vector3 b, float t ) => t switch { From 47cd76a7f7366611fd3a55087e24931d3826a8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 7 Apr 2026 22:32:24 +0200 Subject: [PATCH 283/301] Gaussian sampling --- Runtime/Random.cs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Runtime/Random.cs b/Runtime/Random.cs index da107d5..870018e 100644 --- a/Runtime/Random.cs +++ b/Runtime/Random.cs @@ -1,5 +1,6 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using UnityEngine; using static Freya.Mathfs; using UnityRandom = UnityEngine.Random; @@ -62,6 +63,37 @@ public static class Random { // 3D Orientation /// Returns a random uniformly distributed rotation public static Quaternion Rotation => UnityRandom.rotationUniform; + + // Normal distributions + /// Returns random numbers with a gaussian distribution + /// The distance away from the mean that will contain 68.27% of all values + /// The mean (average) value of the distribution + public static float Gaussian( float variance = 1, float mean = 0 ) { + float u1 = UnityEngine.Random.value; + float u2 = UnityEngine.Random.value; + return MathF.Sqrt( -2 * MathF.Log( u1 ) ) * MathF.Cos( TAU * u2 ) * variance + mean; + } + + /// Samples a gaussian with rejection sampling, retrying when samples end up outside the given range + /// The distance away from the mean that will contain 68.27% of all values + /// The mean (average) value of the distribution + /// The minimum value of the range + /// The maximum value of the range + /// The maximum number of times to attempt to get a value within the range, before giving up and clamping instead + public static float GaussianInRange( float variance = 1, float mean = 0, float min = -1, float max = 1, int samplingMaxAttempts = 3 ) { + float x; + int attempts = 0; + do { + x = Gaussian( variance, mean ); + attempts++; + } while( attempts < samplingMaxAttempts && x.Within( min, max ) == false ); + return x.Clamp( min, max ); + } + + /// Returns a value between -1 and 1 with a variance of 1/4 + public static float GaussianNeg1to1 => GaussianInRange( 1 / 4f ); + /// Returns a value between 0 and 1 with a variance of 1/8 + public static float Gaussian01 => GaussianInRange( 1 / 8f, 1 / 2f, 0, 1 ); } } \ No newline at end of file From 8967b2be71a6d89904ecccb100bb5291bd11e670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Apr 2026 04:14:23 +0200 Subject: [PATCH 284/301] fixed some swizzle extensions not being on Vector3 --- Runtime/Extensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index abd7ffe..817db66 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -63,13 +63,13 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { #region Swizzling /// Returns X and Y as a Vector2, equivalent to new Vector2(v.x,v.y) - [MethodImpl( INLINE )] public static Vector2 XY( this Vector2 v ) => new Vector2( v.x, v.y ); + [MethodImpl( INLINE )] public static Vector2 XY( this Vector3 v ) => new(v.x, v.y); /// Returns Y and X as a Vector2, equivalent to new Vector2(v.y,v.x) - [MethodImpl( INLINE )] public static Vector2 YX( this Vector2 v ) => new Vector2( v.y, v.x ); + [MethodImpl( INLINE )] public static Vector2 YX( this Vector3 v ) => new(v.y, v.x); /// Returns X and Z as a Vector2, equivalent to new Vector2(v.x,v.z) - [MethodImpl( INLINE )] public static Vector2 XZ( this Vector3 v ) => new Vector2( v.x, v.z ); + [MethodImpl( INLINE )] public static Vector2 XZ( this Vector3 v ) => new(v.x, v.z); /// Returns this vector as a Vector3, slotting X into X, and Y into Z, and the input value y into Y. /// Equivalent to new Vector3(v.x,y,v.y) From bc1c75653f74463fb0bfe096b62770960ccb5dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Apr 2026 04:21:07 +0200 Subject: [PATCH 285/301] added inth (half-integers) and related INumber/IRoundable interfaces --- Runtime/Numerics/IHalfNumber.cs | 21 ++ Runtime/Numerics/IHalfNumber.cs.meta | 3 + Runtime/Numerics/INumber.cs | 393 ++++++++++++++++++++++++- Runtime/Numerics/IRoundable.cs | 54 +++- Runtime/Numerics/ISignedNumber.cs | 28 ++ Runtime/Numerics/ISignedNumber.cs.meta | 3 + Runtime/Numerics/inth.cs | 5 +- Runtime/Numerics/rat.cs | 5 +- 8 files changed, 491 insertions(+), 21 deletions(-) create mode 100644 Runtime/Numerics/IHalfNumber.cs create mode 100644 Runtime/Numerics/IHalfNumber.cs.meta create mode 100644 Runtime/Numerics/ISignedNumber.cs create mode 100644 Runtime/Numerics/ISignedNumber.cs.meta diff --git a/Runtime/Numerics/IHalfNumber.cs b/Runtime/Numerics/IHalfNumber.cs new file mode 100644 index 0000000..e935cc2 --- /dev/null +++ b/Runtime/Numerics/IHalfNumber.cs @@ -0,0 +1,21 @@ +using Unity.Mathematics; + +namespace Freya { + + public interface IHalfNumber { + /// Multiplies this by 2 and returns an integer value + public F times2 { get; } + } + + public static partial class mathfs { + /// + public static T times2( T v ) where T : IHalfNumber => v.times2; + + /// + public static int times2( inth v ) => v.times2; + + /// + public static int2 times2( inth2 v ) => v.times2; + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IHalfNumber.cs.meta b/Runtime/Numerics/IHalfNumber.cs.meta new file mode 100644 index 0000000..bc89876 --- /dev/null +++ b/Runtime/Numerics/IHalfNumber.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e4a005c3ebe444fa84eec0e7f9c25c65 +timeCreated: 1775597892 \ No newline at end of file diff --git a/Runtime/Numerics/INumber.cs b/Runtime/Numerics/INumber.cs index 5f82061..b850d01 100644 --- a/Runtime/Numerics/INumber.cs +++ b/Runtime/Numerics/INumber.cs @@ -1,32 +1,405 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; using Unity.Mathematics; -using UnityEngine; namespace Freya { - + public interface INumber { + /// Returns whether this number is an integer public bool isInteger { get; } + + /// Returns whether this vector is the zero vector + public bool isZero { get; } + + /// Returns whether this lies flat along at least one axis + public bool isOrthogonal { get; } } - public interface ISignedNumber : INumber { - public R sign { get; } + public static partial class mathfs { + /// + public static bool isInteger( T v ) where T : INumber => v.isInteger; + + /// + public static bool isInteger( rat v ) => v.isInteger; + + /// + public static bool isInteger( rat2 v ) => v.isInteger; + + /// + public static bool isInteger( inth v ) => v.isInteger; + + /// + public static bool isInteger( inth2 v ) => v.isInteger; + + /// + public static bool isInteger( this int v ) => true; + + /// + public static bool isInteger( this int2 v ) => true; + + /// + public static bool isInteger( this int3 v ) => true; + + /// + public static bool isInteger( this int4 v ) => true; + + /// + public static bool isInteger( this float v ) => v == MathF.Truncate( v ); + + /// + public static bool isInteger( this float2 v ) => v.x.isInteger() && v.y.isInteger(); + + /// + public static bool isInteger( this float3 v ) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger(); + + /// + public static bool isInteger( this float4 v ) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger() && v.w.isInteger(); + + /// + public static bool isInteger( this double v ) => v == Math.Truncate( v ); + + /// + public static bool isInteger( this double2 v ) => v.x.isInteger() && v.y.isInteger(); + + /// + public static bool isInteger( this double3 v ) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger(); + + /// + public static bool isInteger( this double4 v ) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger() && v.w.isInteger(); + + + /// + public static bool isZero( T v ) where T : INumber => v.isZero; + + /// + public static bool isZero( rat v ) => v.isZero; + + /// + public static bool isZero( rat2 v ) => v.isZero; + + /// + public static bool isZero( inth v ) => v.isZero; + + /// + public static bool isZero( inth2 v ) => v.isZero; + + /// + public static bool isZero( this int v ) => true; + + /// + public static bool isZero( this int2 v ) => true; + + /// + public static bool isZero( this int3 v ) => true; + + /// + public static bool isZero( this int4 v ) => true; + + /// + public static bool isZero( this float v ) => v == 0; + + /// + public static bool isZero( this float2 v ) => math.all( v == 0 ); + + /// + public static bool isZero( this float3 v ) => math.all( v == 0 ); + + /// + public static bool isZero( this float4 v ) => math.all( v == 0 ); + + /// + public static bool isZero( this double v ) => v == 0; + + /// + public static bool isZero( this double2 v ) => math.all( v == 0 ); + + /// + public static bool isZero( this double3 v ) => math.all( v == 0 ); + + /// + public static bool isZero( this double4 v ) => math.all( v == 0 ); + + + /// + public static bool isOrthogonal( T v ) where T : INumber => v.isOrthogonal; + + /// + public static bool isOrthogonal( rat v ) => v.isOrthogonal; + + /// + public static bool isOrthogonal( rat2 v ) => v.isOrthogonal; + + /// + public static bool isOrthogonal( inth v ) => v.isOrthogonal; + + /// + public static bool isOrthogonal( inth2 v ) => v.isOrthogonal; + + /// + public static bool isOrthogonal( this int v ) => true; + + /// + public static bool isOrthogonal( this int2 v ) => true; + + /// + public static bool isOrthogonal( this int3 v ) => true; + + /// + public static bool isOrthogonal( this int4 v ) => true; + + /// + public static bool isOrthogonal( this float v ) => v == 0; + + /// + public static bool isOrthogonal( this float2 v ) => math.all( v == 0 ); + + /// + public static bool isOrthogonal( this float3 v ) => math.all( v == 0 ); + + /// + public static bool isOrthogonal( this float4 v ) => math.all( v == 0 ); + + /// + public static bool isOrthogonal( this double v ) => v == 0; + + /// + public static bool isOrthogonal( this double2 v ) => math.all( v == 0 ); + + /// + public static bool isOrthogonal( this double3 v ) => math.all( v == 0 ); + + /// + public static bool isOrthogonal( this double4 v ) => math.all( v == 0 ); + } - public interface INumber : INumber { - /// Returns the absolute value of this number + public interface INumber : INumber { + /// Returns the absolute value of the number. Makes negative values positive public N abs { get; } - public N max( N other ); + + /// Returns the minimum of two numbers public N min( N other ); + /// Returns the maximum of two numbers + public N max( N other ); + + /// The vector from this point to the target. Equivalent to target - this + public N to( N target ); + // I can't do this bc Unity uses older versions of C#: // public static abstract R zero { get; } // public static abstract R one { get; } } - public interface IHalfNumber { - /// Multiplies this by 2 and returns an integer value - public F times2 { get; } + public static partial class mathfs { + /// + public static T abs( T x ) where T : INumber => x.abs; + + /// + public static inth abs( inth x ) => x.abs; + + /// + public static rat abs( rat x ) => x.abs; + + /// + public static inth2 abs( inth2 x ) => x.abs; + + /// + public static rat2 abs( rat2 x ) => x.abs; + + /// + public static int abs( this int x ) => math.abs( x ); + + /// + public static int2 abs( this int2 x ) => math.abs( x ); + + /// + public static int3 abs( this int3 x ) => math.abs( x ); + + /// + public static int4 abs( this int4 x ) => math.abs( x ); + + /// + public static float abs( this float x ) => math.abs( x ); + + /// + public static float2 abs( this float2 x ) => math.abs( x ); + + /// + public static float3 abs( this float3 x ) => math.abs( x ); + + /// + public static float4 abs( this float4 x ) => math.abs( x ); + + /// + public static double abs( this double x ) => math.abs( x ); + + /// + public static double2 abs( this double2 x ) => math.abs( x ); + + /// + public static double3 abs( this double3 x ) => math.abs( x ); + + /// + public static double4 abs( this double4 x ) => math.abs( x ); + + + /// + public static T min( T a, T b ) where T : INumber => a.min( b ); + + /// + public static inth min( inth a, inth b ) => a.min( b ); + + /// + public static rat min( rat a, rat b ) => a.min( b ); + + /// + public static inth2 min( inth2 a, inth2 b ) => a.min( b ); + + /// + public static rat2 min( rat2 a, rat2 b ) => a.min( b ); + + /// + public static int min( this int a, int b ) => math.min( a, b ); + + /// + public static int2 min( this int2 a, int2 b ) => math.min( a, b ); + + /// + public static int3 min( this int3 a, int3 b ) => math.min( a, b ); + + /// + public static int4 min( this int4 a, int4 b ) => math.min( a, b ); + + /// + public static float min( this float a, float b ) => math.min( a, b ); + + /// + public static float2 min( this float2 a, float2 b ) => math.min( a, b ); + + /// + public static float3 min( this float3 a, float3 b ) => math.min( a, b ); + + /// + public static float4 min( this float4 a, float4 b ) => math.min( a, b ); + + /// + public static double min( this double a, double b ) => math.min( a, b ); + + /// + public static double2 min( this double2 a, double2 b ) => math.min( a, b ); + + /// + public static double3 min( this double3 a, double3 b ) => math.min( a, b ); + + /// + public static double4 min( this double4 a, double4 b ) => math.min( a, b ); + + + /// + public static T max( T a, T b ) where T : INumber => a.max( b ); + + /// + public static inth max( inth a, inth b ) => a.max( b ); + + /// + public static rat max( rat a, rat b ) => a.max( b ); + + /// + public static inth2 max( inth2 a, inth2 b ) => a.max( b ); + + /// + public static rat2 max( rat2 a, rat2 b ) => a.max( b ); + + /// + public static int max( this int a, int b ) => math.max( a, b ); + + /// + public static int2 max( this int2 a, int2 b ) => math.max( a, b ); + + /// + public static int3 max( this int3 a, int3 b ) => math.max( a, b ); + + /// + public static int4 max( this int4 a, int4 b ) => math.max( a, b ); + + /// + public static float max( this float a, float b ) => math.max( a, b ); + + /// + public static float2 max( this float2 a, float2 b ) => math.max( a, b ); + + /// + public static float3 max( this float3 a, float3 b ) => math.max( a, b ); + + /// + public static float4 max( this float4 a, float4 b ) => math.max( a, b ); + + /// + public static double max( this double a, double b ) => math.max( a, b ); + + /// + public static double2 max( this double2 a, double2 b ) => math.max( a, b ); + + /// + public static double3 max( this double3 a, double3 b ) => math.max( a, b ); + + /// + public static double4 max( this double4 a, double4 b ) => math.max( a, b ); + + + /// + public static T to( T a, T b ) where T : INumber => a.to( b ); + + /// + public static inth to( inth a, inth b ) => a.to( b ); + + /// + public static rat to( rat a, rat b ) => a.to( b ); + + /// + public static inth2 to( inth2 a, inth2 b ) => a.to( b ); + + /// + public static rat2 to( rat2 a, rat2 b ) => a.to( b ); + + /// + public static int to( this int a, int b ) => b - a; + + /// + public static int2 to( this int2 a, int2 b ) => b - a; + + /// + public static int3 to( this int3 a, int3 b ) => b - a; + + /// + public static int4 to( this int4 a, int4 b ) => b - a; + + /// + public static float to( this float a, float b ) => b - a; + + /// + public static float2 to( this float2 a, float2 b ) => b - a; + + /// + public static float3 to( this float3 a, float3 b ) => b - a; + + /// + public static float4 to( this float4 a, float4 b ) => b - a; + + /// + public static double to( this double a, double b ) => b - a; + + /// + public static double2 to( this double2 a, double2 b ) => b - a; + + /// + public static double3 to( this double3 a, double3 b ) => b - a; + + /// + public static double4 to( this double4 a, double4 b ) => b - a; } + } \ No newline at end of file diff --git a/Runtime/Numerics/IRoundable.cs b/Runtime/Numerics/IRoundable.cs index 5121559..8f396ae 100644 --- a/Runtime/Numerics/IRoundable.cs +++ b/Runtime/Numerics/IRoundable.cs @@ -1,23 +1,59 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) -namespace Freya { +using System; - /// Basically the same as C#'s , but older .net versions don't have all the options - public enum RoundingDirection { - ToEven = 0, - AwayFromZero = 1, - ToZero = 2, - ToNegativeInfinity = 3, - ToPositiveInfinity = 4, - } +namespace Freya { /// Objects that can be rounded to nearby values public interface IRoundable { + + /// Rounds to the nearest integer + /// The rounding method to use public R round( RoundingDirection rounding = RoundingDirection.ToEven ); + + /// Rounds to the nearest integer towards 0 public R floorToward0 { get; } + + /// Rounds to the nearest integer away from 0 public R ceilAwayFrom0 { get; } + + /// Rounds down to the nearest integer public R floor { get; } + + /// Rounds up to the nearest integer public R ceil { get; } } + public static partial class mathfs { + /// + public static R round( V v, RoundingDirection rounding = RoundingDirection.ToEven ) where V : IRoundable => v.round(); + + /// + public static int round( this float v, RoundingDirection rounding = RoundingDirection.ToEven ) => (int)MathF.Round( v, (MidpointRounding)rounding ); + + /// + public static int round( this double v, RoundingDirection rounding = RoundingDirection.ToEven ) => (int)Math.Round( v, (MidpointRounding)rounding ); + + /// + public static R floorToward0( V v ) where V : IRoundable => v.floorToward0; + + /// + public static R ceilAwayFrom0( V v ) where V : IRoundable => v.ceilAwayFrom0; + + /// + public static R floor( V v ) where V : IRoundable => v.floor; + + /// + public static R ceil( V v ) where V : IRoundable => v.ceil; + } + + /// Basically the same as C#'s , but older .net versions don't have all the options + public enum RoundingDirection { + ToEven = 0, + AwayFromZero = 1, + ToZero = 2, + ToNegativeInfinity = 3, + ToPositiveInfinity = 4, + } + } \ No newline at end of file diff --git a/Runtime/Numerics/ISignedNumber.cs b/Runtime/Numerics/ISignedNumber.cs new file mode 100644 index 0000000..9b13d5d --- /dev/null +++ b/Runtime/Numerics/ISignedNumber.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Mathematics; + +namespace Freya { + + public interface ISignedNumber : INumber { + /// Returns the sign of this number. Either -1, 0, or 1 + public R sign { get; } + } + + public static partial class mathfs { + /// + public static T sign( T v ) where T : ISignedNumber => v.sign; + + /// + public static int sign( this int i ) => Math.Sign( i ); + + /// + public static int2 sign( this int2 i ) => new(Math.Sign( i.x ), Math.Sign( i.y )); + + /// + public static int sign( this float i ) => Math.Sign( i ); + + /// + public static int sign( this double i ) => Math.Sign( i ); + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/ISignedNumber.cs.meta b/Runtime/Numerics/ISignedNumber.cs.meta new file mode 100644 index 0000000..fa71514 --- /dev/null +++ b/Runtime/Numerics/ISignedNumber.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c90009622ba8461a8ee07d285dbfe90c +timeCreated: 1775597835 \ No newline at end of file diff --git a/Runtime/Numerics/inth.cs b/Runtime/Numerics/inth.cs index d456ef0..f3a7ee2 100644 --- a/Runtime/Numerics/inth.cs +++ b/Runtime/Numerics/inth.cs @@ -11,7 +11,7 @@ namespace Freya { [Serializable] public struct inth : IComparable, IEquatable, - INumber, + INumber, ISignedNumber, IHalfNumber, IRoundable { @@ -22,10 +22,13 @@ namespace Freya { public inth fromInt( int intValue ) => this.h = intValue * 2; public bool isInteger => h % 2 == 0; + public bool isZero => h == 0; + public bool isOrthogonal => true; public int sign => Math.Sign( h ); public inth abs => new() { h = Math.Abs( h ) }; public inth max( inth other ) => this > other ? this : other; public inth min( inth other ) => this < other ? this : other; + public inth to( inth target ) => target - this; public static int zero => 0; public static inth half => new() { h = 1 }; public static int one => 1; diff --git a/Runtime/Numerics/rat.cs b/Runtime/Numerics/rat.cs index 0241efb..05cbb0a 100644 --- a/Runtime/Numerics/rat.cs +++ b/Runtime/Numerics/rat.cs @@ -10,7 +10,7 @@ namespace Freya { [Serializable] public struct rat : IComparable, IEquatable, - INumber, + INumber, ISignedNumber, IRoundable { /// The numerator of this number. Note: Directly modifying this value will not reduce the fraction @@ -66,6 +66,8 @@ public rat( int num, int den ) { public rat Reciprocal => new(d, n); public bool isInteger => d == 1; + public bool isZero => n == 0; + public bool isOrthogonal => true; /// Returns this number to the power of another integer pow @@ -99,6 +101,7 @@ public bool TryCastToIntHalf( out inth ih ) { public rat abs => new(n.Abs(), d); public rat max( rat other ) => this > other ? this : other; public rat min( rat other ) => this < other ? this : other; + public rat to( rat target ) => target - this; public int sign => MathF.Sign( n ); public int round( RoundingDirection rounding = RoundingDirection.ToEven ) => ( n < 0 == d < 0 ? ( n + d / 2 ) / d : ( n - d / 2 ) / d ); // todo: work out which rounding method this is public int floorToward0 => n < 0 ? ceil : floor; From 1203c728063af60333a2b79a36c3fa958143f0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Apr 2026 04:24:21 +0200 Subject: [PATCH 286/301] added rat2 & inth2, along with IComplex/IDotProduct/IWedgeProduct/IQuadrant2D and vector interfaces --- Runtime/Numerics/IComplex.cs | 55 +++++++ Runtime/Numerics/IComplex.cs.meta | 3 + Runtime/Numerics/IDotProduct.cs | 37 +++++ Runtime/Numerics/IDotProduct.cs.meta | 3 + Runtime/Numerics/IQuadrant2D.cs | 118 ++++++++++++++ Runtime/Numerics/IQuadrant2D.cs.meta | 3 + Runtime/Numerics/IVec.cs | 77 +--------- Runtime/Numerics/IVec1.cs | 129 ++++++++++++++++ Runtime/Numerics/IVec1.cs.meta | 3 + Runtime/Numerics/IVec2.cs | 196 ++++++++++++++++++++++++ Runtime/Numerics/IVec2.cs.meta | 3 + Runtime/Numerics/IVecComponents.cs | 187 ++++++++++++++++++++++ Runtime/Numerics/IVecComponents.cs.meta | 3 + Runtime/Numerics/IWedgeProduct.cs | 35 +++++ Runtime/Numerics/IWedgeProduct.cs.meta | 3 + Runtime/Numerics/inth2.cs | 8 +- Runtime/Numerics/mathfs.cs | 107 +++++++++++++ Runtime/Numerics/mathfs.cs.meta | 3 + Runtime/Numerics/rat2.cs | 9 +- 19 files changed, 904 insertions(+), 78 deletions(-) create mode 100644 Runtime/Numerics/IComplex.cs create mode 100644 Runtime/Numerics/IComplex.cs.meta create mode 100644 Runtime/Numerics/IDotProduct.cs create mode 100644 Runtime/Numerics/IDotProduct.cs.meta create mode 100644 Runtime/Numerics/IQuadrant2D.cs create mode 100644 Runtime/Numerics/IQuadrant2D.cs.meta create mode 100644 Runtime/Numerics/IVec1.cs create mode 100644 Runtime/Numerics/IVec1.cs.meta create mode 100644 Runtime/Numerics/IVec2.cs create mode 100644 Runtime/Numerics/IVec2.cs.meta create mode 100644 Runtime/Numerics/IVecComponents.cs create mode 100644 Runtime/Numerics/IVecComponents.cs.meta create mode 100644 Runtime/Numerics/IWedgeProduct.cs create mode 100644 Runtime/Numerics/IWedgeProduct.cs.meta create mode 100644 Runtime/Numerics/mathfs.cs create mode 100644 Runtime/Numerics/mathfs.cs.meta diff --git a/Runtime/Numerics/IComplex.cs b/Runtime/Numerics/IComplex.cs new file mode 100644 index 0000000..25b83f9 --- /dev/null +++ b/Runtime/Numerics/IComplex.cs @@ -0,0 +1,55 @@ +using Unity.Mathematics; + +namespace Freya { + + /// Objects that can be treated like complex numbers + public interface IComplex { + /// Multiplies as if they were complex numbers. The resulting vector is "rotated" by the other, and scaled by its magnitude. + /// Note that this operation does not use any trigonometry or square roots, it's very cheap to use! + public M complexMul( V other ); + + /// The complex conjugate of this vector, if treated as a complex number. Which, in english, just means it negates the y component + public V complexConj { get; } + } + + public static partial class mathfs { + /// + public static M complexMul( V a, V b ) where V : IComplex => a.complexMul( b ); + + /// + public static rat2 complexMul( rat2 a, rat2 b ) => a.complexMul( b ); + + /// + public static rat2 complexMul( inth2 a, inth2 b ) => a.complexMul( b ); + + /// + public static int2 complexMul( this int2 a, int2 b ) => new(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); + + /// + public static float2 complexMul( this float2 a, float2 b ) => new(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); + + /// + public static double2 complexMul( this double2 a, double2 b ) => new(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); + + + /// + public static V complexConj( V v ) where V : IComplex => v.complexConj; + + /// + public static rat2 complexConj( rat2 v ) => v.complexConj; + + /// + public static inth2 complexConj( inth2 v ) => v.complexConj; + + /// + public static int2 complexConj( this int2 v ) => new(v.x, -v.y); + + /// + public static float2 complexConj( this float2 v ) => new(v.x, -v.y); + + /// + public static double2 complexConj( this double2 v ) => new(v.x, -v.y); + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IComplex.cs.meta b/Runtime/Numerics/IComplex.cs.meta new file mode 100644 index 0000000..c3e03b7 --- /dev/null +++ b/Runtime/Numerics/IComplex.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: de4c219a2a3e4371855d4d0082686edc +timeCreated: 1775599900 \ No newline at end of file diff --git a/Runtime/Numerics/IDotProduct.cs b/Runtime/Numerics/IDotProduct.cs new file mode 100644 index 0000000..37ede22 --- /dev/null +++ b/Runtime/Numerics/IDotProduct.cs @@ -0,0 +1,37 @@ +using Unity.Mathematics; + +namespace Freya { + + /// Objects that implement a dot product + public interface IDotProduct { + /// The dot product between two vectors. This is the sum of the product of each respective component + public D dot( B other ); + } + + public static partial class mathfs { + /// + public static D dot( A a, B b ) where A : IDotProduct => a.dot( b ); + + /// + public static rat dot( rat2 a, rat2 b ) => a.dot( b ); + + /// + public static rat dot( inth2 a, inth2 b ) => a.dot( b ); + + /// + public static rat dot( rat2 a, int2 b ) => a.dot( b ); + + /// + public static rat dot( int2 a, rat2 b ) => b.dot( b ); + + /// + public static int dot( this int2 a, int2 b ) => math.dot( a, b ); + + /// + public static float dot( this float2 a, float2 b ) => math.dot( a, b ); + + /// + public static double dot( this double2 a, double2 b ) => math.dot( a, b ); + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IDotProduct.cs.meta b/Runtime/Numerics/IDotProduct.cs.meta new file mode 100644 index 0000000..983b1d8 --- /dev/null +++ b/Runtime/Numerics/IDotProduct.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7a2af38b25b54b9faa05712669f67d69 +timeCreated: 1775599886 \ No newline at end of file diff --git a/Runtime/Numerics/IQuadrant2D.cs b/Runtime/Numerics/IQuadrant2D.cs new file mode 100644 index 0000000..2796aca --- /dev/null +++ b/Runtime/Numerics/IQuadrant2D.cs @@ -0,0 +1,118 @@ +using Unity.Mathematics; + +namespace Freya { + + /// Objects that reside within four quadrants in 2D + public interface IQuadrant2D { + /// The index of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, + /// increasing in the positive rotation direction/counter-clockwise. + /// Ambiguous positions pick the quadrant in the positive rotation direction.

+ /// Quadrant layout: + /// + /// 1 + /// 0 + /// + /// + /// 2 + /// 3 + ///
+ public int quadrant { get; } + /// The signed of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, + /// increasing in the positive rotation direction/counter-clockwise. + /// Ambiguous positions pick the quadrant in the positive rotation direction.

+ /// Quadrant layout: + /// + /// 1 + /// 0 + /// + /// + /// -2 + /// -1 + ///
+ public int signedQuadrant { get; } + /// The X-axis of the basis within the current quadrant. + /// Ambiguous positions pick the quadrant in the positive rotation direction. Zero-vectors return (1,0) + public int2 quadrantBasisX { get; } + /// Returns the two basis vectors of the quadrant that contains this position. + /// Ambiguous positions pick the quadrant in the positive rotation direction + public (int2 x, int2 y) quadrantBasis { get; } + } + + public static partial class mathfs { + /// + public static int quadrant( V v ) where V : IQuadrant2D => v.quadrant; + + /// + public static int quadrant( rat2 v ) => v.quadrant; + + /// + public static int quadrant( inth2 v ) => v.quadrant; + + /// + public static int quadrant( this int2 v ) => + v.y switch { + > 00 when v.x <= 0 => 1, + <= 0 when v.x < 00 => 2, + < 00 when v.x >= 0 => 3, + _ => 0 + }; + + /// + public static int quadrant( this float2 v ) => + v.y switch { + > 00 when v.x <= 0 => 1, + <= 0 when v.x < 00 => 2, + < 00 when v.x >= 0 => 3, + _ => 0 + }; + + /// + public static int quadrant( this double2 v ) => + v.y switch { + > 00 when v.x <= 0 => 1, + <= 0 when v.x < 00 => 2, + < 00 when v.x >= 0 => 3, + _ => 0 + }; + + + /// + public static int2 quadrantBasisX( V v ) where V : IQuadrant2D => v.quadrantBasisX; + + /// + public static int2 quadrantBasisX( rat2 v ) => v.quadrantBasisX; + + /// + public static int2 quadrantBasisX( inth2 v ) => v.quadrantBasisX; + + /// + public static int2 quadrantBasisX( this int2 v ) => quadrantToBasisX( v.quadrant() ); + + /// + public static int2 quadrantBasisX( this float2 v ) => quadrantToBasisX( v.quadrant() ); + + /// + public static int2 quadrantBasisX( this double2 v ) => quadrantToBasisX( v.quadrant() ); + + + /// + public static (int2 x, int2 y) quadrantBasis( V v ) where V : IQuadrant2D => v.quadrantBasis; + + /// + public static (int2 x, int2 y) quadrantBasis( rat2 v ) => v.quadrantBasis; + + /// + public static (int2 x, int2 y) quadrantBasis( inth2 v ) => v.quadrantBasis; + + /// + public static (int2 x, int2 y) quadrantBasis( this int2 v ) => quadrantToBasis( v.quadrant() ); + + /// + public static (int2 x, int2 y) quadrantBasis( this float2 v ) => quadrantToBasis( v.quadrant() ); + + /// + public static (int2 x, int2 y) quadrantBasis( this double2 v ) => quadrantToBasis( v.quadrant() ); + } + + +} \ No newline at end of file diff --git a/Runtime/Numerics/IQuadrant2D.cs.meta b/Runtime/Numerics/IQuadrant2D.cs.meta new file mode 100644 index 0000000..ee467a6 --- /dev/null +++ b/Runtime/Numerics/IQuadrant2D.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9adcc573f2d54e61bc55661ce9250038 +timeCreated: 1775599895 \ No newline at end of file diff --git a/Runtime/Numerics/IVec.cs b/Runtime/Numerics/IVec.cs index 8cdfdb4..abaf156 100644 --- a/Runtime/Numerics/IVec.cs +++ b/Runtime/Numerics/IVec.cs @@ -4,16 +4,8 @@ namespace Freya { - public interface IVec : IDotProduct, IWedgeProduct { - /// Returns a component of this vector by index - public C this[ int i ] { get; } - /// Returns whether this lies flat along at least one axis - public bool isOrthogonal { get; } - /// Returns whether this vector is the zero vector - public bool isZero { get; } - /// The vector from this point to the target. Equivalent to target - this - public V to( V target ); + public interface IVec : IDotProduct, IWedgeProduct, IVecComponents { /// The squared magnitude of this vector public D magSq { get; } @@ -27,12 +19,6 @@ public interface IVec : IDotProduct, IWedgeProduct { /// This means the magnitude of (1,1) is 2, the magnitude of (2,2) is 4
public C magTaxicab { get; } - /// The minimum of the components of this vector - public C cmin { get; } - /// The maximum of the components of this vector - public C cmax { get; } - /// The sum of the components of this vector - public C csum { get; } /// Returns whether this point is in front of or behind a plane. ///
    @@ -46,66 +32,9 @@ public interface IVec : IDotProduct, IWedgeProduct { public int pointSideOfPlane( V planePos, V planeNormal ); } - /// Objects that implement a dot product - public interface IDotProduct { - /// The dot product between two vectors. This is the sum of the product of each respective component - public D dot( V other ); + public static partial class mathfs { + // todo } - /// Objects that implement the wedge product - public interface IWedgeProduct { - /// The wedge product between two vectors. This is a generalized form of the cross product. - ///
    • In 2D, this returns a scalar, and is sometimes called the perpendicular dot product.
    • - ///
    • In 3D, this returns a vector, and is effectively the same as the cross product (technically it's a bivector but whatever)
    • - ///
    - public W wedge( V other ); - } - - /// Objects that reside within four quadrants in 2D - public interface IQuadrant2D { - /// The index of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, - /// increasing in the positive rotation direction/counter-clockwise. - /// Ambiguous positions pick the quadrant in the positive rotation direction. - public int quadrant { get; } - /// The signed of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, - /// increasing in the positive rotation direction/counter-clockwise. - /// Ambiguous positions pick the quadrant in the positive rotation direction. - public int signedQuadrant { get; } - /// The X-axis of the basis within the current quadrant. - /// Ambiguous positions pick the quadrant in the positive rotation direction. Zero-vectors return (1,0) - public int2 quadrantBasisX { get; } - /// Returns the two basis vectors of the quadrant that contains this position. - /// Ambiguous positions pick the quadrant in the positive rotation direction - public (int2 x, int2 y) quadrantBasis { get; } - } - - /// Objects that can be treated like complex numbers - public interface IComplex { - /// Multiplies as if they were complex numbers. The resulting vector is "rotated" by the other, and scaled by its magnitude. - /// Note that this operation does not use any trigonometry or square roots, it's very cheap to use! - public M complexMul( V other ); - - /// The complex conjugate of this vector, if treated as a complex number. Which, in english, just means it negates the y component - public V complexConj { get; } - } - - public interface IVec2 : IVec, IQuadrant2D, IComplex { - /// The X component of this vector - public C X { get; } - /// The Y component of this vector - public C Y { get; } - - - /// Rotates this vector in the positive rotation direction by 90 degrees. This is usually a counter-clockwise/left turn" - public V rot90 { get; } - /// Rotates this vector in the positive rotation direction by 90 degrees. This is usually a clockwise/right turn" - public V rotNeg90 { get; } - /// Rotates this vector by 180 degrees. Equivalent to negating this vector - public V rot180 { get; } - - - // public V rot45chebyshev { get; } - // public V FromVector2( Vector2 v ); // should only happen for coarse things like inthalf2 and int. rational ones are messy here - } } \ No newline at end of file diff --git a/Runtime/Numerics/IVec1.cs b/Runtime/Numerics/IVec1.cs new file mode 100644 index 0000000..c40bc52 --- /dev/null +++ b/Runtime/Numerics/IVec1.cs @@ -0,0 +1,129 @@ +using Unity.Mathematics; + +namespace Freya { + + public interface IVec1 : IVec { + /// The X component of this vector + public C X { get; } + /// This vector with a reversed X component + public V flipX { get; } + /// This vector with a zeroed-out X component + public V zeroX { get; } + } + + // X component boilerplate + public static partial class mathfs { + + /// + public static C X( V v ) where V : IVec1 => v.X; + + /// + public static rat X( rat2 v ) => v.X; + + /// + public static inth X( inth2 v ) => v.X; + + /// + public static int X( this int2 v ) => v.x; + + /// + public static float X( this float2 v ) => v.x; + + /// + public static double X( this double2 v ) => v.x; + + /// + public static int X( this int3 v ) => v.x; + + /// + public static float X( this float3 v ) => v.x; + + /// + public static double X( this double3 v ) => v.x; + + /// + public static int X( this int4 v ) => v.x; + + /// + public static float X( this float4 v ) => v.x; + + /// + public static double X( this double4 v ) => v.x; + + + /// + public static V flipX( V v ) where V : IVec1 => v.flipX; + + /// + public static rat2 flipX( rat2 v ) => v.flipX; + + /// + public static inth2 flipX( inth2 v ) => v.flipX; + + /// + public static int2 flipX( this int2 v ) => new(-v.x, v.y); + + /// + public static float2 flipX( this float2 v ) => new(-v.x, v.y); + + /// + public static double2 flipX( this double2 v ) => new(-v.x, v.y); + + /// + public static int3 flipX( this int3 v ) => new(-v.x, v.y, v.z); + + /// + public static float3 flipX( this float3 v ) => new(-v.x, v.y, v.z); + + /// + public static double3 flipX( this double3 v ) => new(-v.x, v.y, v.z); + + /// + public static int4 flipX( this int4 v ) => new(-v.x, v.y, v.z, v.w); + + /// + public static float4 flipX( this float4 v ) => new(-v.x, v.y, v.z, v.w); + + /// + public static double4 flipX( this double4 v ) => new(-v.x, v.y, v.z, v.w); + + + /// + public static V zeroX( V v ) where V : IVec1 => v.zeroX; + + /// + public static rat2 zeroX( rat2 v ) => v.zeroX; + + /// + public static inth2 zeroX( inth2 v ) => v.zeroX; + + /// + public static int2 zeroX( this int2 v ) => new(0, v.y); + + /// + public static float2 zeroX( this float2 v ) => new(0, v.y); + + /// + public static double2 zeroX( this double2 v ) => new(0, v.y); + + /// + public static int3 zeroX( this int3 v ) => new(0, v.y, v.z); + + /// + public static float3 zeroX( this float3 v ) => new(0, v.y, v.z); + + /// + public static double3 zeroX( this double3 v ) => new(0, v.y, v.z); + + /// + public static int4 zeroX( this int4 v ) => new(0, v.y, v.z, v.w); + + /// + public static float4 zeroX( this float4 v ) => new(0, v.y, v.z, v.w); + + /// + public static double4 zeroX( this double4 v ) => new(0, v.y, v.z, v.w); + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IVec1.cs.meta b/Runtime/Numerics/IVec1.cs.meta new file mode 100644 index 0000000..af12661 --- /dev/null +++ b/Runtime/Numerics/IVec1.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 960c1180e5bc4e7c8c3f33cd22fb51f6 +timeCreated: 1775605803 \ No newline at end of file diff --git a/Runtime/Numerics/IVec2.cs b/Runtime/Numerics/IVec2.cs new file mode 100644 index 0000000..219d320 --- /dev/null +++ b/Runtime/Numerics/IVec2.cs @@ -0,0 +1,196 @@ +using Unity.Mathematics; + +namespace Freya { + + public interface IVec2 : IVec1, IQuadrant2D, IComplex { + /// The Y component of this vector + public C Y { get; } + /// This vector with a reversed Y component + public V flipY { get; } + /// This vector with a zeroed-out Y component + public V zeroY { get; } + + /// Rotates this vector in the positive rotation direction by 90 degrees. This is usually a counter-clockwise/left turn + public V rot90 { get; } + /// Rotates this vector in the negative rotation direction by 90 degrees. This is usually a clockwise/right turn + public V rotNeg90 { get; } + /// Rotates this vector by 180 degrees. Equivalent to negating this vector + public V rot180 { get; } + + // public V rot45chebyshev { get; } + // public V FromVector2( Vector2 v ); // should only happen for coarse things like inthalf2 and int. rational ones are messy here + } + + public static partial class mathfs { + /// + public static V rot90( V v ) where V : IVec2 => v.rot90; + + /// + public static rat2 rot90( rat2 v ) => v.rot90; + + /// + public static inth2 rot90( inth2 v ) => v.rot90; + + /// + public static int2 rot90( this int2 v ) => new(-v.y, v.x); + + /// + public static float2 rot90( this float2 v ) => new(-v.y, v.x); + + /// + public static double2 rot90( this double2 v ) => new(-v.y, v.x); + + + /// + public static V rotNeg90( V v ) where V : IVec2 => v.rotNeg90; + + /// + public static rat2 rotNeg90( rat2 v ) => v.rotNeg90; + + /// + public static inth2 rotNeg90( inth2 v ) => v.rotNeg90; + + /// + public static int2 rotNeg90( this int2 v ) => new(v.y, -v.x); + + /// + public static float2 rotNeg90( this float2 v ) => new(v.y, -v.x); + + /// + public static double2 rotNeg90( this double2 v ) => new(v.y, -v.x); + + + /// + public static V rot180( V v ) where V : IVec2 => v.rot180; + + /// + public static rat2 rot180( rat2 v ) => -v; + + /// + public static inth2 rot180( inth2 v ) => -v; + + /// + public static int2 rot180( this int2 v ) => -v; + + /// + public static float2 rot180( this float2 v ) => -v; + + /// + public static double2 rot180( this double2 v ) => -v; + + } + + // Y component boilerplate + public static partial class mathfs { + /// + public static C Y( V v ) where V : IVec2 => v.Y; + + /// + public static rat Y( rat2 v ) => v.Y; + + /// + public static inth Y( inth2 v ) => v.Y; + + /// + public static int Y( this int2 v ) => v.y; + + /// + public static float Y( this float2 v ) => v.y; + + /// + public static double Y( this double2 v ) => v.y; + + /// + public static int Y( this int3 v ) => v.y; + + /// + public static float Y( this float3 v ) => v.y; + + /// + public static double Y( this double3 v ) => v.y; + + /// + public static int Y( this int4 v ) => v.y; + + /// + public static float Y( this float4 v ) => v.y; + + /// + public static double Y( this double4 v ) => v.y; + + + /// + public static V flipY( V v ) where V : IVec2 => v.flipY; + + /// + public static rat2 flipY( rat2 v ) => v.flipY; + + /// + public static inth2 flipY( inth2 v ) => v.flipY; + + /// + public static int2 flipY( this int2 v ) => new(v.x, -v.y); + + /// + public static float2 flipY( this float2 v ) => new(v.x, -v.y); + + /// + public static double2 flipY( this double2 v ) => new(v.x, -v.y); + + /// + public static int3 flipY( this int3 v ) => new(v.x, -v.y, v.z); + + /// + public static float3 flipY( this float3 v ) => new(v.x, -v.y, v.z); + + /// + public static double3 flipY( this double3 v ) => new(v.x, -v.y, v.z); + + /// + public static int4 flipY( this int4 v ) => new(v.x, -v.y, v.z, v.w); + + /// + public static float4 flipY( this float4 v ) => new(v.x, -v.y, v.z, v.w); + + /// + public static double4 flipY( this double4 v ) => new(v.x, -v.y, v.z, v.w); + + + /// + public static V zeroY( V v ) where V : IVec2 => v.zeroY; + + /// + public static rat2 zeroY( rat2 v ) => v.zeroY; + + /// + public static inth2 zeroY( inth2 v ) => v.zeroY; + + /// + public static int2 zeroY( this int2 v ) => new(v.x, 0); + + /// + public static float2 zeroY( this float2 v ) => new(v.x, 0); + + /// + public static double2 zeroY( this double2 v ) => new(v.x, 0); + + /// + public static int3 zeroY( this int3 v ) => new(v.x, 0, v.z); + + /// + public static float3 zeroY( this float3 v ) => new(v.x, 0, v.z); + + /// + public static double3 zeroY( this double3 v ) => new(v.x, 0, v.z); + + /// + public static int4 zeroY( this int4 v ) => new(v.x, 0, v.z, v.w); + + /// + public static float4 zeroY( this float4 v ) => new(v.x, 0, v.z, v.w); + + /// + public static double4 zeroY( this double4 v ) => new(v.x, 0, v.z, v.w); + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IVec2.cs.meta b/Runtime/Numerics/IVec2.cs.meta new file mode 100644 index 0000000..b7c988b --- /dev/null +++ b/Runtime/Numerics/IVec2.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1e2bcbef65e14075ad5e39a1d176b2ed +timeCreated: 1775605827 \ No newline at end of file diff --git a/Runtime/Numerics/IVecComponents.cs b/Runtime/Numerics/IVecComponents.cs new file mode 100644 index 0000000..11b7e31 --- /dev/null +++ b/Runtime/Numerics/IVecComponents.cs @@ -0,0 +1,187 @@ +using Unity.Mathematics; + +namespace Freya { + + public interface IVecComponents { + // todo: coooould make a C Component( V elem, int iAxis ) + /// Returns a component of this vector by index + public C this[ int i ] { get; } + /// The minimum of the components of this vector + public C cmin { get; } + /// The maximum of the components of this vector + public C cmax { get; } + /// The sum of the components of this vector + public C csum { get; } + } + + public static partial class mathfs { + + /// + public static C cmin( C v ) where C : IVecComponents => v.cmin; + + /// + public static rat cmin( rat v ) => v; + + /// + public static rat cmin( rat2 v ) => v.cmin; + + /// + public static inth cmin( inth v ) => v; + + /// + public static inth cmin( inth2 v ) => v.cmin; + + /// + public static int cmin( this int v ) => v; + + /// + public static int cmin( this int2 v ) => math.cmin( v ); + + /// + public static int cmin( this int3 v ) => math.cmin( v ); + + /// + public static int cmin( this int4 v ) => math.cmin( v ); + + /// + public static float cmin( this float v ) => v; + + /// + public static float cmin( this float2 v ) => math.cmin( v ); + + /// + public static float cmin( this float3 v ) => math.cmin( v ); + + /// + public static float cmin( this float4 v ) => math.cmin( v ); + + /// + public static double cmin( this double v ) => v; + + /// + public static double cmin( this double2 v ) => math.cmin( v ); + + /// + public static double cmin( this double3 v ) => math.cmin( v ); + + /// + public static double cmin( this double4 v ) => math.cmin( v ); + + + /// + public static C cmax( C v ) where C : IVecComponents => v.cmax; + + /// + public static rat cmax( rat v ) => v; + + /// + public static rat cmax( rat2 v ) => v.cmax; + + /// + public static inth cmax( inth v ) => v; + + /// + public static inth cmax( inth2 v ) => v.cmax; + + /// + public static int cmax( this int v ) => v; + + /// + public static int cmax( this int2 v ) => math.cmax( v ); + + /// + public static int cmax( this int3 v ) => math.cmax( v ); + + /// + public static int cmax( this int4 v ) => math.cmax( v ); + + /// + public static float cmax( this float v ) => v; + + /// + public static float cmax( this float2 v ) => math.cmax( v ); + + /// + public static float cmax( this float3 v ) => math.cmax( v ); + + /// + public static float cmax( this float4 v ) => math.cmax( v ); + + /// + public static double cmax( this double v ) => v; + + /// + public static double cmax( this double2 v ) => math.cmax( v ); + + /// + public static double cmax( this double3 v ) => math.cmax( v ); + + /// + public static double cmax( this double4 v ) => math.cmax( v ); + + + /// + public static C csum( C v ) where C : IVecComponents => v.csum; + + /// + public static rat csum( rat v ) => v; + + /// + public static rat csum( rat2 v ) => v.csum; + + /// + public static inth csum( inth v ) => v; + + /// + public static inth csum( inth2 v ) => v.csum; + + /// + public static int csum( this int v ) => v; + + /// + public static int csum( this int2 v ) => math.csum( v ); + + /// + public static int csum( this int3 v ) => math.csum( v ); + + /// + public static int csum( this int4 v ) => math.csum( v ); + + /// + public static float csum( this float v ) => v; + + /// + public static float csum( this float2 v ) => math.csum( v ); + + /// + public static float csum( this float3 v ) => math.csum( v ); + + /// + public static float csum( this float4 v ) => math.csum( v ); + + /// + public static double csum( this double v ) => v; + + /// + public static double csum( this double2 v ) => math.csum( v ); + + /// + public static double csum( this double3 v ) => math.csum( v ); + + /// + public static double csum( this double4 v ) => math.csum( v ); + + /// + public static int csum( this bool b ) => b ? 1 : 0; + + /// + public static int csum( this bool2 b ) => math.csum( (int2)b ); + + /// + public static int csum( this bool3 b ) => math.csum( (int3)b ); + + /// + public static int csum( this bool4 b ) => math.csum( (int4)b ); + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IVecComponents.cs.meta b/Runtime/Numerics/IVecComponents.cs.meta new file mode 100644 index 0000000..37d7d80 --- /dev/null +++ b/Runtime/Numerics/IVecComponents.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 57a82d6700e243a48effc9a2786a4620 +timeCreated: 1775613169 \ No newline at end of file diff --git a/Runtime/Numerics/IWedgeProduct.cs b/Runtime/Numerics/IWedgeProduct.cs new file mode 100644 index 0000000..7f348bb --- /dev/null +++ b/Runtime/Numerics/IWedgeProduct.cs @@ -0,0 +1,35 @@ +using Unity.Mathematics; + +namespace Freya { + + /// Objects that implement the wedge product + public interface IWedgeProduct { + /// The wedge product between two vectors. This is a generalized form of the cross product. + ///
    • In 2D, this returns a scalar, and is sometimes called the perpendicular dot product.
    • + ///
    • In 3D, this returns a vector, and is effectively the same as the cross product + /// (technically it's a bivector but whatever)
    • + ///
    + public W wedge( V other ); + } + + public static partial class mathfs { + /// + public static W wedge( V a, V b ) where V : IWedgeProduct => a.wedge( b ); + + /// + public static rat wedge( rat2 a, rat2 b ) => a.wedge( b ); + + /// + public static rat wedge( inth2 a, inth2 b ) => a.wedge( b ); + + /// + public static int wedge( this int2 a, int2 b ) => a.x * b.y - a.y * b.x; + + /// + public static float wedge( this float2 a, float2 b ) => a.x * b.y - a.y * b.x; + + /// + public static double wedge( this double2 a, double2 b ) => a.x * b.y - a.y * b.x; + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IWedgeProduct.cs.meta b/Runtime/Numerics/IWedgeProduct.cs.meta new file mode 100644 index 0000000..d992f57 --- /dev/null +++ b/Runtime/Numerics/IWedgeProduct.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 37b54846b53c434ca8491a5454ce6eaf +timeCreated: 1775599891 \ No newline at end of file diff --git a/Runtime/Numerics/inth2.cs b/Runtime/Numerics/inth2.cs index b6e6011..fbacaf9 100644 --- a/Runtime/Numerics/inth2.cs +++ b/Runtime/Numerics/inth2.cs @@ -11,7 +11,7 @@ namespace Freya { [Serializable] public struct inth2 : IEquatable, IVec2, - INumber, + INumber, ISignedNumber, IHalfNumber, IRoundable { @@ -23,8 +23,12 @@ namespace Freya { public inth X => x; public inth Y => y; + public inth2 zeroX => new(0, y); + public inth2 zeroY => new(x, 0); + public inth2 flipX => new(-x, y); + public inth2 flipY => new(x, -y); public inth this[ int i ] => i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( i.ToString() ) }; - public bool isOrthogonal => abs.cmin == 0; + public bool isOrthogonal => ( ceilAwayFrom0 > 0 ).csum() <= 1; public bool isZero => x == 0 && y == 0; public inth2( inth x, inth y ) => ( this.x, this.y ) = ( x, y ); diff --git a/Runtime/Numerics/mathfs.cs b/Runtime/Numerics/mathfs.cs new file mode 100644 index 0000000..e26bf0f --- /dev/null +++ b/Runtime/Numerics/mathfs.cs @@ -0,0 +1,107 @@ +using Unity.Mathematics; +using UnityEngine; + +namespace Freya { + + + /// Static functions appropriate for using static + public static partial class mathfs { + + // todo: this file is under construction + + // global functions? + public static int2 quadrantToBasisX( int i ) => + i switch { + 1 => new int2( 00, +1 ), + 2 => new int2( -1, 00 ), + 3 => new int2( 00, -1 ), + _ => new int2( +1, 00 ) + }; + + public static (int2 x, int2 y) quadrantToBasis( int i ) => + i switch { + 1 => ( new int2( 00, +1 ), new int2( -1, 00 ) ), + 2 => ( new int2( -1, 00 ), new int2( 00, -1 ) ), + 3 => ( new int2( 00, -1 ), new int2( +1, 00 ) ), + _ => ( new int2( +1, 00 ), new int2( 00, +1 ) ) + }; + + + // todo: sort these: + public static rat round( rat r, rat interval, RoundingDirection rounding = RoundingDirection.ToEven ) => ( r / interval ).round( rounding ) * interval; + public static rat2 round( rat2 r, rat interval, RoundingDirection rounding = RoundingDirection.ToEven ) => new(round( r.x, interval ), round( r.y, interval )); + public static rat2 round( rat2 r, rat2 intervals, RoundingDirection rounding = RoundingDirection.ToEven ) => new(round( r.x, intervals.x ), round( r.y, intervals.y )); + + + // UNSORTED: + + public static Rect expandFromCenter( this Rect r, float expansionPerSide ) { + rat2 g = default; + Debug.Log( complexConj( g ) ); + r.xMin -= expansionPerSide; + r.yMin -= expansionPerSide; + r.xMax += expansionPerSide; + r.yMax += expansionPerSide; + return r; + } + + /// Unsigned shortest delta between a and b under modulo mod + public static int modDelta( int a, int b, int mod ) { + a = a.Mod( mod ); + b = b.Mod( mod ); + int delta = math.max( a, b ) - math.min( a, b ); // delta is guaranteed to be positive here + // find shortest direction: + return Mathf.Min( mod - delta, delta ); + } + + /// The signed number of quadrants travered/rotated through in going from a and b + public static int quadrantDelta( int2 a, int2 b ) => a.wedge( b ).sign() * modDelta( a.quadrant(), b.quadrant(), 4 ); + + /// + public static int quadrantDelta( inth2 a, inth2 b ) => a.wedge( b ).sign * modDelta( a.quadrant, b.quadrant, 4 ); + + /// + public static int quadrantDelta( rat2 a, rat2 b ) => a.wedge( b ).sign * modDelta( a.quadrant, b.quadrant, 4 ); + + + public static int pointSideOfPlane( this inth2 p, inth2 planePos, inth2 planeNormal ) => p.pointSideOfPlane( planePos, planeNormal ); + public static int pointSideOfPlane( this int2 p, int2 planePos, int2 planeNormal ) => math.sign( math.dot( p - planePos, planeNormal ) ); + + public static inth divideBy2( this int p ) => new() { h = p }; + public static inth2 divideBy2( this int2 p ) => new(p.x.divideBy2(), p.y.divideBy2()); + + + public static int signedQuadrant( this int2 v ) => v.quadrant() switch { 1 => +1, 2 => -2, 3 => -1, _ => 00 }; + + public static int magChebyshev( this int2 v ) => math.max( math.abs( v.x ), math.abs( v.y ) ); + public static float magChebyshev( this float2 v ) => math.max( math.abs( v.x ), math.abs( v.y ) ); + + public static int2 rot45chebyshev( this int2 v ) { + int m = v.magChebyshev(); + return math.clamp( v + v.rot90(), new int2( -m, -m ), new int2( m, m ) ); + } + + public static float projectionTValue( float2 v, float2 n ) => math.dot( v, n ) / math.dot( n, n ); + + + public static rat projectionTValue( rat2 v, rat2 n ) => dot( v, n ) / dot( n, n ); + public static rat projectionTValue( rat2 v, int2 n ) => dot( v, n ) / Mathfs.dot( n, n ); + + public static rat projectionTValuePerp( rat2 v, rat2 n ) => dot( v, v ) / dot( v, n ); + + public static float2 projectToNormal( float2 v, float2 n ) => n * projectionTValue( v, n ); + public static rat2 projectToNormal( rat2 v, rat2 n ) => n * projectionTValue( v, n ); + public static rat2 projectToNormal( rat2 v, int2 n ) => n * projectionTValue( v, n ); + + public static rat2 projectToNormalPerp( rat2 v, rat2 n ) => n * projectionTValuePerp( v, n ); + + public static rat2 lerp( rat2 a, rat2 b, rat2 t ) => new(lerp( a.x, b.x, t.x ), lerp( a.y, b.y, t.y )); + public static rat2 lerp( rat2 a, rat2 b, rat t ) => a + t * ( b - a ); + public static rat lerp( rat a, rat b, rat t ) => a + t * ( b - a ); + + public static rat inverseLerp( rat a, rat b, rat v ) => ( v - a ) / ( b - a ); + + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/mathfs.cs.meta b/Runtime/Numerics/mathfs.cs.meta new file mode 100644 index 0000000..8227ede --- /dev/null +++ b/Runtime/Numerics/mathfs.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 378e711f01ba400d8850de03d5e43de3 +timeCreated: 1775075911 \ No newline at end of file diff --git a/Runtime/Numerics/rat2.cs b/Runtime/Numerics/rat2.cs index 6f52118..3521cf6 100644 --- a/Runtime/Numerics/rat2.cs +++ b/Runtime/Numerics/rat2.cs @@ -11,14 +11,19 @@ namespace Freya { /// A 2D vector with rational components (ℚ² instead of ℝ²) [Serializable] public struct rat2 : IEquatable, IVec2, - INumber, + INumber, ISignedNumber, + IDotProduct, IRoundable { [SerializeField] public rat x; [SerializeField] public rat y; public rat this[ int i ] => i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( i.ToString() ) }; public rat X => x; public rat Y => y; + public rat2 zeroX => new(0, y); + public rat2 zeroY => new(x, 0); + public rat2 flipX => new(-x, y); + public rat2 flipY => new(x, -y); // public Rational2 rot45chebyshev => throw new NotImplementedException(); public static readonly rat2 zero = new(rat.zero, rat.zero); public static readonly rat2 half = new(rat.half, rat.half); @@ -36,7 +41,7 @@ public static rat2 FromVector2( Vector2 v, int snapStepsPerUnit = 2 ) { public bool isZero => math.all( N == new int2( 0, 0 ) ); public bool isInteger => math.all( D == new int2( 1, 1 ) ); - public bool isOrthogonal => abs.cmin == 0; + public bool isOrthogonal => ( ceilAwayFrom0 > 0 ).csum() <= 1; public bool IsDiagonal => x.abs == y.abs; // Chebyshev distances From fd3b0b347eb16f10a821a37e098b389a3bd15937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Apr 2026 04:27:43 +0200 Subject: [PATCH 287/301] cleanup --- Runtime/Numerics/mathfs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/Numerics/mathfs.cs b/Runtime/Numerics/mathfs.cs index e26bf0f..8911e7a 100644 --- a/Runtime/Numerics/mathfs.cs +++ b/Runtime/Numerics/mathfs.cs @@ -85,7 +85,7 @@ public static int2 rot45chebyshev( this int2 v ) { public static rat projectionTValue( rat2 v, rat2 n ) => dot( v, n ) / dot( n, n ); - public static rat projectionTValue( rat2 v, int2 n ) => dot( v, n ) / Mathfs.dot( n, n ); + public static rat projectionTValue( rat2 v, int2 n ) => dot( v, n ) / dot( n, n ); public static rat projectionTValuePerp( rat2 v, rat2 n ) => dot( v, v ) / dot( v, n ); From 8d1c471846681f7195d305d93a46cd9c8d4fbe79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Apr 2026 04:27:57 +0200 Subject: [PATCH 288/301] Added Pairs enumeration extension --- Runtime/Numerics/EnumerationExtensions.cs | 26 +++++++++++++++++++ .../Numerics/EnumerationExtensions.cs.meta | 3 +++ 2 files changed, 29 insertions(+) create mode 100644 Runtime/Numerics/EnumerationExtensions.cs create mode 100644 Runtime/Numerics/EnumerationExtensions.cs.meta diff --git a/Runtime/Numerics/EnumerationExtensions.cs b/Runtime/Numerics/EnumerationExtensions.cs new file mode 100644 index 0000000..20afa8b --- /dev/null +++ b/Runtime/Numerics/EnumerationExtensions.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Freya { + + public static class EnumerationExtensions { + + public static IEnumerable<(T a, T b)> Pairs( this IEnumerable items, bool loop ) { + bool hasFoundFirst = false; + T first = default; + T prev = default; + foreach( T item in items ) { + if( hasFoundFirst == false ) { + hasFoundFirst = true; + first = item; + } else { + yield return ( prev, item ); + } + prev = item; + } + if( loop && hasFoundFirst ) + yield return ( prev, first ); + } + + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/EnumerationExtensions.cs.meta b/Runtime/Numerics/EnumerationExtensions.cs.meta new file mode 100644 index 0000000..adadfdc --- /dev/null +++ b/Runtime/Numerics/EnumerationExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 61cb471c2d994254a13184e13221a399 +timeCreated: 1775338660 \ No newline at end of file From 2caaa21af8c8fdf340197c40979e37e144bedc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Wed, 8 Apr 2026 19:52:12 +0200 Subject: [PATCH 289/301] codegen cleanup --- Editor/Codegen.meta | 8 + Editor/{ => Codegen}/CodeGenerator.cs | 0 Editor/{ => Codegen}/CodeGenerator.cs.meta | 0 Editor/Codegen/ElemType.cs | 11 + Editor/Codegen/ElemType.cs.meta | 3 + Editor/Codegen/MathfsCodegen.cs | 24 ++ Editor/{ => Codegen}/MathfsCodegen.cs.meta | 0 Editor/Codegen/MatrixCodegen.cs | 119 ++++++++++ Editor/Codegen/MatrixCodegen.cs.meta | 3 + .../SplineCodegen.cs} | 208 ++++-------------- Editor/Codegen/SplineCodegen.cs.meta | 3 + Editor/Codegen/SplineType.cs | 29 +++ Editor/Codegen/SplineType.cs.meta | 3 + 13 files changed, 242 insertions(+), 169 deletions(-) create mode 100644 Editor/Codegen.meta rename Editor/{ => Codegen}/CodeGenerator.cs (100%) rename Editor/{ => Codegen}/CodeGenerator.cs.meta (100%) create mode 100644 Editor/Codegen/ElemType.cs create mode 100644 Editor/Codegen/ElemType.cs.meta create mode 100644 Editor/Codegen/MathfsCodegen.cs rename Editor/{ => Codegen}/MathfsCodegen.cs.meta (100%) create mode 100644 Editor/Codegen/MatrixCodegen.cs create mode 100644 Editor/Codegen/MatrixCodegen.cs.meta rename Editor/{MathfsCodegen.cs => Codegen/SplineCodegen.cs} (73%) create mode 100644 Editor/Codegen/SplineCodegen.cs.meta create mode 100644 Editor/Codegen/SplineType.cs create mode 100644 Editor/Codegen/SplineType.cs.meta diff --git a/Editor/Codegen.meta b/Editor/Codegen.meta new file mode 100644 index 0000000..3cf4efc --- /dev/null +++ b/Editor/Codegen.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 67ce1d58abb5eb743bc3fc5ca17109ca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/CodeGenerator.cs b/Editor/Codegen/CodeGenerator.cs similarity index 100% rename from Editor/CodeGenerator.cs rename to Editor/Codegen/CodeGenerator.cs diff --git a/Editor/CodeGenerator.cs.meta b/Editor/Codegen/CodeGenerator.cs.meta similarity index 100% rename from Editor/CodeGenerator.cs.meta rename to Editor/Codegen/CodeGenerator.cs.meta diff --git a/Editor/Codegen/ElemType.cs b/Editor/Codegen/ElemType.cs new file mode 100644 index 0000000..81b4dbe --- /dev/null +++ b/Editor/Codegen/ElemType.cs @@ -0,0 +1,11 @@ +namespace Freya { + + public enum ElemType { + _1D = 1, + _2D, + _3D, + _4D, + Quat + } + +} \ No newline at end of file diff --git a/Editor/Codegen/ElemType.cs.meta b/Editor/Codegen/ElemType.cs.meta new file mode 100644 index 0000000..5ca214d --- /dev/null +++ b/Editor/Codegen/ElemType.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0af448fc320643498f0d301cc7ecdf14 +timeCreated: 1775666209 \ No newline at end of file diff --git a/Editor/Codegen/MathfsCodegen.cs b/Editor/Codegen/MathfsCodegen.cs new file mode 100644 index 0000000..1b41152 --- /dev/null +++ b/Editor/Codegen/MathfsCodegen.cs @@ -0,0 +1,24 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System.IO; +using UnityEditor; + +namespace Freya { + + public static class MathfsCodegen { + + static GUID guidRuntimeAsm = new("6071c9f2ce0a4407c93af459fa416e54"); // Mathfs.asmdef + static string PathRuntime => Path.GetDirectoryName( AssetDatabase.GUIDToAssetPath( guidRuntimeAsm ) ); + public static string PathSpline => $"{PathRuntime}/Splines"; + public static string PathNumerics => $"{PathRuntime}/Numerics"; + public static string PathMatrices => PathNumerics; + + [MenuItem( "Assets/Run Mathfs Codegen" )] + public static void Regenerate() { + SplineCodegen.GenerateUniformSplines( PathSpline ); + MatrixCodegen.GenerateMatrices( PathMatrices ); + } + + } + +} \ No newline at end of file diff --git a/Editor/MathfsCodegen.cs.meta b/Editor/Codegen/MathfsCodegen.cs.meta similarity index 100% rename from Editor/MathfsCodegen.cs.meta rename to Editor/Codegen/MathfsCodegen.cs.meta diff --git a/Editor/Codegen/MatrixCodegen.cs b/Editor/Codegen/MatrixCodegen.cs new file mode 100644 index 0000000..760147c --- /dev/null +++ b/Editor/Codegen/MatrixCodegen.cs @@ -0,0 +1,119 @@ +using System; +using System.IO; +using System.Linq; + +namespace Freya { + + public static class MatrixCodegen { + public static void GenerateMatrices( string pathMatrices ) { + for( int dim = 1; dim < 5; dim++ ) { // 1D, 2D, 3D, 4D + GenerateMatrixNx1( pathMatrices, 3, GetVectorOfDim( dim ) ); + GenerateMatrixNx1( pathMatrices, 4, GetVectorOfDim( dim ) ); + } + GenerateMatrixNx1( pathMatrices, 4, ElemType.Quat ); + } + + static void GenerateMatrixNx1( string path, int count, ElemType dim ) { + const string vCompStr = "xyzw"; + const string vCompStrUp = "XYZW"; + int elemCompCount = ( (int)dim ).AtMost( 4 ); // quats also have 4 + int[] elemRange = Enumerable.Range( 0, count ).ToArray(); + int[] compRange = Enumerable.Range( 0, elemCompCount ).ToArray(); + string[] compRangeStr = compRange.Select( c => vCompStr[c].ToString() ).ToArray(); + string JoinRange( string separator, Func elem ) => string.Join( separator, elemRange.Select( elem ) ); + string elemType = dim switch { + ElemType._1D => "float", + ElemType.Quat => "Quaternion", + _ => $"Vector{elemCompCount}" + }; + string typePrefix = dim == ElemType._1D ? "" : elemType; + string lerpName = GetLerpName( dim ); + string typeName = $"{typePrefix}Matrix{count}x1"; + string csParams = JoinRange( ", ", i => $"m{i}" ); + string csParamsThis = JoinRange( ", ", i => $"this.m{i}" ); + string ctorParams = JoinRange( ", ", i => $"{elemType} m{i}" ); + string indexerException = $"throw new IndexOutOfRangeException( $\"Matrix row index has to be from 0 to {count - 1}, got: {{row}}\" )"; + string indexerGetterCases = JoinRange( ", ", i => $"{i} => m{i}" ) + $", _ => {indexerException}"; + string equalsCompare = JoinRange( " && ", i => $"m{i}.Equals( other.m{i} )" ); + string equalsOpCompare = JoinRange( " && ", i => $"a.m{i} == b.m{i}" ); + string lerpAtoB = JoinRange( ", ", i => $"{lerpName}( a.m{i}, b.m{i}, t )" ); + bool isMultiComponentVector = dim != ElemType._1D && dim != ElemType.Quat; + + // generate content + CodeGenerator code = new CodeGenerator(); + code.AppendHeader(); + code.Append( "using System;" ); + if( dim != ElemType._1D ) // for Vector2/3 + code.Append( "using UnityEngine;" ); + + using( code.BracketScope( "namespace Freya" ) ) { + code.Summary( $"A {count}x1 column matrix with {elemType} values" ); + using( code.BracketScope( $"[Serializable] public struct {typeName}" ) ) { + // fields + code.Append( $"public {elemType} {csParams};" ); + + // constructors + code.Append( $"public {typeName}({ctorParams}) => ({csParamsThis}) = ({csParams});" ); + if( isMultiComponentVector ) { // compose from float matrices + string s = $"public {typeName}({string.Join( ", ", compRangeStr.Select( c => $"Matrix{count}x1 {c}" ) )}) => "; + s += $"({csParams}) = ({JoinRange( ", ", i => $"new {elemType}({string.Join( ", ", compRangeStr.Select( c => $"{c}.m{i}" ) )})" )});"; + code.Append( s ); + } + + // indexer + using( code.BracketScope( $"public {elemType} this[int row]" ) ) { + code.Append( $"get => row switch{{{indexerGetterCases}}};" ); + using( code.BracketScope( "set" ) ) { + using( code.BracketScope( "switch(row)" ) ) { + code.Append( JoinRange( " ", i => $"case {i}: m{i} = value; break;" ) ); + code.Append( $"default: {indexerException};" ); + } + } + } + + // component extraction for vector-valued matrices + if( isMultiComponentVector ) { + for( int c = 0; c < elemCompCount; c++ ) { + int cc = c; + string parameters = JoinRange( ", ", i => $"m{i}.{vCompStr[cc]}" ); + code.Append( $"public Matrix{count}x1 {vCompStrUp[c]} => new({parameters});" ); + } + } + + // interpolation + code.Summary( "Linearly interpolates between two matrices, based on a value t" ); + code.Param( "t", "The value to blend by" ); + string interpName = dim == ElemType.Quat ? "Slerp" : "Lerp"; + code.Append( $"public static {typeName} {interpName}( {typeName} a, {typeName} b, float t ) => new {typeName}({lerpAtoB});" ); + + // comparison/operators + code.Append( $"public static bool operator ==( {typeName} a, {typeName} b ) => {equalsOpCompare};" ); + code.Append( $"public static bool operator !=( {typeName} a, {typeName} b ) => !( a == b );" ); + code.Append( $"public bool Equals( {typeName} other ) => {equalsCompare};" ); + code.Append( $"public override bool Equals( object obj ) => obj is {typeName} other && Equals( other );" ); + code.Append( $"public override int GetHashCode() => HashCode.Combine( {csParams} );" ); + string stringPrint = JoinRange( "\\n", i => $"[{{m{i}}}]" ); + code.Append( $"public override string ToString() => $\"{stringPrint}\";" ); + } + } + + // save/finalize + File.WriteAllLines( $"{path}/{typeName}.cs", code.content ); + } + + static string GetLerpName( ElemType dim ) { + return dim switch { + ElemType._1D => "Mathfs.Lerp", + ElemType._2D => "Vector2.LerpUnclamped", + ElemType._3D => "Vector3.LerpUnclamped", + ElemType._4D => "Vector4.LerpUnclamped", + ElemType.Quat => "Quaternion.SlerpUnclamped", + _ => throw new IndexOutOfRangeException() + }; + } + + public static ElemType GetVectorOfDim( int dim ) => (ElemType)dim; + + } + +} \ No newline at end of file diff --git a/Editor/Codegen/MatrixCodegen.cs.meta b/Editor/Codegen/MatrixCodegen.cs.meta new file mode 100644 index 0000000..30b9cf0 --- /dev/null +++ b/Editor/Codegen/MatrixCodegen.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5d76c176fdab4141980642aff15417ee +timeCreated: 1775667441 \ No newline at end of file diff --git a/Editor/MathfsCodegen.cs b/Editor/Codegen/SplineCodegen.cs similarity index 73% rename from Editor/MathfsCodegen.cs rename to Editor/Codegen/SplineCodegen.cs index 2d03ef5..202e4a4 100644 --- a/Editor/MathfsCodegen.cs +++ b/Editor/Codegen/SplineCodegen.cs @@ -1,43 +1,14 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) - -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; -using Debug = UnityEngine.Debug; namespace Freya { - public static class MathfsCodegen { - - class SplineType { - public int degree; - public string className; - public string prettyName; - public string prettyNameLower; - public string[] paramNames; - public string[] paramDescs; - public string matrixName; - public RationalMatrix4x4 charMatrix; - - public SplineType( int degree, string className, string prettyName, string matrixName, RationalMatrix4x4 charMatrix, string[] paramNames, string[] paramDescs, string[] paramDescsQuad = null ) { - this.degree = degree; - this.className = className; - this.prettyName = prettyName; - this.prettyNameLower = prettyName.ToLowerInvariant(); - this.paramDescs = paramDescs; - this.matrixName = matrixName; - this.paramNames = paramNames; - this.charMatrix = charMatrix; - } - - public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { - gen.Param( paramNames[i], paramDescs[i] ); - } - } + public static class SplineCodegen { #region Type Definitions @@ -94,6 +65,17 @@ public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { #endregion + public static void GenerateUniformSplines( string splinesPath ) { + string pathUniformSplines = $"{splinesPath}/Uniform Spline Segments"; + for( int dim = 1; dim < 5; dim++ ) { // 1D, 2D, 3D, 4D + GenerateUniformSplineType( pathUniformSplines, typeBezier, dim ); + GenerateUniformSplineType( pathUniformSplines, typeBezierQuad, dim ); + GenerateUniformSplineType( pathUniformSplines, typeHermite, dim ); + GenerateUniformSplineType( pathUniformSplines, typeBspline, dim ); + GenerateUniformSplineType( pathUniformSplines, typeCatRom, dim ); + } + } + // [MenuItem( "Assets/Port Spline Data" )] public static void PortSplineData() { string replacements = ""; @@ -154,131 +136,8 @@ static bool IsSplineType( string name, out SplineType type, out int dim ) { return false; } - enum ElemType { - _1D = 1, - _2D, - _3D, - _4D, - Quat - } - - static ElemType GetVectorOfDim( int dim ) => (ElemType)dim; - - [MenuItem( "Assets/Run Mathfs Codegen" )] - public static void Regenerate() { - for( int dim = 1; dim < 5; dim++ ) { // 1D, 2D, 3D, 4D - GenerateUniformSplineType( typeBezier, dim ); - GenerateUniformSplineType( typeBezierQuad, dim ); - GenerateUniformSplineType( typeHermite, dim ); - GenerateUniformSplineType( typeBspline, dim ); - GenerateUniformSplineType( typeCatRom, dim ); - GenerateMatrixNx1( 3, GetVectorOfDim( dim ) ); - GenerateMatrixNx1( 4, GetVectorOfDim( dim ) ); - } - GenerateMatrixNx1( 4, ElemType.Quat ); - } - - static string GetLerpName( ElemType dim ) { - return dim switch { - ElemType._1D => "Mathfs.Lerp", - ElemType._2D => "Vector2.LerpUnclamped", - ElemType._3D => "Vector3.LerpUnclamped", - ElemType._4D => "Vector4.LerpUnclamped", - ElemType.Quat => "Quaternion.SlerpUnclamped", - _ => throw new IndexOutOfRangeException() - }; - } - - static void GenerateMatrixNx1( int count, ElemType dim ) { - const string vCompStr = "xyzw"; - const string vCompStrUp = "XYZW"; - int elemCompCount = ( (int)dim ).AtMost( 4 ); // quats also have 4 - int[] elemRange = Enumerable.Range( 0, count ).ToArray(); - int[] compRange = Enumerable.Range( 0, elemCompCount ).ToArray(); - string[] compRangeStr = compRange.Select( c => vCompStr[c].ToString() ).ToArray(); - string JoinRange( string separator, Func elem ) => string.Join( separator, elemRange.Select( elem ) ); - string elemType = dim switch { - ElemType._1D => "float", - ElemType.Quat => "Quaternion", - _ => $"Vector{elemCompCount}" - }; - string typePrefix = dim == ElemType._1D ? "" : elemType; - string lerpName = GetLerpName( dim ); - string typeName = $"{typePrefix}Matrix{count}x1"; - string csParams = JoinRange( ", ", i => $"m{i}" ); - string csParamsThis = JoinRange( ", ", i => $"this.m{i}" ); - string ctorParams = JoinRange( ", ", i => $"{elemType} m{i}" ); - string indexerException = $"throw new IndexOutOfRangeException( $\"Matrix row index has to be from 0 to {count - 1}, got: {{row}}\" )"; - string indexerGetterCases = JoinRange( ", ", i => $"{i} => m{i}" ) + $", _ => {indexerException}"; - string equalsCompare = JoinRange( " && ", i => $"m{i}.Equals( other.m{i} )" ); - string equalsOpCompare = JoinRange( " && ", i => $"a.m{i} == b.m{i}" ); - string lerpAtoB = JoinRange( ", ", i => $"{lerpName}( a.m{i}, b.m{i}, t )" ); - bool isMultiComponentVector = dim != ElemType._1D && dim != ElemType.Quat; - - // generate content - CodeGenerator code = new CodeGenerator(); - code.AppendHeader(); - code.Append( "using System;" ); - if( dim != ElemType._1D ) // for Vector2/3 - code.Append( "using UnityEngine;" ); - - using( code.BracketScope( "namespace Freya" ) ) { - code.Summary( $"A {count}x1 column matrix with {elemType} values" ); - using( code.BracketScope( $"[Serializable] public struct {typeName}" ) ) { - // fields - code.Append( $"public {elemType} {csParams};" ); - - // constructors - code.Append( $"public {typeName}({ctorParams}) => ({csParamsThis}) = ({csParams});" ); - if( isMultiComponentVector ) { // compose from float matrices - string s = $"public {typeName}({string.Join( ", ", compRangeStr.Select( c => $"Matrix{count}x1 {c}" ) )}) => "; - s += $"({csParams}) = ({JoinRange( ", ", i => $"new {elemType}({string.Join( ", ", compRangeStr.Select( c => $"{c}.m{i}" ) )})" )});"; - code.Append( s ); - } - - // indexer - using( code.BracketScope( $"public {elemType} this[int row]" ) ) { - code.Append( $"get => row switch{{{indexerGetterCases}}};" ); - using( code.BracketScope( "set" ) ) { - using( code.BracketScope( "switch(row)" ) ) { - code.Append( JoinRange( " ", i => $"case {i}: m{i} = value; break;" ) ); - code.Append( $"default: {indexerException};" ); - } - } - } - - // component extraction for vector-valued matrices - if( isMultiComponentVector ) { - for( int c = 0; c < elemCompCount; c++ ) { - int cc = c; - string parameters = JoinRange( ", ", i => $"m{i}.{vCompStr[cc]}" ); - code.Append( $"public Matrix{count}x1 {vCompStrUp[c]} => new({parameters});" ); - } - } - - // interpolation - code.Summary( "Linearly interpolates between two matrices, based on a value t" ); - code.Param( "t", "The value to blend by" ); - string interpName = dim == ElemType.Quat ? "Slerp" : "Lerp"; - code.Append( $"public static {typeName} {interpName}( {typeName} a, {typeName} b, float t ) => new {typeName}({lerpAtoB});" ); - - // comparison/operators - code.Append( $"public static bool operator ==( {typeName} a, {typeName} b ) => {equalsOpCompare};" ); - code.Append( $"public static bool operator !=( {typeName} a, {typeName} b ) => !( a == b );" ); - code.Append( $"public bool Equals( {typeName} other ) => {equalsCompare};" ); - code.Append( $"public override bool Equals( object obj ) => obj is {typeName} other && Equals( other );" ); - code.Append( $"public override int GetHashCode() => HashCode.Combine( {csParams} );" ); - string stringPrint = JoinRange( "\\n", i => $"[{{m{i}}}]" ); - code.Append( $"public override string ToString() => $\"{stringPrint}\";" ); - } - } - - // save/finalize - string path = $"Assets/Spline Plugin/Mathfs/Runtime/Numerics/{typeName}.cs"; - File.WriteAllLines( path, code.content ); - } - static void GenerateUniformSplineType( SplineType type, int dim ) { + static void GenerateUniformSplineType( string uniformSplinePath, SplineType type, int dim ) { int degree = type.degree; string dataType = dim == 1 ? "float" : $"Vector{dim}"; string polynomType = dim == 1 ? "Polynomial" : $"Polynomial{dim}D"; @@ -487,8 +346,18 @@ static void GenerateUniformSplineType( SplineType type, int dim ) { } } - string path = $"Assets/Spline Plugin/Mathfs/Runtime/Splines/Uniform Spline Segments/{structName}.cs"; - File.WriteAllLines( path, code.content ); + File.WriteAllLines( $"{uniformSplinePath}/{structName}.cs", code.content ); + } + + static string GetLerpName( ElemType dim ) { + return dim switch { + ElemType._1D => "Mathfs.Lerp", + ElemType._2D => "Vector2.LerpUnclamped", + ElemType._3D => "Vector3.LerpUnclamped", + ElemType._4D => "Vector4.LerpUnclamped", + ElemType.Quat => "Quaternion.SlerpUnclamped", + _ => throw new IndexOutOfRangeException() + }; } class MathSum { @@ -559,20 +428,9 @@ string FormatTerm( int i ) { } - public static string GetDegreeName( int d, bool shortName ) { - return d switch { - 1 => "Linear", - 2 => shortName ? "Quad" : "Quadratic", - 3 => "Cubic", - 4 => "Quartic", - 5 => "Quintic", - _ => throw new IndexOutOfRangeException() - }; - } - static readonly string[] comp = { "x", "y", "z", "w" }; - public static void AppendBezierSplit( CodeGenerator code, string structName, string dataType, int degree, int dim ) { + static void AppendBezierSplit( CodeGenerator code, string structName, string dataType, int degree, int dim ) { string LerpStr( string A, string B, int c ) => $"{A}.{comp[c]} + ( {B}.{comp[c]} - {A}.{comp[c]} ) * t"; void AppendLerps( string varName, string A, string B ) { @@ -602,6 +460,18 @@ void AppendLerps( string varName, string A, string B ) { } } + static string GetDegreeName( int d, bool shortName ) { + return d switch { + 1 => "Linear", + 2 => shortName ? "Quad" : "Quadratic", + 3 => "Cubic", + 4 => "Quartic", + 5 => "Quintic", + _ => throw new IndexOutOfRangeException() + }; + } + + } } \ No newline at end of file diff --git a/Editor/Codegen/SplineCodegen.cs.meta b/Editor/Codegen/SplineCodegen.cs.meta new file mode 100644 index 0000000..6d1e7cc --- /dev/null +++ b/Editor/Codegen/SplineCodegen.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9a733093b6cf4cdfba45361185d1ba8e +timeCreated: 1775666119 \ No newline at end of file diff --git a/Editor/Codegen/SplineType.cs b/Editor/Codegen/SplineType.cs new file mode 100644 index 0000000..f5bdc1b --- /dev/null +++ b/Editor/Codegen/SplineType.cs @@ -0,0 +1,29 @@ +namespace Freya { + + public class SplineType { + public int degree; + public string className; + public string prettyName; + public string prettyNameLower; + public string[] paramNames; + public string[] paramDescs; + public string matrixName; + public RationalMatrix4x4 charMatrix; + + public SplineType( int degree, string className, string prettyName, string matrixName, RationalMatrix4x4 charMatrix, string[] paramNames, string[] paramDescs, string[] paramDescsQuad = null ) { + this.degree = degree; + this.className = className; + this.prettyName = prettyName; + this.prettyNameLower = prettyName.ToLowerInvariant(); + this.paramDescs = paramDescs; + this.matrixName = matrixName; + this.paramNames = paramNames; + this.charMatrix = charMatrix; + } + + public void AppendParamStrings( CodeGenerator gen, int degree, int i ) { + gen.Param( paramNames[i], paramDescs[i] ); + } + } + +} \ No newline at end of file diff --git a/Editor/Codegen/SplineType.cs.meta b/Editor/Codegen/SplineType.cs.meta new file mode 100644 index 0000000..65537ab --- /dev/null +++ b/Editor/Codegen/SplineType.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 50863095183c481bb781e9263be4e35f +timeCreated: 1775665591 \ No newline at end of file From 761a3f75bd62b06c34126152de5e8bab13aa55fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Apr 2026 02:24:47 +0200 Subject: [PATCH 290/301] static access codegen for a gazillion types, including extension methods for int2/float3/etc. --- Editor/Codegen/CodeGenerator.cs | 35 +- Editor/Codegen/MathfsCodegen.cs | 75 ++++ Editor/Codegen/MatrixCodegen.cs | 32 +- Editor/Codegen/NumericTypeInfo.cs | 87 ++++ Editor/Codegen/NumericTypeInfo.cs.meta | 3 + Editor/Codegen/SplineCodegen.cs | 74 ++-- Editor/Codegen/StaticAccessExtensions.cs | 373 ++++++++++++++++ Editor/Codegen/StaticAccessExtensions.cs.meta | 3 + .../Codegen/StaticAccessInterfaceGenerator.cs | 223 ++++++++++ .../StaticAccessInterfaceGenerator.cs.meta | 3 + Editor/Mathfs.Editor.asmdef | 3 +- Runtime/Generated static functions.meta | 8 + .../IComplex_static.cs | 44 ++ .../IComplex_static.cs.meta | 2 + .../IDotProduct_static.cs | 48 +++ .../IDotProduct_static.cs.meta | 2 + .../IHalfNumber_static.cs | 18 + .../IHalfNumber_static.cs.meta | 2 + .../INumberBase_static.cs | 144 +++++++ .../INumberBase_static.cs.meta | 2 + .../INumber_static.cs | 188 ++++++++ .../INumber_static.cs.meta | 2 + .../IQuadrant2D_static.cs | 76 ++++ .../IQuadrant2D_static.cs.meta | 2 + .../IRoundable_static.cs | 172 ++++++++ .../IRoundable_static.cs.meta | 2 + .../ISignedNumber_static.cs | 56 +++ .../ISignedNumber_static.cs.meta | 2 + .../ISqrMag_static.cs | 46 ++ .../ISqrMag_static.cs.meta | 2 + .../IVec1Base_static.cs | 114 +++++ .../IVec1Base_static.cs.meta | 2 + .../IVec2Base_static.cs | 114 +++++ .../IVec2Base_static.cs.meta | 2 + .../IVec2_static.cs | 60 +++ .../IVec2_static.cs.meta | 2 + .../IVec3Base_static.cs | 72 ++++ .../IVec3Base_static.cs.meta | 2 + .../IVec4Base_static.cs | 42 ++ .../IVec4Base_static.cs.meta | 2 + .../IVecComponents_static.cs | 114 +++++ .../IVecComponents_static.cs.meta | 2 + .../Generated static functions/IVec_static.cs | 114 +++++ .../IVec_static.cs.meta | 2 + .../IWedgeProduct_static.cs | 38 ++ .../IWedgeProduct_static.cs.meta | 2 + Runtime/Numerics/IComplex.cs | 55 --- Runtime/Numerics/IDotProduct.cs | 37 -- Runtime/Numerics/IHalfNumber.cs | 21 - Runtime/Numerics/INumber.cs | 405 ------------------ Runtime/Numerics/IQuadrant2D.cs | 118 ----- Runtime/Numerics/ISignedNumber.cs | 28 -- Runtime/Numerics/IVec1.cs | 129 ------ Runtime/Numerics/IVec1.cs.meta | 3 - Runtime/Numerics/IVec2.cs | 196 --------- Runtime/Numerics/IVecComponents.cs | 187 -------- Runtime/Numerics/Interfaces.meta | 8 + .../Numerics/Interfaces/BinaryOpAttribute.cs | 8 + .../Interfaces/BinaryOpAttribute.cs.meta | 3 + Runtime/Numerics/Interfaces/IComplex.cs | 15 + .../{ => Interfaces}/IComplex.cs.meta | 0 Runtime/Numerics/Interfaces/IDotProduct.cs | 17 + .../{ => Interfaces}/IDotProduct.cs.meta | 0 Runtime/Numerics/Interfaces/IHalfNumber.cs | 8 + .../{ => Interfaces}/IHalfNumber.cs.meta | 0 Runtime/Numerics/Interfaces/INumberBase.cs | 41 ++ .../INumberBase.cs.meta} | 0 Runtime/Numerics/Interfaces/IQuadrant2D.cs | 41 ++ .../{ => Interfaces}/IQuadrant2D.cs.meta | 0 .../Numerics/{ => Interfaces}/IRoundable.cs | 18 - .../{ => Interfaces}/IRoundable.cs.meta | 0 Runtime/Numerics/Interfaces/ISignedNumber.cs | 16 + .../{ => Interfaces}/ISignedNumber.cs.meta | 0 Runtime/Numerics/Interfaces/ISqrMag.cs | 8 + Runtime/Numerics/Interfaces/ISqrMag.cs.meta | 3 + Runtime/Numerics/{ => Interfaces}/IVec.cs | 12 +- .../Numerics/{ => Interfaces}/IVec.cs.meta | 0 Runtime/Numerics/Interfaces/IVec2.cs | 15 + .../Numerics/{ => Interfaces}/IVec2.cs.meta | 0 Runtime/Numerics/Interfaces/IVec3.cs | 6 + Runtime/Numerics/Interfaces/IVec3.cs.meta | 3 + Runtime/Numerics/Interfaces/IVec4.cs | 6 + Runtime/Numerics/Interfaces/IVec4.cs.meta | 3 + Runtime/Numerics/Interfaces/IVecBase.cs | 42 ++ Runtime/Numerics/Interfaces/IVecBase.cs.meta | 3 + Runtime/Numerics/Interfaces/IVecComponents.cs | 17 + .../{ => Interfaces}/IVecComponents.cs.meta | 0 .../Numerics/{ => Interfaces}/IVectorMath.cs | 0 .../{ => Interfaces}/IVectorMath.cs.meta | 0 .../{ => Interfaces}/IWedgeProduct.cs | 19 +- .../{ => Interfaces}/IWedgeProduct.cs.meta | 0 Runtime/Numerics/inth2.cs | 2 +- Runtime/Numerics/mathfs.cs | 24 +- Runtime/Numerics/rat2.cs | 3 +- 94 files changed, 2625 insertions(+), 1308 deletions(-) create mode 100644 Editor/Codegen/NumericTypeInfo.cs create mode 100644 Editor/Codegen/NumericTypeInfo.cs.meta create mode 100644 Editor/Codegen/StaticAccessExtensions.cs create mode 100644 Editor/Codegen/StaticAccessExtensions.cs.meta create mode 100644 Editor/Codegen/StaticAccessInterfaceGenerator.cs create mode 100644 Editor/Codegen/StaticAccessInterfaceGenerator.cs.meta create mode 100644 Runtime/Generated static functions.meta create mode 100644 Runtime/Generated static functions/IComplex_static.cs create mode 100644 Runtime/Generated static functions/IComplex_static.cs.meta create mode 100644 Runtime/Generated static functions/IDotProduct_static.cs create mode 100644 Runtime/Generated static functions/IDotProduct_static.cs.meta create mode 100644 Runtime/Generated static functions/IHalfNumber_static.cs create mode 100644 Runtime/Generated static functions/IHalfNumber_static.cs.meta create mode 100644 Runtime/Generated static functions/INumberBase_static.cs create mode 100644 Runtime/Generated static functions/INumberBase_static.cs.meta create mode 100644 Runtime/Generated static functions/INumber_static.cs create mode 100644 Runtime/Generated static functions/INumber_static.cs.meta create mode 100644 Runtime/Generated static functions/IQuadrant2D_static.cs create mode 100644 Runtime/Generated static functions/IQuadrant2D_static.cs.meta create mode 100644 Runtime/Generated static functions/IRoundable_static.cs create mode 100644 Runtime/Generated static functions/IRoundable_static.cs.meta create mode 100644 Runtime/Generated static functions/ISignedNumber_static.cs create mode 100644 Runtime/Generated static functions/ISignedNumber_static.cs.meta create mode 100644 Runtime/Generated static functions/ISqrMag_static.cs create mode 100644 Runtime/Generated static functions/ISqrMag_static.cs.meta create mode 100644 Runtime/Generated static functions/IVec1Base_static.cs create mode 100644 Runtime/Generated static functions/IVec1Base_static.cs.meta create mode 100644 Runtime/Generated static functions/IVec2Base_static.cs create mode 100644 Runtime/Generated static functions/IVec2Base_static.cs.meta create mode 100644 Runtime/Generated static functions/IVec2_static.cs create mode 100644 Runtime/Generated static functions/IVec2_static.cs.meta create mode 100644 Runtime/Generated static functions/IVec3Base_static.cs create mode 100644 Runtime/Generated static functions/IVec3Base_static.cs.meta create mode 100644 Runtime/Generated static functions/IVec4Base_static.cs create mode 100644 Runtime/Generated static functions/IVec4Base_static.cs.meta create mode 100644 Runtime/Generated static functions/IVecComponents_static.cs create mode 100644 Runtime/Generated static functions/IVecComponents_static.cs.meta create mode 100644 Runtime/Generated static functions/IVec_static.cs create mode 100644 Runtime/Generated static functions/IVec_static.cs.meta create mode 100644 Runtime/Generated static functions/IWedgeProduct_static.cs create mode 100644 Runtime/Generated static functions/IWedgeProduct_static.cs.meta delete mode 100644 Runtime/Numerics/IComplex.cs delete mode 100644 Runtime/Numerics/IDotProduct.cs delete mode 100644 Runtime/Numerics/IHalfNumber.cs delete mode 100644 Runtime/Numerics/INumber.cs delete mode 100644 Runtime/Numerics/IQuadrant2D.cs delete mode 100644 Runtime/Numerics/ISignedNumber.cs delete mode 100644 Runtime/Numerics/IVec1.cs delete mode 100644 Runtime/Numerics/IVec1.cs.meta delete mode 100644 Runtime/Numerics/IVec2.cs delete mode 100644 Runtime/Numerics/IVecComponents.cs create mode 100644 Runtime/Numerics/Interfaces.meta create mode 100644 Runtime/Numerics/Interfaces/BinaryOpAttribute.cs create mode 100644 Runtime/Numerics/Interfaces/BinaryOpAttribute.cs.meta create mode 100644 Runtime/Numerics/Interfaces/IComplex.cs rename Runtime/Numerics/{ => Interfaces}/IComplex.cs.meta (100%) create mode 100644 Runtime/Numerics/Interfaces/IDotProduct.cs rename Runtime/Numerics/{ => Interfaces}/IDotProduct.cs.meta (100%) create mode 100644 Runtime/Numerics/Interfaces/IHalfNumber.cs rename Runtime/Numerics/{ => Interfaces}/IHalfNumber.cs.meta (100%) create mode 100644 Runtime/Numerics/Interfaces/INumberBase.cs rename Runtime/Numerics/{INumber.cs.meta => Interfaces/INumberBase.cs.meta} (100%) create mode 100644 Runtime/Numerics/Interfaces/IQuadrant2D.cs rename Runtime/Numerics/{ => Interfaces}/IQuadrant2D.cs.meta (100%) rename Runtime/Numerics/{ => Interfaces}/IRoundable.cs (57%) rename Runtime/Numerics/{ => Interfaces}/IRoundable.cs.meta (100%) create mode 100644 Runtime/Numerics/Interfaces/ISignedNumber.cs rename Runtime/Numerics/{ => Interfaces}/ISignedNumber.cs.meta (100%) create mode 100644 Runtime/Numerics/Interfaces/ISqrMag.cs create mode 100644 Runtime/Numerics/Interfaces/ISqrMag.cs.meta rename Runtime/Numerics/{ => Interfaces}/IVec.cs (82%) rename Runtime/Numerics/{ => Interfaces}/IVec.cs.meta (100%) create mode 100644 Runtime/Numerics/Interfaces/IVec2.cs rename Runtime/Numerics/{ => Interfaces}/IVec2.cs.meta (100%) create mode 100644 Runtime/Numerics/Interfaces/IVec3.cs create mode 100644 Runtime/Numerics/Interfaces/IVec3.cs.meta create mode 100644 Runtime/Numerics/Interfaces/IVec4.cs create mode 100644 Runtime/Numerics/Interfaces/IVec4.cs.meta create mode 100644 Runtime/Numerics/Interfaces/IVecBase.cs create mode 100644 Runtime/Numerics/Interfaces/IVecBase.cs.meta create mode 100644 Runtime/Numerics/Interfaces/IVecComponents.cs rename Runtime/Numerics/{ => Interfaces}/IVecComponents.cs.meta (100%) rename Runtime/Numerics/{ => Interfaces}/IVectorMath.cs (100%) rename Runtime/Numerics/{ => Interfaces}/IVectorMath.cs.meta (100%) rename Runtime/Numerics/{ => Interfaces}/IWedgeProduct.cs (51%) rename Runtime/Numerics/{ => Interfaces}/IWedgeProduct.cs.meta (100%) diff --git a/Editor/Codegen/CodeGenerator.cs b/Editor/Codegen/CodeGenerator.cs index 3bda325..a8c7b29 100644 --- a/Editor/Codegen/CodeGenerator.cs +++ b/Editor/Codegen/CodeGenerator.cs @@ -10,13 +10,27 @@ public class CodeGenerator { int scope = 0; public List content = new List(); - public void Append( string s ) => content.Add( $"{new string( '\t', scope )}{s}" ); - public void Comment( string s ) => Append( $"// {s}" ); - public void Using( string s ) => Append( $"using {s};" ); - public void Summary( string s ) => Append( $"/// {s}" ); - public void Param( string param, string desc ) => Append( $"/// {desc}" ); + public static string GetInheritdocString( string s ) => $"/// "; + + public void AppendLine( string s ) => content.Add( $"{new string( '\t', scope )}{s}" ); + public void Comment( string s ) => AppendLine( $"// {s}" ); + public void Using( string s ) => AppendLine( $"using {s};" ); + public void Summary( string s ) => AppendLine( $"/// {s}" ); + public void Inheritdoc( string s ) => AppendLine( GetInheritdocString( s ) ); + public void Param( string param, string desc ) => AppendLine( $"/// {desc}" ); public void LineBreak() => content.Add( "" ); + public void BeginScope( string s, bool includeBrackets = true ) { + AppendLine( includeBrackets ? $"{s} {{" : s ); + scope++; + } + + public void EndScope( bool includeBrackets = true ) { + scope--; + if( includeBrackets ) + AppendLine( "}" ); + } + public void AppendHeader() { Comment( "by Freya Holmér (https://github.com/FreyaHolmer/Mathfs)" ); Comment( $"Do not manually edit - this file is generated by {nameof(MathfsCodegen)}.cs" ); @@ -35,14 +49,11 @@ public void AppendHeader() { public CodeScope( CodeGenerator gen, string s, bool includeBrackets = true ) { this.gen = gen; this.includeBrackets = includeBrackets; - gen.Append( includeBrackets ? $"{s} {{" : s ); - gen.scope++; + gen.BeginScope( s, includeBrackets ); } public void Dispose() { - gen.scope--; - if( includeBrackets ) - gen.Append( "}" ); + gen.EndScope( includeBrackets ); } } @@ -52,13 +63,13 @@ public void Dispose() { public RegionScope( CodeGenerator gen, string s ) { this.gen = gen; - gen.Append( $"#region {s}" ); + gen.AppendLine( $"#region {s}" ); gen.LineBreak(); } public void Dispose() { gen.LineBreak(); - gen.Append( "#endregion" ); + gen.AppendLine( "#endregion" ); } } } diff --git a/Editor/Codegen/MathfsCodegen.cs b/Editor/Codegen/MathfsCodegen.cs index 1b41152..4b97dc3 100644 --- a/Editor/Codegen/MathfsCodegen.cs +++ b/Editor/Codegen/MathfsCodegen.cs @@ -1,6 +1,10 @@ // by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using Unity.Mathematics; using UnityEditor; namespace Freya { @@ -11,14 +15,85 @@ public static class MathfsCodegen { static string PathRuntime => Path.GetDirectoryName( AssetDatabase.GUIDToAssetPath( guidRuntimeAsm ) ); public static string PathSpline => $"{PathRuntime}/Splines"; public static string PathNumerics => $"{PathRuntime}/Numerics"; + public static string PathStaticAccess => $"{PathRuntime}/Generated static functions"; public static string PathMatrices => PathNumerics; + static Dictionary iDefCodegens = new(); + + static StaticAccessInterfaceGenerator GetStaticCodegen( Type iType ) { + // Type iDef = iType.IsGenericType ? iType.GetGenericTypeDefinition() : iType; + string iName = iType.Name.Split( "`" )[0]; + if( iDefCodegens.TryGetValue( iName, out StaticAccessInterfaceGenerator code ) == false ) + iDefCodegens.Add( iName, code = new StaticAccessInterfaceGenerator( iType ) ); + return code; + } + [MenuItem( "Assets/Run Mathfs Codegen" )] public static void Regenerate() { SplineCodegen.GenerateUniformSplines( PathSpline ); MatrixCodegen.GenerateMatrices( PathMatrices ); + + GenerateStaticAccess( PathStaticAccess ); + + // Debug.Log( "Type: " + typeof(rat2).inter ); } + /// Interfaces ignored by the codegen + static Type[] interfaceBlacklist = new[] { + typeof(IEquatable<>), + typeof(IComparable<>), + }; + + static bool IsValidInterface( Type type ) { + if( type.IsGenericType ) + type = type.GetGenericTypeDefinition(); + return interfaceBlacklist.Contains( type ) == false; + } + + public static Type[] numericTypes = new[] { + typeof(rat), + typeof(rat2), + typeof(inth), + typeof(inth2), + }; + public static Type[] externalTypes = new[] { + typeof(int), + typeof(int2), + typeof(int3), + typeof(int4), + }; + + + static void GenerateStaticAccess( string pathStaticAccess ) { + iDefCodegens.Clear(); + + // collect all statics + foreach( Type type in numericTypes ) { + GenerateStaticAccessForInterface( type ); + } + + // extension interfaces/statics for external types + foreach( ( Type nType, NumericTypeInfo info ) in StaticAccessExtensions.numericInfo ) { + if( info.IsExternalType && ( info.numType is NumType.Float32 or NumType.Half16 or NumType.Double64 or NumType.Int32 ) ) { + foreach( Type iType in StaticAccessExtensions.InterfacesOfExternalType( nType ) ) { + GetStaticCodegen( iType ).GenerateSpecificsForType( nType, iType ); + } + } + } + + // finalize static codegen + foreach( StaticAccessInterfaceGenerator gen in iDefCodegens.Values ) { + gen.GenerateCode( pathStaticAccess ); + } + } + + static void GenerateStaticAccessForInterface( Type type ) { + foreach( Type iType in type.GetInterfaces().Where( IsValidInterface ) ) + GetStaticCodegen( iType ).GenerateSpecificsForType( type, iType ); + } + + } + } \ No newline at end of file diff --git a/Editor/Codegen/MatrixCodegen.cs b/Editor/Codegen/MatrixCodegen.cs index 760147c..f9f0ea4 100644 --- a/Editor/Codegen/MatrixCodegen.cs +++ b/Editor/Codegen/MatrixCodegen.cs @@ -42,31 +42,31 @@ static void GenerateMatrixNx1( string path, int count, ElemType dim ) { // generate content CodeGenerator code = new CodeGenerator(); code.AppendHeader(); - code.Append( "using System;" ); + code.AppendLine( "using System;" ); if( dim != ElemType._1D ) // for Vector2/3 - code.Append( "using UnityEngine;" ); + code.AppendLine( "using UnityEngine;" ); using( code.BracketScope( "namespace Freya" ) ) { code.Summary( $"A {count}x1 column matrix with {elemType} values" ); using( code.BracketScope( $"[Serializable] public struct {typeName}" ) ) { // fields - code.Append( $"public {elemType} {csParams};" ); + code.AppendLine( $"public {elemType} {csParams};" ); // constructors - code.Append( $"public {typeName}({ctorParams}) => ({csParamsThis}) = ({csParams});" ); + code.AppendLine( $"public {typeName}({ctorParams}) => ({csParamsThis}) = ({csParams});" ); if( isMultiComponentVector ) { // compose from float matrices string s = $"public {typeName}({string.Join( ", ", compRangeStr.Select( c => $"Matrix{count}x1 {c}" ) )}) => "; s += $"({csParams}) = ({JoinRange( ", ", i => $"new {elemType}({string.Join( ", ", compRangeStr.Select( c => $"{c}.m{i}" ) )})" )});"; - code.Append( s ); + code.AppendLine( s ); } // indexer using( code.BracketScope( $"public {elemType} this[int row]" ) ) { - code.Append( $"get => row switch{{{indexerGetterCases}}};" ); + code.AppendLine( $"get => row switch{{{indexerGetterCases}}};" ); using( code.BracketScope( "set" ) ) { using( code.BracketScope( "switch(row)" ) ) { - code.Append( JoinRange( " ", i => $"case {i}: m{i} = value; break;" ) ); - code.Append( $"default: {indexerException};" ); + code.AppendLine( JoinRange( " ", i => $"case {i}: m{i} = value; break;" ) ); + code.AppendLine( $"default: {indexerException};" ); } } } @@ -76,7 +76,7 @@ static void GenerateMatrixNx1( string path, int count, ElemType dim ) { for( int c = 0; c < elemCompCount; c++ ) { int cc = c; string parameters = JoinRange( ", ", i => $"m{i}.{vCompStr[cc]}" ); - code.Append( $"public Matrix{count}x1 {vCompStrUp[c]} => new({parameters});" ); + code.AppendLine( $"public Matrix{count}x1 {vCompStrUp[c]} => new({parameters});" ); } } @@ -84,16 +84,16 @@ static void GenerateMatrixNx1( string path, int count, ElemType dim ) { code.Summary( "Linearly interpolates between two matrices, based on a value t" ); code.Param( "t", "The value to blend by" ); string interpName = dim == ElemType.Quat ? "Slerp" : "Lerp"; - code.Append( $"public static {typeName} {interpName}( {typeName} a, {typeName} b, float t ) => new {typeName}({lerpAtoB});" ); + code.AppendLine( $"public static {typeName} {interpName}( {typeName} a, {typeName} b, float t ) => new {typeName}({lerpAtoB});" ); // comparison/operators - code.Append( $"public static bool operator ==( {typeName} a, {typeName} b ) => {equalsOpCompare};" ); - code.Append( $"public static bool operator !=( {typeName} a, {typeName} b ) => !( a == b );" ); - code.Append( $"public bool Equals( {typeName} other ) => {equalsCompare};" ); - code.Append( $"public override bool Equals( object obj ) => obj is {typeName} other && Equals( other );" ); - code.Append( $"public override int GetHashCode() => HashCode.Combine( {csParams} );" ); + code.AppendLine( $"public static bool operator ==( {typeName} a, {typeName} b ) => {equalsOpCompare};" ); + code.AppendLine( $"public static bool operator !=( {typeName} a, {typeName} b ) => !( a == b );" ); + code.AppendLine( $"public bool Equals( {typeName} other ) => {equalsCompare};" ); + code.AppendLine( $"public override bool Equals( object obj ) => obj is {typeName} other && Equals( other );" ); + code.AppendLine( $"public override int GetHashCode() => HashCode.Combine( {csParams} );" ); string stringPrint = JoinRange( "\\n", i => $"[{{m{i}}}]" ); - code.Append( $"public override string ToString() => $\"{stringPrint}\";" ); + code.AppendLine( $"public override string ToString() => $\"{stringPrint}\";" ); } } diff --git a/Editor/Codegen/NumericTypeInfo.cs b/Editor/Codegen/NumericTypeInfo.cs new file mode 100644 index 0000000..5dc5d01 --- /dev/null +++ b/Editor/Codegen/NumericTypeInfo.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Mathematics; +using UnityEngine; + +namespace Freya { + + public struct NumericTypeInfo { + public Type nType; + public int dims; + public TypeSource source; + public NumType numType; + + public NumericTypeInfo( Type nType, NumType numType, int dims, TypeSource source ) { + this.nType = nType; + this.numType = numType; + this.dims = dims; + this.source = source; + } + + public bool IsScalar => dims == 1; + public bool IsExternalType => source != TypeSource.Mathfs; + public IEnumerable Components() => Enumerable.Range( 0, dims ); + public IEnumerable Components( Func selector ) => Components().Select( i => selector( i, "xyzw"[i] ) ); + public IEnumerable<(int i, char c)> ComponentsTuples => Components().Select( i => ( i, "xyzw"[i] ) ); + public string JoinComponents( string separator, Func selector ) => string.Join( separator, Components( selector ) ); + public string CompSum( Func selector ) => string.Join( "+", Components( selector ) ); + public string NewFromComps( Func selector ) => $"new({string.Join( ", ", Components( selector ) )})"; + + public string CompAggrInstanceFuncs( string funcName ) { + if( dims == 1 ) + return "{0}"; + return ComponentsTuples.Skip( 1 ).Aggregate( "{0}.x", ( acc, item ) => $"{{0}}.{item.c}.{funcName}({acc})" ); + } + // .Aggregate( "", (agg,elem) => $"{prev}.{funcName}" ); + + + // x + // x.max(y) + // x.max(y.max(z)) + // x.max(y.max(z.max(w))) + + // max( x, y ) + // max( x, max( y, z ) ) + // max( x, max( y, max( z, w ) ) ) + + public bool IsAlwaysIntegerValue => + numType is + NumType.Bool2 or + NumType.Byte8 or NumType.SByte8 or + NumType.Short16 or NumType.UShort16 or + NumType.Int32 or NumType.UInt32 or + NumType.Long64 or NumType.ULong64; + + + public Type TypeAfterRounding => + dims switch { + 1 => typeof(int), + 2 => typeof(int2), + 3 => typeof(int3), + 4 => typeof(int4), + _ => throw new IndexOutOfRangeException() + }; + public Type ComponentType => + numType switch { + NumType.Bool2 => typeof(bool), + NumType.Byte8 => typeof(byte), + NumType.SByte8 => typeof(sbyte), + NumType.Short16 => typeof(short), + NumType.UShort16 => typeof(ushort), + NumType.Int32 => typeof(int), + NumType.UInt32 => typeof(uint), + NumType.Long64 => typeof(long), + NumType.ULong64 => typeof(ulong), + NumType.IntHalf => typeof(inth), + NumType.Rational => typeof(rat), + NumType.Half16 => typeof(half), + NumType.Float32 => typeof(float), + NumType.Double64 => typeof(double), + _ => throw new IndexOutOfRangeException() + }; + + + } + +} \ No newline at end of file diff --git a/Editor/Codegen/NumericTypeInfo.cs.meta b/Editor/Codegen/NumericTypeInfo.cs.meta new file mode 100644 index 0000000..7936b7e --- /dev/null +++ b/Editor/Codegen/NumericTypeInfo.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 43eaff8786a54eb0bf53c4667a0379f0 +timeCreated: 1775947461 \ No newline at end of file diff --git a/Editor/Codegen/SplineCodegen.cs b/Editor/Codegen/SplineCodegen.cs index 202e4a4..f20992b 100644 --- a/Editor/Codegen/SplineCodegen.cs +++ b/Editor/Codegen/SplineCodegen.cs @@ -171,13 +171,13 @@ static void GenerateUniformSplineType( string uniformSplinePath, SplineType type code.Summary( $"An optimized uniform {dim}D {degFullLower} {type.prettyNameLower} segment, with {ptCount} control points" ); using( code.BracketScope( $"[Serializable] public struct {structName} : IParamSplineSegment<{polynomType},{pointMatrixType}>" ) ) { // intentionally always Cubic right now code.LineBreak(); - code.Append( "const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining;" ); + code.AppendLine( "const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining;" ); code.LineBreak(); // fields - code.Append( $"[SerializeField] {pointMatrixType} pointMatrix;" ); - code.Append( $"[NonSerialized] {polynomType} curve;" ); - code.Append( "[NonSerialized] bool validCoefficients;" ); + code.AppendLine( $"[SerializeField] {pointMatrixType} pointMatrix;" ); + code.AppendLine( $"[NonSerialized] {polynomType} curve;" ); + code.AppendLine( "[NonSerialized] bool validCoefficients;" ); code.LineBreak(); // constructors @@ -185,11 +185,11 @@ static void GenerateUniformSplineType( string uniformSplinePath, SplineType type code.Summary( ctorSummary ); for( int i = 0; i < ptCount; i++ ) type.AppendParamStrings( code, degree, i ); - code.Append( $"public {structName}( {ctorParams} ) : this(new {pointMatrixType}({csPoints})){{}}" ); + code.AppendLine( $"public {structName}( {ctorParams} ) : this(new {pointMatrixType}({csPoints})){{}}" ); code.Summary( ctorSummary ); code.Param( "pointMatrix", "The matrix containing the control points of this spline" ); - code.Append( $"public {structName}( {pointMatrixType} pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false);" ); + code.AppendLine( $"public {structName}( {pointMatrixType} pointMatrix ) => (this.pointMatrix,curve,validCoefficients) = (pointMatrix,default,false);" ); code.LineBreak(); @@ -197,44 +197,44 @@ static void GenerateUniformSplineType( string uniformSplinePath, SplineType type using( code.BracketScope( $"public {polynomType} Curve" ) ) { using( code.BracketScope( $"get" ) ) { using( code.Scope( "if( validCoefficients )" ) ) - code.Append( "return curve; // no need to update" ); - code.Append( "validCoefficients = true;" ); + code.AppendLine( "return curve; // no need to update" ); + code.AppendLine( "validCoefficients = true;" ); using( code.Scope( $"return curve = new {polynomType}(" ) ) { for( int icRow = 0; icRow < ptCount; icRow++ ) { MathSum sum = new MathSum(); for( int ip = 0; ip < ptCount; ip++ ) sum.AddTerm( type.charMatrix[icRow, ip], $"{type.paramNames[ip].ToUpperInvariant()}" ); - code.Append( $"{sum}{( icRow < ptCount - 1 ? "," : "" )}" ); + code.AppendLine( $"{sum}{( icRow < ptCount - 1 ? "," : "" )}" ); } } - code.Append( ");" ); + code.AppendLine( ");" ); } // todo: set would be possible! setting the points based on a curve } - code.Append( $"public {pointMatrixType} PointMatrix {{[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); }}" ); + code.AppendLine( $"public {pointMatrixType} PointMatrix {{[MethodImpl( INLINE )] get => pointMatrix; [MethodImpl( INLINE )] set => _ = ( pointMatrix = value, validCoefficients = false ); }}" ); for( int i = 0; i < ptCount; i++ ) { code.Summary( pointDescs[i] ); - code.Append( $"public {dataType} {points[i].ToUpperInvariant()}{{ [MethodImpl( INLINE )] get => pointMatrix.m{i}; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m{i} = value, validCoefficients = false ); }}" ); + code.AppendLine( $"public {dataType} {points[i].ToUpperInvariant()}{{ [MethodImpl( INLINE )] get => pointMatrix.m{i}; [MethodImpl( INLINE )] set => _ = ( pointMatrix.m{i} = value, validCoefficients = false ); }}" ); } code.Summary( $"Get or set a control point position by index. Valid indices from 0 to {degree}" ); using( code.BracketScope( $"public {dataType} this[ int i ]" ) ) { string indexException = $"throw new ArgumentOutOfRangeException( nameof(i), $\"Index has to be in the 0 to {degree} range, and I think {{i}} is outside that range you know\" )"; - code.Append( $"get => i switch {{ {JoinRange( ", ", i => $"{i} => {points[i].ToUpperInvariant()}" )}, _ => {indexException} }};" ); - code.Append( $"set {{ switch( i ){{ {JoinRange( " ", i => $"case {i}: {points[i].ToUpperInvariant()} = value; break;" )} default: {indexException}; }}}}" ); + code.AppendLine( $"get => i switch {{ {JoinRange( ", ", i => $"{i} => {points[i].ToUpperInvariant()}" )}, _ => {indexException} }};" ); + code.AppendLine( $"set {{ switch( i ){{ {JoinRange( " ", i => $"case {i}: {points[i].ToUpperInvariant()} = value; break;" )} default: {indexException}; }}}}" ); } // equality checks string compEquals = JoinRangeStr( " && ", p => $"{p.ToUpperInvariant()}.Equals( other.{p.ToUpperInvariant()} )" ); string toStringParams = JoinRange( ", ", i => $"{{pointMatrix.m{i}}}" ); - code.Append( $"public static bool operator ==( {structName} a, {structName} b ) => a.pointMatrix == b.pointMatrix;" ); - code.Append( $"public static bool operator !=( {structName} a, {structName} b ) => !( a == b );" ); - code.Append( $"public bool Equals( {structName} other ) => {compEquals};" ); - code.Append( $"public override bool Equals( object obj ) => obj is {structName} other && pointMatrix.Equals( other.pointMatrix );" ); - code.Append( $"public override int GetHashCode() => pointMatrix.GetHashCode();" ); - code.Append( $"public override string ToString() => $\"({toStringParams})\";" ); + code.AppendLine( $"public static bool operator ==( {structName} a, {structName} b ) => a.pointMatrix == b.pointMatrix;" ); + code.AppendLine( $"public static bool operator !=( {structName} a, {structName} b ) => !( a == b );" ); + code.AppendLine( $"public bool Equals( {structName} other ) => {compEquals};" ); + code.AppendLine( $"public override bool Equals( object obj ) => obj is {structName} other && pointMatrix.Equals( other.pointMatrix );" ); + code.AppendLine( $"public override int GetHashCode() => pointMatrix.GetHashCode();" ); + code.AppendLine( $"public override string ToString() => $\"({toStringParams})\";" ); code.LineBreak(); // typecasting @@ -245,7 +245,7 @@ static void GenerateUniformSplineType( string uniformSplinePath, SplineType type code.Summary( "Returns this spline segment in 3D, where z = 0" ); code.Param( "curve2D", "The 2D curve to cast to 3D" ); string inParams = JoinRangeStr( ", ", p => $"curve2D.{p.ToUpperInvariant()}" ); - code.Append( $"public static explicit operator {structName3D}( {structName} curve2D ) => new {structName3D}( {inParams} );" ); + code.AppendLine( $"public static explicit operator {structName3D}( {structName} curve2D ) => new {structName3D}( {inParams} );" ); } if( dim == 3 ) { @@ -254,7 +254,7 @@ static void GenerateUniformSplineType( string uniformSplinePath, SplineType type code.Summary( "Returns this curve flattened to 2D. Effectively setting z = 0" ); code.Param( "curve3D", "The 3D curve to flatten to the Z plane" ); string inParams = JoinRangeStr( ", ", p => $"curve3D.{p.ToUpperInvariant()}" ); - code.Append( $"public static explicit operator {structName2D}( {structName} curve3D ) => new {structName2D}( {inParams} );" ); + code.AppendLine( $"public static explicit operator {structName2D}( {structName} curve3D ) => new {structName2D}( {inParams} );" ); } } @@ -286,11 +286,11 @@ static void GenerateUniformSplineType( string uniformSplinePath, SplineType type MathSum sum = new(); for( int iPt = 0; iPt < 4; iPt++ ) sum.AddTerm( C[oPt, iPt], $"s.{type.paramNames[iPt].ToUpperInvariant()}" ); - code.Append( $"{sum}{( oPt < 3 ? "," : "" )}" ); + code.AppendLine( $"{sum}{( oPt < 3 ? "," : "" )}" ); } } - code.Append( ");" ); + code.AppendLine( ");" ); } } } @@ -303,11 +303,11 @@ static void GenerateUniformSplineType( string uniformSplinePath, SplineType type using( code.Scope( $"public static {structName} Lerp( {structName} a, {structName} b, float t ) =>" ) ) { using( code.Scope( "new(" ) ) { for( int i = 0; i < ptCount; i++ ) { - code.Append( $"{lerpName}( a.{points[i].ToUpperInvariant()}, b.{points[i].ToUpperInvariant()}, t )" + ( i == ptCount - 1 ? "" : "," ) ); + code.AppendLine( $"{lerpName}( a.{points[i].ToUpperInvariant()}, b.{points[i].ToUpperInvariant()}, t )" + ( i == ptCount - 1 ? "" : "," ) ); } } - code.Append( ");" ); + code.AppendLine( ");" ); } @@ -321,16 +321,16 @@ static void GenerateUniformSplineType( string uniformSplinePath, SplineType type code.Param( "b", "The second spline segment" ); code.Param( "t", "A value from 0 to 1 to blend between a and b" ); using( code.BracketScope( $"public static {structName} Slerp( {structName} a, {structName} b, float t )" ) ) { - code.Append( $"{dataType} P0 = {lerpName}( a.P0, b.P0, t );" ); - code.Append( $"{dataType} P3 = {lerpName}( a.P3, b.P3, t );" ); + code.AppendLine( $"{dataType} P0 = {lerpName}( a.P0, b.P0, t );" ); + code.AppendLine( $"{dataType} P3 = {lerpName}( a.P3, b.P3, t );" ); using( code.Scope( $"return new {structName}(" ) ) { - code.Append( $"P0," ); - code.Append( $"P0 + {slerpCast}Vector3.SlerpUnclamped( a.P1 - a.P0, b.P1 - b.P0, t )," ); - code.Append( $"P3 + {slerpCast}Vector3.SlerpUnclamped( a.P2 - a.P3, b.P2 - b.P3, t )," ); - code.Append( $"P3" ); + code.AppendLine( $"P0," ); + code.AppendLine( $"P0 + {slerpCast}Vector3.SlerpUnclamped( a.P1 - a.P0, b.P1 - b.P0, t )," ); + code.AppendLine( $"P3 + {slerpCast}Vector3.SlerpUnclamped( a.P2 - a.P3, b.P2 - b.P3, t )," ); + code.AppendLine( $"P3" ); } - code.Append( ");" ); + code.AppendLine( ");" ); } } @@ -438,11 +438,11 @@ void AppendLerps( string varName, string A, string B ) { using( code.Scope( $"{dataType} {varName} = new {dataType}(" ) ) { for( int c = 0; c < dim; c++ ) { string end = c == dim - 1 ? " );" : ","; - code.Append( $"{LerpStr( A, B, c )}{end}" ); + code.AppendLine( $"{LerpStr( A, B, c )}{end}" ); } } } else { // floats - code.Append( $"{dataType} {varName} = {A} + ( {B} - {A} ) * t;" ); + code.AppendLine( $"{dataType} {varName} = {A} + ( {B} - {A} ) * t;" ); } } @@ -453,10 +453,10 @@ void AppendLerps( string varName, string A, string B ) { AppendLerps( "d", "a", "b" ); AppendLerps( "e", "b", "c" ); AppendLerps( "p", "d", "e" ); - code.Append( $"return ( new {structName}( P0, a, d, p ), new {structName}( p, e, c, P3 ) );" ); + code.AppendLine( $"return ( new {structName}( P0, a, d, p ), new {structName}( p, e, c, P3 ) );" ); } else if( degree == 2 ) { AppendLerps( "p", "a", "b" ); - code.Append( $"return ( new {structName}( P0, a, p ), new {structName}( p, b, P2 ) );" ); + code.AppendLine( $"return ( new {structName}( P0, a, p ), new {structName}( p, b, P2 ) );" ); } } diff --git a/Editor/Codegen/StaticAccessExtensions.cs b/Editor/Codegen/StaticAccessExtensions.cs new file mode 100644 index 0000000..2e33d18 --- /dev/null +++ b/Editor/Codegen/StaticAccessExtensions.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; + +namespace Freya { + + public enum TypeSource { + CsNative, + UnityMath, + Unity, + Mathfs + } + + public enum NumType { + Bool2, + Byte8, + SByte8, + Short16, + UShort16, + Int32, + UInt32, + Long64, + ULong64, + IntHalf, + Rational, + Half16, + Float32, + Double64, + } + + public static class StaticAccessExtensions { + + + public static readonly Dictionary numericInfo = new() { + { typeof(rat), new(typeof(rat), NumType.Rational, dims: 1, TypeSource.Mathfs) }, + { typeof(rat2), new(typeof(rat2), NumType.Rational, dims: 2, TypeSource.Mathfs) }, + // { typeof(rat3), new(typeof(rat3), NumType.Rational, dims: 3, TypeSource.Mathfs) }, // todo + // { typeof(rat4), new(typeof(rat4), NumType.Rational, dims: 4, TypeSource.Mathfs) }, + + { typeof(inth), new(typeof(inth), NumType.IntHalf, dims: 1, TypeSource.Mathfs) }, + { typeof(inth2), new(typeof(inth2), NumType.IntHalf, dims: 2, TypeSource.Mathfs) }, + // { typeof(inth3), new(typeof(inth3), NumType.IntHalf, dims: 3, TypeSource.Mathfs) }, // todo + // { typeof(inth4), new(typeof(inth4), NumType.IntHalf, dims: 4, TypeSource.Mathfs) }, + + { typeof(bool), new(typeof(bool), NumType.Bool2, dims: 1, TypeSource.CsNative) }, + { typeof(bool2), new(typeof(bool2), NumType.Bool2, dims: 2, TypeSource.UnityMath) }, + { typeof(bool3), new(typeof(bool3), NumType.Bool2, dims: 3, TypeSource.UnityMath) }, + { typeof(bool4), new(typeof(bool4), NumType.Bool2, dims: 4, TypeSource.UnityMath) }, + + { typeof(byte), new(typeof(byte), NumType.Byte8, dims: 1, TypeSource.CsNative) }, + { typeof(sbyte), new(typeof(sbyte), NumType.SByte8, dims: 1, TypeSource.CsNative) }, + { typeof(short), new(typeof(short), NumType.Short16, dims: 1, TypeSource.CsNative) }, + { typeof(ushort), new(typeof(ushort), NumType.UShort16, dims: 1, TypeSource.CsNative) }, + { typeof(long), new(typeof(long), NumType.Long64, dims: 1, TypeSource.CsNative) }, + { typeof(ulong), new(typeof(ulong), NumType.ULong64, dims: 1, TypeSource.CsNative) }, + + { typeof(uint), new(typeof(uint), NumType.UInt32, dims: 1, TypeSource.CsNative) }, + { typeof(uint2), new(typeof(uint2), NumType.UInt32, dims: 2, TypeSource.UnityMath) }, + { typeof(uint3), new(typeof(uint3), NumType.UInt32, dims: 3, TypeSource.UnityMath) }, + { typeof(uint4), new(typeof(uint4), NumType.UInt32, dims: 4, TypeSource.UnityMath) }, + + { typeof(int), new(typeof(int), NumType.Int32, dims: 1, TypeSource.CsNative) }, + { typeof(int2), new(typeof(int2), NumType.Int32, dims: 2, TypeSource.UnityMath) }, + { typeof(Vector2Int), new(typeof(Vector2Int), NumType.Int32, dims: 2, TypeSource.Unity) }, // legacy unity int2 + { typeof(int3), new(typeof(int3), NumType.Int32, dims: 3, TypeSource.UnityMath) }, + { typeof(Vector3Int), new(typeof(Vector3Int), NumType.Int32, dims: 3, TypeSource.Unity) }, // legacy unity int3 + { typeof(int4), new(typeof(int4), NumType.Int32, dims: 4, TypeSource.UnityMath) }, + // { typeof(Vector4Int), new(typeof(Vector4Int), NumType.Int32, dims: 4, TypeSource.Unity) }, // Unity doesn't define this + + // todo: Colors have a bunch of exceptions, disabling for now + // { typeof(Color32), new(typeof(Color32), NumType.Int32, dims: 4, TypeSource.Unity) }, // basically a byte4 type + // { typeof(Color), new(typeof(Color), NumType.Float32, dims: 4, TypeSource.Unity) }, + + // todo: Unity's half types become floats under basic operations, so they require a lot of exceptions in the codegen. Disabling for now + // { typeof(half), new(typeof(half), NumType.Half16, dims: 1, TypeSource.UnityMath) }, // exception: unity defines this + // { typeof(half2), new(typeof(half2), NumType.Half16, dims: 2, TypeSource.UnityMath) }, + // { typeof(half3), new(typeof(half3), NumType.Half16, dims: 3, TypeSource.UnityMath) }, + // { typeof(half4), new(typeof(half4), NumType.Half16, dims: 4, TypeSource.UnityMath) }, + + { typeof(float), new(typeof(float), NumType.Float32, dims: 1, TypeSource.CsNative) }, + { typeof(float2), new(typeof(float2), NumType.Float32, dims: 2, TypeSource.UnityMath) }, + { typeof(Vector2), new(typeof(Vector2), NumType.Float32, dims: 2, TypeSource.Unity) }, // legacy unity float2 + { typeof(float3), new(typeof(float3), NumType.Float32, dims: 3, TypeSource.UnityMath) }, + { typeof(Vector3), new(typeof(Vector3), NumType.Float32, dims: 3, TypeSource.Unity) }, // legacy unity float3 + { typeof(float4), new(typeof(float4), NumType.Float32, dims: 4, TypeSource.UnityMath) }, + { typeof(Vector4), new(typeof(Vector4), NumType.Float32, dims: 4, TypeSource.Unity) }, // legacy unity float4 + + { typeof(double), new(typeof(double), NumType.Double64, dims: 1, TypeSource.CsNative) }, + { typeof(double2), new(typeof(double2), NumType.Double64, dims: 2, TypeSource.UnityMath) }, + { typeof(double3), new(typeof(double3), NumType.Double64, dims: 3, TypeSource.UnityMath) }, + { typeof(double4), new(typeof(double4), NumType.Double64, dims: 4, TypeSource.UnityMath) }, + }; + + // public static IEnumerable<(Type nType,Type iType)> ExternalTypes + + + // v.abs().csum() // taxicab / L1 + // v.abs().cmax() // chebyshev / max norm + + public static IEnumerable InterfacesOfExternalType( Type nType ) { + HashSet interfaces = new HashSet(); + + NumericTypeInfo info = numericInfo[nType]; + Type V = nType; + Type R = info.TypeAfterRounding; // rounded type + Type C = info.ComponentType; // component type + Type D = C; // dot product result type + Type M = V; // complex multiplication result type + + interfaces.UnionWith( InterfacesOf( typeof(ISignedNumber<>).MakeGenericType( R ) ) ); + if( info.numType is NumType.Double64 or NumType.Float32 or NumType.Half16 or NumType.Rational or NumType.IntHalf ) + interfaces.UnionWith( InterfacesOf( typeof(IRoundable<>).MakeGenericType( R ) ) ); + + switch( info.dims ) { + case 1: + interfaces.UnionWith( InterfacesOf( typeof(INumber<>).MakeGenericType( V ) ) ); + break; + case 2: + Type W2 = D; // wedge product result type + interfaces.UnionWith( InterfacesOf( typeof(IVec2<,,,,>).MakeGenericType( V, C, D, W2, M ) ) ); + break; + case 3: + Type W3 = V; // wedge product result type + interfaces.UnionWith( InterfacesOf( typeof(IVec3<,,,>).MakeGenericType( V, C, D, W3 ) ) ); + break; + case 4: + interfaces.UnionWith( InterfacesOf( typeof(IVec4<,,>).MakeGenericType( V, C, D ) ) ); + break; + } + + // todo: only 2D vectors so far + // if( info.dims == 2 ) // typeof(IVec2) + // else + + + return interfaces; + } + + static IEnumerable InterfacesOf( Type t ) { + if( t == null ) + yield break; + if( t.IsInterface ) + yield return t; + foreach( Type iType in t.GetInterfaces() ) + yield return iType; + } + + public static string GetCustomImplementation( Type iType, Type nType, string member ) { + Type iTypeDef = iType.IsGenericType ? iType.GetGenericTypeDefinition() : iType; + if( numericInfo.TryGetValue( nType, out NumericTypeInfo info ) == false ) { + Debug.LogWarning( $"Missing custom implementation for nType {nType} with iType {iType} member {member} itypedef: {iTypeDef}" ); + return "default"; + } + + if( iTypeDef == typeof(INumberBase) ) { + if( member == nameof(INumberBase.isInteger) ) { + if( info.IsAlwaysIntegerValue ) + return "true"; + if( info.IsScalar ) + return $"{{0}} == {CsMathClass( nType )}.Truncate( {{0}} )"; + return info.JoinComponents( " && ", ( i, c ) => $"{{0}}.{c}.isInteger()" ); + } + if( member == nameof(INumberBase.isZero) ) { + if( info.IsScalar ) + return "{0} == 0"; + if( info.source == TypeSource.UnityMath ) + return "math.all( {0} == 0 )"; + return info.JoinComponents( " && ", ( i, c ) => $"{{0}}.{c} == 0" ); + } + if( member == nameof(INumberBase.isOrthogonal) ) { + if( info.IsScalar ) + return "true"; + string value = "{0}"; + if( info.source == TypeSource.Unity && info.numType == NumType.Int32 ) { + // Vector2Int and Vector3Int requires a cast + value = $"new int{info.dims}({info.JoinComponents( ", ", ( i, c ) => $"{{0}}.{c}" )})"; + } + string ceilOptionally = info.IsAlwaysIntegerValue ? "" : ".ceilAwayFrom0()"; + return $"({value}{ceilOptionally}.abs() > 0).csum() <= 1"; + } + } else if( iTypeDef == typeof(INumber<>) ) { + switch( member ) { + case nameof(INumber.abs): return StandardMathOp( info, "math.abs({0})", "Math.Abs({0})", "abs()" ); + case nameof(INumber.min): return StandardMathOp( info, "math.min({0},{1})", "Math.Min({0},{1})", "min({0})" ); + case nameof(INumber.max): return StandardMathOp( info, "math.max({0},{1})", "Math.Max({0},{1})", "max({0})" ); + case nameof(INumber.to): return "{1} - {0}"; // b - a + } + } else if( iTypeDef == typeof(IComplex<,>) ) { + switch( member ) { + case nameof(IComplex.complexMul): return "new({0}.x*{1}.x-{0}.y*{1}.y, {0}.x*{1}.y+{0}.y*{1}.x)"; + case nameof(IComplex.complexConj): return "new({0}.x, -{0}.y)"; + } + } else if( iTypeDef == typeof(IDotProduct<,>) ) { + switch( member ) { + case nameof(IDotProduct.dot): + // unity.mathematics does not implement dot() for the half type + bool isNotHalf = info.ComponentType != typeof(half); + if( info.source == TypeSource.UnityMath && isNotHalf ) + return "math.dot( {0}, {1} )"; + return info.CompSum( ( i, c ) => $"{{0}}.{c}*{{1}}.{c}" ); + } + } else if( iTypeDef == typeof(IWedgeProduct<,>) ) { + switch( member ) { + case nameof(IWedgeProduct.wedge): + return info.dims switch { + 2 => "{0}.x*{1}.y - {0}.y*{1}.x", + 3 => "new(" + + "{0}.y * {1}.z - {0}.z * {1}.y," + + "{0}.z * {1}.x - {0}.x * {1}.z," + + "{0}.x * {1}.y - {0}.y * {1}.x" + + ")", + _ => throw new NotImplementedException( $"Wedge product not implemented for elements of dimension {info.dims}" ) + }; + } + } else if( iTypeDef == typeof(ISqrMag<>) ) { + switch( member ) { + case nameof(IDotProduct.magSq): + // unity.mathematics does not implement dot() for the half type + bool isNotHalf = info.ComponentType != typeof(half); + if( info.source == TypeSource.UnityMath && isNotHalf ) + return "math.dot( {0}, {0} )"; + return info.CompSum( ( i, c ) => $"{{0}}.{c}*{{0}}.{c}" ); + } + } else if( iTypeDef == typeof(ISignedNumber<>) ) { + if( member == nameof(ISignedNumber.sign) ) { + if( info.source == TypeSource.CsNative ) + return "Math.Sign({0})"; + if( info.source == TypeSource.UnityMath && info.numType is NumType.Float32 or NumType.Double64 ) + return $"(int{info.dims})math.sign({{0}})"; // floats/doubles + return info.NewFromComps( ( i, c ) => $"{{0}}.{c}.sign()" ); + } + } else if( iTypeDef == typeof(IVec2<,,,,>) ) { + if( member == nameof(IVec2.rot90) ) { + return "new(-{0}.y,{0}.x)"; + } else if( member == nameof(IVec2.rotNeg90) ) { + return "new({0}.y,-{0}.x)"; + } else if( member == nameof(IVec2.rot180) ) { + return "new(-{0}.x,-{0}.y)"; + } + } else if( iTypeDef == typeof(IRoundable<>) ) { + switch( member ) { + case nameof(IRoundable.floorToward0): + if( info.source == TypeSource.CsNative && info.IsScalar ) + return "(int)({0}<0?math.ceil({0}):math.floor({0}))"; + return info.NewFromComps( ( i, c ) => $"{{0}}.{c}.floorToward0()" ); + case nameof(IRoundable.ceilAwayFrom0): + if( info.source == TypeSource.CsNative && info.IsScalar ) + return "(int)({0}<0?math.floor({0}):math.ceil({0}))"; + return info.NewFromComps( ( i, c ) => $"{{0}}.{c}.ceilAwayFrom0()" ); + case nameof(IRoundable.floor): + if( info.source == TypeSource.CsNative && info.IsScalar ) + return info.numType switch { + NumType.Double64 => "(int)Math.Floor({0})", + NumType.Float32 => "(int)MathF.Floor({0})", + _ => throw new NotImplementedException( info.numType.ToString() ) + }; + else if( info.source == TypeSource.UnityMath ) + return $"(int{info.dims})math.floor({{0}})"; + return info.NewFromComps( ( i, c ) => $"{{0}}.{c}.floor()" ); + case nameof(IRoundable.ceil): + if( info.source == TypeSource.CsNative && info.IsScalar ) + return info.numType switch { + NumType.Double64 => "(int)Math.Ceiling({0})", + NumType.Float32 => "(int)MathF.Ceiling({0})", + _ => throw new NotImplementedException( info.numType.ToString() ) + }; + else if( info.source == TypeSource.UnityMath ) + return $"(int{info.dims})math.ceil({{0}})"; + return info.NewFromComps( ( i, c ) => $"{{0}}.{c}.ceil()" ); + case nameof(IRoundable.round): + if( info.source == TypeSource.CsNative && info.IsScalar ) + return info.numType switch { + NumType.Double64 => "(int)Math.Round( {0}, (MidpointRounding)rounding )", + NumType.Float32 => "(int)MathF.Round( {0}, (MidpointRounding)rounding )", + _ => throw new NotImplementedException( info.numType.ToString() ) + }; + return info.NewFromComps( ( i, c ) => $"{{0}}.{c}.round(rounding)" ); + } + } else if( iTypeDef == typeof(IVecComponents<>) ) { + switch( member ) { + case "cmin": return UnityMathVecsElseComponentWise( info, "math.cmin({0})", info.CompAggrInstanceFuncs( "min" ) ); + case "cmax": return UnityMathVecsElseComponentWise( info, "math.cmax({0})", info.CompAggrInstanceFuncs( "max" ) ); + case "csum": return UnityMathVecsElseComponentWise( info, "math.csum({0})", info.CompSum( ( i, c ) => $"{{0}}.{c}" ) ); + } + } else if( iTypeDef == typeof(IVec1Base<,,>) && IVecNBase( info, member, 1, out string str1 ) ) { + return str1; + } else if( iTypeDef == typeof(IVec2Base<,,>) && IVecNBase( info, member, 2, out string str2 ) ) { + return str2; + } else if( iTypeDef == typeof(IVec3Base<,,>) && IVecNBase( info, member, 3, out string str3 ) ) { + return str3; + } else if( iTypeDef == typeof(IVec4Base<,,>) && IVecNBase( info, member, 4, out string str4 ) ) { + return str4; + } else if( iTypeDef == typeof(IVec<,,>) ) { + switch( member ) { + case nameof(IVec.magChebyshev): return "{0}.abs().cmax()"; + case nameof(IVec.magTaxicab): return "{0}.abs().csum()"; + case nameof(IVec.pointSideOfPlane): return "({0}-{1}).dot({2}).sign()"; + } + } else if( iTypeDef == typeof(IQuadrant2D) ) { + string rounding = info.numType is NumType.Float32 or NumType.Double64 ? $".{nameof(IRoundable.ceilAwayFrom0)}()" : ""; + return member switch { + nameof(IQuadrant2D.quadrant) => "{0}.y switch {{" + + "> 00 when {0}.x <= 0 => 1," + + "<= 0 when {0}.x < 00 => 2," + + "< 00 when {0}.x >= 0 => 3," + + "_ => 0 }}", + nameof(IQuadrant2D.quadrantBasisX) => $"mathfs.{nameof(mathfs.quadrantToBasisX)}({{0}}{rounding}.quadrant())", + nameof(IQuadrant2D.quadrantBasis) => $"mathfs.{nameof(mathfs.quadrantToBasis)}({{0}}{rounding}.quadrant())", + nameof(IQuadrant2D.signedQuadrant) => $"mathfs.{nameof(mathfs.quadrantToSignedQuadrant)}({{0}}{rounding}.quadrant())", + _ => throw new NotImplementedException() + }; + } + + Debug.LogWarning( $"Missing custom implementation for nType {nType} with iType {iType} member {member}" ); + return "default"; + } + + public static bool IVecNBase( NumericTypeInfo info, string member, int dim, out string str ) { + int comp = member[^1] switch { + 'X' => 0, + 'Y' => 1, + 'Z' => 2, + 'W' => 3, + _ => 0, + }; + + if( member is "X" or "Y" or "Z" or "W" ) { + str = $"{{0}}.{member.ToLower()}"; + return true; + } + if( member is "flipX" or "flipY" or "flipZ" or "flipW" ) { + str = info.NewFromComps( ( i, c ) => $"{( i == comp ? "-" : "+" )}{{0}}.{c}" ); + return true; + } + if( member is "zeroX" or "zeroY" or "zeroZ" or "zeroW" ) { + str = info.NewFromComps( ( i, c ) => i == comp ? "0" : $"{{0}}.{c}" ); + return true; + } + + str = default; + return false; + } + + public static string UnityMathVecsElseComponentWise( NumericTypeInfo info, string funcMathematics, string perComp ) { + if( info.source == TypeSource.UnityMath && info.IsScalar == false ) + return funcMathematics; + return perComp; + // if( info.source == TypeSource.CsNative ) + // return $"{funcNative}"; + // return info.NewFromComponents( ( i, c ) => $"{{0}}.{c}.{string.Format( funcCompExtension, $"{{1}}.{c}" )}" ); + } + + public static string StandardMathOp( NumericTypeInfo info, string funcMathematics, string funcNative, string funcCompExtension ) { + if( info.source == TypeSource.UnityMath ) + return $"{funcMathematics}"; + if( info.source == TypeSource.CsNative ) + return $"{funcNative}"; + return info.NewFromComps( ( i, c ) => $"{{0}}.{c}.{string.Format( funcCompExtension, $"{{1}}.{c}" )}" ); + } + + public static string CsMathClass( Type type ) { + if( type == typeof(float) ) + return nameof(MathF); + if( type == typeof(double) ) + return nameof(Math); + throw new NotImplementedException( type.FullName ); + } + + + } + +} \ No newline at end of file diff --git a/Editor/Codegen/StaticAccessExtensions.cs.meta b/Editor/Codegen/StaticAccessExtensions.cs.meta new file mode 100644 index 0000000..de9a42c --- /dev/null +++ b/Editor/Codegen/StaticAccessExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c22c62cb4f5b4f2fad5c6acf58f05c89 +timeCreated: 1775933332 \ No newline at end of file diff --git a/Editor/Codegen/StaticAccessInterfaceGenerator.cs b/Editor/Codegen/StaticAccessInterfaceGenerator.cs new file mode 100644 index 0000000..e4a43e4 --- /dev/null +++ b/Editor/Codegen/StaticAccessInterfaceGenerator.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Freya { + + public class StaticAccessInterfaceGenerator { + + const string TSelf = "Self"; + const string StaticAccessClassName = nameof(mathfs); + const string StaticAccessClassNameGenerics = StaticAccessClassName + "_generics"; + const string Namespace = nameof(Freya); + + public Type iTypeDef; + public List generics = new(); + public List specifics = new(); + + string FileName => $"{IName}_static.cs"; + bool IsGenericInterface => iTypeDef.IsGenericType; + string IName => IsGenericInterface ? iTypeDef.Name.Split( "`" )[0] : iTypeDef.Name; + IEnumerable GenericTypeDefNames => iTypeDef.GetGenericArguments().Select( x => x.Name ); + string GenericsDefCsv => string.Join( ",", GenericTypeDefNames ); + string GenericsDefWithSelfCsv => string.Join( ",", GenericTypeDefNames.Append( TSelf ) ); + string INameInheritDoc => IName + ( IsGenericInterface ? $"{{{GenericsDefCsv}}}" : "" ); + string INameWithGenerics => IName + ( IsGenericInterface ? $"<{GenericsDefCsv}>" : "" ); + // string INameWithGenericsAndSelf => IName + $"<{GenericsDefWithSelfCsv}>"; + + public void GenerateCode( string targetFolder ) { + if( generics.Count == 0 && specifics.Count == 0 ) + return; // nothing to generate + CodeGenerator code = new(); + code.AppendHeader(); + code.AppendLine( "using System;" ); + code.AppendLine( "using Unity.Mathematics;" ); + code.AppendLine( "using UnityEngine;" ); + using( code.BracketScope( $"namespace {Namespace}" ) ) { + if( generics.Count > 0 ) + using( code.BracketScope( $"public static partial class {StaticAccessClassNameGenerics}" ) ) + generics.ForEach( code.AppendLine ); + if( specifics.Count > 0 ) + using( code.BracketScope( $"public static partial class {StaticAccessClassName}" ) ) + specifics.ForEach( code.AppendLine ); + } + Debug.Log( $"{FileName}:\n{string.Join( "\n", code.content )}" ); + File.WriteAllLines( $"{targetFolder}/{FileName}", code.content ); + } + + public bool PropertyIsValid( PropertyInfo p ) => p.IsSpecialName == false && p.GetIndexParameters().Length == 0; + public bool MethodIsValid( MethodInfo p ) => p.IsSpecialName == false; + + public IEnumerable PropertiesITypeDef => iTypeDef.GetProperties().Where( PropertyIsValid ); + public IEnumerable MethodsITypeDef => iTypeDef.GetMethods().Where( MethodIsValid ); + + public StaticAccessInterfaceGenerator( Type iType ) { + iTypeDef = iType.IsGenericType ? iType.GetGenericTypeDefinition() : iType; + + // Generic implementations of each property, eg: + // /// + // public static bool isInteger( T v ) where T : INumber => v.isInteger; + + // but also, sometimes the Self type is already in the return type + // /// + // public static T abs( T x ) where T : INumber => x.abs; + // incorrectly generates as: + // public static N abs(Self v) where Self : INumber => v.abs; + + + // GENERIC definitions + foreach( PropertyInfo prop in PropertiesITypeDef ) { + string memName = prop.Name; + string access = "public static"; + string retType = GetReturnTypeName( prop ); + string fName = $"{memName}<{GenericsDefWithSelfCsv}>"; + string constraints = $"where {TSelf} : {INameWithGenerics}"; + string parms = $"{TSelf} v"; + string body = $"v.{memName};"; + generics.Add( CodeGenerator.GetInheritdocString( $"{INameInheritDoc}.{memName}" ) ); + generics.Add( $"{access} {retType} {fName}({parms}) {constraints} => {body}" ); + } + + foreach( MethodInfo meth in MethodsITypeDef ) { + CreateStaticAccessForMethod( meth, isGenericDef: true ); + } + } + + + static void CsvAppend( ref string s, string appendage ) { + s = string.IsNullOrEmpty( s ) ? appendage : $"{s}, {appendage}"; + } + + string GetMethodParams( MethodInfo method ) { + return string.Join( ", ", method.GetParameters().Select( p => p.ParameterType.Name + " " + p.Name ) ); + } + + public void GenerateSpecificsForType( Type vType, Type typeImpl ) { + NumericTypeInfo info = StaticAccessExtensions.numericInfo[vType]; + bool addAsExtension = info.IsExternalType; + string param0prefix = addAsExtension ? "this " : ""; + + // /// + // public static bool isZero( rat v ) => v.isZero; + string vTypeName = vType.Name; + + // Specific PROPERTIES + foreach( PropertyInfo prop in typeImpl.GetProperties().Where( PropertyIsValid ) ) { + string memName = prop.Name; + string retTypeName = GetReturnTypeName( prop ); + string paramsInvoc = addAsExtension ? "()" : ""; + + string paramName = "v"; + + string body; + if( addAsExtension ) { + string strToFormat = StaticAccessExtensions.GetCustomImplementation( typeImpl, vType, memName ); + try { + body = string.Format( strToFormat, paramName ); + } catch { + body = "default"; + Debug.LogError( strToFormat ); + } + } else { + body = $"{paramName}.{memName}{paramsInvoc}"; + } + + specifics.Add( CodeGenerator.GetInheritdocString( $"{INameInheritDoc}.{memName}" ) ); + specifics.Add( $"public static {retTypeName} {memName}({param0prefix}{vTypeName} {paramName}) => {body};" ); + } + + // Specific METHODS + foreach( MethodInfo meth in typeImpl.GetMethods().Where( MethodIsValid ) ) { + CreateStaticAccessForMethod( meth, isGenericDef: false, vType, typeImpl ); + } + } + + void CreateStaticAccessForMethod( MethodInfo meth, bool isGenericDef, Type numTypeSelf = null, Type iTypeImpl = null ) { + // binary means we want to name the two input parameters (a,b) + NumericTypeInfo info = numTypeSelf != null ? StaticAccessExtensions.numericInfo[numTypeSelf] : default; + bool addAsExtension = numTypeSelf != null && info.IsExternalType; + string param0prefix = addAsExtension ? "this " : ""; + bool isBinaryOp = meth.CustomAttributes.Any( x => x.AttributeType == typeof(BinaryOpAttribute) ); + string memName = meth.Name; + string access = "public static"; + string retType = GetReturnTypeName( meth, isGenericDef ); + string fName = isGenericDef ? $"{memName}<{GenericsDefWithSelfCsv}>" : memName; + string tSelf = isGenericDef ? TSelf : NameOf( numTypeSelf, isGenericDef ); + if( string.IsNullOrEmpty( tSelf ) ) + Debug.LogWarning( $"Empty param name: {numTypeSelf} in {meth.Name}" ); + string constraints = isGenericDef ? $" where {tSelf} : {INameWithGenerics}" : ""; + string selfParamName = isBinaryOp ? "a" : "v"; + string paramsBody = ""; + string paramsDecl = $"{param0prefix}{tSelf} {selfParamName}"; + List paramNames = new(); + paramNames.Add( selfParamName ); + if( isBinaryOp ) { // f(T a,T b) => a.f(b); + string bTypeName = NameOf( meth.GetParameters().First().ParameterType, isGenericDef ); + if( string.IsNullOrEmpty( bTypeName ) ) + Debug.LogWarning( $"Empty param name: {meth.GetParameters().First()}/{meth.GetParameters().First().ParameterType.Name}/gen: {meth.GetParameters().First().ParameterType.IsGenericType} in meth {meth}" ); + string bName = "b"; + CsvAppend( ref paramsDecl, $"{bTypeName} {bName}" ); + CsvAppend( ref paramsBody, bName ); + paramNames.Add( bName ); + } else { + foreach( ParameterInfo p in meth.GetParameters() ) { + string paramTypeName = NameOf( p.ParameterType, isGenericDef ); + if( string.IsNullOrEmpty( paramTypeName ) ) + Debug.LogWarning( $"Empty param type name: {p.Name} in {meth.Name}" ); + CsvAppend( ref paramsDecl, paramTypeName + " " + p.Name ); + CsvAppend( ref paramsBody, p.Name ); + paramNames.Add( p.Name ); + } + } + string body; + if( addAsExtension ) { + body = StaticAccessExtensions.GetCustomImplementation( iTypeImpl, numTypeSelf, memName ); + if( paramNames.Count > 0 ) + body = string.Format( body, paramNames.ToArray() ); + } else + body = $"{selfParamName}.{memName}({paramsBody})"; + List targetList = isGenericDef ? generics : specifics; + targetList.Add( CodeGenerator.GetInheritdocString( $"{INameInheritDoc}.{memName}" ) ); + targetList.Add( $"{access} {retType} {fName}({paramsDecl}){constraints} => {body};" ); + } + + string NameOf( Type type, bool isGenericDef ) => type.Name; // isGenericDef ? type.Name : type.FullName; + + string GetReturnTypeName( MethodInfo meth, bool isGenericDef ) { + // if( meth.ReturnType.IsGenericParameter ) + return meth.ReturnType.Name; + // return meth.ReturnType.FullName; + } + + string GetReturnTypeName( PropertyInfo prop ) { + Type type = prop.PropertyType; + if( type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTuple<,>) ) { + // type.custom + Type[] args = type.GetGenericArguments(); + TupleElementNamesAttribute attr = prop.GetMethod.ReturnParameter.GetCustomAttribute(); + return "(" + string.Join( ",", args.Zip( attr.TransformNames, ( pType, label ) => label != null ? $"{pType} {label}" : pType.Name ) ) + ")"; + } + + return prop.PropertyType.Name; + + // Debug.Log( $"prop.PropertyType.FullName {prop.PropertyType.FullName}" ); + // Debug.Log( $"prop.GetMethod.ReturnType.FullName {prop.GetMethod.ReturnType.FullName}" ); + // if( prop.IsGenericMethod ) + // Debug.Log( $"prop.GetMethod.GetGenericMethodDefinition().ReturnType {prop.GetMethod.GetGenericMethodDefinition().ReturnType}" ); + + + // prop.GetMethod.gene + + // if(prop.typ) + + // return prop.GetMethod.IsGenericMethod ? prop.GetMethod.GetGenericMethodDefinition().ReturnType.FullName : type.FullName; + } + + + } + +} \ No newline at end of file diff --git a/Editor/Codegen/StaticAccessInterfaceGenerator.cs.meta b/Editor/Codegen/StaticAccessInterfaceGenerator.cs.meta new file mode 100644 index 0000000..c212dc3 --- /dev/null +++ b/Editor/Codegen/StaticAccessInterfaceGenerator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 33527b6429cb4500b0f22c7cfd7c5fff +timeCreated: 1775855648 \ No newline at end of file diff --git a/Editor/Mathfs.Editor.asmdef b/Editor/Mathfs.Editor.asmdef index 1356d96..63e841f 100644 --- a/Editor/Mathfs.Editor.asmdef +++ b/Editor/Mathfs.Editor.asmdef @@ -2,7 +2,8 @@ "name": "Mathfs.Editor", "rootNamespace": "", "references": [ - "GUID:6071c9f2ce0a4407c93af459fa416e54" + "GUID:6071c9f2ce0a4407c93af459fa416e54", + "GUID:d8b63aba1907145bea998dd612889d6b" ], "includePlatforms": [ "Editor" diff --git a/Runtime/Generated static functions.meta b/Runtime/Generated static functions.meta new file mode 100644 index 0000000..6f56a6b --- /dev/null +++ b/Runtime/Generated static functions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e1bef33040b8787439791e0d49d13da7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Generated static functions/IComplex_static.cs b/Runtime/Generated static functions/IComplex_static.cs new file mode 100644 index 0000000..90c8d89 --- /dev/null +++ b/Runtime/Generated static functions/IComplex_static.cs @@ -0,0 +1,44 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static V complexConj(Self v) where Self : IComplex => v.complexConj; + /// + public static M complexMul(Self a, V b) where Self : IComplex => a.complexMul(b); + } + public static partial class mathfs { + /// + public static rat2 complexConj(rat2 v) => v.complexConj; + /// + public static rat2 complexMul(rat2 a, rat2 b) => a.complexMul(b); + /// + public static inth2 complexConj(inth2 v) => v.complexConj; + /// + public static rat2 complexMul(inth2 a, inth2 b) => a.complexMul(b); + /// + public static int2 complexConj(this int2 v) => new(v.x, -v.y); + /// + public static int2 complexMul(this int2 a, int2 b) => new(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); + /// + public static Vector2Int complexConj(this Vector2Int v) => new(v.x, -v.y); + /// + public static Vector2Int complexMul(this Vector2Int a, Vector2Int b) => new(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); + /// + public static float2 complexConj(this float2 v) => new(v.x, -v.y); + /// + public static float2 complexMul(this float2 a, float2 b) => new(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); + /// + public static Vector2 complexConj(this Vector2 v) => new(v.x, -v.y); + /// + public static Vector2 complexMul(this Vector2 a, Vector2 b) => new(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); + /// + public static double2 complexConj(this double2 v) => new(v.x, -v.y); + /// + public static double2 complexMul(this double2 a, double2 b) => new(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); + } +} diff --git a/Runtime/Generated static functions/IComplex_static.cs.meta b/Runtime/Generated static functions/IComplex_static.cs.meta new file mode 100644 index 0000000..d5abcc3 --- /dev/null +++ b/Runtime/Generated static functions/IComplex_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e7a2daf25b51121469a4a2b91830a68b \ No newline at end of file diff --git a/Runtime/Generated static functions/IDotProduct_static.cs b/Runtime/Generated static functions/IDotProduct_static.cs new file mode 100644 index 0000000..c44aeff --- /dev/null +++ b/Runtime/Generated static functions/IDotProduct_static.cs @@ -0,0 +1,48 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static D dot(Self a, B b) where Self : IDotProduct => a.dot(b); + } + public static partial class mathfs { + /// + public static rat dot(rat2 a, int2 b) => a.dot(b); + /// + public static rat dot(rat2 a, rat2 b) => a.dot(b); + /// + public static rat dot(inth2 a, inth2 b) => a.dot(b); + /// + public static Int32 dot(this int2 a, int2 b) => math.dot( a, b ); + /// + public static Int32 dot(this Vector2Int a, Vector2Int b) => a.x*b.x+a.y*b.y; + /// + public static Int32 dot(this int3 a, int3 b) => math.dot( a, b ); + /// + public static Int32 dot(this Vector3Int a, Vector3Int b) => a.x*b.x+a.y*b.y+a.z*b.z; + /// + public static Int32 dot(this int4 a, int4 b) => math.dot( a, b ); + /// + public static Single dot(this float2 a, float2 b) => math.dot( a, b ); + /// + public static Single dot(this Vector2 a, Vector2 b) => a.x*b.x+a.y*b.y; + /// + public static Single dot(this float3 a, float3 b) => math.dot( a, b ); + /// + public static Single dot(this Vector3 a, Vector3 b) => a.x*b.x+a.y*b.y+a.z*b.z; + /// + public static Single dot(this float4 a, float4 b) => math.dot( a, b ); + /// + public static Single dot(this Vector4 a, Vector4 b) => a.x*b.x+a.y*b.y+a.z*b.z+a.w*b.w; + /// + public static Double dot(this double2 a, double2 b) => math.dot( a, b ); + /// + public static Double dot(this double3 a, double3 b) => math.dot( a, b ); + /// + public static Double dot(this double4 a, double4 b) => math.dot( a, b ); + } +} diff --git a/Runtime/Generated static functions/IDotProduct_static.cs.meta b/Runtime/Generated static functions/IDotProduct_static.cs.meta new file mode 100644 index 0000000..bf4e742 --- /dev/null +++ b/Runtime/Generated static functions/IDotProduct_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d486d8c1ce4144a429e7756d6d09b4f3 \ No newline at end of file diff --git a/Runtime/Generated static functions/IHalfNumber_static.cs b/Runtime/Generated static functions/IHalfNumber_static.cs new file mode 100644 index 0000000..bc1e1eb --- /dev/null +++ b/Runtime/Generated static functions/IHalfNumber_static.cs @@ -0,0 +1,18 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static F times2(Self v) where Self : IHalfNumber => v.times2; + } + public static partial class mathfs { + /// + public static Int32 times2(inth v) => v.times2; + /// + public static int2 times2(inth2 v) => v.times2; + } +} diff --git a/Runtime/Generated static functions/IHalfNumber_static.cs.meta b/Runtime/Generated static functions/IHalfNumber_static.cs.meta new file mode 100644 index 0000000..07378e9 --- /dev/null +++ b/Runtime/Generated static functions/IHalfNumber_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eefe704695a4d93409b842f68024ecf7 \ No newline at end of file diff --git a/Runtime/Generated static functions/INumberBase_static.cs b/Runtime/Generated static functions/INumberBase_static.cs new file mode 100644 index 0000000..30c8845 --- /dev/null +++ b/Runtime/Generated static functions/INumberBase_static.cs @@ -0,0 +1,144 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static Boolean isInteger(Self v) where Self : INumberBase => v.isInteger; + /// + public static Boolean isZero(Self v) where Self : INumberBase => v.isZero; + /// + public static Boolean isOrthogonal(Self v) where Self : INumberBase => v.isOrthogonal; + } + public static partial class mathfs { + /// + public static Boolean isInteger(rat v) => v.isInteger; + /// + public static Boolean isZero(rat v) => v.isZero; + /// + public static Boolean isOrthogonal(rat v) => v.isOrthogonal; + /// + public static Boolean isInteger(rat2 v) => v.isInteger; + /// + public static Boolean isZero(rat2 v) => v.isZero; + /// + public static Boolean isOrthogonal(rat2 v) => v.isOrthogonal; + /// + public static Boolean isInteger(inth v) => v.isInteger; + /// + public static Boolean isZero(inth v) => v.isZero; + /// + public static Boolean isOrthogonal(inth v) => v.isOrthogonal; + /// + public static Boolean isInteger(inth2 v) => v.isInteger; + /// + public static Boolean isZero(inth2 v) => v.isZero; + /// + public static Boolean isOrthogonal(inth2 v) => v.isOrthogonal; + /// + public static Boolean isInteger(this Int32 v) => true; + /// + public static Boolean isZero(this Int32 v) => v == 0; + /// + public static Boolean isOrthogonal(this Int32 v) => true; + /// + public static Boolean isInteger(this int2 v) => true; + /// + public static Boolean isZero(this int2 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this int2 v) => (v.abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this Vector2Int v) => true; + /// + public static Boolean isZero(this Vector2Int v) => v.x == 0 && v.y == 0; + /// + public static Boolean isOrthogonal(this Vector2Int v) => (new int2(v.x, v.y).abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this int3 v) => true; + /// + public static Boolean isZero(this int3 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this int3 v) => (v.abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this Vector3Int v) => true; + /// + public static Boolean isZero(this Vector3Int v) => v.x == 0 && v.y == 0 && v.z == 0; + /// + public static Boolean isOrthogonal(this Vector3Int v) => (new int3(v.x, v.y, v.z).abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this int4 v) => true; + /// + public static Boolean isZero(this int4 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this int4 v) => (v.abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this Single v) => v == MathF.Truncate( v ); + /// + public static Boolean isZero(this Single v) => v == 0; + /// + public static Boolean isOrthogonal(this Single v) => true; + /// + public static Boolean isInteger(this float2 v) => v.x.isInteger() && v.y.isInteger(); + /// + public static Boolean isZero(this float2 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this float2 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this Vector2 v) => v.x.isInteger() && v.y.isInteger(); + /// + public static Boolean isZero(this Vector2 v) => v.x == 0 && v.y == 0; + /// + public static Boolean isOrthogonal(this Vector2 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this float3 v) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger(); + /// + public static Boolean isZero(this float3 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this float3 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this Vector3 v) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger(); + /// + public static Boolean isZero(this Vector3 v) => v.x == 0 && v.y == 0 && v.z == 0; + /// + public static Boolean isOrthogonal(this Vector3 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this float4 v) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger() && v.w.isInteger(); + /// + public static Boolean isZero(this float4 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this float4 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this Vector4 v) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger() && v.w.isInteger(); + /// + public static Boolean isZero(this Vector4 v) => v.x == 0 && v.y == 0 && v.z == 0 && v.w == 0; + /// + public static Boolean isOrthogonal(this Vector4 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this Double v) => v == Math.Truncate( v ); + /// + public static Boolean isZero(this Double v) => v == 0; + /// + public static Boolean isOrthogonal(this Double v) => true; + /// + public static Boolean isInteger(this double2 v) => v.x.isInteger() && v.y.isInteger(); + /// + public static Boolean isZero(this double2 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this double2 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this double3 v) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger(); + /// + public static Boolean isZero(this double3 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this double3 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + /// + public static Boolean isInteger(this double4 v) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger() && v.w.isInteger(); + /// + public static Boolean isZero(this double4 v) => math.all( v == 0 ); + /// + public static Boolean isOrthogonal(this double4 v) => (v.ceilAwayFrom0().abs() > 0).csum() <= 1; + } +} diff --git a/Runtime/Generated static functions/INumberBase_static.cs.meta b/Runtime/Generated static functions/INumberBase_static.cs.meta new file mode 100644 index 0000000..11ce42a --- /dev/null +++ b/Runtime/Generated static functions/INumberBase_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b8364fe0fa70dab45901f44ca8f3c2c0 \ No newline at end of file diff --git a/Runtime/Generated static functions/INumber_static.cs b/Runtime/Generated static functions/INumber_static.cs new file mode 100644 index 0000000..3e1c957 --- /dev/null +++ b/Runtime/Generated static functions/INumber_static.cs @@ -0,0 +1,188 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static N abs(Self v) where Self : INumber => v.abs; + /// + public static N min(Self a, N b) where Self : INumber => a.min(b); + /// + public static N max(Self a, N b) where Self : INumber => a.max(b); + /// + public static N to(Self v, N target) where Self : INumber => v.to(target); + } + public static partial class mathfs { + /// + public static rat abs(rat v) => v.abs; + /// + public static rat min(rat a, rat b) => a.min(b); + /// + public static rat max(rat a, rat b) => a.max(b); + /// + public static rat to(rat v, rat target) => v.to(target); + /// + public static rat2 abs(rat2 v) => v.abs; + /// + public static rat2 min(rat2 a, rat2 b) => a.min(b); + /// + public static rat2 max(rat2 a, rat2 b) => a.max(b); + /// + public static rat2 to(rat2 v, rat2 target) => v.to(target); + /// + public static inth abs(inth v) => v.abs; + /// + public static inth min(inth a, inth b) => a.min(b); + /// + public static inth max(inth a, inth b) => a.max(b); + /// + public static inth to(inth v, inth target) => v.to(target); + /// + public static inth2 abs(inth2 v) => v.abs; + /// + public static inth2 min(inth2 a, inth2 b) => a.min(b); + /// + public static inth2 max(inth2 a, inth2 b) => a.max(b); + /// + public static inth2 to(inth2 v, inth2 target) => v.to(target); + /// + public static Int32 abs(this Int32 v) => Math.Abs(v); + /// + public static Int32 min(this Int32 a, Int32 b) => Math.Min(a,b); + /// + public static Int32 max(this Int32 a, Int32 b) => Math.Max(a,b); + /// + public static Int32 to(this Int32 v, Int32 target) => target - v; + /// + public static int2 abs(this int2 v) => math.abs(v); + /// + public static int2 min(this int2 a, int2 b) => math.min(a,b); + /// + public static int2 max(this int2 a, int2 b) => math.max(a,b); + /// + public static int2 to(this int2 v, int2 target) => target - v; + /// + public static Vector2Int abs(this Vector2Int v) => new(v.x.abs(), v.y.abs()); + /// + public static Vector2Int min(this Vector2Int a, Vector2Int b) => new(a.x.min(b.x), a.y.min(b.y)); + /// + public static Vector2Int max(this Vector2Int a, Vector2Int b) => new(a.x.max(b.x), a.y.max(b.y)); + /// + public static Vector2Int to(this Vector2Int v, Vector2Int target) => target - v; + /// + public static int3 abs(this int3 v) => math.abs(v); + /// + public static int3 min(this int3 a, int3 b) => math.min(a,b); + /// + public static int3 max(this int3 a, int3 b) => math.max(a,b); + /// + public static int3 to(this int3 v, int3 target) => target - v; + /// + public static Vector3Int abs(this Vector3Int v) => new(v.x.abs(), v.y.abs(), v.z.abs()); + /// + public static Vector3Int min(this Vector3Int a, Vector3Int b) => new(a.x.min(b.x), a.y.min(b.y), a.z.min(b.z)); + /// + public static Vector3Int max(this Vector3Int a, Vector3Int b) => new(a.x.max(b.x), a.y.max(b.y), a.z.max(b.z)); + /// + public static Vector3Int to(this Vector3Int v, Vector3Int target) => target - v; + /// + public static int4 abs(this int4 v) => math.abs(v); + /// + public static int4 min(this int4 a, int4 b) => math.min(a,b); + /// + public static int4 max(this int4 a, int4 b) => math.max(a,b); + /// + public static int4 to(this int4 v, int4 target) => target - v; + /// + public static Single abs(this Single v) => Math.Abs(v); + /// + public static Single min(this Single a, Single b) => Math.Min(a,b); + /// + public static Single max(this Single a, Single b) => Math.Max(a,b); + /// + public static Single to(this Single v, Single target) => target - v; + /// + public static float2 abs(this float2 v) => math.abs(v); + /// + public static float2 min(this float2 a, float2 b) => math.min(a,b); + /// + public static float2 max(this float2 a, float2 b) => math.max(a,b); + /// + public static float2 to(this float2 v, float2 target) => target - v; + /// + public static Vector2 abs(this Vector2 v) => new(v.x.abs(), v.y.abs()); + /// + public static Vector2 min(this Vector2 a, Vector2 b) => new(a.x.min(b.x), a.y.min(b.y)); + /// + public static Vector2 max(this Vector2 a, Vector2 b) => new(a.x.max(b.x), a.y.max(b.y)); + /// + public static Vector2 to(this Vector2 v, Vector2 target) => target - v; + /// + public static float3 abs(this float3 v) => math.abs(v); + /// + public static float3 min(this float3 a, float3 b) => math.min(a,b); + /// + public static float3 max(this float3 a, float3 b) => math.max(a,b); + /// + public static float3 to(this float3 v, float3 target) => target - v; + /// + public static Vector3 abs(this Vector3 v) => new(v.x.abs(), v.y.abs(), v.z.abs()); + /// + public static Vector3 min(this Vector3 a, Vector3 b) => new(a.x.min(b.x), a.y.min(b.y), a.z.min(b.z)); + /// + public static Vector3 max(this Vector3 a, Vector3 b) => new(a.x.max(b.x), a.y.max(b.y), a.z.max(b.z)); + /// + public static Vector3 to(this Vector3 v, Vector3 target) => target - v; + /// + public static float4 abs(this float4 v) => math.abs(v); + /// + public static float4 min(this float4 a, float4 b) => math.min(a,b); + /// + public static float4 max(this float4 a, float4 b) => math.max(a,b); + /// + public static float4 to(this float4 v, float4 target) => target - v; + /// + public static Vector4 abs(this Vector4 v) => new(v.x.abs(), v.y.abs(), v.z.abs(), v.w.abs()); + /// + public static Vector4 min(this Vector4 a, Vector4 b) => new(a.x.min(b.x), a.y.min(b.y), a.z.min(b.z), a.w.min(b.w)); + /// + public static Vector4 max(this Vector4 a, Vector4 b) => new(a.x.max(b.x), a.y.max(b.y), a.z.max(b.z), a.w.max(b.w)); + /// + public static Vector4 to(this Vector4 v, Vector4 target) => target - v; + /// + public static Double abs(this Double v) => Math.Abs(v); + /// + public static Double min(this Double a, Double b) => Math.Min(a,b); + /// + public static Double max(this Double a, Double b) => Math.Max(a,b); + /// + public static Double to(this Double v, Double target) => target - v; + /// + public static double2 abs(this double2 v) => math.abs(v); + /// + public static double2 min(this double2 a, double2 b) => math.min(a,b); + /// + public static double2 max(this double2 a, double2 b) => math.max(a,b); + /// + public static double2 to(this double2 v, double2 target) => target - v; + /// + public static double3 abs(this double3 v) => math.abs(v); + /// + public static double3 min(this double3 a, double3 b) => math.min(a,b); + /// + public static double3 max(this double3 a, double3 b) => math.max(a,b); + /// + public static double3 to(this double3 v, double3 target) => target - v; + /// + public static double4 abs(this double4 v) => math.abs(v); + /// + public static double4 min(this double4 a, double4 b) => math.min(a,b); + /// + public static double4 max(this double4 a, double4 b) => math.max(a,b); + /// + public static double4 to(this double4 v, double4 target) => target - v; + } +} diff --git a/Runtime/Generated static functions/INumber_static.cs.meta b/Runtime/Generated static functions/INumber_static.cs.meta new file mode 100644 index 0000000..eedeea1 --- /dev/null +++ b/Runtime/Generated static functions/INumber_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 69270ddc049a38b4fa5da7df273018b7 \ No newline at end of file diff --git a/Runtime/Generated static functions/IQuadrant2D_static.cs b/Runtime/Generated static functions/IQuadrant2D_static.cs new file mode 100644 index 0000000..122b217 --- /dev/null +++ b/Runtime/Generated static functions/IQuadrant2D_static.cs @@ -0,0 +1,76 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static Int32 quadrant(Self v) where Self : IQuadrant2D => v.quadrant; + /// + public static Int32 signedQuadrant(Self v) where Self : IQuadrant2D => v.signedQuadrant; + /// + public static int2 quadrantBasisX(Self v) where Self : IQuadrant2D => v.quadrantBasisX; + /// + public static (Unity.Mathematics.int2 x,Unity.Mathematics.int2 y) quadrantBasis(Self v) where Self : IQuadrant2D => v.quadrantBasis; + } + public static partial class mathfs { + /// + public static Int32 quadrant(rat2 v) => v.quadrant; + /// + public static Int32 signedQuadrant(rat2 v) => v.signedQuadrant; + /// + public static int2 quadrantBasisX(rat2 v) => v.quadrantBasisX; + /// + public static (Unity.Mathematics.int2 x,Unity.Mathematics.int2 y) quadrantBasis(rat2 v) => v.quadrantBasis; + /// + public static Int32 quadrant(inth2 v) => v.quadrant; + /// + public static Int32 signedQuadrant(inth2 v) => v.signedQuadrant; + /// + public static int2 quadrantBasisX(inth2 v) => v.quadrantBasisX; + /// + public static (Unity.Mathematics.int2 x,Unity.Mathematics.int2 y) quadrantBasis(inth2 v) => v.quadrantBasis; + /// + public static Int32 quadrant(this int2 v) => v.y switch {> 00 when v.x <= 0 => 1,<= 0 when v.x < 00 => 2,< 00 when v.x >= 0 => 3,_ => 0 }; + /// + public static Int32 signedQuadrant(this int2 v) => mathfs.quadrantToSignedQuadrant(v.quadrant()); + /// + public static int2 quadrantBasisX(this int2 v) => mathfs.quadrantToBasisX(v.quadrant()); + /// + public static (Unity.Mathematics.int2 x,Unity.Mathematics.int2 y) quadrantBasis(this int2 v) => mathfs.quadrantToBasis(v.quadrant()); + /// + public static Int32 quadrant(this Vector2Int v) => v.y switch {> 00 when v.x <= 0 => 1,<= 0 when v.x < 00 => 2,< 00 when v.x >= 0 => 3,_ => 0 }; + /// + public static Int32 signedQuadrant(this Vector2Int v) => mathfs.quadrantToSignedQuadrant(v.quadrant()); + /// + public static int2 quadrantBasisX(this Vector2Int v) => mathfs.quadrantToBasisX(v.quadrant()); + /// + public static (Unity.Mathematics.int2 x,Unity.Mathematics.int2 y) quadrantBasis(this Vector2Int v) => mathfs.quadrantToBasis(v.quadrant()); + /// + public static Int32 quadrant(this float2 v) => v.y switch {> 00 when v.x <= 0 => 1,<= 0 when v.x < 00 => 2,< 00 when v.x >= 0 => 3,_ => 0 }; + /// + public static Int32 signedQuadrant(this float2 v) => mathfs.quadrantToSignedQuadrant(v.ceilAwayFrom0().quadrant()); + /// + public static int2 quadrantBasisX(this float2 v) => mathfs.quadrantToBasisX(v.ceilAwayFrom0().quadrant()); + /// + public static (Unity.Mathematics.int2 x,Unity.Mathematics.int2 y) quadrantBasis(this float2 v) => mathfs.quadrantToBasis(v.ceilAwayFrom0().quadrant()); + /// + public static Int32 quadrant(this Vector2 v) => v.y switch {> 00 when v.x <= 0 => 1,<= 0 when v.x < 00 => 2,< 00 when v.x >= 0 => 3,_ => 0 }; + /// + public static Int32 signedQuadrant(this Vector2 v) => mathfs.quadrantToSignedQuadrant(v.ceilAwayFrom0().quadrant()); + /// + public static int2 quadrantBasisX(this Vector2 v) => mathfs.quadrantToBasisX(v.ceilAwayFrom0().quadrant()); + /// + public static (Unity.Mathematics.int2 x,Unity.Mathematics.int2 y) quadrantBasis(this Vector2 v) => mathfs.quadrantToBasis(v.ceilAwayFrom0().quadrant()); + /// + public static Int32 quadrant(this double2 v) => v.y switch {> 00 when v.x <= 0 => 1,<= 0 when v.x < 00 => 2,< 00 when v.x >= 0 => 3,_ => 0 }; + /// + public static Int32 signedQuadrant(this double2 v) => mathfs.quadrantToSignedQuadrant(v.ceilAwayFrom0().quadrant()); + /// + public static int2 quadrantBasisX(this double2 v) => mathfs.quadrantToBasisX(v.ceilAwayFrom0().quadrant()); + /// + public static (Unity.Mathematics.int2 x,Unity.Mathematics.int2 y) quadrantBasis(this double2 v) => mathfs.quadrantToBasis(v.ceilAwayFrom0().quadrant()); + } +} diff --git a/Runtime/Generated static functions/IQuadrant2D_static.cs.meta b/Runtime/Generated static functions/IQuadrant2D_static.cs.meta new file mode 100644 index 0000000..96e790a --- /dev/null +++ b/Runtime/Generated static functions/IQuadrant2D_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fe329ea050547bc4e86684778a02f71f \ No newline at end of file diff --git a/Runtime/Generated static functions/IRoundable_static.cs b/Runtime/Generated static functions/IRoundable_static.cs new file mode 100644 index 0000000..15cb285 --- /dev/null +++ b/Runtime/Generated static functions/IRoundable_static.cs @@ -0,0 +1,172 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static R floorToward0(Self v) where Self : IRoundable => v.floorToward0; + /// + public static R ceilAwayFrom0(Self v) where Self : IRoundable => v.ceilAwayFrom0; + /// + public static R floor(Self v) where Self : IRoundable => v.floor; + /// + public static R ceil(Self v) where Self : IRoundable => v.ceil; + /// + public static R round(Self v, RoundingDirection rounding) where Self : IRoundable => v.round(rounding); + } + public static partial class mathfs { + /// + public static Int32 floorToward0(rat v) => v.floorToward0; + /// + public static Int32 ceilAwayFrom0(rat v) => v.ceilAwayFrom0; + /// + public static Int32 floor(rat v) => v.floor; + /// + public static Int32 ceil(rat v) => v.ceil; + /// + public static Int32 round(rat v, RoundingDirection rounding) => v.round(rounding); + /// + public static int2 floorToward0(rat2 v) => v.floorToward0; + /// + public static int2 ceilAwayFrom0(rat2 v) => v.ceilAwayFrom0; + /// + public static int2 floor(rat2 v) => v.floor; + /// + public static int2 ceil(rat2 v) => v.ceil; + /// + public static int2 round(rat2 v, RoundingDirection rounding) => v.round(rounding); + /// + public static Int32 floorToward0(inth v) => v.floorToward0; + /// + public static Int32 ceilAwayFrom0(inth v) => v.ceilAwayFrom0; + /// + public static Int32 floor(inth v) => v.floor; + /// + public static Int32 ceil(inth v) => v.ceil; + /// + public static Int32 round(inth v, RoundingDirection rounding) => v.round(rounding); + /// + public static int2 floorToward0(inth2 v) => v.floorToward0; + /// + public static int2 ceilAwayFrom0(inth2 v) => v.ceilAwayFrom0; + /// + public static int2 floor(inth2 v) => v.floor; + /// + public static int2 ceil(inth2 v) => v.ceil; + /// + public static int2 round(inth2 v, RoundingDirection rounding) => v.round(rounding); + /// + public static Int32 floorToward0(this Single v) => (int)(v<0?math.ceil(v):math.floor(v)); + /// + public static Int32 ceilAwayFrom0(this Single v) => (int)(v<0?math.floor(v):math.ceil(v)); + /// + public static Int32 floor(this Single v) => (int)MathF.Floor(v); + /// + public static Int32 ceil(this Single v) => (int)MathF.Ceiling(v); + /// + public static Int32 round(this Single v, RoundingDirection rounding) => (int)MathF.Round( v, (MidpointRounding)rounding ); + /// + public static int2 floorToward0(this float2 v) => new(v.x.floorToward0(), v.y.floorToward0()); + /// + public static int2 ceilAwayFrom0(this float2 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0()); + /// + public static int2 floor(this float2 v) => (int2)math.floor(v); + /// + public static int2 ceil(this float2 v) => (int2)math.ceil(v); + /// + public static int2 round(this float2 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding)); + /// + public static int2 floorToward0(this Vector2 v) => new(v.x.floorToward0(), v.y.floorToward0()); + /// + public static int2 ceilAwayFrom0(this Vector2 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0()); + /// + public static int2 floor(this Vector2 v) => new(v.x.floor(), v.y.floor()); + /// + public static int2 ceil(this Vector2 v) => new(v.x.ceil(), v.y.ceil()); + /// + public static int2 round(this Vector2 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding)); + /// + public static int3 floorToward0(this float3 v) => new(v.x.floorToward0(), v.y.floorToward0(), v.z.floorToward0()); + /// + public static int3 ceilAwayFrom0(this float3 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0(), v.z.ceilAwayFrom0()); + /// + public static int3 floor(this float3 v) => (int3)math.floor(v); + /// + public static int3 ceil(this float3 v) => (int3)math.ceil(v); + /// + public static int3 round(this float3 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding), v.z.round(rounding)); + /// + public static int3 floorToward0(this Vector3 v) => new(v.x.floorToward0(), v.y.floorToward0(), v.z.floorToward0()); + /// + public static int3 ceilAwayFrom0(this Vector3 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0(), v.z.ceilAwayFrom0()); + /// + public static int3 floor(this Vector3 v) => new(v.x.floor(), v.y.floor(), v.z.floor()); + /// + public static int3 ceil(this Vector3 v) => new(v.x.ceil(), v.y.ceil(), v.z.ceil()); + /// + public static int3 round(this Vector3 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding), v.z.round(rounding)); + /// + public static int4 floorToward0(this float4 v) => new(v.x.floorToward0(), v.y.floorToward0(), v.z.floorToward0(), v.w.floorToward0()); + /// + public static int4 ceilAwayFrom0(this float4 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0(), v.z.ceilAwayFrom0(), v.w.ceilAwayFrom0()); + /// + public static int4 floor(this float4 v) => (int4)math.floor(v); + /// + public static int4 ceil(this float4 v) => (int4)math.ceil(v); + /// + public static int4 round(this float4 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding), v.z.round(rounding), v.w.round(rounding)); + /// + public static int4 floorToward0(this Vector4 v) => new(v.x.floorToward0(), v.y.floorToward0(), v.z.floorToward0(), v.w.floorToward0()); + /// + public static int4 ceilAwayFrom0(this Vector4 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0(), v.z.ceilAwayFrom0(), v.w.ceilAwayFrom0()); + /// + public static int4 floor(this Vector4 v) => new(v.x.floor(), v.y.floor(), v.z.floor(), v.w.floor()); + /// + public static int4 ceil(this Vector4 v) => new(v.x.ceil(), v.y.ceil(), v.z.ceil(), v.w.ceil()); + /// + public static int4 round(this Vector4 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding), v.z.round(rounding), v.w.round(rounding)); + /// + public static Int32 floorToward0(this Double v) => (int)(v<0?math.ceil(v):math.floor(v)); + /// + public static Int32 ceilAwayFrom0(this Double v) => (int)(v<0?math.floor(v):math.ceil(v)); + /// + public static Int32 floor(this Double v) => (int)Math.Floor(v); + /// + public static Int32 ceil(this Double v) => (int)Math.Ceiling(v); + /// + public static Int32 round(this Double v, RoundingDirection rounding) => (int)Math.Round( v, (MidpointRounding)rounding ); + /// + public static int2 floorToward0(this double2 v) => new(v.x.floorToward0(), v.y.floorToward0()); + /// + public static int2 ceilAwayFrom0(this double2 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0()); + /// + public static int2 floor(this double2 v) => (int2)math.floor(v); + /// + public static int2 ceil(this double2 v) => (int2)math.ceil(v); + /// + public static int2 round(this double2 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding)); + /// + public static int3 floorToward0(this double3 v) => new(v.x.floorToward0(), v.y.floorToward0(), v.z.floorToward0()); + /// + public static int3 ceilAwayFrom0(this double3 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0(), v.z.ceilAwayFrom0()); + /// + public static int3 floor(this double3 v) => (int3)math.floor(v); + /// + public static int3 ceil(this double3 v) => (int3)math.ceil(v); + /// + public static int3 round(this double3 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding), v.z.round(rounding)); + /// + public static int4 floorToward0(this double4 v) => new(v.x.floorToward0(), v.y.floorToward0(), v.z.floorToward0(), v.w.floorToward0()); + /// + public static int4 ceilAwayFrom0(this double4 v) => new(v.x.ceilAwayFrom0(), v.y.ceilAwayFrom0(), v.z.ceilAwayFrom0(), v.w.ceilAwayFrom0()); + /// + public static int4 floor(this double4 v) => (int4)math.floor(v); + /// + public static int4 ceil(this double4 v) => (int4)math.ceil(v); + /// + public static int4 round(this double4 v, RoundingDirection rounding) => new(v.x.round(rounding), v.y.round(rounding), v.z.round(rounding), v.w.round(rounding)); + } +} diff --git a/Runtime/Generated static functions/IRoundable_static.cs.meta b/Runtime/Generated static functions/IRoundable_static.cs.meta new file mode 100644 index 0000000..c620889 --- /dev/null +++ b/Runtime/Generated static functions/IRoundable_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 60d521fa58ac8be449b220f00b3a5718 \ No newline at end of file diff --git a/Runtime/Generated static functions/ISignedNumber_static.cs b/Runtime/Generated static functions/ISignedNumber_static.cs new file mode 100644 index 0000000..445420c --- /dev/null +++ b/Runtime/Generated static functions/ISignedNumber_static.cs @@ -0,0 +1,56 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static R sign(Self v) where Self : ISignedNumber => v.sign; + } + public static partial class mathfs { + /// + public static Int32 sign(rat v) => v.sign; + /// + public static int2 sign(rat2 v) => v.sign; + /// + public static Int32 sign(inth v) => v.sign; + /// + public static int2 sign(inth2 v) => v.sign; + /// + public static Int32 sign(this Int32 v) => Math.Sign(v); + /// + public static int2 sign(this int2 v) => new(v.x.sign(), v.y.sign()); + /// + public static int2 sign(this Vector2Int v) => new(v.x.sign(), v.y.sign()); + /// + public static int3 sign(this int3 v) => new(v.x.sign(), v.y.sign(), v.z.sign()); + /// + public static int3 sign(this Vector3Int v) => new(v.x.sign(), v.y.sign(), v.z.sign()); + /// + public static int4 sign(this int4 v) => new(v.x.sign(), v.y.sign(), v.z.sign(), v.w.sign()); + /// + public static Int32 sign(this Single v) => Math.Sign(v); + /// + public static int2 sign(this float2 v) => (int2)math.sign(v); + /// + public static int2 sign(this Vector2 v) => new(v.x.sign(), v.y.sign()); + /// + public static int3 sign(this float3 v) => (int3)math.sign(v); + /// + public static int3 sign(this Vector3 v) => new(v.x.sign(), v.y.sign(), v.z.sign()); + /// + public static int4 sign(this float4 v) => (int4)math.sign(v); + /// + public static int4 sign(this Vector4 v) => new(v.x.sign(), v.y.sign(), v.z.sign(), v.w.sign()); + /// + public static Int32 sign(this Double v) => Math.Sign(v); + /// + public static int2 sign(this double2 v) => (int2)math.sign(v); + /// + public static int3 sign(this double3 v) => (int3)math.sign(v); + /// + public static int4 sign(this double4 v) => (int4)math.sign(v); + } +} diff --git a/Runtime/Generated static functions/ISignedNumber_static.cs.meta b/Runtime/Generated static functions/ISignedNumber_static.cs.meta new file mode 100644 index 0000000..2d0458f --- /dev/null +++ b/Runtime/Generated static functions/ISignedNumber_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7894f569523cf8a4bbc22370ab9b661d \ No newline at end of file diff --git a/Runtime/Generated static functions/ISqrMag_static.cs b/Runtime/Generated static functions/ISqrMag_static.cs new file mode 100644 index 0000000..31b16b3 --- /dev/null +++ b/Runtime/Generated static functions/ISqrMag_static.cs @@ -0,0 +1,46 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static D magSq(Self v) where Self : ISqrMag => v.magSq; + } + public static partial class mathfs { + /// + public static rat magSq(rat2 v) => v.magSq; + /// + public static rat magSq(inth2 v) => v.magSq; + /// + public static Int32 magSq(this int2 v) => math.dot( v, v ); + /// + public static Int32 magSq(this Vector2Int v) => v.x*v.x+v.y*v.y; + /// + public static Int32 magSq(this int3 v) => math.dot( v, v ); + /// + public static Int32 magSq(this Vector3Int v) => v.x*v.x+v.y*v.y+v.z*v.z; + /// + public static Int32 magSq(this int4 v) => math.dot( v, v ); + /// + public static Single magSq(this float2 v) => math.dot( v, v ); + /// + public static Single magSq(this Vector2 v) => v.x*v.x+v.y*v.y; + /// + public static Single magSq(this float3 v) => math.dot( v, v ); + /// + public static Single magSq(this Vector3 v) => v.x*v.x+v.y*v.y+v.z*v.z; + /// + public static Single magSq(this float4 v) => math.dot( v, v ); + /// + public static Single magSq(this Vector4 v) => v.x*v.x+v.y*v.y+v.z*v.z+v.w*v.w; + /// + public static Double magSq(this double2 v) => math.dot( v, v ); + /// + public static Double magSq(this double3 v) => math.dot( v, v ); + /// + public static Double magSq(this double4 v) => math.dot( v, v ); + } +} diff --git a/Runtime/Generated static functions/ISqrMag_static.cs.meta b/Runtime/Generated static functions/ISqrMag_static.cs.meta new file mode 100644 index 0000000..b37a9ce --- /dev/null +++ b/Runtime/Generated static functions/ISqrMag_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 65fb0c42dc933ca45a1146f1360c6208 \ No newline at end of file diff --git a/Runtime/Generated static functions/IVec1Base_static.cs b/Runtime/Generated static functions/IVec1Base_static.cs new file mode 100644 index 0000000..5118bde --- /dev/null +++ b/Runtime/Generated static functions/IVec1Base_static.cs @@ -0,0 +1,114 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static C X(Self v) where Self : IVec1Base => v.X; + /// + public static V flipX(Self v) where Self : IVec1Base => v.flipX; + /// + public static V zeroX(Self v) where Self : IVec1Base => v.zeroX; + } + public static partial class mathfs { + /// + public static rat X(rat2 v) => v.X; + /// + public static rat2 flipX(rat2 v) => v.flipX; + /// + public static rat2 zeroX(rat2 v) => v.zeroX; + /// + public static inth X(inth2 v) => v.X; + /// + public static inth2 flipX(inth2 v) => v.flipX; + /// + public static inth2 zeroX(inth2 v) => v.zeroX; + /// + public static Int32 X(this int2 v) => v.x; + /// + public static int2 flipX(this int2 v) => new(-v.x, +v.y); + /// + public static int2 zeroX(this int2 v) => new(0, v.y); + /// + public static Int32 X(this Vector2Int v) => v.x; + /// + public static Vector2Int flipX(this Vector2Int v) => new(-v.x, +v.y); + /// + public static Vector2Int zeroX(this Vector2Int v) => new(0, v.y); + /// + public static Int32 X(this int3 v) => v.x; + /// + public static int3 flipX(this int3 v) => new(-v.x, +v.y, +v.z); + /// + public static int3 zeroX(this int3 v) => new(0, v.y, v.z); + /// + public static Int32 X(this Vector3Int v) => v.x; + /// + public static Vector3Int flipX(this Vector3Int v) => new(-v.x, +v.y, +v.z); + /// + public static Vector3Int zeroX(this Vector3Int v) => new(0, v.y, v.z); + /// + public static Int32 X(this int4 v) => v.x; + /// + public static int4 flipX(this int4 v) => new(-v.x, +v.y, +v.z, +v.w); + /// + public static int4 zeroX(this int4 v) => new(0, v.y, v.z, v.w); + /// + public static Single X(this float2 v) => v.x; + /// + public static float2 flipX(this float2 v) => new(-v.x, +v.y); + /// + public static float2 zeroX(this float2 v) => new(0, v.y); + /// + public static Single X(this Vector2 v) => v.x; + /// + public static Vector2 flipX(this Vector2 v) => new(-v.x, +v.y); + /// + public static Vector2 zeroX(this Vector2 v) => new(0, v.y); + /// + public static Single X(this float3 v) => v.x; + /// + public static float3 flipX(this float3 v) => new(-v.x, +v.y, +v.z); + /// + public static float3 zeroX(this float3 v) => new(0, v.y, v.z); + /// + public static Single X(this Vector3 v) => v.x; + /// + public static Vector3 flipX(this Vector3 v) => new(-v.x, +v.y, +v.z); + /// + public static Vector3 zeroX(this Vector3 v) => new(0, v.y, v.z); + /// + public static Single X(this float4 v) => v.x; + /// + public static float4 flipX(this float4 v) => new(-v.x, +v.y, +v.z, +v.w); + /// + public static float4 zeroX(this float4 v) => new(0, v.y, v.z, v.w); + /// + public static Single X(this Vector4 v) => v.x; + /// + public static Vector4 flipX(this Vector4 v) => new(-v.x, +v.y, +v.z, +v.w); + /// + public static Vector4 zeroX(this Vector4 v) => new(0, v.y, v.z, v.w); + /// + public static Double X(this double2 v) => v.x; + /// + public static double2 flipX(this double2 v) => new(-v.x, +v.y); + /// + public static double2 zeroX(this double2 v) => new(0, v.y); + /// + public static Double X(this double3 v) => v.x; + /// + public static double3 flipX(this double3 v) => new(-v.x, +v.y, +v.z); + /// + public static double3 zeroX(this double3 v) => new(0, v.y, v.z); + /// + public static Double X(this double4 v) => v.x; + /// + public static double4 flipX(this double4 v) => new(-v.x, +v.y, +v.z, +v.w); + /// + public static double4 zeroX(this double4 v) => new(0, v.y, v.z, v.w); + } +} diff --git a/Runtime/Generated static functions/IVec1Base_static.cs.meta b/Runtime/Generated static functions/IVec1Base_static.cs.meta new file mode 100644 index 0000000..9e896ba --- /dev/null +++ b/Runtime/Generated static functions/IVec1Base_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b162f4ffab01ef447b46fb232e67a4af \ No newline at end of file diff --git a/Runtime/Generated static functions/IVec2Base_static.cs b/Runtime/Generated static functions/IVec2Base_static.cs new file mode 100644 index 0000000..0d59d64 --- /dev/null +++ b/Runtime/Generated static functions/IVec2Base_static.cs @@ -0,0 +1,114 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static C Y(Self v) where Self : IVec2Base => v.Y; + /// + public static V flipY(Self v) where Self : IVec2Base => v.flipY; + /// + public static V zeroY(Self v) where Self : IVec2Base => v.zeroY; + } + public static partial class mathfs { + /// + public static rat Y(rat2 v) => v.Y; + /// + public static rat2 flipY(rat2 v) => v.flipY; + /// + public static rat2 zeroY(rat2 v) => v.zeroY; + /// + public static inth Y(inth2 v) => v.Y; + /// + public static inth2 flipY(inth2 v) => v.flipY; + /// + public static inth2 zeroY(inth2 v) => v.zeroY; + /// + public static Int32 Y(this int2 v) => v.y; + /// + public static int2 flipY(this int2 v) => new(+v.x, -v.y); + /// + public static int2 zeroY(this int2 v) => new(v.x, 0); + /// + public static Int32 Y(this Vector2Int v) => v.y; + /// + public static Vector2Int flipY(this Vector2Int v) => new(+v.x, -v.y); + /// + public static Vector2Int zeroY(this Vector2Int v) => new(v.x, 0); + /// + public static Int32 Y(this int3 v) => v.y; + /// + public static int3 flipY(this int3 v) => new(+v.x, -v.y, +v.z); + /// + public static int3 zeroY(this int3 v) => new(v.x, 0, v.z); + /// + public static Int32 Y(this Vector3Int v) => v.y; + /// + public static Vector3Int flipY(this Vector3Int v) => new(+v.x, -v.y, +v.z); + /// + public static Vector3Int zeroY(this Vector3Int v) => new(v.x, 0, v.z); + /// + public static Int32 Y(this int4 v) => v.y; + /// + public static int4 flipY(this int4 v) => new(+v.x, -v.y, +v.z, +v.w); + /// + public static int4 zeroY(this int4 v) => new(v.x, 0, v.z, v.w); + /// + public static Single Y(this float2 v) => v.y; + /// + public static float2 flipY(this float2 v) => new(+v.x, -v.y); + /// + public static float2 zeroY(this float2 v) => new(v.x, 0); + /// + public static Single Y(this Vector2 v) => v.y; + /// + public static Vector2 flipY(this Vector2 v) => new(+v.x, -v.y); + /// + public static Vector2 zeroY(this Vector2 v) => new(v.x, 0); + /// + public static Single Y(this float3 v) => v.y; + /// + public static float3 flipY(this float3 v) => new(+v.x, -v.y, +v.z); + /// + public static float3 zeroY(this float3 v) => new(v.x, 0, v.z); + /// + public static Single Y(this Vector3 v) => v.y; + /// + public static Vector3 flipY(this Vector3 v) => new(+v.x, -v.y, +v.z); + /// + public static Vector3 zeroY(this Vector3 v) => new(v.x, 0, v.z); + /// + public static Single Y(this float4 v) => v.y; + /// + public static float4 flipY(this float4 v) => new(+v.x, -v.y, +v.z, +v.w); + /// + public static float4 zeroY(this float4 v) => new(v.x, 0, v.z, v.w); + /// + public static Single Y(this Vector4 v) => v.y; + /// + public static Vector4 flipY(this Vector4 v) => new(+v.x, -v.y, +v.z, +v.w); + /// + public static Vector4 zeroY(this Vector4 v) => new(v.x, 0, v.z, v.w); + /// + public static Double Y(this double2 v) => v.y; + /// + public static double2 flipY(this double2 v) => new(+v.x, -v.y); + /// + public static double2 zeroY(this double2 v) => new(v.x, 0); + /// + public static Double Y(this double3 v) => v.y; + /// + public static double3 flipY(this double3 v) => new(+v.x, -v.y, +v.z); + /// + public static double3 zeroY(this double3 v) => new(v.x, 0, v.z); + /// + public static Double Y(this double4 v) => v.y; + /// + public static double4 flipY(this double4 v) => new(+v.x, -v.y, +v.z, +v.w); + /// + public static double4 zeroY(this double4 v) => new(v.x, 0, v.z, v.w); + } +} diff --git a/Runtime/Generated static functions/IVec2Base_static.cs.meta b/Runtime/Generated static functions/IVec2Base_static.cs.meta new file mode 100644 index 0000000..5d7ba59 --- /dev/null +++ b/Runtime/Generated static functions/IVec2Base_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5d1005de541dd9f4ea3c895f2c744050 \ No newline at end of file diff --git a/Runtime/Generated static functions/IVec2_static.cs b/Runtime/Generated static functions/IVec2_static.cs new file mode 100644 index 0000000..e7872be --- /dev/null +++ b/Runtime/Generated static functions/IVec2_static.cs @@ -0,0 +1,60 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static V rot90(Self v) where Self : IVec2 => v.rot90; + /// + public static V rotNeg90(Self v) where Self : IVec2 => v.rotNeg90; + /// + public static V rot180(Self v) where Self : IVec2 => v.rot180; + } + public static partial class mathfs { + /// + public static rat2 rot90(rat2 v) => v.rot90; + /// + public static rat2 rotNeg90(rat2 v) => v.rotNeg90; + /// + public static rat2 rot180(rat2 v) => v.rot180; + /// + public static inth2 rot90(inth2 v) => v.rot90; + /// + public static inth2 rotNeg90(inth2 v) => v.rotNeg90; + /// + public static inth2 rot180(inth2 v) => v.rot180; + /// + public static int2 rot90(this int2 v) => new(-v.y,v.x); + /// + public static int2 rotNeg90(this int2 v) => new(v.y,-v.x); + /// + public static int2 rot180(this int2 v) => new(-v.x,-v.y); + /// + public static Vector2Int rot90(this Vector2Int v) => new(-v.y,v.x); + /// + public static Vector2Int rotNeg90(this Vector2Int v) => new(v.y,-v.x); + /// + public static Vector2Int rot180(this Vector2Int v) => new(-v.x,-v.y); + /// + public static float2 rot90(this float2 v) => new(-v.y,v.x); + /// + public static float2 rotNeg90(this float2 v) => new(v.y,-v.x); + /// + public static float2 rot180(this float2 v) => new(-v.x,-v.y); + /// + public static Vector2 rot90(this Vector2 v) => new(-v.y,v.x); + /// + public static Vector2 rotNeg90(this Vector2 v) => new(v.y,-v.x); + /// + public static Vector2 rot180(this Vector2 v) => new(-v.x,-v.y); + /// + public static double2 rot90(this double2 v) => new(-v.y,v.x); + /// + public static double2 rotNeg90(this double2 v) => new(v.y,-v.x); + /// + public static double2 rot180(this double2 v) => new(-v.x,-v.y); + } +} diff --git a/Runtime/Generated static functions/IVec2_static.cs.meta b/Runtime/Generated static functions/IVec2_static.cs.meta new file mode 100644 index 0000000..2d0294a --- /dev/null +++ b/Runtime/Generated static functions/IVec2_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2982c80d636593d4a829b1e71935cc8a \ No newline at end of file diff --git a/Runtime/Generated static functions/IVec3Base_static.cs b/Runtime/Generated static functions/IVec3Base_static.cs new file mode 100644 index 0000000..44063d0 --- /dev/null +++ b/Runtime/Generated static functions/IVec3Base_static.cs @@ -0,0 +1,72 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static C Z(Self v) where Self : IVec3Base => v.Z; + /// + public static V flipZ(Self v) where Self : IVec3Base => v.flipZ; + /// + public static V zeroZ(Self v) where Self : IVec3Base => v.zeroZ; + } + public static partial class mathfs { + /// + public static Int32 Z(this int3 v) => v.z; + /// + public static int3 flipZ(this int3 v) => new(+v.x, +v.y, -v.z); + /// + public static int3 zeroZ(this int3 v) => new(v.x, v.y, 0); + /// + public static Int32 Z(this Vector3Int v) => v.z; + /// + public static Vector3Int flipZ(this Vector3Int v) => new(+v.x, +v.y, -v.z); + /// + public static Vector3Int zeroZ(this Vector3Int v) => new(v.x, v.y, 0); + /// + public static Int32 Z(this int4 v) => v.z; + /// + public static int4 flipZ(this int4 v) => new(+v.x, +v.y, -v.z, +v.w); + /// + public static int4 zeroZ(this int4 v) => new(v.x, v.y, 0, v.w); + /// + public static Single Z(this float3 v) => v.z; + /// + public static float3 flipZ(this float3 v) => new(+v.x, +v.y, -v.z); + /// + public static float3 zeroZ(this float3 v) => new(v.x, v.y, 0); + /// + public static Single Z(this Vector3 v) => v.z; + /// + public static Vector3 flipZ(this Vector3 v) => new(+v.x, +v.y, -v.z); + /// + public static Vector3 zeroZ(this Vector3 v) => new(v.x, v.y, 0); + /// + public static Single Z(this float4 v) => v.z; + /// + public static float4 flipZ(this float4 v) => new(+v.x, +v.y, -v.z, +v.w); + /// + public static float4 zeroZ(this float4 v) => new(v.x, v.y, 0, v.w); + /// + public static Single Z(this Vector4 v) => v.z; + /// + public static Vector4 flipZ(this Vector4 v) => new(+v.x, +v.y, -v.z, +v.w); + /// + public static Vector4 zeroZ(this Vector4 v) => new(v.x, v.y, 0, v.w); + /// + public static Double Z(this double3 v) => v.z; + /// + public static double3 flipZ(this double3 v) => new(+v.x, +v.y, -v.z); + /// + public static double3 zeroZ(this double3 v) => new(v.x, v.y, 0); + /// + public static Double Z(this double4 v) => v.z; + /// + public static double4 flipZ(this double4 v) => new(+v.x, +v.y, -v.z, +v.w); + /// + public static double4 zeroZ(this double4 v) => new(v.x, v.y, 0, v.w); + } +} diff --git a/Runtime/Generated static functions/IVec3Base_static.cs.meta b/Runtime/Generated static functions/IVec3Base_static.cs.meta new file mode 100644 index 0000000..3e372d6 --- /dev/null +++ b/Runtime/Generated static functions/IVec3Base_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a2313d7ddb5772b46b263caf79b61be4 \ No newline at end of file diff --git a/Runtime/Generated static functions/IVec4Base_static.cs b/Runtime/Generated static functions/IVec4Base_static.cs new file mode 100644 index 0000000..cf25429 --- /dev/null +++ b/Runtime/Generated static functions/IVec4Base_static.cs @@ -0,0 +1,42 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static C W(Self v) where Self : IVec4Base => v.W; + /// + public static V flipW(Self v) where Self : IVec4Base => v.flipW; + /// + public static V zeroW(Self v) where Self : IVec4Base => v.zeroW; + } + public static partial class mathfs { + /// + public static Int32 W(this int4 v) => v.w; + /// + public static int4 flipW(this int4 v) => new(+v.x, +v.y, +v.z, -v.w); + /// + public static int4 zeroW(this int4 v) => new(v.x, v.y, v.z, 0); + /// + public static Single W(this float4 v) => v.w; + /// + public static float4 flipW(this float4 v) => new(+v.x, +v.y, +v.z, -v.w); + /// + public static float4 zeroW(this float4 v) => new(v.x, v.y, v.z, 0); + /// + public static Single W(this Vector4 v) => v.w; + /// + public static Vector4 flipW(this Vector4 v) => new(+v.x, +v.y, +v.z, -v.w); + /// + public static Vector4 zeroW(this Vector4 v) => new(v.x, v.y, v.z, 0); + /// + public static Double W(this double4 v) => v.w; + /// + public static double4 flipW(this double4 v) => new(+v.x, +v.y, +v.z, -v.w); + /// + public static double4 zeroW(this double4 v) => new(v.x, v.y, v.z, 0); + } +} diff --git a/Runtime/Generated static functions/IVec4Base_static.cs.meta b/Runtime/Generated static functions/IVec4Base_static.cs.meta new file mode 100644 index 0000000..1d0b974 --- /dev/null +++ b/Runtime/Generated static functions/IVec4Base_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ffe9f8449b895c545902650e7116df95 \ No newline at end of file diff --git a/Runtime/Generated static functions/IVecComponents_static.cs b/Runtime/Generated static functions/IVecComponents_static.cs new file mode 100644 index 0000000..90c2a3c --- /dev/null +++ b/Runtime/Generated static functions/IVecComponents_static.cs @@ -0,0 +1,114 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static C cmin(Self v) where Self : IVecComponents => v.cmin; + /// + public static C cmax(Self v) where Self : IVecComponents => v.cmax; + /// + public static C csum(Self v) where Self : IVecComponents => v.csum; + } + public static partial class mathfs { + /// + public static rat cmin(rat2 v) => v.cmin; + /// + public static rat cmax(rat2 v) => v.cmax; + /// + public static rat csum(rat2 v) => v.csum; + /// + public static inth cmin(inth2 v) => v.cmin; + /// + public static inth cmax(inth2 v) => v.cmax; + /// + public static inth csum(inth2 v) => v.csum; + /// + public static Int32 cmin(this int2 v) => math.cmin(v); + /// + public static Int32 cmax(this int2 v) => math.cmax(v); + /// + public static Int32 csum(this int2 v) => math.csum(v); + /// + public static Int32 cmin(this Vector2Int v) => v.y.min(v.x); + /// + public static Int32 cmax(this Vector2Int v) => v.y.max(v.x); + /// + public static Int32 csum(this Vector2Int v) => v.x+v.y; + /// + public static Int32 cmin(this int3 v) => math.cmin(v); + /// + public static Int32 cmax(this int3 v) => math.cmax(v); + /// + public static Int32 csum(this int3 v) => math.csum(v); + /// + public static Int32 cmin(this Vector3Int v) => v.z.min(v.y.min(v.x)); + /// + public static Int32 cmax(this Vector3Int v) => v.z.max(v.y.max(v.x)); + /// + public static Int32 csum(this Vector3Int v) => v.x+v.y+v.z; + /// + public static Int32 cmin(this int4 v) => math.cmin(v); + /// + public static Int32 cmax(this int4 v) => math.cmax(v); + /// + public static Int32 csum(this int4 v) => math.csum(v); + /// + public static Single cmin(this float2 v) => math.cmin(v); + /// + public static Single cmax(this float2 v) => math.cmax(v); + /// + public static Single csum(this float2 v) => math.csum(v); + /// + public static Single cmin(this Vector2 v) => v.y.min(v.x); + /// + public static Single cmax(this Vector2 v) => v.y.max(v.x); + /// + public static Single csum(this Vector2 v) => v.x+v.y; + /// + public static Single cmin(this float3 v) => math.cmin(v); + /// + public static Single cmax(this float3 v) => math.cmax(v); + /// + public static Single csum(this float3 v) => math.csum(v); + /// + public static Single cmin(this Vector3 v) => v.z.min(v.y.min(v.x)); + /// + public static Single cmax(this Vector3 v) => v.z.max(v.y.max(v.x)); + /// + public static Single csum(this Vector3 v) => v.x+v.y+v.z; + /// + public static Single cmin(this float4 v) => math.cmin(v); + /// + public static Single cmax(this float4 v) => math.cmax(v); + /// + public static Single csum(this float4 v) => math.csum(v); + /// + public static Single cmin(this Vector4 v) => v.w.min(v.z.min(v.y.min(v.x))); + /// + public static Single cmax(this Vector4 v) => v.w.max(v.z.max(v.y.max(v.x))); + /// + public static Single csum(this Vector4 v) => v.x+v.y+v.z+v.w; + /// + public static Double cmin(this double2 v) => math.cmin(v); + /// + public static Double cmax(this double2 v) => math.cmax(v); + /// + public static Double csum(this double2 v) => math.csum(v); + /// + public static Double cmin(this double3 v) => math.cmin(v); + /// + public static Double cmax(this double3 v) => math.cmax(v); + /// + public static Double csum(this double3 v) => math.csum(v); + /// + public static Double cmin(this double4 v) => math.cmin(v); + /// + public static Double cmax(this double4 v) => math.cmax(v); + /// + public static Double csum(this double4 v) => math.csum(v); + } +} diff --git a/Runtime/Generated static functions/IVecComponents_static.cs.meta b/Runtime/Generated static functions/IVecComponents_static.cs.meta new file mode 100644 index 0000000..62428d2 --- /dev/null +++ b/Runtime/Generated static functions/IVecComponents_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d299effa917f60d4b83ce7e7dd05dedf \ No newline at end of file diff --git a/Runtime/Generated static functions/IVec_static.cs b/Runtime/Generated static functions/IVec_static.cs new file mode 100644 index 0000000..b5f5acf --- /dev/null +++ b/Runtime/Generated static functions/IVec_static.cs @@ -0,0 +1,114 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static C magChebyshev(Self v) where Self : IVec => v.magChebyshev; + /// + public static C magTaxicab(Self v) where Self : IVec => v.magTaxicab; + /// + public static Int32 pointSideOfPlane(Self v, V planePos, V planeNormal) where Self : IVec => v.pointSideOfPlane(planePos, planeNormal); + } + public static partial class mathfs { + /// + public static rat magChebyshev(rat2 v) => v.magChebyshev; + /// + public static rat magTaxicab(rat2 v) => v.magTaxicab; + /// + public static Int32 pointSideOfPlane(rat2 v, rat2 planePos, rat2 planeNormal) => v.pointSideOfPlane(planePos, planeNormal); + /// + public static inth magChebyshev(inth2 v) => v.magChebyshev; + /// + public static inth magTaxicab(inth2 v) => v.magTaxicab; + /// + public static Int32 pointSideOfPlane(inth2 v, inth2 planePos, inth2 planeNormal) => v.pointSideOfPlane(planePos, planeNormal); + /// + public static Int32 magChebyshev(this int2 v) => v.abs().cmax(); + /// + public static Int32 magTaxicab(this int2 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this int2 v, int2 planePos, int2 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Int32 magChebyshev(this Vector2Int v) => v.abs().cmax(); + /// + public static Int32 magTaxicab(this Vector2Int v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this Vector2Int v, Vector2Int planePos, Vector2Int planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Int32 magChebyshev(this int3 v) => v.abs().cmax(); + /// + public static Int32 magTaxicab(this int3 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this int3 v, int3 planePos, int3 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Int32 magChebyshev(this Vector3Int v) => v.abs().cmax(); + /// + public static Int32 magTaxicab(this Vector3Int v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this Vector3Int v, Vector3Int planePos, Vector3Int planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Int32 magChebyshev(this int4 v) => v.abs().cmax(); + /// + public static Int32 magTaxicab(this int4 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this int4 v, int4 planePos, int4 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Single magChebyshev(this float2 v) => v.abs().cmax(); + /// + public static Single magTaxicab(this float2 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this float2 v, float2 planePos, float2 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Single magChebyshev(this Vector2 v) => v.abs().cmax(); + /// + public static Single magTaxicab(this Vector2 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this Vector2 v, Vector2 planePos, Vector2 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Single magChebyshev(this float3 v) => v.abs().cmax(); + /// + public static Single magTaxicab(this float3 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this float3 v, float3 planePos, float3 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Single magChebyshev(this Vector3 v) => v.abs().cmax(); + /// + public static Single magTaxicab(this Vector3 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this Vector3 v, Vector3 planePos, Vector3 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Single magChebyshev(this float4 v) => v.abs().cmax(); + /// + public static Single magTaxicab(this float4 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this float4 v, float4 planePos, float4 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Single magChebyshev(this Vector4 v) => v.abs().cmax(); + /// + public static Single magTaxicab(this Vector4 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this Vector4 v, Vector4 planePos, Vector4 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Double magChebyshev(this double2 v) => v.abs().cmax(); + /// + public static Double magTaxicab(this double2 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this double2 v, double2 planePos, double2 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Double magChebyshev(this double3 v) => v.abs().cmax(); + /// + public static Double magTaxicab(this double3 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this double3 v, double3 planePos, double3 planeNormal) => (v-planePos).dot(planeNormal).sign(); + /// + public static Double magChebyshev(this double4 v) => v.abs().cmax(); + /// + public static Double magTaxicab(this double4 v) => v.abs().csum(); + /// + public static Int32 pointSideOfPlane(this double4 v, double4 planePos, double4 planeNormal) => (v-planePos).dot(planeNormal).sign(); + } +} diff --git a/Runtime/Generated static functions/IVec_static.cs.meta b/Runtime/Generated static functions/IVec_static.cs.meta new file mode 100644 index 0000000..a98bb5f --- /dev/null +++ b/Runtime/Generated static functions/IVec_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0a43132aa99fb064b82c4429d717e757 \ No newline at end of file diff --git a/Runtime/Generated static functions/IWedgeProduct_static.cs b/Runtime/Generated static functions/IWedgeProduct_static.cs new file mode 100644 index 0000000..666243b --- /dev/null +++ b/Runtime/Generated static functions/IWedgeProduct_static.cs @@ -0,0 +1,38 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static W wedge(Self a, V b) where Self : IWedgeProduct => a.wedge(b); + } + public static partial class mathfs { + /// + public static rat wedge(rat2 a, rat2 b) => a.wedge(b); + /// + public static rat wedge(inth2 a, inth2 b) => a.wedge(b); + /// + public static Int32 wedge(this int2 a, int2 b) => a.x*b.y - a.y*b.x; + /// + public static Int32 wedge(this Vector2Int a, Vector2Int b) => a.x*b.y - a.y*b.x; + /// + public static int3 wedge(this int3 a, int3 b) => new(a.y * b.z - a.z * b.y,a.z * b.x - a.x * b.z,a.x * b.y - a.y * b.x); + /// + public static Vector3Int wedge(this Vector3Int a, Vector3Int b) => new(a.y * b.z - a.z * b.y,a.z * b.x - a.x * b.z,a.x * b.y - a.y * b.x); + /// + public static Single wedge(this float2 a, float2 b) => a.x*b.y - a.y*b.x; + /// + public static Single wedge(this Vector2 a, Vector2 b) => a.x*b.y - a.y*b.x; + /// + public static float3 wedge(this float3 a, float3 b) => new(a.y * b.z - a.z * b.y,a.z * b.x - a.x * b.z,a.x * b.y - a.y * b.x); + /// + public static Vector3 wedge(this Vector3 a, Vector3 b) => new(a.y * b.z - a.z * b.y,a.z * b.x - a.x * b.z,a.x * b.y - a.y * b.x); + /// + public static Double wedge(this double2 a, double2 b) => a.x*b.y - a.y*b.x; + /// + public static double3 wedge(this double3 a, double3 b) => new(a.y * b.z - a.z * b.y,a.z * b.x - a.x * b.z,a.x * b.y - a.y * b.x); + } +} diff --git a/Runtime/Generated static functions/IWedgeProduct_static.cs.meta b/Runtime/Generated static functions/IWedgeProduct_static.cs.meta new file mode 100644 index 0000000..0fab0c4 --- /dev/null +++ b/Runtime/Generated static functions/IWedgeProduct_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 03e8288e5c11ccd44a34de518786894a \ No newline at end of file diff --git a/Runtime/Numerics/IComplex.cs b/Runtime/Numerics/IComplex.cs deleted file mode 100644 index 25b83f9..0000000 --- a/Runtime/Numerics/IComplex.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Unity.Mathematics; - -namespace Freya { - - /// Objects that can be treated like complex numbers - public interface IComplex { - /// Multiplies as if they were complex numbers. The resulting vector is "rotated" by the other, and scaled by its magnitude. - /// Note that this operation does not use any trigonometry or square roots, it's very cheap to use! - public M complexMul( V other ); - - /// The complex conjugate of this vector, if treated as a complex number. Which, in english, just means it negates the y component - public V complexConj { get; } - } - - public static partial class mathfs { - /// - public static M complexMul( V a, V b ) where V : IComplex => a.complexMul( b ); - - /// - public static rat2 complexMul( rat2 a, rat2 b ) => a.complexMul( b ); - - /// - public static rat2 complexMul( inth2 a, inth2 b ) => a.complexMul( b ); - - /// - public static int2 complexMul( this int2 a, int2 b ) => new(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); - - /// - public static float2 complexMul( this float2 a, float2 b ) => new(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); - - /// - public static double2 complexMul( this double2 a, double2 b ) => new(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); - - - /// - public static V complexConj( V v ) where V : IComplex => v.complexConj; - - /// - public static rat2 complexConj( rat2 v ) => v.complexConj; - - /// - public static inth2 complexConj( inth2 v ) => v.complexConj; - - /// - public static int2 complexConj( this int2 v ) => new(v.x, -v.y); - - /// - public static float2 complexConj( this float2 v ) => new(v.x, -v.y); - - /// - public static double2 complexConj( this double2 v ) => new(v.x, -v.y); - - } - -} \ No newline at end of file diff --git a/Runtime/Numerics/IDotProduct.cs b/Runtime/Numerics/IDotProduct.cs deleted file mode 100644 index 37ede22..0000000 --- a/Runtime/Numerics/IDotProduct.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Unity.Mathematics; - -namespace Freya { - - /// Objects that implement a dot product - public interface IDotProduct { - /// The dot product between two vectors. This is the sum of the product of each respective component - public D dot( B other ); - } - - public static partial class mathfs { - /// - public static D dot( A a, B b ) where A : IDotProduct => a.dot( b ); - - /// - public static rat dot( rat2 a, rat2 b ) => a.dot( b ); - - /// - public static rat dot( inth2 a, inth2 b ) => a.dot( b ); - - /// - public static rat dot( rat2 a, int2 b ) => a.dot( b ); - - /// - public static rat dot( int2 a, rat2 b ) => b.dot( b ); - - /// - public static int dot( this int2 a, int2 b ) => math.dot( a, b ); - - /// - public static float dot( this float2 a, float2 b ) => math.dot( a, b ); - - /// - public static double dot( this double2 a, double2 b ) => math.dot( a, b ); - } - -} \ No newline at end of file diff --git a/Runtime/Numerics/IHalfNumber.cs b/Runtime/Numerics/IHalfNumber.cs deleted file mode 100644 index e935cc2..0000000 --- a/Runtime/Numerics/IHalfNumber.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Unity.Mathematics; - -namespace Freya { - - public interface IHalfNumber { - /// Multiplies this by 2 and returns an integer value - public F times2 { get; } - } - - public static partial class mathfs { - /// - public static T times2( T v ) where T : IHalfNumber => v.times2; - - /// - public static int times2( inth v ) => v.times2; - - /// - public static int2 times2( inth2 v ) => v.times2; - } - -} \ No newline at end of file diff --git a/Runtime/Numerics/INumber.cs b/Runtime/Numerics/INumber.cs deleted file mode 100644 index b850d01..0000000 --- a/Runtime/Numerics/INumber.cs +++ /dev/null @@ -1,405 +0,0 @@ -// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) - -using System; -using Unity.Mathematics; - -namespace Freya { - - public interface INumber { - /// Returns whether this number is an integer - public bool isInteger { get; } - - /// Returns whether this vector is the zero vector - public bool isZero { get; } - - /// Returns whether this lies flat along at least one axis - public bool isOrthogonal { get; } - } - - public static partial class mathfs { - /// - public static bool isInteger( T v ) where T : INumber => v.isInteger; - - /// - public static bool isInteger( rat v ) => v.isInteger; - - /// - public static bool isInteger( rat2 v ) => v.isInteger; - - /// - public static bool isInteger( inth v ) => v.isInteger; - - /// - public static bool isInteger( inth2 v ) => v.isInteger; - - /// - public static bool isInteger( this int v ) => true; - - /// - public static bool isInteger( this int2 v ) => true; - - /// - public static bool isInteger( this int3 v ) => true; - - /// - public static bool isInteger( this int4 v ) => true; - - /// - public static bool isInteger( this float v ) => v == MathF.Truncate( v ); - - /// - public static bool isInteger( this float2 v ) => v.x.isInteger() && v.y.isInteger(); - - /// - public static bool isInteger( this float3 v ) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger(); - - /// - public static bool isInteger( this float4 v ) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger() && v.w.isInteger(); - - /// - public static bool isInteger( this double v ) => v == Math.Truncate( v ); - - /// - public static bool isInteger( this double2 v ) => v.x.isInteger() && v.y.isInteger(); - - /// - public static bool isInteger( this double3 v ) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger(); - - /// - public static bool isInteger( this double4 v ) => v.x.isInteger() && v.y.isInteger() && v.z.isInteger() && v.w.isInteger(); - - - /// - public static bool isZero( T v ) where T : INumber => v.isZero; - - /// - public static bool isZero( rat v ) => v.isZero; - - /// - public static bool isZero( rat2 v ) => v.isZero; - - /// - public static bool isZero( inth v ) => v.isZero; - - /// - public static bool isZero( inth2 v ) => v.isZero; - - /// - public static bool isZero( this int v ) => true; - - /// - public static bool isZero( this int2 v ) => true; - - /// - public static bool isZero( this int3 v ) => true; - - /// - public static bool isZero( this int4 v ) => true; - - /// - public static bool isZero( this float v ) => v == 0; - - /// - public static bool isZero( this float2 v ) => math.all( v == 0 ); - - /// - public static bool isZero( this float3 v ) => math.all( v == 0 ); - - /// - public static bool isZero( this float4 v ) => math.all( v == 0 ); - - /// - public static bool isZero( this double v ) => v == 0; - - /// - public static bool isZero( this double2 v ) => math.all( v == 0 ); - - /// - public static bool isZero( this double3 v ) => math.all( v == 0 ); - - /// - public static bool isZero( this double4 v ) => math.all( v == 0 ); - - - /// - public static bool isOrthogonal( T v ) where T : INumber => v.isOrthogonal; - - /// - public static bool isOrthogonal( rat v ) => v.isOrthogonal; - - /// - public static bool isOrthogonal( rat2 v ) => v.isOrthogonal; - - /// - public static bool isOrthogonal( inth v ) => v.isOrthogonal; - - /// - public static bool isOrthogonal( inth2 v ) => v.isOrthogonal; - - /// - public static bool isOrthogonal( this int v ) => true; - - /// - public static bool isOrthogonal( this int2 v ) => true; - - /// - public static bool isOrthogonal( this int3 v ) => true; - - /// - public static bool isOrthogonal( this int4 v ) => true; - - /// - public static bool isOrthogonal( this float v ) => v == 0; - - /// - public static bool isOrthogonal( this float2 v ) => math.all( v == 0 ); - - /// - public static bool isOrthogonal( this float3 v ) => math.all( v == 0 ); - - /// - public static bool isOrthogonal( this float4 v ) => math.all( v == 0 ); - - /// - public static bool isOrthogonal( this double v ) => v == 0; - - /// - public static bool isOrthogonal( this double2 v ) => math.all( v == 0 ); - - /// - public static bool isOrthogonal( this double3 v ) => math.all( v == 0 ); - - /// - public static bool isOrthogonal( this double4 v ) => math.all( v == 0 ); - - } - - public interface INumber : INumber { - /// Returns the absolute value of the number. Makes negative values positive - public N abs { get; } - - /// Returns the minimum of two numbers - public N min( N other ); - - /// Returns the maximum of two numbers - public N max( N other ); - - /// The vector from this point to the target. Equivalent to target - this - public N to( N target ); - - // I can't do this bc Unity uses older versions of C#: - // public static abstract R zero { get; } - // public static abstract R one { get; } - } - - public static partial class mathfs { - /// - public static T abs( T x ) where T : INumber => x.abs; - - /// - public static inth abs( inth x ) => x.abs; - - /// - public static rat abs( rat x ) => x.abs; - - /// - public static inth2 abs( inth2 x ) => x.abs; - - /// - public static rat2 abs( rat2 x ) => x.abs; - - /// - public static int abs( this int x ) => math.abs( x ); - - /// - public static int2 abs( this int2 x ) => math.abs( x ); - - /// - public static int3 abs( this int3 x ) => math.abs( x ); - - /// - public static int4 abs( this int4 x ) => math.abs( x ); - - /// - public static float abs( this float x ) => math.abs( x ); - - /// - public static float2 abs( this float2 x ) => math.abs( x ); - - /// - public static float3 abs( this float3 x ) => math.abs( x ); - - /// - public static float4 abs( this float4 x ) => math.abs( x ); - - /// - public static double abs( this double x ) => math.abs( x ); - - /// - public static double2 abs( this double2 x ) => math.abs( x ); - - /// - public static double3 abs( this double3 x ) => math.abs( x ); - - /// - public static double4 abs( this double4 x ) => math.abs( x ); - - - /// - public static T min( T a, T b ) where T : INumber => a.min( b ); - - /// - public static inth min( inth a, inth b ) => a.min( b ); - - /// - public static rat min( rat a, rat b ) => a.min( b ); - - /// - public static inth2 min( inth2 a, inth2 b ) => a.min( b ); - - /// - public static rat2 min( rat2 a, rat2 b ) => a.min( b ); - - /// - public static int min( this int a, int b ) => math.min( a, b ); - - /// - public static int2 min( this int2 a, int2 b ) => math.min( a, b ); - - /// - public static int3 min( this int3 a, int3 b ) => math.min( a, b ); - - /// - public static int4 min( this int4 a, int4 b ) => math.min( a, b ); - - /// - public static float min( this float a, float b ) => math.min( a, b ); - - /// - public static float2 min( this float2 a, float2 b ) => math.min( a, b ); - - /// - public static float3 min( this float3 a, float3 b ) => math.min( a, b ); - - /// - public static float4 min( this float4 a, float4 b ) => math.min( a, b ); - - /// - public static double min( this double a, double b ) => math.min( a, b ); - - /// - public static double2 min( this double2 a, double2 b ) => math.min( a, b ); - - /// - public static double3 min( this double3 a, double3 b ) => math.min( a, b ); - - /// - public static double4 min( this double4 a, double4 b ) => math.min( a, b ); - - - /// - public static T max( T a, T b ) where T : INumber => a.max( b ); - - /// - public static inth max( inth a, inth b ) => a.max( b ); - - /// - public static rat max( rat a, rat b ) => a.max( b ); - - /// - public static inth2 max( inth2 a, inth2 b ) => a.max( b ); - - /// - public static rat2 max( rat2 a, rat2 b ) => a.max( b ); - - /// - public static int max( this int a, int b ) => math.max( a, b ); - - /// - public static int2 max( this int2 a, int2 b ) => math.max( a, b ); - - /// - public static int3 max( this int3 a, int3 b ) => math.max( a, b ); - - /// - public static int4 max( this int4 a, int4 b ) => math.max( a, b ); - - /// - public static float max( this float a, float b ) => math.max( a, b ); - - /// - public static float2 max( this float2 a, float2 b ) => math.max( a, b ); - - /// - public static float3 max( this float3 a, float3 b ) => math.max( a, b ); - - /// - public static float4 max( this float4 a, float4 b ) => math.max( a, b ); - - /// - public static double max( this double a, double b ) => math.max( a, b ); - - /// - public static double2 max( this double2 a, double2 b ) => math.max( a, b ); - - /// - public static double3 max( this double3 a, double3 b ) => math.max( a, b ); - - /// - public static double4 max( this double4 a, double4 b ) => math.max( a, b ); - - - /// - public static T to( T a, T b ) where T : INumber => a.to( b ); - - /// - public static inth to( inth a, inth b ) => a.to( b ); - - /// - public static rat to( rat a, rat b ) => a.to( b ); - - /// - public static inth2 to( inth2 a, inth2 b ) => a.to( b ); - - /// - public static rat2 to( rat2 a, rat2 b ) => a.to( b ); - - /// - public static int to( this int a, int b ) => b - a; - - /// - public static int2 to( this int2 a, int2 b ) => b - a; - - /// - public static int3 to( this int3 a, int3 b ) => b - a; - - /// - public static int4 to( this int4 a, int4 b ) => b - a; - - /// - public static float to( this float a, float b ) => b - a; - - /// - public static float2 to( this float2 a, float2 b ) => b - a; - - /// - public static float3 to( this float3 a, float3 b ) => b - a; - - /// - public static float4 to( this float4 a, float4 b ) => b - a; - - /// - public static double to( this double a, double b ) => b - a; - - /// - public static double2 to( this double2 a, double2 b ) => b - a; - - /// - public static double3 to( this double3 a, double3 b ) => b - a; - - /// - public static double4 to( this double4 a, double4 b ) => b - a; - } - - -} \ No newline at end of file diff --git a/Runtime/Numerics/IQuadrant2D.cs b/Runtime/Numerics/IQuadrant2D.cs deleted file mode 100644 index 2796aca..0000000 --- a/Runtime/Numerics/IQuadrant2D.cs +++ /dev/null @@ -1,118 +0,0 @@ -using Unity.Mathematics; - -namespace Freya { - - /// Objects that reside within four quadrants in 2D - public interface IQuadrant2D { - /// The index of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, - /// increasing in the positive rotation direction/counter-clockwise. - /// Ambiguous positions pick the quadrant in the positive rotation direction.

    - /// Quadrant layout: - /// - /// 1 - /// 0 - /// - /// - /// 2 - /// 3 - ///
    - public int quadrant { get; } - /// The signed of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, - /// increasing in the positive rotation direction/counter-clockwise. - /// Ambiguous positions pick the quadrant in the positive rotation direction.

    - /// Quadrant layout: - /// - /// 1 - /// 0 - /// - /// - /// -2 - /// -1 - ///
    - public int signedQuadrant { get; } - /// The X-axis of the basis within the current quadrant. - /// Ambiguous positions pick the quadrant in the positive rotation direction. Zero-vectors return (1,0) - public int2 quadrantBasisX { get; } - /// Returns the two basis vectors of the quadrant that contains this position. - /// Ambiguous positions pick the quadrant in the positive rotation direction - public (int2 x, int2 y) quadrantBasis { get; } - } - - public static partial class mathfs { - /// - public static int quadrant( V v ) where V : IQuadrant2D => v.quadrant; - - /// - public static int quadrant( rat2 v ) => v.quadrant; - - /// - public static int quadrant( inth2 v ) => v.quadrant; - - /// - public static int quadrant( this int2 v ) => - v.y switch { - > 00 when v.x <= 0 => 1, - <= 0 when v.x < 00 => 2, - < 00 when v.x >= 0 => 3, - _ => 0 - }; - - /// - public static int quadrant( this float2 v ) => - v.y switch { - > 00 when v.x <= 0 => 1, - <= 0 when v.x < 00 => 2, - < 00 when v.x >= 0 => 3, - _ => 0 - }; - - /// - public static int quadrant( this double2 v ) => - v.y switch { - > 00 when v.x <= 0 => 1, - <= 0 when v.x < 00 => 2, - < 00 when v.x >= 0 => 3, - _ => 0 - }; - - - /// - public static int2 quadrantBasisX( V v ) where V : IQuadrant2D => v.quadrantBasisX; - - /// - public static int2 quadrantBasisX( rat2 v ) => v.quadrantBasisX; - - /// - public static int2 quadrantBasisX( inth2 v ) => v.quadrantBasisX; - - /// - public static int2 quadrantBasisX( this int2 v ) => quadrantToBasisX( v.quadrant() ); - - /// - public static int2 quadrantBasisX( this float2 v ) => quadrantToBasisX( v.quadrant() ); - - /// - public static int2 quadrantBasisX( this double2 v ) => quadrantToBasisX( v.quadrant() ); - - - /// - public static (int2 x, int2 y) quadrantBasis( V v ) where V : IQuadrant2D => v.quadrantBasis; - - /// - public static (int2 x, int2 y) quadrantBasis( rat2 v ) => v.quadrantBasis; - - /// - public static (int2 x, int2 y) quadrantBasis( inth2 v ) => v.quadrantBasis; - - /// - public static (int2 x, int2 y) quadrantBasis( this int2 v ) => quadrantToBasis( v.quadrant() ); - - /// - public static (int2 x, int2 y) quadrantBasis( this float2 v ) => quadrantToBasis( v.quadrant() ); - - /// - public static (int2 x, int2 y) quadrantBasis( this double2 v ) => quadrantToBasis( v.quadrant() ); - } - - -} \ No newline at end of file diff --git a/Runtime/Numerics/ISignedNumber.cs b/Runtime/Numerics/ISignedNumber.cs deleted file mode 100644 index 9b13d5d..0000000 --- a/Runtime/Numerics/ISignedNumber.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Unity.Mathematics; - -namespace Freya { - - public interface ISignedNumber : INumber { - /// Returns the sign of this number. Either -1, 0, or 1 - public R sign { get; } - } - - public static partial class mathfs { - /// - public static T sign( T v ) where T : ISignedNumber => v.sign; - - /// - public static int sign( this int i ) => Math.Sign( i ); - - /// - public static int2 sign( this int2 i ) => new(Math.Sign( i.x ), Math.Sign( i.y )); - - /// - public static int sign( this float i ) => Math.Sign( i ); - - /// - public static int sign( this double i ) => Math.Sign( i ); - } - -} \ No newline at end of file diff --git a/Runtime/Numerics/IVec1.cs b/Runtime/Numerics/IVec1.cs deleted file mode 100644 index c40bc52..0000000 --- a/Runtime/Numerics/IVec1.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Unity.Mathematics; - -namespace Freya { - - public interface IVec1 : IVec { - /// The X component of this vector - public C X { get; } - /// This vector with a reversed X component - public V flipX { get; } - /// This vector with a zeroed-out X component - public V zeroX { get; } - } - - // X component boilerplate - public static partial class mathfs { - - /// - public static C X( V v ) where V : IVec1 => v.X; - - /// - public static rat X( rat2 v ) => v.X; - - /// - public static inth X( inth2 v ) => v.X; - - /// - public static int X( this int2 v ) => v.x; - - /// - public static float X( this float2 v ) => v.x; - - /// - public static double X( this double2 v ) => v.x; - - /// - public static int X( this int3 v ) => v.x; - - /// - public static float X( this float3 v ) => v.x; - - /// - public static double X( this double3 v ) => v.x; - - /// - public static int X( this int4 v ) => v.x; - - /// - public static float X( this float4 v ) => v.x; - - /// - public static double X( this double4 v ) => v.x; - - - /// - public static V flipX( V v ) where V : IVec1 => v.flipX; - - /// - public static rat2 flipX( rat2 v ) => v.flipX; - - /// - public static inth2 flipX( inth2 v ) => v.flipX; - - /// - public static int2 flipX( this int2 v ) => new(-v.x, v.y); - - /// - public static float2 flipX( this float2 v ) => new(-v.x, v.y); - - /// - public static double2 flipX( this double2 v ) => new(-v.x, v.y); - - /// - public static int3 flipX( this int3 v ) => new(-v.x, v.y, v.z); - - /// - public static float3 flipX( this float3 v ) => new(-v.x, v.y, v.z); - - /// - public static double3 flipX( this double3 v ) => new(-v.x, v.y, v.z); - - /// - public static int4 flipX( this int4 v ) => new(-v.x, v.y, v.z, v.w); - - /// - public static float4 flipX( this float4 v ) => new(-v.x, v.y, v.z, v.w); - - /// - public static double4 flipX( this double4 v ) => new(-v.x, v.y, v.z, v.w); - - - /// - public static V zeroX( V v ) where V : IVec1 => v.zeroX; - - /// - public static rat2 zeroX( rat2 v ) => v.zeroX; - - /// - public static inth2 zeroX( inth2 v ) => v.zeroX; - - /// - public static int2 zeroX( this int2 v ) => new(0, v.y); - - /// - public static float2 zeroX( this float2 v ) => new(0, v.y); - - /// - public static double2 zeroX( this double2 v ) => new(0, v.y); - - /// - public static int3 zeroX( this int3 v ) => new(0, v.y, v.z); - - /// - public static float3 zeroX( this float3 v ) => new(0, v.y, v.z); - - /// - public static double3 zeroX( this double3 v ) => new(0, v.y, v.z); - - /// - public static int4 zeroX( this int4 v ) => new(0, v.y, v.z, v.w); - - /// - public static float4 zeroX( this float4 v ) => new(0, v.y, v.z, v.w); - - /// - public static double4 zeroX( this double4 v ) => new(0, v.y, v.z, v.w); - - } - -} \ No newline at end of file diff --git a/Runtime/Numerics/IVec1.cs.meta b/Runtime/Numerics/IVec1.cs.meta deleted file mode 100644 index af12661..0000000 --- a/Runtime/Numerics/IVec1.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 960c1180e5bc4e7c8c3f33cd22fb51f6 -timeCreated: 1775605803 \ No newline at end of file diff --git a/Runtime/Numerics/IVec2.cs b/Runtime/Numerics/IVec2.cs deleted file mode 100644 index 219d320..0000000 --- a/Runtime/Numerics/IVec2.cs +++ /dev/null @@ -1,196 +0,0 @@ -using Unity.Mathematics; - -namespace Freya { - - public interface IVec2 : IVec1, IQuadrant2D, IComplex { - /// The Y component of this vector - public C Y { get; } - /// This vector with a reversed Y component - public V flipY { get; } - /// This vector with a zeroed-out Y component - public V zeroY { get; } - - /// Rotates this vector in the positive rotation direction by 90 degrees. This is usually a counter-clockwise/left turn - public V rot90 { get; } - /// Rotates this vector in the negative rotation direction by 90 degrees. This is usually a clockwise/right turn - public V rotNeg90 { get; } - /// Rotates this vector by 180 degrees. Equivalent to negating this vector - public V rot180 { get; } - - // public V rot45chebyshev { get; } - // public V FromVector2( Vector2 v ); // should only happen for coarse things like inthalf2 and int. rational ones are messy here - } - - public static partial class mathfs { - /// - public static V rot90( V v ) where V : IVec2 => v.rot90; - - /// - public static rat2 rot90( rat2 v ) => v.rot90; - - /// - public static inth2 rot90( inth2 v ) => v.rot90; - - /// - public static int2 rot90( this int2 v ) => new(-v.y, v.x); - - /// - public static float2 rot90( this float2 v ) => new(-v.y, v.x); - - /// - public static double2 rot90( this double2 v ) => new(-v.y, v.x); - - - /// - public static V rotNeg90( V v ) where V : IVec2 => v.rotNeg90; - - /// - public static rat2 rotNeg90( rat2 v ) => v.rotNeg90; - - /// - public static inth2 rotNeg90( inth2 v ) => v.rotNeg90; - - /// - public static int2 rotNeg90( this int2 v ) => new(v.y, -v.x); - - /// - public static float2 rotNeg90( this float2 v ) => new(v.y, -v.x); - - /// - public static double2 rotNeg90( this double2 v ) => new(v.y, -v.x); - - - /// - public static V rot180( V v ) where V : IVec2 => v.rot180; - - /// - public static rat2 rot180( rat2 v ) => -v; - - /// - public static inth2 rot180( inth2 v ) => -v; - - /// - public static int2 rot180( this int2 v ) => -v; - - /// - public static float2 rot180( this float2 v ) => -v; - - /// - public static double2 rot180( this double2 v ) => -v; - - } - - // Y component boilerplate - public static partial class mathfs { - /// - public static C Y( V v ) where V : IVec2 => v.Y; - - /// - public static rat Y( rat2 v ) => v.Y; - - /// - public static inth Y( inth2 v ) => v.Y; - - /// - public static int Y( this int2 v ) => v.y; - - /// - public static float Y( this float2 v ) => v.y; - - /// - public static double Y( this double2 v ) => v.y; - - /// - public static int Y( this int3 v ) => v.y; - - /// - public static float Y( this float3 v ) => v.y; - - /// - public static double Y( this double3 v ) => v.y; - - /// - public static int Y( this int4 v ) => v.y; - - /// - public static float Y( this float4 v ) => v.y; - - /// - public static double Y( this double4 v ) => v.y; - - - /// - public static V flipY( V v ) where V : IVec2 => v.flipY; - - /// - public static rat2 flipY( rat2 v ) => v.flipY; - - /// - public static inth2 flipY( inth2 v ) => v.flipY; - - /// - public static int2 flipY( this int2 v ) => new(v.x, -v.y); - - /// - public static float2 flipY( this float2 v ) => new(v.x, -v.y); - - /// - public static double2 flipY( this double2 v ) => new(v.x, -v.y); - - /// - public static int3 flipY( this int3 v ) => new(v.x, -v.y, v.z); - - /// - public static float3 flipY( this float3 v ) => new(v.x, -v.y, v.z); - - /// - public static double3 flipY( this double3 v ) => new(v.x, -v.y, v.z); - - /// - public static int4 flipY( this int4 v ) => new(v.x, -v.y, v.z, v.w); - - /// - public static float4 flipY( this float4 v ) => new(v.x, -v.y, v.z, v.w); - - /// - public static double4 flipY( this double4 v ) => new(v.x, -v.y, v.z, v.w); - - - /// - public static V zeroY( V v ) where V : IVec2 => v.zeroY; - - /// - public static rat2 zeroY( rat2 v ) => v.zeroY; - - /// - public static inth2 zeroY( inth2 v ) => v.zeroY; - - /// - public static int2 zeroY( this int2 v ) => new(v.x, 0); - - /// - public static float2 zeroY( this float2 v ) => new(v.x, 0); - - /// - public static double2 zeroY( this double2 v ) => new(v.x, 0); - - /// - public static int3 zeroY( this int3 v ) => new(v.x, 0, v.z); - - /// - public static float3 zeroY( this float3 v ) => new(v.x, 0, v.z); - - /// - public static double3 zeroY( this double3 v ) => new(v.x, 0, v.z); - - /// - public static int4 zeroY( this int4 v ) => new(v.x, 0, v.z, v.w); - - /// - public static float4 zeroY( this float4 v ) => new(v.x, 0, v.z, v.w); - - /// - public static double4 zeroY( this double4 v ) => new(v.x, 0, v.z, v.w); - } - -} \ No newline at end of file diff --git a/Runtime/Numerics/IVecComponents.cs b/Runtime/Numerics/IVecComponents.cs deleted file mode 100644 index 11b7e31..0000000 --- a/Runtime/Numerics/IVecComponents.cs +++ /dev/null @@ -1,187 +0,0 @@ -using Unity.Mathematics; - -namespace Freya { - - public interface IVecComponents { - // todo: coooould make a C Component( V elem, int iAxis ) - /// Returns a component of this vector by index - public C this[ int i ] { get; } - /// The minimum of the components of this vector - public C cmin { get; } - /// The maximum of the components of this vector - public C cmax { get; } - /// The sum of the components of this vector - public C csum { get; } - } - - public static partial class mathfs { - - /// - public static C cmin( C v ) where C : IVecComponents => v.cmin; - - /// - public static rat cmin( rat v ) => v; - - /// - public static rat cmin( rat2 v ) => v.cmin; - - /// - public static inth cmin( inth v ) => v; - - /// - public static inth cmin( inth2 v ) => v.cmin; - - /// - public static int cmin( this int v ) => v; - - /// - public static int cmin( this int2 v ) => math.cmin( v ); - - /// - public static int cmin( this int3 v ) => math.cmin( v ); - - /// - public static int cmin( this int4 v ) => math.cmin( v ); - - /// - public static float cmin( this float v ) => v; - - /// - public static float cmin( this float2 v ) => math.cmin( v ); - - /// - public static float cmin( this float3 v ) => math.cmin( v ); - - /// - public static float cmin( this float4 v ) => math.cmin( v ); - - /// - public static double cmin( this double v ) => v; - - /// - public static double cmin( this double2 v ) => math.cmin( v ); - - /// - public static double cmin( this double3 v ) => math.cmin( v ); - - /// - public static double cmin( this double4 v ) => math.cmin( v ); - - - /// - public static C cmax( C v ) where C : IVecComponents => v.cmax; - - /// - public static rat cmax( rat v ) => v; - - /// - public static rat cmax( rat2 v ) => v.cmax; - - /// - public static inth cmax( inth v ) => v; - - /// - public static inth cmax( inth2 v ) => v.cmax; - - /// - public static int cmax( this int v ) => v; - - /// - public static int cmax( this int2 v ) => math.cmax( v ); - - /// - public static int cmax( this int3 v ) => math.cmax( v ); - - /// - public static int cmax( this int4 v ) => math.cmax( v ); - - /// - public static float cmax( this float v ) => v; - - /// - public static float cmax( this float2 v ) => math.cmax( v ); - - /// - public static float cmax( this float3 v ) => math.cmax( v ); - - /// - public static float cmax( this float4 v ) => math.cmax( v ); - - /// - public static double cmax( this double v ) => v; - - /// - public static double cmax( this double2 v ) => math.cmax( v ); - - /// - public static double cmax( this double3 v ) => math.cmax( v ); - - /// - public static double cmax( this double4 v ) => math.cmax( v ); - - - /// - public static C csum( C v ) where C : IVecComponents => v.csum; - - /// - public static rat csum( rat v ) => v; - - /// - public static rat csum( rat2 v ) => v.csum; - - /// - public static inth csum( inth v ) => v; - - /// - public static inth csum( inth2 v ) => v.csum; - - /// - public static int csum( this int v ) => v; - - /// - public static int csum( this int2 v ) => math.csum( v ); - - /// - public static int csum( this int3 v ) => math.csum( v ); - - /// - public static int csum( this int4 v ) => math.csum( v ); - - /// - public static float csum( this float v ) => v; - - /// - public static float csum( this float2 v ) => math.csum( v ); - - /// - public static float csum( this float3 v ) => math.csum( v ); - - /// - public static float csum( this float4 v ) => math.csum( v ); - - /// - public static double csum( this double v ) => v; - - /// - public static double csum( this double2 v ) => math.csum( v ); - - /// - public static double csum( this double3 v ) => math.csum( v ); - - /// - public static double csum( this double4 v ) => math.csum( v ); - - /// - public static int csum( this bool b ) => b ? 1 : 0; - - /// - public static int csum( this bool2 b ) => math.csum( (int2)b ); - - /// - public static int csum( this bool3 b ) => math.csum( (int3)b ); - - /// - public static int csum( this bool4 b ) => math.csum( (int4)b ); - } - -} \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces.meta b/Runtime/Numerics/Interfaces.meta new file mode 100644 index 0000000..6b0209c --- /dev/null +++ b/Runtime/Numerics/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0381adb3a8a820d4e9eed12a0a1d9c1f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Numerics/Interfaces/BinaryOpAttribute.cs b/Runtime/Numerics/Interfaces/BinaryOpAttribute.cs new file mode 100644 index 0000000..e3cdba2 --- /dev/null +++ b/Runtime/Numerics/Interfaces/BinaryOpAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace Freya { + + [AttributeUsage( AttributeTargets.Method | AttributeTargets.Property )] + public class BinaryOpAttribute : Attribute {} + +} \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/BinaryOpAttribute.cs.meta b/Runtime/Numerics/Interfaces/BinaryOpAttribute.cs.meta new file mode 100644 index 0000000..e14f391 --- /dev/null +++ b/Runtime/Numerics/Interfaces/BinaryOpAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a29244bda02e460aa637a7c0616ba8dd +timeCreated: 1776112169 \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IComplex.cs b/Runtime/Numerics/Interfaces/IComplex.cs new file mode 100644 index 0000000..609917a --- /dev/null +++ b/Runtime/Numerics/Interfaces/IComplex.cs @@ -0,0 +1,15 @@ +using Unity.Mathematics; + +namespace Freya { + + /// Objects that can be treated like complex numbers + public interface IComplex { + /// Multiplies as if they were complex numbers. The resulting vector is "rotated" by the other, and scaled by its magnitude. + /// Note that this operation does not use any trigonometry or square roots, it's very cheap to use! + [BinaryOp] public M complexMul( V other ); + + /// The complex conjugate of this vector, if treated as a complex number. Which, in english, just means it negates the y component + public V complexConj { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IComplex.cs.meta b/Runtime/Numerics/Interfaces/IComplex.cs.meta similarity index 100% rename from Runtime/Numerics/IComplex.cs.meta rename to Runtime/Numerics/Interfaces/IComplex.cs.meta diff --git a/Runtime/Numerics/Interfaces/IDotProduct.cs b/Runtime/Numerics/Interfaces/IDotProduct.cs new file mode 100644 index 0000000..8cbb893 --- /dev/null +++ b/Runtime/Numerics/Interfaces/IDotProduct.cs @@ -0,0 +1,17 @@ +using Unity.Mathematics; + +namespace Freya { + + /// Objects that implement a dot product + public interface IDotProduct : ISqrMag { + /// The dot product between two vectors. This is the sum of the product of each respective component + [BinaryOp] public D dot( B other ); + } + + public static partial class mathfs { + // todo: special case + /// + public static rat dot( int2 a, rat2 b ) => b.dot( b ); + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IDotProduct.cs.meta b/Runtime/Numerics/Interfaces/IDotProduct.cs.meta similarity index 100% rename from Runtime/Numerics/IDotProduct.cs.meta rename to Runtime/Numerics/Interfaces/IDotProduct.cs.meta diff --git a/Runtime/Numerics/Interfaces/IHalfNumber.cs b/Runtime/Numerics/Interfaces/IHalfNumber.cs new file mode 100644 index 0000000..e3cabf8 --- /dev/null +++ b/Runtime/Numerics/Interfaces/IHalfNumber.cs @@ -0,0 +1,8 @@ +namespace Freya { + + public interface IHalfNumber { + /// Multiplies this by 2 and returns an integer value + public F times2 { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IHalfNumber.cs.meta b/Runtime/Numerics/Interfaces/IHalfNumber.cs.meta similarity index 100% rename from Runtime/Numerics/IHalfNumber.cs.meta rename to Runtime/Numerics/Interfaces/IHalfNumber.cs.meta diff --git a/Runtime/Numerics/Interfaces/INumberBase.cs b/Runtime/Numerics/Interfaces/INumberBase.cs new file mode 100644 index 0000000..d74114c --- /dev/null +++ b/Runtime/Numerics/Interfaces/INumberBase.cs @@ -0,0 +1,41 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Mathematics; +using UnityEngine; + +namespace Freya { + + public interface INumberBase { + /// Returns whether this number is an integer + public bool isInteger { get; } + + /// Returns whether this vector is the zero vector + public bool isZero { get; } + + /// Returns whether this is zero or lies along a single axis + public bool isOrthogonal { get; } + + } + + public interface INumber : INumberBase { + /// Returns the absolute value of the number. Makes negative values positive + public N abs { get; } + + /// Returns the minimum of two numbers + [BinaryOp] public N min( N other ); + + /// Returns the maximum of two numbers + [BinaryOp] public N max( N other ); + + /// The vector from this point to the target. Equivalent to target - this + public N to( N target ); + + // I can't do this bc Unity uses older versions of C#: + // public static abstract R zero { get; } + // public static abstract R one { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/INumber.cs.meta b/Runtime/Numerics/Interfaces/INumberBase.cs.meta similarity index 100% rename from Runtime/Numerics/INumber.cs.meta rename to Runtime/Numerics/Interfaces/INumberBase.cs.meta diff --git a/Runtime/Numerics/Interfaces/IQuadrant2D.cs b/Runtime/Numerics/Interfaces/IQuadrant2D.cs new file mode 100644 index 0000000..b76253a --- /dev/null +++ b/Runtime/Numerics/Interfaces/IQuadrant2D.cs @@ -0,0 +1,41 @@ +using Unity.Mathematics; + +namespace Freya { + + /// Objects that reside within four quadrants in 2D + public interface IQuadrant2D { + /// The index of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, + /// increasing in the positive rotation direction/counter-clockwise. + /// Ambiguous positions pick the quadrant in the positive rotation direction.

    + /// Quadrant layout: + /// + /// 1 + /// 0 + /// + /// + /// 2 + /// 3 + ///
    + public int quadrant { get; } + /// The signed of the quadrant containing this position, indexed from 0 to 3, starting from 0 in the top right, + /// increasing in the positive rotation direction/counter-clockwise. + /// Ambiguous positions pick the quadrant in the positive rotation direction.

    + /// Quadrant layout: + /// + /// 1 + /// 0 + /// + /// + /// -2 + /// -1 + ///
    + public int signedQuadrant { get; } + /// The X-axis of the basis within the current quadrant. + /// Ambiguous positions pick the quadrant in the positive rotation direction. Zero-vectors return (1,0) + public int2 quadrantBasisX { get; } + /// Returns the two basis vectors of the quadrant that contains this position. + /// Ambiguous positions pick the quadrant in the positive rotation direction + public (int2 x, int2 y) quadrantBasis { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IQuadrant2D.cs.meta b/Runtime/Numerics/Interfaces/IQuadrant2D.cs.meta similarity index 100% rename from Runtime/Numerics/IQuadrant2D.cs.meta rename to Runtime/Numerics/Interfaces/IQuadrant2D.cs.meta diff --git a/Runtime/Numerics/IRoundable.cs b/Runtime/Numerics/Interfaces/IRoundable.cs similarity index 57% rename from Runtime/Numerics/IRoundable.cs rename to Runtime/Numerics/Interfaces/IRoundable.cs index 8f396ae..1188320 100644 --- a/Runtime/Numerics/IRoundable.cs +++ b/Runtime/Numerics/Interfaces/IRoundable.cs @@ -27,24 +27,6 @@ public interface IRoundable { public static partial class mathfs { /// public static R round( V v, RoundingDirection rounding = RoundingDirection.ToEven ) where V : IRoundable => v.round(); - - /// - public static int round( this float v, RoundingDirection rounding = RoundingDirection.ToEven ) => (int)MathF.Round( v, (MidpointRounding)rounding ); - - /// - public static int round( this double v, RoundingDirection rounding = RoundingDirection.ToEven ) => (int)Math.Round( v, (MidpointRounding)rounding ); - - /// - public static R floorToward0( V v ) where V : IRoundable => v.floorToward0; - - /// - public static R ceilAwayFrom0( V v ) where V : IRoundable => v.ceilAwayFrom0; - - /// - public static R floor( V v ) where V : IRoundable => v.floor; - - /// - public static R ceil( V v ) where V : IRoundable => v.ceil; } /// Basically the same as C#'s , but older .net versions don't have all the options diff --git a/Runtime/Numerics/IRoundable.cs.meta b/Runtime/Numerics/Interfaces/IRoundable.cs.meta similarity index 100% rename from Runtime/Numerics/IRoundable.cs.meta rename to Runtime/Numerics/Interfaces/IRoundable.cs.meta diff --git a/Runtime/Numerics/Interfaces/ISignedNumber.cs b/Runtime/Numerics/Interfaces/ISignedNumber.cs new file mode 100644 index 0000000..e522bc5 --- /dev/null +++ b/Runtime/Numerics/Interfaces/ISignedNumber.cs @@ -0,0 +1,16 @@ +using System; +using Unity.Mathematics; + +namespace Freya { + + public interface ISignedNumber : INumberBase { + /// Returns the sign of this number. Either -1, 0, or 1 + public R sign { get; } + } + + public static partial class mathfs { + /// + public static T sign( T v ) where T : ISignedNumber => v.sign; + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/ISignedNumber.cs.meta b/Runtime/Numerics/Interfaces/ISignedNumber.cs.meta similarity index 100% rename from Runtime/Numerics/ISignedNumber.cs.meta rename to Runtime/Numerics/Interfaces/ISignedNumber.cs.meta diff --git a/Runtime/Numerics/Interfaces/ISqrMag.cs b/Runtime/Numerics/Interfaces/ISqrMag.cs new file mode 100644 index 0000000..11ba365 --- /dev/null +++ b/Runtime/Numerics/Interfaces/ISqrMag.cs @@ -0,0 +1,8 @@ +namespace Freya { + + public interface ISqrMag { + /// The squared magnitude of this vector + public D magSq { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/ISqrMag.cs.meta b/Runtime/Numerics/Interfaces/ISqrMag.cs.meta new file mode 100644 index 0000000..aeb7076 --- /dev/null +++ b/Runtime/Numerics/Interfaces/ISqrMag.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: be0ef07e4c8844ed9bd026a0c11fe408 +timeCreated: 1776111941 \ No newline at end of file diff --git a/Runtime/Numerics/IVec.cs b/Runtime/Numerics/Interfaces/IVec.cs similarity index 82% rename from Runtime/Numerics/IVec.cs rename to Runtime/Numerics/Interfaces/IVec.cs index abaf156..3572745 100644 --- a/Runtime/Numerics/IVec.cs +++ b/Runtime/Numerics/Interfaces/IVec.cs @@ -4,11 +4,7 @@ namespace Freya { - - public interface IVec : IDotProduct, IWedgeProduct, IVecComponents { - - /// The squared magnitude of this vector - public D magSq { get; } + public interface IVec : INumber, IDotProduct, IVecComponents { /// The chebyshev magnitude of this vector. /// In chebyshev distance, diagonal distances are treated the same as orthogonal distances. @@ -19,7 +15,6 @@ public interface IVec : IDotProduct, IWedgeProduct, IVec /// This means the magnitude of (1,1) is 2, the magnitude of (2,2) is 4 public C magTaxicab { get; } - /// Returns whether this point is in front of or behind a plane. ///
      ///
    • returns +1 when in front of the plane
    • @@ -32,9 +27,4 @@ public interface IVec : IDotProduct, IWedgeProduct, IVec public int pointSideOfPlane( V planePos, V planeNormal ); } - public static partial class mathfs { - // todo - } - - } \ No newline at end of file diff --git a/Runtime/Numerics/IVec.cs.meta b/Runtime/Numerics/Interfaces/IVec.cs.meta similarity index 100% rename from Runtime/Numerics/IVec.cs.meta rename to Runtime/Numerics/Interfaces/IVec.cs.meta diff --git a/Runtime/Numerics/Interfaces/IVec2.cs b/Runtime/Numerics/Interfaces/IVec2.cs new file mode 100644 index 0000000..6885db9 --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVec2.cs @@ -0,0 +1,15 @@ +using Unity.Mathematics; + +namespace Freya { + + /// Operations that are unique to 2D vectors + public interface IVec2 : IVec2Base, IWedgeProduct, IQuadrant2D, IComplex { + /// Rotates this vector in the positive rotation direction by 90 degrees. This is usually a counter-clockwise/left turn + public V rot90 { get; } + /// Rotates this vector in the negative rotation direction by 90 degrees. This is usually a clockwise/right turn + public V rotNeg90 { get; } + /// Rotates this vector by 180 degrees. Equivalent to negating this vector + public V rot180 { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IVec2.cs.meta b/Runtime/Numerics/Interfaces/IVec2.cs.meta similarity index 100% rename from Runtime/Numerics/IVec2.cs.meta rename to Runtime/Numerics/Interfaces/IVec2.cs.meta diff --git a/Runtime/Numerics/Interfaces/IVec3.cs b/Runtime/Numerics/Interfaces/IVec3.cs new file mode 100644 index 0000000..89e2afe --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVec3.cs @@ -0,0 +1,6 @@ +namespace Freya { + + /// Operations that are unique to 3D vectors + public interface IVec3 : IVec3Base, IWedgeProduct {} + +} \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVec3.cs.meta b/Runtime/Numerics/Interfaces/IVec3.cs.meta new file mode 100644 index 0000000..dbc9224 --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVec3.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 971c31cdc8c9452c9c3648519ded3e49 +timeCreated: 1776039209 \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVec4.cs b/Runtime/Numerics/Interfaces/IVec4.cs new file mode 100644 index 0000000..f009b66 --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVec4.cs @@ -0,0 +1,6 @@ +namespace Freya { + + /// Operations that are unique to 4D vectors + public interface IVec4 : IVec4Base /*todo: , IWedgeProduct*/ {} + +} \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVec4.cs.meta b/Runtime/Numerics/Interfaces/IVec4.cs.meta new file mode 100644 index 0000000..d136d5c --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVec4.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b527c2f521a545239e46984ef6ce1f7b +timeCreated: 1776039214 \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVecBase.cs b/Runtime/Numerics/Interfaces/IVecBase.cs new file mode 100644 index 0000000..b3c375c --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVecBase.cs @@ -0,0 +1,42 @@ +namespace Freya { + + public interface IVec1Base : IVec { + /// The X component of this vector + public C X { get; } + /// This vector with a reversed X component + public V flipX { get; } + /// This vector with a zeroed-out X component + public V zeroX { get; } + } + + /// Operations for vectors 2D and above + public interface IVec2Base : IVec1Base { + /// The Y component of this vector + public C Y { get; } + /// This vector with a reversed Y component + public V flipY { get; } + /// This vector with a zeroed-out Y component + public V zeroY { get; } + } + + /// Operations for vectors 3D and above + public interface IVec3Base : IVec2Base { + /// The Z component of this vector + public C Z { get; } + /// This vector with a reversed Z component + public V flipZ { get; } + /// This vector with a zeroed-out Z component + public V zeroZ { get; } + } + + /// Operations for vectors 4D and above + public interface IVec4Base : IVec3Base { + /// The W component of this vector + public C W { get; } + /// This vector with a reversed W component + public V flipW { get; } + /// This vector with a zeroed-out W component + public V zeroW { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVecBase.cs.meta b/Runtime/Numerics/Interfaces/IVecBase.cs.meta new file mode 100644 index 0000000..dc47dfa --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVecBase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9bc3a385d7214b5195b24771d33ac1d5 +timeCreated: 1776111377 \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVecComponents.cs b/Runtime/Numerics/Interfaces/IVecComponents.cs new file mode 100644 index 0000000..570f26f --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVecComponents.cs @@ -0,0 +1,17 @@ +using Unity.Mathematics; + +namespace Freya { + + public interface IVecComponents { + // todo: coooould make a C Component( V elem, int iAxis ) + /// Returns a component of this vector by index + public C this[ int i ] { get; } + /// The minimum of the components of this vector + public C cmin { get; } + /// The maximum of the components of this vector + public C cmax { get; } + /// The sum of the components of this vector + public C csum { get; } + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/IVecComponents.cs.meta b/Runtime/Numerics/Interfaces/IVecComponents.cs.meta similarity index 100% rename from Runtime/Numerics/IVecComponents.cs.meta rename to Runtime/Numerics/Interfaces/IVecComponents.cs.meta diff --git a/Runtime/Numerics/IVectorMath.cs b/Runtime/Numerics/Interfaces/IVectorMath.cs similarity index 100% rename from Runtime/Numerics/IVectorMath.cs rename to Runtime/Numerics/Interfaces/IVectorMath.cs diff --git a/Runtime/Numerics/IVectorMath.cs.meta b/Runtime/Numerics/Interfaces/IVectorMath.cs.meta similarity index 100% rename from Runtime/Numerics/IVectorMath.cs.meta rename to Runtime/Numerics/Interfaces/IVectorMath.cs.meta diff --git a/Runtime/Numerics/IWedgeProduct.cs b/Runtime/Numerics/Interfaces/IWedgeProduct.cs similarity index 51% rename from Runtime/Numerics/IWedgeProduct.cs rename to Runtime/Numerics/Interfaces/IWedgeProduct.cs index 7f348bb..4fabc14 100644 --- a/Runtime/Numerics/IWedgeProduct.cs +++ b/Runtime/Numerics/Interfaces/IWedgeProduct.cs @@ -9,27 +9,12 @@ public interface IWedgeProduct { ///
    • In 3D, this returns a vector, and is effectively the same as the cross product /// (technically it's a bivector but whatever)
    • ///
    - public W wedge( V other ); + [BinaryOp] public W wedge( V other ); } - + public static partial class mathfs { /// public static W wedge( V a, V b ) where V : IWedgeProduct => a.wedge( b ); - - /// - public static rat wedge( rat2 a, rat2 b ) => a.wedge( b ); - - /// - public static rat wedge( inth2 a, inth2 b ) => a.wedge( b ); - - /// - public static int wedge( this int2 a, int2 b ) => a.x * b.y - a.y * b.x; - - /// - public static float wedge( this float2 a, float2 b ) => a.x * b.y - a.y * b.x; - - /// - public static double wedge( this double2 a, double2 b ) => a.x * b.y - a.y * b.x; } } \ No newline at end of file diff --git a/Runtime/Numerics/IWedgeProduct.cs.meta b/Runtime/Numerics/Interfaces/IWedgeProduct.cs.meta similarity index 100% rename from Runtime/Numerics/IWedgeProduct.cs.meta rename to Runtime/Numerics/Interfaces/IWedgeProduct.cs.meta diff --git a/Runtime/Numerics/inth2.cs b/Runtime/Numerics/inth2.cs index fbacaf9..062793c 100644 --- a/Runtime/Numerics/inth2.cs +++ b/Runtime/Numerics/inth2.cs @@ -28,7 +28,7 @@ namespace Freya { public inth2 flipX => new(-x, y); public inth2 flipY => new(x, -y); public inth this[ int i ] => i switch { 0 => x, 1 => y, _ => throw new IndexOutOfRangeException( i.ToString() ) }; - public bool isOrthogonal => ( ceilAwayFrom0 > 0 ).csum() <= 1; + public bool isOrthogonal => ( ceilAwayFrom0.abs() > 0 ).csum() <= 1; public bool isZero => x == 0 && y == 0; public inth2( inth x, inth y ) => ( this.x, this.y ) = ( x, y ); diff --git a/Runtime/Numerics/mathfs.cs b/Runtime/Numerics/mathfs.cs index 8911e7a..2363c53 100644 --- a/Runtime/Numerics/mathfs.cs +++ b/Runtime/Numerics/mathfs.cs @@ -1,4 +1,5 @@ -using Unity.Mathematics; +using System; +using Unity.Mathematics; using UnityEngine; namespace Freya { @@ -18,6 +19,8 @@ public static int2 quadrantToBasisX( int i ) => _ => new int2( +1, 00 ) }; + public static int quadrantToSignedQuadrant( int i ) => i switch { 1 => 1, 2 => -2, 3 => -1, _ => 0 }; + public static (int2 x, int2 y) quadrantToBasis( int i ) => i switch { 1 => ( new int2( 00, +1 ), new int2( -1, 00 ) ), @@ -26,15 +29,18 @@ public static (int2 x, int2 y) quadrantToBasis( int i ) => _ => ( new int2( +1, 00 ), new int2( 00, +1 ) ) }; - // todo: sort these: public static rat round( rat r, rat interval, RoundingDirection rounding = RoundingDirection.ToEven ) => ( r / interval ).round( rounding ) * interval; public static rat2 round( rat2 r, rat interval, RoundingDirection rounding = RoundingDirection.ToEven ) => new(round( r.x, interval ), round( r.y, interval )); public static rat2 round( rat2 r, rat2 intervals, RoundingDirection rounding = RoundingDirection.ToEven ) => new(round( r.x, intervals.x ), round( r.y, intervals.y )); - + + // todo: these should probably move to codegen + public static int csum( this bool b ) => b ? 1 : 0; + public static int csum( this bool2 b ) => math.csum( (int2)b ); + public static int csum( this bool3 b ) => math.csum( (int3)b ); + public static int csum( this bool4 b ) => math.csum( (int4)b ); // UNSORTED: - public static Rect expandFromCenter( this Rect r, float expansionPerSide ) { rat2 g = default; Debug.Log( complexConj( g ) ); @@ -64,18 +70,9 @@ public static int modDelta( int a, int b, int mod ) { public static int quadrantDelta( rat2 a, rat2 b ) => a.wedge( b ).sign * modDelta( a.quadrant, b.quadrant, 4 ); - public static int pointSideOfPlane( this inth2 p, inth2 planePos, inth2 planeNormal ) => p.pointSideOfPlane( planePos, planeNormal ); - public static int pointSideOfPlane( this int2 p, int2 planePos, int2 planeNormal ) => math.sign( math.dot( p - planePos, planeNormal ) ); - public static inth divideBy2( this int p ) => new() { h = p }; public static inth2 divideBy2( this int2 p ) => new(p.x.divideBy2(), p.y.divideBy2()); - - public static int signedQuadrant( this int2 v ) => v.quadrant() switch { 1 => +1, 2 => -2, 3 => -1, _ => 00 }; - - public static int magChebyshev( this int2 v ) => math.max( math.abs( v.x ), math.abs( v.y ) ); - public static float magChebyshev( this float2 v ) => math.max( math.abs( v.x ), math.abs( v.y ) ); - public static int2 rot45chebyshev( this int2 v ) { int m = v.magChebyshev(); return math.clamp( v + v.rot90(), new int2( -m, -m ), new int2( m, m ) ); @@ -83,7 +80,6 @@ public static int2 rot45chebyshev( this int2 v ) { public static float projectionTValue( float2 v, float2 n ) => math.dot( v, n ) / math.dot( n, n ); - public static rat projectionTValue( rat2 v, rat2 n ) => dot( v, n ) / dot( n, n ); public static rat projectionTValue( rat2 v, int2 n ) => dot( v, n ) / dot( n, n ); diff --git a/Runtime/Numerics/rat2.cs b/Runtime/Numerics/rat2.cs index 3521cf6..d517da0 100644 --- a/Runtime/Numerics/rat2.cs +++ b/Runtime/Numerics/rat2.cs @@ -11,7 +11,6 @@ namespace Freya { /// A 2D vector with rational components (ℚ² instead of ℝ²) [Serializable] public struct rat2 : IEquatable, IVec2, - INumber, ISignedNumber, IDotProduct, IRoundable { @@ -41,7 +40,7 @@ public static rat2 FromVector2( Vector2 v, int snapStepsPerUnit = 2 ) { public bool isZero => math.all( N == new int2( 0, 0 ) ); public bool isInteger => math.all( D == new int2( 1, 1 ) ); - public bool isOrthogonal => ( ceilAwayFrom0 > 0 ).csum() <= 1; + public bool isOrthogonal => ( ceilAwayFrom0.abs() > 0 ).csum() <= 1; public bool IsDiagonal => x.abs == y.abs; // Chebyshev distances From f747b4380e839dc88635b14aa8a7cf5bd8be5f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Tue, 14 Apr 2026 04:28:07 +0200 Subject: [PATCH 291/301] Vector projection codegen --- Editor/Codegen/NumericTypeInfo.cs | 122 ++++++++++++++++++ Editor/Codegen/StaticAccessExtensions.cs | 17 ++- .../IVec2_static.cs | 54 ++++---- .../IVecProjections_static.cs | 28 ++++ .../IVecProjections_static.cs.meta | 2 + Runtime/Numerics/Interfaces/IVec.cs | 1 + Runtime/Numerics/Interfaces/IVec2.cs | 2 +- Runtime/Numerics/Interfaces/IVecBase.cs | 2 +- .../Numerics/Interfaces/IVecProjections.cs | 9 ++ .../Interfaces/IVecProjections.cs.meta | 3 + Runtime/Numerics/inth2.cs | 3 +- Runtime/Numerics/mathfs.cs | 1 - Runtime/Numerics/rat2.cs | 5 +- 13 files changed, 211 insertions(+), 38 deletions(-) create mode 100644 Runtime/Generated static functions/IVecProjections_static.cs create mode 100644 Runtime/Generated static functions/IVecProjections_static.cs.meta create mode 100644 Runtime/Numerics/Interfaces/IVecProjections.cs create mode 100644 Runtime/Numerics/Interfaces/IVecProjections.cs.meta diff --git a/Editor/Codegen/NumericTypeInfo.cs b/Editor/Codegen/NumericTypeInfo.cs index 5dc5d01..f045d5c 100644 --- a/Editor/Codegen/NumericTypeInfo.cs +++ b/Editor/Codegen/NumericTypeInfo.cs @@ -53,6 +53,9 @@ NumType.Short16 or NumType.UShort16 or NumType.Int32 or NumType.UInt32 or NumType.Long64 or NumType.ULong64; + public bool IsFloatingPoint => numType is NumType.Float32 or NumType.Double64; + public bool IsHalfInteger => numType is NumType.IntHalf; + public bool IsRational => numType is NumType.Rational; public Type TypeAfterRounding => dims switch { @@ -81,6 +84,125 @@ NumType.Int32 or NumType.UInt32 or _ => throw new IndexOutOfRangeException() }; + public Type ScalarProjectionType { + get { + if( IsAlwaysIntegerValue || IsRational || IsHalfInteger ) + return typeof(rat); + if( IsFloatingPoint ) + return ComponentType; // double/float + throw new NotImplementedException(); + } + } + public Type VectorProjectionType { + get { + if( IsAlwaysIntegerValue || IsRational || IsHalfInteger ) + return GetTypeMatching( dims, typeof(rat) ); // integers and half-integers get type promoted to rational + return nType; + } + } + + public static Type GetTypeMatching( int dims, Type componentType ) { + if( componentType == typeof(bool) ) + switch( dims ) { + case 1: return typeof(bool); + case 2: return typeof(bool2); + case 3: return typeof(bool3); + case 4: return typeof(bool4); + } + if( componentType == typeof(int) ) + switch( dims ) { + case 1: return typeof(int); + case 2: return typeof(int2); + case 3: return typeof(int3); + case 4: return typeof(int4); + } + if( componentType == typeof(float) ) + switch( dims ) { + case 1: return typeof(float); + case 2: return typeof(float2); + case 3: return typeof(float3); + case 4: return typeof(float4); + } + if( componentType == typeof(double) ) + switch( dims ) { + case 1: return typeof(double); + case 2: return typeof(double2); + case 3: return typeof(double3); + case 4: return typeof(double4); + } + if( componentType == typeof(half) ) + switch( dims ) { + case 1: return typeof(half); + case 2: return typeof(half2); + case 3: return typeof(half3); + case 4: return typeof(half4); + } + if( componentType == typeof(byte) ) + switch( dims ) { + case 1: return typeof(byte); + // case 2: return typeof(byte2); + // case 3: return typeof(byte3); + // case 4: return typeof(byte4); + } + if( componentType == typeof(sbyte) ) + switch( dims ) { + case 1: return typeof(sbyte); + // case 2: return typeof(sbyte2); + // case 3: return typeof(sbyte3); + // case 4: return typeof(sbyte4); + } + if( componentType == typeof(short) ) + switch( dims ) { + case 1: return typeof(short); + // case 2: return typeof(short2); + // case 3: return typeof(short3); + // case 4: return typeof(short4); + } + if( componentType == typeof(ushort) ) + switch( dims ) { + case 1: return typeof(ushort); + // case 2: return typeof(ushort2); + // case 3: return typeof(ushort3); + // case 4: return typeof(ushort4); + } + if( componentType == typeof(uint) ) + switch( dims ) { + case 1: return typeof(uint); + case 2: return typeof(uint2); + case 3: return typeof(uint3); + case 4: return typeof(uint4); + } + if( componentType == typeof(long) ) + switch( dims ) { + case 1: return typeof(long); + // case 2: return typeof(long2); + // case 3: return typeof(long3); + // case 4: return typeof(long4); + } + if( componentType == typeof(ulong) ) + switch( dims ) { + case 1: return typeof(ulong); + // case 2: return typeof(ulong2); + // case 3: return typeof(ulong3); + // case 4: return typeof(ulong4); + } + if( componentType == typeof(inth) ) + switch( dims ) { + case 1: return typeof(inth); + case 2: return typeof(inth2); + // case 3: return typeof(inth3); + // case 4: return typeof(inth4); + } + if( componentType == typeof(rat) ) + switch( dims ) { + case 1: return typeof(rat); + case 2: return typeof(rat2); + // case 3: return typeof(rat3); + // case 4: return typeof(rat4); + } + throw new NotImplementedException( $"Missing implementation of a {dims}D {componentType.Name} vector" ); + } + } diff --git a/Editor/Codegen/StaticAccessExtensions.cs b/Editor/Codegen/StaticAccessExtensions.cs index 2e33d18..cd5994b 100644 --- a/Editor/Codegen/StaticAccessExtensions.cs +++ b/Editor/Codegen/StaticAccessExtensions.cs @@ -108,6 +108,7 @@ public static IEnumerable InterfacesOfExternalType( Type nType ) { Type D = C; // dot product result type Type M = V; // complex multiplication result type + interfaces.UnionWith( InterfacesOf( typeof(ISignedNumber<>).MakeGenericType( R ) ) ); if( info.numType is NumType.Double64 or NumType.Float32 or NumType.Half16 or NumType.Rational or NumType.IntHalf ) interfaces.UnionWith( InterfacesOf( typeof(IRoundable<>).MakeGenericType( R ) ) ); @@ -118,10 +119,12 @@ public static IEnumerable InterfacesOfExternalType( Type nType ) { break; case 2: Type W2 = D; // wedge product result type - interfaces.UnionWith( InterfacesOf( typeof(IVec2<,,,,>).MakeGenericType( V, C, D, W2, M ) ) ); + Type ProjSc2 = info.ScalarProjectionType; + interfaces.UnionWith( InterfacesOf( typeof(IVec2<,,,,,>).MakeGenericType( V, C, D, W2, M, ProjSc2 ) ) ); break; case 3: Type W3 = V; // wedge product result type + Type ProjSc3 = info.ScalarProjectionType; interfaces.UnionWith( InterfacesOf( typeof(IVec3<,,,>).MakeGenericType( V, C, D, W3 ) ) ); break; case 4: @@ -230,12 +233,12 @@ public static string GetCustomImplementation( Type iType, Type nType, string mem return $"(int{info.dims})math.sign({{0}})"; // floats/doubles return info.NewFromComps( ( i, c ) => $"{{0}}.{c}.sign()" ); } - } else if( iTypeDef == typeof(IVec2<,,,,>) ) { - if( member == nameof(IVec2.rot90) ) { + } else if( iTypeDef == typeof(IVec2<,,,,,>) ) { + if( member == nameof(IVec2.rot90) ) { return "new(-{0}.y,{0}.x)"; - } else if( member == nameof(IVec2.rotNeg90) ) { + } else if( member == nameof(IVec2.rotNeg90) ) { return "new({0}.y,-{0}.x)"; - } else if( member == nameof(IVec2.rot180) ) { + } else if( member == nameof(IVec2.rot180) ) { return "new(-{0}.x,-{0}.y)"; } } else if( iTypeDef == typeof(IRoundable<>) ) { @@ -310,6 +313,10 @@ public static string GetCustomImplementation( Type iType, Type nType, string mem nameof(IQuadrant2D.signedQuadrant) => $"mathfs.{nameof(mathfs.quadrantToSignedQuadrant)}({{0}}{rounding}.quadrant())", _ => throw new NotImplementedException() }; + } else if( iTypeDef == typeof(IVecProjections<,>) ) { + switch( member ) { + case nameof(IVecProjections.projTValue): return "{0}.dot({1})/{1}.dot({1})"; + } } Debug.LogWarning( $"Missing custom implementation for nType {nType} with iType {iType} member {member}" ); diff --git a/Runtime/Generated static functions/IVec2_static.cs b/Runtime/Generated static functions/IVec2_static.cs index e7872be..764bdb6 100644 --- a/Runtime/Generated static functions/IVec2_static.cs +++ b/Runtime/Generated static functions/IVec2_static.cs @@ -6,55 +6,55 @@ using UnityEngine; namespace Freya { public static partial class mathfs_generics { - /// - public static V rot90(Self v) where Self : IVec2 => v.rot90; - /// - public static V rotNeg90(Self v) where Self : IVec2 => v.rotNeg90; - /// - public static V rot180(Self v) where Self : IVec2 => v.rot180; + /// + public static V rot90(Self v) where Self : IVec2 => v.rot90; + /// + public static V rotNeg90(Self v) where Self : IVec2 => v.rotNeg90; + /// + public static V rot180(Self v) where Self : IVec2 => v.rot180; } public static partial class mathfs { - /// + /// public static rat2 rot90(rat2 v) => v.rot90; - /// + /// public static rat2 rotNeg90(rat2 v) => v.rotNeg90; - /// + /// public static rat2 rot180(rat2 v) => v.rot180; - /// + /// public static inth2 rot90(inth2 v) => v.rot90; - /// + /// public static inth2 rotNeg90(inth2 v) => v.rotNeg90; - /// + /// public static inth2 rot180(inth2 v) => v.rot180; - /// + /// public static int2 rot90(this int2 v) => new(-v.y,v.x); - /// + /// public static int2 rotNeg90(this int2 v) => new(v.y,-v.x); - /// + /// public static int2 rot180(this int2 v) => new(-v.x,-v.y); - /// + /// public static Vector2Int rot90(this Vector2Int v) => new(-v.y,v.x); - /// + /// public static Vector2Int rotNeg90(this Vector2Int v) => new(v.y,-v.x); - /// + /// public static Vector2Int rot180(this Vector2Int v) => new(-v.x,-v.y); - /// + /// public static float2 rot90(this float2 v) => new(-v.y,v.x); - /// + /// public static float2 rotNeg90(this float2 v) => new(v.y,-v.x); - /// + /// public static float2 rot180(this float2 v) => new(-v.x,-v.y); - /// + /// public static Vector2 rot90(this Vector2 v) => new(-v.y,v.x); - /// + /// public static Vector2 rotNeg90(this Vector2 v) => new(v.y,-v.x); - /// + /// public static Vector2 rot180(this Vector2 v) => new(-v.x,-v.y); - /// + /// public static double2 rot90(this double2 v) => new(-v.y,v.x); - /// + /// public static double2 rotNeg90(this double2 v) => new(v.y,-v.x); - /// + /// public static double2 rot180(this double2 v) => new(-v.x,-v.y); } } diff --git a/Runtime/Generated static functions/IVecProjections_static.cs b/Runtime/Generated static functions/IVecProjections_static.cs new file mode 100644 index 0000000..a129be8 --- /dev/null +++ b/Runtime/Generated static functions/IVecProjections_static.cs @@ -0,0 +1,28 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) +// Do not manually edit - this file is generated by MathfsCodegen.cs + +using System; +using Unity.Mathematics; +using UnityEngine; +namespace Freya { + public static partial class mathfs_generics { + /// + public static Proj projTValue(Self v, V n) where Self : IVecProjections => v.projTValue(n); + } + public static partial class mathfs { + /// + public static rat projTValue(rat2 v, rat2 n) => v.projTValue(n); + /// + public static rat projTValue(inth2 v, inth2 n) => v.projTValue(n); + /// + public static rat projTValue(this int2 v, int2 n) => v.dot(n)/n.dot(n); + /// + public static rat projTValue(this Vector2Int v, Vector2Int n) => v.dot(n)/n.dot(n); + /// + public static Single projTValue(this float2 v, float2 n) => v.dot(n)/n.dot(n); + /// + public static Single projTValue(this Vector2 v, Vector2 n) => v.dot(n)/n.dot(n); + /// + public static Double projTValue(this double2 v, double2 n) => v.dot(n)/n.dot(n); + } +} diff --git a/Runtime/Generated static functions/IVecProjections_static.cs.meta b/Runtime/Generated static functions/IVecProjections_static.cs.meta new file mode 100644 index 0000000..f5fc6d0 --- /dev/null +++ b/Runtime/Generated static functions/IVecProjections_static.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 11d10ab84ebdfdd46873c692b2bcc55e \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVec.cs b/Runtime/Numerics/Interfaces/IVec.cs index 3572745..4208bc2 100644 --- a/Runtime/Numerics/Interfaces/IVec.cs +++ b/Runtime/Numerics/Interfaces/IVec.cs @@ -25,6 +25,7 @@ public interface IVec : INumber, IDotProduct, IVecComponentsA point inside the plane /// The normal direction of the plane public int pointSideOfPlane( V planePos, V planeNormal ); + } } \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVec2.cs b/Runtime/Numerics/Interfaces/IVec2.cs index 6885db9..90c5cd8 100644 --- a/Runtime/Numerics/Interfaces/IVec2.cs +++ b/Runtime/Numerics/Interfaces/IVec2.cs @@ -3,7 +3,7 @@ namespace Freya { /// Operations that are unique to 2D vectors - public interface IVec2 : IVec2Base, IWedgeProduct, IQuadrant2D, IComplex { + public interface IVec2 : IVec2Base, IVecProjections, IWedgeProduct, IQuadrant2D, IComplex { /// Rotates this vector in the positive rotation direction by 90 degrees. This is usually a counter-clockwise/left turn public V rot90 { get; } /// Rotates this vector in the negative rotation direction by 90 degrees. This is usually a clockwise/right turn diff --git a/Runtime/Numerics/Interfaces/IVecBase.cs b/Runtime/Numerics/Interfaces/IVecBase.cs index b3c375c..d0f6d76 100644 --- a/Runtime/Numerics/Interfaces/IVecBase.cs +++ b/Runtime/Numerics/Interfaces/IVecBase.cs @@ -8,7 +8,7 @@ public interface IVec1Base : IVec { /// This vector with a zeroed-out X component public V zeroX { get; } } - + /// Operations for vectors 2D and above public interface IVec2Base : IVec1Base { /// The Y component of this vector diff --git a/Runtime/Numerics/Interfaces/IVecProjections.cs b/Runtime/Numerics/Interfaces/IVecProjections.cs new file mode 100644 index 0000000..383f6ab --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVecProjections.cs @@ -0,0 +1,9 @@ +namespace Freya { + + public interface IVecProjections { + /// Projects the vector onto a second vector, as a scalar t-value along the second vector. + /// The second vector does not need to be normalized, but if it is, the t-value is also a distance along that vector + public Proj projTValue( V n ); // => math.dot( v, n ) / math.dot( n, n ); + } + +} \ No newline at end of file diff --git a/Runtime/Numerics/Interfaces/IVecProjections.cs.meta b/Runtime/Numerics/Interfaces/IVecProjections.cs.meta new file mode 100644 index 0000000..f2117fb --- /dev/null +++ b/Runtime/Numerics/Interfaces/IVecProjections.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f99cf60bb61f4a33883593f524db0dc5 +timeCreated: 1776130282 \ No newline at end of file diff --git a/Runtime/Numerics/inth2.cs b/Runtime/Numerics/inth2.cs index 062793c..e9ef8cf 100644 --- a/Runtime/Numerics/inth2.cs +++ b/Runtime/Numerics/inth2.cs @@ -10,7 +10,7 @@ namespace Freya { /// A fixed precision data type for half-integers, using a single backing interger. For numbers like: 0, 0.5, 1, 1.5, 2, etc. [Serializable] public struct inth2 : IEquatable, - IVec2, + IVec2, INumber, ISignedNumber, IHalfNumber, @@ -69,6 +69,7 @@ public inth2 FromVector2( Vector2 v, RoundingDirection rounding = RoundingDirect public int2 quadrantBasisX => ceilAwayFrom0.quadrantBasisX(); public (int2 x, int2 y) quadrantBasis => ceilAwayFrom0.quadrantBasis(); public int pointSideOfPlane( inth2 planePos, inth2 planeNormal ) => this.times2.pointSideOfPlane( planePos.times2, planeNormal.times2 ); + public rat projTValue( inth2 n ) => this.dot( n ) / n.dot( n ); public rat2 complexMul( inth2 other ) => ( (rat2)times2.complexMul( other.times2 ) ) / 4; public inth2 complexConj => new(x, -y); diff --git a/Runtime/Numerics/mathfs.cs b/Runtime/Numerics/mathfs.cs index 2363c53..eed432e 100644 --- a/Runtime/Numerics/mathfs.cs +++ b/Runtime/Numerics/mathfs.cs @@ -79,7 +79,6 @@ public static int2 rot45chebyshev( this int2 v ) { } public static float projectionTValue( float2 v, float2 n ) => math.dot( v, n ) / math.dot( n, n ); - public static rat projectionTValue( rat2 v, rat2 n ) => dot( v, n ) / dot( n, n ); public static rat projectionTValue( rat2 v, int2 n ) => dot( v, n ) / dot( n, n ); diff --git a/Runtime/Numerics/rat2.cs b/Runtime/Numerics/rat2.cs index d517da0..c13e966 100644 --- a/Runtime/Numerics/rat2.cs +++ b/Runtime/Numerics/rat2.cs @@ -10,7 +10,7 @@ namespace Freya { /// A 2D vector with rational components (ℚ² instead of ℝ²) [Serializable] public struct rat2 : IEquatable, - IVec2, + IVec2, ISignedNumber, IDotProduct, IRoundable { @@ -56,9 +56,10 @@ public static rat2 FromVector2( Vector2 v, int snapStepsPerUnit = 2 ) { public int signedQuadrant => ceilAwayFrom0.signedQuadrant(); public int2 quadrantBasisX => ceilAwayFrom0.quadrantBasisX(); public (int2 x, int2 y) quadrantBasis => ceilAwayFrom0.quadrantBasis(); - public int pointSideOfPlane( rat2 planePos, rat2 planeNormal ) => ( this - planePos ).dot( planeNormal ).sign; public rat2 complexMul( rat2 other ) => new(x * other.x - y * other.y, x * other.y + y * other.x); public rat2 complexConj => new(x, -y); + public int pointSideOfPlane( rat2 planePos, rat2 planeNormal ) => ( this - planePos ).dot( planeNormal ).sign; + public rat projTValue( rat2 n ) => this.dot( n ) / n.dot( n ); public rat2 normalizedTaxicab => this / magTaxicab; From 294ea7a695113781b8403c404fe5fd64ad32dc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Thu, 16 Apr 2026 17:53:31 +0200 Subject: [PATCH 292/301] cleanup --- Runtime/Numerics/mathfs.cs | 1 - Runtime/Numerics/rat.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Runtime/Numerics/mathfs.cs b/Runtime/Numerics/mathfs.cs index eed432e..df07d5c 100644 --- a/Runtime/Numerics/mathfs.cs +++ b/Runtime/Numerics/mathfs.cs @@ -43,7 +43,6 @@ public static (int2 x, int2 y) quadrantToBasis( int i ) => // UNSORTED: public static Rect expandFromCenter( this Rect r, float expansionPerSide ) { rat2 g = default; - Debug.Log( complexConj( g ) ); r.xMin -= expansionPerSide; r.yMin -= expansionPerSide; r.xMax += expansionPerSide; diff --git a/Runtime/Numerics/rat.cs b/Runtime/Numerics/rat.cs index 05cbb0a..037e7e8 100644 --- a/Runtime/Numerics/rat.cs +++ b/Runtime/Numerics/rat.cs @@ -25,7 +25,6 @@ namespace Freya { public static readonly rat MaxValue = new(int.MaxValue, 1); public static readonly rat MinValue = new(int.MinValue, 1); - /// Creates an exact representation of a rational number /// The numerator of this number /// The denominator of this number From 90b3ccb7985383d4c984a38f092298e1735a0593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 07:03:14 +0200 Subject: [PATCH 293/301] quartic polynomial root solving + minor refactor --- Runtime/Curves/Polynomial.cs | 143 ++------------------- Runtime/Curves/Solve.cs | 187 ++++++++++++++++++++++++++++ Runtime/Curves/Solve.cs.meta | 3 + Runtime/ResultsMax4FloatExt.cs | 49 ++++++++ Runtime/ResultsMax4FloatExt.cs.meta | 3 + Runtime/UtilityTypes.cs | 128 ++++++++++++++++++- 6 files changed, 381 insertions(+), 132 deletions(-) create mode 100644 Runtime/Curves/Solve.cs create mode 100644 Runtime/Curves/Solve.cs.meta create mode 100644 Runtime/ResultsMax4FloatExt.cs create mode 100644 Runtime/ResultsMax4FloatExt.cs.meta diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index 56c5231..ea71c16 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -3,7 +3,7 @@ using System; using System.Runtime.CompilerServices; using System.Text; -using UnityEngine; +using Unity.Mathematics; using UnityEngine.Serialization; namespace Freya { @@ -51,7 +51,7 @@ namespace Freya { /// Creates a polynomial /// The coefficients to use - public Polynomial( Vector4 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.x, coefficients.y, coefficients.z, coefficients.w ); + public Polynomial( float4 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.x, coefficients.y, coefficients.z, coefficients.w ); /// public Polynomial( Matrix4x1 coefficients ) => ( c0, c1, c2, c3 ) = ( coefficients.m0, coefficients.m1, coefficients.m2, coefficients.m3 ); @@ -252,7 +252,7 @@ public static Polynomial FitCubicFrom0( float x1, float x2, float x3, float y0, } /// Calculates the roots (values where this polynomial = 0) - public ResultsMax3 Roots => GetCubicRoots( c0, c1, c2, c3 ); + public ResultsMax3 Roots => Solve.Polynomial( c0, c1, c2, c3 ); /// Calculates the local extrema of this polynomial public ResultsMax2 LocalExtrema => (ResultsMax2)Differentiate().Roots; @@ -296,7 +296,7 @@ public FloatRange OutputRange01 { /// Creates a linear polynomial of the form ax+b from two points a and b /// The first point /// The second point - public static Polynomial Linear( Vector2 a, Vector2 b ) => Linear( a.x, a.y, b.x, b.y ); + public static Polynomial Linear( float2 a, float2 b ) => Linear( a.x, a.y, b.x, b.y ); /// Creates a linear polynomial of the form ax+b from two points /// The coordinate of the first point @@ -321,59 +321,18 @@ public static Polynomial Linear( float x0, float y0, float x1, float y1 ) { /// The cubic coefficient public static Polynomial Cubic( float c0, float c1, float c2, float c3 ) => new Polynomial( c0, c1, c2, c3 ); - static bool ValueAlmost0( float v ) => Mathfs.Approximately( v, 0 ); - /// Given the coefficients for a cubic polynomial, returns the net polynomial type/degree, accounting for values very close to 0 /// The constant coefficient /// The linear coefficient /// The quadratic coefficient /// The cubic coefficient - [MethodImpl( INLINE )] public static int GetPolynomialDegree( float c0, float c1, float c2, float c3 ) => ValueAlmost0( c3 ) ? GetPolynomialDegree( c0, c1, c2 ) : 3; - - /// Given the coefficients for a quadratic polynomial, returns the net polynomial degree, accounting for values very close to 0 - /// The constant coefficient - /// The linear coefficient - /// The quadratic coefficient - [MethodImpl( INLINE )] public static int GetPolynomialDegree( float c0, float c1, float c2 ) => ValueAlmost0( c2 ) ? GetPolynomialDegree( c0, c1 ) : 2; - - /// Given the coefficients for a linear polynomial, returns the net polynomial degree, accounting for values very close to 0 - /// The constant coefficient - /// The linear coefficient - [MethodImpl( INLINE )] public static int GetPolynomialDegree( float c0, float c1 ) => ValueAlmost0( c1 ) ? 0 : 1; - - /// Returns the roots/solutions/x-values where this polynomial equals 0. There's either 0, 1, 2 or 3 roots, filled in left to right among the return values - /// The constant coefficient - /// The linear coefficient - /// The quadratic coefficient - /// The cubic coefficient - public static ResultsMax3 GetCubicRoots( float c0, float c1, float c2, float c3 ) => - GetPolynomialDegree( c0, c1, c2, c3 ) switch { - 0 => default, // either no roots or infinite roots if c == 0 - 1 => new ResultsMax3( SolveLinearRoot( c1, c0 ) ), - 2 => SolveQuadraticRoots( c2, c1, c0 ), - 3 => SolveCubicRoots( c3, c2, c1, c0 ), - _ => throw new IndexOutOfRangeException() - }; - - /// Returns the roots/solutions/x-values where this polynomial equals 0. There's either 0, 1 or 2 roots, filled in left to right among the return values - /// The constant coefficient - /// The linear coefficient - /// The quadratic coefficient - public static ResultsMax2 GetQuadraticRoots( float c0, float c1, float c2 ) => - GetPolynomialDegree( c0, c1, c2 ) switch { - 0 => default, // either no roots or infinite roots if c == 0 - 1 => new ResultsMax2( SolveLinearRoot( c1, c0 ) ), - 2 => SolveQuadraticRoots( c2, c1, c0 ), - _ => throw new IndexOutOfRangeException() - }; - - /// Returns the roots/solutions/x-values where this polynomial equals 0. Returns null if there is no root - /// The constant coefficient - /// The linear coefficient - public static float? GetLinearRoots( float c0, float c1 ) { - if( GetPolynomialDegree( c0, c1 ) == 0 ) - return null; - return -c0 / c1; + /// The quartic coefficient + [MethodImpl( INLINE )] public static int GetPolynomialDegree( float c0, float c1 = 0, float c2 = 0, float c3 = 0, float c4 = 0 ) { + if( Mathfs.Approximately( c4, 0 ) == false ) return 4; + if( Mathfs.Approximately( c3, 0 ) == false ) return 3; + if( Mathfs.Approximately( c2, 0 ) == false ) return 2; + if( Mathfs.Approximately( c1, 0 ) == false ) return 1; + return 0; } /// Linearly interpolates between two polynomials @@ -388,85 +347,6 @@ public static Polynomial Lerp( Polynomial a, Polynomial b, float t ) => t.Lerp( a.c3, b.c3 ) ); - #region Internal root solvers - - // These functions lack safety checks (division by zero etc.) for lower degree equivalency - they presume "a" is always nonzero. - // These are private to avoid people mistaking them for the more stable/safe functions you are more likely to want to use - - [MethodImpl( INLINE )] static float SolveLinearRoot( float a, float b ) => -b / a; - - static ResultsMax2 SolveQuadraticRoots( float a, float b, float c ) { - float rootContent = b * b - 4 * a * c; - if( ValueAlmost0( rootContent ) ) - return new ResultsMax2( -b / ( 2 * a ) ); // two equivalent solutions at one point - - if( rootContent >= 0 ) { // crosses at two points - float u = -b * -( b < 0 ? -1 : 1 ) * MathF.Sqrt( rootContent ); - float r0 = u / ( 2 * a ); - float r1 = ( 2 * c ) / u; - return new ResultsMax2( MathF.Min( r0, r1 ), MathF.Max( r0, r1 ) ); - } - - return default; // no roots - } - - static ResultsMax3 SolveCubicRoots( float a, float b, float c, float d ) { - // first, depress the cubic to make it easier to solve - float aa = a * a; - float ac = a * c; - float bb = b * b; - float p = ( 3 * ac - bb ) / ( 3 * aa ); - float q = ( 2 * bb * b - 9 * ac * b + 27 * aa * d ) / ( 27 * aa * a ); - - ResultsMax3 dpr = SolveDepressedCubicRoots( p, q ); - - // we now have the roots of the depressed cubic, now convert back to the normal cubic - float UndepressRoot( float r ) => r - b / ( 3 * a ); - switch( dpr.count ) { - case 1: return new ResultsMax3( UndepressRoot( dpr.a ) ); - case 2: return new ResultsMax3( UndepressRoot( dpr.a ), UndepressRoot( dpr.b ) ); - case 3: return new ResultsMax3( UndepressRoot( dpr.a ), UndepressRoot( dpr.b ), UndepressRoot( dpr.c ) ); - default: return default; - } - } - - // t³+pt+q = 0 - static ResultsMax3 SolveDepressedCubicRoots( float p, float q ) { - if( ValueAlmost0( p ) ) // triple root - one solution. solve x³+q = 0 => x = cr(-q) - return new ResultsMax3( Mathfs.Cbrt( -q ) ); - float discriminant = 4 * p * p * p + 27 * q * q; - if( discriminant < 0.00001 ) { // two or three roots guaranteed, use trig solution - float pre = 2 * MathF.Sqrt( -p / 3 ); - float acosInner = ( ( 3 * q ) / ( 2 * p ) ) * MathF.Sqrt( -3 / p ); - - float GetRoot( int k ) => pre * MathF.Cos( ( 1f / 3f ) * Mathfs.Acos( acosInner.ClampNeg1to1() ) - ( Mathfs.TAU / 3f ) * k ); - // if acos hits 0 or TAU/2, the offsets will have the same value, - // which means we have a double root plus one regular root on our hands - if( acosInner >= 0.9999f ) - return new ResultsMax3( GetRoot( 0 ), GetRoot( 2 ) ); // two roots - one single and one double root - if( acosInner <= -0.9999f ) - return new ResultsMax3( GetRoot( 1 ), GetRoot( 2 ) ); // two roots - one single and one double root - return new ResultsMax3( GetRoot( 0 ), GetRoot( 1 ), GetRoot( 2 ) ); // three roots - } - - if( discriminant > 0 && p < 0 ) { // one root - float coshInner = ( 1f / 3f ) * Mathfs.Acosh( ( -3 * q.Abs() / ( 2 * p ) ) * MathF.Sqrt( -3 / p ) ); - float r = -2 * Mathfs.Sign( q ) * MathF.Sqrt( -p / 3 ) * Mathfs.Cosh( coshInner ); - return new ResultsMax3( r ); - } - - if( p > 0 ) { // one root - float sinhInner = ( 1f / 3f ) * Mathfs.Asinh( ( ( 3 * q ) / ( 2 * p ) ) * MathF.Sqrt( 3 / p ) ); - float r = ( -2 * MathF.Sqrt( p / 3 ) ) * Mathfs.Sinh( sinhInner ); - return new ResultsMax3( r ); - } - - // no roots - return default; - } - - #endregion - #endregion #region Typecasting & Operators @@ -521,4 +401,5 @@ public override string ToString() { } + } \ No newline at end of file diff --git a/Runtime/Curves/Solve.cs b/Runtime/Curves/Solve.cs new file mode 100644 index 0000000..da67300 --- /dev/null +++ b/Runtime/Curves/Solve.cs @@ -0,0 +1,187 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace Freya { + + public static class Solve { + + /// Solves for x in a polynomial equation c₀ + c₁x = rhs + public static float? Eq( float c0, float c1, float equals ) => Polynomial( c0 - equals, c1 ); + + /// Solves for x in a polynomial equation c₀ + c₁x + c₂x² = rhs. Roots are sorted in increasing order + public static ResultsMax2 Eq( float c0, float c1, float c2, float equals ) => Polynomial( c0 - equals, c1, c2 ); + + /// Solves for x in a polynomial equation c₀ + c₁x + c₂x² + c₃x³ = rhs. Roots are sorted in increasing order + public static ResultsMax3 Eq( float c0, float c1, float c2, float c3, float equals ) => Polynomial( c0 - equals, c1, c2, c3 ); + + /// Solves for x in a polynomial equation c₀ + c₁x + c₂x² + c₃x³ + c₄x⁴ = rhs. Roots are sorted in increasing order + public static ResultsMax4 Eq( float c0, float c1, float c2, float c3, float c4, float equals ) => Polynomial( c0 - equals, c1, c2, c3, c4 ); + + /// Finds roots/x values where a polynomial c₀ + c₁x = 0 + public static float? Polynomial( float c0, float c1 ) => Mathf.Approximately( c1, 0 ) ? null : -c0 / c1; + + /// Finds roots/x values where a polynomial c₀ + c₁x + c₂x² = 0. Roots are sorted in increasing order + public static ResultsMax2 Polynomial( float c0, float c1, float c2 ) { + if( Mathf.Approximately( c2, 0 ) ) + return Polynomial( c0, c1 ); // curve is lower order + float disc = c1 * c1 - 4 * c2 * c0; + if( Mathf.Approximately( disc, 0 ) ) + return Polynomial( c1, 2 * c2 ); // one root + if( disc < 0 ) + return default; // no roots + // two roots: + float q = -( c1 + MathF.Sqrt( disc ) * c1.Sign() ) / 2; + float r0 = q / c2; + float r1 = c0 / q; + return new ResultsMax2( MathF.Min( r0, r1 ), MathF.Max( r0, r1 ) ); + } + + /// Finds roots/x values where a polynomial c₀ + c₁x + c₂x² + c₃x³ = 0. Roots are sorted in increasing order + public static ResultsMax3 Polynomial( float c0, float c1, float c2, float c3 ) { + if( Mathf.Approximately( c3, 0 ) ) + return Polynomial( c0, c1, c2 ); // curve is lower order + if( c2 == 0f && c3 == 1f ) + return SolveDepressedCubicRoots( c0, c1 ); // It's a depressed cubic :( c₀ + c₁t + t³ + + // first, depress the cubic to make it easier to solve + float aa = c3 * c3; + float ac = c3 * c1; + float bb = c2 * c2; + float p_c1 = ( 3 * ac - bb ) / ( 3 * aa ); + float q_c0 = ( 2 * bb * c2 - 9 * ac * c2 + 27 * aa * c0 ) / ( 27 * aa * c3 ); + + ResultsMax3 dpr = SolveDepressedCubicRoots( q_c0, p_c1 ); + + // we now have the roots of the depressed cubic, now convert back to the normal cubic + ResultsMax3 results = default; + for( int i = 0; i < dpr.count; i++ ) + results = results.InsertSorted( dpr[i] - c2 / ( 3 * c3 ) ); + return results; + } + + /// Finds roots/x values where a polynomial c₀ + c₁x + c₂x² + c₃x³ + c₄x⁴ = 0. Roots are sorted in increasing order + static ResultsMax4 Polynomial( float c0, float c1, float c2, float c3, float c4 ) { + if( Mathf.Approximately( c4, 0 ) ) + return Polynomial( c0, c1, c2, c3 ); // curve is lower order + if( Mathf.Approximately( c1, 0 ) && Mathf.Approximately( c3, 0 ) ) + return SolveBiquadraticRoots( c0, c2, c4 ); // curve is biquadratic -> c0 + c2x² + c4x⁴= 0 + + float iA = 1f / c4; + float BoA = c3 * iA; + float BoA2 = BoA * BoA; + float BoA3 = BoA * BoA * BoA; + float BoA4 = BoA2 * BoA2; + + float a = -( 3f / 8f ) * BoA2 + c2 * iA; + float b = BoA3 / 8f - ( c2 / 2 ) * BoA * iA + c1 * iA; + float c = -( 3f / 256 ) * BoA4 + ( c2 / 16f ) * BoA2 * iA - ( c1 / 4 ) * BoA * iA + c0 * iA; + + if( Mathf.Approximately( b, 0 ) ) + return SolveBiquadraticRoots( c, a, 1 ); + + // not biquadratic oh boy + ResultsMax3 yRoots = Solve.Polynomial( ( a * c ) / 2f - ( b * b ) / 8f, -c, -a / 2f, 1 ); + if( yRoots.count == 0 ) + return default; // no roots + + // filter roots + float y = float.NaN; + float vBest = float.NegativeInfinity; + for( int i = 0; i < yRoots.count; i++ ) { + float v = 2 * yRoots[i] - a; + if( Mathf.Approximately( v, 0 ) ) + continue; + if( v >= 0f && v > vBest ) { + y = yRoots[i]; + vBest = v; + } + } + if( float.IsNaN( y ) ) + return default; // no roots + + float R = math.sqrt( vBest ); + float i0 = b / ( 2f * R ); + ResultsMax2 q0 = Solve.Polynomial( y / 2f - i0, +R / 2, 1f ); + ResultsMax2 q1 = Solve.Polynomial( y / 2f + i0, -R / 2, 1f ); + + float offset = -c3 / ( 4 * c4 ); + ResultsMax4 results = default; + for( int i = 0; i < q0.count; i++ ) + results = results.InsertSorted( q0[i] + offset ); + for( int i = 0; i < q1.count; i++ ) + results = results.InsertSorted( q1[i] + offset ); + return results; + } + + /// t³ + c₁t + c₀ = 0 + static ResultsMax3 SolveDepressedCubicRoots( float c0, float c1 ) { + if( Mathf.Approximately( c1, 0 ) ) // triple root - one solution. solve x³+q = 0 => x = cr(-q) + return new ResultsMax3( Mathfs.Cbrt( -c0 ) ); + float discriminant = 4 * c1 * c1 * c1 + 27 * c0 * c0; + if( discriminant < 0.00001 ) { // two or three roots guaranteed, use trig solution + float pre = 2 * MathF.Sqrt( -c1 / 3 ); + float acosInner = ( ( 3 * c0 ) / ( 2 * c1 ) ) * MathF.Sqrt( -3 / c1 ); + + float GetRoot( int k ) => pre * MathF.Cos( ( 1f / 3f ) * Mathfs.Acos( acosInner.ClampNeg1to1() ) - ( Mathfs.TAU / 3f ) * k ); + // if acos hits 0 or TAU/2, the offsets will have the same value, + // which means we have a double root plus one regular root on our hands + if( acosInner >= 0.9999f ) + return new ResultsMax3( GetRoot( 0 ), GetRoot( 2 ) ); // two roots - one single and one double root + if( acosInner <= -0.9999f ) + return new ResultsMax3( GetRoot( 1 ), GetRoot( 2 ) ); // two roots - one single and one double root + return new ResultsMax3( GetRoot( 0 ), GetRoot( 1 ), GetRoot( 2 ) ); // three roots + } + + if( discriminant > 0 && c1 < 0 ) { // one root + float coshInner = ( 1f / 3f ) * Mathfs.Acosh( ( -3 * c0.Abs() / ( 2 * c1 ) ) * MathF.Sqrt( -3 / c1 ) ); + float r = -2 * Mathfs.Sign( c0 ) * MathF.Sqrt( -c1 / 3 ) * Mathfs.Cosh( coshInner ); + return new ResultsMax3( r ); + } + + if( c1 > 0 ) { // one root + float sinhInner = ( 1f / 3f ) * Mathfs.Asinh( ( ( 3 * c0 ) / ( 2 * c1 ) ) * MathF.Sqrt( 3 / c1 ) ); + float r = ( -2 * MathF.Sqrt( c1 / 3 ) ) * Mathfs.Sinh( sinhInner ); + return new ResultsMax3( r ); + } + + // no roots + return default; + } + + /// c4x⁴ + c2x² + c0 = 0 + static ResultsMax4 SolveBiquadraticRoots( float c0, float c2, float c4 ) { + ResultsMax2 z = Polynomial( c0, c2, c4 ); + + // filter roots, keep positive only + if( z.count == 2 ) { + if( z.b < 0 && z.a < 0 ) + return default; // no roots + if( z.a >= 0 && z.b < 0 ) + z = new ResultsMax2( z.a ); + if( z.b >= 0 && z.a < 0 ) + z = new ResultsMax2( z.b ); + } else if( z.count == 1 && z.a < 0 ) { + return default; // no roots + } + + if( z.count == 2 ) { + ( float small, float big ) = z.a < z.b ? ( z.a, z.b ) : ( z.b, z.a ); + big = math.sqrt( big ); + if( Mathf.Approximately( c0, 0 ) ) // three roots + return new ResultsMax4( -big, 0, big ); + // four roots + small = math.sqrt( small ); + return new ResultsMax4( -big, -small, small, big ); + } else if( z.count == 1 ) { + if( Mathf.Approximately( c0, 0 ) ) + return 0; // one root at 0 + // two roots + float x = math.sqrt( z.a ); + return new ResultsMax4( -x, x ); + } + return default; + } + } + +} \ No newline at end of file diff --git a/Runtime/Curves/Solve.cs.meta b/Runtime/Curves/Solve.cs.meta new file mode 100644 index 0000000..5cc9500 --- /dev/null +++ b/Runtime/Curves/Solve.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6926dbd05cff4b04a255583a2d403d49 +timeCreated: 1785466382 \ No newline at end of file diff --git a/Runtime/ResultsMax4FloatExt.cs b/Runtime/ResultsMax4FloatExt.cs new file mode 100644 index 0000000..db089a5 --- /dev/null +++ b/Runtime/ResultsMax4FloatExt.cs @@ -0,0 +1,49 @@ +using System; + +namespace Freya { + + public static class ResultsMax4FloatExt { + public static ResultsMax4 InsertSorted( this ResultsMax4 r, float value ) { + switch( r.count ) { + case 0: return new ResultsMax4( value ); + case 1: + if( value < r.a ) + return new ResultsMax4( value, r.a ); + return new ResultsMax4( r.a, value ); + case 2: + if( value < r.a ) + return new ResultsMax4( value, r.a, r.b ); + if( value < r.b ) + return new ResultsMax4( r.a, value, r.b ); + return new ResultsMax4( r.a, r.b, value ); + case 3: + if( value < r.a ) + return new ResultsMax4( value, r.a, r.b, r.c ); + if( value < r.b ) + return new ResultsMax4( r.a, value, r.b, r.c ); + if( value < r.c ) + return new ResultsMax4( r.a, r.b, value, r.c ); + return new ResultsMax4( r.a, r.b, r.c, value ); + default: throw new IndexOutOfRangeException( "Can't add more than four values to ResultsMax4" ); + } + } + + public static ResultsMax3 InsertSorted( this ResultsMax3 r, float value ) { + switch( r.count ) { + case 0: return new ResultsMax3( value ); + case 1: + if( value < r.a ) + return new ResultsMax3( value, r.a ); + return new ResultsMax3( r.a, value ); + case 2: + if( value < r.a ) + return new ResultsMax3( value, r.a, r.b ); + if( value < r.b ) + return new ResultsMax3( r.a, value, r.b ); + return new ResultsMax3( r.a, r.b, value ); + default: throw new IndexOutOfRangeException( "Can't add more than three values to ResultsMax3" ); + } + } + } + +} \ No newline at end of file diff --git a/Runtime/ResultsMax4FloatExt.cs.meta b/Runtime/ResultsMax4FloatExt.cs.meta new file mode 100644 index 0000000..3c390ce --- /dev/null +++ b/Runtime/ResultsMax4FloatExt.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8fcbd682de6943409948584ed60e7af0 +timeCreated: 1785454763 \ No newline at end of file diff --git a/Runtime/UtilityTypes.cs b/Runtime/UtilityTypes.cs index 4f5081b..be582bc 100644 --- a/Runtime/UtilityTypes.cs +++ b/Runtime/UtilityTypes.cs @@ -5,9 +5,133 @@ using System; using System.Collections; using System.Collections.Generic; +using UnityEngine; namespace Freya { + /// Contains either 0 to 4 valid return values + public readonly struct ResultsMax4 : IEnumerable where T : struct { + + /// The number of valid values + public readonly int count; + + /// The first value. This may or may not be set/defined - use .count to see how many are valid + public readonly T a; + + /// The second value. This may or may not be set/defined - use .count to see how many are valid + public readonly T b; + + /// The third value. This may or may not be set/defined - use .count to see how many are valid + public readonly T c; + + /// The third value. This may or may not be set/defined - use .count to see how many are valid + public readonly T d; + + public ResultsMax4( T a, T b, T c, T d ) => ( this.a, this.b, this.c, this.d, this.count ) = ( a, b, c, d, 4 ); + public ResultsMax4( T a, T b, T c ) => ( this.a, this.b, this.c, this.d, this.count ) = ( a, b, c, default, 3 ); + public ResultsMax4( T a, T b ) => ( this.a, this.b, this.c, this.d, this.count ) = ( a, b, default, default, 2 ); + public ResultsMax4( T a ) => ( this.a, this.b, this.c, this.d, this.count ) = ( a, default, default, default, 1 ); + + /// Returns the valid values at index i. Will throw an index out of range exception for invalid values. Use toghether with .count to ensure you don't get invalid values + /// The index of the result to get + public T this[ int i ] => i switch { 0 => a, 1 => b, 2 => c, 3 => d, _ => throw new IndexOutOfRangeException() }; + + /// Returns a version of these results with one more element added to it. Note: this does not mutate the original struct + /// The value to add + public ResultsMax4 Add( T value ) => + count switch { + 0 => new ResultsMax4( value ), + 1 => new ResultsMax4( a, value ), + 2 => new ResultsMax4( a, b, value ), + 3 => new ResultsMax4( a, b, c, value ), + _ => throw new IndexOutOfRangeException( "Can't add more than four values to ResultsMax4" ) + }; + + + /// Implicitly casts from a tuple with nullables to a results structure + /// The tuple to cast + public static implicit operator ResultsMax4( (T?, T?, T?, T?) tuple ) { + ResultsMax4 results = new ResultsMax4(); + ( T? a, T? b, T? c, T? d ) = tuple; + if( a.HasValue ) results = results.Add( a.Value ); + if( b.HasValue ) results = results.Add( b.Value ); + if( c.HasValue ) results = results.Add( c.Value ); + if( d.HasValue ) results = results.Add( d.Value ); + return results; + } + + /// Implicitly casts a value to a results structure + /// The value to cast + public static implicit operator ResultsMax4( T v ) => new ResultsMax4( v ); + + /// Implicitly casts ResultsMax2 to ResultsMax4 + /// The results to cast + public static implicit operator ResultsMax4( ResultsMax2 m2 ) => + m2.count switch { + 0 => default, + 1 => new ResultsMax4( m2.a ), + 2 => new ResultsMax4( m2.a, m2.b ), + _ => throw new InvalidCastException( "Failed to cast ResultsMax2 to ResultsMax4" ) + }; + + /// Implicitly casts ResultsMax2 to ResultsMax4 + /// The results to cast + public static implicit operator ResultsMax4( ResultsMax3 m3 ) => + m3.count switch { + 0 => default, + 1 => new ResultsMax4( m3.a ), + 2 => new ResultsMax4( m3.a, m3.b ), + 3 => new ResultsMax4( m3.a, m3.b, m3.b ), + _ => throw new InvalidCastException( "Failed to cast ResultsMax2 to ResultsMax4" ) + }; + + /// Explicitly casts ResultsMax4 to ResultsMax3 + /// The results to cast + public static explicit operator ResultsMax3( ResultsMax4 m4 ) => + m4.count switch { + 0 => default, + 1 => new ResultsMax3( m4.a ), + 2 => new ResultsMax3( m4.a, m4.b ), + 3 => new ResultsMax3( m4.a, m4.b, m4.c ), + _ => throw new IndexOutOfRangeException( $"Attempt to cast ResultsMax4 to ResultsMax2 when it had {m4.count} results" ), + }; + + /// Explicitly casts ResultsMax4 to ResultsMax3 + /// The results to cast + public static explicit operator ResultsMax2( ResultsMax4 m4 ) => + m4.count switch { + 0 => default, + 1 => new ResultsMax2( m4.a ), + 2 => new ResultsMax2( m4.a, m4.b ), + _ => throw new IndexOutOfRangeException( $"Attempt to cast ResultsMax4 to ResultsMax2 when it had {m4.count} results" ), + }; + + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public struct ResultsMax4Enumerator : IEnumerator { + + int currentIndex; + readonly ResultsMax4 value; + + public ResultsMax4Enumerator( ResultsMax4 value ) { + this.value = value; + currentIndex = -1; + } + + public bool MoveNext() => ++currentIndex < value.count; + public void Reset() => currentIndex = -1; + public T Current => value[currentIndex]; + object IEnumerator.Current => Current; + public void Dispose() => _ = 0; + } + + public ResultsMax4Enumerator GetEnumerator() => new ResultsMax4Enumerator( this ); + + } + /// Contains either 0, 1, 2 or 3 valid return values public readonly struct ResultsMax3 : IEnumerable where T : struct { @@ -109,7 +233,7 @@ public static implicit operator ResultsMax3( ResultsMax2 m2 ) { throw new InvalidCastException( "Failed to cast ResultsMax2 to ResultsMax3" ); } - + /// Explicitly casts ResultsMax3 to ResultsMax2 /// The results to cast public static explicit operator ResultsMax2( ResultsMax3 m3 ) { @@ -196,6 +320,8 @@ public ResultsMax2 Add( T value ) { } } + public static implicit operator ResultsMax2( T v ) => new ResultsMax2( v ); + public static implicit operator ResultsMax2( T? v ) => v.HasValue ? new ResultsMax2( v.Value ) : default; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); From 21b68b7919b700ed49208ee25c9139e0c6e9bf05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 07:04:16 +0200 Subject: [PATCH 294/301] Vector4.XY() --- Runtime/Extensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 817db66..a6f3ccd 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -65,6 +65,9 @@ public static Vector2 Rotate( this Vector2 v, float angRad ) { /// Returns X and Y as a Vector2, equivalent to new Vector2(v.x,v.y) [MethodImpl( INLINE )] public static Vector2 XY( this Vector3 v ) => new(v.x, v.y); + /// Returns X and Y as a Vector2, equivalent to new Vector2(v.x,v.y) + [MethodImpl( INLINE )] public static Vector2 XY( this Vector4 v ) => new(v.x, v.y); + /// Returns Y and X as a Vector2, equivalent to new Vector2(v.y,v.x) [MethodImpl( INLINE )] public static Vector2 YX( this Vector3 v ) => new(v.y, v.x); From 02fce904eea90866c010de08b6ed90f0d596b1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 07:09:13 +0200 Subject: [PATCH 295/301] int2/int3 to float3/float4 shortcuts --- Runtime/Numerics/mathfs.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Runtime/Numerics/mathfs.cs b/Runtime/Numerics/mathfs.cs index df07d5c..442f8c4 100644 --- a/Runtime/Numerics/mathfs.cs +++ b/Runtime/Numerics/mathfs.cs @@ -33,13 +33,18 @@ public static (int2 x, int2 y) quadrantToBasis( int i ) => public static rat round( rat r, rat interval, RoundingDirection rounding = RoundingDirection.ToEven ) => ( r / interval ).round( rounding ) * interval; public static rat2 round( rat2 r, rat interval, RoundingDirection rounding = RoundingDirection.ToEven ) => new(round( r.x, interval ), round( r.y, interval )); public static rat2 round( rat2 r, rat2 intervals, RoundingDirection rounding = RoundingDirection.ToEven ) => new(round( r.x, intervals.x ), round( r.y, intervals.y )); - + // todo: these should probably move to codegen public static int csum( this bool b ) => b ? 1 : 0; public static int csum( this bool2 b ) => math.csum( (int2)b ); public static int csum( this bool3 b ) => math.csum( (int3)b ); public static int csum( this bool4 b ) => math.csum( (int4)b ); + public static Vector3 asVec3( this int2 v, float z = 0f ) => new(v.x, v.y, z); + public static float3 asFloat3( this int2 v, float z = 0f ) => new(v.x, v.y, z); + public static Vector4 asVec4( this int3 v, float w = 0f ) => new(v.x, v.y, v.z, w); + public static float4 asFloat4( this int3 v, float w = 0f ) => new(v.x, v.y, v.z, w); + // UNSORTED: public static Rect expandFromCenter( this Rect r, float expansionPerSide ) { rat2 g = default; From 4dc1c1742174e0185b5f3457292c1f5105dcc8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 07:09:31 +0200 Subject: [PATCH 296/301] more enumeration helpers --- Runtime/Numerics/EnumerationExtensions.cs | 42 +++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Runtime/Numerics/EnumerationExtensions.cs b/Runtime/Numerics/EnumerationExtensions.cs index 20afa8b..8d5eea7 100644 --- a/Runtime/Numerics/EnumerationExtensions.cs +++ b/Runtime/Numerics/EnumerationExtensions.cs @@ -1,10 +1,23 @@ -using System.Collections.Generic; +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using System.Collections.Generic; +using System.Linq; namespace Freya { public static class EnumerationExtensions { - public static IEnumerable<(T a, T b)> Pairs( this IEnumerable items, bool loop ) { + + /// Enumerates each item pair as tuples + /// The items to enumerate + /// Whether to include a last pair formed by the last element and the first element + /// Given items [a,b,c,d], this returns: + ///
      + ///
    • [(a,b),(b,c),(c,d)] if cyclic == false
    • + ///
    • [(a,b),(b,c),(c,d),(d,a)] if cyclic == true
    • + ///
    + public static IEnumerable<(T a, T b)> Pairs( this IEnumerable items, bool cyclic ) { bool hasFoundFirst = false; T first = default; T prev = default; @@ -17,10 +30,33 @@ public static class EnumerationExtensions { } prev = item; } - if( loop && hasFoundFirst ) + if( cyclic && hasFoundFirst ) yield return ( prev, first ); } + /// A shorthand for selecting the out parameters of bool functions returning true + /// The items to enumerate + /// Predicate selecting tuples with the boolean return value, and the out parameter value + /// items.SelectOutParamsWhereTrue( x => (x.TryThing(out y), y) ) + public static IEnumerable SelectOutParamsWhereTrue( this IEnumerable items, Func predicate ) { + foreach( T item in items ) { + ( bool valid, O value ) = predicate( item ); + if( valid ) + yield return value; + } + } + + /// Tries to select the minimum value. Returns false with a minimum value of int.MaxValue if there are no items + /// The items to enumerate + /// The selector for the minimum value + /// The minimum value found + public static bool TryMin( this IEnumerable items, Func predicate, out int minimum ) => items.Select( predicate ).TryMin( out minimum ); + + /// Tries to select the minimum value. Returns false with a minimum value of int.MaxValue if there are no items + /// The items to enumerate + /// The minimum value found + public static bool TryMin( this IEnumerable items, out int minimum ) => ( minimum = items.Aggregate( int.MaxValue, Math.Min ) ) != int.MaxValue; + } } \ No newline at end of file From 8ba94f0e8f964ab8167d0618edc8a303b202a94a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 07:16:51 +0200 Subject: [PATCH 297/301] integer powers for float (longue switch go brrr) --- Runtime/Numerics/mathfs.cs | 84 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/Runtime/Numerics/mathfs.cs b/Runtime/Numerics/mathfs.cs index 442f8c4..0587a09 100644 --- a/Runtime/Numerics/mathfs.cs +++ b/Runtime/Numerics/mathfs.cs @@ -73,6 +73,90 @@ public static int modDelta( int a, int b, int mod ) { /// public static int quadrantDelta( rat2 a, rat2 b ) => a.wedge( b ).sign * modDelta( a.quadrant, b.quadrant, 4 ); + /// Integer powers of floats, using repeated multiplication. Falls back to standard pow() beyond a power of 16 + public static float pow( this float x, int exp ) { + switch( exp ) { + case 0: return 1f; + case 1: return x; + case 2: return x * x; + case 3: { + return x * x * x; + } + case 4: { + float x2 = x * x; + return x2 * x2; + } + case 5: { + float x2 = x * x; + float x4 = x2 * x2; + return x4 * x; + } + case 6: { + float x2 = x * x; + float x4 = x2 * x2; + return x4 * x2; + } + case 7: { + float x2 = x * x; + float x4 = x2 * x2; + return x4 * x2 * x; + } + case 8: { + float x2 = x * x; + float x4 = x2 * x2; + return x4 * x4; + } + case 9: { + float x2 = x * x; + float x4 = x2 * x2; + float x8 = x4 * x4; + return x8 * x; + } + case 10: { + float x2 = x * x; + float x4 = x2 * x2; + float x8 = x4 * x4; + return x8 * x2; + } + case 11: { + float x2 = x * x; + float x4 = x2 * x2; + float x8 = x4 * x4; + return x8 * x2 * x; + } + case 12: { + float x2 = x * x; + float x4 = x2 * x2; + float x8 = x4 * x4; + return x8 * x4; + } + case 13: { + float x2 = x * x; + float x4 = x2 * x2; + float x8 = x4 * x4; + return x8 * x4 * x; + } + case 14: { + float x2 = x * x; + float x4 = x2 * x2; + float x8 = x4 * x4; + return x8 * x4 * x2; + } + case 15: { + float x2 = x * x; + float x4 = x2 * x2; + float x8 = x4 * x4; + return x8 * x4 * x2 * x; + } + case 16: { + float x2 = x * x; + float x4 = x2 * x2; + float x8 = x4 * x4; + return x8 * x8; + } + default: return MathF.Pow( x, exp ); + } + } public static inth divideBy2( this int p ) => new() { h = p }; public static inth2 divideBy2( this int2 p ) => new(p.x.divideBy2(), p.y.divideBy2()); From a6e03741ad1f0d17fa584bebe0aa9e53cc3e065f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 07:24:41 +0200 Subject: [PATCH 298/301] =?UTF-8?q?m=C3=B6bius=20transform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/Curves/MobiusTf.cs | 64 +++++++++++++++++++++++++++++++++ Runtime/Curves/MobiusTf.cs.meta | 3 ++ Runtime/Numerics/mathfs.cs | 3 ++ 3 files changed, 70 insertions(+) create mode 100644 Runtime/Curves/MobiusTf.cs create mode 100644 Runtime/Curves/MobiusTf.cs.meta diff --git a/Runtime/Curves/MobiusTf.cs b/Runtime/Curves/MobiusTf.cs new file mode 100644 index 0000000..7f7a263 --- /dev/null +++ b/Runtime/Curves/MobiusTf.cs @@ -0,0 +1,64 @@ +// by Freya Holmér (https://github.com/FreyaHolmer/Mathfs) + +using System; +using UnityEngine.Assertions; + +namespace Freya { + + /// The Möbius Transformation as an equation of the form f(x) = (ax+b)/(cx+d) + [Serializable] public struct MobiusTf { + + public float a, b, c, d; + + public static readonly MobiusTf identity = new MobiusTf( 1, 1, 0, 0 ); + public bool IsIdentity => b == 0 && c == 0 && a == d; + + float Determinant => a * d - b * c; + public bool IsValid => Determinant != 0; + public bool IsAffine => c == 0; + public MobiusTf Inverse => new(d, -b, -c, a); + + public MobiusTf( float a, float b, float c, float d ) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + Assert.IsTrue( IsValid ); + } + + /// Evaluates the nth derivative of the mobius transform + /// The parameter value to evaluate at + /// The nth derivative. 0 = evaluates the function. 1 = evaluates the first derivative, etc. + public float eval( float x, int n = 0 ) { + float D = c * x + d; + switch( n ) { + case 0: return ( a * x + b ) / D; + case 1: return Determinant / ( D * D ); + case 2: return -2 * Determinant / ( D * D * D ); + default: + int scale = -Mathfs.Factorial( (uint)n ) * n.esign(); + float num = Determinant * c.pow( n - 1 ); + return scale * ( num / D.pow( n + 1 ) ); + } + } + + /// Evaluates the definite integral from x0 to x1 + public float integrate( float x0, float x1 ) { + float r = ( x1 - x0 ) / ( c * x0 + d ); + float rect = r * ( a * x0 + b ); + float curv = r * r * Determinant * logrem( c * r ); + return rect + curv; + } + + /// A weird natural log remainder type thing (x-ln(x+1))/(x*x) + static float logrem( float x ) { + return x.abs() switch { + 0 => 0.5f, // singularity at 0 + < 0.001f => 0.5f - x / 3 + ( x * x ) / 4, // approximation near 0 + _ => ( x - MathF.Log( 1 + x ) ) / ( x * x ) + }; + } + + } + +} \ No newline at end of file diff --git a/Runtime/Curves/MobiusTf.cs.meta b/Runtime/Curves/MobiusTf.cs.meta new file mode 100644 index 0000000..fbd5737 --- /dev/null +++ b/Runtime/Curves/MobiusTf.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f01d18fb05654c1eb34c0354a370a322 +timeCreated: 1784991861 \ No newline at end of file diff --git a/Runtime/Numerics/mathfs.cs b/Runtime/Numerics/mathfs.cs index 0587a09..605fe51 100644 --- a/Runtime/Numerics/mathfs.cs +++ b/Runtime/Numerics/mathfs.cs @@ -158,6 +158,9 @@ public static float pow( this float x, int exp ) { } } + /// Returns 1 when even, -1 when odd + public static int esign( this int x ) => x % 2 == 0 ? 1 : -1; + public static inth divideBy2( this int p ) => new() { h = p }; public static inth2 divideBy2( this int2 p ) => new(p.x.divideBy2(), p.y.divideBy2()); From aa0a81651691e4259697f18d20cd28f59e25b61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 11:03:53 +0200 Subject: [PATCH 299/301] minor readonly/unity.math things --- Runtime/Curves/Polynomial.cs | 2 +- Runtime/Curves/Polynomial3D.cs | 6 +++--- Runtime/Extensions.cs | 5 +++++ Runtime/Mathfs.cs | 3 +-- Runtime/Numerics/FloatRange.cs | 6 +++--- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Runtime/Curves/Polynomial.cs b/Runtime/Curves/Polynomial.cs index ea71c16..2f3b1bd 100644 --- a/Runtime/Curves/Polynomial.cs +++ b/Runtime/Curves/Polynomial.cs @@ -117,7 +117,7 @@ public float Eval( float t ) { [MethodImpl( INLINE )] public float Eval( float t, int n ) => Differentiate( n ).Eval( t ); - [MethodImpl( INLINE )] public Polynomial Differentiate( int n = 1 ) { + [MethodImpl( INLINE )] public readonly Polynomial Differentiate( int n = 1 ) { return n switch { 0 => this, 1 => new Polynomial( c1, 2 * c2, 3 * c3, 0 ), diff --git a/Runtime/Curves/Polynomial3D.cs b/Runtime/Curves/Polynomial3D.cs index fc8b93b..0e2e7f0 100644 --- a/Runtime/Curves/Polynomial3D.cs +++ b/Runtime/Curves/Polynomial3D.cs @@ -88,7 +88,7 @@ public Polynomial this[ int i ] { }; } - public Vector3 Eval( float t ) { + public readonly Vector3 Eval( float t ) { float t2 = t * t; float t3 = t2 * t; return new Vector3( @@ -100,7 +100,7 @@ public Vector3 Eval( float t ) { [MethodImpl( INLINE )] public Vector3 Eval( float t, int n ) => Differentiate( n ).Eval( t ); - [MethodImpl( INLINE )] public Polynomial3D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n )); + [MethodImpl( INLINE )] public readonly Polynomial3D Differentiate( int n = 1 ) => new(x.Differentiate( n ), y.Differentiate( n ), z.Differentiate( n )); public Polynomial3D ScaleParameterSpace( float factor ) { // ReSharper disable once CompareOfFloatsByEqualityOperator @@ -181,7 +181,7 @@ public static Polynomial3D FitCubicFrom0( float x1, float x2, float x3, Vector3 #region IParamCurve3Diff interface implementations public int Degree => Mathfs.Max( x.Degree, y.Degree, z.Degree ); - public Vector3 EvalDerivative( float t ) => Differentiate().Eval( t ); + public readonly Vector3 EvalDerivative( float t ) => Differentiate().Eval( t ); public Vector3 EvalSecondDerivative( float t ) => Differentiate( 2 ).Eval( t ); public Vector3 EvalThirdDerivative( float t = 0 ) => Differentiate( 3 ).Eval( 0 ); diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index a6f3ccd..541d909 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -2,7 +2,9 @@ using System; using System.Runtime.CompilerServices; +using Unity.Mathematics; using UnityEngine; +using static Unity.Mathematics.math; namespace Freya { @@ -18,6 +20,9 @@ public static class MathfsExtensions { /// [MethodImpl( INLINE )] public static float Angle( this Vector2 v ) => MathF.Atan2( v.y, v.x ); + /// + [MethodImpl( INLINE )] public static float Angle( this float2 v ) => atan2( v.y, v.x ); + /// Rotates the vector 90 degrees clockwise (negative Z axis rotation) [MethodImpl( INLINE )] public static Vector2 Rotate90CW( this Vector2 v ) => new Vector2( v.y, -v.x ); diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 6513cd7..2fcb53b 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -11,7 +11,6 @@ using System.Linq; // used for arbitrary count min/max functions, so it's safe and won't allocate garbage don't worry~ using System.Runtime.CompilerServices; using Unity.Mathematics; - using MidpointRounding = System.MidpointRounding; namespace Freya { @@ -640,7 +639,7 @@ public static Vector4 ClampNeg1to1( Vector4 v ) => [MethodImpl( INLINE )] public static Vector2 Round( Vector2 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector2( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ) ); /// - [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector3( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ) ); + [MethodImpl( INLINE )] public static Vector3 Round( Vector3 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new(Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding )); /// [MethodImpl( INLINE )] public static Vector4 Round( Vector4 value, float snapInterval, System.MidpointRounding midpointRounding = System.MidpointRounding.ToEven ) => new Vector4( Round( value.x, snapInterval, midpointRounding ), Round( value.y, snapInterval, midpointRounding ), Round( value.z, snapInterval, midpointRounding ), Round( value.w, snapInterval, midpointRounding ) ); diff --git a/Runtime/Numerics/FloatRange.cs b/Runtime/Numerics/FloatRange.cs index 7637742..5e2afaa 100644 --- a/Runtime/Numerics/FloatRange.cs +++ b/Runtime/Numerics/FloatRange.cs @@ -42,15 +42,15 @@ namespace Freya { /// Interpolates a value from a to b, based on a parameter t /// The normalized interpolant from a to b. A value of 0 returns a, a value of 1 returns b - public float Lerp( float t ) => Mathfs.Lerp( a, b, t ); + public readonly float Lerp( float t ) => Mathfs.Lerp( a, b, t ); /// Returns the normalized position of the input value v within this range /// The value to get the normalized position of - public float InverseLerp( float v ) => Mathfs.InverseLerp( a, b, v ); + public readonly float InverseLerp( float v ) => Mathfs.InverseLerp( a, b, v ); /// Returns whether or not this range contains the value v (inclusive) /// The value to see if it's inside - public bool Contains( float v ) => v >= MathF.Min( a, b ) && v <= MathF.Max( a, b ); + public readonly bool Contains( float v ) => v >= MathF.Min( a, b ) && v <= MathF.Max( a, b ); /// Returns whether or not this range contains the range r /// The range to see if it's inside From 99be7dc9bec086a7d226264598543da55b5f0d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 11:07:47 +0200 Subject: [PATCH 300/301] PitchYawToDirection --- Runtime/Mathfs.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Runtime/Mathfs.cs b/Runtime/Mathfs.cs index 2fcb53b..710b504 100644 --- a/Runtime/Mathfs.cs +++ b/Runtime/Mathfs.cs @@ -1178,12 +1178,12 @@ public static Vector3 GetRotationMinimizingNormal( Vector3 posA, Vector3 tangent /// Returns the direction of the input angle, as a normalized vector /// The input angle, in radians /// - [MethodImpl( INLINE )] public static Vector2 AngToDir( float aRad ) => new Vector2( MathF.Cos( aRad ), MathF.Sin( aRad ) ); + [MethodImpl( INLINE )] public static float2 AngToDir( float aRad ) => new float2( MathF.Cos( aRad ), MathF.Sin( aRad ) ); /// Returns the angle of the input vector, in radians. You can also use myVector.Angle() /// The vector to get the angle of. It does not have to be normalized /// - [MethodImpl( INLINE )] public static float DirToAng( Vector2 vec ) => MathF.Atan2( vec.y, vec.x ); + [MethodImpl( INLINE )] public static float DirToAng( float2 vec ) => MathF.Atan2( vec.y, vec.x ); /// Returns a 2D orientation from a vector, representing the X axis /// The direction to create a 2D orientation from (does not have to be normalized) @@ -1422,6 +1422,21 @@ public static IEnumerable PointsInCircle( int count, float radius = 1, } } + /// Converts a yaw/pitch pair in radians, into a direction vector + /// A pitch of 0 points along the horizon. An angle of tau/4 points directly up + /// A yaw of 0 points along the axis after your up axis. If Y is up, a yaw of 0 points along Z. If Z is up, a yaw of 0 points along X + /// The axis considered up + public static float3 PitchYawToDirection( float pitch, float yaw, Axis upAxis = Axis.Y ) { + float2 a = AngToDir( yaw ); + float2 b = AngToDir( pitch ); + float3 v = default; + int u = (int)upAxis; + v[u] = b.y; + v[( u + 1 ) % 3] = a.x * b.x; + v[( u + 2 ) % 3] = a.y * b.x; + return v; + } + #endregion #region Angular movement helpers From e1182982b7e4567dcccfafe21cb8c7411523b7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freya=20Holm=C3=A9r?= Date: Fri, 31 Jul 2026 11:08:10 +0200 Subject: [PATCH 301/301] quaternion.Mirror --- Runtime/Extensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Runtime/Extensions.cs b/Runtime/Extensions.cs index 541d909..eb20faf 100644 --- a/Runtime/Extensions.cs +++ b/Runtime/Extensions.cs @@ -526,6 +526,12 @@ public static Quaternion InversePureIm( this Quaternion q ) { /// Add to the magnitude of this quaternion public static Quaternion AddMagnitude( this Quaternion q, float amount ) => amount == 0f ? q : q.Mul( 1 + amount / q.Magnitude() ); + public static quaternion Mirror( this quaternion q, float3 nPlane ) { + float4 v = q.value; + float4 n = new float4( nPlane, 0 ); + return conjugate( reflect( v, n ) ); + } + #endregion #region Transform extensions