It is a Bad Idea to decompose rotations to Euler angles. It will be source of frustration and fragility. If you can change your Camera API then I would recommend you do so and have it accept a rotation, or better yet a transform.
This is how I would do it. Dunno if this will work for you but maybe someone will find it useful:
Consider the trivial case where the Camera is always at a fixed transform behind the Spaceship in the Spaceship's local frame. That is, the Camera is locked onto the Spaceship no matter how it moves. In this case the Camera's world-frame transform can be computed from the Spaceship's:
Code: Select all
cameraTransform = spaceshipTransform * cameraTransformLocalFrame;
Where for berevity the world-frame transforms are understood to be without a "WorldFrame" qualifier in their names.
Note, if done with btTransforms then that math solves both the linear and angular parts.
Consider the more interesting case where the Camera's linear transform relative to the Spaceship is fixed, but its rotation lags a little bit as if it were on a torsional spring. This is trickier. The pseudo code looks something like this (I did not test, it might have bugs, etc):
Code: Select all
// Compute where the Camera is going
cameraTargetRotation = spaceshipRotation * cameraLocalRotation;
// Compute the deltaRotation between where the Camera is, and where it wants to be
deltaRotation = cameraTargetRotation * cameraRotation.inverse();
// Note: how did I know how to compute deltaRotation?
// The trick is to imagine these rotations are operating FROM THE LEFT on a
// hypothetical vector on the far RIGHT. The inverse rotation operates first
// and would rotate the vector into camera-local. The second rotation would
// rotate that result back into the world-frame exactly where it is targeted.
// In other words: the end result would be deltaRotation.
// We want to move the Camera toward its destination, but not the full delta.
// This is where Quaternion's really shine. We can calculate a "partial rotation"
// in the right direction by interpolating on the 4D hypersphere.
// We can either lerp (linear interpolate) which is fast or slerp (spherically interpolate)
// which is slightly more CPU expensive.
//
// In any case we have to compute the the fraction of interpolation between 0 and 1.
// Best to do this dynamically using the timestep between last frame since otherwise
// the rate of Camera movement will be affected by the framerate of the simulation.
// This is easy to tune by setting the natural exponential decay time. Think of this
// like a "half-life" however it is the time for the delta to reduce to 1/e of what
// it was. For fast slewing use a short timescale and for slow slewing increase the
// timescale.
const CAMERA_SLEW_TIMESCALE = 0.3; // seconds, tune this as necessary
fraction = timestep / CAMERA_SLEW_TIMESCSALE;
if (fraction > 1.0) {
// DANGER! fractions greater than 1.0 are not stable
fraction = 1.0;
}
// Compute new cameraRotation
//
// DANGER! Quaternions have a discrepancy of two: for every valid rotation there
// are two Quaternions on the hypersphere that map to it. Normally this is not
// a problem but it IS when interpolating between two Quaternions. Always interpolate
// between the nearest of the two. To do this we check their dot-product and if it
// is negative then they are on opposite hypersphers and we negate one of them
// to the other side.
if (deltaRotation.dot(cameraRotation) < 0.0) {
deltaRotation *= -1.0;
}
partialDeltaRotaton = identityRotation.slerp(deltaRotation, fraction);
cameraRotation = partialDeltaRotation * cameraRotation;
// DANGER! When multiplying two Quaternions together the result can be slightly non-normal
// (unit length != 1.0). This is not a problem when the result will be discarded after
// it is used. However, when a result is saved and used to recompute itself, like we're
// doing here for for cameraRotation, then eventually floating point error will creep in
// and we'll end up with Quaternions that cause linear distortions. Therefore, it is
// important to nomalize cameraRotation after each fresh computation.
cameraRotation.normalize();