-
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Utils/TriggerVolume] Added "trigger" components & system
- The TriggerVolume & Triggerer components can be assigned to entities to make them spatially interact with each other - The TriggerSystem checks for volumes that are triggered and executes their enter, stay or leave actions accordingly
- Loading branch information
Showing
10 changed files
with
303 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#pragma once | ||
|
||
#ifndef RAZ_TRIGGERSYSTEM_HPP | ||
#define RAZ_TRIGGERSYSTEM_HPP | ||
|
||
#include "RaZ/System.hpp" | ||
|
||
namespace Raz { | ||
|
||
class Transform; | ||
class TriggerVolume; | ||
|
||
class TriggerSystem final : public System { | ||
public: | ||
TriggerSystem(); | ||
|
||
bool update(const FrameTimeInfo& timeInfo) override; | ||
|
||
private: | ||
static void processTrigger(TriggerVolume& triggerVolume, const Transform& triggererTransform); | ||
}; | ||
|
||
} // namespace Raz | ||
|
||
#endif // RAZ_TRIGGERSYSTEM_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
#pragma once | ||
|
||
#ifndef RAZ_TRIGGERVOLUME_HPP | ||
#define RAZ_TRIGGERVOLUME_HPP | ||
|
||
#include "RaZ/Component.hpp" | ||
#include "RaZ/Utils/Shape.hpp" | ||
|
||
#include <functional> | ||
#include <variant> | ||
|
||
namespace Raz { | ||
|
||
/// Triggerer component, representing an entity that can interact with triggerable entities. | ||
/// \see TriggerVolume | ||
class Triggerer final : public Component {}; | ||
|
||
/// TriggerVolume component, holding a volume that can be triggered and actions that can be executed accordingly. | ||
/// \see Triggerer, TriggerSystem | ||
class TriggerVolume final : public Component { | ||
friend class TriggerSystem; | ||
|
||
public: | ||
template <typename VolumeT> | ||
explicit TriggerVolume(VolumeT&& volume) : m_volume{ std::forward<VolumeT>(volume) } { | ||
// TODO: the OBB's point containment check isn't implemented yet | ||
static_assert(std::is_same_v<std::decay_t<VolumeT>, AABB> || std::is_same_v<std::decay_t<VolumeT>, Sphere>); | ||
} | ||
|
||
void setEnterAction(std::function<void()> enterAction) { m_enterAction = std::move(enterAction); } | ||
void setStayAction(std::function<void()> stayAction) { m_stayAction = std::move(stayAction); } | ||
void setLeaveAction(std::function<void()> leaveAction) { m_leaveAction = std::move(leaveAction); } | ||
|
||
/// Changes the trigger volume's state. | ||
/// \param enabled True if the trigger volume should be enabled (triggerable), false otherwise. | ||
void enable(bool enabled = true) noexcept { m_enabled = enabled; } | ||
/// Disables the trigger volume, making it non-triggerable. | ||
void disable() noexcept { enable(false); } | ||
void resetEnterAction() { setEnterAction(nullptr); } | ||
void resetStayAction() { setStayAction(nullptr); } | ||
void resetLeaveAction() { setLeaveAction(nullptr); } | ||
|
||
private: | ||
bool m_enabled = true; | ||
|
||
std::variant<AABB, Sphere> m_volume; | ||
std::function<void()> m_enterAction; | ||
std::function<void()> m_stayAction; | ||
std::function<void()> m_leaveAction; | ||
|
||
bool m_isCurrentlyTriggered = false; | ||
}; | ||
|
||
} // namespace Raz | ||
|
||
#endif // RAZ_TRIGGERVOLUME_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
#include "RaZ/Entity.hpp" | ||
#include "RaZ/Math/Transform.hpp" | ||
#include "RaZ/Utils/TriggerSystem.hpp" | ||
#include "RaZ/Utils/TriggerVolume.hpp" | ||
|
||
namespace Raz { | ||
|
||
TriggerSystem::TriggerSystem() { | ||
registerComponents<Triggerer, TriggerVolume>(); | ||
} | ||
|
||
bool TriggerSystem::update(const FrameTimeInfo&) { | ||
for (const Entity* triggererEntity : m_entities) { | ||
if (!triggererEntity->hasComponent<Triggerer>() || !triggererEntity->hasComponent<Transform>()) | ||
continue; | ||
|
||
const auto& triggererTransform = triggererEntity->getComponent<Transform>(); | ||
|
||
for (Entity* triggerVolumeEntity : m_entities) { | ||
if (!triggerVolumeEntity->hasComponent<TriggerVolume>()) | ||
continue; | ||
|
||
auto& triggerVolume = triggerVolumeEntity->getComponent<TriggerVolume>(); | ||
|
||
if (!triggerVolume.m_enabled) | ||
continue; | ||
|
||
processTrigger(triggerVolume, triggererTransform); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
void TriggerSystem::processTrigger(TriggerVolume& triggerVolume, const Transform& triggererTransform) { | ||
const bool wasBeingTriggered = triggerVolume.m_isCurrentlyTriggered; | ||
|
||
triggerVolume.m_isCurrentlyTriggered = std::visit([&triggererTransform] (const auto& volume) { | ||
// TODO: handle all transform info for both the triggerer & the volume | ||
return volume.contains(triggererTransform.getPosition()); | ||
}, triggerVolume.m_volume); | ||
|
||
if (!wasBeingTriggered && !triggerVolume.m_isCurrentlyTriggered) | ||
return; | ||
|
||
const std::function<void()>& action = (!wasBeingTriggered && triggerVolume.m_isCurrentlyTriggered ? triggerVolume.m_enterAction | ||
: (wasBeingTriggered && triggerVolume.m_isCurrentlyTriggered ? triggerVolume.m_stayAction | ||
: triggerVolume.m_leaveAction)); | ||
|
||
if (action) | ||
action(); | ||
} | ||
|
||
} // namespace Raz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
#include "RaZ/Application.hpp" | ||
#include "RaZ/World.hpp" | ||
#include "RaZ/Math/Transform.hpp" | ||
#include "RaZ/Utils/TriggerSystem.hpp" | ||
#include "RaZ/Utils/TriggerVolume.hpp" | ||
|
||
#include <catch2/catch_test_macros.hpp> | ||
|
||
TEST_CASE("TriggerSystem accepted components", "[utils]") { | ||
Raz::World world; | ||
|
||
const auto& triggerSystem = world.addSystem<Raz::TriggerSystem>(); | ||
|
||
const Raz::Entity& triggerer = world.addEntityWithComponent<Raz::Triggerer>(); | ||
const Raz::Entity& triggerVolume = world.addEntityWithComponent<Raz::TriggerVolume>(Raz::Sphere(Raz::Vec3f(0.f), 1.f)); | ||
|
||
world.update({}); | ||
|
||
CHECK(triggerSystem.containsEntity(triggerer)); | ||
CHECK(triggerSystem.containsEntity(triggerVolume)); | ||
} | ||
|
||
TEST_CASE("TriggerSystem trigger actions", "[utils]") { | ||
Raz::World world; | ||
|
||
world.addSystem<Raz::TriggerSystem>(); | ||
|
||
Raz::Entity& triggererEntity = world.addEntityWithComponent<Raz::Transform>(); | ||
triggererEntity.addComponent<Raz::Triggerer>(); | ||
|
||
auto& triggerBox = world.addEntityWithComponent<Raz::Transform>().addComponent<Raz::TriggerVolume>(Raz::AABB(Raz::Vec3f(-1.f), Raz::Vec3f(1.f))); | ||
auto& triggerSphere = world.addEntityWithComponent<Raz::Transform>().addComponent<Raz::TriggerVolume>(Raz::Sphere(Raz::Vec3f(0.f), 1.f)); | ||
|
||
int boxEnterCount = 0; | ||
int boxStayCount = 0; | ||
int boxLeaveCount = 0; | ||
|
||
triggerBox.setEnterAction([&boxEnterCount] () { ++boxEnterCount; }); | ||
triggerBox.setStayAction([&boxStayCount] () { ++boxStayCount; }); | ||
triggerBox.setLeaveAction([&boxLeaveCount] () { ++boxLeaveCount; }); | ||
|
||
int sphereEnterCount = 0; | ||
int sphereStayCount = 0; | ||
int sphereLeaveCount = 0; | ||
|
||
triggerSphere.setEnterAction([&sphereEnterCount] () { ++sphereEnterCount; }); | ||
triggerSphere.setStayAction([&sphereStayCount] () { ++sphereStayCount; }); | ||
triggerSphere.setLeaveAction([&sphereLeaveCount] () { ++sphereLeaveCount; }); | ||
|
||
world.update({}); | ||
|
||
CHECK(boxEnterCount == 1); | ||
CHECK(boxStayCount == 0); | ||
CHECK(boxLeaveCount == 0); | ||
|
||
CHECK(sphereEnterCount == 1); | ||
CHECK(sphereStayCount == 0); | ||
CHECK(sphereLeaveCount == 0); | ||
|
||
world.update({}); | ||
world.update({}); | ||
|
||
CHECK(boxEnterCount == 1); | ||
CHECK(boxStayCount == 2); | ||
CHECK(boxLeaveCount == 0); | ||
|
||
CHECK(sphereEnterCount == 1); | ||
CHECK(sphereStayCount == 2); | ||
CHECK(sphereLeaveCount == 0); | ||
|
||
// Moving the triggerer out of the sphere, but still inside the box | ||
triggererEntity.getComponent<Raz::Transform>().setPosition(Raz::Vec3f(0.75f)); | ||
|
||
world.update({}); | ||
|
||
CHECK(boxEnterCount == 1); | ||
CHECK(boxStayCount == 3); | ||
CHECK(boxLeaveCount == 0); | ||
|
||
CHECK(sphereEnterCount == 1); | ||
CHECK(sphereStayCount == 2); | ||
CHECK(sphereLeaveCount == 1); | ||
|
||
// Moving the triggerer out of both volumes | ||
triggererEntity.getComponent<Raz::Transform>().setPosition(Raz::Vec3f(1.5f)); | ||
|
||
world.update({}); | ||
|
||
CHECK(boxEnterCount == 1); | ||
CHECK(boxStayCount == 3); | ||
CHECK(boxLeaveCount == 1); | ||
|
||
CHECK(sphereEnterCount == 1); | ||
CHECK(sphereStayCount == 2); | ||
CHECK(sphereLeaveCount == 1); | ||
|
||
// Moving the triggerer inside both volumes and resetting all actions | ||
triggererEntity.getComponent<Raz::Transform>().setPosition(Raz::Vec3f(0.f)); | ||
triggerBox.resetEnterAction(); | ||
triggerBox.resetStayAction(); | ||
triggerBox.resetLeaveAction(); | ||
triggerSphere.resetEnterAction(); | ||
triggerSphere.resetStayAction(); | ||
triggerSphere.resetLeaveAction(); | ||
|
||
// Even though both volumes are triggered, nothing is done as no action is set | ||
world.update({}); | ||
|
||
CHECK(boxEnterCount == 1); | ||
CHECK(boxStayCount == 3); | ||
CHECK(boxLeaveCount == 1); | ||
|
||
CHECK(sphereEnterCount == 1); | ||
CHECK(sphereStayCount == 2); | ||
CHECK(sphereLeaveCount == 1); | ||
} |