// TnzCore includes #include "tmeshimage.h" #include "tgl.h" #include "tundo.h" // TnzExt includes #include "ext/plasticdeformerstorage.h" // tcg includes #include "tcg/tcg_macros.h" #include "tcg/tcg_point_ops.h" #include "tcg/tcg_iterator_ops.h" #include "tcg/tcg_function_types.h" #include "tcg/tcg_deleter_types.h" #include "tcg/tcg_unique_ptr.h" // boost includes #include #include using namespace tcg::bgl; #include // STD includes #include #include "plastictool.h" using namespace PlasticToolLocals; //**************************************************************************************** // Local namespace stuff //**************************************************************************************** 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::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() { // There must be at least one remanining triangle return (m_mesh.facesCount() > m_ed.facesCount()); } bool testBoundary() { // Must not join two non-adjacent boundary vertices return (!borderVertex(m_mesh, m_ed.vertex(0)) || !borderVertex(m_mesh, m_ed.vertex(1)) || borderEdge(m_mesh, m_e)); } bool testAdjacency() { // See TriMesh<>::collapseEdge() // Retrieve allowed adjacent vertices 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]); // Test adjacent vertices int v0 = m_ed.vertex(0), v1 = m_ed.vertex(1); const vertex_type &vx0 = m_mesh.vertex(v0); tcg::vertex_traits::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) { // Adjacent vertex - must be found in the allowed list 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 stuff //**************************************************************************************** namespace PlasticToolLocals { struct Closer { const TTextureMesh &m_mesh; TPointD m_pos; double dist2(const TTextureMesh::vertex_type &a) { return tcg::point_ops::dist2(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(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 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 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 closestVertex(const TMeshImage &mi, const TPointD &pos) { std::pair closest((std::numeric_limits::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 &candidateIdx = closestVertex(**mt, pos); std::pair candidate( candidateIdx.first, MeshIndex(mt - meshes.begin(), candidateIdx.second)); if (candidate < closest) closest = candidate; } return closest; } //------------------------------------------------------------------------ std::pair closestEdge(const TMeshImage &mi, const TPointD &pos) { std::pair closest((std::numeric_limits::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 &candidateIdx = closestEdge(**mt, pos); std::pair candidate( candidateIdx.first, MeshIndex(mt - meshes.begin(), candidateIdx.second)); if (candidate < closest) closest = candidate; } return closest; } } // namespace //**************************************************************************************** // Cut Mesh operation //**************************************************************************************** namespace { struct EdgeCut { int m_vIdx; //!< Vertex index to cut from. int m_eIdx; //!< Edge index to cut. EdgeCut(int vIdx, int eIdx) : m_vIdx(vIdx), m_eIdx(eIdx) {} }; struct VertexOccurrence { int m_count; //!< Number of times a vertex occurs. int m_adjacentEdgeIdx[2]; //!< Edge indexes of which a vertex is endpoint. }; //============================================================================ bool buildEdgeCuts(const TMeshImage &mi, const PlasticTool::MeshSelection &edgesSelection, int &meshIdx, std::vector &edgeCuts) { typedef PlasticTool::MeshSelection::objects_container edges_container; typedef PlasticTool::MeshIndex MeshIndex; typedef boost::unordered_map 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) { // Calculate vertex occurrences as edge endpoints 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 &edgeCuts) { VertexOccurrencesMap vertexOccurrences; if (!buildVertexOccurrences(mesh, edges, vertexOccurrences)) return false; // Build endpoints (exactly 2) 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; // Pick the first endpoint on the boundary, if any (otherwise, just pick // one) int *ept, *epEnd = endPoints + 2; ept = std::find_if(endPoints, epEnd, tcg::bind1st(&borderVertex, mesh)); if (ept == epEnd) { // There is no boundary endpoint if (edges.size() < 2) // We should not cut the mesh on a return false; // single edge - no vertex to duplicate! ept = endPoints; } // Build the edge cuts list, expanding the edges selection from // the chosen endpoint edgeCuts.push_back(EdgeCut( // Build the first EdgeCut separately *ept, vertexOccurrences[*ept].m_adjacentEdgeIdx[0])); int e, eCount = int(edges.size()); // Build the remaining ones 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(); // Trivial early bailouts if (edges.empty()) return false; // Selected edges must lie on the same mesh meshIdx = locals::testSingleMesh(edges); if (meshIdx < 0) return false; const TTextureMesh &mesh = *mi.meshes()[meshIdx]; // No selected edge must be on the boundary return (locals::testNoBoundaryEdge(mesh, edges) && locals::buildEdgeCuts(mesh, edges, edgeCuts)); } //------------------------------------------------------------------------ inline bool testCutMesh(const TMeshImage &mi, const PlasticTool::MeshSelection &edgesSelection) { std::vector edgeCuts; int meshIdx; return buildEdgeCuts(mi, edgesSelection, meshIdx, edgeCuts); } //------------------------------------------------------------------------ void slitMesh(TTextureMesh &mesh, int e) //! Opens a slit along the specified edge index. { TTextureMesh::edge_type &ed = mesh.edge(e); assert(ed.facesCount() == 2); // Duplicate the edge and pass one face to the duplicate 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); // Alter the face to host the duplicate 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); } //------------------------------------------------------------------------ /*! \brief Duplicates a mesh edge-vertex pair (the 'cut') and separates their connections to adjacent mesh primitives. \remark The starting vertex is supposed to be on the mesh boundary. \remark Edges with a single neighbouring face can be duplicated, too. */ 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); } } }; // locals int vOrig = edgeCut.m_vIdx, eOrig = edgeCut.m_eIdx; // Create a new vertex at the same position of the original int vDup = mesh.addVertex(vertex_type(mesh.vertex(vOrig).P())); int e = eOrig; if (mesh.edge(e).facesCount() == 2) { // Duplicate the cut edge e = mesh.addEdge(edge_type(vDup, mesh.edge(eOrig).otherVertex(vOrig))); // Transfer one face from the original to the duplicate locals::transferFace(mesh, eOrig, e); } else { // Transfer the original edge to the duplicate vertex locals::transferEdge(mesh, eOrig, vOrig, vDup); } // Edges adjacent to the original vertex that are also adjacent // to the transferred face above must be transferred too int f = mesh.edge(e).face(0); while (f >= 0) { // Retrieve the next edge to transfer int otherE = mesh.otherFaceEdge(f, mesh.edge(e).otherVertex(vDup)); // NOTE: Not "mesh.edgeInciding(vOrig, mesh.otherFaceVertex(f, e))" in the // calculation // of otherE. This is required since by transferring each edge at a // time, // we're 'breaking' faces up - f is adjacent to both vOrig AND vDup! // // The chosen calculation, instead, just asks for the one edge which // does // not have a specific vertex in common to the 2 other edges in the // face. locals::transferEdge(mesh, otherE, vOrig, vDup); // Update e and f e = otherE; f = mesh.edge(otherE).otherFace(f); } } //------------------------------------------------------------------------ } namespace locals_ { // Need to use a named namespace due to // a known gcc 4.2 bug with compiler-generated struct VertexesRecorder // copy constructors. { boost::unordered_set &m_examinedVertexes; public: typedef boost::on_examine_vertex event_filter; public: VertexesRecorder(boost::unordered_set &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 &vertexes) { // Prepare BFS algorithm tcg::unique_ptr colorMapP( (UCHAR *)calloc(mesh.vertices().nodesCount(), sizeof(UCHAR))); locals_::VertexesRecorder vertexesRecorder(vertexes); std::stack verticesQueue; // Launch it boost::breadth_first_visit( mesh, int(mesh.vertices().begin().index()), verticesQueue, boost::make_bfs_visitor(vertexesRecorder), colorMapP.get()); } }; // locals // Retrieve the list of vertexes in the first connected component TTextureMesh &origMesh = *mi.meshes()[meshIdx]; boost::unordered_set firstComponent; locals::buildConnectedComponent(origMesh, firstComponent); if (firstComponent.size() == origMesh.verticesCount()) return; // There are (exactly) 2 connected components. Just duplicate the mesh // and keep/delete found vertexes. 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) { // Retrieve a cutting edge with a single adjacent face - cutting that // will just duplicate the vertex and separate the mesh in 2 connected // components 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)); // At this point, separate the 2 resulting connected components // in 2 separate meshes (if necessary) splitUnconnectedMesh(mi, meshIdx); } //------------------------------------------------------------------------ bool cutMesh(TMeshImage &mi, const PlasticTool::MeshSelection &edgesSelection) { struct locals { static int lastVertex(const TTextureMesh &mesh, const std::vector &edgeCuts) { return mesh.edge(edgeCuts.back().m_eIdx) .otherVertex(edgeCuts.back().m_vIdx); } static int lastBoundaryVertex(const TTextureMesh &mesh, const std::vector &edgeCuts) { int v = lastVertex(mesh, edgeCuts); return ::borderVertex(mesh, v) ? v : -1; } }; // locals std::vector edgeCuts; int meshIdx; if (!::buildEdgeCuts(mi, edgesSelection, meshIdx, edgeCuts)) return false; TTextureMesh &mesh = *mi.meshes()[meshIdx]; int lastBoundaryVertex = locals::lastBoundaryVertex(mesh, edgeCuts); // Slit the mesh on the first edge, in case the cuts do not start // on the mesh boundary std::vector::iterator ecBegin = edgeCuts.begin(); if (!::borderVertex(mesh, ecBegin->m_vIdx)) { ::slitMesh(mesh, ecBegin->m_eIdx); ++ecBegin; } // Cut edges, in the order specified by edgeCuts std::for_each(ecBegin, edgeCuts.end(), tcg::bind1st(&cutEdge, mesh)); // Finally, the mesh could have been split in 2 - we need to separate // the pieces if needed if (lastBoundaryVertex >= 0) splitMesh(mi, meshIdx, lastBoundaryVertex); return true; } } // namespace //**************************************************************************************** // Undo definitions //**************************************************************************************** namespace { class MoveVertexUndo_Mesh final : public TUndo { int m_row, m_col; //!< Xsheet coordinates std::vector m_vIdxs; //!< Moved vertices std::vector m_origVxsPos; //!< Original vertex positions TPointD m_posShift; //!< Vertex positions shift public: MoveVertexUndo_Mesh(const std::vector &vIdxs, const std::vector &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(); // IMPORTANT: In particular, sets the level's } // dirty flag, so Toonz knows it has // to be saved! 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; //!< Xsheet coordinates mutable MeshIndex m_edgeIdx; //!< Edge index 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); // Perform swap TTextureMesh &mesh = *mi->meshes()[m_edgeIdx.m_meshIdx]; m_edgeIdx.m_idx = mesh.swapEdge(m_edgeIdx.m_idx); assert(m_edgeIdx.m_idx >= 0); // Invalidate any deformer associated with mi PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); // Update tool selection l_plasticTool.setMeshEdgesSelection(m_edgeIdx); l_plasticTool.invalidate(); l_plasticTool.notifyImageChanged(); } void undo() const override { redo(); } // Operation is idempotent (indices // are perfectly restored, too) }; //============================================================================== class TTextureMeshUndo : public TUndo { protected: int m_row, m_col; //!< Xsheet coordinates int m_meshIdx; //!< Mesh index in the image at stored xsheet coords mutable TTextureMesh m_origMesh; //!< Copy of the original mesh public: TTextureMeshUndo(int meshIdx) : m_row(::row()), m_col(::column()), m_meshIdx(meshIdx) {} // Let's say 1MB each - storing the mesh is costly 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; //!< Collapsed edge index 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(); // Store the original mesh TTextureMesh &mesh = *mi->meshes()[m_meshIdx]; m_origMesh = mesh; // Collapse mesh.collapseEdge(m_eIdx); mesh.squeeze(); // Invalidate any cached deformer associated with the modified mesh image PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); // Refresh the tool l_plasticTool.clearMeshSelections(); l_plasticTool.invalidate(); l_plasticTool.notifyImageChanged(); } void undo() const override { PlasticTool::TemporaryActivation tempActivate(m_row, m_col); const TMeshImageP &mi = getMeshImage(); // Restore the original mesh TTextureMesh &mesh = *mi->meshes()[m_meshIdx]; mesh = m_origMesh; PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); // Restore selection l_plasticTool.setMeshEdgesSelection(MeshIndex(m_meshIdx, m_eIdx)); l_plasticTool.invalidate(); l_plasticTool.notifyImageChanged(); } }; //============================================================================== class SplitEdgeUndo final : public TTextureMeshUndo { int m_eIdx; //!< Split edge index 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(); // Store the original mesh TTextureMesh &mesh = *mi->meshes()[m_meshIdx]; m_origMesh = mesh; // Split mesh.splitEdge(m_eIdx); // mesh.squeeze(); // // There should be no need to squeeze 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(); // Restore the original mesh TTextureMesh &mesh = *mi->meshes()[m_meshIdx]; mesh = m_origMesh; PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); // Restore selection l_plasticTool.setMeshEdgesSelection(MeshIndex(m_meshIdx, m_eIdx)); l_plasticTool.invalidate(); l_plasticTool.notifyImageChanged(); } }; //============================================================================== class CutEdgesUndo final : public TUndo { int m_row, m_col; //!< Xsheet coordinates TMeshImageP m_origImage; //!< Clone of the original image PlasticTool::MeshSelection m_edgesSelection; //!< Selection to operate on 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); // Restore the original image *mi = *m_origImage; PlasticDeformerStorage::instance()->releaseMeshData(mi.getPointer()); // Restore selection l_plasticTool.setMeshEdgesSelection(m_edgesSelection); l_plasticTool.invalidate(); l_plasticTool.notifyImageChanged(); } }; } // namespace //**************************************************************************************** // PlasticTool functions //**************************************************************************************** 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(); // Build new selection 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) { // Track mouse position m_pos = pos; // Needs to be done now - ensures m_pos is valid m_mvHigh = MeshIndex(); // Reset highlighted primitives if (m_mi) { // Look for nearest primitive std::pair closestVertex = ::closestVertex(*m_mi, pos), closestEdge = ::closestEdge(*m_mi, pos); // Discriminate on fixed metric 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)); // Vertex and edge highlights are mutually exclusive 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}; // Track mouse position m_pressedPos = m_pos = pos; // Update selection locals.updateSelection(m_mvSel, m_mvHigh, me); locals.updateSelection(m_meSel, m_meHigh, me); // Store original vertex positions if (!m_mvSel.isEmpty()) { m_pressedVxsPos = std::vector( 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))); } // Redraw selections invalidate(); } //------------------------------------------------------------------------ void PlasticTool::leftButtonDrag_mesh(const TPointD &pos, const TMouseEvent &me) { // Track mouse position 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) { // Track mouse position 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(); // Sets the level's dirty flag -.-' } } //------------------------------------------------------------------------ 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 &origVxsPos, const TPointD &posShift) { if (m_mvSel.isEmpty() || !m_mi) return; assert(origVxsPos.size() == m_mvSel.objects().size()); // Move selected vertices 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; } // Mesh must be recompiled PlasticDeformerStorage::instance()->invalidateMeshImage( mi.getPointer(), PlasticDeformerStorage::MESH); } //------------------------------------------------------------------------ void PlasticTool::swapEdge_mesh_undo() { if (!(m_mi && m_meSel.hasSingleObject())) return; // Test current edge swapability { const MeshIndex &eIdx = m_meSel.objects().front(); const TTextureMesh &mesh = *m_mi->meshes()[eIdx.m_meshIdx]; if (!::testSwapEdge(mesh, eIdx.m_idx)) return; } // Perform operation std::unique_ptr 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; // Test collapsibility of current edge { const MeshIndex &eIdx = m_meSel.objects().front(); const TTextureMesh &mesh = *m_mi->meshes()[eIdx.m_meshIdx]; if (!::testCollapseEdge(mesh, eIdx.m_idx)) return; } // Perform operation std::unique_ptr 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 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 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); // Red 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); // Blue 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); // Red 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); // Blue glLineWidth(1.0f); glBegin(GL_LINES); drawLine(vx0.P(), vx1.P()); glEnd(); glPopAttrib(); } } } } locals = {this, getPixelSize()}; // The selected mesh image is already drawn by the stage // Draw additional overlays if (m_mi) { locals.drawVertexSelections(); locals.drawEdgeSelections(); locals.drawVertexHighlights(); locals.drawEdgeHighlights(); } }