diff options
Diffstat (limited to '')
93 files changed, 25907 insertions, 0 deletions
diff --git a/include/2geom/2geom.h b/include/2geom/2geom.h new file mode 100644 index 0000000..7bf36ae --- /dev/null +++ b/include/2geom/2geom.h @@ -0,0 +1,75 @@ +/** + * \file + * \brief Include everything + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2011 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_2GEOM_H +#define LIB2GEOM_SEEN_2GEOM_H + +#include <2geom/forward.h> + +// primitives +#include <2geom/coord.h> +#include <2geom/point.h> +#include <2geom/interval.h> +#include <2geom/rect.h> +#include <2geom/angle.h> +#include <2geom/ray.h> +#include <2geom/line.h> +#include <2geom/affine.h> +#include <2geom/transforms.h> + +// curves and paths +#include <2geom/curves.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> + +// fragments +#include <2geom/d2.h> +#include <2geom/linear.h> +#include <2geom/bezier.h> +#include <2geom/sbasis.h> + +// others +#include <2geom/math-utils.h> +#include <2geom/utils.h> + +#endif // LIB2GEOM_SEEN_2GEOM_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/affine.h b/include/2geom/affine.h new file mode 100644 index 0000000..470d5fc --- /dev/null +++ b/include/2geom/affine.h @@ -0,0 +1,244 @@ +/** + * \file + * \brief 3x3 affine transformation matrix. + *//* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> (Original NRAffine definition and related macros) + * Nathan Hurst <njh@mail.csse.monash.edu.au> (Geom::Affine class version of the above) + * Michael G. Sloan <mgsloan@gmail.com> (reorganization and additions) + * Krzysztof Kosiński <tweenk.pl@gmail.com> (removal of boilerplate, docs) + * + * This code is in public domain. + */ + +#ifndef LIB2GEOM_SEEN_AFFINE_H +#define LIB2GEOM_SEEN_AFFINE_H + +#include <boost/operators.hpp> +#include <2geom/forward.h> +#include <2geom/point.h> +#include <2geom/utils.h> + +namespace Geom { + +/** + * @brief 3x3 matrix representing an affine transformation. + * + * Affine transformations on elements of a vector space are transformations which can be + * expressed in terms of matrix multiplication followed by addition + * (\f$x \mapsto A x + b\f$). They can be thought of as generalizations of linear functions + * (\f$y = a x + b\f$) to vector spaces. Affine transformations of points on a 2D plane preserve + * the following properties: + * + * - Colinearity: if three points lie on the same line, they will still be colinear after + * an affine transformation. + * - Ratios of distances between points on the same line are preserved + * - Parallel lines remain parallel. + * + * All affine transformations on 2D points can be written as combinations of scaling, rotation, + * shearing and translation. They can be represented as a combination of a vector and a 2x2 matrix, + * but this form is inconvenient to work with. A better solution is to represent all affine + * transformations on the 2D plane as 3x3 matrices, where the last column has fixed values. + * \f[ A = \left[ \begin{array}{ccc} + c_0 & c_1 & 0 \\ + c_2 & c_3 & 0 \\ + c_4 & c_5 & 1 \end{array} \right]\f] + * + * We then interpret points as row vectors of the form \f$[p_X, p_Y, 1]\f$. Applying a + * transformation to a point can be written as right-multiplication by a 3x3 matrix + * (\f$p' = pA\f$). This subset of matrices is closed under multiplication - combination + * of any two transforms can be expressed as the multiplication of their matrices. + * In this representation, the \f$c_4\f$ and \f$c_5\f$ coefficients represent + * the translation component of the transformation. + * + * Matrices can be multiplied by other more specific transformations. When multiplying, + * the transformations are applied from left to right, so for example <code>m = a * b</code> + * means: @a m first transforms by a, then by b. + * + * @ingroup Transforms + */ +class Affine + : boost::equality_comparable< Affine // generates operator!= from operator== + , boost::multipliable1< Affine + , MultipliableNoncommutative< Affine, Translate + , MultipliableNoncommutative< Affine, Scale + , MultipliableNoncommutative< Affine, Rotate + , MultipliableNoncommutative< Affine, HShear + , MultipliableNoncommutative< Affine, VShear + , MultipliableNoncommutative< Affine, Zoom + > > > > > > > > +{ + Coord _c[6]; +public: + Affine() { + _c[0] = _c[3] = 1; + _c[1] = _c[2] = _c[4] = _c[5] = 0; + } + + /** @brief Create a matrix from its coefficient values. + * It's rather inconvenient to directly create matrices in this way. Use transform classes + * if your transformation has a geometric interpretation. + * @see Translate + * @see Scale + * @see Rotate + * @see HShear + * @see VShear + * @see Zoom */ + Affine(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5) { + _c[0] = c0; _c[1] = c1; + _c[2] = c2; _c[3] = c3; + _c[4] = c4; _c[5] = c5; + } + + /** @brief Access a coefficient by its index. */ + inline Coord operator[](unsigned i) const { return _c[i]; } + inline Coord &operator[](unsigned i) { return _c[i]; } + + /// @name Combine with other transformations + /// @{ + Affine &operator*=(Affine const &m); + // implemented in transforms.cpp + Affine &operator*=(Translate const &t); + Affine &operator*=(Scale const &s); + Affine &operator*=(Rotate const &r); + Affine &operator*=(HShear const &h); + Affine &operator*=(VShear const &v); + Affine &operator*=(Zoom const &); + /// @} + + bool operator==(Affine const &o) const { + for(unsigned i = 0; i < 6; ++i) { + if ( _c[i] != o._c[i] ) return false; + } + return true; + } + + /// @name Get the parameters of the matrix's transform + /// @{ + Point xAxis() const; + Point yAxis() const; + Point translation() const; + Coord expansionX() const; + Coord expansionY() const; + Point expansion() const { return Point(expansionX(), expansionY()); } + /// @} + + /// @name Modify the matrix + /// @{ + void setXAxis(Point const &vec); + void setYAxis(Point const &vec); + + void setTranslation(Point const &loc); + + void setExpansionX(Coord val); + void setExpansionY(Coord val); + void setIdentity(); + /// @} + + /// @name Inspect the matrix's transform + /// @{ + bool isIdentity(Coord eps = EPSILON) const; + + bool isTranslation(Coord eps = EPSILON) const; + bool isScale(Coord eps = EPSILON) const; + bool isUniformScale(Coord eps = EPSILON) const; + bool isRotation(Coord eps = EPSILON) const; + bool isHShear(Coord eps = EPSILON) const; + bool isVShear(Coord eps = EPSILON) const; + + bool isNonzeroTranslation(Coord eps = EPSILON) const; + bool isNonzeroScale(Coord eps = EPSILON) const; + bool isNonzeroUniformScale(Coord eps = EPSILON) const; + bool isNonzeroRotation(Coord eps = EPSILON) const; + bool isNonzeroNonpureRotation(Coord eps = EPSILON) const; + Point rotationCenter() const; + bool isNonzeroHShear(Coord eps = EPSILON) const; + bool isNonzeroVShear(Coord eps = EPSILON) const; + + bool isZoom(Coord eps = EPSILON) const; + bool preservesArea(Coord eps = EPSILON) const; + bool preservesAngles(Coord eps = EPSILON) const; + bool preservesDistances(Coord eps = EPSILON) const; + bool flips() const; + + bool isSingular(Coord eps = EPSILON) const; + /// @} + + /// @name Compute other matrices + /// @{ + Affine withoutTranslation() const { + Affine ret(*this); + ret.setTranslation(Point(0,0)); + return ret; + } + Affine inverse() const; + /// @} + + /// @name Compute scalar values + /// @{ + Coord det() const; + Coord descrim2() const; + Coord descrim() const; + /// @} + inline static Affine identity(); +}; + +/** @brief Print out the Affine (for debugging). + * @relates Affine */ +inline std::ostream &operator<< (std::ostream &out_file, const Geom::Affine &m) { + out_file << "A: " << m[0] << " C: " << m[2] << " E: " << m[4] << "\n"; + out_file << "B: " << m[1] << " D: " << m[3] << " F: " << m[5] << "\n"; + return out_file; +} + +// Affine factories +Affine from_basis(const Point &x_basis, const Point &y_basis, const Point &offset=Point(0,0)); +Affine elliptic_quadratic_form(Affine const &m); + +/** Given a matrix (ignoring the translation) this returns the eigen + * values and vectors. */ +class Eigen{ +public: + Point vectors[2]; + double values[2]; + Eigen(Affine const &m); + Eigen(double M[2][2]); +}; + +/** @brief Create an identity matrix. + * This is a convenience function identical to Affine::identity(). */ +inline Affine identity() { + Affine ret(Affine::identity()); + return ret; // allow NRVO +} + +/** @brief Create an identity matrix. + * @return The matrix + * \f$\left[\begin{array}{ccc} + 1 & 0 & 0 \\ + 0 & 1 & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$. + * @relates Affine */ +inline Affine Affine::identity() { + Affine ret(1.0, 0.0, + 0.0, 1.0, + 0.0, 0.0); + return ret; // allow NRVO +} + +bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON); + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_AFFINE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/angle.h b/include/2geom/angle.h new file mode 100644 index 0000000..f0caaba --- /dev/null +++ b/include/2geom/angle.h @@ -0,0 +1,408 @@ +/** + * \file + * \brief Various trigoniometric helper functions + *//* + * Authors: + * Johan Engelen <goejendaagh@zonnet.nl> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright (C) 2007-2010 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_ANGLE_H +#define LIB2GEOM_SEEN_ANGLE_H + +#include <cmath> +#include <boost/operators.hpp> +#include <2geom/exception.h> +#include <2geom/coord.h> +#include <2geom/point.h> + +namespace Geom { + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif +#ifndef M_1_2PI +# define M_1_2PI 0.159154943091895335768883763373 +#endif + +/** @brief Wrapper for angular values. + * + * This class is a convenience wrapper that implements the behavior generally expected of angles, + * like addition modulo \f$2\pi\f$. The value returned from the default conversion + * to <tt>double</tt> is in the range \f$[-\pi, \pi)\f$ - the convention used by C's + * math library. + * + * This class holds only a single floating point value, so passing it by value will generally + * be faster than passing it by const reference. + * + * @ingroup Primitives + */ +class Angle + : boost::additive< Angle + , boost::additive< Angle, Coord + , boost::equality_comparable< Angle + , boost::equality_comparable< Angle, Coord + > > > > +{ +public: + Angle() : _angle(0) {} + Angle(Coord v) : _angle(v) { _normalize(); } // this can be called implicitly + explicit Angle(Point const &p) : _angle(atan2(p)) { _normalize(); } + Angle(Point const &a, Point const &b) : _angle(angle_between(a, b)) { _normalize(); } + operator Coord() const { return radians(); } + Angle &operator+=(Angle o) { + _angle += o._angle; + _normalize(); + return *this; + } + Angle &operator-=(Angle o) { + _angle -= o._angle; + _normalize(); + return *this; + } + Angle &operator+=(Coord a) { + *this += Angle(a); + return *this; + } + Angle &operator-=(Coord a) { + *this -= Angle(a); + return *this; + } + bool operator==(Angle o) const { + return _angle == o._angle; + } + bool operator==(Coord c) const { + return _angle == Angle(c)._angle; + } + + /** @brief Get the angle as radians. + * @return Number in range \f$[-\pi, \pi)\f$. */ + Coord radians() const { + return _angle >= M_PI ? _angle - 2*M_PI : _angle; + } + /** @brief Get the angle as positive radians. + * @return Number in range \f$[0, 2\pi)\f$. */ + Coord radians0() const { + return _angle; + } + /** @brief Get the angle as degrees in math convention. + * @return Number in range [-180, 180) obtained by scaling the result of radians() + * by \f$180/\pi\f$. */ + Coord degrees() const { return radians() * (180.0 / M_PI); } + /** @brief Get the angle as degrees in clock convention. + * This method converts the angle to the "clock convention": angles start from the +Y axis + * and grow clockwise. This means that 0 corresponds to \f$\pi/2\f$ radians, + * 90 to 0 radians, 180 to \f$-\pi/2\f$ radians, and 270 to \f$\pi\f$ radians. + * @return A number in the range [0, 360). + */ + Coord degreesClock() const { + Coord ret = 90.0 - _angle * (180.0 / M_PI); + if (ret < 0) ret += 360; + return ret; + } + /** @brief Create an angle from its measure in radians. */ + static Angle from_radians(Coord d) { + Angle a(d); + return a; + } + /** @brief Create an angle from its measure in degrees. */ + static Angle from_degrees(Coord d) { + Angle a(d * (M_PI / 180.0)); + return a; + } + /** @brief Create an angle from its measure in degrees in clock convention. + * @see Angle::degreesClock() */ + static Angle from_degrees_clock(Coord d) { + // first make sure d is in [0, 360) + d = std::fmod(d, 360.0); + if (d < 0) d += 360.0; + Coord rad = M_PI/2 - d * (M_PI / 180.0); + if (rad < 0) rad += 2*M_PI; + Angle a; + a._angle = rad; + return a; + } +private: + + void _normalize() { + _angle = std::fmod(_angle, 2*M_PI); + if (_angle < 0) _angle += 2*M_PI; + //_angle -= floor(_angle * (1.0/(2*M_PI))) * 2*M_PI; + } + Coord _angle; // this is always in [0, 2pi) + friend class AngleInterval; +}; + +inline Angle distance(Angle const &a, Angle const &b) { + // the distance cannot be larger than M_PI. + Coord ac = a.radians0(); + Coord bc = b.radians0(); + Coord d = fabs(ac - bc); + return Angle(d > M_PI ? 2*M_PI - d : d); +} + +/** @brief Directed angular interval. + * + * Wrapper for directed angles with defined start and end values. Useful e.g. for representing + * the portion of an ellipse in an elliptical arc. Both extreme angles are contained + * in the interval (it is a closed interval). Angular intervals can also be interptered + * as functions \f$f: [0, 1] \to [-\pi, \pi)\f$, which return the start angle for 0, + * the end angle for 1, and interpolate linearly for other values. Note that such functions + * are not continuous if the interval crosses the angle \f$\pi\f$. + * + * This class can represent all directed angular intervals, including empty ones. + * However, not all possible intervals can be created with the constructors. + * For full control, use the setInitial(), setFinal() and setAngles() methods. + * + * @ingroup Primitives + */ +class AngleInterval + : boost::equality_comparable< AngleInterval > +{ +public: + AngleInterval() {} + /** @brief Create an angular interval from two angles and direction. + * If the initial and final angle are the same, a degenerate interval + * (containing only one angle) will be created. + * @param s Starting angle + * @param e Ending angle + * @param cw Which direction the interval goes. True means that it goes + * in the direction of increasing angles, while false means in the direction + * of decreasing angles. */ + AngleInterval(Angle s, Angle e, bool cw = false) + : _start_angle(s), _end_angle(e), _sweep(cw), _full(false) + {} + AngleInterval(double s, double e, bool cw = false) + : _start_angle(s), _end_angle(e), _sweep(cw), _full(false) + {} + /** @brief Create an angular interval from three angles. + * If the inner angle is exactly equal to initial or final angle, + * the sweep flag will be set to true, i.e. the interval will go + * in the direction of increasing angles. + * + * If the initial and final angle are the same, but the inner angle + * is different, a full angle in the direction of increasing angles + * will be created. + * + * @param s Initial angle + * @param inner Angle contained in the interval + * @param e Final angle */ + AngleInterval(Angle s, Angle inner, Angle e) + : _start_angle(s) + , _end_angle(e) + , _sweep((inner-s).radians0() <= (e-s).radians0()) + , _full(s == e && s != inner) + { + if (_full) { + _sweep = true; + } + } + + /// Get the start angle. + Angle initialAngle() const { return _start_angle; } + /// Get the end angle. + Angle finalAngle() const { return _end_angle; } + /// Check whether the interval goes in the direction of increasing angles. + bool sweep() const { return _sweep; } + /// Check whether the interval contains only a single angle. + bool isDegenerate() const { + return _start_angle == _end_angle && !_full; + } + /// Check whether the interval contains all angles. + bool isFull() const { + return _start_angle == _end_angle && _full; + } + + /** @brief Set the initial angle. + * @param a Angle to set + * @param prefer_full Whether to set a full angular interval when + * the initial angle is set to the final angle */ + void setInitial(Angle a, bool prefer_full = false) { + _start_angle = a; + _full = prefer_full && a == _end_angle; + } + + /** @brief Set the final angle. + * @param a Angle to set + * @param prefer_full Whether to set a full angular interval when + * the initial angle is set to the final angle */ + void setFinal(Angle a, bool prefer_full = false) { + _end_angle = a; + _full = prefer_full && a == _start_angle; + } + /** @brief Set both angles at once. + * The direction (sweep flag) is left unchanged. + * @param s Initial angle + * @param e Final angle + * @param prefer_full Whether to set a full interval when the passed + * initial and final angle are the same */ + void setAngles(Angle s, Angle e, bool prefer_full = false) { + _start_angle = s; + _end_angle = e; + _full = prefer_full && s == e; + } + /// Set whether the interval goes in the direction of increasing angles. + void setSweep(bool s) { _sweep = s; } + + /// Reverse the direction of the interval while keeping contained values the same. + void reverse() { + using std::swap; + swap(_start_angle, _end_angle); + _sweep = !_sweep; + } + /// Get a new interval with reversed direction. + AngleInterval reversed() const { + AngleInterval result(*this); + result.reverse(); + return result; + } + + /// Get an angle corresponding to the specified time value. + Angle angleAt(Coord t) const { + Coord span = extent(); + Angle ret = _start_angle.radians0() + span * (_sweep ? t : -t); + return ret; + } + Angle operator()(Coord t) const { return angleAt(t); } + + /** @brief Compute a time value that would evaluate to the given angle. + * If the start and end angle are exactly the same, NaN will be returned. + * Negative values will be returned for angles between the initial angle + * and the angle exactly opposite the midpoint of the interval. */ + Coord timeAtAngle(Angle a) const { + if (_full) { + Angle ta = _sweep ? a - _start_angle : _start_angle - a; + return ta.radians0() / (2*M_PI); + } + Coord ex = extent(); + Coord outex = 2*M_PI - ex; + if (_sweep) { + Angle midout = _start_angle - outex / 2; + Angle acmp = a - midout, scmp = _start_angle - midout; + if (acmp.radians0() >= scmp.radians0()) { + return (a - _start_angle).radians0() / ex; + } else { + return -(_start_angle - a).radians0() / ex; + } + } else { + Angle midout = _start_angle + outex / 2; + Angle acmp = a - midout, scmp = _start_angle - midout; + if (acmp.radians0() <= scmp.radians0()) { + return (_start_angle - a).radians0() / ex; + } else { + return -(a - _start_angle).radians0() / ex; + } + } + } + + /// Check whether the interval includes the given angle. + bool contains(Angle a) const { + if (_full) return true; + Coord s = _start_angle.radians0(); + Coord e = _end_angle.radians0(); + Coord x = a.radians0(); + if (_sweep) { + if (s < e) return x >= s && x <= e; + return x >= s || x <= e; + } else { + if (s > e) return x <= s && x >= e; + return x <= s || x >= e; + } + } + /** @brief Extent of the angle interval. + * Equivalent to the absolute value of the sweep angle. + * @return Extent in range \f$[0, 2\pi)\f$. */ + Coord extent() const { + if (_full) return 2*M_PI; + return _sweep + ? (_end_angle - _start_angle).radians0() + : (_start_angle - _end_angle).radians0(); + } + /** @brief Get the sweep angle of the interval. + * This is the value you need to add to the initial angle to get the final angle. + * It is positive when sweep is true. Denoted as \f$\Delta\theta\f$ in the SVG + * elliptical arc implementation notes. */ + Coord sweepAngle() const { + if (_full) return _sweep ? 2*M_PI : -2*M_PI; + Coord sa = _end_angle.radians0() - _start_angle.radians0(); + if (_sweep && sa < 0) sa += 2*M_PI; + if (!_sweep && sa > 0) sa -= 2*M_PI; + return sa; + } + + /// Check another interval for equality. + bool operator==(AngleInterval const &other) const { + if (_start_angle != other._start_angle) return false; + if (_end_angle != other._end_angle) return false; + if (_sweep != other._sweep) return false; + if (_full != other._full) return false; + return true; + } + + static AngleInterval create_full(Angle start, bool sweep = true) { + AngleInterval result; + result._start_angle = result._end_angle = start; + result._sweep = sweep; + result._full = true; + return result; + } + +private: + Angle _start_angle; + Angle _end_angle; + bool _sweep; + bool _full; +}; + +/** @brief Given an angle in degrees, return radians + * @relates Angle */ +inline Coord rad_from_deg(Coord deg) { return deg*M_PI/180.0;} +/** @brief Given an angle in radians, return degrees + * @relates Angle */ +inline Coord deg_from_rad(Coord rad) { return rad*180.0/M_PI;} + +} // end namespace Geom + +namespace std { +template <> class iterator_traits<Geom::Angle> {}; +} + +#endif // LIB2GEOM_SEEN_ANGLE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/basic-intersection.h b/include/2geom/basic-intersection.h new file mode 100644 index 0000000..2d0c00d --- /dev/null +++ b/include/2geom/basic-intersection.h @@ -0,0 +1,151 @@ +/** @file + * @brief Basic intersection routines + *//* + * Authors: + * Nathan Hurst <njh@njhurst.com> + * Marco Cecchetti <mrcekets at gmail.com> + * Jean-François Barraud <jf.barraud@gmail.com> + * + * Copyright 2008-2009 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_BASIC_INTERSECTION_H +#define LIB2GEOM_SEEN_BASIC_INTERSECTION_H + +#include <2geom/point.h> +#include <2geom/bezier.h> +#include <2geom/sbasis.h> +#include <2geom/d2.h> + +#include <vector> +#include <utility> + +#define USE_RECURSIVE_INTERSECTOR 0 + + +namespace Geom { + +void find_intersections(std::vector<std::pair<double, double> > &xs, + D2<Bezier> const &A, + D2<Bezier> const &B, + double precision = EPSILON); + +void find_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const &A, + D2<SBasis> const &B, + double precision = EPSILON); + +void find_intersections(std::vector< std::pair<double, double> > &xs, + std::vector<Point> const &A, + std::vector<Point> const &B, + double precision = EPSILON); + +void find_self_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const &A, + double precision = EPSILON); + +void find_self_intersections(std::vector<std::pair<double, double> > &xs, + D2<Bezier> const &A, + double precision = EPSILON); + +/* + * find_intersection + * + * input: A, B - set of control points of two Bezier curve + * input: precision - required precision of computation + * output: xs - set of pairs of parameter values + * at which crossing happens + * + * This routine is based on the Bezier Clipping Algorithm, + * see: Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping + */ +void find_intersections_bezier_clipping (std::vector< std::pair<double, double> > & xs, + std::vector<Point> const& A, + std::vector<Point> const& B, + double precision = EPSILON); +//#endif + +void subdivide(D2<Bezier> const &a, + D2<Bezier> const &b, + std::vector< std::pair<double, double> > const &xs, + std::vector< D2<Bezier> > &av, + std::vector< D2<Bezier> > &bv); + +/* + * find_collinear_normal + * + * input: A, B - set of control points of two Bezier curve + * input: precision - required precision of computation + * output: xs - set of pairs of parameter values + * at which there are collinear normals + * + * This routine is based on the Bezier Clipping Algorithm, + * see: Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping + */ +void find_collinear_normal (std::vector< std::pair<double, double> >& xs, + std::vector<Point> const& A, + std::vector<Point> const& B, + double precision = EPSILON); + +void polish_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const &A, + D2<SBasis> const &B); + + +/** + * Compute the Hausdorf distance from A to B only. + */ +double hausdorfl(D2<SBasis>& A, D2<SBasis> const &B, + double m_precision, + double *a_t=NULL, double *b_t=NULL); + +/** + * Compute the symmetric Hausdorf distance. + */ +double hausdorf(D2<SBasis> &A, D2<SBasis> const &B, + double m_precision, + double *a_t=NULL, double *b_t=NULL); + +/** + * Check if two line segments intersect. If they are collinear, the result is undefined. + * @return True if line segments AB and CD intersect + */ +bool non_collinear_segments_intersect(const Point &A, const Point &B, const Point &C, const Point &D); +} + +#endif // !LIB2GEOM_SEEN_BASIC_INTERSECTION_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/bezier-curve.h b/include/2geom/bezier-curve.h new file mode 100644 index 0000000..754c9cc --- /dev/null +++ b/include/2geom/bezier-curve.h @@ -0,0 +1,366 @@ +/** + * \file + * \brief Bezier curve + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2011 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_BEZIER_CURVE_H +#define LIB2GEOM_SEEN_BEZIER_CURVE_H + +#include <2geom/curve.h> +#include <2geom/sbasis-curve.h> // for non-native winding method +#include <2geom/bezier.h> +#include <2geom/transforms.h> + +namespace Geom +{ + +class BezierCurve : public Curve { +protected: + D2<Bezier> inner; + BezierCurve() {} + BezierCurve(Bezier const &x, Bezier const &y) : inner(x, y) {} + BezierCurve(std::vector<Point> const &pts); + +public: + explicit BezierCurve(D2<Bezier> const &b) : inner(b) {} + + /// @name Access and modify control points + /// @{ + /** @brief Get the order of the Bezier curve. + * A Bezier curve has order() + 1 control points. */ + unsigned order() const { return inner[X].order(); } + /** @brief Get the number of control points. */ + unsigned size() const { return inner[X].order() + 1; } + /** @brief Access control points of the curve. + * @param ix The (zero-based) index of the control point. Note that the caller is responsible for checking that this value is <= order(). + * @return The control point. No-reference return, use setPoint() to modify control points. */ + Point controlPoint(unsigned ix) const { return Point(inner[X][ix], inner[Y][ix]); } + Point operator[](unsigned ix) const { return Point(inner[X][ix], inner[Y][ix]); } + /** @brief Get the control points. + * @return Vector with order() + 1 control points. */ + std::vector<Point> controlPoints() const { return bezier_points(inner); } + D2<Bezier> const &fragment() const { return inner; } + + /** @brief Modify a control point. + * @param ix The zero-based index of the point to modify. Note that the caller is responsible for checking that this value is <= order(). + * @param v The new value of the point */ + void setPoint(unsigned ix, Point const &v) { + inner[X][ix] = v[X]; + inner[Y][ix] = v[Y]; + } + /** @brief Set new control points. + * @param ps Vector which must contain order() + 1 points. + * Note that the caller is responsible for checking the size of this vector. + * @throws LogicalError Thrown when the size of the vector does not match the order. */ + virtual void setPoints(std::vector<Point> const &ps) { + // must be virtual, because HLineSegment will need to redefine it + if (ps.size() != order() + 1) + THROW_LOGICALERROR("BezierCurve::setPoints: incorrect number of points in vector"); + for(unsigned i = 0; i <= order(); i++) { + setPoint(i, ps[i]); + } + } + /// @} + + /// @name Construct a Bezier curve with runtime-determined order. + /// @{ + /** @brief Construct a curve from a vector of control points. + * This will construct the appropriate specialization of BezierCurve (i.e. LineSegment, + * QuadraticBezier or Cubic Bezier) if the number of control points in the passed vector + * does not exceed 4. */ + static BezierCurve *create(std::vector<Point> const &pts); + /// @} + + // implementation of virtual methods goes here + Point initialPoint() const override { return inner.at0(); } + Point finalPoint() const override { return inner.at1(); } + bool isDegenerate() const override; + bool isLineSegment() const override; + void setInitial(Point const &v) override { setPoint(0, v); } + void setFinal(Point const &v) override { setPoint(order(), v); } + Rect boundsFast() const override { return *bounds_fast(inner); } + Rect boundsExact() const override { return *bounds_exact(inner); } + void expandToTransformed(Rect &bbox, Affine const &transform) const override; + OptRect boundsLocal(OptInterval const &i, unsigned deg) const override { + if (!i) return OptRect(); + if(i->min() == 0 && i->max() == 1) return boundsFast(); + if(deg == 0) return bounds_local(inner, i); + // TODO: UUUUUUGGGLLY + if(deg == 1 && order() > 1) return OptRect(bounds_local(Geom::derivative(inner[X]), i), + bounds_local(Geom::derivative(inner[Y]), i)); + return OptRect(); + } + Curve *duplicate() const override { + return new BezierCurve(*this); + } + + Curve *portion(Coord f, Coord t) const override; + + Curve *reverse() const override { + return new BezierCurve(Geom::reverse(inner)); + } + + using Curve::operator*=; + void operator*=(Translate const &tr) override { + for (unsigned i = 0; i < size(); ++i) { + inner[X][i] += tr[X]; + inner[Y][i] += tr[Y]; + } + } + void operator*=(Scale const &s) override { + for (unsigned i = 0; i < size(); ++i) { + inner[X][i] *= s[X]; + inner[Y][i] *= s[Y]; + } + } + void operator*=(Affine const &m) override { + for (unsigned i = 0; i < size(); ++i) { + setPoint(i, controlPoint(i) * m); + } + } + + Curve *derivative() const override { + return new BezierCurve(Geom::derivative(inner[X]), Geom::derivative(inner[Y])); + } + int degreesOfFreedom() const override { + return 2 * (order() + 1); + } + std::vector<Coord> roots(Coord v, Dim2 d) const override { + return (inner[d] - v).roots(); + } + Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const override; + Coord length(Coord tolerance) const override; + std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const override; + Point pointAt(Coord t) const override { return inner.pointAt(t); } + std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const override { + return inner.valueAndDerivatives(t, n); + } + Coord valueAt(Coord t, Dim2 d) const override { return inner[d].valueAt(t); } + D2<SBasis> toSBasis() const override {return inner.toSBasis(); } + bool isNear(Curve const &c, Coord precision) const override; + bool operator==(Curve const &c) const override; + void feed(PathSink &sink, bool) const override; +}; + +template <unsigned degree> +class BezierCurveN + : public BezierCurve +{ + template <unsigned required_degree> + static void assert_degree(BezierCurveN<required_degree> const *) {} + +public: + /// @name Construct Bezier curves + /// @{ + /** @brief Construct a Bezier curve of the specified order with all points zero. */ + BezierCurveN() { + inner = D2<Bezier>(Bezier(Bezier::Order(degree)), Bezier(Bezier::Order(degree))); + } + + /** @brief Construct from 2D Bezier polynomial. */ + explicit BezierCurveN(D2<Bezier > const &x) { + inner = x; + } + + /** @brief Construct from two 1D Bezier polynomials of the same order. */ + BezierCurveN(Bezier x, Bezier y) { + inner = D2<Bezier > (x,y); + } + + /** @brief Construct a Bezier curve from a vector of its control points. */ + BezierCurveN(std::vector<Point> const &points) { + unsigned ord = points.size() - 1; + if (ord != degree) THROW_LOGICALERROR("BezierCurve<degree> does not match number of points"); + for (unsigned d = 0; d < 2; ++d) { + inner[d] = Bezier(Bezier::Order(ord)); + for(unsigned i = 0; i <= ord; i++) + inner[d][i] = points[i][d]; + } + } + + /** @brief Construct a linear segment from its endpoints. */ + BezierCurveN(Point c0, Point c1) { + assert_degree<1>(this); + for(unsigned d = 0; d < 2; d++) + inner[d] = Bezier(c0[d], c1[d]); + } + + /** @brief Construct a quadratic Bezier curve from its control points. */ + BezierCurveN(Point c0, Point c1, Point c2) { + assert_degree<2>(this); + for(unsigned d = 0; d < 2; d++) + inner[d] = Bezier(c0[d], c1[d], c2[d]); + } + + /** @brief Construct a cubic Bezier curve from its control points. */ + BezierCurveN(Point c0, Point c1, Point c2, Point c3) { + assert_degree<3>(this); + for(unsigned d = 0; d < 2; d++) + inner[d] = Bezier(c0[d], c1[d], c2[d], c3[d]); + } + + // default copy + // default assign + + /// @} + + /** @brief Divide a Bezier curve into two curves + * @param t Time value + * @return Pair of Bezier curves \f$(\mathbf{D}, \mathbf{E})\f$ such that + * \f$\mathbf{D}[ [0,1] ] = \mathbf{C}[ [0,t] ]\f$ and + * \f$\mathbf{E}[ [0,1] ] = \mathbf{C}[ [t,1] ]\f$ */ + std::pair<BezierCurveN, BezierCurveN> subdivide(Coord t) const { + std::pair<Bezier, Bezier> sx = inner[X].subdivide(t), sy = inner[Y].subdivide(t); + return std::make_pair( + BezierCurveN(sx.first, sy.first), + BezierCurveN(sx.second, sy.second)); + } + + bool isDegenerate() const override { + return BezierCurve::isDegenerate(); + } + + bool isLineSegment() const override { + if constexpr (degree == 1) { + return true; + } else { + return BezierCurve::isLineSegment(); + } + } + + Curve *duplicate() const override { + return new BezierCurveN(*this); + } + Curve *portion(Coord f, Coord t) const override { + if (degree == 1) { + return new BezierCurveN<1>(pointAt(f), pointAt(t)); + } else { + return new BezierCurveN(Geom::portion(inner, f, t)); + } + } + Curve *reverse() const override { + if (degree == 1) { + return new BezierCurveN<1>(finalPoint(), initialPoint()); + } else { + return new BezierCurveN(Geom::reverse(inner)); + } + } + Curve *derivative() const override; + + Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const override { + return BezierCurve::nearestTime(p, from, to); + } + std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const override { + // call super. this is implemented only to allow specializations + return BezierCurve::intersect(other, eps); + } + int winding(Point const &p) const override { + return Curve::winding(p); + } + void feed(PathSink &sink, bool moveto_initial) const override { + // call super. this is implemented only to allow specializations + BezierCurve::feed(sink, moveto_initial); + } + void expandToTransformed(Rect &bbox, Affine const &transform) const override { + // call super. this is implemented only to allow specializations + BezierCurve::expandToTransformed(bbox, transform); + } +}; + +// BezierCurveN<0> is meaningless; specialize it out +template<> class BezierCurveN<0> : public BezierCurveN<1> { private: BezierCurveN();}; + +/** @brief Line segment. + * Line segments are Bezier curves of order 1. They have only two control points, + * the starting point and the ending point. + * @ingroup Curves */ +typedef BezierCurveN<1> LineSegment; + +/** @brief Quadratic (order 2) Bezier curve. + * @ingroup Curves */ +typedef BezierCurveN<2> QuadraticBezier; + +/** @brief Cubic (order 3) Bezier curve. + * @ingroup Curves */ +typedef BezierCurveN<3> CubicBezier; + +template <unsigned degree> +inline +Curve *BezierCurveN<degree>::derivative() const { + return new BezierCurveN<degree-1>(Geom::derivative(inner[X]), Geom::derivative(inner[Y])); +} + +// optimized specializations +template <> inline bool BezierCurveN<1>::isDegenerate() const { + return inner[X][0] == inner[X][1] && inner[Y][0] == inner[Y][1]; +} +template <> inline bool BezierCurveN<1>::isLineSegment() const { return true; } +template <> Curve *BezierCurveN<1>::derivative() const; +template <> Coord BezierCurveN<1>::nearestTime(Point const &, Coord, Coord) const; +template <> std::vector<CurveIntersection> BezierCurveN<1>::intersect(Curve const &, Coord) const; +template <> std::vector<CurveIntersection> BezierCurveN<2>::intersect(Curve const &, Coord) const; +template <> std::vector<CurveIntersection> BezierCurveN<3>::intersect(Curve const &, Coord) const; +template <> int BezierCurveN<1>::winding(Point const &) const; +template <> void BezierCurveN<1>::feed(PathSink &sink, bool moveto_initial) const; +template <> void BezierCurveN<2>::feed(PathSink &sink, bool moveto_initial) const; +template <> void BezierCurveN<3>::feed(PathSink &sink, bool moveto_initial) const; +template <> void BezierCurveN<1>::expandToTransformed(Rect &bbox, Affine const &transform) const; +template <> void BezierCurveN<2>::expandToTransformed(Rect &bbox, Affine const &transform) const; +template <> void BezierCurveN<3>::expandToTransformed(Rect &bbox, Affine const &transform) const; + +inline Point middle_point(LineSegment const& _segment) { + return ( _segment.initialPoint() + _segment.finalPoint() ) / 2; +} + +inline Coord length(LineSegment const& seg) { + return distance(seg.initialPoint(), seg.finalPoint()); +} + +Coord bezier_length(std::vector<Point> const &points, Coord tolerance = 0.01); +Coord bezier_length(Point p0, Point p1, Point p2, Coord tolerance = 0.01); +Coord bezier_length(Point p0, Point p1, Point p2, Point p3, Coord tolerance = 0.01); + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_BEZIER_CURVE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/bezier-to-sbasis.h b/include/2geom/bezier-to-sbasis.h new file mode 100644 index 0000000..73c55d9 --- /dev/null +++ b/include/2geom/bezier-to-sbasis.h @@ -0,0 +1,94 @@ +/** + * \file + * \brief Conversion between Bezier control points and SBasis curves + *//* + * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_BEZIER_TO_SBASIS_H +#define LIB2GEOM_SEEN_BEZIER_TO_SBASIS_H + +#include <2geom/coord.h> +#include <2geom/point.h> +#include <2geom/d2.h> +#include <2geom/sbasis-to-bezier.h> + +namespace Geom +{ + +#if 0 +inline SBasis bezier_to_sbasis(Coord const *handles, unsigned order) { + if(order == 0) + return Linear(handles[0]); + else if(order == 1) + return Linear(handles[0], handles[1]); + else + return multiply(Linear(1, 0), bezier_to_sbasis(handles, order-1)) + + multiply(Linear(0, 1), bezier_to_sbasis(handles+1, order-1)); +} + + +template <typename T> +inline D2<SBasis> handles_to_sbasis(T const &handles, unsigned order) +{ + double v[2][order+1]; + for(unsigned i = 0; i <= order; i++) + for(unsigned j = 0; j < 2; j++) + v[j][i] = handles[i][j]; + return D2<SBasis>(bezier_to_sbasis(v[0], order), + bezier_to_sbasis(v[1], order)); +} +#endif + + +template <typename T> +inline +D2<SBasis> handles_to_sbasis(T const& handles, unsigned order) +{ + D2<SBasis> sbc; + size_t sz = order + 1; + std::vector<Point> v; + v.reserve(sz); + for (size_t i = 0; i < sz; ++i) + v.push_back(handles[i]); + bezier_to_sbasis(sbc, v); + return sbc; +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_BEZIER_TO_SBASIS_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/bezier-utils.h b/include/2geom/bezier-utils.h new file mode 100644 index 0000000..3e56e6e --- /dev/null +++ b/include/2geom/bezier-utils.h @@ -0,0 +1,99 @@ +/** + * \file + * \brief Bezier fitting algorithms + *//* + * An Algorithm for Automatically Fitting Digitized Curves + * by Philip J. Schneider + * from "Graphics Gems", Academic Press, 1990 + * + * Authors: + * Philip J. Schneider + * Lauris Kaplinski <lauris@ximian.com> + * + * Copyright (C) 1990 Philip J. Schneider + * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_BEZIER_UTILS_H +#define LIB2GEOM_SEEN_BEZIER_UTILS_H + +#include <2geom/point.h> + +namespace Geom { + +Point bezier_pt(unsigned degree, Point const V[], double t); + +int bezier_fit_cubic(Point bezier[], Point const data[], int len, double error); + +int bezier_fit_cubic_r(Point bezier[], Point const data[], int len, double error, + unsigned max_beziers); + +int bezier_fit_cubic_full(Point bezier[], int split_points[], Point const data[], int len, + Point const &tHat1, Point const &tHat2, + double error, unsigned max_beziers); + +Point darray_left_tangent(Point const d[], unsigned const len); +Point darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq); +Point darray_right_tangent(Point const d[], unsigned const length, double const tolerance_sq); + +template <typename iterator> +static void +cubic_bezier_poly_coeff(iterator b, Point *pc) { + double c[10] = {1, + -3, 3, + 3, -6, 3, + -1, 3, -3, 1}; + + int cp = 0; + + for(int i = 0; i < 4; i++) { + pc[i] = Point(0,0); + ++b; + } + for(int i = 0; i < 4; i++) { + --b; + for(int j = 0; j <= i; j++) { + pc[3 - j] += c[cp]*(*b); + cp++; + } + } +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_BEZIER_UTILS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/bezier.h b/include/2geom/bezier.h new file mode 100644 index 0000000..d65b3bd --- /dev/null +++ b/include/2geom/bezier.h @@ -0,0 +1,394 @@ +/** + * @file + * @brief Bernstein-Bezier polynomial + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Michael Sloan <mgsloan@gmail.com> + * Nathan Hurst <njh@njhurst.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_BEZIER_H +#define LIB2GEOM_SEEN_BEZIER_H + +#include <algorithm> +#include <valarray> +#include <2geom/coord.h> +#include <2geom/d2.h> +#include <2geom/math-utils.h> + +namespace Geom { + +/** @brief Compute the value of a Bernstein-Bezier polynomial. + * This method uses a Horner-like fast evaluation scheme. + * @param t Time value + * @param c_ Pointer to coefficients + * @param n Degree of the polynomial (number of coefficients minus one) */ +template <typename T> +inline T bernstein_value_at(double t, T const *c_, unsigned n) { + double u = 1.0 - t; + double bc = 1; + double tn = 1; + T tmp = c_[0]*u; + for(unsigned i=1; i<n; i++){ + tn = tn*t; + bc = bc*(n-i+1)/i; + tmp = (tmp + tn*bc*c_[i])*u; + } + return (tmp + tn*t*c_[n]); +} + +/** @brief Perform Casteljau subdivision of a Bezier polynomial. + * Given an array of coefficients and a time value, computes two new Bernstein-Bezier basis + * polynomials corresponding to the \f$[0, t]\f$ and \f$[t, 1]\f$ intervals of the original one. + * @param t Time value + * @param v Array of input coordinates + * @param left Output polynomial corresponding to \f$[0, t]\f$ + * @param right Output polynomial corresponding to \f$[t, 1]\f$ + * @param order Order of the input polynomial, equal to one less the number of coefficients + * @return Value of the polynomial at @a t */ +template <typename T> +inline T casteljau_subdivision(double t, T const *v, T *left, T *right, unsigned order) { + // The Horner-like scheme gives very slightly different results, but we need + // the result of subdivision to match exactly with Bezier's valueAt function. + T val = bernstein_value_at(t, v, order); + + if (!left && !right) { + return val; + } + + if (!right) { + if (left != v) { + std::copy(v, v + order + 1, left); + } + for (std::size_t i = order; i > 0; --i) { + for (std::size_t j = i; j <= order; ++j) { + left[j] = lerp(t, left[j-1], left[j]); + } + } + left[order] = val; + return left[order]; + } + + if (right != v) { + std::copy(v, v + order + 1, right); + } + for (std::size_t i = 1; i <= order; ++i) { + if (left) { + left[i-1] = right[0]; + } + for (std::size_t j = i; j > 0; --j) { + right[j-1] = lerp(t, right[j-1], right[j]); + } + } + right[0] = val; + if (left) { + left[order] = right[0]; + } + return right[0]; +} + +/** + * @brief Polynomial in Bernstein-Bezier basis + * @ingroup Fragments + */ +class Bezier + : boost::arithmetic< Bezier, double + , boost::additive< Bezier + > > +{ +private: + std::valarray<Coord> c_; + + friend Bezier portion(const Bezier & a, Coord from, Coord to); + friend OptInterval bounds_fast(Bezier const & b); + friend Bezier derivative(const Bezier & a); + friend class Bernstein; + + void + find_bezier_roots(std::vector<double> & solutions, + double l, double r) const; + +protected: + Bezier(Coord const c[], unsigned ord) + : c_(c, ord+1) + {} + +public: + unsigned order() const { return c_.size()-1;} + unsigned degree() const { return order(); } + unsigned size() const { return c_.size();} + + Bezier() {} + Bezier(const Bezier& b) :c_(b.c_) {} + Bezier &operator=(Bezier const &other) { + if ( c_.size() != other.c_.size() ) { + c_.resize(other.c_.size()); + } + c_ = other.c_; + return *this; + } + + bool operator==(Bezier const &other) const + { + if (degree() != other.degree()) { + return false; + } + + for (size_t i = 0; i < c_.size(); i++) { + if (c_[i] != other.c_[i]) { + return false; + } + } + return true; + } + + bool operator!=(Bezier const &other) const + { + return !(*this == other); + } + + struct Order { + unsigned order; + explicit Order(Bezier const &b) : order(b.order()) {} + explicit Order(unsigned o) : order(o) {} + operator unsigned() const { return order; } + }; + + //Construct an arbitrary order bezier + Bezier(Order ord) : c_(0., ord.order+1) { + assert(ord.order == order()); + } + + /// @name Construct Bezier polynomials from their control points + /// @{ + explicit Bezier(Coord c0) : c_(0., 1) { + c_[0] = c0; + } + Bezier(Coord c0, Coord c1) : c_(0., 2) { + c_[0] = c0; c_[1] = c1; + } + Bezier(Coord c0, Coord c1, Coord c2) : c_(0., 3) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3) : c_(0., 4) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4) : c_(0., 5) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5) : c_(0., 6) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5, Coord c6) : c_(0., 7) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; c_[6] = c6; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5, Coord c6, Coord c7) : c_(0., 8) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; c_[6] = c6; c_[7] = c7; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5, Coord c6, Coord c7, Coord c8) : c_(0., 9) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; c_[6] = c6; c_[7] = c7; c_[8] = c8; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5, Coord c6, Coord c7, Coord c8, Coord c9) : c_(0., 10) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; c_[6] = c6; c_[7] = c7; c_[8] = c8; c_[9] = c9; + } + + template <typename Iter> + Bezier(Iter first, Iter last) { + c_.resize(std::distance(first, last)); + for (std::size_t i = 0; first != last; ++first, ++i) { + c_[i] = *first; + } + } + Bezier(std::vector<Coord> const &vec) + : c_(&vec[0], vec.size()) + {} + /// @} + + void resize (unsigned int n, Coord v = 0) { + c_.resize (n, v); + } + void clear() { + c_.resize(0); + } + + //IMPL: FragmentConcept + typedef Coord output_type; + bool isZero(double eps=EPSILON) const { + for(unsigned i = 0; i <= order(); i++) { + if( ! are_near(c_[i], 0., eps) ) return false; + } + return true; + } + bool isConstant(double eps=EPSILON) const { + for(unsigned i = 1; i <= order(); i++) { + if( ! are_near(c_[i], c_[0], eps) ) return false; + } + return true; + } + bool isFinite() const { + for(unsigned i = 0; i <= order(); i++) { + if(!std::isfinite(c_[i])) return false; + } + return true; + } + Coord at0() const { return c_[0]; } + Coord &at0() { return c_[0]; } + Coord at1() const { return c_[order()]; } + Coord &at1() { return c_[order()]; } + + Coord valueAt(double t) const { + return bernstein_value_at(t, &c_[0], order()); + } + Coord operator()(double t) const { return valueAt(t); } + + SBasis toSBasis() const; + + Coord &operator[](unsigned ix) { return c_[ix]; } + Coord const &operator[](unsigned ix) const { return const_cast<std::valarray<Coord>&>(c_)[ix]; } + + void setCoeff(unsigned ix, double val) { c_[ix] = val; } + + // The size of the returned vector equals n_derivs+1. + std::vector<Coord> valueAndDerivatives(Coord t, unsigned n_derivs) const; + + void subdivide(Coord t, Bezier *left, Bezier *right) const; + std::pair<Bezier, Bezier> subdivide(Coord t) const; + + std::vector<Coord> roots() const; + std::vector<Coord> roots(Interval const &ivl) const; + + Bezier forward_difference(unsigned k) const; + Bezier elevate_degree() const; + Bezier reduce_degree() const; + Bezier elevate_to_degree(unsigned newDegree) const; + Bezier deflate() const; + + // basic arithmetic operators + Bezier &operator+=(double v) { + c_ += v; + return *this; + } + Bezier &operator-=(double v) { + c_ -= v; + return *this; + } + Bezier &operator*=(double v) { + c_ *= v; + return *this; + } + Bezier &operator/=(double v) { + c_ /= v; + return *this; + } + Bezier &operator+=(Bezier const &other); + Bezier &operator-=(Bezier const &other); + + /// Unary minus + Bezier operator-() const + { + Bezier result; + result.c_ = -c_; + return result; + } +}; + + +void bezier_to_sbasis (SBasis &sb, Bezier const &bz); + +Bezier operator*(Bezier const &f, Bezier const &g); +inline Bezier multiply(Bezier const &f, Bezier const &g) { + Bezier result = f * g; + return result; +} + +inline Bezier reverse(const Bezier & a) { + Bezier result = Bezier(Bezier::Order(a)); + for(unsigned i = 0; i <= a.order(); i++) + result[i] = a[a.order() - i]; + return result; +} + +Bezier portion(const Bezier & a, double from, double to); + +// XXX Todo: how to handle differing orders +inline std::vector<Point> bezier_points(const D2<Bezier > & a) { + std::vector<Point> result; + for(unsigned i = 0; i <= a[0].order(); i++) { + Point p; + for(unsigned d = 0; d < 2; d++) p[d] = a[d][i]; + result.push_back(p); + } + return result; +} + +Bezier derivative(Bezier const &a); +Bezier integral(Bezier const &a); +OptInterval bounds_fast(Bezier const &b); +OptInterval bounds_exact(Bezier const &b); +OptInterval bounds_local(Bezier const &b, OptInterval const &i); + +/// Expand an interval to the image of a Bézier-Bernstein polynomial, assuming it already contains the initial point x0. +void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2); +void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2, Coord x3); + +inline std::ostream &operator<< (std::ostream &os, const Bezier & b) { + os << "Bezier("; + for(unsigned i = 0; i < b.order(); i++) { + os << format_coord_nice(b[i]) << ", "; + } + os << format_coord_nice(b[b.order()]) << ")"; + return os; +} + +} // namespace Geom + +#endif // LIB2GEOM_SEEN_BEZIER_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/cairo-path-sink.h b/include/2geom/cairo-path-sink.h new file mode 100644 index 0000000..3f7a044 --- /dev/null +++ b/include/2geom/cairo-path-sink.h @@ -0,0 +1,91 @@ +/** + * @file + * @brief Path sink for Cairo contexts + *//* + * Copyright 2014 Krzysztof Kosiński + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_CAIRO_PATH_SINK_H +#define LIB2GEOM_SEEN_CAIRO_PATH_SINK_H + +#ifdef HAVE_CAIRO + +#include <2geom/path-sink.h> +#include <cairo.h> + +namespace Geom { + + +/** @brief Output paths to a Cairo drawing context + * + * This class converts from 2Geom path representation to the Cairo representation. + * Use it to simplify visualizing the results of 2Geom operations with the Cairo library, + * for example: + * @code + * CairoPathSink sink(cr); + * sink.feed(pv); + * cairo_stroke(cr); + * @endcode + * + * Currently the flush method is a no-op, but this is not guaranteed + * to hold forever. + */ +class CairoPathSink + : public PathSink +{ +public: + CairoPathSink(cairo_t *cr); + + void moveTo(Point const &p) override; + void lineTo(Point const &p) override; + void curveTo(Point const &c0, Point const &c1, Point const &p) override; + void quadTo(Point const &c, Point const &p) override; + void arcTo(Coord rx, Coord ry, Coord angle, + bool large_arc, bool sweep, Point const &p) override; + void closePath() override; + void flush() override; + +private: + cairo_t *_cr; + Point _current_point; +}; + +} + +#endif + +#endif // !LIB2GEOM_SEEN_CAIRO_PATH_SINK_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/choose.h b/include/2geom/choose.h new file mode 100644 index 0000000..106d04f --- /dev/null +++ b/include/2geom/choose.h @@ -0,0 +1,147 @@ +/** + * \file + * \brief Calculation of binomial cefficients + *//* + * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_CHOOSE_H +#define LIB2GEOM_SEEN_CHOOSE_H + +#include <vector> + +namespace Geom { + +/** + * @brief Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n + 1, k). + */ +template <typename T> +constexpr void binomial_increment_n(T &b, int n, int k) +{ + b = b * (n + 1) / (n + 1 - k); +} + +/** + * @brief Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n - 1, k). + */ +template <typename T> +constexpr void binomial_decrement_n(T &b, int n, int k) +{ + b = b * (n - k) / n; +} + +/** + * @brief Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n, k + 1). + */ +template <typename T> +constexpr void binomial_increment_k(T &b, int n, int k) +{ + b = b * (n - k) / (k + 1); +} + +/** + * @brief Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n, k - 1). + */ +template <typename T> +constexpr void binomial_decrement_k(T &b, int n, int k) +{ + b = b * k / (n + 1 - k); +} + +/** + * @brief Calculate the (n, k)th binomial coefficient. + */ +template <typename T> +constexpr T choose(unsigned n, unsigned k) +{ + if (k > n) { + return 0; + } + T b = 1; + int max = std::min(k, n - k); + for (int i = 0; i < max; i++) { + binomial_increment_k(b, n, i); + } + return b; +} + +/** + * @brief Class for calculating and accessing a row of Pascal's triangle. + */ +template <typename ValueType> +class BinomialCoefficient +{ +public: + using value_type = ValueType; + using container_type = std::vector<value_type>; + + BinomialCoefficient(unsigned int _n) + : n(_n) + { + coefficients.reserve(n / 2 + 1); + coefficients.emplace_back(1); + value_type b = 1; + for (int i = 0; i < n / 2; i++) { + binomial_increment_k(b, n, i); + coefficients.emplace_back(b); + } + } + + unsigned int size() const + { + return degree() + 1; + } + + unsigned int degree() const + { + return n; + } + + value_type operator[](unsigned int k) const + { + return coefficients[std::min(k, n - k)]; + } + +private: + int const n; + container_type coefficients; +}; + +} // namespace Geom + +#endif // LIB2GEOM_SEEN_CHOOSE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/circle.h b/include/2geom/circle.h new file mode 100644 index 0000000..a4d5f20 --- /dev/null +++ b/include/2geom/circle.h @@ -0,0 +1,165 @@ +/** @file + * @brief Circle shape + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2008-2014 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_CIRCLE_H +#define LIB2GEOM_SEEN_CIRCLE_H + +#include <2geom/forward.h> +#include <2geom/intersection.h> +#include <2geom/point.h> +#include <2geom/rect.h> +#include <2geom/transforms.h> + +namespace Geom { + +class EllipticalArc; + +/** @brief Set of all points at a fixed distance from the center + * @ingroup Shapes */ +class Circle + : boost::equality_comparable1< Circle + , MultipliableNoncommutative< Circle, Translate + , MultipliableNoncommutative< Circle, Rotate + , MultipliableNoncommutative< Circle, Zoom + > > > > +{ + Point _center; + Coord _radius; + +public: + Circle() {} + Circle(Coord cx, Coord cy, Coord r) + : _center(cx, cy), _radius(r) + {} + Circle(Point const ¢er, Coord r) + : _center(center), _radius(r) + {} + + Circle(Coord A, Coord B, Coord C, Coord D) { + setCoefficients(A, B, C, D); + } + + // Construct the unique circle passing through three points. + //Circle(Point const &a, Point const &b, Point const &c); + + Point center() const { return _center; } + Coord center(Dim2 d) const { return _center[d]; } + Coord radius() const { return _radius; } + Coord area() const { return M_PI * _radius * _radius; } + bool isDegenerate() const { return _radius == 0; } + + void setCenter(Point const &p) { _center = p; } + void setRadius(Coord c) { _radius = c; } + + Rect boundsFast() const; + Rect boundsExact() const { return boundsFast(); } + + Point initialPoint() const; + Point finalPoint() const { return initialPoint(); } + Point pointAt(Coord t) const; + Coord valueAt(Coord t, Dim2 d) const; + Coord timeAt(Point const &p) const; + Coord nearestTime(Point const &p) const; + + bool contains(Point const &p) const { return distance(p, _center) <= _radius; } + bool contains(Rect const &other) const; + bool contains(Circle const &other) const; + + bool intersects(Line const &l) const; + bool intersects(LineSegment const &l) const; + bool intersects(Circle const &other) const; + + std::vector<ShapeIntersection> intersect(Line const &other) const; + std::vector<ShapeIntersection> intersect(LineSegment const &other) const; + std::vector<ShapeIntersection> intersect(Circle const &other) const; + + // build a circle by its implicit equation: + // Ax^2 + Ay^2 + Bx + Cy + D = 0 + void setCoefficients(Coord A, Coord B, Coord C, Coord D); + void coefficients(Coord &A, Coord &B, Coord &C, Coord &D) const; + std::vector<Coord> coefficients() const; + + Zoom unitCircleTransform() const; + Zoom inverseUnitCircleTransform() const; + + EllipticalArc * + arc(Point const& initial, Point const& inner, Point const& final) const; + + D2<SBasis> toSBasis() const; + + Circle &operator*=(Translate const &t) { + _center *= t; + return *this; + } + Circle &operator*=(Rotate const &) { + return *this; + } + Circle &operator*=(Zoom const &z) { + _center *= z; + _radius *= z.scale(); + return *this; + } + + bool operator==(Circle const &other) const; + + /** @brief Fit the circle to the passed points using the least squares method. + * @param points Samples at the perimeter of the circle */ + void fit(std::vector<Point> const &points); +}; + +bool are_near(Circle const &a, Circle const &b, Coord eps=EPSILON); + +std::ostream &operator<<(std::ostream &out, Circle const &c); + +template <> +struct ShapeTraits<Circle> { + typedef Coord TimeType; + typedef Interval IntervalType; + typedef Ellipse AffineClosureType; + typedef Intersection<> IntersectionType; +}; + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_CIRCLE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/concepts.h b/include/2geom/concepts.h new file mode 100644 index 0000000..de76d0f --- /dev/null +++ b/include/2geom/concepts.h @@ -0,0 +1,209 @@ +/** + * \file + * \brief Template concepts used by 2Geom + *//* + * Copyright 2007 Michael Sloan <mgsloan@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, output to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_CONCEPTS_H +#define LIB2GEOM_SEEN_CONCEPTS_H + +#include <2geom/sbasis.h> +#include <2geom/interval.h> +#include <2geom/point.h> +#include <2geom/rect.h> +#include <2geom/intersection.h> +#include <vector> +#include <boost/concept/assert.hpp> +#include <2geom/forward.h> +#include <2geom/transforms.h> + +namespace Geom { + +//forward decls +template <typename T> struct ResultTraits; + +template <> struct ResultTraits<double> { + typedef OptInterval bounds_type; + typedef SBasis sb_type; +}; + +template <> struct ResultTraits<Point> { + typedef OptRect bounds_type; + typedef D2<SBasis> sb_type; +}; + +//A concept for one-dimensional functions defined on [0,1] +template <typename T> +struct FragmentConcept { + typedef typename T::output_type OutputType; + typedef typename ResultTraits<OutputType>::bounds_type BoundsType; + typedef typename ResultTraits<OutputType>::sb_type SbType; + T t; + double d; + OutputType o; + bool b; + BoundsType i; + Interval dom; + std::vector<OutputType> v; + unsigned u; + SbType sb; + void constraints() { + t = T(o); + b = t.isZero(d); + b = t.isConstant(d); + b = t.isFinite(); + o = t.at0(); + o = t.at1(); + t.at0() = o; + t.at1() = o; + o = t.valueAt(d); + o = t(d); + v = t.valueAndDerivatives(d, u-1); + //Is a pure derivative (ignoring others) accessor ever much faster? + //u = number of values returned. first val is value. + sb = t.toSBasis(); + t = reverse(t); + i = bounds_fast(t); + i = bounds_exact(t); + i = bounds_local(t, dom); + /*With portion, Interval makes some sense, but instead I'm opting for + doubles, for the following reasons: + A) This way a reversed portion may be specified + B) Performance might be a bit better for piecewise and such + C) Interval version provided below + */ + t = portion(t, d, d); + } +}; + +template <typename T> +struct ShapeConcept { + typedef typename ShapeTraits<T>::TimeType Time; + typedef typename ShapeTraits<T>::IntervalType Interval; + typedef typename ShapeTraits<T>::AffineClosureType AffineClosure; + typedef typename ShapeTraits<T>::IntersectionType Isect; + + T shape, other; + Time t; + Point p; + AffineClosure ac; + Affine m; + Translate tr; + Coord c; + bool bool_; + std::vector<Isect> ivec; + + void constraints() { + p = shape.pointAt(t); + c = shape.valueAt(t, X); + ivec = shape.intersect(other); + t = shape.nearestTime(p); + shape *= tr; + ac = shape; + ac *= m; + bool_ = (shape == shape); + bool_ = (shape != other); + bool_ = shape.isDegenerate(); + //bool_ = are_near(shape, other, c); + } +}; + +template <typename T> +inline T portion(const T& t, const Interval& i) { return portion(t, i.min(), i.max()); } + +template <typename T> +struct EqualityComparableConcept { + T a, b; + bool bool_; + void constraints() { + bool_ = (a == b); + bool_ = (a != b); + } +}; + +template <typename T> +struct NearConcept { + T a, b; + double tol; + bool res; + void constraints() { + res = are_near(a, b, tol); + } +}; + +template <typename T> +struct OffsetableConcept { + T t; + typename T::output_type d; + void constraints() { + t = t + d; t += d; + t = t - d; t -= d; + } +}; + +template <typename T> +struct ScalableConcept { + T t; + typename T::output_type d; + void constraints() { + t = -t; + t = t * d; t *= d; + t = t / d; t /= d; + } +}; + +template <typename T> +struct AddableConcept { + T i, j; + void constraints() { + i += j; i = i + j; + i -= j; i = i - j; + } +}; + +template <typename T> +struct MultiplicableConcept { + T i, j; + void constraints() { + i *= j; i = i * j; + } +}; + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_CONCEPTS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/conic_section_clipper.h b/include/2geom/conic_section_clipper.h new file mode 100644 index 0000000..38bba33 --- /dev/null +++ b/include/2geom/conic_section_clipper.h @@ -0,0 +1,58 @@ +/** @file + * @brief Conic section clipping with respect to a rectangle + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail> + * + * Copyright 2009 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + + + +#ifndef LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_H +#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_H + + +#undef CLIP_WITH_CAIRO_SUPPORT +#include <2geom/conic_section_clipper_impl.h> + + +#endif // _2GEOM_CONIC_SECTION_CLIPPER_H_ + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/conic_section_clipper_cr.h b/include/2geom/conic_section_clipper_cr.h new file mode 100644 index 0000000..6c62494 --- /dev/null +++ b/include/2geom/conic_section_clipper_cr.h @@ -0,0 +1,64 @@ +/** @file + * @brief Conic section clipping with respect to a rectangle + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail> + * + * Copyright 2009 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + + + +//////////////////////////////////////////////////////////////////////////////// +// This header should be used for graphical debugging purpuse only. // +//////////////////////////////////////////////////////////////////////////////// + + +#ifndef LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_CR_H +#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_CR_H + + +#define CLIP_WITH_CAIRO_SUPPORT +#include "conic_section_clipper_impl.h" +#include "conic_section_clipper_impl.cpp" + + +#endif // _2GEOM_CONIC_SECTION_CLIPPER_CR_H_ + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/conic_section_clipper_impl.h b/include/2geom/conic_section_clipper_impl.h new file mode 100644 index 0000000..ee67df1 --- /dev/null +++ b/include/2geom/conic_section_clipper_impl.h @@ -0,0 +1,346 @@ +/** @file + * @brief Conic section clipping with respect to a rectangle + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail> + * + * Copyright 2009 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_H +#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_H + + +#include <2geom/conicsec.h> +#include <2geom/line.h> + +#include <list> +#include <map> + + + +#ifdef CLIP_WITH_CAIRO_SUPPORT + #include <2geom/toys/path-cairo.h> + #define CLIPPER_CLASS clipper_cr +#else + #define CLIPPER_CLASS clipper +#endif + +//#define CLIPDBG + +#ifdef CLIPDBG +#include <2geom/toys/path-cairo.h> +#define DBGINFO(msg) \ + std::cerr << msg << std::endl; +#define DBGPRINT(msg, var) \ + std::cerr << msg << var << std::endl; +#define DBGPRINTIF(cond, msg, var) \ + if (cond) \ + std::cerr << msg << var << std::endl; + +#define DBGPRINT2(msg1, var1, msg2, var2) \ + std::cerr << msg1 << var1 << msg2 << var2 << std::endl; + +#define DBGPRINTCOLL(msg, coll) \ + if (coll.size() != 0) \ + std::cerr << msg << ":\n"; \ + for (size_t i = 0; i < coll.size(); ++i) \ + { \ + std::cerr << i << ": " << coll[i] << "\n"; \ + } + +#else +#define DBGINFO(msg) +#define DBGPRINT(msg, var) +#define DBGPRINTIF(cond, msg, var) +#define DBGPRINT2(msg1, var1, msg2, var2) +#define DBGPRINTCOLL(msg, coll) +#endif + + + + +namespace Geom +{ + +class CLIPPER_CLASS +{ + + public: + +#ifdef CLIP_WITH_CAIRO_SUPPORT + clipper_cr (cairo_t* _cr, const xAx & _cs, const Rect & _R) + : cr(_cr), cs(_cs), R(_R) + { + DBGPRINT ("CLIP: right side: ", R.right()) + DBGPRINT ("CLIP: top side: ", R.top()) + DBGPRINT ("CLIP: left side: ", R.left()) + DBGPRINT ("CLIP: bottom side: ", R.bottom()) + } +#else + clipper (const xAx & _cs, const Rect & _R) + : cs(_cs), R(_R) + { + } +#endif + + bool clip (std::vector<RatQuad> & arcs); + + bool found_any_isolated_point() const + { + return ( !single_points.empty() ); + } + + const std::vector<Point> & isolated_points() const + { + return single_points; + } + + + private: + bool intersect (std::vector<Point> & crossing_points) const; + + bool are_paired (Point & M, const Point & P1, const Point & P2) const; + void pairing (std::vector<Point> & paired_points, + std::vector<Point> & inner_points, + const std::vector<Point> & crossing_points); + + Point find_inner_point_by_bisector_line (const Point & P, + const Point & Q) const; + Point find_inner_point (const Point & P, const Point & Q) const; + + std::list<Point>::iterator split (std::list<Point> & points, + std::list<Point>::iterator sp, + std::list<Point>::iterator fp) const; + void rsplit (std::list<Point> & points, + std::list<Point>::iterator sp, + std::list<Point>::iterator fp, + size_t k) const; + + void rsplit (std::list<Point> & points, + std::list<Point>::iterator sp, + std::list<Point>::iterator fp, + double length) const; + + private: +#ifdef CLIP_WITH_CAIRO_SUPPORT + cairo_t* cr; +#endif + const xAx & cs; + const Rect & R; + std::vector<Point> single_points; +}; + + + + +/* + * Given two point "P", "Q" on the conic section the method computes + * a third point inner to the arc with end-point "P", "Q". + * The new point is found by intersecting the conic with the bisector line + * of the PQ line segment. + */ +inline +Point CLIPPER_CLASS::find_inner_point_by_bisector_line (const Point & P, + const Point & Q) const +{ + DBGPRINT ("CLIP: find_inner_point_by_bisector_line: P = ", P) + DBGPRINT ("CLIP: find_inner_point_by_bisector_line: Q = ", Q) + Line bl = make_bisector_line (LineSegment (P, Q)); + std::vector<double> rts = cs.roots (bl); + //DBGPRINT ("CLIP: find_inner_point: rts.size = ", rts.size()) + double t; + if (rts.size() == 0) + { + THROW_LOGICALERROR ("clipper::find_inner_point_by_bisector_line: " + "no conic-bisector line intersection point"); + } + if (rts.size() == 2) + { + // we suppose that the searched point is the nearest + // to the line segment PQ + t = (std::fabs(rts[0]) < std::fabs(rts[1])) ? rts[0] : rts[1]; + } + else + { + t = rts[0]; + } + return bl.pointAt (t); +} + + +/* + * Given two point "P", "Q" on the conic section the method computes + * a third point inner to the arc with end-point "P", "Q". + * The new point is found by intersecting the conic with the line + * passing through the middle point of the PQ line segment and + * the intersection point of the tangent lines at points P and Q. + */ +inline +Point CLIPPER_CLASS::find_inner_point (const Point & P, const Point & Q) const +{ + + Line l1 = cs.tangent (P); + Line l2 = cs.tangent (Q); + Line l; + // in case we fail to find a crossing point we fall back to the bisector + // method + try + { + OptCrossing oc = intersection(l1, l2); + if (!oc) + { + return find_inner_point_by_bisector_line (P, Q); + } + l.setPoints (l1.pointAt (oc->ta), middle_point (P, Q)); + } + catch (Geom::InfiniteSolutions const &e) + { + return find_inner_point_by_bisector_line (P, Q); + } + + std::vector<double> rts = cs.roots (l); + double t; + if (rts.size() == 0) + { + return find_inner_point_by_bisector_line (P, Q); + } + // the line "l" origin is set to the tangent crossing point so in case + // we find two intersection points only the nearest belongs to the given arc + // pay attention: in case we are dealing with an hyperbola (remember that + // end points are on the same branch, because they are paired) the tangent + // crossing point belongs to the angle delimited by hyperbola asymptotes + // and containing the given hyperbola branch, so the previous statement is + // still true + if (rts.size() == 2) + { + t = (std::fabs(rts[0]) < std::fabs(rts[1])) ? rts[0] : rts[1]; + } + else + { + t = rts[0]; + } + return l.pointAt (t); +} + + +/* + * Given a list of points on the conic section, and given two consecutive + * points belonging to the list and passed by two list iterators, the method + * finds a new point that is inner to the conic arc which has the two passed + * points as initial and final point. This new point is inserted into the list + * between the two passed points and an iterator pointing to the new point + * is returned. + */ +inline +std::list<Point>::iterator CLIPPER_CLASS::split (std::list<Point> & points, + std::list<Point>::iterator sp, + std::list<Point>::iterator fp) const +{ + Point new_point = find_inner_point (*sp, *fp); + std::list<Point>::iterator ip = points.insert (fp, new_point); + //std::cerr << "CLIP: split: [" << *sp << ", " << *ip << ", " + // << *fp << "]" << std::endl; + return ip; +} + + +/* + * Given a list of points on the conic section, and given two consecutive + * points belonging to the list and passed by two list iterators, the method + * recursively finds new points that are inner to the conic arc which has + * the two passed points as initial and final point. The recursion stop after + * "k" recursive calls. These new points are inserted into the list between + * the two passed points, and in the order we cross them going from + * the initial to the final arc point. + */ +inline +void CLIPPER_CLASS::rsplit (std::list<Point> & points, + std::list<Point>::iterator sp, + std::list<Point>::iterator fp, + size_t k) const +{ + if (k == 0) + { + //DBGINFO("CLIP: split: no further split") + return; + } + + std::list<Point>::iterator ip = split (points, sp, fp); + --k; + rsplit (points, sp, ip, k); + rsplit (points, ip, fp, k); +} + + +/* + * Given a list of points on the conic section, and given two consecutive + * points belonging to the list and passed by two list iterators, the method + * recursively finds new points that are inner to the conic arc which has + * the two passed points as initial and final point. The recursion stop when + * the max distance between the new computed inner point and the two passed + * arc end-points is less then the value specified by the "length" parameter. + * These new points are inserted into the list between the two passed points, + * and in the order we cross them going from the initial to the final arc point. + */ +inline +void CLIPPER_CLASS::rsplit (std::list<Point> & points, + std::list<Point>::iterator sp, + std::list<Point>::iterator fp, + double length) const +{ + std::list<Point>::iterator ip = split (points, sp, fp); + double d1 = distance (*sp, *ip); + double d2 = distance (*ip, *fp); + double mdist = std::max (d1, d2); + + if (mdist < length) + { + //DBGINFO("CLIP: split: no further split") + return; + } + + // they have to be called both to keep the number of points in the list + // in the form 2k+1 where k are the sub-arcs the initial arc is split in. + rsplit (points, sp, ip, length); + rsplit (points, ip, fp, length); +} + + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/conicsec.h b/include/2geom/conicsec.h new file mode 100644 index 0000000..bfd5f36 --- /dev/null +++ b/include/2geom/conicsec.h @@ -0,0 +1,537 @@ +/** @file + * @brief Conic Section + *//* + * Authors: + * Nathan Hurst <njh@njhurst.com> + * + * Copyright 2009 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef LIB2GEOM_SEEN_CONICSEC_H +#define LIB2GEOM_SEEN_CONICSEC_H + +#include <2geom/exception.h> +#include <2geom/angle.h> +#include <2geom/rect.h> +#include <2geom/affine.h> +#include <2geom/point.h> +#include <2geom/line.h> +#include <2geom/bezier-curve.h> +#include <2geom/numeric/linear_system.h> +#include <2geom/numeric/symmetric-matrix-fs.h> +#include <2geom/numeric/symmetric-matrix-fs-operation.h> + +#include <optional> + +#include <string> +#include <vector> +#include <ostream> + + + + +namespace Geom +{ + +class RatQuad{ + /** + * A curve of the form B02*A + B12*B*w + B22*C/(B02 + B12*w + B22) + * These curves can exactly represent a piece conic section less than a certain angle (find out) + * + **/ + +public: + Point P[3]; + double w; + RatQuad() {} + RatQuad(Point a, Point b, Point c, double w) : w(w) { + P[0] = a; + P[1] = b; + P[2] = c; + } + double lambda() const; + + static RatQuad fromPointsTangents(Point P0, Point dP0, + Point P, + Point P2, Point dP2); + static RatQuad circularArc(Point P0, Point P1, Point P2); + + CubicBezier toCubic() const; + CubicBezier toCubic(double lam) const; + + Point pointAt(double t) const; + Point at0() const {return P[0];} + Point at1() const {return P[2];} + + void split(RatQuad &a, RatQuad &b) const; + + D2<SBasis> hermite() const; + std::vector<SBasis> homogeneous() const; +}; + + + + +class xAx{ +public: + double c[6]; + + enum kind_t + { + PARABOLA, + CIRCLE, + REAL_ELLIPSE, + IMAGINARY_ELLIPSE, + RECTANGULAR_HYPERBOLA, + HYPERBOLA, + DOUBLE_LINE, + TWO_REAL_PARALLEL_LINES, + TWO_IMAGINARY_PARALLEL_LINES, + TWO_REAL_CROSSING_LINES, + TWO_IMAGINARY_CROSSING_LINES, // crossing at a real point + SINGLE_POINT = TWO_IMAGINARY_CROSSING_LINES, + UNKNOWN + }; + + + xAx() {} + + /* + * Define the conic section by its algebraic equation coefficients + * + * c0, .., c5: equation coefficients + */ + xAx (double c0, double c1, double c2, double c3, double c4, double c5) + { + set (c0, c1, c2, c3, c4, c5); + } + + /* + * Define a conic section by its related symmetric matrix + */ + xAx (const NL::ConstSymmetricMatrixView<3> & C) + { + set(C); + } + + /* + * Define a conic section by computing the one that fits better with + * N points. + * + * points: points to fit + * + * precondition: there must be at least 5 non-overlapping points + */ + xAx (std::vector<Point> const& points) + { + set (points); + } + + /* + * Define a section conic by providing the coordinates of one of its + * vertex,the major axis inclination angle and the coordinates of its foci + * with respect to the unidimensional system defined by the major axis with + * origin set at the provided vertex. + * + * _vertex : section conic vertex V + * _angle : section conic major axis angle + * _dist1: +/-distance btw V and nearest focus + * _dist2: +/-distance btw V and farest focus + * + * prerequisite: _dist1 <= _dist2 + */ + xAx (const Point& _vertex, double _angle, double _dist1, double _dist2) + { + set (_vertex, _angle, _dist1, _dist2); + } + + /* + * Define a conic section by providing one of its vertex and its foci. + * + * _vertex: section conic vertex + * _focus1: section conic focus + * _focus2: section conic focus + */ + xAx (const Point& _vertex, const Point& _focus1, const Point& _focus2) + { + set(_vertex, _focus1, _focus2); + } + + /* + * Define a conic section by passing a focus, the related directrix, + * and the eccentricity (e) + * (e < 1 -> ellipse; e = 1 -> parabola; e > 1 -> hyperbola) + * + * _focus: a focus of the conic section + * _directrix: the directrix related to the given focus + * _eccentricity: the eccentricity parameter of the conic section + */ + xAx (const Point & _focus, const Line & _directrix, double _eccentricity) + { + set (_focus, _directrix, _eccentricity); + } + + /* + * Made up a degenerate conic section as a pair of lines + * + * l1, l2: lines that made up the conic section + */ + xAx (const Line& l1, const Line& l2) + { + set (l1, l2); + } + + /* + * Define the conic section by its algebraic equation coefficients + * c0, ..., c5: equation coefficients + */ + void set (double c0, double c1, double c2, double c3, double c4, double c5) + { + c[0] = c0; c[1] = c1; c[2] = c2; // xx, xy, yy + c[3] = c3; c[4] = c4; // x, y + c[5] = c5; // 1 + } + + /* + * Define a conic section by its related symmetric matrix + */ + void set (const NL::ConstSymmetricMatrixView<3> & C) + { + set(C(0,0), 2*C(1,0), C(1,1), 2*C(2,0), 2*C(2,1), C(2,2)); + } + + void set (std::vector<Point> const& points); + + void set (const Point& _vertex, double _angle, double _dist1, double _dist2); + + void set (const Point& _vertex, const Point& _focus1, const Point& _focus2); + + void set (const Point & _focus, const Line & _directrix, double _eccentricity); + + void set (const Line& l1, const Line& l2); + + + static xAx fromPoint(Point p); + static xAx fromDistPoint(Point p, double d); + static xAx fromLine(Point n, double d); + static xAx fromLine(Line l); + static xAx fromPoints(std::vector<Point> const &pts); + + + template<typename T> + T evaluate_at(T x, T y) const { + return c[0]*x*x + c[1]*x*y + c[2]*y*y + c[3]*x + c[4]*y + c[5]; + } + + double valueAt(Point P) const; + + std::vector<double> implicit_form_coefficients() const { + return std::vector<double>(c, c+6); + } + + template<typename T> + T evaluate_at(T x, T y, T w) const { + return c[0]*x*x + c[1]*x*y + c[2]*y*y + c[3]*x*w + c[4]*y*w + c[5]*w*w; + } + + xAx scale(double sx, double sy) const; + + Point gradient(Point p) const; + + xAx operator-(xAx const &b) const; + xAx operator+(xAx const &b) const; + xAx operator+(double const &b) const; + xAx operator*(double const &b) const; + + std::vector<Point> crossings(Rect r) const; + std::optional<RatQuad> toCurve(Rect const & bnd) const; + std::vector<double> roots(Point d, Point o) const; + + std::vector<double> roots(Line const &l) const; + + static Interval quad_ex(double a, double b, double c, Interval ivl); + + Geom::Affine hessian() const; + + std::optional<Point> bottom() const; + + Interval extrema(Rect r) const; + + + /* + * Return the symmetric matrix related to the conic section. + * Modifying the matrix does not modify the conic section + */ + NL::SymmetricMatrix<3> get_matrix() const + { + NL::SymmetricMatrix<3> C(c); + C(1,0) *= 0.5; C(2,0) *= 0.5; C(2,1) *= 0.5; + return C; + } + + /* + * Return the i-th coefficient of the conic section algebraic equation + * Modifying the returned value does not modify the conic section coefficient + */ + double coeff (size_t i) const + { + return c[i]; + } + + /* + * Return the i-th coefficient of the conic section algebraic equation + * Modifying the returned value modifies the conic section coefficient + */ + double& coeff (size_t i) + { + return c[i]; + } + + kind_t kind () const; + + std::string categorise() const; + + /* + * Return true if the equation: + * c0*x^2 + c1*xy + c2*y^2 + c3*x + c4*y +c5 == 0 + * really defines a conic, false otherwise + */ + bool is_quadratic() const + { + return (coeff(0) != 0 || coeff(1) != 0 || coeff(2) != 0); + } + + /* + * Return true if the conic is degenerate, i.e. if the related matrix + * determinant is null, false otherwise + */ + bool isDegenerate() const + { + return (det_sgn (get_matrix()) == 0); + } + + /* + * Compute the centre of symmetry of the conic section when it exists, + * else it return an uninitialized std::optional<Point> instance. + */ + std::optional<Point> centre() const + { + typedef std::optional<Point> opt_point_t; + + double d = coeff(1) * coeff(1) - 4 * coeff(0) * coeff(2); + if (are_near (d, 0)) return opt_point_t(); + NL::Matrix Q(2, 2); + Q(0,0) = coeff(0); + Q(1,1) = coeff(2); + Q(0,1) = Q(1,0) = coeff(1) * 0.5; + NL::Vector T(2); + T[0] = - coeff(3) * 0.5; + T[1] = - coeff(4) * 0.5; + + NL::LinearSystem ls (Q, T); + NL::Vector sol = ls.SV_solve(); + Point C; + C[0] = sol[0]; + C[1] = sol[1]; + + return opt_point_t(C); + } + + double axis_angle() const; + + void roots (std::vector<double>& sol, Coord v, Dim2 d) const; + + xAx translate (const Point & _offset) const; + + xAx rotate (double angle) const; + + /* + * Rotate the conic section by the given angle wrt the provided point. + * + * _rot_centre: the rotation centre + * _angle: the rotation angle + */ + xAx rotate (const Point & _rot_centre, double _angle) const + { + xAx result + = translate (-_rot_centre).rotate (_angle).translate (_rot_centre); + return result; + } + + /* + * Compute the tangent line of the conic section at the provided point + * + * _point: the conic section point the tangent line pass through + */ + Line tangent (const Point & _point) const + { + NL::Vector pp(3); + pp[0] = _point[0]; pp[1] = _point[1]; pp[2] = 1; + NL::SymmetricMatrix<3> C = get_matrix(); + NL::Vector line = C * pp; + return Line(line[0], line[1], line[2]); + } + + /* + * For a non degenerate conic compute the dual conic. + * TODO: investigate degenerate case + */ + xAx dual () const + { + //assert (! isDegenerate()); + NL::SymmetricMatrix<3> C = get_matrix(); + NL::SymmetricMatrix<3> D = adj(C); + xAx dc(D); + return dc; + } + + bool decompose (Line& l1, Line& l2) const; + + /** + * @brief Division-free decomposition of a degenerate conic section, without + * degeneration test. + * + * When the conic is degenerate, it consists of 0, 1 or 2 lines in the xy-plane. + * This function returns these lines. But it does not check whether the conic + * is really degenerate, so calling it on a non-degenerate conic produces a + * meaningless result. + * + * If the number of lines is less than two, the trailing lines in the returned + * array will be degenerate. Use Line::isDegenerate() to test for that. + * + * This version of the decomposition is division-free, which improves numerical + * stability compared to decompose(). + * + * @param epsilon The numerical threshold for floating point comparison of discriminants. + */ + std::array<Line, 2> decompose_df(Coord epsilon = EPSILON) const; + + /* + * Generate a RatQuad object from a conic arc. + * + * p0: the initial point of the arc + * p1: the inner point of the arc + * p2: the final point of the arc + */ + RatQuad toRatQuad (const Point & p0, + const Point & p1, + const Point & p2) const + { + Point dp0 = gradient (p0); + Point dp2 = gradient (p2); + return + RatQuad::fromPointsTangents (p0, rot90 (dp0), p1, p2, rot90 (dp2)); + } + + /* + * Return the angle related to the normal gradient computed at the passed + * point. + * + * _point: the point at which computes the angle + * + * prerequisite: the passed point must lie on the conic + */ + double angle_at (const Point & _point) const + { + double angle = atan2 (gradient (_point)); + if (angle < 0) angle += (2*M_PI); + return angle; + } + + /* + * Return true if the given point is contained in the conic arc determined + * by the passed points. + * + * _point: the point to be tested + * _initial: the initial point of the arc + * _inner: an inner point of the arc + * _final: the final point of the arc + * + * prerequisite: the passed points must lie on the conic, the inner point + * has to be strictly contained in the arc, except when the + * initial and final points are equal: in such a case if the + * inner point is also equal to them, then they define an arc + * made up by a single point. + * + */ + bool arc_contains (const Point & _point, const Point & _initial, + const Point & _inner, const Point & _final) const + { + AngleInterval ai(angle_at(_initial), angle_at(_inner), angle_at(_final)); + return ai.contains(angle_at(_point)); + } + + Rect arc_bound (const Point & P1, const Point & Q, const Point & P2) const; + + std::vector<Point> allNearestTimes (const Point &P) const; + + /* + * Return the point on the conic section nearest to the passed point "P". + * + * P: the point to compute the nearest one + */ + Point nearestTime (const Point &P) const + { + std::vector<Point> points = allNearestTimes (P); + if ( !points.empty() ) + { + return points.front(); + } + // else + THROW_LOGICALERROR ("nearestTime: no nearest point found"); + return Point(); + } + +}; + +std::vector<Point> intersect(const xAx & C1, const xAx & C2); + +bool clip (std::vector<RatQuad> & rq, const xAx & cs, const Rect & R); + +inline std::ostream &operator<< (std::ostream &out_file, const xAx &x) { + for(double i : x.c) { + out_file << i << ", "; + } + return out_file; +} + +}; + + +#endif // LIB2GEOM_SEEN_CONICSEC_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + diff --git a/include/2geom/convex-hull.h b/include/2geom/convex-hull.h new file mode 100644 index 0000000..4dff9c2 --- /dev/null +++ b/include/2geom/convex-hull.h @@ -0,0 +1,346 @@ +/** @file + * @brief Convex hull data structures + *//* + * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> + * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_CONVEX_HULL_H +#define LIB2GEOM_SEEN_CONVEX_HULL_H + +#include <2geom/point.h> +#include <2geom/rect.h> +#include <vector> +#include <algorithm> +#include <boost/operators.hpp> +#include <optional> +#include <boost/range/iterator_range.hpp> + +namespace Geom { + +namespace { + +/** @brief Iterator for the lower convex hull. + * This iterator allows us to avoid duplicating any points in the hull + * boundary and still express most algorithms in a concise way. */ +class ConvexHullLowerIterator + : public boost::random_access_iterator_helper + < ConvexHullLowerIterator + , Point + , std::ptrdiff_t + , Point const * + , Point const & + > +{ +public: + typedef ConvexHullLowerIterator Self; + ConvexHullLowerIterator() + : _data(NULL) + , _size(0) + , _x(0) + {} + ConvexHullLowerIterator(std::vector<Point> const &pts, std::size_t x) + : _data(&pts[0]) + , _size(pts.size()) + , _x(x) + {} + + Self &operator++() { + *this += 1; + return *this; + } + Self &operator--() { + *this -= 1; + return *this; + } + Self &operator+=(std::ptrdiff_t d) { + _x += d; + return *this; + } + Self &operator-=(std::ptrdiff_t d) { + _x -= d; + return *this; + } + std::ptrdiff_t operator-(Self const &other) const { + return _x - other._x; + } + Point const &operator*() const { + if (_x < _size) { + return _data[_x]; + } else { + return *_data; + } + } + bool operator==(Self const &other) const { + return _data == other._data && _x == other._x; + } + bool operator<(Self const &other) const { + return _data == other._data && _x < other._x; + } + +private: + Point const *_data; + std::size_t _size; + std::size_t _x; +}; + +} // end anonymous namespace + +/** + * @brief Convex hull based on the Andrew's monotone chain algorithm. + * @ingroup Shapes + */ +class ConvexHull { +public: + typedef std::vector<Point>::const_iterator iterator; + typedef std::vector<Point>::const_iterator const_iterator; + typedef std::vector<Point>::const_iterator UpperIterator; + typedef ConvexHullLowerIterator LowerIterator; + + /// @name Construct a convex hull. + /// @{ + + /// Create an empty convex hull. + ConvexHull() {} + /// Construct a singular convex hull. + explicit ConvexHull(Point const &a) + : _boundary(1, a) + , _lower(1) + {} + /// Construct a convex hull of two points. + ConvexHull(Point const &a, Point const &b); + /// Construct a convex hull of three points. + ConvexHull(Point const &a, Point const &b, Point const &c); + /// Construct a convex hull of four points. + ConvexHull(Point const &a, Point const &b, Point const &c, Point const &d); + /// Create a convex hull of a vector of points. + ConvexHull(std::vector<Point> const &pts); + + /// Create a convex hull of a range of points. + template <typename Iter> + ConvexHull(Iter first, Iter last) + : _lower(0) + { + _prune(first, last, _boundary); + _construct(); + } + /// @} + + /// @name Inspect basic properties. + /// @{ + + /// Check for emptiness. + bool empty() const { return _boundary.empty(); } + /// Get the number of points in the hull. + size_t size() const { return _boundary.size(); } + /// Check whether the hull contains only one point. + bool isSingular() const { return _boundary.size() == 1; } + /// Check whether the hull is a line. + bool isLinear() const { return _boundary.size() == 2; } + /// Check whether the hull has zero area. + bool isDegenerate() const { return _boundary.size() < 3; } + /// Calculate the area of the convex hull. + double area() const; + //Point centroid() const; + //double areaAndCentroid(Point &c); + //FatLine maxDiameter() const; + //FatLine minDiameter() const; + /// @} + + /// @name Inspect bounds and extreme points. + /// @{ + + /// Compute the bounding rectangle of the convex hull. + OptRect bounds() const; + + /// Get the leftmost (minimum X) coordinate of the hull. + Coord left() const { return _boundary[0][X]; } + /// Get the rightmost (maximum X) coordinate of the hull. + Coord right() const { return _boundary[_lower-1][X]; } + /// Get the topmost (minimum Y) coordinate of the hull. + Coord top() const { return topPoint()[Y]; } + /// Get the bottommost (maximum Y) coordinate of the hull. + Coord bottom() const { return bottomPoint()[Y]; } + + /// Get the leftmost (minimum X) point of the hull. + /// If the leftmost edge is vertical, the top point of the edge is returned. + Point leftPoint() const { return _boundary[0]; } + /// Get the rightmost (maximum X) point of the hull. + /// If the rightmost edge is vertical, the bottom point edge is returned. + Point rightPoint() const { return _boundary[_lower-1]; } + /// Get the topmost (minimum Y) point of the hull. + /// If the topmost edge is horizontal, the right point of the edge is returned. + Point topPoint() const; + /// Get the bottommost (maximum Y) point of the hull. + /// If the bottommost edge is horizontal, the left point of the edge is returned. + Point bottomPoint() const; + ///@} + + /// @name Iterate over points. + /// @{ + /** @brief Get the begin iterator to the points that form the hull. + * Points are returned beginning with the leftmost one, going along + * the upper (minimum Y) side, and then along the bottom. + * Thus the points are always ordered clockwise. No point is + * repeated. */ + iterator begin() const { return _boundary.begin(); } + /// Get the end iterator to the points that form the hull. + iterator end() const { return _boundary.end(); } + /// Get the first, leftmost point in the hull. + Point const &front() const { return _boundary.front(); } + /// Get the penultimate point of the lower hull. + Point const &back() const { return _boundary.back(); } + Point const &operator[](std::size_t i) const { + return _boundary[i]; + } + + /** @brief Get an iterator range to the upper part of the hull. + * This returns a range that includes the leftmost point, + * all points of the upper hull, and the rightmost point. */ + boost::iterator_range<UpperIterator> upperHull() const { + boost::iterator_range<UpperIterator> r(_boundary.begin(), _boundary.begin() + _lower); + return r; + } + + /** @brief Get an iterator range to the lower part of the hull. + * This returns a range that includes the leftmost point, + * all points of the lower hull, and the rightmost point. */ + boost::iterator_range<LowerIterator> lowerHull() const { + if (_boundary.empty()) { + boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, 0), + LowerIterator(_boundary, 0)); + return r; + } + if (_boundary.size() == 1) { + boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, 0), + LowerIterator(_boundary, 1)); + return r; + } + boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, _lower - 1), + LowerIterator(_boundary, _boundary.size() + 1)); + return r; + } + /// @} + + /// @name Check for containment and intersection. + /// @{ + /** @brief Check whether the given point is inside the hull. + * This takes logarithmic time. */ + bool contains(Point const &p) const; + /** @brief Check whether the given axis-aligned rectangle is inside the hull. + * A rectangle is inside the hull if all of its corners are inside. */ + bool contains(Rect const &r) const; + /// Check whether the given convex hull is completely contained in this one. + bool contains(ConvexHull const &other) const; + //bool interiorContains(Point const &p) const; + //bool interiorContains(Rect const &r) const; + //bool interiorContains(ConvexHull const &other) const; + //bool intersects(Rect const &r) const; + //bool intersects(ConvexHull const &other) const; + + //ConvexHull &operator|=(ConvexHull const &other); + //ConvexHull &operator&=(ConvexHull const &other); + //ConvexHull &operator*=(Affine const &m); + + //ConvexHull &expand(Point const &p); + //void unifyWith(ConvexHull const &other); + //void intersectWith(ConvexHull const &other); + /// @} + + void swap(ConvexHull &other); + void swap(std::vector<Point> &pts); + +private: + void _construct(); + static bool _is_clockwise_turn(Point const &a, Point const &b, Point const &c); + + /// Take a vector of points and produce a pruned sorted vector. + template <typename Iter> + static void _prune(Iter first, Iter last, std::vector<Point> &out) { + std::optional<Point> ymin, ymax, xmin, xmax; + for (Iter i = first; i != last; ++i) { + Point p = *i; + if (!ymin || Point::LexLess<Y>()(p, *ymin)) { + ymin = p; + } + if (!xmin || Point::LexLess<X>()(p, *xmin)) { + xmin = p; + } + if (!ymax || Point::LexGreater<Y>()(p, *ymax)) { + ymax = p; + } + if (!xmax || Point::LexGreater<X>()(p, *xmax)) { + xmax = p; + } + } + if (!ymin) return; + + ConvexHull qhull(*xmin, *xmax, *ymin, *ymax); + for (Iter i = first; i != last; ++i) { + if (qhull.contains(*i)) continue; + out.push_back(*i); + } + + out.push_back(*xmin); + out.push_back(*xmax); + out.push_back(*ymin); + out.push_back(*ymax); + std::sort(out.begin(), out.end(), Point::LexLess<X>()); + out.erase(std::unique(out.begin(), out.end()), out.end()); + } + + /// Sequence of points forming the convex hull polygon. + std::vector<Point> _boundary; + /// Index one past the rightmost point, where the lower part of the boundary starts. + std::size_t _lower; +}; + +/** @brief Output operator for convex hulls. + * Prints out all the coordinates. */ +inline std::ostream &operator<< (std::ostream &out_file, const Geom::ConvexHull &in_cvx) { + out_file << "ConvexHull("; + for(auto i : in_cvx) { + out_file << i << ", "; + } + out_file << ")"; + return out_file; +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_CONVEX_HULL_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/coord.h b/include/2geom/coord.h new file mode 100644 index 0000000..40db84e --- /dev/null +++ b/include/2geom/coord.h @@ -0,0 +1,208 @@ +/** @file + * @brief Integral and real coordinate types and some basic utilities + *//* + * Authors: + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Copyright 2006-2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_COORD_H +#define LIB2GEOM_SEEN_COORD_H + +#include <cmath> +#include <limits> +#include <string> +#include <functional> +#include <boost/operators.hpp> +#include <2geom/forward.h> + +namespace Geom { + +/** @brief 2D axis enumeration (X or Y). + * @ingroup Primitives */ +enum Dim2 { X=0, Y=1 }; + +/** @brief Get the other (perpendicular) dimension. + * @ingroup Primitives */ +inline constexpr Dim2 other_dimension(Dim2 d) { return Dim2(int(d) ^ 1); } + +// TODO: make a smarter implementation with C++11 +template <typename T> +struct D2Traits { + using D1Value = typename T::D1Value; + using D1Reference = typename T::D1Reference; + using D1ConstReference = typename T::D1ConstReference; +}; + +/** @brief Axis extraction functor. + * For use with things such as Boost's transform_iterator. + * @ingroup Utilities */ +template <Dim2 D, typename T> +struct GetAxis { + using result_type = typename D2Traits<T>::D1Value; + using argument_type = T; + typename D2Traits<T>::D1Value operator()(T const &a) const { + return a[D]; + } +}; + +/** @brief Floating point type used to store coordinates. + * @ingroup Primitives */ +using Coord = double; + +/** @brief Type used for integral coordinates. + * @ingroup Primitives */ +using IntCoord = int; + +/** @brief Default "acceptably small" value. + * @ingroup Primitives */ +constexpr Coord EPSILON = 1e-6; + +/** @brief Get a value representing infinity. + * @ingroup Primitives */ +inline constexpr Coord infinity() { return std::numeric_limits<Coord>::infinity(); } + +/** @brief Nearness predicate for values. + * @ingroup Primitives */ +inline constexpr bool are_near(Coord a, Coord b, double eps=EPSILON) { return std::abs(a-b) <= eps; } +inline constexpr bool rel_error_bound(Coord a, Coord b, double eps=EPSILON) { return std::abs(a) <= eps*b; } + +/** @brief Numerically stable linear interpolation. + * @ingroup Primitives */ +inline constexpr Coord lerp(Coord t, Coord a, Coord b) { + return (1 - t) * a + t * b; +} + +/** @brief Traits class used with coordinate types. + * Defines point, interval and rectangle types for the given coordinate type. + * @ingroup Utilities */ +template <typename C> +struct CoordTraits { + using PointType = D2<C>; + using IntervalType = GenericInterval<C>; + using OptIntervalType = GenericOptInterval<C>; + using RectType = GenericRect<C>; + using OptRectType = GenericOptRect<C>; + + using IntervalOps = + boost::equality_comparable< IntervalType + , boost::orable< IntervalType + >>; + + using RectOps = + boost::equality_comparable< RectType + , boost::orable< RectType + , boost::orable< RectType, OptRectType + >>>; +}; + +// NOTE: operator helpers for Rect and Interval are defined here. +// This is to avoid increasing their size through multiple inheritance. + +template<> +struct CoordTraits<IntCoord> { + using PointType = IntPoint; + using IntervalType = IntInterval; + using OptIntervalType = OptIntInterval; + using RectType = IntRect; + using OptRectType = OptIntRect; + + using IntervalOps = + boost::equality_comparable< IntInterval + , boost::additive< IntInterval + , boost::additive< IntInterval, IntCoord + , boost::orable< IntInterval + >>>>; + + using RectOps = + boost::equality_comparable< IntRect + , boost::orable< IntRect + , boost::orable< IntRect, OptIntRect + , boost::additive< IntRect, IntPoint + >>>>; +}; + +template<> +struct CoordTraits<Coord> { + using PointType = Point; + using IntervalType = Interval; + using OptIntervalType = OptInterval; + using RectType = Rect; + using OptRectType = OptRect; + + using IntervalOps = + boost::equality_comparable< Interval + , boost::equality_comparable< Interval, IntInterval + , boost::additive< Interval + , boost::multipliable< Interval + , boost::orable< Interval + , boost::arithmetic< Interval, Coord + >>>>>>; + + using RectOps = + boost::equality_comparable< Rect + , boost::equality_comparable< Rect, IntRect + , boost::orable< Rect + , boost::orable< Rect, OptRect + , boost::additive< Rect, Point + , boost::multipliable< Rect, Affine + >>>>>>; +}; + +/** @brief Convert coordinate to shortest possible string. + * @return The shortest string that parses back to the original value. + * @relates Coord */ +std::string format_coord_shortest(Coord x); + +/** @brief Convert coordinate to human-readable string. + * Unlike format_coord_shortest, this function will not omit a leading zero + * before a decimal point or use small negative exponents. The output format + * is similar to Javascript functions. + * @relates Coord */ +std::string format_coord_nice(Coord x); + +/** @brief Parse coordinate string. + * When using this function in conjunction with format_coord_shortest() + * or format_coord_nice(), the value is guaranteed to be preserved exactly. + * @relates Coord */ +Coord parse_coord(std::string const &s); + +} // namespace Geom + +#endif // LIB2GEOM_SEEN_COORD_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/crossing.h b/include/2geom/crossing.h new file mode 100644 index 0000000..7ca273b --- /dev/null +++ b/include/2geom/crossing.h @@ -0,0 +1,213 @@ +/** + * @file + * @brief Structure representing the intersection of two curves + *//* + * Authors: + * Michael Sloan <mgsloan@gmail.com> + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2006-2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_CROSSING_H +#define LIB2GEOM_SEEN_CROSSING_H + +#include <vector> +#include <2geom/rect.h> +#include <2geom/sweep-bounds.h> +#include <optional> +#include <2geom/pathvector.h> + +namespace Geom { + +//Crossing between one or two paths +struct Crossing { + bool dir; //True: along a, a becomes outside. + double ta, tb; //time on a and b of crossing + unsigned a, b; //storage of indices + Crossing() : dir(false), ta(0), tb(1), a(0), b(1) {} + Crossing(double t_a, double t_b, bool direction) : dir(direction), ta(t_a), tb(t_b), a(0), b(1) {} + Crossing(double t_a, double t_b, unsigned ai, unsigned bi, bool direction) : dir(direction), ta(t_a), tb(t_b), a(ai), b(bi) {} + bool operator==(const Crossing & other) const { return a == other.a && b == other.b && dir == other.dir && ta == other.ta && tb == other.tb; } + bool operator!=(const Crossing & other) const { return !(*this == other); } + + unsigned getOther(unsigned cur) const { return a == cur ? b : a; } + double getTime(unsigned cur) const { return a == cur ? ta : tb; } + double getOtherTime(unsigned cur) const { return a == cur ? tb : ta; } + bool onIx(unsigned ix) const { return a == ix || b == ix; } +}; + +typedef std::optional<Crossing> OptCrossing; + + +/* +struct Edge { + unsigned node, path; + double time; + bool reverse; + Edge(unsigned p, double t, bool r) : path(p), time(t), reverse(r) {} + bool operator==(Edge const &other) const { return other.path == path && other.time == time && other.reverse == reverse; } +}; + +struct CrossingNode { + std::vector<Edge> edges; + CrossingNode() : edges(std::vector<Edge>()) {} + explicit CrossingNode(std::vector<Edge> es) : edges(es) {} + void add_edge(Edge const &e) { + if(std::find(edges.begin(), edges.end(), e) == edges.end()) + edges.push_back(e); + } + double time_on(unsigned p) { + for(unsigned i = 0; i < edges.size(); i++) + if(edges[i].path == p) return edges[i].time; + std::cout << "CrossingNode time_on failed\n"; + return 0; + } +}; + + +typedef std::vector<CrossingNode> CrossingGraph; + +struct TimeOrder { + bool operator()(Edge a, Edge b) { + return a.time < b.time; + } +}; + +class Path; +CrossingGraph create_crossing_graph(PathVector const &p, Crossings const &crs); +*/ + +/*inline bool are_near(Crossing a, Crossing b) { + return are_near(a.ta, b.ta) && are_near(a.tb, b.tb); +} + +struct NearF { bool operator()(Crossing a, Crossing b) { return are_near(a, b); } }; +*/ + +struct CrossingOrder { + unsigned ix; + bool rev; + CrossingOrder(unsigned i, bool r = false) : ix(i), rev(r) {} + bool operator()(Crossing a, Crossing b) { + if(rev) + return (ix == a.a ? a.ta : a.tb) < + (ix == b.a ? b.ta : b.tb); + else + return (ix == a.a ? a.ta : a.tb) > + (ix == b.a ? b.ta : b.tb); + } +}; + +typedef std::vector<Crossing> Crossings; + +typedef std::vector<Crossings> CrossingSet; + +template<typename C> +std::vector<Rect> bounds(C const &a) { + std::vector<Rect> rs; + for (unsigned i = 0; i < a.size(); i++) { + OptRect bb = a[i].boundsFast(); + if (bb) { + rs.push_back(*bb); + } + } + return rs; +} +// provide specific method for Paths because paths can be closed or open. Path::size() is named somewhat wrong... +std::vector<Rect> bounds(Path const &a); + +inline void sort_crossings(Crossings &cr, unsigned ix) { std::sort(cr.begin(), cr.end(), CrossingOrder(ix)); } + +template <typename T> +struct CrossingTraits { + typedef std::vector<T> VectorT; + static inline VectorT init(T const &x) { return VectorT(1, x); } +}; +template <> +struct CrossingTraits<Path> { + typedef PathVector VectorT; + static inline VectorT vector_one(Path const &x) { return VectorT(x); } +}; + +template<typename T> +struct Crosser { + typedef typename CrossingTraits<T>::VectorT VectorT; + virtual ~Crosser() {} + virtual Crossings crossings(T const &a, T const &b) { + return crossings(CrossingTraits<T>::vector_one(a), CrossingTraits<T>::vector_one(b))[0]; } + virtual CrossingSet crossings(VectorT const &a, VectorT const &b) { + CrossingSet results(a.size() + b.size(), Crossings()); + + std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(a), bounds(b)); + for(unsigned i = 0; i < cull.size(); i++) { + for(unsigned jx = 0; jx < cull[i].size(); jx++) { + unsigned j = cull[i][jx]; + unsigned jc = j + a.size(); + Crossings cr = crossings(a[i], b[j]); + for(auto & k : cr) { k.a = i; k.b = jc; } + + //Sort & add A-sorted crossings + sort_crossings(cr, i); + Crossings n(results[i].size() + cr.size()); + std::merge(results[i].begin(), results[i].end(), cr.begin(), cr.end(), n.begin(), CrossingOrder(i)); + results[i] = n; + + //Sort & add B-sorted crossings + sort_crossings(cr, jc); + n.resize(results[jc].size() + cr.size()); + std::merge(results[jc].begin(), results[jc].end(), cr.begin(), cr.end(), n.begin(), CrossingOrder(jc)); + results[jc] = n; + } + } + return results; + } +}; +void merge_crossings(Crossings &a, Crossings &b, unsigned i); +void offset_crossings(Crossings &cr, double a, double b); + +Crossings reverse_ta(Crossings const &cr, std::vector<double> max); +Crossings reverse_tb(Crossings const &cr, unsigned split, std::vector<double> max); +CrossingSet reverse_ta(CrossingSet const &cr, unsigned split, std::vector<double> max); +CrossingSet reverse_tb(CrossingSet const &cr, unsigned split, std::vector<double> max); + +void clean(Crossings &cr_a, Crossings &cr_b); +void delete_duplicates(Crossings &crs); + +} // end namespace Geom + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/curve.h b/include/2geom/curve.h new file mode 100644 index 0000000..9519144 --- /dev/null +++ b/include/2geom/curve.h @@ -0,0 +1,375 @@ +/** + * \file + * \brief Abstract curve type + * + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2009 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef LIB2GEOM_SEEN_CURVE_H +#define LIB2GEOM_SEEN_CURVE_H + +#include <vector> +#include <boost/operators.hpp> +#include <2geom/coord.h> +#include <2geom/point.h> +#include <2geom/interval.h> +#include <2geom/sbasis.h> +#include <2geom/d2.h> +#include <2geom/affine.h> +#include <2geom/intersection.h> + +namespace Geom { + +class PathSink; +typedef Intersection<> CurveIntersection; + +/** + * @brief Abstract continuous curve on a plane defined on [0,1]. + * + * Formally, a curve in 2Geom is defined as a function + * \f$\mathbf{C}: [0, 1] \to \mathbb{R}^2\f$ + * (a function that maps the unit interval to points on a 2D plane). Its image (the set of points + * the curve passes through) will be denoted \f$\mathcal{C} = \mathbf{C}[ [0, 1] ]\f$. + * All curve types available in 2Geom are continuous and differentiable on their + * interior, e.g. \f$(0, 1)\f$. Sometimes the curve's image (value set) is referred to as the curve + * itself for simplicity, but keep in mind that it's not strictly correct. + * + * It is common to think of the parameter as time. The curve can then be interpreted as + * describing the position of some moving object from time \f$t=0\f$ to \f$t=1\f$. + * Because of this, the parameter is frequently called the time value. + * + * Some methods return pointers to newly allocated curves. They are expected to be freed + * by the caller when no longer used. Default implementations are provided for some methods. + * + * @ingroup Curves + */ +class Curve + : boost::equality_comparable<Curve> +{ +public: + virtual ~Curve() {} + + /// @name Evaluate the curve + /// @{ + /** @brief Retrieve the start of the curve. + * @return The point corresponding to \f$\mathbf{C}(0)\f$. */ + virtual Point initialPoint() const = 0; + + /** Retrieve the end of the curve. + * @return The point corresponding to \f$\mathbf{C}(1)\f$. */ + virtual Point finalPoint() const = 0; + + /** @brief Check whether the curve has exactly zero length. + * @return True if the curve's initial point is exactly the same as its final point, and it contains + * no other points (its value set contains only one element). */ + virtual bool isDegenerate() const = 0; + + /// Check whether the curve is a line segment. + virtual bool isLineSegment() const { return false; } + + /** @brief Get the interval of allowed time values. + * @return \f$[0, 1]\f$ */ + virtual Interval timeRange() const { + Interval tr(0, 1); + return tr; + } + + /** @brief Evaluate the curve at a specified time value. + * @param t Time value + * @return \f$\mathbf{C}(t)\f$ */ + virtual Point pointAt(Coord t) const { return pointAndDerivatives(t, 0).front(); } + + /** @brief Evaluate one of the coordinates at the specified time value. + * @param t Time value + * @param d The dimension to evaluate + * @return The specified coordinate of \f$\mathbf{C}(t)\f$ */ + virtual Coord valueAt(Coord t, Dim2 d) const { return pointAt(t)[d]; } + + /** @brief Evaluate the function at the specified time value. Allows curves to be used + * as functors. */ + virtual Point operator() (Coord t) const { return pointAt(t); } + + /** @brief Evaluate the curve and its derivatives. + * This will return a vector that contains the value of the curve and the specified number + * of derivatives. However, the returned vector might contain less elements than specified + * if some derivatives do not exist. + * @param t Time value + * @param n The number of derivatives to compute + * @return Vector of at most \f$n+1\f$ elements of the form \f$[\mathbf{C}(t), + \mathbf{C}'(t), \mathbf{C}''(t), \ldots]\f$ */ + virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const = 0; + /// @} + + /// @name Change the curve's endpoints + /// @{ + /** @brief Change the starting point of the curve. + * After calling this method, it is guaranteed that \f$\mathbf{C}(0) = \mathbf{p}\f$, + * and the curve is still continuous. The precise new shape of the curve varies with curve + * type. + * @param p New starting point of the curve */ + virtual void setInitial(Point const &v) = 0; + + /** @brief Change the ending point of the curve. + * After calling this method, it is guaranteed that \f$\mathbf{C}(0) = \mathbf{p}\f$, + * and the curve is still continuous. The precise new shape of the curve varies + * with curve type. + * @param p New ending point of the curve */ + virtual void setFinal(Point const &v) = 0; + /// @} + + /// @name Compute the bounding box + /// @{ + /** @brief Quickly compute the curve's approximate bounding box. + * The resulting rectangle is guaranteed to contain all points belonging to the curve, + * but it might not be the smallest such rectangle. This method is usually fast. + * @return A rectangle that contains all points belonging to the curve. */ + virtual Rect boundsFast() const = 0; + + /** @brief Compute the curve's exact bounding box. + * This method can be dramatically slower than boundsFast() depending on the curve type. + * @return The smallest possible rectangle containing all of the curve's points. */ + virtual Rect boundsExact() const = 0; + + /** @brief Expand the given rectangle to include the transformed curve, + * assuming it already contains its initial point. + * @param bbox[in,out] bbox The rectangle to expand; it is assumed to already contain (initialPoint() * transform). + * @param transform The transform to apply to the curve before taking its bounding box. */ + virtual void expandToTransformed(Rect &bbox, Affine const &transform) const = 0; + + // I have no idea what the 'deg' parameter is for, so this is undocumented for now. + virtual OptRect boundsLocal(OptInterval const &i, unsigned deg) const = 0; + + /** @brief Compute the bounding box of a part of the curve. + * Since this method returns the smallest possible bounding rectangle of the specified portion, + * it can also be rather slow. + * @param a An interval specifying a part of the curve, or nothing. + * If \f$[0, 1] \subseteq a\f$, then the bounding box for the entire curve + * is calculated. + * @return The smallest possible rectangle containing all points in \f$\mathbf{C}[a]\f$, + * or nothing if the supplied interval is empty. */ + OptRect boundsLocal(OptInterval const &a) const { return boundsLocal(a, 0); } + /// @} + + /// @name Create new curves based on this one + /// @{ + /** @brief Create an exact copy of this curve. + * @return Pointer to a newly allocated curve, identical to the original */ + virtual Curve *duplicate() const = 0; + + /** @brief Transform this curve by an affine transformation. + * Because of this method, all curve types must be closed under affine + * transformations. + * @param m Affine describing the affine transformation */ + void transform(Affine const &m) { + *this *= m; + } + + virtual void operator*=(Translate const &tr) { *this *= Affine(tr); } + virtual void operator*=(Scale const &s) { *this *= Affine(s); } + virtual void operator*=(Rotate const &r) { *this *= Affine(r); } + virtual void operator*=(HShear const &hs) { *this *= Affine(hs); } + virtual void operator*=(VShear const &vs) { *this *= Affine(vs); } + virtual void operator*=(Zoom const &z) { *this *= Affine(z); } + virtual void operator*=(Affine const &m) = 0; + + /** @brief Create a curve transformed by an affine transformation. + * This method returns a new curve instead modifying the existing one. + * @param m Affine describing the affine transformation + * @return Pointer to a new, transformed curve */ + virtual Curve *transformed(Affine const &m) const { + Curve *ret = duplicate(); + ret->transform(m); + return ret; + } + + /** @brief Create a curve that corresponds to a part of this curve. + * For \f$a > b\f$, the returned portion will be reversed with respect to the original. + * The returned curve will always be of the same type. + * @param a Beginning of the interval specifying the portion of the curve + * @param b End of the interval + * @return New curve \f$\mathbf{D}\f$ such that: + * - \f$\mathbf{D}(0) = \mathbf{C}(a)\f$ + * - \f$\mathbf{D}(1) = \mathbf{C}(b)\f$ + * - \f$\mathbf{D}[ [0, 1] ] = \mathbf{C}[ [a?b] ]\f$, + * where \f$[a?b] = [\min(a, b), \max(a, b)]\f$ */ + virtual Curve *portion(Coord a, Coord b) const = 0; + + /** @brief A version of that accepts an Interval. */ + Curve *portion(Interval const &i) const { return portion(i.min(), i.max()); } + + /** @brief Create a reversed version of this curve. + * The result corresponds to <code>portion(1, 0)</code>, but this method might be faster. + * @return Pointer to a new curve \f$\mathbf{D}\f$ such that + * \f$\forall_{x \in [0, 1]} \mathbf{D}(x) = \mathbf{C}(1-x)\f$ */ + virtual Curve *reverse() const { return portion(1, 0); } + + /** @brief Create a derivative of this curve. + * It's best to think of the derivative in physical terms: if the curve describes + * the position of some object on the plane from time \f$t=0\f$ to \f$t=1\f$ as said in the + * introduction, then the curve's derivative describes that object's speed at the same times. + * The second derivative refers to its acceleration, the third to jerk, etc. + * @return New curve \f$\mathbf{D} = \mathbf{C}'\f$. */ + virtual Curve *derivative() const = 0; + /// @} + + /// @name Advanced operations + /// @{ + /** @brief Compute a time value at which the curve comes closest to a specified point. + * The first value with the smallest distance is returned if there are multiple such points. + * @param p Query point + * @param a Minimum time value to consider + * @param b Maximum time value to consider; \f$a < b\f$ + * @return \f$q \in [a, b]: ||\mathbf{C}(q) - \mathbf{p}|| = + \inf(\{r \in \mathbb{R} : ||\mathbf{C}(r) - \mathbf{p}||\})\f$ */ + virtual Coord nearestTime( Point const& p, Coord a = 0, Coord b = 1 ) const; + + /** @brief A version that takes an Interval. */ + Coord nearestTime(Point const &p, Interval const &i) const { + return nearestTime(p, i.min(), i.max()); + } + + /** @brief Compute time values at which the curve comes closest to a specified point. + * @param p Query point + * @param a Minimum time value to consider + * @param b Maximum time value to consider; \f$a < b\f$ + * @return Vector of points closest and equally far away from the query point */ + virtual std::vector<Coord> allNearestTimes( Point const& p, Coord from = 0, + Coord to = 1 ) const; + + /** @brief A version that takes an Interval. */ + std::vector<Coord> allNearestTimes(Point const &p, Interval const &i) { + return allNearestTimes(p, i.min(), i.max()); + } + + /** @brief Compute the arc length of this curve. + * For a curve \f$\mathbf{C}(t) = (C_x(t), C_y(t))\f$, arc length is defined for 2D curves as + * \f[ \ell = \int_{0}^{1} \sqrt { [C_x'(t)]^2 + [C_y'(t)]^2 }\, \text{d}t \f] + * In other words, we divide the curve into infinitely small linear segments + * and add together their lengths. Of course we can't subdivide the curve into + * infinitely many segments on a computer, so this method returns an approximation. + * Not that there is usually no closed form solution to such integrals, so this + * method might be slow. + * @param tolerance Maximum allowed error + * @return Total distance the curve's value travels on the plane when going from 0 to 1 */ + virtual Coord length(Coord tolerance=0.01) const; + + /** @brief Computes time values at which the curve intersects an axis-aligned line. + * @param v The coordinate of the line + * @param d Which axis the coordinate is on. X means a vertical line, Y a horizontal line. */ + virtual std::vector<Coord> roots(Coord v, Dim2 d) const = 0; + + /** @brief Compute the partial winding number of this curve. + * The partial winding number is equal to the difference between the number + * of roots at which the curve goes in the +Y direction and the number of roots + * at which the curve goes in the -Y direction. This method is mainly useful + * for implementing path winding calculation. It will ignore roots which + * are local maxima on the Y axis. + * @param p Point where the winding number should be determined + * @return Winding number contribution at p */ + virtual int winding(Point const &p) const; + + /// Compute intersections with another curve. + virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const; + + /// Compute intersections of this curve with itself. + virtual std::vector<CurveIntersection> intersectSelf(Coord eps = EPSILON) const; + + /** @brief Compute a vector tangent to the curve. + * This will return an unit vector (a Point with length() equal to 1) that denotes a vector + * tangent to the curve. This vector is defined as + * \f$ \mathbf{v}(t) = \frac{\mathbf{C}'(t)}{||\mathbf{C}'(t)||} \f$. It is pointed + * in the direction of increasing \f$t\f$, at the specified time value. The method uses + * l'Hopital's rule when the derivative is zero. A zero vector is returned if no non-zero + * derivative could be found. + * @param t Time value + * @param n The maximum order of derivative to consider + * @return Unit tangent vector \f$\mathbf{v}(t)\f$ */ + virtual Point unitTangentAt(Coord t, unsigned n = 3) const; + + /** @brief Convert the curve to a symmetric power basis polynomial. + * Symmetric power basis polynomials (S-basis for short) are numerical representations + * of curves with excellent numerical properties. Most high level operations provided by 2Geom + * are implemented in terms of S-basis operations, so every curve has to provide a method + * to convert it to an S-basis polynomial on two variables. See SBasis class reference + * for more information. */ + virtual D2<SBasis> toSBasis() const = 0; + /// @} + + /// @name Miscellaneous + /// @{ + /** Return the number of independent parameters required to represent all variations + * of this curve. For example, for Bezier curves it returns the curve's order + * multiplied by 2. */ + virtual int degreesOfFreedom() const { return 0;} + + /** @brief Test equality of two curves. + * Equality means that for any time value, the evaluation of either curve will yield + * the same value. This means non-degenerate curves are not equal to their reverses. + * Note that this tests for exact equality. + * @return True if the curves are identical, false otherwise */ + virtual bool operator==(Curve const &c) const = 0; + + /** @brief Test whether two curves are approximately the same. */ + virtual bool isNear(Curve const &c, Coord precision) const = 0; + + /** @brief Feed the curve to a PathSink */ + virtual void feed(PathSink &sink, bool moveto_initial) const; + /// @} +}; + +inline +Coord nearest_time(Point const& p, Curve const& c) { + return c.nearestTime(p); +} + +// for make benefit glorious library of Boost Pointer Container +inline +Curve *new_clone(Curve const &c) { + return c.duplicate(); +} + +} // end namespace Geom + + +#endif // _2GEOM_CURVE_H_ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/curves.h b/include/2geom/curves.h new file mode 100644 index 0000000..46fb6d9 --- /dev/null +++ b/include/2geom/curves.h @@ -0,0 +1,54 @@ +/** @file + * @brief Include all curve types + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2007-2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_CURVES_H +#define LIB2GEOM_SEEN_CURVES_H + +#include <2geom/curve.h> +#include <2geom/sbasis-curve.h> +#include <2geom/bezier-curve.h> +#include <2geom/elliptical-arc.h> + +#endif // LIB2GEOM_SEEN_CURVES_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + diff --git a/include/2geom/d2.h b/include/2geom/d2.h new file mode 100644 index 0000000..45f036b --- /dev/null +++ b/include/2geom/d2.h @@ -0,0 +1,564 @@ +/** + * \file + * \brief Lifts one dimensional objects into 2D + *//* + * Authors: + * Michael Sloan <mgsloan@gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, output to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_D2_H +#define LIB2GEOM_SEEN_D2_H + +#include <iterator> +#include <boost/concept/assert.hpp> +#include <boost/iterator/transform_iterator.hpp> +#include <2geom/point.h> +#include <2geom/interval.h> +#include <2geom/affine.h> +#include <2geom/rect.h> +#include <2geom/concepts.h> + +namespace Geom { +/** + * @brief Adaptor that creates 2D functions from 1D ones. + * @ingroup Fragments + */ +template <typename T> +class D2 +{ +private: + T f[2]; + +public: + typedef T D1Value; + typedef T &D1Reference; + typedef T const &D1ConstReference; + + D2() {f[X] = f[Y] = T();} + explicit D2(Point const &a) { + f[X] = T(a[X]); f[Y] = T(a[Y]); + } + + D2(T const &a, T const &b) { + f[X] = a; + f[Y] = b; + } + + template <typename Iter> + D2(Iter first, Iter last) { + typedef typename std::iterator_traits<Iter>::value_type V; + typedef typename boost::transform_iterator<GetAxis<X,V>, Iter> XIter; + typedef typename boost::transform_iterator<GetAxis<Y,V>, Iter> YIter; + + XIter xfirst(first, GetAxis<X,V>()), xlast(last, GetAxis<X,V>()); + f[X] = T(xfirst, xlast); + YIter yfirst(first, GetAxis<Y,V>()), ylast(last, GetAxis<Y,V>()); + f[Y] = T(yfirst, ylast); + } + + D2(std::vector<Point> const &vec) { + typedef Point V; + typedef std::vector<Point>::const_iterator Iter; + typedef boost::transform_iterator<GetAxis<X,V>, Iter> XIter; + typedef boost::transform_iterator<GetAxis<Y,V>, Iter> YIter; + + XIter xfirst(vec.begin(), GetAxis<X,V>()), xlast(vec.end(), GetAxis<X,V>()); + f[X] = T(xfirst, xlast); + YIter yfirst(vec.begin(), GetAxis<Y,V>()), ylast(vec.end(), GetAxis<Y,V>()); + f[Y] = T(yfirst, ylast); + } + + //TODO: ask MenTaLguY about operator= as seen in Point + + T& operator[](unsigned i) { return f[i]; } + T const & operator[](unsigned i) const { return f[i]; } + Point point(unsigned i) const { + Point ret(f[X][i], f[Y][i]); + return ret; + } + + //IMPL: FragmentConcept + typedef Point output_type; + bool isZero(double eps=EPSILON) const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return f[X].isZero(eps) && f[Y].isZero(eps); + } + bool isConstant(double eps=EPSILON) const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return f[X].isConstant(eps) && f[Y].isConstant(eps); + } + bool isFinite() const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return f[X].isFinite() && f[Y].isFinite(); + } + Point at0() const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return Point(f[X].at0(), f[Y].at0()); + } + Point at1() const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return Point(f[X].at1(), f[Y].at1()); + } + Point pointAt(double t) const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return (*this)(t); + } + Point valueAt(double t) const { + // TODO: remove this alias + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return (*this)(t); + } + std::vector<Point > valueAndDerivatives(double t, unsigned n) const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + std::vector<Coord> x = f[X].valueAndDerivatives(t, n), + y = f[Y].valueAndDerivatives(t, n); // always returns a vector of size n+1 + std::vector<Point> res(n+1); + for(unsigned i = 0; i <= n; i++) { + res[i] = Point(x[i], y[i]); + } + return res; + } + D2<SBasis> toSBasis() const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return D2<SBasis>(f[X].toSBasis(), f[Y].toSBasis()); + } + + Point operator()(double t) const; + Point operator()(double x, double y) const; +}; +template <typename T> +inline D2<T> reverse(const D2<T> &a) { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return D2<T>(reverse(a[X]), reverse(a[Y])); +} + +template <typename T> +inline D2<T> portion(const D2<T> &a, Coord f, Coord t) { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return D2<T>(portion(a[X], f, t), portion(a[Y], f, t)); +} + +template <typename T> +inline D2<T> portion(const D2<T> &a, Interval i) { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return D2<T>(portion(a[X], i), portion(a[Y], i)); +} + +//IMPL: EqualityComparableConcept +template <typename T> +inline bool +operator==(D2<T> const &a, D2<T> const &b) { + BOOST_CONCEPT_ASSERT((EqualityComparableConcept<T>)); + return a[0]==b[0] && a[1]==b[1]; +} +template <typename T> +inline bool +operator!=(D2<T> const &a, D2<T> const &b) { + BOOST_CONCEPT_ASSERT((EqualityComparableConcept<T>)); + return a[0]!=b[0] || a[1]!=b[1]; +} + +//IMPL: NearConcept +template <typename T> +inline bool +are_near(D2<T> const &a, D2<T> const &b, double tol) { + BOOST_CONCEPT_ASSERT((NearConcept<T>)); + return are_near(a[0], b[0], tol) && are_near(a[1], b[1], tol); +} + +//IMPL: AddableConcept +template <typename T> +inline D2<T> +operator+(D2<T> const &a, D2<T> const &b) { + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] + b[i]; + return r; +} +template <typename T> +inline D2<T> +operator-(D2<T> const &a, D2<T> const &b) { + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] - b[i]; + return r; +} +template <typename T> +inline D2<T> +operator+=(D2<T> &a, D2<T> const &b) { + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + + for(unsigned i = 0; i < 2; i++) + a[i] += b[i]; + return a; +} +template <typename T> +inline D2<T> +operator-=(D2<T> &a, D2<T> const & b) { + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + + for(unsigned i = 0; i < 2; i++) + a[i] -= b[i]; + return a; +} + +//IMPL: ScalableConcept +template <typename T> +inline D2<T> +operator-(D2<T> const & a) { + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = -a[i]; + return r; +} +template <typename T> +inline D2<T> +operator*(D2<T> const & a, Point const & b) { + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] * b[i]; + return r; +} +template <typename T> +inline D2<T> +operator/(D2<T> const & a, Point const & b) { + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + //TODO: b==0? + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] / b[i]; + return r; +} +template <typename T> +inline D2<T> +operator*=(D2<T> &a, Point const & b) { + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + + for(unsigned i = 0; i < 2; i++) + a[i] *= b[i]; + return a; +} +template <typename T> +inline D2<T> +operator/=(D2<T> &a, Point const & b) { + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + //TODO: b==0? + for(unsigned i = 0; i < 2; i++) + a[i] /= b[i]; + return a; +} + +template <typename T> +inline D2<T> operator*(D2<T> const & a, double b) { return D2<T>(a[0]*b, a[1]*b); } +template <typename T> +inline D2<T> operator*=(D2<T> & a, double b) { a[0] *= b; a[1] *= b; return a; } +template <typename T> +inline D2<T> operator/(D2<T> const & a, double b) { return D2<T>(a[0]/b, a[1]/b); } +template <typename T> +inline D2<T> operator/=(D2<T> & a, double b) { a[0] /= b; a[1] /= b; return a; } + +template<typename T> +D2<T> operator*(D2<T> const &v, Affine const &m) { + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + D2<T> ret; + for(unsigned i = 0; i < 2; i++) + ret[i] = v[X] * m[i] + v[Y] * m[i + 2] + m[i + 4]; + return ret; +} + +//IMPL: MultiplicableConcept +template <typename T> +inline D2<T> +operator*(D2<T> const & a, T const & b) { + BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>)); + D2<T> ret; + for(unsigned i = 0; i < 2; i++) + ret[i] = a[i] * b; + return ret; +} + +//IMPL: + +//IMPL: OffsetableConcept +template <typename T> +inline D2<T> +operator+(D2<T> const & a, Point b) { + BOOST_CONCEPT_ASSERT((OffsetableConcept<T>)); + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] + b[i]; + return r; +} +template <typename T> +inline D2<T> +operator-(D2<T> const & a, Point b) { + BOOST_CONCEPT_ASSERT((OffsetableConcept<T>)); + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] - b[i]; + return r; +} +template <typename T> +inline D2<T> +operator+=(D2<T> & a, Point b) { + BOOST_CONCEPT_ASSERT((OffsetableConcept<T>)); + for(unsigned i = 0; i < 2; i++) + a[i] += b[i]; + return a; +} +template <typename T> +inline D2<T> +operator-=(D2<T> & a, Point b) { + BOOST_CONCEPT_ASSERT((OffsetableConcept<T>)); + for(unsigned i = 0; i < 2; i++) + a[i] -= b[i]; + return a; +} + +template <typename T> +inline T +dot(D2<T> const & a, D2<T> const & b) { + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>)); + + T r; + for(unsigned i = 0; i < 2; i++) + r += a[i] * b[i]; + return r; +} + +/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b + * @return \f$a \bullet b = a_X b_X + a_Y b_Y\f$. + * @relates D2 */ +template <typename T> +inline T +dot(D2<T> const & a, Point const & b) { + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + + T r; + for(unsigned i = 0; i < 2; i++) { + r += a[i] * b[i]; + } + return r; +} + +/** @brief Calculates the 'cross product' or 'outer product' of \c a and \c b + * @return \f$a \times b = a_Y b_X - a_X b_Y\f$. + * @relates D2 */ +template <typename T> +inline T +cross(D2<T> const & a, D2<T> const & b) { + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>)); + + return a[1] * b[0] - a[0] * b[1]; +} + + +//equivalent to cw/ccw, for use in situations where rotation direction doesn't matter. +template <typename T> +inline D2<T> +rot90(D2<T> const & a) { + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + return D2<T>(-a[Y], a[X]); +} + +//TODO: concepterize the following functions +template <typename T> +inline D2<T> +compose(D2<T> const & a, T const & b) { + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = compose(a[i],b); + return r; +} + +template <typename T> +inline D2<T> +compose_each(D2<T> const & a, D2<T> const & b) { + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = compose(a[i],b[i]); + return r; +} + +template <typename T> +inline D2<T> +compose_each(T const & a, D2<T> const & b) { + D2<T> r; + for(unsigned i = 0; i < 2; i++) + r[i] = compose(a,b[i]); + return r; +} + + +template<typename T> +inline Point +D2<T>::operator()(double t) const { + Point p; + for(unsigned i = 0; i < 2; i++) + p[i] = (*this)[i](t); + return p; +} + +//TODO: we might want to have this take a Point as the parameter. +template<typename T> +inline Point +D2<T>::operator()(double x, double y) const { + Point p; + for(unsigned i = 0; i < 2; i++) + p[i] = (*this)[i](x, y); + return p; +} + + +template<typename T> +D2<T> derivative(D2<T> const & a) { + return D2<T>(derivative(a[X]), derivative(a[Y])); +} +template<typename T> +D2<T> integral(D2<T> const & a) { + return D2<T>(integral(a[X]), integral(a[Y])); +} + +/** A function to print out the Point. It just prints out the coords + on the given output stream */ +template <typename T> +inline std::ostream &operator<< (std::ostream &out_file, const Geom::D2<T> &in_d2) { + out_file << "X: " << in_d2[X] << " Y: " << in_d2[Y]; + return out_file; +} + +//Some D2 Fragment implementation which requires rect: +template <typename T> +OptRect bounds_fast(const D2<T> &a) { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return OptRect(bounds_fast(a[X]), bounds_fast(a[Y])); +} +template <typename T> +OptRect bounds_exact(const D2<T> &a) { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return OptRect(bounds_exact(a[X]), bounds_exact(a[Y])); +} +template <typename T> +OptRect bounds_local(const D2<T> &a, const OptInterval &t) { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return OptRect(bounds_local(a[X], t), bounds_local(a[Y], t)); +} + + + +// SBasis-specific declarations + +inline D2<SBasis> compose(D2<SBasis> const & a, SBasis const & b) { + return D2<SBasis>(compose(a[X], b), compose(a[Y], b)); +} + +SBasis L2(D2<SBasis> const & a, unsigned k); +double L2(D2<double> const & a); + +D2<SBasis> multiply(Linear const & a, D2<SBasis> const & b); +inline D2<SBasis> operator*(Linear const & a, D2<SBasis> const & b) { return multiply(a, b); } +D2<SBasis> multiply(SBasis const & a, D2<SBasis> const & b); +inline D2<SBasis> operator*(SBasis const & a, D2<SBasis> const & b) { return multiply(a, b); } +D2<SBasis> truncate(D2<SBasis> const & a, unsigned terms); + +unsigned sbasis_size(D2<SBasis> const & a); +double tail_error(D2<SBasis> const & a, unsigned tail); + +//Piecewise<D2<SBasis> > specific declarations + +Piecewise<D2<SBasis> > sectionize(D2<Piecewise<SBasis> > const &a); +D2<Piecewise<SBasis> > make_cuts_independent(Piecewise<D2<SBasis> > const &a); +Piecewise<D2<SBasis> > rot90(Piecewise<D2<SBasis> > const &a); +Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b); +Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Point const &b); +Piecewise<SBasis> cross(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b); + +Piecewise<D2<SBasis> > operator*(Piecewise<D2<SBasis> > const &a, Affine const &m); + +Piecewise<D2<SBasis> > force_continuity(Piecewise<D2<SBasis> > const &f, double tol=0, bool closed=false); + +std::vector<Piecewise<D2<SBasis> > > fuse_nearby_ends(std::vector<Piecewise<D2<SBasis> > > const &f, double tol=0); + +std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > split_at_discontinuities (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwsbin, double tol = .0001); + +Point unitTangentAt(D2<SBasis> const & a, Coord t, unsigned n = 3); + +//bounds specializations with order +inline OptRect bounds_fast(D2<SBasis> const & s, unsigned order=0) { + OptRect retval; + OptInterval xint = bounds_fast(s[X], order); + if (xint) { + OptInterval yint = bounds_fast(s[Y], order); + if (yint) { + retval = Rect(*xint, *yint); + } + } + return retval; +} +inline OptRect bounds_local(D2<SBasis> const & s, OptInterval i, unsigned order=0) { + OptRect retval; + OptInterval xint = bounds_local(s[X], i, order); + OptInterval yint = bounds_local(s[Y], i, order); + if (xint && yint) { + retval = Rect(*xint, *yint); + } + return retval; +} + +std::vector<Interval> level_set( D2<SBasis> const &f, Rect region); +std::vector<Interval> level_set( D2<SBasis> const &f, Point p, double tol); +std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector<Rect> regions); +std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector<Point> pts, double tol); + + +} // end namespace Geom + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/ellipse.h b/include/2geom/ellipse.h new file mode 100644 index 0000000..0d1567a --- /dev/null +++ b/include/2geom/ellipse.h @@ -0,0 +1,260 @@ +/** @file + * @brief Ellipse shape + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef LIB2GEOM_SEEN_ELLIPSE_H +#define LIB2GEOM_SEEN_ELLIPSE_H + +#include <vector> +#include <2geom/angle.h> +#include <2geom/bezier-curve.h> +#include <2geom/exception.h> +#include <2geom/forward.h> +#include <2geom/line.h> +#include <2geom/transforms.h> + +namespace Geom { + +class EllipticalArc; +class Circle; + +/** @brief Set of points with a constant sum of distances from two foci. + * + * An ellipse can be specified in several ways. Internally, 2Geom uses + * the SVG style representation: center, rays and angle between the +X ray + * and the +X axis. Another popular way is to use an implicit equation, + * which is as follows: + * \f$Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0\f$ + * + * @ingroup Shapes */ +class Ellipse + : boost::multipliable< Ellipse, Translate + , boost::multipliable< Ellipse, Scale + , boost::multipliable< Ellipse, Rotate + , boost::multipliable< Ellipse, Zoom + , boost::multipliable< Ellipse, Affine + , boost::equality_comparable< Ellipse + > > > > > > +{ + Point _center; + Point _rays; + Angle _angle; +public: + Ellipse() {} + Ellipse(Point const &c, Point const &r, Coord angle) + : _center(c) + , _rays(r) + , _angle(angle) + {} + Ellipse(Coord cx, Coord cy, Coord rx, Coord ry, Coord angle) + : _center(cx, cy) + , _rays(rx, ry) + , _angle(angle) + {} + Ellipse(double A, double B, double C, double D, double E, double F) { + setCoefficients(A, B, C, D, E, F); + } + /// Construct ellipse from a circle. + Ellipse(Geom::Circle const &c); + + /// Set center, rays and angle. + void set(Point const &c, Point const &r, Coord angle) { + _center = c; + _rays = r; + _angle = angle; + } + /// Set center, rays and angle as constituent values. + void set(Coord cx, Coord cy, Coord rx, Coord ry, Coord a) { + _center[X] = cx; + _center[Y] = cy; + _rays[X] = rx; + _rays[Y] = ry; + _angle = a; + } + /// Set an ellipse by solving its implicit equation. + void setCoefficients(double A, double B, double C, double D, double E, double F); + /// Set the center. + void setCenter(Point const &p) { _center = p; } + /// Set the center by coordinates. + void setCenter(Coord cx, Coord cy) { _center[X] = cx; _center[Y] = cy; } + /// Set both rays of the ellipse. + void setRays(Point const &p) { _rays = p; } + /// Set both rays of the ellipse as coordinates. + void setRays(Coord x, Coord y) { _rays[X] = x; _rays[Y] = y; } + /// Set one of the rays of the ellipse. + void setRay(Coord r, Dim2 d) { _rays[d] = r; } + /// Set the angle the X ray makes with the +X axis. + void setRotationAngle(Angle a) { _angle = a; } + + Point center() const { return _center; } + Coord center(Dim2 d) const { return _center[d]; } + /// Get both rays as a point. + Point rays() const { return _rays; } + /// Get one ray of the ellipse. + Coord ray(Dim2 d) const { return _rays[d]; } + /// Get the angle the X ray makes with the +X axis. + Angle rotationAngle() const { return _angle; } + /// Get the point corresponding to the +X ray of the ellipse. + Point initialPoint() const; + /// Get the point corresponding to the +X ray of the ellipse. + Point finalPoint() const { return initialPoint(); } + + /** @brief Create an ellipse passing through the specified points + * At least five points have to be specified. */ + void fit(std::vector<Point> const& points); + + /** @brief Create an elliptical arc from a section of the ellipse. + * This is mainly useful to determine the flags of the new arc. + * The passed points should lie on the ellipse, otherwise the results + * will be undefined. + * @param ip Initial point of the arc + * @param inner Point in the middle of the arc, used to pick one of two possibilities + * @param fp Final point of the arc + * @return Newly allocated arc, delete when no longer used */ + EllipticalArc *arc(Point const &ip, Point const &inner, Point const &fp); + + /** @brief Return an ellipse with less degrees of freedom. + * The canonical form always has the angle less than \f$\frac{\pi}{2}\f$, + * and zero if the rays are equal (i.e. the ellipse is a circle). */ + Ellipse canonicalForm() const; + void makeCanonical(); + + /** @brief Compute the transform that maps the unit circle to this ellipse. + * Each ellipse can be interpreted as a translated, scaled and rotate unit circle. + * This function returns the transform that maps the unit circle to this ellipse. + * @return Transform from unit circle to the ellipse */ + Affine unitCircleTransform() const; + /** @brief Compute the transform that maps this ellipse to the unit circle. + * This may be a little more precise and/or faster than simply using + * unitCircleTransform().inverse(). An exception will be thrown for + * degenerate ellipses. */ + Affine inverseUnitCircleTransform() const; + + LineSegment majorAxis() const { return ray(X) >= ray(Y) ? axis(X) : axis(Y); } + LineSegment minorAxis() const { return ray(X) < ray(Y) ? axis(X) : axis(Y); } + LineSegment semimajorAxis(int sign = 1) const { + return ray(X) >= ray(Y) ? semiaxis(X, sign) : semiaxis(Y, sign); + } + LineSegment semiminorAxis(int sign = 1) const { + return ray(X) < ray(Y) ? semiaxis(X, sign) : semiaxis(Y, sign); + } + LineSegment axis(Dim2 d) const; + LineSegment semiaxis(Dim2 d, int sign = 1) const; + + /// Get the tight-fitting bounding box of the ellipse. + Rect boundsExact() const; + + /** @brief Get a fast to compute bounding box which contains the ellipse. + * + * The returned rectangle engulfs the ellipse but it may not be the smallest + * axis-aligned rectangle with this property. + */ + Rect boundsFast() const; + + /// Get the coefficients of the ellipse's implicit equation. + std::vector<double> coefficients() const; + void coefficients(Coord &A, Coord &B, Coord &C, Coord &D, Coord &E, Coord &F) const; + + /** @brief Evaluate a point on the ellipse. + * The parameter range is \f$[0, 2\pi)\f$; larger and smaller values + * wrap around. */ + Point pointAt(Coord t) const; + /// Evaluate a single coordinate of a point on the ellipse. + Coord valueAt(Coord t, Dim2 d) const; + + /** @brief Find the time value of a point on an ellipse. + * If the point is not on the ellipse, the returned time value will correspond + * to an intersection with a ray from the origin passing through the point + * with the ellipse. Note that this is NOT the nearest point on the ellipse. */ + Coord timeAt(Point const &p) const; + + /// Get the value of the derivative at time t normalized to unit length. + Point unitTangentAt(Coord t) const; + + /// Check whether the ellipse contains the given point. + bool contains(Point const &p) const; + + /// Compute intersections with an infinite line. + std::vector<ShapeIntersection> intersect(Line const &line) const; + /// Compute intersections with a line segment. + std::vector<ShapeIntersection> intersect(LineSegment const &seg) const; + /// Compute intersections with another ellipse. + std::vector<ShapeIntersection> intersect(Ellipse const &other) const; + /// Compute intersections with a 2D Bezier polynomial. + std::vector<ShapeIntersection> intersect(D2<Bezier> const &other) const; + + Ellipse &operator*=(Translate const &t) { + _center *= t; + return *this; + } + Ellipse &operator*=(Scale const &s) { + _center *= s; + _rays *= s; + return *this; + } + Ellipse &operator*=(Zoom const &z) { + _center *= z; + _rays *= z.scale(); + return *this; + } + Ellipse &operator*=(Rotate const &r); + Ellipse &operator*=(Affine const &m); + + /// Compare ellipses for exact equality. + bool operator==(Ellipse const &other) const; +}; + +/** @brief Test whether two ellipses are approximately the same. + * This will check whether no point on ellipse a is further away from + * the corresponding point on ellipse b than precision. + * @relates Ellipse */ +bool are_near(Ellipse const &a, Ellipse const &b, Coord precision = EPSILON); + +/** @brief Outputs ellipse data, useful for debugging. + * @relates Ellipse */ +std::ostream &operator<<(std::ostream &out, Ellipse const &e); + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_ELLIPSE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/elliptical-arc.h b/include/2geom/elliptical-arc.h new file mode 100644 index 0000000..567e207 --- /dev/null +++ b/include/2geom/elliptical-arc.h @@ -0,0 +1,344 @@ +/** + * \file + * \brief Elliptical arc curve + * + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2009 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_ELLIPTICAL_ARC_H +#define LIB2GEOM_SEEN_ELLIPTICAL_ARC_H + +#include <algorithm> +#include <2geom/affine.h> +#include <2geom/angle.h> +#include <2geom/bezier-curve.h> +#include <2geom/curve.h> +#include <2geom/ellipse.h> +#include <2geom/sbasis-curve.h> // for non-native methods +#include <2geom/utils.h> + +namespace Geom +{ + +class EllipticalArc : public Curve +{ +public: + /** @brief Creates an arc with all variables set to zero. */ + EllipticalArc() + : _initial_point(0,0) + , _final_point(0,0) + , _large_arc(false) + {} + /** @brief Create a new elliptical arc. + * @param ip Initial point of the arc + * @param r Rays of the ellipse as a point + * @param rot Angle of rotation of the X axis of the ellipse in radians + * @param large If true, the large arc is chosen (always >= 180 degrees), otherwise + * the smaller arc is chosen + * @param sweep If true, the clockwise arc is chosen, otherwise the counter-clockwise + * arc is chosen + * @param fp Final point of the arc */ + EllipticalArc( Point const &ip, Point const &r, + Coord rot_angle, bool large_arc, bool sweep, + Point const &fp + ) + : _initial_point(ip) + , _final_point(fp) + , _ellipse(0, 0, r[X], r[Y], rot_angle) + , _angles(0, 0, sweep) + , _large_arc(large_arc) + { + _updateCenterAndAngles(); + } + + /// Create a new elliptical arc, giving the ellipse's rays as separate coordinates. + EllipticalArc( Point const &ip, Coord rx, Coord ry, + Coord rot_angle, bool large_arc, bool sweep, + Point const &fp + ) + : _initial_point(ip) + , _final_point(fp) + , _ellipse(0, 0, rx, ry, rot_angle) + , _angles(0, 0, sweep) + , _large_arc(large_arc) + { + _updateCenterAndAngles(); + } + + /// @name Retrieve basic information + /// @{ + + /** @brief Get a coordinate of the elliptical arc's center. + * @param d The dimension to retrieve + * @return The selected coordinate of the center */ + Coord center(Dim2 d) const { return _ellipse.center(d); } + + /** @brief Get the arc's center + * @return The arc's center, situated on the intersection of the ellipse's rays */ + Point center() const { return _ellipse.center(); } + + /** @brief Get one of the ellipse's rays + * @param d Dimension to retrieve + * @return The selected ray of the ellipse */ + Coord ray(Dim2 d) const { return _ellipse.ray(d); } + + /** @brief Get both rays as a point + * @return Point with X equal to the X ray and Y to Y ray */ + Point rays() const { return _ellipse.rays(); } + + /** @brief Get the defining ellipse's rotation + * @return Angle between the +X ray of the ellipse and the +X axis */ + Angle rotationAngle() const { + return _ellipse.rotationAngle(); + } + + /** @brief Whether the arc is larger than half an ellipse. + * @return True if the arc is larger than \f$\pi\f$, false otherwise */ + bool largeArc() const { return _large_arc; } + + /** @brief Whether the arc turns clockwise + * @return True if the arc makes a clockwise turn when going from initial to final + * point, false otherwise */ + bool sweep() const { return _angles.sweep(); } + + Angle initialAngle() const { return _angles.initialAngle(); } + Angle finalAngle() const { return _angles.finalAngle(); } + /// @} + + /// @name Modify parameters + /// @{ + + /// Change all of the arc's parameters. + void set( Point const &ip, double rx, double ry, + double rot_angle, bool large_arc, bool sweep, + Point const &fp + ) + { + _initial_point = ip; + _final_point = fp; + _ellipse.setRays(rx, ry); + _ellipse.setRotationAngle(rot_angle); + _angles.setSweep(sweep); + _large_arc = large_arc; + _updateCenterAndAngles(); + } + + /// Change all of the arc's parameters. + void set( Point const &ip, Point const &r, + Angle rot_angle, bool large_arc, bool sweep, + Point const &fp + ) + { + _initial_point = ip; + _final_point = fp; + _ellipse.setRays(r); + _ellipse.setRotationAngle(rot_angle); + _angles.setSweep(sweep); + _large_arc = large_arc; + _updateCenterAndAngles(); + } + + /** @brief Change the initial and final point in one operation. + * This method exists because modifying any of the endpoints causes rather costly + * recalculations of the center and extreme angles. + * @param ip New initial point + * @param fp New final point */ + void setEndpoints(Point const &ip, Point const &fp) { + _initial_point = ip; + _final_point = fp; + _updateCenterAndAngles(); + } + /// @} + + /// @name Evaluate the arc as a function + /// @{ + /** Check whether the arc contains the given angle + * @param t The angle to check + * @return True if the arc contains the angle, false otherwise */ + bool containsAngle(Angle angle) const { return _angles.contains(angle); } + + /** @brief Evaluate the arc at the specified angular coordinate + * @param t Angle + * @return Point corresponding to the given angle */ + Point pointAtAngle(Coord t) const; + + /** @brief Evaluate one of the arc's coordinates at the specified angle + * @param t Angle + * @param d The dimension to retrieve + * @return Selected coordinate of the arc at the specified angle */ + Coord valueAtAngle(Coord t, Dim2 d) const; + + /// Compute the curve time value corresponding to the given angular value. + Coord timeAtAngle(Angle a) const { return _angles.timeAtAngle(a); } + + /// Compute the angular domain value corresponding to the given time value. + Angle angleAt(Coord t) const { return _angles.angleAt(t); } + + /** @brief Compute the amount by which the angle parameter changes going from start to end. + * This has range \f$(-2\pi, 2\pi)\f$ and thus cannot be represented as instance + * of the class Angle. Add this to the initial angle to obtain the final angle. */ + Coord sweepAngle() const { return _angles.sweepAngle(); } + + /** @brief Get the elliptical angle spanned by the arc. + * This is basically the absolute value of sweepAngle(). */ + Coord angularExtent() const { return _angles.extent(); } + + /// Get the angular interval of the arc. + AngleInterval angularInterval() const { return _angles; } + + /// Evaluate the arc in the curve domain, i.e. \f$[0, 1]\f$. + Point pointAt(Coord t) const override; + + /// Evaluate a single coordinate on the arc in the curve domain. + Coord valueAt(Coord t, Dim2 d) const override; + + /** @brief Compute a transform that maps the unit circle to the arc's ellipse. + * Each ellipse can be interpreted as a translated, scaled and rotate unit circle. + * This function returns the transform that maps the unit circle to the arc's ellipse. + * @return Transform from unit circle to the arc's ellipse */ + Affine unitCircleTransform() const { + Affine result = _ellipse.unitCircleTransform(); + return result; + } + + /** @brief Compute a transform that maps the arc's ellipse to the unit circle. */ + Affine inverseUnitCircleTransform() const { + Affine result = _ellipse.inverseUnitCircleTransform(); + return result; + } + /// @} + + /// @name Deal with degenerate ellipses. + /// @{ + /** @brief Check whether both rays are nonzero. + * If they are not, the arc is represented as a line segment instead. */ + bool isChord() const { + return ray(X) == 0 || ray(Y) == 0; + } + + /** @brief Get the line segment connecting the arc's endpoints. + * @return A linear segment with initial and final point corresponding to those of the arc. */ + LineSegment chord() const { return LineSegment(_initial_point, _final_point); } + /// @} + + // implementation of overloads goes here + Point initialPoint() const override { return _initial_point; } + Point finalPoint() const override { return _final_point; } + Curve* duplicate() const override { return new EllipticalArc(*this); } + void setInitial(Point const &p) override { + _initial_point = p; + _updateCenterAndAngles(); + } + void setFinal(Point const &p) override { + _final_point = p; + _updateCenterAndAngles(); + } + bool isDegenerate() const override { + return _initial_point == _final_point; + } + bool isLineSegment() const override { return isChord(); } + Rect boundsFast() const override { + return boundsExact(); + } + Rect boundsExact() const override; + void expandToTransformed(Rect &bbox, Affine const &transform) const override; + // TODO: native implementation of the following methods + OptRect boundsLocal(OptInterval const &i, unsigned int deg) const override { + return SBasisCurve(toSBasis()).boundsLocal(i, deg); + } + std::vector<double> roots(double v, Dim2 d) const override; +#ifdef HAVE_GSL + std::vector<double> allNearestTimes( Point const& p, double from = 0, double to = 1 ) const override; + double nearestTime( Point const& p, double from = 0, double to = 1 ) const override { + if ( are_near(ray(X), ray(Y)) && are_near(center(), p) ) { + return from; + } + return allNearestTimes(p, from, to).front(); + } +#endif + std::vector<CurveIntersection> intersect(Curve const &other, Coord eps=EPSILON) const override; + int degreesOfFreedom() const override { return 7; } + Curve *derivative() const override; + + using Curve::operator*=; + void operator*=(Translate const &tr) override; + void operator*=(Scale const &s) override; + void operator*=(Rotate const &r) override; + void operator*=(Zoom const &z) override; + void operator*=(Affine const &m) override; + + std::vector<Point> pointAndDerivatives(Coord t, unsigned int n) const override; + D2<SBasis> toSBasis() const override; + Curve *portion(double f, double t) const override; + Curve *reverse() const override; + bool operator==(Curve const &c) const override; + bool isNear(Curve const &other, Coord precision) const override; + void feed(PathSink &sink, bool moveto_initial) const override; + int winding(Point const &p) const override; + +private: + void _updateCenterAndAngles(); + std::vector<ShapeIntersection> _filterIntersections(std::vector<ShapeIntersection> &&xs, bool is_first) const; + bool _validateIntersection(ShapeIntersection &xing, bool is_first) const; + std::vector<ShapeIntersection> _intersectSameEllipse(EllipticalArc const *other) const; + + Point _initial_point, _final_point; + Ellipse _ellipse; + AngleInterval _angles; + bool _large_arc; +}; // end class EllipticalArc + + +// implemented in elliptical-arc-from-sbasis.cpp +/** @brief Fit an elliptical arc to an SBasis fragment. + * @relates EllipticalArc */ +bool arc_from_sbasis(EllipticalArc &ea, D2<SBasis> const &in, + double tolerance = EPSILON, unsigned num_samples = 20); + +/** @brief Debug output for elliptical arcs. + * @relates EllipticalArc */ +std::ostream &operator<<(std::ostream &out, EllipticalArc const &ea); + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_ELLIPTICAL_ARC_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/exception.h b/include/2geom/exception.h new file mode 100644 index 0000000..b472aae --- /dev/null +++ b/include/2geom/exception.h @@ -0,0 +1,157 @@ +/** + * \file + * \brief Defines the different types of exceptions that 2geom can throw. + * + * There are two main exception classes: LogicalError and RangeError. + * Logical errors are 2geom faults/bugs; RangeErrors are 'user' faults, + * e.g. invalid arguments to lib2geom methods. + * This way, the 'user' can distinguish between groups of exceptions + * ('user' is the coder that uses lib2geom) + * + * Several macro's are defined for easily throwing exceptions + * (e.g. THROW_CONTINUITYERROR). + */ +/* Copyright 2007 Johan Engelen <goejendaagh@zonnet.nl> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_EXCEPTION_H +#define LIB2GEOM_SEEN_EXCEPTION_H + +#include <exception> +#include <sstream> +#include <string> + +namespace Geom { + +/** + * Base exception class, all 2geom exceptions should be derived from this one. + */ +class Exception : public std::exception { +public: + Exception(const char * message, const char *file, const int line) { + std::ostringstream os; + os << "lib2geom exception: " << message << " (" << file << ":" << line << ")"; + msgstr = os.str(); + } + + ~Exception() noexcept override {} // necessary to destroy the string object!!! + + const char* what() const noexcept override { + return msgstr.c_str(); + } +protected: + std::string msgstr; +}; +#define THROW_EXCEPTION(message) throw(Geom::Exception(message, __FILE__, __LINE__)) + +//----------------------------------------------------------------------- + +class LogicalError : public Exception { +public: + LogicalError(const char * message, const char *file, const int line) + : Exception(message, file, line) {} +}; +#define THROW_LOGICALERROR(message) throw(LogicalError(message, __FILE__, __LINE__)) + +class RangeError : public Exception { +public: + RangeError(const char * message, const char *file, const int line) + : Exception(message, file, line) {} +}; +#define THROW_RANGEERROR(message) throw(RangeError(message, __FILE__, __LINE__)) + +//----------------------------------------------------------------------- +// Special case exceptions. Best used with the defines :) + +class NotImplemented : public LogicalError { +public: + NotImplemented(const char *file, const int line) + : LogicalError("Method not implemented", file, line) {} +}; +#define THROW_NOTIMPLEMENTED(i) throw(NotImplemented(__FILE__, __LINE__)) + +class InvariantsViolation : public LogicalError { +public: + InvariantsViolation(const char *file, const int line) + : LogicalError("Invariants violation", file, line) {} +}; +#define THROW_INVARIANTSVIOLATION(i) throw(InvariantsViolation(__FILE__, __LINE__)) +#define ASSERT_INVARIANTS(e) ((e) ? (void)0 : THROW_INVARIANTSVIOLATION()) + +class NotInvertible : public RangeError { +public: + NotInvertible(const char *file, const int line) + : RangeError("Function does not have a unique inverse", file, line) {} +}; +#define THROW_NOTINVERTIBLE(i) throw(NotInvertible(__FILE__, __LINE__)) + +class InfiniteSolutions : public RangeError { +public: + InfiniteSolutions(const char *file, const int line) + : RangeError("There are infinite solutions", file, line) {} +}; +#define THROW_INFINITESOLUTIONS(i) throw(InfiniteSolutions(__FILE__, __LINE__)) + +class InfinitelyManySolutions : public RangeError { +private: + char const *const _message; +public: + InfinitelyManySolutions(const char *file, const int line, char const *message) + : RangeError("There are infinitely many solutions", file, line) + , _message{message} + {} + char const *what() const noexcept override { return _message; } +}; +#define THROW_INFINITELY_MANY_SOLUTIONS(msg) throw(InfinitelyManySolutions(__FILE__, __LINE__, msg)) + +class ContinuityError : public RangeError { +public: + ContinuityError(const char *file, const int line) + : RangeError("Non-contiguous path", file, line) {} +}; +#define THROW_CONTINUITYERROR(i) throw(ContinuityError(__FILE__, __LINE__)) + +struct SVGPathParseError : public std::exception { + char const *what() const noexcept override { return "parse error"; } +}; + + +} // namespace Geom + +#endif + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/forward.h b/include/2geom/forward.h new file mode 100644 index 0000000..2790924 --- /dev/null +++ b/include/2geom/forward.h @@ -0,0 +1,127 @@ +/** + * \file + * \brief Contains forward declarations of 2geom types + *//* + * Authors: + * Johan Engelen <goejendaagh@zonnet.nl> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright (C) 2008-2010 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_FORWARD_H +#define LIB2GEOM_SEEN_FORWARD_H + +namespace Geom { + +// primitives +typedef double Coord; +typedef int IntCoord; +class Point; +class IntPoint; +class Line; +class Ray; +template <typename> class GenericInterval; +template <typename> class GenericOptInterval; +class Interval; +class OptInterval; +typedef GenericInterval<IntCoord> IntInterval; +typedef GenericOptInterval<IntCoord> OptIntInterval; +template <typename> class GenericRect; +template <typename> class GenericOptRect; +class Rect; +class OptRect; +typedef GenericRect<IntCoord> IntRect; +typedef GenericOptRect<IntCoord> OptIntRect; + +// fragments +class Linear; +class Bezier; +class SBasis; +class Poly; + +// shapes +class Circle; +class Ellipse; +class ConvexHull; + +// curves +class Curve; +class SBasisCurve; +class BezierCurve; +template <unsigned degree> class BezierCurveN; +typedef BezierCurveN<1> LineSegment; +typedef BezierCurveN<2> QuadraticBezier; +typedef BezierCurveN<3> CubicBezier; +class EllipticalArc; + +// paths and path sequences +class Path; +class PathVector; +struct PathTime; +class PathInterval; +struct PathVectorTime; + +// errors +class Exception; +class LogicalError; +class RangeError; +class NotImplemented; +class InvariantsViolation; +class NotInvertible; +class ContinuityError; + +// transforms +class Affine; +class Translate; +class Rotate; +class Scale; +class HShear; +class VShear; +class Zoom; + +// templates +template <typename> class D2; +template <typename> class Piecewise; + +// misc +class SVGPathSink; +template <typename> class SVGPathGenerator; + +} + +#endif // SEEN_GEOM_FORWARD_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/generic-interval.h b/include/2geom/generic-interval.h new file mode 100644 index 0000000..1d3cfdb --- /dev/null +++ b/include/2geom/generic-interval.h @@ -0,0 +1,374 @@ +/** + * @file + * @brief Closed interval of generic values + *//* + * Copyright 2011 Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_GENERIC_INTERVAL_H +#define LIB2GEOM_SEEN_GENERIC_INTERVAL_H + +#include <cassert> +#include <iostream> +#include <optional> +#include <2geom/coord.h> + +namespace Geom { + +template <typename C> +class GenericOptInterval; + +/** + * @brief A range of numbers which is never empty. + * @ingroup Primitives + */ +template <typename C> +class GenericInterval + : CoordTraits<C>::IntervalOps +{ + typedef typename CoordTraits<C>::IntervalType CInterval; + typedef GenericInterval<C> Self; +protected: + C _b[2]; +public: + /// @name Create intervals. + /// @{ + /** @brief Create an interval that contains only zero. */ + GenericInterval() { _b[0] = 0; _b[1] = 0; } + /** @brief Create an interval that contains a single point. */ + explicit GenericInterval(C u) { _b[0] = _b[1] = u; } + /** @brief Create an interval that contains all points between @c u and @c v. */ + GenericInterval(C u, C v) { + if (u <= v) { + _b[0] = u; _b[1] = v; + } else { + _b[0] = v; _b[1] = u; + } + } + + /** @brief Create an interval containing a range of values. + * The resulting interval will contain all values from the given range. + * The return type of iterators must be convertible to C. The given range + * must not be empty. For potentially empty ranges, see GenericOptInterval. + * @param start Beginning of the range + * @param end End of the range + * @return Interval that contains all values from [start, end). */ + template <typename InputIterator> + static CInterval from_range(InputIterator start, InputIterator end) { + assert(start != end); + CInterval result(*start++); + for (; start != end; ++start) result.expandTo(*start); + return result; + } + /** @brief Create an interval from a C-style array of values it should contain. */ + static CInterval from_array(C const *c, unsigned n) { + CInterval result = from_range(c, c+n); + return result; + } + /// @} + + /// @name Inspect contained values. + /// @{ + C min() const { return _b[0]; } + C max() const { return _b[1]; } + C extent() const { return max() - min(); } + C middle() const { return (max() + min()) / 2; } + bool isSingular() const { return min() == max(); } + C operator[](unsigned i) const { assert(i < 2); return _b[i]; } + C clamp(C val) const { + if (val < min()) return min(); + if (val > max()) return max(); + return val; + } + /// Return the closer end of the interval. + C nearestEnd(C val) const { + C dmin = std::abs(val - min()), dmax = std::abs(val - max()); + return dmin <= dmax ? min() : max(); + } + /// @} + + /// @name Test coordinates and other intervals for inclusion. + /// @{ + /** @brief Check whether the interval includes this number. */ + bool contains(C val) const { + return min() <= val && val <= max(); + } + /** @brief Check whether the interval includes the given interval. */ + bool contains(CInterval const &val) const { + return min() <= val.min() && val.max() <= max(); + } + /** @brief Check whether the intervals have any common elements. */ + bool intersects(CInterval const &val) const { + return contains(val.min()) || contains(val.max()) || val.contains(*this); + } + /// @} + + /// @name Modify the interval. + /// @{ + //TODO: NaN handleage for the next two? + /** @brief Set the lower boundary of the interval. + * When the given number is larger than the interval's largest element, + * it will be reduced to the single number @c val. */ + void setMin(C val) { + if(val > _b[1]) { + _b[0] = _b[1] = val; + } else { + _b[0] = val; + } + } + /** @brief Set the upper boundary of the interval. + * When the given number is smaller than the interval's smallest element, + * it will be reduced to the single number @c val. */ + void setMax(C val) { + if(val < _b[0]) { + _b[1] = _b[0] = val; + } else { + _b[1] = val; + } + } + /// Set both ends of the interval simultaneously + void setEnds(C a, C b) { + if (a <= b) { + _b[0] = a; + _b[1] = b; + } else { + _b[0] = b; + _b[1] = a; + } + } + /** @brief Extend the interval to include the given number. */ + void expandTo(C val) { + if(val < _b[0]) _b[0] = val; + if(val > _b[1]) _b[1] = val; //no else, as we want to handle NaN + } + /** @brief Expand or shrink the interval in both directions by the given amount. + * After this method, the interval's length (extent) will be increased by + * <code>amount * 2</code>. Negative values can be given; they will shrink the interval. + * Shrinking by a value larger than half the interval's length will create a degenerate + * interval containing only the midpoint of the original. */ + void expandBy(C amount) { + _b[0] -= amount; + _b[1] += amount; + if (_b[0] > _b[1]) { + C halfway = (_b[0]+_b[1])/2; + _b[0] = _b[1] = halfway; + } + } + /** @brief Union the interval with another one. + * The resulting interval will contain all points of both intervals. + * It might also contain some points which didn't belong to either - this happens + * when the intervals did not have any common elements. */ + void unionWith(CInterval const &a) { + if(a._b[0] < _b[0]) _b[0] = a._b[0]; + if(a._b[1] > _b[1]) _b[1] = a._b[1]; + } + /// @} + + /// @name Operators + /// @{ + //IMPL: OffsetableConcept + //TODO: rename output_type to something else in the concept + typedef C output_type; + /** @brief Offset the interval by a specified amount */ + Self &operator+=(C amnt) { + _b[0] += amnt; _b[1] += amnt; + return *this; + } + /** @brief Offset the interval by the negation of the specified amount */ + Self &operator-=(C amnt) { + _b[0] -= amnt; _b[1] -= amnt; + return *this; + } + + /** @brief Return an interval mirrored about 0 */ + Self operator-() const { Self r(-_b[1], -_b[0]); return r; } + // IMPL: AddableConcept + /** @brief Add two intervals. + * Sum is defined as the set of points that can be obtained by adding any two values + * from both operands: \f$S = \{x \in A, y \in B: x + y\}\f$ */ + Self &operator+=(CInterval const &o) { + _b[0] += o._b[0]; + _b[1] += o._b[1]; + return *this; + } + /** @brief Subtract two intervals. + * Difference is defined as the set of points that can be obtained by subtracting + * any value from the second operand from any value from the first operand: + * \f$S = \{x \in A, y \in B: x - y\}\f$ */ + Self &operator-=(CInterval const &o) { + // equal to *this += -o + _b[0] -= o._b[1]; + _b[1] -= o._b[0]; + return *this; + } + /** @brief Union two intervals. + * Note that the intersection-and-assignment operator is not defined, + * because the result of an intersection can be empty, while Interval cannot. */ + Self &operator|=(CInterval const &o) { + unionWith(o); + return *this; + } + /** @brief Test for interval equality. */ + bool operator==(CInterval const &other) const { + return min() == other.min() && max() == other.max(); + } + /// @} +}; + +/** @brief Union two intervals + * @relates GenericInterval */ +template <typename C> +inline GenericInterval<C> unify(GenericInterval<C> const &a, GenericInterval<C> const &b) { + return a | b; +} + +/** + * @brief A range of numbers that can be empty. + * @ingroup Primitives + */ +template <typename C> +class GenericOptInterval + : public std::optional<typename CoordTraits<C>::IntervalType> + , boost::orable< GenericOptInterval<C> + , boost::andable< GenericOptInterval<C> + > > +{ + typedef typename CoordTraits<C>::IntervalType CInterval; + typedef typename CoordTraits<C>::OptIntervalType OptCInterval; + typedef std::optional<CInterval> Base; +public: + /// @name Create optionally empty intervals. + /// @{ + /** @brief Create an empty interval. */ + GenericOptInterval() : Base() {} + /** @brief Wrap an existing interval. */ + GenericOptInterval(GenericInterval<C> const &a) : Base(CInterval(a)) {} + /** @brief Create an interval containing a single point. */ + GenericOptInterval(C u) : Base(CInterval(u)) {} + /** @brief Create an interval containing a range of numbers. */ + GenericOptInterval(C u, C v) : Base(CInterval(u,v)) {} + + /** @brief Create a possibly empty interval containing a range of values. + * The resulting interval will contain all values from the given range. + * The return type of iterators must be convertible to C. The given range + * may be empty. + * @param start Beginning of the range + * @param end End of the range + * @return Interval that contains all values from [start, end), or nothing if the range + * is empty. */ + template <typename InputIterator> + static GenericOptInterval<C> from_range(InputIterator start, InputIterator end) { + if (start == end) { + GenericOptInterval<C> ret; + return ret; + } + GenericOptInterval<C> ret(CInterval::from_range(start, end)); + return ret; + } + /// @} + + /** @brief Check whether this interval is empty. */ + bool empty() { return !*this; } + + /** @brief Union with another interval, gracefully handling empty ones. */ + void unionWith(GenericOptInterval<C> const &a) { + if (a) { + if (*this) { // check that we are not empty + (*this)->unionWith(*a); + } else { + *this = *a; + } + } + } + void intersectWith(GenericOptInterval<C> const &o) { + if (o && *this) { + if (!*this) return; + C u = std::max((*this)->min(), o->min()); + C v = std::min((*this)->max(), o->max()); + if (u <= v) { + *this = CInterval(u, v); + return; + } + } + (*static_cast<Base*>(this)) = std::nullopt; + } + GenericOptInterval<C> &operator|=(OptCInterval const &o) { + unionWith(o); + return *this; + } + GenericOptInterval<C> &operator&=(OptCInterval const &o) { + intersectWith(o); + return *this; + } + + // The equality operators inherited from std::optional don't work with derived types, because + // the template overload ignores that the devived type is also an optional. It would result in + // `GenericInterval() != GenericInterval()` being true. + template <typename U, typename = std::enable_if_t<std::is_base_of_v<Base, U>>> + bool operator==(U const &other) const + { + return static_cast<Base const &>(*this) == static_cast<Base const &>(other); + } + template <typename U, typename = std::enable_if_t<std::is_base_of_v<Base, U>>> + bool operator!=(U const &other) const + { + return static_cast<Base const &>(*this) != static_cast<Base const &>(other); + } +}; + +/** @brief Intersect two intervals and return a possibly empty range of numbers + * @relates GenericOptInterval */ +template <typename C> +inline GenericOptInterval<C> intersect(GenericInterval<C> const &a, GenericInterval<C> const &b) { + return GenericOptInterval<C>(a) & GenericOptInterval<C>(b); +} +/** @brief Intersect two intervals and return a possibly empty range of numbers + * @relates GenericOptInterval */ +template <typename C> +inline GenericOptInterval<C> operator&(GenericInterval<C> const &a, GenericInterval<C> const &b) { + return GenericOptInterval<C>(a) & GenericOptInterval<C>(b); +} + +template <typename C> +inline std::ostream &operator<< (std::ostream &os, + Geom::GenericInterval<C> const &I) { + os << "Interval("<<I.min() << ", "<<I.max() << ")"; + return os; +} + +} // namespace Geom +#endif // !LIB2GEOM_SEEN_GENERIC_INTERVAL_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/generic-rect.h b/include/2geom/generic-rect.h new file mode 100644 index 0000000..4524d43 --- /dev/null +++ b/include/2geom/generic-rect.h @@ -0,0 +1,547 @@ +/** + * \file + * \brief Axis-aligned rectangle + *//* + * Authors: + * Michael Sloan <mgsloan@gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Copyright 2007-2011 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, output to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * Authors of original rect class: + * Lauris Kaplinski <lauris@kaplinski.com> + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * bulia byak <buliabyak@users.sf.net> + * MenTaLguY <mental@rydia.net> + */ + +#ifndef LIB2GEOM_SEEN_GENERIC_RECT_H +#define LIB2GEOM_SEEN_GENERIC_RECT_H + +#include <limits> +#include <iostream> +#include <optional> +#include <2geom/coord.h> + +namespace Geom { + +template <typename C> +class GenericOptRect; + +/** + * @brief Axis aligned, non-empty, generic rectangle. + * @ingroup Primitives + */ +template <typename C> +class GenericRect + : CoordTraits<C>::RectOps +{ + typedef typename CoordTraits<C>::IntervalType CInterval; + typedef typename CoordTraits<C>::PointType CPoint; + typedef typename CoordTraits<C>::RectType CRect; + typedef typename CoordTraits<C>::OptRectType OptCRect; +protected: + CInterval f[2]; +public: + typedef CInterval D1Value; + typedef CInterval &D1Reference; + typedef CInterval const &D1ConstReference; + + /// @name Create rectangles. + /// @{ + /** @brief Create a rectangle that contains only the point at (0,0). */ + GenericRect() { f[X] = f[Y] = CInterval(); } + /** @brief Create a rectangle from X and Y intervals. */ + GenericRect(CInterval const &a, CInterval const &b) { + f[X] = a; + f[Y] = b; + } + /** @brief Create a rectangle from two points. */ + GenericRect(CPoint const &a, CPoint const &b) { + f[X] = CInterval(a[X], b[X]); + f[Y] = CInterval(a[Y], b[Y]); + } + /** @brief Create rectangle from coordinates of two points. */ + GenericRect(C x0, C y0, C x1, C y1) { + f[X] = CInterval(x0, x1); + f[Y] = CInterval(y0, y1); + } + /** @brief Create a rectangle from a range of points. + * The resulting rectangle will contain all points from the range. + * The return type of iterators must be convertible to Point. + * The range must not be empty. For possibly empty ranges, see OptRect. + * @param start Beginning of the range + * @param end End of the range + * @return Rectangle that contains all points from [start, end). */ + template <typename InputIterator> + static CRect from_range(InputIterator start, InputIterator end) { + assert(start != end); + CPoint p1 = *start++; + CRect result(p1, p1); + for (; start != end; ++start) { + result.expandTo(*start); + } + return result; + } + /** @brief Create a rectangle from a C-style array of points it should contain. */ + static CRect from_array(CPoint const *c, unsigned n) { + CRect result = GenericRect<C>::from_range(c, c+n); + return result; + } + /** @brief Create rectangle from origin and dimensions. */ + static CRect from_xywh(C x, C y, C w, C h) { + CPoint xy(x, y); + CPoint wh(w, h); + CRect result(xy, xy + wh); + return result; + } + /** @brief Create rectangle from origin and dimensions. */ + static CRect from_xywh(CPoint const &xy, CPoint const &wh) { + CRect result(xy, xy + wh); + return result; + } + /// Create infinite rectangle. + static CRect infinite() { + CPoint p0(std::numeric_limits<C>::min(), std::numeric_limits<C>::min()); + CPoint p1(std::numeric_limits<C>::max(), std::numeric_limits<C>::max()); + CRect result(p0, p1); + return result; + } + /// @} + + /// @name Inspect dimensions. + /// @{ + CInterval &operator[](unsigned i) { return f[i]; } + CInterval const &operator[](unsigned i) const { return f[i]; } + CInterval &operator[](Dim2 d) { return f[d]; } + CInterval const &operator[](Dim2 d) const { return f[d]; } + + /** @brief Get the corner of the rectangle with smallest coordinate values. + * In 2Geom standard coordinate system, this means upper left. */ + CPoint min() const { CPoint p(f[X].min(), f[Y].min()); return p; } + /** @brief Get the corner of the rectangle with largest coordinate values. + * In 2Geom standard coordinate system, this means lower right. */ + CPoint max() const { CPoint p(f[X].max(), f[Y].max()); return p; } + /** @brief Return the n-th corner of the rectangle. + * Returns corners in the direction of growing angles, starting from + * the one given by min(). For the standard coordinate system used + * in 2Geom (+Y downwards), this means clockwise starting from + * the upper left. */ + CPoint corner(unsigned i) const { + switch(i % 4) { + case 0: return CPoint(f[X].min(), f[Y].min()); + case 1: return CPoint(f[X].max(), f[Y].min()); + case 2: return CPoint(f[X].max(), f[Y].max()); + default: return CPoint(f[X].min(), f[Y].max()); + } + } + + //We should probably remove these - they're coord sys gnostic + /** @brief Return top coordinate of the rectangle (+Y is downwards). */ + C top() const { return f[Y].min(); } + /** @brief Return bottom coordinate of the rectangle (+Y is downwards). */ + C bottom() const { return f[Y].max(); } + /** @brief Return leftmost coordinate of the rectangle (+X is to the right). */ + C left() const { return f[X].min(); } + /** @brief Return rightmost coordinate of the rectangle (+X is to the right). */ + C right() const { return f[X].max(); } + + /** @brief Get the horizontal extent of the rectangle. */ + C width() const { return f[X].extent(); } + /** @brief Get the vertical extent of the rectangle. */ + C height() const { return f[Y].extent(); } + /** @brief Get the ratio of width to height of the rectangle. */ + Coord aspectRatio() const { return Coord(width()) / Coord(height()); } + + /** @brief Get rectangle's width and height as a point. + * @return Point with X coordinate corresponding to the width and the Y coordinate + * corresponding to the height of the rectangle. */ + CPoint dimensions() const { return CPoint(f[X].extent(), f[Y].extent()); } + /** @brief Get the point in the geometric center of the rectangle. */ + CPoint midpoint() const { return CPoint(f[X].middle(), f[Y].middle()); } + + /** @brief Compute rectangle's area. */ + C area() const { return f[X].extent() * f[Y].extent(); } + /** @brief Check whether the rectangle has zero area. */ + bool hasZeroArea() const { return f[X].isSingular() || f[Y].isSingular(); } + + /** @brief Get the larger extent (width or height) of the rectangle. */ + C maxExtent() const { return std::max(f[X].extent(), f[Y].extent()); } + /** @brief Get the smaller extent (width or height) of the rectangle. */ + C minExtent() const { return std::min(f[X].extent(), f[Y].extent()); } + + /** @brief Get rectangle's distance SQUARED away from the given point **/ + C distanceSq(const CPoint pt) const { + auto v = clamp(pt) - pt; + return v.x() * v.x() + v.y() * v.y(); + } + + /** @brief Clamp point to the rectangle. */ + CPoint clamp(CPoint const &p) const { + CPoint result(f[X].clamp(p[X]), f[Y].clamp(p[Y])); + return result; + } + /** @brief Get the nearest point on the edge of the rectangle. */ + CPoint nearestEdgePoint(CPoint const &p) const { + CPoint result = p; + if (!contains(p)) { + result = clamp(p); + } else { + C cx = f[X].nearestEnd(p[X]); + C cy = f[Y].nearestEnd(p[Y]); + if (std::abs(cx - p[X]) <= std::abs(cy - p[Y])) { + result[X] = cx; + } else { + result[Y] = cy; + } + } + return result; + } + /// @} + + /// @name Test other rectangles and points for inclusion. + /// @{ + /** @brief Check whether the rectangles have any common points. */ + bool intersects(GenericRect<C> const &r) const { + return f[X].intersects(r[X]) && f[Y].intersects(r[Y]); + } + /** @brief Check whether the rectangle includes all points in the given rectangle. */ + bool contains(GenericRect<C> const &r) const { + return f[X].contains(r[X]) && f[Y].contains(r[Y]); + } + + /** @brief Check whether the rectangles have any common points. + * Empty rectangles will not intersect with any other rectangle. */ + inline bool intersects(OptCRect const &r) const; + /** @brief Check whether the rectangle includes all points in the given rectangle. + * Empty rectangles will be contained in any non-empty rectangle. */ + inline bool contains(OptCRect const &r) const; + + /** @brief Check whether the given point is within the rectangle. */ + bool contains(CPoint const &p) const { + return f[X].contains(p[X]) && f[Y].contains(p[Y]); + } + /// @} + + /// @name Modify the rectangle. + /// @{ + /** @brief Set the minimum X coordinate of the rectangle. */ + void setLeft(C val) { + f[X].setMin(val); + } + /** @brief Set the maximum X coordinate of the rectangle. */ + void setRight(C val) { + f[X].setMax(val); + } + /** @brief Set the minimum Y coordinate of the rectangle. */ + void setTop(C val) { + f[Y].setMin(val); + } + /** @brief Set the maximum Y coordinate of the rectangle. */ + void setBottom(C val) { + f[Y].setMax(val); + } + /** @brief Set the upper left point of the rectangle. */ + void setMin(CPoint const &p) { + f[X].setMin(p[X]); + f[Y].setMin(p[Y]); + } + /** @brief Set the lower right point of the rectangle. */ + void setMax(CPoint const &p) { + f[X].setMax(p[X]); + f[Y].setMax(p[Y]); + } + /** @brief Enlarge the rectangle to contain the given point. */ + void expandTo(CPoint const &p) { + f[X].expandTo(p[X]); f[Y].expandTo(p[Y]); + } + /** @brief Enlarge the rectangle to contain the argument. */ + void unionWith(CRect const &b) { + f[X].unionWith(b[X]); f[Y].unionWith(b[Y]); + } + /** @brief Enlarge the rectangle to contain the argument. + * Unioning with an empty rectangle results in no changes. */ + void unionWith(OptCRect const &b); + + /** @brief Expand the rectangle in both directions by the specified amount. + * Note that this is different from scaling. Negative values will shrink the + * rectangle. If <code>-amount</code> is larger than + * half of the width, the X interval will contain only the X coordinate + * of the midpoint; same for height. */ + void expandBy(C amount) { + expandBy(amount, amount); + } + /** @brief Expand the rectangle in both directions. + * Note that this is different from scaling. Negative values will shrink the + * rectangle. If <code>-x</code> is larger than + * half of the width, the X interval will contain only the X coordinate + * of the midpoint; same for height. */ + void expandBy(C x, C y) { + f[X].expandBy(x); f[Y].expandBy(y); + } + /** @brief Expand the rectangle by the coordinates of the given point. + * This will expand the width by the X coordinate of the point in both directions + * and the height by Y coordinate of the point. Negative coordinate values will + * shrink the rectangle. If <code>-p[X]</code> is larger than half of the width, + * the X interval will contain only the X coordinate of the midpoint; + * same for height. */ + void expandBy(CPoint const &p) { + expandBy(p[X], p[Y]); + } + /// @} + + /// @name Operators + /// @{ + /** @brief Offset the rectangle by a vector. */ + GenericRect<C> &operator+=(CPoint const &p) { + f[X] += p[X]; + f[Y] += p[Y]; + return *this; + } + /** @brief Offset the rectangle by the negation of a vector. */ + GenericRect<C> &operator-=(CPoint const &p) { + f[X] -= p[X]; + f[Y] -= p[Y]; + return *this; + } + /** @brief Union two rectangles. */ + GenericRect<C> &operator|=(CRect const &o) { + unionWith(o); + return *this; + } + GenericRect<C> &operator|=(OptCRect const &o) { + unionWith(o); + return *this; + } + /** @brief Test for equality of rectangles. */ + bool operator==(CRect const &o) const { return f[X] == o[X] && f[Y] == o[Y]; } + /// @} +}; + +/** + * @brief Axis-aligned generic rectangle that can be empty. + * @ingroup Primitives + */ +template <typename C> +class GenericOptRect + : public std::optional<typename CoordTraits<C>::RectType> + , boost::equality_comparable< typename CoordTraits<C>::OptRectType + , boost::equality_comparable< typename CoordTraits<C>::OptRectType, typename CoordTraits<C>::RectType + , boost::orable< typename CoordTraits<C>::OptRectType + , boost::andable< typename CoordTraits<C>::OptRectType + , boost::andable< typename CoordTraits<C>::OptRectType, typename CoordTraits<C>::RectType + > > > > > +{ + typedef typename CoordTraits<C>::IntervalType CInterval; + typedef typename CoordTraits<C>::OptIntervalType OptCInterval; + typedef typename CoordTraits<C>::PointType CPoint; + typedef typename CoordTraits<C>::RectType CRect; + typedef typename CoordTraits<C>::OptRectType OptCRect; + typedef std::optional<CRect> Base; +public: + typedef CInterval D1Value; + typedef CInterval &D1Reference; + typedef CInterval const &D1ConstReference; + + /// @name Create potentially empty rectangles. + /// @{ + GenericOptRect() : Base() {} + GenericOptRect(GenericRect<C> const &a) : Base(CRect(a)) {} + GenericOptRect(CPoint const &a, CPoint const &b) : Base(CRect(a, b)) {} + GenericOptRect(C x0, C y0, C x1, C y1) : Base(CRect(x0, y0, x1, y1)) {} + /// Creates an empty OptRect when one of the argument intervals is empty. + GenericOptRect(OptCInterval const &x_int, OptCInterval const &y_int) { + if (x_int && y_int) { + *this = CRect(*x_int, *y_int); + } + // else, stay empty. + } + + /** @brief Create a rectangle from a range of points. + * The resulting rectangle will contain all points from the range. + * If the range contains no points, the result will be an empty rectangle. + * The return type of iterators must be convertible to the corresponding + * point type (Point or IntPoint). + * @param start Beginning of the range + * @param end End of the range + * @return Rectangle that contains all points from [start, end). */ + template <typename InputIterator> + static OptCRect from_range(InputIterator start, InputIterator end) { + OptCRect result; + for (; start != end; ++start) { + result.expandTo(*start); + } + return result; + } + /// @} + + /// @name Check other rectangles and points for inclusion. + /// @{ + /** @brief Check for emptiness. */ + inline bool empty() const { return !*this; }; + /** @brief Check whether the rectangles have any common points. + * Empty rectangles will not intersect with any other rectangle. */ + bool intersects(CRect const &r) const { return r.intersects(*this); } + /** @brief Check whether the rectangle includes all points in the given rectangle. + * Empty rectangles will be contained in any non-empty rectangle. */ + bool contains(CRect const &r) const { return *this && (*this)->contains(r); } + + /** @brief Check whether the rectangles have any common points. + * Empty rectangles will not intersect with any other rectangle. + * Two empty rectangles will not intersect each other. */ + bool intersects(OptCRect const &r) const { return *this && (*this)->intersects(r); } + /** @brief Check whether the rectangle includes all points in the given rectangle. + * Empty rectangles will be contained in any non-empty rectangle. + * An empty rectangle will not contain other empty rectangles. */ + bool contains(OptCRect const &r) const { return *this && (*this)->contains(r); } + + /** @brief Check whether the given point is within the rectangle. + * An empty rectangle will not contain any points. */ + bool contains(CPoint const &p) const { return *this && (*this)->contains(p); } + /// @} + + /** @brief Returns an empty optional (testing false) if the rectangle has zero area. */ + OptCRect regularized() const { + return *this && !(*this)->hasZeroArea() ? *this : OptCRect(); + } + + /// @name Modify the potentially empty rectangle. + /// @{ + /** @brief Enlarge the rectangle to contain the argument. + * If this rectangle is empty, after callng this method it will + * be equal to the argument. */ + void unionWith(CRect const &b) { + if (*this) { + (*this)->unionWith(b); + } else { + *this = b; + } + } + /** @brief Enlarge the rectangle to contain the argument. + * Unioning with an empty rectangle results in no changes. + * If this rectangle is empty, after calling this method it will + * be equal to the argument. */ + void unionWith(OptCRect const &b) { + if (b) unionWith(*b); + } + /** @brief Leave only the area overlapping with the argument. + * If the rectangles do not have any points in common, after calling + * this method the rectangle will be empty. */ + void intersectWith(CRect const &b) { + if (!*this) return; + OptCInterval x = (**this)[X] & b[X], y = (**this)[Y] & b[Y]; + if (x && y) { + *this = CRect(*x, *y); + } else { + *(static_cast<Base*>(this)) = std::nullopt; + } + } + /** @brief Leave only the area overlapping with the argument. + * If the argument is empty or the rectangles do not have any points + * in common, after calling this method the rectangle will be empty. */ + void intersectWith(OptCRect const &b) { + if (b) { + intersectWith(*b); + } else { + *(static_cast<Base*>(this)) = std::nullopt; + } + } + /** @brief Create or enlarge the rectangle to contain the given point. + * If the rectangle is empty, after calling this method it will be non-empty + * and it will contain only the given point. */ + void expandTo(CPoint const &p) { + if (*this) { + (*this)->expandTo(p); + } else { + *this = CRect(p, p); + } + } + /// @} + + /// @name Operators + /// @{ + /** @brief Union with @a b */ + GenericOptRect<C> &operator|=(OptCRect const &b) { + unionWith(b); + return *this; + } + /** @brief Intersect with @a b */ + GenericOptRect<C> &operator&=(CRect const &b) { + intersectWith(b); + return *this; + } + /** @brief Intersect with @a b */ + GenericOptRect<C> &operator&=(OptCRect const &b) { + intersectWith(b); + return *this; + } + /** @brief Test for equality. + * All empty rectangles are equal. */ + bool operator==(OptCRect const &other) const { + if (!*this != !other) return false; + return *this ? (**this == *other) : true; + } + bool operator==(CRect const &other) const { + if (!*this) return false; + return **this == other; + } + /// @} +}; + +template <typename C> +inline void GenericRect<C>::unionWith(OptCRect const &b) { + if (b) { + unionWith(*b); + } +} +template <typename C> +inline bool GenericRect<C>::intersects(OptCRect const &r) const { + return r && intersects(*r); +} +template <typename C> +inline bool GenericRect<C>::contains(OptCRect const &r) const { + return !r || contains(*r); +} + +template <typename C> +inline std::ostream &operator<<(std::ostream &out, GenericRect<C> const &r) { + out << "Rect " << r[X] << " x " << r[Y]; + return out; +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_RECT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/geom.h b/include/2geom/geom.h new file mode 100644 index 0000000..7393ff4 --- /dev/null +++ b/include/2geom/geom.h @@ -0,0 +1,66 @@ +/** + * \file + * \brief Various geometrical calculations + * + * Authors: + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * + * Copyright (C) 1999-2002 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ +#ifndef LIB2GEOM_SEEN_GEOM_H +#define LIB2GEOM_SEEN_GEOM_H + +//TODO: move somewhere else + +#include <vector> +#include <2geom/forward.h> +#include <optional> +#include <2geom/bezier-curve.h> +#include <2geom/line.h> + +namespace Geom { + +std::optional<Geom::LineSegment> +rect_line_intersect(Geom::Rect &r, + Geom::LineSegment ls); + +int centroid(std::vector<Geom::Point> const &p, Geom::Point& centroid, double &area); + +} + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/int-interval.h b/include/2geom/int-interval.h new file mode 100644 index 0000000..0faf48d --- /dev/null +++ b/include/2geom/int-interval.h @@ -0,0 +1,63 @@ +/** + * \file + * \brief Closed interval of integer values + *//* + * Copyright 2011 Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_INT_INTERVAL_H +#define LIB2GEOM_SEEN_INT_INTERVAL_H + +#include <2geom/coord.h> +#include <2geom/generic-interval.h> + +namespace Geom { + +/** + * @brief Range of integers that is never empty. + * @ingroup Primitives + */ +typedef GenericInterval<IntCoord> IntInterval; + +/** + * @brief Range of integers that can be empty. + * @ingroup Primitives + */ +typedef GenericOptInterval<IntCoord> OptIntInterval; + +} // namespace Geom +#endif // !LIB2GEOM_SEEN_INT_INTERVAL_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/int-point.h b/include/2geom/int-point.h new file mode 100644 index 0000000..6dbed11 --- /dev/null +++ b/include/2geom/int-point.h @@ -0,0 +1,202 @@ +/** + * \file + * \brief Cartesian point / 2D vector with integer coordinates + *//* + * Copyright 2011 Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_INT_POINT_H +#define LIB2GEOM_SEEN_INT_POINT_H + +#include <stdexcept> +#include <boost/operators.hpp> +#include <2geom/coord.h> + +namespace Geom { + +/** + * @brief Two-dimensional point with integer coordinates. + * + * This class is an exact equivalent of Point, except it stores integer coordinates. + * Integer points are useful in contexts related to rasterized graphics, for example + * for bounding boxes when rendering SVG. + * + * @see Point + * @ingroup Primitives */ +class IntPoint + : boost::additive< IntPoint + , boost::totally_ordered< IntPoint + , boost::multiplicative< IntPoint, IntCoord + , boost::multiplicative< IntPoint + > > > > +{ + IntCoord _pt[2] = { 0, 0 }; +public: + /// @name Create integer points + /// @{ + /** Construct a point at the origin. */ + IntPoint() = default; + /** Construct a point from its coordinates. */ + IntPoint(IntCoord x, IntCoord y) + : _pt{ x, y } + {} + /// @} + + /// @name Access the coordinates of a point + /// @{ + IntCoord operator[](unsigned i) const { + if ( i > Y ) throw std::out_of_range("index out of range"); + return _pt[i]; + } + IntCoord &operator[](unsigned i) { + if ( i > Y ) throw std::out_of_range("index out of range"); + return _pt[i]; + } + IntCoord operator[](Dim2 d) const { return _pt[d]; } + IntCoord &operator[](Dim2 d) { return _pt[d]; } + + IntCoord x() const noexcept { return _pt[X]; } + IntCoord &x() noexcept { return _pt[X]; } + IntCoord y() const noexcept { return _pt[Y]; } + IntCoord &y() noexcept { return _pt[Y]; } + /// @} + + /// @name Vector-like arithmetic operations + /// @{ + IntPoint operator-() const { + return IntPoint(-_pt[X], -_pt[Y]); + } + IntPoint &operator+=(IntPoint const &o) { + _pt[X] += o._pt[X]; + _pt[Y] += o._pt[Y]; + return *this; + } + IntPoint &operator-=(IntPoint const &o) { + _pt[X] -= o._pt[X]; + _pt[Y] -= o._pt[Y]; + return *this; + } + IntPoint &operator*=(IntPoint const &o) { + _pt[X] *= o._pt[X]; + _pt[Y] *= o._pt[Y]; + return *this; + } + IntPoint &operator*=(IntCoord o) { + _pt[X] *= o; + _pt[Y] *= o; + return *this; + } + IntPoint &operator/=(IntPoint const &o) { + _pt[X] /= o._pt[X]; + _pt[Y] /= o._pt[Y]; + return *this; + } + IntPoint &operator/=(IntCoord o) { + _pt[X] /= o; + _pt[Y] /= o; + return *this; + } + /// @} + + /// @name Various utilities + /// @{ + /** @brief Equality operator. */ + bool operator==(IntPoint const &in_pnt) const { + return ((_pt[X] == in_pnt[X]) && (_pt[Y] == in_pnt[Y])); + } + /** @brief Lexicographical ordering for points. + * Y coordinate is regarded as more significant. When sorting according to this + * ordering, the points will be sorted according to the Y coordinate, and within + * points with the same Y coordinate according to the X coordinate. */ + bool operator<(IntPoint const &p) const { + return ( ( _pt[Y] < p[Y] ) || + (( _pt[Y] == p[Y] ) && ( _pt[X] < p[X] ))); + } + /// @} + + /** @brief Lexicographical ordering functor. + * @param d The more significant dimension */ + template <Dim2 d> struct LexLess; + /** @brief Lexicographical ordering functor. + * @param d The more significant dimension */ + template <Dim2 d> struct LexGreater; + /** @brief Lexicographical ordering functor with runtime dimension. */ + struct LexLessRt { + LexLessRt(Dim2 d) : dim(d) {} + inline bool operator()(IntPoint const &a, IntPoint const &b) const; + private: + Dim2 dim; + }; + /** @brief Lexicographical ordering functor with runtime dimension. */ + struct LexGreaterRt { + LexGreaterRt(Dim2 d) : dim(d) {} + inline bool operator()(IntPoint const &a, IntPoint const &b) const; + private: + Dim2 dim; + }; +}; + +template<> struct IntPoint::LexLess<X> { + bool operator()(IntPoint const &a, IntPoint const &b) const { + return a[X] < b[X] || (a[X] == b[X] && a[Y] < b[Y]); + } +}; +template<> struct IntPoint::LexLess<Y> { + bool operator()(IntPoint const &a, IntPoint const &b) const { + return a[Y] < b[Y] || (a[Y] == b[Y] && a[X] < b[X]); + } +}; +template<> struct IntPoint::LexGreater<X> { + bool operator()(IntPoint const &a, IntPoint const &b) const { + return a[X] > b[X] || (a[X] == b[X] && a[Y] > b[Y]); + } +}; +template<> struct IntPoint::LexGreater<Y> { + bool operator()(IntPoint const &a, IntPoint const &b) const { + return a[Y] > b[Y] || (a[Y] == b[Y] && a[X] > b[X]); + } +}; +inline bool IntPoint::LexLessRt::operator()(IntPoint const &a, IntPoint const &b) const { + return dim ? IntPoint::LexLess<Y>()(a, b) : IntPoint::LexLess<X>()(a, b); +} +inline bool IntPoint::LexGreaterRt::operator()(IntPoint const &a, IntPoint const &b) const { + return dim ? IntPoint::LexGreater<Y>()(a, b) : IntPoint::LexGreater<X>()(a, b); +} + +} // namespace Geom + +#endif // !SEEN_GEOM_INT_POINT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/int-rect.h b/include/2geom/int-rect.h new file mode 100644 index 0000000..567d42d --- /dev/null +++ b/include/2geom/int-rect.h @@ -0,0 +1,75 @@ +/** + * \file + * \brief Axis-aligned rectangle with integer coordinates + *//* + * Copyright 2011 Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_INT_RECT_H +#define LIB2GEOM_SEEN_INT_RECT_H + +#include <2geom/coord.h> +#include <2geom/int-interval.h> +#include <2geom/generic-rect.h> + +namespace Geom { + +typedef GenericRect<IntCoord> IntRect; +typedef GenericOptRect<IntCoord> OptIntRect; + +// the functions below do not work when defined generically +inline OptIntRect operator&(IntRect const &a, IntRect const &b) { + OptIntRect ret(a); + ret.intersectWith(b); + return ret; +} +inline OptIntRect intersect(IntRect const &a, IntRect const &b) { + return a & b; +} +inline OptIntRect intersect(OptIntRect const &a, OptIntRect const &b) { + return a & b; +} +inline IntRect unify(IntRect const &a, IntRect const &b) { + return a | b; +} +inline OptIntRect unify(OptIntRect const &a, OptIntRect const &b) { + return a | b; +} + +} // end namespace Geom + +#endif // !LIB2GEOM_SEEN_INT_RECT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/intersection-graph.h b/include/2geom/intersection-graph.h new file mode 100644 index 0000000..940c43c --- /dev/null +++ b/include/2geom/intersection-graph.h @@ -0,0 +1,259 @@ +/** + * \file + * \brief Path intersection graph + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef SEEN_LIB2GEOM_INTERSECTION_GRAPH_H +#define SEEN_LIB2GEOM_INTERSECTION_GRAPH_H + +#include <set> +#include <vector> +#include <boost/ptr_container/ptr_vector.hpp> +#include <boost/intrusive/list.hpp> +#include <2geom/forward.h> +#include <2geom/pathvector.h> + +namespace Geom { + +/** @class PathIntersectionGraph + * @brief Intermediate data for computing Boolean operations on paths. + * + * This class implements the Greiner-Hormann clipping algorithm, + * with improvements inspired by Foster and Overfelt as well as some + * original contributions. + * + * For the purposes of boolean operations, a shape is defined as a PathVector + * using the "even-odd" rule, i.e., regions with odd winding are considered part + * of the shape, whereas regions with even winding are not. + * + * For this reason, the two path-vectors are sometimes called "shapes" or "operands" of + * the boolean operation. Each path-vector may contain several paths, which are called + * either "paths" or "components" in the documentation. + * + * @ingroup Paths + */ +class PathIntersectionGraph +{ + // this is called PathIntersectionGraph so that we can also have a class for polygons, + // e.g. PolygonIntersectionGraph, which is going to be significantly faster +public: + /** @brief Construct a path intersection graph for two shapes described via their boundaries. + * The boundaries are passed as path-vectors. + * + * @param a – the first operand, also referred to as operand A. + * @param b – the second operand, also referred to as operand B. + * @param precision – precision setting used for intersection calculations. + */ + PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision = EPSILON); + + /** + * @brief Get the union of the shapes, A ∪ B. + * + * A point belongs to the union if and only if it belongs to at least one of the operands. + * + * @return A path-vector describing the union of the operands A and B. + */ + PathVector getUnion(); + + /** + * @brief Get the intersection of the shapes, A ∩ B. + * + * A point belongs to the intersection if and only if it belongs to both shapes. + * + * @return A path-vector describing the intersection of the operands A and B. + */ + PathVector getIntersection(); + + /** + * @brief Get the difference of the shapes, A ∖ B. + * + * A point belongs to the difference if and only if it belongs to A but not to B. + * + * @return A path-vector describing the difference of the operands A and B. + */ + PathVector getAminusB(); + + /** + * @brief Get the opposite difference of the shapes, B ∖ A. + * + * A point belongs to the difference if and only if it belongs to B but not to A. + * + * @return A path-vector describing the difference of the operands B and A. + */ + PathVector getBminusA(); + + /** + * @brief Get the symmetric difference of the shapes, A ∆ B. + * + * A point belongs to the symmetric difference if and only if it belongs to one of the two + * shapes A or B, but not both. This is equivalent to the logical XOR operation: the elements + * of A ∆ B are points which are in A XOR in B. + * + * @return A path-vector describing the symmetric difference of the operands A and B. + */ + PathVector getXOR(); + + /// Returns the number of intersections used when computing Boolean operations. + std::size_t size() const; + + /** + * @brief Get the geometric points where the two path-vectors intersect. + * + * Degenerate intersection points, where the shapes merely "kiss", are not retured. + * + * @param defective – whether to return only the defective crossings or only the true crossings. + * @return If defective is true, returns a vector containing all defective intersection points, + * i.e., points that are neither true transverse intersections nor degenerate intersections. + * If defective is false, returns all true transverse intersections. + */ + std::vector<Point> intersectionPoints(bool defective = false) const; + + /** + * @brief Get the geometric points located on path portions between consecutive intersections. + * + * These points were used for the winding number calculations which determined which path portions + * lie inside the other shape and which lie outside. + * + * @return A vector containing all sample points used for winding calculations. + */ + std::vector<Point> windingPoints() const { + return _winding_points; + } + + void fragments(PathVector &in, PathVector &out) const; + + + bool valid() const { return _graph_valid; } + +private: + enum InOutFlag { + INSIDE, + OUTSIDE, + BOTH + }; + + struct IntersectionVertex { + boost::intrusive::list_member_hook<> _hook; + boost::intrusive::list_member_hook<> _proc_hook; + PathVectorTime pos; ///< Intersection time. + Point p; ///< Geometric position of the intersection point; guarantees that endpoints are exact. + IntersectionVertex *neighbor; ///< A pointer to the corresponding vertex on the other shape. + /** Tells us whether the edge originating at this intersection lies inside or outside of + * the shape given by the other path-vector. The "edge originating" at this intersection is + * the portion of the path between this intersection and the next intersection, in the + * direction of increasing path time. */ + InOutFlag next_edge; + unsigned which; ///< Index of the operand path-vector that this intersection vertex lies on. + /** Whether the intersection is defective, which means that for some reason the paths + * neither cross transversally through each other nor "kiss" at a common tangency point. + */ + bool defective; + }; + + typedef boost::intrusive::list + < IntersectionVertex + , boost::intrusive::member_hook + < IntersectionVertex + , boost::intrusive::list_member_hook<> + , &IntersectionVertex::_hook + > + > IntersectionList; + + typedef boost::intrusive::list + < IntersectionVertex + , boost::intrusive::member_hook + < IntersectionVertex + , boost::intrusive::list_member_hook<> + , &IntersectionVertex::_proc_hook + > + > UnprocessedList; + + /// Stores processed intersection information for a single path in an operand path-vector. + struct PathData { + IntersectionList xlist; ///< List of crossings on this particular path. + std::size_t path_index; ///< Index of the path in its path-vector. + int which; ///< Index of the path-vector (in PathIntersectionGraph::_pv) that the path belongs to. + /** Whether this path as a whole is contained INSIDE or OUTSIDE relative to the other path-vector. + * The value BOTH means that some portions of the path are inside while others are outside. + */ + InOutFlag status; + + PathData(int w, std::size_t pi) + : path_index(pi) + , which(w) + , status(BOTH) + {} + }; + + struct IntersectionVertexLess; + typedef IntersectionList::iterator ILIter; + typedef IntersectionList::const_iterator CILIter; + + PathVector _getResult(bool enter_a, bool enter_b); + void _handleNonintersectingPaths(PathVector &result, unsigned which, bool inside); + void _prepareArguments(); + bool _prepareIntersectionLists(Coord precision); + void _assignEdgeWindingParities(Coord precision); + void _assignComponentStatusFromDegenerateIntersections(); + void _removeDegenerateIntersections(); + void _verify(); + + ILIter _getNeighbor(ILIter iter); + PathData &_getPathData(ILIter iter); + + PathVector _pv[2]; ///< Stores the two operand path-vectors, A at _pv[0] and B at _pv[1]. + boost::ptr_vector<IntersectionVertex> _xs; ///< Stores all crossings between the two shapes. + boost::ptr_vector<PathData> _components[2]; ///< Stores the crossing information for the operands. + UnprocessedList _ulist; ///< Temporarily holds all unprocessed during a boolean operation. + bool _graph_valid; ///< Whether all intersections are regular. + /** Stores sample points located on paths of the operand path-vectors, + * between consecutive intersections. + */ + std::vector<Point> _winding_points; + + friend std::ostream &operator<<(std::ostream &, PathIntersectionGraph const &); +}; + +std::ostream &operator<<(std::ostream &os, PathIntersectionGraph const &pig); + +} // namespace Geom + +#endif // SEEN_LIB2GEOM_PATH_GRAPH_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/intersection.h b/include/2geom/intersection.h new file mode 100644 index 0000000..8a23811 --- /dev/null +++ b/include/2geom/intersection.h @@ -0,0 +1,147 @@ +/** + * \file + * \brief Intersection utilities + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef SEEN_LIB2GEOM_INTERSECTION_H +#define SEEN_LIB2GEOM_INTERSECTION_H + +#include <2geom/coord.h> +#include <2geom/point.h> + +namespace Geom { + + +/** @brief Intersection between two shapes. + */ +template <typename TimeA = Coord, typename TimeB = TimeA> +class Intersection + : boost::totally_ordered< Intersection<TimeA, TimeB> > +{ +public: + /** @brief Construct from shape references and time values. + * By default, the intersection point will be halfway between the evaluated + * points on the two shapes. */ + template <typename TA, typename TB> + Intersection(TA const &sa, TB const &sb, TimeA const &ta, TimeB const &tb) + : first(ta) + , second(tb) + , _point(lerp(0.5, sa.pointAt(ta), sb.pointAt(tb))) + {} + + /// Additionally report the intersection point. + Intersection(TimeA const &ta, TimeB const &tb, Point const &p) + : first(ta) + , second(tb) + , _point(p) + {} + + /// Intersection point, as calculated by the intersection algorithm. + Point point() const { + return _point; + } + /// Implicit conversion to Point. + operator Point() const { + return _point; + } + + friend inline void swap(Intersection &a, Intersection &b) { + using std::swap; + swap(a.first, b.first); + swap(a.second, b.second); + swap(a._point, b._point); + } + + bool operator==(Intersection const &other) const { + if (first != other.first) return false; + if (second != other.second) return false; + return true; + } + bool operator<(Intersection const &other) const { + if (first < other.first) return true; + if (first == other.first && second < other.second) return true; + return false; + } + +public: + /// First shape and time value. + TimeA first; + /// Second shape and time value. + TimeB second; +private: + // Recalculation of the intersection point from the time values is in many cases + // less precise than the value obtained directly from the intersection algorithm, + // so we need to store it. + Point _point; +}; + + +// TODO: move into new header? +template <typename T> +struct ShapeTraits { + typedef Coord TimeType; + typedef Interval IntervalType; + typedef T AffineClosureType; + typedef Intersection<> IntersectionType; +}; + +template <typename A, typename B> inline +std::vector< Intersection<A, B> > transpose(std::vector< Intersection<B, A> > const &in) { + std::vector< Intersection<A, B> > result; + for (std::size_t i = 0; i < in.size(); ++i) { + result.push_back(Intersection<A, B>(in[i].second, in[i].first, in[i].point())); + } + return result; +} + +template <typename T> inline +void transpose_in_place(std::vector< Intersection<T, T> > &xs) { + for (std::size_t i = 0; i < xs.size(); ++i) { + std::swap(xs[i].first, xs[i].second); + } +} + +typedef Intersection<> ShapeIntersection; + + +} // namespace Geom + +#endif // SEEN_LIB2GEOM_INTERSECTION_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/interval.h b/include/2geom/interval.h new file mode 100644 index 0000000..11c8f28 --- /dev/null +++ b/include/2geom/interval.h @@ -0,0 +1,245 @@ +/** + * \file + * \brief Simple closed interval class + *//* + * Copyright 2007 Michael Sloan <mgsloan@gmail.com> + * + * Original Rect/Range code by: + * Lauris Kaplinski <lauris@kaplinski.com> + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * bulia byak <buliabyak@users.sf.net> + * MenTaLguY <mental@rydia.net> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, output to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ +#ifndef LIB2GEOM_SEEN_INTERVAL_H +#define LIB2GEOM_SEEN_INTERVAL_H + +#include <boost/none.hpp> +#include <boost/operators.hpp> +#include <2geom/coord.h> +#include <2geom/math-utils.h> +#include <2geom/generic-interval.h> +#include <2geom/int-interval.h> + +namespace Geom { + +/** + * @brief Range of real numbers that is never empty. + * + * Intervals are closed ranges \f$[a, b]\f$, which means they include their endpoints. + * To use them as open ranges, you can use the interiorContains() methods. + * + * @ingroup Primitives + */ +class Interval + : public GenericInterval<Coord> +{ + typedef GenericInterval<Coord> Base; +public: + /// @name Create intervals. + /// @{ + /** @brief Create an interval that contains only zero. */ + Interval() {} + /** @brief Create an interval that contains a single point. */ + explicit Interval(Coord u) : Base(u) {} + /** @brief Create an interval that contains all points between @c u and @c v. */ + Interval(Coord u, Coord v) : Base(u,v) {} + /** @brief Convert from integer interval */ + Interval(IntInterval const &i) : Base(i.min(), i.max()) {} + Interval(Base const &b) : Base(b) {} + + /** @brief Create an interval containing a range of values. + * The resulting interval will contain all values from the given range. + * The return type of iterators must be convertible to Coord. The given range + * must not be empty. For potentially empty ranges, see OptInterval. + * @param start Beginning of the range + * @param end End of the range + * @return Interval that contains all values from [start, end). */ + template <typename InputIterator> + static Interval from_range(InputIterator start, InputIterator end) { + Interval result = Base::from_range(start, end); + return result; + } + /** @brief Create an interval from a C-style array of values it should contain. */ + static Interval from_array(Coord const *c, unsigned n) { + Interval result = from_range(c, c+n); + return result; + } + /// @} + + /// @name Inspect contained values. + /// @{ + /// Check whether both endpoints are finite. + bool isFinite() const { + return std::isfinite(min()) && std::isfinite(max()); + } + /** @brief Map the interval [0,1] onto this one. + * This method simply performs 1D linear interpolation between endpoints. */ + Coord valueAt(Coord t) const { + return lerp(t, min(), max()); + } + /** @brief Compute a time value that maps to the given value. + * The supplied value does not need to be in the interval for this method to work. */ + Coord timeAt(Coord v) const { + return (v - min()) / extent(); + } + /// Find closest time in [0,1] that maps to the given value. */ + Coord nearestTime(Coord v) const { + if (v <= min()) return 0; + if (v >= max()) return 1; + return timeAt(v); + } + /// @} + + /// @name Test coordinates and other intervals for inclusion. + /// @{ + /** @brief Check whether the interior of the interval includes this number. + * Interior means all numbers in the interval except its ends. */ + bool interiorContains(Coord val) const { return min() < val && val < max(); } + /** @brief Check whether the interior of the interval includes the given interval. + * Interior means all numbers in the interval except its ends. */ + bool interiorContains(Interval const &val) const { return min() < val.min() && val.max() < max(); } + /// Check whether the number is contained in the union of the interior and the lower boundary. + bool lowerContains(Coord val) const { return min() <= val && val < max(); } + /// Check whether the given interval is contained in the union of the interior and the lower boundary. + bool lowerContains(Interval const &val) const { return min() <= val.min() && val.max() < max(); } + /// Check whether the number is contained in the union of the interior and the upper boundary. + bool upperContains(Coord val) { return min() < val && val <= max(); } + /// Check whether the given interval is contained in the union of the interior and the upper boundary. + bool upperContains(Interval const &val) const { return min() < val.min() && val.max() <= max(); } + /** @brief Check whether the interiors of the intervals have any common elements. + * A single point in common is not considered an intersection. */ + bool interiorIntersects(Interval const &val) const { + return std::max(min(), val.min()) < std::min(max(), val.max()); + } + /// @} + + /// @name Operators + /// @{ + // IMPL: ScalableConcept + /** @brief Scale an interval */ + Interval &operator*=(Coord s) { + using std::swap; + _b[0] *= s; + _b[1] *= s; + if(s < 0) swap(_b[0], _b[1]); + return *this; + } + /** @brief Scale an interval by the inverse of the specified value */ + Interval &operator/=(Coord s) { + using std::swap; + _b[0] /= s; + _b[1] /= s; + if(s < 0) swap(_b[0], _b[1]); + return *this; + } + /** @brief Multiply two intervals. + * Product is defined as the set of points that can be obtained by multiplying + * any value from the second operand by any value from the first operand: + * \f$S = \{x \in A, y \in B: x * y\}\f$ */ + Interval &operator*=(Interval const &o) { + // TODO implement properly + Coord mn = min(), mx = max(); + expandTo(mn * o.min()); + expandTo(mn * o.max()); + expandTo(mx * o.min()); + expandTo(mx * o.max()); + return *this; + } + bool operator==(IntInterval const &ii) const { + return min() == Coord(ii.min()) && max() == Coord(ii.max()); + } + bool operator==(Interval const &other) const { + return Base::operator==(other); + } + /// @} + + /// @name Rounding to integer values + /// @{ + /** @brief Return the smallest integer interval which contains this one. */ + IntInterval roundOutwards() const { + IntInterval ret(floor(min()), ceil(max())); + return ret; + } + /** @brief Return the largest integer interval which is contained in this one. */ + OptIntInterval roundInwards() const { + IntCoord u = ceil(min()), v = floor(max()); + if (u > v) { OptIntInterval e; return e; } + IntInterval ret(u, v); + return ret; + } + /// @} +}; + +/** + * @brief Range of real numbers that can be empty. + * @ingroup Primitives + */ +class OptInterval + : public GenericOptInterval<Coord> +{ + typedef GenericOptInterval<Coord> Base; +public: + using Base::Base; + using Base::operator==; + using Base::operator!=; + + OptInterval(Base const &b) : Base(b) {} + + /** @brief Promote from IntInterval. */ + OptInterval(IntInterval const &i) : Base(Interval(i)) {} + /** @brief Promote from OptIntInterval. */ + OptInterval(OptIntInterval const &i) : Base() { + if (i) *this = Interval(*i); + } +}; + +// functions required for Python bindings +inline Interval unify(Interval const &a, Interval const &b) +{ + Interval r = a | b; + return r; +} +inline OptInterval intersect(Interval const &a, Interval const &b) +{ + OptInterval r = a & b; + return r; +} + +} // end namespace Geom + +#endif //SEEN_INTERVAL_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/intervaltree/interval_tree.h b/include/2geom/intervaltree/interval_tree.h new file mode 100644 index 0000000..85f91f9 --- /dev/null +++ b/include/2geom/intervaltree/interval_tree.h @@ -0,0 +1,126 @@ +#ifndef E_INTERVAL_TREE +#define E_INTERVAL_TREE + +// From Emin Martinian, licenced LGPL and MPL with permission + +#include <vector> +#include <math.h> +#include <limits> +#include <iostream> + +using std::vector; + +// The interval_tree.h and interval_tree.cc files contain code for +// interval trees implemented using red-black-trees as described in +// the book _Introduction_To_Algorithms_ by Cormen, Leisserson, +// and Rivest. + +// CONVENTIONS: +// Function names: Each word in a function name begins with +// a capital letter. An example funcntion name is +// CreateRedTree(a,b,c). Furthermore, each function name +// should begin with a capital letter to easily distinguish +// them from variables. +// +// Variable names: Each word in a variable name begins with +// a capital letter EXCEPT the first letter of the variable +// name. For example, int newLongInt. Global variables have +// names beginning with "g". An example of a global +// variable name is gNewtonsConstant. + + +#ifndef MAX_INT +#define MAX_INT INT_MAX // some architechturs define INT_MAX not MAX_INT +#endif + +// The Interval class is an Abstract Base Class. This means that no +// instance of the Interval class can exist. Only classes which +// inherit from the Interval class can exist. Furthermore any class +// which inherits from the Interval class must define the member +// functions GetLowPoint and GetHighPoint. +// +// The GetLowPoint should return the lowest point of the interval and +// the GetHighPoint should return the highest point of the interval. + +class Interval { +public: + Interval(); + virtual ~Interval(); + virtual int GetLowPoint() const = 0; + virtual int GetHighPoint() const = 0; + virtual void Print() const; +}; + +class IntervalTreeNode { + friend class IntervalTree; +public: + void Print(IntervalTreeNode*, + IntervalTreeNode*) const; + IntervalTreeNode(); + IntervalTreeNode(Interval *); + ~IntervalTreeNode(); +protected: + Interval * storedInterval; + int key; + int high; + int maxHigh; + bool red; /* if red=0 then the node is black */ + IntervalTreeNode * left; + IntervalTreeNode * right; + IntervalTreeNode * parent; +}; + +struct it_recursion_node { +public: + /* this structure stores the information needed when we take the */ + /* right branch in searching for intervals but possibly come back */ + /* and check the left branch as well. */ + + IntervalTreeNode * start_node; + unsigned int parentIndex; + bool tryRightBranch; +} ; + + +class IntervalTree { +public: + IntervalTree(); + ~IntervalTree(); + void Print() const; + Interval * DeleteNode(IntervalTreeNode *); + IntervalTreeNode * Insert(Interval *); + IntervalTreeNode * GetPredecessorOf(IntervalTreeNode *) const; + IntervalTreeNode * GetSuccessorOf(IntervalTreeNode *) const; + vector<void *> Enumerate(int low, int high) ; + void CheckAssumptions() const; +protected: + /* A sentinel is used for root and for nil. These sentinels are */ + /* created when ITTreeCreate is caled. root->left should always */ + /* point to the node which is the root of the tree. nil points to a */ + /* node which should always be black but has arbitrary children and */ + /* parent and no key or info. The point of using these sentinels is so */ + /* that the root and nil nodes do not require special cases in the code */ + IntervalTreeNode * root; + IntervalTreeNode * nil; + void LeftRotate(IntervalTreeNode *); + void RightRotate(IntervalTreeNode *); + void TreeInsertHelp(IntervalTreeNode *); + void TreePrintHelper(IntervalTreeNode *) const; + void FixUpMaxHigh(IntervalTreeNode *); + void DeleteFixUp(IntervalTreeNode *); + void CheckMaxHighFields(IntervalTreeNode *) const; + int CheckMaxHighFieldsHelper(IntervalTreeNode * y, + const int currentHigh, + int match) const; +private: + unsigned int recursionNodeStackSize; + it_recursion_node * recursionNodeStack; + unsigned int currentParent; + unsigned int recursionNodeStackTop; +}; + + +#endif + + + diff --git a/include/2geom/line.h b/include/2geom/line.h new file mode 100644 index 0000000..9a56602 --- /dev/null +++ b/include/2geom/line.h @@ -0,0 +1,605 @@ +/** + * \file + * \brief Infinite straight line + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Copyright 2008-2011 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_LINE_H +#define LIB2GEOM_SEEN_LINE_H + +#include <cmath> +#include <optional> +#include <2geom/bezier-curve.h> // for LineSegment +#include <2geom/rect.h> +#include <2geom/crossing.h> +#include <2geom/exception.h> +#include <2geom/ray.h> +#include <2geom/angle.h> +#include <2geom/intersection.h> + +namespace Geom +{ + +// class docs in cpp file +class Line + : boost::equality_comparable< Line > +{ +private: + Point _initial; + Point _final; +public: + /// @name Creating lines. + /// @{ + /** @brief Create a default horizontal line. + * Creates a line with unit speed going in +X direction. */ + Line() + : _initial(0,0), _final(1,0) + {} + /** @brief Create a line with the specified inclination. + * @param origin One of the points on the line + * @param angle Angle of the line in mathematical convention */ + Line(Point const &origin, Coord angle) + : _initial(origin) + { + Point v; + sincos(angle, v[Y], v[X]); + _final = _initial + v; + } + + /** @brief Create a line going through two points. + * The first point will be at time 0, while the second one + * will be at time 1. + * @param a Initial point + * @param b First point */ + Line(Point const &a, Point const &b) + : _initial(a) + , _final(b) + {} + + /** @brief Create a line based on the coefficients of its equation. + @see Line::setCoefficients() */ + Line(double a, double b, double c) { + setCoefficients(a, b, c); + } + + /// Create a line by extending a line segment. + explicit Line(LineSegment const &seg) + : _initial(seg.initialPoint()) + , _final(seg.finalPoint()) + {} + + /// Create a line by extending a ray. + explicit Line(Ray const &r) + : _initial(r.origin()) + , _final(r.origin() + r.vector()) + {} + + /// Create a line normal to a vector at a specified distance from origin. + static Line from_normal_distance(Point const &n, Coord c) { + Point start = c * n.normalized(); + Line l(start, start + rot90(n)); + return l; + } + /** @brief Create a line from origin and unit vector. + * Note that each line direction has two possible unit vectors. + * @param o Point through which the line will pass + * @param v Unit vector of the line's direction */ + static Line from_origin_and_vector(Point const &o, Point const &v) { + Line l(o, o + v); + return l; + } + + Line* duplicate() const { + return new Line(*this); + } + /// @} + + /// @name Retrieve and set the line's parameters. + /// @{ + + /// Get the line's origin point. + Point origin() const { return _initial; } + /** @brief Get the line's raw direction vector. + * The length of the retrieved vector is equal to the length of a segment parametrized by + * a time interval of length 1. */ + Point vector() const { return _final - _initial; } + /** @brief Get the line's normalized direction vector. + * The retrieved vector is normalized to unit length. */ + Point versor() const { return (_final - _initial).normalized(); } + /// Angle the line makes with the X axis, in mathematical convention. + Coord angle() const { + Point d = _final - _initial; + double a = std::atan2(d[Y], d[X]); + if (a < 0) a += M_PI; + if (a == M_PI) a = 0; + return a; + } + + /** @brief Set the point at zero time. + * The orientation remains unchanged, modulo numeric errors during addition. */ + void setOrigin(Point const &p) { + Point d = p - _initial; + _initial = p; + _final += d; + } + /** @brief Set the speed of the line. + * Origin remains unchanged. */ + void setVector(Point const &v) { + _final = _initial + v; + } + + /** @brief Set the angle the line makes with the X axis. + * Origin remains unchanged. */ + void setAngle(Coord angle) { + Point v; + sincos(angle, v[Y], v[X]); + v *= distance(_initial, _final); + _final = _initial + v; + } + + /// Set a line based on two points it should pass through. + void setPoints(Point const &a, Point const &b) { + _initial = a; + _final = b; + } + + /** @brief Set the coefficients of the line equation. + * The line equation is: \f$ax + by = c\f$. Points that satisfy the equation + * are on the line. */ + void setCoefficients(double a, double b, double c); + + /** @brief Get the coefficients of the line equation as a vector. + * @return STL vector @a v such that @a v[0] contains \f$a\f$, @a v[1] contains \f$b\f$, + * and @a v[2] contains \f$c\f$. */ + std::vector<double> coefficients() const; + + /// Get the coefficients of the line equation by reference. + void coefficients(Coord &a, Coord &b, Coord &c) const; + + /** @brief Check if the line has more than one point. + * A degenerate line can be created if the line is created from a line equation + * that has no solutions. + * @return True if the line has no points or exactly one point */ + bool isDegenerate() const { + return _initial == _final; + } + /// Check if the line is horizontal (y is constant). + bool isHorizontal() const { + return _initial[Y] == _final[Y]; + } + /// Check if the line is vertical (x is constant). + bool isVertical() const { + return _initial[X] == _final[X]; + } + + /** @brief Reparametrize the line so that it has unit speed. + * Note that the direction of the line may also change. */ + void normalize() { + // this helps with the nasty case of a line that starts somewhere far + // and ends very close to the origin + if (L2sq(_final) < L2sq(_initial)) { + std::swap(_initial, _final); + } + Point v = _final - _initial; + v.normalize(); + _final = _initial + v; + } + /** @brief Return a new line reparametrized for unit speed. */ + Line normalized() const { + Point v = _final - _initial; + v.normalize(); + Line ret(_initial, _initial + v); + return ret; + } + /// @} + + /// @name Evaluate the line as a function. + ///@{ + Point initialPoint() const { + return _initial; + } + Point finalPoint() const { + return _final; + } + Point pointAt(Coord t) const { + return lerp(t, _initial, _final);; + } + + Coord valueAt(Coord t, Dim2 d) const { + return lerp(t, _initial[d], _final[d]); + } + + Coord timeAt(Point const &p) const; + + /** @brief Get a time value corresponding to a projection of a point on the line. + * @param p Arbitrary point. + * @return Time value corresponding to a point closest to @c p. */ + Coord timeAtProjection(Point const& p) const { + if ( isDegenerate() ) return 0; + Point v = vector(); + return dot(p - _initial, v) / dot(v, v); + } + + /** @brief Find a point on the line closest to the query point. + * This is an alias for timeAtProjection(). */ + Coord nearestTime(Point const &p) const { + return timeAtProjection(p); + } + + std::vector<Coord> roots(Coord v, Dim2 d) const; + Coord root(Coord v, Dim2 d) const; + /// @} + + /// @name Create other objects based on this line. + /// @{ + void reverse() { + std::swap(_final, _initial); + } + /** @brief Create a line containing the same points, but in opposite direction. + * @return Line \f$g\f$ such that \f$g(t) = f(1-t)\f$ */ + Line reversed() const { + Line result(_final, _initial); + return result; + } + + /** @brief Same as segment(), but allocate the line segment dynamically. */ + // TODO remove this? + Curve* portion(Coord f, Coord t) const { + LineSegment* seg = new LineSegment(pointAt(f), pointAt(t)); + return seg; + } + + /** @brief Create a segment of this line. + * @param f Time value for the initial point of the segment + * @param t Time value for the final point of the segment + * @return Created line segment */ + LineSegment segment(Coord f, Coord t) const { + return LineSegment(pointAt(f), pointAt(t)); + } + + /// Return the portion of the line that is inside the given rectangle + std::optional<LineSegment> clip(Rect const &r) const; + + /** @brief Create a ray starting at the specified time value. + * The created ray will go in the direction of the line's vector (in the direction + * of increasing time values). + * @param t Time value where the ray should start + * @return Ray starting at t and going in the direction of the vector */ + Ray ray(Coord t) { + Ray result; + result.setOrigin(pointAt(t)); + result.setVector(vector()); + return result; + } + + /** @brief Create a derivative of the line. + * The new line will always be degenerate. Its origin will be equal to this + * line's vector. */ + Line derivative() const { + Point v = vector(); + Line result(v, v); + return result; + } + + /// Create a line transformed by an affine transformation. + Line transformed(Affine const& m) const { + Line l(_initial * m, _final * m); + return l; + } + + /** @brief Get a unit vector normal to the line. + * If Y grows upwards, then this is the left normal. If Y grows downwards, + * then this is the right normal. */ + Point normal() const { + return rot90(vector()).normalized(); + } + + // what does this do? + Point normalAndDist(double & dist) const { + Point n = normal(); + dist = -dot(n, _initial); + return n; + } + + /// Compute an affine matrix representing a reflection about the line. + Affine reflection() const { + Point v = versor(); + Coord x2 = v[X]*v[X], y2 = v[Y]*v[Y], xy = v[X]*v[Y]; + Affine m(x2-y2, 2.*xy, + 2.*xy, y2-x2, + _initial[X], _initial[Y]); + m = Translate(-_initial) * m; + return m; + } + + /** @brief Compute an affine which transforms all points on the line to zero X or Y coordinate. + * This operation is useful in reducing intersection problems to root-finding problems. + * There are many affines which do this transformation. This function returns one that + * preserves angles, areas and distances - a rotation combined with a translation, and + * additionally moves the initial point of the line to (0,0). This way it works without + * problems even for lines perpendicular to the target, though may in some cases have + * lower precision than e.g. a shear transform. + * @param d Which coordinate of points on the line should be zero after the transformation */ + Affine rotationToZero(Dim2 d) const { + Point v = vector(); + if (d == X) { + std::swap(v[X], v[Y]); + } else { + v[Y] = -v[Y]; + } + Affine m = Translate(-_initial) * Rotate(v); + return m; + } + /** @brief Compute a rotation affine which transforms the line to one of the axes. + * @param d Which line should be the axis */ + Affine rotationToAxis(Dim2 d) const { + Affine m = rotationToZero(other_dimension(d)); + return m; + } + + Affine transformTo(Line const &other) const; + /// @} + + std::vector<ShapeIntersection> intersect(Line const &other) const; + std::vector<ShapeIntersection> intersect(Ray const &r) const; + std::vector<ShapeIntersection> intersect(LineSegment const &ls) const; + + template <typename T> + Line &operator*=(T const &tr) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + _initial *= tr; + _final *= tr; + return *this; + } + + bool operator==(Line const &other) const { + if (distance(pointAt(nearestTime(other._initial)), other._initial) != 0) return false; + if (distance(pointAt(nearestTime(other._final)), other._final) != 0) return false; + return true; + } + + template <typename T> + friend Line operator*(Line const &l, T const &tr) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + Line result(l); + result *= tr; + return result; + } +}; // end class Line + +/** @brief Removes intersections outside of the unit interval. + * A helper used to implement line segment intersections. + * @param xs Line intersections + * @param a Whether the first time value has to be in the unit interval + * @param b Whether the second time value has to be in the unit interval + * @return Appropriately filtered intersections */ +void filter_line_segment_intersections(std::vector<ShapeIntersection> &xs, bool a=false, bool b=true); +void filter_ray_intersections(std::vector<ShapeIntersection> &xs, bool a=false, bool b=true); + +/// @brief Compute distance from point to line. +/// @relates Line +inline +double distance(Point const &p, Line const &line) +{ + if (line.isDegenerate()) { + return ::Geom::distance(p, line.initialPoint()); + } else { + Coord t = line.nearestTime(p); + return ::Geom::distance(line.pointAt(t), p); + } +} + +inline +bool are_near(Point const &p, Line const &line, double eps = EPSILON) +{ + return are_near(distance(p, line), 0, eps); +} + +inline +bool are_parallel(Line const &l1, Line const &l2, double eps = EPSILON) +{ + return are_near(cross(l1.vector(), l2.vector()), 0, eps); +} + +/** @brief Test whether two lines are approximately the same. + * This tests for being parallel and the origin of one line being close to the other, + * so it tests whether the images of the lines are similar, not whether the same time values + * correspond to similar points. For example a line from (1,1) to (2,2) and a line from + * (-1,-1) to (0,0) will be the same, because their images match, even though there is + * no time value for which the lines give similar points. + * @relates Line */ +inline +bool are_same(Line const &l1, Line const &l2, double eps = EPSILON) +{ + return are_parallel(l1, l2, eps) && are_near(l1.origin(), l2, eps); +} + +/// Test whether two lines are perpendicular. +/// @relates Line +inline +bool are_orthogonal(Line const &l1, Line const &l2, double eps = EPSILON) +{ + return are_near(dot(l1.vector(), l2.vector()), 0, eps); +} + +// evaluate the angle between l1 and l2 rotating l1 in cw direction +// until it overlaps l2 +// the returned value is an angle in the interval [0, PI[ +inline +double angle_between(Line const& l1, Line const& l2) +{ + double angle = angle_between(l1.vector(), l2.vector()); + if (angle < 0) angle += M_PI; + if (angle == M_PI) angle = 0; + return angle; +} + +inline +double distance(Point const &p, LineSegment const &seg) +{ + double t = seg.nearestTime(p); + return distance(p, seg.pointAt(t)); +} + +inline +bool are_near(Point const &p, LineSegment const &seg, double eps = EPSILON) +{ + return are_near(distance(p, seg), 0, eps); +} + +// build a line passing by _point and orthogonal to _line +inline +Line make_orthogonal_line(Point const &p, Line const &line) +{ + Point d = line.vector().cw(); + Line l(p, p + d); + return l; +} + +// build a line passing by _point and parallel to _line +inline +Line make_parallel_line(Point const &p, Line const &line) +{ + Line result(line); + result.setOrigin(p); + return result; +} + +// build a line passing by the middle point of _segment and orthogonal to it. +inline +Line make_bisector_line(LineSegment const& _segment) +{ + return make_orthogonal_line( middle_point(_segment), Line(_segment) ); +} + +// build the bisector line of the angle between ray(O,A) and ray(O,B) +inline +Line make_angle_bisector_line(Point const &A, Point const &O, Point const &B) +{ + AngleInterval ival(Angle(A-O), Angle(B-O)); + Angle bisect = ival.angleAt(0.5); + return Line(O, bisect); +} + +// prj(P) = rot(v, Point( rot(-v, P-O)[X], 0 )) + O +inline +Point projection(Point const &p, Line const &line) +{ + return line.pointAt(line.nearestTime(p)); +} + +inline +LineSegment projection(LineSegment const &seg, Line const &line) +{ + return line.segment(line.nearestTime(seg.initialPoint()), + line.nearestTime(seg.finalPoint())); +} + +inline +std::optional<LineSegment> clip(Line const &l, Rect const &r) { + return l.clip(r); +} + + +namespace detail +{ + +OptCrossing intersection_impl(Ray const& r1, Line const& l2, unsigned int i); +OptCrossing intersection_impl( LineSegment const& ls1, + Line const& l2, + unsigned int i ); +OptCrossing intersection_impl( LineSegment const& ls1, + Ray const& r2, + unsigned int i ); +} + + +inline +OptCrossing intersection(Ray const& r1, Line const& l2) +{ + return detail::intersection_impl(r1, l2, 0); + +} + +inline +OptCrossing intersection(Line const& l1, Ray const& r2) +{ + return detail::intersection_impl(r2, l1, 1); +} + +inline +OptCrossing intersection(LineSegment const& ls1, Line const& l2) +{ + return detail::intersection_impl(ls1, l2, 0); +} + +inline +OptCrossing intersection(Line const& l1, LineSegment const& ls2) +{ + return detail::intersection_impl(ls2, l1, 1); +} + +inline +OptCrossing intersection(LineSegment const& ls1, Ray const& r2) +{ + return detail::intersection_impl(ls1, r2, 0); + +} + +inline +OptCrossing intersection(Ray const& r1, LineSegment const& ls2) +{ + return detail::intersection_impl(ls2, r1, 1); +} + + +OptCrossing intersection(Line const& l1, Line const& l2); + +OptCrossing intersection(Ray const& r1, Ray const& r2); + +OptCrossing intersection(LineSegment const& ls1, LineSegment const& ls2); + + +} // end namespace Geom + + +#endif // LIB2GEOM_SEEN_LINE_H + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/linear.h b/include/2geom/linear.h new file mode 100644 index 0000000..75c6e01 --- /dev/null +++ b/include/2geom/linear.h @@ -0,0 +1,167 @@ +/** + * \file + * \brief Linear fragment function class + *//* + * Authors: + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * Michael Sloan <mgsloan@gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright (C) 2006-2015 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_LINEAR_H +#define LIB2GEOM_SEEN_LINEAR_H + +#include <2geom/interval.h> +#include <2geom/math-utils.h> + +namespace Geom { + +class SBasis; + +/** + * @brief Function that interpolates linearly between two values. + * @ingroup Fragments + */ +class Linear + : boost::additive< Linear + , boost::arithmetic< Linear, Coord + , boost::equality_comparable< Linear + > > > +{ +public: + Coord a[2]; + Linear() {a[0]=0; a[1]=0;} + Linear(Coord aa, Coord b) {a[0] = aa; a[1] = b;} + Linear(Coord aa) {a[0] = aa; a[1] = aa;} + + Coord operator[](unsigned i) const { + assert(i < 2); + return a[i]; + } + Coord &operator[](unsigned i) { + assert(i < 2); + return a[i]; + } + + //IMPL: FragmentConcept + typedef Coord output_type; + bool isZero(Coord eps=EPSILON) const { return are_near(a[0], 0., eps) && are_near(a[1], 0., eps); } + bool isConstant(Coord eps=EPSILON) const { return are_near(a[0], a[1], eps); } + bool isFinite() const { return std::isfinite(a[0]) && std::isfinite(a[1]); } + + Coord at0() const { return a[0]; } + Coord &at0() { return a[0]; } + Coord at1() const { return a[1]; } + Coord &at1() { return a[1]; } + + Coord valueAt(Coord t) const { return lerp(t, a[0], a[1]); } + Coord operator()(Coord t) const { return valueAt(t); } + + // not very useful, but required for FragmentConcept + std::vector<Coord> valueAndDerivatives(Coord t, unsigned n) { + std::vector<Coord> result(n+1, 0.0); + result[0] = valueAt(t); + if (n >= 1) { + result[1] = a[1] - a[0]; + } + return result; + } + + //defined in sbasis.h + inline SBasis toSBasis() const; + + OptInterval bounds_exact() const { return Interval(a[0], a[1]); } + OptInterval bounds_fast() const { return bounds_exact(); } + OptInterval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); } + + double tri() const { + return a[1] - a[0]; + } + double hat() const { + return (a[1] + a[0])/2; + } + + // addition of other Linears + Linear &operator+=(Linear const &other) { + a[0] += other.a[0]; + a[1] += other.a[1]; + return *this; + } + Linear &operator-=(Linear const &other) { + a[0] -= other.a[0]; + a[1] -= other.a[1]; + return *this; + } + + // + Linear &operator+=(Coord x) { + a[0] += x; a[1] += x; + return *this; + } + Linear &operator-=(Coord x) { + a[0] -= x; a[1] -= x; + return *this; + } + Linear &operator*=(Coord x) { + a[0] *= x; a[1] *= x; + return *this; + } + Linear &operator/=(Coord x) { + a[0] /= x; a[1] /= x; + return *this; + } + Linear operator-() const { + Linear ret(-a[0], -a[1]); + return ret; + } + + bool operator==(Linear const &other) const { + return a[0] == other.a[0] && a[1] == other.a[1]; + } +}; + +inline Linear reverse(Linear const &a) { return Linear(a[1], a[0]); } +inline Linear portion(Linear const &a, Coord from, Coord to) { + Linear result(a.valueAt(from), a.valueAt(to)); + return result; +} + +} // end namespace Geom + +#endif //LIB2GEOM_SEEN_LINEAR_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/math-utils.h b/include/2geom/math-utils.h new file mode 100644 index 0000000..4c35a80 --- /dev/null +++ b/include/2geom/math-utils.h @@ -0,0 +1,140 @@ +/** + * \file + * \brief Low level math functions and compatibility wrappers + *//* + * Authors: + * Johan Engelen <goejendaagh@zonnet.nl> + * Michael G. Sloan <mgsloan@gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Copyright 2006-2009 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_MATH_UTILS_H +#define LIB2GEOM_SEEN_MATH_UTILS_H + +#include <math.h> // sincos is usually only available in math.h +#include <array> +#include <cmath> +#include <utility> // for std::pair +#include <boost/math/special_functions/fpclassify.hpp> + +namespace Geom { + +/** @brief Sign function - indicates the sign of a numeric type. + * Mathsy people will know this is basically the derivative of abs, except for the fact + * that it is defined on 0. + * @return -1 when x is negative, 1 when positive, and 0 if equal to 0. */ +template <class T> inline int sgn(const T& x) { + return (x < 0 ? -1 : (x > 0 ? 1 : 0) ); +// can we 'optimize' this with: +// return ( (T(0) < x) - (x < T(0)) ); +} + +template <class T> inline T sqr(const T& x) {return x * x;} +template <class T> inline T cube(const T& x) {return x * x * x;} + +/** Between function - returns true if a number x is within a range: (min < x) && (max > x). + * The values delimiting the range and the number must have the same type. + */ +template <class T> inline const T& between (const T& min, const T& max, const T& x) + { return (min < x) && (max > x); } + +/** @brief Returns @a x rounded to the nearest multiple of \f$10^{p}\f$. + + Implemented in terms of round, i.e. we make no guarantees as to what happens if x is + half way between two rounded numbers. + + Note: places is the number of decimal places without using scientific (e) notation, not the + number of significant figures. This function may not be suitable for values of x whose + magnitude is so far from 1 that one would want to use scientific (e) notation. + + places may be negative: e.g. places = -2 means rounding to a multiple of .01 +**/ +inline double decimal_round(double x, int p) { + //TODO: possibly implement with modulus instead? + double const multiplier = ::pow(10.0, p); + return ::round( x * multiplier ) / multiplier; +} + +/** @brief Simultaneously compute a sine and a cosine of the same angle. + * This function can be up to 2 times faster than separate computation, depending + * on the platform. It uses the standard library function sincos() if available. + * @param angle Angle + * @param sin_ Variable that will store the sine + * @param cos_ Variable that will store the cosine */ +inline void sincos(double angle, double &sin_, double &cos_) { +#ifdef HAVE_SINCOS + ::sincos(angle, &sin_, &cos_); +#else + sin_ = ::sin(angle); + cos_ = ::cos(angle); +#endif +} + +/** @brief Scale the doubles in the passed array to make them "reasonably large". + * + * All doubles in the passed array will get scaled by the same power of 2 (which is + * a lossless operation) in such a way that their geometric average gets closer to 1. + * + * @tparam N The size of the passed array. + * @param[in,out] values The doubles to be rescaled in place. + * @return The exponent in the power of two by which the doubles got scaled. + */ +template <size_t N> +inline int rescale_homogenous(std::array<double, N> &values) +{ + if constexpr (N == 0) { + return 0; + } + std::array<int, N> exponents; + std::array<double, N> mantissas; + int average = 0; + for (size_t i = 0; i < N; i++) { + mantissas[i] = std::frexp(values[i], &exponents[i]); + average += exponents[i]; + } + average /= (int)N; + for (size_t i = 0; i < N; i++) { + values[i] = std::ldexp(mantissas[i], exponents[i] - average); + } + return -average; +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_MATH_UTILS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/nearest-time.h b/include/2geom/nearest-time.h new file mode 100644 index 0000000..007cd27 --- /dev/null +++ b/include/2geom/nearest-time.h @@ -0,0 +1,141 @@ +/** @file + * @brief Nearest time routines for D2<SBasis> and Piecewise<D2<SBasis>> + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2007-2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef LIB2GEOM_SEEN_NEAREST_TIME_H +#define LIB2GEOM_SEEN_NEAREST_TIME_H + + +#include <vector> + +#include <2geom/d2.h> +#include <2geom/piecewise.h> +#include <2geom/exception.h> +#include <2geom/bezier.h> + + +namespace Geom +{ + +/* + * Given a line L specified by a point A and direction vector v, + * return the point on L nearest to p. Note that the returned value + * is with respect to the _normalized_ direction of v! + */ +inline double nearest_time(Point const &p, Point const &A, Point const &v) +{ + Point d(p - A); + return d[0] * v[0] + d[1] * v[1]; +} + +Coord nearest_time(Point const &p, D2<Bezier> const &bez, Coord from = 0, Coord to = 1); + +//////////////////////////////////////////////////////////////////////////////// +// D2<SBasis> versions + +/* + * Return the parameter t of a nearest point on the portion of the curve "c", + * related to the interval [from, to], to the point "p". + * The needed curve derivative "deriv" is passed as parameter. + * The function return the first nearest point to "p" that is found. + */ +double nearest_time(Point const &p, + D2<SBasis> const &c, D2<SBasis> const &deriv, + double from = 0, double to = 1); + +inline +double nearest_time(Point const &p, + D2<SBasis> const &c, + double from = 0, double to = 1 ) +{ + return nearest_time(p, c, Geom::derivative(c), from, to); +} + +/* + * Return the parameters t of all the nearest times on the portion of + * the curve "c", related to the interval [from, to], to the point "p". + * The needed curve derivative "dc" is passed as parameter. + */ +std::vector<double> +all_nearest_times(Point const& p, + D2<SBasis> const& c, D2<SBasis> const& dc, + double from = 0, double to = 1 ); + +inline +std::vector<double> +all_nearest_times(Point const &p, + D2<SBasis> const &c, + double from = 0, double to = 1) +{ + return all_nearest_times(p, c, Geom::derivative(c), from, to); +} + + +//////////////////////////////////////////////////////////////////////////////// +// Piecewise< D2<SBasis> > versions + +double nearest_time(Point const &p, + Piecewise< D2<SBasis> > const &c, + double from, double to); + +inline +double nearest_time(Point const& p, Piecewise< D2<SBasis> > const &c) +{ + return nearest_time(p, c, c.cuts[0], c.cuts[c.size()]); +} + + +std::vector<double> +all_nearest_times(Point const &p, + Piecewise< D2<SBasis> > const &c, + double from, double to); + +inline +std::vector<double> +all_nearest_times( Point const& p, Piecewise< D2<SBasis> > const& c ) +{ + return all_nearest_times(p, c, c.cuts[0], c.cuts[c.size()]); +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_NEAREST_TIME_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/numeric/fitting-model.h b/include/2geom/numeric/fitting-model.h new file mode 100644 index 0000000..0316f57 --- /dev/null +++ b/include/2geom/numeric/fitting-model.h @@ -0,0 +1,521 @@ +/* + * Fitting Models for Geom Types + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef _NL_FITTING_MODEL_H_ +#define _NL_FITTING_MODEL_H_ + + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier.h> +#include <2geom/bezier-curve.h> +#include <2geom/polynomial.h> +#include <2geom/ellipse.h> +#include <2geom/circle.h> +#include <2geom/utils.h> +#include <2geom/conicsec.h> + + +namespace Geom { namespace NL { + +/* + * A model is an abstraction for an expression dependent from a parameter where + * the coefficients of this expression are the unknowns of the fitting problem. + * For a ceratain number of parameter values we know the related values + * the expression evaluates to: from each parameter value we get a row of + * the matrix of the fitting problem, from each expression value we get + * the related constant term. + * Example: given the model a*x^2 + b*x + c = 0; from x = 1 we get + * the equation a + b + c = 0, in this example the constant term is always + * the same for each parameter value. + * + * A model is required to implement 3 methods: + * + * - size : returns the number of unknown coefficients that appear in + * the expression of the fitting problem; + * - feed : its input is a parameter value and the related expression value, + * it generates a matrix row and a new entry of the constant vector + * of the fitting problem; + * - instance : it has an input parameter represented by the raw vector + * solution of the fitting problem and an output parameter + * of type InstanceType that return a specific object that is + * generated using the fitting problem solution, in the example + * above the object could be a Poly type. + */ + +/* + * completely unknown models must inherit from this template class; + * example: the model a*x^2 + b*x + c = 0 to be solved wrt a, b, c; + * example: the model A(t) = known_sample_value_at(t) to be solved wrt + * the coefficients of the curve A(t) expressed in S-Basis form; + * parameter type: the type of x and t variable in the examples above; + * value type: the type of the known sample values (in the first example + * is constant ) + * instance type: the type of the objects produced by using + * the fitting raw data solution + */ + + + + +template< typename ParameterType, typename ValueType, typename InstanceType > +class LinearFittingModel +{ + public: + typedef ParameterType parameter_type; + typedef ValueType value_type; + typedef InstanceType instance_type; + + static const bool WITH_FIXED_TERMS = false; + + /* + * a LinearFittingModel must implement the following methods: + * + * void feed( VectorView & vector, + * parameter_type const& sample_parameter ) const; + * + * size_t size() const; + * + * void instance(instance_type &, raw_type const& raw_data) const; + * + */ +}; + + +/* + * partially known models must inherit from this template class + * example: the model a*x^2 + 2*x + c = 0 to be solved wrt a and c + */ +template< typename ParameterType, typename ValueType, typename InstanceType > +class LinearFittingModelWithFixedTerms +{ + public: + typedef ParameterType parameter_type; + typedef ValueType value_type; + typedef InstanceType instance_type; + + static const bool WITH_FIXED_TERMS = true; + + /* + * a LinearFittingModelWithFixedTerms must implement the following methods: + * + * void feed( VectorView & vector, + * value_type & fixed_term, + * parameter_type const& sample_parameter ) const; + * + * size_t size() const; + * + * void instance(instance_type &, raw_type const& raw_data) const; + * + */ + + +}; + + +// incomplete model, it can be inherited to make up different kinds of +// instance type; the raw data is a vector of coefficients of a polynomial +// represented in standard power basis +template< typename InstanceType > +class LFMPowerBasis + : public LinearFittingModel<double, double, InstanceType> +{ + public: + LFMPowerBasis(size_t degree) + : m_size(degree + 1) + { + } + + void feed( VectorView & coeff, double sample_parameter ) const + { + coeff[0] = 1; + double x_i = 1; + for (size_t i = 1; i < coeff.size(); ++i) + { + x_i *= sample_parameter; + coeff[i] = x_i; + } + } + + size_t size() const + { + return m_size; + } + + private: + size_t m_size; +}; + + +// this model generates Geom::Poly objects +class LFMPoly + : public LFMPowerBasis<Poly> +{ + public: + LFMPoly(size_t degree) + : LFMPowerBasis<Poly>(degree) + { + } + + void instance(Poly & poly, ConstVectorView const& raw_data) const + { + poly.clear(); + poly.resize(size()); + for (size_t i = 0; i < raw_data.size(); ++i) + { + poly[i] = raw_data[i]; + } + } +}; + + +// incomplete model, it can be inherited to make up different kinds of +// instance type; the raw data is a vector of coefficients of a polynomial +// represented in standard power basis with leading term coefficient equal to 1 +template< typename InstanceType > +class LFMNormalizedPowerBasis + : public LinearFittingModelWithFixedTerms<double, double, InstanceType> +{ + public: + LFMNormalizedPowerBasis(size_t _degree) + : m_model( _degree - 1) + { + assert(_degree > 0); + } + + + void feed( VectorView & coeff, + double & known_term, + double sample_parameter ) const + { + m_model.feed(coeff, sample_parameter); + known_term = coeff[m_model.size()-1] * sample_parameter; + } + + size_t size() const + { + return m_model.size(); + } + + private: + LFMPowerBasis<InstanceType> m_model; +}; + + +// incomplete model, it can be inherited to make up different kinds of +// instance type; the raw data is a vector of coefficients of the equation +// of an ellipse curve +//template< typename InstanceType > +//class LFMEllipseEquation +// : public LinearFittingModelWithFixedTerms<Point, double, InstanceType> +//{ +// public: +// void feed( VectorView & coeff, double & fixed_term, Point const& p ) const +// { +// coeff[0] = p[X] * p[Y]; +// coeff[1] = p[Y] * p[Y]; +// coeff[2] = p[X]; +// coeff[3] = p[Y]; +// coeff[4] = 1; +// fixed_term = p[X] * p[X]; +// } +// +// size_t size() const +// { +// return 5; +// } +//}; + +// incomplete model, it can be inherited to make up different kinds of +// instance type; the raw data is a vector of coefficients of the equation +// of a conic section +template< typename InstanceType > +class LFMConicEquation + : public LinearFittingModelWithFixedTerms<Point, double, InstanceType> +{ + public: + void feed( VectorView & coeff, double & fixed_term, Point const& p ) const + { + coeff[0] = p[X] * p[Y]; + coeff[1] = p[Y] * p[Y]; + coeff[2] = p[X]; + coeff[3] = p[Y]; + coeff[4] = 1; + fixed_term = p[X] * p[X]; + } + + size_t size() const + { + return 5; + } +}; + +// this model generates Ellipse curves +class LFMConicSection + : public LFMConicEquation<xAx> +{ + public: + void instance(xAx & c, ConstVectorView const& coeff) const + { + c.set(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]); + } +}; + +// this model generates Ellipse curves +class LFMEllipse + : public LFMConicEquation<Ellipse> +{ + public: + void instance(Ellipse & e, ConstVectorView const& coeff) const + { + e.setCoefficients(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]); + } +}; + + +// incomplete model, it can be inherited to make up different kinds of +// instance type; the raw data is a vector of coefficients of the equation +// of a circle curve +template< typename InstanceType > +class LFMCircleEquation + : public LinearFittingModelWithFixedTerms<Point, double, InstanceType> +{ + public: + void feed( VectorView & coeff, double & fixed_term, Point const& p ) const + { + coeff[0] = p[X]; + coeff[1] = p[Y]; + coeff[2] = 1; + fixed_term = p[X] * p[X] + p[Y] * p[Y]; + } + + size_t size() const + { + return 3; + } +}; + + +// this model generates Ellipse curves +class LFMCircle + : public LFMCircleEquation<Circle> +{ + public: + void instance(Circle & c, ConstVectorView const& coeff) const + { + c.setCoefficients(1, coeff[0], coeff[1], coeff[2]); + } +}; + + +// this model generates SBasis objects +class LFMSBasis + : public LinearFittingModel<double, double, SBasis> +{ + public: + LFMSBasis( size_t _order ) + : m_size( 2*(_order+1) ), + m_order(_order) + { + } + + void feed( VectorView & coeff, double t ) const + { + double u0 = 1-t; + double u1 = t; + double s = u0 * u1; + coeff[0] = u0; + coeff[1] = u1; + for (size_t i = 2; i < size(); i+=2) + { + u0 *= s; + u1 *= s; + coeff[i] = u0; + coeff[i+1] = u1; + } + } + + size_t size() const + { + return m_size; + } + + void instance(SBasis & sb, ConstVectorView const& raw_data) const + { + sb.resize(m_order+1); + for (unsigned int i = 0, k = 0; i < raw_data.size(); i+=2, ++k) + { + sb[k][0] = raw_data[i]; + sb[k][1] = raw_data[i+1]; + } + } + + private: + size_t m_size; + size_t m_order; +}; + + +// this model generates D2<SBasis> objects +class LFMD2SBasis + : public LinearFittingModel< double, Point, D2<SBasis> > +{ + public: + LFMD2SBasis( size_t _order ) + : mosb(_order) + { + } + + void feed( VectorView & coeff, double t ) const + { + mosb.feed(coeff, t); + } + + size_t size() const + { + return mosb.size(); + } + + void instance(D2<SBasis> & d2sb, ConstMatrixView const& raw_data) const + { + mosb.instance(d2sb[X], raw_data.column_const_view(X)); + mosb.instance(d2sb[Y], raw_data.column_const_view(Y)); + } + + private: + LFMSBasis mosb; +}; + + +// this model generates Bezier objects +class LFMBezier + : public LinearFittingModel<double, double, Bezier> +{ + public: + LFMBezier( size_t _order ) + : m_size(_order + 1), + m_order(_order) + { + binomial_coefficients(m_bc, m_order); + } + + void feed( VectorView & coeff, double t ) const + { + double s = 1; + for (size_t i = 0; i < size(); ++i) + { + coeff[i] = s * m_bc[i]; + s *= t; + } + double u = 1-t; + s = 1; + for (size_t i = size()-1; i > 0; --i) + { + coeff[i] *= s; + s *= u; + } + coeff[0] *= s; + } + + size_t size() const + { + return m_size; + } + + void instance(Bezier & b, ConstVectorView const& raw_data) const + { + assert(b.size() == raw_data.size()); + for (unsigned int i = 0; i < raw_data.size(); ++i) + { + b[i] = raw_data[i]; + } + } + + private: + size_t m_size; + size_t m_order; + std::vector<size_t> m_bc; +}; + + +// this model generates Bezier curves +template <unsigned degree> +class LFMBezierCurveN + : public LinearFittingModel< double, Point, BezierCurveN<degree> > +{ + public: + LFMBezierCurveN() + : mob(degree+1) + { + } + + void feed( VectorView & coeff, double t ) const + { + mob.feed(coeff, t); + } + + size_t size() const + { + return mob.size(); + } + + void instance(BezierCurveN<degree> & bc, ConstMatrixView const& raw_data) const + { + Bezier bx(degree); + Bezier by(degree); + mob.instance(bx, raw_data.column_const_view(X)); + mob.instance(by, raw_data.column_const_view(Y)); + bc = BezierCurveN<degree>(bx, by); + } + + private: + LFMBezier mob; +}; + +} // end namespace NL +} // end namespace Geom + + +#endif // _NL_FITTING_MODEL_H_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/numeric/fitting-tool.h b/include/2geom/numeric/fitting-tool.h new file mode 100644 index 0000000..78d66ca --- /dev/null +++ b/include/2geom/numeric/fitting-tool.h @@ -0,0 +1,562 @@ +/* + * Fitting Tools + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef _NL_FITTING_TOOL_H_ +#define _NL_FITTING_TOOL_H_ + + +#include <2geom/numeric/vector.h> +#include <2geom/numeric/matrix.h> + +#include <2geom/point.h> + +#include <vector> + + +/* + * The least_square_fitter class represents a tool for solving a fitting + * problem with respect to a given model that represents an expression + * dependent from a parameter where the coefficients of this expression + * are the unknowns of the fitting problem. + * The minimizing solution is found by computing the pseudo-inverse + * of the problem matrix + */ + + +namespace Geom { namespace NL { + +namespace detail { + +template< typename ModelT> +class lsf_base +{ + public: + typedef ModelT model_type; + typedef typename model_type::parameter_type parameter_type; + typedef typename model_type::value_type value_type; + + lsf_base( model_type const& _model, size_t forecasted_samples ) + : m_model(_model), + m_total_samples(0), + m_matrix(forecasted_samples, m_model.size()), + m_psdinv_matrix(NULL) + { + } + + // compute pseudo inverse + void update() + { + if (total_samples() == 0) return; + if (m_psdinv_matrix != NULL) + { + delete m_psdinv_matrix; + } + MatrixView mv(m_matrix, 0, 0, total_samples(), m_matrix.columns()); + m_psdinv_matrix = new Matrix( pseudo_inverse(mv) ); + assert(m_psdinv_matrix != NULL); + } + + size_t total_samples() const + { + return m_total_samples; + } + + bool is_full() const + { + return (total_samples() == m_matrix.rows()); + } + + void clear() + { + m_total_samples = 0; + } + + virtual + ~lsf_base() + { + if (m_psdinv_matrix != NULL) + { + delete m_psdinv_matrix; + } + } + + protected: + const model_type & m_model; + size_t m_total_samples; + Matrix m_matrix; + Matrix* m_psdinv_matrix; + +}; // end class lsf_base + + + + +template< typename ModelT, typename ValueType = typename ModelT::value_type> +class lsf_solution +{ +}; + +// a fitting process on samples with value of type double +// produces a solution of type Vector +template< typename ModelT> +class lsf_solution<ModelT, double> + : public lsf_base<ModelT> +{ +public: + typedef ModelT model_type; + typedef typename model_type::parameter_type parameter_type; + typedef typename model_type::value_type value_type; + typedef Vector solution_type; + typedef lsf_base<model_type> base_type; + + using base_type::m_model; + using base_type::m_psdinv_matrix; + using base_type::total_samples; + +public: + lsf_solution<ModelT, double>( model_type const& _model, + size_t forecasted_samples ) + : base_type(_model, forecasted_samples), + m_solution(_model.size()) + { + } + + template< typename VectorT > + solution_type& result(VectorT const& sample_values) + { + assert(sample_values.size() == total_samples()); + ConstVectorView sv(sample_values); + m_solution = (*m_psdinv_matrix) * sv; + return m_solution; + } + + // a comparison between old sample values and the new ones is performed + // in order to minimize computation + // prerequisite: + // old_sample_values.size() == new_sample_values.size() + // no update() call can be performed between two result invocations + template< typename VectorT > + solution_type& result( VectorT const& old_sample_values, + VectorT const& new_sample_values ) + { + assert(old_sample_values.size() == total_samples()); + assert(new_sample_values.size() == total_samples()); + Vector diff(total_samples()); + for (size_t i = 0; i < diff.size(); ++i) + { + diff[i] = new_sample_values[i] - old_sample_values[i]; + } + Vector column(m_model.size()); + Vector delta(m_model.size(), 0.0); + for (size_t i = 0; i < diff.size(); ++i) + { + if (diff[i] != 0) + { + column = m_psdinv_matrix->column_view(i); + column.scale(diff[i]); + delta += column; + } + } + m_solution += delta; + return m_solution; + } + + solution_type& result() + { + return m_solution; + } + +private: + solution_type m_solution; + +}; // end class lsf_solution<ModelT, double> + + +// a fitting process on samples with value of type Point +// produces a solution of type Matrix (with 2 columns) +template< typename ModelT> +class lsf_solution<ModelT, Point> + : public lsf_base<ModelT> +{ +public: + typedef ModelT model_type; + typedef typename model_type::parameter_type parameter_type; + typedef typename model_type::value_type value_type; + typedef Matrix solution_type; + typedef lsf_base<model_type> base_type; + + using base_type::m_model; + using base_type::m_psdinv_matrix; + using base_type::total_samples; + +public: + lsf_solution<ModelT, Point>( model_type const& _model, + size_t forecasted_samples ) + : base_type(_model, forecasted_samples), + m_solution(_model.size(), 2) + { + } + + solution_type& result(std::vector<Point> const& sample_values) + { + assert(sample_values.size() == total_samples()); + Matrix svm(total_samples(), 2); + for (size_t i = 0; i < total_samples(); ++i) + { + svm(i, X) = sample_values[i][X]; + svm(i, Y) = sample_values[i][Y]; + } + m_solution = (*m_psdinv_matrix) * svm; + return m_solution; + } + + // a comparison between old sample values and the new ones is performed + // in order to minimize computation + // prerequisite: + // old_sample_values.size() == new_sample_values.size() + // no update() call can to be performed between two result invocations + solution_type& result( std::vector<Point> const& old_sample_values, + std::vector<Point> const& new_sample_values ) + { + assert(old_sample_values.size() == total_samples()); + assert(new_sample_values.size() == total_samples()); + Matrix diff(total_samples(), 2); + for (size_t i = 0; i < total_samples(); ++i) + { + diff(i, X) = new_sample_values[i][X] - old_sample_values[i][X]; + diff(i, Y) = new_sample_values[i][Y] - old_sample_values[i][Y]; + } + Vector column(m_model.size()); + Matrix delta(m_model.size(), 2, 0.0); + VectorView deltax = delta.column_view(X); + VectorView deltay = delta.column_view(Y); + for (size_t i = 0; i < total_samples(); ++i) + { + if (diff(i, X) != 0) + { + column = m_psdinv_matrix->column_view(i); + column.scale(diff(i, X)); + deltax += column; + } + if (diff(i, Y) != 0) + { + column = m_psdinv_matrix->column_view(i); + column.scale(diff(i, Y)); + deltay += column; + } + } + m_solution += delta; + return m_solution; + } + + solution_type& result() + { + return m_solution; + } + +private: + solution_type m_solution; + +}; // end class lsf_solution<ModelT, Point> + + + + +template< typename ModelT, + bool WITH_FIXED_TERMS = ModelT::WITH_FIXED_TERMS > +class lsf_with_fixed_terms +{ +}; + + +// fitting tool for completely unknown models +template< typename ModelT> +class lsf_with_fixed_terms<ModelT, false> + : public lsf_solution<ModelT> +{ + public: + typedef ModelT model_type; + typedef typename model_type::parameter_type parameter_type; + typedef typename model_type::value_type value_type; + typedef lsf_solution<model_type> base_type; + typedef typename base_type::solution_type solution_type; + + using base_type::total_samples; + using base_type::is_full; + using base_type::m_matrix; + using base_type::m_total_samples; + using base_type::m_model; + + public: + lsf_with_fixed_terms<ModelT, false>( model_type const& _model, + size_t forecasted_samples ) + : base_type(_model, forecasted_samples) + { + } + + void append(parameter_type const& sample_parameter) + { + assert(!is_full()); + VectorView row = m_matrix.row_view(total_samples()); + m_model.feed(row, sample_parameter); + ++m_total_samples; + } + + void append_copy(size_t sample_index) + { + assert(!is_full()); + assert(sample_index < total_samples()); + VectorView dest_row = m_matrix.row_view(total_samples()); + VectorView source_row = m_matrix.row_view(sample_index); + dest_row = source_row; + ++m_total_samples; + } + +}; // end class lsf_with_fixed_terms<ModelT, false> + + +// fitting tool for partially known models +template< typename ModelT> +class lsf_with_fixed_terms<ModelT, true> + : public lsf_solution<ModelT> +{ + public: + typedef ModelT model_type; + typedef typename model_type::parameter_type parameter_type; + typedef typename model_type::value_type value_type; + typedef lsf_solution<model_type> base_type; + typedef typename base_type::solution_type solution_type; + + using base_type::total_samples; + using base_type::is_full; + using base_type::m_matrix; + using base_type::m_total_samples; + using base_type::m_model; + + public: + lsf_with_fixed_terms<ModelT, true>( model_type const& _model, + size_t forecasted_samples ) + : base_type(_model, forecasted_samples), + m_vector(forecasted_samples), + m_vector_view(NULL) + { + } + void append(parameter_type const& sample_parameter) + { + assert(!is_full()); + VectorView row = m_matrix.row_view(total_samples()); + m_model.feed(row, m_vector[total_samples()], sample_parameter); + ++m_total_samples; + } + + void append_copy(size_t sample_index) + { + assert(!is_full()); + assert(sample_index < total_samples()); + VectorView dest_row = m_matrix.row_view(total_samples()); + VectorView source_row = m_matrix.row_view(sample_index); + dest_row = source_row; + m_vector[total_samples()] = m_vector[sample_index]; + ++m_total_samples; + } + + void update() + { + base_type::update(); + if (total_samples() == 0) return; + if (m_vector_view != NULL) + { + delete m_vector_view; + } + m_vector_view = new VectorView(m_vector, base_type::total_samples()); + assert(m_vector_view != NULL); + } + + + ~lsf_with_fixed_terms<model_type, true>() override + { + if (m_vector_view != NULL) + { + delete m_vector_view; + } + } + + protected: + Vector m_vector; + VectorView* m_vector_view; + +}; // end class lsf_with_fixed_terms<ModelT, true> + + +} // end namespace detail + + + + +template< typename ModelT, + typename ValueType = typename ModelT::value_type, + bool WITH_FIXED_TERMS = ModelT::WITH_FIXED_TERMS > +class least_squeares_fitter +{ +}; + + +template< typename ModelT, typename ValueType > +class least_squeares_fitter<ModelT, ValueType, false> + : public detail::lsf_with_fixed_terms<ModelT> +{ + public: + typedef ModelT model_type; + typedef detail::lsf_with_fixed_terms<model_type> base_type; + typedef typename base_type::parameter_type parameter_type; + typedef typename base_type::value_type value_type; + typedef typename base_type::solution_type solution_type; + + public: + least_squeares_fitter<ModelT, ValueType, false>( model_type const& _model, + size_t forecasted_samples ) + : base_type(_model, forecasted_samples) + { + } +}; // end class least_squeares_fitter<ModelT, ValueType, true> + + +template< typename ModelT> +class least_squeares_fitter<ModelT, double, true> + : public detail::lsf_with_fixed_terms<ModelT> +{ + public: + typedef ModelT model_type; + typedef detail::lsf_with_fixed_terms<model_type> base_type; + typedef typename base_type::parameter_type parameter_type; + typedef typename base_type::value_type value_type; + typedef typename base_type::solution_type solution_type; + + using base_type::m_vector_view; + //using base_type::result; // VSC legacy support + solution_type& result( std::vector<Point> const& old_sample_values, + std::vector<Point> const& new_sample_values ) + { + return base_type::result(old_sample_values, new_sample_values); + } + + solution_type& result() + { + return base_type::result(); + } + + public: + least_squeares_fitter<ModelT, double, true>( model_type const& _model, + size_t forecasted_samples ) + : base_type(_model, forecasted_samples) + { + } + + template< typename VectorT > + solution_type& result(VectorT const& sample_values) + { + assert(sample_values.size() == m_vector_view->size()); + Vector sv(sample_values.size()); + for (size_t i = 0; i < sv.size(); ++i) + sv[i] = sample_values[i] - (*m_vector_view)[i]; + return base_type::result(sv); + } + +}; // end class least_squeares_fitter<ModelT, double, true> + + +template< typename ModelT> +class least_squeares_fitter<ModelT, Point, true> + : public detail::lsf_with_fixed_terms<ModelT> +{ + public: + typedef ModelT model_type; + typedef detail::lsf_with_fixed_terms<model_type> base_type; + typedef typename base_type::parameter_type parameter_type; + typedef typename base_type::value_type value_type; + typedef typename base_type::solution_type solution_type; + + using base_type::m_vector_view; + //using base_type::result; // VCS legacy support + solution_type& result( std::vector<Point> const& old_sample_values, + std::vector<Point> const& new_sample_values ) + { + return base_type::result(old_sample_values, new_sample_values); + } + + solution_type& result() + { + return base_type::result(); + } + + + public: + least_squeares_fitter<ModelT, Point, true>( model_type const& _model, + size_t forecasted_samples ) + : base_type(_model, forecasted_samples) + { + } + + solution_type& result(std::vector<Point> const& sample_values) + { + assert(sample_values.size() == m_vector_view->size()); + NL::Matrix sv(sample_values.size(), 2); + for (size_t i = 0; i < sample_values.size(); ++i) + { + sv(i, X) = sample_values[i][X] - (*m_vector_view)[i]; + sv(i, Y) = sample_values[i][Y] - (*m_vector_view)[i]; + } + return base_type::result(sv); + } + +}; // end class least_squeares_fitter<ModelT, Point, true> + + +} // end namespace NL +} // end namespace Geom + + + +#endif // _NL_FITTING_TOOL_H_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/numeric/linear_system.h b/include/2geom/numeric/linear_system.h new file mode 100644 index 0000000..f793e20 --- /dev/null +++ b/include/2geom/numeric/linear_system.h @@ -0,0 +1,138 @@ +/* + * LinearSystem class wraps some gsl routines for solving linear systems + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef _NL_LINEAR_SYSTEM_H_ +#define _NL_LINEAR_SYSTEM_H_ + + +#include <cassert> + +#include <gsl/gsl_linalg.h> + +#include <2geom/numeric/matrix.h> +#include <2geom/numeric/vector.h> + + +namespace Geom { namespace NL { + + +class LinearSystem +{ +public: + LinearSystem(MatrixView & _matrix, VectorView & _vector) + : m_matrix(_matrix), m_vector(_vector), m_solution(_matrix.columns()) + { + } + + LinearSystem(Matrix & _matrix, Vector & _vector) + : m_matrix(_matrix), m_vector(_vector), m_solution(_matrix.columns()) + { + } + + const Vector & LU_solve() + { + assert( matrix().rows() == matrix().columns() + && matrix().rows() == vector().size() ); + int s; + gsl_permutation * p = gsl_permutation_alloc(matrix().rows()); + gsl_linalg_LU_decomp (matrix().get_gsl_matrix(), p, &s); + gsl_linalg_LU_solve( matrix().get_gsl_matrix(), + p, + vector().get_gsl_vector(), + m_solution.get_gsl_vector() + ); + gsl_permutation_free(p); + return solution(); + } + + const Vector & SV_solve() + { + assert( matrix().rows() >= matrix().columns() + && matrix().rows() == vector().size() ); + + gsl_matrix* U = matrix().get_gsl_matrix(); + gsl_matrix* V = gsl_matrix_alloc(matrix().columns(), matrix().columns()); + gsl_vector* S = gsl_vector_alloc(matrix().columns()); + gsl_vector* work = gsl_vector_alloc(matrix().columns()); + + gsl_linalg_SV_decomp( U, V, S, work ); + + gsl_vector* b = vector().get_gsl_vector(); + gsl_vector* x = m_solution.get_gsl_vector(); + + gsl_linalg_SV_solve( U, V, S, b, x); + + gsl_matrix_free(V); + gsl_vector_free(S); + gsl_vector_free(work); + + return solution(); + } + + MatrixView & matrix() + { + return m_matrix; + } + + VectorView & vector() + { + return m_vector; + } + + const Vector & solution() const + { + return m_solution; + } + +private: + MatrixView m_matrix; + VectorView m_vector; + Vector m_solution; +}; + + +} } // end namespaces + + +#endif /*_NL_LINEAR_SYSTEM_H_*/ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/numeric/matrix.h b/include/2geom/numeric/matrix.h new file mode 100644 index 0000000..02851b4 --- /dev/null +++ b/include/2geom/numeric/matrix.h @@ -0,0 +1,603 @@ +/* + * Matrix, MatrixView, ConstMatrixView classes wrap the gsl matrix routines; + * "views" mimic the semantic of C++ references: any operation performed + * on a "view" is actually performed on the "viewed object" + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + + + +#ifndef _NL_MATRIX_H_ +#define _NL_MATRIX_H_ + +#include <2geom/exception.h> +#include <2geom/numeric/vector.h> + +#include <cassert> +#include <utility> // for std::pair +#include <algorithm> // for std::swap +#include <sstream> +#include <string> +#include <gsl/gsl_matrix.h> +#include <gsl/gsl_linalg.h> + + +namespace Geom { namespace NL { + +namespace detail +{ + +class BaseMatrixImpl +{ + public: + virtual ~BaseMatrixImpl() + { + } + + ConstVectorView row_const_view(size_t i) const + { + return ConstVectorView(gsl_matrix_const_row(m_matrix, i)); + } + + ConstVectorView column_const_view(size_t i) const + { + return ConstVectorView(gsl_matrix_const_column(m_matrix, i)); + } + + const double & operator() (size_t i, size_t j) const + { + return *gsl_matrix_const_ptr(m_matrix, i, j); + } + + const gsl_matrix* get_gsl_matrix() const + { + return m_matrix; + } + + bool is_zero() const + { + return gsl_matrix_isnull(m_matrix); + } + + bool is_positive() const + { + for ( unsigned int i = 0; i < rows(); ++i ) + { + for ( unsigned int j = 0; j < columns(); ++j ) + { + if ( (*this)(i,j) <= 0 ) return false; + } + } + return true; + } + + bool is_negative() const + { + for ( unsigned int i = 0; i < rows(); ++i ) + { + for ( unsigned int j = 0; j < columns(); ++j ) + { + if ( (*this)(i,j) >= 0 ) return false; + } + } + return true; + } + + bool is_non_negative() const + { + for ( unsigned int i = 0; i < rows(); ++i ) + { + for ( unsigned int j = 0; j < columns(); ++j ) + { + if ( (*this)(i,j) < 0 ) return false; + } + } + return true; + } + + double max() const + { + return gsl_matrix_max(m_matrix); + } + + double min() const + { + return gsl_matrix_min(m_matrix); + } + + std::pair<size_t, size_t> + max_index() const + { + std::pair<size_t, size_t> indices; + gsl_matrix_max_index(m_matrix, &(indices.first), &(indices.second)); + return indices; + } + + std::pair<size_t, size_t> + min_index() const + { + std::pair<size_t, size_t> indices; + gsl_matrix_min_index(m_matrix, &(indices.first), &(indices.second)); + return indices; + } + + size_t rows() const + { + return m_rows; + } + + size_t columns() const + { + return m_columns; + } + + std::string str() const; + + protected: + size_t m_rows, m_columns; + gsl_matrix* m_matrix; + +}; // end class BaseMatrixImpl + + +inline +bool operator== (BaseMatrixImpl const& m1, BaseMatrixImpl const& m2) +{ + if (m1.rows() != m2.rows() || m1.columns() != m2.columns()) return false; + + for (size_t i = 0; i < m1.rows(); ++i) + for (size_t j = 0; j < m1.columns(); ++j) + if (m1(i,j) != m2(i,j)) return false; + + return true; +} + +template< class charT > +inline +std::basic_ostream<charT> & +operator<< (std::basic_ostream<charT> & os, const BaseMatrixImpl & _matrix) +{ + if (_matrix.rows() == 0 || _matrix.columns() == 0) return os; + + os << "[[" << _matrix(0,0); + for (size_t j = 1; j < _matrix.columns(); ++j) + { + os << ", " << _matrix(0,j); + } + os << "]"; + + for (size_t i = 1; i < _matrix.rows(); ++i) + { + os << ", [" << _matrix(i,0); + for (size_t j = 1; j < _matrix.columns(); ++j) + { + os << ", " << _matrix(i,j); + } + os << "]"; + } + os << "]"; + return os; +} + +inline +std::string BaseMatrixImpl::str() const +{ + std::ostringstream oss; + oss << (*this); + return oss.str(); +} + + +class MatrixImpl : public BaseMatrixImpl +{ + public: + + typedef BaseMatrixImpl base_type; + + void set_all( double x ) + { + gsl_matrix_set_all(m_matrix, x); + } + + void set_identity() + { + gsl_matrix_set_identity(m_matrix); + } + + using base_type::operator(); // VSC legacy support + const double & operator() (size_t i, size_t j) const + { + return base_type::operator ()(i, j); + } + + double & operator() (size_t i, size_t j) + { + return *gsl_matrix_ptr(m_matrix, i, j); + } + + using base_type::get_gsl_matrix; + + gsl_matrix* get_gsl_matrix() + { + return m_matrix; + } + + VectorView row_view(size_t i) + { + return VectorView(gsl_matrix_row(m_matrix, i)); + } + + VectorView column_view(size_t i) + { + return VectorView(gsl_matrix_column(m_matrix, i)); + } + + void swap_rows(size_t i, size_t j) + { + gsl_matrix_swap_rows(m_matrix, i, j); + } + + void swap_columns(size_t i, size_t j) + { + gsl_matrix_swap_columns(m_matrix, i, j); + } + + MatrixImpl & transpose() + { + assert(columns() == rows()); + gsl_matrix_transpose(m_matrix); + return (*this); + } + + MatrixImpl & scale(double x) + { + gsl_matrix_scale(m_matrix, x); + return (*this); + } + + MatrixImpl & translate(double x) + { + gsl_matrix_add_constant(m_matrix, x); + return (*this); + } + + MatrixImpl & operator+=(base_type const& _matrix) + { + gsl_matrix_add(m_matrix, _matrix.get_gsl_matrix()); + return (*this); + } + + MatrixImpl & operator-=(base_type const& _matrix) + { + gsl_matrix_sub(m_matrix, _matrix.get_gsl_matrix()); + return (*this); + } + +}; // end class MatrixImpl + +} // end namespace detail + + +using detail::operator==; +using detail::operator<<; + + +template <size_t N> +class ConstBaseSymmetricMatrix; + + +class Matrix: public detail::MatrixImpl +{ + public: + typedef detail::MatrixImpl base_type; + + public: + // the matrix is not initialized + Matrix(size_t n1, size_t n2) + { + m_rows = n1; + m_columns = n2; + m_matrix = gsl_matrix_alloc(n1, n2); + } + + Matrix(size_t n1, size_t n2, double x) + { + m_rows = n1; + m_columns = n2; + m_matrix = gsl_matrix_alloc(n1, n2); + gsl_matrix_set_all(m_matrix, x); + } + + Matrix(Matrix const& _matrix) + : base_type() + { + m_rows = _matrix.rows(); + m_columns = _matrix.columns(); + m_matrix = gsl_matrix_alloc(rows(), columns()); + gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix()); + } + + explicit + Matrix(base_type::base_type const& _matrix) + { + m_rows = _matrix.rows(); + m_columns = _matrix.columns(); + m_matrix = gsl_matrix_alloc(rows(), columns()); + gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix()); + } + + template <size_t N> + explicit + Matrix(ConstBaseSymmetricMatrix<N> const& _smatrix) + { + m_rows = N; + m_columns = N; + m_matrix = gsl_matrix_alloc(N, N); + for (size_t i = 0; i < N; ++i) + for (size_t j = 0; j < N ; ++j) + (*gsl_matrix_ptr(m_matrix, i, j)) = _smatrix(i,j); + } + + Matrix & operator=(Matrix const& _matrix) + { + assert( rows() == _matrix.rows() && columns() == _matrix.columns() ); + gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix()); + return *this; + } + + Matrix & operator=(base_type::base_type const& _matrix) + { + assert( rows() == _matrix.rows() && columns() == _matrix.columns() ); + gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix()); + return *this; + } + + template <size_t N> + Matrix & operator=(ConstBaseSymmetricMatrix<N> const& _smatrix) + { + assert (rows() == N && columns() == N); + for (size_t i = 0; i < N; ++i) + for (size_t j = 0; j < N ; ++j) + (*this)(i,j) = _smatrix(i,j); + return *this; + } + + ~Matrix() override + { + gsl_matrix_free(m_matrix); + } + + Matrix & transpose() + { + return static_cast<Matrix &>( base_type::transpose() ); + } + + Matrix & scale(double x) + { + return static_cast<Matrix &>( base_type::scale(x) ); + } + + Matrix & translate(double x) + { + return static_cast<Matrix &>( base_type::translate(x) ); + } + + Matrix & operator+=(base_type::base_type const& _matrix) + { + return static_cast<Matrix &>( base_type::operator+=(_matrix) ); + } + + Matrix & operator-=(base_type::base_type const& _matrix) + { + return static_cast<Matrix &>( base_type::operator-=(_matrix) ); + } + + friend + void swap(Matrix & m1, Matrix & m2); + friend + void swap_any(Matrix & m1, Matrix & m2); + +}; // end class Matrix + + +// warning! this operation invalidates any view of the passed matrix objects +inline +void swap(Matrix & m1, Matrix & m2) +{ + assert(m1.rows() == m2.rows() && m1.columns() == m2.columns()); + using std::swap; + swap(m1.m_matrix, m2.m_matrix); +} + +inline void swap_any(Matrix &m1, Matrix &m2) +{ + using std::swap; + swap(m1.m_matrix, m2.m_matrix); + swap(m1.m_rows, m2.m_rows); + swap(m1.m_columns, m2.m_columns); +} + + + +class ConstMatrixView : public detail::BaseMatrixImpl +{ + public: + typedef detail::BaseMatrixImpl base_type; + + public: + ConstMatrixView(const base_type & _matrix, size_t k1, size_t k2, size_t n1, size_t n2) + : m_matrix_view( gsl_matrix_const_submatrix(_matrix.get_gsl_matrix(), k1, k2, n1, n2) ) + { + m_rows = n1; + m_columns = n2; + m_matrix = const_cast<gsl_matrix*>( &(m_matrix_view.matrix) ); + } + + ConstMatrixView(const ConstMatrixView & _matrix) + : base_type(), + m_matrix_view(_matrix.m_matrix_view) + { + m_rows = _matrix.rows(); + m_columns = _matrix.columns(); + m_matrix = const_cast<gsl_matrix*>( &(m_matrix_view.matrix) ); + } + + ConstMatrixView(const base_type & _matrix) + : m_matrix_view(gsl_matrix_const_submatrix(_matrix.get_gsl_matrix(), 0, 0, _matrix.rows(), _matrix.columns())) + { + m_rows = _matrix.rows(); + m_columns = _matrix.columns(); + m_matrix = const_cast<gsl_matrix*>( &(m_matrix_view.matrix) ); + } + + private: + gsl_matrix_const_view m_matrix_view; + +}; // end class ConstMatrixView + + + + +class MatrixView : public detail::MatrixImpl +{ + public: + typedef detail::MatrixImpl base_type; + + public: + MatrixView(base_type & _matrix, size_t k1, size_t k2, size_t n1, size_t n2) + { + m_rows = n1; + m_columns = n2; + m_matrix_view + = gsl_matrix_submatrix(_matrix.get_gsl_matrix(), k1, k2, n1, n2); + m_matrix = &(m_matrix_view.matrix); + } + + MatrixView(const MatrixView & _matrix) + : base_type() + { + m_rows = _matrix.rows(); + m_columns = _matrix.columns(); + m_matrix_view = _matrix.m_matrix_view; + m_matrix = &(m_matrix_view.matrix); + } + + MatrixView(Matrix & _matrix) + { + m_rows = _matrix.rows(); + m_columns = _matrix.columns(); + m_matrix_view + = gsl_matrix_submatrix(_matrix.get_gsl_matrix(), 0, 0, rows(), columns()); + m_matrix = &(m_matrix_view.matrix); + } + + MatrixView & operator=(MatrixView const& _matrix) + { + assert( rows() == _matrix.rows() && columns() == _matrix.columns() ); + gsl_matrix_memcpy(m_matrix, _matrix.m_matrix); + return *this; + } + + MatrixView & operator=(base_type::base_type const& _matrix) + { + assert( rows() == _matrix.rows() && columns() == _matrix.columns() ); + gsl_matrix_memcpy(m_matrix, _matrix.get_gsl_matrix()); + return *this; + } + + MatrixView & transpose() + { + return static_cast<MatrixView &>( base_type::transpose() ); + } + + MatrixView & scale(double x) + { + return static_cast<MatrixView &>( base_type::scale(x) ); + } + + MatrixView & translate(double x) + { + return static_cast<MatrixView &>( base_type::translate(x) ); + } + + MatrixView & operator+=(base_type::base_type const& _matrix) + { + return static_cast<MatrixView &>( base_type::operator+=(_matrix) ); + } + + MatrixView & operator-=(base_type::base_type const& _matrix) + { + return static_cast<MatrixView &>( base_type::operator-=(_matrix) ); + } + + friend + void swap_view(MatrixView & m1, MatrixView & m2); + + private: + gsl_matrix_view m_matrix_view; + +}; // end class MatrixView + + +inline +void swap_view(MatrixView & m1, MatrixView & m2) +{ + assert(m1.rows() == m2.rows() && m1.columns() == m2.columns()); + using std::swap; + swap(m1.m_matrix_view, m2.m_matrix_view); +} + +Vector operator*( detail::BaseMatrixImpl const& A, + detail::BaseVectorImpl const& v ); + +Matrix operator*( detail::BaseMatrixImpl const& A, + detail::BaseMatrixImpl const& B ); + +Matrix pseudo_inverse(detail::BaseMatrixImpl const& A); + +double trace (detail::BaseMatrixImpl const& A); + +double det (detail::BaseMatrixImpl const& A); + +} } // end namespaces + +#endif /*_NL_MATRIX_H_*/ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/numeric/symmetric-matrix-fs-operation.h b/include/2geom/numeric/symmetric-matrix-fs-operation.h new file mode 100644 index 0000000..c5aaa72 --- /dev/null +++ b/include/2geom/numeric/symmetric-matrix-fs-operation.h @@ -0,0 +1,102 @@ +/* + * SymmetricMatrix basic operation + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2009 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef _NL_SYMMETRIC_MATRIX_FS_OPERATION_H_ +#define _NL_SYMMETRIC_MATRIX_FS_OPERATION_H_ + + +#include <2geom/numeric/symmetric-matrix-fs.h> +#include <2geom/numeric/symmetric-matrix-fs-trace.h> + + + + +namespace Geom { namespace NL { + +template <size_t N> +SymmetricMatrix<N> adj(const ConstBaseSymmetricMatrix<N> & S); + +template <> +inline +SymmetricMatrix<2> adj(const ConstBaseSymmetricMatrix<2> & S) +{ + SymmetricMatrix<2> result; + result.get<0,0>() = S.get<1,1>(); + result.get<1,0>() = -S.get<1,0>(); + result.get<1,1>() = S.get<0,0>(); + return result; +} + +template <> +inline +SymmetricMatrix<3> adj(const ConstBaseSymmetricMatrix<3> & S) +{ + SymmetricMatrix<3> result; + + result.get<0,0>() = S.get<1,1>() * S.get<2,2>() - S.get<1,2>() * S.get<2,1>(); + result.get<1,0>() = S.get<0,2>() * S.get<2,1>() - S.get<0,1>() * S.get<2,2>(); + result.get<1,1>() = S.get<0,0>() * S.get<2,2>() - S.get<0,2>() * S.get<2,0>(); + result.get<2,0>() = S.get<0,1>() * S.get<1,2>() - S.get<0,2>() * S.get<1,1>(); + result.get<2,1>() = S.get<0,2>() * S.get<1,0>() - S.get<0,0>() * S.get<1,2>(); + result.get<2,2>() = S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>(); + return result; +} + +template <size_t N> +inline +SymmetricMatrix<N> inverse(const ConstBaseSymmetricMatrix<N> & S) +{ + SymmetricMatrix<N> result = adj(S); + double d = det(S); + assert (d != 0); + result.scale (1/d); + return result; +} + +} /* end namespace NL*/ } /* end namespace Geom*/ + + +#endif // _NL_SYMMETRIC_MATRIX_FS_OPERATION_H_ + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/numeric/symmetric-matrix-fs-trace.h b/include/2geom/numeric/symmetric-matrix-fs-trace.h new file mode 100644 index 0000000..0e7a28c --- /dev/null +++ b/include/2geom/numeric/symmetric-matrix-fs-trace.h @@ -0,0 +1,427 @@ +/* + * SymmetricMatrix trace + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2009 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef _NL_TRACE_H_ +#define _NL_TRACE_H_ + + +#include <2geom/numeric/matrix.h> +#include <2geom/numeric/symmetric-matrix-fs.h> + + + + + +namespace Geom { namespace NL { + + +namespace detail +{ + +/* + * helper routines + */ + +inline +int sgn_prod (int x, int y) +{ + if (x == 0 || y == 0) return 0; + if (x == y) return 1; + return -1; +} + +inline +bool abs_less (double x, double y) +{ + return (std::fabs(x) < std::fabs(y)); +} + + +/* + * trace K-th of symmetric matrix S of order N + */ +template <size_t K, size_t N> +struct trace +{ + static double evaluate(const ConstBaseSymmetricMatrix<N> &S); +}; + +template <size_t N> +struct trace<1,N> +{ + static + double evaluate (const ConstBaseSymmetricMatrix<N> & S) + { + double t = 0; + for (size_t i = 0; i < N; ++i) + { + t += S(i,i); + } + return t; + } +}; + +template <size_t N> +struct trace<N,N> +{ + static + double evaluate (const ConstBaseSymmetricMatrix<N> & S) + { + Matrix M(S); + return det(M); + } +}; + +/* + * trace for symmetric matrix of order 2 + */ +template <> +struct trace<1,2> +{ + static + double evaluate (const ConstBaseSymmetricMatrix<2> & S) + { + return (S.get<0,0>() + S.get<1,1>()); + } +}; + +template <> +struct trace<2,2> +{ + static + double evaluate (const ConstBaseSymmetricMatrix<2> & S) + { + return (S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>()); + } +}; + + +/* + * trace for symmetric matrix of order 3 + */ +template <> +struct trace<1,3> +{ + static + double evaluate (const ConstBaseSymmetricMatrix<3> & S) + { + return (S.get<0,0>() + S.get<1,1>() + S.get<2,2>()); + } +}; + +template <> +struct trace<2,3> +{ + static + double evaluate (const ConstBaseSymmetricMatrix<3> & S) + { + double a00 = S.get<1,1>() * S.get<2,2>() - S.get<1,2>() * S.get<2,1>(); + double a11 = S.get<0,0>() * S.get<2,2>() - S.get<0,2>() * S.get<2,0>(); + double a22 = S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>(); + return (a00 + a11 + a22); + } +}; + +template <> +struct trace<3,3> +{ + static + double evaluate (const ConstBaseSymmetricMatrix<3> & S) + { + double d = S.get<0,0>() * S.get<1,1>() * S.get<2,2>(); + d += (2 * S.get<1,0>() * S.get<2,0>() * S.get<2,1>()); + d -= (S.get<0,0>() * S.get<2,1>() * S.get<2,1>()); + d -= (S.get<1,1>() * S.get<2,0>() * S.get<2,0>()); + d -= (S.get<2,2>() * S.get<1,0>() * S.get<1,0>()); + return d; + } +}; + + +/* + * sign of trace K-th + */ +template <size_t K, size_t N> +struct trace_sgn +{ + static + int evaluate (const ConstBaseSymmetricMatrix<N> & S) + { + double d = trace<K, N>::evaluate(S); + return sgn(d); + } +}; + + +/* + * sign of trace for symmetric matrix of order 2 + */ +template <> +struct trace_sgn<2,2> +{ + static + int evaluate (const ConstBaseSymmetricMatrix<2> & S) + { + double m00 = S.get<0,0>(); + double m10 = S.get<1,0>(); + double m11 = S.get<1,1>(); + + int sm00 = sgn (m00); + int sm10 = sgn (m10); + int sm11 = sgn (m11); + + if (sm10 == 0) + { + return sgn_prod (sm00, sm11); + } + else + { + int sm00m11 = sgn_prod (sm00, sm11); + if (sm00m11 == 1) + { + int e00, e10, e11; + double f00 = std::frexp (m00, &e00); + double f10 = std::frexp (m10, &e10); + double f11 = std::frexp (m11, &e11); + + int e0011 = e00 + e11; + int e1010 = e10 << 1; + int ed = e0011 - e1010; + + if (ed > 1) + { + return 1; + } + else if (ed < -1) + { + return -1; + } + else + { + double d = std::ldexp (f00 * f11, ed) - f10 * f10; + //std::cout << "trace_sgn<2,2>: det = " << d << std::endl; + double eps = std::ldexp (1, -50); + if (std::fabs(d) < eps) return 0; + return sgn (d); + } + } + return -1; + } + } +}; + + +/* + * sign of trace for symmetric matrix of order 3 + */ +template <> +struct trace_sgn<2,3> +{ + static + int evaluate (const ConstBaseSymmetricMatrix<3> & S) + { + double eps = std::ldexp (1, -50); + double t[6]; + + t[0] = S.get<1,1>() * S.get<2,2>(); + t[1] = - S.get<1,2>() * S.get<2,1>(); + t[2] = S.get<0,0>() * S.get<2,2>(); + t[3] = - S.get<0,2>() * S.get<2,0>(); + t[4] = S.get<0,0>() * S.get<1,1>(); + t[5] = - S.get<0,1>() * S.get<1,0>(); + + + double* maxp = std::max_element (t, t+6, abs_less); + int em; + std::frexp(*maxp, &em); + double d = 0; + for (double i : t) + { + d += i; + } + double r = std::fabs (std::ldexp (d, -em)); // relative error + //std::cout << "trace_sgn<2,3>: d = " << d << std::endl; + //std::cout << "trace_sgn<2,3>: r = " << r << std::endl; + if (r < eps) return 0; + if (d > 0) return 1; + return -1; + } +}; + +template <> +struct trace_sgn<3,3> +{ + static + int evaluate (const ConstBaseSymmetricMatrix<3> & S) + { + + double eps = std::ldexp (1, -48); + double t[5]; + + t[0] = S.get<0,0>() * S.get<1,1>() * S.get<2,2>(); + t[1] = 2 * S.get<1,0>() * S.get<2,0>() * S.get<2,1>(); + t[2] = -(S.get<0,0>() * S.get<2,1>() * S.get<2,1>()); + t[3] = -(S.get<1,1>() * S.get<2,0>() * S.get<2,0>()); + t[4] = -(S.get<2,2>() * S.get<1,0>() * S.get<1,0>()); + + double* maxp = std::max_element (t, t+5, abs_less); + int em; + std::frexp(*maxp, &em); + double d = 0; + for (double i : t) + { + d += i; + } + //std::cout << "trace_sgn<3,3>: d = " << d << std::endl; + double r = std::fabs (std::ldexp (d, -em)); // relative error + //std::cout << "trace_sgn<3,3>: r = " << r << std::endl; + + if (r < eps) return 0; + if (d > 0) return 1; + return -1; + } +}; // end struct trace_sgn<3,3> + +} // end namespace detail + + +template <size_t K, size_t N> +inline +double trace (const ConstBaseSymmetricMatrix<N> & _matrix) +{ + return detail::trace<K, N>::evaluate(_matrix); +} + +template <size_t N> +inline +double trace (const ConstBaseSymmetricMatrix<N> & _matrix) +{ + return detail::trace<1, N>::evaluate(_matrix); +} + +template <size_t N> +inline +double det (const ConstBaseSymmetricMatrix<N> & _matrix) +{ + return detail::trace<N, N>::evaluate(_matrix); +} + + +template <size_t K, size_t N> +inline +int trace_sgn (const ConstBaseSymmetricMatrix<N> & _matrix) +{ + return detail::trace_sgn<K, N>::evaluate(_matrix); +} + +template <size_t N> +inline +int trace_sgn (const ConstBaseSymmetricMatrix<N> & _matrix) +{ + return detail::trace_sgn<1, N>::evaluate(_matrix); +} + +template <size_t N> +inline +int det_sgn (const ConstBaseSymmetricMatrix<N> & _matrix) +{ + return detail::trace_sgn<N, N>::evaluate(_matrix); +} + +/* +template <size_t N> +inline +size_t rank (const ConstBaseSymmetricMatrix<N> & S) +{ + THROW_NOTIMPLEMENTED(); + return 0; +} + +template <> +inline +size_t rank<2> (const ConstBaseSymmetricMatrix<2> & S) +{ + if (S.is_zero()) return 0; + double d = S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>(); + if (d != 0) return 2; + return 1; +} + +template <> +inline +size_t rank<3> (const ConstBaseSymmetricMatrix<3> & S) +{ + if (S.is_zero()) return 0; + + double a20 = S.get<0,1>() * S.get<1,2>() - S.get<0,2>() * S.get<1,1>(); + double a21 = S.get<0,2>() * S.get<1,0>() - S.get<0,0>() * S.get<1,2>(); + double a22 = S.get<0,0>() * S.get<1,1>() - S.get<0,1>() * S.get<1,0>(); + double d = a20 * S.get<2,0>() + a21 * S.get<2,1>() + a22 * S.get<2,2>(); + + if (d != 0) return 3; + + if (a20 != 0 || a21 != 0 || a22 != 0) return 2; + + double a00 = S.get<1,1>() * S.get<2,2>() - S.get<1,2>() * S.get<2,1>(); + if (a00 != 0) return 2; + + double a10 = S.get<0,2>() * S.get<2,1>() - S.get<0,1>() * S.get<2,2>(); + if (a10 != 0) return 2; + + double a11 = S.get<0,0>() * S.get<2,2>() - S.get<0,2>() * S.get<2,0>(); + if (a11 != 0) return 2; + + return 1; +} +*/ + +} /* end namespace NL*/ } /* end namespace Geom*/ + + + + +#endif // _NL_TRACE_H_ + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/numeric/symmetric-matrix-fs.h b/include/2geom/numeric/symmetric-matrix-fs.h new file mode 100644 index 0000000..2fadd69 --- /dev/null +++ b/include/2geom/numeric/symmetric-matrix-fs.h @@ -0,0 +1,733 @@ +/* + * SymmetricMatrix, ConstSymmetricMatrixView, SymmetricMatrixView template + * classes implement fixed size symmetric matrix; "views" mimic the semantic + * of C++ references: any operation performed on a "view" is actually performed + * on the "viewed object" + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2009 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef _NL_SYMMETRIC_MATRIX_FS_H_ +#define _NL_SYMMETRIC_MATRIX_FS_H_ + + +#include <2geom/numeric/vector.h> +#include <2geom/numeric/matrix.h> +#include <2geom/utils.h> +#include <2geom/exception.h> + +#include <boost/static_assert.hpp> + +#include <cassert> +#include <utility> // for std::pair +#include <algorithm> // for std::swap, std::copy +#include <sstream> +#include <string> + + + +namespace Geom { namespace NL { + + +namespace detail +{ + +template <size_t I, size_t J, bool B = (I < J)> +struct index +{ + static const size_t K = index<J, I>::K; +}; + +template <size_t I, size_t J> +struct index<I, J, false> +{ + static const size_t K = (((I+1) * I) >> 1) + J; +}; + +} // end namespace detail + + + + +template <size_t N> +class ConstBaseSymmetricMatrix; + +template <size_t N> +class BaseSymmetricMatrix; + +template <size_t N> +class SymmetricMatrix; + +template <size_t N> +class ConstSymmetricMatrixView; + +template <size_t N> +class SymmetricMatrixView; + + + +// declaration needed for friend clause +template <size_t N> +bool operator== (ConstBaseSymmetricMatrix<N> const& _smatrix1, + ConstBaseSymmetricMatrix<N> const& _smatrix2); + + + + +template <size_t N> +class ConstBaseSymmetricMatrix +{ + public: + const static size_t DIM = N; + const static size_t DATA_SIZE = ((DIM+1) * DIM) / 2; + + public: + + ConstBaseSymmetricMatrix (VectorView const& _data) + : m_data(_data) + { + } + + double operator() (size_t i, size_t j) const + { + return m_data[get_index(i,j)]; + } + + template <size_t I, size_t J> + double get() const + { + BOOST_STATIC_ASSERT ((I < N && J < N)); + return m_data[detail::index<I, J>::K]; + } + + + size_t rows() const + { + return DIM; + } + + size_t columns() const + { + return DIM; + } + + bool is_zero() const + { + return m_data.is_zero(); + } + + bool is_positive() const + { + return m_data.is_positive(); + } + + bool is_negative() const + { + return m_data.is_negative(); + } + + bool is_non_negative() const + { + return m_data.is_non_negative(); + } + + double min() const + { + return m_data.min(); + } + + double max() const + { + return m_data.max(); + } + + std::pair<size_t, size_t> + min_index() const + { + std::pair<size_t, size_t> indices(0,0); + double min_value = m_data[0]; + for (size_t i = 1; i < DIM; ++i) + { + for (size_t j = 0; j <= i; ++j) + { + if (min_value > (*this)(i,j)) + { + min_value = (*this)(i,j); + indices.first = i; + indices.second = j; + } + } + } + return indices; + } + + std::pair<size_t, size_t> + max_index() const + { + std::pair<size_t, size_t> indices(0,0); + double max_value = m_data[0]; + for (size_t i = 1; i < DIM; ++i) + { + for (size_t j = 0; j <= i; ++j) + { + if (max_value < (*this)(i,j)) + { + max_value = (*this)(i,j); + indices.first = i; + indices.second = j; + } + } + } + return indices; + } + + size_t min_on_row_index (size_t i) const + { + size_t idx = 0; + double min_value = (*this)(i,0); + for (size_t j = 1; j < DIM; ++j) + { + if (min_value > (*this)(i,j)) + { + min_value = (*this)(i,j); + idx = j; + } + } + return idx; + } + + size_t max_on_row_index (size_t i) const + { + size_t idx = 0; + double max_value = (*this)(i,0); + for (size_t j = 1; j < DIM; ++j) + { + if (max_value < (*this)(i,j)) + { + max_value = (*this)(i,j); + idx = j; + } + } + return idx; + } + + size_t min_on_column_index (size_t j) const + { + return min_on_row_index(j); + } + + size_t max_on_column_index (size_t j) const + { + return max_on_row_index(j); + } + + size_t min_on_diag_index () const + { + size_t idx = 0; + double min_value = (*this)(0,0); + for (size_t i = 1; i < DIM; ++i) + { + if (min_value > (*this)(i,i)) + { + min_value = (*this)(i,i); + idx = i; + } + } + return idx; + } + + size_t max_on_diag_index () const + { + size_t idx = 0; + double max_value = (*this)(0,0); + for (size_t i = 1; i < DIM; ++i) + { + if (max_value < (*this)(i,i)) + { + max_value = (*this)(i,i); + idx = i; + } + } + return idx; + } + + std::string str() const; + + ConstSymmetricMatrixView<N-1> main_minor_const_view() const; + + SymmetricMatrix<N> operator- () const; + + Vector operator* (ConstVectorView _vector) const + { + assert (_vector.size() == DIM); + Vector result(DIM, 0.0); + + for (size_t i = 0; i < DIM; ++i) + { + for (size_t j = 0; j < DIM; ++j) + { + result[i] += (*this)(i,j) * _vector[j]; + } + } + return result; + } + + protected: + static size_t get_index (size_t i, size_t j) + { + if (i < j) return get_index (j, i); + size_t k = (i+1) * i; + k >>= 1; + k += j; + return k; + } + + protected: + ConstVectorView get_data() const + { + return m_data; + } + + friend + bool operator==<N> (ConstBaseSymmetricMatrix const& _smatrix1, + ConstBaseSymmetricMatrix const& _smatrix2); + + protected: + VectorView m_data; + +}; //end ConstBaseSymmetricMatrix + + +template <size_t N> +class BaseSymmetricMatrix : public ConstBaseSymmetricMatrix<N> +{ + public: + typedef ConstBaseSymmetricMatrix<N> base_type; + + + public: + + BaseSymmetricMatrix (VectorView const& _data) + : base_type(_data) + { + } + + double operator() (size_t i, size_t j) const + { + return m_data[base_type::get_index(i,j)]; + } + + double& operator() (size_t i, size_t j) + { + return m_data[base_type::get_index(i,j)]; + } + + template <size_t I, size_t J> + double& get() + { + BOOST_STATIC_ASSERT ((I < N && J < N)); + return m_data[detail::index<I, J>::K]; + } + + void set_all (double x) + { + m_data.set_all(x); + } + + SymmetricMatrixView<N-1> main_minor_view(); + + BaseSymmetricMatrix& transpose() const + { + return (*this); + } + + BaseSymmetricMatrix& translate (double c) + { + m_data.translate(c); + return (*this); + } + + BaseSymmetricMatrix& scale (double c) + { + m_data.scale(c); + return (*this); + } + + BaseSymmetricMatrix& operator+= (base_type const& _smatrix) + { + m_data += (static_cast<const BaseSymmetricMatrix &>(_smatrix).m_data); + return (*this); + } + + BaseSymmetricMatrix& operator-= (base_type const& _smatrix) + { + m_data -= (static_cast<const BaseSymmetricMatrix &>(_smatrix).m_data); + return (*this); + } + + using base_type::DIM; + using base_type::DATA_SIZE; + using base_type::m_data; + using base_type::operator-; + using base_type::operator*; + +}; //end BaseSymmetricMatrix + + +template <size_t N> +class SymmetricMatrix : public BaseSymmetricMatrix<N> +{ + public: + typedef BaseSymmetricMatrix<N> base_type; + typedef typename base_type::base_type base_base_type; + + using base_type::DIM; + using base_type::DATA_SIZE; + using base_type::m_data; + + public: + SymmetricMatrix () + : base_type (VectorView(m_adata, DATA_SIZE)) + { + } + + explicit + SymmetricMatrix (ConstVectorView _data) + : base_type (VectorView(m_adata, DATA_SIZE)) + { + assert (_data.size() == DATA_SIZE); + m_data = _data; + } + + explicit + SymmetricMatrix (const double _data[DATA_SIZE]) + : base_type (VectorView(m_adata, DATA_SIZE)) + { + std::copy (_data, _data + DATA_SIZE, m_adata); + } + + SymmetricMatrix (SymmetricMatrix const& _smatrix) + : base_type (VectorView(m_adata, DATA_SIZE)) + { + m_data = _smatrix.m_data; + } + + explicit + SymmetricMatrix (base_base_type const& _smatrix) + : base_type (VectorView(m_adata, DATA_SIZE)) + { + m_data = static_cast<const ConstSymmetricMatrixView<DIM> &>(_smatrix).m_data; + } + + explicit + SymmetricMatrix (ConstMatrixView const& _matrix) + : base_type (VectorView(m_adata, DATA_SIZE)) + { + assert (_matrix.rows() == N && _matrix.columns() == N); + for (size_t i = 0; i < N; ++i) + for (size_t j = 0; j <= i ; ++j) + (*this)(i,j) = _matrix(i,j); + } + + SymmetricMatrix& operator= (SymmetricMatrix const& _smatrix) + { + m_data = _smatrix.m_data; + return (*this); + } + + SymmetricMatrix& operator= (base_base_type const& _smatrix) + { + + m_data = static_cast<const ConstSymmetricMatrixView<DIM> &>(_smatrix).m_data; + return (*this); + } + + SymmetricMatrix& operator= (ConstMatrixView const& _matrix) + { + assert (_matrix.rows() == N && _matrix.columns() == N); + for (size_t i = 0; i < N; ++i) + for (size_t j = 0; j <= i ; ++j) + (*this)(i,j) = _matrix(i,j); + + return (*this); + } + + // needed for accessing m_adata + friend class ConstSymmetricMatrixView<DIM>; + friend class SymmetricMatrixView<DIM>; + private: + double m_adata[DATA_SIZE]; +}; //end SymmetricMatrix + + +template <size_t N> +class ConstSymmetricMatrixView : public ConstBaseSymmetricMatrix<N> +{ + public: + typedef ConstBaseSymmetricMatrix<N> base_type; + + using base_type::DIM; + using base_type::DATA_SIZE; + using base_type::m_data; + + + public: + + explicit + ConstSymmetricMatrixView (ConstVectorView _data) + : base_type (const_vector_view_cast(_data)) + { + assert (_data.size() == DATA_SIZE); + } + + explicit + ConstSymmetricMatrixView (const double _data[DATA_SIZE]) + : base_type (const_vector_view_cast (ConstVectorView (_data, DATA_SIZE))) + { + } + + ConstSymmetricMatrixView (const ConstSymmetricMatrixView & _smatrix) + : base_type (_smatrix.m_data) + { + } + + ConstSymmetricMatrixView (const base_type & _smatrix) + : base_type (static_cast<const ConstSymmetricMatrixView &>(_smatrix).m_data) + { + } + +}; //end SymmetricMatrix + + +// declaration needed for friend clause +template <size_t N> +void swap_view(SymmetricMatrixView<N> & m1, SymmetricMatrixView<N> & m2); + + +template <size_t N> +class SymmetricMatrixView : public BaseSymmetricMatrix<N> +{ + public: + typedef BaseSymmetricMatrix<N> base_type; + typedef typename base_type::base_type base_base_type; + + using base_type::DIM; + using base_type::DATA_SIZE; + using base_type::m_data; + + public: + + explicit + SymmetricMatrixView (VectorView _data) + : base_type (_data) + { + assert (_data.size() == DATA_SIZE); + } + + explicit + SymmetricMatrixView (double _data[DATA_SIZE]) + : base_type (VectorView (_data, DATA_SIZE)) + { + } + + SymmetricMatrixView (const SymmetricMatrixView & _smatrix) + : base_type (_smatrix.m_data) + { + } + + SymmetricMatrixView (SymmetricMatrix<DIM> & _smatrix) + : base_type (VectorView (_smatrix.m_adata, DATA_SIZE)) + { + } + + SymmetricMatrixView& operator= (const SymmetricMatrixView & _smatrix) + { + m_data = _smatrix.m_data; + return (*this); + } + + SymmetricMatrixView& operator= (const base_base_type & _smatrix) + { + m_data = static_cast<const ConstSymmetricMatrixView<DIM> &>(_smatrix).m_data; + return (*this); + } + + friend + void swap_view<N>(SymmetricMatrixView & m1, SymmetricMatrixView & m2); + +}; //end SymmetricMatrix + + + + +/* + * class ConstBaseSymmetricMatrix methods + */ + +template <size_t N> +inline +std::string ConstBaseSymmetricMatrix<N>::str() const +{ + std::ostringstream oss; + oss << (*this); + return oss.str(); +} + +template <size_t N> +inline +ConstSymmetricMatrixView<N-1> +ConstBaseSymmetricMatrix<N>::main_minor_const_view() const +{ + ConstVectorView data(m_data.get_gsl_vector()->data, DATA_SIZE - DIM); + ConstSymmetricMatrixView<N-1> mm(data); + return mm; +} + +template <size_t N> +inline +SymmetricMatrix<N> ConstBaseSymmetricMatrix<N>::operator- () const +{ + SymmetricMatrix<N> result; + for (size_t i = 0; i < DATA_SIZE; ++i) + { + result.m_data[i] = -m_data[i]; + } + return result; +} + + +/* + * class ConstBaseSymmetricMatrix friend free functions + */ + +template <size_t N> +inline +bool operator== (ConstBaseSymmetricMatrix<N> const& _smatrix1, + ConstBaseSymmetricMatrix<N> const& _smatrix2) +{ + return (_smatrix1.m_data == _smatrix2.m_data); +} + +/* + * class ConstBaseSymmetricMatrix related free functions + */ + +template< size_t N, class charT > +inline +std::basic_ostream<charT> & +operator<< (std::basic_ostream<charT> & os, + const ConstBaseSymmetricMatrix<N> & _matrix) +{ + os << "[[" << _matrix(0,0); + for (size_t j = 1; j < N; ++j) + { + os << ", " << _matrix(0,j); + } + os << "]"; + for (size_t i = 1; i < N; ++i) + { + os << "\n [" << _matrix(i,0); + for (size_t j = 1; j < N; ++j) + { + os << ", " << _matrix(i,j); + } + os << "]"; + } + os << "]"; + return os; +} + + +/* + * class ConstBaseSymmetricMatrix specialized methods + */ + +template<> +inline +size_t ConstBaseSymmetricMatrix<2>::get_index (size_t i, size_t j) +{ + return (i+j); +} + +template<> +inline +size_t ConstBaseSymmetricMatrix<3>::get_index (size_t i, size_t j) +{ + size_t k = i + j; + if (i == 2 || j == 2) ++k; + return k; +} + + +/* + * class BaseSymmetricMatrix methods + */ + +template <size_t N> +inline +SymmetricMatrixView<N-1> BaseSymmetricMatrix<N>::main_minor_view() +{ + VectorView data(m_data.get_gsl_vector()->data, DATA_SIZE - DIM); + SymmetricMatrixView<N-1> mm(data); + return mm; +} + + +/* + * class SymmetricMatrixView friend free functions + */ + +template <size_t N> +inline +void swap_view(SymmetricMatrixView<N> & m1, SymmetricMatrixView<N> & m2) +{ + swap_view(m1.m_data, m2.m_data); +} + +} /* end namespace NL*/ } /* end namespace Geom*/ + + + + +#endif // _NL_SYMMETRIC_MATRIX_FS_H_ + + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/numeric/vector.h b/include/2geom/numeric/vector.h new file mode 100644 index 0000000..b66115b --- /dev/null +++ b/include/2geom/numeric/vector.h @@ -0,0 +1,594 @@ +/* + * Vector, VectorView, ConstVectorView classes wrap the gsl vector routines; + * "views" mimic the semantic of C++ references: any operation performed + * on a "view" is actually performed on the "viewed object" + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + + + +#ifndef _NL_VECTOR_H_ +#define _NL_VECTOR_H_ + +#include <cassert> +#include <algorithm> // for std::swap +#include <vector> +#include <sstream> +#include <string> + + +#include <gsl/gsl_vector.h> +#include <gsl/gsl_blas.h> + + +namespace Geom { namespace NL { + +namespace detail +{ + +class BaseVectorImpl +{ + public: + double const& operator[](size_t i) const + { + return *gsl_vector_const_ptr(m_vector, i); + } + + const gsl_vector* get_gsl_vector() const + { + return m_vector; + } + bool is_zero() const + { + return gsl_vector_isnull(m_vector); + } + + bool is_positive() const + { + for ( size_t i = 0; i < size(); ++i ) + { + if ( (*this)[i] <= 0 ) return false; + } + return true; + } + + bool is_negative() const + { + for ( size_t i = 0; i < size(); ++i ) + { + if ( (*this)[i] >= 0 ) return false; + } + return true; + } + + bool is_non_negative() const + { + for ( size_t i = 0; i < size(); ++i ) + { + if ( (*this)[i] < 0 ) return false; + } + return true; + } + + double max() const + { + return gsl_vector_max(m_vector); + } + + double min() const + { + return gsl_vector_min(m_vector); + } + + size_t max_index() const + { + return gsl_vector_max_index(m_vector); + } + + size_t min_index() const + { + return gsl_vector_min_index(m_vector); + } + + size_t size() const + { + return m_size; + } + + std::string str() const; + + virtual ~BaseVectorImpl() + { + } + + protected: + size_t m_size; + gsl_vector* m_vector; + +}; // end class BaseVectorImpl + + +inline +bool operator== (BaseVectorImpl const& v1, BaseVectorImpl const& v2) +{ + if (v1.size() != v2.size()) return false; + + for (size_t i = 0; i < v1.size(); ++i) + { + if (v1[i] != v2[i]) return false; + } + return true; +} + +template< class charT > +inline +std::basic_ostream<charT> & +operator<< (std::basic_ostream<charT> & os, const BaseVectorImpl & _vector) +{ + if (_vector.size() == 0 ) return os; + os << "[" << _vector[0]; + for (unsigned int i = 1; i < _vector.size(); ++i) + { + os << ", " << _vector[i]; + } + os << "]"; + return os; +} + +inline +std::string BaseVectorImpl::str() const +{ + std::ostringstream oss; + oss << (*this); + return oss.str(); +} + +inline +double dot(BaseVectorImpl const& v1, BaseVectorImpl const& v2) +{ + double result; + gsl_blas_ddot(v1.get_gsl_vector(), v2.get_gsl_vector(), &result); + return result; +} + + +class VectorImpl : public BaseVectorImpl +{ + public: + typedef BaseVectorImpl base_type; + + public: + void set_all(double x) + { + gsl_vector_set_all(m_vector, x); + } + + void set_basis(size_t i) + { + gsl_vector_set_basis(m_vector, i); + } + + using base_type::operator[]; + + double & operator[](size_t i) + { + return *gsl_vector_ptr(m_vector, i); + } + + using base_type::get_gsl_vector; + + gsl_vector* get_gsl_vector() + { + return m_vector; + } + + void swap_elements(size_t i, size_t j) + { + gsl_vector_swap_elements(m_vector, i, j); + } + + void reverse() + { + gsl_vector_reverse(m_vector); + } + + VectorImpl & scale(double x) + { + gsl_vector_scale(m_vector, x); + return (*this); + } + + VectorImpl & translate(double x) + { + gsl_vector_add_constant(m_vector, x); + return (*this); + } + + VectorImpl & operator+=(base_type const& _vector) + { + gsl_vector_add(m_vector, _vector.get_gsl_vector()); + return (*this); + } + + VectorImpl & operator-=(base_type const& _vector) + { + gsl_vector_sub(m_vector, _vector.get_gsl_vector()); + return (*this); + } + +}; // end class VectorImpl + +} // end namespace detail + + +using detail::operator==; +using detail::operator<<; + +class Vector : public detail::VectorImpl +{ + public: + typedef detail::VectorImpl base_type; + + public: + Vector(size_t n) + { + m_size = n; + m_vector = gsl_vector_alloc(n); + } + + Vector(size_t n, double x) + { + m_size = n; + m_vector = gsl_vector_alloc(n); + gsl_vector_set_all(m_vector, x); + } + + // create a vector with n elements all set to zero + // but the i-th that is set to 1 + Vector(size_t n, size_t i) + { + m_size = n; + m_vector = gsl_vector_alloc(n); + gsl_vector_set_basis(m_vector, i); + } + + Vector(Vector const& _vector) + : base_type() + { + m_size = _vector.size(); + m_vector = gsl_vector_alloc(size()); + gsl_vector_memcpy(m_vector, _vector.m_vector); + } + + explicit + Vector(base_type::base_type const& _vector) + { + m_size = _vector.size(); + m_vector = gsl_vector_alloc(size()); + gsl_vector_memcpy(m_vector, _vector.get_gsl_vector()); + } + + ~Vector() override + { + gsl_vector_free(m_vector); + } + + + Vector & operator=(Vector const& _vector) + { + assert( size() == _vector.size() ); + gsl_vector_memcpy(m_vector, _vector.m_vector); + return (*this); + } + + Vector & operator=(base_type::base_type const& _vector) + { + assert( size() == _vector.size() ); + gsl_vector_memcpy(m_vector, _vector.get_gsl_vector()); + return (*this); + } + + Vector & scale(double x) + { + return static_cast<Vector&>( base_type::scale(x) ); + } + + Vector & translate(double x) + { + return static_cast<Vector&>( base_type::translate(x) ); + } + + Vector & operator+=(base_type::base_type const& _vector) + { + return static_cast<Vector&>( base_type::operator+=(_vector) ); + } + + Vector & operator-=(base_type::base_type const& _vector) + { + return static_cast<Vector&>( base_type::operator-=(_vector) ); + } + + friend + void swap(Vector & v1, Vector & v2); + friend + void swap_any(Vector & v1, Vector & v2); + +}; // end class Vector + + +// warning! these operations invalidate any view of the passed vector objects +inline +void swap(Vector & v1, Vector & v2) +{ + assert(v1.size() == v2.size()); + using std::swap; + swap(v1.m_vector, v2.m_vector); +} + +inline +void swap_any(Vector & v1, Vector & v2) +{ + using std::swap; + swap(v1.m_vector, v2.m_vector); + swap(v1.m_size, v2.m_size); +} + + +class ConstVectorView : public detail::BaseVectorImpl +{ + public: + typedef detail::BaseVectorImpl base_type; + + public: + ConstVectorView(const base_type & _vector, size_t n, size_t offset = 0) + : m_vector_view( gsl_vector_const_subvector(_vector.get_gsl_vector(), offset, n) ) + { + m_size = n; + m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) ); + } + + ConstVectorView(const base_type & _vector, size_t n, size_t offset , size_t stride) + : m_vector_view( gsl_vector_const_subvector_with_stride(_vector.get_gsl_vector(), offset, stride, n) ) + { + m_size = n; + m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) ); + } + + ConstVectorView(const double* _vector, size_t n, size_t offset = 0) + : m_vector_view( gsl_vector_const_view_array(_vector + offset, n) ) + { + m_size = n; + m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) ); + } + + ConstVectorView(const double* _vector, size_t n, size_t offset, size_t stride) + : m_vector_view( gsl_vector_const_view_array_with_stride(_vector + offset, stride, n) ) + { + m_size = n; + m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) ); + } + + explicit + ConstVectorView(gsl_vector_const_view _gsl_vector_view) + : m_vector_view(_gsl_vector_view) + { + m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) ); + m_size = m_vector->size; + } + + explicit + ConstVectorView(const std::vector<double>& _vector) + : m_vector_view( gsl_vector_const_view_array(&(_vector[0]), _vector.size()) ) + { + m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) ); + m_size = _vector.size(); + } + + ConstVectorView(const ConstVectorView & _vector) + : base_type(), + m_vector_view(_vector.m_vector_view) + { + m_size = _vector.size(); + m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) ); + } + + ConstVectorView(const base_type & _vector) + : m_vector_view(gsl_vector_const_subvector(_vector.get_gsl_vector(), 0, _vector.size())) + { + m_size = _vector.size(); + m_vector = const_cast<gsl_vector*>( &(m_vector_view.vector) ); + } + + private: + gsl_vector_const_view m_vector_view; + +}; // end class ConstVectorView + + + + +class VectorView : public detail::VectorImpl +{ + public: + typedef detail::VectorImpl base_type; + + public: + VectorView(base_type & _vector, size_t n, size_t offset = 0, size_t stride = 1) + { + m_size = n; + if (stride == 1) + { + m_vector_view + = gsl_vector_subvector(_vector.get_gsl_vector(), offset, n); + m_vector = &(m_vector_view.vector); + } + else + { + m_vector_view + = gsl_vector_subvector_with_stride(_vector.get_gsl_vector(), offset, stride, n); + m_vector = &(m_vector_view.vector); + } + } + + VectorView(double* _vector, size_t n, size_t offset = 0, size_t stride = 1) + { + m_size = n; + if (stride == 1) + { + m_vector_view + = gsl_vector_view_array(_vector + offset, n); + m_vector = &(m_vector_view.vector); + } + else + { + m_vector_view + = gsl_vector_view_array_with_stride(_vector + offset, stride, n); + m_vector = &(m_vector_view.vector); + } + + } + + VectorView(const VectorView & _vector) + : base_type() + { + m_size = _vector.size(); + m_vector_view = _vector.m_vector_view; + m_vector = &(m_vector_view.vector); + } + + VectorView(Vector & _vector) + { + m_size = _vector.size(); + m_vector_view = gsl_vector_subvector(_vector.get_gsl_vector(), 0, size()); + m_vector = &(m_vector_view.vector); + } + + explicit + VectorView(gsl_vector_view _gsl_vector_view) + : m_vector_view(_gsl_vector_view) + { + m_vector = &(m_vector_view.vector); + m_size = m_vector->size; + } + + explicit + VectorView(std::vector<double> & _vector) + { + m_size = _vector.size(); + m_vector_view = gsl_vector_view_array(&(_vector[0]), _vector.size()); + m_vector = &(m_vector_view.vector); + } + + VectorView & operator=(VectorView const& _vector) + { + assert( size() == _vector.size() ); + gsl_vector_memcpy(m_vector, _vector.get_gsl_vector()); + return (*this); + } + + VectorView & operator=(base_type::base_type const& _vector) + { + assert( size() == _vector.size() ); + gsl_vector_memcpy(m_vector, _vector.get_gsl_vector()); + return (*this); + } + + VectorView & scale(double x) + { + return static_cast<VectorView&>( base_type::scale(x) ); + } + + VectorView & translate(double x) + { + return static_cast<VectorView&>( base_type::translate(x) ); + } + + VectorView & operator+=(base_type::base_type const& _vector) + { + return static_cast<VectorView&>( base_type::operator+=(_vector) ); + } + + VectorView & operator-=(base_type::base_type const& _vector) + { + return static_cast<VectorView&>( base_type::operator-=(_vector) ); + } + + friend + void swap_view(VectorView & v1, VectorView & v2); + + private: + gsl_vector_view m_vector_view; + +}; // end class VectorView + + +inline +void swap_view(VectorView & v1, VectorView & v2) +{ + assert( v1.size() == v2.size() ); + using std::swap; + swap(v1.m_vector_view, v2.m_vector_view); // not swap m_vector too +} + +inline +const VectorView & const_vector_view_cast (const ConstVectorView & view) +{ + const detail::BaseVectorImpl & bvi + = static_cast<const detail::BaseVectorImpl &>(view); + const VectorView & vv = reinterpret_cast<const VectorView &>(bvi); + return vv; +} + +inline +VectorView & const_vector_view_cast (ConstVectorView & view) +{ + detail::BaseVectorImpl & bvi + = static_cast<detail::BaseVectorImpl &>(view); + VectorView & vv = reinterpret_cast<VectorView &>(bvi); + return vv; +} + + +} } // end namespaces + + +#endif /*_NL_VECTOR_H_*/ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/ord.h b/include/2geom/ord.h new file mode 100644 index 0000000..e190a4a --- /dev/null +++ b/include/2geom/ord.h @@ -0,0 +1,80 @@ +/** @file + * @brief Comparator template + *//* + * Authors: + * ? <?@?.?> + * + * Copyright ?-? authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_ORD_H +#define LIB2GEOM_SEEN_ORD_H + +namespace { + +enum Cmp { + LESS_THAN=-1, + GREATER_THAN=1, + EQUAL_TO=0 +}; + +static inline Cmp operator-(Cmp x) { + switch(x) { + case LESS_THAN: + return GREATER_THAN; + case GREATER_THAN: + return LESS_THAN; + case EQUAL_TO: + return EQUAL_TO; + } +} + +template <typename T1, typename T2> +inline Cmp cmp(T1 const &a, T2 const &b) { + if ( a < b ) { + return LESS_THAN; + } else if ( b < a ) { + return GREATER_THAN; + } else { + return EQUAL_TO; + } +} + +} + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/orphan-code/arc-length.h b/include/2geom/orphan-code/arc-length.h new file mode 100644 index 0000000..8029f04 --- /dev/null +++ b/include/2geom/orphan-code/arc-length.h @@ -0,0 +1,58 @@ +/** + * \file arc-length.h + * \brief Arc length computations for paths + *//* + * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef __2GEOM_ARC_LENGTH_H +#define __2GEOM_ARC_LENGTH_H + +#include <2geom/path.h> + +/* Routines in this group return a path that looks the same, but + * include extra knots for certain points of interest. */ + +/* find_vector_extreme_points + * extreme points . dir. + */ + +double arc_length_subdividing(Geom::Path const & p, double tol); +double arc_length_integrating(Geom::Path const & p, double tol); + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/orphan-code/chebyshev.h b/include/2geom/orphan-code/chebyshev.h new file mode 100644 index 0000000..f729e1f --- /dev/null +++ b/include/2geom/orphan-code/chebyshev.h @@ -0,0 +1,30 @@ +#ifndef _CHEBYSHEV +#define _CHEBYSHEV + +#include <2geom/sbasis.h> +#include <2geom/interval.h> + +/*** Conversion between Chebyshev approximation and SBasis. + * + */ + +namespace Geom{ + +SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p=0); +SBasis chebyshev_approximant_interpolating (double (*f)(double,void*), int order, Interval in, void* p=0); +SBasis chebyshev(unsigned n); + +}; + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + +#endif diff --git a/include/2geom/orphan-code/intersection-by-smashing.h b/include/2geom/orphan-code/intersection-by-smashing.h new file mode 100644 index 0000000..996ec99 --- /dev/null +++ b/include/2geom/orphan-code/intersection-by-smashing.h @@ -0,0 +1,78 @@ +/* + * Diffeomorphism-based intersector: given two curves + * M(t)=(x(t),y(t)) and N(u)=(X(u),Y(u)) + * and supposing M is a graph over the x-axis, we compute y(x) and the + * transformation: + * X <- X + * Y <- Y - y(X) + * smashes M on the x axis. The intersections are then given by the roots of: + * Y(u) - y(X(u)) = 0 + *//* + * Authors: + * J.-F. Barraud <jfbarraud at gmail.com> + * Copyright 2010 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef SEEN_LIB2GEOM_INTERSECTION_BY_SMASHING_H +#define SEEN_LIB2GEOM_INTERSECTION_BY_SMASHING_H + +#include <2geom/d2.h> +#include <2geom/interval.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <cstdlib> +#include <vector> +#include <algorithm> + + +namespace Geom{ + +struct SmashIntersection { + Rect times; + Rect bbox; +}; + +std::vector<SmashIntersection> smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol); +std::vector<SmashIntersection> monotonic_smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol); +//std::vector<Intersection> monotonic_smash_intersect( Curve const &a, double a_from, double a_to, +// Curve const &b, double b_from, double b_to, double tol); + +std::vector<Interval> monotonicSplit(D2<SBasis> const &p); + +} // end namespace Geom + +#endif // !SEEN_LIB2GEOM_INTERSECTION_BY_SMASHING_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/orphan-code/linear-of.h b/include/2geom/orphan-code/linear-of.h new file mode 100644 index 0000000..9ba1fb2 --- /dev/null +++ b/include/2geom/orphan-code/linear-of.h @@ -0,0 +1,269 @@ +/**
+ * \file
+ * \brief Linear fragment function class
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#ifndef SEEN_LINEAR_OF_H
+#define SEEN_LINEAR_OF_H
+#include <2geom/interval.h>
+#include <2geom/math-utils.h>
+
+namespace Geom{
+
+template <typename T>
+inline T lerp(double t, T a, T b) { return a*(1-t) + b*t; }
+
+template <typename T>
+class SBasisOf;
+
+template <typename T>
+class HatOf{
+public:
+ HatOf () {}
+ HatOf(T d) :d(d) {}
+ operator T() const { return d; }
+ T d;
+};
+
+template <typename T>
+class TriOf{
+public:
+ TriOf () {}
+ TriOf(double d) :d(d) {}
+ operator T() const { return d; }
+ T d;
+};
+
+
+//--------------------------------------------------------------------------
+#ifdef USE_SBASIS_OF
+template <typename T>
+class LinearOf;
+typedef Geom::LinearOf<double> Linear;
+#endif
+//--------------------------------------------------------------------------
+
+template <typename T>
+class LinearOf{
+public:
+ T a[2];
+ LinearOf() {}
+ LinearOf(T aa, T b) {a[0] = aa; a[1] = b;}
+ //LinearOf(double aa, double b) {a[0] = T(aa); a[1] = T(b);}
+ LinearOf(HatOf<T> h, TriOf<T> t) {
+ a[0] = T(h) - T(t)/2;
+ a[1] = T(h) + T(t)/2;
+ }
+
+ LinearOf(HatOf<T> h) {
+ a[0] = T(h);
+ a[1] = T(h);
+ }
+
+ unsigned input_dim(){return T::input_dim() + 1;}
+
+ T operator[](const int i) const {
+ assert(i >= 0);
+ assert(i < 2);
+ return a[i];
+ }
+ T& operator[](const int i) {
+ assert(i >= 0);
+ assert(i < 2);
+ return a[i];
+ }
+
+ //IMPL: FragmentConcept
+ typedef T output_type;
+ inline bool isZero() const { return a[0].isZero() && a[1].isZero(); }
+ inline bool isConstant() const { return a[0] == a[1]; }
+ inline bool isFinite() const { return std::isfinite(a[0]) && std::isfinite(a[1]); }
+
+ inline T at0() const { return a[0]; }
+ inline T at1() const { return a[1]; }
+
+ inline T valueAt(double t) const { return lerp(t, a[0], a[1]); }
+ inline T operator()(double t) const { return valueAt(t); }
+
+ //defined in sbasis.h
+ inline SBasisOf<T> toSBasis() const;
+
+//This is specific for T=double!!
+ inline OptInterval bounds_exact() const { return Interval(a[0], a[1]); }
+ inline OptInterval bounds_fast() const { return bounds_exact(); }
+ inline OptInterval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); }
+
+ operator TriOf<T>() const {
+ return a[1] - a[0];
+ }
+ operator HatOf<T>() const {
+ return (a[1] + a[0])/2;
+ }
+};
+
+template <>
+unsigned LinearOf<double>::input_dim(){return 1;}
+template <>
+inline OptInterval LinearOf<double>::bounds_exact() const { return Interval(a[0], a[1]); }
+template <>
+inline OptInterval LinearOf<double>::bounds_fast() const { return bounds_exact(); }
+template <>
+inline OptInterval LinearOf<double>::bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); }
+template <>
+inline bool LinearOf<double>::isZero() const { return a[0]==0 && a[1]==0; }
+
+template <typename T>
+inline LinearOf<T> reverse(LinearOf<T> const &a) { return LinearOf<T>(a[1], a[0]); }
+
+//IMPL: AddableConcept
+template <typename T>
+inline LinearOf<T> operator+(LinearOf<T> const & a, LinearOf<T> const & b) {
+ return LinearOf<T>(a[0] + b[0], a[1] + b[1]);
+}
+template <typename T>
+inline LinearOf<T> operator-(LinearOf<T> const & a, LinearOf<T> const & b) {
+ return LinearOf<T>(a[0] - b[0], a[1] - b[1]);
+}
+template <typename T>
+inline LinearOf<T>& operator+=(LinearOf<T> & a, LinearOf<T> const & b) {
+ a[0] += b[0]; a[1] += b[1];
+ return a;
+}
+template <typename T>
+inline LinearOf<T>& operator-=(LinearOf<T> & a, LinearOf<T> const & b) {
+ a[0] -= b[0]; a[1] -= b[1];
+ return a;
+}
+//IMPL: OffsetableConcept
+template <typename T>
+inline LinearOf<T> operator+(LinearOf<T> const & a, double b) {
+ return LinearOf<T>(a[0] + b, a[1] + b);
+}
+template <typename T>
+inline LinearOf<T> operator-(LinearOf<T> const & a, double b) {
+ return LinearOf<T>(a[0] - b, a[1] - b);
+}
+template <typename T>
+inline LinearOf<T>& operator+=(LinearOf<T> & a, double b) {
+ a[0] += b; a[1] += b;
+ return a;
+}
+template <typename T>
+inline LinearOf<T>& operator-=(LinearOf<T> & a, double b) {
+ a[0] -= b; a[1] -= b;
+ return a;
+}
+/*
+//We can in fact offset in coeff ring T...
+template <typename T>
+inline LinearOf<T> operator+(LinearOf<T> const & a, T b) {
+ return LinearOf<T>(a[0] + b, a[1] + b);
+}
+template <typename T>
+inline LinearOf<T> operator-(LinearOf<T> const & a, T b) {
+ return LinearOf<T>(a[0] - b, a[1] - b);
+}
+template <typename T>
+inline LinearOf<T>& operator+=(LinearOf<T> & a, T b) {
+ a[0] += b; a[1] += b;
+ return a;
+}
+template <typename T>
+inline LinearOf<T>& operator-=(LinearOf<T> & a, T b) {
+ a[0] -= b; a[1] -= b;
+ return a;
+}
+*/
+
+//IMPL: boost::EqualityComparableConcept
+template <typename T>
+inline bool operator==(LinearOf<T> const & a, LinearOf<T> const & b) {
+ return a[0] == b[0] && a[1] == b[1];
+}
+template <typename T>
+inline bool operator!=(LinearOf<T> const & a, LinearOf<T> const & b) {
+ return a[0] != b[0] || a[1] != b[1];
+}
+//IMPL: ScalableConcept
+template <typename T>
+inline LinearOf<T> operator-(LinearOf<T> const &a) {
+ return LinearOf<T>(-a[0], -a[1]);
+}
+template <typename T>
+inline LinearOf<T> operator*(LinearOf<T> const & a, double b) {
+ return LinearOf<T>(a[0]*b, a[1]*b);
+}
+template <typename T>
+inline LinearOf<T> operator/(LinearOf<T> const & a, double b) {
+ return LinearOf<T>(a[0]/b, a[1]/b);
+}
+template <typename T>
+inline LinearOf<T> operator*=(LinearOf<T> & a, double b) {
+ a[0] *= b; a[1] *= b;
+ return a;
+}
+template <typename T>
+inline LinearOf<T> operator/=(LinearOf<T> & a, double b) {
+ a[0] /= b; a[1] /= b;
+ return a;
+}
+/*
+//We can in fact rescale in coeff ring T... (but not divide!)
+template <typename T>
+inline LinearOf<T> operator*(LinearOf<T> const & a, T b) {
+ return LinearOf<T>(a[0]*b, a[1]*b);
+}
+template <typename T>
+inline LinearOf<T> operator/(LinearOf<T> const & a, T b) {
+ return LinearOf<T>(a[0]/b, a[1]/b);
+}
+template <typename T>
+inline LinearOf<T> operator*=(LinearOf<T> & a, T b) {
+ a[0] *= b; a[1] *= b;
+ return a;
+}
+*/
+
+};
+
+#endif //SEEN_LINEAR_OF_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/include/2geom/orphan-code/linearN.h b/include/2geom/orphan-code/linearN.h new file mode 100644 index 0000000..bb27c30 --- /dev/null +++ b/include/2geom/orphan-code/linearN.h @@ -0,0 +1,363 @@ +/**
+ * @file
+ * @brief LinearN fragment function class
+ *//*
+ * Authors:
+ * JF Barraud <jf.barraud@gmail.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#ifndef SEEN_LINEARN_H
+#define SEEN_LINEARN_H
+#include <2geom/interval.h>
+#include <2geom/math-utils.h>
+#include <2geom/linear.h> //for conversion purpose ( + lerp() )
+
+#include <iostream>
+
+namespace Geom{
+
+//TODO: define this only once!! (see linear.h)
+inline double lerpppp(double t, double a, double b) { return a*(1-t) + b*t; }
+
+template<unsigned n>
+class SBasisN;
+
+template<unsigned n>
+class LinearN{
+public:
+ double a[1<<n];// 1<<n is 2^n
+ LinearN() {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] = 0.;
+ }
+ }
+ LinearN(double aa[]) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] = aa[i];
+ }
+ }
+ LinearN(double c) {
+ for (unsigned i=0; i<(1<<n); i++){
+ a[i] = c;
+ }
+ }
+ LinearN(LinearN<n-1> const &aa, LinearN<n-1> const &b, unsigned var=0) {
+// for (unsigned i=0; i<(1<<n-1); i++){
+// a[i] = aa[i];
+// a[i+(1<<(n-1))] = b[i];
+// }
+ unsigned mask = (1<<var)-1;
+ for (unsigned i=0; i < (1<<(n-1)); i++){
+ unsigned low_i = i & mask, high_i = i & ~mask;
+ unsigned idx0 = (high_i<<1)|low_i;
+ unsigned idx1 = (high_i<<1)|(1<<var)|low_i;
+ a[idx0] = aa[i];
+ a[idx1] = b[i];
+ }
+
+ }
+ double operator[](const int i) const {
+ assert( i >= 0 );
+ assert( i < (1<<n) );
+ return a[i];
+ }
+ double& operator[](const int i) {
+ assert(i >= 0);
+ assert(i < (1<<n) );
+ return a[i];
+ }
+
+ //IMPL: FragmentConcept
+ typedef double output_type;
+ unsigned input_dim() const {return n;}
+ inline bool isZero() const {
+ for (unsigned i=0; i < (1<<n); i++){
+ if (a[i] != 0) return false;
+ }
+ return true; }
+ inline bool isConstant() const {
+ for (unsigned i=1; i < (1<<n); i++){
+ if (a[i] != a[0]) return false;
+ }
+ return true; }
+ inline bool isConstant(unsigned var) const {
+ unsigned mask = (1<<var)-1;
+ for (unsigned i=0; i < (1<<(n-1)); i++){
+ unsigned low_i = i & mask, high_i = i & ~mask;
+ unsigned idx0 = (high_i<<1)|low_i;
+ unsigned idx1 = (high_i<<1)|(1<<var)|low_i;
+ if (a[idx0] != a[idx1]) return false;
+ }
+ return true;
+ }
+ inline bool isFinite() const {
+ for (unsigned i=0; i < (1<<n); i++){
+ if ( !std::isfinite(a[i]) ) return false;
+ }
+ return true; }
+ //value if k-th variable is set to 0.
+ inline LinearN<n-1> at0(unsigned k=0) const {
+ LinearN<n-1> res;
+ unsigned mask = (1<<k)-1;
+ for (unsigned i=0; i < (1<<(n-1)); i++){
+ unsigned low_i = i & mask, high_i = i & ~mask;
+ unsigned idx = (high_i<<1)|low_i;
+ res[i] = a[idx];
+ }
+ return res;
+ }
+ //value if k-th variable is set to 1.
+ inline LinearN<n-1> at1(unsigned k=0) const {
+ LinearN<n-1> res;
+ for (unsigned i=0; i < (1<<(n-1)); i++){
+ unsigned mask = (1<<k)-1;
+ unsigned low_i = i & mask, high_i = i & ~mask;
+ unsigned idx = (high_i<<1)|(1<<k)|low_i;
+ res[i] = a[idx];
+ }
+ return res;
+ }
+ inline double atCorner(unsigned k) const {
+ assert( k < (1<<n) );
+ return a[k];
+ }
+ inline double atCorner(double t[]) const {
+ unsigned k=0;
+ for(unsigned i=0; i<n; i++){
+ if (t[i] == 1.) k = k | (1<<i);
+ else assert( t[i] == 0. );
+ }
+ return atCorner(k);
+ }
+ inline LinearN<n-1> partialEval(double t, unsigned var=0 ) const {
+ LinearN<n-1> res;
+ res = at0(var)*(1-t) + at1(var)*t;
+ return res;
+ }
+
+ //fixed and flags are used for recursion.
+ inline double valueAt(double t[], unsigned fixed=0, unsigned flags=0 ) const {
+ if (fixed == n) {
+ return a[flags];
+ }else{
+ double a0 = valueAt(t, fixed+1, flags);
+ double a1 = valueAt(t, fixed+1, flags|(1<<fixed));
+ return lerpppp( t[fixed], a0, a1 );
+ }
+ }
+ inline double operator()(double t[]) const { return valueAt(t); }
+
+ //defined in sbasisN.h
+ inline SBasisN<n> toSBasisN() const;
+
+ inline OptInterval bounds_exact() const {
+ double min=a[0], max=a[0];
+ for (unsigned i=1; i < (1<<n); i++){
+ if (a[i] < min) min = a[i];
+ if (a[i] > max) max = a[i];
+ }
+ return Interval(min, max);
+ }
+ inline OptInterval bounds_fast() const { return bounds_exact(); }
+ //inline OptInterval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); }
+};
+
+//LinearN<0> are doubles. Specialize them out.
+template<>
+class LinearN<0>{
+public:
+ double d;
+ LinearN () {}
+ LinearN(double d) :d(d) {}
+ operator double() const { return d; }
+ double operator[](const int i) const {assert (i==0); return d;}
+ double& operator[](const int i) {assert (i==0); return d;}
+ typedef double output_type;
+ unsigned input_dim() const {return 0;}
+ inline bool isZero() const { return d==0; }
+ inline bool isConstant() const { return true; }
+ inline bool isFinite() const { return std::isfinite(d); }
+};
+
+//LinearN<1> are usual Linear. Allow conversion.
+Linear toLinear(LinearN<1> f){
+ return Linear(f[0],f[1]);
+}
+
+
+
+//inline Linear reverse(Linear const &a) { return Linear(a[1], a[0]); }
+
+//IMPL: AddableConcept
+template<unsigned n>
+inline LinearN<n> operator+(LinearN<n> const & a, LinearN<n> const & b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] + b[i];
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator-(LinearN<n> const & a, LinearN<n> const & b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] - b[i];
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n>& operator+=(LinearN<n> & a, LinearN<n> const & b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] += b[i];
+ }
+ return a;
+}
+template<unsigned n>
+inline LinearN<n>& operator-=(LinearN<n> & a, LinearN<n> const & b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] -= b[i];
+ }
+ return a;
+}
+//IMPL: OffsetableConcept
+template<unsigned n>
+inline LinearN<n> operator+(LinearN<n> const & a, double b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] + b;
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator-(LinearN<n> const & a, double b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] - b;
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n>& operator+=(LinearN<n> & a, double b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] += b;
+ }
+ return a;
+}
+template<unsigned n>
+inline LinearN<n>& operator-=(LinearN<n> & a, double b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] -= b;
+ }
+ return a;
+}
+//IMPL: boost::EqualityComparableConcept
+template<unsigned n>
+inline bool operator==(LinearN<n> const & a, LinearN<n> const & b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ if (a[i] != b[i]) return false;
+ }
+ return true;
+}
+template<unsigned n>
+inline bool operator!=(LinearN<n> const & a, LinearN<n> const & b) {
+ return !(a==b);
+}
+//IMPL: ScalableConcept
+template<unsigned n>
+inline LinearN<n> operator-(LinearN<n> const &a) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = -a[i];
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator*(LinearN<n> const & a, double b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] * b;
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator/(LinearN<n> const & a, double b) {
+ LinearN<n> res;
+ for (unsigned i=0; i < (1<<n); i++){
+ res[i] = a[i] / b;
+ }
+ return res;
+}
+template<unsigned n>
+inline LinearN<n> operator*=(LinearN<n> & a, double b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] *= b;
+ }
+ return a;
+}
+template<unsigned n>
+inline LinearN<n> operator/=(LinearN<n> & a, double b) {
+ for (unsigned i=0; i < (1<<n); i++){
+ a[i] /= b;
+ }
+ return a;
+}
+
+template<unsigned n>
+void setToVariable(LinearN<n> &x, unsigned k){;
+ x = LinearN<n>(0.);
+ unsigned mask = 1<<k;
+ for (unsigned i=0; i < (1<<n); i++){
+ if ( i & mask ) x[i] = 1;
+ }
+}
+
+template<unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const LinearN<n> &bo) {
+ out_file << "{";
+ for (unsigned i=0; i < (1<<n); i++){
+ out_file << bo[i]<<(i == (1<<n)-1 ? "}" : ",");
+ }
+ return out_file;
+}
+
+
+}
+#endif //SEEN_LINEAR_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/include/2geom/orphan-code/redblacktree.h b/include/2geom/orphan-code/redblacktree.h new file mode 100644 index 0000000..9d79342 --- /dev/null +++ b/include/2geom/orphan-code/redblacktree.h @@ -0,0 +1,121 @@ +/** + * \file + * \brief + * Implementation of Red-Black Tree as described in + * Intorduction to Algorithms. Cormen et al. Mc Grow Hill. 1990. pp 263-280 + * + * The intention is to implement interval trees mentioned in the same book, after the red-black. + * Interval are heavily based on red-black trees (most operations are the same). So, we begin first + * with implementing red-black! + * + * Authors: + * ? <?@?.?> + * + * Copyright 2009-2009 Evangelos Katsikaros + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef SEEN_LIB2GEOM_REDBLACKTREE_H +#define SEEN_LIB2GEOM_REDBLACKTREE_H + +#include <vector> +//#include <cassert> +#include <limits> +#include <cfloat> + +#include <2geom/d2.h> +#include <2geom/interval.h> + +namespace Geom{ + +class RedBlack{ +public: + Interval interval; // Key of the redblack tree will be the min of the interval + RedBlack *left, *right, *parent; + bool isRed; + Coord subtree_max; // subtree_max = max( x->left->subtree_max, x->right->subtree_max, x->high ) + int data; + + RedBlack(): left(0), right(0), parent(0), isRed(false), subtree_max(0.0), data(0) { + Interval interval(0.0, 0.0); + } +/* + RedBlack(Coord min, Coord max): left(0), right(0), parent(0), isRed(false), subtree_max(0.0), data(0) { + Interval interval( min, max ); + } +*/ + inline Coord key(){ return interval.min(); }; + inline Coord high(){ return interval.max(); }; +}; + + +class RedBlackTree{ +public: + RedBlack* root; + + RedBlackTree(): root(0) {} + + void insert(Rect const &r, int shape, int dimension); + void insert(Coord dimension_min, Coord dimension_max, int shape); + + void erase(Rect const &r); + void erase(int shape); + + RedBlack* search(Rect const &r, int dimension); + RedBlack* search(Interval i); + RedBlack* search(Coord a, Coord b); + + void print_tree(); +private: + void inorder_tree_walk(RedBlack* x); + RedBlack* tree_minimum(RedBlack* x); + RedBlack* tree_successor(RedBlack* x); + + void left_rotate(RedBlack* x); + void right_rotate(RedBlack* x); + void tree_insert(RedBlack* x); + + void update_max(RedBlack* x); + + RedBlack* erase(RedBlack* x); // TODO why rerutn pointer? to collect garbage ??? + void erase_fixup(RedBlack* x); + +}; + +} // end namespace Geom + +#endif // !SEEN_LIB2GEOM_REDBLACKTREE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/orphan-code/rtree.h b/include/2geom/orphan-code/rtree.h new file mode 100644 index 0000000..3ffae8e --- /dev/null +++ b/include/2geom/orphan-code/rtree.h @@ -0,0 +1,241 @@ +/** + * \file + * \brief + * Implementation of Red-Black Tree as described in + * Intorduction to Algorithms. Cormen et al. Mc Grow Hill. 1990. pp 263-280 + * + * The intention is to implement interval trees mentioned in the same book, after the red-black. + * Interval are heavily based on red-black trees (most operations are the same). So, we begin first + * with implementing red-black! + * + * Authors: + * ? <?@?.?> + * + * Copyright 2009-2009 Evangelos Katsikaros + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef SEEN_LIB2GEOM_RTREE_H +#define SEEN_LIB2GEOM_RTREE_H + +#include <vector> +#include <utility> + + +#include <2geom/d2.h> +#include <2geom/interval.h> + +namespace Geom{ + +// used only in pick_next( ) +enum enum_add_to_group { + ADD_TO_GROUP_A = 0, + ADD_TO_GROUP_B +}; + + +enum enum_split_strategy { + QUADRATIC_SPIT = 0, + LINEAR_COST, + TOTAL_STRATEGIES // this one must be the last item +}; + + + +template <typename T> +class pedantic_vector:public std::vector<T> { +public: + pedantic_vector(size_t s=0) : std::vector<T>(s) {} + T& operator[](unsigned i) { + //assert(i >= 0); + assert(i < std::vector<T>::size()); + return std::vector<T>::operator[](i); + } + T const& operator[](unsigned i) const { + //assert(i >= 0); + assert(i < std::vector<T>::size()); + return std::vector<T>::operator[](i); + } +/* + erase( std::vector<T>::iterator it ) { + //assert(i >= 0); + assert( it < std::vector<T>::size()); + return std::vector<T>::erase(it); + } +*/ +}; + +class RTreeNode; + +class RTreeRecord_Leaf{ +public: + Rect bounding_box; + int data; + + RTreeRecord_Leaf(): bounding_box(), data(0) + {} + + RTreeRecord_Leaf(Rect bb, int d): bounding_box(bb), data(d) + {} +}; + +class RTreeRecord_NonLeaf{ +public: + Rect bounding_box; + RTreeNode* data; + + RTreeRecord_NonLeaf(): bounding_box(), data(0) + {} + + RTreeRecord_NonLeaf(Rect bb, RTreeNode* d): bounding_box(bb), data(d) + {} +}; + +/* +R-Tree has 2 kinds of nodes +* Leaves which store: + - the actual data + - the bounding box of the data + +* Non-Leaves which store: + - a child node (data) + - the bounding box of the child node + +This causes some code duplication in rtree.cpp. There are 2 cases: +- we care whether we touch a leaf/non-leaf node, since we write data in the node, so we want to + write the correct thing (int or RTreeNode*) +- we do NOT care whether we touch a leaf/non-leaf node, because we only read/write the bounding + boxes which is the same in both cases. + +TODO: +A better design would eliminate the duplication in the 2nd case, but we can't avoid the 1st probably. +*/ +class RTreeNode{ +public: + // first: bounding box + // second: "data" (leaf-node) or node (NON leaf-node) + //pedantic_vector< RTreeRecord_Leaf > children_leaves; // if this is empty, then node is leaf-node + //pedantic_vector< RTreeRecord_NonLeaf > children_nodes; // if this is empty, then node is NON-leaf node + + std::vector< RTreeRecord_Leaf > children_leaves; // if this is empty, then node is leaf-node + std::vector< RTreeRecord_NonLeaf > children_nodes; // if this is empty, then node is NON-leaf node + + RTreeNode(): children_leaves(0), children_nodes(0) + {} + +}; + + +class RTree{ +public: + RTreeNode* root; + + // min/max records per node + unsigned min_records; + unsigned max_records; // allow +1 (used during insert) + + enum_split_strategy split_strategy; + + + RTree( unsigned n, unsigned m, enum_split_strategy split_s ): + root(0), min_records( n ), max_records( m ), split_strategy( split_s ), + tree_height(0) + {} + + void insert( Rect const &r, unsigned shape); + void search( const Rect &search_area, std::vector< int >* result, const RTreeNode* subtree ) const; + //int erase( const RTreeRecord_Leaf & search ); + int erase( const Rect &search_area, const int shape_to_delete ); + +// update + + void print_tree(RTreeNode* subtree_root, int depth ) const; + +private: + unsigned tree_height; // 0 is the root level + + void insert( //Rect const &r, + //int shape, + const RTreeRecord_Leaf &leaf_record, + const bool &insert_high = false, + const unsigned &stop_height = 0, + const RTreeRecord_NonLeaf &nonleaf_record = RTreeRecord_NonLeaf() + ); + // I1 + RTreeNode* choose_node( const Rect &r, const bool &insert_high = false, const unsigned &stop_height=0 ) const; + double find_waste_area( const Rect &a, const Rect &b ) const; + double find_enlargement( const Rect &a, const Rect &b ) const; + + // I2 + std::pair<RTreeNode*, RTreeNode*> split_node( RTreeNode *s ); + // QUADRATIC_SPIT + std::pair<RTreeNode*, RTreeNode*> quadratic_split( RTreeNode* s ); + std::pair<unsigned, unsigned> pick_seeds( RTreeNode* s ) const; + std::pair<unsigned, enum_add_to_group> pick_next( RTreeNode* group_a, RTreeNode* group_b, RTreeNode* s, std::vector<bool> &assigned_v ); + // others... + + // I3 + bool adjust_tree( RTreeNode* position, + std::pair<RTreeNode*, RTreeNode*> &node_division, + bool split_performed + ); + std::pair< RTreeNode*, bool > find_parent( RTreeNode* subtree_root, Rect search_area, RTreeNode* wanted ) const; + + void recalculate_bounding_box( RTreeNode* parent, RTreeNode* child, unsigned &child_in_parent ); + + void copy_group_a_to_existing_node( RTreeNode *position, RTreeNode* group_a ); + RTreeRecord_NonLeaf create_nonleaf_record_from_rtreenode( Rect &new_entry_bounding, RTreeNode *rtreenode ); + RTreeRecord_Leaf create_leaf_record_from_rtreenode( Rect &new_entry_bounding, RTreeNode *rtreenode ); + + // erase +// RTreeNode* find_leaf( RTreeNode* subtree, const RTreeRecord_Leaf &search ) const; + RTreeNode* find_leaf( RTreeNode* subtree, const Rect &search_area, const int shape_to_delete ) const; + + bool condense_tree( RTreeNode* position + // std::pair<RTreeNode*, RTreeNode*> &node_division, // modified: it holds the last split group + // bool initial_split_performed, + // const unsigned min_nodes + // const unsigned max_nodes + ); + int remove_record_from_parent( RTreeNode* parent, RTreeNode* child ); + void sanity_check(RTreeNode* subtree_root, int depth, bool used_during_insert = false ) const; + +}; + +} // end namespace Geom + +#endif // !SEEN_LIB2GEOM_RTREE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/orphan-code/sbasis-of.h b/include/2geom/orphan-code/sbasis-of.h new file mode 100644 index 0000000..e5b76d6 --- /dev/null +++ b/include/2geom/orphan-code/sbasis-of.h @@ -0,0 +1,638 @@ +/**
+ * \file
+ * \brief Defines S-power basis function class
+ * with coefficient in arbitrary ring
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#ifndef SEEN_SBASIS_OF_H
+#define SEEN_SBASIS_OF_H
+#include <vector>
+#include <cassert>
+#include <iostream>
+
+#include <2geom/interval.h>
+#include <2geom/utils.h>
+#include <2geom/exception.h>
+
+#include <2geom/orphan-code/linear-of.h>
+
+namespace Geom {
+
+template<typename T>
+class SBasisOf;
+
+#ifdef USE_SBASIS_OF
+typedef Geom::SBasisOf<double> SBasis;
+#endif
+
+/*** An empty SBasisOf<T> is identically 0. */
+template<typename T>
+class SBasisOf : public std::vector<LinearOf<T> >{
+public:
+ SBasisOf() {}
+ explicit SBasisOf(T a) {
+ this->push_back(LinearOf<T>(a,a));
+ }
+ SBasisOf(SBasisOf<T> const & a) :
+ std::vector<LinearOf<T> >(a)
+ {}
+ SBasisOf(LinearOf<T> const & bo) {
+ this->push_back(bo);
+ }
+ SBasisOf(LinearOf<T>* bo) {
+ this->push_back(*bo);
+ }
+ //static unsigned input_dim(){return T::input_dim()+1;}
+
+ //IMPL: FragmentConcept
+ typedef T output_type;
+ inline bool isZero() const {
+ if(this->empty()) return true;
+ for(unsigned i = 0; i < this->size(); i++) {
+ if(!(*this)[i].isZero()) return false;
+ }
+ return true;
+ }
+ inline bool isConstant() const {
+ if (this->empty()) return true;
+ for (unsigned i = 0; i < this->size(); i++) {
+ if(!(*this)[i].isConstant()) return false;
+ }
+ return true;
+ }
+
+ //TODO: code this...
+ bool isFinite() const;
+
+ inline T at0() const {
+ if(this->empty()) return T(0); else return (*this)[0][0];
+ }
+ inline T at1() const{
+ if(this->empty()) return T(0); else return (*this)[0][1];
+ }
+
+ T valueAt(double t) const {
+ double s = t*(1-t);
+ T p0 = T(0.), p1 = T(0.);
+ for(unsigned k = this->size(); k > 0; k--) {
+ const LinearOf<T> &lin = (*this)[k-1];
+ p0 = p0*s + lin[0];
+ p1 = p1*s + lin[1];
+ }
+ return p0*(1-t) + p1*t;
+ }
+
+ T operator()(double t) const {
+ return valueAt(t);
+ }
+
+ /**
+ * The size of the returned vector equals n+1.
+ */
+ std::vector<T> valueAndDerivatives(double t, unsigned n) const{
+ std::vector<T> ret(n+1);
+ ret[0] = valueAt(t);
+ SBasisOf<T> tmp = *this;
+ for(unsigned i = 0; i < n; i++) {
+ tmp.derive();
+ ret[i+1] = tmp.valueAt(t);
+ }
+ return ret;
+ }
+
+ //The following lines only makes sens if T=double!
+ SBasisOf<T> toSBasis() const { return SBasisOf<T>(*this); }
+ double tailError(unsigned tail) const{
+ Interval bs = *bounds_fast(*this, tail);
+ return std::max(fabs(bs.min()),fabs(bs.max()));
+ }
+
+// compute f(g)
+ SBasisOf<T> operator()(SBasisOf<T> const & g) const;
+
+ LinearOf<T> operator[](unsigned i) const {
+ assert(i < this->size());
+ return std::vector<LinearOf<T> >::operator[](i);
+ }
+
+//MUTATOR PRISON
+ LinearOf<T>& operator[](unsigned i) { return this->at(i); }
+
+ //remove extra zeros
+ void normalize() {
+ while(!this->empty() && this->back().isZero())
+ this->pop_back();
+ }
+
+ void truncate(unsigned k) { if(k < this->size()) this->resize(k); }
+private:
+ void derive(); // in place version
+ unsigned dim;
+};
+
+//template<>
+//inline unsigned SBasisOf<double>::input_dim() { return 1; }
+
+//--------------------------------------------------------------------------
+#ifdef USE_SBASIS_OF
+
+//implemented in sbasis-roots.cpp
+OptInterval bounds_exact(SBasis const &a);
+OptInterval bounds_fast(SBasis const &a, int order = 0);
+OptInterval bounds_local(SBasis const &a, const OptInterval &t, int order = 0);
+
+std::vector<double> roots(SBasis const & s);
+std::vector<std::vector<double> > multi_roots(SBasis const &f,
+ std::vector<double> const &levels,
+ double htol=1e-7,
+ double vtol=1e-7,
+ double a=0,
+ double b=1);
+#endif
+//--------------------------------------------------------------------------
+
+
+//TODO: figure out how to stick this in linear, while not adding an sbasis dep
+template<typename T>
+inline SBasisOf<T> LinearOf<T>::toSBasis() const { return SBasisOf<T>(*this); }
+
+template<typename T>
+inline SBasisOf<T> reverse(SBasisOf<T> const &a) {
+ SBasisOf<T> result;
+ result.reserve(a.size());
+ for(unsigned k = 0; k < a.size(); k++)
+ result.push_back(reverse(a[k]));
+ return result;
+}
+
+//IMPL: ScalableConcept
+template<typename T>
+inline SBasisOf<T> operator-(const SBasisOf<T>& p) {
+ if(p.isZero()) return SBasisOf<T>();
+ SBasisOf<T> result;
+ result.reserve(p.size());
+
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(-p[i]);
+ }
+ return result;
+}
+
+template<typename T>
+SBasisOf<T> operator*(SBasisOf<T> const &a, double k){
+ SBasisOf<T> c;
+ //TODO: what does this mean for vectors of vectors??
+ //c.reserve(a.size());
+ for(unsigned i = 0; i < a.size(); i++)
+ c.push_back(a[i] * k);
+ return c;
+}
+
+template<typename T>
+inline SBasisOf<T> operator*(double k, SBasisOf<T> const &a) { return a*k; }
+template<typename T>
+inline SBasisOf<T> operator/(SBasisOf<T> const &a, double k) { return a*(1./k); }
+template<typename T>
+SBasisOf<T>& operator*=(SBasisOf<T>& a, double b){
+ if (a.isZero()) return a;
+ if (b == 0)
+ a.clear();
+ else
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= b;
+ return a;
+}
+
+template<typename T>
+inline SBasisOf<T>& operator/=(SBasisOf<T>& a, double b) { return (a*=(1./b)); }
+
+/*
+//We can also multiply by element of ring coeff T:
+template<typename T>
+SBasisOf<T> operator*(SBasisOf<T> const &a, T k){
+ SBasisOf<T> c;
+ //TODO: what does this mean for vectors of vectors??
+ //c.reserve(a.size());
+ for(unsigned i = 0; i < a.size(); i++)
+ c.push_back(a[i] * k);
+ return c;
+}
+
+template<typename T>
+inline SBasisOf<T> operator*(T k, SBasisOf<T> const &a) { return a*k; }
+template<typename T>
+SBasisOf<T>& operator*=(SBasisOf<T>& a, T b){
+ if (a.isZero()) return a;
+ if (b == 0)
+ a.clear();
+ else
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= b;
+ return a;
+}
+*/
+
+//IMPL: AddableConcept
+template<typename T>
+inline SBasisOf<T> operator+(const SBasisOf<T>& a, const SBasisOf<T>& b){
+ SBasisOf<T> result;
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ //TODO: what does this mean for vector<vector>;
+ //result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back(a[i] + b[i]);
+ }
+ for(unsigned i = min_size; i < a.size(); i++)
+ result.push_back(a[i]);
+ for(unsigned i = min_size; i < b.size(); i++)
+ result.push_back(b[i]);
+
+ assert(result.size() == out_size);
+ return result;
+}
+
+template<typename T>
+SBasisOf<T> operator-(const SBasisOf<T>& a, const SBasisOf<T>& b){
+ SBasisOf<T> result;
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ //TODO: what does this mean for vector<vector>;
+ //result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back(a[i] - b[i]);
+ }
+ for(unsigned i = min_size; i < a.size(); i++)
+ result.push_back(a[i]);
+ for(unsigned i = min_size; i < b.size(); i++)
+ result.push_back(-b[i]);
+
+ assert(result.size() == out_size);
+ return result;
+}
+
+template<typename T>
+SBasisOf<T>& operator+=(SBasisOf<T>& a, const SBasisOf<T>& b){
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ //TODO: what does this mean for vectors of vectors
+ //a.reserve(out_size);
+ for(unsigned i = 0; i < min_size; i++)
+ a[i] += b[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ a.push_back(b[i]);
+
+ assert(a.size() == out_size);
+ return a;
+}
+
+template<typename T>
+SBasisOf<T>& operator-=(SBasisOf<T>& a, const SBasisOf<T>& b){
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ //TODO: what does this mean for vectors of vectors
+ //a.reserve(out_size);
+ for(unsigned i = 0; i < min_size; i++)
+ a[i] -= b[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ a.push_back(-b[i]);
+
+ assert(a.size() == out_size);
+ return a;
+}
+
+//TODO: remove?
+template<typename T>
+inline SBasisOf<T> operator+(const SBasisOf<T> & a, LinearOf<T> const & b) {
+ if(b.isZero()) return a;
+ if(a.isZero()) return b;
+ SBasisOf<T> result(a);
+ result[0] += b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T> operator-(const SBasisOf<T> & a, LinearOf<T> const & b) {
+ if(b.isZero()) return a;
+ SBasisOf<T> result(a);
+ result[0] -= b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T>& operator+=(SBasisOf<T>& a, const LinearOf<T>& b) {
+ if(a.isZero())
+ a.push_back(b);
+ else
+ a[0] += b;
+ return a;
+}
+template<typename T>
+inline SBasisOf<T>& operator-=(SBasisOf<T>& a, const LinearOf<T>& b) {
+ if(a.isZero())
+ a.push_back(-b);
+ else
+ a[0] -= b;
+ return a;
+}
+
+//IMPL: OffsetableConcept
+/*
+template<typename T>
+inline SBasisOf<T> operator+(const SBasisOf<T> & a, double b) {
+ if(a.isZero()) return LinearOf<T>(b, b);
+ SBasisOf<T> result(a);
+ result[0] += b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T> operator-(const SBasisOf<T> & a, double b) {
+ if(a.isZero()) return LinearOf<T>(-b, -b);
+ SBasisOf<T> result(a);
+ result[0] -= b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T>& operator+=(SBasisOf<T>& a, double b) {
+ if(a.isZero())
+ a.push_back(LinearOf<T>(b,b));
+ else
+ a[0] += b;
+ return a;
+}
+template<typename T>
+inline SBasisOf<T>& operator-=(SBasisOf<T>& a, double b) {
+ if(a.isZero())
+ a.push_back(LinearOf<T>(-b,-b));
+ else
+ a[0] -= b;
+ return a;
+}
+*/
+//We can also offset by elements of coeff ring T
+template<typename T>
+inline SBasisOf<T> operator+(const SBasisOf<T> & a, T b) {
+ if(a.isZero()) return LinearOf<T>(b, b);
+ SBasisOf<T> result(a);
+ result[0] += b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T> operator-(const SBasisOf<T> & a, T b) {
+ if(a.isZero()) return LinearOf<T>(-b, -b);
+ SBasisOf<T> result(a);
+ result[0] -= b;
+ return result;
+}
+template<typename T>
+inline SBasisOf<T>& operator+=(SBasisOf<T>& a, T b) {
+ if(a.isZero())
+ a.push_back(LinearOf<T>(b,b));
+ else
+ a[0] += b;
+ return a;
+}
+template<typename T>
+inline SBasisOf<T>& operator-=(SBasisOf<T>& a, T b) {
+ if(a.isZero())
+ a.push_back(LinearOf<T>(-b,-b));
+ else
+ a[0] -= b;
+ return a;
+}
+
+
+template<typename T>
+SBasisOf<T> shift(SBasisOf<T> const &a, int sh){
+ SBasisOf<T> c = a;
+ if(sh > 0) {
+ c.insert(c.begin(), sh, LinearOf<T>(0,0));
+ } else {
+ //TODO: truncate
+ }
+ return c;
+}
+
+template<typename T>
+SBasisOf<T> shift(LinearOf<T> const &a, int sh) {
+ SBasisOf<T> c;
+ if(sh > 0) {
+ c.insert(c.begin(), sh, LinearOf<T>(0,0));
+ c.push_back(a);
+ }
+ return c;
+}
+
+template<typename T>
+inline SBasisOf<T> truncate(SBasisOf<T> const &a, unsigned terms) {
+ SBasisOf<T> c;
+ c.insert(c.begin(), a.begin(), a.begin() + std::min(terms, (unsigned)a.size()));
+ return c;
+}
+
+template<typename T>
+SBasisOf<T> multiply_add(SBasisOf<T> const &a, SBasisOf<T> const &b, SBasisOf<T> c) {
+ if(a.isZero() || b.isZero())
+ return c;
+ c.resize(a.size() + b.size(), LinearOf<T>(T(0.),T(0.)));
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ T tri = (b[j][1]-b[j][0])*(a[i-j][1]-a[i-j][0]);
+ c[i+1/*shift*/] += LinearOf<T>(-tri);
+ }
+ }
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ for(unsigned dim = 0; dim < 2; dim++)
+ c[i][dim] += b[j][dim]*a[i-j][dim];
+ }
+ }
+ c.normalize();
+ //assert(!(0 == c.back()[0] && 0 == c.back()[1]));
+ return c;
+}
+
+template<typename T>
+SBasisOf<T> multiply(SBasisOf<T> const &a, SBasisOf<T> const &b) {
+ SBasisOf<T> c;
+ if(a.isZero() || b.isZero())
+ return c;
+ return multiply_add(a, b, c);
+}
+
+template<typename T>
+SBasisOf<T> integral(SBasisOf<T> const &c){
+ SBasisOf<T> a;
+ T aTri = T(0.);
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (HatOf<T>(c[k]).d + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ a.normalize();
+ return a;
+}
+
+template<typename T>
+SBasisOf<T> derivative(SBasisOf<T> const &a){
+ SBasisOf<T> c;
+ c.resize(a.size(), LinearOf<T>());
+ if(a.isZero())
+ return c;
+
+ for(unsigned k = 0; k < a.size()-1; k++) {
+ T d = (2*k+1)*(a[k][1] - a[k][0]);
+
+ c[k][0] = d + (k+1)*a[k+1][0];
+ c[k][1] = d - (k+1)*a[k+1][1];
+ }
+ int k = a.size()-1;
+ T d = (2*k+1)*(a[k][1] - a[k][0]);
+ //TODO: do a real test to know if d==0!
+ if(d == T(0.0))
+ c.pop_back();
+ else {
+ c[k][0] = d;
+ c[k][1] = d;
+ }
+
+ return c;
+}
+
+template<typename T>
+void SBasisOf<T>::derive() { // in place version
+ if(isZero()) return;
+ for(unsigned k = 0; k < this->size()-1; k++) {
+ T d = (2*k+1)*((*this)[k][1] - (*this)[k][0]);
+
+ (*this)[k][0] = d + (k+1)*(*this)[k+1][0];
+ (*this)[k][1] = d - (k+1)*(*this)[k+1][1];
+ }
+ int k = this->size()-1;
+ T d = (2*k+1)*((*this)[k][1] - (*this)[k][0]);
+ if(d == 0)//TODO: give this a meaning for general coeff ring.
+ this->pop_back();
+ else {
+ (*this)[k][0] = d;
+ (*this)[k][1] = d;
+ }
+}
+
+
+template<typename T>
+inline SBasisOf<T> operator*(SBasisOf<T> const & a, SBasisOf<T> const & b) {
+ return multiply(a, b);
+}
+
+template<typename T>
+inline SBasisOf<T>& operator*=(SBasisOf<T>& a, SBasisOf<T> const & b) {
+ a = multiply(a, b);
+ return a;
+}
+
+// a(b(t))
+//TODO: compose all compatibles types!
+template<typename T>
+SBasisOf<T> compose(SBasisOf<T> const &a, SBasisOf<T> const &b){
+ SBasisOf<double> s = multiply((SBasisOf<T>(LinearOf<T>(1,1))-b), b);
+ SBasisOf<T> r;
+
+ for(int i = a.size()-1; i >= 0; i--) {
+ r = multiply_add(r, s, SBasisOf<T>(LinearOf<T>(HatOf<T>(a[i][0]))) - b*a[i][0] + b*a[i][1]);
+ }
+ return r;
+}
+
+template<typename T>
+SBasisOf<T> compose(SBasisOf<T> const &a, SBasisOf<T> const &b, unsigned k){
+ SBasisOf<T> s = multiply((SBasisOf<T>(LinearOf<T>(1,1))-b), b);
+ SBasisOf<T> r;
+
+ for(int i = a.size()-1; i >= 0; i--) {
+ r = multiply_add(r, s, SBasisOf<T>(LinearOf<T>(HatOf<T>(a[i][0]))) - b*a[i][0] + b*a[i][1]);
+ }
+ r.truncate(k);
+ return r;
+}
+template<typename T>
+SBasisOf<T> compose(LinearOf<T> const &a, SBasisOf<T> const &b){
+ return compose(SBasisOf<T>(a),b);
+}
+template<typename T>
+SBasisOf<T> compose(SBasisOf<T> const &a, LinearOf<T> const &b){
+ return compose(a,SBasisOf<T>(b));
+}
+template<typename T>//TODO: don't be so lazy!!
+SBasisOf<T> compose(LinearOf<T> const &a, LinearOf<T> const &b){
+ return compose(SBasisOf<T>(a),SBasisOf<T>(b));
+}
+
+
+
+template<typename T>
+inline SBasisOf<T> portion(const SBasisOf<T> &t, double from, double to) { return compose(t, LinearOf<T>(from, to)); }
+
+// compute f(g)
+template<typename T>
+inline SBasisOf<T>
+SBasisOf<T>::operator()(SBasisOf<T> const & g) const {
+ return compose(*this, g);
+}
+
+template<typename T>
+inline std::ostream &operator<< (std::ostream &out_file, const LinearOf<T> &bo) {
+ out_file << "{" << bo[0] << ", " << bo[1] << "}";
+ return out_file;
+}
+
+template<typename T>
+inline std::ostream &operator<< (std::ostream &out_file, const SBasisOf<T> & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ out_file << p[i] << "s^" << i << " + ";
+ }
+ return out_file;
+}
+
+};
+#endif
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/include/2geom/orphan-code/sbasisN.h b/include/2geom/orphan-code/sbasisN.h new file mode 100644 index 0000000..0b5a48f --- /dev/null +++ b/include/2geom/orphan-code/sbasisN.h @@ -0,0 +1,1123 @@ +/**
+ * \file
+ * \brief Multi-dimensional symmetric power basis function.
+ * A SBasisN<n> is a polynomial f of n variables (t0,...,tn-1),
+ * written in a particular form. Let si = ti(1-t_i). f is written as
+ *
+ * f = sum_p s^p a_{p}(t0,...,t_{n-1})
+ *
+ * where p=(p0,...,p_{n-1}) is a multi index (called MultiDegree<n> in the code)
+ * s^p = prod_i si^pi, and a_p is a LinearN<n>.
+ * Recall a LinearN<n> is sum over all choices xi = ti or (1-ti) of terms of form
+ * a * x0*...*x_{n-1}
+ *
+ * Caution: degrees are expressed as degrees of s=t*(1-t). The real degree
+ * (with respect to t) of the polynomial is twice that + 0 or 1 depending
+ * whether the relevant LinearN<n> coeff is constant or not.
+ *//*
+ *
+ * Authors:
+ * JF Barraud <jf.barraud@gmail.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#ifndef SEEN_SBASISN_H
+#define SEEN_SBASISN_H
+#include <vector>
+#include <cassert>
+#include <iostream>
+
+#include <2geom/orphan-code/linearN.h>
+#include <2geom/linear.h>//for conversion purpose
+#include <2geom/sbasis.h>//for conversion purpose
+#include <2geom/interval.h>
+#include <2geom/utils.h>
+#include <2geom/exception.h>
+
+
+namespace Geom{
+
+/** MultiDegree:
+ * \brief multi-degree (p0,...,p^{n-1}) of a s0^p0...s_{n-1}p^{n-1} monomial.
+ *
+ * a "Multi_deg" is a sequence p={p0,...,p_n-1} representing the monomial
+ * s^p = s_0^{p_0}*...*s_{n-1}^{p_{n-1}}.
+ * Caution: the degrees are expressed with respect to si! ( in SBasis code
+ * below, si = ti*(1-ti) ).
+ */
+
+template<unsigned n>
+class MultiDegree{
+public:
+ unsigned p[n];
+ MultiDegree(){
+ for (unsigned i = 0; i <n; i++) {
+ p[i]=0;
+ }
+ }
+ MultiDegree( unsigned const other_p[] ){
+ p = other_p;
+ }
+ MultiDegree(unsigned const idx, unsigned const sizes[]){
+ unsigned q = idx;
+ for (unsigned i = n-1; i >0; i--) {
+ div_t d = std::div(int(q), int(sizes[i]));
+ p[i] = d.rem;
+ q = d.quot;
+ }
+ p[0] = q;
+ }
+ unsigned operator[](const unsigned i) const {
+ assert(i < n); return p[i];
+ }
+ unsigned& operator[](const unsigned i) {
+ assert(i < n); return p[i];
+ }
+
+ unsigned asIdx(unsigned const sizes[]) const{
+ unsigned ret = p[0];
+ bool in_range = (p[0]<sizes[0]);
+ for (unsigned i = 1; i < n; i++) {
+ in_range = in_range && (p[i]<sizes[i]);
+ ret = ret*sizes[i] + p[i];
+ }
+ if (in_range) return ret;
+ //TODO: find a better warning than returning out of range idx!
+ ret =1;
+ for (unsigned i = 0; i < n; i++) {
+ ret *= sizes[i];
+ }
+ return ret;
+ }
+ bool stepUp(unsigned const sizes[], unsigned frozen_mask = 0){
+ unsigned i = 0;
+ while ( i < n && ( (1<<i) & frozen_mask ) ) i++;
+ while ( i <n && p[i] == sizes[i]-1 ) {
+ i++;
+ while (i<n && ( (1<<i) & frozen_mask ) ) i++;
+ }
+ if (i<n){
+ p[i]+=1;
+ for (unsigned j = 0; j < i; j++) {
+ if ( !( (1<<j) & frozen_mask ) ) p[j] = 0;
+ }
+ return true;
+ }else{
+ return false;
+ }
+ }
+ bool stepDown(unsigned const sizes[], unsigned frozen_mask = 0){
+ int i = n-1;
+ while (i>=0 && ( (1<<i) & frozen_mask ) ) i--;
+ while ( i >= 0 && p[i] == 0 ) {
+ i--;
+ while (i>=0 && ( (1<<i) & frozen_mask ) ) i--;
+ }
+ if ( i >= 0 ){
+ p[i]-=1;
+ for (unsigned j = i+1; j < n; j++) {
+ if ( !( (1<<j) & frozen_mask ) ) p[j] = sizes[j]-1;
+ }
+ return true;
+ }else{
+ return false;
+ }
+ }
+};
+
+/**
+ * Returns the maximal degree appearing in the two arguments for each variables.
+ */
+template <unsigned n>
+MultiDegree<n> max(MultiDegree<n> const &p, MultiDegree<n> const &q){
+ MultiDegree<n> ret;
+ for (unsigned i = 0; i <n; i++) {
+ ret.p[i] = (p[i]>q[i] ? p[i] : q[i]);
+ }
+ return ret;
+}
+
+template <unsigned n>
+MultiDegree<n> operator + (MultiDegree<n> const &p, MultiDegree<n> const &q){
+ MultiDegree<n> ret;
+ for (unsigned i = 0; i <n; i++) {
+ ret.p[i] = p[i] + q[i];
+ }
+ return ret;
+}
+template <unsigned n>
+MultiDegree<n> operator += (MultiDegree<n> const &p, MultiDegree<n> const &q){
+ for (unsigned i = 0; i <n; i++) {
+ p[i] += q[i];
+ }
+ return p;
+}
+
+/**
+ * \brief MultiDegree comparison.
+ * A MultiDegree \param p is smaller than another \param q
+ * if all it's smaller for all variables.
+ *
+ * In particular, p<=q and q<=p can both be false!
+ */
+template<unsigned n>
+bool operator<=(MultiDegree<n> const &p, MultiDegree<n> const &q){
+ for (unsigned i = 0; i <n; i++) {
+ if (p[i]>q[i]) return false;
+ }
+ return true;
+}
+
+
+/**
+ * \brief Polynomials of n variables, written in SBasis form.
+ * An SBasisN<n> f is internaly represented as a vector of LinearN<n>.
+ * It should be thought of as an n-dimensional vector: the coef of s0^p0...s_{n-1}p^{n-1}
+ * is soterd in f[p0,...,p_{n-1}]. The sizes of each dimension is stored in "sizes".
+ * Note: an empty SBasis is identically 0.
+ */
+template<unsigned n>
+class SBasisN : public std::vector<LinearN<n> >{
+public:
+ unsigned sizes[n];
+ SBasisN() {
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = 0;
+ }
+ }
+ explicit SBasisN(double a) {
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = 1;
+ }
+ this->push_back(LinearN<n>(a));
+ }
+ SBasisN(SBasisN<n> const & a) : std::vector<LinearN<n> >(a){
+ //TODO: efficient array copy??
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = a.sizes[i];
+ }
+ }
+ SBasisN(LinearN<n> const & bo) {
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = 1;
+ }
+ this->push_back(bo);
+ }
+ SBasisN(LinearN<n>* bo) {
+ for (unsigned i = 0; i < n; i++) {
+ sizes[i] = 1;
+ }
+ this->push_back(*bo);
+ }
+
+//----------------------------------------------
+//-- Degree/Sizing facilities ------------------
+//----------------------------------------------
+/**
+ * Internal recursive function used to compute partial degrees.
+ */
+ bool find_non_empty_level(unsigned var, MultiDegree<n> &fixed_degrees)const{
+ if (this->size()==0){
+ for (unsigned i = 0; i < n; i++) {
+ fixed_degrees[i] = 0;//FIXME this should be -infty!!
+ }
+ return false;
+ }
+ if ( !((*this)[fixed_degrees.asIdx(sizes)].isZero()) ) return true;
+
+ unsigned frozen = (1<<var);
+ if ( fixed_degrees.stepDown(sizes, frozen) ){
+ if ( find_non_empty_level(var, fixed_degrees) ) return true;
+ }
+ if ( fixed_degrees[var] > 0 ){
+ fixed_degrees[var] -= 1;
+ for (unsigned i = 0; i < n; i++) {
+ if (i!=var) fixed_degrees[i] = sizes[i]-1;
+ }
+ if (find_non_empty_level(var, fixed_degrees)) return true;
+ }
+ return false;//FIXME: this should return -infty in all variables!
+ }
+
+/**
+ * Returns the degree of an SBasisN<n> with respect to a given variable form its sizes.
+ * All terms are taken into account, even eventual trailing zeros.
+ * Note: degree is expressed with respect to s = t*(1-t), not t itself.
+ */
+ unsigned quick_degree(unsigned var) const{
+ return ( sizes[var] > 0 ? sizes[var]-1 : 0 );//this should be -infty.
+ }
+/**
+ * Computes the multi degree of the SBasis from it's sizes.
+ * All terms are taken into account, even eventual trailing zeros.
+ * Note: degrees are expressed with respect to s = t*(1-t), not t itself.
+ */
+ MultiDegree<n> quick_multi_degree() const{
+ MultiDegree<n> ret;
+ if (this->size()==0) return ret;//should be -infty for all vars.
+ for (unsigned i = 0; i < n; i++) {
+ assert( sizes[i]>0 );
+ ret.p[i] = sizes[i]-1;
+ }
+ return ret;
+ }
+/**
+ * Returns the degree of an SBasisN<n> with respect to a given variable.
+ * Trailing zeros are not taken into account.
+ * Note: degree is expressed with respect to s = t*(1-t), not t itself.
+ */
+ unsigned degree(unsigned var)const{
+ MultiDegree<n> degrees;
+ for(unsigned i = 0; i < n; i++) {
+ degrees[i] = sizes[i]-1;
+ }
+ if ( find_non_empty_level(var, degrees) ) return degrees[var];
+ else return 0;//should be -infty.
+ }
+/**
+ * Returns the *real* degree of an SBasisN<n> with respect to a given variable.
+ * Trailing zeros are not taken into account.
+ * Note: degree is expressed with respect to t itself, not s = t*(1-t).
+ * In particular: real_t_degree() = 2*degree() + 0 or 1.
+ */
+ unsigned real_t_degree(unsigned var)const{
+ unsigned deg = 0;
+ bool even = true;
+ bool notfound = true;
+ unsigned frozen = (1<<var);
+ MultiDegree<n> degrees;
+ for(unsigned i = 0; i < n; i++) {
+ degrees[i] = sizes[i]-1;
+ }
+ while( notfound ){
+ if ( find_non_empty_level(var, degrees) && degrees[var]>= deg ){
+ deg = degrees[var];
+ even = (*this)[degrees.asIdx(sizes)].isConstant(var);
+ }
+ notfound = even && degrees.stepDown(sizes, frozen);
+ }
+ return 2*deg + ( even ? 0 : 1 );
+ }
+/**
+ * Returns the *real* degrees of an SBasisN<n>.
+ * Trailing zeros are not taken into account.
+ * Note: degree is expressed with respect to t itself, not s = t*(1-t).
+ * In particular: real_t_degree() = 2*degree() + 0 or 1.
+ */
+ MultiDegree<n> real_t_degrees()const{
+ MultiDegree<n>res;
+ for(unsigned i = 0; i < n; i++) {
+ res[i] = real_t_degree(i);
+ }
+ return res;
+ }
+/**
+ * Computes the multi degree of the SBasis.
+ * Trailing zeros are not taken into account.
+ * Note: degree is expressed with respect to s = t*(1-t), not t itself.
+ */
+ MultiDegree<n> multi_degree() const{
+ MultiDegree<n> ret;
+ if (this->size()==0) return ret;//should be -infty for all vars.
+ for (unsigned i = 0; i < n; i++) {
+ ret[i] = this->degree(i);
+ }
+ return ret;
+ }
+/**
+ * Returns the highest degree over all variables.
+ * Note: degree is expressed with respect to s = t*(1-t), not t itself.
+ */
+ unsigned max_degree() const {
+ if (this->size()==0) return 0;//should be -infty!
+ unsigned d=0;
+ for (unsigned i = 0; i < n; i++) {
+ assert( sizes[i]>0 );
+ if (d < sizes[i]-1) d = sizes[i]-1;
+ }
+ return d;
+ }
+
+/**
+ * Resize an SBasisN<n> to match new sizes.
+ *
+ * Caution: if a new size is smaller, the corresponding coefficients are discarded.
+ */
+ void multi_resize(unsigned new_sizes[], LinearN<n> def_value = LinearN<n>(0.)){
+ SBasisN<n> result;
+ bool nothing_todo = true;
+ unsigned tot_size = 1;
+ for(unsigned i = 0; i < n; i++) {
+ nothing_todo = nothing_todo && (sizes[i] == new_sizes[i]);
+ result.sizes[i] = new_sizes[i];
+ tot_size *= new_sizes[i];
+ }
+ if (nothing_todo) return;
+ result.resize(tot_size, def_value);
+ for(unsigned i = 0; i < tot_size; i++) {
+ MultiDegree<n> d( i, result.sizes );
+ unsigned j = d.asIdx(sizes);
+ if ( j < this->size() ){
+ result[i] = (*this)[j];
+ }
+ }
+ *this = result;
+ }
+
+ //remove extra zeros
+ void normalize() {
+ MultiDegree<n> max_p = multi_degree();
+ unsigned new_sizes[n];
+ for (unsigned i=0; i<n; i++){
+ new_sizes[i] = max_p[i]+1;
+ }
+ multi_resize(new_sizes);
+ }
+
+//-----------------------------
+//-- Misc. --------------------
+//-----------------------------
+
+/**
+ * Returns the number of variables this function takes as input: n.
+ */
+ unsigned input_dim(){return n;};
+
+ //IMPL: FragmentConcept
+ typedef double output_type;
+
+ inline bool isZero() const {
+ if(this->size()==0) return true;
+ for(unsigned i = 0; i < this->size(); i++) {
+ if(!(*this)[i].isZero()) return false;
+ }
+ return true;
+ }
+ inline bool isConstant() const {
+ if (this->size()==0) return true;
+ if(!(*this)[0].isConstant()) return false;
+ for (unsigned i = 1; i < this->size(); i++) {
+ if(!(*this)[i].isZero()) return false;
+ }
+ return true;
+ }
+
+ bool isFinite() const{
+ for (unsigned i = 0; i < this->size(); i++) {
+ if(!(*this)[i].isFinite()) return false;
+ }
+ return true;
+ }
+
+
+//------------------------------------------
+//-- Evaluation methods --------------------
+//------------------------------------------
+/**
+ * Returns the value of the SBasis at a given corner of [0,1]^n.
+ * \param k describes the corner: if i-th bit is 0, ti=0, otherwise ti=1.
+ */
+ inline double atCorner(unsigned k) const {
+ if(this->size()==0) return 0.;
+ return (*this)[0].atCorner(k);
+ }
+/**
+ * Returns the value of the SBasis at a given corner of [0,1]^n.
+ * \param t[n] describes the corner: the values should be 0's and 1's.
+ */
+ inline double atCorner(double t[]) const {
+ if(this->size()==0) return 0.;
+ return (*this)[0].atCorner(t);
+ }
+/**
+ * Returns a "slice" of the array.
+ * Returns an SBasis containing all the coeff of (s-)degree \param deg in variable \param var
+ */
+ //TODO: move by bigger blocks (?) but they are broken into pieces in general...
+ SBasisN<n> slice(unsigned const var, unsigned const deg) const{
+ if (deg >= sizes[var] ) return SBasisN<n>();
+ SBasisN<n> res;
+ unsigned tot_size = 1;
+ for (unsigned i = 0; i < n; i++) {
+ res.sizes[i] = (i==var ? 1 : sizes[i]);
+ tot_size *= res.sizes[i];
+ }
+ res.resize( tot_size, LinearN<n>(0.));
+ for (unsigned i = 0; i < tot_size; i++) {
+ MultiDegree<n> d(i,res.sizes);
+ d.p[var] = deg;
+ res[i] = (*this)[d.asIdx(sizes)];
+ }
+ return res;
+ }
+/**
+ * Returns a the SBasisN<n-1> obtained by setting variable \param var to 0.
+ */
+ inline SBasisN<n-1> at0(unsigned var=0, unsigned deg=0) const {
+ SBasisN<n> sl = slice(var,deg);
+ SBasisN<n-1> res;
+ res.reserve(sl.size());
+ for (unsigned i = 0; i < n-1; i++) {
+ res.sizes[i] = sizes[ ( i<var ? i : i+1 ) ];
+ }
+ for (unsigned i = 0; i < sl.size(); i++) {
+ res.push_back( sl[i].at0(var) );
+ }
+ return res;
+ }
+/**
+ * Returns a the SBasisN<n-1> obtained by setting variable \param var to 1.
+ */
+ inline SBasisN<n-1> at1(unsigned var=0, unsigned deg=0) const {
+ SBasisN<n> sl = slice(var,deg);
+ SBasisN<n-1> res;
+ res.reserve(sl.size());
+ for (unsigned i = 0; i < n-1; i++) {
+ res.sizes[i] = sizes[ ( i<var ? i : i+1 ) ];
+ }
+ for (unsigned i = 0; i < sl.size(); i++) {
+ res.push_back( sl[i].at1(var) );
+ }
+ return res;
+ }
+/**
+ * Returns a the SBasisN<n-1> obtained by setting variable \param var to \param t.
+ */
+ inline SBasisN<n-1> partialEval(double t, unsigned var=0 ) const {
+ SBasisN<n> sl;
+ double s = t*(1-t);
+ double si = 1;
+ for (unsigned i = 0; i <sizes[var]; i++) {
+ sl = sl + slice(var, i)*si;
+ si *= s;
+ }
+ SBasisN<n-1> res;
+ res.resize(sl.size(), LinearN<n-1>(0.));
+ for (unsigned i = 0; i < n-1; i++) {
+ res.sizes[i] = sizes[ ( i<var ? i : i+1 ) ];
+ }
+ for (unsigned i = 0; i < sl.size(); i++) {
+ res[i] = sl[i].partialEval(t,var);
+ }
+ return res;
+ }
+
+/**
+ * \brief Internal recursive function.
+ * Replace each variable by it's value in the 's=t*(1-t)' factor
+ * but not in the LinearN<n> coeffs. Then sum up all coefficients.
+ * \param t[n]: values of the variables.
+ */
+ LinearN<n> sumCoefs( double t[], unsigned const k, unsigned const idx) const{
+ LinearN<n> a;
+ if (k == n){
+ a = (*this)[idx];
+ return (*this)[idx];
+ }
+ double s = t[k]*(1-t[k]);
+ double si=1;
+ for (unsigned i=0; i<sizes[k]; i++){
+ a += sumCoefs(t,k+1,idx*sizes[k]+i)*si;;
+ si *= s;
+ }
+ return a;
+ }
+/**
+ * Evaluate at given n-dimensional point.
+ * \param t[n]: values of the variables.
+ */
+ double valueAt(double t[]) const {
+ LinearN<n> a = sumCoefs(t,0,0);
+ return a.valueAt(t);
+ }
+
+ double operator()(double t[]) const {
+ return valueAt(t);
+ }
+
+ //double valueAndDerivative(double t, double &der) const;
+ //std::vector<double> valueAndDerivatives(double t, unsigned n) const;
+ //SBasisN toSBasisN() const { return SBasisN(*this); }
+ //double tailError(unsigned tail) const;
+
+
+//--------------------------------------------------
+//-- Coeff. manipulation ---------------------------
+//--------------------------------------------------
+
+/**
+ * Accessing the SBasisN<n> coefficients.
+ */
+ LinearN<n> operator[](unsigned i) const {
+ assert(i < this->size());
+ return std::vector<LinearN<n> >::operator[](i);
+ }
+ LinearN<n> operator[](MultiDegree<n> const &p) const {
+ unsigned i = p.asIdx(sizes);
+ assert(i < this->size());
+ return std::vector<LinearN<n> >::operator[](i);
+ }
+
+//MUTATOR PRISON
+ LinearN<n>& operator[](unsigned i) { return this->at(i); }
+// LinearN<n>& operator[](MultiDegree const &p) {
+// unsigned i = p.asIdx(sizes);
+// return this->at(i);
+// }
+
+ void appendCoef(const SBasisN<n-1> &a, const SBasisN<n-1> &b, unsigned var=0){
+ unsigned new_sizes[n];
+ MultiDegree<n-1> deg_a = a.multi_degree(), deg_b = b.multi_degree();
+ MultiDegree<n-1> dcoef = max( deg_a, deg_b );
+ for (unsigned i=0; i<n; i++){
+ if ( i == var ){
+ new_sizes[var] = sizes[var] + 1;
+ }else{
+ unsigned coef_size = dcoef[(i<var?i:i-1)] + 1;
+ new_sizes[i] = ( sizes[i]>coef_size ? sizes[i] : coef_size );
+ }
+ }
+ multi_resize(new_sizes);
+
+ MultiDegree<n> d;
+ d[var] = sizes[var]-1;
+ unsigned frozen_mask = (1<<var);
+ do{
+ for (unsigned i=0; i<n-1; i++){
+ dcoef.p[i] = d.p[ ( i<var ? i : i+1) ];
+ }
+ LinearN<n-1> a_d,b_d;
+ unsigned ia = dcoef.asIdx(a.sizes);
+ if ( ia < a.size() ) a_d = a[ia];
+ unsigned ib = dcoef.asIdx(b.sizes);
+ if ( ib < b.size() ) b_d = b[ib];
+ (*this)[d.asIdx(sizes)] = LinearN<n>(a_d,b_d);
+ }while (d.stepUp(sizes,frozen_mask));
+ }
+
+//private:
+ //void derive(); // in place version
+};
+
+//SBasisN<0> is a double. Specialize it out.
+template<>
+class SBasisN<0>{
+public:
+ double d;
+ SBasisN () {}
+ SBasisN(double d) :d(d) {}
+ operator double() const { return d; }
+};
+
+
+//SBasisN<1> are usual SBasis. Allow conversion.
+SBasis toSBasis(SBasisN<1> f){
+ SBasis res(f.size(), Linear());
+ for (unsigned i = 0; i < f.size(); i++) {
+ res[i] = toLinear(f[i]);
+ }
+ return res;
+}
+
+//TODO: figure out how to stick this in linear, while not adding an sbasis dep
+template<unsigned n>
+inline SBasisN<n> LinearN<n>::toSBasisN() const { return SBasisN<n>(*this); }
+
+
+
+
+//implemented in sbasis-roots.cpp
+//OptInterval bounds_exact(SBasisN const &a);
+//OptInterval bounds_fast(SBasisN const &a, int order = 0);
+//OptInterval bounds_local(SBasisN const &a, const OptInterval &t, int order = 0);
+
+/** Returns a function which reverses the domain of a.
+ \param a sbasis function
+
+useful for reversing a parameteric curve.
+*/
+//template<unsigned n>
+//inline SBasisN<n> reverse(SBasisN<n> const &a);
+
+//IMPL: ScalableConcept
+template<unsigned n>
+inline SBasisN<n> operator-(const SBasisN<n>& p) {
+ if(p.isZero()) return SBasisN<n>();
+ SBasisN<n> result;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = p.sizes[i];
+ }
+ result.reserve(p.size());
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(-p[i]);
+ }
+ return result;
+}
+template<unsigned n>
+SBasisN<n> operator*(SBasisN<n> const &a, double c){
+ if(a.isZero()) return SBasisN<n>();
+ SBasisN<n> result;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = a.sizes[i];
+ }
+ result.reserve(a.size());
+ for(unsigned i = 0; i < a.size(); i++) {
+ result.push_back(a[i] * c);
+ }
+ return result;
+}
+template<unsigned n>
+inline SBasisN<n> operator*(double k, SBasisN<n> const &a) { return a*k; }
+template<unsigned n>
+inline SBasisN<n> operator/(SBasisN<n> const &a, double k) { return a*(1./k); }
+template<unsigned n>
+SBasisN<n>& operator*=(SBasisN<n>& a, double c){
+ for(unsigned i = 0; i < a.size(); i++) a[i] *= c;
+ return a;
+}
+template<unsigned n>
+inline SBasisN<n>& operator/=(SBasisN<n>& a, double b) { return (a*=(1./b)); }
+
+//IMPL: AddableConcept
+template<unsigned n>
+SBasisN<n> operator + (const SBasisN<n>& a, const SBasisN<n>& b){
+ if( a.isZero() ) return b;
+ if( b.isZero() ) return a;
+ SBasisN<n> result;
+ MultiDegree<n> deg = max(a.quick_multi_degree(),b.quick_multi_degree());
+ unsigned max_size = 1;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = deg[i]+1;
+ max_size *= result.sizes[i];
+ }
+ result.resize( max_size, LinearN<n>(0.) );
+ for(unsigned i = 0; i < result.size(); i++) {
+ MultiDegree<n> p(i,result.sizes);
+ unsigned ia = p.asIdx(a.sizes);
+ unsigned ib = p.asIdx(b.sizes);
+ if (ia<a.size()) {
+ result[i] += a[ia];
+ }
+ if (ib<b.size()) {
+ result[i] += b[ib];
+ }
+ }
+ return result;
+}
+template<unsigned n>
+SBasisN<n> operator-(const SBasisN<n>& a, const SBasisN<n>& b){return a+(-b);}
+template<unsigned n>
+SBasisN<n>& operator+=(SBasisN<n>& a, const SBasisN<n>& b){
+ if(b.isZero()) return a;
+ a = a + b;
+ return a;
+}
+template<unsigned n>
+SBasisN<n>& operator-=(SBasisN<n>& a, const SBasisN<n>& b){
+ a += -b;
+ return a;
+}
+
+//TODO: remove?
+template<unsigned n>
+inline SBasisN<n> operator+(const SBasisN<n> & a, LinearN<n> const & b) {
+ if(b.isZero()) return a;
+ if(a.isZero()) return b;
+ SBasisN<n> result(a);
+ result[0] += b;
+ return result;
+}
+template<unsigned n>
+
+inline SBasisN<n> operator-(const SBasisN<n> & a, LinearN<n> const & b) {
+ if(b.isZero()) return a;
+ if(a.isZero()) return -b;
+ SBasisN<n> result(a);
+ result[0] -= b;
+ return result;
+}
+template<unsigned n>
+inline SBasisN<n>& operator+=(SBasisN<n>& a, const LinearN<n>& b) {
+ if(a.size()==0)
+ a.push_back(b);
+ else
+ a[0] += b;
+ return a;
+}
+template<unsigned n>
+inline SBasisN<n>& operator-=(SBasisN<n>& a, const LinearN<n>& b) {
+ if(a.size()==0)
+ a.push_back(-b);
+ else
+ a[0] -= b;
+ return a;
+}
+
+//IMPL: OffsetableConcept
+template<unsigned n>
+inline SBasisN<n> operator+(const SBasisN<n> & a, double b) {
+ if(a.isZero()) return LinearN<n>(b);
+ SBasisN<n> result(a);
+ result[0] += b;
+ return result;
+}
+template<unsigned n>
+inline SBasisN<n> operator-(const SBasisN<n> & a, double b) {
+ if(a.isZero()) return LinearN<n>(-b);
+ SBasisN<n> result(a);
+ result[0] -= b;
+ return result;
+}
+template<unsigned n>
+inline SBasisN<n>& operator+=(SBasisN<n>& a, double b) {
+ if(a.size()==0)
+ a.push_back(LinearN<n>(b));
+ else
+ a[0] += b;
+ return a;
+}
+template<unsigned n>
+inline SBasisN<n>& operator-=(SBasisN<n>& a, double b) {
+ if(a.size()==0)
+ a.push_back(LinearN<n>(-b));
+ else
+ a[0] -= b;
+ return a;
+}
+
+template<unsigned n>
+SBasisN<n> shift(SBasisN<n> const &a, MultiDegree<n> sh){
+ SBasisN<n> result;
+ MultiDegree<n> deg = a.quick_multi_degree() + sh;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = deg[i]+1;
+ }
+ unsigned max_size = deg.asIdx(result.sizes);
+ result.resize( max_size, LinearN<n>(0.) );
+ for(unsigned i = 0; i < a.size(); i++) {
+ MultiDegree<n> p(i,a.sizes);
+ p+=sh;
+ result[p.asIdx(result.sizes)]=a[i];
+ }
+ return result;
+}
+template<unsigned n>
+SBasisN<n> shift(LinearN<n> const &a, MultiDegree<n> sh){
+ SBasisN<n> result;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = sh[i]+1;
+ }
+ unsigned max_size = sh.asIdx(result.sizes);
+ result.resize( max_size, LinearN<n>(0.) );
+ result[max_size-1]=a;
+ return result;
+}
+//shift only in one variable
+template<unsigned n>
+SBasisN<n> shift(LinearN<n> const &a, unsigned sh, unsigned var){
+ assert( var < n );
+ SBasisN<n> result;
+ for(unsigned i = 0; i < n; i++) {
+ result.sizes[i] = 1;
+ }
+ result.sizes[var] = sh+1;
+ result.resize( sh+1, LinearN<n>(0.) );
+ result[sh]=a;
+ return result;
+}
+
+//truncate only in first variable
+template<unsigned n>
+inline SBasisN<n> truncate(SBasisN<n> const &a, unsigned first_size) {
+ if ( first_size <= a.sizes[0] ) return a;
+ SBasisN<n> c;
+ for (unsigned i = 0; i < n; i++) {
+ c.sizes[i] = a.sizes[i];
+ }
+ c.sizes[0] = first_size;
+ unsigned tot_size = 1;
+ for(unsigned i = 0; i < n; i++) {
+ tot_size*=c.sizes[i];
+ }
+ c.insert(c.begin(), a.begin(), a.begin() + tot_size);
+ return c;
+}
+
+template<unsigned n>
+SBasisN<n> multiply(SBasisN<n> const &a, SBasisN<n> const &b){
+ SBasisN<n> c;
+ MultiDegree<n> d;
+ MultiDegree<n> t_deg = a.real_t_degrees() + b.real_t_degrees();
+ for(unsigned i = 0; i < n; i++) {
+ d[i] = ( t_deg[i]%2 == 0 ? t_deg[i]/2 : (t_deg[i]-1)/2 ) ;
+ }
+ unsigned new_sizes[n], tot_size = 1;
+ for(unsigned i = 0; i < n; i++) {
+ //c.sizes[i] = d[i] + 1+1;//product of linears might give 1 more s in each dir!!
+ new_sizes[i] = d[i] + 1;
+ tot_size*=new_sizes[i];
+ }
+ c.resize( tot_size, LinearN<n>(0.) );
+ for(unsigned i = 0; i < n; i++) {
+ c.sizes[i] = new_sizes[i];
+ }
+
+ for(unsigned ai = 0; ai < a.size(); ai++) {
+ for(unsigned bj = 0; bj < b.size(); bj++) {
+ MultiDegree<n> di( ai, a.sizes );
+ MultiDegree<n> dj( bj, b.sizes );
+ //compute a[ai]*b[bj]:
+ for(unsigned p = 0; p < (1<<n); p++) {
+ for(unsigned q = 0; q < (1<<n); q++) {
+
+ //compute a[ai][p]*b[bj][q]:
+ unsigned m = p^q;//m has ones for factors s, 0 for (t-s) or ((1-t)-s).
+ for(unsigned r = 0; r < (1<<n); r++) {
+ if (!(r&m)) {// a 1 in r means take t (or (1-t)), otherwise take -s.
+ int sign = 1;
+ MultiDegree<n> dr;
+ unsigned t0 = 0, t1 = 0;
+ for (unsigned var = 0; var < n; var++) {
+ //if var is in mask m, no choice, take s
+ if ( m & (1<<var) ){
+ dr.p[var] = 1;
+ }//if var is in mask r, take t or (1-t)
+ else if ( r & (1<<var) ){
+ dr.p[var] = 0;
+ if ( p&(1<<var) ) {
+ t0 = t0 | (1<<var);
+ }else{
+ t1 = t1 | (1<<var);
+ }
+ }//ohterwise take -s
+ else{
+ dr.p[var] = 1;
+ sign *= -1;
+ }
+ }
+ unsigned idx = (di+dj+dr).asIdx(c.sizes);
+ if (idx < c.size()){
+ for(unsigned s = 0; s < (1<<n); s++) {
+ if ( (t0 & ~s) || (t1 & s) ){
+ c[idx][s] += 0;
+ }else{
+ c[idx][s] += sign * a[ai][p] * b[bj][q];
+ }
+ }
+ }
+ }
+ }//r loop: all choices have been expanded in the product a[ai][p]*b[bj][q]
+ }//q loop
+ }//p loop: all products a[ai][p]*b[bj][q] have been computed.
+ }//bj loop
+ }//ai loop: all a[ai]b[bj] have been computed.
+
+ //TODO: normalize c, or even better, compute with the right size from scratch
+ return c;
+}
+
+
+template<unsigned n>
+inline SBasisN<n> operator*(SBasisN<n> const & a, SBasisN<n> const & b) {
+ return multiply(a, b);
+}
+
+template<unsigned n>
+inline SBasisN<n>& operator*=(SBasisN<n>& a, SBasisN<n> const & b) {
+ a = multiply(a, b);
+ return a;
+}
+
+template<unsigned m,unsigned n>
+SBasisN<m> compose(LinearN<n> const &f, std::vector<SBasisN<m> > const &t, unsigned fixed=0, unsigned flags=0 ){
+ assert (t.size() == n );
+ if (fixed == n) {
+ return SBasisN<m>(1.) * f[flags];
+ }else{
+ SBasisN<m> a0 = compose(f, t, fixed+1, flags);
+ SBasisN<m> a1 = compose(f, t, fixed+1, flags|(1<<fixed));
+ return (-t[fixed]+1) * a0 + t[fixed] * a1;
+ }
+}
+
+template<unsigned m,unsigned n>
+SBasisN<m> compose(SBasisN<n> const &f, std::vector<SBasisN<m> > const &t, unsigned const k=0, unsigned const idx = 0){
+ assert (t.size() == n );
+ if (k == n){
+ return compose( f[idx], t);
+ }
+ SBasisN<m> a;
+ SBasisN<m> s = multiply( t[k], (-t[k]+1.) );
+ SBasisN<m> si= SBasisN<m>(1.);
+ for (unsigned i=0; i<f.sizes[k]; i++){
+ a += compose(f, t,k+1,idx*f.sizes[k]+i)*si;;
+ si *= s;
+ }
+ return a;
+}
+
+template <unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const MultiDegree<n> & d) {
+ out_file << "s^{";
+ for(unsigned i = 0; i < n; i++) {
+ out_file << d[i] << (i == n-1 ? "}" : ",");
+ }
+ return out_file;
+}
+template <unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const SBasisN<n> & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ MultiDegree<n> d(i, p.sizes);
+ out_file << d << " " << p[i] << " + ";
+ }
+ return out_file;
+}
+
+
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+
+#if 0
+
+
+// This performs a multiply and accumulate operation in about the same time as multiply. return a*b + c
+template<unsigned n>
+SBasisN<n> multiply_add(SBasisN<n> const &a, SBasisN<n> const &b, SBasisN<n> c);
+
+template<unsigned n>
+SBasisN<n> integral(SBasisN<n> const &c);
+template<unsigned n>
+SBasisN<n> derivative(SBasisN<n> const &a);
+
+template<unsigned n>
+SBasisN<n> sqrt(SBasisN<n> const &a, int k);
+
+// return a kth order approx to 1/a)
+template<unsigned n>
+SBasisN<n> reciprocal(LinearN<n> const &a, int k);
+template<unsigned n>
+SBasisN<n> divide(SBasisN<n> const &a, SBasisN<n> const &b, int k);
+
+
+/** Returns the degree of the first non zero coefficient.
+ \param a sbasis function
+ \param tol largest abs val considered 0
+ \returns first non zero coefficient
+*/
+template<unsigned n>
+inline unsigned
+valuation(SBasisN<n> const &a, double tol=0){
+ unsigned val=0;
+ while( val<a.size() &&
+ fabs(a[val][0])<tol &&
+ fabs(a[val][1])<tol )
+ val++;
+ return val;
+}
+
+// a(b(t))
+template<unsigned n>
+SBasisN<n> compose(SBasisN<n> const &a, SBasisN<n> const &b);
+template<unsigned n>
+SBasisN<n> compose(SBasisN<n> const &a, SBasisN<n> const &b, unsigned k);
+template<unsigned n>
+SBasisN<n> inverse(SBasisN<n> a, int k);
+//compose_inverse(f,g)=compose(f,inverse(g)), but is numerically more stable in some good cases...
+//TODO: requires g(0)=0 & g(1)=1 atm. generalization should be obvious.
+template<unsigned n>
+SBasisN<n> compose_inverse(SBasisN<n> const &f, SBasisN<n> const &g, unsigned order=2, double tol=1e-3);
+
+/** Returns the sbasis on domain [0,1] that was t on [from, to]
+ \param a sbasis function
+ \param from,to interval
+ \returns sbasis
+
+*/
+template<unsigned n>
+inline SBasisN<n> portion(const SBasisN<n> &t, double from, double to) { return compose(t, LinearN<n>(from, to)); }
+
+// compute f(g)
+template<unsigned n>
+inline SBasisN<n>
+SBasisN<n>::operator()(SBasisN<n> const & g) const {
+ return compose(*this, g);
+}
+
+template<unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const LinearN<n> &bo) {
+ out_file << "{" << bo[0] << ", " << bo[1] << "}";
+ return out_file;
+}
+
+template<unsigned n>
+inline std::ostream &operator<< (std::ostream &out_file, const SBasisN<n> & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ out_file << p[i] << "s^" << i << " + ";
+ }
+ return out_file;
+}
+
+// These are deprecated, use sbasis-math.h versions if possible
+template<unsigned n>
+SBasisN<n> sin(LinearN<n> bo, int k);
+template<unsigned n>
+SBasisN<n> cos(LinearN<n> bo, int k);
+
+template<unsigned n>
+std::vector<double> roots(SBasisN<n> const & s);
+template<unsigned n>
+std::vector<std::vector<double> > multi_roots(SBasisN<n> const &f,
+ std::vector<double> const &levels,
+ double htol=1e-7,
+ double vtol=1e-7,
+ double a=0,
+ double b=1);
+
+#endif
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+//--------------------------------------------------
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+#endif
diff --git a/include/2geom/parallelogram.h b/include/2geom/parallelogram.h new file mode 100644 index 0000000..0c7134f --- /dev/null +++ b/include/2geom/parallelogram.h @@ -0,0 +1,83 @@ +/* + * Authors: + * Thomas Holder + * Sergei Izmailov + * + * Copyright 2020 Authors + * + * SPDX-License-Identifier: LGPL-2.1 or MPL-1.1 + */ + +#ifndef LIB2GEOM_SEEN_PARALLELOGRAM_H +#define LIB2GEOM_SEEN_PARALLELOGRAM_H + +#include <2geom/affine.h> +#include <2geom/rect.h> + +namespace Geom { + +/** + * Paralellogram, representing a linear transformation of a rectangle. + * + * Implements efficient "contains" and "intersects" operations. + */ +class Parallelogram { + Affine m_affine; + + /// Transformed unit rectangle + Parallelogram(Affine const &affine) + : m_affine(affine) + { + } + + public: + explicit Parallelogram(Rect const &rect) + : m_affine(rect.width(), 0, 0, rect.height(), rect.left(), rect.top()) + { + } + + Point corner(unsigned i) const; + + Point midpoint() const { return Point(0.5, 0.5) * m_affine; } + + /// Area (non-negative) + Coord area() const { return m_affine.descrim2(); } + + /// Axis-aligned bounding box + Rect bounds() const; + + bool intersects(Parallelogram const &) const; + bool intersects(Rect const &rect) const { return intersects(Parallelogram(rect)); } + + bool contains(Point const &) const; + bool contains(Parallelogram const &) const; + bool contains(Rect const &rect) const { return contains(Parallelogram(rect)); } + + /// Returns shorter side length + Coord minExtent() const; + + /// Returns longer side length + Coord maxExtent() const; + + /// Return a new transformed parallelogram + Parallelogram operator*(Affine const &affine) const { return m_affine * affine; } + Parallelogram &operator*=(Affine const &affine) { m_affine *= affine; return *this; } + + /// True if this parallelogram does not have right angles + bool isSheared(Coord eps = EPSILON) const; +}; + +} // namespace Geom + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/path-intersection.h b/include/2geom/path-intersection.h new file mode 100644 index 0000000..7d7cec9 --- /dev/null +++ b/include/2geom/path-intersection.h @@ -0,0 +1,118 @@ +/** + * \file + * \brief Path intersection + *//* + * Authors: + * ? <?@?.?> + * + * Copyright ?-? authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_PATH_INTERSECTION_H +#define LIB2GEOM_SEEN_PATH_INTERSECTION_H + +#include <2geom/crossing.h> +#include <2geom/path.h> +#include <2geom/sweep-bounds.h> + +namespace Geom { + +int winding(Path const &path, Point const &p); +bool path_direction(Path const &p); + +inline bool contains(Path const & p, Point const &i, bool evenodd = true) { + return (evenodd ? winding(p, i) % 2 : winding(p, i)) != 0; +} + +template<typename T> +Crossings curve_sweep(Path const &a, Path const &b) { + T t; + Crossings ret; + std::vector<Rect> bounds_a = bounds(a), bounds_b = bounds(b); + std::vector<std::vector<unsigned> > ixs = sweep_bounds(bounds_a, bounds_b); + for(unsigned i = 0; i < a.size(); i++) { + for(std::vector<unsigned>::iterator jp = ixs[i].begin(); jp != ixs[i].end(); ++jp) { + Crossings cc = t.crossings(a[i], b[*jp]); + offset_crossings(cc, i, *jp); + ret.insert(ret.end(), cc.begin(), cc.end()); + } + } + return ret; +} + +Crossings pair_intersect(Curve const & A, Interval const &Ad, + Curve const & B, Interval const &Bd); +Crossings mono_intersect(Curve const & A, Interval const &Ad, + Curve const & B, Interval const &Bd); + +struct SimpleCrosser : public Crosser<Path> { + Crossings crossings(Curve const &a, Curve const &b); + Crossings crossings(Path const &a, Path const &b) override { return curve_sweep<SimpleCrosser>(a, b); } + CrossingSet crossings(PathVector const &a, PathVector const &b) override { return Crosser<Path>::crossings(a, b); } +}; + +struct MonoCrosser : public Crosser<Path> { + Crossings crossings(Path const &a, Path const &b) override { return crossings(PathVector(a), PathVector(b))[0]; } + CrossingSet crossings(PathVector const &a, PathVector const &b) override; +}; + +typedef SimpleCrosser DefaultCrosser; + +std::vector<double> path_mono_splits(Path const &p); + +CrossingSet crossings_among(PathVector const & p); +Crossings self_crossings(Path const & a); + +inline Crossings crossings(Curve const & a, Curve const & b) { + DefaultCrosser c = DefaultCrosser(); + return c.crossings(a, b); +} + +inline Crossings crossings(Path const & a, Path const & b) { + DefaultCrosser c = DefaultCrosser(); + return c.crossings(a, b); +} + +inline CrossingSet crossings(PathVector const & a, PathVector const & b) { + DefaultCrosser c = DefaultCrosser(); + return c.crossings(a, b); +} + +} + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/path-sink.h b/include/2geom/path-sink.h new file mode 100644 index 0000000..35bd1cd --- /dev/null +++ b/include/2geom/path-sink.h @@ -0,0 +1,253 @@ +/** + * \file + * \brief callback interface for SVG path data + *//* + * Copyright 2007 MenTaLguY <mental@rydia.net> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_PATH_SINK_H +#define LIB2GEOM_SEEN_PATH_SINK_H + +#include <2geom/forward.h> +#include <2geom/pathvector.h> +#include <2geom/curves.h> +#include <iterator> + +namespace Geom { + + +/** @brief Callback interface for processing path data. + * + * PathSink provides an interface that allows one to easily write + * code which processes path data, for instance when converting + * between path formats used by different graphics libraries. + * It is also useful for writing algorithms which must do something + * for each curve in the path. + * + * To store a path in a new format, implement the virtual methods + * for segments in a derived class and call feed(). + * + * @ingroup Paths + */ +class PathSink { +public: + /** @brief Move to a different point without creating a segment. + * Usually starts a new subpath. */ + virtual void moveTo(Point const &p) = 0; + /// Output a line segment. + virtual void lineTo(Point const &p) = 0; + /// Output a quadratic Bezier segment. + virtual void curveTo(Point const &c0, Point const &c1, Point const &p) = 0; + /// Output a cubic Bezier segment. + virtual void quadTo(Point const &c, Point const &p) = 0; + /** @brief Output an elliptical arc segment. + * See the EllipticalArc class for the documentation of parameters. */ + virtual void arcTo(Coord rx, Coord ry, Coord angle, + bool large_arc, bool sweep, Point const &p) = 0; + + /// Close the current path with a line segment. + virtual void closePath() = 0; + /** @brief Flush any internal state of the generator. + * This call should implicitly finish the current subpath. + * Calling this method should be idempotent, because the default + * implementations of path() and pathvector() will call it + * multiple times in a row. */ + virtual void flush() = 0; + // Get the current point, e.g. where the initial point of the next segment will be. + //virtual Point currentPoint() const = 0; + + /** @brief Undo the last segment. + * This method is optional. + * @return true true if a segment was erased, false otherwise. */ + virtual bool backspace() { return false; } + + // these have a default implementation + virtual void feed(Curve const &c, bool moveto_initial = true); + /** @brief Output a subpath. + * Calls the appropriate segment methods according to the contents + * of the passed subpath. You can override this function. + * NOTE: if you override only some of the feed() functions, + * always write this in the derived class: + * @code + using PathSink::feed; + @endcode + * Otherwise the remaining methods will be hidden. */ + virtual void feed(Path const &p); + /** @brief Output a path. + * Calls feed() on each path in the vector. You can override this function. */ + virtual void feed(PathVector const &v); + /// Output an axis-aligned rectangle, using moveTo, lineTo and closePath. + virtual void feed(Rect const &); + /// Output a circle as two elliptical arcs. + virtual void feed(Circle const &e); + /// Output an ellipse as two elliptical arcs. + virtual void feed(Ellipse const &e); + + virtual ~PathSink() {} +}; + +/** @brief Store paths to an output iterator + * @ingroup Paths */ +template <typename OutputIterator> +class PathIteratorSink : public PathSink { +public: + explicit PathIteratorSink(OutputIterator out) + : _in_path(false), _out(out) {} + + void moveTo(Point const &p) override { + flush(); + _path.start(p); + _start_p = p; + _in_path = true; + } +//TODO: what if _in_path = false? + + /** @brief Detect if the builder is in a path and thus will NOT + create a new moveTo command when given the next line + @return true if the builder is inside a subpath. + */ + bool inPath() const { + return _in_path; + } + + void lineTo(Point const &p) override { + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } + _path.template appendNew<LineSegment>(p); + } + + void quadTo(Point const &c, Point const &p) override { + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } + _path.template appendNew<QuadraticBezier>(c, p); + } + + void curveTo(Point const &c0, Point const &c1, Point const &p) override { + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } + _path.template appendNew<CubicBezier>(c0, c1, p); + } + + void arcTo(Coord rx, Coord ry, Coord angle, + bool large_arc, bool sweep, Point const &p) override + { + // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" + if (!_in_path) { + moveTo(_start_p); + } + _path.template appendNew<EllipticalArc>(rx, ry, angle, + large_arc, sweep, p); + } + + bool backspace() override + { + if (_in_path && _path.size() > 0) { + _path.erase_last(); + return true; + } + return false; + } + + void append(Path const &other) + { + if (!_in_path) { + moveTo(other.initialPoint()); + } + _path.append(other); + } + + void closePath() override { + if (_in_path) { + _path.close(); + flush(); + } + } + + void flush() override { + if (_in_path) { + _in_path = false; + *_out++ = _path; + _path.clear(); + } + } + + void setStitching(bool s) { + _path.setStitching(s); + } + + using PathSink::feed; + void feed(Path const &other) override + { + flush(); + *_out++ = other; + } + +protected: + bool _in_path; + OutputIterator _out; + Path _path; + Point _start_p; +}; + +typedef std::back_insert_iterator<PathVector> SubpathInserter; + +/** @brief Store paths to a PathVector + * @ingroup Paths */ +class PathBuilder : public PathIteratorSink<SubpathInserter> { +private: + PathVector _pathset; +public: + /// Create a builder that outputs to an internal pathvector. + PathBuilder() : PathIteratorSink<SubpathInserter>(SubpathInserter(_pathset)) {} + /// Create a builder that outputs to pathvector given by reference. + PathBuilder(PathVector &pv) : PathIteratorSink<SubpathInserter>(SubpathInserter(pv)) {} + + /// Retrieve the path + PathVector const &peek() const {return _pathset;} + /// Clear the stored path vector + void clear() { _pathset.clear(); } +}; + +} + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/path.h b/include/2geom/path.h new file mode 100644 index 0000000..f3042d7 --- /dev/null +++ b/include/2geom/path.h @@ -0,0 +1,917 @@ +/** @file + * @brief Path - a sequence of contiguous curves + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2014 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_PATH_H +#define LIB2GEOM_SEEN_PATH_H + +#include <cstddef> +#include <iterator> +#include <algorithm> +#include <iostream> +#include <memory> +#include <optional> +#include <utility> +#include <vector> + +#include <boost/operators.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include <2geom/intersection.h> +#include <2geom/curve.h> +#include <2geom/bezier-curve.h> +#include <2geom/transforms.h> + +namespace Geom { + +class Path; +class ConvexHull; + +namespace PathInternal { + +typedef boost::ptr_vector<Curve> Sequence; + +struct PathData { + Sequence curves; + OptRect fast_bounds; +}; + +template <typename P> +class BaseIterator + : public boost::random_access_iterator_helper + < BaseIterator<P> + , Curve const + , std::ptrdiff_t + , Curve const * + , Curve const & + > +{ + protected: + BaseIterator(P &p, unsigned i) : path(&p), index(i) {} + // default copy, default assign + typedef BaseIterator<P> Self; + + public: + BaseIterator() : path(NULL), index(0) {} + + bool operator<(BaseIterator const &other) const { + return path == other.path && index < other.index; + } + bool operator==(BaseIterator const &other) const { + return path == other.path && index == other.index; + } + Curve const &operator*() const { + return (*path)[index]; + } + + Self &operator++() { + ++index; + return *this; + } + Self &operator--() { + --index; + return *this; + } + Self &operator+=(std::ptrdiff_t d) { + index += d; + return *this; + } + Self &operator-=(std::ptrdiff_t d) { + index -= d; + return *this; + } + std::ptrdiff_t operator-(Self const &other) const { + assert(path == other.path); + return (std::ptrdiff_t)index - (std::ptrdiff_t)other.index; + } + + private: + P *path; + unsigned index; + + friend class ::Geom::Path; +}; + +} + +/** @brief Generalized time value in the path. + * + * This class exists because when mapping the range of multiple curves onto the same interval + * as the curve index, we lose some precision. For instance, a path with 16 curves will + * have 4 bits less precision than a path with 1 curve. If you need high precision results + * in long paths, either use this class and related methods instead of the standard methods + * pointAt(), nearestTime() and so on, or use curveAt() to first obtain the curve, then + * call the method again to obtain a high precision result. + * + * @ingroup Paths */ +struct PathTime + : boost::totally_ordered<PathTime> +{ + typedef PathInternal::Sequence::size_type size_type; + + Coord t; ///< Time value in the curve + size_type curve_index; ///< Index of the curve in the path + + PathTime() : t(0), curve_index(0) {} + PathTime(size_type idx, Coord tval) : t(tval), curve_index(idx) {} + + bool operator<(PathTime const &other) const { + if (curve_index < other.curve_index) return true; + if (curve_index == other.curve_index) { + return t < other.t; + } + return false; + } + bool operator==(PathTime const &other) const { + return curve_index == other.curve_index && t == other.t; + } + /// Convert times at or beyond 1 to 0 on the next curve. + void normalizeForward(size_type path_size) { + if (t >= 1) { + curve_index = (curve_index + 1) % path_size; + t = 0; + } + } + /// Convert times at or before 0 to 1 on the previous curve. + void normalizeBackward(size_type path_size) { + if (t <= 0) { + curve_index = (curve_index - 1) % path_size; + t = 1; + } + } + + Coord asFlatTime() const { return curve_index + t; } +}; + +inline std::ostream &operator<<(std::ostream &os, PathTime const &pos) { + os << pos.curve_index << ": " << format_coord_nice(pos.t); + return os; +} + + +/** @brief Contiguous subset of the path's parameter domain. + * This is a directed interval, which allows one to specify any contiguous subset + * of the path's domain, including subsets that wrap around the initial point + * of the path. + * @ingroup Paths */ +class PathInterval { +public: + typedef PathInternal::Sequence::size_type size_type; + + /** @brief Default interval. + * Default-constructed PathInterval includes only the initial point of the initial segment. */ + PathInterval(); + + /** @brief Construct an interval in the path's parameter domain. + * @param from Initial time + * @param to Final time + * @param cross_start If true, the interval will proceed from the initial to final + * time through the initial point of the path, wrapping around the closing segment; + * otherwise it will not wrap around the closing segment. + * @param path_size Size of the path to which this interval applies, required + * to clean up degenerate cases */ + PathInterval(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size); + + /// Get the time value of the initial point. + PathTime const &initialTime() const { return _from; } + /// Get the time value of the final point. + PathTime const &finalTime() const { return _to; } + + PathTime const &from() const { return _from; } + PathTime const &to() const { return _to; } + + /// Check whether the interval has only one point. + bool isDegenerate() const { return _from == _to; } + /// True if the interval goes in the direction of decreasing time values. + bool reverse() const { return _reverse; } + /// True if the interior of the interval contains the initial point of the path. + bool crossesStart() const { return _cross_start; } + + /// Test a path time for inclusion. + bool contains(PathTime const &pos) const; + + /// Get a time at least @a min_dist away in parameter space from the ends. + /// If no such time exists, the middle point is returned. + PathTime inside(Coord min_dist = EPSILON) const; + + /// Select one of two intervals with given endpoints by parameter direction. + static PathInterval from_direction(PathTime const &from, PathTime const &to, + bool reversed, size_type path_size); + + /// Select one of two intervals with given endpoints by whether it includes the initial point. + static PathInterval from_start_crossing(PathTime const &from, PathTime const &to, + bool cross_start, size_type path_size) { + PathInterval result(from, to, cross_start, path_size); + return result; + } + + size_type pathSize() const { return _path_size; } + size_type curveCount() const; + +private: + PathTime _from, _to; + size_type _path_size; + bool _cross_start, _reverse; +}; + +/// Create an interval in the direction of increasing time value. +/// @relates PathInterval +inline PathInterval forward_interval(PathTime const &from, PathTime const &to, + PathInterval::size_type path_size) +{ + PathInterval result = PathInterval::from_direction(from, to, false, path_size); + return result; +} + +/// Create an interval in the direction of decreasing time value. +/// @relates PathInterval +inline PathInterval backward_interval(PathTime const &from, PathTime const &to, + PathInterval::size_type path_size) +{ + PathInterval result = PathInterval::from_direction(from, to, true, path_size); + return result; +} + +/// Output an interval in the path's domain. +/// @relates PathInterval +inline std::ostream &operator<<(std::ostream &os, PathInterval const &ival) { + os << "PathInterval["; + if (ival.crossesStart()) { + os << ival.from() << " -> 0: 0.0 -> " << ival.to(); + } else { + os << ival.from() << " -> " << ival.to(); + } + os << "]"; + return os; +} + +typedef Intersection<PathTime> PathIntersection; + +template <> +struct ShapeTraits<Path> { + typedef PathTime TimeType; + typedef PathInterval IntervalType; + typedef Path AffineClosureType; + typedef PathIntersection IntersectionType; +}; + +/** @brief Stores information about the extremum points on a path, with respect + * to one of the coordinate axes. + * @relates Path::extrema() + */ +struct PathExtrema { + /** Points with the minimum and maximum value of a coordinate. */ + Point min_point, max_point; + + /** Directions in which the OTHER coordinates change at the extremum points. + * + * - equals +1.0 if the other coordinate increases, + * - equals 0.0 if the other coordinate is constant (e.g., for an axis-aligned segment), + * - equals -1.0 if the other coordinate decreases. + */ + float glance_direction_at_min, glance_direction_at_max; + + /** Path times corresponding to minimum and maximum points. */ + PathTime min_time, max_time; +}; + +/** @brief Sequence of contiguous curves, aka spline. + * + * Path represents a sequence of contiguous curves, also known as a spline. + * It corresponds to a "subpath" in SVG terminology. It can represent both + * open and closed subpaths. The final point of each curve is exactly + * equal to the initial point of the next curve. + * + * The path always contains a linear closing segment that connects + * the final point of the last "real" curve to the initial point of the + * first curve. This way the curves form a closed loop even for open paths. + * If the closing segment has nonzero length and the path is closed, it is + * considered a normal part of the path data. There are three distinct sets + * of end iterators one can use to iterate over the segments: + * + * - Iterating between @a begin() and @a end() will iterate over segments + * which are part of the path. + * - Iterating between @a begin() and @a end_closed() + * will always iterate over a closed loop of segments. + * - Iterating between @a begin() and @a end_open() will always skip + * the final linear closing segment. + * + * If the final point of the last "real" segment coincides exactly with the initial + * point of the first segment, the closing segment will be absent from both + * [begin(), end_open()) and [begin(), end_closed()). + * + * Normally, an exception will be thrown when you try to insert a curve + * that makes the path non-continuous. If you are working with unsanitized + * curve data, you can call setStitching(true), which will insert line segments + * to make the path continuous. + * + * Internally, Path uses copy-on-write data. This is done for two reasons: first, + * copying a Curve requires calling a virtual function, so it's a little more expensive + * that normal copying; and second, it reduces the memory cost of copying the path. + * Therefore you can return Path and PathVector from functions without worrying + * about temporary copies. + * + * Note that this class cannot represent arbitrary shapes, which may contain holes. + * To do that, use PathVector, which is more generic. + * + * It's not very convenient to create a Path directly. To construct paths more easily, + * use PathBuilder. + * + * @ingroup Paths */ +class Path + : boost::equality_comparable< Path > +{ +public: + typedef PathInternal::PathData PathData; + typedef PathInternal::Sequence Sequence; + typedef PathInternal::BaseIterator<Path> iterator; + typedef PathInternal::BaseIterator<Path const> const_iterator; + typedef Sequence::size_type size_type; + typedef Sequence::difference_type difference_type; + + class ClosingSegment : public LineSegment { + public: + ClosingSegment() : LineSegment() {} + ClosingSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} + Curve *duplicate() const override { return new ClosingSegment(*this); } + Curve *reverse() const override { return new ClosingSegment((*this)[1], (*this)[0]); } + }; + + class StitchSegment : public LineSegment { + public: + StitchSegment() : LineSegment() {} + StitchSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} + Curve *duplicate() const override { return new StitchSegment(*this); } + Curve *reverse() const override { return new StitchSegment((*this)[1], (*this)[0]); } + }; + + // Path(Path const &other) - use default copy constructor + + /// Construct an empty path starting at the specified point. + explicit Path(Point const &p = Point()) + : _data(new PathData()) + , _closing_seg(new ClosingSegment(p, p)) + , _closed(false) + , _exception_on_stitch(true) + { + _data->curves.push_back(_closing_seg); + } + + /// Construct a path containing a range of curves. + template <typename Iter> + Path(Iter first, Iter last, bool closed = false, bool stitch = false) + : _data(new PathData()) + , _closed(closed) + , _exception_on_stitch(!stitch) + { + for (Iter i = first; i != last; ++i) { + _data->curves.push_back(i->duplicate()); + } + if (!_data->curves.empty()) { + _closing_seg = new ClosingSegment(_data->curves.back().finalPoint(), + _data->curves.front().initialPoint()); + } else { + _closing_seg = new ClosingSegment(); + } + _data->curves.push_back(_closing_seg); + } + + /// Create a path from a rectangle. + explicit Path(Rect const &r); + /// Create a path from a convex hull. + explicit Path(ConvexHull const &); + /// Create a path from a circle, using two elliptical arcs. + explicit Path(Circle const &c); + /// Create a path from an ellipse, using two elliptical arcs. + explicit Path(Ellipse const &e); + + virtual ~Path() {} + + // Path &operator=(Path const &other) - use default assignment operator + + /** @brief Swap contents with another path + * @todo Add noexcept specifiers for C++11 */ + void swap(Path &other) noexcept { + using std::swap; + swap(other._data, _data); + swap(other._closing_seg, _closing_seg); + swap(other._closed, _closed); + swap(other._exception_on_stitch, _exception_on_stitch); + } + /** @brief Swap contents of two paths. + * @relates Path */ + friend inline void swap(Path &a, Path &b) noexcept { a.swap(b); } + + /** @brief Access a curve by index */ + Curve const &operator[](size_type i) const { return _data->curves[i]; } + /** @brief Access a curve by index */ + Curve const &at(size_type i) const { return _data->curves.at(i); } + + /** @brief Access the first curve in the path. + * Since the curve always contains at least a degenerate closing segment, + * it is always safe to use this method. */ + Curve const &front() const { return _data->curves.front(); } + /// Alias for front(). + Curve const &initialCurve() const { return _data->curves.front(); } + /** @brief Access the last curve in the path. */ + Curve const &back() const { return back_default(); } + Curve const &back_open() const { + if (empty()) return _data->curves.back(); + return _data->curves[_data->curves.size() - 2]; + } + Curve const &back_closed() const { + return _closing_seg->isDegenerate() + ? _data->curves[_data->curves.size() - 2] + : _data->curves[_data->curves.size() - 1]; + } + Curve const &back_default() const { + return _includesClosingSegment() + ? back_closed() + : back_open(); + } + Curve const &finalCurve() const { return back_default(); } + + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator end() const { return end_default(); } + const_iterator end_default() const { return const_iterator(*this, size_default()); } + const_iterator end_open() const { return const_iterator(*this, size_open()); } + const_iterator end_closed() const { return const_iterator(*this, size_closed()); } + iterator begin() { return iterator(*this, 0); } + iterator end() { return end_default(); } + iterator end_default() { return iterator(*this, size_default()); } + iterator end_open() { return iterator(*this, size_open()); } + iterator end_closed() { return iterator(*this, size_closed()); } + + /// Size without the closing segment, even if the path is closed. + size_type size_open() const { return _data->curves.size() - 1; } + + /** @brief Size with the closing segment, if it makes a difference. + * If the closing segment is degenerate, i.e. its initial and final points + * are exactly equal, then it is not included in this size. */ + size_type size_closed() const { + return _closing_seg->isDegenerate() ? _data->curves.size() - 1 : _data->curves.size(); + } + + /// Natural size of the path. + size_type size_default() const { + return _includesClosingSegment() ? size_closed() : size_open(); + } + /// Natural size of the path. + size_type size() const { return size_default(); } + + size_type max_size() const { return _data->curves.max_size() - 1; } + + /** @brief Check whether path is empty. + * The path is empty if it contains only the closing segment, which according + * to the continuity invariant must be degenerate. Note that unlike standard + * containers, two empty paths are not necessarily identical, because the + * degenerate closing segment may be at a different point, affecting the operation + * of methods such as appendNew(). */ + bool empty() const { return (_data->curves.size() == 1); } + + /// Check whether the path is closed. + bool closed() const { return _closed; } + + /** @brief Set whether the path is closed. + * When closing a path where the last segment can be represented as a closing + * segment, the last segment will be removed. When opening a path, the closing + * segment will be erased. This means that closing and then opening a path + * will not always give back the original path. */ + void close(bool closed = true); + + /** @brief Remove all curves from the path. + * The initial and final points of the closing segment are set to (0,0). + * The stitching flag remains unchanged. */ + void clear(); + + /** @brief Get the approximate bounding box. + * The rectangle returned by this method will contain all the curves, but it's not + * guaranteed to be the smallest possible one */ + OptRect boundsFast() const; + + /** @brief Get a tight-fitting bounding box. + * This will return the smallest possible axis-aligned rectangle containing + * all the curves in the path. */ + OptRect boundsExact() const; + + Piecewise<D2<SBasis> > toPwSb() const; + + /// Test paths for exact equality. + bool operator==(Path const &other) const; + + /// Apply a transform to each curve. + template <typename T> + Path &operator*=(T const &tr) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + _unshare(); + for (std::size_t i = 0; i < _data->curves.size(); ++i) { + _data->curves[i] *= tr; + } + return *this; + } + + template <typename T> + friend Path operator*(Path const &path, T const &tr) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + Path result(path); + result *= tr; + return result; + } + + /** @brief Get the allowed range of time values. + * @return Values for which pointAt() and valueAt() yield valid results. */ + Interval timeRange() const; + + /** Get the curve at the specified time value. + * @param t Time value + * @param rest Optional storage for the corresponding time value in the curve */ + Curve const &curveAt(Coord t, Coord *rest = NULL) const; + + /// Get the closing segment of the path. + LineSegment const &closingSegment() const { return *_closing_seg; } + + /** @brief Get the point at the specified time value. + * Note that this method has reduced precision with respect to calling pointAt() + * directly on the curve. If you want high precision results, use the version + * that takes a PathTime parameter. + * + * Allowed time values range from zero to the number of curves; you can retrieve + * the allowed range of values with timeRange(). */ + Point pointAt(Coord t) const; + + /// Get one coordinate (X or Y) at the specified time value. + Coord valueAt(Coord t, Dim2 d) const; + + /// Get the curve at the specified path time. + Curve const &curveAt(PathTime const &pos) const; + /// Get the point at the specified path time. + Point pointAt(PathTime const &pos) const; + /// Get one coordinate at the specified path time. + Coord valueAt(PathTime const &pos, Dim2 d) const; + + Point operator()(Coord t) const { return pointAt(t); } + + /** @brief Find the extrema of the specified coordinate. + * + * Returns a PathExtrema struct describing "witness" points on the path + * where the specified coordinate attains its minimum and maximum values. + */ + PathExtrema extrema(Dim2 d) const; + + /// Compute intersections with axis-aligned line. + std::vector<PathTime> roots(Coord v, Dim2 d) const; + + /// Compute intersections with another path. + std::vector<PathIntersection> intersect(Path const &other, Coord precision = EPSILON) const; + + /// Compute intersections of the path with itself. + std::vector<PathIntersection> intersectSelf(Coord precision = EPSILON) const; + + /** @brief Determine the winding number at the specified point. + * + * The winding number is the number of full turns made by a ray that connects the passed + * point and the path's value (i.e. the result of the pointAt() method) as the time increases + * from 0 to the maximum valid value. Positive numbers indicate turns in the direction + * of increasing angles. + * + * Winding numbers are often used as the definition of what is considered "inside" + * the shape. Typically points with either nonzero winding or odd winding are + * considered to be inside the path. */ + int winding(Point const &p) const; + + std::vector<Coord> allNearestTimes(Point const &p, Coord from, Coord to) const; + std::vector<Coord> allNearestTimes(Point const &p) const { + return allNearestTimes(p, 0, size_default()); + } + + PathTime nearestTime(Point const &p, Coord *dist = NULL) const; + std::vector<Coord> nearestTimePerCurve(Point const &p) const; + + std::vector<Point> nodes() const; + + void appendPortionTo(Path &p, Coord f, Coord t) const; + + /** @brief Append a subset of this path to another path. + * An extra stitching segment will be inserted if the start point of the portion + * and the final point of the target path do not match exactly. + * The closing segment of the target path will be modified. */ + void appendPortionTo(Path &p, PathTime const &from, PathTime const &to, bool cross_start = false) const { + PathInterval ival(from, to, cross_start, size_closed()); + appendPortionTo(p, ival, std::nullopt, std::nullopt); + } + + /** @brief Append a subset of this path to another path. + * This version allows you to explicitly pass a PathInterval. */ + void appendPortionTo(Path &p, PathInterval const &ival) const { + appendPortionTo(p, ival, std::nullopt, std::nullopt); + } + + /** @brief Append a subset of this path to another path, specifying endpoints. + * This method is for use in situations where endpoints of the portion segments + * have to be set exactly, for instance when computing Boolean operations. */ + void appendPortionTo(Path &p, PathInterval const &ival, + std::optional<Point> const &p_from, std::optional<Point> const &p_to) const; + + Path portion(Coord f, Coord t) const { + Path ret; + ret.close(false); + appendPortionTo(ret, f, t); + return ret; + } + + Path portion(Interval const &i) const { return portion(i.min(), i.max()); } + + /** @brief Get a subset of the current path with full precision. + * When @a from is larger (later in the path) than @a to, the returned portion + * will be reversed. If @a cross_start is true, the portion will be reversed + * and will cross the initial point of the path. Therefore, when @a from is larger + * than @a to and @a cross_start is true, the returned portion will not be reversed, + * but will "wrap around" the end of the path. */ + Path portion(PathTime const &from, PathTime const &to, bool cross_start = false) const { + Path ret; + ret.close(false); + appendPortionTo(ret, from, to, cross_start); + return ret; + } + + /** @brief Get a subset of the current path with full precision. + * This version allows you to explicitly pass a PathInterval. */ + Path portion(PathInterval const &ival) const { + Path ret; + ret.close(false); + appendPortionTo(ret, ival); + return ret; + } + + /** @brief Obtain a reversed version of the current path. + * The final point of the current path will become the initial point + * of the reversed path, unless it is closed and has a non-degenerate + * closing segment. In that case, the new initial point will be the final point + * of the last "real" segment. */ + Path reversed() const; + + void insert(iterator pos, Curve const &curve); + + template <typename Iter> + void insert(iterator pos, Iter first, Iter last) { + _unshare(); + Sequence::iterator seq_pos(seq_iter(pos)); + Sequence source; + for (; first != last; ++first) { + source.push_back(first->duplicate()); + } + do_update(seq_pos, seq_pos, source); + } + + void erase(iterator pos); + void erase(iterator first, iterator last); + + // erase last segment of path + void erase_last() { erase(iterator(*this, size() - 1)); } + + void start(Point const &p); + + /** @brief Get the first point in the path. */ + Point initialPoint() const { return (*_closing_seg)[1]; } + + /** @brief Get the last point in the path. + * If the path is closed, this is always the same as the initial point. */ + Point finalPoint() const { return (*_closing_seg)[_closed ? 1 : 0]; } + + /** @brief Get the unit tangent vector at the start of the path, + * or the zero vector if undefined. */ + Point initialUnitTangent() const { + for (auto const &curve : *this) { + if (!curve.isDegenerate()) { + return curve.unitTangentAt(0.0); + } + } + return Point(); + } + + /** @brief Get the unit tangent vector at the end of the path, + * or the zero vector if undefined. */ + Point finalUnitTangent() const { + for (auto it = end(); it != begin();) { + --it; + if (!it->isDegenerate()) { + return it->unitTangentAt(1.0); + } + } + return Point(); + } + + void setInitial(Point const &p) { + _unshare(); + _closed = false; + _data->curves.front().setInitial(p); + _closing_seg->setFinal(p); + } + void setFinal(Point const &p) { + _unshare(); + _closed = false; + _data->curves[size_open() - 1].setFinal(p); + _closing_seg->setInitial(p); + } + + /** @brief Add a new curve to the end of the path. + * This inserts the new curve right before the closing segment. + * The path takes ownership of the passed pointer, which should not be freed. */ + void append(Curve *curve) { + _unshare(); + stitchTo(curve->initialPoint()); + do_append(curve); + } + + void append(Curve const &curve) { + _unshare(); + stitchTo(curve.initialPoint()); + do_append(curve.duplicate()); + } + void append(D2<SBasis> const &curve) { + _unshare(); + stitchTo(Point(curve[X][0][0], curve[Y][0][0])); + do_append(new SBasisCurve(curve)); + } + void append(Path const &other) { + replace(end_open(), other.begin(), other.end()); + } + + void replace(iterator replaced, Curve const &curve); + void replace(iterator first, iterator last, Curve const &curve); + void replace(iterator replaced, Path const &path); + void replace(iterator first, iterator last, Path const &path); + + template <typename Iter> + void replace(iterator replaced, Iter first, Iter last) { + replace(replaced, replaced + 1, first, last); + } + + template <typename Iter> + void replace(iterator first_replaced, iterator last_replaced, Iter first, Iter last) { + _unshare(); + Sequence::iterator seq_first_replaced(seq_iter(first_replaced)); + Sequence::iterator seq_last_replaced(seq_iter(last_replaced)); + Sequence source; + for (; first != last; ++first) { + source.push_back(first->duplicate()); + } + do_update(seq_first_replaced, seq_last_replaced, source); + } + + /** @brief Append a new curve to the path. + * + * This family of methods will automatically use the current final point of the path + * as the first argument of the new curve's constructor. To call this method, + * you'll need to write e.g.: + * @code + path.template appendNew<CubicBezier>(control1, control2, end_point); + @endcode + * It is important to note that the coordinates passed to appendNew should be finite! + * If one of the coordinates is infinite, 2geom will throw a ContinuityError exception. + */ + template <typename CurveType, typename... Args> + void appendNew(Args&&... args) { + _unshare(); + do_append(new CurveType(finalPoint(), std::forward<Args>(args)...)); + } + + /** @brief Reduce the closing segment to a point if it's shorter than precision. + * Do this by moving the final point. */ + void snapEnds(Coord precision = EPSILON); + + /// Append a stitching segment ending at the specified point. + void stitchTo(Point const &p); + + /** @brief Return a copy of the path without degenerate curves, except possibly for a + * degenerate closing segment. */ + Path withoutDegenerateCurves() const; + + /** @brief Verify the continuity invariant. + * If the path is not contiguous, this will throw a CountinuityError. */ + void checkContinuity() const; + + /** @brief Enable or disable the throwing of exceptions when stitching discontinuities. + * Normally stitching will cause exceptions, but when you are working with unsanitized + * curve data, you can disable these exceptions. */ + void setStitching(bool x) { + _exception_on_stitch = !x; + } + +private: + static Sequence::iterator seq_iter(iterator const &iter) { + return iter.path->_data->curves.begin() + iter.index; + } + static Sequence::const_iterator seq_iter(const_iterator const &iter) { + return iter.path->_data->curves.begin() + iter.index; + } + + // whether the closing segment is part of the path + bool _includesClosingSegment() const { + return _closed && !_closing_seg->isDegenerate(); + } + void _unshare() { + // Called before every mutation. + // Ensure we have our own copy of curve data and reset cached values + if (!_data.unique()) { + _data.reset(new PathData(*_data)); + _closing_seg = static_cast<ClosingSegment*>(&_data->curves.back()); + } + _data->fast_bounds = OptRect(); + } + PathTime _factorTime(Coord t) const; + + void stitch(Sequence::iterator first_replaced, Sequence::iterator last_replaced, Sequence &sequence); + void do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source); + + // n.b. takes ownership of curve object + void do_append(Curve *curve); + + std::shared_ptr<PathData> _data; + ClosingSegment *_closing_seg; + bool _closed; + bool _exception_on_stitch; +}; // end class Path + +Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths); + +inline Coord nearest_time(Point const &p, Path const &c) { + PathTime pt = c.nearestTime(p); + return pt.curve_index + pt.t; +} + +bool are_near(Path const &a, Path const &b, Coord precision = EPSILON); + +/** + * @brief Find the first point where two paths diverge away from one another. + * + * If the two paths have a common starting point, the algorithm follows them for as long as the + * images of the paths coincide and finds the first point where they stop coinciding. Note that + * only the images of paths in the plane are compared, and not their parametrizations, so this + * is not a functional (parametric) coincidence. If you want to test parametric coincidence, use + * bool are_near(Path const&, Path const&, Coord) instead. + * + * The function returns the point where the traces of the two paths finally diverge up to the + * specified precision. If the traces (images) of the paths are nearly identical until the end, + * the returned point is their (almost) common endpoint. If however the image of one of the paths + * is completely contained in the image of the other path, the returned point is the endpoint of + * the shorter path. + * + * If the paths have different starting points, then the returned intersection has the special + * time values of -1.0 on both paths and the returned intersection point is the midpoint of the + * line segment connecting the two starting points. + * + * @param first The first path to follow; corresponds to .first in the return value. + * @param second The second path to follow; corresponds to .second in the return value. + * @param precision How close the paths' images need to be in order to be considered as overlapping. + * @return A path intersection specifying the point and path times where the two paths part ways. + */ +PathIntersection parting_point(Path const &first, Path const &second, Coord precision = EPSILON); + +std::ostream &operator<<(std::ostream &out, Path const &path); + +} // end namespace Geom + + +#endif // LIB2GEOM_SEEN_PATH_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/pathvector.h b/include/2geom/pathvector.h new file mode 100644 index 0000000..3fd7d36 --- /dev/null +++ b/include/2geom/pathvector.h @@ -0,0 +1,304 @@ +/** @file + * @brief PathVector - a sequence of subpaths + *//* + * Authors: + * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2008-2014 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_PATHVECTOR_H +#define LIB2GEOM_SEEN_PATHVECTOR_H + +#include <optional> +#include <boost/concept/requires.hpp> +#include <boost/range/algorithm/equal.hpp> +#include <2geom/forward.h> +#include <2geom/path.h> +#include <2geom/transforms.h> + +namespace Geom { + +/** @brief Generalized time value in the path vector. + * + * This class exists because mapping the range of multiple curves onto the same interval + * as the curve index, we lose some precision. For instance, a path with 16 curves will + * have 4 bits less precision than a path with 1 curve. If you need high precision results + * in long paths, use this class and related methods instead of the standard methods + * pointAt(), nearestTime() and so on. + * + * @ingroup Paths */ +struct PathVectorTime + : public PathTime + , boost::totally_ordered<PathVectorTime> +{ + size_type path_index; ///< Index of the path in the vector + + PathVectorTime() : PathTime(0, 0), path_index(0) {} + PathVectorTime(size_type _i, size_type _c, Coord _t) + : PathTime(_c, _t), path_index(_i) {} + PathVectorTime(size_type _i, PathTime const &pos) + : PathTime(pos), path_index(_i) {} + + bool operator<(PathVectorTime const &other) const { + if (path_index < other.path_index) return true; + if (path_index == other.path_index) { + return static_cast<PathTime const &>(*this) < static_cast<PathTime const &>(other); + } + return false; + } + bool operator==(PathVectorTime const &other) const { + return path_index == other.path_index + && static_cast<PathTime const &>(*this) == static_cast<PathTime const &>(other); + } + + PathTime const &asPathTime() const { + return *static_cast<PathTime const *>(this); + } +}; + +inline std::ostream &operator<<(std::ostream &os, PathVectorTime const &pvt) { + os << pvt.path_index << ": " << pvt.asPathTime(); + return os; +} + +typedef Intersection<PathVectorTime> PathVectorIntersection; +typedef PathVectorIntersection PVIntersection; ///< Alias to save typing + +template <> +struct ShapeTraits<PathVector> { + typedef PathVectorTime TimeType; + //typedef PathVectorInterval IntervalType; + typedef PathVector AffineClosureType; + typedef PathVectorIntersection IntersectionType; +}; + +/** @brief Sequence of subpaths. + * + * This class corresponds to the SVG notion of a path: + * a sequence of any number of open or closed contiguous subpaths. + * Unlike Path, this class is closed under boolean operations. + * + * If you want to represent an arbitrary shape, this is the best class to use. + * Shapes with a boundary that is composed of only a single contiguous + * component can be represented with Path instead. + * + * @ingroup Paths + */ +class PathVector + : MultipliableNoncommutative< PathVector, Affine + , MultipliableNoncommutative< PathVector, Translate + , MultipliableNoncommutative< PathVector, Scale + , MultipliableNoncommutative< PathVector, Rotate + , MultipliableNoncommutative< PathVector, HShear + , MultipliableNoncommutative< PathVector, VShear + , MultipliableNoncommutative< PathVector, Zoom + , boost::equality_comparable< PathVector + > > > > > > > > +{ + typedef std::vector<Path> Sequence; +public: + typedef PathVectorTime Position; + typedef Sequence::iterator iterator; + typedef Sequence::const_iterator const_iterator; + typedef Sequence::size_type size_type; + typedef Path value_type; + typedef Path &reference; + typedef Path const &const_reference; + typedef Path *pointer; + typedef std::ptrdiff_t difference_type; + + PathVector() {} + PathVector(Path const &p) + : _data(1, p) + {} + template <typename InputIter> + PathVector(InputIter first, InputIter last) + : _data(first, last) + {} + + /// Check whether the vector contains any paths. + bool empty() const { return _data.empty(); } + /// Get the number of paths in the vector. + size_type size() const { return _data.size(); } + /// Get the total number of curves in the vector. + size_type curveCount() const; + + iterator begin() { return _data.begin(); } + iterator end() { return _data.end(); } + const_iterator begin() const { return _data.begin(); } + const_iterator end() const { return _data.end(); } + Path &operator[](size_type index) { + return _data[index]; + } + Path const &operator[](size_type index) const { + return _data[index]; + } + Path &at(size_type index) { + return _data.at(index); + } + Path const &at(size_type index) const { + return _data.at(index); + } + Path &front() { return _data.front(); } + Path const &front() const { return _data.front(); } + Path &back() { return _data.back(); } + Path const &back() const { return _data.back(); } + /// Append a path at the end. + void push_back(Path const &path) { + _data.push_back(path); + } + /// Remove the last path. + void pop_back() { + _data.pop_back(); + } + iterator insert(iterator pos, Path const &p) { + return _data.insert(pos, p); + } + template <typename InputIter> + void insert(iterator out, InputIter first, InputIter last) { + _data.insert(out, first, last); + } + /// Remove a path from the vector. + iterator erase(iterator i) { + return _data.erase(i); + } + /// Remove a range of paths from the vector. + iterator erase(iterator first, iterator last) { + return _data.erase(first, last); + } + /// Remove all paths from the vector. + void clear() { _data.clear(); } + /** @brief Change the number of paths. + * If the vector size increases, it is passed with paths that contain only + * a degenerate closing segment at (0,0). */ + void resize(size_type n) { _data.resize(n); } + /** @brief Reverse the direction of paths in the vector. + * @param reverse_paths If this is true, the order of paths is reversed as well; + * otherwise each path is reversed, but their order in the + * PathVector stays the same */ + void reverse(bool reverse_paths = true); + /** @brief Get a new vector with reversed direction of paths. + * @param reverse_paths If this is true, the order of paths is reversed as well; + * otherwise each path is reversed, but their order in the + * PathVector stays the same */ + PathVector reversed(bool reverse_paths = true) const; + + /// Get the range of allowed time values. + Interval timeRange() const { + Interval ret(0, curveCount()); return ret; + } + /** @brief Get the first point in the first path of the vector. + * This method will throw an exception if the vector doesn't contain any paths. */ + Point initialPoint() const { + return _data.front().initialPoint(); + } + /** @brief Get the last point in the last path of the vector. + * This method will throw an exception if the vector doesn't contain any paths. */ + Point finalPoint() const { + return _data.back().finalPoint(); + } + /** @brief Get all intersections of the path-vector with itself. This includes both + * self-intersections of constituent paths and intersections between different paths. */ + std::vector<PathVectorIntersection> intersectSelf(Coord precision = EPSILON) const; + Path &pathAt(Coord t, Coord *rest = NULL); + Path const &pathAt(Coord t, Coord *rest = NULL) const; + Curve const &curveAt(Coord t, Coord *rest = NULL) const; + Coord valueAt(Coord t, Dim2 d) const; + Point pointAt(Coord t) const; + + Path &pathAt(PathVectorTime const &pos) { + return const_cast<Path &>(static_cast<PathVector const*>(this)->pathAt(pos)); + } + Path const &pathAt(PathVectorTime const &pos) const { + return at(pos.path_index); + } + Curve const &curveAt(PathVectorTime const &pos) const { + return at(pos.path_index).at(pos.curve_index); + } + Point pointAt(PathVectorTime const &pos) const { + return at(pos.path_index).at(pos.curve_index).pointAt(pos.t); + } + Coord valueAt(PathVectorTime const &pos, Dim2 d) const { + return at(pos.path_index).at(pos.curve_index).valueAt(pos.t, d); + } + + OptRect boundsFast() const; + OptRect boundsExact() const; + + template <typename T> + BOOST_CONCEPT_REQUIRES(((TransformConcept<T>)), (PathVector &)) + operator*=(T const &t) { + if (empty()) return *this; + for (auto & i : *this) { + i *= t; + } + return *this; + } + + bool operator==(PathVector const &other) const { + return boost::range::equal(_data, other._data); + } + + void snapEnds(Coord precision = EPSILON); + + std::vector<PVIntersection> intersect(PathVector const &other, Coord precision = EPSILON) const; + + /** @brief Determine the winding number at the specified point. + * This is simply the sum of winding numbers for constituent paths. */ + int winding(Point const &p) const; + + std::optional<PathVectorTime> nearestTime(Point const &p, Coord *dist = NULL) const; + std::vector<PathVectorTime> allNearestTimes(Point const &p, Coord *dist = NULL) const; + + std::vector<Point> nodes() const; + +private: + PathVectorTime _factorTime(Coord t) const; + + Sequence _data; +}; + +inline OptRect bounds_fast(PathVector const &pv) { return pv.boundsFast(); } +inline OptRect bounds_exact(PathVector const &pv) { return pv.boundsExact(); } + +std::ostream &operator<<(std::ostream &out, PathVector const &pv); + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_PATHVECTOR_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/piecewise.h b/include/2geom/piecewise.h new file mode 100644 index 0000000..e34df15 --- /dev/null +++ b/include/2geom/piecewise.h @@ -0,0 +1,945 @@ +/** @file + * @brief Piecewise function class + *//* + * Copyright 2007 Michael Sloan <mgsloan@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, output to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_PIECEWISE_H +#define LIB2GEOM_SEEN_PIECEWISE_H + +#include <vector> +#include <map> +#include <utility> +#include <boost/concept_check.hpp> +#include <2geom/concepts.h> +#include <2geom/math-utils.h> +#include <2geom/sbasis.h> + + +namespace Geom { +/** + * @brief Function defined as discrete pieces. + * + * The Piecewise class manages a sequence of elements of a type as segments and + * the ’cuts’ between them. These cuts are time values which separate the pieces. + * This function representation allows for more interesting functions, as it provides + * a viable output for operations such as inversion, which may require multiple + * SBasis to properly invert the original. + * + * As for technical details, while the actual SBasis segments begin on the first + * cut and end on the last, the function is defined throughout all inputs by ex- + * tending the first and last segments. The exact switching between segments is + * arbitrarily such that beginnings (t=0) have preference over endings (t=1). This + * only matters if it is discontinuous at the location. + * \f[ + * f(t) \rightarrow \left\{ + * \begin{array}{cc} + * s_1,& t <= c_2 \\ + * s_2,& c_2 <= t <= c_3\\ + * \ldots \\ + * s_n,& c_n <= t + * \end{array}\right. + * \f] + * + * @ingroup Fragments + */ +template <typename T> +class Piecewise { + BOOST_CLASS_REQUIRE(T, Geom, FragmentConcept); + + public: + std::vector<double> cuts; + std::vector<T> segs; + //segs[i] stretches from cuts[i] to cuts[i+1]. + + Piecewise() {} + + explicit Piecewise(const T &s) { + push_cut(0.); + push_seg(s); + push_cut(1.); + } + + unsigned input_dim(){return 1;} + + typedef typename T::output_type output_type; + + explicit Piecewise(const output_type & v) { + push_cut(0.); + push_seg(T(v)); + push_cut(1.); + } + + inline void reserve(unsigned i) { segs.reserve(i); cuts.reserve(i + 1); } + + inline T const& operator[](unsigned i) const { return segs[i]; } + inline T& operator[](unsigned i) { return segs[i]; } + inline output_type operator()(double t) const { return valueAt(t); } + inline output_type valueAt(double t) const { + unsigned n = segN(t); + return segs[n](segT(t, n)); + } + inline output_type firstValue() const { + return valueAt(cuts.front()); + } + inline output_type lastValue() const { + return valueAt(cuts.back()); + } + + /** + * The size of the returned vector equals n_derivs+1. + */ + std::vector<output_type> valueAndDerivatives(double t, unsigned n_derivs) const { + unsigned n = segN(t); + std::vector<output_type> ret, val = segs[n].valueAndDerivatives(segT(t, n), n_derivs); + double mult = 1; + for(unsigned i = 0; i < val.size(); i++) { + ret.push_back(val[i] * mult); + mult /= cuts[n+1] - cuts[n]; + } + return ret; + } + + //TODO: maybe it is not a good idea to have this? + Piecewise<T> operator()(SBasis f); + Piecewise<T> operator()(Piecewise<SBasis>f); + + inline unsigned size() const { return segs.size(); } + inline bool empty() const { return segs.empty(); } + inline void clear() { + segs.clear(); + cuts.clear(); + } + + /**Convenience/implementation hiding function to add segment/cut pairs. + * Asserts that basic size and order invariants are correct + */ + inline void push(const T &s, double to) { + assert(cuts.size() - segs.size() == 1); + push_seg(s); + push_cut(to); + } + inline void push(T &&s, double to) { + assert(cuts.size() - segs.size() == 1); + push_seg(s); + push_cut(to); + } + //Convenience/implementation hiding function to add cuts. + inline void push_cut(double c) { + ASSERT_INVARIANTS(cuts.empty() || c > cuts.back()); + cuts.push_back(c); + } + //Convenience/implementation hiding function to add segments. + inline void push_seg(const T &s) { segs.push_back(s); } + inline void push_seg(T &&s) { segs.emplace_back(s); } + + /**Returns the segment index which corresponds to a 'global' piecewise time. + * Also takes optional low/high parameters to expedite the search for the segment. + */ + inline unsigned segN(double t, int low = 0, int high = -1) const { + high = (high == -1) ? size() : high; + if(t < cuts[0]) return 0; + if(t >= cuts[size()]) return size() - 1; + while(low < high) { + int mid = (high + low) / 2; //Lets not plan on having huge (> INT_MAX / 2) cut sequences + double mv = cuts[mid]; + if(mv < t) { + if(t < cuts[mid + 1]) return mid; else low = mid + 1; + } else if(t < mv) { + if(cuts[mid - 1] < t) return mid - 1; else high = mid - 1; + } else { + return mid; + } + } + return low; + } + + /**Returns the time within a segment, given the 'global' piecewise time. + * Also takes an optional index parameter which may be used for efficiency or to find the time on a + * segment outside its range. If it is left to its default, -1, it will call segN to find the index. + */ + inline double segT(double t, int i = -1) const { + if(i == -1) i = segN(t); + assert(i >= 0); + return (t - cuts[i]) / (cuts[i+1] - cuts[i]); + } + + inline double mapToDomain(double t, unsigned i) const { + return (1-t)*cuts[i] + t*cuts[i+1]; //same as: t * (cuts[i+1] - cuts[i]) + cuts[i] + } + + //Offsets the piecewise domain + inline void offsetDomain(double o) { + assert(std::isfinite(o)); + if(o != 0) + for(unsigned i = 0; i <= size(); i++) + cuts[i] += o; + } + + //Scales the domain of the function by a value. 0 will result in an empty Piecewise. + inline void scaleDomain(double s) { + assert(s > 0); + if(s == 0) { + cuts.clear(); segs.clear(); + return; + } + for(unsigned i = 0; i <= size(); i++) + cuts[i] *= s; + } + + //Retrieves the domain in interval form + inline Interval domain() const { return Interval(cuts.front(), cuts.back()); } + + //Transforms the domain into another interval + inline void setDomain(Interval dom) { + if(empty()) return; + /* dom can not be empty + if(dom.empty()) { + cuts.clear(); segs.clear(); + return; + }*/ + double cf = cuts.front(); + double o = dom.min() - cf, s = dom.extent() / (cuts.back() - cf); + for(unsigned i = 0; i <= size(); i++) + cuts[i] = (cuts[i] - cf) * s + o; + //fix floating point precision errors. + cuts[0] = dom.min(); + cuts[size()] = dom.max(); + } + + //Concatenates this Piecewise function with another, offsetting time of the other to match the end. + inline void concat(const Piecewise<T> &other) { + if(other.empty()) return; + + if(empty()) { + cuts = other.cuts; segs = other.segs; + return; + } + + segs.insert(segs.end(), other.segs.begin(), other.segs.end()); + double t = cuts.back() - other.cuts.front(); + cuts.reserve(cuts.size() + other.size()); + for(unsigned i = 0; i < other.size(); i++) + push_cut(other.cuts[i + 1] + t); + } + + //Like concat, but ensures continuity. + inline void continuousConcat(const Piecewise<T> &other) { + boost::function_requires<AddableConcept<typename T::output_type> >(); + if(other.empty()) return; + + if(empty()) { segs = other.segs; cuts = other.cuts; return; } + + typename T::output_type y = segs.back().at1() - other.segs.front().at0(); + double t = cuts.back() - other.cuts.front(); + reserve(size() + other.size()); + for(unsigned i = 0; i < other.size(); i++) + push(other[i] + y, other.cuts[i + 1] + t); + } + + //returns true if the Piecewise<T> meets some basic invariants. + inline bool invariants() const { + // segs between cuts + if(!(segs.size() + 1 == cuts.size() || (segs.empty() && cuts.empty()))) + return false; + // cuts in order + for(unsigned i = 0; i < segs.size(); i++) + if(cuts[i] >= cuts[i+1]) + return false; + return true; + } + +}; + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +inline typename FragmentConcept<T>::BoundsType bounds_fast(const Piecewise<T> &f) { + boost::function_requires<FragmentConcept<T> >(); + + if(f.empty()) return typename FragmentConcept<T>::BoundsType(); + typename FragmentConcept<T>::BoundsType ret(bounds_fast(f[0])); + for(unsigned i = 1; i < f.size(); i++) + ret.unionWith(bounds_fast(f[i])); + return ret; +} + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +inline typename FragmentConcept<T>::BoundsType bounds_exact(const Piecewise<T> &f) { + boost::function_requires<FragmentConcept<T> >(); + + if(f.empty()) return typename FragmentConcept<T>::BoundsType(); + typename FragmentConcept<T>::BoundsType ret(bounds_exact(f[0])); + for(unsigned i = 1; i < f.size(); i++) + ret.unionWith(bounds_exact(f[i])); + return ret; +} + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +inline typename FragmentConcept<T>::BoundsType bounds_local(const Piecewise<T> &f, const OptInterval &_m) { + boost::function_requires<FragmentConcept<T> >(); + + if(f.empty() || !_m) return typename FragmentConcept<T>::BoundsType(); + Interval const &m = *_m; + if(m.isSingular()) return typename FragmentConcept<T>::BoundsType(f(m.min())); + + unsigned fi = f.segN(m.min()), ti = f.segN(m.max()); + double ft = f.segT(m.min(), fi), tt = f.segT(m.max(), ti); + + if(fi == ti) return bounds_local(f[fi], Interval(ft, tt)); + + typename FragmentConcept<T>::BoundsType ret(bounds_local(f[fi], Interval(ft, 1.))); + for(unsigned i = fi + 1; i < ti; i++) + ret.unionWith(bounds_exact(f[i])); + if(tt != 0.) ret.unionWith(bounds_local(f[ti], Interval(0., tt))); + + return ret; +} + +/** + * Returns a portion of a piece of a Piecewise<T>, given the piece's index and a to/from time. + * \relates Piecewise + */ +template<typename T> +T elem_portion(const Piecewise<T> &a, unsigned i, double from, double to) { + assert(i < a.size()); + double rwidth = 1 / (a.cuts[i+1] - a.cuts[i]); + return portion( a[i], (from - a.cuts[i]) * rwidth, (to - a.cuts[i]) * rwidth ); +} + +/**Piecewise<T> partition(const Piecewise<T> &pw, std::vector<double> const &c); + * Further subdivides the Piecewise<T> such that there is a cut at every value in c. + * Precondition: c sorted lower to higher. + * + * //Given Piecewise<T> a and b: + * Piecewise<T> ac = a.partition(b.cuts); + * Piecewise<T> bc = b.partition(a.cuts); + * //ac.cuts should be equivalent to bc.cuts + * + * \relates Piecewise + */ +template<typename T> +Piecewise<T> partition(const Piecewise<T> &pw, std::vector<double> const &c) { + assert(pw.invariants()); + if(c.empty()) return Piecewise<T>(pw); + + Piecewise<T> ret = Piecewise<T>(); + ret.reserve(c.size() + pw.cuts.size() - 1); + + if(pw.empty()) { + ret.cuts = c; + for(unsigned i = 0; i < c.size() - 1; i++) + ret.push_seg(T()); + return ret; + } + + unsigned si = 0, ci = 0; //Segment index, Cut index + + //if the cuts have something earlier than the Piecewise<T>, add portions of the first segment + while(ci < c.size() && c[ci] < pw.cuts.front()) { + bool isLast = (ci == c.size()-1 || c[ci + 1] >= pw.cuts.front()); + ret.push_cut(c[ci]); + ret.push_seg( elem_portion(pw, 0, c[ci], isLast ? pw.cuts.front() : c[ci + 1]) ); + ci++; + } + + ret.push_cut(pw.cuts[0]); + double prev = pw.cuts[0]; //previous cut + //Loop which handles cuts within the Piecewise<T> domain + //Should have the cuts = segs + 1 invariant + while(si < pw.size() && ci <= c.size()) { + if(ci == c.size() && prev <= pw.cuts[si]) { //cuts exhausted, straight copy the rest + ret.segs.insert(ret.segs.end(), pw.segs.begin() + si, pw.segs.end()); + ret.cuts.insert(ret.cuts.end(), pw.cuts.begin() + si + 1, pw.cuts.end()); + return ret; + }else if(ci == c.size() || c[ci] >= pw.cuts[si + 1]) { //no more cuts within this segment, finalize + if(prev > pw.cuts[si]) { //segment already has cuts, so portion is required + ret.push_seg(portion(pw[si], pw.segT(prev, si), 1.0)); + } else { //plain copy is fine + ret.push_seg(pw[si]); + } + ret.push_cut(pw.cuts[si + 1]); + prev = pw.cuts[si + 1]; + si++; + } else if(c[ci] == pw.cuts[si]){ //coincident + //Already finalized the seg with the code immediately above + ci++; + } else { //plain old subdivision + ret.push(elem_portion(pw, si, prev, c[ci]), c[ci]); + prev = c[ci]; + ci++; + } + } + + //input cuts extend further than this Piecewise<T>, extend the last segment. + while(ci < c.size()) { + if(c[ci] > prev) { + ret.push(elem_portion(pw, pw.size() - 1, prev, c[ci]), c[ci]); + prev = c[ci]; + } + ci++; + } + return ret; +} + +/** + * Returns a Piecewise<T> with a defined domain of [min(from, to), max(from, to)]. + * \relates Piecewise + */ +template<typename T> +Piecewise<T> portion(const Piecewise<T> &pw, double from, double to) { + if(pw.empty() || from == to) return Piecewise<T>(); + + Piecewise<T> ret; + + double temp = from; + from = std::min(from, to); + to = std::max(temp, to); + + unsigned i = pw.segN(from); + ret.push_cut(from); + if(i == pw.size() - 1 || to <= pw.cuts[i + 1]) { //to/from inhabit the same segment + ret.push(elem_portion(pw, i, from, to), to); + return ret; + } + ret.push_seg(portion( pw[i], pw.segT(from, i), 1.0 )); + i++; + unsigned fi = pw.segN(to, i); + ret.reserve(fi - i + 1); + if (to == pw.cuts[fi]) fi-=1; + + ret.segs.insert(ret.segs.end(), pw.segs.begin() + i, pw.segs.begin() + fi); //copy segs + ret.cuts.insert(ret.cuts.end(), pw.cuts.begin() + i, pw.cuts.begin() + fi + 1); //and their cuts + + ret.push_seg( portion(pw[fi], 0.0, pw.segT(to, fi))); + if(to != ret.cuts.back()) ret.push_cut(to); + ret.invariants(); + return ret; +} + +//TODO: seems like these should be mutating +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +Piecewise<T> remove_short_cuts(Piecewise<T> const &f, double tol) { + if(f.empty()) return f; + Piecewise<T> ret; + ret.reserve(f.size()); + ret.push_cut(f.cuts[0]); + for(unsigned i=0; i<f.size(); i++){ + if (f.cuts[i+1]-f.cuts[i] >= tol || i==f.size()-1) { + ret.push(f[i], f.cuts[i+1]); + } + } + return ret; +} + +//TODO: seems like these should be mutating +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +Piecewise<T> remove_short_cuts_extending(Piecewise<T> const &f, double tol) { + if(f.empty()) return f; + Piecewise<T> ret; + ret.reserve(f.size()); + ret.push_cut(f.cuts[0]); + double last = f.cuts[0]; // last cut included + for(unsigned i=0; i<f.size(); i++){ + if (f.cuts[i+1]-f.cuts[i] >= tol) { + ret.push(elem_portion(f, i, last, f.cuts[i+1]), f.cuts[i+1]); + last = f.cuts[i+1]; + } + } + return ret; +} + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +std::vector<double> roots(const Piecewise<T> &pw) { + std::vector<double> ret; + for(unsigned i = 0; i < pw.size(); i++) { + std::vector<double> sr = roots(pw[i]); + for (double & j : sr) ret.push_back(j * (pw.cuts[i + 1] - pw.cuts[i]) + pw.cuts[i]); + + } + return ret; +} + +//IMPL: OffsetableConcept +/** + * ... + * \return \f$ a + b = \f$ + * \relates Piecewise + */ +template<typename T> +Piecewise<T> operator+(Piecewise<T> const &a, typename T::output_type b) { + boost::function_requires<OffsetableConcept<T> >(); +//TODO:empty + Piecewise<T> ret; + ret.segs.reserve(a.size()); + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] + b); + return ret; +} +template<typename T> +Piecewise<T> operator-(Piecewise<T> const &a, typename T::output_type b) { + boost::function_requires<OffsetableConcept<T> >(); +//TODO: empty + Piecewise<T> ret; + ret.segs.reserve(a.size()); + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] - b); + return ret; +} +template<typename T> +Piecewise<T>& operator+=(Piecewise<T>& a, typename T::output_type b) { + boost::function_requires<OffsetableConcept<T> >(); + + if(a.empty()) { a.push_cut(0.); a.push(T(b), 1.); return a; } + + for(unsigned i = 0; i < a.size();i++) + a[i] += b; + return a; +} +template<typename T> +Piecewise<T>& operator-=(Piecewise<T>& a, typename T::output_type b) { + boost::function_requires<OffsetableConcept<T> >(); + + if(a.empty()) { a.push_cut(0.); a.push(T(-b), 1.); return a; } + + for(unsigned i = 0;i < a.size();i++) + a[i] -= b; + return a; +} + +//IMPL: ScalableConcept +/** + * ... + * \return \f$ -a = \f$ + * \relates Piecewise + */ +template<typename T> +Piecewise<T> operator-(Piecewise<T> const &a) { + boost::function_requires<ScalableConcept<T> >(); + + Piecewise<T> ret; + ret.segs.reserve(a.size()); + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(- a[i]); + return ret; +} +/** + * ... + * \return \f$ a * b = \f$ + * \relates Piecewise + */ +template<typename T> +Piecewise<T> operator*(Piecewise<T> const &a, double b) { + boost::function_requires<ScalableConcept<T> >(); + + if(a.empty()) return Piecewise<T>(); + + Piecewise<T> ret; + ret.segs.reserve(a.size()); + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] * b); + return ret; +} +/** + * ... + * \return \f$ a * b = \f$ + * \relates Piecewise + */ +template<typename T> +Piecewise<T> operator*(Piecewise<T> const &a, T b) { + boost::function_requires<ScalableConcept<T> >(); + + if(a.empty()) return Piecewise<T>(); + + Piecewise<T> ret; + ret.segs.reserve(a.size()); + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] * b); + return ret; +} +/** + * ... + * \return \f$ a / b = \f$ + * \relates Piecewise + */ +template<typename T> +Piecewise<T> operator/(Piecewise<T> const &a, double b) { + boost::function_requires<ScalableConcept<T> >(); + + //FIXME: b == 0? + if(a.empty()) return Piecewise<T>(); + + Piecewise<T> ret; + ret.segs.reserve(a.size()); + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] / b); + return ret; +} +template<typename T> +Piecewise<T>& operator*=(Piecewise<T>& a, double b) { + boost::function_requires<ScalableConcept<T> >(); + + for(unsigned i = 0; i < a.size();i++) + a[i] *= b; + return a; +} +template<typename T> +Piecewise<T>& operator/=(Piecewise<T>& a, double b) { + boost::function_requires<ScalableConcept<T> >(); + + //FIXME: b == 0? + + for(unsigned i = 0; i < a.size();i++) + a[i] /= b; + return a; +} + +//IMPL: AddableConcept +/** + * ... + * \return \f$ a + b = \f$ + * \relates Piecewise + */ +template<typename T> +Piecewise<T> operator+(Piecewise<T> const &a, Piecewise<T> const &b) { + boost::function_requires<AddableConcept<T> >(); + + Piecewise<T> pa = partition(a, b.cuts), pb = partition(b, a.cuts); + Piecewise<T> ret; + assert(pa.size() == pb.size()); + ret.segs.reserve(pa.size()); + ret.cuts = pa.cuts; + for (unsigned i = 0; i < pa.size(); i++) + ret.push_seg(pa[i] + pb[i]); + return ret; +} +/** + * ... + * \return \f$ a - b = \f$ + * \relates Piecewise + */ +template<typename T> +Piecewise<T> operator-(Piecewise<T> const &a, Piecewise<T> const &b) { + boost::function_requires<AddableConcept<T> >(); + + Piecewise<T> pa = partition(a, b.cuts), pb = partition(b, a.cuts); + Piecewise<T> ret = Piecewise<T>(); + assert(pa.size() == pb.size()); + ret.segs.reserve(pa.size()); + ret.cuts = pa.cuts; + for (unsigned i = 0; i < pa.size(); i++) + ret.push_seg(pa[i] - pb[i]); + return ret; +} +template<typename T> +inline Piecewise<T>& operator+=(Piecewise<T> &a, Piecewise<T> const &b) { + a = a+b; + return a; +} +template<typename T> +inline Piecewise<T>& operator-=(Piecewise<T> &a, Piecewise<T> const &b) { + a = a-b; + return a; +} + +/** + * ... + * \return \f$ a \cdot b = \f$ + * \relates Piecewise + */ +template<typename T1,typename T2> +Piecewise<T2> operator*(Piecewise<T1> const &a, Piecewise<T2> const &b) { + //function_requires<MultiplicableConcept<T1> >(); + //function_requires<MultiplicableConcept<T2> >(); + + Piecewise<T1> pa = partition(a, b.cuts); + Piecewise<T2> pb = partition(b, a.cuts); + Piecewise<T2> ret = Piecewise<T2>(); + assert(pa.size() == pb.size()); + ret.segs.reserve(pa.size()); + ret.cuts = pa.cuts; + for (unsigned i = 0; i < pa.size(); i++) + ret.push_seg(pa[i] * pb[i]); + return ret; +} + +/** + * ... + * \return \f$ a \cdot b \f$ + * \relates Piecewise + */ +template<typename T> +inline Piecewise<T>& operator*=(Piecewise<T> &a, Piecewise<T> const &b) { + a = a * b; + return a; +} + +Piecewise<SBasis> divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, unsigned k); +//TODO: replace divide(a,b,k) by divide(a,b,tol,k)? +//TODO: atm, relative error is ~(tol/a)%. Find a way to make it independent of a. +//Nota: the result is 'truncated' where b is smaller than 'zero': ~ a/max(b,zero). +Piecewise<SBasis> +divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero=1.e-3); +Piecewise<SBasis> +divide(SBasis const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero=1.e-3); +Piecewise<SBasis> +divide(Piecewise<SBasis> const &a, SBasis const &b, double tol, unsigned k, double zero=1.e-3); +Piecewise<SBasis> +divide(SBasis const &a, SBasis const &b, double tol, unsigned k, double zero=1.e-3); + +//Composition: functions called compose_* are pieces of compose that are factored out in pw.cpp. +std::map<double,unsigned> compose_pullback(std::vector<double> const &cuts, SBasis const &g); +int compose_findSegIdx(std::map<double,unsigned>::iterator const &cut, + std::map<double,unsigned>::iterator const &next, + std::vector<double> const &levels, + SBasis const &g); + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +Piecewise<T> compose(Piecewise<T> const &f, SBasis const &g){ + /// \todo add concept check + Piecewise<T> result; + if (f.empty()) return result; + if (g.isZero()) return Piecewise<T>(f(0)); + if (f.size()==1){ + double t0 = f.cuts[0], width = f.cuts[1] - t0; + return (Piecewise<T>) compose(f.segs[0],compose(Linear(-t0 / width, (1-t0) / width), g)); + } + + //first check bounds... + Interval bs = *bounds_fast(g); + if (f.cuts.front() > bs.max() || bs.min() > f.cuts.back()){ + int idx = (bs.max() < f.cuts[1]) ? 0 : f.cuts.size()-2; + double t0 = f.cuts[idx], width = f.cuts[idx+1] - t0; + return (Piecewise<T>) compose(f.segs[idx],compose(Linear(-t0 / width, (1-t0) / width), g)); + } + + std::vector<double> levels;//we can forget first and last cuts... + levels.insert(levels.begin(),f.cuts.begin()+1,f.cuts.end()-1); + //TODO: use a std::vector<pairs<double,unsigned> > instead of a map<double,unsigned>. + std::map<double,unsigned> cuts_pb = compose_pullback(levels,g); + + //-- Compose each piece of g with the relevant seg of f. + result.cuts.push_back(0.); + std::map<double,unsigned>::iterator cut=cuts_pb.begin(); + std::map<double,unsigned>::iterator next=cut; next++; + while(next!=cuts_pb.end()){ + //assert(std::abs(int((*cut).second-(*next).second))<1); + //TODO: find a way to recover from this error? the root finder missed some root; + // the levels/variations of f might be too close/fast... + int idx = compose_findSegIdx(cut,next,levels,g); + double t0=(*cut).first; + double t1=(*next).first; + + if (!are_near(t0,t1,EPSILON*EPSILON)) { // prevent adding cuts that are extremely close together and that may cause trouble with rounding e.g. when reversing the path + SBasis sub_g=compose(g, Linear(t0,t1)); + sub_g=compose(Linear(-f.cuts[idx]/(f.cuts[idx+1]-f.cuts[idx]), + (1-f.cuts[idx])/(f.cuts[idx+1]-f.cuts[idx])),sub_g); + result.push(compose(f[idx],sub_g),t1); + } + + cut++; + next++; + } + return(result); +} + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +Piecewise<T> compose(Piecewise<T> const &f, Piecewise<SBasis> const &g){ +/// \todo add concept check + Piecewise<T> result; + for(unsigned i = 0; i < g.segs.size(); i++){ + Piecewise<T> fgi=compose(f, g.segs[i]); + fgi.setDomain(Interval(g.cuts[i], g.cuts[i+1])); + result.concat(fgi); + } + return result; +} + +/* +Piecewise<D2<SBasis> > compose(D2<SBasis2d> const &sb2d, Piecewise<D2<SBasis> > const &pwd2sb){ +/// \todo add concept check + Piecewise<D2<SBasis> > result; + result.push_cut(0.); + for(unsigned i = 0; i < pwd2sb.size(); i++){ + result.push(compose_each(sb2d,pwd2sb[i]),i+1); + } + return result; +}*/ + +/** Compose an SBasis with the inverse of another. + * WARNING: It's up to the user to check that the second SBasis is indeed + * invertible (i.e. strictly increasing or decreasing). + * \return \f$ f \cdot g^{-1} \f$ + * \relates Piecewise + */ +Piecewise<SBasis> pw_compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double zero); + + + +template <typename T> +Piecewise<T> Piecewise<T>::operator()(SBasis f){return compose((*this),f);} +template <typename T> +Piecewise<T> Piecewise<T>::operator()(Piecewise<SBasis>f){return compose((*this),f);} + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +Piecewise<T> integral(Piecewise<T> const &a) { + Piecewise<T> result; + result.segs.resize(a.segs.size()); + result.cuts = a.cuts; + typename T::output_type c = a.segs[0].at0(); + for(unsigned i = 0; i < a.segs.size(); i++){ + result.segs[i] = integral(a.segs[i])*(a.cuts[i+1]-a.cuts[i]); + result.segs[i]+= c-result.segs[i].at0(); + c = result.segs[i].at1(); + } + return result; +} + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +Piecewise<T> derivative(Piecewise<T> const &a) { + Piecewise<T> result; + result.segs.resize(a.segs.size()); + result.cuts = a.cuts; + for(unsigned i = 0; i < a.segs.size(); i++){ + result.segs[i] = derivative(a.segs[i])/(a.cuts[i+1]-a.cuts[i]); + } + return result; +} + +std::vector<double> roots(Piecewise<SBasis> const &f); + +std::vector<std::vector<double> >multi_roots(Piecewise<SBasis> const &f, std::vector<double> const &values); + +//TODO: implement level_sets directly for pwsb instead of sb (and derive it fo sb). +//It should be faster than the reverse as the algorithm may jump over full cut intervals. +std::vector<Interval> level_set(Piecewise<SBasis> const &f, Interval const &level, double tol=1e-5); +std::vector<Interval> level_set(Piecewise<SBasis> const &f, double v, double vtol, double tol=1e-5); +//std::vector<Interval> level_sets(Piecewise<SBasis> const &f, std::vector<Interval> const &levels, double tol=1e-5); +//std::vector<Interval> level_sets(Piecewise<SBasis> const &f, std::vector<double> &v, double vtol, double tol=1e-5); + + +/** + * ... + * \return ... + * \relates Piecewise + */ +template<typename T> +Piecewise<T> reverse(Piecewise<T> const &f) { + Piecewise<T> ret = Piecewise<T>(); + ret.reserve(f.size()); + double start = f.cuts[0]; + double end = f.cuts.back(); + for (unsigned i = 0; i < f.cuts.size(); i++) { + double x = f.cuts[f.cuts.size() - 1 - i]; + ret.push_cut(end - (x - start)); + } + for (unsigned i = 0; i < f.segs.size(); i++) + ret.push_seg(reverse(f[f.segs.size() - i - 1])); + return ret; +} + +/** + * Interpolates between a and b. + * \return a if t = 0, b if t = 1, or an interpolation between a and b for t in [0,1] + * \relates Piecewise + */ +template<typename T> +Piecewise<T> lerp(double t, Piecewise<T> const &a, Piecewise<T> b) { + // Make sure both paths have the same number of segments and cuts at the same locations + b.setDomain(a.domain()); + Piecewise<T> pA = partition(a, b.cuts); + Piecewise<T> pB = partition(b, a.cuts); + + return (pA*(1-t) + pB*t); +} + +} +#endif //LIB2GEOM_SEEN_PIECEWISE_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/point.h b/include/2geom/point.h new file mode 100644 index 0000000..3a29066 --- /dev/null +++ b/include/2geom/point.h @@ -0,0 +1,449 @@ +/** @file + * @brief Cartesian point / 2D vector and related operations + *//* + * Authors: + * Michael G. Sloan <mgsloan@gmail.com> + * Nathan Hurst <njh@njhurst.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright (C) 2006-2009 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_POINT_H +#define LIB2GEOM_SEEN_POINT_H + +#include <iostream> +#include <iterator> +#include <boost/operators.hpp> +#include <2geom/forward.h> +#include <2geom/coord.h> +#include <2geom/int-point.h> +#include <2geom/math-utils.h> +#include <2geom/utils.h> + +namespace Geom { + +class Point + : boost::additive< Point + , boost::totally_ordered< Point + , boost::multiplicative< Point, Coord + , boost::multiplicative< Point + , boost::multiplicative< Point, IntPoint + , MultipliableNoncommutative< Point, Affine + , MultipliableNoncommutative< Point, Translate + , MultipliableNoncommutative< Point, Rotate + , MultipliableNoncommutative< Point, Scale + , MultipliableNoncommutative< Point, HShear + , MultipliableNoncommutative< Point, VShear + , MultipliableNoncommutative< Point, Zoom + > > > > > > > > > > > > // base class chaining, see documentation for Boost.Operator +{ + Coord _pt[2] = { 0, 0 }; +public: + using D1Value = Coord; + using D1Reference = Coord &; + using D1ConstReference = Coord const &; + + /// @name Create points + /// @{ + /** Construct a point at the origin. */ + Point() = default; + /** Construct a point from its coordinates. */ + Point(Coord x, Coord y) + : _pt{ x, y } + {} + /** Construct from integer point. */ + Point(IntPoint const &p) + : Point(p[X], p[Y]) + {} + /** @brief Construct a point from its polar coordinates. + * The angle is specified in radians, in the mathematical convention (increasing + * counter-clockwise from +X). */ + static Point polar(Coord angle, Coord radius) { + Point ret(polar(angle)); + ret *= radius; + return ret; + } + /** @brief Construct an unit vector from its angle. + * The angle is specified in radians, in the mathematical convention (increasing + * counter-clockwise from +X). */ + static Point polar(Coord angle); + /// @} + + /// @name Access the coordinates of a point + /// @{ + Coord operator[](unsigned i) const { return _pt[i]; } + Coord &operator[](unsigned i) { return _pt[i]; } + + Coord operator[](Dim2 d) const noexcept { return _pt[d]; } + Coord &operator[](Dim2 d) noexcept { return _pt[d]; } + + Coord x() const noexcept { return _pt[X]; } + Coord &x() noexcept { return _pt[X]; } + Coord y() const noexcept { return _pt[Y]; } + Coord &y() noexcept { return _pt[Y]; } + /// @} + + /// @name Vector operations + /// @{ + /** @brief Compute the distance from origin. + * @return Length of the vector from origin to this point */ + Coord length() const { return std::hypot(_pt[0], _pt[1]); } + void normalize(); + Point normalized() const { + Point ret(*this); + ret.normalize(); + return ret; + } + + /** @brief Return a point like this point but rotated -90 degrees. + * If the y axis grows downwards and the x axis grows to the + * right, then this is 90 degrees counter-clockwise. */ + Point ccw() const { + return Point(_pt[Y], -_pt[X]); + } + + /** @brief Return a point like this point but rotated +90 degrees. + * If the y axis grows downwards and the x axis grows to the + * right, then this is 90 degrees clockwise. */ + Point cw() const { + return Point(-_pt[Y], _pt[X]); + } + /// @} + + /// @name Vector-like arithmetic operations + /// @{ + Point operator-() const { + return Point(-_pt[X], -_pt[Y]); + } + Point &operator+=(Point const &o) { + _pt[X] += o._pt[X]; + _pt[Y] += o._pt[Y]; + return *this; + } + Point &operator-=(Point const &o) { + _pt[X] -= o._pt[X]; + _pt[Y] -= o._pt[Y]; + return *this; + } + Point &operator*=(Coord s) { + for (double & i : _pt) i *= s; + return *this; + } + Point &operator*=(Point const &o) { + _pt[X] *= o._pt[X]; + _pt[Y] *= o._pt[Y]; + return *this; + } + Point &operator*=(IntPoint const &o) { + _pt[X] *= o.x(); + _pt[Y] *= o.y(); + return *this; + } + Point &operator/=(Coord s) { + //TODO: s == 0? + for (double & i : _pt) i /= s; + return *this; + } + Point &operator/=(Point const &o) { + _pt[X] /= o._pt[X]; + _pt[Y] /= o._pt[Y]; + return *this; + } + Point &operator/=(IntPoint const &o) { + _pt[X] /= o.x(); + _pt[Y] /= o.y(); + return *this; + } + /// @} + + /// @name Affine transformations + /// @{ + Point &operator*=(Affine const &m); + // implemented in transforms.cpp + Point &operator*=(Translate const &t); + Point &operator*=(Scale const &s); + Point &operator*=(Rotate const &r); + Point &operator*=(HShear const &s); + Point &operator*=(VShear const &s); + Point &operator*=(Zoom const &z); + /// @} + + /// @name Conversion to integer points + /// @{ + /** @brief Round to nearest integer coordinates. */ + IntPoint round() const { + IntPoint ret(::round(_pt[X]), ::round(_pt[Y])); + return ret; + } + /** @brief Round coordinates downwards. */ + IntPoint floor() const { + IntPoint ret(::floor(_pt[X]), ::floor(_pt[Y])); + return ret; + } + /** @brief Round coordinates upwards. */ + IntPoint ceil() const { + IntPoint ret(::ceil(_pt[X]), ::ceil(_pt[Y])); + return ret; + } + /// @} + + /// @name Various utilities + /// @{ + /** @brief Check whether both coordinates are finite. */ + bool isFinite() const { + for (double i : _pt) { + if(!std::isfinite(i)) return false; + } + return true; + } + /** @brief Check whether both coordinates are zero. */ + bool isZero() const { + return _pt[X] == 0 && _pt[Y] == 0; + } + /** @brief Check whether the length of the vector is close to 1. */ + bool isNormalized(Coord eps=EPSILON) const { + return are_near(length(), 1.0, eps); + } + /** @brief Equality operator. + * This tests for exact identity (as opposed to are_near()). Note that due to numerical + * errors, this test might return false even if the points should be identical. */ + bool operator==(const Point &in_pnt) const { + return (_pt[X] == in_pnt[X]) && (_pt[Y] == in_pnt[Y]); + } + /** @brief Lexicographical ordering for points. + * Y coordinate is regarded as more significant. When sorting according to this + * ordering, the points will be sorted according to the Y coordinate, and within + * points with the same Y coordinate according to the X coordinate. */ + bool operator<(const Point &p) const { + return _pt[Y] < p[Y] || (_pt[Y] == p[Y] && _pt[X] < p[X]); + } + /// @} + + /** @brief Lexicographical ordering functor. + * @param d The dimension with higher significance */ + template <Dim2 DIM> struct LexLess; + template <Dim2 DIM> struct LexGreater; + //template <Dim2 DIM, typename First = std::less<Coord>, typename Second = std::less<Coord> > LexOrder; + /** @brief Lexicographical ordering functor with runtime dimension. */ + struct LexLessRt { + LexLessRt(Dim2 d) : dim(d) {} + inline bool operator()(Point const &a, Point const &b) const; + private: + Dim2 dim; + }; + struct LexGreaterRt { + LexGreaterRt(Dim2 d) : dim(d) {} + inline bool operator()(Point const &a, Point const &b) const; + private: + Dim2 dim; + }; + //template <typename First = std::less<Coord>, typename Second = std::less<Coord> > LexOrder +}; + +/** @brief Output operator for points. + * Prints out the coordinates. + * @relates Point */ +std::ostream &operator<<(std::ostream &out, const Geom::Point &p); + +template<> struct Point::LexLess<X> { + typedef std::less<Coord> Primary; + typedef std::less<Coord> Secondary; + typedef std::less<Coord> XOrder; + typedef std::less<Coord> YOrder; + bool operator()(Point const &a, Point const &b) const { + return a[X] < b[X] || (a[X] == b[X] && a[Y] < b[Y]); + } +}; +template<> struct Point::LexLess<Y> { + typedef std::less<Coord> Primary; + typedef std::less<Coord> Secondary; + typedef std::less<Coord> XOrder; + typedef std::less<Coord> YOrder; + bool operator()(Point const &a, Point const &b) const { + return a[Y] < b[Y] || (a[Y] == b[Y] && a[X] < b[X]); + } +}; +template<> struct Point::LexGreater<X> { + typedef std::greater<Coord> Primary; + typedef std::greater<Coord> Secondary; + typedef std::greater<Coord> XOrder; + typedef std::greater<Coord> YOrder; + bool operator()(Point const &a, Point const &b) const { + return a[X] > b[X] || (a[X] == b[X] && a[Y] > b[Y]); + } +}; +template<> struct Point::LexGreater<Y> { + typedef std::greater<Coord> Primary; + typedef std::greater<Coord> Secondary; + typedef std::greater<Coord> XOrder; + typedef std::greater<Coord> YOrder; + bool operator()(Point const &a, Point const &b) const { + return a[Y] > b[Y] || (a[Y] == b[Y] && a[X] > b[X]); + } +}; +inline bool Point::LexLessRt::operator()(Point const &a, Point const &b) const { + return dim ? Point::LexLess<Y>()(a, b) : Point::LexLess<X>()(a, b); +} +inline bool Point::LexGreaterRt::operator()(Point const &a, Point const &b) const { + return dim ? Point::LexGreater<Y>()(a, b) : Point::LexGreater<X>()(a, b); +} + +/** @brief Compute the second (Euclidean) norm of @a p. + * This corresponds to the length of @a p. The result will not overflow even if + * \f$p_X^2 + p_Y^2\f$ is larger that the maximum value that can be stored + * in a <code>double</code>. + * @return \f$\sqrt{p_X^2 + p_Y^2}\f$ + * @relates Point */ +inline Coord L2(Point const &p) { + return p.length(); +} + +/** @brief Compute the square of the Euclidean norm of @a p. + * Warning: this can overflow where L2 won't. + * @return \f$p_X^2 + p_Y^2\f$ + * @relates Point */ +inline Coord L2sq(Point const &p) { + return p[0]*p[0] + p[1]*p[1]; +} + +/** @brief Returns p * Geom::rotate_degrees(90), but more efficient. + * + * Angle direction in 2Geom: If you use the traditional mathematics convention that y + * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If + * you take the common non-mathematical convention that y increases downwards, then positive angles + * are clockwise, as is common outside of mathematics. + * + * There is no function to rotate by -90 degrees: use -rot90(p) instead. + * @relates Point */ +inline Point rot90(Point const &p) { + return Point(-p[Y], p[X]); +} + +/** @brief Linear interpolation between two points. + * @param t Time value + * @param a First point + * @param b Second point + * @return Point on a line between a and b. The ratio of its distance from a + * and the distance between a and b will be equal to t. + * @relates Point */ +inline Point lerp(Coord t, Point const &a, Point const &b) { + return (1 - t) * a + t * b; +} + +/** @brief Return a point halfway between the specified ones. + * @relates Point */ +inline Point middle_point(Point const &p1, Point const &p2) { + return lerp(0.5, p1, p2); +} + +/** @brief Compute the dot product of a and b. + * Dot product can be interpreted as a measure of how parallel the vectors are. + * For perpendicular vectors, it is zero. For parallel ones, its absolute value is highest, + * and the sign depends on whether they point in the same direction (+) or opposite ones (-). + * @return \f$a \cdot b = a_X b_X + a_Y b_Y\f$. + * @relates Point */ +inline Coord dot(Point const &a, Point const &b) { + return a[X] * b[X] + a[Y] * b[Y]; +} + +/** @brief Compute the 2D cross product. + * This is also known as "perp dot product". It will be zero for parallel vectors, + * and the absolute value will be highest for perpendicular vectors. + * @return \f$a \times b = a_X b_Y - a_Y b_X\f$. + * @relates Point*/ +inline Coord cross(Point const &a, Point const &b) +{ + // equivalent implementation: + // return dot(a, b.ccw()); + return a[X] * b[Y] - a[Y] * b[X]; +} + +/// Compute the (Euclidean) distance between points. +/// @relates Point +inline Coord distance (Point const &a, Point const &b) { + return (a - b).length(); +} + +/// Compute the square of the distance between points. +/// @relates Point +inline Coord distanceSq (Point const &a, Point const &b) { + return L2sq(a - b); +} + +//IMPL: NearConcept +/// Test whether two points are no further apart than some threshold. +/// @relates Point +inline bool are_near(Point const &a, Point const &b, double eps = EPSILON) { + // do not use an unqualified calls to distance before the empty + // specialization of iterator_traits is defined - see end of file + return are_near((a - b).length(), 0, eps); +} + +/// Test whether the relative distance between two points is less than some threshold. +inline bool are_near_rel(Point const &a, Point const &b, double eps = EPSILON) { + return (a - b).length() <= eps * (a.length() + b.length()) / 2; +} + +/// Test whether three points lie approximately on the same line. +/// @relates Point +inline bool are_collinear(Point const& p1, Point const& p2, Point const& p3, + double eps = EPSILON) +{ + return are_near( cross(p3, p2) - cross(p3, p1) + cross(p2, p1), 0, eps); +} + +Point unit_vector(Point const &a); +Coord L1(Point const &p); +Coord LInfty(Point const &p); +bool is_zero(Point const &p); +bool is_unit_vector(Point const &p, Coord eps = EPSILON); +double atan2(Point const &p); +double angle_between(Point const &a, Point const &b); +Point abs(Point const &b); +Point constrain_angle(Point const &A, Point const &B, unsigned int n = 4, Geom::Point const &dir = Geom::Point(1,0)); + +} // end namespace Geom + +// This is required to fix a bug in GCC 4.3.3 (and probably others) that causes the compiler +// to try to instantiate the iterator_traits template and fail. Probably it thinks that Point +// is an iterator and tries to use std::distance instead of Geom::distance. +namespace std { +template <> class iterator_traits<Geom::Point> {}; +} + +#endif // LIB2GEOM_SEEN_POINT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/polynomial.h b/include/2geom/polynomial.h new file mode 100644 index 0000000..640cab6 --- /dev/null +++ b/include/2geom/polynomial.h @@ -0,0 +1,264 @@ +/** + * \file + * \brief Polynomial in canonical (monomial) basis + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_POLY_H +#define LIB2GEOM_SEEN_POLY_H +#include <assert.h> +#include <vector> +#include <iostream> +#include <algorithm> +#include <complex> +#include <2geom/coord.h> +#include <2geom/utils.h> + +namespace Geom { + +/** @brief Polynomial in canonical (monomial) basis. + * @ingroup Fragments */ +class Poly : public std::vector<double>{ +public: + // coeff; // sum x^i*coeff[i] + + //unsigned size() const { return coeff.size();} + unsigned degree() const { return size()-1;} + + //double operator[](const int i) const { return (*this)[i];} + //double& operator[](const int i) { return (*this)[i];} + + Poly operator+(const Poly& p) const { + Poly result; + const unsigned out_size = std::max(size(), p.size()); + const unsigned min_size = std::min(size(), p.size()); + result.reserve(out_size); + + for(unsigned i = 0; i < min_size; i++) { + result.push_back((*this)[i] + p[i]); + } + for(unsigned i = min_size; i < size(); i++) + result.push_back((*this)[i]); + for(unsigned i = min_size; i < p.size(); i++) + result.push_back(p[i]); + assert(result.size() == out_size); + return result; + } + Poly operator-(const Poly& p) const { + Poly result; + const unsigned out_size = std::max(size(), p.size()); + const unsigned min_size = std::min(size(), p.size()); + result.reserve(out_size); + + for(unsigned i = 0; i < min_size; i++) { + result.push_back((*this)[i] - p[i]); + } + for(unsigned i = min_size; i < size(); i++) + result.push_back((*this)[i]); + for(unsigned i = min_size; i < p.size(); i++) + result.push_back(-p[i]); + assert(result.size() == out_size); + return result; + } + Poly operator-=(const Poly& p) { + const unsigned out_size = std::max(size(), p.size()); + const unsigned min_size = std::min(size(), p.size()); + resize(out_size); + + for(unsigned i = 0; i < min_size; i++) { + (*this)[i] -= p[i]; + } + for(unsigned i = min_size; i < out_size; i++) + (*this)[i] = -p[i]; + return *this; + } + Poly operator-(const double k) const { + Poly result; + const unsigned out_size = size(); + result.reserve(out_size); + + for(unsigned i = 0; i < out_size; i++) { + result.push_back((*this)[i]); + } + result[0] -= k; + return result; + } + Poly operator-() const { + Poly result; + result.resize(size()); + + for(unsigned i = 0; i < size(); i++) { + result[i] = -(*this)[i]; + } + return result; + } + Poly operator*(const double p) const { + Poly result; + const unsigned out_size = size(); + result.reserve(out_size); + + for(unsigned i = 0; i < out_size; i++) { + result.push_back((*this)[i]*p); + } + assert(result.size() == out_size); + return result; + } + // equivalent to multiply by x^terms, negative terms are disallowed + Poly shifted(unsigned const terms) const { + Poly result; + size_type const out_size = size() + terms; + result.reserve(out_size); + + result.resize(terms, 0.0); + result.insert(result.end(), this->begin(), this->end()); + + assert(result.size() == out_size); + return result; + } + Poly operator*(const Poly& p) const; + + template <typename T> + T eval(T x) const { + T r = 0; + for(int k = size()-1; k >= 0; k--) { + r = r*x + T((*this)[k]); + } + return r; + } + + template <typename T> + T operator()(T t) const { return (T)eval(t);} + + void normalize(); + + void monicify(); + Poly() {} + Poly(const Poly& p) : std::vector<double>(p) {} + Poly(const double a) {push_back(a);} + +public: + template <class T, class U> + void val_and_deriv(T x, U &pd) const { + pd[0] = back(); + int nc = size() - 1; + int nd = pd.size() - 1; + for(unsigned j = 1; j < pd.size(); j++) + pd[j] = 0.0; + for(int i = nc -1; i >= 0; i--) { + int nnd = std::min(nd, nc-i); + for(int j = nnd; j >= 1; j--) + pd[j] = pd[j]*x + operator[](i); + pd[0] = pd[0]*x + operator[](i); + } + double cnst = 1; + for(int i = 2; i <= nd; i++) { + cnst *= i; + pd[i] *= cnst; + } + } + + static Poly linear(double ax, double b) { + Poly p; + p.push_back(b); + p.push_back(ax); + return p; + } +}; + +inline Poly operator*(double a, Poly const & b) { return b * a;} + +Poly integral(Poly const & p); +Poly derivative(Poly const & p); +Poly divide_out_root(Poly const & p, double x); +Poly compose(Poly const & a, Poly const & b); +Poly divide(Poly const &a, Poly const &b, Poly &r); +Poly gcd(Poly const &a, Poly const &b, const double tol=1e-10); + +/*** solve(Poly p) + * find all p.degree() roots of p. + * This function can take a long time with suitably crafted polynomials, but in practice it should be fast. Should we provide special forms for degree() <= 4? + */ +std::vector<std::complex<double> > solve(const Poly & p); + +#ifdef HAVE_GSL +/*** solve_reals(Poly p) + * find all real solutions to Poly p. + * currently we just use solve and pick out the suitably real looking values, there may be a better algorithm. + */ +std::vector<double> solve_reals(const Poly & p); +#endif +double polish_root(Poly const & p, double guess, double tol); + + +/** @brief Analytically solve quadratic equation. + * The equation is given in the standard form: ax^2 + bx + c = 0. + * Only real roots are returned. */ +std::vector<Coord> solve_quadratic(Coord a, Coord b, Coord c); + +/** @brief Analytically solve cubic equation. + * The equation is given in the standard form: ax^3 + bx^2 + cx + d = 0. + * Only real roots are returned. */ +std::vector<Coord> solve_cubic(Coord a, Coord b, Coord c, Coord d); + + +inline std::ostream &operator<< (std::ostream &out_file, const Poly &in_poly) { + if(in_poly.size() == 0) + out_file << "0"; + else { + for(int i = (int)in_poly.size()-1; i >= 0; --i) { + if(i == 1) { + out_file << "" << in_poly[i] << "*x"; + out_file << " + "; + } else if(i) { + out_file << "" << in_poly[i] << "*x^" << i; + out_file << " + "; + } else + out_file << in_poly[i]; + + } + } + return out_file; +} + +} // namespace Geom + +#endif //LIB2GEOM_SEEN_POLY_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/ray.h b/include/2geom/ray.h new file mode 100644 index 0000000..4e60fd8 --- /dev/null +++ b/include/2geom/ray.h @@ -0,0 +1,192 @@ +/** + * \file + * \brief Infinite straight ray + *//* + * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_RAY_H +#define LIB2GEOM_SEEN_RAY_H + +#include <vector> +#include <2geom/point.h> +#include <2geom/bezier-curve.h> // for LineSegment +#include <2geom/exception.h> +#include <2geom/math-utils.h> +#include <2geom/transforms.h> +#include <2geom/angle.h> + +namespace Geom +{ + +/** + * @brief Straight ray from a specific point to infinity. + * + * Rays are "half-lines" - they begin at some specific point and extend in a straight line + * to infinity. + * + * @ingroup Primitives + */ +class Ray { +private: + Point _origin; + Point _vector; + +public: + Ray() : _origin(0,0), _vector(1,0) {} + Ray(Point const& origin, Coord angle) + : _origin(origin) + { + sincos(angle, _vector[Y], _vector[X]); + } + Ray(Point const& A, Point const& B) { + setPoints(A, B); + } + Point origin() const { return _origin; } + Point vector() const { return _vector; } + Point versor() const { return _vector.normalized(); } + void setOrigin(Point const &o) { _origin = o; } + void setVector(Point const& v) { _vector = v; } + Coord angle() const { return std::atan2(_vector[Y], _vector[X]); } + void setAngle(Coord a) { sincos(a, _vector[Y], _vector[X]); } + void setPoints(Point const &a, Point const &b) { + _origin = a; + _vector = b - a; + if (are_near(_vector, Point(0,0)) ) + _vector = Point(0,0); + else + _vector.normalize(); + } + bool isDegenerate() const { + return ( _vector[X] == 0 && _vector[Y] == 0 ); + } + Point pointAt(Coord t) const { + return _origin + _vector * t; + } + Coord valueAt(Coord t, Dim2 d) const { + return _origin[d] + _vector[d] * t; + } + std::vector<Coord> roots(Coord v, Dim2 d) const { + std::vector<Coord> result; + if ( _vector[d] != 0 ) { + double t = (v - _origin[d]) / _vector[d]; + if (t >= 0) result.push_back(t); + } else if (_vector[(d+1)%2] == v) { + THROW_INFINITESOLUTIONS(); + } + return result; + } + Coord nearestTime(Point const& point) const { + if ( isDegenerate() ) return 0; + double t = dot(point - _origin, _vector); + if (t < 0) t = 0; + return t; + } + Ray reverse() const { + Ray result; + result.setOrigin(_origin); + result.setVector(-_vector); + return result; + } + Curve *portion(Coord f, Coord t) const { + return new LineSegment(pointAt(f), pointAt(t)); + } + LineSegment segment(Coord f, Coord t) const { + return LineSegment(pointAt(f), pointAt(t)); + } + Ray transformed(Affine const& m) const { + return Ray(_origin * m, (_origin + _vector) * m); + } +}; // end class Ray + +inline +double distance(Point const& _point, Ray const& _ray) { + double t = _ray.nearestTime(_point); + return ::Geom::distance(_point, _ray.pointAt(t)); +} + +inline +bool are_near(Point const& _point, Ray const& _ray, double eps = EPSILON) { + return are_near(distance(_point, _ray), 0, eps); +} + +inline +bool are_same(Ray const& r1, Ray const& r2, double eps = EPSILON) { + return are_near(r1.vector(), r2.vector(), eps) + && are_near(r1.origin(), r2.origin(), eps); +} + +// evaluate the angle between r1 and r2 rotating r1 in cw or ccw direction on r2 +// the returned value is an angle in the interval [0, 2PI[ +inline +double angle_between(Ray const& r1, Ray const& r2, bool cw = true) { + double angle = angle_between(r1.vector(), r2.vector()); + if (angle < 0) angle += 2*M_PI; + if (!cw) angle = 2*M_PI - angle; + return angle; +} + +/** + * @brief Returns the angle bisector for the two given rays. + * + * @a r1 is rotated half the way to @a r2 in either clockwise or counter-clockwise direction. + * + * @pre Both passed rays must have the same origin. + * + * @remarks If the versors of both given rays point in the same direction, the direction of the + * angle bisector ray depends on the third parameter: + * - If @a cw is set to @c true, the returned ray will equal the passed rays @a r1 and @a r2. + * - If @a cw is set to @c false, the returned ray will go in the opposite direction. + * + * @throws RangeError if the given rays do not have the same origins + */ +inline +Ray make_angle_bisector_ray(Ray const& r1, Ray const& r2, bool cw = true) +{ + if ( !are_near(r1.origin(), r2.origin()) ) + { + THROW_RANGEERROR("passed rays do not have the same origin"); + } + + Ray bisector(r1.origin(), r1.origin() + r1.vector() * Rotate(angle_between(r1, r2) / 2.0)); + + return (cw ? bisector : bisector.reverse()); +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_RAY_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/rect.h b/include/2geom/rect.h new file mode 100644 index 0000000..5edfc95 --- /dev/null +++ b/include/2geom/rect.h @@ -0,0 +1,263 @@ +/** + * \file + * \brief Axis-aligned rectangle + *//* + * Authors: + * Michael Sloan <mgsloan@gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Copyright 2007-2011 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, output to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * Authors of original rect class: + * Lauris Kaplinski <lauris@kaplinski.com> + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * bulia byak <buliabyak@users.sf.net> + * MenTaLguY <mental@rydia.net> + */ + +#ifndef LIB2GEOM_SEEN_RECT_H +#define LIB2GEOM_SEEN_RECT_H + +#include <2geom/affine.h> +#include <2geom/interval.h> +#include <2geom/int-rect.h> + +namespace Geom { + +/** Values for the <align> parameter of preserveAspectRatio. + * See: http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute */ +enum Align { + ALIGN_NONE, + ALIGN_XMIN_YMIN, + ALIGN_XMID_YMIN, + ALIGN_XMAX_YMIN, + ALIGN_XMIN_YMID, + ALIGN_XMID_YMID, + ALIGN_XMAX_YMID, + ALIGN_XMIN_YMAX, + ALIGN_XMID_YMAX, + ALIGN_XMAX_YMAX +}; + +/** Values for the <meetOrSlice> parameter of preserveAspectRatio. + * See: http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute */ +enum Expansion { + EXPANSION_MEET, + EXPANSION_SLICE +}; + +/// Convert an align specification to coordinate fractions. +Point align_factors(Align align); + +/** @brief Structure that specifies placement of within a viewport. + * Use this to create transformations that preserve aspect. */ +struct Aspect { + Align align; + Expansion expansion; + bool deferred; ///< for SVG compatibility + + Aspect(Align a = ALIGN_NONE, Expansion ex = EXPANSION_MEET) + : align(a), expansion(ex), deferred(false) + {} +}; + +/** + * @brief Axis aligned, non-empty rectangle. + * @ingroup Primitives + */ +class Rect + : public GenericRect<Coord> +{ + typedef GenericRect<Coord> Base; +public: + /// @name Create rectangles. + /// @{ + /** @brief Create a rectangle that contains only the point at (0,0). */ + Rect() {} + /** @brief Create a rectangle from X and Y intervals. */ + Rect(Interval const &a, Interval const &b) : Base(a,b) {} + /** @brief Create a rectangle from two points. */ + Rect(Point const &a, Point const &b) : Base(a,b) {} + Rect(Coord x0, Coord y0, Coord x1, Coord y1) : Base(x0, y0, x1, y1) {} + Rect(Base const &b) : Base(b) {} + Rect(IntRect const &ir) : Base(ir.min(), ir.max()) {} + /// @} + + /// @name Inspect dimensions. + /// @{ + /** @brief Check whether the rectangle has zero area up to specified tolerance. + * @param eps Maximum value of the area to consider empty + * @return True if rectangle has an area smaller than tolerance, false otherwise */ + bool hasZeroArea(Coord eps = EPSILON) const { return (area() <= eps); } + /// Check whether the rectangle has finite area + bool isFinite() const { return (*this)[X].isFinite() && (*this)[Y].isFinite(); } + /// Calculate the diameter of the smallest circle that would contain the rectangle. + Coord diameter() const { return distance(corner(0), corner(2)); } + /// @} + + /// @name Test other rectangles and points for inclusion. + /// @{ + /** @brief Check whether the interiors of the rectangles have any common points. */ + bool interiorIntersects(Rect const &r) const { + return f[X].interiorIntersects(r[X]) && f[Y].interiorIntersects(r[Y]); + } + /** @brief Check whether the interior includes the given point. */ + bool interiorContains(Point const &p) const { + return f[X].interiorContains(p[X]) && f[Y].interiorContains(p[Y]); + } + /** @brief Check whether the interior includes all points in the given rectangle. + * Interior of the rectangle is the entire rectangle without its borders. */ + bool interiorContains(Rect const &r) const { + return f[X].interiorContains(r[X]) && f[Y].interiorContains(r[Y]); + } + inline bool interiorContains(OptRect const &r) const; + /// @} + + /// @name Rounding to integer coordinates + /// @{ + /** @brief Return the smallest integer rectangle which contains this one. */ + IntRect roundOutwards() const { + IntRect ir(f[X].roundOutwards(), f[Y].roundOutwards()); + return ir; + } + /** @brief Return the largest integer rectangle which is contained in this one. */ + OptIntRect roundInwards() const { + OptIntRect oir(f[X].roundInwards(), f[Y].roundInwards()); + return oir; + } + /// @} + + /// @name SVG viewbox functionality. + /// @{ + /** @brief Transform contents to viewport. + * Computes an affine that transforms the contents of this rectangle + * to the specified viewport. The aspect parameter specifies how to + * to the transformation (whether the aspect ratio of content + * should be kept and where it should be placed in the viewport). */ + Affine transformTo(Rect const &viewport, Aspect const &aspect = Aspect()) const; + /// @} + + /// @name Operators + /// @{ + Rect &operator*=(Affine const &m); + bool operator==(IntRect const &ir) const { + return f[X] == ir[X] && f[Y] == ir[Y]; + } + bool operator==(Rect const &other) const { + return Base::operator==(other); + } + /// @} +}; + +/** + * @brief Axis-aligned rectangle that can be empty. + * @ingroup Primitives + */ +class OptRect + : public GenericOptRect<Coord> +{ + typedef GenericOptRect<Coord> Base; +public: + OptRect() : Base() {} + OptRect(Rect const &a) : Base(a) {} + OptRect(Point const &a, Point const &b) : Base(a, b) {} + OptRect(Coord x0, Coord y0, Coord x1, Coord y1) : Base(x0, y0, x1, y1) {} + OptRect(OptInterval const &x_int, OptInterval const &y_int) : Base(x_int, y_int) {} + OptRect(Base const &b) : Base(b) {} + + OptRect(IntRect const &r) : Base(Rect(r)) {} + OptRect(OptIntRect const &r) : Base() { + if (r) *this = Rect(*r); + } + + Affine transformTo(Rect const &viewport, Aspect const &aspect = Aspect()) { + Affine ret = Affine::identity(); + if (empty()) return ret; + ret = (*this)->transformTo(viewport, aspect); + return ret; + } + + bool operator==(OptRect const &other) const { + return Base::operator==(other); + } + bool operator==(Rect const &other) const { + return Base::operator==(other); + } +}; + +Coord distanceSq(Point const &p, Rect const &rect); +Coord distance(Point const &p, Rect const &rect); +/// Minimum square of distance to rectangle, or infinity if empty. +Coord distanceSq(Point const &p, OptRect const &rect); +/// Minimum distance to rectangle, or infinity if empty. +Coord distance(Point const &p, OptRect const &rect); + +inline bool Rect::interiorContains(OptRect const &r) const { + return !r || interiorContains(static_cast<Rect const &>(*r)); +} + +// the functions below do not work when defined generically +inline OptRect operator&(Rect const &a, Rect const &b) { + OptRect ret(a); + ret.intersectWith(b); + return ret; +} +inline OptRect intersect(Rect const &a, Rect const &b) { + return a & b; +} +inline OptRect intersect(OptRect const &a, OptRect const &b) { + return a & b; +} +inline Rect unify(Rect const &a, Rect const &b) { + return a | b; +} +inline OptRect unify(OptRect const &a, OptRect const &b) { + return a | b; +} + +/** @brief Union a list of rectangles + * @deprecated Use OptRect::from_range instead */ +inline Rect union_list(std::vector<Rect> const &r) { + if(r.empty()) return Rect(Interval(0,0), Interval(0,0)); + Rect ret = r[0]; + for(unsigned i = 1; i < r.size(); i++) + ret.unionWith(r[i]); + return ret; +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_RECT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/sbasis-2d.h b/include/2geom/sbasis-2d.h new file mode 100644 index 0000000..98dec67 --- /dev/null +++ b/include/2geom/sbasis-2d.h @@ -0,0 +1,371 @@ +/** + * \file + * \brief Obsolete 2D SBasis function class + *//* + * Authors: + * Nathan Hurst <?@?.?> + * JFBarraud <?@?.?> + * + * Copyright 2006-2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_SBASIS_2D_H +#define LIB2GEOM_SEEN_SBASIS_2D_H +#include <vector> +#include <cassert> +#include <algorithm> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <iostream> + +namespace Geom{ + +class Linear2d{ +public: + /* + u 0,1 + v 0,2 + */ + double a[4]; + Linear2d() { + a[0] = 0; + a[1] = 0; + a[2] = 0; + a[3] = 0; + } + Linear2d(double aa) { + for(double & i : a) + i = aa; + } + Linear2d(double a00, double a01, double a10, double a11) + { + a[0] = a00; + a[1] = a01; + a[2] = a10; + a[3] = a11; + } + + double operator[](const int i) const { + assert(i >= 0); + assert(i < 4); + return a[i]; + } + double& operator[](const int i) { + assert(i >= 0); + assert(i < 4); + return a[i]; + } + double apply(double u, double v) { + return (a[0]*(1-u)*(1-v) + + a[1]*u*(1-v) + + a[2]*(1-u)*v + + a[3]*u*v); + } +}; + +inline Linear extract_u(Linear2d const &a, double u) { + return Linear(a[0]*(1-u) + + a[1]*u, + a[2]*(1-u) + + a[3]*u); +} +inline Linear extract_v(Linear2d const &a, double v) { + return Linear(a[0]*(1-v) + + a[2]*v, + a[1]*(1-v) + + a[3]*v); +} +inline Linear2d operator-(Linear2d const &a) { + return Linear2d(-a.a[0], -a.a[1], + -a.a[2], -a.a[3]); +} +inline Linear2d operator+(Linear2d const & a, Linear2d const & b) { + return Linear2d(a[0] + b[0], + a[1] + b[1], + a[2] + b[2], + a[3] + b[3]); +} +inline Linear2d operator-(Linear2d const & a, Linear2d const & b) { + return Linear2d(a[0] - b[0], + a[1] - b[1], + a[2] - b[2], + a[3] - b[3]); +} +inline Linear2d& operator+=(Linear2d & a, Linear2d const & b) { + for(unsigned i = 0; i < 4; i++) + a[i] += b[i]; + return a; +} +inline Linear2d& operator-=(Linear2d & a, Linear2d const & b) { + for(unsigned i = 0; i < 4; i++) + a[i] -= b[i]; + return a; +} +inline Linear2d& operator*=(Linear2d & a, double b) { + for(unsigned i = 0; i < 4; i++) + a[i] *= b; + return a; +} + +inline bool operator==(Linear2d const & a, Linear2d const & b) { + for(unsigned i = 0; i < 4; i++) + if(a[i] != b[i]) + return false; + return true; +} +inline bool operator!=(Linear2d const & a, Linear2d const & b) { + for(unsigned i = 0; i < 4; i++) + if(a[i] == b[i]) + return false; + return true; +} +inline Linear2d operator*(double const a, Linear2d const & b) { + return Linear2d(a*b[0], a*b[1], + a*b[2], a*b[3]); +} + +class SBasis2d : public std::vector<Linear2d>{ +public: + // vector in u,v + unsigned us, vs; // number of u terms, v terms + SBasis2d() {} + SBasis2d(Linear2d const & bo) + : us(1), vs(1) { + push_back(bo); + } + SBasis2d(SBasis2d const & a) + : std::vector<Linear2d>(a), us(a.us), vs(a.vs) {} + + Linear2d& index(unsigned ui, unsigned vi) { + assert(ui < us); + assert(vi < vs); + return (*this)[ui + vi*us]; + } + + Linear2d index(unsigned ui, unsigned vi) const { + if(ui >= us) + return Linear2d(0); + if(vi >= vs) + return Linear2d(0); + return (*this)[ui + vi*us]; + } + + double apply(double u, double v) const { + double s = u*(1-u); + double t = v*(1-v); + Linear2d p; + double tk = 1; +// XXX rewrite as horner + for(unsigned vi = 0; vi < vs; vi++) { + double sk = 1; + for(unsigned ui = 0; ui < us; ui++) { + p += (sk*tk)*index(ui, vi); + sk *= s; + } + tk *= t; + } + return p.apply(u,v); + } + + void clear() { + fill(begin(), end(), Linear2d(0)); + } + + void normalize(); // remove extra zeros + + double tail_error(unsigned tail) const; + + void truncate(unsigned k); +}; + +inline SBasis2d operator-(const SBasis2d& p) { + SBasis2d result; + result.reserve(p.size()); + + for(unsigned i = 0; i < p.size(); i++) { + result.push_back(-p[i]); + } + return result; +} + +inline SBasis2d operator+(const SBasis2d& a, const SBasis2d& b) { + SBasis2d result; + result.us = std::max(a.us, b.us); + result.vs = std::max(a.vs, b.vs); + const unsigned out_size = result.us*result.vs; + result.resize(out_size); + + for(unsigned vi = 0; vi < result.vs; vi++) { + for(unsigned ui = 0; ui < result.us; ui++) { + Linear2d bo; + if(ui < a.us && vi < a.vs) + bo += a.index(ui, vi); + if(ui < b.us && vi < b.vs) + bo += b.index(ui, vi); + result.index(ui, vi) = bo; + } + } + return result; +} + +inline SBasis2d operator-(const SBasis2d& a, const SBasis2d& b) { + SBasis2d result; + result.us = std::max(a.us, b.us); + result.vs = std::max(a.vs, b.vs); + const unsigned out_size = result.us*result.vs; + result.resize(out_size); + + for(unsigned vi = 0; vi < result.vs; vi++) { + for(unsigned ui = 0; ui < result.us; ui++) { + Linear2d bo; + if(ui < a.us && vi < a.vs) + bo += a.index(ui, vi); + if(ui < b.us && vi < b.vs) + bo -= b.index(ui, vi); + result.index(ui, vi) = bo; + } + } + return result; +} + + +inline SBasis2d& operator+=(SBasis2d& a, const Linear2d& b) { + if(a.size() < 1) + a.push_back(b); + else + a[0] += b; + return a; +} + +inline SBasis2d& operator-=(SBasis2d& a, const Linear2d& b) { + if(a.size() < 1) + a.push_back(-b); + else + a[0] -= b; + return a; +} + +inline SBasis2d& operator+=(SBasis2d& a, double b) { + if(a.size() < 1) + a.push_back(Linear2d(b)); + else { + for(unsigned i = 0; i < 4; i++) + a[0] += double(b); + } + return a; +} + +inline SBasis2d& operator-=(SBasis2d& a, double b) { + if(a.size() < 1) + a.push_back(Linear2d(-b)); + else { + a[0] -= b; + } + return a; +} + +inline SBasis2d& operator*=(SBasis2d& a, double b) { + for(unsigned i = 0; i < a.size(); i++) + a[i] *= b; + return a; +} + +inline SBasis2d& operator/=(SBasis2d& a, double b) { + for(unsigned i = 0; i < a.size(); i++) + a[i] *= (1./b); + return a; +} + +SBasis2d operator*(double k, SBasis2d const &a); +SBasis2d operator*(SBasis2d const &a, SBasis2d const &b); + +SBasis2d shift(SBasis2d const &a, int sh); + +SBasis2d shift(Linear2d const &a, int sh); + +SBasis2d truncate(SBasis2d const &a, unsigned terms); + +SBasis2d multiply(SBasis2d const &a, SBasis2d const &b); + +SBasis2d integral(SBasis2d const &c); + +SBasis2d partial_derivative(SBasis2d const &a, int dim); + +SBasis2d sqrt(SBasis2d const &a, int k); + +// return a kth order approx to 1/a) +SBasis2d reciprocal(Linear2d const &a, int k); + +SBasis2d divide(SBasis2d const &a, SBasis2d const &b, int k); + +// a(b(t)) +SBasis2d compose(SBasis2d const &a, SBasis2d const &b); +SBasis2d compose(SBasis2d const &a, SBasis2d const &b, unsigned k); +SBasis2d inverse(SBasis2d const &a, int k); + +// these two should probably be replaced with compose +SBasis extract_u(SBasis2d const &a, double u); +SBasis extract_v(SBasis2d const &a, double v); + +SBasis compose(Linear2d const &a, D2<SBasis> const &p); + +SBasis compose(SBasis2d const &fg, D2<SBasis> const &p); + +D2<SBasis> compose_each(D2<SBasis2d> const &fg, D2<SBasis> const &p); + +inline std::ostream &operator<< (std::ostream &out_file, const Linear2d &bo) { + out_file << "{" << bo[0] << ", " << bo[1] << "}, "; + out_file << "{" << bo[2] << ", " << bo[3] << "}"; + return out_file; +} + +inline std::ostream &operator<< (std::ostream &out_file, const SBasis2d & p) { + for(unsigned i = 0; i < p.size(); i++) { + out_file << p[i] << "s^" << i << " + "; + } + return out_file; +} + +D2<SBasis> +sb2dsolve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B, unsigned degmax=2); + +D2<SBasis> +sb2d_cubic_solve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B); + +} // end namespace Geom + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/sbasis-curve.h b/include/2geom/sbasis-curve.h new file mode 100644 index 0000000..93d6772 --- /dev/null +++ b/include/2geom/sbasis-curve.h @@ -0,0 +1,160 @@ +/** + * \file + * \brief Symmetric power basis curve + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2009 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_SBASIS_CURVE_H +#define LIB2GEOM_SEEN_SBASIS_CURVE_H + +#include <2geom/curve.h> +#include <2geom/exception.h> +#include <2geom/nearest-time.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/transforms.h> + +namespace Geom +{ + +/** @brief Symmetric power basis curve. + * + * Symmetric power basis (S-basis for short) polynomials are a versatile numeric + * representation of arbitrary continuous curves. They are the main representation of curves + * in 2Geom. + * + * S-basis is defined for odd degrees and composed of the following polynomials: + * \f{align*}{ + P_k^0(t) &= t^k (1-t)^{k+1} \\ + P_k^1(t) &= t^{k+1} (1-t)^k \f} + * This can be understood more easily with the help of the chart below. Each square + * represents a product of a specific number of \f$t\f$ and \f$(1-t)\f$ terms. Red dots + * are the canonical (monomial) basis, the green dots are the Bezier basis, and the blue + * dots are the S-basis, all of them of degree 7. + * + * @image html sbasis.png "Illustration of the monomial, Bezier and symmetric power bases" + * + * The S-Basis has several important properties: + * - S-basis polynomials are closed under multiplication. + * - Evaluation is fast, using a modified Horner scheme. + * - Degree change is as trivial as in the monomial basis. To elevate, just add extra + * zero coefficients. To reduce the degree, truncate the terms in the highest powers. + * Compare this with Bezier curves, where degree change is complicated. + * - Conversion between S-basis and Bezier basis is numerically stable. + * + * More in-depth information can be found in the following paper: + * J Sanchez-Reyes, "The symmetric analogue of the polynomial power basis". + * ACM Transactions on Graphics, Vol. 16, No. 3, July 1997, pages 319--357. + * http://portal.acm.org/citation.cfm?id=256162 + * + * @ingroup Curves + */ +class SBasisCurve : public Curve { +private: + D2<SBasis> inner; + +public: + explicit SBasisCurve(D2<SBasis> const &sb) : inner(sb) {} + explicit SBasisCurve(Curve const &other) : inner(other.toSBasis()) {} + + Curve *duplicate() const override { return new SBasisCurve(*this); } + Point initialPoint() const override { return inner.at0(); } + Point finalPoint() const override { return inner.at1(); } + bool isDegenerate() const override { return inner.isConstant(0); } + bool isLineSegment() const override { return inner[X].size() == 1; } + Point pointAt(Coord t) const override { return inner.valueAt(t); } + std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const override { + return inner.valueAndDerivatives(t, n); + } + Coord valueAt(Coord t, Dim2 d) const override { return inner[d].valueAt(t); } + void setInitial(Point const &v) override { + for (unsigned d = 0; d < 2; d++) { inner[d][0][0] = v[d]; } + } + void setFinal(Point const &v) override { + for (unsigned d = 0; d < 2; d++) { inner[d][0][1] = v[d]; } + } + Rect boundsFast() const override { return *bounds_fast(inner); } + Rect boundsExact() const override { return *bounds_exact(inner); } + void expandToTransformed(Rect &bbox, Affine const &transform) const override { + bbox |= bounds_exact(inner * transform); + } + OptRect boundsLocal(OptInterval const &i, unsigned deg) const override { + return bounds_local(inner, i, deg); + } + std::vector<Coord> roots(Coord v, Dim2 d) const override { return Geom::roots(inner[d] - v); } + Coord nearestTime( Point const& p, Coord from = 0, Coord to = 1 ) const override { + return nearest_time(p, inner, from, to); + } + std::vector<Coord> allNearestTimes( Point const& p, Coord from = 0, + Coord to = 1 ) const override + { + return all_nearest_times(p, inner, from, to); + } + Coord length(Coord tolerance) const override { return ::Geom::length(inner, tolerance); } + Curve *portion(Coord f, Coord t) const override { + return new SBasisCurve(Geom::portion(inner, f, t)); + } + + using Curve::operator*=; + void operator*=(Affine const &m) override { inner = inner * m; } + + Curve *derivative() const override { + return new SBasisCurve(Geom::derivative(inner)); + } + D2<SBasis> toSBasis() const override { return inner; } + bool operator==(Curve const &c) const override { + SBasisCurve const *other = dynamic_cast<SBasisCurve const *>(&c); + if (!other) return false; + return inner == other->inner; + } + bool isNear(Curve const &/*c*/, Coord /*eps*/) const override { + THROW_NOTIMPLEMENTED(); + return false; + } + int degreesOfFreedom() const override { + return inner[0].degreesOfFreedom() + inner[1].degreesOfFreedom(); + } +}; + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_SBASIS_CURVE_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/sbasis-geometric.h b/include/2geom/sbasis-geometric.h new file mode 100644 index 0000000..7f1e8aa --- /dev/null +++ b/include/2geom/sbasis-geometric.h @@ -0,0 +1,146 @@ +/** + * \file + * \brief two-dimensional geometric operators. + * + * These operators are built on a more 'polynomially robust' + * transformation to map a function that takes a [0,1] parameter to a + * 2d vector into a function that takes the same [0,1] parameter to a + * unit vector with the same direction. + * + * Rather that using (X/sqrt(X))(t) which involves two unstable + * operations, sqrt and divide, this approach forms a curve directly + * from the various tangent directions at each end (angular jet). As + * a result, the final path has a convergence behaviour derived from + * that of the sin and cos series. -- njh + *//* + * Copyright 2007, JFBarraud + * Copyright 2007, njh + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_SBASIS_GEOMETRIC_H +#define LIB2GEOM_SEEN_SBASIS_GEOMETRIC_H + +#include <2geom/d2.h> +#include <2geom/piecewise.h> +#include <vector> + +namespace Geom { + +Piecewise<D2<SBasis> > +cutAtRoots(Piecewise<D2<SBasis> > const &M, double tol=1e-4); + +Piecewise<SBasis> +atan2(D2<SBasis> const &vect, + double tol=.01, unsigned order=3); + +Piecewise<SBasis> +atan2(Piecewise<D2<SBasis> >const &vect, + double tol=.01, unsigned order=3); + +D2<Piecewise<SBasis> > +tan2(SBasis const &angle, + double tol=.01, unsigned order=3); + +D2<Piecewise<SBasis> > +tan2(Piecewise<SBasis> const &angle, + double tol=.01, unsigned order=3); + +Piecewise<D2<SBasis> > +unitVector(D2<SBasis> const &vect, + double tol=.01, unsigned order=3); +Piecewise<D2<SBasis> > +unitVector(Piecewise<D2<SBasis> > const &vect, + double tol=.01, unsigned order=3); + +// Piecewise<D2<SBasis> > +// uniform_speed(D2<SBasis> const M, +// double tol=.1); + +Piecewise<SBasis> curvature( D2<SBasis> const &M, double tol=.01); +Piecewise<SBasis> curvature(Piecewise<D2<SBasis> > const &M, double tol=.01); + +Piecewise<SBasis> arcLengthSb( D2<SBasis> const &M, double tol=.01); +Piecewise<SBasis> arcLengthSb(Piecewise<D2<SBasis> > const &M, double tol=.01); + +double length( D2<SBasis> const &M, double tol=.01); +double length(Piecewise<D2<SBasis> > const &M, double tol=.01); + +void length_integrating(D2<SBasis> const &B, double &result, double &abs_error, double tol); + +Piecewise<D2<SBasis> > +arc_length_parametrization(D2<SBasis> const &M, + unsigned order=3, + double tol=.01); +Piecewise<D2<SBasis> > +arc_length_parametrization(Piecewise<D2<SBasis> > const &M, + unsigned order=3, + double tol=.01); + + +unsigned centroid(Piecewise<D2<SBasis> > const &p, Point& centroid, double &area); + +std::vector<D2<SBasis> > +cubics_fitting_curvature(Point const &M0, Point const &M1, + Point const &dM0, Point const &dM1, + double d2M0xdM0, double d2M1xdM1, + int insist_on_speed_signs = 1, + double epsilon = 1e-5); + +std::vector<D2<SBasis> > +cubics_fitting_curvature(Point const &M0, Point const &M1, + Point const &dM0, Point const &dM1, + Point const &d2M0, Point const &d2M1, + int insist_on_speed_signs = 1, + double epsilon = 1e-5); + +std::vector<D2<SBasis> > +cubics_with_prescribed_curvature(Point const &M0, Point const &M1, + Point const &dM0, Point const &dM1, + double k0, double k1, + int insist_on_speed_signs = 1, + double error = 1e-5); + + +std::vector<double> find_tangents(Point P, D2<SBasis> const &A); +std::vector<double> find_tangents_by_vector(Point V, D2<SBasis> const &A); +std::vector<double> find_normals(Point P, D2<SBasis> const &A); +std::vector<double> find_normals_by_vector(Point V, D2<SBasis> const &A); + +}; + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + diff --git a/include/2geom/sbasis-math.h b/include/2geom/sbasis-math.h new file mode 100644 index 0000000..e191dae --- /dev/null +++ b/include/2geom/sbasis-math.h @@ -0,0 +1,99 @@ +/** @file + * @brief some std functions to work with (pw)s-basis + *//* + * Authors: + * Jean-Francois Barraud + * + * Copyright (C) 2006-2007 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +//this a first try to define sqrt, cos, sin, etc... +//TODO: define a truncated compose(sb,sb, order) and extend it to pw<sb>. +//TODO: in all these functions, compute 'order' according to 'tol'. +//TODO: use template to define the pw version automatically from the sb version? + +#ifndef LIB2GEOM_SEEN_SBASIS_MATH_H +#define LIB2GEOM_SEEN_SBASIS_MATH_H + + +#include <2geom/sbasis.h> +#include <2geom/piecewise.h> +#include <2geom/d2.h> + +namespace Geom{ +//-|x|--------------------------------------------------------------- +Piecewise<SBasis> abs( SBasis const &f); +Piecewise<SBasis> abs(Piecewise<SBasis>const &f); + +//- max(f,g), min(f,g) ---------------------------------------------- +Piecewise<SBasis> max( SBasis const &f, SBasis const &g); +Piecewise<SBasis> max(Piecewise<SBasis> const &f, SBasis const &g); +Piecewise<SBasis> max( SBasis const &f, Piecewise<SBasis> const &g); +Piecewise<SBasis> max(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g); +Piecewise<SBasis> min( SBasis const &f, SBasis const &g); +Piecewise<SBasis> min(Piecewise<SBasis> const &f, SBasis const &g); +Piecewise<SBasis> min( SBasis const &f, Piecewise<SBasis> const &g); +Piecewise<SBasis> min(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g); + +//-sign(x)--------------------------------------------------------------- +Piecewise<SBasis> signSb( SBasis const &f); +Piecewise<SBasis> signSb(Piecewise<SBasis>const &f); + +//-Sqrt--------------------------------------------------------------- +Piecewise<SBasis> sqrt( SBasis const &f, double tol=1e-3, int order=3); +Piecewise<SBasis> sqrt(Piecewise<SBasis>const &f, double tol=1e-3, int order=3); + +//-sin/cos-------------------------------------------------------------- +Piecewise<SBasis> cos( SBasis const &f, double tol=1e-3, int order=3); +Piecewise<SBasis> cos(Piecewise<SBasis> const &f, double tol=1e-3, int order=3); +Piecewise<SBasis> sin( SBasis const &f, double tol=1e-3, int order=3); +Piecewise<SBasis> sin(Piecewise<SBasis> const &f, double tol=1e-3, int order=3); +//-Log--------------------------------------------------------------- +Piecewise<SBasis> log( SBasis const &f, double tol=1e-3, int order=3); +Piecewise<SBasis> log(Piecewise<SBasis>const &f, double tol=1e-3, int order=3); + +//--1/x------------------------------------------------------------ +//TODO: change this... +Piecewise<SBasis> reciprocalOnDomain(Interval range, double tol=1e-3); +Piecewise<SBasis> reciprocal( SBasis const &f, double tol=1e-3, int order=3); +Piecewise<SBasis> reciprocal(Piecewise<SBasis>const &f, double tol=1e-3, int order=3); + +//--interpolate------------------------------------------------------------ +Piecewise<SBasis> interpolate( std::vector<double> times, std::vector<double> values, unsigned smoothness = 1); +} + +#endif //SEEN_GEOM_PW_SB_CALCULUS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/sbasis-poly.h b/include/2geom/sbasis-poly.h new file mode 100644 index 0000000..d18bc36 --- /dev/null +++ b/include/2geom/sbasis-poly.h @@ -0,0 +1,56 @@ +/** @file + * @brief Conversion between SBasis and Poly. Not recommended for general use due to instability. + *//* + * Authors: + * ? <?@?.?> + * + * Copyright ?-? authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_SBASIS_POLY_H +#define LIB2GEOM_SEEN_SBASIS_POLY_H + +#include <2geom/polynomial.h> +#include <2geom/sbasis.h> + +namespace Geom{ + +SBasis poly_to_sbasis(Poly const & p); +Poly sbasis_to_poly(SBasis const & s); + +}; + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/sbasis-to-bezier.h b/include/2geom/sbasis-to-bezier.h new file mode 100644 index 0000000..eadb47b --- /dev/null +++ b/include/2geom/sbasis-to-bezier.h @@ -0,0 +1,87 @@ +/** + * \file + * \brief Conversion between SBasis and Bezier basis polynomials + *//* + * Authors: + * ? <?@?.?> + * + * Copyright ?-? authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_SBASIS_TO_BEZIER_H +#define LIB2GEOM_SEEN_SBASIS_TO_BEZIER_H + +#include <2geom/d2.h> +#include <2geom/pathvector.h> + +#include <vector> + +namespace Geom { + +class PathBuilder; + +void sbasis_to_bezier (Bezier &bz, SBasis const &sb, size_t sz = 0); +void sbasis_to_bezier (D2<Bezier> &bz, D2<SBasis> const &sb, size_t sz = 0); +void sbasis_to_bezier (std::vector<Point> & bz, D2<SBasis> const& sb, size_t sz = 0); +void sbasis_to_cubic_bezier (std::vector<Point> & bz, D2<SBasis> const& sb); +void bezier_to_sbasis (SBasis & sb, Bezier const& bz); +void bezier_to_sbasis (D2<SBasis> & sb, std::vector<Point> const& bz); +void build_from_sbasis(PathBuilder &pb, D2<SBasis> const &B, double tol, bool only_cubicbeziers); + +#if 0 +// this produces a degree k bezier from a degree k sbasis +Bezier +sbasis_to_bezier(SBasis const &B, unsigned q = 0); + +// inverse +SBasis bezier_to_sbasis(Bezier const &B); + + +std::vector<Geom::Point> +sbasis_to_bezier(D2<SBasis> const &B, unsigned q = 0); +#endif + + +PathVector path_from_piecewise(Piecewise<D2<SBasis> > const &B, double tol, bool only_cubicbeziers = false); + +Path path_from_sbasis(D2<SBasis> const &B, double tol, bool only_cubicbeziers = false); +inline Path cubicbezierpath_from_sbasis(D2<SBasis> const &B, double tol) + { return path_from_sbasis(B, tol, true); } + +} // end namespace Geom + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/sbasis.h b/include/2geom/sbasis.h new file mode 100644 index 0000000..5cb0e93 --- /dev/null +++ b/include/2geom/sbasis.h @@ -0,0 +1,530 @@ +/** @file + * @brief Polynomial in symmetric power basis (S-basis) + *//* + * Authors: + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * Michael Sloan <mgsloan@gmail.com> + * + * Copyright (C) 2006-2007 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_SBASIS_H +#define LIB2GEOM_SEEN_SBASIS_H +#include <cassert> +#include <iostream> +#include <utility> +#include <vector> + +#include <2geom/linear.h> +#include <2geom/interval.h> +#include <2geom/utils.h> +#include <2geom/exception.h> + +//#define USE_SBASISN 1 + + +#if defined(USE_SBASIS_OF) + +#include "sbasis-of.h" + +#elif defined(USE_SBASISN) + +#include "sbasisN.h" +namespace Geom{ + +/*** An empty SBasis is identically 0. */ +class SBasis : public SBasisN<1>; + +}; +#else + +namespace Geom { + +/** + * @brief Polynomial in symmetric power basis + * @ingroup Fragments + */ +class SBasis { + std::vector<Linear> d; + void push_back(Linear const&l) { d.push_back(l); } + +public: + // As part of our migration away from SBasis isa vector we provide this minimal set of vector interface methods. + size_t size() const {return d.size();} + typedef std::vector<Linear>::iterator iterator; + typedef std::vector<Linear>::const_iterator const_iterator; + Linear operator[](unsigned i) const { + return d[i]; + } + Linear& operator[](unsigned i) { return d.at(i); } + const_iterator begin() const { return d.begin();} + const_iterator end() const { return d.end();} + iterator begin() { return d.begin();} + iterator end() { return d.end();} + bool empty() const { return d.size() == 1 && d[0][0] == 0 && d[0][1] == 0; } + Linear &back() {return d.back();} + Linear const &back() const {return d.back();} + void pop_back() { + if (d.size() > 1) { + d.pop_back(); + } else { + d[0][0] = 0; + d[0][1] = 0; + } + } + void resize(unsigned n) { d.resize(std::max<unsigned>(n, 1));} + void resize(unsigned n, Linear const& l) { d.resize(std::max<unsigned>(n, 1), l);} + void reserve(unsigned n) { d.reserve(n);} + void clear() { + d.resize(1); + d[0][0] = 0; + d[0][1] = 0; + } + void insert(iterator before, const_iterator src_begin, const_iterator src_end) { d.insert(before, src_begin, src_end);} + Linear& at(unsigned i) { return d.at(i);} + //void insert(Linear* before, int& n, Linear const &l) { d.insert(std::vector<Linear>::iterator(before), n, l);} + bool operator==(SBasis const&B) const { return d == B.d;} + bool operator!=(SBasis const&B) const { return d != B.d;} + + SBasis() + : d(1, Linear(0, 0)) + {} + explicit SBasis(double a) + : d(1, Linear(a, a)) + {} + explicit SBasis(double a, double b) + : d(1, Linear(a, b)) + {} + SBasis(SBasis const &a) + : d(a.d) + {} + SBasis(std::vector<Linear> ls) + : d(std::move(ls)) + {} + SBasis(Linear const &bo) + : d(1, bo) + {} + SBasis(Linear* bo) + : d(1, bo ? *bo : Linear(0, 0)) + {} + explicit SBasis(size_t n, Linear const&l) : d(n, l) {} + + SBasis(Coord c0, Coord c1, Coord c2, Coord c3) + : d(2) + { + d[0][0] = c0; + d[1][0] = c1; + d[1][1] = c2; + d[0][1] = c3; + } + SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5) + : d(3) + { + d[0][0] = c0; + d[1][0] = c1; + d[2][0] = c2; + d[2][1] = c3; + d[1][1] = c4; + d[0][1] = c5; + } + SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5, + Coord c6, Coord c7) + : d(4) + { + d[0][0] = c0; + d[1][0] = c1; + d[2][0] = c2; + d[3][0] = c3; + d[3][1] = c4; + d[2][1] = c5; + d[1][1] = c6; + d[0][1] = c7; + } + SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5, + Coord c6, Coord c7, Coord c8, Coord c9) + : d(5) + { + d[0][0] = c0; + d[1][0] = c1; + d[2][0] = c2; + d[3][0] = c3; + d[4][0] = c4; + d[4][1] = c5; + d[3][1] = c6; + d[2][1] = c7; + d[1][1] = c8; + d[0][1] = c9; + } + + // construct from a sequence of coefficients + template <typename Iter> + SBasis(Iter first, Iter last) { + assert(std::distance(first, last) % 2 == 0); + assert(std::distance(first, last) >= 2); + for (; first != last; ++first) { + --last; + push_back(Linear(*first, *last)); + } + } + + //IMPL: FragmentConcept + typedef double output_type; + inline bool isZero(double eps=EPSILON) const { + assert(size() > 0); + for(unsigned i = 0; i < size(); i++) { + if(!(*this)[i].isZero(eps)) return false; + } + return true; + } + inline bool isConstant(double eps=EPSILON) const { + assert(size() > 0); + if(!(*this)[0].isConstant(eps)) return false; + for (unsigned i = 1; i < size(); i++) { + if(!(*this)[i].isZero(eps)) return false; + } + return true; + } + + bool isFinite() const; + inline Coord at0() const { return (*this)[0][0]; } + inline Coord &at0() { return (*this)[0][0]; } + inline Coord at1() const { return (*this)[0][1]; } + inline Coord &at1() { return (*this)[0][1]; } + + int degreesOfFreedom() const { return size()*2;} + + double valueAt(double t) const { + assert(size() > 0); + double s = t*(1-t); + double p0 = 0, p1 = 0; + for(unsigned k = size(); k > 0; k--) { + const Linear &lin = (*this)[k-1]; + p0 = p0*s + lin[0]; + p1 = p1*s + lin[1]; + } + return (1-t)*p0 + t*p1; + } + //double valueAndDerivative(double t, double &der) const { + //} + double operator()(double t) const { + return valueAt(t); + } + + std::vector<double> valueAndDerivatives(double t, unsigned n) const; + + SBasis toSBasis() const { return SBasis(*this); } + + double tailError(unsigned tail) const; + +// compute f(g) + SBasis operator()(SBasis const & g) const; + +//MUTATOR PRISON + //remove extra zeros + void normalize() { + while(size() > 1 && back().isZero(0)) + pop_back(); + } + + void truncate(unsigned k) { if(k < size()) resize(std::max<size_t>(k, 1)); } +private: + void derive(); // in place version +}; + +//TODO: figure out how to stick this in linear, while not adding an sbasis dep +inline SBasis Linear::toSBasis() const { return SBasis(*this); } + +//implemented in sbasis-roots.cpp +OptInterval bounds_exact(SBasis const &a); +OptInterval bounds_fast(SBasis const &a, int order = 0); +OptInterval bounds_local(SBasis const &a, const OptInterval &t, int order = 0); + +/** Returns a function which reverses the domain of a. + \param a sbasis function + \relates SBasis + +useful for reversing a parameteric curve. +*/ +inline SBasis reverse(SBasis const &a) { + SBasis result(a.size(), Linear()); + + for(unsigned k = 0; k < a.size(); k++) + result[k] = reverse(a[k]); + return result; +} + +//IMPL: ScalableConcept +inline SBasis operator-(const SBasis& p) { + if(p.isZero()) return SBasis(); + SBasis result(p.size(), Linear()); + + for(unsigned i = 0; i < p.size(); i++) { + result[i] = -p[i]; + } + return result; +} +SBasis operator*(SBasis const &a, double k); +inline SBasis operator*(double k, SBasis const &a) { return a*k; } +inline SBasis operator/(SBasis const &a, double k) { return a*(1./k); } +SBasis& operator*=(SBasis& a, double b); +inline SBasis& operator/=(SBasis& a, double b) { return (a*=(1./b)); } + +//IMPL: AddableConcept +SBasis operator+(const SBasis& a, const SBasis& b); +SBasis operator-(const SBasis& a, const SBasis& b); +SBasis& operator+=(SBasis& a, const SBasis& b); +SBasis& operator-=(SBasis& a, const SBasis& b); + +//TODO: remove? +/*inline SBasis operator+(const SBasis & a, Linear const & b) { + if(b.isZero()) return a; + if(a.isZero()) return b; + SBasis result(a); + result[0] += b; + return result; +} +inline SBasis operator-(const SBasis & a, Linear const & b) { + if(b.isZero()) return a; + SBasis result(a); + result[0] -= b; + return result; +} +inline SBasis& operator+=(SBasis& a, const Linear& b) { + if(a.isZero()) + a.push_back(b); + else + a[0] += b; + return a; +} +inline SBasis& operator-=(SBasis& a, const Linear& b) { + if(a.isZero()) + a.push_back(-b); + else + a[0] -= b; + return a; + }*/ + +//IMPL: OffsetableConcept +inline SBasis operator+(const SBasis & a, double b) { + if(a.isZero()) return Linear(b, b); + SBasis result(a); + result[0] += b; + return result; +} +inline SBasis operator-(const SBasis & a, double b) { + if(a.isZero()) return Linear(-b, -b); + SBasis result(a); + result[0] -= b; + return result; +} +inline SBasis& operator+=(SBasis& a, double b) { + if(a.isZero()) + a = SBasis(Linear(b,b)); + else + a[0] += b; + return a; +} +inline SBasis& operator-=(SBasis& a, double b) { + if(a.isZero()) + a = SBasis(Linear(-b,-b)); + else + a[0] -= b; + return a; +} + +SBasis shift(SBasis const &a, int sh); +SBasis shift(Linear const &a, int sh); + +inline SBasis truncate(SBasis const &a, unsigned terms) { + SBasis c; + c.insert(c.begin(), a.begin(), a.begin() + std::min(terms, (unsigned)a.size())); + return c; +} + +SBasis multiply(SBasis const &a, SBasis const &b); +// This performs a multiply and accumulate operation in about the same time as multiply. return a*b + c +SBasis multiply_add(SBasis const &a, SBasis const &b, SBasis c); + +SBasis integral(SBasis const &c); +SBasis derivative(SBasis const &a); + +SBasis sqrt(SBasis const &a, int k); + +// return a kth order approx to 1/a) +SBasis reciprocal(Linear const &a, int k); +SBasis divide(SBasis const &a, SBasis const &b, int k); + +inline SBasis operator*(SBasis const & a, SBasis const & b) { + return multiply(a, b); +} + +inline SBasis& operator*=(SBasis& a, SBasis const & b) { + a = multiply(a, b); + return a; +} + +/** Returns the degree of the first non zero coefficient. + \param a sbasis function + \param tol largest abs val considered 0 + \return first non zero coefficient + \relates SBasis +*/ +inline unsigned +valuation(SBasis const &a, double tol=0){ + unsigned val=0; + while( val<a.size() && + fabs(a[val][0])<tol && + fabs(a[val][1])<tol ) + val++; + return val; +} + +// a(b(t)) +SBasis compose(SBasis const &a, SBasis const &b); +SBasis compose(SBasis const &a, SBasis const &b, unsigned k); +SBasis inverse(SBasis a, int k); +//compose_inverse(f,g)=compose(f,inverse(g)), but is numerically more stable in some good cases... +//TODO: requires g(0)=0 & g(1)=1 atm. generalization should be obvious. +SBasis compose_inverse(SBasis const &f, SBasis const &g, unsigned order=2, double tol=1e-3); + +/** Returns the sbasis on domain [0,1] that was t on [from, to] + \param t sbasis function + \param from,to interval + \return sbasis + \relates SBasis +*/ +SBasis portion(const SBasis &t, double from, double to); +inline SBasis portion(const SBasis &t, Interval const &ivl) { return portion(t, ivl.min(), ivl.max()); } + +// compute f(g) +inline SBasis +SBasis::operator()(SBasis const & g) const { + return compose(*this, g); +} + +inline std::ostream &operator<< (std::ostream &out_file, const Linear &bo) { + out_file << "{" << bo[0] << ", " << bo[1] << "}"; + return out_file; +} + +inline std::ostream &operator<< (std::ostream &out_file, const SBasis & p) { + for(unsigned i = 0; i < p.size(); i++) { + if (i != 0) { + out_file << " + "; + } + out_file << p[i] << "s^" << i; + } + return out_file; +} + +// These are deprecated, use sbasis-math.h versions if possible +SBasis sin(Linear bo, int k); +SBasis cos(Linear bo, int k); + +std::vector<double> roots(SBasis const & s); +std::vector<double> roots(SBasis const & s, Interval const inside); +std::vector<std::vector<double> > multi_roots(SBasis const &f, + std::vector<double> const &levels, + double htol=1e-7, + double vtol=1e-7, + double a=0, + double b=1); + +//--------- Levelset like functions ----------------------------------------------------- + +/** Solve f(t) = v +/- tolerance. The collection of intervals where + * v - vtol <= f(t) <= v+vtol + * is returned (with a precision tol on the boundaries). + \param f sbasis function + \param level the value of v. + \param vtol: error tolerance on v. + \param a, b limit search on domain [a,b] + \param tol: tolerance on the result bounds. + \returns a vector of intervals. +*/ +std::vector<Interval> level_set (SBasis const &f, + double level, + double vtol = 1e-5, + double a=0., + double b=1., + double tol = 1e-5); + +/** Solve f(t)\in I=[u,v], which defines a collection of intervals (J_k). More precisely, + * a collection (J'_k) is returned with J'_k = J_k up to a given tolerance. + \param f sbasis function + \param level: the given interval of deisred values for f. + \param a, b limit search on domain [a,b] + \param tol: tolerance on the bounds of the result. + \returns a vector of intervals. +*/ +std::vector<Interval> level_set (SBasis const &f, + Interval const &level, + double a=0., + double b=1., + double tol = 1e-5); + +/** 'Solve' f(t) = v +/- tolerance for several values of v at once. + \param f sbasis function + \param levels vector of values, that should be sorted. + \param vtol: error tolerance on v. + \param a, b limit search on domain [a,b] + \param tol: the bounds of the returned intervals are exact up to that tolerance. + \returns a vector of vectors of intervals. +*/ +std::vector<std::vector<Interval> > level_sets (SBasis const &f, + std::vector<double> const &levels, + double a=0., + double b=1., + double vtol = 1e-5, + double tol = 1e-5); + +/** 'Solve' f(t)\in I=[u,v] for several intervals I at once. + \param f sbasis function + \param levels vector of 'y' intervals, that should be disjoints and sorted. + \param a, b limit search on domain [a,b] + \param tol: the bounds of the returned intervals are exact up to that tolerance. + \returns a vector of vectors of intervals. +*/ +std::vector<std::vector<Interval> > level_sets (SBasis const &f, + std::vector<Interval> const &levels, + double a=0., + double b=1., + double tol = 1e-5); + +} +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : +#endif diff --git a/include/2geom/solver.h b/include/2geom/solver.h new file mode 100644 index 0000000..5b082cb --- /dev/null +++ b/include/2geom/solver.h @@ -0,0 +1,88 @@ +/** + * \file + * \brief Finding roots of Bernstein-Bezier polynomials + *//* + * Authors: + * ? <?@?.?> + * + * Copyright ?-? authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_SOLVER_H +#define LIB2GEOM_SEEN_SOLVER_H + +#include <2geom/point.h> +#include <2geom/sbasis.h> +#include <vector> + +namespace Geom { + + class Point; + class Bezier; + +unsigned +crossing_count(Geom::Point const *V, /* Control pts of Bezier curve */ + unsigned degree); /* Degree of Bezier curve */ +void +find_parametric_bezier_roots( + Geom::Point const *w, /* The control points */ + unsigned degree, /* The degree of the polynomial */ + std::vector<double> & solutions, /* RETURN candidate t-values */ + unsigned depth); /* The depth of the recursion */ + +unsigned +crossing_count(double const *V, /* Control pts of Bezier curve */ + unsigned degree, /* Degree of Bezier curve */ + double left_t, double right_t); + + +void +find_bernstein_roots( + double const *w, /* The control points */ + unsigned degree, /* The degree of the polynomial */ + std::vector<double> & solutions, /* RETURN candidate t-values */ + unsigned depth, /* The depth of the recursion */ + double left_t=0, double right_t=1, bool use_secant=true); + +}; + +void +find_bernstein_roots(std::vector<double> &solutions, /* RETURN candidate t-values */ + Geom::Bezier const& bz, + double left_t, double right_t); + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/svg-path-parser.h b/include/2geom/svg-path-parser.h new file mode 100644 index 0000000..e25316c --- /dev/null +++ b/include/2geom/svg-path-parser.h @@ -0,0 +1,199 @@ +/** + * \file + * \brief parse SVG path specifications + * + * Copyright 2007 MenTaLguY <mental@rydia.net> + * Copyright 2007 Aaron Spike <aaron@ekips.org> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_SVG_PATH_PARSER_H +#define LIB2GEOM_SEEN_SVG_PATH_PARSER_H + +#include <iostream> +#include <iterator> +#include <stdexcept> +#include <vector> +#include <cstdio> +#include <2geom/exception.h> +#include <2geom/point.h> +#include <2geom/path-sink.h> +#include <2geom/forward.h> + +namespace Geom { + +/** @brief Read SVG path data and feed it to a PathSink + * + * This class provides an interface to an SVG path data parser written in Ragel. + * It supports parsing the path data either at once or block-by-block. + * Use the parse() functions to parse complete data and the feed() and finish() + * functions to parse partial data. + * + * The parser will call the appropriate methods on the PathSink supplied + * at construction. To store the path in memory as a PathVector, pass + * a PathBuilder. You can also use one of the freestanding helper functions + * if you don't need to parse data block-by-block. + * + * @ingroup Paths + */ +class SVGPathParser { +public: + SVGPathParser(PathSink &sink); + ~SVGPathParser(); + + /** @brief Reset internal state. + * Discards the internal state associated with partially parsed data, + * letting you start from scratch. Note that any partial data written + * to the path sink is not affected - you need to clear it yourself. */ + void reset(); + + /** @brief Parse a C-style string. + * The path sink is flushed and the internal state is reset after this call. + * Note that the state is not reset before this method, so you can use it to + * process the last block of partial data. + * @param str String to parse + * @param len Length of string or -1 if null-terminated */ + void parse(char const *str, int len = -1); + /** @brief Parse an STL string. */ + void parse(std::string const &s); + + /** @brief Parse a part of path data stored in a C-style string. + * This method does not reset internal state, so it can be called multiple + * times to parse successive blocks of a longer SVG path data string. + * To finish parsing, call finish() after the final block or call parse() + * with the last block of data. + * @param str String to parse + * @param len Length of string or -1 if null-terminated */ + void feed(char const *str, int len = -1); + /** @brief Parse a part of path data stored in an STL string. */ + void feed(std::string const &s); + + /** @brief Finalize parsing. + * After the last block of data was submitted with feed(), call this method + * to finalize parsing, flush the path sink and reset internal state. + * You should not call this after parse(). */ + void finish(); + + /** @brief Set the threshold for considering the closing segment degenerate. + * When the current point was reached by a relative command, is closer + * to the initial point of the path than the specified threshold + * and a 'z' is encountered, the last segment will be adjusted instead so that + * the closing segment has exactly zero length. This is useful when reading + * SVG 1.1 paths that have non-linear final segments written in relative + * coordinates, which always suffer from some loss of precision. SVG 2 + * allows alternate placement of 'z' which does not have this problem. */ + void setZSnapThreshold(Coord threshold) { _z_snap_threshold = threshold; } + Coord zSnapThreshold() const { return _z_snap_threshold; } + +private: + bool _absolute; + bool _moveto_was_absolute; + Point _current; + Point _initial; + Point _cubic_tangent; + Point _quad_tangent; + std::vector<Coord> _params; + PathSink &_sink; + Coord _z_snap_threshold; + Curve *_curve; + + int cs; + std::string _number_part; + + void _push(Coord value); + Coord _pop(); + bool _pop_flag(); + Coord _pop_coord(Geom::Dim2 axis); + Point _pop_point(); + void _moveTo(Point const &p); + void _lineTo(Point const &p); + void _curveTo(Point const &c0, Point const &c1, Point const &p); + void _quadTo(Point const &c, Point const &p); + void _arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p); + void _closePath(); + void _pushCurve(Curve *c); + + void _parse(char const *str, char const *strend, bool finish); +}; + +/** @brief Feed SVG path data to the specified sink + * @ingroup Paths */ +void parse_svg_path(char const *str, PathSink &sink); +/** @brief Feed SVG path data to the specified sink + * @ingroup Paths */ +inline void parse_svg_path(std::string const &str, PathSink &sink) { + parse_svg_path(str.c_str(), sink); +} +/** Feed SVG path data from a C stream to the specified sink + * @ingroup Paths */ +void parse_svg_path_file(FILE *fi, PathSink &sink); + +/** @brief Create path vector from SVG path data stored in a C string + * @ingroup Paths */ +inline PathVector parse_svg_path(char const *str) { + PathVector ret; + SubpathInserter iter(ret); + PathIteratorSink<SubpathInserter> generator(iter); + + parse_svg_path(str, generator); + return ret; +} + +/** @brief Create path vector from a C stream with SVG path data + * @ingroup Paths */ +inline PathVector read_svgd_f(FILE * fi) { + PathVector ret; + SubpathInserter iter(ret); + PathIteratorSink<SubpathInserter> generator(iter); + + parse_svg_path_file(fi, generator); + return ret; +} + +/** @brief Create path vector from SVG path data stored in a file + * @ingroup Paths */ +inline PathVector read_svgd(char const *filename) { + FILE* fi = fopen(filename, "r"); + if(fi == NULL) throw(std::runtime_error("Error opening file")); + PathVector out = read_svgd_f(fi); + fclose(fi); + return out; +} + +} // end namespace Geom + +#endif +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/svg-path-writer.h b/include/2geom/svg-path-writer.h new file mode 100644 index 0000000..92a80ec --- /dev/null +++ b/include/2geom/svg-path-writer.h @@ -0,0 +1,122 @@ +/** @file + * @brief Path sink which writes an SVG-compatible command string + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2014 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_SVG_PATH_WRITER_H +#define LIB2GEOM_SEEN_SVG_PATH_WRITER_H + +#include <2geom/path-sink.h> +#include <sstream> + +namespace Geom { + +/** @brief Serialize paths to SVG path data strings. + * You can access the generated string by calling the str() method. + * @ingroup Paths + */ +class SVGPathWriter + : public PathSink +{ +public: + SVGPathWriter(); + ~SVGPathWriter() override {} + + void moveTo(Point const &p) override; + void lineTo(Point const &p) override; + void quadTo(Point const &c, Point const &p) override; + void curveTo(Point const &c0, Point const &c1, Point const &p) override; + void arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p) override; + void closePath() override; + void flush() override; + + /// Clear any path data written so far. + void clear(); + + /** @brief Set output precision. + * When the parameter is negative, the path writer enters a verbatim mode + * which preserves all values exactly. */ + void setPrecision(int prec); + + /** @brief Enable or disable length optimization. + * + * When set to true, the path writer will optimize the generated path data + * for minimum length. However, this will make the data less readable, + * because spaces between commands and coordinates will be omitted where + * unnecessary for correct parsing. + * + * When set to false, the string will be a straightforward, partially redundant + * representation of the passed commands, optimized for readability. + * Commands and coordinates will always be separated by spaces and the command + * symbol will not be omitted for multiple consecutive commands of the same type. + * + * Length optimization is turned off by default. */ + void setOptimize(bool opt) { _optimize = opt; } + + /** @brief Enable or disable the use of V, H, T and S commands where possible. + * Shorthands are turned on by default. */ + void setUseShorthands(bool use) { _use_shorthands = use; } + + /// Retrieve the generated path data string. + std::string str() const { return _s.str(); } + +private: + void _setCommand(char cmd); + std::string _formatCoord(Coord par); + + std::ostringstream _s, _ns; + std::vector<Coord> _current_pars; + Point _subpath_start; + Point _current; + Point _quad_tangent; + Point _cubic_tangent; + Coord _epsilon; + int _precision; + bool _optimize; + bool _use_shorthands; + char _command; +}; + +std::string write_svg_path(PathVector const &pv, int prec = -1, bool optimize = false, bool shorthands = true); + +} // namespace Geom + +#endif // LIB2GEOM_SEEN_SVG_PATH_WRITER_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/sweep-bounds.h b/include/2geom/sweep-bounds.h new file mode 100644 index 0000000..e0ebf29 --- /dev/null +++ b/include/2geom/sweep-bounds.h @@ -0,0 +1,62 @@ +/** + * \file + * \brief Sweepline intersection of groups of rectangles + *//* + * Authors: + * ? <?@?.?> + * + * Copyright ?-? authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_SWEEP_H +#define LIB2GEOM_SEEN_SWEEP_H + +#include <vector> +#include <2geom/d2.h> + +namespace Geom { + +std::vector<std::vector<unsigned> > +sweep_bounds(std::vector<Rect>, Dim2 dim = X); + +std::vector<std::vector<unsigned> > +sweep_bounds(std::vector<Rect>, std::vector<Rect>, Dim2 dim = X); + +} + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/sweeper.h b/include/2geom/sweeper.h new file mode 100644 index 0000000..233d181 --- /dev/null +++ b/include/2geom/sweeper.h @@ -0,0 +1,189 @@ +/** @file + * @brief Class for implementing sweepline algorithms + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_SWEEPER_H +#define LIB2GEOM_SEEN_SWEEPER_H + +#include <2geom/coord.h> +#include <algorithm> +#include <vector> +#include <boost/intrusive/list.hpp> + +namespace Geom { + +// exposition only +template <typename Item> +class SweepVector { +public: + typedef typename std::vector<Item>::const_iterator ItemIterator; + + SweepVector(std::vector<Item> const &v) + : _items(v) + {} + + std::vector<Item> const &items() { return _items; } + Interval itemBounds(ItemIterator /*ii*/) { return Interval(); } + + void addActiveItem(ItemIterator /*ii*/) {} + void removeActiveItem(ItemIterator /*ii*/) {} + +private: + std::vector<Item> const &_items; +}; + +/** @brief Generic sweepline algorithm. + * + * This class encapsulates an algorithm that sorts the objects according + * to their bounds, then moves an imaginary line (sweepline) over those + * bounds from left to right. Objects are added to the active list when + * the line starts intersecting their bounds, and removed when it completely + * passes over them. + * + * To use this, create a class that exposes the following methods: + * - Range items() - returns a forward iterable range of items that will be swept. + * - Interval itemBounds(iterator i) - given an iterator from the above range, + * compute the bounding interval of the referenced item in the direction of sweep. + * - void addActiveItem(iterator i) - add an item to the active list. + * - void removeActiveItem(iterator i) - remove an item from the active list. + * + * Create the object, then instantiate this template with the above class + * as the template parameter, pass it the constructed object of the class, + * and call the process() method. + * + * A good choice for the active list is a Boost intrusive list, which allows + * you to get an iterator from a value in constant time. + * + * Look in path.cpp for example usage. + * + * @tparam Item The type of items to sweep + * @tparam SweepTraits Traits class that defines the items' bounds, + * how to interpret them and how to sort the events + * @ingroup Utilities + */ +template <typename SweepSet> +class Sweeper { +public: + typedef typename SweepSet::ItemIterator Iter; + + explicit Sweeper(SweepSet &set) + : _set(set) + { + std::size_t sz = std::distance(set.items().begin(), set.items().end()); + _entry_events.reserve(sz); + _exit_events.reserve(sz); + } + + /** @brief Process entry and exit events. + * This will iterate over all inserted items, calling the methods + * addActiveItem and removeActiveItem on the SweepSet passed at construction + * according to the order of the boundaries of each item. */ + void process() { + if (_set.items().empty()) return; + + Iter last = _set.items().end(); + for (Iter i = _set.items().begin(); i != last; ++i) { + Interval b = _set.itemBounds(i); + // guard against NANs + assert(b.min() == b.min() && b.max() == b.max()); + _entry_events.push_back(Event(b.max(), i)); + _exit_events.push_back(Event(b.min(), i)); + } + + std::make_heap(_entry_events.begin(), _entry_events.end()); + std::make_heap(_exit_events.begin(), _exit_events.end()); + + Event next_entry = _get_next(_entry_events); + Event next_exit = _get_next(_exit_events); + + while (next_entry || next_exit) { + assert(next_exit); + + if (!next_entry || next_exit > next_entry) { + // exit event - remove record from active list + _set.removeActiveItem(next_exit.item); + next_exit = _get_next(_exit_events); + } else { + // entry event - add record to active list + _set.addActiveItem(next_entry.item); + next_entry = _get_next(_entry_events); + } + } + } + +private: + struct Event + : boost::totally_ordered<Event> + { + Coord coord; + Iter item; + + Event(Coord c, Iter const &i) + : coord(c), item(i) + {} + Event() + : coord(nan("")), item() + {} + bool operator<(Event const &other) const { return coord < other.coord; } + bool operator==(Event const &other) const { return coord == other.coord; } + operator bool() const { return !std::isnan(coord); } + }; + + static Event _get_next(std::vector<Event> &heap) { + if (heap.empty()) { + Event e; + return e; + } + std::pop_heap(heap.begin(), heap.end()); + Event ret = heap.back(); + heap.pop_back(); + return ret; + } + + SweepSet &_set; + std::vector<Event> _entry_events; + std::vector<Event> _exit_events; +}; + +} // namespace Geom + +#endif // !LIB2GEOM_SEEN_SWEEPER_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/symbolic/determinant-minor.h b/include/2geom/symbolic/determinant-minor.h new file mode 100644 index 0000000..d70c397 --- /dev/null +++ b/include/2geom/symbolic/determinant-minor.h @@ -0,0 +1,175 @@ +/* + * GiNaC Copyright (C) 1999-2008 Johannes Gutenberg University Mainz, Germany + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GEOM_SL_DETERMINANT_MINOR_H_ +#define _GEOM_SL_DETERMINANT_MINOR_H_ + +#include <map> + + +namespace Geom { namespace SL { + +/* + * determinant_minor + * This routine has been taken from the ginac project + * and adapted as needed; comments are the original ones. + */ + +/** Recursive determinant for small matrices having at least one symbolic + * entry. The basic algorithm, known as Laplace-expansion, is enhanced by + * some bookkeeping to avoid calculation of the same submatrices ("minors") + * more than once. According to W.M.Gentleman and S.C.Johnson this algorithm + * is better than elimination schemes for matrices of sparse multivariate + * polynomials and also for matrices of dense univariate polynomials if the + * matrix' dimesion is larger than 7. + * + * @return the determinant as a new expression (in expanded form) + * @see matrix::determinant() */ + +template< typename Coeff > +Coeff determinant_minor(Matrix<Coeff> const& M) +{ + assert(M.rows() == M.columns()); + // for small matrices the algorithm does not make any sense: + const unsigned int n = M.columns(); + if (n == 1) + return M(0,0); + if (n == 2) + return (M(0,0) * M(1,1) - M(0,1) * M(1,0)); + if (n == 3) + return ( M(0,0)*M(1,1)*M(2,2) + M(0,2)*M(1,0)*M(2,1) + + M(0,1)*M(1,2)*M(2,0) - M(0,2)*M(1,1)*M(2,0) + - M(0,0)*M(1,2)*M(2,1) - M(0,1)*M(1,0)*M(2,2) ); + + // This algorithm can best be understood by looking at a naive + // implementation of Laplace-expansion, like this one: + // ex det; + // matrix minorM(this->rows()-1,this->cols()-1); + // for (unsigned r1=0; r1<this->rows(); ++r1) { + // // shortcut if element(r1,0) vanishes + // if (m[r1*col].is_zero()) + // continue; + // // assemble the minor matrix + // for (unsigned r=0; r<minorM.rows(); ++r) { + // for (unsigned c=0; c<minorM.cols(); ++c) { + // if (r<r1) + // minorM(r,c) = m[r*col+c+1]; + // else + // minorM(r,c) = m[(r+1)*col+c+1]; + // } + // } + // // recurse down and care for sign: + // if (r1%2) + // det -= m[r1*col] * minorM.determinant_minor(); + // else + // det += m[r1*col] * minorM.determinant_minor(); + // } + // return det.expand(); + // What happens is that while proceeding down many of the minors are + // computed more than once. In particular, there are binomial(n,k) + // kxk minors and each one is computed factorial(n-k) times. Therefore + // it is reasonable to store the results of the minors. We proceed from + // right to left. At each column c we only need to retrieve the minors + // calculated in step c-1. We therefore only have to store at most + // 2*binomial(n,n/2) minors. + + // Unique flipper counter for partitioning into minors + std::vector<unsigned int> Pkey; + Pkey.reserve(n); + // key for minor determinant (a subpartition of Pkey) + std::vector<unsigned int> Mkey; + Mkey.reserve(n-1); + // we store our subminors in maps, keys being the rows they arise from + typedef typename std::map<std::vector<unsigned>, Coeff> Rmap; + typedef typename std::map<std::vector<unsigned>, Coeff>::value_type Rmap_value; + Rmap A; + Rmap B; + Coeff det; + // initialize A with last column: + for (unsigned int r = 0; r < n; ++r) + { + Pkey.erase(Pkey.begin(),Pkey.end()); + Pkey.push_back(r); + A.insert(Rmap_value(Pkey,M(r,n-1))); + } + // proceed from right to left through matrix + for (int c = n-2; c >= 0; --c) + { + Pkey.erase(Pkey.begin(),Pkey.end()); // don't change capacity + Mkey.erase(Mkey.begin(),Mkey.end()); + for (unsigned int i = 0; i < n-c; ++i) + Pkey.push_back(i); + unsigned int fc = 0; // controls logic for our strange flipper counter + do + { + det = Geom::SL::zero<Coeff>()(); + for (unsigned int r = 0; r < n-c; ++r) + { + // maybe there is nothing to do? + if (M(Pkey[r], c).is_zero()) + continue; + // create the sorted key for all possible minors + Mkey.erase(Mkey.begin(),Mkey.end()); + for (unsigned int i = 0; i < n-c; ++i) + if (i != r) + Mkey.push_back(Pkey[i]); + // Fetch the minors and compute the new determinant + if (r % 2) + det -= M(Pkey[r],c)*A[Mkey]; + else + det += M(Pkey[r],c)*A[Mkey]; + } + // store the new determinant at its place in B: + if (!det.is_zero()) + B.insert(Rmap_value(Pkey,det)); + // increment our strange flipper counter + for (fc = n-c; fc > 0; --fc) + { + ++Pkey[fc-1]; + if (Pkey[fc-1]<fc+c) + break; + } + if (fc < n-c && fc > 0) + for (unsigned int j = fc; j < n-c; ++j) + Pkey[j] = Pkey[j-1]+1; + } while(fc); + // next column, so change the role of A and B: + A.swap(B); + B.clear(); + } + + return det; +} + + + +} /*end namespace Geom*/ } /*end namespace SL*/ + +#endif // _GEOM_SL_DETERMINANT_MINOR_H_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/symbolic/implicit.h b/include/2geom/symbolic/implicit.h new file mode 100644 index 0000000..82d77cd --- /dev/null +++ b/include/2geom/symbolic/implicit.h @@ -0,0 +1,353 @@ +/* + * Routines to compute the implicit equation of a parametric polynomial curve + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#ifndef _GEOM_SL_IMPLICIT_H_ +#define _GEOM_SL_IMPLICIT_H_ + + + +#include <2geom/symbolic/multipoly.h> +#include <2geom/symbolic/matrix.h> + + +#include <2geom/exception.h> + +#include <array> + + +namespace Geom { namespace SL { + +typedef MultiPoly<1, double> MVPoly1; +typedef MultiPoly<2, double> MVPoly2; +typedef MultiPoly<3, double> MVPoly3; +typedef std::array<MVPoly1, 3> poly_vector_type; +typedef std::array<poly_vector_type, 2> basis_type; +typedef std::array<double, 3> coeff_vector_type; + +namespace detail { + +/* + * transform a univariate polynomial f(t) in a 3-variate polynomial + * p(t, x, y) = f(t) * x^i * y^j + */ +inline +void poly1_to_poly3(MVPoly3 & p3, MVPoly1 const& p1, size_t i, size_t j) +{ + multi_index_type I = make_multi_index(0, i, j); + for (; I[0] < p1.get_poly().size(); ++I[0]) + { + p3.coefficient(I, p1[I[0]]); + } +} + +/* + * evaluates the degree of a poly_vector_type, such a degree is defined as: + * deg({p[0](t), p[1](t), p[2](t)}) := {max(deg(p[i](t)), i = 0, 1, 2), k} + * here k is the index where the max is achieved, + * if deg(p[i](t)) == deg(p[j](t)) and i < j then k = i + */ +inline +std::pair<size_t, size_t> deg(poly_vector_type const& p) +{ + std::pair<size_t, size_t> d; + d.first = p[0].get_poly().real_degree(); + d.second = 0; + size_t k = p[1].get_poly().real_degree(); + if (d.first < k) + { + d.first = k; + d.second = 1; + } + k = p[2].get_poly().real_degree(); + if (d.first < k) + { + d.first = k; + d.second = 2; + } + return d; +} + +} // end namespace detail + + +/* + * A polynomial parametrization could be seen as 1-variety V in R^3, + * intersection of two surfaces x = f(t), y = g(t), this variety V has + * attached an ideal I in the ring of polynomials in t, x, y with coefficients + * on reals; a basis of generators for I is given by p(t, x, y) = x - f(t), + * q(t, x, y) = y - g(t); such a basis has the nice property that could be + * written as a couple of vectors of dim 3 with entries in R[t]; the original + * polinomials p and q can be obtained by doing a dot product between each + * vector and the vector {x, y, 1} + * As reference you can read the text book: + * Ideals, Varieties and Algorithms by Cox, Little, O'Shea + */ +inline +void make_initial_basis(basis_type& b, MVPoly1 const& p, MVPoly1 const& q) +{ + // first basis vector + b[0][0] = 1; + b[0][1] = 0; + b[0][2] = -p; + + // second basis vector + b[1][0] = 0; + b[1][1] = 1; + b[1][2] = -q; +} + +/* + * Starting from the initial basis for the ideal I is possible to make up + * a new basis, still showing off the nice property that each generator is + * a moving line that is a linear combination of x, y, 1 where the coefficients + * are polynomials in R[t], and moreover each generator is of minimal degree. + * Can be proved that given a polynomial parametrization f(t), g(t) + * we are able to make up a "micro" basis of generators p(t, x, y), q(t, x, y) + * for the ideal I such that the deg(p, t) = m <= n/2 and deg(q, t) = n - m, + * where n = max(deg(f(t)), deg(g(t))); this let us halve the order of + * the Bezout matrix. + * Reference: + * Zheng, Sederberg - A Direct Approach to Computing the micro-basis + * of a Planar Rational Curves + * Deng, Chen, Shen - Computing micro-Basis of Rational Curves and Surfaces + * Using Polynomial Matrix Factorization + */ +inline +void microbasis(basis_type& b, MVPoly1 const& p, MVPoly1 const& q) +{ + typedef std::pair<size_t, size_t> degree_pair_t; + + size_t n = std::max(p.get_poly().real_degree(), q.get_poly().real_degree()); + make_initial_basis(b, p, q); + degree_pair_t n0 = detail::deg(b[0]); + degree_pair_t n1 = detail::deg(b[1]); + size_t d; + double r0, r1; + //size_t iter = 0; + while ((n0.first + n1.first) > n)// && iter < 30) + { +// ++iter; +// std::cout << "iter = " << iter << std::endl; +// for (size_t i= 0; i < 2; ++i) +// for (size_t j= 0; j < 3; ++j) +// std::cout << b[i][j] << std::endl; +// std::cout << n0.first << ", " << n0.second << std::endl; +// std::cout << n1.first << ", " << n1.second << std::endl; +// std::cout << "-----" << std::endl; +// if (n0.first < n1.first) +// { +// d = n1.first - n0.first; +// r = b[1][n1.second][n1.first] / b[0][n1.second][n0.first]; +// for (size_t i = 0; i < b[0].size(); ++i) +// b[1][i] -= ((r * b[0][i]).get_poly() << d); +// b[1][n1.second][n1.first] = 0; +// n1 = detail::deg(b[1]); +// } +// else +// { +// d = n0.first - n1.first; +// r = b[0][n0.second][n0.first] / b[1][n0.second][n1.first]; +// for (size_t i = 0; i < b[0].size(); ++i) +// b[0][i] -= ((r * b[1][i]).get_poly() << d); +// b[0][n0.second][n0.first] = 0; +// n0 = detail::deg(b[0]); +// } + + // this version shouldn't suffer of ill-conditioning due to + // cancellation issue + if (n0.first < n1.first) + { + d = n1.first - n0.first; + r0 = b[0][n1.second][n0.first]; + r1 = b[1][n1.second][n1.first]; + for (size_t i = 0; i < b[0].size(); ++i) + { + b[1][i] *= r0; + b[1][i] -= ((r1 * b[0][i]).get_poly() << d); + // without the following division the modulus grows + // beyond the limit of the double type + b[1][i] /= r0; + } + n1 = detail::deg(b[1]); + } + else + { + d = n0.first - n1.first; + r0 = b[0][n1.second][n0.first]; + r1 = b[1][n1.second][n1.first]; + + for (size_t i = 0; i < b[0].size(); ++i) + { + b[0][i] *= r1; + b[0][i] -= ((r0 * b[1][i]).get_poly() << d); + b[0][i] /= r1; + } + n0 = detail::deg(b[0]); + } + + } +} + +/* + * computes the dot product: + * p(t, x, y) = {p0(t), p1(t), p2(t)} . {x, y, 1} + */ +inline +void basis_to_poly(MVPoly3 & p0, poly_vector_type const& v) +{ + MVPoly3 p1, p2; + detail::poly1_to_poly3(p0, v[0], 1,0); + detail::poly1_to_poly3(p1, v[1], 0,1); + detail::poly1_to_poly3(p2, v[2], 0,0); + p0 += p1; + p0 += p2; +} + + +/* + * Make up a Bezout matrix with two basis genarators as input. + * + * A Bezout matrix is the matrix related to the symmetric bilinear form + * (f,g) -> B[f,g] where B[f,g](s,t) = (f(t)*g(s) - f(s)*g(t))/(s-t) + * where f, g are polynomials, this function is called a bezoutian. + * Given a basis of generators {p(t, x, y), q(t, x, y)} for the ideal I + * related to our parametrization x = f(t), y = g(t), we are able to prove + * that the implicit equation of such polynomial parametrization can be + * evaluated computing the determinant of the Bezout matrix made up using + * the polinomial p and q as univariate polynomials in t with coefficients + * in R[x,y], so the resulting Bezout matrix will be a matrix with bivariate + * polynomials as entries. A Bezout matrix is always symmetric. + * Reference: + * Sederberg, Zheng - Algebraic Methods for Computer Aided Geometric Design + */ +Matrix<MVPoly2> +make_bezout_matrix (MVPoly3 const& p, MVPoly3 const& q) +{ + size_t pdeg = p.get_poly().real_degree(); + size_t qdeg = q.get_poly().real_degree(); + size_t n = std::max(pdeg, qdeg); + + Matrix<MVPoly2> BM(n, n); + //std::cerr << "rows, columns " << BM.rows() << " , " << BM.columns() << std::endl; + for (size_t i = n; i >= 1; --i) + { + for (size_t j = n; j >= i; --j) + { + size_t m = std::min(i, n + 1 - j); + //std::cerr << "m = " << m << std::endl; + for (size_t k = 1; k <= m; ++k) + { + //BM(i-1,j-1) += (p[j-1+k] * q[i-k] - p[i-k] * q[j-1+k]); + BM(n-i,n-j) += (p.coefficient(j-1+k) * q.coefficient(i-k) + - p.coefficient(i-k) * q.coefficient(j-1+k)); + } + } + } + + for (size_t i = 0; i < n; ++i) + { + for (size_t j = 0; j < i; ++j) + BM(j,i) = BM(i,j); + } + return BM; +} + +/* + * Make a matrix that represents a main minor (i.e. with the diagonal + * on the diagonal of the matrix to which it owns) of the Bezout matrix + * with order n-1 where n is the order of the Bezout matrix. + * The minor is obtained by removing the "h"-th row and the "h"-th column, + * and as the Bezout matrix is symmetric. + */ +Matrix<MVPoly2> +make_bezout_main_minor (MVPoly3 const& p, MVPoly3 const& q, size_t h) +{ + size_t pdeg = p.get_poly().real_degree(); + size_t qdeg = q.get_poly().real_degree(); + size_t n = std::max(pdeg, qdeg); + + Matrix<MVPoly2> BM(n-1, n-1); + size_t u = 0, v; + for (size_t i = 1; i <= n; ++i) + { + v = 0; + if (i == h) + { + u = 1; + continue; + } + for (size_t j = 1; j <= i; ++j) + { + if (j == h) + { + v = 1; + continue; + } + size_t m = std::min(i, n + 1 - j); + for (size_t k = 1; k <= m; ++k) + { + //BM(i-u-1,j-v-1) += (p[j-1+k] * q[i-k] - p[i-k] * q[j-1+k]); + BM(i-u-1,j-v-1) += (p.coefficient(j-1+k) * q.coefficient(i-k) + - p.coefficient(i-k) * q.coefficient(j-1+k)); + } + } + } + + --n; + for (size_t i = 0; i < n; ++i) + { + for (size_t j = 0; j < i; ++j) + BM(j,i) = BM(i,j); + } + return BM; +} + + +} /*end namespace Geom*/ } /*end namespace SL*/ + + + + +#endif // _GEOM_SL_IMPLICIT_H_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/symbolic/matrix.h b/include/2geom/symbolic/matrix.h new file mode 100644 index 0000000..d9dc690 --- /dev/null +++ b/include/2geom/symbolic/matrix.h @@ -0,0 +1,265 @@ +/* + * Matrix<CoeffT> class template + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + + +#ifndef _GEOM_SL_MATRIX_H_ +#define _GEOM_SL_MATRIX_H_ + + +#include <array> +#include <vector> +#include <map> + +#include <2geom/point.h> +#include <2geom/numeric/matrix.h> +#include <2geom/symbolic/multipoly.h> + + + + +namespace Geom { namespace SL { + +/* + * generic Matrix class template + * needed for building up a matrix with polynomial entries + */ +template< typename Coeff> +class Matrix +{ + public: + typedef Coeff coeff_type; + typedef std::vector<coeff_type> container_type; + + Matrix() + {} + + Matrix(size_t m, size_t n) + : m_data(m*n), m_rows(m), m_columns(n) + { + } + + void resize(size_t m, size_t n) + { + m_data.resize(m,n); + m_rows = m; + m_columns = n; + } + + size_t rows() const + { + return m_rows; + } + + size_t columns() const + { + return m_columns; + } + + coeff_type const& operator() (size_t i, size_t j) const + { + return m_data[i * columns() + j]; + } + + coeff_type & operator() (size_t i, size_t j) + { + return m_data[i * columns() + j]; + } + + + private: + container_type m_data; + size_t m_rows; + size_t m_columns; +}; + + +template< typename Coeff, typename charT > +inline +std::basic_ostream<charT> & +operator<< ( std::basic_ostream<charT> & os, + const Matrix<Coeff> & _matrix ) +{ + if (_matrix.rows() == 0 || _matrix.columns() == 0) return os; + + os << "{{" << _matrix(0,0); + for (size_t j = 1; j < _matrix.columns(); ++j) + { + os << ", " << _matrix(0,j); + } + os << "}"; + + for (size_t i = 1; i < _matrix.rows(); ++i) + { + os << ", {" << _matrix(i,0); + for (size_t j = 1; j < _matrix.columns(); ++j) + { + os << ", " << _matrix(i,j); + } + os << "}"; + } + os << "}"; + return os; +} + +template <size_t N, typename CoeffT, typename T> +void polynomial_matrix_evaluate (Matrix<T> & A, + Matrix< MultiPoly<N, CoeffT> > const& M, + std::array<T, N> const& X) +{ + A.resize(M.rows(), M.columns()); + for (size_t i = 0; i < M.rows(); ++i) + { + for (size_t j = 0; j < M.columns(); ++j) + { + A(i,j) = M(i,j)(X); + } + } +} + + +inline +void polynomial_matrix_evaluate (NL::Matrix & A, + Matrix< MultiPoly<2, double> > const& M, + Point const& P) +{ + for (size_t i = 0; i < M.rows(); ++i) + { + for (size_t j = 0; j < M.columns(); ++j) + { + A(i,j) = M(i,j)(P[X], P[Y]); + } + } +} + + +/* +template< typename Coeff> +class SymmetricSquareMatrix +{ + public: + typedef Coeff coeff_type; + typedef std::vector<coeff_type> container_type; + + SymmetricSquareMatrix(size_t n) + : m_data((n*n)/2 + n), m_size(n) + { + + } + + size_t rows() const + { + return m_size; + } + + size_t columns() const + { + return m_size; + } + + coeff_type const& operator() (size_t i, size_t j) const + { + return m_data[i * columns() + j]; + } + + coeff_type & operator() (size_t i, size_t j) + { + return m_data[i * columns() + j]; + } + + coeff_type det() + { + + } + + private: + container_type m_data; + size_t m_size; +}; +*/ + +/* + * This is an adaptation of the LU algorithm used in the numerical case. + * This algorithm is based on the article due to Bareiss: + * "Sylvester's identity and multistep integer-preserving Gaussian elimination" + */ + +/* +template< typename CoeffT > +CoeffT det(Matrix<CoeffT> const& M) +{ + assert(M.rows() == M.columns()); + + Matrix<CoeffT> A(M); + CoeffT n; + CoeffT d = one<CoeffT>()(); + for (size_t k = 1; k < A.rows(); ++k) + { + for (size_t i = k; i < A.rows(); ++i) + { + for (size_t j = k; j < A.columns(); ++j) + { + n = A(i,j) * A(k-1,k-1) - A(k-1,j) * A(i,k-1); +// std::cout << "k, i, j: " +// << k << ", " << i << ", " << j << std::endl; +// std::cout << "n = " << n << std::endl; +// std::cout << "d = " << d << std::endl; + A(i,j) = factor(n, d); + } + } + d = A(k-1,k-1); + } + return A(A.rows()-1, A.columns()-1); +} +*/ + + + +} /*end namespace Geom*/ } /*end namespace SL*/ + + +#include <2geom/symbolic/determinant-minor.h> + + +#endif // _GEOM_SL_MATRIX_H_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/symbolic/multi-index.h b/include/2geom/symbolic/multi-index.h new file mode 100644 index 0000000..311fae8 --- /dev/null +++ b/include/2geom/symbolic/multi-index.h @@ -0,0 +1,169 @@ +/* + * A multi-index is an ordered sequence of unsigned int + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef _GEOM_SL_MULTI_INDEX_H_ +#define _GEOM_SL_MULTI_INDEX_H_ + + +#include <2geom/exception.h> + +#include <valarray> + +#include <boost/preprocessor/cat.hpp> +#include <boost/preprocessor/repetition/enum_params.hpp> +#include <boost/preprocessor/repetition/repeat.hpp> +#include <boost/preprocessor/repetition/repeat_from_to.hpp> + + + + +/* + * an helper macro for generating function with declaration: + * multi_index_type make_multi_index (size_t i0, ..., size_t iN) + * that is a facility to make up a multi-index from a list of values + */ + +#define GEOM_SL_MAX_RANK 10 + +#define GEOM_SL_ASSIGN_INDEX(z, k, unused) I[k] = BOOST_PP_CAT(i, k); + +#define GEOM_SL_MAKE_MULTI_INDEX(z, N, unused) \ +inline \ +multi_index_type make_multi_index (BOOST_PP_ENUM_PARAMS(N, size_t i)) \ +{ \ + multi_index_type I(N); \ + BOOST_PP_REPEAT(N, GEOM_SL_ASSIGN_INDEX, unused) \ + return I; \ +} +// end macro GEOM_SL_MAKE_MULTI_INDEX + + + + +namespace Geom { namespace SL { + +/* + * A multi-index is an ordered sequence of unsigned int; + * it's useful for representing exponent, degree and coefficient index + * of a multi-variate polynomial; + * example: given a monomial x_(0)^i_(0)*x_(1)^i_(1)*...*x_(N-1)^i_(N-1) + * we can write it in the simpler form X^I where X=(x_(0), .., x_(N-1)) + * and I=(i_(0), .., i_(N-1)) is a multi-index + * A multi-index is represented as a valarray this let us make simple + * arithmetic operations on a multi-index + */ + +typedef std::valarray<size_t> multi_index_type; + + +// make up a multi-index of size N and fill it with zeroes +inline +multi_index_type multi_index_zero(size_t N) +{ + return multi_index_type(N); +} + +// helper functions for generating a multi-index from a list of values +// we create an amount of GEOM_SL_MAX_RANK of suzh functions +BOOST_PP_REPEAT_FROM_TO(0, GEOM_SL_MAX_RANK, GEOM_SL_MAKE_MULTI_INDEX, unused) + + +// helper function for generating a multi-index of size N +// from a single index v that is placed at position i with i in [0,N[ +template <size_t N> +inline +multi_index_type make_multi_index(size_t i, size_t v) +{ + if (!(i < N)) + THROW_RANGEERROR ("make_multi_index<N> from a single index: " + "out of range position"); + multi_index_type I(N); + I[i] = v; + return I; +} + +// transform a N size multi-index in (N-1)-size multi-index +// by removing the first index: (i1, i2,...,iN) -> (i2,..,iN) +inline +multi_index_type shift(multi_index_type const& I, size_t i = 1) +{ + size_t N = I.size() - i; + multi_index_type J = I[std::slice(i, N, 1)]; + return J; +} + +// valarray operator== returns a valarray of bool +inline +bool is_equal(multi_index_type const& I, multi_index_type const& J) +{ + if (I.size() != J.size()) return false; + for (size_t i = 0; i < I.size(); ++i) + if (I[i] != J[i]) return false; + return true; +} + +// extended operator<< for printing a multi-index +template <typename charT> +inline +std::basic_ostream<charT> & +operator<< (std::basic_ostream<charT> & os, + const Geom::SL::multi_index_type & I) +{ + if (I.size() == 0 ) return os; + os << "[" << I[0]; + for (unsigned int i = 1; i < I.size(); ++i) + { + os << ", " << I[i]; + } + os << "]"; + return os; +} + +} /*end namespace Geom*/ } /*end namespace SL*/ + +// argument dependent name lookup doesn't work with typedef +using Geom::SL::operator<<; + + +#endif // _GEOM_SL_MULTI_INDEX_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/symbolic/multipoly.h b/include/2geom/symbolic/multipoly.h new file mode 100644 index 0000000..ab3a5f4 --- /dev/null +++ b/include/2geom/symbolic/multipoly.h @@ -0,0 +1,684 @@ +/* + * MultiPoly<N, CoeffT> class template + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef _GEOM_SL_MULTIPOLY_H_ +#define _GEOM_SL_MULTIPOLY_H_ + +#include <utility> + +#include <array> +#include <functional> +#include <type_traits> + +#include <2geom/symbolic/unity-builder.h> +#include <2geom/symbolic/mvpoly-tools.h> + + +namespace Geom { namespace SL { + +/* + * MultiPoly<N, CoeffT> class template + * + * It represents a multi-variate polynomial with N indeterminates + * and coefficients of type CoeffT, but it doesn't support explicit + * symbol attaching; the indeterminates should be thought as implicitly + * defined in an automatic enumerative style: x_(0),...,x_(N-1) . + * + */ + +template <size_t N, typename CoeffT> +class MultiPoly +{ +public: + typedef typename mvpoly<N, CoeffT>::type poly_type; + typedef CoeffT coeff_type; + static const size_t rank = N; + +public: + MultiPoly() + { + } + + MultiPoly(poly_type p) + : m_poly(std::move(p)) + { + } + + // create a mv polynomial of type c*X^I + MultiPoly(coeff_type c, multi_index_type const& I = multi_index_zero(N)) + : m_poly(monomial<N, coeff_type>::make(I, c)) + { + } + + // create a mv polynomial p(x_(N-M),...,x_(N-1))*X'^I + // where I.size() == N-M and X'=(x_(0),...,x_(N-M-1)) + template <size_t M> + MultiPoly (MultiPoly<M, CoeffT> const& p, + multi_index_type const& I = multi_index_zero(N-M), + typename std::enable_if_t<(M > 0) && (M < N)>* = 0) + { + Geom::SL::coefficient<N-M-1, poly_type>::set_safe(m_poly, I, p.m_poly); + } + + /* + * assignment operators + */ + MultiPoly& operator=(poly_type const& p) + { + m_poly = p; + return (*this); + } + + MultiPoly& operator=(coeff_type const& c) + { + multi_index_type I = multi_index_zero(N); + (*this) = MultiPoly(c); + return (*this); + } + + // return the degree of the mv polynomial wrt the ordering OrderT + template <typename OrderT> + multi_index_type degree() const + { + return Geom::SL::mvdegree<N, CoeffT, OrderT>::value(m_poly); + } + + // return the coefficient of the term with the highest degree + // wrt the ordering OrderT + template <typename OrderT> + coeff_type const& leading_coefficient() const + { + return (*this)(degree<OrderT>()); + } + + template <typename OrderT> + coeff_type & leading_coefficient() + { + return (*this)(degree<OrderT>()); + } + + // return the coefficient of the term of degree 0 (wrt all indeterminates) + coeff_type const& trailing_coefficient() const + { + return (*this)(multi_index_zero(N)); + } + + coeff_type & trailing_coefficient() + { + return (*this)(multi_index_zero(N)); + } + + // access coefficient methods with no out-of-range checking + coeff_type const& operator() (multi_index_type const& I) const + { + return Geom::SL::coefficient<N-1, poly_type>::get(m_poly, I); + } + + coeff_type & operator() (multi_index_type const& I) + { + return Geom::SL::coefficient<N-1, poly_type>::get(m_poly, I); + } + + // safe coefficient get method + coeff_type const& coefficient(multi_index_type const& I) const + { + return Geom::SL::coefficient<N-1, poly_type>::get_safe(m_poly, I); + } + + // safe coefficient set method + void coefficient(multi_index_type const& I, coeff_type const& c) + { + Geom::SL::coefficient<N-1, poly_type>::set_safe(m_poly, I, c); + } + + // access the mv poly of rank N-1 with no out-of-range checking + typename poly_type::coeff_type const& + operator[] (size_t const& i) const + { + return m_poly[i]; + } + + typename poly_type::coeff_type & + operator[] (size_t const& i) + { + return m_poly[i]; + } + + // safe access to the mv poly of rank N-1 + typename poly_type::coeff_type const& + coefficient(size_t const& i) const + { + return m_poly.coefficient(i); + } + + void coefficient (size_t const& i, + typename poly_type::coeff_type const& c) + { + m_poly.coefficient(i, c); + } + + /* + * polynomail evaluation: + * T can be any type that is able to be + and * with the coefficient type + */ + template <typename T> + T operator() (std::array<T, N> const& X) const + { + return Geom::SL::mvpoly<N, CoeffT>::evaluate(m_poly, X); + } + + template <typename T> + typename std::enable_if_t<(N == 1), T> + operator() (T const& x0) const + { + std::array<T, N> X = {{x0}}; + return Geom::SL::mvpoly<N, CoeffT>::evaluate(m_poly, X); + } + + template <typename T> + typename std::enable_if_t<(N == 2), T> + operator() (T const& x0, T const& x1) const + { + std::array<T, N> X = {{x0, x1}}; + return Geom::SL::mvpoly<N, CoeffT>::evaluate(m_poly, X); + } + + template <typename T> + typename std::enable_if_t<(N == 3), T> + operator() (T const& x0, T const& x1, T const& x2) const + { + std::array<T, N> X = {{x0, x1, x2}}; + return Geom::SL::mvpoly<N, CoeffT>::evaluate(m_poly, X); + } + + /* + * trim leading zero coefficients + */ + void normalize() + { + Geom::SL::mvpoly<N, CoeffT>::normalize(m_poly); + } + + /* + * select the sub multi-variate polynomial with rank M + * which is unambiguously characterized by the multi-index I + * requirements: + * - M > 0 && M < N; + * - multi-index size == N-M + */ + template <size_t M> + typename mvpoly<M, CoeffT>::type const& + select (multi_index_type const& I= multi_index_zero(N-M), + typename std::enable_if_t<(M > 0) && (M < N)>* = 0) const + { + return Geom::SL::coefficient<N-M-1, poly_type>::get_safe(m_poly, I); + } + + poly_type const& get_poly() const + { + return m_poly; + } + + bool is_zero() const + { + return ((*this) == zero); + } + + // return the opposite mv poly + MultiPoly operator-() const + { + MultiPoly r(-m_poly); + return r; + } + + /* + * multipoly-multipoly mutating operators + */ + MultiPoly& operator+=(MultiPoly const& p) + { + m_poly += p.m_poly; + return (*this); + } + + MultiPoly& operator-=(MultiPoly const& p) + { + m_poly -= p.m_poly; + return (*this); + } + + MultiPoly& operator*=(MultiPoly const& p) + { + m_poly *= p.m_poly; + return (*this); + } + + MultiPoly& operator<<=(multi_index_type const& I) + { + Geom::SL::mvpoly<N, CoeffT>::shift(m_poly, I); + return (*this); + } + + bool operator==(MultiPoly const& q) const + { + return (m_poly == q.m_poly); + } + + bool operator!=(MultiPoly const& q) const + { + return !((*this) == q); + } + + /* + * multipoly-coefficient mutating operators + */ + MultiPoly& operator+=(CoeffT const& c) + { + trailing_coefficient() += c; + return (*this); + } + + MultiPoly& operator-=(CoeffT const& c) + { + trailing_coefficient() -= c; + return (*this); + } + + MultiPoly& operator*=(CoeffT const& c) + { + mvpoly<N, CoeffT>::template + for_each<0>(m_poly, std::bind(mvpoly<0, CoeffT>::multiply_to, std::placeholders::_1, c)); + return (*this); + } + + MultiPoly& operator/=(CoeffT const& c) + { + mvpoly<N, CoeffT>::template + for_each<0>(m_poly, std::bind(mvpoly<0, CoeffT>::divide_to, std::placeholders::_1, c)); + return (*this); + } + + /* + * multipoly-polynomial mutating operators + */ + MultiPoly& operator+=(poly_type const& p) + { + m_poly += p; + return (*this); + } + + MultiPoly& operator-=(poly_type const& p) + { + m_poly -= p; + return (*this); + } + + MultiPoly& operator*=(poly_type const& p) + { + m_poly *= p; + return (*this); + } + + /* + * multipoly<N>-multipoly<M> mutating operators + * requirements: + * - M > 0 && M < N; + * - they must have the same coefficient type. + */ + + template <size_t M> + typename std::enable_if_t<(M > 0) && (M < N), MultiPoly> & + operator+= (MultiPoly<M, CoeffT> const& p) + { + multi_index_type I = multi_index_zero(N-M); + Geom::SL::coefficient<N-M-1, poly_type>::get(m_poly, I) += p.m_poly; + return (*this); + } + + template <size_t M> + typename std::enable_if_t<(M > 0) && (M < N), MultiPoly> & + operator-= (MultiPoly<M, CoeffT> const& p) + { + multi_index_type I = multi_index_zero(N-M); + Geom::SL::coefficient<N-M-1, poly_type>::get(m_poly, I) -= p.m_poly; + return (*this); + } + + template <size_t M> + typename std::enable_if_t<(M > 0) && (M < N), MultiPoly> & + operator*= (MultiPoly<M, CoeffT> const& p) + { + mvpoly<N, CoeffT>::template + for_each<M>(m_poly, std::bind(mvpoly<M, CoeffT>::multiply_to, std::placeholders::_1, p.m_poly)); + return (*this); + } + + /* + * we need MultiPoly instantiations to be each other friend + * in order to be able of implementing operations between + * MultiPoly instantiations with a different ranks + */ + template<size_t M, typename C> + friend class MultiPoly; + + template< typename charT, size_t M, typename C> + friend + std::basic_ostream<charT> & + operator<< (std::basic_ostream<charT> & os, const MultiPoly<M, C> & p); + + static const MultiPoly zero; + static const MultiPoly one; + static const coeff_type zero_coeff; + static const coeff_type one_coeff; + +private: + poly_type m_poly; + +}; // end class MultiPoly + + +/* + * zero and one element spezcialization for MultiPoly + */ +template <size_t N, typename CoeffT> +struct zero<MultiPoly<N, CoeffT>, false> +{ + MultiPoly<N, CoeffT> operator() () + { + CoeffT _0c = zero<CoeffT>()(); + MultiPoly<N, CoeffT> _0(_0c); + return _0; + } +}; + + +template <size_t N, typename CoeffT> +struct one<MultiPoly<N, CoeffT>, false> +{ + MultiPoly<N, CoeffT> operator() () + { + CoeffT _1c = one<CoeffT>()(); + MultiPoly<N, CoeffT> _1(_1c); + return _1; + } +}; + + +/* + * initialization of MultiPoly static data members + */ +template <size_t N, typename CoeffT> +const MultiPoly<N, CoeffT> MultiPoly<N, CoeffT>::one + = Geom::SL::one< MultiPoly<N, CoeffT> >()(); + +template <size_t N, typename CoeffT> +const MultiPoly<N, CoeffT> MultiPoly<N, CoeffT>::zero + = Geom::SL::zero< MultiPoly<N, CoeffT> >()(); + +template <size_t N, typename CoeffT> +const typename MultiPoly<N, CoeffT>::coeff_type MultiPoly<N, CoeffT>::zero_coeff + = Geom::SL::zero<typename MultiPoly<N, CoeffT>::coeff_type>()(); + +template <size_t N, typename CoeffT> +const typename MultiPoly<N, CoeffT>::coeff_type MultiPoly<N, CoeffT>::one_coeff + = Geom::SL::one<typename MultiPoly<N, CoeffT>::coeff_type>()(); + + +/* + * operator<< extended to print out a mv poly type + */ +template <typename charT, size_t N, typename CoeffT> +inline +std::basic_ostream<charT> & +operator<< (std::basic_ostream<charT> & os, const MultiPoly<N, CoeffT> & p) +{ + return operator<<(os, p.m_poly); +} + +/* + * equivalent to multiply by X^I + */ +template <size_t N, typename CoeffT> +inline +MultiPoly<N, CoeffT> +operator<< (MultiPoly<N, CoeffT> const& p, multi_index_type const& I) +{ + MultiPoly<N, CoeffT> r(p); + r <<= I; + return r; +} + +/* + * MultiPoly<M, CoeffT> - MultiPoly<N, CoeffT> binary mathematical operators + */ + +template <size_t M, size_t N, typename CoeffT> +inline +typename std::enable_if_t<(M > 0) && (M <= N), MultiPoly<N, CoeffT> > +operator+ (MultiPoly<N, CoeffT> const& p, + MultiPoly<M, CoeffT> const& q ) +{ + MultiPoly<N, CoeffT> r(p); + r += q; + return r; +} + +template <size_t M, size_t N, typename CoeffT> +inline +typename std::enable_if_t<(N > 0) && (M > N), MultiPoly<M, CoeffT> > +operator+ (MultiPoly<N, CoeffT> const& p, + MultiPoly<M, CoeffT> const& q ) +{ + MultiPoly<M, CoeffT> r(q); + r += p; + return r; +} + +template <size_t M, size_t N, typename CoeffT> +inline +typename std::enable_if_t<(M > 0) && (M <= N), MultiPoly<N, CoeffT> > +operator- (MultiPoly<N, CoeffT> const& p, + MultiPoly<M, CoeffT> const& q ) +{ + MultiPoly<N, CoeffT> r(p); + r -= q; + return r; +} + +template <size_t M, size_t N, typename CoeffT> +inline +typename std::enable_if_t<(N > 0) && (M > N), MultiPoly<M, CoeffT> > +operator- (MultiPoly<N, CoeffT> const& p, + MultiPoly<M, CoeffT> const& q ) +{ + MultiPoly<M, CoeffT> r(-q); + r += p; + return r; +} + + +template <size_t M, size_t N, typename CoeffT> +inline +typename std::enable_if_t<(M > 0) && (M <= N), MultiPoly<N, CoeffT> > +operator* (MultiPoly<N, CoeffT> const& p, + MultiPoly<M, CoeffT> const& q ) +{ + MultiPoly<N, CoeffT> r(p); + r *= q; + return r; +} + +template <size_t M, size_t N, typename CoeffT> +inline +typename std::enable_if_t<(N > 0) && (M > N), MultiPoly<M, CoeffT> > +operator* (MultiPoly<N, CoeffT> const& p, + MultiPoly<M, CoeffT> const& q ) +{ + MultiPoly<M, CoeffT> r(q); + r *= p; + return r; +} + +/* + * MultiPoly-coefficient and coefficient-MultiPoly binary mathematical operators + */ + +template <size_t N, typename CoeffT> +inline +MultiPoly<N, CoeffT> operator+(MultiPoly<N, CoeffT> const& p, CoeffT const& c) +{ + MultiPoly<N, CoeffT> r(p); + r += c; + return r; +} + +template <size_t N, typename CoeffT> +inline +MultiPoly<N, CoeffT> operator+(CoeffT const& c, MultiPoly<N, CoeffT> const& p) +{ + MultiPoly<N, CoeffT> r(p); + r += c; + return r; +} + +template <size_t N, typename CoeffT> +inline +MultiPoly<N, CoeffT> operator-(MultiPoly<N, CoeffT> const& p, CoeffT const& c) +{ + MultiPoly<N, CoeffT> r(p); + r -= c; + return r; +} + +template <size_t N, typename CoeffT> +inline +MultiPoly<N, CoeffT> operator-(CoeffT const& c, MultiPoly<N, CoeffT> const& p) +{ + MultiPoly<N, CoeffT> r(-p); + r += c; + return r; +} + +template <size_t N, typename CoeffT> +inline +MultiPoly<N, CoeffT> operator*(MultiPoly<N, CoeffT> const& p, CoeffT const& c) +{ + MultiPoly<N, CoeffT> r(p); + r *= c; + return r; +} + +template <size_t N, typename CoeffT> +inline +MultiPoly<N, CoeffT> operator*(CoeffT const& c, MultiPoly<N, CoeffT> const& p) +{ + MultiPoly<N, CoeffT> r(p); + r *= c; + return r; +} + + +template <size_t N, typename CoeffT> +inline +MultiPoly<N, CoeffT> operator/(MultiPoly<N, CoeffT> const& p, CoeffT const& c) +{ + MultiPoly<N, CoeffT> r(p); + r /= c; + return r; +} + + + + +/* +template< size_t N, typename CoeffT > +MultiPoly<N, CoeffT> +factor( MultiPoly<N, CoeffT> const& f, + MultiPoly<N, CoeffT> const& g ) +{ + typedef MultiPoly<N, CoeffT> poly_type; + + if (g == poly_type::one) return f; + poly_type h(g), q, r(f); + multi_index_type deg_r = r.template degree<ordering::lex>(); + multi_index_type deg_g = g.template degree<ordering::lex>(); + multi_index_type deg0 = multi_index_zero(deg_g.size()); + CoeffT ltg = g(deg_g); + if (is_equal(deg_g, deg0)) return (f / ltg); + //h(deg_g) = 0; +// std::cout << "deg_g = " << deg_g << std::endl; +// std::cout << "ltg = " << ltg << std::endl; + CoeffT lt, ltr; + multi_index_type deg(1, deg_g.size()); + size_t iter = 0; + while (!is_equal(deg, deg0) && iter < 10000) + { + ++iter; + deg = deg_r - deg_g; + ltr = r(deg_r); + lt = ltr / ltg; + q.coefficient(deg, lt); + //r(deg_r) = 0; + r -= ((lt * g) << deg); + deg_r = r.template degree<ordering::lex>(); +// std::cout << "deg_r = " << deg_r << std::endl; +// std::cout << "ltr = " << ltr << std::endl; +// std::cout << "deg = " << deg << std::endl; +// std::cout << "lt = " << lt << std::endl; +// std::cout << "q = " << q << std::endl; +// std::cout << "r = " << r << std::endl; + +// break; + } + //std::cout << "iter = " << iter << std::endl; + return q; +} +*/ + + +} /*end namespace Geom*/ } /*end namespace SL*/ + + + + +#endif /* _MULTIPOLY_H_ */ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/symbolic/mvpoly-tools.h b/include/2geom/symbolic/mvpoly-tools.h new file mode 100644 index 0000000..34dece7 --- /dev/null +++ b/include/2geom/symbolic/mvpoly-tools.h @@ -0,0 +1,690 @@ +/* + * Routines that extend univariate polynomial functions + * to multi-variate polynomial exploiting recursion at compile time + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef _GEOM_SL_MVPOLY_TOOLS_H_ +#define _GEOM_SL_MVPOLY_TOOLS_H_ + + +#include <2geom/exception.h> + +#include <2geom/symbolic/multi-index.h> +#include <2geom/symbolic/unity-builder.h> +#include <2geom/symbolic/polynomial.h> + +#include <array> +#include <functional> +#include <iostream> +#include <type_traits> + + +namespace Geom { namespace SL { + +/* + * rank<PolyT>::value == total amount of indeterminates + * x_(0),x_(1),...,x_(rank-1) that belong to type PolyT + */ + +template <typename T> +struct rank +{ + static const size_t value = 0; +}; + +template <typename CoeffT> +struct rank< Polynomial<CoeffT> > +{ + static const size_t value = rank<CoeffT>::value + 1; +}; + + +/* + * mvpoly<N, CoeffT> creates a multi-variate polynomial type + * by nesting N-1 Polynomial class template and setting + * the coefficient type of the most nested Polynomial to CoeffT + * example: mvpoly<3, double>::type is the same than + * Polynomial< Polynomial< Polynomial<double> > > + */ + +template <size_t N, typename CoeffT> +struct mvpoly +{ + typedef Polynomial<typename mvpoly<N-1, CoeffT>::type> type; + typedef CoeffT coeff_type; + static const size_t rank = N; + + /* + * computes the lexicographic degree of the mv polynomial p + */ + static + multi_index_type lex_degree (type const& p) + { + multi_index_type D(N); + lex_degree_impl<0>(p, D); + return D; + } + + /* + * Returns in the out-parameter D an N-sequence where each entry value + * represents the max degree of the polynomial related to the passed + * index I, if one index value in I is greater than the related max degree + * the routine returns false otherwise it returns true. + * This routine can be used to test if a given multi-index I is related + * to an actual initialized coefficient. + */ + static + bool max_degree (type const& p, + multi_index_type& D, + multi_index_type const& I) + { + if (I.size() != N) + THROW_RANGEERROR ("multi-index with wrong length"); + D.resize(N); + return max_degree_impl<0>(p, D, I); + } + + /* + * Returns in the out-parameter D an N-sequence where each entry value + * represents the real degree of the polynomial related to the passed + * index I, if one index value in I is greater than the related real degree + * the routine returns false otherwise it returns true. + * This routine can be used to test if a given multi-index I is related + * to an actual initialized and non-zero coefficient. + */ + + static + bool real_degree (type const& p, + multi_index_type& D, + multi_index_type const& I) + { + if (I.size() != N) + THROW_RANGEERROR ("multi-index with wrong length"); + D.resize(N); + return real_degree_impl<0>(p, D, I); + } + + /* + * Multiplies p by X^I + */ + static + void shift(type & p, multi_index_type const& I) + { + if (I.size() != N) + THROW_RANGEERROR ("multi-index with wrong length"); + shift_impl<0>(p, I); + } + + /* + * mv poly evaluation: + * T can be any type that is able to be += with the coefficient type + * and that can be *= with the same type T moreover a specialization + * of zero struct for the type T is needed + */ + template <typename T> + static + T evaluate(type const& p, std::array<T, N> const& X) + { + return evaluate_impl<T, 0>(p, X); + } + + /* + * trim leading zero coefficients + */ + static + void normalize(type & p) + { + p.normalize(); + for (size_t k = 0; k < p.size(); ++k) + mvpoly<N-1, CoeffT>::normalize(p[k]); + } + + /* + * Applies the unary operator "op" to each coefficient of p with rank M. + * For instance when M = 0 op is applied to each coefficient + * of the multi-variate polynomial p. + * When M < N the function call recursively the for_each routine + * for p.real_degree() times, when M == N the operator "op" is invoked on p; + */ + template <size_t M> + static + void for_each + (type & p, + std::function<void (typename mvpoly<M, CoeffT>::type &)> const& op, + typename std::enable_if_t<(M < N)>* = 0) + { + for (size_t k = 0; k <= p.real_degree(); ++k) + { + mvpoly<N-1, CoeffT>::template for_each<M>(p[k], op); + } + } + + template <size_t M> + static + void for_each + (type & p, + std::function<void (typename mvpoly<M, CoeffT>::type &)> const& op, + typename std::enable_if_t<(M == N)>* = 0) + { + op(p); + } + + // this is only an helper function to be passed to the for_each routine + static + void multiply_to (type& p, type const& q) + { + p *= q; + } + + private: + template <size_t i> + static + void lex_degree_impl (type const& p, multi_index_type& D) + { + D[i] = p.real_degree(); + mvpoly<N-1, CoeffT>::template lex_degree_impl<i+1>(p[D[i]], D); + } + + template <size_t i> + static + bool max_degree_impl (type const& p, + multi_index_type& D, + multi_index_type const& I) + { + D[i] = p.max_degree(); + if (I[i] > D[i]) return false; + return + mvpoly<N-1, CoeffT>::template max_degree_impl<i+1>(p[I[i]], D, I); + } + + template <size_t i> + static + bool real_degree_impl (type const& p, + multi_index_type& D, + multi_index_type const& I) + { + D[i] = p.real_degree(); + if (I[i] > D[i]) return false; + return + mvpoly<N-1, CoeffT>::template real_degree_impl<i+1>(p[I[i]], D, I); + } + + template <size_t i> + static + void shift_impl(type & p, multi_index_type const& I) + { + p <<= I[i]; + for (size_t k = 0; k < p.size(); ++k) + { + mvpoly<N-1, CoeffT>::template shift_impl<i+1>(p[k], I); + } + } + + template <typename T, size_t i> + static + T evaluate_impl(type const& p, std::array<T, N+i> const& X) + { +// T r = zero<T>()(); +// for (size_t k = p.max_degree(); k > 0; --k) +// { +// r += mvpoly<N-1, CoeffT>::template evaluate_impl<T, i+1>(p[k], X); +// r *= X[i]; +// } +// r += mvpoly<N-1, CoeffT>::template evaluate_impl<T, i+1>(p[0], X); + + int n = p.max_degree(); + T r = mvpoly<N-1, CoeffT>::template evaluate_impl<T, i+1>(p[n], X); + for (int k = n - 1; k >= 0; --k) + { + r *= X[i]; + r += mvpoly<N-1, CoeffT>::template evaluate_impl<T, i+1>(p[k], X); + } + return r; + } + + template <size_t M, typename C> + friend struct mvpoly; +}; + +/* + * rank 0 mv poly, that is a scalar value (usually a numeric value), + * the routines implemented here are used only to stop recursion + * (but for_each) + */ +template< typename CoeffT > +struct mvpoly<0, CoeffT> +{ + typedef CoeffT type; + typedef CoeffT coeff_type; + static const size_t rank = 0; + + template <size_t M> + static + void for_each + (type & p, + std::function<void (typename mvpoly<M, CoeffT>::type &)> const& op, + typename std::enable_if_t<(M == 0)>* = 0) + { + op(p); + } + + // multiply_to and divide_to are only helper functions + // to be passed to the for_each routine + static + void multiply_to (type& p, type const& q) + { + p *= q; + } + + static + void divide_to (type& p, type const& c) + { + p /= c; + } + + private: + template <size_t i> + static + void lex_degree_impl (type const &/*p*/, multi_index_type&/*D*/) + { + return; + } + + template <size_t i> + static + bool max_degree_impl (type const &/*p*/, + multi_index_type &/*D*/, + multi_index_type const &/*I*/) + { + return true; + } + + template <size_t i> + static + bool real_degree_impl (type const &/*p*/, + multi_index_type &/*D*/, + multi_index_type const &/*I*/) + { + return true; + } + + template <size_t i> + static + void shift_impl(type &/*p*/, multi_index_type const &/*I*/) + {} + + template <typename T, size_t i> + static + T evaluate_impl(type const &p, std::array<T, i> const &/*X*/) + { + return p; + } + + static + void normalize(type &/*p*/) + {} + + + template <size_t M, typename C> + friend struct mvpoly; +}; + + +/* + * monomial::make generate a mv-poly made up by a single term: + * monomial::make<N>(I,c) == c*X^I, where X=(x_(0), .., x_(N-1)) + */ + +template <size_t N, typename CoeffT> +struct monomial +{ + typedef typename mvpoly<N, CoeffT>::type poly_type; + + static inline + poly_type make(multi_index_type const& I, CoeffT c) + { + if (I.size() != N) // an exponent for each indeterminate + THROW_RANGEERROR ("multi-index with wrong length"); + + return make_impl<0>(I, c); + } + + private: + // at i-th level of recursion I need to pick up the i-th exponent in "I" + // so I pass i as a template parameter, this trick is needed to avoid + // to create a new multi-index at each recursion level: + // (J = I[std::slice[1, I.size()-1, 1)]) that will be more expensive + template <size_t i> + static + poly_type make_impl(multi_index_type const& I, CoeffT c) + { + poly_type p(monomial<N-1,CoeffT>::template make_impl<i+1>(I, c), I[i]); + return p; + } + + // make_impl private require that monomial classes to be each other friend + template <size_t M, typename C> + friend struct monomial; +}; + + +// case N = 0 for stopping recursion +template <typename CoeffT> +struct monomial<0, CoeffT> +{ + private: + template <size_t i> + static + CoeffT make_impl(multi_index_type const &/*I*/, CoeffT c) + { + return c; + } + + template<size_t N, typename C> + friend struct monomial; +}; + + +/* + * coefficient<N, PolyT> + * + * N should be in the range [0, rank<PolyT>-1] + * + * "type" == the type of the coefficient of the polynomial with + * rank = rank<PolyT> - N - 1, that is it is the type of the object returned + * by applying the operator[] of a Polynomial object N+1 times; + * + * "zero" represents the zero element (in the group theory meaning) + * for the coefficient type "type"; having it as a static class member + * allows to return always a (const) reference by the "get_safe" method + * + * get(p, I) returns the coefficient of the monomial X^I + * this method doesn't check if such a coefficient really exists, + * so it's up to the user checking that the passed multi-index I is + * not out of range + * + * get_safe(p, I) returns the coefficient of the monomial X^I + * in case such a coefficient doesn't really exist "zero" is returned + * + * set_safe(p, I, c) set the coefficient of the monomial X^I to "c" + * in case such a coefficient doesn't really exist this method creates it + * and creates all monomials X^J with J < I that don't exist yet, setting + * their coefficients to "zero"; + * (with J < I we mean "<" wrt the lexicographic order) + * + */ + +template <size_t N, typename T> +struct coefficient +{ +}; + + +template <size_t N, typename CoeffT> +struct coefficient< N, Polynomial<CoeffT> > +{ + typedef typename coefficient<N-1, CoeffT>::type type; + typedef Polynomial<CoeffT> poly_type; + + static const type zero; + + static + type const& get(poly_type const& p, multi_index_type const& I) + { + if (I.size() != N+1) + THROW_RANGEERROR ("multi-index with wrong length"); + + return get_impl<0>(p, I); + } + + static + type & get(poly_type & p, multi_index_type const& I) + { + if (I.size() != N+1) + THROW_RANGEERROR ("multi-index with wrong length"); + + return get_impl<0>(p, I); + } + + static + type const& get_safe(poly_type const& p, multi_index_type const& I) + { + if (I.size() != N+1) + THROW_RANGEERROR ("multi-index with wrong length"); + + return get_safe_impl<0>(p, I); + } + + static + void set_safe(poly_type & p, multi_index_type const& I, type const& c) + { + if (I.size() != N+1) + THROW_RANGEERROR ("multi-index with wrong length"); + + return set_safe_impl<0>(p, I, c); + } + + private: + template <size_t i> + static + type const& get_impl(poly_type const& p, multi_index_type const& I) + { + return coefficient<N-1, CoeffT>::template get_impl<i+1>(p[I[i]], I); + } + + template <size_t i> + static + type & get_impl(poly_type & p, multi_index_type const& I) + { + return coefficient<N-1, CoeffT>::template get_impl<i+1>(p[I[i]], I); + } + + template <size_t i> + static + type const& get_safe_impl(poly_type const& p, multi_index_type const& I) + { + if (I[i] > p.max_degree()) + { + return zero; + } + else + { + return + coefficient<N-1, CoeffT>::template get_safe_impl<i+1>(p[I[i]], I); + } + } + + template <size_t i> + static + void set_safe_impl(poly_type & p, multi_index_type const& I, type const& c) + { + if (I[i] > p.max_degree()) + { + multi_index_type J = shift(I, i+1); + CoeffT m = monomial<N, type>::make(J, c); + p.coefficient(I[i], m); + } + else + { + coefficient<N-1, CoeffT>::template set_safe_impl<i+1>(p[I[i]], I, c); + } + } + + template<size_t M, typename T> + friend struct coefficient; + +}; + +// initialization of static member zero +template <size_t N, typename CoeffT> +const typename coefficient< N, Polynomial<CoeffT> >::type +coefficient< N, Polynomial<CoeffT> >::zero + = Geom::SL::zero<typename coefficient< N, Polynomial<CoeffT> >::type >()(); + + +// case N = 0 for stopping recursion +template <typename CoeffT> +struct coefficient< 0, Polynomial<CoeffT> > +{ + typedef CoeffT type; + typedef Polynomial<CoeffT> poly_type; + + static const type zero; + + static + type const& get(poly_type const& p, multi_index_type const& I) + { + if (I.size() != 1) + THROW_RANGEERROR ("multi-index with wrong length"); + + return p[I[0]]; + } + + static + type & get(poly_type & p, multi_index_type const& I) + { + if (I.size() != 1) + THROW_RANGEERROR ("multi-index with wrong length"); + + return p[I[0]]; + } + + static + type const& get_safe(poly_type const& p, multi_index_type const& I) + { + if (I.size() != 1) + THROW_RANGEERROR ("multi-index with wrong length"); + + return p.coefficient(I[0]); + } + + static + void set_safe(poly_type & p, multi_index_type const& I, type const& c) + { + if (I.size() != 1) + THROW_RANGEERROR ("multi-index with wrong length"); + + p.coefficient(I[0], c); + } + + private: + template <size_t i> + static + type const& get_impl(poly_type const& p, multi_index_type const& I) + { + return p[I[i]]; + } + + template <size_t i> + static + type & get_impl(poly_type & p, multi_index_type const& I) + { + return p[I[i]]; + } + + template <size_t i> + static + type const& get_safe_impl(poly_type const& p, multi_index_type const& I) + { + return p.coefficient(I[i]); + } + + template <size_t i> + static + void set_safe_impl(poly_type & p, multi_index_type const& I, type const& c) + { + p.coefficient(I[i], c); + } + + template<size_t M, typename T> + friend struct coefficient; +}; + +// initialization of static member zero +template <typename CoeffT> +const typename coefficient< 0, Polynomial<CoeffT> >::type +coefficient< 0, Polynomial<CoeffT> >::zero + = Geom::SL::zero<typename coefficient< 0, Polynomial<CoeffT> >::type >()(); + + +/* + * ordering types: + * lex : lexicographic ordering + * ilex : inverse lexicographic ordering + * max_lex : max degree + lexicographic ordering for disambiguation + * + */ + +namespace ordering +{ + struct lex; // WARNING: at present only lex ordering is supported + struct ilex; + struct max_lex; +} + + +/* + * degree of a mv poly wrt a given ordering + */ + +template <size_t N, typename CoeffT, typename OrderT = ordering::lex> +struct mvdegree +{}; + +template <size_t N, typename CoeffT> +struct mvdegree<N, CoeffT, ordering::lex> +{ + typedef typename mvpoly<N, CoeffT>::type poly_type; + typedef ordering::lex ordering; + + static + multi_index_type value(poly_type const& p) + { + return Geom::SL::mvpoly<N, CoeffT>::lex_degree(p); + } +}; + +} /*end namespace Geom*/ } /*end namespace SL*/ + + +#endif // _GEOM_SL_MVPOLY_TOOLS_H_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/symbolic/polynomial.h b/include/2geom/symbolic/polynomial.h new file mode 100644 index 0000000..fea7e6c --- /dev/null +++ b/include/2geom/symbolic/polynomial.h @@ -0,0 +1,569 @@ +/* + * Polynomial<CoeffT> class template + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef _GEOM_SL_POLYNOMIAL_H_ +#define _GEOM_SL_POLYNOMIAL_H_ + + +#include <2geom/symbolic/unity-builder.h> + +#include <vector> +#include <string> + +#include <2geom/exception.h> + + + + +namespace Geom { namespace SL { + +/* + * Polynomial<CoeffT> class template + * + * It represents a generic univariate polynomial with coefficients + * of type CoeffT. One way to get a multi-variate polynomial is + * to utilize a Polynomial instantiation as coefficient type + * in a recursive style. + * + */ + +template< typename CoeffT > +class Polynomial +{ + public: + typedef CoeffT coeff_type; + typedef std::vector<coeff_type> coeff_container_t; + typedef typename coeff_container_t::iterator iterator; + typedef typename coeff_container_t::const_iterator const_iterator; + + /* + * a Polynomial should be never empty + */ + Polynomial() + { + m_coeff.push_back(zero_coeff); + } + + explicit + Polynomial(CoeffT const& c, size_t i = 0) + { + m_coeff.resize(i, zero_coeff); + m_coeff.push_back(c); + } + + /* + * forwarding of some std::vector methods + */ + + size_t size() const + { + return m_coeff.size(); + } + + const_iterator begin() const + { + return m_coeff.begin(); + } + + const_iterator end() const + { + return m_coeff.end(); + } + + iterator begin() + { + return m_coeff.begin(); + } + + iterator end() + { + return m_coeff.end(); + } + + void reserve(size_t n) + { + m_coeff.reserve(n); + } + + size_t capacity() const + { + return m_coeff.capacity(); + } + + /* + * degree of the term with the highest degree + * and an initialized coefficient (even if zero) + */ + size_t max_degree() const + { + if (size() == 0) + THROW_INVARIANTSVIOLATION (0); + + return (size() - 1); + } + + void max_degree(size_t n) + { + m_coeff.resize(n+1, zero_coeff); + } + + /* + * degree of the term with the highest degree + * and an initialized coefficient that is not null + */ + size_t real_degree() const + { + if (size() == 0) + THROW_INVARIANTSVIOLATION (0); + + const_iterator it = end() - 1; + for (; it != begin(); --it) + { + if (*it != zero_coeff) break; + } + size_t i = static_cast<size_t>(it - begin()); + return i; + } + + bool is_zero() const + { + if (size() == 0) + THROW_INVARIANTSVIOLATION (0); + + if (real_degree() != 0) return false; + if (m_coeff[0] != zero_coeff) return false; + return true; + } + + /* + * trim leading zero coefficients + * after calling normalize max_degree == real_degree + */ + void normalize() + { + size_t rd = real_degree(); + if (rd != max_degree()) + { + m_coeff.erase(begin() + rd + 1, end()); + } + } + + coeff_type const& operator[] (size_t i) const + { + return m_coeff[i]; + } + + coeff_type & operator[] (size_t i) + { + return m_coeff[i]; + } + + // safe coefficient getter routine + coeff_type const& coefficient(size_t i) const + { + if (i > max_degree()) + { + return zero_coeff; + } + else + { + return m_coeff[i]; + } + } + + // safe coefficient setter routine + void coefficient(size_t i, coeff_type const& c) + { + //std::cerr << "i: " << i << " c: " << c << std::endl; + if (i > max_degree()) + { + if (c == zero_coeff) return; + reserve(i+1); + m_coeff.resize(i, zero_coeff); + m_coeff.push_back(c); + } + else + { + m_coeff[i] = c; + } + } + + coeff_type const& leading_coefficient() const + { + return m_coeff[real_degree()]; + } + + coeff_type & leading_coefficient() + { + return m_coeff[real_degree()]; + } + + /* + * polynomail evaluation: + * T can be any type that is able to be + and * with the coefficient type + */ + template <typename T> + T operator() (T const& x) const + { + T r = zero<T>()(); + for(size_t i = max_degree(); i > 0; --i) + { + r += (*this)[i]; + r *= x; + } + r += (*this)[0]; + return r; + } + + // opposite polynomial + Polynomial operator-() const + { + Polynomial r; + // we need r.m_coeff to be empty so we can utilize push_back + r.m_coeff.pop_back(); + r.reserve(size()); + for(size_t i = 0; i < size(); ++i) + { + r.m_coeff.push_back( -(*this)[i] ); + } + return r; + } + + /* + * polynomial-polynomial mutating operators + */ + + Polynomial& operator+=(Polynomial const& p) + { + size_t sz = std::min(size(), p.size()); + for (size_t i = 0; i < sz; ++i) + { + (*this)[i] += p[i]; + } + if (size() < p.size()) + { + m_coeff.insert(end(), p.begin() + size(), p.end()); + } + return (*this); + } + + Polynomial& operator-=(Polynomial const& p) + { + size_t sz = std::min(size(), p.size()); + for (size_t i = 0; i < sz; ++i) + { + (*this)[i] -= p[i]; + } + reserve(p.size()); + for(size_t i = sz; i < p.size(); ++i) + { + m_coeff.push_back( -p[i] ); + } + return (*this); + } + + Polynomial& operator*=(Polynomial const& p) + { + Polynomial r; + r.m_coeff.resize(size() + p.size() - 1, zero_coeff); + + for (size_t i = 0; i < size(); ++i) + { + for (size_t j = 0; j < p.size(); ++j) + { + r[i+j] += (*this)[i] * p[j]; + } + } + (*this) = r; + return (*this); + } + + /* + * equivalent to multiply by x^n + */ + Polynomial& operator<<=(size_t n) + { + m_coeff.insert(begin(), n, zero_coeff); + return (*this); + } + + /* + * polynomial-coefficient mutating operators + */ + + Polynomial& operator=(coeff_type const& c) + { + m_coeff[0] = c; + return (*this); + } + + Polynomial& operator+=(coeff_type const& c) + { + (*this)[0] += c; + return (*this); + } + + Polynomial& operator-=(coeff_type const& c) + { + (*this)[0] -= c; + return (*this); + } + + Polynomial& operator*=(coeff_type const& c) + { + for (size_t i = 0; i < size(); ++i) + { + (*this)[i] *= c; + } + return (*this); + } + + // return the poly in a string form + std::string str() const; + + private: + // with zero_coeff defined as a static data member + // coefficient(size_t i) safe get method can always + // return a (const) reference + static const coeff_type zero_coeff; + coeff_container_t m_coeff; + +}; // end class Polynomial + + +/* + * zero and one element spezcialization for Polynomial + */ + +template< typename CoeffT > +struct zero<Polynomial<CoeffT>, false> +{ + Polynomial<CoeffT> operator() () const + { + CoeffT zc = zero<CoeffT>()(); + Polynomial<CoeffT> z(zc); + return z; + } +}; + +template< typename CoeffT > +struct one<Polynomial<CoeffT>, false> +{ + Polynomial<CoeffT> operator() () + { + CoeffT _1c = one<CoeffT>()(); + Polynomial<CoeffT> _1(_1c); + return _1; + } +}; + + +/* + * initialization of Polynomial static data members + */ + +template< typename CoeffT > +const typename Polynomial<CoeffT>::coeff_type Polynomial<CoeffT>::zero_coeff + = zero<typename Polynomial<CoeffT>::coeff_type>()(); + +/* + * Polynomial - Polynomial binary mathematical operators + */ + +template< typename CoeffT > +inline +bool operator==(Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q) +{ + size_t d = p.real_degree(); + if (d != q.real_degree()) return false; + for (size_t i = 0; i <= d; ++i) + { + if (p[i] != q[i]) return false; + } + return true; +} + +template< typename CoeffT > +inline +bool operator!=(Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q) +{ + return !(p == q); +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator+( Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q ) +{ + Polynomial<CoeffT> r(p); + r += q; + return r; +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator-( Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q ) +{ + Polynomial<CoeffT> r(p); + r -= q; + return r; +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator*( Polynomial<CoeffT> const& p, Polynomial<CoeffT> const& q ) +{ + Polynomial<CoeffT> r(p); + r *= q; + return r; +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> operator<<(Polynomial<CoeffT> const& p, size_t n) +{ + Polynomial<CoeffT> r(p); + r <<= n; + return r; +} + + +/* + * polynomial-coefficient and coefficient-polynomial mathematical operators + */ + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator+( Polynomial<CoeffT> const& p, CoeffT const& c ) +{ + Polynomial<CoeffT> r(p); + r += c; + return r; +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator+( CoeffT const& c, Polynomial<CoeffT> const& p) +{ + return (p + c); +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator-( Polynomial<CoeffT> const& p, CoeffT const& c ) +{ + Polynomial<CoeffT> r(p); + r -= c; + return r; +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator-( CoeffT const& c, Polynomial<CoeffT> const& p) +{ + return (p - c); +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator*( Polynomial<CoeffT> const& p, CoeffT const& c ) +{ + Polynomial<CoeffT> r(p); + r *= c; + return r; +} + +template< typename CoeffT > +inline +Polynomial<CoeffT> +operator*( CoeffT const& c, Polynomial<CoeffT> const& p) +{ + return (p * c); +} + + +/* + * operator<< extension for printing Polynomial + * and str() method for transforming a Polynomial into a string + */ + +template< typename charT, typename CoeffT > +inline +std::basic_ostream<charT> & +operator<< (std::basic_ostream<charT> & os, const Polynomial<CoeffT> & p) +{ + if (p.size() == 0) return os; + os << "{" << p[0]; + for (size_t i = 1; i < p.size(); ++i) + { + os << ", " << p[i]; + } + os << "}"; + return os; +} + + +template< typename CoeffT > +inline +std::string Polynomial<CoeffT>::str() const +{ + std::ostringstream oss; + oss << (*this); + return oss.str(); +} + + +} /*end namespace Geom*/ } /*end namespace SL*/ + + + + +#endif // _GEOM_SL_POLYNOMIAL_H_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/symbolic/unity-builder.h b/include/2geom/symbolic/unity-builder.h new file mode 100644 index 0000000..cb8046f --- /dev/null +++ b/include/2geom/symbolic/unity-builder.h @@ -0,0 +1,102 @@ +/* + * Routines to make up "zero" and "one" elements of a ring + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * + * Copyright 2008 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef _GEOM_SL_UNITY_BUILDER_H_ +#define _GEOM_SL_UNITY_BUILDER_H_ + + +#include <type_traits> + + + +namespace Geom { namespace SL { + + +/* + * zero builder function class type + * + * made up a zero element, in the algebraic ring theory meaning, + * for the type T + */ + +template< typename T, bool numeric = std::is_arithmetic<T>::value > +struct zero +{}; + +// specialization for basic numeric type +template< typename T > +struct zero<T, true> +{ + T operator() () const + { + return 0; + } +}; + + +/* + * one builder function class type + * + * made up a one element, in the algebraic ring theory meaning, + * for the type T + */ + +template< typename T, bool numeric = std::is_arithmetic<T>::value > +struct one +{}; + +// specialization for basic numeric type +template< typename T > +struct one<T, true> +{ + T operator() () + { + return 1; + } +}; + +} /*end namespace Geom*/ } /*end namespace SL*/ + + +#endif // _GEOM_SL_UNITY_BUILDER_H_ + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/transforms.h b/include/2geom/transforms.h new file mode 100644 index 0000000..cc55e29 --- /dev/null +++ b/include/2geom/transforms.h @@ -0,0 +1,370 @@ +/** + * @file + * @brief Affine transformation classes + *//* + * Authors: + * ? <?@?.?> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Johan Engelen + * + * Copyright ?-2012 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_TRANSFORMS_H +#define LIB2GEOM_SEEN_TRANSFORMS_H + +#include <cmath> +#include <2geom/forward.h> +#include <2geom/affine.h> +#include <2geom/angle.h> +#include <boost/concept/assert.hpp> + +namespace Geom { + +/** @brief Type requirements for transforms. + * @ingroup Concepts */ +template <typename T> +struct TransformConcept { + T t, t2; + Affine m; + Point p; + bool bool_; + Coord epsilon; + void constraints() { + m = t; //implicit conversion + m *= t; + m = m * t; + m = t * m; + p *= t; + p = p * t; + t *= t; + t = t * t; + t = pow(t, 3); + bool_ = (t == t); + bool_ = (t != t); + t = T::identity(); + t = t.inverse(); + bool_ = are_near(t, t2); + bool_ = are_near(t, t2, epsilon); + } +}; + +/** @brief Base template for transforms. + * This class is an implementation detail and should not be used directly. */ +template <typename T> +class TransformOperations + : boost::equality_comparable< T + , boost::multipliable< T + > > +{ +public: + template <typename T2> + Affine operator*(T2 const &t) const { + Affine ret(*static_cast<T const*>(this)); ret *= t; return ret; + } +}; + +/** @brief Integer exponentiation for transforms. + * Negative exponents will yield the corresponding power of the inverse. This function + * can also be applied to matrices. + * @param t Affine or transform to exponantiate + * @param n Exponent + * @return \f$A^n\f$ if @a n is positive, \f$(A^{-1})^n\f$ if negative, identity if zero. + * @ingroup Transforms */ +template <typename T> +T pow(T const &t, int n) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + if (n == 0) return T::identity(); + T result(T::identity()); + T x(n < 0 ? t.inverse() : t); + if (n < 0) n = -n; + while ( n ) { // binary exponentiation - fast + if ( n & 1 ) { result *= x; --n; } + x *= x; n /= 2; + } + return result; +} + +/** @brief Translation by a vector. + * @ingroup Transforms */ +class Translate + : public TransformOperations< Translate > +{ + Point vec; +public: + /// Create a translation that doesn't do anything. + Translate() : vec(0, 0) {} + /// Construct a translation from its vector. + Translate(Point const &p) : vec(p) {} + /// Construct a translation from its coordinates. + Translate(Coord x, Coord y) : vec(x, y) {} + + operator Affine() const { Affine ret(1, 0, 0, 1, vec[X], vec[Y]); return ret; } + Coord operator[](Dim2 dim) const { return vec[dim]; } + Coord operator[](unsigned dim) const { return vec[dim]; } + Translate &operator*=(Translate const &o) { vec += o.vec; return *this; } + bool operator==(Translate const &o) const { return vec == o.vec; } + + Point vector() const { return vec; } + /// Get the inverse translation. + Translate inverse() const { return Translate(-vec); } + /// Get a translation that doesn't do anything. + static Translate identity() { Translate ret; return ret; } + + friend class Point; +}; + +inline bool are_near(Translate const &a, Translate const &b, Coord eps=EPSILON) { + return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps); +} + +/** @brief Scaling from the origin. + * During scaling, the point (0,0) will not move. To obtain a scale with a different + * invariant point, combine with translation to the origin and back. + * @ingroup Transforms */ +class Scale + : public TransformOperations< Scale > +{ + Point vec; +public: + /// Create a scaling that doesn't do anything. + Scale() : vec(1, 1) {} + /// Create a scaling from two scaling factors given as coordinates of a point. + explicit Scale(Point const &p) : vec(p) {} + /// Create a scaling from two scaling factors. + Scale(Coord x, Coord y) : vec(x, y) {} + /// Create an uniform scaling from a single scaling factor. + explicit Scale(Coord s) : vec(s, s) {} + inline operator Affine() const { Affine ret(vec[X], 0, 0, vec[Y], 0, 0); return ret; } + + Coord operator[](Dim2 d) const { return vec[d]; } + Coord operator[](unsigned d) const { return vec[d]; } + //TODO: should we keep these mutators? add them to the other transforms? + Coord &operator[](Dim2 d) { return vec[d]; } + Coord &operator[](unsigned d) { return vec[d]; } + Scale &operator*=(Scale const &b) { vec[X] *= b[X]; vec[Y] *= b[Y]; return *this; } + bool operator==(Scale const &o) const { return vec == o.vec; } + + Point vector() const { return vec; } + Scale inverse() const { return Scale(1./vec[0], 1./vec[1]); } + static Scale identity() { Scale ret; return ret; } + + friend class Point; +}; + +inline bool are_near(Scale const &a, Scale const &b, Coord eps=EPSILON) { + return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps); +} + +/** @brief Rotation around the origin. + * Combine with translations to the origin and back to get a rotation around a different point. + * @ingroup Transforms */ +class Rotate + : public TransformOperations< Rotate > +{ + Point vec; ///< @todo Convert to storing the angle, as it's more space-efficient. +public: + /// Construct a zero-degree rotation. + Rotate() : vec(1, 0) {} + /** @brief Construct a rotation from its angle in radians. + * Positive arguments correspond to counter-clockwise rotations (if Y grows upwards). */ + explicit Rotate(Coord theta) : vec(Point::polar(theta)) {} + /// Construct a rotation from its characteristic vector. + explicit Rotate(Point const &p) : vec(unit_vector(p)) {} + /// Construct a rotation from the coordinates of its characteristic vector. + explicit Rotate(Coord x, Coord y) { Rotate(Point(x, y)); } + operator Affine() const { Affine ret(vec[X], vec[Y], -vec[Y], vec[X], 0, 0); return ret; } + + /** @brief Get the characteristic vector of the rotation. + * @return A vector that would be obtained by applying this transform to the X versor. */ + Point vector() const { return vec; } + Coord angle() const { return atan2(vec); } + Coord operator[](Dim2 dim) const { return vec[dim]; } + Coord operator[](unsigned dim) const { return vec[dim]; } + Rotate &operator*=(Rotate const &o) { vec *= o; return *this; } + bool operator==(Rotate const &o) const { return vec == o.vec; } + Rotate inverse() const { + Rotate r; + r.vec = Point(vec[X], -vec[Y]); + return r; + } + /// @brief Get a zero-degree rotation. + static Rotate identity() { Rotate ret; return ret; } + /** @brief Construct a rotation from its angle in degrees. + * Positive arguments correspond to clockwise rotations if Y grows downwards. */ + static Rotate from_degrees(Coord deg) { + Coord rad = (deg / 180.0) * M_PI; + return Rotate(rad); + } + static Affine around(Point const &p, Coord angle); + + friend class Point; +}; + +inline bool are_near(Rotate const &a, Rotate const &b, Coord eps=EPSILON) { + return are_near(a[X], b[X], eps) && are_near(a[Y], b[Y], eps); +} + +/** @brief Common base for shearing transforms. + * This class is an implementation detail and should not be used directly. + * @ingroup Transforms */ +template <typename S> +class ShearBase + : public TransformOperations< S > +{ +protected: + Coord f; + ShearBase(Coord _f) : f(_f) {} +public: + Coord factor() const { return f; } + void setFactor(Coord nf) { f = nf; } + S &operator*=(S const &s) { f += s.f; return static_cast<S &>(*this); } + bool operator==(S const &s) const { return f == s.f; } + S inverse() const { S ret(-f); return ret; } + static S identity() { S ret(0); return ret; } + + friend class Point; + friend class Affine; +}; + +/** @brief Horizontal shearing. + * Points on the X axis will not move. Combine with translations to get a shear + * with a different invariant line. + * @ingroup Transforms */ +class HShear + : public ShearBase<HShear> +{ +public: + explicit HShear(Coord h) : ShearBase<HShear>(h) {} + operator Affine() const { Affine ret(1, 0, f, 1, 0, 0); return ret; } +}; + +inline bool are_near(HShear const &a, HShear const &b, Coord eps=EPSILON) { + return are_near(a.factor(), b.factor(), eps); +} + +/** @brief Vertical shearing. + * Points on the Y axis will not move. Combine with translations to get a shear + * with a different invariant line. + * @ingroup Transforms */ +class VShear + : public ShearBase<VShear> +{ +public: + explicit VShear(Coord h) : ShearBase<VShear>(h) {} + operator Affine() const { Affine ret(1, f, 0, 1, 0, 0); return ret; } +}; + +inline bool are_near(VShear const &a, VShear const &b, Coord eps=EPSILON) { + return are_near(a.factor(), b.factor(), eps); +} + +/** @brief Combination of a translation and uniform scale. + * The translation part is applied first, then the result is scaled from the new origin. + * This way when the class is used to accumulate a zoom transform, trans always points + * to the new origin in original coordinates. + * @ingroup Transforms */ +class Zoom + : public TransformOperations< Zoom > +{ + Coord _scale; + Point _trans; + Zoom() : _scale(1), _trans() {} +public: + /// Construct a zoom from a scaling factor. + explicit Zoom(Coord s) : _scale(s), _trans() {} + /// Construct a zoom from a translation. + explicit Zoom(Translate const &t) : _scale(1), _trans(t.vector()) {} + /// Construct a zoom from a scaling factor and a translation. + Zoom(Coord s, Translate const &t) : _scale(s), _trans(t.vector()) {} + + operator Affine() const { + Affine ret(_scale, 0, 0, _scale, _trans[X] * _scale, _trans[Y] * _scale); + return ret; + } + Zoom &operator*=(Zoom const &z) { + _trans += z._trans / _scale; + _scale *= z._scale; + return *this; + } + bool operator==(Zoom const &z) const { return _scale == z._scale && _trans == z._trans; } + + Coord scale() const { return _scale; } + void setScale(Coord s) { _scale = s; } + Point translation() const { return _trans; } + void setTranslation(Point const &p) { _trans = p; } + Zoom inverse() const { Zoom ret(1/_scale, Translate(-_trans*_scale)); return ret; } + static Zoom identity() { Zoom ret(1.0); return ret; } + static Zoom map_rect(Rect const &old_r, Rect const &new_r); + + friend class Point; + friend class Affine; +}; + +inline bool are_near(Zoom const &a, Zoom const &b, Coord eps=EPSILON) { + return are_near(a.scale(), b.scale(), eps) && + are_near(a.translation(), b.translation(), eps); +} + +/** @brief Specialization of exponentiation for Scale. + * @relates Scale */ +template<> +inline Scale pow(Scale const &s, int n) { + Scale ret(::pow(s[X], n), ::pow(s[Y], n)); + return ret; +} +/** @brief Specialization of exponentiation for Translate. + * @relates Translate */ +template<> +inline Translate pow(Translate const &t, int n) { + Translate ret(t[X] * n, t[Y] * n); + return ret; +} + + +/** @brief Reflects objects about line. + * The line, defined by a vector along the line and a point on it, acts as a mirror. + * @ingroup Transforms + * @see Line::reflection() + */ +Affine reflection(Point const & vector, Point const & origin); + +//TODO: decomposition of Affine into some finite combination of the above classes + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_TRANSFORMS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/2geom/utils.h b/include/2geom/utils.h new file mode 100644 index 0000000..87f49bb --- /dev/null +++ b/include/2geom/utils.h @@ -0,0 +1,114 @@ +/** + * \file + * \brief Various utility functions. + *//* + * Copyright 2007 Johan Engelen <goejendaagh@zonnet.nl> + * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_UTILS_H +#define LIB2GEOM_SEEN_UTILS_H + +#include <cstddef> +#include <vector> +#include <boost/operators.hpp> + +namespace Geom { + +// Throw these errors instead of aserting so code can handle them if needed. +using ErrorCode = int; +enum Errors : ErrorCode { + GEOM_ERR_INTERSECGRAPH, +}; + +void binomial_coefficients(std::vector<size_t>& bc, std::size_t n); + +struct EmptyClass {}; + +/** + * @brief Noncommutative multiplication helper. + * Generates operator*(T, U) from operator*=(T, U). Does not generate operator*(U, T) + * like boost::multipliable does. This makes it suitable for noncommutative cases, + * such as transforms. + */ +template <class T, class U, class B = EmptyClass> +struct MultipliableNoncommutative : B +{ + friend T operator*(T const &lhs, U const &rhs) { + T nrv(lhs); nrv *= rhs; return nrv; + } +}; + +/** @brief Null output iterator + * Use this if you want to discard a result returned through an output iterator. */ +struct NullIterator + : public boost::output_iterator_helper<NullIterator> +{ + NullIterator() {} + + template <typename T> + void operator=(T const &) {} +}; + +/** @brief Get the next iterator in the container with wrap-around. + * If the iterator would become the end iterator after incrementing, + * return the begin iterator instead. */ +template <typename Iter, typename Container> +Iter cyclic_next(Iter i, Container &c) { + ++i; + if (i == c.end()) { + i = c.begin(); + } + return i; +} + +/** @brief Get the previous iterator in the container with wrap-around. + * If the passed iterator is the begin iterator, return the iterator + * just before the end iterator instead. */ +template <typename Iter, typename Container> +Iter cyclic_prior(Iter i, Container &c) { + if (i == c.begin()) { + i = c.end(); + } + --i; + return i; +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_UTILS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/toys/lpe-framework.h b/include/toys/lpe-framework.h new file mode 100644 index 0000000..9de5aa0 --- /dev/null +++ b/include/toys/lpe-framework.h @@ -0,0 +1,77 @@ +/** + * \file lpe-framework.h + * \brief A framework for writing an Inkscape Live Path Effect toy. + * + * Instead of using the standard toy framework, one can use this LPE + * framework when creating an LPE for Inkscape. When new 2geom functions + * are required for the LPE, adding this functionality directly in Inkscape + * greatly increases compile times. Using this framework, one only has to + * rebuild 2geom, which speeds up development considerably. (Think about + * how much of Inkscape will have to be rebuild if you change/add something + * in point.h ...) + * An example of how to use this framework is lpe.test.cpp. + *//* + * Copyright 2009 Johan Engelen <goejendaagh@zonnet.nl> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef _2GEOM_LPE_TOY_FRAMEWORK_H_ +#define _2GEOM_LPE_TOY_FRAMEWORK_H_ + +/** + * This should greatly simplify creating toy code for a Live Path Effect (LPE) for Inkscape + */ + + +#include <toys/toy-framework-2.h> +#include <2geom/pathvector.h> + +class LPEToy: public Toy { +public: + LPEToy(); + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override; + virtual Geom::PathVector + doEffect_path (Geom::PathVector const & path_in); + virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > + doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in); + + /** this boolean defaults to false, it concatenates the input path to one pwd2, + * instead of normally 'splitting' the path into continuous pwd2 paths. */ + bool concatenate_before_pwd2; + PointSetHandle curve_handle; +}; + +#endif // _2GEOM_LPE_TOY_FRAMEWORK_H_ +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/include/toys/path-cairo.h b/include/toys/path-cairo.h new file mode 100644 index 0000000..4c0403f --- /dev/null +++ b/include/toys/path-cairo.h @@ -0,0 +1,57 @@ +#ifndef PATH_CAIRO +#define PATH_CAIRO + + +#include <cairo.h> +#include <2geom/line.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/d2.h> +#include <2geom/piecewise.h> +#include <2geom/path.h> +#include <2geom/convex-hull.h> +#include <vector> +#include <string> + +typedef struct _cairo cairo_t; + +void cairo_curve(cairo_t *cr, Geom::Curve const &c); +void cairo_rectangle(cairo_t *cr, Geom::Rect const &r); +void cairo_convex_hull(cairo_t *cr, Geom::ConvexHull const &r); +void cairo_path(cairo_t *cr, Geom::Path const &p); +void cairo_path(cairo_t *cr, Geom::PathVector const &p); +void cairo_path_stitches(cairo_t *cr, Geom::Path const &p); +void cairo_path_stitches(cairo_t *cr, Geom::PathVector const &p); + +void cairo_d2_sb(cairo_t *cr, Geom::D2<Geom::SBasis> const &p); +void cairo_d2_sb_handles(cairo_t *cr, Geom::D2<Geom::SBasis> const &p); +void cairo_d2_sb2d(cairo_t* cr, Geom::D2<Geom::SBasis2d> const &sb2, Geom::Point dir, double width); +void cairo_sb2d(cairo_t* cr, Geom::SBasis2d const &sb2, Geom::Point dir, double width); + +void cairo_d2_pw_sb(cairo_t *cr, Geom::D2<Geom::Piecewise<Geom::SBasis> > const &p); +void cairo_pw_d2_sb(cairo_t *cr, Geom::Piecewise<Geom::D2<Geom::SBasis> > const &p); + + +void draw_line(cairo_t *cr, const Geom::Line& l, const Geom::Rect& r); +void draw_line(cairo_t *cr, Geom::Point n, double d, Geom::Rect r); +void draw_line(cairo_t *cr, Geom::Point a, Geom::Point b, Geom::Rect r); + +void draw_line_seg(cairo_t *cr, Geom::Point a, Geom::Point b); +void draw_line_seg_with_arrow(cairo_t *cr, Geom::Point a, Geom::Point b, double dangle = 15*M_PI/180, double radius = 20); +void draw_spot(cairo_t *cr, Geom::Point h); +void draw_handle(cairo_t *cr, Geom::Point h); +void draw_cross(cairo_t *cr, Geom::Point h); +void draw_circ(cairo_t *cr, Geom::Point h); +void draw_ray(cairo_t *cr, Geom::Point h, Geom::Point dir); +void draw_ray(cairo_t *cr, const Geom::Ray& ray, const Geom::Rect& r); +void draw_line_segment(cairo_t *cr, const Geom::LineSegment& ls, const Geom::Rect& r); + +void cairo_move_to(cairo_t *cr, Geom::Point p1); +void cairo_line_to(cairo_t *cr, Geom::Point p1); +void cairo_curve_to(cairo_t *cr, Geom::Point p1, Geom::Point p2, Geom::Point p3); + +// H in [0,360) +// S, V, R, G, B in [0,1] +void convertHSVtoRGB(const double H, const double S, const double V, + double& R, double& G, double& B); +#endif diff --git a/include/toys/toy-framework-2.h b/include/toys/toy-framework-2.h new file mode 100644 index 0000000..5504dfd --- /dev/null +++ b/include/toys/toy-framework-2.h @@ -0,0 +1,451 @@ + +#ifndef _2GEOM_TOY_FRAMEWORK2_H_ +#define _2GEOM_TOY_FRAMEWORK2_H_ + + + +#include <cairo.h> +#include <gtk/gtk.h> +#include <iostream> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include <assert.h> +#include <2geom/exception.h> +#include <2geom/point.h> +#include <2geom/geom.h> +#include <2geom/sbasis.h> +#include <2geom/d2.h> +#include <sched.h> +#include <toys/path-cairo.h> + +using std::vector; + +//Utility functions +double uniform(); + +void draw_text(cairo_t *cr, Geom::Point pos, const char* txt, bool bottom = false, const char* fontdesc = "Sans"); +void draw_text(cairo_t *cr, Geom::Point pos, const std::string& txt, bool bottom = false, const std::string& fontdesc = "Sans"); +void draw_number(cairo_t *cr, Geom::Point pos, int num, std::string name=std::string(), bool bottom = true); +void draw_number(cairo_t *cr, Geom::Point pos, unsigned num, std::string name=std::string(), bool bottom = true); +void draw_number(cairo_t *cr, Geom::Point pos, double num, std::string name=std::string(), bool bottom = true); + +struct colour{ + double r,g,b,a; + colour(double r, double g, double b, double a) : r(r), g(g), b(b), a(a) {} + static colour from_hsv( float H, // hue shift (radians) + float S, // saturation shift (scalar) + float V, // value multiplier (scalar) + float A + ); + static colour from_hsl( float H, // hue shift (radians) + float S, // saturation shift (scalar) + float L, // value multiplier (scalar) + float A + ); +}; +void cairo_set_source_rgba(cairo_t* cr, colour c); + +class Handle{ +public: + std::string name; + float rgb[3]; + Handle() {rgb[0] = rgb[1] = rgb[2] = 0;} + virtual ~Handle() {} + virtual void draw(cairo_t *cr, bool annotes=false) = 0; + + virtual void* hit(Geom::Point pos) = 0; + virtual void move_to(void* hit, Geom::Point om, Geom::Point m) = 0; + virtual void load(FILE* f)=0; + virtual void save(FILE* f)=0; +}; + +class Toggle : public Handle{ +public: + Geom::Rect bounds; + const char* text; + bool on; + Toggle() : bounds(Geom::Point(0,0), Geom::Point(0,0)), text(""), on(false) {} + Toggle(const char* txt, bool v) : bounds(Geom::Point(0,0), Geom::Point(0,0)), text(txt), on(v) {} + Toggle(Geom::Rect bnds, const char* txt, bool v) : bounds(bnds), text(txt), on(v) {} + void draw(cairo_t *cr, bool annotes = false) override; + void toggle(); + void set(bool state); + void handle_click(GdkEventButton* e); + void* hit(Geom::Point pos) override; + void move_to(void* /*hit*/, Geom::Point /*om*/, Geom::Point /*m*/) override { /* not implemented */ } + void load(FILE* /*f*/) override { /* not implemented */ } + void save(FILE* /*f*/) override { /* not implemented */ } +}; + + +template< typename T> +class VectorHandle : public Handle +{ + public: + VectorHandle() + : m_handles() + { + } + void draw(cairo_t *cr, bool annotes=false) override + { + for (iterator it = m_handles.begin(); it != m_handles.end(); ++it) + it->draw(cr, annotes); + } + + void* hit(Geom::Point pos) override + { + void* result = NULL; + for (iterator it = m_handles.begin(); it != m_handles.end(); ++it) + { + result = it->hit(pos); + if (result != NULL) break; + } + return result; + } + + void move_to(void* hit, Geom::Point om, Geom::Point m) override + { + if (hit != NULL) + { + static_cast<T*>(hit)->move_to(hit, om, m); + } + } + + void load(FILE* f) override + { + for (iterator it = m_handles.begin(); it != m_handles.end(); ++it) + it->load(f); + } + + void save(FILE* f) override + { + for (iterator it = m_handles.begin(); it != m_handles.end(); ++it) + it->save(f); + } + + void clear() + { + m_handles.clear(); + } + + void reserve(size_t sz) + { + m_handles.reserve(sz); + } + + size_t size() const + { + return m_handles.size(); + } + + void push_back (const T& _handle) + { + m_handles.push_back(_handle); + } + + const T& operator[] (size_t i) const + { + return m_handles.at(i); + } + + T& operator[] (size_t i) + { + return m_handles.at(i); + } + + private: + typedef typename std::vector<T>::iterator iterator; + std::vector<T> m_handles; +}; // end class VectorHandle + + +class PointHandle : public Handle{ +public: + PointHandle(double x, double y) : pos(x,y) {} + PointHandle(Geom::Point pt) : pos(pt) {} + PointHandle() {} + Geom::Point pos; + void draw(cairo_t *cr, bool annotes = false) override; + + void* hit(Geom::Point mouse) override; + void move_to(void* hit, Geom::Point om, Geom::Point m) override; + void load(FILE* f) override; + void save(FILE* f) override; +}; + +class PointSetHandle : public Handle{ +public: + PointSetHandle() {} + std::vector<Geom::Point> pts; + void draw(cairo_t *cr, bool annotes = false) override; + + void* hit(Geom::Point mouse) override; + void move_to(void* hit, Geom::Point om, Geom::Point m) override; + void push_back(double x, double y) {pts.emplace_back(x,y);} + void push_back(Geom::Point pt) {pts.push_back(pt);} + unsigned size() {return pts.size();} + Geom::D2<Geom::SBasis> asBezier(); + void load(FILE* f) override; + void save(FILE* f) override; +}; + +class RectHandle : public Handle{ +public: + RectHandle() {} + RectHandle(Geom::Rect pos, bool show_center_handle) : pos(pos), show_center_handle(show_center_handle) {} + Geom::Rect pos; + bool show_center_handle; + void draw(cairo_t *cr, bool annotes = false) override; + + void* hit(Geom::Point mouse) override; + void move_to(void* hit, Geom::Point om, Geom::Point m) override; + void load(FILE* f) override; + void save(FILE* f) override; +}; + + +// used by Slider +inline std::string default_formatter(double x) +{ + std::ostringstream os; + os << x; + return os.str(); +} + +class Slider : public Handle +{ + public: + + typedef std::string (*formatter_t) (double ); + typedef double value_type; + + Slider() + : m_handle(), m_pos(Geom::Point(0,0)), m_length(1), + m_min(0), m_max(1), m_step(0), m_dir(Geom::X), + m_label(""), m_formatter(&default_formatter) + { + value(0); + } + + // pass step = 0 for having a continuos value variation + Slider( value_type _min, value_type _max, value_type _step, + value_type _value, std::string _label = "" ) + : m_handle(),m_pos(Geom::Point(0,0)), m_length(1), + m_min(_min), m_max(_max), m_step(_step), m_dir(Geom::X), + m_label(std::move(_label)), m_formatter(&default_formatter) + { + value(_value); + } + + void set( value_type _min, value_type _max, value_type _step, + value_type _value, const std::string& _label = "" ) + { + m_min = _min; + m_max = _max; + m_step = _step; + m_label = _label; + value(_value); + } + + value_type value() const; + + void value(value_type _value); + + value_type max_value() const + { + return m_max; + } + + void max_value(value_type _value); + + value_type min_value() const + { + return m_min; + } + + void min_value(value_type _value); + + // dir = X horizontal slider dir = Y vertical slider + void geometry(Geom::Point _pos, value_type _length, Geom::Dim2 _dir = Geom::X); + + void draw(cairo_t* cr, bool annotate = false) override; + + void formatter( formatter_t _formatter ) + { + m_formatter = _formatter; + } + + void* hit(Geom::Point pos) override + { + if (m_handle.hit(pos) != NULL) + return this; + return NULL; + } + + void move_to(void* hit, Geom::Point om, Geom::Point m) override; + + void load(FILE* f) override + { + m_handle.load(f); + } + + void save(FILE* f) override + { + m_handle.save(f); + } + + private: + PointHandle m_handle; + Geom::Point m_pos; + value_type m_length; + value_type m_min, m_max, m_step; + int m_dir; + std::string m_label; + formatter_t m_formatter; +}; + + + + + +class Toy { +public: + vector<Handle*> handles; + bool mouse_down = false; + Geom::Point old_mouse_point; + Handle* selected = nullptr; + void* hit_data = nullptr; + int canvas_click_button = 0; + double notify_offset = 0.0; + std::string name; + bool show_timings = false; + FILE* spool_file = nullptr; // if non-NULL we record all interactions to this file + + Toy() {} + + virtual ~Toy() {} + + virtual void draw(cairo_t *cr, std::ostringstream *notify, int w, int h, bool save, std::ostringstream *timing_stream); + + virtual void mouse_moved(GdkEventMotion* e); + virtual void mouse_pressed(GdkEventButton* e); + virtual void mouse_released(GdkEventButton* e); + virtual void canvas_click(Geom::Point at, int button); + virtual void scroll(GdkEventScroll* e); + + virtual void key_hit(GdkEventKey */*e*/) {} + + //Cheapo way of informing the framework what the toy would like drawn for it. + virtual bool should_draw_numbers() { return true; } + virtual int should_draw_bounds() { return 0; } + + virtual void first_time(int /*argc*/, char** /*argv*/) {} + + virtual void resize_canvas(Geom::Rect const & /*s*/) {} + virtual void load(FILE* f); + virtual void save(FILE* f); +}; + +//Framework Accesors +void redraw(); +void take_screenshot(const char* file); +void init(int argc, char **argv, Toy *t, int width=600, int height=600); + +void toggle_events(std::vector<Toggle> &ts, GdkEventButton* e); +void draw_toggles(cairo_t *cr, std::vector<Toggle> &ts); +Geom::Point read_point(FILE* f); + + + + + +const long long NS_PER_SECOND = 1000000000LL; +const long long NS_PER_NS = 1; + + +class Timer{ +public: + Timer() {} + // note that CPU time is tracked per-thread, so the timer is only useful + // in the thread it was start()ed from. + + class Time{ + public: + double value; + Time(long long /*s*/, long long l) : value(l) {} + Time(double v) : value(v) {} + Time operator/(double iters) const { + return Time(value / iters); + } + }; + + void start() { + nsec(start_time); + } + void lap(long long &ns) { + nsec(ns); + ns -= start_time; + } + Time lap() { + long long ns; + nsec(ns); + return Time(start_time, ns - start_time); + } + void nsec(long long &ns) { +#if ! (defined(_WIN32) || defined(__APPLE__)) + clock_gettime(clock, &ts); + ns = ts.tv_sec * NS_PER_SECOND + ts.tv_nsec / NS_PER_NS; +#else + ns = 0; +#endif + } + /** Ask the OS nicely for a big time slice */ + void ask_for_timeslice() { +#ifndef _WIN32 + sched_yield(); +#endif + } +private: + long long start_time; +#if ! (defined(_WIN32) || defined(__APPLE__)) + struct timespec ts; +# ifdef _POSIX_THREAD_CPUTIME + static const clockid_t clock = CLOCK_THREAD_CPUTIME_ID; +# else +# ifdef CLOCK_MONOTONIC + static const clockid_t clock = CLOCK_MONOTONIC; +# else + static const clockid_t clock = CLOCK_REALTIME; +# endif +# endif +#endif +}; + +inline std::ostream& operator<<(std::ostream& o, Timer::Time const &t) { + double tm = t.value; + unsigned prefix = 0; + char prefixes[] = "num kMGT"; + while(prefix < sizeof(prefixes) and tm > 1000) { + tm /= 1000.0; + prefix += 1; + } + o << tm << prefixes[prefix] << "s"; + return o; +} + + + +#endif // _2GEOM_TOY_FRAMEWORK2_H_ +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : |