Orientation align

TLDR: How would you implement an algorithm that makes an entity rotate until it gets close to a target orientation at which point it slows down the rotation and completely stops the rotation when it hits the matching orientation? Also, what is the simplest way in PlayCanvas to get an entity’s orientation around the Y axis in a scale of (-180 to 180)?

I’m working with objects only moving on a 2D plane. I found this nice algorithm to smoothly match a game object’s orientation with that of another target game object. Here it is in pseudo-code:

1 class Align: 
2 # Holds the kinematic data for the character and target 
3 character 
4 target 
5 
6 # Holds the max angular acceleration and rotation 
7 # of the character 
8 maxAngularAcceleration 
9 maxRotation 
10 
11 # Holds the radius for arriving at the target 
12 targetRadius 
13 speed 
14 # Holds the radius for beginning to slow down 
15 slowRadius 
16 
17 # Holds the time over which to achieve target speed 
18 timeToTarget = 0.1 
19 
20 def getSteering(target): 
21 
22 # Create the structure to hold our output 
23 steering = new SteeringOutput() 
24 
25 # Get the naive direction to the target 
26 rotation = target.orientation - 
27 character.orientation
28 
29 # Map the result to the (-pi, pi) interval 
30 rotation = mapToRange(rotation) 
31 rotationSize = abs(rotation) 
32 
33 # Check if we are there, return no steering 
34 if rotationSize < targetRadius 
35 return None 
36 
37 # If we are outside the slowRadius, then use 
38 # maximum rotation 
39 if rotationSize > slowRadius: 
40 targetRotation = maxRotation 
41
42 # Otherwise calculate a scaled rotation 
43 else: 
44 targetRotation = 
45 maxRotation * rotationSize / slowRadius 
46 
47 # The final target rotation combines 
48 # speed (already in the variable) and direction 
49 targetRotation *= rotation / rotationSize 
50 
51 # Acceleration tries to get to the target rotation 
52 steering.angular = 
53 targetRotation - character.rotation 
54 steering.angular /= timeToTarget 
55
56 # Check if the acceleration is too great 
57 angularAcceleration = abs(steering.angular) 
58 if angularAcceleration > maxAngularAcceleration: 
59 steering.angular /= angularAcceleration 
60 steering.angular *= maxAngularAcceleration 
61 
62 # Output the steering 
63 steering.linear = 0 
64 return steering

The algorithm requires that I be able to calculate the angle around the Y axis for both objects in order to determine the shortest ‘distance’ and direction between both orientations. It is implemented in radians but can easily be done with degrees. I have written a function that takes in a start angle and stop angle and returns the shortest distance between them in positive or negative degrees indicating the direction. The function takes in any positive or negative numbers, and outputs degrees between -180 and 180.

I have been trying to get the entities’ Y axis orientation by doing entity.getRotation().y but I quickly realized that this output is not on a -180 to 180 scale but rather a -90 to 90 scale and is dependant on the rotation value of x (0 or 180) to know the correct half of the circle. This is starting to get complicated.

The algorithm is pretty simple, it basically makes the entity rotate until it gets close to the target orientation at which point it slows down the rotation and stops rotation when it hits the matching orientation. How would you implement this in PlayCanvas? Is there a simpler way, or one that fits more naturally with PlayCanvas, that the one in the proposed algorithm. If not, what is the simplest way to get an entity’s orientation around the Y axis in a scale of (-180 to 180) ?

How to get an entity’s Y rotation in radians using pc.Entity#forward and Math.atan2:

// Let's assume the entity's forward axis (negative Z) is not 
// pointing straight up/down (Y axis)
var forward = this.entity.forward;
var yRotRads = Math.atan2(-forward.x, -forward.z);

To ease a Y rotation from one number to another, you could just use pc.math.lerpAngle.

2 Likes

To use lerpAngle, I’ll have to set the rotation with pc.Entity.setRotation, won’t I? Will that still work on a dynamic rigidbody? I was under the impression that the Entity setPosition and setRotation methods were not to be used on a dynamic rigidbody.

You can use pc.Entity#setEulerAngles (or preferably pc.Entity#setLocalEulerAngles which is faster to execute). Just set the y parameter to the angle around the Y axis.

Correct. To set the position/rotation of an entity with a rigid body, instead use pc.RigidBodyComponent#teleport.

1 Like

But the teleport method sets the position and the rotation and I only want to set the rotation, since I’m using forces and impulses for position changes. Should I just do something like this:

this.entity.rigidbody.teleport(this.entity.getPosition(), myNewRotation)

Yes, that’s correct.

Actually, I get weird stuttering behavior when I do that. It looks like the best option is still to implement the algorithm I found that sets angularVelocity instead. I’ll try it again, this time with your suggestion of using entity.forward and atan2.

You should not used teleport every frame. It’s to instantly move a rigid body from one place to another. By calling it, you are ‘interfering’ with the physics simulation. So, a good use case is when your level resets.

Dynamic bodies should just be controlled via forces/impulses or by setting linear/angular velocities.

1 Like

Ok, this confirms what I thought. Lerp and slerp are great for static or kinematic entities, but in practice, you can’t really use them to set a dynamic rigidbody’s rotation.

1 Like