/** Make a path from a d2 sbasis. \param p the d2 Symmetric basis polynomial \returns a Path If only_cubicbeziers is true, the resulting path may only contain CubicBezier curves. TODO: some of this logic should be lifted into svg-path */ std::vector<Geom::Path> path_from_piecewise(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &B, double tol, bool only_cubicbeziers) { Geom::PathBuilder pb; if(B.size() == 0) return pb.peek(); Geom::Point start = B[0].at0(); pb.moveTo(start); for(unsigned i = 0; ; i++) { if ( (i+1 == B.size()) || !are_near(B[i+1].at0(), B[i].at1(), tol) ) { //start of a new path if (are_near(start, B[i].at1()) && sbasis_size(B[i]) <= 1) { pb.closePath(); //last line seg already there (because of .closePath()) goto no_add; } build_from_sbasis(pb, B[i], tol, only_cubicbeziers); if (are_near(start, B[i].at1())) { //it's closed, the last closing segment was not a straight line so it needed to be added, but still make it closed here with degenerate straight line. pb.closePath(); } no_add: if (i+1 >= B.size()) { break; } start = B[i+1].at0(); pb.moveTo(start); } else { build_from_sbasis(pb, B[i], tol, only_cubicbeziers); } } pb.flush(); return pb.peek(); }
Curve* EllipticalArc::portion(double f, double t) const { // fix input arguments if (f < 0) f = 0; if (f > 1) f = 1; if (t < 0) t = 0; if (t > 1) t = 1; if ( are_near(f, t) ) { EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate()); arc->_center = arc->_initial_point = arc->_final_point = pointAt(f); arc->_start_angle = arc->_end_angle = _start_angle; arc->_rot_angle = _rot_angle; arc->_sweep = _sweep; arc->_large_arc = _large_arc; return arc; } EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate()); arc->_initial_point = pointAt(f); arc->_final_point = pointAt(t); if ( f > t ) arc->_sweep = !_sweep; if ( _large_arc && fabs(sweepAngle() * (t-f)) < M_PI) arc->_large_arc = false; arc->_updateCenterAndAngles(arc->isSVGCompliant()); //TODO: be more clever return arc; }
bool make_elliptical_arc::make_elliptiarc() { const NL::Vector & coeff = fitter.result(); Ellipse e; try { e.setCoefficients(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]); } catch(LogicalError const &exc) { return false; } Point inner_point = curve(0.5); #ifdef CPP11 std::unique_ptr<EllipticalArc> arc( e.arc(initial_point, inner_point, final_point) ); #else std::auto_ptr<EllipticalArc> arc( e.arc(initial_point, inner_point, final_point) ); #endif ea = *arc; if ( !are_near( e.center(), ea.center(), tol_at_center * std::min(e.ray(X),e.ray(Y)) ) ) { return false; } return true; }
void Affine::setExpansionY(double val) { double exp_y = expansionY(); if(!are_near(exp_y, 0.0)) { //TODO: best way to deal with it is to skip op? double coef = val / expansionY(); for(unsigned i=2; i<4; i++) _c[i] *= coef; } }
/** @brief Check whether this matrix represents pure, nonzero uniform scaling. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} a_1 & 0 & 0 \\ 0 & a_2 & 0 \\ 0 & 0 & 1 \end{array}\right]\f$ where \f$|a_1| = |a_2|\f$ * and \f$a_1, a_2 \neq 1\f$. */ bool Affine::isNonzeroUniformScale(Coord eps) const { if (isSingular(eps)) return false; // we need to test both c0 and c3 to handle the case of flips, // which should be treated as nonzero uniform scales return !(are_near(_c[0], 1.0, eps) && are_near(_c[3], 1.0, eps)) && are_near(fabs(_c[0]), fabs(_c[3]), eps) && are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); }
Point Curve::unitTangentAt(Coord t, unsigned n) const { std::vector<Point> derivs = pointAndDerivatives(t, n); for (unsigned deriv_n = 1; deriv_n < derivs.size(); deriv_n++) { Coord length = derivs[deriv_n].length(); if ( ! are_near(length, 0) ) { // length of derivative is non-zero, so return unit vector return derivs[deriv_n] / length; } } return Point (0,0); };
void SVGPathWriter::lineTo(Point const &p) { // The weird setting of _current is to avoid drift with many almost-aligned segments // The additional conditions ensure that the smaller dimension is rounded to zero bool written = false; if (_use_shorthands) { Point r = _current - p; if (are_near(p[X], _current[X], _epsilon) && std::abs(r[X]) < std::abs(r[Y])) { // emit vlineto _setCommand('V'); _current_pars.push_back(p[Y]); _current[Y] = p[Y]; written = true; } else if (are_near(p[Y], _current[Y], _epsilon) && std::abs(r[Y]) < std::abs(r[X])) { // emit hlineto _setCommand('H'); _current_pars.push_back(p[X]); _current[X] = p[X]; written = true; } } if (!written) { // emit normal lineto if (_command != 'M' && _command != 'L') { _setCommand('L'); } _current_pars.push_back(p[X]); _current_pars.push_back(p[Y]); _current = p; } _cubic_tangent = _quad_tangent = _current; if (!_optimize) { flush(); } }
void SVGPathWriter::quadTo(Point const &c, Point const &p) { bool shorthand = _use_shorthands && are_near(c, _quad_tangent, _epsilon); _setCommand(shorthand ? 'T' : 'Q'); if (!shorthand) { _current_pars.push_back(c[X]); _current_pars.push_back(c[Y]); } _current_pars.push_back(p[X]); _current_pars.push_back(p[Y]); _current = _cubic_tangent = p; _quad_tangent = p + (p - c); if (!_optimize) { flush(); } }
void SVGPathWriter::curveTo(Point const &p1, Point const &p2, Point const &p3) { bool shorthand = _use_shorthands && are_near(p1, _cubic_tangent, _epsilon); _setCommand(shorthand ? 'S' : 'C'); if (!shorthand) { _current_pars.push_back(p1[X]); _current_pars.push_back(p1[Y]); } _current_pars.push_back(p2[X]); _current_pars.push_back(p2[Y]); _current_pars.push_back(p3[X]); _current_pars.push_back(p3[Y]); _current = _quad_tangent = p3; _cubic_tangent = p3 + (p3 - p2); if (!_optimize) { flush(); } }
/** @brief Check whether this matrix represents pure scaling. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} a & 0 & 0 \\ 0 & b & 0 \\ 0 & 0 & 1 \end{array}\right]\f$. */ bool Affine::isScale(Coord eps) const { if (isSingular(eps)) return false; return are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); }
/** @brief Check whether this matrix represents pure, nonzero scaling. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} a & 0 & 0 \\ 0 & b & 0 \\ 0 & 0 & 1 \end{array}\right]\f$ and \f$a, b \neq 1\f$. */ bool Affine::isNonzeroScale(Coord eps) const { if (isSingular(eps)) return false; return (!are_near(_c[0], 1.0, eps) || !are_near(_c[3], 1.0, eps)) && //NOTE: these are the diags, and the next line opposite diags are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); }
/* * NOTE: this implementation follows Standard SVG 1.1 implementation guidelines * for elliptical arc curves. See Appendix F.6. */ void EllipticalArc::_updateCenterAndAngles(bool svg) { Point d = initialPoint() - finalPoint(); // TODO move this to SVGElipticalArc? if (svg) { if ( initialPoint() == finalPoint() ) { _rot_angle = _start_angle = _end_angle = 0; _center = initialPoint(); _rays = Geom::Point(0,0); _large_arc = _sweep = false; return; } _rays[X] = std::fabs(_rays[X]); _rays[Y] = std::fabs(_rays[Y]); if ( are_near(ray(X), 0) || are_near(ray(Y), 0) ) { _rays[X] = L2(d) / 2; _rays[Y] = 0; _rot_angle = std::atan2(d[Y], d[X]); _start_angle = 0; _end_angle = M_PI; _center = middle_point(initialPoint(), finalPoint()); _large_arc = false; _sweep = false; return; } } else { if ( are_near(initialPoint(), finalPoint()) ) { if ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) { _start_angle = _end_angle = 0; _center = initialPoint(); return; } else { THROW_RANGEERROR("initial and final point are the same"); } } if ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) { // but initialPoint != finalPoint THROW_RANGEERROR( "there is no ellipse that satisfies the given constraints: " "ray(X) == 0 && ray(Y) == 0 but initialPoint != finalPoint" ); } if ( are_near(ray(Y), 0) ) { Point v = initialPoint() - finalPoint(); if ( are_near(L2sq(v), 4*ray(X)*ray(X)) ) { Angle angle(v); if ( are_near( angle, _rot_angle ) ) { _start_angle = 0; _end_angle = M_PI; _center = v/2 + finalPoint(); return; } angle -= M_PI; if ( are_near( angle, _rot_angle ) ) { _start_angle = M_PI; _end_angle = 0; _center = v/2 + finalPoint(); return; } THROW_RANGEERROR( "there is no ellipse that satisfies the given constraints: " "ray(Y) == 0 " "and slope(initialPoint - finalPoint) != rotation_angle " "and != rotation_angle + PI" ); } if ( L2sq(v) > 4*ray(X)*ray(X) ) { THROW_RANGEERROR( "there is no ellipse that satisfies the given constraints: " "ray(Y) == 0 and distance(initialPoint, finalPoint) > 2*ray(X)" ); } else { THROW_RANGEERROR( "there is infinite ellipses that satisfy the given constraints: " "ray(Y) == 0 and distance(initialPoint, finalPoint) < 2*ray(X)" ); } } if ( are_near(ray(X), 0) ) { Point v = initialPoint() - finalPoint(); if ( are_near(L2sq(v), 4*ray(Y)*ray(Y)) ) { double angle = std::atan2(v[Y], v[X]); if (angle < 0) angle += 2*M_PI; double rot_angle = _rot_angle.radians() + M_PI/2; if ( !(rot_angle < 2*M_PI) ) rot_angle -= 2*M_PI; if ( are_near( angle, rot_angle ) ) { _start_angle = M_PI/2; _end_angle = 3*M_PI/2; _center = v/2 + finalPoint(); return; } angle -= M_PI; if ( angle < 0 ) angle += 2*M_PI; if ( are_near( angle, rot_angle ) ) { _start_angle = 3*M_PI/2; _end_angle = M_PI/2; _center = v/2 + finalPoint(); return; } THROW_RANGEERROR( "there is no ellipse that satisfies the given constraints: " "ray(X) == 0 " "and slope(initialPoint - finalPoint) != rotation_angle + PI/2 " "and != rotation_angle + (3/2)*PI" ); } if ( L2sq(v) > 4*ray(Y)*ray(Y) ) { THROW_RANGEERROR( "there is no ellipse that satisfies the given constraints: " "ray(X) == 0 and distance(initialPoint, finalPoint) > 2*ray(Y)" ); } else { THROW_RANGEERROR( "there is infinite ellipses that satisfy the given constraints: " "ray(X) == 0 and distance(initialPoint, finalPoint) < 2*ray(Y)" ); } } } Rotate rm(_rot_angle); Affine m(rm); m[1] = -m[1]; m[2] = -m[2]; Point p = (d / 2) * m; double rx2 = _rays[X] * _rays[X]; double ry2 = _rays[Y] * _rays[Y]; double rxpy = _rays[X] * p[Y]; double rypx = _rays[Y] * p[X]; double rx2py2 = rxpy * rxpy; double ry2px2 = rypx * rypx; double num = rx2 * ry2; double den = rx2py2 + ry2px2; assert(den != 0); double rad = num / den; Point c(0,0); if (rad > 1) { rad -= 1; rad = std::sqrt(rad); if (_large_arc == _sweep) rad = -rad; c = rad * Point(rxpy / ray(Y), -rypx / ray(X)); _center = c * rm + middle_point(initialPoint(), finalPoint()); } else if (rad == 1 || svg) { double lamda = std::sqrt(1 / rad); _rays[X] *= lamda; _rays[Y] *= lamda; _center = middle_point(initialPoint(), finalPoint()); } else { THROW_RANGEERROR( "there is no ellipse that satisfies the given constraints" ); } Point sp((p[X] - c[X]) / ray(X), (p[Y] - c[Y]) / ray(Y)); Point ep((-p[X] - c[X]) / ray(X), (-p[Y] - c[Y]) / ray(Y)); Point v(1, 0); _start_angle = angle_between(v, sp); double sweep_angle = angle_between(sp, ep); if (!_sweep && sweep_angle > 0) sweep_angle -= 2*M_PI; if (_sweep && sweep_angle < 0) sweep_angle += 2*M_PI; _end_angle = _start_angle; _end_angle += sweep_angle; }
std::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from, double to ) const { std::vector<double> result; if ( from > to ) std::swap(from, to); if ( from < 0 || to > 1 ) { THROW_RANGEERROR("[from,to] interval out of range"); } if ( ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) || are_near(from, to) ) { result.push_back(from); return result; } else if ( are_near(ray(X), 0) || are_near(ray(Y), 0) ) { LineSegment seg(pointAt(from), pointAt(to)); Point np = seg.pointAt( seg.nearestPoint(p) ); if ( are_near(ray(Y), 0) ) { if ( are_near(_rot_angle, M_PI/2) || are_near(_rot_angle, 3*M_PI/2) ) { result = roots(np[Y], Y); } else { result = roots(np[X], X); } } else { if ( are_near(_rot_angle, M_PI/2) || are_near(_rot_angle, 3*M_PI/2) ) { result = roots(np[X], X); } else { result = roots(np[Y], Y); } } return result; } else if ( are_near(ray(X), ray(Y)) ) { Point r = p - center(); if ( are_near(r, Point(0,0)) ) { THROW_INFINITESOLUTIONS(0); } // TODO: implement case r != 0 // Point np = ray(X) * unit_vector(r); // std::vector<double> solX = roots(np[X],X); // std::vector<double> solY = roots(np[Y],Y); // double t; // if ( are_near(solX[0], solY[0]) || are_near(solX[0], solY[1])) // { // t = solX[0]; // } // else // { // t = solX[1]; // } // if ( !(t < from || t > to) ) // { // result.push_back(t); // } // else // { // // } } // solve the equation <D(E(t),t)|E(t)-p> == 0 // that provides min and max distance points // on the ellipse E wrt the point p // after the substitutions: // cos(t) = (1 - s^2) / (1 + s^2) // sin(t) = 2t / (1 + s^2) // where s = tan(t/2) // we get a 4th degree equation in s /* * ry s^4 ((-cy + py) Cos[Phi] + (cx - px) Sin[Phi]) + * ry ((cy - py) Cos[Phi] + (-cx + px) Sin[Phi]) + * 2 s^3 (rx^2 - ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) + * 2 s (-rx^2 + ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) */ Point p_c = p - center(); double rx2_ry2 = (ray(X) - ray(Y)) * (ray(X) + ray(Y)); double sinrot, cosrot; sincos(_rot_angle, sinrot, cosrot); double expr1 = ray(X) * (p_c[X] * cosrot + p_c[Y] * sinrot); Poly coeff; coeff.resize(5); coeff[4] = ray(Y) * ( p_c[Y] * cosrot - p_c[X] * sinrot ); coeff[3] = 2 * ( rx2_ry2 + expr1 ); coeff[2] = 0; coeff[1] = 2 * ( -rx2_ry2 + expr1 ); coeff[0] = -coeff[4]; // for ( unsigned int i = 0; i < 5; ++i ) // std::cerr << "c[" << i << "] = " << coeff[i] << std::endl; std::vector<double> real_sol; // gsl_poly_complex_solve raises an error // if the leading coefficient is zero if ( are_near(coeff[4], 0) ) { real_sol.push_back(0); if ( !are_near(coeff[3], 0) ) { double sq = -coeff[1] / coeff[3]; if ( sq > 0 ) { double s = std::sqrt(sq); real_sol.push_back(s); real_sol.push_back(-s); } } } else { real_sol = solve_reals(coeff); } for ( unsigned int i = 0; i < real_sol.size(); ++i ) { real_sol[i] = 2 * std::atan(real_sol[i]); if ( real_sol[i] < 0 ) real_sol[i] += 2*M_PI; } // when s -> Infinity then <D(E)|E-p> -> 0 iff coeff[4] == 0 // so we add M_PI to the solutions being lim arctan(s) = PI when s->Infinity if ( (real_sol.size() % 2) != 0 ) { real_sol.push_back(M_PI); } double mindistsq1 = std::numeric_limits<double>::max(); double mindistsq2 = std::numeric_limits<double>::max(); double dsq; unsigned int mi1, mi2; for ( unsigned int i = 0; i < real_sol.size(); ++i ) { dsq = distanceSq(p, pointAtAngle(real_sol[i])); if ( mindistsq1 > dsq ) { mindistsq2 = mindistsq1; mi2 = mi1; mindistsq1 = dsq; mi1 = i; } else if ( mindistsq2 > dsq ) { mindistsq2 = dsq; mi2 = i; } } double t = map_to_01( real_sol[mi1] ); if ( !(t < from || t > to) ) { result.push_back(t); } bool second_sol = false; t = map_to_01( real_sol[mi2] ); if ( real_sol.size() == 4 && !(t < from || t > to) ) { if ( result.empty() || are_near(mindistsq1, mindistsq2) ) { result.push_back(t); second_sol = true; } } // we need to test extreme points too double dsq1 = distanceSq(p, pointAt(from)); double dsq2 = distanceSq(p, pointAt(to)); if ( second_sol ) { if ( mindistsq2 > dsq1 ) { result.clear(); result.push_back(from); mindistsq2 = dsq1; } else if ( are_near(mindistsq2, dsq) ) { result.push_back(from); } if ( mindistsq2 > dsq2 ) { result.clear(); result.push_back(to); } else if ( are_near(mindistsq2, dsq2) ) { result.push_back(to); } } else { if ( result.empty() ) { if ( are_near(dsq1, dsq2) ) { result.push_back(from); result.push_back(to); } else if ( dsq2 > dsq1 ) { result.push_back(from); } else { result.push_back(to); } } } return result; }
/** @brief Check whether this matrix represents pure uniform scaling. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} a_1 & 0 & 0 \\ 0 & a_2 & 0 \\ 0 & 0 & 1 \end{array}\right]\f$ where \f$|a_1| = |a_2|\f$. */ bool Affine::isUniformScale(Coord eps) const { if (isSingular(eps)) return false; return are_near(fabs(_c[0]), fabs(_c[3]), eps) && are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); }
/** @brief Check whether this matrix represents zooming. * Zooming is any combination of translation and uniform non-flipping scaling. * It preserves angles, ratios of distances between arbitrary points * and unit vectors of line segments. * @param eps Numerical tolerance * @return True iff the matrix is invertible and of the form * \f$\left[\begin{array}{ccc} a & 0 & 0 \\ 0 & a & 0 \\ b & c & 1 \end{array}\right]\f$. */ bool Affine::isZoom(Coord eps) const { if (isSingular(eps)) return false; return are_near(_c[0], _c[3], eps) && are_near(_c[1], 0, eps) && are_near(_c[2], 0, eps); }
/** @brief Check whether this matrix represents a pure nonzero translation. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} 1 & 0 & 0 \\ 0 & 1 & 0 \\ a & b & 1 \end{array}\right]\f$ and \f$a, b \neq 0\f$ */ bool Affine::isNonzeroTranslation(Coord eps) const { return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) && (!are_near(_c[4], 0.0, eps) || !are_near(_c[5], 0.0, eps)); }
/** @brief Check whether this matrix represents a pure, nonzero rotation. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} a & b & 0 \\ -b & a & 0 \\ 0 & 0 & 1 \end{array}\right]\f$, \f$a^2 + b^2 = 1\f$ and \f$a \neq 1\f$. */ bool Affine::isNonzeroRotation(Coord eps) const { return !are_near(_c[0], 1.0, eps) && are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) && are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps) && are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps); }
/** @brief Check whether this matrix represents a pure translation. * Will return true for the identity matrix, which represents a zero translation. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} 1 & 0 & 0 \\ 0 & 1 & 0 \\ a & b & 1 \end{array}\right]\f$ */ bool Affine::isTranslation(Coord eps) const { return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps); }
/** @brief Check whether the transformation preserves distances between points. * This means that the transformation can be any combination of translation, * rotation and flipping. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} a & b & 0 \\ -b & a & 0 \\ c & d & 1 \end{array}\right]\f$ or \f$\left[\begin{array}{ccc} -a & b & 0 \\ b & a & 0 \\ c & d & 1 \end{array}\right]\f$ and \f$a^2 + b^2 = 1\f$. */ bool Affine::preservesDistances(Coord eps) const { return ((are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps)) || (are_near(_c[0], -_c[3], eps) && are_near(_c[1], _c[2], eps))) && are_near(_c[0] * _c[0] + _c[1] * _c[1], 1.0, eps); }
/** @brief Check whether the transformation preserves angles between lines. * This means that the transformation can be any combination of translation, uniform scaling, * rotation and flipping. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} a & b & 0 \\ -b & a & 0 \\ c & d & 1 \end{array}\right]\f$ or \f$\left[\begin{array}{ccc} -a & b & 0 \\ b & a & 0 \\ c & d & 1 \end{array}\right]\f$. */ bool Affine::preservesAngles(Coord eps) const { if (isSingular(eps)) return false; return (are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps)) || (are_near(_c[0], -_c[3], eps) && are_near(_c[1], _c[2], eps)); }
/** @brief Check whether the transformation preserves areas of polygons. * This means that the transformation can be any combination of translation, rotation, * shearing and squeezing (non-uniform scaling such that the absolute value of the product * of Y-scale and X-scale is 1). * @param eps Numerical tolerance * @return True iff \f$|\det A| = 1\f$. */ bool Affine::preservesArea(Coord eps) const { return are_near(descrim2(), 1.0, eps); }
TEST(AffineTest, Nearness) { Affine a1(1, 0, 1, 2, 1e-8, 1e-8); Affine a2(1+1e-8, 0, 1, 2-1e-8, -1e-8, -1e-8); EXPECT_TRUE(are_near(a1, a2, 1e-7)); EXPECT_FALSE(are_near(a1, a2, 1e-9)); }
void LPESimplify::generateHelperPathAndSmooth(Geom::PathVector &result) { if(steps < 1) { return; } Geom::PathVector tmp_path; Geom::CubicBezier const *cubic = NULL; for (Geom::PathVector::iterator path_it = result.begin(); path_it != result.end(); ++path_it) { if (path_it->empty()) { continue; } Geom::Path::iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::iterator curve_it2 = ++(path_it->begin());// outgoing curve Geom::Path::iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop SPCurve *nCurve = new SPCurve(); if (path_it->closed()) { // if the path is closed, maybe we have to stop a bit earlier because the // closing line segment has zerolength. const Geom::Curve &closingline = path_it->back_closed(); // the closing line segment is always of type // Geom::LineSegment. if (are_near(closingline.initialPoint(), closingline.finalPoint())) { // closingline.isDegenerate() did not work, because it only checks for // *exact* zero length, which goes wrong for relative coordinates and // rounding errors... // the closing line segment has zero-length. So stop before that one! curve_endit = path_it->end_open(); } } if(helper_size > 0) { drawNode(curve_it1->initialPoint()); } nCurve->moveto(curve_it1->initialPoint()); Geom::Point start = Geom::Point(0,0); while (curve_it1 != curve_endit) { cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); Geom::Point point_at1 = curve_it1->initialPoint(); Geom::Point point_at2 = curve_it1->finalPoint(); Geom::Point point_at3 = curve_it1->finalPoint(); Geom::Point point_at4 = curve_it1->finalPoint(); if(start == Geom::Point(0,0)) { start = point_at1; } if (cubic) { point_at1 = (*cubic)[1]; point_at2 = (*cubic)[2]; } if(path_it->closed() && curve_it2 == curve_endit) { point_at4 = start; } if(curve_it2 != curve_endit) { cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it2); if (cubic) { point_at4 = (*cubic)[1]; } } Geom::Ray ray1(point_at2, point_at3); Geom::Ray ray2(point_at3, point_at4); double angle1 = Geom::deg_from_rad(ray1.angle()); double angle2 = Geom::deg_from_rad(ray2.angle()); if((smooth_angles >= std::abs(angle2 - angle1)) && !are_near(point_at4,point_at3) && !are_near(point_at2,point_at3)) { double dist = Geom::distance(point_at2,point_at3); Geom::Angle angleFixed = ray2.angle(); angleFixed -= Geom::Angle::from_degrees(180.0); point_at2 = Geom::Point::polar(angleFixed, dist) + point_at3; } nCurve->curveto(point_at1, point_at2, curve_it1->finalPoint()); cubic = dynamic_cast<Geom::CubicBezier const *>(nCurve->last_segment()); if (cubic) { point_at1 = (*cubic)[1]; point_at2 = (*cubic)[2]; if(helper_size > 0) { if(!are_near((*cubic)[0],(*cubic)[1])) { drawHandle((*cubic)[1]); drawHandleLine((*cubic)[0],(*cubic)[1]); } if(!are_near((*cubic)[3],(*cubic)[2])) { drawHandle((*cubic)[2]); drawHandleLine((*cubic)[3],(*cubic)[2]); } } } if(helper_size > 0) { drawNode(curve_it1->finalPoint()); } ++curve_it1; ++curve_it2; } if (path_it->closed()) { nCurve->closepath_current(); } tmp_path.push_back(nCurve->get_pathvector()[0]); nCurve->reset(); delete nCurve; } result = tmp_path; }
TEST(AffineTest, Classification) { { Affine a; // identity EXPECT_TRUE(a.isIdentity()); EXPECT_TRUE(a.isTranslation()); EXPECT_TRUE(a.isScale()); EXPECT_TRUE(a.isUniformScale()); EXPECT_TRUE(a.isRotation()); EXPECT_TRUE(a.isHShear()); EXPECT_TRUE(a.isVShear()); EXPECT_TRUE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_TRUE(a.preservesArea()); EXPECT_TRUE(a.preservesAngles()); EXPECT_TRUE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = Translate(10, 15); // pure translation EXPECT_FALSE(a.isIdentity()); EXPECT_TRUE(a.isTranslation()); EXPECT_FALSE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_TRUE(a.isZoom()); EXPECT_TRUE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_TRUE(a.preservesArea()); EXPECT_TRUE(a.preservesAngles()); EXPECT_TRUE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = Scale(-1.0, 1.0); // flip on the X axis EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_TRUE(a.isScale()); EXPECT_TRUE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); // zoom must be non-flipping EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_TRUE(a.isNonzeroScale()); EXPECT_TRUE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_TRUE(a.preservesArea()); EXPECT_TRUE(a.preservesAngles()); EXPECT_TRUE(a.preservesDistances()); EXPECT_TRUE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = Scale(0.5, 0.5); // pure uniform scale EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_TRUE(a.isScale()); EXPECT_TRUE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_TRUE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_TRUE(a.isNonzeroScale()); EXPECT_TRUE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_FALSE(a.preservesArea()); EXPECT_TRUE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = Scale(0.5, -0.5); // pure uniform flipping scale EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_TRUE(a.isScale()); EXPECT_TRUE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); // zoom must be non-flipping EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_TRUE(a.isNonzeroScale()); EXPECT_TRUE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_FALSE(a.preservesArea()); EXPECT_TRUE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_TRUE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = Scale(0.5, 0.7); // pure non-uniform scale EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_TRUE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_TRUE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_FALSE(a.preservesArea()); EXPECT_FALSE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = Scale(0.5, 2.0); // "squeeze" transform (non-uniform scale with det=1) EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_TRUE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_TRUE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_TRUE(a.preservesArea()); EXPECT_FALSE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = Rotate(0.7); // pure rotation EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_FALSE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_TRUE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_TRUE(a.isNonzeroRotation()); EXPECT_TRUE(a.isNonzeroNonpureRotation()); EXPECT_EQ(a.rotationCenter(), Point(0.0,0.0)); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_TRUE(a.preservesArea()); EXPECT_TRUE(a.preservesAngles()); EXPECT_TRUE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Point rotation_center(1.23,4.56); Affine a = Translate(-rotation_center) * Rotate(0.7) * Translate(rotation_center); // rotation around (1.23,4.56) EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_FALSE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_TRUE(a.isNonzeroNonpureRotation()); EXPECT_TRUE(are_near(a.rotationCenter(), rotation_center, 1e-7)); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_TRUE(a.preservesArea()); EXPECT_TRUE(a.preservesAngles()); EXPECT_TRUE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = HShear(0.5); // pure horizontal shear EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_FALSE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_TRUE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_TRUE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_TRUE(a.preservesArea()); EXPECT_FALSE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = VShear(0.5); // pure vertical shear EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_FALSE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_TRUE(a.isVShear()); EXPECT_FALSE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_TRUE(a.isNonzeroVShear()); EXPECT_TRUE(a.preservesArea()); EXPECT_FALSE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); } { Affine a = Zoom(3.0, Translate(10, 15)); // zoom EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_FALSE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_TRUE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_FALSE(a.preservesArea()); EXPECT_TRUE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_FALSE(a.isSingular()); EXPECT_TRUE(a.withoutTranslation().isUniformScale()); EXPECT_TRUE(a.withoutTranslation().isNonzeroUniformScale()); } { Affine a(0, 0, 0, 0, 0, 0); // zero matrix (singular) EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_FALSE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_FALSE(a.preservesArea()); EXPECT_FALSE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_TRUE(a.isSingular()); } { Affine a(0, 1, 0, 1, 10, 10); // another singular matrix EXPECT_FALSE(a.isIdentity()); EXPECT_FALSE(a.isTranslation()); EXPECT_FALSE(a.isScale()); EXPECT_FALSE(a.isUniformScale()); EXPECT_FALSE(a.isRotation()); EXPECT_FALSE(a.isHShear()); EXPECT_FALSE(a.isVShear()); EXPECT_FALSE(a.isZoom()); EXPECT_FALSE(a.isNonzeroTranslation()); EXPECT_FALSE(a.isNonzeroScale()); EXPECT_FALSE(a.isNonzeroUniformScale()); EXPECT_FALSE(a.isNonzeroRotation()); EXPECT_FALSE(a.isNonzeroNonpureRotation()); EXPECT_FALSE(a.isNonzeroHShear()); EXPECT_FALSE(a.isNonzeroVShear()); EXPECT_FALSE(a.preservesArea()); EXPECT_FALSE(a.preservesAngles()); EXPECT_FALSE(a.preservesDistances()); EXPECT_FALSE(a.flips()); EXPECT_TRUE(a.isSingular()); } }
std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const { std::vector<Coord> sol; if ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) { if ( center(d) == v ) sol.push_back(0); return sol; } static const char* msg[2][2] = { { "d == X; ray(X) == 0; " "s = (v - center(X)) / ( -ray(Y) * std::sin(_rot_angle) ); " "s should be contained in [-1,1]", "d == X; ray(Y) == 0; " "s = (v - center(X)) / ( ray(X) * std::cos(_rot_angle) ); " "s should be contained in [-1,1]" }, { "d == Y; ray(X) == 0; " "s = (v - center(X)) / ( ray(Y) * std::cos(_rot_angle) ); " "s should be contained in [-1,1]", "d == Y; ray(Y) == 0; " "s = (v - center(X)) / ( ray(X) * std::sin(_rot_angle) ); " "s should be contained in [-1,1]" }, }; for ( unsigned int dim = 0; dim < 2; ++dim ) { if ( are_near(ray((Dim2) dim), 0) ) { if ( initialPoint()[d] == v && finalPoint()[d] == v ) { THROW_INFINITESOLUTIONS(0); } if ( (initialPoint()[d] < finalPoint()[d]) && (initialPoint()[d] > v || finalPoint()[d] < v) ) { return sol; } if ( (initialPoint()[d] > finalPoint()[d]) && (finalPoint()[d] > v || initialPoint()[d] < v) ) { return sol; } double ray_prj = 0.0; switch(d) { case X: switch(dim) { case X: ray_prj = -ray(Y) * std::sin(_rot_angle); break; case Y: ray_prj = ray(X) * std::cos(_rot_angle); break; } break; case Y: switch(dim) { case X: ray_prj = ray(Y) * std::cos(_rot_angle); break; case Y: ray_prj = ray(X) * std::sin(_rot_angle); break; } break; } double s = (v - center(d)) / ray_prj; if ( s < -1 || s > 1 ) { THROW_LOGICALERROR(msg[d][dim]); } switch(dim) { case X: s = std::asin(s); // return a value in [-PI/2,PI/2] if ( logical_xor( _sweep, are_near(initialAngle(), M_PI/2) ) ) { if ( s < 0 ) s += 2*M_PI; } else { s = M_PI - s; if (!(s < 2*M_PI) ) s -= 2*M_PI; } break; case Y: s = std::acos(s); // return a value in [0,PI] if ( logical_xor( _sweep, are_near(initialAngle(), 0) ) ) { s = 2*M_PI - s; if ( !(s < 2*M_PI) ) s -= 2*M_PI; } break; } //std::cerr << "s = " << rad_to_deg(s); s = map_to_01(s); //std::cerr << " -> t: " << s << std::endl; if ( !(s < 0 || s > 1) ) sol.push_back(s); return sol; } } double rotx, roty; sincos(_rot_angle, roty, rotx); if (d == X) roty = -roty; double rxrotx = ray(X) * rotx; double c_v = center(d) - v; double a = -rxrotx + c_v; double b = ray(Y) * roty; double c = rxrotx + c_v; //std::cerr << "a = " << a << std::endl; //std::cerr << "b = " << b << std::endl; //std::cerr << "c = " << c << std::endl; if ( are_near(a,0) ) { sol.push_back(M_PI); if ( !are_near(b,0) ) { double s = 2 * std::atan(-c/(2*b)); if ( s < 0 ) s += 2*M_PI; sol.push_back(s); } } else { double delta = b * b - a * c; //std::cerr << "delta = " << delta << std::endl; if ( are_near(delta, 0) ) { double s = 2 * std::atan(-b/a); if ( s < 0 ) s += 2*M_PI; sol.push_back(s); } else if ( delta > 0 ) { double sq = std::sqrt(delta); double s = 2 * std::atan( (-b - sq) / a ); if ( s < 0 ) s += 2*M_PI; sol.push_back(s); s = 2 * std::atan( (-b + sq) / a ); if ( s < 0 ) s += 2*M_PI; sol.push_back(s); } } std::vector<double> arc_sol; for (unsigned int i = 0; i < sol.size(); ++i ) { //std::cerr << "s = " << rad_to_deg(sol[i]); sol[i] = map_to_01(sol[i]); //std::cerr << " -> t: " << sol[i] << std::endl; if ( !(sol[i] < 0 || sol[i] > 1) ) arc_sol.push_back(sol[i]); } return arc_sol; }
/** @brief Check whether this matrix is singular. * Singular matrices have no inverse, which means that applying them to a set of points * results in a loss of information. * @param eps Numerical tolerance * @return True iff the determinant is near zero. */ bool Affine::isSingular(Coord eps) const { return are_near(det(), 0.0, eps); }
/** @brief Check whether this matrix represents pure, nonzero vertical shearing. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} 1 & k & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{array}\right]\f$ and \f$k \neq 0\f$. */ bool Affine::isNonzeroVShear(Coord eps) const { return are_near(_c[0], 1.0, eps) && !are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) && are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); }
/** @brief Nearness predicate for affine transforms * @returns True if all entries of matrices are within eps of each other */ bool are_near(Affine const &a, Affine const &b, Coord eps) { return are_near(a[0], b[0], eps) && are_near(a[1], b[1], eps) && are_near(a[2], b[2], eps) && are_near(a[3], b[3], eps) && are_near(a[4], b[4], eps) && are_near(a[5], b[5], eps); }
/** @brief Check whether this matrix is an identity matrix. * @param eps Numerical tolerance * @return True iff the matrix is of the form * \f$\left[\begin{array}{ccc} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{array}\right]\f$ */ bool Affine::isIdentity(Coord eps) const { return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) && are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); }