Skip to content

Commit

Permalink
Use BezierSpline.Rotation(t) to slerp between rotations of node trans…
Browse files Browse the repository at this point in the history
…forms independent of spline tangent.

Add Node button on spline editor.
Namespace refactor.
  • Loading branch information
Luke Thompson committed Sep 6, 2017
1 parent e168f1e commit ec972ea
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 49 deletions.
5 changes: 3 additions & 2 deletions Editor/BezierEditor.cs → Editor/BezierNodeEditor.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
using Sigtrap.Bezier;

namespace Sigtrap {
namespace Sigtrap.Bezier.Editors {
[CustomEditor(typeof(BezierNode))]
public class BezierEditor : Editor {
public class BezierNodeEditor : Editor {
[DrawGizmo(GizmoType.Selected)]
public void OnSceneGUI(){
BezierNode bn = (BezierNode)target;
Expand Down
File renamed without changes.
21 changes: 21 additions & 0 deletions Editor/BezierSplineEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
using Sigtrap.Bezier;

namespace Sigtrap.Bezier.Editors {
[CustomEditor(typeof(BezierSpline))]
public class BezierSplineEditor : Editor {
public override void OnInspectorGUI(){
base.OnInspectorGUI();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Nodes");
if (GUILayout.Button("Add Node")){
GameObject node = new GameObject("Node");
node.AddComponent<BezierNode>();
node.transform.SetParent((target as BezierSpline).transform, false);
Undo.RegisterCreatedObjectUndo(node, "Create Bezier Node");
}
}
}
}
12 changes: 12 additions & 0 deletions Editor/BezierSplineEditor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 15 additions & 7 deletions Examples/BezierFollower.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
using UnityEngine;
using System.Collections;

namespace Sigtrap {
namespace Sigtrap.Bezier {
[ExecuteInEditMode]
public class BezierFollower : MonoBehaviour {
public enum LoopMode {NONE, LOOP, BOUNCE}
public enum RotateMode {NONE, TANGENT, TRANSFORM}
public BezierSpline path;
[Tooltip("+1 to follow path forward, -1 to follow backwards")]
public int forward = 1;
Expand All @@ -21,13 +22,13 @@ public bool autofollow {
}
public float speed = 0.1f;
private int dir = 1;
[Tooltip("Rotate transform to align with spline")]
public bool rotate = true;
[Tooltip("If on, speed will be linearly corrected for stretching of spline.")]
private bool speedCorrection = true;
public LoopMode loopMode = LoopMode.LOOP;
[Tooltip("TANGENT: Rotate transform to align with spline\nTRANSFORM: Slerp rotation between node transforms")]
public RotateMode rotate = RotateMode.NONE;

void Update(){
void LateUpdate(){
if (path){
// Only use auto mode when playing; makes no sense when manually sliding t in editor
if (auto && Application.isPlaying){
Expand All @@ -43,8 +44,13 @@ void Update(){
// If not auto-following, just apply t manually
transform.position = path.Spline(t, speedCorrection);
}
if (rotate){
transform.forward = forward * path.Tangent(t, speedCorrection);
switch (rotate){
case RotateMode.TANGENT:
transform.forward = forward * path.Tangent(t, speedCorrection);
break;
case RotateMode.TRANSFORM:
transform.rotation = path.Rotation(t);
break;
}
}
}
Expand Down Expand Up @@ -85,7 +91,9 @@ void UpdateT(){

#if UNITY_EDITOR
void OnDrawGizmosSelected(){
path.DrawPath();
if (path) {
path.DrawPath();
}
}
#endif
}
Expand Down
2 changes: 1 addition & 1 deletion Scripts/BezierNode.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using UnityEngine;
using System.Collections;

namespace Sigtrap {
namespace Sigtrap.Bezier {
[ExecuteInEditMode]
public class BezierNode : MonoBehaviour {
public enum Symmetry {FULL, ANGLE, NONE}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using UnityEditor;
using System.Collections;

// Editor code for Bezier Curve
namespace Sigtrap {
// Gizmos code for Bezier Curve
namespace Sigtrap.Bezier {
public partial class BezierSpline : MonoBehaviour {
[Header("Editor Settings")]

Expand All @@ -31,7 +31,7 @@ public partial class BezierSpline : MonoBehaviour {
/// Called by editor and/or child nodes when selected to draw path and all nodes
/// </summary>
public void OnDrawGizmosSelected(){
GetNodes();
if(!GetNodes())return;
Color gcol = Gizmos.color;

// Draw handle vectors and caps per node
Expand Down
File renamed without changes.
90 changes: 54 additions & 36 deletions Scripts/BezierSpline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#endif

// Runtime code for BezierCurve
namespace Sigtrap {
namespace Sigtrap.Bezier {
public partial class BezierSpline : MonoBehaviour {
#if UNITY_EDITOR
[MenuItem("GameObject/Create Simple Bezier")]
Expand Down Expand Up @@ -82,6 +82,7 @@ public static Vector3 Curve(float t, Vector3 start, Vector3 handle1, Vector3 han
/// <param name="t">Fractional position along curve.</param>
/// <param name="constantSpeed">Use piecewise stretch correction?</param>
public Vector3 Spline(float t, bool constantSpeed){
// Ensure everything's initialised and check for cheap result at t==0||t==1
Vector3? temp = PreBezier(ref t);
if (temp.HasValue){
return temp.Value;
Expand All @@ -90,14 +91,14 @@ public Vector3 Spline(float t, bool constantSpeed){
// Use stateless piecewise stretch correction
return GetSector(t).Curve(t, constantSpeed);
}

/// <summary>
/// Calculate position at t along this spline.
/// Stateful differential stretch correction.
/// </summary>
/// <param name="t">Fractional position along curve.</param>
/// <param name="dT">Amount to increment t. If zero, uses piecewise stretch correction.</param>
public Vector3 Spline(ref float t, float dT){
// Ensure everything's initialised and check for cheap result at t==0||t==1
Vector3? temp = PreBezier(ref t);
if (temp.HasValue){
return temp.Value;
Expand All @@ -114,6 +115,14 @@ public Vector3 Spline(ref float t, float dT){
public Vector3 Tangent(float t, bool unstretch){
return GetSector(t).TanGlobal(t, unstretch);
}
/// <summary>
/// Slerp between rotations of node transforms
/// </summary>
/// <param name="t">T.</param>
public Quaternion Rotation(float t){
PreBezier(ref t);
return GetSector(t).Rotation(t);
}
#endregion

private Vector3? PreBezier(ref float t){
Expand All @@ -135,15 +144,16 @@ public Vector3 Tangent(float t, bool unstretch){
}
return null;
}
private void GetNodes(){
private bool GetNodes(){
_nodes = GetComponentsInChildren<BezierNode>();
if (_nodes == null || _nodes.Length == 0){
throw new MissingComponentException("No BezierNodes found parented to BezierCurve. Cannot calculate spline.");
return false;
}
if (_closed && _nodes.Length > 1){
System.Array.Resize(ref _nodes, _nodes.Length + 1);
_nodes[_nodes.Length - 1] = _nodes[0];
}
return true;
}
private void Precache(){
// Setup sectors, calculate constant stuff etc
Expand Down Expand Up @@ -254,88 +264,96 @@ private float GlobalToLocalT(float t){
}
return (t - _tOffset) / _tLength;
}
private float RemapPiecewise(float tLocal){
private float RemapPiecewise(float tSector){
float t0 = 0;
float t1 = 0;
// Find segment t resides in, and remap according to that
for (int segment=0; segment<_segmentT1s.Length; ++segment){
t1 += _segmentT1s[segment];
if (t1 > tLocal){
if (t1 > tSector){
// Remove offset to get remainder
tLocal -= t0;
tSector -= t0;
// Get scale of segment length relative to average
// Equivalent to seglength / (1/intSegs)
float segScale = (t1 - t0) * (float)_segmentT1s.Length;
// Rescale remainder
tLocal /= segScale;
tSector /= segScale;
// Add linear offset back on
tLocal += ((float)segment/(float)_segmentT1s.Length);
tSector += ((float)segment/(float)_segmentT1s.Length);
break;
}
t0 = t1;
}
return tLocal;
return tSector;
}

/// <summary>
/// Is the given global t within this sector?
/// </summary>
/// <returns><c>true</c> if given global t falls within this sector</returns>
/// <param name="tGlobal">Global t</param>
public bool InSector(float tGlobal){
return (tGlobal >= _tOffset && tGlobal < (_tOffset + _tLength));
/// <param name="tSpline">Global t</param>
public bool InSector(float tSpline){
return (tSpline >= _tOffset && tSpline < (_tOffset + _tLength));
}
/// <summary>
/// Calculate position from given global t, using piecewise stretch correction (or none)
/// </summary>
/// <param name="tGlobal">Global t</param>
/// <param name="tSpline">Global/spline t</param>
/// <param name="unstretch">If true, correct stretch with piecewise approximation</param>
public Vector3 Curve(float tGlobal, bool unstretch){
tGlobal = GlobalToLocalT(tGlobal);
public Vector3 Curve(float tSpline, bool unstretch){
tSpline = GlobalToLocalT(tSpline);
if (unstretch){
// Remap t
tGlobal = RemapPiecewise(tGlobal);
tSpline = RemapPiecewise(tSpline);
}
return Curve(tGlobal);
return Curve(tSpline);
}
/// <summary>
/// Calculate position from current global t and global dT using differential stretch correction
/// If no dT given, remaps t using piecewise approximation
/// </summary>
/// <param name="tGlobal">T global.</param>
/// <param name="dtGlobal">Dt global.</param>
public Vector3 Curve(ref float tGlobal, float dtGlobal){
tGlobal = Mathf.Clamp01(tGlobal);
float tLocal = GlobalToLocalT(tGlobal);
/// <param name="tSpline">Global/spline t</param>
/// <param name="dtGlobal">Global/spline delta t</param>
public Vector3 Curve(ref float tSpline, float dtGlobal){
tSpline = Mathf.Clamp01(tSpline);
float tSector = GlobalToLocalT(tSpline);

// Remap local t
if (dtGlobal == 0){
// If no dT, get piecewise-remapped approx of t
tLocal = RemapPiecewise(tLocal);
tSector = RemapPiecewise(tSector);
} else {
// Get local derivative
float dCdT = ((3 * _c1 * tLocal * tLocal) + (2 * _c2 * tLocal) + _c3).magnitude;
float dCdT = ((3 * _c1 * tSector * tSector) + (2 * _c2 * tSector) + _c3).magnitude;
// Transform global dT to local, then divide by local derivative. Add transformed increment to t.
tLocal += (dtGlobal / _tLength) / dCdT;
tSector += (dtGlobal / _tLength) / dCdT;
}

// Transform local t back to global.
tGlobal = _tOffset + (tLocal * _tLength);
tSpline = _tOffset + (tSector * _tLength);

return Curve(tLocal, false);
return Curve(tSector, false);
}
private Vector3 Curve(float tLocal){
return _c1*tLocal*tLocal*tLocal + _c2*tLocal*tLocal + _c3*tLocal + _c4;
private Vector3 Curve(float tSector){
return _c1*tSector*tSector*tSector + _c2*tSector*tSector + _c3*tSector + _c4;
}
public Vector3 TanLocal(float tLocal, bool unstretch){
public Vector3 TanLocal(float tSector, bool unstretch){
if (unstretch){
tLocal = RemapPiecewise(tLocal);
tSector = RemapPiecewise(tSector);
}
return ((3 * _c1 * tLocal * tLocal) + (2 * _c2 * tLocal) + _c3);
return ((3 * _c1 * tSector * tSector) + (2 * _c2 * tSector) + _c3);
}
public Vector3 TanGlobal(float tSpline, bool unstretch){
tSpline = Mathf.Clamp01(tSpline);
return TanLocal(GlobalToLocalT(tSpline), unstretch);
}
public Vector3 TanGlobal(float tGlobal, bool unstretch){
tGlobal = Mathf.Clamp01(tGlobal);
return TanLocal(GlobalToLocalT(tGlobal), unstretch);
/// <summary>
/// Slerp between rotations of node transforms
/// </summary>
/// <param name="tSpline">Global/spline t</param>
public Quaternion Rotation(float tSpline){
float tSector = GlobalToLocalT(Mathf.Clamp01(tSpline));
return Quaternion.Slerp(_start.transform.rotation, _end.transform.rotation, tSector);
}
}
}
Expand Down

0 comments on commit ec972ea

Please sign in to comment.