diff --git a/src/Cinematic/ArrowGenerator.cs b/src/Cinematic/ArrowGenerator.cs index 6d7573d..e0db24f 100644 --- a/src/Cinematic/ArrowGenerator.cs +++ b/src/Cinematic/ArrowGenerator.cs @@ -7,10 +7,12 @@ namespace UnityExplorer { public static GameObject CreateArrow(Vector3 arrowPosition, Quaternion arrowRotation){ GameObject cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + cylinder.GetComponent().enabled = false; cylinder.GetComponent().mesh = CreateCylinderMesh(0.01f, 20, 2); cylinder.transform.rotation = Quaternion.LookRotation(Vector3.down, Vector3.up); GameObject cone = GameObject.CreatePrimitive(PrimitiveType.Sphere); + cone.GetComponent().enabled = false; cone.GetComponent().mesh = CreateConeMesh(10, 0.05f, 0.1f); cone.transform.SetParent(cylinder.transform, true); diff --git a/src/UI/Panels/CamPaths.cs b/src/UI/Panels/CamPaths.cs index bf788f4..87dfd10 100644 --- a/src/UI/Panels/CamPaths.cs +++ b/src/UI/Panels/CamPaths.cs @@ -36,6 +36,9 @@ namespace UnityExplorer.UI.Panels GameObject followObject = null; + Toggle visualizePathToggle; + public GameObject pathVisualizer = new GameObject("PathVisualizer"); + // ~~~~~~~~ UI construction / callbacks ~~~~~~~~ protected override void ConstructPanelContent() @@ -70,13 +73,19 @@ namespace UnityExplorer.UI.Panels Toggle closedLoopToggle = new Toggle(); GameObject toggleClosedLoopObj = UIFactory.CreateToggle(horiGroup, "Close path in a loop", out closedLoopToggle, out Text toggleClosedLoopText); UIFactory.SetLayoutElement(toggleClosedLoopObj, minHeight: 25, flexibleWidth: 9999); - closedLoopToggle.onValueChanged.AddListener((isClosedLoop) => {closedLoop = isClosedLoop;}); closedLoopToggle.isOn = false; + closedLoopToggle.onValueChanged.AddListener((isClosedLoop) => {closedLoop = isClosedLoop; MaybeRedrawPath();}); toggleClosedLoopText.text = "Close path in a loop"; InputFieldRef TimeInput = null; GameObject secondRow = AddInputField("Time", "Path time (in seconds at 60fps):", $"Default: {time}", out TimeInput, Time_OnEndEdit, false); TimeInput.Text = time.ToString(); + + GameObject visualizePathObj = UIFactory.CreateToggle(secondRow, "Visualize Path", out visualizePathToggle, out Text visualizePathText); + UIFactory.SetLayoutElement(visualizePathObj, minHeight: 25, flexibleWidth: 9999); + visualizePathToggle.isOn = false; + visualizePathToggle.onValueChanged.AddListener(ToggleVisualizePath); + visualizePathText.text = "Visualize path"; } private void UpdateListNodes(){ @@ -92,6 +101,43 @@ namespace UnityExplorer.UI.Panels } } + private void ToggleVisualizePath(bool enable){ + if (enable){ + if (controlPoints.Count > 2){ + UpdateCatmullRomMoverData(); + + List lookaheadPoints = GetCameraPathsManager().GetLookaheadPoints(); + int skip_points = 5; // How many points do we have to skip before drawing another arrow (otherwise they look very cluttered) + int n = 0; + for (int i=0; i < lookaheadPoints.Count; i++){ + // We also want to draw an arrow on the last point in case its skipped. + if (n < skip_points && i != lookaheadPoints.Count - 1){ + n++; + continue; + } + + GameObject arrow = ArrowGenerator.CreateArrow(lookaheadPoints[i].position, lookaheadPoints[i].rotation); + arrow.transform.SetParent(pathVisualizer.transform, true); + n = 0; + } + } + + } + else { + foreach (Transform transform in pathVisualizer.transform) + { + UnityEngine.Object.Destroy(transform.gameObject); + } + } + } + + private void MaybeRedrawPath(){ + if (visualizePathToggle.isOn){ + ToggleVisualizePath(false); + ToggleVisualizePath(true); + } + } + private void DrawNodeOptions(CatmullRom.CatmullRomPoint point, int index){ GameObject horiGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "LightOptions", true, false, true, false, 4, default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft); @@ -106,6 +152,8 @@ namespace UnityExplorer.UI.Panels point.rotation = followObject != null ? FreeCamPanel.ourCamera.transform.localRotation : FreeCamPanel.ourCamera.transform.rotation; controlPoints[index] = point; + MaybeRedrawPath(); + EventSystemHelper.SetSelectedGameObject(null); }; @@ -132,7 +180,7 @@ namespace UnityExplorer.UI.Panels ButtonRef destroyButton = UIFactory.CreateButton(horiGroup, "Delete", "Delete"); UIFactory.SetLayoutElement(destroyButton.GameObject, minWidth: 80, minHeight: 25, flexibleWidth: 9999); - destroyButton.OnClick += () => {controlPoints.Remove(point); UpdateListNodes();}; + destroyButton.OnClick += () => {controlPoints.Remove(point); UpdateListNodes(); MaybeRedrawPath();}; //ShowPos and rot? } @@ -157,12 +205,10 @@ namespace UnityExplorer.UI.Panels void StartButton_OnClick() { if(GetCameraPathsManager()){ - GetCameraPathsManager().setClosedLoop(closedLoop); - GetCameraPathsManager().setLocalPoints(followObject != null); - GetCameraPathsManager().setSplinePoints(controlPoints.ToArray()); - GetCameraPathsManager().setTime(time); + UpdateCatmullRomMoverData(); GetCameraPathsManager().StartPath(); UIManager.ShowMenu = false; + pathVisualizer.SetActive(false); } EventSystemHelper.SetSelectedGameObject(null); @@ -171,6 +217,7 @@ namespace UnityExplorer.UI.Panels void TogglePause_OnClick(){ if(GetCameraPathsManager()){ GetCameraPathsManager().TogglePause(); + pathVisualizer.SetActive(!GetCameraPathsManager().IsPaused()); } EventSystemHelper.SetSelectedGameObject(null); @@ -179,6 +226,7 @@ namespace UnityExplorer.UI.Panels void Stop_OnClick(){ if (GetCameraPathsManager()){ GetCameraPathsManager().Stop(); + pathVisualizer.SetActive(true); } EventSystemHelper.SetSelectedGameObject(null); @@ -194,6 +242,7 @@ namespace UnityExplorer.UI.Panels controlPoints.Add(point); UpdateListNodes(); + MaybeRedrawPath(); EventSystemHelper.SetSelectedGameObject(null); } @@ -219,10 +268,16 @@ namespace UnityExplorer.UI.Panels } public void UpdatedFollowObject(GameObject obj){ - if(followObject != null) TranslatePointsToGlobal(); + // Had to include this check because the pathVisualizer was null for some reason + if (pathVisualizer == null) pathVisualizer = new GameObject("PathVisualizer"); + if (followObject != null){ + TranslatePointsToGlobal(); + pathVisualizer.transform.SetParent(null, true); + } followObject = obj; if (obj != null){ TranslatePointsToLocal(); + pathVisualizer.transform.SetParent(obj.transform, true); } UpdateListNodes(); } @@ -252,5 +307,14 @@ namespace UnityExplorer.UI.Panels controlPoints = newControlPoints; } + + void UpdateCatmullRomMoverData(){ + GetCameraPathsManager().setClosedLoop(closedLoop); + GetCameraPathsManager().setLocalPoints(followObject != null); + GetCameraPathsManager().setSplinePoints(controlPoints.ToArray()); + GetCameraPathsManager().setTime(time); + + GetCameraPathsManager().CalculateLookahead(); + } } } diff --git a/src/UI/Widgets/CatmullRom.cs b/src/UI/Widgets/CatmullRom.cs index 5a49d7a..7e60a1b 100644 --- a/src/UI/Widgets/CatmullRom.cs +++ b/src/UI/Widgets/CatmullRom.cs @@ -10,6 +10,8 @@ using UnhollowerRuntimeLib; using Il2CppInterop.Runtime.Injection; #endif +using UnityExplorer.UI; + namespace UnityExplorer.CatmullRom { // Struct to keep position, rotation and fov of a spline point @@ -128,6 +130,10 @@ namespace UnityExplorer.CatmullRom playingPath = !playingPath; } + public bool IsPaused(){ + return playingPath; + } + public void Stop(){ playingPath = false; delta = lookaheadDelta; @@ -190,8 +196,7 @@ namespace UnityExplorer.CatmullRom while (room <= 0.0001f) { // If the camera reached the last lookAhead we stop it if (delta >= 1){ - playingPath = false; - delta = 0; + PathFinished(); } delta = Math.Min(delta + lookaheadDelta, 1); @@ -344,7 +349,7 @@ namespace UnityExplorer.CatmullRom return a * t * t * t + b * t * t + c * t + d; } - void CalculateLookahead() { + public void CalculateLookahead() { // We assume 60 locked fps. float frames = time * 60; float pathLength = 0; @@ -370,5 +375,15 @@ namespace UnityExplorer.CatmullRom //ExplorerCore.LogWarning($"on path length {pathLength}"); //ExplorerCore.LogWarning($"lookaheadDelta {lookaheadDelta}"); } + + public List GetLookaheadPoints(){ + return lookaheadPoints; + } + + void PathFinished(){ + playingPath = false; + delta = 0; + UIManager.GetPanel(UIManager.Panels.CamPaths).pathVisualizer.SetActive(true); + } } }