Files
BABA_YAGA/Assets/Opsive/UltimateCharacterController/Scripts/Motion/Path.cs
2026-06-14 23:57:44 +07:00

196 lines
9.5 KiB
C#

/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Motion
{
using UnityEngine;
/// <summary>
/// Allows for a user-defined path that objects can follow.
/// </summary>
public class Path : MonoBehaviour
{
[Tooltip("The points which represent the curve.")]
[SerializeField] protected Vector3[] m_ControlPoints;
public Vector3[] ControlPoints { get { return m_ControlPoints; } set { m_ControlPoints = value; } }
private CubicBezierCurve[] m_Curve;
/// <summary>
/// Initialize the default values.
/// </summary>
private void Awake()
{
if (m_ControlPoints == null || m_ControlPoints.Length <= 1) {
return;
}
m_Curve = new CubicBezierCurve[(m_ControlPoints.Length / 3)];
for (int i = 0; i < m_Curve.Length; ++i) {
var startIndex = i * 3;
m_Curve[i] = new CubicBezierCurve(transform.TransformPoint(m_ControlPoints[startIndex]), transform.TransformPoint(m_ControlPoints[startIndex + 1]),
transform.TransformPoint(m_ControlPoints[startIndex + 2]), transform.TransformPoint(m_ControlPoints[startIndex + 3]));
}
}
/// <summary>
/// Returns the tangent of the curve near the specified position.
/// </summary>
/// <param name="position">The position to retrieve the tangent of.</param>
/// <param name="index">The index of the last curve segnement.</param>
/// <returns>The tangent of the curve near the specified position.</returns>
public Vector3 GetTangent(Vector3 position, ref int index)
{
var time = m_Curve[index].GetTime(position);
if (time == 1 && index < m_Curve.Length - 1) {
// If the time is equal to 1 then the position is at an endpoint. Determine if the current curve is closer to the given position or if the next curve is closer.
var distance = (m_Curve[index].GetClosestPoint(position) - position).sqrMagnitude;
var nextDistance = (m_Curve[index + 1].GetClosestPoint(position) - position).sqrMagnitude;
if (nextDistance < distance) {
// The next curve is closer - increase the index and retrieve a new time.
index++;
time = m_Curve[index].GetTime(position);
}
} else if (time == 0 && index > 0) {
// If the time is equal to 0 then the position is at an endpoint. Determine if the current curve is closer to the given position or if the previous curve is closer.
var distance = (m_Curve[index].GetClosestPoint(position) - position).sqrMagnitude;
var prevDistance = (m_Curve[index - 1].GetClosestPoint(position) - position).sqrMagnitude;
if (prevDistance < distance) {
// The previous curve is closer - decrease the index and retrieve a new time.
index--;
time = m_Curve[index].GetTime(position);
}
}
return m_Curve[index].GetTangent(time);
}
/// <summary>
/// Returns the tangent of the curve near the specified position.
/// </summary>
/// <param name="position">The position to retrieve the tangent of.</param>
/// <param name="index">The index of the last curve segnement.</param>
/// <returns>The tangent of the curve near the specified position.</returns>
public Vector3 GetClosestPoint(Vector3 position, ref int index)
{
var time = m_Curve[index].GetTime(position);
if (time == 1 && index < m_Curve.Length - 1) {
// If the time is equal to 1 then the position is at an endpoint. Determine if the current curve is closer to the given position or if the next curve is closer.
var distance = (m_Curve[index].GetClosestPoint(position) - position).sqrMagnitude;
var nextDistance = (m_Curve[index + 1].GetClosestPoint(position) - position).sqrMagnitude;
if (nextDistance < distance) {
// The next curve is closer - increase the index and retrieve a new time.
index++;
}
} else if (time == 0 && index > 0) {
// If the time is equal to 0 then the position is at an endpoint. Determine if the current curve is closer to the given position or if the previous curve is closer.
var distance = (m_Curve[index].GetClosestPoint(position) - position).sqrMagnitude;
var prevDistance = (m_Curve[index - 1].GetClosestPoint(position) - position).sqrMagnitude;
if (prevDistance < distance) {
// The previous curve is closer - decrease the index and retrieve a new time.
index--;
}
}
return m_Curve[index].GetClosestPoint(position);
}
/// <summary>
/// Represents one segment of a cubic bezier curve.
/// </summary>
public class CubicBezierCurve
{
private const int c_StepCount = 300;
private Vector3 m_P0;
private Vector3 m_P1;
private Vector3 m_P2;
private Vector3 m_P3;
/// <summary>
/// Four parameter constructor.
/// </summary>
/// <param name="p0">The first point that makes up the curve.</param>
/// <param name="p1">The second point that makes up the curve.</param>
/// <param name="p2">The third point that makes up the curve.</param>
/// <param name="p3">The fourth point that makes up the curve.</param>
public CubicBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
m_P0 = p0;
m_P1 = p1;
m_P2 = p2;
m_P3 = p3;
}
/// <summary>
/// Returns the point of the bezier curve at the normalized position of the curve.
/// </summary>
/// <param name="time">The normalized position within the curve.</param>
/// <returns>The point of the bezier curve at the normalized position of the curve.</returns>
public Vector3 GetPoint(float time)
{
return (((-m_P0 + 3 * (m_P1 - m_P2) + m_P3) * time + (3 * (m_P0 + m_P2) - 6 * m_P1)) * time + 3 * (m_P1 - m_P0)) * time + m_P0;
}
/// <summary>
/// Returns the tangent. This tangent is the first derivative of the curve.
/// </summary>
/// <param name="time">The normalized position within the curve.</param>
/// <returns>The tangent of the curve.</returns>
public Vector3 GetTangent(float time)
{
return (3 * (1 - time) * (1 - time) * (m_P1 - m_P0) + 6 * (1 - time) * time * (m_P2 - m_P1) + 3 * time * time * (m_P3 - m_P2)).normalized;
}
/// <summary>
/// Returns the closest time at the specified position.
/// </summary>
/// <param name="position">The position to retrieve the time of.</param>
/// <param name="endCap">Should the end cap be included?</param>
/// <returns>The closest time at the specified position.</returns>
public float GetTime(Vector3 position)
{
return GetTime(position, 0, 1);
}
/// <summary>
/// Returns the closest time at the specified position.
/// </summary>
/// <param name="position">The position to retrieve the time of.</param>
/// <param name="minTime">The minimum time to search within the curve.</param>
/// <param name="maxTime">The maximum time to search within the curve.</param>
/// <returns>The closest time at the specified position.</returns>
public float GetTime(Vector3 position, float minTime, float maxTime)
{
float closestTime = 0f;
float closestDistance = float.MaxValue;
float step = (maxTime - minTime) / c_StepCount;
var steps = c_StepCount + 1;
// Walk the curve looking for the closest point to the specified position. Store the closest point and return the corresponding time to that point.
for (int i = 0; i < steps; ++i) {
var t = minTime + step * i;
var distance = (GetPoint(t) - position).sqrMagnitude;
if (distance < closestDistance) {
closestDistance = distance;
closestTime = t;
}
}
return closestTime;
}
/// <summary>
/// Returns the closest point on the curve to the specified position.
/// </summary>
/// <param name="position">The position to retrieve the closest point on the curve of.</param>
/// <param name="endCap">Should the curve's end cap be included?</param>
/// <returns>The closest point on the curve to the specified position.</returns>
public Vector3 GetClosestPoint(Vector3 position)
{
return GetPoint(GetTime(position));
}
}
}
}