Optional
opt_name: stringname of this Subject
Protected
bods_The RigidBodys in this simulation.
Protected
collisionHow close in space we need to be to a collision, to decide to handle it, as a percentage of the targetGap = distanceTol/2.
Function to print collisions, or null to turn off printing collisions.
Private
compute'I' for ImpulseSim
Protected
debugFunction to paint canvases, for debugging. If defined, this will be called within
moveObjects()
so you can see the simulation state after each
time step (you will need to arrange your debugger to pause after
each invocation of debugPaint_ to see the state).
Private
distanceDistance tolerance, for determining if RigidBody is in contact with another RigidBody. Contact point must have relative normal distance less than this.
Protected
forceThe ForceLaws in this simulation.
Protected
showWhether to add Forces to the SimList so they can be seen.
Protected
simThe SimList holds SimObjects so they can be made visible.
Protected
simRNG_The pseudo random number generator, used in collision handling and computing forces.
Protected
simSuggested size for the SimView. This is mainly for tests to communicate with TestViewerApp.
Protected
varsThe variables that determine the state of the simulation; there are six variables for each RigidBody, plus some others for time, energy, etc.
Private
velocityVelocity tolerance, for determining if RigidBody is in contact with another RigidBody. Contact point must have relative normal velocity smaller than this.
Private
warningfor warning that proximity test is off
Static
Readonly
COLLISIONS_For debugging, this allows code to look for collisions, but does not actually return them. This allows to debug code for finding nearest point between objects.
Static
Readonly
DEBUG_Show the impulse applied at each collision.
Static
ELASTICITY_Name of event broadcast from setElasticity.
Static
Readonly
SMALL_Impulse smaller than this is not marked as a discontinuous change in the velocity variables of the objects colliding.
Static
Readonly
TINY_Impulse smaller than this is regarded as insignificant at various points in the collision handling algorithm.
Add the RigidBody to the simulation and SimList, and add a set of 6 variables for the RigidBody to the VarsList.
Using FunctionVariable's ensures that the variables on the VarsList have the same values as the RigidBody's (because the FunctionVariables retrieve and store their values in the RigidBody's). There is no need for a separate step to coordinate between the VarsList and the RigidBody's, they are automatically in sync.
RigidBody to add to the simulation
Adds the ForceLaw to the list of ForceLaws operating in this simulation, if it is not already on the list.
the ForceLaw to add
if adding a second DampingLaw or GravityLaw
Adds the given Observer to this Subject's list of Observers, so that the Observer
will be notified of changes in this Subject. An Observer may call Subject.addObserver
during its observe
method.
the Observer to add
Adds the Parameter to the list of this Subject's available Parameters.
the Parameter to add
if a Parameter with the same name already exists.
Private
applyApplies the given impulse to the objects involved in the given collision, at the impact point of the collision.
collision where the impulse should be applied
magnitude of impulse to apply
Protected
applyApplies the Force by modifying the array representing rate of change of each
variable. The Force specifies which RigidBody it works on so we can figure out
which variable rates to modify. If showForces_ is true
, adds the Force
to the SimList with an immediate expiration time.
vector of rigid body accelerations
the Force to be applied
Private
applyApplies the impulse by modifying the simulation variables.
Notifies all Observers that this Subject has changed by calling observe on each Observer.
An Observer may call addObserver or removeObserver during its observe
method.
a SubjectEvent with information relating to the change
Notifies all Observers that the Parameter with the given name has changed by calling observe on each Observer.
the language-independent or English name of the Parameter that has changed
if there is no Parameter with the given name
Private
checkRemoves all RigidBodys, ForceLaws, most Variables, and clears the SimList. This is used in applications to build a new configuration of RigidBodys. This should give essentially the same state that you would get from making a new RigidBodySim, except for parameters (like gravity) that may have been changed.
The alternative is to create a new RigidBodySim; that would be 'cleaner' but then you must unhook the old RigidBodySim from all the various user controls and graph and such, and hook up the new one.
Creates a PointMass which is displayed as a circle, and adds it to the SimList, for debugging only. The expiration time on temporary SimObjects is set to 'now', so that they are removed right away during the next call to advance().
name of the SimObject that is created
center of the circle
radius of the circle
Optional
expireTime: numberthe time when the DisplayObject will be removed; the default expireTime is 'now'.
Defines the differential equations of this ODESim; for an input set of variables, returns the current rate of change for each variable (the first derivative of each variable with respect to time).
The timeStep
is the time since the state variables were last fully calculated, which
can be and often is zero. The current time can be regarded as getTime() + timeStep
.
The input variables correspond to the Simulation state at that time. Note that
timeStep
is different from the time step used to advance the Simulation (as in
AdvanceStrategy.advance).
The timeStep
is typically used when finding collisions in
CollisionSim.findCollisions.
the current array of state variables (input),
corresponding to the state at getTime() + timeStep
array of change rates for each variable (output), all values are zero on entry.
the current time step (might be zero)
null
if the evaluation succeeds, otherwise an object relating to the
error that occurred. The change
array contains the output results.
Finds collisions based on the passed in state variables. Can rely on modifyObjects having been called prior, with this set of state variables. Uses the state saved by saveState as the 'before' state for comparison.
The list of collisions that are passed in can potentially have collisions from the near future that were found previously. The CollisionSim should avoid adding collisions that are duplicates of those already on the list.
the list of collisions to add to
the current array of state variables
the size of the current time step, in seconds
Returns a RigidBody in this simulation by specifying its name or index in the list of RigidBodys.
index in list of RigidBodys or name of the RigidBody (either the English or language-independent version of the name)
the RigidBody with the given name or at the given position in the list of RigidBodys
if requesting a non-existing body.
Protected
getReturns whether broadcasting is enabled for this Subject. See setBroadcast.
whether broadcasting is enabled for this Subject
Returns the collision distance accuracy, a fraction between zero and one; when the
collision distance is within accuracy * targetGap
of the target gap distance, then
the collision is considered close enough to handle (apply an impulse).
the collision accuracy, a fraction between 0 (exclusive) and 1 (inclusive)
Returns the collision handling method being used.
the collision handling method being used
Returns the current EnergyInfo for this system.
an EnergyInfo object representing the current energy of this system.
Returns the ParameterBoolean with the given name.
the language-independent or English name of the ParameterBoolean
the ParameterBoolean with the given name
if there is no ParameterBoolean with the given name
Returns the ParameterNumber with the given name.
the language-independent or English name of the ParameterNumber
the ParameterNumber with the given name
if there is no ParameterNumber with the given name
Returns the ParameterString with the given name.
the language-independent or English name of the ParameterString
the ParameterString with the given name
if there is no ParameterString with the given name
Returns the seed of the pseudo random number generator (RNG) used in this simulation. The RNG is used during collision handling and contact force calculation. To get reproducible results, set this seed at the start of a simulation, and the RNG will then always give the same sequence of random numbers.
the seed of the pseudo random number generator
Returns the suggested size for the SimView. This is mainly for tests to communicate with test/TestViewerApp.TestViewerApp.
suggested size for the SimView
Adjusts the simulation state based on the given Collisions.
For example, this might reverse the velocities of objects colliding against a wall.
The simulation state is contained in the vars
array of state variables from
getVarsList.
Note that these Collisions will typically be from the very near future; CollisionAdvance backs up to just before the moment of collision before handling Collisions.
the list of current collisions
Optional
opt_totals: CollisionTotalsCollisionTotals object to update with number of collisions handled
true if was able to handle the collision, changing state of simulation.
Private
handleHandles a set of collisions using the serial collision handling method. We keep handling collisions at random until we reach a state where there is no collision. Note that this can take many times thru the loop, up to thousands of times.
The idea is that multiple collisions can be simulated as a rapid series of instantaneous collisions as objects collide and ricochet against each other many times until they finally separate or the energy of the collisions is absorbed by the sequence of inelastic collisions. It is as though the objects are all separated by a tiny gap, so that when object A is struck, it then collides with object B, and then object B collides with object C and so forth. But this gap is so tiny that no time passes, and we can calculate the results of all these collisions here very quickly.
Each time thru the loop, we pick a single focus collision to resolve. If there are no joints, then we simply resolve that single collision. If there are joints on either body involved in the chosen focus collision, we find all the joints that are connected via other joints (for example, a chain would include all the joints in the chain). We then do a simultaneous type calculation for that set of original focus collision plus connected joints. Because we must maintain the velocity of the joint contact at zero (which means elasticity is zero), we can include the joints in the calculation and maintain the integrity of the joints.
The order of processing contacts is determined by the randomInts function. It is important to use randomInts here to ensure that we get an even distribution of integers each time thru the loop. For example if the same sequence were used every time thru the loop, then the contacts at the start of the sequence would be processed much more frequently, and the algorithm would be much less efficient.
To provide a non-random option you can use the setRandomSeed method to set the seed used by the random number generator. This will provide a reproducible series of random numbers. This is done when running tests.
Turn on the showVelo
flag to show visually the velocity at each contact point and get
a sense of how this algorithm works. See setDebugPaint for
additional steps needed to have the contact forces drawn while stepping thru this
method.
The hybrid
option uses the following policy: Each time thru the loop, we
focus on the collision with the largest velocity, and we include the set of 'active'
collisions (non-joint and non-contact) that are happening on either body involved in
the initial focus collision; we then do simultaneous type collision handling on this
set of collisions.
An example where hybrid option makes a difference: a block falling onto the ground so that both its corners hit simultaneously -- with hybrid option the block bounces straight up without spinning, because both collisions are treated simultaneously. Hybrid works more correctly than simultaneous in cases like the 'one hits two' scenario, or in Newton's Cradle. See test/MultipleCollisionTest.MultipleCollisionTest for other examples.
Contacts are treated with elasticity zero when there are only contacts in the set of
collisions. This is to reduce the 'jitter' at contacts, stopping residual velocity at
contact points. But when there is a large velocity collision in the set, then contacts
are treated with the same elasticity as other collisions, as given by getElasticity()
.
We continue handling collisions until all collision velocities are smaller than
small_velocity
.
For joints: Math.abs(velocity) < small_velocity
For non-joints: velocity > -small_velocity (i.e. positive or small negative)
About doPanic: It seems to save only about 10 percent on the number of times thru the loop, but more important is that it might occasionally allow finding a solution versus being in an infinite loop here.
The concept here is to consider one 'focus' collision at a time.
Find the set of contacts/joints that are directly involved in that focus collision
For the pure serial method, only look at the focus collision and attached joints
For the hybrid method, look at focus and joints, plus other collisions involving either body of the focus collision
Find the impulse that reverses the velocity at each collision involved This step uses the simultaneous type of calculation for the set of collisions under consideration during each step.
Find the new collision velocities, given all the impulses found so far; continue the above until there are no collisions remaining.
This is like dealing with collisions serially, except we do it all here, and so avoid going back to the (slow) collision detection loop until we see that all collision points are separating or in contact.
TO DO when only a single collision is being processed, we could avoid a lot of code here and perhaps save time. Ie. don't need to set up a sub-matrix, or even call compute_forces.
TO DO make the exception thrown more informational: is it because accuracy in the solution is poor, or some other reason.
the set of collisions to handle
true means use a hybrid collision handling method that uses the 'simultaneous' method in some cases
Optional
opt_totals: CollisionTotalsCollisionTotals object to update with number of collisions handled
Optional
grouped: booleantreat joints connected to focus collision simultaneously with the focus collision and with zero elasticity at the joint.
Optional
lastPass: booleando a final 'pass' on all collisions with zero elasticity to ensure that each collision has non-negative velocity. This pass only happens at end once all collisions are smaller than the small_velocity.
Optional
small_velocity: numberhandle collisions until they are this small
Optional
doPanic: booleantrue means that the velocity tolerance is loosened when there are many successive collisions
whether any change was made to the collisions
Private
handleHandles a set of collisions using the 'simultaneous' collision handling method. Finds impulses so that every collision has a change in velocity given by the initial velocity and the elasticity.
list of RigidBodyCollisions
Optional
opt_totals: CollisionTotalsCollisionTotals object to update with number of collisions handled
whether any change was made to the collisions
Private
hcs_Pick a collision to focus on, either randomly or the 'biggest'.
When we reach a point where there are no more collisions, then focus = -1
.
Part of the handleCollisionsSerial
process.
turns on debug messages
only handle collisions bigger than this
loop counter, number of times this method has been called
which contacts are joints
normal velocity at each contact
index of focus collision, or -1 when all collisions are small
Private
hcs_Handles one focus collision within the handleCollisionsSerial process. Modifies the velocity and impulse for that focus collision, and also adjusts connected collisions (if any).
use hybrid 'simultaneous and serial' collision handling
treat joints connected to focus collision
turns on debug messages
only handle collisions bigger than this
loop counter, number of times this method has been called
index of the collision to handle
which contacts are joints
elasticity at each contact
normal velocity at each contact (updated by this method)
cumulative impulse (updated by this method)
the set of collisions being treated
the matrix that says how collisions affect each other
Private
influenceReturns the change in relative normal velocity at collision ci resulting from a unit impulse on the given body at collision cj.
We have two collision points, ci and cj. How much does a unit impulse at cj on the given body affect the relative normal velocity at ci?
To have a direct effect, the body must be directly involved in both collisions. Let the notation c(a,b) mean that collision c is between bodies a and b. Then for the pair of collisions
ci(0,1), cj(1, 2)
we know that the impulse at cj directly affects the velocity of body 1, which in turn affects the velocity at ci.
On the other hand, if there is no common body in the two collisions, then there is no direct effect. For example:
ci(0, 1), cj(2, 3)
Suppose there is an additional contact/collision c(1,2). Still, the impulse at cj(2,3) only affects bodies 2 and 3, which has no direct effect at the contact ci(0,1) between bodies 1 and 2. Indirectly, you can get an effect. While the matrix entry (see makeCollisionMatrix for the above combo would be zero, you would have non-zero entries for:
ci(0,1), cj(1, 2)
ci(1,2), cj(2, 3)
Therefore, you would get an indirect effect between ci(0,1) and cj(2,3) thru the various matrix entries. In solving for the correct amount of impulse at each collision, the matrix solver will adjust for indirect connections like this. Because pushing at c(2,3) affects c(1,2), so you may need to adjust the impulse at c(1,2), which in turn will affect what’s happening at c(0,1).
So, again, the question here is to find the direct effect of a unit impulse at cj on the given body for the relative normal velocity at ci.
A unit impulse at cj(1,2) has a certain effect on body 1; the effect is a change in the velocity (all 3 velocities: horizontal, vertical, rotational) which is calculated by the size of the impulse (assumed to be 1 here), the direction of the impulse (either the normal of cj or opposite of the normal), and the vector from the cm (center of mass) of body 1 to cj; this vector is either R or R2 of cj depending on whether body 1 is the normal body or primary body in collision cj.
The change in normal velocity at ci(0,1) depends on: the change in velocity of body 1, and the vector from cm of body 1 to ci; this vector is either R or R2 of ci depending on whether body 1 is the normal body or primary body in collision ci.
The relative normal velocity, vi, at ci(a,b) is given by:
vi = ni . (va + wa x rai - (vb + wb x rbi))
ni is the normal at collision ci
va, vb is the translational velocity of body a or b respectively
wa, wb is the rotational velocity of body a or b respectively
rai, rbi is the vector from body a or b's cm to collision ci.
The change in velocity of body a from impulse fj at collision cj is:
change in translational velocity = ∆va = fj nj / ma
change in rotational velocity = ∆wa = (raj x fj nj) / Ia
ma is the mass of body a
Ia is the rotational inertia of body a
nj is the normal at collision cj
raj is the vector from body a's cm to collision cj.
Therefore, the change in vi from impulse fj at cj on body a is:
∆vi = ni . (∆va + ∆wa x rai)
= ni . ( fj nj/ ma + (raj x fj nj) x rai / Ia)
Note that the change in vi from the impulse fj at cj on body b is not considered here. Here is how the vector cross product is calculated:
(rj x n) x ri = [0, 0, rjx ny - rjy nx] x ri
= [-riy(rjx ny - rjy nx), rix(rjx ny - rjy nx), 0]
the change in relative normal velocity at collision ci resulting from a unit impulse on the given body at collision cj.
Protected
makeReturns a matrix where the (i, j)
th entry is how much the relative normal
velocity at collision i
will change from a unit impulse being applied at
collision j
.
TO DO it is a symmetric matrix, so we could save time by only calculating upper triangle and then copying to lower triangle.
TO DO this is the same as ContactSim.calculate_a_matrix, so we need to use just one of these (duplicate code currently). March 2012.
list of RigidBodyCollisions
matrix that tells how much impulse
at collision point i
affects relative normal velocity at collision point j
Prints the message to console, preceded by the current simulation time. Draws the time in green, the message in black; you can add colors in the message by adding more '%c' symbols in the message string and pass additional colors.
message to print, optionally with '%c' where color changes are desired
Rest
...colors: string[]CSS color or background strings, to change the color in the message at points in the message marked by the string '%c'
Removes the RigidBody from the simulation and SimList, and removes the corresponding variables from the VarsList.
RigidBody to remove from the simulation
Removes the ForceLaw from the list of ForceLaws operating in this simulation.
the ForceLaw to remove
whether the ForceLaw was removed
Removes the Observer from this Subject's list of Observers. An Observer may
call removeObserver
during its observe
method.
the Observer to detach from list of Observers
Removes the Parameter from the list of this Subject's available Parameters.
the Parameter to remove
Sets the Simulation back to its initial conditions, see saveInitialState, and calls modifyObjects. Broadcasts event named 'RESET'.
Restores the Simulation state that was saved with saveState.
Saves the current variables and time as the initial state, so that this initial state can be restored with reset. Broadcasts event named 'INITIAL_STATE_SAVED'.
Saves the current state of the Simulation, so that we can back up to this state later on. The state is defined mainly by the set of Simulation variables, see getVarsList, but can include other data. This state is typically used for collision detection as the before collision state, see CollisionSim.findCollisions.
Protected
setSets whether this Subject will broadcast events, typically used to temporarily disable broadcasting. Intended to be used in situations where a subclass overrides a method that broadcasts an event. This allows the subclass to prevent the superclass broadcasting that event, so that the subclass can broadcast the event when the method is completed.
whether this Subject should broadcast events
the previous value
Sets the collision distance accuracy, a fraction between zero and one; when the
collision distance is within accuracy * targetGap
of the target gap distance, then
the collision is considered close enough to handle (apply an impulse).
how close in distance to be in order to handle a collision
if value is out of the range 0 to 1, or is exactly zero
Sets a function for printing collisions. The function is called for each collision that occurs. The function takes two variables: a RigidBodyCollision and a Terminal. This can be defined from within the Terminal by the user. Here is an example function (FastBallApp is a good place to try it).
sim.setCollisionFunction(function(c,t) {
const s = c.getDetectedTime().toFixed(2)+"\t"
+c.getImpulse().toFixed(2)+"\t"
+c.getPrimaryBody().getName()+"\t"
+c.getNormalBody().getName();
t.println(s);
})
the function to print collisions, or null to turn off printing collisions
Sets the collision handling method to use,
the collision handling method to use
Sets the elasticity of all RigidBodys to this value. Elasticity is used when calculating collisions; a value of 1.0 means perfectly elastic where the kinetic energy after collision is the same as before (extremely bouncy), while a value of 0 means no elasticity (no bounce).
Broadcasts an 'ELASTICITY_SET' event.
elasticity to set on all RigidBodys, a number from 0 to 1.
if there are no RigidBodys
Sets the seed of the pseudo random number generator (RNG) used in this simulation. The RNG is used during collision handling and contact force calculation. To get reproducible results, set this seed at the start of a simulation, and the RNG will then always give the same sequence of random numbers.
the seed of the pseudo random number generator
Sets whether to show collisions visually. Note that setShowForces will also change whether to show collisions.
whether to show collisions visually.
Sets the suggested size for the SimView. This is mainly for tests to communicate with test/TestViewerApp.TestViewerApp.
the suggested size for the SimView
Sets the Terminal object that this simulation can print data into.
the Terminal object that this simulation can print data into.
Returns a minimal string representation of this object, usually giving just identity information like the class name and name of the object.
For an object whose main purpose is to represent another Printable object, it is
recommended to include the result of calling toStringShort
on that other object.
For example, calling toStringShort()
on a DisplayShape might return something like
this:
DisplayShape{polygon:Polygon{'chain3'}}
a minimal string representation of this object.
Static
Private
largestReturns size of 'largest' velocity among the set of velocities. Here 'largest' means either:
which contacts are joints
normal velocity at each contact
size of largest velocity
Generated using TypeDoc
Simulation of RigidBody movement with collisions. ImpulseSim adds methods for collision detection and collision handling to the superclass RigidBodySim.
The overall collision handling algorithm is implemented in CollisionAdvance. The two main methods that CollisionAdvance asks ImpulseSim to perform are:
findCollisions Returns the current set of collisions and also contacts. Checks each corner and edge of each body to see if it is penetrating into another body (or nearby for a contact). Creates a RigidBodyCollision corresponding to each collision (or contact) found and returns them in an array.
handleCollisions For each collision, calculates and applies the appropriate impulse to handle the collision.
More information:
2D Physics Engine Overview
The math and physics underlying RigidBodySim, ImpulseSim and ContactSim are described on the myPhysicsLab website.
ContactSim has more about how resting contacts are found.
ComputeForces is the algorithm used when finding multiple simultaneous impulses during collision handling.
Parameters Created
ParameterString named
COLLISION_HANDLING
, see setCollisionHandlingParameterNumber named
COLLISION_ACCURACY
, see setCollisionAccuracyParameterNumber named
DISTANCE_TOL
, see setDistanceTolParameterNumber named
VELOCITY_TOL
, see setVelocityTolParameterNumber named
RANDOM_SEED
, see setRandomSeedSee also the RigidBodySim super class for additional Parameters.
Collision Handling Options
There are several different collision handling options available. See the section on Multiple Simultaneous Collisions and the CollisionHandling enum.
Contacts During Collision Handling
Resting contacts, joints, and imminent collisions are involved in the collision handling mechanism, as part of a multiple simultaneous collision. The handleCollisions method can calculate the impulse needed at each point, which can be a big performance win.
Consider for example a situation where there are 2 or more bodies in resting contact, and a third body collides into one of the resting bodies. The result will be a complex series of ricochet collisions back and forth until finally all the bodies are either separating or in resting contact.
If you calculate such a scenario by considering only one collision at a time, then after each collision we would step forward in time, find collisions, back up to where we were, handle the collision, and do that over and over for each ricochet. This would take far more compute time than doing the equivalent inside of
handleCollisions
.Finding the Collision Impulse
This reviews some of the math involved in handleCollisions.
Define these symbols
Then we have
We also have
therefore
this corresponds to the equation in ComputeForces
so we put the
(1+e) v_i
factor into theb
vector when passing to ComputeForces.On the myPhysicsLab Rigid Body Collisions web page is a derivation of the following equation which gives the value of
j
for a single collision.In an earlier version of
handleCollisions
, the above equation was used directly. Now we use ComputeForces because there might be multiple simultaneous collisions, but it amounts to the same equation. If there is only a single collision, then the above equation still exists in the following pieces:the
(1+e) v_i
factor is in theb
vector (v_i
is the same asvab
above).the denominator is the
A
matrixThis corresponds to solving for
j
asThis only works when A is a scalar value not a matrix. Otherwise you would left-multiply by the inverse of the A matrix.
TO DO use UtilCollision.subsetCollisions1 to arrange collisions into separate groups, so that contacts are handled with zero elasticity more often. Currently, when contacts are handled simultaneously with a high-velocity collision, we use the non-zero elasticity on the contacts also, even if they are not connected to the collision.
TO DO the momentum stuff was pretty klugey and ugly; I'm commenting it out Dec 2009; the text info might be useful, but it needs to be made prettier. The 'momentum arrows' didn't seem to add much insight.
TO DO the distance and velocity tolerance is stored on the RigidBody and also here; this is confusing and error prone; and if they are different then you might see the wrong value in the user control; could it be useful to have different distance/velocity tolerance on different bodies? If not, perhaps we can find a better way to communicate the distance/velocity tolerance to the RigidBody.
TO DO collision impact: make these into a SimObject, similar to Force, and add to SimList; then user can decide how to represent them
TO DO The method findCollisions is doing twice as much work as it needs to: once you check if body A collides with body B, you don't have to check if body B collides with body A.