for debugging, this distinguishes whether this is used for contact forces or collision impulses
pseudo random number generator, used to randomly decide order in which to calculate forces
Private
name_name of this ComputeForces instance, for debugging only
Private
nextThe next contact policy to use for deciding order in which to treat contacts.
Private
orderOrder in which contacts were treated; each entry is index of contact in A matrix, for debugging only.
Private
pRNGpseudo random number generator, used to randomly decide order in which to calculate forces
Private
preOrder in which to treat contacts; each entry is index of contact in A matrix, when the NextContactPolicy.PRE_ORDERED policy is used.
Calculates the forces at each contact point of a multi-body contact situation. Can also be used to find impulse needed at each collision point in a multi-body collision.
an n x n matrix giving change in acceleration for force at each contact
force at each contact (vector, length n), this is what is solved for and is returned via this array (this array is zeroed out at start).
external and inertial forces in the system (vector, length n)
indicates which contacts are Joints (vector, length n)
true shows debugging messages
the current simulation time, used only for debugging
Optional
tolerance: numberthe tolerance to use for checking the results. If not provided, then no check is done.
error code, -1 if successful otherwise an error occurred
Returns the policy for choosing which contact to treat next.
Sets the policy for choosing which contact to treat next.
Optional
preOrder: number[]Order in which to treat contacts; each entry is
index of contact in A matrix. Used only with NextContactPolicy.PRE_ORDERED
.
Static
checkReturns true if the given force and accel vectors satisfy the constraints that
if f != 0
then a = 0
, or if f = 0
then a >= 0
.
ignore deviations from constraints smaller than this
array of forces applied to a set of contacts
array of accelerations at a set of contacts
whether each contact is a joint
true if the force and accel vectors satisfy the constraints
Static
maxGenerated using TypeDoc
Computes forces at contact points between RigidBodys, or impulses at collision points between RigidBodys. The compute_forces method is an implementation of the algorithm given in the paper
More info at:
2D Physics Engine Overview
The Math and Physics Underlying ContactSim
ContactSim
ImpulseSim
This documentation is written assuming that contact forces and resulting accelerations are being calculated, but everything applies equally when calculating multiple simultaneous collision impulses and resulting velocities.
Terminology
This documentation uses several terms from the Baraff paper, such as C, NC, Acc. See that paper for precise definitions. Roughly these are:
C is the set of contacts that have some force applied and zero acceleration
NC is the set of contacts that have no force applied because they are separating (they have negative acceleration)
Acc is the subset of the A matrix corresponding to just the the set of contacts C
The algorithm starts with both C and NC being empty. We then examine one contact at a time, moving it into C or NC, and possibly moving existing contacts between C and NC as necessary.
Constraints
The acceleration of the gap distance at each contact point is given by
We find forces such that
The first condition ensures that acceleration is non-negative, so that bodies are not accelerating into each other: contact forces can only push, never pull. The second condition means that either
Joints
Joints are contact points that can both push and pull, and which never break their contact. Regular contact points can only push, and the contact is broken if the objects move apart. Joints are called 'bilateral constraints' in the Baraff paper.
Joints have different constraints: the force can be be positive or negative, but the acceleration is always exactly zero.
Return Value
The return value from compute_forces is
-1
if successful, meaning that a set of forces were found so that the acceleration satisfies the above constraints. If not successful, the caller can check the set of forces that were calculated to see if the resulting acceleration at each contact point is still acceptable.See the method checkForceAccel for how to check the accelerations. For example, the following code calculates and checks the acceleration from the calculated forces.
Redundant Contacts
It can often happen that the set of contacts is redundant in that pushing on one contact leads the algorithm to increase forces at other contacts in such a way that the push at that contact is negated, leading to no change in acceleration at that contact. This means that we cannot independently set the acceleration at that contact.
This shows up mathematically as the Acc matrix being 'poorly conditioned' or singular if we were to add that contact to C, with some row being a linear combination of other rows. (Condition number is a measure of how close a matrix is to being singular). Each row of the matrix corresponds to a contact point. Another way to say the same thing is that there is a linear combination of rows that equals the zero vector. Then any one of those rows is 'redundant' (because it can be expressed as a linear combination of other rows).
Simulations where redundant contacts show up include: the Pile simulation where a pile of rectangular blocks is resting on each other in a corner on the ground; and the DoNothingGrinder where the shuttle blocks are wedged between immoveable blocks.
Deferred (Rejected) Contacts
We avoid adding redundant contacts to C, to keep the Acc matrix non-singular as long as possible. When starting to drive a contact to zero acceleration, we first check to see if adding this contact to C will make Acc singular. If so, we defer (also called reject) the contact, so the contact goes on a list of rejects (the
R
array) which are handled only after all other non-deferred contacts have been treated, and then only if the acceleration at the deferred contact is large enough to worry about.For the normal non-deferred contacts we have a limit on the acceptable acceleration of
SMALL_POSITIVE
; but for deferred contacts, we have a different larger limit on the acceptable acceleration of 100 times that amount. Here 'acceptable' means whether the overall solution is acceptable, so that thecompute_forces
method can indicate success in its return value.There are some other ways for a contact to be 'deferred' (or 'rejected'). One is when moving a contact from NC to C. When that happens, we do the same kind of check of whether the contact will make Acc singular, and if so we defer the contact.
Another way that a contact can be deferred is if we notice that a 'zero step' was made repeatedly at the same contact. Often when a contact moves from NC to C, the step size is close to zero because the contact had zero acceleration. This is OK, but if we then notice that the contact moves back from C to NC with another zero size step, we defer that contact because this can lead to an infinite loop. Note that there can be intervening zero steps of other contacts; for example, contact A, then B, and then C all move from NC to C with zero size steps, then A moves back from C to NC with a zero step -- we would defer contact A. But if any other the intervening steps (for B and C) were non-zero size then we would not defer contact A.
Note that we can only defer a contact when it has zero force on it in the solution as calculated to date. This is because all contacts in C usually have some non-zero force, and if you removed a contact from C without first reducing its force to zero then the solution would no longer be balanced and the acceleration at other contacts in C would no longer be zero and contacts in NC might have negative acceleration. Therefore we can defer a contact
d
before starting to drive it to zero acceleration, because it is not yet in C and has no force. But as soon as you start driving to zero, you have committed to putting the contactd
into C because each step increases the force atd
. We can defer any contact that is currently in NC because it has no force. In the 'zero step' case, we can defer the contact that is in C only if it has zero force on it.Order of Treating Contacts
The order in which we handle (or 'treat') contacts is important and can affect what solution is found. The policy is set via the setNextContactPolicy method. The default policy is NextContactPolicy.HYBRID which first treats Joints in random order, and then non-Joints in the order defined by which has the most negative acceleration.
There are three other contact order policies: NextContactPolicy.MIN_ACCEL, NextContactPolicy.RANDOM, NextContactPolicy.PRE_ORDERED. Some of these are used for testing.
Infinite Loop Detection
There is a mechanism to detect infinite loops where a series of contacts keeps being rejected over and over. Part of the mechanism looks at whether any progress was made in the latest step by seeing if the acceleration at the contacts has changed.
The details of the infinite loop detection are as follows: There is a second 'reRejects' list which contains twice-rejected contacts. If we try to treat a reject, but then reject it again, it goes into the reRejects list and is removed from the rejects list. When any progress is made, the reRejects go back to the rejects list to be treated again. If the rejects list is exhausted without making any progress, then an infinite loop is detected, then we abandon the entire process, returning an error code. It is then up to the caller to decide if the resulting solution is adequate or not.
Sometimes Acc Becomes Singular
Despite the effort to keep Acc non-singular, we sometimes need to treat a contact that will make Acc singular because the contact has acceleration that is unacceptably large. In most cases this 'unacceptably large' acceleration is actually very small, like 1E-8 where the limit is 1E-10.
This algorithm is able to still find a solution when Acc is singular, but then the forces can become unreasonably large in order to drive the acceleration to a small value. What seems to often happen is the following: we are driving contact
d
to zero even though it makes Acc singular (ifd
were added to C) – this happens when we are treating a previously deferred contact, and is towards the end of the process when all non-deferred contacts are in the solution. What usually happens is that some other contact immediately moves from C to NC and then theAcc+d
matrix becomes non-singular, which is a good result.This algorithm is able to find a solution as long as the
b
vector is in the column space of the A matrix. This shows up in two places: first, we use a method of solving the matrix problemA x = b
that can deal with a singular matrix like this. Second, we will see that when trying to drive a 'redundant' contact to zero acceleration that thedelta_a
(the change in acceleration from applying force at the contact) is zero; normally this means that we cannot drive that contact to zero acceleration and would fail; but instead it typically is the case that the total acceleration at that contact is already zero (or close to zero) because we have driven the other contacts to zero, and the redundant contact is dependent on those.Will Not Find Minimal Forces
This algorithm is not guaranteed to find the minimum set of forces that will satisfy the constraints. Rather, the solution found (the set of forces) is sensitive to the order in which contacts are treated.
See
myphysicslab.lab.engine2D.test.UtilEngine_test
for unit tests that use random contact orderings; those tests show that the maximum force and the length of the force vector depends on the ordering, and also on the criteria for when a matrix is poorly conditioned (which affects when we defer treating a contact that would make the Acc matrix poorly conditioned).Performance Tweaks
ComputeForces keeps a matrix allocated that is reused, to avoid re-allocating a large matrix which seems to be a performance bottleneck. Some of the matrix algorithms were modified so that the matrix can be over-sized. Also we reuse several vectors between calls to
compute_forces
, to avoid reallocation.This resulted in an 11% reduction in running time for the
pile_20_random_blocks
performance test.Future Improvements
See Future Improvements in 2D Physics Engine Overview.
Remaining Mysteries
There are two remaining 'math mysteries' in
compute_forces
:When we go to drive a redundant contact
d
to zero, it pushes a contact from C to NC, so thatd
can be added and Acc stays non-singular. Is that guaranteed somehow?When unreasonably large forces are calculated, it looks like it’s usually because there is a pair of opposed contacts and somehow choosing the order so that these are treated close together (in the sequence of which contact to treat when) is what causes a large force to occur. Is there a way to recognize this and avoid it? Perhaps the two contacts are close to linearly dependent? Or maybe adding the second makes the condition of Acc bad?