-
Notifications
You must be signed in to change notification settings - Fork 50
Getting Started
The ORCA Simulation is computed in steps. A single step represent the time elasped between the current simulation step and the next result. It's very much like your regular 'frame', but tied to a scalar value. That just means you'll probably want to use Time.deltaTime
as step value.
ℹ️ This page will go through creating what already exists within the (generic) ORCABundle.cs class, in order to make things more explicit. If you don't care, you can jump directly to Using ORCABundle.
The ORCA Simulation needs a few things to run (any of which can be empty), namely :
- A list of agents (simulated each step)
- A list of obstacles (static, computed once)
- A list of dynamic obstacles (moving/shape shifting, computed each step)
- A list of raycasts (resolved each step)
- A simulation solver
Each ingredient will be fed to the simulation, and it can run from there.
When the simulation run a single steps, it account for a variety of things, but the most important properties are each agent' radius
, prefVelocity
and position
.
Agent collision is expressed as a circle, while obstacles are either open or closed polylines.
⚠️ Each simulation step is executed in a vaccum, meaning that while the simulation takes care of maintaining Agents' position up to date, a simulation step does not rely on previous steps to yield accurate results.
The basic requirements look like so :
using UnityEngine;
using Unity.Mathematics; // ORCA uses the unity.mathematic package
using Nebukam.ORCA; // Import the ORCA package
public class MyORCASimulation : MonoBehaviour{
// Container for our agents
protected AgentGroup<Agent> m_agents = new AgentGroup<Agent>();
// Containers for our obstacles
protected ObstacleGroup m_staticObstacles = new ObstacleGroup();
protected ObstacleGroup m_dynamicObstacles = new ObstacleGroup();
// Container for our raycasts
protected RaycastGroup m_raycasts = new RaycastGroup();
// The simulation solver
protected ORCA m_orca;
}
Next, we'll instantiate & setup a new solver. We only need to do this once, so we'll use Awake
.
void Awake(){
// Create the solver
m_orca = new Nebukam.ORCA.ORCA();
// Set the plane on which we execute the simulation
// Make sure to be coherent when setting agents initial position ;)
m_orca.plane = AxisPair.XY;
// Set each of the simulation components
// we initialized in properties earlier
m_orca.agents = m_agents;
m_orca.staticObstacles = m_staticObstacles;
m_orca.dynamicObstacles = m_dynamicObstacles;
m_orca.raycasts = m_raycasts;
}
Note that each container will be kept updated within the simulation as you update their content. For example, no need to do m_orca.agent = m_agents
each time you made changes to the agents list. Additionally, if you are updating the simulation asynchronously, the changes will only be reflected on the next Schedule of the simulation, as data is 'locked' in place for the time of the simulation.
Now let's add an agent to the simulation.
You can do so where you see fit, just make sure you keep a reference to it somewhere. For the sake of the example, we'll add this line to our Awake
method :
// Create an agent inside the m_agents group directly.
Agent myFirstAgent = m_agents.Add(new float3(0f,0f,0f)) as Agent;
// Give our agent a radius
myFirstAgent.radius = 1f;
// Give our agent a prefered velocity
// This is the velocity the simulation will use as an 'intention'.
myFirstAgent.prefVelocity = new float3(1f,1f,0f);
// Set our agent maxSpeed
// This allows the simulation to know to which extent (if any) it can overshoot the agent's velocity
// to accomodate avoidance.
myFirstAgent.maxSpeed = 1f; //1f = max 1 unit per second.
We now have the most basic agent.
Finally, we want to update our simulation!
Since ORCA run on Unity's job system, you can execute the simulation synchronously, or asynchronously. Each method has its own pros & cons; we'll implement both in the Update
method.
This is the most straightforward approach, but also the most CPU-intensive approach.
Pro : allows you to get the simulation results right away.
Con : defeat the purpose of multi-threading (but still runs on multiple threads), can be super expensive depending on the complexity of the simulation.
// Synchronous way
void Update(){
// Force execution of the simulation, using Time.deltaTime as a timestep
m_orca.Run(Time.deltaTime);
// That's it, sim is up to date from there \o/
// Use results...
}
This is less straightforward, but much more efficient.
Pro : Runs asynchronously on multiple threads, unclog the mean thread.
Con : The results of the simulation are offset by at least an Update
, depending on the complexity of your setup.
// Asynchronous way
void Update(){
// Check if the simulation is complete yet, and wraps it up if it is
if(m_orca.TryComplete()){
// That's it, sim is up to date from there
// Use results...
}
// Schedule the simulation on the job system
// If the simulation is already running, the delta time will be added up
// so the simulation stays up-to-date with the current time.
m_orca.Schedule(Time.deltaTime);
}
So, that's all fun and games, we now have a running simulation with a single agent. But how do you get back the actual simulation result to make something out of it ?
Remember our agent from earlier, myFirstAgent
? It's the data holder. It's been updated by the simulation.
You can fetch the following from it :
// This the agent' velocity withing the simulation.
// That velocity guarantees it doesn't hit an obstacle or another agent.
myFirstAgent.velocity;
// That's the agent' position, updated according to its computed velocity.
myFirstAgent.position;
If you do nothing from there, and keep the simulation running, the agents will always be up to date.
You can edit their prefVelocity, radiuses etc as you see fit, whenever you see fit, it will be accounted for at the end of the next update.
The final class, with added IDisposable implementation :
using UnityEngine;
using Unity.Mathematics; // ORCA uses the unity.mathematic package
using Nebukam.ORCA; // Import the ORCA package
public class MyORCASimulation : MonoBehaviour, System.IDisposable
{
protected AgentGroup<Agent> m_agents = new AgentGroup<Agent>();
protected ObstacleGroup m_staticObstacles = new ObstacleGroup();
protected ObstacleGroup m_dynamicObstacles = new ObstacleGroup();
protected RaycastGroup m_raycasts = new RaycastGroup();
protected ORCA m_orca;
protected Agent m_myFirstAgent;
void Awake(){
m_orca = new Nebukam.ORCA.ORCA();
m_orca.plane = AxisPair.XY;
m_orca.agents = m_agents;
m_orca.staticObstacles = m_staticObstacles;
m_orca.dynamicObstacles = m_dynamicObstacles;
m_orca.raycasts = m_raycasts;
m_myFirstAgent = m_agents.Add(new float3(0f,0f,0f)) as Agent;
m_myFirstAgent.radius = 1f;
m_myFirstAgent.prefVelocity = new float3(1f,1f,0f);
m_myFirstAgent.maxSpeed = 1f;
}
void Update(){
m_orca.Run(Time.deltaTime);
// Simulation result
float3 agentVelocity = m_myFirstAgent.velocity;
float3 agentPosition = m_myFirstAgent.position;
}
#region System.IDisposable
protected virtual void Dispose(bool disposing)
{
if (!disposing) { return; }
m_orca.DisposeAll();
m_agents.Clear(true);
m_staticObstacles.Clear(true);
m_dynamicObstacles.Clear(true);
m_raycasts.Clear(true);
}
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
#endregion
}
Unity might complain about some unmanaged resources not being disposed correctly -- this is because Dispose
is not called on the MonoBehaviour, effectively cleanup the simulation resources.
This package is part of N:Toolkit, a collection of packages for Unity3D || developed by Timothé Lapetite || Released under MIT