diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index eaed4407..afe26351 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -12,6 +12,7 @@ #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" @@ -19,6 +20,7 @@ #include "polyscope/volume_mesh.h" #include +#include #include #include @@ -544,6 +546,90 @@ void addVolumeGrid() { } +void addSparseVolumeGrid() { + 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 = -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 >= 0.35f && dist <= 1.0f) { + occupiedCells.push_back({i, j, k}); + } + } + } + } + // 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); + + // 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); + } + + // Node scalar: x-coordinate at node positions + { + + std::vector nodeIndices; + std::vector nodeValues; + 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); + } + psGrid->addNodeScalarQuantity("node x-coord", nodeIndices, nodeValues); + } + + // Cell color: random colors + { + std::vector cellColors(occupiedCells.size()); + for (size_t i = 0; i < occupiedCells.size(); i++) { + cellColors[i] = glm::vec3{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}; + } + psGrid->addCellColorQuantity("cell color", cellColors); + } + + // Node color: random colors + { + std::vector nodeIndices; + std::vector nodeColors; + for (const auto& nodeInds : nodeSet) { + int32_t ni, nj, nk; + std::tie(ni, nj, nk) = nodeInds; + nodeIndices.push_back({ni, nj, nk}); + nodeColors.push_back(glm::vec3{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}); + } + psGrid->addNodeColorQuantity("node color", nodeIndices, nodeColors); + } +} + + void loadFloatingImageData(polyscope::CameraView* targetView = nullptr) { // load an image from disk as example data @@ -864,6 +950,10 @@ void callback() { addVolumeGrid(); } + if (ImGui::Button("add sparse volume grid")) { + addSparseVolumeGrid(); + } + // ImPlot // dummy data if (ImGui::TreeNode("ImPlot")) { @@ -911,7 +1001,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/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 13ea05fe..38349f97 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; @@ -155,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; @@ -364,6 +379,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/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 873f83d2..cc4d2d49 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; @@ -113,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; @@ -257,6 +269,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..7e06d358 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; @@ -145,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; @@ -300,6 +312,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/shaders/grid_shaders.h b/include/polyscope/render/opengl/shaders/grid_shaders.h index 225891c2..15175039 100644 --- a/include/polyscope/render/opengl/shaders/grid_shaders.h +++ b/include/polyscope/render/opengl/shaders/grid_shaders.h @@ -20,8 +20,16 @@ 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; +extern const ShaderReplacementRule GRIDCUBE_PLANE_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 diff --git a/include/polyscope/sparse_volume_grid.h b/include/polyscope/sparse_volume_grid.h new file mode 100644 index 00000000..2569dc85 --- /dev/null +++ b/include/polyscope/sparse_volume_grid.h @@ -0,0 +1,232 @@ +// 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 "polyscope/sparse_volume_grid_color_quantity.h" +#include "polyscope/sparse_volume_grid_quantity.h" +#include "polyscope/sparse_volume_grid_scalar_quantity.h" + +#include +#include +#include + +namespace polyscope { + +class SparseVolumeGrid; +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 + + // 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; + + // === Grid info + uint64_t nCells() const; + glm::vec3 getOrigin() const; + 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. Values array must be passed in the same order as initial input cell list. + template + 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,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); + + // Cell color. Values array must be passed in the same order as initial input cell list. + template + 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,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); + + // 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); + std::vector addSparseGridShaderRules(std::vector initRules, bool pickOnly = false); + void setSparseVolumeGridUniforms(render::ShaderProgram& p, bool pickOnly = false); + + // === 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 edges. + 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(); + + // 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; + + // 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; + PersistentValue edgeColor; + PersistentValue material; + PersistentValue cubeSizeFactor; + + // Compute cell positions and GPU indices from occupiedCellsData + void computeCellPositions(); + + // Compute canonical node indices and corner index buffers from occupiedCellsData + void computeCornerNodeIndices(); + + // Picking-related + bool nodesHaveBeenUsed = false; + void buildCellInfoGUI(const SparseVolumeGridPickResult& result); + void buildNodeInfoGUI(const SparseVolumeGridPickResult& result); + + // Drawing related things + std::shared_ptr program; + std::shared_ptr pickProgram; + + // === Helpers + void checkForDuplicateCells(); + size_t findCellFlatIndex(glm::ivec3 cellInd3); + size_t findNodeFlatIndex(glm::ivec3 nodeInd3); + void ensureRenderProgramPrepared(); + void ensurePickProgramPrepared(); + + // Quantity impl methods + 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); + +// 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); + +// 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..7fac41d9 --- /dev/null +++ b/include/polyscope/sparse_volume_grid.ipp @@ -0,0 +1,149 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +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 +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); +} + +// ===================================================== +// ============== 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 +// ===================================================== + +template +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 +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"); + } + return addNodeScalarQuantityImpl(name, standardizeVectorArray(nodeIndices), + standardizeArray(nodeValues), type); +} + +template +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 +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"); + } + 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..f7c657f1 --- /dev/null +++ b/include/polyscope/sparse_volume_grid_color_quantity.h @@ -0,0 +1,62 @@ +// 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: + // Node color constructor + 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; } + +protected: + bool nodeIndicesAreCanonical; // true if user-provided indices matched canonical order exactly (set by constructor) +}; + +} // 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..f5bebbff --- /dev/null +++ b/include/polyscope/sparse_volume_grid_quantity.h @@ -0,0 +1,25 @@ +// 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() {}; + + // 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 +}; + + +} // 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..d16f088c --- /dev/null +++ b/include/polyscope/sparse_volume_grid_scalar_quantity.h @@ -0,0 +1,64 @@ +// 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: + SparseVolumeGridScalarQuantity(std::string name, SparseVolumeGrid& grid, const std::string& definedOn_, + const std::vector& values_, DataType dataType_); + + virtual void draw() override; + virtual void buildCustomUI() override; + virtual void refresh() override; + + 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; } + +protected: + bool nodeIndicesAreCanonical; // true if user-provided indices matched canonical order exactly (set by constructor) +}; + +} // namespace polyscope diff --git a/include/polyscope/types.h b/include/polyscope/types.h index 53c4cebe..f94a4f84 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 @@ -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"}, @@ -212,8 +218,11 @@ enum class ManagedBufferType { Arr2Vec3, Arr3Vec3, Arr4Vec3, - UInt32, Int32, + IVec2, + IVec3, + IVec4, + UInt32, UVec2, UVec3, UVec4 @@ -227,8 +236,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/CMakeLists.txt b/src/CMakeLists.txt index 4953d1bd..8b4eca3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -225,6 +225,11 @@ SET(SRCS volume_grid.cpp volume_grid_scalar_quantity.cpp + # Sparse volume grid + sparse_volume_grid.cpp + sparse_volume_grid_scalar_quantity.cpp + sparse_volume_grid_color_quantity.cpp + # Camera view camera_view.cpp @@ -359,6 +364,11 @@ 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}/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/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 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/managed_buffer.cpp b/src/render/managed_buffer.cpp index 35317b12..6da6c267 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; } @@ -603,26 +615,6 @@ template<> ManagedBufferMap& ManagedBufferMap& 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); } @@ -453,6 +485,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"); }; @@ -846,16 +881,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 +1097,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 +1532,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; @@ -2140,8 +2238,14 @@ 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_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); + 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..608f0133 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); } @@ -675,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"); }; @@ -1318,6 +1354,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 +1607,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 +2080,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; @@ -2607,8 +2721,14 @@ 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_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); + 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/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 b55603ca..0cc479ad 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 sphereCenterView; + out vec3 a_gridCoordToFrag; + flat out ivec3 cellIndToFrag; + out vec3 centerToFrag; ${ GEOM_DECLARATIONS }$ @@ -90,50 +92,59 @@ 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]; - 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 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; - uvec3 nodeInd; - uvec3 cellInd; + 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; 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; 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(); @@ -148,6 +159,7 @@ const ShaderStageSpecification FLEX_GRIDCUBE_FRAG_SHADER = { // uniforms { + {"u_modelView", RenderDataType::Matrix44Float}, }, { }, // attributes @@ -160,15 +172,29 @@ const ShaderStageSpecification FLEX_GRIDCUBE_FRAG_SHADER = { R"( ${ GLSL_VERSION }$ + in vec3 a_gridCoordToFrag; + flat in ivec3 cellIndToFrag; + in vec3 centerToFrag; + 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); + vec3 cellInd3f = vec3(cellIndToFrag); + + // 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 +204,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 +423,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"( @@ -430,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; @@ -462,6 +524,216 @@ 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 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_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 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); + 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"( + in float a_valueToFrag; + )"}, + {"GENERATE_SHADE_VALUE", R"( + float shadeValue = a_valueToFrag; + )"}, + }, + /* uniforms */ {}, + /* attributes */ { + {"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 */ {} +); + +const ShaderReplacementRule GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR ( + /* rule name */ "GRIDCUBE_PROPAGATE_ATTR_NODE_COLOR", + { /* replacement sources */ + {"VERT_DECLARATIONS", R"( + 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_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 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); + 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"( + in vec3 a_colorToFrag; + )"}, + {"GENERATE_SHADE_VALUE", R"( + vec3 shadeColor = a_colorToFrag; + )"}, + }, + /* uniforms */ {}, + /* attributes */ { + {"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/render/templated_buffers.cpp b/src/render/templated_buffers.cpp index c6d374e5..d48b2ac0 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 <> @@ -142,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 <> @@ -243,13 +274,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 <> @@ -365,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); diff --git a/src/sparse_volume_grid.cpp b/src/sparse_volume_grid.cpp new file mode 100644 index 00000000..f3b9d4cc --- /dev/null +++ b/src/sparse_volume_grid.cpp @@ -0,0 +1,691 @@ +// 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" + +#include "imgui.h" + +#include +#include +#include + +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()), + // 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 */}), + 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_), + + // == 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 + 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(); + + 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] = ijk; + } + + cellPositions.markHostBufferUpdated(); + cellIndices.markHostBufferUpdated(); +} + + +void SparseVolumeGrid::ensureHaveCornerNodeIndices() { + if (haveCornerNodeIndices) return; + computeCornerNodeIndices(); + haveCornerNodeIndices = true; +} + +void SparseVolumeGrid::computeCornerNodeIndices() { + size_t n = occupiedCellsData.size(); + + // 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, ci.y + dy, ci.z + dz)); + } + + // 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 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); + } + + 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, ci.y + dy, ci.z + dz); + cornerNodeIndsData[c][i] = nodeToIndex[nodeIjk]; + } + } + } + } + + for (int c = 0; c < 8; c++) { + cornerNodeInds[c].markHostBufferUpdated(); + } +} + + +void SparseVolumeGrid::buildCustomUI() { + ImGui::Text("%llu cells", static_cast(nCells())); + + { // 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(); + } +} + + +void SparseVolumeGrid::buildCustomOptionsUI() { + if (render::buildMaterialOptionsGui(material.get())) { + material.manuallyChanged(); + setMaterial(material.get()); + } + + // 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 SparseVolumeGrid::draw() { + if (!enabled.get()) return; + + if (dominantQuantity == nullptr) { + // if there is no dominant quantity, the structure handles drawing + + // Ensure we have prepared buffers + ensureRenderProgramPrepared(); + + // 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 (!isEnabled()) { + return; + } + + for (auto& x : quantities) { + x.second->drawDelayed(); + } + for (auto& x : floatingQuantities) { + x.second->drawDelayed(); + } +} + + +void SparseVolumeGrid::drawPick() { + if (!isEnabled()) return; + + ensurePickProgramPrepared(); + + // Set program uniforms + setSparseVolumeGridUniforms(*pickProgram, true); + + // 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(), + addSparseGridShaderRules({"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; + + // Request pick indices + size_t pickCount = nCells(); + size_t pickStart = pick::requestPickBufferRange(this, pickCount); + + // clang-format off + pickProgram = render::engine->requestShader( + "GRIDCUBE", + 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()); +} + + +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(); +} + + +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}; + + glm::vec3 localPos = (rawResult.position - origin) / gridCellWidth; + + // Find the cell index + 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 + +SparseVolumeGrid* SparseVolumeGrid::setColor(glm::vec3 val) { + color = val; + requestRedraw(); + return this; +} +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(); + 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(); } + + +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"); + } + } + + if (wantsCullPosition()) { + initRules.push_back("GRIDCUBE_CULLPOS_FROM_CENTER"); + } + + 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, + const std::vector& occupiedCells) { + checkInitialized(); + + SparseVolumeGrid* s = new SparseVolumeGrid(name, origin, gridCellWidth, occupiedCells); + + bool success = registerStructure(s); + if (!success) { + safeDelete(s); + } + + return s; +} + +// === Quantity base class + +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 + +SparseVolumeGridCellScalarQuantity* +SparseVolumeGrid::addCellScalarQuantityImpl(std::string name, const std::vector& data, DataType type) { + checkForQuantityWithNameAndDeleteOrError(name); + SparseVolumeGridCellScalarQuantity* q = new SparseVolumeGridCellScalarQuantity(name, *this, data, type); + addQuantity(q); + return q; +} + +SparseVolumeGridNodeScalarQuantity* +SparseVolumeGrid::addNodeScalarQuantityImpl(std::string name, const std::vector& nodeIndices, + const std::vector& nodeValues, DataType type) { + checkForQuantityWithNameAndDeleteOrError(name); + SparseVolumeGridNodeScalarQuantity* q = + new SparseVolumeGridNodeScalarQuantity(name, *this, nodeIndices, nodeValues, type); + addQuantity(q); + markNodesAsUsed(); + return q; +} + +SparseVolumeGridCellColorQuantity* SparseVolumeGrid::addCellColorQuantityImpl(std::string name, + const std::vector& colors) { + checkForQuantityWithNameAndDeleteOrError(name); + SparseVolumeGridCellColorQuantity* q = new SparseVolumeGridCellColorQuantity(name, *this, colors); + addQuantity(q); + return q; +} + +SparseVolumeGridNodeColorQuantity* +SparseVolumeGrid::addNodeColorQuantityImpl(std::string name, const std::vector& nodeIndices, + const std::vector& nodeColors) { + checkForQuantityWithNameAndDeleteOrError(name); + SparseVolumeGridNodeColorQuantity* q = new SparseVolumeGridNodeColorQuantity(name, *this, nodeIndices, nodeColors); + addQuantity(q); + markNodesAsUsed(); + 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..fcd493f6 --- /dev/null +++ b/src/sparse_volume_grid_color_quantity.cpp @@ -0,0 +1,128 @@ +// 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" + +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; + + if (!program) createProgram(); + + // Set uniforms + parent.setSparseVolumeGridUniforms(*program); + setColorUniforms(*program); + + render::engine->setBackfaceCull(true); + program->draw(); +} + + +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({ + "GRIDCUBE_PROPAGATE_ATTR_CELL_COLOR", + "SHADE_COLOR" + }) + ) + ) + ); + // clang-format on + + parent.setCellGeometryAttributes(*program); + program->setAttribute("a_color", colors.getRenderAttributeBuffer()); + render::engine->setMaterial(*program, parent.getMaterial()); +} + + +void SparseVolumeGridCellColorQuantity::buildCellInfoGUI(size_t cellInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + + 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(); +} + +// ======================================================== +// ========== Node Color ========== +// ======================================================== + +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() { + + // 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 + + 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 new file mode 100644 index 00000000..288b5320 --- /dev/null +++ b/src/sparse_volume_grid_scalar_quantity.cpp @@ -0,0 +1,133 @@ +// 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" + +namespace polyscope { + +// === 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; + + if (!program) createProgram(); + + // Set uniforms + parent.setSparseVolumeGridUniforms(*program); + setScalarUniforms(*program); + + 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::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({ + "GRIDCUBE_PROPAGATE_ATTR_CELL_SCALAR" + }) + ) + ) + ); + // clang-format on + + parent.setCellGeometryAttributes(*program); + program->setAttribute("a_value", values.getRenderAttributeBuffer()); + program->setTextureFromColormap("t_colormap", cMap.get()); + render::engine->setMaterial(*program, parent.getMaterial()); +} + + +void SparseVolumeGridCellScalarQuantity::buildCellInfoGUI(size_t cellInd) { + ImGui::TextUnformatted(name.c_str()); + ImGui::NextColumn(); + ImGui::Text("%g", values.getValue(cellInd)); + ImGui::NextColumn(); +} + +// ======================================================== +// ========== 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_) {} + +void SparseVolumeGridNodeScalarQuantity::createProgram() { + + // 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 diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index 5d3934ac..15529f70 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(); } @@ -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() { @@ -197,14 +199,14 @@ 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"); } } if (wantsCullPosition()) { - initRules.push_back("GRIDCUBE_CULLPOS_FROM_CENTER"); + initRules.push_back("GRIDCUBE_PLANE_CULLPOS_FROM_CENTER"); } return initRules; @@ -402,8 +404,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; } 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..aa6548f9 100644 --- a/test/include/polyscope_test.h +++ b/test/include/polyscope_test.h @@ -14,6 +14,7 @@ #include "polyscope/point_cloud.h" #include "polyscope/polyscope.h" #include "polyscope/simple_triangle_mesh.h" +#include "polyscope/sparse_volume_grid.h" #include "polyscope/surface_mesh.h" #include "polyscope/types.h" #include "polyscope/volume_grid.h" diff --git a/test/src/sparse_volume_grid_test.cpp b/test/src/sparse_volume_grid_test.cpp new file mode 100644 index 00000000..b26eeb89 --- /dev/null +++ b/test/src/sparse_volume_grid_test.cpp @@ -0,0 +1,325 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope_test.h" + +#include +#include + +// ============================================================ +// =============== Sparse volume grid tests +// ============================================================ + +// 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}; + std::vector occupiedCells; + 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, 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, ci.y + dy, ci.z + dz}); + } + } + } + } + + 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", d.origin, d.cellWidth, d.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, SparseVolumeGridEdges) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + psGrid->setEdgeWidth(1.f); + psGrid->setEdgeColor({1.f, 0.f, 0.f}); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + +TEST_F(PolyscopeTest, SparseVolumeGridSlicePlane) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + polyscope::addSlicePlane(); + + polyscope::show(3); + + polyscope::removeAllSlicePlanes(); + 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, 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(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + polyscope::SparseVolumeGridScalarQuantity* q = psGrid->addCellScalarQuantity("cell scalar", d.cellScalars); + q->setEnabled(true); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridNodeScalar) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + polyscope::SparseVolumeGridScalarQuantity* q = + psGrid->addNodeScalarQuantity("node scalar", d.nodeIndices, d.nodeScalars); + q->setEnabled(true); + + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridCellColor) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + polyscope::SparseVolumeGridCellColorQuantity* q = psGrid->addCellColorQuantity("cell color", d.cellColors); + q->setEnabled(true); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + +TEST_F(PolyscopeTest, SparseVolumeGridNodeColor) { + auto d = buildSparseGridTestData(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + polyscope::SparseVolumeGridNodeColorQuantity* q = + psGrid->addNodeColorQuantity("node color", d.nodeIndices, d.nodeColors); + q->setEnabled(true); + + polyscope::show(3); + + polyscope::removeAllStructures(); +} + + +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(); + + polyscope::SparseVolumeGrid* psGrid = + polyscope::registerSparseVolumeGrid("test sparse grid", d.origin, d.cellWidth, d.occupiedCells); + + EXPECT_EQ(psGrid->nCells(), d.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(); +}