garaphone
garaphone

Reputation: 1

How to determine the angle of rotation of the Kinematic body to the other Kinematic body in godot engine?

There are 2 airplanes, I need the plane controlled by the AI to understand what angle to turn to fly in the direction of the plane, which is controlled by the player. I didn't find any youtube videos on this subject.

Here is the code that I used:

var direction = (transform.origin - target.transform.origin).normalized()
# Calculate the angle between the direction vector and the object's current forward direction
var horizontal_angle = atan2(direction.x, direction.z) - rotation.y
var vertical_angle = atan2(direction.y, direction.z) - global_rotation.x

But something went wrong and it doesn't work as expected. As a result, the angles either have the wrong values, or offset, or change in different ways.

Upvotes: 0

Views: 1588

Answers (1)

Theraot
Theraot

Reputation: 40075

The problem is to solve the rotation between two directions:

  • The direction the AI plane is looking at.
  • The direction towards the player plane.

You need to have them in the same space. So decide if you are going to work on the local coordinates of the AI plane, or in global space.


Global space

  • The direction the AI plane is looking at is:

    var current_direction := -global_transform.basis.z
    
  • And the direction towards the player plane is:

    var target_direction := global_transform.origin.direction_to(target.transform.origin)
    

    Which you might also write as:

    var target_direction := (target.transform.origin - global_transform.origin).normalized()
    

However, I'll go with the AI plane local space for this answer.

Local space

  • The direction the AI plane is looking at is:

    var current_direction := Vector3.FORWARD
    
  • And the direction towards the player plane is:

    var target_direction := to_local(target.global_transform.origin).normalized()
    

Rotation between vectors

Regardless, you have two vectors. First instinct is to use angle_to to get the angle between them, but that won't tell you the axis of rotation (nor the direction of rotation). Instead you need to use signed_angle_to, but that requires to know the axis first…

Well, the axis of rotation is the the cross product normalized, so:

var axis := current_direction.cross(target_direction).normalized()
var angle := from.signed_angle_to(to, axis)

I remind you that the cross product will give you a vector that perpendicular to both of its inputs. But if one of them is the zero vector or if they are both equal, you get the zero vector, you might want to check for that.

Alright, what if you don't want axis-angle? Well, it is trivial to make a Quat from that:

var quat := Quat(axis, angle)

What if you wanted a Basis? Easy:

var basis := Basis(quat)

A Transform? I got you:

var transform := Transform(Vector3.ZERO, Basis(quat))

Alright, what if you don't want those either? OK, if you really, really, really want, we can convert that to Euler angles:

var euler := quat.get_euler()

Rotating

I suppose you want to rotate the KinematicBody according to the rotation you found. First of all, if you wanted to do that directly, you would use look_at and avoid all this trouble. Thus, I believe you want do it smoothly.

To do that, I want to encourage to use axis-angle, because then you can define an angular velocity and use rotate (or rotate_object_local if you were using local coordinates). For example:

export var angular_velocity := PI # radians per second

func _physics_process(delta:float) -> void:
    # …
    var angle_step := min(angular_velocity * delta, abs(angle)) * sign(angle)
    rotate(axis, angle_step)

An issue you will find is that these consecutive rotation introduce an unintended roll. Which you can fix by also rotating so the the upwards direction (instead of the forward direction) goes towards the global up direction, by the same means.

See also: How can I point an object to the center of the screen in Godot for the case of smoothly rotating a gun in first person view to whatever is hit by a raycast along the main axis of the camera (i.e. along the center of the screen). Rotate a thing to a thing, same idea.


Trigonometry

Since you tagged this with trigonometry and atan2 tags, I'll show you the source code for signed_angle_to:

real_t Vector3::signed_angle_to(const Vector3 &p_to, const Vector3 &p_axis) const {
    Vector3 cross_to = cross(p_to);
    real_t unsigned_angle = Math::atan2(cross_to.length(), dot(p_to));
    real_t sign = cross_to.dot(p_axis);
    return (sign < 0) ? -unsigned_angle : unsigned_angle;
}

What is going on here? Well, as you remember the parameters to atan2 are the y and x components of a vector of which you are getting its angle.

If you think in polar coordinates for a moment, you would remember that the y and x components are the r*sin(θ) and r*cos(θ) respectively.

Well, we can get the r*sin(θ) and r*cos(θ) of the angle between our vectors from cross and dot product identities:

a·b = |a||b|cos(θ)
|axb| == |a||b|sin(θ)

Which is why we pass the length of the cross product and the dot product as parameters of atan2 here:

real_t unsigned_angle = Math::atan2(cross_to.length(), dot(p_to));

Now, since the length of the dot product is always positive, we will get an angle between 0 and π (i.e. in the first two quadrants).

To discriminate we check if the cross product is in the same direction as the axis that was provided as parameter. If they are in the same general direction the dot product will be positive (look again at the dot product identity, and recall that the cosine is positive from -π/2 to π/2, so the dot product is positive is the angle between the vectors is less than π/2), if it isn't positive then we change the sign of the resulting angle:

real_t sign = cross_to.dot(p_axis);
return (sign < 0) ? -unsigned_angle : unsigned_angle;

Upvotes: 0

Related Questions