diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index f1eaee4b..c358f0f4 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -143,7 +143,8 @@ void processFileOBJ(std::string filename) { randColor[iV] = {{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}}; } polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("cX_really_really_stupid_long_name_how_dumb", valX); - polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("cY", valY); + auto q = polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("cY", valY); + q->setOnscreenColorbarEnabled(true); // set the onscreen colormap for this one polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("cZ", valZ); polyscope::getSurfaceMesh(niceName)->addVertexColorQuantity("vColor", randColor); polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("cY_sym", valY, polyscope::DataType::SYMMETRIC); diff --git a/include/polyscope/color_bar.h b/include/polyscope/color_bar.h new file mode 100644 index 00000000..afca0996 --- /dev/null +++ b/include/polyscope/color_bar.h @@ -0,0 +1,90 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/quantity.h" +#include "polyscope/render/color_maps.h" +#include "polyscope/render/engine.h" +#include "polyscope/widget.h" + +#include + + +namespace polyscope { + +// A histogram that shows up in ImGUI window +// ONEDAY: we could definitely make a better histogram widget for categorical data... + +class OncreenColorBarWidget; + +class ColorBar { +public: + friend class OnscreenColorBarWidget; + ColorBar(Quantity& parent_); // must call buildHistogram() with data after + ~ColorBar(); + + void buildHistogram(const std::vector& values, DataType datatype); + void updateColormap(const std::string& newColormap); + + // Width = -1 means set automatically + void buildUI(float width = -1.0); + + Quantity& parent; + std::pair colormapRange; // in DATA values, not [0,1] + + void exportColorbarToSVG(const std::string& filename); + + // Getters and setters + + void setOnscreenColorbarEnabled(bool newEnabled); + bool getOnscreenColorbarEnabled(); + + // Location in screen coords. (-1,-1), means "place automatically" (default) + void setOnscreenColorbarLocation(glm::vec2 newScreenCoords); + glm::vec2 getOnscreenColorbarLocation(); + +private: + // Basic data defining the color map + DataType dataType = DataType::STANDARD; + std::pair dataRange; + + // == The inline horizontal histogram visualization in the structures bar + + // Manage histogram counts + void fillHistogramBuffers(); + size_t rawHistBinCount = 51; + std::vector rawHistCurveY; + std::vector> rawHistCurveX; + + // Render to a texture for the inline histogram visualization in the structures bar + void renderInlineHistogramToTexture(); + void prepareInlineHistogram(); + unsigned int texDim = 600; + std::shared_ptr inlineHistogramTexture = nullptr; + std::shared_ptr inlineHistogramFramebuffer = nullptr; + std::shared_ptr inlineHistogramProgram = nullptr; + std::string colormap = "viridis"; + + // A few parameters which control appearance + float bottomBarHeight = 0.35; + float bottomBarGap = 0.1; + + // == The optional vertical colorbar which floats ont he main display + PersistentValue onscreenColorbarEnabled; + PersistentValue onscreenColorbarLocation; + std::shared_ptr cmapTexture; // this is just the plain colormap rgb + void prepareOnscreenColorBar(); + std::unique_ptr onscreenColorBarWidget = nullptr; +}; + +class OnscreenColorBarWidget : public Widget { +public: + OnscreenColorBarWidget(ColorBar& parent_); + virtual void draw() override; + +private: + ColorBar& parent; +}; + + +} // namespace polyscope diff --git a/include/polyscope/curve_network_scalar_quantity.h b/include/polyscope/curve_network_scalar_quantity.h index 27c79bed..8885a9cc 100644 --- a/include/polyscope/curve_network_scalar_quantity.h +++ b/include/polyscope/curve_network_scalar_quantity.h @@ -4,7 +4,6 @@ #include "polyscope/affine_remapper.h" #include "polyscope/curve_network.h" -#include "polyscope/histogram.h" #include "polyscope/render/color_maps.h" #include "polyscope/scalar_quantity.h" diff --git a/include/polyscope/histogram.h b/include/polyscope/histogram.h deleted file mode 100644 index bf157560..00000000 --- a/include/polyscope/histogram.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run - -#pragma once - -#include "polyscope/render/color_maps.h" -#include "polyscope/render/engine.h" - -#include - - -namespace polyscope { - -// A histogram that shows up in ImGUI window -// ONEDAY: we could definitely make a better histogram widget for categorical data... - -class Histogram { -public: - Histogram(); // must call buildHistogram() with data after - Histogram(std::vector& values, DataType datatype); // internally calls buildHistogram() - - ~Histogram(); - - void buildHistogram(const std::vector& values, DataType datatype); - void updateColormap(const std::string& newColormap); - - // Width = -1 means set automatically - void buildUI(float width = -1.0); - - std::pair colormapRange; // in DATA values, not [0,1] - -private: - // = Helpers - - // Manage the actual histogram - void fillBuffers(); - size_t rawHistBinCount = 51; - - DataType dataType = DataType::STANDARD; - std::vector rawHistCurveY; - std::vector> rawHistCurveX; - std::pair dataRange; - - // Render to texture - void renderToTexture(); - void prepare(); - - unsigned int texDim = 600; - std::shared_ptr texture = nullptr; - std::shared_ptr framebuffer = nullptr; - std::shared_ptr program = nullptr; - std::string colormap = "viridis"; - - // A few parameters which control appearance - float bottomBarHeight = 0.35; - float bottomBarGap = 0.1; -}; - - -}; // namespace polyscope diff --git a/include/polyscope/internal.h b/include/polyscope/internal.h index 96218338..e57ffa82 100644 --- a/include/polyscope/internal.h +++ b/include/polyscope/internal.h @@ -26,5 +26,15 @@ extern bool& pointCloudEfficiencyWarningReported; // global members extern FloatingQuantityStructure*& globalFloatingQuantityStructure; + +// == UI and layout related +extern float imguiStackMargin; +extern float lastWindowHeightPolyscope; +extern float lastWindowHeightUser; +extern float lastRightSideFreeX; +extern float lastRightSideFreeY; +extern float leftWindowsWidth; +extern float rightWindowsWidth; + } // namespace internal } // namespace polyscope diff --git a/include/polyscope/persistent_value.h b/include/polyscope/persistent_value.h index 517a88b3..efc69ae4 100644 --- a/include/polyscope/persistent_value.h +++ b/include/polyscope/persistent_value.h @@ -134,6 +134,7 @@ extern PersistentCache persistentCache_double; extern PersistentCache persistentCache_float; extern PersistentCache persistentCache_bool; extern PersistentCache persistentCache_string; +extern PersistentCache persistentCache_glmvec2; extern PersistentCache persistentCache_glmvec3; extern PersistentCache persistentCache_glmmat4; extern PersistentCache> persistentCache_scaleddouble; @@ -150,6 +151,7 @@ template<> inline PersistentCache& getPersistentCacheR template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_float; } template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_bool; } template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_string; } +template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_glmvec2; } template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_glmvec3; } template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_glmmat4; } template<> inline PersistentCache>& getPersistentCacheRef>() { return persistentCache_scaleddouble; } diff --git a/include/polyscope/point_cloud_color_quantity.h b/include/polyscope/point_cloud_color_quantity.h index 8ebc346c..8c924b5a 100644 --- a/include/polyscope/point_cloud_color_quantity.h +++ b/include/polyscope/point_cloud_color_quantity.h @@ -4,7 +4,6 @@ #include "polyscope/affine_remapper.h" #include "polyscope/color_quantity.h" -#include "polyscope/histogram.h" #include "polyscope/point_cloud.h" #include "polyscope/point_cloud_quantity.h" diff --git a/include/polyscope/point_cloud_scalar_quantity.h b/include/polyscope/point_cloud_scalar_quantity.h index 903b9ed7..644d6020 100644 --- a/include/polyscope/point_cloud_scalar_quantity.h +++ b/include/polyscope/point_cloud_scalar_quantity.h @@ -3,7 +3,6 @@ #pragma once #include "polyscope/affine_remapper.h" -#include "polyscope/histogram.h" #include "polyscope/point_cloud.h" #include "polyscope/render/color_maps.h" #include "polyscope/scalar_quantity.h" diff --git a/include/polyscope/render/color_maps.h b/include/polyscope/render/color_maps.h index ebd59f27..660089e3 100644 --- a/include/polyscope/render/color_maps.h +++ b/include/polyscope/render/color_maps.h @@ -81,8 +81,11 @@ struct ValueColorMap { double scaledVal = val * (values.size() - 1); double lowerVal = std::floor(scaledVal); double upperBlendVal = scaledVal - lowerVal; - unsigned int lowerInd = static_cast(lowerVal); - unsigned int upperInd = lowerInd + 1; + int lowerInd = static_cast(lowerVal); + int upperInd = lowerInd + 1; + + lowerInd = std::min(std::max(0, lowerInd), (int)values.size() - 1); + upperInd = std::min(std::max(0, upperInd), (int)values.size() - 1); return (float)(1.0 - upperBlendVal) * values[lowerInd] + (float)upperBlendVal * values[upperInd]; } diff --git a/include/polyscope/render/engine.h b/include/polyscope/render/engine.h index 48a7493c..5272b4f4 100644 --- a/include/polyscope/render/engine.h +++ b/include/polyscope/render/engine.h @@ -614,6 +614,7 @@ class Engine { std::vector> colorMaps; const ValueColorMap& getColorMap(const std::string& name); void loadColorMap(std::string cmapName, std::string filename); + std::shared_ptr getColorMapTexture2d(const std::string& cmapName); // Helpers std::vector screenTrianglesCoords(); // two triangles which cover the screen diff --git a/include/polyscope/scalar_quantity.h b/include/polyscope/scalar_quantity.h index 9d55dbb6..9644b712 100644 --- a/include/polyscope/scalar_quantity.h +++ b/include/polyscope/scalar_quantity.h @@ -3,7 +3,7 @@ #pragma once #include "polyscope/affine_remapper.h" -#include "polyscope/histogram.h" +#include "polyscope/color_bar.h" #include "polyscope/persistent_value.h" #include "polyscope/polyscope.h" #include "polyscope/render/engine.h" @@ -33,6 +33,9 @@ class ScalarQuantity { template void updateData(const V& newValues); + // Export the current colorbar as an SVG file + void exportColorbarToSVG(const std::string& filename); + // === Members QuantityT& quantity; @@ -51,6 +54,15 @@ class ScalarQuantity { std::pair getMapRange(); QuantityT* resetMapRange(); // reset to full range std::pair getDataRange(); + + // Color bar options (it is always displayed inline in the structures panel) + QuantityT* setOnscreenColorbarEnabled(bool newEnabled); + bool getOnscreenColorbarEnabled(); + + // Location in screen coords. (-1,-1), means "place automatically" (default) + QuantityT* setOnscreenColorbarLocation(glm::vec2 newScreenCoords); + glm::vec2 getOnscreenColorbarLocation(); + // Isolines // NOTE there's a name typo, errant `s` in isolinesEnabled (leaving to avoid breaking change) @@ -79,7 +91,8 @@ class ScalarQuantity { std::pair dataRange; PersistentValue vizRangeMin; PersistentValue vizRangeMax; - Histogram hist; + + ColorBar colorBar; // Parameters PersistentValue cMap; diff --git a/include/polyscope/scalar_quantity.ipp b/include/polyscope/scalar_quantity.ipp index bff31d15..31cb0f21 100644 --- a/include/polyscope/scalar_quantity.ipp +++ b/include/polyscope/scalar_quantity.ipp @@ -12,7 +12,7 @@ ScalarQuantity::ScalarQuantity(QuantityT& quantity_, const std::vecto dataType(dataType_), dataRange(robustMinMax(values.data, 1e-5)), vizRangeMin(quantity.uniquePrefix() + "vizRangeMin", -777.), // set later, vizRangeMax(quantity.uniquePrefix() + "vizRangeMax", -777.), // including clearing cache - cMap(quantity.uniquePrefix() + "cmap", defaultColorMap(dataType)), + colorBar(quantity), cMap(quantity.uniquePrefix() + "cmap", defaultColorMap(dataType)), isolinesEnabled(quantity.uniquePrefix() + "isolinesEnabled", false), isolineStyle(quantity.uniquePrefix() + "isolinesStyle", IsolineStyle::Stripe), isolinePeriod(quantity.uniquePrefix() + "isolinePeriod", @@ -22,8 +22,8 @@ ScalarQuantity::ScalarQuantity(QuantityT& quantity_, const std::vecto { values.checkInvalidValues(); - hist.updateColormap(cMap.get()); - hist.buildHistogram(values.data, dataType); + colorBar.updateColormap(cMap.get()); + colorBar.buildHistogram(values.data, dataType); // TODO: I think we might be building the histogram ^^^ twice for many quantities if (vizRangeMin.holdsDefaultValue()) { // min and max should always have same cache state @@ -38,7 +38,7 @@ void ScalarQuantity::buildScalarUI() { if (render::buildColormapSelector(cMap.get())) { quantity.refresh(); - hist.updateColormap(cMap.get()); + colorBar.updateColormap(cMap.get()); setColorMap(getColorMap()); } @@ -82,10 +82,10 @@ void ScalarQuantity::buildScalarUI() { // Draw the histogram of values - hist.colormapRange = std::pair(vizRangeMin.get(), vizRangeMax.get()); + colorBar.colormapRange = std::pair(vizRangeMin.get(), vizRangeMax.get()); float windowWidth = ImGui::GetWindowWidth(); float histWidth = 0.75 * windowWidth; - hist.buildUI(histWidth); + colorBar.buildUI(histWidth); // Data range // Note: %g specifiers are generally nicer than %e, but here we don't acutally have a choice. ImGui (for somewhat @@ -220,6 +220,14 @@ void ScalarQuantity::buildScalarOptionsUI() { if (dataType != DataType::CATEGORICAL) { if (ImGui::MenuItem("Enable isolines", NULL, isolinesEnabled.get())) setIsolinesEnabled(!isolinesEnabled.get()); } + if (ImGui::MenuItem("Onscreen Colorbar", NULL, colorBar.getOnscreenColorbarEnabled())) { + colorBar.setOnscreenColorbarEnabled(!colorBar.getOnscreenColorbarEnabled()); + } + if (ImGui::MenuItem("Export Colorbar")) { + std::string filename = quantity.parent.name + "_" + quantity.name + "_colorbar.svg"; + colorBar.exportColorbarToSVG(filename); + polyscope::info("Exported colorbar to " + filename); + } } template @@ -294,6 +302,12 @@ QuantityT* ScalarQuantity::resetMapRange() { return &quantity; } + +template +void ScalarQuantity::exportColorbarToSVG(const std::string& filename) { + colorBar.exportColorbarToSVG(filename); +} + template template void ScalarQuantity::updateData(const V& newValues) { @@ -306,7 +320,7 @@ void ScalarQuantity::updateData(const V& newValues) { template QuantityT* ScalarQuantity::setColorMap(std::string val) { cMap = val; - hist.updateColormap(cMap.get()); + colorBar.updateColormap(cMap.get()); quantity.refresh(); requestRedraw(); return &quantity; @@ -316,6 +330,29 @@ std::string ScalarQuantity::getColorMap() { return cMap.get(); } + +template +QuantityT* ScalarQuantity::setOnscreenColorbarEnabled(bool newEnabled) { + colorBar.setOnscreenColorbarEnabled(newEnabled); + requestRedraw(); + return &quantity; +} +template +bool ScalarQuantity::getOnscreenColorbarEnabled() { + return colorBar.getOnscreenColorbarEnabled(); +} + +template +QuantityT* ScalarQuantity::setOnscreenColorbarLocation(glm::vec2 newScreenCoords) { + colorBar.setOnscreenColorbarLocation(newScreenCoords); + requestRedraw(); + return &quantity; +} +template +glm::vec2 ScalarQuantity::getOnscreenColorbarLocation() { + return colorBar.getOnscreenColorbarLocation(); +} + template QuantityT* ScalarQuantity::setMapRange(std::pair val) { vizRangeMin = val.first; diff --git a/include/polyscope/scalar_render_image_quantity.h b/include/polyscope/scalar_render_image_quantity.h index 9614a0c0..50851e7e 100644 --- a/include/polyscope/scalar_render_image_quantity.h +++ b/include/polyscope/scalar_render_image_quantity.h @@ -3,7 +3,6 @@ #pragma once #include "polyscope/affine_remapper.h" -#include "polyscope/histogram.h" #include "polyscope/render/color_maps.h" #include "polyscope/render_image_quantity_base.h" #include "polyscope/scalar_quantity.h" diff --git a/include/polyscope/surface_parameterization_quantity.h b/include/polyscope/surface_parameterization_quantity.h index 957bac4a..656c5a9d 100644 --- a/include/polyscope/surface_parameterization_quantity.h +++ b/include/polyscope/surface_parameterization_quantity.h @@ -3,7 +3,6 @@ #pragma once #include "polyscope/affine_remapper.h" -#include "polyscope/histogram.h" #include "polyscope/parameterization_quantity.h" #include "polyscope/render/color_maps.h" #include "polyscope/render/engine.h" diff --git a/include/polyscope/surface_scalar_quantity.h b/include/polyscope/surface_scalar_quantity.h index 80260f11..1b2eab61 100644 --- a/include/polyscope/surface_scalar_quantity.h +++ b/include/polyscope/surface_scalar_quantity.h @@ -5,7 +5,6 @@ #include "polyscope/polyscope.h" #include "polyscope/affine_remapper.h" -#include "polyscope/histogram.h" #include "polyscope/render/color_maps.h" #include "polyscope/render/engine.h" #include "polyscope/scalar_quantity.h" diff --git a/include/polyscope/volume_grid_scalar_quantity.h b/include/polyscope/volume_grid_scalar_quantity.h index 503c046a..5bc200e9 100644 --- a/include/polyscope/volume_grid_scalar_quantity.h +++ b/include/polyscope/volume_grid_scalar_quantity.h @@ -5,7 +5,6 @@ #include "polyscope/polyscope.h" #include "polyscope/affine_remapper.h" -#include "polyscope/histogram.h" #include "polyscope/render/color_maps.h" #include "polyscope/scalar_quantity.h" #include "polyscope/surface_mesh.h" diff --git a/include/polyscope/volume_mesh_scalar_quantity.h b/include/polyscope/volume_mesh_scalar_quantity.h index a31be7a0..795537ad 100644 --- a/include/polyscope/volume_mesh_scalar_quantity.h +++ b/include/polyscope/volume_mesh_scalar_quantity.h @@ -5,7 +5,6 @@ #include "polyscope/polyscope.h" #include "polyscope/affine_remapper.h" -#include "polyscope/histogram.h" #include "polyscope/render/color_maps.h" #include "polyscope/render/engine.h" #include "polyscope/scalar_quantity.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e5a0679b..4fab560c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -183,7 +183,7 @@ SET(SRCS disjoint_sets.cpp file_helpers.cpp camera_parameters.cpp - histogram.cpp + color_bar.cpp persistent_value.cpp color_management.cpp transformation_gizmo.cpp @@ -271,6 +271,7 @@ SET(HEADERS ${INCLUDE_ROOT}/camera_view.h ${INCLUDE_ROOT}/camera_view.ipp ${INCLUDE_ROOT}/check_invalid_values.h + ${INCLUDE_ROOT}/color_bar.h ${INCLUDE_ROOT}/color_management.h ${INCLUDE_ROOT}/color_image_quantity.h ${INCLUDE_ROOT}/color_render_image_quantity.h @@ -293,7 +294,6 @@ SET(HEADERS ${INCLUDE_ROOT}/floating_quantity.h ${INCLUDE_ROOT}/floating_quantities.h ${INCLUDE_ROOT}/group.h - ${INCLUDE_ROOT}/histogram.h ${INCLUDE_ROOT}/image_quantity.h ${INCLUDE_ROOT}/imgui_config.h ${INCLUDE_ROOT}/implicit_helpers.h diff --git a/src/color_bar.cpp b/src/color_bar.cpp new file mode 100644 index 00000000..20e68ab2 --- /dev/null +++ b/src/color_bar.cpp @@ -0,0 +1,442 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope/color_bar.h" + +#include "polyscope/affine_remapper.h" +#include "polyscope/polyscope.h" + +#include "imgui.h" + +#include +#include +#include +#include + +namespace polyscope { + +ColorBar::ColorBar(Quantity& parent_) + : parent(parent_), onscreenColorbarEnabled(parent.uniquePrefix() + "onscreenColorbarEnabled", false), + onscreenColorbarLocation(parent.uniquePrefix() + "onscreenColorbarLocation", glm::vec2(-1., -1.)) {} + +ColorBar::~ColorBar() {} + +void ColorBar::buildHistogram(const std::vector& values, DataType dataType_) { + dataType = dataType_; + + // Build arrays of values + size_t N = values.size(); + + // == Build histogram + dataRange = robustMinMax(values); + colormapRange = dataRange; + + // Helper to build the four histogram variants + auto buildCurve = [&](size_t binCount, std::vector>& curveX, std::vector& curveY) { + // linspace coords + double range = dataRange.second - dataRange.first; + double inc = range / binCount; + std::vector sumBin(binCount, 0.0); + + // count values in buckets + for (size_t iData = 0; iData < N; iData++) { + + double iBinf = binCount * (values[iData] - dataRange.first) / range; + size_t iBin = std::floor(glm::clamp(iBinf, 0.0, (double)binCount - 1)); + + // NaN values and finite values near the bottom of float range lead to craziness, so only increment bins if we got + // something reasonable + if (iBin < binCount) { + sumBin[iBin] += 1.0; + } + } + + + // build histogram coords + curveX = std::vector>(binCount); + curveY = std::vector(binCount); + double prevSumU = 0.0; + double prevSumW = 0.0; + double prevXEnd = dataRange.first; + for (size_t iBin = 0; iBin < binCount; iBin++) { + // y value + curveY[iBin] = sumBin[iBin]; + + // x value + double xEnd = prevXEnd + inc; + curveX[iBin] = {{static_cast(prevXEnd), static_cast(xEnd)}}; + prevXEnd = xEnd; + } + + { // Rescale curves to [0,1] in both dimensions + double maxHeight = *std::max_element(curveY.begin(), curveY.end()); + for (size_t i = 0; i < binCount; i++) { + curveX[i][0] = (curveX[i][0] - dataRange.first) / range; + curveX[i][1] = (curveX[i][1] - dataRange.first) / range; + curveY[i] /= maxHeight; + } + } + }; + + buildCurve(rawHistBinCount, rawHistCurveX, rawHistCurveY); +} + + +void ColorBar::updateColormap(const std::string& newColormap) { + colormap = newColormap; + inlineHistogramProgram.reset(); + cmapTexture.reset(); +} + +void ColorBar::fillHistogramBuffers() { + + if (rawHistCurveY.size() == 0) { + exception("histogram fillBuffers() called before buildHistogram"); + } + + // Push to buffer + std::vector coords; + + float histHeightStart = bottomBarHeight + bottomBarGap; + + for (size_t i = 0; i < rawHistCurveX.size(); i++) { + + float leftX = rawHistCurveX[i][0]; + float rightX = rawHistCurveX[i][1]; + + float leftYTop = histHeightStart + (1. - histHeightStart) * rawHistCurveY[i]; + float rightYTop = histHeightStart + (1. - histHeightStart) * rawHistCurveY[i]; + + float leftYBot = histHeightStart; + float rightYBot = histHeightStart; + + // = Lower triangle (lower left, lower right, upper left) + coords.push_back(glm::vec2{leftX, leftYBot}); + coords.push_back(glm::vec2{rightX, rightYBot}); + coords.push_back(glm::vec2{leftX, leftYTop}); + + // = Upper triangle (lower right, upper right, upper left) + coords.push_back(glm::vec2{rightX, rightYBot}); + coords.push_back(glm::vec2{rightX, rightYTop}); + coords.push_back(glm::vec2{leftX, leftYTop}); + } + + // the long skinny bar along the bottom, which always shows regardless of histogram values + coords.push_back(glm::vec2{0., 0.}); + coords.push_back(glm::vec2{1., 0.}); + coords.push_back(glm::vec2{0., bottomBarHeight}); + coords.push_back(glm::vec2{1., 0.}); + coords.push_back(glm::vec2{1., bottomBarHeight}); + coords.push_back(glm::vec2{0., bottomBarHeight}); + + + inlineHistogramProgram->setAttribute("a_coord", coords); +} + +void ColorBar::prepareInlineHistogram() { + + inlineHistogramFramebuffer = render::engine->generateFrameBuffer(texDim, texDim); + inlineHistogramTexture = render::engine->generateTextureBuffer(TextureFormat::RGBA8, texDim, texDim); + inlineHistogramFramebuffer->addColorBuffer(inlineHistogramTexture); + + // Create the inlineHistogramProgram + if (dataType == DataType::CATEGORICAL) { + // for categorical data only + inlineHistogramProgram = render::engine->requestShader("HISTOGRAM_CATEGORICAL", {"SHADE_CATEGORICAL_COLORMAP"}, + render::ShaderReplacementDefaults::Process); + } else { + // common case + inlineHistogramProgram = render::engine->requestShader("HISTOGRAM", {"SHADE_COLORMAP_VALUE"}, + render::ShaderReplacementDefaults::Process); + } + + inlineHistogramProgram->setTextureFromColormap("t_colormap", colormap, true); + + fillHistogramBuffers(); +} + + +void ColorBar::renderInlineHistogramToTexture() { + + if (!inlineHistogramProgram) { + prepareInlineHistogram(); + } + + inlineHistogramFramebuffer->clearColor = {0.0, 0.0, 0.0}; + inlineHistogramFramebuffer->clearAlpha = 0.2; + inlineHistogramFramebuffer->setViewport(0, 0, texDim, texDim); + inlineHistogramFramebuffer->bindForRendering(); + inlineHistogramFramebuffer->clear(); + + // = Set uniforms + + if (dataType == DataType::CATEGORICAL) { + // Used to restore [0,1] tvals to the orininal data range for the categorical int remapping + inlineHistogramProgram->setUniform("u_dataRangeLow", dataRange.first); + inlineHistogramProgram->setUniform("u_dataRangeHigh", dataRange.second); + } else { + // Colormap range (remapped to the 0-1 coords we use) + float rangeLow = (colormapRange.first - dataRange.first) / (dataRange.second - dataRange.first); + float rangeHigh = (colormapRange.second - dataRange.first) / (dataRange.second - dataRange.first); + inlineHistogramProgram->setUniform("u_rangeLow", rangeLow); + inlineHistogramProgram->setUniform("u_rangeHigh", rangeHigh); + } + + inlineHistogramProgram->draw(); +} + +void ColorBar::setOnscreenColorbarEnabled(bool newEnabled) { + onscreenColorbarEnabled.set(newEnabled); + if (newEnabled) { + prepareOnscreenColorBar(); + } +} +bool ColorBar::getOnscreenColorbarEnabled() { return onscreenColorbarEnabled.get(); } + +void ColorBar::setOnscreenColorbarLocation(glm::vec2 newScreenCoords) { onscreenColorbarLocation.set(newScreenCoords); } +glm::vec2 ColorBar::getOnscreenColorbarLocation() { return onscreenColorbarLocation.get(); } + +void ColorBar::prepareOnscreenColorBar() { + if (onscreenColorBarWidget) { + // Already created, nothing to do + return; + } + onscreenColorBarWidget = std::unique_ptr(new OnscreenColorBarWidget(*this)); +} + +OnscreenColorBarWidget::OnscreenColorBarWidget(ColorBar& parent_) : parent(parent_) {} + +void OnscreenColorBarWidget::draw() { + if (!parent.parent.isEnabled()) return; + if (!parent.getOnscreenColorbarEnabled()) return; + + if (!parent.cmapTexture) { + parent.cmapTexture = render::engine->getColorMapTexture2d(parent.colormap); + } + + bool placeAutomatically = + (parent.onscreenColorbarLocation.get().x == -1.f && parent.onscreenColorbarLocation.get().y == -1.f); + + // Draw the color bar + + // NOTE: right nwo the size of the colorbar is scaled by uiScale, but the positioning/margins are not. This is + // consistent with the other positioning/marching logic in the main gui panels. (Maybe they should all be scaled?) + float barRegionWidth = 30.0f * options::uiScale; + float tickRegionWidth = 90.0f * options::uiScale; + float marginWidth = 10.0f * options::uiScale; + float barRegionHeight = 300.0f * options::uiScale; + float borderWidth = 2.0f * options::uiScale; + float tickWidth = 5.0f * options::uiScale; + + + ImVec2 barTopLeft; + if (placeAutomatically) { + barTopLeft = ImVec2(internal::lastRightSideFreeX - barRegionWidth - tickRegionWidth - marginWidth, + internal::lastRightSideFreeY + marginWidth + internal::imguiStackMargin); + } else { + barTopLeft = ImVec2(parent.onscreenColorbarLocation.get().x, parent.onscreenColorbarLocation.get().y); + } + + ImDrawList* dl = ImGui::GetBackgroundDrawList(); + ImU32 backgroundColor = IM_COL32(255, 255, 255, 180); + + // dd a semi-transparent background + dl->AddRectFilled(ImVec2(barTopLeft.x - marginWidth, barTopLeft.y - marginWidth), + ImVec2(barTopLeft.x + barRegionWidth + tickRegionWidth + marginWidth, + barTopLeft.y + barRegionHeight + marginWidth), + backgroundColor); + + // draw the actual colormap gradient rectangle + render::engine->preserveResourceUntilImguiFrameCompletes(parent.cmapTexture); + dl->AddImage((ImTextureID)(intptr_t)parent.cmapTexture->getNativeHandle(), ImVec2(barTopLeft.x, barTopLeft.y), + ImVec2(barTopLeft.x + barRegionWidth, barTopLeft.y + barRegionHeight), ImVec2(0, 1), ImVec2(1, 0)); + + // draw the border around the map + dl->AddRect(barTopLeft, ImVec2(barTopLeft.x + barRegionWidth, barTopLeft.y + barRegionHeight), IM_COL32(0, 0, 0, 255), + 0.f, ImDrawFlags_None, borderWidth); + + // draw text ticks + int nTicks = 5; + for (int i = 0; i < nTicks; i++) { + float t = (float)i / (float)(nTicks - 1); + float yPos = barTopLeft.y + t * (barRegionHeight - 0.5 * borderWidth); + double val = parent.colormapRange.second - t * (parent.colormapRange.second - parent.colormapRange.first); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%.4g", val); + + // Make a little tick mark + dl->AddLine(ImVec2(barTopLeft.x + barRegionWidth - borderWidth, yPos), + ImVec2(barTopLeft.x + barRegionWidth + tickWidth, yPos), IM_COL32_BLACK, borderWidth); + + // Draw the actual text + ImVec2 textSize = ImGui::CalcTextSize(buffer); + dl->AddText(ImVec2(barTopLeft.x + barRegionWidth + 2.f * tickWidth, yPos - textSize.y / 2), IM_COL32_BLACK, buffer); + } + + if (placeAutomatically) { + internal::lastRightSideFreeX -= + (barRegionWidth + tickRegionWidth + marginWidth + 2.f * tickWidth) + internal::imguiStackMargin; + } +} + +void ColorBar::exportColorbarToSVG(const std::string& filename) { + std::ofstream svgFile(filename); + if (!svgFile.is_open()) { + polyscope::warning("Failed to open file for SVG export: " + filename); + return; + } + + const render::ValueColorMap& valueColormap = render::engine->getColorMap(colormap); + + // SVG dimensions + float svgWidth = 100.0f; + float svgHeight = 400.0f; + float barWidth = 30.0f; + float tickLength = 10.0f; + float textOffset = 15.0f; + int nTicks = 5; + + // SVG header + svgFile << "\n"; + svgFile << "\n"; + + // Gradient definition + svgFile << " \n"; + svgFile << " \n"; + + // Sample colormap at multiple points + int nSamples = 50; + for (int i = 0; i <= nSamples; i++) { + float t = (float)i / (float)nSamples; + glm::vec3 color = valueColormap.getValue(t); + int r = static_cast(color.x * 255); + int g = static_cast(color.y * 255); + int b = static_cast(color.z * 255); + svgFile << " \n"; + } + + svgFile << " \n"; + svgFile << " \n"; + + // Draw the colorbar rectangle + svgFile << " \n"; + + // Ticks and labels + for (int i = 0; i < nTicks; i++) { + float t = (float)i / (float)(nTicks - 1); + float yPos = 10 + t * (svgHeight - 20); + double val = colormapRange.second - t * (colormapRange.second - colormapRange.first); + + // tick mark + svgFile << " \n"; + + // text label + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%.4g", val); + svgFile << " " << buffer << "\n"; + } + + svgFile << "\n"; + svgFile.close(); +} + +void ColorBar::buildUI(float width) { + + renderInlineHistogramToTexture(); + + // Compute size for image + float aspect = 4.0; + float w = width; + if (w == -1.0) { + w = .7 * ImGui::GetWindowWidth(); + } + float h = w / aspect; + + // Render image + ImGui::Image((ImTextureID)(intptr_t)inlineHistogramTexture->getNativeHandle(), ImVec2(w, h), ImVec2(0, 1), + ImVec2(1, 0)); + render::engine->preserveResourceUntilImguiFrameCompletes(inlineHistogramTexture); + + // Helpful info for drawing annotations below + ImU32 annoColor = ImGui::ColorConvertFloat4ToU32(ImVec4(254 / 255., 221 / 255., 66 / 255., 1.0)); + ImU32 annoColorDark = ImGui::ColorConvertFloat4ToU32(ImVec4(5. / 255., 5. / 255., 5. / 255., 1.0)); + ImVec2 imageLowerLeft(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y); + + // Draw a cursor popup on mouseover + if (ImGui::IsItemHovered()) { + // Get mouse x coodinate within image + float mouseX = ImGui::GetMousePos().x - ImGui::GetCursorScreenPos().x - ImGui::GetScrollX(); + double mouseT = mouseX / w; + double val = dataRange.first + mouseT * (dataRange.second - dataRange.first); + + ImGui::SetTooltip("%g", val); + + // Draw line + ImVec2 lineStart(imageLowerLeft.x + mouseX, imageLowerLeft.y - h - 3); + ImVec2 lineEnd(imageLowerLeft.x + mouseX, imageLowerLeft.y - 4); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, annoColor); + } + + + /* This is pretty neat, but ultimately I decided I don't like the look of it. It has + * some value as a more obvious user widget than dragging imgui's sliders, however. + + { // Draw triangles that indicate where the colormap is + + // clang-format off + + float markerTriWidth = 0.05; + float imageLeft = imageLowerLeft.x; + float imageRight = imageLeft + w; + float imageTop = imageLowerLeft.y - 4; + float imageBot = imageTop + h; + + float leftX = (colormapRange.first - dataRange.first) / (dataRange.second - dataRange.first); + float rightX = (colormapRange.second - dataRange.first) / (dataRange.second - dataRange.first); + float markerTriHeight = bottomBarHeight / 2; + + // left triangle + ImGui::GetWindowDrawList()->AddTriangleFilled( + {imageLeft + w*(leftX - markerTriWidth) , imageTop}, + {imageLeft + w*(leftX + markerTriWidth) , imageTop}, + {imageLeft + w*leftX , imageTop - h*markerTriHeight}, + annoColor + ); + ImGui::GetWindowDrawList()->AddTriangle( + {imageLeft + w*(leftX - markerTriWidth) , imageTop}, + {imageLeft + w*(leftX + markerTriWidth) , imageTop}, + {imageLeft + w*leftX , imageTop - h*markerTriHeight}, + annoColorDark + ); + + // right triangle + ImGui::GetWindowDrawList()->AddTriangleFilled( + {imageLeft + w*(rightX - markerTriWidth) , imageTop}, + {imageLeft + w*(rightX + markerTriWidth) , imageTop}, + {imageLeft + w*rightX , imageTop - h*markerTriHeight}, + annoColor + ); + ImGui::GetWindowDrawList()->AddTriangle( + {imageLeft + w*(rightX - markerTriWidth) , imageTop}, + {imageLeft + w*(rightX + markerTriWidth) , imageTop}, + {imageLeft + w*rightX , imageTop - h*markerTriHeight}, + annoColorDark + ); + + // clang-format on + } + + */ + + + // NOTE: the onscreen colorbar gets drawn in the draw() command, rather than with + // the rest of the UI stuff here, because we need it to happen even if this UI + // panel is collapsed. +} + + +} // namespace polyscope diff --git a/src/histogram.cpp b/src/histogram.cpp deleted file mode 100644 index 12394e62..00000000 --- a/src/histogram.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run - -#include "polyscope/histogram.h" - -#include "polyscope/affine_remapper.h" -#include "polyscope/polyscope.h" - -#include "imgui.h" - -#include -#include -#include - -namespace polyscope { - -Histogram::Histogram() {} - -Histogram::Histogram(std::vector& values, DataType dataType) { buildHistogram(values, dataType); } - -Histogram::~Histogram() {} - -void Histogram::buildHistogram(const std::vector& values, DataType dataType_) { - dataType = dataType_; - - // Build arrays of values - size_t N = values.size(); - - // == Build histogram - dataRange = robustMinMax(values); - colormapRange = dataRange; - - // Helper to build the four histogram variants - auto buildCurve = [&](size_t binCount, std::vector>& curveX, std::vector& curveY) { - // linspace coords - double range = dataRange.second - dataRange.first; - double inc = range / binCount; - std::vector sumBin(binCount, 0.0); - - // count values in buckets - for (size_t iData = 0; iData < N; iData++) { - - double iBinf = binCount * (values[iData] - dataRange.first) / range; - size_t iBin = std::floor(glm::clamp(iBinf, 0.0, (double)binCount - 1)); - - // NaN values and finite values near the bottom of float range lead to craziness, so only increment bins if we got - // something reasonable - if (iBin < binCount) { - sumBin[iBin] += 1.0; - } - } - - - // build histogram coords - curveX = std::vector>(binCount); - curveY = std::vector(binCount); - double prevSumU = 0.0; - double prevSumW = 0.0; - double prevXEnd = dataRange.first; - for (size_t iBin = 0; iBin < binCount; iBin++) { - // y value - curveY[iBin] = sumBin[iBin]; - - // x value - double xEnd = prevXEnd + inc; - curveX[iBin] = {{static_cast(prevXEnd), static_cast(xEnd)}}; - prevXEnd = xEnd; - } - - { // Rescale curves to [0,1] in both dimensions - double maxHeight = *std::max_element(curveY.begin(), curveY.end()); - for (size_t i = 0; i < binCount; i++) { - curveX[i][0] = (curveX[i][0] - dataRange.first) / range; - curveX[i][1] = (curveX[i][1] - dataRange.first) / range; - curveY[i] /= maxHeight; - } - } - }; - - buildCurve(rawHistBinCount, rawHistCurveX, rawHistCurveY); -} - - -void Histogram::updateColormap(const std::string& newColormap) { - colormap = newColormap; - if (program) { - program.reset(); - } -} - -void Histogram::fillBuffers() { - - if (rawHistCurveY.size() == 0) { - exception("histogram fillBuffers() called before buildHistogram"); - } - - // Push to buffer - std::vector coords; - - float histHeightStart = bottomBarHeight + bottomBarGap; - - for (size_t i = 0; i < rawHistCurveX.size(); i++) { - - float leftX = rawHistCurveX[i][0]; - float rightX = rawHistCurveX[i][1]; - - float leftYTop = histHeightStart + (1. - histHeightStart) * rawHistCurveY[i]; - float rightYTop = histHeightStart + (1. - histHeightStart) * rawHistCurveY[i]; - - float leftYBot = histHeightStart; - float rightYBot = histHeightStart; - - // = Lower triangle (lower left, lower right, upper left) - coords.push_back(glm::vec2{leftX, leftYBot}); - coords.push_back(glm::vec2{rightX, rightYBot}); - coords.push_back(glm::vec2{leftX, leftYTop}); - - // = Upper triangle (lower right, upper right, upper left) - coords.push_back(glm::vec2{rightX, rightYBot}); - coords.push_back(glm::vec2{rightX, rightYTop}); - coords.push_back(glm::vec2{leftX, leftYTop}); - } - - // the long skinny bar along the bottom, which always shows regardless of histogram values - coords.push_back(glm::vec2{0., 0.}); - coords.push_back(glm::vec2{1., 0.}); - coords.push_back(glm::vec2{0., bottomBarHeight}); - coords.push_back(glm::vec2{1., 0.}); - coords.push_back(glm::vec2{1., bottomBarHeight}); - coords.push_back(glm::vec2{0., bottomBarHeight}); - - - program->setAttribute("a_coord", coords); -} - -void Histogram::prepare() { - - framebuffer = render::engine->generateFrameBuffer(texDim, texDim); - texture = render::engine->generateTextureBuffer(TextureFormat::RGBA8, texDim, texDim); - framebuffer->addColorBuffer(texture); - - // Create the program - if (dataType == DataType::CATEGORICAL) { - // for categorical data only - program = render::engine->requestShader("HISTOGRAM_CATEGORICAL", {"SHADE_CATEGORICAL_COLORMAP"}, - render::ShaderReplacementDefaults::Process); - } else { - // common case - program = render::engine->requestShader("HISTOGRAM", {"SHADE_COLORMAP_VALUE"}, - render::ShaderReplacementDefaults::Process); - } - - program->setTextureFromColormap("t_colormap", colormap, true); - - fillBuffers(); -} - - -void Histogram::renderToTexture() { - - if (!program) { - prepare(); - } - - framebuffer->clearColor = {0.0, 0.0, 0.0}; - framebuffer->clearAlpha = 0.2; - framebuffer->setViewport(0, 0, texDim, texDim); - framebuffer->bindForRendering(); - framebuffer->clear(); - - // = Set uniforms - - if (dataType == DataType::CATEGORICAL) { - // Used to restore [0,1] tvals to the orininal data range for the categorical int remapping - program->setUniform("u_dataRangeLow", dataRange.first); - program->setUniform("u_dataRangeHigh", dataRange.second); - } else { - // Colormap range (remapped to the 0-1 coords we use) - float rangeLow = (colormapRange.first - dataRange.first) / (dataRange.second - dataRange.first); - float rangeHigh = (colormapRange.second - dataRange.first) / (dataRange.second - dataRange.first); - program->setUniform("u_rangeLow", rangeLow); - program->setUniform("u_rangeHigh", rangeHigh); - } - - - // Draw - program->draw(); -} - - -void Histogram::buildUI(float width) { - - // NOTE: I'm surprised this works, since we're drawing in the middle of imgui's processing. Possible source of bugs? - renderToTexture(); - - // Compute size for image - float aspect = 4.0; - float w = width; - if (w == -1.0) { - w = .7 * ImGui::GetWindowWidth(); - } - float h = w / aspect; - - // Render image - ImGui::Image((ImTextureID)(intptr_t)texture->getNativeHandle(), ImVec2(w, h), ImVec2(0, 1), ImVec2(1, 0)); - render::engine->preserveResourceUntilImguiFrameCompletes(texture); - - // Helpful info for drawing annotations below - ImU32 annoColor = ImGui::ColorConvertFloat4ToU32(ImVec4(254 / 255., 221 / 255., 66 / 255., 1.0)); - ImU32 annoColorDark = ImGui::ColorConvertFloat4ToU32(ImVec4(5. / 255., 5. / 255., 5. / 255., 1.0)); - ImVec2 imageLowerLeft(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y); - - // Draw a cursor popup on mouseover - if (ImGui::IsItemHovered()) { - // Get mouse x coodinate within image - float mouseX = ImGui::GetMousePos().x - ImGui::GetCursorScreenPos().x - ImGui::GetScrollX(); - double mouseT = mouseX / w; - double val = dataRange.first + mouseT * (dataRange.second - dataRange.first); - - ImGui::SetTooltip("%g", val); - - // Draw line - ImVec2 lineStart(imageLowerLeft.x + mouseX, imageLowerLeft.y - h - 3); - ImVec2 lineEnd(imageLowerLeft.x + mouseX, imageLowerLeft.y - 4); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, annoColor); - } - - - /* This is pretty neat, but ultimately I decided I don't like the look of it. It has - * some value as a more obvious user widget than dragging imgui's sliders, however. - - { // Draw triangles that indicate where the colormap is - - // clang-format off - - float markerTriWidth = 0.05; - float imageLeft = imageLowerLeft.x; - float imageRight = imageLeft + w; - float imageTop = imageLowerLeft.y - 4; - float imageBot = imageTop + h; - - float leftX = (colormapRange.first - dataRange.first) / (dataRange.second - dataRange.first); - float rightX = (colormapRange.second - dataRange.first) / (dataRange.second - dataRange.first); - float markerTriHeight = bottomBarHeight / 2; - - // left triangle - ImGui::GetWindowDrawList()->AddTriangleFilled( - {imageLeft + w*(leftX - markerTriWidth) , imageTop}, - {imageLeft + w*(leftX + markerTriWidth) , imageTop}, - {imageLeft + w*leftX , imageTop - h*markerTriHeight}, - annoColor - ); - ImGui::GetWindowDrawList()->AddTriangle( - {imageLeft + w*(leftX - markerTriWidth) , imageTop}, - {imageLeft + w*(leftX + markerTriWidth) , imageTop}, - {imageLeft + w*leftX , imageTop - h*markerTriHeight}, - annoColorDark - ); - - // right triangle - ImGui::GetWindowDrawList()->AddTriangleFilled( - {imageLeft + w*(rightX - markerTriWidth) , imageTop}, - {imageLeft + w*(rightX + markerTriWidth) , imageTop}, - {imageLeft + w*rightX , imageTop - h*markerTriHeight}, - annoColor - ); - ImGui::GetWindowDrawList()->AddTriangle( - {imageLeft + w*(rightX - markerTriWidth) , imageTop}, - {imageLeft + w*(rightX + markerTriWidth) , imageTop}, - {imageLeft + w*rightX , imageTop - h*markerTriHeight}, - annoColorDark - ); - - // clang-format on - } - - */ -} - - -} // namespace polyscope diff --git a/src/internal.cpp b/src/internal.cpp index fb874d62..29e9c9f8 100644 --- a/src/internal.cpp +++ b/src/internal.cpp @@ -14,5 +14,13 @@ uint64_t getNextUniqueID() { return uniqueID++; } bool& pointCloudEfficiencyWarningReported = state::globalContext.pointCloudEfficiencyWarningReported; FloatingQuantityStructure*& globalFloatingQuantityStructure = state::globalContext.globalFloatingQuantityStructure; +float imguiStackMargin = 10; +float lastWindowHeightPolyscope = 200; +float lastWindowHeightUser = 200; +float lastRightSideFreeX = 10; +float lastRightSideFreeY = 10; +float leftWindowsWidth = -1.; +float rightWindowsWidth = -1.; + } // namespace internal } // namespace polyscope diff --git a/src/persistent_value.cpp b/src/persistent_value.cpp index 6476bb36..85e62f76 100644 --- a/src/persistent_value.cpp +++ b/src/persistent_value.cpp @@ -12,6 +12,7 @@ PersistentCache persistentCache_double; PersistentCache persistentCache_float; PersistentCache persistentCache_bool; PersistentCache persistentCache_string; +PersistentCache persistentCache_glmvec2; PersistentCache persistentCache_glmvec3; PersistentCache persistentCache_glmmat4; PersistentCache> persistentCache_scaleddouble; diff --git a/src/polyscope.cpp b/src/polyscope.cpp index 9d9b360e..171f8ee3 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -45,13 +45,8 @@ bool redrawNextFrame = true; bool unshowRequested = false; // Some state about imgui windows to stack them -float imguiStackMargin = 10; -float lastWindowHeightPolyscope = 200; -float lastWindowHeightUser = 200; constexpr float INITIAL_LEFT_WINDOWS_WIDTH = 305; constexpr float INITIAL_RIGHT_WINDOWS_WIDTH = 500; -float leftWindowsWidth = -1.; -float rightWindowsWidth = -1.; auto prevMainLoopTime = std::chrono::steady_clock::now(); float rollingMainLoopDurationMicrosec = 0.; @@ -137,12 +132,12 @@ void writePrefsFile() { } void setInitialWindowWidths() { - leftWindowsWidth = INITIAL_LEFT_WINDOWS_WIDTH * options::uiScale; - rightWindowsWidth = INITIAL_RIGHT_WINDOWS_WIDTH * options::uiScale; + internal::leftWindowsWidth = INITIAL_LEFT_WINDOWS_WIDTH * options::uiScale; + internal::rightWindowsWidth = INITIAL_RIGHT_WINDOWS_WIDTH * options::uiScale; } void ensureWindowWidthsSet() { - if (leftWindowsWidth <= 0. || rightWindowsWidth <= 0.) { + if (internal::leftWindowsWidth <= 0. || internal::rightWindowsWidth <= 0.) { setInitialWindowWidths(); } } @@ -662,14 +657,15 @@ void userGuiBegin() { ImVec2 userGuiLoc; if (options::userGuiIsOnRightSide) { // right side - userGuiLoc = ImVec2(view::windowWidth - (rightWindowsWidth + imguiStackMargin), imguiStackMargin); - ImGui::SetNextWindowSize(ImVec2(rightWindowsWidth, 0.)); + userGuiLoc = ImVec2(view::windowWidth - (internal::rightWindowsWidth + internal::imguiStackMargin), + internal::imguiStackMargin); + ImGui::SetNextWindowSize(ImVec2(internal::rightWindowsWidth, 0.)); } else { // left side if (options::buildDefaultGuiPanels) { - userGuiLoc = ImVec2(leftWindowsWidth + 3 * imguiStackMargin, imguiStackMargin); + userGuiLoc = ImVec2(internal::leftWindowsWidth + 3 * internal::imguiStackMargin, internal::imguiStackMargin); } else { - userGuiLoc = ImVec2(imguiStackMargin, imguiStackMargin); + userGuiLoc = ImVec2(internal::imguiStackMargin, internal::imguiStackMargin); } } @@ -682,10 +678,15 @@ void userGuiBegin() { void userGuiEnd() { if (options::userGuiIsOnRightSide) { - rightWindowsWidth = INITIAL_RIGHT_WINDOWS_WIDTH * options::uiScale; - lastWindowHeightUser = imguiStackMargin + ImGui::GetWindowHeight(); + internal::rightWindowsWidth = INITIAL_RIGHT_WINDOWS_WIDTH * options::uiScale; + internal::lastWindowHeightUser = + internal::imguiStackMargin + ImGui::GetWindowHeight(); // TODO using deprecated function + internal::lastRightSideFreeX = view::windowWidth - internal::imguiStackMargin; + internal::lastRightSideFreeY = internal::lastWindowHeightUser; } else { - lastWindowHeightUser = 0; + internal::lastWindowHeightUser = 0; + internal::lastRightSideFreeX = view::windowWidth - internal::imguiStackMargin; + internal::lastRightSideFreeY = 0; } ImGui::End(); ImGui::PopID(); @@ -698,8 +699,8 @@ void buildPolyscopeGui() { // Create window static bool showPolyscopeWindow = true; - ImGui::SetNextWindowPos(ImVec2(imguiStackMargin, imguiStackMargin)); - ImGui::SetNextWindowSize(ImVec2(leftWindowsWidth, 0.)); + ImGui::SetNextWindowPos(ImVec2(internal::imguiStackMargin, internal::imguiStackMargin)); + ImGui::SetNextWindowSize(ImVec2(internal::leftWindowsWidth, 0.)); ImGui::Begin("Polyscope", &showPolyscopeWindow); @@ -740,7 +741,8 @@ void buildPolyscopeGui() { } if (ImGui::IsItemHovered()) { - ImGui::SetNextWindowPos(ImVec2(2 * imguiStackMargin + leftWindowsWidth, imguiStackMargin)); + ImGui::SetNextWindowPos( + ImVec2(2 * internal::imguiStackMargin + internal::leftWindowsWidth, internal::imguiStackMargin)); ImGui::SetNextWindowSize(ImVec2(0., 0.)); // clang-format off @@ -848,8 +850,8 @@ void buildPolyscopeGui() { } - lastWindowHeightPolyscope = imguiStackMargin + ImGui::GetWindowHeight(); - leftWindowsWidth = ImGui::GetWindowWidth(); + internal::lastWindowHeightPolyscope = ImGui::GetWindowHeight(); + internal::leftWindowsWidth = ImGui::GetWindowWidth(); ImGui::End(); } @@ -860,9 +862,10 @@ void buildStructureGui() { // Create window static bool showStructureWindow = true; - ImGui::SetNextWindowPos(ImVec2(imguiStackMargin, lastWindowHeightPolyscope + 2 * imguiStackMargin)); - ImGui::SetNextWindowSize( - ImVec2(leftWindowsWidth, view::windowHeight - lastWindowHeightPolyscope - 3 * imguiStackMargin)); + ImGui::SetNextWindowPos( + ImVec2(internal::imguiStackMargin, internal::lastWindowHeightPolyscope + 2 * internal::imguiStackMargin)); + ImGui::SetNextWindowSize(ImVec2(internal::leftWindowsWidth, view::windowHeight - internal::lastWindowHeightPolyscope - + 3 * internal::imguiStackMargin)); ImGui::Begin("Structures", &showStructureWindow); // only show groups if there are any @@ -920,7 +923,7 @@ void buildStructureGui() { ImGui::PopID(); } - leftWindowsWidth = ImGui::GetWindowWidth(); + internal::leftWindowsWidth = ImGui::GetWindowWidth(); ImGui::End(); } @@ -930,9 +933,9 @@ void buildPickGui() { if (haveSelection()) { - ImGui::SetNextWindowPos(ImVec2(view::windowWidth - (rightWindowsWidth + imguiStackMargin), - 2 * imguiStackMargin + lastWindowHeightUser)); - ImGui::SetNextWindowSize(ImVec2(rightWindowsWidth, 0.)); + ImGui::SetNextWindowPos(ImVec2(view::windowWidth - (internal::rightWindowsWidth + internal::imguiStackMargin), + internal::lastRightSideFreeY + internal::imguiStackMargin)); + ImGui::SetNextWindowSize(ImVec2(internal::rightWindowsWidth, 0.)); ImGui::Begin("Selection", nullptr); PickResult selection = getSelection(); @@ -957,7 +960,8 @@ void buildPickGui() { ImGui::TextUnformatted("ERROR: INVALID STRUCTURE"); } - rightWindowsWidth = ImGui::GetWindowWidth(); + internal::rightWindowsWidth = ImGui::GetWindowWidth(); + internal::lastRightSideFreeY += internal::imguiStackMargin + ImGui::GetWindowHeight(); ImGui::End(); } } @@ -981,11 +985,15 @@ void buildUserGuiAndInvokeCallback() { if (beganUserGUI) { userGuiEnd(); } else { - lastWindowHeightUser = imguiStackMargin; + internal::lastWindowHeightUser = internal::imguiStackMargin; + internal::lastRightSideFreeX = view::windowWidth - internal::imguiStackMargin; + internal::lastRightSideFreeY = internal::imguiStackMargin; } } else { - lastWindowHeightUser = imguiStackMargin; + internal::lastWindowHeightUser = internal::imguiStackMargin; + internal::lastRightSideFreeX = view::windowWidth - internal::imguiStackMargin; + internal::lastRightSideFreeY = internal::imguiStackMargin; } } diff --git a/src/render/engine.cpp b/src/render/engine.cpp index c959a262..1c82db01 100644 --- a/src/render/engine.cpp +++ b/src/render/engine.cpp @@ -356,7 +356,7 @@ void Engine::buildEngineGui() { options::ssaaFactor = ssaaFactor; requestRedraw(); } - + if (ImGui::InputFloat("UI Scale", &options::uiScale, 0.25f)) { options::uiScale = std::min(options::uiScale, 4.f); options::uiScale = std::max(options::uiScale, 0.25f); @@ -1160,6 +1160,25 @@ void Engine::loadDefaultColorMaps() { loadDefaultColorMap("hsv"); } +std::shared_ptr Engine::getColorMapTexture2d(const std::string& cmapName) { + const ValueColorMap& colormap = render::engine->getColorMap(cmapName); + + // Fill a buffer with the data + unsigned int dataLength = colormap.values.size() * 3; + std::vector colorBuffer(dataLength); + for (unsigned int i = 0; i < colormap.values.size(); i++) { + colorBuffer[3 * i + 0] = static_cast(colormap.values[i][0]); + colorBuffer[3 * i + 1] = static_cast(colormap.values[i][1]); + colorBuffer[3 * i + 2] = static_cast(colormap.values[i][2]); + } + + std::shared_ptr textureBuffer = + engine->generateTextureBuffer(TextureFormat::RGB32F, 1, colormap.values.size(), &(colorBuffer[0])); + + textureBuffer->setFilterMode(FilterMode::Linear); + + return textureBuffer; +} void Engine::showTextureInImGuiWindow(std::string windowName, TextureBuffer* buffer) { ImGui::Begin(windowName.c_str()); diff --git a/src/surface_scalar_quantity.cpp b/src/surface_scalar_quantity.cpp index 35948f14..894c5f57 100644 --- a/src/surface_scalar_quantity.cpp +++ b/src/surface_scalar_quantity.cpp @@ -64,10 +64,7 @@ SurfaceVertexScalarQuantity::SurfaceVertexScalarQuantity(std::string name, const SurfaceMesh& mesh_, DataType dataType_) : SurfaceScalarQuantity(name, mesh_, "vertex", values_, dataType_) -{ - values.ensureHostBufferPopulated(); - hist.buildHistogram(values.data, dataType); -} +{} void SurfaceVertexScalarQuantity::createProgram() { // Create the program to draw this quantity @@ -132,11 +129,7 @@ SurfaceFaceScalarQuantity::SurfaceFaceScalarQuantity(std::string name, const std SurfaceMesh& mesh_, DataType dataType_) : SurfaceScalarQuantity(name, mesh_, "face", values_, dataType_) -{ - values.ensureHostBufferPopulated(); - parent.faceAreas.ensureHostBufferPopulated(); - hist.buildHistogram(values.data, dataType); -} +{} void SurfaceFaceScalarQuantity::createProgram() { // Create the program to draw this quantity @@ -182,10 +175,7 @@ SurfaceEdgeScalarQuantity::SurfaceEdgeScalarQuantity(std::string name, const std SurfaceMesh& mesh_, DataType dataType_) : SurfaceScalarQuantity(name, mesh_, "edge", values_, dataType_) -{ - values.ensureHostBufferPopulated(); - hist.buildHistogram(values.data, dataType); -} +{} void SurfaceEdgeScalarQuantity::createProgram() { // Create the program to draw this quantity @@ -227,10 +217,7 @@ SurfaceHalfedgeScalarQuantity::SurfaceHalfedgeScalarQuantity(std::string name, c SurfaceMesh& mesh_, DataType dataType_) : SurfaceScalarQuantity(name, mesh_, "halfedge", values_, dataType_) -{ - values.ensureHostBufferPopulated(); - hist.buildHistogram(values.data, dataType); -} +{} void SurfaceHalfedgeScalarQuantity::createProgram() { // Create the program to draw this quantity @@ -272,10 +259,7 @@ SurfaceCornerScalarQuantity::SurfaceCornerScalarQuantity(std::string name, const SurfaceMesh& mesh_, DataType dataType_) : SurfaceScalarQuantity(name, mesh_, "corner", values_, dataType_) -{ - values.ensureHostBufferPopulated(); - hist.buildHistogram(values.data, dataType); -} +{} void SurfaceCornerScalarQuantity::createProgram() { // Create the program to draw this quantity @@ -342,8 +326,6 @@ SurfaceTextureScalarQuantity::SurfaceTextureScalarQuantity(std::string name, Sur : SurfaceScalarQuantity(name, mesh_, "vertex", values_, dataType_), TextureMapQuantity(*this, dimX_, dimY_, origin_), param(param_) { values.setTextureSize(dimX, dimY); - values.ensureHostBufferPopulated(); - hist.buildHistogram(values.data, dataType); if (dataType == DataType::CATEGORICAL) { // default to nearest filtering for categorical data, it's probably what the user wants diff --git a/src/volume_mesh_scalar_quantity.cpp b/src/volume_mesh_scalar_quantity.cpp index 3170b2f2..127d8901 100644 --- a/src/volume_mesh_scalar_quantity.cpp +++ b/src/volume_mesh_scalar_quantity.cpp @@ -217,8 +217,8 @@ void VolumeMeshVertexScalarQuantity::buildCustomUI() { VolumeMeshScalarQuantity::buildCustomUI(); if (isDrawingLevelSet) { - ImGui::DragFloat("##value", &levelSetValue, 0.01f, (float)hist.colormapRange.first, - (float)hist.colormapRange.second); + ImGui::DragFloat("##value", &levelSetValue, 0.01f, (float)colorBar.colormapRange.first, + (float)colorBar.colormapRange.second); if (ImGui::BeginMenu("Show Quantity")) { std::map>::iterator it; for (it = parent.quantities.begin(); it != parent.quantities.end(); it++) { diff --git a/test/src/misc_test.cpp b/test/src/misc_test.cpp index 6862cd3c..e796ddcc 100644 --- a/test/src/misc_test.cpp +++ b/test/src/misc_test.cpp @@ -21,7 +21,32 @@ TEST_F(PolyscopeTest, TestScalarQuantity) { q1->setMapRange(newRange); EXPECT_EQ(newRange, q1->getMapRange()); + polyscope::show(3); + + polyscope::removeAllStructures(); +} + +TEST_F(PolyscopeTest, TestScalarColormapQuantity) { + auto psPoints = registerPointCloud(); + + std::vector vScalar(psPoints->nPoints(), 7.); + auto q1 = psPoints->addScalarQuantity("vScalar", vScalar); + q1->setEnabled(true); + polyscope::show(3); + // set colormap by name + q1->setColorMap("plasma"); + EXPECT_EQ("plasma", q1->getColorMap()); + polyscope::show(3); + + // enable the onscreen colormap + q1->setOnscreenColorbarEnabled(true); + EXPECT_TRUE(q1->getOnscreenColorbarEnabled()); + polyscope::show(3); + + // set its location manually + q1->setOnscreenColorbarLocation(glm::vec2(500.f, 500.f)); + EXPECT_EQ(glm::vec2(500.f, 500.f), q1->getOnscreenColorbarLocation()); polyscope::show(3); polyscope::removeAllStructures();