From fb352b5fdfeb169d205ae10475da1f5105d523e9 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 27 Jan 2025 23:13:33 -0800 Subject: [PATCH 01/17] initial WIP on better picking --- include/polyscope/context.h | 9 ++++++ include/polyscope/pick.h | 35 +++++++++++++++++----- include/polyscope/polyscope.h | 4 +++ include/polyscope/render/engine.h | 2 +- include/polyscope/view.h | 2 ++ src/pick.cpp | 48 ++++++++++++++++++++++++++----- src/polyscope.cpp | 13 +++++++++ src/view.cpp | 13 +++++++-- 8 files changed, 109 insertions(+), 17 deletions(-) diff --git a/include/polyscope/context.h b/include/polyscope/context.h index 0c591c00..36be7cc3 100644 --- a/include/polyscope/context.h +++ b/include/polyscope/context.h @@ -97,6 +97,15 @@ struct Context { glm::vec3 flightTargetViewT, flightInitialViewT; float flightTargetFov, flightInitialFov; + // ====================================================== + // === Picking globals from pick.h / pick.cpp + // ====================================================== + + size_t currLocalPickInd = 0; + Structure* currPickStructure = nullptr; + bool haveSelectionVal = false; + size_t nextPickBufferInd = 1; + std::unordered_map> structureRanges; // ====================================================== // === Internal globals from internal.h diff --git a/include/polyscope/pick.h b/include/polyscope/pick.h index 2facd906..3c2830fc 100644 --- a/include/polyscope/pick.h +++ b/include/polyscope/pick.h @@ -8,23 +8,39 @@ #include namespace polyscope { -namespace pick { +// == Main query -// == 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); +// 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(PickQueryResult) to get +// structure-specific info, like which vertex/face was clicked on. + +// Return type for pick queries +struct PickQueryResult { + bool isHit; + Structure* structure; + std::string structureType; + std::string structureName; + glm::vec3 position; + float depth; +}; + +// Query functions to evaluate a pick. +// Internally, these do a render pass to populate relevant information, then query the resulting buffers. +PickQueryResult queryPickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates +PickQueryResult queryPickAtBufferCoords(int xPos, int yPos); // takes indices into render buffer +namespace pick { -// == Main query +// 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. - // == Stateful picking: track and update a current selection // Get/Set the "selected" item, if there is one (output has same meaning as evaluatePickQuery()); @@ -38,6 +54,11 @@ void resetSelectionIfStructure(Structure* s); // If something from this structur // == 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. +size_t requestPickBufferRange(Structure* requestingStructure, size_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); 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/view.h b/include/polyscope/view.h index 985a9606..296b78e5 100644 --- a/include/polyscope/view.h +++ b/include/polyscope/view.h @@ -139,6 +139,7 @@ bool getWindowResizable(); 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 bufferCoordsToWorldPosition(int xPos, int yPos); // Get and set camera from json string std::string getViewAsJson(); @@ -150,6 +151,7 @@ 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::vec2 bufferIndsToScreenCoords(int xPos, int yPos); // == Internal helpers. Should probably not be called in user code. diff --git a/src/pick.cpp b/src/pick.cpp index 2e45eed7..613ffc86 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -9,19 +9,53 @@ #include namespace polyscope { + +PickQueryResult queryPickAtScreenCoords(glm::vec2 screenCoords) { + int xInd, yInd; + std::tie(xInd, yInd) = view::screenCoordsToBufferInds(screenCoords); + return queryPickAtBufferCoords(xInd, yInd); +} +PickQueryResult queryPickAtBufferCoords(int xPos, int yPos) { + PickQueryResult result; + + // Query the render buffer + result.position = view::bufferCoordsToWorldPosition(xPos, yPos); + result.depth = glm::length(result.position - view::getCameraWorldPosition()); + + // Query the pick buffer + std::pair rawPickResult = pick::evaluatePickQuery(xPos, yPos); + + // Transcribe result into return tuple + result.structure = rawPickResult.first; + if (rawPickResult.first == nullptr) { + result.isHit = false; + result.structureType = ""; + result.structureName = ""; + } else { + result.isHit = true; + std::tuple lookupResult = lookUpStructure(rawPickResult.first); + result.structureType = std::get<0>(lookupResult); + result.structureName = std::get<1>(lookupResult); + } + + + + return result; +} + + namespace pick { -size_t currLocalPickInd = 0; -Structure* currPickStructure = nullptr; -bool haveSelectionVal = false; +size_t& currLocalPickInd = state::globalContext.currLocalPickInd; +Structure*& currPickStructure = state::globalContext.currPickStructure; +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" - // +size_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 diff --git a/src/polyscope.cpp b/src/polyscope.cpp index 037eeebd..34fef9f7 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -1065,6 +1065,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) { diff --git a/src/view.cpp b/src/view.cpp index 4511205e..e111f7c3 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -102,6 +102,11 @@ std::tuple screenCoordsToBufferInds(glm::vec2 screenCoords) { return std::tuple(xPos, yPos); } +glm::vec2 bufferIndsToScreenCoords(int xPos, int yPos) { + return glm::vec2{xPos * static_cast(view::windowWidth) / view::bufferWidth, + yPos * static_cast(view::windowHeight) / view::bufferHeight}; +} + void processRotate(glm::vec2 startP, glm::vec2 endP) { if (startP == endP) { @@ -521,11 +526,15 @@ glm::vec3 bufferCoordsToWorldRay(glm::vec2 bufferCoords) { return worldRayDir; } - glm::vec3 screenCoordsToWorldPosition(glm::vec2 screenCoords) { - int xInd, yInd; std::tie(xInd, yInd) = screenCoordsToBufferInds(screenCoords); + return bufferCoordsToWorldPosition(xInd, yInd); +} + +glm::vec3 bufferCoordsToWorldPosition(int xInd, int yInd) { + + glm::vec2 screenCoords = bufferIndsToScreenCoords(xInd, yInd); glm::mat4 view = getCameraViewMatrix(); glm::mat4 viewInv = glm::inverse(view); From e3026dd0b40909729b21962203e8b490cc178642 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 3 Feb 2025 05:56:32 +0100 Subject: [PATCH 02/17] use new PickResult() and interpretPick() throughout --- include/polyscope/camera_view.h | 9 +- include/polyscope/context.h | 9 +- include/polyscope/curve_network.h | 10 +- include/polyscope/elementary_geometry.h | 0 .../polyscope/floating_quantity_structure.h | 2 +- include/polyscope/pick.h | 47 +++++--- include/polyscope/point_cloud.h | 11 +- include/polyscope/simple_triangle_mesh.h | 10 +- include/polyscope/structure.h | 3 +- include/polyscope/surface_mesh.h | 20 ++-- include/polyscope/types.h | 2 + include/polyscope/volume_grid.h | 13 ++- include/polyscope/volume_mesh.h | 9 +- src/camera_view.cpp | 23 +++- src/curve_network.cpp | 49 ++++++-- src/elementary_geometry.cpp | 0 src/floating_quantity_structure.cpp | 2 +- src/pick.cpp | 70 +++++------ src/point_cloud.cpp | 30 ++++- src/polyscope.cpp | 23 +++- src/simple_triangle_mesh.cpp | 25 +++- src/surface_mesh.cpp | 110 ++++++++++++++---- src/volume_grid.cpp | 45 +++++-- src/volume_mesh.cpp | 43 ++++++- 24 files changed, 428 insertions(+), 137 deletions(-) create mode 100644 include/polyscope/elementary_geometry.h create mode 100644 src/elementary_geometry.cpp 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 36be7cc3..3b4a17c8 100644 --- a/include/polyscope/context.h +++ b/include/polyscope/context.h @@ -4,7 +4,7 @@ #include #include - +#include #include #include @@ -101,11 +101,10 @@ struct Context { // === Picking globals from pick.h / pick.cpp // ====================================================== - size_t currLocalPickInd = 0; - Structure* currPickStructure = nullptr; + PickResult currSelectionPickResult; bool haveSelectionVal = false; - size_t nextPickBufferInd = 1; - std::unordered_map> structureRanges; + 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..e1746d75 100644 --- a/include/polyscope/curve_network.h +++ b/include/polyscope/curve_network.h @@ -36,6 +36,11 @@ struct QuantityTypeHelper { typedef CurveNetworkQuantity type; }; +struct CurveNetworkPickResult { + CurveNetworkElement elementType; // which kind of element did we click + int64_t index; // index of the clicked element +}; + class CurveNetwork : public QuantityStructure { public: // === Member functions === @@ -48,7 +53,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 +132,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 diff --git a/include/polyscope/elementary_geometry.h b/include/polyscope/elementary_geometry.h new file mode 100644 index 00000000..e69de29b 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 +#include #include +#include "polyscope/utilities.h" +#include "polyscope/weak_handle.h" + namespace polyscope { +// Forward decls +class Structure; + // == Main query // 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(PickQueryResult) to get +// 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 PickQueryResult { - bool isHit; - Structure* structure; - std::string structureType; - std::string structureName; +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 bufferCoords; 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. -PickQueryResult queryPickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates -PickQueryResult queryPickAtBufferCoords(int xPos, int yPos); // takes indices into render buffer +PickResult queryPickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates +PickResult queryPickAtBufferCoords(int xPos, int yPos); // takes indices into render buffer 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. +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. // == 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 @@ -57,11 +66,11 @@ void resetSelectionIfStructure(Structure* s); // If something from this structur // 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. -size_t requestPickBufferRange(Structure* requestingStructure, size_t count); +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/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..010d93ca 100644 --- a/include/polyscope/surface_mesh.h +++ b/include/polyscope/surface_mesh.h @@ -51,6 +51,10 @@ struct QuantityTypeHelper { typedef SurfaceMeshQuantity type; }; +struct SurfaceMeshPickResult { + MeshElement elementType; // which kind of element did we click + int64_t index; // index of the clicked element +}; // === The grand surface mesh class @@ -75,7 +79,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 +170,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 @@ -381,11 +387,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 diff --git a/include/polyscope/types.h b/include/polyscope/types.h index 6280d363..10a3b5df 100644 --- a/include/polyscope/types.h +++ b/include/polyscope/types.h @@ -18,9 +18,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 CurveNetworkElement { NODE = 0, EDGE }; enum class MeshShadeStyle { Smooth = 0, Flat, TriFlat }; 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/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/camera_view.cpp b/src/camera_view.cpp index 5f3c708b..5d9ef2ee 100644 --- a/src/camera_view.cpp +++ b/src/camera_view.cpp @@ -323,7 +323,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 +356,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..82068d73 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -342,15 +342,20 @@ 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.index); + break; + } + case CurveNetworkElement::EDGE: { + buildEdgePickUI(result.index); + break; } + }; } void CurveNetwork::buildNodePickUI(size_t nodeInd) { @@ -457,6 +462,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); + + } 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..e69de29b 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/pick.cpp b/src/pick.cpp index 613ffc86..58376c50 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -10,59 +10,61 @@ namespace polyscope { -PickQueryResult queryPickAtScreenCoords(glm::vec2 screenCoords) { +PickResult queryPickAtScreenCoords(glm::vec2 screenCoords) { int xInd, yInd; std::tie(xInd, yInd) = view::screenCoordsToBufferInds(screenCoords); return queryPickAtBufferCoords(xInd, yInd); } -PickQueryResult queryPickAtBufferCoords(int xPos, int yPos) { - PickQueryResult result; +PickResult queryPickAtBufferCoords(int xPos, int yPos) { + PickResult result; // Query the render buffer result.position = view::bufferCoordsToWorldPosition(xPos, yPos); result.depth = glm::length(result.position - view::getCameraWorldPosition()); // Query the pick buffer - std::pair rawPickResult = pick::evaluatePickQuery(xPos, yPos); + std::pair rawPickResult = pick::pickAtBufferCoords(xPos, yPos); // Transcribe result into return tuple result.structure = rawPickResult.first; + result.bufferCoords = glm::ivec2(xPos, yPos); + result.screenCoords = view::bufferIndsToScreenCoords(xPos, yPos); 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; } namespace pick { -size_t& currLocalPickInd = state::globalContext.currLocalPickInd; -Structure*& currPickStructure = state::globalContext.currPickStructure; +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 = state::globalContext.nextPickBufferInd; // 0 reserved for "none" +uint64_t& nextPickBufferInd = state::globalContext.nextPickBufferInd; // 0 reserved for "none" // Track which ranges have been allocated to which structures -std::unordered_map> structureRanges = state::globalContext.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) { @@ -78,7 +80,7 @@ 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; @@ -88,39 +90,31 @@ size_t requestPickBufferRange(Structure* requestingStructure, size_t count) { void resetSelection() { haveSelectionVal = false; - currLocalPickInd = 0; - currPickStructure = nullptr; + currSelectionPickResult = PickResult(); } bool haveSelection() { return haveSelectionVal; } void resetSelectionIfStructure(Structure* s) { - if (haveSelectionVal && currPickStructure == s) { + if (haveSelectionVal && currSelectionPickResult.structure == s) { resetSelection(); } } -std::pair getSelection() { - if (haveSelectionVal) { - return {currPickStructure, currLocalPickInd}; - } else { - return {nullptr, 0}; - } -} +PickResult getSelection() { return currSelectionPickResult; } -void setSelection(std::pair newPick) { - if (newPick.first == nullptr) { +void setSelection(PickResult newPick) { + if (!newPick.isHit) { resetSelection(); } else { haveSelectionVal = true; - currPickStructure = newPick.first; - currLocalPickInd = newPick.second; + currSelectionPickResult = newPick; } } // == Helpers -std::pair globalIndexToLocal(size_t globalInd) { +std::pair globalIndexToLocal(uint64_t globalInd) { // ONEDAY: this could be asymptotically better if we cared @@ -128,8 +122,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}; @@ -139,28 +133,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. @@ -193,7 +187,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 34fef9f7..cb57136f 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,7 +405,7 @@ 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}); + PickResult pickResult = queryPickAtScreenCoords(glm::vec2{p.x, p.y}); pick::setSelection(pickResult); } @@ -761,11 +762,25 @@ void buildPickGui() { ImGui::SetNextWindowSize(ImVec2(rightWindowsWidth, 0.)); ImGui::Begin("Selection", nullptr); - std::pair selection = pick::getSelection(); + PickResult selection = pick::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(); 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..e1d0086e 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -1083,32 +1083,51 @@ void SurfaceMesh::setSurfaceMeshUniforms(render::ShaderProgram& p) { } -void SurfaceMesh::buildPickUI(size_t localPickID) { - - // 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); +void SurfaceMesh::buildPickUI(const PickResult& rawResult) { + 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 +1140,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,7 +1165,8 @@ 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()); @@ -1166,7 +1186,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 +1210,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 +1234,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()); @@ -1403,6 +1426,49 @@ 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 + } else if (rawResult.localIndex < halfedgePickIndStart) { + // Edge pick + result.elementType = MeshElement::EDGE; + result.index = rawResult.localIndex - edgePickIndStart; + + // TODO tEdge + } else if (rawResult.localIndex < cornerPickIndStart) { + // Halfedge pick + result.elementType = MeshElement::HALFEDGE; + result.index = rawResult.localIndex - halfedgePickIndStart; + + // TODO tEdge + } 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 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..370d4653 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -799,14 +799,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) { From 4c7e4094d1f72ef84cdb3330c1be540e3615f4bb Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 3 Feb 2025 06:27:34 +0100 Subject: [PATCH 03/17] show t value along edge for selection --- include/polyscope/curve_network.h | 5 +++-- include/polyscope/elementary_geometry.h | 15 +++++++++++++++ src/CMakeLists.txt | 2 ++ src/curve_network.cpp | 22 +++++++++++++--------- src/elementary_geometry.cpp | 22 ++++++++++++++++++++++ 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/include/polyscope/curve_network.h b/include/polyscope/curve_network.h index e1746d75..8ecc7c4d 100644 --- a/include/polyscope/curve_network.h +++ b/include/polyscope/curve_network.h @@ -39,6 +39,7 @@ struct QuantityTypeHelper { 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 { @@ -191,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 index e69de29b..f569c596 100644 --- a/include/polyscope/elementary_geometry.h +++ b/include/polyscope/elementary_geometry.h @@ -0,0 +1,15 @@ +// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run + +#pragma once + +#include +#include + +#include + +namespace polyscope { + +float computeTValAlongLine(glm::vec3 queryP, glm::vec3 lineStart, glm::vec3 lineEnd); + + +} \ No newline at end of file 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/curve_network.cpp b/src/curve_network.cpp index 82068d73..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" @@ -348,17 +349,18 @@ void CurveNetwork::buildPickUI(const PickResult& rawResult) { switch (result.elementType) { case CurveNetworkElement::NODE: { - buildNodePickUI(result.index); + buildNodePickUI(result); break; } case CurveNetworkElement::EDGE: { - buildEdgePickUI(result.index); + 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(); @@ -379,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(); @@ -481,10 +485,10 @@ CurveNetworkPickResult CurveNetwork::interpretPickResult(const PickResult& rawRe // compute the t \in [0,1] along the edge int32_t iStart = edgeTailInds.getValue(result.index); - int32_t iEnd= edgeTipInds.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"); } diff --git a/src/elementary_geometry.cpp b/src/elementary_geometry.cpp index e69de29b..61c282d4 100644 --- a/src/elementary_geometry.cpp +++ b/src/elementary_geometry.cpp @@ -0,0 +1,22 @@ +// 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; +} + +} // namespace polyscope \ No newline at end of file From 12e9107ea2c1769d17436d569cf4af9089ded4ce Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 3 Feb 2025 06:52:15 +0100 Subject: [PATCH 04/17] pick barycoords in selection --- include/polyscope/elementary_geometry.h | 6 +++++ include/polyscope/surface_mesh.h | 5 ++-- src/elementary_geometry.cpp | 13 ++++++++++ src/surface_mesh.cpp | 33 ++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/include/polyscope/elementary_geometry.h b/include/polyscope/elementary_geometry.h index f569c596..52bc9e02 100644 --- a/include/polyscope/elementary_geometry.h +++ b/include/polyscope/elementary_geometry.h @@ -9,7 +9,13 @@ 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/surface_mesh.h b/include/polyscope/surface_mesh.h index 010d93ca..c7e7d5a6 100644 --- a/include/polyscope/surface_mesh.h +++ b/include/polyscope/surface_mesh.h @@ -52,8 +52,9 @@ struct QuantityTypeHelper { }; struct SurfaceMeshPickResult { - MeshElement elementType; // which kind of element did we click - int64_t index; // index of the clicked element + 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 diff --git a/src/elementary_geometry.cpp b/src/elementary_geometry.cpp index 61c282d4..0ae03717 100644 --- a/src/elementary_geometry.cpp +++ b/src/elementary_geometry.cpp @@ -19,4 +19,17 @@ float computeTValAlongLine(glm::vec3 queryP, glm::vec3 lineStart, glm::vec3 line 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/surface_mesh.cpp b/src/surface_mesh.cpp index e1d0086e..27367961 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" @@ -1170,6 +1170,11 @@ void SurfaceMesh::buildFaceInfoGui(const SurfaceMeshPickResult& result) { 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(); @@ -1446,18 +1451,40 @@ SurfaceMeshPickResult SurfaceMesh::interpretPickResult(const PickResult& rawResu 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; - // TODO tEdge + } else if (rawResult.localIndex < cornerPickIndStart) { // Halfedge pick result.elementType = MeshElement::HALFEDGE; result.index = rawResult.localIndex - halfedgePickIndStart; - // TODO tEdge } else if (rawResult.localIndex < cornerPickIndStart + nCorners()) { // Corner pick result.elementType = MeshElement::CORNER; From 3656699febe63ff782d2cd4b87e49b6565aa5f65 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 3 Feb 2025 07:34:16 +0100 Subject: [PATCH 05/17] vertex/face only selection modes for meshes --- include/polyscope/persistent_value.h | 2 + include/polyscope/surface_mesh.h | 7 +- include/polyscope/types.h | 3 +- src/persistent_value.cpp | 1 + .../opengl/shaders/surface_mesh_shaders.cpp | 10 +- src/surface_mesh.cpp | 116 +++++++++++++----- 6 files changed, 105 insertions(+), 34 deletions(-) diff --git a/include/polyscope/persistent_value.h b/include/polyscope/persistent_value.h index 7d5bed04..517a88b3 100644 --- a/include/polyscope/persistent_value.h +++ b/include/polyscope/persistent_value.h @@ -144,6 +144,7 @@ extern PersistentCache 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/surface_mesh.h b/include/polyscope/surface_mesh.h index c7e7d5a6..e0834a0e 100644 --- a/include/polyscope/surface_mesh.h +++ b/include/polyscope/surface_mesh.h @@ -290,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); @@ -361,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(); @@ -405,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 10a3b5df..894ca864 100644 --- a/include/polyscope/types.h +++ b/include/polyscope/types.h @@ -18,8 +18,9 @@ enum class BackFacePolicy { Identical, Different, Custom, Cull }; enum class PointRenderMode { Sphere = 0, Quad }; enum class MeshElement { VERTEX = 0, FACE, EDGE, HALFEDGE, CORNER }; -enum class CurveNetworkElement { NODE = 0, EDGE }; 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 }; 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/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/surface_mesh.cpp b/src/surface_mesh.cpp index 27367961..2d9c6acf 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -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); @@ -1384,6 +1411,29 @@ 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::MenuItem("Mark edges as used (selectable)", NULL, edgesHaveBeenUsed)) { + markEdgesAsUsed(); + } + if (ImGui::MenuItem("Mark halfedges as used (selectable)", NULL, halfedgesHaveBeenUsed)) { + markHalfedgesAsUsed(); + } + if (ImGui::MenuItem("Mark corners as used (selectable)", NULL, cornersHaveBeenUsed)) { + markCornersAsUsed(); + } ImGui::EndMenu(); } @@ -1694,6 +1744,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 From b9c04d8080e08937e5d2fb258e4eb255acb4d701 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 3 Feb 2025 07:39:47 +0100 Subject: [PATCH 06/17] missing include --- include/polyscope/context.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/polyscope/context.h b/include/polyscope/context.h index 3b4a17c8..daa71101 100644 --- a/include/polyscope/context.h +++ b/include/polyscope/context.h @@ -2,9 +2,9 @@ #pragma once +#include #include #include -#include #include #include @@ -15,6 +15,7 @@ #include #include #include +#include #include #include From c485d52ef341aefb2d88e99570f1132c184cfa09 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 3 Feb 2025 07:53:38 +0100 Subject: [PATCH 07/17] missing uniform --- src/camera_view.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/camera_view.cpp b/src/camera_view.cpp index 5d9ef2ee..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(); } From b4d7ef260379669605b1022ed99305b5d32a2489 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Mon, 3 Feb 2025 08:02:16 +0100 Subject: [PATCH 08/17] missing uniform for volume mesh --- src/volume_mesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index 370d4653..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(); } From 5589bc517b6f442c2563d353765062b3ce8d1adc Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 8 Feb 2025 21:42:05 +0100 Subject: [PATCH 09/17] make naming more consistent for coords/bufferinds --- include/polyscope/view.h | 10 ++++++++-- src/view.cpp | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/include/polyscope/view.h b/include/polyscope/view.h index 296b78e5..e177f83a 100644 --- a/include/polyscope/view.h +++ b/include/polyscope/view.h @@ -137,9 +137,9 @@ 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 bufferIndsToWorldRay(glm::ivec2 bufferInds); glm::vec3 screenCoordsToWorldPosition(glm::vec2 screenCoords); // queries the depth buffer to get full position -glm::vec3 bufferCoordsToWorldPosition(int xPos, int yPos); +glm::vec3 bufferIndsToWorldPosition(glm::ivec2 bufferInds); // Get and set camera from json string std::string getViewAsJson(); @@ -151,7 +151,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. @@ -177,6 +179,10 @@ void processClipPlaneShift(double amount); void processZoom(double amount); void processKeyboardNavigation(ImGuiIO& io); +// deprecated, bad names, see variants above +glm::vec3 bufferCoordsToWorldRay(glm::vec2 bufferCoords); +glm::vec3 bufferCoordsToWorldPosition(int xPos, int yPos); + } // namespace view } // namespace polyscope diff --git a/src/view.cpp b/src/view.cpp index e111f7c3..62159b35 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -102,11 +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) { @@ -513,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(); @@ -532,6 +544,10 @@ glm::vec3 screenCoordsToWorldPosition(glm::vec2 screenCoords) { return bufferCoordsToWorldPosition(xInd, yInd); } +glm::vec3 bufferIndsToWorldPosition(glm::ivec2 bufferInds) { + return bufferCoordsToWorldPosition(bufferInds.x, bufferInds.y); +} + glm::vec3 bufferCoordsToWorldPosition(int xInd, int yInd) { glm::vec2 screenCoords = bufferIndsToScreenCoords(xInd, yInd); From c62b6d5a55f05fde9cd11444d02017ba6f6a2397 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 8 Feb 2025 21:42:30 +0100 Subject: [PATCH 10/17] coord/bufferind renaming --- include/polyscope/pick.h | 2 +- src/pick.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/polyscope/pick.h b/include/polyscope/pick.h index b796f7a0..941aaf51 100644 --- a/include/polyscope/pick.h +++ b/include/polyscope/pick.h @@ -39,7 +39,7 @@ struct PickResult { // Query functions to evaluate a pick. // Internally, these do a render pass to populate relevant information, then query the resulting buffers. PickResult queryPickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates -PickResult queryPickAtBufferCoords(int xPos, int yPos); // takes indices into render buffer +PickResult queryPickAtBufferInds(glm::ivec2 bufferInds); // takes indices into render buffer namespace pick { diff --git a/src/pick.cpp b/src/pick.cpp index 58376c50..a1d45c03 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -12,23 +12,23 @@ namespace polyscope { PickResult queryPickAtScreenCoords(glm::vec2 screenCoords) { int xInd, yInd; - std::tie(xInd, yInd) = view::screenCoordsToBufferInds(screenCoords); - return queryPickAtBufferCoords(xInd, yInd); + glm::ivec2 bufferInds = view::screenCoordsToBufferIndsVec(screenCoords); + return queryPickAtBufferInds(bufferInds); } -PickResult queryPickAtBufferCoords(int xPos, int yPos) { +PickResult queryPickAtBufferInds(glm::ivec2 bufferInds) { PickResult result; // Query the render buffer - result.position = view::bufferCoordsToWorldPosition(xPos, yPos); + result.position = view::bufferIndsToWorldPosition(bufferInds); result.depth = glm::length(result.position - view::getCameraWorldPosition()); // Query the pick buffer - std::pair rawPickResult = pick::pickAtBufferCoords(xPos, yPos); + std::pair rawPickResult = pick::pickAtBufferCoords(bufferInds.x, bufferInds.y); // Transcribe result into return tuple result.structure = rawPickResult.first; - result.bufferCoords = glm::ivec2(xPos, yPos); - result.screenCoords = view::bufferIndsToScreenCoords(xPos, yPos); + result.bufferCoords = bufferInds; + result.screenCoords = view::bufferIndsToScreenCoords(bufferInds); if (rawPickResult.first == nullptr) { result.isHit = false; result.structureType = ""; From eec433a696ef7347867d410fa6f2a3311788cdcd Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 8 Feb 2025 21:42:39 +0100 Subject: [PATCH 11/17] update demo to new picking style --- examples/demo-app/demo_app.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index afa339b8..28636c19 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -808,7 +808,7 @@ void callback() { 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::queryPickAtScreenCoords(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; @@ -818,11 +818,12 @@ void callback() { polyscope::operator<<(std::cout, worldRay) << std::endl; std::cout << " worldPos: "; polyscope::operator<<(std::cout, worldPos) << std::endl; - if (pickPair.first == nullptr) { + 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 From 8452285a862859a08cee0c83a3c98300b01bd9eb Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Sat, 8 Feb 2025 21:57:26 +0100 Subject: [PATCH 12/17] more coords/inds renaming --- include/polyscope/pick.h | 2 +- src/pick.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/polyscope/pick.h b/include/polyscope/pick.h index 941aaf51..5ac10739 100644 --- a/include/polyscope/pick.h +++ b/include/polyscope/pick.h @@ -30,7 +30,7 @@ struct PickResult { std::string structureType = ""; std::string structureName = ""; glm::vec2 screenCoords; - glm::ivec2 bufferCoords; + glm::ivec2 bufferInds; glm::vec3 position; float depth; uint64_t localIndex = INVALID_IND_64; diff --git a/src/pick.cpp b/src/pick.cpp index a1d45c03..0a159490 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -27,7 +27,7 @@ PickResult queryPickAtBufferInds(glm::ivec2 bufferInds) { // Transcribe result into return tuple result.structure = rawPickResult.first; - result.bufferCoords = bufferInds; + result.bufferInds = bufferInds; result.screenCoords = view::bufferIndsToScreenCoords(bufferInds); if (rawPickResult.first == nullptr) { result.isHit = false; From 14c21a4fa7e6049dcdf71eb035237a6549a7a8f3 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Thu, 20 Mar 2025 00:27:16 -0700 Subject: [PATCH 13/17] move stateful selection functions out of pick namespace --- include/polyscope/pick.h | 19 ++++++++------- src/pick.cpp | 52 ++++++++++++++++++++-------------------- src/polyscope.cpp | 15 ++++++------ 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/include/polyscope/pick.h b/include/polyscope/pick.h index 5ac10739..8f98f7bf 100644 --- a/include/polyscope/pick.h +++ b/include/polyscope/pick.h @@ -39,16 +39,8 @@ struct PickResult { // Query functions to evaluate a pick. // Internally, these do a render pass to populate relevant information, then query the resulting buffers. PickResult queryPickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates -PickResult queryPickAtBufferInds(glm::ivec2 bufferInds); // takes indices into render buffer +PickResult queryPickAtBufferInds(glm::ivec2 bufferInds); // takes indices into render buffer -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. // == Stateful picking: track and update a current selection @@ -60,6 +52,15 @@ 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 diff --git a/src/pick.cpp b/src/pick.cpp index 0a159490..00d235e3 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -46,6 +46,32 @@ PickResult queryPickAtBufferInds(glm::ivec2 bufferInds) { 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 { @@ -86,32 +112,6 @@ uint64_t requestPickBufferRange(Structure* requestingStructure, uint64_t count) return ret; } -// == Manage stateful picking - -void resetSelection() { - haveSelectionVal = false; - currSelectionPickResult = PickResult(); -} - -bool haveSelection() { return haveSelectionVal; } - -void resetSelectionIfStructure(Structure* s) { - if (haveSelectionVal && currSelectionPickResult.structure == s) { - resetSelection(); - } -} - -PickResult getSelection() { return currSelectionPickResult; } - -void setSelection(PickResult newPick) { - if (!newPick.isHit) { - resetSelection(); - } else { - haveSelectionVal = true; - currSelectionPickResult = newPick; - } -} - // == Helpers std::pair globalIndexToLocal(uint64_t globalInd) { diff --git a/src/polyscope.cpp b/src/polyscope.cpp index cb57136f..4ec21779 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -406,7 +406,7 @@ void processInputEvents() { if (dragDistSinceLastRelease < dragIgnoreThreshold) { ImVec2 p = ImGui::GetMousePos(); PickResult pickResult = queryPickAtScreenCoords(glm::vec2{p.x, p.y}); - pick::setSelection(pickResult); + setSelection(pickResult); } // Reset the drag distance after any release @@ -415,7 +415,7 @@ void processInputEvents() { // Clear pick if (ImGui::IsMouseReleased(1)) { if (dragDistSinceLastRelease < dragIgnoreThreshold) { - pick::resetSelection(); + resetSelection(); } dragDistSinceLastRelease = 0.0; } @@ -755,20 +755,19 @@ 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); - PickResult selection = pick::getSelection(); + PickResult selection = getSelection(); 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::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()); @@ -1122,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; @@ -1183,7 +1182,7 @@ void removeAllStructures() { } requestRedraw(); - pick::resetSelection(); + resetSelection(); } From be340eaf4c564515606f8be846dcdd3d2309c007 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 21 Mar 2025 00:19:49 -0700 Subject: [PATCH 14/17] don't allow enabling edge selections without an index --- src/surface_mesh.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index 2d9c6acf..9766ce00 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -1425,14 +1425,25 @@ void SurfaceMesh::buildCustomOptionsUI() { ImGui::Separator(); - if (ImGui::MenuItem("Mark edges as used (selectable)", NULL, edgesHaveBeenUsed)) { - markEdgesAsUsed(); - } - if (ImGui::MenuItem("Mark halfedges as used (selectable)", NULL, halfedgesHaveBeenUsed)) { - markHalfedgesAsUsed(); - } - if (ImGui::MenuItem("Mark corners as used (selectable)", NULL, cornersHaveBeenUsed)) { - markCornersAsUsed(); + + 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(); From 79b05e4decbaf4c2799d9b8f771b7e4808e8ced6 Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 21 Mar 2025 01:04:21 -0700 Subject: [PATCH 15/17] fix pick depth for pretty transparency, read depth from pick buffer, deprecate old functions --- examples/demo-app/demo_app.cpp | 5 ++--- include/polyscope/view.h | 4 +--- src/pick.cpp | 12 ++++++++---- src/render/opengl/gl_engine.cpp | 2 +- src/view.cpp | 27 +++++++-------------------- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index 28636c19..34947e98 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -807,7 +807,6 @@ void callback() { std::tie(xInd, yInd) = polyscope::view::screenCoordsToBufferInds(screenCoords); glm::vec3 worldRay = polyscope::view::screenCoordsToWorldRay(screenCoords); - glm::vec3 worldPos = polyscope::view::screenCoordsToWorldPosition(screenCoords); polyscope::PickResult pickResult = polyscope::queryPickAtScreenCoords(screenCoords); std::cout << "Polyscope scene test click " << std::endl; @@ -817,7 +816,7 @@ void callback() { std::cout << " worldRay: "; polyscope::operator<<(std::cout, worldRay) << std::endl; std::cout << " worldPos: "; - polyscope::operator<<(std::cout, worldPos) << std::endl; + polyscope::operator<<(std::cout, pickResult.position) << std::endl; if (pickResult.isHit) { std::cout << " structure: " << pickResult.structureType << " " << pickResult.structureName << " local ind: " << pickResult.localIndex << std::endl; @@ -827,7 +826,7 @@ void callback() { } // 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/view.h b/include/polyscope/view.h index e177f83a..b6654d96 100644 --- a/include/polyscope/view.h +++ b/include/polyscope/view.h @@ -138,8 +138,7 @@ bool getWindowResizable(); // Get world geometry corresponding to a screen pixel (e.g. from a mouse click) glm::vec3 screenCoordsToWorldRay(glm::vec2 screenCoords); glm::vec3 bufferIndsToWorldRay(glm::ivec2 bufferInds); -glm::vec3 screenCoordsToWorldPosition(glm::vec2 screenCoords); // queries the depth buffer to get full position -glm::vec3 bufferIndsToWorldPosition(glm::ivec2 bufferInds); +glm::vec3 screenCoordsAndDepthToWorldPosition(glm::vec2 screenCoords, float clipDepth); // Get and set camera from json string std::string getViewAsJson(); @@ -181,7 +180,6 @@ void processKeyboardNavigation(ImGuiIO& io); // deprecated, bad names, see variants above glm::vec3 bufferCoordsToWorldRay(glm::vec2 bufferCoords); -glm::vec3 bufferCoordsToWorldPosition(int xPos, int yPos); } // namespace view diff --git a/src/pick.cpp b/src/pick.cpp index 00d235e3..16f58f01 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -18,17 +18,21 @@ PickResult queryPickAtScreenCoords(glm::vec2 screenCoords) { PickResult queryPickAtBufferInds(glm::ivec2 bufferInds) { PickResult result; - // Query the render buffer - result.position = view::bufferIndsToWorldPosition(bufferInds); - result.depth = glm::length(result.position - view::getCameraWorldPosition()); - // 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 = ""; 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/view.cpp b/src/view.cpp index 62159b35..5f615d9b 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -538,19 +538,15 @@ glm::vec3 bufferCoordsToWorldRay(glm::vec2 bufferCoords) { return worldRayDir; } -glm::vec3 screenCoordsToWorldPosition(glm::vec2 screenCoords) { - int xInd, yInd; - std::tie(xInd, yInd) = screenCoordsToBufferInds(screenCoords); - return bufferCoordsToWorldPosition(xInd, yInd); -} -glm::vec3 bufferIndsToWorldPosition(glm::ivec2 bufferInds) { - return bufferCoordsToWorldPosition(bufferInds.x, bufferInds.y); -} +glm::vec3 screenCoordsAndDepthToWorldPosition(glm::vec2 screenCoords, float clipDepth) { -glm::vec3 bufferCoordsToWorldPosition(int xInd, int yInd) { + 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}; + } - glm::vec2 screenCoords = bufferIndsToScreenCoords(xInd, yInd); glm::mat4 view = getCameraViewMatrix(); glm::mat4 viewInv = glm::inverse(view); @@ -558,19 +554,10 @@ glm::vec3 bufferCoordsToWorldPosition(int xInd, int yInd) { 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; From 0dd042088dc8c988d134e2c6e83be0391623cbac Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 21 Mar 2025 01:34:02 -0700 Subject: [PATCH 16/17] rename pick functions --- examples/demo-app/demo_app.cpp | 2 +- include/polyscope/pick.h | 4 ++-- src/pick.cpp | 7 ++++--- src/polyscope.cpp | 2 +- test/src/camera_view_test.cpp | 2 +- test/src/curve_network_test.cpp | 2 +- test/src/point_cloud_test.cpp | 4 ++-- test/src/surface_mesh_test.cpp | 6 +++--- 8 files changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/demo-app/demo_app.cpp b/examples/demo-app/demo_app.cpp index 34947e98..7472a9fc 100644 --- a/examples/demo-app/demo_app.cpp +++ b/examples/demo-app/demo_app.cpp @@ -807,7 +807,7 @@ void callback() { std::tie(xInd, yInd) = polyscope::view::screenCoordsToBufferInds(screenCoords); glm::vec3 worldRay = polyscope::view::screenCoordsToWorldRay(screenCoords); - polyscope::PickResult pickResult = polyscope::queryPickAtScreenCoords(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; diff --git a/include/polyscope/pick.h b/include/polyscope/pick.h index 8f98f7bf..536e2951 100644 --- a/include/polyscope/pick.h +++ b/include/polyscope/pick.h @@ -38,8 +38,8 @@ struct PickResult { // Query functions to evaluate a pick. // Internally, these do a render pass to populate relevant information, then query the resulting buffers. -PickResult queryPickAtScreenCoords(glm::vec2 screenCoords); // takes screen coordinates -PickResult queryPickAtBufferInds(glm::ivec2 bufferInds); // takes indices into render buffer +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 diff --git a/src/pick.cpp b/src/pick.cpp index 16f58f01..b7ae2f40 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -10,12 +10,13 @@ namespace polyscope { -PickResult queryPickAtScreenCoords(glm::vec2 screenCoords) { +PickResult pickAtScreenCoords(glm::vec2 screenCoords) { int xInd, yInd; glm::ivec2 bufferInds = view::screenCoordsToBufferIndsVec(screenCoords); - return queryPickAtBufferInds(bufferInds); + return pickAtBufferInds(bufferInds); } -PickResult queryPickAtBufferInds(glm::ivec2 bufferInds) { + +PickResult pickAtBufferInds(glm::ivec2 bufferInds) { PickResult result; // Query the pick buffer diff --git a/src/polyscope.cpp b/src/polyscope.cpp index 4ec21779..e788d5da 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -405,7 +405,7 @@ void processInputEvents() { // Don't pick at the end of a long drag if (dragDistSinceLastRelease < dragIgnoreThreshold) { ImVec2 p = ImGui::GetMousePos(); - PickResult pickResult = queryPickAtScreenCoords(glm::vec2{p.x, p.y}); + PickResult pickResult = pickAtScreenCoords(glm::vec2{p.x, p.y}); setSelection(pickResult); } 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(); } From bf1288fd12d49f288d3f723935e84083d8823f3f Mon Sep 17 00:00:00 2001 From: Nicholas Sharp Date: Fri, 21 Mar 2025 01:36:53 -0700 Subject: [PATCH 17/17] bump to C++17 for gtest --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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")