diff --git a/Curve/CubicBezier3D.cs b/Curve/CubicBezierCurve3D.cs similarity index 54% rename from Curve/CubicBezier3D.cs rename to Curve/CubicBezierCurve3D.cs index 7bb1ea1..8060071 100644 --- a/Curve/CubicBezier3D.cs +++ b/Curve/CubicBezierCurve3D.cs @@ -1,41 +1,59 @@ -using Microsoft.Xna.Framework; - -namespace MoonTools.Core.Curve -{ - public static class CubicBezier3D - { - public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) - { - if (t < 0 || t > 1) { throw new System.ArgumentException($"{t} is an invalid value. Must be between 0 and 1"); } - - return (1f - t) * (1f - t) * (1f - t) * p0 + - 3f * (1f - t) * (1f - t) * t * p1 + - 3f * (1f - t) * t * t * p2 + - t * t * t * p3; - } - - public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) - { - return Point(p0, p1, p2, p3, NormalizedT(t, minT, maxT)); - } - - public static Vector3 FirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) - { - if (t < 0 || t > 1) { throw new System.ArgumentException($"{t} is an invalid value. Must be between 0 and 1"); } - - return 3f * (1f - t) * (1f - t) * (p1 - p0) + - 6f * (1f - t) * t * (p2 - p1) + - 3f * t * t * (p3 - p2); - } - - public static Vector3 FirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) - { - return FirstDerivative(p0, p1, p2, p3, NormalizedT(t, minT, maxT)); - } - - private static float NormalizedT(float t, float minT, float maxT) - { - return ((t - minT)) / (maxT - minT); - } - } -} +using Microsoft.Xna.Framework; + +namespace MoonTools.Core.Curve +{ + public struct CubicBezierCurve3D + { + public Vector3 p0; + public Vector3 p1; + public Vector3 p2; + public Vector3 p3; + + public CubicBezierCurve3D(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3) + { + this.p0 = p0; + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + } + + public Vector3 Point(float t) => Point(p0, p1, p2, p3, t); + + public Vector3 Point(float t, float minT, float maxT) => Point(p0, p1, p2, p3, t, minT, maxT); + + public Vector3 Velocity(float t) => FirstDerivative(p0, p1, p2, p3, t); + + public Vector3 Velocity(float t, float minT, float maxT) => FirstDerivative(p0, p1, p2, p3, t, minT, maxT); + + public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) + { + if (t < 0 || t > 1) { throw new System.ArgumentException($"{t} is an invalid value. Must be between 0 and 1"); } + + return (1f - t) * (1f - t) * (1f - t) * p0 + + 3f * (1f - t) * (1f - t) * t * p1 + + 3f * (1f - t) * t * t * p2 + + t * t * t * p3; + } + + public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) + { + return Point(p0, p1, p2, p3, Normalized(t, minT, maxT)); + } + + public static Vector3 FirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) + { + if (t < 0 || t > 1) { throw new System.ArgumentException($"{t} is an invalid value. Must be between 0 and 1"); } + + return 3f * (1f - t) * (1f - t) * (p1 - p0) + + 6f * (1f - t) * t * (p2 - p1) + + 3f * t * t * (p3 - p2); + } + + public static Vector3 FirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) + { + return FirstDerivative(p0, p1, p2, p3, Normalized(t, minT, maxT)); + } + + private static float Normalized(float t, float minT, float maxT) => ((t - minT)) / (maxT - minT); + } +} \ No newline at end of file diff --git a/Test/Bezier3D.cs b/Test/Bezier3D.cs deleted file mode 100644 index 47b538d..0000000 --- a/Test/Bezier3D.cs +++ /dev/null @@ -1,77 +0,0 @@ -using NUnit.Framework; -using FluentAssertions; - -using MoonTools.Core.Curve; -using Microsoft.Xna.Framework; - -namespace Tests.TestExtensions -{ - static class TestExtensions - { - public static bool ApproximatelyEquals(this Vector3 v1, Vector3 v2) - { - return (v1 - v2).Length() <= 0.001f; - } - } -} - -namespace Tests -{ - using TestExtensions; - - public class Bezier3DTests - { - [Test] - public void Point() - { - var p0 = new Vector3(-4, -4, -3); - var p1 = new Vector3(-2, 4, 0); - var p2 = new Vector3(2, -4, 3); - var p3 = new Vector3(4, 4, 0); - - CubicBezier3D.Point(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector3(0, 0, 0.75f)); - CubicBezier3D.Point(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector3(0, 0, 0.75f)); - CubicBezier3D.Point(p0, p1, p2, p3, 0.25f).Should().BeEquivalentTo(new Vector3(-2.1875f, -0.5f, -0.84375f)); - CubicBezier3D.Point(p0, p1, p2, p3, 0.75f).Should().BeEquivalentTo(new Vector3(2.1875f, 0.5f, 1.21875f)); - } - - [Test] - public void PointNormalized() - { - var p0 = new Vector3(-4, -4, -3); - var p1 = new Vector3(-2, 4, 0); - var p2 = new Vector3(2, -4, 3); - var p3 = new Vector3(4, 4, 0); - - CubicBezier3D.Point(p0, p1, p2, p3, 3, 2, 4).Should().BeEquivalentTo(new Vector3(0, 0, 0.75f)); - CubicBezier3D.Point(p0, p1, p2, p3, 2, 1, 5).Should().BeEquivalentTo(new Vector3(-2.1875f, -0.5f, -0.84375f)); - CubicBezier3D.Point(p0, p1, p2, p3, 11, 2, 14).Should().BeEquivalentTo(new Vector3(2.1875f, 0.5f, 1.21875f)); - } - - [Test] - public void FirstDerivative() - { - var p0 = new Vector3(-4, -4, -3); - var p1 = new Vector3(-2, 4, 0); - var p2 = new Vector3(2, -4, 3); - var p3 = new Vector3(4, 4, 0); - - CubicBezier3D.FirstDerivative(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); - CubicBezier3D.FirstDerivative(p0, p1, p2, p3, 0.25f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, 7.875f)); - CubicBezier3D.FirstDerivative(p0, p1, p2, p3, 0.75f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f)); - } - - [Test] - public void FirstDerivativeNormalized() - { - var p0 = new Vector3(-4, -4, -3); - var p1 = new Vector3(-2, 4, 0); - var p2 = new Vector3(2, -4, 3); - var p3 = new Vector3(4, 4, 0); - - CubicBezier3D.FirstDerivative(p0, p1, p2, p3, 3, 2, 4).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); - CubicBezier3D.FirstDerivative(p0, p1, p2, p3, 2, 1, 5).Should().BeEquivalentTo(new Vector3(8.25f, 6f, 7.875f)); - CubicBezier3D.FirstDerivative(p0, p1, p2, p3, 11, 2, 14).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f)); - } - } -} \ No newline at end of file diff --git a/Test/CubicBezierCurve3D.cs b/Test/CubicBezierCurve3D.cs new file mode 100644 index 0000000..294c1cf --- /dev/null +++ b/Test/CubicBezierCurve3D.cs @@ -0,0 +1,127 @@ +using NUnit.Framework; +using FluentAssertions; + +using MoonTools.Core.Curve; +using Microsoft.Xna.Framework; + +namespace Tests +{ + public class CubicBezierCurve3DMathTests + { + [Test] + public void Point() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(-2, 4, 0); + var p2 = new Vector3(2, -4, 3); + var p3 = new Vector3(4, 4, 0); + + CubicBezierCurve3D.Point(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector3(0, 0, 0.75f)); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector3(0, 0, 0.75f)); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 0.25f).Should().BeEquivalentTo(new Vector3(-2.1875f, -0.5f, -0.84375f)); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 0.75f).Should().BeEquivalentTo(new Vector3(2.1875f, 0.5f, 1.21875f)); + } + + [Test] + public void PointNormalized() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(-2, 4, 0); + var p2 = new Vector3(2, -4, 3); + var p3 = new Vector3(4, 4, 0); + + CubicBezierCurve3D.Point(p0, p1, p2, p3, 3, 2, 4).Should().BeEquivalentTo(new Vector3(0, 0, 0.75f)); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 2, 1, 5).Should().BeEquivalentTo(new Vector3(-2.1875f, -0.5f, -0.84375f)); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 11, 2, 14).Should().BeEquivalentTo(new Vector3(2.1875f, 0.5f, 1.21875f)); + } + + [Test] + public void FirstDerivative() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(-2, 4, 0); + var p2 = new Vector3(2, -4, 3); + var p3 = new Vector3(4, 4, 0); + + CubicBezierCurve3D.FirstDerivative(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); + CubicBezierCurve3D.FirstDerivative(p0, p1, p2, p3, 0.25f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, 7.875f)); + CubicBezierCurve3D.FirstDerivative(p0, p1, p2, p3, 0.75f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f)); + } + + [Test] + public void FirstDerivativeNormalized() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(-2, 4, 0); + var p2 = new Vector3(2, -4, 3); + var p3 = new Vector3(4, 4, 0); + + CubicBezierCurve3D.FirstDerivative(p0, p1, p2, p3, 3, 2, 4).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); + CubicBezierCurve3D.FirstDerivative(p0, p1, p2, p3, 2, 1, 5).Should().BeEquivalentTo(new Vector3(8.25f, 6f, 7.875f)); + CubicBezierCurve3D.FirstDerivative(p0, p1, p2, p3, 11, 2, 14).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f)); + } + } + + public class CubicBezierCurve3DStructTests + { + [Test] + public void Point() + { + var myCurve = new CubicBezierCurve3D( + new Vector3(-4, -4, -3), + new Vector3(-2, 4, 0), + new Vector3(2, -4, 3), + new Vector3(4, 4, 0) + ); + + myCurve.Point(0.5f).Should().BeEquivalentTo(new Vector3(0, 0, 0.75f)); + myCurve.Point(0.25f).Should().BeEquivalentTo(new Vector3(-2.1875f, -0.5f, -0.84375f)); + myCurve.Point(0.75f).Should().BeEquivalentTo(new Vector3(2.1875f, 0.5f, 1.21875f)); + } + + [Test] + public void PointNormalized() + { + var myCurve = new CubicBezierCurve3D( + new Vector3(-4, -4, -3), + new Vector3(-2, 4, 0), + new Vector3(2, -4, 3), + new Vector3(4, 4, 0) + ); + + myCurve.Point(3, 2, 4).Should().BeEquivalentTo(new Vector3(0, 0, 0.75f)); + myCurve.Point(2, 1, 5).Should().BeEquivalentTo(new Vector3(-2.1875f, -0.5f, -0.84375f)); + myCurve.Point(11, 2, 14).Should().BeEquivalentTo(new Vector3(2.1875f, 0.5f, 1.21875f)); + } + + [Test] + public void Velocity() + { + var myCurve = new CubicBezierCurve3D( + new Vector3(-4, -4, -3), + new Vector3(-2, 4, 0), + new Vector3(2, -4, 3), + new Vector3(4, 4, 0) + ); + + myCurve.Velocity(0.5f).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); + myCurve.Velocity(0.25f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, 7.875f)); + myCurve.Velocity(0.75f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f)); + } + + [Test] + public void VelocityNormalized() + { + var myCurve = new CubicBezierCurve3D( + new Vector3(-4, -4, -3), + new Vector3(-2, 4, 0), + new Vector3(2, -4, 3), + new Vector3(4, 4, 0) + ); + + myCurve.Velocity(3, 2, 4).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); + myCurve.Velocity(2, 1, 5).Should().BeEquivalentTo(new Vector3(8.25f, 6f, 7.875f)); + myCurve.Velocity(11, 2, 14).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f)); + } + } +} \ No newline at end of file