Spatial queries

In addition to collision callbacks w/ triggers, Obi supports several kinds of spatial queries. These are useful to know which simplices are nearby a given position, which simplices are inside a given shape, or if a ray hits any simplex.

Query methods

The ObiSolver class exposes several spatial query methods. All of them are synchronous: they will be executed as soon as you call them and return results immediately.

Most query methods take one or more QueryShape structs as input, and one or more AffineTransform structs (there must be exactly one AffineTransform per QueryShape). All query methods output one or more QueryResult structs.

Here's a list of query methods available:

// Multiple general queries: pass a list of heterogeneous query shapes, return list of results.
// All queries are performed in parallel.
// All other query methods are built on top of this one.
void SpatialQuery(ObiNativeQueryShapeList shapes, ObiNativeAffineTransformList transforms, ObiNativeQueryResultList results);
// Single general query: pass a single query shape, returns the result.
QueryResult[] SpatialQuery(QueryShape shape, AffineTransform transform);
// Multiple raycasts: pass a list of rays, returns list of hits. All raycasts are performed in parallel.
QueryResult[] Raycast(List rays, int filter, float maxDistance, float rayThickness);
// Single raycast: pass a single ray, returns whether it hit, and the hit result.
bool Raycast(Ray ray, out QueryResult hitInfo, int filter, float maxDistance, float rayThickness);

QueryShape

Queries can take one or more QueryShape structs as input, that describe the query being performed. These are the contents of a QueryShape:

type (QueryType)
Type of query being performed: Box, Sphere, or Ray.
center (Vector4)
Center of the query shape, in the local space defined by the corresponding AffineTransform.
size (Vector4)
Size of the query shape, in the local space defined by the corresponding AffineTransform.
contactOffset (float)
Additional thickness added to this query's shape.
maxDistance (float)
Maximum allowable distance between a simplex and this query shape
filter ()int
32 bit integer describing the collision filter used for this query. Most significant 16 bits encode a collision mask, less significant 16 bits encode a collision category. See collisions.

QueryResult

All queries will return one or more QueryResult structs, containing information about simplices that are close to a queried shape, or hit by a ray. These are the contents of a QueryResult:

simplexIndex (int)
index of the simplex.
queryIndex (int)
index of the query in the input ObiNativeQueryShapeList. Use this to correlate each QueryResult to the query that spawned it.
simplexBary (Vector4)
Barycentric coords of nearest point/ray hit in simplex.
queryPoint (Vector4)
Nearest point in query shape, expressed in solver space.
normal (Vector4)
Shortest direction between simplex and query shape, expressed in solver space.
distance (float)
Distance between simplex and query shape.

Examples

Determining distance from a point for all simplices within a radius of it

The trick here is to use a sphere of radius 0 to represent the point. Then use maxDistance to indicate we are interested in all simplices within a given radius of the point. Note that this sample assumes there are only 0-simplices (particles) in the solver.

using UnityEngine;
using Obi;

public class DistanceToPoint : MonoBehaviour
{
    public ObiSolver solver;
    public float radius = 1;

    void Update()
    {
        int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);

        var query = new QueryShape(QueryShape.QueryType.Sphere, Vector3.zero, Vector3.zero, 0, radius, filter);
        var xform = new AffineTransform(transform.position, transform.rotation, transform.localScale);

        QueryResult[] results = solver.SpatialQuery(query, xform);

        // Iterate over results and draw their distance to the point.
        // We're assuming the solver only contains 0-simplices (particles).
        for (int i = 0; i < results.Length; ++i)
        {
            if (results[i].distance < radius)
            {
                Vector3 pos = solver.transform.TransformPoint(solver.positions[results[i].simplexIndex]);
                Debug.DrawLine(transform.position, pos, Color.yellow);
            }
        }
    }
}

Determining all simplices inside some boxes

We'll use the generic SpatialQuery method to query multiple boxes at once. Then, we will look for QueryResults with a negative distance, as these indicate the simplex is inside one of the boxes. Note that this sample assumes there are only 0-simplices (particles) in the solver.

using UnityEngine;
using Obi;

public class OverlapTest : MonoBehaviour
{
    public ObiSolver solver;
    public Transform[] cubes;

    ObiNativeQueryShapeList queries;
    ObiNativeAffineTransformList transforms;
    ObiNativeQueryResultList results;

    private void Start()
    {
        queries = new ObiNativeQueryShapeList();
        transforms = new ObiNativeAffineTransformList();
        results = new ObiNativeQueryResultList();
    }

    private void OnDestroy()
    {
        queries.Dispose();
        transforms.Dispose();
        results.Dispose();
    }

    void Update()
    {
        int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);

        queries.Clear();
        transforms.Clear();

        for (int i = 0; i < cubes.Length; ++i)
        {
            queries.Add(new QueryShape()
            {
                type = QueryShape.QueryType.Box,
                center = Vector3.zero,
                size = new Vector3(1,1,1),
                contactOffset = 0,
                maxDistance = 0,
                filter = filter
            });

            transforms.Add(new AffineTransform(cubes[i].position,cubes[i].rotation,cubes[i].localScale));
        }

        solver.SpatialQuery(queries,transforms,results);

        // Iterate over results and draw their distance to the center of the cube.
        // We're assuming the solver only contains 0-simplices (particles).
        for (int i = 0; i < results.count; ++i)
        {
            if (results[i].distance < 0)
            {
                int particleIndex = solver.simplices[results[i].simplexIndex];
                Vector3 pos = solver.transform.TransformPoint(solver.positions[particleIndex]);
                Debug.DrawLine(transforms[results[i].queryIndex].translation, pos, Color.yellow);
            }
        }
    }
}

Single, thick raycast

Use the single Raycast method, we'll pass a non-zero thickness value to it. When the ray hits a simplex, the QueryResult will contain a point on the surface of the simplex. When the ray passes merely passes within thickness distance of a simplex (without hitting it) the QueryResult will contain the closest point between the ray and the simplex.

using UnityEngine;
using Obi;

public class SingleRaycast : MonoBehaviour
{
    public ObiSolver solver;

    void Update()
    {
        int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);

        // perform a raycast, check if it hit anything:
        if (solver.Raycast(new Ray(Vector3.zero, Vector3.up), out QueryResult result, filter, 50, 0.1f))
        {
            // get the start and size of the simplex that was hit:
            int simplexStart = solver.simplexCounts.GetSimplexStartAndSize(result.simplexIndex, out int simplexSize);

            // Debug draw the simplex:
            for (int i = 0; i < simplexSize; ++i)
            {
                int particleIndex = solver.simplices[simplexStart + i];
                Vector3 pos = solver.transform.TransformPoint(solver.positions[particleIndex]);
                Debug.DrawRay(pos, Vector3.up, Color.yellow);
            }
        }
    }
}

Multi raycast

We'll perform multiple raycasts at once. This is more efficient than performing many individual raycasts.

using System.Collections.Generic;
using UnityEngine;
using Obi;

public class MultiRaycast : MonoBehaviour
{
    public ObiSolver solver;
    public Transform[] rayTransforms;
    List<Ray> rays = new List<Ray>();

    void Update()
    {
        // build a list of rays from the transform array:
        rays.Clear();
        for (int i = 0; i < rayTransforms.Length; ++i)
            rays.Add(new Ray(rayTransforms[i].position, rayTransforms[i].up));

        int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);

        // perform multiple raycasts in parallel
        var results = solver.Raycast(rays, filter);

        if (results != null)
        for (int i = 0; i < results.Length; ++i)
        {
            // draw the normal at the hit position
            if (results[i].simplexIndex >= 0)
            {
               var normal = solver.transform.TransformPoint(results[i].normal);
               Debug.DrawRay(rays[i].GetPoint(results[i].distance), normal, Color.yellow);
            }
        }
    }
}