greg6 said:
but wondering if theres an elegant solution I'm not quite smart enough to grasp...? What should I be looking for?
There are many ways to tackle such control problems. I can quickly list a few of them…
The first thing you might want to to do is figuring out how to bring a rigid body to a target for our next frame.
If you use rigid body simulation, you might want to avoid setting velocities directly, but instead you usually try to apply force and torque so the desired velocities are generated from that.
That's easy for the linear part to calculate force, but the angular part is harder, as you need to factor in nonuniform inertia to calculate torque.
After you make this work, there is a problem: Your body is at target position in the next frame as desired, but it also still has high velocity at this point, so in the frame after that you ‘overshoot’ the target and miss it again.
If you calculate forces again to bring it back, your body will oscillate forth and back, which is not what we want.
So we realize: We do not only want to drive to target, we also want to control velocity. E.g. we want it to be zero at the target, so the body sticks there.
Knowing that, we now have a better definition of our problem.
We have current state (position and velocity).
We have a target state (position and velocity).
We want to calculate a trajectory to drive from current to target, and we might also want to calculate the time it will take to get there. Probably we also want to minimize both energy and time to get to the target.
Splines are a common way to calculate a trajectory. If we use a cubic Bezier with 4 control points, the end points represent current position and target, and the lines from end point to the closer internal control point can represent velocity vectors.
However, there is some other spline which is better suited to model physics than Bezier. Unfortunately i have forgotten which one that was.
Another way i often use is to give maximum and minimum accelerations, and then calculate the trajectory respecting those limitations working with the equations of motion directly.
This is nice because it also gives the time to target, and it even gives time to the point where we switch from acceleration to deceleration phase.
Because we apply constant acceleration to the body during those phases, the resulting trajectory is perfectly smooth and natural.
However, it's a bit complicated. I get very similar results from using damping, and that's really easy to use.
Here's some snippets (1D, 3D, and quaternion) using critically damping, which does not overshoot:
static float SmoothCD (float from, float to, float &vel, float smoothTime, float deltaTime)
{
// Programming Gems 4
float omega = 2.f/smoothTime;
float x = omega*deltaTime;
float exp = 1.f/(1.f+x+0.48f*x*x+0.235f*x*x*x);
float change = from - to;
float temp = (vel+omega*change)*deltaTime;
vel = (vel - omega*temp)*exp;
return to + (change+temp)*exp;
}
static sVec3 SmoothCD (sVec3 from, sVec3 to, sVec3 &vel, float smoothTime, float deltaTime)
{
// Programming Gems 4
float omega = 2.f/smoothTime;
float x = omega*deltaTime;
float exp = 1.f/(1.f+x+0.48f*x*x+0.235f*x*x*x);
sVec3 change = from - to;
sVec3 temp = (vel+omega*change)*deltaTime;
vel = (vel - omega*temp)*exp;
return to + (change+temp)*exp;
}
static sQuat SmoothCD (sQuat from, sQuat to, sVec3 &vel, float smoothTime, float deltaTime)
{
auto QuatFromAToB = [](const sQuat &qA, const sQuat &qB) // global
{
sQuat q;
if (qA.Dot(qB) < 0.0f)
{
q[0] = qA[0]; q[1] = qA[1]; q[2] = qA[2];
q[3] = -qA[3];
}
else
{
q[0] = -qA[0]; q[1] = -qA[1]; q[2] = -qA[2];
q[3] = qA[3];
}
return qB * q;
};
// Programming Gems 4
float omega = 2.f/smoothTime;
float x = omega*deltaTime;
float exp = 1.f/(1.f+x+0.48f*x*x+0.235f*x*x*x);
sVec3 change = sQuat(QuatFromAToB (to, from)).ToRotationVector(); // todo: small angle approx?
sVec3 temp = (vel+omega*change)*deltaTime;
vel = (vel - omega*temp)*exp;
//return to + (change+temp)*exp;
sQuat rot; rot.FromRotationVector((change+temp)*exp); // todo: small angle approx?
return rot * to;
}
To use it you only need to know current velocity. Target velocity is assumed to be zero, and you don't get any time output.
But for most applications that's good enough, smooth and simple.
deltaTime is the timestep, and a larger value for smoothTime makes the results smooth but less responsive.
The velocity you give gets modified so it becomes the new velocity you want to have.
EDIT: Forgot to mention the returned value is the new position or orientation.
Another thing many people use is PID controllers. But i lack experience and can't talk about that.