From da53c90405848b59db0e9d1c2146f4819ad1c2ab Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 14 Apr 2024 21:24:11 +0300 Subject: [PATCH] World picking: added signal to widget, fixed few rendering issues with HitmanBloodMoney.ZIP level. Small refactoring --- .../Include/Widgets/SceneRenderWidget.h | 19 ++ BMEdit/Editor/Source/Render/Camera.cpp | 7 +- .../Source/Widgets/SceneRenderWidget.cpp | 175 +++++++++++------- BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 4 + 4 files changed, 130 insertions(+), 75 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 1e2f9bf..a9f1dc5 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -49,6 +49,21 @@ namespace widgets float fFrameTime { .0f }; // how much time used for render this frame }; + struct RayCastObjectDescription + { + enum EPriority { + EP_STATIC_OBJECT = 0, + EP_DYNAMIC_OBJECT = 1 + }; + + EPriority ePrio { EPriority::EP_STATIC_OBJECT }; + float fRayOriginDistance { .0f }; + gamelib::scene::SceneObject::Ptr pObject { nullptr }; + + // Operators + bool operator<(const RayCastObjectDescription& another) const; + }; + class SceneRenderWidget : public QOpenGLWidget { Q_OBJECT @@ -97,11 +112,15 @@ namespace widgets std::optional getGameObjectBoundingBox(const gamelib::scene::SceneObject::Ptr& pObject, bool bWorldTransform = true) const; std::optional getGameObjectBoundingBox(const gamelib::scene::SceneObject* pObject, bool bWorldTransform = true) const; + std::vector performRayCastToScene(const QPointF& screenSpace, const gamelib::scene::SceneObject::Ptr& pStartObject = nullptr) const; + signals: void resourcesReady(); void resourceLoadFailed(const QString& reason); void frameReady(const RenderStats& stats); + void worldSelectionChanged(const std::vector& selectedObjects); + public slots: void onRedrawRequested(); diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index af00c10..3e9b724 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -17,12 +17,7 @@ namespace render Ray Camera::getRayFromScreen(float x, float y) const { constexpr float kSign = 1.f; - - glm::vec4 vRayClip = glm::vec4( - (2.f * x) / static_cast(m_vScreenSize.x) - 1.f, - 1.f - (2.f * y) / static_cast(m_vScreenSize.y), - kSign, 1.f); - + glm::vec4 vRayClip = glm::vec4((2.f * x) / static_cast(m_vScreenSize.x) - 1.f, 1.f - (2.f * y) / static_cast(m_vScreenSize.y), kSign, 1.f); glm::vec4 vRayEye = glm::inverse(m_mProj) * vRayClip; vRayEye = glm::vec4 { vRayEye.x, vRayEye.y, kSign, .0f }; glm::vec3 vRayWorld = glm::normalize(glm::vec3(glm::inverse(m_mView) * vRayEye)); diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index dfa96ef..39027ec 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -37,6 +37,21 @@ namespace widgets "AdditionalResources", "AllLevels/mainsceneincludes.zip", "AllLevels/equipment.zip" }; + bool RayCastObjectDescription::operator<(const widgets::RayCastObjectDescription& another) const + { + if (another.ePrio == ePrio) + { + if (std::fabsf(another.fRayOriginDistance - fRayOriginDistance) <= std::numeric_limits::epsilon()) + { + return false; // They are completely same (except object itself, but who cares?) + } + + return fRayOriginDistance < another.fRayOriginDistance; + } + + return static_cast(ePrio) < static_cast(another.ePrio); + } + using namespace render; struct SceneRenderWidget::GLResources @@ -346,51 +361,16 @@ namespace widgets { QOpenGLWidget::mousePressEvent(event); - if (event->button() == Qt::MouseButton::RightButton && m_pLastRoom != nullptr) + if (event->button() == Qt::MouseButton::RightButton && !m_pLevel->getSceneObjects().empty()) { // Begin ray cast const auto vMouseClickPos = event->position(); - render::Ray sRay = m_camera.getRayFromScreen(static_cast(vMouseClickPos.x()), - static_cast(vMouseClickPos.y())); - // Need to find intersects with this thing. Need to visit only current room - if (auto pRoom = m_pLastRoom->rRoom.lock()) + // If no rooms on level we should use ROOT as initial point (not recommended in MOST cases) + auto result = performRayCastToScene(vMouseClickPos, (!m_pLastRoom && m_rooms.empty()) ? m_pLevel->getSceneObjects()[0] : nullptr); + if (!result.empty()) { - using R = gamelib::scene::SceneObject::EVisitResult; - std::vector vHitList; - - // Hit dynamic - // TODO: Implement me please - - // Hit static - pRoom->visitChildren([&sRay, &vHitList, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - if (auto bbox = getGameObjectBoundingBox(pObject); bbox.has_value()) - { - // Need to exclude objects where bbox origin is inside - if (sRay.intersect(bbox.value(), false)) - { - vHitList.push_back(pObject); - return R::VR_NEXT; - } - } - - return R::VR_CONTINUE; - }); - - // Sort hit list by distance to camera - std::sort(vHitList.begin(), vHitList.end(), [&sRay, this](const gamelib::scene::SceneObject::Ptr& pFirst, const gamelib::scene::SceneObject::Ptr& pSecond) { - const float fDist0 = glm::distance(sRay.vOrigin, pFirst->getPosition()); - const float fDist1 = glm::distance(sRay.vOrigin, pSecond->getPosition()); - - return fDist0 < fDist1; - }); - - if (!vHitList.empty()) - { - // TODO: Remove later! - setGeomViewMode(vHitList[0].get()); - setSelectedObject(vHitList[0].get()); - } + emit worldSelectionChanged(result); } } } @@ -663,6 +643,64 @@ namespace widgets return model.boundingBox; } + std::vector SceneRenderWidget::performRayCastToScene(const QPointF& screenSpace, const gamelib::scene::SceneObject::Ptr& pStartObject) const + { + render::Ray sRay = m_camera.getRayFromScreen(static_cast(screenSpace.x()), + static_cast(screenSpace.y())); + + gamelib::scene::SceneObject* pRoot = pStartObject.get(); + + if (!pRoot) + { + if (m_pLastRoom && !m_pLastRoom->rRoom.expired()) + { + pRoot = m_pLastRoom->rRoom.lock().get(); + } + } + + // Need to find intersects with this thing. Need to visit only current room + if (pRoot) + { + using R = gamelib::scene::SceneObject::EVisitResult; + + std::vector collectedObjects {}; + + static RayCastObjectDescription::EPriority s_CurrentPrio = RayCastObjectDescription::EPriority::EP_STATIC_OBJECT; + + auto hitObjVisitor = [&sRay, &collectedObjects, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (auto bbox = getGameObjectBoundingBox(pObject); bbox.has_value()) + { + // Need to exclude objects where bbox origin is inside + if (sRay.intersect(bbox.value(), false)) + { + auto& obj = collectedObjects.emplace_back(); + obj.ePrio = s_CurrentPrio; + obj.pObject = pObject; + obj.fRayOriginDistance = glm::distance(sRay.vOrigin, pObject->getPosition()); + return R::VR_NEXT; + } + } + + return R::VR_CONTINUE; + }; + + // Hit dynamic (not implemented yet) + s_CurrentPrio = RayCastObjectDescription::EPriority::EP_DYNAMIC_OBJECT; // Now dynamic objects + // TODO: Iterate over dynamic objects and check collision with them + + // Hit static + s_CurrentPrio = RayCastObjectDescription::EPriority::EP_STATIC_OBJECT; // Now static objects + pRoot->visitChildren(hitObjVisitor); + + // Sort hit list by distance to camera + std::sort(collectedObjects.begin(), collectedObjects.end()); + + return collectedObjects; + } + + return {}; + } + void SceneRenderWidget::onRedrawRequested() { if (m_pLevel) @@ -1195,7 +1233,7 @@ namespace widgets // Update room updateCameraRoomAttachment(stats); - if (pRootGeom != m_pLevel->getSceneObjects()[0].get() || m_rooms.empty() /* on some levels m_rooms cache could be not presented! */) + if (pRootGeom != m_pLevel->getSceneObjects()[0].get()) { // Render from specific node (no performance optimisations here) collectRenderEntriesIntoRenderList(pRootGeom, entries, stats, bIgnoreVisibility); @@ -1231,39 +1269,33 @@ namespace widgets } } } - } - // Try to render dynamic objects - const auto& vObjects = m_pLevel->getSceneObjects(); - auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { - return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); - }); + // Try to render dynamic objects + const auto& vObjects = m_pLevel->getSceneObjects(); + auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { + return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); + }); - if (it != vObjects.end()) - { - // Add this 'ROOM' as another thing to visit - const gamelib::scene::SceneObject* pDynRoot = it->get(); + gamelib::scene::SceneObject* pDynRoot = nullptr; - using R = gamelib::scene::SceneObject::EVisitResult; - pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - if (!pObject->getName().ends_with("_LOCATIONS.zip")) - { - // Collect everything inside - collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); - } + if (it != vObjects.end()) + { + // Add this 'ROOM' as another thing to visit + pDynRoot = it->get(); + } + else + { + // Need to collect every ZActor, ZHM3Actor, ZItem things from the whole scene :( + using R = gamelib::scene::SceneObject::EVisitResult; - return R::VR_NEXT; - }); - } - else - { - // Need to collect every ZActor, ZHM3Actor, ZItem things from the whole scene :( + pDynRoot = m_pLevel->getSceneObjects()[0].get(); + } + + // Visit dynamic root using R = gamelib::scene::SceneObject::EVisitResult; - m_pLevel->getSceneObjects()[0]->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - static const std::array kAllowedTypes = { // only base types - "ZActor", "ZPlayer", "ZItem", "ZItemAmmo" - }; + pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + static const std::array kAllowedTypes = { "ZActor", "ZPlayer", "ZItem", "ZItemAmmo", "ZLNKOBJ" }; for (const auto& sEntBaseName : kAllowedTypes) { @@ -1275,7 +1307,7 @@ namespace widgets // If object based on ... if (pObject->isInheritedOf(sEntBaseName)) { - if (isGameObjectInActiveRoom(pObject)) + if (isGameObjectInActiveRoom(pObject) || m_rooms.empty()) { collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); return R::VR_NEXT; // accepted, jump to next @@ -1287,6 +1319,11 @@ namespace widgets return R::VR_CONTINUE; }); } + else + { + // Ok, stupid render approach here. Just render everything starts from ROOT (dynamic & static doesn't matter in this case) + collectRenderEntriesIntoRenderList(m_pLevel->getSceneObjects()[0].get(), entries, stats, bIgnoreVisibility); + } } // Add debug stuff diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 267dde4..0c567f1 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -136,6 +136,10 @@ void BMEditMainWindow::connectActions() connect(ui->actionRenderMode_RenderRoomBoundingBoxes, &QAction::toggled, this, [this](bool val) { ui->sceneGLView->setShouldRenderRoomBoundingBox(val); }); + connect(ui->sceneGLView, &widgets::SceneRenderWidget::worldSelectionChanged, this, [this](const std::vector& hitList) { + // On hit performed we need to react somehow + // TODO: Do something here + }); } void BMEditMainWindow::connectDockWidgetActions()