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; } }