| |
| |
|
|
| #include "tmeshimage.h" |
| #include "tgl.h" |
| #include "tundo.h" |
| |
| |
| #include "ext/plasticdeformerstorage.h" |
| |
| |
| #include "tcg/tcg_macros.h" |
| #include "tcg/tcg_point_ops.h" |
| #include "tcg/tcg_iterator_ops.h" |
| #include "tcg/tcg_function_types.h" |
| |
| |
| #include <boost/unordered_set.hpp> |
| #include <boost/unordered_map.hpp> |
| |
| using namespace tcg::bgl; |
| |
| #include <boost/graph/breadth_first_search.hpp> |
| |
| |
| #include <stack> |
| |
| #include "plastictool.h" |
| |
| using namespace PlasticToolLocals; |
| |
| |
| |
| |
| |
| namespace { |
| |
| typedef PlasticTool::MeshIndex MeshIndex; |
| typedef TTextureMesh::vertex_type vertex_type; |
| typedef TTextureMesh::edge_type edge_type; |
| typedef TTextureMesh::face_type face_type; |
| |
| |
| |
| bool borderEdge(const TTextureMesh &mesh, int e) { |
| return (mesh.edge(e).facesCount() < 2); |
| } |
| |
| bool borderVertex(const TTextureMesh &mesh, int v) { |
| const TTextureVertex &vx = mesh.vertex(v); |
| |
| tcg::vertex_traits<TTextureVertex>::edges_const_iterator et, |
| eEnd(vx.edgesEnd()); |
| for (et = vx.edgesBegin(); et != eEnd; ++et) { |
| if (borderEdge(mesh, *et)) return true; |
| } |
| |
| return false; |
| } |
| |
| |
| |
| bool testSwapEdge(const TTextureMesh &mesh, int e) { |
| return (mesh.edge(e).facesCount() == 2); |
| } |
| |
| |
| |
| bool testCollapseEdge(const TTextureMesh &mesh, int e) { |
| struct Locals { |
| const TTextureMesh &m_mesh; |
| int m_e; |
| const TTextureMesh::edge_type &m_ed; |
| |
| bool testTrianglesCount() { |
| |
| return (m_mesh.facesCount() > m_ed.facesCount()); |
| } |
| |
| bool testBoundary() { |
| |
| return (!borderVertex(m_mesh, m_ed.vertex(0)) || |
| !borderVertex(m_mesh, m_ed.vertex(1)) || borderEdge(m_mesh, m_e)); |
| } |
| |
| bool testAdjacency() { |
| |
| |
| |
| int f, fCount = m_ed.facesCount(); |
| int allowedV[6], *avt, *avEnd = allowedV + 3 * fCount; |
| |
| for (f = 0, avt = allowedV; f != fCount; ++f, avt += 3) |
| m_mesh.faceVertices(m_ed.face(f), avt[0], avt[1], avt[2]); |
| |
| |
| int v0 = m_ed.vertex(0), v1 = m_ed.vertex(1); |
| |
| const vertex_type &vx0 = m_mesh.vertex(v0); |
| |
| tcg::vertex_traits<vertex_type>::edges_const_iterator et, |
| eEnd = vx0.edgesEnd(); |
| for (et = vx0.edgesBegin(); et != eEnd; ++et) { |
| int otherV = m_mesh.edge(*et).otherVertex(v0); |
| |
| if (m_mesh.edgeInciding(v1, otherV) >= 0) { |
| |
| if (std::find(allowedV, avEnd, otherV) == avEnd) return false; |
| } |
| } |
| |
| return true; |
| } |
| } locals = {mesh, e, mesh.edge(e)}; |
| |
| return (locals.testTrianglesCount() && locals.testBoundary() && |
| locals.testAdjacency()); |
| } |
| |
| } |
| |
| |
| |
| |
| |
| namespace PlasticToolLocals { |
| |
| struct Closer { |
| const TTextureMesh &m_mesh; |
| TPointD m_pos; |
| |
| double dist2(const TTextureMesh::vertex_type &a) { |
| return tcg::point_ops::dist2<TPointD>(a.P(), m_pos); |
| } |
| |
| double dist2(const TTextureMesh::edge_type &a) { |
| const TTextureMesh::vertex_type &avx0 = m_mesh.vertex(a.vertex(0)), |
| &avx1 = m_mesh.vertex(a.vertex(1)); |
| |
| return sq(tcg::point_ops::segDist<TPointD>(avx0.P(), avx1.P(), m_pos)); |
| } |
| |
| bool operator()(const TTextureMesh::vertex_type &a, |
| const TTextureMesh::vertex_type &b) { |
| return (dist2(a) < dist2(b)); |
| } |
| |
| bool operator()(const TTextureMesh::edge_type &a, |
| const TTextureMesh::edge_type &b) { |
| return (dist2(a) < dist2(b)); |
| } |
| }; |
| |
| |
| |
| static std::pair<double, int> closestVertex(const TTextureMesh &mesh, |
| const TPointD &pos) { |
| Closer closer = {mesh, pos}; |
| int vIdx = int( |
| std::min_element(mesh.vertices().begin(), mesh.vertices().end(), closer) |
| .index()); |
| |
| return std::make_pair(closer.dist2(mesh.vertex(vIdx)), vIdx); |
| } |
| |
| |
| |
| static std::pair<double, int> closestEdge(const TTextureMesh &mesh, |
| const TPointD &pos) { |
| Closer closer = {mesh, pos}; |
| int eIdx = |
| int(std::min_element(mesh.edges().begin(), mesh.edges().end(), closer) |
| .index()); |
| |
| return std::make_pair(closer.dist2(mesh.edge(eIdx)), eIdx); |
| } |
| |
| |
| |
| std::pair<double, MeshIndex> closestVertex(const TMeshImage &mi, |
| const TPointD &pos) { |
| std::pair<double, MeshIndex> closest((std::numeric_limits<double>::max)(), |
| MeshIndex()); |
| |
| const TMeshImage::meshes_container &meshes = mi.meshes(); |
| |
| TMeshImage::meshes_container::const_iterator mt, mEnd = meshes.end(); |
| for (mt = meshes.begin(); mt != mEnd; ++mt) { |
| const std::pair<double, int> &candidateIdx = closestVertex(**mt, pos); |
| |
| std::pair<double, MeshIndex> candidate( |
| candidateIdx.first, |
| MeshIndex(mt - meshes.begin(), candidateIdx.second)); |
| |
| if (candidate < closest) closest = candidate; |
| } |
| |
| return closest; |
| } |
| |
| |
| |
| std::pair<double, MeshIndex> closestEdge(const TMeshImage &mi, |
| const TPointD &pos) { |
| std::pair<double, MeshIndex> closest((std::numeric_limits<double>::max)(), |
| MeshIndex()); |
| |
| const TMeshImage::meshes_container &meshes = mi.meshes(); |
| |
| TMeshImage::meshes_container::const_iterator mt, mEnd = meshes.end(); |
| for (mt = meshes.begin(); mt != mEnd; ++mt) { |
| const std::pair<double, int> &candidateIdx = closestEdge(**mt, pos); |
| |
| std::pair<double, MeshIndex> candidate( |
| candidateIdx.first, |
| MeshIndex(mt - meshes.begin(), candidateIdx.second)); |
| |
| if (candidate < closest) closest = candidate; |
| } |
| |
| return closest; |
| } |
| |
| } |
| |
| |
| |
| |
| |
| namespace { |
| |
| struct EdgeCut { |
| int m_vIdx; |
| int m_eIdx; |
| |
| EdgeCut(int vIdx, int eIdx) : m_vIdx(vIdx), m_eIdx(eIdx) {} |
| }; |
| |
| struct VertexOccurrence { |
| int m_count; |
| int m_adjacentEdgeIdx[2]; |
| }; |
| |
| |
| |
| bool buildEdgeCuts(const TMeshImage &mi, |
| const PlasticTool::MeshSelection &edgesSelection, |
| int &meshIdx, std::vector<EdgeCut> &edgeCuts) { |
| typedef PlasticTool::MeshSelection::objects_container edges_container; |
| typedef PlasticTool::MeshIndex MeshIndex; |
| typedef boost::unordered_map<int, VertexOccurrence> VertexOccurrencesMap; |
| |
| struct locals { |
| static bool differentMesh(const MeshIndex &a, const MeshIndex &b) { |
| return (a.m_meshIdx != b.m_meshIdx); |
| } |
| |
| static int testSingleMesh(const edges_container &edges) { |
| assert(!edges.empty()); |
| return (std::find_if(edges.begin(), edges.end(), |
| tcg::bind2nd(&differentMesh, edges.front())) == |
| edges.end()) |
| ? edges.front().m_meshIdx |
| : -1; |
| } |
| |
| static bool testNoBoundaryEdge(const TTextureMesh &mesh, |
| const edges_container &edges) { |
| edges_container::const_iterator et, eEnd = edges.end(); |
| for (et = edges.begin(); et != eEnd; ++et) |
| if (::borderEdge(mesh, et->m_idx)) return false; |
| |
| return true; |
| } |
| |
| static bool buildVertexOccurrences( |
| const TTextureMesh &mesh, const edges_container &edges, |
| VertexOccurrencesMap &vertexOccurrences) { |
| |
| edges_container::const_iterator et, eEnd = edges.end(); |
| for (et = edges.begin(); et != eEnd; ++et) { |
| const edge_type &ed = mesh.edge(et->m_idx); |
| int v0 = ed.vertex(0), v1 = ed.vertex(1); |
| |
| VertexOccurrence &vo0 = vertexOccurrences[v0], |
| &vo1 = vertexOccurrences[v1]; |
| |
| if (vo0.m_count > 1 || vo1.m_count > 1) return false; |
| |
| vo0.m_adjacentEdgeIdx[vo0.m_count++] = |
| vo1.m_adjacentEdgeIdx[vo1.m_count++] = et->m_idx; |
| } |
| |
| return true; |
| } |
| |
| static bool buildEdgeCuts(const TTextureMesh &mesh, |
| const edges_container &edges, |
| std::vector<EdgeCut> &edgeCuts) { |
| VertexOccurrencesMap vertexOccurrences; |
| if (!buildVertexOccurrences(mesh, edges, vertexOccurrences)) return false; |
| |
| |
| int endPoints[2]; |
| int epCount = 0; |
| |
| VertexOccurrencesMap::iterator ot, oEnd = vertexOccurrences.end(); |
| for (ot = vertexOccurrences.begin(); ot != oEnd; ++ot) { |
| if (ot->second.m_count == 1) { |
| if (epCount > 1) return false; |
| |
| endPoints[epCount++] = ot->first; |
| } |
| } |
| |
| if (epCount != 2) return false; |
| |
| |
| |
| int *ept, *epEnd = endPoints + 2; |
| |
| ept = std::find_if(endPoints, epEnd, tcg::bind1st(&borderVertex, mesh)); |
| if (ept == epEnd) { |
| |
| if (edges.size() < 2) |
| return false; |
| |
| ept = endPoints; |
| } |
| |
| |
| |
| |
| edgeCuts.push_back(EdgeCut( |
| *ept, vertexOccurrences[*ept].m_adjacentEdgeIdx[0])); |
| |
| int e, eCount = int(edges.size()); |
| for (e = 1; e != eCount; ++e) { |
| const EdgeCut &lastCut = edgeCuts.back(); |
| |
| int vIdx = mesh.edge(lastCut.m_eIdx).otherVertex(lastCut.m_vIdx); |
| |
| const int(&adjEdges)[2] = vertexOccurrences[vIdx].m_adjacentEdgeIdx; |
| int eIdx = (adjEdges[0] == lastCut.m_eIdx) ? adjEdges[1] : adjEdges[0]; |
| |
| edgeCuts.push_back(EdgeCut(vIdx, eIdx)); |
| } |
| |
| return true; |
| } |
| }; |
| |
| const edges_container &edges = edgesSelection.objects(); |
| |
| |
| if (edges.empty()) return false; |
| |
| |
| meshIdx = locals::testSingleMesh(edges); |
| if (meshIdx < 0) return false; |
| |
| const TTextureMesh &mesh = *mi.meshes()[meshIdx]; |
| |
| |
| return (locals::testNoBoundaryEdge(mesh, edges) && |
| locals::buildEdgeCuts(mesh, edges, edgeCuts)); |
| } |
| |
| |
| |
| inline bool testCutMesh(const TMeshImage &mi, |
| const PlasticTool::MeshSelection &edgesSelection) { |
| std::vector<EdgeCut> edgeCuts; |
| int meshIdx; |
| |
| return buildEdgeCuts(mi, edgesSelection, meshIdx, edgeCuts); |
| } |
| |
| |
| |
| void slitMesh(TTextureMesh &mesh, |
| int e) |
| { |
| TTextureMesh::edge_type &ed = mesh.edge(e); |
| |
| assert(ed.facesCount() == 2); |
| |
| |
| TTextureMesh::edge_type edDup(ed.vertex(0), ed.vertex(1)); |
| |
| int f = ed.face(1); |
| |
| edDup.addFace(f); |
| ed.eraseFace(ed.facesBegin() + 1); |
| |
| int eDup = mesh.addEdge(edDup); |
| |
| |
| TTextureMesh::face_type &fc = mesh.face(f); |
| |
| (fc.edge(0) == e) |
| ? fc.setEdge(0, eDup) |
| : (fc.edge(1) == e) ? fc.setEdge(1, eDup) : fc.setEdge(2, eDup); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void cutEdge(TTextureMesh &mesh, const EdgeCut &edgeCut) { |
| struct locals { |
| static void transferEdge(TTextureMesh &mesh, int e, int vFrom, int vTo) { |
| edge_type &ed = mesh.edge(e); |
| |
| vertex_type &vxFrom = mesh.vertex(vFrom), &vxTo = mesh.vertex(vTo); |
| |
| (ed.vertex(0) == vFrom) ? ed.setVertex(0, vTo) : ed.setVertex(1, vTo); |
| |
| vxTo.addEdge(e); |
| vxFrom.eraseEdge( |
| std::find(vxFrom.edges().begin(), vxFrom.edges().end(), e)); |
| } |
| |
| static void transferFace(TTextureMesh &mesh, int eFrom, int eTo) { |
| edge_type &edFrom = mesh.edge(eFrom), &edTo = mesh.edge(eTo); |
| |
| int f = mesh.edge(eFrom).face(1); |
| { |
| face_type &fc = mesh.face(f); |
| |
| (fc.edge(0) == eFrom) |
| ? fc.setEdge(0, eTo) |
| : (fc.edge(1) == eFrom) ? fc.setEdge(1, eTo) : fc.setEdge(2, eTo); |
| |
| edTo.addFace(f); |
| edFrom.eraseFace(edFrom.facesBegin() + 1); |
| } |
| } |
| |
| }; |
| |
| int vOrig = edgeCut.m_vIdx, eOrig = edgeCut.m_eIdx; |
| |
| |
| int vDup = mesh.addVertex(vertex_type(mesh.vertex(vOrig).P())); |
| |
| int e = eOrig; |
| if (mesh.edge(e).facesCount() == 2) { |
| |
| e = mesh.addEdge(edge_type(vDup, mesh.edge(eOrig).otherVertex(vOrig))); |
| |
| |
| locals::transferFace(mesh, eOrig, e); |
| } else { |
| |
| locals::transferEdge(mesh, eOrig, vOrig, vDup); |
| } |
| |
| |
| |
| int f = mesh.edge(e).face(0); |
| |
| while (f >= 0) { |
| |
| int otherE = mesh.otherFaceEdge(f, mesh.edge(e).otherVertex(vDup)); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| locals::transferEdge(mesh, otherE, vOrig, vDup); |
| |
| |
| e = otherE; |
| f = mesh.edge(otherE).otherFace(f); |
| } |
| } |
| |
| |
| } |
| namespace locals_ { |
| |
| struct VertexesRecorder |
| { |
| boost::unordered_set<int> &m_examinedVertexes; |
| |
| public: |
| typedef boost::on_examine_vertex event_filter; |
| |
| public: |
| VertexesRecorder(boost::unordered_set<int> &examinedVertexes) |
| : m_examinedVertexes(examinedVertexes) {} |
| |
| void operator()(int v, const TTextureMesh &) { m_examinedVertexes.insert(v); } |
| }; |
| } |
| namespace { |
| |
| void splitUnconnectedMesh(TMeshImage &mi, int meshIdx) { |
| struct locals { |
| static void buildConnectedComponent(const TTextureMesh &mesh, |
| boost::unordered_set<int> &vertexes) { |
| |
| std::unique_ptr<UCHAR[]> colorMapP(new UCHAR[mesh.vertices().nodesCount()]()); |
| |
| locals_::VertexesRecorder vertexesRecorder(vertexes); |
| std::stack<int> verticesQueue; |
| |
| |
| boost::breadth_first_visit( |
| mesh, int(mesh.vertices().begin().index()), verticesQueue, |
| boost::make_bfs_visitor(vertexesRecorder), colorMapP.get()); |
| } |
| }; |
| |
| |
| TTextureMesh &origMesh = *mi.meshes()[meshIdx]; |
| |
| boost::unordered_set<int> firstComponent; |
| locals::buildConnectedComponent(origMesh, firstComponent); |
| |
| if (firstComponent.size() == origMesh.verticesCount()) return; |
| |
| |
| |
| TTextureMeshP dupMeshPtr(new TTextureMesh(origMesh)); |
| TTextureMesh &dupMesh = *dupMeshPtr; |
| |
| TTextureMesh::vertices_container &vertices = origMesh.vertices(); |
| |
| TTextureMesh::vertices_container::iterator vt, vEnd = vertices.end(); |
| for (vt = vertices.begin(); vt != vEnd;) { |
| int v = int(vt.index()); |
| ++vt; |
| |
| if (firstComponent.count(v)) |
| dupMesh.removeVertex(v); |
| else |
| origMesh.removeVertex(v); |
| } |
| |
| dupMesh.squeeze(); |
| origMesh.squeeze(); |
| |
| mi.meshes().push_back(dupMeshPtr); |
| } |
| |
| |
| |
| void splitMesh(TMeshImage &mi, int meshIdx, int lastBoundaryVertex) { |
| |
| |
| |
| TTextureMesh &mesh = *mi.meshes()[meshIdx]; |
| |
| int e; |
| { |
| const vertex_type &lbVx = mesh.vertex(lastBoundaryVertex); |
| |
| vertex_type::edges_const_iterator et = std::find_if( |
| lbVx.edgesBegin(), lbVx.edgesEnd(), tcg::bind1st(&borderEdge, mesh)); |
| assert(et != lbVx.edgesEnd()); |
| |
| e = *et; |
| } |
| |
| cutEdge(mesh, EdgeCut(lastBoundaryVertex, e)); |
| |
| |
| |
| splitUnconnectedMesh(mi, meshIdx); |
| } |
| |
| |
| |
| bool cutMesh(TMeshImage &mi, const PlasticTool::MeshSelection &edgesSelection) { |
| struct locals { |
| static int lastVertex(const TTextureMesh &mesh, |
| const std::vector<EdgeCut> &edgeCuts) { |
| return mesh.edge(edgeCuts.back().m_eIdx) |
| .otherVertex(edgeCuts.back().m_vIdx); |
| } |
| |
| static int lastBoundaryVertex(const TTextureMesh &mesh, |
| const std::vector<EdgeCut> &edgeCuts) { |
| int v = lastVertex(mesh, edgeCuts); |
| return ::borderVertex(mesh, v) ? v : -1; |
| } |
| |
| }; |
| |
| std::vector<EdgeCut> edgeCuts; |
| int meshIdx; |
| |
| if (!::buildEdgeCuts(mi, edgesSelection, meshIdx, edgeCuts)) return false; |
| |
| TTextureMesh &mesh = *mi.meshes()[meshIdx]; |
| |
| int lastBoundaryVertex = locals::lastBoundaryVertex(mesh, edgeCuts); |
| |
| |
| |
| std::vector<EdgeCut>::iterator ecBegin = edgeCuts.begin(); |
| |
| if (!::borderVertex(mesh, ecBegin->m_vIdx)) { |
| ::slitMesh(mesh, ecBegin->m_eIdx); |
| ++ecBegin; |
| } |
| |
| |
| std::for_each(ecBegin, edgeCuts.end(), tcg::bind1st(&cutEdge, mesh)); |
| |
| |
| |
| if (lastBoundaryVertex >= 0) splitMesh(mi, meshIdx, lastBoundaryVertex); |
| |
| return true; |
| } |
| |
| } |
| |
| |
| |
| |
| |
| namespace { |
| |
| class MoveVertexUndo_Mesh final : public TUndo { |
| int m_row, m_col; |
| |
| std::vector<MeshIndex> m_vIdxs; |
| std::vector<TPointD> m_origVxsPos; |
| TPointD m_posShift; |
| |
| public: |
| MoveVertexUndo_Mesh(const std::vector<MeshIndex> &vIdxs, |
| const std::vector<TPointD> &origVxsPos, |
| const TPointD &posShift) |
| : m_row(::row()) |
| , m_col(::column()) |
| , m_vIdxs(vIdxs) |
| , m_origVxsPos(origVxsPos) |
| , m_posShift(posShift) { |
| assert(m_vIdxs.size() == m_origVxsPos.size()); |
| } |
| |
| int getSize() const override { |
| return int(sizeof(*this) + |
| m_vIdxs.size() * (sizeof(int) + 2 * sizeof(TPointD))); |
| } |
| |
| void redo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| l_plasticTool.setMeshVertexesSelection(m_vIdxs); |
| l_plasticTool.moveVertex_mesh(m_origVxsPos, m_posShift); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool |
| .notifyImageChanged(); |
| } |
| |
| void undo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| l_plasticTool.setMeshVertexesSelection(m_vIdxs); |
| l_plasticTool.moveVertex_mesh(m_origVxsPos, TPointD()); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool.notifyImageChanged(); |
| } |
| }; |
| |
| |
| |
| class SwapEdgeUndo final : public TUndo { |
| int m_row, m_col; |
| mutable MeshIndex m_edgeIdx; |
| |
| public: |
| SwapEdgeUndo(const MeshIndex &edgeIdx) |
| : m_row(::row()), m_col(::column()), m_edgeIdx(edgeIdx) {} |
| |
| int getSize() const override { return sizeof(*this); } |
| |
| void redo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| const TMeshImageP &mi = TMeshImageP(TTool::getImage(true)); |
| assert(mi); |
| |
| |
| TTextureMesh &mesh = *mi->meshes()[m_edgeIdx.m_meshIdx]; |
| |
| m_edgeIdx.m_idx = mesh.swapEdge(m_edgeIdx.m_idx); |
| assert(m_edgeIdx.m_idx >= 0); |
| |
| |
| PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); |
| |
| |
| l_plasticTool.setMeshEdgesSelection(m_edgeIdx); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool.notifyImageChanged(); |
| } |
| |
| void undo() const override { redo(); } |
| |
| }; |
| |
| |
| |
| class TTextureMeshUndo : public TUndo { |
| protected: |
| int m_row, m_col; |
| |
| int m_meshIdx; |
| mutable TTextureMesh m_origMesh; |
| |
| public: |
| TTextureMeshUndo(int meshIdx) |
| : m_row(::row()), m_col(::column()), m_meshIdx(meshIdx) {} |
| |
| |
| int getSize() const override { return 1 << 20; } |
| |
| TMeshImageP getMeshImage() const { |
| const TMeshImageP &mi = TMeshImageP(TTool::getImage(true)); |
| assert(mi); |
| |
| return mi; |
| } |
| }; |
| |
| |
| |
| class CollapseEdgeUndo final : public TTextureMeshUndo { |
| int m_eIdx; |
| |
| public: |
| CollapseEdgeUndo(const MeshIndex &edgeIdx) |
| : TTextureMeshUndo(edgeIdx.m_meshIdx), m_eIdx(edgeIdx.m_idx) {} |
| |
| void redo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| const TMeshImageP &mi = getMeshImage(); |
| |
| |
| TTextureMesh &mesh = *mi->meshes()[m_meshIdx]; |
| m_origMesh = mesh; |
| |
| |
| mesh.collapseEdge(m_eIdx); |
| mesh.squeeze(); |
| |
| |
| PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); |
| |
| |
| l_plasticTool.clearMeshSelections(); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool.notifyImageChanged(); |
| } |
| |
| void undo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| const TMeshImageP &mi = getMeshImage(); |
| |
| |
| TTextureMesh &mesh = *mi->meshes()[m_meshIdx]; |
| mesh = m_origMesh; |
| |
| PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); |
| |
| |
| l_plasticTool.setMeshEdgesSelection(MeshIndex(m_meshIdx, m_eIdx)); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool.notifyImageChanged(); |
| } |
| }; |
| |
| |
| |
| class SplitEdgeUndo final : public TTextureMeshUndo { |
| int m_eIdx; |
| |
| public: |
| SplitEdgeUndo(const MeshIndex &edgeIdx) |
| : TTextureMeshUndo(edgeIdx.m_meshIdx), m_eIdx(edgeIdx.m_idx) {} |
| |
| void redo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| const TMeshImageP &mi = getMeshImage(); |
| |
| |
| TTextureMesh &mesh = *mi->meshes()[m_meshIdx]; |
| m_origMesh = mesh; |
| |
| |
| mesh.splitEdge(m_eIdx); |
| |
| |
| |
| assert(mesh.vertices().size() == mesh.vertices().nodesCount()); |
| assert(mesh.edges().size() == mesh.edges().nodesCount()); |
| assert(mesh.faces().size() == mesh.faces().nodesCount()); |
| |
| PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); |
| |
| l_plasticTool.clearMeshSelections(); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool.notifyImageChanged(); |
| } |
| |
| void undo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| const TMeshImageP &mi = getMeshImage(); |
| |
| |
| TTextureMesh &mesh = *mi->meshes()[m_meshIdx]; |
| mesh = m_origMesh; |
| |
| PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); |
| |
| |
| l_plasticTool.setMeshEdgesSelection(MeshIndex(m_meshIdx, m_eIdx)); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool.notifyImageChanged(); |
| } |
| }; |
| |
| |
| |
| class CutEdgesUndo final : public TUndo { |
| int m_row, m_col; |
| TMeshImageP m_origImage; |
| |
| PlasticTool::MeshSelection m_edgesSelection; |
| |
| public: |
| CutEdgesUndo(const PlasticTool::MeshSelection &edgesSelection) |
| : m_row(::row()) |
| , m_col(::column()) |
| , m_origImage(TTool::getImage(false)->cloneImage()) |
| , m_edgesSelection(edgesSelection) {} |
| |
| int getSize() const override { return 1 << 20; } |
| |
| bool do_() const { |
| TMeshImageP mi = TTool::getImage(true); |
| |
| if (::cutMesh(*mi, m_edgesSelection)) { |
| PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); |
| |
| l_plasticTool.clearMeshSelections(); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool.notifyImageChanged(); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void redo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| bool ret = do_(); |
| (void)ret; |
| assert(ret); |
| } |
| |
| void undo() const override { |
| PlasticTool::TemporaryActivation tempActivate(m_row, m_col); |
| |
| TMeshImageP mi = TTool::getImage(true); |
| |
| |
| *mi = *m_origImage; |
| |
| PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); |
| |
| |
| l_plasticTool.setMeshEdgesSelection(m_edgesSelection); |
| |
| l_plasticTool.invalidate(); |
| l_plasticTool.notifyImageChanged(); |
| } |
| }; |
| |
| } |
| |
| |
| |
| |
| |
| void PlasticTool::storeMeshImage() { |
| TMeshImageP mi = getImage(false); |
| |
| if (mi != m_mi) { |
| m_mi = mi; |
| clearMeshSelections(); |
| } |
| } |
| |
| |
| |
| void PlasticTool::setMeshSelection(MeshSelection &target, |
| const MeshSelection &newSel) { |
| if (newSel.isEmpty()) { |
| target.selectNone(); |
| target.makeNotCurrent(); |
| |
| return; |
| } |
| |
| target.setObjects(newSel.objects()); |
| |
| target.notifyView(); |
| target.makeCurrent(); |
| } |
| |
| |
| |
| void PlasticTool::toggleMeshSelection(MeshSelection &target, |
| const MeshSelection &addition) { |
| typedef MeshSelection::objects_container objects_container; |
| |
| const objects_container &storedIdxs = target.objects(); |
| const objects_container &addedIdxs = addition.objects(); |
| |
| |
| objects_container selectedIdxs; |
| |
| if (target.contains(addition)) { |
| std::set_difference(storedIdxs.begin(), storedIdxs.end(), addedIdxs.begin(), |
| addedIdxs.end(), std::back_inserter(selectedIdxs)); |
| } else { |
| std::set_union(storedIdxs.begin(), storedIdxs.end(), addedIdxs.begin(), |
| addedIdxs.end(), std::back_inserter(selectedIdxs)); |
| } |
| |
| setMeshSelection(target, selectedIdxs); |
| } |
| |
| |
| |
| void PlasticTool::clearMeshSelections() { |
| m_mvHigh = m_meHigh = MeshIndex(); |
| |
| m_mvSel.selectNone(); |
| m_mvSel.makeNotCurrent(); |
| |
| m_meSel.selectNone(); |
| m_meSel.makeNotCurrent(); |
| } |
| |
| |
| |
| void PlasticTool::setMeshVertexesSelection(const MeshSelection &vSel) { |
| setMeshSelection(m_meSel, MeshSelection()), setMeshSelection(m_mvSel, vSel); |
| } |
| |
| |
| |
| void PlasticTool::toggleMeshVertexesSelection(const MeshSelection &vSel) { |
| setMeshSelection(m_meSel, MeshSelection()), |
| toggleMeshSelection(m_mvSel, vSel); |
| } |
| |
| |
| |
| void PlasticTool::setMeshEdgesSelection(const MeshSelection &eSel) { |
| setMeshSelection(m_meSel, eSel), setMeshSelection(m_mvSel, MeshSelection()); |
| } |
| |
| |
| |
| void PlasticTool::toggleMeshEdgesSelection(const MeshSelection &eSel) { |
| toggleMeshSelection(m_meSel, eSel), |
| setMeshSelection(m_mvSel, MeshSelection()); |
| } |
| |
| |
| |
| void PlasticTool::mouseMove_mesh(const TPointD &pos, const TMouseEvent &me) { |
| |
| m_pos = pos; |
| |
| m_mvHigh = MeshIndex(); |
| |
| if (m_mi) { |
| |
| std::pair<double, MeshIndex> closestVertex = ::closestVertex(*m_mi, pos), |
| closestEdge = ::closestEdge(*m_mi, pos); |
| |
| |
| const double hDistSq = sq(getPixelSize() * MESH_HIGHLIGHT_DISTANCE); |
| |
| m_mvHigh = m_meHigh = MeshIndex(); |
| |
| if (closestEdge.first < hDistSq) m_meHigh = closestEdge.second; |
| |
| if (closestVertex.first < hDistSq) |
| m_mvHigh = closestVertex.second, m_meHigh = MeshIndex(); |
| } |
| |
| assert(!(m_mvHigh && |
| m_meHigh)); |
| |
| invalidate(); |
| } |
| |
| |
| |
| void PlasticTool::leftButtonDown_mesh(const TPointD &pos, |
| const TMouseEvent &me) { |
| struct Locals { |
| PlasticTool *m_this; |
| |
| void updateSelection(MeshSelection &sel, const MeshIndex &idx, |
| const TMouseEvent &me) { |
| if (idx) { |
| MeshSelection newSel(idx); |
| |
| if (me.isCtrlPressed()) |
| m_this->toggleMeshSelection(sel, newSel); |
| else if (!sel.contains(newSel)) |
| m_this->setMeshSelection(sel, newSel); |
| } else |
| m_this->setMeshSelection(sel, MeshSelection()); |
| } |
| |
| static TPointD vertexPos(const TMeshImage &mi, const MeshIndex &meshIdx) { |
| return mi.meshes()[meshIdx.m_meshIdx]->vertex(meshIdx.m_idx).P(); |
| } |
| |
| } locals = {this}; |
| |
| |
| m_pressedPos = m_pos = pos; |
| |
| |
| locals.updateSelection(m_mvSel, m_mvHigh, me); |
| locals.updateSelection(m_meSel, m_meHigh, me); |
| |
| |
| if (!m_mvSel.isEmpty()) { |
| m_pressedVxsPos = std::vector<TPointD>( |
| tcg::make_cast_it(m_mvSel.objects().begin(), |
| tcg::bind1st(&Locals::vertexPos, *m_mi)), |
| tcg::make_cast_it(m_mvSel.objects().end(), |
| tcg::bind1st(&Locals::vertexPos, *m_mi))); |
| } |
| |
| |
| invalidate(); |
| } |
| |
| |
| |
| void PlasticTool::leftButtonDrag_mesh(const TPointD &pos, |
| const TMouseEvent &me) { |
| |
| m_pos = pos; |
| |
| if (!m_mvSel.isEmpty()) { |
| moveVertex_mesh(m_pressedVxsPos, pos - m_pressedPos); |
| invalidate(); |
| } |
| } |
| |
| |
| |
| void PlasticTool::leftButtonUp_mesh(const TPointD &pos, const TMouseEvent &me) { |
| |
| m_pos = pos; |
| |
| if (m_dragged && !m_mvSel.isEmpty()) { |
| TUndoManager::manager()->add(new MoveVertexUndo_Mesh( |
| m_mvSel.objects(), m_pressedVxsPos, pos - m_pressedPos)); |
| |
| invalidate(); |
| notifyImageChanged(); |
| } |
| } |
| |
| |
| |
| void PlasticTool::addContextMenuActions_mesh(QMenu *menu) { |
| bool ret = true; |
| |
| if (!m_meSel.isEmpty()) { |
| if (m_meSel.hasSingleObject()) { |
| const MeshIndex &mIdx = m_meSel.objects().front(); |
| const TTextureMesh &mesh = *m_mi->meshes()[mIdx.m_meshIdx]; |
| |
| if (::testSwapEdge(mesh, mIdx.m_idx)) { |
| QAction *swapEdge = menu->addAction(tr("Swap Edge")); |
| ret = ret && connect(swapEdge, SIGNAL(triggered()), &l_plasticTool, |
| SLOT(swapEdge_mesh_undo())); |
| } |
| |
| if (::testCollapseEdge(mesh, mIdx.m_idx)) { |
| QAction *collapseEdge = menu->addAction(tr("Collapse Edge")); |
| ret = ret && connect(collapseEdge, SIGNAL(triggered()), &l_plasticTool, |
| SLOT(collapseEdge_mesh_undo())); |
| } |
| |
| QAction *splitEdge = menu->addAction(tr("Split Edge")); |
| ret = ret && connect(splitEdge, SIGNAL(triggered()), &l_plasticTool, |
| SLOT(splitEdge_mesh_undo())); |
| } |
| |
| if (::testCutMesh(*m_mi, m_meSel)) { |
| QAction *cutEdges = menu->addAction(tr("Cut Mesh")); |
| ret = ret && connect(cutEdges, SIGNAL(triggered()), &l_plasticTool, |
| SLOT(cutEdges_mesh_undo())); |
| } |
| |
| menu->addSeparator(); |
| } |
| |
| assert(ret); |
| } |
| |
| |
| |
| void PlasticTool::moveVertex_mesh(const std::vector<TPointD> &origVxsPos, |
| const TPointD &posShift) { |
| if (m_mvSel.isEmpty() || !m_mi) return; |
| |
| assert(origVxsPos.size() == m_mvSel.objects().size()); |
| |
| |
| TMeshImageP mi = getImage(true); |
| assert(m_mi == mi); |
| |
| int v, vCount = int(m_mvSel.objects().size()); |
| for (v = 0; v != vCount; ++v) { |
| const MeshIndex &meshIndex = m_mvSel.objects()[v]; |
| |
| TTextureMesh &mesh = *mi->meshes()[meshIndex.m_meshIdx]; |
| mesh.vertex(meshIndex.m_idx).P() = origVxsPos[v] + posShift; |
| } |
| |
| |
| PlasticDeformerStorage::instance()->invalidateMeshImage( |
| mi.getPointer(), PlasticDeformerStorage::MESH); |
| } |
| |
| |
| |
| void PlasticTool::swapEdge_mesh_undo() { |
| if (!(m_mi && m_meSel.hasSingleObject())) return; |
| |
| |
| { |
| const MeshIndex &eIdx = m_meSel.objects().front(); |
| const TTextureMesh &mesh = *m_mi->meshes()[eIdx.m_meshIdx]; |
| |
| if (!::testSwapEdge(mesh, eIdx.m_idx)) return; |
| } |
| |
| |
| std::unique_ptr<TUndo> undo(new SwapEdgeUndo(m_meSel.objects().front())); |
| undo->redo(); |
| |
| TUndoManager::manager()->add(undo.release()); |
| } |
| |
| |
| |
| void PlasticTool::collapseEdge_mesh_undo() { |
| if (!(m_mi && m_meSel.hasSingleObject())) return; |
| |
| |
| { |
| const MeshIndex &eIdx = m_meSel.objects().front(); |
| const TTextureMesh &mesh = *m_mi->meshes()[eIdx.m_meshIdx]; |
| |
| if (!::testCollapseEdge(mesh, eIdx.m_idx)) return; |
| } |
| |
| |
| std::unique_ptr<TUndo> undo(new CollapseEdgeUndo(m_meSel.objects().front())); |
| undo->redo(); |
| |
| TUndoManager::manager()->add(undo.release()); |
| } |
| |
| |
| |
| void PlasticTool::splitEdge_mesh_undo() { |
| if (!(m_mi && m_meSel.hasSingleObject())) return; |
| |
| std::unique_ptr<TUndo> undo(new SplitEdgeUndo(m_meSel.objects().front())); |
| undo->redo(); |
| |
| TUndoManager::manager()->add(undo.release()); |
| } |
| |
| |
| |
| void PlasticTool::cutEdges_mesh_undo() { |
| if (!m_mi) return; |
| |
| std::unique_ptr<CutEdgesUndo> undo(new CutEdgesUndo(m_meSel.objects())); |
| |
| if (undo->do_()) TUndoManager::manager()->add(undo.release()); |
| } |
| |
| |
| |
| void PlasticTool::draw_mesh() { |
| struct Locals { |
| PlasticTool *m_this; |
| double m_pixelSize; |
| |
| void drawLine(const TPointD &a, const TPointD &b) { |
| glVertex2d(a.x, a.y); |
| glVertex2d(b.x, b.y); |
| } |
| |
| void drawVertexSelections() { |
| typedef MeshSelection::objects_container objects_container; |
| const objects_container &objects = m_this->m_mvSel.objects(); |
| |
| glColor3ub(255, 0, 0); |
| glLineWidth(1.0f); |
| |
| const double hSize = MESH_SELECTED_HANDLE_SIZE * m_pixelSize; |
| |
| objects_container::const_iterator vt, vEnd = objects.end(); |
| for (vt = objects.begin(); vt != vEnd; ++vt) { |
| const TTextureVertex &vx = |
| m_this->m_mi->meshes()[vt->m_meshIdx]->vertex(vt->m_idx); |
| |
| ::drawFullSquare(vx.P(), hSize); |
| } |
| } |
| |
| void drawEdgeSelections() { |
| typedef MeshSelection::objects_container objects_container; |
| const objects_container &objects = m_this->m_meSel.objects(); |
| |
| glColor3ub(0, 0, 255); |
| glLineWidth(2.0f); |
| |
| glBegin(GL_LINES); |
| |
| objects_container::const_iterator et, eEnd = objects.end(); |
| for (et = objects.begin(); et != eEnd; ++et) { |
| const TTextureVertex |
| &vx0 = |
| m_this->m_mi->meshes()[et->m_meshIdx]->edgeVertex(et->m_idx, 0), |
| &vx1 = |
| m_this->m_mi->meshes()[et->m_meshIdx]->edgeVertex(et->m_idx, 1); |
| |
| drawLine(vx0.P(), vx1.P()); |
| } |
| |
| glEnd(); |
| } |
| |
| void drawVertexHighlights() { |
| if (m_this->m_mvHigh) { |
| const MeshIndex &vHigh = m_this->m_mvHigh; |
| |
| const TTextureMesh::vertex_type &vx = |
| m_this->m_mi->meshes()[vHigh.m_meshIdx]->vertex(vHigh.m_idx); |
| |
| glColor3ub(255, 0, 0); |
| glLineWidth(1.0f); |
| |
| const double hSize = MESH_HIGHLIGHTED_HANDLE_SIZE * m_pixelSize; |
| |
| ::drawSquare(vx.P(), hSize); |
| } |
| } |
| |
| void drawEdgeHighlights() { |
| if (m_this->m_meHigh) { |
| const MeshIndex &eHigh = m_this->m_meHigh; |
| |
| const TTextureMesh::vertex_type |
| &vx0 = m_this->m_mi->meshes()[eHigh.m_meshIdx]->edgeVertex( |
| eHigh.m_idx, 0), |
| &vx1 = m_this->m_mi->meshes()[eHigh.m_meshIdx]->edgeVertex( |
| eHigh.m_idx, 1); |
| |
| { |
| glPushAttrib(GL_LINE_BIT); |
| |
| glEnable(GL_LINE_STIPPLE); |
| glLineStipple(1, 0xCCCC); |
| |
| glColor3ub(0, 0, 255); |
| glLineWidth(1.0f); |
| |
| glBegin(GL_LINES); |
| drawLine(vx0.P(), vx1.P()); |
| glEnd(); |
| |
| glPopAttrib(); |
| } |
| } |
| } |
| |
| } locals = {this, getPixelSize()}; |
| |
| |
| |
| |
| if (m_mi) { |
| locals.drawVertexSelections(); |
| locals.drawEdgeSelections(); |
| |
| locals.drawVertexHighlights(); |
| locals.drawEdgeHighlights(); |
| } |
| } |
| |