// -------------------------------------------------------------------- // Snapping // -------------------------------------------------------------------- /* This file is part of the extensible drawing editor Ipe. Copyright (C) 1993-2004 Otfried Cheong Ipe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. As a special exception, you have permission to link Ipe with the CGAL library and distribute executables, as long as you follow the requirements of the Gnu General Public License in regard to all of the software in the executable aside from CGAL. Ipe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ipe; if not, you can find it at "http://www.gnu.org/copyleft/gpl.html", or write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ipesnap.h" #include "ipepage.h" #include "ipevisitor.h" #include "ipegroup.h" #include "iperef.h" #include "ipepath.h" /*! \defgroup high Ipe Management \brief Classes to manage Ipe documents and objects. This module contains classes used in the implementation of the Ipe program itself. They are in Ipelib because they are independant of the UI toolkit, and so are more easily accessible for a port of Ipe to another toolkit. The only classes from this module you may be interested in are IpeVisitor (which is essential to traverse an Ipe object structure), and perhaps IpeSnapData (if you are writing an Ipelet whose behavior depends on the current snap setting in the Ipe program). */ /*! \class IpeSnapData \ingroup high \brief Performs snapping operations, and stores snapping state. */ // -------------------------------------------------------------------- class CollectSegs : public IpeVisitor { public: CollectSegs(const IpeVector &mouse, double snapDist, const IpePage *page); virtual void VisitGroup(const IpeGroup *obj); virtual void VisitPath(const IpePath *obj); virtual void VisitReference(const IpeReference *obj); public: std::vector iSegs; std::vector iBeziers; std::vector iArcs; private: std::vector iMatrices; IpeVector iMouse; double iDist; }; CollectSegs::CollectSegs(const IpeVector &mouse, double snapDist, const IpePage *page) : iMouse(mouse), iDist(snapDist) { iMatrices.push_back(IpeMatrix()); // identity matrix for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) { if (page->Layer(it->Layer()).IsSnapping()) it->Object()->Accept(*this); } } void CollectSegs::VisitGroup(const IpeGroup *obj) { iMatrices.push_back(iMatrices.back() * obj->Matrix()); for (IpeGroup::const_iterator it = obj->begin(); it != obj->end(); ++it) (*it)->Accept(*this); iMatrices.pop_back(); } void CollectSegs::VisitReference(const IpeReference *obj) { if (obj->Object()) { iMatrices.push_back(iMatrices.back() * obj->Matrix()); obj->Object()->Accept(*this); iMatrices.pop_back(); } } // XXX use bounding boxes for subsegs/beziers to speed up ? void CollectSegs::VisitPath(const IpePath *obj) { IpeBezier b; IpeArc arc; IpeMatrix m = obj->Matrix(); for (int i = 0; i < obj->NumSubPaths(); ++i) { const IpeSubPath *sp = obj->SubPath(i); switch (sp->Type()) { case IpeSubPath::EEllipse: if (sp->Distance(iMouse, m, iDist) < iDist) iArcs.push_back(m * IpeArc(sp->AsEllipse()->Matrix())); break; case IpeSubPath::EClosedSpline: { std::vector bez; sp->AsClosedSpline()->Beziers(bez); for (uint i = 0; i < bez.size(); ++i) { b = m * bez[i]; if (b.Distance(iMouse, iDist) < iDist) iBeziers.push_back(b); } break; } case IpeSubPath::ESegments: { const IpeSegmentSubPath *ssp = sp->AsSegs(); int ns = ssp->Closed() ? -1 : 0; IpeVector u[2]; for (int j = ns; j < ssp->NumSegments(); ++j) { IpePathSegment seg = j < 0 ? ssp->ClosingSegment(u) : ssp->Segment(j); switch (seg.Type()) { case IpePathSegment::ESegment: if (seg.Distance(iMouse, m, iDist) < iDist) iSegs.push_back(IpeSegment(m * seg.CP(0), m * seg.CP(1))); break; case IpePathSegment::EBezier: case IpePathSegment::EQuad: b = m * seg.Bezier(); if (b.Distance(iMouse, iDist) < iDist) iBeziers.push_back(b); break; case IpePathSegment::EArc: arc = m * seg.Arc(); if (arc.Distance(iMouse, iDist) < iDist) iArcs.push_back(arc); break; case IpePathSegment::ESpline: { std::vector bez; seg.Beziers(bez); for (uint i = 0; i < bez.size(); ++i) { b = m * bez[i]; if (b.Distance(iMouse, iDist) < iDist) iBeziers.push_back(b); } break; } } } break; } } } } // -------------------------------------------------------------------- /*! Find line through \a base with slope determined by angular snap size and direction. */ IpeLine IpeSnapData::GetLine(const IpeVector &mouse, const IpeVector &base) const { IpeAngle alpha = iDir; IpeVector d = mouse - base; if (d.Len() > 2.0) { alpha = d.Angle() - iDir; alpha.Normalize(0.0); alpha = iAngleSize * int(alpha / iAngleSize + 0.5) + iDir; } return IpeLine(base, IpeVector(alpha)); } //! Perform intersection snapping. bool IpeSnapData::IntersectionSnap(IpeVector &pos, const IpePage *page, double snapDist) const { CollectSegs segs(pos, snapDist, page); double d = snapDist; IpeVector pos1 = pos; IpeVector v; double d1; for (uint i = 0; i < segs.iSegs.size(); i++) { for (uint j = i+1; j < segs.iSegs.size(); j++) { if (segs.iSegs[i].Intersects(segs.iSegs[j], v) && (d1 = (pos - v).Len()) < d) { d = d1; pos1 = v; } } } if (d < snapDist) { pos = pos1; return true; } return false; } //! Perform snapping to intersection of angular line and pos. bool IpeSnapData::SnapAngularIntersection(IpeVector &pos, const IpeLine &l, const IpePage *page, double snapDist) const { CollectSegs segs(pos, snapDist, page); double d = snapDist; IpeVector pos1 = pos; IpeVector v; double d1; for (std::vector::const_iterator it = segs.iSegs.begin(); it != segs.iSegs.end(); ++it) { if (it->Intersects(l, v) && (d1 = (pos - v).Len()) < d) { d = d1; pos1 = v; } } for (std::vector::const_iterator it = segs.iArcs.begin(); it != segs.iArcs.end(); ++it) { if (it->Intersects(l, v) && (d1 = (pos - v).Len()) < d) { d = d1; pos1 = v; } } for (std::vector::const_iterator it = segs.iBeziers.begin(); it != segs.iBeziers.end(); ++it) { if (it->Intersects(l, v) && (d1 = (pos - v).Len()) < d) { d = d1; pos1 = v; } } if (d < snapDist) { pos = pos1; return true; } return false; } //! Tries vertex, intersection, boundary, and grid snapping. /*! If snapping occurred, \a pos is set to the new user space position. */ bool IpeSnapData::SimpleSnap(IpeVector &pos, const IpePage *page, double snapDist) const { double d = snapDist; IpeVector fifi = pos; // highest priority: vertex snapping if (iSnap & ESnapVtx) { for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) { if (page->Layer(it->Layer()).IsSnapping()) it->SnapVtx(pos, fifi, d); } } if (iSnap & ESnapInt) { IntersectionSnap(pos, page, d); } // Return if snapping has occurred if (d < snapDist) { pos = fifi; return true; } // boundary snapping if (iSnap & ESnapBd) { for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) { if (page->Layer(it->Layer()).IsSnapping()) it->SnapBnd(pos, fifi, d); } if (d < snapDist) { pos = fifi; return true; } } // grid snapping: alway occurs if (iSnap & ESnapGrid) { int grid = iGridSize; fifi.iX = grid * int(pos.iX / grid + 0.5); fifi.iY = grid * int(pos.iY / grid + 0.5); pos = fifi; return true; } return false; } //! Performs snapping of position \a pos. /*! Returns \c true if snapping occurred. In that case \a pos is set to the new user space position. Automatic angular snapping occurs if \a autoOrg is not null --- the value is then used as the origin for automatic angular snapping. */ bool IpeSnapData::Snap(IpeVector &pos, const IpePage *page, double snapDist, IpeVector *autoOrg) const { // automatic angular snapping and angular snapping both on? if (autoOrg && (iSnap & ESnapAuto) && (iSnap & ESnapAngle)) { // only one possible point! IpeLine angular = GetLine(pos, iOrigin); IpeLine automat = GetLine(pos, *autoOrg); IpeVector v; if (angular.Intersects(automat, v)) { pos = v; return true; } // if the two lines do not intersect, use following case } // case of only one angular snapping mode if ((iSnap & ESnapAngle) || (autoOrg && (iSnap & ESnapAuto))) { IpeVector org; if (iSnap & ESnapAngle) org = iOrigin; else org = *autoOrg; IpeLine l = GetLine(pos, org); pos = l.Project(pos); if (iSnap & ESnapBd) SnapAngularIntersection(pos, l, page, snapDist); return true; } // we are not in any angular snapping mode return SimpleSnap(pos, page, snapDist); } //! Set axis origin and direction from edge near mouse. /*! Returns \c true if successful. */ bool IpeSnapData::SetEdge(const IpeVector &pos, const IpePage *page) { CollectSegs segs(pos, 0.001, page); if (!segs.iSegs.empty()) { IpeSegment seg = segs.iSegs.front(); IpeLine l = seg.Line(); iOrigin = l.Project(pos); IpeVector dir = l.Dir(); if ((iOrigin - seg.iP).Len() > (iOrigin - seg.iQ).Len()) dir = -dir; iDir = dir.Angle(); return true; } // should also support IpeArc and IpeBezier return false; } // --------------------------------------------------------------------