using UnityEngine; using System.Collections; using System.Collections.Generic; using Dreamteck; namespace Dreamteck.Splines { //The Spline class defines a spline with world coordinates. It comes with various sampling methods [System.Serializable] public class Spline { public enum Direction { Forward = 1, Backward = -1 } public enum Type { CatmullRom, BSpline, Bezier, Linear }; public SplinePoint[] points = new SplinePoint[0]; public Type type = Type.Bezier; public bool linearAverageDirection = true; public AnimationCurve customValueInterpolation = null; public AnimationCurve customNormalInterpolation = null; public int sampleRate = 10; /// /// Returns true if the spline is closed /// public bool isClosed { get { return closed && points.Length >= 3; } } /// /// The step size of the percent incrementation when evaluating a spline (based on percision) /// public double moveStep { get { if (type == Type.Linear) return 1f / (points.Length-1); return 1f / (iterations-1); } set { } } /// /// The total count of samples for the spline (based on the sample rate) /// public int iterations { get { if (type == Type.Linear) return closed ? points.Length + 1 : points.Length; int segments = closed ? points.Length : points.Length - 1; return sampleRate * segments - segments + 1; } } public float knotParametrization { get { return _knotParametrization; } set { _knotParametrization = Mathf.Clamp01(value); } } private static Vector3[] P = new Vector3[4]; private static Vector3 A1; private static Vector3 A2; private static Vector3 A3; private static Vector3 B1; private static Vector3 B2; private static float t1; private static float t2; private static float t3; [SerializeField] private bool closed = false; [SerializeField, Range(0f, 1f)] private float _knotParametrization; public Spline(Type type){ this.type = type; points = new SplinePoint[0]; } public Spline(Type type, int sampleRate) { this.type = type; this.sampleRate = sampleRate; points = new SplinePoint[0]; } /// /// Calculate the length of the spline /// /// Calculate from [0-1] default: 0f /// Calculate to [0-1] default: 1f /// Resolution multiplier for precision [0-1] default: 1f /// public float CalculateLength(double from = 0.0, double to = 1.0, double resolution = 1.0) { if (points.Length == 0) return 0f; resolution = DMath.Clamp01(resolution); if (resolution == 0.0) return 0f; from = DMath.Clamp01(from); to = DMath.Clamp01(to); if (to < from) to = from; double percent = from; Vector3 lastPos = EvaluatePosition(percent); float sum = 0f; while (true) { percent = DMath.Move(percent, to, moveStep / resolution); Vector3 pos = EvaluatePosition(percent); sum += (pos - lastPos).magnitude; lastPos = pos; if (percent == to) break; } return sum; } /// /// Project point on the spline. Returns evaluation percent. /// /// 3D Point /// Subdivisions default: 4 /// Sample from [0-1] default: 0f /// Sample to [0-1] default: 1f /// public double Project(Vector3 position, int subdivide = 4, double from = 0.0, double to = 1.0) { if (points.Length == 0) return 0.0; if (closed && from == 0.0 && to == 1.0) //Handle looped splines { double closest = GetClosestPoint(subdivide, position, from, to, Mathf.RoundToInt(Mathf.Max(iterations / points.Length, 10)) * 5); if (closest < moveStep) { double nextClosest = GetClosestPoint(subdivide, position, 0.5, to, Mathf.RoundToInt(Mathf.Max(iterations / points.Length, 10)) * 5); if (Vector3.Distance(position, EvaluatePosition(nextClosest)) < Vector3.Distance(position, EvaluatePosition(closest))) return nextClosest; } return closest; } return GetClosestPoint(subdivide, position, from, to, Mathf.RoundToInt(Mathf.Max(iterations / points.Length, 10)) * 5); } /// /// Casts rays along the spline against all colliders in the scene /// /// Hit information /// The percent of evaluation where the hit occured /// Layer mask for the raycast /// Resolution multiplier for precision [0-1] default: 1f /// Raycast from [0-1] default: 0f /// Raycast to [0-1] default: 1f /// Should hit triggers? (not supported in 5.1) /// public bool Raycast(out RaycastHit hit, out double hitPercent, LayerMask layerMask, double resolution = 1.0, double from = 0.0, double to = 1.0, QueryTriggerInteraction hitTriggers = QueryTriggerInteraction.UseGlobal ) { resolution = DMath.Clamp01(resolution); from = DMath.Clamp01(from); to = DMath.Clamp01(to); double percent = from; Vector3 fromPos = EvaluatePosition(percent); hitPercent = 0f; if (resolution == 0f) { hit = new RaycastHit(); hitPercent = 0f; return false; } while (true) { double prevPercent = percent; percent = DMath.Move(percent, to, moveStep / resolution); Vector3 toPos = EvaluatePosition(percent); if (Physics.Linecast(fromPos, toPos, out hit, layerMask, hitTriggers)) { double segmentPercent = (hit.point - fromPos).sqrMagnitude / (toPos - fromPos).sqrMagnitude; hitPercent = DMath.Lerp(prevPercent, percent, segmentPercent); return true; } fromPos = toPos; if (percent == to) break; } return false; } /// /// Casts rays along the spline against all colliders in the scene and returns all hits. Order is not guaranteed. /// /// Hit information /// The percents of evaluation where each hit occured /// Layer mask for the raycast /// Resolution multiplier for precision [0-1] default: 1f /// Raycast from [0-1] default: 0f /// Raycast to [0-1] default: 1f /// Should hit triggers? (not supported in 5.1) /// public bool RaycastAll(out RaycastHit[] hits, out double[] hitPercents, LayerMask layerMask, double resolution = 1.0, double from = 0.0, double to = 1.0, QueryTriggerInteraction hitTriggers = QueryTriggerInteraction.UseGlobal ) { resolution = DMath.Clamp01(resolution); from = DMath.Clamp01(from); to = DMath.Clamp01(to); double percent = from; Vector3 fromPos = EvaluatePosition(percent); List hitList = new List(); List percentList = new List(); if (resolution == 0f) { hits = new RaycastHit[0]; hitPercents = new double[0]; return false; } bool hasHit = false; while (true) { double prevPercent = percent; percent = DMath.Move(percent, to, moveStep / resolution); Vector3 toPos = EvaluatePosition(percent); RaycastHit[] h = Physics.RaycastAll(fromPos, toPos - fromPos, Vector3.Distance(fromPos, toPos), layerMask, hitTriggers); for (int i = 0; i < h.Length; i++) { hasHit = true; double segmentPercent = (h[i].point - fromPos).sqrMagnitude / (toPos - fromPos).sqrMagnitude; percentList.Add(DMath.Lerp(prevPercent, percent, segmentPercent)); hitList.Add(h[i]); } fromPos = toPos; if (percent == to) break; } hits = hitList.ToArray(); hitPercents = percentList.ToArray(); return hasHit; } /// /// Converts a point index to spline percent /// /// The point index /// public double GetPointPercent(int pointIndex) { if (closed) { return DMath.Clamp01((double)pointIndex / points.Length); } return DMath.Clamp01((double)pointIndex / (points.Length - 1)); } /// /// Evaluate the spline and return position. This is simpler and faster than Evaluate. /// /// Percent of evaluation [0-1] public Vector3 EvaluatePosition(double percent) { if (points.Length == 0) return Vector3.zero; Vector3 position = new Vector3(); EvaluatePosition(percent, ref position); return position; } /// /// Evaluate the spline at the given time and return a SplineSample /// /// Percent of evaluation [0-1] public SplineSample Evaluate(double percent) { SplineSample result = new SplineSample(); Evaluate(percent, ref result); return result; } /// /// Evaluate the spline at the position of a given point and return a SplineSample /// /// Point index public SplineSample Evaluate(int pointIndex) { SplineSample result = new SplineSample(); Evaluate(GetPointPercent(pointIndex), ref result); return result; } /// /// Evaluate the splien at the given point and write the result to the "result" object /// /// The result output /// Point index public void Evaluate(int pointIndex, ref SplineSample result) { Evaluate(GetPointPercent(pointIndex), ref result); } /// /// Evaluate the splien at the given time and write the result to the "result" object /// /// The result output /// Percent of evaluation [0-1] public void Evaluate(double percent, ref SplineSample sample) { if (points.Length == 0) { sample = new SplineSample(); return; } percent = DMath.Clamp01(percent); if (closed && points.Length <= 2) { closed = false; } if (points.Length == 1) { sample.position = points[0].position; sample.up = points[0].normal; sample.forward = Vector3.forward; sample.size = points[0].size; sample.color = points[0].color; sample.percent = percent; return; } double doubleIndex = (points.Length - 1) * percent; if (closed) { doubleIndex = points.Length * percent; } int fromIndex = DMath.FloorInt(doubleIndex); int toIndex = fromIndex + 1; if (closed) { if (fromIndex >= points.Length - 1) { fromIndex = points.Length - 1; } if(toIndex > points.Length - 1) { toIndex = 0; } } else { if(toIndex > points.Length-1) { toIndex = points.Length - 1; } } double getPercent = doubleIndex - fromIndex; sample.percent = percent; float valueInterpolation = (float)getPercent; if (customValueInterpolation != null) { if (customValueInterpolation.length > 0) { valueInterpolation = customValueInterpolation.Evaluate(valueInterpolation); } } float normalInterpolation = (float)getPercent; if (customNormalInterpolation != null) { if (customNormalInterpolation.length > 0) { normalInterpolation = customNormalInterpolation.Evaluate(normalInterpolation); } } sample.size = Mathf.Lerp(points[fromIndex].size, points[toIndex].size, valueInterpolation); sample.color = Color.Lerp(points[fromIndex].color, points[toIndex].color, valueInterpolation); sample.up = Vector3.Slerp(points[fromIndex].normal, points[toIndex].normal, normalInterpolation); EvaluatePositionAndTangent(ref sample.position, ref sample.forward, percent); if (type == Type.BSpline) { double step = 1.0 / (iterations - 1); if (percent <= 1.0 - step && percent >= step) { sample.forward = EvaluatePosition(percent + step) - EvaluatePosition(percent - step); } else { Vector3 back = Vector3.zero, front = Vector3.zero; if (closed) { if (percent < step) EvaluatePosition(1.0 - (step - percent), ref back); else EvaluatePosition(percent - step, ref back); if (percent > 1.0 - step) EvaluatePosition(step - (1.0 - percent), ref front); else EvaluatePosition(percent + step, ref front); sample.forward = front - back; } else { EvaluatePosition(percent - step, ref back); back = sample.position - back; EvaluatePosition(percent + step, ref front); front = front - sample.position; sample.forward = Vector3.Slerp(front, back, back.magnitude / front.magnitude); } } } sample.forward.Normalize(); } [System.Obsolete("This override is obsolete. Use Evaluate(int pointIndex, ref SplineSample sample) instead")] public void Evaluate(ref SplineSample sample, int pointIndex) { Evaluate(pointIndex, ref sample); } [System.Obsolete("This override is obsolete. Use Evaluate(double percent, ref SplineSample sample) instead")] public void Evaluate(ref SplineSample sample, double percent) { Evaluate(percent, ref sample); } /// /// Evaluates the spline segment and writes the results to the array /// /// Start position [0-1] /// Target position [from-1] /// public void Evaluate(ref SplineSample[] samples, double from = 0.0, double to = 1.0) { if (points.Length == 0) { samples = new SplineSample[0]; return; } from = DMath.Clamp01(from); to = DMath.Clamp(to, from, 1.0); double fromValue = from * (iterations - 1); double toValue = to * (iterations - 1); int clippedIterations = DMath.CeilInt(toValue) - DMath.FloorInt(fromValue) + 1; if (samples == null) samples = new SplineSample[clippedIterations]; else if (samples.Length != clippedIterations) samples = new SplineSample[clippedIterations]; double percent = from; double ms = moveStep; int index = 0; while (true) { samples[index] = Evaluate(percent); index++; if (index >= samples.Length) break; percent = DMath.Move(percent, to, ms); } } /// /// Evaluates the spline segment and writes uniformly spaced results to the array /// /// Start position [0-1] /// Target position [from-1] /// public void EvaluateUniform(ref SplineSample[] samples, ref double[] originalSamplePercents, double from = 0.0, double to = 1.0) { if (points.Length == 0) { samples = new SplineSample[0]; return; } from = DMath.Clamp01(from); to = DMath.Clamp(to, from, 1.0); double fromValue = from * (iterations - 1); double toValue = to * (iterations - 1); int clippedIterations = DMath.CeilInt(toValue) - DMath.FloorInt(fromValue) + 1; if (samples == null || samples.Length != clippedIterations) samples = new SplineSample[clippedIterations]; if (originalSamplePercents == null || originalSamplePercents.Length != clippedIterations) { originalSamplePercents = new double[clippedIterations]; } float lengthStep = CalculateLength(from, to) / (iterations - 1); Evaluate(from, ref samples[0]); samples[0].percent = originalSamplePercents[0] = from; double lastPercent = from; float moved = 0f; for (int i = 1; i < samples.Length - 1; i++) { Evaluate(Travel(lastPercent, lengthStep, out moved, Direction.Forward), ref samples[i]); lastPercent = samples[i].percent; originalSamplePercents[i] = lastPercent; samples[i].percent = DMath.Lerp(from, to, (double)i/ (samples.Length - 1)); } Evaluate(to, ref samples[samples.Length - 1]); samples[samples.Length - 1].percent = originalSamplePercents[originalSamplePercents.Length - 1] = to; } /// /// Evaluates the spline segment based on the spline's precision and returns only the position. /// /// The position buffer /// Start position [0-1] /// Target position [from-1] /// public void EvaluatePositions(ref Vector3[] positions, double from = 0.0, double to = 1.0) { if (points.Length == 0) { positions = new Vector3[0]; return; } from = DMath.Clamp01(from); to = DMath.Clamp(to, from, 1.0); double fromValue = from * (iterations - 1); double toValue = to * (iterations - 1); int clippedIterations = DMath.CeilInt(toValue) - DMath.FloorInt(fromValue) + 1; if (positions.Length != clippedIterations) positions = new Vector3[clippedIterations]; double percent = from; double ms = moveStep; int index = 0; while (true) { positions[index] = EvaluatePosition(percent); index++; if (index >= positions.Length) break; percent = DMath.Move(percent, to, ms); } } /// /// Returns the percent from the spline at a given distance from the start point /// /// The start point /// /// The distance to travel /// The direction towards which to move /// public double Travel(double start, float distance, out float moved, Direction direction) { moved = 0f; if (points.Length <= 1) return 0.0; if (direction == Direction.Forward && start >= 1.0) return 1.0; else if (direction == Direction.Backward && start <= 0.0) return 0.0; ; if (distance == 0f) return DMath.Clamp01(start); Vector3 pos = Vector3.zero; EvaluatePosition(start, ref pos); Vector3 lastPosition = pos; double lastPercent = start; int i = iterations - 1; int nextSampleIndex = direction == Spline.Direction.Forward ? DMath.CeilInt(start * i) : DMath.FloorInt(start * i); float lastDistance = 0f; double percent = start; while (true) { percent = (double)nextSampleIndex / i; pos = EvaluatePosition(percent); lastDistance = Vector3.Distance(pos, lastPosition); lastPosition = pos; moved += lastDistance; if (moved >= distance) break; lastPercent = percent; if (direction == Spline.Direction.Forward) { if (nextSampleIndex == i) break; nextSampleIndex++; } else { if (nextSampleIndex == 0) break; nextSampleIndex--; } } return DMath.Lerp(lastPercent, percent, 1f - (moved - distance) / lastDistance); } public double Travel(double start, float distance, Spline.Direction direction = Spline.Direction.Forward) { float moved; return Travel(start, distance, out moved, direction); } public void EvaluatePosition(double percent, ref Vector3 position) { if (points.Length == 0) { position = Vector3.zero; return; } if (points.Length == 1) { position = points[0].position; return; } percent = DMath.Clamp01(percent); double doubleIndex = (points.Length - 1) * percent; if (closed) { doubleIndex = points.Length * percent; } int pointIndex = DMath.FloorInt(doubleIndex); if (type == Type.Bezier) { pointIndex = Mathf.Clamp(pointIndex, 0, Mathf.Max(points.Length - 1, 0)); } CalculatePosition(ref position, doubleIndex - pointIndex, pointIndex); } [System.Obsolete("This override is obsolete. Use EvaluatePosition(double percent, ref Vector3 position) instead")] public void EvaluatePosition(ref Vector3 position, double percent) { EvaluatePosition(percent, ref position); } public void EvaluateTangent(double percent, ref Vector3 tangent) { if (points.Length < 2) { tangent = Vector3.forward; return; } percent = DMath.Clamp01(percent); double doubleIndex = (points.Length - 1) * percent; if (closed) { doubleIndex = points.Length * percent; } int pointIndex = DMath.FloorInt(doubleIndex); if (type == Type.Bezier) { pointIndex = Mathf.Clamp(pointIndex, 0, Mathf.Max(points.Length - 1, 0)); } CalculateTangent(ref tangent, doubleIndex - pointIndex, pointIndex); } public void EvaluatePositionAndTangent(ref Vector3 position, ref Vector3 tangent, double percent) { if (points.Length == 0) { position = Vector3.zero; tangent = Vector3.forward; return; } if (points.Length == 1) { position = points[0].position; tangent = Vector3.forward; return; } percent = DMath.Clamp01(percent); double doubleIndex = (points.Length - 1) * percent; if (closed) { doubleIndex = points.Length * percent; } int pointIndex = DMath.FloorInt(doubleIndex); if (type == Type.Bezier) { pointIndex = Mathf.Clamp(pointIndex, 0, Mathf.Max(points.Length - 1, 0)); } CalculatePositionAndTangent(doubleIndex - pointIndex, pointIndex, ref position, ref tangent); } //Get closest point in spline segment. Used for projection private double GetClosestPoint(int iterations, Vector3 point, double start, double end, int slices) { if (iterations <= 0) { float startDist = (point - EvaluatePosition(start)).sqrMagnitude; float endDist = (point - EvaluatePosition(end)).sqrMagnitude; if (startDist < endDist) return start; else if (endDist < startDist) return end; else return (start + end) / 2; } double closestPercent = 0.0; float closestDistance = Mathf.Infinity; double tick = (end - start) / slices; double t = start; Vector3 pos = Vector3.zero; while (true) { EvaluatePosition(t, ref pos); float dist = (point - pos).sqrMagnitude; if (dist < closestDistance) { closestDistance = dist; closestPercent = t; } if (t == end) break; t = DMath.Move(t, end, tick); } double newStart = closestPercent - tick; if (newStart < start) newStart = start; double newEnd = closestPercent + tick; if (newEnd > end) newEnd = end; return GetClosestPoint(--iterations, point, newStart, newEnd, slices); } /// /// Break the closed spline /// public void Break() { Break(0); } /// /// Break the closed spline at given point /// /// public void Break(int at) { if (!closed) return; if (at >= points.Length) return; if (at < 0) return; SplinePoint[] previousPoints = new SplinePoint[points.Length]; points.CopyTo(previousPoints, 0); for (int i = at; i < previousPoints.Length; i++) { points[i - at] = previousPoints[i]; } for (int i = 0; i < at; i++) { points[(points.Length - at) + i] = previousPoints[i]; } closed = false; } /// /// Close the spline. This will cause the first and last points of the spline to merge /// public void Close() { if (points.Length < 3) { Debug.LogError("Points need to be at least 3 to close the spline"); return; } closed = true; } /// /// Convert the spline to a Bezier path /// public void CatToBezierTangents() { switch (type) { case Type.Linear: for (int i = 0; i < points.Length; i++) { points[i].type = SplinePoint.Type.Broken; points[i].SetTangentPosition(points[i].position); points[i].SetTangent2Position(points[i].position); } break; case Type.CatmullRom: for (int i = 0; i < points.Length; i++) { points[i].type = SplinePoint.Type.SmoothMirrored; double percent = GetPointPercent(i); Vector3 tangent = Vector3.forward; EvaluateTangent(percent, ref tangent); if(_knotParametrization > 0f) { ComputeCatPoints(i); points[i].SetTangent2Position(points[i].position + tangent.normalized * Vector3.Distance(P[0], P[2]) / 6f); } else { points[i].SetTangent2Position(points[i].position + tangent / 3f); } } break; case Type.BSpline: //No BSPline support yet break; } type = Type.Bezier; } /// /// Evaluates the position of the spline using one of the algorithms /// private void CalculatePosition(ref Vector3 position, double percent, int pointIndex) { switch (type) { case Type.CatmullRom: ComputeCatPoints(pointIndex); if (_knotParametrization < 0.000001f) { CalculateCatmullRomPositionFast(ref position, percent, pointIndex); } else { CalculateCatmullRomComponents(percent); CalculateCatmullRomPosition(percent, ref position); } break; case Type.Bezier: CalculateBezierPosition(ref position, percent, pointIndex); break; case Type.BSpline: ComputeCatPoints(pointIndex); CalculateBSplinePosition(ref position, percent, pointIndex); break; case Type.Linear: ComputeCatPoints(pointIndex); CalculateLinearPosition(ref position, percent, pointIndex); break; } } /// /// Evaluates the direction of the spline using one of the algorithms /// private void CalculateTangent(ref Vector3 tangent, double percent, int pointIndex) { switch (type) { case Type.CatmullRom: ComputeCatPoints(pointIndex); if (_knotParametrization < 0.000001f) { CalculateCatmullRomTangentFast(ref tangent, percent, pointIndex); } else { CalculateCatmullRomComponents(percent); CalculateCatmullRomTangent(percent, ref tangent); } break; case Type.Bezier: CalculateBezierTangent(ref tangent, percent, pointIndex); break; case Type.Linear: ComputeCatPoints(pointIndex); CalculateLinearTangent(ref tangent, percent, pointIndex); break; } } /// /// Slightly faster than calling GetPoint and GetTangent separately /// private void CalculatePositionAndTangent(double percent, int pointIndex, ref Vector3 position, ref Vector3 tangent) { switch (type) { case Type.CatmullRom: ComputeCatPoints(pointIndex); if (_knotParametrization < 0.000001f) { CalculateCatmullRomPositionFast(ref position, percent, pointIndex); CalculateCatmullRomTangentFast(ref tangent, percent, pointIndex); } else { CalculateCatmullRomComponents(percent); CalculateCatmullRomPosition(percent, ref position); CalculateCatmullRomTangent(percent, ref tangent); } break; case Type.Bezier: CalculateBezierPosition(ref position, percent, pointIndex); CalculateBezierTangent(ref tangent, percent, pointIndex); break; case Type.BSpline: ComputeCatPoints(pointIndex); CalculateBSplinePosition(ref position, percent, pointIndex); break; case Type.Linear: ComputeCatPoints(pointIndex); CalculateLinearPosition(ref position, percent, pointIndex); CalculateLinearTangent(ref tangent, percent, pointIndex); break; } } private void CalculateLinearPosition(ref Vector3 position, double t, int i) { if (points.Length == 0) { position = Vector3.zero; return; } position = Vector3.Lerp(P[1], P[2], (float)t); } private void CalculateLinearTangent(ref Vector3 tangent, double t, int i) { if (points.Length == 0) { tangent = Vector3.forward; return; } if (linearAverageDirection) tangent = Vector3.Slerp(P[1] - P[0], P[2] - P[1], 0.5f); else tangent = P[2] - P[1]; } private void CalculateBSplinePosition(ref Vector3 position, double time, int i) { if (points.Length > 0) position = points[0].position; if (points.Length > 1) { float tf = (float)DMath.Clamp01(time); position = ((-P[0] + P[2]) / 2f + tf * ((P[0] - 2f * P[1] + P[2]) / 2f + tf * (-P[0] + 3f * P[1] - 3f * P[2] + P[3]) / 6f)) * tf + (P[0] + 4f * P[1] + P[2]) / 6f; } } private void CalculateBezierPosition(ref Vector3 position, double t, int i) { if (points.Length > 0) position = points[0].position; else return; if (!closed && points.Length == 1) return; t = DMath.Clamp01(t); int it = i + 1; if (it >= points.Length) { it = 0; } float ft = (float)t; float nt = 1f - ft; position = nt * nt * nt * points[i].position + 3f * nt * nt * ft * points[i].tangent2 + 3f * nt * ft * ft * points[it].tangent + ft * ft * ft * points[it].position; } private void CalculateBezierTangent(ref Vector3 tangent, double t, int i) { if (points.Length > 0) tangent = points[0].tangent; else return; if (!closed && points.Length == 1) return; t = DMath.Clamp01(t); int it = i + 1; if (it >= points.Length) { it = 0; } float ft = (float)t; float nt = 1f - ft; tangent = -3f * nt * nt * points[i].position + 3f * nt * nt * points[i].tangent2 - 6f * ft * nt * points[i].tangent2 - 3f * ft * ft * points[it].tangent + 6f * ft * nt * points[it].tangent + 3f * ft * ft * points[it].position; } private void CalculateCatmullRomComponents(double t) { const float t0 = 0f; t1 = GetInterval(P[0], P[1]); t2 = GetInterval(P[1], P[2]) + t1; t3 = GetInterval(P[2], P[3]) + t2; float tf = Mathf.LerpUnclamped(t1, t2, (float)t); A1 = (t1 - tf) / (t1 - t0) * P[0] + (tf - t0) / (t1 - t0) * P[1]; A2 = (t2 - tf) / (t2 - t1) * P[1] + (tf - t1) / (t2 - t1) * P[2]; A3 = (t3 - tf) / (t3 - t2) * P[2] + (tf - t2) / (t3 - t2) * P[3]; B1 = (t2 - tf) / (t2 - t0) * A1 + (tf - t0) / (t2 - t0) * A2; B2 = (t3 - tf) / (t3 - t1) * A2 + (tf - t1) / (t3 - t1) * A3; float GetInterval(Vector3 a, Vector3 b) { return Mathf.Pow((a - b).sqrMagnitude, _knotParametrization * 0.5f); } } private void CalculateCatmullRomPosition(double t, ref Vector3 position) { float tf = Mathf.LerpUnclamped(t1, t2, (float)t); position = (t2 - tf) / (t2 - t1) * B1 + (tf - t1) / (t2 - t1) * B2; } private void CalculateCatmullRomTangent(double t, ref Vector3 tangent) { float tf = Mathf.LerpUnclamped(t1, t2, (float)t); Vector3 A1p = (P[1] - P[0]) / t1; Vector3 A2p = (P[2] - P[1]) / (t2 - t1); Vector3 A3p = (P[3] - P[2]) / (t3 - t2); Vector3 B1p = (A2 - A1) / t2 + (t2 - tf) / t2 * A1p + tf / t2 * A2p; Vector3 B2p = (A3 - A2) / (t3 - t1) + (t3 - tf) / (t3 - t1) * A2p + (tf - t1) / (t3 - t1) * A3p; tangent = (B2 - B1) / (t2 - t1) + (t2 - tf) / (t2 - t1) * B1p + (tf - t1) / (t2 - t1) * B2p; } private void CalculateCatmullRomPositionFast(ref Vector3 position, double t, int i) { float t1 = (float)t; float t2 = t1 * t1; float t3 = t2 * t1; if (points.Length > 0) { position = points[0].position; } if (!closed && i >= points.Length) return; if (points.Length > 1) { position = 0.5f * ((2f * P[1]) + (-P[0] + P[2]) * t1 + (2f * P[0] - 5f * P[1] + 4f * P[2] - P[3]) * t2 + (-P[0] + 3f * P[1] - 3f * P[2] + P[3]) * t3); } } private void CalculateCatmullRomTangentFast(ref Vector3 tangent, double t, int i) { float t1 = (float)t; float t2 = t1 * t1; if (!closed && i >= points.Length) return; if (points.Length > 1) { tangent = (6 * t2 - 6 * t1) * P[1] + (3 * t2 - 4 * t1 + 1) * (P[2] - P[0]) * 0.5f + (-6 * t2 + 6 * t1) * P[2] + (3 * t2 - 2 * t1) * (P[3] - P[1]) * 0.5f; } } private void ComputeCatPoints(int i) { int p1 = i - 1; int p2 = i; int p3 = i + 1; int p4 = i + 2; if (closed) { if(p1 < 0) { p1 += points.Length; } if (p2 >= points.Length) { p2 -= points.Length; } if (p3 >= points.Length) { p3 -= points.Length; } if(p4 >= points.Length) { p4 -= points.Length; } P[0] = points[p1].position; P[1] = points[p2].position; P[2] = points[p3].position; P[3] = points[p4].position; } else { if(p1 < 0) { P[0] = points[0].position; P[0] += (P[0] - points[1].position); } else { P[0] = points[p1].position; } P[1] = points[p2].position; if (p3 >= points.Length) { P[2] = points[points.Length - 1].position; Vector3 pos = P[2]; P[2] += P[2] - points[points.Length - 2].position; P[3] = P[2] + (P[2] - pos); } else { P[2] = points[p3].position; if(p4 >= points.Length) { P[3] = P[2] + (P[2] - points[p3 - 1].position); } else { P[3] = points[p4].position; } } } } public static void FormatFromTo(ref double from, ref double to, bool preventInvert = true) { from = DMath.Clamp01(from); to = DMath.Clamp01(to); if (preventInvert && from > to) { double tmp = from; from = to; to = tmp; } else to = DMath.Clamp(to, 0.0, 1.0); } } }