Quaternion interpolation

Goal of this exercise is to practice implement the interpolation of rotations using quaternions.

Description of main project file

After attaching main10_2 to the project it renders a futuristic city with flying cars. The flying cars start from at a skyscraper and fly around in the city. If drawing is too demanding for your computer, then in the function initModels replace city model with a less complex one, lowering multisampling values should also help (ine glutSetOption(GLUT_MULTISAMPLE, 4);). The Assimp library with simple material implementation is used for fbx object loading.

Cars move on a curve created by keyPoints interpolation with the Catmull-Rom spline. They don’t have changes of orientations defined, your task is to compute their rotations in time.

For easier debugging following features were implemented for camera:

Most work will be done in the function animationMatrix. It contains a variable speed, that can be useful for debugging.

Direction computation

First we need to calculate quaternions that are responsible for the ship rotation between consecutive control points. We want the ship to be rotated in the direction of the next control point. For this we need to acquire the normalized direction vector for every two sequential control points as in the picture bellow.

wektory Next you have to compute the quaternion that describes the starting rotation (it is (0,0,1)) and defined by a direction vector. To calculate the rotation between one vector and another one, calculate the axis of rotation, using the cross product, and angle of rotation, using the dot product. It might be tempting to calculate rotations between starting direction and all the direction vectors, but this can lead to undesired behavior like spinning. Thus it is better to calculate rotation between consecutive vectors and acumulate those rotations.

Task

Fill in the missing code in function initKeyRoation so that it will provide std::vector<glm::quat> keyRotation with the correct quaternions.

  1. Initialize variable glm::vec3 oldDirection with vector (0,0,1), which is the initial direction.
  2. Initialize variable glm::quat oldRotationCamera with identity rotation (1,0,0,0).
  3. iterate from 0 to keyPoints-1 with i as index:
    1. Calculate new direction: subtract starting point: keyPoints[i] from ending point: keyPoints[i+1].
    2. Calculate new rotation, use function glm::rotationCamera multiply results with oldRotationCamera from right and normalize.
    3. add this new rotation to keyRotation vector.
    4. Overwrite oldRotationCamera with new rotation and oldDritection with new direction.
  4. After the loop there is one less quaternion than control points add one more with values (1,0,0,0).

Interpolation

For interpolation we will use the function slerp described by equation

\[ slerp(\hat{q_i},\hat{q}_{i+1},t) = \frac{\sin(\phi(1-t))}{\sin(\phi)}\hat{q_i}+\frac{\sin(\phi t)}{\sin(\phi)}\hat{q}_{i+1}\]

where \(\hat{q_i},\hat{q}_{i+1}\) are interpolated quaternions, \(t\) is parameter from 0 to 1 and \(\phi\) can be acquired from equation: \(cos\phi = q_xr_x+q_yr_y+q_zr_z+q_wr_w\). dla \(t\in\left[0,1\right]\) slerp calculates closest path from quaternions p and q in space of unitary quaternions. You don’t have to calculate it manually, instead use function glm::slerp.

Task

In function animationMatrix add quaternion interpolation.


glm::mat4 animationMatrix(float time) {
    ...
    //index of first keyPoint
    int index = 0;

    while (distances[index] <= time) {
        time = time - distances[index];
        index += 1;
    }

    //t coefitient between 0 and 1 for interpolation
    float t = time / distances[index];
    ...
    //implement corect animation
    auto animationRotation = glm::quat(1,0,0,0);

    glm::mat4 result = glm::translate(pos) * glm::mat4_cast(animationRotation);


    return result;

}

Acquire from keyRotation quaternions of index equal to index and index+1 call glm::slerp with them as arguments. Third parameter is supposed to be value t.

While acquiring values from keyRotation remember not to get outside of its scope. One idea is to clamp with 0 and size of vector.

The resulting animation would be continuous but not smooth at control points. It happens because the function is not smooth with t equal to 0 and 1. To mitigate this effect we will use function glm::squat.

Squat function similarly like Catmull-Rom takes 4 values but instead taking four consecutive vectors, it takes two consecutive quaternions and two intermediate quaternions. It is described by the equation:

\[squad(\hat{q}_{i},\hat{q}_{i+1},\hat{a}_{i},\hat{a}_{i+1},t)=slerp(slerp(\hat{q}_{i},\hat{q}_{i+1},t),slerp(\hat{a}_{i},\hat{a}_{i+1},t),2t(1-t))\]

where \(\hat{q}_{i},\hat{q}_{i+1}\) are interpolated quaternions, \(t\) is parameter from 0 to 1 and \(\hat{a}_{i},\hat{a}_{i+1}\) are described by equation:

\[\hat{a}_i = \hat{q}_i\exp\left[-\frac{\log(\hat{q}_i^{-1}\hat{q}_{i-1})+\log(\hat{q}_i^{-1}\hat{q}_{i+1})}{4}\right].\]

Task

Change slerp interpolation to squat interpolation. First acquire \(\hat{q}_{i-1}\), \(\hat{q}_{i}\), \(\hat{q}_{i+2}\), \(\hat{q}_{i+2}\) from keyRotation (\(i\) refers to index as before), calculate \(\hat{a}_i\), \(\hat{a}_{i+1}\), functions glm::inverse, glm::exp, glm::log will be useful. finally replace slerp to squat.

Now transitions between rotations should be smooth. One remaining problem is to fix landing of the flying cars. Set manually the last few quaternions in such a way that the ship appears to land horizontally.