I am making a "cinematic camera" that pans around a scene. Each step has a position for the camera and a point that the camera should be looking at. For instance:
1. position = (3, 2, 1), facing = (0, 0, 0)
2. position = (6, 5, 4), facing = (9, 9, 9)
The camera should start at (3, 2, 1) and be looking at the point (0, 0, 0). By the end of the panning the camera should be positioned at (6, 5, 4) and be looking at the point (9, 9, 9).
I've written some C# to do this:
public static IEnumerable<(Vector3, Vector3)> CinematicCamera(params (Vector3 location, Vector3 facing, double time)[] points) {
if (points.Length < 2) {
throw new ArgumentException("Need at least 2 points.");
}
List<(Vector3, Vector3)> returnMe = new List<(Vector3, Vector3)>();
for (int i = 1; i < points.Length; i++) {
int numSteps = (int)(points[i].time * 20); // camera runs at 20 frames per second
double startDistance = Distance(points[i - 1].location, points[i - 1].facing); // distance the camera starts from the point it's looking at
double endDistance = Distance(points[i].location, points[i].facing); // distance the camera ends from the point it's looking at
Vector3 startDirection = points[i - 1].location - points[i - 1].facing; // get the start directional vector from the location point and facing point
Vector3 endDirection = points[i].location - points[i].facing; // get the end directional vector from the location point and facing point
for (int stepNum = 0; stepNum < numSteps; stepNum++) {
double progress = (double)stepNum / numSteps; // 0 to 1 based on current step, used for lerp
double distance = Lerp(startDistance, endDistance, progress); // lerp the distance from the previous point to the next point
Vector3 currentDirection = Lerp(startDirection, endDirection, progress); // lerp the directional vector
Vector3 lookAt = Lerp(points[i - 1].facing, points[i].facing, progress); // new point to look at
Vector3 position = lookAt + currentDirection * distance / Length(currentDirection); // new position
returnMe.Add((position, lookAt));
}
}
return returnMe;
}
// helper methods
private static double Distance(Vector3 a, Vector3 b) => Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2) + Math.Pow(a.Z - b.Z, 2));
private static double Length(Vector3 v) => Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z);
private static double Lerp(double start, double end, double amount) => start + (end - start) * amount;
private static Vector3 Lerp(Vector3 start, Vector3 end, double amount) => new Vector3(Lerp(start.X, end.X, amount), Lerp(start.Y, end.Y, amount), Lerp(start.Z, end.Z, amount));
Vector3 is a custom data type that uses double to store x, y, and z. This all works, but the camera is a bit jittery due to floating point imprecision. I graphed some of the points in Excel and you can see the jitter in the graph:
I'm considering switching the entire implementation from double to decimal (double in C# uses 8 bytes while decimal uses 16), but I'm not sure that really addresses the root of the problem. How can I simplify this so that I run into less floating point weirdness?
