MetaCore/Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInteraction.cpp
2026-07-16 10:26:51 +08:00

141 lines
8.8 KiB
C++

#include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h"
#include "MetaCorePlatform/MetaCoreInput.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
#include "MetaCoreRender/MetaCoreRenderTypes.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include <algorithm>
#include <cmath>
#include <deque>
#include <memory>
#include <mutex>
#include <utility>
#include <unordered_map>
#include <vector>
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<Completed> CompletedQueries{};
bool Alive = true;
};
EventCallback Callback{};
std::shared_ptr<AsyncState> Async = std::make_shared<AsyncState>();
std::uint64_t NextSequence = 1;
std::uint64_t LatestHoverSequence = 0;
std::uint64_t InteractionRevision = 1;
std::deque<std::uint64_t> PointerSequenceOrder{};
std::unordered_map<std::uint64_t, Completed> 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<MetaCoreInteractableComponent>() && object.GetComponent<MetaCoreInteractableComponent>().Enabled &&
object.HasComponent<MetaCoreMeshRendererComponent>() && object.GetComponent<MetaCoreMeshRendererComponent>().Visible &&
(object.GetComponent<MetaCoreInteractableComponent>().EventMask & mask) != 0;
}
};
MetaCoreRuntimeInteraction::MetaCoreRuntimeInteraction() : Impl_(std::make_unique<Impl>()) {}
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, const PickRequest& requestPick, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed) {
std::vector<Impl::Completed> 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<MetaCoreInteractableComponent>() ? object.GetComponent<MetaCoreInteractableComponent>().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::uint32_t>(std::clamp(cursor.x - viewport.Left, 0.0F, viewport.Width - 1.0F));
const auto localY = static_cast<std::uint32_t>(std::clamp(viewport.Height - 1.0F - (cursor.y - viewport.Top), 0.0F, viewport.Height - 1.0F));
const std::weak_ptr<Impl::AsyncState> 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