Now, knowing this, which do you recommend: fake it using game logic? or "springy control system"? I'm leaning towards springy control system, but unsure how to implement it. Does the spring not need to be connected to something else?
If the bike is a physical system, then I would lean towards the springy control system too. A real, physical spring would indeed need to be connected to something, but we can pretend we're implicitly connecting it to some absolute world frame and ignore that detail.
There's only one angular degree of freedom that needs to be influenced to handle leaning/rolling. The trick is determining the axis we will use to measure it and handling any corner cases that arise.
Here's one approach:
Code: Select all
//Find a vector perpendicular both to the Up vector and the bike's forward vector.
var xDirection = Vector3.Normalize(Vector3.Cross(bike.OrientationMatrix.Forward, Up)); //This needs NaN-protection.
//We would like the bike.OrientationMatrix.Right to align with our computed xDirection.
//We know by the way the xDirection was constructed that the bike's right vector
//and the xDirection are on the same plane, so we can compute some axis-angle
//rotation to perform a roll.
//Note that the axis is technically going to be parallel to the bike's forward direction, but the sign matters.
float angle = (float)Math.Acos(MathHelper.Clamp(Vector3.Dot(bike.OrientationMatrix.Right, xDirection), -1, 1));
angle *= (float)Math.Sign(Vector3.Dot(Vector3.Cross(bike.OrientationMatrix.Right, xDirection), bike.OrientationMatrix.Forward));
//Apply a corrective angular impulse to get closer to our goal.
if(Math.Abs(angle) > 0)
{
//Create an angular spring as you would a regular spring.
//Use a damped spring of the form SpringForce = Stiffness * Error - Damping * Velocity.
//The 'velocity' term is going to be the speed of rotation around the controlled degree of freedom.
//In this case, that means the bike's forward vector.
//The angular velocity of an entity is stored as a vector representing the axis of rotation with magnitude equal to the rotation speed in radians.
float currentVelocity = Vector3.Dot(bike.AngularVelocity, bike.OrientationMatrix.Forward);
//Note that tuning the stiffness and damping coefficients can be tricky. Going too high
//can cause instability depending on the time step. Play around with them for a while.
bike.AngularMomentum += bike.OrientationMatrix.Forward * (stiffnessCoefficient * angle - dampingCoefficient * currentVelocity);
}
I typed that up in WordPad and didn't even attempt to compile or test it, so it may not work perfectly on the first try- but the general concept is there, hopefully without too many goofs
There do exist some problems with this approach. It acts on the world's reference frame, so flipping in the air or going through loops will behave oddly. You could disable the correction mechanism if the bike is not touching the ground.
To mitigate the problem and allow loops to work, you could use some information to define the current 'up' direction. One possibility is to shoot a ray cast down to the ground beneath the bike and use the hit normal. This has corner cases of its own since it relies on the content quite heavily.
Another option is to define meta data along the track which includes 'up' directions along the path. As the bike moves, the current 'up' direction interpolates between the metadata definitions. This gives you full control, but also
requires full control- meaning it would take longer to create content.
On the other hand, if the bike is
not a physical system, then faking it is probably better. When I say physical system, I mean something like a set of entities connected by constraints or a Vehicle with a couple of wheels. In any case, its motion is defined by physical behaviors as opposed to teleportation or tight external control. If the bike body is teleporting along to match arbitrary wheel positions, that would be nonphysical.
You said: "the constraint must be solved with other constraints. Updating it in isolation externally will not be robust."
Does that mean if I use the current code, I must update the rear wheel and front steering in the same fashion?
Not quite;
all constraints must be solved together for robustness. That means contact constraints and everything else. Theoretically you could update all constraints outside of the engine, but there would be no point. It's much easier and safer to just give the constraint to the Space and let it automatically Just Work.
