diff --git a/Curve/ArgumentChecker.cs b/Curve/ArgumentChecker.cs new file mode 100644 index 0000000..005d6e6 --- /dev/null +++ b/Curve/ArgumentChecker.cs @@ -0,0 +1,13 @@ +namespace MoonTools.Core.Curve +{ + public static class ArgumentChecker + { + public static void CheckT(float t, float startTime = 0, float endTime = 1) + { + if (t < startTime || t > endTime) + { + throw new System.ArgumentException($"{t} is not a valid value for t. Must be between {startTime} and {endTime}."); + } + } + } +} \ No newline at end of file diff --git a/Curve/CubicBezierCurve3D.cs b/Curve/CubicBezierCurve3D.cs index c079f95..c206083 100644 --- a/Curve/CubicBezierCurve3D.cs +++ b/Curve/CubicBezierCurve3D.cs @@ -27,7 +27,7 @@ namespace MoonTools.Core.Curve 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"); } + ArgumentChecker.CheckT(t); return (1f - t) * (1f - t) * (1f - t) * p0 + 3f * (1f - t) * (1f - t) * t * p1 + @@ -37,12 +37,13 @@ namespace MoonTools.Core.Curve public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) { + ArgumentChecker.CheckT(t, minT, maxT); return Point(p0, p1, p2, p3, TimeHelper.Normalized(t, minT, maxT)); } public static Vector3 Velocity(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"); } + ArgumentChecker.CheckT(t); return 3f * (1f - t) * (1f - t) * (p1 - p0) + 6f * (1f - t) * t * (p2 - p1) + @@ -51,6 +52,7 @@ namespace MoonTools.Core.Curve public static Vector3 Velocity(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) { + ArgumentChecker.CheckT(t, minT, maxT); return Velocity(p0, p1, p2, p3, TimeHelper.Normalized(t, minT, maxT)); } } diff --git a/Curve/QuadraticBezierCurve2D.cs b/Curve/QuadraticBezierCurve2D.cs new file mode 100644 index 0000000..0fad27c --- /dev/null +++ b/Curve/QuadraticBezierCurve2D.cs @@ -0,0 +1,148 @@ +using Microsoft.Xna.Framework; +using MoonTools.Core.Curve.Extensions; + +namespace MoonTools.Core.Curve +{ + /// + /// A 2-dimensional Bezier curve defined by 3 points. + /// + public struct QuadraticBezierCurve2D + { + /// + /// The start point. + /// + public Vector2 p0; + + /// + /// The control point. + /// + public Vector2 p1; + + /// + /// The end point. + /// + public Vector2 p2; + + /// The start point. + /// The control point. + /// The end point. + public QuadraticBezierCurve2D(Vector2 p0, Vector2 p1, Vector2 p2) + { + this.p0 = p0; + this.p1 = p1; + this.p2 = p2; + } + + /// + /// Returns the curve coordinate given by t. + /// + /// A value between 0 and 1. + public Vector2 Point(float t) => Point(p0, p1, p2, t); + + /// + /// Returns the curve coordinate given by a normalized time value. + /// + /// A time value between startTime and endTime. + /// + /// + public Vector2 Point(float t, float startTime, float endTime) => Point(p0, p1, p2, t, startTime, endTime); + + /// + /// Returns the instantaneous velocity on the curve given by t. + /// + /// A value between 0 and 1. + public Vector2 Velocity(float t) => Velocity(p0, p1, p2, t); + + /// + /// Returns the instantaneous velocity on the curve given by a normalized time value. + /// + /// A value between startTime and endTime. + /// + /// + public Vector2 Velocity(float t, float startTime, float endTime) => Velocity(p0, p1, p2, t, startTime, endTime); + + /// + /// Performs degree elevation on the curve to turn it into a Cubic Bezier curve. + /// + public CubicBezierCurve2D AsCubic() + { + var (p0, p1, p2, p3) = AsCubic(this.p0, this.p1, this.p2); + return new CubicBezierCurve2D(p0, p1, p2, p3); + } + + /// + /// Given quadratic control points, returns cubic control points. + /// + public static (Vector2, Vector2, Vector2, Vector2) AsCubic(Vector2 p0, Vector2 p1, Vector2 p2) + { + var cubicP0 = p0; + var cubicP1 = (2f / 3f * p1) + ((1f / 3f) * p0); + var cubicP2 = (2f / 3f * p1) + ((1f / 3f) * p2); + var cubicP3 = p2; + + return (cubicP0, cubicP1, cubicP2, cubicP3); + } + + /// + /// Returns the curve coordinate given by 3 points and a time value. + /// + /// The start point. + /// The control point. + /// The end point. + /// A value between 0 and 1. + public static Vector2 Point(Vector2 p0, Vector2 p1, Vector2 p2, float t) + { + return QuadraticBezierCurve3D.Point( + new Vector3(p0.X, p0.Y, 0), + new Vector3(p1.X, p1.Y, 0), + new Vector3(p2.X, p2.Y, 0), + t + ).XY(); + } + + /// + /// Returns the curve coordinate given by 3 points and a normalized time value. + /// + /// The start point. + /// The control point. + /// The end point. + /// A value between startTime and endTime. + /// + /// + public static Vector2 Point(Vector2 p0, Vector2 p1, Vector2 p2, float t, float startTime, float endTime) + { + return Point(p0, p1, p2, TimeHelper.Normalized(t, startTime, endTime)); + } + + /// + /// Returns the instantaneous velocity given by 3 points and a normalized time value. + /// + /// The start point. + /// The control point. + /// The end point. + /// A value between 0 and 1. + public static Vector2 Velocity(Vector2 p0, Vector2 p1, Vector2 p2, float t) + { + return QuadraticBezierCurve3D.Velocity( + new Vector3(p0.X, p0.Y, 0), + new Vector3(p1.X, p1.Y, 0), + new Vector3(p2.X, p2.Y, 0), + t + ).XY(); + } + + /// + /// Returns the instantaneous velocity given by 3 points and a normalized time value. + /// + /// The start point. + /// The control point. + /// The end point. + /// A value between startTime and endTime. + /// + /// + public static Vector2 Velocity(Vector2 p0, Vector2 p1, Vector2 p2, float t, float startTime, float endTime) + { + return Velocity(p0, p1, p2, TimeHelper.Normalized(t, startTime, endTime)); + } + } +} \ No newline at end of file diff --git a/Curve/QuadraticBezierCurve3D.cs b/Curve/QuadraticBezierCurve3D.cs new file mode 100644 index 0000000..e710fb3 --- /dev/null +++ b/Curve/QuadraticBezierCurve3D.cs @@ -0,0 +1,142 @@ +using Microsoft.Xna.Framework; + +namespace MoonTools.Core.Curve +{ + /// + /// A 3-dimensional Bezier curve defined by 3 points. + /// + public struct QuadraticBezierCurve3D + { + /// + /// The start point. + /// + public Vector3 p0; + + /// + /// The control point. + /// + public Vector3 p1; + + /// + /// The end point. + /// + public Vector3 p2; + + /// The start point. + /// The control point. + /// The end point. + public QuadraticBezierCurve3D(Vector3 p0, Vector3 p1, Vector3 p2) + { + this.p0 = p0; + this.p1 = p1; + this.p2 = p2; + } + + /// + /// Returns the curve coordinate given by t. + /// + /// A value between 0 and 1. + public Vector3 Point(float t) => Point(p0, p1, p2, t); + + /// + /// Returns the curve coordinate given by a normalized time value. + /// + /// A time value between startTime and endTime. + /// + /// + public Vector3 Point(float t, float startTime, float endTime) => Point(p0, p1, p2, t, startTime, endTime); + + /// + /// Returns the instantaneous velocity on the curve given by t. + /// + /// A value between 0 and 1. + public Vector3 Velocity(float t) => Velocity(p0, p1, p2, t); + + /// + /// Returns the instantaneous velocity on the curve given by a normalized time value. + /// + /// An arbitrary value between startTime and endTime. + /// + /// + public Vector3 Velocity(float t, float startTime, float endTime) => Velocity(p0, p1, p2, t, startTime, endTime); + + /// + /// Performs degree elevation on the curve to turn it into a Cubic Bezier curve. + /// + /// The same curve expressed as a cubic curve. + public CubicBezierCurve3D AsCubic() + { + var (p0, p1, p2, p3) = AsCubic(this.p0, this.p1, this.p2); + return new CubicBezierCurve3D(p0, p1, p2, p3); + } + + /// + /// Given quadratic control points, returns cubic control points. + /// + public static (Vector3, Vector3, Vector3, Vector3) AsCubic(Vector3 p0, Vector3 p1, Vector3 p2) + { + var cubicP0 = p0; + var cubicP1 = (2f / 3f * p1) + ((1f / 3f) * p0); + var cubicP2 = (2f / 3f * p1) + ((1f / 3f) * p2); + var cubicP3 = p2; + + return (cubicP0, cubicP1, cubicP2, cubicP3); + } + + /// + /// Returns the curve coordinate given by 3 points and a time value. + /// + /// The start point. + /// The control point. + /// The end point. + /// A value between 0 and 1. + public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, float t) + { + var (cubicP0, cubicP1, cubicP2, cubicP3) = AsCubic(p0, p1, p2); + return CubicBezierCurve3D.Point(cubicP0, cubicP1, cubicP2, cubicP3, t); + } + + /// + /// Returns the curve coordinate given by 3 points and a normalized time value. + /// + /// The start point. + /// The control point. + /// The end point. + /// A value between startTime and endTime. + /// + /// + public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, float t, float startTime, float endTime) + { + var (cubicP0, cubicP1, cubicP2, cubicP3) = AsCubic(p0, p1, p2); + return CubicBezierCurve3D.Point(cubicP0, cubicP1, cubicP2, cubicP3, t, startTime, endTime); + } + + /// + /// Returns the instantaneous velocity given by 3 points and a normalized time value. + /// + /// The start point. + /// The control point. + /// The end point. + /// A value between 0 and 1. + public static Vector3 Velocity(Vector3 p0, Vector3 p1, Vector3 p2, float t) + { + var (cubicP0, cubicP1, cubicP2, cubicP3) = AsCubic(p0, p1, p2); + return CubicBezierCurve3D.Velocity(cubicP0, cubicP1, cubicP2, cubicP3, t); + } + + /// + /// Returns the instantaneous velocity given by 3 points and a normalized time value. + /// + /// The start point. + /// The control point. + /// The end point. + /// A value between startTime and endTime. + /// + /// + public static Vector3 Velocity(Vector3 p0, Vector3 p1, Vector3 p2, float t, float startTime, float endTime) + { + var (cubicP0, cubicP1, cubicP2, cubicP3) = AsCubic(p0, p1, p2); + return CubicBezierCurve3D.Velocity(cubicP0, cubicP1, cubicP2, cubicP3, t, startTime, endTime); + } + } +} \ No newline at end of file diff --git a/Curve/Helper.cs b/Curve/TimeHelper.cs similarity index 100% rename from Curve/Helper.cs rename to Curve/TimeHelper.cs diff --git a/Test/CubicBezierCurve2D.cs b/Test/CubicBezierCurve2D.cs index dd19a21..08f06ac 100644 --- a/Test/CubicBezierCurve2D.cs +++ b/Test/CubicBezierCurve2D.cs @@ -3,6 +3,7 @@ using FluentAssertions; using MoonTools.Core.Curve; using Microsoft.Xna.Framework; +using System; namespace Tests { @@ -20,6 +21,9 @@ namespace Tests CubicBezierCurve2D.Point(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector2(0, 0)); CubicBezierCurve2D.Point(p0, p1, p2, p3, 0.25f).Should().BeEquivalentTo(new Vector2(-2.1875f, -0.5f)); CubicBezierCurve2D.Point(p0, p1, p2, p3, 0.75f).Should().BeEquivalentTo(new Vector2(2.1875f, 0.5f)); + + Action invalidTime = () => CubicBezierCurve2D.Point(p0, p1, p2, p3, 1.5f); + invalidTime.Should().Throw(); } [Test] @@ -33,6 +37,9 @@ namespace Tests CubicBezierCurve2D.Point(p0, p1, p2, p3, 3, 2, 4).Should().BeEquivalentTo(new Vector2(0, 0)); CubicBezierCurve2D.Point(p0, p1, p2, p3, 2, 1, 5).Should().BeEquivalentTo(new Vector2(-2.1875f, -0.5f)); CubicBezierCurve2D.Point(p0, p1, p2, p3, 11, 2, 14).Should().BeEquivalentTo(new Vector2(2.1875f, 0.5f)); + + Action invalidTime = () => CubicBezierCurve2D.Point(p0, p1, p2, p3, 15, 2, 5); + invalidTime.Should().Throw(); } [Test] @@ -46,6 +53,9 @@ namespace Tests CubicBezierCurve2D.Velocity(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector2(9, 0)); CubicBezierCurve2D.Velocity(p0, p1, p2, p3, 0.25f).Should().BeEquivalentTo(new Vector2(8.25f, 6f)); CubicBezierCurve2D.Velocity(p0, p1, p2, p3, 0.75f).Should().BeEquivalentTo(new Vector2(8.25f, 6f)); + + Action invalidTime = () => CubicBezierCurve2D.Velocity(p0, p1, p2, p3, 1.5f); + invalidTime.Should().Throw(); } [Test] @@ -59,6 +69,9 @@ namespace Tests CubicBezierCurve2D.Velocity(p0, p1, p2, p3, 3, 2, 4).Should().BeEquivalentTo(new Vector2(9, 0)); CubicBezierCurve2D.Velocity(p0, p1, p2, p3, 2, 1, 5).Should().BeEquivalentTo(new Vector2(8.25f, 6f)); CubicBezierCurve2D.Velocity(p0, p1, p2, p3, 11, 2, 14).Should().BeEquivalentTo(new Vector2(8.25f, 6f)); + + Action invalidTime = () => CubicBezierCurve2D.Velocity(p0, p1, p2, p3, 15, 2, 5); + invalidTime.Should().Throw(); } } @@ -77,6 +90,8 @@ namespace Tests myCurve.Point(0.5f).Should().BeEquivalentTo(new Vector2(0, 0)); myCurve.Point(0.25f).Should().BeEquivalentTo(new Vector2(-2.1875f, -0.5f)); myCurve.Point(0.75f).Should().BeEquivalentTo(new Vector2(2.1875f, 0.5f)); + + myCurve.Invoking(x => x.Point(1.5f)).Should().Throw(); } [Test] @@ -92,6 +107,8 @@ namespace Tests myCurve.Point(3, 2, 4).Should().BeEquivalentTo(new Vector2(0, 0)); myCurve.Point(2, 1, 5).Should().BeEquivalentTo(new Vector2(-2.1875f, -0.5f)); myCurve.Point(11, 2, 14).Should().BeEquivalentTo(new Vector2(2.1875f, 0.5f)); + + myCurve.Invoking(x => x.Point(15, 2, 4)).Should().Throw(); } [Test] @@ -107,6 +124,8 @@ namespace Tests myCurve.Velocity(0.5f).Should().BeEquivalentTo(new Vector2(9, 0)); myCurve.Velocity(0.25f).Should().BeEquivalentTo(new Vector2(8.25f, 6f)); myCurve.Velocity(0.75f).Should().BeEquivalentTo(new Vector2(8.25f, 6f)); + + myCurve.Invoking(x => x.Velocity(1.5f)).Should().Throw(); } [Test] @@ -122,6 +141,8 @@ namespace Tests myCurve.Velocity(3, 2, 4).Should().BeEquivalentTo(new Vector2(9, 0)); myCurve.Velocity(2, 1, 5).Should().BeEquivalentTo(new Vector2(8.25f, 6f)); myCurve.Velocity(11, 2, 14).Should().BeEquivalentTo(new Vector2(8.25f, 6f)); + + myCurve.Invoking(x => x.Velocity(15, 2, 5)).Should().Throw(); } } } \ No newline at end of file diff --git a/Test/CubicBezierCurve3D.cs b/Test/CubicBezierCurve3D.cs index 912006a..baa89d1 100644 --- a/Test/CubicBezierCurve3D.cs +++ b/Test/CubicBezierCurve3D.cs @@ -3,6 +3,7 @@ using FluentAssertions; using MoonTools.Core.Curve; using Microsoft.Xna.Framework; +using System; namespace Tests { @@ -16,10 +17,15 @@ namespace Tests var p2 = new Vector3(2, -4, 3); var p3 = new Vector3(4, 4, 0); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 0f).Should().BeEquivalentTo(p0); 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)); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 1f).Should().BeEquivalentTo(p3); + + Action invalidTime = () => CubicBezierCurve3D.Point(p0, p1, p2, p3, 1.5f); + invalidTime.Should().Throw(); } [Test] @@ -30,9 +36,14 @@ namespace Tests var p2 = new Vector3(2, -4, 3); var p3 = new Vector3(4, 4, 0); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 15, 15, 17).Should().BeEquivalentTo(p0); 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)); + CubicBezierCurve3D.Point(p0, p1, p2, p3, 1, -8, 1).Should().BeEquivalentTo(p3); + + Action invalidTime = () => CubicBezierCurve3D.Point(p0, p1, p2, p3, 15, 2, 5); + invalidTime.Should().Throw(); } [Test] @@ -46,6 +57,9 @@ namespace Tests CubicBezierCurve3D.Velocity(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); CubicBezierCurve3D.Velocity(p0, p1, p2, p3, 0.25f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, 7.875f)); CubicBezierCurve3D.Velocity(p0, p1, p2, p3, 0.75f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f)); + + Action invalidTime = () => CubicBezierCurve3D.Velocity(p0, p1, p2, p3, 1.5f); + invalidTime.Should().Throw(); } [Test] @@ -59,6 +73,9 @@ namespace Tests CubicBezierCurve3D.Velocity(p0, p1, p2, p3, 3, 2, 4).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); CubicBezierCurve3D.Velocity(p0, p1, p2, p3, 2, 1, 5).Should().BeEquivalentTo(new Vector3(8.25f, 6f, 7.875f)); CubicBezierCurve3D.Velocity(p0, p1, p2, p3, 11, 2, 14).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f)); + + Action invalidTime = () => CubicBezierCurve3D.Velocity(p0, p1, p2, p3, 15, 2, 5); + invalidTime.Should().Throw(); } } @@ -74,9 +91,13 @@ namespace Tests new Vector3(4, 4, 0) ); + myCurve.Point(0f).Should().BeEquivalentTo(myCurve.p0); 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)); + myCurve.Point(1f).Should().BeEquivalentTo(myCurve.p3); + + myCurve.Invoking(x => x.Point(1.5f)).Should().Throw(); } [Test] @@ -89,9 +110,13 @@ namespace Tests new Vector3(4, 4, 0) ); + myCurve.Point(15, 15, 17).Should().BeEquivalentTo(myCurve.p0); 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)); + myCurve.Point(1, -8, 1).Should().BeEquivalentTo(myCurve.p3); + + myCurve.Invoking(x => x.Point(15, 2, 5)).Should().Throw(); } [Test] @@ -107,6 +132,8 @@ namespace Tests 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)); + + myCurve.Invoking(x => x.Velocity(1.5f)).Should().Throw(); } [Test] @@ -122,6 +149,8 @@ namespace Tests 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)); + + myCurve.Invoking(x => x.Velocity(15, 2, 5)).Should().Throw(); } } } \ No newline at end of file diff --git a/Test/QuadraticBezierCurve2D.cs b/Test/QuadraticBezierCurve2D.cs new file mode 100644 index 0000000..360f8a8 --- /dev/null +++ b/Test/QuadraticBezierCurve2D.cs @@ -0,0 +1,155 @@ +using NUnit.Framework; +using FluentAssertions; + +using MoonTools.Core.Curve; +using Microsoft.Xna.Framework; +using System; + +namespace Tests +{ + public class QuadraticBezierCurve2DMathTests + { + [Test] + public void Point() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + QuadraticBezierCurve2D.Point(p0, p1, p2, 0).Should().BeEquivalentTo(p0); + QuadraticBezierCurve2D.Point(p0, p1, p2, 0.25f).Should().BeEquivalentTo(new Vector2(-1.25f, -3.5f)); + QuadraticBezierCurve2D.Point(p0, p1, p2, 0.75f).Should().BeEquivalentTo(new Vector2(2.75f, 0.5f)); + QuadraticBezierCurve2D.Point(p0, p1, p2, 1).Should().BeEquivalentTo(p2); + + Action invalidTime = () => QuadraticBezierCurve2D.Point(p0, p1, p2, 1.5f); + invalidTime.Should().Throw(); + } + + [Test] + public void PointNormalized() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + QuadraticBezierCurve2D.Point(p0, p1, p2, 15, 15, 17).Should().BeEquivalentTo(p0); + QuadraticBezierCurve2D.Point(p0, p1, p2, 2, 1, 5).Should().BeEquivalentTo(new Vector2(-1.25f, -3.5f)); + QuadraticBezierCurve2D.Point(p0, p1, p2, 8, 2, 10).Should().BeEquivalentTo(new Vector2(2.75f, 0.5f)); + QuadraticBezierCurve2D.Point(p0, p1, p2, 1, -8, 1).Should().BeEquivalentTo(p2); + + Action invalidTime = () => QuadraticBezierCurve2D.Point(p0, p1, p2, 15, 2, 5); + invalidTime.Should().Throw(); + } + + [Test] + public void Velocity() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + QuadraticBezierCurve2D.Velocity(p0, p1, p2, 0.25f).Should().BeEquivalentTo(new Vector2(10f, 3.9999998f)); + QuadraticBezierCurve2D.Velocity(p0, p1, p2, 0.75f).Should().BeEquivalentTo(new Vector2(6, 12)); + + Action invalidTime = () => QuadraticBezierCurve2D.Velocity(p0, p1, p2, 1.5f); + invalidTime.Should().Throw(); + } + + [Test] + public void VelocityNormalized() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + QuadraticBezierCurve2D.Velocity(p0, p1, p2, 2, 1, 5).Should().BeEquivalentTo(new Vector2(10f, 3.9999998f)); + QuadraticBezierCurve2D.Velocity(p0, p1, p2, 8, 2, 10).Should().BeEquivalentTo(new Vector2(6, 12f)); + + Action invalidTime = () => QuadraticBezierCurve2D.Velocity(p0, p1, p1, 15, 2, 5); + invalidTime.Should().Throw(); + } + } + + public class QuadraticBezierCurve2DStructTests + { + [Test] + public void Point() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + var myCurve = new QuadraticBezierCurve2D(p0, p1, p2); + + myCurve.Point(0).Should().BeEquivalentTo(p0); + myCurve.Point(0.25f).Should().BeEquivalentTo(new Vector2(-1.25f, -3.5f)); + myCurve.Point(0.75f).Should().BeEquivalentTo(new Vector2(2.75f, 0.5f)); + myCurve.Point(1).Should().BeEquivalentTo(p2); + + myCurve.Invoking(x => x.Point(1.5f)).Should().Throw(); + } + + [Test] + public void PointNormalized() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + var myCurve = new QuadraticBezierCurve2D(p0, p1, p2); + + myCurve.Point(15, 15, 17).Should().BeEquivalentTo(p0); + myCurve.Point(2, 1, 5).Should().BeEquivalentTo(new Vector2(-1.25f, -3.5f)); + myCurve.Point(8, 2, 10).Should().BeEquivalentTo(new Vector2(2.75f, 0.5f)); + myCurve.Point(1, -8, 1).Should().BeEquivalentTo(p2); + + myCurve.Invoking(x => x.Point(15, 2, 5)).Should().Throw(); + } + + [Test] + public void Velocity() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + var myCurve = new QuadraticBezierCurve2D(p0, p1, p2); + + myCurve.Velocity(0.25f).Should().BeEquivalentTo(new Vector2(10f, 3.9999998f)); + myCurve.Velocity(0.75f).Should().BeEquivalentTo(new Vector2(6, 12)); + + myCurve.Invoking(x => x.Velocity(1.5f)).Should().Throw(); + } + + [Test] + public void VelocityNormalized() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + var myCurve = new QuadraticBezierCurve2D(p0, p1, p2); + + myCurve.Velocity(2, 1, 5).Should().BeEquivalentTo(new Vector2(10f, 3.9999998f)); + myCurve.Velocity(8, 2, 10).Should().BeEquivalentTo(new Vector2(6, 12)); + + myCurve.Invoking(x => x.Velocity(15, 2, 5)).Should().Throw(); + } + + [Test] + public void AsCubic() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(2, -4); + var p2 = new Vector2(4, 4); + + var myCurve = new QuadraticBezierCurve2D(p0, p1, p2); + var myCubicCurve = myCurve.AsCubic(); + + myCurve.Point(0f).Should().BeEquivalentTo(myCubicCurve.Point(0f)); + myCurve.Point(0.25f).Should().BeEquivalentTo(myCubicCurve.Point(0.25f)); + myCurve.Point(0.75f).Should().BeEquivalentTo(myCubicCurve.Point(0.75f)); + myCurve.Point(1f).Should().BeEquivalentTo(myCubicCurve.Point(1f)); + } + } +} \ No newline at end of file diff --git a/Test/QuadraticBezierCurve3D.cs b/Test/QuadraticBezierCurve3D.cs new file mode 100644 index 0000000..9662095 --- /dev/null +++ b/Test/QuadraticBezierCurve3D.cs @@ -0,0 +1,155 @@ +using NUnit.Framework; +using FluentAssertions; + +using MoonTools.Core.Curve; +using Microsoft.Xna.Framework; +using System; + +namespace Tests +{ + public class QuadraticBezierCurve3DMathTests + { + [Test] + public void Point() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + QuadraticBezierCurve3D.Point(p0, p1, p2, 0).Should().BeEquivalentTo(p0); + QuadraticBezierCurve3D.Point(p0, p1, p2, 0.25f).Should().BeEquivalentTo(new Vector3(-1.25f, -3.5f, -0.5625f)); + QuadraticBezierCurve3D.Point(p0, p1, p2, 0.75f).Should().BeEquivalentTo(new Vector3(2.75f, 0.5f, 0.9375f)); + QuadraticBezierCurve3D.Point(p0, p1, p2, 1).Should().BeEquivalentTo(p2); + + Action invalidTime = () => QuadraticBezierCurve3D.Point(p0, p1, p2, 1.5f); + invalidTime.Should().Throw(); + } + + [Test] + public void PointNormalized() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + QuadraticBezierCurve3D.Point(p0, p1, p2, 15, 15, 17).Should().BeEquivalentTo(p0); + QuadraticBezierCurve3D.Point(p0, p1, p2, 2, 1, 5).Should().BeEquivalentTo(new Vector3(-1.25f, -3.5f, -0.5625f)); + QuadraticBezierCurve3D.Point(p0, p1, p2, 8, 2, 10).Should().BeEquivalentTo(new Vector3(2.75f, 0.5f, 0.9375f)); + QuadraticBezierCurve3D.Point(p0, p1, p2, 1, -8, 1).Should().BeEquivalentTo(p2); + + Action invalidTime = () => QuadraticBezierCurve3D.Point(p0, p1, p2, 15, 2, 5); + invalidTime.Should().Throw(); + } + + [Test] + public void Velocity() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + QuadraticBezierCurve3D.Velocity(p0, p1, p2, 0.25f).Should().BeEquivalentTo(new Vector3(10f, 3.9999998f, 7.5f)); + QuadraticBezierCurve3D.Velocity(p0, p1, p2, 0.75f).Should().BeEquivalentTo(new Vector3(6, 12, -1.5f)); + + Action invalidTime = () => QuadraticBezierCurve3D.Velocity(p0, p1, p2, 1.5f); + invalidTime.Should().Throw(); + } + + [Test] + public void VelocityNormalized() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + QuadraticBezierCurve3D.Velocity(p0, p1, p2, 2, 1, 5).Should().BeEquivalentTo(new Vector3(10f, 3.9999998f, 7.5f)); + QuadraticBezierCurve3D.Velocity(p0, p1, p2, 8, 2, 10).Should().BeEquivalentTo(new Vector3(6, 12, -1.5f)); + + Action invalidTime = () => QuadraticBezierCurve3D.Velocity(p0, p1, p1, 15, 2, 5); + invalidTime.Should().Throw(); + } + } + + public class QuadraticBezierCurve3DStructTests + { + [Test] + public void Point() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + var myCurve = new QuadraticBezierCurve3D(p0, p1, p2); + + myCurve.Point(0).Should().BeEquivalentTo(p0); + myCurve.Point(0.25f).Should().BeEquivalentTo(new Vector3(-1.25f, -3.5f, -0.5625f)); + myCurve.Point(0.75f).Should().BeEquivalentTo(new Vector3(2.75f, 0.5f, 0.9375f)); + myCurve.Point(1).Should().BeEquivalentTo(p2); + + myCurve.Invoking(x => x.Point(1.5f)).Should().Throw(); + } + + [Test] + public void PointNormalized() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + var myCurve = new QuadraticBezierCurve3D(p0, p1, p2); + + myCurve.Point(15, 15, 17).Should().BeEquivalentTo(p0); + myCurve.Point(2, 1, 5).Should().BeEquivalentTo(new Vector3(-1.25f, -3.5f, -0.5625f)); + myCurve.Point(8, 2, 10).Should().BeEquivalentTo(new Vector3(2.75f, 0.5f, 0.9375f)); + myCurve.Point(1, -8, 1).Should().BeEquivalentTo(p2); + + myCurve.Invoking(x => x.Point(15, 2, 5)).Should().Throw(); + } + + [Test] + public void Velocity() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + var myCurve = new QuadraticBezierCurve3D(p0, p1, p2); + + myCurve.Velocity(0.25f).Should().BeEquivalentTo(new Vector3(10f, 3.9999998f, 7.5f)); + myCurve.Velocity(0.75f).Should().BeEquivalentTo(new Vector3(6, 12, -1.5f)); + + myCurve.Invoking(x => x.Velocity(1.5f)).Should().Throw(); + } + + [Test] + public void VelocityNormalized() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + var myCurve = new QuadraticBezierCurve3D(p0, p1, p2); + + myCurve.Velocity(2, 1, 5).Should().BeEquivalentTo(new Vector3(10f, 3.9999998f, 7.5f)); + myCurve.Velocity(8, 2, 10).Should().BeEquivalentTo(new Vector3(6, 12, -1.5f)); + + myCurve.Invoking(x => x.Velocity(15, 2, 5)).Should().Throw(); + } + + [Test] + public void AsCubic() + { + var p0 = new Vector3(-4, -4, -3); + var p1 = new Vector3(2, -4, 3); + var p2 = new Vector3(4, 4, 0); + + var myCurve = new QuadraticBezierCurve3D(p0, p1, p2); + var myCubicCurve = myCurve.AsCubic(); + + myCurve.Point(0f).Should().BeEquivalentTo(myCubicCurve.Point(0f)); + myCurve.Point(0.25f).Should().BeEquivalentTo(myCubicCurve.Point(0.25f)); + myCurve.Point(0.75f).Should().BeEquivalentTo(myCubicCurve.Point(0.75f)); + myCurve.Point(1f).Should().BeEquivalentTo(myCubicCurve.Point(1f)); + } + } +} \ No newline at end of file