diff --git a/cmake/recipes/external/imgui.cmake b/cmake/recipes/external/imgui.cmake index ab994c26..7e4fd843 100644 --- a/cmake/recipes/external/imgui.cmake +++ b/cmake/recipes/external/imgui.cmake @@ -32,7 +32,7 @@ include(FetchContent) FetchContent_Declare( imgui GIT_REPOSITORY https://github.com/adobe/imgui.git - GIT_TAG 3f1593b28346da2ad34edbab9432b93deb305f42 + GIT_TAG docking_v1.83 ) FetchContent_MakeAvailable(imgui) diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index ea82d234..11907cf2 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -35,6 +35,11 @@ if(LAGRANGE_KEEP_TRANSITION_CODE) target_compile_definitions(lagrange_core PUBLIC -DLA_KEEP_TRANSITION_CODE) endif() +option(LAGRANGE_DISABLE_FPE "Disable floating point exception code" OFF) +if(LAGRANGE_DISABLE_FPE) + target_compile_definitions(lagrange_core PRIVATE -DLA_DISABLE_FPE) +endif() + # 2. target sources file(GLOB_RECURSE INC_FILES "include/*.h") file(GLOB_RECURSE SRC_FILES "src/*.cpp") diff --git a/modules/core/include/lagrange/compute_vertex_normal.h b/modules/core/include/lagrange/compute_vertex_normal.h index ec1a1013..eb490beb 100644 --- a/modules/core/include/lagrange/compute_vertex_normal.h +++ b/modules/core/include/lagrange/compute_vertex_normal.h @@ -30,7 +30,8 @@ template void compute_vertex_normal( MeshType& mesh, const igl::PerVertexNormalsWeightingType weighting = - igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE) + igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE, + bool recompute_facet_normals = false) { static_assert(MeshTrait::is_mesh(), "Input type is not Mesh"); using Index = IndexOf; @@ -40,7 +41,7 @@ void compute_vertex_normal( throw std::runtime_error("Input mesh is not triangle mesh."); } - if (!mesh.has_facet_attribute("normal")) { + if (!mesh.has_facet_attribute("normal") || recompute_facet_normals) { compute_triangle_normal(mesh); LA_ASSERT(mesh.has_facet_attribute("normal")); } diff --git a/modules/core/src/fpe.cpp b/modules/core/src/fpe.cpp index cd9aea8b..6420dda6 100644 --- a/modules/core/src/fpe.cpp +++ b/modules/core/src/fpe.cpp @@ -9,7 +9,7 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ -#ifdef _WIN32 +#if defined(_WIN32) || defined(LA_DISABLE_FPE) namespace { @@ -29,11 +29,11 @@ inline int fedisableexcept(unsigned int excepts) } // namespace -#elif __linux__ +#elif defined(__linux__) #include -#elif __APPLE__ +#elif defined(__APPLE__) #include @@ -41,6 +41,7 @@ inline int fedisableexcept(unsigned int excepts) // http://www-personal.umich.edu/~williams/archive/computation/fe-handling-example.c namespace { + inline int feenableexcept(unsigned int excepts) { static fenv_t fenv; diff --git a/modules/core/src/predicates.cpp b/modules/core/src/predicates.cpp index bd2a1ac7..37e0faff 100644 --- a/modules/core/src/predicates.cpp +++ b/modules/core/src/predicates.cpp @@ -4251,6 +4251,6 @@ REAL insphere(const REAL* pa, const REAL* pb, const REAL* pc, const REAL* pd, co } } // namespace lagrange -#if defined(__GNUC__) +#if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif diff --git a/modules/ui/include/lagrange/ui/components/GLMesh.h b/modules/ui/include/lagrange/ui/components/GLMesh.h index 97c3752f..a12ab5e4 100644 --- a/modules/ui/include/lagrange/ui/components/GLMesh.h +++ b/modules/ui/include/lagrange/ui/components/GLMesh.h @@ -38,13 +38,6 @@ struct DefaultShaderIndicesNames constexpr static const entt::id_type TriangleIndices = entt::hashed_string{"_triangle_indices"}; }; -struct GLOcclusionQuery -{ - GLuint id; - GLenum type; - GLint last_result; -}; - struct GLMesh { std::shared_ptr get_attribute_buffer(entt::id_type id) const diff --git a/modules/ui/include/lagrange/ui/utils/mesh.impl.h b/modules/ui/include/lagrange/ui/utils/mesh.impl.h index 7a22b0de..09503d27 100644 --- a/modules/ui/include/lagrange/ui/utils/mesh.impl.h +++ b/modules/ui/include/lagrange/ui/utils/mesh.impl.h @@ -17,11 +17,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include @@ -161,6 +161,8 @@ template AABB get_mesh_bounds(const MeshBase* mesh_base) { const auto& mesh = reinterpret_cast(*mesh_base); + if (mesh.get_num_vertices() == 0) return AABB(); + const auto& V = mesh.get_vertices(); return AABB( V.colwise().minCoeff().template cast(), @@ -248,7 +250,9 @@ void ensure_is_selected_attribute(MeshBase* d) if (mesh.is_edge_data_initialized_new() && !mesh.has_edge_attribute_new(attrib_name)) { mesh.add_edge_attribute_new(attrib_name); - mesh.import_edge_attribute_new(attrib_name, AttributeArray::Zero(mesh.get_num_edges_new(), 1)); + mesh.import_edge_attribute_new( + attrib_name, + AttributeArray::Zero(mesh.get_num_edges_new(), 1)); } if (!mesh.has_vertex_attribute(attrib_name)) { @@ -552,7 +556,7 @@ void select_vertices_in_frustum( using Index = typename MeshType::Index; const auto num_vertices = mesh.get_num_vertices(); - const auto& vertices = mesh.get_vertices(); + const auto& vertices = mesh.get_vertices(); AttributeArray attr; @@ -891,7 +895,6 @@ void select_facets( } - template void filter_closest_vertex( MeshBase* mesh_base, @@ -900,7 +903,6 @@ void filter_closest_vertex( const Camera& camera, const Eigen::Vector2i& viewport_pos) { - auto& mesh = reinterpret_cast(*mesh_base); using AttribArray = typename MeshType::AttributeArray; @@ -936,7 +938,8 @@ void filter_closest_vertex( if (vertex_attrib(vi) == 0.0f) continue; - const auto proj = camera.project_with_depth(vertices.row(vi).template cast()); + const auto proj = + camera.project_with_depth(vertices.row(vi).template cast()); const auto diff = (Eigen::Vector2i(int(proj.x()), int(camera.get_window_height() - proj.y())) - viewport_pos) @@ -984,7 +987,6 @@ void filter_closest_vertex( const auto value = (sel_behavior != SelectionBehavior::ERASE) ? Scalar(1) : Scalar(0); if (final_buffer.min_vi != lagrange::INVALID()) { - vertex_attrib(final_buffer.min_vi, 0) = value; for (auto equiv_vi : final_buffer.equivalence) { vertex_attrib(equiv_vi, 0) = value; @@ -1038,8 +1040,7 @@ void register_mesh_type(const std::string& display_name = entt::type_id().template func<&detail::get_mesh_attribute_range>( "get_mesh_attribute_range"_hs); - entt::meta().template func<&detail::get_mesh_bounds>( - "get_mesh_bounds"_hs); + entt::meta().template func<&detail::get_mesh_bounds>("get_mesh_bounds"_hs); // Ensure attribs @@ -1112,11 +1113,9 @@ void register_mesh_type(const std::string& display_name = entt::type_id().template func<&detail::select_vertices_by_color>( "select_vertices_by_color"_hs); - entt::meta().template func<&detail::select_facets>( - "select_facets"_hs); + entt::meta().template func<&detail::select_facets>("select_facets"_hs); entt::meta().template func<&detail::filter_closest_vertex>( "filter_closest_vertex"_hs); - } diff --git a/modules/ui/src/Viewer.cpp b/modules/ui/src/Viewer.cpp index 0dc5a3fe..2440562c 100644 --- a/modules/ui/src/Viewer.cpp +++ b/modules/ui/src/Viewer.cpp @@ -397,6 +397,9 @@ bool Viewer::run(const std::function& main_loop) m_systems.run(Systems::Stage::Interface, registry()); + if (main_loop) { + if (!main_loop(registry())) glfwSetWindowShouldClose(m_window, true); + } show_last_shader_error(); end_dockspace(); @@ -405,11 +408,6 @@ bool Viewer::run(const std::function& main_loop) end_imgui_frame(); - if (main_loop) { - if (!main_loop(registry())) glfwSetWindowShouldClose(m_window, true); - } - - m_systems.run(Systems::Stage::Simulation, registry()); diff --git a/modules/ui/src/default_components.cpp b/modules/ui/src/default_components.cpp index fab29109..bf3815cc 100644 --- a/modules/ui/src/default_components.cpp +++ b/modules/ui/src/default_components.cpp @@ -79,10 +79,31 @@ void show_mesh_geometry(Registry* rptr, Entity orig_e) } ImGui::Text("MeshType: %s", typeinfo.name().data()); + const size_t num_vertices = get_num_vertices(mesh_data); + if (num_vertices == 0) ImGui::PushStyleColor(ImGuiCol_Text, ImGui::Spectrum::RED400); + ImGui::Text("Vertices: %zu", num_vertices); + if (num_vertices == 0) ImGui::PopStyleColor(); + + const size_t num_facets = get_num_facets(mesh_data); + if (num_facets == 0) ImGui::PushStyleColor(ImGuiCol_Text, ImGui::Spectrum::RED400); + ImGui::Text("Facets: %zu", num_facets); + if (num_facets == 0) ImGui::PopStyleColor(); + int sub_id = m.submesh_index; + if (m.submesh_index == entt::null) { + ImGui::Text("Submesh/Material ID not set"); + ImGui::SameLine(); - if (ImGui::InputInt("Submesh/Material ID", &sub_id)) { - if (sub_id >= 0) m.submesh_index = entt::id_type(sub_id); + if (ImGui::Button("Set")) { + m.submesh_index = 0; + } + } else { + if (ImGui::InputInt("Submesh/Material ID", &sub_id) && sub_id >= 0) { + m.submesh_index = entt::id_type(sub_id); + } + if (ImGui::Button("Unset Submesh/Material ID")) { + m.submesh_index = entt::null; + } } if (has_accelerated_picking(r, m.entity)) { @@ -1034,12 +1055,18 @@ void show_bounds(Registry* rptr, Entity e) const auto& bounds = rptr->get(e); const auto show_bb = [](const AABB& bb) { - Eigen::Vector3f tmp_min = bb.min(), tmp_max = bb.max(), tmp_extent = bb.diagonal(), - tmp_center = bb.center(); - ImGui::DragFloat3("Min", tmp_min.data()); - ImGui::DragFloat3("Max", tmp_max.data()); - ImGui::DragFloat3("Extent", tmp_extent.data()); - ImGui::DragFloat3("Center", tmp_center.data()); + if (bb.isEmpty()) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::Spectrum::RED400); + ImGui::Text("Empty"); + ImGui::PopStyleColor(); + } else { + Eigen::Vector3f tmp_min = bb.min(), tmp_max = bb.max(), tmp_extent = bb.diagonal(), + tmp_center = bb.center(); + ImGui::DragFloat3("Min", tmp_min.data()); + ImGui::DragFloat3("Max", tmp_max.data()); + ImGui::DragFloat3("Extent", tmp_extent.data()); + ImGui::DragFloat3("Center", tmp_center.data()); + } }; ImGui::Text("Local"); diff --git a/modules/ui/src/panels/ViewportPanel.cpp b/modules/ui/src/panels/ViewportPanel.cpp index 4b18fe9b..604ccf40 100644 --- a/modules/ui/src/panels/ViewportPanel.cpp +++ b/modules/ui/src/panels/ViewportPanel.cpp @@ -234,6 +234,13 @@ void draw_framebuffer_popup(Registry& registry, ViewportPanel& data) ImGui::InputInt("Width", &v.width); ImGui::InputInt("Height", &v.height); ImGui::Checkbox("Auto near/far", &v.auto_nearfar); + + if (v.auto_nearfar) { + auto n = v.computed_camera.get_near(); + auto f = v.computed_camera.get_far(); + ImGui::InputFloat("Computed near", &n); + ImGui::InputFloat("Computed far", &f); + } } if (ImGui::CollapsingHeader("Layer visibility")) { diff --git a/modules/ui/src/systems/camera_systems.cpp b/modules/ui/src/systems/camera_systems.cpp index bcbadd80..1553bbb2 100644 --- a/modules/ui/src/systems/camera_systems.cpp +++ b/modules/ui/src/systems/camera_systems.cpp @@ -203,8 +203,8 @@ void camera_focusfit_system(Registry& registry) } else { const float dist = (radius * 2.0f) / std::tan(cam.get_fov() / 2.0f); const auto dir = cam.get_direction().normalized().eval(); - const auto new_pos = cam.get_lookat() - dir * dist; - const auto old_pos = cam.get_position(); + const Eigen::Vector3f new_pos = cam.get_lookat() - dir * dist; + const Eigen::Vector3f old_pos = cam.get_position(); fit_reached = (old_pos - new_pos).squaredNorm() < eps; cam.set_position(t * new_pos + (1.0f - t) * cam.get_position()); @@ -240,4 +240,4 @@ void camera_focusfit_system(Registry& registry) } } // namespace ui -} // namespace lagrange \ No newline at end of file +} // namespace lagrange diff --git a/modules/ui/src/systems/render_background.cpp b/modules/ui/src/systems/render_background.cpp index 5f26670e..f1f42e72 100644 --- a/modules/ui/src/systems/render_background.cpp +++ b/modules/ui/src/systems/render_background.cpp @@ -29,7 +29,6 @@ struct SkyboxCubeVertexData void render_background(Registry& r) { - auto& rctx = get_render_context(r); auto& viewport = get_render_context_viewport(r); // Grab and bind fbo if set diff --git a/modules/ui/src/systems/render_geometry.cpp b/modules/ui/src/systems/render_geometry.cpp index 4d00f476..c3a30ef8 100644 --- a/modules/ui/src/systems/render_geometry.cpp +++ b/modules/ui/src/systems/render_geometry.cpp @@ -413,8 +413,6 @@ void render_gl_render_queue(Registry& r) auto& cam = viewport.computed_camera; const auto P = cam.get_perspective(); const auto V = cam.get_view(); - const auto PV = cam.get_PV(); - const auto PVinv = cam.get_PV().inverse().eval(); const auto cameraPos = cam.get_position(); const auto screen_size = cam.get_window_size(); diff --git a/modules/ui/src/systems/render_viewports.cpp b/modules/ui/src/systems/render_viewports.cpp index c902e5ae..7eeccc24 100644 --- a/modules/ui/src/systems/render_viewports.cpp +++ b/modules/ui/src/systems/render_viewports.cpp @@ -47,11 +47,11 @@ void adjust_camera(Registry& registry, ViewportComponent& viewport, Camera& cam) auto near_plane = cam.get_near(); auto far_plane = cam.get_far(); - if (furthest != std::numeric_limits::min()) { + if (furthest > 0) { far_plane = 1.01f * furthest; } - if (nearest != std::numeric_limits::max()) { - near_plane = std::max(0.99f * nearest, near_plane); + if (nearest > 0) { + near_plane = 0.99f * nearest; } cam.set_planes(near_plane, far_plane); } diff --git a/modules/ui/src/systems/update_scene_bounds.cpp b/modules/ui/src/systems/update_scene_bounds.cpp index 507111b8..9ff1c8eb 100644 --- a/modules/ui/src/systems/update_scene_bounds.cpp +++ b/modules/ui/src/systems/update_scene_bounds.cpp @@ -25,7 +25,9 @@ void update_scene_bounds_system(Registry& registry) auto view = registry.view(); for (auto e : view) { auto& b = view.get(e); - b.global = b.local.transformed(view.get(e).global); + if (!b.local.isEmpty()) { + b.global = b.local.transformed(view.get(e).global); + } } } @@ -87,22 +89,14 @@ void update_scene_bounds_system(Registry& registry) } - // If parent does not have bounds component yet if (!registry.has(parent)) { auto& parent_bounds = registry.emplace(parent); - if (registry.has(parent)) { - const auto& t = registry.get(parent); - const auto local_pos = t.local.matrix().block<3, 1>(0, 3).eval(); - const auto global_pos = t.global.matrix().block<3, 1>(0, 3).eval(); - parent_bounds.local = AABB(local_pos, local_pos); - parent_bounds.global = AABB(global_pos, global_pos); - parent_bounds.bvh_node = parent_bounds.global; //Init with empty box at global position - } else { - parent_bounds.local = AABB(); - parent_bounds.global = AABB(); - parent_bounds.bvh_node = bvh_node; //Init with child - } + // Empty bounds + parent_bounds.local = AABB(); + parent_bounds.global = AABB(); + // Init with child's bvh + parent_bounds.bvh_node = bvh_node; } // Update parent and mark as dirty diff --git a/modules/ui/src/utils/bounds.cpp b/modules/ui/src/utils/bounds.cpp index 7ea2936c..929696e3 100644 --- a/modules/ui/src/utils/bounds.cpp +++ b/modules/ui/src/utils/bounds.cpp @@ -19,7 +19,11 @@ namespace lagrange { namespace ui { - +/** + * Returns the least distance between `from` and any point within any bounding box. + * Returns 0 if `from` lies within a bounding box. + * Returns -1 if no bounds exist. + */ float get_nearest_bounds_distance( const Registry& ctx, const Eigen::Vector3f& from, @@ -33,34 +37,52 @@ float get_nearest_bounds_distance( const auto& box = bounds.global; if (box.isEmpty()) return; const auto dst = box.squaredExteriorDistance(from); - if (dst == 0.0f) return; min_dst = std::min(dst, min_dst); }); - min_dst = std::sqrt(min_dst); + if (min_dst != std::numeric_limits::max()) { + return std::sqrt(min_dst); + } - return min_dst; + return -1; } +/** + * Returns the greatest distance between `from` and any point within any bounding box. + * Returns -1 if no bounds exist. + */ float get_furthest_bounds_distance( const Registry& ctx, const Eigen::Vector3f& from, const Layer& visible, const Layer& hidden) { - float max_dst = std::numeric_limits::min(); + float max_dst = -1; ctx.view().each([&](Entity e, const Bounds& bounds) { if (!ui::is_visible_in(ctx, e, visible, hidden)) return; const auto& box = bounds.global; if (box.isEmpty()) return; - Eigen::Vector3f dmin = (from - box.min()).cwiseAbs(); - Eigen::Vector3f dmax = (from - box.max()).cwiseAbs(); - Eigen::Vector3f dmaxmax = dmin.cwiseMax(dmax); - max_dst = std::max(max_dst, dmaxmax.maxCoeff()); + + const auto center = (box.min() + box.max()) / 2; + float dist = 0; + for (auto i = 0; i < 3; i++) { + if (from[i] < center[i]) { + const float tmp = from[i] - box.max()[i]; + dist += tmp * tmp; + } else { + const float tmp = from[i] - box.min()[i]; + dist += tmp * tmp; + } + } + max_dst = std::max(max_dst, dist); }); - return max_dst; + if (max_dst >= 0) { + return std::sqrt(max_dst); + } + + return -1; } AABB get_scene_bounding_box(const Registry& registry) diff --git a/modules/ui/src/utils/lights.cpp b/modules/ui/src/utils/lights.cpp index 2c886921..4f8cd2dd 100644 --- a/modules/ui/src/utils/lights.cpp +++ b/modules/ui/src/utils/lights.cpp @@ -90,7 +90,7 @@ Entity add_directional_light( auto viz = ui::show_mesh(r, m, DefaultShaders::TrianglesToLines); ui::get_material(r, viz)->set_int(RasterizerOptions::Pass, 10); - auto dims = (Eigen::Vector3f::Ones() + 100 * initial_dir).normalized() * 1.0f; + Eigen::Vector3f dims = (Eigen::Vector3f::Ones() + 100 * initial_dir).normalized() * 1.0f; ui::set_transform(r, viz, Eigen::Scaling(dims)); ui::set_parent(r, viz, e); ui::add_to_layer(r, viz, DefaultLayers::NoShadow); @@ -139,7 +139,7 @@ lagrange::ui::Entity add_spot_light( DefaultShaders::TrianglesToLines); ui::get_material(r, viz)->set_int(RasterizerOptions::Pass, 10); - auto dims = (Eigen::Vector3f::Ones() + 2 * initial_dir).normalized() * 1.0f; + Eigen::Vector3f dims = (Eigen::Vector3f::Ones() + 2 * initial_dir).normalized() * 1.0f; ui::set_transform(r, viz, Eigen::Scaling(dims)); ui::set_parent(r, viz, e); ui::add_to_layer(r, viz, DefaultLayers::NoShadow); diff --git a/modules/ui/src/utils/render.cpp b/modules/ui/src/utils/render.cpp index ea7c3f0b..eb0c5a33 100644 --- a/modules/ui/src/utils/render.cpp +++ b/modules/ui/src/utils/render.cpp @@ -347,11 +347,7 @@ void update_vao(VertexData& vd) continue; } - auto& vbo = buffer->vbo(); - - if (vbo.size == 0) { - lagrange::logger().warn("VBO at location {} is empty", i); - } + const auto& vbo = buffer->vbo(); LA_ASSERT( vbo.target == GL_ARRAY_BUFFER, @@ -377,7 +373,7 @@ void update_vao(VertexData& vd) // Bind index array if (vd.index_buffer) { - auto& vbo = vd.index_buffer->vbo(); + const auto& vbo = vd.index_buffer->vbo(); LA_ASSERT( vbo.target == GL_ELEMENT_ARRAY_BUFFER, diff --git a/modules/ui/tests/test_bounds.cpp b/modules/ui/tests/test_bounds.cpp new file mode 100644 index 00000000..3ee983c5 --- /dev/null +++ b/modules/ui/tests/test_bounds.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +#include +#include + +namespace ui = lagrange::ui; + +TEST_CASE("get_nearest_bounds_distance", "[ui][bounds]") +{ + auto check_nearest_bounds_distance = []( + const ui::Registry& r, + const Eigen::Vector3f& from, + float expected_distance) + { + ui::Layer visible_layers(true); + ui::Layer hidden_layers(false); + float nearest = ui::get_nearest_bounds_distance(r, from, visible_layers, hidden_layers); + REQUIRE(nearest == Approx(expected_distance)); + }; + + SECTION("no bounds") + { + ui::Registry registry_without_bounds; + const Eigen::Vector3f from(1, 1, 1); + check_nearest_bounds_distance(registry_without_bounds, from, -1); + } + + ui::Registry registry_with_bounds; + ui::Entity e = registry_with_bounds.create(); + Eigen::Vector3f min(0, 0, 0); + Eigen::Vector3f max(10, 10, 10); + ui::AABB bb(Eigen::AlignedBox3f(min, max)); + registry_with_bounds.emplace(e, ui::Bounds{bb, bb, bb}); + + SECTION("on min corner") + { + const Eigen::Vector3f from(0, 0, 0); + check_nearest_bounds_distance(registry_with_bounds, from, 0); + } + + SECTION("on max corner") + { + const Eigen::Vector3f from(10, 10, 10); + check_nearest_bounds_distance(registry_with_bounds, from, 0); + } + + SECTION("inside bounds near min corner") + { + const Eigen::Vector3f from(1, 1, 1); + check_nearest_bounds_distance(registry_with_bounds, from, 0); + } + + SECTION("inside bounds near max corner") + { + const Eigen::Vector3f from(9, 9, 9); + check_nearest_bounds_distance(registry_with_bounds, from, 0); + } + + SECTION("outside bounds near min corner") + { + const Eigen::Vector3f from(-1, -1, -1); + check_nearest_bounds_distance(registry_with_bounds, from, std::sqrt(3)); + } + + SECTION("outside bounds near max corner") + { + const Eigen::Vector3f from(11, 11, 11); + check_nearest_bounds_distance(registry_with_bounds, from, std::sqrt(3)); + } +} + +TEST_CASE("get_furthest_bounds_distance", "[ui][bounds]") +{ + auto check_furthest_bounds_distance = []( + const ui::Registry& r, + const Eigen::Vector3f& from, + float expected_distance) + { + ui::Layer visible_layers(true); + ui::Layer hidden_layers(false); + float furthest = ui::get_furthest_bounds_distance(r, from, visible_layers, hidden_layers); + REQUIRE(furthest == Approx(expected_distance)); + }; + + SECTION("no bounds") + { + ui::Registry registry_without_bounds; + check_furthest_bounds_distance(registry_without_bounds, Eigen::Vector3f(1, 1, 1), -1); + } + + ui::Registry registry_with_bounds; + ui::Entity e = registry_with_bounds.create(); + Eigen::Vector3f min(0, 0, 0); + Eigen::Vector3f max(10, 10, 10); + ui::AABB bb(Eigen::AlignedBox3f(min, max)); + registry_with_bounds.emplace(e, ui::Bounds{bb, bb, bb}); + + SECTION("on min corner") + { + const Eigen::Vector3f from(0, 0, 0); + check_furthest_bounds_distance(registry_with_bounds, from, std::sqrt(300)); + } + + SECTION("on max corner") + { + const Eigen::Vector3f from(10, 10, 10); + check_furthest_bounds_distance(registry_with_bounds, from, std::sqrt(300)); + } + + SECTION("inside bounds near min corner") + { + const Eigen::Vector3f from(1, 1, 1); + check_furthest_bounds_distance(registry_with_bounds, from, std::sqrt(243)); + } + + SECTION("inside bounds near max corner") + { + const Eigen::Vector3f from(9, 9, 9); + check_furthest_bounds_distance(registry_with_bounds, from, std::sqrt(243)); + } + + SECTION("outside bounds near min corner") + { + const Eigen::Vector3f from(-1, -1, -1); + check_furthest_bounds_distance(registry_with_bounds, from, std::sqrt(363)); + } + + SECTION("outside bounds near max corner") + { + const Eigen::Vector3f from(11, 11, 11); + check_furthest_bounds_distance(registry_with_bounds, from, std::sqrt(363)); + } +}