master
Evan Hemsley 2019-10-28 11:17:14 -07:00
parent a4123f9c03
commit a51e7003fe
6 changed files with 214 additions and 16 deletions

View File

@ -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));
}
}
}

View File

@ -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 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) 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) 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"); } 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); 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);
} }
} }

9
Curve/Extensions.cs Normal file
View File

@ -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);
}
}

7
Curve/Helper.cs Normal file
View File

@ -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);
}
}

127
Test/CubicBezierCurve2D.cs Normal file
View File

@ -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));
}
}
}

View File

@ -36,29 +36,29 @@ namespace Tests
} }
[Test] [Test]
public void FirstDerivative() public void Velocity()
{ {
var p0 = new Vector3(-4, -4, -3); var p0 = new Vector3(-4, -4, -3);
var p1 = new Vector3(-2, 4, 0); var p1 = new Vector3(-2, 4, 0);
var p2 = new Vector3(2, -4, 3); var p2 = new Vector3(2, -4, 3);
var p3 = new Vector3(4, 4, 0); var p3 = new Vector3(4, 4, 0);
CubicBezierCurve3D.FirstDerivative(p0, p1, p2, p3, 0.5f).Should().BeEquivalentTo(new Vector3(9, 0, 4.5f)); CubicBezierCurve3D.Velocity(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.Velocity(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.75f).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f));
} }
[Test] [Test]
public void FirstDerivativeNormalized() public void VelocityNormalized()
{ {
var p0 = new Vector3(-4, -4, -3); var p0 = new Vector3(-4, -4, -3);
var p1 = new Vector3(-2, 4, 0); var p1 = new Vector3(-2, 4, 0);
var p2 = new Vector3(2, -4, 3); var p2 = new Vector3(2, -4, 3);
var p3 = new Vector3(4, 4, 0); 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.Velocity(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.Velocity(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, 11, 2, 14).Should().BeEquivalentTo(new Vector3(8.25f, 6f, -1.125f));
} }
} }