[Possibly Solved] Math problem for limiting a rigidbodies velocity

I am having trouble thinking of what the math would be to limit how much velocity I add to my rigidbody.

Here are some examples to better explain my goals
Click for examples

- Example 1-
maxVelocity = 4,0,0
addingVelocity = 2,0,0
myRigidbody.velocity = 3,0,0

goal velocity to add = 1,0,0

- Example 2-
maxVelocity = 4,0,0
addingVelocity = 2,0,0
myRigidbody.velocity = 5,0,0

goal velocity to add = 0,0,0

- Example 3-
maxVelocity = -4,0,0
addingVelocity = -2,0,0
myRigidbody.velocity = 5,0,0

goal velocity to add = -2,0,0

Note - The Mathf.Sign() of the maxVelocity and addingVelocity will always be the same.

My goal is to only add the required amount of velocity to reach the maxVelocity, however, if the rigidbody is already at or above the maxVelocity, then I don’t want to add anything.

Please note that doing Vector3.ClampMagnitude(addingVelocity, maxVelocityMagnitude) is not what I am looking for. That would make it so the addingVelocity does not go over the maxVelocity, but it wont give me the minimum velocity required.

This seems like it should be simple math, but whats getting me is the values signs.

Thank you for any help!

EDIT - Just to try and better explain my goal…
I need to find out how much I can add to move towards my target max velocity.
The maxVelocity is more so the maxVelocityToHave, not the overall max velocity of the rigidbody.
Please keep in mind that I do not know what the goal velocity is. The goal velocity is what we are trying to figure out.

EDIT 2 -
This may be a possible solution, but need to test it.
One big problem this has is it causes you to move faster when going against walls that have no friction.
Click for possible solution

    public static void LimitAddRelativeForce(this Rigidbody myRigidbody, Vector3 velocity, float maxMagnitude, Vector3 clampAxis, ForceMode forceMode)
    {
        myRigidbody.AddRelativeForce(LimitLocalVelocity(myRigidbody, velocity, maxMagnitude, clampAxis), forceMode);
    }

    public static void LimitHorizontalAddRelativeForce(this Rigidbody myRigidbody, Vector3 velocity, float maxMagnitude, ForceMode forceMode)
    {
        LimitAddRelativeForce(myRigidbody, velocity, maxMagnitude, new Vector3(1, 0, 1), forceMode);
    }
    public static Vector3 LimitVelocity(Rigidbody myRigidbody, Vector3 velocity, float maxMagnitude, Vector3 clampAxis)
    {
        Vector3 clampedVelocity = ClampVelocity(myRigidbody, velocity, maxMagnitude, clampAxis);
        return LimitVelocity(myRigidbody, velocity, clampedVelocity, maxMagnitude);
    }
    public static Vector3 LimitLocalVelocity(Rigidbody myRigidbody, Vector3 velocity, float maxMagnitude, Vector3 clampAxis)
    {
        Vector3 clampedVelocity = ClampLocalVelocity(myRigidbody, velocity, maxMagnitude, clampAxis);
        return LimitVelocity(myRigidbody, velocity, clampedVelocity, maxMagnitude);
    }
    static Vector3 LimitVelocity(Rigidbody myRigidbody, Vector3 velocity, Vector3 clampedVelocity, float maxMagnitude)
    {
        for(int i = 0; i < 3; i++)
        {
            if(Mathf.Sign(clampedVelocity[i]) == Mathf.Sign(velocity[i]))
            {
                clampedVelocity[i] = Mathf.Sign(clampedVelocity[i]) * (Mathf.Clamp(Mathf.Abs(clampedVelocity[i]), 0, Mathf.Abs(velocity[i])));
            }else{
                clampedVelocity[i] = 0;
            }
        }
        return clampedVelocity;
    }
    public static Vector3 ClampVelocity(Rigidbody myRigidbody, Vector3 velocity, float maxMagnitude, Vector3 clampAxis)
    {
        return Vector3.ClampMagnitude(velocity - myRigidbody.velocity, maxMagnitude).SelectAxis(clampAxis);
    }

    public static Vector3 ClampLocalVelocity(Rigidbody myRigidbody, Vector3 velocity, float maxMagnitude, Vector3 clampAxis)
    {
        Vector3 clampedVelocity = ClampVelocity(myRigidbody, myRigidbody.transform.TransformDirection(velocity), maxMagnitude, Vector3.one);
        return myRigidbody.transform.InverseTransformDirection(clampedVelocity).SelectAxis(clampAxis);
    }
    public static Vector3 SelectAxis(this Vector3 vector, Vector3 axis)
    {
        Vector3 selectedAxis = Vector3.zero;
        if(axis.x != 0) selectedAxis.x = vector.x;
        if(axis.y != 0) selectedAxis.y = vector.y;
        if(axis.z != 0) selectedAxis.z = vector.z;

        return selectedAxis;
    }

The usage looks like this…

Vector3 targetVelocity = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized * force;
myRigidbody.LimitHorizontalAddRelativeForce(targetVelocity, force, ForceMode.VelocityChange);

Other possible solutions (that may need more added to work) are…
Click for other possible solutions

Posted by Todd Wasson
Click for code

        Vector3 maxVelocity = velocity.normalized * maxMagnitude;
        Vector3 projectedVelocity = myRigidbody.velocity + velocity;
        Vector3 difference = Vector3.zero;

        if(myRigidbody.velocity.magnitude < maxVelocity.magnitude)
        {
            if(projectedVelocity.magnitude > maxVelocity.magnitude )
            {
               difference = projectedVelocity - maxVelocity;
            }
        }
        return velocity - difference;

Posted by hpjohn
Click for code

        float maxMagnitude = 4;
        Vector3 addVelocity = new Vector3( 1, 0, 2 ); //or whatever from input
        Vector3 maxVelocity = addVelocity.normalized * maxMagnitude;
        Vector3 currentVelocity = GetComponent<Rigidbody>().velocity;

        Vector3 goalVelocity = Vector3.zero;

        for ( int i = 0; i < 3; i++ ) {
            if ( Mathf.Sign( maxVelocity[i] ) != Mathf.Sign( currentVelocity[i] ) ) {
                goalVelocity[i] = Mathf.Min( Mathf.Abs( addVelocity[i] ), Mathf.Abs( maxVelocity[i] ) ) * Mathf.Sign( addVelocity[i] );
            } else if ( Mathf.Abs( currentVelocity[i] ) < Mathf.Abs( maxVelocity[i] ) ) {
                goalVelocity[i] = Mathf.Min( Mathf.Abs( addVelocity[i] ), Mathf.Abs( maxVelocity[i] - currentVelocity[i] ) ) * Mathf.Sign( addVelocity[i] );
            }
        }

I posted that below, but decided to bring it up here since most of this thread is just me failing to explain what exactly it is that I am wanting. You can probably skip reading what is below.

are those values supposed to represent local or world velocity?

It will be world since myRigidbody.velocity is world. Local space conversions are done beforehand for the addingVelocity.
In other words, dont worry about the vectors space.

Not sure if it’s a good idea to be adding the velocity directly like this instead of using forces and letting the physics take care of it, but if that’s what you want to do, here’s how I’d go about it:

What I’d try doing first is computing the velocity it would attain without any limit (I’ll call it projectedVelocity). Then just do a subtraction to pop the projectedVelocity to the maxVelocity. If you don’t mind using an if() statement, something like this might work (untested, just doing this off the top of my head):

//Using your variables
//Given:
Vector3 maxVelocity = ...;
Vector3 addingVelocity = ...;
myRigidbody.velocity = ....;

//New variables:
Vector3 projectedVelocity = myRigidbody.velocity + addingVelocity;
Vector3 difference = Vector3.zero;

//A couple of different ways for limiting the velocity come to mind that concern the direction of the final velocity vector. 
//Probably the easiest way with this approach would be to make sure the final velocity is pointed in the same direction as it would be if the velocity were unrestricted. 
//So you're letting the direction change as it would without the maxVelocity restriction, but then scaling back the velocity:

if(projectedVelocity.magnitude > maxVelocity.magnitude)
{
    difference = projectedVelocity - maxVelocity;
}

Vector3 goalVelocityToAdd = addingVelocity - difference;

When thinking about this stuff I visualize 3D vectors instead of thinking about it component by component where signs get confusing. When you’re trying to cap a vector, you just create a new vector that joins the head of one to the tail of the other by subtracting them to get the difference.

When you’ve got it running correctly, I’d recommend reading my article here to make it run a lot faster if this is performance critical:

http://www.performancesimulations.com/wp/how-to-get-big-speed-increases-in-unitys-physics-or-any-math-heavy-code/

I will be using AddForce with a ForceMode of VelocityChange, not changing the velocity directly.

If my math is correct, I think your code is not the answer I need.

Lets take my first 2 examples.
Click for example 1 - Success

  • Example 1-
    maxVelocity = 4,0,0
    addingVelocity = 2,0,0
    myRigidbody.velocity = 3,0,0

goal velocity to add = 1,0,0

Vector3 projectedVelocity = 5,0,0; // 3,0,0 + 2,0,0
Vector3 difference = Vector3.zero;

if(5 > 4) // it is
{
    difference = 1; //5 - 4
}

Vector3 goalVelocityToAdd = 1,0,0; // 2,0,0 - 1,0,0

All seems fine here.

Click for example 2 - Fail

  • Example 2-
    maxVelocity = 4,0,0
    addingVelocity = 2,0,0
    myRigidbody.velocity = 5,0,0

goal velocity to add = 0,0,0

Vector3 projectedVelocity = 7,0,0; // 5,0,0 + 2,0,0
Vector3 difference = Vector3.zero;

if(7 > 4) // it is
{
    difference = 3; //7 - 4
}

Vector3 goalVelocityToAdd = -1,0,0; // 2,0,0 - 3,0,0

Goal velocity was not reached, I do not want to add in an opposite direction to make it go slower.

Here is a hackish attempt I did that does not work, but maybe it can spark some ideas.
I think the part that does not work is the part where the signs of the velocity is not the same with the signs of the rigidbody.velocity.
Click for code

    public static Vector3 LimitVelocity(Rigidbody myRigidbody, Vector3 velocity, float maxMagnitude)
    {
        Vector3 maxVelocity = velocity.normalized * maxMagnitude;
        Vector3 squaredVelocity = Vector3.Scale(velocity, velocity);
        Vector3 squaredRigidbodyVelocity = Vector3.Scale(myRigidbody.velocity, myRigidbody.velocity);
        Vector3 squaredMaxVelocity = Vector3.Scale(maxVelocity, maxVelocity);

        float[] limitedVelocityValues = new float[3];

        for(int i = 0; i < limitedVelocityValues.Length; i++)
        {
            if(Mathf.Sign(velocity.SelectAxis(i)) == Mathf.Sign(myRigidbody.velocity.SelectAxis(i)))
            {
                if(Mathf.Abs(velocity.SelectAxis(i)) + Mathf.Abs(myRigidbody.velocity.SelectAxis(i)) < Mathf.Abs(maxVelocity.SelectAxis(i)))
                {
                    limitedVelocityValues[i] = velocity.SelectAxis(i);
                }else{
                    float x = Mathf.Abs(maxVelocity.SelectAxis(i)) - Mathf.Abs(myRigidbody.velocity.SelectAxis(i));
                    limitedVelocityValues[i] = (x > 0) ? x : 0;
                }
            }else{
                if(squaredVelocity.SelectAxis(i) - squaredRigidbodyVelocity.SelectAxis(i) <= squaredMaxVelocity.SelectAxis(i))
                {
                    limitedVelocityValues[i] = velocity.SelectAxis(i);
                }else{
                    limitedVelocityValues[i] = maxVelocity.SelectAxis(i) - myRigidbody.velocity.SelectAxis(i);
                }
            }
        }

        return new Vector3(limitedVelocityValues[0], limitedVelocityValues[1], limitedVelocityValues[2]);
    }

Oh, right, I see what you mean. I wasn’t thinking about case 2. Would this work instead?

//Do this computation only once or just set it to whatever value you want 
//by making this a public variable and setting it in the inspector
float maxVelocityMagnitude = maxVelocity.magnitude;

if(projectedVelocity.magnitude > maxVelocityMagnitude && rigidBody.velocity.magnitude <  maxVelocityMagnitude)
{
    difference = projectedVelocity - maxVelocity;
}

Case 1 should be covered then just like before, but now it will only set the difference to a non-zero value if the rigidBody was going slower than the maxVelocityMagnitude. Otherwise difference stays at 0 and it doesn’t change the velocity in order to satisfy case 2. I’d think then you could probably forget about all the complicated ifs and worrying about the signs of the numbers.

What it looks like you’re trying to do is let something speed up, but not overshoot the max velocity. At the same time, if for some reason the object is moving faster than the max velocity, do nothing.

My thought process on this was breaking it into two parts in that if statement. First part:

projectedVelocity.magnitude > maxVelocityMagnitude

At this point you’ve computed how fast the object will be moving if you added the full addVelocity. You’ll only compute a difference if the new velocity would be less than the maxVelocityMagnitude. This is a good way of doing things because the directions don’t matter then, so you can forget about the signs of the components and it should work in any number of dimensions.

So to modify the velocity (getting a non-zero difference), the above would have to be true and the following would have to be true too:

rigidBody.velocity.magnitude < maxVelocityMagnitude

If the body was moving faster than maxVelocityMagnitude before you did anything to it with addedVelocity, difference would stay at 0 and the velocity would just change by whatever your addingVelocity variable was. Is that good enough? Another way to write it would be this:

Vector3 difference = Vector3.zero;  //If either of the next if statements is not true, difference magnitude will be 0.

if(rigidBody.velocity.magnitude <  maxVelocityMagnitude)
{
    if(projectedVelocity.magnitude > maxVelocityMagnitude )
    {
       difference = projectedVelocity - maxVelocity;
    }
}

If my math is correct, this still does not give the desired results.

Example 1 - Success

  • Example 1-
    maxVelocity = 4,0,0
    addingVelocity = 2,0,0
    myRigidbody.velocity = 3,0,0

goal velocity to add = 1,0,0

if(3 < 4) //it is
{
    if(5 > 4) //it is
    {
        difference = 1,0,0; //5,0,0 - 4,0,0
    }
}

Goal velocity of 1,0,0 is reached

Example 2 - Success

  • Example 2-
    maxVelocity = 4,0,0
    addingVelocity = 2,0,0
    myRigidbody.velocity = 5,0,0

goal velocity to add = 0,0,0

if(5 < 4) //it is not
{
    //skip
}

Goal velocity of 0,0,0 is reached

Example 3 - Fail

  • Example 3-
    maxVelocity = -4,0,0
    addingVelocity = -2,0,0
    myRigidbody.velocity = 5,0,0

goal velocity to add = -2,0,0

if(5 < 4) //it is not
{
    //skip
}

Goal velocity of -2,0,0 is not reached

It seems the signs of the vectors are whats giving you problems as well :wink:

Please also keep in mind that the signs of each vector value x y and z may have different signs as well, so I do not think comparing them with magnitude will be the way to go, we need to do it per value.

If you did not see my example of what I did so far in my previous post, check it out. I think I already have the correct method for when all signs are the same, but when they are not, that is my issue.

Is it only ever going to affect the x axis? Solution is alot simpler if it never moves on y and z
if no, question 2: how are you creating a max velocity in 3 dimensions?
[maxVelocity = 2,3,4] suggests that it can go as fast as it wants in the negative directions

Example 3: I though that’s what you wanted.

Is it “goal velocity to add” or is it “goal velocity?” In that last case myRigidbody.velocity.magnitude is 5 while maxVelocity.magnitude is 4. Since myRigidbody is going faster than your maxVelocity.magnitude I thought you didn’t want it to be modified then except by your addingVelocity variable which would cap the velocity if it was accelerating beyond it, but leave it alone if you it wasn’t. The if block gets skipped and difference = 0, so you’re left with changing velocity by your original addingVelocity. If that’s not what you want you’ll have to be more specific.

Here’s a walkthrough:

//Given:
Vector3 maxVelocity = -4...;
Vector3 addingVelocity =-2 ...;
myRigidbody.velocity = 5....;

//New variables:
Vector3 projectedVelocity = myRigidbody.velocity + addingVelocity;

At this point:
projectedVelocity = 3.
rigidBody.velocity.magnitude = 5
maxVelocityMagnitude = 4

Now the first if():

if(rigidBody.velocity.magnitude <  maxVelocityMagnitude)

if(5<4)

At this point difference = 0 so:

goalVelocityToAdd = addingVelocity - difference;

goalVelocityToAdd = -2 - 0
goalVelocityToAdd = -2

Your original goalVelocityToAdd is left untouched. You’re now free to change the velocity (slow down perhaps or accelerate in a direction that does not increase velocity beyond maxVelocityMagnitude?) without it being modified. If that’s not what you wanted then you’ll have to explain it again because I then don’t understand what you’re trying to do. I thought you were just trying to restrict/modify the velocity to hit a certain magnitude on acceleration unless it was already moving faster than that.

Good point, I just realized that now. Yeah, I don’t understand what he’s trying to do here then.

Are you trying to limit the velocity separately in different directions? I.e., only allow velocity = 2 in x direction with velocity = 3 in y and so forth?

If so, you ought to be able to follow the same basic pattern and thinking I laid out, probably. Just do it separately on each axis and where you see magnitude, use absolute value instead.

It can be any direction (any axis, multiple axis)

Goal velocity to add or goal velocity is basically the same. Even if my current rigidbody velocity is 5,0,0 and I am trying to add a velocity of -3, dont look at it as subtracting velocity, but instead as just adding velocity in the other direction as long as my maxVelocity is also in that direction.
(In other words, when your player moves left and right, even though it might be going from +x to -x, you don’t see it as going to a negative direction, its just moving towards the left side, or adding its movement to the left.)

I’ll try to explain in more detail.

The max velocity I want to reach is a magnitude of 4 in what every velocity I plug in.
Lets say I want to add a velocity of 2,0,0
My rigidbodies current velocity is 3,0,0
The max I want to add to my rigidbody velocity in the x axis is 4, but I am trying to add 2 and the current velocity is already 3, so I cannot add the 2 or it will go over the max of 4, so I need to calculate what I can add.

Now the confusing part is lets say
The max velocity I want to reach is a magnitude of 4 in what every velocity I plug in.
Lets say I want to add a velocity of -2,0,0
My rigidbodies current velocity is 5,0,0
The max I want to add to my rigidbody velocity in the x axis is -4 and I am trying to add -2, but the current velocity is 5. Since the target max velocity is in the negative x, I need to find out how much I can add to move towards my target max velocity. Since the velocity is 5 and I want to add -2, 5 + -2 is 3, which is not past the max velocity of -4, so I am allowed to move -2.

If the max velocity I want to reach is a magnitude of 1 in what every velocity I plug in.
Lets say I want to add a velocity of -2,0,0
My rigidbodies current velocity is 0,0,0
The max velocity I can have is -1,0,0 , but I am trying to add -2 to the current 0 velocity, therefore I need to find out how much I could add to not go past the -1, which would be -1.

Hopefully this makes things more clear. The key point here is I need to find out how much I can add to move towards my target max velocity.
I will edit my main post with this line.

Please check one of my previous posts where I posted some code I did to try and solve this. This is exactly what I am doing for when all the signs are the same, but when they are not, this method will not work (as far as I can tell).

hpjohn, I think what he probably wants is for maxVelocity.x = 2 probably to mean -2 and 2. So for instance in his example 3:

maxVelocity = -4,0,0
addingVelocity = -2,0,0
myRigidbody.velocity = 5,0,0
goal velocity to add = -2,0,0

his velocity on the next step will become:
myRigidbody.velocity.x = myRigidbody.velocity.x + goal velocity to add.x
myRigidbody.velocity.x = 5 - 2
myRigidbody.velocity.x = 3

So it was moving to the right really fast for whatever reason (above the max velocity of 4 or -4), but the player can still accelerate left in order to slow down without having the speed capped in the meantime down from 5 to 4.

If so, he ought to be able to do pretty much the same thing I laid out, but using absolute values instead of magnitudes and doing it separately for each axis. Too late for me to try it now, but if he hasn’t figured it out by tomorrow I’ll come back.

Is this what you mean? This is from my example code that I posted in a previous post above.
Click for code

        float[] limitedVelocityValues = new float[3];

        for(int i = 0; i < limitedVelocityValues.Length; i++)
        {
            if(Mathf.Sign(velocity.SelectAxis(i)) == Mathf.Sign(myRigidbody.velocity.SelectAxis(i)))
            {
                if(Mathf.Abs(velocity.SelectAxis(i)) + Mathf.Abs(myRigidbody.velocity.SelectAxis(i)) < Mathf.Abs(maxVelocity.SelectAxis(i)))
                {
                    limitedVelocityValues[i] = velocity.SelectAxis(i);
                }else{
                    float x = Mathf.Abs(maxVelocity.SelectAxis(i)) - Mathf.Abs(myRigidbody.velocity.SelectAxis(i));
                    limitedVelocityValues[i] = (x > 0) ? x : 0;
                }
            }

If so, then I do not think this works for when the signs are not equal.

Please keep in mind that I do not know what the goal velocity is. The goal velocity is what we are trying to figure out.

No, I meant rewrite my code to do it on each axis separately. That if/elseif stuff is too nasty for me to even look at. :wink:

So to clarify more, you want to have a maxVelocityMagnitude, rather than a specific maxVelocity - (2.5,2.5,2.5) exceeds a magnitude of 4, but none of the components do.
And let’s say you are travelling in 4,0,0 and wish to add 0,1,0 then things get complicated

In my example code, the maxVelocity is determined like this
Vector3 maxVelocity = velocity.normalized * maxMagnitude;
(Check my edit below on the issue of how I am getting the maxVelocity.)
As long as each resulting velocity value (x,y,z) does not go past that target maxVelocity, then it is valid.

Example -
maxMagnitude = 4;
velocity = 1,0,1
maxVelocity = 4,0,4
The most the velocity could end up being is 3,0,3 but this depends on what the current rigidbody velocity is.

EDIT - However, I just realized that normalizing doesnt put the values to 1, but the concept is the same. I do not want the velocity to go over what ever the maxVelocity is. It should not matter how I determine the maxVelocity.

What do you mean by traveling in 4,0,0.
Do you mean my rigidbodies velocity is currently moving in 4,0,0 and I am trying to add a 0,1,0 to it? In that case, the maxVelocity would be 0,4,0 (if the magnitude is 4) and the velocity would have to be somewhere from 0 + since the maxVelocity is a positive (velocity and max velocity will always be the same sign).
If I was adding 0,1,0 and the max allowed was 0,4,0 and the current velocity is 4,0,0 then I can just go ahead and add the 0,1,0 since it does not go over the 0,4,0