using System;
using UnityEngine;
namespace Framework.Support {
/// A cubic bezier curve based on 4 points
public struct CubicBezierCurve {
/// Point the curve will start at
public Vector3 P0;
/// Point towards which the curve leaves the starting point
public Vector3 P1;
/// Point away from which the curve reaches the ending point
public Vector3 P2;
/// Point the curve ends at
public Vector3 P3;
///
/// Initializes a new cubic bezier curve from the provided support points
///
/// Point the curve will start at
///
/// Point in whose direction the curve will leave the starting point
///
///
/// Point from whose direction the curve will reach the ending point
///
/// Point the curve will end at
public CubicBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3) {
this.P0 = p0;
this.P1 = p1;
this.P2 = p2;
this.P3 = p3;
}
/// Approximates the length of the bezier curve
/// The approximate length of the berzier curve
public float ApproximateLength() {
const int SegmentCount = 10;
float approximateLength = 0.0f;
// Break the curve into line segments and sum the length of those line segments.
// I couldn't find any code that actually calculates the length based on the input
// parameters to the bezier formula.
Vector3 previous;
Vector3 current = this.P0;
for(int index = 1; index < SegmentCount; ++index) {
previous = current;
current = Interpolate(
this.P0, this.P1, this.P2, this.P3, (float)index / (float)SegmentCount
);
approximateLength += (current - previous).magnitude;
}
return approximateLength + (this.P3 - current).magnitude;
}
#if false
internal static float MaybeGetLength(Vector3 b0, Vector3 b1, Vector3 b2, Vector3 b3) {
Vector3 p0 = (b0 - b1);
Vector3 p1 = (b2 - b1);
Vector3 p2;
Vector3 p3 = (b3 - b2);
float l0 = p0.magnitude;
float l1 = p1.magnitude;
float l3 = p3.magnitude;
if(l0 > 0.0f) {
p0 /= l0;
}
if(l1 > 0.0f) {
p1 /= l1;
}
if(l3 > 0.0f) {
p3 /= l3;
}
p2 = -p1;
float a = Mathf.Abs(Vector3.Dot(p0, p1)) + Mathf.Abs(Vector3.Dot(p2, p3));
if((a > 1.98f) || ((l0 + l1 + l3) < ((4.0f - a) * 8.0f))) {
return l0 + l1 + l3;
}
Vector3 bl0 = b0;
Vector3 bl1 = (b0 + b1) * 0.5f;
Vector3 mid = (b1 + b2) * 0.5f;
Vector3 bl2 = (bl1 + mid) * 0.5f;
Vector3 br3 = b3;
Vector3 br2 = (b2 + b3) * 0.5f;
Vector3 br1 = (br2 + mid) * 0.5f;
Vector3 br0 = (br1 + bl2) * 0.5f;
Vector3 bl3 = br0;
return MaybeGetLength(bl0, bl1, bl2, bl3) + MaybeGetLength(br0, br1, br2, br3);
}
#endif
/// Calculates the point at the specified interpolation time
/// Time at which the point will be calculated
/// The interpolated point at the specified time
public Vector3 GetPointAtTime(float t) {
return Interpolate(this.P0, this.P1, this.P2, this.P3, t);
}
///
/// Calculates a point on the berzier curve at the specified distance
/// (following the curve) from the start
///
/// Distance from the start of the curve
/// The interpolated location
///
///
/// This is a very rough approximation since the velocity along the curve
/// can be less than one (eg. at a tight bend), so using this method does not
/// guarantee equal distance traveled for the resulting point when linearly
/// increasing the distance.
///
///
/// If you need a more accurate version of this method, consider splitting
/// the curve into line segments, caching the length of those line segments
/// and interpolating the points along the curve this way. Or finding an
/// equation that calculates T from distance (I couldn't find one.)
///
///
public Vector3 GetPointAtDistance(float distanceFromStart) {
return Interpolate(
this.P0, this.P1, this.P2, this.P3, distanceFromStart / ApproximateLength()
);
}
/// Interpolates a point along a cubic bezier curve
/// Location from which the curve starts
/// Point towards which the direction of the curve starts out
///
/// Point from which towards the end the direction of the curve ends
///
/// Location at which the curve ends
/// Interpolation interval in the range 0.0 .. 1.0
/// The interpolated position
public static Vector3 Interpolate(
Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t
) {
return
(float)Math.Pow(1 - t, 3) * p0 +
3 * (float)Math.Pow(1 - t, 2) * t * p1 +
3 * (1 - t) * (float)Math.Pow(t, 2) * p2 +
(float)Math.Pow(t, 3) * p3;
}
}
} // namespace Framework.Support