ADD: PickingSystem

This commit is contained in:
sebastian 2026-08-02 20:35:06 +02:00
parent 4ce6791343
commit 7d9b31fe12
9 changed files with 132 additions and 1 deletions

View File

@ -153,6 +153,9 @@ add_library(Engine STATIC
engine/src/game/GameMode.h engine/src/game/GameMode.h
engine/src/game/TurnBasedGameMode.cpp engine/src/game/TurnBasedGameMode.cpp
engine/src/game/TurnBasedGameMode.h engine/src/game/TurnBasedGameMode.h
engine/src/ecs/standardComponents/PickableComponent.h
engine/src/ecs/systems/PickingSystem.cpp
engine/src/ecs/systems/PickingSystem.h
) )
target_include_directories(Engine PUBLIC engine/src) target_include_directories(Engine PUBLIC engine/src)
target_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui spdlog::spdlog tinyobjloader stb_image) target_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui spdlog::spdlog tinyobjloader stb_image)

View File

@ -0,0 +1,13 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_PICKABLECOMPONENT_H
#define COLORRACE_PICKABLECOMPONENT_H
namespace engine {
struct PickableComponent {
float radius;
};
}
#endif //COLORRACE_PICKABLECOMPONENT_H

View File

@ -0,0 +1,27 @@
//
// Created by sebastian on 02.08.26.
//
#include "PickingSystem.h"
std::pair<glm::vec3, glm::vec3> engine::PickingSystem::screenToRay(const glm::vec2 &ndc, const Camera &camera) {
glm::mat4 invVP = glm::inverse(camera.getProjectionMatrix() * camera.getViewMatrix());
glm::vec4 nearPoint = invVP * glm::vec4(ndc.x, -ndc.y, -1.0f, 1.0f); // Y invertiert (Screen vs. NDC)
glm::vec4 farPoint = invVP * glm::vec4(ndc.x, -ndc.y, 1.0f, 1.0f);
nearPoint /= nearPoint.w;
farPoint /= farPoint.w;
auto origin = glm::vec3(nearPoint);
glm::vec3 dir = glm::normalize(glm::vec3(farPoint) - glm::vec3(nearPoint));
return {origin, dir};
}
bool engine::PickingSystem::raySphereIntersect(const glm::vec3 &origin, const glm::vec3 &dir, const glm::vec3 &center,
float radius, float &t) {
glm::vec3 oc = origin - center;
float b = glm::dot(oc, dir);
float c = glm::dot(oc, oc) - radius * radius;
float discriminant = b * b - c;
if (discriminant < 0.0f) return false;
t = -b - std::sqrt(discriminant);
return t >= 0.0f;
}

View File

@ -0,0 +1,54 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_PICKINGSYSTEM_H
#define COLORRACE_PICKINGSYSTEM_H
#include <optional>
#include "imgui.h"
#include "core/inputsOutputs/inputs/Mouse.h"
#include "core/inputsOutputs/inputs/MouseButton.h"
#include "ecs/EntityRegistry.h"
#include "ecs/standardComponents/PickableComponent.h"
#include "ecs/standardComponents/TransformComponent.h"
#include "renderer/entities/Camera.h"
namespace engine {
class Mouse;
class PickingSystem {
public:
explicit PickingSystem(Camera& camera) : m_camera(camera) {}
std::optional<Entity> update(const Mouse& mouse, EntityRegistry& registry) {
if (ImGui::GetIO().WantCaptureMouse) return std::nullopt;
if (!mouse.isClickEvent(MouseButton::LEFT)) return std::nullopt;
auto [origin, dir] = screenToRay(mouse.getXY(/*ndc=*/true), m_camera);
float closestT = std::numeric_limits<float>::max();
std::optional<Entity> hit;
for (const auto& [entity, pickable] : registry.getPool<PickableComponent>()) {
const auto transform = registry.getComponent<TransformComponent>(entity);
if (float t; raySphereIntersect(origin, dir, transform.m_position, pickable.radius, t) && t < closestT) {
closestT = t;
hit = entity;
}
}
return hit;
}
private:
Camera& m_camera;
static std::pair<glm::vec3, glm::vec3> screenToRay(const glm::vec2& screenPos, const Camera& camera);
static bool raySphereIntersect(const glm::vec3& origin, const glm::vec3& dir, const glm::vec3& center, float radius, float& t);
};
}
#endif //COLORRACE_PICKINGSYSTEM_H

View File

@ -34,6 +34,22 @@ void GameLayer::onUpdate(float deltaTime) {
Layer::onUpdate(deltaTime); Layer::onUpdate(deltaTime);
m_cameraController->update(getKeyboard(), *m_camera, deltaTime); m_cameraController->update(getKeyboard(), *m_camera, deltaTime);
for (const auto& event : m_gameMode->pollEvents()) {
std::visit([this]<typename T>(const T& e) {
if constexpr (std::is_same_v<T, ludo::PieceMovedEvent>) {
m_piecePresenter.onPieceMoved(e, m_gameMode->getState());
}
// DiceRolledEvent/TurnChangedEvent -> ImGui-State aktualisieren
}, event);
}
if (auto entity = m_pickingSystem.update(getMouse(), m_entityRegistry)) {
if (auto pieceIndex = m_piecePresenter.getPieceIndex(*entity)) {
m_gameMode->sendCommand(ludo::MovePieceCommand{m_localPlayer, *pieceIndex});
}
}
} }
void GameLayer::onRender() { void GameLayer::onRender() {

View File

@ -8,6 +8,7 @@
#include "core/inputsOutputs/controller/CameraController.h" #include "core/inputsOutputs/controller/CameraController.h"
#include "ecs/EntityRegistry.h" #include "ecs/EntityRegistry.h"
#include "ecs/systems/PickingSystem.h"
#include "layer/Layer.h" #include "layer/Layer.h"
#include "ludo/BoardLayout.h" #include "ludo/BoardLayout.h"
#include "ludo/PiecePresenter.h" #include "ludo/PiecePresenter.h"
@ -21,7 +22,8 @@ public:
GameLayer(std::shared_ptr<ludo::LudoGameMode> gameMode, engine::EntityRegistry& entityRegistry, std::shared_ptr<engine::Camera> cam, std::shared_ptr<engine::PointLight> light, GameLayer(std::shared_ptr<ludo::LudoGameMode> gameMode, engine::EntityRegistry& entityRegistry, std::shared_ptr<engine::Camera> cam, std::shared_ptr<engine::PointLight> light,
engine::Keyboard& keyboard, engine::Mouse& mouse, std::shared_ptr<engine::Model> boardModel, std::unordered_map<std::string, std::shared_ptr<engine::Model>> pieceModdels) engine::Keyboard& keyboard, engine::Mouse& mouse, std::shared_ptr<engine::Model> boardModel, std::unordered_map<std::string, std::shared_ptr<engine::Model>> pieceModdels)
: Layer(keyboard, mouse), m_gameMode(std::move(gameMode)), m_entityRegistry(entityRegistry), m_camera(std::move(cam)), m_renderer(std::make_unique<engine::MeshRenderer>()), : Layer(keyboard, mouse), m_gameMode(std::move(gameMode)), m_entityRegistry(entityRegistry), m_camera(std::move(cam)), m_renderer(std::make_unique<engine::MeshRenderer>()),
m_pointLight(std::move(light)), m_boardLayout(std::move(boardModel)), m_piecePresenter(m_entityRegistry, std::move(pieceModdels), m_boardLayout){} m_pointLight(std::move(light)), m_boardLayout(std::move(boardModel)), m_piecePresenter(m_entityRegistry, std::move(pieceModdels), m_boardLayout),
m_pickingSystem(*m_camera){}
void onUpdate(float deltaTime) override; void onUpdate(float deltaTime) override;
void onRender() override; void onRender() override;
@ -41,6 +43,7 @@ private:
ludo::PiecePresenter m_piecePresenter; ludo::PiecePresenter m_piecePresenter;
engine::PlayerID m_localPlayer = 0; engine::PlayerID m_localPlayer = 0;
std::shared_ptr<ludo::LudoGameMode> m_gameMode; std::shared_ptr<ludo::LudoGameMode> m_gameMode;
engine::PickingSystem m_pickingSystem;
}; };

View File

@ -16,6 +16,7 @@
namespace ludo { namespace ludo {
inline constexpr int kTrackLength = 40; inline constexpr int kTrackLength = 40;
inline constexpr int kHomeStretchLength = 4; inline constexpr int kHomeStretchLength = 4;
inline constexpr float kPieceRadius = 0.3f;
enum class PlayerColor : uint8_t { Red, Blue, Yellow, Green, Count}; enum class PlayerColor : uint8_t { Red, Blue, Yellow, Green, Count};
constexpr std::string_view toString(PlayerColor color) constexpr std::string_view toString(PlayerColor color)

View File

@ -5,6 +5,7 @@
#include "PiecePresenter.h" #include "PiecePresenter.h"
#include "ecs/standardComponents/MeshComponent.h" #include "ecs/standardComponents/MeshComponent.h"
#include "ecs/standardComponents/PickableComponent.h"
#include "ecs/standardComponents/TransformComponent.h" #include "ecs/standardComponents/TransformComponent.h"
void ludo::PiecePresenter::spawnPieces(const ludo::LudoGameState &state) { void ludo::PiecePresenter::spawnPieces(const ludo::LudoGameState &state) {
@ -16,6 +17,7 @@ void ludo::PiecePresenter::spawnPieces(const ludo::LudoGameState &state) {
{0.334296f, 0.334296f, 0.334296f}); {0.334296f, 0.334296f, 0.334296f});
m_registry.addComponent<engine::TransformComponent>(entity, transform); m_registry.addComponent<engine::TransformComponent>(entity, transform);
m_registry.addComponent<engine::MeshComponent>(entity, engine::MeshComponent(m_pieceModels[std::string(ludo::toString(piece.color))])); m_registry.addComponent<engine::MeshComponent>(entity, engine::MeshComponent(m_pieceModels[std::string(ludo::toString(piece.color))]));
m_registry.addComponent<engine::PickableComponent>(entity, engine::PickableComponent(kPieceRadius));
} }
} }
@ -26,3 +28,9 @@ void ludo::PiecePresenter::onPieceMoved(const ludo::PieceMovedEvent &event, cons
auto& transform = m_registry.getComponent<engine::TransformComponent>(entity); auto& transform = m_registry.getComponent<engine::TransformComponent>(entity);
transform.m_position = target; transform.m_position = target;
} }
std::optional<int> ludo::PiecePresenter::getPieceIndex(std::optional<engine::Entity>::value_type entity) {
auto it = std::ranges::find(m_pieceEntities, entity);
if (it == m_pieceEntities.end()) return std::nullopt;
return static_cast<int>(std::distance(m_pieceEntities.begin(), it));
}

View File

@ -4,6 +4,8 @@
#ifndef COLORRACE_PIECEPRESENTER_H #ifndef COLORRACE_PIECEPRESENTER_H
#define COLORRACE_PIECEPRESENTER_H #define COLORRACE_PIECEPRESENTER_H
#include <optional>
#include "BoardLayout.h" #include "BoardLayout.h"
#include "LudoGameState.h" #include "LudoGameState.h"
#include "ecs/EntityRegistry.h" #include "ecs/EntityRegistry.h"
@ -19,6 +21,10 @@ namespace ludo {
void spawnPieces(const ludo::LudoGameState& state); void spawnPieces(const ludo::LudoGameState& state);
void onPieceMoved(const ludo::PieceMovedEvent& event, const ludo::LudoGameState& state); void onPieceMoved(const ludo::PieceMovedEvent& event, const ludo::LudoGameState& state);
[[nodiscard]] const std::vector<engine::Entity>& getPieceEntities() const { return m_pieceEntities; }
[[nodiscard]] std::optional<int> getPieceIndex(std::optional<engine::Entity>::value_type entity);
private: private:
engine::EntityRegistry& m_registry; engine::EntityRegistry& m_registry;
std::unordered_map<std::string, std::shared_ptr<engine::Model>> m_pieceModels; std::unordered_map<std::string, std::shared_ptr<engine::Model>> m_pieceModels;