From e1e1f3d9abc9acb5cd52fa42682f1fe518f35a05 Mon Sep 17 00:00:00 2001 From: originalnicodr Date: Sun, 26 Feb 2023 19:12:06 -0300 Subject: [PATCH] Added Camera Path System prototype --- src/ExplorerBehaviour.cs | 397 ++++++++++++++++++++++++++++++++++++++ src/ExplorerCore.cs | 5 + src/UI/Panels/CamPaths.cs | 88 +++++++++ src/UI/UIManager.cs | 6 +- 4 files changed, 494 insertions(+), 2 deletions(-) create mode 100644 src/UI/Panels/CamPaths.cs diff --git a/src/ExplorerBehaviour.cs b/src/ExplorerBehaviour.cs index 8014451..dea2f6b 100644 --- a/src/ExplorerBehaviour.cs +++ b/src/ExplorerBehaviour.cs @@ -7,6 +7,7 @@ using UnhollowerRuntimeLib; using Il2CppInterop.Runtime.Injection; #endif #endif +using UnityExplorer.UI.Panels; namespace UnityExplorer { @@ -70,6 +71,8 @@ namespace UnityExplorer } } + // Cinematic stuff + public class TimeScaleController : MonoBehaviour { internal static TimeScaleController Instance { get; private set; } @@ -112,4 +115,398 @@ namespace UnityExplorer settingTimeScale = false; } } + + /* + Based of JPBotelho implementation + https://github.com/JPBotelho/Catmull-Rom-Splines + + Catmull-Rom splines are Hermite curves with special tangent values. + Hermite curve formula: + (2t^3 - 3t^2 + 1) * p0 + (t^3 - 2t^2 + t) * m0 + (-2t^3 + 3t^2) * p1 + (t^3 - t^2) * m1 + For points p0 and p1 passing through points m0 and m1 interpolated over t = [0, 1] + Tangent M[k] = (P[k+1] - P[k-1]) / 2 + */ + public class CatmullRom + { + //Struct to keep position, normal and tangent of a spline point + [System.Serializable] + public struct CatmullRomPoint + { + public Vector3 position; + public Vector3 tangent; + public Vector3 normal; + + public CatmullRomPoint(Vector3 position, Vector3 tangent, Vector3 normal) + { + this.position = position; + this.tangent = tangent; + this.normal = normal; + } + } + + private int resolution; //Amount of points between control points. [Tesselation factor] + private bool closedLoop; + + private CatmullRomPoint[] splinePoints; //Generated spline points + + private Vector3[] controlPoints; + public bool playingPath; + int currentPoint = 0; + + //Returns spline points. Count is contorolPoints * resolution + [resolution] points if closed loop. + public CatmullRomPoint[] GetPoints() + { + if(splinePoints == null) + { + throw new System.NullReferenceException("Spline not Initialized!"); + } + + return splinePoints; + } + + public CatmullRom(Transform[] controlPoints, int resolution, bool closedLoop) + { + if(controlPoints == null || controlPoints.Length <= 2 || resolution < 2) + { + ExplorerCore.Log(controlPoints); + throw new ArgumentException("Catmull Rom Error: Too few control points or resolution too small"); + } + + this.controlPoints = new Vector3[controlPoints.Length]; + for(int i = 0; i < controlPoints.Length; i++) + { + this.controlPoints[i] = controlPoints[i].position; + } + + this.resolution = resolution; + this.closedLoop = closedLoop; + + GenerateSplinePoints(); + } + + //Updates control points + public void Update(Transform[] controlPoints) + { + if(controlPoints.Length <= 0 || controlPoints == null) + { + throw new ArgumentException("Invalid control points"); + } + + this.controlPoints = new Vector3[controlPoints.Length]; + for(int i = 0; i < controlPoints.Length; i++) + { + this.controlPoints[i] = controlPoints[i].position; + } + + GenerateSplinePoints(); + } + + //Updates resolution and closed loop values + public void Update(int resolution, bool closedLoop) + { + if(resolution < 2) + { + throw new ArgumentException("Invalid Resolution. Make sure it's >= 1"); + } + this.resolution = resolution; + this.closedLoop = closedLoop; + + GenerateSplinePoints(); + } + + //Draws a line between every point and the next. + public void DrawSpline(Color color) + { + if(ValidatePoints()) + { + //LineRenderer lineRenderer = new GameObject().GetComponent(); + + for(int i = 0; i < splinePoints.Length; i++) + { + if(i == splinePoints.Length - 1 && closedLoop) + { + //lineRenderer.SetPosition(i, splinePoints[0].position); + //lineRenderer.SetPosition(i + 1, splinePoints[i].position); + + Debug.DrawLine(splinePoints[i].position, splinePoints[0].position, color); + } + else if(i < splinePoints.Length - 1) + { + //lineRenderer.SetPosition(i, splinePoints[i].position); + //lineRenderer.SetPosition(i + 1, splinePoints[i + 1].position); + + Debug.DrawLine(splinePoints[i].position, splinePoints[i+1].position, color); + } + } + } + } + + public void DrawNormals(float extrusion, Color color) + { + if(ValidatePoints()) + { + for(int i = 0; i < splinePoints.Length; i++) + { + Debug.DrawLine(splinePoints[i].position, splinePoints[i].position + splinePoints[i].normal * extrusion, color); + } + } + } + + public void DrawTangents(float extrusion, Color color) + { + if(ValidatePoints()) + { + for(int i = 0; i < splinePoints.Length; i++) + { + Debug.DrawLine(splinePoints[i].position, splinePoints[i].position + splinePoints[i].tangent * extrusion, color); + } + } + } + + //Validates if splinePoints have been set already. Throws nullref exception. + private bool ValidatePoints() + { + if(splinePoints == null) + { + throw new NullReferenceException("Spline not initialized!"); + } + return splinePoints != null; + } + + //Sets the length of the point array based on resolution/closed loop. + private void InitializeProperties() + { + int pointsToCreate; + if (closedLoop) + { + pointsToCreate = resolution * controlPoints.Length; //Loops back to the beggining, so no need to adjust for arrays starting at 0 + } + else + { + pointsToCreate = resolution * (controlPoints.Length - 1); + } + + splinePoints = new CatmullRomPoint[pointsToCreate]; + } + + //Math stuff to generate the spline points + private void GenerateSplinePoints() + { + InitializeProperties(); + + Vector3 p0, p1; //Start point, end point + Vector3 m0, m1; //Tangents + + // First for loop goes through each individual control point and connects it to the next, so 0-1, 1-2, 2-3 and so on + int closedAdjustment = closedLoop ? 0 : 1; + for (int currentPoint = 0; currentPoint < controlPoints.Length - closedAdjustment; currentPoint++) + { + bool closedLoopFinalPoint = (closedLoop && currentPoint == controlPoints.Length - 1); + + p0 = controlPoints[currentPoint]; + + if(closedLoopFinalPoint) + { + p1 = controlPoints[0]; + } + else + { + p1 = controlPoints[currentPoint + 1]; + } + + // m0 + if (currentPoint == 0) // Tangent M[k] = (P[k+1] - P[k-1]) / 2 + { + if(closedLoop) + { + m0 = p1 - controlPoints[controlPoints.Length - 1]; + } + else + { + m0 = p1 - p0; + } + } + else + { + m0 = p1 - controlPoints[currentPoint - 1]; + } + + // m1 + if (closedLoop) + { + if (currentPoint == controlPoints.Length - 1) //Last point case + { + m1 = controlPoints[(currentPoint + 2) % controlPoints.Length] - p0; + } + else if (currentPoint == 0) //First point case + { + m1 = controlPoints[currentPoint + 2] - p0; + } + else + { + m1 = controlPoints[(currentPoint + 2) % controlPoints.Length] - p0; + } + } + else + { + if (currentPoint < controlPoints.Length - 2) + { + m1 = controlPoints[(currentPoint + 2) % controlPoints.Length] - p0; + } + else + { + m1 = p1 - p0; + } + } + + m0 *= 0.5f; //Doing this here instead of in every single above statement + m1 *= 0.5f; + + float pointStep = 1.0f / resolution; + + if ((currentPoint == controlPoints.Length - 2 && !closedLoop) || closedLoopFinalPoint) //Final point + { + pointStep = 1.0f / (resolution - 1); // last point of last segment should reach p1 + } + + // Creates [resolution] points between this control point and the next + for (int tesselatedPoint = 0; tesselatedPoint < resolution; tesselatedPoint++) + { + float t = tesselatedPoint * pointStep; + + CatmullRomPoint point = Evaluate(p0, p1, m0, m1, t); + + splinePoints[currentPoint * resolution + tesselatedPoint] = point; + } + } + } + + //Evaluates curve at t[0, 1]. Returns point/normal/tan struct. [0, 1] means clamped between 0 and 1. + public static CatmullRomPoint Evaluate(Vector3 start, Vector3 end, Vector3 tanPoint1, Vector3 tanPoint2, float t) + { + Vector3 position = CalculatePosition(start, end, tanPoint1, tanPoint2, t); + Vector3 tangent = CalculateTangent(start, end, tanPoint1, tanPoint2, t); + Vector3 normal = NormalFromTangent(tangent); + + return new CatmullRomPoint(position, tangent, normal); + } + + //Calculates curve position at t[0, 1] + public static Vector3 CalculatePosition(Vector3 start, Vector3 end, Vector3 tanPoint1, Vector3 tanPoint2, float t) + { + // Hermite curve formula: + // (2t^3 - 3t^2 + 1) * p0 + (t^3 - 2t^2 + t) * m0 + (-2t^3 + 3t^2) * p1 + (t^3 - t^2) * m1 + Vector3 position = (2.0f * t * t * t - 3.0f * t * t + 1.0f) * start + + (t * t * t - 2.0f * t * t + t) * tanPoint1 + + (-2.0f * t * t * t + 3.0f * t * t) * end + + (t * t * t - t * t) * tanPoint2; + + return position; + } + + //Calculates tangent at t[0, 1] + public static Vector3 CalculateTangent(Vector3 start, Vector3 end, Vector3 tanPoint1, Vector3 tanPoint2, float t) + { + // Calculate tangents + // p'(t) = (6t² - 6t)p0 + (3t² - 4t + 1)m0 + (-6t² + 6t)p1 + (3t² - 2t)m1 + Vector3 tangent = (6 * t * t - 6 * t) * start + + (3 * t * t - 4 * t + 1) * tanPoint1 + + (-6 * t * t + 6 * t) * end + + (3 * t * t - 2 * t) * tanPoint2; + + return tangent.normalized; + } + + //Calculates normal vector from tangent + public static Vector3 NormalFromTangent(Vector3 tangent) + { + return Vector3.Cross(tangent, Vector3.up).normalized / 2; + } + + public void MaybeRunPath(){ + if(playingPath){ + if(ExplorerCore.CameraPathsManager != null && currentPoint < ExplorerCore.CameraPathsManager.GetPoints().Length){ + FreeCamPanel.ourCamera.transform.position = ExplorerCore.CameraPathsManager.GetPoints()[currentPoint].position; + //FreeCamPanel.ourCamera.transform.rotation = ExplorerCore.CameraPathsManager.GetPoints()[currentPoint].rotation; + currentPoint++; + if(currentPoint >= ExplorerCore.CameraPathsManager.GetPoints().Length) + playingPath = false; + } + } + } + + public void StartPath(){ + playingPath = true; + } + } + + /* + + public struct LineDrawer + { + private LineRenderer lineRenderer; + private float lineSize; + + public LineDrawer(float lineSize = 0.2f) + { + //GameObject lineObj = new GameObject("LineObj"); + //lineRenderer = lineObj.AddComponent(); + + //Particles/Additive + //lineRenderer.material = new Material(Shader.Find("Hidden/Internal-Colored")); + + lineRenderer = new GameObject().GetComponent(); + + this.lineSize = lineSize; + } + + private void init(float lineSize = 0.2f) + { + if (lineRenderer == null) + { + //GameObject lineObj = new GameObject("LineObj"); + //lineRenderer = lineObj.AddComponent(); + + //Particles/Additive + //lineRenderer.material = new Material(Shader.Find("Hidden/Internal-Colored")); + + lineRenderer = new GameObject().GetComponent(); + + this.lineSize = lineSize; + } + } + + //Draws lines through the provided vertices + public void DrawLineInGameView(Vector3 start, Vector3 end, Color color) + { + if (lineRenderer == null) + { + init(0.2f); + } + + //Set color + lineRenderer.startColor = color; + lineRenderer.endColor = color; + + //Set width + lineRenderer.startWidth = lineSize; + lineRenderer.endWidth = lineSize; + + //Set line count which is 2 + lineRenderer.positionCount = 2; + + //Set the postion of both two lines + lineRenderer.SetPosition(0, start); + lineRenderer.SetPosition(1, end); + } + + public void Destroy() + { + if (lineRenderer != null) + { + //UnityEngine.Object.Destroy(lineRenderer.gameObject); + } + } + } + */ } diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index 5cd1741..b0b6e86 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -29,6 +29,8 @@ namespace UnityExplorer public static HarmonyLib.Harmony Harmony { get; } = new HarmonyLib.Harmony(GUID); + public static CatmullRom CameraPathsManager = null; + /// /// Initialize UnityExplorer with the provided Loader implementation. /// @@ -83,6 +85,9 @@ namespace UnityExplorer { UIManager.ShowMenu = !UIManager.ShowMenu; } + + if(CameraPathsManager != null) + CameraPathsManager.MaybeRunPath(); } diff --git a/src/UI/Panels/CamPaths.cs b/src/UI/Panels/CamPaths.cs new file mode 100644 index 0000000..32e2302 --- /dev/null +++ b/src/UI/Panels/CamPaths.cs @@ -0,0 +1,88 @@ +using UniverseLib.Input; +using UniverseLib.UI; +using UniverseLib.UI.Models; +#if UNHOLLOWER +using UnhollowerRuntimeLib; +#endif +#if INTEROP +using Il2CppInterop.Runtime.Injection; +#endif + +using System.Collections.Generic; +using UnityEngine; +using System; +using System.Collections; + +namespace UnityExplorer.UI.Panels +{ + public class CamPaths : UEPanel + { + public CamPaths(UIBase owner) : base(owner) + { + } + + public override string Name => "CamPaths"; + public override UIManager.Panels PanelType => UIManager.Panels.CamPaths; + public override int MinWidth => 400; + public override int MinHeight => 600; + public override Vector2 DefaultAnchorMin => new(0.4f, 0.4f); + public override Vector2 DefaultAnchorMax => new(0.6f, 0.6f); + public override bool NavButtonWanted => true; + public override bool ShouldSaveActiveState => true; + + static ButtonRef startButton; + public List controlPoints = new List(); + + // ~~~~~~~~ UI construction / callbacks ~~~~~~~~ + + protected override void ConstructPanelContent() + { + startButton = UIFactory.CreateButton(ContentRoot, "Start", "Start CamPath"); + UIFactory.SetLayoutElement(startButton.GameObject, minWidth: 150, minHeight: 25, flexibleWidth: 9999); + startButton.OnClick += StartButton_OnClick; + + startButton = UIFactory.CreateButton(ContentRoot, "AddCamNode", "Add Cam node"); + UIFactory.SetLayoutElement(startButton.GameObject, minWidth: 150, minHeight: 25, flexibleWidth: 9999); + startButton.OnClick += AddNode_OnClick; + + } + + void AddSpacer(int height) + { + GameObject obj = UIFactory.CreateUIObject("Spacer", ContentRoot); + UIFactory.SetLayoutElement(obj, minHeight: height, flexibleHeight: 0); + } + + void StartButton_OnClick() + { + int resolution = 500; + bool closedLoop = false; + //float normalExtrusion = 0; + //float tangentExtrusion = 0; + + if(ExplorerCore.CameraPathsManager == null) + ExplorerCore.CameraPathsManager = new CatmullRom(controlPoints.ToArray(), resolution, closedLoop); + else{ + ExplorerCore.CameraPathsManager.Update(controlPoints.ToArray()); + ExplorerCore.CameraPathsManager.Update(resolution, closedLoop); + } + + ExplorerCore.Log("Iniciamos path."); + ExplorerCore.CameraPathsManager.StartPath(); + + ExplorerCore.Log("Clicked start path button."); + } + + private Transform NewTransform(Transform original){ + GameObject ob = new GameObject("CamPathNode"); + ob.transform.position = original.position; + //ob.transform.rotation = original.rotation; + return ob.transform; + } + + void AddNode_OnClick(){ + Camera freeCam = FreeCamPanel.ourCamera; + controlPoints.Add(NewTransform(freeCam.transform)); + } + } +} diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index 6a59f21..d3acbe6 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -23,7 +23,8 @@ namespace UnityExplorer.UI HookManager, Clipboard, Freecam, - LightsManager + LightsManager, + CamPaths } public enum VerticalAnchor @@ -45,7 +46,7 @@ namespace UnityExplorer.UI public static RectTransform NavBarRect; public static GameObject NavbarTabButtonHolder; - private static readonly Vector2 NAVBAR_DIMENSIONS = new(1150f, 35f); + private static readonly Vector2 NAVBAR_DIMENSIONS = new(1250f, 35f); private static ButtonRef closeBtn; private static TimeScaleWidget timeScaleWidget; @@ -92,6 +93,7 @@ namespace UnityExplorer.UI UIPanels.Add(Panels.HookManager, new HookManagerPanel(UiBase)); UIPanels.Add(Panels.Freecam, new FreeCamPanel(UiBase)); UIPanels.Add(Panels.LightsManager, new LightsManager(UiBase)); + UIPanels.Add(Panels.CamPaths, new CamPaths(UiBase)); UIPanels.Add(Panels.Clipboard, new ClipboardPanel(UiBase)); UIPanels.Add(Panels.ConsoleLog, new LogPanel(UiBase)); UIPanels.Add(Panels.Options, new OptionsPanel(UiBase));