141 lines
8.8 KiB
C++
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
|