Travel time math problem

tin

Joined
Jan 20, 2025
Messages
6
Reaction score
0
Hi =) this is my first post here so i might be doing something wrong!(if so just tell me)
I am working on a game in unity C#. but i felt this question might fit better here since it's not related to unity in any way!
My problem: I want to know the time it takes to travel and stop at a distance/position assuming my initial position to be 0. What I know is: initial velocity (v0) , acceleration(a), distance (d) and max velocity(v_max). To complicate it even more I want VelocityDirection and separate times for the following stages: (stage1/accel time, stage2/cruice time, stage3/decel time) but combined they should form total time.
I have managed to get it somewhat working but im constantly running into scenarios where it won't really work. I'd think this problem is already solved except for my stage complication. But I could probably make that work in another way if someone could help me calculate the TotalTime. important to note that d and v0 can both be positive and negative the values i have been using lately are: v0 = +-34, a=25, v_max = 50, d = 45.

I couldn't post images so here is a link to my image illustration of 7 different scenarios and the desired movement:


Here is my flawed code for reference.
//////////////////////////////////////////////////////////////
private struct ManoverStages
{
public float stage1time;
public float stage2time;
public float stage3time;
public float stage1dir;
public float stage3dir;
public ManoverStages(float stage1, float stage2, float stage3, float dir1, float dir3)
{
this .stage1time = stage1;
this .stage2time = stage2;
this .stage3time = stage3;
this .stage1dir = dir1;
this .stage3dir = dir3;
}
}

private ManoverStages TimeNeadedForManover(float v0, float a, float v_lim, float d)
{
//INITIAL VELOCCITY IS ON COURCE
bool onCource = v0 * d > 0;
bool willOverShoot;
//NORMALIZE DISTANCE
d = math.abs(d);
v0 = math.abs(v0);
//DIRECTION
float dir1 = 1;
float dir3 = -1;
//TIME
float t1;
float t2;
float t3;
//DISTANCE
float d1;
float d2;
float d3;
//REACHABLE LIMIT
float v_reach;
float t_lim;
float d_lim;
//DELTA INITIAL
float Dt0 = (0 - v0) / a;
float Dd0 = ((0 + v0) / 2) * Dt0;
//CHECK OVER SHOOT
willOverShoot = d < Dd0;

//UPDATE DISTAANCE
if(!onCource)
{
d += Dd0;
}
else if(Dd0 > d)
{
t3 = Dt0;
d3 = Dd0;
d -= Dd0;
}
//VELLOCITY ACHEVABLE ON HALF THE DISTANCE
float v_half = math.sqrt(2 * a * d / 2);
//SET REACH
v_reach = v_lim > v_half ? v_half : v_lim;
t_lim = (v_reach - 0) / a;
d_lim = (0 + v_reach) / 2 * t_lim;
//ACCELERATING STAGE
t1 = t_lim;
d1 = d_lim;
if (onCource)
{
t1 -= Dt0;
d1 -= Dd0;
//t1 = (v0 - v_reach) / a;
//d1 = (v_reach + v0) / 2 * t1;
}
//DECELERATING STAGE
t3 = t_lim;
d3 = d_lim;
//CRUICE STAGE
d2 = d - d1 - d3;
t2 = d2 / v_lim;

return new ManoverStages((onCource ? t1 : t1 + Dt0), t2,t3,dir1,dir3);
}
 
Joined
Sep 21, 2022
Messages
226
Reaction score
36
I think there are two ways to do something like this.

(1) Figure out all the different cases, then work out the math for each one. This means the human is doing all the work, very error prone, and the program is just acting like a spreadsheet.

(2) Simulate a pilot and a ship, with very small time intervals.

The pilot is just a function that given position, velocity, and target position, returns 3 possible states: accelerate, decelerate, or cruise.

The ship routine just updates the position based on what the pilot decides, records how much time is used on each state, and decides when to stop.

The pilot logic would look something like:
Code:
P: ship position
V: ship velocity
D: target position

compute minimum stop distance
(the MSD math is not too complicated)
IF (going in wrong direction) OR (too fast) THEN
  RETURN "D"
ELSEIF ABS(D-P)<=MSD THEN
  RETURN "D"
ELSEIF ABS(V)<MAX THEN
  RETURN "A"
ELSE
  RETURN "C"
ENDIF
If it were me, I would code (2) first, because the logic is simple and the math only requires basic algebra.

If that didn't give me the right answers, I'd go with method (1).
 
Joined
Sep 21, 2022
Messages
226
Reaction score
36
Here is some pseudocode.

A couple of notes.

The velocity can not be exactly zero, the ship and pilot use the sign for direction.

The first version wouldn't cruise, the pilot accelerated when just under the maximum speed, and decelerated when just over, so it toggled. I added a threshold to prevent that.
Code:
ACC=25
VMAX=50

FUNC MSD(V)
'minimum stopping distance
LOCAL T
T=ABS(V)/ACC
RETURN (0.5)*ACC*T*T
END

FUNC PILOT(P,V,D)
LOCAL E,O
E=1:'threshold
IF (P<D AND V<0) OR (D<P AND 0<V) THEN
  O="D":'wrong way
ELSEIF (VMAX+E)<ABS(V) THEN
  O="D":'too fast
ELSEIF ABS(D-P)<=MSD(V) THEN
  O="D":'stopping
ELSEIF ABS(V)<(VMAX-E) THEN
  O="A":'too slow
ELSE
  O="C"
ENDIF
RETURN O
END

FUNC SHIP(P0,V0,D)
'P0: initial position
'V0: initial velocity
'D: target position
LOCAL DV,I,P,T1,T2,T3,V,X
T1=0:'accel time (+ accel)
T2=0:'cruise time (0 accel)
T3=0:'decel time (- accel)
V=V0
IF V==0 THEN V=0.0001
I=1/10000:'time interval
P=P0
WHILE 0.001<ABS(P-D)
  X=PILOT(P,V,D)
  DV=ACC*I:'change in V
  IF X=="A" THEN
    IF V<0 THEN
      V-=DV
      T3+=I
    ELSE
      V+=DV
      T1+=I
    ENDIF
  ELSEIF X=="D" THEN
    IF V<0 THEN
      V+=DV
      T1+=I
      IF V==0 THEN V=0.0001
    ELSE
      V-=DV
      T3+=I
      IF V==0 THEN V=-0.0001
    ENDIF
  ELSE
    T2+=I
  ENDIF
  P += (V*I)
WEND
RETURN [T1,T2,T3]
END
At the moment, I don't see a good way to test this.
 
Joined
Jan 23, 2025
Messages
3
Reaction score
0
Calculate the time to accelerate to v_max and the distance covered.
Check if v_max is achievable within the given distance, or if the target distance is reached before hitting v_max.
Calculate the deceleration time (if applicable) and the distance covered during deceleration.
If a cruise phase exists (i.e., if you reach v_max), calculate the cruise time (t2).
 
Joined
Jan 23, 2025
Messages
3
Reaction score
0
Calculate the time to accelerate to v_max and the distance covered.
Check if v_max is achievable within the given distance, or if the target distance is reached before hitting v_max.
Calculate the deceleration time (if applicable) and the distance covered during deceleration.
If a cruise phase exists (i.e., if you reach v_max), calculate the cruise time (t2).
You're using absolute values for the distance (d) and initial velocity (v0), but the direction of movement (dir1, dir3) seems to be getting mixed up in terms
 
Joined
Jan 23, 2025
Messages
3
Reaction score
0
You're using absolute values for the distance (d) and initial velocity (v0), but the direction of movement (dir1, dir3) seems to be getting mixed up in terms
(willOverShoot) seems to be based on the assumption that deceleration will always result in stopping at the target distance, check whether you can actually reach the target in the acceleration phase, not just deceleration.
 

tin

Joined
Jan 20, 2025
Messages
6
Reaction score
0
Here is some pseudocode.

A couple of notes.

The velocity can not be exactly zero, the ship and pilot use the sign for direction.

The first version wouldn't cruise, the pilot accelerated when just under the maximum speed, and decelerated when just over, so it toggled. I added a threshold to prevent that.
Code:
ACC=25
VMAX=50

FUNC MSD(V)
'minimum stopping distance
LOCAL T
T=ABS(V)/ACC
RETURN (0.5)*ACC*T*T
END

FUNC PILOT(P,V,D)
LOCAL E,O
E=1:'threshold
IF (P<D AND V<0) OR (D<P AND 0<V) THEN
  O="D":'wrong way
ELSEIF (VMAX+E)<ABS(V) THEN
  O="D":'too fast
ELSEIF ABS(D-P)<=MSD(V) THEN
  O="D":'stopping
ELSEIF ABS(V)<(VMAX-E) THEN
  O="A":'too slow
ELSE
  O="C"
ENDIF
RETURN O
END

FUNC SHIP(P0,V0,D)
'P0: initial position
'V0: initial velocity
'D: target position
LOCAL DV,I,P,T1,T2,T3,V,X
T1=0:'accel time (+ accel)
T2=0:'cruise time (0 accel)
T3=0:'decel time (- accel)
V=V0
IF V==0 THEN V=0.0001
I=1/10000:'time interval
P=P0
WHILE 0.001<ABS(P-D)
  X=PILOT(P,V,D)
  DV=ACC*I:'change in V
  IF X=="A" THEN
    IF V<0 THEN
      V-=DV
      T3+=I
    ELSE
      V+=DV
      T1+=I
    ENDIF
  ELSEIF X=="D" THEN
    IF V<0 THEN
      V+=DV
      T1+=I
      IF V==0 THEN V=0.0001
    ELSE
      V-=DV
      T3+=I
      IF V==0 THEN V=-0.0001
    ENDIF
  ELSE
    T2+=I
  ENDIF
  P += (V*I)
WEND
RETURN [T1,T2,T3]
END
At the moment, I don't see a good way to test this.
wow! Thanks for all the examples! =) I totally agree about not wanting to figure out all the different cases in your example 1. I feel I'm slowly doing just that when I was trying to make my formula work for all cases. If i understood you completely the second alternative was to run a simulation?! But since i'm going to be doing these calculations realtime in one frame/mid game. I don't think running a simulation is such a good idea might be wrong! I'm going to lock into it and if i misunderstood you i might be back soon when i have taken a better look.
 

tin

Joined
Jan 20, 2025
Messages
6
Reaction score
0
Calculate the time to accelerate to v_max and the distance covered.
Check if v_max is achievable within the given distance, or if the target distance is reached before hitting v_max.
Calculate the deceleration time (if applicable) and the distance covered during deceleration.
If a cruise phase exists (i.e., if you reach v_max), calculate the cruise time (t2).
This is exactly the route I first took but soon realized more and more if statements like the ones you have thought of for example there is a chance it can't break in time and has to " reverse" after passing the destination. I tried to show all different scenarios I could think of in my picture but as far as I can see, I am always going to hardcode a lot of different scenarios like @WhiteCube mentioned. which I want to avoid at any cost.
You're using absolute values for the distance (d) and initial velocity (v0), but the direction of movement (dir1, dir3) seems to be getting mixed up in terms
hahaha might be doing something funky ther... but the reason I am using absolute values is that I can't use square root else. which again leads to future problems of having to manually reverse it for some specific (InitialVelocity,destination) scenarios.
(willOverShoot) seems to be based on the assumption that deceleration will always result in stopping at the target distance, check whether you can actually reach the target in the acceleration phase, not just deceleration.
Will overShoot is true if it can NOTdecelerate from the initialVelocity to stop before the destination. if the initial velocity is away from the destination my code just adds: (time needed to slow initialVelocity) to the acceleration stage and extends the distance to include the total distance. but it's again messy and I have rewritten it all 4 times but it's still unreadable.
 

tin

Joined
Jan 20, 2025
Messages
6
Reaction score
0
What I'm currently Looking for is a neat radable equation that works mostly on its own or perhaps is divided into many small readable equations. but that takes directions into account and most importantly calculates the whole time even if it starts with opposite (initialVelocity and destination) or overshoots the destination. If you got any other solution pls let me know =). Still looking into your code @WhiteCube.
 

tin

Joined
Jan 20, 2025
Messages
6
Reaction score
0
I have written some new code i am yet to test it so there might be some errors, but i have to go for the moment and don't want to leave you guys hanging so thanks @supremedee for making me realize i was on the right track! and thx @WhiteCube for your code it made me realize how overcomplicated i was making the if statements for myself. PS tell me if this was what you intended on from the beginning because i still don't really understand your simulation idea.
Here is the current code. If I know myself I will be back soon with problems! haha =P
C#:
 private (float time,float dir)[] TimeNeadedForManover(float v0, float a, float v_lim, float d)
 {
     List<(float time,float dir)> timeDir = new List<(float,float)> ();
     float currentDist = d;
     float currentVel = v0;

     void addStage(float timeSpent, float newVel, float distChange)
     {           
         float dir = Utility.Normalize(currentVel - newVel); // sets to one of the folowing -1 , 0 , 1
         timeDir.Add((timeSpent,dir));
         currentDist += distChange;
         currentVel = newVel;
     }
     // INITIAL
     {
         float initialStopTime = GetTime(v0, 0, a);
         float initialStopDist = GetDist(v0, 0, initialStopTime);
         // WRONG DIRECTION
         if (v0 * d < 0)
         {
             addStage(initialStopTime, 0, initialStopDist);
         }
         // OVERSHOOT
         else if (initialStopDist > d || initialStopDist < d)
         {
             addStage(initialStopTime, 0, -initialStopDist);
         }
         // VELOCITY TO HIGH
         else if (v0 > v_lim || v0 < -v_lim)
         {
             float timeSlowing = GetTime(v0, v_lim, a);
             float distSlowing = GetDist(v0, v_lim, timeSlowing);
             addStage(timeSlowing, v_lim, -timeSlowing);
         }
     }       
     // ACCELERATING
     {
         float midRemainingDist = currentDist / 2;
         float halfwayVel = GetVel(currentVel, a, midRemainingDist);
         float reachedVel = halfwayVel;
         if (halfwayVel > v_lim)
         {
             reachedVel = v_lim;
         }
         float accelerateTime = GetTime(currentVel, reachedVel, a);
         float accelerateDist = GetDist(currentVel, reachedVel, accelerateTime);
         addStage(accelerateTime, reachedVel, -accelerateDist);
     }
     // DECELLERATING
     {
         float breakTime = GetTime(currentVel, 0, a);
         float breakDist = GetDist(currentVel, 0, breakTime);
         currentDist -= breakDist;
         if (currentDist != 0) // +CRUICING
         {
             float cruiceTime = currentDist / v_lim;
             addStage(cruiceTime, v_lim, -currentDist);
         }
         addStage(breakTime, 0, 0/*breakDist already subtracted*/);
     }
     return timeDir.ToArray ();
 }   
 /// <summary>
 /// t = (v-v0)/a
 /// </summary>   
 private float GetTime(float v0, float v, float a)
 {
     return (v - v0) / a;
 }
 /// <summary>   
 /// d = ((v0 + v)/2)*t
 /// </summary>   
 private float GetDist(float v0, float v, float t)
 {
     return (v0 + v) / 2 * t;
 }
 /// <summary>
 /// v^2 = 2ad + v0^2
 /// </summary>   
 private float GetVel(float v0, float a, float d)
 {       
     float dir = Utility.Normalize(d); // sets to one of the folowing -1 , 0 , 1
     d = math.abs(d);
     return dir * math.sqrt((2 * a * d) + (v0 * v0));
 }
 
Joined
Sep 21, 2022
Messages
226
Reaction score
36
(1) hard math, fast routine

(2) easy math, slow routine

One other option is to precompute.

This reminds me of a machine code routine that drew circles, the author needed it fast, but floating point routines were very slow at the time, so instead of calling a cosine function, they put some cosine values into the program as data.

The circles were not perfect, but they were good enough.

You could use (2) to calculate as many scenarios as the game needs. Hard code that data into your program. At run time, select the closest one.
 
Joined
Sep 21, 2022
Messages
226
Reaction score
36
Simulation versus formula:

Consider the distance formula: d = (0.5)*a*t*t

Works fine when the object's initial (or final) velocity is zero. It can be changed to use an initial velocity, but that change is not obvious. (not to me)

This loop on the other hand, is very easy to change.

Turn the V variable into a function argument, then it can begin at any value.
Code:
FUNC distance(A,T)
'A:acceleration
'T:time (duration)
LOCAL D,E,F,I,V
V=0
E=1000:'time slices
F=T/E:'time interval
D=0:'distance
FOR I=1 TO E
 D+=(V*F)
 V+=(A*F)
NEXT
RETURN D
END
 

tin

Joined
Jan 20, 2025
Messages
6
Reaction score
0
One other option is to precompute.
Yeah this might be necessary to some degree! because it's a pretty beefy chunk of code to run in one frame. I could do something combining the best of all worlds. I don't need cruising or breaking time while accelerating so perhaps I can load it slowly and not all at once. Some scenarios can be precomputed. The precision is not critical, it's actually not used for distances but instead rotation but it's the exact same concept so thought i would keep that out of the description to not confuse anyone. and a couple of degrees here or there is not so bad. also it's hard to precompute for any potential initial velocity.
Consider the distance formula: d = (0.5)*a*t*t
I cant find any documentation about this formula where did you find it?
Might be correct I just dont whant to inplement somthing im not sertent about.
Turn the V variable into a function argument, then it can begin at any value.
Must be honest i find it hard to read whatever language you code in i'm not so well rounded. but i really appreciate your concepts and solutions!!
I have actually got the code running and working altho it took quite a few changes, mostly reversing values and such.
C#:
/// <summary>
/// returnes stages with Time and direction
/// </summary>   
public static (float time, float dir)[] TimeNeadedForManover(float v0, float a, float v_lim, float d)
{
    List<(float time, float dir)> timeDir = new List<(float, float)>();
    float currentDist = d;
    float currentVel = v0;

    void addStage(float timeSpent, float newVel, float distChange)
    {
        float dir;
        if (newVel == currentVel) //Cruicing
            dir = Normalize(currentVel);
        else
            dir = Normalize(-(currentVel - newVel));
        timeDir.Add((timeSpent, dir));
        currentDist -= distChange;
        currentVel = newVel;
    }
    // INITIAL
    {
        float initialStopTime = GetTime(v0, 0, a);
        float initialStopDist = GetDist(v0, 0, initialStopTime);
        // WRONG DIRECTION
        if (v0 * d < 0)
        {
            addStage(initialStopTime, 0, initialStopDist);
        }
        // OVERSHOOT
        else if (initialStopDist > Mathf.Abs(d) || initialStopDist < -Mathf.Abs(d))
        {
            addStage(initialStopTime, 0, initialStopDist);
        }
        // VELOCITY TO HIGH
        else if (v0 > v_lim || v0 < -v_lim)
        {
            float v = Normalize(v0) * v_lim;
            float timeSlowing = GetTime(v0, v, a);
            float distSlowing = GetDist(v0, v, timeSlowing);
            addStage(timeSlowing, v, distSlowing);
        }
    }
    // ACCELERATING
    {
        float currentStopTime = GetTime(currentVel, 0, a);
        float currentStopDist = GetDist(currentVel, 0, currentStopTime);
        float halfWayDist = (currentDist - currentStopDist) / 2;
        float halfWayVel = GetVel(currentVel, a, halfWayDist);
        float reachedVel = halfWayVel;
        if (halfWayVel > v_lim || halfWayVel < -v_lim)
        {
            float v = Normalize(halfWayVel) * v_lim;
            reachedVel = v;
        }
        float accelerateTime = GetTime(currentVel, reachedVel, a);
        float accelerateDist = GetDist(currentVel, reachedVel, accelerateTime);
        addStage(accelerateTime, reachedVel, accelerateDist);
    }
    // DECELLERATING
    {
        float breakTime = GetTime(currentVel, 0, a);
        float breakDist = GetDist(currentVel, 0, breakTime);
        { // +CRUICING
            float cruiceDist = currentDist - breakDist;
            float cruiceTime = Mathf.Abs(cruiceDist / v_lim);
            addStage(cruiceTime, currentVel, cruiceDist);
        }
        addStage(breakTime, 0, breakDist);
    }
    return timeDir.ToArray();
}
/// <summary>
/// sets to one of the folowing -1 , 0 , 1;
/// </summary>   
public static float Normalize(float value)
{
    if (value == 0)
        return 0;
    return Mathf.Abs(value) / value;
}
/// <summary>
/// t = (v-v0)/a
/// </summary>   
public static float GetTime(float v0, float v, float a)
{
    return Mathf.Abs((v - v0) / a);
}
/// <summary>   
/// d = ((v0 + v)/2)*t
/// </summary>   
public static float GetDist(float v0, float v, float t)
{
    return (v0 + v) / 2 * t;
}
/// <summary>
/// v^2 = 2ad + v0^2
/// </summary>   
public static float GetVel(float v0, float a, float d)
{
    float dir = Utility.Normalize(d); // sets to one of the folowing -1 , 0 , 1
    d = math.abs(d);
    return dir * math.sqrt((2 * a * d) + (v0 * v0));
}
I am yet to find any scenarios where it wont work (pic↓) (within a 2 degree margin), with (a = 25, v_lim = 50). Might try to close it a little but I'm not concerned about it.
Screenshot 2025-01-20 222422.png
 
Joined
Sep 21, 2022
Messages
226
Reaction score
36
You seem to have a good grasp on your problem so I won't comment any more about that.

I post pseudocode. Its a description of the steps in an algorithm using a mix of conventions of programming languages with informal, usually self-explanatory, notation of actions and conditions.

The purpose is that programmers would translate a logic diagram into pseudocode, then pseudocode into actual code. In theory, it should be readable by any programmer. I should work on that.

I don't post actual code because that's unpaid work.

I get the distance formula from an acceleration constant, apply a bit of caculus to get a velocity function, and again to get a distance function.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,249
Messages
2,571,244
Members
47,876
Latest member
Kiptechie

Latest Threads

Top