relative spline time

hey guys,

I’m having a small issue that hopefully someone can help out with!

So basically I’m trying to do a dynamic spline, where I can constantly add points to my spline. That’s not the issue though…

So the issue is say for example I’m at point 0.5f on my spline, and I add a new point then the interpolated position will of course change.

What I need is for t to adjust when I add a new point, I’m guessing I’ll have to remove some value from t to account for this change, like when I add a point t -= x etc but I need help figuring out what I actually need to remove?

Any ideas?

Thanks!

If you are adding new waypoints, that shouldnt affect t from 0->1 between currentWaypoint and NextWaypoint
Unless you are adding points between existing points to refine the curve?
Or have you somehow got t 0->1 from firstWaypoint to lastWaypoint (weird)

take a look at this (you’ll need gizmos on) this should show you what I mean.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Demo : MonoBehaviour
{
    private Vector2 MPWorldSpace
    {
        get {
            Vector3 p = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, -Camera.main.transform.position.z));

            return new Vector2 (p.x, p.y);
        }
    }

    public CRSpline spline;

    public float t = 0.3f;

    void Update ()
    {
        if (Input.GetMouseButtonDown (0))
        {
            this.spline.AddPoint (MPWorldSpace);
        }
    }

    void OnDrawGizmos ()
    {
        if (!Application.isPlaying)
            return;

        Gizmos.DrawSphere (MPWorldSpace, 0.1f);

        spline.DebugDraw ();

        Gizmos.DrawCube (spline.Interp (t), Vector3.one * 0.1f);
    }
}

[System.Serializable]
public class CRSpline
{
    public List <Vector3> pts = new List <Vector3> ();
  
    public CRSpline (params Vector3[] pts)
    {
        this.pts.AddRange (pts);
    }
  
    public void AddPoint (Vector3 p)
    {
        this.pts.Add (p);
    }
  
    public Vector3 Interp (float t)
    {
        if (this.pts.Count < 4)
            return Vector3.zero;
      
        int numSections = pts.Count - 3;
        int currPt = Mathf.Min(Mathf.FloorToInt(t * (float) numSections), numSections - 1);
        float u = t * (float) numSections - (float) currPt;
      
        Vector3 a = pts [currPt];
        Vector3 b = pts [currPt + 1];
        Vector3 c = pts [currPt + 2];
        Vector3 d = pts [currPt + 3];
      
        return .5f * (
            (-a + 3f * b - 3f * c + d) * (u * u * u)
            + (2f * a - 5f * b + 4f * c - d) * (u * u)
            + (-a + c) * u
            + 2f * b
            );
    }
  
    public void DebugDraw (int quality = 50)
    {
        if (this.pts.Count < 4)
            return;
      
        Gizmos.color = Color.white;
        Vector3 prevPt = Interp (0);
      
        for (int i = 1; i <= quality; i++)
        {
          
            float pm = (float) i / quality;
            Vector3 currPt = Interp(pm);
            Gizmos.DrawLine(currPt, prevPt);
            prevPt = currPt;
          
            Gizmos.DrawWireSphere (currPt, 0.01f);
        }
    }
}

So it appears that t is 0->1 for the entire splie, on not each waypoint…

int currPt = Mathf.Min(Mathf.FloorToInt(t * (float) numSections), numSections - 1);
float u = t * (float) numSections - (float) currPt;

This appears to be your formula for dealing with t.

Just reverse it. You know what the previous numSections was, and now you can know what the new numSections is, just solve the old and new so that the result falls the same distance between the 2 waypoints you were in in the old. It’s just some algebra.

I’m still a little confused, can you possibly show me a example?

Thanks!

your float u is actually 0->1 inbetween the 2 adjacent points

you have u = t * num - currPt, do algebra to rearrange it for t

float uOld = tOld * (float) numSectionsOld - (float) currPtOld; // u = tn-c
float tNew = (uOld + currPtOld) / numSectionsNew; // t = (u+c)/n

hmm, I’m still struggling a little.

Here’s what I have now, but it doesn’t work as expected:

public class CRSpline
{
    public List <Vector3> pts = new List <Vector3> ();
   
    public CRSpline (params Vector3[] pts)
    {
        this.pts.AddRange (pts);
    }
   
    public void AddPoint (Vector3 p)
    {
        this.pts.Add (p);
    }

    private float tOld;
    private int numSectionsOld, currPtOld, numSectionsNew;

    public Vector3 Interp (float t)
    {
        if (this.pts.Count < 4)
            return Vector3.zero;

        numSectionsNew = this.pts.Count;

        int numSections = pts.Count - 3;

        int currPt = Mathf.Min (Mathf.FloorToInt (t * (float) numSections), numSections - 1);

        float uOld = tOld * (float) numSectionsOld - (float) currPtOld; // u = tn-c
        float tNew = (uOld + currPtOld) / numSectionsNew; // t = (u+c)/n

        // --
        //float u = t * (float) numSections - (float) currPt;
        float u = tNew * (float) numSections - (float) currPt;

        Vector3 a = pts [currPt];
        Vector3 b = pts [currPt + 1];
        Vector3 c = pts [currPt + 2];
        Vector3 d = pts [currPt + 3];

        numSectionsOld = this.pts.Count;
        currPtOld = currPt;

        return .5f * (
            (-a + 3f * b - 3f * c + d) * (u * u * u)
            + (2f * a - 5f * b + 4f * c - d) * (u * u)
            + (-a + c) * u
            + 2f * b
            );
    }
   
    public void DebugDraw (int quality = 10)
    {
        if (this.pts.Count < 4)
            return;

        int relativeQuality = this.pts.Count * quality;

        Gizmos.color = Color.white;
        Vector3 prevPt = Interp (0);
       
        for (int i = 1; i <= relativeQuality; i++)
        {
           
            float pm = (float) i / relativeQuality;
            Vector3 currPt = Interp(pm);
            Gizmos.DrawLine(currPt, prevPt);
            prevPt = currPt;
           
            Gizmos.DrawWireSphere (currPt, 0.01f);
        }
    }
}

You can leave the interp code as it was before, the changes only need to be made in the AddPoint code (Because that’s when the value of t needs to be modified)
Also, you need to assign tNew back to t in demo (perhaps return the new value from AddPoint)
numSections old and new is numsections (pts.Count - 3) before and after adding the new point.
You wont need any new member variables in the CRSpline class, they can all be private to the scope of AddPoint

I tried this, but I still failed … :frowning: :

public void AddPoint (Vector3 p, out float _t)
    {
        float uOld = tOld * (float) pts.Count - (float) currPtOld; // u = tn-c
        float tNew = (uOld + currPtOld) / pts.Count + 1; // t = (u+c)/n

        _t = tNew;

        this.pts.Add (p);
    }

    public Vector3 Interp (float t)
    {
        if (this.pts.Count < 4)
            return Vector3.zero;

        int numSections = pts.Count - 3;
        int currPt = Mathf.Min (Mathf.FloorToInt (t * (float) numSections), numSections - 1);

        float u = t * (float) numSections - (float) currPt;

        Vector3 a = pts [currPt];
        Vector3 b = pts [currPt + 1];
        Vector3 c = pts [currPt + 2];
        Vector3 d = pts [currPt + 3];

        tOld = t;
        currPtOld = currPt;

        return .5f * ((-a + 3f * b - 3f * c + d) * (u * u * u) + (2f * a - 5f * b + 4f * c - d) * (u * u) + (-a + c) * u + 2f * b);
    }

uhhh try putting brackets around pts.Count + 1

ok so it’s a little better but it still moves, any idea why?

public class CRSpline
{
    public List <Vector3> pts = new List <Vector3> ();
   
    private float tOld;

    public CRSpline (params Vector3[] pts)
    {
        this.pts.AddRange (pts);
    }

    public void AddPoint (Vector3 p, out float _t)
    {
        float uOld = tOld * (float) pts.Count - (float) pts.Count; // u = tn-c
        float tNew = (uOld + pts.Count) / (pts.Count + 1); // t = (u+c)/n
       
        _t = tNew;
       
        this.pts.Add (p);
    }
   
    public Vector3 Interp (float t)
    {
        if (this.pts.Count < 4)
            return Vector3.zero;
       
        int numSections = pts.Count - 3;
        int currPt = Mathf.Min(Mathf.FloorToInt(t * (float) numSections), numSections - 1);
        float u = t * (float) numSections - (float) currPt;

        Vector3 a = pts [currPt];
        Vector3 b = pts [currPt + 1];
        Vector3 c = pts [currPt + 2];
        Vector3 d = pts [currPt + 3];

        tOld = t;

        return .5f * (
            (-a + 3f * b - 3f * c + d) * (u * u * u)
            + (2f * a - 5f * b + 4f * c - d) * (u * u)
            + (-a + c) * u
            + 2f * b
            );
    }
   
    public void DebugDraw (int quality = 50)
    {
        if (this.pts.Count < 4) return;
       
        Vector3 prevPt = Interp (0);
       
        for (int i = 1; i <= quality; i++)
        {
            float pm = (float) i / quality;
            Vector3 currPt = Interp(pm);
            Gizmos.DrawLine(currPt, prevPt);
            prevPt = currPt;
           
            Gizmos.DrawWireSphere (currPt, 0.01f);
        }
    }
}

Now youve replaced currentPoint with pts.count ?

oh yeah… but even when I swap it back it still behaves the same?

    public void AddPoint (Vector3 p, out float _t)
    {
        float uOld = tOld * (float) pts.Count - (float) currPtOld; // u = tn-c
        float tNew = (uOld + currPtOld) / (pts.Count + 1); // t = (u+c)/n
      
        _t = tNew;
      
        this.pts.Add (p);
    }
  
    public Vector3 Interp (float t)
    {
        if (this.pts.Count < 4)
            return Vector3.zero;
      
        int numSections = pts.Count - 3;
        int currPt = Mathf.Min (Mathf.FloorToInt (t * (float) numSections), numSections - 1);
      
        float u = t * (float) numSections - (float) currPt;
      
        Vector3 a = pts [currPt];
        Vector3 b = pts [currPt + 1];
        Vector3 c = pts [currPt + 2];
        Vector3 d = pts [currPt + 3];
      
        tOld = t;
        currPtOld = currPt;
      
        return .5f * ((-a + 3f * b - 3f * c + d) * (u * u * u) + (2f * a - 5f * b + 4f * c - d) * (u * u) + (-a + c) * u + 2f * b);
    }

I just noticed that although i told you

You actually used pts.Count +1 instead of numSections (which - read your script - is pts.count-3)

Do you have a demo of this working? because I really can’t figure this out :frowning:

ayeayeaaaaye
Hand-hold mode engaged

Personally i’d return the value from AddFloat instead of using an out parameter
then call it a little differently

//...
    void Update () {
        if ( Input.GetMouseButtonDown( 0 ) ) {
            t = this.spline.AddPoint( MPWorldSpace, t );
        }
    }
//...

    public float AddPoint ( Vector3 p, float t ) {
        //these 3 lines from your own script
        int numSections = pts.Count - 3;
        int currPt = Mathf.Min( Mathf.FloorToInt( t * (float) numSections ), numSections - 1 );
        float u = t * (float) numSections - (float) currPt; //here is u = tn - c


        this.pts.Add( p );


        int numSectionsNew = pts.Count - 3;

        if ( pts.Count > 4 ) { //Don't modify t if there aren't enough points yet
            return ( u + currPt ) / numSectionsNew; // here t = (u + c) / n
        } else {
            return t;
        }
    }

you beauty! thanks allot I see what you mean now!