From a51e7003fe2eaa95bf7cae754704f68ba691f409 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Mon, 28 Oct 2019 11:17:14 -0700 Subject: [PATCH] cubic 2D --- Curve/CubicBezierCurve2D.cs | 57 ++++++++++++++++ Curve/CubicBezierCurve3D.cs | 14 ++-- Curve/Extensions.cs | 9 +++ Curve/Helper.cs | 7 ++ Test/CubicBezierCurve2D.cs | 127 ++++++++++++++++++++++++++++++++++++ Test/CubicBezierCurve3D.cs | 16 ++--- 6 files changed, 214 insertions(+), 16 deletions(-) create mode 100644 Curve/CubicBezierCurve2D.cs create mode 100644 Curve/Extensions.cs create mode 100644 Curve/Helper.cs create mode 100644 Test/CubicBezierCurve2D.cs diff --git a/Curve/CubicBezierCurve2D.cs b/Curve/CubicBezierCurve2D.cs new file mode 100644 index 0000000..f3acebd --- /dev/null +++ b/Curve/CubicBezierCurve2D.cs @@ -0,0 +1,57 @@ +using Microsoft.Xna.Framework; +using MoonTools.Core.Curve.Extensions; + +namespace MoonTools.Core.Curve +{ + public struct CubicBezierCurve2D + { + public Vector2 p0; + public Vector2 p1; + public Vector2 p2; + public Vector2 p3; + + public CubicBezierCurve2D(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) + { + this.p0 = p0; + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + } + + public Vector2 Point(float t) => Point(p0, p1, p2, p3, t); + public Vector2 Point(float t, float minT, float maxT) => Point(p0, p1, p2, p3, t, minT, maxT); + public Vector2 Velocity(float t) => Velocity(p0, p1, p2, p3, t); + public Vector2 Velocity(float t, float minT, float maxT) => Velocity(p0, p1, p2, p3, TimeHelper.Normalized(t, minT, maxT)); + public static Vector2 Point(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t) + { + return CubicBezierCurve3D.Point( + new Vector3(p0.X, p0.Y, 0), + new Vector3(p1.X, p1.Y, 0), + new Vector3(p2.X, p2.Y, 0), + new Vector3(p3.X, p3.Y, 0), + t + ).XY(); + } + + public static Vector2 Point(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t, float minT, float maxT) + { + return Point(p0, p1, p2, p3, TimeHelper.Normalized(t, minT, maxT)); + } + + public static Vector2 Velocity(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t) + { + return CubicBezierCurve3D.Velocity( + new Vector3(p0.X, p0.Y, 0), + new Vector3(p1.X, p1.Y, 0), + new Vector3(p2.X, p2.Y, 0), + new Vector3(p3.X, p3.Y, 0), + t + ).XY(); + } + + public static Vector2 Velocity(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t, float minT, float maxT) + { + return Velocity(p0, p1, p2, p3, TimeHelper.Normalized(t, minT, maxT)); + } + } +} \ No newline at end of file diff --git a/Curve/CubicBezierCurve3D.cs b/Curve/CubicBezierCurve3D.cs index 8060071..c079f95 100644 --- a/Curve/CubicBezierCurve3D.cs +++ b/Curve/CubicBezierCurve3D.cs @@ -21,9 +21,9 @@ namespace MoonTools.Core.Curve 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) => Velocity(p0, p1, p2, p3, t); - public Vector3 Velocity(float t, float minT, float maxT) => FirstDerivative(p0, p1, p2, p3, t, minT, maxT); + public Vector3 Velocity(float t, float minT, float maxT) => Velocity(p0, p1, p2, p3, t, minT, maxT); public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { @@ -37,10 +37,10 @@ namespace MoonTools.Core.Curve 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)); + return Point(p0, p1, p2, p3, TimeHelper.Normalized(t, minT, maxT)); } - public static Vector3 FirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) + 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"); } @@ -49,11 +49,9 @@ namespace MoonTools.Core.Curve 3f * t * t * (p3 - p2); } - public static Vector3 FirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) + public static Vector3 Velocity(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) { - return FirstDerivative(p0, p1, p2, p3, Normalized(t, minT, maxT)); + return Velocity(p0, p1, p2, p3, TimeHelper.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/Curve/Extensions.cs b/Curve/Extensions.cs new file mode 100644 index 0000000..a24a24a --- /dev/null +++ b/Curve/Extensions.cs @@ -0,0 +1,9 @@ +using Microsoft.Xna.Framework; + +namespace MoonTools.Core.Curve.Extensions +{ + public static class Vector2Extensions + { + public static Vector2 XY(this Vector3 vector) => new Vector2(vector.X, vector.Y); + } +} \ No newline at end of file diff --git a/Curve/Helper.cs b/Curve/Helper.cs new file mode 100644 index 0000000..a0f6757 --- /dev/null +++ b/Curve/Helper.cs @@ -0,0 +1,7 @@ +namespace MoonTools.Core.Curve +{ + public static class TimeHelper + { + public static float Normalized(float t, float minT, float maxT) => ((t - minT)) / (maxT - minT); + } +} \ No newline at end of file diff --git a/Test/CubicBezierCurve2D.cs b/Test/CubicBezierCurve2D.cs new file mode 100644 index 0000000..dd19a21 --- /dev/null +++ b/Test/CubicBezierCurve2D.cs @@ -0,0 +1,127 @@ +using NUnit.Framework; +using FluentAssertions; + +using MoonTools.Core.Curve; +using Microsoft.Xna.Framework; + +namespace Tests +{ + public class CubicBezierCurve2DTests + { + [Test] + public void Point() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(-2, 4); + var p2 = new Vector2(2, -4); + var p3 = new Vector2(4, 4); + + CubicBezierCurve2D.Point(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector2(0, 0)); + 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)); + } + + [Test] + public void PointNormalized() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(-2, 4); + var p2 = new Vector2(2, -4); + var p3 = new Vector2(4, 4); + + 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)); + } + + [Test] + public void Velocity() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(-2, 4); + var p2 = new Vector2(2, -4); + var p3 = new Vector2(4, 4); + + 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)); + } + + [Test] + public void VelocityNormalized() + { + var p0 = new Vector2(-4, -4); + var p1 = new Vector2(-2, 4); + var p2 = new Vector2(2, -4); + var p3 = new Vector2(4, 4); + + 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)); + } + } + + public class CubicBezierCurve2DStructTests + { + [Test] + public void Point() + { + var myCurve = new CubicBezierCurve2D( + new Vector2(-4, -4), + new Vector2(-2, 4), + new Vector2(2, -4), + new Vector2(4, 4) + ); + + 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)); + } + + [Test] + public void PointNormalized() + { + var myCurve = new CubicBezierCurve2D( + new Vector2(-4, -4), + new Vector2(-2, 4), + new Vector2(2, -4), + new Vector2(4, 4) + ); + + 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)); + } + + [Test] + public void Velocity() + { + var myCurve = new CubicBezierCurve2D( + new Vector2(-4, -4), + new Vector2(-2, 4), + new Vector2(2, -4), + new Vector2(4, 4) + ); + + 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)); + } + + [Test] + public void VelocityNormalized() + { + var myCurve = new CubicBezierCurve2D( + new Vector2(-4, -4), + new Vector2(-2, 4), + new Vector2(2, -4), + new Vector2(4, 4) + ); + + 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)); + } + } +} \ No newline at end of file diff --git a/Test/CubicBezierCurve3D.cs b/Test/CubicBezierCurve3D.cs index 294c1cf..912006a 100644 --- a/Test/CubicBezierCurve3D.cs +++ b/Test/CubicBezierCurve3D.cs @@ -36,29 +36,29 @@ namespace Tests } [Test] - public void FirstDerivative() + public void Velocity() { 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)); + 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)); } [Test] - public void FirstDerivativeNormalized() + public void VelocityNormalized() { 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)); + 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)); } }