diff --git a/src/ExplorerBehaviour.cs b/src/ExplorerBehaviour.cs index 497fa4e..f508fc2 100644 --- a/src/ExplorerBehaviour.cs +++ b/src/ExplorerBehaviour.cs @@ -132,483 +132,4 @@ namespace UnityExplorer } } } - - /* - 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 Quaternion rotation; - public float fov; - - public CatmullRomPoint(Vector3 position, Quaternion rotation, float fov) - { - this.position = position; - this.rotation = rotation; - this.fov = fov; - } - } - - [System.Serializable] - public struct PathControlPoint - { - public Vector3 position; - public Quaternion rotation; - public float fov; - public int frames; - - public PathControlPoint(Vector3 position, Quaternion rotation, float fov, int frames = 500) - { - this.position = position; - this.rotation = rotation; - this.fov = fov; - this.frames = frames; - } - } - - private bool closedLoop; - - private CatmullRomPoint[] splinePoints; //Generated spline points - - private PathControlPoint[] controlPoints; - public bool playingPath; - int currentPoint = 0; - - //Returns spline points. Count is the summation of all the node frames (it excludes the last node frames if path isn't a closed loop). - public CatmullRomPoint[] GetPoints() - { - if(splinePoints == null) - { - throw new System.NullReferenceException("Spline not Initialized!"); - } - - return splinePoints; - } - - public CatmullRom(PathControlPoint[] controlPoints, bool closedLoop) - { - if(controlPoints == null || controlPoints.Length <= 2) - { - ExplorerCore.Log(controlPoints); - throw new ArgumentException("Catmull Rom Error: Too few control points!"); - } - - this.controlPoints = controlPoints; - this.closedLoop = closedLoop; - - GenerateSplinePoints(); - } - - //Updates control points - public void Update(PathControlPoint[] controlPoints) - { - if(controlPoints.Length <= 0 || controlPoints == null) - { - throw new ArgumentException("Invalid control points"); - } - - this.controlPoints = controlPoints; - - GenerateSplinePoints(); - } - - //Updates closed loop values - public void Update(bool closedLoop) - { - this.closedLoop = closedLoop; - - GenerateSplinePoints(); - } - - //Draws a line between every point and the next. - public void DrawSpline(Color color) - { - if(ValidatePoints()) - { - - for(int i = 0; i < splinePoints.Length; i++) - { - if(i == splinePoints.Length - 1 && closedLoop) - { - Debug.DrawLine(splinePoints[i].position, splinePoints[0].position, color); - } - else if(i < splinePoints.Length - 1) - { - Debug.DrawLine(splinePoints[i].position, splinePoints[i+1].position, 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 total amount of frames. - private void InitializeProperties() - { - int pointsToCreate = 0; - if (closedLoop) - { - for (var i = 0; i < controlPoints.Length; i++) - { - pointsToCreate += controlPoints[i].frames;//Loops back to the beggining, so no need to adjust for arrays starting at 0 - } - } - else - { - for (var i = 0; i < controlPoints.Length - 1; i++) - { - pointsToCreate += controlPoints[i].frames; - } - } - - //if(splinePoints != null) - // Array.Clear(splinePoints, 0, splinePoints.Length); - - splinePoints = new CatmullRomPoint[pointsToCreate]; - } - - public void MaybeRunPath(){ - if(playingPath){ - if(ExplorerCore.CameraPathsManager != null && currentPoint < splinePoints.Length){ - CatmullRomPoint point = splinePoints[currentPoint]; - - //ExplorerCore.Log($"velocity: {Vector3.Distance(point.position,FreeCamPanel.ourCamera.transform.position)}/node"); - - FreeCamPanel.ourCamera.transform.position = point.position; - FreeCamPanel.ourCamera.transform.rotation = point.rotation; - FreeCamPanel.ourCamera.fieldOfView = point.fov; - currentPoint++; - if(currentPoint >= splinePoints.Length) - playingPath = false; - } - } - } - - public void StartPath(){ - currentPoint = 0; - playingPath = true; - } - - public void Stop(){ - currentPoint = 0; - playingPath = false; - } - - public void Pause(){ - playingPath = false; - } - - public void Continue(){ - if(currentPoint < ExplorerCore.CameraPathsManager.GetPoints().Length) - playingPath = true; - } - - private void GenerateSplinePoints() - { - InitializeProperties(); - - Vector3 p0, p1, p2, p3; //Previous position, Start position, end position, Next position - Vector4 r0, r1, r2, r3; //Previous rotation, Start rotation, end rotation, Next rotation - float fov0, fov1; - - //Fix rotation - /* - for(int i = 0; i < controlPoints.Length; i++){ - if(i>0 && Dot(controlPoints[i - 1].rotation, controlPoints[i].rotation) < 0){ - Quaternion q = controlPoints[i].rotation; - controlPoints[i].rotation = new Quaternion(- q.x, - q.y, - q.z, - q.w); - ExplorerCore.Log($"cambio orientacion n° {i}"); - } - } - */ - - // 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; - int totalframes = 0;//count the total number of frames so we can save the points in the right splinePoints position - - for (int currentPoint = 0; currentPoint < controlPoints.Length - closedAdjustment; currentPoint++) - { - bool closedLoopFinalPoint = (closedLoop && currentPoint == controlPoints.Length - 1); - - //Had tu add "controlPoints.Length" because C# mod doesnt work well with negative values - int previousPoint = closedLoop ? (currentPoint + controlPoints.Length - 1)%(controlPoints.Length) : System.Math.Max(currentPoint - 1, 0); - int endSegmentPoint = closedLoop ? (currentPoint + 1)%(controlPoints.Length) : System.Math.Min(currentPoint + 1, controlPoints.Length - 1); - int nextPoint = closedLoop ? (currentPoint + 2)%(controlPoints.Length) : System.Math.Min(currentPoint + 2, controlPoints.Length - 1); - - p0 = controlPoints[previousPoint].position; - r0 = QuaternionToVector4(controlPoints[previousPoint].rotation); - - p1 = controlPoints[currentPoint].position; - r1 = QuaternionToVector4(controlPoints[currentPoint].rotation); - - p2 = controlPoints[endSegmentPoint].position; - r2 = QuaternionToVector4(controlPoints[endSegmentPoint].rotation); - - p3 = controlPoints[nextPoint].position; - r3 = QuaternionToVector4(controlPoints[nextPoint].rotation); - - //Check if we are using the shortest path on the rotation. If not, change r1 to represent that shortest path. - if (Vector4.Dot(r0, r1) < 0) - r1 = - r1; - - if (Vector4.Dot(r1, r2) < 0) - r2 = - r2; - - if (Vector4.Dot(r2, r3) < 0) - r3 = - r3; - - fov0 = controlPoints[currentPoint].fov; - fov1 = controlPoints[endSegmentPoint].fov; - - int frames = controlPoints[currentPoint].frames; //resolution and time the node takes to get to the other node - float pointStep = 1.0f / frames; - - if ((currentPoint == controlPoints.Length - 2 && !closedLoop) || closedLoopFinalPoint) //Final point - { - pointStep = 1.0f / (frames - 1); // last point of last segment should reach p1 - } - - // Creates [frames] points between this control point and the next - for (int tesselatedPoint = 0; tesselatedPoint < frames; tesselatedPoint++) - { - float t = tesselatedPoint * pointStep; - - CatmullRomPoint point = Evaluate(p0, p1, p2, p3, r0, r1, r2, r3, fov0, fov1, t); - - splinePoints[totalframes + tesselatedPoint] = point; - } - totalframes+=frames; - } - } - - static CatmullRomPoint Evaluate(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Vector4 r0, Vector4 r1, Vector4 r2, Vector4 r3, float fovStart, float fovEnd, float t) - { - Vector3 position = CatmullRomInterpolation(p0, p1, p2, p3, t); - Vector4 v4rot = CatmullRomInterpolation(r0, r1, r2, r3, t); - Quaternion rotation = new Quaternion(v4rot.x, v4rot.y, v4rot.z, v4rot.w); - float fov = Mathf.SmoothStep(fovStart, fovEnd, t); - - return new CatmullRomPoint(position, rotation, fov); - } - - //Implementation from: https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html - static Vector3 CatmullRomInterpolation(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t){ - float tension = 0; - float alpha = 0.5f; - - float t01 = (float) System.Math.Pow((double) Vector3.Distance(p0, p1), (double) alpha); - float t12 = (float) System.Math.Pow((double) Vector3.Distance(p1, p2), (double) alpha); - float t23 = (float) System.Math.Pow((double) Vector3.Distance(p2, p3), (double) alpha); - - Vector3 v1 = t01 != 0 ? (p1 - p0) / t01 : new Vector3(0, 0, 0); - Vector3 v2 = t23 != 0 ? (p3 - p2) / t23 : new Vector3(0, 0, 0); - - Vector3 m1 = (1.0f - tension) * (p2 - p1 + t12 * (v1 - (p2 -p0) / (t01 + t12))); - Vector3 m2 = (1.0f - tension) * (p2 - p1 + t12 * (v2 - (p3 -p1) / (t12 + t23))); - - Vector3 a = 2.0f * (p1 - p2) + m1 + m2; - Vector3 b = -3.0f * (p1 - p2) - m1 - m1 - m2; - Vector3 c = m1; - Vector3 d = p1; - - return a * t * t * t + b * t * t + c * t + d; - } - - static Vector4 CatmullRomInterpolation(Vector4 r0, Vector4 r1, Vector4 r2, Vector4 r3, float t){ - float tension = 0; - float alpha = 0.5f; - - float t01 = (float) System.Math.Pow((double) Vector4.Distance(r0, r1), (double) alpha); - float t12 = (float) System.Math.Pow((double) Vector4.Distance(r1, r2), (double) alpha); - float t23 = (float) System.Math.Pow((double) Vector4.Distance(r2, r3), (double) alpha); - - Vector4 v1 = t01 != 0 ? (r1 - r0) / t01 : new Vector4(0, 0, 0, 0); - Vector4 v2 = t23 != 0 ? (r3 - r2) / t23 : new Vector4(0, 0, 0, 0); - - Vector4 m1 = (1.0f - tension) * (r2 - r1 + t12 * (v1 - (r2 -r0) / (t01 + t12))); - Vector4 m2 = (1.0f - tension) * (r2 - r1 + t12 * (v2 - (r3 -r1) / (t12 + t23))); - - Vector4 a = 2.0f * (r1 - r2) + m1 + m2; - Vector4 b = -3.0f * (r1 - r2) - m1 - m1 - m2; - Vector4 c = m1; - Vector4 d = r1; - - return a * t * t * t + b * t * t + c * t + d; - //return Vector4.Lerp(r1, r2, t); - } - - static Vector3 CatmullRomAlt(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) - { - float t2 = t * t; - float t3 = t2 * t; - - Vector3 v0 = (p2 - p0) * 0.5f; - Vector3 v1 = (p3 - p1) * 0.5f; - - float h1 = 2 * t3 - 3 * t2 + 1; - float h2 = -2 * t3 + 3 * t2; - float h3 = t3 - 2 * t2 + t; - float h4 = t3 - t2; - - Vector3 interpolatedPoint = h1 * p1 + h2 * p2 + h3 * v0 + h4 * v1; - return interpolatedPoint; - } - - static Vector4 CatmullRomAlt(Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3, float t) - { - float t2 = t * t; - float t3 = t2 * t; - - Vector4 v0 = (p2 - p0) * 0.5f; - Vector4 v1 = (p3 - p1) * 0.5f; - - float h1 = 2 * t3 - 3 * t2 + 1; - float h2 = -2 * t3 + 3 * t2; - float h3 = t3 - 2 * t2 + t; - float h4 = t3 - t2; - - Vector4 interpolatedPoint = h1 * p1 + h2 * p2 + h3 * v0 + h4 * v1; - return interpolatedPoint; - } - - //Used for calculating path length for constant speed paths - public float[] GenerateSplinePointsByRes(int resolution = 3000) //resolution of each segment - { - List splineSegmentDistances = new List(); - - 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].position; - - if(closedLoopFinalPoint) - { - p1 = controlPoints[0].position; - } - else - { - p1 = controlPoints[currentPoint + 1].position; - } - - // m0 - if (currentPoint == 0) // Tangent M[k] = (P[k+1] - P[k-1]) / 2 - { - if(closedLoop) - { - m0 = p1 - controlPoints[controlPoints.Length - 1].position; - } - else - { - m0 = p1 - p0; - } - } - else - { - m0 = p1 - controlPoints[currentPoint - 1].position; - } - - // m1 - if (closedLoop) - { - if (currentPoint == controlPoints.Length - 1) //Last point case - { - m1 = controlPoints[(currentPoint + 2) % controlPoints.Length].position - p0; - } - else if (currentPoint == 0) //First point case - { - m1 = controlPoints[currentPoint + 2].position - p0; - } - else - { - m1 = controlPoints[(currentPoint + 2) % controlPoints.Length].position - p0; - } - } - else - { - if (currentPoint < controlPoints.Length - 2) - { - m1 = controlPoints[(currentPoint + 2) % controlPoints.Length].position - 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 - } - - Vector3 pos1; - Vector3 pos0 = new Vector3(0, 0, 0); //simply initialization so the compiler doesnt complain. - float segmentDistance = 0; - - // Creates [resolution] points between this control point and the next - for (int tesselatedPoint = 1; tesselatedPoint < resolution; tesselatedPoint++) - { - if(tesselatedPoint == 1) - pos0 = CalculatePosition(p0, p1, m0, m1, 0); - - float t = tesselatedPoint * pointStep; - pos1 = CalculatePosition(p0, p1, m0, m1, t); - segmentDistance += Vector3.Distance(pos0, pos1); - - pos0 = pos1; - } - - splineSegmentDistances.Add(segmentDistance); - } - - return splineSegmentDistances.ToArray(); - } - - //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; - } - - static Vector4 QuaternionToVector4(Quaternion q){ - return new Vector4(q.x, q.y, q.z, q.w); - } - } } diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index f060a81..b2407a1 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -7,6 +7,7 @@ global using UnityEngine; global using UnityEngine.UI; global using UniverseLib; global using UniverseLib.Utility; +using UnityExplorer.CatmullRom; using UnityExplorer.Config; using UnityExplorer.ObjectExplorer; using UnityExplorer.Runtime; @@ -29,8 +30,6 @@ namespace UnityExplorer public static HarmonyLib.Harmony Harmony { get; } = new HarmonyLib.Harmony(GUID); - public static CatmullRom CameraPathsManager = null; - /// /// Initialize UnityExplorer with the provided Loader implementation. /// @@ -59,7 +58,7 @@ namespace UnityExplorer UnityCrashPrevention.Init(); //Extra inits - KeypressListener.Setup(); + KeypressListener.Setup(); } // Do a delayed setup so that objects aren't destroyed instantly. @@ -85,9 +84,6 @@ 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 index 87b53aa..3ecd30b 100644 --- a/src/UI/Panels/CamPaths.cs +++ b/src/UI/Panels/CamPaths.cs @@ -21,20 +21,18 @@ namespace UnityExplorer.UI.Panels { } - public override string Name => "CamPaths"; + public override string Name => "Cam Paths"; public override UIManager.Panels PanelType => UIManager.Panels.CamPaths; - public override int MinWidth => 1000; + public override int MinWidth => 600; 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; - public List controlPoints = new List(); + public List controlPoints = new List(); List UINodes = new List(); bool closedLoop; - - bool constantSpeed; - int pathTotalFrames = 2000; + float speed = 10; // ~~~~~~~~ UI construction / callbacks ~~~~~~~~ @@ -44,24 +42,27 @@ namespace UnityExplorer.UI.Panels default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft); UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleWidth: 9999); - ButtonRef startButton = UIFactory.CreateButton(horiGroup, "Start", "Start CamPath"); - UIFactory.SetLayoutElement(startButton.GameObject, minWidth: 150, minHeight: 25, flexibleWidth: 9999); + ButtonRef startButton = UIFactory.CreateButton(horiGroup, "Start", "▶"); + UIFactory.SetLayoutElement(startButton.GameObject, minWidth: 50, minHeight: 25); + startButton.ButtonText.fontSize = 20; startButton.OnClick += StartButton_OnClick; - ButtonRef pauseContinueButton = UIFactory.CreateButton(horiGroup, "Pause/Continue", "Pause/Continue"); - UIFactory.SetLayoutElement(pauseContinueButton.GameObject, minWidth: 150, minHeight: 25, flexibleWidth: 9999); - pauseContinueButton.OnClick += PauseContinue_OnClick; + ButtonRef pauseContinueButton = UIFactory.CreateButton(horiGroup, "Pause/Continue", "❚❚/▶"); + UIFactory.SetLayoutElement(pauseContinueButton.GameObject, minWidth: 50, minHeight: 25); + pauseContinueButton.ButtonText.fontSize = 20; + pauseContinueButton.OnClick += TogglePause_OnClick; - ButtonRef stopButton = UIFactory.CreateButton(horiGroup, "Stop", "Stop CamPath"); - UIFactory.SetLayoutElement(stopButton.GameObject, minWidth: 150, minHeight: 25, flexibleWidth: 9999); + ButtonRef stopButton = UIFactory.CreateButton(horiGroup, "Stop", "■"); + UIFactory.SetLayoutElement(stopButton.GameObject, minWidth: 50, minHeight: 25); + stopButton.ButtonText.fontSize = 20; stopButton.OnClick += Stop_OnClick; ButtonRef AddNode = UIFactory.CreateButton(horiGroup, "AddCamNode", "Add Cam node"); - UIFactory.SetLayoutElement(AddNode.GameObject, minWidth: 150, minHeight: 25, flexibleWidth: 9999); + UIFactory.SetLayoutElement(AddNode.GameObject, minWidth: 150, minHeight: 25); AddNode.OnClick += AddNode_OnClick; ButtonRef DeletePath = UIFactory.CreateButton(horiGroup, "DeletePath", "Delete Path"); - UIFactory.SetLayoutElement(DeletePath.GameObject, minWidth: 150, minHeight: 25, flexibleWidth: 9999); + UIFactory.SetLayoutElement(DeletePath.GameObject, minWidth: 150, minHeight: 25); DeletePath.OnClick += () => {controlPoints.Clear(); UpdateListNodes();}; Toggle closedLoopToggle = new Toggle(); @@ -71,16 +72,9 @@ namespace UnityExplorer.UI.Panels closedLoopToggle.isOn = false; toggleClosedLoopText.text = "Close path in a loop"; - Toggle constantspeedToggle = new Toggle(); - GameObject toggleConstantSpeedObj = UIFactory.CreateToggle(horiGroup, "Constant speed", out constantspeedToggle, out Text toggleConstantSpeedText); - UIFactory.SetLayoutElement(toggleConstantSpeedObj, minHeight: 25, flexibleWidth: 9999); - constantspeedToggle.onValueChanged.AddListener((isConstantSpeed) => {constantSpeed = isConstantSpeed; UpdateListNodes();}); - constantspeedToggle.isOn = false; - toggleConstantSpeedText.text = "Constant speed"; - - InputFieldRef CompletePathFramesInput = null; - AddInputField("Frames", "Constant speed complete path duration (in frames):", "Default: 2000", out CompletePathFramesInput, TotalFrames_OnEndEdit, false); - CompletePathFramesInput.Text = pathTotalFrames.ToString(); + InputFieldRef SpeedInput = null; + AddInputField("Speed", "Path speed:", $"Default: {speed}", out SpeedInput, Speed_OnEndEdit, false); + SpeedInput.Text = speed.ToString(); } private void UpdateListNodes(){ @@ -91,12 +85,12 @@ namespace UnityExplorer.UI.Panels } for(int i = 0; i < controlPoints.Count; i++) { - CatmullRom.PathControlPoint point = controlPoints[i]; + CatmullRom.CatmullRomPoint point = controlPoints[i]; DrawNodeOptions(point, i); } } - private void DrawNodeOptions(CatmullRom.PathControlPoint point, int index){ + 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); UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleWidth: 9999); @@ -121,32 +115,9 @@ namespace UnityExplorer.UI.Panels UIFactory.SetLayoutElement(destroyButton.GameObject, minWidth: 80, minHeight: 25, flexibleWidth: 9999); destroyButton.OnClick += () => {controlPoints.Remove(point); UpdateListNodes();}; - //Frames - if (!constantSpeed){ - InputFieldRef framesInput = null; - AddInputField("Frames", "Length (in frames):", $"Default: {point.frames}", out framesInput, (frames) => { FramesInput_OnEndEdit(point, index, frames); }); - framesInput.Text = point.frames.ToString(); - } - //ShowPos and rot? } - void PauseContinue_OnClick(){ - if(ExplorerCore.CameraPathsManager.playingPath) - ExplorerCore.CameraPathsManager.Pause(); - else - ExplorerCore.CameraPathsManager.Continue(); - } - - void Stop_OnClick(){ - CatmullRom.PathControlPoint point = controlPoints[0]; - ExplorerCore.CameraPathsManager.Stop(); - - FreeCamPanel.ourCamera.transform.position = point.position; - FreeCamPanel.ourCamera.transform.rotation = point.rotation; - FreeCamPanel.ourCamera.fieldOfView = point.fov; - } - GameObject AddInputField(string name, string labelText, string placeHolder, out InputFieldRef inputField, Action onInputEndEdit, bool shouldDeleteOnUpdate = true) { GameObject row = UIFactory.CreateHorizontalGroup(ContentRoot, "Editor Field", @@ -155,10 +126,10 @@ namespace UnityExplorer.UI.Panels UINodes.Add(row); //To delete it when we update the node list Text posLabel = UIFactory.CreateLabel(row, $"{name}_Label", labelText); - UIFactory.SetLayoutElement(posLabel.gameObject, minWidth: 100, minHeight: 25); + UIFactory.SetLayoutElement(posLabel.gameObject, minWidth: 80, minHeight: 25); inputField = UIFactory.CreateInputField(row, $"{name}_Input", placeHolder); - UIFactory.SetLayoutElement(inputField.GameObject, minWidth: 125, minHeight: 25, flexibleWidth: 9999); + UIFactory.SetLayoutElement(inputField.GameObject, minWidth: 50, minHeight: 25); inputField.Component.GetOnEndEdit().AddListener(onInputEndEdit); return row; @@ -166,78 +137,52 @@ namespace UnityExplorer.UI.Panels void StartButton_OnClick() { - //float normalExtrusion = 0; - //float tangentExtrusion = 0; - - if(ExplorerCore.CameraPathsManager == null) - ExplorerCore.CameraPathsManager = new CatmullRom(controlPoints.ToArray(), closedLoop); - else{ - ExplorerCore.CameraPathsManager.Update(closedLoop); - ExplorerCore.CameraPathsManager.Update(controlPoints.ToArray()); + if(GetCameraPathsManager()){ + GetCameraPathsManager().setClosedLoop(closedLoop); + GetCameraPathsManager().setSplinePoints(controlPoints.ToArray()); + GetCameraPathsManager().setSpeed(speed); + GetCameraPathsManager().StartPath(); } - - if(constantSpeed){ - UpdateNodeFramesConstantspeed(); - } - - ExplorerCore.CameraPathsManager.StartPath(); } - void FramesInput_OnEndEdit(CatmullRom.PathControlPoint point, int index, string input){ - EventSystemHelper.SetSelectedGameObject(null); - - if (!ParseUtility.TryParse(input, out int parsed, out Exception parseEx)) - { - ExplorerCore.LogWarning($"Could not parse value: {parseEx.ReflectionExToString()}"); - //ui_input.Text = point.frames.ToString(); - return; + void TogglePause_OnClick(){ + if(GetCameraPathsManager()){ + GetCameraPathsManager().TogglePause(); } + } - int frames = parsed; - - if(frames > 1){ - point.frames = frames; - controlPoints[index] = point; + void Stop_OnClick(){ + if (GetCameraPathsManager()){ + GetCameraPathsManager().Stop(); } - else - throw new ArgumentException("Catmull Rom Error: Too few frames!"); } void AddNode_OnClick(){ Camera freeCam = FreeCamPanel.ourCamera; - CatmullRom.PathControlPoint point = new CatmullRom.PathControlPoint(freeCam.transform.position, freeCam.transform.rotation, freeCam.fieldOfView); + CatmullRom.CatmullRomPoint point = new CatmullRom.CatmullRomPoint(freeCam.transform.position, freeCam.transform.rotation, freeCam.fieldOfView); + ExplorerCore.LogWarning($"Added point: {point.position}"); controlPoints.Add(point); - UpdateListNodes(); } - void UpdateNodeFramesConstantspeed(){ - float[] d = ExplorerCore.CameraPathsManager.GenerateSplinePointsByRes(3000); - float dTotal = d.Sum(); - int closedAdjustment = closedLoop ? 0 : 1; - - for(int i = 0; i < controlPoints.Count - closedAdjustment; i++) { - CatmullRom.PathControlPoint point = controlPoints[i]; - point.frames = (int)(pathTotalFrames *(d[i] / dTotal)); - controlPoints[i] = point; - } - - ExplorerCore.CameraPathsManager.Update(controlPoints.ToArray()); - } - - - void TotalFrames_OnEndEdit(string input) + void Speed_OnEndEdit(string input) { EventSystemHelper.SetSelectedGameObject(null); if (!ParseUtility.TryParse(input, out int parsed, out Exception parseEx)) { ExplorerCore.LogWarning($"Could not parse value: {parseEx.ReflectionExToString()}"); - //pathTotalFrames.Text = pathTotalFrames.ToString(); return; } - pathTotalFrames = parsed; + speed = parsed; + if(GetCameraPathsManager()){ + GetCameraPathsManager().setSpeed(speed); + } + } + + private CatmullRom.CatmullRomMover GetCameraPathsManager(){ + return FreeCamPanel.cameraPathMover; } } } diff --git a/src/UI/Panels/FreeCamPanel.cs b/src/UI/Panels/FreeCamPanel.cs index f86621d..ec5792a 100644 --- a/src/UI/Panels/FreeCamPanel.cs +++ b/src/UI/Panels/FreeCamPanel.cs @@ -31,6 +31,7 @@ namespace UnityExplorer.UI.Panels public static Camera ourCamera; internal static Camera lastMainCamera; internal static FreeCamBehaviour freeCamScript; + internal static CatmullRom.CatmullRomMover cameraPathMover; internal static float desiredMoveSpeed = 5f; @@ -121,6 +122,9 @@ namespace UnityExplorer.UI.Panels if (!freeCamScript) freeCamScript = ourCamera.gameObject.AddComponent(); + if (!cameraPathMover) + cameraPathMover = ourCamera.gameObject.AddComponent(); + ourCamera.transform.position = (Vector3)currentUserCameraPosition; ourCamera.transform.rotation = (Quaternion)currentUserCameraRotation; @@ -156,6 +160,12 @@ namespace UnityExplorer.UI.Panels freeCamScript = null; } + if (cameraPathMover) + { + GameObject.Destroy(cameraPathMover); + cameraPathMover = null; + } + if (lastMainCamera) lastMainCamera.enabled = true; } diff --git a/src/UI/Widgets/CatmullRom.cs b/src/UI/Widgets/CatmullRom.cs new file mode 100644 index 0000000..493be93 --- /dev/null +++ b/src/UI/Widgets/CatmullRom.cs @@ -0,0 +1,365 @@ +using UnityExplorer.UI.Panels; + +using System; +using System.Threading; + +#if UNHOLLOWER +using UnhollowerRuntimeLib; +#endif +#if INTEROP +using Il2CppInterop.Runtime.Injection; +#endif + +namespace UnityExplorer.CatmullRom +{ + // Struct to keep position, rotation and fov of a spline point + [System.Serializable] + public struct CatmullRomPoint + { + public Vector3 position; + public Quaternion rotation; + public float fov; + + public CatmullRomPoint(Vector3 position, Quaternion rotation, float fov) + { + this.position = position; + this.rotation = rotation; + this.fov = fov; + } + + public CatmullRomPoint(Vector3 position, Vector4 rotation, float fov) + { + this.position = position; + this.rotation = Vector4ToQuaternion(rotation); + this.fov = fov; + } + + public static Vector4 QuaternionToVector4(Quaternion q){ + return new Vector4(q.x, q.y, q.z, q.w); + } + + public static Quaternion Vector4ToQuaternion(Vector4 v){ + return new Quaternion(v.x, v.y, v.z, v.w); + } + + public static CatmullRomPoint operator +(CatmullRomPoint a, CatmullRomPoint b) + { + Vector4 ra = QuaternionToVector4(a.rotation); + Vector4 rb = QuaternionToVector4(b.rotation); + + Vector4 newRot = ra + rb; + return new CatmullRomPoint(a.position + b.position, Vector4ToQuaternion(newRot), a.fov + b.fov); // what to do with the fov?? + } + + public static CatmullRomPoint operator -(CatmullRomPoint a, CatmullRomPoint b) + { + Vector4 newRot = QuaternionToVector4(a.rotation) - QuaternionToVector4(b.rotation); + return new CatmullRomPoint(a.position - b.position, Vector4ToQuaternion(newRot), a.fov - b.fov); // what to do with the fov?? + } + + public static CatmullRomPoint operator /(CatmullRomPoint a, float b) + { + //Vector4 newRot = QuaternionToVector4(a.rotation)/b; + //return new CatmullRomPoint(a.position/b, Vector4ToQuaternion(newRot), a.fov / b); // what to do with the fov?? + return new CatmullRomPoint(a.position/b, a.rotation, a.fov); + } + + public static CatmullRomPoint operator *(CatmullRomPoint a, float b) + { + //Vector4 newRot = QuaternionToVector4(a.rotation)*b; + //return new CatmullRomPoint(a.position*b, Vector4ToQuaternion(newRot), a.fov * b); // what to do with the fov?? + return new CatmullRomPoint(a.position*b, a.rotation, a.fov); // what to do with the fov?? + } + + public CatmullRomPoint Normalize() + { + position.Normalize(); + return this; + } + } + + public class CatmullRomMover : MonoBehaviour + { +#if CPP + static CatmullRomMover() + { + ClassInjector.RegisterTypeInIl2Cpp(); + } + + public CatmullRomMover(IntPtr ptr) : base(ptr) { } +#endif + + bool playingPath; + private bool closedLoop; + private CatmullRomPoint[] splinePoints; + // The point the camera will be following along the path + private CatmullRomPoint lookahead; + // How smooth the path is (beware of not making too many calculations per update tho) + float lookaheadDelta; + float speed; + // Current position in the path, from 0 to 1 + float delta; + + public CatmullRomMover(){ + splinePoints = new CatmullRomPoint[] { }; + speed = 10; + lookaheadDelta = 0.01f; + delta = lookaheadDelta; + } + + public void StartPath(){ + if(splinePoints == null || splinePoints.Length <= 2) + { + ExplorerCore.Log(splinePoints); + throw new ArgumentException("Catmull Rom Error: Too few spline points!"); + } + else { + playingPath = true; + MoveCameraToPoint(splinePoints[0]); + lookahead = GetPointFromPath(delta); + } + } + + public void TogglePause(){ + playingPath = !playingPath; + } + + public void Stop(){ + playingPath = false; + delta = lookaheadDelta; + lookahead = GetPointFromPath(delta); + MoveCameraToPoint(splinePoints[0]); + } + + public void setClosedLoop(bool newClosedLoop){ + closedLoop = newClosedLoop; + ExplorerCore.LogWarning("Update closedLoop!"); + } + + public void setSplinePoints(CatmullRomPoint[] newSplinePoints){ + splinePoints = newSplinePoints; + ExplorerCore.LogWarning("Update spline points!"); + } + + public void setSpeed(float newSpeed){ + speed = newSpeed; + lookaheadDelta = newSpeed / 1000; + } + + public void Update(){ + if (delta >= 1 && FreeCamPanel.ourCamera != null){ + playingPath = false; + delta = lookaheadDelta; + } + if (playingPath) AdvanceMover(Time.deltaTime); + } + + /* + private CatmullRomPoint GetPointFromPath(float d){ + //ExplorerCore.LogWarning(splinePoints.Length); + int currentSegment = (int)(splinePoints.Length * d); // have to do + 1 if closed loop + //ExplorerCore.LogWarning(currentSegment); + CatmullRomPoint lastPointInPath = splinePoints[Math.Min(currentSegment, splinePoints.Length - 1)]; + CatmullRomPoint nextPointInPath = splinePoints[Math.Min(currentSegment + 1, splinePoints.Length - 1)]; + + float interpolationBetweenPaths = (d - (float)currentSegment / (float)splinePoints.Length) * (float)splinePoints.Length; + // Dumb Lerp to test things. Should use proper CatmullRom instead. + return lastPointInPath * (1 - interpolationBetweenPaths) + nextPointInPath * interpolationBetweenPaths; + } + */ + + private CatmullRomPoint GetCurrentPoint(){ + return new CatmullRomPoint(FreeCamPanel.ourCamera.transform.position, FreeCamPanel.ourCamera.transform.rotation, FreeCamPanel.ourCamera.fieldOfView); + } + + private void AdvanceMover(float dt){ + // We use 0.01665f (60fps) in place of Time.DeltaTime so the camera moves at fullspeed even in slow motion. + float move = 0.01665f * speed; // units to move + while (move > 0.0) { + CatmullRomPoint currentPoint = GetCurrentPoint(); + // distance between target and current position + float room = Vector3.Distance(lookahead.position, currentPoint.position); + + // how much we're actually moving this iteration + // move the whole distance to the lookahead, or would the move cut us short + float actual = Math.Min(move, room); + + MoveCameraStep(currentPoint, actual, room); + + // update move to be the remaining amount we need to move + move -= actual; + //ExplorerCore.LogWarning($"move: {move}, actual: {actual}, room: {room}, delta:{delta}"); + currentPoint = GetCurrentPoint(); + if(delta > 1) break; + //ExplorerCore.LogWarning($"currentPoint.position: {currentPoint.position}, currentPoint.rotation: {currentPoint.rotation}"); + + // update room to check if we need a new lookahead + room = Vector3.Distance(lookahead.position, currentPoint.position); + // while ends, need another lookaheadDelta for the next Update + if (room <= 0) { + delta += lookaheadDelta; + lookahead = GetPointFromPath(delta); + } + } + } + + public void MoveCameraStep(CatmullRomPoint currentPoint, float actual, float room){ + // move my position accordingly + //CatmullRomPoint direction = ((lookahead - currentPoint) / room)*actual; + + CatmullRomPoint direction = (lookahead - currentPoint).Normalize() * actual; + //float rotationDiff = Vector4.Distance(CatmullRomPoint.QuaternionToVector4(lookahead.rotation), CatmullRomPoint.QuaternionToVector4(currentPoint.rotation)); + //ExplorerCore.LogWarning($"direction.rotation: {direction.rotation}, rotation diff: {rotationDiff}"); + + Vector4 ra = CatmullRomPoint.QuaternionToVector4(currentPoint.rotation); + Vector4 rb = CatmullRomPoint.QuaternionToVector4(direction.rotation) * actual / room; + + if (Vector4.Dot(ra, rb) < 0) + { + //ExplorerCore.LogWarning($"tengo que arreglar quaternion"); + rb = - rb; + } + FreeCamPanel.ourCamera.transform.position += direction.position; + FreeCamPanel.ourCamera.transform.rotation = CatmullRomPoint.Vector4ToQuaternion(ra + rb); + //FreeCamPanel.ourCamera.transform.rotation = lookahead.rotation; + FreeCamPanel.ourCamera.fieldOfView += direction.fov * actual / room; + //ExplorerCore.LogWarning($"current cam pos: {FreeCamPanel.ourCamera.transform.position} at d: {delta}"); + } + + public void MoveCameraToPoint(CatmullRomPoint newPoint){ + FreeCamPanel.ourCamera.transform.position = newPoint.position; + FreeCamPanel.ourCamera.transform.rotation = newPoint.rotation; + FreeCamPanel.ourCamera.fieldOfView = newPoint.fov; + } + + /*test*/ + private CatmullRomPoint GetPointFromPath(float d) + { + Vector3 p0, p1, p2, p3; //Previous position, Start position, end position, Next position + Vector4 r0, r1, r2, r3; //Previous rotation, Start rotation, end rotation, Next rotation + float fov0, fov1; + + //Fix rotation + /* + for(int i = 0; i < splinePoints.Length; i++){ + if(i>0 && Dot(splinePoints[i - 1].rotation, splinePoints[i].rotation) < 0){ + Quaternion q = splinePoints[i].rotation; + splinePoints[i].rotation = new Quaternion(- q.x, - q.y, - q.z, - q.w); + ExplorerCore.Log($"cambio orientacion n° {i}"); + } + } + */ + + // 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; + + int currentPoint = (int)((splinePoints.Length - closedAdjustment) * d); // have to do + 1 if closed loop + + //ExplorerCore.LogWarning($"currentPoint: {currentPoint}, d: {d}"); + + bool closedLoopFinalPoint = (closedLoop && currentPoint == splinePoints.Length - 1); + + //Had tu add "splinePoints.Length" because C# mod doesnt work well with negative values + int previousPoint = closedLoop ? (currentPoint + splinePoints.Length - 1)%(splinePoints.Length) : System.Math.Max(currentPoint - 1, 0); + int endSegmentPoint = closedLoop ? (currentPoint + 1)%(splinePoints.Length) : System.Math.Min(currentPoint + 1, splinePoints.Length - 1); + int nextPoint = closedLoop ? (currentPoint + 2)%(splinePoints.Length) : System.Math.Min(currentPoint + 2, splinePoints.Length - 1); + + ExplorerCore.LogWarning($"p0: {previousPoint}, p1: {currentPoint}, p2: {endSegmentPoint}, p3: {nextPoint},"); + + p0 = splinePoints[previousPoint].position; + r0 = CatmullRomPoint.QuaternionToVector4(splinePoints[previousPoint].rotation); + + p1 = splinePoints[currentPoint].position; + r1 = CatmullRomPoint.QuaternionToVector4(splinePoints[currentPoint].rotation); + + p2 = splinePoints[endSegmentPoint].position; + r2 = CatmullRomPoint.QuaternionToVector4(splinePoints[endSegmentPoint].rotation); + + p3 = splinePoints[nextPoint].position; + r3 = CatmullRomPoint.QuaternionToVector4(splinePoints[nextPoint].rotation); + + if (d >= 1) return splinePoints[currentPoint]; + + //Check if we are using the shortest path on the rotation. If not, change r1 to represent that shortest path. + if (Vector4.Dot(r0, r1) < 0) + r1 = - r1; + + if (Vector4.Dot(r1, r2) < 0) + r2 = - r2; + + if (Vector4.Dot(r2, r3) < 0) + r3 = - r3; + + fov0 = splinePoints[currentPoint].fov; + fov1 = splinePoints[endSegmentPoint].fov; + + //float t = (d - (float)currentPoint / (float)(splinePoints.Length + closedAdjustment)) * (float)(splinePoints.Length + closedAdjustment); + float t = ((splinePoints.Length - closedAdjustment) * d) % 1; + + //ExplorerCore.LogWarning($"t: {t}, d: {d}"); + + CatmullRomPoint newPoint = Evaluate(p0, p1, p2, p3, r0, r1, r2, r3, fov0, fov1, t); + + //ExplorerCore.LogWarning($"p1.pos: {p1}, p2.pos: {p2}, t: {t}, dist-p1: {Vector3.Distance(GetCurrentPoint().position, p1)}, dist-p2: {Vector3.Distance(GetCurrentPoint().position, p2)}"); + + return newPoint; + } + + private CatmullRomPoint Evaluate(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Vector4 r0, Vector4 r1, Vector4 r2, Vector4 r3, float fovStart, float fovEnd, float t) + { + Vector3 position = CatmullRomInterpolation(p0, p1, p2, p3, t); + Vector4 v4rot = CatmullRomInterpolation(r0, r1, r2, r3, t); + Quaternion rotation = new Quaternion(v4rot.x, v4rot.y, v4rot.z, v4rot.w); + float fov = Mathf.SmoothStep(fovStart, fovEnd, t); + + return new CatmullRomPoint(position, rotation, fov); + } + + //Implementation from: https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html + private Vector3 CatmullRomInterpolation(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t){ + float tension = 0; + float alpha = 0.5f; + + float t01 = (float) System.Math.Pow((double) Vector3.Distance(p0, p1), (double) alpha); + float t12 = (float) System.Math.Pow((double) Vector3.Distance(p1, p2), (double) alpha); + float t23 = (float) System.Math.Pow((double) Vector3.Distance(p2, p3), (double) alpha); + + Vector3 v1 = t01 != 0 ? (p1 - p0) / t01 : new Vector3(0, 0, 0); + Vector3 v2 = t23 != 0 ? (p3 - p2) / t23 : new Vector3(0, 0, 0); + + Vector3 m1 = (1.0f - tension) * (p2 - p1 + t12 * (v1 - (p2 -p0) / (t01 + t12))); + Vector3 m2 = (1.0f - tension) * (p2 - p1 + t12 * (v2 - (p3 -p1) / (t12 + t23))); + + Vector3 a = 2.0f * (p1 - p2) + m1 + m2; + Vector3 b = -3.0f * (p1 - p2) - m1 - m1 - m2; + Vector3 c = m1; + Vector3 d = p1; + + return a * t * t * t + b * t * t + c * t + d; + } + + private Vector4 CatmullRomInterpolation(Vector4 r0, Vector4 r1, Vector4 r2, Vector4 r3, float t){ + float tension = 0; + float alpha = 0.5f; + + float t01 = (float) System.Math.Pow((double) Vector4.Distance(r0, r1), (double) alpha); + float t12 = (float) System.Math.Pow((double) Vector4.Distance(r1, r2), (double) alpha); + float t23 = (float) System.Math.Pow((double) Vector4.Distance(r2, r3), (double) alpha); + + Vector4 v1 = t01 != 0 ? (r1 - r0) / t01 : new Vector4(0, 0, 0, 0); + Vector4 v2 = t23 != 0 ? (r3 - r2) / t23 : new Vector4(0, 0, 0, 0); + + Vector4 m1 = (1.0f - tension) * (r2 - r1 + t12 * (v1 - (r2 -r0) / (t01 + t12))); + Vector4 m2 = (1.0f - tension) * (r2 - r1 + t12 * (v2 - (r3 -r1) / (t12 + t23))); + + Vector4 a = 2.0f * (r1 - r2) + m1 + m2; + Vector4 b = -3.0f * (r1 - r2) - m1 - m1 - m2; + Vector4 c = m1; + Vector4 d = r1; + + return a * t * t * t + b * t * t + c * t + d; + //return Vector4.Lerp(r1, r2, t); + } + } + +}