Contact and Collision
Collision Group and Mask
Collision groups and masks in RaiSim are 64-bit bit sets (raisim::CollisionGroup), as in most other physics engines.
raisim::COLLISION(n) returns the bit for group n (1 << n). Consider the following example:
raisim::World world;
auto sphere0 = world.addSphere(1, 1, "default", raisim::COLLISION(0), raisim::COLLISION(0) | raisim::COLLISION(1));
auto sphere1 = world.addSphere(1, 1, "default", raisim::COLLISION(1), raisim::COLLISION(0) | raisim::COLLISION(1));
auto sphere2 = world.addSphere(1, 1, "default", raisim::COLLISION(2), raisim::COLLISION(1));
auto sphere3 = world.addSphere(1, 1, "default", raisim::COLLISION(3), -1);
In the above example, sphere0 is in collision group 0 and can collide with collision groups 0 and 1.
sphere1 is in collision group 1 and can collide with collision groups 0 and 1.
sphere2 is in collision group 2 and can collide with collision group 1.
sphere3 is in collision group 3 and its mask accepts every group (-1 sets all bits).
The collision group and mask use AND logic. For A and B to collide, A’s group must be in B’s mask and B’s group must be in A’s mask.
sphere0 can collide with sphere1.
sphere1 cannot collide with sphere2 (sphere1’s mask does not include group 2).
sphere3 cannot collide with any of the other spheres, because none of their masks includes group 3.
It can still collide with a ground added with world.addGround(), whose default mask is -1.
By default, movable objects use collisionGroup = 1 (that is, COLLISION(0)) and collisionMask = -1, so they collide with everything.
Static objects (e.g., ground and heightmap) are in the static collision group RAISIM_STATIC_COLLISION_GROUP
(bit 63; bit 31 on Windows) and their default mask is -1.
Contacts
raisim::Object (and thus raisim::ArticulatedSystem and raisim::SingleBodyObject) have a method getContacts which returns the list of contacts.
For example,
auto& contactsOnAnymal = anymal->getContacts();
The Contact class is header-only and can be found at include/raisim/contact/Contact.hpp.
Each contact has two participants, objectA and objectB. The position (getPosition()) and
normal (getNormal()) are expressed in the world frame; the normal points from objectB to objectA.
The impulse (getImpulse()) is the impulse acting on objectA, expressed in the contact frame;
objectB receives the negated impulse. Divide it by the time step to obtain a force.
A contact frame is defined such that its z-axis is collinear with the contact normal and its origin is at the contact point.
Its x- and y-axes are chosen arbitrarily.
getContactFrame() returns the transpose of the contact frame’s rotation: each row is a contact-frame axis in world coordinates.
Here is a more detailed example:
/// Check all contact impulses acting on "LF_SHANK"
auto footIndex = anymal->getBodyIdx("LF_SHANK");
/// For all contacts on the robot, check ...
for(auto& contact: anymal->getContacts()) {
if (contact.skip()) continue; /// the second entry of a self-collision point is set to 'skip'
if ( footIndex == contact.getlocalBodyIndex() ) {
std::cout<<"Contact impulse in the contact frame: "<<contact.getImpulse().e().transpose()<<std::endl;
/// the impulse acts on objectA. You can check if this object is objectA or B by:
std::cout<<"is ObjectA: "<<contact.isObjectA()<<std::endl;
std::cout<<"Contact frame: \n"<<contact.getContactFrame().e().transpose()<<std::endl;
/// getContactFrame() is transposed, so transpose it back to map contact-frame vectors to the world frame.
std::cout<<"Contact impulse in the world frame: "<<(contact.getContactFrame().e().transpose() * contact.getImpulse().e()).transpose()<<std::endl;
std::cout<<"Contact Normal in the world frame: "<<contact.getNormal().e().transpose()<<std::endl;
std::cout<<"Contact position in the world frame: "<<contact.getPosition().e().transpose()<<std::endl;
std::cout<<"It collides with: "<<world.getObject(contact.getPairObjectIndex())->getName()<<std::endl;
if (contact.getPairObjectBodyType() != raisim::BodyType::STATIC) {
/// Static objects do not store contacts, so check whether the pair object is static.
/// This saves computation in RaiSim.
world.getObject(contact.getPairObjectIndex())->getContacts(); /// You can use the same methods on the pair object
}
std::cout<<"See Contact.hpp for the full list of methods"<<std::endl;
}
}
getImpulse() dereferences an internal pointer that is set only for contacts of awake dynamic
objects during World::integrate(). Check getImpulsePtr() for nullptr before reading
impulses on kinematic objects or before the first integration step.
Collision detection details
For a detailed breakdown of collision pairs, narrowphase algorithms, and per-pair contact counts, see the Collision Detection and Colliders section.
Contact Solver Notes
RaiSim uses a bisection-based per-contact solver for rigid contacts and tendon
constraint rows. Closed-loop pin and equality constraints of articulated systems
are eliminated exactly before the solve (see
Closed-Loop Systems). World::setContactSolverParam
still accepts the historical alpha arguments, but the current implementation
keeps the three alpha values fixed internally; only maxIter (default 150)
and threshold (default 1e-8 per constraint row) affect the solver
configuration. World::setERP(erp, erp2) controls the position-error
reduction terms used by the contact solve.
The solver warm-starts ordinary rigid contacts from the previous converged solve when the object pair matches and a previous contact lies within 1 cm of the new one. Contacts that are approaching quickly (impacts) start from zero. The cached impulse is stored in world coordinates and projected into the current contact frame and friction cone before it is applied. Warm-start data is intentionally not reused after a non-converged or stalled solve.
Particle-style contacts are handled differently. Deformable and granular contact points are particles or mesh vertices, so their positions do not identify a persistent contact. The contact solver therefore does not warm-start deformable or granular contacts; they are solved from zero impulses each step.
World::setContactSolverIterationOrder(order) sets the starting sweep
direction for the next contact solve (true is forward). The bisection
solver then flips the stored direction after each solve, so subsequent solves
alternate between forward and reverse sweeps unless the application sets the
starting direction again. Pin rows added directly to the solver make every solve
sweep forward; articulated-system loop constraints are eliminated before the solve
and do not.
API
You can get a vector of contacts on an object using raisim::Object::getContacts.
Each element in the vector has the following API:
-
class Contact
A contact point between two collision bodies, as stored in Object::getContacts().
Every contact has two participants, objectA and objectB (assigned by the collision detector). A Contact entry is stored in each participant’s contact list, except on STATIC objects. Both entries share the same position, normal, contact frame, depth, impulse and collision-body handles; use isObjectA() to tell which side the entry belongs to.
Conventions:
getNormal() is a unit vector pointing from objectB toward objectA (in both entries).
getContactFrame() is a rotation matrix whose rows are the contact-frame axes expressed in the world frame; its third row equals the normal.
getImpulse() is the impulse acting on objectA, expressed in the contact frame. objectB receives the negated impulse. The world-frame impulse on objectA is getContactFrame().transpose() * getImpulse().
Public Functions
-
inline explicit Contact(const Vec<3> &position, const Vec<3> &normal, const Mat<3, 3> &frame, bool objectA, size_t contactProblemIndex, size_t contactIndexInObject, size_t pairObjectIndex, BodyType pairObjectBodyType, size_t pairContactIndexInPairObject, size_t localBodyIndex, double depth, CollisionBodyHandlePtr colA, CollisionBodyHandlePtr colB)
Created by the World during collision detection; users normally only read contacts.
- Parameters:
position – [in] Contact position in the world frame.
normal – [in] Unit normal pointing from objectB to objectA.
frame – [in] Contact frame (see getContactFrame()).
objectA – [in] True if this entry belongs to objectA.
contactProblemIndex – [in] Index in World::getContactProblem().
contactIndexInObject – [in] Index of this entry in its object’s contact list.
pairObjectIndex – [in] World index of the other object.
pairObjectBodyType – [in] Body type of the other object.
pairContactIndexInPairObject – [in] Index of the paired entry in the other object’s contact list.
localBodyIndex – [in] Local index of the body (within this object) that is in contact.
depth – [in] Signed separation (negative when penetrating), see getDepth().
colA – [in] Collision body of objectA.
colB – [in] Collision body of objectB.
-
inline const Vec<3> &getPosition() const
the contact position
- Returns:
the contact position in the world frame
-
inline const Vec<3> &getSolverPosition() const
Point used for the contact Jacobian. It can be the body witness while getPosition() remains the reported terrain-surface point.
- Returns:
the solver contact position in the world frame (usually equal to getPosition())
-
inline const Vec<3> &getNormal() const
the contact normal vector, pointing from objectB to objectA. The same vector is stored in the entries of both objects, so check isObjectA() before using it as “this object’s” normal.
- Returns:
unit normal in the world frame
-
inline const Mat<3, 3> &getContactFrame() const
returns a TRANSPOSE of the frame that the impulse is expressed: each row is a contact-frame axis in world coordinates and the third row is the normal. A world-frame vector v is expressed in the contact frame as getContactFrame() * v.
- Returns:
contact frame
-
inline size_t getIndexContactProblem() const
returns the corresponding index in raisim::World::getContactProblem
- Returns:
contact index
-
inline size_t getIndexInObjectContactList() const
returns the corresponding index in raisim::Object::getContacts
- Returns:
contact index
-
inline size_t getPairObjectIndex() const
returns the contacting object index in raisim::World::getObjectList
- Returns:
object index
-
inline size_t getPairContactIndexInPairObject() const
returns the contact index in the contacting (the paired) object in raisim::Object::getContacts This does not work if the pair body is STATIC because contacts on static bodies are not stored. First check the pair body type with getPairObjectBodyType
- Returns:
contact index
-
inline const Vec<3> *getImpulsePtr() const
returns the impulse pointer. You have to divide this number by the time step to get the force
- Returns:
pointer to the impulse on objectA in the contact frame (N*s); see getImpulse(). nullptr until the World binds it in World::integrate(), which it does only for contacts of awake DYNAMIC objects (e.g. contacts stored on KINEMATIC objects keep nullptr).
-
inline const Vec<3> &getImpulse() const
returns the impulse. You have to divide this number by the time step to get the force
- Returns:
impulse (N*s) acting on objectA, expressed in the contact frame (z component along the normal). Negate it for objectB. Dereferences getImpulsePtr() without a check, so it must only be called when that pointer is non-null.
-
inline bool isObjectA() const
returns if the object is objectA. When there is a contact, the two paired objects are assigned to be objectA and objectB arbitrarily. The contact frame is defined such that its z-axis is pointing towards the objectA. The contact impulse is defined as an external force that objectA experiences.
- Returns:
true if this entry belongs to objectA
-
inline BodyType getPairObjectBodyType() const
returns the body type of the paired object. Please read https://raisim.com/sections/Object.html#body-types to learn about it
- Returns:
the body type
-
inline Mat<3, 3> &getInvInertia()
returns the inverse apparent inertia of the contacting point of this object
- Returns:
inverse apparent inertia, expressed in the contact frame
-
inline size_t getlocalBodyIndex() const
get local body index of this contact. The local index is assigned to each moving body in an object
- Returns:
the body index.
-
inline double getDepth() const
The signed separation of the contact along the normal, in meters
- Returns:
the negated penetration depth: negative while the bodies overlap
-
inline bool isSelfCollision() const
returns if the contact is self collision (two bodies of the same articulated system). Only the objectA entry is flagged; the objectB entry has skip() == true instead.
- Returns:
if this is a self collision
-
inline bool skip() const
this is set true for the objectB entry of a self-collision point so that you don’t count them twice
- Returns:
if you can skip this contact point while iterating contact points
-
inline CollisionBodyHandlePtr getCollisionBodyA()
get the collision body handle of objectA (the same in both entries, so it is not necessarily this object’s body; check isObjectA())
- Returns:
collision body
-
inline CollisionBodyHandlePtr getCollisionBodyB()
get the collision body handle of objectB (the same in both entries; check isObjectA())
- Returns:
collision body
Public Static Functions
-
static inline void computeFrame(const Vec<3> &zAxis, Mat<3, 3> &frame)
Build a contact frame from a normal: the third row of
frameis the normalized zAxis, the first two rows are orthonormal tangent directions (all expressed in the world frame).- Parameters:
zAxis – [in] Contact normal (need not be normalized; must be non-zero).
frame – [out] Rotation matrix whose rows are the contact axes (world-to-contact rotation).