using UnityEngine;
using System.Collections.Generic;
namespace Pathfinding.Util {
/// Interpolates along a sequence of points
public class PathInterpolator {
List path;
float distanceToSegmentStart;
float currentDistance;
float currentSegmentLength = float.PositiveInfinity;
float totalDistance = float.PositiveInfinity;
/// Current position
public virtual Vector3 position {
get {
float t = currentSegmentLength > 0.0001f ? (currentDistance - distanceToSegmentStart) / currentSegmentLength : 0f;
return Vector3.Lerp(path[segmentIndex], path[segmentIndex+1], t);
}
}
/// Last point in the path
public Vector3 endPoint {
get {
return path[path.Count-1];
}
}
/// Tangent of the curve at the current position
public Vector3 tangent {
get {
return path[segmentIndex+1] - path[segmentIndex];
}
}
/// Remaining distance until the end of the path
public float remainingDistance {
get {
return totalDistance - distance;
}
set {
distance = totalDistance - value;
}
}
/// Traversed distance from the start of the path
public float distance {
get {
return currentDistance;
}
set {
currentDistance = value;
while (currentDistance < distanceToSegmentStart && segmentIndex > 0) PrevSegment();
while (currentDistance > distanceToSegmentStart + currentSegmentLength && segmentIndex < path.Count - 2) NextSegment();
}
}
///
/// Current segment.
/// The start and end points of the segment are path[value] and path[value+1].
///
public int segmentIndex { get; private set; }
///
/// True if this instance has a path set.
/// See: SetPath
///
public bool valid {
get {
return path != null;
}
}
/// Appends the remaining path between and to buffer
public void GetRemainingPath (List buffer) {
if (!valid) throw new System.Exception("PathInterpolator is not valid");
buffer.Add(position);
for (int i = segmentIndex+1; i < path.Count; i++) {
buffer.Add(path[i]);
}
}
///
/// Set the path to interpolate along.
/// This will reset all interpolation variables.
///
public void SetPath (List path) {
this.path = path;
currentDistance = 0;
segmentIndex = 0;
distanceToSegmentStart = 0;
if (path == null) {
totalDistance = float.PositiveInfinity;
currentSegmentLength = float.PositiveInfinity;
return;
}
if (path.Count < 2) throw new System.ArgumentException("Path must have a length of at least 2");
currentSegmentLength = (path[1] - path[0]).magnitude;
totalDistance = 0f;
var prev = path[0];
for (int i = 1; i < path.Count; i++) {
var current = path[i];
totalDistance += (current - prev).magnitude;
prev = current;
}
}
/// Move to the specified segment and move a fraction of the way to the next segment
public void MoveToSegment (int index, float fractionAlongSegment) {
if (path == null) return;
if (index < 0 || index >= path.Count - 1) throw new System.ArgumentOutOfRangeException("index");
while (segmentIndex > index) PrevSegment();
while (segmentIndex < index) NextSegment();
distance = distanceToSegmentStart + Mathf.Clamp01(fractionAlongSegment) * currentSegmentLength;
}
/// Move as close as possible to the specified point
public void MoveToClosestPoint (Vector3 point) {
if (path == null) return;
float bestDist = float.PositiveInfinity;
float bestFactor = 0f;
int bestIndex = 0;
for (int i = 0; i < path.Count-1; i++) {
float factor = VectorMath.ClosestPointOnLineFactor(path[i], path[i+1], point);
Vector3 closest = Vector3.Lerp(path[i], path[i+1], factor);
float dist = (point - closest).sqrMagnitude;
if (dist < bestDist) {
bestDist = dist;
bestFactor = factor;
bestIndex = i;
}
}
MoveToSegment(bestIndex, bestFactor);
}
public void MoveToLocallyClosestPoint (Vector3 point, bool allowForwards = true, bool allowBackwards = true) {
if (path == null) return;
while (allowForwards && segmentIndex < path.Count - 2 && (path[segmentIndex+1] - point).sqrMagnitude <= (path[segmentIndex] - point).sqrMagnitude) {
NextSegment();
}
while (allowBackwards && segmentIndex > 0 && (path[segmentIndex-1] - point).sqrMagnitude <= (path[segmentIndex] - point).sqrMagnitude) {
PrevSegment();
}
// Check the distances to the two segments extending from the vertex path[segmentIndex]
// and pick the position on those segments that is closest to the #point parameter.
float factor1 = 0, factor2 = 0, d1 = float.PositiveInfinity, d2 = float.PositiveInfinity;
if (segmentIndex > 0) {
factor1 = VectorMath.ClosestPointOnLineFactor(path[segmentIndex-1], path[segmentIndex], point);
d1 = (Vector3.Lerp(path[segmentIndex-1], path[segmentIndex], factor1) - point).sqrMagnitude;
}
if (segmentIndex < path.Count - 1) {
factor2 = VectorMath.ClosestPointOnLineFactor(path[segmentIndex], path[segmentIndex+1], point);
d2 = (Vector3.Lerp(path[segmentIndex], path[segmentIndex+1], factor2) - point).sqrMagnitude;
}
if (d1 < d2) MoveToSegment(segmentIndex - 1, factor1);
else MoveToSegment(segmentIndex, factor2);
}
public void MoveToCircleIntersection2D (Vector3 circleCenter3D, float radius, IMovementPlane transform) {
if (path == null) return;
// Move forwards as long as we are getting closer to circleCenter3D
while (segmentIndex < path.Count - 2 && VectorMath.ClosestPointOnLineFactor(path[segmentIndex], path[segmentIndex+1], circleCenter3D) > 1) {
NextSegment();
}
var circleCenter = transform.ToPlane(circleCenter3D);
// Move forwards as long as the current segment endpoint is within the circle
while (segmentIndex < path.Count - 2 && (transform.ToPlane(path[segmentIndex+1]) - circleCenter).sqrMagnitude <= radius*radius) {
NextSegment();
}
// Calculate the intersection with the circle. This involves some math.
var factor = VectorMath.LineCircleIntersectionFactor(circleCenter, transform.ToPlane(path[segmentIndex]), transform.ToPlane(path[segmentIndex+1]), radius);
// Move to the intersection point
MoveToSegment(segmentIndex, factor);
}
protected virtual void PrevSegment () {
segmentIndex--;
currentSegmentLength = (path[segmentIndex+1] - path[segmentIndex]).magnitude;
distanceToSegmentStart -= currentSegmentLength;
}
protected virtual void NextSegment () {
segmentIndex++;
distanceToSegmentStart += currentSegmentLength;
currentSegmentLength = (path[segmentIndex+1] - path[segmentIndex]).magnitude;
}
}
}