Clamp a forward vector to a specific range

686 Views Asked by At

Right now, I have a 360 degree camera that rotates around the player. I currently get the angle between the player's forward and the camera's forward and this works correctly. If the camera is directly behind the player the angle returns 0, and when the camera is in front of the player and looking towards the player, it returns 180.

 void AngleBetweenCameraAndPlayer()
    {
            var playerAngle = _playerTransform.forward;
            var camAngle = _cameraTransform.forward;
            playerAngle.y = 0;
            camAngle.y = 0;
            var horizDiffAngle = Vector3.Angle(playerAngle, camAngle);
    }

I shoot the ball using .AddForce on its rigidbody, and the ball moves in the direction of the cameras forward.

_ballrb.AddForce(_cameraForward * (power * _timer), ForceMode.Impulse);  // projects the ball forward relative to the camera's forward by x power and x time held down.

I then in a separate method draw the balls trajectory using a kinematic equation. This script is attached to the ball gameObject and works correctly.

   private void DrawProjection()
    {
        lineRenderer.positionCount = Mathf.CeilToInt(linePoints / timeBetweenPoints) + 1;
        Vector3 startPosition = transform.position;
        Vector3 startVelocity = _cameraForward* (power * _timer);
        int i = 0;
        lineRenderer.SetPosition(i, startPosition);
        

         for (float time = 0; time < linePoints; time += timeBetweenPoints)
         {
             i++;
             Vector3 point = startPosition + time * startVelocity;
             point.y = startPosition.y + startVelocity.y * time +   (Physics.gravity.y/2f  * Mathf.Pow(time, 2));
             
             lineRenderer.SetPosition(i, point);
         }
    }

The problem I am running into is that when the player is trying to kick a ball, I want the angle of which it can shoot to be locked/ clamped 45 degrees from the players forward, and when you go past 45 degrees, you can still shoot, but it will be locked at that 45 degree angle.

Some ideas I've had is to clamp the _cameraForward by that angle and not let the angle go past 45 degrees, or using an if statement saying that if the angle is less than 45 degrees you can shoot and it will project the line, but the issue is when you go past 45 degrees, you can no longer shoot and it wont project and this is not what I want. I still want you to be able to shoot and project, just locked at that 45 degree angle.

3

There are 3 best solutions below

3
derHugo On BEST ANSWER

A slightly (imho improved) way of Serlite's answer would be to not go through Quaternion but directly stay with Vector3.

There is Vector3.Slerp which is specifically for directions and maintains the same magnitude all the time (other than Vector3.Lerp).

It also handles the clamping already since the factor is always clamped to be between 0 and 1.

Vector3 GetDirectionWithinLimit(Vector3 initialDirection, Vector3 targetDirection, float maxAngle)
{
    float angle = Vector3.Angle(initialDirection, targetDirection);

    // You could probably keep this to save some work
    // otherwise `Vector3.Slerp` will anyway clamp the factor to be between `0` and `1`
    //// If angle is within the limit, no need to do further calculations
    //if (angle <= maxAngle)
    //{
    //    return targetDirection;
    //}

    // Factor will be 0 - 1 if angle > maxAngle
    // Otherwise factor will be > 1 but Slerp clamps it to 1 anyway
    float factor = maxAngle / angle;
    return Vector3.Slerp(initialDirection, targetDirection, factor);
}

Personally I would keep it that way but some people like one-liners ^^

Vector3 GetDirectionWithinLimit(Vector3 initialDirection, Vector3 targetDirection, float maxAngle) => Vector3.Slerp(initialDirection, targetDirection, maxAngle / Vector3.Angle(initialDirection, targetDirection));
2
rustyBucketBay On

You must have tried it, but when you calculate the angle just clamp it?

void AngleBetweenCameraAndPlayer()
    {
            var playerAngle = _playerTransform.forward;
            var camAngle = _cameraTransform.forward;
            playerAngle.y = 0;
            camAngle.y = 0;
            var horizDiffAngle = Vector3.Angle(playerAngle, camAngle);
            if (horizDiffAngle > 45)
                horizDiffAngle = 45
            else if (horizDiffAngle < -45)
                horizDiffAngle = -45

    }
3
Serlite On

Here's a trick you can use when you want to limit a rotation (or change in direction) to a certain angle - use Quaternion.Lerp()! This allows you to generate a rotation that is partway in between two different rotations, which is useful if you want to calculate a rotation that is within a particular limit.

For example, if the angle between _playerTransform.forward and _cameraTransform.forward is 90 degrees, but your maximum angle is 45 degrees, then the parameters you'd want to pass in would be:

  • a: The rotation to point along the player's forward, which you can calculate using Quaternion.LookRotation()
  • b: The rotation to point along the camera's forward
  • t: The amount of the rotation that we allow - so if the raw rotation is 90 degrees, then this value would be 45 / 90 = 0.5

Once you calculate the rotation using Quaternion.Lerp(), just multiply it with Vector3.forward and you get a new direction is that towards _cameraTransform.forward, but within the allowable angle.

Let's see what this could look like in code:

Vector3 GetDirectionWithinLimit(Vector3 initialDirection, Vector3 targetDirection, float angleLimit)
{
    float angleToTarget = Vector3.Angle(initialDirection, targetDirection);

    // If angle is within the limit, no need to do further calculations
    if (angleToTarget <= angleLimit)
    {
        return targetDirection;
    }

    // Calculate out a, b, and t
    Quaternion initialRotation = Quaternion.LookRotation(initialDirection);
    Quaternion targetRotation = Quaternion.LookRotation(targetDirection);
    float allowableRotationAmount = angleLimit / angleToTarget;

    // Now we can determine the angle-limited rotation
    Quaternion allowableRotation = Quaternion.Lerp(initialRotation, targetRotation, allowableRotationAmount);
    return allowableRotation * Vector3.forward;
}

How would you use this in your code? Well, a snippet might look like:

var playerAngle = _playerTransform.forward;
var camAngle = _cameraTransform.forward;
playerAngle.y = 0;
camAngle.y = 0;
var shootingDirection = GetDirectionWithinLimit(playerAngle, camAngle, 45);

After that, you can use this direction instead of the camera direction in DrawProjection() and elsewhere.