diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
commit | 35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch) | |
tree | 657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/2geom | |
parent | Initial commit. (diff) | |
download | inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.tar.xz inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.zip |
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
120 files changed, 41396 insertions, 0 deletions
diff --git a/src/2geom/!PLEASE DON'T MAKE CHANGES IN THESE FILES.README b/src/2geom/!PLEASE DON'T MAKE CHANGES IN THESE FILES.README new file mode 100644 index 0000000..e0919de --- /dev/null +++ b/src/2geom/!PLEASE DON'T MAKE CHANGES IN THESE FILES.README @@ -0,0 +1,14 @@ +This is an in-tree copy of lib2geom, a 2D geometry library started +by Inkscape developers. If you want to change code in 2Geom, you should +commit it first to upstream repository and then execute this command: + +rsync -r --existing --exclude CMakeLists.txt /path/to/lib2geom/src/2geom/ /path/to/inkscape/src/2geom/ + +The command above will only update existing files. If you add new files +to 2Geom, you'll need to copy the new files manually. Same if you remove +some files. Note that the trailing slashes are required! + +2Geom's git repository is hosted on GitHub + https://github.com/inkscape/lib2geom +and mirrored on GitLab + https://gitlab.com/inkscape/lib2geom diff --git a/src/2geom/2geom.h b/src/2geom/2geom.h new file mode 100644 index 0000000..813e243 --- /dev/null +++ b/src/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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/CMakeLists.txt b/src/2geom/CMakeLists.txt new file mode 100644 index 0000000..e1e708e --- /dev/null +++ b/src/2geom/CMakeLists.txt @@ -0,0 +1,139 @@ +# Override error flag just for this folder +if (CMAKE_BUILD_TYPE MATCHES Strict) + set(CMAKE_CXX_FLAGS_STRICT "${CMAKE_CXX_FLAGS_STRICT} -Wno-error=deprecated-declarations") +endif() + +if(HAVE_SINCOS) + add_definitions(-DHAVE_SINCOS) +endif() + +set(2geom_SRC + affine.cpp + basic-intersection.cpp + bezier.cpp + bezier-clipping.cpp + bezier-curve.cpp + bezier-utils.cpp + cairo-path-sink.cpp + circle.cpp + # conic_section_clipper_impl.cpp + # conicsec.cpp + convex-hull.cpp + coord.cpp + crossing.cpp + curve.cpp + d2-sbasis.cpp + ellipse.cpp + elliptical-arc.cpp + elliptical-arc-from-sbasis.cpp + geom.cpp + intersection-graph.cpp + line.cpp + nearest-time.cpp + numeric/matrix.cpp + path-intersection.cpp + path-sink.cpp + path.cpp + pathvector.cpp + piecewise.cpp + point.cpp + polynomial.cpp + rect.cpp + # recursive-bezier-intersection.cpp + sbasis-2d.cpp + sbasis-geometric.cpp + sbasis-math.cpp + sbasis-poly.cpp + sbasis-roots.cpp + sbasis-to-bezier.cpp + sbasis.cpp + solve-bezier.cpp + solve-bezier-one-d.cpp + solve-bezier-parametric.cpp + svg-path-parser.cpp + svg-path-writer.cpp + sweep-bounds.cpp + transforms.cpp + utils.cpp + + + # ------- + 2geom.h + # Headers + affine.h + angle.h + basic-intersection.h + bezier-curve.h + bezier-to-sbasis.h + bezier-utils.h + bezier.h + cairo-path-sink.h + choose.h + circle.h + concepts.h + conic_section_clipper.h + conic_section_clipper_cr.h + conic_section_clipper_impl.h + conicsec.h + convex-hull.h + coord.h + crossing.h + curve.h + curves.h + d2.h + ellipse.h + elliptical-arc.h + exception.h + forward.h + generic-interval.h + generic-rect.h + geom.h + int-interval.h + int-point.h + int-rect.h + intersection-graph.h + intersection.h + interval.h + line.h + linear.h + math-utils.h + nearest-time.h + ord.h + path-intersection.h + path-sink.h + path.h + pathvector.h + piecewise.h + point.h + polynomial.h + ray.h + rect.h + sbasis-2d.h + sbasis-curve.h + sbasis-geometric.h + sbasis-math.h + sbasis-poly.h + sbasis-to-bezier.h + sbasis.h + solver.h + svg-path-parser.h + svg-path-writer.h + sweep-bounds.h + sweeper.h + transforms.h + utils.h + + numeric/fitting-model.h + numeric/fitting-tool.h + numeric/linear_system.h + numeric/matrix.h + numeric/symmetric-matrix-fs-operation.h + numeric/symmetric-matrix-fs-trace.h + numeric/symmetric-matrix-fs.h + numeric/vector.h +) + +# make lib for 2geom_LIB +add_inkscape_lib(2geom_LIB "${2geom_SRC}") +target_include_directories(2geom_LIB PRIVATE ${DoubleConversion_INCLUDE_DIRS}) +target_link_libraries(2geom_LIB PRIVATE ${DoubleConversion_LIBRARIES}) diff --git a/src/2geom/affine.cpp b/src/2geom/affine.cpp new file mode 100644 index 0000000..48179e8 --- /dev/null +++ b/src/2geom/affine.cpp @@ -0,0 +1,522 @@ +/* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Michael G. Sloan <mgsloan@gmail.com> + * + * This code is in public domain + */ + +#include <2geom/affine.h> +#include <2geom/point.h> +#include <2geom/polynomial.h> +#include <2geom/utils.h> + +namespace Geom { + +/** Creates a Affine given an axis and origin point. + * The axis is represented as two vectors, which represent skew, rotation, and scaling in two dimensions. + * from_basis(Point(1, 0), Point(0, 1), Point(0, 0)) would return the identity matrix. + + \param x_basis the vector for the x-axis. + \param y_basis the vector for the y-axis. + \param offset the translation applied by the matrix. + \return The new Affine. + */ +//NOTE: Inkscape's version is broken, so when including this version, you'll have to search for code with this func +Affine from_basis(Point const &x_basis, Point const &y_basis, Point const &offset) { + return Affine(x_basis[X], x_basis[Y], + y_basis[X], y_basis[Y], + offset [X], offset [Y]); +} + +Point Affine::xAxis() const { + return Point(_c[0], _c[1]); +} + +Point Affine::yAxis() const { + return Point(_c[2], _c[3]); +} + +/// Gets the translation imparted by the Affine. +Point Affine::translation() const { + return Point(_c[4], _c[5]); +} + +void Affine::setXAxis(Point const &vec) { + for(int i = 0; i < 2; i++) + _c[i] = vec[i]; +} + +void Affine::setYAxis(Point const &vec) { + for(int i = 0; i < 2; i++) + _c[i + 2] = vec[i]; +} + +/// Sets the translation imparted by the Affine. +void Affine::setTranslation(Point const &loc) { + for(int i = 0; i < 2; i++) + _c[i + 4] = loc[i]; +} + +/** Calculates the amount of x-scaling imparted by the Affine. This is the scaling applied to + * the original x-axis region. It is \emph{not} the overall x-scaling of the transformation. + * Equivalent to L2(m.xAxis()). */ +double Affine::expansionX() const { + return sqrt(_c[0] * _c[0] + _c[1] * _c[1]); +} + +/** Calculates the amount of y-scaling imparted by the Affine. This is the scaling applied before + * the other transformations. It is \emph{not} the overall y-scaling of the transformation. + * Equivalent to L2(m.yAxis()). */ +double Affine::expansionY() const { + return sqrt(_c[2] * _c[2] + _c[3] * _c[3]); +} + +void Affine::setExpansionX(double val) { + double exp_x = expansionX(); + if (exp_x != 0.0) { //TODO: best way to deal with it is to skip op? + double coef = val / expansionX(); + for (unsigned i = 0; i < 2; ++i) { + _c[i] *= coef; + } + } +} + +void Affine::setExpansionY(double val) { + double exp_y = expansionY(); + if (exp_y != 0.0) { //TODO: best way to deal with it is to skip op? + double coef = val / expansionY(); + for (unsigned i = 2; i < 4; ++i) { + _c[i] *= coef; + } + } +} + +/** Sets this matrix to be the Identity Affine. */ +void Affine::setIdentity() { + _c[0] = 1.0; _c[1] = 0.0; + _c[2] = 0.0; _c[3] = 1.0; + _c[4] = 0.0; _c[5] = 0.0; +} + +/** @brief Check whether this matrix is an identity matrix. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + 1 & 0 & 0 \\ + 0 & 1 & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$ */ +bool Affine::isIdentity(Coord eps) const { + return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) && + are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); +} + +/** @brief Check whether this matrix represents a pure translation. + * Will return true for the identity matrix, which represents a zero translation. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + 1 & 0 & 0 \\ + 0 & 1 & 0 \\ + a & b & 1 \end{array}\right]\f$ */ +bool Affine::isTranslation(Coord eps) const { + return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) && + are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps); +} +/** @brief Check whether this matrix represents a pure nonzero translation. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + 1 & 0 & 0 \\ + 0 & 1 & 0 \\ + a & b & 1 \end{array}\right]\f$ and \f$a, b \neq 0\f$ */ +bool Affine::isNonzeroTranslation(Coord eps) const { + return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) && + are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) && + (!are_near(_c[4], 0.0, eps) || !are_near(_c[5], 0.0, eps)); +} + +/** @brief Check whether this matrix represents pure scaling. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a & 0 & 0 \\ + 0 & b & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$. */ +bool Affine::isScale(Coord eps) const { + if (isSingular(eps)) return false; + return are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); +} + +/** @brief Check whether this matrix represents pure, nonzero scaling. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a & 0 & 0 \\ + 0 & b & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$ and \f$a, b \neq 1\f$. */ +bool Affine::isNonzeroScale(Coord eps) const { + if (isSingular(eps)) return false; + return (!are_near(_c[0], 1.0, eps) || !are_near(_c[3], 1.0, eps)) && //NOTE: these are the diags, and the next line opposite diags + are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); +} + +/** @brief Check whether this matrix represents pure uniform scaling. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a_1 & 0 & 0 \\ + 0 & a_2 & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$ where \f$|a_1| = |a_2|\f$. */ +bool Affine::isUniformScale(Coord eps) const { + if (isSingular(eps)) return false; + return are_near(fabs(_c[0]), fabs(_c[3]), eps) && + are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); +} + +/** @brief Check whether this matrix represents pure, nonzero uniform scaling. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a_1 & 0 & 0 \\ + 0 & a_2 & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$ where \f$|a_1| = |a_2|\f$ + * and \f$a_1, a_2 \neq 1\f$. */ +bool Affine::isNonzeroUniformScale(Coord eps) const { + if (isSingular(eps)) return false; + // we need to test both c0 and c3 to handle the case of flips, + // which should be treated as nonzero uniform scales + return !(are_near(_c[0], 1.0, eps) && are_near(_c[3], 1.0, eps)) && + are_near(fabs(_c[0]), fabs(_c[3]), eps) && + are_near(_c[1], 0.0, eps) && are_near(_c[2], 0.0, eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); +} + +/** @brief Check whether this matrix represents a pure rotation. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a & b & 0 \\ + -b & a & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$ and \f$a^2 + b^2 = 1\f$. */ +bool Affine::isRotation(Coord eps) const { + return are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps) && + are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps); +} + +/** @brief Check whether this matrix represents a pure, nonzero rotation. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a & b & 0 \\ + -b & a & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$, \f$a^2 + b^2 = 1\f$ and \f$a \neq 1\f$. */ +bool Affine::isNonzeroRotation(Coord eps) const { + return !are_near(_c[0], 1.0, eps) && + are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps) && + are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps); +} + +/** @brief Check whether this matrix represents a non-zero rotation about any point. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a & b & 0 \\ + -b & a & 0 \\ + c & d & 1 \end{array}\right]\f$, \f$a^2 + b^2 = 1\f$ and \f$a \neq 1\f$. */ +bool Affine::isNonzeroNonpureRotation(Coord eps) const { + return !are_near(_c[0], 1.0, eps) && + are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) && + are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps); +} + +/** @brief For a (possibly non-pure) non-zero-rotation matrix, calculate the rotation center. + * @pre The matrix must be a non-zero-rotation matrix to prevent division by zero, see isNonzeroNonpureRotation(). + * @return The rotation center x, the solution to the equation + * \f$A x = x\f$. */ +Point Affine::rotationCenter() const { + Coord x = (_c[2]*_c[5]+_c[4]-_c[4]*_c[3]) / (1-_c[3]-_c[0]+_c[0]*_c[3]-_c[2]*_c[1]); + Coord y = (_c[1]*x + _c[5]) / (1 - _c[3]); + return Point(x,y); +}; + +/** @brief Check whether this matrix represents pure horizontal shearing. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + 1 & 0 & 0 \\ + k & 1 & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$. */ +bool Affine::isHShear(Coord eps) const { + return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) && + are_near(_c[3], 1.0, eps) && are_near(_c[4], 0.0, eps) && + are_near(_c[5], 0.0, eps); +} +/** @brief Check whether this matrix represents pure, nonzero horizontal shearing. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + 1 & 0 & 0 \\ + k & 1 & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$ and \f$k \neq 0\f$. */ +bool Affine::isNonzeroHShear(Coord eps) const { + return are_near(_c[0], 1.0, eps) && are_near(_c[1], 0.0, eps) && + !are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); +} + +/** @brief Check whether this matrix represents pure vertical shearing. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + 1 & k & 0 \\ + 0 & 1 & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$. */ +bool Affine::isVShear(Coord eps) const { + return are_near(_c[0], 1.0, eps) && are_near(_c[2], 0.0, eps) && + are_near(_c[3], 1.0, eps) && are_near(_c[4], 0.0, eps) && + are_near(_c[5], 0.0, eps); +} + +/** @brief Check whether this matrix represents pure, nonzero vertical shearing. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + 1 & k & 0 \\ + 0 & 1 & 0 \\ + 0 & 0 & 1 \end{array}\right]\f$ and \f$k \neq 0\f$. */ +bool Affine::isNonzeroVShear(Coord eps) const { + return are_near(_c[0], 1.0, eps) && !are_near(_c[1], 0.0, eps) && + are_near(_c[2], 0.0, eps) && are_near(_c[3], 1.0, eps) && + are_near(_c[4], 0.0, eps) && are_near(_c[5], 0.0, eps); +} + +/** @brief Check whether this matrix represents zooming. + * Zooming is any combination of translation and uniform non-flipping scaling. + * It preserves angles, ratios of distances between arbitrary points + * and unit vectors of line segments. + * @param eps Numerical tolerance + * @return True iff the matrix is invertible and of the form + * \f$\left[\begin{array}{ccc} + a & 0 & 0 \\ + 0 & a & 0 \\ + b & c & 1 \end{array}\right]\f$. */ +bool Affine::isZoom(Coord eps) const { + if (isSingular(eps)) return false; + return are_near(_c[0], _c[3], eps) && are_near(_c[1], 0, eps) && are_near(_c[2], 0, eps); +} + +/** @brief Check whether the transformation preserves areas of polygons. + * This means that the transformation can be any combination of translation, rotation, + * shearing and squeezing (non-uniform scaling such that the absolute value of the product + * of Y-scale and X-scale is 1). + * @param eps Numerical tolerance + * @return True iff \f$|\det A| = 1\f$. */ +bool Affine::preservesArea(Coord eps) const +{ + return are_near(descrim2(), 1.0, eps); +} + +/** @brief Check whether the transformation preserves angles between lines. + * This means that the transformation can be any combination of translation, uniform scaling, + * rotation and flipping. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a & b & 0 \\ + -b & a & 0 \\ + c & d & 1 \end{array}\right]\f$ or + \f$\left[\begin{array}{ccc} + -a & b & 0 \\ + b & a & 0 \\ + c & d & 1 \end{array}\right]\f$. */ +bool Affine::preservesAngles(Coord eps) const +{ + if (isSingular(eps)) return false; + return (are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps)) || + (are_near(_c[0], -_c[3], eps) && are_near(_c[1], _c[2], eps)); +} + +/** @brief Check whether the transformation preserves distances between points. + * This means that the transformation can be any combination of translation, + * rotation and flipping. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a & b & 0 \\ + -b & a & 0 \\ + c & d & 1 \end{array}\right]\f$ or + \f$\left[\begin{array}{ccc} + -a & b & 0 \\ + b & a & 0 \\ + c & d & 1 \end{array}\right]\f$ and \f$a^2 + b^2 = 1\f$. */ +bool Affine::preservesDistances(Coord eps) const +{ + return ((are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps)) || + (are_near(_c[0], -_c[3], eps) && are_near(_c[1], _c[2], eps))) && + are_near(_c[0] * _c[0] + _c[1] * _c[1], 1.0, eps); +} + +/** @brief Check whether this transformation flips objects. + * A transformation flips objects if it has a negative scaling component. */ +bool Affine::flips() const { + return det() < 0; +} + +/** @brief Check whether this matrix is singular. + * Singular matrices have no inverse, which means that applying them to a set of points + * results in a loss of information. + * @param eps Numerical tolerance + * @return True iff the determinant is near zero. */ +bool Affine::isSingular(Coord eps) const { + return are_near(det(), 0.0, eps); +} + +/** @brief Compute the inverse matrix. + * Inverse is a matrix (denoted \f$A^{-1}\f$) such that \f$AA^{-1} = A^{-1}A = I\f$. + * Singular matrices have no inverse (for example a matrix that has two of its columns equal). + * For such matrices, the identity matrix will be returned instead. + * @param eps Numerical tolerance + * @return Inverse of the matrix, or the identity matrix if the inverse is undefined. + * @post (m * m.inverse()).isIdentity() == true */ +Affine Affine::inverse() const { + Affine d; + + double mx = std::max(fabs(_c[0]) + fabs(_c[1]), + fabs(_c[2]) + fabs(_c[3])); // a random matrix norm (either l1 or linfty + if(mx > 0) { + Geom::Coord const determ = det(); + if (!rel_error_bound(std::sqrt(fabs(determ)), mx)) { + Geom::Coord const ideterm = 1.0 / (determ); + + d._c[0] = _c[3] * ideterm; + d._c[1] = -_c[1] * ideterm; + d._c[2] = -_c[2] * ideterm; + d._c[3] = _c[0] * ideterm; + d._c[4] = (-_c[4] * d._c[0] - _c[5] * d._c[2]); + d._c[5] = (-_c[4] * d._c[1] - _c[5] * d._c[3]); + } else { + d.setIdentity(); + } + } else { + d.setIdentity(); + } + + return d; +} + +/** @brief Calculate the determinant. + * @return \f$\det A\f$. */ +Coord Affine::det() const { + // TODO this can overflow + return _c[0] * _c[3] - _c[1] * _c[2]; +} + +/** @brief Calculate the square of the descriminant. + * This is simply the absolute value of the determinant. + * @return \f$|\det A|\f$. */ +Coord Affine::descrim2() const { + return fabs(det()); +} + +/** @brief Calculate the descriminant. + * If the matrix doesn't contain a shearing or non-uniform scaling component, this value says + * how will the length of any line segment change after applying this transformation + * to arbitrary objects on a plane. The new length will be + * @code line_seg.length() * m.descrim()) @endcode + * @return \f$\sqrt{|\det A|}\f$. */ +Coord Affine::descrim() const { + return sqrt(descrim2()); +} + +/** @brief Combine this transformation with another one. + * After this operation, the matrix will correspond to the transformation + * obtained by first applying the original version of this matrix, and then + * applying @a m. */ +Affine &Affine::operator*=(Affine const &o) { + Coord nc[6]; + for(int a = 0; a < 5; a += 2) { + for(int b = 0; b < 2; b++) { + nc[a + b] = _c[a] * o._c[b] + _c[a + 1] * o._c[b + 2]; + } + } + for(int a = 0; a < 6; ++a) { + _c[a] = nc[a]; + } + _c[4] += o._c[4]; + _c[5] += o._c[5]; + return *this; +} + +//TODO: What's this!?! +/** Given a matrix m such that unit_circle = m*x, this returns the + * quadratic form x*A*x = 1. + * @relates Affine */ +Affine elliptic_quadratic_form(Affine const &m) { + double od = m[0] * m[1] + m[2] * m[3]; + Affine ret (m[0]*m[0] + m[1]*m[1], od, + od, m[2]*m[2] + m[3]*m[3], + 0, 0); + return ret; // allow NRVO +} + +Eigen::Eigen(Affine const &m) { + double const B = -m[0] - m[3]; + double const C = m[0]*m[3] - m[1]*m[2]; + + std::vector<double> v = solve_quadratic(1, B, C); + + for (unsigned i = 0; i < v.size(); ++i) { + values[i] = v[i]; + vectors[i] = unit_vector(rot90(Point(m[0] - values[i], m[1]))); + } + for (unsigned i = v.size(); i < 2; ++i) { + values[i] = 0; + vectors[i] = Point(0,0); + } +} + +Eigen::Eigen(double m[2][2]) { + double const B = -m[0][0] - m[1][1]; + double const C = m[0][0]*m[1][1] - m[1][0]*m[0][1]; + + std::vector<double> v = solve_quadratic(1, B, C); + + for (unsigned i = 0; i < v.size(); ++i) { + values[i] = v[i]; + vectors[i] = unit_vector(rot90(Point(m[0][0] - values[i], m[0][1]))); + } + for (unsigned i = v.size(); i < 2; ++i) { + values[i] = 0; + vectors[i] = Point(0,0); + } +} + +/** @brief Nearness predicate for affine transforms. + * @returns True if all entries of matrices are within eps of each other. + * @relates Affine */ +bool are_near(Affine const &a, Affine const &b, Coord eps) +{ + return are_near(a[0], b[0], eps) && are_near(a[1], b[1], eps) && + are_near(a[2], b[2], eps) && are_near(a[3], b[3], eps) && + are_near(a[4], b[4], eps) && are_near(a[5], b[5], eps); +} + +} //namespace Geom + +/* + 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/src/2geom/affine.h b/src/2geom/affine.h new file mode 100644 index 0000000..470d5fc --- /dev/null +++ b/src/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/src/2geom/angle.h b/src/2geom/angle.h new file mode 100644 index 0000000..f0caaba --- /dev/null +++ b/src/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/src/2geom/basic-intersection.cpp b/src/2geom/basic-intersection.cpp new file mode 100644 index 0000000..7707037 --- /dev/null +++ b/src/2geom/basic-intersection.cpp @@ -0,0 +1,493 @@ +/** @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. + * + */ + +#include <2geom/basic-intersection.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/exception.h> + +#ifdef HAVE_GSL +#include <gsl/gsl_vector.h> +#include <gsl/gsl_multiroots.h> +#endif + +using std::vector; +namespace Geom { + +//#ifdef USE_RECURSIVE_INTERSECTOR + +// void find_intersections(std::vector<std::pair<double, double> > &xs, +// D2<SBasis> const & A, +// D2<SBasis> const & B) { +// vector<Point> BezA, BezB; +// sbasis_to_bezier(BezA, A); +// sbasis_to_bezier(BezB, B); + +// xs.clear(); + +// find_intersections_bezier_recursive(xs, BezA, BezB); +// } +// void find_intersections(std::vector< std::pair<double, double> > & xs, +// std::vector<Point> const& A, +// std::vector<Point> const& B, +// double precision){ +// find_intersections_bezier_recursive(xs, A, B, precision); +// } + +//#else + +namespace detail{ namespace bezier_clipping { +void portion(std::vector<Point> &B, Interval const &I); +void derivative(std::vector<Point> &D, std::vector<Point> const &B); +}; }; + +void find_intersections(std::vector<std::pair<double, double> > &xs, + D2<Bezier> const & A, + D2<Bezier> const & B, + double precision) +{ + find_intersections_bezier_clipping(xs, bezier_points(A), bezier_points(B), precision); +} + +void find_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const & A, + D2<SBasis> const & B, + double precision) +{ + vector<Point> BezA, BezB; + sbasis_to_bezier(BezA, A); + sbasis_to_bezier(BezB, B); + + find_intersections_bezier_clipping(xs, BezA, BezB, precision); +} + +void find_intersections(std::vector< std::pair<double, double> > & xs, + std::vector<Point> const& A, + std::vector<Point> const& B, + double precision) +{ + find_intersections_bezier_clipping(xs, A, B, precision); +} + +//#endif + +/* + * split the curve at the midpoint, returning an array with the two parts + * Temporary storage is minimized by using part of the storage for the result + * to hold an intermediate value until it is no longer needed. + */ +// TODO replace with Bezier method +void split(vector<Point> const &p, double t, + vector<Point> &left, vector<Point> &right) { + const unsigned sz = p.size(); + //Geom::Point Vtemp[sz][sz]; + vector<vector<Point> > Vtemp(sz); + for ( size_t i = 0; i < sz; ++i ) + Vtemp[i].reserve(sz); + + /* Copy control points */ + std::copy(p.begin(), p.end(), Vtemp[0].begin()); + + /* Triangle computation */ + for (unsigned i = 1; i < sz; i++) { + for (unsigned j = 0; j < sz - i; j++) { + Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]); + } + } + + left.resize(sz); + right.resize(sz); + for (unsigned j = 0; j < sz; j++) + left[j] = Vtemp[j][0]; + for (unsigned j = 0; j < sz; j++) + right[j] = Vtemp[sz-1-j][j]; +} + + + +void find_self_intersections(std::vector<std::pair<double, double> > &xs, + D2<Bezier> const &A, + double precision) +{ + std::vector<double> dr = derivative(A[X]).roots(); + { + std::vector<double> dyr = derivative(A[Y]).roots(); + dr.insert(dr.begin(), dyr.begin(), dyr.end()); + } + dr.push_back(0); + dr.push_back(1); + // We want to be sure that we have no empty segments + std::sort(dr.begin(), dr.end()); + std::vector<double>::iterator new_end = std::unique(dr.begin(), dr.end()); + dr.resize( new_end - dr.begin() ); + + std::vector< D2<Bezier> > pieces; + for (unsigned i = 0; i < dr.size() - 1; ++i) { + pieces.push_back(portion(A, dr[i], dr[i+1])); + } + /*{ + vector<Point> l, r, in = A; + for(unsigned i = 0; i < dr.size()-1; i++) { + split(in, (dr[i+1]-dr[i]) / (1 - dr[i]), l, r); + pieces.push_back(l); + in = r; + } + }*/ + + for(unsigned i = 0; i < dr.size()-1; i++) { + for(unsigned j = i+1; j < dr.size()-1; j++) { + std::vector<std::pair<double, double> > section; + + find_intersections(section, pieces[i], pieces[j], precision); + for(unsigned k = 0; k < section.size(); k++) { + double l = section[k].first; + double r = section[k].second; +// XXX: This condition will prune out false positives, but it might create some false negatives. Todo: Confirm it is correct. + if(j == i+1) + //if((l == 1) && (r == 0)) + if( ( l > precision ) && (r < precision) )//FIXME: what precision should be used here??? + continue; + xs.push_back(std::make_pair((1-l)*dr[i] + l*dr[i+1], + (1-r)*dr[j] + r*dr[j+1])); + } + } + } + + // Because i is in order, xs should be roughly already in order? + //sort(xs.begin(), xs.end()); + //unique(xs.begin(), xs.end()); +} + +void find_self_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const &A, + double precision) +{ + D2<Bezier> in; + sbasis_to_bezier(in, A); + find_self_intersections(xs, in, precision); +} + + +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) +{ + if (xs.empty()) { + av.push_back(a); + bv.push_back(b); + return; + } + + std::pair<double, double> prev = std::make_pair(0., 0.); + for (unsigned i = 0; i < xs.size(); ++i) { + av.push_back(portion(a, prev.first, xs[i].first)); + bv.push_back(portion(b, prev.second, xs[i].second)); + av.back()[X].at0() = bv.back()[X].at0() = lerp(0.5, av.back()[X].at0(), bv.back()[X].at0()); + av.back()[X].at1() = bv.back()[X].at1() = lerp(0.5, av.back()[X].at1(), bv.back()[X].at1()); + av.back()[Y].at0() = bv.back()[Y].at0() = lerp(0.5, av.back()[Y].at0(), bv.back()[Y].at0()); + av.back()[Y].at1() = bv.back()[Y].at1() = lerp(0.5, av.back()[Y].at1(), bv.back()[Y].at1()); + prev = xs[i]; + } + av.push_back(portion(a, prev.first, 1)); + bv.push_back(portion(b, prev.second, 1)); + av.back()[X].at0() = bv.back()[X].at0() = lerp(0.5, av.back()[X].at0(), bv.back()[X].at0()); + av.back()[X].at1() = bv.back()[X].at1() = lerp(0.5, av.back()[X].at1(), bv.back()[X].at1()); + av.back()[Y].at0() = bv.back()[Y].at0() = lerp(0.5, av.back()[Y].at0(), bv.back()[Y].at0()); + av.back()[Y].at1() = bv.back()[Y].at1() = lerp(0.5, av.back()[Y].at1(), bv.back()[Y].at1()); +} + +#ifdef HAVE_GSL +#include <gsl/gsl_multiroots.h> + +struct rparams +{ + D2<SBasis> const &A; + D2<SBasis> const &B; +}; + +static int +intersect_polish_f (const gsl_vector * x, void *params, + gsl_vector * f) +{ + const double x0 = gsl_vector_get (x, 0); + const double x1 = gsl_vector_get (x, 1); + + Geom::Point dx = ((struct rparams *) params)->A(x0) - + ((struct rparams *) params)->B(x1); + + gsl_vector_set (f, 0, dx[0]); + gsl_vector_set (f, 1, dx[1]); + + return GSL_SUCCESS; +} +#endif + +union dbl_64{ + long long i64; + double d64; +}; + +static double EpsilonBy(double value, int eps) +{ + dbl_64 s; + s.d64 = value; + s.i64 += eps; + return s.d64; +} + + +static void intersect_polish_root (D2<SBasis> const &A, double &s, + D2<SBasis> const &B, double &t) { +#ifdef HAVE_GSL + const gsl_multiroot_fsolver_type *T; + gsl_multiroot_fsolver *sol; + + int status; + size_t iter = 0; +#endif + std::vector<Point> as, bs; + as = A.valueAndDerivatives(s, 2); + bs = B.valueAndDerivatives(t, 2); + Point F = as[0] - bs[0]; + double best = dot(F, F); + + for(int i = 0; i < 4; i++) { + + /** + we want to solve + J*(x1 - x0) = f(x0) + + |dA(s)[0] -dB(t)[0]| (X1 - X0) = A(s) - B(t) + |dA(s)[1] -dB(t)[1]| + **/ + + // We're using the standard transformation matricies, which is numerically rather poor. Much better to solve the equation using elimination. + + Affine jack(as[1][0], as[1][1], + -bs[1][0], -bs[1][1], + 0, 0); + Point soln = (F)*jack.inverse(); + double ns = s - soln[0]; + double nt = t - soln[1]; + + as = A.valueAndDerivatives(ns, 2); + bs = B.valueAndDerivatives(nt, 2); + F = as[0] - bs[0]; + double trial = dot(F, F); + if (trial > best*0.1) {// we have standards, you know + // At this point we could do a line search + break; + } + best = trial; + s = ns; + t = nt; + } + +#ifdef HAVE_GSL + const size_t n = 2; + struct rparams p = {A, B}; + gsl_multiroot_function f = {&intersect_polish_f, n, &p}; + + double x_init[2] = {s, t}; + gsl_vector *x = gsl_vector_alloc (n); + + gsl_vector_set (x, 0, x_init[0]); + gsl_vector_set (x, 1, x_init[1]); + + T = gsl_multiroot_fsolver_hybrids; + sol = gsl_multiroot_fsolver_alloc (T, 2); + gsl_multiroot_fsolver_set (sol, &f, x); + + do + { + iter++; + status = gsl_multiroot_fsolver_iterate (sol); + + if (status) /* check if solver is stuck */ + break; + + status = + gsl_multiroot_test_residual (sol->f, 1e-12); + } + while (status == GSL_CONTINUE && iter < 1000); + + s = gsl_vector_get (sol->x, 0); + t = gsl_vector_get (sol->x, 1); + + gsl_multiroot_fsolver_free (sol); + gsl_vector_free (x); +#endif + + { + // This code does a neighbourhood search for minor improvements. + double best_v = L1(A(s) - B(t)); + //std::cout << "------\n" << best_v << std::endl; + Point best(s,t); + while (true) { + Point trial = best; + double trial_v = best_v; + for(int nsi = -1; nsi < 2; nsi++) { + for(int nti = -1; nti < 2; nti++) { + Point n(EpsilonBy(best[0], nsi), + EpsilonBy(best[1], nti)); + double c = L1(A(n[0]) - B(n[1])); + //std::cout << c << "; "; + if (c < trial_v) { + trial = n; + trial_v = c; + } + } + } + if(trial == best) { + //std::cout << "\n" << s << " -> " << s - best[0] << std::endl; + //std::cout << t << " -> " << t - best[1] << std::endl; + //std::cout << best_v << std::endl; + s = best[0]; + t = best[1]; + return; + } else { + best = trial; + best_v = trial_v; + } + } + } +} + + +void polish_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const &A, D2<SBasis> const &B) +{ + for(unsigned i = 0; i < xs.size(); i++) + intersect_polish_root(A, xs[i].first, + B, xs[i].second); +} + +/** + * Compute the Hausdorf distance from A to B only. + */ +double hausdorfl(D2<SBasis>& A, D2<SBasis> const& B, + double m_precision, + double *a_t, double* b_t) { + std::vector< std::pair<double, double> > xs; + std::vector<Point> Az, Bz; + sbasis_to_bezier (Az, A); + sbasis_to_bezier (Bz, B); + find_collinear_normal(xs, Az, Bz, m_precision); + double h_dist = 0, h_a_t = 0, h_b_t = 0; + double dist = 0; + Point Ax = A.at0(); + double t = Geom::nearest_time(Ax, B); + dist = Geom::distance(Ax, B(t)); + if (dist > h_dist) { + h_a_t = 0; + h_b_t = t; + h_dist = dist; + } + Ax = A.at1(); + t = Geom::nearest_time(Ax, B); + dist = Geom::distance(Ax, B(t)); + if (dist > h_dist) { + h_a_t = 1; + h_b_t = t; + h_dist = dist; + } + for (size_t i = 0; i < xs.size(); ++i) + { + Point At = A(xs[i].first); + Point Bu = B(xs[i].second); + double distAtBu = Geom::distance(At, Bu); + t = Geom::nearest_time(At, B); + dist = Geom::distance(At, B(t)); + //FIXME: we might miss it due to floating point precision... + if (dist >= distAtBu-.1 && distAtBu > h_dist) { + h_a_t = xs[i].first; + h_b_t = xs[i].second; + h_dist = distAtBu; + } + + } + if(a_t) *a_t = h_a_t; + if(b_t) *b_t = h_b_t; + + return h_dist; +} + +/** + * Compute the symmetric Hausdorf distance. + */ +double hausdorf(D2<SBasis>& A, D2<SBasis> const& B, + double m_precision, + double *a_t, double* b_t) { + double h_dist = hausdorfl(A, B, m_precision, a_t, b_t); + + double dist = 0; + Point Bx = B.at0(); + double t = Geom::nearest_time(Bx, A); + dist = Geom::distance(Bx, A(t)); + if (dist > h_dist) { + if(a_t) *a_t = t; + if(b_t) *b_t = 0; + h_dist = dist; + } + Bx = B.at1(); + t = Geom::nearest_time(Bx, A); + dist = Geom::distance(Bx, A(t)); + if (dist > h_dist) { + if(a_t) *a_t = t; + if(b_t) *b_t = 1; + h_dist = dist; + } + + return h_dist; +} + +bool non_collinear_segments_intersect(const Point &A, const Point &B, const Point &C, const Point &D) +{ + return cross(D - C, A - C) * cross(D - C, B - C) < 0 && // + cross(B - A, C - A) * cross(B - A, D - A) < 0; +} +}; + +/* + 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/src/2geom/basic-intersection.h b/src/2geom/basic-intersection.h new file mode 100644 index 0000000..2d0c00d --- /dev/null +++ b/src/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/src/2geom/bezier-clipping.cpp b/src/2geom/bezier-clipping.cpp new file mode 100644 index 0000000..e7bbcc3 --- /dev/null +++ b/src/2geom/bezier-clipping.cpp @@ -0,0 +1,1163 @@ +/* + * Implement the Bezier clipping algorithm for finding + * Bezier curve intersection points and collinear normals + * + * 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. + */ + + + + +#include <2geom/basic-intersection.h> +#include <2geom/choose.h> +#include <2geom/point.h> +#include <2geom/interval.h> +#include <2geom/bezier.h> +#include <2geom/numeric/matrix.h> +#include <2geom/convex-hull.h> +#include <2geom/line.h> + +#include <cassert> +#include <vector> +#include <algorithm> +#include <utility> +//#include <iomanip> + +using std::swap; + + +#define VERBOSE 0 +#define CHECK 0 + + +namespace Geom { + +namespace detail { namespace bezier_clipping { + +//////////////////////////////////////////////////////////////////////////////// +// for debugging +// + +void print(std::vector<Point> const& cp, const char* msg = "") +{ + std::cerr << msg << std::endl; + for (size_t i = 0; i < cp.size(); ++i) + std::cerr << i << " : " << cp[i] << std::endl; +} + +template< class charT > +std::basic_ostream<charT> & +operator<< (std::basic_ostream<charT> & os, const Interval & I) +{ + os << "[" << I.min() << ", " << I.max() << "]"; + return os; +} + +double angle (std::vector<Point> const& A) +{ + size_t n = A.size() -1; + double a = std::atan2(A[n][Y] - A[0][Y], A[n][X] - A[0][X]); + return (180 * a / M_PI); +} + +size_t get_precision(Interval const& I) +{ + double d = I.extent(); + double e = 0.1, p = 10; + int n = 0; + while (n < 16 && d < e) + { + p *= 10; + e = 1/p; + ++n; + } + return n; +} + +void range_assertion(int k, int m, int n, const char* msg) +{ + if ( k < m || k > n) + { + std::cerr << "range assertion failed: \n" + << msg << std::endl + << "value: " << k + << " range: " << m << ", " << n << std::endl; + assert (k >= m && k <= n); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// numerical routines + +/* + * Compute the binomial coefficient (n, k) + */ +double binomial(unsigned int n, unsigned int k) +{ + return choose<double>(n, k); +} + +/* + * Compute the determinant of the 2x2 matrix with column the point P1, P2 + */ +double det(Point const& P1, Point const& P2) +{ + return P1[X]*P2[Y] - P1[Y]*P2[X]; +} + +/* + * Solve the linear system [P1,P2] * P = Q + * in case there isn't exactly one solution the routine returns false + */ +bool solve(Point & P, Point const& P1, Point const& P2, Point const& Q) +{ + double d = det(P1, P2); + if (d == 0) return false; + d = 1 / d; + P[X] = det(Q, P2) * d; + P[Y] = det(P1, Q) * d; + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// interval routines + +/* + * Map the sub-interval I in [0,1] into the interval J and assign it to J + */ +void map_to(Interval & J, Interval const& I) +{ + J.setEnds(J.valueAt(I.min()), J.valueAt(I.max())); +} + +//////////////////////////////////////////////////////////////////////////////// +// bezier curve routines + +/* + * Return true if all the Bezier curve control points are near, + * false otherwise + */ +// Bezier.isConstant(precision) +bool is_constant(std::vector<Point> const& A, double precision) +{ + for (unsigned int i = 1; i < A.size(); ++i) + { + if(!are_near(A[i], A[0], precision)) + return false; + } + return true; +} + +/* + * Compute the hodograph of the bezier curve B and return it in D + */ +// derivative(Bezier) +void derivative(std::vector<Point> & D, std::vector<Point> const& B) +{ + D.clear(); + size_t sz = B.size(); + if (sz == 0) return; + if (sz == 1) + { + D.resize(1, Point(0,0)); + return; + } + size_t n = sz-1; + D.reserve(n); + for (size_t i = 0; i < n; ++i) + { + D.push_back(n*(B[i+1] - B[i])); + } +} + +/* + * Compute the hodograph of the Bezier curve B rotated of 90 degree + * and return it in D; we have N(t) orthogonal to B(t) for any t + */ +// rot90(derivative(Bezier)) +void normal(std::vector<Point> & N, std::vector<Point> const& B) +{ + derivative(N,B); + for (size_t i = 0; i < N.size(); ++i) + { + N[i] = rot90(N[i]); + } +} + +/* + * Compute the portion of the Bezier curve "B" wrt the interval [0,t] + */ +// portion(Bezier, 0, t) +void left_portion(Coord t, std::vector<Point> & B) +{ + size_t n = B.size(); + for (size_t i = 1; i < n; ++i) + { + for (size_t j = n-1; j > i-1 ; --j) + { + B[j] = lerp(t, B[j-1], B[j]); + } + } +} + +/* + * Compute the portion of the Bezier curve "B" wrt the interval [t,1] + */ +// portion(Bezier, t, 1) +void right_portion(Coord t, std::vector<Point> & B) +{ + size_t n = B.size(); + for (size_t i = 1; i < n; ++i) + { + for (size_t j = 0; j < n-i; ++j) + { + B[j] = lerp(t, B[j], B[j+1]); + } + } +} + +/* + * Compute the portion of the Bezier curve "B" wrt the interval "I" + */ +// portion(Bezier, I) +void portion (std::vector<Point> & B , Interval const& I) +{ + if (I.min() == 0) + { + if (I.max() == 1) return; + left_portion(I.max(), B); + return; + } + right_portion(I.min(), B); + if (I.max() == 1) return; + double t = I.extent() / (1 - I.min()); + left_portion(t, B); +} + + +//////////////////////////////////////////////////////////////////////////////// +// tags + +struct intersection_point_tag; +struct collinear_normal_tag; +template <typename Tag> +OptInterval clip(std::vector<Point> const& A, + std::vector<Point> const& B, + double precision); +template <typename Tag> +void iterate(std::vector<Interval>& domsA, + std::vector<Interval>& domsB, + std::vector<Point> const& A, + std::vector<Point> const& B, + Interval const& domA, + Interval const& domB, + double precision ); + + +//////////////////////////////////////////////////////////////////////////////// +// intersection + +/* + * Make up an orientation line using the control points c[i] and c[j] + * the line is returned in the output parameter "l" in the form of a 3 element + * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized. + */ +// Line(c[i], c[j]) +void orientation_line (std::vector<double> & l, + std::vector<Point> const& c, + size_t i, size_t j) +{ + l[0] = c[j][Y] - c[i][Y]; + l[1] = c[i][X] - c[j][X]; + l[2] = cross(c[j], c[i]); + double length = std::sqrt(l[0] * l[0] + l[1] * l[1]); + assert (length != 0); + l[0] /= length; + l[1] /= length; + l[2] /= length; +} + +/* + * Pick up an orientation line for the Bezier curve "c" and return it in + * the output parameter "l" + */ +Line pick_orientation_line (std::vector<Point> const &c, double precision) +{ + size_t i = c.size(); + while (--i > 0 && are_near(c[0], c[i], precision)) + {} + + // this should never happen because when a new curve portion is created + // we check that it is not constant; + // however this requires that the precision used in the is_constant + // routine has to be the same used here in the are_near test + assert(i != 0); + + Line line(c[0], c[i]); + return line; + //std::cerr << "i = " << i << std::endl; +} + +/* + * Make up an orientation line for constant bezier curve; + * the orientation line is made up orthogonal to the other curve base line; + * the line is returned in the output parameter "l" in the form of a 3 element + * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized. + */ +Line orthogonal_orientation_line (std::vector<Point> const &c, + Point const &p, + double precision) +{ + // this should never happen + assert(!is_constant(c, precision)); + + Line line(p, (c.back() - c.front()).cw() + p); + return line; +} + +/* + * Compute the signed distance of the point "P" from the normalized line l + */ +double signed_distance(Point const &p, Line const &l) +{ + Coord a, b, c; + l.coefficients(a, b, c); + return a * p[X] + b * p[Y] + c; +} + +/* + * Compute the min and max distance of the control points of the Bezier + * curve "c" from the normalized orientation line "l". + * This bounds are returned through the output Interval parameter"bound". + */ +Interval fat_line_bounds (std::vector<Point> const &c, + Line const &l) +{ + Interval bound(0, 0); + for (size_t i = 0; i < c.size(); ++i) { + bound.expandTo(signed_distance(c[i], l)); + } + return bound; +} + +/* + * return the x component of the intersection point between the line + * passing through points p1, p2 and the line Y = "y" + */ +double intersect (Point const& p1, Point const& p2, double y) +{ + // we are sure that p2[Y] != p1[Y] because this routine is called + // only when the lower or the upper bound is crossed + double dy = (p2[Y] - p1[Y]); + double s = (y - p1[Y]) / dy; + return (p2[X]-p1[X])*s + p1[X]; +} + +/* + * Clip the Bezier curve "B" wrt the fat line defined by the orientation + * line "l" and the interval range "bound", the new parameter interval for + * the clipped curve is returned through the output parameter "dom" + */ +OptInterval clip_interval (std::vector<Point> const& B, + Line const &l, + Interval const &bound) +{ + double n = B.size() - 1; // number of sub-intervals + std::vector<Point> D; // distance curve control points + D.reserve (B.size()); + for (size_t i = 0; i < B.size(); ++i) + { + const double d = signed_distance(B[i], l); + D.push_back (Point(i/n, d)); + } + //print(D); + + ConvexHull p; + p.swap(D); + //print(p); + + bool plower, phigher; + bool clower, chigher; + double t, tmin = 1, tmax = 0; +// std::cerr << "bound : " << bound << std::endl; + + plower = (p[0][Y] < bound.min()); + phigher = (p[0][Y] > bound.max()); + if (!(plower || phigher)) // inside the fat line + { + if (tmin > p[0][X]) tmin = p[0][X]; + if (tmax < p[0][X]) tmax = p[0][X]; +// std::cerr << "0 : inside " << p[0] +// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl; + } + + for (size_t i = 1; i < p.size(); ++i) + { + clower = (p[i][Y] < bound.min()); + chigher = (p[i][Y] > bound.max()); + if (!(clower || chigher)) // inside the fat line + { + if (tmin > p[i][X]) tmin = p[i][X]; + if (tmax < p[i][X]) tmax = p[i][X]; +// std::cerr << i << " : inside " << p[i] +// << " : tmin = " << tmin << ", tmax = " << tmax +// << std::endl; + } + if (clower != plower) // cross the lower bound + { + t = intersect(p[i-1], p[i], bound.min()); + if (tmin > t) tmin = t; + if (tmax < t) tmax = t; + plower = clower; +// std::cerr << i << " : lower " << p[i] +// << " : tmin = " << tmin << ", tmax = " << tmax +// << std::endl; + } + if (chigher != phigher) // cross the upper bound + { + t = intersect(p[i-1], p[i], bound.max()); + if (tmin > t) tmin = t; + if (tmax < t) tmax = t; + phigher = chigher; +// std::cerr << i << " : higher " << p[i] +// << " : tmin = " << tmin << ", tmax = " << tmax +// << std::endl; + } + } + + // we have to test the closing segment for intersection + size_t last = p.size() - 1; + clower = (p[0][Y] < bound.min()); + chigher = (p[0][Y] > bound.max()); + if (clower != plower) // cross the lower bound + { + t = intersect(p[last], p[0], bound.min()); + if (tmin > t) tmin = t; + if (tmax < t) tmax = t; +// std::cerr << "0 : lower " << p[0] +// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl; + } + if (chigher != phigher) // cross the upper bound + { + t = intersect(p[last], p[0], bound.max()); + if (tmin > t) tmin = t; + if (tmax < t) tmax = t; +// std::cerr << "0 : higher " << p[0] +// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl; + } + + if (tmin == 1 && tmax == 0) { + return OptInterval(); + } else { + return Interval(tmin, tmax); + } +} + +/* + * Clip the Bezier curve "B" wrt the Bezier curve "A" for individuating + * intersection points the new parameter interval for the clipped curve + * is returned through the output parameter "dom" + */ +template <> +OptInterval clip<intersection_point_tag> (std::vector<Point> const& A, + std::vector<Point> const& B, + double precision) +{ + Line bl; + if (is_constant(A, precision)) { + Point M = middle_point(A.front(), A.back()); + bl = orthogonal_orientation_line(B, M, precision); + } else { + bl = pick_orientation_line(A, precision); + } + bl.normalize(); + Interval bound = fat_line_bounds(A, bl); + return clip_interval(B, bl, bound); +} + + +/////////////////////////////////////////////////////////////////////////////// +// collinear normal + +/* + * Compute a closed focus for the Bezier curve B and return it in F + * A focus is any curve through which all lines perpendicular to B(t) pass. + */ +void make_focus (std::vector<Point> & F, std::vector<Point> const& B) +{ + assert (B.size() > 2); + size_t n = B.size() - 1; + normal(F, B); + Point c(1, 1); +#if VERBOSE + if (!solve(c, F[0], -F[n-1], B[n]-B[0])) + { + std::cerr << "make_focus: unable to make up a closed focus" << std::endl; + } +#else + solve(c, F[0], -F[n-1], B[n]-B[0]); +#endif +// std::cerr << "c = " << c << std::endl; + + + // B(t) + c(t) * N(t) + double n_inv = 1 / (double)(n); + Point c0ni; + F.push_back(c[1] * F[n-1]); + F[n] += B[n]; + for (size_t i = n-1; i > 0; --i) + { + F[i] *= -c[0]; + c0ni = F[i]; + F[i] += (c[1] * F[i-1]); + F[i] *= (i * n_inv); + F[i] -= c0ni; + F[i] += B[i]; + } + F[0] *= c[0]; + F[0] += B[0]; +} + +/* + * Compute the projection on the plane (t, d) of the control points + * (t, u, D(t,u)) where D(t,u) = <(B(t) - F(u)), B'(t)> with 0 <= t, u <= 1 + * B is a Bezier curve and F is a focus of another Bezier curve. + * See Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping. + */ +void distance_control_points (std::vector<Point> & D, + std::vector<Point> const& B, + std::vector<Point> const& F) +{ + assert (B.size() > 1); + assert (!F.empty()); + const size_t n = B.size() - 1; + const size_t m = F.size() - 1; + const size_t r = 2 * n - 1; + const double r_inv = 1 / (double)(r); + D.clear(); + D.reserve (B.size() * F.size()); + + std::vector<Point> dB; + dB.reserve(n); + for (size_t k = 0; k < n; ++k) + { + dB.push_back (B[k+1] - B[k]); + } + NL::Matrix dBB(n,B.size()); + for (size_t i = 0; i < n; ++i) + for (size_t j = 0; j < B.size(); ++j) + dBB(i,j) = dot (dB[i], B[j]); + NL::Matrix dBF(n, F.size()); + for (size_t i = 0; i < n; ++i) + for (size_t j = 0; j < F.size(); ++j) + dBF(i,j) = dot (dB[i], F[j]); + + size_t l; + double bc; + Point dij; + std::vector<double> d(F.size()); + for (size_t i = 0; i <= r; ++i) + { + for (size_t j = 0; j <= m; ++j) + { + d[j] = 0; + } + const size_t k0 = std::max(i, n) - n; + const size_t kn = std::min(i, n-1); + const double bri = n / binomial(r,i); + for (size_t k = k0; k <= kn; ++k) + { + //if (k > i || (i-k) > n) continue; + l = i - k; +#if CHECK + assert (l <= n); +#endif + bc = bri * binomial(n,l) * binomial(n-1, k); + for (size_t j = 0; j <= m; ++j) + { + //d[j] += bc * dot(dB[k], B[l] - F[j]); + d[j] += bc * (dBB(k,l) - dBF(k,j)); + } + } + double dmin, dmax; + dmin = dmax = d[m]; + for (size_t j = 0; j < m; ++j) + { + if (dmin > d[j]) dmin = d[j]; + if (dmax < d[j]) dmax = d[j]; + } + dij[0] = i * r_inv; + dij[1] = dmin; + D.push_back (dij); + dij[1] = dmax; + D.push_back (dij); + } +} + +/* + * Clip the Bezier curve "B" wrt the focus "F"; the new parameter interval for + * the clipped curve is returned through the output parameter "dom" + */ +OptInterval clip_interval (std::vector<Point> const& B, + std::vector<Point> const& F) +{ + std::vector<Point> D; // distance curve control points + distance_control_points(D, B, F); + //print(D, "D"); +// ConvexHull chD(D); +// std::vector<Point>& p = chD.boundary; // convex hull vertices + + ConvexHull p; + p.swap(D); + //print(p, "CH(D)"); + + bool plower, clower; + double t, tmin = 1, tmax = 0; + + plower = (p[0][Y] < 0); + if (p[0][Y] == 0) // on the x axis + { + if (tmin > p[0][X]) tmin = p[0][X]; + if (tmax < p[0][X]) tmax = p[0][X]; +// std::cerr << "0 : on x axis " << p[0] +// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl; + } + + for (size_t i = 1; i < p.size(); ++i) + { + clower = (p[i][Y] < 0); + if (p[i][Y] == 0) // on x axis + { + if (tmin > p[i][X]) tmin = p[i][X]; + if (tmax < p[i][X]) tmax = p[i][X]; +// std::cerr << i << " : on x axis " << p[i] +// << " : tmin = " << tmin << ", tmax = " << tmax +// << std::endl; + } + else if (clower != plower) // cross the x axis + { + t = intersect(p[i-1], p[i], 0); + if (tmin > t) tmin = t; + if (tmax < t) tmax = t; + plower = clower; +// std::cerr << i << " : lower " << p[i] +// << " : tmin = " << tmin << ", tmax = " << tmax +// << std::endl; + } + } + + // we have to test the closing segment for intersection + size_t last = p.size() - 1; + clower = (p[0][Y] < 0); + if (clower != plower) // cross the x axis + { + t = intersect(p[last], p[0], 0); + if (tmin > t) tmin = t; + if (tmax < t) tmax = t; +// std::cerr << "0 : lower " << p[0] +// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl; + } + if (tmin == 1 && tmax == 0) { + return OptInterval(); + } else { + return Interval(tmin, tmax); + } +} + +/* + * Clip the Bezier curve "B" wrt the Bezier curve "A" for individuating + * points which have collinear normals; the new parameter interval + * for the clipped curve is returned through the output parameter "dom" + */ +template <> +OptInterval clip<collinear_normal_tag> (std::vector<Point> const& A, + std::vector<Point> const& B, + double /*precision*/) +{ + std::vector<Point> F; + make_focus(F, A); + return clip_interval(B, F); +} + + + +const double MAX_PRECISION = 1e-8; +const double MIN_CLIPPED_SIZE_THRESHOLD = 0.8; +const Interval UNIT_INTERVAL(0,1); +const OptInterval EMPTY_INTERVAL; +const Interval H1_INTERVAL(0, 0.5); +const Interval H2_INTERVAL(nextafter(0.5, 1.0), 1.0); + +/* + * iterate + * + * input: + * A, B: control point sets of two bezier curves + * domA, domB: real parameter intervals of the two curves + * precision: required computational precision of the returned parameter ranges + * output: + * domsA, domsB: sets of parameter intervals + * + * The parameter intervals are computed by using a Bezier clipping algorithm, + * in case the clipping doesn't shrink the initial interval more than 20%, + * a subdivision step is performed. + * If during the computation both curves collapse to a single point + * the routine exits independently by the precision reached in the computation + * of the curve intervals. + */ +template <> +void iterate<intersection_point_tag> (std::vector<Interval>& domsA, + std::vector<Interval>& domsB, + std::vector<Point> const& A, + std::vector<Point> const& B, + Interval const& domA, + Interval const& domB, + double precision ) +{ + // in order to limit recursion + static size_t counter = 0; + if (domA.extent() == 1 && domB.extent() == 1) counter = 0; + if (++counter > 100) return; +#if VERBOSE + std::cerr << std::fixed << std::setprecision(16); + std::cerr << ">> curve subdision performed <<" << std::endl; + std::cerr << "dom(A) : " << domA << std::endl; + std::cerr << "dom(B) : " << domB << std::endl; +// std::cerr << "angle(A) : " << angle(A) << std::endl; +// std::cerr << "angle(B) : " << angle(B) << std::endl; +#endif + + if (precision < MAX_PRECISION) + precision = MAX_PRECISION; + + std::vector<Point> pA = A; + std::vector<Point> pB = B; + std::vector<Point>* C1 = &pA; + std::vector<Point>* C2 = &pB; + + Interval dompA = domA; + Interval dompB = domB; + Interval* dom1 = &dompA; + Interval* dom2 = &dompB; + + OptInterval dom; + + if ( is_constant(A, precision) && is_constant(B, precision) ){ + Point M1 = middle_point(C1->front(), C1->back()); + Point M2 = middle_point(C2->front(), C2->back()); + if (are_near(M1,M2)){ + domsA.push_back(domA); + domsB.push_back(domB); + } + return; + } + + size_t iter = 0; + while (++iter < 100 + && (dompA.extent() >= precision || dompB.extent() >= precision)) + { +#if VERBOSE + std::cerr << "iter: " << iter << std::endl; +#endif + dom = clip<intersection_point_tag>(*C1, *C2, precision); + + if (dom.empty()) + { +#if VERBOSE + std::cerr << "dom: empty" << std::endl; +#endif + return; + } +#if VERBOSE + std::cerr << "dom : " << dom << std::endl; +#endif + // all other cases where dom[0] > dom[1] are invalid + assert(dom->min() <= dom->max()); + + map_to(*dom2, *dom); + + portion(*C2, *dom); + if (is_constant(*C2, precision) && is_constant(*C1, precision)) + { + Point M1 = middle_point(C1->front(), C1->back()); + Point M2 = middle_point(C2->front(), C2->back()); +#if VERBOSE + std::cerr << "both curves are constant: \n" + << "M1: " << M1 << "\n" + << "M2: " << M2 << std::endl; + print(*C2, "C2"); + print(*C1, "C1"); +#endif + if (are_near(M1,M2)) + break; // append the new interval + else + return; // exit without appending any new interval + } + + + // if we have clipped less than 20% than we need to subdive the curve + // with the largest domain into two sub-curves + if (dom->extent() > MIN_CLIPPED_SIZE_THRESHOLD) + { +#if VERBOSE + std::cerr << "clipped less than 20% : " << dom->extent() << std::endl; + std::cerr << "angle(pA) : " << angle(pA) << std::endl; + std::cerr << "angle(pB) : " << angle(pB) << std::endl; +#endif + std::vector<Point> pC1, pC2; + Interval dompC1, dompC2; + if (dompA.extent() > dompB.extent()) + { + pC1 = pC2 = pA; + portion(pC1, H1_INTERVAL); + portion(pC2, H2_INTERVAL); + dompC1 = dompC2 = dompA; + map_to(dompC1, H1_INTERVAL); + map_to(dompC2, H2_INTERVAL); + iterate<intersection_point_tag>(domsA, domsB, pC1, pB, + dompC1, dompB, precision); + iterate<intersection_point_tag>(domsA, domsB, pC2, pB, + dompC2, dompB, precision); + } + else + { + pC1 = pC2 = pB; + portion(pC1, H1_INTERVAL); + portion(pC2, H2_INTERVAL); + dompC1 = dompC2 = dompB; + map_to(dompC1, H1_INTERVAL); + map_to(dompC2, H2_INTERVAL); + iterate<intersection_point_tag>(domsB, domsA, pC1, pA, + dompC1, dompA, precision); + iterate<intersection_point_tag>(domsB, domsA, pC2, pA, + dompC2, dompA, precision); + } + return; + } + + swap(C1, C2); + swap(dom1, dom2); +#if VERBOSE + std::cerr << "dom(pA) : " << dompA << std::endl; + std::cerr << "dom(pB) : " << dompB << std::endl; +#endif + } + domsA.push_back(dompA); + domsB.push_back(dompB); +} + + +/* + * iterate + * + * input: + * A, B: control point sets of two bezier curves + * domA, domB: real parameter intervals of the two curves + * precision: required computational precision of the returned parameter ranges + * output: + * domsA, domsB: sets of parameter intervals + * + * The parameter intervals are computed by using a Bezier clipping algorithm, + * in case the clipping doesn't shrink the initial interval more than 20%, + * a subdivision step is performed. + * If during the computation one of the two curve interval length becomes less + * than MAX_PRECISION the routine exits independently by the precision reached + * in the computation of the other curve interval. + */ +template <> +void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, + std::vector<Interval>& domsB, + std::vector<Point> const& A, + std::vector<Point> const& B, + Interval const& domA, + Interval const& domB, + double precision) +{ + // in order to limit recursion + static size_t counter = 0; + if (domA.extent() == 1 && domB.extent() == 1) counter = 0; + if (++counter > 100) return; +#if VERBOSE + std::cerr << std::fixed << std::setprecision(16); + std::cerr << ">> curve subdision performed <<" << std::endl; + std::cerr << "dom(A) : " << domA << std::endl; + std::cerr << "dom(B) : " << domB << std::endl; +// std::cerr << "angle(A) : " << angle(A) << std::endl; +// std::cerr << "angle(B) : " << angle(B) << std::endl; +#endif + + if (precision < MAX_PRECISION) + precision = MAX_PRECISION; + + std::vector<Point> pA = A; + std::vector<Point> pB = B; + std::vector<Point>* C1 = &pA; + std::vector<Point>* C2 = &pB; + + Interval dompA = domA; + Interval dompB = domB; + Interval* dom1 = &dompA; + Interval* dom2 = &dompB; + + OptInterval dom; + + size_t iter = 0; + while (++iter < 100 + && (dompA.extent() >= precision || dompB.extent() >= precision)) + { +#if VERBOSE + std::cerr << "iter: " << iter << std::endl; +#endif + dom = clip<collinear_normal_tag>(*C1, *C2, precision); + + if (dom.empty()) { +#if VERBOSE + std::cerr << "dom: empty" << std::endl; +#endif + return; + } +#if VERBOSE + std::cerr << "dom : " << dom << std::endl; +#endif + assert(dom->min() <= dom->max()); + + map_to(*dom2, *dom); + + // it's better to stop before losing computational precision + if (iter > 1 && (dom2->extent() <= MAX_PRECISION)) + { +#if VERBOSE + std::cerr << "beyond max precision limit" << std::endl; +#endif + break; + } + + portion(*C2, *dom); + if (iter > 1 && is_constant(*C2, precision)) + { +#if VERBOSE + std::cerr << "new curve portion pC1 is constant" << std::endl; +#endif + break; + } + + + // if we have clipped less than 20% than we need to subdive the curve + // with the largest domain into two sub-curves + if ( dom->extent() > MIN_CLIPPED_SIZE_THRESHOLD) + { +#if VERBOSE + std::cerr << "clipped less than 20% : " << dom->extent() << std::endl; + std::cerr << "angle(pA) : " << angle(pA) << std::endl; + std::cerr << "angle(pB) : " << angle(pB) << std::endl; +#endif + std::vector<Point> pC1, pC2; + Interval dompC1, dompC2; + if (dompA.extent() > dompB.extent()) + { + if ((dompA.extent() / 2) < MAX_PRECISION) + { + break; + } + pC1 = pC2 = pA; + portion(pC1, H1_INTERVAL); + if (false && is_constant(pC1, precision)) + { +#if VERBOSE + std::cerr << "new curve portion pC1 is constant" << std::endl; +#endif + break; + } + portion(pC2, H2_INTERVAL); + if (is_constant(pC2, precision)) + { +#if VERBOSE + std::cerr << "new curve portion pC2 is constant" << std::endl; +#endif + break; + } + dompC1 = dompC2 = dompA; + map_to(dompC1, H1_INTERVAL); + map_to(dompC2, H2_INTERVAL); + iterate<collinear_normal_tag>(domsA, domsB, pC1, pB, + dompC1, dompB, precision); + iterate<collinear_normal_tag>(domsA, domsB, pC2, pB, + dompC2, dompB, precision); + } + else + { + if ((dompB.extent() / 2) < MAX_PRECISION) + { + break; + } + pC1 = pC2 = pB; + portion(pC1, H1_INTERVAL); + if (is_constant(pC1, precision)) + { +#if VERBOSE + std::cerr << "new curve portion pC1 is constant" << std::endl; +#endif + break; + } + portion(pC2, H2_INTERVAL); + if (is_constant(pC2, precision)) + { +#if VERBOSE + std::cerr << "new curve portion pC2 is constant" << std::endl; +#endif + break; + } + dompC1 = dompC2 = dompB; + map_to(dompC1, H1_INTERVAL); + map_to(dompC2, H2_INTERVAL); + iterate<collinear_normal_tag>(domsB, domsA, pC1, pA, + dompC1, dompA, precision); + iterate<collinear_normal_tag>(domsB, domsA, pC2, pA, + dompC2, dompA, precision); + } + return; + } + + swap(C1, C2); + swap(dom1, dom2); +#if VERBOSE + std::cerr << "dom(pA) : " << dompA << std::endl; + std::cerr << "dom(pB) : " << dompB << std::endl; +#endif + } + domsA.push_back(dompA); + domsB.push_back(dompB); +} + + +/* + * get_solutions + * + * input: A, B - set of control points of two Bezier curve + * input: precision - required precision of computation + * input: clip - the routine used for clipping + * output: xs - set of pairs of parameter values + * at which the clipping algorithm converges + * + * This routine is based on the Bezier Clipping Algorithm, + * see: Sederberg - Computer Aided Geometric Design + */ +template <typename Tag> +void get_solutions (std::vector< std::pair<double, double> >& xs, + std::vector<Point> const& A, + std::vector<Point> const& B, + double precision) +{ + std::pair<double, double> ci; + std::vector<Interval> domsA, domsB; + iterate<Tag> (domsA, domsB, A, B, UNIT_INTERVAL, UNIT_INTERVAL, precision); + if (domsA.size() != domsB.size()) + { + assert (domsA.size() == domsB.size()); + } + xs.clear(); + xs.reserve(domsA.size()); + for (size_t i = 0; i < domsA.size(); ++i) + { +#if VERBOSE + std::cerr << i << " : domA : " << domsA[i] << std::endl; + std::cerr << "extent A: " << domsA[i].extent() << " "; + std::cerr << "precision A: " << get_precision(domsA[i]) << std::endl; + std::cerr << i << " : domB : " << domsB[i] << std::endl; + std::cerr << "extent B: " << domsB[i].extent() << " "; + std::cerr << "precision B: " << get_precision(domsB[i]) << std::endl; +#endif + ci.first = domsA[i].middle(); + ci.second = domsB[i].middle(); + xs.push_back(ci); + } +} + +} /* end namespace bezier_clipping */ } /* end namespace detail */ + + +/* + * 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) +{ + using detail::bezier_clipping::get_solutions; + using detail::bezier_clipping::collinear_normal_tag; + get_solutions<collinear_normal_tag>(xs, A, B, precision); +} + + +/* + * find_intersections_bezier_clipping + * + * 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) +{ + using detail::bezier_clipping::get_solutions; + using detail::bezier_clipping::intersection_point_tag; + get_solutions<intersection_point_tag>(xs, A, B, precision); +} + +} // end namespace Geom + + + + +/* + 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/src/2geom/bezier-curve.cpp b/src/2geom/bezier-curve.cpp new file mode 100644 index 0000000..73fafe4 --- /dev/null +++ b/src/2geom/bezier-curve.cpp @@ -0,0 +1,516 @@ +/* Bezier curve implementation + * + * 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. + */ + +#include <2geom/bezier-curve.h> +#include <2geom/path-sink.h> +#include <2geom/basic-intersection.h> +#include <2geom/nearest-time.h> + +namespace Geom +{ + +/** + * @class BezierCurve + * @brief Two-dimensional Bezier curve of arbitrary order. + * + * Bezier curves are an expansion of the concept of linear interpolation to n points. + * Linear segments in 2Geom are in fact Bezier curves of order 1. + * + * Let \f$\mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\ldots\mathbf{p}_n}\f$ denote a Bezier curve + * of order \f$n\f$ defined by the points \f$\mathbf{p}_0, \mathbf{p}_1, \ldots, \mathbf{p}_n\f$. + * Bezier curve of order 1 is a linear interpolation curve between two points, defined as + * \f[ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1}(t) = (1-t)\mathbf{p}_0 + t\mathbf{p}_1 \f] + * If we now substitute points \f$\mathbf{p_0}\f$ and \f$\mathbf{p_1}\f$ in this definition + * by linear interpolations, we get the definition of a Bezier curve of order 2, also called + * a quadratic Bezier curve. + * \f{align*}{ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2}(t) + &= (1-t) \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1}(t) + t \mathbf{B}_{\mathbf{p}_1\mathbf{p}_2}(t) \\ + \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2}(t) + &= (1-t)^2\mathbf{p}_0 + 2(1-t)t\mathbf{p}_1 + t^2\mathbf{p}_2 \f} + * By substituting points for quadratic Bezier curves in the original definition, + * we get a Bezier curve of order 3, called a cubic Bezier curve. + * \f{align*}{ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2\mathbf{p}_3}(t) + &= (1-t) \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2}(t) + + t \mathbf{B}_{\mathbf{p}_1\mathbf{p}_2\mathbf{p}_3}(t) \\ + \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\mathbf{p}_2\mathbf{p}_3}(t) + &= (1-t)^3\mathbf{p}_0+3(1-t)^2t\mathbf{p}_1+3(1-t)t^2\mathbf{p}_2+t^3\mathbf{p}_3 \f} + * In general, a Bezier curve or order \f$n\f$ can be recursively defined as + * \f[ \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\ldots\mathbf{p}_n}(t) + = (1-t) \mathbf{B}_{\mathbf{p}_0\mathbf{p}_1\ldots\mathbf{p}_{n-1}}(t) + + t \mathbf{B}_{\mathbf{p}_1\mathbf{p}_2\ldots\mathbf{p}_n}(t) \f] + * + * This substitution can be repeated an arbitrary number of times. To picture this, imagine + * the evaluation of a point on the curve as follows: first, all control points are joined with + * straight lines, and a point corresponding to the selected time value is marked on them. + * Then, the marked points are joined with straight lines and the point corresponding to + * the time value is marked. This is repeated until only one marked point remains, which is the + * point at the selected time value. + * + * @image html bezier-curve-evaluation.png "Evaluation of the Bezier curve" + * + * An important property of the Bezier curves is that their parameters (control points) + * have an intuitive geometric interpretation. Because of this, they are frequently used + * in vector graphics editors. + * + * Every Bezier curve is contained in its control polygon (the convex polygon composed + * of its control points). This fact is useful for sweepline algorithms and intersection. + * + * @par Implementation notes + * The order of a Bezier curve is immuable once it has been created. Normally, you should + * know the order at compile time and use the BezierCurveN template. If you need to determine + * the order at runtime, use the BezierCurve::create() function. It will create a BezierCurveN + * for orders 1, 2 and 3 (up to cubic Beziers), so you can later <tt>dynamic_cast</tt> + * to those types, and for higher orders it will create an instance of BezierCurve. + * + * @relates BezierCurveN + * @ingroup Curves + */ + +/** + * @class BezierCurveN + * @brief Bezier curve with compile-time specified order. + * + * @tparam degree unsigned value indicating the order of the Bezier curve + * + * @relates BezierCurve + * @ingroup Curves + */ + + +BezierCurve::BezierCurve(std::vector<Point> const &pts) + : inner(pts) +{ + if (pts.size() < 2) { + THROW_RANGEERROR("Bezier curve must have at least 2 control points"); + } +} + +bool BezierCurve::isDegenerate() const +{ + for (unsigned d = 0; d < 2; ++d) { + Coord ic = inner[d][0]; + for (unsigned i = 1; i < size(); ++i) { + if (inner[d][i] != ic) return false; + } + } + return true; +} + +Coord BezierCurve::length(Coord tolerance) const +{ + switch (order()) + { + case 0: + return 0.0; + case 1: + return distance(initialPoint(), finalPoint()); + case 2: + { + std::vector<Point> pts = controlPoints(); + return bezier_length(pts[0], pts[1], pts[2], tolerance); + } + case 3: + { + std::vector<Point> pts = controlPoints(); + return bezier_length(pts[0], pts[1], pts[2], pts[3], tolerance); + } + default: + return bezier_length(controlPoints(), tolerance); + } +} + +std::vector<CurveIntersection> +BezierCurve::intersect(Curve const &other, Coord eps) const +{ + std::vector<CurveIntersection> result; + + // in case we encounter an order-1 curve created from a vector + // or a degenerate elliptical arc + if (isLineSegment()) { + LineSegment ls(initialPoint(), finalPoint()); + result = ls.intersect(other); + return result; + } + + // here we are sure that this curve is at least a quadratic Bezier + BezierCurve const *bez = dynamic_cast<BezierCurve const *>(&other); + if (bez) { + std::vector<std::pair<double, double> > xs; + find_intersections(xs, inner, bez->inner, eps); + for (unsigned i = 0; i < xs.size(); ++i) { + CurveIntersection x(*this, other, xs[i].first, xs[i].second); + result.push_back(x); + } + return result; + } + + // pass other intersection types to the other curve + result = other.intersect(*this, eps); + transpose_in_place(result); + return result; +} + +bool BezierCurve::isNear(Curve const &c, Coord precision) const +{ + if (this == &c) return true; + + BezierCurve const *other = dynamic_cast<BezierCurve const *>(&c); + if (!other) return false; + + if (!are_near(inner.at0(), other->inner.at0(), precision)) return false; + if (!are_near(inner.at1(), other->inner.at1(), precision)) return false; + + if (size() == other->size()) { + for (unsigned i = 1; i < order(); ++i) { + if (!are_near(inner.point(i), other->inner.point(i), precision)) { + return false; + } + } + return true; + } else { + // TODO: comparison after degree elevation + return false; + } +} + +bool BezierCurve::operator==(Curve const &c) const +{ + if (this == &c) return true; + + BezierCurve const *other = dynamic_cast<BezierCurve const *>(&c); + if (!other) return false; + if (size() != other->size()) return false; + + for (unsigned i = 0; i < size(); ++i) { + if (controlPoint(i) != other->controlPoint(i)) return false; + } + return true; +} + +Coord BezierCurve::nearestTime(Point const &p, Coord from, Coord to) const +{ + return nearest_time(p, inner, from, to); +} + +void BezierCurve::feed(PathSink &sink, bool moveto_initial) const +{ + if (size() > 4) { + Curve::feed(sink, moveto_initial); + return; + } + + Point ip = controlPoint(0); + if (moveto_initial) { + sink.moveTo(ip); + } + switch (size()) { + case 2: + sink.lineTo(controlPoint(1)); + break; + case 3: + sink.quadTo(controlPoint(1), controlPoint(2)); + break; + case 4: + sink.curveTo(controlPoint(1), controlPoint(2), controlPoint(3)); + break; + default: + // TODO: add a path sink method that accepts a vector of control points + // and converts to cubic spline by default + assert(false); + break; + } +} + +BezierCurve *BezierCurve::create(std::vector<Point> const &pts) +{ + switch (pts.size()) { + case 0: + case 1: + THROW_LOGICALERROR("BezierCurve::create: too few points in vector"); + return NULL; + case 2: + return new LineSegment(pts[0], pts[1]); + case 3: + return new QuadraticBezier(pts[0], pts[1], pts[2]); + case 4: + return new CubicBezier(pts[0], pts[1], pts[2], pts[3]); + default: + return new BezierCurve(pts); + } +} + +// optimized specializations for LineSegment + +template <> +Curve *BezierCurveN<1>::derivative() const { + double dx = inner[X][1] - inner[X][0], dy = inner[Y][1] - inner[Y][0]; + return new BezierCurveN<1>(Point(dx,dy),Point(dx,dy)); +} + +template<> +Coord BezierCurveN<1>::nearestTime(Point const& p, Coord from, Coord to) const +{ + using std::swap; + + if ( from > to ) swap(from, to); + Point ip = pointAt(from); + Point fp = pointAt(to); + Point v = fp - ip; + Coord l2v = L2sq(v); + if (l2v == 0) return 0; + Coord t = dot( p - ip, v ) / l2v; + if ( t <= 0 ) return from; + else if ( t >= 1 ) return to; + else return from + t*(to-from); +} + +template <> +std::vector<CurveIntersection> BezierCurveN<1>::intersect(Curve const &other, Coord eps) const +{ + std::vector<CurveIntersection> result; + + // only handle intersections with other LineSegments here + if (other.isLineSegment()) { + Line this_line(initialPoint(), finalPoint()); + Line other_line(other.initialPoint(), other.finalPoint()); + result = this_line.intersect(other_line); + filter_line_segment_intersections(result, true, true); + return result; + } + + // pass all other types to the other curve + result = other.intersect(*this, eps); + transpose_in_place(result); + return result; +} + +template <> +int BezierCurveN<1>::winding(Point const &p) const +{ + Point ip = inner.at0(), fp = inner.at1(); + if (p[Y] == std::max(ip[Y], fp[Y])) return 0; + + Point v = fp - ip; + assert(v[Y] != 0); + Coord t = (p[Y] - ip[Y]) / v[Y]; + assert(t >= 0 && t <= 1); + Coord xcross = lerp(t, ip[X], fp[X]); + if (xcross > p[X]) { + return v[Y] > 0 ? 1 : -1; + } + return 0; +} + +template <> +void BezierCurveN<1>::feed(PathSink &sink, bool moveto_initial) const +{ + if (moveto_initial) { + sink.moveTo(controlPoint(0)); + } + sink.lineTo(controlPoint(1)); +} + +template <> +void BezierCurveN<2>::feed(PathSink &sink, bool moveto_initial) const +{ + if (moveto_initial) { + sink.moveTo(controlPoint(0)); + } + sink.quadTo(controlPoint(1), controlPoint(2)); +} + +template <> +void BezierCurveN<3>::feed(PathSink &sink, bool moveto_initial) const +{ + if (moveto_initial) { + sink.moveTo(controlPoint(0)); + } + sink.curveTo(controlPoint(1), controlPoint(2), controlPoint(3)); +} + + +static Coord bezier_length_internal(std::vector<Point> &v1, Coord tolerance, int level) +{ + /* The Bezier length algorithm used in 2Geom utilizes a simple fact: + * the Bezier curve is longer than the distance between its endpoints + * but shorter than the length of the polyline formed by its control + * points. When the difference between the two values is smaller than the + * error tolerance, we can be sure that the true value is no further than + * 0.5 * tolerance from their arithmetic mean. When it's larger, we recursively + * subdivide the Bezier curve into two parts and add their lengths. + * + * We cap the maximum number of subdivisions at 256, which corresponds to 8 levels. + */ + Coord lower = distance(v1.front(), v1.back()); + Coord upper = 0.0; + for (size_t i = 0; i < v1.size() - 1; ++i) { + upper += distance(v1[i], v1[i+1]); + } + if (upper - lower <= 2*tolerance || level >= 8) { + return (lower + upper) / 2; + } + + + std::vector<Point> v2 = v1; + + /* Compute the right subdivision directly in v1 and the left one in v2. + * Explanation of the algorithm used: + * We have to compute the left and right edges of this triangle in which + * the top row are the control points of the Bezier curve, and each cell + * is equal to the arithmetic mean of the cells directly above it + * to the right and left. This corresponds to subdividing the Bezier curve + * at time value 0.5: the left edge has the control points of the first + * portion of the Bezier curve and the right edge - the second one. + * In the example we subdivide a curve with 5 control points (order 4). + * + * Start: + * 0 1 2 3 4 + * ? ? ? ? + * ? ? ? + * ? ? + * ? + * # means we have overwritten the value, ? means we don't know + * the value yet. Numbers mean the value is at i-th position in the vector. + * + * After loop with i==1 + * # 1 2 3 4 + * 0 ? ? ? -> write 0 to v2[1] + * ? ? ? + * ? ? + * ? + * + * After loop with i==2 + * # # 2 3 4 + * # 1 ? ? + * 0 ? ? -> write 0 to v2[2] + * ? ? + * ? + * + * After loop with i==3 + * # # # 3 4 + * # # 2 ? + * # 1 ? + * 0 ? -> write 0 to v2[3] + * ? + * + * After loop with i==4, we have the right edge of the triangle in v1, + * and we write the last value needed for the left edge in v2[4]. + */ + + for (size_t i = 1; i < v1.size(); ++i) { + for (size_t j = i; j > 0; --j) { + v1[j-1] = 0.5 * (v1[j-1] + v1[j]); + } + v2[i] = v1[0]; + } + + return bezier_length_internal(v1, 0.5 * tolerance, level + 1) + + bezier_length_internal(v2, 0.5 * tolerance, level + 1); +} + +/** @brief Compute the length of a bezier curve given by a vector of its control points + * @relatesalso BezierCurve */ +Coord bezier_length(std::vector<Point> const &points, Coord tolerance) +{ + if (points.size() < 2) return 0.0; + std::vector<Point> v1 = points; + return bezier_length_internal(v1, tolerance, 0); +} + +static Coord bezier_length_internal(Point a0, Point a1, Point a2, Coord tolerance, int level) +{ + Coord lower = distance(a0, a2); + Coord upper = distance(a0, a1) + distance(a1, a2); + + if (upper - lower <= 2*tolerance || level >= 8) { + return (lower + upper) / 2; + } + + Point // Casteljau subdivision + // b0 = a0, + // c0 = a2, + b1 = 0.5*(a0 + a1), + c1 = 0.5*(a1 + a2), + b2 = 0.5*(b1 + c1); // == c2 + return bezier_length_internal(a0, b1, b2, 0.5 * tolerance, level + 1) + + bezier_length_internal(b2, c1, a2, 0.5 * tolerance, level + 1); +} + +/** @brief Compute the length of a quadratic bezier curve given by its control points + * @relatesalso QuadraticBezier */ +Coord bezier_length(Point a0, Point a1, Point a2, Coord tolerance) +{ + return bezier_length_internal(a0, a1, a2, tolerance, 0); +} + +static Coord bezier_length_internal(Point a0, Point a1, Point a2, Point a3, Coord tolerance, int level) +{ + Coord lower = distance(a0, a3); + Coord upper = distance(a0, a1) + distance(a1, a2) + distance(a2, a3); + + if (upper - lower <= 2*tolerance || level >= 8) { + return (lower + upper) / 2; + } + + Point // Casteljau subdivision + // b0 = a0, + // c0 = a3, + b1 = 0.5*(a0 + a1), + t0 = 0.5*(a1 + a2), + c1 = 0.5*(a2 + a3), + b2 = 0.5*(b1 + t0), + c2 = 0.5*(t0 + c1), + b3 = 0.5*(b2 + c2); // == c3 + return bezier_length_internal(a0, b1, b2, b3, 0.5 * tolerance, level + 1) + + bezier_length_internal(b3, c2, c1, a3, 0.5 * tolerance, level + 1); +} + +/** @brief Compute the length of a cubic bezier curve given by its control points + * @relatesalso CubicBezier */ +Coord bezier_length(Point a0, Point a1, Point a2, Point a3, Coord tolerance) +{ + return bezier_length_internal(a0, a1, a2, a3, tolerance, 0); +} + +} // end namespace Geom + +/* + 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/src/2geom/bezier-curve.h b/src/2geom/bezier-curve.h new file mode 100644 index 0000000..9416ba7 --- /dev/null +++ b/src/2geom/bezier-curve.h @@ -0,0 +1,352 @@ +/** + * \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 + virtual Point initialPoint() const { return inner.at0(); } + virtual Point finalPoint() const { return inner.at1(); } + virtual bool isDegenerate() const; + virtual bool isLineSegment() const { return size() == 2; } + virtual void setInitial(Point const &v) { setPoint(0, v); } + virtual void setFinal(Point const &v) { setPoint(order(), v); } + virtual Rect boundsFast() const { return *bounds_fast(inner); } + virtual Rect boundsExact() const { return *bounds_exact(inner); } + virtual OptRect boundsLocal(OptInterval const &i, unsigned deg) const { + 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(); + } + virtual Curve *duplicate() const { + return new BezierCurve(*this); + } + virtual Curve *portion(Coord f, Coord t) const { + return new BezierCurve(Geom::portion(inner, f, t)); + } + virtual Curve *reverse() const { + return new BezierCurve(Geom::reverse(inner)); + } + + using Curve::operator*=; + virtual void operator*=(Translate const &tr) { + for (unsigned i = 0; i < size(); ++i) { + inner[X][i] += tr[X]; + inner[Y][i] += tr[Y]; + } + } + virtual void operator*=(Scale const &s) { + for (unsigned i = 0; i < size(); ++i) { + inner[X][i] *= s[X]; + inner[Y][i] *= s[Y]; + } + } + virtual void operator*=(Affine const &m) { + for (unsigned i = 0; i < size(); ++i) { + setPoint(i, controlPoint(i) * m); + } + } + + virtual Curve *derivative() const { + return new BezierCurve(Geom::derivative(inner[X]), Geom::derivative(inner[Y])); + } + virtual int degreesOfFreedom() const { + return 2 * (order() + 1); + } + virtual std::vector<Coord> roots(Coord v, Dim2 d) const { + return (inner[d] - v).roots(); + } + virtual Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const; + virtual Coord length(Coord tolerance) const; + virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const; + virtual Point pointAt(Coord t) const { return inner.pointAt(t); } + virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const { + return inner.valueAndDerivatives(t, n); + } + virtual Coord valueAt(Coord t, Dim2 d) const { return inner[d].valueAt(t); } + virtual D2<SBasis> toSBasis() const {return inner.toSBasis(); } + virtual bool isNear(Curve const &c, Coord precision) const; + virtual bool operator==(Curve const &c) const; + virtual void feed(PathSink &sink, bool) const; +}; + +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)); + } + + virtual bool isDegenerate() const { + return BezierCurve::isDegenerate(); + } + + virtual bool isLineSegment() const { + return size() == 2; + } + + virtual Curve *duplicate() const { + return new BezierCurveN(*this); + } + virtual Curve *portion(Coord f, Coord t) const { + if (degree == 1) { + return new BezierCurveN<1>(pointAt(f), pointAt(t)); + } else { + return new BezierCurveN(Geom::portion(inner, f, t)); + } + } + virtual Curve *reverse() const { + if (degree == 1) { + return new BezierCurveN<1>(finalPoint(), initialPoint()); + } else { + return new BezierCurveN(Geom::reverse(inner)); + } + } + virtual Curve *derivative() const; + + virtual Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const { + return BezierCurve::nearestTime(p, from, to); + } + virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const { + // call super. this is implemented only to allow specializations + return BezierCurve::intersect(other, eps); + } + virtual int winding(Point const &p) const { + return Curve::winding(p); + } + virtual void feed(PathSink &sink, bool moveto_initial) const { + // call super. this is implemented only to allow specializations + BezierCurve::feed(sink, moveto_initial); + } +}; + +// 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 <> 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; + +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/src/2geom/bezier-to-sbasis.h b/src/2geom/bezier-to-sbasis.h new file mode 100644 index 0000000..73c55d9 --- /dev/null +++ b/src/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/src/2geom/bezier-utils.cpp b/src/2geom/bezier-utils.cpp new file mode 100644 index 0000000..181b5b3 --- /dev/null +++ b/src/2geom/bezier-utils.cpp @@ -0,0 +1,997 @@ +/* Bezier interpolation for inkscape drawing code. + * + * Original code published in: + * An Algorithm for Automatically Fitting Digitized Curves + * by Philip J. Schneider + * "Graphics Gems", Academic Press, 1990 + * + * Authors: + * Philip J. Schneider + * Lauris Kaplinski <lauris@kaplinski.com> + * Peter Moulder <pmoulder@mail.csse.monash.edu.au> + * + * Copyright (C) 1990 Philip J. Schneider + * Copyright (C) 2001 Lauris Kaplinski + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2003,2004 Monash University + * + * 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. + * + */ + +#define SP_HUGE 1e5 +#define noBEZIER_DEBUG + +#ifdef HAVE_IEEEFP_H +# include <ieeefp.h> +#endif + +#include <2geom/bezier-utils.h> +#include <2geom/math-utils.h> +#include <assert.h> + +namespace Geom { + +/* Forward declarations */ +static void generate_bezier(Point b[], Point const d[], double const u[], unsigned len, + Point const &tHat1, Point const &tHat2, double tolerance_sq); +static void estimate_lengths(Point bezier[], + Point const data[], double const u[], unsigned len, + Point const &tHat1, Point const &tHat2); +static void estimate_bi(Point b[4], unsigned ei, + Point const data[], double const u[], unsigned len); +static void reparameterize(Point const d[], unsigned len, double u[], Point const bezCurve[]); +static double NewtonRaphsonRootFind(Point const Q[], Point const &P, double u); +static Point darray_center_tangent(Point const d[], unsigned center, unsigned length); +static Point darray_right_tangent(Point const d[], unsigned const len); +static unsigned copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[]); +static void chord_length_parameterize(Point const d[], double u[], unsigned len); +static double compute_max_error_ratio(Point const d[], double const u[], unsigned len, + Point const bezCurve[], double tolerance, + unsigned *splitPoint); +static double compute_hook(Point const &a, Point const &b, double const u, Point const bezCurve[], + double const tolerance); + + +static Point const unconstrained_tangent(0, 0); + + +/* + * B0, B1, B2, B3 : Bezier multipliers + */ + +#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) ) +#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) ) +#define B2(u) ( 3 * u * u * ( 1.0 - u ) ) +#define B3(u) ( u * u * u ) + +#ifdef BEZIER_DEBUG +# define DOUBLE_ASSERT(x) assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) ) +# define BEZIER_ASSERT(b) do { \ + DOUBLE_ASSERT((b)[0][X]); DOUBLE_ASSERT((b)[0][Y]); \ + DOUBLE_ASSERT((b)[1][X]); DOUBLE_ASSERT((b)[1][Y]); \ + DOUBLE_ASSERT((b)[2][X]); DOUBLE_ASSERT((b)[2][Y]); \ + DOUBLE_ASSERT((b)[3][X]); DOUBLE_ASSERT((b)[3][Y]); \ + } while(0) +#else +# define DOUBLE_ASSERT(x) do { } while(0) +# define BEZIER_ASSERT(b) do { } while(0) +#endif + + +/** + * Fit a single-segment Bezier curve to a set of digitized points. + * + * \return Number of segments generated, or -1 on error. + */ +int +bezier_fit_cubic(Point *bezier, Point const *data, int len, double error) +{ + return bezier_fit_cubic_r(bezier, data, len, error, 1); +} + +/** + * Fit a multi-segment Bezier curve to a set of digitized points, with + * possible weedout of identical points and NaNs. + * + * \param max_beziers Maximum number of generated segments + * \param Result array, must be large enough for n. segments * 4 elements. + * + * \return Number of segments generated, or -1 on error. + */ +int +bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers) +{ + if(bezier == NULL || + data == NULL || + len <= 0 || + max_beziers >= (1ul << (31 - 2 - 1 - 3))) + return -1; + + Point *uniqued_data = new Point[len]; + unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data); + + if ( uniqued_len < 2 ) { + delete[] uniqued_data; + return 0; + } + + /* Call fit-cubic function with recursion. */ + int const ret = bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len, + unconstrained_tangent, unconstrained_tangent, + error, max_beziers); + delete[] uniqued_data; + return ret; +} + +/** + * Copy points from src to dest, filter out points containing NaN and + * adjacent points with equal x and y. + * \return length of dest + */ +static unsigned +copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[]) +{ + unsigned si = 0; + for (;;) { + if ( si == src_len ) { + return 0; + } + if (!std::isnan(src[si][X]) && + !std::isnan(src[si][Y])) { + dest[0] = Point(src[si]); + ++si; + break; + } + si++; + } + unsigned di = 0; + for (; si < src_len; ++si) { + Point const src_pt = Point(src[si]); + if ( src_pt != dest[di] + && !std::isnan(src_pt[X]) + && !std::isnan(src_pt[Y])) { + dest[++di] = src_pt; + } + } + unsigned dest_len = di + 1; + assert( dest_len <= src_len ); + return dest_len; +} + +/** + * Fit a multi-segment Bezier curve to a set of digitized points, without + * possible weedout of identical points and NaNs. + * + * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1]. + * \param max_beziers Maximum number of generated segments + * \param Result array, must be large enough for n. segments * 4 elements. + */ +int +bezier_fit_cubic_full(Point bezier[], int split_points[], + Point const data[], int const len, + Point const &tHat1, Point const &tHat2, + double const error, unsigned const max_beziers) +{ + if(!(bezier != NULL) || + !(data != NULL) || + !(len > 0) || + !(max_beziers >= 1) || + !(error >= 0.0)) + return -1; + + if ( len < 2 ) return 0; + + if ( len == 2 ) { + /* We have 2 points, which can be fitted trivially. */ + bezier[0] = data[0]; + bezier[3] = data[len - 1]; + double const dist = distance(bezier[0], bezier[3]) / 3.0; + if (std::isnan(dist)) { + /* Numerical problem, fall back to straight line segment. */ + bezier[1] = bezier[0]; + bezier[2] = bezier[3]; + } else { + bezier[1] = ( is_zero(tHat1) + ? ( 2 * bezier[0] + bezier[3] ) / 3. + : bezier[0] + dist * tHat1 ); + bezier[2] = ( is_zero(tHat2) + ? ( bezier[0] + 2 * bezier[3] ) / 3. + : bezier[3] + dist * tHat2 ); + } + BEZIER_ASSERT(bezier); + return 1; + } + + /* Parameterize points, and attempt to fit curve */ + unsigned splitPoint; /* Point to split point set at. */ + bool is_corner; + { + double *u = new double[len]; + chord_length_parameterize(data, u, len); + if ( u[len - 1] == 0.0 ) { + /* Zero-length path: every point in data[] is the same. + * + * (Clients aren't allowed to pass such data; handling the case is defensive + * programming.) + */ + delete[] u; + return 0; + } + + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize(data, len, u, bezier); + + /* Find max deviation of points to fitted curve. */ + double const tolerance = sqrt(error + 1e-9); + double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint); + + if ( fabs(maxErrorRatio) <= 1.0 ) { + BEZIER_ASSERT(bezier); + delete[] u; + return 1; + } + + /* If error not too large, then try some reparameterization and iteration. */ + if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) { + int const maxIterations = 4; /* std::max times to try iterating */ + for (int i = 0; i < maxIterations; i++) { + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize(data, len, u, bezier); + maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint); + if ( fabs(maxErrorRatio) <= 1.0 ) { + BEZIER_ASSERT(bezier); + delete[] u; + return 1; + } + } + } + delete[] u; + is_corner = (maxErrorRatio < 0); + } + + if (is_corner) { + assert(splitPoint < unsigned(len)); + if (splitPoint == 0) { + if (is_zero(tHat1)) { + /* Got spike even with unconstrained initial tangent. */ + ++splitPoint; + } else { + return bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2, + error, max_beziers); + } + } else if (splitPoint == unsigned(len - 1)) { + if (is_zero(tHat2)) { + /* Got spike even with unconstrained final tangent. */ + --splitPoint; + } else { + return bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent, + error, max_beziers); + } + } + } + + if ( 1 < max_beziers ) { + /* + * Fitting failed -- split at max error point and fit recursively + */ + unsigned const rec_max_beziers1 = max_beziers - 1; + + Point recTHat2, recTHat1; + if (is_corner) { + if(!(0 < splitPoint && splitPoint < unsigned(len - 1))) + return -1; + recTHat1 = recTHat2 = unconstrained_tangent; + } else { + /* Unit tangent vector at splitPoint. */ + recTHat2 = darray_center_tangent(data, splitPoint, len); + recTHat1 = -recTHat2; + } + int const nsegs1 = bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1, + tHat1, recTHat2, error, rec_max_beziers1); + if ( nsegs1 < 0 ) { +#ifdef BEZIER_DEBUG + g_print("fit_cubic[1]: recursive call failed\n"); +#endif + return -1; + } + assert( nsegs1 != 0 ); + if (split_points != NULL) { + split_points[nsegs1 - 1] = splitPoint; + } + unsigned const rec_max_beziers2 = max_beziers - nsegs1; + int const nsegs2 = bezier_fit_cubic_full(bezier + nsegs1*4, + ( split_points == NULL + ? NULL + : split_points + nsegs1 ), + data + splitPoint, len - splitPoint, + recTHat1, tHat2, error, rec_max_beziers2); + if ( nsegs2 < 0 ) { +#ifdef BEZIER_DEBUG + g_print("fit_cubic[2]: recursive call failed\n"); +#endif + return -1; + } + +#ifdef BEZIER_DEBUG + g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n", + nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers); +#endif + return nsegs1 + nsegs2; + } else { + return -1; + } +} + + +/** + * Fill in \a bezier[] based on the given data and tangent requirements, using + * a least-squares fit. + * + * Each of tHat1 and tHat2 should be either a zero vector or a unit vector. + * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise, + * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3]. + * + * \param tolerance_sq Used only for an initial guess as to tangent directions + * when \a tHat1 or \a tHat2 is zero. + */ +static void +generate_bezier(Point bezier[], + Point const data[], double const u[], unsigned const len, + Point const &tHat1, Point const &tHat2, + double const tolerance_sq) +{ + bool const est1 = is_zero(tHat1); + bool const est2 = is_zero(tHat2); + Point est_tHat1( est1 + ? darray_left_tangent(data, len, tolerance_sq) + : tHat1 ); + Point est_tHat2( est2 + ? darray_right_tangent(data, len, tolerance_sq) + : tHat2 ); + estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); + /* We find that darray_right_tangent tends to produce better results + for our current freehand tool than full estimation. */ + if (est1) { + estimate_bi(bezier, 1, data, u, len); + if (bezier[1] != bezier[0]) { + est_tHat1 = unit_vector(bezier[1] - bezier[0]); + } + estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); + } +} + + +static void +estimate_lengths(Point bezier[], + Point const data[], double const uPrime[], unsigned const len, + Point const &tHat1, Point const &tHat2) +{ + double C[2][2]; /* Matrix C. */ + double X[2]; /* Matrix X. */ + + /* Create the C and X matrices. */ + C[0][0] = 0.0; + C[0][1] = 0.0; + C[1][0] = 0.0; + C[1][1] = 0.0; + X[0] = 0.0; + X[1] = 0.0; + + /* First and last control points of the Bezier curve are positioned exactly at the first and + last data points. */ + bezier[0] = data[0]; + bezier[3] = data[len - 1]; + + for (unsigned i = 0; i < len; i++) { + /* Bezier control point coefficients. */ + double const b0 = B0(uPrime[i]); + double const b1 = B1(uPrime[i]); + double const b2 = B2(uPrime[i]); + double const b3 = B3(uPrime[i]); + + /* rhs for eqn */ + Point const a1 = b1 * tHat1; + Point const a2 = b2 * tHat2; + + C[0][0] += dot(a1, a1); + C[0][1] += dot(a1, a2); + C[1][0] = C[0][1]; + C[1][1] += dot(a2, a2); + + /* Additional offset to the data point from the predicted point if we were to set bezier[1] + to bezier[0] and bezier[2] to bezier[3]. */ + Point const shortfall + = ( data[i] + - ( ( b0 + b1 ) * bezier[0] ) + - ( ( b2 + b3 ) * bezier[3] ) ); + X[0] += dot(a1, shortfall); + X[1] += dot(a2, shortfall); + } + + /* We've constructed a pair of equations in the form of a matrix product C * alpha = X. + Now solve for alpha. */ + double alpha_l, alpha_r; + + /* Compute the determinants of C and X. */ + double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]; + if ( det_C0_C1 != 0 ) { + /* Apparently Kramer's rule. */ + double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0]; + double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]; + alpha_l = det_X_C1 / det_C0_C1; + alpha_r = det_C0_X / det_C0_C1; + } else { + /* The matrix is under-determined. Try requiring alpha_l == alpha_r. + * + * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same + * variable in the equations. We can do this by adding the columns of C to form a single + * column, to be multiplied by alpha to give the column vector X. + * + * We try each row in turn. + */ + double const c0 = C[0][0] + C[0][1]; + if (c0 != 0) { + alpha_l = alpha_r = X[0] / c0; + } else { + double const c1 = C[1][0] + C[1][1]; + if (c1 != 0) { + alpha_l = alpha_r = X[1] / c1; + } else { + /* Let the below code handle this. */ + alpha_l = alpha_r = 0.; + } + } + } + + /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get + coincident control points that lead to divide by zero in any subsequent + NewtonRaphsonRootFind() call.) */ + /// \todo Check whether this special-casing is necessary now that + /// NewtonRaphsonRootFind handles non-positive denominator. + if ( alpha_l < 1.0e-6 || + alpha_r < 1.0e-6 ) + { + alpha_l = alpha_r = distance(data[0], data[len-1]) / 3.0; + } + + /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and + right, respectively. */ + bezier[1] = alpha_l * tHat1 + bezier[0]; + bezier[2] = alpha_r * tHat2 + bezier[3]; + + return; +} + +static double lensq(Point const p) { + return dot(p, p); +} + +static void +estimate_bi(Point bezier[4], unsigned const ei, + Point const data[], double const u[], unsigned const len) +{ + if(!(1 <= ei && ei <= 2)) + return; + unsigned const oi = 3 - ei; + double num[2] = {0., 0.}; + double den = 0.; + for (unsigned i = 0; i < len; ++i) { + double const ui = u[i]; + double const b[4] = { + B0(ui), + B1(ui), + B2(ui), + B3(ui) + }; + + for (unsigned d = 0; d < 2; ++d) { + num[d] += b[ei] * (b[0] * bezier[0][d] + + b[oi] * bezier[oi][d] + + b[3] * bezier[3][d] + + - data[i][d]); + } + den -= b[ei] * b[ei]; + } + + if (den != 0.) { + for (unsigned d = 0; d < 2; ++d) { + bezier[ei][d] = num[d] / den; + } + } else { + bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.; + } +} + +/** + * Given set of points and their parameterization, try to find a better assignment of parameter + * values for the points. + * + * \param d Array of digitized points. + * \param u Current parameter values. + * \param bezCurve Current fitted curve. + * \param len Number of values in both d and u arrays. + * Also the size of the array that is allocated for return. + */ +static void +reparameterize(Point const d[], + unsigned const len, + double u[], + Point const bezCurve[]) +{ + assert( 2 <= len ); + + unsigned const last = len - 1; + assert( bezCurve[0] == d[0] ); + assert( bezCurve[3] == d[last] ); + assert( u[0] == 0.0 ); + assert( u[last] == 1.0 ); + /* Otherwise, consider including 0 and last in the below loop. */ + + for (unsigned i = 1; i < last; i++) { + u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]); + } +} + +/** + * Use Newton-Raphson iteration to find better root. + * + * \param Q Current fitted curve + * \param P Digitized point + * \param u Parameter value for "P" + * + * \return Improved u + */ +static double +NewtonRaphsonRootFind(Point const Q[], Point const &P, double const u) +{ + assert( 0.0 <= u ); + assert( u <= 1.0 ); + + /* Generate control vertices for Q'. */ + Point Q1[3]; + for (unsigned i = 0; i < 3; i++) { + Q1[i] = 3.0 * ( Q[i+1] - Q[i] ); + } + + /* Generate control vertices for Q''. */ + Point Q2[2]; + for (unsigned i = 0; i < 2; i++) { + Q2[i] = 2.0 * ( Q1[i+1] - Q1[i] ); + } + + /* Compute Q(u), Q'(u) and Q''(u). */ + Point const Q_u = bezier_pt(3, Q, u); + Point const Q1_u = bezier_pt(2, Q1, u); + Point const Q2_u = bezier_pt(1, Q2, u); + + /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the + distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the + distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum + distance from P to Q(u)). */ + Point const diff = Q_u - P; + double numerator = dot(diff, Q1_u); + double denominator = dot(Q1_u, Q1_u) + dot(diff, Q2_u); + + double improved_u; + if ( denominator > 0. ) { + /* One iteration of Newton-Raphson: + improved_u = u - f(u)/f'(u) */ + improved_u = u - ( numerator / denominator ); + } else { + /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather + than local minimum), so we move an arbitrary amount in the right direction. */ + if ( numerator > 0. ) { + improved_u = u * .98 - .01; + } else if ( numerator < 0. ) { + /* Deliberately asymmetrical, to reduce the chance of cycling. */ + improved_u = .031 + u * .98; + } else { + improved_u = u; + } + } + + if (!std::isfinite(improved_u)) { + improved_u = u; + } else if ( improved_u < 0.0 ) { + improved_u = 0.0; + } else if ( improved_u > 1.0 ) { + improved_u = 1.0; + } + + /* Ensure that improved_u isn't actually worse. */ + { + double const diff_lensq = lensq(diff); + for (double proportion = .125; ; proportion += .125) { + if ( lensq( bezier_pt(3, Q, improved_u) - P ) > diff_lensq ) { + if ( proportion > 1.0 ) { + //g_warning("found proportion %g", proportion); + improved_u = u; + break; + } + improved_u = ( ( 1 - proportion ) * improved_u + + proportion * u ); + } else { + break; + } + } + } + + DOUBLE_ASSERT(improved_u); + return improved_u; +} + +/** + * Evaluate a Bezier curve at parameter value \a t. + * + * \param degree The degree of the Bezier curve: 3 for cubic, 2 for quadratic etc. Must be less + * than 4. + * \param V The control points for the Bezier curve. Must have (\a degree+1) + * elements. + * \param t The "parameter" value, specifying whereabouts along the curve to + * evaluate. Typically in the range [0.0, 1.0]. + * + * Let s = 1 - t. + * BezierII(1, V) gives (s, t) * V, i.e. t of the way + * from V[0] to V[1]. + * BezierII(2, V) gives (s**2, 2*s*t, t**2) * V. + * BezierII(3, V) gives (s**3, 3 s**2 t, 3s t**2, t**3) * V. + * + * The derivative of BezierII(i, V) with respect to t + * is i * BezierII(i-1, V'), where for all j, V'[j] = + * V[j + 1] - V[j]. + */ +Point +bezier_pt(unsigned const degree, Point const V[], double const t) +{ + /** Pascal's triangle. */ + static int const pascal[4][4] = {{1, 0, 0, 0}, + {1, 1, 0, 0}, + {1, 2, 1, 0}, + {1, 3, 3, 1}}; + assert( degree < 4); + double const s = 1.0 - t; + + /* Calculate powers of t and s. */ + double spow[4]; + double tpow[4]; + spow[0] = 1.0; spow[1] = s; + tpow[0] = 1.0; tpow[1] = t; + for (unsigned i = 1; i < degree; ++i) { + spow[i + 1] = spow[i] * s; + tpow[i + 1] = tpow[i] * t; + } + + Point ret = spow[degree] * V[0]; + for (unsigned i = 1; i <= degree; ++i) { + ret += pascal[degree][i] * spow[degree - i] * tpow[i] * V[i]; + } + return ret; +} + +/* + * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent : + * Approximate unit tangents at endpoints and "center" of digitized curve + */ + +/** + * Estimate the (forward) tangent at point d[first + 0.5]. + * + * Unlike the center and right versions, this calculates the tangent in + * the way one might expect, i.e., wrt increasing index into d. + * \pre (2 \<= len) and (d[0] != d[1]). + **/ +Point +darray_left_tangent(Point const d[], unsigned const len) +{ + assert( len >= 2 ); + assert( d[0] != d[1] ); + return unit_vector( d[1] - d[0] ); +} + +/** + * Estimates the (backward) tangent at d[last - 0.5]. + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre 2 \<= len. + * \pre d[len - 1] != d[len - 2]. + * \pre all[p in d] in_svg_plane(p). + */ +static Point +darray_right_tangent(Point const d[], unsigned const len) +{ + assert( 2 <= len ); + unsigned const last = len - 1; + unsigned const prev = last - 1; + assert( d[last] != d[prev] ); + return unit_vector( d[prev] - d[last] ); +} + +/** + * Estimate the (forward) tangent at point d[0]. + * + * Unlike the center and right versions, this calculates the tangent in + * the way one might expect, i.e., wrt increasing index into d. + * + * \pre 2 \<= len. + * \pre d[0] != d[1]. + * \pre all[p in d] in_svg_plane(p). + * \post is_unit_vector(ret). + **/ +Point +darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq) +{ + assert( 2 <= len ); + assert( 0 <= tolerance_sq ); + for (unsigned i = 1;;) { + Point const pi(d[i]); + Point const t(pi - d[0]); + double const distsq = dot(t, t); + if ( tolerance_sq < distsq ) { + return unit_vector(t); + } + ++i; + if (i == len) { + return ( distsq == 0 + ? darray_left_tangent(d, len) + : unit_vector(t) ); + } + } +} + +/** + * Estimates the (backward) tangent at d[last]. + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre 2 \<= len. + * \pre d[len - 1] != d[len - 2]. + * \pre all[p in d] in_svg_plane(p). + */ +Point +darray_right_tangent(Point const d[], unsigned const len, double const tolerance_sq) +{ + assert( 2 <= len ); + assert( 0 <= tolerance_sq ); + unsigned const last = len - 1; + for (unsigned i = last - 1;; i--) { + Point const pi(d[i]); + Point const t(pi - d[last]); + double const distsq = dot(t, t); + if ( tolerance_sq < distsq ) { + return unit_vector(t); + } + if (i == 0) { + return ( distsq == 0 + ? darray_right_tangent(d, len) + : unit_vector(t) ); + } + } +} + +/** + * Estimates the (backward) tangent at d[center], by averaging the two + * segments connected to d[center] (and then normalizing the result). + * + * \note The tangent is "backwards", i.e. it is with respect to + * decreasing index rather than increasing index. + * + * \pre (0 \< center \< len - 1) and d is uniqued (at least in + * the immediate vicinity of \a center). + */ +static Point +darray_center_tangent(Point const d[], + unsigned const center, + unsigned const len) +{ + assert( center != 0 ); + assert( center < len - 1 ); + + Point ret; + if ( d[center + 1] == d[center - 1] ) { + /* Rotate 90 degrees in an arbitrary direction. */ + Point const diff = d[center] - d[center - 1]; + ret = rot90(diff); + } else { + ret = d[center - 1] - d[center + 1]; + } + ret.normalize(); + return ret; +} + + +/** + * Assign parameter values to digitized points using relative distances between points. + * + * \pre Parameter array u must have space for \a len items. + */ +static void +chord_length_parameterize(Point const d[], double u[], unsigned const len) +{ + if(!( 2 <= len )) + return; + + /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */ + u[0] = 0.0; + for (unsigned i = 1; i < len; i++) { + double const dist = distance(d[i], d[i-1]); + u[i] = u[i-1] + dist; + } + + /* Then scale to [0.0 .. 1.0]. */ + double tot_len = u[len - 1]; + if(!( tot_len != 0 )) + return; + if (std::isfinite(tot_len)) { + for (unsigned i = 1; i < len; ++i) { + u[i] /= tot_len; + } + } else { + /* We could do better, but this probably never happens anyway. */ + for (unsigned i = 1; i < len; ++i) { + u[i] = i / (double) ( len - 1 ); + } + } + + /** \todo + * It's been reported that u[len - 1] can differ from 1.0 on some + * systems (amd64), despite it having been calculated as x / x where x + * is isFinite and non-zero. + */ + if (u[len - 1] != 1) { + double const diff = u[len - 1] - 1; + if (fabs(diff) > 1e-13) { + assert(0); // No warnings in 2geom + //g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1", + // u[len - 1], diff); + } + u[len - 1] = 1; + } + +#ifdef BEZIER_DEBUG + assert( u[0] == 0.0 && u[len - 1] == 1.0 ); + for (unsigned i = 1; i < len; i++) { + assert( u[i] >= u[i-1] ); + } +#endif +} + + + + +/** + * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum + * error is non-zero) set \a *splitPoint to the corresponding index. + * + * \pre 2 \<= len. + * \pre u[0] == 0. + * \pre u[len - 1] == 1.0. + * \post ((ret == 0.0) + * || ((*splitPoint \< len - 1) + * \&\& (*splitPoint != 0 || ret \< 0.0))). + */ +static double +compute_max_error_ratio(Point const d[], double const u[], unsigned const len, + Point const bezCurve[], double const tolerance, + unsigned *const splitPoint) +{ + assert( 2 <= len ); + unsigned const last = len - 1; + assert( bezCurve[0] == d[0] ); + assert( bezCurve[3] == d[last] ); + assert( u[0] == 0.0 ); + assert( u[last] == 1.0 ); + /* I.e. assert that the error for the first & last points is zero. + * Otherwise we should include those points in the below loop. + * The assertion is also necessary to ensure 0 < splitPoint < last. + */ + + double maxDistsq = 0.0; /* Maximum error */ + double max_hook_ratio = 0.0; + unsigned snap_end = 0; + Point prev = bezCurve[0]; + for (unsigned i = 1; i <= last; i++) { + Point const curr = bezier_pt(3, bezCurve, u[i]); + double const distsq = lensq( curr - d[i] ); + if ( distsq > maxDistsq ) { + maxDistsq = distsq; + *splitPoint = i; + } + double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance); + if (max_hook_ratio < hook_ratio) { + max_hook_ratio = hook_ratio; + snap_end = i; + } + prev = curr; + } + + double const dist_ratio = sqrt(maxDistsq) / tolerance; + double ret; + if (max_hook_ratio <= dist_ratio) { + ret = dist_ratio; + } else { + assert(0 < snap_end); + ret = -max_hook_ratio; + *splitPoint = snap_end - 1; + } + assert( ret == 0.0 + || ( ( *splitPoint < last ) + && ( *splitPoint != 0 || ret < 0. ) ) ); + return ret; +} + +/** + * Whereas compute_max_error_ratio() checks for itself that each data point + * is near some point on the curve, this function checks that each point on + * the curve is near some data point (or near some point on the polyline + * defined by the data points, or something like that: we allow for a + * "reasonable curviness" from such a polyline). "Reasonable curviness" + * means we draw a circle centred at the midpoint of a..b, of radius + * proportional to the length |a - b|, and require that each point on the + * segment of bezCurve between the parameters of a and b be within that circle. + * If any point P on the bezCurve segment is outside of that allowable + * region (circle), then we return some metric that increases with the + * distance from P to the circle. + * + * Given that this is a fairly arbitrary criterion for finding appropriate + * places for sharp corners, we test only one point on bezCurve, namely + * the point on bezCurve with parameter halfway between our estimated + * parameters for a and b. (Alternatives are taking the farthest of a + * few parameters between those of a and b, or even using a variant of + * NewtonRaphsonFindRoot() for finding the maximum rather than minimum + * distance.) + */ +static double +compute_hook(Point const &a, Point const &b, double const u, Point const bezCurve[], + double const tolerance) +{ + Point const P = bezier_pt(3, bezCurve, u); + double const dist = distance((a+b)*.5, P); + if (dist < tolerance) { + return 0; + } + double const allowed = distance(a, b) + tolerance; + return dist / allowed; + /** \todo + * effic: Hooks are very rare. We could start by comparing + * distsq, only resorting to the more expensive L2 in cases of + * uncertainty. + */ +} + +} + +/* + 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/src/2geom/bezier-utils.h b/src/2geom/bezier-utils.h new file mode 100644 index 0000000..3e56e6e --- /dev/null +++ b/src/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/src/2geom/bezier.cpp b/src/2geom/bezier.cpp new file mode 100644 index 0000000..0c9d12c --- /dev/null +++ b/src/2geom/bezier.cpp @@ -0,0 +1,324 @@ +/** + * @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. + * + */ + +#include <2geom/bezier.h> +#include <2geom/solver.h> +#include <2geom/concepts.h> + +namespace Geom { + +std::vector<Coord> Bezier::valueAndDerivatives(Coord t, unsigned n_derivs) const { + /* This is inelegant, as it uses several extra stores. I think there might be a way to + * evaluate roughly in situ. */ + + // initialize return vector with zeroes, such that we only need to replace the non-zero derivs + std::vector<Coord> val_n_der(n_derivs + 1, Coord(0.0)); + + // initialize temp storage variables + std::valarray<Coord> d_(order()+1); + for(unsigned i = 0; i < size(); i++) { + d_[i] = c_[i]; + } + + unsigned nn = n_derivs + 1; + if(n_derivs > order()) { + nn = order()+1; // only calculate the non zero derivs + } + for(unsigned di = 0; di < nn; di++) { + //val_n_der[di] = (casteljau_subdivision(t, &d_[0], NULL, NULL, order() - di)); + val_n_der[di] = bernstein_value_at(t, &d_[0], order() - di); + for(unsigned i = 0; i < order() - di; i++) { + d_[i] = (order()-di)*(d_[i+1] - d_[i]); + } + } + + return val_n_der; +} + +void Bezier::subdivide(Coord t, Bezier *left, Bezier *right) const +{ + if (left) { + left->c_.resize(size()); + if (right) { + right->c_.resize(size()); + casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0], + &left->c_[0], &right->c_[0], order()); + } else { + casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0], + &left->c_[0], NULL, order()); + } + } else if (right) { + right->c_.resize(size()); + casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0], + NULL, &right->c_[0], order()); + } +} + +std::pair<Bezier, Bezier> Bezier::subdivide(Coord t) const +{ + std::pair<Bezier, Bezier> ret; + subdivide(t, &ret.first, &ret.second); + return ret; +} + +std::vector<Coord> Bezier::roots() const +{ + std::vector<Coord> solutions; + find_bezier_roots(solutions, 0, 1); + std::sort(solutions.begin(), solutions.end()); + return solutions; +} + +std::vector<Coord> Bezier::roots(Interval const &ivl) const +{ + std::vector<Coord> solutions; + find_bernstein_roots(&const_cast<std::valarray<Coord>&>(c_)[0], order(), solutions, 0, ivl.min(), ivl.max()); + std::sort(solutions.begin(), solutions.end()); + return solutions; +} + +Bezier Bezier::forward_difference(unsigned k) const +{ + Bezier fd(Order(order()-k)); + unsigned n = fd.size(); + + for(unsigned i = 0; i < n; i++) { + fd[i] = 0; + for(unsigned j = i; j < n; j++) { + fd[i] += (((j)&1)?-c_[j]:c_[j])*choose<double>(n, j-i); + } + } + return fd; +} + +Bezier Bezier::elevate_degree() const +{ + Bezier ed(Order(order()+1)); + unsigned n = size(); + ed[0] = c_[0]; + ed[n] = c_[n-1]; + for(unsigned i = 1; i < n; i++) { + ed[i] = (i*c_[i-1] + (n - i)*c_[i])/(n); + } + return ed; +} + +Bezier Bezier::reduce_degree() const +{ + if(order() == 0) return *this; + Bezier ed(Order(order()-1)); + unsigned n = size(); + ed[0] = c_[0]; + ed[n-1] = c_[n]; // ensure exact endpoints + unsigned middle = n/2; + for(unsigned i = 1; i < middle; i++) { + ed[i] = (n*c_[i] - i*ed[i-1])/(n-i); + } + for(unsigned i = n-1; i >= middle; i--) { + ed[i] = (n*c_[i] - i*ed[n-i])/(i); + } + return ed; +} + +Bezier Bezier::elevate_to_degree(unsigned newDegree) const +{ + Bezier ed = *this; + for(unsigned i = degree(); i < newDegree; i++) { + ed = ed.elevate_degree(); + } + return ed; +} + +Bezier Bezier::deflate() const +{ + if(order() == 0) return *this; + unsigned n = order(); + Bezier b(Order(n-1)); + for(unsigned i = 0; i < n; i++) { + b[i] = (n*c_[i+1])/(i+1); + } + return b; +} + +SBasis Bezier::toSBasis() const +{ + SBasis sb; + bezier_to_sbasis(sb, (*this)); + return sb; + //return bezier_to_sbasis(&c_[0], order()); +} + +Bezier &Bezier::operator+=(Bezier const &other) +{ + if (c_.size() > other.size()) { + c_ += other.elevate_to_degree(degree()).c_; + } else if (c_.size() < other.size()) { + *this = elevate_to_degree(other.degree()); + c_ += other.c_; + } else { + c_ += other.c_; + } + return *this; +} + +Bezier &Bezier::operator-=(Bezier const &other) +{ + if (c_.size() > other.size()) { + c_ -= other.elevate_to_degree(degree()).c_; + } else if (c_.size() < other.size()) { + *this = elevate_to_degree(other.degree()); + c_ -= other.c_; + } else { + c_ -= other.c_; + } + return *this; +} + + + +Bezier operator*(Bezier const &f, Bezier const &g) +{ + unsigned m = f.order(); + unsigned n = g.order(); + Bezier h(Bezier::Order(m+n)); + // h_k = sum_(i+j=k) (m i)f_i (n j)g_j / (m+n k) + + for(unsigned i = 0; i <= m; i++) { + const double fi = choose<double>(m,i)*f[i]; + for(unsigned j = 0; j <= n; j++) { + h[i+j] += fi * choose<double>(n,j)*g[j]; + } + } + for(unsigned k = 0; k <= m+n; k++) { + h[k] /= choose<double>(m+n, k); + } + return h; +} + +Bezier portion(Bezier const &a, double from, double to) +{ + Bezier ret(a); + + bool reverse_result = false; + if (from > to) { + std::swap(from, to); + reverse_result = true; + } + + do { + if (from == 0) { + if (to == 1) { + break; + } + casteljau_subdivision<double>(to, &ret.c_[0], &ret.c_[0], NULL, ret.order()); + break; + } + casteljau_subdivision<double>(from, &ret.c_[0], NULL, &ret.c_[0], ret.order()); + if (to == 1) break; + casteljau_subdivision<double>((to - from) / (1 - from), &ret.c_[0], &ret.c_[0], NULL, ret.order()); + // to protect against numerical inaccuracy in the above expression, we manually set + // the last coefficient to a value evaluated directly from the original polynomial + ret.c_[ret.order()] = a.valueAt(to); + } while(0); + + if (reverse_result) { + std::reverse(&ret.c_[0], &ret.c_[0] + ret.c_.size()); + } + return ret; +} + +Bezier derivative(Bezier const &a) +{ + //if(a.order() == 1) return Bezier(0.0); + if(a.order() == 1) return Bezier(a.c_[1]-a.c_[0]); + Bezier der(Bezier::Order(a.order()-1)); + + for(unsigned i = 0; i < a.order(); i++) { + der.c_[i] = a.order()*(a.c_[i+1] - a.c_[i]); + } + return der; +} + +Bezier integral(Bezier const &a) +{ + Bezier inte(Bezier::Order(a.order()+1)); + + inte[0] = 0; + for(unsigned i = 0; i < inte.order(); i++) { + inte[i+1] = inte[i] + a[i]/(inte.order()); + } + return inte; +} + +OptInterval bounds_fast(Bezier const &b) +{ + OptInterval ret = Interval::from_array(&const_cast<Bezier&>(b).c_[0], b.size()); + return ret; +} + +OptInterval bounds_exact(Bezier const &b) +{ + OptInterval ret(b.at0(), b.at1()); + std::vector<Coord> r = derivative(b).roots(); + for (unsigned i = 0; i < r.size(); ++i) { + ret->expandTo(b.valueAt(r[i])); + } + return ret; +} + +OptInterval bounds_local(Bezier const &b, OptInterval const &i) +{ + //return bounds_local(b.toSBasis(), i); + if (i) { + return bounds_fast(portion(b, i->min(), i->max())); + } else { + return OptInterval(); + } +} + +} // end namespace Geom + +/* + 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/src/2geom/bezier.h b/src/2geom/bezier.h new file mode 100644 index 0000000..fc2fe5f --- /dev/null +++ b/src/2geom/bezier.h @@ -0,0 +1,364 @@ +/** + * @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 <boost/optional.hpp> +#include <2geom/choose.h> +#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; + } + + 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); +}; + + +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); + +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; +} + +} +#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/src/2geom/cairo-path-sink.cpp b/src/2geom/cairo-path-sink.cpp new file mode 100644 index 0000000..244a08b --- /dev/null +++ b/src/2geom/cairo-path-sink.cpp @@ -0,0 +1,123 @@ +/** + * @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, 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. + * + */ + +#include <cairo.h> +#include <2geom/cairo-path-sink.h> +#include <2geom/elliptical-arc.h> + +namespace Geom { + +CairoPathSink::CairoPathSink(cairo_t *cr) + : _cr(cr) +{} + +void CairoPathSink::moveTo(Point const &p) +{ + cairo_move_to(_cr, p[X], p[Y]); + _current_point = p; +} + +void CairoPathSink::lineTo(Point const &p) +{ + cairo_line_to(_cr, p[X], p[Y]); + _current_point = p; +} + +void CairoPathSink::curveTo(Point const &p1, Point const &p2, Point const &p3) +{ + cairo_curve_to(_cr, p1[X], p1[Y], p2[X], p2[Y], p3[X], p3[Y]); + _current_point = p3; +} + +void CairoPathSink::quadTo(Point const &p1, Point const &p2) +{ + // degree-elevate to cubic Bezier, since Cairo doesn't do quad Beziers + // google "Bezier degree elevation" for more info + Point q1 = (1./3.) * _current_point + (2./3.) * p1; + Point q2 = (2./3.) * p1 + (1./3.) * p2; + // q3 = p2 + cairo_curve_to(_cr, q1[X], q1[Y], q2[X], q2[Y], p2[X], p2[Y]); + _current_point = p2; +} + +void CairoPathSink::arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p) +{ + EllipticalArc arc(_current_point, rx, ry, angle, large_arc, sweep, p); + // Cairo only does circular arcs. + // To do elliptical arcs, we must use a temporary transform. + Affine uct = arc.unitCircleTransform(); + + // TODO move Cairo-2Geom matrix conversion into a common location + cairo_matrix_t cm; + cm.xx = uct[0]; + cm.xy = uct[2]; + cm.x0 = uct[4]; + cm.yx = uct[1]; + cm.yy = uct[3]; + cm.y0 = uct[5]; + + cairo_save(_cr); + cairo_transform(_cr, &cm); + if (sweep) { + cairo_arc(_cr, 0, 0, 1, arc.initialAngle(), arc.finalAngle()); + } else { + cairo_arc_negative(_cr, 0, 0, 1, arc.initialAngle(), arc.finalAngle()); + } + _current_point = p; + cairo_restore(_cr); + + /* Note that an extra linear segment will be inserted before the arc + * if Cairo considers the current point distinct from the initial point + * of the arc; we could partially alleviate this by not emitting + * linear segments that are followed by arc segments, but this would require + * buffering the input curves. */ +} + +void CairoPathSink::closePath() +{ + cairo_close_path(_cr); +} + +void CairoPathSink::flush() {} + +} // namespace Geom + +/* + 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/src/2geom/cairo-path-sink.h b/src/2geom/cairo-path-sink.h new file mode 100644 index 0000000..9fec7e0 --- /dev/null +++ b/src/2geom/cairo-path-sink.h @@ -0,0 +1,87 @@ +/** + * @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 + +#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); + 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(Coord rx, Coord ry, Coord angle, + bool large_arc, bool sweep, Point const &p); + void closePath(); + void flush(); + +private: + cairo_t *_cr; + Point _current_point; +}; + +} + +#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/src/2geom/choose.h b/src/2geom/choose.h new file mode 100644 index 0000000..64ce76f --- /dev/null +++ b/src/2geom/choose.h @@ -0,0 +1,140 @@ +/** + * \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 { + +// XXX: Can we keep only the left terms easily? +// this would more than halve the array +// row index becomes n2 = n/2, row2 = n2*(n2+1)/2, row = row2*2+(n&1)?n2:0 +// we could also leave off the ones + +template <typename T> +T choose(unsigned n, unsigned k) { + static std::vector<T> pascals_triangle; + static unsigned rows_done = 0; + // indexing is (0,0,), (1,0), (1,1), (2, 0)... + // to get (i, j) i*(i+1)/2 + j + if(/*k < 0 ||*/ k > n) return 0; + if(rows_done <= n) {// we haven't got there yet + if(rows_done == 0) { + pascals_triangle.push_back(1); + rows_done = 1; + } + while(rows_done <= n) { + unsigned p = pascals_triangle.size() - rows_done; + pascals_triangle.push_back(1); + for(unsigned i = 0; i < rows_done-1; i++) { + pascals_triangle.push_back(pascals_triangle[p] + + pascals_triangle[p+1]); + p++; + } + pascals_triangle.push_back(1); + rows_done ++; + } + } + unsigned row = (n*(n+1))/2; + return pascals_triangle[row+k]; +} + +// Is it faster to store them or compute them on demand? +/*template <typename T> +T choose(unsigned n, unsigned k) { + T r = 1; + for(unsigned i = 1; i <= k; i++) + r = (r*(n-k+i))/i; + return r; + }*/ + + + +template <typename ValueType> +class BinomialCoefficient +{ + public: + typedef ValueType value_type; + typedef std::vector<value_type> container_type; + + BinomialCoefficient(unsigned int _n) + : n(_n), m(n >> 1) + { + coefficients.reserve(m+1); + coefficients.push_back(1); + int h = m + 1; + value_type bct = 1; + for (int i = 1; i < h; ++i) + { + bct *= (n-i+1); + bct /= i; + coefficients.push_back(bct); + } + } + + unsigned int size() const + { + return degree() +1; + } + + unsigned int degree() const + { + return n; + } + + value_type operator[] (unsigned int k) const + { + if (k > m) k = n - k; + return coefficients[k]; + } + + private: + const int n; + const unsigned int m; + container_type coefficients; +}; + +} // end 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/src/2geom/circle.cpp b/src/2geom/circle.cpp new file mode 100644 index 0000000..934a8d3 --- /dev/null +++ b/src/2geom/circle.cpp @@ -0,0 +1,337 @@ +/** @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. + */ + +#include <2geom/circle.h> +#include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +namespace Geom { + +Rect Circle::boundsFast() const +{ + Point rr(_radius, _radius); + Rect bbox(_center - rr, _center + rr); + return bbox; +} + +void Circle::setCoefficients(Coord A, Coord B, Coord C, Coord D) +{ + if (A == 0) { + THROW_RANGEERROR("square term coefficient == 0"); + } + + //std::cerr << "B = " << B << " C = " << C << " D = " << D << std::endl; + + Coord b = B / A; + Coord c = C / A; + Coord d = D / A; + + _center[X] = -b/2; + _center[Y] = -c/2; + Coord r2 = _center[X] * _center[X] + _center[Y] * _center[Y] - d; + + if (r2 < 0) { + THROW_RANGEERROR("ray^2 < 0"); + } + + _radius = std::sqrt(r2); +} + +void Circle::coefficients(Coord &A, Coord &B, Coord &C, Coord &D) const +{ + A = 1; + B = -2 * _center[X]; + C = -2 * _center[Y]; + D = _center[X] * _center[X] + _center[Y] * _center[Y] - _radius * _radius; +} + +std::vector<Coord> Circle::coefficients() const +{ + std::vector<Coord> c(4); + coefficients(c[0], c[1], c[2], c[3]); + return c; +} + + +Zoom Circle::unitCircleTransform() const +{ + Zoom ret(_radius, _center / _radius); + return ret; +} + +Zoom Circle::inverseUnitCircleTransform() const +{ + if (_radius == 0) { + THROW_RANGEERROR("degenerate circle does not have an inverse unit circle transform"); + } + + Zoom ret(1/_radius, Translate(-_center)); + return ret; +} + +Point Circle::initialPoint() const +{ + Point p(_center); + p[X] += _radius; + return p; +} + +Point Circle::pointAt(Coord t) const { + return _center + Point::polar(t) * _radius; +} + +Coord Circle::valueAt(Coord t, Dim2 d) const { + Coord delta = (d == X ? std::cos(t) : std::sin(t)); + return _center[d] + delta * _radius; +} + +Coord Circle::timeAt(Point const &p) const { + if (_center == p) return 0; + return atan2(p - _center); +} + +Coord Circle::nearestTime(Point const &p) const { + return timeAt(p); +} + +bool Circle::contains(Rect const &r) const +{ + for (unsigned i = 0; i < 4; ++i) { + if (!contains(r.corner(i))) return false; + } + return true; +} + +bool Circle::contains(Circle const &other) const +{ + Coord cdist = distance(_center, other._center); + Coord rdist = fabs(_radius - other._radius); + return cdist <= rdist; +} + +bool Circle::intersects(Line const &l) const +{ + // http://mathworld.wolfram.com/Circle-LineIntersection.html + Coord dr = l.vector().length(); + Coord r = _radius; + Coord D = cross(l.initialPoint(), l.finalPoint()); + Coord delta = r*r * dr*dr - D*D; + if (delta >= 0) return true; + return false; +} + +bool Circle::intersects(Circle const &other) const +{ + Coord cdist = distance(_center, other._center); + Coord rsum = _radius + other._radius; + return cdist <= rsum; +} + + +std::vector<ShapeIntersection> Circle::intersect(Line const &l) const +{ + // http://mathworld.wolfram.com/Circle-LineIntersection.html + Coord dr = l.vector().length(); + Coord dx = l.vector().x(); + Coord dy = l.vector().y(); + Coord D = cross(l.initialPoint() - _center, l.finalPoint() - _center); + Coord delta = _radius*_radius * dr*dr - D*D; + + std::vector<ShapeIntersection> result; + if (delta < 0) return result; + if (delta == 0) { + Coord ix = (D*dy) / (dr*dr); + Coord iy = (-D*dx) / (dr*dr); + Point ip(ix, iy); ip += _center; + result.push_back(ShapeIntersection(timeAt(ip), l.timeAt(ip), ip)); + return result; + } + + Coord sqrt_delta = std::sqrt(delta); + Coord signmod = dy < 0 ? -1 : 1; + + Coord i1x = (D*dy + signmod * dx * sqrt_delta) / (dr*dr); + Coord i1y = (-D*dx + fabs(dy) * sqrt_delta) / (dr*dr); + Point i1p(i1x, i1y); i1p += _center; + + Coord i2x = (D*dy - signmod * dx * sqrt_delta) / (dr*dr); + Coord i2y = (-D*dx - fabs(dy) * sqrt_delta) / (dr*dr); + Point i2p(i2x, i2y); i2p += _center; + + result.push_back(ShapeIntersection(timeAt(i1p), l.timeAt(i1p), i1p)); + result.push_back(ShapeIntersection(timeAt(i2p), l.timeAt(i2p), i2p)); + return result; +} + +std::vector<ShapeIntersection> Circle::intersect(LineSegment const &l) const +{ + std::vector<ShapeIntersection> result = intersect(Line(l)); + filter_line_segment_intersections(result); + return result; +} + +std::vector<ShapeIntersection> Circle::intersect(Circle const &other) const +{ + std::vector<ShapeIntersection> result; + + if (*this == other) { + THROW_INFINITESOLUTIONS(); + } + if (contains(other)) return result; + if (!intersects(other)) return result; + + // See e.g. http://mathworld.wolfram.com/Circle-CircleIntersection.html + // Basically, we figure out where is the third point of a triangle + // with two points in the centers and with edge lengths equal to radii + Point cv = other._center - _center; + Coord d = cv.length(); + Coord R = radius(), r = other.radius(); + + if (d == R + r) { + Point px = lerp(R / d, _center, other._center); + Coord T = timeAt(px), t = other.timeAt(px); + result.push_back(ShapeIntersection(T, t, px)); + return result; + } + + // q is the distance along the line between centers to the perpendicular line + // that goes through both intersections. + Coord q = (d*d - r*r + R*R) / (2*d); + Point qp = lerp(q/d, _center, other._center); + + // The triangle given by the points: + // _center, qp, intersection + // is a right triangle. Determine the distance between qp and intersection + // using the Pythagorean theorem. + Coord h = std::sqrt(R*R - q*q); + Point qd = (h/d) * cv.cw(); + + // now compute the intersection points + Point x1 = qp + qd; + Point x2 = qp - qd; + + result.push_back(ShapeIntersection(timeAt(x1), other.timeAt(x1), x1)); + result.push_back(ShapeIntersection(timeAt(x2), other.timeAt(x2), x2)); + return result; +} + +/** + @param inner a point whose angle with the circle center is inside the angle that the arc spans + */ +EllipticalArc * +Circle::arc(Point const& initial, Point const& inner, Point const& final) const +{ + // TODO native implementation! + Ellipse e(_center[X], _center[Y], _radius, _radius, 0); + return e.arc(initial, inner, final); +} + +bool Circle::operator==(Circle const &other) const +{ + if (_center != other._center) return false; + if (_radius != other._radius) return false; + return true; +} + +D2<SBasis> Circle::toSBasis() const +{ + D2<SBasis> B; + Linear bo = Linear(0, 2 * M_PI); + + B[0] = cos(bo,4); + B[1] = sin(bo,4); + + B = B * _radius + _center; + + return B; +} + + +void Circle::fit(std::vector<Point> const& points) +{ + size_t sz = points.size(); + if (sz < 2) { + THROW_RANGEERROR("fitting error: too few points passed"); + } + if (sz == 2) { + _center = points[0] * 0.5 + points[1] * 0.5; + _radius = distance(points[0], points[1]) / 2; + return; + } + + NL::LFMCircle model; + NL::least_squeares_fitter<NL::LFMCircle> fitter(model, sz); + + for (size_t i = 0; i < sz; ++i) { + fitter.append(points[i]); + } + fitter.update(); + + NL::Vector z(sz, 0.0); + model.instance(*this, fitter.result(z)); +} + + +bool are_near(Circle const &a, Circle const &b, Coord eps) +{ + // to check whether no point on a is further than eps from b, + // we check two things: + // 1. if radii differ by more than eps, there is definitely a point that fails + // 2. if they differ by less, we check the centers. They have to be closer + // together if the radius differs, since the maximum distance will be + // equal to sum of radius difference and distance between centers. + if (!are_near(a.radius(), b.radius(), eps)) return false; + Coord adjusted_eps = eps - fabs(a.radius() - b.radius()); + return are_near(a.center(), b.center(), adjusted_eps); +} + +std::ostream &operator<<(std::ostream &out, Circle const &c) +{ + out << "Circle(" << c.center() << ", " << format_coord_nice(c.radius()) << ")"; + return out; +} + +} // end namespace Geom + +/* + 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/src/2geom/circle.h b/src/2geom/circle.h new file mode 100644 index 0000000..a4d5f20 --- /dev/null +++ b/src/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/src/2geom/concepts.h b/src/2geom/concepts.h new file mode 100644 index 0000000..de76d0f --- /dev/null +++ b/src/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/src/2geom/conic_section_clipper.h b/src/2geom/conic_section_clipper.h new file mode 100644 index 0000000..38bba33 --- /dev/null +++ b/src/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/src/2geom/conic_section_clipper_cr.h b/src/2geom/conic_section_clipper_cr.h new file mode 100644 index 0000000..6c62494 --- /dev/null +++ b/src/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/src/2geom/conic_section_clipper_impl.cpp b/src/2geom/conic_section_clipper_impl.cpp new file mode 100644 index 0000000..23731af --- /dev/null +++ b/src/2geom/conic_section_clipper_impl.cpp @@ -0,0 +1,574 @@ +/* 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 CLIP_WITH_CAIRO_SUPPORT + #include <2geom/conic_section_clipper.h> +#endif + +namespace Geom +{ + +/* + * Find rectangle-conic crossing points. They are returned in the + * "crossing_points" parameter. + * The method returns true if the conic section intersects at least one + * of the four lines passing through rectangle edges, else it returns false. + */ +bool CLIPPER_CLASS::intersect (std::vector<Point> & crossing_points) const +{ + crossing_points.clear(); + + std::vector<double> rts; + std::vector<Point> cpts; + // rectangle corners + enum {TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT}; + + bool no_crossing = true; + + // right edge + cs.roots (rts, R.right(), X); + if (!rts.empty()) + { + no_crossing = false; + DBGPRINT ("CLIP: right: rts[0] = ", rts[0]) + DBGPRINTIF ((rts.size() == 2), "CLIP: right: rts[1] = ", rts[1]) + + Point corner1 = R.corner(TOP_RIGHT); + Point corner2 = R.corner(BOTTOM_RIGHT); + + for (size_t i = 0; i < rts.size(); ++i) + { + if (rts[i] < R.top() || rts[i] > R.bottom()) continue; + Point P (R.right(), rts[i]); + if (are_near (P, corner1)) + P = corner1; + else if (are_near (P, corner2)) + P = corner2; + + cpts.push_back (P); + } + if (cpts.size() == 2 && are_near (cpts[0], cpts[1])) + { + cpts[0] = middle_point (cpts[0], cpts[1]); + cpts.pop_back(); + } + } + + // top edge + cs.roots (rts, R.top(), Y); + if (!rts.empty()) + { + no_crossing = false; + DBGPRINT ("CLIP: top: rts[0] = ", rts[0]) + DBGPRINTIF ((rts.size() == 2), "CLIP: top: rts[1] = ", rts[1]) + + Point corner1 = R.corner(TOP_RIGHT); + Point corner2 = R.corner(TOP_LEFT); + + for (size_t i = 0; i < rts.size(); ++i) + { + if (rts[i] < R.left() || rts[i] > R.right()) continue; + Point P (rts[i], R.top()); + if (are_near (P, corner1)) + P = corner1; + else if (are_near (P, corner2)) + P = corner2; + + cpts.push_back (P); + } + if (cpts.size() == 2 && are_near (cpts[0], cpts[1])) + { + cpts[0] = middle_point (cpts[0], cpts[1]); + cpts.pop_back(); + } + } + + // left edge + cs.roots (rts, R.left(), X); + if (!rts.empty()) + { + no_crossing = false; + DBGPRINT ("CLIP: left: rts[0] = ", rts[0]) + DBGPRINTIF ((rts.size() == 2), "CLIP: left: rts[1] = ", rts[1]) + + Point corner1 = R.corner(TOP_LEFT); + Point corner2 = R.corner(BOTTOM_LEFT); + + for (size_t i = 0; i < rts.size(); ++i) + { + if (rts[i] < R.top() || rts[i] > R.bottom()) continue; + Point P (R.left(), rts[i]); + if (are_near (P, corner1)) + P = corner1; + else if (are_near (P, corner2)) + P = corner2; + + cpts.push_back (P); + } + if (cpts.size() == 2 && are_near (cpts[0], cpts[1])) + { + cpts[0] = middle_point (cpts[0], cpts[1]); + cpts.pop_back(); + } + } + + // bottom edge + cs.roots (rts, R.bottom(), Y); + if (!rts.empty()) + { + no_crossing = false; + DBGPRINT ("CLIP: bottom: rts[0] = ", rts[0]) + DBGPRINTIF ((rts.size() == 2), "CLIP: bottom: rts[1] = ", rts[1]) + + Point corner1 = R.corner(BOTTOM_RIGHT); + Point corner2 = R.corner(BOTTOM_LEFT); + + for (size_t i = 0; i < rts.size(); ++i) + { + if (rts[i] < R.left() || rts[i] > R.right()) continue; + Point P (rts[i], R.bottom()); + if (are_near (P, corner1)) + P = corner1; + else if (are_near (P, corner2)) + P = corner2; + + cpts.push_back (P); + } + if (cpts.size() == 2 && are_near (cpts[0], cpts[1])) + { + cpts[0] = middle_point (cpts[0], cpts[1]); + cpts.pop_back(); + } + } + + DBGPRINT ("CLIP: intersect: crossing_points.size (with duplicates) = ", + cpts.size()) + + // remove duplicates + std::sort (cpts.begin(), cpts.end(), Point::LexLess<X>()); + cpts.erase (std::unique (cpts.begin(), cpts.end()), cpts.end()); + + + // Order crossing points on the rectangle edge clockwise, so two consecutive + // crossing points would be the end points of a conic arc all inside or all + // outside the rectangle. + std::map<double, size_t> cp_angles; + for (size_t i = 0; i < cpts.size(); ++i) + { + cp_angles.insert (std::make_pair (cs.angle_at (cpts[i]), i)); + } + + std::map<double, size_t>::const_iterator pos; + for (pos = cp_angles.begin(); pos != cp_angles.end(); ++pos) + { + crossing_points.push_back (cpts[pos->second]); + } + + DBGPRINT ("CLIP: intersect: crossing_points.size = ", crossing_points.size()) + DBGPRINTCOLL ("CLIP: intersect: crossing_points:", crossing_points) + + return no_crossing; +} // end function intersect + + + +inline +double signed_triangle_area (Point const& p1, Point const& p2, Point const& p3) +{ + return (cross(p2, p3) - cross(p1, p3) + cross(p1, p2)); +} + + +/* + * Test if two crossing points are the end points of a conic arc inner to the + * rectangle. In such a case the method returns true, else it returns false. + * Moreover by the parameter "M" it returns a point inner to the conic arc + * with the given end-points. + * + */ +bool CLIPPER_CLASS::are_paired (Point& M, const Point & P1, const Point & P2) const +{ + using std::swap; + + /* + * we looks for the points on the conic whose tangent is parallel to the + * arc chord P1P2, they will be extrema of the conic arc P1P2 wrt the + * direction orthogonal to the chord + */ + Point dir = P2 - P1; + DBGPRINT ("CLIP: are_paired: first point: ", P1) + DBGPRINT ("CLIP: are_paired: second point: ", P2) + + double grad0 = 2 * cs.coeff(0) * dir[0] + cs.coeff(1) * dir[1]; + double grad1 = cs.coeff(1) * dir[0] + 2 * cs.coeff(2) * dir[1]; + double grad2 = cs.coeff(3) * dir[0] + cs.coeff(4) * dir[1]; + + + /* + * such points are found intersecating the conic section with the line + * orthogonal to "grad": the derivative wrt the "dir" direction + */ + Line gl (grad0, grad1, grad2); + std::vector<double> rts; + rts = cs.roots (gl); + DBGPRINT ("CLIP: are_paired: extrema: rts.size() = ", rts.size()) + + + + std::vector<Point> extrema; + for (size_t i = 0; i < rts.size(); ++i) + { + extrema.push_back (gl.pointAt (rts[i])); + } + + if (extrema.size() == 2) + { + // in case we are dealing with an hyperbola we could have two extrema + // on the same side wrt the line passing through P1 and P2, but + // only the nearer extremum is on the arc P1P2 + double side0 = signed_triangle_area (P1, extrema[0], P2); + double side1 = signed_triangle_area (P1, extrema[1], P2); + + if (sgn(side0) == sgn(side1)) + { + if (std::fabs(side0) > std::fabs(side1)) { + swap(extrema[0], extrema[1]); + } + extrema.pop_back(); + } + } + + std::vector<Point> inner_points; + for (size_t i = 0; i < extrema.size(); ++i) + { + if (!R.contains (extrema[i])) continue; + // in case we are dealing with an ellipse tangent to two orthogonal + // rectangle edges we could have two extrema on opposite sides wrt the + // line passing through P1P2 and both inner the rectangle; anyway, since + // we order the crossing points clockwise we have only one extremum + // that follows such an ordering wrt P1 and P2; + // remark: the other arc will be selected when we test for the arc P2P1. + double P1angle = cs.angle_at (P1); + double P2angle = cs.angle_at (P2); + double Qangle = cs.angle_at (extrema[i]); + if (P1angle < P2angle && !(P1angle <= Qangle && Qangle <= P2angle)) + continue; + if (P1angle > P2angle && !(P1angle <= Qangle || Qangle <= P2angle)) + continue; + + inner_points.push_back (extrema[i]); + } + + if (inner_points.size() > 1) + { + THROW_LOGICALERROR ("conic section clipper: " + "more than one extremum found"); + } + else if (inner_points.size() == 1) + { + M = inner_points.front(); + return true; + } + + return false; +} + + +/* + * Pair the points contained in the "crossing_points" vector; the paired points + * are put in the paired_points vector so that given a point with an even index + * and the next one they are the end points of a conic arc that is inner to the + * rectangle. In the "inner_points" are returned points that are inner to the + * arc, where the inner point with index k is related to the arc with end + * points with indexes 2k, 2k+1. In case there are unpaired points the are put + * in to the "single_points" vector. + */ +void CLIPPER_CLASS::pairing (std::vector<Point> & paired_points, + std::vector<Point> & inner_points, + const std::vector<Point> & crossing_points) +{ + paired_points.clear(); + paired_points.reserve (crossing_points.size()); + + inner_points.clear(); + inner_points.reserve (crossing_points.size() / 2); + + single_points.clear(); + + // to keep trace of which crossing points have been paired + std::vector<bool> paired (crossing_points.size(), false); + + Point M; + + // by the way we have ordered crossing points we need to test one point wrt + // the next point only, for pairing; moreover the last point need to be + // tested wrt the first point; pay attention: one point can be paired both + // with the previous and the next one: this is not an error, think of + // crossing points that are tangent to the rectangle edge (and inner); + for (size_t i = 0; i < crossing_points.size(); ++i) + { + // we need to test the last point wrt the first one + size_t j = (i == 0) ? (crossing_points.size() - 1) : (i-1); + if (are_paired (M, crossing_points[j], crossing_points[i])) + { +#ifdef CLIP_WITH_CAIRO_SUPPORT + cairo_set_source_rgba(cr, 0.1, 0.1, 0.8, 1.0); + draw_line_seg (cr, crossing_points[j], crossing_points[i]); + draw_handle (cr, crossing_points[j]); + draw_handle (cr, crossing_points[i]); + draw_handle (cr, M); + cairo_stroke (cr); +#endif + paired[j] = paired[i] = true; + paired_points.push_back (crossing_points[j]); + paired_points.push_back (crossing_points[i]); + inner_points.push_back (M); + } + } + + // some point are not paired with any point, e.g. a crossing point tangent + // to a rectangle edge but with the conic arc outside the rectangle + for (size_t i = 0; i < paired.size(); ++i) + { + if (!paired[i]) + single_points.push_back (crossing_points[i]); + } + DBGPRINTCOLL ("single_points", single_points) + +} + + +/* + * This method clip the section conic wrt the rectangle and returns the inner + * conic arcs as a vector of RatQuad objects by the "arcs" parameter. + */ +bool CLIPPER_CLASS::clip (std::vector<RatQuad> & arcs) +{ + using std::swap; + + arcs.clear(); + std::vector<Point> crossing_points; + std::vector<Point> paired_points; + std::vector<Point> inner_points; + + Line l1, l2; + if (cs.decompose (l1, l2)) + { + bool inner_empty = true; + + DBGINFO ("CLIP: degenerate section conic") + + boost::optional<LineSegment> ls1 = Geom::clip (l1, R); + if (ls1) + { + if (ls1->isDegenerate()) + { + single_points.push_back (ls1->initialPoint()); + } + else + { + Point M = middle_point (*ls1); + arcs.push_back + (RatQuad (ls1->initialPoint(), M, ls1->finalPoint(), 1)); + inner_empty = false; + } + } + + boost::optional<LineSegment> ls2 = Geom::clip (l2, R); + if (ls2) + { + if (ls2->isDegenerate()) + { + single_points.push_back (ls2->initialPoint()); + } + else + { + Point M = middle_point (*ls2); + arcs.push_back + (RatQuad (ls2->initialPoint(), M, ls2->finalPoint(), 1)); + inner_empty = false; + } + } + + return !inner_empty; + } + + + bool no_crossing = intersect (crossing_points); + + // if the only crossing point is a rectangle corner than the section conic + // is all outside the rectangle + if (crossing_points.size() == 1) + { + for (size_t i = 0; i < 4; ++i) + { + if (crossing_points[0] == R.corner(i)) + { + single_points.push_back (R.corner(i)); + return false; + } + } + } + + // if the conic does not cross any line passing through a rectangle edge or + // it is tangent to only one edge then it is an ellipse + if (no_crossing + || (crossing_points.size() == 1 && single_points.empty())) + { + // if the ellipse centre is inside the rectangle + // then so it is the ellipse + boost::optional<Point> c = cs.centre(); + if (c && R.contains (*c)) + { + DBGPRINT ("CLIP: ellipse with centre", *c) + // we set paired and inner points by finding the ellipse + // intersection with its axes; this choice let us having a more + // accurate RatQuad parametric arc + paired_points.resize(4); + std::vector<double> rts; + double angle = cs.axis_angle(); + Line axis1 (*c, angle); + rts = cs.roots (axis1); + if (rts[0] > rts[1]) swap (rts[0], rts[1]); + paired_points[0] = axis1.pointAt (rts[0]); + paired_points[1] = axis1.pointAt (rts[1]); + paired_points[2] = paired_points[1]; + paired_points[3] = paired_points[0]; + Line axis2 (*c, angle + M_PI/2); + rts = cs.roots (axis2); + if (rts[0] > rts[1]) swap (rts[0], rts[1]); + inner_points.push_back (axis2.pointAt (rts[0])); + inner_points.push_back (axis2.pointAt (rts[1])); + } + else if (crossing_points.size() == 1) + { + // so we have a tangent crossing point but the ellipse is outside + // the rectangle + single_points.push_back (crossing_points[0]); + } + } + else + { + // in case the conic section intersects any of the four lines passing + // through the rectangle edges but it does not cross any rectangle edge + // then the conic is all outer of the rectangle + if (crossing_points.empty()) return false; + // else we need to pair crossing points, and to find an arc inner point + // in order to generate a RatQuad object + pairing (paired_points, inner_points, crossing_points); + } + + + // we split arcs until the end-point distance is less than a given value, + // in this way the RatQuad parametrization is enough accurate + std::list<Point> points; + std::list<Point>::iterator sp, ip, fp; + for (size_t i = 0, j = 0; i < paired_points.size(); i += 2, ++j) + { + //DBGPRINT ("CLIP: clip: P = ", paired_points[i]) + //DBGPRINT ("CLIP: clip: M = ", inner_points[j]) + //DBGPRINT ("CLIP: clip: Q = ", paired_points[i+1]) + + // in case inner point and end points are near is better not split + // the conic arc further or we could get a degenerate RatQuad object + if (are_near (paired_points[i], inner_points[j], 1e-4) + && are_near (paired_points[i+1], inner_points[j], 1e-4)) + { + arcs.push_back (cs.toRatQuad (paired_points[i], + inner_points[j], + paired_points[i+1])); + continue; + } + + // populate the list + points.push_back(paired_points[i]); + points.push_back(inner_points[j]); + points.push_back(paired_points[i+1]); + + // an initial unconditioned splitting + sp = points.begin(); + ip = sp; ++ip; + fp = ip; ++fp; + rsplit (points, sp, ip, size_t(1u)); + rsplit (points, ip, fp, size_t(1u)); + + // length conditioned split + sp = points.begin(); + fp = sp; ++fp; + while (fp != points.end()) + { + rsplit (points, sp, fp, 100.0); + sp = fp; + ++fp; + } + + sp = points.begin(); + ip = sp; ++ip; + fp = ip; ++fp; + //DBGPRINT ("CLIP: points ", j) + //DBGPRINT ("CLIP: points.size = ", points.size()) + while (ip != points.end()) + { +#ifdef CLIP_WITH_CAIRO_SUPPORT + cairo_set_source_rgba(cr, 0.1, 0.1, 0.8, 1.0); + draw_handle (cr, *sp); + draw_handle (cr, *ip); + cairo_stroke (cr); +#endif + //std::cerr << "CLIP: arc: [" << *sp << ", " << *ip << ", " + // << *fp << "]" << std::endl; + arcs.push_back (cs.toRatQuad (*sp, *ip, *fp)); + sp = fp; + ip = sp; ++ip; + fp = ip; ++fp; + } + points.clear(); + } + DBGPRINT ("CLIP: arcs.size() = ", arcs.size()) + return (arcs.size() != 0); +} // end method clip + + +} // end namespace geom + + + + +/* + 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/src/2geom/conic_section_clipper_impl.h b/src/2geom/conic_section_clipper_impl.h new file mode 100644 index 0000000..2e8fd24 --- /dev/null +++ b/src/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 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/src/2geom/conicsec.cpp b/src/2geom/conicsec.cpp new file mode 100644 index 0000000..7172374 --- /dev/null +++ b/src/2geom/conicsec.cpp @@ -0,0 +1,1496 @@ +/* + * 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. + */ + + +#include <2geom/conicsec.h> +#include <2geom/conic_section_clipper.h> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + + +// File: convert.h +#include <utility> +#include <sstream> +#include <stdexcept> + +namespace Geom +{ + +LineSegment intersection(Line l, Rect r) { + boost::optional<LineSegment> seg = l.clip(r); + if (seg) { + return *seg; + } else { + return LineSegment(Point(0,0), Point(0,0)); + } +} + +static double det(Point a, Point b) { + return a[0]*b[1] - a[1]*b[0]; +} + +template <typename T> +static T det(T a, T b, T c, T d) { + return a*d - b*c; +} + +template <typename T> +static T det(T M[2][2]) { + return M[0][0]*M[1][1] - M[1][0]*M[0][1]; +} + +template <typename T> +static T det3(T M[3][3]) { + return ( M[0][0] * det(M[1][1], M[1][2], + M[2][1], M[2][2]) + -M[1][0] * det(M[0][1], M[0][2], + M[2][1], M[2][2]) + +M[2][0] * det(M[0][1], M[0][2], + M[1][1], M[1][2])); +} + +static double boxprod(Point a, Point b, Point c) { + return det(a,b) - det(a,c) + det(b,c); +} + +class BadConversion : public std::runtime_error { +public: + BadConversion(const std::string& s) + : std::runtime_error(s) + { } +}; + +template <typename T> +inline std::string stringify(T x) +{ + std::ostringstream o; + if (!(o << x)) + throw BadConversion("stringify(T)"); + return o.str(); +} + + /* A G4 continuous cubic parametric approximation for rational quadratics. + See + An analysis of cubic approximation schemes for conic sections + Michael Floater + SINTEF + + This is less accurate overall than some of his other schemes, but + produces very smooth joins and is still optimally h^-6 + convergent. + */ + +double RatQuad::lambda() const { + return 2*(6*w*w +1 -std::sqrt(3*w*w+1))/(12*w*w+3); +} + +RatQuad RatQuad::fromPointsTangents(Point P0, Point dP0, + Point P, + Point P2, Point dP2) { + Line Line0 = Line::from_origin_and_vector(P0, dP0); + Line Line2 = Line::from_origin_and_vector(P2, dP2); + try { + OptCrossing oc = intersection(Line0, Line2); + if(!oc) // what to do? + return RatQuad(Point(), Point(), Point(), 0); // need opt really + //assert(0); + Point P1 = Line0.pointAt((*oc).ta); + double triarea = boxprod(P0, P1, P2); +// std::cout << "RatQuad::fromPointsTangents: triarea = " << triarea << std::endl; + if (triarea == 0) + { + return RatQuad(P0, 0.5*(P0+P2), P2, 1); + } + double tau0 = boxprod(P, P1, P2)/triarea; + double tau1 = boxprod(P0, P, P2)/triarea; + double tau2 = boxprod(P0, P1, P)/triarea; + if (tau0 == 0 || tau1 == 0 || tau2 == 0) + { + return RatQuad(P0, 0.5*(P0+P2), P2, 1); + } + double w = tau1/(2*std::sqrt(tau0*tau2)); +// std::cout << "RatQuad::fromPointsTangents: tau0 = " << tau0 << std::endl; +// std::cout << "RatQuad::fromPointsTangents: tau1 = " << tau1 << std::endl; +// std::cout << "RatQuad::fromPointsTangents: tau2 = " << tau2 << std::endl; +// std::cout << "RatQuad::fromPointsTangents: w = " << w << std::endl; + return RatQuad(P0, P1, P2, w); + } catch(Geom::InfiniteSolutions) { + return RatQuad(P0, 0.5*(P0+P2), P2, 1); + } + return RatQuad(Point(), Point(), Point(), 0); // need opt really +} + +RatQuad RatQuad::circularArc(Point P0, Point P1, Point P2) { + return RatQuad(P0, P1, P2, dot(unit_vector(P0 - P1), unit_vector(P0 - P2))); +} + + +CubicBezier RatQuad::toCubic() const { + return toCubic(lambda()); +} + +CubicBezier RatQuad::toCubic(double lamb) const { + return CubicBezier(P[0], + (1-lamb)*P[0] + lamb*P[1], + (1-lamb)*P[2] + lamb*P[1], + P[2]); +} + +Point RatQuad::pointAt(double t) const { + Bezier xt(P[0][0], P[1][0]*w, P[2][0]); + Bezier yt(P[0][1], P[1][1]*w, P[2][1]); + double wt = Bezier(1, w, 1).valueAt(t); + return Point(xt.valueAt(t)/wt, + yt.valueAt(t)/wt); +} + +void RatQuad::split(RatQuad &a, RatQuad &b) const { + a.P[0] = P[0]; + b.P[2] = P[2]; + a.P[1] = (P[0]+w*P[1])/(1+w); + b.P[1] = (w*P[1]+P[2])/(1+w); + a.w = b.w = std::sqrt((1+w)/2); + a.P[2] = b.P[0] = (0.5*a.P[1]+0.5*b.P[1]); +} + + +D2<SBasis> RatQuad::hermite() const { + SBasis t = Linear(0, 1); + SBasis omt = Linear(1, 0); + + D2<SBasis> out(omt*omt*P[0][0]+2*omt*t*P[1][0]*w+t*t*P[2][0], + omt*omt*P[0][1]+2*omt*t*P[1][1]*w+t*t*P[2][1]); + for(int dim = 0; dim < 2; dim++) { + out[dim] = divide(out[dim], (omt*omt+2*omt*t*w+t*t), 2); + } + return out; +} + + std::vector<SBasis> RatQuad::homogeneous() const { + std::vector<SBasis> res(3, SBasis()); + Bezier xt(P[0][0], P[1][0]*w, P[2][0]); + bezier_to_sbasis(res[0],xt); + Bezier yt(P[0][1], P[1][1]*w, P[2][1]); + bezier_to_sbasis(res[1],yt); + Bezier wt(1, w, 1); + bezier_to_sbasis(res[2],wt); + return res; +} + +#if 0 + std::string xAx::categorise() const { + double M[3][3] = {{c[0], c[1], c[3]}, + {c[1], c[2], c[4]}, + {c[3], c[4], c[5]}}; + double D = det3(M); + if (c[0] == 0 && c[1] == 0 && c[2] == 0) + return "line"; + std::string res = stringify(D); + double descr = c[1]*c[1] - c[0]*c[2]; + if (descr < 0) { + if (c[0] == c[2] && c[1] == 0) + return res + "circle"; + return res + "ellipse"; + } else if (descr == 0) { + return res + "parabola"; + } else if (descr > 0) { + if (c[0] + c[2] == 0) { + if (D == 0) + return res + "two lines"; + return res + "rectangular hyperbola"; + } + return res + "hyperbola"; + + } + return "no idea!"; +} +#endif + + +std::vector<Point> decompose_degenerate(xAx const & C1, xAx const & C2, xAx const & xC0) { + std::vector<Point> res; + double A[2][2] = {{2*xC0.c[0], xC0.c[1]}, + {xC0.c[1], 2*xC0.c[2]}}; +//Point B0 = xC0.bottom(); + double const determ = det(A); + //std::cout << determ << "\n"; + if (fabs(determ) >= 1e-20) { // hopeful, I know + Geom::Coord const ideterm = 1.0 / determ; + + double b[2] = {-xC0.c[3], -xC0.c[4]}; + Point B0((A[1][1]*b[0] -A[0][1]*b[1]), + (-A[1][0]*b[0] + A[0][0]*b[1])); + B0 *= ideterm; + Point n0, n1; + // Are these just the eigenvectors of A11? + if(xC0.c[0] == xC0.c[2]) { + double b = 0.5*xC0.c[1]/xC0.c[0]; + double c = xC0.c[2]/xC0.c[0]; + //assert(fabs(b*b-c) > 1e-10); + double d = std::sqrt(b*b-c); + //assert(fabs(b-d) > 1e-10); + n0 = Point(1, b+d); + n1 = Point(1, b-d); + } else if(fabs(xC0.c[0]) > fabs(xC0.c[2])) { + double b = 0.5*xC0.c[1]/xC0.c[0]; + double c = xC0.c[2]/xC0.c[0]; + //assert(fabs(b*b-c) > 1e-10); + double d = std::sqrt(b*b-c); + //assert(fabs(b-d) > 1e-10); + n0 = Point(1, b+d); + n1 = Point(1, b-d); + } else { + double b = 0.5*xC0.c[1]/xC0.c[2]; + double c = xC0.c[0]/xC0.c[2]; + //assert(fabs(b*b-c) > 1e-10); + double d = std::sqrt(b*b-c); + //assert(fabs(b-d) > 1e-10); + n0 = Point(b+d, 1); + n1 = Point(b-d, 1); + } + + Line L0 = Line::from_origin_and_vector(B0, rot90(n0)); + Line L1 = Line::from_origin_and_vector(B0, rot90(n1)); + + std::vector<double> rts = C1.roots(L0); + for(unsigned i = 0; i < rts.size(); i++) { + Point P = L0.pointAt(rts[i]); + res.push_back(P); + } + rts = C1.roots(L1); + for(unsigned i = 0; i < rts.size(); i++) { + Point P = L1.pointAt(rts[i]); + res.push_back(P); + } + } else { + // single or double line + // check for completely zero case (what to do?) + assert(xC0.c[0] || xC0.c[1] || + xC0.c[2] || xC0.c[3] || + xC0.c[4] || xC0.c[5]); + Point trial_pt(0,0); + Point g = xC0.gradient(trial_pt); + if(L2sq(g) == 0) { + trial_pt[0] += 1; + g = xC0.gradient(trial_pt); + if(L2sq(g) == 0) { + trial_pt[1] += 1; + g = xC0.gradient(trial_pt); + if(L2sq(g) == 0) { + trial_pt[0] += 1; + g = xC0.gradient(trial_pt); + if(L2sq(g) == 0) { + trial_pt = Point(1.5,0.5); + g = xC0.gradient(trial_pt); + } + } + } + } + //std::cout << trial_pt << ", " << g << "\n"; + /** + * At this point we have tried up to 4 points: 0,0, 1,0, 1,1, 2,1, 1.5,1.5 + * + * No degenerate conic can pass through these points, so we can assume + * that we've found a perpendicular to the double line. + * Proof: + * any degenerate must consist of at most 2 lines. 1.5,0.5 is not on any pair of lines + * passing through the previous 4 trials. + * + * alternatively, there may be a way to determine this directly from xC0 + */ + assert(L2sq(g) != 0); + + Line Lx = Line::from_origin_and_vector(trial_pt, g); // a line along the gradient + std::vector<double> rts = xC0.roots(Lx); + for(unsigned i = 0; i < rts.size(); i++) { + Point P0 = Lx.pointAt(rts[i]); + //std::cout << P0 << "\n"; + Line L = Line::from_origin_and_vector(P0, rot90(g)); + std::vector<double> cnrts; + // It's very likely that at least one of the conics is degenerate, this will hopefully pick the more generate of the two. + if(fabs(C1.hessian().det()) > fabs(C2.hessian().det())) + cnrts = C1.roots(L); + else + cnrts = C2.roots(L); + for(unsigned j = 0; j < cnrts.size(); j++) { + Point P = L.pointAt(cnrts[j]); + res.push_back(P); + } + } + } + return res; +} + +double xAx_descr(xAx const & C) { + double mC[3][3] = {{C.c[0], (C.c[1])/2, (C.c[3])/2}, + {(C.c[1])/2, C.c[2], (C.c[4])/2}, + {(C.c[3])/2, (C.c[4])/2, C.c[5]}}; + + return det3(mC); +} + + +std::vector<Point> intersect(xAx const & C1, xAx const & C2) { + // You know, if either of the inputs are degenerate we should use them first! + if(xAx_descr(C1) == 0) { + return decompose_degenerate(C1, C2, C1); + } + if(xAx_descr(C2) == 0) { + return decompose_degenerate(C1, C2, C2); + } + std::vector<Point> res; + SBasis T(Linear(-1,1)); + SBasis S(Linear(1,1)); + SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2}, + {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2}, + {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}}; + + SBasis D = det3(C); + std::vector<double> rts = Geom::roots(D); + if(rts.empty()) { + T = Linear(1,1); + S = Linear(-1,1); + SBasis C[3][3] = {{T*C1.c[0]+S*C2.c[0], (T*C1.c[1]+S*C2.c[1])/2, (T*C1.c[3]+S*C2.c[3])/2}, + {(T*C1.c[1]+S*C2.c[1])/2, T*C1.c[2]+S*C2.c[2], (T*C1.c[4]+S*C2.c[4])/2}, + {(T*C1.c[3]+S*C2.c[3])/2, (T*C1.c[4]+S*C2.c[4])/2, T*C1.c[5]+S*C2.c[5]}}; + + D = det3(C); + rts = Geom::roots(D); + } + // at this point we have a T and S and perhaps some roots that represent our degenerate conic + // Let's just pick one randomly (can we do better?) + //for(unsigned i = 0; i < rts.size(); i++) { + if(!rts.empty()) { + unsigned i = 0; + double t = T.valueAt(rts[i]); + double s = S.valueAt(rts[i]); + xAx xC0 = C1*t + C2*s; + //::draw(cr, xC0, screen_rect); // degen + + return decompose_degenerate(C1, C2, xC0); + + + } else { + std::cout << "What?" << std::endl; + ;//std::cout << D << "\n"; + } + return res; +} + + +xAx xAx::fromPoint(Point p) { + return xAx(1., 0, 1., -2*p[0], -2*p[1], dot(p,p)); +} + +xAx xAx::fromDistPoint(Point /*p*/, double /*d*/) { + return xAx();//1., 0, 1., -2*(1+d)*p[0], -2*(1+d)*p[1], dot(p,p)+d*d); +} + +xAx xAx::fromLine(Point n, double d) { + return xAx(n[0]*n[0], 2*n[0]*n[1], n[1]*n[1], 2*d*n[0], 2*d*n[1], d*d); +} + +xAx xAx::fromLine(Line l) { + double dist; + Point norm = l.normalAndDist(dist); + + return fromLine(norm, dist); +} + +xAx xAx::fromPoints(std::vector<Geom::Point> const &pt) { + Geom::NL::Vector V(pt.size(), -1.0); + Geom::NL::Matrix M(pt.size(), 5); + for(unsigned i = 0; i < pt.size(); i++) { + Geom::Point P = pt[i]; + Geom::NL::VectorView vv = M.row_view(i); + vv[0] = P[0]*P[0]; + vv[1] = P[0]*P[1]; + vv[2] = P[1]*P[1]; + vv[3] = P[0]; + vv[4] = P[1]; + } + + Geom::NL::LinearSystem ls(M, V); + + Geom::NL::Vector x = ls.SV_solve(); + return Geom::xAx(x[0], x[1], x[2], x[3], x[4], 1); + +} + + + +double xAx::valueAt(Point P) const { + return evaluate_at(P[0], P[1]); +} + +xAx xAx::scale(double sx, double sy) const { + return xAx(c[0]*sx*sx, c[1]*sx*sy, c[2]*sy*sy, + c[3]*sx, c[4]*sy, c[5]); +} + +Point xAx::gradient(Point p) const{ + double x = p[0]; + double y = p[1]; + return Point(2*c[0]*x + c[1]*y + c[3], + c[1]*x + 2*c[2]*y + c[4]); +} + +xAx xAx::operator-(xAx const &b) const { + xAx res; + for(int i = 0; i < 6; i++) { + res.c[i] = c[i] - b.c[i]; + } + return res; +} +xAx xAx::operator+(xAx const &b) const { + xAx res; + for(int i = 0; i < 6; i++) { + res.c[i] = c[i] + b.c[i]; + } + return res; +} +xAx xAx::operator+(double const &b) const { + xAx res; + for(int i = 0; i < 5; i++) { + res.c[i] = c[i]; + } + res.c[5] = c[5] + b; + return res; +} + +xAx xAx::operator*(double const &b) const { + xAx res; + for(int i = 0; i < 6; i++) { + res.c[i] = c[i] * b; + } + return res; +} + + std::vector<Point> xAx::crossings(Rect r) const { + std::vector<Point> res; + for(int ei = 0; ei < 4; ei++) { + Geom::LineSegment ls(r.corner(ei), r.corner(ei+1)); + D2<SBasis> lssb = ls.toSBasis(); + SBasis edge_curve = evaluate_at(lssb[0], lssb[1]); + std::vector<double> rts = Geom::roots(edge_curve); + for(unsigned eci = 0; eci < rts.size(); eci++) { + res.push_back(lssb.valueAt(rts[eci])); + } + } + return res; +} + + boost::optional<RatQuad> xAx::toCurve(Rect const & bnd) const { + std::vector<Point> crs = crossings(bnd); + if(crs.size() == 1) { + Point A = crs[0]; + Point dA = rot90(gradient(A)); + if(L2sq(dA) <= 1e-10) { // perhaps a single point? + return boost::optional<RatQuad> (); + } + LineSegment ls = intersection(Line::from_origin_and_vector(A, dA), bnd); + return RatQuad::fromPointsTangents(A, dA, ls.pointAt(0.5), ls[1], dA); + } + else if(crs.size() >= 2 && crs.size() < 4) { + Point A = crs[0]; + Point C = crs[1]; + if(crs.size() == 3) { + if(distance(A, crs[2]) > distance(A, C)) + C = crs[2]; + else if(distance(C, crs[2]) > distance(A, C)) + A = crs[2]; + } + Line bisector = make_bisector_line(LineSegment(A, C)); + std::vector<double> bisect_rts = this->roots(bisector); + if(!bisect_rts.empty()) { + int besti = -1; + for(unsigned i =0; i < bisect_rts.size(); i++) { + Point p = bisector.pointAt(bisect_rts[i]); + if(bnd.contains(p)) { + besti = i; + } + } + if(besti >= 0) { + Point B = bisector.pointAt(bisect_rts[besti]); + + Point dA = gradient(A); + Point dC = gradient(C); + if(L2sq(dA) <= 1e-10 || L2sq(dC) <= 1e-10) { + return RatQuad::fromPointsTangents(A, C-A, B, C, A-C); + } + + RatQuad rq = RatQuad::fromPointsTangents(A, rot90(dA), + B, C, rot90(dC)); + return rq; + //std::vector<SBasis> hrq = rq.homogeneous(); + /*SBasis vertex_poly = evaluate_at(hrq[0], hrq[1], hrq[2]); + std::vector<double> rts = roots(vertex_poly); + for(unsigned i = 0; i < rts.size(); i++) { + //draw_circ(cr, Point(rq.pointAt(rts[i]))); + }*/ + } + } + } + return boost::optional<RatQuad>(); +} + + std::vector<double> xAx::roots(Point d, Point o) const { + // Find the roots on line l + // form the quadratic Q(t) = 0 by composing l with xAx + double q2 = c[0]*d[0]*d[0] + c[1]*d[0]*d[1] + c[2]*d[1]*d[1]; + double q1 = (2*c[0]*d[0]*o[0] + + c[1]*(d[0]*o[1]+d[1]*o[0]) + + 2*c[2]*d[1]*o[1] + + c[3]*d[0] + c[4]*d[1]); + double q0 = c[0]*o[0]*o[0] + c[1]*o[0]*o[1] + c[2]*o[1]*o[1] + c[3]*o[0] + c[4]*o[1] + c[5]; + std::vector<double> r; + if(q2 == 0) { + if(q1 == 0) { + return r; + } + r.push_back(-q0/q1); + } else { + double desc = q1*q1 - 4*q2*q0; + /*std::cout << q2 << ", " + << q1 << ", " + << q0 << "; " + << desc << "\n";*/ + if (desc < 0) + return r; + else if (desc == 0) + r.push_back(-q1/(2*q2)); + else { + desc = std::sqrt(desc); + double t; + if (q1 == 0) + { + t = -0.5 * desc; + } + else + { + t = -0.5 * (q1 + sgn(q1) * desc); + } + r.push_back(t/q2); + r.push_back(q0/t); + } + } + return r; +} + +std::vector<double> xAx::roots(Line const &l) const { + return roots(l.versor(), l.origin()); +} + +Interval xAx::quad_ex(double a, double b, double c, Interval ivl) { + double cx = -b*0.5/a; + Interval bnds((a*ivl.min()+b)*ivl.min()+c, (a*ivl.max()+b)*ivl.max()+c); + if(ivl.contains(cx)) + bnds.expandTo((a*cx+b)*cx+c); + return bnds; +} + +Geom::Affine xAx::hessian() const { + Geom::Affine m(2*c[0], c[1], + c[1], 2*c[2], + 0, 0); + return m; +} + + +boost::optional<Point> solve(double A[2][2], double b[2]) { + double const determ = det(A); + if (determ != 0.0) { // hopeful, I know + Geom::Coord const ideterm = 1.0 / determ; + + return Point ((A[1][1]*b[0] -A[0][1]*b[1]), + (-A[1][0]*b[0] + A[0][0]*b[1]))* ideterm; + } else { + return boost::optional<Point>(); + } +} + +boost::optional<Point> xAx::bottom() const { + double A[2][2] = {{2*c[0], c[1]}, + {c[1], 2*c[2]}}; + double b[2] = {-c[3], -c[4]}; + return solve(A, b); + //return Point(-c[3], -c[4])*hessian().inverse(); +} + +Interval xAx::extrema(Rect r) const { + if (c[0] == 0 && c[1] == 0 && c[2] == 0) { + Interval ext(valueAt(r.corner(0))); + for(int i = 1; i < 4; i++) + ext |= Interval(valueAt(r.corner(i))); + return ext; + } + double k = r[X].min(); + Interval ext = quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[Y]); + k = r[X].max(); + ext |= quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[Y]); + k = r[Y].min(); + ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[X]); + k = r[Y].max(); + ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[X]); + boost::optional<Point> B0 = bottom(); + if (B0 && r.contains(*B0)) + ext.expandTo(0); + return ext; +} + + + + + + + + + +/* + * helper functions + */ + +bool at_infinity (Point const& p) +{ + if (p[X] == infinity() || p[X] == -infinity() + || p[Y] == infinity() || p[Y] == -infinity()) + { + return true; + } + return false; +} + +inline +double signed_triangle_area (Point const& p1, Point const& p2, Point const& p3) +{ + return (cross(p2, p3) - cross(p1, p3) + cross(p1, p2)); +} + + + +/* + * 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 + */ +void xAx::set(std::vector<Point> const& points) +{ + size_t sz = points.size(); + if (sz < 5) + { + THROW_RANGEERROR("fitting error: too few points passed"); + } + NL::LFMConicSection model; + NL::least_squeares_fitter<NL::LFMConicSection> fitter(model, sz); + + for (size_t i = 0; i < sz; ++i) + { + fitter.append(points[i]); + } + fitter.update(); + + NL::Vector z(sz, 0.0); + model.instance(*this, fitter.result(z)); +} + +/* + * 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 + */ +void xAx::set (const Point& _vertex, double _angle, double _dist1, double _dist2) +{ + using std::swap; + + if (_dist2 == infinity() || _dist2 == -infinity()) // parabola + { + if (_dist1 == infinity()) // degenerate to a line + { + Line l(_vertex, _angle); + std::vector<double> lcoeff = l.coefficients(); + coeff(3) = lcoeff[0]; + coeff(4) = lcoeff[1]; + coeff(5) = lcoeff[2]; + return; + } + + // y^2 - 4px == 0 + double cD = -4 * _dist1; + + double cosa = std::cos (_angle); + double sina = std::sin (_angle); + double cca = cosa * cosa; + double ssa = sina * sina; + double csa = cosa * sina; + + coeff(0) = ssa; + coeff(1) = -2 * csa; + coeff(2) = cca; + coeff(3) = cD * cosa; + coeff(4) = cD * sina; + + double VxVx = _vertex[X] * _vertex[X]; + double VxVy = _vertex[X] * _vertex[Y]; + double VyVy = _vertex[Y] * _vertex[Y]; + + coeff(5) = coeff(0) * VxVx + coeff(1) * VxVy + coeff(2) * VyVy + - coeff(3) * _vertex[X] - coeff(4) * _vertex[Y]; + coeff(3) -= (2 * coeff(0) * _vertex[X] + coeff(1) * _vertex[Y]); + coeff(4) -= (2 * coeff(2) * _vertex[Y] + coeff(1) * _vertex[X]); + + return; + } + + if (std::fabs(_dist1) > std::fabs(_dist2)) + { + swap (_dist1, _dist2); + } + if (_dist1 < 0) + { + _angle -= M_PI; + _dist1 = -_dist1; + _dist2 = -_dist2; + } + + // ellipse and hyperbola + double lin_ecc = (_dist2 - _dist1) / 2; + double rx = (_dist2 + _dist1) / 2; + + double cA = rx * rx - lin_ecc * lin_ecc; + double cC = rx * rx; + double cF = - cA * cC; +// std::cout << "cA: " << cA << std::endl; +// std::cout << "cC: " << cC << std::endl; +// std::cout << "cF: " << cF << std::endl; + + double cosa = std::cos (_angle); + double sina = std::sin (_angle); + double cca = cosa * cosa; + double ssa = sina * sina; + double csa = cosa * sina; + + coeff(0) = cca * cA + ssa * cC; + coeff(2) = ssa * cA + cca * cC; + coeff(1) = 2 * csa * (cA - cC); + + Point C (rx * cosa + _vertex[X], rx * sina + _vertex[Y]); + double CxCx = C[X] * C[X]; + double CxCy = C[X] * C[Y]; + double CyCy = C[Y] * C[Y]; + + coeff(3) = -2 * coeff(0) * C[X] - coeff(1) * C[Y]; + coeff(4) = -2 * coeff(2) * C[Y] - coeff(1) * C[X]; + coeff(5) = cF + coeff(0) * CxCx + coeff(1) * CxCy + coeff(2) * CyCy; +} + +/* + * 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 + */ +void xAx::set (const Point& _vertex, const Point& _focus1, const Point& _focus2) +{ + if (at_infinity(_vertex)) + { + THROW_RANGEERROR("case not handled: vertex at infinity"); + } + if (at_infinity(_focus2)) + { + if (at_infinity(_focus1)) + { + THROW_RANGEERROR("case not handled: both focus at infinity"); + } + Point VF = _focus1 - _vertex; + double dist1 = L2(VF); + double angle = atan2(VF); + set(_vertex, angle, dist1, infinity()); + return; + } + else if (at_infinity(_focus1)) + { + Point VF = _focus2 - _vertex; + double dist1 = L2(VF); + double angle = atan2(VF); + set(_vertex, angle, dist1, infinity()); + return; + } + assert (are_collinear (_vertex, _focus1, _focus2)); + if (!are_near(_vertex, _focus1)) + { + Point VF = _focus1 - _vertex; + Line axis(_vertex, _focus1); + double angle = atan2(VF); + double dist1 = L2(VF); + double dist2 = distance (_vertex, _focus2); + double t = axis.timeAt(_focus2); + if (t < 0) dist2 = -dist2; +// std::cout << "t = " << t << std::endl; +// std::cout << "dist2 = " << dist2 << std::endl; + set (_vertex, angle, dist1, dist2); + } + else if (!are_near(_vertex, _focus2)) + { + Point VF = _focus2 - _vertex; + double angle = atan2(VF); + double dist1 = 0; + double dist2 = L2(VF); + set (_vertex, angle, dist1, dist2); + } + else + { + coeff(0) = coeff(2) = 1; + coeff(1) = coeff(3) = coeff(4) = coeff(5) = 0; + } +} + +/* + * 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 + */ +void xAx::set (const Point & _focus, const Line & _directrix, double _eccentricity) +{ + Point O = _directrix.pointAt (_directrix.timeAtProjection (_focus)); + //std::cout << "O = " << O << std::endl; + Point OF = _focus - O; + double p = L2(OF); + + coeff(0) = 1 - _eccentricity * _eccentricity; + coeff(1) = 0; + coeff(2) = 1; + coeff(3) = -2 * p; + coeff(4) = 0; + coeff(5) = p * p; + + double angle = atan2 (OF); + + (*this) = rotate (angle); + //std::cout << "O = " << O << std::endl; + (*this) = translate (O); +} + +/* + * Made up a degenerate conic section as a pair of lines + * + * l1, l2: lines that made up the conic section + */ +void xAx::set (const Line& l1, const Line& l2) +{ + std::vector<double> cl1 = l1.coefficients(); + std::vector<double> cl2 = l2.coefficients(); + + coeff(0) = cl1[0] * cl2[0]; + coeff(2) = cl1[1] * cl2[1]; + coeff(5) = cl1[2] * cl2[2]; + coeff(1) = cl1[0] * cl2[1] + cl1[1] * cl2[0]; + coeff(3) = cl1[0] * cl2[2] + cl1[2] * cl2[0]; + coeff(4) = cl1[1] * cl2[2] + cl1[2] * cl2[1]; +} + + + +/* + * Return the section conic kind + */ +xAx::kind_t xAx::kind () const +{ + + xAx conic(*this); + NL::SymmetricMatrix<3> C = conic.get_matrix(); + NL::ConstSymmetricMatrixView<2> A = C.main_minor_const_view(); + + double t1 = trace(A); + double t2 = det(A); + //double T3 = det(C); + int st1 = trace_sgn(A); + int st2 = det_sgn(A); + int sT3 = det_sgn(C); + + //std::cout << "T3 = " << T3 << std::endl; + //std::cout << "sT3 = " << sT3 << std::endl; + //std::cout << "t2 = " << t2 << std::endl; + //std::cout << "t1 = " << t1 << std::endl; + //std::cout << "st2 = " << st2 << std::endl; + + if (sT3 != 0) + { + if (st2 == 0) + { + return PARABOLA; + } + else if (st2 == 1) + { + + if (sT3 * st1 < 0) + { + NL::SymmetricMatrix<2> discr; + discr(0,0) = 4; discr(1,1) = t2; discr(1,0) = t1; + int discr_sgn = - det_sgn (discr); + //std::cout << "t1 * t1 - 4 * t2 = " + // << (t1 * t1 - 4 * t2) << std::endl; + //std::cout << "discr_sgn = " << discr_sgn << std::endl; + if (discr_sgn == 0) + { + return CIRCLE; + } + else + { + return REAL_ELLIPSE; + } + } + else // sT3 * st1 > 0 + { + return IMAGINARY_ELLIPSE; + } + } + else // t2 < 0 + { + if (st1 == 0) + { + return RECTANGULAR_HYPERBOLA; + } + else + { + return HYPERBOLA; + } + } + } + else // T3 == 0 + { + if (st2 == 0) + { + //double T2 = NL::trace<2>(C); + int sT2 = NL::trace_sgn<2>(C); + //std::cout << "T2 = " << T2 << std::endl; + //std::cout << "sT2 = " << sT2 << std::endl; + + if (sT2 == 0) + { + return DOUBLE_LINE; + } + if (sT2 == -1) + { + return TWO_REAL_PARALLEL_LINES; + } + else // T2 > 0 + { + return TWO_IMAGINARY_PARALLEL_LINES; + } + } + else if (st2 == -1) + { + return TWO_REAL_CROSSING_LINES; + } + else // t2 > 0 + { + return TWO_IMAGINARY_CROSSING_LINES; + } + } + return UNKNOWN; +} + +/* + * Return a string representing the conic section kind + */ +std::string xAx::categorise() const +{ + kind_t KIND = kind(); + + switch (KIND) + { + case PARABOLA : + return "parabola"; + case CIRCLE : + return "circle"; + case REAL_ELLIPSE : + return "real ellispe"; + case IMAGINARY_ELLIPSE : + return "imaginary ellispe"; + case RECTANGULAR_HYPERBOLA : + return "rectangular hyperbola"; + case HYPERBOLA : + return "hyperbola"; + case DOUBLE_LINE : + return "double line"; + case TWO_REAL_PARALLEL_LINES : + return "two real parallel lines"; + case TWO_IMAGINARY_PARALLEL_LINES : + return "two imaginary parallel lines"; + case TWO_REAL_CROSSING_LINES : + return "two real crossing lines"; + case TWO_IMAGINARY_CROSSING_LINES : + return "two imaginary crossing lines"; + default : + return "unknown"; + } +} + +/* + * Compute the solutions of the conic section algebraic equation with respect to + * one coordinate after substituting to the other coordinate the passed value + * + * sol: the computed solutions + * v: the provided value + * d: the index of the coordinate the passed value have to be substituted to + */ +void xAx::roots (std::vector<double>& sol, Coord v, Dim2 d) const +{ + sol.clear(); + if (d < 0 || d > Y) + { + THROW_RANGEERROR("dimension parameter out of range"); + } + + // p*t^2 + q*t + r = 0; + double p, q, r; + + if (d == X) + { + p = coeff(2); + q = coeff(4) + coeff(1) * v; + r = coeff(5) + (coeff(0) * v + coeff(3)) * v; + } + else + { + p = coeff(0); + q = coeff(3) + coeff(1) * v; + r = coeff(5) + (coeff(2) * v + coeff(4)) * v; + } + + if (p == 0) + { + if (q == 0) return; + double t = -r/q; + sol.push_back(t); + return; + } + + if (q == 0) + { + if ((p > 0 && r > 0) || (p < 0 && r < 0)) return; + double t = -r / p; + t = std::sqrt (t); + sol.push_back(-t); + sol.push_back(t); + return; + } + + if (r == 0) + { + double t = -q/p; + sol.push_back(0); + sol.push_back(t); + return; + } + + + //std::cout << "p = " << p << ", q = " << q << ", r = " << r << std::endl; + double delta = q * q - 4 * p * r; + if (delta < 0) return; + if (delta == 0) + { + double t = -q / (2 * p); + sol.push_back(t); + return; + } + // else + double srd = std::sqrt(delta); + double t = - (q + sgn(q) * srd) / 2; + sol.push_back (t/p); + sol.push_back (r/t); + +} + +/* + * Return the inclination angle of the major axis of the conic section + */ +double xAx::axis_angle() const +{ + if (coeff(0) == 0 && coeff(1) == 0 && coeff(2) == 0) + { + Line l (coeff(3), coeff(4), coeff(5)); + return l.angle(); + } + if (coeff(1) == 0 && (coeff(0) == coeff(2))) return 0; + + double angle; + + int sgn_discr = det_sgn (get_matrix().main_minor_const_view()); + if (sgn_discr == 0) + { + //std::cout << "rotation_angle: sgn_discr = " + // << sgn_discr << std::endl; + angle = std::atan2 (-coeff(1), 2 * coeff(2)); + if (angle < 0) angle += 2*M_PI; + if (angle >= M_PI) angle -= M_PI; + + } + else + { + angle = std::atan2 (coeff(1), coeff(0) - coeff(2)); + if (angle < 0) angle += 2*M_PI; + angle -= M_PI; + if (angle < 0) angle += 2*M_PI; + angle /= 2; + if (angle >= M_PI) angle -= M_PI; + } + //std::cout << "rotation_angle : angle = " << angle << std::endl; + return angle; +} + +/* + * Translate the conic section by the given vector offset + * + * _offset: represent the vector offset + */ +xAx xAx::translate (const Point & _offset) const +{ + double B = coeff(1) / 2; + double D = coeff(3) / 2; + double E = coeff(4) / 2; + + Point T = - _offset; + + xAx cs; + cs.coeff(0) = coeff(0); + cs.coeff(1) = coeff(1); + cs.coeff(2) = coeff(2); + + Point DE; + DE[0] = coeff(0) * T[0] + B * T[1]; + DE[1] = B * T[0] + coeff(2) * T[1]; + + cs.coeff(3) = (DE[0] + D) * 2; + cs.coeff(4) = (DE[1] + E) * 2; + + cs.coeff(5) = dot (T, DE) + 2 * (T[0] * D + T[1] * E) + coeff(5); + + return cs; +} + + +/* + * Rotate the conic section by the given angle wrt the point (0,0) + * + * angle: represent the rotation angle + */ +xAx xAx::rotate (double angle) const +{ + double c = std::cos(-angle); + double s = std::sin(-angle); + double cc = c * c; + double ss = s * s; + double cs = c * s; + + xAx result; + result.coeff(5) = coeff(5); + + // quadratic terms + double Bcs = coeff(1) * cs; + + result.coeff(0) = coeff(0) * cc + Bcs + coeff(2) * ss; + result.coeff(2) = coeff(0) * ss - Bcs + coeff(2) * cc; + result.coeff(1) = coeff(1) * (cc - ss) + 2 * (coeff(2) - coeff(0)) * cs; + + // linear terms + result.coeff(3) = coeff(3) * c + coeff(4) * s; + result.coeff(4) = coeff(4) * c - coeff(3) * s; + + return result; +} + + +/* + * Decompose a degenerate conic in two lines the conic section is made by. + * Return true if the decomposition is successful, else if it fails. + * + * l1, l2: out parameters where the decomposed conic section is returned + */ +bool xAx::decompose (Line& l1, Line& l2) const +{ + NL::SymmetricMatrix<3> C = get_matrix(); + if (!is_quadratic() || !isDegenerate()) + { + return false; + } + NL::Matrix M(C); + NL::SymmetricMatrix<3> D = -adj(C); + + if (!D.is_zero()) // D == 0 <=> rank(C) < 2 + { + + //if (D.get<0,0>() < 0 || D.get<1,1>() < 0 || D.get<2,2>() < 0) + //{ + //std::cout << "C: \n" << C << std::endl; + //std::cout << "D: \n" << D << std::endl; + + /* + * This case should be impossible because any diagonal element + * of D is a square, but due to non exact aritmethic computation + * it can actually happen; however the algorithm seems to work + * correctly even if some diagonal term is negative, the only + * difference is that we should compute the absolute value of + * diagonal elements. So until we elaborate a better degenerate + * test it's better not rising exception when we have a negative + * diagonal element. + */ + //} + + NL::Vector d(3); + d[0] = std::fabs (D.get<0,0>()); + d[1] = std::fabs (D.get<1,1>()); + d[2] = std::fabs (D.get<2,2>()); + + size_t idx = d.max_index(); + if (d[idx] == 0) + { + THROW_LOGICALERROR ("xAx::decompose: " + "rank 2 but adjoint with null diagonal"); + } + d[0] = D(idx,0); d[1] = D(idx,1); d[2] = D(idx,2); + d.scale (1 / std::sqrt (std::fabs (D(idx,idx)))); + M(1,2) += d[0]; M(2,1) -= d[0]; + M(0,2) -= d[1]; M(2,0) += d[1]; + M(0,1) += d[2]; M(1,0) -= d[2]; + + //std::cout << "C: \n" << C << std::endl; + //std::cout << "D: \n" << D << std::endl; + //std::cout << "d = " << d << std::endl; + //std::cout << "M = " << M << std::endl; + } + + std::pair<size_t, size_t> max_ij = M.max_index(); + std::pair<size_t, size_t> min_ij = M.min_index(); + double abs_max = std::fabs (M(max_ij.first, max_ij.second)); + double abs_min = std::fabs (M(min_ij.first, min_ij.second)); + size_t i_max, j_max; + if (abs_max > abs_min) + { + i_max = max_ij.first; + j_max = max_ij.second; + } + else + { + i_max = min_ij.first; + j_max = min_ij.second; + } + l1.setCoefficients (M(i_max,0), M(i_max,1), M(i_max,2)); + l2.setCoefficients (M(0, j_max), M(1,j_max), M(2,j_max)); + + return true; +} + + +/* + * Return the rectangle that bound the conic section arc characterized by + * the passed points. + * + * P1: the initial point of the arc + * Q: the inner point of the arc + * P2: the final point of the arc + * + * prerequisite: the passed points must lie on the conic + */ +Rect xAx::arc_bound (const Point & P1, const Point & Q, const Point & P2) const +{ + using std::swap; + //std::cout << "BOUND: P1 = " << P1 << std::endl; + //std::cout << "BOUND: Q = " << Q << std::endl; + //std::cout << "BOUND: P2 = " << P2 << std::endl; + + Rect B(P1, P2); + double Qside = signed_triangle_area (P1, Q, P2); + //std::cout << "BOUND: Qside = " << Qside << std::endl; + + Line gl[2]; + bool empty[2] = {false, false}; + + try // if the passed coefficients lead to an equation 0x + 0y + c == 0, + { // with c != 0 the setCoefficients rise an exception + gl[0].setCoefficients (coeff(1), 2 * coeff(2), coeff(4)); + } + catch(Geom::LogicalError const &e) + { + empty[0] = true; + } + + try + { + gl[1].setCoefficients (2 * coeff(0), coeff(1), coeff(3)); + } + catch(Geom::LogicalError const &e) + { + empty[1] = true; + } + + std::vector<double> rts; + std::vector<Point> M; + for (size_t dim = 0; dim < 2; ++dim) + { + if (empty[dim]) continue; + rts = roots (gl[dim]); + M.clear(); + for (size_t i = 0; i < rts.size(); ++i) + M.push_back (gl[dim].pointAt (rts[i])); + if (M.size() == 1) + { + double Mside = signed_triangle_area (P1, M[0], P2); + if (sgn(Mside) == sgn(Qside)) + { + //std::cout << "BOUND: M.size() == 1" << std::endl; + B[dim].expandTo(M[0][dim]); + } + } + else if (M.size() == 2) + { + //std::cout << "BOUND: M.size() == 2" << std::endl; + if (M[0][dim] > M[1][dim]) + swap (M[0], M[1]); + + if (M[0][dim] > B[dim].max()) + { + double Mside = signed_triangle_area (P1, M[0], P2); + if (sgn(Mside) == sgn(Qside)) + B[dim].setMax(M[0][dim]); + } + else if (M[1][dim] < B[dim].min()) + { + double Mside = signed_triangle_area (P1, M[1], P2); + if (sgn(Mside) == sgn(Qside)) + B[dim].setMin(M[1][dim]); + } + else + { + double Mside = signed_triangle_area (P1, M[0], P2); + if (sgn(Mside) == sgn(Qside)) + B[dim].setMin(M[0][dim]); + Mside = signed_triangle_area (P1, M[1], P2); + if (sgn(Mside) == sgn(Qside)) + B[dim].setMax(M[1][dim]); + } + } + } + + return B; +} + +/* + * Return all points on the conic section nearest to the passed point "P". + * + * P: the point to compute the nearest one + */ +std::vector<Point> xAx::allNearestTimes (const Point &P) const +{ + // TODO: manage the circle - centre case + std::vector<Point> points; + + // named C the conic we look for points (x,y) on C such that + // dot (grad (C(x,y)), rot90 (P -(x,y))) == 0; the set of points satisfying + // this equation is still a conic G, so the wanted points can be found by + // intersecting C with G + xAx G (-coeff(1), + 2 * (coeff(0) - coeff(2)), + coeff(1), + -coeff(4) + coeff(1) * P[X] - 2 * coeff(0) * P[Y], + coeff(3) - coeff(1) * P[Y] + 2 * coeff(2) * P[X], + -coeff(3) * P[Y] + coeff(4) * P[X]); + + std::vector<Point> crs = intersect (*this, G); + + //std::cout << "NEAREST POINT: crs.size = " << crs.size() << std::endl; + if (crs.empty()) return points; + + size_t idx = 0; + double mindist = distanceSq (crs[0], P); + std::vector<double> dist; + dist.push_back (mindist); + + for (size_t i = 1; i < crs.size(); ++i) + { + dist.push_back (distanceSq (crs[i], P)); + if (mindist > dist.back()) + { + idx = i; + mindist = dist.back(); + } + } + + points.push_back (crs[idx]); + for (size_t i = 0; i < crs.size(); ++i) + { + if (i == idx) continue; + if (dist[i] == mindist) + points.push_back (crs[i]); + } + + return points; +} + + + +bool clip (std::vector<RatQuad> & rq, const xAx & cs, const Rect & R) +{ + clipper aclipper (cs, R); + return aclipper.clip (rq); +} + + +} // end namespace Geom + + + + +/* + 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/src/2geom/conicsec.h b/src/2geom/conicsec.h new file mode 100644 index 0000000..b84edc6 --- /dev/null +++ b/src/2geom/conicsec.h @@ -0,0 +1,518 @@ +/** @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 <boost/optional/optional.hpp> + +#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; + boost::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; + + boost::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 boost::optional<Point> instance. + */ + boost::optional<Point> centre() const + { + typedef boost::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; + + /* + * 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(int i = 0; i < 6; i++) { + out_file << x.c[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/src/2geom/convex-hull.cpp b/src/2geom/convex-hull.cpp new file mode 100644 index 0000000..4f5e067 --- /dev/null +++ b/src/2geom/convex-hull.cpp @@ -0,0 +1,746 @@ +/** @file + * @brief Convex hull of a set of points + *//* + * Authors: + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * Michael G. Sloan <mgsloan@gmail.com> + * 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. + * + */ + +#include <2geom/convex-hull.h> +#include <2geom/exception.h> +#include <algorithm> +#include <map> +#include <iostream> +#include <cassert> +#include <boost/array.hpp> + +/** Todo: + + modify graham scan to work top to bottom, rather than around angles + + intersection + + minimum distance between convex hulls + + maximum distance between convex hulls + + hausdorf metric? + + check all degenerate cases carefully + + check all algorithms meet all invariants + + generalise rotating caliper algorithm (iterator/circulator?) +*/ + +using std::vector; +using std::map; +using std::pair; +using std::make_pair; +using std::swap; + +namespace Geom { + +ConvexHull::ConvexHull(Point const &a, Point const &b) + : _boundary(2) + , _lower(0) +{ + _boundary[0] = a; + _boundary[1] = b; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + _construct(); +} + +ConvexHull::ConvexHull(Point const &a, Point const &b, Point const &c) + : _boundary(3) + , _lower(0) +{ + _boundary[0] = a; + _boundary[1] = b; + _boundary[2] = c; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + _construct(); +} + +ConvexHull::ConvexHull(Point const &a, Point const &b, Point const &c, Point const &d) + : _boundary(4) + , _lower(0) +{ + _boundary[0] = a; + _boundary[1] = b; + _boundary[2] = c; + _boundary[3] = d; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + _construct(); +} + +ConvexHull::ConvexHull(std::vector<Point> const &pts) + : _lower(0) +{ + //if (pts.size() > 16) { // arbitrary threshold + // _prune(pts.begin(), pts.end(), _boundary); + //} else { + _boundary = pts; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + //} + _construct(); +} + +bool ConvexHull::_is_clockwise_turn(Point const &a, Point const &b, Point const &c) +{ + if (b == c) return false; + return cross(b-a, c-a) > 0; +} + +void ConvexHull::_construct() +{ + // _boundary must already be sorted in LexLess<X> order + if (_boundary.empty()) { + _lower = 0; + return; + } + if (_boundary.size() == 1 || (_boundary.size() == 2 && _boundary[0] == _boundary[1])) { + _boundary.resize(1); + _lower = 1; + return; + } + if (_boundary.size() == 2) { + _lower = 2; + return; + } + + std::size_t k = 2; + for (std::size_t i = 2; i < _boundary.size(); ++i) { + while (k >= 2 && !_is_clockwise_turn(_boundary[k-2], _boundary[k-1], _boundary[i])) { + --k; + } + std::swap(_boundary[k++], _boundary[i]); + } + + _lower = k; + std::sort(_boundary.begin() + k, _boundary.end(), Point::LexGreater<X>()); + _boundary.push_back(_boundary.front()); + for (std::size_t i = _lower; i < _boundary.size(); ++i) { + while (k > _lower && !_is_clockwise_turn(_boundary[k-2], _boundary[k-1], _boundary[i])) { + --k; + } + std::swap(_boundary[k++], _boundary[i]); + } + + _boundary.resize(k-1); +} + +double ConvexHull::area() const +{ + if (size() <= 2) return 0; + + double a = 0; + for (std::size_t i = 0; i < size()-1; ++i) { + a += cross(_boundary[i], _boundary[i+1]); + } + a += cross(_boundary.back(), _boundary.front()); + return fabs(a * 0.5); +} + +OptRect ConvexHull::bounds() const +{ + OptRect ret; + if (empty()) return ret; + ret = Rect(left(), top(), right(), bottom()); + return ret; +} + +Point ConvexHull::topPoint() const +{ + Point ret; + ret[Y] = std::numeric_limits<Coord>::infinity(); + + for (UpperIterator i = upperHull().begin(); i != upperHull().end(); ++i) { + if (ret[Y] >= i->y()) { + ret = *i; + } else { + break; + } + } + + return ret; +} + +Point ConvexHull::bottomPoint() const +{ + Point ret; + ret[Y] = -std::numeric_limits<Coord>::infinity(); + + for (LowerIterator j = lowerHull().begin(); j != lowerHull().end(); ++j) { + if (ret[Y] <= j->y()) { + ret = *j; + } else { + break; + } + } + + return ret; +} + +template <typename Iter, typename Lex> +bool below_x_monotonic_polyline(Point const &p, Iter first, Iter last, Lex lex) +{ + typename Lex::Secondary above; + Iter f = std::lower_bound(first, last, p, lex); + if (f == last) return false; + if (f == first) { + if (p == *f) return true; + return false; + } + + Point a = *(f-1), b = *f; + if (a[X] == b[X]) { + if (above(p[Y], a[Y]) || above(b[Y], p[Y])) return false; + } else { + // TODO: maybe there is a more numerically stable method + Coord y = lerp((p[X] - a[X]) / (b[X] - a[X]), a[Y], b[Y]); + if (above(p[Y], y)) return false; + } + return true; +} + +bool ConvexHull::contains(Point const &p) const +{ + if (_boundary.empty()) return false; + if (_boundary.size() == 1) { + if (_boundary[0] == p) return true; + return false; + } + + // 1. verify that the point is in the relevant X range + if (p[X] < _boundary[0][X] || p[X] > _boundary[_lower-1][X]) return false; + + // 2. check whether it is below the upper hull + UpperIterator ub = upperHull().begin(), ue = upperHull().end(); + if (!below_x_monotonic_polyline(p, ub, ue, Point::LexLess<X>())) return false; + + // 3. check whether it is above the lower hull + LowerIterator lb = lowerHull().begin(), le = lowerHull().end(); + if (!below_x_monotonic_polyline(p, lb, le, Point::LexGreater<X>())) return false; + + return true; +} + +bool ConvexHull::contains(Rect const &r) const +{ + for (unsigned i = 0; i < 4; ++i) { + if (!contains(r.corner(i))) return false; + } + return true; +} + +bool ConvexHull::contains(ConvexHull const &ch) const +{ + // TODO: requires interiorContains. + // We have to check all points of ch, and each point takes logarithmic time. + // If there are more points in ch that here, it is faster to make the check + // the other way around. + /*if (ch.size() > size()) { + for (iterator i = begin(); i != end(); ++i) { + if (ch.interiorContains(*i)) return false; + } + return true; + }*/ + + for (iterator i = ch.begin(); i != ch.end(); ++i) { + if (!contains(*i)) return false; + } + return true; +} + +void ConvexHull::swap(ConvexHull &other) +{ + _boundary.swap(other._boundary); + std::swap(_lower, other._lower); +} + +void ConvexHull::swap(std::vector<Point> &pts) +{ + _boundary.swap(pts); + _lower = 0; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + _construct(); +} + +#if 0 +/*** SignedTriangleArea + * returns the area of the triangle defined by p0, p1, p2. A clockwise triangle has positive area. + */ +double +SignedTriangleArea(Point p0, Point p1, Point p2) { + return cross((p1 - p0), (p2 - p0)); +} + +class angle_cmp{ +public: + Point o; + angle_cmp(Point o) : o(o) {} + +#if 0 + bool + operator()(Point a, Point b) { + // not remove this check or std::sort could crash + if (a == b) return false; + Point da = a - o; + Point db = b - o; + if (da == -db) return false; + +#if 1 + double aa = da[0]; + double ab = db[0]; + if((da[1] == 0) && (db[1] == 0)) + return da[0] < db[0]; + if(da[1] == 0) + return true; // infinite tangent + if(db[1] == 0) + return false; // infinite tangent + aa = da[0] / da[1]; + ab = db[0] / db[1]; + if(aa > ab) + return true; +#else + //assert((ata > atb) == (aa < ab)); + double aa = atan2(da); + double ab = atan2(db); + if(aa < ab) + return true; +#endif + if(aa == ab) + return L2sq(da) < L2sq(db); + return false; + } +#else + bool operator() (Point const& a, Point const& b) + { + // not remove this check or std::sort could generate + // a segmentation fault because it needs a strict '<' + // but due to round errors a == b doesn't mean dxy == dyx + if (a == b) return false; + Point da = a - o; + Point db = b - o; + if (da == -db) return false; + double dxy = da[X] * db[Y]; + double dyx = da[Y] * db[X]; + if (dxy > dyx) return true; + else if (dxy < dyx) return false; + return L2sq(da) < L2sq(db); + } +#endif +}; + +//Mathematically incorrect mod, but more useful. +int mod(int i, int l) { + return i >= 0 ? + i % l : (i % l) + l; +} +//OPT: usages can often be replaced by conditions + +/*** ConvexHull::add_point + * to add a point we need to find whether the new point extends the boundary, and if so, what it + * obscures. Tarjan? Jarvis?*/ +void +ConvexHull::merge(Point p) { + std::vector<Point> out; + + int len = boundary.size(); + + if(len < 2) { + if(boundary.empty() || boundary[0] != p) + boundary.push_back(p); + return; + } + + bool pushed = false; + + bool pre = is_left(p, -1); + for(int i = 0; i < len; i++) { + bool cur = is_left(p, i); + if(pre) { + if(cur) { + if(!pushed) { + out.push_back(p); + pushed = true; + } + continue; + } + else if(!pushed) { + out.push_back(p); + pushed = true; + } + } + out.push_back(boundary[i]); + pre = cur; + } + + boundary = out; +} +//OPT: quickly find an obscured point and find the bounds by extending from there. then push all points not within the bounds in order. + //OPT: use binary searches to find the actual starts/ends, use known rights as boundaries. may require cooperation of find_left algo. + +/*** ConvexHull::is_clockwise + * We require that successive pairs of edges always turn right. + * We return false on collinear points + * proposed algorithm: walk successive edges and require triangle area is positive. + */ +bool +ConvexHull::is_clockwise() const { + if(is_degenerate()) + return true; + Point first = boundary[0]; + Point second = boundary[1]; + for(std::vector<Point>::const_iterator it(boundary.begin()+2), e(boundary.end()); + it != e;) { + if(SignedTriangleArea(first, second, *it) > 0) + return false; + first = second; + second = *it; + ++it; + } + return true; +} + +/*** ConvexHull::top_point_first + * We require that the first point in the convex hull has the least y coord, and that off all such points on the hull, it has the least x coord. + * proposed algorithm: track lexicographic minimum while walking the list. + */ +bool +ConvexHull::top_point_first() const { + if(size() <= 1) return true; + std::vector<Point>::const_iterator pivot = boundary.begin(); + for(std::vector<Point>::const_iterator it(boundary.begin()+1), + e(boundary.end()); + it != e; it++) { + if((*it)[1] < (*pivot)[1]) + pivot = it; + else if(((*it)[1] == (*pivot)[1]) && + ((*it)[0] < (*pivot)[0])) + pivot = it; + } + return pivot == boundary.begin(); +} +//OPT: since the Y values are orderly there should be something like a binary search to do this. + +bool +ConvexHull::meets_invariants() const { + return is_clockwise() && top_point_first(); +} + +/*** ConvexHull::is_degenerate + * We allow three degenerate cases: empty, 1 point and 2 points. In many cases these should be handled explicitly. + */ +bool +ConvexHull::is_degenerate() const { + return boundary.size() < 3; +} + + +int sgn(double x) { + if(x == 0) return 0; + return (x<0)?-1:1; +} + +bool same_side(Point L[2], Point xs[4]) { + int side = 0; + for(int i = 0; i < 4; i++) { + int sn = sgn(SignedTriangleArea(L[0], L[1], xs[i])); + if(sn && !side) + side = sn; + else if(sn != side) return false; + } + return true; +} + +/** find bridging pairs between two convex hulls. + * this code is based on Hormoz Pirzadeh's masters thesis. There is room for optimisation: + * 1. reduce recomputation + * 2. use more efficient angle code + * 3. write as iterator + */ +std::vector<pair<int, int> > bridges(ConvexHull a, ConvexHull b) { + vector<pair<int, int> > ret; + + // 1. find maximal points on a and b + int ai = 0, bi = 0; + // 2. find first copodal pair + double ap_angle = atan2(a[ai+1] - a[ai]); + double bp_angle = atan2(b[bi+1] - b[bi]); + Point L[2] = {a[ai], b[bi]}; + while(ai < int(a.size()) || bi < int(b.size())) { + if(ap_angle == bp_angle) { + // In the case of parallel support lines, we must consider all four pairs of copodal points + { + assert(0); // untested + Point xs[4] = {a[ai-1], a[ai+1], b[bi-1], b[bi+1]}; + if(same_side(L, xs)) ret.push_back(make_pair(ai, bi)); + xs[2] = b[bi]; + xs[3] = b[bi+2]; + if(same_side(L, xs)) ret.push_back(make_pair(ai, bi)); + xs[0] = a[ai]; + xs[1] = a[ai+2]; + if(same_side(L, xs)) ret.push_back(make_pair(ai, bi)); + xs[2] = b[bi-1]; + xs[3] = b[bi+1]; + if(same_side(L, xs)) ret.push_back(make_pair(ai, bi)); + } + ai++; + ap_angle += angle_between(a[ai] - a[ai-1], a[ai+1] - a[ai]); + L[0] = a[ai]; + bi++; + bp_angle += angle_between(b[bi] - b[bi-1], b[bi+1] - b[bi]); + L[1] = b[bi]; + std::cout << "parallel\n"; + } else if(ap_angle < bp_angle) { + ai++; + ap_angle += angle_between(a[ai] - a[ai-1], a[ai+1] - a[ai]); + L[0] = a[ai]; + Point xs[4] = {a[ai-1], a[ai+1], b[bi-1], b[bi+1]}; + if(same_side(L, xs)) ret.push_back(make_pair(ai, bi)); + } else { + bi++; + bp_angle += angle_between(b[bi] - b[bi-1], b[bi+1] - b[bi]); + L[1] = b[bi]; + Point xs[4] = {a[ai-1], a[ai+1], b[bi-1], b[bi+1]}; + if(same_side(L, xs)) ret.push_back(make_pair(ai, bi)); + } + } + return ret; +} + +unsigned find_bottom_right(ConvexHull const &a) { + unsigned it = 1; + while(it < a.boundary.size() && + a.boundary[it][Y] > a.boundary[it-1][Y]) + it++; + return it-1; +} + +/*** ConvexHull sweepline_intersection(ConvexHull a, ConvexHull b); + * find the intersection between two convex hulls. The intersection is also a convex hull. + * (Proof: take any two points both in a and in b. Any point between them is in a by convexity, + * and in b by convexity, thus in both. Need to prove still finite bounds.) + * This algorithm works by sweeping a line down both convex hulls in parallel, working out the left and right edges of the new hull. + */ +ConvexHull sweepline_intersection(ConvexHull const &a, ConvexHull const &b) { + ConvexHull ret; + + unsigned al = 0; + unsigned bl = 0; + + while(al+1 < a.boundary.size() && + (a.boundary[al+1][Y] > b.boundary[bl][Y])) { + al++; + } + while(bl+1 < b.boundary.size() && + (b.boundary[bl+1][Y] > a.boundary[al][Y])) { + bl++; + } + // al and bl now point to the top of the first pair of edges that overlap in y value + //double sweep_y = std::min(a.boundary[al][Y], + // b.boundary[bl][Y]); + return ret; +} + +/*** ConvexHull intersection(ConvexHull a, ConvexHull b); + * find the intersection between two convex hulls. The intersection is also a convex hull. + * (Proof: take any two points both in a and in b. Any point between them is in a by convexity, + * and in b by convexity, thus in both. Need to prove still finite bounds.) + */ +ConvexHull intersection(ConvexHull /*a*/, ConvexHull /*b*/) { + ConvexHull ret; + /* + int ai = 0, bi = 0; + int aj = a.boundary.size() - 1; + int bj = b.boundary.size() - 1; + */ + /*while (true) { + if(a[ai] + }*/ + return ret; +} + +template <typename T> +T idx_to_pair(pair<T, T> p, int idx) { + return idx?p.second:p.first; +} + +/*** ConvexHull merge(ConvexHull a, ConvexHull b); + * find the smallest convex hull that surrounds a and b. + */ +ConvexHull merge(ConvexHull a, ConvexHull b) { + ConvexHull ret; + + std::cout << "---\n"; + std::vector<pair<int, int> > bpair = bridges(a, b); + + // Given our list of bridges {(pb1, qb1), ..., (pbk, qbk)} + // we start with the highest point in p0, q0, say it is p0. + // then the merged hull is p0, ..., pb1, qb1, ..., qb2, pb2, ... + // In other words, either of the two polygons vertices are added in order until the vertex coincides with a bridge point, at which point we swap. + + unsigned state = (a[0][Y] < b[0][Y])?0:1; + ret.boundary.reserve(a.size() + b.size()); + ConvexHull chs[2] = {a, b}; + unsigned idx = 0; + + for(unsigned k = 0; k < bpair.size(); k++) { + unsigned limit = idx_to_pair(bpair[k], state); + std::cout << bpair[k].first << " , " << bpair[k].second << "; " + << idx << ", " << limit << ", s: " + << state + << " \n"; + while(idx <= limit) { + ret.boundary.push_back(chs[state][idx++]); + } + state = 1-state; + idx = idx_to_pair(bpair[k], state); + } + while(idx < chs[state].size()) { + ret.boundary.push_back(chs[state][idx++]); + } + return ret; +} + +ConvexHull graham_merge(ConvexHull a, ConvexHull b) { + ConvexHull result; + + // we can avoid the find pivot step because of top_point_first + if(b.boundary[0] <= a.boundary[0]) + swap(a, b); + + result.boundary = a.boundary; + result.boundary.insert(result.boundary.end(), + b.boundary.begin(), b.boundary.end()); + +/** if we modified graham scan to work top to bottom as proposed in lect754.pdf we could replace the + angle sort with a simple merge sort type algorithm. furthermore, we could do the graham scan + online, avoiding a bunch of memory copies. That would probably be linear. -- njh*/ + result.angle_sort(); + result.graham_scan(); + + return result; +} + +ConvexHull andrew_merge(ConvexHull a, ConvexHull b) { + ConvexHull result; + + // we can avoid the find pivot step because of top_point_first + if(b.boundary[0] <= a.boundary[0]) + swap(a, b); + + result.boundary = a.boundary; + result.boundary.insert(result.boundary.end(), + b.boundary.begin(), b.boundary.end()); + +/** if we modified graham scan to work top to bottom as proposed in lect754.pdf we could replace the + angle sort with a simple merge sort type algorithm. furthermore, we could do the graham scan + online, avoiding a bunch of memory copies. That would probably be linear. -- njh*/ + result.andrew_scan(); + + return result; +} + +//TODO: reinstate +/*ConvexCover::ConvexCover(Path const &sp) : path(&sp) { + cc.reserve(sp.size()); + for(Geom::Path::const_iterator it(sp.begin()), end(sp.end()); it != end; ++it) { + cc.push_back(ConvexHull((*it).begin(), (*it).end())); + } +}*/ + +double ConvexHull::centroid_and_area(Geom::Point& centroid) const { + const unsigned n = boundary.size(); + if (n < 2) + return 0; + if(n < 3) { + centroid = (boundary[0] + boundary[1])/2; + return 0; + } + Geom::Point centroid_tmp(0,0); + double atmp = 0; + for (unsigned i = n-1, j = 0; j < n; i = j, j++) { + const double ai = cross(boundary[j], boundary[i]); + atmp += ai; + centroid_tmp += (boundary[j] + boundary[i])*ai; // first moment. + } + if (atmp != 0) { + centroid = centroid_tmp / (3 * atmp); + } + return atmp / 2; +} + +// TODO: This can be made lg(n) using golden section/fibonacci search three starting points, say 0, +// n/2, n-1 construct a new point, say (n/2 + n)/2 throw away the furthest boundary point iterate +// until interval is a single value +Point const * ConvexHull::furthest(Point direction) const { + Point const * p = &boundary[0]; + double d = dot(*p, direction); + for(unsigned i = 1; i < boundary.size(); i++) { + double dd = dot(boundary[i], direction); + if(d < dd) { + p = &boundary[i]; + d = dd; + } + } + return p; +} + + +// returns (a, (b,c)), three points which define the narrowest diameter of the hull as the pair of +// lines going through b,c, and through a, parallel to b,c TODO: This can be made linear time by +// moving point tc incrementally from the previous value (it can only move in one direction). It +// is currently n*O(furthest) +double ConvexHull::narrowest_diameter(Point &a, Point &b, Point &c) { + Point tb = boundary.back(); + double d = std::numeric_limits<double>::max(); + for(unsigned i = 0; i < boundary.size(); i++) { + Point tc = boundary[i]; + Point n = -rot90(tb-tc); + Point ta = *furthest(n); + double td = dot(n, ta-tb)/dot(n,n); + if(td < d) { + a = ta; + b = tb; + c = tc; + d = td; + } + tb = tc; + } + return d; +} +#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/src/2geom/convex-hull.h b/src/2geom/convex-hull.h new file mode 100644 index 0000000..b4f0788 --- /dev/null +++ b/src/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 <boost/optional.hpp> +#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) { + boost::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(unsigned i = 0; i < in_cvx.size(); i++) { + out_file << in_cvx[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/src/2geom/coord.cpp b/src/2geom/coord.cpp new file mode 100644 index 0000000..29208d3 --- /dev/null +++ b/src/2geom/coord.cpp @@ -0,0 +1,123 @@ +/** @file + * @brief Conversion between Coord and strings + *//* + * 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. + */ + +// Most of the code in this file is derived from: +// https://code.google.com/p/double-conversion/ +// The copyright notice for that code is attached below. +// +// Copyright 2010 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <2geom/coord.h> +#include <cstdint> +#include <cstdlib> +#include <cassert> +#include <cstring> +#include <climits> +#include <cstdarg> +#include <cmath> + +#include <double-conversion/double-conversion.h> + +namespace Geom { + +std::string format_coord_shortest(Coord x) +{ + static double_conversion::DoubleToStringConverter conv( + double_conversion::DoubleToStringConverter::UNIQUE_ZERO, + "inf", "NaN", 'e', -3, 6, 0, 0); + std::string ret(' ', 32); + double_conversion::StringBuilder builder(&ret[0], 32); + conv.ToShortest(x, &builder); + ret.resize(builder.position()); + return ret; +} + +std::string format_coord_nice(Coord x) +{ + static double_conversion::DoubleToStringConverter conv( + double_conversion::DoubleToStringConverter::UNIQUE_ZERO, + "inf", "NaN", 'e', -6, 21, 0, 0); + std::string ret(' ', 32); + double_conversion::StringBuilder builder(&ret[0], 32); + conv.ToShortest(x, &builder); + ret.resize(builder.position()); + return ret; +} + +Coord parse_coord(std::string const &s) +{ + static double_conversion::StringToDoubleConverter conv( + double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES | + double_conversion::StringToDoubleConverter::ALLOW_TRAILING_SPACES | + double_conversion::StringToDoubleConverter::ALLOW_SPACES_AFTER_SIGN, + 0.0, nan(""), "inf", "NaN"); + int dummy; + return conv.StringToDouble(s.c_str(), s.length(), &dummy); +} + +} // namespace Geom + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/coord.h b/src/2geom/coord.h new file mode 100644 index 0000000..9cc220d --- /dev/null +++ b/src/2geom/coord.h @@ -0,0 +1,214 @@ +/** @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 Dim2 other_dimension(Dim2 d) { return d == Y ? X : Y; } + +// TODO: make a smarter implementation with C++11 +template <typename T> +struct D2Traits { + typedef typename T::D1Value D1Value; + typedef typename T::D1Reference D1Reference; + typedef typename T::D1ConstReference D1ConstReference; +}; + +/** @brief Axis extraction functor. + * For use with things such as Boost's transform_iterator. + * @ingroup Utilities */ +template <Dim2 D, typename T> +struct GetAxis { + typedef typename D2Traits<T>::D1Value result_type; + typedef T argument_type; + typename D2Traits<T>::D1Value operator()(T const &a) const { + return a[D]; + } +}; + +/** @brief Floating point type used to store coordinates. + * @ingroup Primitives */ +typedef double Coord; + +/** @brief Type used for integral coordinates. + * @ingroup Primitives */ +typedef int IntCoord; + +/** @brief Default "acceptably small" value. + * @ingroup Primitives */ +const Coord EPSILON = 1e-6; //1e-18; + +/** @brief Get a value representing infinity. + * @ingroup Primitives */ +inline Coord infinity() { return std::numeric_limits<Coord>::infinity(); } + +/** @brief Nearness predicate for values. + * @ingroup Primitives */ +inline bool are_near(Coord a, Coord b, double eps=EPSILON) { return a-b <= eps && a-b >= -eps; } +inline bool rel_error_bound(Coord a, Coord b, double eps=EPSILON) { return a <= eps*b && a >= -eps*b; } + +/** @brief Numerically stable linear interpolation. + * @ingroup Primitives */ +inline 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 { + typedef D2<C> PointType; + typedef GenericInterval<C> IntervalType; + typedef GenericOptInterval<C> OptIntervalType; + typedef GenericRect<C> RectType; + typedef GenericOptRect<C> OptRectType; + + typedef + boost::equality_comparable< IntervalType + , boost::orable< IntervalType + > > + IntervalOps; + + typedef + boost::equality_comparable< RectType + , boost::orable< RectType + , boost::orable< RectType, OptRectType + > > > + RectOps; +}; + +// NOTE: operator helpers for Rect and Interval are defined here. +// This is to avoid increasing their size through multiple inheritance. + +template<> +struct CoordTraits<IntCoord> { + typedef IntPoint PointType; + typedef IntInterval IntervalType; + typedef OptIntInterval OptIntervalType; + typedef IntRect RectType; + typedef OptIntRect OptRectType; + + typedef + boost::equality_comparable< IntInterval + , boost::additive< IntInterval + , boost::additive< IntInterval, IntCoord + , boost::orable< IntInterval + > > > > + IntervalOps; + + typedef + boost::equality_comparable< IntRect + , boost::orable< IntRect + , boost::orable< IntRect, OptIntRect + , boost::additive< IntRect, IntPoint + > > > > + RectOps; +}; + +template<> +struct CoordTraits<Coord> { + typedef Point PointType; + typedef Interval IntervalType; + typedef OptInterval OptIntervalType; + typedef Rect RectType; + typedef OptRect OptRectType; + + typedef + boost::equality_comparable< Interval + , boost::equality_comparable< Interval, IntInterval + , boost::additive< Interval + , boost::multipliable< Interval + , boost::orable< Interval + , boost::arithmetic< Interval, Coord + > > > > > > + IntervalOps; + + typedef + boost::equality_comparable< Rect + , boost::equality_comparable< Rect, IntRect + , boost::orable< Rect + , boost::orable< Rect, OptRect + , boost::additive< Rect, Point + , boost::multipliable< Rect, Affine + > > > > > > + RectOps; +}; + +/** @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); + +} // end 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/src/2geom/crossing.cpp b/src/2geom/crossing.cpp new file mode 100644 index 0000000..e27a2fc --- /dev/null +++ b/src/2geom/crossing.cpp @@ -0,0 +1,233 @@ +#include <2geom/crossing.h> +#include <2geom/path.h> + +namespace Geom { + +//bool edge_involved_in(Edge const &e, Crossing const &c) { +// if(e.path == c.a) { +// if(e.time == c.ta) return true; +// } else if(e.path == c.b) { +// if(e.time == c.tb) return true; +// } +// return false; +//} + +double wrap_dist(double from, double to, double size, bool rev) { + if(rev) { + if(to > from) { + return from + (size - to); + } else { + return from - to; + } + } else { + if(to < from) { + return to + (size - from); + } else { + return to - from; + } + } +} +/* +CrossingGraph create_crossing_graph(PathVector const &p, Crossings const &crs) { + std::vector<Point> locs; + CrossingGraph ret; + for(unsigned i = 0; i < crs.size(); i++) { + Point pnt = p[crs[i].a].pointAt(crs[i].ta); + unsigned j = 0; + for(; j < locs.size(); j++) { + if(are_near(pnt, locs[j])) break; + } + if(j == locs.size()) { + ret.push_back(CrossingNode()); + locs.push_back(pnt); + } + ret[j].add_edge(Edge(crs[i].a, crs[i].ta, false)); + ret[j].add_edge(Edge(crs[i].a, crs[i].ta, true)); + ret[j].add_edge(Edge(crs[i].b, crs[i].tb, false)); + ret[j].add_edge(Edge(crs[i].b, crs[i].tb, true)); + } + + for(unsigned i = 0; i < ret.size(); i++) { + for(unsigned j = 0; j < ret[i].edges.size(); j++) { + unsigned pth = ret[i].edges[j].path; + double t = ret[i].edges[j].time; + bool rev = ret[i].edges[j].reverse; + double size = p[pth].size()+1; + double best = size; + unsigned bix = ret.size(); + for(unsigned k = 0; k < ret.size(); k++) { + for(unsigned l = 0; l < ret[k].edges.size(); l++) { + if(ret[i].edges[j].path == ret[k].edges[l].path && (k != i || l != j)) { + double d = wrap_dist(t, ret[i].edges[j].time, size, rev); + if(d < best) { + best = d; + bix = k; + } + } + } + } + if(bix == ret.size()) { + std::cout << "couldn't find an adequate next-crossing node"; + bix = i; + } + ret[i].edges[j].node = bix; + } + } + + return ret; + */ + /* Various incoherent code bits + // list of sets of edges, each set corresponding to those emanating from the path + CrossingGraph ret; + std::vector<Edge> edges(crs.size()); + + std::vector<std::vector<bool> > used; + unsigned i, j; + do { + first_false(used, i, j); + CrossingNode cn; + do { + unsigned di = i, dj = j; + crossing_dual(di, dj); + if(!used[di,dj]) { + + } + } + + } while(!used[i,j]) + + + for(unsigned j = 0; j < crs[i].size(); j++) { + + edges.push_back(Edge(i, crs[i][j].getOtherTime(i), false)); + edges.push_back(Edge(i, crs[i][j].getOtherTime(i), true)); + } + std::sort(edges.begin(), edges.end(), TimeOrder()); + for(unsigned j = 0; j < edges.size(); ) { + CrossingNode cn; + double t = edges[j].time; + while(j < edges.size() && are_near(edges[j].time, t)) { + cn.edges.push_back(edges[j]); + } + } +*/ +//} + +// 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) { + std::vector<Rect> rs; + for (unsigned i = 0; i < a.size_default(); i++) { + OptRect bb = a[i].boundsFast(); + if (bb) { + rs.push_back(*bb); + } + } + return rs; +} + +void merge_crossings(Crossings &a, Crossings &b, unsigned i) { + Crossings n; + sort_crossings(b, i); + n.resize(a.size() + b.size()); + std::merge(a.begin(), a.end(), b.begin(), b.end(), n.begin(), CrossingOrder(i)); + a = n; +} + +void offset_crossings(Crossings &cr, double a, double b) { + for(unsigned i = 0; i < cr.size(); i++) { + cr[i].ta += a; + cr[i].tb += b; + } +} + +Crossings reverse_ta(Crossings const &cr, std::vector<double> max) { + Crossings ret; + for(Crossings::const_iterator i = cr.begin(); i != cr.end(); ++i) { + double mx = max[i->a]; + ret.push_back(Crossing(i->ta > mx+0.01 ? (1 - (i->ta - mx) + mx) : mx - i->ta, + i->tb, !i->dir)); + } + return ret; +} + +Crossings reverse_tb(Crossings const &cr, unsigned split, std::vector<double> max) { + Crossings ret; + for(Crossings::const_iterator i = cr.begin(); i != cr.end(); ++i) { + double mx = max[i->b - split]; + ret.push_back(Crossing(i->ta, i->tb > mx+0.01 ? (1 - (i->tb - mx) + mx) : mx - i->tb, + !i->dir)); + } + return ret; +} + +CrossingSet reverse_ta(CrossingSet const &cr, unsigned split, std::vector<double> max) { + CrossingSet ret; + for(unsigned i = 0; i < cr.size(); i++) { + Crossings res = reverse_ta(cr[i], max); + if(i < split) std::reverse(res.begin(), res.end()); + ret.push_back(res); + } + return ret; +} + +CrossingSet reverse_tb(CrossingSet const &cr, unsigned split, std::vector<double> max) { + CrossingSet ret; + for(unsigned i = 0; i < cr.size(); i++) { + Crossings res = reverse_tb(cr[i], split, max); + if(i >= split) std::reverse(res.begin(), res.end()); + ret.push_back(res); + } + return ret; +} + +// Delete any duplicates in a vector of crossings +// A crossing is considered to be a duplicate when it has both t_a and t_b near to another crossing's t_a and t_b +// For example, duplicates will be found when calculating the intersections of a linesegment with a polygon, if the +// endpoint of that line coincides with a cusp node of the polygon. In that case, an intersection will be found of +// the linesegment with each of the polygon's linesegments extending from the cusp node (i.e. two intersections) +void delete_duplicates(Crossings &crs) { + Crossings::reverse_iterator rit = crs.rbegin(); + + for (rit = crs.rbegin(); rit!= crs.rend(); ++rit) { + Crossings::reverse_iterator rit2 = rit; + while (++rit2 != crs.rend()) { + if (Geom::are_near((*rit).ta, (*rit2).ta) && Geom::are_near((*rit).tb, (*rit2).tb)) { + crs.erase((rit + 1).base()); // This +1 and .base() construction is needed to convert to a regular iterator + break; // out of while loop, and continue with next iteration of for loop + } + } + } +} + +void clean(Crossings &/*cr_a*/, Crossings &/*cr_b*/) { +/* if(cr_a.empty()) return; + + //Remove anything with dupes + + for(Eraser<Crossings> i(&cr_a); !i.ended(); i++) { + const Crossing cur = *i; + Eraser<Crossings> next(i); + next++; + if(are_near(cur, *next)) { + cr_b.erase(std::find(cr_b.begin(), cr_b.end(), cur)); + for(i = next; near(*i, cur); i++) { + cr_b.erase(std::find(cr_b.begin(), cr_b.end(), *i)); + } + continue; + } + } +*/ +} + +} + +/* + 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/src/2geom/crossing.h b/src/2geom/crossing.h new file mode 100644 index 0000000..0f007b1 --- /dev/null +++ b/src/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 <boost/optional/optional.hpp> +#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 boost::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(unsigned k = 0; k < cr.size(); k++) { cr[k].a = i; cr[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/src/2geom/curve.cpp b/src/2geom/curve.cpp new file mode 100644 index 0000000..8ad0178 --- /dev/null +++ b/src/2geom/curve.cpp @@ -0,0 +1,187 @@ +/* Abstract curve type - implementation of default methods + * + * 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. + */ + +#include <2geom/curve.h> +#include <2geom/exception.h> +#include <2geom/nearest-time.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/ord.h> +#include <2geom/path-sink.h> + +//#include <iostream> + +namespace Geom +{ + +Coord Curve::nearestTime(Point const& p, Coord a, Coord b) const +{ + return nearest_time(p, toSBasis(), a, b); +} + +std::vector<Coord> Curve::allNearestTimes(Point const& p, Coord from, Coord to) const +{ + return all_nearest_times(p, toSBasis(), from, to); +} + +Coord Curve::length(Coord tolerance) const +{ + return ::Geom::length(toSBasis(), tolerance); +} + +int Curve::winding(Point const &p) const +{ + try { + std::vector<Coord> ts = roots(p[Y], Y); + if(ts.empty()) return 0; + std::sort(ts.begin(), ts.end()); + + // skip endpoint roots when they are local maxima on the Y axis + // this follows the convention used in other winding routines, + // i.e. that the bottommost coordinate is not part of the shape + bool ignore_0 = unitTangentAt(0)[Y] <= 0; + bool ignore_1 = unitTangentAt(1)[Y] >= 0; + + int wind = 0; + for (std::size_t i = 0; i < ts.size(); ++i) { + Coord t = ts[i]; + //std::cout << t << std::endl; + if ((t == 0 && ignore_0) || (t == 1 && ignore_1)) continue; + if (valueAt(t, X) > p[X]) { // root is ray intersection + Point tangent = unitTangentAt(t); + if (tangent[Y] > 0) { + // at the point of intersection, curve goes in +Y direction, + // so it winds in the direction of positive angles + ++wind; + } else if (tangent[Y] < 0) { + --wind; + } + } + } + return wind; + } catch (InfiniteSolutions const &e) { + // this means we encountered a line segment exactly coincident with the point + // skip, since this will be taken care of by endpoint roots in other segments + return 0; + } +} + +std::vector<CurveIntersection> Curve::intersect(Curve const &/*other*/, Coord /*eps*/) const +{ + // TODO: approximate as Bezier + THROW_NOTIMPLEMENTED(); +} + +std::vector<CurveIntersection> Curve::intersectSelf(Coord eps) const +{ + std::vector<CurveIntersection> result; + // Monotonic segments cannot have self-intersections. + // Thus, we can split the curve at roots and intersect the portions. + std::vector<Coord> splits; + std::unique_ptr<Curve> deriv(derivative()); + splits = deriv->roots(0, X); + if (splits.empty()) { + return result; + } + deriv.reset(); + splits.push_back(1.); + + boost::ptr_vector<Curve> parts; + Coord previous = 0; + for (unsigned i = 0; i < splits.size(); ++i) { + if (splits[i] == 0.) continue; + parts.push_back(portion(previous, splits[i])); + previous = splits[i]; + } + + Coord prev_i = 0; + for (unsigned i = 0; i < parts.size()-1; ++i) { + Interval dom_i(prev_i, splits[i]); + prev_i = splits[i]; + + Coord prev_j = 0; + for (unsigned j = i+1; j < parts.size(); ++j) { + Interval dom_j(prev_j, splits[j]); + prev_j = splits[j]; + + std::vector<CurveIntersection> xs = parts[i].intersect(parts[j], eps); + for (unsigned k = 0; k < xs.size(); ++k) { + // to avoid duplicated intersections, skip values at exactly 1 + if (xs[k].first == 1. || xs[k].second == 1.) continue; + + Coord ti = dom_i.valueAt(xs[k].first); + Coord tj = dom_j.valueAt(xs[k].second); + + CurveIntersection real(ti, tj, xs[k].point()); + result.push_back(real); + } + } + } + return result; +} + +Point Curve::unitTangentAt(Coord t, unsigned n) const +{ + std::vector<Point> derivs = pointAndDerivatives(t, n); + for (unsigned deriv_n = 1; deriv_n < derivs.size(); deriv_n++) { + Coord length = derivs[deriv_n].length(); + if ( ! are_near(length, 0) ) { + // length of derivative is non-zero, so return unit vector + return derivs[deriv_n] / length; + } + } + return Point (0,0); +}; + +void Curve::feed(PathSink &sink, bool moveto_initial) const +{ + std::vector<Point> pts; + sbasis_to_bezier(pts, toSBasis(), 2); //TODO: use something better! + if (moveto_initial) { + sink.moveTo(initialPoint()); + } + sink.curveTo(pts[0], pts[1], pts[2]); +} + +} // namespace Geom + +/* + 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/src/2geom/curve.h b/src/2geom/curve.h new file mode 100644 index 0000000..4470366 --- /dev/null +++ b/src/2geom/curve.h @@ -0,0 +1,369 @@ +/** + * \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 boundsExact() depending on the curve type. + * @return The smallest possible rectangle containing all of the curve's points. */ + virtual Rect boundsExact() 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/src/2geom/curves.h b/src/2geom/curves.h new file mode 100644 index 0000000..46fb6d9 --- /dev/null +++ b/src/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/src/2geom/d2-sbasis.cpp b/src/2geom/d2-sbasis.cpp new file mode 100644 index 0000000..07eccce --- /dev/null +++ b/src/2geom/d2-sbasis.cpp @@ -0,0 +1,364 @@ +/** + * \file + * \brief Some two-dimensional SBasis operations + *//* + * Authors: + * MenTaLguy <mental@rydia.net> + * Jean-François Barraud <jf.barraud@gmail.com> + * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl> + * + * Copyright 2007-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, 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. + * + */ + +#include <2geom/d2.h> +#include <2geom/piecewise.h> + +namespace Geom { + +SBasis L2(D2<SBasis> const & a, unsigned k) { return sqrt(dot(a, a), k); } + +D2<SBasis> multiply(Linear const & a, D2<SBasis> const & b) { + return D2<SBasis>(multiply(a, b[X]), multiply(a, b[Y])); +} + +D2<SBasis> multiply(SBasis const & a, D2<SBasis> const & b) { + return D2<SBasis>(multiply(a, b[X]), multiply(a, b[Y])); +} + +D2<SBasis> truncate(D2<SBasis> const & a, unsigned terms) { + return D2<SBasis>(truncate(a[X], terms), truncate(a[Y], terms)); +} + +unsigned sbasis_size(D2<SBasis> const & a) { + return std::max((unsigned) a[0].size(), (unsigned) a[1].size()); +} + +//TODO: Is this sensical? shouldn't it be like pythagorean or something? +double tail_error(D2<SBasis> const & a, unsigned tail) { + return std::max(a[0].tailError(tail), a[1].tailError(tail)); +} + +Piecewise<D2<SBasis> > sectionize(D2<Piecewise<SBasis> > const &a) { + Piecewise<SBasis> x = partition(a[0], a[1].cuts), y = partition(a[1], a[0].cuts); + assert(x.size() == y.size()); + Piecewise<D2<SBasis> > ret; + for(unsigned i = 0; i < x.size(); i++) + ret.push_seg(D2<SBasis>(x[i], y[i])); + ret.cuts.insert(ret.cuts.end(), x.cuts.begin(), x.cuts.end()); + return ret; +} + +D2<Piecewise<SBasis> > make_cuts_independent(Piecewise<D2<SBasis> > const &a) { + D2<Piecewise<SBasis> > ret; + for(unsigned d = 0; d < 2; d++) { + for(unsigned i = 0; i < a.size(); i++) + ret[d].push_seg(a[i][d]); + ret[d].cuts.insert(ret[d].cuts.end(), a.cuts.begin(), a.cuts.end()); + } + return ret; +} + +Piecewise<D2<SBasis> > rot90(Piecewise<D2<SBasis> > const &M){ + Piecewise<D2<SBasis> > result; + if (M.empty()) return M; + result.push_cut(M.cuts[0]); + for (unsigned i=0; i<M.size(); i++){ + result.push(rot90(M[i]),M.cuts[i+1]); + } + return result; +} + +/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b + * @return \f[ + * f(t) \rightarrow \left\{ + * \begin{array}{c} + * a_1 \bullet b_1 \\ + * a_2 \bullet b_2 \\ + * \ldots \\ + * a_n \bullet b_n \\ + * \end{array}\right. + * \f] + * @relates Piecewise */ +Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b) +{ + Piecewise<SBasis > result; + if (a.empty() || b.empty()) return result; + Piecewise<D2<SBasis> > aa = partition(a,b.cuts); + Piecewise<D2<SBasis> > bb = partition(b,a.cuts); + + result.push_cut(aa.cuts.front()); + for (unsigned i=0; i<aa.size(); i++){ + result.push(dot(aa.segs[i],bb.segs[i]),aa.cuts[i+1]); + } + return result; +} + +/** @brief Calculates the 'dot product' or 'inner product' of \c a and \c b + * @return \f[ + * f(t) \rightarrow \left\{ + * \begin{array}{c} + * a_1 \bullet b \\ + * a_2 \bullet b \\ + * \ldots \\ + * a_n \bullet b \\ + * \end{array}\right. + * \f] + * @relates Piecewise */ +Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Point const &b) +{ + Piecewise<SBasis > result; + if (a.empty()) return result; + + result.push_cut(a.cuts.front()); + for (unsigned i = 0; i < a.size(); ++i){ + result.push(dot(a.segs[i],b), a.cuts[i+1]); + } + return result; +} + + +Piecewise<SBasis> cross(Piecewise<D2<SBasis> > const &a, + Piecewise<D2<SBasis> > const &b){ + Piecewise<SBasis > result; + if (a.empty() || b.empty()) return result; + Piecewise<D2<SBasis> > aa = partition(a,b.cuts); + Piecewise<D2<SBasis> > bb = partition(b,a.cuts); + + result.push_cut(aa.cuts.front()); + for (unsigned i=0; i<a.size(); i++){ + result.push(cross(aa.segs[i],bb.segs[i]),aa.cuts[i+1]); + } + return result; +} + +Piecewise<D2<SBasis> > operator*(Piecewise<D2<SBasis> > const &a, Affine const &m) { + Piecewise<D2<SBasis> > result; + if(a.empty()) return result; + result.push_cut(a.cuts[0]); + for (unsigned i = 0; i < a.size(); i++) { + result.push(a[i] * m, a.cuts[i+1]); + } + return result; +} + +//if tol>0, only force continuity where the jump is smaller than tol. +Piecewise<D2<SBasis> > force_continuity(Piecewise<D2<SBasis> > const &f, double tol, bool closed) +{ + if (f.size()==0) return f; + Piecewise<D2<SBasis> > result=f; + unsigned cur = (closed)? 0:1; + unsigned prev = (closed)? f.size()-1:0; + while(cur<f.size()){ + Point pt0 = f.segs[prev].at1(); + Point pt1 = f.segs[cur ].at0(); + if (tol<=0 || L2sq(pt0-pt1)<tol*tol){ + pt0 = (pt0+pt1)/2; + for (unsigned dim=0; dim<2; dim++){ + SBasis &prev_sb=result.segs[prev][dim]; + SBasis &cur_sb =result.segs[cur][dim]; + Coord const c=pt0[dim]; + if (prev_sb.isZero(0)) { + prev_sb = SBasis(Linear(0.0, c)); + } else { + prev_sb[0][1] = c; + } + if (cur_sb.isZero(0)) { + cur_sb = SBasis(Linear(c, 0.0)); + } else { + cur_sb[0][0] = c; + } + } + } + prev = cur++; + } + return result; +} + +std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > +split_at_discontinuities (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwsbin, double tol) +{ + using namespace Geom; + std::vector<Piecewise<D2<SBasis> > > ret; + unsigned piece_start = 0; + for (unsigned i=0; i<pwsbin.segs.size(); i++){ + if (i==(pwsbin.segs.size()-1) || L2(pwsbin.segs[i].at1()- pwsbin.segs[i+1].at0()) > tol){ + Piecewise<D2<SBasis> > piece; + piece.cuts.push_back(pwsbin.cuts[piece_start]); + for (unsigned j = piece_start; j<i+1; j++){ + piece.segs.push_back(pwsbin.segs[j]); + piece.cuts.push_back(pwsbin.cuts[j+1]); + } + ret.push_back(piece); + piece_start = i+1; + } + } + return ret; +} + +Point unitTangentAt(D2<SBasis> const & a, Coord t, unsigned n) +{ + std::vector<Point> derivs = a.valueAndDerivatives(t, n); + for (unsigned deriv_n = 1; deriv_n < derivs.size(); deriv_n++) { + Coord length = derivs[deriv_n].length(); + if ( ! are_near(length, 0) ) { + // length of derivative is non-zero, so return unit vector + return derivs[deriv_n] / length; + } + } + return Point (0,0); +} + +static void set_first_point(Piecewise<D2<SBasis> > &f, Point const &a){ + if ( f.empty() ){ + f.concat(Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(Linear(a[X])), SBasis(Linear(a[Y]))))); + return; + } + for (unsigned dim=0; dim<2; dim++){ + f.segs.front()[dim][0][0] = a[dim]; + } +} +static void set_last_point(Piecewise<D2<SBasis> > &f, Point const &a){ + if ( f.empty() ){ + f.concat(Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(Linear(a[X])), SBasis(Linear(a[Y]))))); + return; + } + for (unsigned dim=0; dim<2; dim++){ + f.segs.back()[dim][0][1] = a[dim]; + } +} + +std::vector<Piecewise<D2<SBasis> > > fuse_nearby_ends(std::vector<Piecewise<D2<SBasis> > > const &f, double tol){ + + if ( f.empty()) return f; + std::vector<Piecewise<D2<SBasis> > > result; + std::vector<std::vector<unsigned> > pre_result; + for (unsigned i=0; i<f.size(); i++){ + bool inserted = false; + Point a = f[i].firstValue(); + Point b = f[i].lastValue(); + for (unsigned j=0; j<pre_result.size(); j++){ + Point aj = f.at(pre_result[j].back()).lastValue(); + Point bj = f.at(pre_result[j].front()).firstValue(); + if ( L2(a-aj) < tol ) { + pre_result[j].push_back(i); + inserted = true; + break; + } + if ( L2(b-bj) < tol ) { + pre_result[j].insert(pre_result[j].begin(),i); + inserted = true; + break; + } + } + if (!inserted) { + pre_result.push_back(std::vector<unsigned>()); + pre_result.back().push_back(i); + } + } + for (unsigned i=0; i<pre_result.size(); i++){ + Piecewise<D2<SBasis> > comp; + for (unsigned j=0; j<pre_result[i].size(); j++){ + Piecewise<D2<SBasis> > new_comp = f.at(pre_result[i][j]); + if ( j>0 ){ + set_first_point( new_comp, comp.segs.back().at1() ); + } + comp.concat(new_comp); + } + if ( L2(comp.firstValue()-comp.lastValue()) < tol ){ + //TODO: check sizes!!! + set_last_point( comp, comp.segs.front().at0() ); + } + result.push_back(comp); + } + return result; +} + +/* + * Computes the intersection of two sets given as (ordered) union of intervals. + */ +static std::vector<Interval> intersect( std::vector<Interval> const &a, std::vector<Interval> const &b){ + std::vector<Interval> result; + //TODO: use order! + for (unsigned i=0; i < a.size(); i++){ + for (unsigned j=0; j < b.size(); j++){ + OptInterval c( a[i] ); + c &= b[j]; + if ( c ) { + result.push_back( *c ); + } + } + } + return result; +} + +std::vector<Interval> level_set( D2<SBasis> const &f, Rect region){ + std::vector<Rect> regions( 1, region ); + return level_sets( f, regions ).front(); +} +std::vector<Interval> level_set( D2<SBasis> const &f, Point p, double tol){ + Rect region(p, p); + region.expandBy( tol ); + return level_set( f, region ); +} +std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector<Rect> regions){ + std::vector<Interval> regsX (regions.size(), Interval() ); + std::vector<Interval> regsY (regions.size(), Interval() ); + for ( unsigned i=0; i < regions.size(); i++ ){ + regsX[i] = regions[i][X]; + regsY[i] = regions[i][Y]; + } + std::vector<std::vector<Interval> > x_in_regs = level_sets( f[X], regsX ); + std::vector<std::vector<Interval> > y_in_regs = level_sets( f[Y], regsY ); + std::vector<std::vector<Interval> >result(regions.size(), std::vector<Interval>() ); + for (unsigned i=0; i<regions.size(); i++){ + result[i] = intersect ( x_in_regs[i], y_in_regs[i] ); + } + return result; +} +std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector<Point> pts, double tol){ + std::vector<Rect> regions( pts.size(), Rect() ); + for (unsigned i=0; i<pts.size(); i++){ + regions[i] = Rect( pts[i], pts[i] ); + regions[i].expandBy( tol ); + } + return level_sets( f, regions ); +} + + +} // namespace Geom + + +/* + 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/src/2geom/d2.h b/src/2geom/d2.h new file mode 100644 index 0000000..45f036b --- /dev/null +++ b/src/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/src/2geom/ellipse.cpp b/src/2geom/ellipse.cpp new file mode 100644 index 0000000..ad20623 --- /dev/null +++ b/src/2geom/ellipse.cpp @@ -0,0 +1,676 @@ +/** @file + * @brief Ellipse 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. + */ + +#include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +namespace Geom { + +Ellipse::Ellipse(Geom::Circle const &c) + : _center(c.center()) + , _rays(c.radius(), c.radius()) + , _angle(0) +{} + +void Ellipse::setCoefficients(double A, double B, double C, double D, double E, double F) +{ + double den = 4*A*C - B*B; + if (den == 0) { + THROW_RANGEERROR("den == 0, while computing ellipse centre"); + } + _center[X] = (B*E - 2*C*D) / den; + _center[Y] = (B*D - 2*A*E) / den; + + // evaluate the a coefficient of the ellipse equation in normal form + // E(x,y) = a*(x-cx)^2 + b*(x-cx)*(y-cy) + c*(y-cy)^2 = 1 + // where b = a*B , c = a*C, (cx,cy) == centre + double num = A * sqr(_center[X]) + + B * _center[X] * _center[Y] + + C * sqr(_center[Y]) + - F; + + + //evaluate ellipse rotation angle + _angle = std::atan2( -B, -(A - C) )/2; + + // evaluate the length of the ellipse rays + double sinrot, cosrot; + sincos(_angle, sinrot, cosrot); + double cos2 = cosrot * cosrot; + double sin2 = sinrot * sinrot; + double cossin = cosrot * sinrot; + + den = A * cos2 + B * cossin + C * sin2; + if (den == 0) { + THROW_RANGEERROR("den == 0, while computing 'rx' coefficient"); + } + double rx2 = num / den; + if (rx2 < 0) { + THROW_RANGEERROR("rx2 < 0, while computing 'rx' coefficient"); + } + _rays[X] = std::sqrt(rx2); + + den = C * cos2 - B * cossin + A * sin2; + if (den == 0) { + THROW_RANGEERROR("den == 0, while computing 'ry' coefficient"); + } + double ry2 = num / den; + if (ry2 < 0) { + THROW_RANGEERROR("ry2 < 0, while computing 'rx' coefficient"); + } + _rays[Y] = std::sqrt(ry2); + + // the solution is not unique so we choose always the ellipse + // with a rotation angle between 0 and PI/2 + makeCanonical(); +} + +Point Ellipse::initialPoint() const +{ + Coord sinrot, cosrot; + sincos(_angle, sinrot, cosrot); + Point p(ray(X) * cosrot + center(X), ray(X) * sinrot + center(Y)); + return p; +} + + +Affine Ellipse::unitCircleTransform() const +{ + Affine ret = Scale(ray(X), ray(Y)) * Rotate(_angle); + ret.setTranslation(center()); + return ret; +} + +Affine Ellipse::inverseUnitCircleTransform() const +{ + if (ray(X) == 0 || ray(Y) == 0) { + THROW_RANGEERROR("a degenerate ellipse doesn't have an inverse unit circle transform"); + } + Affine ret = Translate(-center()) * Rotate(-_angle) * Scale(1/ray(X), 1/ray(Y)); + return ret; +} + + +LineSegment Ellipse::axis(Dim2 d) const +{ + Point a(0, 0), b(0, 0); + a[d] = -1; + b[d] = 1; + LineSegment ls(a, b); + ls.transform(unitCircleTransform()); + return ls; +} + +LineSegment Ellipse::semiaxis(Dim2 d, int sign) const +{ + Point a(0, 0), b(0, 0); + b[d] = sgn(sign); + LineSegment ls(a, b); + ls.transform(unitCircleTransform()); + return ls; +} + +Rect Ellipse::boundsExact() const +{ + Angle extremes[2][2]; + double sinrot, cosrot; + sincos(_angle, sinrot, cosrot); + + extremes[X][0] = std::atan2( -ray(Y) * sinrot, ray(X) * cosrot ); + extremes[X][1] = extremes[X][0] + M_PI; + extremes[Y][0] = std::atan2( ray(Y) * cosrot, ray(X) * sinrot ); + extremes[Y][1] = extremes[Y][0] + M_PI; + + Rect result; + for (unsigned d = 0; d < 2; ++d) { + result[d] = Interval(valueAt(extremes[d][0], d ? Y : X), + valueAt(extremes[d][1], d ? Y : X)); + } + return result; +} + +std::vector<double> Ellipse::coefficients() const +{ + std::vector<double> c(6); + coefficients(c[0], c[1], c[2], c[3], c[4], c[5]); + return c; +} + +void Ellipse::coefficients(Coord &A, Coord &B, Coord &C, Coord &D, Coord &E, Coord &F) const +{ + if (ray(X) == 0 || ray(Y) == 0) { + THROW_RANGEERROR("a degenerate ellipse doesn't have an implicit form"); + } + + double cosrot, sinrot; + sincos(_angle, sinrot, cosrot); + double cos2 = cosrot * cosrot; + double sin2 = sinrot * sinrot; + double cossin = cosrot * sinrot; + double invrx2 = 1 / (ray(X) * ray(X)); + double invry2 = 1 / (ray(Y) * ray(Y)); + + A = invrx2 * cos2 + invry2 * sin2; + B = 2 * (invrx2 - invry2) * cossin; + C = invrx2 * sin2 + invry2 * cos2; + D = -2 * A * center(X) - B * center(Y); + E = -2 * C * center(Y) - B * center(X); + F = A * center(X) * center(X) + + B * center(X) * center(Y) + + C * center(Y) * center(Y) + - 1; +} + + +void Ellipse::fit(std::vector<Point> const &points) +{ + size_t sz = points.size(); + if (sz < 5) { + THROW_RANGEERROR("fitting error: too few points passed"); + } + NL::LFMEllipse model; + NL::least_squeares_fitter<NL::LFMEllipse> fitter(model, sz); + + for (size_t i = 0; i < sz; ++i) { + fitter.append(points[i]); + } + fitter.update(); + + NL::Vector z(sz, 0.0); + model.instance(*this, fitter.result(z)); +} + + +EllipticalArc * +Ellipse::arc(Point const &ip, Point const &inner, Point const &fp) +{ + // This is resistant to degenerate ellipses: + // both flags evaluate to false in that case. + + bool large_arc_flag = false; + bool sweep_flag = false; + + // Determination of large arc flag: + // large_arc is false when the inner point is on the same side + // of the center---initial point line as the final point, AND + // is on the same side of the center---final point line as the + // initial point. + // Additionally, large_arc is always false when we have exactly + // 1/2 of an arc, i.e. the cross product of the center -> initial point + // and center -> final point vectors is zero. + // Negating the above leads to the condition for large_arc being true. + Point fv = fp - _center; + Point iv = ip - _center; + Point innerv = inner - _center; + double ifcp = cross(fv, iv); + + if (ifcp != 0 && (sgn(cross(fv, innerv)) != sgn(ifcp) || + sgn(cross(iv, innerv)) != sgn(-ifcp))) + { + large_arc_flag = true; + } + + //cross(-iv, fv) && large_arc_flag + + + // Determination of sweep flag: + // For clarity, let's assume that Y grows up. Then the cross product + // is positive for points on the left side of a vector and negative + // on the right side of a vector. + // + // cross(?, v) > 0 + // o-------------------> + // cross(?, v) < 0 + // + // If the arc is small (large_arc_flag is false) and the final point + // is on the right side of the vector initial point -> center, + // we have to go in the direction of increasing angles + // (counter-clockwise) and the sweep flag is true. + // If the arc is large, the opposite is true, since we have to reach + // the final point going the long way - in the other direction. + // We can express this observation as: + // cross(_center - ip, fp - _center) < 0 xor large_arc flag + // This is equal to: + // cross(-iv, fv) < 0 xor large_arc flag + // But cross(-iv, fv) is equal to cross(fv, iv) due to antisymmetry + // of the cross product, so we end up with the condition below. + if ((ifcp < 0) ^ large_arc_flag) { + sweep_flag = true; + } + + EllipticalArc *ret_arc = new EllipticalArc(ip, ray(X), ray(Y), rotationAngle(), + large_arc_flag, sweep_flag, fp); + return ret_arc; +} + +Ellipse &Ellipse::operator*=(Rotate const &r) +{ + _angle += r.angle(); + _center *= r; + return *this; +} + +Ellipse &Ellipse::operator*=(Affine const& m) +{ + Affine a = Scale(ray(X), ray(Y)) * Rotate(_angle); + Affine mwot = m.withoutTranslation(); + Affine am = a * mwot; + Point new_center = _center * m; + + if (are_near(am.descrim(), 0)) { + double angle; + if (am[0] != 0) { + angle = std::atan2(am[2], am[0]); + } else if (am[1] != 0) { + angle = std::atan2(am[3], am[1]); + } else { + angle = M_PI/2; + } + Point v = Point::polar(angle) * am; + _center = new_center; + _rays[X] = L2(v); + _rays[Y] = 0; + _angle = atan2(v); + return *this; + } else if (mwot.isScale(0) && _angle.radians() == 0) { + _rays[X] = _rays[X] * mwot[0]; + _rays[Y] = _rays[Y] * mwot[3]; + _center = new_center; + return *this; + } + + std::vector<double> coeff = coefficients(); + Affine q( coeff[0], coeff[1]/2, + coeff[1]/2, coeff[2], + 0, 0 ); + + Affine invm = mwot.inverse(); + q = invm * q ; + std::swap(invm[1], invm[2]); + q *= invm; + setCoefficients(q[0], 2*q[1], q[3], 0, 0, -1); + _center = new_center; + + return *this; +} + +Ellipse Ellipse::canonicalForm() const +{ + Ellipse result(*this); + result.makeCanonical(); + return result; +} + +void Ellipse::makeCanonical() +{ + if (_rays[X] == _rays[Y]) { + _angle = 0; + return; + } + + if (_angle < 0) { + _angle += M_PI; + } + if (_angle >= M_PI/2) { + std::swap(_rays[X], _rays[Y]); + _angle -= M_PI/2; + } +} + +Point Ellipse::pointAt(Coord t) const +{ + Point p = Point::polar(t); + p *= unitCircleTransform(); + return p; +} + +Coord Ellipse::valueAt(Coord t, Dim2 d) const +{ + Coord sinrot, cosrot, cost, sint; + sincos(rotationAngle(), sinrot, cosrot); + sincos(t, sint, cost); + + if ( d == X ) { + return ray(X) * cosrot * cost + - ray(Y) * sinrot * sint + + center(X); + } else { + return ray(X) * sinrot * cost + + ray(Y) * cosrot * sint + + center(Y); + } +} + +Coord Ellipse::timeAt(Point const &p) const +{ + // degenerate ellipse is basically a reparametrized line segment + if (ray(X) == 0 || ray(Y) == 0) { + if (ray(X) != 0) { + return asin(Line(axis(X)).timeAt(p)); + } else if (ray(Y) != 0) { + return acos(Line(axis(Y)).timeAt(p)); + } else { + return 0; + } + } + Affine iuct = inverseUnitCircleTransform(); + return Angle(atan2(p * iuct)).radians0(); // return a value in [0, 2pi) +} + +Point Ellipse::unitTangentAt(Coord t) const +{ + Point p = Point::polar(t + M_PI/2); + p *= unitCircleTransform().withoutTranslation(); + p.normalize(); + return p; +} + +bool Ellipse::contains(Point const &p) const +{ + Point tp = p * inverseUnitCircleTransform(); + return tp.length() <= 1; +} + +std::vector<ShapeIntersection> Ellipse::intersect(Line const &line) const +{ + + std::vector<ShapeIntersection> result; + + if (line.isDegenerate()) return result; + if (ray(X) == 0 || ray(Y) == 0) { + // TODO intersect with line segment. + return result; + } + + // Ax^2 + Bxy + Cy^2 + Dx + Ey + F + Coord A, B, C, D, E, F; + coefficients(A, B, C, D, E, F); + Affine iuct = inverseUnitCircleTransform(); + + // generic case + Coord a, b, c; + line.coefficients(a, b, c); + Point lv = line.vector(); + + if (fabs(lv[X]) > fabs(lv[Y])) { + // y = -a/b x - c/b + Coord q = -a/b; + Coord r = -c/b; + + // substitute that into the ellipse equation, making it quadratic in x + Coord I = A + B*q + C*q*q; // x^2 terms + Coord J = B*r + C*2*q*r + D + E*q; // x^1 terms + Coord K = C*r*r + E*r + F; // x^0 terms + std::vector<Coord> xs = solve_quadratic(I, J, K); + + for (unsigned i = 0; i < xs.size(); ++i) { + Point p(xs[i], q*xs[i] + r); + result.push_back(ShapeIntersection(atan2(p * iuct), line.timeAt(p), p)); + } + } else { + Coord q = -b/a; + Coord r = -c/a; + + Coord I = A*q*q + B*q + C; + Coord J = A*2*q*r + B*r + D*q + E; + Coord K = A*r*r + D*r + F; + std::vector<Coord> xs = solve_quadratic(I, J, K); + + for (unsigned i = 0; i < xs.size(); ++i) { + Point p(q*xs[i] + r, xs[i]); + result.push_back(ShapeIntersection(atan2(p * iuct), line.timeAt(p), p)); + } + } + return result; +} + +std::vector<ShapeIntersection> Ellipse::intersect(LineSegment const &seg) const +{ + // we simply re-use the procedure for lines and filter out + // results where the line time value is outside of the unit interval. + std::vector<ShapeIntersection> result = intersect(Line(seg)); + filter_line_segment_intersections(result); + return result; +} + +std::vector<ShapeIntersection> Ellipse::intersect(Ellipse const &other) const +{ + // handle degenerate cases first + if (ray(X) == 0 || ray(Y) == 0) { + + } + // intersection of two ellipses can be solved analytically. + // http://maptools.home.comcast.net/~maptools/BivariateQuadratics.pdf + + Coord A, B, C, D, E, F; + Coord a, b, c, d, e, f; + + // NOTE: the order of coefficients is different to match the convention in the PDF above + // Ax^2 + Bx^2 + Cx + Dy + Exy + F + this->coefficients(A, E, B, C, D, F); + other.coefficients(a, e, b, c, d, f); + + // Assume that Q is the ellipse equation given by uppercase letters + // and R is the equation given by lowercase ones. An intersection exists when + // there is a coefficient mu such that + // mu Q + R = 0 + // + // This can be written in the following way: + // + // | ff cc/2 dd/2 | |1| + // mu Q + R = [1 x y] | cc/2 aa ee/2 | |x| = 0 + // | dd/2 ee/2 bb | |y| + // + // where aa = mu A + a and so on. The determinant can be explicitly written out, + // giving an equation which is cubic in mu and can be solved analytically. + + Coord I, J, K, L; + I = (-E*E*F + 4*A*B*F + C*D*E - A*D*D - B*C*C) / 4; + J = -((E*E - 4*A*B) * f + (2*E*F - C*D) * e + (2*A*D - C*E) * d + + (2*B*C - D*E) * c + (C*C - 4*A*F) * b + (D*D - 4*B*F) * a) / 4; + K = -((e*e - 4*a*b) * F + (2*e*f - c*d) * E + (2*a*d - c*e) * D + + (2*b*c - d*e) * C + (c*c - 4*a*f) * B + (d*d - 4*b*f) * A) / 4; + L = (-e*e*f + 4*a*b*f + c*d*e - a*d*d - b*c*c) / 4; + + std::vector<Coord> mus = solve_cubic(I, J, K, L); + Coord mu = infinity(); + std::vector<ShapeIntersection> result; + + // Now that we have solved for mu, we need to check whether the conic + // determined by mu Q + R is reducible to a product of two lines. If it's not, + // it means that there are no intersections. If it is, the intersections of these + // lines with the original ellipses (if there are any) give the coordinates + // of intersections. + + // Prefer middle root if there are three. + // Out of three possible pairs of lines that go through four points of intersection + // of two ellipses, this corresponds to cross-lines. These intersect the ellipses + // at less shallow angles than the other two options. + if (mus.size() == 3) { + std::swap(mus[1], mus[0]); + } + for (unsigned i = 0; i < mus.size(); ++i) { + Coord aa = mus[i] * A + a; + Coord bb = mus[i] * B + b; + Coord ee = mus[i] * E + e; + Coord delta = ee*ee - 4*aa*bb; + if (delta < 0) continue; + mu = mus[i]; + break; + } + + // if no suitable mu was found, there are no intersections + if (mu == infinity()) return result; + + Coord aa = mu * A + a; + Coord bb = mu * B + b; + Coord cc = mu * C + c; + Coord dd = mu * D + d; + Coord ee = mu * E + e; + Coord ff = mu * F + f; + + unsigned line_num = 0; + Line lines[2]; + + if (aa != 0) { + bb /= aa; cc /= aa; dd /= aa; ee /= aa; /*ff /= aa;*/ + Coord s = (ee + std::sqrt(ee*ee - 4*bb)) / 2; + Coord q = ee - s; + Coord alpha = (dd - cc*q) / (s - q); + Coord beta = cc - alpha; + + line_num = 2; + lines[0] = Line(1, q, alpha); + lines[1] = Line(1, s, beta); + } else if (bb != 0) { + cc /= bb; /*dd /= bb;*/ ee /= bb; ff /= bb; + Coord s = ee; + Coord q = 0; + Coord alpha = cc / ee; + Coord beta = ff * ee / cc; + + line_num = 2; + lines[0] = Line(q, 1, alpha); + lines[1] = Line(s, 1, beta); + } else if (ee != 0) { + line_num = 2; + lines[0] = Line(ee, 0, dd); + lines[1] = Line(0, 1, cc/ee); + } else if (cc != 0 || dd != 0) { + line_num = 1; + lines[0] = Line(cc, dd, ff); + } + + // intersect with the obtained lines and report intersections + for (unsigned li = 0; li < line_num; ++li) { + std::vector<ShapeIntersection> as = intersect(lines[li]); + std::vector<ShapeIntersection> bs = other.intersect(lines[li]); + + if (!as.empty() && as.size() == bs.size()) { + for (unsigned i = 0; i < as.size(); ++i) { + ShapeIntersection ix(as[i].first, bs[i].first, + middle_point(as[i].point(), bs[i].point())); + result.push_back(ix); + } + } + } + return result; +} + +std::vector<ShapeIntersection> Ellipse::intersect(D2<Bezier> const &b) const +{ + Coord A, B, C, D, E, F; + coefficients(A, B, C, D, E, F); + + Bezier x = A*b[X]*b[X] + B*b[X]*b[Y] + C*b[Y]*b[Y] + D*b[X] + E*b[Y] + F; + std::vector<Coord> r = x.roots(); + + std::vector<ShapeIntersection> result; + for (unsigned i = 0; i < r.size(); ++i) { + Point p = b.valueAt(r[i]); + result.push_back(ShapeIntersection(timeAt(p), r[i], p)); + } + return result; +} + +bool Ellipse::operator==(Ellipse const &other) const +{ + if (_center != other._center) return false; + + Ellipse a = this->canonicalForm(); + Ellipse b = other.canonicalForm(); + + if (a._rays != b._rays) return false; + if (a._angle != b._angle) return false; + + return true; +} + + +bool are_near(Ellipse const &a, Ellipse const &b, Coord precision) +{ + // We want to know whether no point on ellipse a is further than precision + // from the corresponding point on ellipse b. To check this, we compute + // the four extreme points at the end of each ray for each ellipse + // and check whether they are sufficiently close. + + // First, we need to correct the angles on the ellipses, so that they are + // no further than M_PI/4 apart. This can always be done by rotating + // and exchanging axes. + Ellipse ac = a, bc = b; + if (distance(ac.rotationAngle(), bc.rotationAngle()).radians0() >= M_PI/2) { + ac.setRotationAngle(ac.rotationAngle() + M_PI); + } + if (distance(ac.rotationAngle(), bc.rotationAngle()) >= M_PI/4) { + Angle d1 = distance(ac.rotationAngle() + M_PI/2, bc.rotationAngle()); + Angle d2 = distance(ac.rotationAngle() - M_PI/2, bc.rotationAngle()); + Coord adj = d1.radians0() < d2.radians0() ? M_PI/2 : -M_PI/2; + ac.setRotationAngle(ac.rotationAngle() + adj); + ac.setRays(ac.ray(Y), ac.ray(X)); + } + + // Do the actual comparison by computing four points on each ellipse. + Point tps[] = {Point(1,0), Point(0,1), Point(-1,0), Point(0,-1)}; + for (unsigned i = 0; i < 4; ++i) { + if (!are_near(tps[i] * ac.unitCircleTransform(), + tps[i] * bc.unitCircleTransform(), + precision)) + return false; + } + return true; +} + +std::ostream &operator<<(std::ostream &out, Ellipse const &e) +{ + out << "Ellipse(" << e.center() << ", " << e.rays() + << ", " << format_coord_nice(e.rotationAngle()) << ")"; + return out; +} + +} // end namespace Geom + + +/* + 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/src/2geom/ellipse.h b/src/2geom/ellipse.h new file mode 100644 index 0000000..f6089c9 --- /dev/null +++ b/src/2geom/ellipse.h @@ -0,0 +1,253 @@ +/** @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; + + /// 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/src/2geom/elliptical-arc-from-sbasis.cpp b/src/2geom/elliptical-arc-from-sbasis.cpp new file mode 100644 index 0000000..c536d89 --- /dev/null +++ b/src/2geom/elliptical-arc-from-sbasis.cpp @@ -0,0 +1,341 @@ +/** @file + * @brief Fitting elliptical arc to SBasis + * + * This file contains the implementation of the function arc_from_sbasis. + *//* + * 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. + */ + +#include <2geom/curve.h> +#include <2geom/angle.h> +#include <2geom/utils.h> +#include <2geom/bezier-curve.h> +#include <2geom/elliptical-arc.h> +#include <2geom/sbasis-curve.h> // for non-native methods +#include <2geom/numeric/vector.h> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> +#include <algorithm> + +namespace Geom { + +// forward declaration +namespace detail +{ + struct ellipse_equation; +} + +/* + * make_elliptical_arc + * + * convert a parametric polynomial curve given in symmetric power basis form + * into an EllipticalArc type; in order to be successful the input curve + * has to look like an actual elliptical arc even if a certain tolerance + * is allowed through an ad-hoc parameter. + * The conversion is performed through an interpolation on a certain amount of + * sample points computed on the input curve; + * the interpolation computes the coefficients of the general implicit equation + * of an ellipse (A*X^2 + B*XY + C*Y^2 + D*X + E*Y + F = 0), then from the + * implicit equation we compute the parametric form. + * + */ +class make_elliptical_arc +{ + public: + typedef D2<SBasis> curve_type; + + /* + * constructor + * + * it doesn't execute the conversion but set the input and output parameters + * + * _ea: the output EllipticalArc that will be generated; + * _curve: the input curve to be converted; + * _total_samples: the amount of sample points to be taken + * on the input curve for performing the conversion + * _tolerance: how much likelihood is required between the input curve + * and the generated elliptical arc; the smaller it is the + * the tolerance the higher it is the likelihood. + */ + make_elliptical_arc( EllipticalArc& _ea, + curve_type const& _curve, + unsigned int _total_samples, + double _tolerance ); + + private: + bool bound_exceeded( unsigned int k, detail::ellipse_equation const & ee, + double e1x, double e1y, double e2 ); + + bool check_bound(double A, double B, double C, double D, double E, double F); + + void fit(); + + bool make_elliptiarc(); + + void print_bound_error(unsigned int k) + { + std::cerr + << "tolerance error" << std::endl + << "at point: " << k << std::endl + << "error value: "<< dist_err << std::endl + << "bound: " << dist_bound << std::endl + << "angle error: " << angle_err + << " (" << angle_tol << ")" << std::endl; + } + + public: + /* + * perform the actual conversion + * return true if the conversion is successful, false on the contrary + */ + bool operator()() + { + // initialize the reference + const NL::Vector & coeff = fitter.result(); + fit(); + if ( !check_bound(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]) ) + return false; + if ( !(make_elliptiarc()) ) return false; + return true; + } + + private: + EllipticalArc& ea; // output elliptical arc + const curve_type & curve; // input curve + Piecewise<D2<SBasis> > dcurve; // derivative of the input curve + NL::LFMEllipse model; // model used for fitting + // perform the actual fitting task + NL::least_squeares_fitter<NL::LFMEllipse> fitter; + // tolerance: the user-defined tolerance parameter; + // tol_at_extr: the tolerance at end-points automatically computed + // on the value of "tolerance", and usually more strict; + // tol_at_center: tolerance at the center of the ellipse + // angle_tol: tolerance for the angle btw the input curve tangent + // versor and the ellipse normal versor at the sample points + double tolerance, tol_at_extr, tol_at_center, angle_tol; + Point initial_point, final_point; // initial and final end-points + unsigned int N; // total samples + unsigned int last; // N-1 + double partitions; // N-1 + std::vector<Point> p; // sample points + double dist_err, dist_bound, angle_err; +}; + +namespace detail +{ +/* + * ellipse_equation + * + * this is an helper struct, it provides two routines: + * the first one evaluates the implicit form of an ellipse on a given point + * the second one computes the normal versor at a given point of an ellipse + * in implicit form + */ +struct ellipse_equation +{ + ellipse_equation(double a, double b, double c, double d, double e, double f) + : A(a), B(b), C(c), D(d), E(e), F(f) + { + } + + double operator()(double x, double y) const + { + // A * x * x + B * x * y + C * y * y + D * x + E * y + F; + return (A * x + B * y + D) * x + (C * y + E) * y + F; + } + + double operator()(Point const& p) const + { + return (*this)(p[X], p[Y]); + } + + Point normal(double x, double y) const + { + Point n( 2 * A * x + B * y + D, 2 * C * y + B * x + E ); + return unit_vector(n); + } + + Point normal(Point const& p) const + { + return normal(p[X], p[Y]); + } + + double A, B, C, D, E, F; +}; + +} // end namespace detail + +make_elliptical_arc:: +make_elliptical_arc( EllipticalArc& _ea, + curve_type const& _curve, + unsigned int _total_samples, + double _tolerance ) + : ea(_ea), curve(_curve), + dcurve( unitVector(derivative(curve)) ), + model(), fitter(model, _total_samples), + tolerance(_tolerance), tol_at_extr(tolerance/2), + tol_at_center(0.1), angle_tol(0.1), + initial_point(curve.at0()), final_point(curve.at1()), + N(_total_samples), last(N-1), partitions(N-1), p(N) +{ +} + +/* + * check that the coefficients computed by the fit method satisfy + * the tolerance parameters at the k-th sample point + */ +bool +make_elliptical_arc:: +bound_exceeded( unsigned int k, detail::ellipse_equation const & ee, + double e1x, double e1y, double e2 ) +{ + dist_err = std::fabs( ee(p[k]) ); + dist_bound = std::fabs( e1x * p[k][X] + e1y * p[k][Y] + e2 ); + // check that the angle btw the tangent versor to the input curve + // and the normal versor of the elliptical arc, both evaluate + // at the k-th sample point, are really othogonal + angle_err = std::fabs( dot( dcurve(k/partitions), ee.normal(p[k]) ) ); + //angle_err *= angle_err; + return ( dist_err > dist_bound || angle_err > angle_tol ); +} + +/* + * check that the coefficients computed by the fit method satisfy + * the tolerance parameters at each sample point + */ +bool +make_elliptical_arc:: +check_bound(double A, double B, double C, double D, double E, double F) +{ + detail::ellipse_equation ee(A, B, C, D, E, F); + + // check error magnitude at the end-points + double e1x = (2*A + B) * tol_at_extr; + double e1y = (B + 2*C) * tol_at_extr; + double e2 = ((D + E) + (A + B + C) * tol_at_extr) * tol_at_extr; + if (bound_exceeded(0, ee, e1x, e1y, e2)) + { + print_bound_error(0); + return false; + } + if (bound_exceeded(0, ee, e1x, e1y, e2)) + { + print_bound_error(last); + return false; + } + + // e1x = derivative((ee(x,y), x) | x->tolerance, y->tolerance + e1x = (2*A + B) * tolerance; + // e1y = derivative((ee(x,y), y) | x->tolerance, y->tolerance + e1y = (B + 2*C) * tolerance; + // e2 = ee(tolerance, tolerance) - F; + e2 = ((D + E) + (A + B + C) * tolerance) * tolerance; +// std::cerr << "e1x = " << e1x << std::endl; +// std::cerr << "e1y = " << e1y << std::endl; +// std::cerr << "e2 = " << e2 << std::endl; + + // check error magnitude at sample points + for ( unsigned int k = 1; k < last; ++k ) + { + if ( bound_exceeded(k, ee, e1x, e1y, e2) ) + { + print_bound_error(k); + return false; + } + } + + return true; +} + +/* + * fit + * + * supply the samples to the fitter and compute + * the ellipse implicit equation coefficients + */ +void make_elliptical_arc::fit() +{ + for (unsigned int k = 0; k < N; ++k) + { + p[k] = curve( k / partitions ); + fitter.append(p[k]); + } + fitter.update(); + + NL::Vector z(N, 0.0); + fitter.result(z); +} + +bool make_elliptical_arc::make_elliptiarc() +{ + const NL::Vector & coeff = fitter.result(); + Ellipse e; + try + { + e.setCoefficients(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]); + } + catch(LogicalError const &exc) + { + return false; + } + + Point inner_point = curve(0.5); + + std::unique_ptr<EllipticalArc> arc( e.arc(initial_point, inner_point, final_point) ); + ea = *arc; + + if ( !are_near( e.center(), + ea.center(), + tol_at_center * std::min(e.ray(X),e.ray(Y)) + ) + ) + { + return false; + } + return true; +} + + + +bool arc_from_sbasis(EllipticalArc &ea, D2<SBasis> const &in, + double tolerance, unsigned num_samples) +{ + make_elliptical_arc convert(ea, in, num_samples, tolerance); + return convert(); +} + +} // end namespace Geom + +/* + 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/src/2geom/elliptical-arc.cpp b/src/2geom/elliptical-arc.cpp new file mode 100644 index 0000000..3603f80 --- /dev/null +++ b/src/2geom/elliptical-arc.cpp @@ -0,0 +1,946 @@ +/* + * SVG Elliptical Arc Class + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@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. + */ + +#include <cfloat> +#include <limits> +#include <memory> + +#include <2geom/bezier-curve.h> +#include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> +#include <2geom/path-sink.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/transforms.h> +#include <2geom/utils.h> + +#include <2geom/numeric/vector.h> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +namespace Geom +{ + +/** + * @class EllipticalArc + * @brief Elliptical arc curve + * + * Elliptical arc is a curve taking the shape of a section of an ellipse. + * + * The arc function has two forms: the regular one, mapping the unit interval to points + * on 2D plane (the linear domain), and a second form that maps some interval + * \f$A \subseteq [0,2\pi)\f$ to the same points (the angular domain). The interval \f$A\f$ + * determines which part of the ellipse forms the arc. The arc is said to contain an angle + * if its angular domain includes that angle (and therefore it is defined for that angle). + * + * The angular domain considers each ellipse to be + * a rotated, scaled and translated unit circle: 0 corresponds to \f$(1,0)\f$ on the unit circle, + * \f$\pi/2\f$ corresponds to \f$(0,1)\f$, \f$\pi\f$ to \f$(-1,0)\f$ and \f$3\pi/2\f$ + * to \f$(0,-1)\f$. After the angle is mapped to a point from a unit circle, the point is + * transformed using a matrix of this form + * \f[ M = \left[ \begin{array}{ccc} + r_X \cos(\theta) & -r_Y \sin(\theta) & 0 \\ + r_X \sin(\theta) & r_Y \cos(\theta) & 0 \\ + c_X & c_Y & 1 \end{array} \right] \f] + * where \f$r_X, r_Y\f$ are the X and Y rays of the ellipse, \f$\theta\f$ is its angle of rotation, + * and \f$c_X, c_Y\f$ the coordinates of the ellipse's center - thus mapping the angle + * to some point on the ellipse. Note that for example the point at angluar coordinate 0, + * the center and the point at angular coordinate \f$\pi/4\f$ do not necessarily + * create an angle of \f$\pi/4\f$ radians; it is only the case if both axes of the ellipse + * are of the same length (i.e. it is a circle). + * + * @image html ellipse-angular-coordinates.png "An illustration of the angular domain" + * + * Each arc is defined by five variables: The initial and final point, the ellipse's rays, + * and the ellipse's rotation. Each set of those parameters corresponds to four different arcs, + * with two of them larger than half an ellipse and two of them turning clockwise while traveling + * from initial to final point. The two flags disambiguate between them: "large arc flag" selects + * the bigger arc, while the "sweep flag" selects the arc going in the direction of positive + * angles. Angles always increase when going from the +X axis in the direction of the +Y axis, + * so if Y grows downwards, this means clockwise. + * + * @image html elliptical-arc-flags.png "Meaning of arc flags (Y grows downwards)" + * + * @ingroup Curves + */ + + +/** @brief Compute bounds of an elliptical arc. + * The bounds computation works as follows. The extreme X and Y points + * are either the endpoints or local minima / maxima of the ellipse. + * We already have endpoints, and we can find the local extremes + * by computing a partial derivative with respect to the angle + * and equating that to zero: + * \f{align*}{ + x &= r_x \cos \varphi \cos \theta - r_y \sin \varphi \sin \theta + c_x \\ + \frac{\partial x}{\partial \theta} &= -r_x \cos \varphi \sin \theta - r_y \sin \varphi \cos \theta = 0 \\ + \frac{\sin \theta}{\cos \theta} &= \tan\theta = -\frac{r_y \sin \varphi}{r_x \cos \varphi} \\ + \theta &= \tan^{-1} \frac{-r_y \sin \varphi}{r_x \cos \varphi} + \f} + * The local extremes correspond to two angles separated by \f$\pi\f$. + * Once we compute these angles, we check whether they belong to the arc, + * and if they do, we evaluate the ellipse at these angles. + * The bounding box of the arc is equal to the bounding box of the endpoints + * and the local extrema that belong to the arc. + */ +Rect EllipticalArc::boundsExact() const +{ + if (isChord()) { + return chord().boundsExact(); + } + + Coord coord[2][4] = { + { _initial_point[X], _final_point[X], 0, 0 }, + { _initial_point[Y], _final_point[Y], 0, 0 } + }; + int ncoord[2] = { 2, 2 }; + + Angle extremes[2][2]; + double sinrot, cosrot; + sincos(rotationAngle(), sinrot, cosrot); + + extremes[X][0] = std::atan2( -ray(Y) * sinrot, ray(X) * cosrot ); + extremes[X][1] = extremes[X][0] + M_PI; + extremes[Y][0] = std::atan2( ray(Y) * cosrot, ray(X) * sinrot ); + extremes[Y][1] = extremes[Y][0] + M_PI; + + for (unsigned d = 0; d < 2; ++d) { + for (unsigned i = 0; i < 2; ++i) { + if (containsAngle(extremes[d][i])) { + coord[d][ncoord[d]++] = valueAtAngle(extremes[d][i], d ? Y : X); + } + } + } + + Interval xival = Interval::from_range(coord[X], coord[X] + ncoord[X]); + Interval yival = Interval::from_range(coord[Y], coord[Y] + ncoord[Y]); + Rect result(xival, yival); + return result; +} + + +Point EllipticalArc::pointAtAngle(Coord t) const +{ + Point ret = _ellipse.pointAt(t); + return ret; +} + +Coord EllipticalArc::valueAtAngle(Coord t, Dim2 d) const +{ + return _ellipse.valueAt(t, d); +} + +std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const +{ + std::vector<Coord> sol; + + if (isChord()) { + sol = chord().roots(v, d); + return sol; + } + + Interval unit_interval(0, 1); + + double rotx, roty; + if (d == X) { + sincos(rotationAngle(), roty, rotx); + roty = -roty; + } else { + sincos(rotationAngle(), rotx, roty); + } + + double rxrotx = ray(X) * rotx; + double c_v = center(d) - v; + + double a = -rxrotx + c_v; + double b = ray(Y) * roty; + double c = rxrotx + c_v; + //std::cerr << "a = " << a << std::endl; + //std::cerr << "b = " << b << std::endl; + //std::cerr << "c = " << c << std::endl; + + if (a == 0) + { + sol.push_back(M_PI); + if (b != 0) + { + double s = 2 * std::atan(-c/(2*b)); + if ( s < 0 ) s += 2*M_PI; + sol.push_back(s); + } + } + else + { + double delta = b * b - a * c; + //std::cerr << "delta = " << delta << std::endl; + if (delta == 0) { + double s = 2 * std::atan(-b/a); + if ( s < 0 ) s += 2*M_PI; + sol.push_back(s); + } + else if ( delta > 0 ) + { + double sq = std::sqrt(delta); + double s = 2 * std::atan( (-b - sq) / a ); + if ( s < 0 ) s += 2*M_PI; + sol.push_back(s); + s = 2 * std::atan( (-b + sq) / a ); + if ( s < 0 ) s += 2*M_PI; + sol.push_back(s); + } + } + + std::vector<double> arc_sol; + for (unsigned int i = 0; i < sol.size(); ++i ) { + //std::cerr << "s = " << deg_from_rad(sol[i]); + sol[i] = timeAtAngle(sol[i]); + //std::cerr << " -> t: " << sol[i] << std::endl; + if (unit_interval.contains(sol[i])) { + arc_sol.push_back(sol[i]); + } + } + return arc_sol; +} + + +// D(E(t,C),t) = E(t+PI/2,O), where C is the ellipse center +// the derivative doesn't rotate the ellipse but there is a translation +// of the parameter t by an angle of PI/2 so the ellipse points are shifted +// of such an angle in the cw direction +Curve *EllipticalArc::derivative() const +{ + if (isChord()) { + return chord().derivative(); + } + + EllipticalArc *result = static_cast<EllipticalArc*>(duplicate()); + result->_ellipse.setCenter(0, 0); + result->_angles.setInitial(result->_angles.initialAngle() + M_PI/2); + result->_angles.setFinal(result->_angles.finalAngle() + M_PI/2); + result->_initial_point = result->pointAtAngle( result->initialAngle() ); + result->_final_point = result->pointAtAngle( result->finalAngle() ); + return result; +} + + +std::vector<Point> +EllipticalArc::pointAndDerivatives(Coord t, unsigned int n) const +{ + if (isChord()) { + return chord().pointAndDerivatives(t, n); + } + + unsigned int nn = n+1; // nn represents the size of the result vector. + std::vector<Point> result; + result.reserve(nn); + double angle = angleAt(t); + std::unique_ptr<EllipticalArc> ea( static_cast<EllipticalArc*>(duplicate()) ); + ea->_ellipse.setCenter(0, 0); + unsigned int m = std::min(nn, 4u); + for ( unsigned int i = 0; i < m; ++i ) + { + result.push_back( ea->pointAtAngle(angle) ); + angle += (sweep() ? M_PI/2 : -M_PI/2); + if ( !(angle < 2*M_PI) ) angle -= 2*M_PI; + } + m = nn / 4; + for ( unsigned int i = 1; i < m; ++i ) + { + for ( unsigned int j = 0; j < 4; ++j ) + result.push_back( result[j] ); + } + m = nn - 4 * m; + for ( unsigned int i = 0; i < m; ++i ) + { + result.push_back( result[i] ); + } + if ( !result.empty() ) // nn != 0 + result[0] = pointAtAngle(angle); + return result; +} + +Point EllipticalArc::pointAt(Coord t) const +{ + if (isChord()) return chord().pointAt(t); + return _ellipse.pointAt(angleAt(t)); +} + +Coord EllipticalArc::valueAt(Coord t, Dim2 d) const +{ + if (isChord()) return chord().valueAt(t, d); + return valueAtAngle(angleAt(t), d); +} + +Curve* EllipticalArc::portion(double f, double t) const +{ + // fix input arguments + if (f < 0) f = 0; + if (f > 1) f = 1; + if (t < 0) t = 0; + if (t > 1) t = 1; + + if (f == t) { + EllipticalArc *arc = new EllipticalArc(); + arc->_initial_point = arc->_final_point = pointAt(f); + return arc; + } + + EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate()); + arc->_initial_point = pointAt(f); + arc->_final_point = pointAt(t); + arc->_angles.setAngles(angleAt(f), angleAt(t)); + if (f > t) arc->_angles.setSweep(!sweep()); + if ( _large_arc && fabs(angularExtent() * (t-f)) <= M_PI) { + arc->_large_arc = false; + } + return arc; +} + +// the arc is the same but traversed in the opposite direction +Curve *EllipticalArc::reverse() const +{ + using std::swap; + EllipticalArc *rarc = static_cast<EllipticalArc*>(duplicate()); + rarc->_angles.reverse(); + swap(rarc->_initial_point, rarc->_final_point); + return rarc; +} + +#ifdef HAVE_GSL // GSL is required for function "solve_reals" +std::vector<double> EllipticalArc::allNearestTimes( Point const& p, double from, double to ) const +{ + std::vector<double> result; + + if ( from > to ) std::swap(from, to); + if ( from < 0 || to > 1 ) + { + THROW_RANGEERROR("[from,to] interval out of range"); + } + + if ( ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) || are_near(from, to) ) + { + result.push_back(from); + return result; + } + else if ( are_near(ray(X), 0) || are_near(ray(Y), 0) ) + { + LineSegment seg(pointAt(from), pointAt(to)); + Point np = seg.pointAt( seg.nearestTime(p) ); + if ( are_near(ray(Y), 0) ) + { + if ( are_near(rotationAngle(), M_PI/2) + || are_near(rotationAngle(), 3*M_PI/2) ) + { + result = roots(np[Y], Y); + } + else + { + result = roots(np[X], X); + } + } + else + { + if ( are_near(rotationAngle(), M_PI/2) + || are_near(rotationAngle(), 3*M_PI/2) ) + { + result = roots(np[X], X); + } + else + { + result = roots(np[Y], Y); + } + } + return result; + } + else if ( are_near(ray(X), ray(Y)) ) + { + Point r = p - center(); + if ( are_near(r, Point(0,0)) ) + { + THROW_INFINITESOLUTIONS(0); + } + // TODO: implement case r != 0 +// Point np = ray(X) * unit_vector(r); +// std::vector<double> solX = roots(np[X],X); +// std::vector<double> solY = roots(np[Y],Y); +// double t; +// if ( are_near(solX[0], solY[0]) || are_near(solX[0], solY[1])) +// { +// t = solX[0]; +// } +// else +// { +// t = solX[1]; +// } +// if ( !(t < from || t > to) ) +// { +// result.push_back(t); +// } +// else +// { +// +// } + } + + // solve the equation <D(E(t),t)|E(t)-p> == 0 + // that provides min and max distance points + // on the ellipse E wrt the point p + // after the substitutions: + // cos(t) = (1 - s^2) / (1 + s^2) + // sin(t) = 2t / (1 + s^2) + // where s = tan(t/2) + // we get a 4th degree equation in s + /* + * ry s^4 ((-cy + py) Cos[Phi] + (cx - px) Sin[Phi]) + + * ry ((cy - py) Cos[Phi] + (-cx + px) Sin[Phi]) + + * 2 s^3 (rx^2 - ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) + + * 2 s (-rx^2 + ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) + */ + + Point p_c = p - center(); + double rx2_ry2 = (ray(X) - ray(Y)) * (ray(X) + ray(Y)); + double sinrot, cosrot; + sincos(rotationAngle(), sinrot, cosrot); + double expr1 = ray(X) * (p_c[X] * cosrot + p_c[Y] * sinrot); + Poly coeff; + coeff.resize(5); + coeff[4] = ray(Y) * ( p_c[Y] * cosrot - p_c[X] * sinrot ); + coeff[3] = 2 * ( rx2_ry2 + expr1 ); + coeff[2] = 0; + coeff[1] = 2 * ( -rx2_ry2 + expr1 ); + coeff[0] = -coeff[4]; + +// for ( unsigned int i = 0; i < 5; ++i ) +// std::cerr << "c[" << i << "] = " << coeff[i] << std::endl; + + std::vector<double> real_sol; + // gsl_poly_complex_solve raises an error + // if the leading coefficient is zero + if ( are_near(coeff[4], 0) ) + { + real_sol.push_back(0); + if ( !are_near(coeff[3], 0) ) + { + double sq = -coeff[1] / coeff[3]; + if ( sq > 0 ) + { + double s = std::sqrt(sq); + real_sol.push_back(s); + real_sol.push_back(-s); + } + } + } + else + { + real_sol = solve_reals(coeff); + } + + for ( unsigned int i = 0; i < real_sol.size(); ++i ) + { + real_sol[i] = 2 * std::atan(real_sol[i]); + if ( real_sol[i] < 0 ) real_sol[i] += 2*M_PI; + } + // when s -> Infinity then <D(E)|E-p> -> 0 iff coeff[4] == 0 + // so we add M_PI to the solutions being lim arctan(s) = PI when s->Infinity + if ( (real_sol.size() % 2) != 0 ) + { + real_sol.push_back(M_PI); + } + + double mindistsq1 = std::numeric_limits<double>::max(); + double mindistsq2 = std::numeric_limits<double>::max(); + double dsq = 0; + unsigned int mi1 = 0, mi2 = 0; + for ( unsigned int i = 0; i < real_sol.size(); ++i ) + { + dsq = distanceSq(p, pointAtAngle(real_sol[i])); + if ( mindistsq1 > dsq ) + { + mindistsq2 = mindistsq1; + mi2 = mi1; + mindistsq1 = dsq; + mi1 = i; + } + else if ( mindistsq2 > dsq ) + { + mindistsq2 = dsq; + mi2 = i; + } + } + + double t = timeAtAngle(real_sol[mi1]); + if ( !(t < from || t > to) ) + { + result.push_back(t); + } + + bool second_sol = false; + t = timeAtAngle(real_sol[mi2]); + if ( real_sol.size() == 4 && !(t < from || t > to) ) + { + if ( result.empty() || are_near(mindistsq1, mindistsq2) ) + { + result.push_back(t); + second_sol = true; + } + } + + // we need to test extreme points too + double dsq1 = distanceSq(p, pointAt(from)); + double dsq2 = distanceSq(p, pointAt(to)); + if ( second_sol ) + { + if ( mindistsq2 > dsq1 ) + { + result.clear(); + result.push_back(from); + mindistsq2 = dsq1; + } + else if ( are_near(mindistsq2, dsq) ) + { + result.push_back(from); + } + if ( mindistsq2 > dsq2 ) + { + result.clear(); + result.push_back(to); + } + else if ( are_near(mindistsq2, dsq2) ) + { + result.push_back(to); + } + + } + else + { + if ( result.empty() ) + { + if ( are_near(dsq1, dsq2) ) + { + result.push_back(from); + result.push_back(to); + } + else if ( dsq2 > dsq1 ) + { + result.push_back(from); + } + else + { + result.push_back(to); + } + } + } + + return result; +} +#endif + + +void EllipticalArc::_filterIntersections(std::vector<ShapeIntersection> &xs, bool is_first) const +{ + Interval unit(0, 1); + std::vector<ShapeIntersection>::reverse_iterator i = xs.rbegin(), last = xs.rend(); + while (i != last) { + Coord &t = is_first ? i->first : i->second; + assert(are_near(_ellipse.pointAt(t), i->point(), 1e-5)); + t = timeAtAngle(t); + if (!unit.contains(t)) { + xs.erase((++i).base()); + continue; + } else { + assert(are_near(pointAt(t), i->point(), 1e-5)); + ++i; + } + } +} + +std::vector<CurveIntersection> EllipticalArc::intersect(Curve const &other, Coord eps) const +{ + if (isLineSegment()) { + LineSegment ls(_initial_point, _final_point); + return ls.intersect(other, eps); + } + + std::vector<CurveIntersection> result; + + if (other.isLineSegment()) { + LineSegment ls(other.initialPoint(), other.finalPoint()); + result = _ellipse.intersect(ls); + _filterIntersections(result, true); + return result; + } + + BezierCurve const *bez = dynamic_cast<BezierCurve const *>(&other); + if (bez) { + result = _ellipse.intersect(bez->fragment()); + _filterIntersections(result, true); + return result; + } + + EllipticalArc const *arc = dynamic_cast<EllipticalArc const *>(&other); + if (arc) { + result = _ellipse.intersect(arc->_ellipse); + _filterIntersections(result, true); + arc->_filterIntersections(result, false); + return result; + } + + // in case someone wants to make a custom curve type + result = other.intersect(*this, eps); + transpose_in_place(result); + return result; +} + + +void EllipticalArc::_updateCenterAndAngles() +{ + // See: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + Point d = initialPoint() - finalPoint(); + Point mid = middle_point(initialPoint(), finalPoint()); + + // if ip = sp, the arc contains no other points + if (initialPoint() == finalPoint()) { + _ellipse = Ellipse(); + _ellipse.setCenter(initialPoint()); + _angles = AngleInterval(); + _large_arc = false; + return; + } + + // rays should be positive + _ellipse.setRays(std::fabs(ray(X)), std::fabs(ray(Y))); + + if (isChord()) { + _ellipse.setRays(L2(d) / 2, 0); + _ellipse.setRotationAngle(atan2(d)); + _ellipse.setCenter(mid); + _angles.setAngles(0, M_PI); + _angles.setSweep(false); + _large_arc = false; + return; + } + + Rotate rot(rotationAngle()); // the matrix in F.6.5.3 + Rotate invrot = rot.inverse(); // the matrix in F.6.5.1 + + Point r = rays(); + Point p = (initialPoint() - mid) * invrot; // x', y' in F.6.5.1 + Point c(0,0); // cx', cy' in F.6.5.2 + + // Correct out-of-range radii + Coord lambda = hypot(p[X]/r[X], p[Y]/r[Y]); + if (lambda > 1) { + r *= lambda; + _ellipse.setRays(r); + _ellipse.setCenter(mid); + } else { + // evaluate F.6.5.2 + Coord rxry = r[X]*r[X] * r[Y]*r[Y]; + Coord pxry = p[X]*p[X] * r[Y]*r[Y]; + Coord rxpy = r[X]*r[X] * p[Y]*p[Y]; + Coord rad = (rxry - pxry - rxpy)/(rxpy + pxry); + // normally rad should never be negative, but numerical inaccuracy may cause this + if (rad > 0) { + rad = std::sqrt(rad); + if (sweep() == _large_arc) { + rad = -rad; + } + c = rad * Point(r[X]*p[Y]/r[Y], -r[Y]*p[X]/r[X]); + _ellipse.setCenter(c * rot + mid); + } else { + _ellipse.setCenter(mid); + } + } + + // Compute start and end angles. + // If the ellipse was enlarged, c will be zero - this is correct. + Point sp((p[X] - c[X]) / r[X], (p[Y] - c[Y]) / r[Y]); + Point ep((-p[X] - c[X]) / r[X], (-p[Y] - c[Y]) / r[Y]); + Point v(1, 0); + + _angles.setInitial(angle_between(v, sp)); + _angles.setFinal(angle_between(v, ep)); + + /*double sweep_angle = angle_between(sp, ep); + if (!sweep() && sweep_angle > 0) sweep_angle -= 2*M_PI; + if (sweep() && sweep_angle < 0) sweep_angle += 2*M_PI;*/ +} + +D2<SBasis> EllipticalArc::toSBasis() const +{ + if (isChord()) { + return chord().toSBasis(); + } + + D2<SBasis> arc; + // the interval of parametrization has to be [0,1] + Coord et = initialAngle().radians() + sweepAngle(); + Linear param(initialAngle().radians(), et); + Coord cosrot, sinrot; + sincos(rotationAngle(), sinrot, cosrot); + + // order = 4 seems to be enough to get a perfect looking elliptical arc + SBasis arc_x = ray(X) * cos(param,4); + SBasis arc_y = ray(Y) * sin(param,4); + arc[0] = arc_x * cosrot - arc_y * sinrot + Linear(center(X), center(X)); + arc[1] = arc_x * sinrot + arc_y * cosrot + Linear(center(Y), center(Y)); + + // ensure that endpoints remain exact + for ( int d = 0 ; d < 2 ; d++ ) { + arc[d][0][0] = initialPoint()[d]; + arc[d][0][1] = finalPoint()[d]; + } + + return arc; +} + +// All operations that do not contain skew can be evaluated +// without passing through the implicit form of the ellipse, +// which preserves precision. + +void EllipticalArc::operator*=(Translate const &tr) +{ + _initial_point *= tr; + _final_point *= tr; + _ellipse *= tr; +} + +void EllipticalArc::operator*=(Scale const &s) +{ + _initial_point *= s; + _final_point *= s; + _ellipse *= s; +} + +void EllipticalArc::operator*=(Rotate const &r) +{ + _initial_point *= r; + _final_point *= r; + _ellipse *= r; +} + +void EllipticalArc::operator*=(Zoom const &z) +{ + _initial_point *= z; + _final_point *= z; + _ellipse *= z; +} + +void EllipticalArc::operator*=(Affine const& m) +{ + if (isChord()) { + _initial_point *= m; + _final_point *= m; + _ellipse.setCenter(middle_point(_initial_point, _final_point)); + _ellipse.setRays(0, 0); + _ellipse.setRotationAngle(0); + return; + } + + _initial_point *= m; + _final_point *= m; + _ellipse *= m; + if (m.det() < 0) { + _angles.setSweep(!sweep()); + } + + // ellipse transformation does not preserve its functional form, + // i.e. e.pointAt(0.5)*m and (e*m).pointAt(0.5) can be different. + // We need to recompute start / end angles. + _angles.setInitial(_ellipse.timeAt(_initial_point)); + _angles.setFinal(_ellipse.timeAt(_final_point)); +} + +bool EllipticalArc::operator==(Curve const &c) const +{ + EllipticalArc const *other = dynamic_cast<EllipticalArc const *>(&c); + if (!other) return false; + if (_initial_point != other->_initial_point) return false; + if (_final_point != other->_final_point) return false; + // TODO: all arcs with ellipse rays which are too small + // and fall back to a line should probably be equal + if (rays() != other->rays()) return false; + if (rotationAngle() != other->rotationAngle()) return false; + if (_large_arc != other->_large_arc) return false; + if (sweep() != other->sweep()) return false; + return true; +} + +bool EllipticalArc::isNear(Curve const &c, Coord precision) const +{ + EllipticalArc const *other = dynamic_cast<EllipticalArc const *>(&c); + if (!other) { + if (isChord()) { + return c.isNear(chord(), precision); + } + return false; + } + + if (!are_near(_initial_point, other->_initial_point, precision)) return false; + if (!are_near(_final_point, other->_final_point, precision)) return false; + if (isChord() && other->isChord()) return true; + + if (sweep() != other->sweep()) return false; + if (!are_near(_ellipse, other->_ellipse, precision)) return false; + return true; +} + +void EllipticalArc::feed(PathSink &sink, bool moveto_initial) const +{ + if (moveto_initial) { + sink.moveTo(_initial_point); + } + sink.arcTo(ray(X), ray(Y), rotationAngle(), _large_arc, sweep(), _final_point); +} + +int EllipticalArc::winding(Point const &p) const +{ + using std::swap; + + double sinrot, cosrot; + sincos(rotationAngle(), sinrot, cosrot); + + Angle ymin_a = std::atan2( ray(Y) * cosrot, ray(X) * sinrot ); + Angle ymax_a = ymin_a + M_PI; + + Point ymin = pointAtAngle(ymin_a); + Point ymax = pointAtAngle(ymax_a); + if (ymin[Y] > ymax[Y]) { + swap(ymin, ymax); + swap(ymin_a, ymax_a); + } + + Interval yspan(ymin[Y], ymax[Y]); + if (!yspan.lowerContains(p[Y])) return 0; + + bool left = cross(ymax - ymin, p - ymin) > 0; + bool inside = _ellipse.contains(p); + bool includes_ymin = _angles.contains(ymin_a); + bool includes_ymax = _angles.contains(ymax_a); + + AngleInterval rarc(ymin_a, ymax_a, true), + larc(ymax_a, ymin_a, true); + + // we'll compute the result for an arc in the direction of increasing angles + // and then negate if necessary + Angle ia = initialAngle(), fa = finalAngle(); + Point ip = _initial_point, fp = _final_point; + if (!sweep()) { + swap(ia, fa); + swap(ip, fp); + } + + bool initial_left = larc.contains(ia); + bool initial_right = !initial_left; // rarc.contains(ia); + bool final_left = larc.contains(fa); + bool final_right = !final_left; // rarc.contains(fa); + + int result = 0; + if (inside || left) { + if (includes_ymin && final_right) { + Interval ival(ymin[Y], fp[Y]); + if (ival.lowerContains(p[Y])) { + ++result; + } + } + if (initial_right && final_right && !largeArc()) { + Interval ival(ip[Y], fp[Y]); + if (ival.lowerContains(p[Y])) { + ++result; + } + } + if (initial_right && includes_ymax) { + Interval ival(ip[Y], ymax[Y]); + if (ival.lowerContains(p[Y])) { + ++result; + } + } + if (!initial_right && !final_right && includes_ymin && includes_ymax) { + Interval ival(ymax[Y], ymin[Y]); + if (ival.lowerContains(p[Y])) { + ++result; + } + } + } + if (left && !inside) { + if (includes_ymin && initial_left) { + Interval ival(ymin[Y], ip[Y]); + if (ival.lowerContains(p[Y])) { + --result; + } + } + if (initial_left && final_left && !largeArc()) { + Interval ival(ip[Y], fp[Y]); + if (ival.lowerContains(p[Y])) { + --result; + } + } + if (final_left && includes_ymax) { + Interval ival(fp[Y], ymax[Y]); + if (ival.lowerContains(p[Y])) { + --result; + } + } + if (!initial_left && !final_left && includes_ymin && includes_ymax) { + Interval ival(ymax[Y], ymin[Y]); + if (ival.lowerContains(p[Y])) { + --result; + } + } + } + return sweep() ? result : -result; +} + +std::ostream &operator<<(std::ostream &out, EllipticalArc const &ea) +{ + out << "EllipticalArc(" + << ea.initialPoint() << ", " + << format_coord_nice(ea.ray(X)) << ", " << format_coord_nice(ea.ray(Y)) << ", " + << format_coord_nice(ea.rotationAngle()) << ", " + << "large_arc=" << (ea.largeArc() ? "true" : "false") << ", " + << "sweep=" << (ea.sweep() ? "true" : "false") << ", " + << ea.finalPoint() << ")"; + return out; +} + +} // end namespace Geom + +/* + 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/src/2geom/elliptical-arc.h b/src/2geom/elliptical-arc.h new file mode 100644 index 0000000..d0f9db9 --- /dev/null +++ b/src/2geom/elliptical-arc.h @@ -0,0 +1,341 @@ +/** + * \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$. + virtual Point pointAt(Coord t) const; + + /// Evaluate a single coordinate on the arc in the curve domain. + virtual Coord valueAt(Coord t, Dim2 d) const; + + /** @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 + virtual Point initialPoint() const { return _initial_point; } + virtual Point finalPoint() const { return _final_point; } + virtual Curve* duplicate() const { return new EllipticalArc(*this); } + virtual void setInitial(Point const &p) { + _initial_point = p; + _updateCenterAndAngles(); + } + virtual void setFinal(Point const &p) { + _final_point = p; + _updateCenterAndAngles(); + } + virtual bool isDegenerate() const { + return _initial_point == _final_point; + } + virtual bool isLineSegment() const { return isChord(); } + virtual Rect boundsFast() const { + return boundsExact(); + } + virtual Rect boundsExact() const; + // TODO: native implementation of the following methods + virtual OptRect boundsLocal(OptInterval const &i, unsigned int deg) const { + return SBasisCurve(toSBasis()).boundsLocal(i, deg); + } + virtual std::vector<double> roots(double v, Dim2 d) const; +#ifdef HAVE_GSL + virtual std::vector<double> allNearestTimes( Point const& p, double from = 0, double to = 1 ) const; + virtual double nearestTime( Point const& p, double from = 0, double to = 1 ) const { + if ( are_near(ray(X), ray(Y)) && are_near(center(), p) ) { + return from; + } + return allNearestTimes(p, from, to).front(); + } +#endif + virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps=EPSILON) const; + virtual int degreesOfFreedom() const { return 7; } + virtual Curve *derivative() const; + + using Curve::operator*=; + virtual void operator*=(Translate const &tr); + virtual void operator*=(Scale const &s); + virtual void operator*=(Rotate const &r); + virtual void operator*=(Zoom const &z); + virtual void operator*=(Affine const &m); + + virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned int n) const; + virtual D2<SBasis> toSBasis() const; + virtual Curve *portion(double f, double t) const; + virtual Curve *reverse() const; + virtual bool operator==(Curve const &c) const; + virtual bool isNear(Curve const &other, Coord precision) const; + virtual void feed(PathSink &sink, bool moveto_initial) const; + virtual int winding(Point const &p) const; + +private: + void _updateCenterAndAngles(); + void _filterIntersections(std::vector<ShapeIntersection> &xs, bool is_first) 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/src/2geom/exception.h b/src/2geom/exception.h new file mode 100644 index 0000000..ab6f821 --- /dev/null +++ b/src/2geom/exception.h @@ -0,0 +1,145 @@ +/** + * \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(); + } + + virtual ~Exception() throw() {} // necessary to destroy the string object!!! + + virtual const char* what() const throw () { + 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 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 throw() { 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/src/2geom/forward.h b/src/2geom/forward.h new file mode 100644 index 0000000..2790924 --- /dev/null +++ b/src/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/src/2geom/generic-interval.h b/src/2geom/generic-interval.h new file mode 100644 index 0000000..e7892e2 --- /dev/null +++ b/src/2geom/generic-interval.h @@ -0,0 +1,361 @@ +/** + * @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 <boost/none.hpp> +#include <boost/optional.hpp> +#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 boost::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 boost::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)) = boost::none; + } + GenericOptInterval<C> &operator|=(OptCInterval const &o) { + unionWith(o); + return *this; + } + GenericOptInterval<C> &operator&=(OptCInterval const &o) { + intersectWith(o); + return *this; + } +}; + +/** @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/src/2geom/generic-rect.h b/src/2geom/generic-rect.h new file mode 100644 index 0000000..f611f3e --- /dev/null +++ b/src/2geom/generic-rect.h @@ -0,0 +1,536 @@ +/** + * \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 <boost/optional.hpp> +#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 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 boost::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 boost::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); } + /// @} + + /// @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)) = boost::none; + } + } + /** @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)) = boost::none; + } + } + /** @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/src/2geom/geom.cpp b/src/2geom/geom.cpp new file mode 100644 index 0000000..71bbbf5 --- /dev/null +++ b/src/2geom/geom.cpp @@ -0,0 +1,395 @@ +/** + * \brief Various geometrical calculations. + */ + +#include <2geom/geom.h> +#include <2geom/point.h> +#include <algorithm> +#include <2geom/rect.h> + +using std::swap; + +namespace Geom { + +enum IntersectorKind { + intersects = 0, + parallel, + coincident, + no_intersection +}; + +/** + * Finds the intersection of the two (infinite) lines + * defined by the points p such that dot(n0, p) == d0 and dot(n1, p) == d1. + * + * If the two lines intersect, then \a result becomes their point of + * intersection; otherwise, \a result remains unchanged. + * + * This function finds the intersection of the two lines (infinite) + * defined by n0.X = d0 and x1.X = d1. The algorithm is as follows: + * To compute the intersection point use kramer's rule: + * \verbatim + * convert lines to form + * ax + by = c + * dx + ey = f + * + * ( + * e.g. a = (x2 - x1), b = (y2 - y1), c = (x2 - x1)*x1 + (y2 - y1)*y1 + * ) + * + * In our case we use: + * a = n0.x d = n1.x + * b = n0.y e = n1.y + * c = d0 f = d1 + * + * so: + * + * adx + bdy = cd + * adx + aey = af + * + * bdy - aey = cd - af + * (bd - ae)y = cd - af + * + * y = (cd - af)/(bd - ae) + * + * repeat for x and you get: + * + * x = (fb - ce)/(bd - ae) \endverbatim + * + * If the denominator (bd-ae) is 0 then the lines are parallel, if the + * numerators are 0 then the lines coincide. + * + * \todo Why not use existing but outcommented code below + * (HAVE_NEW_INTERSECTOR_CODE)? + */ +IntersectorKind +line_intersection(Geom::Point const &n0, double const d0, + Geom::Point const &n1, double const d1, + Geom::Point &result) +{ + double denominator = dot(Geom::rot90(n0), n1); + double X = n1[Geom::Y] * d0 - + n0[Geom::Y] * d1; + /* X = (-d1, d0) dot (n0[Y], n1[Y]) */ + + if (denominator == 0) { + if ( X == 0 ) { + return coincident; + } else { + return parallel; + } + } + + double Y = n0[Geom::X] * d1 - + n1[Geom::X] * d0; + + result = Geom::Point(X, Y) / denominator; + + return intersects; +} + + + +/* ccw exists as a building block */ +int +intersector_ccw(const Geom::Point& p0, const Geom::Point& p1, + const Geom::Point& p2) +/* Determine which way a set of three points winds. */ +{ + Geom::Point d1 = p1 - p0; + Geom::Point d2 = p2 - p0; + /* compare slopes but avoid division operation */ + double c = dot(Geom::rot90(d1), d2); + if(c > 0) + return +1; // ccw - do these match def'n in header? + if(c < 0) + return -1; // cw + + /* Colinear [or NaN]. Decide the order. */ + if ( ( d1[0] * d2[0] < 0 ) || + ( d1[1] * d2[1] < 0 ) ) { + return -1; // p2 < p0 < p1 + } else if ( dot(d1,d1) < dot(d2,d2) ) { + return +1; // p0 <= p1 < p2 + } else { + return 0; // p0 <= p2 <= p1 + } +} + +/** Determine whether the line segment from p00 to p01 intersects the + infinite line passing through p10 and p11. This doesn't find the + point of intersection, use the line_intersect function above, + or the segment_intersection interface below. + + \pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11. +*/ +bool +line_segment_intersectp(Geom::Point const &p00, Geom::Point const &p01, + Geom::Point const &p10, Geom::Point const &p11) +{ + if(p00 == p01) return false; + if(p10 == p11) return false; + + return ((intersector_ccw(p00, p01, p10) * intersector_ccw(p00, p01, p11)) <= 0 ); +} + + +/** Determine whether two line segments intersect. This doesn't find + the point of intersection, use the line_intersect function above, + or the segment_intersection interface below. + + \pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11. +*/ +bool +segment_intersectp(Geom::Point const &p00, Geom::Point const &p01, + Geom::Point const &p10, Geom::Point const &p11) +{ + if(p00 == p01) return false; + if(p10 == p11) return false; + + /* true iff ( (the p1 segment straddles the p0 infinite line) + * and (the p0 segment straddles the p1 infinite line) ). */ + return (line_segment_intersectp(p00, p01, p10, p11) && + line_segment_intersectp(p10, p11, p00, p01)); +} + +/** Determine whether \& where a line segments intersects an (infinite) line. + +If there is no intersection, then \a result remains unchanged. + +\pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11. +**/ +IntersectorKind +line_segment_intersect(Geom::Point const &p00, Geom::Point const &p01, + Geom::Point const &p10, Geom::Point const &p11, + Geom::Point &result) +{ + if(line_segment_intersectp(p00, p01, p10, p11)) { + Geom::Point n0 = (p01 - p00).ccw(); + double d0 = dot(n0,p00); + + Geom::Point n1 = (p11 - p10).ccw(); + double d1 = dot(n1,p10); + return line_intersection(n0, d0, n1, d1, result); + } else { + return no_intersection; + } +} + + +/** Determine whether \& where two line segments intersect. + +If the two segments don't intersect, then \a result remains unchanged. + +\pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11. +**/ +IntersectorKind +segment_intersect(Geom::Point const &p00, Geom::Point const &p01, + Geom::Point const &p10, Geom::Point const &p11, + Geom::Point &result) +{ + if(segment_intersectp(p00, p01, p10, p11)) { + Geom::Point n0 = (p01 - p00).ccw(); + double d0 = dot(n0,p00); + + Geom::Point n1 = (p11 - p10).ccw(); + double d1 = dot(n1,p10); + return line_intersection(n0, d0, n1, d1, result); + } else { + return no_intersection; + } +} + +/** Determine whether \& where two line segments intersect. + +If the two segments don't intersect, then \a result remains unchanged. + +\pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11. +**/ +IntersectorKind +line_twopoint_intersect(Geom::Point const &p00, Geom::Point const &p01, + Geom::Point const &p10, Geom::Point const &p11, + Geom::Point &result) +{ + Geom::Point n0 = (p01 - p00).ccw(); + double d0 = dot(n0,p00); + + Geom::Point n1 = (p11 - p10).ccw(); + double d1 = dot(n1,p10); + return line_intersection(n0, d0, n1, d1, result); +} + +// this is used to compare points for std::sort below +static bool +is_less(Point const &A, Point const &B) +{ + if (A[X] < B[X]) { + return true; + } else if (A[X] == B[X] && A[Y] < B[Y]) { + return true; + } else { + return false; + } +} + +// TODO: this can doubtlessly be improved +static void +eliminate_duplicates_p(std::vector<Point> &pts) +{ + unsigned int size = pts.size(); + + if (size < 2) + return; + + if (size == 2) { + if (pts[0] == pts[1]) { + pts.pop_back(); + } + } else { + std::sort(pts.begin(), pts.end(), &is_less); + if (size == 3) { + if (pts[0] == pts[1]) { + pts.erase(pts.begin()); + } else if (pts[1] == pts[2]) { + pts.pop_back(); + } + } else { + // we have size == 4 + if (pts[2] == pts[3]) { + pts.pop_back(); + } + if (pts[0] == pts[1]) { + pts.erase(pts.begin()); + } + } + } +} + +/** Determine whether \& where an (infinite) line intersects a rectangle. + * + * \a c0, \a c1 are diagonal corners of the rectangle and + * \a p1, \a p1 are distinct points on the line + * + * \return A list (possibly empty) of points of intersection. If two such points (say \a r0 and \a + * r1) then it is guaranteed that the order of \a r0, \a r1 along the line is the same as the that + * of \a c0, \a c1 (i.e., the vectors \a r1 - \a r0 and \a p1 - \a p0 point into the same + * direction). + */ +std::vector<Geom::Point> +rect_line_intersect(Geom::Point const &c0, Geom::Point const &c1, + Geom::Point const &p0, Geom::Point const &p1) +{ + using namespace Geom; + + std::vector<Point> results; + + Point A(c0); + Point C(c1); + + Point B(A[X], C[Y]); + Point D(C[X], A[Y]); + + Point res; + + if (line_segment_intersect(p0, p1, A, B, res) == intersects) { + results.push_back(res); + } + if (line_segment_intersect(p0, p1, B, C, res) == intersects) { + results.push_back(res); + } + if (line_segment_intersect(p0, p1, C, D, res) == intersects) { + results.push_back(res); + } + if (line_segment_intersect(p0, p1, D, A, res) == intersects) { + results.push_back(res); + } + + eliminate_duplicates_p(results); + + if (results.size() == 2) { + // sort the results so that the order is the same as that of p0 and p1 + Point dir1 (results[1] - results[0]); + Point dir2 (p1 - p0); + if (dot(dir1, dir2) < 0) { + swap(results[0], results[1]); + } + } + + return results; +} + +/** Determine whether \& where an (infinite) line intersects a rectangle. + * + * \a c0, \a c1 are diagonal corners of the rectangle and + * \a p1, \a p1 are distinct points on the line + * + * \return A list (possibly empty) of points of intersection. If two such points (say \a r0 and \a + * r1) then it is guaranteed that the order of \a r0, \a r1 along the line is the same as the that + * of \a c0, \a c1 (i.e., the vectors \a r1 - \a r0 and \a p1 - \a p0 point into the same + * direction). + */ +boost::optional<LineSegment> +rect_line_intersect(Geom::Rect &r, + Geom::LineSegment ls) +{ + std::vector<Point> results; + + results = rect_line_intersect(r.min(), r.max(), ls[0], ls[1]); + if(results.size() == 2) { + return LineSegment(results[0], results[1]); + } + return boost::optional<LineSegment>(); +} + +boost::optional<LineSegment> +rect_line_intersect(Geom::Rect &r, + Geom::Line l) +{ + return rect_line_intersect(r, l.segment(0, 1)); +} + +/** + * polyCentroid: Calculates the centroid (xCentroid, yCentroid) and area of a polygon, given its + * vertices (x[0], y[0]) ... (x[n-1], y[n-1]). It is assumed that the contour is closed, i.e., that + * the vertex following (x[n-1], y[n-1]) is (x[0], y[0]). The algebraic sign of the area is + * positive for counterclockwise ordering of vertices in x-y plane; otherwise negative. + + * Returned values: + 0 for normal execution; + 1 if the polygon is degenerate (number of vertices < 3); + 2 if area = 0 (and the centroid is undefined). + + * for now we require the path to be a polyline and assume it is closed. +**/ + +int centroid(std::vector<Geom::Point> const &p, Geom::Point& centroid, double &area) { + const unsigned n = p.size(); + if (n < 3) + return 1; + Geom::Point centroid_tmp(0,0); + double atmp = 0; + for (unsigned i = n-1, j = 0; j < n; i = j, j++) { + const double ai = cross(p[j], p[i]); + atmp += ai; + centroid_tmp += (p[j] + p[i])*ai; // first moment. + } + area = atmp / 2; + if (atmp != 0) { + centroid = centroid_tmp / (3 * atmp); + return 0; + } + return 2; +} + +} + +/* + 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/src/2geom/geom.h b/src/2geom/geom.h new file mode 100644 index 0000000..6ba8122 --- /dev/null +++ b/src/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 <boost/optional/optional.hpp> +#include <2geom/bezier-curve.h> +#include <2geom/line.h> + +namespace Geom { + +boost::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/src/2geom/int-interval.h b/src/2geom/int-interval.h new file mode 100644 index 0000000..0faf48d --- /dev/null +++ b/src/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/src/2geom/int-point.h b/src/2geom/int-point.h new file mode 100644 index 0000000..50d3a67 --- /dev/null +++ b/src/2geom/int-point.h @@ -0,0 +1,180 @@ +/** + * \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 + > > +{ + IntCoord _pt[2]; +public: + /// @name Creating integer points + /// @{ + IntPoint() { } + IntPoint(IntCoord x, IntCoord y) { + _pt[X] = x; + _pt[Y] = 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 throw() { return _pt[X]; } + IntCoord &x() throw() { return _pt[X]; } + IntCoord y() const throw() { return _pt[Y]; } + IntCoord &y() throw() { return _pt[Y]; } + /// @} + + /// @name Vector-like arithmetic operations + /// @{ + IntPoint operator-() const { + IntPoint ret(-_pt[X], -_pt[Y]); + return ret; + } + 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; + } + /// @} + + /// @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/src/2geom/int-rect.h b/src/2geom/int-rect.h new file mode 100644 index 0000000..567d42d --- /dev/null +++ b/src/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/src/2geom/intersection-graph.cpp b/src/2geom/intersection-graph.cpp new file mode 100644 index 0000000..1d06552 --- /dev/null +++ b/src/2geom/intersection-graph.cpp @@ -0,0 +1,494 @@ +/** + * \file + * \brief Intersection graph for Boolean operations + *//* + * 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. + */ + +#include <2geom/intersection-graph.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/utils.h> +#include <iostream> +#include <iterator> + +namespace Geom { + +struct PathIntersectionGraph::IntersectionVertexLess { + bool operator()(IntersectionVertex const &a, IntersectionVertex const &b) const { + return a.pos < b.pos; + } +}; + +/** @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. + * + * @ingroup Paths + */ + +PathIntersectionGraph::PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision) + : _graph_valid(true) +{ + if (a.empty() || b.empty()) return; + + _pv[0] = a; + _pv[1] = b; + + _prepareArguments(); + bool has_intersections = _prepareIntersectionLists(precision); + if (!has_intersections) return; + + _assignEdgeWindingParities(precision); + _assignComponentStatusFromDegenerateIntersections(); + _removeDegenerateIntersections(); + if (_graph_valid) { + _verify(); + } +} + +void PathIntersectionGraph::_prepareArguments() +{ + // all paths must be closed, otherwise we will miss some intersections + for (int w = 0; w < 2; ++w) { + for (std::size_t i = 0; i < _pv[w].size(); ++i) { + _pv[w][i].close(); + } + } + // remove degenerate segments + for (int w = 0; w < 2; ++w) { + for (std::size_t i = _pv[w].size(); i > 0; --i) { + if (_pv[w][i-1].empty()) { + _pv[w].erase(_pv[w].begin() + (i-1)); + continue; + } + for (std::size_t j = _pv[w][i-1].size(); j > 0; --j) { + if (_pv[w][i-1][j-1].isDegenerate()) { + _pv[w][i-1].erase(_pv[w][i-1].begin() + (j-1)); + } + } + } + } +} + +bool PathIntersectionGraph::_prepareIntersectionLists(Coord precision) +{ + std::vector<PVIntersection> pxs = _pv[0].intersect(_pv[1], precision); + // NOTE: this early return means that the path data structures will not be created + // if there are no intersections at all! + if (pxs.empty()) return false; + + // prepare intersection lists for each path component + for (unsigned w = 0; w < 2; ++w) { + for (std::size_t i = 0; i < _pv[w].size(); ++i) { + _components[w].push_back(new PathData(w, i)); + } + } + + // create intersection vertices + for (std::size_t i = 0; i < pxs.size(); ++i) { + IntersectionVertex *xa, *xb; + xa = new IntersectionVertex(); + xb = new IntersectionVertex(); + //xa->processed = xb->processed = false; + xa->which = 0; xb->which = 1; + xa->pos = pxs[i].first; + xb->pos = pxs[i].second; + xa->p = xb->p = pxs[i].point(); + xa->neighbor = xb; + xb->neighbor = xa; + xa->next_edge = xb->next_edge = OUTSIDE; + xa->defective = xb->defective = false; + _xs.push_back(xa); + _xs.push_back(xb); + _components[0][xa->pos.path_index].xlist.push_back(*xa); + _components[1][xb->pos.path_index].xlist.push_back(*xb); + } + + // sort components according to time value of intersections + for (unsigned w = 0; w < 2; ++w) { + for (std::size_t i = 0; i < _components[w].size(); ++i) { + _components[w][i].xlist.sort(IntersectionVertexLess()); + } + } + + return true; +} + +void PathIntersectionGraph::_assignEdgeWindingParities(Coord precision) +{ + // determine the winding numbers of path portions between intersections + for (unsigned w = 0; w < 2; ++w) { + unsigned ow = (w+1) % 2; + + for (unsigned li = 0; li < _components[w].size(); ++li) { + IntersectionList &xl = _components[w][li].xlist; + for (ILIter i = xl.begin(); i != xl.end(); ++i) { + ILIter n = cyclic_next(i, xl); + std::size_t pi = i->pos.path_index; + + PathInterval ival = forward_interval(i->pos, n->pos, _pv[w][pi].size()); + PathTime mid = ival.inside(precision); + + Point wpoint = _pv[w][pi].pointAt(mid); + _winding_points.push_back(wpoint); + int wdg = _pv[ow].winding(wpoint); + if (wdg % 2) { + i->next_edge = INSIDE; + } else { + i->next_edge = OUTSIDE; + } + } + } + } +} + +void PathIntersectionGraph::_assignComponentStatusFromDegenerateIntersections() +{ + // If a path has only degenerate intersections, assign its status now. + // This protects against later accidentally picking a point for winding + // determination that is exactly at a removed intersection. + for (unsigned w = 0; w < 2; ++w) { + for (unsigned li = 0; li < _components[w].size(); ++li) { + IntersectionList &xl = _components[w][li].xlist; + bool has_in = false; + bool has_out = false; + for (ILIter i = xl.begin(); i != xl.end(); ++i) { + has_in |= (i->next_edge == INSIDE); + has_out |= (i->next_edge == OUTSIDE); + } + if (has_in && !has_out) { + _components[w][li].status = INSIDE; + } + if (!has_in && has_out) { + _components[w][li].status = OUTSIDE; + } + } + } +} + +void PathIntersectionGraph::_removeDegenerateIntersections() +{ + // remove intersections that don't change between in/out + for (unsigned w = 0; w < 2; ++w) { + for (unsigned li = 0; li < _components[w].size(); ++li) { + IntersectionList &xl = _components[w][li].xlist; + for (ILIter i = xl.begin(); i != xl.end();) { + ILIter n = cyclic_next(i, xl); + if (i->next_edge == n->next_edge) { + bool last_node = (i == n); + ILIter nn = _getNeighbor(n); + IntersectionList &oxl = _getPathData(nn).xlist; + + // When exactly 3 out of 4 edges adjacent to an intersection + // have the same winding, we have a defective intersection, + // which is neither degenerate nor normal. Those can occur in paths + // that contain overlapping segments. We cannot handle that case + // for now, so throw an exception. + if (cyclic_prior(nn, oxl)->next_edge != nn->next_edge) { + _graph_valid = false; + n->defective = true; + nn->defective = true; + ++i; + continue; + } + + oxl.erase(nn); + xl.erase(n); + if (last_node) break; + } else { + ++i; + } + } + } + } +} + +void PathIntersectionGraph::_verify() +{ + for (unsigned w = 0; w < 2; ++w) { + for (unsigned li = 0; li < _components[w].size(); ++li) { + IntersectionList &xl = _components[w][li].xlist; + assert(xl.size() % 2 == 0); + for (ILIter i = xl.begin(); i != xl.end(); ++i) { + ILIter j = cyclic_next(i, xl); + assert(i->next_edge != j->next_edge); + } + } + } +} + +PathVector PathIntersectionGraph::getUnion() +{ + PathVector result = _getResult(false, false); + _handleNonintersectingPaths(result, 0, false); + _handleNonintersectingPaths(result, 1, false); + return result; +} + +PathVector PathIntersectionGraph::getIntersection() +{ + PathVector result = _getResult(true, true); + _handleNonintersectingPaths(result, 0, true); + _handleNonintersectingPaths(result, 1, true); + return result; +} + +PathVector PathIntersectionGraph::getAminusB() +{ + PathVector result = _getResult(false, true); + _handleNonintersectingPaths(result, 0, false); + _handleNonintersectingPaths(result, 1, true); + return result; +} + +PathVector PathIntersectionGraph::getBminusA() +{ + PathVector result = _getResult(true, false); + _handleNonintersectingPaths(result, 1, false); + _handleNonintersectingPaths(result, 0, true); + return result; +} + +PathVector PathIntersectionGraph::getXOR() +{ + PathVector r1, r2; + r1 = getAminusB(); + r2 = getBminusA(); + std::copy(r2.begin(), r2.end(), std::back_inserter(r1)); + return r1; +} + +std::size_t PathIntersectionGraph::size() const +{ + std::size_t result = 0; + for (std::size_t i = 0; i < _components[0].size(); ++i) { + result += _components[0][i].xlist.size(); + } + return result; +} + +std::vector<Point> PathIntersectionGraph::intersectionPoints(bool defective) const +{ + std::vector<Point> result; + + typedef IntersectionList::const_iterator CILIter; + for (std::size_t i = 0; i < _components[0].size(); ++i) { + for (CILIter j = _components[0][i].xlist.begin(); j != _components[0][i].xlist.end(); ++j) { + if (j->defective == defective) { + result.push_back(j->p); + } + } + } + return result; +} + +void PathIntersectionGraph::fragments(PathVector &in, PathVector &out) const +{ + typedef boost::ptr_vector<PathData>::const_iterator PIter; + for (unsigned w = 0; w < 2; ++w) { + for (PIter li = _components[w].begin(); li != _components[w].end(); ++li) { + for (CILIter k = li->xlist.begin(); k != li->xlist.end(); ++k) { + CILIter n = cyclic_next(k, li->xlist); + // TODO: investigate why non-contiguous paths are sometimes generated here + Path frag(k->p); + frag.setStitching(true); + PathInterval ival = forward_interval(k->pos, n->pos, _pv[w][k->pos.path_index].size()); + _pv[w][k->pos.path_index].appendPortionTo(frag, ival, k->p, n->p); + if (k->next_edge == INSIDE) { + in.push_back(frag); + } else { + out.push_back(frag); + } + } + } + } +} + +PathVector PathIntersectionGraph::_getResult(bool enter_a, bool enter_b) +{ + typedef boost::ptr_vector<PathData>::iterator PIter; + PathVector result; + if (_xs.empty()) return result; + + // reset processed status + _ulist.clear(); + for (unsigned w = 0; w < 2; ++w) { + for (PIter li = _components[w].begin(); li != _components[w].end(); ++li) { + for (ILIter k = li->xlist.begin(); k != li->xlist.end(); ++k) { + _ulist.push_back(*k); + } + } + } + + unsigned n_processed = 0; + + while (true) { + // get unprocessed intersection + if (_ulist.empty()) break; + IntersectionVertex &iv = _ulist.front(); + unsigned w = iv.which; + ILIter i = _components[w][iv.pos.path_index].xlist.iterator_to(iv); + + result.push_back(Path(i->p)); + result.back().setStitching(true); + + while (i->_proc_hook.is_linked()) { + ILIter prev = i; + std::size_t pi = i->pos.path_index; + // determine which direction to go + // union: always go outside + // intersection: always go inside + // a minus b: go inside in b, outside in a + // b minus a: go inside in a, outside in b + bool reverse = false; + if (w == 0) { + reverse = (i->next_edge == INSIDE) ^ enter_a; + } else { + reverse = (i->next_edge == INSIDE) ^ enter_b; + } + + // get next intersection + if (reverse) { + i = cyclic_prior(i, _components[w][pi].xlist); + } else { + i = cyclic_next(i, _components[w][pi].xlist); + } + + // append portion of path + PathInterval ival = PathInterval::from_direction( + prev->pos.asPathTime(), i->pos.asPathTime(), + reverse, _pv[i->which][pi].size()); + + _pv[i->which][pi].appendPortionTo(result.back(), ival, prev->p, i->p); + + // mark both vertices as processed + //prev->processed = true; + //i->processed = true; + n_processed += 2; + if (prev->_proc_hook.is_linked()) { + _ulist.erase(_ulist.iterator_to(*prev)); + } + if (i->_proc_hook.is_linked()) { + _ulist.erase(_ulist.iterator_to(*i)); + } + + // switch to the other path + i = _getNeighbor(i); + w = i->which; + } + result.back().close(true); + + assert(!result.back().empty()); + } + + /*if (n_processed != size() * 2) { + std::cerr << "Processed " << n_processed << " intersections, expected " << (size() * 2) << std::endl; + }*/ + assert(n_processed == size() * 2); + + return result; +} + +void PathIntersectionGraph::_handleNonintersectingPaths(PathVector &result, unsigned which, bool inside) +{ + /* Every component that has any intersections will be processed by _getResult. + * Here we take care of paths that don't have any intersections. They are either + * completely inside or completely outside the other pathvector. We test this by + * evaluating the winding rule at the initial point. If inside is true and + * the path is inside, we add it to the result. + */ + unsigned w = which; + unsigned ow = (w+1) % 2; + + for (std::size_t i = 0; i < _pv[w].size(); ++i) { + // the path data vector might have been left empty if there were no intersections at all + bool has_path_data = !_components[w].empty(); + // Skip if the path has intersections + if (has_path_data && !_components[w][i].xlist.empty()) continue; + bool path_inside = false; + + // Use the in/out determination from constructor, if available + if (has_path_data && _components[w][i].status == INSIDE) { + path_inside = true; + } else if (has_path_data && _components[w][i].status == OUTSIDE) { + path_inside = false; + } else { + int wdg = _pv[ow].winding(_pv[w][i].initialPoint()); + path_inside = wdg % 2 != 0; + } + + if (path_inside == inside) { + result.push_back(_pv[w][i]); + } + } +} + +PathIntersectionGraph::ILIter PathIntersectionGraph::_getNeighbor(ILIter iter) +{ + unsigned ow = (iter->which + 1) % 2; + return _components[ow][iter->neighbor->pos.path_index].xlist.iterator_to(*iter->neighbor); +} + +PathIntersectionGraph::PathData & +PathIntersectionGraph::_getPathData(ILIter iter) +{ + return _components[iter->which][iter->pos.path_index]; +} + +std::ostream &operator<<(std::ostream &os, PathIntersectionGraph const &pig) +{ + typedef PathIntersectionGraph::IntersectionList::const_iterator CILIter; + os << "Intersection graph:\n" + << pig._xs.size()/2 << " total intersections\n" + << pig.size() << " considered intersections\n"; + for (std::size_t i = 0; i < pig._components[0].size(); ++i) { + PathIntersectionGraph::IntersectionList const &xl = pig._components[0][i].xlist; + for (CILIter j = xl.begin(); j != xl.end(); ++j) { + os << j->pos << " - " << j->neighbor->pos << " @ " << j->p << "\n"; + } + } + return os; +} + +} // namespace Geom + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/intersection-graph.h b/src/2geom/intersection-graph.h new file mode 100644 index 0000000..332c83f --- /dev/null +++ b/src/2geom/intersection-graph.h @@ -0,0 +1,157 @@ +/** + * \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 +{ + // 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: + PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision = EPSILON); + + PathVector getUnion(); + PathVector getIntersection(); + PathVector getAminusB(); + PathVector getBminusA(); + PathVector getXOR(); + + /// Returns the number of intersections used when computing Boolean operations. + std::size_t size() const; + std::vector<Point> intersectionPoints(bool defective = false) const; + 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; + Point p; // guarantees that endpoints are exact + IntersectionVertex *neighbor; + InOutFlag next_edge; + unsigned which; + 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; + + struct PathData { + IntersectionList xlist; + std::size_t path_index; + int which; + 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]; + boost::ptr_vector<IntersectionVertex> _xs; + boost::ptr_vector<PathData> _components[2]; + UnprocessedList _ulist; + bool _graph_valid; + 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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/intersection.h b/src/2geom/intersection.h new file mode 100644 index 0000000..bbce199 --- /dev/null +++ b/src/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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/interval.h b/src/2geom/interval.h new file mode 100644 index 0000000..5f51428 --- /dev/null +++ b/src/2geom/interval.h @@ -0,0 +1,252 @@ +/** + * \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/optional.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) { + 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) { + return (v - min()) / extent(); + } + /// Find closest time in [0,1] that maps to the given value. */ + Coord nearestTime(Coord v) { + 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) { 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: + /// @name Create optionally empty intervals. + /// @{ + /** @brief Create an empty interval. */ + OptInterval() : Base() {} + /** @brief Wrap an existing interval. */ + OptInterval(Interval const &a) : Base(a) {} + /** @brief Create an interval containing a single point. */ + OptInterval(Coord u) : Base(u) {} + /** @brief Create an interval containing a range of numbers. */ + OptInterval(Coord u, Coord v) : Base(u,v) {} + 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/src/2geom/line.cpp b/src/2geom/line.cpp new file mode 100644 index 0000000..ce9b9cc --- /dev/null +++ b/src/2geom/line.cpp @@ -0,0 +1,609 @@ +/* + * Infinite Straight Line + * + * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com> + * Nathan Hurst + * + * 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. + */ + +#include <algorithm> +#include <2geom/line.h> +#include <2geom/math-utils.h> + +namespace Geom +{ + +/** + * @class Line + * @brief Infinite line on a plane. + * + * A line is specified as two points through which it passes. Lines can be interpreted as functions + * \f$ f: (-\infty, \infty) \to \mathbb{R}^2\f$. Zero corresponds to the first (origin) point, + * one corresponds to the second (final) point. All other points are computed as a linear + * interpolation between those two: \f$p = (1-t) a + t b\f$. Many such functions have the same + * image and therefore represent the same lines; for example, adding \f$b-a\f$ to both points + * yields the same line. + * + * 2Geom can represent the same line in many ways by design: using a different representation + * would lead to precision loss. For example, a line from (1e30, 1e30) to (10,0) would actually + * evaluate to (0,0) at time 1 if it was stored as origin and normalized versor, + * or origin and angle. + * + * @ingroup Primitives + */ + +/** @brief Set the line by solving the line equation. + * A line is a set of points that satisfies the line equation + * \f$Ax + By + C = 0\f$. This function changes the line so that its points + * satisfy the line equation with the given coefficients. */ +void Line::setCoefficients (Coord a, Coord b, Coord c) +{ + // degenerate case + if (a == 0 && b == 0) { + if (c != 0) { + THROW_LOGICALERROR("the passed coefficients give the empty set"); + } + _initial = Point(0,0); + _final = Point(0,0); + return; + } + + // The way final / initial points are set based on coefficients is somewhat unusual. + // This is done to make sure that calling coefficients() will give back + // (almost) the same values. + + // vertical case + if (a == 0) { + // b must be nonzero + _initial = Point(-b/2, -c / b); + _final = _initial; + _final[X] = b/2; + return; + } + + // horizontal case + if (b == 0) { + _initial = Point(-c / a, a/2); + _final = _initial; + _final[Y] = -a/2; + return; + } + + // This gives reasonable results regardless of the magnitudes of a, b and c. + _initial = Point(-b/2,a/2); + _final = Point(b/2,-a/2); + + Point offset(-c/(2*a), -c/(2*b)); + + _initial += offset; + _final += offset; +} + +void Line::coefficients(Coord &a, Coord &b, Coord &c) const +{ + Point v = vector().cw(); + a = v[X]; + b = v[Y]; + c = cross(_initial, _final); +} + +/** @brief Get the implicit line equation coefficients. + * Note that conversion to implicit form always causes loss of + * precision when dealing with lines that start far from the origin + * and end very close to it. It is recommended to normalize the line + * before converting it to implicit form. + * @return Vector with three values corresponding to the A, B and C + * coefficients of the line equation for this line. */ +std::vector<Coord> Line::coefficients() const +{ + std::vector<Coord> c(3); + coefficients(c[0], c[1], c[2]); + return c; +} + +/** @brief Find intersection with an axis-aligned line. + * @param v Coordinate of the axis-aligned line + * @param d Which axis the coordinate is on. X means a vertical line, Y means a horizontal line. + * @return Time values at which this line intersects the query line. */ +std::vector<Coord> Line::roots(Coord v, Dim2 d) const { + std::vector<Coord> result; + Coord r = root(v, d); + if (std::isfinite(r)) { + result.push_back(r); + } + return result; +} + +Coord Line::root(Coord v, Dim2 d) const +{ + assert(d == X || d == Y); + Point vs = vector(); + if (vs[d] != 0) { + return (v - _initial[d]) / vs[d]; + } else { + return nan(""); + } +} + +boost::optional<LineSegment> Line::clip(Rect const &r) const +{ + Point v = vector(); + // handle horizontal and vertical lines first, + // since the root-based code below will break for them + for (unsigned i = 0; i < 2; ++i) { + Dim2 d = (Dim2) i; + Dim2 o = other_dimension(d); + if (v[d] != 0) continue; + if (r[d].contains(_initial[d])) { + Point a, b; + a[o] = r[o].min(); + b[o] = r[o].max(); + a[d] = b[d] = _initial[d]; + if (v[o] > 0) { + return LineSegment(a, b); + } else { + return LineSegment(b, a); + } + } else { + return boost::none; + } + } + + Interval xpart(root(r[X].min(), X), root(r[X].max(), X)); + Interval ypart(root(r[Y].min(), Y), root(r[Y].max(), Y)); + if (!xpart.isFinite() || !ypart.isFinite()) { + return boost::none; + } + + OptInterval common = xpart & ypart; + if (common) { + Point p1 = pointAt(common->min()), p2 = pointAt(common->max()); + LineSegment result(r.clamp(p1), r.clamp(p2)); + return result; + } else { + return boost::none; + } + + /* old implementation using coefficients: + + if (fabs(b) > fabs(a)) { + p0 = Point(r[X].min(), (-c - a*r[X].min())/b); + if (p0[Y] < r[Y].min()) + p0 = Point((-c - b*r[Y].min())/a, r[Y].min()); + if (p0[Y] > r[Y].max()) + p0 = Point((-c - b*r[Y].max())/a, r[Y].max()); + p1 = Point(r[X].max(), (-c - a*r[X].max())/b); + if (p1[Y] < r[Y].min()) + p1 = Point((-c - b*r[Y].min())/a, r[Y].min()); + if (p1[Y] > r[Y].max()) + p1 = Point((-c - b*r[Y].max())/a, r[Y].max()); + } else { + p0 = Point((-c - b*r[Y].min())/a, r[Y].min()); + if (p0[X] < r[X].min()) + p0 = Point(r[X].min(), (-c - a*r[X].min())/b); + if (p0[X] > r[X].max()) + p0 = Point(r[X].max(), (-c - a*r[X].max())/b); + p1 = Point((-c - b*r[Y].max())/a, r[Y].max()); + if (p1[X] < r[X].min()) + p1 = Point(r[X].min(), (-c - a*r[X].min())/b); + if (p1[X] > r[X].max()) + p1 = Point(r[X].max(), (-c - a*r[X].max())/b); + } + return LineSegment(p0, p1); */ +} + +/** @brief Get a time value corresponding to a point. + * @param p Point on the line. If the point is not on the line, + * the returned value will be meaningless. + * @return Time value t such that \f$f(t) = p\f$. + * @see timeAtProjection */ +Coord Line::timeAt(Point const &p) const +{ + Point v = vector(); + // degenerate case + if (v[X] == 0 && v[Y] == 0) { + return 0; + } + + // use the coordinate that will give better precision + if (fabs(v[X]) > fabs(v[Y])) { + return (p[X] - _initial[X]) / v[X]; + } else { + return (p[Y] - _initial[Y]) / v[Y]; + } +} + +/** @brief Create a transformation that maps one line to another. + * This will return a transformation \f$A\f$ such that + * \f$L_1(t) \cdot A = L_2(t)\f$, where \f$L_1\f$ is this line + * and \f$L_2\f$ is the line passed as the parameter. The returned + * transformation will preserve angles. */ +Affine Line::transformTo(Line const &other) const +{ + Affine result = Translate(-_initial); + result *= Rotate(angle_between(vector(), other.vector())); + result *= Scale(other.vector().length() / vector().length()); + result *= Translate(other._initial); + return result; +} + +std::vector<ShapeIntersection> Line::intersect(Line const &other) const +{ + std::vector<ShapeIntersection> result; + + Point v1 = vector(); + Point v2 = other.vector(); + Coord cp = cross(v1, v2); + if (cp == 0) return result; + + Point odiff = other.initialPoint() - initialPoint(); + Coord t1 = cross(odiff, v2) / cp; + Coord t2 = cross(odiff, v1) / cp; + result.push_back(ShapeIntersection(*this, other, t1, t2)); + return result; +} + +std::vector<ShapeIntersection> Line::intersect(Ray const &r) const +{ + Line other(r); + std::vector<ShapeIntersection> result = intersect(other); + filter_ray_intersections(result, false, true); + return result; +} + +std::vector<ShapeIntersection> Line::intersect(LineSegment const &ls) const +{ + Line other(ls); + std::vector<ShapeIntersection> result = intersect(other); + filter_line_segment_intersections(result, false, true); + return result; +} + + + +void filter_line_segment_intersections(std::vector<ShapeIntersection> &xs, bool a, bool b) +{ + Interval unit(0, 1); + std::vector<ShapeIntersection>::reverse_iterator i = xs.rbegin(), last = xs.rend(); + while (i != last) { + if ((a && !unit.contains(i->first)) || (b && !unit.contains(i->second))) { + xs.erase((++i).base()); + } else { + ++i; + } + } +} + +void filter_ray_intersections(std::vector<ShapeIntersection> &xs, bool a, bool b) +{ + Interval unit(0, 1); + std::vector<ShapeIntersection>::reverse_iterator i = xs.rbegin(), last = xs.rend(); + while (i != last) { + if ((a && i->first < 0) || (b && i->second < 0)) { + xs.erase((++i).base()); + } else { + ++i; + } + } +} + +namespace detail +{ + +inline +OptCrossing intersection_impl(Point const &v1, Point const &o1, + Point const &v2, Point const &o2) +{ + Coord cp = cross(v1, v2); + if (cp == 0) return OptCrossing(); + + Point odiff = o2 - o1; + + Crossing c; + c.ta = cross(odiff, v2) / cp; + c.tb = cross(odiff, v1) / cp; + return c; +} + + +OptCrossing intersection_impl(Ray const& r1, Line const& l2, unsigned int i) +{ + using std::swap; + + OptCrossing crossing = + intersection_impl(r1.vector(), r1.origin(), + l2.vector(), l2.origin() ); + + if (crossing) { + if (crossing->ta < 0) { + return OptCrossing(); + } else { + if (i != 0) { + swap(crossing->ta, crossing->tb); + } + return crossing; + } + } + if (are_near(r1.origin(), l2)) { + THROW_INFINITESOLUTIONS(); + } else { + return OptCrossing(); + } +} + + +OptCrossing intersection_impl( LineSegment const& ls1, + Line const& l2, + unsigned int i ) +{ + using std::swap; + + OptCrossing crossing = + intersection_impl(ls1.finalPoint() - ls1.initialPoint(), + ls1.initialPoint(), + l2.vector(), + l2.origin() ); + + if (crossing) { + if ( crossing->getTime(0) < 0 + || crossing->getTime(0) > 1 ) + { + return OptCrossing(); + } else { + if (i != 0) { + swap((*crossing).ta, (*crossing).tb); + } + return crossing; + } + } + if (are_near(ls1.initialPoint(), l2)) { + THROW_INFINITESOLUTIONS(); + } else { + return OptCrossing(); + } +} + + +OptCrossing intersection_impl( LineSegment const& ls1, + Ray const& r2, + unsigned int i ) +{ + using std::swap; + + Point direction = ls1.finalPoint() - ls1.initialPoint(); + OptCrossing crossing = + intersection_impl( direction, + ls1.initialPoint(), + r2.vector(), + r2.origin() ); + + if (crossing) { + if ( (crossing->getTime(0) < 0) + || (crossing->getTime(0) > 1) + || (crossing->getTime(1) < 0) ) + { + return OptCrossing(); + } else { + if (i != 0) { + swap(crossing->ta, crossing->tb); + } + return crossing; + } + } + + if ( are_near(r2.origin(), ls1) ) { + bool eqvs = (dot(direction, r2.vector()) > 0); + if ( are_near(ls1.initialPoint(), r2.origin()) && !eqvs) { + crossing->ta = crossing->tb = 0; + return crossing; + } else if ( are_near(ls1.finalPoint(), r2.origin()) && eqvs) { + if (i == 0) { + crossing->ta = 1; + crossing->tb = 0; + } else { + crossing->ta = 0; + crossing->tb = 1; + } + return crossing; + } else { + THROW_INFINITESOLUTIONS(); + } + } else if ( are_near(ls1.initialPoint(), r2) ) { + THROW_INFINITESOLUTIONS(); + } else { + OptCrossing no_crossing; + return no_crossing; + } +} + +} // end namespace detail + + + +OptCrossing intersection(Line const& l1, Line const& l2) +{ + OptCrossing c = detail::intersection_impl( + l1.vector(), l1.origin(), + l2.vector(), l2.origin()); + + if (!c && distance(l1.origin(), l2) == 0) { + THROW_INFINITESOLUTIONS(); + } + return c; +} + +OptCrossing intersection(Ray const& r1, Ray const& r2) +{ + OptCrossing crossing = + detail::intersection_impl( r1.vector(), r1.origin(), + r2.vector(), r2.origin() ); + + if (crossing) + { + if ( crossing->ta < 0 + || crossing->tb < 0 ) + { + OptCrossing no_crossing; + return no_crossing; + } + else + { + return crossing; + } + } + + if ( are_near(r1.origin(), r2) || are_near(r2.origin(), r1) ) + { + if ( are_near(r1.origin(), r2.origin()) + && !are_near(r1.vector(), r2.vector()) ) + { + crossing->ta = crossing->tb = 0; + return crossing; + } + else + { + THROW_INFINITESOLUTIONS(); + } + } + else + { + OptCrossing no_crossing; + return no_crossing; + } +} + + +OptCrossing intersection( LineSegment const& ls1, LineSegment const& ls2 ) +{ + Point direction1 = ls1.finalPoint() - ls1.initialPoint(); + Point direction2 = ls2.finalPoint() - ls2.initialPoint(); + OptCrossing crossing = + detail::intersection_impl( direction1, + ls1.initialPoint(), + direction2, + ls2.initialPoint() ); + + if (crossing) + { + if ( crossing->getTime(0) < 0 + || crossing->getTime(0) > 1 + || crossing->getTime(1) < 0 + || crossing->getTime(1) > 1 ) + { + OptCrossing no_crossing; + return no_crossing; + } + else + { + return crossing; + } + } + + bool eqvs = (dot(direction1, direction2) > 0); + if ( are_near(ls2.initialPoint(), ls1) ) + { + if ( are_near(ls1.initialPoint(), ls2.initialPoint()) && !eqvs ) + { + crossing->ta = crossing->tb = 0; + return crossing; + } + else if ( are_near(ls1.finalPoint(), ls2.initialPoint()) && eqvs ) + { + crossing->ta = 1; + crossing->tb = 0; + return crossing; + } + else + { + THROW_INFINITESOLUTIONS(); + } + } + else if ( are_near(ls2.finalPoint(), ls1) ) + { + if ( are_near(ls1.finalPoint(), ls2.finalPoint()) && !eqvs ) + { + crossing->ta = crossing->tb = 1; + return crossing; + } + else if ( are_near(ls1.initialPoint(), ls2.finalPoint()) && eqvs ) + { + crossing->ta = 0; + crossing->tb = 1; + return crossing; + } + else + { + THROW_INFINITESOLUTIONS(); + } + } + else + { + OptCrossing no_crossing; + return no_crossing; + } +} + +Line make_angle_bisector_line(Line const& l1, Line const& l2) +{ + OptCrossing crossing; + try + { + crossing = intersection(l1, l2); + } + catch(InfiniteSolutions const &e) + { + return l1; + } + if (!crossing) + { + THROW_RANGEERROR("passed lines are parallel"); + } + Point O = l1.pointAt(crossing->ta); + Point A = l1.pointAt(crossing->ta + 1); + double angle = angle_between(l1.vector(), l2.vector()); + Point B = (angle > 0) ? l2.pointAt(crossing->tb + 1) + : l2.pointAt(crossing->tb - 1); + + return make_angle_bisector_line(A, O, B); +} + + + + +} // end namespace Geom + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(substatement-open . 0)) + indent-tabs-mode:nil + c-brace-offset:0 + fill-column:99 + End: + vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : +*/ diff --git a/src/2geom/line.h b/src/2geom/line.h new file mode 100644 index 0000000..5d570d0 --- /dev/null +++ b/src/2geom/line.h @@ -0,0 +1,604 @@ +/** + * \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 <boost/optional.hpp> +#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 retrieved vector is normalized to unit length. */ + 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 + boost::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 +boost::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/src/2geom/linear.h b/src/2geom/linear.h new file mode 100644 index 0000000..75c6e01 --- /dev/null +++ b/src/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/src/2geom/math-utils.h b/src/2geom/math-utils.h new file mode 100644 index 0000000..ee923f5 --- /dev/null +++ b/src/2geom/math-utils.h @@ -0,0 +1,110 @@ +/** + * \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 <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 +} + +} // 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/src/2geom/nearest-time.cpp b/src/2geom/nearest-time.cpp new file mode 100644 index 0000000..1039210 --- /dev/null +++ b/src/2geom/nearest-time.cpp @@ -0,0 +1,322 @@ +/** @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. + */ + + +#include <2geom/nearest-time.h> +#include <algorithm> + +namespace Geom +{ + +Coord nearest_time(Point const &p, D2<Bezier> const &input, Coord from, Coord to) +{ + Interval domain(from, to); + bool partial = false; + + if (domain.min() < 0 || domain.max() > 1) { + THROW_RANGEERROR("[from,to] interval out of bounds"); + } + + if (input.isConstant(0)) return from; + + D2<Bezier> bez; + if (domain.min() != 0 || domain.max() != 1) { + bez = portion(input, domain) - p; + partial = true; + } else { + bez = input - p; + } + + // find extrema of the function x(t)^2 + y(t)^2 + // use the fact that (f^2)' = 2 f f' + // this reduces the order of the distance function by 1 + D2<Bezier> deriv = derivative(bez); + std::vector<Coord> ts = (multiply(bez[X], deriv[X]) + multiply(bez[Y], deriv[Y])).roots(); + + Coord t = -1, mind = infinity(); + for (unsigned i = 0; i < ts.size(); ++i) { + Coord droot = L2sq(bez.valueAt(ts[i])); + if (droot < mind) { + mind = droot; + t = ts[i]; + } + } + + // also check endpoints + Coord dinitial = L2sq(bez.at0()); + Coord dfinal = L2sq(bez.at1()); + + if (dinitial < mind) { + mind = dinitial; + t = 0; + } + if (dfinal < mind) { + //mind = dfinal; + t = 1; + } + + if (partial) { + t = domain.valueAt(t); + } + return t; +} + +//////////////////////////////////////////////////////////////////////////////// +// D2<SBasis> versions + +/* + * Return the parameter t of the nearest time value 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. + * The function return the first nearest time value to "p" that is found. + */ + +double nearest_time(Point const& p, + D2<SBasis> const& c, + D2<SBasis> const& dc, + double from, double to ) +{ + if ( from > to ) std::swap(from, to); + if ( from < 0 || to > 1 ) + { + THROW_RANGEERROR("[from,to] interval out of bounds"); + } + if (c.isConstant()) return from; + SBasis dd = dot(c - p, dc); + //std::cout << dd << std::endl; + std::vector<double> zeros = Geom::roots(dd); + + double closest = from; + double min_dist_sq = L2sq(c(from) - p); + for ( size_t i = 0; i < zeros.size(); ++i ) + { + double distsq = L2sq(c(zeros[i]) - p); + if ( min_dist_sq > L2sq(c(zeros[i]) - p) ) + { + closest = zeros[i]; + min_dist_sq = distsq; + } + } + if ( min_dist_sq > L2sq( c(to) - p ) ) + closest = to; + return closest; + +} + +/* + * Return the parameters t of all the nearest points 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, double to) +{ + if (from > to) { + std::swap(from, to); + } + if (from < 0 || to > 1) { + THROW_RANGEERROR("[from,to] interval out of bounds"); + } + + std::vector<double> result; + if (c.isConstant()) { + result.push_back(from); + return result; + } + SBasis dd = dot(c - p, dc); + + std::vector<double> zeros = Geom::roots(dd); + std::vector<double> candidates; + candidates.push_back(from); + candidates.insert(candidates.end(), zeros.begin(), zeros.end()); + candidates.push_back(to); + std::vector<double> distsq; + distsq.reserve(candidates.size()); + for (unsigned i = 0; i < candidates.size(); ++i) { + distsq.push_back(L2sq(c(candidates[i]) - p)); + } + unsigned closest = 0; + double dsq = distsq[0]; + for (unsigned i = 1; i < candidates.size(); ++i) { + if (dsq > distsq[i]) { + closest = i; + dsq = distsq[i]; + } + } + for (unsigned i = 0; i < candidates.size(); ++i) { + if (distsq[closest] == distsq[i]) { + result.push_back(candidates[i]); + } + } + return result; +} + + +//////////////////////////////////////////////////////////////////////////////// +// Piecewise< D2<SBasis> > versions + + +double nearest_time(Point const &p, + Piecewise< D2<SBasis> > const &c, + double from, double to) +{ + if (from > to) std::swap(from, to); + if (from < c.cuts[0] || to > c.cuts[c.size()]) { + THROW_RANGEERROR("[from,to] interval out of bounds"); + } + + unsigned si = c.segN(from); + unsigned ei = c.segN(to); + if (si == ei) { + double nearest = + nearest_time(p, c[si], c.segT(from, si), c.segT(to, si)); + return c.mapToDomain(nearest, si); + } + + double t; + double nearest = nearest_time(p, c[si], c.segT(from, si)); + unsigned int ni = si; + double dsq; + double mindistsq = distanceSq(p, c[si](nearest)); + Rect bb; + for (unsigned i = si + 1; i < ei; ++i) { + bb = *bounds_fast(c[i]); + dsq = distanceSq(p, bb); + if ( mindistsq <= dsq ) continue; + + t = nearest_time(p, c[i]); + dsq = distanceSq(p, c[i](t)); + if (mindistsq > dsq) { + nearest = t; + ni = i; + mindistsq = dsq; + } + } + bb = *bounds_fast(c[ei]); + dsq = distanceSq(p, bb); + if (mindistsq > dsq) { + t = nearest_time(p, c[ei], 0, c.segT(to, ei)); + dsq = distanceSq(p, c[ei](t)); + if (mindistsq > dsq) { + nearest = t; + ni = ei; + } + } + return c.mapToDomain(nearest, ni); +} + +std::vector<double> +all_nearest_times(Point const &p, + Piecewise< D2<SBasis> > const &c, + double from, double to) +{ + if (from > to) { + std::swap(from, to); + } + if (from < c.cuts[0] || to > c.cuts[c.size()]) { + THROW_RANGEERROR("[from,to] interval out of bounds"); + } + + unsigned si = c.segN(from); + unsigned ei = c.segN(to); + if ( si == ei ) + { + std::vector<double> all_nearest = + all_nearest_times(p, c[si], c.segT(from, si), c.segT(to, si)); + for ( unsigned int i = 0; i < all_nearest.size(); ++i ) + { + all_nearest[i] = c.mapToDomain(all_nearest[i], si); + } + return all_nearest; + } + std::vector<double> all_t; + std::vector< std::vector<double> > all_np; + all_np.push_back( all_nearest_times(p, c[si], c.segT(from, si)) ); + std::vector<unsigned> ni; + ni.push_back(si); + double dsq; + double mindistsq = distanceSq( p, c[si](all_np.front().front()) ); + Rect bb; + + for (unsigned i = si + 1; i < ei; ++i) { + bb = *bounds_fast(c[i]); + dsq = distanceSq(p, bb); + if ( mindistsq < dsq ) continue; + all_t = all_nearest_times(p, c[i]); + dsq = distanceSq( p, c[i](all_t.front()) ); + if ( mindistsq > dsq ) + { + all_np.clear(); + all_np.push_back(all_t); + ni.clear(); + ni.push_back(i); + mindistsq = dsq; + } + else if ( mindistsq == dsq ) + { + all_np.push_back(all_t); + ni.push_back(i); + } + } + bb = *bounds_fast(c[ei]); + dsq = distanceSq(p, bb); + if (mindistsq >= dsq) { + all_t = all_nearest_times(p, c[ei], 0, c.segT(to, ei)); + dsq = distanceSq( p, c[ei](all_t.front()) ); + if (mindistsq > dsq) { + for (unsigned int i = 0; i < all_t.size(); ++i) { + all_t[i] = c.mapToDomain(all_t[i], ei); + } + return all_t; + } else if (mindistsq == dsq) { + all_np.push_back(all_t); + ni.push_back(ei); + } + } + std::vector<double> all_nearest; + for (unsigned i = 0; i < all_np.size(); ++i) { + for (unsigned int j = 0; j < all_np[i].size(); ++j) { + all_nearest.push_back( c.mapToDomain(all_np[i][j], ni[i]) ); + } + } + all_nearest.erase(std::unique(all_nearest.begin(), all_nearest.end()), + all_nearest.end()); + return all_nearest; +} + +} // end namespace Geom + + diff --git a/src/2geom/nearest-time.h b/src/2geom/nearest-time.h new file mode 100644 index 0000000..007cd27 --- /dev/null +++ b/src/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/src/2geom/numeric/fitting-model.h b/src/2geom/numeric/fitting-model.h new file mode 100644 index 0000000..0316f57 --- /dev/null +++ b/src/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/src/2geom/numeric/fitting-tool.h b/src/2geom/numeric/fitting-tool.h new file mode 100644 index 0000000..f2e856a --- /dev/null +++ b/src/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); + } + + virtual + ~lsf_with_fixed_terms<model_type, true>() + { + 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/src/2geom/numeric/linear_system.h b/src/2geom/numeric/linear_system.h new file mode 100644 index 0000000..f793e20 --- /dev/null +++ b/src/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/src/2geom/numeric/matrix.cpp b/src/2geom/numeric/matrix.cpp new file mode 100644 index 0000000..98ff3b6 --- /dev/null +++ b/src/2geom/numeric/matrix.cpp @@ -0,0 +1,154 @@ +/* + * 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. + */ + + +#include <2geom/numeric/matrix.h> +#include <2geom/numeric/vector.h> + + +namespace Geom { namespace NL { + +Vector operator*( detail::BaseMatrixImpl const& A, + detail::BaseVectorImpl const& v ) +{ + assert(A.columns() == v.size()); + + Vector result(A.rows(), 0.0); + for (size_t i = 0; i < A.rows(); ++i) + for (size_t j = 0; j < A.columns(); ++j) + result[i] += A(i,j) * v[j]; + + return result; +} + +Matrix operator*( detail::BaseMatrixImpl const& A, + detail::BaseMatrixImpl const& B ) +{ + assert(A.columns() == B.rows()); + + Matrix C(A.rows(), B.columns(), 0.0); + for (size_t i = 0; i < C.rows(); ++i) + for (size_t j = 0; j < C.columns(); ++j) + for (size_t k = 0; k < A.columns(); ++k) + C(i,j) += A(i,k) * B(k, j); + + return C; +} + +Matrix pseudo_inverse(detail::BaseMatrixImpl const& A) +{ + + Matrix U(A); + Matrix V(A.columns(), A.columns()); + Vector s(A.columns()); + gsl_vector* work = gsl_vector_alloc(A.columns()); + + gsl_linalg_SV_decomp( U.get_gsl_matrix(), + V.get_gsl_matrix(), + s.get_gsl_vector(), + work ); + + Matrix P(A.columns(), A.rows(), 0.0); + + int sz = s.size(); + while ( sz-- > 0 && s[sz] == 0 ) {} + ++sz; + if (sz == 0) return P; + VectorView sv(s, sz); + + for (size_t i = 0; i < sv.size(); ++i) + { + VectorView v = V.column_view(i); + v.scale(1/sv[i]); + for (size_t h = 0; h < P.rows(); ++h) + for (size_t k = 0; k < P.columns(); ++k) + P(h,k) += V(h,i) * U(k,i); + } + + return P; +} + + +double trace (detail::BaseMatrixImpl const& A) +{ + if (A.rows() != A.columns()) + { + THROW_RANGEERROR ("NL::Matrix: computing trace: " + "rows() != columns()"); + } + double t = 0; + for (size_t i = 0; i < A.rows(); ++i) + { + t += A(i,i); + } + return t; +} + + +double det (detail::BaseMatrixImpl const& A) +{ + if (A.rows() != A.columns()) + { + THROW_RANGEERROR ("NL::Matrix: computing determinant: " + "rows() != columns()"); + } + + Matrix LU(A); + int s; + gsl_permutation * p = gsl_permutation_alloc(LU.rows()); + gsl_linalg_LU_decomp (LU.get_gsl_matrix(), p, &s); + + double t = 1; + for (size_t i = 0; i < LU.rows(); ++i) + { + t *= LU(i,i); + } + + gsl_permutation_free(p); + return t; +} + + +} } // end namespaces + +/* + 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/src/2geom/numeric/matrix.h b/src/2geom/numeric/matrix.h new file mode 100644 index 0000000..5cebf10 --- /dev/null +++ b/src/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; + } + + virtual ~Matrix() + { + 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/src/2geom/numeric/symmetric-matrix-fs-operation.h b/src/2geom/numeric/symmetric-matrix-fs-operation.h new file mode 100644 index 0000000..c5aaa72 --- /dev/null +++ b/src/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/src/2geom/numeric/symmetric-matrix-fs-trace.h b/src/2geom/numeric/symmetric-matrix-fs-trace.h new file mode 100644 index 0000000..eff3dd2 --- /dev/null +++ b/src/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 (size_t i = 0; i < 6; ++i) + { + d += t[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 (size_t i = 0; i < 5; ++i) + { + d += t[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/src/2geom/numeric/symmetric-matrix-fs.h b/src/2geom/numeric/symmetric-matrix-fs.h new file mode 100644 index 0000000..2fadd69 --- /dev/null +++ b/src/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/src/2geom/numeric/vector.h b/src/2geom/numeric/vector.h new file mode 100644 index 0000000..f28289f --- /dev/null +++ b/src/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()); + } + + virtual ~Vector() + { + 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/src/2geom/ord.h b/src/2geom/ord.h new file mode 100644 index 0000000..e190a4a --- /dev/null +++ b/src/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/src/2geom/path-intersection.cpp b/src/2geom/path-intersection.cpp new file mode 100644 index 0000000..07e38ba --- /dev/null +++ b/src/2geom/path-intersection.cpp @@ -0,0 +1,730 @@ +#include <2geom/path-intersection.h> + +#include <2geom/ord.h> + +//for path_direction: +#include <2geom/sbasis-geometric.h> +#include <2geom/line.h> +#ifdef HAVE_GSL +#include <gsl/gsl_vector.h> +#include <gsl/gsl_multiroots.h> +#endif + +namespace Geom { + +/// Compute winding number of the path at the specified point +int winding(Path const &path, Point const &p) { + return path.winding(p); +} + +/** + * This function should only be applied to simple paths (regions), as otherwise + * a boolean winding direction is undefined. It returns true for fill, false for + * hole. Defaults to using the sign of area when it reaches funny cases. + */ +bool path_direction(Path const &p) { + if(p.empty()) return false; + + /*goto doh; + //could probably be more efficient, but this is a quick job + double y = p.initialPoint()[Y]; + double x = p.initialPoint()[X]; + Cmp res = cmp(p[0].finalPoint()[Y], y); + for(unsigned i = 1; i < p.size(); i++) { + Cmp final_to_ray = cmp(p[i].finalPoint()[Y], y); + Cmp initial_to_ray = cmp(p[i].initialPoint()[Y], y); + // if y is included, these will have opposite values, giving order. + Cmp c = cmp(final_to_ray, initial_to_ray); + if(c != EQUAL_TO) { + std::vector<double> rs = p[i].roots(y, Y); + for(unsigned j = 0; j < rs.size(); j++) { + double nx = p[i].valueAt(rs[j], X); + if(nx > x) { + x = nx; + res = c; + } + } + } else if(final_to_ray == EQUAL_TO) goto doh; + } + return res < 0; + + doh:*/ + //Otherwise fallback on area + + Piecewise<D2<SBasis> > pw = p.toPwSb(); + double area; + Point centre; + Geom::centroid(pw, centre, area); + return area > 0; +} + +//pair intersect code based on njh's pair-intersect + +/** A little sugar for appending a list to another */ +template<typename T> +void append(T &a, T const &b) { + a.insert(a.end(), b.begin(), b.end()); +} + +/** + * Finds the intersection between the lines defined by A0 & A1, and B0 & B1. + * Returns through the last 3 parameters, returning the t-values on the lines + * and the cross-product of the deltas (a useful byproduct). The return value + * indicates if the time values are within their proper range on the line segments. + */ +bool +linear_intersect(Point const &A0, Point const &A1, Point const &B0, Point const &B1, + double &tA, double &tB, double &det) { + bool both_lines_non_zero = (!are_near(A0, A1)) && (!are_near(B0, B1)); + + // Cramer's rule as cross products + Point Ad = A1 - A0, + Bd = B1 - B0, + d = B0 - A0; + det = cross(Ad, Bd); + + double det_rel = det; // Calculate the determinant of the normalized vectors + if (both_lines_non_zero) { + det_rel /= Ad.length(); + det_rel /= Bd.length(); + } + + if( fabs(det_rel) < 1e-12 ) { // If the cross product is NEARLY zero, + // Then one of the linesegments might have length zero + if (both_lines_non_zero) { + // If that's not the case, then we must have either: + // - parallel lines, with no intersections, or + // - coincident lines, with an infinite number of intersections + // Either is quite useless, so we'll just bail out + return false; + } // Else, one of the linesegments is zero, and we might still be able to calculate a single intersection point + } // Else we haven't bailed out, and we'll try to calculate the intersections + + double detinv = 1.0 / det; + tA = cross(d, Bd) * detinv; + tB = cross(d, Ad) * detinv; + return (tA >= 0.) && (tA <= 1.) && (tB >= 0.) && (tB <= 1.); +} + + +#if 0 +typedef union dbl_64{ + long long i64; + double d64; +}; + +static double EpsilonOf(double value) +{ + dbl_64 s; + s.d64 = value; + if(s.i64 == 0) + { + s.i64++; + return s.d64 - value; + } + else if(s.i64-- < 0) + return s.d64 - value; + else + return value - s.d64; +} +#endif + +#ifdef HAVE_GSL +struct rparams { + Curve const &A; + Curve const &B; +}; + +static int +intersect_polish_f (const gsl_vector * x, void *params, + gsl_vector * f) +{ + const double x0 = gsl_vector_get (x, 0); + const double x1 = gsl_vector_get (x, 1); + + Geom::Point dx = ((struct rparams *) params)->A(x0) - + ((struct rparams *) params)->B(x1); + + gsl_vector_set (f, 0, dx[0]); + gsl_vector_set (f, 1, dx[1]); + + return GSL_SUCCESS; +} +#endif + +static void +intersect_polish_root (Curve const &A, double &s, Curve const &B, double &t) +{ + std::vector<Point> as, bs; + as = A.pointAndDerivatives(s, 2); + bs = B.pointAndDerivatives(t, 2); + Point F = as[0] - bs[0]; + double best = dot(F, F); + + for(int i = 0; i < 4; i++) { + + /** + we want to solve + J*(x1 - x0) = f(x0) + + |dA(s)[0] -dB(t)[0]| (X1 - X0) = A(s) - B(t) + |dA(s)[1] -dB(t)[1]| + **/ + + // We're using the standard transformation matricies, which is numerically rather poor. Much better to solve the equation using elimination. + + Affine jack(as[1][0], as[1][1], + -bs[1][0], -bs[1][1], + 0, 0); + Point soln = (F)*jack.inverse(); + double ns = s - soln[0]; + double nt = t - soln[1]; + + if (ns<0) ns=0; + else if (ns>1) ns=1; + if (nt<0) nt=0; + else if (nt>1) nt=1; + + as = A.pointAndDerivatives(ns, 2); + bs = B.pointAndDerivatives(nt, 2); + F = as[0] - bs[0]; + double trial = dot(F, F); + if (trial > best*0.1) // we have standards, you know + // At this point we could do a line search + break; + best = trial; + s = ns; + t = nt; + } + +#ifdef HAVE_GSL + if(0) { // the GSL version is more accurate, but taints this with GPL + int status; + size_t iter = 0; + const size_t n = 2; + struct rparams p = {A, B}; + gsl_multiroot_function f = {&intersect_polish_f, n, &p}; + + double x_init[2] = {s, t}; + gsl_vector *x = gsl_vector_alloc (n); + + gsl_vector_set (x, 0, x_init[0]); + gsl_vector_set (x, 1, x_init[1]); + + const gsl_multiroot_fsolver_type *T = gsl_multiroot_fsolver_hybrids; + gsl_multiroot_fsolver *sol = gsl_multiroot_fsolver_alloc (T, 2); + gsl_multiroot_fsolver_set (sol, &f, x); + + do + { + iter++; + status = gsl_multiroot_fsolver_iterate (sol); + + if (status) /* check if solver is stuck */ + break; + + status = + gsl_multiroot_test_residual (sol->f, 1e-12); + } + while (status == GSL_CONTINUE && iter < 1000); + + s = gsl_vector_get (sol->x, 0); + t = gsl_vector_get (sol->x, 1); + + gsl_multiroot_fsolver_free (sol); + gsl_vector_free (x); + } +#endif +} + +/** + * This uses the local bounds functions of curves to generically intersect two. + * It passes in the curves, time intervals, and keeps track of depth, while + * returning the results through the Crossings parameter. + */ +void pair_intersect(Curve const & A, double Al, double Ah, + Curve const & B, double Bl, double Bh, + Crossings &ret, unsigned depth = 0) { + // std::cout << depth << "(" << Al << ", " << Ah << ")\n"; + OptRect Ar = A.boundsLocal(Interval(Al, Ah)); + if (!Ar) return; + + OptRect Br = B.boundsLocal(Interval(Bl, Bh)); + if (!Br) return; + + if(! Ar->intersects(*Br)) return; + + //Checks the general linearity of the function + if((depth > 12)) { // || (A.boundsLocal(Interval(Al, Ah), 1).maxExtent() < 0.1 + //&& B.boundsLocal(Interval(Bl, Bh), 1).maxExtent() < 0.1)) { + double tA, tB, c; + if(linear_intersect(A.pointAt(Al), A.pointAt(Ah), + B.pointAt(Bl), B.pointAt(Bh), + tA, tB, c)) { + tA = tA * (Ah - Al) + Al; + tB = tB * (Bh - Bl) + Bl; + intersect_polish_root(A, tA, + B, tB); + if(depth % 2) + ret.push_back(Crossing(tB, tA, c < 0)); + else + ret.push_back(Crossing(tA, tB, c > 0)); + return; + } + } + if(depth > 12) return; + double mid = (Bl + Bh)/2; + pair_intersect(B, Bl, mid, + A, Al, Ah, + ret, depth+1); + pair_intersect(B, mid, Bh, + A, Al, Ah, + ret, depth+1); +} + +Crossings pair_intersect(Curve const & A, Interval const &Ad, + Curve const & B, Interval const &Bd) { + Crossings ret; + pair_intersect(A, Ad.min(), Ad.max(), B, Bd.min(), Bd.max(), ret); + return ret; +} + +/** A simple wrapper around pair_intersect */ +Crossings SimpleCrosser::crossings(Curve const &a, Curve const &b) { + Crossings ret; + pair_intersect(a, 0, 1, b, 0, 1, ret); + return ret; +} + + +//same as below but curves not paths +void mono_intersect(Curve const &A, double Al, double Ah, + Curve const &B, double Bl, double Bh, + Crossings &ret, double tol = 0.1, unsigned depth = 0) { + if( Al >= Ah || Bl >= Bh) return; + //std::cout << " " << depth << "[" << Al << ", " << Ah << "]" << "[" << Bl << ", " << Bh << "]"; + + Point A0 = A.pointAt(Al), A1 = A.pointAt(Ah), + B0 = B.pointAt(Bl), B1 = B.pointAt(Bh); + //inline code that this implies? (without rect/interval construction) + Rect Ar = Rect(A0, A1), Br = Rect(B0, B1); + if(!Ar.intersects(Br) || A0 == A1 || B0 == B1) return; + + if(depth > 12 || (Ar.maxExtent() < tol && Ar.maxExtent() < tol)) { + double tA, tB, c; + if(linear_intersect(A.pointAt(Al), A.pointAt(Ah), + B.pointAt(Bl), B.pointAt(Bh), + tA, tB, c)) { + tA = tA * (Ah - Al) + Al; + tB = tB * (Bh - Bl) + Bl; + intersect_polish_root(A, tA, + B, tB); + if(depth % 2) + ret.push_back(Crossing(tB, tA, c < 0)); + else + ret.push_back(Crossing(tA, tB, c > 0)); + return; + } + } + if(depth > 12) return; + double mid = (Bl + Bh)/2; + mono_intersect(B, Bl, mid, + A, Al, Ah, + ret, tol, depth+1); + mono_intersect(B, mid, Bh, + A, Al, Ah, + ret, tol, depth+1); +} + +Crossings mono_intersect(Curve const & A, Interval const &Ad, + Curve const & B, Interval const &Bd) { + Crossings ret; + mono_intersect(A, Ad.min(), Ad.max(), B, Bd.min(), Bd.max(), ret); + return ret; +} + +/** + * Takes two paths and time ranges on them, with the invariant that the + * paths are monotonic on the range. Splits A when the linear intersection + * doesn't exist or is inaccurate. Uses the fact that it is monotonic to + * do very fast local bounds. + */ +void mono_pair(Path const &A, double Al, double Ah, + Path const &B, double Bl, double Bh, + Crossings &ret, double /*tol*/, unsigned depth = 0) { + if( Al >= Ah || Bl >= Bh) return; + std::cout << " " << depth << "[" << Al << ", " << Ah << "]" << "[" << Bl << ", " << Bh << "]"; + + Point A0 = A.pointAt(Al), A1 = A.pointAt(Ah), + B0 = B.pointAt(Bl), B1 = B.pointAt(Bh); + //inline code that this implies? (without rect/interval construction) + Rect Ar = Rect(A0, A1), Br = Rect(B0, B1); + if(!Ar.intersects(Br) || A0 == A1 || B0 == B1) return; + + if(depth > 12 || (Ar.maxExtent() < 0.1 && Ar.maxExtent() < 0.1)) { + double tA, tB, c; + if(linear_intersect(A0, A1, B0, B1, + tA, tB, c)) { + tA = tA * (Ah - Al) + Al; + tB = tB * (Bh - Bl) + Bl; + if(depth % 2) + ret.push_back(Crossing(tB, tA, c < 0)); + else + ret.push_back(Crossing(tA, tB, c > 0)); + return; + } + } + if(depth > 12) return; + double mid = (Bl + Bh)/2; + mono_pair(B, Bl, mid, + A, Al, Ah, + ret, depth+1); + mono_pair(B, mid, Bh, + A, Al, Ah, + ret, depth+1); +} + +/** This returns the times when the x or y derivative is 0 in the curve. */ +std::vector<double> curve_mono_splits(Curve const &d) { + Curve* deriv = d.derivative(); + std::vector<double> rs = deriv->roots(0, X); + append(rs, deriv->roots(0, Y)); + delete deriv; + std::sort(rs.begin(), rs.end()); + return rs; +} + +/** Convenience function to add a value to each entry in a vector of doubles. */ +std::vector<double> offset_doubles(std::vector<double> const &x, double offs) { + std::vector<double> ret; + for(unsigned i = 0; i < x.size(); i++) { + ret.push_back(x[i] + offs); + } + return ret; +} + +/** + * Finds all the monotonic splits for a path. Only includes the split between + * curves if they switch derivative directions at that point. + */ +std::vector<double> path_mono_splits(Path const &p) { + std::vector<double> ret; + if(p.empty()) return ret; + + bool pdx=2, pdy=2; //Previous derivative direction + for(unsigned i = 0; i < p.size(); i++) { + std::vector<double> spl = offset_doubles(curve_mono_splits(p[i]), i); + bool dx = p[i].initialPoint()[X] > (spl.empty()? p[i].finalPoint()[X] : + p.valueAt(spl.front(), X)); + bool dy = p[i].initialPoint()[Y] > (spl.empty()? p[i].finalPoint()[Y] : + p.valueAt(spl.front(), Y)); + //The direction changed, include the split time + if(dx != pdx || dy != pdy) { + ret.push_back(i); + pdx = dx; pdy = dy; + } + append(ret, spl); + } + return ret; +} + +/** + * Applies path_mono_splits to multiple paths, and returns the results such that + * time-set i corresponds to Path i. + */ +std::vector<std::vector<double> > paths_mono_splits(PathVector const &ps) { + std::vector<std::vector<double> > ret; + for(unsigned i = 0; i < ps.size(); i++) + ret.push_back(path_mono_splits(ps[i])); + return ret; +} + +/** + * Processes the bounds for a list of paths and a list of splits on them, yielding a list of rects for each. + * Each entry i corresponds to path i of the input. The number of rects in each entry is guaranteed to be the + * number of splits for that path, subtracted by one. + */ +std::vector<std::vector<Rect> > split_bounds(PathVector const &p, std::vector<std::vector<double> > splits) { + std::vector<std::vector<Rect> > ret; + for(unsigned i = 0; i < p.size(); i++) { + std::vector<Rect> res; + for(unsigned j = 1; j < splits[i].size(); j++) + res.push_back(Rect(p[i].pointAt(splits[i][j-1]), p[i].pointAt(splits[i][j]))); + ret.push_back(res); + } + return ret; +} + +/** + * This is the main routine of "MonoCrosser", and implements a monotonic strategy on multiple curves. + * Finds crossings between two sets of paths, yielding a CrossingSet. [0, a.size()) of the return correspond + * to the sorted crossings of a with paths of b. The rest of the return, [a.size(), a.size() + b.size()], + * corresponds to the sorted crossings of b with paths of a. + * + * This function does two sweeps, one on the bounds of each path, and after that cull, one on the curves within. + * This leads to a certain amount of code complexity, however, most of that is factored into the above functions + */ +CrossingSet MonoCrosser::crossings(PathVector const &a, PathVector const &b) { + if(b.empty()) return CrossingSet(a.size(), Crossings()); + CrossingSet results(a.size() + b.size(), Crossings()); + if(a.empty()) return results; + + std::vector<std::vector<double> > splits_a = paths_mono_splits(a), splits_b = paths_mono_splits(b); + std::vector<std::vector<Rect> > bounds_a = split_bounds(a, splits_a), bounds_b = split_bounds(b, splits_b); + + std::vector<Rect> bounds_a_union, bounds_b_union; + for(unsigned i = 0; i < bounds_a.size(); i++) bounds_a_union.push_back(union_list(bounds_a[i])); + for(unsigned i = 0; i < bounds_b.size(); i++) bounds_b_union.push_back(union_list(bounds_b[i])); + + std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds_a_union, bounds_b_union); + Crossings n; + 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 res; + + //Sweep of the monotonic portions + std::vector<std::vector<unsigned> > cull2 = sweep_bounds(bounds_a[i], bounds_b[j]); + for(unsigned k = 0; k < cull2.size(); k++) { + for(unsigned lx = 0; lx < cull2[k].size(); lx++) { + unsigned l = cull2[k][lx]; + mono_pair(a[i], splits_a[i][k-1], splits_a[i][k], + b[j], splits_b[j][l-1], splits_b[j][l], + res, .1); + } + } + + for(unsigned k = 0; k < res.size(); k++) { res[k].a = i; res[k].b = jc; } + + merge_crossings(results[i], res, i); + merge_crossings(results[i], res, jc); + } + } + + return results; +} + +/* This function is similar codewise to the MonoCrosser, the main difference is that it deals with + * only one set of paths and includes self intersection +CrossingSet crossings_among(PathVector const &p) { + CrossingSet results(p.size(), Crossings()); + if(p.empty()) return results; + + std::vector<std::vector<double> > splits = paths_mono_splits(p); + std::vector<std::vector<Rect> > prs = split_bounds(p, splits); + std::vector<Rect> rs; + for(unsigned i = 0; i < prs.size(); i++) rs.push_back(union_list(prs[i])); + + std::vector<std::vector<unsigned> > cull = sweep_bounds(rs); + + //we actually want to do the self-intersections, so add em in: + for(unsigned i = 0; i < cull.size(); i++) cull[i].push_back(i); + + for(unsigned i = 0; i < cull.size(); i++) { + for(unsigned jx = 0; jx < cull[i].size(); jx++) { + unsigned j = cull[i][jx]; + Crossings res; + + //Sweep of the monotonic portions + std::vector<std::vector<unsigned> > cull2 = sweep_bounds(prs[i], prs[j]); + for(unsigned k = 0; k < cull2.size(); k++) { + for(unsigned lx = 0; lx < cull2[k].size(); lx++) { + unsigned l = cull2[k][lx]; + mono_pair(p[i], splits[i][k-1], splits[i][k], + p[j], splits[j][l-1], splits[j][l], + res, .1); + } + } + + for(unsigned k = 0; k < res.size(); k++) { res[k].a = i; res[k].b = j; } + + merge_crossings(results[i], res, i); + merge_crossings(results[j], res, j); + } + } + + return results; +} +*/ + + +Crossings curve_self_crossings(Curve const &a) { + Crossings res; + std::vector<double> spl; + spl.push_back(0); + append(spl, curve_mono_splits(a)); + spl.push_back(1); + for(unsigned i = 1; i < spl.size(); i++) + for(unsigned j = i+1; j < spl.size(); j++) + pair_intersect(a, spl[i-1], spl[i], a, spl[j-1], spl[j], res); + return res; +} + +/* +void mono_curve_intersect(Curve const & A, double Al, double Ah, + Curve const & B, double Bl, double Bh, + Crossings &ret, unsigned depth=0) { + // std::cout << depth << "(" << Al << ", " << Ah << ")\n"; + Point A0 = A.pointAt(Al), A1 = A.pointAt(Ah), + B0 = B.pointAt(Bl), B1 = B.pointAt(Bh); + //inline code that this implies? (without rect/interval construction) + if(!Rect(A0, A1).intersects(Rect(B0, B1)) || A0 == A1 || B0 == B1) return; + + //Checks the general linearity of the function + if((depth > 12) || (A.boundsLocal(Interval(Al, Ah), 1).maxExtent() < 0.1 + && B.boundsLocal(Interval(Bl, Bh), 1).maxExtent() < 0.1)) { + double tA, tB, c; + if(linear_intersect(A0, A1, B0, B1, tA, tB, c)) { + tA = tA * (Ah - Al) + Al; + tB = tB * (Bh - Bl) + Bl; + if(depth % 2) + ret.push_back(Crossing(tB, tA, c < 0)); + else + ret.push_back(Crossing(tA, tB, c > 0)); + return; + } + } + if(depth > 12) return; + double mid = (Bl + Bh)/2; + mono_curve_intersect(B, Bl, mid, + A, Al, Ah, + ret, depth+1); + mono_curve_intersect(B, mid, Bh, + A, Al, Ah, + ret, depth+1); +} + +std::vector<std::vector<double> > curves_mono_splits(Path const &p) { + std::vector<std::vector<double> > ret; + for(unsigned i = 0; i <= p.size(); i++) { + std::vector<double> spl; + spl.push_back(0); + append(spl, curve_mono_splits(p[i])); + spl.push_back(1); + ret.push_back(spl); + } +} + +std::vector<std::vector<Rect> > curves_split_bounds(Path const &p, std::vector<std::vector<double> > splits) { + std::vector<std::vector<Rect> > ret; + for(unsigned i = 0; i < splits.size(); i++) { + std::vector<Rect> res; + for(unsigned j = 1; j < splits[i].size(); j++) + res.push_back(Rect(p.pointAt(splits[i][j-1]+i), p.pointAt(splits[i][j]+i))); + ret.push_back(res); + } + return ret; +} + +Crossings path_self_crossings(Path const &p) { + Crossings ret; + std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(p)); + std::vector<std::vector<double> > spl = curves_mono_splits(p); + std::vector<std::vector<Rect> > bnds = curves_split_bounds(p, spl); + for(unsigned i = 0; i < cull.size(); i++) { + Crossings res; + for(unsigned k = 1; k < spl[i].size(); k++) + for(unsigned l = k+1; l < spl[i].size(); l++) + mono_curve_intersect(p[i], spl[i][k-1], spl[i][k], p[i], spl[i][l-1], spl[i][l], res); + offset_crossings(res, i, i); + append(ret, res); + for(unsigned jx = 0; jx < cull[i].size(); jx++) { + unsigned j = cull[i][jx]; + res.clear(); + + std::vector<std::vector<unsigned> > cull2 = sweep_bounds(bnds[i], bnds[j]); + for(unsigned k = 0; k < cull2.size(); k++) { + for(unsigned lx = 0; lx < cull2[k].size(); lx++) { + unsigned l = cull2[k][lx]; + mono_curve_intersect(p[i], spl[i][k-1], spl[i][k], p[j], spl[j][l-1], spl[j][l], res); + } + } + + //if(fabs(int(i)-j) == 1 || fabs(int(i)-j) == p.size()-1) { + Crossings res2; + for(unsigned k = 0; k < res.size(); k++) { + if(res[k].ta != 0 && res[k].ta != 1 && res[k].tb != 0 && res[k].tb != 1) { + res.push_back(res[k]); + } + } + res = res2; + //} + offset_crossings(res, i, j); + append(ret, res); + } + } + return ret; +} +*/ + +Crossings self_crossings(Path const &p) { + Crossings ret; + std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(p)); + for(unsigned i = 0; i < cull.size(); i++) { + Crossings res = curve_self_crossings(p[i]); + offset_crossings(res, i, i); + append(ret, res); + for(unsigned jx = 0; jx < cull[i].size(); jx++) { + unsigned j = cull[i][jx]; + res.clear(); + pair_intersect(p[i], 0, 1, p[j], 0, 1, res); + + //if(fabs(int(i)-j) == 1 || fabs(int(i)-j) == p.size()-1) { + Crossings res2; + for(unsigned k = 0; k < res.size(); k++) { + if(res[k].ta != 0 && res[k].ta != 1 && res[k].tb != 0 && res[k].tb != 1) { + res2.push_back(res[k]); + } + } + res = res2; + //} + offset_crossings(res, i, j); + append(ret, res); + } + } + return ret; +} + +void flip_crossings(Crossings &crs) { + for(unsigned i = 0; i < crs.size(); i++) + crs[i] = Crossing(crs[i].tb, crs[i].ta, crs[i].b, crs[i].a, !crs[i].dir); +} + +CrossingSet crossings_among(PathVector const &p) { + CrossingSet results(p.size(), Crossings()); + if(p.empty()) return results; + + SimpleCrosser cc; + + std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(p)); + for(unsigned i = 0; i < cull.size(); i++) { + Crossings res = self_crossings(p[i]); + for(unsigned k = 0; k < res.size(); k++) { res[k].a = res[k].b = i; } + merge_crossings(results[i], res, i); + flip_crossings(res); + merge_crossings(results[i], res, i); + for(unsigned jx = 0; jx < cull[i].size(); jx++) { + unsigned j = cull[i][jx]; + + Crossings res = cc.crossings(p[i], p[j]); + for(unsigned k = 0; k < res.size(); k++) { res[k].a = i; res[k].b = j; } + merge_crossings(results[i], res, i); + merge_crossings(results[j], res, j); + } + } + return results; +} + +} + +/* + 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/src/2geom/path-intersection.h b/src/2geom/path-intersection.h new file mode 100644 index 0000000..f06eeaf --- /dev/null +++ b/src/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) { return curve_sweep<SimpleCrosser>(a, b); } + CrossingSet crossings(PathVector const &a, PathVector const &b) { return Crosser<Path>::crossings(a, b); } +}; + +struct MonoCrosser : public Crosser<Path> { + Crossings crossings(Path const &a, Path const &b) { return crossings(PathVector(a), PathVector(b))[0]; } + CrossingSet crossings(PathVector const &a, PathVector const &b); +}; + +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/src/2geom/path-sink.cpp b/src/2geom/path-sink.cpp new file mode 100644 index 0000000..77301b7 --- /dev/null +++ b/src/2geom/path-sink.cpp @@ -0,0 +1,104 @@ +/* + * 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, 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. + * + */ + +#include <2geom/sbasis-to-bezier.h> +#include <2geom/path-sink.h> +#include <2geom/exception.h> +#include <2geom/circle.h> +#include <2geom/ellipse.h> + +namespace Geom { + +void PathSink::feed(Curve const &c, bool moveto_initial) +{ + c.feed(*this, moveto_initial); +} + +void PathSink::feed(Path const &path) { + flush(); + moveTo(path.front().initialPoint()); + + // never output the closing segment to the sink + Path::const_iterator iter = path.begin(), last = path.end_open(); + for (; iter != last; ++iter) { + iter->feed(*this, false); + } + if (path.closed()) { + closePath(); + } + flush(); +} + +void PathSink::feed(PathVector const &pv) { + for (PathVector::const_iterator i = pv.begin(); i != pv.end(); ++i) { + feed(*i); + } +} + +void PathSink::feed(Rect const &r) { + moveTo(r.corner(0)); + lineTo(r.corner(1)); + lineTo(r.corner(2)); + lineTo(r.corner(3)); + closePath(); +} + +void PathSink::feed(Circle const &e) { + Coord r = e.radius(); + Point c = e.center(); + Point a = c + Point(0, +r); + Point b = c + Point(0, -r); + + moveTo(a); + arcTo(r, r, 0, false, false, b); + arcTo(r, r, 0, false, false, a); + closePath(); +} + +void PathSink::feed(Ellipse const &e) { + Point s = e.pointAt(0); + moveTo(s); + arcTo(e.ray(X), e.ray(Y), e.rotationAngle(), false, false, e.pointAt(M_PI)); + arcTo(e.ray(X), e.ray(Y), e.rotationAngle(), false, false, s); + closePath(); +} + +} + +/* + 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/src/2geom/path-sink.h b/src/2geom/path-sink.h new file mode 100644 index 0000000..3bdb007 --- /dev/null +++ b/src/2geom/path-sink.h @@ -0,0 +1,245 @@ +/** + * \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) { + flush(); + _path.start(p); + _start_p = p; + _in_path = true; + } +//TODO: what if _in_path = false? + + void lineTo(Point const &p) { + // 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) { + // 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) { + // 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) + { + // 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() + { + 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() { + if (_in_path) { + _path.close(); + flush(); + } + } + + void flush() { + 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) + { + 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/src/2geom/path.cpp b/src/2geom/path.cpp new file mode 100644 index 0000000..3288eb4 --- /dev/null +++ b/src/2geom/path.cpp @@ -0,0 +1,1128 @@ +/** @file + * @brief Path - a sequence of contiguous curves (implementation file) + *//* + * 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. + */ + +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/transforms.h> +#include <2geom/circle.h> +#include <2geom/ellipse.h> +#include <2geom/convex-hull.h> +#include <2geom/svg-path-writer.h> +#include <2geom/sweeper.h> +#include <algorithm> +#include <limits> + +using std::swap; +using namespace Geom::PathInternal; + +namespace Geom { + +// this represents an empty interval +PathInterval::PathInterval() + : _from(0, 0.0) + , _to(0, 0.0) + , _path_size(1) + , _cross_start(false) + , _reverse(false) +{} + +PathInterval::PathInterval(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size) + : _from(from) + , _to(to) + , _path_size(path_size) + , _cross_start(cross_start) + , _reverse(cross_start ? to >= from : to < from) +{ + if (_reverse) { + _to.normalizeForward(_path_size); + if (_from != _to) { + _from.normalizeBackward(_path_size); + } + } else { + _from.normalizeForward(_path_size); + if (_from != _to) { + _to.normalizeBackward(_path_size); + } + } + + if (_from == _to) { + _reverse = false; + _cross_start = false; + } +} + +bool PathInterval::contains(PathTime const &pos) const { + if (_cross_start) { + if (_reverse) { + return pos >= _to || _from >= pos; + } else { + return pos >= _from || _to >= pos; + } + } else { + if (_reverse) { + return _to <= pos && pos <= _from; + } else { + return _from <= pos && pos <= _to; + } + } +} + +PathInterval::size_type PathInterval::curveCount() const +{ + if (isDegenerate()) return 0; + if (_cross_start) { + if (_reverse) { + return _path_size - _to.curve_index + _from.curve_index + 1; + } else { + return _path_size - _from.curve_index + _to.curve_index + 1; + } + } else { + if (_reverse) { + return _from.curve_index - _to.curve_index + 1; + } else { + return _to.curve_index - _from.curve_index + 1; + } + } +} + +PathTime PathInterval::inside(Coord min_dist) const +{ + // If there is some node further than min_dist (in time coord) from the ends, + // return that node. Otherwise, return the middle. + PathTime result(0, 0.0); + + if (!_cross_start && _from.curve_index == _to.curve_index) { + PathTime result(_from.curve_index, lerp(0.5, _from.t, _to.t)); + return result; + } + // If _cross_start, then we can be sure that at least one node is in the domain. + // If dcurve == 0, it actually means that all curves are included in the domain + + if (_reverse) { + size_type dcurve = (_path_size + _from.curve_index - _to.curve_index) % _path_size; + bool from_close = _from.t < min_dist; + bool to_close = _to.t > 1 - min_dist; + + if (dcurve == 0) { + dcurve = _path_size; + } + + if (dcurve == 1) { + if (from_close || to_close) { + result.curve_index = _from.curve_index; + Coord tmid = _from.t - ((1 - _to.t) + _from.t) * 0.5; + if (tmid < 0) { + result.curve_index = (_path_size + result.curve_index - 1) % _path_size; + tmid += 1; + } + result.t = tmid; + return result; + } + + result.curve_index = _from.curve_index; + return result; + } + + result.curve_index = (_to.curve_index + 1) % _path_size; + if (to_close) { + if (dcurve == 2) { + result.t = 0.5; + } else { + result.curve_index = (result.curve_index + 1) % _path_size; + } + } + return result; + } else { + size_type dcurve = (_path_size + _to.curve_index - _from.curve_index) % _path_size; + bool from_close = _from.t > 1 - min_dist; + bool to_close = _to.t < min_dist; + + if (dcurve == 0) { + dcurve = _path_size; + } + + if (dcurve == 1) { + if (from_close || to_close) { + result.curve_index = _from.curve_index; + Coord tmid = ((1 - _from.t) + _to.t) * 0.5 + _from.t; + if (tmid >= 1) { + result.curve_index = (result.curve_index + 1) % _path_size; + tmid -= 1; + } + result.t = tmid; + return result; + } + + result.curve_index = _to.curve_index; + return result; + } + + result.curve_index = (_from.curve_index + 1) % _path_size; + if (from_close) { + if (dcurve == 2) { + result.t = 0.5; + } else { + result.curve_index = (result.curve_index + 1) % _path_size; + } + } + return result; + } + + result.curve_index = _reverse ? _from.curve_index : _to.curve_index; + return result; +} + +PathInterval PathInterval::from_direction(PathTime const &from, PathTime const &to, bool reversed, size_type path_size) +{ + PathInterval result; + result._from = from; + result._to = to; + result._path_size = path_size; + + if (reversed) { + result._to.normalizeForward(path_size); + if (result._from != result._to) { + result._from.normalizeBackward(path_size); + } + } else { + result._from.normalizeForward(path_size); + if (result._from != result._to) { + result._to.normalizeBackward(path_size); + } + } + + if (result._from == result._to) { + result._reverse = false; + result._cross_start = false; + } else { + result._reverse = reversed; + if (reversed) { + result._cross_start = from < to; + } else { + result._cross_start = to < from; + } + } + return result; +} + + +Path::Path(Rect const &r) + : _data(new PathData()) + , _closing_seg(new ClosingSegment(r.corner(3), r.corner(0))) + , _closed(true) + , _exception_on_stitch(true) +{ + for (unsigned i = 0; i < 3; ++i) { + _data->curves.push_back(new LineSegment(r.corner(i), r.corner(i+1))); + } + _data->curves.push_back(_closing_seg); +} + +Path::Path(ConvexHull const &ch) + : _data(new PathData()) + , _closing_seg(new ClosingSegment(Point(), Point())) + , _closed(true) + , _exception_on_stitch(true) +{ + if (ch.empty()) { + _data->curves.push_back(_closing_seg); + return; + } + + _closing_seg->setInitial(ch.back()); + _closing_seg->setFinal(ch.front()); + + Point last = ch.front(); + + for (std::size_t i = 1; i < ch.size(); ++i) { + _data->curves.push_back(new LineSegment(last, ch[i])); + last = ch[i]; + } + + _data->curves.push_back(_closing_seg); + _closed = true; +} + +Path::Path(Circle const &c) + : _data(new PathData()) + , _closing_seg(NULL) + , _closed(true) + , _exception_on_stitch(true) +{ + Point p1 = c.pointAt(0); + Point p2 = c.pointAt(M_PI); + _data->curves.push_back(new EllipticalArc(p1, c.radius(), c.radius(), 0, false, true, p2)); + _data->curves.push_back(new EllipticalArc(p2, c.radius(), c.radius(), 0, false, true, p1)); + _closing_seg = new ClosingSegment(p1, p1); + _data->curves.push_back(_closing_seg); +} + +Path::Path(Ellipse const &e) + : _data(new PathData()) + , _closing_seg(NULL) + , _closed(true) + , _exception_on_stitch(true) +{ + Point p1 = e.pointAt(0); + Point p2 = e.pointAt(M_PI); + _data->curves.push_back(new EllipticalArc(p1, e.rays(), e.rotationAngle(), false, true, p2)); + _data->curves.push_back(new EllipticalArc(p2, e.rays(), e.rotationAngle(), false, true, p1)); + _closing_seg = new ClosingSegment(p1, p1); + _data->curves.push_back(_closing_seg); +} + +void Path::close(bool c) +{ + if (c == _closed) return; + if (c && _data->curves.size() >= 2) { + // when closing, if last segment is linear and ends at initial point, + // replace it with the closing segment + Sequence::iterator last = _data->curves.end() - 2; + if (last->isLineSegment() && last->finalPoint() == initialPoint()) { + _closing_seg->setInitial(last->initialPoint()); + _data->curves.erase(last); + } + } + _closed = c; +} + +void Path::clear() +{ + _unshare(); + _data->curves.pop_back().release(); + _data->curves.clear(); + _closing_seg->setInitial(Point(0, 0)); + _closing_seg->setFinal(Point(0, 0)); + _data->curves.push_back(_closing_seg); + _closed = false; +} + +OptRect Path::boundsFast() const +{ + OptRect bounds; + if (empty()) { + return bounds; + } + // if the path is not empty, we look for cached bounds + if (_data->fast_bounds) { + return _data->fast_bounds; + } + + bounds = front().boundsFast(); + const_iterator iter = begin(); + // the closing path segment can be ignored, because it will always + // lie within the bbox of the rest of the path + if (iter != end()) { + for (++iter; iter != end(); ++iter) { + bounds.unionWith(iter->boundsFast()); + } + } + _data->fast_bounds = bounds; + return bounds; +} + +OptRect Path::boundsExact() const +{ + OptRect bounds; + if (empty()) + return bounds; + bounds = front().boundsExact(); + const_iterator iter = begin(); + // the closing path segment can be ignored, because it will always lie within the bbox of the rest of the path + if (iter != end()) { + for (++iter; iter != end(); ++iter) { + bounds.unionWith(iter->boundsExact()); + } + } + return bounds; +} + +Piecewise<D2<SBasis> > Path::toPwSb() const +{ + Piecewise<D2<SBasis> > ret; + ret.push_cut(0); + unsigned i = 1; + bool degenerate = true; + // pw<d2<>> is always open. so if path is closed, add closing segment as well to pwd2. + for (const_iterator it = begin(); it != end_default(); ++it) { + if (!it->isDegenerate()) { + ret.push(it->toSBasis(), i++); + degenerate = false; + } + } + if (degenerate) { + // if path only contains degenerate curves, no second cut is added + // so we need to create at least one segment manually + ret = Piecewise<D2<SBasis> >(initialPoint()); + } + return ret; +} + +template <typename iter> +iter inc(iter const &x, unsigned n) { + iter ret = x; + for (unsigned i = 0; i < n; i++) + ret++; + return ret; +} + +bool Path::operator==(Path const &other) const +{ + if (this == &other) + return true; + if (_closed != other._closed) + return false; + return _data->curves == other._data->curves; +} + +void Path::start(Point const &p) { + if (_data->curves.size() > 1) { + clear(); + } + _closing_seg->setInitial(p); + _closing_seg->setFinal(p); +} + +Interval Path::timeRange() const +{ + Interval ret(0, size_default()); + return ret; +} + +Curve const &Path::curveAt(Coord t, Coord *rest) const +{ + PathTime pos = _factorTime(t); + if (rest) { + *rest = pos.t; + } + return at(pos.curve_index); +} + +Point Path::pointAt(Coord t) const +{ + return pointAt(_factorTime(t)); +} + +Coord Path::valueAt(Coord t, Dim2 d) const +{ + return valueAt(_factorTime(t), d); +} + +Curve const &Path::curveAt(PathTime const &pos) const +{ + return at(pos.curve_index); +} +Point Path::pointAt(PathTime const &pos) const +{ + return at(pos.curve_index).pointAt(pos.t); +} +Coord Path::valueAt(PathTime const &pos, Dim2 d) const +{ + return at(pos.curve_index).valueAt(pos.t, d); +} + +std::vector<PathTime> Path::roots(Coord v, Dim2 d) const +{ + std::vector<PathTime> res; + for (unsigned i = 0; i < size(); i++) { + std::vector<Coord> temp = (*this)[i].roots(v, d); + for (unsigned j = 0; j < temp.size(); j++) + res.push_back(PathTime(i, temp[j])); + } + return res; +} + + +// The class below implements sweepline optimization for curve intersection in paths. +// Instead of O(N^2), this takes O(N + X), where X is the number of overlaps +// between the bounding boxes of curves. + +struct CurveIntersectionSweepSet +{ +public: + struct CurveRecord { + boost::intrusive::list_member_hook<> _hook; + Curve const *curve; + Rect bounds; + std::size_t index; + unsigned which; + + CurveRecord(Curve const *pc, std::size_t idx, unsigned w) + : curve(pc) + , bounds(curve->boundsFast()) + , index(idx) + , which(w) + {} + }; + + typedef std::vector<CurveRecord>::const_iterator ItemIterator; + + CurveIntersectionSweepSet(std::vector<PathIntersection> &result, + Path const &a, Path const &b, Coord precision) + : _result(result) + , _precision(precision) + , _sweep_dir(X) + { + std::size_t asz = a.size(), bsz = b.size(); + _records.reserve(asz + bsz); + + for (std::size_t i = 0; i < asz; ++i) { + _records.push_back(CurveRecord(&a[i], i, 0)); + } + for (std::size_t i = 0; i < bsz; ++i) { + _records.push_back(CurveRecord(&b[i], i, 1)); + } + + OptRect abb = a.boundsFast() | b.boundsFast(); + if (abb && abb->height() > abb->width()) { + _sweep_dir = Y; + } + } + + std::vector<CurveRecord> const &items() { return _records; } + Interval itemBounds(ItemIterator ii) { + return ii->bounds[_sweep_dir]; + } + + void addActiveItem(ItemIterator ii) { + unsigned w = ii->which; + unsigned ow = (w+1) % 2; + + _active[w].push_back(const_cast<CurveRecord&>(*ii)); + + for (ActiveCurveList::iterator i = _active[ow].begin(); i != _active[ow].end(); ++i) { + if (!ii->bounds.intersects(i->bounds)) continue; + std::vector<CurveIntersection> cx = ii->curve->intersect(*i->curve, _precision); + for (std::size_t k = 0; k < cx.size(); ++k) { + PathTime tw(ii->index, cx[k].first), tow(i->index, cx[k].second); + _result.push_back(PathIntersection( + w == 0 ? tw : tow, + w == 0 ? tow : tw, + cx[k].point())); + } + } + } + void removeActiveItem(ItemIterator ii) { + ActiveCurveList &acl = _active[ii->which]; + acl.erase(acl.iterator_to(*ii)); + } + +private: + typedef boost::intrusive::list + < CurveRecord + , boost::intrusive::member_hook + < CurveRecord + , boost::intrusive::list_member_hook<> + , &CurveRecord::_hook + > + > ActiveCurveList; + + std::vector<CurveRecord> _records; + std::vector<PathIntersection> &_result; + ActiveCurveList _active[2]; + Coord _precision; + Dim2 _sweep_dir; +}; + +std::vector<PathIntersection> Path::intersect(Path const &other, Coord precision) const +{ + std::vector<PathIntersection> result; + + CurveIntersectionSweepSet cisset(result, *this, other, precision); + Sweeper<CurveIntersectionSweepSet> sweeper(cisset); + sweeper.process(); + + // preprocessing to remove duplicate intersections at endpoints + std::size_t asz = size(), bsz = other.size(); + for (std::size_t i = 0; i < result.size(); ++i) { + result[i].first.normalizeForward(asz); + result[i].second.normalizeForward(bsz); + } + std::sort(result.begin(), result.end()); + result.erase(std::unique(result.begin(), result.end()), result.end()); + + return result; +} + +int Path::winding(Point const &p) const { + int wind = 0; + + /* To handle all the edge cases, we consider the maximum Y edge of the bounding box + * as not included in box. This way paths that contain linear horizontal + * segments will be treated correctly. */ + for (const_iterator i = begin(); i != end_closed(); ++i) { + Rect bounds = i->boundsFast(); + + if (bounds.height() == 0) continue; + if (p[X] > bounds.right() || !bounds[Y].lowerContains(p[Y])) { + // Ray doesn't intersect bbox, so we ignore this segment + continue; + } + + if (p[X] < bounds.left()) { + /* Ray intersects the curve's bbox, but the point is outside it. + * The winding contribution is exactly the same as that + * of a linear segment with the same initial and final points. */ + Point ip = i->initialPoint(); + Point fp = i->finalPoint(); + Rect eqbox(ip, fp); + + if (eqbox[Y].lowerContains(p[Y])) { + /* The ray intersects the equivalent linear segment. + * Determine winding contribution based on its derivative. */ + if (ip[Y] < fp[Y]) { + wind += 1; + } else if (ip[Y] > fp[Y]) { + wind -= 1; + } else { + // should never happen, because bounds.height() was not zero + assert(false); + } + } + } else { + // point is inside bbox + wind += i->winding(p); + } + } + return wind; +} + +std::vector<double> Path::allNearestTimes(Point const &_point, double from, double to) const +{ + // TODO from and to are not used anywhere. + // rewrite this to simplify. + using std::swap; + + if (from > to) + swap(from, to); + const Path &_path = *this; + unsigned int sz = _path.size(); + if (_path.closed()) + ++sz; + if (from < 0 || to > sz) { + THROW_RANGEERROR("[from,to] interval out of bounds"); + } + double sif, st = modf(from, &sif); + double eif, et = modf(to, &eif); + unsigned int si = static_cast<unsigned int>(sif); + unsigned int ei = static_cast<unsigned int>(eif); + if (si == sz) { + --si; + st = 1; + } + if (ei == sz) { + --ei; + et = 1; + } + if (si == ei) { + std::vector<double> all_nearest = _path[si].allNearestTimes(_point, st, et); + for (unsigned int i = 0; i < all_nearest.size(); ++i) { + all_nearest[i] = si + all_nearest[i]; + } + return all_nearest; + } + std::vector<double> all_t; + std::vector<std::vector<double> > all_np; + all_np.push_back(_path[si].allNearestTimes(_point, st)); + std::vector<unsigned int> ni; + ni.push_back(si); + double dsq; + double mindistsq = distanceSq(_point, _path[si].pointAt(all_np.front().front())); + Rect bb(Geom::Point(0, 0), Geom::Point(0, 0)); + for (unsigned int i = si + 1; i < ei; ++i) { + bb = (_path[i].boundsFast()); + dsq = distanceSq(_point, bb); + if (mindistsq < dsq) + continue; + all_t = _path[i].allNearestTimes(_point); + dsq = distanceSq(_point, _path[i].pointAt(all_t.front())); + if (mindistsq > dsq) { + all_np.clear(); + all_np.push_back(all_t); + ni.clear(); + ni.push_back(i); + mindistsq = dsq; + } else if (mindistsq == dsq) { + all_np.push_back(all_t); + ni.push_back(i); + } + } + bb = (_path[ei].boundsFast()); + dsq = distanceSq(_point, bb); + if (mindistsq >= dsq) { + all_t = _path[ei].allNearestTimes(_point, 0, et); + dsq = distanceSq(_point, _path[ei].pointAt(all_t.front())); + if (mindistsq > dsq) { + for (unsigned int i = 0; i < all_t.size(); ++i) { + all_t[i] = ei + all_t[i]; + } + return all_t; + } else if (mindistsq == dsq) { + all_np.push_back(all_t); + ni.push_back(ei); + } + } + std::vector<double> all_nearest; + for (unsigned int i = 0; i < all_np.size(); ++i) { + for (unsigned int j = 0; j < all_np[i].size(); ++j) { + all_nearest.push_back(ni[i] + all_np[i][j]); + } + } + all_nearest.erase(std::unique(all_nearest.begin(), all_nearest.end()), all_nearest.end()); + return all_nearest; +} + +std::vector<Coord> Path::nearestTimePerCurve(Point const &p) const +{ + // return a single nearest time for each curve in this path + std::vector<Coord> np; + for (const_iterator it = begin(); it != end_default(); ++it) { + np.push_back(it->nearestTime(p)); + } + return np; +} + +PathTime Path::nearestTime(Point const &p, Coord *dist) const +{ + Coord mindist = std::numeric_limits<Coord>::max(); + PathTime ret; + + if (_data->curves.size() == 1) { + // naked moveto + ret.curve_index = 0; + ret.t = 0; + if (dist) { + *dist = distance(_closing_seg->initialPoint(), p); + } + return ret; + } + + for (size_type i = 0; i < size_default(); ++i) { + Curve const &c = at(i); + if (distance(p, c.boundsFast()) >= mindist) continue; + + Coord t = c.nearestTime(p); + Coord d = distance(c.pointAt(t), p); + if (d < mindist) { + mindist = d; + ret.curve_index = i; + ret.t = t; + } + } + if (dist) { + *dist = mindist; + } + + return ret; +} + +std::vector<Point> Path::nodes() const +{ + std::vector<Point> result; + size_type path_size = size_closed(); + for (size_type i = 0; i < path_size; ++i) { + result.push_back(_data->curves[i].initialPoint()); + } + return result; +} + +void Path::appendPortionTo(Path &ret, double from, double to) const +{ + if (!(from >= 0 && to >= 0)) { + THROW_RANGEERROR("from and to must be >=0 in Path::appendPortionTo"); + } + if (to == 0) + to = size() + 0.999999; + if (from == to) { + return; + } + double fi, ti; + double ff = modf(from, &fi), tf = modf(to, &ti); + if (tf == 0) { + ti--; + tf = 1; + } + const_iterator fromi = inc(begin(), (unsigned)fi); + if (fi == ti && from < to) { + ret.append(fromi->portion(ff, tf)); + return; + } + const_iterator toi = inc(begin(), (unsigned)ti); + if (ff != 1.) { + // fromv->setInitial(ret.finalPoint()); + ret.append(fromi->portion(ff, 1.)); + } + if (from >= to) { + const_iterator ender = end(); + if (ender->initialPoint() == ender->finalPoint()) + ++ender; + ret.insert(ret.end(), ++fromi, ender); + ret.insert(ret.end(), begin(), toi); + } else { + ret.insert(ret.end(), ++fromi, toi); + } + ret.append(toi->portion(0., tf)); +} + +void Path::appendPortionTo(Path &target, PathInterval const &ival, + boost::optional<Point> const &p_from, boost::optional<Point> const &p_to) const +{ + assert(ival.pathSize() == size_closed()); + + if (ival.isDegenerate()) { + Point stitch_to = p_from ? *p_from : pointAt(ival.from()); + target.stitchTo(stitch_to); + return; + } + + PathTime const &from = ival.from(), &to = ival.to(); + + bool reverse = ival.reverse(); + int di = reverse ? -1 : 1; + size_type s = size_closed(); + + if (!ival.crossesStart() && from.curve_index == to.curve_index) { + Curve *c = (*this)[from.curve_index].portion(from.t, to.t); + if (p_from) { + c->setInitial(*p_from); + } + if (p_to) { + c->setFinal(*p_to); + } + target.append(c); + } else { + Curve *c_first = (*this)[from.curve_index].portion(from.t, reverse ? 0 : 1); + if (p_from) { + c_first->setInitial(*p_from); + } + target.append(c_first); + + for (size_type i = (from.curve_index + s + di) % s; i != to.curve_index; + i = (i + s + di) % s) + { + if (reverse) { + target.append((*this)[i].reverse()); + } else { + target.append((*this)[i].duplicate()); + } + } + + Curve *c_last = (*this)[to.curve_index].portion(reverse ? 1 : 0, to.t); + if (p_to) { + c_last->setFinal(*p_to); + } + target.append(c_last); + } +} + +Path Path::reversed() const +{ + typedef std::reverse_iterator<Sequence::const_iterator> RIter; + + Path ret(finalPoint()); + if (empty()) return ret; + + ret._data->curves.pop_back(); // this also deletes the closing segment from ret + + RIter iter(_includesClosingSegment() ? _data->curves.end() : _data->curves.end() - 1); + RIter rend(_data->curves.begin()); + + if (_closed) { + // when the path is closed, there are two cases: + if (front().isLineSegment()) { + // 1. initial segment is linear: it becomes the new closing segment. + rend = RIter(_data->curves.begin() + 1); + ret._closing_seg = new ClosingSegment(front().finalPoint(), front().initialPoint()); + } else { + // 2. initial segment is not linear: the closing segment becomes degenerate. + // However, skip it if it's already degenerate. + Point fp = finalPoint(); + ret._closing_seg = new ClosingSegment(fp, fp); + } + } else { + // when the path is open, we reverse all real curves, and add a reversed closing segment. + ret._closing_seg = static_cast<ClosingSegment *>(_closing_seg->reverse()); + } + + for (; iter != rend; ++iter) { + ret._data->curves.push_back(iter->reverse()); + } + ret._data->curves.push_back(ret._closing_seg); + ret._closed = _closed; + return ret; +} + + +void Path::insert(iterator pos, Curve const &curve) +{ + _unshare(); + Sequence::iterator seq_pos(seq_iter(pos)); + Sequence source; + source.push_back(curve.duplicate()); + do_update(seq_pos, seq_pos, source); +} + +void Path::erase(iterator pos) +{ + _unshare(); + Sequence::iterator seq_pos(seq_iter(pos)); + + Sequence stitched; + do_update(seq_pos, seq_pos + 1, stitched); +} + +void Path::erase(iterator first, iterator last) +{ + _unshare(); + Sequence::iterator seq_first = seq_iter(first); + Sequence::iterator seq_last = seq_iter(last); + + Sequence stitched; + do_update(seq_first, seq_last, stitched); +} + +void Path::stitchTo(Point const &p) +{ + if (!empty() && _closing_seg->initialPoint() != p) { + if (_exception_on_stitch) { + THROW_CONTINUITYERROR(); + } + _unshare(); + do_append(new StitchSegment(_closing_seg->initialPoint(), p)); + } +} + +void Path::replace(iterator replaced, Curve const &curve) +{ + replace(replaced, replaced + 1, curve); +} + +void Path::replace(iterator first_replaced, iterator last_replaced, Curve const &curve) +{ + _unshare(); + Sequence::iterator seq_first_replaced(seq_iter(first_replaced)); + Sequence::iterator seq_last_replaced(seq_iter(last_replaced)); + Sequence source(1); + source.push_back(curve.duplicate()); + + do_update(seq_first_replaced, seq_last_replaced, source); +} + +void Path::replace(iterator replaced, Path const &path) +{ + replace(replaced, path.begin(), path.end()); +} + +void Path::replace(iterator first, iterator last, Path const &path) +{ + replace(first, last, path.begin(), path.end()); +} + +void Path::snapEnds(Coord precision) +{ + if (!_closed) return; + if (_data->curves.size() > 1 && are_near(_closing_seg->length(precision), 0, precision)) { + _unshare(); + _closing_seg->setInitial(_closing_seg->finalPoint()); + (_data->curves.end() - 1)->setFinal(_closing_seg->finalPoint()); + } +} + +// replace curves between first and last with contents of source, +// +void Path::do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source) +{ + // TODO: handle cases where first > last in closed paths? + bool last_beyond_closing_segment = (last == _data->curves.end()); + + // special case: + // if do_update replaces the closing segment, we have to regenerate it + if (source.empty()) { + if (first == last) return; // nothing to do + + // only removing some segments + if ((!_closed && first == _data->curves.begin()) || (!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) { + // just adjust the closing segment + // do nothing + } else if (first->initialPoint() != (last - 1)->finalPoint()) { + if (_exception_on_stitch) { + THROW_CONTINUITYERROR(); + } + source.push_back(new StitchSegment(first->initialPoint(), (last - 1)->finalPoint())); + } + } else { + // replacing + if (first == _data->curves.begin() && last == _data->curves.end()) { + // special case: replacing everything should work the same in open and closed curves + _data->curves.erase(_data->curves.begin(), _data->curves.end() - 1); + _closing_seg->setFinal(source.front().initialPoint()); + _closing_seg->setInitial(source.back().finalPoint()); + _data->curves.transfer(_data->curves.begin(), source.begin(), source.end(), source); + return; + } + + // stitch in front + if (!_closed && first == _data->curves.begin()) { + // not necessary to stitch in front + } else if (first->initialPoint() != source.front().initialPoint()) { + if (_exception_on_stitch) { + THROW_CONTINUITYERROR(); + } + source.insert(source.begin(), new StitchSegment(first->initialPoint(), source.front().initialPoint())); + } + + // stitch at the end + if ((!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) { + // repurpose the closing segment as the stitch segment + // do nothing + } else if (source.back().finalPoint() != (last - 1)->finalPoint()) { + if (_exception_on_stitch) { + THROW_CONTINUITYERROR(); + } + source.push_back(new StitchSegment(source.back().finalPoint(), (last - 1)->finalPoint())); + } + } + + // do not erase the closing segment, adjust it instead + if (last_beyond_closing_segment) { + --last; + } + _data->curves.erase(first, last); + _data->curves.transfer(first, source.begin(), source.end(), source); + + // adjust closing segment + if (size_open() == 0) { + _closing_seg->setFinal(_closing_seg->initialPoint()); + } else { + _closing_seg->setInitial(back_open().finalPoint()); + _closing_seg->setFinal(front().initialPoint()); + } + + checkContinuity(); +} + +void Path::do_append(Curve *c) +{ + if (&_data->curves.front() == _closing_seg) { + _closing_seg->setFinal(c->initialPoint()); + } else { + // if we can't freely move the closing segment, we check whether + // the new curve connects with the last non-closing curve + if (c->initialPoint() != _closing_seg->initialPoint()) { + THROW_CONTINUITYERROR(); + } + if (_closed && c->isLineSegment() && + c->finalPoint() == _closing_seg->finalPoint()) + { + // appending a curve that matches the closing segment has no effect + delete c; + return; + } + } + _data->curves.insert(_data->curves.end() - 1, c); + _closing_seg->setInitial(c->finalPoint()); +} + +void Path::checkContinuity() const +{ + Sequence::const_iterator i = _data->curves.begin(), j = _data->curves.begin(); + ++j; + for (; j != _data->curves.end(); ++i, ++j) { + if (i->finalPoint() != j->initialPoint()) { + THROW_CONTINUITYERROR(); + } + } + if (_data->curves.front().initialPoint() != _data->curves.back().finalPoint()) { + THROW_CONTINUITYERROR(); + } +} + +// breaks time value into integral and fractional part +PathTime Path::_factorTime(Coord t) const +{ + size_type sz = size_default(); + if (t < 0 || t > sz) { + THROW_RANGEERROR("parameter t out of bounds"); + } + + PathTime ret; + Coord k; + ret.t = modf(t, &k); + ret.curve_index = k; + if (ret.curve_index == sz) { + --ret.curve_index; + ret.t = 1; + } + return ret; +} + +Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths) +{ + Piecewise<D2<SBasis> > ret = paths[0].toPwSb(); + for (unsigned i = 1; i < paths.size(); i++) { + ret.concat(paths[i].toPwSb()); + } + return ret; +} + +bool are_near(Path const &a, Path const &b, Coord precision) +{ + if (a.size() != b.size()) return false; + + for (unsigned i = 0; i < a.size(); ++i) { + if (!a[i].isNear(b[i], precision)) return false; + } + return true; +} + +std::ostream &operator<<(std::ostream &out, Path const &path) +{ + SVGPathWriter pw; + pw.feed(path); + out << pw.str(); + return out; +} + +} // end namespace Geom + +/* + 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/src/2geom/path.h b/src/2geom/path.h new file mode 100644 index 0000000..81511b2 --- /dev/null +++ b/src/2geom/path.h @@ -0,0 +1,874 @@ +/** @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 <iterator> +#include <algorithm> +#include <iostream> +#include <boost/operators.hpp> +#include <boost/ptr_container/ptr_vector.hpp> +#include <boost/shared_ptr.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; + } + + 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 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) {} + virtual Curve *duplicate() const { return new ClosingSegment(*this); } + virtual Curve *reverse() const { return new ClosingSegment((*this)[1], (*this)[0]); } + }; + + class StitchSegment : public LineSegment { + public: + StitchSegment() : LineSegment() {} + StitchSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} + virtual Curve *duplicate() const { return new StitchSegment(*this); } + virtual Curve *reverse() const { 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) throw() { + 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) throw() { 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); } + + /// 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; + + /** @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, boost::none, boost::none); + } + + /** @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, boost::none, boost::none); + } + + /** @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, + boost::optional<Point> const &p_from, boost::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]; } + + 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 A> + void appendNew(A a) { + _unshare(); + do_append(new CurveType(finalPoint(), a)); + } + + template <typename CurveType, typename A, typename B> + void appendNew(A a, B b) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b)); + } + + template <typename CurveType, typename A, typename B, typename C> + void appendNew(A a, B b, C c) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D> + void appendNew(A a, B b, C c, D d) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E> + void appendNew(A a, B b, C c, D d, E e) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E, typename F> + void appendNew(A a, B b, C c, D d, E e, F f) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e, f)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E, typename F, typename G> + void appendNew(A a, B b, C c, D d, E e, F f, G g) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E, typename F, typename G, + typename H> + void appendNew(A a, B b, C c, D d, E e, F f, G g, H h) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g, h)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E, typename F, typename G, + typename H, typename I> + void appendNew(A a, B b, C c, D d, E e, F f, G g, H h, I i) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g, h, i)); + } + + /** @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 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); + + boost::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); + +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/src/2geom/pathvector.cpp b/src/2geom/pathvector.cpp new file mode 100644 index 0000000..8872587 --- /dev/null +++ b/src/2geom/pathvector.cpp @@ -0,0 +1,335 @@ +/** @file + * @brief PathVector - a sequence of subpaths + *//* + * Authors: + * Johan Engelen <goejendaagh@zonnet.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. + */ + +#include <2geom/affine.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/svg-path-writer.h> +#include <2geom/sweeper.h> + +namespace Geom { + +//PathVector &PathVector::operator+=(PathVector const &other); + +PathVector::size_type PathVector::curveCount() const +{ + size_type n = 0; + for (const_iterator it = begin(); it != end(); ++it) { + n += it->size_default(); + } + return n; +} + +void PathVector::reverse(bool reverse_paths) +{ + if (reverse_paths) { + std::reverse(begin(), end()); + } + for (iterator i = begin(); i != end(); ++i) { + *i = i->reversed(); + } +} + +PathVector PathVector::reversed(bool reverse_paths) const +{ + PathVector ret; + for (const_iterator i = begin(); i != end(); ++i) { + ret.push_back(i->reversed()); + } + if (reverse_paths) { + std::reverse(ret.begin(), ret.end()); + } + return ret; +} + +Path &PathVector::pathAt(Coord t, Coord *rest) +{ + return const_cast<Path &>(static_cast<PathVector const*>(this)->pathAt(t, rest)); +} +Path const &PathVector::pathAt(Coord t, Coord *rest) const +{ + PathVectorTime pos = _factorTime(t); + if (rest) { + *rest = Coord(pos.curve_index) + pos.t; + } + return at(pos.path_index); +} +Curve const &PathVector::curveAt(Coord t, Coord *rest) const +{ + PathVectorTime pos = _factorTime(t); + if (rest) { + *rest = pos.t; + } + return at(pos.path_index).at(pos.curve_index); +} +Coord PathVector::valueAt(Coord t, Dim2 d) const +{ + PathVectorTime pos = _factorTime(t); + return at(pos.path_index).at(pos.curve_index).valueAt(pos.t, d); +} +Point PathVector::pointAt(Coord t) const +{ + PathVectorTime pos = _factorTime(t); + return at(pos.path_index).at(pos.curve_index).pointAt(pos.t); +} + +OptRect PathVector::boundsFast() const +{ + OptRect bound; + if (empty()) return bound; + + bound = front().boundsFast(); + for (const_iterator it = ++begin(); it != end(); ++it) { + bound.unionWith(it->boundsFast()); + } + return bound; +} + +OptRect PathVector::boundsExact() const +{ + OptRect bound; + if (empty()) return bound; + + bound = front().boundsExact(); + for (const_iterator it = ++begin(); it != end(); ++it) { + bound.unionWith(it->boundsExact()); + } + return bound; +} + +void PathVector::snapEnds(Coord precision) +{ + for (std::size_t i = 0; i < size(); ++i) { + (*this)[i].snapEnds(precision); + } +} + +// sweepline optimization +// this is very similar to CurveIntersectionSweepSet in path.cpp +// should probably be merged +class PathIntersectionSweepSet { +public: + struct PathRecord { + boost::intrusive::list_member_hook<> _hook; + Path const *path; + std::size_t index; + unsigned which; + + PathRecord(Path const &p, std::size_t i, unsigned w) + : path(&p) + , index(i) + , which(w) + {} + }; + + typedef std::vector<PathRecord>::iterator ItemIterator; + + PathIntersectionSweepSet(std::vector<PVIntersection> &result, + PathVector const &a, PathVector const &b, Coord precision) + : _result(result) + , _precision(precision) + { + _records.reserve(a.size() + b.size()); + for (std::size_t i = 0; i < a.size(); ++i) { + _records.push_back(PathRecord(a[i], i, 0)); + } + for (std::size_t i = 0; i < b.size(); ++i) { + _records.push_back(PathRecord(b[i], i, 1)); + } + } + + std::vector<PathRecord> &items() { return _records; } + + Interval itemBounds(ItemIterator ii) { + OptRect r = ii->path->boundsFast(); + if (!r) return Interval(); + return (*r)[X]; + } + + void addActiveItem(ItemIterator ii) { + unsigned w = ii->which; + unsigned ow = (ii->which + 1) % 2; + + for (ActivePathList::iterator i = _active[ow].begin(); i != _active[ow].end(); ++i) { + if (!ii->path->boundsFast().intersects(i->path->boundsFast())) continue; + std::vector<PathIntersection> px = ii->path->intersect(*i->path, _precision); + for (std::size_t k = 0; k < px.size(); ++k) { + PathVectorTime tw(ii->index, px[k].first), tow(i->index, px[k].second); + _result.push_back(PVIntersection( + w == 0 ? tw : tow, + w == 0 ? tow : tw, + px[k].point())); + } + } + _active[w].push_back(*ii); + } + + void removeActiveItem(ItemIterator ii) { + ActivePathList &apl = _active[ii->which]; + apl.erase(apl.iterator_to(*ii)); + } + +private: + typedef boost::intrusive::list + < PathRecord + , boost::intrusive::member_hook + < PathRecord + , boost::intrusive::list_member_hook<> + , &PathRecord::_hook + > + > ActivePathList; + + std::vector<PVIntersection> &_result; + std::vector<PathRecord> _records; + ActivePathList _active[2]; + Coord _precision; +}; + +std::vector<PVIntersection> PathVector::intersect(PathVector const &other, Coord precision) const +{ + std::vector<PVIntersection> result; + + PathIntersectionSweepSet pisset(result, *this, other, precision); + Sweeper<PathIntersectionSweepSet> sweeper(pisset); + sweeper.process(); + + std::sort(result.begin(), result.end()); + + return result; +} + +int PathVector::winding(Point const &p) const +{ + int wind = 0; + for (const_iterator i = begin(); i != end(); ++i) { + if (!i->boundsFast().contains(p)) continue; + wind += i->winding(p); + } + return wind; +} + +boost::optional<PathVectorTime> PathVector::nearestTime(Point const &p, Coord *dist) const +{ + boost::optional<PathVectorTime> retval; + + Coord mindist = infinity(); + for (size_type i = 0; i < size(); ++i) { + Coord d; + PathTime pos = (*this)[i].nearestTime(p, &d); + if (d < mindist) { + mindist = d; + retval = PathVectorTime(i, pos.curve_index, pos.t); + } + } + + if (dist) { + *dist = mindist; + } + return retval; +} + +std::vector<PathVectorTime> PathVector::allNearestTimes(Point const &p, Coord *dist) const +{ + std::vector<PathVectorTime> retval; + + Coord mindist = infinity(); + for (size_type i = 0; i < size(); ++i) { + Coord d; + PathTime pos = (*this)[i].nearestTime(p, &d); + if (d < mindist) { + mindist = d; + retval.clear(); + } + if (d <= mindist) { + retval.push_back(PathVectorTime(i, pos.curve_index, pos.t)); + } + } + + if (dist) { + *dist = mindist; + } + return retval; +} + +std::vector<Point> PathVector::nodes() const +{ + std::vector<Point> result; + for (size_type i = 0; i < size(); ++i) { + size_type path_size = (*this)[i].size_closed(); + for (size_type j = 0; j < path_size; ++j) { + result.push_back((*this)[i][j].initialPoint()); + } + } + return result; +} + +PathVectorTime PathVector::_factorTime(Coord t) const +{ + PathVectorTime ret; + Coord rest = 0; + ret.t = modf(t, &rest); + ret.curve_index = rest; + for (; ret.path_index < size(); ++ret.path_index) { + unsigned s = _data.at(ret.path_index).size_default(); + if (s > ret.curve_index) break; + // special case for the last point + if (s == ret.curve_index && ret.path_index + 1 == size()) { + --ret.curve_index; + ret.t = 1; + break; + } + ret.curve_index -= s; + } + return ret; +} + +std::ostream &operator<<(std::ostream &out, PathVector const &pv) +{ + SVGPathWriter wr; + wr.feed(pv); + out << wr.str(); + return out; +} + +} // namespace Geom + +/* + 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/src/2geom/pathvector.h b/src/2geom/pathvector.h new file mode 100644 index 0000000..0cbe5bf --- /dev/null +++ b/src/2geom/pathvector.h @@ -0,0 +1,301 @@ +/** @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 <boost/concept/requires.hpp> +#include <boost/shared_ptr.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(); + } + 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 (iterator i = begin(); i != end(); ++i) { + *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; + + boost::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/src/2geom/piecewise.cpp b/src/2geom/piecewise.cpp new file mode 100644 index 0000000..090da8e --- /dev/null +++ b/src/2geom/piecewise.cpp @@ -0,0 +1,266 @@ +/* + * piecewise.cpp - Piecewise function class + * + * Copyright 2007 Michael Sloan <mgsloan@gmail.com> + * Copyright 2007 JF Barraud + * + * 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. + * + */ + +#include <2geom/piecewise.h> +#include <iterator> +#include <map> + +namespace Geom { + +Piecewise<SBasis> divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, unsigned k) { + Piecewise<SBasis> pa = partition(a, b.cuts), pb = partition(b, a.cuts); + Piecewise<SBasis> ret = Piecewise<SBasis>(); + assert(pa.size() == pb.size()); + ret.cuts = pa.cuts; + for (unsigned i = 0; i < pa.size(); i++) + ret.push_seg(divide(pa[i], pb[i], k)); + return ret; +} + +Piecewise<SBasis> +divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero) { + Piecewise<SBasis> pa = partition(a, b.cuts), pb = partition(b, a.cuts); + Piecewise<SBasis> ret = Piecewise<SBasis>(); + assert(pa.size() == pb.size()); + for (unsigned i = 0; i < pa.size(); i++){ + Piecewise<SBasis> divi = divide(pa[i], pb[i], tol, k, zero); + divi.setDomain(Interval(pa.cuts[i],pa.cuts[i+1])); + ret.concat(divi); + } + return ret; +} +Piecewise<SBasis> divide(Piecewise<SBasis> const &a, SBasis const &b, double tol, unsigned k, double zero){ + return divide(a,Piecewise<SBasis>(b),tol,k,zero); +} +Piecewise<SBasis> divide(SBasis const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero){ + return divide(Piecewise<SBasis>(a),b,tol,k,zero); +} +Piecewise<SBasis> divide(SBasis const &a, SBasis const &b, double tol, unsigned k, double zero) { + if (b.tailError(0)<2*zero){ + //TODO: have a better look at sgn(b). + double sgn= (b(.5)<0.)?-1.:1; + return Piecewise<SBasis>(Linear(sgn/zero)*a); + } + + if (fabs(b.at0())>zero && fabs(b.at1())>zero ){ + SBasis c,r=a; + //TODO: what is a good relative tol? atm, c=a/b +/- (tol/a)%... + + k+=1; + r.resize(k, Linear(0,0)); + c.resize(k, Linear(0,0)); + + //assert(b.at0()!=0 && b.at1()!=0); + for (unsigned i=0; i<k; i++){ + Linear ci = Linear(r[i][0]/b[0][0],r[i][1]/b[0][1]); + c[i]=ci; + r-=shift(ci*b,i); + } + + if (r.tailError(k)<tol) return Piecewise<SBasis>(c); + } + + Piecewise<SBasis> c0,c1; + c0 = divide(compose(a,Linear(0.,.5)),compose(b,Linear(0.,.5)),tol,k); + c1 = divide(compose(a,Linear(.5,1.)),compose(b,Linear(.5,1.)),tol,k); + c0.setDomain(Interval(0.,.5)); + c1.setDomain(Interval(.5,1.)); + c0.concat(c1); + return c0; +} + + +//-- compose(pw<T>,SBasis) --------------- +/* + the purpose of the following functions is only to reduce the code in piecewise.h + TODO: use a vector<pairs<double,unsigned> > instead of a map<double,unsigned>. + */ + +std::map<double,unsigned> compose_pullback(std::vector<double> const &values, SBasis const &g){ + std::map<double,unsigned> result; + + std::vector<std::vector<double> > roots = multi_roots(g, values); + for(unsigned i=0; i<roots.size(); i++){ + for(unsigned j=0; j<roots[i].size();j++){ + result[roots[i][j]]=i; + } + } + // Also map 0 and 1 to the first value above(or =) g(0) and g(1). + if(result.count(0.)==0){ + unsigned i=0; + while (i<values.size()&&(g.at0()>values[i])) i++; + result[0.]=i; + } + if(result.count(1.)==0){ + unsigned i=0; + while (i<values.size()&&(g.at1()>values[i])) i++; + result[1.]=i; + } + return(result); +} + +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){ + double t0=(*cut).first; + unsigned idx0=(*cut).second; + double t1=(*next).first; + unsigned idx1=(*next).second; + assert(t0<t1); + int idx; //idx of the relevant f.segs + if (std::max(idx0,idx1)==levels.size()){ //g([t0,t1]) is above the top level, + idx=levels.size()-1; + } else if (idx0 != idx1){ //g([t0,t1]) crosses from level idx0 to idx1, + idx=std::min(idx0,idx1); + } else if(g((t0+t1)/2) < levels[idx0]) { //g([t0,t1]) is a 'U' under level idx0, + idx=idx0-1; + } else if(g((t0+t1)/2) > levels[idx0]) { //g([t0,t1]) is a 'bump' over level idx0, + idx=idx0; + } else { //g([t0,t1]) is contained in level idx0!... + idx = (idx0==levels.size())? idx0-1:idx0; + } + + //move idx back from levels f.cuts + idx+=1; + return idx; +} + + +Piecewise<SBasis> pw_compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double zero){ + Piecewise<SBasis> result; + + assert( f.size()>0 && g.size()>0); + SBasis g01 = g; + bool flip = ( g01.at0() > g01.at1() ); + + //OptInterval g_range = bounds_exact(g); + OptInterval g_range( Interval( g.at0(), g.at1() )); + + g01 -= g_range->min(); + g01 /= g_range->extent(); + if ( flip ){ + g01 *= -1.; + g01 += 1.; + } +#if 1 + assert( std::abs( g01.at0() - 0. ) < zero ); + assert( std::abs( g01.at1() - 1. ) < zero ); + //g[0][0] = 0.; + //g[0][1] = 1.; +#endif + + SBasis foginv = compose_inverse( f, g01, order, zero ); + SBasis err = compose( foginv, g01) - f; + + if ( err.tailError(0) < zero ){ + result = Piecewise<SBasis> (foginv); + }else{ + SBasis g_portion = portion( g01, Interval(0.,.5) ); + SBasis f_portion = portion( f, Interval(0.,.5) ); + result = pw_compose_inverse(f_portion, g_portion, order, zero); + + g_portion = portion( g01, Interval(.5, 1.) ); + f_portion = portion( f, Interval(.5, 1.) ); + Piecewise<SBasis> result_next; + result_next = pw_compose_inverse(f_portion, g_portion, order, zero); + result.concat( result_next ); + } + if (flip) { + result = reverse(result); + } + result.setDomain(*g_range); + return result; +} + + +std::vector<double> roots(Piecewise<SBasis> const &f){ + std::vector<double> result; + for (unsigned i=0; i<f.size(); i++){ + std::vector<double> rts=roots(f.segs[i]); + + for (unsigned r=0; r<rts.size(); r++){ + result.push_back(f.mapToDomain(rts[r], i)); + } + } + return result; +} + +std::vector<std::vector<double> > multi_roots(Piecewise<SBasis> const &f, std::vector<double> const &values) { + std::vector<std::vector<double> > result(values.size()); + for (unsigned i=0; i<f.size(); i++) { + std::vector<std::vector<double> > rts = multi_roots(f.segs[i], values); + for(unsigned j=0; j<rts.size(); j++) { + for(unsigned r=0; r<rts[j].size(); r++){ + result[j].push_back(f.mapToDomain(rts[j][r], i)); + } + } + } + return result; +} + + +std::vector<Interval> level_set(Piecewise<SBasis> const &f, Interval const &level, double tol){ + std::vector<Interval> result; + for (unsigned i=0; i<f.size(); i++){ + std::vector<Interval> resulti = level_set( f[i], level, 0., 1., tol); + for (unsigned j=0; j<resulti.size(); j++){ + double a = f.cuts[i] + resulti[j].min() * ( f.cuts[i+1] - f.cuts[i] ); + double b = f.cuts[i] + resulti[j].max() * ( f.cuts[i+1] - f.cuts[i] ); + Interval domj( a, b ); + //Interval domj( f.mapToDomain(resulti[j].min(), i ), f.mapToDomain(resulti[j].max(), i ) ); + + if ( j==0 && !result.empty() && result.back().intersects(domj) ){ + result.back().unionWith(domj); + }else{ + result.push_back(domj); + } + } + } + return result; +} +std::vector<Interval> level_set(Piecewise<SBasis> const &f, double v, double vtol, double tol){ + Interval level ( v-vtol, v+vtol ); + return level_set( f, level, tol); +} + + +} +/* + 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/src/2geom/piecewise.h b/src/2geom/piecewise.h new file mode 100644 index 0000000..6cc1de4 --- /dev/null +++ b/src/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 (unsigned j = 0; j < sr.size(); j++) ret.push_back(sr[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/src/2geom/point.cpp b/src/2geom/point.cpp new file mode 100644 index 0000000..cbe53c4 --- /dev/null +++ b/src/2geom/point.cpp @@ -0,0 +1,274 @@ +/** + * \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. + */ + +#include <assert.h> +#include <math.h> +#include <2geom/angle.h> +#include <2geom/coord.h> +#include <2geom/point.h> +#include <2geom/transforms.h> + +namespace Geom { + +/** + * @class Point + * @brief Two-dimensional point that doubles as a vector. + * + * Points in 2Geom are represented in Cartesian coordinates, e.g. as a pair of numbers + * that store the X and Y coordinates. Each point is also a vector in \f$\mathbb{R}^2\f$ + * from the origin (point at 0,0) to the stored coordinates, + * and has methods implementing several vector operations (like length()). + * + * @section OpNotePoint Operator note + * + * Most operators are provided by Boost operator helpers, so they are not visible in this class. + * If @a p, @a q, @a r denote points, @a s a floating-point scalar, and @a m a transformation matrix, + * then the following operations are available: + * @code + p += q; p -= q; r = p + q; r = p - q; + p *= s; p /= s; q = p * s; q = s * p; q = p / s; + p *= m; q = p * m; q = m * p; + @endcode + * It is possible to left-multiply a point by a matrix, even though mathematically speaking + * this is undefined. The result is a point identical to that obtained by right-multiplying. + * + * @ingroup Primitives */ + +Point Point::polar(Coord angle) { + Point ret; + Coord remainder = Angle(angle).radians0(); + if (are_near(remainder, 0) || are_near(remainder, 2*M_PI)) { + ret[X] = 1; + ret[Y] = 0; + } else if (are_near(remainder, M_PI/2)) { + ret[X] = 0; + ret[Y] = 1; + } else if (are_near(remainder, M_PI)) { + ret[X] = -1; + ret[Y] = 0; + } else if (are_near(remainder, 3*M_PI/2)) { + ret[X] = 0; + ret[Y] = -1; + } else { + sincos(angle, ret[Y], ret[X]); + } + return ret; +} + +/** @brief Normalize the vector representing the point. + * After this method returns, the length of the vector will be 1 (unless both coordinates are + * zero - the zero point will be returned then). The function tries to handle infinite + * coordinates gracefully. If any of the coordinates are NaN, the function will do nothing. + * @post \f$-\epsilon < \left|this\right| - 1 < \epsilon\f$ + * @see unit_vector(Geom::Point const &) */ +void Point::normalize() { + double len = hypot(_pt[0], _pt[1]); + if(len == 0) return; + if(std::isnan(len)) return; + static double const inf = HUGE_VAL; + if(len != inf) { + *this /= len; + } else { + unsigned n_inf_coords = 0; + /* Delay updating pt in case neither coord is infinite. */ + Point tmp; + for ( unsigned i = 0 ; i < 2 ; ++i ) { + if ( _pt[i] == inf ) { + ++n_inf_coords; + tmp[i] = 1.0; + } else if ( _pt[i] == -inf ) { + ++n_inf_coords; + tmp[i] = -1.0; + } else { + tmp[i] = 0.0; + } + } + switch (n_inf_coords) { + case 0: { + /* Can happen if both coords are near +/-DBL_MAX. */ + *this /= 4.0; + len = hypot(_pt[0], _pt[1]); + assert(len != inf); + *this /= len; + break; + } + case 1: { + *this = tmp; + break; + } + case 2: { + *this = tmp * sqrt(0.5); + break; + } + } + } +} + +/** @brief Compute the first norm (Manhattan distance) of @a p. + * This is equal to the sum of absolutes values of the coordinates. + * @return \f$|p_X| + |p_Y|\f$ + * @relates Point */ +Coord L1(Point const &p) { + Coord d = 0; + for ( int i = 0 ; i < 2 ; i++ ) { + d += fabs(p[i]); + } + return d; +} + +/** @brief Compute the infinity norm (maximum norm) of @a p. + * @return \f$\max(|p_X|, |p_Y|)\f$ + * @relates Point */ +Coord LInfty(Point const &p) { + Coord const a(fabs(p[0])); + Coord const b(fabs(p[1])); + return ( a < b || std::isnan(b) + ? b + : a ); +} + +/** @brief True if the point has both coordinates zero. + * NaNs are treated as not equal to zero. + * @relates Point */ +bool is_zero(Point const &p) { + return ( p[0] == 0 && + p[1] == 0 ); +} + +/** @brief True if the point has a length near 1. The are_near() function is used. + * @relates Point */ +bool is_unit_vector(Point const &p, Coord eps) { + return are_near(L2(p), 1.0, eps); +} +/** @brief Return the angle between the point and the +X axis. + * @return Angle in \f$(-\pi, \pi]\f$. + * @relates Point */ +Coord atan2(Point const &p) { + return std::atan2(p[Y], p[X]); +} + +/** @brief Compute the angle between a and b relative to the origin. + * The computation is done by projecting b onto the basis defined by a, rot90(a). + * @return Angle in \f$(-\pi, \pi]\f$. + * @relates Point */ +Coord angle_between(Point const &a, Point const &b) { + return std::atan2(cross(a,b), dot(a,b)); +} + +/** @brief Create a normalized version of a point. + * This is equivalent to copying the point and calling its normalize() method. + * The returned point will be (0,0) if the argument has both coordinates equal to zero. + * If any coordinate is NaN, this function will do nothing. + * @param a Input point + * @return Point on the unit circle in the same direction from origin as a, or the origin + * if a has both coordinates equal to zero + * @relates Point */ +Point unit_vector(Point const &a) +{ + Point ret(a); + ret.normalize(); + return ret; +} +/** @brief Return the "absolute value" of the point's vector. + * This is defined in terms of the default lexicographical ordering. If the point is "larger" + * that the origin (0, 0), its negation is returned. You can check whether + * the points' vectors have the same direction (e.g. lie + * on the same line passing through the origin) using + * @code abs(a).normalize() == abs(b).normalize() @endcode + * To check with some margin of error, use + * @code are_near(abs(a).normalize(), abs(b).normalize()) @endcode + * Although naively this should take the absolute value of each coordinate, such an operation + * is not very useful. + * @relates Point */ +Point abs(Point const &b) +{ + Point ret; + if (b[Y] < 0.0) { + ret = -b; + } else if (b[Y] == 0.0) { + ret = b[X] < 0.0 ? -b : b; + } else { + ret = b; + } + return ret; +} + +/** @brief Transform the point by the specified matrix. */ +Point &Point::operator*=(Affine const &m) { + double x = _pt[X], y = _pt[Y]; + for(int i = 0; i < 2; i++) { + _pt[i] = x * m[i] + y * m[i + 2] + m[i + 4]; + } + return *this; +} + +/** @brief Snap the angle B - A - dir to multiples of \f$2\pi/n\f$. + * The 'dir' argument must be normalized (have unit length), otherwise the result + * is undefined. + * @return Point with the same distance from A as B, with a snapped angle. + * @post distance(A, B) == distance(A, result) + * @post angle_between(result - A, dir) == \f$2k\pi/n, k \in \mathbb{N}\f$ + * @relates Point */ +Point constrain_angle(Point const &A, Point const &B, unsigned int n, Point const &dir) +{ + // for special cases we could perhaps use explicit testing (which might be faster) + if (n == 0.0) { + return B; + } + Point diff(B - A); + double angle = -angle_between(diff, dir); + double k = round(angle * (double)n / (2.0*M_PI)); + return A + dir * Rotate(k * 2.0 * M_PI / (double)n) * L2(diff); +} + +std::ostream &operator<<(std::ostream &out, const Geom::Point &p) +{ + out << "(" << format_coord_nice(p[X]) << ", " + << format_coord_nice(p[Y]) << ")"; + return out; +} + +} // end namespace Geom + +/* + 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/src/2geom/point.h b/src/2geom/point.h new file mode 100644 index 0000000..30afb63 --- /dev/null +++ b/src/2geom/point.h @@ -0,0 +1,427 @@ +/** @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 + , 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]; +public: + typedef Coord D1Value; + typedef Coord &D1Reference; + typedef Coord const &D1ConstReference; + + /// @name Create points + /// @{ + /** Construct a point on the origin. */ + Point() + { _pt[X] = _pt[Y] = 0; } + + /** Construct a point from its coordinates. */ + Point(Coord x, Coord y) { + _pt[X] = x; _pt[Y] = y; + } + /** Construct from integer point. */ + Point(IntPoint const &p) { + _pt[X] = p[X]; + _pt[Y] = 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 throw() { return _pt[d]; } + Coord &operator[](Dim2 d) throw() { return _pt[d]; } + + Coord x() const throw() { return _pt[X]; } + Coord &x() throw() { return _pt[X]; } + Coord y() const throw() { return _pt[Y]; } + Coord &y() throw() { 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 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) { + for ( unsigned i = 0 ; i < 2 ; ++i ) { + _pt[i] += o._pt[i]; + } + return *this; + } + Point &operator-=(Point const &o) { + for ( unsigned i = 0 ; i < 2 ; ++i ) { + _pt[i] -= o._pt[i]; + } + return *this; + } + Point &operator*=(Coord s) { + for ( unsigned i = 0 ; i < 2 ; ++i ) _pt[i] *= s; + return *this; + } + Point &operator/=(Coord s) { + //TODO: s == 0? + for ( unsigned i = 0 ; i < 2 ; ++i ) _pt[i] /= s; + 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 ( unsigned i = 0 ; i < 2 ; ++i ) { + if(!std::isfinite(_pt[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 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/src/2geom/polynomial.cpp b/src/2geom/polynomial.cpp new file mode 100644 index 0000000..e853b9a --- /dev/null +++ b/src/2geom/polynomial.cpp @@ -0,0 +1,337 @@ +/** + * \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. + */ + +#include <algorithm> +#include <2geom/polynomial.h> +#include <2geom/math-utils.h> +#include <math.h> + +#ifdef HAVE_GSL +#include <gsl/gsl_poly.h> +#endif + +namespace Geom { + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +Poly Poly::operator*(const Poly& p) const { + Poly result; + result.resize(degree() + p.degree()+1); + + for(unsigned i = 0; i < size(); i++) { + for(unsigned j = 0; j < p.size(); j++) { + result[i+j] += (*this)[i] * p[j]; + } + } + return result; +} + +/*double Poly::eval(double x) const { + return gsl_poly_eval(&coeff[0], size(), x); + }*/ + +void Poly::normalize() { + while(back() == 0) + pop_back(); +} + +void Poly::monicify() { + normalize(); + + double scale = 1./back(); // unitize + + for(unsigned i = 0; i < size(); i++) { + (*this)[i] *= scale; + } +} + + +#ifdef HAVE_GSL +std::vector<std::complex<double> > solve(Poly const & pp) { + Poly p(pp); + p.normalize(); + gsl_poly_complex_workspace * w + = gsl_poly_complex_workspace_alloc (p.size()); + + gsl_complex_packed_ptr z = new double[p.degree()*2]; + double* a = new double[p.size()]; + for(unsigned int i = 0; i < p.size(); i++) + a[i] = p[i]; + std::vector<std::complex<double> > roots; + //roots.resize(p.degree()); + + gsl_poly_complex_solve (a, p.size(), w, z); + delete[]a; + + gsl_poly_complex_workspace_free (w); + + for (unsigned int i = 0; i < p.degree(); i++) { + roots.push_back(std::complex<double> (z[2*i] ,z[2*i+1])); + //printf ("z%d = %+.18f %+.18f\n", i, z[2*i], z[2*i+1]); + } + delete[] z; + return roots; +} + +std::vector<double > solve_reals(Poly const & p) { + std::vector<std::complex<double> > roots = solve(p); + std::vector<double> real_roots; + + for(unsigned int i = 0; i < roots.size(); i++) { + if(roots[i].imag() == 0) // should be more lenient perhaps + real_roots.push_back(roots[i].real()); + } + return real_roots; +} +#endif + +double polish_root(Poly const & p, double guess, double tol) { + Poly dp = derivative(p); + + double fn = p(guess); + while(fabs(fn) > tol) { + guess -= fn/dp(guess); + fn = p(guess); + } + return guess; +} + +Poly integral(Poly const & p) { + Poly result; + + result.reserve(p.size()+1); + result.push_back(0); // arbitrary const + for(unsigned i = 0; i < p.size(); i++) { + result.push_back(p[i]/(i+1)); + } + return result; + +} + +Poly derivative(Poly const & p) { + Poly result; + + if(p.size() <= 1) + return Poly(0); + result.reserve(p.size()-1); + for(unsigned i = 1; i < p.size(); i++) { + result.push_back(i*p[i]); + } + return result; +} + +Poly compose(Poly const & a, Poly const & b) { + Poly result; + + for(unsigned i = a.size(); i > 0; i--) { + result = Poly(a[i-1]) + result * b; + } + return result; + +} + +/* This version is backwards - dividing taylor terms +Poly divide(Poly const &a, Poly const &b, Poly &r) { + Poly c; + r = a; // remainder + + const unsigned k = a.size(); + r.resize(k, 0); + c.resize(k, 0); + + for(unsigned i = 0; i < k; i++) { + double ci = r[i]/b[0]; + c[i] += ci; + Poly bb = ci*b; + std::cout << ci <<"*" << b << ", r= " << r << std::endl; + r -= bb.shifted(i); + } + + return c; +} +*/ + +Poly divide(Poly const &a, Poly const &b, Poly &r) { + Poly c; + r = a; // remainder + assert(b.size() > 0); + + const unsigned k = a.degree(); + const unsigned l = b.degree(); + c.resize(k, 0.); + + for(unsigned i = k; i >= l; i--) { + //assert(i >= 0); + double ci = r.back()/b.back(); + c[i-l] += ci; + Poly bb = ci*b; + //std::cout << ci <<"*(" << b.shifted(i-l) << ") = " + // << bb.shifted(i-l) << " r= " << r << std::endl; + r -= bb.shifted(i-l); + r.pop_back(); + } + //std::cout << "r= " << r << std::endl; + r.normalize(); + c.normalize(); + + return c; +} + +Poly gcd(Poly const &a, Poly const &b, const double /*tol*/) { + if(a.size() < b.size()) + return gcd(b, a); + if(b.size() <= 0) + return a; + if(b.size() == 1) + return a; + Poly r; + divide(a, b, r); + return gcd(b, r); +} + + + + +std::vector<Coord> solve_quadratic(Coord a, Coord b, Coord c) +{ + std::vector<Coord> result; + + if (a == 0) { + // linear equation + if (b == 0) return result; + result.push_back(-c/b); + return result; + } + + Coord delta = b*b - 4*a*c; + + if (delta == 0) { + // one root + result.push_back(-b / (2*a)); + } else if (delta > 0) { + // two roots + Coord delta_sqrt = sqrt(delta); + + // Use different formulas depending on sign of b to preserve + // numerical stability. See e.g.: + // http://people.csail.mit.edu/bkph/articles/Quadratics.pdf + int sign = b >= 0 ? 1 : -1; + Coord t = -0.5 * (b + sign * delta_sqrt); + result.push_back(t / a); + result.push_back(c / t); + } + // no roots otherwise + + std::sort(result.begin(), result.end()); + return result; +} + + +std::vector<Coord> solve_cubic(Coord a, Coord b, Coord c, Coord d) +{ + // based on: + // http://mathworld.wolfram.com/CubicFormula.html + + if (a == 0) { + return solve_quadratic(b, c, d); + } + if (d == 0) { + // divide by x + std::vector<Coord> result = solve_quadratic(a, b, c); + result.push_back(0); + std::sort(result.begin(), result.end()); + return result; + } + + std::vector<Coord> result; + + // 1. divide everything by a to bring to canonical form + b /= a; + c /= a; + d /= a; + + // 2. eliminate x^2 term: x^3 + 3Qx - 2R = 0 + Coord Q = (3*c - b*b) / 9; + Coord R = (-27 * d + b * (9*c - 2*b*b)) / 54; + + // 3. compute polynomial discriminant + Coord D = Q*Q*Q + R*R; + Coord term1 = b/3; + + if (D > 0) { + // only one real root + Coord S = cbrt(R + sqrt(D)); + Coord T = cbrt(R - sqrt(D)); + result.push_back(-b/3 + S + T); + } else if (D == 0) { + // 3 real roots, 2 of which are equal + Coord rroot = cbrt(R); + result.reserve(3); + result.push_back(-term1 + 2*rroot); + result.push_back(-term1 - rroot); + result.push_back(-term1 - rroot); + } else { + // 3 distinct real roots + assert(Q < 0); + Coord theta = acos(R / sqrt(-Q*Q*Q)); + Coord rroot = 2 * sqrt(-Q); + result.reserve(3); + result.push_back(-term1 + rroot * cos(theta / 3)); + result.push_back(-term1 + rroot * cos((theta + 2*M_PI) / 3)); + result.push_back(-term1 + rroot * cos((theta + 4*M_PI) / 3)); + } + + std::sort(result.begin(), result.end()); + return result; +} + + +/*Poly divide_out_root(Poly const & p, double x) { + assert(1); + }*/ + +} //namespace Geom + +/* + 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/src/2geom/polynomial.h b/src/2geom/polynomial.h new file mode 100644 index 0000000..5ab2aa4 --- /dev/null +++ b/src/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/src/2geom/ray.h b/src/2geom/ray.h new file mode 100644 index 0000000..4e60fd8 --- /dev/null +++ b/src/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/src/2geom/rect.cpp b/src/2geom/rect.cpp new file mode 100644 index 0000000..5a821e9 --- /dev/null +++ b/src/2geom/rect.cpp @@ -0,0 +1,187 @@ +/* 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, 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. + */ + +#include <2geom/rect.h> +#include <2geom/transforms.h> + +namespace Geom { + +Point align_factors(Align g) { + Point p; + switch (g) { + case ALIGN_XMIN_YMIN: + p[X] = 0.0; + p[Y] = 0.0; + break; + case ALIGN_XMID_YMIN: + p[X] = 0.5; + p[Y] = 0.0; + break; + case ALIGN_XMAX_YMIN: + p[X] = 1.0; + p[Y] = 0.0; + break; + case ALIGN_XMIN_YMID: + p[X] = 0.0; + p[Y] = 0.5; + break; + case ALIGN_XMID_YMID: + p[X] = 0.5; + p[Y] = 0.5; + break; + case ALIGN_XMAX_YMID: + p[X] = 1.0; + p[Y] = 0.5; + break; + case ALIGN_XMIN_YMAX: + p[X] = 0.0; + p[Y] = 1.0; + break; + case ALIGN_XMID_YMAX: + p[X] = 0.5; + p[Y] = 1.0; + break; + case ALIGN_XMAX_YMAX: + p[X] = 1.0; + p[Y] = 1.0; + break; + default: + break; + } + return p; +} + + +/** @brief Transform the rectangle by an affine. + * The result of the transformation might not be axis-aligned. The return value + * of this operation will be the smallest axis-aligned rectangle containing + * all points of the true result. */ +Rect &Rect::operator*=(Affine const &m) { + Point pts[4]; + for (unsigned i=0; i<4; ++i) pts[i] = corner(i) * m; + Coord minx = std::min(std::min(pts[0][X], pts[1][X]), std::min(pts[2][X], pts[3][X])); + Coord miny = std::min(std::min(pts[0][Y], pts[1][Y]), std::min(pts[2][Y], pts[3][Y])); + Coord maxx = std::max(std::max(pts[0][X], pts[1][X]), std::max(pts[2][X], pts[3][X])); + Coord maxy = std::max(std::max(pts[0][Y], pts[1][Y]), std::max(pts[2][Y], pts[3][Y])); + f[X].setMin(minx); f[X].setMax(maxx); + f[Y].setMin(miny); f[Y].setMax(maxy); + return *this; +} + +Affine Rect::transformTo(Rect const &viewport, Aspect const &aspect) const +{ + // 1. translate viewbox to origin + Geom::Affine total = Translate(-min()); + + // 2. compute scale + Geom::Point vdims = viewport.dimensions(); + Geom::Point dims = dimensions(); + Geom::Scale scale(vdims[X] / dims[X], vdims[Y] / dims[Y]); + + if (aspect.align == ALIGN_NONE) { + // apply non-uniform scale + total *= scale * Translate(viewport.min()); + } else { + double uscale = 0; + if (aspect.expansion == EXPANSION_MEET) { + uscale = std::min(scale[X], scale[Y]); + } else { + uscale = std::max(scale[X], scale[Y]); + } + scale = Scale(uscale); + + // compute offset for align + Geom::Point offset = vdims - dims * scale; + offset *= Scale(align_factors(aspect.align)); + total *= scale * Translate(viewport.min() + offset); + } + + return total; +} + +Coord distanceSq(Point const &p, Rect const &rect) +{ + double dx = 0, dy = 0; + if ( p[X] < rect.left() ) { + dx = p[X] - rect.left(); + } else if ( p[X] > rect.right() ) { + dx = rect.right() - p[X]; + } + if (p[Y] < rect.top() ) { + dy = rect.top() - p[Y]; + } else if ( p[Y] > rect.bottom() ) { + dy = p[Y] - rect.bottom(); + } + return dx*dx+dy*dy; +} + +/** @brief Returns the smallest distance between p and rect. + * @relates Rect */ +Coord distance(Point const &p, Rect const &rect) +{ + // copy of distanceSq, because we need to use hypot() + double dx = 0, dy = 0; + if ( p[X] < rect.left() ) { + dx = p[X] - rect.left(); + } else if ( p[X] > rect.right() ) { + dx = rect.right() - p[X]; + } + if (p[Y] < rect.top() ) { + dy = rect.top() - p[Y]; + } else if ( p[Y] > rect.bottom() ) { + dy = p[Y] - rect.bottom(); + } + return hypot(dx, dy); +} + +Coord distanceSq(Point const &p, OptRect const &rect) +{ + if (!rect) return std::numeric_limits<Coord>::max(); + return distanceSq(p, *rect); +} +Coord distance(Point const &p, OptRect const &rect) +{ + if (!rect) return std::numeric_limits<Coord>::max(); + return distance(p, *rect); +} + +} // namespace Geom + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/rect.h b/src/2geom/rect.h new file mode 100644 index 0000000..d867414 --- /dev/null +++ b/src/2geom/rect.h @@ -0,0 +1,264 @@ +/** + * \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 <boost/optional.hpp> +#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/src/2geom/recursive-bezier-intersection.cpp b/src/2geom/recursive-bezier-intersection.cpp new file mode 100644 index 0000000..e86192f --- /dev/null +++ b/src/2geom/recursive-bezier-intersection.cpp @@ -0,0 +1,476 @@ + + + +#include <2geom/basic-intersection.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/exception.h> + + +#include <gsl/gsl_vector.h> +#include <gsl/gsl_multiroots.h> + + +unsigned intersect_steps = 0; + +using std::vector; + +namespace Geom { + +class OldBezier { +public: + std::vector<Geom::Point> p; + OldBezier() { + } + void split(double t, OldBezier &a, OldBezier &b) const; + Point operator()(double t) const; + + ~OldBezier() {} + + void bounds(double &minax, double &maxax, + double &minay, double &maxay) { + // Compute bounding box for a + minax = p[0][X]; // These are the most likely to be extremal + maxax = p.back()[X]; + if( minax > maxax ) + std::swap(minax, maxax); + for(unsigned i = 1; i < p.size()-1; i++) { + if( p[i][X] < minax ) + minax = p[i][X]; + else if( p[i][X] > maxax ) + maxax = p[i][X]; + } + + minay = p[0][Y]; // These are the most likely to be extremal + maxay = p.back()[Y]; + if( minay > maxay ) + std::swap(minay, maxay); + for(unsigned i = 1; i < p.size()-1; i++) { + if( p[i][Y] < minay ) + minay = p[i][Y]; + else if( p[i][Y] > maxay ) + maxay = p[i][Y]; + } + + } + +}; + +static void +find_intersections_bezier_recursive(std::vector<std::pair<double, double> > & xs, + OldBezier a, + OldBezier b); + +void +find_intersections_bezier_recursive( std::vector<std::pair<double, double> > &xs, + vector<Geom::Point> const & A, + vector<Geom::Point> const & B, + double /*precision*/) { + OldBezier a, b; + a.p = A; + b.p = B; + return find_intersections_bezier_recursive(xs, a,b); +} + + +/* + * split the curve at the midpoint, returning an array with the two parts + * Temporary storage is minimized by using part of the storage for the result + * to hold an intermediate value until it is no longer needed. + */ +void OldBezier::split(double t, OldBezier &left, OldBezier &right) const { + const unsigned sz = p.size(); + //Geom::Point Vtemp[sz][sz]; + std::vector< std::vector< Geom::Point > > Vtemp; + for (size_t i = 0; i < sz; ++i ) + Vtemp[i].reserve(sz); + + /* Copy control points */ + std::copy(p.begin(), p.end(), Vtemp[0].begin()); + + /* Triangle computation */ + for (unsigned i = 1; i < sz; i++) { + for (unsigned j = 0; j < sz - i; j++) { + Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]); + } + } + + left.p.resize(sz); + right.p.resize(sz); + for (unsigned j = 0; j < sz; j++) + left.p[j] = Vtemp[j][0]; + for (unsigned j = 0; j < sz; j++) + right.p[j] = Vtemp[sz-1-j][j]; +} + +#if 0 +/* + * split the curve at the midpoint, returning an array with the two parts + * Temporary storage is minimized by using part of the storage for the result + * to hold an intermediate value until it is no longer needed. + */ +Point OldBezier::operator()(double t) const { + const unsigned sz = p.size(); + Geom::Point Vtemp[sz][sz]; + + /* Copy control points */ + std::copy(p.begin(), p.end(), Vtemp[0]); + + /* Triangle computation */ + for (unsigned i = 1; i < sz; i++) { + for (unsigned j = 0; j < sz - i; j++) { + Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]); + } + } + return Vtemp[sz-1][0]; +} +#endif + +// suggested by Sederberg. +Point OldBezier::operator()(double const t) const { + size_t const n = p.size()-1; + Point r; + for(int dim = 0; dim < 2; dim++) { + double const u = 1.0 - t; + double bc = 1; + double tn = 1; + double tmp = p[0][dim]*u; + for(size_t i=1; i<n; i++){ + tn = tn*t; + bc = bc*(n-i+1)/i; + tmp = (tmp + tn*bc*p[i][dim])*u; + } + r[dim] = (tmp + tn*t*p[n][dim]); + } + return r; +} + + +/* + * Test the bounding boxes of two OldBezier curves for interference. + * Several observations: + * First, it is cheaper to compute the bounding box of the second curve + * and test its bounding box for interference than to use a more direct + * approach of comparing all control points of the second curve with + * the various edges of the bounding box of the first curve to test + * for interference. + * Second, after a few subdivisions it is highly probable that two corners + * of the bounding box of a given Bezier curve are the first and last + * control point. Once this happens once, it happens for all subsequent + * subcurves. It might be worth putting in a test and then short-circuit + * code for further subdivision levels. + * Third, in the final comparison (the interference test) the comparisons + * should both permit equality. We want to find intersections even if they + * occur at the ends of segments. + * Finally, there are tighter bounding boxes that can be derived. It isn't + * clear whether the higher probability of rejection (and hence fewer + * subdivisions and tests) is worth the extra work. + */ + +bool intersect_BB( OldBezier a, OldBezier b ) { + double minax, maxax, minay, maxay; + a.bounds(minax, maxax, minay, maxay); + double minbx, maxbx, minby, maxby; + b.bounds(minbx, maxbx, minby, maxby); + // Test bounding box of b against bounding box of a + // Not >= : need boundary case + return !( ( minax > maxbx ) || ( minay > maxby ) + || ( minbx > maxax ) || ( minby > maxay ) ); +} + +/* + * Recursively intersect two curves keeping track of their real parameters + * and depths of intersection. + * The results are returned in a 2-D array of doubles indicating the parameters + * for which intersections are found. The parameters are in the order the + * intersections were found, which is probably not in sorted order. + * When an intersection is found, the parameter value for each of the two + * is stored in the index elements array, and the index is incremented. + * + * If either of the curves has subdivisions left before it is straight + * (depth > 0) + * that curve (possibly both) is (are) subdivided at its (their) midpoint(s). + * the depth(s) is (are) decremented, and the parameter value(s) corresponding + * to the midpoints(s) is (are) computed. + * Then each of the subcurves of one curve is intersected with each of the + * subcurves of the other curve, first by testing the bounding boxes for + * interference. If there is any bounding box interference, the corresponding + * subcurves are recursively intersected. + * + * If neither curve has subdivisions left, the line segments from the first + * to last control point of each segment are intersected. (Actually the + * only the parameter value corresponding to the intersection point is found). + * + * The apriori flatness test is probably more efficient than testing at each + * level of recursion, although a test after three or four levels would + * probably be worthwhile, since many curves become flat faster than their + * asymptotic rate for the first few levels of recursion. + * + * The bounding box test fails much more frequently than it succeeds, providing + * substantial pruning of the search space. + * + * Each (sub)curve is subdivided only once, hence it is not possible that for + * one final line intersection test the subdivision was at one level, while + * for another final line intersection test the subdivision (of the same curve) + * was at another. Since the line segments share endpoints, the intersection + * is robust: a near-tangential intersection will yield zero or two + * intersections. + */ +void recursively_intersect( OldBezier a, double t0, double t1, int deptha, + OldBezier b, double u0, double u1, int depthb, + std::vector<std::pair<double, double> > ¶meters) +{ + intersect_steps ++; + //std::cout << deptha << std::endl; + if( deptha > 0 ) + { + OldBezier A[2]; + a.split(0.5, A[0], A[1]); + double tmid = (t0+t1)*0.5; + deptha--; + if( depthb > 0 ) + { + OldBezier B[2]; + b.split(0.5, B[0], B[1]); + double umid = (u0+u1)*0.5; + depthb--; + if( intersect_BB( A[0], B[0] ) ) + recursively_intersect( A[0], t0, tmid, deptha, + B[0], u0, umid, depthb, + parameters ); + if( intersect_BB( A[1], B[0] ) ) + recursively_intersect( A[1], tmid, t1, deptha, + B[0], u0, umid, depthb, + parameters ); + if( intersect_BB( A[0], B[1] ) ) + recursively_intersect( A[0], t0, tmid, deptha, + B[1], umid, u1, depthb, + parameters ); + if( intersect_BB( A[1], B[1] ) ) + recursively_intersect( A[1], tmid, t1, deptha, + B[1], umid, u1, depthb, + parameters ); + } + else + { + if( intersect_BB( A[0], b ) ) + recursively_intersect( A[0], t0, tmid, deptha, + b, u0, u1, depthb, + parameters ); + if( intersect_BB( A[1], b ) ) + recursively_intersect( A[1], tmid, t1, deptha, + b, u0, u1, depthb, + parameters ); + } + } + else + if( depthb > 0 ) + { + OldBezier B[2]; + b.split(0.5, B[0], B[1]); + double umid = (u0 + u1)*0.5; + depthb--; + if( intersect_BB( a, B[0] ) ) + recursively_intersect( a, t0, t1, deptha, + B[0], u0, umid, depthb, + parameters ); + if( intersect_BB( a, B[1] ) ) + recursively_intersect( a, t0, t1, deptha, + B[0], umid, u1, depthb, + parameters ); + } + else // Both segments are fully subdivided; now do line segments + { + double xlk = a.p.back()[X] - a.p[0][X]; + double ylk = a.p.back()[Y] - a.p[0][Y]; + double xnm = b.p.back()[X] - b.p[0][X]; + double ynm = b.p.back()[Y] - b.p[0][Y]; + double xmk = b.p[0][X] - a.p[0][X]; + double ymk = b.p[0][Y] - a.p[0][Y]; + double det = xnm * ylk - ynm * xlk; + if( 1.0 + det == 1.0 ) + return; + else + { + double detinv = 1.0 / det; + double s = ( xnm * ymk - ynm *xmk ) * detinv; + double t = ( xlk * ymk - ylk * xmk ) * detinv; + if( ( s < 0.0 ) || ( s > 1.0 ) || ( t < 0.0 ) || ( t > 1.0 ) ) + return; + parameters.push_back(std::pair<double, double>(t0 + s * ( t1 - t0 ), + u0 + t * ( u1 - u0 ))); + } + } +} + +inline double log4( double x ) { return log(x)/log(4.); } + +/* + * Wang's theorem is used to estimate the level of subdivision required, + * but only if the bounding boxes interfere at the top level. + * Assuming there is a possible intersection, recursively_intersect is + * used to find all the parameters corresponding to intersection points. + * these are then sorted and returned in an array. + */ + +double Lmax(Point p) { + return std::max(fabs(p[X]), fabs(p[Y])); +} + + +unsigned wangs_theorem(OldBezier /*a*/) { + return 6; // seems a good approximation! + + /* + const double INV_EPS = (1L<<14); // The value of 1.0 / (1L<<14) is enough for most applications + + double la1 = Lmax( ( a.p[2] - a.p[1] ) - (a.p[1] - a.p[0]) ); + double la2 = Lmax( ( a.p[3] - a.p[2] ) - (a.p[2] - a.p[1]) ); + double l0 = std::max(la1, la2); + unsigned ra; + if( l0 * 0.75 * M_SQRT2 + 1.0 == 1.0 ) + ra = 0; + else + ra = (unsigned)ceil( log4( M_SQRT2 * 6.0 / 8.0 * INV_EPS * l0 ) ); + //std::cout << ra << std::endl; + return ra;*/ +} + +struct rparams +{ + OldBezier &A; + OldBezier &B; +}; + +/*static int +intersect_polish_f (const gsl_vector * x, void *params, + gsl_vector * f) +{ + const double x0 = gsl_vector_get (x, 0); + const double x1 = gsl_vector_get (x, 1); + + Geom::Point dx = ((struct rparams *) params)->A(x0) - + ((struct rparams *) params)->B(x1); + + gsl_vector_set (f, 0, dx[0]); + gsl_vector_set (f, 1, dx[1]); + + return GSL_SUCCESS; +}*/ + +/*union dbl_64{ + long long i64; + double d64; +};*/ + +/*static double EpsilonBy(double value, int eps) +{ + dbl_64 s; + s.d64 = value; + s.i64 += eps; + return s.d64; +}*/ + +/* +static void intersect_polish_root (OldBezier &A, double &s, + OldBezier &B, double &t) { + const gsl_multiroot_fsolver_type *T; + gsl_multiroot_fsolver *sol; + + int status; + size_t iter = 0; + + const size_t n = 2; + struct rparams p = {A, B}; + gsl_multiroot_function f = {&intersect_polish_f, n, &p}; + + double x_init[2] = {s, t}; + gsl_vector *x = gsl_vector_alloc (n); + + gsl_vector_set (x, 0, x_init[0]); + gsl_vector_set (x, 1, x_init[1]); + + T = gsl_multiroot_fsolver_hybrids; + sol = gsl_multiroot_fsolver_alloc (T, 2); + gsl_multiroot_fsolver_set (sol, &f, x); + + do + { + iter++; + status = gsl_multiroot_fsolver_iterate (sol); + + if (status) // check if solver is stuck + break; + + status = + gsl_multiroot_test_residual (sol->f, 1e-12); + } + while (status == GSL_CONTINUE && iter < 1000); + + s = gsl_vector_get (sol->x, 0); + t = gsl_vector_get (sol->x, 1); + + gsl_multiroot_fsolver_free (sol); + gsl_vector_free (x); + + // This code does a neighbourhood search for minor improvements. + double best_v = L1(A(s) - B(t)); + //std::cout << "------\n" << best_v << std::endl; + Point best(s,t); + while (true) { + Point trial = best; + double trial_v = best_v; + for(int nsi = -1; nsi < 2; nsi++) { + for(int nti = -1; nti < 2; nti++) { + Point n(EpsilonBy(best[0], nsi), + EpsilonBy(best[1], nti)); + double c = L1(A(n[0]) - B(n[1])); + //std::cout << c << "; "; + if (c < trial_v) { + trial = n; + trial_v = c; + } + } + } + if(trial == best) { + //std::cout << "\n" << s << " -> " << s - best[0] << std::endl; + //std::cout << t << " -> " << t - best[1] << std::endl; + //std::cout << best_v << std::endl; + s = best[0]; + t = best[1]; + return; + } else { + best = trial; + best_v = trial_v; + } + } +}*/ + + +void find_intersections_bezier_recursive( std::vector<std::pair<double, double> > &xs, + OldBezier a, OldBezier b) +{ + if( intersect_BB( a, b ) ) + { + recursively_intersect( a, 0., 1., wangs_theorem(a), + b, 0., 1., wangs_theorem(b), + xs); + } + /*for(unsigned i = 0; i < xs.size(); i++) + intersect_polish_root(a, xs[i].first, + b, xs[i].second);*/ + std::sort(xs.begin(), xs.end()); +} + + +}; + +/* + 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/src/2geom/sbasis-2d.cpp b/src/2geom/sbasis-2d.cpp new file mode 100644 index 0000000..53b09cd --- /dev/null +++ b/src/2geom/sbasis-2d.cpp @@ -0,0 +1,202 @@ +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> + +namespace Geom{ + +SBasis extract_u(SBasis2d const &a, double u) { + SBasis sb(a.vs, Linear()); + double s = u*(1-u); + + for(unsigned vi = 0; vi < a.vs; vi++) { + double sk = 1; + Linear bo(0,0); + for(unsigned ui = 0; ui < a.us; ui++) { + bo += (extract_u(a.index(ui, vi), u))*sk; + sk *= s; + } + sb[vi] = bo; + } + + return sb; +} + +SBasis extract_v(SBasis2d const &a, double v) { + SBasis sb(a.us, Linear()); + double s = v*(1-v); + + for(unsigned ui = 0; ui < a.us; ui++) { + double sk = 1; + Linear bo(0,0); + for(unsigned vi = 0; vi < a.vs; vi++) { + bo += (extract_v(a.index(ui, vi), v))*sk; + sk *= s; + } + sb[ui] = bo; + } + + return sb; +} + +SBasis compose(Linear2d const &a, D2<SBasis> const &p) { + D2<SBasis> omp(-p[X] + 1, -p[Y] + 1); + return multiply(omp[0], omp[1])*a[0] + + multiply(p[0], omp[1])*a[1] + + multiply(omp[0], p[1])*a[2] + + multiply(p[0], p[1])*a[3]; +} + +SBasis +compose(SBasis2d const &fg, D2<SBasis> const &p) { + SBasis B; + SBasis s[2]; + SBasis ss[2]; + for(unsigned dim = 0; dim < 2; dim++) + s[dim] = p[dim]*(Linear(1) - p[dim]); + ss[1] = Linear(1); + for(unsigned vi = 0; vi < fg.vs; vi++) { + ss[0] = ss[1]; + for(unsigned ui = 0; ui < fg.us; ui++) { + unsigned i = ui + vi*fg.us; + B += ss[0]*compose(fg[i], p); + ss[0] *= s[0]; + } + ss[1] *= s[1]; + } + return B; +} + +D2<SBasis> +compose_each(D2<SBasis2d> const &fg, D2<SBasis> const &p) { + return D2<SBasis>(compose(fg[X], p), compose(fg[Y], p)); +} + +SBasis2d partial_derivative(SBasis2d const &f, int dim) { + SBasis2d result; + for(unsigned i = 0; i < f.size(); i++) { + result.push_back(Linear2d(0,0,0,0)); + } + result.us = f.us; + result.vs = f.vs; + + for(unsigned i = 0; i < f.us; i++) { + for(unsigned j = 0; j < f.vs; j++) { + Linear2d lin = f.index(i,j); + Linear2d dlin(lin[1+dim]-lin[0], lin[1+2*dim]-lin[dim], lin[3-dim]-lin[2*(1-dim)], lin[3]-lin[2-dim]); + result[i+j*result.us] += dlin; + unsigned di = dim?j:i; + if (di>=1){ + float motpi = dim?-1:1; + Linear2d ds_lin_low( lin[0], -motpi*lin[1], motpi*lin[2], -lin[3] ); + result[(i+dim-1)+(j-dim)*result.us] += di*ds_lin_low; + + Linear2d ds_lin_hi( lin[1+dim]-lin[0], lin[1+2*dim]-lin[dim], lin[3]-lin[2-dim], lin[3-dim]-lin[2-dim] ); + result[i+j*result.us] += di*ds_lin_hi; + } + } + } + return result; +} + +/** + * Finds a path which traces the 0 contour of f, traversing from A to B as a single d2<sbasis>. + * degmax specifies the degree (degree = 2*degmax-1, so a degmax of 2 generates a cubic fit). + * The algorithm is based on dividing out derivatives at each end point and does not use the curvature for fitting. + * It is less accurate than sb2d_cubic_solve, although this may be fixed in the future. + */ +D2<SBasis> +sb2dsolve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B, unsigned degmax){ + //g_warning("check f(A)= %f = f(B) = %f =0!", f.apply(A[X],A[Y]), f.apply(B[X],B[Y])); + + SBasis2d dfdu = partial_derivative(f, 0); + SBasis2d dfdv = partial_derivative(f, 1); + Geom::Point dfA(dfdu.apply(A[X],A[Y]),dfdv.apply(A[X],A[Y])); + Geom::Point dfB(dfdu.apply(B[X],B[Y]),dfdv.apply(B[X],B[Y])); + Geom::Point nA = dfA/(dfA[X]*dfA[X]+dfA[Y]*dfA[Y]); + Geom::Point nB = dfB/(dfB[X]*dfB[X]+dfB[Y]*dfB[Y]); + + D2<SBasis>result(SBasis(degmax, Linear()), SBasis(degmax, Linear())); + double fact_k=1; + double sign = 1.; + for(int dim = 0; dim < 2; dim++) + result[dim][0] = Linear(A[dim],B[dim]); + for(unsigned k=1; k<degmax; k++){ + // these two lines make the solutions worse! + //fact_k *= k; + //sign = -sign; + SBasis f_on_curve = compose(f,result); + Linear reste = f_on_curve[k]; + double ax = -reste[0]/fact_k*nA[X]; + double ay = -reste[0]/fact_k*nA[Y]; + double bx = -sign*reste[1]/fact_k*nB[X]; + double by = -sign*reste[1]/fact_k*nB[Y]; + + result[X][k] = Linear(ax,bx); + result[Y][k] = Linear(ay,by); + //sign *= 3; + } + return result; +} + +/** + * Finds a path which traces the 0 contour of f, traversing from A to B as a single cubic d2<sbasis>. + * The algorithm is based on matching direction and curvature at each end point. + */ +//TODO: handle the case when B is "behind" A for the natural orientation of the level set. +//TODO: more generally, there might be up to 4 solutions. Choose the best one! +D2<SBasis> +sb2d_cubic_solve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B){ + D2<SBasis>result;//(Linear(A[X],B[X]),Linear(A[Y],B[Y])); + //g_warning("check 0 = %f = %f!", f.apply(A[X],A[Y]), f.apply(B[X],B[Y])); + + SBasis2d f_u = partial_derivative(f , 0); + SBasis2d f_v = partial_derivative(f , 1); + SBasis2d f_uu = partial_derivative(f_u, 0); + SBasis2d f_uv = partial_derivative(f_v, 0); + SBasis2d f_vv = partial_derivative(f_v, 1); + + Geom::Point dfA(f_u.apply(A[X],A[Y]),f_v.apply(A[X],A[Y])); + Geom::Point dfB(f_u.apply(B[X],B[Y]),f_v.apply(B[X],B[Y])); + + Geom::Point V0 = rot90(dfA); + Geom::Point V1 = rot90(dfB); + + double D2fVV0 = f_uu.apply(A[X],A[Y])*V0[X]*V0[X]+ + 2*f_uv.apply(A[X],A[Y])*V0[X]*V0[Y]+ + f_vv.apply(A[X],A[Y])*V0[Y]*V0[Y]; + double D2fVV1 = f_uu.apply(B[X],B[Y])*V1[X]*V1[X]+ + 2*f_uv.apply(B[X],B[Y])*V1[X]*V1[Y]+ + f_vv.apply(B[X],B[Y])*V1[Y]*V1[Y]; + + std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(A,B,V0,V1,D2fVV0,D2fVV1); + if (candidates.empty()) { + return D2<SBasis>(SBasis(Linear(A[X],B[X])), SBasis(Linear(A[Y],B[Y]))); + } + //TODO: I'm sure std algorithm could do that for me... + double error = -1; + unsigned best = 0; + for (unsigned i=0; i<candidates.size(); i++){ + Interval bounds = *bounds_fast(compose(f,candidates[i])); + double new_error = (fabs(bounds.max())>fabs(bounds.min()) ? fabs(bounds.max()) : fabs(bounds.min()) ); + if ( new_error < error || error < 0 ){ + error = new_error; + best = i; + } + } + return candidates[best]; +} + + + + +}; + +/* + 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/src/2geom/sbasis-2d.h b/src/2geom/sbasis-2d.h new file mode 100644 index 0000000..c7d9b00 --- /dev/null +++ b/src/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(unsigned i = 0 ; i < 4; 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/src/2geom/sbasis-curve.h b/src/2geom/sbasis-curve.h new file mode 100644 index 0000000..cfc4ee9 --- /dev/null +++ b/src/2geom/sbasis-curve.h @@ -0,0 +1,157 @@ +/** + * \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()) {} + + virtual Curve *duplicate() const { return new SBasisCurve(*this); } + virtual Point initialPoint() const { return inner.at0(); } + virtual Point finalPoint() const { return inner.at1(); } + virtual bool isDegenerate() const { return inner.isConstant(0); } + virtual bool isLineSegment() const { return inner[X].size() == 1; } + virtual Point pointAt(Coord t) const { return inner.valueAt(t); } + virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const { + return inner.valueAndDerivatives(t, n); + } + virtual Coord valueAt(Coord t, Dim2 d) const { return inner[d].valueAt(t); } + virtual void setInitial(Point const &v) { + for (unsigned d = 0; d < 2; d++) { inner[d][0][0] = v[d]; } + } + virtual void setFinal(Point const &v) { + for (unsigned d = 0; d < 2; d++) { inner[d][0][1] = v[d]; } + } + virtual Rect boundsFast() const { return *bounds_fast(inner); } + virtual Rect boundsExact() const { return *bounds_exact(inner); } + virtual OptRect boundsLocal(OptInterval const &i, unsigned deg) const { + return bounds_local(inner, i, deg); + } + virtual std::vector<Coord> roots(Coord v, Dim2 d) const { return Geom::roots(inner[d] - v); } + virtual Coord nearestTime( Point const& p, Coord from = 0, Coord to = 1 ) const { + return nearest_time(p, inner, from, to); + } + virtual std::vector<Coord> allNearestTimes( Point const& p, Coord from = 0, + Coord to = 1 ) const + { + return all_nearest_times(p, inner, from, to); + } + virtual Coord length(Coord tolerance) const { return ::Geom::length(inner, tolerance); } + virtual Curve *portion(Coord f, Coord t) const { + return new SBasisCurve(Geom::portion(inner, f, t)); + } + + using Curve::operator*=; + virtual void operator*=(Affine const &m) { inner = inner * m; } + + virtual Curve *derivative() const { + return new SBasisCurve(Geom::derivative(inner)); + } + virtual D2<SBasis> toSBasis() const { return inner; } + virtual bool operator==(Curve const &c) const { + SBasisCurve const *other = dynamic_cast<SBasisCurve const *>(&c); + if (!other) return false; + return inner == other->inner; + } + virtual bool isNear(Curve const &/*c*/, Coord /*eps*/) const { + THROW_NOTIMPLEMENTED(); + return false; + } + virtual int degreesOfFreedom() const { + 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/src/2geom/sbasis-geometric.cpp b/src/2geom/sbasis-geometric.cpp new file mode 100644 index 0000000..275b096 --- /dev/null +++ b/src/2geom/sbasis-geometric.cpp @@ -0,0 +1,791 @@ +/** Geometric operators on D2<SBasis> (1D->2D). + * Copyright 2012 JBC Engelen + * Copyright 2007 JF Barraud + * Copyright 2007 N Hurst + * + * The functions defined in this header related to 2d geometric operations such as arc length, + * unit_vector, curvature, and centroid. Most are built on top of unit_vector, which takes an + * arbitrary D2 and returns a D2 with unit length with the same direction. + * + * Todo/think about: + * arclength D2 -> sbasis (giving arclength function) + * does uniform_speed return natural parameterisation? + * integrate sb2d code from normal-bundle + * angle(md<2>) -> sbasis (gives angle from vector - discontinuous?) + * osculating circle center? + * + **/ + +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/sbasis-geometric.h> + +//namespace Geom{ +using namespace Geom; +using namespace std; + +//Some utils first. +//TODO: remove this!! +/** + * Return a list of doubles that appear in both a and b to within error tol + * a, b, vector of double + * tol tolerance + */ +static vector<double> +vect_intersect(vector<double> const &a, vector<double> const &b, double tol=0.){ + vector<double> inter; + unsigned i=0,j=0; + while ( i<a.size() && j<b.size() ){ + if (fabs(a[i]-b[j])<tol){ + inter.push_back(a[i]); + i+=1; + j+=1; + }else if (a[i]<b[j]){ + i+=1; + }else if (a[i]>b[j]){ + j+=1; + } + } + return inter; +} + +//------------------------------------------------------------------------------ +static SBasis divide_by_sk(SBasis const &a, int k) { + if ( k>=(int)a.size()){ + //make sure a is 0? + return SBasis(); + } + if(k < 0) return shift(a,-k); + SBasis c; + c.insert(c.begin(), a.begin()+k, a.end()); + return c; +} + +static SBasis divide_by_t0k(SBasis const &a, int k) { + if(k < 0) { + SBasis c = Linear(0,1); + for (int i=2; i<=-k; i++){ + c*=c; + } + c*=a; + return(c); + }else{ + SBasis c = Linear(1,0); + for (int i=2; i<=k; i++){ + c*=c; + } + c*=a; + return(divide_by_sk(c,k)); + } +} + +static SBasis divide_by_t1k(SBasis const &a, int k) { + if(k < 0) { + SBasis c = Linear(1,0); + for (int i=2; i<=-k; i++){ + c*=c; + } + c*=a; + return(c); + }else{ + SBasis c = Linear(0,1); + for (int i=2; i<=k; i++){ + c*=c; + } + c*=a; + return(divide_by_sk(c,k)); + } +} + +static D2<SBasis> RescaleForNonVanishingEnds(D2<SBasis> const &MM, double ZERO=1.e-4){ + D2<SBasis> M = MM; + //TODO: divide by all the s at once!!! + while ((M[0].size()>1||M[1].size()>1) && + fabs(M[0].at0())<ZERO && + fabs(M[1].at0())<ZERO && + fabs(M[0].at1())<ZERO && + fabs(M[1].at1())<ZERO){ + M[0] = divide_by_sk(M[0],1); + M[1] = divide_by_sk(M[1],1); + } + while ((M[0].size()>1||M[1].size()>1) && + fabs(M[0].at0())<ZERO && fabs(M[1].at0())<ZERO){ + M[0] = divide_by_t0k(M[0],1); + M[1] = divide_by_t0k(M[1],1); + } + while ((M[0].size()>1||M[1].size()>1) && + fabs(M[0].at1())<ZERO && fabs(M[1].at1())<ZERO){ + M[0] = divide_by_t1k(M[0],1); + M[1] = divide_by_t1k(M[1],1); + } + return M; +} + +/*static D2<SBasis> RescaleForNonVanishing(D2<SBasis> const &MM, double ZERO=1.e-4){ + std::vector<double> levels; + levels.push_back(-ZERO); + levels.push_back(ZERO); + //std::vector<std::vector<double> > mr = multi_roots(MM, levels); + }*/ + + +//================================================================= +//TODO: what's this for?!?! +Piecewise<D2<SBasis> > +Geom::cutAtRoots(Piecewise<D2<SBasis> > const &M, double ZERO){ + vector<double> rts; + for (unsigned i=0; i<M.size(); i++){ + vector<double> seg_rts = roots((M.segs[i])[0]); + seg_rts = vect_intersect(seg_rts, roots((M.segs[i])[1]), ZERO); + Linear mapToDom = Linear(M.cuts[i],M.cuts[i+1]); + for (unsigned r=0; r<seg_rts.size(); r++){ + seg_rts[r]= mapToDom(seg_rts[r]); + } + rts.insert(rts.end(),seg_rts.begin(),seg_rts.end()); + } + return partition(M,rts); +} + +/** Return a function which gives the angle of vect at each point. + \param vect a piecewise parameteric curve. + \param tol the maximum error allowed. + \param order the maximum degree to use for approximation + \relates Piecewise +*/ +Piecewise<SBasis> +Geom::atan2(Piecewise<D2<SBasis> > const &vect, double tol, unsigned order){ + Piecewise<SBasis> result; + Piecewise<D2<SBasis> > v = cutAtRoots(vect,tol); + result.cuts.push_back(v.cuts.front()); + for (unsigned i=0; i<v.size(); i++){ + + D2<SBasis> vi = RescaleForNonVanishingEnds(v.segs[i]); + SBasis x=vi[0], y=vi[1]; + Piecewise<SBasis> angle; + angle = divide (x*derivative(y)-y*derivative(x), x*x+y*y, tol, order); + + //TODO: I don't understand this - sign. + angle = integral(-angle); + Point vi0 = vi.at0(); + angle += -std::atan2(vi0[1],vi0[0]) - angle[0].at0(); + //TODO: deal with 2*pi jumps form one seg to the other... + //TODO: not exact at t=1 because of the integral. + //TODO: force continuity? + + angle.setDomain(Interval(v.cuts[i],v.cuts[i+1])); + result.concat(angle); + } + return result; +} +/** Return a function which gives the angle of vect at each point. + \param vect a piecewise parameteric curve. + \param tol the maximum error allowed. + \param order the maximum degree to use for approximation + \relates Piecewise, D2 +*/ +Piecewise<SBasis> +Geom::atan2(D2<SBasis> const &vect, double tol, unsigned order){ + return atan2(Piecewise<D2<SBasis> >(vect),tol,order); +} + +/** tan2 is the pseudo-inverse of atan2. It takes an angle and returns a unit_vector that points in the direction of angle. + \param angle a piecewise function of angle wrt t. + \param tol the maximum error allowed. + \param order the maximum degree to use for approximation + \relates D2, SBasis +*/ +D2<Piecewise<SBasis> > +Geom::tan2(SBasis const &angle, double tol, unsigned order){ + return tan2(Piecewise<SBasis>(angle), tol, order); +} + +/** tan2 is the pseudo-inverse of atan2. It takes an angle and returns a unit_vector that points in the direction of angle. + \param angle a piecewise function of angle wrt t. + \param tol the maximum error allowed. + \param order the maximum degree to use for approximation + \relates Piecewise, D2 +*/ +D2<Piecewise<SBasis> > +Geom::tan2(Piecewise<SBasis> const &angle, double tol, unsigned order){ + return D2<Piecewise<SBasis> >(cos(angle, tol, order), sin(angle, tol, order)); +} + +/** Return a Piecewise<D2<SBasis> > which points in the same direction as V_in, but has unit_length. + \param V_in the original path. + \param tol the maximum error allowed. + \param order the maximum degree to use for approximation + +unitVector(x,y) is computed as (b,-a) where a and b are solutions of: + ax+by=0 (eqn1) and a^2+b^2=1 (eqn2) + + \relates Piecewise, D2 +*/ +Piecewise<D2<SBasis> > +Geom::unitVector(D2<SBasis> const &V_in, double tol, unsigned order){ + //TODO: Handle vanishing vectors... + // -This approach is numerically bad. Find a stable way to rescale V_in to have non vanishing ends. + // -This done, unitVector will have jumps at zeros: fill the gaps with arcs of circles. + D2<SBasis> V = RescaleForNonVanishingEnds(V_in); + + if (V[0].isZero(tol) && V[1].isZero(tol)) + return Piecewise<D2<SBasis> >(D2<SBasis>(Linear(1),SBasis())); + SBasis x = V[0], y = V[1]; + SBasis r_eqn1, r_eqn2; + + Point v0 = unit_vector(V.at0()); + Point v1 = unit_vector(V.at1()); + SBasis a = SBasis(order+1, Linear(0.)); + a[0] = Linear(-v0[1],-v1[1]); + SBasis b = SBasis(order+1, Linear(0.)); + b[0] = Linear( v0[0], v1[0]); + + r_eqn1 = -(a*x+b*y); + r_eqn2 = Linear(1.)-(a*a+b*b); + + for (unsigned k=1; k<=order; k++){ + double r0 = (k<r_eqn1.size())? r_eqn1.at(k).at0() : 0; + double r1 = (k<r_eqn1.size())? r_eqn1.at(k).at1() : 0; + double rr0 = (k<r_eqn2.size())? r_eqn2.at(k).at0() : 0; + double rr1 = (k<r_eqn2.size())? r_eqn2.at(k).at1() : 0; + double a0,a1,b0,b1;// coeffs in a[k] and b[k] + + //the equations to solve at this point are: + // a0*x(0)+b0*y(0)=r0 & 2*a0*a(0)+2*b0*b(0)=rr0 + //and + // a1*x(1)+b1*y(1)=r1 & 2*a1*a(1)+2*b1*b(1)=rr1 + a0 = r0/dot(v0,V.at0())*v0[0]-rr0/2*v0[1]; + b0 = r0/dot(v0,V.at0())*v0[1]+rr0/2*v0[0]; + a1 = r1/dot(v1,V.at1())*v1[0]-rr1/2*v1[1]; + b1 = r1/dot(v1,V.at1())*v1[1]+rr1/2*v1[0]; + + a[k] = Linear(a0,a1); + b[k] = Linear(b0,b1); + + //TODO: use "incremental" rather than explicit formulas. + r_eqn1 = -(a*x+b*y); + r_eqn2 = Linear(1)-(a*a+b*b); + } + + //our candidate is: + D2<SBasis> unitV; + unitV[0] = b; + unitV[1] = -a; + + //is it good? + double rel_tol = std::max(1.,std::max(V_in[0].tailError(0),V_in[1].tailError(0)))*tol; + if (r_eqn1.tailError(order)>rel_tol || r_eqn2.tailError(order)>tol){ + //if not: subdivide and concat results. + Piecewise<D2<SBasis> > unitV0, unitV1; + unitV0 = unitVector(compose(V,Linear(0,.5)),tol,order); + unitV1 = unitVector(compose(V,Linear(.5,1)),tol,order); + unitV0.setDomain(Interval(0.,.5)); + unitV1.setDomain(Interval(.5,1.)); + unitV0.concat(unitV1); + return(unitV0); + }else{ + //if yes: return it as pw. + Piecewise<D2<SBasis> > result; + result=(Piecewise<D2<SBasis> >)unitV; + return result; + } +} + +/** Return a Piecewise<D2<SBasis> > which points in the same direction as V_in, but has unit_length. + \param V_in the original path. + \param tol the maximum error allowed. + \param order the maximum degree to use for approximation + +unitVector(x,y) is computed as (b,-a) where a and b are solutions of: + ax+by=0 (eqn1) and a^2+b^2=1 (eqn2) + + \relates Piecewise +*/ +Piecewise<D2<SBasis> > +Geom::unitVector(Piecewise<D2<SBasis> > const &V, double tol, unsigned order){ + Piecewise<D2<SBasis> > result; + Piecewise<D2<SBasis> > VV = cutAtRoots(V); + result.cuts.push_back(VV.cuts.front()); + for (unsigned i=0; i<VV.size(); i++){ + Piecewise<D2<SBasis> > unit_seg; + unit_seg = unitVector(VV.segs[i],tol, order); + unit_seg.setDomain(Interval(VV.cuts[i],VV.cuts[i+1])); + result.concat(unit_seg); + } + return result; +} + +/** returns a function giving the arclength at each point in M. + \param M the Element. + \param tol the maximum error allowed. + \relates Piecewise +*/ +Piecewise<SBasis> +Geom::arcLengthSb(Piecewise<D2<SBasis> > const &M, double tol){ + Piecewise<D2<SBasis> > dM = derivative(M); + Piecewise<SBasis> dMlength = sqrt(dot(dM,dM),tol,3); + Piecewise<SBasis> length = integral(dMlength); + length-=length.segs.front().at0(); + return length; +} + +/** returns a function giving the arclength at each point in M. + \param M the Element. + \param tol the maximum error allowed. + \relates Piecewise, D2 +*/ +Piecewise<SBasis> +Geom::arcLengthSb(D2<SBasis> const &M, double tol){ + return arcLengthSb(Piecewise<D2<SBasis> >(M), tol); +} + +#if 0 +double +Geom::length(D2<SBasis> const &M, + double tol){ + Piecewise<SBasis> length = arcLengthSb(M, tol); + return length.segs.back().at1(); +} +double +Geom::length(Piecewise<D2<SBasis> > const &M, + double tol){ + Piecewise<SBasis> length = arcLengthSb(M, tol); + return length.segs.back().at1(); +} +#endif + +/** returns a function giving the curvature at each point in M. + \param M the Element. + \param tol the maximum error allowed. + \relates Piecewise, D2 + \todo claimed incomplete. Check. +*/ +Piecewise<SBasis> +Geom::curvature(D2<SBasis> const &M, double tol) { + D2<SBasis> dM=derivative(M); + Piecewise<D2<SBasis> > unitv = unitVector(dM,tol); + Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv); + Piecewise<SBasis> k = cross(derivative(unitv),unitv); + k = divide(k,dMlength,tol,3); + return(k); +} + +/** returns a function giving the curvature at each point in M. + \param M the Element. + \param tol the maximum error allowed. + \relates Piecewise + \todo claimed incomplete. Check. +*/ +Piecewise<SBasis> +Geom::curvature(Piecewise<D2<SBasis> > const &V, double tol){ + Piecewise<SBasis> result; + Piecewise<D2<SBasis> > VV = cutAtRoots(V); + result.cuts.push_back(VV.cuts.front()); + for (unsigned i=0; i<VV.size(); i++){ + Piecewise<SBasis> curv_seg; + curv_seg = curvature(VV.segs[i],tol); + curv_seg.setDomain(Interval(VV.cuts[i],VV.cuts[i+1])); + result.concat(curv_seg); + } + return result; +} + +//================================================================= + +/** Reparameterise M to have unit speed. + \param M the Element. + \param tol the maximum error allowed. + \param order the maximum degree to use for approximation + \relates Piecewise, D2 +*/ +Piecewise<D2<SBasis> > +Geom::arc_length_parametrization(D2<SBasis> const &M, + unsigned order, + double tol){ + Piecewise<D2<SBasis> > u; + u.push_cut(0); + + Piecewise<SBasis> s = arcLengthSb(Piecewise<D2<SBasis> >(M),tol); + for (unsigned i=0; i < s.size();i++){ + double t0=s.cuts[i],t1=s.cuts[i+1]; + if ( are_near(s(t0),s(t1)) ) { + continue; + } + D2<SBasis> sub_M = compose(M,Linear(t0,t1)); + D2<SBasis> sub_u; + for (unsigned dim=0;dim<2;dim++){ + SBasis sub_s = s.segs[i]; + sub_s-=sub_s.at0(); + sub_s/=sub_s.at1(); + sub_u[dim]=compose_inverse(sub_M[dim],sub_s, order, tol); + } + u.push(sub_u,s(t1)); + } + return u; +} + +/** Reparameterise M to have unit speed. + \param M the Element. + \param tol the maximum error allowed. + \param order the maximum degree to use for approximation + \relates Piecewise +*/ +Piecewise<D2<SBasis> > +Geom::arc_length_parametrization(Piecewise<D2<SBasis> > const &M, + unsigned order, + double tol){ + Piecewise<D2<SBasis> > result; + for (unsigned i=0; i<M.size(); i++) { + result.concat( arc_length_parametrization(M[i],order,tol) ); + } + return result; +} + +#include <gsl/gsl_integration.h> +static double sb_length_integrating(double t, void* param) { + SBasis* pc = (SBasis*)param; + return sqrt((*pc)(t)); +} + +/** Calculates the length of a D2<SBasis> through gsl integration. + \param B the Element. + \param tol the maximum error allowed. + \param result variable to be incremented with the length of the path + \param abs_error variable to be incremented with the estimated error + \relates D2 +If you only want the length, this routine may be faster/more accurate. +*/ +void Geom::length_integrating(D2<SBasis> const &B, double &result, double &abs_error, double tol) { + D2<SBasis> dB = derivative(B); + SBasis dB2 = dot(dB, dB); + + gsl_function F; + gsl_integration_workspace * w + = gsl_integration_workspace_alloc (20); + F.function = &sb_length_integrating; + F.params = (void*)&dB2; + double quad_result, err; + /* We could probably use the non adaptive code here if we removed any cusps first. */ + + gsl_integration_qag (&F, 0, 1, 0, tol, 20, + GSL_INTEG_GAUSS21, w, &quad_result, &err); + + abs_error += err; + result += quad_result; +} + +/** Calculates the length of a D2<SBasis> through gsl integration. + \param s the Element. + \param tol the maximum error allowed. + \relates D2 +If you only want the total length, this routine faster and more accurate than constructing an arcLengthSb. +*/ +double +Geom::length(D2<SBasis> const &s, + double tol){ + double result = 0; + double abs_error = 0; + length_integrating(s, result, abs_error, tol); + return result; +} +/** Calculates the length of a Piecewise<D2<SBasis> > through gsl integration. + \param s the Element. + \param tol the maximum error allowed. + \relates Piecewise +If you only want the total length, this routine faster and more accurate than constructing an arcLengthSb. +*/ +double +Geom::length(Piecewise<D2<SBasis> > const &s, + double tol){ + double result = 0; + double abs_error = 0; + for (unsigned i=0; i < s.size();i++){ + length_integrating(s[i], result, abs_error, tol); + } + return result; +} + +/** + * Centroid using sbasis integration. + \param p the Element. + \param centroid on return contains the centroid of the shape + \param area on return contains the signed area of the shape. + \relates Piecewise +This approach uses green's theorem to compute the area and centroid using integrals. For curved shapes this is much faster than converting to polyline. Note that without an uncross operation the output is not the absolute area. + + * Returned values: + 0 for normal execution; + 2 if area is zero, meaning centroid is meaningless. + + */ +unsigned Geom::centroid(Piecewise<D2<SBasis> > const &p, Point& centroid, double &area) { + Point centroid_tmp(0,0); + double atmp = 0; + for(unsigned i = 0; i < p.size(); i++) { + SBasis curl = dot(p[i], rot90(derivative(p[i]))); + SBasis A = integral(curl); + D2<SBasis> C = integral(multiply(curl, p[i])); + atmp += A.at1() - A.at0(); + centroid_tmp += C.at1()- C.at0(); // first moment. + } +// join ends + centroid_tmp *= 2; + Point final = p[p.size()-1].at1(), initial = p[0].at0(); + const double ai = cross(final, initial); + atmp += ai; + centroid_tmp += (final + initial)*ai; // first moment. + + area = atmp / 2; + if (atmp != 0) { + centroid = centroid_tmp / (3 * atmp); + return 0; + } + return 2; +} + +/** + * Find cubics with prescribed curvatures at both ends. + * + * this requires to solve a system of the form + * + * \f[ + * \lambda_1 = a_0 \lambda_0^2 + c_0 + * \lambda_0 = a_1 \lambda_1^2 + c_1 + * \f] + * + * which is a deg 4 equation in lambda 0. + * Below are basic functions dedicated to solving this assuming a0 and a1 !=0. + */ + +static OptInterval +find_bounds_for_lambda0(double aa0,double aa1,double cc0,double cc1, + int insist_on_speeds_signs){ + + double a0=aa0,a1=aa1,c0=cc0,c1=cc1; + Interval result; + bool flip = a1<0; + if (a1<0){a1=-a1; c1=-c1;} + if (a0<0){a0=-a0; c0=-c0;} + double a = (a0<a1 ? a0 : a1); + double c = (c0<c1 ? c0 : c1); + double delta = 1-4*a*c; + if ( delta < 0 ) + return OptInterval();//return empty interval + double lambda_max = (1+std::sqrt(delta))/2/a; + + result = Interval(c,lambda_max); + if (flip) + result *= -1; + if (insist_on_speeds_signs == 1){ + if (result.max() < 0)//Caution: setMin with max<new min... + return OptInterval();//return empty interval + result.setMin(0); + } + result = Interval(result.min()-.1,result.max()+.1);//just in case all our approx. were exact... + return result; +} + +static +std::vector<double> +solve_lambda0(double a0,double a1,double c0,double c1, + int insist_on_speeds_signs){ + + SBasis p(3, Linear()); + p[0] = Linear( a1*c0*c0+c1, a1*a0*(a0+ 2*c0) +a1*c0*c0 +c1 -1 ); + p[1] = Linear( -a1*a0*(a0+2*c0), -a1*a0*(3*a0+2*c0) ); + p[2] = Linear( a1*a0*a0 ); + + OptInterval domain = find_bounds_for_lambda0(a0,a1,c0,c1,insist_on_speeds_signs); + if ( !domain ) + return std::vector<double>(); + p = compose(p,Linear(domain->min(),domain->max())); + std::vector<double>rts = roots(p); + for (unsigned i=0; i<rts.size(); i++){ + rts[i] = domain->min() + rts[i] * domain->extent(); + } + return rts; +} + +/** +* \brief returns the cubics fitting direction and curvature of a given +* input curve at two points. +* +* The input can be the +* value, speed, and acceleration +* or +* value, speed, and cross(acceleration,speed) +* of the original curve at the both ends. +* (the second is often technically useful, as it avoids unnecessary division by |v|^2) +* Recall that K=1/R=cross(acceleration,speed)/|speed|^3. +* +* Moreover, a 7-th argument 'insist_on_speed_signs' can be supplied to select solutions: +* If insist_on_speed_signs == 1, only consider solutions where speeds at both ends are positively +* proportional to the given ones. +* If insist_on_speed_signs == 0, allow speeds to point in the opposite direction (both at the same time) +* If insist_on_speed_signs == -1, allow speeds to point in both direction independently. +* +* \relates D2 +*/ +std::vector<D2<SBasis> > +Geom::cubics_fitting_curvature(Point const &M0, Point const &M1, + Point const &dM0, Point const &dM1, + double d2M0xdM0, double d2M1xdM1, + int insist_on_speed_signs, + double epsilon){ + std::vector<D2<SBasis> > result; + + //speed of cubic bezier will be lambda0*dM0 and lambda1*dM1, + //with lambda0 and lambda1 s.t. curvature at both ends is the same + //as the curvature of the given curve. + std::vector<double> lambda0,lambda1; + double dM1xdM0=cross(dM1,dM0); + if (fabs(dM1xdM0)<epsilon){ + if (fabs(d2M0xdM0)<epsilon || fabs(d2M1xdM1)<epsilon){ + return result; + } + double lbda02 = 6.*cross(M1-M0,dM0)/d2M0xdM0; + double lbda12 =-6.*cross(M1-M0,dM1)/d2M1xdM1; + if (lbda02<0 || lbda12<0){ + return result; + } + lambda0.push_back(std::sqrt(lbda02) ); + lambda1.push_back(std::sqrt(lbda12) ); + }else{ + //solve: lambda1 = a0 lambda0^2 + c0 + // lambda0 = a1 lambda1^2 + c1 + double a0,c0,a1,c1; + a0 = -d2M0xdM0/2/dM1xdM0; + c0 = 3*cross(M1-M0,dM0)/dM1xdM0; + a1 = -d2M1xdM1/2/dM1xdM0; + c1 = -3*cross(M1-M0,dM1)/dM1xdM0; + + if (fabs(a0)<epsilon){ + lambda1.push_back( c0 ); + lambda0.push_back( a1*c0*c0 + c1 ); + }else if (fabs(a1)<epsilon){ + lambda0.push_back( c1 ); + lambda1.push_back( a0*c1*c1 + c0 ); + }else{ + //find lamda0 by solving a deg 4 equation d0+d1*X+...+d4*X^4=0 + vector<double> solns=solve_lambda0(a0,a1,c0,c1,insist_on_speed_signs); + for (unsigned i=0;i<solns.size();i++){ + double lbda0=solns[i]; + double lbda1=c0+a0*lbda0*lbda0; + //is this solution pointing in the + direction at both ends? + if (lbda0>=0. && lbda1>=0.){ + lambda0.push_back( lbda0); + lambda1.push_back( lbda1); + } + //is this solution pointing in the - direction at both ends? + else if (lbda0<=0. && lbda1<=0. && insist_on_speed_signs<=0){ + lambda0.push_back( lbda0); + lambda1.push_back( lbda1); + } + //ok,this solution is pointing in the + and - directions. + else if (insist_on_speed_signs<0){ + lambda0.push_back( lbda0); + lambda1.push_back( lbda1); + } + } + } + } + + for (unsigned i=0; i<lambda0.size(); i++){ + Point V0 = lambda0[i]*dM0; + Point V1 = lambda1[i]*dM1; + D2<SBasis> cubic; + for(unsigned dim=0;dim<2;dim++){ + SBasis c(2, Linear()); + c[0] = Linear(M0[dim],M1[dim]); + c[1] = Linear( M0[dim]-M1[dim]+V0[dim], + -M0[dim]+M1[dim]-V1[dim]); + cubic[dim] = c; + } +#if 0 + Piecewise<SBasis> k = curvature(result); + double dM0_l = dM0.length(); + double dM1_l = dM1.length(); + g_warning("Target radii: %f, %f", dM0_l*dM0_l*dM0_l/d2M0xdM0,dM1_l*dM1_l*dM1_l/d2M1xdM1); + g_warning("Obtained radii: %f, %f",1/k.valueAt(0),1/k.valueAt(1)); +#endif + result.push_back(cubic); + } + return(result); +} + +std::vector<D2<SBasis> > +Geom::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, + double epsilon){ + double d2M0xdM0 = cross(d2M0,dM0); + double d2M1xdM1 = cross(d2M1,dM1); + return cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0xdM0,d2M1xdM1,insist_on_speed_signs,epsilon); +} + +std::vector<D2<SBasis> > +Geom::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, + double epsilon){ + double length; + length = dM0.length(); + double d2M0xdM0 = k0*length*length*length; + length = dM1.length(); + double d2M1xdM1 = k1*length*length*length; + return cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0xdM0,d2M1xdM1,insist_on_speed_signs,epsilon); +} + + +namespace Geom { +/** +* \brief returns all the parameter values of A whose tangent passes through P. +* \relates D2 +*/ +std::vector<double> find_tangents(Point P, D2<SBasis> const &A) { + SBasis crs (cross(A - P, derivative(A))); + return roots(crs); +} + +/** +* \brief returns all the parameter values of A whose normal passes through P. +* \relates D2 +*/ +std::vector<double> find_normals(Point P, D2<SBasis> const &A) { + SBasis crs (dot(A - P, derivative(A))); + return roots(crs); +} + +/** +* \brief returns all the parameter values of A whose normal is parallel to vector V. +* \relates D2 +*/ +std::vector<double> find_normals_by_vector(Point V, D2<SBasis> const &A) { + SBasis crs = dot(derivative(A), V); + return roots(crs); +} +/** +* \brief returns all the parameter values of A whose tangent is parallel to vector V. +* \relates D2 +*/ +std::vector<double> find_tangents_by_vector(Point V, D2<SBasis> const &A) { + SBasis crs = dot(derivative(A), rot90(V)); + return roots(crs); +} + +} +//}; // namespace + + +/* + 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/src/2geom/sbasis-geometric.h b/src/2geom/sbasis-geometric.h new file mode 100644 index 0000000..7f1e8aa --- /dev/null +++ b/src/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/src/2geom/sbasis-math.cpp b/src/2geom/sbasis-math.cpp new file mode 100644 index 0000000..b10bf93 --- /dev/null +++ b/src/2geom/sbasis-math.cpp @@ -0,0 +1,379 @@ +/* + * sbasis-math.cpp - 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'. + +#include <2geom/d2.h> +#include <2geom/sbasis-math.h> +#include <stdio.h> +#include <math.h> +//#define ZERO 1e-3 + + +namespace Geom { + + +//-|x|----------------------------------------------------------------------- +/** Return the absolute value of a function pointwise. + \param f function +*/ +Piecewise<SBasis> abs(SBasis const &f){ + return abs(Piecewise<SBasis>(f)); +} +/** Return the absolute value of a function pointwise. + \param f function +*/ +Piecewise<SBasis> abs(Piecewise<SBasis> const &f){ + Piecewise<SBasis> absf=partition(f,roots(f)); + for (unsigned i=0; i<absf.size(); i++){ + if (absf.segs[i](.5)<0) absf.segs[i]*=-1; + } + return absf; +} + +//-max(x,y), min(x,y)-------------------------------------------------------- +/** Return the greater of the two functions pointwise. + \param f, g two functions +*/ +Piecewise<SBasis> max( SBasis const &f, SBasis const &g){ + return max(Piecewise<SBasis>(f),Piecewise<SBasis>(g)); +} +/** Return the greater of the two functions pointwise. + \param f, g two functions +*/ +Piecewise<SBasis> max(Piecewise<SBasis> const &f, SBasis const &g){ + return max(f,Piecewise<SBasis>(g)); +} +/** Return the greater of the two functions pointwise. + \param f, g two functions +*/ +Piecewise<SBasis> max( SBasis const &f, Piecewise<SBasis> const &g){ + return max(Piecewise<SBasis>(f),g); +} +/** Return the greater of the two functions pointwise. + \param f, g two functions +*/ +Piecewise<SBasis> max(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g){ + Piecewise<SBasis> max=partition(f,roots(f-g)); + Piecewise<SBasis> gg =partition(g,max.cuts); + max = partition(max,gg.cuts); + for (unsigned i=0; i<max.size(); i++){ + if (max.segs[i](.5)<gg.segs[i](.5)) max.segs[i]=gg.segs[i]; + } + return max; +} + +/** Return the more negative of the two functions pointwise. + \param f, g two functions +*/ +Piecewise<SBasis> +min( SBasis const &f, SBasis const &g){ return -max(-f,-g); } +/** Return the more negative of the two functions pointwise. + \param f, g two functions +*/ +Piecewise<SBasis> +min(Piecewise<SBasis> const &f, SBasis const &g){ return -max(-f,-g); } +/** Return the more negative of the two functions pointwise. + \param f, g two functions +*/ +Piecewise<SBasis> +min( SBasis const &f, Piecewise<SBasis> const &g){ return -max(-f,-g); } +/** Return the more negative of the two functions pointwise. + \param f, g two functions +*/ +Piecewise<SBasis> +min(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g){ return -max(-f,-g); } + + +//-sign(x)--------------------------------------------------------------- +/** Return the sign of the two functions pointwise. + \param f function +*/ +Piecewise<SBasis> signSb(SBasis const &f){ + return signSb(Piecewise<SBasis>(f)); +} +/** Return the sign of the two functions pointwise. + \param f function +*/ +Piecewise<SBasis> signSb(Piecewise<SBasis> const &f){ + Piecewise<SBasis> sign=partition(f,roots(f)); + for (unsigned i=0; i<sign.size(); i++){ + sign.segs[i] = (sign.segs[i](.5)<0)? Linear(-1.):Linear(1.); + } + return sign; +} + +//-Sqrt---------------------------------------------------------- +static Piecewise<SBasis> sqrt_internal(SBasis const &f, + double tol, + int order){ + SBasis sqrtf; + if(f.isZero() || order == 0){ + return Piecewise<SBasis>(sqrtf); + } + if (f.at0()<-tol*tol && f.at1()<-tol*tol){ + return sqrt_internal(-f,tol,order); + }else if (f.at0()>tol*tol && f.at1()>tol*tol){ + sqrtf.resize(order+1, Linear(0,0)); + sqrtf[0] = Linear(std::sqrt(f[0][0]), std::sqrt(f[0][1])); + SBasis r = f - multiply(sqrtf, sqrtf); // remainder + for(unsigned i = 1; int(i) <= order && i<r.size(); i++) { + Linear ci(r[i][0]/(2*sqrtf[0][0]), r[i][1]/(2*sqrtf[0][1])); + SBasis cisi = shift(ci, i); + r -= multiply(shift((sqrtf*2 + cisi), i), SBasis(ci)); + r.truncate(order+1); + sqrtf[i] = ci; + if(r.tailError(i) == 0) // if exact + break; + } + }else{ + sqrtf = Linear(std::sqrt(fabs(f.at0())), std::sqrt(fabs(f.at1()))); + } + + double err = (f - multiply(sqrtf, sqrtf)).tailError(0); + if (err<tol){ + return Piecewise<SBasis>(sqrtf); + } + + Piecewise<SBasis> sqrtf0,sqrtf1; + sqrtf0 = sqrt_internal(compose(f,Linear(0.,.5)),tol,order); + sqrtf1 = sqrt_internal(compose(f,Linear(.5,1.)),tol,order); + sqrtf0.setDomain(Interval(0.,.5)); + sqrtf1.setDomain(Interval(.5,1.)); + sqrtf0.concat(sqrtf1); + return sqrtf0; +} + +/** Compute the sqrt of a function. + \param f function +*/ +Piecewise<SBasis> sqrt(SBasis const &f, double tol, int order){ + return sqrt(max(f,Linear(tol*tol)),tol,order); +} + +/** Compute the sqrt of a function. + \param f function +*/ +Piecewise<SBasis> sqrt(Piecewise<SBasis> const &f, double tol, int order){ + Piecewise<SBasis> result; + Piecewise<SBasis> zero = Piecewise<SBasis>(Linear(tol*tol)); + zero.setDomain(f.domain()); + Piecewise<SBasis> ff=max(f,zero); + + for (unsigned i=0; i<ff.size(); i++){ + Piecewise<SBasis> sqrtfi = sqrt_internal(ff.segs[i],tol,order); + sqrtfi.setDomain(Interval(ff.cuts[i],ff.cuts[i+1])); + result.concat(sqrtfi); + } + return result; +} + +//-Yet another sin/cos-------------------------------------------------------------- + +/** Compute the sine of a function. + \param f function + \param tol maximum error + \param order maximum degree polynomial to use +*/ +Piecewise<SBasis> sin( SBasis const &f, double tol, int order){return(cos(-f+M_PI/2,tol,order));} +/** Compute the sine of a function. + \param f function + \param tol maximum error + \param order maximum degree polynomial to use +*/ +Piecewise<SBasis> sin(Piecewise<SBasis> const &f, double tol, int order){return(cos(-f+M_PI/2,tol,order));} + +/** Compute the cosine of a function. + \param f function + \param tol maximum error + \param order maximum degree polynomial to use +*/ +Piecewise<SBasis> cos(Piecewise<SBasis> const &f, double tol, int order){ + Piecewise<SBasis> result; + for (unsigned i=0; i<f.size(); i++){ + Piecewise<SBasis> cosfi = cos(f.segs[i],tol,order); + cosfi.setDomain(Interval(f.cuts[i],f.cuts[i+1])); + result.concat(cosfi); + } + return result; +} + +/** Compute the cosine of a function. + \param f function + \param tol maximum error + \param order maximum degree polynomial to use +*/ +Piecewise<SBasis> cos( SBasis const &f, double tol, int order){ + double alpha = (f.at0()+f.at1())/2.; + SBasis x = f-alpha; + double d = x.tailError(0),err=1; + //estimate cos(x)-sum_0^order (-1)^k x^2k/2k! by the first neglicted term + for (int i=1; i<=2*order; i++) err*=d/i; + + if (err<tol){ + SBasis xk=Linear(1), c=Linear(1), s=Linear(0); + for (int k=1; k<=2*order; k+=2){ + xk*=x/k; + //take also truncature errors into account... + err+=xk.tailError(order); + xk.truncate(order); + s+=xk; + xk*=-x/(k+1); + //take also truncature errors into account... + err+=xk.tailError(order); + xk.truncate(order); + c+=xk; + } + if (err<tol){ + return Piecewise<SBasis>(std::cos(alpha)*c-std::sin(alpha)*s); + } + } + Piecewise<SBasis> c0,c1; + c0 = cos(compose(f,Linear(0.,.5)),tol,order); + c1 = cos(compose(f,Linear(.5,1.)),tol,order); + c0.setDomain(Interval(0.,.5)); + c1.setDomain(Interval(.5,1.)); + c0.concat(c1); + return c0; +} + +//--1/x------------------------------------------------------------ +//TODO: this implementation is just wrong. Remove or redo! + +void truncateResult(Piecewise<SBasis> &f, int order){ + if (order>=0){ + for (unsigned k=0; k<f.segs.size(); k++){ + f.segs[k].truncate(order); + } + } +} + +Piecewise<SBasis> reciprocalOnDomain(Interval range, double tol){ + Piecewise<SBasis> reciprocal_fn; + //TODO: deduce R from tol... + double R=2.; + SBasis reciprocal1_R=reciprocal(Linear(1,R),3); + double a=range.min(), b=range.max(); + if (a*b<0){ + b=std::max(fabs(a),fabs(b)); + a=0; + }else if (b<0){ + a=-range.max(); + b=-range.min(); + } + + if (a<=tol){ + reciprocal_fn.push_cut(0); + int i0=(int) floor(std::log(tol)/std::log(R)); + a = std::pow(R,i0); + reciprocal_fn.push(Linear(1/a),a); + }else{ + int i0=(int) floor(std::log(a)/std::log(R)); + a = std::pow(R,i0); + reciprocal_fn.cuts.push_back(a); + } + + while (a<b){ + reciprocal_fn.push(reciprocal1_R/a,R*a); + a*=R; + } + if (range.min()<0 || range.max()<0){ + Piecewise<SBasis>reciprocal_fn_neg; + //TODO: define reverse(pw<sb>); + reciprocal_fn_neg.cuts.push_back(-reciprocal_fn.cuts.back()); + for (unsigned i=0; i<reciprocal_fn.size(); i++){ + int idx=reciprocal_fn.segs.size()-1-i; + reciprocal_fn_neg.push_seg(-reverse(reciprocal_fn.segs.at(idx))); + reciprocal_fn_neg.push_cut(-reciprocal_fn.cuts.at(idx)); + } + if (range.max()>0){ + reciprocal_fn_neg.concat(reciprocal_fn); + } + reciprocal_fn=reciprocal_fn_neg; + } + + return(reciprocal_fn); +} + +Piecewise<SBasis> reciprocal(SBasis const &f, double tol, int order){ + Piecewise<SBasis> reciprocal_fn=reciprocalOnDomain(*bounds_fast(f), tol); + Piecewise<SBasis> result=compose(reciprocal_fn,f); + truncateResult(result,order); + return(result); +} +Piecewise<SBasis> reciprocal(Piecewise<SBasis> const &f, double tol, int order){ + Piecewise<SBasis> reciprocal_fn=reciprocalOnDomain(*bounds_fast(f), tol); + Piecewise<SBasis> result=compose(reciprocal_fn,f); + truncateResult(result,order); + return(result); +} + +/** + * \brief Returns a Piecewise SBasis with prescribed values at prescribed times. + * + * \param times: vector of times at which the values are given. Should be sorted in increasing order. + * \param values: vector of prescribed values. Should have the same size as times and be sorted accordingly. + * \param smoothness: (defaults to 1) regularity class of the result: 0=piecewise linear, 1=continuous derivative, etc... + */ +Piecewise<SBasis> interpolate(std::vector<double> times, std::vector<double> values, unsigned smoothness){ + assert ( values.size() == times.size() ); + if ( values.empty() ) return Piecewise<SBasis>(); + if ( values.size() == 1 ) return Piecewise<SBasis>(values[0]);//what about time?? + + SBasis sk = shift(Linear(1.),smoothness); + SBasis bump_in = integral(sk); + bump_in -= bump_in.at0(); + bump_in /= bump_in.at1(); + SBasis bump_out = reverse( bump_in ); + + Piecewise<SBasis> result; + result.cuts.push_back(times[0]); + for (unsigned i = 0; i<values.size()-1; i++){ + result.push(bump_out*values[i]+bump_in*values[i+1],times[i+1]); + } + return result; +} + +} + +/* + 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/src/2geom/sbasis-math.h b/src/2geom/sbasis-math.h new file mode 100644 index 0000000..e191dae --- /dev/null +++ b/src/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/src/2geom/sbasis-poly.cpp b/src/2geom/sbasis-poly.cpp new file mode 100644 index 0000000..ffee43f --- /dev/null +++ b/src/2geom/sbasis-poly.cpp @@ -0,0 +1,59 @@ +#include <2geom/sbasis-poly.h> + +namespace Geom{ + +/** Changes the basis of p to be sbasis. + \param p the Monomial basis polynomial + \returns the Symmetric basis polynomial + +This algorithm is horribly slow and numerically terrible. Only for testing. +*/ +SBasis poly_to_sbasis(Poly const & p) { + SBasis x = Linear(0, 1); + SBasis r; + + for(int i = p.size()-1; i >= 0; i--) { + r = SBasis(Linear(p[i], p[i])) + multiply(x, r); + } + r.normalize(); + return r; + +} + +/** Changes the basis of p to be monomial. + \param p the Symmetric basis polynomial + \returns the Monomial basis polynomial + +This algorithm is horribly slow and numerically terrible. Only for testing. +*/ +Poly sbasis_to_poly(SBasis const & sb) { + if(sb.isZero()) + return Poly(); + Poly S; // (1-x)x = -1*x^2 + 1*x + 0 + Poly A, B; + B.push_back(0); + B.push_back(1); + A.push_back(1); + A.push_back(-1); + S = A*B; + Poly r; + + for(int i = sb.size()-1; i >= 0; i--) { + r = S*r + sb[i][0]*A + sb[i][1]*B; + } + r.normalize(); + return r; +} + +}; + +/* + 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/src/2geom/sbasis-poly.h b/src/2geom/sbasis-poly.h new file mode 100644 index 0000000..d18bc36 --- /dev/null +++ b/src/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/src/2geom/sbasis-roots.cpp b/src/2geom/sbasis-roots.cpp new file mode 100644 index 0000000..244b7ef --- /dev/null +++ b/src/2geom/sbasis-roots.cpp @@ -0,0 +1,656 @@ +/** + * @file + * @brief Root finding for sbasis functions. + *//* + * Authors: + * Nathan Hurst <njh@njhurst.com> + * JF Barraud + * Copyright 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. + * + */ + + /* + * It is more efficient to find roots of f(t) = c_0, c_1, ... all at once, rather than iterating. + * + * Todo/think about: + * multi-roots using bernstein method, one approach would be: + sort c + take median and find roots of that + whenever a segment lies entirely on one side of the median, + find the median of the half and recurse. + + in essence we are implementing quicksort on a continuous function + + * the gsl poly roots finder is faster than bernstein too, but we don't use it for 3 reasons: + + a) it requires conversion to poly, which is numerically unstable + + b) it requires gsl (which is currently not a dependency, and would bring in a whole slew of unrelated stuff) + + c) it finds all roots, even complex ones. We don't want to accidentally treat a nearly real root as a real root + +From memory gsl poly roots was about 10 times faster than bernstein in the case where all the roots +are in [0,1] for polys of order 5. I spent some time working out whether eigenvalue root finding +could be done directly in sbasis space, but the maths was too hard for me. -- njh + +jfbarraud: eigenvalue root finding could be done directly in sbasis space ? + +njh: I don't know, I think it should. You would make a matrix whose characteristic polynomial was +correct, but do it by putting the sbasis terms in the right spots in the matrix. normal eigenvalue +root finding makes a matrix that is a diagonal + a row along the top. This matrix has the property +that its characteristic poly is just the poly whose coefficients are along the top row. + +Now an sbasis function is a linear combination of the poly coeffs. So it seems to me that you +should be able to put the sbasis coeffs directly into a matrix in the right spots so that the +characteristic poly is the sbasis. You'll still have problems b) and c). + +We might be able to lift an eigenvalue solver and include that directly into 2geom. Eigenvalues +also allow you to find intersections of multiple curves but require solving n*m x n*m matrices. + + **/ + +#include <cmath> +#include <map> + +#include <2geom/sbasis.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/solver.h> + +using namespace std; + +namespace Geom{ + +/** Find the smallest interval that bounds a + \param a sbasis function + \returns interval + +*/ + +#ifdef USE_SBASIS_OF +OptInterval bounds_exact(SBasisOf<double> const &a) { + Interval result = Interval(a.at0(), a.at1()); + SBasisOf<double> df = derivative(a); + vector<double>extrema = roots(df); + for (unsigned i=0; i<extrema.size(); i++){ + result.extendTo(a(extrema[i])); + } + return result; +} +#else +OptInterval bounds_exact(SBasis const &a) { + Interval result = Interval(a.at0(), a.at1()); + SBasis df = derivative(a); + vector<double>extrema = roots(df); + for (unsigned i=0; i<extrema.size(); i++){ + result.expandTo(a(extrema[i])); + } + return result; +} +#endif + +/** Find a small interval that bounds a + \param a sbasis function + \returns interval + +*/ +// I have no idea how this works, some clever bounding argument by jfb. +#ifdef USE_SBASIS_OF +OptInterval bounds_fast(const SBasisOf<double> &sb, int order) { +#else +OptInterval bounds_fast(const SBasis &sb, int order) { +#endif + Interval res(0,0); // an empty sbasis is 0. + + for(int j = sb.size()-1; j>=order; j--) { + double a=sb[j][0]; + double b=sb[j][1]; + + double v, t = 0; + v = res.min(); + if (v<0) t = ((b-a)/v+1)*0.5; + if (v>=0 || t<0 || t>1) { + res.setMin(std::min(a,b)); + } else { + res.setMin(lerp(t, a+v*t, b)); + } + + v = res.max(); + if (v>0) t = ((b-a)/v+1)*0.5; + if (v<=0 || t<0 || t>1) { + res.setMax(std::max(a,b)); + }else{ + res.setMax(lerp(t, a+v*t, b)); + } + } + if (order>0) res*=std::pow(.25,order); + return res; +} + +/** Find a small interval that bounds a(t) for t in i to order order + \param sb sbasis function + \param i domain interval + \param order number of terms + \return interval + +*/ +#ifdef USE_SBASIS_OF +OptInterval bounds_local(const SBasisOf<double> &sb, const OptInterval &i, int order) { +#else +OptInterval bounds_local(const SBasis &sb, const OptInterval &i, int order) { +#endif + double t0=i->min(), t1=i->max(), lo=0., hi=0.; + for(int j = sb.size()-1; j>=order; j--) { + double a=sb[j][0]; + double b=sb[j][1]; + + double t = 0; + if (lo<0) t = ((b-a)/lo+1)*0.5; + if (lo>=0 || t<t0 || t>t1) { + lo = std::min(a*(1-t0)+b*t0+lo*t0*(1-t0),a*(1-t1)+b*t1+lo*t1*(1-t1)); + }else{ + lo = lerp(t, a+lo*t, b); + } + + if (hi>0) t = ((b-a)/hi+1)*0.5; + if (hi<=0 || t<t0 || t>t1) { + hi = std::max(a*(1-t0)+b*t0+hi*t0*(1-t0),a*(1-t1)+b*t1+hi*t1*(1-t1)); + }else{ + hi = lerp(t, a+hi*t, b); + } + } + Interval res = Interval(lo,hi); + if (order>0) res*=std::pow(.25,order); + return res; +} + +//-- multi_roots ------------------------------------ +// goal: solve f(t)=c for several c at once. +/* algo: -compute f at both ends of the given segment [a,b]. + -compute bounds m<df(t)<M for df on the segment. + let c and C be the levels below and above f(a): + going from f(a) down to c with slope m takes at least time (f(a)-c)/m + going from f(a) up to C with slope M takes at least time (C-f(a))/M + From this we conclude there are no roots before a'=a+min((f(a)-c)/m,(C-f(a))/M). + Do the same for b: compute some b' such that there are no roots in (b',b]. + -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b']. + unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots, + making things tricky and unpleasant... +*/ +//TODO: Make sure the code is "rounding-errors proof" and take care about repetition of roots! + + +static int upper_level(vector<double> const &levels,double x,double tol=0.){ + return(upper_bound(levels.begin(),levels.end(),x-tol)-levels.begin()); +} + +#ifdef USE_SBASIS_OF +static void multi_roots_internal(SBasis const &f, + SBasis const &df, +#else +static void multi_roots_internal(SBasis const &f, + SBasis const &df, +#endif + std::vector<double> const &levels, + std::vector<std::vector<double> > &roots, + double htol, + double vtol, + double a, + double fa, + double b, + double fb){ + + if (f.isZero(0)){ + int idx; + idx=upper_level(levels,0,vtol); + if (idx<(int)levels.size()&&fabs(levels.at(idx))<=vtol){ + roots[idx].push_back(a); + roots[idx].push_back(b); + } + return; + } +////useful? +// if (f.size()==1){ +// int idxa=upper_level(levels,fa); +// int idxb=upper_level(levels,fb); +// if (fa==fb){ +// if (fa==levels[idxa]){ +// roots[a]=idxa; +// roots[b]=idxa; +// } +// return; +// } +// int idx_min=std::min(idxa,idxb); +// int idx_max=std::max(idxa,idxb); +// if (idx_max==levels.size()) idx_max-=1; +// for(int i=idx_min;i<=idx_max; i++){ +// double t=a+(b-a)*(levels[i]-fa)/(fb-fa); +// if(a<t&&t<b) roots[t]=i; +// } +// return; +// } + if ((b-a)<htol){ + //TODO: use different tol for t and f ? + //TODO: unsigned idx ? (remove int casts when fixed) + int idx=std::min(upper_level(levels,fa,vtol),upper_level(levels,fb,vtol)); + if (idx==(int)levels.size()) idx-=1; + double c=levels.at(idx); + if((fa-c)*(fb-c)<=0||fabs(fa-c)<vtol||fabs(fb-c)<vtol){ + roots[idx].push_back((a+b)/2); + } + return; + } + + int idxa=upper_level(levels,fa,vtol); + int idxb=upper_level(levels,fb,vtol); + + Interval bs = *bounds_local(df,Interval(a,b)); + + //first times when a level (higher or lower) can be reached from a or b. + double ta_hi,tb_hi,ta_lo,tb_lo; + ta_hi=ta_lo=b+1;//default values => no root there. + tb_hi=tb_lo=a-1;//default values => no root there. + + if (idxa<(int)levels.size() && fabs(fa-levels.at(idxa))<vtol){//a can be considered a root. + //ta_hi=ta_lo=a; + roots[idxa].push_back(a); + ta_hi=ta_lo=a+htol; + }else{ + if (bs.max()>0 && idxa<(int)levels.size()) + ta_hi=a+(levels.at(idxa )-fa)/bs.max(); + if (bs.min()<0 && idxa>0) + ta_lo=a+(levels.at(idxa-1)-fa)/bs.min(); + } + if (idxb<(int)levels.size() && fabs(fb-levels.at(idxb))<vtol){//b can be considered a root. + //tb_hi=tb_lo=b; + roots[idxb].push_back(b); + tb_hi=tb_lo=b-htol; + }else{ + if (bs.min()<0 && idxb<(int)levels.size()) + tb_hi=b+(levels.at(idxb )-fb)/bs.min(); + if (bs.max()>0 && idxb>0) + tb_lo=b+(levels.at(idxb-1)-fb)/bs.max(); + } + + double t0,t1; + t0=std::min(ta_hi,ta_lo); + t1=std::max(tb_hi,tb_lo); + //hum, rounding errors frighten me! so I add this +tol... + if (t0>t1+htol) return;//no root here. + + if (fabs(t1-t0)<htol){ + multi_roots_internal(f,df,levels,roots,htol,vtol,t0,f(t0),t1,f(t1)); + }else{ + double t,t_left,t_right,ft,ft_left,ft_right; + t_left =t_right =t =(t0+t1)/2; + ft_left=ft_right=ft=f(t); + int idx=upper_level(levels,ft,vtol); + if (idx<(int)levels.size() && fabs(ft-levels.at(idx))<vtol){//t can be considered a root. + roots[idx].push_back(t); + //we do not want to count it twice (from the left and from the right) + t_left =t-htol/2; + t_right=t+htol/2; + ft_left =f(t_left); + ft_right=f(t_right); + } + multi_roots_internal(f,df,levels,roots,htol,vtol,t0 ,f(t0) ,t_left,ft_left); + multi_roots_internal(f,df,levels,roots,htol,vtol,t_right,ft_right,t1 ,f(t1) ); + } +} + +/** Solve f(t)=c for several c at once. + \param f sbasis function + \param levels vector of 'y' values + \param htol, vtol + \param a, b left and right bounds + \returns a vector of vectors, one for each y giving roots + +Effectively computes: +results = roots(f(y_i)) for all y_i + +* algo: -compute f at both ends of the given segment [a,b]. + -compute bounds m<df(t)<M for df on the segment. + let c and C be the levels below and above f(a): + going from f(a) down to c with slope m takes at least time (f(a)-c)/m + going from f(a) up to C with slope M takes at least time (C-f(a))/M + From this we conclude there are no roots before a'=a+min((f(a)-c)/m,(C-f(a))/M). + Do the same for b: compute some b' such that there are no roots in (b',b]. + -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b']. + unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots, + making things tricky and unpleasant... + +TODO: Make sure the code is "rounding-errors proof" and take care about repetition of roots! +*/ +std::vector<std::vector<double> > multi_roots(SBasis const &f, + std::vector<double> const &levels, + double htol, + double vtol, + double a, + double b){ + + std::vector<std::vector<double> > roots(levels.size(), std::vector<double>()); + + SBasis df=derivative(f); + multi_roots_internal(f,df,levels,roots,htol,vtol,a,f(a),b,f(b)); + + return(roots); +} + + +static bool compareIntervalMin( Interval I, Interval J ){ + return I.min()<J.min(); +} +static bool compareIntervalMax( Interval I, Interval J ){ + return I.max()<J.max(); +} + +//find the first interval whose max is >= x +static unsigned upper_level(vector<Interval> const &levels, double x ){ + return( lower_bound( levels.begin(), levels.end(), Interval(x,x), compareIntervalMax) - levels.begin() ); +} + +static std::vector<Interval> fuseContiguous(std::vector<Interval> const &sets, double tol=0.){ + std::vector<Interval> result; + if (sets.empty() ) return result; + result.push_back( sets.front() ); + for (unsigned i=1; i < sets.size(); i++ ){ + if ( result.back().max() + tol >= sets[i].min() ){ + result.back().unionWith( sets[i] ); + }else{ + result.push_back( sets[i] ); + } + } + return result; +} + +/** level_sets internal method. +* algorithm: (~adaptation of Newton method versus 'accroissements finis') + -compute f at both ends of the given segment [a,b]. + -compute bounds m<df(t)<M for df on the segment. + Suppose f(a) is between two 'levels' c and C. Then + f won't enter c before a + (f(a)-c.max())/m + f won't enter C before a + (C.min()-f(a))/M + From this we conclude nothing happens before a'=a+min((f(a)-c.max())/m,(C.min()-f(a))/M). + We do the same for b: compute some b' such that nothing happens in (b',b]. + -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b']. + + If f(a) or f(b) belongs to some 'level' C, then use the same argument to find a' or b' such + that f remains in C on [a,a'] or [b',b]. In case f is monotonic, we also know f won't enter another + level before or after some time, allowing us to restrict the search a little more. + + unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots, + making things tricky and unpleasant... +*/ + +static void level_sets_internal(SBasis const &f, + SBasis const &df, + std::vector<Interval> const &levels, + std::vector<std::vector<Interval> > &solsets, + double a, + double fa, + double b, + double fb, + double tol=1e-5){ + + if (f.isZero(0)){ + unsigned idx; + idx=upper_level( levels, 0. ); + if (idx<levels.size() && levels[idx].contains(0.)){ + solsets[idx].push_back( Interval(a,b) ) ; + } + return; + } + + unsigned idxa=upper_level(levels,fa); + unsigned idxb=upper_level(levels,fb); + + Interval bs = *bounds_local(df,Interval(a,b)); + + //first times when a level (higher or lower) can be reached from a or b. + double ta_hi; // f remains below next level for t<ta_hi + double ta_lo; // f remains above prev level for t<ta_lo + double tb_hi; // f remains below next level for t>tb_hi + double tb_lo; // f remains above next level for t>tb_lo + + ta_hi=ta_lo=b+1;//default values => no root there. + tb_hi=tb_lo=a-1;//default values => no root there. + + //--- if f(a) belongs to a level.------- + if ( idxa < levels.size() && levels[idxa].contains( fa ) ){ + //find the first time when we may exit this level. + ta_lo = a + ( levels[idxa].min() - fa)/bs.min(); + ta_hi = a + ( levels[idxa].max() - fa)/bs.max(); + if ( ta_lo < a || ta_lo > b ) ta_lo = b; + if ( ta_hi < a || ta_hi > b ) ta_hi = b; + //move to that time for the next iteration. + solsets[idxa].push_back( Interval( a, std::min( ta_lo, ta_hi ) ) ); + }else{ + //--- if f(b) does not belong to a level.------- + if ( idxa == 0 ){ + ta_lo = b; + }else{ + ta_lo = a + ( levels[idxa-1].max() - fa)/bs.min(); + if ( ta_lo < a ) ta_lo = b; + } + if ( idxa == levels.size() ){ + ta_hi = b; + }else{ + ta_hi = a + ( levels[idxa].min() - fa)/bs.max(); + if ( ta_hi < a ) ta_hi = b; + } + } + + //--- if f(b) belongs to a level.------- + if (idxb<levels.size() && levels.at(idxb).contains(fb)){ + //find the first time from b when we may exit this level. + tb_lo = b + ( levels[idxb].min() - fb ) / bs.max(); + tb_hi = b + ( levels[idxb].max() - fb ) / bs.min(); + if ( tb_lo > b || tb_lo < a ) tb_lo = a; + if ( tb_hi > b || tb_hi < a ) tb_hi = a; + //move to that time for the next iteration. + solsets[idxb].push_back( Interval( std::max( tb_lo, tb_hi ), b) ); + }else{ + //--- if f(b) does not belong to a level.------- + if ( idxb == 0 ){ + tb_lo = a; + }else{ + tb_lo = b + ( levels[idxb-1].max() - fb)/bs.max(); + if ( tb_lo > b ) tb_lo = a; + } + if ( idxb == levels.size() ){ + tb_hi = a; + }else{ + tb_hi = b + ( levels[idxb].min() - fb)/bs.min(); + if ( tb_hi > b ) tb_hi = a; + } + + + if ( bs.min() < 0 && idxb < levels.size() ) + tb_hi = b + ( levels[idxb ].min() - fb ) / bs.min(); + if ( bs.max() > 0 && idxb > 0 ) + tb_lo = b + ( levels[idxb-1].max() - fb ) / bs.max(); + } + + //let [t0,t1] be the next interval where to search. + double t0=std::min(ta_hi,ta_lo); + double t1=std::max(tb_hi,tb_lo); + + if (t0>=t1) return;//no root here. + + //if the interval is smaller than our resolution: + //pretend f simultaneously meets all the levels between f(t0) and f(t1)... + if ( t1 - t0 <= tol ){ + Interval f_t0t1 ( f(t0), f(t1) ); + unsigned idxmin = std::min(idxa, idxb); + unsigned idxmax = std::max(idxa, idxb); + //push [t0,t1] into all crossed level. Cheat to avoid overlapping intervals on different levels? + if ( idxmax > idxmin ){ + for (unsigned idx = idxmin; idx < idxmax; idx++){ + solsets[idx].push_back( Interval( t0, t1 ) ); + } + } + if ( idxmax < levels.size() && f_t0t1.intersects( levels[idxmax] ) ){ + solsets[idxmax].push_back( Interval( t0, t1 ) ); + } + return; + } + + //To make sure we finally exit the level jump at least by tol: + t0 = std::min( std::max( t0, a + tol ), b ); + t1 = std::max( std::min( t1, b - tol ), a ); + + double t =(t0+t1)/2; + double ft=f(t); + level_sets_internal( f, df, levels, solsets, t0, f(t0), t, ft ); + level_sets_internal( f, df, levels, solsets, t, ft, t1, f(t1) ); +} + +std::vector<std::vector<Interval> > level_sets(SBasis const &f, + std::vector<Interval> const &levels, + double a, double b, double tol){ + + std::vector<std::vector<Interval> > solsets(levels.size(), std::vector<Interval>()); + + SBasis df=derivative(f); + level_sets_internal(f,df,levels,solsets,a,f(a),b,f(b),tol); + // Fuse overlapping intervals... + for (unsigned i=0; i<solsets.size(); i++){ + if ( solsets[i].size() == 0 ) continue; + std::sort( solsets[i].begin(), solsets[i].end(), compareIntervalMin ); + solsets[i] = fuseContiguous( solsets[i], tol ); + } + return solsets; +} + +std::vector<Interval> level_set (SBasis const &f, double level, double vtol, double a, double b, double tol){ + Interval fat_level( level - vtol, level + vtol ); + return level_set(f, fat_level, a, b, tol); +} +std::vector<Interval> level_set (SBasis const &f, Interval const &level, double a, double b, double tol){ + std::vector<Interval> levels(1,level); + return level_sets(f,levels, a, b, tol).front() ; +} +std::vector<std::vector<Interval> > level_sets (SBasis const &f, std::vector<double> const &levels, double vtol, double a, double b, double tol){ + std::vector<Interval> fat_levels( levels.size(), Interval()); + for (unsigned i = 0; i < levels.size(); i++){ + fat_levels[i] = Interval( levels[i]-vtol, levels[i]+vtol); + } + return level_sets(f, fat_levels, a, b, tol); +} + + +//------------------------------------- +//------------------------------------- + + +void subdiv_sbasis(SBasis const & s, + std::vector<double> & roots, + double left, double right) { + OptInterval bs = bounds_fast(s); + if(!bs || bs->min() > 0 || bs->max() < 0) + return; // no roots here + if(s.tailError(1) < 1e-7) { + double t = s[0][0] / (s[0][0] - s[0][1]); + roots.push_back(left*(1-t) + t*right); + return; + } + double middle = (left + right)/2; + subdiv_sbasis(compose(s, Linear(0, 0.5)), roots, left, middle); + subdiv_sbasis(compose(s, Linear(0.5, 1.)), roots, middle, right); +} + +// It is faster to use the bernstein root finder for small degree polynomials (<100?. + +std::vector<double> roots1(SBasis const & s) { + std::vector<double> res; + double d = s[0][0] - s[0][1]; + if(d != 0) { + double r = s[0][0] / d; + if(0 <= r && r <= 1) + res.push_back(r); + } + return res; +} + +std::vector<double> roots1(SBasis const & s, Interval const ivl) { + std::vector<double> res; + double d = s[0][0] - s[0][1]; + if(d != 0) { + double r = s[0][0] / d; + if(ivl.contains(r)) + res.push_back(r); + } + return res; +} + +/** Find all t s.t s(t) = 0 + \param a sbasis function + \see Bezier::roots + \returns vector of zeros (roots) + +*/ +std::vector<double> roots(SBasis const & s) { + switch(s.size()) { + case 0: + assert(false); + return std::vector<double>(); + case 1: + return roots1(s); + default: + { + Bezier bz; + sbasis_to_bezier(bz, s); + return bz.roots(); + } + } +} +std::vector<double> roots(SBasis const & s, Interval const ivl) { + switch(s.size()) { + case 0: + assert(false); + return std::vector<double>(); + case 1: + return roots1(s, ivl); + default: + { + Bezier bz; + sbasis_to_bezier(bz, s); + return bz.roots(ivl); + } + } +} + +}; + +/* + 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/src/2geom/sbasis-to-bezier.cpp b/src/2geom/sbasis-to-bezier.cpp new file mode 100644 index 0000000..010cf7c --- /dev/null +++ b/src/2geom/sbasis-to-bezier.cpp @@ -0,0 +1,570 @@ +/* + * Symmetric Power Basis - Bernstein Basis conversion routines + * + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * + * 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. + */ + + +#include <2geom/sbasis-to-bezier.h> +#include <2geom/d2.h> +#include <2geom/choose.h> +#include <2geom/path-sink.h> +#include <2geom/exception.h> +#include <2geom/convex-hull.h> + +#include <iostream> + + + + +namespace Geom +{ + +/* + * Symmetric Power Basis - Bernstein Basis conversion routines + * + * some remark about precision: + * interval [0,1], subdivisions: 10^3 + * - bezier_to_sbasis : up to degree ~72 precision is at least 10^-5 + * up to degree ~87 precision is at least 10^-3 + * - sbasis_to_bezier : up to order ~63 precision is at least 10^-15 + * precision is at least 10^-14 even beyond order 200 + * + * interval [-1,1], subdivisions: 10^3 + * - bezier_to_sbasis : up to degree ~21 precision is at least 10^-5 + * up to degree ~24 precision is at least 10^-3 + * - sbasis_to_bezier : up to order ~11 precision is at least 10^-5 + * up to order ~13 precision is at least 10^-3 + * + * interval [-10,10], subdivisions: 10^3 + * - bezier_to_sbasis : up to degree ~7 precision is at least 10^-5 + * up to degree ~8 precision is at least 10^-3 + * - sbasis_to_bezier : up to order ~3 precision is at least 10^-5 + * up to order ~4 precision is at least 10^-3 + * + * references: + * this implementation is based on the following article: + * J.Sanchez-Reyes - The Symmetric Analogue of the Polynomial Power Basis + */ + +inline +double binomial(unsigned int n, unsigned int k) +{ + return choose<double>(n, k); +} + +inline +int sgn(unsigned int j, unsigned int k) +{ + assert (j >= k); + // we are sure that j >= k + return ((j-k) & 1u) ? -1 : 1; +} + + +/** Changes the basis of p to be bernstein. + \param p the Symmetric basis polynomial + \returns the Bernstein basis polynomial + + if the degree is even q is the order in the symmetrical power basis, + if the degree is odd q is the order + 1 + n is always the polynomial degree, i. e. the Bezier order + sz is the number of bezier handles. +*/ +void sbasis_to_bezier (Bezier & bz, SBasis const& sb, size_t sz) +{ + assert(sb.size() > 0); + + size_t q, n; + bool even; + if (sz == 0) + { + q = sb.size(); + if (sb[q-1][0] == sb[q-1][1]) + { + even = true; + --q; + n = 2*q; + } + else + { + even = false; + n = 2*q-1; + } + } + else + { + q = (sz > 2*sb.size()-1) ? sb.size() : (sz+1)/2; + n = sz-1; + even = false; + } + bz.clear(); + bz.resize(n+1); + double Tjk; + for (size_t k = 0; k < q; ++k) + { + for (size_t j = k; j < n-k; ++j) // j <= n-k-1 + { + Tjk = binomial(n-2*k-1, j-k); + bz[j] += (Tjk * sb[k][0]); + bz[n-j] += (Tjk * sb[k][1]); // n-k <-> [k][1] + } + } + if (even) + { + bz[q] += sb[q][0]; + } + // the resulting coefficients are with respect to the scaled Bernstein + // basis so we need to divide them by (n, j) binomial coefficient + for (size_t j = 1; j < n; ++j) + { + bz[j] /= binomial(n, j); + } + bz[0] = sb[0][0]; + bz[n] = sb[0][1]; +} + +void sbasis_to_bezier(D2<Bezier> &bz, D2<SBasis> const &sb, size_t sz) +{ + if (sz == 0) { + sz = std::max(sb[X].size(), sb[Y].size())*2; + } + sbasis_to_bezier(bz[X], sb[X], sz); + sbasis_to_bezier(bz[Y], sb[Y], sz); +} + +/** Changes the basis of p to be Bernstein. + \param p the D2 Symmetric basis polynomial + \returns the D2 Bernstein basis polynomial + + sz is always the polynomial degree, i. e. the Bezier order +*/ +void sbasis_to_bezier (std::vector<Point> & bz, D2<SBasis> const& sb, size_t sz) +{ + D2<Bezier> bez; + sbasis_to_bezier(bez, sb, sz); + bz = bezier_points(bez); +} + +/** Changes the basis of p to be Bernstein. + \param p the D2 Symmetric basis polynomial + \returns the D2 Bernstein basis cubic polynomial + +Bezier is always cubic. +For general asymmetric case, fit the SBasis function value at midpoint +For parallel, symmetric case, find the point of closest approach to the midpoint +For parallel, anti-symmetric case, fit the SBasis slope at midpoint +*/ +void sbasis_to_cubic_bezier (std::vector<Point> & bz, D2<SBasis> const& sb) +{ + double delx[2], dely[2]; + double xprime[2], yprime[2]; + double midx = 0; + double midy = 0; + double midx_0, midy_0; + double numer[2], numer_0[2]; + double denom; + double div; + + if ((sb[X].size() == 0) || (sb[Y].size() == 0)) { + THROW_RANGEERROR("size of sb is too small"); + } + + sbasis_to_bezier(bz, sb, 4); // zeroth-order estimate + if ((sb[X].size() < 3) && (sb[Y].size() < 3)) + return; // cubic bezier estimate is exact + Geom::ConvexHull bezhull(bz); + +// calculate first derivatives of x and y wrt t + + for (int i = 0; i < 2; ++i) { + xprime[i] = sb[X][0][1] - sb[X][0][0]; + yprime[i] = sb[Y][0][1] - sb[Y][0][0]; + } + if (sb[X].size() > 1) { + xprime[0] += sb[X][1][0]; + xprime[1] -= sb[X][1][1]; + } + if (sb[Y].size() > 1) { + yprime[0] += sb[Y][1][0]; + yprime[1] -= sb[Y][1][1]; + } + +// calculate midpoint at t = 0.5 + + div = 2; + for (size_t i = 0; i < sb[X].size(); ++i) { + midx += (sb[X][i][0] + sb[X][i][1])/div; + div *= 4; + } + + div = 2; + for (size_t i = 0; i < sb[Y].size(); ++i) { + midy += (sb[Y][i][0] + sb[Y][i][1])/div; + div *= 4; + } + +// is midpoint in hull: if not, the solution will be ill-conditioned, LP Bug 1428683 + + if (!bezhull.contains(Geom::Point(midx, midy))) + return; + +// calculate Bezier control arms + + midx = 8*midx - 4*bz[0][X] - 4*bz[3][X]; // re-define relative to center + midy = 8*midy - 4*bz[0][Y] - 4*bz[3][Y]; + midx_0 = sb[X][1][0] + sb[X][1][1]; // zeroth order estimate + midy_0 = sb[Y][1][0] + sb[Y][1][1]; + + if ((std::abs(xprime[0]) < EPSILON) && (std::abs(yprime[0]) < EPSILON) + && ((std::abs(xprime[1]) > EPSILON) || (std::abs(yprime[1]) > EPSILON))) { // degenerate handle at 0 : use distance of closest approach + numer[0] = midx*xprime[1] + midy*yprime[1]; + denom = 3.0*(xprime[1]*xprime[1] + yprime[1]*yprime[1]); + delx[0] = 0; + dely[0] = 0; + delx[1] = -xprime[1]*numer[0]/denom; + dely[1] = -yprime[1]*numer[0]/denom; + } else if ((std::abs(xprime[1]) < EPSILON) && (std::abs(yprime[1]) < EPSILON) + && ((std::abs(xprime[0]) > EPSILON) || (std::abs(yprime[0]) > EPSILON))) { // degenerate handle at 1 : ditto + numer[1] = midx*xprime[0] + midy*yprime[0]; + denom = 3.0*(xprime[0]*xprime[0] + yprime[0]*yprime[0]); + delx[0] = xprime[0]*numer[1]/denom; + dely[0] = yprime[0]*numer[1]/denom; + delx[1] = 0; + dely[1] = 0; + } else if (std::abs(xprime[1]*yprime[0] - yprime[1]*xprime[0]) > // general case : fit mid fxn value + 0.002 * std::abs(xprime[1]*xprime[0] + yprime[1]*yprime[0])) { // approx. 0.1 degree of angle + double test1 = (bz[1][Y] - bz[0][Y])*(bz[3][X] - bz[0][X]) - (bz[1][X] - bz[0][X])*(bz[3][Y] - bz[0][Y]); + double test2 = (bz[2][Y] - bz[0][Y])*(bz[3][X] - bz[0][X]) - (bz[2][X] - bz[0][X])*(bz[3][Y] - bz[0][Y]); + if (test1*test2 < 0) // reject anti-symmetric case, LP Bug 1428267 & Bug 1428683 + return; + denom = 3.0*(xprime[1]*yprime[0] - yprime[1]*xprime[0]); + for (int i = 0; i < 2; ++i) { + numer_0[i] = xprime[1 - i]*midy_0 - yprime[1 - i]*midx_0; + numer[i] = xprime[1 - i]*midy - yprime[1 - i]*midx; + delx[i] = xprime[i]*numer[i]/denom; + dely[i] = yprime[i]*numer[i]/denom; + if (numer_0[i]*numer[i] < 0) // check for reversal of direction, LP Bug 1544680 + return; + } + if (std::abs((numer[0] - numer_0[0])*numer_0[1]) > 10.0*std::abs((numer[1] - numer_0[1])*numer_0[0]) // check for asymmetry + || std::abs((numer[1] - numer_0[1])*numer_0[0]) > 10.0*std::abs((numer[0] - numer_0[0])*numer_0[1])) + return; + } else if ((xprime[0]*xprime[1] < 0) || (yprime[0]*yprime[1] < 0)) { // symmetric case : use distance of closest approach + numer[0] = midx*xprime[0] + midy*yprime[0]; + denom = 6.0*(xprime[0]*xprime[0] + yprime[0]*yprime[0]); + delx[0] = xprime[0]*numer[0]/denom; + dely[0] = yprime[0]*numer[0]/denom; + delx[1] = -delx[0]; + dely[1] = -dely[0]; + } else { // anti-symmetric case : fit mid slope + // calculate slope at t = 0.5 + midx = 0; + div = 1; + for (size_t i = 0; i < sb[X].size(); ++i) { + midx += (sb[X][i][1] - sb[X][i][0])/div; + div *= 4; + } + midy = 0; + div = 1; + for (size_t i = 0; i < sb[Y].size(); ++i) { + midy += (sb[Y][i][1] - sb[Y][i][0])/div; + div *= 4; + } + if (midx*yprime[0] != midy*xprime[0]) { + denom = midx*yprime[0] - midy*xprime[0]; + numer[0] = midx*(bz[3][Y] - bz[0][Y]) - midy*(bz[3][X] - bz[0][X]); + for (int i = 0; i < 2; ++i) { + delx[i] = xprime[0]*numer[0]/denom; + dely[i] = yprime[0]*numer[0]/denom; + } + } else { // linear case + for (int i = 0; i < 2; ++i) { + delx[i] = (bz[3][X] - bz[0][X])/3; + dely[i] = (bz[3][Y] - bz[0][Y])/3; + } + } + } + bz[1][X] = bz[0][X] + delx[0]; + bz[1][Y] = bz[0][Y] + dely[0]; + bz[2][X] = bz[3][X] - delx[1]; + bz[2][Y] = bz[3][Y] - dely[1]; +} + +/** Changes the basis of p to be sbasis. + \param p the Bernstein basis polynomial + \returns the Symmetric basis polynomial + + if the degree is even q is the order in the symmetrical power basis, + if the degree is odd q is the order + 1 + n is always the polynomial degree, i. e. the Bezier order +*/ +void bezier_to_sbasis (SBasis & sb, Bezier const& bz) +{ + size_t n = bz.order(); + size_t q = (n+1) / 2; + size_t even = (n & 1u) ? 0 : 1; + sb.clear(); + sb.resize(q + even, Linear(0, 0)); + double Tjk; + for (size_t k = 0; k < q; ++k) + { + for (size_t j = k; j < q; ++j) + { + Tjk = sgn(j, k) * binomial(n-j-k, j-k) * binomial(n, k); + sb[j][0] += (Tjk * bz[k]); + sb[j][1] += (Tjk * bz[n-k]); // n-j <-> [j][1] + } + for (size_t j = k+1; j < q; ++j) + { + Tjk = sgn(j, k) * binomial(n-j-k-1, j-k-1) * binomial(n, k); + sb[j][0] += (Tjk * bz[n-k]); + sb[j][1] += (Tjk * bz[k]); // n-j <-> [j][1] + } + } + if (even) + { + for (size_t k = 0; k < q; ++k) + { + Tjk = sgn(q,k) * binomial(n, k); + sb[q][0] += (Tjk * (bz[k] + bz[n-k])); + } + sb[q][0] += (binomial(n, q) * bz[q]); + sb[q][1] = sb[q][0]; + } + sb[0][0] = bz[0]; + sb[0][1] = bz[n]; +} + + +/** Changes the basis of d2 p to be sbasis. + \param p the d2 Bernstein basis polynomial + \returns the d2 Symmetric basis polynomial + + if the degree is even q is the order in the symmetrical power basis, + if the degree is odd q is the order + 1 + n is always the polynomial degree, i. e. the Bezier order +*/ +void bezier_to_sbasis (D2<SBasis> & sb, std::vector<Point> const& bz) +{ + size_t n = bz.size() - 1; + size_t q = (n+1) / 2; + size_t even = (n & 1u) ? 0 : 1; + sb[X].clear(); + sb[Y].clear(); + sb[X].resize(q + even, Linear(0, 0)); + sb[Y].resize(q + even, Linear(0, 0)); + double Tjk; + for (size_t k = 0; k < q; ++k) + { + for (size_t j = k; j < q; ++j) + { + Tjk = sgn(j, k) * binomial(n-j-k, j-k) * binomial(n, k); + sb[X][j][0] += (Tjk * bz[k][X]); + sb[X][j][1] += (Tjk * bz[n-k][X]); + sb[Y][j][0] += (Tjk * bz[k][Y]); + sb[Y][j][1] += (Tjk * bz[n-k][Y]); + } + for (size_t j = k+1; j < q; ++j) + { + Tjk = sgn(j, k) * binomial(n-j-k-1, j-k-1) * binomial(n, k); + sb[X][j][0] += (Tjk * bz[n-k][X]); + sb[X][j][1] += (Tjk * bz[k][X]); + sb[Y][j][0] += (Tjk * bz[n-k][Y]); + sb[Y][j][1] += (Tjk * bz[k][Y]); + } + } + if (even) + { + for (size_t k = 0; k < q; ++k) + { + Tjk = sgn(q,k) * binomial(n, k); + sb[X][q][0] += (Tjk * (bz[k][X] + bz[n-k][X])); + sb[Y][q][0] += (Tjk * (bz[k][Y] + bz[n-k][Y])); + } + sb[X][q][0] += (binomial(n, q) * bz[q][X]); + sb[X][q][1] = sb[X][q][0]; + sb[Y][q][0] += (binomial(n, q) * bz[q][Y]); + sb[Y][q][1] = sb[Y][q][0]; + } + sb[X][0][0] = bz[0][X]; + sb[X][0][1] = bz[n][X]; + sb[Y][0][0] = bz[0][Y]; + sb[Y][0][1] = bz[n][Y]; +} + + +} // end namespace Geom + + +#if 0 +/* +* This version works by inverting a reasonable upper bound on the error term after subdividing the +* curve at $a$. We keep biting off pieces until there is no more curve left. +* +* Derivation: The tail of the power series is $a_ks^k + a_{k+1}s^{k+1} + \ldots = e$. A +* subdivision at $a$ results in a tail error of $e*A^k, A = (1-a)a$. Let this be the desired +* tolerance tol $= e*A^k$ and invert getting $A = e^{1/k}$ and $a = 1/2 - \sqrt{1/4 - A}$ +*/ +void +subpath_from_sbasis_incremental(Geom::OldPathSetBuilder &pb, D2<SBasis> B, double tol, bool initial) { + const unsigned k = 2; // cubic bezier + double te = B.tail_error(k); + assert(B[0].std::isfinite()); + assert(B[1].std::isfinite()); + + //std::cout << "tol = " << tol << std::endl; + while(1) { + double A = std::sqrt(tol/te); // pow(te, 1./k) + double a = A; + if(A < 1) { + A = std::min(A, 0.25); + a = 0.5 - std::sqrt(0.25 - A); // quadratic formula + if(a > 1) a = 1; // clamp to the end of the segment + } else + a = 1; + assert(a > 0); + //std::cout << "te = " << te << std::endl; + //std::cout << "A = " << A << "; a=" << a << std::endl; + D2<SBasis> Bs = compose(B, Linear(0, a)); + assert(Bs.tail_error(k)); + std::vector<Geom::Point> bez = sbasis_to_bezier(Bs, 2); + reverse(bez.begin(), bez.end()); + if (initial) { + pb.start_subpath(bez[0]); + initial = false; + } + pb.push_cubic(bez[1], bez[2], bez[3]); + +// move to next piece of curve + if(a >= 1) break; + B = compose(B, Linear(a, 1)); + te = B.tail_error(k); + } +} + +#endif + +namespace Geom{ + +/** Make a path from a d2 sbasis. + \param p the d2 Symmetric basis polynomial + \returns a Path + + If only_cubicbeziers is true, the resulting path may only contain CubicBezier curves. +*/ +void build_from_sbasis(Geom::PathBuilder &pb, D2<SBasis> const &B, double tol, bool only_cubicbeziers) { + if (!B.isFinite()) { + THROW_EXCEPTION("assertion failed: B.isFinite()"); + } + if(tail_error(B, 3) < tol || sbasis_size(B) == 2) { // nearly cubic enough + if( !only_cubicbeziers && (sbasis_size(B) <= 1) ) { + pb.lineTo(B.at1()); + } else { + std::vector<Geom::Point> bez; +// sbasis_to_bezier(bez, B, 4); + sbasis_to_cubic_bezier(bez, B); + pb.curveTo(bez[1], bez[2], bez[3]); + } + } else { + build_from_sbasis(pb, compose(B, Linear(0, 0.5)), tol, only_cubicbeziers); + build_from_sbasis(pb, compose(B, Linear(0.5, 1)), tol, only_cubicbeziers); + } +} + +/** Make a path from a d2 sbasis. + \param p the d2 Symmetric basis polynomial + \returns a Path + + If only_cubicbeziers is true, the resulting path may only contain CubicBezier curves. +*/ +Path +path_from_sbasis(D2<SBasis> const &B, double tol, bool only_cubicbeziers) { + PathBuilder pb; + pb.moveTo(B.at0()); + build_from_sbasis(pb, B, tol, only_cubicbeziers); + pb.flush(); + return pb.peek().front(); +} + +/** Make a path from a d2 sbasis. + \param p the d2 Symmetric basis polynomial + \returns a Path + + If only_cubicbeziers is true, the resulting path may only contain CubicBezier curves. + TODO: some of this logic should be lifted into svg-path +*/ +PathVector +path_from_piecewise(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &B, double tol, bool only_cubicbeziers) { + Geom::PathBuilder pb; + if(B.size() == 0) return pb.peek(); + Geom::Point start = B[0].at0(); + pb.moveTo(start); + for(unsigned i = 0; ; i++) { + if ( (i+1 == B.size()) + || !are_near(B[i+1].at0(), B[i].at1(), tol) ) + { + //start of a new path + if (are_near(start, B[i].at1()) && sbasis_size(B[i]) <= 1) { + pb.closePath(); + //last line seg already there (because of .closePath()) + goto no_add; + } + build_from_sbasis(pb, B[i], tol, only_cubicbeziers); + if (are_near(start, B[i].at1())) { + //it's closed, the last closing segment was not a straight line so it needed to be added, but still make it closed here with degenerate straight line. + pb.closePath(); + } + no_add: + if (i+1 >= B.size()) { + break; + } + start = B[i+1].at0(); + pb.moveTo(start); + } else { + build_from_sbasis(pb, B[i], tol, only_cubicbeziers); + } + } + pb.flush(); + return pb.peek(); +} + +} + +/* + 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/src/2geom/sbasis-to-bezier.h b/src/2geom/sbasis-to-bezier.h new file mode 100644 index 0000000..eadb47b --- /dev/null +++ b/src/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/src/2geom/sbasis.cpp b/src/2geom/sbasis.cpp new file mode 100644 index 0000000..3efc227 --- /dev/null +++ b/src/2geom/sbasis.cpp @@ -0,0 +1,681 @@ +/* + * sbasis.cpp - S-power basis function class + supporting classes + * + * 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. + */ + +#include <cmath> + +#include <2geom/sbasis.h> +#include <2geom/math-utils.h> + +namespace Geom { + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +/** bound the error from term truncation + \param tail first term to chop + \returns the largest possible error this truncation could give +*/ +double SBasis::tailError(unsigned tail) const { + Interval bs = *bounds_fast(*this, tail); + return std::max(fabs(bs.min()),fabs(bs.max())); +} + +/** test all coefficients are finite +*/ +bool SBasis::isFinite() const { + for(unsigned i = 0; i < size(); i++) { + if(!(*this)[i].isFinite()) + return false; + } + return true; +} + +/** Compute the value and the first n derivatives + \param t position to evaluate + \param n number of derivatives (not counting value) + \returns a vector with the value and the n derivative evaluations + +There is an elegant way to compute the value and n derivatives for a polynomial using a variant of horner's rule. Someone will someday work out how for sbasis. +*/ +std::vector<double> SBasis::valueAndDerivatives(double t, unsigned n) const { + std::vector<double> ret(n+1); + ret[0] = valueAt(t); + SBasis tmp = *this; + for(unsigned i = 1; i < n+1; i++) { + tmp.derive(); + ret[i] = tmp.valueAt(t); + } + return ret; +} + + +/** Compute the pointwise sum of a and b (Exact) + \param a,b sbasis functions + \returns sbasis equal to a+b + +*/ +SBasis operator+(const SBasis& a, const SBasis& b) { + const unsigned out_size = std::max(a.size(), b.size()); + const unsigned min_size = std::min(a.size(), b.size()); + SBasis result(out_size, Linear()); + + for(unsigned i = 0; i < min_size; i++) { + result[i] = a[i] + b[i]; + } + for(unsigned i = min_size; i < a.size(); i++) + result[i] = a[i]; + for(unsigned i = min_size; i < b.size(); i++) + result[i] = b[i]; + + assert(result.size() == out_size); + return result; +} + +/** Compute the pointwise difference of a and b (Exact) + \param a,b sbasis functions + \returns sbasis equal to a-b + +*/ +SBasis operator-(const SBasis& a, const SBasis& b) { + const unsigned out_size = std::max(a.size(), b.size()); + const unsigned min_size = std::min(a.size(), b.size()); + SBasis result(out_size, Linear()); + + for(unsigned i = 0; i < min_size; i++) { + result[i] = a[i] - b[i]; + } + for(unsigned i = min_size; i < a.size(); i++) + result[i] = a[i]; + for(unsigned i = min_size; i < b.size(); i++) + result[i] = -b[i]; + + assert(result.size() == out_size); + return result; +} + +/** Compute the pointwise sum of a and b and store in a (Exact) + \param a,b sbasis functions + \returns sbasis equal to a+b + +*/ +SBasis& operator+=(SBasis& a, const SBasis& b) { + const unsigned out_size = std::max(a.size(), b.size()); + const unsigned min_size = std::min(a.size(), b.size()); + a.resize(out_size); + + for(unsigned i = 0; i < min_size; i++) + a[i] += b[i]; + for(unsigned i = min_size; i < b.size(); i++) + a[i] = b[i]; + + assert(a.size() == out_size); + return a; +} + +/** Compute the pointwise difference of a and b and store in a (Exact) + \param a,b sbasis functions + \returns sbasis equal to a-b + +*/ +SBasis& operator-=(SBasis& a, const SBasis& b) { + const unsigned out_size = std::max(a.size(), b.size()); + const unsigned min_size = std::min(a.size(), b.size()); + a.resize(out_size); + + for(unsigned i = 0; i < min_size; i++) + a[i] -= b[i]; + for(unsigned i = min_size; i < b.size(); i++) + a[i] = -b[i]; + + assert(a.size() == out_size); + return a; +} + +/** Compute the pointwise product of a and b (Exact) + \param a,b sbasis functions + \returns sbasis equal to a*b + +*/ +SBasis operator*(SBasis const &a, double k) { + SBasis c(a.size(), Linear()); + for(unsigned i = 0; i < a.size(); i++) + c[i] = a[i] * k; + return c; +} + +/** Compute the pointwise product of a and b and store the value in a (Exact) + \param a,b sbasis functions + \returns sbasis equal to a*b + +*/ +SBasis& operator*=(SBasis& 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; +} + +/** multiply a by x^sh in place (Exact) + \param a sbasis function + \param sh power + \returns a + +*/ +SBasis shift(SBasis const &a, int sh) { + size_t n = a.size()+sh; + SBasis c(n, Linear()); + size_t m = std::max(0, sh); + + for(int i = 0; i < sh; i++) + c[i] = Linear(0,0); + for(size_t i = m, j = std::max(0,-sh); i < n; i++, j++) + c[i] = a[j]; + return c; +} + +/** multiply a by x^sh (Exact) + \param a linear function + \param sh power + \returns a* x^sh + +*/ +SBasis shift(Linear const &a, int sh) { + size_t n = 1+sh; + SBasis c(n, Linear()); + + for(int i = 0; i < sh; i++) + c[i] = Linear(0,0); + if(sh >= 0) + c[sh] = a; + return c; +} + +#if 0 +SBasis multiply(SBasis const &a, SBasis const &b) { + // c = {a0*b0 - shift(1, a.Tri*b.Tri), a1*b1 - shift(1, a.Tri*b.Tri)} + + // shift(1, a.Tri*b.Tri) + SBasis c(a.size() + b.size(), Linear(0,0)); + if(a.isZero() || b.isZero()) + return c; + for(unsigned j = 0; j < b.size(); j++) { + for(unsigned i = j; i < a.size()+j; i++) { + double tri = b[j].tri()*a[i-j].tri(); + c[i+1/*shift*/] += Linear(-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; +} +#else + +/** Compute the pointwise product of a and b adding c (Exact) + \param a,b,c sbasis functions + \returns sbasis equal to a*b+c + +The added term is almost free +*/ +SBasis multiply_add(SBasis const &a, SBasis const &b, SBasis c) { + if(a.isZero() || b.isZero()) + return c; + c.resize(a.size() + b.size(), Linear(0,0)); + for(unsigned j = 0; j < b.size(); j++) { + for(unsigned i = j; i < a.size()+j; i++) { + double tri = b[j].tri()*a[i-j].tri(); + c[i+1/*shift*/] += Linear(-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; +} + +/** Compute the pointwise product of a and b (Exact) + \param a,b sbasis functions + \returns sbasis equal to a*b + +*/ +SBasis multiply(SBasis const &a, SBasis const &b) { + if(a.isZero() || b.isZero()) { + SBasis c(1, Linear(0,0)); + return c; + } + SBasis c(a.size() + b.size(), Linear(0,0)); + return multiply_add(a, b, c); +} +#endif +/** Compute the integral of a (Exact) + \param a sbasis functions + \returns sbasis integral(a) + +*/ +SBasis integral(SBasis const &c) { + SBasis a; + a.resize(c.size() + 1, Linear(0,0)); + a[0] = Linear(0,0); + + for(unsigned k = 1; k < c.size() + 1; k++) { + double ahat = -c[k-1].tri()/(2*k); + a[k][0] = a[k][1] = ahat; + } + double aTri = 0; + for(int k = c.size()-1; k >= 0; k--) { + aTri = (c[k].hat() + (k+1)*aTri/2)/(2*k+1); + a[k][0] -= aTri/2; + a[k][1] += aTri/2; + } + a.normalize(); + return a; +} + +/** Compute the derivative of a (Exact) + \param a sbasis functions + \returns sbasis da/dt + +*/ +SBasis derivative(SBasis const &a) { + SBasis c; + c.resize(a.size(), Linear(0,0)); + if(a.isZero()) + return c; + + for(unsigned k = 0; k < a.size()-1; k++) { + double 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; + double d = (2*k+1)*(a[k][1] - a[k][0]); + if (d == 0 && k > 0) { + c.pop_back(); + } else { + c[k][0] = d; + c[k][1] = d; + } + + return c; +} + +/** Compute the derivative of this inplace (Exact) + +*/ +void SBasis::derive() { // in place version + if(isZero()) return; + for(unsigned k = 0; k < size()-1; k++) { + double 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 = size()-1; + double d = (2*k+1)*((*this)[k][1] - (*this)[k][0]); + if (d == 0 && k > 0) { + pop_back(); + } else { + (*this)[k][0] = d; + (*this)[k][1] = d; + } +} + +/** Compute the sqrt of a + \param a sbasis functions + \returns sbasis \f[ \sqrt{a} \f] + +It is recommended to use the piecewise version unless you have good reason. +TODO: convert int k to unsigned k, and remove cast +*/ +SBasis sqrt(SBasis const &a, int k) { + SBasis c; + if(a.isZero() || k == 0) + return c; + c.resize(k, Linear(0,0)); + c[0] = Linear(std::sqrt(a[0][0]), std::sqrt(a[0][1])); + SBasis r = a - multiply(c, c); // remainder + + for(unsigned i = 1; i <= (unsigned)k && i<r.size(); i++) { + Linear ci(r[i][0]/(2*c[0][0]), r[i][1]/(2*c[0][1])); + SBasis cisi = shift(ci, i); + r -= multiply(shift((c*2 + cisi), i), SBasis(ci)); + r.truncate(k+1); + c += cisi; + if(r.tailError(i) == 0) // if exact + break; + } + + return c; +} + +/** Compute the recpirocal of a + \param a sbasis functions + \returns sbasis 1/a + +It is recommended to use the piecewise version unless you have good reason. +*/ +SBasis reciprocal(Linear const &a, int k) { + SBasis c; + assert(!a.isZero()); + c.resize(k, Linear(0,0)); + double r_s0 = (a.tri()*a.tri())/(-a[0]*a[1]); + double r_s0k = 1; + for(unsigned i = 0; i < (unsigned)k; i++) { + c[i] = Linear(r_s0k/a[0], r_s0k/a[1]); + r_s0k *= r_s0; + } + return c; +} + +/** Compute a / b to k terms + \param a,b sbasis functions + \returns sbasis a/b + +It is recommended to use the piecewise version unless you have good reason. +*/ +SBasis divide(SBasis const &a, SBasis const &b, int k) { + SBasis c; + assert(!a.isZero()); + SBasis r = a; // remainder + + k++; + r.resize(k, Linear(0,0)); + c.resize(k, Linear(0,0)); + + for(unsigned i = 0; i < (unsigned)k; i++) { + Linear ci(r[i][0]/b[0][0], r[i][1]/b[0][1]); //H0 + c[i] += ci; + r -= shift(multiply(ci,b), i); + r.truncate(k+1); + if(r.tailError(i) == 0) // if exact + break; + } + + return c; +} + +/** Compute a composed with b + \param a,b sbasis functions + \returns sbasis a(b(t)) + + return a0 + s(a1 + s(a2 +... where s = (1-u)u; ak =(1 - u)a^0_k + ua^1_k +*/ +SBasis compose(SBasis const &a, SBasis const &b) { + SBasis s = multiply((SBasis(Linear(1,1))-b), b); + SBasis r; + + for(int i = a.size()-1; i >= 0; i--) { + r = multiply_add(r, s, SBasis(Linear(a[i][0])) - b*a[i][0] + b*a[i][1]); + } + return r; +} + +/** Compute a composed with b to k terms + \param a,b sbasis functions + \returns sbasis a(b(t)) + + return a0 + s(a1 + s(a2 +... where s = (1-u)u; ak =(1 - u)a^0_k + ua^1_k +*/ +SBasis compose(SBasis const &a, SBasis const &b, unsigned k) { + SBasis s = multiply((SBasis(Linear(1,1))-b), b); + SBasis r; + + for(int i = a.size()-1; i >= 0; i--) { + r = multiply_add(r, s, SBasis(Linear(a[i][0])) - b*a[i][0] + b*a[i][1]); + } + r.truncate(k); + return r; +} + +SBasis portion(const SBasis &t, double from, double to) { + double fv = t.valueAt(from); + double tv = t.valueAt(to); + SBasis ret = compose(t, Linear(from, to)); + ret.at0() = fv; + ret.at1() = tv; + return ret; +} + +/* +Inversion algorithm. The notation is certainly very misleading. The +pseudocode should say: + +c(v) := 0 +r(u) := r_0(u) := u +for i:=0 to k do + c_i(v) := H_0(r_i(u)/(t_1)^i; u) + c(v) := c(v) + c_i(v)*t^i + r(u) := r(u) ? c_i(u)*(t(u))^i +endfor +*/ + +//#define DEBUG_INVERSION 1 + +/** find the function a^-1 such that a^-1 composed with a to k terms is the identity function + \param a sbasis function + \returns sbasis a^-1 s.t. a^-1(a(t)) = 1 + + The function must have 'unit range'("a00 = 0 and a01 = 1") and be monotonic. +*/ +SBasis inverse(SBasis a, int k) { + assert(a.size() > 0); + double a0 = a[0][0]; + if(a0 != 0) { + a -= a0; + } + double a1 = a[0][1]; + assert(a1 != 0); // not invertable. + + if(a1 != 1) { + a /= a1; + } + SBasis c(k, Linear()); // c(v) := 0 + if(a.size() >= 2 && k == 2) { + c[0] = Linear(0,1); + Linear t1(1+a[1][0], 1-a[1][1]); // t_1 + c[1] = Linear(-a[1][0]/t1[0], -a[1][1]/t1[1]); + } else if(a.size() >= 2) { // non linear + SBasis r = Linear(0,1); // r(u) := r_0(u) := u + Linear t1(1./(1+a[1][0]), 1./(1-a[1][1])); // 1./t_1 + Linear one(1,1); + Linear t1i = one; // t_1^0 + SBasis one_minus_a = SBasis(one) - a; + SBasis t = multiply(one_minus_a, a); // t(u) + SBasis ti(one); // t(u)^0 +#ifdef DEBUG_INVERSION + std::cout << "a=" << a << std::endl; + std::cout << "1-a=" << one_minus_a << std::endl; + std::cout << "t1=" << t1 << std::endl; + //assert(t1 == t[1]); +#endif + + //c.resize(k+1, Linear(0,0)); + for(unsigned i = 0; i < (unsigned)k; i++) { // for i:=0 to k do +#ifdef DEBUG_INVERSION + std::cout << "-------" << i << ": ---------" <<std::endl; + std::cout << "r=" << r << std::endl + << "c=" << c << std::endl + << "ti=" << ti << std::endl + << std::endl; +#endif + if(r.size() <= i) // ensure enough space in the remainder, probably not needed + r.resize(i+1, Linear(0,0)); + Linear ci(r[i][0]*t1i[0], r[i][1]*t1i[1]); // c_i(v) := H_0(r_i(u)/(t_1)^i; u) +#ifdef DEBUG_INVERSION + std::cout << "t1i=" << t1i << std::endl; + std::cout << "ci=" << ci << std::endl; +#endif + for(int dim = 0; dim < 2; dim++) // t1^-i *= 1./t1 + t1i[dim] *= t1[dim]; + c[i] = ci; // c(v) := c(v) + c_i(v)*t^i + // change from v to u parameterisation + SBasis civ = one_minus_a*ci[0] + a*ci[1]; + // r(u) := r(u) - c_i(u)*(t(u))^i + // We can truncate this to the number of final terms, as no following terms can + // contribute to the result. + r -= multiply(civ,ti); + r.truncate(k); + if(r.tailError(i) == 0) + break; // yay! + ti = multiply(ti,t); + } +#ifdef DEBUG_INVERSION + std::cout << "##########################" << std::endl; +#endif + } else + c = Linear(0,1); // linear + c -= a0; // invert the offset + c /= a1; // invert the slope + return c; +} + +/** Compute the sine of a to k terms + \param b linear function + \returns sbasis sin(a) + +It is recommended to use the piecewise version unless you have good reason. +*/ +SBasis sin(Linear b, int k) { + SBasis s(k+2, Linear()); + s[0] = Linear(std::sin(b[0]), std::sin(b[1])); + double tr = s[0].tri(); + double t2 = b.tri(); + s[1] = Linear(std::cos(b[0])*t2 - tr, -std::cos(b[1])*t2 + tr); + + t2 *= t2; + for(int i = 0; i < k; i++) { + Linear bo(4*(i+1)*s[i+1][0] - 2*s[i+1][1], + -2*s[i+1][0] + 4*(i+1)*s[i+1][1]); + bo -= s[i]*(t2/(i+1)); + + + s[i+2] = bo/double(i+2); + } + + return s; +} + +/** Compute the cosine of a + \param b linear function + \returns sbasis cos(a) + +It is recommended to use the piecewise version unless you have good reason. +*/ +SBasis cos(Linear bo, int k) { + return sin(Linear(bo[0] + M_PI/2, + bo[1] + M_PI/2), + k); +} + +/** compute fog^-1. + \param f,g sbasis functions + \returns sbasis f(g^-1(t)). + +("zero" = double comparison threshold. *!*we might divide by "zero"*!*) +TODO: compute order according to tol? +TODO: requires g(0)=0 & g(1)=1 atm... adaptation to other cases should be obvious! +*/ +SBasis compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double zero){ + SBasis result(order, Linear(0.)); //result + SBasis r=f; //remainder + SBasis Pk=Linear(1)-g,Qk=g,sg=Pk*Qk; + Pk.truncate(order); + Qk.truncate(order); + Pk.resize(order,Linear(0.)); + Qk.resize(order,Linear(0.)); + r.resize(order,Linear(0.)); + + int vs = valuation(sg,zero); + if (vs == 0) { // to prevent infinite loop + return result; + } + + for (unsigned k=0; k<order; k+=vs){ + double p10 = Pk.at(k)[0];// we have to solve the linear system: + double p01 = Pk.at(k)[1];// + double q10 = Qk.at(k)[0];// p10*a + q10*b = r10 + double q01 = Qk.at(k)[1];// & + double r10 = r.at(k)[0];// p01*a + q01*b = r01 + double r01 = r.at(k)[1];// + double a,b; + double det = p10*q01-p01*q10; + + //TODO: handle det~0!! + if (fabs(det)<zero){ + a=b=0; + }else{ + a=( q01*r10-q10*r01)/det; + b=(-p01*r10+p10*r01)/det; + } + result[k] = Linear(a,b); + r=r-Pk*a-Qk*b; + + Pk=Pk*sg; + Qk=Qk*sg; + + Pk.resize(order,Linear(0.)); // truncates if too high order, expands with zeros if too low + Qk.resize(order,Linear(0.)); + r.resize(order,Linear(0.)); + + } + result.normalize(); + return result; +} + +} + +/* + 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/src/2geom/sbasis.h b/src/2geom/sbasis.h new file mode 100644 index 0000000..6923017 --- /dev/null +++ b/src/2geom/sbasis.h @@ -0,0 +1,529 @@ +/** @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 <vector> +#include <cassert> +#include <iostream> + +#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> const &ls) + : d(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/src/2geom/solve-bezier-one-d.cpp b/src/2geom/solve-bezier-one-d.cpp new file mode 100644 index 0000000..3a25e98 --- /dev/null +++ b/src/2geom/solve-bezier-one-d.cpp @@ -0,0 +1,245 @@ + +#include <2geom/solver.h> +#include <2geom/choose.h> +#include <2geom/bezier.h> +#include <2geom/point.h> + +#include <cmath> +#include <algorithm> +//#include <valarray> + +/*** Find the zeros of the bernstein function. The code subdivides until it is happy with the + * linearity of the function. This requires an O(degree^2) subdivision for each step, even when + * there is only one solution. + */ + +namespace Geom{ + +template<class t> +static int SGN(t x) { return (x > 0 ? 1 : (x < 0 ? -1 : 0)); } + +//const unsigned MAXDEPTH = 23; // Maximum depth for recursion. Using floats means 23 bits precision max + +//const double BEPSILON = ldexp(1.0,(-MAXDEPTH-1)); /*Flatness control value */ +//const double SECANT_EPSILON = 1e-13; // secant method converges much faster, get a bit more precision +/** + * This function is called _a lot_. We have included various manual memory management stuff to reduce the amount of mallocing that goes on. In the future it is possible that this will hurt performance. + **/ +class Bernsteins{ +public: + static const size_t MAX_DEPTH = 53; + size_t degree, N; + std::vector<double> &solutions; + //std::vector<double> bc; + BinomialCoefficient<double> bc; + + Bernsteins(size_t _degree, std::vector<double> & sol) + : degree(_degree), N(degree+1), solutions(sol), bc(degree) + { + } + + unsigned + control_poly_flat_enough(double const *V); + + void + find_bernstein_roots(double const *w, /* The control points */ + unsigned depth, /* The depth of the recursion */ + double left_t, double right_t); +}; +/* + * find_bernstein_roots : Given an equation in Bernstein-Bernstein form, find all + * of the roots in the open interval (0, 1). Return the number of roots found. + */ +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, double right_t, bool /*use_secant*/) +{ + Bernsteins B(degree, solutions); + B.find_bernstein_roots(w, depth, left_t, right_t); +} + +void +find_bernstein_roots(std::vector<double> &solutions, /* RETURN candidate t-values */ + Geom::Bezier const &bz, /* The control points */ + double left_t, double right_t) +{ + Bernsteins B(bz.degree(), solutions); + Geom::Bezier& bzl = const_cast<Geom::Bezier&>(bz); + double* w = &(bzl[0]); + B.find_bernstein_roots(w, 0, left_t, right_t); +} + + + +void Bernsteins::find_bernstein_roots(double const *w, /* The control points */ + unsigned depth, /* The depth of the recursion */ + double left_t, + double right_t) +{ + + size_t n_crossings = 0; + + int old_sign = SGN(w[0]); + //std::cout << "w[0] = " << w[0] << std::endl; + for (size_t i = 1; i < N; i++) + { + //std::cout << "w[" << i << "] = " << w[i] << std::endl; + int sign = SGN(w[i]); + if (sign != 0) + { + if (sign != old_sign && old_sign != 0) + { + ++n_crossings; + } + old_sign = sign; + } + } + //std::cout << "n_crossings = " << n_crossings << std::endl; + if (n_crossings == 0) return; // no solutions here + + if (n_crossings == 1) /* Unique solution */ + { + //std::cout << "depth = " << depth << std::endl; + /* Stop recursion when the tree is deep enough */ + /* if deep enough, return 1 solution at midpoint */ + if (depth > MAX_DEPTH) + { + //printf("bottom out %d\n", depth); + const double Ax = right_t - left_t; + const double Ay = w[degree] - w[0]; + + solutions.push_back(left_t - Ax*w[0] / Ay); + return; + } + + + double s = 0, t = 1; + double e = 1e-10; + int side = 0; + double r, fs = w[0], ft = w[degree]; + + for (size_t n = 0; n < 100; ++n) + { + r = (fs*t - ft*s) / (fs - ft); + if (fabs(t-s) < e * fabs(t+s)) break; + + double fr = bernstein_value_at(r, w, degree); + + if (fr * ft > 0) + { + t = r; ft = fr; + if (side == -1) fs /= 2; + side = -1; + } + else if (fs * fr > 0) + { + s = r; fs = fr; + if (side == +1) ft /= 2; + side = +1; + } + else break; + } + solutions.push_back(r*right_t + (1-r)*left_t); + return; + + } + + /* Otherwise, solve recursively after subdividing control polygon */ +// double Left[N], /* New left and right */ +// Right[N]; /* control polygons */ + //const double t = 0.5; + double* LR = new double[2*N]; + double* Left = LR; + double* Right = LR + N; + + std::copy(w, w + N, Right); + + Left[0] = Right[0]; + for (size_t i = 1; i < N; ++i) + { + for (size_t j = 0; j < N-i; ++j) + { + Right[j] = (Right[j] + Right[j+1]) * 0.5; + } + Left[i] = Right[0]; + } + + double mid_t = (left_t + right_t) * 0.5; + + + find_bernstein_roots(Left, depth+1, left_t, mid_t); + + + /* Solution is exactly on the subdivision point. */ + if (Right[0] == 0) + { + solutions.push_back(mid_t); + } + + find_bernstein_roots(Right, depth+1, mid_t, right_t); + delete[] LR; +} + +#if 0 +/* + * control_poly_flat_enough : + * Check if the control polygon of a Bernstein curve is flat enough + * for recursive subdivision to bottom out. + * + */ +unsigned +Bernsteins::control_poly_flat_enough(double const *V) +{ + /* Find the perpendicular distance from each interior control point to line connecting V[0] and + * V[degree] */ + + /* Derive the implicit equation for line connecting first */ + /* and last control points */ + const double a = V[0] - V[degree]; + + double max_distance_above = 0.0; + double max_distance_below = 0.0; + double ii = 0, dii = 1./degree; + for (unsigned i = 1; i < degree; i++) { + ii += dii; + /* Compute distance from each of the points to that line */ + const double d = (a + V[i]) * ii - a; + double dist = d*d; + // Find the largest distance + if (d < 0.0) + max_distance_below = std::min(max_distance_below, -dist); + else + max_distance_above = std::max(max_distance_above, dist); + } + + const double abSquared = 1./((a * a) + 1); + + const double intercept_1 = (a - max_distance_above * abSquared); + const double intercept_2 = (a - max_distance_below * abSquared); + + /* Compute bounding interval*/ + const double left_intercept = std::min(intercept_1, intercept_2); + const double right_intercept = std::max(intercept_1, intercept_2); + + const double error = 0.5 * (right_intercept - left_intercept); + //printf("error %g %g %g\n", error, a, BEPSILON * a); + return error < BEPSILON * 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/src/2geom/solve-bezier-parametric.cpp b/src/2geom/solve-bezier-parametric.cpp new file mode 100644 index 0000000..2fb3f41 --- /dev/null +++ b/src/2geom/solve-bezier-parametric.cpp @@ -0,0 +1,189 @@ +#include <2geom/bezier.h> +#include <2geom/point.h> +#include <2geom/solver.h> +#include <algorithm> + +namespace Geom { + +/*** Find the zeros of the parametric function in 2d defined by two beziers X(t), Y(t). The code subdivides until it happy with the linearity of the bezier. This requires an n^2 subdivision for each step, even when there is only one solution. + * + * Perhaps it would be better to subdivide particularly around nodes with changing sign, rather than simply cutting in half. + */ + +#define SGN(a) (((a)<0) ? -1 : 1) + +/* + * Forward declarations + */ +unsigned +crossing_count(Geom::Point const *V, unsigned degree); +static unsigned +control_poly_flat_enough(Geom::Point const *V, unsigned degree); +static double +compute_x_intercept(Geom::Point const *V, unsigned degree); + +const unsigned MAXDEPTH = 64; /* Maximum depth for recursion */ + +const double BEPSILON = ldexp(1.0,-MAXDEPTH-1); /*Flatness control value */ + +unsigned total_steps, total_subs; + +/* + * find_bezier_roots : Given an equation in Bernstein-Bezier form, find all + * of the roots in the interval [0, 1]. Return the number of roots found. + */ +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 */ +{ + total_steps++; + const unsigned max_crossings = crossing_count(w, degree); + switch (max_crossings) { + case 0: /* No solutions here */ + return; + + case 1: + /* Unique solution */ + /* Stop recursion when the tree is deep enough */ + /* if deep enough, return 1 solution at midpoint */ + if (depth >= MAXDEPTH) { + solutions.push_back((w[0][Geom::X] + w[degree][Geom::X]) / 2.0); + return; + } + + // I thought secant method would be faster here, but it'aint. -- njh + + if (control_poly_flat_enough(w, degree)) { + solutions.push_back(compute_x_intercept(w, degree)); + return; + } + break; + } + + /* Otherwise, solve recursively after subdividing control polygon */ + + //Geom::Point Left[degree+1], /* New left and right */ + // Right[degree+1]; /* control polygons */ + std::vector<Geom::Point> Left( degree+1 ), Right(degree+1); + + casteljau_subdivision(0.5, w, Left.data(), Right.data(), degree); + total_subs ++; + find_parametric_bezier_roots(Left.data(), degree, solutions, depth+1); + find_parametric_bezier_roots(Right.data(), degree, solutions, depth+1); +} + + +/* + * crossing_count: + * Count the number of times a Bezier control polygon + * crosses the 0-axis. This number is >= the number of roots. + * + */ +unsigned +crossing_count(Geom::Point const *V, /* Control pts of Bezier curve */ + unsigned degree) /* Degree of Bezier curve */ +{ + unsigned n_crossings = 0; /* Number of zero-crossings */ + + int old_sign = SGN(V[0][Geom::Y]); + for (unsigned i = 1; i <= degree; i++) { + int sign = SGN(V[i][Geom::Y]); + if (sign != old_sign) + n_crossings++; + old_sign = sign; + } + return n_crossings; +} + + + +/* + * control_poly_flat_enough : + * Check if the control polygon of a Bezier curve is flat enough + * for recursive subdivision to bottom out. + * + */ +static unsigned +control_poly_flat_enough(Geom::Point const *V, /* Control points */ + unsigned degree) /* Degree of polynomial */ +{ + /* Find the perpendicular distance from each interior control point to line connecting V[0] and + * V[degree] */ + + /* Derive the implicit equation for line connecting first */ + /* and last control points */ + const double a = V[0][Geom::Y] - V[degree][Geom::Y]; + const double b = V[degree][Geom::X] - V[0][Geom::X]; + const double c = V[0][Geom::X] * V[degree][Geom::Y] - V[degree][Geom::X] * V[0][Geom::Y]; + + const double abSquared = (a * a) + (b * b); + + //double distance[degree]; /* Distances from pts to line */ + std::vector<double> distance(degree); /* Distances from pts to line */ + for (unsigned i = 1; i < degree; i++) { + /* Compute distance from each of the points to that line */ + double & dist(distance[i-1]); + const double d = a * V[i][Geom::X] + b * V[i][Geom::Y] + c; + dist = d*d / abSquared; + if (d < 0.0) + dist = -dist; + } + + + // Find the largest distance + double max_distance_above = 0.0; + double max_distance_below = 0.0; + for (unsigned i = 0; i < degree-1; i++) { + const double d = distance[i]; + if (d < 0.0) + max_distance_below = std::min(max_distance_below, d); + if (d > 0.0) + max_distance_above = std::max(max_distance_above, d); + } + + const double intercept_1 = (c + max_distance_above) / -a; + const double intercept_2 = (c + max_distance_below) / -a; + + /* Compute bounding interval*/ + const double left_intercept = std::min(intercept_1, intercept_2); + const double right_intercept = std::max(intercept_1, intercept_2); + + const double error = 0.5 * (right_intercept - left_intercept); + + if (error < BEPSILON) + return 1; + + return 0; +} + + + +/* + * compute_x_intercept : + * Compute intersection of chord from first control point to last + * with 0-axis. + * + */ +static double +compute_x_intercept(Geom::Point const *V, /* Control points */ + unsigned degree) /* Degree of curve */ +{ + const Geom::Point A = V[degree] - V[0]; + + return (A[Geom::X]*V[0][Geom::Y] - A[Geom::Y]*V[0][Geom::X]) / -A[Geom::Y]; +} + +}; + +/* + 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/src/2geom/solve-bezier.cpp b/src/2geom/solve-bezier.cpp new file mode 100644 index 0000000..4ff42bb --- /dev/null +++ b/src/2geom/solve-bezier.cpp @@ -0,0 +1,304 @@ + +#include <2geom/solver.h> +#include <2geom/choose.h> +#include <2geom/bezier.h> +#include <2geom/point.h> + +#include <cmath> +#include <algorithm> + +/*** Find the zeros of a Bezier. The code subdivides until it is happy with the linearity of the + * function. This requires an O(degree^2) subdivision for each step, even when there is only one + * solution. + * + * We try fairly hard to correctly handle multiple roots. + */ + +//#define debug(x) do{x;}while(0) +#define debug(x) + +namespace Geom{ + +template<class t> +static int SGN(t x) { return (x > 0 ? 1 : (x < 0 ? -1 : 0)); } + +class Bernsteins{ +public: + static const size_t MAX_DEPTH = 22; + std::vector<double> &solutions; + //std::vector<double> dsolutions; + + Bernsteins(std::vector<double> & sol) + : solutions(sol) + {} + + void subdivide(double const *V, + double t, + double *Left, + double *Right); + + double secant(Bezier const &bz); + + + void find_bernstein_roots(Bezier const &bz, unsigned depth, + double left_t, double right_t); +}; + +template <typename T> +inline std::ostream &operator<< (std::ostream &out_file, const std::vector<T> & b) { + out_file << "["; + for(unsigned i = 0; i < b.size(); i++) { + out_file << b[i] << ", "; + } + return out_file << "]"; +} + +void convex_hull_marching(Bezier const &src_bz, Bezier bz, + std::vector<double> &solutions, + double left_t, + double right_t) +{ + while(bz.order() > 0 && bz[0] == 0) { + std::cout << "deflate\n"; + bz = bz.deflate(); + solutions.push_back(left_t); + } + std::cout << std::endl; + if (bz.order() > 0) { + + int old_sign = SGN(bz[0]); + + double left_bound = 0; + double dt = 0; + for (size_t i = 1; i < bz.size(); i++) + { + int sign = SGN(bz[i]); + if (sign != old_sign) + { + dt = double(i) / bz.order(); + left_bound = dt * bz[0] / (bz[0] - bz[i]); + break; + } + old_sign = sign; + } + if (dt == 0) return; + std::cout << bz << std::endl; + std::cout << "dt = " << dt << std::endl; + std::cout << "left_t = " << left_t << std::endl; + std::cout << "right_t = " << right_t << std::endl; + std::cout << "left bound = " << left_bound + << " = " << bz(left_bound) << std::endl; + double new_left_t = left_bound * (right_t - left_t) + left_t; + std::cout << "new_left_t = " << new_left_t << std::endl; + Bezier bzr = portion(src_bz, new_left_t, 1); + while(bzr.order() > 0 && bzr[0] == 0) { + std::cout << "deflate\n"; + bzr = bzr.deflate(); + solutions.push_back(new_left_t); + } + if (left_t < new_left_t) { + convex_hull_marching(src_bz, bzr, + solutions, + new_left_t, right_t); + } else { + std::cout << "epsilon reached\n"; + while(bzr.order() > 0 && fabs(bzr[0]) <= 1e-10) { + std::cout << "deflate\n"; + bzr = bzr.deflate(); + std::cout << bzr << std::endl; + solutions.push_back(new_left_t); + } + + } + } +} + +void +Bezier::find_bezier_roots(std::vector<double> &solutions, + double left_t, double right_t) const { + Bezier bz = *this; + //convex_hull_marching(bz, bz, solutions, left_t, right_t); + //return; + + // a constant bezier, even if identically zero, has no roots + if (bz.isConstant()) { + return; + } + + while(bz[0] == 0) { + debug(std::cout << "deflate\n"); + bz = bz.deflate(); + solutions.push_back(0); + } + if (bz.degree() == 1) { + debug(std::cout << "linear\n"); + + if (SGN(bz[0]) != SGN(bz[1])) { + double d = bz[0] - bz[1]; + if(d != 0) { + double r = bz[0] / d; + if(0 <= r && r <= 1) + solutions.push_back(r); + } + } + return; + } + + //std::cout << "initial = " << bz << std::endl; + Bernsteins B(solutions); + B.find_bernstein_roots(bz, 0, left_t, right_t); + //std::cout << solutions << std::endl; +} + +void Bernsteins::find_bernstein_roots(Bezier const &bz, + unsigned depth, + double left_t, + double right_t) +{ + debug(std::cout << left_t << ", " << right_t << std::endl); + size_t n_crossings = 0; + + int old_sign = SGN(bz[0]); + //std::cout << "w[0] = " << bz[0] << std::endl; + for (size_t i = 1; i < bz.size(); i++) + { + //std::cout << "w[" << i << "] = " << w[i] << std::endl; + int sign = SGN(bz[i]); + if (sign != 0) + { + if (sign != old_sign && old_sign != 0) + { + ++n_crossings; + } + old_sign = sign; + } + } + // if last control point is zero, that counts as crossing too + if (SGN(bz[bz.size()-1]) == 0) { + ++n_crossings; + } + + //std::cout << "n_crossings = " << n_crossings << std::endl; + if (n_crossings == 0) return; // no solutions here + + if (n_crossings == 1) /* Unique solution */ + { + //std::cout << "depth = " << depth << std::endl; + /* Stop recursion when the tree is deep enough */ + /* if deep enough, return 1 solution at midpoint */ + if (depth > MAX_DEPTH) + { + //printf("bottom out %d\n", depth); + const double Ax = right_t - left_t; + const double Ay = bz.at1() - bz.at0(); + + solutions.push_back(left_t - Ax*bz.at0() / Ay); + return; + } + + double r = secant(bz); + solutions.push_back(r*right_t + (1-r)*left_t); + return; + } + /* Otherwise, solve recursively after subdividing control polygon */ + Bezier::Order o(bz); + Bezier Left(o), Right = bz; + double split_t = (left_t + right_t) * 0.5; + + // If subdivision is working poorly, split around the leftmost root of the derivative + if (depth > 2) { + debug(std::cout << "derivative mode\n"); + Bezier dbz = derivative(bz); + + debug(std::cout << "initial = " << dbz << std::endl); + std::vector<double> dsolutions = dbz.roots(Interval(left_t, right_t)); + debug(std::cout << "dsolutions = " << dsolutions << std::endl); + + double dsplit_t = 0.5; + if(!dsolutions.empty()) { + dsplit_t = dsolutions[0]; + split_t = left_t + (right_t - left_t)*dsplit_t; + debug(std::cout << "split_value = " << bz(split_t) << std::endl); + debug(std::cout << "splitting around " << dsplit_t << " = " + << split_t << "\n"); + + } + std::pair<Bezier, Bezier> LR = bz.subdivide(dsplit_t); + Left = LR.first; + Right = LR.second; + } else { + // split at midpoint, because it is cheap + Left[0] = Right[0]; + for (size_t i = 1; i < bz.size(); ++i) + { + for (size_t j = 0; j < bz.size()-i; ++j) + { + Right[j] = (Right[j] + Right[j+1]) * 0.5; + } + Left[i] = Right[0]; + } + } + debug(std::cout << "Solution is exactly on the subdivision point.\n"); + debug(std::cout << Left << " , " << Right << std::endl); + Left = reverse(Left); + while(Right.order() > 0 && fabs(Right[0]) <= 1e-10) { + debug(std::cout << "deflate\n"); + Right = Right.deflate(); + Left = Left.deflate(); + solutions.push_back(split_t); + } + Left = reverse(Left); + if (Right.order() > 0) { + debug(std::cout << Left << " , " << Right << std::endl); + find_bernstein_roots(Left, depth+1, left_t, split_t); + find_bernstein_roots(Right, depth+1, split_t, right_t); + } +} + +double Bernsteins::secant(Bezier const &bz) { + double s = 0, t = 1; + double e = 1e-14; + int side = 0; + double r, fs = bz.at0(), ft = bz.at1(); + + for (size_t n = 0; n < 100; ++n) + { + r = (fs*t - ft*s) / (fs - ft); + if (fabs(t-s) < e * fabs(t+s)) { + debug(std::cout << "error small " << fabs(t-s) + << ", accepting solution " << r + << "after " << n << "iterations\n"); + return r; + } + + double fr = bz.valueAt(r); + + if (fr * ft > 0) + { + t = r; ft = fr; + if (side == -1) fs /= 2; + side = -1; + } + else if (fs * fr > 0) + { + s = r; fs = fr; + if (side == +1) ft /= 2; + side = +1; + } + else break; + } + return r; +} + +}; + +/* + 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/src/2geom/solver.h b/src/2geom/solver.h new file mode 100644 index 0000000..5b082cb --- /dev/null +++ b/src/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/src/2geom/svg-path-parser.cpp b/src/2geom/svg-path-parser.cpp new file mode 100644 index 0000000..0338110 --- /dev/null +++ b/src/2geom/svg-path-parser.cpp @@ -0,0 +1,1615 @@ + +#line 1 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" +/** + * \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. + * + */ + +#include <cstdio> +#include <cmath> +#include <vector> +#include <glib.h> + +#include <2geom/point.h> +#include <2geom/svg-path-parser.h> +#include <2geom/angle.h> + +namespace Geom { + + +#line 48 "/home/mc/lib2geom/src/2geom/svg-path-parser.cpp" +static const char _svg_path_actions[] = { + 0, 1, 0, 1, 1, 1, 2, 1, + 3, 1, 4, 1, 5, 1, 15, 2, + 1, 0, 2, 1, 6, 2, 1, 7, + 2, 1, 8, 2, 1, 9, 2, 1, + 10, 2, 1, 11, 2, 1, 12, 2, + 1, 13, 2, 1, 14, 2, 2, 0, + 2, 3, 0, 2, 4, 0, 2, 5, + 0, 3, 1, 6, 0, 3, 1, 7, + 0, 3, 1, 8, 0, 3, 1, 9, + 0, 3, 1, 10, 0, 3, 1, 11, + 0, 3, 1, 12, 0, 3, 1, 13, + 0, 3, 1, 14, 0 +}; + +static const short _svg_path_key_offsets[] = { + 0, 0, 9, 18, 21, 23, 35, 45, + 48, 50, 53, 55, 67, 77, 80, 82, + 91, 103, 112, 121, 130, 133, 135, 147, + 157, 160, 162, 174, 184, 187, 189, 198, + 205, 211, 218, 225, 231, 241, 251, 254, + 256, 268, 278, 281, 283, 295, 304, 316, + 325, 335, 339, 341, 348, 352, 354, 364, + 368, 370, 380, 389, 398, 401, 403, 415, + 425, 428, 430, 442, 452, 455, 457, 469, + 479, 482, 484, 496, 506, 509, 511, 523, + 533, 536, 538, 550, 559, 571, 580, 592, + 601, 613, 622, 634, 643, 647, 649, 658, + 667, 670, 672, 676, 678, 687, 696, 705, + 708, 710, 722, 732, 735, 737, 749, 759, + 762, 764, 776, 786, 789, 791, 803, 812, + 824, 833, 845, 854, 858, 860, 869, 878, + 881, 883, 895, 905, 908, 910, 922, 932, + 935, 937, 949, 959, 962, 964, 976, 985, + 997, 1006, 1018, 1027, 1031, 1033, 1042, 1051, + 1054, 1056, 1068, 1078, 1081, 1083, 1095, 1104, + 1108, 1110, 1119, 1128, 1131, 1133, 1137, 1139, + 1148, 1157, 1166, 1175, 1184, 1196, 1205, 1209, + 1211, 1220, 1229, 1238, 1247, 1251, 1253, 1263, + 1267, 1269, 1279, 1283, 1285, 1295, 1299, 1301, + 1311, 1315, 1317, 1327, 1331, 1333, 1343, 1347, + 1349, 1359, 1363, 1365, 1375, 1379, 1381, 1391, + 1395, 1397, 1407, 1411, 1413, 1423, 1427, 1429, + 1439, 1443, 1445, 1455, 1459, 1461, 1470, 1474, + 1476, 1486, 1498, 1507, 1517, 1524, 1528, 1530, + 1534, 1536, 1546, 1552, 1584, 1614, 1646, 1678, + 1710, 1740, 1772, 1802, 1834, 1864, 1896, 1926, + 1958, 1988, 2020, 2050, 2082, 2112, 2144, 2174, + 2206, 2236, 2268, 2298, 2330, 2360, 2392, 2422, + 2454, 2484, 2508, 2532, 2564, 2594, 2624, 2656 +}; + +static const char _svg_path_trans_keys[] = { + 13, 32, 43, 45, 46, 9, 10, 48, + 57, 13, 32, 43, 45, 46, 9, 10, + 48, 57, 46, 48, 57, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 43, + 45, 48, 57, 13, 32, 44, 46, 9, + 10, 43, 45, 48, 57, 46, 48, 57, + 48, 57, 46, 48, 57, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 43, + 45, 48, 57, 13, 32, 44, 46, 9, + 10, 43, 45, 48, 57, 46, 48, 57, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 13, 32, 44, 46, 69, + 101, 9, 10, 43, 45, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 13, 32, 43, 45, 46, 9, 10, 48, + 57, 13, 32, 43, 45, 46, 9, 10, + 48, 57, 46, 48, 57, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 43, + 45, 48, 57, 13, 32, 44, 46, 9, + 10, 43, 45, 48, 57, 46, 48, 57, + 48, 57, 13, 32, 44, 46, 69, 101, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 9, 10, 43, 45, 48, 57, + 46, 48, 57, 48, 57, 13, 32, 44, + 69, 101, 9, 10, 48, 57, 13, 32, + 44, 48, 49, 9, 10, 13, 32, 48, + 49, 9, 10, 13, 32, 44, 48, 49, + 9, 10, 13, 32, 44, 48, 49, 9, + 10, 13, 32, 48, 49, 9, 10, 13, + 32, 44, 46, 9, 10, 43, 45, 48, + 57, 13, 32, 44, 46, 9, 10, 43, + 45, 48, 57, 46, 48, 57, 48, 57, + 13, 32, 44, 46, 69, 101, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 9, 10, 43, 45, 48, 57, 46, 48, + 57, 48, 57, 13, 32, 44, 46, 69, + 101, 9, 10, 43, 45, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 13, 32, 44, 46, 69, 101, 9, 10, + 43, 45, 48, 57, 13, 32, 43, 45, + 46, 9, 10, 48, 57, 13, 32, 44, + 46, 69, 101, 9, 10, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 9, 10, 48, 57, 43, 45, 48, 57, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 43, 45, 48, 57, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 13, 32, 43, 45, + 46, 9, 10, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 46, 48, + 57, 48, 57, 13, 32, 44, 46, 69, + 101, 9, 10, 43, 45, 48, 57, 13, + 32, 44, 46, 9, 10, 43, 45, 48, + 57, 46, 48, 57, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 46, 48, 57, 48, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 46, + 48, 57, 48, 57, 13, 32, 44, 46, + 69, 101, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 9, 10, 43, 45, + 48, 57, 46, 48, 57, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 43, + 45, 48, 57, 13, 32, 44, 46, 9, + 10, 43, 45, 48, 57, 46, 48, 57, + 48, 57, 13, 32, 44, 46, 69, 101, + 9, 10, 43, 45, 48, 57, 13, 32, + 43, 45, 46, 9, 10, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 43, + 45, 48, 57, 13, 32, 43, 45, 46, + 9, 10, 48, 57, 13, 32, 44, 46, + 69, 101, 9, 10, 43, 45, 48, 57, + 13, 32, 43, 45, 46, 9, 10, 48, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 43, 45, 48, 57, 48, + 57, 13, 32, 43, 45, 46, 9, 10, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 46, 48, 57, 48, 57, + 43, 45, 48, 57, 48, 57, 13, 32, + 43, 45, 46, 9, 10, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 13, 32, 43, 45, 46, 9, 10, 48, + 57, 46, 48, 57, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 46, 48, 57, 48, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 46, + 48, 57, 48, 57, 13, 32, 44, 46, + 69, 101, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 9, 10, 43, 45, + 48, 57, 46, 48, 57, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 43, + 45, 48, 57, 13, 32, 43, 45, 46, + 9, 10, 48, 57, 13, 32, 44, 46, + 69, 101, 9, 10, 43, 45, 48, 57, + 13, 32, 43, 45, 46, 9, 10, 48, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 43, 45, + 48, 57, 48, 57, 13, 32, 43, 45, + 46, 9, 10, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 46, 48, + 57, 48, 57, 13, 32, 44, 46, 69, + 101, 9, 10, 43, 45, 48, 57, 13, + 32, 44, 46, 9, 10, 43, 45, 48, + 57, 46, 48, 57, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 46, 48, 57, 48, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 46, + 48, 57, 48, 57, 13, 32, 44, 46, + 69, 101, 9, 10, 43, 45, 48, 57, + 13, 32, 43, 45, 46, 9, 10, 48, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 43, 45, 48, 57, 48, + 57, 13, 32, 43, 45, 46, 9, 10, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 46, 48, 57, 48, 57, + 13, 32, 44, 46, 69, 101, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 9, 10, 43, 45, 48, 57, 46, 48, + 57, 48, 57, 13, 32, 44, 46, 69, + 101, 9, 10, 43, 45, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 43, 45, 48, 57, 48, 57, 13, 32, + 43, 45, 46, 9, 10, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 46, 48, 57, 48, 57, 43, 45, 48, + 57, 48, 57, 13, 32, 43, 45, 46, + 9, 10, 48, 57, 13, 32, 43, 45, + 46, 9, 10, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 13, 32, + 43, 45, 46, 9, 10, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 13, 32, 44, 46, 69, 101, 9, 10, + 43, 45, 48, 57, 13, 32, 43, 45, + 46, 9, 10, 48, 57, 43, 45, 48, + 57, 48, 57, 13, 32, 43, 45, 46, + 9, 10, 48, 57, 13, 32, 43, 45, + 46, 9, 10, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 13, 32, + 43, 45, 46, 9, 10, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 43, + 45, 48, 57, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 43, 45, + 48, 57, 48, 57, 13, 32, 44, 46, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 13, 32, 44, 46, 9, + 10, 43, 45, 48, 57, 13, 32, 44, + 48, 49, 9, 10, 43, 45, 48, 57, + 48, 57, 43, 45, 48, 57, 48, 57, + 13, 32, 44, 46, 9, 10, 43, 45, + 48, 57, 13, 32, 77, 109, 9, 10, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 72, 76, 77, 81, + 83, 84, 86, 90, 97, 99, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 69, 72, 76, 77, 81, 83, + 84, 86, 90, 97, 99, 101, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 72, 76, 77, 81, 83, 84, + 86, 90, 97, 99, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 69, 72, 76, 77, 81, 83, 84, 86, + 90, 97, 99, 101, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 72, 76, 77, 81, + 83, 84, 86, 90, 97, 99, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 69, 72, 76, 77, 81, 83, + 84, 86, 90, 97, 99, 101, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 72, 76, 77, 81, 83, 84, + 86, 90, 97, 99, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 69, 72, 76, 77, 81, 83, 84, 86, + 90, 97, 99, 101, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 72, 76, 77, 81, + 83, 84, 86, 90, 97, 99, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 69, 72, 76, 77, 81, 83, + 84, 86, 90, 97, 99, 101, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 72, 76, 77, 81, 83, 84, + 86, 90, 97, 99, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 69, 72, 76, 77, 81, 83, 84, 86, + 90, 97, 99, 101, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 72, 76, 77, 81, + 83, 84, 86, 90, 97, 99, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 13, 32, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 13, 32, 44, 46, + 65, 67, 69, 72, 76, 77, 81, 83, + 84, 86, 90, 97, 99, 101, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 72, 76, 77, 81, 83, 84, + 86, 90, 97, 99, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 0 +}; + +static const char _svg_path_single_lengths[] = { + 0, 5, 5, 1, 0, 6, 4, 1, + 0, 1, 0, 6, 4, 1, 0, 5, + 6, 5, 5, 5, 1, 0, 6, 4, + 1, 0, 6, 4, 1, 0, 5, 5, + 4, 5, 5, 4, 4, 4, 1, 0, + 6, 4, 1, 0, 6, 5, 6, 5, + 6, 2, 0, 3, 2, 0, 4, 2, + 0, 4, 5, 5, 1, 0, 6, 4, + 1, 0, 6, 4, 1, 0, 6, 4, + 1, 0, 6, 4, 1, 0, 6, 4, + 1, 0, 6, 5, 6, 5, 6, 5, + 6, 5, 6, 5, 2, 0, 5, 5, + 1, 0, 2, 0, 5, 5, 5, 1, + 0, 6, 4, 1, 0, 6, 4, 1, + 0, 6, 4, 1, 0, 6, 5, 6, + 5, 6, 5, 2, 0, 5, 5, 1, + 0, 6, 4, 1, 0, 6, 4, 1, + 0, 6, 4, 1, 0, 6, 5, 6, + 5, 6, 5, 2, 0, 5, 5, 1, + 0, 6, 4, 1, 0, 6, 5, 2, + 0, 5, 5, 1, 0, 2, 0, 5, + 5, 5, 5, 5, 6, 5, 2, 0, + 5, 5, 5, 5, 2, 0, 4, 2, + 0, 4, 2, 0, 4, 2, 0, 4, + 2, 0, 4, 2, 0, 4, 2, 0, + 4, 2, 0, 4, 2, 0, 4, 2, + 0, 4, 2, 0, 4, 2, 0, 4, + 2, 0, 4, 2, 0, 5, 2, 0, + 4, 6, 5, 4, 5, 2, 0, 2, + 0, 4, 4, 26, 24, 26, 26, 26, + 24, 26, 24, 26, 24, 26, 24, 26, + 24, 26, 24, 26, 24, 26, 24, 26, + 24, 26, 24, 26, 24, 26, 24, 26, + 24, 22, 22, 26, 24, 24, 26, 24 +}; + +static const char _svg_path_range_lengths[] = { + 0, 2, 2, 1, 1, 3, 3, 1, + 1, 1, 1, 3, 3, 1, 1, 2, + 3, 2, 2, 2, 1, 1, 3, 3, + 1, 1, 3, 3, 1, 1, 2, 1, + 1, 1, 1, 1, 3, 3, 1, 1, + 3, 3, 1, 1, 3, 2, 3, 2, + 2, 1, 1, 2, 1, 1, 3, 1, + 1, 3, 2, 2, 1, 1, 3, 3, + 1, 1, 3, 3, 1, 1, 3, 3, + 1, 1, 3, 3, 1, 1, 3, 3, + 1, 1, 3, 2, 3, 2, 3, 2, + 3, 2, 3, 2, 1, 1, 2, 2, + 1, 1, 1, 1, 2, 2, 2, 1, + 1, 3, 3, 1, 1, 3, 3, 1, + 1, 3, 3, 1, 1, 3, 2, 3, + 2, 3, 2, 1, 1, 2, 2, 1, + 1, 3, 3, 1, 1, 3, 3, 1, + 1, 3, 3, 1, 1, 3, 2, 3, + 2, 3, 2, 1, 1, 2, 2, 1, + 1, 3, 3, 1, 1, 3, 2, 1, + 1, 2, 2, 1, 1, 1, 1, 2, + 2, 2, 2, 2, 3, 2, 1, 1, + 2, 2, 2, 2, 1, 1, 3, 1, + 1, 3, 1, 1, 3, 1, 1, 3, + 1, 1, 3, 1, 1, 3, 1, 1, + 3, 1, 1, 3, 1, 1, 3, 1, + 1, 3, 1, 1, 3, 1, 1, 3, + 1, 1, 3, 1, 1, 2, 1, 1, + 3, 3, 2, 3, 1, 1, 1, 1, + 1, 3, 1, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 1, 1, 3, 3, 3, 3, 3 +}; + +static const short _svg_path_index_offsets[] = { + 0, 0, 8, 16, 19, 21, 31, 39, + 42, 44, 47, 49, 59, 67, 70, 72, + 80, 90, 98, 106, 114, 117, 119, 129, + 137, 140, 142, 152, 160, 163, 165, 173, + 180, 186, 193, 200, 206, 214, 222, 225, + 227, 237, 245, 248, 250, 260, 268, 278, + 286, 295, 299, 301, 307, 311, 313, 321, + 325, 327, 335, 343, 351, 354, 356, 366, + 374, 377, 379, 389, 397, 400, 402, 412, + 420, 423, 425, 435, 443, 446, 448, 458, + 466, 469, 471, 481, 489, 499, 507, 517, + 525, 535, 543, 553, 561, 565, 567, 575, + 583, 586, 588, 592, 594, 602, 610, 618, + 621, 623, 633, 641, 644, 646, 656, 664, + 667, 669, 679, 687, 690, 692, 702, 710, + 720, 728, 738, 746, 750, 752, 760, 768, + 771, 773, 783, 791, 794, 796, 806, 814, + 817, 819, 829, 837, 840, 842, 852, 860, + 870, 878, 888, 896, 900, 902, 910, 918, + 921, 923, 933, 941, 944, 946, 956, 964, + 968, 970, 978, 986, 989, 991, 995, 997, + 1005, 1013, 1021, 1029, 1037, 1047, 1055, 1059, + 1061, 1069, 1077, 1085, 1093, 1097, 1099, 1107, + 1111, 1113, 1121, 1125, 1127, 1135, 1139, 1141, + 1149, 1153, 1155, 1163, 1167, 1169, 1177, 1181, + 1183, 1191, 1195, 1197, 1205, 1209, 1211, 1219, + 1223, 1225, 1233, 1237, 1239, 1247, 1251, 1253, + 1261, 1265, 1267, 1275, 1279, 1281, 1289, 1293, + 1295, 1303, 1313, 1321, 1329, 1336, 1340, 1342, + 1346, 1348, 1356, 1362, 1392, 1420, 1450, 1480, + 1510, 1538, 1568, 1596, 1626, 1654, 1684, 1712, + 1742, 1770, 1800, 1828, 1858, 1886, 1916, 1944, + 1974, 2002, 2032, 2060, 2090, 2118, 2148, 2176, + 2206, 2234, 2258, 2282, 2312, 2340, 2368, 2398 +}; + +static const short _svg_path_indicies[] = { + 0, 0, 2, 2, 3, 0, 4, 1, + 5, 5, 6, 6, 7, 5, 8, 1, + 9, 10, 1, 11, 1, 12, 12, 14, + 15, 16, 16, 12, 13, 11, 1, 17, + 17, 19, 20, 17, 18, 21, 1, 22, + 23, 1, 24, 1, 25, 26, 1, 27, + 1, 28, 28, 30, 31, 32, 32, 28, + 29, 27, 1, 33, 33, 35, 36, 33, + 34, 37, 1, 38, 39, 1, 40, 1, + 41, 41, 42, 42, 43, 41, 44, 1, + 28, 28, 30, 27, 32, 32, 28, 29, + 26, 1, 35, 35, 34, 34, 36, 35, + 37, 1, 45, 45, 46, 46, 47, 45, + 48, 1, 49, 49, 50, 50, 51, 49, + 52, 1, 53, 54, 1, 55, 1, 56, + 56, 58, 59, 60, 60, 56, 57, 55, + 1, 61, 61, 63, 64, 61, 62, 65, + 1, 66, 67, 1, 68, 1, 69, 69, + 71, 72, 73, 73, 69, 70, 68, 1, + 74, 74, 76, 77, 74, 75, 78, 1, + 79, 80, 1, 81, 1, 82, 82, 83, + 84, 84, 82, 81, 1, 85, 85, 86, + 87, 88, 85, 1, 86, 86, 87, 88, + 86, 1, 89, 89, 90, 91, 92, 89, + 1, 93, 93, 94, 95, 96, 93, 1, + 94, 94, 95, 96, 94, 1, 97, 97, + 99, 100, 97, 98, 101, 1, 102, 102, + 104, 105, 102, 103, 106, 1, 107, 108, + 1, 109, 1, 110, 110, 112, 113, 114, + 114, 110, 111, 109, 1, 115, 115, 117, + 118, 115, 116, 119, 1, 120, 121, 1, + 122, 1, 56, 56, 58, 55, 60, 60, + 56, 57, 54, 1, 63, 63, 62, 62, + 64, 63, 65, 1, 69, 69, 71, 68, + 73, 73, 69, 70, 67, 1, 76, 76, + 75, 75, 77, 76, 78, 1, 82, 82, + 83, 81, 84, 84, 82, 80, 1, 123, + 123, 124, 1, 124, 1, 82, 82, 83, + 82, 124, 1, 125, 125, 126, 1, 126, + 1, 69, 69, 71, 72, 69, 70, 126, + 1, 127, 127, 128, 1, 128, 1, 56, + 56, 58, 59, 56, 57, 128, 1, 129, + 129, 130, 130, 131, 129, 132, 1, 133, + 133, 134, 134, 135, 133, 136, 1, 137, + 138, 1, 139, 1, 140, 140, 142, 143, + 144, 144, 140, 141, 139, 1, 145, 145, + 147, 148, 145, 146, 149, 1, 150, 151, + 1, 152, 1, 153, 153, 155, 156, 157, + 157, 153, 154, 152, 1, 158, 158, 160, + 161, 158, 159, 162, 1, 163, 164, 1, + 165, 1, 166, 166, 168, 169, 170, 170, + 166, 167, 165, 1, 171, 171, 173, 174, + 171, 172, 175, 1, 176, 177, 1, 178, + 1, 179, 179, 181, 182, 183, 183, 179, + 180, 178, 1, 184, 184, 186, 187, 184, + 185, 188, 1, 189, 190, 1, 191, 1, + 192, 192, 194, 195, 196, 196, 192, 193, + 191, 1, 197, 197, 199, 200, 197, 198, + 201, 1, 202, 203, 1, 204, 1, 140, + 140, 142, 139, 144, 144, 140, 141, 138, + 1, 147, 147, 146, 146, 148, 147, 149, + 1, 153, 153, 155, 152, 157, 157, 153, + 154, 151, 1, 160, 160, 159, 159, 161, + 160, 162, 1, 166, 166, 168, 165, 170, + 170, 166, 167, 164, 1, 173, 173, 172, + 172, 174, 173, 175, 1, 179, 179, 181, + 178, 183, 183, 179, 180, 177, 1, 186, + 186, 185, 185, 187, 186, 188, 1, 192, + 192, 194, 191, 196, 196, 192, 193, 190, + 1, 199, 199, 198, 198, 200, 199, 201, + 1, 205, 205, 206, 1, 206, 1, 207, + 207, 208, 208, 209, 207, 210, 1, 211, + 211, 212, 212, 213, 211, 214, 1, 215, + 216, 1, 217, 1, 218, 218, 219, 1, + 219, 1, 220, 220, 221, 221, 222, 220, + 223, 1, 224, 224, 225, 225, 226, 224, + 227, 1, 228, 228, 229, 229, 230, 228, + 231, 1, 232, 233, 1, 234, 1, 235, + 235, 237, 238, 239, 239, 235, 236, 234, + 1, 240, 240, 242, 243, 240, 241, 244, + 1, 245, 246, 1, 247, 1, 248, 248, + 250, 251, 252, 252, 248, 249, 247, 1, + 253, 253, 255, 256, 253, 254, 257, 1, + 258, 259, 1, 260, 1, 261, 261, 263, + 264, 265, 265, 261, 262, 260, 1, 266, + 266, 268, 269, 266, 267, 270, 1, 271, + 272, 1, 273, 1, 235, 235, 237, 234, + 239, 239, 235, 236, 233, 1, 242, 242, + 241, 241, 243, 242, 244, 1, 248, 248, + 250, 247, 252, 252, 248, 249, 246, 1, + 255, 255, 254, 254, 256, 255, 257, 1, + 261, 261, 263, 260, 265, 265, 261, 262, + 259, 1, 268, 268, 267, 267, 269, 268, + 270, 1, 274, 274, 275, 1, 275, 1, + 276, 276, 277, 277, 278, 276, 279, 1, + 280, 280, 281, 281, 282, 280, 283, 1, + 284, 285, 1, 286, 1, 287, 287, 289, + 290, 291, 291, 287, 288, 286, 1, 292, + 292, 294, 295, 292, 293, 296, 1, 297, + 298, 1, 299, 1, 300, 300, 302, 303, + 304, 304, 300, 301, 299, 1, 305, 305, + 307, 308, 305, 306, 309, 1, 310, 311, + 1, 312, 1, 313, 313, 315, 316, 317, + 317, 313, 314, 312, 1, 318, 318, 320, + 321, 318, 319, 322, 1, 323, 324, 1, + 325, 1, 287, 287, 289, 286, 291, 291, + 287, 288, 285, 1, 294, 294, 293, 293, + 295, 294, 296, 1, 300, 300, 302, 299, + 304, 304, 300, 301, 298, 1, 307, 307, + 306, 306, 308, 307, 309, 1, 313, 313, + 315, 312, 317, 317, 313, 314, 311, 1, + 320, 320, 319, 319, 321, 320, 322, 1, + 326, 326, 327, 1, 327, 1, 328, 328, + 329, 329, 330, 328, 331, 1, 332, 332, + 333, 333, 334, 332, 335, 1, 336, 337, + 1, 338, 1, 339, 339, 341, 342, 343, + 343, 339, 340, 338, 1, 344, 344, 346, + 347, 344, 345, 348, 1, 349, 350, 1, + 351, 1, 339, 339, 341, 338, 343, 343, + 339, 340, 337, 1, 346, 346, 345, 345, + 347, 346, 348, 1, 352, 352, 353, 1, + 353, 1, 354, 354, 355, 355, 356, 354, + 357, 1, 358, 358, 359, 359, 360, 358, + 361, 1, 362, 363, 1, 364, 1, 365, + 365, 366, 1, 366, 1, 367, 367, 368, + 368, 369, 367, 370, 1, 371, 371, 372, + 372, 373, 371, 374, 1, 375, 375, 376, + 376, 377, 375, 378, 1, 379, 379, 380, + 380, 381, 379, 382, 1, 383, 383, 384, + 384, 385, 383, 386, 1, 12, 12, 14, + 11, 16, 16, 12, 13, 10, 1, 19, + 19, 18, 18, 20, 19, 21, 1, 387, + 387, 388, 1, 388, 1, 389, 389, 390, + 390, 391, 389, 392, 1, 393, 393, 394, + 394, 395, 393, 396, 1, 397, 397, 398, + 398, 399, 397, 400, 1, 401, 401, 402, + 402, 403, 401, 404, 1, 405, 405, 406, + 1, 406, 1, 12, 12, 14, 15, 12, + 13, 406, 1, 407, 407, 408, 1, 408, + 1, 339, 339, 341, 342, 339, 340, 408, + 1, 409, 409, 410, 1, 410, 1, 313, + 313, 315, 316, 313, 314, 410, 1, 411, + 411, 412, 1, 412, 1, 300, 300, 302, + 303, 300, 301, 412, 1, 413, 413, 414, + 1, 414, 1, 287, 287, 289, 290, 287, + 288, 414, 1, 415, 415, 416, 1, 416, + 1, 261, 261, 263, 264, 261, 262, 416, + 1, 417, 417, 418, 1, 418, 1, 248, + 248, 250, 251, 248, 249, 418, 1, 419, + 419, 420, 1, 420, 1, 235, 235, 237, + 238, 235, 236, 420, 1, 421, 421, 422, + 1, 422, 1, 192, 192, 194, 195, 192, + 193, 422, 1, 423, 423, 424, 1, 424, + 1, 179, 179, 181, 182, 179, 180, 424, + 1, 425, 425, 426, 1, 426, 1, 166, + 166, 168, 169, 166, 167, 426, 1, 427, + 427, 428, 1, 428, 1, 153, 153, 155, + 156, 153, 154, 428, 1, 429, 429, 430, + 1, 430, 1, 140, 140, 142, 143, 140, + 141, 430, 1, 431, 431, 432, 1, 432, + 1, 117, 117, 116, 116, 118, 117, 119, + 1, 433, 433, 434, 1, 434, 1, 110, + 110, 112, 113, 110, 111, 434, 1, 110, + 110, 112, 109, 114, 114, 110, 111, 108, + 1, 104, 104, 103, 103, 105, 104, 106, + 1, 435, 435, 437, 438, 435, 436, 439, + 1, 440, 440, 441, 442, 443, 440, 1, + 444, 444, 445, 1, 445, 1, 446, 446, + 447, 1, 447, 1, 28, 28, 30, 31, + 28, 29, 447, 1, 448, 448, 449, 450, + 448, 1, 451, 451, 453, 454, 455, 456, + 457, 458, 459, 460, 461, 462, 463, 464, + 465, 466, 467, 457, 468, 469, 470, 471, + 472, 473, 474, 465, 451, 452, 24, 1, + 475, 475, 41, 43, 476, 477, 478, 479, + 449, 480, 481, 482, 483, 484, 485, 486, + 487, 488, 450, 489, 490, 491, 492, 484, + 475, 42, 44, 1, 493, 493, 495, 496, + 497, 498, 499, 500, 501, 502, 503, 504, + 505, 506, 507, 508, 509, 499, 510, 511, + 512, 513, 514, 515, 516, 507, 493, 494, + 40, 1, 493, 493, 495, 40, 497, 498, + 499, 500, 501, 502, 503, 504, 505, 506, + 507, 508, 509, 499, 510, 511, 512, 513, + 514, 515, 516, 507, 493, 494, 39, 1, + 517, 517, 519, 520, 521, 522, 523, 524, + 525, 526, 527, 528, 529, 530, 531, 532, + 533, 523, 534, 535, 536, 537, 538, 539, + 540, 531, 517, 518, 122, 1, 541, 541, + 49, 51, 476, 477, 478, 479, 449, 480, + 481, 482, 483, 484, 485, 486, 487, 488, + 450, 489, 490, 491, 492, 484, 541, 50, + 52, 1, 542, 542, 544, 545, 546, 547, + 548, 549, 550, 551, 552, 553, 554, 555, + 556, 557, 558, 548, 559, 560, 561, 562, + 563, 564, 565, 556, 542, 543, 204, 1, + 566, 566, 133, 135, 476, 477, 478, 479, + 449, 480, 481, 482, 483, 484, 485, 486, + 487, 488, 450, 489, 490, 491, 492, 484, + 566, 134, 136, 1, 542, 542, 544, 204, + 546, 547, 548, 549, 550, 551, 552, 553, + 554, 555, 556, 557, 558, 548, 559, 560, + 561, 562, 563, 564, 565, 556, 542, 543, + 203, 1, 542, 542, 544, 545, 546, 547, + 549, 550, 551, 552, 553, 554, 555, 556, + 557, 558, 559, 560, 561, 562, 563, 564, + 565, 556, 542, 543, 206, 1, 567, 567, + 569, 570, 571, 572, 573, 574, 575, 576, + 577, 578, 579, 580, 581, 582, 583, 573, + 584, 585, 586, 587, 588, 589, 590, 581, + 567, 568, 217, 1, 591, 591, 211, 213, + 476, 477, 478, 479, 449, 480, 481, 482, + 483, 484, 485, 486, 487, 488, 450, 489, + 490, 491, 492, 484, 591, 212, 214, 1, + 567, 567, 569, 217, 571, 572, 573, 574, + 575, 576, 577, 578, 579, 580, 581, 582, + 583, 573, 584, 585, 586, 587, 588, 589, + 590, 581, 567, 568, 216, 1, 567, 567, + 569, 570, 571, 572, 574, 575, 576, 577, + 578, 579, 580, 581, 582, 583, 584, 585, + 586, 587, 588, 589, 590, 581, 567, 568, + 219, 1, 592, 592, 594, 595, 596, 597, + 598, 599, 600, 601, 602, 603, 604, 605, + 606, 607, 608, 598, 609, 610, 611, 612, + 613, 614, 615, 606, 592, 593, 273, 1, + 616, 616, 228, 230, 476, 477, 478, 479, + 449, 480, 481, 482, 483, 484, 485, 486, + 487, 488, 450, 489, 490, 491, 492, 484, + 616, 229, 231, 1, 592, 592, 594, 273, + 596, 597, 598, 599, 600, 601, 602, 603, + 604, 605, 606, 607, 608, 598, 609, 610, + 611, 612, 613, 614, 615, 606, 592, 593, + 272, 1, 592, 592, 594, 595, 596, 597, + 599, 600, 601, 602, 603, 604, 605, 606, + 607, 608, 609, 610, 611, 612, 613, 614, + 615, 606, 592, 593, 275, 1, 617, 617, + 619, 620, 621, 622, 623, 624, 625, 626, + 627, 628, 629, 630, 631, 632, 633, 623, + 634, 635, 636, 637, 638, 639, 640, 631, + 617, 618, 325, 1, 641, 641, 280, 282, + 476, 477, 478, 479, 449, 480, 481, 482, + 483, 484, 485, 486, 487, 488, 450, 489, + 490, 491, 492, 484, 641, 281, 283, 1, + 617, 617, 619, 325, 621, 622, 623, 624, + 625, 626, 627, 628, 629, 630, 631, 632, + 633, 623, 634, 635, 636, 637, 638, 639, + 640, 631, 617, 618, 324, 1, 617, 617, + 619, 620, 621, 622, 624, 625, 626, 627, + 628, 629, 630, 631, 632, 633, 634, 635, + 636, 637, 638, 639, 640, 631, 617, 618, + 327, 1, 642, 642, 644, 645, 646, 647, + 648, 649, 650, 651, 652, 653, 654, 655, + 656, 657, 658, 648, 659, 660, 661, 662, + 663, 664, 665, 656, 642, 643, 351, 1, + 666, 666, 332, 334, 476, 477, 478, 479, + 449, 480, 481, 482, 483, 484, 485, 486, + 487, 488, 450, 489, 490, 491, 492, 484, + 666, 333, 335, 1, 642, 642, 644, 351, + 646, 647, 648, 649, 650, 651, 652, 653, + 654, 655, 656, 657, 658, 648, 659, 660, + 661, 662, 663, 664, 665, 656, 642, 643, + 350, 1, 642, 642, 644, 645, 646, 647, + 649, 650, 651, 652, 653, 654, 655, 656, + 657, 658, 659, 660, 661, 662, 663, 664, + 665, 656, 642, 643, 353, 1, 667, 667, + 669, 670, 671, 672, 673, 674, 675, 676, + 677, 678, 679, 680, 681, 682, 683, 673, + 684, 685, 686, 687, 688, 689, 690, 681, + 667, 668, 364, 1, 691, 691, 358, 360, + 476, 477, 478, 479, 449, 480, 481, 482, + 483, 484, 485, 486, 487, 488, 450, 489, + 490, 491, 492, 484, 691, 359, 361, 1, + 667, 667, 669, 364, 671, 672, 673, 674, + 675, 676, 677, 678, 679, 680, 681, 682, + 683, 673, 684, 685, 686, 687, 688, 689, + 690, 681, 667, 668, 363, 1, 667, 667, + 669, 670, 671, 672, 674, 675, 676, 677, + 678, 679, 680, 681, 682, 683, 684, 685, + 686, 687, 688, 689, 690, 681, 667, 668, + 366, 1, 692, 692, 693, 694, 695, 696, + 697, 698, 699, 700, 701, 702, 703, 704, + 705, 706, 707, 708, 709, 710, 711, 702, + 692, 1, 712, 712, 476, 477, 478, 479, + 449, 480, 481, 482, 483, 484, 485, 486, + 487, 488, 450, 489, 490, 491, 492, 484, + 712, 1, 451, 451, 453, 24, 455, 456, + 457, 458, 459, 460, 461, 462, 463, 464, + 465, 466, 467, 457, 468, 469, 470, 471, + 472, 473, 474, 465, 451, 452, 23, 1, + 451, 451, 453, 454, 455, 456, 458, 459, + 460, 461, 462, 463, 464, 465, 466, 467, + 468, 469, 470, 471, 472, 473, 474, 465, + 451, 452, 388, 1, 517, 517, 519, 520, + 521, 522, 524, 525, 526, 527, 528, 529, + 530, 531, 532, 533, 534, 535, 536, 537, + 538, 539, 540, 531, 517, 518, 432, 1, + 517, 517, 519, 122, 521, 522, 523, 524, + 525, 526, 527, 528, 529, 530, 531, 532, + 533, 523, 534, 535, 536, 537, 538, 539, + 540, 531, 517, 518, 121, 1, 493, 493, + 495, 496, 497, 498, 500, 501, 502, 503, + 504, 505, 506, 507, 508, 509, 510, 511, + 512, 513, 514, 515, 516, 507, 493, 494, + 445, 1, 0 +}; + +static const short _svg_path_trans_targs[] = { + 2, 0, 3, 4, 172, 2, 3, 4, + 172, 4, 172, 5, 6, 7, 173, 8, + 180, 6, 7, 173, 8, 267, 8, 267, + 235, 10, 16, 11, 12, 13, 17, 14, + 231, 12, 13, 17, 14, 238, 14, 238, + 237, 15, 9, 10, 16, 19, 20, 21, + 44, 19, 20, 21, 44, 21, 44, 22, + 23, 24, 45, 25, 55, 23, 24, 45, + 25, 46, 25, 46, 26, 27, 28, 47, + 29, 52, 27, 28, 47, 29, 48, 29, + 48, 30, 31, 32, 49, 31, 32, 33, + 228, 34, 35, 36, 227, 34, 35, 36, + 227, 37, 38, 226, 39, 225, 37, 38, + 226, 39, 225, 39, 225, 40, 41, 42, + 221, 43, 222, 41, 42, 221, 43, 270, + 43, 270, 239, 50, 51, 53, 54, 56, + 57, 59, 60, 61, 82, 59, 60, 61, + 82, 61, 82, 62, 63, 64, 83, 65, + 216, 63, 64, 83, 65, 84, 65, 84, + 66, 67, 68, 85, 69, 213, 67, 68, + 85, 69, 86, 69, 86, 70, 71, 72, + 87, 73, 210, 71, 72, 87, 73, 88, + 73, 88, 74, 75, 76, 89, 77, 207, + 75, 76, 89, 77, 90, 77, 90, 78, + 79, 80, 91, 81, 204, 79, 80, 91, + 81, 243, 81, 243, 241, 93, 244, 95, + 96, 97, 247, 95, 96, 97, 247, 97, + 247, 245, 99, 248, 15, 9, 10, 16, + 102, 103, 104, 117, 102, 103, 104, 117, + 104, 117, 105, 106, 107, 118, 108, 201, + 106, 107, 118, 108, 119, 108, 119, 109, + 110, 111, 120, 112, 198, 110, 111, 120, + 112, 121, 112, 121, 113, 114, 115, 122, + 116, 195, 114, 115, 122, 116, 251, 116, + 251, 249, 124, 252, 126, 127, 128, 141, + 126, 127, 128, 141, 128, 141, 129, 130, + 131, 142, 132, 192, 130, 131, 142, 132, + 143, 132, 143, 133, 134, 135, 144, 136, + 189, 134, 135, 144, 136, 145, 136, 145, + 137, 138, 139, 146, 140, 186, 138, 139, + 146, 140, 255, 140, 255, 253, 148, 256, + 150, 151, 152, 157, 150, 151, 152, 157, + 152, 157, 153, 154, 155, 158, 156, 183, + 154, 155, 158, 156, 259, 156, 259, 257, + 160, 260, 162, 163, 164, 263, 162, 163, + 164, 263, 164, 263, 261, 166, 264, 19, + 20, 21, 44, 59, 60, 61, 82, 95, + 96, 97, 247, 15, 9, 10, 16, 2, + 3, 4, 172, 175, 268, 102, 103, 104, + 117, 126, 127, 128, 141, 150, 151, 152, + 157, 162, 163, 164, 263, 181, 182, 184, + 185, 187, 188, 190, 191, 193, 194, 196, + 197, 199, 200, 202, 203, 205, 206, 208, + 209, 211, 212, 214, 215, 217, 218, 220, + 269, 223, 224, 37, 38, 226, 39, 225, + 34, 35, 36, 227, 230, 271, 232, 233, + 234, 1, 171, 236, 9, 15, 10, 18, + 58, 174, 94, 100, 1, 101, 125, 149, + 161, 265, 167, 168, 169, 170, 171, 176, + 177, 178, 179, 236, 18, 58, 94, 100, + 101, 125, 149, 161, 265, 167, 168, 169, + 170, 176, 177, 178, 179, 236, 9, 15, + 10, 18, 58, 229, 94, 100, 1, 101, + 125, 149, 161, 265, 167, 168, 169, 170, + 171, 176, 177, 178, 179, 240, 20, 19, + 21, 18, 58, 219, 94, 100, 1, 101, + 125, 149, 161, 265, 167, 168, 169, 170, + 171, 176, 177, 178, 179, 240, 242, 60, + 59, 61, 18, 58, 92, 94, 100, 1, + 101, 125, 149, 161, 265, 167, 168, 169, + 170, 171, 176, 177, 178, 179, 242, 246, + 96, 95, 97, 18, 58, 98, 94, 100, + 1, 101, 125, 149, 161, 265, 167, 168, + 169, 170, 171, 176, 177, 178, 179, 246, + 250, 103, 102, 104, 18, 58, 123, 94, + 100, 1, 101, 125, 149, 161, 265, 167, + 168, 169, 170, 171, 176, 177, 178, 179, + 250, 254, 127, 126, 128, 18, 58, 147, + 94, 100, 1, 101, 125, 149, 161, 265, + 167, 168, 169, 170, 171, 176, 177, 178, + 179, 254, 258, 151, 150, 152, 18, 58, + 159, 94, 100, 1, 101, 125, 149, 161, + 265, 167, 168, 169, 170, 171, 176, 177, + 178, 179, 258, 262, 163, 162, 164, 18, + 58, 165, 94, 100, 1, 101, 125, 149, + 161, 265, 167, 168, 169, 170, 171, 176, + 177, 178, 179, 262, 266, 18, 58, 94, + 100, 1, 101, 125, 149, 161, 265, 167, + 168, 169, 170, 171, 176, 177, 178, 179, + 266 +}; + +static const char _svg_path_trans_actions[] = { + 9, 0, 51, 51, 51, 0, 1, 1, + 1, 0, 0, 0, 3, 15, 3, 15, + 0, 0, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 3, 15, 3, 15, + 0, 0, 1, 0, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 9, 51, 51, + 51, 0, 1, 1, 1, 0, 0, 0, + 3, 15, 3, 15, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 3, 15, 3, + 15, 0, 0, 1, 0, 1, 1, 0, + 0, 0, 3, 3, 0, 0, 0, 0, + 0, 7, 7, 7, 7, 0, 0, 0, + 0, 7, 48, 7, 48, 48, 0, 1, + 0, 1, 1, 0, 0, 0, 3, 15, + 3, 15, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 9, 51, 51, 51, 0, 1, 1, + 1, 0, 0, 0, 3, 15, 3, 15, + 0, 0, 1, 0, 1, 1, 0, 0, + 0, 3, 15, 3, 15, 0, 0, 1, + 0, 1, 1, 0, 0, 0, 3, 15, + 3, 15, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 3, 15, 3, 15, 0, + 0, 1, 0, 1, 1, 0, 0, 0, + 3, 15, 3, 15, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 0, 0, 9, + 51, 51, 51, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 9, 51, 51, 51, + 9, 51, 51, 51, 0, 1, 1, 1, + 0, 0, 0, 3, 15, 3, 15, 0, + 0, 1, 0, 1, 1, 0, 0, 0, + 3, 15, 3, 15, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 3, 15, 3, + 15, 0, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 0, 9, 51, 51, 51, + 0, 1, 1, 1, 0, 0, 0, 3, + 15, 3, 15, 0, 0, 1, 0, 1, + 1, 0, 0, 0, 3, 15, 3, 15, + 0, 0, 1, 0, 1, 1, 0, 0, + 0, 3, 15, 3, 15, 0, 0, 1, + 0, 1, 1, 0, 0, 0, 0, 0, + 9, 51, 51, 51, 0, 1, 1, 1, + 0, 0, 0, 3, 15, 3, 15, 0, + 0, 1, 0, 1, 1, 0, 0, 0, + 0, 0, 9, 51, 51, 51, 0, 1, + 1, 1, 0, 0, 0, 0, 0, 11, + 54, 54, 54, 11, 54, 54, 54, 11, + 54, 54, 54, 11, 54, 54, 54, 11, + 54, 54, 54, 0, 0, 11, 54, 54, + 54, 11, 54, 54, 54, 11, 54, 54, + 54, 11, 54, 54, 54, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 5, 45, 5, 45, 45, + 5, 5, 5, 5, 0, 0, 0, 0, + 0, 0, 0, 18, 57, 18, 57, 18, + 18, 0, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 21, 61, 21, + 61, 21, 21, 0, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 42, 89, 42, + 89, 42, 42, 0, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 0, 30, 73, + 30, 73, 30, 30, 0, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 0, 24, + 65, 24, 65, 24, 24, 0, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 0, + 36, 81, 36, 81, 36, 36, 0, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 0, 33, 77, 33, 77, 33, 33, 0, + 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, + 33, 0, 39, 85, 39, 85, 39, 39, + 0, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 0, 27, 69, 27, 69, 27, + 27, 0, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 0, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 0 +}; + +static const char _svg_path_eof_actions[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 18, 0, 21, 21, 42, + 0, 30, 0, 30, 30, 24, 0, 24, + 24, 36, 0, 36, 36, 33, 0, 33, + 33, 39, 0, 39, 39, 27, 0, 27, + 27, 13, 0, 18, 18, 42, 42, 21 +}; + +static const int svg_path_start = 234; +static const int svg_path_first_final = 234; + +static const int svg_path_en_main = 234; + + +#line 47 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + + +SVGPathParser::SVGPathParser(PathSink &sink) + : _absolute(false) + , _sink(sink) + , _z_snap_threshold(0) + , _curve(NULL) +{ + reset(); +} + +SVGPathParser::~SVGPathParser() +{ + delete _curve; +} + +void SVGPathParser::reset() { + _absolute = false; + _current = _initial = Point(0, 0); + _quad_tangent = _cubic_tangent = Point(0, 0); + _params.clear(); + delete _curve; + _curve = NULL; + + +#line 1113 "/home/mc/lib2geom/src/2geom/svg-path-parser.cpp" + { + cs = svg_path_start; + } + +#line 73 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + +} + +void SVGPathParser::parse(char const *str, int len) +{ + if (len < 0) { + len = std::strlen(str); + } + _parse(str, str + len, true); +} + +void SVGPathParser::parse(std::string const &s) +{ + _parse(s.c_str(), s.c_str() + s.size(), true); +} + +void SVGPathParser::feed(char const *str, int len) +{ + if (len < 0) { + len = std::strlen(str); + } + _parse(str, str + len, false); +} + +void SVGPathParser::feed(std::string const &s) +{ + _parse(s.c_str(), s.c_str() + s.size(), false); +} + +void SVGPathParser::finish() +{ + char const *empty = ""; + _parse(empty, empty, true); +} + +void SVGPathParser::_push(Coord value) +{ + _params.push_back(value); +} + +Coord SVGPathParser::_pop() +{ + Coord value = _params.back(); + _params.pop_back(); + return value; +} + +bool SVGPathParser::_pop_flag() +{ + return _pop() != 0.0; +} + +Coord SVGPathParser::_pop_coord(Dim2 axis) +{ + if (_absolute) { + return _pop(); + } else { + return _pop() + _current[axis]; + } +} + +Point SVGPathParser::_pop_point() +{ + Coord y = _pop_coord(Y); + Coord x = _pop_coord(X); + return Point(x, y); +} + +void SVGPathParser::_moveTo(Point const &p) +{ + _pushCurve(NULL); // flush + _sink.moveTo(p); + _quad_tangent = _cubic_tangent = _current = _initial = p; +} + +void SVGPathParser::_lineTo(Point const &p) +{ + _pushCurve(new LineSegment(_current, p)); + _quad_tangent = _cubic_tangent = _current = p; +} + +void SVGPathParser::_curveTo(Point const &c0, Point const &c1, Point const &p) +{ + _pushCurve(new CubicBezier(_current, c0, c1, p)); + _quad_tangent = _current = p; + _cubic_tangent = p + ( p - c1 ); +} + +void SVGPathParser::_quadTo(Point const &c, Point const &p) +{ + _pushCurve(new QuadraticBezier(_current, c, p)); + _cubic_tangent = _current = p; + _quad_tangent = p + ( p - c ); +} + +void SVGPathParser::_arcTo(Coord rx, Coord ry, Coord angle, + bool large_arc, bool sweep, Point const &p) +{ + if (_current == p) { + return; // ignore invalid (ambiguous) arc segments where start and end point are the same (per SVG spec) + } + + _pushCurve(new EllipticalArc(_current, fabs(rx), fabs(ry), angle, large_arc, sweep, p)); + _quad_tangent = _cubic_tangent = _current = p; +} + +void SVGPathParser::_closePath() +{ + if (_curve && (!_absolute || !_moveto_was_absolute) && + are_near(_initial, _current, _z_snap_threshold)) + { + _curve->setFinal(_initial); + } + + _pushCurve(NULL); // flush + _sink.closePath(); + _quad_tangent = _cubic_tangent = _current = _initial; +} + +void SVGPathParser::_pushCurve(Curve *c) +{ + if (_curve) { + _sink.feed(*_curve, false); + delete _curve; + } + _curve = c; +} + +void SVGPathParser::_parse(char const *str, char const *strend, bool finish) +{ + char const *p = str; + char const *pe = strend; + char const *eof = finish ? pe : NULL; + char const *start = NULL; + + +#line 1255 "/home/mc/lib2geom/src/2geom/svg-path-parser.cpp" + { + int _klen; + unsigned int _trans; + const char *_acts; + unsigned int _nacts; + const char *_keys; + + if ( p == pe ) + goto _test_eof; + if ( cs == 0 ) + goto _out; +_resume: + _keys = _svg_path_trans_keys + _svg_path_key_offsets[cs]; + _trans = _svg_path_index_offsets[cs]; + + _klen = _svg_path_single_lengths[cs]; + if ( _klen > 0 ) { + const char *_lower = _keys; + const char *_mid; + const char *_upper = _keys + _klen - 1; + while (1) { + if ( _upper < _lower ) + break; + + _mid = _lower + ((_upper-_lower) >> 1); + if ( (*p) < *_mid ) + _upper = _mid - 1; + else if ( (*p) > *_mid ) + _lower = _mid + 1; + else { + _trans += (unsigned int)(_mid - _keys); + goto _match; + } + } + _keys += _klen; + _trans += _klen; + } + + _klen = _svg_path_range_lengths[cs]; + if ( _klen > 0 ) { + const char *_lower = _keys; + const char *_mid; + const char *_upper = _keys + (_klen<<1) - 2; + while (1) { + if ( _upper < _lower ) + break; + + _mid = _lower + (((_upper-_lower) >> 1) & ~1); + if ( (*p) < _mid[0] ) + _upper = _mid - 2; + else if ( (*p) > _mid[1] ) + _lower = _mid + 2; + else { + _trans += (unsigned int)((_mid - _keys)>>1); + goto _match; + } + } + _trans += _klen; + } + +_match: + _trans = _svg_path_indicies[_trans]; + cs = _svg_path_trans_targs[_trans]; + + if ( _svg_path_trans_actions[_trans] == 0 ) + goto _again; + + _acts = _svg_path_actions + _svg_path_trans_actions[_trans]; + _nacts = (unsigned int) *_acts++; + while ( _nacts-- > 0 ) + { + switch ( *_acts++ ) + { + case 0: +#line 209 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + start = p; + } + break; + case 1: +#line 213 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + if (start) { + std::string buf(start, p); + _push(g_ascii_strtod(buf.c_str(), NULL)); + start = NULL; + } else { + std::string buf(str, p); + _push(g_ascii_strtod((_number_part + buf).c_str(), NULL)); + _number_part.clear(); + } + } + break; + case 2: +#line 225 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _push(1.0); + } + break; + case 3: +#line 229 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _push(0.0); + } + break; + case 4: +#line 233 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _absolute = true; + } + break; + case 5: +#line 237 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _absolute = false; + } + break; + case 6: +#line 241 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _moveto_was_absolute = _absolute; + _moveTo(_pop_point()); + } + break; + case 7: +#line 246 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(_pop_point()); + } + break; + case 8: +#line 250 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(Point(_pop_coord(X), _current[Y])); + } + break; + case 9: +#line 254 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(Point(_current[X], _pop_coord(Y))); + } + break; + case 10: +#line 258 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + Point c0 = _pop_point(); + _curveTo(c0, c1, p); + } + break; + case 11: +#line 265 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + _curveTo(_cubic_tangent, c1, p); + } + break; + case 12: +#line 271 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c = _pop_point(); + _quadTo(c, p); + } + break; + case 13: +#line 277 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + _quadTo(_quad_tangent, p); + } + break; + case 14: +#line 282 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point point = _pop_point(); + bool sweep = _pop_flag(); + bool large_arc = _pop_flag(); + double angle = rad_from_deg(_pop()); + double ry = _pop(); + double rx = _pop(); + + _arcTo(rx, ry, angle, large_arc, sweep, point); + } + break; + case 15: +#line 293 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _closePath(); + } + break; +#line 1449 "/home/mc/lib2geom/src/2geom/svg-path-parser.cpp" + } + } + +_again: + if ( cs == 0 ) + goto _out; + if ( ++p != pe ) + goto _resume; + _test_eof: {} + if ( p == eof ) + { + const char *__acts = _svg_path_actions + _svg_path_eof_actions[cs]; + unsigned int __nacts = (unsigned int) *__acts++; + while ( __nacts-- > 0 ) { + switch ( *__acts++ ) { + case 1: +#line 213 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + if (start) { + std::string buf(start, p); + _push(g_ascii_strtod(buf.c_str(), NULL)); + start = NULL; + } else { + std::string buf(str, p); + _push(g_ascii_strtod((_number_part + buf).c_str(), NULL)); + _number_part.clear(); + } + } + break; + case 6: +#line 241 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _moveto_was_absolute = _absolute; + _moveTo(_pop_point()); + } + break; + case 7: +#line 246 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(_pop_point()); + } + break; + case 8: +#line 250 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(Point(_pop_coord(X), _current[Y])); + } + break; + case 9: +#line 254 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(Point(_current[X], _pop_coord(Y))); + } + break; + case 10: +#line 258 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + Point c0 = _pop_point(); + _curveTo(c0, c1, p); + } + break; + case 11: +#line 265 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + _curveTo(_cubic_tangent, c1, p); + } + break; + case 12: +#line 271 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c = _pop_point(); + _quadTo(c, p); + } + break; + case 13: +#line 277 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + _quadTo(_quad_tangent, p); + } + break; + case 14: +#line 282 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + Point point = _pop_point(); + bool sweep = _pop_flag(); + bool large_arc = _pop_flag(); + double angle = rad_from_deg(_pop()); + double ry = _pop(); + double rx = _pop(); + + _arcTo(rx, ry, angle, large_arc, sweep, point); + } + break; + case 15: +#line 293 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + { + _closePath(); + } + break; +#line 1555 "/home/mc/lib2geom/src/2geom/svg-path-parser.cpp" + } + } + } + + _out: {} + } + +#line 435 "/home/mc/lib2geom/src/2geom/svg-path-parser.rl" + + + if (finish) { + if (cs < svg_path_first_final) { + throw SVGPathParseError(); + } + } else if (start != NULL) { + _number_part = std::string(start, pe); + } + + if (finish) { + _pushCurve(NULL); + _sink.flush(); + reset(); + } +} + +void parse_svg_path(char const *str, PathSink &sink) +{ + SVGPathParser parser(sink); + parser.parse(str); +} + +void parse_svg_path_file(FILE *fi, PathSink &sink) +{ + static const size_t BUFFER_SIZE = 4096; + char buffer[BUFFER_SIZE]; + size_t bytes_read; + SVGPathParser parser(sink); + + while (true) { + bytes_read = fread(buffer, 1, BUFFER_SIZE, fi); + if (bytes_read < BUFFER_SIZE) { + parser.parse(buffer, bytes_read); + break; + } else { + parser.feed(buffer, bytes_read); + } + } +} + +} // namespace Geom + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/svg-path-parser.h b/src/2geom/svg-path-parser.h new file mode 100644 index 0000000..e25316c --- /dev/null +++ b/src/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/src/2geom/svg-path-writer.cpp b/src/2geom/svg-path-writer.cpp new file mode 100644 index 0000000..c484a17 --- /dev/null +++ b/src/2geom/svg-path-writer.cpp @@ -0,0 +1,296 @@ +/** @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. + */ + +#include <cmath> +#include <iomanip> +#include <2geom/coord.h> +#include <2geom/svg-path-writer.h> +#include <glib.h> + +namespace Geom { + +static inline bool is_digit(char c) { + return c >= '0' && c <= '9'; +} + +SVGPathWriter::SVGPathWriter() + : _epsilon(0) + , _precision(-1) + , _optimize(false) + , _use_shorthands(true) + , _command(0) +{ + // always use C locale for number formatting + _ns.imbue(std::locale::classic()); + _ns.unsetf(std::ios::floatfield); +} + +void SVGPathWriter::moveTo(Point const &p) +{ + _setCommand('M'); + _current_pars.push_back(p[X]); + _current_pars.push_back(p[Y]); + + _current = _subpath_start = _quad_tangent = _cubic_tangent = p; + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::lineTo(Point const &p) +{ + // The weird setting of _current is to avoid drift with many almost-aligned segments + // The additional conditions ensure that the smaller dimension is rounded to zero + bool written = false; + if (_use_shorthands) { + Point r = _current - p; + if (are_near(p[X], _current[X], _epsilon) && std::abs(r[X]) < std::abs(r[Y])) { + // emit vlineto + _setCommand('V'); + _current_pars.push_back(p[Y]); + _current[Y] = p[Y]; + written = true; + } else if (are_near(p[Y], _current[Y], _epsilon) && std::abs(r[Y]) < std::abs(r[X])) { + // emit hlineto + _setCommand('H'); + _current_pars.push_back(p[X]); + _current[X] = p[X]; + written = true; + } + } + + if (!written) { + // emit normal lineto + if (_command != 'M' && _command != 'L') { + _setCommand('L'); + } + _current_pars.push_back(p[X]); + _current_pars.push_back(p[Y]); + _current = p; + } + + _cubic_tangent = _quad_tangent = _current; + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::quadTo(Point const &c, Point const &p) +{ + bool shorthand = _use_shorthands && are_near(c, _quad_tangent, _epsilon); + + _setCommand(shorthand ? 'T' : 'Q'); + if (!shorthand) { + _current_pars.push_back(c[X]); + _current_pars.push_back(c[Y]); + } + _current_pars.push_back(p[X]); + _current_pars.push_back(p[Y]); + + _current = _cubic_tangent = p; + _quad_tangent = p + (p - c); + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::curveTo(Point const &p1, Point const &p2, Point const &p3) +{ + bool shorthand = _use_shorthands && are_near(p1, _cubic_tangent, _epsilon); + + _setCommand(shorthand ? 'S' : 'C'); + if (!shorthand) { + _current_pars.push_back(p1[X]); + _current_pars.push_back(p1[Y]); + } + _current_pars.push_back(p2[X]); + _current_pars.push_back(p2[Y]); + _current_pars.push_back(p3[X]); + _current_pars.push_back(p3[Y]); + + _current = _quad_tangent = p3; + _cubic_tangent = p3 + (p3 - p2); + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p) +{ + _setCommand('A'); + _current_pars.push_back(rx); + _current_pars.push_back(ry); + _current_pars.push_back(deg_from_rad(angle)); + _current_pars.push_back(large_arc ? 1. : 0.); + _current_pars.push_back(sweep ? 1. : 0.); + _current_pars.push_back(p[X]); + _current_pars.push_back(p[Y]); + + _current = _quad_tangent = _cubic_tangent = p; + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::closePath() +{ + flush(); + if (_optimize) { + _s << "z"; + } else { + _s << " z"; + } + _current = _quad_tangent = _cubic_tangent = _subpath_start; +} + +void SVGPathWriter::flush() +{ + if (_command == 0 || _current_pars.empty()) return; + + if (_optimize) { + _s << _command; + } else { + if (_s.tellp() != 0) { + _s << ' '; + } + _s << _command; + } + + char lastchar = _command; + bool contained_dot = false; + + for (unsigned i = 0; i < _current_pars.size(); ++i) { + // TODO: optimize the use of absolute / relative coords + std::string cs = _formatCoord(_current_pars[i]); + + // Separator handling logic. + // Floating point values can end with a digit or dot + // and start with a digit, a plus or minus sign, or a dot. + // The following cases require a separator: + // * digit-digit + // * digit-dot (only if the previous number didn't contain a dot) + // * dot-digit + if (_optimize) { + // C++11: change to front() + char firstchar = cs[0]; + if (is_digit(lastchar)) { + if (is_digit(firstchar)) { + _s << " "; + } else if (firstchar == '.' && !contained_dot) { + _s << " "; + } + } else if (lastchar == '.' && is_digit(firstchar)) { + _s << " "; + } + _s << cs; + + // C++11: change to back() + lastchar = cs[cs.length()-1]; + contained_dot = cs.find('.') != std::string::npos; + } else { + _s << " " << cs; + } + } + _current_pars.clear(); + _command = 0; +} + +void SVGPathWriter::clear() +{ + _s.clear(); + _s.str(""); + _ns.clear(); + _ns.str(""); + _command = 0; + _current_pars.clear(); + _current = Point(0,0); + _subpath_start = Point(0,0); +} + +void SVGPathWriter::setPrecision(int prec) +{ + _precision = prec; + if (prec < 0) { + _epsilon = 0; + } else { + _epsilon = std::pow(10., -prec); + _ns << std::setprecision(_precision); + } +} + +void SVGPathWriter::_setCommand(char cmd) +{ + if (_command != 0 && _command != cmd) { + flush(); + } + _command = cmd; +} + +std::string SVGPathWriter::_formatCoord(Coord par) +{ + std::string ret; + if (_precision < 0) { + ret = format_coord_shortest(par); + } else { + _ns << par; + ret = _ns.str(); + _ns.clear(); + _ns.str(""); + } + return ret; +} + + +std::string write_svg_path(PathVector const &pv, int prec, bool optimize, bool shorthands) +{ + SVGPathWriter writer; + writer.setPrecision(prec); + writer.setOptimize(optimize); + writer.setUseShorthands(shorthands); + + writer.feed(pv); + return writer.str(); +} + +} // namespace Geom + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/svg-path-writer.h b/src/2geom/svg-path-writer.h new file mode 100644 index 0000000..e639541 --- /dev/null +++ b/src/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() {} + + void moveTo(Point const &p); + void lineTo(Point const &p); + void quadTo(Point const &c, Point const &p); + void curveTo(Point const &c0, Point const &c1, Point const &p); + void arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p); + void closePath(); + void flush(); + + /// 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:encoding=utf-8:textwidth=99 : diff --git a/src/2geom/sweep-bounds.cpp b/src/2geom/sweep-bounds.cpp new file mode 100644 index 0000000..48f168b --- /dev/null +++ b/src/2geom/sweep-bounds.cpp @@ -0,0 +1,157 @@ +#include <2geom/sweep-bounds.h> + +#include <algorithm> + +namespace Geom { + +struct Event { + double x; + unsigned ix; + bool closing; + Event(double pos, unsigned i, bool c) : x(pos), ix(i), closing(c) {} +// Lexicographic ordering by x then closing + bool operator<(Event const &other) const { + if(x < other.x) return true; + if(x > other.x) return false; + return closing < other.closing; + } + bool operator==(Event const &other) const { + return other.x == x && other.ix == ix && other.closing == closing; + } +}; + +std::vector<std::vector<unsigned> > fake_cull(unsigned a, unsigned b); + +/** + * \brief Make a list of pairs of self intersections in a list of Rects. + * + * \param rs: vector of Rect. + * \param d: dimension to sweep along + * + * [(A = rs[i], B = rs[j]) for i,J in enumerate(pairs) for j in J] + * then A.left <= B.left + */ + +std::vector<std::vector<unsigned> > sweep_bounds(std::vector<Rect> rs, Dim2 d) { + std::vector<Event> events; events.reserve(rs.size()*2); + std::vector<std::vector<unsigned> > pairs(rs.size()); + + for(unsigned i = 0; i < rs.size(); i++) { + events.push_back(Event(rs[i][d].min(), i, false)); + events.push_back(Event(rs[i][d].max(), i, true)); + } + std::sort(events.begin(), events.end()); + + std::vector<unsigned> open; + for(unsigned i = 0; i < events.size(); i++) { + unsigned ix = events[i].ix; + if(events[i].closing) { + std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix); + //if(iter != open.end()) + open.erase(iter); + } else { + for(unsigned j = 0; j < open.size(); j++) { + unsigned jx = open[j]; + if(rs[jx][1-d].intersects(rs[ix][1-d])) { + pairs[jx].push_back(ix); + } + } + open.push_back(ix); + } + } + return pairs; +} + +/** + * \brief Make a list of pairs of red-blue intersections between two lists of Rects. + * + * \param a: vector of Rect. + * \param b: vector of Rect. + * \param d: dimension to scan along + * + * [(A = rs[i], B = rs[j]) for i,J in enumerate(pairs) for j in J] + * then A.left <= B.left, A in a, B in b + */ +std::vector<std::vector<unsigned> > sweep_bounds(std::vector<Rect> a, std::vector<Rect> b, Dim2 d) { + std::vector<std::vector<unsigned> > pairs(a.size()); + if(a.empty() || b.empty()) return pairs; + std::vector<Event> events[2]; + events[0].reserve(a.size()*2); + events[1].reserve(b.size()*2); + + for(unsigned n = 0; n < 2; n++) { + unsigned sz = n ? b.size() : a.size(); + events[n].reserve(sz*2); + for(unsigned i = 0; i < sz; i++) { + Rect r = n ? b[i] : a[i]; + events[n].push_back(Event(r[d].min(), i, false)); + events[n].push_back(Event(r[d].max(), i, true)); + } + std::sort(events[n].begin(), events[n].end()); + } + + std::vector<unsigned> open[2]; + bool n = events[1].front() < events[0].front(); + {// As elegant as putting the initialiser in the for was, it upsets some legacy compilers (MS VS C++) + unsigned i[] = {0,0}; + for(; i[n] < events[n].size();) { + unsigned ix = events[n][i[n]].ix; + bool closing = events[n][i[n]].closing; + //std::cout << n << "[" << ix << "] - " << (closing ? "closer" : "opener") << "\n"; + if(closing) { + open[n].erase(std::find(open[n].begin(), open[n].end(), ix)); + } else { + if(n) { + //n = 1 + //opening a B, add to all open a + for(unsigned j = 0; j < open[0].size(); j++) { + unsigned jx = open[0][j]; + if(a[jx][1-d].intersects(b[ix][1-d])) { + pairs[jx].push_back(ix); + } + } + } else { + //n = 0 + //opening an A, add all open b + for(unsigned j = 0; j < open[1].size(); j++) { + unsigned jx = open[1][j]; + if(b[jx][1-d].intersects(a[ix][1-d])) { + pairs[ix].push_back(jx); + } + } + } + open[n].push_back(ix); + } + i[n]++; + if(i[n]>=events[n].size()) {break;} + n = (events[!n][i[!n]] < events[n][i[n]]) ? !n : n; + }} + return pairs; +} + +//Fake cull, until the switch to the real sweep is made. +std::vector<std::vector<unsigned> > fake_cull(unsigned a, unsigned b) { + std::vector<std::vector<unsigned> > ret; + + std::vector<unsigned> all; + for(unsigned j = 0; j < b; j++) + all.push_back(j); + + for(unsigned i = 0; i < a; i++) + ret.push_back(all); + + return ret; +} + +} + +/* + 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/src/2geom/sweep-bounds.h b/src/2geom/sweep-bounds.h new file mode 100644 index 0000000..e0ebf29 --- /dev/null +++ b/src/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/src/2geom/sweeper.h b/src/2geom/sweeper.h new file mode 100644 index 0000000..3532df2 --- /dev/null +++ b/src/2geom/sweeper.h @@ -0,0 +1,190 @@ +/** @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> +#include <boost/range/algorithm/heap_algorithm.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)); + } + + boost::make_heap(_entry_events); + boost::make_heap(_exit_events); + + 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; + } + boost::pop_heap(heap); + 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/src/2geom/transforms.cpp b/src/2geom/transforms.cpp new file mode 100644 index 0000000..41d3952 --- /dev/null +++ b/src/2geom/transforms.cpp @@ -0,0 +1,205 @@ +/** + * @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. + */ + +#include <boost/concept_check.hpp> +#include <2geom/point.h> +#include <2geom/transforms.h> +#include <2geom/rect.h> + +namespace Geom { + +/** @brief Zoom between rectangles. + * Given two rectangles, compute a zoom that maps one to the other. + * Rectangles are assumed to have the same aspect ratio. */ +Zoom Zoom::map_rect(Rect const &old_r, Rect const &new_r) +{ + Zoom ret; + ret._scale = new_r.width() / old_r.width(); + ret._trans = new_r.min() - old_r.min(); + return ret; +} + +// Point transformation methods. +Point &Point::operator*=(Translate const &t) +{ + _pt[X] += t.vec[X]; + _pt[Y] += t.vec[Y]; + return *this; +} +Point &Point::operator*=(Scale const &s) +{ + _pt[X] *= s.vec[X]; + _pt[Y] *= s.vec[Y]; + return *this; +} +Point &Point::operator*=(Rotate const &r) +{ + double x = _pt[X], y = _pt[Y]; + _pt[X] = x * r.vec[X] - y * r.vec[Y]; + _pt[Y] = y * r.vec[X] + x * r.vec[Y]; + return *this; +} +Point &Point::operator*=(HShear const &h) +{ + _pt[X] += h.f * _pt[X]; + return *this; +} +Point &Point::operator*=(VShear const &v) +{ + _pt[Y] += v.f * _pt[Y]; + return *this; +} +Point &Point::operator*=(Zoom const &z) +{ + _pt[X] += z._trans[X]; + _pt[Y] += z._trans[Y]; + _pt[X] *= z._scale; + _pt[Y] *= z._scale; + return *this; +} + +// Affine multiplication methods. + +/** @brief Combine this transformation with a translation. */ +Affine &Affine::operator*=(Translate const &t) { + _c[4] += t[X]; + _c[5] += t[Y]; + return *this; +} + +/** @brief Combine this transformation with scaling. */ +Affine &Affine::operator*=(Scale const &s) { + _c[0] *= s[X]; _c[1] *= s[Y]; + _c[2] *= s[X]; _c[3] *= s[Y]; + _c[4] *= s[X]; _c[5] *= s[Y]; + return *this; +} + +/** @brief Combine this transformation a rotation. */ +Affine &Affine::operator*=(Rotate const &r) { + // TODO: we just convert the Rotate to an Affine and use the existing operator*=() + // is there a better way? + *this *= (Affine) r; + return *this; +} + +/** @brief Combine this transformation with horizontal shearing (skew). */ +Affine &Affine::operator*=(HShear const &h) { + _c[0] += h.f * _c[1]; + _c[2] += h.f * _c[3]; + _c[4] += h.f * _c[5]; + return *this; +} + +/** @brief Combine this transformation with vertical shearing (skew). */ +Affine &Affine::operator*=(VShear const &v) { + _c[1] += v.f * _c[0]; + _c[3] += v.f * _c[2]; + _c[5] += v.f * _c[4]; + return *this; +} + +Affine &Affine::operator*=(Zoom const &z) { + _c[0] *= z._scale; _c[1] *= z._scale; + _c[2] *= z._scale; _c[3] *= z._scale; + _c[4] += z._trans[X]; _c[5] += z._trans[Y]; + _c[4] *= z._scale; _c[5] *= z._scale; + return *this; +} + +Affine Rotate::around(Point const &p, Coord angle) +{ + Affine result = Translate(-p) * Rotate(angle) * Translate(p); + return result; +} + +Affine reflection(Point const & vector, Point const & origin) +{ + Geom::Point vn = unit_vector(vector); + Coord cx2 = vn[X] * vn[X]; + Coord cy2 = vn[Y] * vn[Y]; + Coord c2xy = 2 * vn[X] * vn[Y]; + Affine mirror ( cx2 - cy2, c2xy, + c2xy, cy2 - cx2, + 0, 0 ); + return Translate(-origin) * mirror * Translate(origin); +} + +// this checks whether the requirements of TransformConcept are satisfied for all transforms. +// if you add a new transform type, include it here! +void check_transforms() +{ +#ifdef BOOST_CONCEPT_ASSERT + BOOST_CONCEPT_ASSERT((TransformConcept<Translate>)); + BOOST_CONCEPT_ASSERT((TransformConcept<Scale>)); + BOOST_CONCEPT_ASSERT((TransformConcept<Rotate>)); + BOOST_CONCEPT_ASSERT((TransformConcept<HShear>)); + BOOST_CONCEPT_ASSERT((TransformConcept<VShear>)); + BOOST_CONCEPT_ASSERT((TransformConcept<Zoom>)); + BOOST_CONCEPT_ASSERT((TransformConcept<Affine>)); // Affine is also a transform +#endif + + // check inter-transform multiplication + Affine m; + Translate t(Translate::identity()); + Scale s(Scale::identity()); + Rotate r(Rotate::identity()); + HShear h(HShear::identity()); + VShear v(VShear::identity()); + Zoom z(Zoom::identity()); + + // notice that the first column is always the same and enumerates all transform types, + // while the second one changes to each transform type in turn. + // cppcheck-suppress redundantAssignment + m = t * t; m = t * s; m = t * r; m = t * h; m = t * v; m = t * z; + m = s * t; m = s * s; m = s * r; m = s * h; m = s * v; m = s * z; + m = r * t; m = r * s; m = r * r; m = r * h; m = r * v; m = r * z; + m = h * t; m = h * s; m = h * r; m = h * h; m = h * v; m = h * z; + m = v * t; m = v * s; m = v * r; m = v * h; m = v * v; m = v * z; + m = z * t; m = z * s; m = z * r; m = z * h; m = z * v; m = z * z; +} + +} // namespace Geom + +/* + 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/src/2geom/transforms.h b/src/2geom/transforms.h new file mode 100644 index 0000000..cc55e29 --- /dev/null +++ b/src/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/src/2geom/utils.cpp b/src/2geom/utils.cpp new file mode 100644 index 0000000..83d93cc --- /dev/null +++ b/src/2geom/utils.cpp @@ -0,0 +1,86 @@ +/** Various utility functions. + * + * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com> + * 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. + * + */ + + +#include <2geom/utils.h> + + +namespace Geom +{ + +// return a vector that contains all the binomial coefficients of degree n +void binomial_coefficients(std::vector<size_t>& bc, std::size_t n) +{ + size_t s = n+1; + bc.clear(); + bc.resize(s); + bc[0] = 1; + for (size_t i = 1; i < n; ++i) + { + size_t k = i >> 1; + if (i & 1u) + { + bc[k+1] = bc[k] << 1; + } + for (size_t j = k; j > 0; --j) + { + bc[j] += bc[j-1]; + } + } + s >>= 1; + for (size_t i = 0; i < s; ++i) + { + bc[n-i] = bc[i]; + } +} + +} // end namespace Geom + + + + + + + + + + + +/* + 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/src/2geom/utils.h b/src/2geom/utils.h new file mode 100644 index 0000000..01579d8 --- /dev/null +++ b/src/2geom/utils.h @@ -0,0 +1,111 @@ +/** + * \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 { + +// proper logical xor +inline bool logical_xor (bool a, bool b) { return (a || b) && !(a && b); } + +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 : |