I'm developing an animation application with a 2D virtual camera. The camera viewport can be positioned and scaled in the keyframes and is then interpolated to render the final animation from the viewpoint of the camera. I'm looking for the best way to interpolate the camera's parameters of x,y position, and scale so that objects in the scene are transformed by the camera change size at a constant rate and so that all objects/pixels in the scene travel in a straight line.
The transform matrix for rendering the scene from the point of view of the camera is calculated from the position and scale as follows, where DimX, DimY are the dimensions of the scene image, Pos and Scale are the position and scale of the camera (the variables that I want to interpolate).
LCen := PointF(DimX*0.5, DimY*0.5); CamTransformInv := TMatrix.CreateTranslation(-(Pos.X + LCen.X), -(Pos.Y + LCen.Y)); LScaleInv := 1 / Scale; CamTransformInv := CamTransformInv * TMatrix.CreateScaling(LScaleInv, LScaleInv); CamTransformInv := CamTransformInv * TMatrix.CreateTranslation(LCen.X, LCen.Y);
Below is an animation created by linearly interpolating the scale and position. The interpolation is as follows, where k is the interpolation ratio.
CamT.Scale := LinearInterpolate(Cam1.Scale, Cam2.Scale, k); CamT.Pos.X := LinearInterpolate(Cam1.Pos.X, Cam2.Pos.X, k); CamT.Pos.Y := LinearInterpolate(Cam1.Pos.Y, Cam2.Pos.Y, k);
The black line extends from the center of the viewport in the start position to the center of the viewport in the end position. You can see the effect of it appearing to speed up as it zooms in and then suddenly slowing down as it repeats, which I'd like to avoid. On the plus side, all objects in the scene move in a straight line. I've made the animation loop to make the acceleration effect more obvious.
Here's what the camera viewport is doing during the interpolation. The position and scale are both changing.
So I modified my code to linearly interpolate the Ln of the scale and take Exp of the result.
CamT.Scale := Exp(LinearInterpolate(Ln(Cam1.Scale), Ln(Cam2.Scale), k)); CamT.Pos.X := LinearInterpolate(Cam1.Pos.X, Cam2.Pos.X, k); CamT.Pos.Y := LinearInterpolate(Cam1.Pos.Y, Cam2.Pos.Y, k);
This results in an exponential interpolation with the scale change slowing down as it zooms in which looks good since objects in the scene then grow at a constant rate. This makes sense because objects in the scene get multiplied by scale whereas objects get added by position, so interpolation of scale should be multiplicative. This is achieved by taking the log before interpolating. The position is still as linear as before. The problem now is that parts of the image to the sides of the line move in a curve. It doesn't look right (see the top of the tower).
Here's what the camera viewport is doing during the interpolation. Note that the top left and bottom right corners of the viewport are traveling along curves.
It occurred to me that the problem is because I'm interpolating the scale non-linearly and the position linearly. If I made the position decelerate in the same way as the scale is decelerating then it would look correct. However, I can't think how this would be computed as the position and scale are coupled. If there's no scale change then the position should change linearly, but the greater the scale change the greater the non-linearity of the position should be. The position interpolation should depend on the scale somehow.
Is there a standard way of doing this? I have tried the following thinking that it should work, but it's not right either.
CamT.Scale := Exp(LinearInterpolate(Ln(Cam1.Scale), Ln(Cam2.Scale), k)); CamT.Pos.X := LinearInterpolate(Cam1.Pos.X/Cam1.Scale, Cam2.Pos.X/Cam2.Scale, k) * CamT.Scale; CamT.Pos.Y := LinearInterpolate(Cam1.Pos.Y/Cam1.Scale, Cam2.Pos.Y/Cam2.Scale, k) * CamT.Scale;
I did find a similar question from over 10 years ago. The suggestion there was to interpolate the inverse of scale, but that doesn't seem correct mathematically and didn't work when I tried it (I got the opposite acceleration effect).