From fdd9b0c0ba39a599909b85ee92007336096f2fc4 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Sat, 6 Sep 2025 21:14:01 +0700 Subject: [PATCH 1/6] Add Draw OBB to example 12 --- 12_MeshLoaders/main.cpp | 58 ++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/12_MeshLoaders/main.cpp b/12_MeshLoaders/main.cpp index 5e9a8114d..10e85251c 100644 --- a/12_MeshLoaders/main.cpp +++ b/12_MeshLoaders/main.cpp @@ -18,6 +18,14 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc using device_base_t = MonoWindowApplication; using asset_base_t = BuiltinResourcesApplication; + enum DrawBoundingBoxMode + { + DBBM_NONE, + DBBM_AABB, + DBBM_OBB, + DBBM_COUNT + }; + public: inline MeshLoadersApp(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) : IApplicationFramework(_localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD), @@ -131,7 +139,9 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc if (event.keyCode == E_KEY_CODE::EKC_R && event.action == SKeyboardEvent::ECA_RELEASED) reload = true; if (event.keyCode == E_KEY_CODE::EKC_B && event.action == SKeyboardEvent::ECA_RELEASED) - m_drawBBs = !m_drawBBs; + { + m_drawBBMode = DrawBoundingBoxMode((m_drawBBMode + 1) % DBBM_COUNT); + } } camera.keyboardProcess(events); }, @@ -153,10 +163,10 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc m_renderer->render(cb,CSimpleDebugRenderer::SViewParams(viewMatrix,viewProjMatrix)); } #ifdef NBL_BUILD_DEBUG_DRAW - if (m_drawBBs) + if (m_drawBBMode != DBBM_NONE) { const ISemaphore::SWaitInfo drawFinished = { .semaphore = m_semaphore.get(),.value = m_realFrameIx + 1u }; - m_drawAABB->render(cb, drawFinished, m_aabbInstances, viewProjMatrix); + m_drawAABB->render(cb, drawFinished, m_drawBBMode == DBBM_OBB ? m_obbInstances : m_aabbInstances, viewProjMatrix); } #endif cb->endRenderPass(); @@ -388,6 +398,7 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc core::vector worldTforms; const auto& converted = reservation.getGPUObjects(); m_aabbInstances.resize(converted.size()); + m_obbInstances.resize(converted.size()); for (uint32_t i = 0; i < converted.size(); i++) { const auto& geom = converted[i]; @@ -398,20 +409,39 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc const auto transformed = hlsl::shapes::util::transform(promotedWorld,promoted); printAABB(transformed,"Transformed"); bound = hlsl::shapes::util::union_(transformed,bound); + const auto tmpWorld = hlsl::float32_t3x4(promotedWorld); + const auto world4x4 = float32_t4x4{ + tmpWorld[0], + tmpWorld[1], + tmpWorld[2], + float32_t4(0, 0, 0, 1) + }; #ifdef NBL_BUILD_DEBUG_DRAW - auto& inst = m_aabbInstances[i]; + + auto& aabbInst = m_aabbInstances[i]; const auto tmpAabb = shapes::AABB<3,float>(promoted.minVx, promoted.maxVx); - hlsl::float32_t4x4 instanceTransform = ext::debug_draw::DrawAABB::getTransformFromAABB(tmpAabb); - const auto tmpWorld = hlsl::float32_t3x4(promotedWorld); - inst.color = { 1,1,1,1 }; - inst.transform[0] = tmpWorld[0]; - inst.transform[1] = tmpWorld[1]; - inst.transform[2] = tmpWorld[2]; - inst.transform[3] = float32_t4(0, 0, 0, 1); - inst.transform = hlsl::mul(inst.transform, instanceTransform); + hlsl::float32_t4x4 aabbTransform = ext::debug_draw::DrawAABB::getTransformFromAABB(tmpAabb); + aabbInst.color = { 1,1,1,1 }; + aabbInst.transform = hlsl::mul(world4x4, aabbTransform); + + auto& obbInst = m_obbInstances[i]; + const auto& cpuGeom = geometries[i].get(); + const auto obb = CPolygonGeometryManipulator::calculateOBB({ + .fetch = [geo = cpuGeom, &world4x4](size_t vertex_i) { + hlsl::float32_t3 pt; + geo->getPositionView().decodeElement(vertex_i, pt); + return pt; + }, + .size = cpuGeom->getPositionView().getElementCount(), + }); + obbInst.color = { 0, 0, 1, 1 }; + const auto obbTransform = ext::debug_draw::DrawAABB::getTransformFromOBB(obb); + obbInst.transform = hlsl::mul(world4x4, obbTransform); + #endif } + printAABB(bound,"Total"); if (!m_renderer->addGeometries({ &converted.front().get(),converted.size() })) return false; @@ -461,10 +491,12 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc // mutables std::string m_modelPath; - bool m_drawBBs = true; + DrawBoundingBoxMode m_drawBBMode; #ifdef NBL_BUILD_DEBUG_DRAW smart_refctd_ptr m_drawAABB; std::vector m_aabbInstances; + std::vector m_obbInstances; + #endif }; From d35ab8724355f2fe230129094acaeeacb17b6471 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Tue, 16 Sep 2025 11:24:42 +0700 Subject: [PATCH 2/6] Add example 72 --- 72_GeometryInspector/CMakeLists.txt | 51 ++ 72_GeometryInspector/include/common.hpp | 22 + 72_GeometryInspector/include/transform.hpp | 162 ++++ 72_GeometryInspector/main.cpp | 777 ++++++++++++++++++ CMakeLists.txt | 1 + .../geometry/CSimpleDebugRenderer.hpp | 3 + 6 files changed, 1016 insertions(+) create mode 100644 72_GeometryInspector/CMakeLists.txt create mode 100644 72_GeometryInspector/include/common.hpp create mode 100644 72_GeometryInspector/include/transform.hpp create mode 100644 72_GeometryInspector/main.cpp diff --git a/72_GeometryInspector/CMakeLists.txt b/72_GeometryInspector/CMakeLists.txt new file mode 100644 index 000000000..697399e91 --- /dev/null +++ b/72_GeometryInspector/CMakeLists.txt @@ -0,0 +1,51 @@ +include(common RESULT_VARIABLE RES) +if(NOT RES) + message(FATAL_ERROR "common.cmake not found. Should be in {repo_root}/cmake directory") +endif() + +if(NBL_BUILD_IMGUI AND NBL_BUILD_DEBUG_DRAW) + set(NBL_INCLUDE_SERACH_DIRECTORIES + "${CMAKE_CURRENT_SOURCE_DIR}/include" + ) + + list(APPEND NBL_LIBRARIES + imtestengine + imguizmo + "${NBL_EXT_IMGUI_UI_LIB}" + ) + + if (NBL_BUILD_MITSUBA_LOADER) + list(APPEND NBL_INCLUDE_SERACH_DIRECTORIES + "${NBL_EXT_MITSUBA_LOADER_INCLUDE_DIRS}" + ) + list(APPEND NBL_LIBRARIES + "${NBL_EXT_MITSUBA_LOADER_LIB}" + ) + endif() + + nbl_create_executable_project("" "" "${NBL_INCLUDE_SERACH_DIRECTORIES}" "${NBL_LIBRARIES}" "${NBL_EXECUTABLE_PROJECT_CREATION_PCH_TARGET}") + + if(NBL_EMBED_BUILTIN_RESOURCES) + set(_BR_TARGET_ ${EXECUTABLE_NAME}_builtinResourceData) + set(RESOURCE_DIR "app_resources") + + get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) + get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) + get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) + + file(GLOB_RECURSE BUILTIN_RESOURCE_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}/*") + foreach(RES_FILE ${BUILTIN_RESOURCE_FILES}) + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "${RES_FILE}") + endforeach() + + ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${RESOURCE_DIR}" "nbl::this_example::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") + + LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} ${_BR_TARGET_}) + endif() + + add_dependencies(${EXECUTABLE_NAME} ${NBL_EXT_DEBUG_DRAW_TARGET}) + target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${NBL_EXT_DEBUG_DRAW_TARGET}) + target_include_directories(${EXECUTABLE_NAME} PUBLIC $) + +endif() + diff --git a/72_GeometryInspector/include/common.hpp b/72_GeometryInspector/include/common.hpp new file mode 100644 index 000000000..cc06db2c1 --- /dev/null +++ b/72_GeometryInspector/include/common.hpp @@ -0,0 +1,22 @@ +#ifndef _NBL_THIS_EXAMPLE_COMMON_H_INCLUDED_ +#define _NBL_THIS_EXAMPLE_COMMON_H_INCLUDED_ + + +#include "nbl/examples/examples.hpp" + +using namespace nbl; +using namespace core; +using namespace hlsl; +using namespace system; +using namespace asset; +using namespace ui; +using namespace video; +using namespace scene; +using namespace nbl::examples; + +#include "transform.hpp" +#include "nbl/ui/ICursorControl.h" +#include "nbl/ext/ImGui/ImGui.h" +#include "imgui/imgui_internal.h" + +#endif // __NBL_THIS_EXAMPLE_COMMON_H_INCLUDED__ \ No newline at end of file diff --git a/72_GeometryInspector/include/transform.hpp b/72_GeometryInspector/include/transform.hpp new file mode 100644 index 000000000..6ac299c4b --- /dev/null +++ b/72_GeometryInspector/include/transform.hpp @@ -0,0 +1,162 @@ +#ifndef _NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED_ +#define _NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED_ + + +#include "nbl/ui/ICursorControl.h" + +#include "nbl/ext/ImGui/ImGui.h" + +#include "imgui/imgui_internal.h" +#include "imguizmo/ImGuizmo.h" + + +struct TransformRequestParams +{ + float camDistance = 8.f; + uint8_t sceneTexDescIx = ~0; + bool useWindow = false, editTransformDecomposition = false, enableViewManipulate = false; +}; + +nbl::hlsl::uint16_t2 EditTransform(float* cameraView, const float* cameraProjection, float* matrix, const TransformRequestParams& params) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::TRANSLATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::LOCAL); + static bool useSnap = false; + static float snap[3] = { 1.f, 1.f, 1.f }; + static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; + static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; + static bool boundSizing = false; + static bool boundSizingSnap = false; + + if (params.editTransformDecomposition) + { + if (ImGui::IsKeyPressed(ImGuiKey_T)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(ImGuiKey_R)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(ImGuiKey_S)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Universal", mCurrentGizmoOperation == ImGuizmo::UNIVERSAL)) + mCurrentGizmoOperation = ImGuizmo::UNIVERSAL; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation); + ImGui::InputFloat3("Rt", matrixRotation); + ImGui::InputFloat3("Sc", matrixScale); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + if (ImGui::IsKeyPressed(ImGuiKey_S) && ImGui::IsKeyPressed(ImGuiKey_LeftShift)) + useSnap = !useSnap; + ImGui::Checkbox("##UseSnap", &useSnap); + ImGui::SameLine(); + + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + ImGui::InputFloat3("Snap", &snap[0]); + break; + case ImGuizmo::ROTATE: + ImGui::InputFloat("Angle Snap", &snap[0]); + break; + case ImGuizmo::SCALE: + ImGui::InputFloat("Scale Snap", &snap[0]); + break; + } + ImGui::Checkbox("Bound Sizing", &boundSizing); + if (boundSizing) + { + ImGui::PushID(3); + ImGui::Checkbox("##BoundSizing", &boundSizingSnap); + ImGui::SameLine(); + ImGui::InputFloat3("Snap", boundsSnap); + ImGui::PopID(); + } + } + + ImGuiIO& io = ImGui::GetIO(); + float viewManipulateRight = io.DisplaySize.x; + float viewManipulateTop = 0; + static ImGuiWindowFlags gizmoWindowFlags = 0; + + /* + for the "useWindow" case we just render to a gui area, + otherwise to fake full screen transparent window + + note that for both cases we make sure gizmo being + rendered is aligned to our texture scene using + imgui "cursor" screen positions + */ +// TODO: this shouldn't be handled here I think + SImResourceInfo info; + info.textureID = params.sceneTexDescIx; + info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; + + nbl::hlsl::uint16_t2 retval; + if (params.useWindow) + { + ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImVec2(400, 20), ImGuiCond_Appearing); + ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); + ImGui::Begin("Gizmo", 0, gizmoWindowFlags); + ImGuizmo::SetDrawlist(); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + retval = {contentRegionSize.x,contentRegionSize.y}; + + viewManipulateRight = cursorPos.x + contentRegionSize.x; + viewManipulateTop = cursorPos.y; + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + gizmoWindowFlags = (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); + } + else + { + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window + ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + retval = {contentRegionSize.x,contentRegionSize.y}; + + viewManipulateRight = cursorPos.x + contentRegionSize.x; + viewManipulateTop = cursorPos.y; + } + + ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, NULL, useSnap ? &snap[0] : NULL, boundSizing ? bounds : NULL, boundSizingSnap ? boundsSnap : NULL); + + if(params.enableViewManipulate) + ImGuizmo::ViewManipulate(cameraView, params.camDistance, ImVec2(viewManipulateRight - 128, viewManipulateTop), ImVec2(128, 128), 0x10101010); + + ImGui::End(); + ImGui::PopStyleColor(); + + return retval; +} + +#endif // __NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED__ \ No newline at end of file diff --git a/72_GeometryInspector/main.cpp b/72_GeometryInspector/main.cpp new file mode 100644 index 000000000..5fe0421da --- /dev/null +++ b/72_GeometryInspector/main.cpp @@ -0,0 +1,777 @@ +// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#include "common.hpp" + +#include "../3rdparty/portable-file-dialogs/portable-file-dialogs.h" + +#ifdef NBL_BUILD_MITSUBA_LOADER +#include "nbl/ext/MitsubaLoader/CSerializedLoader.h" +#endif + +#include "nbl/ext/DebugDraw/CDrawAABB.h" +#include "nbl/ext/ImGui/ImGui.h" + +class GeometryInspectorApp final : public MonoWindowApplication, public BuiltinResourcesApplication +{ + using device_base_t = MonoWindowApplication; + using asset_base_t = BuiltinResourcesApplication; + + enum DrawBoundingBoxMode + { + DBBM_NONE, + DBBM_AABB, + DBBM_OBB, + DBBM_COUNT + }; + + public: + static float32_t4x4 intofloat32_t4x4(const matrix4SIMD& mat) + { + return float32_t4x4{ + mat.rows[0].x, mat.rows[0].y, mat.rows[0].z, mat.rows[0].w, + mat.rows[1].x, mat.rows[1].y, mat.rows[1].z, mat.rows[1].w, + mat.rows[2].x, mat.rows[2].y, mat.rows[2].z, mat.rows[2].w, + mat.rows[3].x, mat.rows[3].y, mat.rows[3].z, mat.rows[3].w, + }; + } + + static float32_t4x4 intofloat32_t4x4(const matrix3x4SIMD& mat) + { + return float32_t4x4{ + mat.rows[0].x, mat.rows[0].y, mat.rows[0].z, mat.rows[0].w, + mat.rows[1].x, mat.rows[1].y, mat.rows[1].z, mat.rows[1].w, + mat.rows[2].x, mat.rows[2].y, mat.rows[2].z, mat.rows[2].w, + 0.0f, 0.0f, 0.0f, 1.0f, + }; + } + + static float32_t4x4 intofloat32_t4x4(const float32_t3x4& mat) + { + return float32_t4x4{ + mat[0], + mat[1], + mat[2], + float32_t4(0.0f, 0.0f, 0.0f, 1.0f), + }; + } + inline GeometryInspectorApp(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) + : IApplicationFramework(_localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD), + device_base_t({1280,720}, EF_D32_SFLOAT, _localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD) {} + + inline bool onAppInitialized(smart_refctd_ptr&& system) override + { + if (!asset_base_t::onAppInitialized(smart_refctd_ptr(system))) + return false; + #ifdef NBL_BUILD_MITSUBA_LOADER + m_assetMgr->addAssetLoader(make_smart_refctd_ptr()); + #endif + if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) + return false; + + m_semaphore = m_device->createSemaphore(m_realFrameIx); + if (!m_semaphore) + return logFail("Failed to Create a Semaphore!"); + + auto pool = m_device->createCommandPool(getGraphicsQueue()->getFamilyIndex(),IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + for (auto i=0u; icreateCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{m_cmdBufs.data()+i,1})) + return logFail("Couldn't create Command Buffer!"); + } + + + auto scRes = static_cast(m_surface->getSwapchainResources()); + m_renderer = CSimpleDebugRenderer::create(m_assetMgr.get(),scRes->getRenderpass(),0,{}); + if (!m_renderer) + return logFail("Failed to create renderer!"); + + auto* renderpass = scRes->getRenderpass(); + + { + ext::debug_draw::DrawAABB::SCreationParameters params = {}; + params.assetManager = m_assetMgr; + params.transfer = getTransferUpQueue(); + params.drawMode = ext::debug_draw::DrawAABB::ADM_DRAW_BATCH; + params.batchPipelineLayout = ext::debug_draw::DrawAABB::createDefaultPipelineLayout(m_device.get()); + params.renderpass = smart_refctd_ptr(renderpass); + params.utilities = m_utils; + m_bbRenderer = ext::debug_draw::DrawAABB::create(std::move(params)); + } + + // gui descriptor setup + { + using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; + { + IGPUSampler::SParams params; + params.AnisotropicFilter = 1u; + params.TextureWrapU = ETC_REPEAT; + params.TextureWrapV = ETC_REPEAT; + params.TextureWrapW = ETC_REPEAT; + + m_ui.samplers.gui = m_device->createSampler(params); + m_ui.samplers.gui->setObjectDebugName("Nabla IMGUI UI Sampler"); + } + + std::array, 69u> immutableSamplers; + for (auto& it : immutableSamplers) + it = smart_refctd_ptr(m_ui.samplers.scene); + + immutableSamplers[nbl::ext::imgui::UI::FontAtlasTexId] = smart_refctd_ptr(m_ui.samplers.gui); + + nbl::ext::imgui::UI::SCreationParameters params; + + params.resources.texturesInfo = { .setIx = 0u, .bindingIx = 0u }; + params.resources.samplersInfo = { .setIx = 0u, .bindingIx = 1u }; + params.assetManager = m_assetMgr; + params.pipelineCache = nullptr; + params.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, MaxUITextureCount); + params.renderpass = smart_refctd_ptr(renderpass); + params.streamingBuffer = nullptr; + params.subpassIx = 0u; + params.transfer = getGraphicsQueue(); + params.utilities = m_utils; + { + m_ui.manager = ext::imgui::UI::create(std::move(params)); + + // note that we use default layout provided by our extension, but you are free to create your own by filling nbl::ext::imgui::UI::S_CREATION_PARAMETERS::resources + const auto* descriptorSetLayout = m_ui.manager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); + + IDescriptorPool::SCreateInfo descriptorPoolInfo = {}; + descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = (uint32_t)nbl::ext::imgui::UI::DefaultSamplerIx::COUNT; + descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = MaxUITextureCount; + descriptorPoolInfo.maxSets = 1u; + descriptorPoolInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT; + + m_guiDescriptorSetPool = m_device->createDescriptorPool(std::move(descriptorPoolInfo)); + assert(m_guiDescriptorSetPool); + + m_guiDescriptorSetPool->createDescriptorSets(1u, &descriptorSetLayout, &m_ui.descriptorSet); + assert(m_ui.descriptorSet); + } + } + + m_ui.manager->registerListener( + [this]() -> void { + ImGuiIO& io = ImGui::GetIO(); + + m_camera.setProjectionMatrix([&]() + { + static matrix4SIMD projection; + + projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovRH( + core::radians(m_cameraSetting.fov), + io.DisplaySize.x / io.DisplaySize.y, + m_cameraSetting.zNear, + m_cameraSetting.zFar); + + return projection; + }()); + + ImGuizmo::SetOrthographic(false); + ImGuizmo::BeginFrame(); + + // create a window and insert the inspector + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); + ImGui::Begin("Controls"); + + ImGui::SameLine(); + + ImGui::Text("Camera"); + + ImGui::SliderFloat("Move speed", &m_cameraSetting.moveSpeed, 0.1f, 10.f); + ImGui::SliderFloat("Rotate speed", &m_cameraSetting.rotateSpeed, 0.1f, 10.f); + ImGui::SliderFloat("Fov", &m_cameraSetting.fov, 20.f, 150.f); + ImGui::SliderFloat("zNear", &m_cameraSetting.zNear, 0.1f, 100.f); + ImGui::SliderFloat("zFar", &m_cameraSetting.zFar, 110.f, 10000.f); + + + ImGui::Text("Inspector"); + ImGui::ListBox("Selected polygon", &m_selectedMesh, + [](void* userData, int index) -> const char* { + auto* meshInstances = reinterpret_cast(userData); + return meshInstances[index].name.data(); + }, + m_meshInstances.data(), + m_meshInstances.size()); + + ImGui::Checkbox("Draw AABB", &m_shouldDrawAABB); + ImGui::Checkbox("Draw OBB", &m_shouldDrawOBB); + if (ImGuizmo::IsUsing()) + { + ImGui::Text("Using gizmo"); + } + else + { + ImGui::Text(ImGuizmo::IsOver() ? "Over gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::TRANSLATE) ? "Over translate gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::ROTATE) ? "Over rotate gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::SCALE) ? "Over scale gizmo" : ""); + } + ImGui::Separator(); + + static struct + { + hlsl::float32_t4x4 view, projection, model; + } imguizmoM16InOut; + + ImGuizmo::SetID(0u); + + auto& selectedInstance = m_renderer->getInstance(m_selectedMesh); + + imguizmoM16InOut.view = hlsl::transpose(intofloat32_t4x4(m_camera.getViewMatrix())); + imguizmoM16InOut.projection = hlsl::transpose(intofloat32_t4x4(m_camera.getProjectionMatrix())); + imguizmoM16InOut.projection[1][1] *= -1.f; // Flip y coordinates. https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ + imguizmoM16InOut.model = hlsl::transpose(intofloat32_t4x4(selectedInstance.world)); + { + m_transformParams.enableViewManipulate = true; + EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], m_transformParams); + } + selectedInstance.world = hlsl::float32_t3x4(hlsl::transpose(imguizmoM16InOut.model)); + + ImGui::End(); + }); + // + if (!reloadModel()) + return false; + + m_camera.mapKeysToArrows(); + + onAppInitializedFinish(); + return true; + } + + bool updateGUIDescriptorSet() + { + // texture atlas, note we don't create info & write pair for the font sampler because UI extension's is immutable and baked into DS layout + static std::array descriptorInfo; + static IGPUDescriptorSet::SWriteDescriptorSet writes[MaxUITextureCount]; + + descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].desc = smart_refctd_ptr(m_ui.manager->getFontAtlasView()); + + for (uint32_t i = 0; i < descriptorInfo.size(); ++i) + { + writes[i].dstSet = m_ui.descriptorSet.get(); + writes[i].binding = 0u; + writes[i].arrayElement = i; + writes[i].count = 1u; + } + writes[nbl::ext::imgui::UI::FontAtlasTexId].info = descriptorInfo.data() + nbl::ext::imgui::UI::FontAtlasTexId; + + return m_device->updateDescriptorSets(writes, {}); + } + + inline void update(const std::chrono::microseconds nextPresentationTimestamp) + { + m_camera.setMoveSpeed(m_cameraSetting.moveSpeed); + m_camera.setRotateSpeed(m_cameraSetting.rotateSpeed); + + static std::chrono::microseconds previousEventTimestamp{}; + + m_inputSystem->getDefaultMouse(&m_mouse); + m_inputSystem->getDefaultKeyboard(&m_keyboard); + + struct + { + std::vector mouse{}; + std::vector keyboard{}; + } capturedEvents; + + m_camera.beginInputProcessing(nextPresentationTimestamp); + { + const auto& io = ImGui::GetIO(); + m_mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void + { + if (!io.WantCaptureMouse) + m_camera.mouseProcess(events); // don't capture the events, only let m_camera handle them with its impl + + for (const auto& e : events) // here capture + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + capturedEvents.mouse.emplace_back(e); + + } + }, m_logger.get()); + + bool reload = false; + m_keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void + { + if (!io.WantCaptureKeyboard) + m_camera.keyboardProcess(events); // don't capture the events, only let m_camera handle them with its impl + + for (const auto& e : events) // here capture + { + if (e.timeStamp < previousEventTimestamp) + continue; + if (e.keyCode == E_KEY_CODE::EKC_R && e.action == SKeyboardEvent::ECA_RELEASED) + reload = true; + + previousEventTimestamp = e.timeStamp; + capturedEvents.keyboard.emplace_back(e); + } + }, m_logger.get()); + if (reload) reloadModel(); + + } + m_camera.endInputProcessing(nextPresentationTimestamp); + + const core::SRange mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); + const core::SRange keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); + const auto cursorPosition = m_window->getCursorControl()->getPosition(); + const auto mousePosition = float32_t2(cursorPosition.x, cursorPosition.y) - float32_t2(m_window->getX(), m_window->getY()); + + const ext::imgui::UI::SUpdateParameters params = + { + .mousePosition = mousePosition, + .displaySize = { m_window->getWidth(), m_window->getHeight() }, + .mouseEvents = mouseEvents, + .keyboardEvents = keyboardEvents + }; + + m_ui.manager->update(params); + } + + inline IQueue::SSubmitInfo::SSemaphoreInfo renderFrame(const std::chrono::microseconds nextPresentationTimestamp) override + { + update(nextPresentationTimestamp); + + // + const auto resourceIx = m_realFrameIx % MaxFramesInFlight; + + auto* const cb = m_cmdBufs.data()[resourceIx].get(); + cb->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); + cb->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + // clear to black for both things + { + // begin renderpass + { + auto scRes = static_cast(m_surface->getSwapchainResources()); + auto* framebuffer = scRes->getFramebuffer(device_base_t::getCurrentAcquire().imageIndex); + const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; + const IGPUCommandBuffer::SClearDepthStencilValue depthValue = { .depth = 0.f }; + const VkRect2D currentRenderArea = + { + .offset = {0,0}, + .extent = {framebuffer->getCreationParameters().width,framebuffer->getCreationParameters().height} + }; + const IGPUCommandBuffer::SRenderpassBeginInfo info = + { + .framebuffer = framebuffer, + .colorClearValues = &clearValue, + .depthStencilClearValues = &depthValue, + .renderArea = currentRenderArea + }; + cb->beginRenderPass(info,IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); + + const SViewport viewport = { + .x = static_cast(currentRenderArea.offset.x), + .y = static_cast(currentRenderArea.offset.y), + .width = static_cast(currentRenderArea.extent.width), + .height = static_cast(currentRenderArea.extent.height) + }; + cb->setViewport(0u,1u,&viewport); + + cb->setScissor(0u,1u,¤tRenderArea); + } + + // draw scene + float32_t3x4 viewMatrix; + float32_t4x4 viewProjMatrix; + { + // TODO: get rid of legacy matrices + { + memcpy(&viewMatrix,m_camera.getViewMatrix().pointer(),sizeof(viewMatrix)); + memcpy(&viewProjMatrix,m_camera.getConcatenatedMatrix().pointer(),sizeof(viewProjMatrix)); + } + m_renderer->render(cb,CSimpleDebugRenderer::SViewParams(viewMatrix,viewProjMatrix)); + } + + const ISemaphore::SWaitInfo drawFinished = { .semaphore = m_semaphore.get(),.value = m_realFrameIx + 1u }; + const auto& renderInstance = m_renderer->getInstance(m_selectedMesh); + const auto& meshInstance = m_meshInstances[m_selectedMesh]; + core::vector debugDrawInstances; + debugDrawInstances.reserve(2); + const auto world4x4 = float32_t4x4{ + renderInstance.world[0], + renderInstance.world[1], + renderInstance.world[2], + float32_t4(0, 0, 0, 1) + }; + if (m_shouldDrawAABB) + { + const auto aabbTransform = ext::debug_draw::DrawAABB::getTransformFromAABB(meshInstance.aabb); + debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = hlsl::mul(world4x4, aabbTransform), .color = float32_t4(1, 1, 1, 1)}); + } + if (m_shouldDrawOBB) + { + const auto obbTransform = ext::debug_draw::DrawAABB::getTransformFromOBB(meshInstance.obb); + debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = hlsl::mul(world4x4, obbTransform), .color = float32_t4(0, 0, 1, 1)}); + } + m_bbRenderer->render(cb, drawFinished, debugDrawInstances, viewProjMatrix); + + cb->beginDebugMarker("Render ImGui"); + const auto uiParams = m_ui.manager->getCreationParameters(); + auto* uiPipeline = m_ui.manager->getPipeline(); + cb->bindGraphicsPipeline(uiPipeline); + cb->bindDescriptorSets(EPBP_GRAPHICS, uiPipeline->getLayout(), uiParams.resources.texturesInfo.setIx, 1u, &m_ui.descriptorSet.get()); + if (!m_ui.manager->render(cb, drawFinished)) + { + m_logger->log("TODO: need to present acquired image before bailing because its already acquired.",ILogger::ELL_ERROR); + return {}; + } + cb->endDebugMarker(); + + cb->endRenderPass(); + } + cb->end(); + + IQueue::SSubmitInfo::SSemaphoreInfo retval = + { + .semaphore = m_semaphore.get(), + .value = ++m_realFrameIx, + .stageMask = PIPELINE_STAGE_FLAGS::ALL_GRAPHICS_BITS + }; + const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[] = + { + {.cmdbuf = cb } + }; + const IQueue::SSubmitInfo::SSemaphoreInfo acquired[] = { + { + .semaphore = device_base_t::getCurrentAcquire().semaphore, + .value = device_base_t::getCurrentAcquire().acquireCount, + .stageMask = PIPELINE_STAGE_FLAGS::NONE + } + }; + const IQueue::SSubmitInfo infos[] = + { + { + .waitSemaphores = acquired, + .commandBuffers = commandBuffers, + .signalSemaphores = {&retval,1} + } + }; + + if (getGraphicsQueue()->submit(infos) != IQueue::RESULT::SUCCESS) + { + retval.semaphore = nullptr; // so that we don't wait on semaphore that will never signal + m_realFrameIx--; + } + + std::string caption = "[Nabla Engine] Geometry Inspector"; + { + caption += ", displaying ["; + caption += m_modelPath; + caption += "]"; + m_window->setCaption(caption); + } + + updateGUIDescriptorSet(); + return retval; + } + + protected: + const video::IGPURenderpass::SCreationParams::SSubpassDependency* getDefaultSubpassDependencies() const override + { + // Subsequent submits don't wait for each other, hence its important to have External Dependencies which prevent users of the depth attachment overlapping. + const static IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = { + // wipe-transition of Color to ATTACHMENT_OPTIMAL and depth + { + .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .dstSubpass = 0, + .memoryBarrier = { + // last place where the depth can get modified in previous frame, `COLOR_ATTACHMENT_OUTPUT_BIT` is implicitly later + .srcStageMask = PIPELINE_STAGE_FLAGS::LATE_FRAGMENT_TESTS_BIT, + // don't want any writes to be available, we'll clear + .srcAccessMask = ACCESS_FLAGS::NONE, + // destination needs to wait as early as possible + // TODO: `COLOR_ATTACHMENT_OUTPUT_BIT` shouldn't be needed, because its a logically later stage, see TODO in `ECommonEnums.h` + .dstStageMask = PIPELINE_STAGE_FLAGS::EARLY_FRAGMENT_TESTS_BIT | PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + // because depth and color get cleared first no read mask + .dstAccessMask = ACCESS_FLAGS::DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + } + // leave view offsets and flags default + }, + // color from ATTACHMENT_OPTIMAL to PRESENT_SRC + { + .srcSubpass = 0, + .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .memoryBarrier = { + // last place where the color can get modified, depth is implicitly earlier + .srcStageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + // only write ops, reads can't be made available + .srcAccessMask = ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + // spec says nothing is needed when presentation is the destination + } + // leave view offsets and flags default + }, + IGPURenderpass::SCreationParams::DependenciesEnd + }; + return dependencies; + } + + private: + // TODO: standardise this across examples, and take from `argv` + bool m_nonInteractiveTest = false; + + bool reloadModel() + { + if (m_nonInteractiveTest) // TODO: maybe also take from argv and argc + m_modelPath = (sharedInputCWD/"ply/Spanner-ply.ply").string(); + else + { + pfd::open_file file("Choose a supported Model File", sharedInputCWD.string(), + { + "All Supported Formats", "*.ply *.stl *.serialized *.obj", + "TODO (.ply)", "*.ply", + "TODO (.stl)", "*.stl", + "Mitsuba 0.6 Serialized (.serialized)", "*.serialized", + "Wavefront Object (.obj)", "*.obj" + }, + false + ); + if (file.result().empty()) + return false; + m_modelPath = file.result()[0]; + } + + // free up + m_renderer->m_instances.clear(); + m_renderer->clearGeometries({.semaphore=m_semaphore.get(),.value=m_realFrameIx}); + m_assetMgr->clearAllAssetCache(); + + //! load the geometry + IAssetLoader::SAssetLoadParams params = {}; + params.logger = m_logger.get(); + auto bundle = m_assetMgr->getAsset(m_modelPath,params); + if (bundle.getContents().empty()) + return false; + + // + core::vector> geometries; + switch (bundle.getAssetType()) + { + case IAsset::E_TYPE::ET_GEOMETRY: + for (const auto& item : bundle.getContents()) + if (auto polyGeo=IAsset::castDown(item); polyGeo) + geometries.push_back(polyGeo); + break; + default: + m_logger->log("Asset loaded but not a supported type (ET_GEOMETRY,ET_GEOMETRY_COLLECTION)",ILogger::ELL_ERROR); + break; + } + if (geometries.empty()) + return false; + + using aabb_t = hlsl::shapes::AABB<3,float32_t>; + auto printAABB = [&](const aabb_t& aabb, const char* extraMsg="")->void + { + m_logger->log("%s AABB is (%f,%f,%f) -> (%f,%f,%f)",ILogger::ELL_INFO,extraMsg,aabb.minVx.x,aabb.minVx.y,aabb.minVx.z,aabb.maxVx.x,aabb.maxVx.y,aabb.maxVx.z); + }; + auto bound = aabb_t::create(); + // convert the geometries + { + smart_refctd_ptr converter = CAssetConverter::create({.device=m_device.get()}); + + const auto transferFamily = getTransferUpQueue()->getFamilyIndex(); + + struct SInputs : CAssetConverter::SInputs + { + virtual inline std::span getSharedOwnershipQueueFamilies(const size_t groupCopyID, const asset::ICPUBuffer* buffer, const CAssetConverter::patch_t& patch) const + { + return sharedBufferOwnership; + } + + core::vector sharedBufferOwnership; + } inputs = {}; + core::vector> patches(geometries.size(),CSimpleDebugRenderer::DefaultPolygonGeometryPatch); + { + inputs.logger = m_logger.get(); + std::get>(inputs.assets) = {&geometries.front().get(),geometries.size()}; + std::get>(inputs.patches) = patches; + // set up shared ownership so we don't have to + core::unordered_set families; + families.insert(transferFamily); + families.insert(getGraphicsQueue()->getFamilyIndex()); + if (families.size()>1) + for (const auto fam : families) + inputs.sharedBufferOwnership.push_back(fam); + } + + // reserve + auto reservation = converter->reserve(inputs); + if (!reservation) + { + m_logger->log("Failed to reserve GPU objects for CPU->GPU conversion!",ILogger::ELL_ERROR); + return false; + } + + // convert + { + auto semaphore = m_device->createSemaphore(0u); + + constexpr auto MultiBuffering = 2; + std::array,MultiBuffering> commandBuffers = {}; + { + auto pool = m_device->createCommandPool(transferFamily,IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT|IGPUCommandPool::CREATE_FLAGS::TRANSIENT_BIT); + pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY,commandBuffers,smart_refctd_ptr(m_logger)); + } + commandBuffers.front()->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + + std::array commandBufferSubmits; + for (auto i=0; ilog("Failed to await submission feature!", ILogger::ELL_ERROR); + return false; + } + } + + auto tmp = hlsl::float32_t4x3( + hlsl::float32_t3(1,0,0), + hlsl::float32_t3(0,1,0), + hlsl::float32_t3(0,0,1), + hlsl::float32_t3(0,0,0) + ); + const auto& converted = reservation.getGPUObjects(); + core::vector meshWorlds; + for (uint32_t i = 0; i < converted.size(); i++) + { + const auto& geom = converted[i]; + const auto aabb = geom.value->getAABB(); + printAABB(aabb,"Geometry"); + tmp[3].x += aabb.getExtent().x; + meshWorlds.emplace_back(hlsl::transpose(tmp)); + const auto transformed = hlsl::shapes::util::transform(meshWorlds.back(), aabb); + bound = hlsl::shapes::util::union_(transformed,bound); + + const auto& cpuGeom = geometries[i].get(); + const auto obb = CPolygonGeometryManipulator::calculateOBB({ + .fetch = [geo = cpuGeom](size_t vertex_i) { + hlsl::float32_t3 pt; + geo->getPositionView().decodeElement(vertex_i, pt); + return pt; + }, + .size = cpuGeom->getPositionView().getElementCount(), + }); + + m_meshInstances.push_back({ .name = std::format("Mesh {}", i), .aabb = aabb, .obb = obb }); + } + + printAABB(bound,"Total"); + if (!m_renderer->addGeometries({ &converted.front().get(),converted.size() })) + return false; + + for (auto geom_i = 0u; geom_i < m_renderer->getGeometries().size(); geom_i++) + m_renderer->m_instances.push_back({ + .world = meshWorlds[geom_i], + .packedGeo = &m_renderer->getGeometry(geom_i) + }); + } + + // get scene bounds and reset m_camera + { + const float32_t distance = 0.05; + const auto diagonal = bound.getExtent(); + { + const auto measure = hlsl::length(diagonal); + const auto aspectRatio = float(m_window->getWidth())/float(m_window->getHeight()); + m_camera.setProjectionMatrix(core::matrix4SIMD::buildProjectionMatrixPerspectiveFovRH(1.2f,aspectRatio,distance*measure*0.1,measure*4.0)); + m_camera.setMoveSpeed(measure*0.04); + } + const auto pos = bound.maxVx+diagonal*distance; + m_camera.setPosition(vectorSIMDf(pos.x,pos.y,pos.z)); + const auto center = (bound.minVx+bound.maxVx)*0.5f; + m_camera.setTarget(vectorSIMDf(center.x,center.y,center.z)); + } + + // TODO: write out the geometry + + return true; + } + + // Maximum frames which can be simultaneously submitted, used to cycle through our per-frame resources like command buffers + constexpr static inline uint32_t MaxFramesInFlight = 3u; + constexpr static inline uint8_t MaxUITextureCount = 1u; + // + smart_refctd_ptr m_renderer; + + struct MeshInstance + { + std::string name; + hlsl::shapes::AABB<3, float32_t> aabb; + hlsl::shapes::OBB<3, float32_t> obb; + }; + core::vector m_meshInstances; + int m_selectedMesh = 0; + // + smart_refctd_ptr m_semaphore; + uint64_t m_realFrameIx = 0; + std::array,MaxFramesInFlight> m_cmdBufs; + // + InputSystem::ChannelReader m_mouse; + InputSystem::ChannelReader m_keyboard; + // + struct CameraSetting + { + float fov = 60.f; + float zNear = 0.1f; + float zFar = 10000.f; + float moveSpeed = 1.f; + float rotateSpeed = 1.f; + float viewWidth = 10.f; + float camYAngle = 165.f / 180.f * 3.14159f; + float camXAngle = 32.f / 180.f * 3.14159f; + + } m_cameraSetting; + Camera m_camera = Camera(core::vectorSIMDf(0, 0, 0), core::vectorSIMDf(0, 0, 0), core::matrix4SIMD()); + // mutables + std::string m_modelPath; + + smart_refctd_ptr m_bbRenderer; + bool m_shouldDrawAABB; + bool m_shouldDrawOBB; + + struct C_UI + { + nbl::core::smart_refctd_ptr manager; + + struct + { + core::smart_refctd_ptr gui, scene; + } samplers; + + core::smart_refctd_ptr descriptorSet; + } m_ui; + core::smart_refctd_ptr m_guiDescriptorSetPool; + + TransformRequestParams m_transformParams; + }; + +NBL_MAIN_FUNC(GeometryInspectorApp) diff --git a/CMakeLists.txt b/CMakeLists.txt index a29b34314..cacf77c98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ if(NBL_BUILD_EXAMPLES) add_subdirectory(70_FLIPFluids) add_subdirectory(71_RayTracingPipeline) + add_subdirectory(72_GeometryInspector) # add new examples *before* NBL_GET_ALL_TARGETS invocation, it gathers recursively all targets created so far in this subdirectory NBL_GET_ALL_TARGETS(TARGETS) diff --git a/common/include/nbl/examples/geometry/CSimpleDebugRenderer.hpp b/common/include/nbl/examples/geometry/CSimpleDebugRenderer.hpp index 9a9e5c966..f38de81ca 100644 --- a/common/include/nbl/examples/geometry/CSimpleDebugRenderer.hpp +++ b/common/include/nbl/examples/geometry/CSimpleDebugRenderer.hpp @@ -365,6 +365,9 @@ class CSimpleDebugRenderer final : public core::IReferenceCounted inline const auto& getGeometries() const {return m_geoms;} inline auto& getGeometry(const uint32_t ix) {return m_geoms[ix];} + inline const auto& getInstances() const {return m_instances;} + inline auto& getInstance(const uint32_t ix) {return m_instances[ix];} + // inline void render(video::IGPUCommandBuffer* cmdbuf, const SViewParams& viewParams) const { From 096030db385727f794e5487fbf9478c2f9a201ab Mon Sep 17 00:00:00 2001 From: kevyuu Date: Mon, 29 Dec 2025 10:24:41 +0700 Subject: [PATCH 3/6] Change geometry_inspector example number from 72 to 73 --- {72_GeometryInspector => 73_GeometryInspector}/CMakeLists.txt | 0 .../include/common.hpp | 0 .../include/transform.hpp | 0 {72_GeometryInspector => 73_GeometryInspector}/main.cpp | 0 CMakeLists.txt | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename {72_GeometryInspector => 73_GeometryInspector}/CMakeLists.txt (100%) rename {72_GeometryInspector => 73_GeometryInspector}/include/common.hpp (100%) rename {72_GeometryInspector => 73_GeometryInspector}/include/transform.hpp (100%) rename {72_GeometryInspector => 73_GeometryInspector}/main.cpp (100%) diff --git a/72_GeometryInspector/CMakeLists.txt b/73_GeometryInspector/CMakeLists.txt similarity index 100% rename from 72_GeometryInspector/CMakeLists.txt rename to 73_GeometryInspector/CMakeLists.txt diff --git a/72_GeometryInspector/include/common.hpp b/73_GeometryInspector/include/common.hpp similarity index 100% rename from 72_GeometryInspector/include/common.hpp rename to 73_GeometryInspector/include/common.hpp diff --git a/72_GeometryInspector/include/transform.hpp b/73_GeometryInspector/include/transform.hpp similarity index 100% rename from 72_GeometryInspector/include/transform.hpp rename to 73_GeometryInspector/include/transform.hpp diff --git a/72_GeometryInspector/main.cpp b/73_GeometryInspector/main.cpp similarity index 100% rename from 72_GeometryInspector/main.cpp rename to 73_GeometryInspector/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fc61648b1..aae2400b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,7 @@ if(NBL_BUILD_EXAMPLES) add_subdirectory(70_FLIPFluids) add_subdirectory(71_RayTracingPipeline) add_subdirectory(72_GeometryInspector) - add_subdirectory(72_CooperativeBinarySearch) + add_subdirectory(73_CooperativeBinarySearch) # add new examples *before* NBL_GET_ALL_TARGETS invocation, it gathers recursively all targets created so far in this subdirectory NBL_GET_ALL_TARGETS(TARGETS) From 95e057ff89ae5cb3323f5dc1471f6502343ba1e2 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Mon, 29 Dec 2025 17:55:02 +0700 Subject: [PATCH 4/6] Fix example after merge --- 12_MeshLoaders/main.cpp | 17 ++++++++------- 73_GeometryInspector/CMakeLists.txt | 32 +---------------------------- 73_GeometryInspector/main.cpp | 4 ++-- CMakeLists.txt | 4 ++-- 4 files changed, 13 insertions(+), 44 deletions(-) diff --git a/12_MeshLoaders/main.cpp b/12_MeshLoaders/main.cpp index c588b0e61..7d4a59825 100644 --- a/12_MeshLoaders/main.cpp +++ b/12_MeshLoaders/main.cpp @@ -208,7 +208,6 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc drawParams.commandBuffer = cb; drawParams.cameraMat = viewProjMatrix; m_drawAABB->render(drawParams, drawFinished, m_drawBBMode == DBBM_OBB ? m_obbInstances : m_aabbInstances); ->>>>>>> master } #endif cb->endRenderPass(); @@ -480,13 +479,6 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc const auto transformed = hlsl::shapes::util::transform(promotedWorld,promoted); printAABB(transformed,"Transformed"); bound = hlsl::shapes::util::union_(transformed,bound); - const auto tmpWorld = hlsl::float32_t3x4(promotedWorld); - const auto world4x4 = float32_t4x4{ - tmpWorld[0], - tmpWorld[1], - tmpWorld[2], - float32_t4(0, 0, 0, 1) - }; #ifdef NBL_BUILD_DEBUG_DRAW @@ -495,12 +487,19 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc hlsl::float32_t3x4 aabbTransform = ext::debug_draw::DrawAABB::getTransformFromAABB(tmpAabb); const auto tmpWorld = hlsl::float32_t3x4(promotedWorld); + const auto world4x4 = float32_t4x4{ + tmpWorld[0], + tmpWorld[1], + tmpWorld[2], + float32_t4(0, 0, 0, 1) + }; + aabbInst.color = { 1,1,1,1 }; aabbInst.transform[0] = tmpWorld[0]; aabbInst.transform[1] = tmpWorld[1]; aabbInst.transform[2] = tmpWorld[2]; aabbInst.transform[3] = float32_t4(0, 0, 0, 1); - aabbInst.transform = math::linalg::promoted_mul(aabbInst.transform, instanceTransform); + aabbInst.transform = math::linalg::promoted_mul(aabbInst.transform, aabbTransform); auto& obbInst = m_obbInstances[i]; const auto& cpuGeom = geometries[i].get(); diff --git a/73_GeometryInspector/CMakeLists.txt b/73_GeometryInspector/CMakeLists.txt index 697399e91..57e32dd63 100644 --- a/73_GeometryInspector/CMakeLists.txt +++ b/73_GeometryInspector/CMakeLists.txt @@ -14,38 +14,8 @@ if(NBL_BUILD_IMGUI AND NBL_BUILD_DEBUG_DRAW) "${NBL_EXT_IMGUI_UI_LIB}" ) - if (NBL_BUILD_MITSUBA_LOADER) - list(APPEND NBL_INCLUDE_SERACH_DIRECTORIES - "${NBL_EXT_MITSUBA_LOADER_INCLUDE_DIRS}" - ) - list(APPEND NBL_LIBRARIES - "${NBL_EXT_MITSUBA_LOADER_LIB}" - ) - endif() - nbl_create_executable_project("" "" "${NBL_INCLUDE_SERACH_DIRECTORIES}" "${NBL_LIBRARIES}" "${NBL_EXECUTABLE_PROJECT_CREATION_PCH_TARGET}") - if(NBL_EMBED_BUILTIN_RESOURCES) - set(_BR_TARGET_ ${EXECUTABLE_NAME}_builtinResourceData) - set(RESOURCE_DIR "app_resources") - - get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) - get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) - get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) - - file(GLOB_RECURSE BUILTIN_RESOURCE_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}/*") - foreach(RES_FILE ${BUILTIN_RESOURCE_FILES}) - LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "${RES_FILE}") - endforeach() - - ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${RESOURCE_DIR}" "nbl::this_example::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") - - LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} ${_BR_TARGET_}) - endif() - - add_dependencies(${EXECUTABLE_NAME} ${NBL_EXT_DEBUG_DRAW_TARGET}) - target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${NBL_EXT_DEBUG_DRAW_TARGET}) - target_include_directories(${EXECUTABLE_NAME} PUBLIC $) - + target_link_libraries(${EXECUTABLE_NAME} PRIVATE Nabla::ext::DebugDraw) endif() diff --git a/73_GeometryInspector/main.cpp b/73_GeometryInspector/main.cpp index 5fe0421da..029fe5d08 100644 --- a/73_GeometryInspector/main.cpp +++ b/73_GeometryInspector/main.cpp @@ -410,14 +410,14 @@ class GeometryInspectorApp final : public MonoWindowApplication, public BuiltinR if (m_shouldDrawAABB) { const auto aabbTransform = ext::debug_draw::DrawAABB::getTransformFromAABB(meshInstance.aabb); - debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = hlsl::mul(world4x4, aabbTransform), .color = float32_t4(1, 1, 1, 1)}); + debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = math::linalg::promoted_mul(world4x4, aabbTransform), .color = float32_t4(1, 1, 1, 1)}); } if (m_shouldDrawOBB) { const auto obbTransform = ext::debug_draw::DrawAABB::getTransformFromOBB(meshInstance.obb); debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = hlsl::mul(world4x4, obbTransform), .color = float32_t4(0, 0, 1, 1)}); } - m_bbRenderer->render(cb, drawFinished, debugDrawInstances, viewProjMatrix); + m_bbRenderer->render({ cb, viewProjMatrix }, drawFinished, debugDrawInstances); cb->beginDebugMarker("Render ImGui"); const auto uiParams = m_ui.manager->getCreationParameters(); diff --git a/CMakeLists.txt b/CMakeLists.txt index aae2400b8..8543fc152 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,8 +90,8 @@ if(NBL_BUILD_EXAMPLES) add_subdirectory(70_FLIPFluids) add_subdirectory(71_RayTracingPipeline) - add_subdirectory(72_GeometryInspector) - add_subdirectory(73_CooperativeBinarySearch) + add_subdirectory(72_CooperativeBinarySearch) + add_subdirectory(73_GeometryInspector) # add new examples *before* NBL_GET_ALL_TARGETS invocation, it gathers recursively all targets created so far in this subdirectory NBL_GET_ALL_TARGETS(TARGETS) From ab85572234a02e87b071ebf1a42c682a7ae8dbe3 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Mon, 29 Dec 2025 19:27:22 +0700 Subject: [PATCH 5/6] Updated obbInst transform calculation to use math::linalg::promoted_mul --- 12_MeshLoaders/main.cpp | 8 ++------ 73_GeometryInspector/main.cpp | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/12_MeshLoaders/main.cpp b/12_MeshLoaders/main.cpp index 7d4a59825..a04ede3f4 100644 --- a/12_MeshLoaders/main.cpp +++ b/12_MeshLoaders/main.cpp @@ -495,11 +495,7 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc }; aabbInst.color = { 1,1,1,1 }; - aabbInst.transform[0] = tmpWorld[0]; - aabbInst.transform[1] = tmpWorld[1]; - aabbInst.transform[2] = tmpWorld[2]; - aabbInst.transform[3] = float32_t4(0, 0, 0, 1); - aabbInst.transform = math::linalg::promoted_mul(aabbInst.transform, aabbTransform); + aabbInst.transform = math::linalg::promoted_mul(world4x4, aabbTransform); auto& obbInst = m_obbInstances[i]; const auto& cpuGeom = geometries[i].get(); @@ -513,7 +509,7 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc }); obbInst.color = { 0, 0, 1, 1 }; const auto obbTransform = ext::debug_draw::DrawAABB::getTransformFromOBB(obb); - obbInst.transform = hlsl::mul(world4x4, obbTransform); + obbInst.transform = math::linalg::promoted_mul(world4x4, obbTransform); #endif } diff --git a/73_GeometryInspector/main.cpp b/73_GeometryInspector/main.cpp index 029fe5d08..0ffcb6fa7 100644 --- a/73_GeometryInspector/main.cpp +++ b/73_GeometryInspector/main.cpp @@ -415,7 +415,7 @@ class GeometryInspectorApp final : public MonoWindowApplication, public BuiltinR if (m_shouldDrawOBB) { const auto obbTransform = ext::debug_draw::DrawAABB::getTransformFromOBB(meshInstance.obb); - debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = hlsl::mul(world4x4, obbTransform), .color = float32_t4(0, 0, 1, 1)}); + debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = math::linalg::promoted_mul(world4x4, obbTransform), .color = float32_t4(0, 0, 1, 1)}); } m_bbRenderer->render({ cb, viewProjMatrix }, drawFinished, debugDrawInstances); From 0772b53bd6323a27fa39e601816cfc4be6da1cd5 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Thu, 8 Jan 2026 12:07:17 +0700 Subject: [PATCH 6/6] Fix example to remove CDrawAABB::getTransformFromOBB --- 12_MeshLoaders/main.cpp | 12 +++++------- 73_GeometryInspector/main.cpp | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/12_MeshLoaders/main.cpp b/12_MeshLoaders/main.cpp index a04ede3f4..57a2a50b6 100644 --- a/12_MeshLoaders/main.cpp +++ b/12_MeshLoaders/main.cpp @@ -499,17 +499,15 @@ class MeshLoadersApp final : public MonoWindowApplication, public BuiltinResourc auto& obbInst = m_obbInstances[i]; const auto& cpuGeom = geometries[i].get(); - const auto obb = CPolygonGeometryManipulator::calculateOBB({ - .fetch = [geo = cpuGeom, &world4x4](size_t vertex_i) { + const auto obb = CPolygonGeometryManipulator::calculateOBB( + cpuGeom->getPositionView().getElementCount(), + [geo = cpuGeom, &world4x4](size_t vertex_i) { hlsl::float32_t3 pt; geo->getPositionView().decodeElement(vertex_i, pt); return pt; - }, - .size = cpuGeom->getPositionView().getElementCount(), - }); + }); obbInst.color = { 0, 0, 1, 1 }; - const auto obbTransform = ext::debug_draw::DrawAABB::getTransformFromOBB(obb); - obbInst.transform = math::linalg::promoted_mul(world4x4, obbTransform); + obbInst.transform = math::linalg::promoted_mul(world4x4, obb.transform); #endif } diff --git a/73_GeometryInspector/main.cpp b/73_GeometryInspector/main.cpp index 0ffcb6fa7..8a487d707 100644 --- a/73_GeometryInspector/main.cpp +++ b/73_GeometryInspector/main.cpp @@ -414,8 +414,7 @@ class GeometryInspectorApp final : public MonoWindowApplication, public BuiltinR } if (m_shouldDrawOBB) { - const auto obbTransform = ext::debug_draw::DrawAABB::getTransformFromOBB(meshInstance.obb); - debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = math::linalg::promoted_mul(world4x4, obbTransform), .color = float32_t4(0, 0, 1, 1)}); + debugDrawInstances.push_back(ext::debug_draw::InstanceData{ .transform = math::linalg::promoted_mul(world4x4, meshInstance.obb.transform), .color = float32_t4(0, 0, 1, 1)}); } m_bbRenderer->render({ cb, viewProjMatrix }, drawFinished, debugDrawInstances); @@ -672,14 +671,13 @@ class GeometryInspectorApp final : public MonoWindowApplication, public BuiltinR bound = hlsl::shapes::util::union_(transformed,bound); const auto& cpuGeom = geometries[i].get(); - const auto obb = CPolygonGeometryManipulator::calculateOBB({ - .fetch = [geo = cpuGeom](size_t vertex_i) { + const auto obb = CPolygonGeometryManipulator::calculateOBB( + cpuGeom->getPositionView().getElementCount(), + [geo = cpuGeom](size_t vertex_i) { hlsl::float32_t3 pt; geo->getPositionView().decodeElement(vertex_i, pt); return pt; - }, - .size = cpuGeom->getPositionView().getElementCount(), - }); + }); m_meshInstances.push_back({ .name = std::format("Mesh {}", i), .aabb = aabb, .obb = obb }); }