#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h" #include "MetaCorePhysics/MetaCorePhysics.h" #include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "MetaCorePlatform/MetaCoreInput.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreScene/MetaCoreScene.h" #include #include #include #include #include #include #include #include #include namespace MetaCore { class MetaCoreRuntimeInteraction::Impl { public: enum class QueryKind { Hover, Down, Up }; struct Completed { std::uint64_t Sequence; QueryKind Kind; glm::vec2 Position; MetaCoreId Object; glm::vec3 World; std::uint64_t SceneRevision; std::uint64_t InteractionRevision; }; struct AsyncState { std::mutex Mutex{}; std::vector CompletedQueries{}; bool Alive = true; }; EventCallback Callback{}; std::shared_ptr Async = std::make_shared(); std::uint64_t NextSequence = 1; std::uint64_t LatestHoverSequence = 0; std::uint64_t InteractionRevision = 1; std::deque PointerSequenceOrder{}; std::unordered_map PointerResults{}; MetaCoreId Hovered = 0; MetaCoreId Captured = 0; glm::vec2 PressPosition{0.0F}; glm::vec2 PreviousPosition{0.0F}; bool Dragging = false; void Emit(MetaCorePointerEventType type, MetaCoreId target, glm::vec2 position, glm::vec3 world = {}, std::uint32_t button = 0) { if (Callback && target != 0) Callback({type, target, button, position, position - PreviousPosition, world}); } bool IsInteractive(MetaCoreScene& scene, MetaCoreId id, std::uint32_t mask) const { auto object = scene.FindGameObject(id); return object && object.HasComponent() && object.GetComponent().Enabled && object.HasComponent() && object.GetComponent().Visible && (object.GetComponent().EventMask & mask) != 0; } }; MetaCoreRuntimeInteraction::MetaCoreRuntimeInteraction() : Impl_(std::make_unique()) {} MetaCoreRuntimeInteraction::~MetaCoreRuntimeInteraction() { std::scoped_lock lock(Impl_->Async->Mutex); Impl_->Async->Alive = false; Impl_->Async->CompletedQueries.clear(); } void MetaCoreRuntimeInteraction::SetEventCallback(EventCallback callback) { Impl_->Callback = std::move(callback); } void MetaCoreRuntimeInteraction::Cancel() { if (Impl_->Captured) Impl_->Emit(MetaCorePointerEventType::Cancel, Impl_->Captured, Impl_->PreviousPosition); if (Impl_->Hovered) Impl_->Emit(MetaCorePointerEventType::Leave, Impl_->Hovered, Impl_->PreviousPosition); Impl_->Captured = Impl_->Hovered = 0; Impl_->Dragging = false; ++Impl_->InteractionRevision; } void MetaCoreRuntimeInteraction::Update(MetaCoreScene& scene, MetaCoreEditorViewportRenderer& renderer, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed) { Update(scene, [&renderer](std::uint32_t x, std::uint32_t y, PickCallback callback) { return renderer.RequestPick(x, y, [callback = std::move(callback)](const MetaCoreFilamentSceneBridge::PickResult& result) { callback({result.ObjectId, result.WorldPosition}); }); }, input, viewport, mouseConsumed); } void MetaCoreRuntimeInteraction::Update(MetaCoreScene& scene, MetaCoreEditorViewportRenderer& renderer, MetaCorePhysicsWorld& physics, const MetaCoreSceneView& sceneView, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed) { Update(scene, [this, &scene, &renderer, &physics, sceneView, viewport](std::uint32_t x, std::uint32_t y, PickCallback callback) { const glm::vec2 screenPosition{ viewport.Left + static_cast(x), viewport.Top + viewport.Height - 1.0F - static_cast(y)}; const MetaCoreCameraRay ray = MetaCoreScreenPointToRay(sceneView, viewport, screenPosition); std::optional physicsHit; if (const auto candidate = physics.Raycast(ray.Origin, ray.Direction, std::max(sceneView.FarClip, 0.1F)); candidate.has_value()) { MetaCoreId target = candidate->ColliderObjectId; if (!Impl_->IsInteractive(scene, target, MetaCoreInteraction_All)) target = candidate->RigidBodyObjectId; if (Impl_->IsInteractive(scene, target, MetaCoreInteraction_All)) { physicsHit = *candidate; physicsHit->ColliderObjectId = target; } } return renderer.RequestPick(x, y, [callback = std::move(callback), physicsHit, origin = ray.Origin](const MetaCoreFilamentSceneBridge::PickResult& gpu) { if (physicsHit.has_value()) { const float gpuDistance = gpu.ObjectId == 0 ? std::numeric_limits::infinity() : glm::length(gpu.WorldPosition - origin); if (physicsHit->Distance <= gpuDistance) { callback({physicsHit->ColliderObjectId, physicsHit->Position}); return; } } callback({gpu.ObjectId, gpu.WorldPosition}); }); }, input, viewport, mouseConsumed); } void MetaCoreRuntimeInteraction::Update(MetaCoreScene& scene, const PickRequest& requestPick, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed) { std::vector completed; { std::scoped_lock lock(Impl_->Async->Mutex); completed.swap(Impl_->Async->CompletedQueries); } std::sort(completed.begin(), completed.end(), [](const auto& a, const auto& b){ return a.Sequence < b.Sequence; }); const auto processResult = [&](const Impl::Completed& result) { if (result.SceneRevision != scene.GetRevision() || result.InteractionRevision != Impl_->InteractionRevision) return; if (result.Kind == Impl::QueryKind::Hover) { if (result.Sequence != Impl_->LatestHoverSequence) return; const MetaCoreId next = Impl_->IsInteractive(scene, result.Object, MetaCoreInteraction_Hover) ? result.Object : 0; if (next != Impl_->Hovered) { if (Impl_->Hovered) Impl_->Emit(MetaCorePointerEventType::Leave, Impl_->Hovered, result.Position); Impl_->Hovered = next; if (next) Impl_->Emit(MetaCorePointerEventType::Enter, next, result.Position, result.World); } else if (next) Impl_->Emit(MetaCorePointerEventType::Move, next, result.Position, result.World); } else if (result.Kind == Impl::QueryKind::Down) { if (Impl_->IsInteractive(scene, result.Object, MetaCoreInteraction_Click | MetaCoreInteraction_Drag)) { Impl_->Captured = result.Object; Impl_->PressPosition = result.Position; Impl_->Dragging = false; Impl_->Emit(MetaCorePointerEventType::Down, result.Object, result.Position, result.World); } } else if (result.Kind == Impl::QueryKind::Up && Impl_->Captured) { const MetaCoreId target = Impl_->Captured; Impl_->Emit(MetaCorePointerEventType::Up, target, result.Position, result.World); if (Impl_->Dragging) Impl_->Emit(MetaCorePointerEventType::DragEnd, target, result.Position, result.World); else if (result.Object == target && Impl_->IsInteractive(scene, target, MetaCoreInteraction_Click)) Impl_->Emit(MetaCorePointerEventType::Click, target, result.Position, result.World); Impl_->Captured = 0; Impl_->Dragging = false; } }; for (const auto& result : completed) { if (result.Kind == Impl::QueryKind::Hover) processResult(result); else Impl_->PointerResults.insert_or_assign(result.Sequence, result); } while (!Impl_->PointerSequenceOrder.empty()) { const auto iterator = Impl_->PointerResults.find(Impl_->PointerSequenceOrder.front()); if (iterator == Impl_->PointerResults.end()) break; processResult(iterator->second); Impl_->PointerResults.erase(iterator); Impl_->PointerSequenceOrder.pop_front(); } const glm::vec2 cursor = input.GetCursorPosition(); if (Impl_->Captured && !Impl_->IsInteractive(scene, Impl_->Captured, MetaCoreInteraction_Click | MetaCoreInteraction_Drag)) Cancel(); if (!input.IsWindowFocused() || mouseConsumed || cursor.x < viewport.Left || cursor.y < viewport.Top || cursor.x >= viewport.Left + viewport.Width || cursor.y >= viewport.Top + viewport.Height) { Cancel(); Impl_->PreviousPosition = cursor; return; } if (Impl_->Captured && input.IsMouseButtonDown(MetaCoreMouseButton::Left)) { auto object = scene.FindGameObject(Impl_->Captured); const float threshold = object && object.HasComponent() ? object.GetComponent().DragThresholdPixels : 5.0F; if (!Impl_->Dragging && glm::length(cursor - Impl_->PressPosition) >= threshold && Impl_->IsInteractive(scene, Impl_->Captured, MetaCoreInteraction_Drag)) { Impl_->Dragging = true; Impl_->Emit(MetaCorePointerEventType::DragStart, Impl_->Captured, cursor); } if (Impl_->Dragging) Impl_->Emit(MetaCorePointerEventType::Drag, Impl_->Captured, cursor); } const auto submit = [&](Impl::QueryKind kind) { const std::uint64_t sequence = Impl_->NextSequence++; const std::uint64_t revision = scene.GetRevision(); if (kind == Impl::QueryKind::Hover) Impl_->LatestHoverSequence = sequence; else Impl_->PointerSequenceOrder.push_back(sequence); const std::uint64_t interactionRevision = Impl_->InteractionRevision; const auto localX = static_cast(std::clamp(cursor.x - viewport.Left, 0.0F, viewport.Width - 1.0F)); const auto localY = static_cast(std::clamp(viewport.Height - 1.0F - (cursor.y - viewport.Top), 0.0F, viewport.Height - 1.0F)); const std::weak_ptr async = Impl_->Async; const bool submitted = requestPick(localX, localY, [async, sequence, kind, cursor, revision, interactionRevision](const MetaCoreInteractionPickResult& result) { const auto state = async.lock(); if (!state) return; std::scoped_lock lock(state->Mutex); if (state->Alive) state->CompletedQueries.push_back({sequence, kind, cursor, result.ObjectId, result.WorldPosition, revision, interactionRevision}); }); if (!submitted && kind != Impl::QueryKind::Hover && !Impl_->PointerSequenceOrder.empty()) Impl_->PointerSequenceOrder.pop_back(); }; submit(Impl::QueryKind::Hover); if (input.WasMouseButtonPressed(MetaCoreMouseButton::Left)) submit(Impl::QueryKind::Down); if (input.WasMouseButtonReleased(MetaCoreMouseButton::Left)) submit(Impl::QueryKind::Up); Impl_->PreviousPosition = cursor; } } // namespace MetaCore