Загрузка данных


using System;
using UnityEngine;

public enum PathMoveAxis
{
    Horizontal, // A/D
    Vertical    // W/S
}

[DisallowMultipleComponent]
public class Path : MonoBehaviour
{
    [Header("Path Type")]
    public bool isDoorPath = false;

    [Header("Movement")]
    [SerializeField] private PathMoveAxis moveAxis = PathMoveAxis.Horizontal;
    public PathMoveAxis MoveAxis => moveAxis;

    [Header("Main Path -> Main Path Switch (optional)")]
    [Tooltip("Trigger collider (empty GO with Collider set to IsTrigger) that enables switching between main paths.")]
    [SerializeField] private Collider mainSwitchTrigger;

    [Tooltip("Other main path to switch to when inside mainSwitchTrigger and pressing opposite axis.")]
    [SerializeField] private Path otherMainPath;

    public Collider MainSwitchTrigger => mainSwitchTrigger;
    public Path OtherMainPath => otherMainPath;

    [Header("Points (auto from children)")]
    [SerializeField] private Transform[] points;

    [Header("Curve Settings")]
    [SerializeField, Range(4, 64)] private int samplesPerSegment = 20;

    // Arc-length lookup table (sampled)
    private Vector3[] samples;     // positions along curve
    private float[] sampleCumLen;  // cumulative length at each sample
    private float totalLen;

    public float TotalLength => totalLen;

    private void Awake() => Rebuild();

#if UNITY_EDITOR
    private void OnValidate()
    {
        if (!Application.isPlaying) Rebuild();
    }
#endif

    public void Rebuild()
    {
        int n = transform.childCount;
        points = new Transform[n];
        for (int i = 0; i < n; i++) points[i] = transform.GetChild(i);

        if (n < 2)
        {
            totalLen = 0f;
            samples = Array.Empty<Vector3>();
            sampleCumLen = Array.Empty<float>();
            return;
        }

        int segCount = n - 1;
        int steps = Mathf.Max(4, samplesPerSegment);

        int sampleCount = segCount * steps + 1;
        samples = new Vector3[sampleCount];
        sampleCumLen = new float[sampleCount];

        int idx = 0;
        samples[idx] = points[0].position;
        sampleCumLen[idx] = 0f;
        idx++;

        totalLen = 0f;
        Vector3 prev = samples[0];

        for (int seg = 0; seg < segCount; seg++)
        {
            Vector3 p0 = points[Mathf.Max(seg - 1, 0)].position;
            Vector3 p1 = points[seg].position;
            Vector3 p2 = points[seg + 1].position;
            Vector3 p3 = points[Mathf.Min(seg + 2, n - 1)].position;

            for (int j = 1; j <= steps; j++)
            {
                float t = (float)j / steps;
                Vector3 pos = CatmullRom(p0, p1, p2, p3, t);

                samples[idx] = pos;

                totalLen += Vector3.Distance(prev, pos);
                sampleCumLen[idx] = totalLen;

                prev = pos;
                idx++;
            }
        }
    }

    public Vector3 EvaluatePosition(float s)
    {
        if (samples == null || samples.Length == 0) return transform.position;
        if (samples.Length == 1 || totalLen <= 0f) return samples[0];

        s = Mathf.Clamp(s, 0f, totalLen);

        int i = FindSampleIndex(s);
        int i2 = Mathf.Min(i + 1, samples.Length - 1);

        float a = sampleCumLen[i];
        float b = sampleCumLen[i2];
        float t = (b <= a) ? 0f : (s - a) / (b - a);

        return Vector3.Lerp(samples[i], samples[i2], t);
    }

    public Vector3 EvaluateTangent(float s)
    {
        if (samples == null || samples.Length < 2 || totalLen <= 0f) return Vector3.right;

        s = Mathf.Clamp(s, 0f, totalLen);

        float eps = Mathf.Max(0.001f, totalLen * 0.001f);
        float s0 = Mathf.Clamp(s - eps, 0f, totalLen);
        float s1 = Mathf.Clamp(s + eps, 0f, totalLen);

        Vector3 p0 = EvaluatePosition(s0);
        Vector3 p1 = EvaluatePosition(s1);
        Vector3 d = p1 - p0;

        return d.sqrMagnitude < 1e-8f ? Vector3.right : d.normalized;
    }

    public float ClosestS(Vector3 worldPos)
    {
        if (samples == null || samples.Length < 2 || totalLen <= 0f) return 0f;

        int best = 0;
        float bestD = float.PositiveInfinity;

        for (int i = 0; i < samples.Length; i++)
        {
            float d = (worldPos - samples[i]).sqrMagnitude;
            if (d < bestD)
            {
                bestD = d;
                best = i;
            }
        }

        return sampleCumLen[best];
    }

    private int FindSampleIndex(float s)
    {
        int low = 0;
        int high = sampleCumLen.Length - 2;

        while (low <= high)
        {
            int mid = (low + high) / 2;
            float a = sampleCumLen[mid];
            float b = sampleCumLen[mid + 1];

            if (s < a) high = mid - 1;
            else if (s >= b) low = mid + 1;
            else return mid;
        }

        return Mathf.Clamp(low, 0, sampleCumLen.Length - 2);
    }

    private static Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
    {
        float t2 = t * t;
        float t3 = t2 * t;

        return 0.5f * (
            (2f * p1) +
            (-p0 + p2) * t +
            (2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
            (-p0 + 3f * p1 - 3f * p2 + p3) * t3
        );
    }

    private void OnDrawGizmos()
    {
        int n = transform.childCount;
        if (n < 2) return;

        Gizmos.color = isDoorPath ? Color.magenta : Color.cyan;

        if (samples != null && samples.Length >= 2)
        {
            for (int i = 0; i < samples.Length - 1; i++)
                Gizmos.DrawLine(samples[i], samples[i + 1]);
            return;
        }

        Transform prev = transform.GetChild(0);
        for (int i = 1; i < n; i++)
        {
            Transform cur = transform.GetChild(i);
            Gizmos.DrawLine(prev.position, cur.position);
            prev = cur;
        }
    }
}