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