using UnityEngine;
using System.Collections.Generic;
using Pathfinding.Util;
namespace Pathfinding {
[AddComponentMenu("Pathfinding/Modifiers/Simple Smooth")]
[System.Serializable]
[RequireComponent(typeof(Seeker))]
///
/// Modifier which smooths the path. This modifier can smooth a path by either moving the points closer together (Simple) or using Bezier curves (Bezier).\n
/// \ingroup modifiers
/// Attach this component to the same GameObject as a Seeker component.
/// \n
/// This component will hook in to the Seeker's path post-processing system and will post process any paths it searches for.
/// Take a look at the Modifier Priorities settings on the Seeker component to determine where in the process this modifier should process the path.
/// \n
/// \n
/// Several smoothing types are available, here follows a list of them and a short description of what they do, and how they work.
/// But the best way is really to experiment with them yourself.\n
///
/// - Simple Smooths the path by drawing all points close to each other. This results in paths that might cut corners if you are not careful.
/// It will also subdivide the path to create more more points to smooth as otherwise it would still be quite rough.
/// [Open online documentation to see images]
/// - Bezier Smooths the path using Bezier curves. This results a smooth path which will always pass through all points in the path, but make sure it doesn't turn too quickly.
/// [Open online documentation to see images]
/// - OffsetSimple An alternative to Simple smooth which will offset the path outwards in each step to minimize the corner-cutting.
/// But be careful, if too high values are used, it will turn into loops and look really ugly.
/// - Curved Non Uniform [Open online documentation to see images]
///
/// Note: Modifies vectorPath array
/// TODO: Make the smooth modifier take the world geometry into account when smoothing
///
[HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_simple_smooth_modifier.php")]
public class SimpleSmoothModifier : MonoModifier {
#if UNITY_EDITOR
[UnityEditor.MenuItem("CONTEXT/Seeker/Add Simple Smooth Modifier")]
public static void AddComp (UnityEditor.MenuCommand command) {
(command.context as Component).gameObject.AddComponent(typeof(SimpleSmoothModifier));
}
#endif
public override int Order { get { return 50; } }
/// Type of smoothing to use
public SmoothType smoothType = SmoothType.Simple;
/// Number of times to subdivide when not using a uniform length
[Tooltip("The number of times to subdivide (divide in half) the path segments. [0...inf] (recommended [1...10])")]
public int subdivisions = 2;
/// Number of times to apply smoothing
[Tooltip("Number of times to apply smoothing")]
public int iterations = 2;
/// Determines how much smoothing to apply in each smooth iteration. 0.5 usually produces the nicest looking curves.
[Tooltip("Determines how much smoothing to apply in each smooth iteration. 0.5 usually produces the nicest looking curves")]
[Range(0, 1)]
public float strength = 0.5F;
///
/// Toggle to divide all lines in equal length segments.
/// See:
///
[Tooltip("Toggle to divide all lines in equal length segments")]
public bool uniformLength = true;
///
/// The length of the segments in the smoothed path when using .
/// A high value yields rough paths and low value yields very smooth paths, but is slower
///
[Tooltip("The length of each segment in the smoothed path. A high value yields rough paths and low value yields very smooth paths, but is slower")]
public float maxSegmentLength = 2F;
/// Length factor of the bezier curves' tangents'
[Tooltip("Length factor of the bezier curves' tangents")]
public float bezierTangentLength = 0.4F;
/// Offset to apply in each smoothing iteration when using Offset Simple. See:
[Tooltip("Offset to apply in each smoothing iteration when using Offset Simple")]
public float offset = 0.2F;
/// Roundness factor used for CurvedNonuniform
[Tooltip("How much to smooth the path. A higher value will give a smoother path, but might take the character far off the optimal path.")]
public float factor = 0.1F;
public enum SmoothType {
Simple,
Bezier,
OffsetSimple,
CurvedNonuniform
}
public override void Apply (Path p) {
// This should never trigger unless some other modifier has messed stuff up
if (p.vectorPath == null) {
Debug.LogWarning("Can't process NULL path (has another modifier logged an error?)");
return;
}
List path = null;
switch (smoothType) {
case SmoothType.Simple:
path = SmoothSimple(p.vectorPath); break;
case SmoothType.Bezier:
path = SmoothBezier(p.vectorPath); break;
case SmoothType.OffsetSimple:
path = SmoothOffsetSimple(p.vectorPath); break;
case SmoothType.CurvedNonuniform:
path = CurvedNonuniform(p.vectorPath); break;
}
if (path != p.vectorPath) {
ListPool.Release(ref p.vectorPath);
p.vectorPath = path;
}
}
public List CurvedNonuniform (List path) {
if (maxSegmentLength <= 0) {
Debug.LogWarning("Max Segment Length is <= 0 which would cause DivByZero-exception or other nasty errors (avoid this)");
return path;
}
int pointCounter = 0;
for (int i = 0; i < path.Count-1; i++) {
//pointCounter += Mathf.FloorToInt ((path[i]-path[i+1]).magnitude / maxSegmentLength)+1;
float dist = (path[i]-path[i+1]).magnitude;
//In order to avoid floating point errors as much as possible, and in lack of a better solution
//loop through it EXACTLY as the other code further down will
for (float t = 0; t <= dist; t += maxSegmentLength) {
pointCounter++;
}
}
List subdivided = ListPool.Claim(pointCounter);
// Set first velocity
Vector3 preEndVel = (path[1]-path[0]).normalized;
for (int i = 0; i < path.Count-1; i++) {
float dist = (path[i]-path[i+1]).magnitude;
Vector3 startVel1 = preEndVel;
Vector3 endVel1 = i < path.Count-2 ? ((path[i+2]-path[i+1]).normalized - (path[i]-path[i+1]).normalized).normalized : (path[i+1]-path[i]).normalized;
Vector3 startVel = startVel1 * dist * factor;
Vector3 endVel = endVel1 * dist * factor;
Vector3 start = path[i];
Vector3 end = path[i+1];
float onedivdist = 1F / dist;
for (float t = 0; t <= dist; t += maxSegmentLength) {
float t2 = t * onedivdist;
subdivided.Add(GetPointOnCubic(start, end, startVel, endVel, t2));
}
preEndVel = endVel1;
}
subdivided[subdivided.Count-1] = path[path.Count-1];
return subdivided;
}
public static Vector3 GetPointOnCubic (Vector3 a, Vector3 b, Vector3 tan1, Vector3 tan2, float t) {
float t2 = t*t, t3 = t2*t;
float h1 = 2*t3 - 3*t2 + 1; // calculate basis function 1
float h2 = -2*t3 + 3*t2; // calculate basis function 2
float h3 = t3 - 2*t2 + t; // calculate basis function 3
float h4 = t3 - t2; // calculate basis function 4
return h1*a + // multiply and sum all funtions
h2*b + // together to build the interpolated
h3*tan1 + // point along the curve.
h4*tan2;
}
public List SmoothOffsetSimple (List path) {
if (path.Count <= 2 || iterations <= 0) {
return path;
}
if (iterations > 12) {
Debug.LogWarning("A very high iteration count was passed, won't let this one through");
return path;
}
int maxLength = (path.Count-2)*(int)Mathf.Pow(2, iterations)+2;
List subdivided = ListPool.Claim(maxLength);
List subdivided2 = ListPool.Claim(maxLength);
for (int i = 0; i < maxLength; i++) { subdivided.Add(Vector3.zero); subdivided2.Add(Vector3.zero); }
for (int i = 0; i < path.Count; i++) {
subdivided[i] = path[i];
}
for (int iteration = 0; iteration < iterations; iteration++) {
int currentPathLength = (path.Count-2)*(int)Mathf.Pow(2, iteration)+2;
//Switch the arrays
List tmp = subdivided;
subdivided = subdivided2;
subdivided2 = tmp;
const float nextMultiplier = 1F;
for (int i = 0; i < currentPathLength-1; i++) {
Vector3 current = subdivided2[i];
Vector3 next = subdivided2[i+1];
Vector3 normal = Vector3.Cross(next-current, Vector3.up);
normal = normal.normalized;
bool firstRight = false;
bool secondRight = false;
bool setFirst = false;
bool setSecond = false;
if (i != 0 && !VectorMath.IsColinearXZ(current, next, subdivided2[i-1])) {
setFirst = true;
firstRight = VectorMath.RightOrColinearXZ(current, next, subdivided2[i-1]);
}
if (i < currentPathLength-1 && !VectorMath.IsColinearXZ(current, next, subdivided2[i+2])) {
setSecond = true;
secondRight = VectorMath.RightOrColinearXZ(current, next, subdivided2[i+2]);
}
if (setFirst) {
subdivided[i*2] = current + (firstRight ? normal*offset*nextMultiplier : -normal*offset*nextMultiplier);
} else {
subdivided[i*2] = current;
}
if (setSecond) {
subdivided[i*2+1] = next + (secondRight ? normal*offset*nextMultiplier : -normal*offset*nextMultiplier);
} else {
subdivided[i*2+1] = next;
}
}
subdivided[(path.Count-2)*(int)Mathf.Pow(2, iteration+1)+2-1] = subdivided2[currentPathLength-1];
}
ListPool.Release(ref subdivided2);
return subdivided;
}
public List SmoothSimple (List path) {
if (path.Count < 2) return path;
List subdivided;
if (uniformLength) {
// Clamp to a small value to avoid the path being divided into a huge number of segments
maxSegmentLength = Mathf.Max(maxSegmentLength, 0.005f);
float pathLength = 0;
for (int i = 0; i < path.Count-1; i++) {
pathLength += Vector3.Distance(path[i], path[i+1]);
}
int estimatedNumberOfSegments = Mathf.FloorToInt(pathLength / maxSegmentLength);
// Get a list with an initial capacity high enough so that we can add all points
subdivided = ListPool.Claim(estimatedNumberOfSegments+2);
float distanceAlong = 0;
// Sample points every [maxSegmentLength] world units along the path
for (int i = 0; i < path.Count-1; i++) {
var start = path[i];
var end = path[i+1];
float length = Vector3.Distance(start, end);
while (distanceAlong < length) {
subdivided.Add(Vector3.Lerp(start, end, distanceAlong / length));
distanceAlong += maxSegmentLength;
}
distanceAlong -= length;
}
// Make sure we get the exact position of the last point
subdivided.Add(path[path.Count-1]);
} else {
subdivisions = Mathf.Max(subdivisions, 0);
if (subdivisions > 10) {
Debug.LogWarning("Very large number of subdivisions. Cowardly refusing to subdivide every segment into more than " + (1 << subdivisions) + " subsegments");
subdivisions = 10;
}
int steps = 1 << subdivisions;
subdivided = ListPool.Claim((path.Count-1)*steps + 1);
Polygon.Subdivide(path, subdivided, steps);
}
if (strength > 0) {
for (int it = 0; it < iterations; it++) {
Vector3 prev = subdivided[0];
for (int i = 1; i < subdivided.Count-1; i++) {
Vector3 tmp = subdivided[i];
// prev is at this point set to the value that subdivided[i-1] had before this loop started
// Move the point closer to the average of the adjacent points
subdivided[i] = Vector3.Lerp(tmp, (prev+subdivided[i+1])/2F, strength);
prev = tmp;
}
}
}
return subdivided;
}
public List SmoothBezier (List path) {
if (subdivisions < 0) subdivisions = 0;
int subMult = 1 << subdivisions;
List subdivided = ListPool.Claim();
for (int i = 0; i < path.Count-1; i++) {
Vector3 tangent1;
Vector3 tangent2;
if (i == 0) {
tangent1 = path[i+1]-path[i];
} else {
tangent1 = path[i+1]-path[i-1];
}
if (i == path.Count-2) {
tangent2 = path[i]-path[i+1];
} else {
tangent2 = path[i]-path[i+2];
}
tangent1 *= bezierTangentLength;
tangent2 *= bezierTangentLength;
Vector3 v1 = path[i];
Vector3 v2 = v1+tangent1;
Vector3 v4 = path[i+1];
Vector3 v3 = v4+tangent2;
for (int j = 0; j < subMult; j++) {
subdivided.Add(AstarSplines.CubicBezier(v1, v2, v3, v4, (float)j/subMult));
}
}
// Assign the last point
subdivided.Add(path[path.Count-1]);
return subdivided;
}
}
}