# 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_keyFrames = 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_keyFrames.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)
)
```

With linear interpolation (lerp), the distance between each step is equal between the starting and ending positions. However, this distance is not constant when projected on the sphere representing the rotation. Thus, the speed is non uniform accross the entire interpolation.

With spherical linear interpolation (slerp), the interpolation is mapped on a segment of the sphere so that the projected distance is constant on that projection. The speed is thus constant accross all time steps.

Here is a simple animation to plot this difference of Lerps vs Slerp