Particles are the basic building blocks of Obi. Each particle has the following properties:
The following properties are used by fluid particles only, see fluid blueprints for a detailed description of each one:
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:
A reference to the ObiActor to which this particle belongs.
The index of this particle in the actor's arrays.
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; } } } } }
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:
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; } } }