Smooth LookAt (like RotateTowards in Unity)


#1

Hi!

I want to make a smooth LookAt, I mean, rotating over time the object towards a 3D point.

The smoothly interpolation (see exemple below) is not what I want in my case because the object rotating is also moving at the same time (causing the object not rotating towards the 3D point but a specific orientation):

var percent = this.time / this.movementDuration;
//Use slerp to smoothly interpolate between two angles
//http://developer.playcanvas.com/en/api/pc.Quat.html#slerp
angle.slerp(fromRotation, toRotation, percent );

In Unity, there’s the RotateTowards function for this purpose: https://docs.unity3d.com/ScriptReference/Quaternion.RotateTowards.html

Is there a function for this in PlayCanvas, or an exemple where I can take some codes?

Thank you for your help.


#2

PlayCanvas doesn’t have one built in but you can look at how Unity3D have implemented it and copy it over to PlayCanvas https://github.com/Unity-Technologies/UnityCsReference/blob/54dd67f09e090b1ad5ba1f55886f327106406b0c/Runtime/Export/Quaternion.cs#L193


#3

Thank you for this reference. I will take a look at it, try to implement and share it here.


#4

Here is my working code. Please feel free to recommend modifications.

@yaustar You can also add theses Quat functions in the Playcanvas engine.

this.movementDuration = 3; //The duration (Secs)
this.time =0;
this.rotateTowardThis = new pc.Vec3(-7,5,0); //The 3D point to rotate toward

Camera.prototype.update = function(dt) {
    this.time += dt; 
    var percent = this.time / this.movementDuration;

    var original_rotation = new pc.Quat();
    var final_rotation = new pc.Quat();
    
    original_rotation.copy(this.entity.getRotation());
    this.entity.lookAt(this.rotateTowardThis); 
    final_rotation.copy(this.entity.getRotation());
    
    this.entity.setRotation(original_rotation);
        
    var new_rotation = this.rotateTowards(original_rotation, final_rotation, percent);
    
    this.entity.setRotation(new_rotation);
}

//// Quat functions

// Get the dot product between two quaternions
OrbitCamera.prototype.dot = function (quat_left, quat_right) {
    var dot = quat_left.x * quat_right.x + quat_left.y * quat_right.y + 
        quat_left.z * quat_right.z + quat_left.w * quat_right.w;

    return dot;
};

// Returns the angle in degrees between two rotations /a/ and /b/.
OrbitCamera.prototype.quatAngle = function (quat_a, quat_b) {
    var dot = this.dot(quat_a, quat_b);
    
    if(quat_a.equals(quat_b) )
    {
        return 0;
    }        
    
    var rad2Deg = 1 / (Math.PI / 180);

    var angle = Math.acos(Math.min(Math.abs(dot), 1)) * 2 * rad2Deg;

    return angle;
    
};

// Rotates a rotation A towards B.
OrbitCamera.prototype.rotateTowards = function (quat_a, quat_b, percent) {
    var angle = this.quatAngle(quat_a, quat_b);
        
    if (angle === 0)
    {
        return quat_b;
    }
    
    return new pc.Quat().slerp(quat_a, quat_b, percent);
    
};


#5

Nice work!

This looks slightly off compared to Unity’s. The value being passed in Unity’s is the number of degrees to rotate by where the above version is rotating by a percentage of what’s remaining.

This would mean that as the entity gets closer to the target rotation, the speed of the rotation gets slower.

Untested code below but this was how I saw the Unity code:

OrbitCamera.prototype.rotateTowards = function (quat_a, quat_b, degreesDelta) {
    var angle = this.quatAngle(quat_a, quat_b);
        
    if (angle === 0)
    {
        return quat_b;
    }
    
    return new pc.Quat().slerp(quat_a, quat_b, Math.min(1, degreesDelta / angle));
    
};

#6

True, the speed of the rotation was getting slower when getting closer to the target.

I solved this by not setting the original_rotation variable in update but only when the animation started.
In my case, I used percentage for a specific reason but I think your solution will work too.

I’m happy to document this here and contribute to the playcanvas community.