From cfdb7d706ea2961d2dfcbc07ed618a6d6273bff1 Mon Sep 17 00:00:00 2001 From: originalnicodr Date: Wed, 1 Mar 2023 17:55:16 -0300 Subject: [PATCH] Added custom frame amount for each node. --- src/ExplorerBehaviour.cs | 266 +++++++++++++++++++++++++------------- src/UI/Panels/CamPaths.cs | 55 ++++++-- 2 files changed, 223 insertions(+), 98 deletions(-) diff --git a/src/ExplorerBehaviour.cs b/src/ExplorerBehaviour.cs index 0909675..e9de170 100644 --- a/src/ExplorerBehaviour.cs +++ b/src/ExplorerBehaviour.cs @@ -154,16 +154,17 @@ namespace UnityExplorer public Vector3 position; public Quaternion rotation; public float fov; + public int frames; - public PathControlPoint(Vector3 position, Quaternion rotation, float fov) + public PathControlPoint(Vector3 position, Quaternion rotation, float fov, int frames = 500) { this.position = position; this.rotation = rotation; this.fov = fov; + this.frames = frames; } } - private int resolution; //Amount of points between control points. [Tesselation factor] private bool closedLoop; private CatmullRomPoint[] splinePoints; //Generated spline points @@ -172,7 +173,7 @@ namespace UnityExplorer public bool playingPath; int currentPoint = 0; - //Returns spline points. Count is contorolPoints * resolution + [resolution] points if closed loop. + //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) @@ -183,16 +184,15 @@ namespace UnityExplorer return splinePoints; } - public CatmullRom(PathControlPoint[] controlPoints, int resolution, bool closedLoop) + public CatmullRom(PathControlPoint[] controlPoints, bool closedLoop) { - if(controlPoints == null || controlPoints.Length <= 2 || resolution < 2) + if(controlPoints == null || controlPoints.Length <= 2) { ExplorerCore.Log(controlPoints); - throw new ArgumentException("Catmull Rom Error: Too few control points or resolution too small"); + throw new ArgumentException("Catmull Rom Error: Too few control points!"); } this.controlPoints = controlPoints; - this.resolution = resolution; this.closedLoop = closedLoop; GenerateSplinePoints(); @@ -211,14 +211,9 @@ namespace UnityExplorer GenerateSplinePoints(); } - //Updates resolution and closed loop values - public void Update(int resolution, bool closedLoop) + //Updates closed loop values + public void Update(bool closedLoop) { - if(resolution < 2) - { - throw new ArgumentException("Invalid Resolution. Make sure it's >= 1"); - } - this.resolution = resolution; this.closedLoop = closedLoop; GenerateSplinePoints(); @@ -229,22 +224,15 @@ namespace UnityExplorer { 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); } } @@ -283,20 +271,29 @@ namespace UnityExplorer return splinePoints != null; } - //Sets the length of the point array based on resolution/closed loop. + //Sets the length of the point array based on total amount of frames. private void InitializeProperties() { - int pointsToCreate; + int pointsToCreate = 0; if (closedLoop) { - pointsToCreate = resolution * controlPoints.Length; //Loops back to the beggining, so no need to adjust for arrays starting at 0 + 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 { - pointsToCreate = resolution * (controlPoints.Length - 1); + for (var i = 0; i < controlPoints.Length - 1; i++) + { + pointsToCreate += controlPoints[i].frames; + } } - splinePoints = new CatmullRomPoint[pointsToCreate]; + //if(splinePoints != null) + // Array.Clear(splinePoints, 0, splinePoints.Length); + + splinePoints = new CatmullRomPoint[pointsToCreate]; } //Math stuff to generate the spline points @@ -311,6 +308,15 @@ namespace UnityExplorer // 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; + + /* + Quaternion[] rotationArray = new Quaternion[controlPoints.Length]; + for(int i = 0; i < controlPoints.Length - closedAdjustment; i++) + rotationArray[i] = controlPoints[i].rotation; + */ + + 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); @@ -380,22 +386,24 @@ namespace UnityExplorer m0 *= 0.5f; //Doing this here instead of in every single above statement m1 *= 0.5f; - float pointStep = 1.0f / resolution; + 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 / (resolution - 1); // last point of last segment should reach p1 + pointStep = 1.0f / (frames - 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++) + // 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, r0, r1, m0, m1, fov0, fov1, t); - splinePoints[currentPoint * resolution + tesselatedPoint] = point; + splinePoints[totalframes + tesselatedPoint] = point; } + totalframes+=frames; } } @@ -445,13 +453,14 @@ namespace UnityExplorer public void MaybeRunPath(){ if(playingPath){ - if(ExplorerCore.CameraPathsManager != null && currentPoint < ExplorerCore.CameraPathsManager.GetPoints().Length){ - CatmullRomPoint point = ExplorerCore.CameraPathsManager.GetPoints()[currentPoint]; + if(ExplorerCore.CameraPathsManager != null && currentPoint < splinePoints.Length){ + CatmullRomPoint point = splinePoints[currentPoint]; + FreeCamPanel.ourCamera.transform.position = point.position; FreeCamPanel.ourCamera.transform.rotation = point.rotation; FreeCamPanel.ourCamera.fieldOfView = point.fov; currentPoint++; - if(currentPoint >= ExplorerCore.CameraPathsManager.GetPoints().Length) + if(currentPoint >= splinePoints.Length) playingPath = false; } } @@ -477,73 +486,154 @@ namespace UnityExplorer } } - /* - - public struct LineDrawer - { - private LineRenderer lineRenderer; - private float lineSize; - - public LineDrawer(float lineSize = 0.2f) + //SQUAD + //Ported by CodeKiwi + //https://forum.unity.com/threads/need-help-implementing-quaternion-squad.273931/#post-4447033 + public class Squad { + // Returns a smoothed quaternion along the set of quaternions making up the spline, each quaternion is along an equidistant value in t + public static Quaternion Spline(Quaternion[] quaternions , float t ) { - //GameObject lineObj = new GameObject("LineObj"); - //lineRenderer = lineObj.AddComponent(); + var section = (int)((quaternions.Length - 1) * t); - //Particles/Additive - //lineRenderer.material = new Material(Shader.Find("Hidden/Internal-Colored")); + var alongLine = (quaternions.Length - 1) * t - section; - lineRenderer = new GameObject().GetComponent(); - - this.lineSize = lineSize; - } - - private void init(float lineSize = 0.2f) - { - if (lineRenderer == null) + if (section == 0) { - //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; + return SplineSegment(quaternions[section], quaternions[section], quaternions[section + 1], quaternions[section + 2], alongLine); } - } - - //Draws lines through the provided vertices - public void DrawLineInGameView(Vector3 start, Vector3 end, Color color) - { - if (lineRenderer == null) + else if( section == quaternions.Length - 2 && section > 0) { - init(0.2f); + return SplineSegment(quaternions[section - 1], quaternions[section], quaternions[section + 1], quaternions[section + 1], alongLine); + } + else if ( section >= 1 && section 0.9999f) { - //UnityEngine.Object.Destroy(lineRenderer.gameObject); + return fro; } + + float theta = Mathf.Acos(dot); + var sinT = 1.0f / Mathf.Sin(theta); + + var newFactor = Mathf.Sin(factor * theta) * sinT; + + var invFactor = Mathf.Sin((1.0f - factor) * theta) * sinT; + + return new Quaternion(invFactor * fro.x + newFactor * to.x, invFactor * fro.y + newFactor * to.y, invFactor * fro.z + newFactor * to.z, invFactor * fro.w + newFactor * to.w); + } + + + + // Returns a smooth approximation between q1 and q2 using t1 and t2 as 'tangents' + static Quaternion SQUAD(Quaternion q1 , Quaternion t1 , Quaternion t2 , Quaternion q2 , float t) + { + float slerpT = 2.0f * t * (1.0f - t); + + Quaternion slerp1 = SlerpNoInvert(q1, q2, t); + + Quaternion slerp2 = SlerpNoInvert(t1, t2, t); + + return SlerpNoInvert(slerp1, slerp2, slerpT); + } + + + + // Returns a quaternion between q1 and q2 as part of a smooth SQUAD segment + public static Quaternion SplineSegment(Quaternion q0, Quaternion q1, Quaternion q2, Quaternion q3, float t) + { + Quaternion qa = Intermediate(q0, q1, q2); + Quaternion qb = Intermediate(q1, q2, q3); + return SQUAD(q1, qa, qb, q2, t); + + } + + static public void Exp(ref Quaternion a ) + { + float angle = Mathf.Sqrt(a.x * a.x + a.y * a.y + a.z * a.z); + + float sinAngle = Mathf.Sin(angle); + a.w = Mathf.Cos(angle); + + if (Mathf.Abs(sinAngle) >= 1.0e-15) + { + float coeff = sinAngle / angle; + a.x *= coeff; + a.y *= coeff; + a.z *= coeff; + } + + } + static Quaternion Add(Quaternion a , Quaternion b ) + { + var r = new Quaternion(); + r.w = a.w + b.w; + r.x = a.x + b.x; + r.y = a.y + b.y; + r.z = a.z + b.z; + return r; + } + + static void Scale(ref Quaternion a, float s) + { + a.w *= s; + a.x *= s; + a.y *= s; + a.z *= s; + } + + // Tries to compute sensible tangent values for the quaternion + static Quaternion Intermediate(Quaternion q0, Quaternion q1, Quaternion q2 ) + { + Quaternion q1inv = Quaternion.Inverse(q1); + + Quaternion c1 = q1inv * q2; + + Quaternion c2 = q1inv * q0; + + //c1.Log(); + //c2.Log(); + + Quaternion c3 = Add(c2, c1);// c2 + c1; + Scale(ref c3, -0.25f);// c3.Scale(-0.25f); + + Exp(ref c3);// c3.Exp(); + + Quaternion r = q1 * c3; +#if MONO + return Normalize(r); +#else + r.Normalize(); + return r; +#endif + } +#if MONO + public static float Dot(Quaternion a, Quaternion b) + { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + public static Quaternion Normalize(Quaternion q) + { + float mag = Mathf.Sqrt(Dot(q, q)); + + if (mag < Mathf.Epsilon) + return Quaternion.identity; + + return new Quaternion(q.x / mag, q.y / mag, q.z / mag, q.w / mag); + } +#endif + } - */ } diff --git a/src/UI/Panels/CamPaths.cs b/src/UI/Panels/CamPaths.cs index 98db0aa..a7430ec 100644 --- a/src/UI/Panels/CamPaths.cs +++ b/src/UI/Panels/CamPaths.cs @@ -76,12 +76,13 @@ namespace UnityExplorer.UI.Panels //UIElements.Remove(comp); } - foreach (var point in controlPoints) { - DrawNodeOptions(point); + for(int i = 0; i < controlPoints.Count; i++) { + CatmullRom.PathControlPoint point = controlPoints[i]; + DrawNodeOptions(point, i); } } - private void DrawNodeOptions(CatmullRom.PathControlPoint point){ + private void DrawNodeOptions(CatmullRom.PathControlPoint 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); @@ -100,11 +101,16 @@ namespace UnityExplorer.UI.Panels UIFactory.SetLayoutElement(moveToPointButton.GameObject, minWidth: 100, minHeight: 25, flexibleWidth: 9999); moveToPointButton.OnClick += () => {FreeCamPanel.ourCamera.transform.position = point.position; FreeCamPanel.ourCamera.transform.rotation = point.rotation; FreeCamPanel.ourCamera.fieldOfView = point.fov;}; + //Add node next + ButtonRef destroyButton = UIFactory.CreateButton(horiGroup, "Delete", "Delete"); UIFactory.SetLayoutElement(destroyButton.GameObject, minWidth: 80, minHeight: 25, flexibleWidth: 9999); destroyButton.OnClick += () => {controlPoints.Remove(point); UpdateListNodes();}; //Frames + InputFieldRef framesInput = null; + AddInputField("Frames", "Length (in frames):", "Default: 500", out framesInput, (frames) => { FramesInput_OnEndEdit(point, index, frames); }); + framesInput.Text = point.frames.ToString(); //ShowPos and rot? } @@ -125,28 +131,57 @@ namespace UnityExplorer.UI.Panels FreeCamPanel.ourCamera.fieldOfView = point.fov; } - void AddSpacer(int height) + GameObject AddInputField(string name, string labelText, string placeHolder, out InputFieldRef inputField, Action onInputEndEdit) { - GameObject obj = UIFactory.CreateUIObject("Spacer", ContentRoot); - UIFactory.SetLayoutElement(obj, minHeight: height, flexibleHeight: 0); + GameObject row = UIFactory.CreateHorizontalGroup(ContentRoot, "Editor Field", + false, false, true, true, 5, default, new Color(1, 1, 1, 0)); + 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); + + inputField = UIFactory.CreateInputField(row, $"{name}_Input", placeHolder); + UIFactory.SetLayoutElement(inputField.GameObject, minWidth: 125, minHeight: 25, flexibleWidth: 9999); + inputField.Component.GetOnEndEdit().AddListener(onInputEndEdit); + + return row; } void StartButton_OnClick() { - int resolution = 500; //float normalExtrusion = 0; //float tangentExtrusion = 0; if(ExplorerCore.CameraPathsManager == null) - ExplorerCore.CameraPathsManager = new CatmullRom(controlPoints.ToArray(), resolution, closedLoop); + ExplorerCore.CameraPathsManager = new CatmullRom(controlPoints.ToArray(), closedLoop); else{ ExplorerCore.CameraPathsManager.Update(controlPoints.ToArray()); - ExplorerCore.CameraPathsManager.Update(resolution, closedLoop); + ExplorerCore.CameraPathsManager.Update(closedLoop); } - + 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; + } + + int frames = parsed; + + if(frames > 1){ + point.frames = frames; + controlPoints[index] = point; + } + 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);