cubic bezier curve 3d implementation

master
Evan Hemsley 2019-10-25 01:25:39 -07:00
parent 8ea81d09ba
commit a4123f9c03
3 changed files with 186 additions and 118 deletions

View File

@ -1,41 +1,59 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
namespace MoonTools.Core.Curve namespace MoonTools.Core.Curve
{ {
public static class CubicBezier3D public struct CubicBezierCurve3D
{ {
public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) public Vector3 p0;
{ public Vector3 p1;
if (t < 0 || t > 1) { throw new System.ArgumentException($"{t} is an invalid value. Must be between 0 and 1"); } public Vector3 p2;
public Vector3 p3;
return (1f - t) * (1f - t) * (1f - t) * p0 +
3f * (1f - t) * (1f - t) * t * p1 + public CubicBezierCurve3D(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
3f * (1f - t) * t * t * p2 + {
t * t * t * p3; this.p0 = p0;
} this.p1 = p1;
this.p2 = p2;
public static Vector3 Point(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT) this.p3 = p3;
{ }
return Point(p0, p1, p2, p3, NormalizedT(t, minT, maxT));
} public Vector3 Point(float t) => Point(p0, p1, p2, p3, t);
public static Vector3 FirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) public Vector3 Point(float t, float minT, float maxT) => Point(p0, p1, p2, p3, t, minT, maxT);
{
if (t < 0 || t > 1) { throw new System.ArgumentException($"{t} is an invalid value. Must be between 0 and 1"); } public Vector3 Velocity(float t) => FirstDerivative(p0, p1, p2, p3, t);
return 3f * (1f - t) * (1f - t) * (p1 - p0) + public Vector3 Velocity(float t, float minT, float maxT) => FirstDerivative(p0, p1, p2, p3, t, minT, maxT);
6f * (1f - t) * t * (p2 - p1) +
3f * t * t * (p3 - p2); 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"); }
public static Vector3 FirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t, float minT, float maxT)
{ return (1f - t) * (1f - t) * (1f - t) * p0 +
return FirstDerivative(p0, p1, p2, p3, NormalizedT(t, minT, maxT)); 3f * (1f - t) * (1f - t) * t * p1 +
} 3f * (1f - t) * t * t * p2 +
t * t * t * p3;
private static float NormalizedT(float t, float minT, float maxT) }
{
return ((t - minT)) / (maxT - minT); 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);
}
}

View File

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

127
Test/CubicBezierCurve3D.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 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));
}
}
}