From 6afa12a71859cb2f73e6ab854de385a3c8c38dff Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sun, 8 Feb 2026 12:39:25 -0800 Subject: [PATCH 01/18] initial sparse volume grid --- include/polyscope/sparse_volume_grid.h | 116 ++++++++++ include/polyscope/sparse_volume_grid.ipp | 42 ++++ src/CMakeLists.txt | 5 + src/sparse_volume_grid.cpp | 263 +++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/include/polyscope_test.h | 1 + test/src/sparse_volume_grid_test.cpp | 64 ++++++ 7 files changed, 492 insertions(+) create mode 100644 include/polyscope/sparse_volume_grid.h create mode 100644 include/polyscope/sparse_volume_grid.ipp create mode 100644 src/sparse_volume_grid.cpp create mode 100644 test/src/sparse_volume_grid_test.cpp diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h new file mode 100644 index 00000000..c40b219e --- /dev/null +++ b/include/polyscope/sparse_volume_grid.h @@ -0,0 +1,116 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/color_management.h" +#include "polyscope/polyscope.h" +#include "polyscope/render/engine.h" +#include "polyscope/standardize_data_array.h" +#include "polyscope/structure.h" + +#include +#include + +namespace polyscope { + +class SparseVolumeGrid; + +class SparseVolumeGrid : public Structure { +public: + // Construct a new sparse volume grid structure + SparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, + std::vector occupiedCells); + + // === Overloads + + // Standard structure overrides + virtual void draw() override; + virtual void drawDelayed() override; + virtual void drawPick() override; + virtual void drawPickDelayed() override; + virtual void updateObjectSpaceBounds() override; + virtual std::string typeName() override; + virtual void refresh() override; + + // Build the imgui display + virtual void buildCustomUI() override; + virtual void buildCustomOptionsUI() override; + virtual void buildPickUI(const PickResult& result) override; + + // Misc data + static const std::string structureTypeName; + + // === Geometry members + render::ManagedBuffer cellPositions; + render::ManagedBuffer cellIndices; // uvec3 for GPU; derived from signed occupiedCells + + // === Grid info + uint64_t nCells() const; + glm::vec3 getOrigin() const; + glm::vec3 getGridCellWidth() const; + + // === Getters and setters for visualization settings + + // Color of the grid cubes + SparseVolumeGrid* setColor(glm::vec3 val); + glm::vec3 getColor(); + + // Material + SparseVolumeGrid* setMaterial(std::string name); + std::string getMaterial(); + + // Scaling factor for the size of the little cubes + SparseVolumeGrid* setCubeSizeFactor(double newVal); + double getCubeSizeFactor(); + +private: + // Field data + glm::vec3 origin; + glm::vec3 gridCellWidth; + + // === Storage for managed quantities + std::vector cellPositionsData; + std::vector cellIndicesData; // uvec3 for GPU attribute + + // User-facing occupied cell indices (signed) + std::vector occupiedCellsData; + + // === Visualization parameters + PersistentValue color; + PersistentValue material; + PersistentValue cubeSizeFactor; + + // Compute cell positions and GPU indices from occupiedCellsData + void computeCellPositions(); + + // Picking-related + size_t globalPickConstant = INVALID_IND_64; + glm::vec3 pickColor; + + // Drawing related things + std::shared_ptr program; + std::shared_ptr pickProgram; + + // === Helpers + void ensureRenderProgramPrepared(); + void ensurePickProgramPrepared(); +}; + + +// Register a sparse volume grid +template +SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, + const T& occupiedCells); + +// Non-template overloads +SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, + const std::vector& occupiedCells); + +// Shorthand to get a sparse volume grid from polyscope +inline SparseVolumeGrid* getSparseVolumeGrid(std::string name = ""); +inline bool hasSparseVolumeGrid(std::string name = ""); +inline void removeSparseVolumeGrid(std::string name = "", bool errorIfAbsent = false); + +} // namespace polyscope + +#include "polyscope/sparse_volume_grid.ipp" diff --git a/include/polyscope/sparse_volume_grid.ipp b/include/polyscope/sparse_volume_grid.ipp new file mode 100644 index 00000000..e0735dfc --- /dev/null +++ b/include/polyscope/sparse_volume_grid.ipp @@ -0,0 +1,42 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/utilities.h" + +namespace polyscope { + +inline uint64_t SparseVolumeGrid::nCells() const { return occupiedCellsData.size(); } + +inline glm::vec3 SparseVolumeGrid::getOrigin() const { return origin; } +inline glm::vec3 SparseVolumeGrid::getGridCellWidth() const { return gridCellWidth; } + +// Template registration +template +SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, + const T& occupiedCells) { + checkInitialized(); + + SparseVolumeGrid* s = new SparseVolumeGrid(name, origin, gridCellWidth, + standardizeVectorArray(occupiedCells)); + + bool success = registerStructure(s); + if (!success) { + safeDelete(s); + } + + return s; +} + +// Shorthand to get a sparse volume grid from polyscope +inline SparseVolumeGrid* getSparseVolumeGrid(std::string name) { + return dynamic_cast(getStructure(SparseVolumeGrid::structureTypeName, name)); +} +inline bool hasSparseVolumeGrid(std::string name) { + return hasStructure(SparseVolumeGrid::structureTypeName, name); +} +inline void removeSparseVolumeGrid(std::string name, bool errorIfAbsent) { + removeStructure(SparseVolumeGrid::structureTypeName, name, errorIfAbsent); +} + +} // namespace polyscope diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4953d1bd..dca84c0c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -225,6 +225,9 @@ SET(SRCS volume_grid.cpp volume_grid_scalar_quantity.cpp + # Sparse volume grid + sparse_volume_grid.cpp + # Camera view camera_view.cpp @@ -359,6 +362,8 @@ SET(HEADERS ${INCLUDE_ROOT}/volume_grid.ipp ${INCLUDE_ROOT}/volume_grid_quantity.h ${INCLUDE_ROOT}/volume_grid_scalar_quantity.h + ${INCLUDE_ROOT}/sparse_volume_grid.h + ${INCLUDE_ROOT}/sparse_volume_grid.ipp ${INCLUDE_ROOT}/weak_handle.h ) diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp new file mode 100644 index 00000000..74ebe2ef --- /dev/null +++ b/src/sparse_volume_grid.cpp @@ -0,0 +1,263 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope/sparse_volume_grid.h" + +#include "polyscope/pick.h" + +#include "imgui.h" + +namespace polyscope { + +// Initialize statics +const std::string SparseVolumeGrid::structureTypeName = "Sparse Volume Grid"; + +SparseVolumeGrid::SparseVolumeGrid(std::string name, glm::vec3 origin_, glm::vec3 gridCellWidth_, + std::vector occupiedCells) + : Structure(name, typeName()), + + // clang-format off + // == managed quantities + cellPositions(this, uniquePrefix() + "#cellPositions", cellPositionsData, std::bind(&SparseVolumeGrid::computeCellPositions, this)), + cellIndices(this, uniquePrefix() + "#cellIndices", cellIndicesData, [](){/* do nothing, gets handled by computeCellPositions */}), + + origin(origin_), gridCellWidth(gridCellWidth_), + + // == persistent options + color( uniquePrefix() + "color", getNextUniqueColor()), + material( uniquePrefix() + "material", "clay"), + cubeSizeFactor( uniquePrefix() + "cubeSizeFactor", 0.f) +// clang-format on +{ + occupiedCellsData = std::move(occupiedCells); + + computeCellPositions(); + + cullWholeElements.setPassive(true); + updateObjectSpaceBounds(); +} + + +void SparseVolumeGrid::computeCellPositions() { + size_t n = occupiedCellsData.size(); + + cellPositionsData.resize(n); + cellIndicesData.resize(n); + + for (size_t i = 0; i < n; i++) { + glm::ivec3 ijk = occupiedCellsData[i]; + cellPositionsData[i] = origin + (glm::vec3(ijk) + 0.5f) * gridCellWidth; + cellIndicesData[i] = glm::uvec3(ijk); // cast to unsigned for GPU attribute + } + + cellPositions.markHostBufferUpdated(); + cellIndices.markHostBufferUpdated(); +} + + +void SparseVolumeGrid::buildCustomUI() { + ImGui::Text("%llu cells", static_cast(nCells())); + + { // Color + if (ImGui::ColorEdit3("Color", &color.get()[0], ImGuiColorEditFlags_NoInputs)) setColor(color.get()); + } +} + + +void SparseVolumeGrid::buildCustomOptionsUI() { + if (render::buildMaterialOptionsGui(material.get())) { + material.manuallyChanged(); + setMaterial(material.get()); + } + + // Shrinky effect + if (ImGui::SliderFloat("Cell Shrink", &cubeSizeFactor.get(), 0.0, 1., "%.3f", ImGuiSliderFlags_Logarithmic)) { + cubeSizeFactor.manuallyChanged(); + requestRedraw(); + } +} + + +void SparseVolumeGrid::draw() { + if (!enabled.get()) return; + + // Ensure we have prepared buffers + ensureRenderProgramPrepared(); + + // Set program uniforms + setStructureUniforms(*program); + program->setUniform("u_gridSpacing", gridCellWidth); + program->setUniform("u_cubeSizeFactor", 1.f - cubeSizeFactor.get()); + program->setUniform("u_baseColor", color.get()); + render::engine->setMaterialUniforms(*program, material.get()); + + // Draw the actual grid + render::engine->setBackfaceCull(true); + program->draw(); +} + + +void SparseVolumeGrid::drawDelayed() { + if (!enabled.get()) return; +} + + +void SparseVolumeGrid::drawPick() { + if (!isEnabled()) return; + + ensurePickProgramPrepared(); + + // Set program uniforms + setStructureUniforms(*pickProgram); + pickProgram->setUniform("u_gridSpacing", gridCellWidth); + pickProgram->setUniform("u_cubeSizeFactor", 1.f - cubeSizeFactor.get()); + pickProgram->setUniform("u_pickColor", pickColor); + + // Draw the actual grid + render::engine->setBackfaceCull(true); + pickProgram->draw(); +} + + +void SparseVolumeGrid::drawPickDelayed() { + if (!isEnabled()) return; +} + + +void SparseVolumeGrid::ensureRenderProgramPrepared() { + if (program) return; + + // clang-format off + program = render::engine->requestShader("GRIDCUBE", + render::engine->addMaterialRules(material.get(), + addStructureRules({"SHADE_BASECOLOR"}) + ) + ); + // clang-format on + + program->setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); + program->setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); + + render::engine->setMaterial(*program, material.get()); +} + + +void SparseVolumeGrid::ensurePickProgramPrepared() { + if (pickProgram) return; + + // clang-format off + pickProgram = render::engine->requestShader( + "GRIDCUBE", + addStructureRules({"GRIDCUBE_CONSTANT_PICK"}), + render::ShaderReplacementDefaults::Pick + ); + // clang-format on + + pickProgram->setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); + pickProgram->setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); + + if (globalPickConstant == INVALID_IND_64) { + globalPickConstant = pick::requestPickBufferRange(this, 1); + pickColor = pick::indToVec(static_cast(globalPickConstant)); + } +} + + +void SparseVolumeGrid::updateObjectSpaceBounds() { + + if (cellPositionsData.empty()) { + // no cells, degenerate bounds at origin + objectSpaceBoundingBox = std::make_tuple(origin, origin); + objectSpaceLengthScale = glm::length(gridCellWidth); + return; + } + + glm::vec3 bboxMin = cellPositionsData[0]; + glm::vec3 bboxMax = cellPositionsData[0]; + for (const glm::vec3& p : cellPositionsData) { + bboxMin = glm::min(bboxMin, p); + bboxMax = glm::max(bboxMax, p); + } + + // Expand by half a cell width in each direction (positions are cell centers) + glm::vec3 halfCell = 0.5f * gridCellWidth; + bboxMin -= halfCell; + bboxMax += halfCell; + + objectSpaceBoundingBox = std::make_tuple(bboxMin, bboxMax); + objectSpaceLengthScale = glm::length(bboxMax - bboxMin); +} + + +std::string SparseVolumeGrid::typeName() { return structureTypeName; } + + +void SparseVolumeGrid::refresh() { + Structure::refresh(); + + program.reset(); + pickProgram.reset(); +} + + +void SparseVolumeGrid::buildPickUI(const PickResult& rawResult) { + + // Determine which cell was clicked, CPU-side + glm::vec3 localPos = (rawResult.position - origin) / gridCellWidth; + + // Find the cell index + glm::ivec3 cellInd3{static_cast(std::floor(localPos.x)), static_cast(std::floor(localPos.y)), + static_cast(std::floor(localPos.z))}; + + ImGui::TextUnformatted(("Cell index: (" + std::to_string(cellInd3.x) + "," + std::to_string(cellInd3.y) + "," + + std::to_string(cellInd3.z) + ")") + .c_str()); + + glm::vec3 cellCenter = origin + (glm::vec3(cellInd3) + 0.5f) * gridCellWidth; + std::stringstream buffer; + buffer << cellCenter; + ImGui::TextUnformatted(("Position: " + buffer.str()).c_str()); +} + + +// === Option getters and setters + +SparseVolumeGrid* SparseVolumeGrid::setColor(glm::vec3 val) { + color = val; + requestRedraw(); + return this; +} +glm::vec3 SparseVolumeGrid::getColor() { return color.get(); } + +SparseVolumeGrid* SparseVolumeGrid::setMaterial(std::string m) { + material = m; + refresh(); + requestRedraw(); + return this; +} +std::string SparseVolumeGrid::getMaterial() { return material.get(); } + +SparseVolumeGrid* SparseVolumeGrid::setCubeSizeFactor(double newVal) { + cubeSizeFactor = newVal; + requestRedraw(); + return this; +} +double SparseVolumeGrid::getCubeSizeFactor() { return cubeSizeFactor.get(); } + + +// === Register functions (non-template overload) + +SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, + const std::vector& occupiedCells) { + checkInitialized(); + + SparseVolumeGrid* s = new SparseVolumeGrid(name, origin, gridCellWidth, occupiedCells); + + bool success = registerStructure(s); + if (!success) { + safeDelete(s); + } + + return s; +} + +} // namespace polyscope diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 020cdc72..0745f94d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -104,6 +104,7 @@ set(TEST_SRCS src/surface_mesh_test.cpp src/volume_mesh_test.cpp src/volume_grid_test.cpp + src/sparse_volume_grid_test.cpp src/camera_view_test.cpp src/group_test.cpp src/floating_test.cpp diff --git a/test/include/polyscope_test.h b/test/include/polyscope_test.h index f73dfd95..548f11bd 100644 --- a/test/include/polyscope_test.h +++ b/test/include/polyscope_test.h @@ -17,6 +17,7 @@ #include "polyscope/surface_mesh.h" #include "polyscope/types.h" #include "polyscope/volume_grid.h" +#include "polyscope/sparse_volume_grid.h" #include "polyscope/volume_mesh.h" // Which polyscope backend to use for testing diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp new file mode 100644 index 00000000..c7c727e0 --- /dev/null +++ b/test/src/sparse_volume_grid_test.cpp @@ -0,0 +1,64 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope_test.h" + + +// ============================================================ +// =============== Sparse volume grid tests +// ============================================================ + +TEST_F(PolyscopeTest, SparseVolumeGridShow) { + glm::vec3 origin{-3., -3., -3.}; + glm::vec3 cellWidth{0.5, 0.5, 0.5}; + + // Create some occupied cells + std::vector occupiedCells; + for (uint32_t i = 0; i < 8; i += 2) { + for (uint32_t j = 0; j < 10; j += 2) { + for (uint32_t k = 0; k < 12; k += 2) { + occupiedCells.push_back({i, j, k}); + } + } + } + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + + polyscope::show(3); + + EXPECT_TRUE(polyscope::hasSparseVolumeGrid("test sparse grid")); + EXPECT_FALSE(polyscope::hasSparseVolumeGrid("other grid")); + polyscope::removeAllStructures(); + EXPECT_FALSE(polyscope::hasSparseVolumeGrid("test sparse grid")); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridBasicOptions) { + glm::vec3 origin{-3., -3., -3.}; + glm::vec3 cellWidth{0.5, 0.5, 0.5}; + + std::vector occupiedCells; + for (uint32_t i = 0; i < 8; i += 2) { + for (uint32_t j = 0; j < 10; j += 2) { + for (uint32_t k = 0; k < 12; k += 2) { + occupiedCells.push_back({i, j, k}); + } + } + } + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + + EXPECT_EQ(psGrid->nCells(), occupiedCells.size()); + + // Material + psGrid->setMaterial("flat"); + EXPECT_EQ(psGrid->getMaterial(), "flat"); + polyscope::show(3); + + // Grid size factor + psGrid->setCubeSizeFactor(0.5); + polyscope::show(3); + + polyscope::removeAllStructures(); +} From 2295074f1a63f981bdfd9bd77375d0c62aa37f3c Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 9 Feb 2026 09:13:09 -0800 Subject: [PATCH 02/18] initial impl for sparse volume grid quantities --- examples/demo-app/demo_app.cpp | 98 ++++++++++ .../render/opengl/shaders/grid_shaders.h | 6 + include/polyscope/sparse_volume_grid.h | 41 +++++ include/polyscope/sparse_volume_grid.ipp | 39 ++++ .../sparse_volume_grid_color_quantity.h | 41 +++++ .../polyscope/sparse_volume_grid_quantity.h | 21 +++ .../sparse_volume_grid_scalar_quantity.h | 45 +++++ src/CMakeLists.txt | 5 + src/render/mock_opengl/mock_gl_engine.cpp | 4 + src/render/opengl/gl_engine.cpp | 4 + src/render/opengl/shaders/grid_shaders.cpp | 165 +++++++++++++++++ src/sparse_volume_grid.cpp | 53 ++++++ src/sparse_volume_grid_color_quantity.cpp | 157 ++++++++++++++++ src/sparse_volume_grid_scalar_quantity.cpp | 169 ++++++++++++++++++ test/src/sparse_volume_grid_test.cpp | 127 +++++++++++++ 15 files changed, 975 insertions(+) create mode 100644 include/polyscope/sparse_volume_grid_color_quantity.h create mode 100644 include/polyscope/sparse_volume_grid_quantity.h create mode 100644 include/polyscope/sparse_volume_grid_scalar_quantity.h create mode 100644 src/sparse_volume_grid_color_quantity.cpp create mode 100644 src/sparse_volume_grid_scalar_quantity.cpp diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index eaed4407..25627dec 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -16,9 +16,11 @@ #include "polyscope/types.h" #include "polyscope/view.h" #include "polyscope/volume_grid.h" +#include "polyscope/sparse_volume_grid.h" #include "polyscope/volume_mesh.h" #include +#include #include #include @@ -544,6 +546,98 @@ void addVolumeGrid() { } +void addSparseVolumeGrid() { + glm::vec3 origin{-5., -5., -5.}; + glm::vec3 cellWidth{0.5, 0.5, 0.5}; + + // Build a spherical shell of occupied cells + std::vector occupiedCells; + for (int i = -10; i <= 10; i++) { + for (int j = -10; j <= 10; j++) { + for (int k = -10; k <= 10; k++) { + glm::vec3 cellCenter = origin + (glm::vec3(i, j, k) + 0.5f) * cellWidth; + float dist = glm::length(cellCenter - origin); + if (dist >= 2.0f && dist <= 4.0f) { + occupiedCells.push_back({i, j, k}); + } + } + } + } + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("sparse grid", origin, cellWidth, occupiedCells); + + // Cell scalar: distance from origin + { + std::vector cellDist(occupiedCells.size()); + for (size_t i = 0; i < occupiedCells.size(); i++) { + glm::vec3 cellCenter = origin + (glm::vec3(occupiedCells[i]) + 0.5f) * cellWidth; + cellDist[i] = glm::length(cellCenter - origin); + } + psGrid->addCellScalarQuantity("cell distance", cellDist)->setEnabled(true); + } + + // Node scalar: x-coordinate at node positions + { + // Gather all unique nodes for the occupied cells + // Node (ci+dx-1, cj+dy-1, ck+dz-1) for dx,dy,dz in {0,1} + std::set> nodeSet; + for (const auto& ci : occupiedCells) { + for (int dx = 0; dx < 2; dx++) { + for (int dy = 0; dy < 2; dy++) { + for (int dz = 0; dz < 2; dz++) { + nodeSet.insert({ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1}); + } + } + } + } + + std::vector nodeIndices; + std::vector nodeValues; + for (const auto& [ni, nj, nk] : nodeSet) { + nodeIndices.push_back({ni, nj, nk}); + glm::vec3 nodePos = origin + glm::vec3(ni, nj, nk) * cellWidth; + nodeValues.push_back(nodePos.x); + } + psGrid->addNodeScalarQuantity("node x-coord", nodeIndices, nodeValues); + } + + // Cell color: RGB from normalized position + { + std::vector cellColors(occupiedCells.size()); + for (size_t i = 0; i < occupiedCells.size(); i++) { + glm::vec3 cellCenter = origin + (glm::vec3(occupiedCells[i]) + 0.5f) * cellWidth; + // Map to [0,1] range + cellColors[i] = glm::clamp((cellCenter - origin) / 10.f + 0.5f, 0.f, 1.f); + } + psGrid->addCellColorQuantity("cell color", cellColors); + } + + // Node color: RGB from normalized node position + { + std::set> nodeSet; + for (const auto& ci : occupiedCells) { + for (int dx = 0; dx < 2; dx++) { + for (int dy = 0; dy < 2; dy++) { + for (int dz = 0; dz < 2; dz++) { + nodeSet.insert({ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1}); + } + } + } + } + + std::vector nodeIndices; + std::vector nodeColors; + for (const auto& [ni, nj, nk] : nodeSet) { + nodeIndices.push_back({ni, nj, nk}); + glm::vec3 nodePos = origin + glm::vec3(ni, nj, nk) * cellWidth; + nodeColors.push_back(glm::clamp((nodePos - origin) / 10.f + 0.5f, 0.f, 1.f)); + } + psGrid->addNodeColorQuantity("node color", nodeIndices, nodeColors); + } +} + + void loadFloatingImageData(polyscope::CameraView* targetView = nullptr) { // load an image from disk as example data @@ -864,6 +958,10 @@ void callback() { addVolumeGrid(); } + if (ImGui::Button("add sparse volume grid")) { + addSparseVolumeGrid(); + } + // ImPlot // dummy data if (ImGui::TreeNode("ImPlot")) { diff --git a/include/polyscope/render/opengl/shaders/grid_shaders.h b/include/polyscope/render/opengl/shaders/grid_shaders.h index 225891c2..3cd8e0f2 100644 --- a/include/polyscope/render/opengl/shaders/grid_shaders.h +++ b/include/polyscope/render/opengl/shaders/grid_shaders.h @@ -23,6 +23,12 @@ extern const ShaderReplacementRule GRIDCUBE_WIREFRAME; extern const ShaderReplacementRule GRIDCUBE_CONSTANT_PICK; extern const ShaderReplacementRule GRIDCUBE_CULLPOS_FROM_CENTER; +// Attribute-based rules for sparse volume grid quantities +extern const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR; +extern const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR; +extern const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR; +extern const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR; + } // namespace backend_openGL3 } // namespace render diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h index c40b219e..29932ce1 100644 --- a/include/polyscope/sparse_volume_grid.h +++ b/include/polyscope/sparse_volume_grid.h @@ -8,12 +8,18 @@ #include "polyscope/standardize_data_array.h" #include "polyscope/structure.h" +#include "polyscope/sparse_volume_grid_color_quantity.h" +#include "polyscope/sparse_volume_grid_quantity.h" +#include "polyscope/sparse_volume_grid_scalar_quantity.h" + #include #include namespace polyscope { class SparseVolumeGrid; +class SparseVolumeGridScalarQuantity; +class SparseVolumeGridColorQuantity; class SparseVolumeGrid : public Structure { public: @@ -48,6 +54,30 @@ class SparseVolumeGrid : public Structure { uint64_t nCells() const; glm::vec3 getOrigin() const; glm::vec3 getGridCellWidth() const; + const std::vector& getOccupiedCells() const; + + // === Quantities + + // Cell scalar + template + SparseVolumeGridScalarQuantity* addCellScalarQuantity(std::string name, const T& values, + DataType type = DataType::STANDARD); + + // Node scalar + template + SparseVolumeGridScalarQuantity* addNodeScalarQuantity(std::string name, const TI& nodeIndices, + const TV& nodeValues, DataType type = DataType::STANDARD); + + // Cell color + template + SparseVolumeGridColorQuantity* addCellColorQuantity(std::string name, const T& colors); + + // Node color + template + SparseVolumeGridColorQuantity* addNodeColorQuantity(std::string name, const TI& nodeIndices, const TC& nodeColors); + + // Set cell geometry attributes on a shader program + void setCellGeometryAttributes(render::ShaderProgram& p); // === Getters and setters for visualization settings @@ -94,6 +124,17 @@ class SparseVolumeGrid : public Structure { // === Helpers void ensureRenderProgramPrepared(); void ensurePickProgramPrepared(); + + // Quantity impl methods + SparseVolumeGridScalarQuantity* addCellScalarQuantityImpl(std::string name, const std::vector& data, + DataType type); + SparseVolumeGridScalarQuantity* addNodeScalarQuantityImpl(std::string name, + const std::vector& nodeIndices, + const std::vector& nodeValues, DataType type); + SparseVolumeGridColorQuantity* addCellColorQuantityImpl(std::string name, const std::vector& colors); + SparseVolumeGridColorQuantity* addNodeColorQuantityImpl(std::string name, + const std::vector& nodeIndices, + const std::vector& nodeColors); }; diff --git a/include/polyscope/sparse_volume_grid.ipp b/include/polyscope/sparse_volume_grid.ipp index e0735dfc..dfc3dfd9 100644 --- a/include/polyscope/sparse_volume_grid.ipp +++ b/include/polyscope/sparse_volume_grid.ipp @@ -10,6 +10,7 @@ inline uint64_t SparseVolumeGrid::nCells() const { return occupiedCellsData.size inline glm::vec3 SparseVolumeGrid::getOrigin() const { return origin; } inline glm::vec3 SparseVolumeGrid::getGridCellWidth() const { return gridCellWidth; } +inline const std::vector& SparseVolumeGrid::getOccupiedCells() const { return occupiedCellsData; } // Template registration template @@ -39,4 +40,42 @@ inline void removeSparseVolumeGrid(std::string name, bool errorIfAbsent) { removeStructure(SparseVolumeGrid::structureTypeName, name, errorIfAbsent); } +// ===================================================== +// ============== Quantities +// ===================================================== + +template +SparseVolumeGridScalarQuantity* SparseVolumeGrid::addCellScalarQuantity(std::string name, const T& values, + DataType type) { + validateSize(values, nCells(), "sparse volume grid cell scalar quantity " + name); + return addCellScalarQuantityImpl(name, standardizeArray(values), type); +} + +template +SparseVolumeGridScalarQuantity* SparseVolumeGrid::addNodeScalarQuantity(std::string name, const TI& nodeIndices, + const TV& nodeValues, DataType type) { + if (adaptorF_size(nodeIndices) != adaptorF_size(nodeValues)) { + exception("SparseVolumeGrid::addNodeScalarQuantity: nodeIndices and nodeValues must have the same size"); + } + return addNodeScalarQuantityImpl(name, standardizeVectorArray(nodeIndices), + standardizeArray(nodeValues), type); +} + +template +SparseVolumeGridColorQuantity* SparseVolumeGrid::addCellColorQuantity(std::string name, const T& colors) { + validateSize(colors, nCells(), "sparse volume grid cell color quantity " + name); + return addCellColorQuantityImpl(name, standardizeVectorArray(colors)); +} + +template +SparseVolumeGridColorQuantity* SparseVolumeGrid::addNodeColorQuantity(std::string name, const TI& nodeIndices, + const TC& nodeColors) { + if (adaptorF_size(nodeIndices) != adaptorF_size(nodeColors)) { + exception("SparseVolumeGrid::addNodeColorQuantity: nodeIndices and nodeColors must have the same size"); + } + return addNodeColorQuantityImpl(name, standardizeVectorArray(nodeIndices), + standardizeVectorArray(nodeColors)); +} + + } // namespace polyscope diff --git a/include/polyscope/sparse_volume_grid_color_quantity.h b/include/polyscope/sparse_volume_grid_color_quantity.h new file mode 100644 index 00000000..ac9af034 --- /dev/null +++ b/include/polyscope/sparse_volume_grid_color_quantity.h @@ -0,0 +1,41 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/color_quantity.h" +#include "polyscope/sparse_volume_grid.h" +#include "polyscope/sparse_volume_grid_quantity.h" + +#include + +namespace polyscope { + +class SparseVolumeGridColorQuantity : public SparseVolumeGridQuantity, + public ColorQuantity { +public: + // Cell color constructor + SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& colors); + + // Node color constructor + SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& nodeIndices, + const std::vector& nodeColors); + + virtual void draw() override; + virtual void refresh() override; + + virtual std::string niceName() override; + +private: + bool isNodeQuantity = false; + void createProgram(); + std::shared_ptr program; + + // Node-mode packed data (8 corner colors, separated by R/G/B channel, 2 vec4 each) + std::vector nodeR04Data, nodeR47Data, nodeG04Data, nodeG47Data, nodeB04Data, nodeB47Data; + std::unique_ptr> nodeR04, nodeR47, nodeG04, nodeG47, nodeB04, nodeB47; + + void packNodeColors(const std::vector& nodeIndices, const std::vector& nodeColors); +}; + + +} // namespace polyscope diff --git a/include/polyscope/sparse_volume_grid_quantity.h b/include/polyscope/sparse_volume_grid_quantity.h new file mode 100644 index 00000000..5a671cfb --- /dev/null +++ b/include/polyscope/sparse_volume_grid_quantity.h @@ -0,0 +1,21 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/quantity.h" +#include "polyscope/structure.h" + +namespace polyscope { + +class SparseVolumeGrid; + +class SparseVolumeGridQuantity : public Quantity { +public: + SparseVolumeGridQuantity(std::string name, SparseVolumeGrid& parentStructure, bool dominates = false); + virtual ~SparseVolumeGridQuantity() {}; + + SparseVolumeGrid& parent; // shadows and hides the generic member in Quantity +}; + + +} // namespace polyscope diff --git a/include/polyscope/sparse_volume_grid_scalar_quantity.h b/include/polyscope/sparse_volume_grid_scalar_quantity.h new file mode 100644 index 00000000..1debde31 --- /dev/null +++ b/include/polyscope/sparse_volume_grid_scalar_quantity.h @@ -0,0 +1,45 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include "polyscope/render/color_maps.h" +#include "polyscope/scalar_quantity.h" +#include "polyscope/sparse_volume_grid.h" +#include "polyscope/sparse_volume_grid_quantity.h" + +#include + +namespace polyscope { + +class SparseVolumeGridScalarQuantity : public SparseVolumeGridQuantity, + public ScalarQuantity { + +public: + // Cell scalar constructor + SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& values, + DataType dataType); + + // Node scalar constructor + SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& nodeIndices, + const std::vector& nodeValues, DataType dataType); + + virtual void draw() override; + virtual void buildCustomUI() override; + virtual void refresh() override; + + virtual std::string niceName() override; + +private: + bool isNodeQuantity = false; + void createProgram(); + std::shared_ptr program; + + // Node-mode packed data (8 corner values per cell, packed into 2 vec4) + std::vector nodeValues04Data, nodeValues47Data; + std::unique_ptr> nodeValues04, nodeValues47; + + void packNodeValues(const std::vector& nodeIndices, const std::vector& nodeValues); +}; + + +} // namespace polyscope diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dca84c0c..8b4eca3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -227,6 +227,8 @@ SET(SRCS # Sparse volume grid sparse_volume_grid.cpp + sparse_volume_grid_scalar_quantity.cpp + sparse_volume_grid_color_quantity.cpp # Camera view camera_view.cpp @@ -364,6 +366,9 @@ SET(HEADERS ${INCLUDE_ROOT}/volume_grid_scalar_quantity.h ${INCLUDE_ROOT}/sparse_volume_grid.h ${INCLUDE_ROOT}/sparse_volume_grid.ipp + ${INCLUDE_ROOT}/sparse_volume_grid_quantity.h + ${INCLUDE_ROOT}/sparse_volume_grid_scalar_quantity.h + ${INCLUDE_ROOT}/sparse_volume_grid_color_quantity.h ${INCLUDE_ROOT}/weak_handle.h ) diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index 7bf0f0ff..14d4ccda 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -2142,6 +2142,10 @@ void MockGLEngine::populateDefaultShadersAndRules() { registerShaderRule("GRIDCUBE_WIREFRAME", GRIDCUBE_WIREFRAME); registerShaderRule("GRIDCUBE_CONSTANT_PICK", GRIDCUBE_CONSTANT_PICK); registerShaderRule("GRIDCUBE_CULLPOS_FROM_CENTER", GRIDCUBE_CULLPOS_FROM_CENTER); + registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR", GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR); + registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR); + registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR", GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR); + registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR", GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR); // sphere things registerShaderRule("SPHERE_PROPAGATE_VALUE", SPHERE_PROPAGATE_VALUE); diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index d54f81ef..cbc57a47 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -2609,6 +2609,10 @@ void GLEngine::populateDefaultShadersAndRules() { registerShaderRule("GRIDCUBE_WIREFRAME", GRIDCUBE_WIREFRAME); registerShaderRule("GRIDCUBE_CONSTANT_PICK", GRIDCUBE_CONSTANT_PICK); registerShaderRule("GRIDCUBE_CULLPOS_FROM_CENTER", GRIDCUBE_CULLPOS_FROM_CENTER); + registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR", GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR); + registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR); + registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR", GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR); + registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR", GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR); // sphere things registerShaderRule("SPHERE_PROPAGATE_VALUE", SPHERE_PROPAGATE_VALUE); diff --git a/src/render/opengl/shaders/grid_shaders.cpp b/src/render/opengl/shaders/grid_shaders.cpp index b55603ca..3c649bad 100644 --- a/src/render/opengl/shaders/grid_shaders.cpp +++ b/src/render/opengl/shaders/grid_shaders.cpp @@ -462,6 +462,171 @@ const ShaderReplacementRule GRIDCUBE_CULLPOS_FROM_CENTER( /* textures */ {} ); +// == Attribute-based rules for sparse volume grid quantities == + +const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR ( + /* rule name */ "GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR", + { /* replacement sources */ + {"VERT_DECLARATIONS", R"( + in float a_value; + out float a_valueToGeom; + )"}, + {"VERT_ASSIGNMENTS", R"( + a_valueToGeom = a_value; + )"}, + {"GEOM_DECLARATIONS", R"( + in float a_valueToGeom[]; + out float a_valueToFrag; + )"}, + {"GEOM_PER_EMIT", R"( + a_valueToFrag = a_valueToGeom[0]; + )"}, + {"FRAG_DECLARATIONS", R"( + in float a_valueToFrag; + )"}, + {"GENERATE_SHADE_VALUE", R"( + float shadeValue = a_valueToFrag; + )"}, + }, + /* uniforms */ {}, + /* attributes */ { + {"a_value", RenderDataType::Float}, + }, + /* textures */ {} +); + +const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR ( + /* rule name */ "GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", + { /* replacement sources */ + {"VERT_DECLARATIONS", R"( + in vec3 a_color; + out vec3 a_colorToGeom; + )"}, + {"VERT_ASSIGNMENTS", R"( + a_colorToGeom = a_color; + )"}, + {"GEOM_DECLARATIONS", R"( + in vec3 a_colorToGeom[]; + flat out vec3 a_colorToFrag; + )"}, + {"GEOM_PER_EMIT", R"( + a_colorToFrag = a_colorToGeom[0]; + )"}, + {"FRAG_DECLARATIONS", R"( + flat in vec3 a_colorToFrag; + )"}, + {"GENERATE_SHADE_VALUE", R"( + vec3 shadeColor = a_colorToFrag; + )"}, + }, + /* uniforms */ {}, + /* attributes */ { + {"a_color", RenderDataType::Vector3Float}, + }, + /* textures */ {} +); + +const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR ( + /* rule name */ "GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR", + { /* replacement sources */ + {"VERT_DECLARATIONS", R"( + in vec4 a_nodeValues04; + in vec4 a_nodeValues47; + out vec4 a_nodeValues04ToGeom; + out vec4 a_nodeValues47ToGeom; + )"}, + {"VERT_ASSIGNMENTS", R"( + a_nodeValues04ToGeom = a_nodeValues04; + a_nodeValues47ToGeom = a_nodeValues47; + )"}, + {"GEOM_DECLARATIONS", R"( + in vec4 a_nodeValues04ToGeom[]; + in vec4 a_nodeValues47ToGeom[]; + out float a_valueToFrag; + )"}, + {"GEOM_PER_EMIT", R"( + { + uint cornerIdx = (nodeInd.x - cellInd.x) * 4u + (nodeInd.y - cellInd.y) * 2u + (nodeInd.z - cellInd.z); + a_valueToFrag = (cornerIdx < 4u) ? a_nodeValues04ToGeom[0][cornerIdx] : a_nodeValues47ToGeom[0][cornerIdx - 4u]; + } + )"}, + {"FRAG_DECLARATIONS", R"( + in float a_valueToFrag; + )"}, + {"GENERATE_SHADE_VALUE", R"( + float shadeValue = a_valueToFrag; + )"}, + }, + /* uniforms */ {}, + /* attributes */ { + {"a_nodeValues04", RenderDataType::Vector4Float}, + {"a_nodeValues47", RenderDataType::Vector4Float}, + }, + /* textures */ {} +); + +const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR ( + /* rule name */ "GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR", + { /* replacement sources */ + {"VERT_DECLARATIONS", R"( + in vec4 a_nodeR04; + in vec4 a_nodeR47; + in vec4 a_nodeG04; + in vec4 a_nodeG47; + in vec4 a_nodeB04; + in vec4 a_nodeB47; + out vec4 a_nodeR04ToGeom; + out vec4 a_nodeR47ToGeom; + out vec4 a_nodeG04ToGeom; + out vec4 a_nodeG47ToGeom; + out vec4 a_nodeB04ToGeom; + out vec4 a_nodeB47ToGeom; + )"}, + {"VERT_ASSIGNMENTS", R"( + a_nodeR04ToGeom = a_nodeR04; + a_nodeR47ToGeom = a_nodeR47; + a_nodeG04ToGeom = a_nodeG04; + a_nodeG47ToGeom = a_nodeG47; + a_nodeB04ToGeom = a_nodeB04; + a_nodeB47ToGeom = a_nodeB47; + )"}, + {"GEOM_DECLARATIONS", R"( + in vec4 a_nodeR04ToGeom[]; + in vec4 a_nodeR47ToGeom[]; + in vec4 a_nodeG04ToGeom[]; + in vec4 a_nodeG47ToGeom[]; + in vec4 a_nodeB04ToGeom[]; + in vec4 a_nodeB47ToGeom[]; + out vec3 a_colorToFrag; + )"}, + {"GEOM_PER_EMIT", R"( + { + uint cornerIdx = (nodeInd.x - cellInd.x) * 4u + (nodeInd.y - cellInd.y) * 2u + (nodeInd.z - cellInd.z); + float r = (cornerIdx < 4u) ? a_nodeR04ToGeom[0][cornerIdx] : a_nodeR47ToGeom[0][cornerIdx - 4u]; + float g = (cornerIdx < 4u) ? a_nodeG04ToGeom[0][cornerIdx] : a_nodeG47ToGeom[0][cornerIdx - 4u]; + float b = (cornerIdx < 4u) ? a_nodeB04ToGeom[0][cornerIdx] : a_nodeB47ToGeom[0][cornerIdx - 4u]; + a_colorToFrag = vec3(r, g, b); + } + )"}, + {"FRAG_DECLARATIONS", R"( + in vec3 a_colorToFrag; + )"}, + {"GENERATE_SHADE_VALUE", R"( + vec3 shadeColor = a_colorToFrag; + )"}, + }, + /* uniforms */ {}, + /* attributes */ { + {"a_nodeR04", RenderDataType::Vector4Float}, + {"a_nodeR47", RenderDataType::Vector4Float}, + {"a_nodeG04", RenderDataType::Vector4Float}, + {"a_nodeG47", RenderDataType::Vector4Float}, + {"a_nodeB04", RenderDataType::Vector4Float}, + {"a_nodeB47", RenderDataType::Vector4Float}, + }, + /* textures */ {} +); + } } } diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 74ebe2ef..110d2a9f 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -1,6 +1,8 @@ // Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run #include "polyscope/sparse_volume_grid.h" +#include "polyscope/sparse_volume_grid_color_quantity.h" +#include "polyscope/sparse_volume_grid_scalar_quantity.h" #include "polyscope/pick.h" @@ -260,4 +262,55 @@ SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, g return s; } +// === Quantity base class + +SparseVolumeGridQuantity::SparseVolumeGridQuantity(std::string name_, SparseVolumeGrid& grid_, bool dominates_) + : Quantity(name_, grid_, dominates_), parent(grid_) {} + + +// === Set cell geometry attributes helper + +void SparseVolumeGrid::setCellGeometryAttributes(render::ShaderProgram& p) { + p.setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); + p.setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); +} + + +// === Quantity adders + +SparseVolumeGridScalarQuantity* SparseVolumeGrid::addCellScalarQuantityImpl(std::string name, + const std::vector& data, + DataType type) { + checkForQuantityWithNameAndDeleteOrError(name); + SparseVolumeGridScalarQuantity* q = new SparseVolumeGridScalarQuantity(name, *this, data, type); + addQuantity(q); + return q; +} + +SparseVolumeGridScalarQuantity* SparseVolumeGrid::addNodeScalarQuantityImpl( + std::string name, const std::vector& nodeIndices, const std::vector& nodeValues, + DataType type) { + checkForQuantityWithNameAndDeleteOrError(name); + SparseVolumeGridScalarQuantity* q = new SparseVolumeGridScalarQuantity(name, *this, nodeIndices, nodeValues, type); + addQuantity(q); + return q; +} + +SparseVolumeGridColorQuantity* SparseVolumeGrid::addCellColorQuantityImpl(std::string name, + const std::vector& colors) { + checkForQuantityWithNameAndDeleteOrError(name); + SparseVolumeGridColorQuantity* q = new SparseVolumeGridColorQuantity(name, *this, colors); + addQuantity(q); + return q; +} + +SparseVolumeGridColorQuantity* SparseVolumeGrid::addNodeColorQuantityImpl( + std::string name, const std::vector& nodeIndices, const std::vector& nodeColors) { + checkForQuantityWithNameAndDeleteOrError(name); + SparseVolumeGridColorQuantity* q = new SparseVolumeGridColorQuantity(name, *this, nodeIndices, nodeColors); + addQuantity(q); + return q; +} + + } // namespace polyscope diff --git a/src/sparse_volume_grid_color_quantity.cpp b/src/sparse_volume_grid_color_quantity.cpp new file mode 100644 index 00000000..e4bcfd82 --- /dev/null +++ b/src/sparse_volume_grid_color_quantity.cpp @@ -0,0 +1,157 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope/sparse_volume_grid_color_quantity.h" + +#include "polyscope/polyscope.h" + +#include "imgui.h" + +#include + +namespace polyscope { + +// Hash for glm::ivec3 +struct IVec3HashColor { + size_t operator()(const glm::ivec3& v) const { + size_t h = std::hash()(v.x); + h ^= std::hash()(v.y) + 0x9e3779b9 + (h << 6) + (h >> 2); + h ^= std::hash()(v.z) + 0x9e3779b9 + (h << 6) + (h >> 2); + return h; + } +}; + +// === Cell color constructor +SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& colors_) + : SparseVolumeGridQuantity(name, grid, true), ColorQuantity(*this, colors_), isNodeQuantity(false) {} + +// === Node color constructor +SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& nodeIndices, + const std::vector& nodeColors) + : SparseVolumeGridQuantity(name, grid, true), + ColorQuantity(*this, std::vector(grid.nCells(), glm::vec3{0.f, 0.f, 0.f})), isNodeQuantity(true) { + packNodeColors(nodeIndices, nodeColors); +} + + +void SparseVolumeGridColorQuantity::draw() { + if (!isEnabled()) return; + + if (!program) createProgram(); + + // Set uniforms + parent.setStructureUniforms(*program); + program->setUniform("u_gridSpacing", parent.getGridCellWidth()); + program->setUniform("u_cubeSizeFactor", 1.f - static_cast(parent.getCubeSizeFactor())); + setColorUniforms(*program); + render::engine->setMaterialUniforms(*program, parent.getMaterial()); + + render::engine->setBackfaceCull(true); + program->draw(); +} + + +void SparseVolumeGridColorQuantity::createProgram() { + + // clang-format off + program = render::engine->requestShader("GRIDCUBE", + render::engine->addMaterialRules(parent.getMaterial(), + addColorRules( + parent.addStructureRules({ + isNodeQuantity ? "GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR" : "GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", + "SHADE_COLOR" + }) + ) + ) + ); + // clang-format on + + parent.setCellGeometryAttributes(*program); + + if (isNodeQuantity) { + program->setAttribute("a_nodeR04", nodeR04->getRenderAttributeBuffer()); + program->setAttribute("a_nodeR47", nodeR47->getRenderAttributeBuffer()); + program->setAttribute("a_nodeG04", nodeG04->getRenderAttributeBuffer()); + program->setAttribute("a_nodeG47", nodeG47->getRenderAttributeBuffer()); + program->setAttribute("a_nodeB04", nodeB04->getRenderAttributeBuffer()); + program->setAttribute("a_nodeB47", nodeB47->getRenderAttributeBuffer()); + } else { + program->setAttribute("a_color", colors.getRenderAttributeBuffer()); + } + + render::engine->setMaterial(*program, parent.getMaterial()); +} + + +void SparseVolumeGridColorQuantity::refresh() { + program.reset(); + Quantity::refresh(); +} + +std::string SparseVolumeGridColorQuantity::niceName() { return name + " (color)"; } + + +void SparseVolumeGridColorQuantity::packNodeColors(const std::vector& nodeIndices, + const std::vector& nodeColors) { + // Build lookup map + std::unordered_map nodeMap; + for (size_t i = 0; i < nodeIndices.size(); i++) { + nodeMap[nodeIndices[i]] = nodeColors[i]; + } + + size_t nCells = parent.nCells(); + const std::vector& occupiedCells = parent.getOccupiedCells(); + + nodeR04Data.resize(nCells); + nodeR47Data.resize(nCells); + nodeG04Data.resize(nCells); + nodeG47Data.resize(nCells); + nodeB04Data.resize(nCells); + nodeB47Data.resize(nCells); + + for (size_t i = 0; i < nCells; i++) { + glm::ivec3 ci = occupiedCells[i]; + glm::vec3 cornerColors[8]; + for (int dx = 0; dx < 2; dx++) { + for (int dy = 0; dy < 2; dy++) { + for (int dz = 0; dz < 2; dz++) { + int cornerIdx = dx * 4 + dy * 2 + dz; + glm::ivec3 nodeIjk(ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1); + auto it = nodeMap.find(nodeIjk); + if (it == nodeMap.end()) { + exception("SparseVolumeGridColorQuantity [" + name + "]: missing node color at (" + + std::to_string(nodeIjk.x) + "," + std::to_string(nodeIjk.y) + "," + + std::to_string(nodeIjk.z) + ")"); + } + cornerColors[cornerIdx] = it->second; + } + } + } + + // Pack by channel: R, G, B each get 2 vec4 (corners 0-3, 4-7) + nodeR04Data[i] = glm::vec4(cornerColors[0].r, cornerColors[1].r, cornerColors[2].r, cornerColors[3].r); + nodeR47Data[i] = glm::vec4(cornerColors[4].r, cornerColors[5].r, cornerColors[6].r, cornerColors[7].r); + nodeG04Data[i] = glm::vec4(cornerColors[0].g, cornerColors[1].g, cornerColors[2].g, cornerColors[3].g); + nodeG47Data[i] = glm::vec4(cornerColors[4].g, cornerColors[5].g, cornerColors[6].g, cornerColors[7].g); + nodeB04Data[i] = glm::vec4(cornerColors[0].b, cornerColors[1].b, cornerColors[2].b, cornerColors[3].b); + nodeB47Data[i] = glm::vec4(cornerColors[4].b, cornerColors[5].b, cornerColors[6].b, cornerColors[7].b); + } + + // Create managed buffers for the packed data + nodeR04 = std::unique_ptr>( + new render::ManagedBuffer(&parent, name + "#nodeR04", nodeR04Data)); + nodeR47 = std::unique_ptr>( + new render::ManagedBuffer(&parent, name + "#nodeR47", nodeR47Data)); + nodeG04 = std::unique_ptr>( + new render::ManagedBuffer(&parent, name + "#nodeG04", nodeG04Data)); + nodeG47 = std::unique_ptr>( + new render::ManagedBuffer(&parent, name + "#nodeG47", nodeG47Data)); + nodeB04 = std::unique_ptr>( + new render::ManagedBuffer(&parent, name + "#nodeB04", nodeB04Data)); + nodeB47 = std::unique_ptr>( + new render::ManagedBuffer(&parent, name + "#nodeB47", nodeB47Data)); +} + + +} // namespace polyscope diff --git a/src/sparse_volume_grid_scalar_quantity.cpp b/src/sparse_volume_grid_scalar_quantity.cpp new file mode 100644 index 00000000..95e55ea9 --- /dev/null +++ b/src/sparse_volume_grid_scalar_quantity.cpp @@ -0,0 +1,169 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope/sparse_volume_grid_scalar_quantity.h" + +#include "polyscope/polyscope.h" + +#include "imgui.h" + +#include + +namespace polyscope { + +// Hash for glm::ivec3 +struct IVec3Hash { + size_t operator()(const glm::ivec3& v) const { + size_t h = std::hash()(v.x); + h ^= std::hash()(v.y) + 0x9e3779b9 + (h << 6) + (h >> 2); + h ^= std::hash()(v.z) + 0x9e3779b9 + (h << 6) + (h >> 2); + return h; + } +}; + +// === Cell scalar constructor +SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& values_, DataType dataType_) + : SparseVolumeGridQuantity(name, grid, true), ScalarQuantity(*this, values_, dataType_), isNodeQuantity(false) {} + +// === Node scalar constructor +SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& nodeIndices, + const std::vector& nodeValues, + DataType dataType_) + : SparseVolumeGridQuantity(name, grid, true), + ScalarQuantity(*this, std::vector(grid.nCells(), 0.f), dataType_), isNodeQuantity(true) { + packNodeValues(nodeIndices, nodeValues); +} + + +void SparseVolumeGridScalarQuantity::draw() { + if (!isEnabled()) return; + + if (!program) createProgram(); + + // Set uniforms + parent.setStructureUniforms(*program); + program->setUniform("u_gridSpacing", parent.getGridCellWidth()); + program->setUniform("u_cubeSizeFactor", 1.f - static_cast(parent.getCubeSizeFactor())); + setScalarUniforms(*program); + render::engine->setMaterialUniforms(*program, parent.getMaterial()); + + render::engine->setBackfaceCull(true); + program->draw(); +} + + +void SparseVolumeGridScalarQuantity::buildCustomUI() { + ImGui::SameLine(); + + if (ImGui::Button("Options")) { + ImGui::OpenPopup("OptionsPopup"); + } + if (ImGui::BeginPopup("OptionsPopup")) { + buildScalarOptionsUI(); + ImGui::EndPopup(); + } + + buildScalarUI(); +} + + +void SparseVolumeGridScalarQuantity::createProgram() { + + // clang-format off + program = render::engine->requestShader("GRIDCUBE", + render::engine->addMaterialRules(parent.getMaterial(), + addScalarRules( + parent.addStructureRules({ + isNodeQuantity ? "GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR" : "GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR" + }) + ) + ) + ); + // clang-format on + + parent.setCellGeometryAttributes(*program); + + if (isNodeQuantity) { + program->setAttribute("a_nodeValues04", nodeValues04->getRenderAttributeBuffer()); + program->setAttribute("a_nodeValues47", nodeValues47->getRenderAttributeBuffer()); + } else { + program->setAttribute("a_value", values.getRenderAttributeBuffer()); + } + + program->setTextureFromColormap("t_colormap", cMap.get()); + render::engine->setMaterial(*program, parent.getMaterial()); +} + + +void SparseVolumeGridScalarQuantity::refresh() { + program.reset(); + Quantity::refresh(); +} + +std::string SparseVolumeGridScalarQuantity::niceName() { return name + " (scalar)"; } + + +void SparseVolumeGridScalarQuantity::packNodeValues(const std::vector& nodeIndices, + const std::vector& nodeValues) { + // Build lookup map + std::unordered_map nodeMap; + for (size_t i = 0; i < nodeIndices.size(); i++) { + nodeMap[nodeIndices[i]] = nodeValues[i]; + } + + size_t nCells = parent.nCells(); + const std::vector& occupiedCells = parent.getOccupiedCells(); + + nodeValues04Data.resize(nCells); + nodeValues47Data.resize(nCells); + + // Also fill values.data with per-cell averages for data range computation + valuesData.resize(nCells); + + for (size_t i = 0; i < nCells; i++) { + glm::ivec3 ci = occupiedCells[i]; + float vals[8]; + for (int dx = 0; dx < 2; dx++) { + for (int dy = 0; dy < 2; dy++) { + for (int dz = 0; dz < 2; dz++) { + int cornerIdx = dx * 4 + dy * 2 + dz; + glm::ivec3 nodeIjk(ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1); + auto it = nodeMap.find(nodeIjk); + if (it == nodeMap.end()) { + exception("SparseVolumeGridScalarQuantity [" + name + "]: missing node value at (" + + std::to_string(nodeIjk.x) + "," + std::to_string(nodeIjk.y) + "," + + std::to_string(nodeIjk.z) + ")"); + } + vals[cornerIdx] = it->second; + } + } + } + nodeValues04Data[i] = glm::vec4(vals[0], vals[1], vals[2], vals[3]); + nodeValues47Data[i] = glm::vec4(vals[4], vals[5], vals[6], vals[7]); + + // Compute average for data range + float avg = 0; + for (int c = 0; c < 8; c++) avg += vals[c]; + valuesData[i] = avg / 8.f; + } + + // Update data range from the node values themselves + if (!nodeValues.empty()) { + float minVal = nodeValues[0], maxVal = nodeValues[0]; + for (float v : nodeValues) { + minVal = std::min(minVal, v); + maxVal = std::max(maxVal, v); + } + dataRange = {minVal, maxVal}; + } + + // Create managed buffers for the packed data + nodeValues04 = std::unique_ptr>( + new render::ManagedBuffer(&parent, name + "#nodeValues04", nodeValues04Data)); + nodeValues47 = std::unique_ptr>( + new render::ManagedBuffer(&parent, name + "#nodeValues47", nodeValues47Data)); +} + + +} // namespace polyscope diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp index c7c727e0..9f6ad290 100644 --- a/test/src/sparse_volume_grid_test.cpp +++ b/test/src/sparse_volume_grid_test.cpp @@ -33,6 +33,133 @@ TEST_F(PolyscopeTest, SparseVolumeGridShow) { } +TEST_F(PolyscopeTest, SparseVolumeGridCellScalar) { + glm::vec3 origin{-3., -3., -3.}; + glm::vec3 cellWidth{0.5, 0.5, 0.5}; + + std::vector occupiedCells; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + for (int k = 0; k < 4; k++) { + occupiedCells.push_back({i, j, k}); + } + } + } + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + + std::vector scalarVals(occupiedCells.size()); + for (size_t i = 0; i < scalarVals.size(); i++) { + scalarVals[i] = static_cast(i); + } + psGrid->addCellScalarQuantity("cell scalar", scalarVals); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridNodeScalar) { + glm::vec3 origin{-3., -3., -3.}; + glm::vec3 cellWidth{0.5, 0.5, 0.5}; + + std::vector occupiedCells; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + for (int k = 0; k < 4; k++) { + occupiedCells.push_back({i, j, k}); + } + } + } + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + + // Node indices: corners of each cell are at (ci+dx-1, cj+dy-1, ck+dz-1) for dx,dy,dz in {0,1} + // For cells (0..3)^3, nodes range from -1..3 + std::vector nodeIndices; + std::vector nodeValues; + for (int i = -1; i <= 3; i++) { + for (int j = -1; j <= 3; j++) { + for (int k = -1; k <= 3; k++) { + nodeIndices.push_back({i, j, k}); + nodeValues.push_back(static_cast(i + j + k)); + } + } + } + psGrid->addNodeScalarQuantity("node scalar", nodeIndices, nodeValues); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridCellColor) { + glm::vec3 origin{-3., -3., -3.}; + glm::vec3 cellWidth{0.5, 0.5, 0.5}; + + std::vector occupiedCells; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + for (int k = 0; k < 4; k++) { + occupiedCells.push_back({i, j, k}); + } + } + } + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + + std::vector colorVals(occupiedCells.size()); + for (size_t i = 0; i < colorVals.size(); i++) { + colorVals[i] = glm::vec3(static_cast(i) / colorVals.size(), 0.5f, 0.3f); + } + psGrid->addCellColorQuantity("cell color", colorVals); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridNodeColor) { + glm::vec3 origin{-3., -3., -3.}; + glm::vec3 cellWidth{0.5, 0.5, 0.5}; + + std::vector occupiedCells; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + for (int k = 0; k < 4; k++) { + occupiedCells.push_back({i, j, k}); + } + } + } + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + + // Node indices: corners of each cell are at (ci+dx-1, cj+dy-1, ck+dz-1) for dx,dy,dz in {0,1} + std::vector nodeIndices; + std::vector nodeColors; + for (int i = -1; i <= 3; i++) { + for (int j = -1; j <= 3; j++) { + for (int k = -1; k <= 3; k++) { + nodeIndices.push_back({i, j, k}); + nodeColors.push_back(glm::vec3((i + 1) / 4.f, (j + 1) / 4.f, (k + 1) / 4.f)); + } + } + } + psGrid->addNodeColorQuantity("node color", nodeIndices, nodeColors); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + TEST_F(PolyscopeTest, SparseVolumeGridBasicOptions) { glm::vec3 origin{-3., -3., -3.}; glm::vec3 cellWidth{0.5, 0.5, 0.5}; From 348b39698012c9c6845487f143ba2d1c609e46f8 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Feb 2026 15:56:29 -1000 Subject: [PATCH 03/18] add edges test --- test/src/sparse_volume_grid_test.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp index 9f6ad290..f58d7b07 100644 --- a/test/src/sparse_volume_grid_test.cpp +++ b/test/src/sparse_volume_grid_test.cpp @@ -32,6 +32,31 @@ TEST_F(PolyscopeTest, SparseVolumeGridShow) { EXPECT_FALSE(polyscope::hasSparseVolumeGrid("test sparse grid")); } +TEST_F(PolyscopeTest, SparseVolumeGridEdges) { + glm::vec3 origin{-3., -3., -3.}; + glm::vec3 cellWidth{0.5, 0.5, 0.5}; + + // Create some occupied cells + std::vector occupiedCells; + for (uint32_t i = 0; i < 8; i += 2) { + for (uint32_t j = 0; j < 10; j += 2) { + for (uint32_t k = 0; k < 12; k += 2) { + occupiedCells.push_back({i, j, k}); + } + } + } + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + + psGrid->setEdgeWidth(1.f); + psGrid->setEdgeColor({1.f, 0.f, 0.f}); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + TEST_F(PolyscopeTest, SparseVolumeGridCellScalar) { glm::vec3 origin{-3., -3., -3.}; From 33cf90fc42bf36b0d0a860021d8fb46148729cd5 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Feb 2026 15:58:40 -1000 Subject: [PATCH 04/18] change rendering strategy for edges and normals --- examples/demo-app/demo_app.cpp | 27 +-- .../render/opengl/shaders/grid_shaders.h | 1 + include/polyscope/sparse_volume_grid.h | 25 ++- src/render/mock_opengl/mock_gl_engine.cpp | 1 + src/render/opengl/gl_engine.cpp | 1 + src/render/opengl/shaders/common.cpp | 8 + src/render/opengl/shaders/grid_shaders.cpp | 83 ++++++--- src/sparse_volume_grid.cpp | 168 ++++++++++++++---- src/sparse_volume_grid_color_quantity.cpp | 11 +- src/sparse_volume_grid_scalar_quantity.cpp | 7 +- src/volume_grid.cpp | 12 +- 11 files changed, 249 insertions(+), 95 deletions(-) diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index 25627dec..d55e65a7 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -12,11 +12,11 @@ #include "polyscope/point_cloud.h" #include "polyscope/render/managed_buffer.h" #include "polyscope/simple_triangle_mesh.h" +#include "polyscope/sparse_volume_grid.h" #include "polyscope/surface_mesh.h" #include "polyscope/types.h" #include "polyscope/view.h" #include "polyscope/volume_grid.h" -#include "polyscope/sparse_volume_grid.h" #include "polyscope/volume_mesh.h" #include @@ -547,17 +547,18 @@ void addVolumeGrid() { void addSparseVolumeGrid() { - glm::vec3 origin{-5., -5., -5.}; - glm::vec3 cellWidth{0.5, 0.5, 0.5}; + glm::vec3 origin{-1., -1., -1.}; + int32_t N = 20; + glm::vec3 cellWidth{1.0f / N, 1.0f / N, 1.0f / N}; // Build a spherical shell of occupied cells std::vector occupiedCells; - for (int i = -10; i <= 10; i++) { - for (int j = -10; j <= 10; j++) { - for (int k = -10; k <= 10; k++) { + for (int i = -N; i <= N; i++) { + for (int j = -N; j <= N; j++) { + for (int k = -N; k <= N; k++) { glm::vec3 cellCenter = origin + (glm::vec3(i, j, k) + 0.5f) * cellWidth; float dist = glm::length(cellCenter - origin); - if (dist >= 2.0f && dist <= 4.0f) { + if (dist >= 0.35f && dist <= 1.0f) { occupiedCells.push_back({i, j, k}); } } @@ -574,7 +575,7 @@ void addSparseVolumeGrid() { glm::vec3 cellCenter = origin + (glm::vec3(occupiedCells[i]) + 0.5f) * cellWidth; cellDist[i] = glm::length(cellCenter - origin); } - psGrid->addCellScalarQuantity("cell distance", cellDist)->setEnabled(true); + psGrid->addCellScalarQuantity("cell distance", cellDist); } // Node scalar: x-coordinate at node positions @@ -594,7 +595,9 @@ void addSparseVolumeGrid() { std::vector nodeIndices; std::vector nodeValues; - for (const auto& [ni, nj, nk] : nodeSet) { + for (const auto& nodeInds : nodeSet) { + int32_t ni, nj, nk; + std::tie(ni, nj, nk) = nodeInds; nodeIndices.push_back({ni, nj, nk}); glm::vec3 nodePos = origin + glm::vec3(ni, nj, nk) * cellWidth; nodeValues.push_back(nodePos.x); @@ -628,7 +631,9 @@ void addSparseVolumeGrid() { std::vector nodeIndices; std::vector nodeColors; - for (const auto& [ni, nj, nk] : nodeSet) { + for (const auto& nodeInds : nodeSet) { + int32_t ni, nj, nk; + std::tie(ni, nj, nk) = nodeInds; nodeIndices.push_back({ni, nj, nk}); glm::vec3 nodePos = origin + glm::vec3(ni, nj, nk) * cellWidth; nodeColors.push_back(glm::clamp((nodePos - origin) / 10.f + 0.5f, 0.f, 1.f)); @@ -1009,7 +1014,7 @@ int main(int argc, char** argv) { // polyscope::view::windowWidth = 600; // polyscope::view::windowHeight = 800; // polyscope::options::maxFPS = -1; - polyscope::options::verbosity = 101; + polyscope::options::verbosity = 200; polyscope::options::enableRenderErrorChecks = true; polyscope::options::allowHeadlessBackends = true; diff --git a/include/polyscope/render/opengl/shaders/grid_shaders.h b/include/polyscope/render/opengl/shaders/grid_shaders.h index 3cd8e0f2..c117e375 100644 --- a/include/polyscope/render/opengl/shaders/grid_shaders.h +++ b/include/polyscope/render/opengl/shaders/grid_shaders.h @@ -20,6 +20,7 @@ extern const ShaderStageSpecification FLEX_GRIDCUBE_PLANE_FRAG_SHADER; extern const ShaderReplacementRule GRIDCUBE_PROPAGATE_NODE_VALUE; extern const ShaderReplacementRule GRIDCUBE_PROPAGATE_CELL_VALUE; extern const ShaderReplacementRule GRIDCUBE_WIREFRAME; +extern const ShaderReplacementRule GRIDCUBE_PLANE_WIREFRAME; extern const ShaderReplacementRule GRIDCUBE_CONSTANT_PICK; extern const ShaderReplacementRule GRIDCUBE_CULLPOS_FROM_CENTER; diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h index 29932ce1..1caa244c 100644 --- a/include/polyscope/sparse_volume_grid.h +++ b/include/polyscope/sparse_volume_grid.h @@ -24,8 +24,7 @@ class SparseVolumeGridColorQuantity; class SparseVolumeGrid : public Structure { public: // Construct a new sparse volume grid structure - SparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, - std::vector occupiedCells); + SparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, std::vector occupiedCells); // === Overloads @@ -65,8 +64,8 @@ class SparseVolumeGrid : public Structure { // Node scalar template - SparseVolumeGridScalarQuantity* addNodeScalarQuantity(std::string name, const TI& nodeIndices, - const TV& nodeValues, DataType type = DataType::STANDARD); + SparseVolumeGridScalarQuantity* addNodeScalarQuantity(std::string name, const TI& nodeIndices, const TV& nodeValues, + DataType type = DataType::STANDARD); // Cell color template @@ -76,8 +75,10 @@ class SparseVolumeGrid : public Structure { template SparseVolumeGridColorQuantity* addNodeColorQuantity(std::string name, const TI& nodeIndices, const TC& nodeColors); - // Set cell geometry attributes on a shader program + // Rendering related helpers void setCellGeometryAttributes(render::ShaderProgram& p); + std::vector addSparseGridShaderRules(std::vector initRules, bool pickOnly = false); + void setSparseVolumeGridUniforms(render::ShaderProgram& p, bool pickOnly = false); // === Getters and setters for visualization settings @@ -85,6 +86,15 @@ class SparseVolumeGrid : public Structure { SparseVolumeGrid* setColor(glm::vec3 val); glm::vec3 getColor(); + // Width of the edges. Scaled such that 1 is a reasonable weight for visible edges, but values 1 can be used for + // bigger edges. Use 0. to disable. + SparseVolumeGrid* setEdgeWidth(double newVal); + double getEdgeWidth(); + + // Color of edges + SparseVolumeGrid* setEdgeColor(glm::vec3 val); + glm::vec3 getEdgeColor(); + // Material SparseVolumeGrid* setMaterial(std::string name); std::string getMaterial(); @@ -107,6 +117,8 @@ class SparseVolumeGrid : public Structure { // === Visualization parameters PersistentValue color; + PersistentValue edgeWidth; + PersistentValue edgeColor; PersistentValue material; PersistentValue cubeSizeFactor; @@ -132,8 +144,7 @@ class SparseVolumeGrid : public Structure { const std::vector& nodeIndices, const std::vector& nodeValues, DataType type); SparseVolumeGridColorQuantity* addCellColorQuantityImpl(std::string name, const std::vector& colors); - SparseVolumeGridColorQuantity* addNodeColorQuantityImpl(std::string name, - const std::vector& nodeIndices, + SparseVolumeGridColorQuantity* addNodeColorQuantityImpl(std::string name, const std::vector& nodeIndices, const std::vector& nodeColors); }; diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index 14d4ccda..a197024f 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -2140,6 +2140,7 @@ void MockGLEngine::populateDefaultShadersAndRules() { registerShaderRule("GRIDCUBE_PROPAGATE_NODE_VALUE", GRIDCUBE_PROPAGATE_NODE_VALUE); registerShaderRule("GRIDCUBE_PROPAGATE_CELL_VALUE", GRIDCUBE_PROPAGATE_CELL_VALUE); registerShaderRule("GRIDCUBE_WIREFRAME", GRIDCUBE_WIREFRAME); + registerShaderRule("GRIDCUBE_PLANE_WIREFRAME", GRIDCUBE_PLANE_WIREFRAME); registerShaderRule("GRIDCUBE_CONSTANT_PICK", GRIDCUBE_CONSTANT_PICK); registerShaderRule("GRIDCUBE_CULLPOS_FROM_CENTER", GRIDCUBE_CULLPOS_FROM_CENTER); registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR", GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR); diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index cbc57a47..1189376c 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -2607,6 +2607,7 @@ void GLEngine::populateDefaultShadersAndRules() { registerShaderRule("GRIDCUBE_PROPAGATE_NODE_VALUE", GRIDCUBE_PROPAGATE_NODE_VALUE); registerShaderRule("GRIDCUBE_PROPAGATE_CELL_VALUE", GRIDCUBE_PROPAGATE_CELL_VALUE); registerShaderRule("GRIDCUBE_WIREFRAME", GRIDCUBE_WIREFRAME); + registerShaderRule("GRIDCUBE_PLANE_WIREFRAME", GRIDCUBE_PLANE_WIREFRAME); registerShaderRule("GRIDCUBE_CONSTANT_PICK", GRIDCUBE_CONSTANT_PICK); registerShaderRule("GRIDCUBE_CULLPOS_FROM_CENTER", GRIDCUBE_CULLPOS_FROM_CENTER); registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR", GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR); diff --git a/src/render/opengl/shaders/common.cpp b/src/render/opengl/shaders/common.cpp index 578f3f98..58f98551 100644 --- a/src/render/opengl/shaders/common.cpp +++ b/src/render/opengl/shaders/common.cpp @@ -134,6 +134,14 @@ float selectMax(vec3 keys, vec3 values) { return outSum / outCount; } +vec3 sharpenToAxis(vec3 v, float sharpness) { + vec3 s = sign(v); + vec3 absV = abs(v); + vec3 sharpened = pow(absV, vec3(sharpness)); + sharpened = normalize(sharpened); + return s * sharpened; +} + // Used to sample colors. Samples a series of most-distant values from a range [0,1] // offset from a starting value 'start' and wrapped around. index=0 returns start. // We only actually output distinct floats for the first 10 bits, then the pattern repeats. diff --git a/src/render/opengl/shaders/grid_shaders.cpp b/src/render/opengl/shaders/grid_shaders.cpp index 3c649bad..78df6f2d 100644 --- a/src/render/opengl/shaders/grid_shaders.cpp +++ b/src/render/opengl/shaders/grid_shaders.cpp @@ -79,7 +79,7 @@ R"( uniform vec3 u_gridSpacing; uniform float u_cubeSizeFactor; - out vec3 sphereCenterView; + out vec3 a_gridCoordToFrag; // TODO working here ${ GEOM_DECLARATIONS }$ @@ -90,15 +90,25 @@ R"( mat4 T = u_projMatrix * u_modelView; + // node corner logical +1/-1 offsets + vec3 c0 = vec3(-1.f, -1.f, -1.f); + vec3 c1 = vec3(-1.f, -1.f, 1.f); + vec3 c2 = vec3(-1.f, 1.f, -1.f); + vec3 c3 = vec3(-1.f, 1.f, 1.f); + vec3 c4 = vec3( 1.f, -1.f, -1.f); + vec3 c5 = vec3( 1.f, -1.f, 1.f); + vec3 c6 = vec3( 1.f, 1.f, -1.f); + vec3 c7 = vec3( 1.f, 1.f, 1.f); + // node corner positions - vec4 p0 = T * vec4(center + vec3(-1.f, -1.f, -1.f)*dvec, 1.f); - vec4 p1 = T * vec4(center + vec3(-1.f, -1.f, 1.f)*dvec, 1.f); - vec4 p2 = T * vec4(center + vec3(-1.f, 1.f, -1.f)*dvec, 1.f); - vec4 p3 = T * vec4(center + vec3(-1.f, 1.f, 1.f)*dvec, 1.f); - vec4 p4 = T * vec4(center + vec3( 1.f, -1.f, -1.f)*dvec, 1.f); - vec4 p5 = T * vec4(center + vec3( 1.f, -1.f, 1.f)*dvec, 1.f); - vec4 p6 = T * vec4(center + vec3( 1.f, 1.f, -1.f)*dvec, 1.f); - vec4 p7 = T * vec4(center + vec3( 1.f, 1.f, 1.f)*dvec, 1.f); + vec4 p0 = T * vec4(center + c0 * dvec, 1.f); + vec4 p1 = T * vec4(center + c1 * dvec, 1.f); + vec4 p2 = T * vec4(center + c2 * dvec, 1.f); + vec4 p3 = T * vec4(center + c3 * dvec, 1.f); + vec4 p4 = T * vec4(center + c4 * dvec, 1.f); + vec4 p5 = T * vec4(center + c5 * dvec, 1.f); + vec4 p6 = T * vec4(center + c6 * dvec, 1.f); + vec4 p7 = T * vec4(center + c7 * dvec, 1.f); // node corner indices uvec3 iCenter = a_cellIndToGeom[0]; @@ -120,20 +130,20 @@ R"( // this is the order to emit veritces to get a cube triangle strip // 3, 7, 1, 5, 4, 7, 6, 3, 2, 1, 0, 4, 2, 6, - /* 7 */ nodePos = p7; nodeInd = i7; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 3 */ nodePos = p3; nodeInd = i3; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 5 */ nodePos = p5; nodeInd = i5; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 1 */ nodePos = p1; nodeInd = i1; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 0 */ nodePos = p0; nodeInd = i0; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 3 */ nodePos = p3; nodeInd = i3; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 2 */ nodePos = p2; nodeInd = i2; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 7 */ nodePos = p7; nodeInd = i7; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 6 */ nodePos = p6; nodeInd = i6; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 5 */ nodePos = p5; nodeInd = i5; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 4 */ nodePos = p4; nodeInd = i4; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 0 */ nodePos = p0; nodeInd = i0; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 6 */ nodePos = p6; nodeInd = i6; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); - /* 2 */ nodePos = p2; nodeInd = i2; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; EmitVertex(); + /* 7 */ nodePos = p7; nodeInd = i7; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); + /* 3 */ nodePos = p3; nodeInd = i3; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); + /* 5 */ nodePos = p5; nodeInd = i5; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); + /* 1 */ nodePos = p1; nodeInd = i1; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c1; EmitVertex(); + /* 0 */ nodePos = p0; nodeInd = i0; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); + /* 3 */ nodePos = p3; nodeInd = i3; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); + /* 2 */ nodePos = p2; nodeInd = i2; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); + /* 7 */ nodePos = p7; nodeInd = i7; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); + /* 6 */ nodePos = p6; nodeInd = i6; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); + /* 5 */ nodePos = p5; nodeInd = i5; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); + /* 4 */ nodePos = p4; nodeInd = i4; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c4; EmitVertex(); + /* 0 */ nodePos = p0; nodeInd = i0; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); + /* 6 */ nodePos = p6; nodeInd = i6; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); + /* 2 */ nodePos = p2; nodeInd = i2; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); EndPrimitive(); @@ -148,6 +158,7 @@ const ShaderStageSpecification FLEX_GRIDCUBE_FRAG_SHADER = { // uniforms { + {"u_modelView", RenderDataType::Matrix44Float}, }, { }, // attributes @@ -160,15 +171,26 @@ const ShaderStageSpecification FLEX_GRIDCUBE_FRAG_SHADER = { R"( ${ GLSL_VERSION }$ + in vec3 a_gridCoordToFrag; + uniform mat4 u_modelView; layout(location = 0) out vec4 outputF; ${ FRAG_DECLARATIONS }$ + vec3 sharpenToAxis(vec3 v, float sharpness); + void main() { float depth = gl_FragCoord.z; ${ GLOBAL_FRAGMENT_FILTER_PREP }$ ${ GLOBAL_FRAGMENT_FILTER }$ + + vec3 coordLocalAbs = abs(a_gridCoordToFrag); + float maxCoord = max(max(coordLocalAbs.x, coordLocalAbs.y), coordLocalAbs.z); + + // compute a normal vector from the coord + vec3 shadeNormal = sharpenToAxis(a_gridCoordToFrag, 8.0f); + shadeNormal = normalize(mat3(u_modelView) * shadeNormal); // transform to view space // Shading ${ GENERATE_SHADE_VALUE }$ @@ -178,7 +200,6 @@ R"( ${ APPLY_WIREFRAME }$ // Lighting - vec3 shadeNormal = vec3(0.f, 0.f, 0.f); // use the COMPUTE_SHADE_NORMAL_FROM_POSITION rule ${ PERTURB_SHADE_NORMAL }$ ${ GENERATE_LIT_COLOR }$ @@ -398,6 +419,20 @@ const ShaderReplacementRule GRIDCUBE_PROPAGATE_CELL_VALUE ( const ShaderReplacementRule GRIDCUBE_WIREFRAME ( /* rule name */ "GRIDCUBE_WIREFRAME", + { + /* replacement sources */ + {"APPLY_WIREFRAME", R"( + vec3 wireframe_UVW = 1.f - coordLocalAbs; + vec3 wireframe_mask = vec3(notEqual(abs(a_gridCoordToFrag), vec3(1.f, 1.f, 1.f))).zxy; + )"}, + }, + /* uniforms */ {}, + /* attributes */ {}, + /* textures */ {} +); + +const ShaderReplacementRule GRIDCUBE_PLANE_WIREFRAME ( + /* rule name */ "GRIDCUBE_PLANE_WIREFRAME", { /* replacement sources */ {"APPLY_WIREFRAME", R"( diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 110d2a9f..2548824b 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -16,7 +16,6 @@ const std::string SparseVolumeGrid::structureTypeName = "Sparse Volume Grid"; SparseVolumeGrid::SparseVolumeGrid(std::string name, glm::vec3 origin_, glm::vec3 gridCellWidth_, std::vector occupiedCells) : Structure(name, typeName()), - // clang-format off // == managed quantities cellPositions(this, uniquePrefix() + "#cellPositions", cellPositionsData, std::bind(&SparseVolumeGrid::computeCellPositions, this)), @@ -26,10 +25,12 @@ SparseVolumeGrid::SparseVolumeGrid(std::string name, glm::vec3 origin_, glm::vec // == persistent options color( uniquePrefix() + "color", getNextUniqueColor()), + edgeWidth( uniquePrefix() + "edgeWidth", 0.f), + edgeColor( uniquePrefix() + "edgeColor", glm::vec3{0., 0., 0.}), material( uniquePrefix() + "material", "clay"), cubeSizeFactor( uniquePrefix() + "cubeSizeFactor", 0.f) -// clang-format on -{ + { + // clang-format on occupiedCellsData = std::move(occupiedCells); computeCellPositions(); @@ -62,6 +63,41 @@ void SparseVolumeGrid::buildCustomUI() { { // Color if (ImGui::ColorEdit3("Color", &color.get()[0], ImGuiColorEditFlags_NoInputs)) setColor(color.get()); } + + { // Edge options + ImGui::SameLine(); + ImGui::PushItemWidth(100 * options::uiScale); + if (edgeWidth.get() == 0.) { + bool showEdges = false; + if (ImGui::Checkbox("Edges", &showEdges)) { + setEdgeWidth(1.); + } + } else { + bool showEdges = true; + if (ImGui::Checkbox("Edges", &showEdges)) { + setEdgeWidth(0.); + } + + // Edge color + ImGui::PushItemWidth(100 * options::uiScale); + if (ImGui::ColorEdit3("Edge Color", &edgeColor.get()[0], ImGuiColorEditFlags_NoInputs)) + setEdgeColor(edgeColor.get()); + ImGui::PopItemWidth(); + + // Edge width + ImGui::SameLine(); + ImGui::PushItemWidth(75 * options::uiScale); + if (ImGui::SliderFloat("Width", &edgeWidth.get(), 0.001, 2.)) { + // NOTE: this intentionally circumvents the setEdgeWidth() setter to avoid repopulating the buffer as the + // slider is dragged---otherwise we repopulate the buffer on every change. This is a + // lazy solution instead of better state/buffer management. + edgeWidth.manuallyChanged(); + requestRedraw(); + } + ImGui::PopItemWidth(); + } + ImGui::PopItemWidth(); + } } @@ -82,24 +118,42 @@ void SparseVolumeGrid::buildCustomOptionsUI() { void SparseVolumeGrid::draw() { if (!enabled.get()) return; - // Ensure we have prepared buffers - ensureRenderProgramPrepared(); + if (dominantQuantity == nullptr) { + // if there is no dominant quantity, the structure handles drawing - // Set program uniforms - setStructureUniforms(*program); - program->setUniform("u_gridSpacing", gridCellWidth); - program->setUniform("u_cubeSizeFactor", 1.f - cubeSizeFactor.get()); - program->setUniform("u_baseColor", color.get()); - render::engine->setMaterialUniforms(*program, material.get()); + // Ensure we have prepared buffers + ensureRenderProgramPrepared(); - // Draw the actual grid - render::engine->setBackfaceCull(true); - program->draw(); + // Set program uniforms + setSparseVolumeGridUniforms(*program); + program->setUniform("u_baseColor", color.get()); + + // Draw the actual grid + program->draw(); + render::engine->setBackfaceCull(true); + } + + // Draw the quantities + for (auto& x : quantities) { + x.second->draw(); + } + for (auto& x : floatingQuantities) { + x.second->draw(); + } } void SparseVolumeGrid::drawDelayed() { - if (!enabled.get()) return; + if (!isEnabled()) { + return; + } + + for (auto& x : quantities) { + x.second->drawDelayed(); + } + for (auto& x : floatingQuantities) { + x.second->drawDelayed(); + } } @@ -109,9 +163,7 @@ void SparseVolumeGrid::drawPick() { ensurePickProgramPrepared(); // Set program uniforms - setStructureUniforms(*pickProgram); - pickProgram->setUniform("u_gridSpacing", gridCellWidth); - pickProgram->setUniform("u_cubeSizeFactor", 1.f - cubeSizeFactor.get()); + setSparseVolumeGridUniforms(*pickProgram, true); pickProgram->setUniform("u_pickColor", pickColor); // Draw the actual grid @@ -131,7 +183,7 @@ void SparseVolumeGrid::ensureRenderProgramPrepared() { // clang-format off program = render::engine->requestShader("GRIDCUBE", render::engine->addMaterialRules(material.get(), - addStructureRules({"SHADE_BASECOLOR"}) + addSparseGridShaderRules({"SHADE_BASECOLOR"}) ) ); // clang-format on @@ -149,7 +201,7 @@ void SparseVolumeGrid::ensurePickProgramPrepared() { // clang-format off pickProgram = render::engine->requestShader( "GRIDCUBE", - addStructureRules({"GRIDCUBE_CONSTANT_PICK"}), + addSparseGridShaderRules({"GRIDCUBE_CONSTANT_PICK"}, true), render::ShaderReplacementDefaults::Pick ); // clang-format on @@ -230,6 +282,25 @@ SparseVolumeGrid* SparseVolumeGrid::setColor(glm::vec3 val) { } glm::vec3 SparseVolumeGrid::getColor() { return color.get(); } +SparseVolumeGrid* SparseVolumeGrid::setEdgeWidth(double newVal) { + double oldEdgeWidth = edgeWidth.get(); + edgeWidth = newVal; + if ((oldEdgeWidth != 0) != (newVal != 0)) { + // if it changed to/from zero, we disabled/enabled edgges, and need a new program + refresh(); + } + requestRedraw(); + return this; +} +double SparseVolumeGrid::getEdgeWidth() { return edgeWidth.get(); } + +SparseVolumeGrid* SparseVolumeGrid::setEdgeColor(glm::vec3 val) { + edgeColor = val; + requestRedraw(); + return this; +} +glm::vec3 SparseVolumeGrid::getEdgeColor() { return edgeColor.get(); } + SparseVolumeGrid* SparseVolumeGrid::setMaterial(std::string m) { material = m; refresh(); @@ -246,6 +317,37 @@ SparseVolumeGrid* SparseVolumeGrid::setCubeSizeFactor(double newVal) { double SparseVolumeGrid::getCubeSizeFactor() { return cubeSizeFactor.get(); } +void SparseVolumeGrid::setCellGeometryAttributes(render::ShaderProgram& p) { + p.setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); + p.setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); +} + +std::vector SparseVolumeGrid::addSparseGridShaderRules(std::vector initRules, bool pickOnly) { + if (!pickOnly) { + + if (getEdgeWidth() > 0) { + initRules.push_back("GRIDCUBE_WIREFRAME"); + initRules.push_back("MESH_WIREFRAME"); + } + } + return addStructureRules(initRules); +} + +void SparseVolumeGrid::setSparseVolumeGridUniforms(render::ShaderProgram& p, bool pickOnly) { + setStructureUniforms(p); + p.setUniform("u_gridSpacing", gridCellWidth); + p.setUniform("u_cubeSizeFactor", 1.f - cubeSizeFactor.get()); + if (!pickOnly) { + if (getEdgeWidth() > 0) { + float edgeMult = 2.0f; // need to be a bit thicker to look nice for these + p.setUniform("u_edgeWidth", edgeMult * getEdgeWidth() * render::engine->getCurrentPixelScaling()); + p.setUniform("u_edgeColor", getEdgeColor()); + } + + render::engine->setMaterialUniforms(p, material.get()); + } +} + // === Register functions (non-template overload) SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, @@ -267,29 +369,20 @@ SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, g SparseVolumeGridQuantity::SparseVolumeGridQuantity(std::string name_, SparseVolumeGrid& grid_, bool dominates_) : Quantity(name_, grid_, dominates_), parent(grid_) {} - -// === Set cell geometry attributes helper - -void SparseVolumeGrid::setCellGeometryAttributes(render::ShaderProgram& p) { - p.setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); - p.setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); -} - - // === Quantity adders -SparseVolumeGridScalarQuantity* SparseVolumeGrid::addCellScalarQuantityImpl(std::string name, - const std::vector& data, - DataType type) { +SparseVolumeGridScalarQuantity* +SparseVolumeGrid::addCellScalarQuantityImpl(std::string name, const std::vector& data, DataType type) { checkForQuantityWithNameAndDeleteOrError(name); SparseVolumeGridScalarQuantity* q = new SparseVolumeGridScalarQuantity(name, *this, data, type); addQuantity(q); return q; } -SparseVolumeGridScalarQuantity* SparseVolumeGrid::addNodeScalarQuantityImpl( - std::string name, const std::vector& nodeIndices, const std::vector& nodeValues, - DataType type) { +SparseVolumeGridScalarQuantity* SparseVolumeGrid::addNodeScalarQuantityImpl(std::string name, + const std::vector& nodeIndices, + const std::vector& nodeValues, + DataType type) { checkForQuantityWithNameAndDeleteOrError(name); SparseVolumeGridScalarQuantity* q = new SparseVolumeGridScalarQuantity(name, *this, nodeIndices, nodeValues, type); addQuantity(q); @@ -304,8 +397,9 @@ SparseVolumeGridColorQuantity* SparseVolumeGrid::addCellColorQuantityImpl(std::s return q; } -SparseVolumeGridColorQuantity* SparseVolumeGrid::addNodeColorQuantityImpl( - std::string name, const std::vector& nodeIndices, const std::vector& nodeColors) { +SparseVolumeGridColorQuantity* SparseVolumeGrid::addNodeColorQuantityImpl(std::string name, + const std::vector& nodeIndices, + const std::vector& nodeColors) { checkForQuantityWithNameAndDeleteOrError(name); SparseVolumeGridColorQuantity* q = new SparseVolumeGridColorQuantity(name, *this, nodeIndices, nodeColors); addQuantity(q); diff --git a/src/sparse_volume_grid_color_quantity.cpp b/src/sparse_volume_grid_color_quantity.cpp index e4bcfd82..c3a2bd95 100644 --- a/src/sparse_volume_grid_color_quantity.cpp +++ b/src/sparse_volume_grid_color_quantity.cpp @@ -41,11 +41,8 @@ void SparseVolumeGridColorQuantity::draw() { if (!program) createProgram(); // Set uniforms - parent.setStructureUniforms(*program); - program->setUniform("u_gridSpacing", parent.getGridCellWidth()); - program->setUniform("u_cubeSizeFactor", 1.f - static_cast(parent.getCubeSizeFactor())); + parent.setSparseVolumeGridUniforms(*program); setColorUniforms(*program); - render::engine->setMaterialUniforms(*program, parent.getMaterial()); render::engine->setBackfaceCull(true); program->draw(); @@ -58,7 +55,7 @@ void SparseVolumeGridColorQuantity::createProgram() { program = render::engine->requestShader("GRIDCUBE", render::engine->addMaterialRules(parent.getMaterial(), addColorRules( - parent.addStructureRules({ + parent.addSparseGridShaderRules({ isNodeQuantity ? "GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR" : "GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", "SHADE_COLOR" }) @@ -121,8 +118,8 @@ void SparseVolumeGridColorQuantity::packNodeColors(const std::vector auto it = nodeMap.find(nodeIjk); if (it == nodeMap.end()) { exception("SparseVolumeGridColorQuantity [" + name + "]: missing node color at (" + - std::to_string(nodeIjk.x) + "," + std::to_string(nodeIjk.y) + "," + - std::to_string(nodeIjk.z) + ")"); + std::to_string(nodeIjk.x) + "," + std::to_string(nodeIjk.y) + "," + std::to_string(nodeIjk.z) + + ")"); } cornerColors[cornerIdx] = it->second; } diff --git a/src/sparse_volume_grid_scalar_quantity.cpp b/src/sparse_volume_grid_scalar_quantity.cpp index 95e55ea9..b806b460 100644 --- a/src/sparse_volume_grid_scalar_quantity.cpp +++ b/src/sparse_volume_grid_scalar_quantity.cpp @@ -42,11 +42,8 @@ void SparseVolumeGridScalarQuantity::draw() { if (!program) createProgram(); // Set uniforms - parent.setStructureUniforms(*program); - program->setUniform("u_gridSpacing", parent.getGridCellWidth()); - program->setUniform("u_cubeSizeFactor", 1.f - static_cast(parent.getCubeSizeFactor())); + parent.setSparseVolumeGridUniforms(*program); setScalarUniforms(*program); - render::engine->setMaterialUniforms(*program, parent.getMaterial()); render::engine->setBackfaceCull(true); program->draw(); @@ -74,7 +71,7 @@ void SparseVolumeGridScalarQuantity::createProgram() { program = render::engine->requestShader("GRIDCUBE", render::engine->addMaterialRules(parent.getMaterial(), addScalarRules( - parent.addStructureRules({ + parent.addSparseGridShaderRules({ isNodeQuantity ? "GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR" : "GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR" }) ) diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index 5d3934ac..80db766c 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -75,8 +75,8 @@ void VolumeGrid::buildCustomUI() { ImGui::PushItemWidth(75 * options::uiScale); if (ImGui::SliderFloat("Width", &edgeWidth.get(), 0.001, 2.)) { // NOTE: this intentionally circumvents the setEdgeWidth() setter to avoid repopulating the buffer as the - // slider is dragged---otherwise we repopulate the buffer on every change, which mostly works fine. This is a - // lazy solution instead of better state/buffer management. setEdgeWidth(getEdgeWidth()); + // slider is dragged---otherwise we repopulate the buffer on every change. This is a + // lazy solution instead of better state/buffer management. edgeWidth.manuallyChanged(); requestRedraw(); } @@ -197,7 +197,7 @@ std::vector VolumeGrid::addGridCubeRules(std::vector i if (withShade) { if (getEdgeWidth() > 0) { - initRules.push_back("GRIDCUBE_WIREFRAME"); + initRules.push_back("GRIDCUBE_PLANE_WIREFRAME"); // initRules.push_back("WIREFRAME_SIMPLE"); initRules.push_back("MESH_WIREFRAME"); } @@ -402,8 +402,12 @@ VolumeGrid* VolumeGrid::setMaterial(std::string m) { std::string VolumeGrid::getMaterial() { return material.get(); } VolumeGrid* VolumeGrid::setEdgeWidth(double newVal) { + double oldEdgeWidth = edgeWidth.get(); edgeWidth = newVal; - refresh(); + if ((oldEdgeWidth != 0) != (newVal != 0)) { + // if it changed to/from zero, we disabled/enabled edgges, and need a new program + refresh(); + } requestRedraw(); return this; } From 4d8e5b4f57be883d2755a18da20756fbe819845d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Feb 2026 16:01:01 -1000 Subject: [PATCH 05/18] ui width tweak --- src/sparse_volume_grid.cpp | 2 ++ src/volume_grid.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 2548824b..6f9e37f2 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -108,10 +108,12 @@ void SparseVolumeGrid::buildCustomOptionsUI() { } // Shrinky effect + ImGui::PushItemWidth(150 * options::uiScale); if (ImGui::SliderFloat("Cell Shrink", &cubeSizeFactor.get(), 0.0, 1., "%.3f", ImGuiSliderFlags_Logarithmic)) { cubeSizeFactor.manuallyChanged(); requestRedraw(); } + ImGui::PopItemWidth(); } diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index 80db766c..50ba7ce4 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -94,10 +94,12 @@ void VolumeGrid::buildCustomOptionsUI() { } // Shrinky effect + ImGui::PushItemWidth(150 * options::uiScale); if (ImGui::SliderFloat("Cell Shrink", &cubeSizeFactor.get(), 0.0, 1., "%.3f", ImGuiSliderFlags_Logarithmic)) { cubeSizeFactor.manuallyChanged(); requestRedraw(); } + ImGui::PopItemWidth(); } void VolumeGrid::draw() { From ebca23b22d34337d9e07ee777de33e44be83dd8d Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Feb 2026 16:40:45 -1000 Subject: [PATCH 06/18] add integer vector types --- include/polyscope/render/engine.h | 15 +++ .../render/mock_opengl/mock_gl_engine.h | 12 ++ include/polyscope/render/opengl/gl_engine.h | 12 ++ src/render/engine.cpp | 16 +++ src/render/mock_opengl/mock_gl_engine.cpp | 118 +++++++++++++++-- src/render/opengl/gl_engine.cpp | 124 +++++++++++++++++- src/render/templated_buffers.cpp | 49 +++++-- 7 files changed, 320 insertions(+), 26 deletions(-) diff --git a/include/polyscope/render/engine.h b/include/polyscope/render/engine.h index 13ea05fe..5ce2a41a 100644 --- a/include/polyscope/render/engine.h +++ b/include/polyscope/render/engine.h @@ -47,6 +47,9 @@ enum class RenderDataType { Matrix44Float, Float, Int, + Vector2Int, + Vector3Int, + Vector4Int, UInt, Vector2UInt, Vector3UInt, @@ -78,6 +81,9 @@ class AttributeBuffer { virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; + virtual void setData(const std::vector& data) = 0; + virtual void setData(const std::vector& data) = 0; + virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; @@ -107,6 +113,9 @@ class AttributeBuffer { virtual glm::vec3 getData_vec3(size_t ind) = 0; virtual glm::vec4 getData_vec4(size_t ind) = 0; virtual int getData_int(size_t ind) = 0; + virtual glm::ivec2 getData_ivec2(size_t ind) = 0; + virtual glm::ivec3 getData_ivec3(size_t ind) = 0; + virtual glm::ivec4 getData_ivec4(size_t ind) = 0; virtual uint32_t getData_uint32(size_t ind) = 0; virtual glm::uvec2 getData_uvec2(size_t ind) = 0; virtual glm::uvec3 getData_uvec3(size_t ind) = 0; @@ -119,6 +128,9 @@ class AttributeBuffer { virtual std::vector getDataRange_vec3(size_t ind, size_t count) = 0; virtual std::vector getDataRange_vec4(size_t ind, size_t count) = 0; virtual std::vector getDataRange_int(size_t ind, size_t count) = 0; + virtual std::vector getDataRange_ivec2(size_t ind, size_t count) = 0; + virtual std::vector getDataRange_ivec3(size_t ind, size_t count) = 0; + virtual std::vector getDataRange_ivec4(size_t ind, size_t count) = 0; virtual std::vector getDataRange_uint32(size_t ind, size_t count) = 0; virtual std::vector getDataRange_uvec2(size_t ind, size_t count) = 0; virtual std::vector getDataRange_uvec3(size_t ind, size_t count) = 0; @@ -364,6 +376,9 @@ class ShaderProgram { virtual void setUniform(std::string name, glm::vec4 val) = 0; virtual void setUniform(std::string name, std::array val) = 0; virtual void setUniform(std::string name, float x, float y, float z, float w) = 0; + virtual void setUniform(std::string name, glm::ivec2 val) = 0; + virtual void setUniform(std::string name, glm::ivec3 val) = 0; + virtual void setUniform(std::string name, glm::ivec4 val) = 0; virtual void setUniform(std::string name, glm::uvec2 val) = 0; virtual void setUniform(std::string name, glm::uvec3 val) = 0; virtual void setUniform(std::string name, glm::uvec4 val) = 0; diff --git a/include/polyscope/render/mock_opengl/mock_gl_engine.h b/include/polyscope/render/mock_opengl/mock_gl_engine.h index 873f83d2..4e1fadca 100644 --- a/include/polyscope/render/mock_opengl/mock_gl_engine.h +++ b/include/polyscope/render/mock_opengl/mock_gl_engine.h @@ -27,6 +27,9 @@ class GLAttributeBuffer : public AttributeBuffer { void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; + void setData(const std::vector& data) override; + void setData(const std::vector& data) override; + void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; @@ -46,6 +49,9 @@ class GLAttributeBuffer : public AttributeBuffer { glm::vec3 getData_vec3(size_t ind) override; glm::vec4 getData_vec4(size_t ind) override; int getData_int(size_t ind) override; + glm::ivec2 getData_ivec2(size_t ind) override; + glm::ivec3 getData_ivec3(size_t ind) override; + glm::ivec4 getData_ivec4(size_t ind) override; uint32_t getData_uint32(size_t ind) override; glm::uvec2 getData_uvec2(size_t ind) override; glm::uvec3 getData_uvec3(size_t ind) override; @@ -58,6 +64,9 @@ class GLAttributeBuffer : public AttributeBuffer { std::vector getDataRange_vec3(size_t ind, size_t count) override; std::vector getDataRange_vec4(size_t ind, size_t count) override; std::vector getDataRange_int(size_t ind, size_t count) override; + std::vector getDataRange_ivec2(size_t ind, size_t count) override; + std::vector getDataRange_ivec3(size_t ind, size_t count) override; + std::vector getDataRange_ivec4(size_t ind, size_t count) override; std::vector getDataRange_uint32(size_t ind, size_t count) override; std::vector getDataRange_uvec2(size_t ind, size_t count) override; std::vector getDataRange_uvec3(size_t ind, size_t count) override; @@ -257,6 +266,9 @@ class GLShaderProgram : public ShaderProgram { void setUniform(std::string name, glm::vec4 val) override; void setUniform(std::string name, std::array val) override; void setUniform(std::string name, float x, float y, float z, float w) override; + void setUniform(std::string name, glm::ivec2 val) override; + void setUniform(std::string name, glm::ivec3 val) override; + void setUniform(std::string name, glm::ivec4 val) override; void setUniform(std::string name, glm::uvec2 val) override; void setUniform(std::string name, glm::uvec3 val) override; void setUniform(std::string name, glm::uvec4 val) override; diff --git a/include/polyscope/render/opengl/gl_engine.h b/include/polyscope/render/opengl/gl_engine.h index a1b84204..7e8f2039 100644 --- a/include/polyscope/render/opengl/gl_engine.h +++ b/include/polyscope/render/opengl/gl_engine.h @@ -55,6 +55,9 @@ class GLAttributeBuffer : public AttributeBuffer { void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; + void setData(const std::vector& data) override; + void setData(const std::vector& data) override; + void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; @@ -74,6 +77,9 @@ class GLAttributeBuffer : public AttributeBuffer { glm::vec3 getData_vec3(size_t ind) override; glm::vec4 getData_vec4(size_t ind) override; int getData_int(size_t ind) override; + glm::ivec2 getData_ivec2(size_t ind) override; + glm::ivec3 getData_ivec3(size_t ind) override; + glm::ivec4 getData_ivec4(size_t ind) override; uint32_t getData_uint32(size_t ind) override; glm::uvec2 getData_uvec2(size_t ind) override; glm::uvec3 getData_uvec3(size_t ind) override; @@ -86,6 +92,9 @@ class GLAttributeBuffer : public AttributeBuffer { std::vector getDataRange_vec3(size_t ind, size_t count) override; std::vector getDataRange_vec4(size_t ind, size_t count) override; std::vector getDataRange_int(size_t ind, size_t count) override; + std::vector getDataRange_ivec2(size_t ind, size_t count) override; + std::vector getDataRange_ivec3(size_t ind, size_t count) override; + std::vector getDataRange_ivec4(size_t ind, size_t count) override; std::vector getDataRange_uint32(size_t ind, size_t count) override; std::vector getDataRange_uvec2(size_t ind, size_t count) override; std::vector getDataRange_uvec3(size_t ind, size_t count) override; @@ -300,6 +309,9 @@ class GLShaderProgram : public ShaderProgram { void setUniform(std::string name, glm::vec4 val) override; void setUniform(std::string name, std::array val) override; void setUniform(std::string name, float x, float y, float z, float w) override; + void setUniform(std::string name, glm::ivec2 val) override; + void setUniform(std::string name, glm::ivec3 val) override; + void setUniform(std::string name, glm::ivec4 val) override; void setUniform(std::string name, glm::uvec2 val) override; void setUniform(std::string name, glm::uvec3 val) override; void setUniform(std::string name, glm::uvec4 val) override; diff --git a/src/render/engine.cpp b/src/render/engine.cpp index 6794c1f8..a81c6cbf 100644 --- a/src/render/engine.cpp +++ b/src/render/engine.cpp @@ -63,6 +63,12 @@ std::string renderDataTypeName(const RenderDataType& r) { return "Float"; case RenderDataType::Int: return "Int"; + case RenderDataType::Vector2Int: + return "Vector2Int"; + case RenderDataType::Vector3Int: + return "Vector3Int"; + case RenderDataType::Vector4Int: + return "Vector4Int"; case RenderDataType::UInt: return "UInt"; case RenderDataType::Vector2UInt: @@ -89,6 +95,12 @@ int sizeInBytes(const RenderDataType& r) { return 4; case RenderDataType::Int: return 4; + case RenderDataType::Vector2Int: + return 2 * 4; + case RenderDataType::Vector3Int: + return 3 * 4; + case RenderDataType::Vector4Int: + return 4 * 4; case RenderDataType::UInt: return 4; case RenderDataType::Vector2UInt: @@ -108,6 +120,10 @@ int renderDataTypeCountCompatbility(const RenderDataType r1, const RenderDataTyp if (r1 == RenderDataType::Vector2Float && r2 == RenderDataType::Float) return 2; if (r1 == RenderDataType::Vector3Float && r2 == RenderDataType::Float) return 3; if (r1 == RenderDataType::Vector4Float && r2 == RenderDataType::Float) return 4; + + if (r1 == RenderDataType::Vector2Int && r2 == RenderDataType::Int) return 2; + if (r1 == RenderDataType::Vector3Int && r2 == RenderDataType::Int) return 3; + if (r1 == RenderDataType::Vector4Int && r2 == RenderDataType::Int) return 4; if (r1 == RenderDataType::Vector2UInt && r2 == RenderDataType::UInt) return 2; if (r1 == RenderDataType::Vector3UInt && r2 == RenderDataType::UInt) return 3; diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index a197024f..087e2f86 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -145,12 +145,22 @@ void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::Int); setData_helper(data); } - +void GLAttributeBuffer::setData(const std::vector& data) { + checkType(RenderDataType::Vector2Int); + setData_helper(data); +} +void GLAttributeBuffer::setData(const std::vector& data) { + checkType(RenderDataType::Vector3Int); + setData_helper(data); +} +void GLAttributeBuffer::setData(const std::vector& data) { + checkType(RenderDataType::Vector4Int); + setData_helper(data); +} void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::UInt); setData_helper(data); } - void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::Vector2UInt); setData_helper(data); @@ -159,7 +169,6 @@ void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::Vector3UInt); setData_helper(data); } - void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::Vector4UInt); setData_helper(data); @@ -199,20 +208,32 @@ int GLAttributeBuffer::getData_int(size_t ind) { if (getType() != RenderDataType::Int) exception("bad getData type"); return getData_helper(ind); } +glm::ivec2 GLAttributeBuffer::getData_ivec2(size_t ind) { + if (getType() != RenderDataType::Vector2Int) exception("bad getData type"); + return getData_helper(ind); +} +glm::ivec3 GLAttributeBuffer::getData_ivec3(size_t ind) { + if (getType() != RenderDataType::Vector3Int) exception("bad getData type"); + return getData_helper(ind); +} +glm::ivec4 GLAttributeBuffer::getData_ivec4(size_t ind) { + if (getType() != RenderDataType::Vector4Int) exception("bad getData type"); + return getData_helper(ind); +} uint32_t GLAttributeBuffer::getData_uint32(size_t ind) { if (getType() != RenderDataType::UInt) exception("bad getData type"); return getData_helper(ind); } glm::uvec2 GLAttributeBuffer::getData_uvec2(size_t ind) { - if (getType() != RenderDataType::Vector2Float) exception("bad getData type"); + if (getType() != RenderDataType::Vector2UInt) exception("bad getData type"); return getData_helper(ind); } glm::uvec3 GLAttributeBuffer::getData_uvec3(size_t ind) { - if (getType() != RenderDataType::Vector3Float) exception("bad getData type"); + if (getType() != RenderDataType::Vector3UInt) exception("bad getData type"); return getData_helper(ind); } glm::uvec4 GLAttributeBuffer::getData_uvec4(size_t ind) { - if (getType() != RenderDataType::Vector4Float) exception("bad getData type"); + if (getType() != RenderDataType::Vector4UInt) exception("bad getData type"); return getData_helper(ind); } @@ -256,6 +277,18 @@ std::vector GLAttributeBuffer::getDataRange_int(size_t start, size_t count) if (getType() != RenderDataType::Int) exception("bad getData type"); return getDataRange_helper(start, count); } +std::vector GLAttributeBuffer::getDataRange_ivec2(size_t start, size_t count) { + if (getType() != RenderDataType::Vector2Int) exception("bad getData type"); + return getDataRange_helper(start, count); +} +std::vector GLAttributeBuffer::getDataRange_ivec3(size_t start, size_t count) { + if (getType() != RenderDataType::Vector3Int) exception("bad getData type"); + return getDataRange_helper(start, count); +} +std::vector GLAttributeBuffer::getDataRange_ivec4(size_t start, size_t count) { + if (getType() != RenderDataType::Vector4Int) exception("bad getData type"); + return getDataRange_helper(start, count); +} std::vector GLAttributeBuffer::getDataRange_uint32(size_t start, size_t count) { if (getType() != RenderDataType::UInt) exception("bad getData type"); return getDataRange_helper(start, count); @@ -270,7 +303,6 @@ std::vector GLAttributeBuffer::getDataRange_uvec3(size_t start, size } std::vector GLAttributeBuffer::getDataRange_uvec4(size_t start, size_t count) { if (getType() != RenderDataType::Vector4UInt) exception("bad getData type"); - bind(); return getDataRange_helper(start, count); } @@ -846,16 +878,22 @@ void GLShaderProgram::assignBufferToVAO(GLShaderAttribute& a) { switch (a.type) { case RenderDataType::Float: break; - case RenderDataType::Int: - break; - case RenderDataType::UInt: - break; case RenderDataType::Vector2Float: break; case RenderDataType::Vector3Float: break; case RenderDataType::Vector4Float: break; + case RenderDataType::Int: + break; + case RenderDataType::Vector2Int: + break; + case RenderDataType::Vector3Int: + break; + case RenderDataType::Vector4Int: + break; + case RenderDataType::UInt: + break; case RenderDataType::Vector2UInt: break; case RenderDataType::Vector3UInt: @@ -1056,6 +1094,54 @@ void GLShaderProgram::setUniform(std::string name, float x, float y, float z, fl throw std::invalid_argument("Tried to set nonexistent uniform with name " + name); } +// Set a int vector2 uniform +void GLShaderProgram::setUniform(std::string name, glm::ivec2 val) { + + for (GLShaderUniform& u : uniforms) { + if (u.name == name) { + if (u.type == RenderDataType::Vector2Int) { + u.isSet = true; + } else { + throw std::invalid_argument("Tried to set GLShaderUniform with wrong type"); + } + return; + } + } + throw std::invalid_argument("Tried to set nonexistent uniform with name " + name); +} + +// Set a int vector3 uniform +void GLShaderProgram::setUniform(std::string name, glm::ivec3 val) { + + for (GLShaderUniform& u : uniforms) { + if (u.name == name) { + if (u.type == RenderDataType::Vector3Int) { + u.isSet = true; + } else { + throw std::invalid_argument("Tried to set GLShaderUniform with wrong type"); + } + return; + } + } + throw std::invalid_argument("Tried to set nonexistent uniform with name " + name); +} + +// Set a int vector4 uniform +void GLShaderProgram::setUniform(std::string name, glm::ivec4 val) { + + for (GLShaderUniform& u : uniforms) { + if (u.name == name) { + if (u.type == RenderDataType::Vector4Int) { + u.isSet = true; + } else { + throw std::invalid_argument("Tried to set GLShaderUniform with wrong type"); + } + return; + } + } + throw std::invalid_argument("Tried to set nonexistent uniform with name " + name); +} + // Set a uint vector2 uniform void GLShaderProgram::setUniform(std::string name, glm::uvec2 val) { @@ -1443,6 +1529,15 @@ void GLShaderProgram::setIndex(std::shared_ptr externalBuffer) // values don't make sense anyway, so I think it's okay to just let it slide indexSizeMult = 1; break; + case RenderDataType::Vector2Int: + indexSizeMult = 2; + break; + case RenderDataType::Vector3Int: + indexSizeMult = 3; + break; + case RenderDataType::Vector4Int: + indexSizeMult = 4; + break; case RenderDataType::UInt: indexSizeMult = 1; break; @@ -2143,6 +2238,7 @@ void MockGLEngine::populateDefaultShadersAndRules() { registerShaderRule("GRIDCUBE_PLANE_WIREFRAME", GRIDCUBE_PLANE_WIREFRAME); registerShaderRule("GRIDCUBE_CONSTANT_PICK", GRIDCUBE_CONSTANT_PICK); registerShaderRule("GRIDCUBE_CULLPOS_FROM_CENTER", GRIDCUBE_CULLPOS_FROM_CENTER); + registerShaderRule("GRIDCUBE_PLANE_CULLPOS_FROM_CENTER", GRIDCUBE_PLANE_CULLPOS_FROM_CENTER); registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR", GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR); registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR); registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR", GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR); diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index 1189376c..a10ef08d 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -321,12 +321,23 @@ void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::Int); setData_helper(data); } +void GLAttributeBuffer::setData(const std::vector& data) { + checkType(RenderDataType::Vector2Int); + setData_helper(data); +} +void GLAttributeBuffer::setData(const std::vector& data) { + checkType(RenderDataType::Vector3Int); + setData_helper(data); +} +void GLAttributeBuffer::setData(const std::vector& data) { + checkType(RenderDataType::Vector4Int); + setData_helper(data); +} void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::UInt); setData_helper(data); } - void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::Vector2UInt); setData_helper(data); @@ -335,7 +346,6 @@ void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::Vector3UInt); setData_helper(data); } - void GLAttributeBuffer::setData(const std::vector& data) { checkType(RenderDataType::Vector4UInt); setData_helper(data); @@ -375,20 +385,32 @@ int GLAttributeBuffer::getData_int(size_t ind) { if (getType() != RenderDataType::Int) exception("bad getData type"); return getData_helper(ind); } +glm::ivec2 GLAttributeBuffer::getData_ivec2(size_t ind) { + if (getType() != RenderDataType::Vector2Int) exception("bad getData type"); + return getData_helper(ind); +} +glm::ivec3 GLAttributeBuffer::getData_ivec3(size_t ind) { + if (getType() != RenderDataType::Vector3Int) exception("bad getData type"); + return getData_helper(ind); +} +glm::ivec4 GLAttributeBuffer::getData_ivec4(size_t ind) { + if (getType() != RenderDataType::Vector4Int) exception("bad getData type"); + return getData_helper(ind); +} uint32_t GLAttributeBuffer::getData_uint32(size_t ind) { if (getType() != RenderDataType::UInt) exception("bad getData type"); return getData_helper(ind); } glm::uvec2 GLAttributeBuffer::getData_uvec2(size_t ind) { - if (getType() != RenderDataType::Vector2Float) exception("bad getData type"); + if (getType() != RenderDataType::Vector2UInt) exception("bad getData type"); return getData_helper(ind); } glm::uvec3 GLAttributeBuffer::getData_uvec3(size_t ind) { - if (getType() != RenderDataType::Vector3Float) exception("bad getData type"); + if (getType() != RenderDataType::Vector3UInt) exception("bad getData type"); return getData_helper(ind); } glm::uvec4 GLAttributeBuffer::getData_uvec4(size_t ind) { - if (getType() != RenderDataType::Vector4Float) exception("bad getData type"); + if (getType() != RenderDataType::Vector4UInt) exception("bad getData type"); return getData_helper(ind); } @@ -433,6 +455,18 @@ std::vector GLAttributeBuffer::getDataRange_int(size_t start, size_t count) if (getType() != RenderDataType::Int) exception("bad getData type"); return getDataRange_helper(start, count); } +std::vector GLAttributeBuffer::getDataRange_ivec2(size_t start, size_t count) { + if (getType() != RenderDataType::Vector2Int) exception("bad getData type"); + return getDataRange_helper(start, count); +} +std::vector GLAttributeBuffer::getDataRange_ivec3(size_t start, size_t count) { + if (getType() != RenderDataType::Vector3Int) exception("bad getData type"); + return getDataRange_helper(start, count); +} +std::vector GLAttributeBuffer::getDataRange_ivec4(size_t start, size_t count) { + if (getType() != RenderDataType::Vector4Int) exception("bad getData type"); + return getDataRange_helper(start, count); +} std::vector GLAttributeBuffer::getDataRange_uint32(size_t start, size_t count) { if (getType() != RenderDataType::UInt) exception("bad getData type"); return getDataRange_helper(start, count); @@ -447,7 +481,6 @@ std::vector GLAttributeBuffer::getDataRange_uvec3(size_t start, size } std::vector GLAttributeBuffer::getDataRange_uvec4(size_t start, size_t count) { if (getType() != RenderDataType::Vector4UInt) exception("bad getData type"); - bind(); return getDataRange_helper(start, count); } @@ -1318,6 +1351,18 @@ void GLShaderProgram::assignBufferToVAO(GLShaderAttribute& a) { glVertexAttribPointer(a.location + iArrInd, 4, GL_FLOAT, GL_FALSE, sizeof(float) * 4 * a.arrayCount, reinterpret_cast(sizeof(float) * 4 * iArrInd)); break; + case RenderDataType::Vector2Int: + glVertexAttribPointer(a.location + iArrInd, 2, GL_INT, GL_FALSE, sizeof(int32_t) * 2 * a.arrayCount, + reinterpret_cast(sizeof(int32_t) * 2 * iArrInd)); + break; + case RenderDataType::Vector3Int: + glVertexAttribPointer(a.location + iArrInd, 3, GL_INT, GL_FALSE, sizeof(int32_t) * 3 * a.arrayCount, + reinterpret_cast(sizeof(int32_t) * 3 * iArrInd)); + break; + case RenderDataType::Vector4Int: + glVertexAttribPointer(a.location + iArrInd, 4, GL_INT, GL_FALSE, sizeof(int32_t) * 4 * a.arrayCount, + reinterpret_cast(sizeof(int32_t) * 4 * iArrInd)); + break; case RenderDataType::Vector2UInt: glVertexAttribPointer(a.location + iArrInd, 2, GL_UNSIGNED_INT, GL_FALSE, sizeof(uint32_t) * 2 * a.arrayCount, reinterpret_cast(sizeof(uint32_t) * 2 * iArrInd)); @@ -1559,6 +1604,63 @@ void GLShaderProgram::setUniform(std::string name, float x, float y, float z, fl throw std::invalid_argument("Tried to set nonexistent uniform with name " + name); } +// Set a int vector2 uniform +void GLShaderProgram::setUniform(std::string name, glm::ivec2 val) { + glUseProgram(compiledProgram->getHandle()); + + for (GLShaderUniform& u : uniforms) { + if (u.name == name) { + if (u.location == -1) return; + if (u.type == RenderDataType::Vector2Int) { + glUniform2i(u.location, val.x, val.y); + u.isSet = true; + } else { + throw std::invalid_argument("Tried to set GLShaderUniform with wrong type"); + } + return; + } + } + throw std::invalid_argument("Tried to set nonexistent uniform with name " + name); +} + +// Set a int vector3 uniform +void GLShaderProgram::setUniform(std::string name, glm::ivec3 val) { + glUseProgram(compiledProgram->getHandle()); + + for (GLShaderUniform& u : uniforms) { + if (u.name == name) { + if (u.location == -1) return; + if (u.type == RenderDataType::Vector3Int) { + glUniform3i(u.location, val.x, val.y, val.z); + u.isSet = true; + } else { + throw std::invalid_argument("Tried to set GLShaderUniform with wrong type"); + } + return; + } + } + throw std::invalid_argument("Tried to set nonexistent uniform with name " + name); +} + +// Set a int vector4 uniform +void GLShaderProgram::setUniform(std::string name, glm::ivec4 val) { + glUseProgram(compiledProgram->getHandle()); + + for (GLShaderUniform& u : uniforms) { + if (u.name == name) { + if (u.location == -1) return; + if (u.type == RenderDataType::Vector4Int) { + glUniform4i(u.location, val.x, val.y, val.z, val.w); + u.isSet = true; + } else { + throw std::invalid_argument("Tried to set GLShaderUniform with wrong type"); + } + return; + } + } + throw std::invalid_argument("Tried to set nonexistent uniform with name " + name); +} + // Set a uint vector2 uniform void GLShaderProgram::setUniform(std::string name, glm::uvec2 val) { glUseProgram(compiledProgram->getHandle()); @@ -1975,6 +2077,15 @@ void GLShaderProgram::setIndex(std::shared_ptr externalBuffer) // values don't make sense anyway, so I think it's okay to just let it slide indexSizeMult = 1; break; + case RenderDataType::Vector2Int: + indexSizeMult = 2; + break; + case RenderDataType::Vector3Int: + indexSizeMult = 3; + break; + case RenderDataType::Vector4Int: + indexSizeMult = 4; + break; case RenderDataType::UInt: indexSizeMult = 1; break; @@ -2610,6 +2721,7 @@ void GLEngine::populateDefaultShadersAndRules() { registerShaderRule("GRIDCUBE_PLANE_WIREFRAME", GRIDCUBE_PLANE_WIREFRAME); registerShaderRule("GRIDCUBE_CONSTANT_PICK", GRIDCUBE_CONSTANT_PICK); registerShaderRule("GRIDCUBE_CULLPOS_FROM_CENTER", GRIDCUBE_CULLPOS_FROM_CENTER); + registerShaderRule("GRIDCUBE_PLANE_CULLPOS_FROM_CENTER", GRIDCUBE_PLANE_CULLPOS_FROM_CENTER); registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR", GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR); registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR); registerShaderRule("GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR", GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR); diff --git a/src/render/templated_buffers.cpp b/src/render/templated_buffers.cpp index c6d374e5..0bc1b6c9 100644 --- a/src/render/templated_buffers.cpp +++ b/src/render/templated_buffers.cpp @@ -56,18 +56,33 @@ std::shared_ptr generateAttributeBuffer(Engine* engi } template <> -std::shared_ptr generateAttributeBuffer(Engine* engine) { - return engine->generateAttributeBuffer(RenderDataType::UInt); +std::shared_ptr generateAttributeBuffer(Engine* engine) { + return engine->generateAttributeBuffer(RenderDataType::Int); } template <> -std::shared_ptr generateAttributeBuffer(Engine* engine) { +std::shared_ptr generateAttributeBuffer(Engine* engine) { + return engine->generateAttributeBuffer(RenderDataType::Vector2Int); +} + +template <> +std::shared_ptr generateAttributeBuffer(Engine* engine) { + return engine->generateAttributeBuffer(RenderDataType::Vector3Int); +} + +template <> +std::shared_ptr generateAttributeBuffer(Engine* engine) { + return engine->generateAttributeBuffer(RenderDataType::Vector4Int); +} + +template <> +std::shared_ptr generateAttributeBuffer(Engine* engine) { return engine->generateAttributeBuffer(RenderDataType::UInt); } template <> -std::shared_ptr generateAttributeBuffer(Engine* engine) { - return engine->generateAttributeBuffer(RenderDataType::Int); +std::shared_ptr generateAttributeBuffer(Engine* engine) { + return engine->generateAttributeBuffer(RenderDataType::UInt); } template <> @@ -85,6 +100,7 @@ std::shared_ptr generateAttributeBuffer(Engine* eng return engine->generateAttributeBuffer(RenderDataType::Vector4UInt); } + // == Get buffer data at a single location template <> @@ -243,13 +259,28 @@ std::vector getAttributeBufferDataRange(AttributeBuffer& buf } template <> -std::vector getAttributeBufferDataRange(AttributeBuffer& buff, size_t ind, size_t count) { - return buff.getDataRange_uint32(ind, count); +std::vector getAttributeBufferDataRange(AttributeBuffer& buff, size_t ind, size_t count) { + return buff.getDataRange_int(ind, count); } template <> -std::vector getAttributeBufferDataRange(AttributeBuffer& buff, size_t ind, size_t count) { - return buff.getDataRange_int(ind, count); +std::vector getAttributeBufferDataRange(AttributeBuffer& buff, size_t ind, size_t count) { + return buff.getDataRange_ivec2(ind, count); +} + +template <> +std::vector getAttributeBufferDataRange(AttributeBuffer& buff, size_t ind, size_t count) { + return buff.getDataRange_ivec3(ind, count); +} + +template <> +std::vector getAttributeBufferDataRange(AttributeBuffer& buff, size_t ind, size_t count) { + return buff.getDataRange_ivec4(ind, count); +} + +template <> +std::vector getAttributeBufferDataRange(AttributeBuffer& buff, size_t ind, size_t count) { + return buff.getDataRange_uint32(ind, count); } template <> From 9f8f46a2e9a2ab3cd52a4ff8b197237399a711af Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Feb 2026 16:58:51 -1000 Subject: [PATCH 07/18] more fixes to add vector int buffers --- include/polyscope/numeric_helpers.h | 13 ++++++ include/polyscope/render/engine.h | 3 ++ include/polyscope/render/managed_buffer.h | 5 ++- .../render/mock_opengl/mock_gl_engine.h | 3 ++ include/polyscope/render/opengl/gl_engine.h | 3 ++ include/polyscope/types.h | 16 +++++--- src/render/managed_buffer.cpp | 40 ++++++++----------- src/render/mock_opengl/mock_gl_engine.cpp | 3 ++ src/render/opengl/gl_engine.cpp | 3 ++ src/render/templated_buffers.cpp | 27 +++++++++++-- 10 files changed, 83 insertions(+), 33 deletions(-) diff --git a/include/polyscope/numeric_helpers.h b/include/polyscope/numeric_helpers.h index f451b7b7..eeac8aba 100644 --- a/include/polyscope/numeric_helpers.h +++ b/include/polyscope/numeric_helpers.h @@ -72,6 +72,19 @@ inline bool allComponentsFinite(const glm::vec4& x) { return glm::all(glm::isfinite(x)); } +template <> +inline bool allComponentsFinite(const glm::ivec2& x) { + return true; +} +template <> +inline bool allComponentsFinite(const glm::ivec3& x) { + return true; +} +template <> +inline bool allComponentsFinite(const glm::ivec4& x) { + return true; +} + template <> inline bool allComponentsFinite(const glm::uvec2& x) { return true; diff --git a/include/polyscope/render/engine.h b/include/polyscope/render/engine.h index 5ce2a41a..38349f97 100644 --- a/include/polyscope/render/engine.h +++ b/include/polyscope/render/engine.h @@ -167,6 +167,9 @@ class TextureBuffer { virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; + virtual void setData(const std::vector& data) = 0; + virtual void setData(const std::vector& data) = 0; + virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; virtual void setData(const std::vector& data) = 0; diff --git a/include/polyscope/render/managed_buffer.h b/include/polyscope/render/managed_buffer.h index 6930ff8c..71c5deda 100644 --- a/include/polyscope/render/managed_buffer.h +++ b/include/polyscope/render/managed_buffer.h @@ -290,8 +290,11 @@ class ManagedBufferRegistry { ManagedBufferMap> managedBufferMap_arr2vec3; ManagedBufferMap> managedBufferMap_arr3vec3; ManagedBufferMap> managedBufferMap_arr4vec3; - ManagedBufferMap managedBufferMap_uint32; ManagedBufferMap managedBufferMap_int32; + ManagedBufferMap managedBufferMap_ivec2; + ManagedBufferMap managedBufferMap_ivec3; + ManagedBufferMap managedBufferMap_ivec4; + ManagedBufferMap managedBufferMap_uint32; ManagedBufferMap managedBufferMap_uvec2; ManagedBufferMap managedBufferMap_uvec3; ManagedBufferMap managedBufferMap_uvec4; diff --git a/include/polyscope/render/mock_opengl/mock_gl_engine.h b/include/polyscope/render/mock_opengl/mock_gl_engine.h index 4e1fadca..cc4d2d49 100644 --- a/include/polyscope/render/mock_opengl/mock_gl_engine.h +++ b/include/polyscope/render/mock_opengl/mock_gl_engine.h @@ -122,6 +122,9 @@ class GLTextureBuffer : public TextureBuffer { void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; + void setData(const std::vector& data) override; + void setData(const std::vector& data) override; + void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; diff --git a/include/polyscope/render/opengl/gl_engine.h b/include/polyscope/render/opengl/gl_engine.h index 7e8f2039..7e06d358 100644 --- a/include/polyscope/render/opengl/gl_engine.h +++ b/include/polyscope/render/opengl/gl_engine.h @@ -154,6 +154,9 @@ class GLTextureBuffer : public TextureBuffer { void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; + void setData(const std::vector& data) override; + void setData(const std::vector& data) override; + void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; void setData(const std::vector& data) override; diff --git a/include/polyscope/types.h b/include/polyscope/types.h index 53c4cebe..e0bf5031 100644 --- a/include/polyscope/types.h +++ b/include/polyscope/types.h @@ -7,9 +7,9 @@ // Various types / enums / forward declarations which are broadly useful // The string templates allow to/from string like: -// to_string(ProjectionMode::Perspective) -// from_string("Perspective") -// bool success = try_from_string("Perspective", val_out); +// enum_to_string(ProjectionMode::Perspective) +// enum_from_string("Perspective") +// bool success = try_enum_from_string("Perspective", val_out); // see utilities.h for implementation // clang-format off @@ -212,8 +212,11 @@ enum class ManagedBufferType { Arr2Vec3, Arr3Vec3, Arr4Vec3, - UInt32, Int32, + IVec2, + IVec3, + IVec4, + UInt32, UVec2, UVec3, UVec4 @@ -227,8 +230,11 @@ POLYSCOPE_DEFINE_ENUM_NAMES(ManagedBufferType, {ManagedBufferType::Arr2Vec3, "Arr2Vec3"}, {ManagedBufferType::Arr3Vec3, "Arr3Vec3"}, {ManagedBufferType::Arr4Vec3, "Arr4Vec3"}, - {ManagedBufferType::UInt32, "UInt32"}, {ManagedBufferType::Int32, "Int32"}, + {ManagedBufferType::IVec2, "IVec2"}, + {ManagedBufferType::IVec3, "IVec3"}, + {ManagedBufferType::IVec4, "IVec4"}, + {ManagedBufferType::UInt32, "UInt32"}, {ManagedBufferType::UVec2, "UVec2"}, {ManagedBufferType::UVec3, "UVec3"}, {ManagedBufferType::UVec4, "UVec4"} diff --git a/src/render/managed_buffer.cpp b/src/render/managed_buffer.cpp index 35317b12..723c331d 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -528,9 +528,12 @@ std::tuple ManagedBufferRegistry::hasManagedBufferType( if (hasManagedBuffer>(name)) return std::make_tuple(true, ManagedBufferType::Arr3Vec3); if (hasManagedBuffer>(name)) return std::make_tuple(true, ManagedBufferType::Arr4Vec3); - if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::UInt32); if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::Int32); + if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::IVec2); + if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::IVec3); + if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::IVec4); + if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::UInt32); if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::UVec2); if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::UVec3); if (hasManagedBuffer(name)) return std::make_tuple(true, ManagedBufferType::UVec4); @@ -555,9 +558,12 @@ template class ManagedBuffer>; template class ManagedBuffer>; template class ManagedBuffer>; -template class ManagedBuffer; template class ManagedBuffer; +template class ManagedBuffer; +template class ManagedBuffer; +template class ManagedBuffer; +template class ManagedBuffer; template class ManagedBuffer; template class ManagedBuffer; template class ManagedBuffer; @@ -575,9 +581,12 @@ template struct ManagedBufferMap>; template struct ManagedBufferMap>; template struct ManagedBufferMap>; -template struct ManagedBufferMap; template struct ManagedBufferMap; +template struct ManagedBufferMap; +template struct ManagedBufferMap; +template struct ManagedBufferMap; +template struct ManagedBufferMap; template struct ManagedBufferMap; template struct ManagedBufferMap; template struct ManagedBufferMap; @@ -593,8 +602,11 @@ template<> ManagedBufferMap& ManagedBufferMap ManagedBufferMap>& ManagedBufferMap>::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_arr2vec3; } template<> ManagedBufferMap>& ManagedBufferMap>::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_arr3vec3; } template<> ManagedBufferMap>& ManagedBufferMap>::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_arr4vec3; } -template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_uint32; } template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_int32; } +template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_ivec2; } +template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_ivec3; } +template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_ivec4; } +template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_uint32; } template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_uvec2; } template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_uvec3; } template<> ManagedBufferMap& ManagedBufferMap::getManagedBufferMapRef (ManagedBufferRegistry* r) { return r->managedBufferMap_uvec4; } @@ -604,25 +616,7 @@ template<> ManagedBufferMap& ManagedBufferMap& data) { checkGLError(); }; void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; +void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; +void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; +void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index a10ef08d..608f0133 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -708,6 +708,9 @@ void GLTextureBuffer::setData(const std::vector& data) { checkGLError(); }; void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; +void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; +void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; +void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; void GLTextureBuffer::setData(const std::vector& data) { exception("not implemented"); }; diff --git a/src/render/templated_buffers.cpp b/src/render/templated_buffers.cpp index 0bc1b6c9..d48b2ac0 100644 --- a/src/render/templated_buffers.cpp +++ b/src/render/templated_buffers.cpp @@ -158,13 +158,28 @@ uint64_t getAttributeBufferData(AttributeBuffer& buff, size_t ind) { } template <> -uint32_t getAttributeBufferData(AttributeBuffer& buff, size_t ind) { - return buff.getData_uint32(ind); +int32_t getAttributeBufferData(AttributeBuffer& buff, size_t ind) { + return buff.getData_int(ind); } template <> -int32_t getAttributeBufferData(AttributeBuffer& buff, size_t ind) { - return buff.getData_int(ind); +glm::ivec2 getAttributeBufferData(AttributeBuffer& buff, size_t ind) { + return buff.getData_ivec2(ind); +} + +template <> +glm::ivec3 getAttributeBufferData(AttributeBuffer& buff, size_t ind) { + return buff.getData_ivec3(ind); +} + +template <> +glm::ivec4 getAttributeBufferData(AttributeBuffer& buff, size_t ind) { + return buff.getData_ivec4(ind); +} + +template <> +uint32_t getAttributeBufferData(AttributeBuffer& buff, size_t ind) { + return buff.getData_uint32(ind); } template <> @@ -396,6 +411,10 @@ template std::shared_ptr generateTextureBuffer(DeviceB template std::shared_ptr generateTextureBuffer(DeviceBufferType D, Engine* engine); template std::shared_ptr generateTextureBuffer(DeviceBufferType D, Engine* engine); +template std::shared_ptr generateTextureBuffer(DeviceBufferType D, Engine* engine); +template std::shared_ptr generateTextureBuffer(DeviceBufferType D, Engine* engine); +template std::shared_ptr generateTextureBuffer(DeviceBufferType D, Engine* engine); + template std::shared_ptr generateTextureBuffer(DeviceBufferType D, Engine* engine); template std::shared_ptr generateTextureBuffer(DeviceBufferType D, Engine* engine); template std::shared_ptr generateTextureBuffer(DeviceBufferType D, Engine* engine); From b8e274e19fd782e48ff18dfe0b2e8588f1827e41 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Feb 2026 17:09:14 -1000 Subject: [PATCH 08/18] fix slice planes for sparse grid --- .../render/opengl/shaders/grid_shaders.h | 1 + include/polyscope/sparse_volume_grid.h | 4 +- src/render/opengl/shaders/grid_shaders.cpp | 87 ++++--- src/sparse_volume_grid.cpp | 8 +- src/volume_grid.cpp | 2 +- test/src/sparse_volume_grid_test.cpp | 213 ++++++++---------- 6 files changed, 163 insertions(+), 152 deletions(-) diff --git a/include/polyscope/render/opengl/shaders/grid_shaders.h b/include/polyscope/render/opengl/shaders/grid_shaders.h index c117e375..15175039 100644 --- a/include/polyscope/render/opengl/shaders/grid_shaders.h +++ b/include/polyscope/render/opengl/shaders/grid_shaders.h @@ -23,6 +23,7 @@ extern const ShaderReplacementRule GRIDCUBE_WIREFRAME; extern const ShaderReplacementRule GRIDCUBE_PLANE_WIREFRAME; extern const ShaderReplacementRule GRIDCUBE_CONSTANT_PICK; extern const ShaderReplacementRule GRIDCUBE_CULLPOS_FROM_CENTER; +extern const ShaderReplacementRule GRIDCUBE_PLANE_CULLPOS_FROM_CENTER; // Attribute-based rules for sparse volume grid quantities extern const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR; diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h index 1caa244c..fd324ec9 100644 --- a/include/polyscope/sparse_volume_grid.h +++ b/include/polyscope/sparse_volume_grid.h @@ -47,7 +47,7 @@ class SparseVolumeGrid : public Structure { // === Geometry members render::ManagedBuffer cellPositions; - render::ManagedBuffer cellIndices; // uvec3 for GPU; derived from signed occupiedCells + render::ManagedBuffer cellIndices; // === Grid info uint64_t nCells() const; @@ -110,7 +110,7 @@ class SparseVolumeGrid : public Structure { // === Storage for managed quantities std::vector cellPositionsData; - std::vector cellIndicesData; // uvec3 for GPU attribute + std::vector cellIndicesData; // User-facing occupied cell indices (signed) std::vector occupiedCellsData; diff --git a/src/render/opengl/shaders/grid_shaders.cpp b/src/render/opengl/shaders/grid_shaders.cpp index 78df6f2d..977b6e37 100644 --- a/src/render/opengl/shaders/grid_shaders.cpp +++ b/src/render/opengl/shaders/grid_shaders.cpp @@ -20,7 +20,7 @@ const ShaderStageSpecification FLEX_GRIDCUBE_VERT_SHADER = { // attributes { {"a_cellPosition", RenderDataType::Vector3Float}, - {"a_cellInd", RenderDataType::Vector3UInt}, + {"a_cellInd", RenderDataType::Vector3Int}, }, {}, // textures @@ -30,9 +30,9 @@ R"( ${ GLSL_VERSION }$ in vec3 a_cellPosition; - in uvec3 a_cellInd; + in ivec3 a_cellInd; - out uvec3 a_cellIndToGeom; + out ivec3 a_cellIndToGeom; ${ VERT_DECLARATIONS }$ @@ -71,7 +71,7 @@ R"( layout(points) in; layout(triangle_strip, max_vertices=14) out; - in uvec3 a_cellIndToGeom[]; + in ivec3 a_cellIndToGeom[]; uniform mat4 u_modelView; uniform mat4 u_projMatrix; @@ -79,7 +79,9 @@ R"( uniform vec3 u_gridSpacing; uniform float u_cubeSizeFactor; - out vec3 a_gridCoordToFrag; // TODO working here + out vec3 a_gridCoordToFrag; + flat out ivec3 cellIndToFrag; + out vec3 centerToFrag; ${ GEOM_DECLARATIONS }$ @@ -111,39 +113,38 @@ R"( vec4 p7 = T * vec4(center + c7 * dvec, 1.f); // node corner indices - uvec3 iCenter = a_cellIndToGeom[0]; - uvec3 i0 = iCenter + uvec3(0, 0, 0); - uvec3 i1 = iCenter + uvec3(0, 0, 1); - uvec3 i2 = iCenter + uvec3(0, 1, 0); - uvec3 i3 = iCenter + uvec3(0, 1, 1); - uvec3 i4 = iCenter + uvec3(1, 0, 0); - uvec3 i5 = iCenter + uvec3(1, 0, 1); - uvec3 i6 = iCenter + uvec3(1, 1, 0); - uvec3 i7 = iCenter + uvec3(1, 1, 1); + ivec3 iCenter = a_cellIndToGeom[0]; + ivec3 i0 = iCenter + ivec3(0, 0, 0); + ivec3 i1 = iCenter + ivec3(0, 0, 1); + ivec3 i2 = iCenter + ivec3(0, 1, 0); + ivec3 i3 = iCenter + ivec3(0, 1, 1); + ivec3 i4 = iCenter + ivec3(1, 0, 0); + ivec3 i5 = iCenter + ivec3(1, 0, 1); + ivec3 i6 = iCenter + ivec3(1, 1, 0); + ivec3 i7 = iCenter + ivec3(1, 1, 1); ${ GEOM_COMPUTE_BEFORE_EMIT }$ vec4 nodePos; - uvec3 nodeInd; - uvec3 cellInd; + ivec3 nodeInd; // this is the order to emit veritces to get a cube triangle strip // 3, 7, 1, 5, 4, 7, 6, 3, 2, 1, 0, 4, 2, 6, - /* 7 */ nodePos = p7; nodeInd = i7; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); - /* 3 */ nodePos = p3; nodeInd = i3; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); - /* 5 */ nodePos = p5; nodeInd = i5; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); - /* 1 */ nodePos = p1; nodeInd = i1; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c1; EmitVertex(); - /* 0 */ nodePos = p0; nodeInd = i0; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); - /* 3 */ nodePos = p3; nodeInd = i3; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); - /* 2 */ nodePos = p2; nodeInd = i2; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); - /* 7 */ nodePos = p7; nodeInd = i7; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); - /* 6 */ nodePos = p6; nodeInd = i6; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); - /* 5 */ nodePos = p5; nodeInd = i5; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); - /* 4 */ nodePos = p4; nodeInd = i4; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c4; EmitVertex(); - /* 0 */ nodePos = p0; nodeInd = i0; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); - /* 6 */ nodePos = p6; nodeInd = i6; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); - /* 2 */ nodePos = p2; nodeInd = i2; cellInd = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); + /* 7 */ nodePos = p7; nodeInd = i7; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); + /* 3 */ nodePos = p3; nodeInd = i3; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); + /* 5 */ nodePos = p5; nodeInd = i5; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); + /* 1 */ nodePos = p1; nodeInd = i1; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c1; EmitVertex(); + /* 0 */ nodePos = p0; nodeInd = i0; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); + /* 3 */ nodePos = p3; nodeInd = i3; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); + /* 2 */ nodePos = p2; nodeInd = i2; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); + /* 7 */ nodePos = p7; nodeInd = i7; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); + /* 6 */ nodePos = p6; nodeInd = i6; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); + /* 5 */ nodePos = p5; nodeInd = i5; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); + /* 4 */ nodePos = p4; nodeInd = i4; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c4; EmitVertex(); + /* 0 */ nodePos = p0; nodeInd = i0; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); + /* 6 */ nodePos = p6; nodeInd = i6; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); + /* 2 */ nodePos = p2; nodeInd = i2; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); EndPrimitive(); @@ -172,6 +173,8 @@ R"( ${ GLSL_VERSION }$ in vec3 a_gridCoordToFrag; + flat in ivec3 cellIndToFrag; + in vec3 centerToFrag; uniform mat4 u_modelView; layout(location = 0) out vec4 outputF; @@ -187,6 +190,7 @@ R"( vec3 coordLocalAbs = abs(a_gridCoordToFrag); float maxCoord = max(max(coordLocalAbs.x, coordLocalAbs.y), coordLocalAbs.z); + vec3 cellInd3f = vec3(cellIndToFrag); // compute a normal vector from the coord vec3 shadeNormal = sharpenToAxis(a_gridCoordToFrag, 8.0f); @@ -465,6 +469,29 @@ const ShaderReplacementRule GRIDCUBE_CONSTANT_PICK( const ShaderReplacementRule GRIDCUBE_CULLPOS_FROM_CENTER( /* rule name */ "GRIDCUBE_CULLPOS_FROM_CENTER", + { /* replacement sources */ + + {"FRAG_DECLARATIONS", R"( + uniform vec3 u_gridSpacing; + )"}, + {"GLOBAL_FRAGMENT_FILTER_PREP", R"( + // NOTE: you would expect the constant below to be 0.f, to cull from the center of the cell. + // We intentionally use 0.167 instead and slightly shift it, to avoid common default causes + // where the plane slices right through the center of the cell, and you get random patterns + // of cull/not-cull based on floating point error. + const float cull_shift = 0.167; + vec3 cullPos = (u_modelView * vec4(centerToFrag + cull_shift * u_gridSpacing, 1.f)).xyz; + )"}, + }, + /* uniforms */ { + {"u_gridSpacing", RenderDataType::Vector3Float}, + }, + /* attributes */ {}, + /* textures */ {} +); + +const ShaderReplacementRule GRIDCUBE_PLANE_CULLPOS_FROM_CENTER( + /* rule name */ "GRIDCUBE_PLANE_CULLPOS_FROM_CENTER", { /* replacement sources */ {"FRAG_DECLARATIONS", R"( uniform mat4 u_modelView; diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 6f9e37f2..2ef41c09 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -49,7 +49,7 @@ void SparseVolumeGrid::computeCellPositions() { for (size_t i = 0; i < n; i++) { glm::ivec3 ijk = occupiedCellsData[i]; cellPositionsData[i] = origin + (glm::vec3(ijk) + 0.5f) * gridCellWidth; - cellIndicesData[i] = glm::uvec3(ijk); // cast to unsigned for GPU attribute + cellIndicesData[i] = ijk; } cellPositions.markHostBufferUpdated(); @@ -326,12 +326,16 @@ void SparseVolumeGrid::setCellGeometryAttributes(render::ShaderProgram& p) { std::vector SparseVolumeGrid::addSparseGridShaderRules(std::vector initRules, bool pickOnly) { if (!pickOnly) { - if (getEdgeWidth() > 0) { initRules.push_back("GRIDCUBE_WIREFRAME"); initRules.push_back("MESH_WIREFRAME"); } } + + if (wantsCullPosition()) { + initRules.push_back("GRIDCUBE_CULLPOS_FROM_CENTER"); + } + return addStructureRules(initRules); } diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index 50ba7ce4..15529f70 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -206,7 +206,7 @@ std::vector VolumeGrid::addGridCubeRules(std::vector i } if (wantsCullPosition()) { - initRules.push_back("GRIDCUBE_CULLPOS_FROM_CENTER"); + initRules.push_back("GRIDCUBE_PLANE_CULLPOS_FROM_CENTER"); } return initRules; diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp index f58d7b07..9974e303 100644 --- a/test/src/sparse_volume_grid_test.cpp +++ b/test/src/sparse_volume_grid_test.cpp @@ -2,27 +2,85 @@ #include "polyscope_test.h" +#include +#include // ============================================================ // =============== Sparse volume grid tests // ============================================================ -TEST_F(PolyscopeTest, SparseVolumeGridShow) { +// Helper: create a block of occupied cells from [-N, N)^3, ensuring negative indices are tested. +// Also produces matching node indices/values covering all corners of occupied cells. +struct SparseGridTestData { glm::vec3 origin{-3., -3., -3.}; glm::vec3 cellWidth{0.5, 0.5, 0.5}; - - // Create some occupied cells std::vector occupiedCells; - for (uint32_t i = 0; i < 8; i += 2) { - for (uint32_t j = 0; j < 10; j += 2) { - for (uint32_t k = 0; k < 12; k += 2) { - occupiedCells.push_back({i, j, k}); + std::vector nodeIndices; + + // Per-cell scalar: linear index + std::vector cellScalars; + // Per-cell color + std::vector cellColors; + // Per-node scalar: sum of node coords + std::vector nodeScalars; + // Per-node color + std::vector nodeColors; +}; + +SparseGridTestData buildSparseGridTestData(int N = 3) { + SparseGridTestData d; + + // Cells from [-N, N) + for (int i = -N; i < N; i++) { + for (int j = -N; j < N; j++) { + for (int k = -N; k < N; k++) { + d.occupiedCells.push_back({i, j, k}); } } } + // Per-cell quantities + d.cellScalars.resize(d.occupiedCells.size()); + d.cellColors.resize(d.occupiedCells.size()); + for (size_t i = 0; i < d.occupiedCells.size(); i++) { + d.cellScalars[i] = static_cast(i); + glm::ivec3 ci = d.occupiedCells[i]; + d.cellColors[i] = glm::vec3((ci.x + N) / (2.f * N), (ci.y + N) / (2.f * N), (ci.z + N) / (2.f * N)); + } + + // Gather all unique node indices + // Node (ci+dx-1, cj+dy-1, ck+dz-1) for dx,dy,dz in {0,1} + std::set> nodeSet; + for (const auto& ci : d.occupiedCells) { + for (int dx = 0; dx < 2; dx++) { + for (int dy = 0; dy < 2; dy++) { + for (int dz = 0; dz < 2; dz++) { + nodeSet.insert({ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1}); + } + } + } + } + + d.nodeScalars.reserve(nodeSet.size()); + d.nodeColors.reserve(nodeSet.size()); + for (const auto& n : nodeSet) { + int ni, nj, nk; + std::tie(ni, nj, nk) = n; + d.nodeIndices.push_back({ni, nj, nk}); + d.nodeScalars.push_back(static_cast(ni + nj + nk)); + d.nodeColors.push_back( + glm::vec3((ni + N) / (2.f * N + 1.f), (nj + N) / (2.f * N + 1.f), (nk + N) / (2.f * N + 1.f))); + } + + return d; +} + + +TEST_F(PolyscopeTest, SparseVolumeGridShow) { + auto d = buildSparseGridTestData(); + polyscope::SparseVolumeGrid* psGrid = - polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); polyscope::show(3); @@ -33,21 +91,10 @@ TEST_F(PolyscopeTest, SparseVolumeGridShow) { } TEST_F(PolyscopeTest, SparseVolumeGridEdges) { - glm::vec3 origin{-3., -3., -3.}; - glm::vec3 cellWidth{0.5, 0.5, 0.5}; - - // Create some occupied cells - std::vector occupiedCells; - for (uint32_t i = 0; i < 8; i += 2) { - for (uint32_t j = 0; j < 10; j += 2) { - for (uint32_t k = 0; k < 12; k += 2) { - occupiedCells.push_back({i, j, k}); - } - } - } + auto d = buildSparseGridTestData(); polyscope::SparseVolumeGrid* psGrid = - polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); psGrid->setEdgeWidth(1.f); psGrid->setEdgeColor({1.f, 0.f, 0.f}); @@ -57,28 +104,27 @@ TEST_F(PolyscopeTest, SparseVolumeGridEdges) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, SparseVolumeGridSlicePlane) { + auto d = buildSparseGridTestData(); -TEST_F(PolyscopeTest, SparseVolumeGridCellScalar) { - glm::vec3 origin{-3., -3., -3.}; - glm::vec3 cellWidth{0.5, 0.5, 0.5}; + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - std::vector occupiedCells; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - for (int k = 0; k < 4; k++) { - occupiedCells.push_back({i, j, k}); - } - } - } + polyscope::addSlicePlane(); + + polyscope::show(3); + + polyscope::removeAllSlicePlanes(); + polyscope::removeAllStructures(); +} + +TEST_F(PolyscopeTest, SparseVolumeGridCellScalar) { + auto d = buildSparseGridTestData(); polyscope::SparseVolumeGrid* psGrid = - polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - std::vector scalarVals(occupiedCells.size()); - for (size_t i = 0; i < scalarVals.size(); i++) { - scalarVals[i] = static_cast(i); - } - psGrid->addCellScalarQuantity("cell scalar", scalarVals); + psGrid->addCellScalarQuantity("cell scalar", d.cellScalars); polyscope::show(3); @@ -87,34 +133,12 @@ TEST_F(PolyscopeTest, SparseVolumeGridCellScalar) { TEST_F(PolyscopeTest, SparseVolumeGridNodeScalar) { - glm::vec3 origin{-3., -3., -3.}; - glm::vec3 cellWidth{0.5, 0.5, 0.5}; - - std::vector occupiedCells; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - for (int k = 0; k < 4; k++) { - occupiedCells.push_back({i, j, k}); - } - } - } + auto d = buildSparseGridTestData(); polyscope::SparseVolumeGrid* psGrid = - polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - // Node indices: corners of each cell are at (ci+dx-1, cj+dy-1, ck+dz-1) for dx,dy,dz in {0,1} - // For cells (0..3)^3, nodes range from -1..3 - std::vector nodeIndices; - std::vector nodeValues; - for (int i = -1; i <= 3; i++) { - for (int j = -1; j <= 3; j++) { - for (int k = -1; k <= 3; k++) { - nodeIndices.push_back({i, j, k}); - nodeValues.push_back(static_cast(i + j + k)); - } - } - } - psGrid->addNodeScalarQuantity("node scalar", nodeIndices, nodeValues); + psGrid->addNodeScalarQuantity("node scalar", d.nodeIndices, d.nodeScalars); polyscope::show(3); @@ -123,26 +147,12 @@ TEST_F(PolyscopeTest, SparseVolumeGridNodeScalar) { TEST_F(PolyscopeTest, SparseVolumeGridCellColor) { - glm::vec3 origin{-3., -3., -3.}; - glm::vec3 cellWidth{0.5, 0.5, 0.5}; - - std::vector occupiedCells; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - for (int k = 0; k < 4; k++) { - occupiedCells.push_back({i, j, k}); - } - } - } + auto d = buildSparseGridTestData(); polyscope::SparseVolumeGrid* psGrid = - polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - std::vector colorVals(occupiedCells.size()); - for (size_t i = 0; i < colorVals.size(); i++) { - colorVals[i] = glm::vec3(static_cast(i) / colorVals.size(), 0.5f, 0.3f); - } - psGrid->addCellColorQuantity("cell color", colorVals); + psGrid->addCellColorQuantity("cell color", d.cellColors); polyscope::show(3); @@ -151,33 +161,12 @@ TEST_F(PolyscopeTest, SparseVolumeGridCellColor) { TEST_F(PolyscopeTest, SparseVolumeGridNodeColor) { - glm::vec3 origin{-3., -3., -3.}; - glm::vec3 cellWidth{0.5, 0.5, 0.5}; - - std::vector occupiedCells; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - for (int k = 0; k < 4; k++) { - occupiedCells.push_back({i, j, k}); - } - } - } + auto d = buildSparseGridTestData(); polyscope::SparseVolumeGrid* psGrid = - polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - // Node indices: corners of each cell are at (ci+dx-1, cj+dy-1, ck+dz-1) for dx,dy,dz in {0,1} - std::vector nodeIndices; - std::vector nodeColors; - for (int i = -1; i <= 3; i++) { - for (int j = -1; j <= 3; j++) { - for (int k = -1; k <= 3; k++) { - nodeIndices.push_back({i, j, k}); - nodeColors.push_back(glm::vec3((i + 1) / 4.f, (j + 1) / 4.f, (k + 1) / 4.f)); - } - } - } - psGrid->addNodeColorQuantity("node color", nodeIndices, nodeColors); + psGrid->addNodeColorQuantity("node color", d.nodeIndices, d.nodeColors); polyscope::show(3); @@ -186,22 +175,12 @@ TEST_F(PolyscopeTest, SparseVolumeGridNodeColor) { TEST_F(PolyscopeTest, SparseVolumeGridBasicOptions) { - glm::vec3 origin{-3., -3., -3.}; - glm::vec3 cellWidth{0.5, 0.5, 0.5}; - - std::vector occupiedCells; - for (uint32_t i = 0; i < 8; i += 2) { - for (uint32_t j = 0; j < 10; j += 2) { - for (uint32_t k = 0; k < 12; k += 2) { - occupiedCells.push_back({i, j, k}); - } - } - } + auto d = buildSparseGridTestData(); polyscope::SparseVolumeGrid* psGrid = - polyscope::registerSparseVolumeGrid("test sparse grid", origin, cellWidth, occupiedCells); + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - EXPECT_EQ(psGrid->nCells(), occupiedCells.size()); + EXPECT_EQ(psGrid->nCells(), d.occupiedCells.size()); // Material psGrid->setMaterial("flat"); From 0e1965deb8e32fa4ee0f5d1a021cdea2c3e78171 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Feb 2026 17:29:29 -1000 Subject: [PATCH 09/18] fix tests and shader bug --- src/render/opengl/shaders/grid_shaders.cpp | 60 +++++++++++----------- test/src/sparse_volume_grid_test.cpp | 14 +++-- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/render/opengl/shaders/grid_shaders.cpp b/src/render/opengl/shaders/grid_shaders.cpp index 977b6e37..87966cdb 100644 --- a/src/render/opengl/shaders/grid_shaders.cpp +++ b/src/render/opengl/shaders/grid_shaders.cpp @@ -113,38 +113,38 @@ R"( vec4 p7 = T * vec4(center + c7 * dvec, 1.f); // node corner indices - ivec3 iCenter = a_cellIndToGeom[0]; - ivec3 i0 = iCenter + ivec3(0, 0, 0); - ivec3 i1 = iCenter + ivec3(0, 0, 1); - ivec3 i2 = iCenter + ivec3(0, 1, 0); - ivec3 i3 = iCenter + ivec3(0, 1, 1); - ivec3 i4 = iCenter + ivec3(1, 0, 0); - ivec3 i5 = iCenter + ivec3(1, 0, 1); - ivec3 i6 = iCenter + ivec3(1, 1, 0); - ivec3 i7 = iCenter + ivec3(1, 1, 1); + ivec3 cellInd = a_cellIndToGeom[0]; + ivec3 i0 = cellInd + ivec3(0, 0, 0); + ivec3 i1 = cellInd + ivec3(0, 0, 1); + ivec3 i2 = cellInd + ivec3(0, 1, 0); + ivec3 i3 = cellInd + ivec3(0, 1, 1); + ivec3 i4 = cellInd + ivec3(1, 0, 0); + ivec3 i5 = cellInd + ivec3(1, 0, 1); + ivec3 i6 = cellInd + ivec3(1, 1, 0); + ivec3 i7 = cellInd + ivec3(1, 1, 1); ${ GEOM_COMPUTE_BEFORE_EMIT }$ vec4 nodePos; ivec3 nodeInd; - // this is the order to emit veritces to get a cube triangle strip + // this is the order to emit vertices to get a cube triangle strip // 3, 7, 1, 5, 4, 7, 6, 3, 2, 1, 0, 4, 2, 6, - /* 7 */ nodePos = p7; nodeInd = i7; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); - /* 3 */ nodePos = p3; nodeInd = i3; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); - /* 5 */ nodePos = p5; nodeInd = i5; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); - /* 1 */ nodePos = p1; nodeInd = i1; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c1; EmitVertex(); - /* 0 */ nodePos = p0; nodeInd = i0; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); - /* 3 */ nodePos = p3; nodeInd = i3; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); - /* 2 */ nodePos = p2; nodeInd = i2; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); - /* 7 */ nodePos = p7; nodeInd = i7; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); - /* 6 */ nodePos = p6; nodeInd = i6; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); - /* 5 */ nodePos = p5; nodeInd = i5; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); - /* 4 */ nodePos = p4; nodeInd = i4; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c4; EmitVertex(); - /* 0 */ nodePos = p0; nodeInd = i0; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); - /* 6 */ nodePos = p6; nodeInd = i6; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); - /* 2 */ nodePos = p2; nodeInd = i2; centerToFrag = center; cellIndToFrag = iCenter; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); + /* 7 */ nodePos = p7; nodeInd = i7; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); + /* 3 */ nodePos = p3; nodeInd = i3; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); + /* 5 */ nodePos = p5; nodeInd = i5; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); + /* 1 */ nodePos = p1; nodeInd = i1; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c1; EmitVertex(); + /* 0 */ nodePos = p0; nodeInd = i0; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); + /* 3 */ nodePos = p3; nodeInd = i3; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c3; EmitVertex(); + /* 2 */ nodePos = p2; nodeInd = i2; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); + /* 7 */ nodePos = p7; nodeInd = i7; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c7; EmitVertex(); + /* 6 */ nodePos = p6; nodeInd = i6; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); + /* 5 */ nodePos = p5; nodeInd = i5; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c5; EmitVertex(); + /* 4 */ nodePos = p4; nodeInd = i4; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c4; EmitVertex(); + /* 0 */ nodePos = p0; nodeInd = i0; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c0; EmitVertex(); + /* 6 */ nodePos = p6; nodeInd = i6; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c6; EmitVertex(); + /* 2 */ nodePos = p2; nodeInd = i2; centerToFrag = center; cellIndToFrag = cellInd; ${ GEOM_PER_EMIT }$ gl_Position = nodePos; a_gridCoordToFrag = c2; EmitVertex(); EndPrimitive(); @@ -608,8 +608,8 @@ const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR ( )"}, {"GEOM_PER_EMIT", R"( { - uint cornerIdx = (nodeInd.x - cellInd.x) * 4u + (nodeInd.y - cellInd.y) * 2u + (nodeInd.z - cellInd.z); - a_valueToFrag = (cornerIdx < 4u) ? a_nodeValues04ToGeom[0][cornerIdx] : a_nodeValues47ToGeom[0][cornerIdx - 4u]; + int cornerIdx = (nodeInd.x - cellInd.x) * 4 + (nodeInd.y - cellInd.y) * 2 + (nodeInd.z - cellInd.z); + a_valueToFrag = (cornerIdx < 4) ? a_nodeValues04ToGeom[0][cornerIdx] : a_nodeValues47ToGeom[0][cornerIdx - 4]; } )"}, {"FRAG_DECLARATIONS", R"( @@ -663,10 +663,10 @@ const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR ( )"}, {"GEOM_PER_EMIT", R"( { - uint cornerIdx = (nodeInd.x - cellInd.x) * 4u + (nodeInd.y - cellInd.y) * 2u + (nodeInd.z - cellInd.z); - float r = (cornerIdx < 4u) ? a_nodeR04ToGeom[0][cornerIdx] : a_nodeR47ToGeom[0][cornerIdx - 4u]; - float g = (cornerIdx < 4u) ? a_nodeG04ToGeom[0][cornerIdx] : a_nodeG47ToGeom[0][cornerIdx - 4u]; - float b = (cornerIdx < 4u) ? a_nodeB04ToGeom[0][cornerIdx] : a_nodeB47ToGeom[0][cornerIdx - 4u]; + int cornerIdx = (nodeInd.x - cellInd.x) * 4 + (nodeInd.y - cellInd.y) * 2 + (nodeInd.z - cellInd.z); + float r = (cornerIdx < 4) ? a_nodeR04ToGeom[0][cornerIdx] : a_nodeR47ToGeom[0][cornerIdx - 4]; + float g = (cornerIdx < 4) ? a_nodeG04ToGeom[0][cornerIdx] : a_nodeG47ToGeom[0][cornerIdx - 4]; + float b = (cornerIdx < 4) ? a_nodeB04ToGeom[0][cornerIdx] : a_nodeB47ToGeom[0][cornerIdx - 4]; a_colorToFrag = vec3(r, g, b); } )"}, diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp index 9974e303..84cdb19c 100644 --- a/test/src/sparse_volume_grid_test.cpp +++ b/test/src/sparse_volume_grid_test.cpp @@ -124,7 +124,8 @@ TEST_F(PolyscopeTest, SparseVolumeGridCellScalar) { polyscope::SparseVolumeGrid* psGrid = polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - psGrid->addCellScalarQuantity("cell scalar", d.cellScalars); + polyscope::SparseVolumeGridScalarQuantity* q = psGrid->addCellScalarQuantity("cell scalar", d.cellScalars); + q->setEnabled(true); polyscope::show(3); @@ -138,7 +139,10 @@ TEST_F(PolyscopeTest, SparseVolumeGridNodeScalar) { polyscope::SparseVolumeGrid* psGrid = polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - psGrid->addNodeScalarQuantity("node scalar", d.nodeIndices, d.nodeScalars); + polyscope::SparseVolumeGridScalarQuantity* q = + psGrid->addNodeScalarQuantity("node scalar", d.nodeIndices, d.nodeScalars); + q->setEnabled(true); + polyscope::show(3); @@ -152,7 +156,8 @@ TEST_F(PolyscopeTest, SparseVolumeGridCellColor) { polyscope::SparseVolumeGrid* psGrid = polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - psGrid->addCellColorQuantity("cell color", d.cellColors); + polyscope::SparseVolumeGridColorQuantity* q = psGrid->addCellColorQuantity("cell color", d.cellColors); + q->setEnabled(true); polyscope::show(3); @@ -166,7 +171,8 @@ TEST_F(PolyscopeTest, SparseVolumeGridNodeColor) { polyscope::SparseVolumeGrid* psGrid = polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - psGrid->addNodeColorQuantity("node color", d.nodeIndices, d.nodeColors); + polyscope::SparseVolumeGridColorQuantity* q = psGrid->addNodeColorQuantity("node color", d.nodeIndices, d.nodeColors); + q->setEnabled(true); polyscope::show(3); From e823804f6133b521a5106091a3034debebfb7935 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 13 Feb 2026 23:00:55 -1000 Subject: [PATCH 10/18] fix up node value indexing --- include/polyscope/sparse_volume_grid.h | 42 +++++- include/polyscope/sparse_volume_grid.ipp | 80 ++++++++++- .../sparse_volume_grid_color_quantity.h | 9 +- .../sparse_volume_grid_scalar_quantity.h | 9 +- src/render/opengl/shaders/grid_shaders.cpp | 135 ++++++++++++------ src/sparse_volume_grid.cpp | 85 +++++++++++ src/sparse_volume_grid_color_quantity.cpp | 107 +++----------- src/sparse_volume_grid_scalar_quantity.cpp | 103 +++---------- test/src/sparse_volume_grid_test.cpp | 86 +++++++++++ 9 files changed, 415 insertions(+), 241 deletions(-) diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h index fd324ec9..b9218d48 100644 --- a/include/polyscope/sparse_volume_grid.h +++ b/include/polyscope/sparse_volume_grid.h @@ -55,23 +55,48 @@ class SparseVolumeGrid : public Structure { glm::vec3 getGridCellWidth() const; const std::vector& getOccupiedCells() const; + // === Node-valued data helpers + // All lazily computed on first access. + + // Corner-to-node index buffers (one per corner, indexed by cell) + // Corner c = dx*4 + dy*2 + dz maps cell[i] -> index in canonical node order + render::ManagedBuffer cornerNodeInds[8]; + + uint64_t nNodes(); + const std::vector& getCanonicalNodeInds(); + void ensureHaveCornerNodeIndices(); + + // Helper for quantities. Given node-valued data with any order, re-orders and takes subsets to exactly match the node + // data layout. The input may contain extra/repeated entries, and be in any order, so long as all required node values + // are there. If entries are missing, an error is thrown. If the data was already in canonical order which is the + // output, the out param nodeIndicesAreCanonical is set to true, and no reordering is done. + template + std::vector canonicalizeNodeValueArray(const std::string& quantityName, const std::vector& nodeIndices, + const std::vector& nodeValues, bool& nodeIndicesAreCanonical); + // === Quantities - // Cell scalar + // Cell scalar. Values array must be passed in the same order as initial input cell list. template SparseVolumeGridScalarQuantity* addCellScalarQuantity(std::string name, const T& values, DataType type = DataType::STANDARD); - // Node scalar + // Node scalar. Indices are _node_ indices; the nodes are a shifted sparse grid offset from the cell enumeration. For + // a cell with indices ijk, its corrners are the nodes with indices (i k j, i+1 j k, ..., i+1 j+1, k+1). Node values + // are passed via a paired set of arrays, giving the node index and node value for each. Node values may be passed in + // any order, and having extra entries is fine too, as long as all required nodes values are present. template SparseVolumeGridScalarQuantity* addNodeScalarQuantity(std::string name, const TI& nodeIndices, const TV& nodeValues, DataType type = DataType::STANDARD); - // Cell color + // Cell color. Values array must be passed in the same order as initial input cell list. template SparseVolumeGridColorQuantity* addCellColorQuantity(std::string name, const T& colors); - // Node color + // Node color. Indices are _node_ indices; the nodes are a shifted sparse grid offset from the cell enumeration. For + // a cell with indices ijk, its corrners are the nodes with indices (i k j, i+1 j k, ..., i+1 j+1, k+1). Node values + // are passed via a paired set of arrays, giving the node index and node value for each. Node values may be passed in + // any order, and having extra entries is fine too, as long as all required nodes values are present. template SparseVolumeGridColorQuantity* addNodeColorQuantity(std::string name, const TI& nodeIndices, const TC& nodeColors); @@ -115,6 +140,11 @@ class SparseVolumeGrid : public Structure { // User-facing occupied cell indices (signed) std::vector occupiedCellsData; + // Canonical sorted node indices and corner index buffers (lazily computed) + bool haveCornerNodeIndices = false; + std::vector canonicalNodeIndsData; + std::vector cornerNodeIndsData[8]; + // === Visualization parameters PersistentValue color; PersistentValue edgeWidth; @@ -125,6 +155,9 @@ class SparseVolumeGrid : public Structure { // Compute cell positions and GPU indices from occupiedCellsData void computeCellPositions(); + // Compute canonical node indices and corner index buffers from occupiedCellsData + void computeCornerNodeIndices(); + // Picking-related size_t globalPickConstant = INVALID_IND_64; glm::vec3 pickColor; @@ -134,6 +167,7 @@ class SparseVolumeGrid : public Structure { std::shared_ptr pickProgram; // === Helpers + void checkForDuplicateCells(); void ensureRenderProgramPrepared(); void ensurePickProgramPrepared(); diff --git a/include/polyscope/sparse_volume_grid.ipp b/include/polyscope/sparse_volume_grid.ipp index dfc3dfd9..522cb872 100644 --- a/include/polyscope/sparse_volume_grid.ipp +++ b/include/polyscope/sparse_volume_grid.ipp @@ -7,10 +7,18 @@ namespace polyscope { inline uint64_t SparseVolumeGrid::nCells() const { return occupiedCellsData.size(); } +inline uint64_t SparseVolumeGrid::nNodes() { + ensureHaveCornerNodeIndices(); + return canonicalNodeIndsData.size(); +} inline glm::vec3 SparseVolumeGrid::getOrigin() const { return origin; } inline glm::vec3 SparseVolumeGrid::getGridCellWidth() const { return gridCellWidth; } inline const std::vector& SparseVolumeGrid::getOccupiedCells() const { return occupiedCellsData; } +inline const std::vector& SparseVolumeGrid::getCanonicalNodeInds() { + ensureHaveCornerNodeIndices(); + return canonicalNodeIndsData; +} // Template registration template @@ -18,8 +26,8 @@ SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, g const T& occupiedCells) { checkInitialized(); - SparseVolumeGrid* s = new SparseVolumeGrid(name, origin, gridCellWidth, - standardizeVectorArray(occupiedCells)); + SparseVolumeGrid* s = + new SparseVolumeGrid(name, origin, gridCellWidth, standardizeVectorArray(occupiedCells)); bool success = registerStructure(s); if (!success) { @@ -33,13 +41,75 @@ SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, g inline SparseVolumeGrid* getSparseVolumeGrid(std::string name) { return dynamic_cast(getStructure(SparseVolumeGrid::structureTypeName, name)); } -inline bool hasSparseVolumeGrid(std::string name) { - return hasStructure(SparseVolumeGrid::structureTypeName, name); -} +inline bool hasSparseVolumeGrid(std::string name) { return hasStructure(SparseVolumeGrid::structureTypeName, name); } inline void removeSparseVolumeGrid(std::string name, bool errorIfAbsent) { removeStructure(SparseVolumeGrid::structureTypeName, name, errorIfAbsent); } +// ===================================================== +// ============== Helpers +// ===================================================== + +template +std::vector SparseVolumeGrid::canonicalizeNodeValueArray(const std::string& quantityName, + const std::vector& nodeIndices, + const std::vector& nodeValues, + bool& nodeIndicesAreCanonical) { + ensureHaveCornerNodeIndices(); + const std::vector& canonical = canonicalNodeIndsData; + nodeIndicesAreCanonical = true; // overwrite to false below if we find any issues + + // fast path: if it is already canonical, just copy the input array + if (nodeIndices.size() == canonical.size()) { + bool alreadyCanonical = true; + for (size_t i = 0; i < canonical.size(); i++) { + if (nodeIndices[i] != canonical[i]) { + alreadyCanonical = false; + break; + } + } + if (alreadyCanonical) { + nodeIndicesAreCanonical = true; + return nodeValues; // already in canonical order, so just return it + } + } + nodeIndicesAreCanonical = false; + + + // Build sort permutation of user-provided indices into canonical (lexicographic) order + auto ivec3Less = [](const glm::ivec3& a, const glm::ivec3& b) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + return a.z < b.z; + }; + std::vector order(nodeIndices.size()); + std::iota(order.begin(), order.end(), 0); + std::sort(order.begin(), order.end(), [&](size_t a, size_t b) { return ivec3Less(nodeIndices[a], nodeIndices[b]); }); + + // Merge-walk sorted user indices against canonical indices + std::vector canonicalOutput(canonical.size()); + size_t ui = 0; + for (size_t ci = 0; ci < canonical.size(); ci++) { + + // Any user entries less than canonical[ci] are extras + while (ui < order.size() && ivec3Less(nodeIndices[order[ui]], canonical[ci])) { + ui++; + } + + // Must match canonical[ci] + if (ui >= order.size() || nodeIndices[order[ui]] != canonical[ci]) { + exception(quantityName + ": missing node value at node index (" + std::to_string(canonical[ci].x) + "," + + std::to_string(canonical[ci].y) + "," + std::to_string(canonical[ci].z) + ")"); + } + + canonicalOutput[ci] = nodeValues[order[ui]]; + ui++; + } + + return canonicalOutput; +} + + // ===================================================== // ============== Quantities // ===================================================== diff --git a/include/polyscope/sparse_volume_grid_color_quantity.h b/include/polyscope/sparse_volume_grid_color_quantity.h index ac9af034..10bdfaa9 100644 --- a/include/polyscope/sparse_volume_grid_color_quantity.h +++ b/include/polyscope/sparse_volume_grid_color_quantity.h @@ -25,16 +25,13 @@ class SparseVolumeGridColorQuantity : public SparseVolumeGridQuantity, virtual std::string niceName() override; + bool getNodeIndicesAreCanonical() const { return nodeIndicesAreCanonical; } + private: bool isNodeQuantity = false; + bool nodeIndicesAreCanonical; // true if user-provided indices matched canonical order exactly (set by constructor) void createProgram(); std::shared_ptr program; - - // Node-mode packed data (8 corner colors, separated by R/G/B channel, 2 vec4 each) - std::vector nodeR04Data, nodeR47Data, nodeG04Data, nodeG47Data, nodeB04Data, nodeB47Data; - std::unique_ptr> nodeR04, nodeR47, nodeG04, nodeG47, nodeB04, nodeB47; - - void packNodeColors(const std::vector& nodeIndices, const std::vector& nodeColors); }; diff --git a/include/polyscope/sparse_volume_grid_scalar_quantity.h b/include/polyscope/sparse_volume_grid_scalar_quantity.h index 1debde31..f7aadd3f 100644 --- a/include/polyscope/sparse_volume_grid_scalar_quantity.h +++ b/include/polyscope/sparse_volume_grid_scalar_quantity.h @@ -29,16 +29,13 @@ class SparseVolumeGridScalarQuantity : public SparseVolumeGridQuantity, virtual std::string niceName() override; + bool getNodeIndicesAreCanonical() const { return nodeIndicesAreCanonical; } + private: bool isNodeQuantity = false; + bool nodeIndicesAreCanonical; // true if user-provided indices matched canonical order exactly (set by constructor) void createProgram(); std::shared_ptr program; - - // Node-mode packed data (8 corner values per cell, packed into 2 vec4) - std::vector nodeValues04Data, nodeValues47Data; - std::unique_ptr> nodeValues04, nodeValues47; - - void packNodeValues(const std::vector& nodeIndices, const std::vector& nodeValues); }; diff --git a/src/render/opengl/shaders/grid_shaders.cpp b/src/render/opengl/shaders/grid_shaders.cpp index 87966cdb..0cc479ad 100644 --- a/src/render/opengl/shaders/grid_shaders.cpp +++ b/src/render/opengl/shaders/grid_shaders.cpp @@ -592,24 +592,52 @@ const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR ( /* rule name */ "GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR", { /* replacement sources */ {"VERT_DECLARATIONS", R"( - in vec4 a_nodeValues04; - in vec4 a_nodeValues47; - out vec4 a_nodeValues04ToGeom; - out vec4 a_nodeValues47ToGeom; + in float a_nodeValue0; + in float a_nodeValue1; + in float a_nodeValue2; + in float a_nodeValue3; + in float a_nodeValue4; + in float a_nodeValue5; + in float a_nodeValue6; + in float a_nodeValue7; + out float a_nodeValue0ToGeom; + out float a_nodeValue1ToGeom; + out float a_nodeValue2ToGeom; + out float a_nodeValue3ToGeom; + out float a_nodeValue4ToGeom; + out float a_nodeValue5ToGeom; + out float a_nodeValue6ToGeom; + out float a_nodeValue7ToGeom; )"}, {"VERT_ASSIGNMENTS", R"( - a_nodeValues04ToGeom = a_nodeValues04; - a_nodeValues47ToGeom = a_nodeValues47; + a_nodeValue0ToGeom = a_nodeValue0; + a_nodeValue1ToGeom = a_nodeValue1; + a_nodeValue2ToGeom = a_nodeValue2; + a_nodeValue3ToGeom = a_nodeValue3; + a_nodeValue4ToGeom = a_nodeValue4; + a_nodeValue5ToGeom = a_nodeValue5; + a_nodeValue6ToGeom = a_nodeValue6; + a_nodeValue7ToGeom = a_nodeValue7; )"}, {"GEOM_DECLARATIONS", R"( - in vec4 a_nodeValues04ToGeom[]; - in vec4 a_nodeValues47ToGeom[]; + in float a_nodeValue0ToGeom[]; + in float a_nodeValue1ToGeom[]; + in float a_nodeValue2ToGeom[]; + in float a_nodeValue3ToGeom[]; + in float a_nodeValue4ToGeom[]; + in float a_nodeValue5ToGeom[]; + in float a_nodeValue6ToGeom[]; + in float a_nodeValue7ToGeom[]; out float a_valueToFrag; )"}, {"GEOM_PER_EMIT", R"( { int cornerIdx = (nodeInd.x - cellInd.x) * 4 + (nodeInd.y - cellInd.y) * 2 + (nodeInd.z - cellInd.z); - a_valueToFrag = (cornerIdx < 4) ? a_nodeValues04ToGeom[0][cornerIdx] : a_nodeValues47ToGeom[0][cornerIdx - 4]; + float vals[8] = float[8]( + a_nodeValue0ToGeom[0], a_nodeValue1ToGeom[0], a_nodeValue2ToGeom[0], a_nodeValue3ToGeom[0], + a_nodeValue4ToGeom[0], a_nodeValue5ToGeom[0], a_nodeValue6ToGeom[0], a_nodeValue7ToGeom[0] + ); + a_valueToFrag = vals[cornerIdx]; } )"}, {"FRAG_DECLARATIONS", R"( @@ -621,8 +649,14 @@ const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR ( }, /* uniforms */ {}, /* attributes */ { - {"a_nodeValues04", RenderDataType::Vector4Float}, - {"a_nodeValues47", RenderDataType::Vector4Float}, + {"a_nodeValue0", RenderDataType::Float}, + {"a_nodeValue1", RenderDataType::Float}, + {"a_nodeValue2", RenderDataType::Float}, + {"a_nodeValue3", RenderDataType::Float}, + {"a_nodeValue4", RenderDataType::Float}, + {"a_nodeValue5", RenderDataType::Float}, + {"a_nodeValue6", RenderDataType::Float}, + {"a_nodeValue7", RenderDataType::Float}, }, /* textures */ {} ); @@ -631,43 +665,52 @@ const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR ( /* rule name */ "GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR", { /* replacement sources */ {"VERT_DECLARATIONS", R"( - in vec4 a_nodeR04; - in vec4 a_nodeR47; - in vec4 a_nodeG04; - in vec4 a_nodeG47; - in vec4 a_nodeB04; - in vec4 a_nodeB47; - out vec4 a_nodeR04ToGeom; - out vec4 a_nodeR47ToGeom; - out vec4 a_nodeG04ToGeom; - out vec4 a_nodeG47ToGeom; - out vec4 a_nodeB04ToGeom; - out vec4 a_nodeB47ToGeom; + in vec3 a_nodeColor0; + in vec3 a_nodeColor1; + in vec3 a_nodeColor2; + in vec3 a_nodeColor3; + in vec3 a_nodeColor4; + in vec3 a_nodeColor5; + in vec3 a_nodeColor6; + in vec3 a_nodeColor7; + out vec3 a_nodeColor0ToGeom; + out vec3 a_nodeColor1ToGeom; + out vec3 a_nodeColor2ToGeom; + out vec3 a_nodeColor3ToGeom; + out vec3 a_nodeColor4ToGeom; + out vec3 a_nodeColor5ToGeom; + out vec3 a_nodeColor6ToGeom; + out vec3 a_nodeColor7ToGeom; )"}, {"VERT_ASSIGNMENTS", R"( - a_nodeR04ToGeom = a_nodeR04; - a_nodeR47ToGeom = a_nodeR47; - a_nodeG04ToGeom = a_nodeG04; - a_nodeG47ToGeom = a_nodeG47; - a_nodeB04ToGeom = a_nodeB04; - a_nodeB47ToGeom = a_nodeB47; + a_nodeColor0ToGeom = a_nodeColor0; + a_nodeColor1ToGeom = a_nodeColor1; + a_nodeColor2ToGeom = a_nodeColor2; + a_nodeColor3ToGeom = a_nodeColor3; + a_nodeColor4ToGeom = a_nodeColor4; + a_nodeColor5ToGeom = a_nodeColor5; + a_nodeColor6ToGeom = a_nodeColor6; + a_nodeColor7ToGeom = a_nodeColor7; )"}, {"GEOM_DECLARATIONS", R"( - in vec4 a_nodeR04ToGeom[]; - in vec4 a_nodeR47ToGeom[]; - in vec4 a_nodeG04ToGeom[]; - in vec4 a_nodeG47ToGeom[]; - in vec4 a_nodeB04ToGeom[]; - in vec4 a_nodeB47ToGeom[]; + in vec3 a_nodeColor0ToGeom[]; + in vec3 a_nodeColor1ToGeom[]; + in vec3 a_nodeColor2ToGeom[]; + in vec3 a_nodeColor3ToGeom[]; + in vec3 a_nodeColor4ToGeom[]; + in vec3 a_nodeColor5ToGeom[]; + in vec3 a_nodeColor6ToGeom[]; + in vec3 a_nodeColor7ToGeom[]; out vec3 a_colorToFrag; )"}, {"GEOM_PER_EMIT", R"( { int cornerIdx = (nodeInd.x - cellInd.x) * 4 + (nodeInd.y - cellInd.y) * 2 + (nodeInd.z - cellInd.z); - float r = (cornerIdx < 4) ? a_nodeR04ToGeom[0][cornerIdx] : a_nodeR47ToGeom[0][cornerIdx - 4]; - float g = (cornerIdx < 4) ? a_nodeG04ToGeom[0][cornerIdx] : a_nodeG47ToGeom[0][cornerIdx - 4]; - float b = (cornerIdx < 4) ? a_nodeB04ToGeom[0][cornerIdx] : a_nodeB47ToGeom[0][cornerIdx - 4]; - a_colorToFrag = vec3(r, g, b); + vec3 cols[8] = vec3[8]( + a_nodeColor0ToGeom[0], a_nodeColor1ToGeom[0], a_nodeColor2ToGeom[0], a_nodeColor3ToGeom[0], + a_nodeColor4ToGeom[0], a_nodeColor5ToGeom[0], a_nodeColor6ToGeom[0], a_nodeColor7ToGeom[0] + ); + a_colorToFrag = cols[cornerIdx]; } )"}, {"FRAG_DECLARATIONS", R"( @@ -679,12 +722,14 @@ const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR ( }, /* uniforms */ {}, /* attributes */ { - {"a_nodeR04", RenderDataType::Vector4Float}, - {"a_nodeR47", RenderDataType::Vector4Float}, - {"a_nodeG04", RenderDataType::Vector4Float}, - {"a_nodeG47", RenderDataType::Vector4Float}, - {"a_nodeB04", RenderDataType::Vector4Float}, - {"a_nodeB47", RenderDataType::Vector4Float}, + {"a_nodeColor0", RenderDataType::Vector3Float}, + {"a_nodeColor1", RenderDataType::Vector3Float}, + {"a_nodeColor2", RenderDataType::Vector3Float}, + {"a_nodeColor3", RenderDataType::Vector3Float}, + {"a_nodeColor4", RenderDataType::Vector3Float}, + {"a_nodeColor5", RenderDataType::Vector3Float}, + {"a_nodeColor6", RenderDataType::Vector3Float}, + {"a_nodeColor7", RenderDataType::Vector3Float}, }, /* textures */ {} ); diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 2ef41c09..f3f49a54 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -8,6 +8,9 @@ #include "imgui.h" +#include +#include + namespace polyscope { // Initialize statics @@ -20,6 +23,16 @@ SparseVolumeGrid::SparseVolumeGrid(std::string name, glm::vec3 origin_, glm::vec // == managed quantities cellPositions(this, uniquePrefix() + "#cellPositions", cellPositionsData, std::bind(&SparseVolumeGrid::computeCellPositions, this)), cellIndices(this, uniquePrefix() + "#cellIndices", cellIndicesData, [](){/* do nothing, gets handled by computeCellPositions */}), + cornerNodeInds{ + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds0", cornerNodeIndsData[0]), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds1", cornerNodeIndsData[1]), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds2", cornerNodeIndsData[2]), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds3", cornerNodeIndsData[3]), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds4", cornerNodeIndsData[4]), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds5", cornerNodeIndsData[5]), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds6", cornerNodeIndsData[6]), + render::ManagedBuffer(this, uniquePrefix() + "#cornerNodeInds7", cornerNodeIndsData[7]), + }, origin(origin_), gridCellWidth(gridCellWidth_), @@ -33,12 +46,29 @@ SparseVolumeGrid::SparseVolumeGrid(std::string name, glm::vec3 origin_, glm::vec // clang-format on occupiedCellsData = std::move(occupiedCells); + checkForDuplicateCells(); computeCellPositions(); cullWholeElements.setPassive(true); updateObjectSpaceBounds(); } +void SparseVolumeGrid::checkForDuplicateCells() { + + // sort occupied cells and check for duplicates + std::vector sortedCells = occupiedCellsData; + std::sort(sortedCells.begin(), sortedCells.end(), [](const glm::ivec3& a, const glm::ivec3& b) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + return a.z < b.z; + }); + for (size_t i = 1; i < sortedCells.size(); i++) { + if (sortedCells[i] == sortedCells[i - 1]) { + error("[Polyscope] sparse volume grid " + name + " has repeated cell (" + std::to_string(sortedCells[i].x) + "," + + std::to_string(sortedCells[i].y) + "," + std::to_string(sortedCells[i].z) + ")"); + } + } +} void SparseVolumeGrid::computeCellPositions() { size_t n = occupiedCellsData.size(); @@ -57,6 +87,61 @@ void SparseVolumeGrid::computeCellPositions() { } +void SparseVolumeGrid::ensureHaveCornerNodeIndices() { + if (haveCornerNodeIndices) return; + computeCornerNodeIndices(); + haveCornerNodeIndices = true; +} + +void SparseVolumeGrid::computeCornerNodeIndices() { + size_t n = occupiedCellsData.size(); + + auto ivec3Less = [](const glm::ivec3& a, const glm::ivec3& b) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + return a.z < b.z; + }; + + // Collect all node ivec3 from all cells + std::vector allNodes; + allNodes.reserve(n * 8); + for (size_t i = 0; i < n; i++) { + glm::ivec3 ci = occupiedCellsData[i]; + for (int dx = 0; dx < 2; dx++) + for (int dy = 0; dy < 2; dy++) + for (int dz = 0; dz < 2; dz++) allNodes.push_back(glm::ivec3(ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1)); + } + + // Sort and deduplicate to get canonical order + std::sort(allNodes.begin(), allNodes.end(), ivec3Less); + allNodes.erase(std::unique(allNodes.begin(), allNodes.end()), allNodes.end()); + canonicalNodeIndsData = std::move(allNodes); + + // Build corner index buffers using binary search + for (int c = 0; c < 8; c++) { + cornerNodeIndsData[c].resize(n); + } + + for (size_t i = 0; i < n; i++) { + glm::ivec3 ci = occupiedCellsData[i]; + for (int dx = 0; dx < 2; dx++) { + for (int dy = 0; dy < 2; dy++) { + for (int dz = 0; dz < 2; dz++) { + int c = dx * 4 + dy * 2 + dz; + glm::ivec3 nodeIjk(ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1); + auto it = std::lower_bound(canonicalNodeIndsData.begin(), canonicalNodeIndsData.end(), nodeIjk, ivec3Less); + cornerNodeIndsData[c][i] = static_cast(it - canonicalNodeIndsData.begin()); + } + } + } + } + + for (int c = 0; c < 8; c++) { + cornerNodeInds[c].markHostBufferUpdated(); + } +} + + void SparseVolumeGrid::buildCustomUI() { ImGui::Text("%llu cells", static_cast(nCells())); diff --git a/src/sparse_volume_grid_color_quantity.cpp b/src/sparse_volume_grid_color_quantity.cpp index c3a2bd95..cfeb875d 100644 --- a/src/sparse_volume_grid_color_quantity.cpp +++ b/src/sparse_volume_grid_color_quantity.cpp @@ -6,34 +6,9 @@ #include "imgui.h" -#include - namespace polyscope { -// Hash for glm::ivec3 -struct IVec3HashColor { - size_t operator()(const glm::ivec3& v) const { - size_t h = std::hash()(v.x); - h ^= std::hash()(v.y) + 0x9e3779b9 + (h << 6) + (h >> 2); - h ^= std::hash()(v.z) + 0x9e3779b9 + (h << 6) + (h >> 2); - return h; - } -}; - -// === Cell color constructor -SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& colors_) - : SparseVolumeGridQuantity(name, grid, true), ColorQuantity(*this, colors_), isNodeQuantity(false) {} - -// === Node color constructor -SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& nodeIndices, - const std::vector& nodeColors) - : SparseVolumeGridQuantity(name, grid, true), - ColorQuantity(*this, std::vector(grid.nCells(), glm::vec3{0.f, 0.f, 0.f})), isNodeQuantity(true) { - packNodeColors(nodeIndices, nodeColors); -} - +// === Sparse volume grid color quantity void SparseVolumeGridColorQuantity::draw() { if (!isEnabled()) return; @@ -67,12 +42,10 @@ void SparseVolumeGridColorQuantity::createProgram() { parent.setCellGeometryAttributes(*program); if (isNodeQuantity) { - program->setAttribute("a_nodeR04", nodeR04->getRenderAttributeBuffer()); - program->setAttribute("a_nodeR47", nodeR47->getRenderAttributeBuffer()); - program->setAttribute("a_nodeG04", nodeG04->getRenderAttributeBuffer()); - program->setAttribute("a_nodeG47", nodeG47->getRenderAttributeBuffer()); - program->setAttribute("a_nodeB04", nodeB04->getRenderAttributeBuffer()); - program->setAttribute("a_nodeB47", nodeB47->getRenderAttributeBuffer()); + for (int c = 0; c < 8; c++) { + program->setAttribute("a_nodeColor" + std::to_string(c), + colors.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c])); + } } else { program->setAttribute("a_color", colors.getRenderAttributeBuffer()); } @@ -88,67 +61,21 @@ void SparseVolumeGridColorQuantity::refresh() { std::string SparseVolumeGridColorQuantity::niceName() { return name + " (color)"; } +// === Cell color quantity -void SparseVolumeGridColorQuantity::packNodeColors(const std::vector& nodeIndices, - const std::vector& nodeColors) { - // Build lookup map - std::unordered_map nodeMap; - for (size_t i = 0; i < nodeIndices.size(); i++) { - nodeMap[nodeIndices[i]] = nodeColors[i]; - } - - size_t nCells = parent.nCells(); - const std::vector& occupiedCells = parent.getOccupiedCells(); - - nodeR04Data.resize(nCells); - nodeR47Data.resize(nCells); - nodeG04Data.resize(nCells); - nodeG47Data.resize(nCells); - nodeB04Data.resize(nCells); - nodeB47Data.resize(nCells); - - for (size_t i = 0; i < nCells; i++) { - glm::ivec3 ci = occupiedCells[i]; - glm::vec3 cornerColors[8]; - for (int dx = 0; dx < 2; dx++) { - for (int dy = 0; dy < 2; dy++) { - for (int dz = 0; dz < 2; dz++) { - int cornerIdx = dx * 4 + dy * 2 + dz; - glm::ivec3 nodeIjk(ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1); - auto it = nodeMap.find(nodeIjk); - if (it == nodeMap.end()) { - exception("SparseVolumeGridColorQuantity [" + name + "]: missing node color at (" + - std::to_string(nodeIjk.x) + "," + std::to_string(nodeIjk.y) + "," + std::to_string(nodeIjk.z) + - ")"); - } - cornerColors[cornerIdx] = it->second; - } - } - } +SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& colors_) + : SparseVolumeGridQuantity(name, grid, true), ColorQuantity(*this, colors_), isNodeQuantity(false), + nodeIndicesAreCanonical(true) {} - // Pack by channel: R, G, B each get 2 vec4 (corners 0-3, 4-7) - nodeR04Data[i] = glm::vec4(cornerColors[0].r, cornerColors[1].r, cornerColors[2].r, cornerColors[3].r); - nodeR47Data[i] = glm::vec4(cornerColors[4].r, cornerColors[5].r, cornerColors[6].r, cornerColors[7].r); - nodeG04Data[i] = glm::vec4(cornerColors[0].g, cornerColors[1].g, cornerColors[2].g, cornerColors[3].g); - nodeG47Data[i] = glm::vec4(cornerColors[4].g, cornerColors[5].g, cornerColors[6].g, cornerColors[7].g); - nodeB04Data[i] = glm::vec4(cornerColors[0].b, cornerColors[1].b, cornerColors[2].b, cornerColors[3].b); - nodeB47Data[i] = glm::vec4(cornerColors[4].b, cornerColors[5].b, cornerColors[6].b, cornerColors[7].b); - } - // Create managed buffers for the packed data - nodeR04 = std::unique_ptr>( - new render::ManagedBuffer(&parent, name + "#nodeR04", nodeR04Data)); - nodeR47 = std::unique_ptr>( - new render::ManagedBuffer(&parent, name + "#nodeR47", nodeR47Data)); - nodeG04 = std::unique_ptr>( - new render::ManagedBuffer(&parent, name + "#nodeG04", nodeG04Data)); - nodeG47 = std::unique_ptr>( - new render::ManagedBuffer(&parent, name + "#nodeG47", nodeG47Data)); - nodeB04 = std::unique_ptr>( - new render::ManagedBuffer(&parent, name + "#nodeB04", nodeB04Data)); - nodeB47 = std::unique_ptr>( - new render::ManagedBuffer(&parent, name + "#nodeB47", nodeB47Data)); -} +// === Node color quantity +SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& nodeIndices, + const std::vector& nodeColors) + : SparseVolumeGridQuantity(name, grid, true), + ColorQuantity(*this, parent.canonicalizeNodeValueArray(name, nodeIndices, nodeColors, nodeIndicesAreCanonical)), + isNodeQuantity(true) {} } // namespace polyscope diff --git a/src/sparse_volume_grid_scalar_quantity.cpp b/src/sparse_volume_grid_scalar_quantity.cpp index b806b460..a6d212f6 100644 --- a/src/sparse_volume_grid_scalar_quantity.cpp +++ b/src/sparse_volume_grid_scalar_quantity.cpp @@ -6,35 +6,9 @@ #include "imgui.h" -#include - namespace polyscope { -// Hash for glm::ivec3 -struct IVec3Hash { - size_t operator()(const glm::ivec3& v) const { - size_t h = std::hash()(v.x); - h ^= std::hash()(v.y) + 0x9e3779b9 + (h << 6) + (h >> 2); - h ^= std::hash()(v.z) + 0x9e3779b9 + (h << 6) + (h >> 2); - return h; - } -}; - -// === Cell scalar constructor -SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& values_, DataType dataType_) - : SparseVolumeGridQuantity(name, grid, true), ScalarQuantity(*this, values_, dataType_), isNodeQuantity(false) {} - -// === Node scalar constructor -SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& nodeIndices, - const std::vector& nodeValues, - DataType dataType_) - : SparseVolumeGridQuantity(name, grid, true), - ScalarQuantity(*this, std::vector(grid.nCells(), 0.f), dataType_), isNodeQuantity(true) { - packNodeValues(nodeIndices, nodeValues); -} - +// === SparseVolumeGridScalarQuantity void SparseVolumeGridScalarQuantity::draw() { if (!isEnabled()) return; @@ -82,8 +56,10 @@ void SparseVolumeGridScalarQuantity::createProgram() { parent.setCellGeometryAttributes(*program); if (isNodeQuantity) { - program->setAttribute("a_nodeValues04", nodeValues04->getRenderAttributeBuffer()); - program->setAttribute("a_nodeValues47", nodeValues47->getRenderAttributeBuffer()); + for (int c = 0; c < 8; c++) { + program->setAttribute("a_nodeValue" + std::to_string(c), + values.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c])); + } } else { program->setAttribute("a_value", values.getRenderAttributeBuffer()); } @@ -101,66 +77,23 @@ void SparseVolumeGridScalarQuantity::refresh() { std::string SparseVolumeGridScalarQuantity::niceName() { return name + " (scalar)"; } -void SparseVolumeGridScalarQuantity::packNodeValues(const std::vector& nodeIndices, - const std::vector& nodeValues) { - // Build lookup map - std::unordered_map nodeMap; - for (size_t i = 0; i < nodeIndices.size(); i++) { - nodeMap[nodeIndices[i]] = nodeValues[i]; - } +// === Cell scalar quantity - size_t nCells = parent.nCells(); - const std::vector& occupiedCells = parent.getOccupiedCells(); - - nodeValues04Data.resize(nCells); - nodeValues47Data.resize(nCells); - - // Also fill values.data with per-cell averages for data range computation - valuesData.resize(nCells); - - for (size_t i = 0; i < nCells; i++) { - glm::ivec3 ci = occupiedCells[i]; - float vals[8]; - for (int dx = 0; dx < 2; dx++) { - for (int dy = 0; dy < 2; dy++) { - for (int dz = 0; dz < 2; dz++) { - int cornerIdx = dx * 4 + dy * 2 + dz; - glm::ivec3 nodeIjk(ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1); - auto it = nodeMap.find(nodeIjk); - if (it == nodeMap.end()) { - exception("SparseVolumeGridScalarQuantity [" + name + "]: missing node value at (" + - std::to_string(nodeIjk.x) + "," + std::to_string(nodeIjk.y) + "," + - std::to_string(nodeIjk.z) + ")"); - } - vals[cornerIdx] = it->second; - } - } - } - nodeValues04Data[i] = glm::vec4(vals[0], vals[1], vals[2], vals[3]); - nodeValues47Data[i] = glm::vec4(vals[4], vals[5], vals[6], vals[7]); +SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& values_, DataType dataType_) + : SparseVolumeGridQuantity(name, grid, true), ScalarQuantity(*this, values_, dataType_), isNodeQuantity(false), + nodeIndicesAreCanonical(true) {} - // Compute average for data range - float avg = 0; - for (int c = 0; c < 8; c++) avg += vals[c]; - valuesData[i] = avg / 8.f; - } - // Update data range from the node values themselves - if (!nodeValues.empty()) { - float minVal = nodeValues[0], maxVal = nodeValues[0]; - for (float v : nodeValues) { - minVal = std::min(minVal, v); - maxVal = std::max(maxVal, v); - } - dataRange = {minVal, maxVal}; - } +// === Node scalar quantity - // Create managed buffers for the packed data - nodeValues04 = std::unique_ptr>( - new render::ManagedBuffer(&parent, name + "#nodeValues04", nodeValues04Data)); - nodeValues47 = std::unique_ptr>( - new render::ManagedBuffer(&parent, name + "#nodeValues47", nodeValues47Data)); -} +SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& nodeIndices, + const std::vector& nodeValues, DataType dataType_) + : SparseVolumeGridQuantity(name, grid, true), + ScalarQuantity(*this, parent.canonicalizeNodeValueArray(name, nodeIndices, nodeValues, nodeIndicesAreCanonical), + dataType_), + isNodeQuantity(true) {} } // namespace polyscope diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp index 84cdb19c..ae2f4544 100644 --- a/test/src/sparse_volume_grid_test.cpp +++ b/test/src/sparse_volume_grid_test.cpp @@ -180,6 +180,92 @@ TEST_F(PolyscopeTest, SparseVolumeGridNodeColor) { } +TEST_F(PolyscopeTest, SparseVolumeGridDuplicateCellsThrows) { + auto d = buildSparseGridTestData(); + + // Add a duplicate cell + std::vector cellsWithDup = d.occupiedCells; + cellsWithDup.push_back(d.occupiedCells[0]); + + EXPECT_THROW(polyscope::registerSparseVolumeGrid("dup grid", d.origin, d.cellWidth, cellsWithDup), + std::logic_error); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridNodeMissingValuesThrows) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + // Drop the last node to create a missing entry + std::vector partialIndices(d.nodeIndices.begin(), d.nodeIndices.end() - 1); + std::vector partialScalars(d.nodeScalars.begin(), d.nodeScalars.end() - 1); + std::vector partialColors(d.nodeColors.begin(), d.nodeColors.end() - 1); + + EXPECT_THROW(psGrid->addNodeScalarQuantity("missing scalar", partialIndices, partialScalars), std::runtime_error); + EXPECT_THROW(psGrid->addNodeColorQuantity("missing color", partialIndices, partialColors), std::runtime_error); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridNodeExtraValuesOk) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + // Add extra node entries not present in the grid + std::vector extraIndices = d.nodeIndices; + std::vector extraScalars = d.nodeScalars; + std::vector extraColors = d.nodeColors; + extraIndices.push_back({999, 999, 999}); + extraScalars.push_back(0.f); + extraColors.push_back({0.f, 0.f, 0.f}); + + // Should not throw + EXPECT_NO_THROW(psGrid->addNodeScalarQuantity("extra scalar", extraIndices, extraScalars)); + EXPECT_NO_THROW(psGrid->addNodeColorQuantity("extra color", extraIndices, extraColors)); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridNodeCanonicalFlag) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + // The test data's nodeIndices come from std::set iteration, which is sorted — should match canonical order + auto* qScalar = psGrid->addNodeScalarQuantity("canonical scalar", d.nodeIndices, d.nodeScalars); + EXPECT_TRUE(qScalar->getNodeIndicesAreCanonical()); + + auto* qColor = psGrid->addNodeColorQuantity("canonical color", d.nodeIndices, d.nodeColors); + EXPECT_TRUE(qColor->getNodeIndicesAreCanonical()); + + // Now provide the same data in reversed order — should NOT be canonical + std::vector reversedIndices(d.nodeIndices.rbegin(), d.nodeIndices.rend()); + std::vector reversedScalars(d.nodeScalars.rbegin(), d.nodeScalars.rend()); + std::vector reversedColors(d.nodeColors.rbegin(), d.nodeColors.rend()); + + auto* qScalar2 = psGrid->addNodeScalarQuantity("reversed scalar", reversedIndices, reversedScalars); + EXPECT_FALSE(qScalar2->getNodeIndicesAreCanonical()); + + auto* qColor2 = psGrid->addNodeColorQuantity("reversed color", reversedIndices, reversedColors); + EXPECT_FALSE(qColor2->getNodeIndicesAreCanonical()); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + TEST_F(PolyscopeTest, SparseVolumeGridBasicOptions) { auto d = buildSparseGridTestData(); From e2236f4c6214ec804552faa50cf9cb167b3c411e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Feb 2026 11:50:04 -1000 Subject: [PATCH 11/18] avoid reallocation in point cloud --- src/point_cloud.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index 618dc2b5..edd7a60b 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -190,10 +190,9 @@ void PointCloud::ensurePickProgramPrepared() { setPointProgramGeometryAttributes(*pickProgram); // Fill color buffer with packed point indices - std::vector pickColors; - for (size_t i = pickStart; i < pickStart + pickCount; i++) { - glm::vec3 val = pick::indToVec(i); - pickColors.push_back(pick::indToVec(i)); + std::vector pickColors(pickCount); + for (size_t i = 0; i < pickCount; i++) { + pickColors[i] = pick::indToVec(i + pickStart); } // Store data in buffers From f8b045c2e4fc7d7f8c1cd8d44c9a32f744f16adc Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Feb 2026 11:50:14 -1000 Subject: [PATCH 12/18] use random colors for demo --- examples/demo-app/demo_app.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index d55e65a7..ccee878f 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -605,18 +605,16 @@ void addSparseVolumeGrid() { psGrid->addNodeScalarQuantity("node x-coord", nodeIndices, nodeValues); } - // Cell color: RGB from normalized position + // Cell color: random colors { std::vector cellColors(occupiedCells.size()); for (size_t i = 0; i < occupiedCells.size(); i++) { - glm::vec3 cellCenter = origin + (glm::vec3(occupiedCells[i]) + 0.5f) * cellWidth; - // Map to [0,1] range - cellColors[i] = glm::clamp((cellCenter - origin) / 10.f + 0.5f, 0.f, 1.f); + cellColors[i] = glm::vec3{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}; } psGrid->addCellColorQuantity("cell color", cellColors); } - // Node color: RGB from normalized node position + // Node color: random colors { std::set> nodeSet; for (const auto& ci : occupiedCells) { @@ -635,8 +633,7 @@ void addSparseVolumeGrid() { int32_t ni, nj, nk; std::tie(ni, nj, nk) = nodeInds; nodeIndices.push_back({ni, nj, nk}); - glm::vec3 nodePos = origin + glm::vec3(ni, nj, nk) * cellWidth; - nodeColors.push_back(glm::clamp((nodePos - origin) / 10.f + 0.5f, 0.f, 1.f)); + nodeColors.push_back(glm::vec3{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}); } psGrid->addNodeColorQuantity("node color", nodeIndices, nodeColors); } From b680cc7c2d80b6188f558a69e6c32654a0f3cc2a Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Feb 2026 11:51:08 -1000 Subject: [PATCH 13/18] test picking --- test/src/sparse_volume_grid_test.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp index ae2f4544..7f32e9e1 100644 --- a/test/src/sparse_volume_grid_test.cpp +++ b/test/src/sparse_volume_grid_test.cpp @@ -118,6 +118,18 @@ TEST_F(PolyscopeTest, SparseVolumeGridSlicePlane) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, SparseVolumeGridPick) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + // Don't bother trying to actually click on anything, but make sure this doesn't crash + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + + polyscope::removeAllStructures(); +} + TEST_F(PolyscopeTest, SparseVolumeGridCellScalar) { auto d = buildSparseGridTestData(); @@ -156,7 +168,7 @@ TEST_F(PolyscopeTest, SparseVolumeGridCellColor) { polyscope::SparseVolumeGrid* psGrid = polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - polyscope::SparseVolumeGridColorQuantity* q = psGrid->addCellColorQuantity("cell color", d.cellColors); + polyscope::SparseVolumeGridCellColorQuantity* q = psGrid->addCellColorQuantity("cell color", d.cellColors); q->setEnabled(true); polyscope::show(3); @@ -171,7 +183,8 @@ TEST_F(PolyscopeTest, SparseVolumeGridNodeColor) { polyscope::SparseVolumeGrid* psGrid = polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); - polyscope::SparseVolumeGridColorQuantity* q = psGrid->addNodeColorQuantity("node color", d.nodeIndices, d.nodeColors); + polyscope::SparseVolumeGridNodeColorQuantity* q = + psGrid->addNodeColorQuantity("node color", d.nodeIndices, d.nodeColors); q->setEnabled(true); polyscope::show(3); @@ -187,8 +200,7 @@ TEST_F(PolyscopeTest, SparseVolumeGridDuplicateCellsThrows) { std::vector cellsWithDup = d.occupiedCells; cellsWithDup.push_back(d.occupiedCells[0]); - EXPECT_THROW(polyscope::registerSparseVolumeGrid("dup grid", d.origin, d.cellWidth, cellsWithDup), - std::logic_error); + EXPECT_THROW(polyscope::registerSparseVolumeGrid("dup grid", d.origin, d.cellWidth, cellsWithDup), std::logic_error); polyscope::removeAllStructures(); } From 9e7085c22f17e48957caf1fa3613cfaf2001ce78 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Feb 2026 11:51:48 -1000 Subject: [PATCH 14/18] split sparse volume quantities to separate classes, improve picking --- include/polyscope/sparse_volume_grid.h | 67 +++-- include/polyscope/sparse_volume_grid.ipp | 14 +- .../sparse_volume_grid_color_quantity.h | 44 +++- .../polyscope/sparse_volume_grid_quantity.h | 4 + .../sparse_volume_grid_scalar_quantity.h | 48 +++- include/polyscope/types.h | 6 + src/sparse_volume_grid.cpp | 238 +++++++++++++++--- src/sparse_volume_grid_color_quantity.cpp | 105 +++++--- src/sparse_volume_grid_scalar_quantity.cpp | 100 +++++--- 9 files changed, 484 insertions(+), 142 deletions(-) diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h index b9218d48..218839ff 100644 --- a/include/polyscope/sparse_volume_grid.h +++ b/include/polyscope/sparse_volume_grid.h @@ -18,12 +18,24 @@ namespace polyscope { class SparseVolumeGrid; -class SparseVolumeGridScalarQuantity; -class SparseVolumeGridColorQuantity; +class SparseVolumeGridCellScalarQuantity; +class SparseVolumeGridNodeScalarQuantity; +class SparseVolumeGridCellColorQuantity; +class SparseVolumeGridNodeColorQuantity; + +struct SparseVolumeGridPickResult { + SparseVolumeGridElement elementType; + glm::ivec3 cellIndex; // only populated if cell + uint64_t cellFlatIndex; // only populated if cell + glm::ivec3 nodeIndex; // only populated if node +}; class SparseVolumeGrid : public Structure { public: // Construct a new sparse volume grid structure + // The origin is the NODE/CORNER orgin. That is, the cell 0,0,0, will have its lower-left corner sitting at this + // origin. If you wish to specify the CENTER of the the 0,0,0 cell, you should pass (cellOrigin - 0.5 * + // gridCellWidth). SparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, std::vector occupiedCells); // === Overloads @@ -78,27 +90,34 @@ class SparseVolumeGrid : public Structure { // Cell scalar. Values array must be passed in the same order as initial input cell list. template - SparseVolumeGridScalarQuantity* addCellScalarQuantity(std::string name, const T& values, - DataType type = DataType::STANDARD); + SparseVolumeGridCellScalarQuantity* addCellScalarQuantity(std::string name, const T& values, + DataType type = DataType::STANDARD); // Node scalar. Indices are _node_ indices; the nodes are a shifted sparse grid offset from the cell enumeration. For // a cell with indices ijk, its corrners are the nodes with indices (i k j, i+1 j k, ..., i+1 j+1, k+1). Node values // are passed via a paired set of arrays, giving the node index and node value for each. Node values may be passed in // any order, and having extra entries is fine too, as long as all required nodes values are present. template - SparseVolumeGridScalarQuantity* addNodeScalarQuantity(std::string name, const TI& nodeIndices, const TV& nodeValues, - DataType type = DataType::STANDARD); + SparseVolumeGridNodeScalarQuantity* addNodeScalarQuantity(std::string name, const TI& nodeIndices, + const TV& nodeValues, DataType type = DataType::STANDARD); // Cell color. Values array must be passed in the same order as initial input cell list. template - SparseVolumeGridColorQuantity* addCellColorQuantity(std::string name, const T& colors); + SparseVolumeGridCellColorQuantity* addCellColorQuantity(std::string name, const T& colors); // Node color. Indices are _node_ indices; the nodes are a shifted sparse grid offset from the cell enumeration. For // a cell with indices ijk, its corrners are the nodes with indices (i k j, i+1 j k, ..., i+1 j+1, k+1). Node values // are passed via a paired set of arrays, giving the node index and node value for each. Node values may be passed in // any order, and having extra entries is fine too, as long as all required nodes values are present. template - SparseVolumeGridColorQuantity* addNodeColorQuantity(std::string name, const TI& nodeIndices, const TC& nodeColors); + SparseVolumeGridNodeColorQuantity* addNodeColorQuantity(std::string name, const TI& nodeIndices, + const TC& nodeColors); + + // Force the grid to act as if nodes are in use (enable them for picking) + void markNodesAsUsed(); + + // Get data related to picking/selection + SparseVolumeGridPickResult interpretPickResult(const PickResult& result); // Rendering related helpers void setCellGeometryAttributes(render::ShaderProgram& p); @@ -159,8 +178,9 @@ class SparseVolumeGrid : public Structure { void computeCornerNodeIndices(); // Picking-related - size_t globalPickConstant = INVALID_IND_64; - glm::vec3 pickColor; + bool nodesHaveBeenUsed = false; + void buildCellInfoGUI(const SparseVolumeGridPickResult& result); + void buildNodeInfoGUI(const SparseVolumeGridPickResult& result); // Drawing related things std::shared_ptr program; @@ -168,27 +188,36 @@ class SparseVolumeGrid : public Structure { // === Helpers void checkForDuplicateCells(); + size_t findCellFlatIndex(glm::ivec3 cellInd3); + size_t findNodeFlatIndex(glm::ivec3 nodeInd3); void ensureRenderProgramPrepared(); void ensurePickProgramPrepared(); // Quantity impl methods - SparseVolumeGridScalarQuantity* addCellScalarQuantityImpl(std::string name, const std::vector& data, - DataType type); - SparseVolumeGridScalarQuantity* addNodeScalarQuantityImpl(std::string name, - const std::vector& nodeIndices, - const std::vector& nodeValues, DataType type); - SparseVolumeGridColorQuantity* addCellColorQuantityImpl(std::string name, const std::vector& colors); - SparseVolumeGridColorQuantity* addNodeColorQuantityImpl(std::string name, const std::vector& nodeIndices, - const std::vector& nodeColors); + SparseVolumeGridCellScalarQuantity* addCellScalarQuantityImpl(std::string name, const std::vector& data, + DataType type); + SparseVolumeGridNodeScalarQuantity* addNodeScalarQuantityImpl(std::string name, + const std::vector& nodeIndices, + const std::vector& nodeValues, DataType type); + SparseVolumeGridCellColorQuantity* addCellColorQuantityImpl(std::string name, const std::vector& colors); + SparseVolumeGridNodeColorQuantity* addNodeColorQuantityImpl(std::string name, + const std::vector& nodeIndices, + const std::vector& nodeColors); }; // Register a sparse volume grid +// The origin is the NODE/CORNER orgin. That is, the cell 0,0,0, will have its lower-left corner sitting at this +// origin. If you wish to specify the CENTER of the the 0,0,0 cell, you should pass (cellOrigin - 0.5 * +// gridCellWidth). template SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, const T& occupiedCells); -// Non-template overloads +// Register a sparse volume grid (non-templated overload) +// The origin is the NODE/CORNER orgin. That is, the cell 0,0,0, will have its lower-left corner sitting at this +// origin. If you wish to specify the CENTER of the the 0,0,0 cell, you should pass (cellOrigin - 0.5 * +// gridCellWidth). SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, glm::vec3 gridCellWidth, const std::vector& occupiedCells); diff --git a/include/polyscope/sparse_volume_grid.ipp b/include/polyscope/sparse_volume_grid.ipp index 522cb872..a6b93e9b 100644 --- a/include/polyscope/sparse_volume_grid.ipp +++ b/include/polyscope/sparse_volume_grid.ipp @@ -115,15 +115,15 @@ std::vector SparseVolumeGrid::canonicalizeNodeValueArray(const std::string& q // ===================================================== template -SparseVolumeGridScalarQuantity* SparseVolumeGrid::addCellScalarQuantity(std::string name, const T& values, - DataType type) { +SparseVolumeGridCellScalarQuantity* SparseVolumeGrid::addCellScalarQuantity(std::string name, const T& values, + DataType type) { validateSize(values, nCells(), "sparse volume grid cell scalar quantity " + name); return addCellScalarQuantityImpl(name, standardizeArray(values), type); } template -SparseVolumeGridScalarQuantity* SparseVolumeGrid::addNodeScalarQuantity(std::string name, const TI& nodeIndices, - const TV& nodeValues, DataType type) { +SparseVolumeGridNodeScalarQuantity* SparseVolumeGrid::addNodeScalarQuantity(std::string name, const TI& nodeIndices, + const TV& nodeValues, DataType type) { if (adaptorF_size(nodeIndices) != adaptorF_size(nodeValues)) { exception("SparseVolumeGrid::addNodeScalarQuantity: nodeIndices and nodeValues must have the same size"); } @@ -132,14 +132,14 @@ SparseVolumeGridScalarQuantity* SparseVolumeGrid::addNodeScalarQuantity(std::str } template -SparseVolumeGridColorQuantity* SparseVolumeGrid::addCellColorQuantity(std::string name, const T& colors) { +SparseVolumeGridCellColorQuantity* SparseVolumeGrid::addCellColorQuantity(std::string name, const T& colors) { validateSize(colors, nCells(), "sparse volume grid cell color quantity " + name); return addCellColorQuantityImpl(name, standardizeVectorArray(colors)); } template -SparseVolumeGridColorQuantity* SparseVolumeGrid::addNodeColorQuantity(std::string name, const TI& nodeIndices, - const TC& nodeColors) { +SparseVolumeGridNodeColorQuantity* SparseVolumeGrid::addNodeColorQuantity(std::string name, const TI& nodeIndices, + const TC& nodeColors) { if (adaptorF_size(nodeIndices) != adaptorF_size(nodeColors)) { exception("SparseVolumeGrid::addNodeColorQuantity: nodeIndices and nodeColors must have the same size"); } diff --git a/include/polyscope/sparse_volume_grid_color_quantity.h b/include/polyscope/sparse_volume_grid_color_quantity.h index 10bdfaa9..f7c657f1 100644 --- a/include/polyscope/sparse_volume_grid_color_quantity.h +++ b/include/polyscope/sparse_volume_grid_color_quantity.h @@ -13,26 +13,50 @@ namespace polyscope { class SparseVolumeGridColorQuantity : public SparseVolumeGridQuantity, public ColorQuantity { public: - // Cell color constructor - SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& colors); - // Node color constructor - SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& nodeIndices, - const std::vector& nodeColors); + SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, const std::string& definedOn_, + const std::vector& colors_); virtual void draw() override; virtual void refresh() override; virtual std::string niceName() override; +protected: + const std::string definedOn; + std::shared_ptr program; + virtual void createProgram() = 0; +}; + +// ======================================================== +// ========== Cell Color ========== +// ======================================================== + +class SparseVolumeGridCellColorQuantity : public SparseVolumeGridColorQuantity { +public: + SparseVolumeGridCellColorQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& cellColors); + + virtual void createProgram() override; + virtual void buildCellInfoGUI(size_t cellInd) override; +}; + + +// ======================================================== +// ========== Node Color ========== +// ======================================================== + +class SparseVolumeGridNodeColorQuantity : public SparseVolumeGridColorQuantity { +public: + SparseVolumeGridNodeColorQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& nodeIndices, + const std::vector& nodeColors); + + virtual void createProgram() override; + virtual void buildNodeInfoGUI(size_t nodeInd) override; bool getNodeIndicesAreCanonical() const { return nodeIndicesAreCanonical; } -private: - bool isNodeQuantity = false; +protected: bool nodeIndicesAreCanonical; // true if user-provided indices matched canonical order exactly (set by constructor) - void createProgram(); - std::shared_ptr program; }; - } // namespace polyscope diff --git a/include/polyscope/sparse_volume_grid_quantity.h b/include/polyscope/sparse_volume_grid_quantity.h index 5a671cfb..f5bebbff 100644 --- a/include/polyscope/sparse_volume_grid_quantity.h +++ b/include/polyscope/sparse_volume_grid_quantity.h @@ -14,6 +14,10 @@ class SparseVolumeGridQuantity : public Quantity { SparseVolumeGridQuantity(std::string name, SparseVolumeGrid& parentStructure, bool dominates = false); virtual ~SparseVolumeGridQuantity() {}; + // Build info GUI for picked elements (overridden by subclasses to display quantity values) + virtual void buildCellInfoGUI(size_t cellInd); + virtual void buildNodeInfoGUI(size_t nodeInd); + SparseVolumeGrid& parent; // shadows and hides the generic member in Quantity }; diff --git a/include/polyscope/sparse_volume_grid_scalar_quantity.h b/include/polyscope/sparse_volume_grid_scalar_quantity.h index f7aadd3f..d9262a4f 100644 --- a/include/polyscope/sparse_volume_grid_scalar_quantity.h +++ b/include/polyscope/sparse_volume_grid_scalar_quantity.h @@ -13,15 +13,9 @@ namespace polyscope { class SparseVolumeGridScalarQuantity : public SparseVolumeGridQuantity, public ScalarQuantity { - public: - // Cell scalar constructor - SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& values, - DataType dataType); - - // Node scalar constructor - SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& nodeIndices, - const std::vector& nodeValues, DataType dataType); + SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, const std::string& definedOn_, + const std::vector& values_, DataType dataType_); virtual void draw() override; virtual void buildCustomUI() override; @@ -29,14 +23,42 @@ class SparseVolumeGridScalarQuantity : public SparseVolumeGridQuantity, virtual std::string niceName() override; +protected: + const std::string definedOn; + std::shared_ptr program; + virtual void createProgram() = 0; +}; + +// ======================================================== +// ========== Cell Scalar ========== +// ======================================================== + +class SparseVolumeGridCellScalarQuantity : public SparseVolumeGridScalarQuantity { +public: + SparseVolumeGridCellScalarQuantity(std::string name, SparseVolumeGrid& grid, const std::vector& cellValues, + DataType dataType); + + virtual void createProgram() override; + virtual void buildCellInfoGUI(size_t cellInd) override; +}; + + +// ======================================================== +// ========== Node Scalar ========== +// ======================================================== + +class SparseVolumeGridNodeScalarQuantity : public SparseVolumeGridScalarQuantity { +public: + SparseVolumeGridNodeScalarQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& nodeIndices, + const std::vector& nodeValues, DataType dataType); + + virtual void createProgram() override; + virtual void buildNodeInfoGUI(size_t nodeInd) override; bool getNodeIndicesAreCanonical() const { return nodeIndicesAreCanonical; } -private: - bool isNodeQuantity = false; +protected: bool nodeIndicesAreCanonical; // true if user-provided indices matched canonical order exactly (set by constructor) - void createProgram(); - std::shared_ptr program; }; - } // namespace polyscope diff --git a/include/polyscope/types.h b/include/polyscope/types.h index e0bf5031..f94a4f84 100644 --- a/include/polyscope/types.h +++ b/include/polyscope/types.h @@ -158,6 +158,12 @@ POLYSCOPE_DEFINE_ENUM_NAMES(VolumeGridElement, {VolumeGridElement::CELL, "Cell"} ); +enum class SparseVolumeGridElement { CELL = 0, NODE }; +POLYSCOPE_DEFINE_ENUM_NAMES(SparseVolumeGridElement, + {SparseVolumeGridElement::CELL, "Cell"}, + {SparseVolumeGridElement::NODE, "Node"} +); + enum class IsolineStyle { Stripe = 0, Contour }; POLYSCOPE_DEFINE_ENUM_NAMES(IsolineStyle, {IsolineStyle::Stripe, "Stripe"}, diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index f3f49a54..339b434e 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -16,6 +16,16 @@ namespace polyscope { // Initialize statics const std::string SparseVolumeGrid::structureTypeName = "Sparse Volume Grid"; +namespace { + +// comparator for sorting int3 vecs +bool ivec3Less(const glm::ivec3& a, const glm::ivec3& b) { + if (a.x != b.x) return a.x < b.x; + if (a.y != b.y) return a.y < b.y; + return a.z < b.z; +}; +} // namespace + SparseVolumeGrid::SparseVolumeGrid(std::string name, glm::vec3 origin_, glm::vec3 gridCellWidth_, std::vector occupiedCells) : Structure(name, typeName()), @@ -96,12 +106,6 @@ void SparseVolumeGrid::ensureHaveCornerNodeIndices() { void SparseVolumeGrid::computeCornerNodeIndices() { size_t n = occupiedCellsData.size(); - auto ivec3Less = [](const glm::ivec3& a, const glm::ivec3& b) { - if (a.x != b.x) return a.x < b.x; - if (a.y != b.y) return a.y < b.y; - return a.z < b.z; - }; - // Collect all node ivec3 from all cells std::vector allNodes; allNodes.reserve(n * 8); @@ -251,7 +255,6 @@ void SparseVolumeGrid::drawPick() { // Set program uniforms setSparseVolumeGridUniforms(*pickProgram, true); - pickProgram->setUniform("u_pickColor", pickColor); // Draw the actual grid render::engine->setBackfaceCull(true); @@ -285,21 +288,28 @@ void SparseVolumeGrid::ensureRenderProgramPrepared() { void SparseVolumeGrid::ensurePickProgramPrepared() { if (pickProgram) return; + // Request pick indices + size_t pickCount = nCells(); + size_t pickStart = pick::requestPickBufferRange(this, pickCount); + // clang-format off pickProgram = render::engine->requestShader( "GRIDCUBE", - addSparseGridShaderRules({"GRIDCUBE_CONSTANT_PICK"}, true), + addSparseGridShaderRules({"GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR"}, true), render::ShaderReplacementDefaults::Pick ); // clang-format on + // Fill color buffer with packed point indices + std::vector pickColors(pickCount); + for (size_t i = 0; i < pickCount; i++) { + pickColors[i] = pick::indToVec(i + pickStart); + } + pickProgram->setAttribute("a_color", pickColors); + + pickProgram->setAttribute("a_cellPosition", cellPositions.getRenderAttributeBuffer()); pickProgram->setAttribute("a_cellInd", cellIndices.getRenderAttributeBuffer()); - - if (globalPickConstant == INVALID_IND_64) { - globalPickConstant = pick::requestPickBufferRange(this, 1); - pickColor = pick::indToVec(static_cast(globalPickConstant)); - } } @@ -340,25 +350,185 @@ void SparseVolumeGrid::refresh() { } -void SparseVolumeGrid::buildPickUI(const PickResult& rawResult) { +SparseVolumeGridPickResult SparseVolumeGrid::interpretPickResult(const PickResult& rawResult) { + const float nodePickRad = 0.8; // threshold to click node, measured in a [-1,1] cube + + if (rawResult.structure != this) { + exception("called interpretPickResult(), but the pick result is not from this structure"); + } + + SparseVolumeGridPickResult result; + result.cellIndex = glm::vec3{0, 0, 0}; + result.cellFlatIndex = INVALID_IND_64; + result.nodeIndex = glm::vec3{0, 0, 0}; - // Determine which cell was clicked, CPU-side glm::vec3 localPos = (rawResult.position - origin) / gridCellWidth; // Find the cell index - glm::ivec3 cellInd3{static_cast(std::floor(localPos.x)), static_cast(std::floor(localPos.y)), - static_cast(std::floor(localPos.z))}; + glm::ivec3 cellInd3 = cellIndicesData[rawResult.localIndex]; + + // Fractional position within cell [0,1] + glm::vec3 fractional = localPos - glm::vec3(cellInd3); + + // Local coordinates in [-1,1] within the scaled cell + glm::vec3 coordModShift = 2.f * fractional - 1.f; + float csf = cubeSizeFactor.get(); + glm::vec3 coordLocal = (csf < 1.f) ? (coordModShift / (1.f - csf)) : coordModShift; + float distFromCorner = glm::length(1.f - glm::abs(coordLocal)); + + bool doPickNodes; + if (nodesHaveBeenUsed) { + doPickNodes = distFromCorner < nodePickRad; + } else { + doPickNodes = false; + } + + if (doPickNodes) { + // return a node pick + + ensureHaveCornerNodeIndices(); + + // find the nearest corner + int cornerDx = fractional.x > 0.5f ? 1 : 0; + int cornerDy = fractional.y > 0.5f ? 1 : 0; + int cornerDz = fractional.z > 0.5f ? 1 : 0; + glm::ivec3 nodeInd3(cellInd3.x + cornerDx - 1, cellInd3.y + cornerDy - 1, cellInd3.z + cornerDz - 1); + + result.elementType = SparseVolumeGridElement::NODE; + result.nodeIndex = nodeInd3; + + } else { + // return a cell pick + + result.elementType = SparseVolumeGridElement::CELL; + result.cellIndex = cellInd3; + result.cellFlatIndex = rawResult.localIndex; + } + + return result; +} + + +void SparseVolumeGrid::buildPickUI(const PickResult& rawResult) { + + SparseVolumeGridPickResult result = interpretPickResult(rawResult); + + switch (result.elementType) { + case SparseVolumeGridElement::NODE: { + buildNodeInfoGUI(result); + break; + } + case SparseVolumeGridElement::CELL: { + buildCellInfoGUI(result); + break; + } + }; +} + + +void SparseVolumeGrid::buildCellInfoGUI(const SparseVolumeGridPickResult& result) { + + glm::ivec3 cellInd3 = result.cellIndex; + size_t flatInd = result.cellFlatIndex; ImGui::TextUnformatted(("Cell index: (" + std::to_string(cellInd3.x) + "," + std::to_string(cellInd3.y) + "," + std::to_string(cellInd3.z) + ")") .c_str()); + if (flatInd != INVALID_IND_64) { + ImGui::TextUnformatted(("Cell #" + std::to_string(flatInd)).c_str()); + } glm::vec3 cellCenter = origin + (glm::vec3(cellInd3) + 0.5f) * gridCellWidth; std::stringstream buffer; buffer << cellCenter; ImGui::TextUnformatted(("Position: " + buffer.str()).c_str()); + + if (flatInd != INVALID_IND_64) { + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Indent(20.); + + // Build GUI to show the quantities + ImGui::Columns(2); + ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() / 3); + for (auto& x : quantities) { + SparseVolumeGridQuantity* q = static_cast(x.second.get()); + q->buildCellInfoGUI(flatInd); + } + + ImGui::Indent(-20.); + } +} + + +void SparseVolumeGrid::buildNodeInfoGUI(const SparseVolumeGridPickResult& result) { + + glm::ivec3 nodeInd3 = result.nodeIndex; + + ImGui::TextUnformatted(("Node index: (" + std::to_string(nodeInd3.x) + "," + std::to_string(nodeInd3.y) + "," + + std::to_string(nodeInd3.z) + ")") + .c_str()); + + // Find canonical node index + ensureHaveCornerNodeIndices(); + auto it = std::lower_bound(canonicalNodeIndsData.begin(), canonicalNodeIndsData.end(), nodeInd3, ivec3Less); + size_t canonicalInd = INVALID_IND_64; + if (it != canonicalNodeIndsData.end() && *it == nodeInd3) { + canonicalInd = static_cast(it - canonicalNodeIndsData.begin()); + ImGui::TextUnformatted(("Node #" + std::to_string(canonicalInd)).c_str()); + } + + glm::vec3 nodePos = origin + glm::vec3(nodeInd3 + 1) * gridCellWidth; + std::stringstream buffer; + buffer << nodePos; + ImGui::TextUnformatted(("Position: " + buffer.str()).c_str()); + + if (canonicalInd != INVALID_IND_64) { + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Indent(20.); + + // Build GUI to show the quantities + ImGui::Columns(2); + ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() / 3); + for (auto& x : quantities) { + SparseVolumeGridQuantity* q = static_cast(x.second.get()); + q->buildNodeInfoGUI(canonicalInd); + } + + ImGui::Indent(-20.); + } +} + + +size_t SparseVolumeGrid::findCellFlatIndex(glm::ivec3 cellInd3) { + for (size_t i = 0; i < occupiedCellsData.size(); i++) { + if (occupiedCellsData[i] == cellInd3) return i; + } + return INVALID_IND_64; } +size_t SparseVolumeGrid::findNodeFlatIndex(glm::ivec3 nodeInd3) { + + if (!haveCornerNodeIndices) { + error("findFlatNodeIndex requires that node indices have been prepared"); + return INVALID_IND_64; + } + + // binary search + auto it = std::lower_bound(canonicalNodeIndsData.begin(), canonicalNodeIndsData.end(), nodeInd3, ivec3Less); + size_t canonicalInd = INVALID_IND_64; + if (it != canonicalNodeIndsData.end() && *it == nodeInd3) { + return static_cast(it - canonicalNodeIndsData.begin()); + } + + return INVALID_IND_64; +} + +void SparseVolumeGrid::markNodesAsUsed() { nodesHaveBeenUsed = true; } + // === Option getters and setters @@ -460,40 +630,46 @@ SparseVolumeGrid* registerSparseVolumeGrid(std::string name, glm::vec3 origin, g SparseVolumeGridQuantity::SparseVolumeGridQuantity(std::string name_, SparseVolumeGrid& grid_, bool dominates_) : Quantity(name_, grid_, dominates_), parent(grid_) {} +// Default implementations +void SparseVolumeGridQuantity::buildCellInfoGUI(size_t cellInd) {} +void SparseVolumeGridQuantity::buildNodeInfoGUI(size_t nodeInd) {} + // === Quantity adders -SparseVolumeGridScalarQuantity* +SparseVolumeGridCellScalarQuantity* SparseVolumeGrid::addCellScalarQuantityImpl(std::string name, const std::vector& data, DataType type) { checkForQuantityWithNameAndDeleteOrError(name); - SparseVolumeGridScalarQuantity* q = new SparseVolumeGridScalarQuantity(name, *this, data, type); + SparseVolumeGridCellScalarQuantity* q = new SparseVolumeGridCellScalarQuantity(name, *this, data, type); addQuantity(q); return q; } -SparseVolumeGridScalarQuantity* SparseVolumeGrid::addNodeScalarQuantityImpl(std::string name, - const std::vector& nodeIndices, - const std::vector& nodeValues, - DataType type) { +SparseVolumeGridNodeScalarQuantity* +SparseVolumeGrid::addNodeScalarQuantityImpl(std::string name, const std::vector& nodeIndices, + const std::vector& nodeValues, DataType type) { checkForQuantityWithNameAndDeleteOrError(name); - SparseVolumeGridScalarQuantity* q = new SparseVolumeGridScalarQuantity(name, *this, nodeIndices, nodeValues, type); + SparseVolumeGridNodeScalarQuantity* q = + new SparseVolumeGridNodeScalarQuantity(name, *this, nodeIndices, nodeValues, type); addQuantity(q); + markNodesAsUsed(); return q; } -SparseVolumeGridColorQuantity* SparseVolumeGrid::addCellColorQuantityImpl(std::string name, - const std::vector& colors) { +SparseVolumeGridCellColorQuantity* SparseVolumeGrid::addCellColorQuantityImpl(std::string name, + const std::vector& colors) { checkForQuantityWithNameAndDeleteOrError(name); - SparseVolumeGridColorQuantity* q = new SparseVolumeGridColorQuantity(name, *this, colors); + SparseVolumeGridCellColorQuantity* q = new SparseVolumeGridCellColorQuantity(name, *this, colors); addQuantity(q); return q; } -SparseVolumeGridColorQuantity* SparseVolumeGrid::addNodeColorQuantityImpl(std::string name, - const std::vector& nodeIndices, - const std::vector& nodeColors) { +SparseVolumeGridNodeColorQuantity* +SparseVolumeGrid::addNodeColorQuantityImpl(std::string name, const std::vector& nodeIndices, + const std::vector& nodeColors) { checkForQuantityWithNameAndDeleteOrError(name); - SparseVolumeGridColorQuantity* q = new SparseVolumeGridColorQuantity(name, *this, nodeIndices, nodeColors); + SparseVolumeGridNodeColorQuantity* q = new SparseVolumeGridNodeColorQuantity(name, *this, nodeIndices, nodeColors); addQuantity(q); + markNodesAsUsed(); return q; } diff --git a/src/sparse_volume_grid_color_quantity.cpp b/src/sparse_volume_grid_color_quantity.cpp index cfeb875d..fcd493f6 100644 --- a/src/sparse_volume_grid_color_quantity.cpp +++ b/src/sparse_volume_grid_color_quantity.cpp @@ -10,6 +10,11 @@ namespace polyscope { // === Sparse volume grid color quantity +SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, + const std::string& definedOn_, + const std::vector& colors_) + : SparseVolumeGridQuantity(name, grid, true), ColorQuantity(*this, colors_), definedOn(definedOn_) {} + void SparseVolumeGridColorQuantity::draw() { if (!isEnabled()) return; @@ -24,14 +29,30 @@ void SparseVolumeGridColorQuantity::draw() { } -void SparseVolumeGridColorQuantity::createProgram() { +void SparseVolumeGridColorQuantity::refresh() { + program.reset(); + Quantity::refresh(); +} + +std::string SparseVolumeGridColorQuantity::niceName() { return name + " (" + definedOn + " color)"; } + + +// ======================================================== +// ========== Cell Color ========== +// ======================================================== + +SparseVolumeGridCellColorQuantity::SparseVolumeGridCellColorQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& cellColors) + : SparseVolumeGridColorQuantity(name, grid, "cell", cellColors) {} + +void SparseVolumeGridCellColorQuantity::createProgram() { // clang-format off program = render::engine->requestShader("GRIDCUBE", render::engine->addMaterialRules(parent.getMaterial(), addColorRules( parent.addSparseGridShaderRules({ - isNodeQuantity ? "GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR" : "GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", + "GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", "SHADE_COLOR" }) ) @@ -40,42 +61,68 @@ void SparseVolumeGridColorQuantity::createProgram() { // clang-format on parent.setCellGeometryAttributes(*program); - - if (isNodeQuantity) { - for (int c = 0; c < 8; c++) { - program->setAttribute("a_nodeColor" + std::to_string(c), - colors.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c])); - } - } else { - program->setAttribute("a_color", colors.getRenderAttributeBuffer()); - } - + program->setAttribute("a_color", colors.getRenderAttributeBuffer()); render::engine->setMaterial(*program, parent.getMaterial()); } -void SparseVolumeGridColorQuantity::refresh() { - program.reset(); - Quantity::refresh(); -} +void SparseVolumeGridCellColorQuantity::buildCellInfoGUI(size_t cellInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); -std::string SparseVolumeGridColorQuantity::niceName() { return name + " (color)"; } + glm::vec3 tempColor = colors.getValue(cellInd); + ImGui::ColorEdit3("", &tempColor[0], ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoPicker); + ImGui::SameLine(); + std::stringstream buffer; + buffer << tempColor; + ImGui::TextUnformatted(buffer.str().c_str()); + ImGui::NextColumn(); +} -// === Cell color quantity +// ======================================================== +// ========== Node Color ========== +// ======================================================== -SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& colors_) - : SparseVolumeGridQuantity(name, grid, true), ColorQuantity(*this, colors_), isNodeQuantity(false), - nodeIndicesAreCanonical(true) {} +SparseVolumeGridNodeColorQuantity::SparseVolumeGridNodeColorQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& nodeIndices, + const std::vector& nodeColors) + : SparseVolumeGridColorQuantity( + name, grid, "node", grid.canonicalizeNodeValueArray(name, nodeIndices, nodeColors, nodeIndicesAreCanonical)) { +} +void SparseVolumeGridNodeColorQuantity::createProgram() { -// === Node color quantity + // clang-format off + program = render::engine->requestShader("GRIDCUBE", + render::engine->addMaterialRules(parent.getMaterial(), + addColorRules( + parent.addSparseGridShaderRules({ + "GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR", + "SHADE_COLOR" + }) + ) + ) + ); + // clang-format on -SparseVolumeGridColorQuantity::SparseVolumeGridColorQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& nodeIndices, - const std::vector& nodeColors) - : SparseVolumeGridQuantity(name, grid, true), - ColorQuantity(*this, parent.canonicalizeNodeValueArray(name, nodeIndices, nodeColors, nodeIndicesAreCanonical)), - isNodeQuantity(true) {} + parent.setCellGeometryAttributes(*program); + for (int c = 0; c < 8; c++) { + program->setAttribute("a_nodeColor" + std::to_string(c), + colors.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c])); + } + render::engine->setMaterial(*program, parent.getMaterial()); +} +void SparseVolumeGridNodeColorQuantity::buildNodeInfoGUI(size_t nodeInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + + glm::vec3 tempColor = colors.getValue(nodeInd); + ImGui::ColorEdit3("", &tempColor[0], ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoPicker); + ImGui::SameLine(); + std::stringstream buffer; + buffer << tempColor; + ImGui::TextUnformatted(buffer.str().c_str()); + ImGui::NextColumn(); +} } // namespace polyscope diff --git a/src/sparse_volume_grid_scalar_quantity.cpp b/src/sparse_volume_grid_scalar_quantity.cpp index a6d212f6..288b5320 100644 --- a/src/sparse_volume_grid_scalar_quantity.cpp +++ b/src/sparse_volume_grid_scalar_quantity.cpp @@ -8,7 +8,12 @@ namespace polyscope { -// === SparseVolumeGridScalarQuantity +// === Sparse volume grid scalar quantity + +SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, + const std::string& definedOn_, + const std::vector& values_, DataType dataType_) + : SparseVolumeGridQuantity(name, grid, true), ScalarQuantity(*this, values_, dataType_), definedOn(definedOn_) {} void SparseVolumeGridScalarQuantity::draw() { if (!isEnabled()) return; @@ -39,14 +44,31 @@ void SparseVolumeGridScalarQuantity::buildCustomUI() { } -void SparseVolumeGridScalarQuantity::createProgram() { +void SparseVolumeGridScalarQuantity::refresh() { + program.reset(); + Quantity::refresh(); +} + +std::string SparseVolumeGridScalarQuantity::niceName() { return name + " (" + definedOn + " scalar)"; } + + +// ======================================================== +// ========== Cell Scalar ========== +// ======================================================== + +SparseVolumeGridCellScalarQuantity::SparseVolumeGridCellScalarQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& cellValues, + DataType dataType_) + : SparseVolumeGridScalarQuantity(name, grid, "cell", cellValues, dataType_) {} + +void SparseVolumeGridCellScalarQuantity::createProgram() { // clang-format off program = render::engine->requestShader("GRIDCUBE", render::engine->addMaterialRules(parent.getMaterial(), addScalarRules( parent.addSparseGridShaderRules({ - isNodeQuantity ? "GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR" : "GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR" + "GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR" }) ) ) @@ -54,46 +76,58 @@ void SparseVolumeGridScalarQuantity::createProgram() { // clang-format on parent.setCellGeometryAttributes(*program); - - if (isNodeQuantity) { - for (int c = 0; c < 8; c++) { - program->setAttribute("a_nodeValue" + std::to_string(c), - values.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c])); - } - } else { - program->setAttribute("a_value", values.getRenderAttributeBuffer()); - } - + program->setAttribute("a_value", values.getRenderAttributeBuffer()); program->setTextureFromColormap("t_colormap", cMap.get()); render::engine->setMaterial(*program, parent.getMaterial()); } -void SparseVolumeGridScalarQuantity::refresh() { - program.reset(); - Quantity::refresh(); +void SparseVolumeGridCellScalarQuantity::buildCellInfoGUI(size_t cellInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + ImGui::Text("%g", values.getValue(cellInd)); + ImGui::NextColumn(); } -std::string SparseVolumeGridScalarQuantity::niceName() { return name + " (scalar)"; } - - -// === Cell scalar quantity - -SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& values_, DataType dataType_) - : SparseVolumeGridQuantity(name, grid, true), ScalarQuantity(*this, values_, dataType_), isNodeQuantity(false), - nodeIndicesAreCanonical(true) {} +// ======================================================== +// ========== Node Scalar ========== +// ======================================================== +SparseVolumeGridNodeScalarQuantity::SparseVolumeGridNodeScalarQuantity(std::string name, SparseVolumeGrid& grid, + const std::vector& nodeIndices, + const std::vector& nodeValues, + DataType dataType_) + : SparseVolumeGridScalarQuantity( + name, grid, "node", grid.canonicalizeNodeValueArray(name, nodeIndices, nodeValues, nodeIndicesAreCanonical), + dataType_) {} -// === Node scalar quantity +void SparseVolumeGridNodeScalarQuantity::createProgram() { -SparseVolumeGridScalarQuantity::SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& nodeIndices, - const std::vector& nodeValues, DataType dataType_) - : SparseVolumeGridQuantity(name, grid, true), - ScalarQuantity(*this, parent.canonicalizeNodeValueArray(name, nodeIndices, nodeValues, nodeIndicesAreCanonical), - dataType_), - isNodeQuantity(true) {} + // clang-format off + program = render::engine->requestShader("GRIDCUBE", + render::engine->addMaterialRules(parent.getMaterial(), + addScalarRules( + parent.addSparseGridShaderRules({ + "GRIDCUBE_PROPAGATE_ATTR_NODE_SCALAR" + }) + ) + ) + ); + // clang-format on + parent.setCellGeometryAttributes(*program); + for (int c = 0; c < 8; c++) { + program->setAttribute("a_nodeValue" + std::to_string(c), + values.getIndexedRenderAttributeBuffer(parent.cornerNodeInds[c])); + } + program->setTextureFromColormap("t_colormap", cMap.get()); + render::engine->setMaterial(*program, parent.getMaterial()); +} +void SparseVolumeGridNodeScalarQuantity::buildNodeInfoGUI(size_t nodeInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + ImGui::Text("%g", values.getValue(nodeInd)); + ImGui::NextColumn(); +} } // namespace polyscope From 2660a7d76b4ccaebc954b49ca169b4c07259a503 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Feb 2026 12:31:51 -1000 Subject: [PATCH 15/18] fix includes for Windows --- include/polyscope/sparse_volume_grid.h | 1 + include/polyscope/sparse_volume_grid.ipp | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h index 218839ff..97cf4e4b 100644 --- a/include/polyscope/sparse_volume_grid.h +++ b/include/polyscope/sparse_volume_grid.h @@ -13,6 +13,7 @@ #include "polyscope/sparse_volume_grid_scalar_quantity.h" #include +#include #include namespace polyscope { diff --git a/include/polyscope/sparse_volume_grid.ipp b/include/polyscope/sparse_volume_grid.ipp index a6b93e9b..7fac41d9 100644 --- a/include/polyscope/sparse_volume_grid.ipp +++ b/include/polyscope/sparse_volume_grid.ipp @@ -2,8 +2,6 @@ #pragma once -#include "polyscope/utilities.h" - namespace polyscope { inline uint64_t SparseVolumeGrid::nCells() const { return occupiedCellsData.size(); } From b49092b1343044c38df6f5b29726ea869a0562e1 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Feb 2026 13:46:43 -1000 Subject: [PATCH 16/18] fix node indexing convention --- examples/demo-app/demo_app.cpp | 36 ++++++++++------------------ src/sparse_volume_grid.cpp | 24 +++++++++++++++---- test/src/sparse_volume_grid_test.cpp | 30 +++++++++++++++++++++-- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index ccee878f..afe26351 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -564,7 +564,20 @@ void addSparseVolumeGrid() { } } } + // Gather all unique nodes for the occupied cells + // Node (ci+dx, cj+dy, ck+dz) for dx,dy,dz in {0,1} + std::set> nodeSet; + for (const auto& ci : occupiedCells) { + for (int dx = 0; dx < 2; dx++) { + for (int dy = 0; dy < 2; dy++) { + for (int dz = 0; dz < 2; dz++) { + nodeSet.insert({ci.x + dx, ci.y + dy, ci.z + dz}); + } + } + } + } + std::cout << "adding sparse volume grid with " << occupiedCells.size() << " cells" << std::endl; polyscope::SparseVolumeGrid* psGrid = polyscope::registerSparseVolumeGrid("sparse grid", origin, cellWidth, occupiedCells); @@ -580,18 +593,6 @@ void addSparseVolumeGrid() { // Node scalar: x-coordinate at node positions { - // Gather all unique nodes for the occupied cells - // Node (ci+dx-1, cj+dy-1, ck+dz-1) for dx,dy,dz in {0,1} - std::set> nodeSet; - for (const auto& ci : occupiedCells) { - for (int dx = 0; dx < 2; dx++) { - for (int dy = 0; dy < 2; dy++) { - for (int dz = 0; dz < 2; dz++) { - nodeSet.insert({ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1}); - } - } - } - } std::vector nodeIndices; std::vector nodeValues; @@ -616,17 +617,6 @@ void addSparseVolumeGrid() { // Node color: random colors { - std::set> nodeSet; - for (const auto& ci : occupiedCells) { - for (int dx = 0; dx < 2; dx++) { - for (int dy = 0; dy < 2; dy++) { - for (int dz = 0; dz < 2; dz++) { - nodeSet.insert({ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1}); - } - } - } - } - std::vector nodeIndices; std::vector nodeColors; for (const auto& nodeInds : nodeSet) { diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp index 339b434e..f3b9d4cc 100644 --- a/src/sparse_volume_grid.cpp +++ b/src/sparse_volume_grid.cpp @@ -10,6 +10,7 @@ #include #include +#include namespace polyscope { @@ -113,7 +114,7 @@ void SparseVolumeGrid::computeCornerNodeIndices() { glm::ivec3 ci = occupiedCellsData[i]; for (int dx = 0; dx < 2; dx++) for (int dy = 0; dy < 2; dy++) - for (int dz = 0; dz < 2; dz++) allNodes.push_back(glm::ivec3(ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1)); + for (int dz = 0; dz < 2; dz++) allNodes.push_back(glm::ivec3(ci.x + dx, ci.y + dy, ci.z + dz)); } // Sort and deduplicate to get canonical order @@ -121,7 +122,21 @@ void SparseVolumeGrid::computeCornerNodeIndices() { allNodes.erase(std::unique(allNodes.begin(), allNodes.end()), allNodes.end()); canonicalNodeIndsData = std::move(allNodes); - // Build corner index buffers using binary search + // Build a hashmap from node ivec3 to canonical index + auto ivec3Hash = [](const glm::ivec3& v) { + size_t h = std::hash()(v.x); + h ^= std::hash()(v.y) + 0x9e3779b9 + (h << 6) + (h >> 2); + h ^= std::hash()(v.z) + 0x9e3779b9 + (h << 6) + (h >> 2); + return h; + }; + auto ivec3Equal = [](const glm::ivec3& a, const glm::ivec3& b) { return a == b; }; + std::unordered_map nodeToIndex( + canonicalNodeIndsData.size(), ivec3Hash, ivec3Equal); + for (size_t i = 0; i < canonicalNodeIndsData.size(); i++) { + nodeToIndex[canonicalNodeIndsData[i]] = static_cast(i); + } + + // Build corner index buffers using hashmap lookup for (int c = 0; c < 8; c++) { cornerNodeIndsData[c].resize(n); } @@ -132,9 +147,8 @@ void SparseVolumeGrid::computeCornerNodeIndices() { for (int dy = 0; dy < 2; dy++) { for (int dz = 0; dz < 2; dz++) { int c = dx * 4 + dy * 2 + dz; - glm::ivec3 nodeIjk(ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1); - auto it = std::lower_bound(canonicalNodeIndsData.begin(), canonicalNodeIndsData.end(), nodeIjk, ivec3Less); - cornerNodeIndsData[c][i] = static_cast(it - canonicalNodeIndsData.begin()); + glm::ivec3 nodeIjk(ci.x + dx, ci.y + dy, ci.z + dz); + cornerNodeIndsData[c][i] = nodeToIndex[nodeIjk]; } } } diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp index 7f32e9e1..b26eeb89 100644 --- a/test/src/sparse_volume_grid_test.cpp +++ b/test/src/sparse_volume_grid_test.cpp @@ -49,13 +49,13 @@ SparseGridTestData buildSparseGridTestData(int N = 3) { } // Gather all unique node indices - // Node (ci+dx-1, cj+dy-1, ck+dz-1) for dx,dy,dz in {0,1} + // Node (ci+dx, cj+dy, ck+dz) for dx,dy,dz in {0,1} std::set> nodeSet; for (const auto& ci : d.occupiedCells) { for (int dx = 0; dx < 2; dx++) { for (int dy = 0; dy < 2; dy++) { for (int dz = 0; dz < 2; dz++) { - nodeSet.insert({ci.x + dx - 1, ci.y + dy - 1, ci.z + dz - 1}); + nodeSet.insert({ci.x + dx, ci.y + dy, ci.z + dz}); } } } @@ -130,6 +130,32 @@ TEST_F(PolyscopeTest, SparseVolumeGridPick) { polyscope::removeAllStructures(); } + +TEST_F(PolyscopeTest, SparseVolumeGridNodeIndexingConvention) { + // Verify that cell (i,j,k) has corner nodes at (i+dx, j+dy, k+dz) for dx,dy,dz in {0,1}. + // We register a single cell at (0,0,0), then provide node values at exactly those 8 corners + // in canonical (sorted) order. If the indexing convention is correct, getNodeIndicesAreCanonical() + // should return true. + + std::vector cells = {{0, 0, 0}}; + glm::vec3 origin{0.f, 0.f, 0.f}; + glm::vec3 cellWidth{1.f, 1.f, 1.f}; + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("index test grid", origin, cellWidth, cells); + + // The 8 corner nodes of cell (0,0,0) should be (0,0,0) through (1,1,1), in lexicographic order + std::vector nodeIndices = { + {0, 0, 0}, {0, 0, 1}, {0, 1, 0}, {0, 1, 1}, {1, 0, 0}, {1, 0, 1}, {1, 1, 0}, {1, 1, 1}, + }; + std::vector nodeScalars(8, 1.f); + + auto* q = psGrid->addNodeScalarQuantity("index check", nodeIndices, nodeScalars); + EXPECT_TRUE(q->getNodeIndicesAreCanonical()); + + polyscope::removeAllStructures(); +} + TEST_F(PolyscopeTest, SparseVolumeGridCellScalar) { auto d = buildSparseGridTestData(); From 52103933e27fc401fa3f8267b4de646de308146e Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 16 Feb 2026 13:47:41 -1000 Subject: [PATCH 17/18] run formatter --- include/polyscope/sparse_volume_grid_scalar_quantity.h | 4 ++-- src/render/managed_buffer.cpp | 4 +--- test/include/polyscope_test.h | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/polyscope/sparse_volume_grid_scalar_quantity.h b/include/polyscope/sparse_volume_grid_scalar_quantity.h index d9262a4f..d16f088c 100644 --- a/include/polyscope/sparse_volume_grid_scalar_quantity.h +++ b/include/polyscope/sparse_volume_grid_scalar_quantity.h @@ -50,8 +50,8 @@ class SparseVolumeGridCellScalarQuantity : public SparseVolumeGridScalarQuantity class SparseVolumeGridNodeScalarQuantity : public SparseVolumeGridScalarQuantity { public: SparseVolumeGridNodeScalarQuantity(std::string name, SparseVolumeGrid& grid, - const std::vector& nodeIndices, - const std::vector& nodeValues, DataType dataType); + const std::vector& nodeIndices, const std::vector& nodeValues, + DataType dataType); virtual void createProgram() override; virtual void buildNodeInfoGUI(size_t nodeInd) override; diff --git a/src/render/managed_buffer.cpp b/src/render/managed_buffer.cpp index 723c331d..6da6c267 100644 --- a/src/render/managed_buffer.cpp +++ b/src/render/managed_buffer.cpp @@ -615,8 +615,6 @@ template<> ManagedBufferMap& ManagedBufferMap Date: Mon, 16 Feb 2026 20:05:50 -1000 Subject: [PATCH 18/18] comment fixes --- include/polyscope/sparse_volume_grid.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h index 97cf4e4b..2569dc85 100644 --- a/include/polyscope/sparse_volume_grid.h +++ b/include/polyscope/sparse_volume_grid.h @@ -95,9 +95,9 @@ class SparseVolumeGrid : public Structure { DataType type = DataType::STANDARD); // Node scalar. Indices are _node_ indices; the nodes are a shifted sparse grid offset from the cell enumeration. For - // a cell with indices ijk, its corrners are the nodes with indices (i k j, i+1 j k, ..., i+1 j+1, k+1). Node values - // are passed via a paired set of arrays, giving the node index and node value for each. Node values may be passed in - // any order, and having extra entries is fine too, as long as all required nodes values are present. + // a cell with indices ijk, its corrners are the nodes with indices [(i,j,k), (i,j,k+1), ..., (i+1,j+1,k+1)]. Node + // values are passed via a paired set of arrays, giving the node index and node value for each. Node values may be + // passed in any order, and having extra entries is fine too, as long as all required nodes values are present. template SparseVolumeGridNodeScalarQuantity* addNodeScalarQuantity(std::string name, const TI& nodeIndices, const TV& nodeValues, DataType type = DataType::STANDARD); @@ -107,9 +107,9 @@ class SparseVolumeGrid : public Structure { SparseVolumeGridCellColorQuantity* addCellColorQuantity(std::string name, const T& colors); // Node color. Indices are _node_ indices; the nodes are a shifted sparse grid offset from the cell enumeration. For - // a cell with indices ijk, its corrners are the nodes with indices (i k j, i+1 j k, ..., i+1 j+1, k+1). Node values - // are passed via a paired set of arrays, giving the node index and node value for each. Node values may be passed in - // any order, and having extra entries is fine too, as long as all required nodes values are present. + // a cell with indices ijk, its corrners are the nodes with indices [(i,j,k), (i,j,k+1), ..., (i+1,j+1,k+1)]. Node + // values are passed via a paired set of arrays, giving the node index and node value for each. Node values may be + // passed in any order, and having extra entries is fine too, as long as all required nodes values are present. template SparseVolumeGridNodeColorQuantity* addNodeColorQuantity(std::string name, const TI& nodeIndices, const TC& nodeColors); @@ -125,14 +125,14 @@ class SparseVolumeGrid : public Structure { std::vector addSparseGridShaderRules(std::vector initRules, bool pickOnly = false); void setSparseVolumeGridUniforms(render::ShaderProgram& p, bool pickOnly = false); - // === Getters and setters for visualization settings + // === Getters and setters // Color of the grid cubes SparseVolumeGrid* setColor(glm::vec3 val); glm::vec3 getColor(); - // Width of the edges. Scaled such that 1 is a reasonable weight for visible edges, but values 1 can be used for - // bigger edges. Use 0. to disable. + // Width of the edges. Scaled such that 1 is a reasonable weight for visible edges, but values >1 can be used for + // bigger edges. Use 0. to disable edges. SparseVolumeGrid* setEdgeWidth(double newVal); double getEdgeWidth();