using exprivia; using sam; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; public class OrbitArrowsController : MonoBehaviour { public int gapThresh = 30; public int minSubPathVertexCount = 10; public int arrowBodyVertexCount = 100; public int arrowBodyVertexDist = 2; public int arrowHeadVertexCount = 50; public int arrowHeadVertexDist = 2; public float arrowElevation = 1.0f; public int arrowSeparation = 100; public float arrowBodyStartWidth = 0.05f; public float arrowBodyEndWidth = 0.05f; public float arrowHeadStartWidth = 0.15f; public float minScale = 0.0001f; public float maxScale = 1000.0f; public Material ascendingMaterial; //public Material descendingMaterial; public Color ascendingColor = Color.red; public Color descendingColor = Color.blue; ProductData data; SortedDictionary posDic; List> paths; List ascendingFlags; int arrowBodyLength; int arrowHeadLength; int arrowLength; public Camera cam; static OrbitArrowsController instance; #if UNITY_EDITOR [MenuItem("Exprivia/DataViewer/RegenerateArrows")] static public void RegenerateArrows() { instance?.GenerateAllArrows(); } #endif private void Awake() { instance = this; if (cam == null) cam = Camera.main; } public void OnProductSelected(ProductData pd) { data = pd; GenerateAllArrows(); } public void GenerateAllArrows() { ProductUserData ud = data.profile.GetUserData("LatStop"); List latStopList = (data.userData[ud.fieldIndex] as List); ud = data.profile.GetUserData("LonStop"); List lonStopList = (data.userData[ud.fieldIndex] as List); ud = data.profile.GetUserData("DeltaSeconds"); List durationList = (data.userData[ud.fieldIndex] as List); arrowBodyLength = (arrowBodyVertexCount - 1) * arrowBodyVertexDist; arrowHeadLength = (arrowHeadVertexCount - 1) * arrowHeadVertexDist; arrowLength = arrowBodyLength + arrowHeadLength; posDic = new SortedDictionary(); foreach (var dp in data.dataPoints) { int t = (int)(dp.time - data.actualStartTime).TotalSeconds; if (!posDic.ContainsKey(t)) { posDic.Add(t, (dp.latitude, dp.longitude)); } /* int dpi = dp.dataPointIndex; t += durationList[dpi]; if (!posDic.ContainsKey(t)) { float lat = latStopList[dpi]; while (lat > 90.0f) lat -= 180.0f; while (lat < -90.0f) lat += 180.0f; float lon = lonStopList[dpi]; while (lon > 180.0f) lon -= 360.0f; while (lon < -180.0f) lon += 360.0f; posDic.Add(t, (lat, lon)); } */ } int n = posDic.Count; // Debug.Log($"OrbitArrowsController: satellite path has {n} points"); int[] keys = new int[n]; posDic.Keys.CopyTo(keys, 0); paths = new List>(); ascendingFlags = new List(); // Debug.Log($"creating initial subpath, inserting first point with lat {posDic[keys[0]].Item1}"); List<(int, (float, float))> p = new List<(int, (float, float))>(); bool currentAscending = false; int lastT = keys[0]; p.Add((lastT, posDic[lastT])); foreach (var kv in posDic) { if (kv.Key == lastT) continue; bool ascendingChanged = false; if (p.Count > 1) { bool ascendingState = (kv.Value.Item1 > p.Last().Item2.Item1); // Debug.Log($"1. next ascending state computed as {ascendingState} current {currentAscending} count {p.Count} last {p.Last().Item2.Item1} new {kv.Value.Item1}"); if (ascendingState != currentAscending) { // Debug.Log($"1. ascendingChanged from {currentAscending} to {ascendingState} when p.Count is {p.Count}"); ascendingChanged = true; } } if (((kv.Key - lastT) > gapThresh) || ascendingChanged) { if (p.Count > minSubPathVertexCount) { // Debug.Log($"1. saving path with ascending flag {currentAscending} p.Count is {p.Count}"); paths.Add(p); ascendingFlags.Add(currentAscending); } else { // Debug.Log($"Discarding subpath with only {p.Count} entries"); } // Debug.Log($"1. creating new subpath"); p = new List<(int, (float, float))>(); } if (p.Count > 0) { if (lastT < kv.Key - 1) { int t1 = lastT; float lat1 = p[p.Count - 1].Item2.Item1; float lon1 = p[p.Count - 1].Item2.Item2; int t2 = kv.Key; float lat2 = kv.Value.Item1; float lon2 = kv.Value.Item2; float invrange = 1.0f / (t2 - t1); while (lastT < kv.Key - 1) { lastT++; float f = invrange * (lastT - t1); float latmix = Mathf.Lerp(lat1, lat2, f); float lonmix = Mathf.Lerp(lon1, lon2, f); if (p.Count > 1) { bool ascendingState = (latmix > p.Last().Item2.Item1); // Debug.Log($"2. next ascending state computed as {ascendingState} current {currentAscending} count {p.Count} last {p.Last().Item2.Item1} new {latmix}" ); if (ascendingState != currentAscending) { // Debug.Log($"2. ascendingChanged from {currentAscending} to {ascendingState} when p.Count is {p.Count}"); ascendingChanged = true; if (p.Count > minSubPathVertexCount) { // Debug.Log($"2. saving path with ascending flag {currentAscending} p.Count is {p.Count}"); paths.Add(p); ascendingFlags.Add(currentAscending); } else { // Debug.Log($"Discarding subpath with only {p.Count} entries"); } // Debug.Log($"2. creating new subpath"); p = new List<(int, (float, float))>(); //break; } } else if (p.Count == 1) { currentAscending = (latmix > p.Last().Item2.Item1); // Debug.Log($"1. about to add second (lerped) point to path. currentAscending computed as {currentAscending} last {p.Last().Item2.Item1} new {latmix}"); } // Debug.Log($"1. Adding point with latitude {latmix}"); p.Add((lastT, (latmix, lonmix))); } } } if (p.Count == 1) { currentAscending = (kv.Value.Item1 > p.Last().Item2.Item1); // Debug.Log($"2. about to add second point to path. currentAscending computed as {currentAscending} last {p.Last().Item2.Item1} new {kv.Value.Item1}"); } // Debug.Log($"2. Adding point with latitude {kv.Value.Item1}"); p.Add((kv.Key, kv.Value)); lastT = kv.Key; } if (p.Count > minSubPathVertexCount) { // Debug.Log($"3. saving path with ascending flag {currentAscending} p.Count is {p.Count}"); paths.Add(p); ascendingFlags.Add(currentAscending); } else { // Debug.Log($"3. Discarding subpath with only {p.Count} entries"); } // Debug.Log($"- --- -- - - - - Generated {paths.Count} sub-paths"); foreach (var path in paths) { // Debug.Log($"Path starts at time {path[0].Item1} ends at time {path[path.Count - 1].Item1} with {path.Count} entries"); } transform.DestroyAllChildren(); if (paths.Count == 0) { Debug.LogWarning("OrbitArrowsController: could not generate any sub-path"); return; } //foreach (var path in paths) for (int pi = 0; pi < paths.Count; pi++) { var path = paths[pi]; int s = path.Count - arrowLength; bool ascending = ascendingFlags[pi]; for (int i = 0; i < s; i += (arrowLength + arrowSeparation)) { CreateArrow(path, i, ascending? ascendingColor : descendingColor); } } } void CreateArrow(List<(int, (float, float))> p, int startIndex, Color color) { if (p.Count < (startIndex + arrowLength)) { Debug.LogWarning("Not enough room to fit arrow on path"); return; } GameObject arrowBodyGO = new GameObject("Arrow Body"); arrowBodyGO.transform.SetParent(transform); LineRenderer arrowBody = arrowBodyGO.AddComponent(); arrowBody.positionCount = arrowBodyVertexCount; Vector3[] arrowBodyPositions = new Vector3[arrowBodyVertexCount]; float startLat = p[startIndex].Item2.Item1; float startLon = p[startIndex].Item2.Item2; Vector3 startPos = sam.Utils.DegLonLatHgtToXYZ(new Vector3(startLon, startLat, arrowElevation), true); float e = startPos.magnitude; float endLat = p[startIndex + ((arrowBodyVertexCount - 1) * arrowBodyVertexDist)].Item2.Item1; float endLon = p[startIndex + ((arrowBodyVertexCount - 1) * arrowBodyVertexDist)].Item2.Item2; Vector3 endPos = sam.Utils.DegLonLatHgtToXYZ(new Vector3(endLon, endLat, arrowElevation), true); for (int i = 0; i < arrowBodyVertexCount; i++) { float f = ((float)i / (arrowBodyVertexCount - 1)); Vector3 pos = Vector3.Lerp(startPos, endPos, f); pos = pos.normalized * e; arrowBodyPositions[i] = pos; if (i > 0) Debug.DrawLine(arrowBodyPositions[i - 1], arrowBodyPositions[i], Random.ColorHSV(), float.MaxValue, false); } arrowBody.SetPositions(arrowBodyPositions); arrowBody.startWidth = arrowBodyStartWidth; arrowBody.endWidth = arrowBodyEndWidth; arrowBody.material = ascendingMaterial; arrowBody.startColor = arrowBody.endColor = color; GameObject arrowHeadGO = new GameObject("Arrow Head"); arrowHeadGO.transform.SetParent(transform); LineRenderer arrowHead = arrowHeadGO.AddComponent(); arrowHead.positionCount = arrowHeadVertexCount; Vector3[] arrowHeadPositions = new Vector3[arrowHeadVertexCount]; startLat = p[startIndex + arrowBodyLength].Item2.Item1; startLon = p[startIndex + arrowBodyLength].Item2.Item2; startPos = sam.Utils.DegLonLatHgtToXYZ(new Vector3(startLon, startLat, arrowElevation), true); endLat = p[startIndex + arrowBodyLength + ((arrowHeadVertexCount - 1) * arrowHeadVertexDist)].Item2.Item1; endLon = p[startIndex + arrowBodyLength + ((arrowHeadVertexCount - 1) * arrowHeadVertexDist)].Item2.Item2; endPos = sam.Utils.DegLonLatHgtToXYZ(new Vector3(endLon, endLat, arrowElevation), true); for (int i = 0; i < arrowHeadVertexCount; i++) { float f = ((float)i / (arrowHeadVertexCount - 1)); Vector3 pos = Vector3.Lerp(startPos, endPos, f); pos = pos.normalized * e; arrowHeadPositions[i] = pos; if (i > 0) Debug.DrawLine(arrowHeadPositions[i - 1], arrowHeadPositions[i], Random.ColorHSV(), float.MaxValue, false); } arrowHead.SetPositions(arrowHeadPositions); arrowHead.startWidth = arrowHeadStartWidth; arrowHead.endWidth = 0; arrowHead.material = ascendingMaterial; arrowHead.startColor = arrowHead.endColor = color; OrbitArrowResizer resizer = arrowBodyGO.AddComponent(); resizer.Initialize(arrowHead, arrowHeadPositions[0]); } #if false void CreateArrow(List<(int, (float, float))> p, int startIndex, Color color) { if (p.Count < (startIndex + arrowLength)) { Debug.LogWarning("Not enough room to fit arrow on path"); return; } GameObject arrowBodyGO = new GameObject("Arrow Body"); arrowBodyGO.transform.SetParent(transform); LineRenderer arrowBody = arrowBodyGO.AddComponent(); arrowBody.positionCount = arrowBodyVertexCount; Vector3[] arrowBodyPositions = new Vector3[arrowBodyVertexCount]; for (int i = 0; i < arrowBodyVertexCount; i++) { float lat = p[startIndex + (i * arrowBodyVertexDist)].Item2.Item1; float lon = p[startIndex + (i * arrowBodyVertexDist)].Item2.Item2; Vector3 lonLatElev = new Vector3(lon, lat, arrowElevation); arrowBodyPositions[i] = sam.Utils.DegLonLatHgtToXYZ(lonLatElev, true); if (i > 0) Debug.DrawLine(arrowBodyPositions[i - 1], arrowBodyPositions[i], Random.ColorHSV(), float.MaxValue, false); if (i > 0) { float dx = Mathf.Abs(arrowBodyPositions[i].x - arrowBodyPositions[i - 1].x); if (dx > 1.0f) { Debug.LogError("Detected invalid vertex in arrow LineRenderer.."); } } } arrowBody.SetPositions(arrowBodyPositions); arrowBody.startWidth = arrowBodyStartWidth; arrowBody.endWidth = arrowBodyEndWidth; arrowBody.material = ascendingMaterial; arrowBody.startColor = arrowBody.endColor = color; GameObject arrowHeadGO = new GameObject("Arrow Head"); arrowHeadGO.transform.SetParent(transform); LineRenderer arrowHead = arrowHeadGO.AddComponent(); arrowHead.positionCount = arrowHeadVertexCount; Vector3[] arrowHeadPositions = new Vector3[arrowHeadVertexCount]; for (int i = 0; i < arrowHeadVertexCount; i++) { float lat = p[startIndex + arrowBodyLength + (i * arrowHeadVertexDist)].Item2.Item1; float lon = p[startIndex + arrowBodyLength + (i * arrowHeadVertexDist)].Item2.Item2; Vector3 lonLatElev = new Vector3(lon, lat, arrowElevation); arrowHeadPositions[i] = sam.Utils.DegLonLatHgtToXYZ(lonLatElev, true); } arrowHead.SetPositions(arrowHeadPositions); arrowHead.startWidth = arrowHeadStartWidth; arrowHead.endWidth = 0; arrowHead.material = ascendingMaterial; arrowHead.startColor = arrowHead.endColor = color; OrbitArrowResizer resizer = arrowBodyGO.AddComponent(); resizer.Initialize(arrowHead, arrowHeadPositions[0]); } #endif public class OrbitArrowResizer : MonoBehaviour { LineRenderer LR; LineRenderer headLR; Camera cam; Vector3 center; OrbitArrowsController controller; public void Initialize(LineRenderer head, Vector3 c) { headLR = head; center = c; LR = GetComponent(); controller = GetComponentInParent(); cam = controller.cam; } public void Update() { float s = (cam.transform.position - center).magnitude; s = Mathf.Clamp(s, controller.minScale, controller.maxScale); LR.startWidth = controller.arrowBodyStartWidth * s; LR.endWidth = controller.arrowBodyEndWidth * s; headLR.startWidth = controller.arrowHeadStartWidth * s; headLR.endWidth = 0.0f; } } }