That is caused in large part by the time step duration being too long relative to the motion. The integrator has a hard time keeping things accurate. This is one of those factors that makes fully physical vehicles 'generally more expensive for the same level of stability compared to the Vehicle class.'
For example, setting the Space.TimeStepSettings.TimeStepDuration to 1/600f (defaults to 1/60f) and then calling Space.Update() 10 times or using Space.Update(dt) should almost entirely mitigate the effect. That is unreasonably expensive though.
A SwivelHingeAngularJoint or RevoluteLimit, being 1DOF angular constraints can be used to control the steering angle of the wheels as well. They work slightly differently compared to RevoluteMotors; the wheels will steer towards the final direction faster, but they tend to torque out of alignment a little bit while doing so. Reducing the time step duration a little (1/120f or 1/180f) helps a lot. The misalignment might also be mitigable by incrementally changing the goal state rather than jumping directly to it, but the odd behavior will still exist on a large time step duration.
For the back wheels in particular, using a RevoluteAngularJoint instead of a SwivelHingeAngularJoint+RevoluteMotor to control their steering will work better. The RevoluteAngularJoint works on two degrees of freedom simultaneously, making it better suited for the job. It can't, however, be used for steering the driving wheels because it will fight against any rolling motion present prior to changing the steering direction- in effect, the car would fully brake every time the wheels turned.
Here's an example implementation of an unsteerable wheel using the RevoluteAngularJoint, similar in design to the SuspensionCarDemo:
Code: Select all
void AddBackWheel(Vector3 wheelOffset, Entity body)
{
var wheel = new Cylinder(body.Position + wheelOffset, .2f, .5f, 5f);
wheel.Material.KineticFriction = 4.5f;
wheel.Material.StaticFriction = 4.5f;
wheel.Orientation = Quaternion.CreateFromAxisAngle(Vector3.Forward, MathHelper.PiOver2);
//Preventing the occasional pointless collision pair can speed things up.
CollisionRules.AddRule(wheel, body, CollisionRule.NoBroadPhase);
//Connect the wheel to the body.
var pointOnLineJoint = new PointOnLineJoint(body, wheel, wheel.Position, Vector3.Down, wheel.Position);
var suspensionLimit = new LinearAxisLimit(body, wheel, wheel.Position, wheel.Position, Vector3.Down, -1, 0);
//This linear axis motor will give the suspension its springiness by pushing the wheels outward.
var suspensionSpring = new LinearAxisMotor(body, wheel, wheel.Position, wheel.Position, Vector3.Down);
suspensionSpring.Settings.Mode = MotorMode.Servomechanism;
suspensionSpring.Settings.Servo.Goal = 0;
suspensionSpring.Settings.Servo.SpringSettings.StiffnessConstant = 300;
suspensionSpring.Settings.Servo.SpringSettings.DampingConstant = 70;
var revoluteAngularJoint = new RevoluteAngularJoint(body, wheel, Vector3.Right);
//Add the wheel and connection to the space.
Space.Add(wheel);
Space.Add(pointOnLineJoint);
Space.Add(suspensionLimit);
Space.Add(suspensionSpring);
Space.Add(revoluteAngularJoint);
}
As for the front steering wheels, to stop turning outside of the desired range, you may want to add a RevoluteLimit. The RevoluteLimit tends to be stronger than the RevoluteMotor in this kind of difficult situation. So, for example, you could add this to the front wheels:
Code: Select all
var steeringConstraint = new RevoluteLimit(body, wheel, Vector3.Up, Vector3.Right, -maximumTurnAngle,
maximumTurnAngle);
Space.Add(steeringConstraint);
I think I'll spend a little while checking the RevoluteMotor to see if I can make it handle this situation better, since it's really the most intuitive option for handling steering. No promises at this point, though
