diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d077cb..374fec9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,9 @@ add_library(Engine STATIC engine/src/game/GameMode.h engine/src/game/TurnBasedGameMode.cpp 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_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui spdlog::spdlog tinyobjloader stb_image) diff --git a/engine/src/ecs/standardComponents/PickableComponent.h b/engine/src/ecs/standardComponents/PickableComponent.h new file mode 100644 index 0000000..0ac4eff --- /dev/null +++ b/engine/src/ecs/standardComponents/PickableComponent.h @@ -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 diff --git a/engine/src/ecs/systems/PickingSystem.cpp b/engine/src/ecs/systems/PickingSystem.cpp new file mode 100644 index 0000000..50e166d --- /dev/null +++ b/engine/src/ecs/systems/PickingSystem.cpp @@ -0,0 +1,27 @@ +// +// Created by sebastian on 02.08.26. +// + +#include "PickingSystem.h" + +std::pair 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 ¢er, + 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; +} diff --git a/engine/src/ecs/systems/PickingSystem.h b/engine/src/ecs/systems/PickingSystem.h new file mode 100644 index 0000000..2976ef7 --- /dev/null +++ b/engine/src/ecs/systems/PickingSystem.h @@ -0,0 +1,54 @@ +// +// Created by sebastian on 02.08.26. +// + +#ifndef COLORRACE_PICKINGSYSTEM_H +#define COLORRACE_PICKINGSYSTEM_H +#include + +#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 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::max(); + std::optional hit; + + for (const auto& [entity, pickable] : registry.getPool()) { + const auto transform = registry.getComponent(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 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 diff --git a/game/src/GameLayer.cpp b/game/src/GameLayer.cpp index d10b7ee..c5e0b5d 100644 --- a/game/src/GameLayer.cpp +++ b/game/src/GameLayer.cpp @@ -34,6 +34,22 @@ void GameLayer::onUpdate(float deltaTime) { Layer::onUpdate(deltaTime); m_cameraController->update(getKeyboard(), *m_camera, deltaTime); + for (const auto& event : m_gameMode->pollEvents()) { + std::visit([this](const T& e) { + if constexpr (std::is_same_v) { + 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() { diff --git a/game/src/GameLayer.h b/game/src/GameLayer.h index 9c1360a..90b4aef 100644 --- a/game/src/GameLayer.h +++ b/game/src/GameLayer.h @@ -8,6 +8,7 @@ #include "core/inputsOutputs/controller/CameraController.h" #include "ecs/EntityRegistry.h" +#include "ecs/systems/PickingSystem.h" #include "layer/Layer.h" #include "ludo/BoardLayout.h" #include "ludo/PiecePresenter.h" @@ -21,7 +22,8 @@ public: GameLayer(std::shared_ptr gameMode, engine::EntityRegistry& entityRegistry, std::shared_ptr cam, std::shared_ptr light, engine::Keyboard& keyboard, engine::Mouse& mouse, std::shared_ptr boardModel, std::unordered_map> pieceModdels) : Layer(keyboard, mouse), m_gameMode(std::move(gameMode)), m_entityRegistry(entityRegistry), m_camera(std::move(cam)), m_renderer(std::make_unique()), - 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 onRender() override; @@ -41,6 +43,7 @@ private: ludo::PiecePresenter m_piecePresenter; engine::PlayerID m_localPlayer = 0; std::shared_ptr m_gameMode; + engine::PickingSystem m_pickingSystem; }; diff --git a/game/src/ludo/LudoTypes.h b/game/src/ludo/LudoTypes.h index 3a44cff..c85a521 100644 --- a/game/src/ludo/LudoTypes.h +++ b/game/src/ludo/LudoTypes.h @@ -16,6 +16,7 @@ namespace ludo { inline constexpr int kTrackLength = 40; inline constexpr int kHomeStretchLength = 4; + inline constexpr float kPieceRadius = 0.3f; enum class PlayerColor : uint8_t { Red, Blue, Yellow, Green, Count}; constexpr std::string_view toString(PlayerColor color) diff --git a/game/src/ludo/PiecePresenter.cpp b/game/src/ludo/PiecePresenter.cpp index c1936b1..0d6e6b4 100644 --- a/game/src/ludo/PiecePresenter.cpp +++ b/game/src/ludo/PiecePresenter.cpp @@ -5,6 +5,7 @@ #include "PiecePresenter.h" #include "ecs/standardComponents/MeshComponent.h" +#include "ecs/standardComponents/PickableComponent.h" #include "ecs/standardComponents/TransformComponent.h" 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}); m_registry.addComponent(entity, transform); m_registry.addComponent(entity, engine::MeshComponent(m_pieceModels[std::string(ludo::toString(piece.color))])); + m_registry.addComponent(entity, engine::PickableComponent(kPieceRadius)); } } @@ -26,3 +28,9 @@ void ludo::PiecePresenter::onPieceMoved(const ludo::PieceMovedEvent &event, cons auto& transform = m_registry.getComponent(entity); transform.m_position = target; } + +std::optional ludo::PiecePresenter::getPieceIndex(std::optional::value_type entity) { + auto it = std::ranges::find(m_pieceEntities, entity); + if (it == m_pieceEntities.end()) return std::nullopt; + return static_cast(std::distance(m_pieceEntities.begin(), it)); +} diff --git a/game/src/ludo/PiecePresenter.h b/game/src/ludo/PiecePresenter.h index d023ae6..b620b35 100644 --- a/game/src/ludo/PiecePresenter.h +++ b/game/src/ludo/PiecePresenter.h @@ -4,6 +4,8 @@ #ifndef COLORRACE_PIECEPRESENTER_H #define COLORRACE_PIECEPRESENTER_H +#include + #include "BoardLayout.h" #include "LudoGameState.h" #include "ecs/EntityRegistry.h" @@ -19,6 +21,10 @@ namespace ludo { void spawnPieces(const ludo::LudoGameState& state); void onPieceMoved(const ludo::PieceMovedEvent& event, const ludo::LudoGameState& state); + [[nodiscard]] const std::vector& getPieceEntities() const { return m_pieceEntities; } + + [[nodiscard]] std::optional getPieceIndex(std::optional::value_type entity); + private: engine::EntityRegistry& m_registry; std::unordered_map> m_pieceModels;