Collision callbacks

Obi does not work with the default Unity collision callbacks (OnCollisionEnter,OnCollisionExit,OnCollisionStay...) because of performance reasons. Instead, the ObiSolver component can provide a list of all contacts generated each frame upon request. You can then iterate this list of contacts and perform any custom logic you need.

To request the simplex-collider contact list from the solver, subscribe to its OnCollision event. To retrieve the simplex-simplex contact list, subscribe to its OnParticleCollision event.

The Oni.Contact struct contains the following information:

BodyA

Index of the first simplex involved in the contact.

BodyB

Index of the collider / second simplex involved in the contact. In case of simplex-collider contacts, this index can be used to retrieve the actual collider object, see below.

PointA

Barycentric coordinates of the contact point on the surface of the first simplex.

PointB

Barycentric coordinates of the contact point on the surface of the second simplex. For simplex-collider contacts, solver-space contact point on the surface of the collider.

Distance

Distance between both bodies (simplex-collider or simplex-simplex) before resolving the collision, if any. The distance can be either positive (bodies do not overlap each other) or negative (both bodies partially or completely overlap). A value of zero means the bodies are just touching each other.

Obi uses a continuous-collision detection method known as speculative contacts, which can generate contacts even when an actual collision isnĀ“t taking place. If you want to prune all speculative contacts and consider actual collisions only, check for distances below a small threshold (e.g 0.01).

Normal

The collider's surface normal at the contact point.

Tangent

The collider's surface tangent at the contact point.

Bitangent

The collider's surface bitangent at the contact point.

Normal impulse

Impulse magnitude applied by the contact in the normal direction. The larger this value, the stronger the initial collision force was.

Tangent impulse

Impulse magnitude applied by the contact in the tangent direction. The larger this value, the higher the friction between the particle and the collider.

Bitangent impulse

Impulse magnitude applied by the contact in the bitangent direction. The larger this value, the higher the friction between the particle and the collider.

Stick impulse

Impulse magnitude applied by the contact against the normal direction. This impulse keeps the particle stuck to the collider, only generated for sticky materials.

Retrieving the collider involved in a contact

You can get a reference to the collider involved in any contact by doing:

ObiColliderBase collider = ObiColliderWorld.GetInstance().colliderHandles[contact.bodyB].owner;

Retrieving the particle involved in a contact

In Obi 6.X, collision detection is simplex-based. A simplex can be a triangle (3 particles), an edge (2 particles) or a single particle. The solver.simplices array stores simplices as particle index tuples (see surface collisions for more info). To retrieve the solver indices of the particles in a simplex, you can do the following:

// retrieve the offset and size of the simplex in the solver.simplices array:
int simplexStart = solver.simplexCounts.GetSimplexStartAndSize(contact.bodyA, out int simplexSize);

// starting at simplexStart, iterate over all particles in the simplex:
for (int i = 0; i < simplexSize; ++i)
{
	int particleIndex = solver.simplices[simplexStart + i];

	// do something with each particle, for instance get its position:
	var position = solver.positions[particleIndex];
}

In case there isn't any actor using surface collisions in your solver, all simplices will have size 1 and the code above can be simplified to:

// get the particle index directly, as all simplices are guaranteed to have size 1:
int particleIndex = solver.simplices[contact.bodyA];

// do something with the particle, for instance get its position:
var position = solver.positions[particleIndex];

Retrieving the actor involved in a contact

You can get a reference to the ObiActor (ObiCloth,ObiRope,ObiEmitter...) involved in any contact by doing:

ObiSolver.ParticleInActor pa = solver.particleToActor[particleIndex];

See the above section to learn how to retrieve particle indices from the contact.

The ParticleInActor struct has two public variables:

Actor

A reference to the ObiActor to which this particle belongs.

IndexInActor

The index of this particle in the actor's arrays. For instance, you can kill a ObiEmitter particle upon collision by doing this:

ObiSolver.ParticleInActor pa = solver.particleToActor[particleIndex];
ObiEmitter emitter = pa.actor as ObiEmitter;

if (emitter != null)
    emitter.life[pa.indexInActor] = 0;

Some examples

Here's an example of a component that subscribes to the solver's OnCollision event and iterates over all the contacts. Then, it retrieves the collider object for all actual (non-speculative) contacts:

using UnityEngine;
using Obi;

[RequireComponent(typeof(ObiSolver))]
public class CollisionEventHandler : MonoBehaviour {

 	ObiSolver solver;

	void Awake(){
		solver = GetComponent<ObiSolver>();
	}

	void OnEnable () {
		solver.OnCollision += Solver_OnCollision;
	}

	void OnDisable(){
		solver.OnCollision -= Solver_OnCollision;
	}

	void Solver_OnCollision (object sender, Obi.ObiSolver.ObiCollisionEventArgs e)
	{
		var world = ObiColliderWorld.GetInstance();

		// just iterate over all contacts in the current frame:
		foreach (Oni.Contact contact in e.contacts)
		{
			// if this one is an actual collision:
			if (contact.distance < 0.01)
			{
				ObiColliderBase col = world.colliderHandles[contact.bodyB].owner;
				if (col != null)
				{
					// do something with the collider.
				}
			}
		}
	}

}
			

A script very similar to this one has been used to draw all contacts in the following image,using Unity's Gizmos class. Here you can see speculative contacts (distance > 0) drawn in green, and actual contacts (distance <= 0) drawn in red:


Note the row of speculative contacts right across the middle. Since the floor plane is made out of 2 triangles and all particles about to touch the floor lie inside both triangles' bounding boxes, Obi considers they all have a quite high probability of colliding with both triangles. So speculative contacts are generated for the closest feature on the opposite triangle: the central edge. These are ignored later on the constraint enforcement phase, so normal and tangent impulses are only calculated and applied for the red contacts. However stick impulses can still be generated for speculative contacts, if they are close enough to becoming an actual contact.

Another example: the following script will scale gravity for all particles in contact with a trigger that has the tag "zeroGravity". This is done by applying an acceleration that counteracts gravity to all particles in contact with the trigger.

using UnityEngine;
using Obi;

[RequireComponent(typeof(ObiSolver))]
public class ZeroGravityZone: MonoBehaviour
{
    ObiSolver solver;
    public float antiGravityScale = 2;

    ObiSolver.ObiCollisionEventArgs collisionEvent;

    void Awake()
    {
        solver = GetComponent<ObiSolver>();
    }

    void OnEnable()
    {
        solver.OnCollision += Solver_OnCollision;
    }

    void OnDisable()
    {
        solver.OnCollision -= Solver_OnCollision;
    }

    void Solver_OnCollision(object sender, Obi.ObiSolver.ObiCollisionEventArgs e)
    {
        // calculate an acceleration that counteracts gravity:
        Vector4 antiGravityAccel = -(Vector4)solver.parameters.gravity * antiGravityScale * Time.deltaTime;

        var world = ObiColliderWorld.GetInstance();
        foreach (Oni.Contact contact in e.contacts)
        {
            // this one is an actual collision:
            if (contact.distance < 0.01)
            {
                ObiColliderBase col = world.colliderHandles[contact.bodyB].owner;

                // if this collider is tagged as "zero gravity":
                if (col != null && col.gameObject.CompareTag("zeroGravity"))
                {
                    // get the index of the particle involved in the contact:
                    int particleIndex = solver.simplices[contact.bodyA];

                    // set the particle velocity:
                    solver.velocities[particleIndex] += antiGravityAccel;
                }
            }
        }
    }
}

no triggers, the fluid pours down and splashes on contact with the floor.
the glass box is a zeroGravity trigger, using antiGravityScale=2 in the above script will reverse gravity for all particles within the trigger.