Added constant speed option to cam paths.

This commit is contained in:
originalnicodr committed 2023-04-01 15:57:46 -03:00
1 parent fa0bd95f3c
commit 88c8a889dc
2 files changed
+343 -13

No files matched your search

+275 -3
View File
@@ -8,6 +8,8 @@ using Il2CppInterop.Runtime.Injection;
#endif
#endif
using UnityExplorer.UI.Panels;
using System;
namespace UnityExplorer
{
@@ -195,7 +197,7 @@ namespace UnityExplorer
this.controlPoints = controlPoints;
this.closedLoop = closedLoop;
GenerateSplinePoints();
GenerateSplinePoints2();
}
//Updates control points
@@ -208,7 +210,7 @@ namespace UnityExplorer
this.controlPoints = controlPoints;
GenerateSplinePoints();
GenerateSplinePoints2();
}
//Updates closed loop values
@@ -216,7 +218,7 @@ namespace UnityExplorer
{
this.closedLoop = closedLoop;
GenerateSplinePoints();
GenerateSplinePoints2();
}
//Draws a line between every point and the next.
@@ -495,6 +497,8 @@ namespace UnityExplorer
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;
@@ -523,5 +527,273 @@ namespace UnityExplorer
if(currentPoint < ExplorerCore.CameraPathsManager.GetPoints().Length)
playingPath = true;
}
public float[] GenerateSplinePointsByRes(int resolution = 3000) //resolution of each segment
{
List<float> splineSegmentDistances = new List<float>();
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();
}
private void GenerateSplinePoints2()
{
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 = Evaluate2(p0, p1, p2, p3, r0, r1, r2, r3, fov0, fov1, t);
splinePoints[totalframes + tesselatedPoint] = point;
}
totalframes+=frames;
}
}
static Vector3 CatmullRomInterpolate(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 CatmullRomInterpolate(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;
}
//Implementation from: https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html
static Vector3 CatmullRom2(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 CatmullRom2(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 CatmullRomPoint Evaluate2(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Vector4 r0, Vector4 r1, Vector4 r2, Vector4 r3, float fovStart, float fovEnd, float t)
{
Vector3 position = CatmullRom2(p0, p1, p2, p3, t);
Vector4 v4rot = CatmullRom2(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, new Vector3(0,0,0), new Vector3(0,0,0), fov);
}
}
}
+68 -10
View File
@@ -23,7 +23,7 @@ namespace UnityExplorer.UI.Panels
public override string Name => "CamPaths";
public override UIManager.Panels PanelType => UIManager.Panels.CamPaths;
public override int MinWidth => 700;
public override int MinWidth => 1000;
public override int MinHeight => 600;
public override Vector2 DefaultAnchorMin => new(0.4f, 0.4f);
public override Vector2 DefaultAnchorMax => new(0.6f, 0.6f);
@@ -33,6 +33,9 @@ namespace UnityExplorer.UI.Panels
List<GameObject> UINodes = new List<GameObject>();
bool closedLoop;
bool constantSpeed;
int pathTotalFrames = 2000;
// ~~~~~~~~ UI construction / callbacks ~~~~~~~~
protected override void ConstructPanelContent()
@@ -62,11 +65,22 @@ namespace UnityExplorer.UI.Panels
DeletePath.OnClick += () => {controlPoints.Clear(); UpdateListNodes();};
Toggle closedLoopToggle = new Toggle();
GameObject toggleObj = UIFactory.CreateToggle(horiGroup, "Close path in a loop", out closedLoopToggle, out Text toggleText);
UIFactory.SetLayoutElement(toggleObj, minHeight: 25, flexibleWidth: 9999);
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;
toggleText.text = "Close path in a loop";
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", "Complete path duration (in frames):", "Default: 2000", out CompletePathFramesInput, TotalFrames_OnEndEdit, false);
CompletePathFramesInput.Text = pathTotalFrames.ToString();
}
private void UpdateListNodes(){
@@ -108,9 +122,11 @@ namespace UnityExplorer.UI.Panels
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();
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?
}
@@ -131,11 +147,12 @@ namespace UnityExplorer.UI.Panels
FreeCamPanel.ourCamera.fieldOfView = point.fov;
}
GameObject AddInputField(string name, string labelText, string placeHolder, out InputFieldRef inputField, Action<string> onInputEndEdit)
GameObject AddInputField(string name, string labelText, string placeHolder, out InputFieldRef inputField, Action<string> onInputEndEdit, bool shouldDeleteOnUpdate = true)
{
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
if(shouldDeleteOnUpdate)
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);
@@ -155,8 +172,12 @@ namespace UnityExplorer.UI.Panels
if(ExplorerCore.CameraPathsManager == null)
ExplorerCore.CameraPathsManager = new CatmullRom(controlPoints.ToArray(), closedLoop);
else{
ExplorerCore.CameraPathsManager.Update(controlPoints.ToArray());
ExplorerCore.CameraPathsManager.Update(closedLoop);
ExplorerCore.CameraPathsManager.Update(controlPoints.ToArray());
}
if(constantSpeed){
UpdateNodeFramesConstantspeed();
}
ExplorerCore.CameraPathsManager.StartPath();
@@ -189,5 +210,42 @@ namespace UnityExplorer.UI.Panels
UpdateListNodes();
}
void UpdateNodeFramesConstantspeed(){
float[] d = ExplorerCore.CameraPathsManager.GenerateSplinePointsByRes(3000);
ExplorerCore.Log($"d.Length: {d.Length}");
float dTotal = d.Sum();
ExplorerCore.Log($"dTotal: {dTotal}");
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.Log($"d[{i}]: {d[i]}");
ExplorerCore.Log(controlPoints[i].frames);
}
ExplorerCore.CameraPathsManager.Update(controlPoints.ToArray());
}
void TotalFrames_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;
}
}
}