Newer
Older
CGTrack / Assets / Scripts / Track.cs
@Pascal Syma Pascal Syma on 25 Jul 2021 11 KB Initial Commit
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

public class Track : MonoBehaviour
{
    public int nextDistance = 1;
    public GameObject pointPrefab;
    public float maxDistance = 1f;
    public bool realtimeUpdate = false;
    public bool renderTrack = false;
    public Mesh original;

    private List<Vector3> _bezierPoints = new List<Vector3>();
    private List<float> _distances = new List<float>();
    public float _totalDistance = 0f;
    private List<int> _pointIndex = new List<int>();

    private void Start()
    {
        CalculateSpline();

        CalculateTrack();
    }

    public float GetPositionOnTrack(float progress, out Vector3 position, out Quaternion rotation, out float a)
    {
        progress = Mathf.Repeat(progress, _totalDistance);


        var progressInt = 0;
        var lastDistance = 0f;
        var progressMod = 0f;
        foreach (var distance in _distances)
        {
            if (progress <= distance)
            {
                progressMod = (progress - lastDistance) / (distance - lastDistance);
                break;
            }

            progressInt++;
            lastDistance = distance;
        }

        var firstPoint = _bezierPoints[progressInt];
        var secondPoint = progressInt+1 >= _bezierPoints.Count ? _bezierPoints[0] : _bezierPoints[progressInt + 1];
        var thirdPoint = progressInt+2 >= _bezierPoints.Count ? _bezierPoints[0] : _bezierPoints[progressInt + 2];


        position = Vector3.Lerp(firstPoint, secondPoint, progressMod);
        rotation = Quaternion.Lerp(Quaternion.LookRotation( secondPoint - firstPoint, Vector3.left), Quaternion.LookRotation(  thirdPoint- secondPoint, Vector3.left), progressMod)*Quaternion.Euler(0, 0, -90);
        // a = (secondPoint - firstPoint).y * 9.81f / Vector3.Distance(firstPoint, secondPoint);

        a = 0;
        return progress;
    }

    public void AddPoint()
    {
        var nextPosition = gameObject.transform.position;
        var nextRotation = Quaternion.identity;
        
        // get last child
        if (gameObject.transform.childCount > 0) {
            var lastChild = gameObject.transform.GetChild(gameObject.transform.childCount - 1);

            nextPosition = lastChild.position + lastChild.forward * nextDistance;
            nextRotation = lastChild.rotation;
        }

        
#if UNITY_EDITOR
        var nextChild = PrefabUtility.InstantiatePrefab(pointPrefab) as GameObject;
    #else
        var nextChild = Instantiate(pointPrefab) as GameObject;
#endif
        nextChild.transform.SetPositionAndRotation(nextPosition, nextRotation);
        nextChild.transform.SetParent(gameObject.transform);
    }

    public void AddPoint(int sibling)
    {
        if (gameObject.transform.childCount < sibling + 2)
        {
            AddPoint();
            return;
        }

        var previous = gameObject.transform.GetChild(sibling);
        var next = gameObject.transform.GetChild(sibling + 1);
        
#if UNITY_EDITOR
        var newChild = PrefabUtility.InstantiatePrefab(pointPrefab) as GameObject;
#else
        var newChild = Instantiate(pointPrefab) as GameObject;
#endif
        var p2 = previous.gameObject.GetComponent<TrackPoint>().GetForwardTan();
        var p3 = next.gameObject.GetComponent<TrackPoint>().GetBackwardTan();

        var p1 = previous.position;
        var p4 = next.position;
        
        // calculate midpoint and midrotation
        newChild.transform.SetPositionAndRotation(CalculateBezierPoint(p1, p2, p3, p4, 0.5f), Quaternion.FromToRotation(Vector3.forward, p3-p2));
        newChild.transform.SetParent(gameObject.transform);
        newChild.transform.SetSiblingIndex(sibling + 1);
    }

    private void OnDrawGizmos()
    {
        if (realtimeUpdate)
            CalculateSpline();

        if (_bezierPoints.Count <= 0)
            return;
        
        var previous = _bezierPoints[_bezierPoints.Count - 1];
        var preprevious = _bezierPoints[_bezierPoints.Count - 2];
        var previousLeft = (preprevious + previous) / 2;
        previousLeft += Quaternion.LookRotation( previous - preprevious, Vector3.left) *
                        Quaternion.Euler(0, 0, -90) * Vector3.left;
        var previousRight = (preprevious + previous) / 2;
        previousRight += Quaternion.LookRotation( previous - preprevious, Vector3.left) *
                         Quaternion.Euler(0, 0, -90) * Vector3.right;
        for (int i = 0; i < _bezierPoints.Count; i++)
        {
            var point = _bezierPoints[i];

            Gizmos.color = _pointIndex.Contains(i) ? Color.blue : Color.red;
            Gizmos.DrawSphere(point, _pointIndex.Contains(i) ? 0.1f : 0.07f);

            Gizmos.color = Color.green;
            Gizmos.DrawLine(previous, point);
            
            // "gravity"
            Gizmos.color = Color.yellow;
            Gizmos.DrawLine((point + previous)/2,(point + previous)/2 + (point - previous).y * Vector3.down * 9.81f);

            Gizmos.color = Color.magenta;
            var pointLeft = (point + previous)/2;
            pointLeft += Quaternion.LookRotation(point - previous, Vector3.left) *
                         Quaternion.Euler(0, 0, -90) * Vector3.left;
            var pointRight = (point + previous)/2;
            pointRight += Quaternion.LookRotation(point - previous, Vector3.left) *
                          Quaternion.Euler(0, 0, -90) * Vector3.right;
            Gizmos.DrawSphere(pointLeft, 0.1f);
            Gizmos.DrawSphere(pointRight, 0.1f);

            Gizmos.color = Color.cyan;
            Gizmos.DrawLine(previousLeft, pointLeft);
            Gizmos.DrawLine(previousRight, pointRight);
            
            
            previous = point;
            previousLeft = pointLeft;
            previousRight = pointRight;
        }
    }

    // public void CalculateTrack()
    // {
    //     MeshFilter meshFilter = gameObject.GetComponent(typeof(MeshFilter)) != null ? gameObject.GetComponent<MeshFilter>() : gameObject.AddComponent<MeshFilter>();
    //    
    //     var combine = new List<CombineInstance>();
    //     for (int i = 0; i < _bezierPoints.Count; i++)
    //     {
    //         var point = _bezierPoints[i];
    //         var nextPoint = i+1 >= _bezierPoints.Count ? _bezierPoints[0] : _bezierPoints[i + 1];
    //         var scale = nextPoint - point;
    //         var combineInstance = new CombineInstance();
    //         combineInstance.mesh = original;
    //         combineInstance.subMeshIndex = 0;
    //         combineInstance.transform = Matrix4x4.Translate((point+nextPoint)/2) * Matrix4x4.Rotate(Quaternion.LookRotation( nextPoint - point, Vector3.left)*Quaternion.Euler(0, 0, -90)) * Matrix4x4.Scale(new Vector3(1, 1, scale.magnitude/0.5f));
    //         
    //         
    //         combine.Add(combineInstance);
    //     }
    //     meshFilter.mesh.CombineMeshes(combine.ToArray());
    // }
    public void CalculateTrack()
    {
        MeshFilter meshFilter = gameObject.GetComponent(typeof(MeshFilter)) != null ? gameObject.GetComponent<MeshFilter>() : gameObject.AddComponent<MeshFilter>();

        if( meshFilter.sharedMesh == null )
            meshFilter.sharedMesh = new Mesh();
        Mesh mesh = meshFilter.sharedMesh;
        mesh.Clear();
        
        List<Vector3> verticies = new List<Vector3>();
        List<Vector3> normals = new List<Vector3>();
        List<Vector2> uvs = new List<Vector2>();
        List<int> triags = new List<int>();
        for (int i = 0; i < _bezierPoints.Count; i++)
        {
            var point = _bezierPoints[i];
            var nextPoint = i+1 >= _bezierPoints.Count ? _bezierPoints[0] : _bezierPoints[i + 1];
            var scale = nextPoint - point;
            
            var center = (point+nextPoint)/2;
            for (int j = 0; j < original.vertices.Length; j++)
            {
                verticies.Add(Quaternion.LookRotation(nextPoint - point, Vector3.left) *
                    Quaternion.Euler(0, 0, -90) * original.vertices[j] + center);
                // verticies.Add(original.vertices[j] + center);
                normals.Add(original.normals[j]);
                uvs.Add(original.uv[0]);
                
            }

            for (int j = 0; j < original.triangles.Length; j++)
            {
                triags.Add(original.triangles[j] + i * original.vertices.Length);
            }
            // combineInstance.transform = Matrix4x4.Translate((point+nextPoint)/2) * Matrix4x4.Rotate(Quaternion.LookRotation( nextPoint - point, Vector3.left)*Quaternion.Euler(0, 0, -90)) * Matrix4x4.Scale(new Vector3(1, 1, scale.magnitude/0.5f));
            
        }
        mesh.SetVertices(verticies);
        mesh.SetNormals(normals);
        mesh.SetUVs(0, uvs);
        mesh.SetTriangles(triags, 0);
        // meshFilter.mesh = mesh;
    }

    public void CalculateSpline()
    {
        _bezierPoints.Clear();
        _pointIndex.Clear();
        _distances.Clear();
        
        if (gameObject.transform.childCount < 2)
            return;
        
        var previous = gameObject.transform.GetChild(0);
        var previousTrackPoint = previous.GetComponent<TrackPoint>();
        for (int i = 1; i < gameObject.transform.childCount + 1; i++)
        {
            var next = gameObject.transform.GetChild(i % gameObject.transform.childCount);
            var subdivCount = -1;
            var nextTrackPoint = next.GetComponent<TrackPoint>();

            var p1 = previous.position;
            var p2 = previousTrackPoint.GetForwardTan();
            var p3 = nextTrackPoint.GetBackwardTan();
            var p4 = next.position;

            var dis = float.MaxValue;

            while (dis > maxDistance)
            {
                subdivCount++;
                dis = Vector3.Distance(CalculateBezierPoint(p1, p2, p3, p4, 1f / (subdivCount + 1f)), p1);
            }

            for (int j = 1; j < subdivCount+1; j++)
            {
                var t = ((float) j) / (subdivCount+1);
                // var midpoint = Vector3.Slerp(previous.position, next.position, ((float) j+1) / (subdivCount+1));
                
                _bezierPoints.Add(CalculateBezierPoint(p1, p2, p3, p4, t));
            }
            _pointIndex.Add(_bezierPoints.Count);
            _bezierPoints.Add(p4);

            previous = next;
            previousTrackPoint = nextTrackPoint;
        }

        var distance = 0f;
        _distances.Add(0);
        var p = _bezierPoints[0];
        for (int i = 1; i < _bezierPoints.Count; i++)
        {
            distance += Vector3.Distance(p, _bezierPoints[i]);
            _distances.Add(distance);
        }

        this._totalDistance = distance;
    }

    /// <summary>
    /// Calculate a point on the Bezier Curve.
    /// P = (1−t)³P₁ + 3(1−t)²tP₂ + 3(1−t)t²P₃ + t³P₄
    /// based on <see href="https://javascript.info/bezier-curve#maths">https://javascript.info/bezier-curve#maths</see>
    /// </summary>
    /// <param name="p1">First point</param>
    /// <param name="p2">Forward tangent of the first point</param>
    /// <param name="p3">Backward tangent of the second point</param>
    /// <param name="p4">Second point</param>
    /// <param name="t">Point "progress" [0,1]</param>
    /// <returns>Point on the Bezier Curve</returns>
    public static Vector3 CalculateBezierPoint(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float t)
    {
        t = Mathf.Clamp01(t);
        var minusT = 1 - t;
        
        return Mathf.Pow(minusT, 3) * p1 + 3 * Mathf.Pow(minusT, 2) * t * p2 +
               3 * minusT * Mathf.Pow(t, 2) * p3 + Mathf.Pow(t, 3) * p4;
    }

}