diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index afa339b8..7472a9fc 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -807,8 +807,7 @@ void callback() { std::tie(xInd, yInd) = polyscope::view::screenCoordsToBufferInds(screenCoords); glm::vec3 worldRay = polyscope::view::screenCoordsToWorldRay(screenCoords); - glm::vec3 worldPos = polyscope::view::screenCoordsToWorldPosition(screenCoords); - std::pair pickPair = polyscope::pick::pickAtScreenCoords(screenCoords); + polyscope::PickResult pickResult = polyscope::pickAtScreenCoords(screenCoords); std::cout << "Polyscope scene test click " << std::endl; std::cout << " io.MousePos.x: " << io.MousePos.x << " io.MousePos.y: " << io.MousePos.y << std::endl; @@ -817,16 +816,17 @@ void callback() { std::cout << " worldRay: "; polyscope::operator<<(std::cout, worldRay) << std::endl; std::cout << " worldPos: "; - polyscope::operator<<(std::cout, worldPos) << std::endl; - if (pickPair.first == nullptr) { + polyscope::operator<<(std::cout, pickResult.position) << std::endl; + if (pickResult.isHit) { + std::cout << " structure: " << pickResult.structureType << " " << pickResult.structureName + << " local ind: " << pickResult.localIndex << std::endl; + } else { std::cout << " structure: " << "none" << std::endl; - } else { - std::cout << " structure: " << pickPair.first << " element id: " << pickPair.second << std::endl; } // Construct point at click location - polyscope::registerPointCloud("click point", std::vector({worldPos})); + polyscope::registerPointCloud("click point", std::vector({pickResult.position})); // Construct unit-length vector pointing in the direction of the click // (this depends only on the camera parameters, and does not require accessing the depth buffer) diff --git a/include/polyscope/camera_view.h b/include/polyscope/camera_view.h index 7c70e407..5f7ec990 100644 --- a/include/polyscope/camera_view.h +++ b/include/polyscope/camera_view.h @@ -25,6 +25,10 @@ struct QuantityTypeHelper { }; */ +struct CameraViewPickResult { + // currently nothing, just following the same pattern as other structures +}; + class CameraView : public QuantityStructure { public: // === Member functions === @@ -37,7 +41,7 @@ class CameraView : public QuantityStructure { // Build the imgui display virtual void buildCustomUI() override; virtual void buildCustomOptionsUI() override; - virtual void buildPickUI(size_t localPickID) override; + virtual void buildPickUI(const PickResult& result) override; // Standard structure overrides virtual void draw() override; @@ -71,6 +75,9 @@ class CameraView : public QuantityStructure { // Update the current viewer to look through this camer void setViewToThisCamera(bool withFlight = false); + // get data related to picking/selection + CameraViewPickResult interpretPickResult(const PickResult& result); + // === Get/set visualization parameters // Set focal length of the camera. This only effects how it the camera widget is rendered diff --git a/include/polyscope/context.h b/include/polyscope/context.h index 0c591c00..daa71101 100644 --- a/include/polyscope/context.h +++ b/include/polyscope/context.h @@ -2,10 +2,10 @@ #pragma once +#include #include #include - #include #include #include @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -97,6 +98,14 @@ struct Context { glm::vec3 flightTargetViewT, flightInitialViewT; float flightTargetFov, flightInitialFov; + // ====================================================== + // === Picking globals from pick.h / pick.cpp + // ====================================================== + + PickResult currSelectionPickResult; + bool haveSelectionVal = false; + uint64_t nextPickBufferInd = 1; + std::unordered_map> structureRanges; // ====================================================== // === Internal globals from internal.h diff --git a/include/polyscope/curve_network.h b/include/polyscope/curve_network.h index 08043890..8ecc7c4d 100644 --- a/include/polyscope/curve_network.h +++ b/include/polyscope/curve_network.h @@ -36,6 +36,12 @@ struct QuantityTypeHelper { typedef CurveNetworkQuantity type; }; +struct CurveNetworkPickResult { + CurveNetworkElement elementType; // which kind of element did we click + int64_t index; // index of the clicked element + float tEdge = -1; // if the pick is an edge, the t-value in [0,1] along the edge +}; + class CurveNetwork : public QuantityStructure { public: // === Member functions === @@ -48,7 +54,7 @@ class CurveNetwork : public QuantityStructure { // Build the imgui display virtual void buildCustomUI() override; virtual void buildCustomOptionsUI() override; - virtual void buildPickUI(size_t localPickID) override; + virtual void buildPickUI(const PickResult&) override; virtual void draw() override; virtual void drawDelayed() override; @@ -127,6 +133,9 @@ class CurveNetwork : public QuantityStructure { template void updateNodePositions2D(const V& newPositions); + // get data related to picking/selection + CurveNetworkPickResult interpretPickResult(const PickResult& result); + // === Get/set visualization parameters // set the base color of the points @@ -183,8 +192,8 @@ class CurveNetwork : public QuantityStructure { float computeRadiusMultiplierUniform(); // Pick helpers - void buildNodePickUI(size_t nodeInd); - void buildEdgePickUI(size_t edgeInd); + void buildNodePickUI(const CurveNetworkPickResult& result); + void buildEdgePickUI(const CurveNetworkPickResult& result); // === Quantity adder implementations // clang-format off diff --git a/include/polyscope/elementary_geometry.h b/include/polyscope/elementary_geometry.h new file mode 100644 index 00000000..52bc9e02 --- /dev/null +++ b/include/polyscope/elementary_geometry.h @@ -0,0 +1,21 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include +#include + +#include + +namespace polyscope { + +// Compute t \in [0,1] for a point along hte line from lineStart -- lineEnd +float computeTValAlongLine(glm::vec3 queryP, glm::vec3 lineStart, glm::vec3 lineEnd); + +// Project a point onto a plane. planeNormal must be unit +glm::vec3 projectToPlane(glm::vec3 queryP, glm::vec3 planeNormal, glm::vec3 pointOnPlane); + +// Compute the signed area of triangle ABC which lies in the plane give by normal +float signedTriangleArea(glm::vec3 normal, glm::vec3 pA, glm::vec3 pB, glm::vec3 pC); + +} \ No newline at end of file diff --git a/include/polyscope/floating_quantity_structure.h b/include/polyscope/floating_quantity_structure.h index c23e03f0..c678dc69 100644 --- a/include/polyscope/floating_quantity_structure.h +++ b/include/polyscope/floating_quantity_structure.h @@ -44,7 +44,7 @@ class FloatingQuantityStructure : public QuantityStructure persistentCache_BackFacePolicy; extern PersistentCache persistentCache_MeshNormalType; extern PersistentCache persistentCache_FilterMode; extern PersistentCache persistentCache_IsolineStyle; +extern PersistentCache persistentCache_MeshSelectionMode; template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_double; } template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_float; } @@ -159,6 +160,7 @@ template<> inline PersistentCache& getPersistentCacheR template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_MeshNormalType; } template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_FilterMode; } template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_IsolineStyle; } +template<> inline PersistentCache& getPersistentCacheRef() { return persistentCache_MeshSelectionMode; } } // clang-format on diff --git a/include/polyscope/pick.h b/include/polyscope/pick.h index 2facd906..536e2951 100644 --- a/include/polyscope/pick.h +++ b/include/polyscope/pick.h @@ -2,45 +2,76 @@ #pragma once -#include "polyscope/structure.h" - #include +#include #include -namespace polyscope { -namespace pick { +#include "polyscope/utilities.h" +#include "polyscope/weak_handle.h" +namespace polyscope { -// == Set up picking -// Called by a structure to figure out what data it should render to the pick buffer. -// Request 'count' contiguous indices for drawing a pick buffer. The return value is the start of the range. -size_t requestPickBufferRange(Structure* requestingStructure, size_t count); - +// Forward decls +class Structure; // == Main query -// Get the structure which was clicked on (nullptr if none), and the pick ID in local indices for that structure (such -// that 0 is the first index as returned from requestPickBufferRange()) -std::pair pickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates -std::pair pickAtBufferCoords(int xPos, int yPos); // takes indices into the buffer -std::pair evaluatePickQuery(int xPos, int yPos); // old, badly named. takes buffer coordinates. + +// Pick queries test a screen location in the rendered viewport, and return a variety of info about what is underneath +// the pixel at that point, including what structure is under the cursor, and the scene depth and color. +// +// This information can be fed into structure-specific functions like SurfaceMesh::interpretPick(PickResult) to get +// structure-specific info, like which vertex/face was clicked on. + +// Return type for pick queries +struct PickResult { + bool isHit = false; + Structure* structure = nullptr; + WeakHandle structureHandle; // same as .structure, but with lifetime tracking + std::string structureType = ""; + std::string structureName = ""; + glm::vec2 screenCoords; + glm::ivec2 bufferInds; + glm::vec3 position; + float depth; + uint64_t localIndex = INVALID_IND_64; +}; + +// Query functions to evaluate a pick. +// Internally, these do a render pass to populate relevant information, then query the resulting buffers. +PickResult pickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates +PickResult pickAtBufferInds(glm::ivec2 bufferInds); // takes indices into render buffer // == Stateful picking: track and update a current selection -// Get/Set the "selected" item, if there is one (output has same meaning as evaluatePickQuery()); -std::pair getSelection(); -void setSelection(std::pair newPick); +// Get/Set the "selected" item, if there is one +PickResult getSelection(); +void setSelection(PickResult newPick); void resetSelection(); bool haveSelection(); void resetSelectionIfStructure(Structure* s); // If something from this structure is selected, clear the selection // (useful if a structure is being deleted) +namespace pick { + +// Old, deprecated picking API. Use the above functions instead. +// Get the structure which was clicked on (nullptr if none), and the pick ID in local indices for that structure (such +// that 0 is the first index as returned from requestPickBufferRange()) +std::pair pickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates +std::pair pickAtBufferCoords(int xPos, int yPos); // takes indices into the buffer +std::pair evaluatePickQuery(int xPos, int yPos); // old, badly named. takes buffer coordinates. + // == Helpers +// Set up picking (internal) +// Called by a structure to figure out what data it should render to the pick buffer. +// Request 'count' contiguous indices for drawing a pick buffer. The return value is the start of the range. +uint64_t requestPickBufferRange(Structure* requestingStructure, uint64_t count); + // Convert between global pick indexing for the whole program, and local per-structure pick indexing -std::pair globalIndexToLocal(size_t globalInd); -size_t localIndexToGlobal(std::pair localPick); +std::pair globalIndexToLocal(uint64_t globalInd); +uint64_t localIndexToGlobal(std::pair localPick); // Convert indices to float3 color and back // Structures will want to use these to fill their pick buffers diff --git a/include/polyscope/point_cloud.h b/include/polyscope/point_cloud.h index 3627d460..c0bc61e2 100644 --- a/include/polyscope/point_cloud.h +++ b/include/polyscope/point_cloud.h @@ -5,6 +5,7 @@ #include "polyscope/affine_remapper.h" #include "polyscope/color_management.h" #include "polyscope/persistent_value.h" +#include "polyscope/pick.h" #include "polyscope/point_cloud_quantity.h" #include "polyscope/polyscope.h" #include "polyscope/render/engine.h" @@ -37,6 +38,10 @@ struct QuantityTypeHelper { typedef PointCloudQuantity type; }; +struct PointCloudPickResult { + int64_t index; +}; + class PointCloud : public QuantityStructure { public: // === Member functions === @@ -49,7 +54,7 @@ class PointCloud : public QuantityStructure { // Build the imgui display virtual void buildCustomUI() override; virtual void buildCustomOptionsUI() override; - virtual void buildPickUI(size_t localPickID) override; + virtual void buildPickUI(const PickResult& result) override; // Standard structure overrides virtual void draw() override; @@ -116,6 +121,9 @@ class PointCloud : public QuantityStructure { size_t nPoints(); glm::vec3 getPointPosition(size_t iPt); + // get data related to picking/selection + PointCloudPickResult interpretPickResult(const PickResult& result); + // Misc data static const std::string structureTypeName; @@ -193,7 +201,6 @@ class PointCloud : public QuantityStructure { PointCloudScalarQuantity& resolveTransparencyQuantity(); // helper }; - // Shorthand to add a point cloud to polyscope template PointCloud* registerPointCloud(std::string name, const T& points); diff --git a/include/polyscope/polyscope.h b/include/polyscope/polyscope.h index 25f1f29d..438d000f 100644 --- a/include/polyscope/polyscope.h +++ b/include/polyscope/polyscope.h @@ -129,6 +129,10 @@ Structure* getStructure(std::string type, std::string name = ""); // True if such a structure exists bool hasStructure(std::string type, std::string name = ""); +// Look up the string type and name for a structure from its pointer +// (performs a naive search over all structures for now, use sparingly) +std::tuple lookUpStructure(Structure* structure); + // De-register a structure, of any type. Also removes any quantities associated with the structure void removeStructure(Structure* structure, bool errorIfAbsent = false); void removeStructure(std::string type, std::string name, bool errorIfAbsent = false); diff --git a/include/polyscope/render/engine.h b/include/polyscope/render/engine.h index c37d3f86..3d423f25 100644 --- a/include/polyscope/render/engine.h +++ b/include/polyscope/render/engine.h @@ -584,7 +584,7 @@ class Engine { TransparencyMode getTransparencyMode(); bool transparencyEnabled(); virtual void applyTransparencySettings() = 0; - void addSlicePlane(std::string uniquePostfix); + void addSlicePlane(std::string uniquePostfix); // TODO move slice planes out of the engine void removeSlicePlane(std::string uniquePostfix); bool slicePlanesEnabled(); // true if there is at least one slice plane in the scene virtual void setFrontFaceCCW(bool newVal) = 0; // true if CCW triangles are considered front-facing; false otherwise diff --git a/include/polyscope/simple_triangle_mesh.h b/include/polyscope/simple_triangle_mesh.h index b2ae36f2..1cf0cf5b 100644 --- a/include/polyscope/simple_triangle_mesh.h +++ b/include/polyscope/simple_triangle_mesh.h @@ -24,6 +24,11 @@ class SimpleTriangleMesh; // typedef SimpleTriangleMeshQuantity type; // }; + +struct SimpleTriangleMeshPickResult { + // this does nothing for now, just matching pattern from other structures +}; + class SimpleTriangleMesh : public QuantityStructure { public: // === Member functions === @@ -36,7 +41,7 @@ class SimpleTriangleMesh : public QuantityStructure { // Build the imgui display virtual void buildCustomUI() override; virtual void buildCustomOptionsUI() override; - virtual void buildPickUI(size_t localPickID) override; + virtual void buildPickUI(const PickResult& result) override; // Standard structure overrides virtual void draw() override; @@ -63,6 +68,9 @@ class SimpleTriangleMesh : public QuantityStructure { // Misc data static const std::string structureTypeName; + // get data related to picking/selection + SimpleTriangleMeshPickResult interpretPickResult(const PickResult& result); + // === Get/set visualization parameters // set the base color of the surface diff --git a/include/polyscope/structure.h b/include/polyscope/structure.h index 3fd141e9..0ce4bdea 100644 --- a/include/polyscope/structure.h +++ b/include/polyscope/structure.h @@ -10,6 +10,7 @@ #include "glm/glm.hpp" #include "polyscope/persistent_value.h" +#include "polyscope/pick.h" #include "polyscope/render/engine.h" #include "polyscope/transformation_gizmo.h" #include "polyscope/weak_handle.h" @@ -54,7 +55,7 @@ class Structure : public render::ManagedBufferRegistry, public virtual WeakRefer virtual void buildStructureOptionsUI(); // overridden by structure quantities to add to the options menu virtual void buildQuantitiesUI(); // build quantities, if they exist. Overridden by QuantityStructure. virtual void buildSharedStructureUI(); // Draw any UI elements shared between all instances of the structure - virtual void buildPickUI(size_t localPickID) = 0; // Draw pick UI elements when index localPickID is selected + virtual void buildPickUI(const PickResult& result) = 0; // Draw pick UI elements based on a selection result // = Identifying data const std::string name; // should be unique amongst registered structures with this type diff --git a/include/polyscope/surface_mesh.h b/include/polyscope/surface_mesh.h index fbb19ab7..e0834a0e 100644 --- a/include/polyscope/surface_mesh.h +++ b/include/polyscope/surface_mesh.h @@ -51,6 +51,11 @@ struct QuantityTypeHelper { typedef SurfaceMeshQuantity type; }; +struct SurfaceMeshPickResult { + MeshElement elementType; // which kind of element did we click + int64_t index; // index of the clicked element + glm::vec3 baryCoords = glm::vec3{-1., -1., -1}; // coordinates in face, populated only for triangular face picks +}; // === The grand surface mesh class @@ -75,7 +80,7 @@ class SurfaceMesh : public QuantityStructure { // Build the imgui display virtual void buildCustomUI() override; virtual void buildCustomOptionsUI() override; - virtual void buildPickUI(size_t localPickID) override; + virtual void buildPickUI(const PickResult&) override; // Render the the structure on screen virtual void draw() override; @@ -166,8 +171,10 @@ class SurfaceMesh : public QuantityStructure { // special quantity-related methods SurfaceParameterizationQuantity* getParameterization(std::string name); + // get data related to picking/selection + SurfaceMeshPickResult interpretPickResult(const PickResult& result); - // === Make a one-time selection + // Make a one-time selection long long int selectVertex(); // === Mutate @@ -283,6 +290,10 @@ class SurfaceMesh : public QuantityStructure { SurfaceMesh* setShadeStyle(MeshShadeStyle newStyle); MeshShadeStyle getShadeStyle(); + // Selection mode + SurfaceMesh* setSelectionMode(MeshSelectionMode newMode); + MeshSelectionMode getSelectionMode(); + // == Rendering helpers used by quantities // void fillGeometryBuffers(render::ShaderProgram& p); @@ -354,12 +365,12 @@ class SurfaceMesh : public QuantityStructure { PersistentValue backFacePolicy; PersistentValue backFaceColor; PersistentValue shadeStyle; + PersistentValue selectionMode; // Do setup work related to drawing, including allocating openGL data void prepare(); void preparePick(); - /// == Compute indices & geometry data void computeTriangleCornerInds(); void computeTriangleAllVertexInds(); @@ -381,11 +392,11 @@ class SurfaceMesh : public QuantityStructure { // Within each set, uses the implicit ordering from the mesh data structure // These starts are LOCAL indices, indexing elements only with the mesh size_t facePickIndStart, edgePickIndStart, halfedgePickIndStart, cornerPickIndStart; - void buildVertexInfoGui(size_t vInd); - void buildFaceInfoGui(size_t fInd); - void buildEdgeInfoGui(size_t eInd); - void buildHalfedgeInfoGui(size_t heInd); - void buildCornerInfoGui(size_t cInd); + void buildVertexInfoGui(const SurfaceMeshPickResult& result); + void buildFaceInfoGui(const SurfaceMeshPickResult& result); + void buildEdgeInfoGui(const SurfaceMeshPickResult& result); + void buildHalfedgeInfoGui(const SurfaceMeshPickResult& result); + void buildCornerInfoGui(const SurfaceMeshPickResult& result); // Manage per-element transparency // which (scalar) quantity to set point size from @@ -398,6 +409,7 @@ class SurfaceMesh : public QuantityStructure { std::shared_ptr program; std::shared_ptr pickProgram; + bool usingSimplePick = false; // === Helper functions diff --git a/include/polyscope/types.h b/include/polyscope/types.h index 6280d363..894ca864 100644 --- a/include/polyscope/types.h +++ b/include/polyscope/types.h @@ -19,8 +19,11 @@ enum class BackFacePolicy { Identical, Different, Custom, Cull }; enum class PointRenderMode { Sphere = 0, Quad }; enum class MeshElement { VERTEX = 0, FACE, EDGE, HALFEDGE, CORNER }; enum class MeshShadeStyle { Smooth = 0, Flat, TriFlat }; +enum class MeshSelectionMode { Auto = 0, VerticesOnly, FacesOnly }; +enum class CurveNetworkElement { NODE = 0, EDGE }; enum class VolumeMeshElement { VERTEX = 0, EDGE, FACE, CELL }; enum class VolumeCellType { TET = 0, HEX }; +enum class VolumeGridElement { NODE = 0, CELL }; enum class IsolineStyle { Stripe = 0, Contour }; enum class ImplicitRenderMode { SphereMarch, FixedStep }; diff --git a/include/polyscope/view.h b/include/polyscope/view.h index 985a9606..b6654d96 100644 --- a/include/polyscope/view.h +++ b/include/polyscope/view.h @@ -137,8 +137,8 @@ bool getWindowResizable(); // Get world geometry corresponding to a screen pixel (e.g. from a mouse click) glm::vec3 screenCoordsToWorldRay(glm::vec2 screenCoords); -glm::vec3 bufferCoordsToWorldRay(int xPos, int yPos); -glm::vec3 screenCoordsToWorldPosition(glm::vec2 screenCoords); // queries the depth buffer to get full position +glm::vec3 bufferIndsToWorldRay(glm::ivec2 bufferInds); +glm::vec3 screenCoordsAndDepthToWorldPosition(glm::vec2 screenCoords, float clipDepth); // Get and set camera from json string std::string getViewAsJson(); @@ -150,6 +150,9 @@ void setCameraFromJson(std::string jsonData, bool flyTo); std::string to_string(ProjectionMode mode); std::string to_string(NavigateStyle style); std::tuple screenCoordsToBufferInds(glm::vec2 screenCoords); +glm::ivec2 screenCoordsToBufferIndsVec(glm::vec2 screenCoords); +glm::vec2 bufferIndsToScreenCoords(int xPos, int yPos); +glm::vec2 bufferIndsToScreenCoords(glm::ivec2 bufferInds); // == Internal helpers. Should probably not be called in user code. @@ -175,6 +178,9 @@ void processClipPlaneShift(double amount); void processZoom(double amount); void processKeyboardNavigation(ImGuiIO& io); +// deprecated, bad names, see variants above +glm::vec3 bufferCoordsToWorldRay(glm::vec2 bufferCoords); + } // namespace view } // namespace polyscope diff --git a/include/polyscope/volume_grid.h b/include/polyscope/volume_grid.h index 9c1b7071..8e12dfe8 100644 --- a/include/polyscope/volume_grid.h +++ b/include/polyscope/volume_grid.h @@ -26,6 +26,10 @@ struct QuantityTypeHelper { typedef VolumeGridQuantity type; }; +struct VolumeGridPickResult { + VolumeGridElement elementType; // which kind of element did we click + int64_t index; // index of the clicked element +}; class VolumeGrid : public QuantityStructure { public: @@ -45,7 +49,7 @@ class VolumeGrid : public QuantityStructure { // Build the imgui display virtual void buildCustomUI() override; virtual void buildCustomOptionsUI() override; - virtual void buildPickUI(size_t localPickID) override; + virtual void buildPickUI(const PickResult& result) override; // Misc data static const std::string structureTypeName; @@ -117,6 +121,9 @@ class VolumeGrid : public QuantityStructure { // force the grid to act as if the specified elements are in use (aka enable them for picking, etc) void markNodesAsUsed(); void markCellsAsUsed(); + + // get data related to picking/selection + VolumeGridPickResult interpretPickResult(const PickResult& result); // === Getters and setters for visualization settings @@ -169,8 +176,8 @@ class VolumeGrid : public QuantityStructure { // These starts are LOCAL indices, indexing elements only with the mesh size_t globalPickConstant = INVALID_IND_64; glm::vec3 pickColor; - void buildNodeInfoGUI(size_t vInd); - void buildCellInfoGUI(size_t cInd); + void buildNodeInfoGUI(const VolumeGridPickResult& result); + void buildCellInfoGUI(const VolumeGridPickResult& result); bool nodesHaveBeenUsed = false; bool cellsHaveBeenUsed = false; diff --git a/include/polyscope/volume_mesh.h b/include/polyscope/volume_mesh.h index 6dfb760b..83e4d30a 100644 --- a/include/polyscope/volume_mesh.h +++ b/include/polyscope/volume_mesh.h @@ -34,6 +34,10 @@ struct QuantityTypeHelper { typedef VolumeMeshQuantity type; }; +struct VolumeMeshPickResult { + VolumeMeshElement elementType; // which kind of element did we click + int64_t index; // index of the clicked element +}; // === The grand volume mesh class @@ -52,7 +56,7 @@ class VolumeMesh : public QuantityStructure { // Build the imgui display virtual void buildCustomUI() override; virtual void buildCustomOptionsUI() override; - virtual void buildPickUI(size_t localPickID) override; + virtual void buildPickUI(const PickResult& result) override; // Render the the structure on screen virtual void draw() override; @@ -139,6 +143,9 @@ class VolumeMesh : public QuantityStructure { void computeTets(); // fills tet buffer void ensureHaveTets(); // ensure the tet buffer is filled (but don't rebuild if already done) + // get data related to picking/selection + VolumeMeshPickResult interpretPickResult(const PickResult& result); + // === Member variables === static const std::string structureTypeName; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e1cd2c3..e5a0679b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -190,6 +190,7 @@ SET(SRCS slice_plane.cpp weak_handle.cpp marching_cubes.cpp + elementary_geometry.cpp ## Structures @@ -286,6 +287,7 @@ SET(HEADERS ${INCLUDE_ROOT}/curve_network_vector_quantity.h ${INCLUDE_ROOT}/disjoint_sets.h ${INCLUDE_ROOT}/depth_render_image_quantity.h + ${INCLUDE_ROOT}/elementary_geometry.h ${INCLUDE_ROOT}/file_helpers.h ${INCLUDE_ROOT}/floating_quantity_structure.h ${INCLUDE_ROOT}/floating_quantity.h diff --git a/src/camera_view.cpp b/src/camera_view.cpp index 5f3c708b..7b3bbeda 100644 --- a/src/camera_view.cpp +++ b/src/camera_view.cpp @@ -125,6 +125,7 @@ void CameraView::drawPick() { // Set uniforms setStructureUniforms(*pickFrameProgram); + pickFrameProgram->setUniform("u_vertPickRadius", 0.); pickFrameProgram->draw(); } @@ -323,7 +324,25 @@ void CameraView::geometryChanged() { QuantityStructure::refresh(); } -void CameraView::buildPickUI(size_t localPickID) { +CameraViewPickResult CameraView::interpretPickResult(const PickResult& rawResult) { + + if (rawResult.structure != this) { + // caller must ensure that the PickResult belongs to this structure + // by checking the structure pointer or name + exception("called interpretPickResult(), but the pick result is not from this structure"); + } + + CameraViewPickResult result; + + // currently nothing + + return result; +} + +void CameraView::buildPickUI(const PickResult& rawResult) { + + CameraViewPickResult result = interpretPickResult(rawResult); + ImGui::Text("center: %s", to_string(params.getPosition()).c_str()); ImGui::Text("look dir: %s", to_string(params.getLookDir()).c_str()); ImGui::Text("up dir: %s", to_string(params.getUpDir()).c_str()); @@ -338,10 +357,11 @@ void CameraView::buildPickUI(size_t localPickID) { ImGui::Indent(20.); // Build GUI to show the quantities + // TODO this is inconsistently supported for other structures ImGui::Columns(2); ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() / 3); for (auto& x : quantities) { - x.second->buildPickUI(localPickID); + x.second->buildPickUI(rawResult.localIndex); } ImGui::Indent(-20.); diff --git a/src/curve_network.cpp b/src/curve_network.cpp index f9b917b5..5b9942cd 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -2,6 +2,7 @@ #include "polyscope/curve_network.h" +#include "polyscope/elementary_geometry.h" #include "polyscope/pick.h" #include "polyscope/polyscope.h" #include "polyscope/render/engine.h" @@ -342,18 +343,24 @@ void CurveNetwork::refresh() { void CurveNetwork::recomputeGeometryIfPopulated() { edgeCenters.recomputeIfPopulated(); } -void CurveNetwork::buildPickUI(size_t localPickID) { +void CurveNetwork::buildPickUI(const PickResult& rawResult) { - if (localPickID < nNodes()) { - buildNodePickUI(localPickID); - } else if (localPickID < nNodes() + nEdges()) { - buildEdgePickUI(localPickID - nNodes()); - } else { - exception("Bad pick index in curve network"); + CurveNetworkPickResult result = interpretPickResult(rawResult); + + switch (result.elementType) { + case CurveNetworkElement::NODE: { + buildNodePickUI(result); + break; + } + case CurveNetworkElement::EDGE: { + buildEdgePickUI(result); + break; } + }; } -void CurveNetwork::buildNodePickUI(size_t nodeInd) { +void CurveNetwork::buildNodePickUI(const CurveNetworkPickResult& result) { + int32_t nodeInd = result.index; ImGui::TextUnformatted(("node #" + std::to_string(nodeInd) + " ").c_str()); ImGui::SameLine(); @@ -374,12 +381,14 @@ void CurveNetwork::buildNodePickUI(size_t nodeInd) { ImGui::Indent(-20.); } -void CurveNetwork::buildEdgePickUI(size_t edgeInd) { +void CurveNetwork::buildEdgePickUI(const CurveNetworkPickResult& result) { + int32_t edgeInd = result.index; + ImGui::TextUnformatted(("edge #" + std::to_string(edgeInd) + " ").c_str()); ImGui::SameLine(); - size_t n0 = edgeTailInds.getValue(edgeInd); - size_t n1 = edgeTipInds.getValue(edgeInd); - ImGui::TextUnformatted((" " + std::to_string(n0) + " -- " + std::to_string(n1)).c_str()); + int32_t n0 = edgeTailInds.getValue(edgeInd); + int32_t n1 = edgeTipInds.getValue(edgeInd); + ImGui::Text(" %d -- %d t_select = %.4f", n0, n1, result.tEdge); ImGui::Spacing(); ImGui::Spacing(); @@ -457,6 +466,36 @@ void CurveNetwork::updateObjectSpaceBounds() { objectSpaceLengthScale = 2 * std::sqrt(lengthScale); } +CurveNetworkPickResult CurveNetwork::interpretPickResult(const PickResult& rawResult) { + + if (rawResult.structure != this) { + // caller must ensure that the PickResult belongs to this structure + // by checking the structure pointer or name + exception("called interpretPickResult(), but the pick result is not from this structure"); + } + + CurveNetworkPickResult result; + + if (rawResult.localIndex < nNodes()) { + result.elementType = CurveNetworkElement::NODE; + result.index = rawResult.localIndex; + } else if (rawResult.localIndex < nNodes() + nEdges()) { + result.elementType = CurveNetworkElement::EDGE; + result.index = rawResult.localIndex - nNodes(); + + // compute the t \in [0,1] along the edge + int32_t iStart = edgeTailInds.getValue(result.index); + int32_t iEnd = edgeTipInds.getValue(result.index); + glm::vec3 pStart = nodePositions.getValue(iStart); + glm::vec3 pEnd = nodePositions.getValue(iEnd); + result.tEdge = computeTValAlongLine(rawResult.position, pStart, pEnd); + } else { + exception("Bad pick index in curve network"); + } + + return result; +} + CurveNetwork* CurveNetwork::setColor(glm::vec3 newVal) { color = newVal; polyscope::requestRedraw(); diff --git a/src/elementary_geometry.cpp b/src/elementary_geometry.cpp new file mode 100644 index 00000000..0ae03717 --- /dev/null +++ b/src/elementary_geometry.cpp @@ -0,0 +1,35 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#include "polyscope/elementary_geometry.h" + + +#include +#include + +#include + +namespace polyscope { + +float computeTValAlongLine(glm::vec3 queryP, glm::vec3 lineStart, glm::vec3 lineEnd) { + glm::vec3 lineVec = lineEnd - lineStart; + glm::vec3 queryVec = queryP - lineStart; + float len2 = glm::length2(lineVec); + float t = glm::dot(queryVec, lineVec) / len2; + t = glm::clamp(t, 0.f, 1.f); + return t; +} + +glm::vec3 projectToPlane(glm::vec3 queryP, glm::vec3 planeNormal, glm::vec3 pointOnPlane) { + glm::vec3 pVec = queryP - pointOnPlane; + glm::vec3 pVecOrtho = glm::dot(pVec, planeNormal) * planeNormal; + return queryP - pVecOrtho; +} + +float signedTriangleArea(glm::vec3 normal, glm::vec3 pA, glm::vec3 pB, glm::vec3 pC) { + glm::vec3 cross = glm::cross(pB - pA, pC - pA); + float sign = glm::sign(glm::dot(normal, cross)); + float area = glm::length(cross) / 2.; + return sign * area; +} + +} // namespace polyscope \ No newline at end of file diff --git a/src/floating_quantity_structure.cpp b/src/floating_quantity_structure.cpp index 60950d1b..89d0b9d6 100644 --- a/src/floating_quantity_structure.cpp +++ b/src/floating_quantity_structure.cpp @@ -66,7 +66,7 @@ void FloatingQuantityStructure::buildUI() { } -void FloatingQuantityStructure::buildPickUI(size_t localPickID) {} +void FloatingQuantityStructure::buildPickUI(const PickResult& result) {}; // since hasExtents is false, the length scale and bbox value should never be used bool FloatingQuantityStructure::hasExtents() { return false; } diff --git a/src/persistent_value.cpp b/src/persistent_value.cpp index 18b10c82..6476bb36 100644 --- a/src/persistent_value.cpp +++ b/src/persistent_value.cpp @@ -22,6 +22,7 @@ PersistentCache persistentCache_BackFacePolicy; PersistentCache persistentCache_MeshNormalType; PersistentCache persistentCache_FilterMode; PersistentCache persistentCache_IsolineStyle; +PersistentCache persistentCache_MeshSelectionMode; // clang-format on } // namespace detail } // namespace polyscope diff --git a/src/pick.cpp b/src/pick.cpp index 2e45eed7..b7ae2f40 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -9,26 +9,93 @@ #include namespace polyscope { + +PickResult pickAtScreenCoords(glm::vec2 screenCoords) { + int xInd, yInd; + glm::ivec2 bufferInds = view::screenCoordsToBufferIndsVec(screenCoords); + return pickAtBufferInds(bufferInds); +} + +PickResult pickAtBufferInds(glm::ivec2 bufferInds) { + PickResult result; + + // Query the pick buffer + // (this necessarily renders to pickFrameBuffer) + std::pair rawPickResult = pick::pickAtBufferCoords(bufferInds.x, bufferInds.y); + + // Query the depth buffer populated above + render::FrameBuffer* pickFramebuffer = render::engine->pickFramebuffer.get(); + float clipDepth = pickFramebuffer->readDepth(bufferInds.x, view::bufferHeight - bufferInds.y); + + // Transcribe result into return tuple + result.structure = rawPickResult.first; + result.bufferInds = bufferInds; + result.screenCoords = view::bufferIndsToScreenCoords(bufferInds); + result.position = view::screenCoordsAndDepthToWorldPosition(result.screenCoords, clipDepth); + result.depth = glm::length(result.position - view::getCameraWorldPosition()); + + if (rawPickResult.first == nullptr) { + result.isHit = false; + result.structureType = ""; + result.structureName = ""; + result.localIndex = INVALID_IND_64; + } else { + result.structureHandle = result.structure->getWeakHandle(); + result.isHit = true; + std::tuple lookupResult = lookUpStructure(rawPickResult.first); + result.structureType = std::get<0>(lookupResult); + result.structureName = std::get<1>(lookupResult); + result.localIndex = rawPickResult.second; + } + + return result; +} + +// == Manage stateful picking + +void resetSelection() { + state::globalContext.haveSelectionVal = false; + state::globalContext.currSelectionPickResult = PickResult(); +} + +bool haveSelection() { return state::globalContext.haveSelectionVal; } + +void resetSelectionIfStructure(Structure* s) { + if (state::globalContext.haveSelectionVal && state::globalContext.currSelectionPickResult.structure == s) { + resetSelection(); + } +} + +PickResult getSelection() { return state::globalContext.currSelectionPickResult; } + +void setSelection(PickResult newPick) { + if (!newPick.isHit) { + resetSelection(); + } else { + state::globalContext.haveSelectionVal = true; + state::globalContext.currSelectionPickResult = newPick; + } +} + + namespace pick { -size_t currLocalPickInd = 0; -Structure* currPickStructure = nullptr; -bool haveSelectionVal = false; +PickResult& currSelectionPickResult = state::globalContext.currSelectionPickResult; +bool& haveSelectionVal = state::globalContext.haveSelectionVal; // The next pick index that a structure can use to identify its elements // (get it by calling request pickBufferRange()) -size_t nextPickBufferInd = 1; // 0 reserved for "none" - // +uint64_t& nextPickBufferInd = state::globalContext.nextPickBufferInd; // 0 reserved for "none" + // Track which ranges have been allocated to which structures -// std::vector> structureRanges; -std::unordered_map> structureRanges; +std::unordered_map> structureRanges = state::globalContext.structureRanges; // == Set up picking -size_t requestPickBufferRange(Structure* requestingStructure, size_t count) { +uint64_t requestPickBufferRange(Structure* requestingStructure, uint64_t count) { // Check if we can satisfy the request - size_t maxPickInd = std::numeric_limits::max(); + uint64_t maxPickInd = std::numeric_limits::max(); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshift-count-overflow" if (bitsForPickPacking < 22) { @@ -44,49 +111,15 @@ size_t requestPickBufferRange(Structure* requestingStructure, size_t count) { "enumerating structure elements for pick buffer.)"); } - size_t ret = nextPickBufferInd; + uint64_t ret = nextPickBufferInd; nextPickBufferInd += count; structureRanges[requestingStructure] = std::make_tuple(ret, nextPickBufferInd); return ret; } -// == Manage stateful picking - -void resetSelection() { - haveSelectionVal = false; - currLocalPickInd = 0; - currPickStructure = nullptr; -} - -bool haveSelection() { return haveSelectionVal; } - -void resetSelectionIfStructure(Structure* s) { - if (haveSelectionVal && currPickStructure == s) { - resetSelection(); - } -} - -std::pair getSelection() { - if (haveSelectionVal) { - return {currPickStructure, currLocalPickInd}; - } else { - return {nullptr, 0}; - } -} - -void setSelection(std::pair newPick) { - if (newPick.first == nullptr) { - resetSelection(); - } else { - haveSelectionVal = true; - currPickStructure = newPick.first; - currLocalPickInd = newPick.second; - } -} - // == Helpers -std::pair globalIndexToLocal(size_t globalInd) { +std::pair globalIndexToLocal(uint64_t globalInd) { // ONEDAY: this could be asymptotically better if we cared @@ -94,8 +127,8 @@ std::pair globalIndexToLocal(size_t globalInd) { for (const auto& x : structureRanges) { Structure* structure = x.first; - size_t rangeStart = std::get<0>(x.second); - size_t rangeEnd = std::get<1>(x.second); + uint64_t rangeStart = std::get<0>(x.second); + uint64_t rangeEnd = std::get<1>(x.second); if (globalInd >= rangeStart && globalInd < rangeEnd) { return {structure, globalInd - rangeStart}; @@ -105,28 +138,28 @@ std::pair globalIndexToLocal(size_t globalInd) { return {nullptr, 0}; } -size_t localIndexToGlobal(std::pair localPick) { +uint64_t localIndexToGlobal(std::pair localPick) { if (localPick.first == nullptr) return 0; if (structureRanges.find(localPick.first) == structureRanges.end()) { exception("structure does not match any allocated pick range"); } - std::tuple range = structureRanges[localPick.first]; - size_t rangeStart = std::get<0>(range); - size_t rangeEnd = std::get<1>(range); + std::tuple range = structureRanges[localPick.first]; + uint64_t rangeStart = std::get<0>(range); + uint64_t rangeEnd = std::get<1>(range); return rangeStart + localPick.second; } -std::pair pickAtScreenCoords(glm::vec2 screenCoords) { +std::pair pickAtScreenCoords(glm::vec2 screenCoords) { int xInd, yInd; std::tie(xInd, yInd) = view::screenCoordsToBufferInds(screenCoords); return pickAtBufferCoords(xInd, yInd); } -std::pair pickAtBufferCoords(int xPos, int yPos) { return evaluatePickQuery(xPos, yPos); } +std::pair pickAtBufferCoords(int xPos, int yPos) { return evaluatePickQuery(xPos, yPos); } -std::pair evaluatePickQuery(int xPos, int yPos) { +std::pair evaluatePickQuery(int xPos, int yPos) { // NOTE: hack used for debugging: if xPos == yPos == -1 we do a pick render but do not query the value. @@ -159,7 +192,7 @@ std::pair evaluatePickQuery(int xPos, int yPos) { // Read from the pick buffer std::array result = pickFramebuffer->readFloat4(xPos, view::bufferHeight - yPos); - size_t globalInd = pick::vecToInd(glm::vec3{result[0], result[1], result[2]}); + uint64_t globalInd = pick::vecToInd(glm::vec3{result[0], result[1], result[2]}); return pick::globalIndexToLocal(globalInd); } diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index a947a115..3dfb254a 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -205,6 +205,25 @@ size_t PointCloud::nPoints() { return points.size(); } glm::vec3 PointCloud::getPointPosition(size_t iPt) { return points.getValue(iPt); } +PointCloudPickResult PointCloud::interpretPickResult(const PickResult& rawResult) { + if (rawResult.structure != this) { + // caller must ensure that the PickResult belongs to this structure + // by checking the structure pointer or name + exception("called interpretPickResult(), but the pick result is not from this structure"); + } + + PointCloudPickResult result; + + if (rawResult.localIndex < nPoints()) { + result.index = rawResult.localIndex; + } else { + exception("Bad pick index in point cloud"); + } + + return result; +} + + std::vector PointCloud::addPointCloudRules(std::vector initRules, bool withPointCloud) { initRules = addStructureRules(initRules); if (withPointCloud) { @@ -240,10 +259,13 @@ PointCloudScalarQuantity& PointCloud::resolvePointRadiusQuantity() { return *sizeScalarQ; } -void PointCloud::buildPickUI(size_t localPickID) { - ImGui::TextUnformatted(("#" + std::to_string(localPickID) + " ").c_str()); +void PointCloud::buildPickUI(const PickResult& rawResult) { + + PointCloudPickResult result = interpretPickResult(rawResult); + + ImGui::TextUnformatted(("point #" + std::to_string(result.index) + " ").c_str()); ImGui::SameLine(); - ImGui::TextUnformatted(to_string(getPointPosition(localPickID)).c_str()); + ImGui::TextUnformatted(to_string(getPointPosition(result.index)).c_str()); ImGui::Spacing(); ImGui::Spacing(); @@ -254,7 +276,7 @@ void PointCloud::buildPickUI(size_t localPickID) { ImGui::Columns(2); ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() / 3); for (auto& x : quantities) { - x.second->buildPickUI(localPickID); + x.second->buildPickUI(result.index); } ImGui::Indent(-20.); diff --git a/src/polyscope.cpp b/src/polyscope.cpp index 037eeebd..e788d5da 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -12,6 +12,7 @@ #include "polyscope/options.h" #include "polyscope/pick.h" #include "polyscope/render/engine.h" +#include "polyscope/utilities.h" #include "polyscope/view.h" #include "stb_image.h" @@ -404,8 +405,8 @@ void processInputEvents() { // Don't pick at the end of a long drag if (dragDistSinceLastRelease < dragIgnoreThreshold) { ImVec2 p = ImGui::GetMousePos(); - std::pair pickResult = pick::pickAtScreenCoords(glm::vec2{p.x, p.y}); - pick::setSelection(pickResult); + PickResult pickResult = pickAtScreenCoords(glm::vec2{p.x, p.y}); + setSelection(pickResult); } // Reset the drag distance after any release @@ -414,7 +415,7 @@ void processInputEvents() { // Clear pick if (ImGui::IsMouseReleased(1)) { if (dragDistSinceLastRelease < dragIgnoreThreshold) { - pick::resetSelection(); + resetSelection(); } dragDistSinceLastRelease = 0.0; } @@ -754,18 +755,31 @@ void buildStructureGui() { } void buildPickGui() { - if (pick::haveSelection()) { + if (haveSelection()) { ImGui::SetNextWindowPos(ImVec2(view::windowWidth - (rightWindowsWidth + imguiStackMargin), 2 * imguiStackMargin + lastWindowHeightUser)); ImGui::SetNextWindowSize(ImVec2(rightWindowsWidth, 0.)); ImGui::Begin("Selection", nullptr); - std::pair selection = pick::getSelection(); + PickResult selection = getSelection(); - ImGui::TextUnformatted((selection.first->typeName() + ": " + selection.first->name).c_str()); + + ImGui::Text("screen coordinates: (%.2f,%.2f) depth: %g", selection.screenCoords.x, selection.screenCoords.y, + selection.depth); + ImGui::Text("world position: <%g, %g, %g>", selection.position.x, selection.position.y, selection.position.z); + ImGui::NewLine(); + + ImGui::TextUnformatted((selection.structureType + ": " + selection.structureName).c_str()); ImGui::Separator(); - selection.first->buildPickUI(selection.second); + + if (selection.structureHandle.isValid()) { + selection.structureHandle.get().buildPickUI(selection); + } else { + // this is a paranoid check, it _should_ never happen since we + // clear the selection when a structure is deleted + ImGui::TextUnformatted("ERROR: INVALID STRUCTURE"); + } rightWindowsWidth = ImGui::GetWindowWidth(); ImGui::End(); @@ -1065,6 +1079,19 @@ bool hasStructure(std::string type, std::string name) { return sMap.find(name) != sMap.end(); } +std::tuple lookUpStructure(Structure* structure) { + + for (auto& typeMap : state::structures) { + for (auto& entry : typeMap.second) { + if (entry.second.get() == structure) { + return std::tuple(typeMap.first, entry.first); + } + } + } + + // not found + return std::tuple("", ""); +} void removeStructure(std::string type, std::string name, bool errorIfAbsent) { @@ -1094,7 +1121,7 @@ void removeStructure(std::string type, std::string name, bool errorIfAbsent) { for (auto& g : state::groups) { g.second->removeChildStructure(*s); } - pick::resetSelectionIfStructure(s); + resetSelectionIfStructure(s); sMap.erase(s->name); updateStructureExtents(); return; @@ -1155,7 +1182,7 @@ void removeAllStructures() { } requestRedraw(); - pick::resetSelection(); + resetSelection(); } diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index eaca91be..eff22285 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -977,7 +977,7 @@ float GLFrameBuffer::readDepth(int xPos, int yPos) { bind(); // Read from the buffer - float result; + float result = 1.; glReadPixels(xPos, yPos, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &result); return result; diff --git a/src/render/opengl/shaders/surface_mesh_shaders.cpp b/src/render/opengl/shaders/surface_mesh_shaders.cpp index 4f3a3068..860cb2fc 100644 --- a/src/render/opengl/shaders/surface_mesh_shaders.cpp +++ b/src/render/opengl/shaders/surface_mesh_shaders.cpp @@ -703,22 +703,26 @@ const ShaderReplacementRule MESH_PROPAGATE_PICK_SIMPLE ( // this one does faces {"FRAG_DECLARATIONS", R"( flat in vec3 vertexColors[3]; flat in vec3 faceColor; + uniform float u_vertPickRadius; )"}, {"GENERATE_SHADE_VALUE", R"( // Parameters defining the pick shape (in barycentric 0-1 units) - float vertRadius = 0.2; vec3 shadeColor = faceColor; // Test vertices and corners + float nearestRad = 1.0-u_vertPickRadius; for(int i = 0; i < 3; i++) { - if(a_barycoordToFrag[i] > 1.0-vertRadius) { + if(a_barycoordToFrag[i] > nearestRad) { + nearestRad = a_barycoordToFrag[i]; shadeColor = vertexColors[i]; } } )"}, }, - /* uniforms */ {}, + /* uniforms */ { + {"u_vertPickRadius", RenderDataType::Float}, + }, /* attributes */ { {"a_vertexColors", RenderDataType::Vector3Float, 3}, {"a_faceColor", RenderDataType::Vector3Float}, diff --git a/src/simple_triangle_mesh.cpp b/src/simple_triangle_mesh.cpp index b5f2a553..9171b224 100644 --- a/src/simple_triangle_mesh.cpp +++ b/src/simple_triangle_mesh.cpp @@ -76,9 +76,6 @@ void SimpleTriangleMesh::buildCustomOptionsUI() { } } -void SimpleTriangleMesh::buildPickUI(size_t localPickID) { - // Do nothing for now, we just pick a single constant for the whole structure -} void SimpleTriangleMesh::draw() { if (!isEnabled()) { @@ -272,6 +269,28 @@ void SimpleTriangleMesh::updateObjectSpaceBounds() { objectSpaceLengthScale = 2 * std::sqrt(lengthScale); } +SimpleTriangleMeshPickResult SimpleTriangleMesh::interpretPickResult(const PickResult& rawResult) { + + if (rawResult.structure != this) { + // caller must ensure that the PickResult belongs to this structure + // by checking the structure pointer or name + exception("called interpretPickResult(), but the pick result is not from this structure"); + } + + SimpleTriangleMeshPickResult result; + + // currently nothing + + return result; +} + +void SimpleTriangleMesh::buildPickUI(const PickResult& rawResult) { + SimpleTriangleMeshPickResult result = interpretPickResult(rawResult); + + // Do nothing for now, we just pick a single constant for the whole structure +} + + std::string SimpleTriangleMesh::typeName() { return structureTypeName; } // === Option getters and setters diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index b16082f9..9766ce00 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -2,8 +2,8 @@ #include "polyscope/surface_mesh.h" -#include "glm/fwd.hpp" #include "polyscope/combining_hash_functions.h" +#include "polyscope/elementary_geometry.h" #include "polyscope/pick.h" #include "polyscope/polyscope.h" #include "polyscope/render/engine.h" @@ -61,7 +61,8 @@ edgeColor( uniquePrefix() + "edgeColor", glm::vec3{0., 0., 0. edgeWidth( uniquePrefix() + "edgeWidth", 0.), backFacePolicy( uniquePrefix() + "backFacePolicy", BackFacePolicy::Different), backFaceColor( uniquePrefix() + "backFaceColor", glm::vec3(1.f - surfaceColor.get().r, 1.f - surfaceColor.get().g, 1.f - surfaceColor.get().b)), -shadeStyle( uniquePrefix() + "shadeStyle", MeshShadeStyle::Flat) +shadeStyle( uniquePrefix() + "shadeStyle", MeshShadeStyle::Flat), +selectionMode( uniquePrefix() + "selectionMode", MeshSelectionMode::Auto) // clang-format on {} @@ -734,8 +735,7 @@ void SurfaceMesh::draw() { if (program == nullptr) { prepare(); - // do this now to reduce lag when picking later, etc - // FIXME + // do this now to reduce lag when picking later // preparePick(); } @@ -792,6 +792,22 @@ void SurfaceMesh::drawPick() { // Set uniforms setStructureUniforms(*pickProgram); + if (usingSimplePick) { + float radVal; + switch (selectionMode.get()) { + case MeshSelectionMode::Auto: + radVal = 0.2; + break; + case MeshSelectionMode::VerticesOnly: + radVal = 1.; + break; + case MeshSelectionMode::FacesOnly: + radVal = 0.; + break; + } + pickProgram->setUniform("u_vertPickRadius", radVal); + } + pickProgram->draw(); render::engine->setBackfaceCull(); // return to default setting @@ -813,10 +829,19 @@ void SurfaceMesh::prepare() { void SurfaceMesh::preparePick() { + switch (selectionMode.get()) { + case MeshSelectionMode::Auto: + usingSimplePick = !(edgesHaveBeenUsed || halfedgesHaveBeenUsed || cornersHaveBeenUsed); + break; + case MeshSelectionMode::VerticesOnly: + usingSimplePick = true; + break; + case MeshSelectionMode::FacesOnly: + usingSimplePick = true; + break; + } - bool simplePick = !(edgesHaveBeenUsed || halfedgesHaveBeenUsed || cornersHaveBeenUsed); - - if (simplePick) { + if (usingSimplePick) { pickProgram = render::engine->requestShader("MESH", addSurfaceMeshRules({"MESH_PROPAGATE_PICK_SIMPLE"}, true, false), render::ShaderReplacementDefaults::Pick); @@ -870,7 +895,6 @@ void SurfaceMesh::setMeshPickAttributes(render::ShaderProgram& p) { // CPU-side processing. Maybe the solution is to directly render ints? // make sure we have the relevant indexing data - bool simplePick = !(edgesHaveBeenUsed || halfedgesHaveBeenUsed || cornersHaveBeenUsed); triangleVertexInds.ensureHostBufferPopulated(); triangleFaceInds.ensureHostBufferPopulated(); if (edgesHaveBeenUsed) triangleAllEdgeInds.ensureHostBufferPopulated(); @@ -905,7 +929,7 @@ void SurfaceMesh::setMeshPickAttributes(render::ShaderProgram& p) { // Reserve space vertexColors.reserve(3 * nFacesTriangulation()); faceColor.reserve(3 * nFacesTriangulation()); - if (!simplePick) { + if (!usingSimplePick) { halfedgeColors.reserve(3 * nFacesTriangulation()); cornerColors.reserve(3 * nFacesTriangulation()); } @@ -936,7 +960,7 @@ void SurfaceMesh::setMeshPickAttributes(render::ShaderProgram& p) { } // Second half does halfedges/edges/corners, not used for simple mode - if (simplePick) { + if (usingSimplePick) { iFTri++; continue; } @@ -948,42 +972,45 @@ void SurfaceMesh::setMeshPickAttributes(render::ShaderProgram& p) { // == Build edge index data, if needed + if (!usingSimplePick) { + if (edgesHaveBeenUsed || halfedgesHaveBeenUsed) { - if (edgesHaveBeenUsed || halfedgesHaveBeenUsed) { - - const std::vector& eDataVec = - (edgesHaveBeenUsed && !halfedgesHaveBeenUsed) ? triangleAllEdgeInds.data : triangleAllHalfedgeInds.data; - size_t offset = - (edgesHaveBeenUsed && !halfedgesHaveBeenUsed) ? edgeGlobalPickIndStart : halfedgeGlobalPickIndStart; + const std::vector& eDataVec = + (edgesHaveBeenUsed && !halfedgesHaveBeenUsed) ? triangleAllEdgeInds.data : triangleAllHalfedgeInds.data; + size_t offset = + (edgesHaveBeenUsed && !halfedgesHaveBeenUsed) ? edgeGlobalPickIndStart : halfedgeGlobalPickIndStart; - // clang-format off + // clang-format off std::array eColor = { fColor, pick::indToVec(eDataVec[9*iFTri + 1] + offset), fColor }; - // clang-format on - if (j == 1) eColor[0] = pick::indToVec(eDataVec[9 * iFTri + 0] + offset); - if (j + 2 == D) eColor[2] = pick::indToVec(eDataVec[9 * iFTri + 2] + offset); + // clang-format on + if (j == 1) eColor[0] = pick::indToVec(eDataVec[9 * iFTri + 0] + offset); + if (j + 2 == D) eColor[2] = pick::indToVec(eDataVec[9 * iFTri + 2] + offset); - for (int j = 0; j < 3; j++) halfedgeColors.push_back(eColor); - } else { - for (int j = 0; j < 3; j++) halfedgeColors.push_back({fColor, fColor, fColor}); + for (int j = 0; j < 3; j++) halfedgeColors.push_back(eColor); + } else { + for (int j = 0; j < 3; j++) halfedgeColors.push_back({fColor, fColor, fColor}); + } } // == Build corner index data, if needed - if (cornersHaveBeenUsed) { - // clang-format off + if (!usingSimplePick) { + if (cornersHaveBeenUsed) { + // clang-format off std::array cColor = { pick::indToVec(triangleCornerInds.data[3*iFTri + 0] + cornerGlobalPickIndStart), pick::indToVec(triangleCornerInds.data[3*iFTri + 1] + cornerGlobalPickIndStart), pick::indToVec(triangleCornerInds.data[3*iFTri + 2] + cornerGlobalPickIndStart), }; - // clang-format on - for (int j = 0; j < 3; j++) cornerColors.push_back(cColor); - } else { - for (int j = 0; j < 3; j++) cornerColors.push_back({vColor[0], vColor[1], vColor[2]}); + // clang-format on + for (int j = 0; j < 3; j++) cornerColors.push_back(cColor); + } else { + for (int j = 0; j < 3; j++) cornerColors.push_back({vColor[0], vColor[1], vColor[2]}); + } } iFTri++; @@ -1002,7 +1029,7 @@ void SurfaceMesh::setMeshPickAttributes(render::ShaderProgram& p) { faceColorsBuff->setData(faceColor); pickProgram->setAttribute("a_faceColor", faceColorsBuff); - if (!simplePick) { + if (!usingSimplePick) { std::shared_ptr halfedgeColorsBuff = render::engine->generateAttributeBuffer(RenderDataType::Vector3Float, 3); @@ -1083,32 +1110,51 @@ void SurfaceMesh::setSurfaceMeshUniforms(render::ShaderProgram& p) { } -void SurfaceMesh::buildPickUI(size_t localPickID) { +void SurfaceMesh::buildPickUI(const PickResult& rawResult) { - // Selection type - if (localPickID < facePickIndStart) { - buildVertexInfoGui(localPickID); - } else if (localPickID < edgePickIndStart) { - buildFaceInfoGui(localPickID - facePickIndStart); - } else if (localPickID < halfedgePickIndStart) { - buildEdgeInfoGui(localPickID - edgePickIndStart); - } else if (localPickID < cornerPickIndStart) { - buildHalfedgeInfoGui(localPickID - halfedgePickIndStart); + SurfaceMeshPickResult result = interpretPickResult(rawResult); + switch (result.elementType) { + case MeshElement::VERTEX: { + buildVertexInfoGui(result); + break; + } + case MeshElement::FACE: { + buildFaceInfoGui(result); + break; + } + case MeshElement::EDGE: { + buildEdgeInfoGui(result); + break; + } + case MeshElement::HALFEDGE: { + buildHalfedgeInfoGui(result); + + // Also build the edge gui while we're here if (edgesHaveBeenUsed) { - // do the edge one too (see not in pick buffer filler) - uint32_t halfedgeInd = localPickID - halfedgePickIndStart; + // do the edge one too (see note in pick buffer filler) + uint32_t halfedgeInd = result.index; if (halfedgeInd >= halfedgeEdgeCorrespondence.size()) { exception("problem with halfedge edge indices"); } uint32_t edgeInd = halfedgeEdgeCorrespondence[halfedgeInd]; + // construct a pick result for the edge + SurfaceMeshPickResult edgePickResult = result; + edgePickResult.elementType = MeshElement::EDGE; + edgePickResult.index = edgeInd; + ImGui::NewLine(); - buildEdgeInfoGui(edgeInd); + buildEdgeInfoGui(edgePickResult); } - } else { - buildCornerInfoGui(localPickID - cornerPickIndStart); + + break; } + case MeshElement::CORNER: { + buildCornerInfoGui(result); + break; + } + }; } glm::vec2 SurfaceMesh::projectToScreenSpace(glm::vec3 coord) { @@ -1121,8 +1167,8 @@ glm::vec2 SurfaceMesh::projectToScreenSpace(glm::vec3 coord) { return glm::vec2{screenPoint.x, screenPoint.y} / screenPoint.w; } -void SurfaceMesh::buildVertexInfoGui(size_t vInd) { - +void SurfaceMesh::buildVertexInfoGui(const SurfaceMeshPickResult& result) { + size_t vInd = result.index; size_t displayInd = vInd; ImGui::TextUnformatted(("Vertex #" + std::to_string(displayInd)).c_str()); @@ -1146,10 +1192,16 @@ void SurfaceMesh::buildVertexInfoGui(size_t vInd) { ImGui::Columns(1); } -void SurfaceMesh::buildFaceInfoGui(size_t fInd) { +void SurfaceMesh::buildFaceInfoGui(const SurfaceMeshPickResult& result) { + size_t fInd = result.index; size_t displayInd = fInd; ImGui::TextUnformatted(("Face #" + std::to_string(displayInd)).c_str()); + if (result.baryCoords != glm::vec3{-1., -1., -1.}) { + ImGui::Text("selected barycoords = <%.3f, %.3f, %.3f>", result.baryCoords.x, result.baryCoords.y, + result.baryCoords.z); + } + ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); @@ -1166,7 +1218,8 @@ void SurfaceMesh::buildFaceInfoGui(size_t fInd) { ImGui::Columns(1); } -void SurfaceMesh::buildEdgeInfoGui(size_t eInd) { +void SurfaceMesh::buildEdgeInfoGui(const SurfaceMeshPickResult& result) { + size_t eInd = result.index; size_t displayInd = eInd; if (edgePerm.size() > 0) { displayInd = edgePerm[eInd]; @@ -1189,7 +1242,8 @@ void SurfaceMesh::buildEdgeInfoGui(size_t eInd) { ImGui::Columns(1); } -void SurfaceMesh::buildHalfedgeInfoGui(size_t heInd) { +void SurfaceMesh::buildHalfedgeInfoGui(const SurfaceMeshPickResult& result) { + size_t heInd = result.index; size_t displayInd = heInd; if (halfedgePerm.size() > 0) { displayInd = halfedgePerm[heInd]; @@ -1212,7 +1266,8 @@ void SurfaceMesh::buildHalfedgeInfoGui(size_t heInd) { ImGui::Columns(1); } -void SurfaceMesh::buildCornerInfoGui(size_t cInd) { +void SurfaceMesh::buildCornerInfoGui(const SurfaceMeshPickResult& result) { + size_t cInd = result.index; size_t displayInd = cInd; ImGui::TextUnformatted(("Corner #" + std::to_string(displayInd)).c_str()); @@ -1356,6 +1411,40 @@ void SurfaceMesh::buildCustomOptionsUI() { } } } + ImGui::EndMenu(); + } + + // Selection mode + if (ImGui::BeginMenu("Selection Mode")) { + if (ImGui::MenuItem("auto", NULL, selectionMode.get() == MeshSelectionMode::Auto)) + setSelectionMode(MeshSelectionMode::Auto); + if (ImGui::MenuItem("vertices only", NULL, selectionMode.get() == MeshSelectionMode::VerticesOnly)) + setSelectionMode(MeshSelectionMode::VerticesOnly); + if (ImGui::MenuItem("faces only", NULL, selectionMode.get() == MeshSelectionMode::FacesOnly)) + setSelectionMode(MeshSelectionMode::FacesOnly); + + ImGui::Separator(); + + + if (ImGui::BeginMenu("Add to auto")) { + + std::string edgeMsg = "edges"; + bool edgeSelectionAllowed = !edgePerm.empty(); + if (!edgeSelectionAllowed) { + edgeMsg += " [must set edge indices]"; + } + if (ImGui::MenuItem(edgeMsg.c_str(), NULL, edgesHaveBeenUsed, edgeSelectionAllowed)) { + markEdgesAsUsed(); + } + if (ImGui::MenuItem("halfedges", NULL, halfedgesHaveBeenUsed)) { + markHalfedgesAsUsed(); + } + if (ImGui::MenuItem("corners", NULL, cornersHaveBeenUsed)) { + markCornersAsUsed(); + } + + ImGui::EndMenu(); + } ImGui::EndMenu(); } @@ -1403,6 +1492,71 @@ void SurfaceMesh::updateObjectSpaceBounds() { std::string SurfaceMesh::typeName() { return structureTypeName; } +SurfaceMeshPickResult SurfaceMesh::interpretPickResult(const PickResult& rawResult) { + + if (rawResult.structure != this) { + // caller must ensure that the PickResult belongs to this structure + // by checking the structure pointer or name + exception("called interpretPickResult(), but the pick result is not from this structure"); + } + + SurfaceMeshPickResult result; + + if (rawResult.localIndex < facePickIndStart) { + // Vertex pick + result.elementType = MeshElement::VERTEX; + result.index = rawResult.localIndex; + } else if (rawResult.localIndex < edgePickIndStart) { + // Face pick + result.elementType = MeshElement::FACE; + result.index = rawResult.localIndex - facePickIndStart; + + // TODO barycoords + size_t D = faceIndsStart[result.index + 1] - faceIndsStart[result.index]; + if (D == 3) { + + // gather values and project onto plane + size_t iStart = faceIndsStart[result.index]; + uint32_t vA = faceIndsEntries[iStart]; + uint32_t vB = faceIndsEntries[iStart + 1]; + uint32_t vC = faceIndsEntries[iStart + 2]; + glm::vec3 pA = vertexPositions.getValue(vA); + glm::vec3 pB = vertexPositions.getValue(vB); + glm::vec3 pC = vertexPositions.getValue(vC); + glm::vec3 normal = glm::normalize(glm::cross(pB - pA, pC - pA)); + glm::vec3 x = projectToPlane(rawResult.position, normal, pA); + + // compute barycentric coordinates as ratio of signed areas + float areaABC = signedTriangleArea(normal, pA, pB, pC); + float areaXBC = signedTriangleArea(normal, x, pB, pC); + float areaXCA = signedTriangleArea(normal, x, pC, pA); + float areaXAB = signedTriangleArea(normal, x, pA, pB); + glm::vec3 barycoord{areaXBC / areaABC, areaXCA / areaABC, areaXAB / areaABC}; + result.baryCoords = barycoord; + } + + } else if (rawResult.localIndex < halfedgePickIndStart) { + // Edge pick + result.elementType = MeshElement::EDGE; + result.index = rawResult.localIndex - edgePickIndStart; + + + } else if (rawResult.localIndex < cornerPickIndStart) { + // Halfedge pick + result.elementType = MeshElement::HALFEDGE; + result.index = rawResult.localIndex - halfedgePickIndStart; + + } else if (rawResult.localIndex < cornerPickIndStart + nCorners()) { + // Corner pick + result.elementType = MeshElement::CORNER; + result.index = rawResult.localIndex - cornerPickIndStart; + } else { + exception("Bad pick index in curve network"); + } + + return result; +} + long long int SurfaceMesh::selectVertex() { // Make sure we can see edges @@ -1601,6 +1755,14 @@ SurfaceMesh* SurfaceMesh::setShadeStyle(MeshShadeStyle newStyle) { } MeshShadeStyle SurfaceMesh::getShadeStyle() { return shadeStyle.get(); } +SurfaceMesh* SurfaceMesh::setSelectionMode(MeshSelectionMode newMode) { + selectionMode = newMode; + refresh(); + requestRedraw(); + return this; +} +MeshSelectionMode SurfaceMesh::getSelectionMode() { return selectionMode.get(); } + // === Quantity adders diff --git a/src/view.cpp b/src/view.cpp index 4511205e..5f615d9b 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -102,6 +102,21 @@ std::tuple screenCoordsToBufferInds(glm::vec2 screenCoords) { return std::tuple(xPos, yPos); } +glm::ivec2 screenCoordsToBufferIndsVec(glm::vec2 screenCoords) { + glm::ivec2 out; + std::tie(out.x, out.y) = screenCoordsToBufferInds(screenCoords); + return out; +} + +glm::vec2 bufferIndsToScreenCoords(int xPos, int yPos) { + return glm::vec2{xPos * static_cast(view::windowWidth) / view::bufferWidth, + yPos * static_cast(view::windowHeight) / view::bufferHeight}; +} + +glm::vec2 bufferIndsToScreenCoords(glm::ivec2 bufferInds) { + return bufferIndsToScreenCoords(bufferInds.x, bufferInds.y); +} + void processRotate(glm::vec2 startP, glm::vec2 endP) { if (startP == endP) { @@ -508,6 +523,8 @@ glm::vec3 screenCoordsToWorldRay(glm::vec2 screenCoords) { return worldRayDir; } +glm::vec3 bufferIndsToWorldRay(glm::vec2 bufferInds) { return bufferCoordsToWorldRay(bufferInds); } + glm::vec3 bufferCoordsToWorldRay(glm::vec2 bufferCoords) { glm::mat4 view = getCameraViewMatrix(); @@ -522,10 +539,14 @@ glm::vec3 bufferCoordsToWorldRay(glm::vec2 bufferCoords) { } -glm::vec3 screenCoordsToWorldPosition(glm::vec2 screenCoords) { +glm::vec3 screenCoordsAndDepthToWorldPosition(glm::vec2 screenCoords, float clipDepth) { + + if (clipDepth == 1.) { + // if we didn't hit anything in the depth buffer, just return infinity + float inf = std::numeric_limits::infinity(); + return glm::vec3{inf, inf, inf}; + } - int xInd, yInd; - std::tie(xInd, yInd) = screenCoordsToBufferInds(screenCoords); glm::mat4 view = getCameraViewMatrix(); glm::mat4 viewInv = glm::inverse(view); @@ -533,19 +554,10 @@ glm::vec3 screenCoordsToWorldPosition(glm::vec2 screenCoords) { glm::mat4 projInv = glm::inverse(proj); // glm::vec2 depthRange = {0., 1.}; // no support for nonstandard depth range, currently - // query the depth buffer to get depth - render::FrameBuffer* sceneFramebuffer = render::engine->sceneBuffer.get(); - float depth = sceneFramebuffer->readDepth(xInd, view::bufferHeight - yInd); - if (depth == 1.) { - // if we didn't hit anything in the depth buffer, just return infinity - float inf = std::numeric_limits::infinity(); - return glm::vec3{inf, inf, inf}; - } - // convert depth to world units glm::vec2 screenPos{screenCoords.x / static_cast(view::windowWidth), 1.f - screenCoords.y / static_cast(view::windowHeight)}; - float z = depth * 2.0f - 1.0f; + float z = clipDepth * 2.0f - 1.0f; glm::vec4 clipPos = glm::vec4(screenPos * 2.0f - 1.0f, z, 1.0f); glm::vec4 viewPos = projInv * clipPos; viewPos /= viewPos.w; diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index 1ad3b668..0f5fd9a1 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -427,18 +427,21 @@ void VolumeGrid::markNodesAsUsed() { nodesHaveBeenUsed = true; } void VolumeGrid::markCellsAsUsed() { cellsHaveBeenUsed = true; } +VolumeGridPickResult VolumeGrid::interpretPickResult(const PickResult& rawResult) { -void VolumeGrid::buildPickUI(size_t localPickID) { + if (rawResult.structure != this) { + // caller must ensure that the PickResult belongs to this structure + // by checking the structure pointer or name + exception("called interpretPickResult(), but the pick result is not from this structure"); + } + + VolumeGridPickResult result; - // See note in ensurePickProgramPrepared(). // Picking for this structure works different, and identifies which element with a depth query CPU side. float nodePickRad = 0.8; // measured in a [-1,1] cube - ImGuiIO& io = ImGui::GetIO(); - glm::vec2 screenCoords{io.MousePos.x, io.MousePos.y}; - glm::vec3 pickPos = view::screenCoordsToWorldPosition(screenCoords); - glm::vec3 localPickPos = (pickPos - boundMin) / (boundMax - boundMin); + glm::vec3 localPickPos = (rawResult.position - boundMin) / (boundMax - boundMin); localPickPos = clamp(localPickPos, glm::vec3(0.), glm::vec3(1.)); // on [0,1.] // NOTE: this logic is duplicated with shader @@ -467,7 +470,8 @@ void VolumeGrid::buildPickUI(size_t localPickID) { glm::uvec3 nodeInd3{std::round(coordUnit.x), std::round(coordUnit.y), std::round(coordUnit.z)}; uint64_t nodeInd = flattenNodeIndex(nodeInd3); - buildNodeInfoGUI(nodeInd); + result.elementType = VolumeGridElement::NODE; + result.index = nodeInd; } else { // Pick a cell @@ -476,13 +480,33 @@ void VolumeGrid::buildPickUI(size_t localPickID) { cellInd3 = clamp(cellInd3, glm::uvec3(0), gridCellDim - 1u); uint64_t cellInd = flattenCellIndex(cellInd3); - buildCellInfoGUI(cellInd); + result.elementType = VolumeGridElement::CELL; + result.index = cellInd; + } + + return result; +} + +void VolumeGrid::buildPickUI(const PickResult& rawResult) { + + VolumeGridPickResult result = interpretPickResult(rawResult); + + switch (result.elementType) { + case VolumeGridElement::NODE: { + buildNodeInfoGUI(result); + break; } + case VolumeGridElement::CELL: { + buildCellInfoGUI(result); + break; + } + }; } -void VolumeGrid::buildNodeInfoGUI(size_t nInd) { +void VolumeGrid::buildNodeInfoGUI(const VolumeGridPickResult& result) { + size_t nInd = result.index; size_t displayInd = nInd; glm::uvec3 nodeInd3 = unflattenNodeIndex(nInd); @@ -511,8 +535,9 @@ void VolumeGrid::buildNodeInfoGUI(size_t nInd) { ImGui::Indent(-20.); } -void VolumeGrid::buildCellInfoGUI(size_t cellInd) { +void VolumeGrid::buildCellInfoGUI(const VolumeGridPickResult& result) { + size_t cellInd = result.index; size_t displayInd = cellInd; glm::uvec3 cellInd3 = unflattenCellIndex(cellInd); diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index 97f0e868..b84f7cad 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -484,6 +484,7 @@ void VolumeMesh::drawPick() { // Set uniforms setVolumeMeshUniforms(*pickProgram); setStructureUniforms(*pickProgram); + pickProgram->setUniform("u_vertPickRadius", 0.2); pickProgram->draw(); } @@ -799,14 +800,49 @@ void VolumeMesh::computeCellCenters() { cellCenters.markHostBufferUpdated(); } -void VolumeMesh::buildPickUI(size_t localPickID) { + +VolumeMeshPickResult VolumeMesh::interpretPickResult(const PickResult& rawResult) { + + if (rawResult.structure != this) { + // caller must ensure that the PickResult belongs to this structure + // by checking the structure pointer or name + exception("called interpretPickResult(), but the pick result is not from this structure"); + } + + VolumeMeshPickResult result; // Selection type - if (localPickID < cellPickIndStart) { - buildVertexInfoGui(localPickID); + if (rawResult.localIndex < cellPickIndStart) { + result.elementType = VolumeMeshElement::VERTEX; + result.index = rawResult.localIndex; + } else if (rawResult.localIndex < nVertices() + nCells()) { + result.elementType = VolumeMeshElement::CELL; + result.index = rawResult.localIndex - cellPickIndStart; } else { - buildCellInfoGUI(localPickID - cellPickIndStart); + exception("Bad pick index in volume mesh"); } + + return result; +} + +void VolumeMesh::buildPickUI(const PickResult& rawResult) { + + VolumeMeshPickResult result = interpretPickResult(rawResult); + + switch (result.elementType) { + case VolumeMeshElement::VERTEX: { + buildVertexInfoGui(result.index); + break; + } + case VolumeMeshElement::CELL: { + buildCellInfoGUI(result.index); + break; + } + default: { + /* do nothing */ + break; + } + }; } void VolumeMesh::buildVertexInfoGui(size_t vInd) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c6b24484..020cdc72 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,13 +12,13 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) ### Configure the compiler -# NOTE: Polyscope itself uses C++11, but the tests use C++14 because googletest requires it. +# NOTE: Polyscope itself uses C++11, but the tests use C++17 because googletest requires it. if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") # using Clang (linux or apple) or GCC message("Using clang/gcc compiler flags") - SET(BASE_CXX_FLAGS "-std=c++14 -Wall -Wextra -g3") # use C++14 for tests only + SET(BASE_CXX_FLAGS "-std=c++17 -Wall -Wextra -g3") # use C++17 for tests only SET(DISABLED_WARNINGS " -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-deprecated-declarations -Wno-missing-braces -Wno-unused-private-field") SET(TRACE_INCLUDES " -H -Wno-error=unused-command-line-argument") diff --git a/test/src/camera_view_test.cpp b/test/src/camera_view_test.cpp index 9cb9893a..defc1749 100644 --- a/test/src/camera_view_test.cpp +++ b/test/src/camera_view_test.cpp @@ -75,7 +75,7 @@ TEST_F(PolyscopeTest, CameraViewPick) { // This probably doesn't actually click on anything, but it does populate the pick buffers and makes sure that nothing // crashes - polyscope::pick::pickAtScreenCoords(glm::vec2{0.3, 0.8}); + polyscope::pickAtScreenCoords(glm::vec2{0.3, 0.8}); polyscope::show(3); diff --git a/test/src/curve_network_test.cpp b/test/src/curve_network_test.cpp index 3b5ebeb9..a16fb975 100644 --- a/test/src/curve_network_test.cpp +++ b/test/src/curve_network_test.cpp @@ -33,7 +33,7 @@ TEST_F(PolyscopeTest, CurveNetworkPick) { auto psCurve = registerCurveNetwork(); // Don't bother trying to actually click on anything, but make sure this doesn't crash - polyscope::pick::evaluatePickQuery(77, 88); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); polyscope::removeAllStructures(); } diff --git a/test/src/point_cloud_test.cpp b/test/src/point_cloud_test.cpp index f2102c02..ae312160 100644 --- a/test/src/point_cloud_test.cpp +++ b/test/src/point_cloud_test.cpp @@ -66,10 +66,10 @@ TEST_F(PolyscopeTest, PointCloudPick) { auto psPoints = registerPointCloud(); // Don't bother trying to actually click on anything, but make sure this doesn't crash - polyscope::pick::evaluatePickQuery(77, 88); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); psPoints->setPointRenderMode(polyscope::PointRenderMode::Quad); - polyscope::pick::evaluatePickQuery(77, 88); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); polyscope::removeAllStructures(); } diff --git a/test/src/surface_mesh_test.cpp b/test/src/surface_mesh_test.cpp index 3949d865..07ce9669 100644 --- a/test/src/surface_mesh_test.cpp +++ b/test/src/surface_mesh_test.cpp @@ -112,11 +112,11 @@ TEST_F(PolyscopeTest, SurfaceMeshPick) { auto psMesh = registerTriangleMesh(); // Don't bother trying to actually click on anything, but make sure this doesn't crash - polyscope::pick::evaluatePickQuery(77, 88); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); // Do it again with edges enabled psMesh->setEdgeWidth(1.0); - polyscope::pick::evaluatePickQuery(77, 88); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); polyscope::removeAllStructures(); } @@ -605,7 +605,7 @@ TEST_F(PolyscopeTest, SimpleTriangleMeshPick) { auto psMesh = registerSimpleTriangleMesh(); // Don't bother trying to actually click on anything, but make sure this doesn't crash - polyscope::pick::evaluatePickQuery(77, 88); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); polyscope::removeAllStructures(); }