I’m working on a ledge-hanging system that uses bezier curves to define the edge that can be hung from/shimmied along.
I discovered that one cannot simply get the next point on the edge by increasing the parameter “time” as you’ll get points that are spaced unevenly.
I found this primer on Bezier curves that told me I would have to compile a Look Up Table of time-for-distance values in order to move along the curve by a specific amount.
I do that by looping through the curve by time and adding up the distance between the current and previous points to get the distance of each point in the table.
Like So
private BezierTableData[] _LUT;
public BezierTableData[] LookUpTable
{
get
{
if(dirty || _LUT == null)
{
int segResolution = 50;
int tableSize = close ? segResolution * points.Length : segResolution * points.Length - 1;
_LUT = new BezierTableData[tableSize + 1]; //wipe the array
float totalDist = 0f;
float totalTime = 0f;
int luP = 0;
for(; totalTime < 1.0f; totalTime += 1.0f/tableSize) //loop through the entire curve in equal steps of time
{
Vector3 point = this.GetPointAt(totalTime); // get point at this time
if(totalTime != 0) //if this is not the first loop
{
totalDist += Vector3.Distance(point, _LUT[luP - 1].pos); //add the distance between this and the last point.
}
_LUT[luP] = new BezierTableData(point, totalTime, totalDist); //set the look up point.
luP++; //increment total look up point index
}
dirty = false;
}
return (BezierTableData[])_LUT.Clone(); // return a clone of the table so that we can't mess with it.
}
}
So I figure now to find a point by distance, I can just find the two Look Up Points that have a greater and smaller distance, respectively, and return a point on the vector between those two points and it would be close enough.
Like this I thought.
public Vector3 GetPointByDist(BezierCurve ledge, float dist)
{
BezierTableData[] table = ledge.LookUpTable;
Vector3 point = Vector3.zero;
if (dist > ledge.length) //handle closed loops
{
if (ledge.close)
{
dist -= ledge.length;
}
else
{
dist = ledge.length;
}
}
for (int i = 0; i < table.Length - 1; i++)
{
if (table[i + 1].d >= dist && table[i].d <= dist) //find what two points we are between
{
dist -= table[i].d; //get just the distance from the next point.
Vector3 btween = table[i + 1].pos - table[i].pos;
point = table[i + 1].pos + (btween * dist);
}
}
return point;
}
To move along the curve, I thought I would simply find the distance of the current position on the curve, and then add the amount of distance I wish to move, get that point by the distance, and move to it.
Current distance on the curve
public float GetDistByPoint(BezierCurve ledge, Vector3 origin) // get the distance of a point on the curve
{
BezierTableData[] table = ledge.LookUpTable;
float dist = 0f; //how far this point is on the curve in units.
float closestDist = 999f;
float bcTime = 0;
//look through our LUT to find the points we are between
for (int i = 0; i < table.Length; i++)
{
float pointDist = Vector3.Distance(origin, table[i].pos); //distance between the origin and this table point
if (pointDist < closestDist) //if this table point is closest.
{
bcTime = table[i].t; //grab the time of the nearest table point
closestDist = pointDist; // set the closest distance
dist = table[i].d; // grab the closest table point
//tablePointDist = table[i].d;
}
}
float interval = (1.0f / table.Length);
//do a binary search to find a more exact point
for (int i = 0; i < 10; i++) // max of 10 loops cause aint nobody got the time for that!
{
Vector3 p1 = ledge.GetPointAt(bcTime + (interval / 2.0f)); //grab a point on either side of the table point and get the distance to the origin
Vector3 p2 = ledge.GetPointAt(bcTime - (interval / 2.0f));
float p1Dist = Vector3.Distance(origin, p1);
float p2Dist = Vector3.Distance(origin, p2);
if (p1Dist < closestDist) //if we have found a point that is closer to the origin, we keep it.
{
bcTime += (interval / 2.0f); //adjust the time of the point we are getting.
closestDist = p1Dist; // set the distance to beat.
dist += p1Dist; // add the distance to this new point
//Debug.Log("Getting closer");
}
else if (p2Dist < closestDist)
{
bcTime -= (interval / 2.0f);
closestDist = p2Dist;
dist += p2Dist;
//Debug.Log("Getting closer");
}
if (p1Dist > closestDist || p2Dist > closestDist) // if either point has overshot the origin, we need to shrink our interval.
{
if (interval > .0001f) //if our interval isn't small enough
{
interval = (interval / 2.0f);
}
else //interval is really really small.
{
//there's our point!
//Debug.Log("Got Point from binary search");
break; // break out of the loop because we are done
}
}
}
return dist;
}
Add distance and move
cur_dist = GetDistByPoint(bc, closestPoint);
float moveDist = (moveSpeed * inpX) * Time.deltaTime/10; //how far we should move in distance
Vector3 nextPoint = GetPointByDist(bc, cur_dist + moveDist);
transform.position = nextPoint;
cur_dist = GetDistByPoint(bc, closestPoint);
float moveDist = (moveSpeed * inpX) * Time.deltaTime/10; //how far we should move in distance
Vector3 nextPoint = GetPointByDist(bc, cur_dist + moveDist);
transform.position = nextPoint;
But when I try to move a Transform around the curve this way, I still get a big slowdown in movement around the control points of the Bezier curve, similar to when I was using Time to find points.
I’ve got that feeling like the issue is something dumb, but I’d really appreciate another pair of eyes on this. Thanks for your time!