Scripting Particles

Particles are the basic building blocks of Obi. Each particle has the following properties:

Activation flag
A boolean value that indicates if the particle is being updated by the solver or not. Inactive particles are particles that have been optimized out of the simulation, or that have been reserved for later use (when a cloth tears, or a rope increases its length, for instance).
Position
Position of the particles in the actor (in local space).
Rest position
Rest position of the particles in the actor. These are used to ignore inter-particle collisions between particles that were already intersecting in the actor's rest configuration.
Orientation
Orientation of the particles in the actor (in local space).
Rest orientation
Rest orientation of the particles in the actor.
Velocity
Particle velocities (in local space).
Angular velocity
Particle angular velocities (in local space).
Inverse mass
Inverse mass for each particle. An inverse mass of 0 means the particle's mass is infinite, so its position will be unaffected by dynamics (allowing you to override it manually).
Inverse rotational mass
Inverse rotational mass for each particle. An inverse mass of 0 means the particle's rotational mass is infinite, so its orientation will be unaffected by dynamics (allowing you to override it manually).
Principal radii
Particle radius used for collision detection, in each of the ellipsoid principal axis, sorted in descending magnitude.
Phase
Integer value that is used to determine collision phase and particle type (solid or fluid).
Color
Particle color, only used by ObiParticleRenderer and ObiFluidRenderer.
External forces
Per-particle external forces. You can write values into the solver.externalForces array to apply custom forces to particles.

Each ObiActor has an array for each of these properties, that contains the rest (initial) data for all the particles of that actor. These arrays are serialized by Unity when you save a prefab or a scene that contains the actor.

Each ObiSolver also has an array for each property, that stores the current data for all particles in use by any actor managed by the solver.

Actor particle indices run from 0 to the amount of particles in that actor. Solver particle indices run from 0 to solver.maxParticles, as they simulate multiple actors. You can map from actor particle indices to solver particle indices using the actor's particleIndices array.

// solver index of the first particle in the actor.
int particleSolverIndex = actor.particleIndices[0];

// use it to get the particle's current velocity.
Vector3 velocity = solver.velocities[particleSolverIndex];
			

Some examples

Particle positions, orientations and linear/angular velocities are usually the only properties you´ll be interested in retrieving, since all other properties are not modified by the solver during simulation.

The solver will also generate renderable particle positions and orientations at the end of every frame. You can access local-space renderable positions/orientations directly:

Vector3 pos = solver.renderablePositions[actor.particleIndices[0]];
			

Or you can call actor.GetParticlePosition(particleIndex) (respectively actor.GetParticleOrientation(particleIndex)) at any time for any actor managed by the solver, to get a renderable particle position conveniently expressed in world space.

The next example is a component that will calculate the center of mass of an actor as the mass-weighted sum of all particle positions, and render it using OnDrawGizmos()

using UnityEngine;
using Obi;

[RequireComponent(typeof(ObiActor))]
public class ActorCOM : MonoBehaviour {

	ObiActor actor;

	void Awake(){
		actor = GetComponent<ObiActor>();
	}

	void OnDrawGizmos(){

		if (actor == null || !actor.InSolver)
			return;

		Gizmos.color = Color.red;
		Vector3 com = Vector3.zero;
		float massAccumulator = 0;

		// To iterate over all particles in an actor, you can use the length of any property array.
		// They are all the same length. In this case, we use the invMasses array.
		for (int i = 0; i < actor.invMasses.Length; ++i){

			if (actor.invMasses[i] > 0){
				massAccumulator += 1.0f / actor.invMasses[i];
				com += actor.GetParticlePosition(i) / actor.invMasses[i];
			}

		}

		com /= massAccumulator;
		Gizmos.DrawWireSphere(com,0.1f);
	}
}
			

Here's a sample script to visualize an actor's particle velocities using OnDrawGizmos(). Positions and velocities are expressed in the actor's local space. Since we are interested in drawing the velocities in world space, Gizmos.matrix has been set to the actor's local-to-world matrix.

using UnityEngine;
using Obi;

[RequireComponent(typeof(ObiActor))]
public class VelocityVisualizer : MonoBehaviour {

	ObiActor actor;

	void Awake(){
		actor = GetComponent<ObiActor>();
	}

	void OnDrawGizmos(){

		if (actor == null || !actor.InSolver)
			return;

		Gizmos.color = Color.red;
		Gizmos.matrix = actor.ActorLocalToWorldMatrix;

		for (int i = 0; i < actor.velocities.Length; ++i){
			int indexInSolver = actor.particleIndices[i];
			Gizmos.DrawRay(solver.positions[indexInSolver],
				       solver.velocities[indexInSolver] * Time.fixedDeltaTime);
		}
	}
}
			

The following script fixes in place all particles that are near a given anchor transform.

using UnityEngine;
using System.Collections;
using Obi;

[RequireComponent(typeof(ObiActor))]
public class DistanceAnchor : MonoBehaviour {

	ObiActor actor;
	public Transform anchor;
	public float anchorRadius = 0.5f;

	void Awake(){
		actor = GetComponent<ObiActor>();
	}

	void Start(){
		if (actor.InSolver){
			for (int i = 0; i < actor.velocities.Length; ++i){

				// if the particle is visually close enough to the anchor, fix it.
				if (Vector3.Distance(actor.GetParticlePosition(i), anchor.position) < anchorRadius){
					int indexInSolver = actor.particleIndices[i];
					solver.velocities[indexInSolver] = Vector3.zero;
					solver.invMasses[indexInSolver] = 0;
				}
			}
		}
	}
}