W4 Collision System

From Ciliz|W4

Scope

The Collision system provides the handling of intersections between objects. For one object, you can set several different volumes (bounding boxes, spheres) at once and subscribe to intersections.

The W4 Engine implements the following types of interactions with colliders:

  • Intersect - intersection of two colliders.
  • Raycast - collider and ray intersection.
  • Screencast - intersection of the collider and the ray emitted from the given screen coordinates.

The following examples of the working code of the collision system can be found in the W4 Engine:

  • apps/internal/gist-colliding
  • apps/internal/test-screencast-blocking
  • apps/samples/gist-raycast

Let's take a closer look at the Collision system. Consider the main elements of the Collision system:

  • BoundingVolume - interface. This is a visitor whose descendants implement methods for calculating intersections. Also BoundingVolume contains a data about local transformation.
  • CollisionInfo - structure containing an intersection data. The data depends on the type of interaction and the types of interacting volumes.
  • Collider - class containing BoundingVolume and callback management mechanisms.
  • ColliderEventDispatcher - utility static class containing arrays of colliders, distributed by type of interaction. This class also contains general onUpdate event handling methods for calculating Intersect and onTouch (Screencast calculation).
  • Node (see W4_Coordinate_system_and_Node_structure) contains the collider management interface.

Collider management

Any node has access to the collider management interface, which allows it to add, remove, and find colliders by name.

The number of colliders is not limited.

Note. Colliders must have unique names. When adding a collider with the same name as an existing one, a warning will be displayed in the console, and the existing collider will be destroyed.

Adding a collider

1) Adding a collider by passing arguments to the Collider class constructor:

template<typename... Args>
cref<Collider> addCollider(Args&&... args);
template<typename String, typename... Args>
cref<Collider> addCollider(const String& name, Args&&... args);

Code example:

// automatic generation of collider name
cube->addCollider(make::sptr<core::AABB>(1.f));
// collider name set manually 
cube->addCollider("Intersect", make::sptr<core::AABB>(1.f));

2) Adding a collider by specifying the BoundingVolume type and passing arguments to the constructor of the given type T:

template<typename T, typename... Args>
cref<Collider> addCollider(Args&&... args);
template<typename T, typename String, typename... Args>
cref<Collider> addCollider(const String& name, Args&&... args);

Code example:

// automatic generation of collider name
cube->addCollider<core::AABB>(1.f);
// collider name set manually 
cube->addCollider<core::AABB>("Intersect", 1.f);

All options give identical results:

  • collider is an Axis-aligned Bounding Box with a side size of 1;
  • collider with an automatically generated name added in the first case, or "Intersect" in the second.

Getting a collider

Signature:

cref<Collider> getCollider(const std::string& name);

The addCollider method returns a pointer to the created collider:

auto collider = cube->addCollider<core::BoundingBox>("Intersect", 1.f);

If you know the name of the collider, you can get a pointer to it:

auto collider = cube->getCollider("Intersect");

Note. If the collider's name is specified incorrectly, an error is displayed in the console, and the program terminates.

Removing a collider

Signature:

void removeCollider(cref<Collider>);
void removeCollider(const std::string& name);

Delete the collider by pointer:

cube->removeCollider(collider);

Delete the collider by name:

cube->removeCollider("Intersect");

The Collider class

This class implements the control of collider interactions.

Bounding Volume

The collider cannot be created without a given volume, but you can change the collider's volume using the method:

void setVolume(cref<BoundingVolume>);

Local Transform

Method signature:

void setLocalTransform(math::Transform::cref);
math::Transform::cref   getLocalTransform() const;

void setLocalTranslation(math::vec3::cref);
math::vec3::cref getLocalTranslation() const;

void setLocalRotation(math::Rotator::cref);
math::Rotator::cref getLocalRotation() const;

void setLocalScale(math::vec3::cref);
math::vec3::cref getLocalScale() const;

Features:

  • BoundingVolume has its own local transform relative to the parent node (its interface is identical to the Node transform management interface).
  • The Collider class proxies the interface to the BoundingVolume.
  • Changes to the node and/or collider transform are calculated at the moment of interaction.

Intersect

Below is the part of the Collider class interface that is responsible for intersections:

using IntersectCallback = std::function<void(const CollisionInfo&)>;

void setIntersecting(bool flag = true);
bool isIntersecting() const;

void setIntersectCallback(const IntersectCallback&);
void resetIntersectCallback();

Note. By default, the collider does not take part in intersections.

How it works:

  • The intersection is calculated in onUpdate() (see IGame class description) every frame.
  • For all crossed colliders, the corresponding IntersectCallback will be called (if set).
  • There can be only one IntersectCallback per collider.
  • When setting IntersectCallback, setIntersecting() is automatically called on the collider.

Enable intersection participation:

collider->setIntersecting();
collider->setIntersecting(true);

Disable intersection participation:

collider->setIntersecting(false);

Find out if the collider is involved in intersections:

auto isIntersecting = collider->isIntersecting();

Set IntersectCallback:

collider->setIntersectCallback([](const CollisionInfo& info)
{
  // ...
});

Reset (i.e. return to default disabled state) IntersectCallback:

collider->resetIntersectCallback();

Screencast

Below is the part of the Collider class interface that is responsible for Screencast:

enum class ScreencastEvent
{
    Down,
    Move,
    Up
};

using ScreencastCallback = std::function<void(const CollisionInfo&)>;

void setBlockScreencasts(bool flag = true);
bool isBlockScreencasts() const;

void setScreencastCallback(ScreencastEvent, const ScreencastCallback&);
void resetScreencastCallback(ScreencastEvent);

void setScreencastCallback(const ScreencastCallback&);
void resetScreencastCallback();

Features:

  • Input events Touch::Begin, Touch::Move and Touch::End (see W4 HAL and W4 Event System description) are equivalent to ScreencastEvent::Down, ScreencastEvent::Move and ScreencastEvent::Up. A callback can be set for any of these events.
  • The user can generate any of the above Touch-events by himself (for example, for the so-called “virtual click”).
  • The collider can block or pass the Screencast through itself. By default, all colliders block Screencast. If the collider blocks the Screencast, then callbacks for colliders farther in the direction of the ray will not be called.

Enable blocking:

collider->setBlockScreencasts();
collider->setBlockScreencasts(true);

Disable blocking:

collider->setBlockScreencasts(false);

Find out if Screencast is being blocked by this collider:

auto isBlockScreencasts = collider->isBlockScreencasts();

Set ScreencastCallback:

// ScreencastEvent::Down
collider->setScreencastCallback(ScreencastEvent::Down, [](const CollisionInfo& info)
{
  // ...
});
// or 
collider->setScreencastCallback([](const CollisionInfo& info)
{
  // ...
});

// ScreencastEvent::Move
collider->setScreencastCallback(ScreencastEvent::Move, [](const CollisionInfo& info)
{
  // ...
});

// ScreencastEvent::Up
collider->setScreencastCallback(ScreencastEvent::Up, [](const CollisionInfo& info)
{
  // ...
});

Reset (i.e. return to default "blocking" state) ScreencastCallback:

// ScreencastEvent::Down
collider->resetScreencastCallback(ScreencastEvent::Down);
// or 
collider->resetScreencastCallback();

// ScreencastEvent::Move
collider->resetScreencastCallback(ScreencastEvent::Move);

// ScreencastEvent::Up
collider->resetScreencastCallback(ScreencastEvent::Up);

Raycast

Below is the part of the Collider class interface that is responsible for Raycast:

using RaycastCallback = std::function<void(const CollisionInfo&)>;

void setReceiveRaycasts(bool flag = true);
bool isReceiveRaycasts() const;

void setBlockRaycasts(bool flag = true);
bool isBlockRaycasts() const;

void setRaycastCallback(const RaycastCallback&);
void resetRaycastCallback();
  • If RaycastCallback is not set for the collider or the isReceiveRaycasts flag is not enabled, then the collider does not participate in the raycast calculation.
  • By default, colliders do not block raycast.