From 768d25f4abf503264a889c2d288c06db99569d7e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 29 Dec 2024 14:04:57 -0500 Subject: [PATCH 1/4] exclude widgets with default mouse interaction --- src/polyscope.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/polyscope.cpp b/src/polyscope.cpp index 575f8e2b..cab3c6b6 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -319,18 +319,20 @@ void processInputEvents() { } bool widgetCapturedMouse = false; - for (WeakHandle wHandle : state::widgets) { - if (wHandle.isValid()) { - Widget& w = wHandle.get(); - widgetCapturedMouse = w.interact(); - if (widgetCapturedMouse) { - break; - } - } - } // Handle scroll events for 3D view if (state::doDefaultMouseInteraction) { + + for (WeakHandle wHandle : state::widgets) { + if (wHandle.isValid()) { + Widget& w = wHandle.get(); + widgetCapturedMouse = w.interact(); + if (widgetCapturedMouse) { + break; + } + } + } + if (!io.WantCaptureMouse && !widgetCapturedMouse) { double xoffset = io.MouseWheelH; double yoffset = io.MouseWheel; From 210b6dac0ee84372c2d5b97832c5a492e3672fe3 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 29 Dec 2024 19:44:56 -0500 Subject: [PATCH 2/4] fix centering of messages --- src/messages.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/messages.cpp b/src/messages.cpp index e556f88c..093689b4 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -106,8 +106,10 @@ void buildWarningUI(std::string warningBaseString, std::string warningDetailStri std::max(warningBaseTextSize.y, std::max(warningDetailTextSize.y, warningRepeatTextSize.y))); ImVec2 warningModalSize( std::max(view::windowWidth / 5.0f, std::min(warningMaxTextSize.x + 50, view::windowWidth / 2.0f)), 0); + ImVec2 warningModalPos((view::windowWidth - warningModalSize.x) / 2, view::windowHeight / 3); ImGui::SetNextWindowSize(warningModalSize); + ImGui::SetNextWindowPos(warningModalPos, ImGuiCond_Always); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(190. / 255., 166. / 255., 0, 1.0)); if (ImGui::BeginPopupModal("WARNING", NULL, ImGuiWindowFlags_NoMove)) { From 69cf6ed4f9c0271ce7dc5d3aea0abd8e7655b5ac Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 29 Dec 2024 19:47:37 -0500 Subject: [PATCH 3/4] check & warn for inf/nan values in input --- include/polyscope/camera_parameters.h | 1 + include/polyscope/check_invalid_values.h | 28 +++++++ include/polyscope/color_quantity.ipp | 4 +- include/polyscope/numeric_helpers.h | 84 +++++++++++++++++++ include/polyscope/options.h | 3 + .../polyscope/parameterization_quantity.ipp | 5 +- include/polyscope/render/managed_buffer.h | 2 + include/polyscope/scalar_quantity.ipp | 3 + include/polyscope/vector_quantity.ipp | 5 +- src/CMakeLists.txt | 2 + src/camera_parameters.cpp | 16 ++++ src/camera_view.cpp | 7 ++ src/curve_network.cpp | 1 + src/options.cpp | 1 + src/point_cloud.cpp | 1 + src/render/managed_buffer.cpp | 6 ++ src/surface_mesh.cpp | 2 + src/volume_mesh.cpp | 1 + 18 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 include/polyscope/check_invalid_values.h create mode 100644 include/polyscope/numeric_helpers.h diff --git a/include/polyscope/camera_parameters.h b/include/polyscope/camera_parameters.h index 90ac1d7b..55447340 100644 --- a/include/polyscope/camera_parameters.h +++ b/include/polyscope/camera_parameters.h @@ -87,6 +87,7 @@ class CameraParameters { // create/test 'invalid' params static CameraParameters createInvalid(); bool isValid() const; + bool isfinite() const; // The intrinsic & extrinsics parameters that define the camera CameraIntrinsics intrinsics; diff --git a/include/polyscope/check_invalid_values.h b/include/polyscope/check_invalid_values.h new file mode 100644 index 00000000..4f80ff9f --- /dev/null +++ b/include/polyscope/check_invalid_values.h @@ -0,0 +1,28 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace polyscope { + +template +void checkInvalidValues(std::string name, const std::vector& data) { + if (options::warnForInvalidValues) { + for (const T& val : data) { + if (!allComponentsFinite(val)) { + warning("Invalid +-inf or NaN values detected.\n(set warnForInvalidValues=false to disable this warning)", + "In buffer: " + name); + break; + } + } + } +} + +} // namespace polyscope \ No newline at end of file diff --git a/include/polyscope/color_quantity.ipp b/include/polyscope/color_quantity.ipp index cd20821b..3a14a9b3 100644 --- a/include/polyscope/color_quantity.ipp +++ b/include/polyscope/color_quantity.ipp @@ -4,7 +4,9 @@ namespace polyscope { template ColorQuantity::ColorQuantity(QuantityT& quantity_, const std::vector& colors_) - : quantity(quantity_), colors(&quantity, quantity.uniquePrefix() + "colors", colorsData), colorsData(colors_) {} + : quantity(quantity_), colors(&quantity, quantity.uniquePrefix() + "colors", colorsData), colorsData(colors_) { + colors.checkInvalidValues(); +} template void ColorQuantity::buildColorUI() {} diff --git a/include/polyscope/numeric_helpers.h b/include/polyscope/numeric_helpers.h new file mode 100644 index 00000000..f0a7559a --- /dev/null +++ b/include/polyscope/numeric_helpers.h @@ -0,0 +1,84 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include +#include +#include +#include + +namespace polyscope { + + +// Base case: call the scalar version +template +bool allComponentsFinite(const T& x) { + return std::isfinite(x); +} + +template <> +inline bool allComponentsFinite(const glm::vec2& x) { + return glm::all(glm::isfinite(x)); +} +template <> +inline bool allComponentsFinite(const glm::vec3& x) { + return glm::all(glm::isfinite(x)); +} +template <> +inline bool allComponentsFinite(const glm::vec4& x) { + return glm::all(glm::isfinite(x)); +} + +template <> +inline bool allComponentsFinite(const glm::uvec2& x) { + return true; +} +template <> +inline bool allComponentsFinite(const glm::uvec3& x) { + return true; +} +template <> +inline bool allComponentsFinite(const glm::uvec4& x) { + return true; +} + +template <> +inline bool allComponentsFinite(const glm::mat2x2& x) { + for (size_t i = 0; i < 2; i++) + if (!allComponentsFinite(glm::row(x, i))) return false; + return true; +} +template <> +inline bool allComponentsFinite(const glm::mat3x3& x) { + for (size_t i = 0; i < 3; i++) + if (!allComponentsFinite(glm::row(x, i))) return false; + return true; +} +template <> +inline bool allComponentsFinite(const glm::mat4x4& x) { + for (size_t i = 0; i < 4; i++) + if (!allComponentsFinite(glm::row(x, i))) return false; + return true; +} + +template <> +inline bool allComponentsFinite>(const std::array& x) { + for (size_t i = 0; i < x.size(); i++) + if (!glm::all(glm::isfinite(x[i]))) return false; + return true; +} +template <> +inline bool allComponentsFinite>(const std::array& x) { + for (size_t i = 0; i < x.size(); i++) + if (!glm::all(glm::isfinite(x[i]))) return false; + return true; +} +template <> +inline bool allComponentsFinite>(const std::array& x) { + for (size_t i = 0; i < x.size(); i++) + if (!glm::all(glm::isfinite(x[i]))) return false; + return true; +} + + +} // namespace polyscope \ No newline at end of file diff --git a/include/polyscope/options.h b/include/polyscope/options.h index 356412ea..7ac28ffb 100644 --- a/include/polyscope/options.h +++ b/include/polyscope/options.h @@ -66,6 +66,9 @@ extern bool giveFocusOnShow; // If true, hide the polyscope window when a show() command finishes (default: true) extern bool hideWindowAfterShow; +// Give warnings for inf/nan values +extern bool warnForInvalidValues; + // === Scene options // Behavior of the ground plane diff --git a/include/polyscope/parameterization_quantity.ipp b/include/polyscope/parameterization_quantity.ipp index 3054f078..f7254e12 100644 --- a/include/polyscope/parameterization_quantity.ipp +++ b/include/polyscope/parameterization_quantity.ipp @@ -53,8 +53,9 @@ ParameterizationQuantity::ParameterizationQuantity(QuantityT& quantit gridBackgroundColor(quantity.uniquePrefix() + "#gridBackgroundColor", render::RGB_PINK), altDarkness(quantity.uniquePrefix() + "#altDarkness", 0.5), cMap(quantity.uniquePrefix() + "#cMap", "phase") - -{} +{ + coords.checkInvalidValues(); +} template diff --git a/include/polyscope/render/managed_buffer.h b/include/polyscope/render/managed_buffer.h index 1b1d656e..ccaefb87 100644 --- a/include/polyscope/render/managed_buffer.h +++ b/include/polyscope/render/managed_buffer.h @@ -89,6 +89,8 @@ class ManagedBuffer : public virtual WeakReferrable { bool dataGetsComputed; // if true, the value gets computed on-demand by calling computeFunc() std::function computeFunc; // (optional) callback which populates the `data` buffer + // sanity check helper + void checkInvalidValues(); // mark as texture, set size void setTextureSize(uint32_t sizeX); diff --git a/include/polyscope/scalar_quantity.ipp b/include/polyscope/scalar_quantity.ipp index 0c5fba9d..374e7064 100644 --- a/include/polyscope/scalar_quantity.ipp +++ b/include/polyscope/scalar_quantity.ipp @@ -1,7 +1,9 @@ // Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run #include "imgui.h" +#include "polyscope/check_invalid_values.h" #include "polyscope/utilities.h" + namespace polyscope { template @@ -17,6 +19,7 @@ ScalarQuantity::ScalarQuantity(QuantityT& quantity_, const std::vecto isolineDarkness(quantity.uniquePrefix() + "isolineDarkness", 0.7) { + values.checkInvalidValues(); hist.updateColormap(cMap.get()); hist.buildHistogram(values.data); diff --git a/include/polyscope/vector_quantity.ipp b/include/polyscope/vector_quantity.ipp index faae7ae5..e415123d 100644 --- a/include/polyscope/vector_quantity.ipp +++ b/include/polyscope/vector_quantity.ipp @@ -1,6 +1,5 @@ // Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run - #include "polyscope/standardize_data_array.h" namespace polyscope { @@ -125,6 +124,7 @@ VectorQuantity::VectorQuantity(QuantityT& quantity_, const std::vecto : VectorQuantityBase(quantity_, vectorType_), vectors(&quantity_, quantity_.uniquePrefix() + "#values", vectorsData), vectorRoots(vectorRoots_), vectorsData(vectors_) { + vectors.checkInvalidValues(); this->updateMaxLength(); } @@ -236,6 +236,9 @@ TangentVectorQuantity::TangentVectorQuantity(QuantityT& quantity_, tangentBasisY(&quantity_, quantity_.uniquePrefix() + "#basisY", tangentBasisYData), vectorRoots(vectorRoots_), tangentVectorsData(tangentVectors_), tangentBasisXData(tangentBasisX_), tangentBasisYData(tangentBasisY_), nSym(nSym_) { + tangentVectors.checkInvalidValues(); + tangentBasisX.checkInvalidValues(); + tangentBasisY.checkInvalidValues(); this->updateMaxLength(); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 826051d9..f074ec4c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -266,6 +266,7 @@ SET(HEADERS ${INCLUDE_ROOT}/camera_parameters.ipp ${INCLUDE_ROOT}/camera_view.h ${INCLUDE_ROOT}/camera_view.ipp + ${INCLUDE_ROOT}/check_invalid_values.h ${INCLUDE_ROOT}/color_management.h ${INCLUDE_ROOT}/color_image_quantity.h ${INCLUDE_ROOT}/color_render_image_quantity.h @@ -293,6 +294,7 @@ SET(HEADERS ${INCLUDE_ROOT}/implicit_helpers.h ${INCLUDE_ROOT}/implicit_helpers.ipp ${INCLUDE_ROOT}/messages.h + ${INCLUDE_ROOT}/numeric_helpers.h ${INCLUDE_ROOT}/options.h ${INCLUDE_ROOT}/parameterization_quantity.h ${INCLUDE_ROOT}/parameterization_quantity.ipp diff --git a/src/camera_parameters.cpp b/src/camera_parameters.cpp index 358eafcf..00c3bbff 100644 --- a/src/camera_parameters.cpp +++ b/src/camera_parameters.cpp @@ -2,6 +2,9 @@ #include "polyscope/camera_parameters.h" +#include "polyscope/numeric_helpers.h" +#include "polyscope/options.h" + #include #include @@ -171,6 +174,19 @@ CameraParameters CameraParameters::createInvalid() { } bool CameraParameters::isValid() const { return intrinsics.isValid() && extrinsics.isValid(); } +bool CameraParameters::isfinite() const { + // confusing: two different notions of "valid". Here we mean "finite". There's another notion above which uses a flag + // to mark not-set-yet params. + + bool isValid = true; + + isValid = isValid && allComponentsFinite(intrinsics.getAspectRatioWidthOverHeight()); + isValid = isValid && allComponentsFinite(intrinsics.getFoVVerticalDegrees()); + isValid = isValid && allComponentsFinite(extrinsics.getE()); + + return isValid; +} + // == Forwarding getters for the camera class glm::vec3 CameraParameters::getT() const { return extrinsics.getT(); } diff --git a/src/camera_view.cpp b/src/camera_view.cpp index bbbdab0e..325ef3a6 100644 --- a/src/camera_view.cpp +++ b/src/camera_view.cpp @@ -29,6 +29,13 @@ CameraView::CameraView(std::string name, const CameraParameters& params_) widgetThickness(uniquePrefix() + "#widgetThickness", 0.02), widgetColor(uniquePrefix() + "#widgetColor", glm::vec3{0., 0., 0.}) { + if (options::warnForInvalidValues) { + if (!params.isfinite()) { + warning("Invalid +-inf or NaN values detected.\n(set warnForInvalidValues=false to disable this warning)", + "In camera view: " + name); + } + } + updateObjectSpaceBounds(); } diff --git a/src/curve_network.cpp b/src/curve_network.cpp index 52a69347..f9b917b5 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -30,6 +30,7 @@ CurveNetwork::CurveNetwork(std::string name, std::vector nodes_, std: material(uniquePrefix() + "#material", "clay") // clang-format on { + nodePositions.checkInvalidValues(); // Copy interleaved data in to tip and tails buffers below edgeTailIndsData.resize(edges_.size()); diff --git a/src/options.cpp b/src/options.cpp index 3b87de4d..7b565949 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -23,6 +23,7 @@ bool automaticallyComputeSceneExtents = true; bool invokeUserCallbackForNestedShow = false; bool giveFocusOnShow = false; bool hideWindowAfterShow = true; +bool warnForInvalidValues = true; bool screenshotTransparency = true; std::string screenshotExtension = ".png"; diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index 47009ee9..a947a115 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -33,6 +33,7 @@ PointCloud::PointCloud(std::string name, std::vector points_) material(uniquePrefix() + "material", "clay") // clang-format on { + points.checkInvalidValues(); cullWholeElements.setPassive(true); updateObjectSpaceBounds(); } diff --git a/src/render/managed_buffer.cpp b/src/render/managed_buffer.cpp index ecba28e6..b8287ff7 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -5,6 +5,7 @@ #include "polyscope/render/managed_buffer.h" +#include "polyscope/check_invalid_values.h" #include "polyscope/internal.h" #include "polyscope/messages.h" #include "polyscope/polyscope.h" @@ -39,6 +40,11 @@ ManagedBuffer::ManagedBuffer(ManagedBufferRegistry* registry_, const std::str template ManagedBuffer::~ManagedBuffer() {} +template +void ManagedBuffer::checkInvalidValues() { + polyscope::checkInvalidValues(name, data); +} + template void ManagedBuffer::setTextureSize(uint32_t sizeX_) { if (deviceBufferType != DeviceBufferType::Attribute) exception("managed buffer can only be set as texture once"); diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index 74e8dde2..559c31f5 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -73,6 +73,7 @@ SurfaceMesh::SurfaceMesh(std::string name_, const std::vector& vertex faceIndsEntries = faceIndsEntries_; faceIndsStart = faceIndsStart_; + vertexPositions.checkInvalidValues(); computeConnectivityData(); updateObjectSpaceBounds(); } @@ -84,6 +85,7 @@ SurfaceMesh::SurfaceMesh(std::string name_, const std::vector& vertex vertexPositionsData = vertexPositions_; nestedFacesToFlat(facesIn); + vertexPositions.checkInvalidValues(); computeConnectivityData(); updateObjectSpaceBounds(); } diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index 31010ed1..97f0e868 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -135,6 +135,7 @@ edgeWidth(uniquePrefix() + "edgeWidth", 0.), activeLevelSetQuantity(nullptr) { // clang-format on + vertexPositions.checkInvalidValues(); cullWholeElements.setPassive(true); From f4fdc73beaf64283739508039e3bd81bf0a0333e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 29 Dec 2024 20:14:11 -0500 Subject: [PATCH 4/4] headless messaging fixes --- .../render/mock_opengl/mock_gl_engine.h | 3 +++ src/messages.cpp | 27 ++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/include/polyscope/render/mock_opengl/mock_gl_engine.h b/include/polyscope/render/mock_opengl/mock_gl_engine.h index df2651c7..6560902c 100644 --- a/include/polyscope/render/mock_opengl/mock_gl_engine.h +++ b/include/polyscope/render/mock_opengl/mock_gl_engine.h @@ -328,6 +328,9 @@ class MockGLEngine : public Engine { virtual void shutdown() override; void checkError(bool fatal = false) override; + // mock backend is always headless + virtual bool isHeadless() override { return true; } + void swapDisplayBuffers() override; std::vector readDisplayBuffer() override; diff --git a/src/messages.cpp b/src/messages.cpp index 093689b4..a936d7ae 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -233,8 +233,11 @@ void terminatingError(std::string message) { std::cout << options::printPrefix << "[ERROR] " << message << std::endl; } - auto func = std::bind(buildErrorUI, message, true); - pushContext(func, false); + // Enter a modal UI loop showing the warning + if (!isHeadless()) { // don't do it if running headless + auto func = std::bind(buildErrorUI, message, true); + pushContext(func, false); + } // Quit the program shutdown(true); @@ -243,6 +246,13 @@ void terminatingError(std::string message) { void warning(std::string baseMessage, std::string detailMessage) { + // print to stdout + if (options::verbosity > 0) { + std::cout << options::printPrefix << "[WARNING] " << baseMessage; + if (detailMessage != "") std::cout << " --- " << detailMessage; + std::cout << std ::endl; + } + // Look for a message with the same name bool found = false; for (WarningMessage& w : warningMessages) { @@ -268,16 +278,13 @@ void showDelayedWarnings() { showingWarning = true; WarningMessage& currMessage = warningMessages.front(); - if (options::verbosity > 0) { - std::cout << options::printPrefix << "[WARNING] " << currMessage.baseMessage; - if (currMessage.detailMessage != "") std::cout << " --- " << currMessage.detailMessage; - if (currMessage.repeatCount > 0) std::cout << " (and " << currMessage.repeatCount << " similar messages)."; - std::cout << std ::endl; + // Enter a modal UI loop showing the warning + if (!isHeadless()) { // don't do it if running headless + auto func = + std::bind(buildWarningUI, currMessage.baseMessage, currMessage.detailMessage, currMessage.repeatCount); + pushContext(func, false); } - auto func = std::bind(buildWarningUI, currMessage.baseMessage, currMessage.detailMessage, currMessage.repeatCount); - pushContext(func, false); - warningMessages.pop_front(); showingWarning = false; }