Difference between revisions of "W4 Collision System"

From Ciliz|W4
 
(19 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
== Scope ==
 
== 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 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:
 
The W4 Engine implements the following types of interactions with colliders:
* '''Intersect''' - intersection of two colliders.
+
* '''Intersect''' - an intersection of two colliders.
 
* '''Raycast''' - collider and ray intersection.
 
* '''Raycast''' - collider and ray intersection.
* '''Screencast''' - intersection of the collider and the ray emitted from the given screen coordinates.  
+
* '''Screencast''' - an 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:
+
Samples of the Collision system working code provided below are available in the W4 Engine:
 
* apps/internal/gist-colliding  
 
* apps/internal/gist-colliding  
 
* apps/internal/test-screencast-blocking
 
* apps/internal/test-screencast-blocking
 
* apps/samples/gist-raycast
 
* apps/samples/gist-raycast
  
Let's take a closer look at the Collision system.
+
Let's take a closer look at the Collision system and review its main elements:
Consider the main elements of the Collision system:
+
* '''BoundingVolume''' - an interface. This is a visitor whose descendants implement methods for calculating intersections. Also, '''BoundingVolume''' contains data about local transformation.
* '''BoundingVolume''' - interface. This is a visitor whose descendants implement methods for calculating intersections. Also BoundingVolume contains local Transform.
 
 
* '''CollisionInfo''' - structure containing an intersection data. The data depends on the type of interaction and the types of interacting volumes.
 
* '''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.
+
* '''Collider''' - a 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).
+
* '''ColliderEventDispatcher''' - utility static class containing arrays of colliders, distributed by type of interaction. This class also comprises general '''onUpdate''' event handling methods for '''Intersect''' and '''onTouch''' (Screencast) calculation.
 
* '''Node''' (see [[W4_Coordinate_system_and_Node_structure]]) contains the collider management interface.
 
* '''Node''' (see [[W4_Coordinate_system_and_Node_structure]]) contains the collider management interface.
  
 
== Collider management ==
 
== Collider management ==
Any node has access to the collider management interface, which allows it to add, remove, and find colliders by name.
+
Any node has access to the collider management interface, that enables to add, remove, and find colliders by name.
  
 
The number of colliders is not limited.
 
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.
+
Note. Create a unique name for every collider. When adding a collider with the name coinciding with the existing one, a warning is displayed in the console, and the existing collider is destroyed.
 
=== Adding a collider ===
 
=== Adding a collider ===
1) Adding a collider by passing arguments to the Collider class constructor:
+
1) Adding a collider by passing arguments to the '''Collider''' class constructor:
 
<syntaxhighlight lang="c++">
 
<syntaxhighlight lang="c++">
 
template<typename... Args>
 
template<typename... Args>
Line 37: Line 36:
 
Code example:
 
Code example:
 
<syntaxhighlight lang="c++">
 
<syntaxhighlight lang="c++">
// automatic generation of collider name
+
// automatic generation of the collider name
 
cube->addCollider(make::sptr<core::AABB>(1.f));
 
cube->addCollider(make::sptr<core::AABB>(1.f));
// collider name set manually
+
// manual setting of the collider name  
 
cube->addCollider("Intersect", make::sptr<core::AABB>(1.f));
 
cube->addCollider("Intersect", make::sptr<core::AABB>(1.f));
 
</syntaxhighlight>
 
</syntaxhighlight>
2) Adding a collider by specifying the BoundingVolume type and passing arguments to the constructor of the given type T:
+
2) Adding a collider by specifying the '''BoundingVolume''' type and passing arguments to the constructor of the given type '''T''':
 
<syntaxhighlight lang="c++">
 
<syntaxhighlight lang="c++">
 
template<typename T, typename... Args>
 
template<typename T, typename... Args>
Line 51: Line 50:
 
Code example:
 
Code example:
 
<syntaxhighlight lang="c++">
 
<syntaxhighlight lang="c++">
// automatic generation of collider name
+
// automatic generation of the collider name
 
cube->addCollider<core::AABB>(1.f);
 
cube->addCollider<core::AABB>(1.f);
// collider name set manually
+
// manual setting of the collider name
 
cube->addCollider<core::AABB>("Intersect", 1.f);
 
cube->addCollider<core::AABB>("Intersect", 1.f);
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 59: Line 58:
 
* collider is an Axis-aligned Bounding Box with a side size of 1;
 
* 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.
 
* collider with an automatically generated name added in the first case, or "Intersect" in the second.
 +
 
=== Getting a collider ===
 
=== Getting a collider ===
 
Signature:
 
Signature:
Line 65: Line 65:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The addCollider method returns a pointer to the created collider:
+
The '''addCollider''' method returns a pointer to the created collider:
 
<syntaxhighlight lang="c++">
 
<syntaxhighlight lang="c++">
 
auto collider = cube->addCollider<core::BoundingBox>("Intersect", 1.f);
 
auto collider = cube->addCollider<core::BoundingBox>("Intersect", 1.f);
Line 75: Line 75:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Note. If the collider's name is specified incorrectly, an error is displayed in the console, and the program terminates.
+
Note. In case the collider's name is specified incorrectly, an error is displayed on the console, and the program terminates.
  
 
=== Removing a collider ===
 
=== Removing a collider ===
 
+
Signature:
 +
<syntaxhighlight lang="c++">
 +
void removeCollider(cref<Collider>);
 +
void removeCollider(const std::string& name);
 +
</syntaxhighlight>
 +
Delete the collider by pointer:
 +
<syntaxhighlight lang="c++">
 +
cube->removeCollider(collider);
 +
</syntaxhighlight>
 +
Delete the collider by name:
 +
<syntaxhighlight lang="c++">
 +
cube->removeCollider("Intersect");
 +
</syntaxhighlight>
 
== The Collider class ==
 
== The Collider class ==
This class implements control of collider interactions
+
This class implements the control of collider interactions.
 
=== Bounding Volume ===
 
=== Bounding Volume ===
 +
The collider cannot be created without a given volume, but you can change the collider's volume using the method:
 +
<syntaxhighlight lang="c++">
 +
void setVolume(cref<BoundingVolume>);
 +
</syntaxhighlight>
 +
=== Local Transform ===
 +
Method signature:
 +
<syntaxhighlight lang="c++">
 +
void setLocalTransform(math::Transform::cref);
 +
math::Transform::cref  getLocalTransform() const;
  
=== Local Transform ===
+
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;
 +
</syntaxhighlight>
 +
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 via [https://en.wikipedia.org/wiki/Lazy_evaluation lazy evaluation] at the moment of interaction.
  
 
=== Intersect ===
 
=== Intersect ===
 +
Below is the part of the '''Collider''' class interface responsible for intersections:
 +
 +
<syntaxhighlight lang="c++">
 +
using IntersectCallback = std::function<void(const CollisionInfo&)>;
 +
 +
void setIntersecting(bool flag = true);
 +
bool isIntersecting() const;
 +
 +
void setIntersectCallback(const IntersectCallback&);
 +
void resetIntersectCallback();
 +
</syntaxhighlight>
 +
Note. By default, the collider does not take part in intersections.
 +
 +
How it works:
 +
* The intersection is calculated by every frame in the '''onUpdate()''' method (see [[IGame class]] description).
 +
* For all intersected 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:
 +
<syntaxhighlight lang="c++">
 +
collider->setIntersecting();
 +
collider->setIntersecting(true);
 +
</syntaxhighlight>
 +
Disable intersection participation:
 +
<syntaxhighlight lang="c++">
 +
collider->setIntersecting(false);
 +
</syntaxhighlight>
 +
Find out if the collider is involved in intersections:
 +
<syntaxhighlight lang="c++">
 +
auto isIntersecting = collider->isIntersecting();
 +
</syntaxhighlight>
 +
Set '''IntersectCallback''':
 +
<syntaxhighlight lang="c++">
 +
collider->setIntersectCallback([](const CollisionInfo& info)
 +
{
 +
  // ...
 +
});
 +
</syntaxhighlight>
 +
Reset (i.e. return to default disabled state) '''IntersectCallback''':
 +
<syntaxhighlight lang="c++">
 +
collider->resetIntersectCallback();
 +
</syntaxhighlight>
  
 
=== Screencast ===
 
=== Screencast ===
Reaction to TouchEvent
+
Below is the part of the Collider class interface that is responsible for Screencast:
 +
<syntaxhighlight lang="c++">
 +
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();
 +
</syntaxhighlight>
 +
 
 +
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 himself (for example, for the “virtual click”).
 +
* The collider can block or pass the Screencast through itself. By default, all colliders always block Screencast. If the collider blocks it, then callbacks for colliders farther in the direction of the ray will not be called.
 +
 
 +
Enable blocking:
 +
<syntaxhighlight lang="c++">
 +
collider->setBlockScreencasts();
 +
collider->setBlockScreencasts(true);
 +
</syntaxhighlight>
 +
Disable blocking:
 +
<syntaxhighlight lang="c++">
 +
collider->setBlockScreencasts(false);
 +
</syntaxhighlight>
 +
Find out if Screencast is being blocked by the collider:
 +
<syntaxhighlight lang="c++">
 +
auto isBlockScreencasts = collider->isBlockScreencasts();
 +
</syntaxhighlight>
 +
Set '''ScreencastCallback''':
 +
<syntaxhighlight lang="c++">
 +
// 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)
 +
{
 +
  // ...
 +
});
 +
</syntaxhighlight>
 +
Reset (i.e. return to default "blocking" state) '''ScreencastCallback''':
 +
<syntaxhighlight lang="c++">
 +
// ScreencastEvent::Down
 +
collider->resetScreencastCallback(ScreencastEvent::Down);
 +
// or
 +
collider->resetScreencastCallback();
 +
 
 +
// ScreencastEvent::Move
 +
collider->resetScreencastCallback(ScreencastEvent::Move);
 +
 
 +
// ScreencastEvent::Up
 +
collider->resetScreencastCallback(ScreencastEvent::Up);
 +
</syntaxhighlight>
 +
 
 
=== Raycast ===
 
=== Raycast ===
 +
Below is the part of the Collider class interface responsible for Raycast:
 +
<syntaxhighlight lang="c++">
 +
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();
 +
</syntaxhighlight>
 +
If '''RaycastCallback''' is not set for the collider or the '''isReceiveRaycasts''' flag is not enabled, the collider does not participate in the raycast calculation. By default, colliders do not block raycast.
 +
 +
How to use:
 +
* Raycast to nearest collider (the Render class interface):
 +
<syntaxhighlight lang="c++">
 +
static core::CollisionInfo Render::raycast(const math::Ray& ray);
 +
</syntaxhighlight>
 +
The above method returns the closest intersection with the ray. '''RaycastCallback''' is called for all colliders, up to the first blocking one. If there is no intersection, then '''CollisionInfo.target = nullptr'''.
 +
 +
* Raycast to nearest blocking collider (the Render class interface):
 +
<syntaxhighlight lang="c++">
 +
static std::vector<core::CollisionInfo> Render::raycastAll(const math::Ray& ray);
 +
</syntaxhighlight>
 +
The above method returns all intersections with the given ray, up to the first blocking collider. '''RaycastCallback''' is called for all colliders. The returned array is sorted by distance.
 +
 +
* Set '''RaycastCallback''':
 +
<syntaxhighlight lang="c++">
 +
collider->setRaycastCallback([](const CollisionInfo& info)
 +
{
 +
  // ...
 +
});
 +
</syntaxhighlight>
 +
 +
* Reset (i.e. to "do not block raycast" state) '''RaycastCallback''':
 +
 +
<syntaxhighlight lang="c++">
 +
collider->resetRaycastCallback();
 +
</syntaxhighlight>

Latest revision as of 08:16, 3 August 2020

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 - an intersection of two colliders.
  • Raycast - collider and ray intersection.
  • Screencast - an intersection of the collider and the ray emitted from the given screen coordinates.

Samples of the Collision system working code provided below are available 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 and review its main elements:

  • BoundingVolume - an interface. This is a visitor whose descendants implement methods for calculating intersections. Also, BoundingVolume contains 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 - a class containing BoundingVolume and callback management mechanisms.
  • ColliderEventDispatcher - utility static class containing arrays of colliders, distributed by type of interaction. This class also comprises general onUpdate event handling methods for 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, that enables to add, remove, and find colliders by name.

The number of colliders is not limited.

Note. Create a unique name for every collider. When adding a collider with the name coinciding with the existing one, a warning is displayed in the console, and the existing collider is 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 the collider name
cube->addCollider(make::sptr<core::AABB>(1.f));
// manual setting of the collider name 
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 the collider name
cube->addCollider<core::AABB>(1.f);
// manual setting of the collider name  
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. In case the collider's name is specified incorrectly, an error is displayed on 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 via lazy evaluation at the moment of interaction.

Intersect

Below is the part of the Collider class interface 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 by every frame in the onUpdate() method (see IGame class description).
  • For all intersected 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 himself (for example, for the “virtual click”).
  • The collider can block or pass the Screencast through itself. By default, all colliders always block Screencast. If the collider blocks it, 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 the 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 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, the collider does not participate in the raycast calculation. By default, colliders do not block raycast.

How to use:

  • Raycast to nearest collider (the Render class interface):
static core::CollisionInfo Render::raycast(const math::Ray& ray);

The above method returns the closest intersection with the ray. RaycastCallback is called for all colliders, up to the first blocking one. If there is no intersection, then CollisionInfo.target = nullptr.

  • Raycast to nearest blocking collider (the Render class interface):
static std::vector<core::CollisionInfo> Render::raycastAll(const math::Ray& ray);

The above method returns all intersections with the given ray, up to the first blocking collider. RaycastCallback is called for all colliders. The returned array is sorted by distance.

  • Set RaycastCallback:
collider->setRaycastCallback([](const CollisionInfo& info)
{
  // ...
});
  • Reset (i.e. to "do not block raycast" state) RaycastCallback:
collider->resetRaycastCallback();