Subtle determinism bug, and solution

Post Reply
RobW
Posts: 33
Joined: Fri Feb 01, 2008 9:44 am

Subtle determinism bug, and solution

Post by RobW »

We came over this subtle determinism bug:

(From btDiscreteDynamicsWorld::stepSimulation)...

Code: Select all

	if (numSimulationSubSteps)
	{

		saveKinematicState(fixedTimeStep);

		applyGravity();

		//clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
		int clampedSimulationSteps = (numSimulationSubSteps > maxSubSteps)? maxSubSteps : numSimulationSubSteps;

		for (int i=0;i<clampedSimulationSteps;i++)
		{
			internalSingleStepSimulation(fixedTimeStep);
			synchronizeMotionStates();
		}

	} 

	synchronizeMotionStates();

	clearForces();
As you can see, gravity is applied once per call to stepSimulation. Now, this is where it goes wrong...consider an object which is asleep when 'stepSimulation' is entered, and wakes up on one of the internal timesteps. It will have no gravity since it was asleep when 'applyGravity' was called which doesn't apply it to sleeping objects. This doesn't sound like a major issue, but it breaks determinism if you try to replay a sequence of physics events and during the playback, the program's frame rate is different (which is quite common, say, if you change the camera angle for the 'replay'). Although the fixed internal timestep means the framerate in theory doesn't matter, if the framerate is different then the batching of internal timesteps is different, and this bug can lead to gravity being missed for one or more internal timesteps when it was applied the first time around (or vice versa).

There are several solutions; the simplest is probably to apply gravity to all objects, sleeping or not. I think that is harmless because the force will never be integrated, and cleared at the end of 'stepSimulation'.

I think there is another bug lurking here too: I contest whether it is feasible to have a single accumulated force and torque vector. Since the addition of callbacks tied into the internal framerate, if you apply forces in one of these callbacks they will actually compound over internal timesteps which they should not do. My solution, in the end, is to have an 'internal' force and torque and an 'external' force and torque. Internal forces are cleared after each internal timestep, and external forces are cleared only after each 'stepSimultion'. If you apply forces at the 'game' framerate (i.e. between calls to 'stepSimulation' then you apply external forces, if you apply forces in a physics callback, you apply internal forces. It feels like a shame to have to bloat the data structures like this, but I couldn't see a better solution; I'm suprised nobody has noticed this problem since 'setInternalTickCallback' was added. Anyway, I moved gravity to be an internal force, which is an alternate fix for the problem.

Erwin, I'm afraid my changes are now so extensive I can't easily provide a patch for either of these issues, but I would be happy to prepare one if you let me know which solution you prefer, the changes are very simple to implement anyhow.

EDIT: thinking about it more, the cleanest solution is probably just to remove the 'isActive' clause from btRigidBody::applyGravity. Infact, it's probably faster overall, it's probably quicker to add gravity than to check and branch. I haven't tried this solution, it may have side-effects.

Many thanks,
Rob
Post Reply