I’m trying to create two lerps that go sequentially. I’ve done a lot of searching and there weren’t any good answers. I tried using things like dottween and iTween and have ended up with nothing.
I’m trying to move 2000 objects around. Essentially a timeline of data points from period 1 to period 2 to period 3. Using iTween destroyed my performance.
My initial method was create the first coroutine to go A to B. Then at the end of that coroutine, call my second one. The first one gets called in update. But i did debug statements and seems that they don’t get called sequentially.
t is a value between 0 and 1, which is common for any lerping library. 0 is A, 0.5 is B and 1 is C. Plug it in changing Mathf.Lerp to Vector3.Lerp and of course float ABC becomes Vector3 ABC.
Just a note, iTween is not the best choice. I would use Leantween or Dotween. Personally I use LeanTween for many things and it works great. There are different calls you can do. There are ways to create paths or at the end of a LeantTween call, you can do a OnComplete that has it do the next step when the tween is finished. Works super well.
LeanTween.move(targetObject, someVector, .5f).setOnComplete(() =>
{
//Tween is complete, do something next.
});
This is just a basic example of moving something over .5 secs and then when it’s done, it will do something else for example.
@hippocoder ,
So if i need to go from point A to B, i can’t just use a discrete value. i need to keep incrementing it. So my first thought is, call the Lerp3 function in Update, and then increment t by .1. Is this the correct approach, so that it smoothly goes from a to b to c?
Okay, so i created my lerp3 function and called it in update. I have 2 problems.
One, it runs very fast. I want it to be slower and have control over the speed. Ideally it should run about 5 seconds.
The second problem is that while the animation is running it runs at about 2 fps. Probably because there’s 2000 objects and this function is being called on update in all of them. So how can i speed this up.
Cache the Transforms of each object for looping (the transform getter has some safe-checking that only really needs to be done once during the lerp animation)
Set localposition instead of position. (setting position causes unity to internally figure out the local position via traversing the ancestry of that object’s heirarchy)
multiply all your floats together before multiplying a vector with the float. A Vector*(floatfloatfloat) gives the same result as Vectorfloatfloat*float. But the first is 6 multiplications while the latter is 9 multiplications.
keep the hierarchy of those 2000 objects as simple as possible. if each object has one child then the loop is actually affecting 4000 transforms. if each object has 4 children thats 10000 transforms to update.
affecting the rotation will also multiply the number of calculations changing the local position and rotation of each object that has 4 children is actually 20000 updates
Unity 5.5 has plans to greatly improve the performance of updating the transforms by using a dirty transform concept.
and as BoredMormon said, use a manager. the less work that you don’t have unity do for you automatically, the faster your code is going to run. One Update message is far faster than 2000 Update messages
However, the most important thing to do off to bat to figure out this problem is.
I hate to come and be the “Well, actually,” guy, but multiplying b by 2 is also wrong. You need to multiply t by the number of points - 1, and then subtract by the whatever number the leftmost point in the lerp is (so if you are lerping between points a & b, a is the 1st point and b is the 2nd, so you subtract 1).
A bad formula for this is Lerp(point1, point2, t * (numberOfPoints - 1) - leftPointNumber
So for 3 points and the 2nd lerp, it’s Mathf.Lerp(b, c, (t * 2f) - 1f);
Here’s the code for this too. Hopefully, this saves someone in the future from 3 hours of figuring it out.
Hey ! Here is a version less visible but without condition (indispensable for shader) :
// t between -1 and 1
float Lerp3(float a, float b, float c, float t)
{
return lerp(lerp(a, b, min(t, 0.0f) + 1.0f), c, max(t, 0.0f));
}
With t include in [-1, 1]. To convert t from [0, 1] to [-1, 1] apply:
// t between 0 and 1
float Lerp3(float a, float b, float c, float t)
{
t = t * 2.0f - 1.0f;
return lerp(lerp(a, b, min(t, 0.0f) + 1.0f), c, max(t, 0.0f));
}
I would also like to state a limitation of all the proposed formulas. If we consider the area under the curve that occupies a, b and c. So, a will occupy 25%, b 50% and c 25%. If anyone has a mathematical solution to this problem, I would be curious to know it.
This isn’t terribly optimal. Not your fault per se, just wanted to add that if someone wants to call this in a loop, it’s better to turn this into a dedicated class and internally cache the distances. Also it should’ve been magnitude (instead of sqrDistance) on line 22.
Here’s an example
using System;
using UnityEngine;
public class MultiPointLerp {
static readonly Vector3 _NaN => new(float.NaN, float.NaN, float.NaN);
Vector3[] _pts; // points
float[] _dist; // cached distances
float? _total; // cached total distance
public Vector3 this[int index] => _pts[index];
public int Count => _pts.Length;
public MultiPointLerp(params Vector3[] points) {
if(points is null) throw new ArgumentException();
_pts = new Vector3[points.Length];
Array.Copy(points, 0, _pts, 0, points.Length);
}
public MultiPointLerp(Vector3[] points, int index, int count) {
if(points is null) throw new ArgumentException();
_pts = new Vector3[count];
Array.Copy(points, index, _pts, 0, count);
}
public float TotalDistance { get {
if(!_total.HasValue) _total = getTotalDistance();
return _total.Value;
} }
public float GetDistance(int index) {
_dist??= getDistances();
return _dist[index];
}
public Vector3 GetDirection(int index) {
if(index >= Count - 1) throw new ArgumentOutOfRangeException();
var seg = GetSegment(index);
return (seg.b - seg.a).normalized;
}
public (Vector3 a, Vector3 b) GetSegment(int index) {
if(index >= Count - 1) throw new ArgumentOutOfRangeException();
return ( this[index], this[index+1] );
}
public float GetInterpolant(float time, out int index) {
index = 0;
if(Count == 0) { index = -1; return 1f; }
if(Count == 1 || time <= 0f) return this[0];
if(time >= 1f) return this[Count - 1];
var c = TotalDistance * time;
while(c - _dist[index] > 0f) c -= _dist[index++];
return c / _dist[index];
}
public Vector3 GetSegmentPoint(int index, float t)
=> (1f - t) * this[index] + t * this[index+1];
public Vector3 GetPoint(float time) {
var t = GetInterpolant(time, out int index);
if(index < 0) return _NaN;
return GetSegmentPoint(index, t);
}
float getTotalDistance() {
_dist??= getDistances();
var total = 0f;
for(int i = 0; i < _dist.Length; i++)
total += _dist[i];
return total;
}
float[] getDistances() {
var d = new float[Count - 1];
for(int i = 0; i < Count - 1; i++)
d[i] = (this[i+1] - this[i]).magnitude;
return d;
}
}
(haven’t tested it; edit: fixed some problems; edit: added some stuff etc)
I almost wanted to point this out as well. When doing a distance / length normalized lerp you almost always want to calculate and store that normalization data only once and reuse it. Once the lerp as started the positions in the array should not change anyways, otherwise you would get strange effects while lerping.