diff --git a/include/polyscope/context.h b/include/polyscope/context.h index 085be831..7ff902b7 100644 --- a/include/polyscope/context.h +++ b/include/polyscope/context.h @@ -108,6 +108,7 @@ struct Context { uint64_t nextPickBufferInd = 1; std::unordered_map> structureRanges; std::unordered_map> quantityRanges; + std::list> pickCallbacks; // ====================================================== // === Internal globals from internal.h diff --git a/include/polyscope/pick.h b/include/polyscope/pick.h index 72d4ff99..fd9a6121 100644 --- a/include/polyscope/pick.h +++ b/include/polyscope/pick.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -56,6 +57,9 @@ bool haveSelection(); void resetSelectionIfStructure(Structure* s); // If something from this structure is selected, clear the selection // (useful if a structure is being deleted) +std::list>::iterator registerPickCallback(const std::function& f); +void removePickCallback(std::list>::iterator callback); + namespace pick { // Old, deprecated picking API. Use the above functions instead. diff --git a/include/polyscope/polyscope.h b/include/polyscope/polyscope.h index 3d3ae7fe..5bf6e29a 100644 --- a/include/polyscope/polyscope.h +++ b/include/polyscope/polyscope.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -108,6 +109,8 @@ extern bool& doDefaultMouseInteraction; // a callback function used to render a "user" gui extern std::function& userCallback; +extern std::list>& pickCallbacks; + // representative center for all registered structures glm::vec3 center(); diff --git a/include/polyscope/structure.h b/include/polyscope/structure.h index a153e96f..9bbb806f 100644 --- a/include/polyscope/structure.h +++ b/include/polyscope/structure.h @@ -116,6 +116,9 @@ class Structure : public render::ManagedBufferRegistry, public virtual WeakRefer Structure* setTransformGizmoEnabled(bool newVal); bool getTransformGizmoEnabled(); + Structure* setPickable(bool pickable); + bool getPickable(); + protected: // = State PersistentValue enabled; @@ -130,6 +133,7 @@ class Structure : public render::ManagedBufferRegistry, public virtual WeakRefer PersistentValue cullWholeElements; PersistentValue> ignoredSlicePlaneNames; + PersistentValue pickable; // Manage the bounding box & length scale // (this is defined _before_ the object transform is applied. To get the scale/bounding box after transforms, use the diff --git a/include/polyscope/surface_mesh.h b/include/polyscope/surface_mesh.h index e3bb9b82..89a88671 100644 --- a/include/polyscope/surface_mesh.h +++ b/include/polyscope/surface_mesh.h @@ -82,6 +82,12 @@ class SurfaceMesh : public QuantityStructure { virtual void buildCustomOptionsUI() override; virtual void buildPickUI(const PickResult&) override; + void registerVertexPickCallback(const std::function& f); + void registerEdgePickCallback(const std::function& f); + void registerHalfedgePickCallback(const std::function& f); + void registerFacePickCallback(const std::function& f); + void registerCornerPickCallback(const std::function& f); + // Render the the structure on screen virtual void draw() override; virtual void drawDelayed() override; @@ -237,8 +243,8 @@ class SurfaceMesh : public QuantityStructure { size_t nEdges(); // NOTE causes population of nEdgesCount size_t nCornersCount = 0; // = nHalfedges = sum face degree - size_t nCorners() const { return nCornersCount; } - size_t nHalfedges() const { return nCornersCount; } + size_t nCorners() const { return cornerDataSize == INVALID_IND ? nCornersCount : cornerDataSize; } + size_t nHalfedges() const { return halfedgeDataSize == INVALID_IND ? nCornersCount : halfedgeDataSize; } // = Mesh helpers void nestedFacesToFlat(const std::vector>& nestedInds); @@ -357,7 +363,6 @@ class SurfaceMesh : public QuantityStructure { std::vector halfedgeEdgeCorrespondence; // ugly hack used to save a pick buffer attr, filled out lazily w/ edge indices - // Visualization settings PersistentValue surfaceColor; PersistentValue edgeColor; @@ -399,6 +404,15 @@ class SurfaceMesh : public QuantityStructure { void buildHalfedgeInfoGui(const SurfaceMeshPickResult& result); void buildCornerInfoGui(const SurfaceMeshPickResult& result); + std::list> vertexPickCallbacks; + std::list> edgePickCallbacks; + std::list> halfedgePickCallbacks; + std::list> facePickCallbacks; + std::list> cornerPickCallbacks; + std::list>::iterator pickCallbackHandle; + bool hasPickCallback = false; + void handlePick(PickResult result); + // Manage per-element transparency // which (scalar) quantity to set point size from // TODO make these PersistentValue<>? diff --git a/include/polyscope/surface_mesh.ipp b/include/polyscope/surface_mesh.ipp index 7d35e279..d426eba9 100644 --- a/include/polyscope/surface_mesh.ipp +++ b/include/polyscope/surface_mesh.ipp @@ -136,6 +136,8 @@ void SurfaceMesh::setEdgePermutation(const T& perm, size_t expectedSize) { // now that we have edge indexing, enable edge-related stuff markEdgesAsUsed(); + + triangleAllEdgeInds.recomputeIfPopulated(); } template @@ -163,6 +165,8 @@ void SurfaceMesh::setHalfedgePermutation(const T& perm, size_t expectedSize) { } markHalfedgesAsUsed(); + triangleAllEdgeInds.recomputeIfPopulated(); + triangleAllHalfedgeInds.recomputeIfPopulated(); } template @@ -190,6 +194,8 @@ void SurfaceMesh::setCornerPermutation(const T& perm, size_t expectedSize) { } markCornersAsUsed(); + triangleAllEdgeInds.recomputeIfPopulated(); + triangleAllCornerInds.recomputeIfPopulated(); } diff --git a/src/curve_network.cpp b/src/curve_network.cpp index 9a7d35d1..f29a898f 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -179,7 +179,7 @@ void CurveNetwork::drawDelayed() { } void CurveNetwork::drawPick() { - if (!isEnabled()) { + if (!isEnabled() || !getPickable()) { return; } diff --git a/src/pick.cpp b/src/pick.cpp index db1cfe6e..e497a5c7 100644 --- a/src/pick.cpp +++ b/src/pick.cpp @@ -87,6 +87,14 @@ void setSelection(PickResult newPick) { } } +std::list>::iterator registerPickCallback(const std::function& f) { + return state::globalContext.pickCallbacks.insert(state::globalContext.pickCallbacks.end(), f); +} + +void removePickCallback(std::list>::iterator callback) { + state::globalContext.pickCallbacks.erase(callback); +} + namespace pick { diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index 25e24a05..45dadd69 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -118,7 +118,7 @@ void PointCloud::drawDelayed() { } void PointCloud::drawPick() { - if (!isEnabled()) { + if (!isEnabled() || !getPickable()) { return; } diff --git a/src/polyscope.cpp b/src/polyscope.cpp index aa733d37..bd8e6233 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -521,6 +521,9 @@ void processInputEvents() { glm::vec2 screenCoords{io.MousePos.x, io.MousePos.y}; PickResult pickResult = pickAtScreenCoords(screenCoords); setSelection(pickResult); + for (std::function& callback : state::globalContext.pickCallbacks) { + callback(pickResult); + } } } diff --git a/src/state.cpp b/src/state.cpp index d57c5ffa..74a12f13 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -20,6 +20,7 @@ std::vector>& slicePlanes = globalContext.slicePlane std::vector>& widgets = globalContext.widgets; bool& doDefaultMouseInteraction = globalContext.doDefaultMouseInteraction; std::function& userCallback = globalContext.userCallback; +std::list>& pickCallbacks = globalContext.pickCallbacks; } // namespace state } // namespace polyscope diff --git a/src/structure.cpp b/src/structure.cpp index 264e0ff3..ebd9409b 100644 --- a/src/structure.cpp +++ b/src/structure.cpp @@ -15,13 +15,14 @@ Structure::Structure(std::string name_, std::string subtypeName_) transformGizmo(subtypeName + "#" + name + "#transform_gizmo", objectTransform.get(), &objectTransform), cullWholeElements(subtypeName + "#" + name + "#cullWholeElements", false), ignoredSlicePlaneNames(subtypeName + "#" + name + "#ignored_slice_planes", {}), + pickable(subtypeName + "#" + name + "#pickable", true), objectSpaceBoundingBox( std::tuple{glm::vec3{-777, -777, -777}, glm::vec3{-777, -777, -777}}), objectSpaceLengthScale(-777) { validateName(name); } -Structure::~Structure() {}; +Structure::~Structure(){}; Structure* Structure::setEnabled(bool newEnabled) { if (newEnabled == isEnabled()) return this; @@ -318,6 +319,13 @@ Structure* Structure::setTransformGizmoEnabled(bool newVal) { } bool Structure::getTransformGizmoEnabled() { return transformGizmo.enabled.get(); } +Structure* Structure::setPickable(bool newPickable) { + pickable = newPickable; + requestRedraw(); + return this; +} +bool Structure::getPickable() { return pickable.get(); } + Structure* Structure::setIgnoreSlicePlane(std::string name, bool newValue) { if (getIgnoreSlicePlane(name) == newValue) { diff --git a/src/surface_mesh.cpp b/src/surface_mesh.cpp index fe198f81..fb8315d2 100644 --- a/src/surface_mesh.cpp +++ b/src/surface_mesh.cpp @@ -199,6 +199,8 @@ void SurfaceMesh::computeTriangleAllEdgeInds() { triangleAllEdgeInds.data.resize(3 * 3 * nFacesTriangulation()); halfedgeEdgeCorrespondence.resize(nHalfedges()); + bool haveCustomHalfedgeIndex = !halfedgePerm.empty(); + // used to loop over edges std::unordered_map, size_t, polyscope::hash_combine::hash>> seenEdgeInds; @@ -242,7 +244,10 @@ void SurfaceMesh::computeTriangleAllEdgeInds() { thisEdgeInd = seenEdgeInds[key]; } - halfedgeEdgeCorrespondence[start + j] = thisEdgeInd; + size_t he = start + j; + if (haveCustomHalfedgeIndex) he = halfedgePerm[he]; + + halfedgeEdgeCorrespondence[he] = thisEdgeInd; thisTriInds[j] = thisEdgeInd; } @@ -779,7 +784,7 @@ void SurfaceMesh::drawDelayed() { } void SurfaceMesh::drawPick() { - if (!isEnabled()) { + if (!isEnabled() || !getPickable()) { return; } @@ -822,7 +827,7 @@ void SurfaceMesh::drawPick() { } void SurfaceMesh::drawPickDelayed() { - if (!isEnabled()) { + if (!isEnabled() || !getPickable()) { return; } @@ -1188,6 +1193,81 @@ glm::vec2 SurfaceMesh::projectToScreenSpace(glm::vec3 coord) { return glm::vec2{screenPoint.x, screenPoint.y} / screenPoint.w; } +void SurfaceMesh::registerVertexPickCallback(const std::function& f) { + if (!hasPickCallback) { + pickCallbackHandle = registerPickCallback([&](PickResult result) { handlePick(result); }); + hasPickCallback = true; + } + vertexPickCallbacks.insert(vertexPickCallbacks.end(), f); +} +void SurfaceMesh::registerEdgePickCallback(const std::function& f) { + if (!hasPickCallback) { + pickCallbackHandle = registerPickCallback([&](PickResult result) { handlePick(result); }); + hasPickCallback = true; + } + edgePickCallbacks.insert(edgePickCallbacks.end(), f); +} +void SurfaceMesh::registerHalfedgePickCallback(const std::function& f) { + if (!hasPickCallback) { + pickCallbackHandle = registerPickCallback([&](PickResult result) { handlePick(result); }); + hasPickCallback = true; + } + halfedgePickCallbacks.insert(halfedgePickCallbacks.end(), f); +} +void SurfaceMesh::registerFacePickCallback(const std::function& f) { + if (!hasPickCallback) { + pickCallbackHandle = registerPickCallback([&](PickResult result) { handlePick(result); }); + hasPickCallback = true; + } + facePickCallbacks.insert(facePickCallbacks.end(), f); +} +void SurfaceMesh::registerCornerPickCallback(const std::function& f) { + if (!hasPickCallback) { + pickCallbackHandle = registerPickCallback([&](PickResult result) { handlePick(result); }); + hasPickCallback = true; + } + cornerPickCallbacks.insert(cornerPickCallbacks.end(), f); +} +void SurfaceMesh::handlePick(PickResult rawResult) { + if (!rawResult.isHit || rawResult.structureName != name) return; + SurfaceMeshPickResult result = interpretPickResult(rawResult); + + switch (result.elementType) { + case MeshElement::VERTEX: { + for (const std::function& f : vertexPickCallbacks) f(result.index); + break; + } + case MeshElement::FACE: { + for (const std::function& f : facePickCallbacks) f(result.index); + break; + } + case MeshElement::EDGE: { + for (const std::function& f : edgePickCallbacks) f(result.index); + break; + } + case MeshElement::HALFEDGE: { + for (const std::function& f : halfedgePickCallbacks) f(result.index); + + // Also call edge pick callbacks while we're here + if (edgesHaveBeenUsed) { + // 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]; + for (const std::function& f : edgePickCallbacks) f(edgeInd); + } + + break; + } + case MeshElement::CORNER: { + for (const std::function& f : cornerPickCallbacks) f(result.index); + break; + } + }; +} + void SurfaceMesh::buildVertexInfoGui(const SurfaceMeshPickResult& result) { size_t vInd = result.index; size_t displayInd = vInd; @@ -1242,9 +1322,6 @@ void SurfaceMesh::buildFaceInfoGui(const SurfaceMeshPickResult& result) { void SurfaceMesh::buildEdgeInfoGui(const SurfaceMeshPickResult& result) { size_t eInd = result.index; size_t displayInd = eInd; - if (edgePerm.size() > 0) { - displayInd = edgePerm[eInd]; - } ImGui::TextUnformatted(("Edge #" + std::to_string(displayInd)).c_str()); ImGui::Spacing(); @@ -1266,9 +1343,6 @@ void SurfaceMesh::buildEdgeInfoGui(const SurfaceMeshPickResult& result) { void SurfaceMesh::buildHalfedgeInfoGui(const SurfaceMeshPickResult& result) { size_t heInd = result.index; size_t displayInd = heInd; - if (halfedgePerm.size() > 0) { - displayInd = halfedgePerm[heInd]; - } ImGui::TextUnformatted(("Halfedge #" + std::to_string(displayInd)).c_str()); ImGui::Spacing(); diff --git a/src/volume_grid.cpp b/src/volume_grid.cpp index f108a767..882b5c38 100644 --- a/src/volume_grid.cpp +++ b/src/volume_grid.cpp @@ -148,7 +148,7 @@ void VolumeGrid::drawDelayed() { } void VolumeGrid::drawPick() { - if (!isEnabled()) { + if (!isEnabled() || !getPickable()) { return; } @@ -170,7 +170,7 @@ void VolumeGrid::drawPick() { // Draw the actual grid render::engine->setBackfaceCull(true); pickProgram->draw(); - + for (auto& x : quantities) { x.second->drawPick(); } diff --git a/src/volume_mesh.cpp b/src/volume_mesh.cpp index 78094bbc..fd814a99 100644 --- a/src/volume_mesh.cpp +++ b/src/volume_mesh.cpp @@ -473,7 +473,7 @@ void VolumeMesh::drawDelayed() { } void VolumeMesh::drawPick() { - if (!isEnabled()) { + if (!isEnabled() || !getPickable()) { return; }