266 lines
8.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Collections;
public class BezierWay : MonoBehaviour
{
[Header("Настройки кривой")]
[SerializeField] private bool _cyclic = false;
[SerializeField] private float _gizmoSize = 0.2f;
[SerializeField] private int _gizmoSegments = 50;
[SerializeField] private Color _gizmoColor = Color.green;
[SerializeField] private Color _pointColor = Color.white;
public List<Transform> _points = new List<Transform>();
private List<Vector3> _cachedWorldPositions = new List<Vector3>();
// Свойства
public bool IsCyclic => _cyclic;
public int PointsCount => _points.Count;
public Transform FirstPoint => _points[0];
public bool Busy/* { get; private set; }*/;
private Coroutine businessCoroutine;
void Start()
{
CachePoints();
Busy = false;
}
public void SetBusiness(bool value)
{
Busy = value;
}
public void SetBusiness(bool value, float delay)
{
if(businessCoroutine != null)
StopCoroutine(businessCoroutine);
businessCoroutine = StartCoroutine(DelayedBusiness(value, delay));
}
private IEnumerator DelayedBusiness(bool value, float delay)
{
yield return new WaitForSeconds(delay);
Busy = value;
businessCoroutine = null;
}
/// <summary>
/// Кэширует все дочерние объекты как точки кривой
/// </summary>
//[ContextMenu("Cache Points")]
public void CachePoints()
{
_points.Clear();
// Собираем все дочерние объекты
foreach (Transform child in transform)
{
_points.Add(child);
}
if (_points.Count < 2)
{
Debug.LogWarning($"BezierWay: Недостаточно точек для построения кривой (минимум 2). Найдено: {_points.Count}");
return;
}
// Кэшируем мировые позиции
_cachedWorldPositions.Clear();
foreach (var point in _points)
{
if (point != null)
_cachedWorldPositions.Add(point.position);
else
_cachedWorldPositions.Add(Vector3.zero);
}
}
/// <summary>
/// Получает позицию на кривой Безье по параметру t (0-1)
/// </summary>
/// <param name="t">Параметр от 0 до 1</param>
/// <returns>Позиция на кривой</returns>
public Vector3 GetPoint(float t)
{
if (_points.Count < 2)
{
Debug.LogError("Недостаточно точек для интерполяции");
return Vector3.zero;
}
t = Mathf.Clamp01(t);
if (_points.Count == 2)
{
// Простая линейная интерполяция для двух точек
return Vector3.Lerp(_cachedWorldPositions[0], _cachedWorldPositions[1], t);
}
// Для кривых с 3+ точками используем Catmull-Rom сплайны (аппроксимация Безье)
return GetCatmullRomPosition(t);
}
/// <summary>
/// Получает позицию на кривой с использованием Catmull-Rom сплайнов
/// </summary>
private Vector3 GetCatmullRomPosition(float t)
{
int pointCount = _cachedWorldPositions.Count;
// Вычисляем сегмент и локальный t внутри сегмента
float segmentT;
int p0, p1, p2, p3;
if (_cyclic)
{
// Циклическая кривая
float segmentFloat = t * pointCount;
int segmentIndex = Mathf.FloorToInt(segmentFloat);
segmentT = segmentFloat - segmentIndex;
// Индексы для циклической кривой
p1 = segmentIndex % pointCount;
p2 = (p1 + 1) % pointCount;
p0 = (p1 - 1 + pointCount) % pointCount;
p3 = (p2 + 1) % pointCount;
}
else
{
// Нециклическая кривая
int segments = pointCount - 1;
float segmentFloat = t * segments;
int segmentIndex = Mathf.Clamp(Mathf.FloorToInt(segmentFloat), 0, segments - 1);
segmentT = segmentFloat - segmentIndex;
p1 = segmentIndex;
p2 = segmentIndex + 1;
p0 = Mathf.Max(0, p1 - 1);
p3 = Mathf.Min(pointCount - 1, p2 + 1);
}
// Получаем позиции точек
Vector3 point0 = _cachedWorldPositions[p0];
Vector3 point1 = _cachedWorldPositions[p1];
Vector3 point2 = _cachedWorldPositions[p2];
Vector3 point3 = _cachedWorldPositions[p3];
// Catmull-Rom интерполяция
float t2 = segmentT * segmentT;
float t3 = t2 * segmentT;
// Коэффициенты с натяжением
Vector3 result = 0.5f * (
2.0f * point1 +
(-point0 + point2) * (segmentT) +
(2.0f * point0 - 5.0f * point1 + 4.0f * point2 - point3) * (t2) +
(-point0 + 3.0f * point1 - 3.0f * point2 + point3) * (t3)
);
return result;
}
/// <summary>
/// Получает касательную на кривой в точке t
/// </summary>
public Vector3 GetTangent(float t)
{
float delta = 0.01f;
Vector3 p1 = GetPoint(Mathf.Clamp01(t - delta));
Vector3 p2 = GetPoint(Mathf.Clamp01(t + delta));
return (p2 - p1).normalized;
}
void OnDrawGizmos()
{
// Обновляем кэш позиций перед использованием в редакторе
UpdateCacheForEditor();
if (_points.Count < 2)
{
return;
}
// Рисуем контрольные точки
Gizmos.color = _pointColor;
foreach (var point in _points)
{
if (point != null)
{
Gizmos.DrawSphere(point.position, _gizmoSize);
}
}
// Рисуем кривую
Gizmos.color = _gizmoColor;
Vector3 prevPoint = GetPoint(0);
for (int i = 1; i <= _gizmoSegments; i++)
{
float t = i / (float)_gizmoSegments;
Vector3 currentPoint = GetPoint(t);
Gizmos.DrawLine(prevPoint, currentPoint);
prevPoint = currentPoint;
}
// Если циклическая, рисуем замыкающую линию
if (_cyclic && _points.Count > 2)
{
Gizmos.color = Color.cyan;
Vector3 endPoint = GetPoint(1);
Vector3 startPoint = GetPoint(0);
Gizmos.DrawLine(endPoint, startPoint);
}
// Рисуем направление кривой (стрелки)
DrawDirectionArrows();
}
/// <summary>
/// Обновляет кэш для использования в редакторе
/// </summary>
private void UpdateCacheForEditor()
{
// Очищаем и обновляем список точек из дочерних объектов
_points.Clear();
foreach (Transform child in transform)
{
_points.Add(child);
}
// Обновляем кэш позиций
_cachedWorldPositions.Clear();
foreach (var point in _points)
{
if (point != null)
_cachedWorldPositions.Add(point.position);
else
_cachedWorldPositions.Add(Vector3.zero);
}
}
private void DrawDirectionArrows()
{
Gizmos.color = Color.red;
int arrowsCount = Mathf.Min(10, _gizmoSegments);
for (int i = 0; i <= arrowsCount; i++)
{
float t = i / (float)arrowsCount;
Vector3 point = GetPoint(t);
Vector3 tangent = GetTangent(t);
if (tangent != Vector3.zero)
{
Vector3 arrowTip = point + tangent * _gizmoSize * 1.5f;
Vector3 perpendicular = Vector3.Cross(tangent, Vector3.up).normalized;
Gizmos.DrawLine(point, arrowTip);
Gizmos.DrawLine(arrowTip, arrowTip - tangent * _gizmoSize * 0.3f + perpendicular * _gizmoSize * 0.3f);
Gizmos.DrawLine(arrowTip, arrowTip - tangent * _gizmoSize * 0.3f - perpendicular * _gizmoSize * 0.3f);
}
}
}
}