How to clamp a Unit Vector Y axis

I think this is a very interesting problem, I want to clamp a unit Vector’s Y axis while maintaining its initial Magnitude.

Basically, if a Vector is pointing upwards, it will be clamped to a certain value:
math.clamp(unit.Y, 0.2, 0.5)

However, simply doing this will only make the Magnitude shorter (Lowered angle), somewhat like this:

Here’s what I desire:

image
** The values shown here are just an example. What I want is for the Unit Vector to maintain its magnitude while clamping.

Now, in theory we could just add the respective values into the X adthe Z axis, but this would also change the trajectory of the Vector:

    local clampedY = math.clamp(dist.Y, 0.2, 0.5)
    local YDiff = dist.Y - clampedY
    local XSigned, ZSigned = math.sign(dist.X), math.sign(dist.Z)
    dist = Vector3.new(dist.X + YDiff * XSigned, clampedY, dist.Z + ZSigned * ZSigned)

So I would need to find a way to multiply the determined X and Z axis for it to add up to the original Magnitude. But I’m currently clueless on how to do this.

Thanks.

1 Like

If I understand you correctly, try :Cross()

1 Like

Correct me if I’m wrong, but I believe :Cross() finds a perpendicular vector to the inputed vectors, in which the output vector does not cater for my desire as it finds a sign value and an entirely different vector.

Thanks for your reply.

1 Like

Can you explain why you want to do this? That would help with coming up with an answer.

You want to tilt or rotate the vector away from the Y-axis, maintaining length, until it’s tip lies below a certain threshold? Is that an accurate wording of your question?

Here’s an illustration:

We know |v| and |AB|, and together with AE those form a right angle triangle so we can use Pythagoras’ thing: |AE| = sqrt(|v|^2 + |AB|^2). |AE| is the length of the horizontal component of the result vector. The vertical component is just maxY or minY (A.y = |AB| in the illustration).

function clampYMaintainMagnitude(v: Vector3, minY: number, maxY: number): Vector3
    --Vertical component
    local dirY = Vector3.new(0, 1, 0)
    local clampedLenY = math.clamp(v.Y, minY, maxY)
    if clampedLenY == v.Y then return v end
    
    --Horizontal component
    local dirXZ = v * Vector3.new(1, 0, 1) --Component-wise multiplication like this "sets the Y component to 0"
    local lenXZ = math.sqrt(math.pow(v.Magnitude, 2), math.pow(clampedLenY, 2))
    
    return dirY * clampedLenY + dirXZ * lenXZ
end

I can’t test the code right now so let me know if it doesn’t behave as expected

2 Likes

Wouldn’t |AE| be |AE| = sqrt(|v|^2 - |AB|^2)? A few lines would need to be changed tho and these seemed to work for me:

lenXZ would be this:

local lenXZ = math.sqrt(math.pow(v.Magnitude, 2) - math.pow(clampedLenY, 2))

I believe this is some what you had intended and had been a mistake since math.sqrt only takes 1 arg

The return statement should be:

return dirY * clampedLenY + dirXZ.Unit * lenXZ

dirXZ should be normalised

2 Likes

Whoops yeah you’re right about all that. Here’s a fixed version:

function clampYMaintainMagnitude(v: Vector3, minY: number, maxY: number): Vector3
    --Vertical component
    local dirY = Vector3.new(0, 1, 0)
    local clampedLenY = math.clamp(v.Y, minY, maxY)
    if clampedLenY == v.Y then return v end
    
    --Horizontal component
    local dirXZ = (v * Vector3.new(1, 0, 1)).Unit --Component-wise multiplication like this "sets the Y component to 0"
    local lenXZ = math.sqrt((v.Magnitude * v.Magnitude) - (clampedLenY * clampedLenY))
    
    return dirY * clampedLenY + dirXZ * lenXZ
end

I also changed it to not use math.pow, although that’s not strictly wrong. Hope this works, still can’t test it :sweat_smile:

3 Likes

Just a minor nitpick, the magnitude is a bit inaccurate as it’s supposed to be 1 (I’m getting minor discrepancies in 0.1 - 0.2). Could this be attributed to the fact that we cant calculate the X and the Z horizontal segment individually ? I’m still unsure.

Your calculation works perfectly fine besides that minor issue though.

1 Like