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 self-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).
External Forces
External force applied to each particle during a simulation step. This value gets resetted to zero at the end of each simulation step.
External Torques
External torque applied to each particle during a simulation step. This value gets resetted to zero at the end of each simulation step.
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 actor id (less significant bits 0-23, up to 16 million actors), if it's self colliding (bit 24), particle type (bit 25, solid or fluid) or one-sided (bit 26).
Filter
Integer value used for collision filtering. Less significat 16 bits contain a collision category (from 0 to 15) and most significant 16 bits contain a collision mask. See collisions.
Color
Particle color, only used by ObiParticleRenderer and ObiFluidRenderer.

The following properties are used by fluid particles only, see fluid blueprints for a detailed description of each one:

Fluid Data
A 4-component vector containing basic fluid data for fluid particles: density, lagrange multiplier, local gradient, neighborhood gradient.
User Data
A 4-component vector that contains arbitrary per-particle data set by the user. The data in this array will be affected by diffusion.
Diffusion
Intensity of the diffusion effect applied to the User Data values.
Vorticity confinement
Per-particle value that controls the amount of vorticity injected back into the fluid. Higher vorticity values will result in more "swirly" fluid.
Vorticity
Particle vorticity. This can be understood as the angular angular velocity of a fluid particles' neighborhood.
Smoothing radii
Per-particle neighborhood radius, expressed as a percentage of the particle's radius. For fluid particles, all neighboring particles within this distance will be considered.
Buoyancy
Buoyancy values. A negative value will make the particle denser than air, a positive value will make it lighter than air.
Athmospheric Drag
Amount of drag applied to particles near the surface of the fluid due to friction against surrounding air.
Athmospheric Pressure
Amount of pressure applied to particles near the surface of the fluid due to the surrounding air.
Rest density
The particle's rest density, expressed in kg/m3. Denser particles have higher mass.
Viscosities
Viscosity value for each particle. When neighboring particles have different viscosity values, their viscosities are averaged.
Surface tension
Per-particle surface tension value.

Each ObiSolver has an array for each one of these properties, that stores the current data for all particles in use by any actor managed by the solver. All spatial properties (positions, orientations, velocities, vorticities, etc) are expressed in the solver's local space.

Actor particle indices run from 0 to the amount of particles in that actor. However, since particle data for an actor might be scattered across the solver arrays (see architecture), you need to map from actor index to solver index. You can map from actor particle indices to solver particle indices using the actor's solverIndices array.

// index of the first particle in the actor:
int actorIndex = 0;

// solver index of the first particle in the actor.
int solverIndex = actor.solverIndices[actorIndex];

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

You can also do the opposite: given a solver index, find out which actor it belongs to and its index in the actor:

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

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.

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 them like this:

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

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.isLoaded)
			return;

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

		// Iterate over all particles in an actor:
		for (int i = 0; i < actor.solverIndices.Length; ++i){

			// retrieve the particle index in the solver:
			int solverIndex = actor.solverIndices[i];

			// look up the inverse mass of this particle:
			float invMass = actor.solver.invMasses[solverIndex];

			// accumulate it:
			if (invMass > 0)
			{
				massAccumulator += 1.0f / invMass;
				com += actor.solver.positions[solverIndex] / invMass;
			}

		}

		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 solver's local space. Since we are interested in drawing the velocities in world space, Gizmos.matrix has been set to the solver'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.isLoaded)
			return;

		Gizmos.color = Color.red;
		Gizmos.matrix = actor.solver.transform.localToWorldMatrix;

		for (int i = 0; i < actor.solverIndices.Length; ++i){
			int solverIndex = actor.solverIndices[i];
			Gizmos.DrawRay(actor.solver.positions[solverIndex],
				       actor.solver.velocities[solverIndex] * 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.isLoaded){
			for (int i = 0; i < actor.solverIndices.Length; ++i){

				int solverIndex = actor.solverIndices[i];

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

Using Jobs to process particles

You can use AsNativeArray<T>() to wrap a NativeArray around any of the solver particle data lists listed at the top of this page (positions, velocities, orientations, etc). Note this doesn't perform a copy of the data, it just maps a NativeArray onto it. This allows you to access particle data efficiently from Unity jobs, and write custom multithreaded code.

Here's an example component that attracts all particles in an actor towards it, by applying a custom acceleration in a Burst-compiled job:

a gravity well implemented using jobs.

using UnityEngine;
using Obi;
using Unity.Burst;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;

public class GravityWell : MonoBehaviour
{
    public ObiActor actor;
    public float gravityStrength = 1;

    public void Start()
    {
        if (actor != null)
            actor.solver.OnBeginStep += Solver_OnBeginStep;
    }

    public void OnDestroy()
    {
        if (actor != null)
            actor.solver.OnBeginStep -= Solver_OnBeginStep;
    }

    private void Solver_OnBeginStep(ObiSolver solver, float stepTime)
    {
        var indices = new NativeArray<int>(actor.solverIndices, Allocator.TempJob);

        var job = new CustomGravityJob
        {
            indices = indices,
            velocities = actor.solver.velocities.AsNativeArray<float4>(),
            positions = actor.solver.positions.AsNativeArray<float4>(),
            wellPosition = actor.solver.transform.InverseTransformPoint(transform.position),
            gravityStrength = gravityStrength,
            deltaTime = stepTime,
        };

        job.Schedule(indices.Length, 128).Complete();
    }

    [BurstCompile]
    struct CustomGravityJob : IJobParallelFor
    {
        [ReadOnly][DeallocateOnJobCompletion] public NativeArray<int> indices;
        [ReadOnly] public NativeArray<float4> positions;
        public NativeArray<float4> velocities;

        [ReadOnly] public float3 wellPosition;
        [ReadOnly] public float gravityStrength;
        [ReadOnly] public float deltaTime;

        public void Execute(int i)
        {
            var index = indices[i];
            var vel = velocities[index];
            vel.xyz += math.normalizesafe(wellPosition - positions[index].xyz) * gravityStrength * deltaTime;
            velocities[index] = vel;
        }
    }
}