Practical 6 - Elements of solution

This page provides elements of solution for the Keyframe Animation practical.

Exercise 1: KeyFrame interpolator

Here it the solution of the KeyFrame interpolation function:

class KeyFrames:

    ...

    def value(self, time):
        """ Computes interpolated value from keyframes, for a given time """

        # 1. ensure time is within bounds else return boundary keyframe
        if time <= self.times[0]:
            return self.values[0]
        if time >= self.times[-1]:
            return self.values[-1]

        # 2. search for closest index entry in self.times, using bisect_left function
        t_i = bisect_left(self.times, time) - 1     # note the -1

        # 3. using the retrieved index, interpolate between the two neighboring values
        # in self.values, using the initially stored self.interpolate function
        f = (time - self.times[t_i]) / (self.times[t_i + 1] - self.times[t_i])
        return self.interpolate(self.values[t_i], self.values[t_i + 1], f)

Just a note about bisect_left() function: this function returns the index where the value time should be inserted in the list self.times (from the left), so that the list is still ordered after insertion. With the notation of the practical, this value corresponds to \(t_{i+1}\).

For example, bisect_left((0, 3, 6), 2) returns 1, which is the index of value 3. Thus for \(t = 2\), \(t_{i} = 0\) and \(t_{i+1} = 1\)

Exercise 2: TransformKeyFrames class

class TransformKeyFrames:
    """ KeyFrames-like object dedicated to 3D transforms """
    def __init__(self, translate_keys, rotate_keys, scale_keys):
        """ stores 3 keyframe sets for translation, rotation, scale """
        self.translate_keys = KeyFrames(translate_keys)
        ...     # TO COMPLETE for other components  (2 lines)


    def value(self, time):
        """ Compute each component's interpolation and compose TRS matrix """
        translate_mat = translate(self.translate_keys.value(time))
        ...     # TO COMPLETE for other components (2 lines)

        # return the composed matrix
        return translate_mat @ ...      # TO COMPLETE

Note that:

  • In the KeyFrames constructor, the default interpolation function is the linear lerp() function. When needed, simply pass the spherical interpolation function quaternion_slerp() defined in the transform.py module.

  • Functions like translate() to create 4x4 matrices are also defined in that module. Look for the ones you need.

  • Compose the final transformation matrix in the correct order.

An illustration of the linear vs spherical interpolation

Here is a simple scene you can create to illustrate the linear vs spherical interpolation:

translate_keys = {0: vec(0, 0, 0)}
rotate_keys = {1: quaternion(),
               3: quaternion_from_euler(180, 0, 0),
               5: quaternion_from_euler(360, 0, 0)}
scale_keys = {0: 1}
keynode = KeyFrameControlNode(translate_keys, rotate_keys, scale_keys)
keynode.add(Cylinder(shader))
viewer.add(keynode)

A cylinder it simply turning around the camera axis in 4 seconds, starting after 1 second. Try to change the interpolation method the rotation keyframes:

  • With a spherical interpolation the rotation is continuous, as expected.

  • With a linear interpolation the movement is not continuous: positions are not attained at the correct times. Between two keypoints, the rotation is fast, then slow, then fast again.

  • Restart the animation at will to clearly see the rotation.

To restart an animation, map a key (e.g. space) in the Viewer class:

if key == glfw.KEY_SPACE:
    glfw.set_time(0)
)