diff options
Diffstat (limited to 'src')
292 files changed, 80748 insertions, 0 deletions
diff --git a/src/2geom/CMakeLists.txt b/src/2geom/CMakeLists.txt new file mode 100755 index 0000000..80b9e8e --- /dev/null +++ b/src/2geom/CMakeLists.txt @@ -0,0 +1,206 @@ +# (re-)generate parser file with ragel if it's available +SET(SVG_PARSER_CPP "svg-path-parser.cpp") +SET(SVG_PARSER_RL "svg-path-parser.rl") +find_program(RAGEL_PROGRAM + NAMES ragel + HINTS /usr/bin + /usr/local/bin +) +if(RAGEL_PROGRAM) + message(STATUS "Found Ragel in ${RAGEL_PROGRAM}. ${SVG_PARSER_CPP} will be recreated from ${SVG_PARSER_RL}.") + add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/${SVG_PARSER_CPP}" + COMMAND ${RAGEL_PROGRAM} -o "${SVG_PARSER_CPP}" "${SVG_PARSER_RL}" + DEPENDS "${SVG_PARSER_RL}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating ${SVG_PARSER_CPP} with ragel") +else() + message(STATUS "Ragel NOT found. Using stale ${SVG_PARSER_CPP}.") +endif() + + +add_library(2geom ${LIB_TYPE} + # sources + affine.cpp + + basic-intersection.cpp + bezier.cpp + bezier-clipping.cpp + bezier-curve.cpp + bezier-utils.cpp + + cairo-path-sink.cpp + circle.cpp + concepts.cpp + conicsec.cpp + conic_section_clipper_impl.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 + + parallelogram.cpp + parting-point.cpp + path-extrema.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 + self-intersect.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 + + # headers (for IDE support only) + # private: + planar-graph.h + + # public: + ${2GEOM_INCLUDE_DIR}/2geom/affine.h + ${2GEOM_INCLUDE_DIR}/2geom/angle.h + + ${2GEOM_INCLUDE_DIR}/2geom/basic-intersection.h + ${2GEOM_INCLUDE_DIR}/2geom/bezier.h + ${2GEOM_INCLUDE_DIR}/2geom/bezier-curve.h + ${2GEOM_INCLUDE_DIR}/2geom/bezier-to-sbasis.h + ${2GEOM_INCLUDE_DIR}/2geom/bezier-utils.h + + ${2GEOM_INCLUDE_DIR}/2geom/cairo-path-sink.h + ${2GEOM_INCLUDE_DIR}/2geom/choose.h + ${2GEOM_INCLUDE_DIR}/2geom/circle.h + ${2GEOM_INCLUDE_DIR}/2geom/concepts.h + ${2GEOM_INCLUDE_DIR}/2geom/conicsec.h + ${2GEOM_INCLUDE_DIR}/2geom/conic_section_clipper.h + ${2GEOM_INCLUDE_DIR}/2geom/conic_section_clipper_cr.h + ${2GEOM_INCLUDE_DIR}/2geom/conic_section_clipper_impl.h + ${2GEOM_INCLUDE_DIR}/2geom/convex-hull.h + ${2GEOM_INCLUDE_DIR}/2geom/coord.h + ${2GEOM_INCLUDE_DIR}/2geom/crossing.h + ${2GEOM_INCLUDE_DIR}/2geom/curve.h + ${2GEOM_INCLUDE_DIR}/2geom/curves.h + + ${2GEOM_INCLUDE_DIR}/2geom/d2.h + + ${2GEOM_INCLUDE_DIR}/2geom/ellipse.h + ${2GEOM_INCLUDE_DIR}/2geom/elliptical-arc.h + ${2GEOM_INCLUDE_DIR}/2geom/exception.h + + ${2GEOM_INCLUDE_DIR}/2geom/forward.h + + ${2GEOM_INCLUDE_DIR}/2geom/geom.h + + ${2GEOM_INCLUDE_DIR}/2geom/intersection.h + ${2GEOM_INCLUDE_DIR}/2geom/intersection-graph.h + + ${2GEOM_INCLUDE_DIR}/2geom/line.h + ${2GEOM_INCLUDE_DIR}/2geom/linear.h + + ${2GEOM_INCLUDE_DIR}/2geom/math-utils.h + + ${2GEOM_INCLUDE_DIR}/2geom/nearest-time.h + + ${2GEOM_INCLUDE_DIR}/2geom/ord.h + + ${2GEOM_INCLUDE_DIR}/2geom/parallelogram.h + ${2GEOM_INCLUDE_DIR}/2geom/path-intersection.h + ${2GEOM_INCLUDE_DIR}/2geom/path-sink.h + ${2GEOM_INCLUDE_DIR}/2geom/path.h + ${2GEOM_INCLUDE_DIR}/2geom/pathvector.h + ${2GEOM_INCLUDE_DIR}/2geom/piecewise.h + ${2GEOM_INCLUDE_DIR}/2geom/point.h + ${2GEOM_INCLUDE_DIR}/2geom/polynomial.h + + ${2GEOM_INCLUDE_DIR}/2geom/ray.h + ${2GEOM_INCLUDE_DIR}/2geom/rect.h + + ${2GEOM_INCLUDE_DIR}/2geom/sbasis-2d.h + ${2GEOM_INCLUDE_DIR}/2geom/sbasis-curve.h + ${2GEOM_INCLUDE_DIR}/2geom/sbasis-geometric.h + ${2GEOM_INCLUDE_DIR}/2geom/sbasis-math.h + ${2GEOM_INCLUDE_DIR}/2geom/sbasis-poly.h + ${2GEOM_INCLUDE_DIR}/2geom/sbasis-to-bezier.h + ${2GEOM_INCLUDE_DIR}/2geom/sbasis.h + ${2GEOM_INCLUDE_DIR}/2geom/solver.h + ${2GEOM_INCLUDE_DIR}/2geom/svg-path-parser.h + ${2GEOM_INCLUDE_DIR}/2geom/svg-path-writer.h + ${2GEOM_INCLUDE_DIR}/2geom/sweeper.h + ${2GEOM_INCLUDE_DIR}/2geom/sweep-bounds.h + + ${2GEOM_INCLUDE_DIR}/2geom/transforms.h + + ${2GEOM_INCLUDE_DIR}/2geom/utils.h +) + +# make lib for 2geom +target_include_directories(2geom + PUBLIC + ${GLIB_INCLUDE_DIRS} + ${GSL_INCLUDE_DIRS} + ${CAIRO_INCLUDE_DIRS} + ${DoubleConversion_INCLUDE_DIRS} + $<BUILD_INTERFACE:${2GEOM_INCLUDE_DIR}> + $<BUILD_INTERFACE:${2GEOM_INCLUDE_DIR}/2geom> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/2geom-${2GEOM_VERSION}> + ) + +target_link_libraries(2geom + PUBLIC + ${GLIB_LIBRARIES} + ${GSL_LIBRARIES} + ${CAIRO_LIBRARIES} + ${DoubleConversion_LIBRARIES} + ) + +set_target_properties(2geom PROPERTIES SOVERSION "${2GEOM_ABI_VERSION}") + +install(TARGETS 2geom + EXPORT 2geom_targets + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT "lib2geom${2GEOM_VERSION}" + LIBRARY + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT "lib2geom${2GEOM_VERSION}" + NAMELINK_COMPONENT "lib2geom_dev" + ARCHIVE + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT "lib2geom${2GEOM_VERSION}" +) + +add_library(2Geom::2geom ALIAS 2geom) 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/basic-intersection.cpp b/src/2geom/basic-intersection.cpp new file mode 100644 index 0000000..61d7a6d --- /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(auto & k : section) { + double l = k.first; + double r = 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.emplace_back((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 (const auto & x : xs) { + av.push_back(portion(a, prev.first, x.first)); + bv.push_back(portion(b, prev.second, x.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 = x; + } + 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(auto & x : xs) + intersect_polish_root(A, x.first, + B, x.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 (auto & x : xs) + { + Point At = A(x.first); + Point Bu = B(x.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 = x.first; + h_b_t = x.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/bezier-clipping.cpp b/src/2geom/bezier-clipping.cpp new file mode 100644 index 0000000..27da3d2 --- /dev/null +++ b/src/2geom/bezier-clipping.cpp @@ -0,0 +1,1174 @@ +/* + * 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 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 (auto & i : N) + { + i = rot90(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 (auto i : c) { + bound.expandTo(signed_distance(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.emplace_back(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()); + int rci = 1; + int b1 = 1; + 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 = (double)n / rci; + + // assert(rci == binomial(r, i)); + binomial_increment_k(rci, r, i); + + int b2 = b1; + 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 * b2; + + // assert(b2 == binomial(n, l) * binomial(n - 1, k)); + binomial_decrement_k(b2, n, l); + binomial_increment_k(b2, 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)); + } + } + + // assert(b1 == binomial(n, i - k0) * binomial(n - 1, k0)); + if (i < n) { + binomial_increment_k(b1, n, i); + } else { + binomial_increment_k(b1, n - 1, k0); + } + + 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..ca0f787 --- /dev/null +++ b/src/2geom/bezier-curve.cpp @@ -0,0 +1,695 @@ +/* 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> +#include <2geom/polynomial.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; +} + +/** Return false if there are at least 3 distinct control points, true otherwise. */ +bool BezierCurve::isLineSegment() const +{ + auto const last_idx = size() - 1; + if (last_idx == 1) { + return true; + } + auto const start = controlPoint(0); + auto const end = controlPoint(last_idx); + for (unsigned i = 1; i < last_idx; ++i) { + auto const pi = controlPoint(i); + if (pi != start && pi != end) { + return false; + } + } + return true; +} + +void BezierCurve::expandToTransformed(Rect &bbox, Affine const &transform) const +{ + bbox |= bounds_exact(inner * transform); +} + +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 (auto & i : xs) { + CurveIntersection x(*this, other, i.first, 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 { + // Must equalize the degrees before comparing + BezierCurve elevated_this, elevated_other; + for (size_t dim = 0; dim < 2; dim++) { + unsigned const our_degree = inner[dim].degree(); + unsigned const other_degree = other->inner[dim].degree(); + + if (our_degree < other_degree) { + // Elevate our degree + elevated_this.inner[dim] = inner[dim].elevate_to_degree(other_degree); + elevated_other.inner[dim] = other->inner[dim]; + } else if (our_degree > other_degree) { + // Elevate the other's degree + elevated_this.inner[dim] = inner[dim]; + elevated_other.inner[dim] = other->inner[dim].elevate_to_degree(our_degree); + } else { + // Equal degrees: just copy + elevated_this.inner[dim] = inner[dim]; + elevated_other.inner[dim] = other->inner[dim]; + } + } + assert(elevated_other.size() == elevated_this.size()); + return elevated_this.isNear(elevated_other, precision); + } +} + +Curve *BezierCurve::portion(Coord f, Coord t) const +{ + if (f == 0.0 && t == 1.0) { + return duplicate(); + } + if (f == 1.0 && t == 0.0) { + return reverse(); + } + return new BezierCurve(Geom::portion(inner, f, t)); +} + +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); +} + +/* Specialized intersection routine for line segments. */ +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; +} + +/** @brief Find intersections of a low-degree Bézier curve with a line segment. + * + * Uses algebraic solutions to low-degree polynomial equations which may be faster + * and more precise than iterative methods. + * + * @tparam degree The degree of the Bézier curve; must be 2 or 3. + * @param curve A Bézier curve of the given degree. + * @param line A line (but really a segment). + * @return Intersections between the passed curve and the fundamental segment of the line + * (the segment where the time parameter lies in the unit interval). + */ +template <unsigned degree> +static std::vector<CurveIntersection> bezier_line_intersections(BezierCurveN<degree> const &curve, Line const &line) +{ + static_assert(degree == 2 || degree == 3, "bezier_line_intersections<degree>() error: degree must be 2 or 3."); + + auto const length = distance(line.initialPoint(), line.finalPoint()); + if (length == 0) { + return {}; + } + std::vector<CurveIntersection> result; + + // Find the isometry mapping the line to the x-axis, taking the initial point to the origin + // and the final point to (length, 0). Apply this transformation to the Bézier curve and + // extract the y-coordinate polynomial. + auto const transform = line.rotationToZero(Y); + Bezier const y = (curve.fragment() * transform)[Y]; + std::vector<double> roots; + + // Find roots of the polynomial y. + { + double const c2 = y[0] + y[2] - 2.0 * y[1]; + double const c1 = y[1] - y[0]; + double const c0 = y[0]; + + if constexpr (degree == 2) { + roots = solve_quadratic(c2, 2.0 * c1, c0); + } else if constexpr (degree == 3) { + double const c3 = y[3] - y[0] + 3.0 * (y[1] - y[2]); + roots = solve_cubic(c3, 3.0 * c2, 3.0 * c1 , c0); + } + } + + // Filter the roots and assemble intersections. + for (double root : roots) { + if (root < 0.0 || root > 1.0) { + continue; + } + Coord x = (curve.pointAt(root) * transform)[X]; + if (x < 0.0 || x > length) { + continue; + } + result.emplace_back(curve, line, root, x / length); + } + return result; +} + +/* Specialized intersection routine for quadratic Bézier curves. */ +template <> +std::vector<CurveIntersection> BezierCurveN<2>::intersect(Curve const &other, Coord eps) const +{ + if (auto other_bezier = dynamic_cast<BezierCurve const *>(&other)) { + auto const other_degree = other_bezier->order(); + if (other_degree == 1) { + // Use the exact method to intersect a quadratic Bézier with a line segment. + auto line = Line(other_bezier->initialPoint(), other_bezier->finalPoint()); + return bezier_line_intersections<2>(*this, line); + } + // TODO: implement exact intersection of two quadratic Béziers using the method of resultants. + } + return BezierCurve::intersect(other, eps); +} + +/* Specialized intersection routine for cubic Bézier curves. */ +template <> +std::vector<CurveIntersection> BezierCurveN<3>::intersect(Curve const &other, Coord eps) const +{ + if (auto other_bezier = dynamic_cast<BezierCurve const *>(&other)) { + if (other_bezier->order() == 1) { + // Use the exact method to intersect a cubic Bézier with a line segment. + auto line = Line(other_bezier->initialPoint(), other_bezier->finalPoint()); + return bezier_line_intersections<3>(*this, line); + } + } + return BezierCurve::intersect(other, eps); +} + +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 void bezier_expand_to_image(Rect &range, Point const &x0, Point const &x1, Point const &x2) +{ + for (auto i : { X, Y }) { + bezier_expand_to_image(range[i], x0[i], x1[i], x2[i]); + } +} + +static void bezier_expand_to_image(Rect &range, Point const &x0, Point const &x1, Point const &x2, Point const &x3) +{ + for (auto i : { X, Y }) { + bezier_expand_to_image(range[i], x0[i], x1[i], x2[i], x3[i]); + } +} + +template <> +void BezierCurveN<1>::expandToTransformed(Rect &bbox, Affine const &transform) const +{ + bbox.expandTo(finalPoint() * transform); +} + +template <> +void BezierCurveN<2>::expandToTransformed(Rect &bbox, Affine const &transform) const +{ + bezier_expand_to_image(bbox, controlPoint(0) * transform, + controlPoint(1) * transform, + controlPoint(2) * transform); +} + +template <> +void BezierCurveN<3>::expandToTransformed(Rect &bbox, Affine const &transform) const +{ + bezier_expand_to_image(bbox, controlPoint(0) * transform, + controlPoint(1) * transform, + controlPoint(2) * transform, + controlPoint(3) * transform); +} + +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-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.cpp b/src/2geom/bezier.cpp new file mode 100644 index 0000000..264b3c2 --- /dev/null +++ b/src/2geom/bezier.cpp @@ -0,0 +1,415 @@ +/** + * @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> +#include <2geom/choose.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)); + int n = fd.size(); + + for (int i = 0; i < n; i++) { + fd[i] = 0; + int b = (i & 1) ? -1 : 1; // b = (-1)^j binomial(n, j - i) + for (int j = i; j < n; j++) { + fd[i] += c_[j] * b; + binomial_increment_k(b, n, j - i); + b = -b; + } + } + 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) +{ + int m = f.order(); + int 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) + + int mci = 1; + for (int i = 0; i <= m; i++) { + double const fi = mci * f[i]; + + int ncj = 1; + for (int j = 0; j <= n; j++) { + h[i + j] += fi * ncj * g[j]; + binomial_increment_k(ncj, n, j); + } + + binomial_increment_k(mci, m, i); + } + + int mnck = 1; + for (int k = 0; k <= m + n; k++) { + h[k] /= mnck; + binomial_increment_k(mnck, 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 (double i : r) { + ret->expandTo(b.valueAt(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(); + } +} + +/* + * The general bézier of degree n is + * + * p(t) = sum_{i = 0...n} binomial(n, i) t^i (1 - t)^(n - i) x[i] + * + * It can be written explicitly as a polynomial in t as + * + * p(t) = sum_{i = 0...n} binomial(n, i) t^i [ sum_{j = 0...i} binomial(i, j) (-1)^(i - j) x[j] ] + * + * Its derivative is + * + * p'(t) = n sum_{i = 1...n} binomial(n - 1, i - 1) t^(i - 1) [ sum_{j = 0...i} binomial(i, j) (-1)^(i - j) x[j] ] + * + * This is used by the various specialisations below as an optimisation for low degree n <= 3. + * In the remaining cases, the generic implementation is used which resorts to iteration. + */ + +void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2) +{ + range.expandTo(x2); + + if (range.contains(x1)) { + // The interval contains all control points, and therefore the entire curve. + return; + } + + // p'(t) / 2 = at + b + auto a = (x2 - x1) - (x1 - x0); + auto b = x1 - x0; + + // t = -b / a + if (std::abs(a) > EPSILON) { + auto t = -b / a; + if (t > 0.0 && t < 1.0) { + auto s = 1.0 - t; + auto x = s * s * x0 + 2 * s * t * x1 + t * t * x2; + range.expandTo(x); + } + } +} + +void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2, Coord x3) +{ + range.expandTo(x3); + + if (range.contains(x1) && range.contains(x2)) { + // The interval contains all control points, and therefore the entire curve. + return; + } + + // p'(t) / 3 = at^2 + 2bt + c + auto a = (x3 - x0) - 3 * (x2 - x1); + auto b = (x2 - x1) - (x1 - x0); + auto c = x1 - x0; + + auto expand = [&] (Coord t) { + if (t > 0.0 && t < 1.0) { + auto s = 1.0 - t; + auto x = s * s * s * x0 + 3 * s * s * t * x1 + 3 * t * t * s * x2 + t * t * t * x3; + range.expandTo(x); + } + }; + + // t = (-b ± sqrt(b^2 - ac)) / a + if (std::abs(a) < EPSILON) { + if (std::abs(b) > EPSILON) { + expand(-c / (2 * b)); + } + } else { + auto d2 = b * b - a * c; + if (d2 >= 0.0) { + auto bsign = b >= 0.0 ? 1 : -1; + auto tmp = -(b + bsign * std::sqrt(d2)); + expand(tmp / a); + expand(c / tmp); // Using Vieta's formula: product of roots == c/a + } + } +} + +} // 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.cpp b/src/2geom/cairo-path-sink.cpp new file mode 100644 index 0000000..a04f715 --- /dev/null +++ b/src/2geom/cairo-path-sink.cpp @@ -0,0 +1,127 @@ +/** + * @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. + * + */ + +#ifdef HAVE_CAIRO + +#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 + +#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/circle.cpp b/src/2geom/circle.cpp new file mode 100644 index 0000000..d97487a --- /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.emplace_back(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.emplace_back(timeAt(i1p), l.timeAt(i1p), i1p); + result.emplace_back(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.emplace_back(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.emplace_back(timeAt(x1), other.timeAt(x1), x1); + result.emplace_back(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/concepts.cpp b/src/2geom/concepts.cpp new file mode 100644 index 0000000..e8c8e5c --- /dev/null +++ b/src/2geom/concepts.cpp @@ -0,0 +1,69 @@ +/** + * \file + * \brief Concept checking + *//* + * Copyright 2015 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, 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 <boost/concept/assert.hpp> +#include <2geom/concepts.h> + +#include <2geom/line.h> +#include <2geom/circle.h> +#include <2geom/ellipse.h> +#include <2geom/curves.h> +#include <2geom/convex-hull.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> + +#include <2geom/bezier.h> +#include <2geom/sbasis.h> +#include <2geom/linear.h> +#include <2geom/d2.h> + +namespace Geom { + +void concept_checks() +{ + BOOST_CONCEPT_ASSERT((ShapeConcept<Line>)); + //BOOST_CONCEPT_ASSERT((ShapeConcept<Circle>)); + //BOOST_CONCEPT_ASSERT((ShapeConcept<Ellipse>)); + BOOST_CONCEPT_ASSERT((ShapeConcept<BezierCurve>)); + BOOST_CONCEPT_ASSERT((ShapeConcept<EllipticalArc>)); + //BOOST_CONCEPT_ASSERT((ShapeConcept<SBasisCurve>)); + //BOOST_CONCEPT_ASSERT((ShapeConcept<ConvexHull>)); + //BOOST_CONCEPT_ASSERT((ShapeConcept<Path>)); + //BOOST_CONCEPT_ASSERT((ShapeConcept<PathVector>)); + + BOOST_CONCEPT_ASSERT((NearConcept<Coord>)); + BOOST_CONCEPT_ASSERT((NearConcept<Point>)); + + BOOST_CONCEPT_ASSERT((FragmentConcept<Bezier>)); + BOOST_CONCEPT_ASSERT((FragmentConcept<Linear>)); + BOOST_CONCEPT_ASSERT((FragmentConcept<SBasis>)); +} + +} // end namespace Geom diff --git a/src/2geom/conic_section_clipper_impl.cpp b/src/2geom/conic_section_clipper_impl.cpp new file mode 100644 index 0000000..8b0445e --- /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. + */ + +#include <optional> + +#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 (double rt : rts) + { + if (rt < R.top() || rt > R.bottom()) continue; + Point P (R.right(), rt); + 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 (double rt : rts) + { + if (rt < R.left() || rt > R.right()) continue; + Point P (rt, 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 (double rt : rts) + { + if (rt < R.top() || rt > R.bottom()) continue; + Point P (R.left(), rt); + 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 (double rt : rts) + { + if (rt < R.left() || rt > R.right()) continue; + Point P (rt, 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 (double rt : rts) + { + extrema.push_back (gl.pointAt (rt)); + } + + 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 (auto & i : extrema) + { + if (!R.contains (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 (i); + if (P1angle < P2angle && !(P1angle <= Qangle && Qangle <= P2angle)) + continue; + if (P1angle > P2angle && !(P1angle <= Qangle || Qangle <= P2angle)) + continue; + + inner_points.push_back (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") + + std::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.emplace_back(ls1->initialPoint(), M, ls1->finalPoint(), 1); + inner_empty = false; + } + } + + std::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.emplace_back(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 + std::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/conicsec.cpp b/src/2geom/conicsec.cpp new file mode 100644 index 0000000..0865c0e --- /dev/null +++ b/src/2geom/conicsec.cpp @@ -0,0 +1,1640 @@ +/* + * 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> +#include <optional> + +namespace Geom +{ + +LineSegment intersection(Line l, Rect r) { + std::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 const&) { + 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(double rt : rts) { + Point P = L0.pointAt(rt); + res.push_back(P); + } + rts = C1.roots(L1); + for(double rt : rts) { + Point P = L1.pointAt(rt); + 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(double rt : rts) { + Point P0 = Lx.pointAt(rt); + //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(double cnrt : cnrts) { + Point P = L.pointAt(cnrt); + 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(double rt : rts) { + res.push_back(lssb.valueAt(rt)); + } + } + return res; +} + + std::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 std::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 std::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; +} + + +std::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 std::optional<Point>(); + } +} + +std::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]); + std::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; +} + +std::array<Line, 2> xAx::decompose_df(Coord epsilon) const +{ + // For the classification of degenerate conics, see https://mathworld.wolfram.com/QuadraticCurve.html + using std::sqrt, std::abs; + + // Create 2 degenerate lines + auto const origin = Point(0, 0); + std::array<Line, 2> result = {Line(origin, origin), Line(origin, origin)}; + + double A = c[0]; + double B = c[1]; + double C = c[2]; + double D = c[3]; + double E = c[4]; + double F = c[5]; + Coord discriminant = sqr(B) - 4 * A * C; + if (discriminant < -epsilon) { + return result; + } + + bool single_line = false; // In the generic case, there will be 2 lines. + bool parallel_lines = false; + if (discriminant < epsilon) { + discriminant = 0; + parallel_lines = true; + // Check the secondary discriminant + Coord const secondary = sqr(D) + sqr(E) - 4 * F * (A + C); + if (secondary < -epsilon) { + return result; + } + single_line = (secondary < epsilon); + } + + if (abs(A) > epsilon || abs(C) > epsilon) { + // This is the typical case: either x² or y² come with a nonzero coefficient. + // To guard against numerical errors, we check which of the coefficients A, C has larger absolute value. + + bool const swap_xy = abs(C) > abs(A); + if (swap_xy) { + std::swap(A, C); + std::swap(D, E); + } + + // From now on, we may assume that A is "reasonably large". + if (parallel_lines) { + if (single_line) { + // Special case: a single line. + std::array<double, 3> coeffs = {sqrt(abs(A)), sqrt(abs(C)), sqrt(abs(F))}; + if (swap_xy) { + std::swap(coeffs[0], coeffs[1]); + } + rescale_homogenous(coeffs); + result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]); + return result; + } + + // Two parallel lines. + Coord const quotient_discriminant = sqr(D) - 4 * A * F; + if (quotient_discriminant < 0) { + return result; + } + Coord const sqrt_disc = sqrt(quotient_discriminant); + double const c1 = 0.5 * (D - sqrt_disc); + double const c2 = c1 + sqrt_disc; + std::array<double, 3> coeffs = {A, 0.5 * B, c1}; + if (swap_xy) { + std::swap(coeffs[0], coeffs[1]); + } + rescale_homogenous(coeffs); + result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]); + + coeffs = {A, 0.5 * B, c2}; + if (swap_xy) { + std::swap(coeffs[0], coeffs[1]); + } + rescale_homogenous(coeffs); + result[1].setCoefficients(coeffs[0], coeffs[1], coeffs[2]); + return result; + } + + // Now for the typical case of 2 non-parallel lines. + + // We know that A is further away from 0 than C is. + // The mathematical derivation of the solution is as follows: + // let Δ = B² - 4AC (the discriminant); we know Δ > 0. + // Write δ = sqrt(Δ); we know that this is also positive. + // Then the product AΔ is nonzero, so the equation + // Ax² + Bxy + Cy² + Dx + Ey + F = 0 + // is equivalent to + // AΔ (Ax² + Bxy + Cy² + Dx + Ey + F) = 0. + // Consider the two factors + // L_1 = Aδx + 0.5 (Bδ-Δ)y + EA - 0.5 D(B-δ) + // L_2 = Aδx + 0.5 (Bδ+Δ)y - EA + 0.5 D(B+δ) + // With a bit of algebra, you can show that L_1 * L_2 expands + // to AΔ (Ax² + Bxy + Cy² + Dx + Ey + F) (in order to get the + // correct value of F, you have to use the fact that the conic + // is degenerate). Therefore, the factors L_1 and L_2 are in + // fact equations of the two lines to be found. + Coord const delta = sqrt(discriminant); + std::array<double, 3> coeffs1 = {A * delta, 0.5 * (B * delta - discriminant), E * A - 0.5 * D * (B - delta)}; + std::array<double, 3> coeffs2 = {coeffs1[0], coeffs1[1] + discriminant, D * delta - coeffs1[2]}; + if (swap_xy) { // We must unswap the coefficients of x and y + std::swap(coeffs1[0], coeffs1[1]); + std::swap(coeffs2[0], coeffs2[1]); + } + + unsigned index = 0; + if (coeffs1[0] != 0 || coeffs1[1] != 0) { + rescale_homogenous(coeffs1); + result[index++].setCoefficients(coeffs1[0], coeffs1[1], coeffs1[2]); + } + if (coeffs2[0] != 0 || coeffs2[1] != 0) { + rescale_homogenous(coeffs2); + result[index].setCoefficients(coeffs2[0], coeffs2[1], coeffs2[2]); + } + return result; + } + + // If we're here, then A==0 and C==0. + if (abs(B) < epsilon) { // A == B == C == 0, so the conic reduces to Dx + Ey + F. + if (D == 0 && E == 0) { + return result; + } + std::array<double, 3> coeffs = {D, E, F}; + rescale_homogenous(coeffs); + result[0].setCoefficients(coeffs[0], coeffs[1], coeffs[2]); + return result; + } + + // OK, so A == C == 0 but B != 0. In other words, the conic has the form + // Bxy + Dx + Ey + F. Since B != 0, the zero set stays the same if we multiply the + // equation by B, which gives us this equation: + // B²xy + BDx + BEy + BF = 0. + // The above factors as (Bx + E)(By + D) = 0. + std::array<double, 2> nonzero_coeffs = {B, E}; + rescale_homogenous(nonzero_coeffs); + result[0].setCoefficients(nonzero_coeffs[0], 0, nonzero_coeffs[1]); + + nonzero_coeffs = {B, D}; + rescale_homogenous(nonzero_coeffs); + result[1].setCoefficients(0, nonzero_coeffs[0], nonzero_coeffs[1]); + return result; +} + +/* + * 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 (double rt : rts) + M.push_back (gl[dim].pointAt (rt)); + 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/convex-hull.cpp b/src/2geom/convex-hull.cpp new file mode 100644 index 0000000..f801fcc --- /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 (auto i : upperHull()) { + 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 (auto j : lowerHull()) { + 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 (auto i : ch) { + 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/coord.cpp b/src/2geom/coord.cpp new file mode 100644 index 0000000..205a82f --- /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 const 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 const 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 const 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/2geom/crossing.cpp b/src/2geom/crossing.cpp new file mode 100644 index 0000000..1159fb0 --- /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(auto & i : cr) { + i.ta += a; + i.tb += b; + } +} + +Crossings reverse_ta(Crossings const &cr, std::vector<double> max) { + Crossings ret; + for(const auto & i : cr) { + 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(const auto & i : cr) { + 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/curve.cpp b/src/2geom/curve.cpp new file mode 100644 index 0000000..f79edb3 --- /dev/null +++ b/src/2geom/curve.cpp @@ -0,0 +1,235 @@ +/* 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> + * RafaÅ‚ Siejakowski <rs@rs-math.net> + * + * 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> + +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 (double t : ts) { + //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 +{ + /// Represents a sub-arc of the curve. + struct Subcurve + { + std::unique_ptr<Curve> curve; + Interval parameter_range; + + Subcurve(Curve *piece, Coord from, Coord to) + : curve{piece} + , parameter_range{from, to} + {} + }; + + /// A closure to split the curve into portions at the prescribed split points. + auto const split_into_subcurves = [=](std::vector<Coord> const &splits) { + std::vector<Subcurve> result; + result.reserve(splits.size() + 1); + Coord previous = 0; + for (Coord split : splits) { + // Use global EPSILON since we're operating on normalized curve times. + if (split < EPSILON || split > 1.0 - EPSILON) { + continue; + } + result.emplace_back(portion(previous, split), previous, split); + previous = split; + } + result.emplace_back(portion(previous, 1.0), previous, 1.0); + return result; + }; + + /// A closure to find pairwise intersections between the passed subcurves. + auto const pairwise_intersect = [=](std::vector<Subcurve> const &subcurves) { + std::vector<CurveIntersection> result; + for (unsigned i = 0; i < subcurves.size(); i++) { + for (unsigned j = i + 1; j < subcurves.size(); j++) { + auto const xings = subcurves[i].curve->intersect(*subcurves[j].curve, eps); + for (auto const &xing : xings) { + // To avoid duplicate intersections, skip values at exactly 1. + if (xing.first == 1. || xing.second == 1.) { + continue; + } + Coord const ti = subcurves[i].parameter_range.valueAt(xing.first); + Coord const tj = subcurves[j].parameter_range.valueAt(xing.second); + result.emplace_back(ti, tj, xing.point()); + } + } + } + std::sort(result.begin(), result.end()); + return result; + }; + + // Monotonic segments cannot have self-intersections. Thus, we can split + // the curve at critical points of the X or Y coordinate and intersect + // the portions. However, there's the risk that a juncture between two + // adjacent portions is mistaken for an intersection due to numerical errors. + // Hence, we run the algorithm for both the X and Y coordinates and only + // keep the intersections that show up in both intersection lists. + + // Find the critical points of both coordinates. + std::unique_ptr<Curve> deriv{derivative()}; + auto const crits_x = deriv->roots(0, X); + auto const crits_y = deriv->roots(0, Y); + if (crits_x.empty() || crits_y.empty()) { + return {}; + } + + // Split into pieces in two ways and find self-intersections. + auto const pieces_x = split_into_subcurves(crits_x); + auto const pieces_y = split_into_subcurves(crits_y); + auto const crossings_from_x = pairwise_intersect(pieces_x); + auto const crossings_from_y = pairwise_intersect(pieces_y); + if (crossings_from_x.empty() || crossings_from_y.empty()) { + return {}; + } + + // Filter the results, only keeping self-intersections found by both approaches. + std::vector<CurveIntersection> result; + unsigned index_y = 0; + for (auto &&candidate_x : crossings_from_x) { + // Find a crossing corresponding to this one in the y-method collection. + while (index_y != crossings_from_y.size()) { + auto const gap = crossings_from_y[index_y].first - candidate_x.first; + if (std::abs(gap) < EPSILON) { + // We found the matching intersection! + result.emplace_back(candidate_x); + index_y++; + break; + } else if (gap < 0.0) { + index_y++; + } else { + break; + } + } + } + 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/d2-sbasis.cpp b/src/2geom/d2-sbasis.cpp new file mode 100644 index 0000000..4e95f6f --- /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 (auto & j : pre_result){ + Point aj = f.at(j.back()).lastValue(); + Point bj = f.at(j.front()).firstValue(); + if ( L2(a-aj) < tol ) { + j.push_back(i); + inserted = true; + break; + } + if ( L2(b-bj) < tol ) { + j.insert(j.begin(),i); + inserted = true; + break; + } + } + if (!inserted) { + pre_result.emplace_back(); + pre_result.back().push_back(i); + } + } + for (auto & i : pre_result){ + Piecewise<D2<SBasis> > comp; + for (unsigned j=0; j<i.size(); j++){ + Piecewise<D2<SBasis> > new_comp = f.at(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 (auto i : a){ + for (auto j : b){ + OptInterval c( i ); + c &= 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/doxygen.cpp b/src/2geom/doxygen.cpp new file mode 100644 index 0000000..3c64eec --- /dev/null +++ b/src/2geom/doxygen.cpp @@ -0,0 +1,301 @@ +/* + * Doxygen documentation for the lib2geom library + * + * Authors: + * Krzysztof KosiÅ„ski <tweenk.pl@gmail.com> + * + * Copyright 2009-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. + */ + +// Main page of the documentation - contains logo and introductory text +/** + * @mainpage + * + * @image html 2geom-logo.png + * + * @section Introduction + * + * 2Geom is a computational geometry library intended for use with 2D vector graphics. + * It concentrates on high-level algorithms, such as computing the length of a curve + * or Boolean operations on paths. It evolved from the geometry code used + * in Inkscape, a free software, cross-platform vector graphics editor. + * + * @section UserGuide User guide + * + * - @subpage Overview "Overview of 2Geom" + * - @ref Primitives "Primitives" - points, angles, lines, axis-aligned rectangles... + * - @ref Transforms "Transformations" - mathematical representation for operations + * like translation, scaling and rotation. + * - @ref Fragments "Fragments" - one-dimensional functions and related utilities. + * - @ref Curves "Curves" - functions mapping the unit interval to points on a plane. + * - @ref Shapes "Shapes" - circles, ellipses, polygons and the like. + * - @ref Paths "Paths" - sequences of contiguous curves, aka splines, and their processing. + * - @ref ShapeOps "Shape operations" - boolean algebra, offsets and other advanced operations. + * - @ref Containers "Geometric containers" - efficient ways to store and retrieve + * geometric information. + * - @ref Utilities "Utilities" - other useful code that does not fit under the above categories. + * - @subpage ReleaseNotes "Release notes" - what's new in 2Geom + * + * @section DeveloperInfo Developer information + * + * - @subpage CodingStandards "Coding standards used in 2Geom" + */ + +// Overview subpage +/** + * @page Overview Overview of 2Geom + * + * 2Geom has two APIs: a high level one, which uses virtual functions to allow handling + * objects of in a generic way without knowing their actual type at compile time, + * and a lower-level one based on templates, which is designed with performance in mind. + * For performance-critical tasks it may be necessary to use the lower level API. + * + * @section CoordSys Standard coordinate system + * + * 2Geom's standard coordinate system is common for computer graphics: the X axis grows + * to the right and the Y axis grows downwards. Angles start from the +X axis + * and grow towards the +Y axis (clockwise). + * + * @image html coords.png Standard coordinate system in 2Geom + * + * Most functions can be used without taking the coordinate system into account, + * as their interpretation is the same regardless of the coordinate system. However, + * a few of them depend on this definition, for example Rect's top() and bottom() methods. + * + * @section OpNote Operator note + * + * Most operators are provided by Boost operator helpers. This means that not all operators + * are defined in the class. For example, Rect only implements the operators + * +=, -= for points and *= for affines. The corresponding +, - and * operators + * are generated automatically by Boost. + */ + +// RELEASE NOTES +// Update this to describe the most important API changes. +/** + * @page ReleaseNotes 2Geom release notes + * + * @section Ver04 Version 0.4 + * - API additions: + * - Integer versions of Point, Interval and OptInterval, called + * IntPoint, IntInterval and OptIntInterval. + * - New geometric primitives: Angle and AngleInterval. + * - Major changes: + * - Matrix has been renamed to Affine. + * - Classification methods of Affine, for example Affine::isRotation(), will now + * return true for transforms that are close to identity. This is to reflect the + * fact that an identity transform can be interpreted as a rotation by zero + * degrees. To get the old behavior of returning false for identity, use + * methods prefixed with "Nonzero", e.g. Affine::isNonzeroRotation(). + * - EllipticalArc and SVGEllipticalArc have been merged. Now there is only the former. + * All arcs are SVG-compliant. + * - Minor changes: + * - Affine::without_translation() is now called Affine::withoutTranslation(). + * - Interval::strict_contains() is now called Interval::interiorContains(). + * The same change has been made for Rect. + * - Some unclear and unused operators of D2 were removed, for instance D2 * Point. + * - Interval is now a derived class of a GenericInterval template. + * - Rect is no longer a D2 specialization. + * - isnan.h merged with math-utils.h. + * @section Ver03 Version 0.3 + * - release notes were started after this version. + */ + +/** + * @page CodingStandards Coding standards and conventions used in 2Geom + * + * @section Filenames + * + * Files and directories should be all lowercase. Words should be separated with hyphens (-). + * Underscores, capital letters and non-ASCII characters should not be used. + * + * @section Indenting + * + * All files should use 4 spaces as indentation. + * + * @section Namespaces + * + * All classes intended for direct use by the end users should be in the Geom namespace. + * Contents of namespaces should not be indented. Closing brace of a namespace + * should have a comment indicating which namespace it is closing. + * @code + namespace Geom { + namespace FooInternal { + + unsigned some_function() + { + // ...code... + } + + } // namespace FooInternal + } // namespace Geom + @endcode + * + * @section Classes + * + * @code + // superclass list should use Boost notation, + // especially if there is more than one. + class Foo + : public Bar + , public Baz + { + // constructors should use Boost notation if the class has superclasses. + Foo(int a) + : Bar(a) + , Baz(b) + { + // constructor body + } + Foo(int a) { + // constructor with default initialization of superclasses + } + + // methods use camelCaseNames. + // one-line methods can be collapsed. + bool isActive() { return _blurp; } + // multi-line methods have the opening brace on the same line. + void invert() { + // ... code ... + } + + // static functions use lowercase_with_underscores. + // static factory functions should be called from_something. + static Foo from_point(Point const &p) { + // ... + } + }; // end of class Foo + + // Closing brace of a class should have the above comment, unless it's very short. + @endcode + * + * @section FreeFuns Free functions + * + * Functions should use lowercase_with_underscores names. The opening brace of + * the definition should be on a separate line. + * + * @section InlineInClasses When to use inline + * + * The "inline" keyword is not required when the body of the function is given + * in the definition of the class. Do not mark such functions inline, because + * they are automatically marked as inline by the compiler. It is only + * necessary to use the inline keyword when the body of the function is given + * after the class definition. + */ + +// Documentation for groups +/** + * @defgroup Transforms Affine transformations + * @brief Transformations of the plane such as rotation and scaling + * + * Each transformation class represent a set of affine transforms that is closed + * under multiplication. Those are translation, scaling, rotation, horizontal shearing + * and vertical shearing. Any affine transform can be obtained by combining those + * basic operations. + * + * Each of the transforms can be applied to points and matrices (using multiplication). + * Each can also be converted into a matrix (which can represent any composition + * of transforms generically). All (except translation) use the origin (0,0) as the invariant + * point (e.g. one that stays in the same place after applying the transform to the plane). + * To obtain transforms with different invariant points, combine them with translation to + * and back from the origin. For example, to get a 60 degree rotation around the point @a p: + * @code Affine rot_around_p = Translate(-p) * Rotate::from_degrees(60) * Translate(p); @endcode + * + * Multiplication of transforms is associative: the result of an expression involving + * points and matrices is the same regardless of the order of evaluating multiplications. + * + * If you need to transform a complicated object + * by A, then B, and then C, you should first compute the total transform and apply it to the + * object in one go. This way instead of performing 3 expensive operations, you will only do + * two very fast matrix multiplications and one complex transformation. Here is an example: + * @code + transformed_path = long_path * A * B * C; // wrong! long_path will be transformed 3 times. + transformed_path = long_path * (A * B * C); // good! long_path will be transformed only once. + Affine total = A * B * C; // you can store the transform to apply it to several objects. + transformed_path = long_path * total; // good! + @endcode + * Ordering note: if you compose transformations via multiplication, they are applied + * from left to right. If you write <code> ptrans = p * A * B * C * D;</code>, then it means + * that @a ptrans is obtained from @a p by first transforming it by A, then by B, then by C, + * and finally by D. This is a consequence of interpreting points as row vectors, instead + * of the more common column vector interpretation; 2Geom's choice leads to more intuitive + * notation. + */ + +/** + * @defgroup Primitives Primitives + * @brief Basic mathematical objects such as intervals and points + * + * 2Geom has several basic geometrical objects: points, lines, intervals, angles, + * and others. Most of those objects can be treated as sets of points or numbers + * satisfying some equation or as functions. + */ + +/** + * @defgroup Fragments Fragments and related classes + * @brief 1D functions on the unit interval + * + * Each type of fragments represents one of the various ways in which a function from + * the unit interval to the real line may be given. These are the most important mathematical + * primitives in 2Geom. + */ + +/** + * @defgroup Curves Curves + * @brief Functions mapping the unit interval to a plane + * + * Curves are functions \f$\mathbf{C}: [0, 1] \to \mathbb{R}^2\f$. For details, see + * the documentation for the Curve class. All curves can be included in paths and path sequences. + */ + +/** + * @defgroup Shapes Basic shapes + * @brief Circles, ellipes, polygons... + * + * Among the shapes supported by 2Geom are circles, ellipses and polygons. + * Polygons can also be represented by paths containing only linear segments. + */ + +/** + * @defgroup Paths Paths and path sequences + * @brief Sequences of contiguous curves, aka splines, and their processing + */ + +/** + * @defgroup Utilities Miscellaneous utilities + * @brief Useful code that does not fit under other categories. + */ + +/* + 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..42cb36d --- /dev/null +++ b/src/2geom/ellipse.cpp @@ -0,0 +1,790 @@ +/** @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/conicsec.h> +#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 +{ + auto const trans = unitCircleTransform(); + + auto proj_bounds = [&] (Dim2 d) { + // The dth coordinate function pulls back to trans[d] * x + trans[d + 2] * y + trans[d + 4] + // in the coordinate system where the ellipse is a unit circle. We compute its range of + // values on the unit circle. + auto const r = std::hypot(trans[d], trans[d + 2]); + auto const mid = trans[d + 4]; + return Interval(mid - r, mid + r); + }; + + return { proj_bounds(X), proj_bounds(Y) }; +} + +Rect Ellipse::boundsFast() const +{ + // Every ellipse is contained in the circle with the same center and radius + // equal to the larger of the two rays. We return the bounding square + // of this circle (this is really fast but only exact for circles). + auto const larger_ray = (ray(X) > ray(Y) ? ray(X) : ray(Y)); + assert(larger_ray >= 0.0); + auto const rr = Point(larger_ray, larger_ray); + return Rect(_center - rr, _center + rr); +} + +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; + } + + // 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] *= std::abs(mwot[0]); + _rays[Y] *= std::abs(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; +} + +/** @brief Convert curve time on the major axis to the corresponding angle + * parameters on a degenerate ellipse collapsed onto that axis. + * @param t The curve time on the major axis of an ellipse. + * @param vertical If true, the major axis goes from angle -Ï€/2 to +Ï€/2; + * otherwise, the major axis connects angles Ï€ and 0. + * @return The two angles at which the collapsed ellipse passes through the + * major axis point corresponding to the given time \f$t \in [0, 1]\f$. + */ +static std::array<Coord, 2> axis_time_to_angles(Coord t, bool vertical) +{ + Coord const to_unit = std::clamp(2.0 * t - 1.0, -1.0, 1.0); + if (vertical) { + double const arcsin = std::asin(to_unit); + return {arcsin, M_PI - arcsin}; + } else { + double const arccos = std::acos(to_unit); + return {arccos, -arccos}; + } +} + +/** @brief For each intersection of some shape with the major axis of an ellipse, produce one or two + * intersections of a degenerate ellipse (collapsed onto that axis) with the same shape. + * + * @param axis_intersections The intersections of some shape with the major axis. + * @param vertical Whether this is the vertical major axis (in the ellipse's natural coordinates). + * @return A vector with doubled intersections (corresponding to the two passages of the squashed + * ellipse through that point) and swapped order of the intersected shapes. +*/ +static std::vector<ShapeIntersection> double_axis_intersections(std::vector<ShapeIntersection> &&axis_intersections, + bool vertical) +{ + if (axis_intersections.empty()) { + return {}; + } + std::vector<ShapeIntersection> result; + result.reserve(2 * axis_intersections.size()); + + for (auto const &x : axis_intersections) { + for (auto a : axis_time_to_angles(x.second, vertical)) { + result.emplace_back(a, x.first, x.point()); // Swap first <-> converted second. + if (x.second == 0.0 || x.second == 1.0) { + break; // Do not double up endpoint intersections. + } + } + } + return result; +} + +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) { + return double_axis_intersections(line.intersect(majorAxis()), ray(X) == 0); + } + + // Ax^2 + Bxy + Cy^2 + Dx + Ey + F + std::array<Coord, 6> coeffs; + coefficients(coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5]); + rescale_homogenous(coeffs); + auto [A, B, C, D, E, F] = coeffs; + Affine iuct = inverseUnitCircleTransform(); + + // generic case + std::array<Coord, 3> line_coeffs; + line.coefficients(line_coeffs[0], line_coeffs[1], line_coeffs[2]); + rescale_homogenous(line_coeffs); + auto [a, b, c] = line_coeffs; + 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 (double x : xs) { + Point p(x, q*x + r); + result.emplace_back(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 (double x : xs) { + Point p(q*x + r, x); + result.emplace_back(atan2(p * iuct), line.timeAt(p), p); + } + } + return result; +} + +std::vector<ShapeIntersection> Ellipse::intersect(LineSegment const &seg) const +{ + if (!boundsFast().intersects(seg.boundsFast())) { + return {}; + } + + // We simply reuse the procedure for lines and filter out + // results where the line time value is outside of the unit interval, + // but we apply a small tolerance to account for numerical errors. + double const param_prec = EPSILON / seg.length(0.0); + // TODO: accept a precision setting instead of always using EPSILON + // (requires an ABI break). + + auto xings = intersect(Line(seg)); + if (xings.empty()) { + return xings; + } + decltype(xings) result; + result.reserve(xings.size()); + + for (auto const &x : xings) { + if (x.second < -param_prec || x.second > 1.0 + param_prec) { + continue; + } + result.emplace_back(x.first, std::clamp(x.second, 0.0, 1.0), x.point()); + } + return result; +} + +std::vector<ShapeIntersection> Ellipse::intersect(Ellipse const &other) const +{ + // Handle degenerate cases first. + if (ray(X) == 0 || ray(Y) == 0) { // Degenerate ellipse, collapsed to the major axis. + return double_axis_intersections(other.intersect(majorAxis()), ray(X) == 0); + } + if (*this == other) { // Two identical ellipses. + THROW_INFINITELY_MANY_SOLUTIONS("The two ellipses are identical."); + } + if (!boundsFast().intersects(other.boundsFast())) { + return {}; + } + + // Find coefficients of the implicit equations of the two ellipses and rescale + // them (losslessly) for better numerical conditioning. + std::array<double, 6> coeffs; + coefficients(coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5]); + rescale_homogenous(coeffs); + auto [A, B, C, D, E, F] = coeffs; + + std::array<double, 6> otheffs; + other.coefficients(otheffs[0], otheffs[1], otheffs[2], otheffs[3], otheffs[4], otheffs[5]); + rescale_homogenous(otheffs); + auto [a, b, c, d, e, f] = otheffs; + + // Assume that Q(x, y) = 0 is the ellipse equation given by uppercase letters + // and R(x, y) = 0 is the equation given by lowercase ones. + // In other words, Q is the quadratic function describing this ellipse and + // R is the quadratic function for the other ellipse. + // + // A point (x, y) is common to both ellipses if and only if it solves the system + // { Q(x, y) = 0, + // { R(x, y) = 0. + // + // If µ is any real number, we can multiply the first equation by µ and add that + // to the first equation, obtaining the new system of equations: + // { Q(x, y) = 0, + // { µQ(x, y) + R(x, y) = 0. + // + // The first equation still says that (x, y) is a point on this ellipse, but the + // second equation uses the new expression (µQ + R) instead of the original R. + // + // Why do we do this? The reason is that the set of functions {µQ + R : µ real} + // is a "real system of conics" and there's a theorem which guarantees that such a system + // always contains a "degenerate conic" [proof below]. + // In practice, the degenerate conic will describe a line or a pair of lines, and intersecting + // a line with an ellipse is much easier than intersecting two ellipses directly. + // + // But in order to be able to do this, we must find a value of µ for which µQ + R is degenerate. + // We can write the expression (µQ + R)(x, y) in the following way: + // + // | aa bb/2 dd/2 | |x| + // (µQ + R)(x, y) = [x y 1] | bb/2 cc ee/2 | |y| + // | dd/2 ee/2 ff | |1| + // + // where aa = µA + a and so on. The determinant can be explicitly written out, + // giving an equation which is cubic in µ and can be solved analytically. + // The conic µQ + R is degenerate if and only if this determinant is 0. + // + // Proof that there's always a degenerate conic: a cubic real polynomial always has a root, + // and if the polynomial in µ isn't cubic (coefficient of µ^3 is zero), then the starting + // conic is already degenerate. + + Coord I, J, K, L; // Coefficients of µ in the expression for the determinant. + I = (-B*B*F + 4*A*C*F + D*E*B - A*E*E - C*D*D) / 4; + J = -((B*B - 4*A*C) * f + (2*B*F - D*E) * b + (2*A*E - D*B) * e + + (2*C*D - E*B) * d + (D*D - 4*A*F) * c + (E*E - 4*C*F) * a) / 4; + K = -((b*b - 4*a*c) * F + (2*b*f - d*e) * B + (2*a*e - d*b) * E + + (2*c*d - e*b) * D + (d*d - 4*a*f) * C + (e*e - 4*c*f) * A) / 4; + L = (-b*b*f + 4*a*c*f + d*e*b - a*e*e - c*d*d) / 4; + + std::vector<Coord> mus = solve_cubic(I, J, K, L); + Coord mu = infinity(); + + // Now that we have solved for µ, we need to check whether the conic + // determined by µ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]); + } + /// Discriminant within this radius of 0 will be considered zero. + static Coord const discriminant_precision = 1e-10; + + for (Coord candidate_mu : mus) { + Coord const aa = std::fma(candidate_mu, A, a); + Coord const bb = std::fma(candidate_mu, B, b); + Coord const cc = std::fma(candidate_mu, C, c); + Coord const delta = sqr(bb) - 4*aa*cc; + if (delta < -discriminant_precision) { + continue; + } + mu = candidate_mu; + break; + } + + // if no suitable mu was found, there are no intersections + if (mu == infinity()) { + return {}; + } + + // Create the degenerate conic and decompose it into lines. + std::array<double, 6> degen = {std::fma(mu, A, a), std::fma(mu, B, b), std::fma(mu, C, c), + std::fma(mu, D, d), std::fma(mu, E, e), std::fma(mu, F, f)}; + rescale_homogenous(degen); + auto const lines = xAx(degen[0], degen[1], degen[2], + degen[3], degen[4], degen[5]).decompose_df(discriminant_precision); + + // intersect with the obtained lines and report intersections + std::vector<ShapeIntersection> result; + for (auto const &line : lines) { + if (line.isDegenerate()) { + continue; + } + auto as = intersect(line); + // NOTE: If we only cared about the intersection points, we could simply + // intersect this ellipse with the lines and ignore the other ellipse. + // But we need the time coordinates on the other ellipse as well. + auto bs = other.intersect(line); + if (as.empty() || bs.empty()) { + continue; + } + // Due to numerical errors, a tangency may sometimes be found as 1 intersection + // on one ellipse and 2 intersections on the other. If this happens, we average + // the points of the two intersections. + auto const intersection_average = [](ShapeIntersection const &i, + ShapeIntersection const &j) -> ShapeIntersection + { + return ShapeIntersection(i.first, j.first, middle_point(i.point(), j.point())); + }; + auto const synthesize_intersection = [&](ShapeIntersection const &i, + ShapeIntersection const &j) -> void + { + result.emplace_back(i.first, j.first, middle_point(i.point(), j.point())); + }; + if (as.size() == 2) { + if (bs.size() == 2) { + synthesize_intersection(as[0], bs[0]); + synthesize_intersection(as[1], bs[1]); + } else if (bs.size() == 1) { + synthesize_intersection(intersection_average(as[0], as[1]), bs[0]); + } + } else if (as.size() == 1) { + if (bs.size() == 2) { + synthesize_intersection(as[0], intersection_average(bs[0], bs[1])); + } else if (bs.size() == 1) { + synthesize_intersection(as[0], bs[0]); + } + } + } + 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); + + // We plug the X and Y curves into the implicit equation and solve for t. + 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 (double & i : r) { + Point p = b.valueAt(i); + result.emplace_back(timeAt(p), 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 (auto & tp : tps) { + if (!are_near(tp * ac.unitCircleTransform(), + tp * 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/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..63e534c --- /dev/null +++ b/src/2geom/elliptical-arc.cpp @@ -0,0 +1,1045 @@ +/* + * 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 compute the local extremes. + * 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 { _initial_point, _final_point }; + } + + if (_angles.isFull()) { + return _ellipse.boundsExact(); + } + + auto const trans = unitCircleTransform(); + + auto proj_bounds = [&] (Dim2 d) { + // The dth coordinate function pulls back to trans[d] * x + trans[d + 2] * y + trans[d + 4] + // in the coordinate system where the ellipse is a unit circle. We compute its range of + // values on the unit circle arc. + auto result = Interval(_initial_point[d], _final_point[d]); + + auto const v = Point(trans[d], trans[d + 2]); + auto const r = v.length(); + auto const mid = trans[d + 4]; + auto const angle = Angle(v); + + if (_angles.contains(angle)) { + result.expandTo(mid + r); + } + if (_angles.contains(angle + M_PI)) { + result.expandTo(mid - r); + } + + return result; + }; + + return { proj_bounds(X), proj_bounds(Y) }; +} + +void EllipticalArc::expandToTransformed(Rect &bbox, Affine const &transform) const +{ + bbox.expandTo(_final_point * transform); + + if (isChord() || bbox.contains(_ellipse.boundsFast())) { + return; + } + + auto const trans = unitCircleTransform() * transform; + + for (auto d : { X, Y }) { + // See boundsExact() for explanation. + auto const v = Point(trans[d], trans[d + 2]); + auto const r = v.length(); + auto const mid = trans[d + 4]; + + if (_angles.isFull()) { + bbox[d].unionWith(Interval(mid - r, mid + r)); + } else { + auto const angle = Angle(v); + if (_angles.contains(angle)) { + bbox[d].expandTo(mid + r); + } + if (_angles.contains(angle + M_PI)) { + bbox[d].expandTo(mid - r); + } + } + } +} + +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 (double & i : sol) { + //std::cerr << "s = " << deg_from_rad(sol[i]); + i = timeAtAngle(i); + //std::cerr << " -> t: " << sol[i] << std::endl; + if (unit_interval.contains(i)) { + arc_sol.push_back(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 (t == 0.0) { + return initialPoint(); + } + if (t == 1.0) { + return finalPoint(); + } + 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 + f = std::clamp(f, 0.0, 1.0); + t = std::clamp(t, 0.0, 1.0); + + if (f == t) { + EllipticalArc *arc = new EllipticalArc(); + arc->_initial_point = arc->_final_point = pointAt(f); + return arc; + } + if (f == 0.0 && t == 1.0) { + return duplicate(); + } + if (f == 1.0 && t == 0.0) { + return reverse(); + } + + 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 (double & i : real_sol) + { + i = 2 * std::atan(i); + if ( i < 0 ) 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 + +/** @brief Convert the passed intersections to curve time parametrization + * and filter out any invalid intersections. + */ +std::vector<ShapeIntersection> EllipticalArc::_filterIntersections(std::vector<ShapeIntersection> &&xs, + bool is_first) const +{ + std::vector<ShapeIntersection> result; + result.reserve(xs.size()); + for (auto &xing : xs) { + if (_validateIntersection(xing, is_first)) { + result.emplace_back(std::move(xing)); + } + } + return result; +} + +/** @brief Convert the passed intersection to curve time and check whether the intersection + * is numerically sane. + * + * @param xing The intersection to convert to curve time and to validate. + * @param is_first If true, this arc is the first of the intersected curves; if false, it's second. + * @return Whether the intersection is valid. + * + * Note that the intersection is guaranteed to be converted only if the return value is true. + */ +bool EllipticalArc::_validateIntersection(ShapeIntersection &xing, bool is_first) const +{ + static auto const UNIT_INTERVAL = Interval(0, 1); + constexpr auto EPS = 1e-4; + + Coord &t = is_first ? xing.first : xing.second; + if (!are_near_rel(_ellipse.pointAt(t), xing.point(), EPS)) { + return false; + } + + t = timeAtAngle(t); + if (!UNIT_INTERVAL.contains(t)) { + return false; + } + if (!are_near_rel(pointAt(t), xing.point(), EPS)) { + return false; + } + return true; +} + +std::vector<CurveIntersection> EllipticalArc::intersect(Curve const &other, Coord eps) const +{ + if (isLineSegment()) { + LineSegment ls(_initial_point, _final_point); + return ls.intersect(other, eps); + } + + if (other.isLineSegment()) { + LineSegment ls(other.initialPoint(), other.finalPoint()); + return _filterIntersections(_ellipse.intersect(ls), true); + } + + if (auto bez = dynamic_cast<BezierCurve const *>(&other)) { + return _filterIntersections(_ellipse.intersect(bez->fragment()), true); + } + + if (auto arc = dynamic_cast<EllipticalArc const *>(&other)) { + std::vector<CurveIntersection> crossings; + try { + crossings = _ellipse.intersect(arc->_ellipse); + } catch (InfinitelyManySolutions &) { + // This could happen if the two arcs come from the same ellipse. + return _intersectSameEllipse(arc); + } + return arc->_filterIntersections(_filterIntersections(std::move(crossings), true), false); + } + + // in case someone wants to make a custom curve type + auto result = other.intersect(*this, eps); + transpose_in_place(result); + return result; +} + +/** @brief Check if two arcs on the same ellipse intersect/overlap. + * + * @param other Another elliptical arc on the same ellipse as this one. + * @return If the arcs overlap, the returned vector contains synthesized intersections + * at the start and end of the overlap. + * If the arcs do not overlap, an empty vector is returned. + */ +std::vector<ShapeIntersection> EllipticalArc::_intersectSameEllipse(EllipticalArc const *other) const +{ + assert(_ellipse == other->_ellipse); + auto const &other_angles = other->angularInterval(); + std::vector<ShapeIntersection> result; + + /// A closure to create an "intersection" at the prescribed angle. + auto const synthesize_intersection = [&](Angle angle) { + auto const time = timeAtAngle(angle); + if (result.end() == std::find_if(result.begin(), result.end(), + [=](ShapeIntersection const &xing) -> bool { + return xing.first == time; + })) + { + result.emplace_back(time, other->timeAtAngle(angle), _ellipse.pointAt(angle)); + } + }; + + for (auto a : {_angles.initialAngle(), _angles.finalAngle()}) { + if (other_angles.contains(a)) { + synthesize_intersection(a); + } + } + for (auto a : {other_angles.initialAngle(), other_angles.finalAngle()}) { + if (_angles.contains(a)) { + synthesize_intersection(a); + } + } + 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()); + + auto degenerate_ellipse = [&] { + _ellipse = Ellipse(); + _ellipse.setCenter(initialPoint()); + _angles = AngleInterval(); + _large_arc = false; + }; + + // if ip = fp, the arc contains no other points + if (initialPoint() == finalPoint()) { + degenerate_ellipse(); + 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 = d / 2 * 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 const denominator = rxpy + pxry; + if (denominator == 0.0) { + degenerate_ellipse(); + return; + } + Coord rad = (rxry - pxry - rxpy) / denominator; + // 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); + } + + if (!Interval(ymin[Y], ymax[Y]).lowerContains(p[Y])) { + return 0; + } + + bool const left = cross(ymax - ymin, p - ymin) > 0; + bool const inside = _ellipse.contains(p); + if (_angles.isFull()) { + if (inside) { + return sweep() ? 1 : -1; + } + return 0; + } + bool const includes_ymin = _angles.contains(ymin_a); + bool const 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 const initial_left = larc.contains(ia); + bool const final_left = larc.contains(fa); + + bool intersects_left = false, intersects_right = false; + if (inside || left) { + // The point is inside the ellipse or to the left of it, so the rightwards horizontal ray + // may intersect the part of the arc contained in the right half of the ellipse. + // There are four ways in which this can happen. + + intersects_right = + // Possiblity 1: the arc extends into the right half through the min-Y point + // and the ray intersects this extension: + (includes_ymin && !final_left && Interval(ymin[Y], fp[Y]).lowerContains(p[Y])) + || + // Possibility 2: the arc starts and ends within the right half (hence, it cannot be the + // "large arc") and the ray's Y-coordinate is within the Y-coordinate range of the arc: + (!initial_left && !final_left && !largeArc() && Interval(ip[Y], fp[Y]).lowerContains(p[Y])) + || + // Possibility 3: the arc starts in the right half and continues through the max-Y + // point into the left half: + (!initial_left && includes_ymax && Interval(ip[Y], ymax[Y]).lowerContains(p[Y])) + || + // Possibility 4: the entire right half of the ellipse is contained in the arc. + (initial_left && final_left && includes_ymin && includes_ymax); + } + if (left && !inside) { + // The point is to the left of the ellipse, so the rightwards horizontal ray + // may intersect the part of the arc contained in the left half of the ellipse. + // There are four ways in which this can happen. + + intersects_left = + // Possibility 1: the arc starts in the left half and continues through the min-Y + // point into the right half: + (includes_ymin && initial_left && Interval(ymin[Y], ip[Y]).lowerContains(p[Y])) + || + // Possibility 2: the arc starts and ends within the left half (hence, it cannot be the + // "large arc") and the ray's Y-coordinate is within the Y-coordinate range of the arc: + (initial_left && final_left && !largeArc() && Interval(ip[Y], fp[Y]).lowerContains(p[Y])) + || + // Possibility 3: the arc extends into the left half through the max-Y point + // and the ray intersects this extension: + (final_left && includes_ymax && Interval(fp[Y], ymax[Y]).lowerContains(p[Y])) + || + // Possibility 4: the entire left half of the ellipse is contained in the arc. + (!initial_left && !final_left && includes_ymin && includes_ymax); + + } + int const winding_assuming_increasing_angles = (int)intersects_right - (int)intersects_left; + return sweep() ? winding_assuming_increasing_angles : -winding_assuming_increasing_angles; +} + +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/geom.cpp b/src/2geom/geom.cpp new file mode 100644 index 0000000..791e3a6 --- /dev/null +++ b/src/2geom/geom.cpp @@ -0,0 +1,396 @@ +/** + * \brief Various geometrical calculations. + */ + +#include <2geom/geom.h> +#include <2geom/point.h> +#include <algorithm> +#include <optional> +#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). + */ +std::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 std::optional<LineSegment>(); +} + +std::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/intersection-graph.cpp b/src/2geom/intersection-graph.cpp new file mode 100644 index 0000000..524267e --- /dev/null +++ b/src/2geom/intersection-graph.cpp @@ -0,0 +1,535 @@ +/** + * \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 { + +/// Function object for comparing intersection vertices based on the intersection time. +struct PathIntersectionGraph::IntersectionVertexLess { + bool operator()(IntersectionVertex const &a, IntersectionVertex const &b) const { + return a.pos < b.pos; + } +}; + +PathIntersectionGraph::PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision) + : _graph_valid(true) +{ + _pv[0] = a; + _pv[1] = b; + + if (a.empty() || b.empty()) return; + + _prepareArguments(); + bool has_intersections = _prepareIntersectionLists(precision); + if (!has_intersections) return; + + _assignEdgeWindingParities(precision); + + // 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. + _assignComponentStatusFromDegenerateIntersections(); + _removeDegenerateIntersections(); + if (_graph_valid) { + _verify(); + } +} + +/** Prepare the operands stored in PathIntersectionGraph::_pv by closing all of their constituent + * paths and removing degenerate segments from them. + */ +void PathIntersectionGraph::_prepareArguments() +{ + // all paths must be closed, otherwise we will miss some intersections + for (auto & w : _pv) { + for (auto & i : w) { + i.close(); + } + } + // remove degenerate segments + for (auto & w : _pv) { + for (std::size_t i = w.size(); i > 0; --i) { + if (w[i-1].empty()) { + w.erase(w.begin() + (i-1)); + continue; + } + for (std::size_t j = w[i-1].size(); j > 0; --j) { + if (w[i-1][j-1].isDegenerate()) { + w[i-1].erase(w[i-1].begin() + (j-1)); + } + } + } + } +} + +/** @brief Compute the lists of intersections between the constituent paths of both operands. + * @param precision – the precision setting for the sweepline algorithm. + * @return Whether any intersections were found. + */ +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 (auto & px : pxs) { + IntersectionVertex *xa, *xb; + xa = new IntersectionVertex(); + xb = new IntersectionVertex(); + //xa->processed = xb->processed = false; + xa->which = 0; xb->which = 1; + xa->pos = px.first; + xb->pos = px.second; + xa->p = xb->p = px.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 intersections in each component according to time value + for (auto & _component : _components) { + for (std::size_t i = 0; i < _component.size(); ++i) { + _component[i].xlist.sort(IntersectionVertexLess()); + } + } + + return true; +} + +/** Determine whether path portions between consecutive intersections lie inside or outside + * of the other path-vector. + */ +void PathIntersectionGraph::_assignEdgeWindingParities(Coord precision) +{ + for (unsigned w = 0; w < 2; ++w) { + unsigned ow = (w+1) % 2; ///< The index of the other operand + + for (unsigned li = 0; li < _components[w].size(); ++li) { // Traverse all paths in the component + IntersectionList &xl = _components[w][li].xlist; + for (ILIter i = xl.begin(); i != xl.end(); ++i) { // Traverse all intersections in the path + ILIter n = cyclic_next(i, xl); + std::size_t pi = i->pos.path_index; + + /// Path time interval from the current crossing to the next one + 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; + } + } + } + } +} + +/** Detect the situation where a path is either entirely inside or entirely outside of the other + * path-vector and set the status flag accordingly. + */ +void PathIntersectionGraph::_assignComponentStatusFromDegenerateIntersections() +{ + for (auto & _component : _components) { + for (unsigned li = 0; li < _component.size(); ++li) { + IntersectionList &xl = _component[li].xlist; + bool has_in = false; + bool has_out = false; + for (auto & i : xl) { + has_in |= (i.next_edge == INSIDE); + has_out |= (i.next_edge == OUTSIDE); + } + if (has_in && !has_out) { + _component[li].status = INSIDE; + } + if (!has_in && has_out) { + _component[li].status = OUTSIDE; + } + } + } +} + +/** Remove intersections that don't change between in/out. + * + * In general, a degenerate intersection can happen at a point where + * two shapes "kiss" (are tangent) but do not cross into each other. + */ +void PathIntersectionGraph::_removeDegenerateIntersections() +{ + for (auto & _component : _components) { + for (unsigned li = 0; li < _component.size(); ++li) { + IntersectionList &xl = _component[li].xlist; + for (ILIter i = xl.begin(); i != xl.end();) { + ILIter n = cyclic_next(i, xl); + if (i->next_edge == n->next_edge) { // Both edges inside or both outside + bool last_node = (i == n); ///< Whether this is the last remaining crossing. + 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. + if (cyclic_prior(nn, oxl)->next_edge != nn->next_edge) { + // Not a backtrack - set the defective flag. + _graph_valid = false; + n->defective = true; + nn->defective = true; + ++i; + continue; + } + // Erase the degenerate or defective crossings + oxl.erase(nn); + xl.erase(n); + if (last_node) break; + } else { + ++i; + } + } + } + } +} + +/** Verify that all paths contain an even number of intersections and that + * the intersection graph does not contain leaves (degree one vertices). + */ +void PathIntersectionGraph::_verify() +{ +#ifndef NDEBUG + for (auto & _component : _components) { + for (unsigned li = 0; li < _component.size(); ++li) { + IntersectionList &xl = _component[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); + } + } + } +#endif +} + +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; + + for (std::size_t i = 0; i < _components[0].size(); ++i) { + for (const auto & j : _components[0][i].xlist) { + 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); + } + } + } + } +} + +/** @brief Compute the partial result of a boolean operation by looking at components containing + * intersections and stitching the correct path portions between them, depending on the truth + * table of the operation. + * + * @param enter_a – whether the path portions contained inside operand A should be part of the boundary + * of the boolean operation's result. + * @param enter_b – whether the path portions contained inside operand B should be part of the boundary + * of the boolean operation's result. + * + * These two flags completely determine how to resolve the crossings when building the result + * and therefore encode which boolean operation we are performing. For example, the boolean intersection + * corresponds to enter_a == true and enter_b == true, as can be seen by looking at a Venn diagram. + */ +PathVector PathIntersectionGraph::_getResult(bool enter_a, bool enter_b) +{ + PathVector result; + if (_xs.empty()) return result; + + // Create the list of intersections to process + _ulist.clear(); + for (auto & _component : _components) { + for (auto & li : _component) { + for (auto & k : li.xlist) { + _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); + bool reverse = false; ///< Whether to traverse the current component in the backwards direction. + while (i->_proc_hook.is_linked()) { + ILIter prev = i; + std::size_t pi = i->pos.path_index; ///< Index of the path in its PathVector + // 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 + if (w == 0) { // The path we're on is a part of A + reverse = (i->next_edge == INSIDE) ^ enter_a; + } else { // The path we're on is a part of B + 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 to the result + 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); + + // count both vertices as processed + 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); + if (reverse){ + result.back() = result.back().reversed(); + } + if (result.back().empty()) { + // std::cerr << "Path is empty" << std::endl; + throw GEOM_ERR_INTERSECGRAPH; + } + } + + if (n_processed != size() * 2) { + // std::cerr << "Processed " << n_processed << " intersections, expected " << (size() * 2) << std::endl; + throw GEOM_ERR_INTERSECGRAPH; + } + + return result; +} + +/** @brief Select intersection-free path components ahead of a boolean operation based on whether + * they should be a part of that operation's result. + * + * Every component that has 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 path-vector. + * + * @param result – output parameter to store the selected components. + * @param which – which of the two operands to search for intersection-free paths. + * @param inside – If set to true, add paths entirely contained inside the other path-vector to + * the result. If set to false, add paths entirely outside of the other path-vector instead. + */ +void PathIntersectionGraph::_handleNonintersectingPaths(PathVector &result, unsigned which, bool inside) +{ + 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 status flag set in the 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 { + // The status flag is ambiguous: we evaluate the winding number of the initial point. + int wdg = _pv[ow].winding(_pv[w][i].initialPoint()); + path_inside = wdg % 2 != 0; + } + + if (path_inside == inside) { + result.push_back(_pv[w][i]); + } + } +} + +/** @brief Get an iterator to the corresponding crossing on the other path-vector. + * + * @param ILIter – an iterator to a list of intersections in one of the path-vectors. + * @return An iterator to the corresponding intersection in the other path-vector. + */ +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); +} + +/** Get the path data for the path containing the intersection given an iterator to the intersection */ +PathIntersectionGraph::PathData & +PathIntersectionGraph::_getPathData(ILIter iter) +{ + return _components[iter->which][iter->pos.path_index]; +} + +/** Format the PathIntersectionGraph for output. */ +std::ostream &operator<<(std::ostream &os, PathIntersectionGraph const &pig) +{ + 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 (const auto & j : xl) { + 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:fileencoding=utf-8:textwidth=99 : + diff --git a/src/2geom/intervaltree/interval_tree.cc b/src/2geom/intervaltree/interval_tree.cc new file mode 100644 index 0000000..01a222a --- /dev/null +++ b/src/2geom/intervaltree/interval_tree.cc @@ -0,0 +1,799 @@ +#include "interval_tree.h" +#include <stdio.h> +#include <math.h> +#include <assert.h> + +// From Emin Martinian, licenced LGPL and MPL with permission + + +using namespace std; + +// If the symbol CHECK_INTERVAL_TREE_ASSUMPTIONS is defined then the +// code does a lot of extra checking to make sure certain assumptions +// are satisfied. This only needs to be done if you suspect bugs are +// present or if you make significant changes and want to make sure +// your changes didn't mess anything up. +//#define CHECK_INTERVAL_TREE_ASSUMPTIONS 1 + + +const int MIN_INT=-MAX_INT; + +IntervalTreeNode::IntervalTreeNode(){} + +IntervalTreeNode::IntervalTreeNode(Interval * newInterval) + : storedInterval (newInterval) , + key(newInterval->GetLowPoint()), + high(newInterval->GetHighPoint()) , + maxHigh(high) { +} +IntervalTreeNode::~IntervalTreeNode(){} +Interval::Interval(){} +Interval::~Interval(){} +void Interval::Print() const { + cout << "No Print Method defined for instance of Interval" << endl; +} + +IntervalTree::IntervalTree() +{ + nil = new IntervalTreeNode; + nil->left = nil->right = nil->parent = nil; + nil->red = 0; + nil->key = nil->high = nil->maxHigh = MIN_INT; + nil->storedInterval = NULL; + + root = new IntervalTreeNode; + root->parent = root->left = root->right = nil; + root->key = root->high = root->maxHigh = MAX_INT; + root->red=0; + root->storedInterval = NULL; + + /* the following are used for the Enumerate function */ + recursionNodeStackSize = 8; // the tree depth is approximately lg(n), this is a 256 element tree. The code will adapt to deeper trees, but this saves considerable space for small trees. + recursionNodeStack = new it_recursion_node[recursionNodeStackSize]; + recursionNodeStackTop = 1; + recursionNodeStack[0].start_node = NULL; + +} + +/***********************************************************************/ +/* FUNCTION: LeftRotate */ +/**/ +/* INPUTS: the node to rotate on */ +/**/ +/* OUTPUT: None */ +/**/ +/* Modifies Input: this, x */ +/**/ +/* EFFECTS: Rotates as described in _Introduction_To_Algorithms by */ +/* Cormen, Leiserson, Rivest (Chapter 14). Basically this */ +/* makes the parent of x be to the left of x, x the parent of */ +/* its parent before the rotation and fixes other pointers */ +/* accordingly. Also updates the maxHigh fields of x and y */ +/* after rotation. */ +/***********************************************************************/ + +void IntervalTree::LeftRotate(IntervalTreeNode* x) { + IntervalTreeNode* y; + + /* I originally wrote this function to use the sentinel for */ + /* nil to avoid checking for nil. However this introduces a */ + /* very subtle bug because sometimes this function modifies */ + /* the parent pointer of nil. This can be a problem if a */ + /* function which calls LeftRotate also uses the nil sentinel */ + /* and expects the nil sentinel's parent pointer to be unchanged */ + /* after calling this function. For example, when DeleteFixUP */ + /* calls LeftRotate it expects the parent pointer of nil to be */ + /* unchanged. */ + + y=x->right; + x->right=y->left; + + if (y->left != nil) y->left->parent=x; /* used to use sentinel here */ + /* and do an unconditional assignment instead of testing for nil */ + + y->parent=x->parent; + + /* instead of checking if x->parent is the root as in the book, we */ + /* count on the root sentinel to implicitly take care of this case */ + if( x == x->parent->left) { + x->parent->left=y; + } else { + x->parent->right=y; + } + y->left=x; + x->parent=y; + + x->maxHigh=std::max(x->left->maxHigh,std::max(x->right->maxHigh,x->high)); + y->maxHigh=std::max(x->maxHigh,std::max(y->right->maxHigh,y->high)); +#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS + CheckAssumptions(); +#elif defined(DEBUG_ASSERT) + assert(!nil->red,"nil not red in ITLeftRotate"); + assert((nil->maxHigh=MIN_INT), + "nil->maxHigh != MIN_INT in ITLeftRotate"); +#endif +} + + +/***********************************************************************/ +/* FUNCTION: RighttRotate */ +/**/ +/* INPUTS: node to rotate on */ +/**/ +/* OUTPUT: None */ +/**/ +/* Modifies Input?: this, y */ +/**/ +/* EFFECTS: Rotates as described in _Introduction_To_Algorithms by */ +/* Cormen, Leiserson, Rivest (Chapter 14). Basically this */ +/* makes the parent of x be to the left of x, x the parent of */ +/* its parent before the rotation and fixes other pointers */ +/* accordingly. Also updates the maxHigh fields of x and y */ +/* after rotation. */ +/***********************************************************************/ + + +void IntervalTree::RightRotate(IntervalTreeNode* y) { + IntervalTreeNode* x; + + /* I originally wrote this function to use the sentinel for */ + /* nil to avoid checking for nil. However this introduces a */ + /* very subtle bug because sometimes this function modifies */ + /* the parent pointer of nil. This can be a problem if a */ + /* function which calls LeftRotate also uses the nil sentinel */ + /* and expects the nil sentinel's parent pointer to be unchanged */ + /* after calling this function. For example, when DeleteFixUP */ + /* calls LeftRotate it expects the parent pointer of nil to be */ + /* unchanged. */ + + x=y->left; + y->left=x->right; + + if (nil != x->right) x->right->parent=y; /*used to use sentinel here */ + /* and do an unconditional assignment instead of testing for nil */ + + /* instead of checking if x->parent is the root as in the book, we */ + /* count on the root sentinel to implicitly take care of this case */ + x->parent=y->parent; + if( y == y->parent->left) { + y->parent->left=x; + } else { + y->parent->right=x; + } + x->right=y; + y->parent=x; + + y->maxHigh=std::max(y->left->maxHigh,std::max(y->right->maxHigh,y->high)); + x->maxHigh=std::max(x->left->maxHigh,std::max(y->maxHigh,x->high)); +#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS + CheckAssumptions(); +#elif defined(DEBUG_ASSERT) + assert(!nil->red,"nil not red in ITRightRotate"); + assert((nil->maxHigh=MIN_INT), + "nil->maxHigh != MIN_INT in ITRightRotate"); +#endif +} + +/***********************************************************************/ +/* FUNCTION: TreeInsertHelp */ +/**/ +/* INPUTS: z is the node to insert */ +/**/ +/* OUTPUT: none */ +/**/ +/* Modifies Input: this, z */ +/**/ +/* EFFECTS: Inserts z into the tree as if it were a regular binary tree */ +/* using the algorithm described in _Introduction_To_Algorithms_ */ +/* by Cormen et al. This function is only intended to be called */ +/* by the InsertTree function and not by the user */ +/***********************************************************************/ + +void IntervalTree::TreeInsertHelp(IntervalTreeNode* z) { + /* This function should only be called by InsertITTree (see above) */ + IntervalTreeNode* x; + IntervalTreeNode* y; + + z->left=z->right=nil; + y=root; + x=root->left; + while( x != nil) { + y=x; + if ( x->key > z->key) { + x=x->left; + } else { /* x->key <= z->key */ + x=x->right; + } + } + z->parent=y; + if ( (y == root) || + (y->key > z->key) ) { + y->left=z; + } else { + y->right=z; + } + + +#if defined(DEBUG_ASSERT) + assert(!nil->red,"nil not red in ITTreeInsertHelp"); + assert((nil->maxHigh=MIN_INT), + "nil->maxHigh != MIN_INT in ITTreeInsertHelp"); +#endif +} + + +/***********************************************************************/ +/* FUNCTION: FixUpMaxHigh */ +/**/ +/* INPUTS: x is the node to start from*/ +/**/ +/* OUTPUT: none */ +/**/ +/* Modifies Input: this */ +/**/ +/* EFFECTS: Travels up to the root fixing the maxHigh fields after */ +/* an insertion or deletion */ +/***********************************************************************/ + +void IntervalTree::FixUpMaxHigh(IntervalTreeNode * x) { + while(x != root) { + x->maxHigh=std::max(x->high,std::max(x->left->maxHigh,x->right->maxHigh)); + x=x->parent; + } +#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS + CheckAssumptions(); +#endif +} + +/* Before calling InsertNode the node x should have its key set */ + +/***********************************************************************/ +/* FUNCTION: InsertNode */ +/**/ +/* INPUTS: newInterval is the interval to insert*/ +/**/ +/* OUTPUT: This function returns a pointer to the newly inserted node */ +/* which is guaranteed to be valid until this node is deleted. */ +/* What this means is if another data structure stores this */ +/* pointer then the tree does not need to be searched when this */ +/* is to be deleted. */ +/**/ +/* Modifies Input: tree */ +/**/ +/* EFFECTS: Creates a node node which contains the appropriate key and */ +/* info pointers and inserts it into the tree. */ +/***********************************************************************/ + +IntervalTreeNode * IntervalTree::Insert(Interval * newInterval) +{ + IntervalTreeNode * y; + IntervalTreeNode * x; + IntervalTreeNode * newNode; + + x = new IntervalTreeNode(newInterval); + TreeInsertHelp(x); + FixUpMaxHigh(x->parent); + newNode = x; + x->red=1; + while(x->parent->red) { /* use sentinel instead of checking for root */ + if (x->parent == x->parent->parent->left) { + y=x->parent->parent->right; + if (y->red) { + x->parent->red=0; + y->red=0; + x->parent->parent->red=1; + x=x->parent->parent; + } else { + if (x == x->parent->right) { + x=x->parent; + LeftRotate(x); + } + x->parent->red=0; + x->parent->parent->red=1; + RightRotate(x->parent->parent); + } + } else { /* case for x->parent == x->parent->parent->right */ + /* this part is just like the section above with */ + /* left and right interchanged */ + y=x->parent->parent->left; + if (y->red) { + x->parent->red=0; + y->red=0; + x->parent->parent->red=1; + x=x->parent->parent; + } else { + if (x == x->parent->left) { + x=x->parent; + RightRotate(x); + } + x->parent->red=0; + x->parent->parent->red=1; + LeftRotate(x->parent->parent); + } + } + } + root->left->red=0; + return(newNode); + +#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS + CheckAssumptions(); +#elif defined(DEBUG_ASSERT) + assert(!nil->red,"nil not red in ITTreeInsert"); + assert(!root->red,"root not red in ITTreeInsert"); + assert((nil->maxHigh=MIN_INT), + "nil->maxHigh != MIN_INT in ITTreeInsert"); +#endif +} + +/***********************************************************************/ +/* FUNCTION: GetSuccessorOf */ +/**/ +/* INPUTS: x is the node we want the successor of */ +/**/ +/* OUTPUT: This function returns the successor of x or NULL if no */ +/* successor exists. */ +/**/ +/* Modifies Input: none */ +/**/ +/* Note: uses the algorithm in _Introduction_To_Algorithms_ */ +/***********************************************************************/ + +IntervalTreeNode * IntervalTree::GetSuccessorOf(IntervalTreeNode * x) const +{ + IntervalTreeNode* y; + + if (nil != (y = x->right)) { /* assignment to y is intentional */ + while(y->left != nil) { /* returns the minimum of the right subtree of x */ + y=y->left; + } + return(y); + } else { + y=x->parent; + while(x == y->right) { /* sentinel used instead of checking for nil */ + x=y; + y=y->parent; + } + if (y == root) return(nil); + return(y); + } +} + +/***********************************************************************/ +/* FUNCTION: GetPredecessorOf */ +/**/ +/* INPUTS: x is the node to get predecessor of */ +/**/ +/* OUTPUT: This function returns the predecessor of x or NULL if no */ +/* predecessor exists. */ +/**/ +/* Modifies Input: none */ +/**/ +/* Note: uses the algorithm in _Introduction_To_Algorithms_ */ +/***********************************************************************/ + +IntervalTreeNode * IntervalTree::GetPredecessorOf(IntervalTreeNode * x) const { + IntervalTreeNode* y; + + if (nil != (y = x->left)) { /* assignment to y is intentional */ + while(y->right != nil) { /* returns the maximum of the left subtree of x */ + y=y->right; + } + return(y); + } else { + y=x->parent; + while(x == y->left) { + if (y == root) return(nil); + x=y; + y=y->parent; + } + return(y); + } +} + +/***********************************************************************/ +/* FUNCTION: Print */ +/**/ +/* INPUTS: none */ +/**/ +/* OUTPUT: none */ +/**/ +/* EFFECTS: This function recursively prints the nodes of the tree */ +/* inorder. */ +/**/ +/* Modifies Input: none */ +/**/ +/* Note: This function should only be called from ITTreePrint */ +/***********************************************************************/ + +void IntervalTreeNode::Print(IntervalTreeNode * nil, + IntervalTreeNode * root) const { + storedInterval->Print(); + printf(", k=%i, h=%i, mH=%i",key,high,maxHigh); + printf(" l->key="); + if( left == nil) printf("NULL"); else printf("%i",left->key); + printf(" r->key="); + if( right == nil) printf("NULL"); else printf("%i",right->key); + printf(" p->key="); + if( parent == root) printf("NULL"); else printf("%i",parent->key); + printf(" red=%i\n",red); +} + +void IntervalTree::TreePrintHelper( IntervalTreeNode* x) const { + + if (x != nil) { + TreePrintHelper(x->left); + x->Print(nil,root); + TreePrintHelper(x->right); + } +} + +IntervalTree::~IntervalTree() { + IntervalTreeNode * x = root->left; + vector<IntervalTreeNode *> stuffToFree; + + if (x != nil) { + if (x->left != nil) { + stuffToFree.push_back(x->left); + } + if (x->right != nil) { + stuffToFree.push_back(x->right); + } + // delete x->storedInterval; + delete x; + while(! stuffToFree.empty() ) { + x = stuffToFree.back(); + stuffToFree.pop_back(); + if (x->left != nil) { + stuffToFree.push_back(x->left); + } + if (x->right != nil) { + stuffToFree.push_back(x->right); + } + // delete x->storedInterval; + delete x; + } + } + delete nil; + delete root; + delete[] recursionNodeStack; +} + + +/***********************************************************************/ +/* FUNCTION: Print */ +/**/ +/* INPUTS: none */ +/**/ +/* OUTPUT: none */ +/**/ +/* EFFECT: This function recursively prints the nodes of the tree */ +/* inorder. */ +/**/ +/* Modifies Input: none */ +/**/ +/***********************************************************************/ + +void IntervalTree::Print() const { + TreePrintHelper(root->left); +} + +/***********************************************************************/ +/* FUNCTION: DeleteFixUp */ +/**/ +/* INPUTS: x is the child of the spliced */ +/* out node in DeleteNode. */ +/**/ +/* OUTPUT: none */ +/**/ +/* EFFECT: Performs rotations and changes colors to restore red-black */ +/* properties after a node is deleted */ +/**/ +/* Modifies Input: this, x */ +/**/ +/* The algorithm from this function is from _Introduction_To_Algorithms_ */ +/***********************************************************************/ + +void IntervalTree::DeleteFixUp(IntervalTreeNode* x) { + IntervalTreeNode * w; + IntervalTreeNode * rootLeft = root->left; + + while( (!x->red) && (rootLeft != x)) { + if (x == x->parent->left) { + w=x->parent->right; + if (w->red) { + w->red=0; + x->parent->red=1; + LeftRotate(x->parent); + w=x->parent->right; + } + if ( (!w->right->red) && (!w->left->red) ) { + w->red=1; + x=x->parent; + } else { + if (!w->right->red) { + w->left->red=0; + w->red=1; + RightRotate(w); + w=x->parent->right; + } + w->red=x->parent->red; + x->parent->red=0; + w->right->red=0; + LeftRotate(x->parent); + x=rootLeft; /* this is to exit while loop */ + } + } else { /* the code below is has left and right switched from above */ + w=x->parent->left; + if (w->red) { + w->red=0; + x->parent->red=1; + RightRotate(x->parent); + w=x->parent->left; + } + if ( (!w->right->red) && (!w->left->red) ) { + w->red=1; + x=x->parent; + } else { + if (!w->left->red) { + w->right->red=0; + w->red=1; + LeftRotate(w); + w=x->parent->left; + } + w->red=x->parent->red; + x->parent->red=0; + w->left->red=0; + RightRotate(x->parent); + x=rootLeft; /* this is to exit while loop */ + } + } + } + x->red=0; + +#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS + CheckAssumptions(); +#elif defined(DEBUG_ASSERT) + assert(!nil->red,"nil not black in ITDeleteFixUp"); + assert((nil->maxHigh=MIN_INT), + "nil->maxHigh != MIN_INT in ITDeleteFixUp"); +#endif +} + + +/***********************************************************************/ +/* FUNCTION: DeleteNode */ +/**/ +/* INPUTS: tree is the tree to delete node z from */ +/**/ +/* OUTPUT: returns the Interval stored at deleted node */ +/**/ +/* EFFECT: Deletes z from tree and but don't call destructor */ +/* Then calls FixUpMaxHigh to fix maxHigh fields then calls */ +/* ITDeleteFixUp to restore red-black properties */ +/**/ +/* Modifies Input: z */ +/**/ +/* The algorithm from this function is from _Introduction_To_Algorithms_ */ +/***********************************************************************/ + +Interval * IntervalTree::DeleteNode(IntervalTreeNode * z){ + IntervalTreeNode* y; + IntervalTreeNode* x; + Interval * returnValue = z->storedInterval; + + y= ((z->left == nil) || (z->right == nil)) ? z : GetSuccessorOf(z); + x= (y->left == nil) ? y->right : y->left; + if (root == (x->parent = y->parent)) { /* assignment of y->p to x->p is intentional */ + root->left=x; + } else { + if (y == y->parent->left) { + y->parent->left=x; + } else { + y->parent->right=x; + } + } + if (y != z) { /* y should not be nil in this case */ + +#ifdef DEBUG_ASSERT + assert( (y!=nil),"y is nil in DeleteNode \n"); +#endif + /* y is the node to splice out and x is its child */ + + y->maxHigh = MIN_INT; + y->left=z->left; + y->right=z->right; + y->parent=z->parent; + z->left->parent=z->right->parent=y; + if (z == z->parent->left) { + z->parent->left=y; + } else { + z->parent->right=y; + } + FixUpMaxHigh(x->parent); + if (!(y->red)) { + y->red = z->red; + DeleteFixUp(x); + } else + y->red = z->red; + delete z; +#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS + CheckAssumptions(); +#elif defined(DEBUG_ASSERT) + assert(!nil->red,"nil not black in ITDelete"); + assert((nil->maxHigh=MIN_INT),"nil->maxHigh != MIN_INT in ITDelete"); +#endif + } else { + FixUpMaxHigh(x->parent); + if (!(y->red)) DeleteFixUp(x); + delete y; +#ifdef CHECK_INTERVAL_TREE_ASSUMPTIONS + CheckAssumptions(); +#elif defined(DEBUG_ASSERT) + assert(!nil->red,"nil not black in ITDelete"); + assert((nil->maxHigh=MIN_INT),"nil->maxHigh != MIN_INT in ITDelete"); +#endif + } + return returnValue; +} + + +/***********************************************************************/ +/* FUNCTION: Overlap */ +/**/ +/* INPUTS: [a1,a2] and [b1,b2] are the low and high endpoints of two */ +/* closed intervals. */ +/**/ +/* OUTPUT: stack containing pointers to the nodes between [low,high] */ +/**/ +/* Modifies Input: none */ +/**/ +/* EFFECT: returns 1 if the intervals overlap, and 0 otherwise */ +/***********************************************************************/ + +int Overlap(int a1, int a2, int b1, int b2) { + if (a1 <= b1) { + return( (b1 <= a2) ); + } else { + return( (a1 <= b2) ); + } +} + + +/***********************************************************************/ +/* FUNCTION: Enumerate */ +/**/ +/* INPUTS: tree is the tree to look for intervals overlapping the */ +/* closed interval [low,high] */ +/**/ +/* OUTPUT: stack containing pointers to the nodes overlapping */ +/* [low,high] */ +/**/ +/* Modifies Input: none */ +/**/ +/* EFFECT: Returns a stack containing pointers to nodes containing */ +/* intervals which overlap [low,high] in O(max(N,k*log(N))) */ +/* where N is the number of intervals in the tree and k is */ +/* the number of overlapping intervals */ +/**/ +/* Note: This basic idea for this function comes from the */ +/* _Introduction_To_Algorithms_ book by Cormen et al, but */ +/* modifications were made to return all overlapping intervals */ +/* instead of just the first overlapping interval as in the */ +/* book. The natural way to do this would require recursive */ +/* calls of a basic search function. I translated the */ +/* recursive version into an interative version with a stack */ +/* as described below. */ +/***********************************************************************/ + + + +/* The basic idea for the function below is to take the IntervalSearch */ +/* function from the book and modify to find all overlapping intervals */ +/* instead of just one. This means that any time we take the left */ +/* branch down the tree we must also check the right branch if and only if */ +/* we find an overlapping interval in that left branch. Note this is a */ +/* recursive condition because if we go left at the root then go left */ +/* again at the first left child and find an overlap in the left subtree */ +/* of the left child of root we must recursively check the right subtree */ +/* of the left child of root as well as the right child of root. */ + +vector<void *> IntervalTree::Enumerate(int low, int high) { + vector<void *> enumResultStack; + IntervalTreeNode* x=root->left; + int stuffToDo = (x != nil); + + // Possible speed up: add min field to prune right searches // + +#ifdef DEBUG_ASSERT + assert((recursionNodeStackTop == 1), + "recursionStack not empty when entering IntervalTree::Enumerate"); +#endif + currentParent = 0; + + while(stuffToDo) { + if (Overlap(low,high,x->key,x->high) ) { + enumResultStack.push_back(x->storedInterval); + recursionNodeStack[currentParent].tryRightBranch=true; + } + if(x->left->maxHigh >= low) { // implies x != nil + if ( recursionNodeStackTop == recursionNodeStackSize ) { + recursionNodeStackSize *= 2; + recursionNodeStack = (it_recursion_node *) + realloc(recursionNodeStack, + recursionNodeStackSize * sizeof(it_recursion_node)); + if (recursionNodeStack == NULL) + assert("realloc failed in IntervalTree::Enumerate\n"); + } + recursionNodeStack[recursionNodeStackTop].start_node = x; + recursionNodeStack[recursionNodeStackTop].tryRightBranch = 0; + recursionNodeStack[recursionNodeStackTop].parentIndex = currentParent; + currentParent = recursionNodeStackTop++; + x = x->left; + } else { + x = x->right; + } + stuffToDo = (x != nil); + while( (!stuffToDo) && (recursionNodeStackTop > 1) ) { + if(recursionNodeStack[--recursionNodeStackTop].tryRightBranch) { + x=recursionNodeStack[recursionNodeStackTop].start_node->right; + currentParent=recursionNodeStack[recursionNodeStackTop].parentIndex; + recursionNodeStack[currentParent].tryRightBranch=true; + stuffToDo = ( x != nil); + } + } + } +#ifdef DEBUG_ASSERT + assert((recursionNodeStackTop == 1), + "recursionStack not empty when exiting IntervalTree::Enumerate"); +#endif + return enumResultStack; +} + + + +int IntervalTree::CheckMaxHighFieldsHelper(IntervalTreeNode * y, + const int currentHigh, + int match) const +{ + if (y != nil) { + match = CheckMaxHighFieldsHelper(y->left,currentHigh,match) ? + 1 : match; + assert(y->high <= currentHigh); + if (y->high == currentHigh) + match = 1; + match = CheckMaxHighFieldsHelper(y->right,currentHigh,match) ? + 1 : match; + } + return match; +} + + + +/* Make sure the maxHigh fields for everything makes sense. * + * If something is wrong, print a warning and exit */ +void IntervalTree::CheckMaxHighFields(IntervalTreeNode * x) const { + if (x != nil) { + CheckMaxHighFields(x->left); + if(!(CheckMaxHighFieldsHelper(x,x->maxHigh,0) > 0)) { + assert("error found in CheckMaxHighFields.\n"); + } + CheckMaxHighFields(x->right); + } +} + +void IntervalTree::CheckAssumptions() const { + assert(nil->key == MIN_INT); + assert(nil->high == MIN_INT); + assert(nil->maxHigh == MIN_INT); + assert(root->key == MAX_INT); + assert(root->high == MAX_INT); + assert(root->maxHigh == MAX_INT); + assert(nil->storedInterval == NULL); + assert(root->storedInterval == NULL); + assert(nil->red == 0); + assert(root->red == 0); + CheckMaxHighFields(root->left); +} + + + diff --git a/src/2geom/intervaltree/test2.cc b/src/2geom/intervaltree/test2.cc new file mode 100644 index 0000000..bce448e --- /dev/null +++ b/src/2geom/intervaltree/test2.cc @@ -0,0 +1,74 @@ +#include <iostream> + +// Nathan Hurst and Emin Martinian, licenced LGPL and MPL with permission + + +#include "interval_tree.h" + +class SimpleInterval : public Interval { +public: + SimpleInterval() : + _low(0), + _high(0), + _node(NULL) + {} + SimpleInterval(const int low,const int high) + :_low(low), + _high(high), + _node(NULL) + { } + + int GetLowPoint() const { return _low;} + int GetHighPoint() const { return _high;} + IntervalTreeNode * GetNode() { return _node;} + void SetNode(IntervalTreeNode * node) {_node = node;} + virtual void Print() const { + printf("(%d, %d)", _low, _high); + } +protected: + int _low; + int _high; + IntervalTreeNode * _node; + +}; + +using namespace std; + +#include <stdlib.h> +#include <time.h> + +int main() { + const int N = 1L<<24; + SimpleInterval *x = new SimpleInterval[N]; + for(int i = 0; i < N; i++) { + x[i] = SimpleInterval(random(), random()); + } + + cout << "sizeof(SimpleInterval)" << sizeof(SimpleInterval) << endl; + cout << "sizeof(IntervalTreeNode)" << sizeof(IntervalTreeNode) << endl; + cout << "sizeof(it_recursion_node)" << sizeof(it_recursion_node) << endl; + cout << "sizeof(IntervalTree)" << sizeof(IntervalTree) << endl; + + IntervalTree itree; + int onn = 0; + for(int nn = 1; nn < N; nn*=2) { + for(int i = onn; i < nn; i++) { + itree.Insert(&x[i]); + } + onn = nn; + clock_t s = clock(); + + int iters = 0; + int outputs = 0; + while(clock() - s < CLOCKS_PER_SEC/4) { + vector<void *> n = itree.Enumerate(random(), random()) ; + outputs += n.size(); + //cout << n.size() << endl; + iters++; + } + clock_t e = clock(); + double total = double(e - s)/(CLOCKS_PER_SEC); + cout << total << " " << outputs << " " << total/outputs << " " << nn << endl; + } + //itree.Print(); +} diff --git a/src/2geom/line.cpp b/src/2geom/line.cpp new file mode 100644 index 0000000..3db3039 --- /dev/null +++ b/src/2geom/line.cpp @@ -0,0 +1,610 @@ +/* + * 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 <optional> +#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(""); + } +} + +std::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 std::nullopt; + } + } + + 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 std::nullopt; + } + + 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 std::nullopt; + } + + /* 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.emplace_back(*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/nearest-time.cpp b/src/2geom/nearest-time.cpp new file mode 100644 index 0000000..e52251c --- /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 (double i : ts) { + Coord droot = L2sq(bez.valueAt(i)); + if (droot < mind) { + mind = droot; + t = 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 (double zero : zeros) + { + double distsq = L2sq(c(zero) - p); + if ( min_dist_sq > L2sq(c(zero) - p) ) + { + closest = zero; + 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 (double candidate : candidates) { + distsq.push_back(L2sq(c(candidate) - 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 (double & i : all_nearest) + { + i = c.mapToDomain(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 (double & i : all_t) { + i = c.mapToDomain(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/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/orphan-code/arc-length.cpp b/src/2geom/orphan-code/arc-length.cpp new file mode 100644 index 0000000..3f72862 --- /dev/null +++ b/src/2geom/orphan-code/arc-length.cpp @@ -0,0 +1,292 @@ +/* + * arc-length.cpp + * + * 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. + * + */ + +#include <2geom/arc-length.h> +#include <2geom/bezier-utils.h> +#include <2geom/polynomial.h> +using namespace Geom; + +/** Calculates the length of a cubic element through subdivision. + * The 'tol' parameter is the maximum error allowed. This is used to subdivide the curve where necessary. + */ +double cubic_length_subdividing(Path::Elem const & e, double tol) { + Point v[3]; + for(int i = 0; i < 3; i++) + v[i] = e[i+1] - e[0]; + Point orth = v[2]; // unit normal to path line + rot90(orth); + orth.normalize(); + double err = fabs(dot(orth, v[1])) + fabs(dot(orth, v[0])); + if(err < tol) { + return distance(e.first(), e.last()); // approximately a line + } else { + Point mid[3]; + double result; + for(int i = 0; i < 3; i++) + mid[i] = lerp(0.5, e[i], e[i+1]); + Point midmid[2]; + for(int i = 0; i < 2; i++) + midmid[i] = lerp(0.5, mid[i], mid[i+1]); + Point midmidmid = lerp(0.5, midmid[0], midmid[1]); + { + Point curve[4] = {e[0], mid[0], midmid[0], midmidmid}; + Path::Elem e0(cubicto, std::vector<Point>::const_iterator(curve), std::vector<Point>::const_iterator(curve) + 4); + result = cubic_length_subdividing(e0, tol); + } { + Point curve[4] = {midmidmid, midmid[1], mid[2], e[3]}; + Path::Elem e1(cubicto, std::vector<Point>::const_iterator(curve), std::vector<Point>::const_iterator(curve) + 4); + return result + cubic_length_subdividing(e1, tol); + } + } +} + +/** Calculates the length of a path through iteration and subsequent subdivision. + * Currently handles cubic curves and lines. + * The 'tol' parameter is the maximum error allowed. This is used to subdivide the curve where necessary. + */ +double arc_length_subdividing(Path const & p, double tol) { + double result = 0; + + for(Path::const_iterator iter(p.begin()), end(p.end()); iter != end; ++iter) { + if(dynamic_cast<LineTo *>(iter.cmd())) + result += distance((*iter).first(), (*iter).last()); + else if(dynamic_cast<CubicTo *>(iter.cmd())) + result += cubic_length_subdividing(*iter, tol); + else + ; + } + + return result; +} + + +#ifdef HAVE_GSL +#include <gsl/gsl_integration.h> +static double poly_length_integrating(double t, void* param) { + Poly* pc = (Poly*)param; + return hypot(pc[0].eval(t), pc[1].eval(t)); +} + +/** Calculates the length of a path Element through gsl integration. + \param pe the Element. + \param t the parametric input 0 to 1 which specifies the amount of the curve to use. + \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 +*/ +void arc_length_integrating(Path::Elem pe, double t, double tol, double &result, double &abs_error) { + if(dynamic_cast<LineTo *>(iter.cmd())) + result += distance(pe.first(), pe.last()) * t; + else if(dynamic_cast<QuadTo *>(iter.cmd()) || + dynamic_cast<CubicTo *>(iter.cmd())) { + Poly B[2] = {get_parametric_poly(pe, X), get_parametric_poly(pe, Y)}; + for(int i = 0; i < 2; i++) + B[i] = derivative(B[i]); + + gsl_function F; + gsl_integration_workspace * w + = gsl_integration_workspace_alloc (20); + F.function = &poly_length_integrating; + F.params = (void*)B; + double quad_result, err; + /* We could probably use the non adaptive code here if we removed any cusps first. */ + int returncode = + gsl_integration_qag (&F, 0, t, 0, tol, 20, + GSL_INTEG_GAUSS21, w, &quad_result, &err); + + abs_error += err; + result += quad_result; + } else + return; +} + +/** Calculates the length of a Path through gsl integration. The parameter 'tol' is the maximum error allowed. */ +double arc_length_integrating(Path const & p, double tol) { + double result = 0, abserr = 0; + + for(Path::const_iterator iter(p.begin()), end(p.end()); iter != end; ++iter) { + arc_length_integrating(*iter, 1.0, tol, result, abserr); + } + //printf("got %g with err %g\n", result, abserr); + + return result; +} + +/** Calculates the arc length to a specific location on the path. The parameter 'tol' is the maximum error allowed. */ +double arc_length_integrating(Path const & p, Path::Location const & pl, double tol) { + double result = 0, abserr = 0; + ptrdiff_t offset = pl.it - p.begin(); + + assert(offset >= 0); + assert(offset < p.size()); + + for(Path::const_iterator iter(p.begin()), end(p.end()); + (iter != pl.it); ++iter) { + arc_length_integrating(*iter, 1.0, tol, result, abserr); + } + arc_length_integrating(*pl.it, pl.t, tol, result, abserr); + + return result; +} + +/* We use a somewhat surprising result for this that s'(t) = |p'(t)| + Thus, we can use a derivative based root finder. +*/ + +#include <stdio.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_roots.h> + +struct arc_length_params +{ + Path::Elem pe; + double s,tol, result, abs_error; + double left, right; +}; + +double +arc_length (double t, void *params) +{ + struct arc_length_params *p + = (struct arc_length_params *) params; + + double result = 0, abs_error = 0; + if(t < 0) t = 0; + if(t > 1) t = 1; + if(!((t >= 0) && (t <= 1))) { + printf("t = %g\n", t); + } + assert((t >= 0) && (t <= 1)); + arc_length_integrating(p->pe, t, p->tol, result, abs_error); + return result - p->s ; +} + +double +arc_length_deriv (double t, void *params) +{ + struct arc_length_params *p + = (struct arc_length_params *) params; + + Point pos, tgt, acc; + p->pe.point_tangent_acc_at(t, pos, tgt, acc); + return L2(tgt); +} + +void +arc_length_fdf (double t, void *params, + double *y, double *dy) +{ + *y = arc_length(t, params); + *dy = arc_length_deriv(t, params); +} + +double polish_brent(double t, arc_length_params &alp) { + int status; + int iter = 0, max_iter = 10; + const gsl_root_fsolver_type *T; + gsl_root_fsolver *solver; + double x_lo = 0.0, x_hi = 1.0; + gsl_function F; + + F.function = &arc_length; + F.params = &alp; + + T = gsl_root_fsolver_brent; + solver = gsl_root_fsolver_alloc (T); + gsl_root_fsolver_set (solver, &F, x_lo, x_hi); + + do + { + iter++; + status = gsl_root_fsolver_iterate (solver); + t = gsl_root_fsolver_root (solver); + x_lo = gsl_root_fsolver_x_lower (solver); + x_hi = gsl_root_fsolver_x_upper (solver); + status = gsl_root_test_interval (x_lo, x_hi, + 0, alp.tol); + + //if (status == GSL_SUCCESS) + // printf ("Converged:\n"); + + } + while (status == GSL_CONTINUE && iter < max_iter); + return t; +} + +double polish (double t, arc_length_params &alp) { + int status; + int iter = 0, max_iter = 5; + const gsl_root_fdfsolver_type *T; + gsl_root_fdfsolver *solver; + double t0; + gsl_function_fdf FDF; + + FDF.f = &arc_length; + FDF.df = &arc_length_deriv; + FDF.fdf = &arc_length_fdf; + FDF.params = &alp; + + T = gsl_root_fdfsolver_newton; + solver = gsl_root_fdfsolver_alloc (T); + gsl_root_fdfsolver_set (solver, &FDF, t); + + do + { + iter++; + status = gsl_root_fdfsolver_iterate (solver); + t0 = t; + t = gsl_root_fdfsolver_root (solver); + status = gsl_root_test_delta (t, t0, 0, alp.tol); + + if (status == GSL_SUCCESS) + ;//printf ("Converged:\n"); + + printf ("%5d %10.7f %+10.7f\n", + iter, t, t - t0); + } + while (status == GSL_CONTINUE && iter < max_iter); + return 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/orphan-code/chebyshev.cpp b/src/2geom/orphan-code/chebyshev.cpp new file mode 100644 index 0000000..c886daf --- /dev/null +++ b/src/2geom/orphan-code/chebyshev.cpp @@ -0,0 +1,126 @@ +#include <2geom/chebyshev.h> + +#include <2geom/sbasis.h> +#include <2geom/sbasis-poly.h> + +#include <vector> +using std::vector; + +#include <gsl/gsl_math.h> +#include <gsl/gsl_chebyshev.h> + +namespace Geom{ + +SBasis cheb(unsigned n) { + static std::vector<SBasis> basis; + if(basis.empty()) { + basis.push_back(Linear(1,1)); + basis.push_back(Linear(0,1)); + } + for(unsigned i = basis.size(); i <= n; i++) { + basis.push_back(Linear(0,2)*basis[i-1] - basis[i-2]); + } + + return basis[n]; +} + +SBasis cheb_series(unsigned n, double* cheb_coeff) { + SBasis r; + for(unsigned i = 0; i < n; i++) { + double cof = cheb_coeff[i]; + //if(i == 0) + //cof /= 2; + r += cheb(i)*cof; + } + + return r; +} + +SBasis clenshaw_series(unsigned m, double* cheb_coeff) { + /** b_n = a_n + b_n-1 = 2*x*b_n + a_n-1 + b_n-k = 2*x*b_{n-k+1} + a_{n-k} - b_{n - k + 2} + b_0 = x*b_1 + a_0 - b_2 + */ + + double a = -1, b = 1; + SBasis d, dd; + SBasis y = (Linear(0, 2) - (a+b)) / (b-a); + SBasis y2 = 2*y; + for(int j = m - 1; j >= 1; j--) { + SBasis sv = d; + d = y2*d - dd + cheb_coeff[j]; + dd = sv; + } + + return y*d - dd + 0.5*cheb_coeff[0]; +} + +SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p) { + gsl_cheb_series *cs = gsl_cheb_alloc (order+2); + + gsl_function F; + + F.function = f; + F.params = p; + + gsl_cheb_init (cs, &F, in[0], in[1]); + + SBasis r = compose(clenshaw_series(order, cs->c), Linear(-1,1)); + + gsl_cheb_free (cs); + return r; +} + +struct wrap { + double (*f)(double,void*); + void* pp; + double fa, fb; + Interval in; +}; + +double f_interp(double x, void* p) { + struct wrap *wr = (struct wrap *)p; + double z = (x - wr->in[0]) / (wr->in[1] - wr->in[0]); + return (wr->f)(x, wr->pp) - ((1 - z)*wr->fa + z*wr->fb); +} + +SBasis chebyshev_approximant_interpolating (double (*f)(double,void*), + int order, Interval in, void* p) { + double fa = f(in[0], p); + double fb = f(in[1], p); + struct wrap wr; + wr.fa = fa; + wr.fb = fb; + wr.in = in; + printf("%f %f\n", fa, fb); + wr.f = f; + wr.pp = p; + return compose(Linear(in[0], in[1]), Linear(fa, fb)) + chebyshev_approximant(f_interp, order, in, &wr) + Linear(fa, fb); +} + +SBasis chebyshev(unsigned n) { + static std::vector<SBasis> basis; + if(basis.empty()) { + basis.push_back(Linear(1,1)); + basis.push_back(Linear(0,1)); + } + for(unsigned i = basis.size(); i <= n; i++) { + basis.push_back(Linear(0,2)*basis[i-1] - basis[i-2]); + } + + return basis[n]; +} + +}; + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/2geom/orphan-code/intersection-by-bezier-clipping.cpp b/src/2geom/orphan-code/intersection-by-bezier-clipping.cpp new file mode 100644 index 0000000..c55f623 --- /dev/null +++ b/src/2geom/orphan-code/intersection-by-bezier-clipping.cpp @@ -0,0 +1,560 @@ + +/* + * Find intersecions between two Bezier curves. + * The intersection points are found by using Bezier clipping. + * + * 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/bezier.h> +#include <2geom/interval.h> +#include <2geom/convex-hull.h> + + +#include <vector> +#include <utility> +#include <iomanip> + + +namespace Geom { + +namespace detail { namespace bezier_clipping { + + +//////////////////////////////////////////////////////////////////////////////// +// for debugging +// + +inline +void print(std::vector<Point> const& cp) +{ + for (size_t i = 0; i < cp.size(); ++i) + std::cerr << i << " : " << cp[i] << std::endl; +} + +template< class charT > +inline +std::basic_ostream<charT> & +operator<< (std::basic_ostream<charT> & os, const Interval & I) +{ + os << "[" << I.min() << ", " << I.max() << "]"; + return os; +} + +inline +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); +} + +inline +size_t get_precision(Interval const& I) +{ + double d = I.extent(); + double e = 1, p = 1; + size_t n = 0; + while (n < 16 && (are_near(d, 0, e))) + { + p *= 10; + e = 1 /p; + ++n; + } + return n; +} + +//////////////////////////////////////////////////////////////////////////////// + + +/* + * return true if all the Bezier curve control points are near, + * false otherwise + */ +inline +bool is_constant(std::vector<Point> const& A, double precision = EPSILON) +{ + for (unsigned int i = 1; i < A.size(); ++i) + { + if(!are_near(A[i], A[0], precision)) + return false; + } + return true; +} + +/* + * 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. + */ +inline +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[i], c[j]); + 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" + */ +inline +void pick_orientation_line (std::vector<double> & l, + std::vector<Point> const& c) +{ + size_t i = c.size(); + while (--i > 0 && are_near(c[0], c[i])) + {} + if (i == 0) + { + // 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); + } + orientation_line(l, c, 0, i); + //std::cerr << "i = " << i << std::endl; +} + +/* + * Compute the signed distance of the point "P" from the normalized line l + */ +inline +double distance (Point const& P, std::vector<double> const& l) +{ + return l[X] * P[X] + l[Y] * P[Y] + l[2]; +} + +/* + * 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". + */ +inline +void fat_line_bounds (Interval& bound, + std::vector<Point> const& c, + std::vector<double> const& l) +{ + bound[0] = 0; + bound[1] = 0; + double d; + for (size_t i = 0; i < c.size(); ++i) + { + d = distance(c[i], l); + if (bound[0] > d) bound[0] = d; + if (bound[1] < d) bound[1] = d; + } +} + +/* + * return the x component of the intersection point between the line + * passing through points p1, p2 and the line Y = "y" + */ +inline +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" + */ +void clip (Interval& dom, + std::vector<Point> const& B, + std::vector<double> 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()); + double d; + for (size_t i = 0; i < B.size(); ++i) + { + d = distance (B[i], l); + D.push_back (Point(i/n, d)); + } + //print(D); + ConvexHull chD(D); + std::vector<Point> & p = chD.boundary; // convex hull vertices + + //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; + } + + dom[0] = tmin; + dom[1] = tmax; +} + +/* + * Compute the portion of the Bezier curve "B" wrt the interval "I" + */ +void portion (std::vector<Point> & B, Interval const& I) +{ + Bezier::Order bo(B.size()-1); + Bezier Bx(bo), By(bo); + for (size_t i = 0; i < B.size(); ++i) + { + Bx[i] = B[i][X]; + By[i] = B[i][Y]; + } + Bx = portion(Bx, I.min(), I.max()); + By = portion(By, I.min(), I.max()); + assert (Bx.size() == By.size()); + B.resize(Bx.size()); + for (size_t i = 0; i < Bx.size(); ++i) + { + B[i][X] = Bx[i]; + B[i][Y] = By[i]; + } +} + +/* + * Map the sub-interval I in [0,1] into the interval J and assign it to J + */ +inline +void map_to(Interval & J, Interval const& I) +{ + double length = J.extent(); + J[1] = I.max() * length + J[0]; + J[0] = I.min() * length + J[0]; +} + +/* + * The interval [1,0] is used to represent the empty interval, this routine + * is just an helper function for creating such an interval + */ +inline +Interval make_empty_interval() +{ + Interval I(0); + I[0] = 1; + return I; +} + + + + +const double MAX_PRECISION = 1e-8; +const double MIN_CLIPPED_SIZE_THRESHOLD = 0.8; +const Interval UNIT_INTERVAL(0,1); +const Interval EMPTY_INTERVAL = make_empty_interval(); +const Interval H1_INTERVAL(0, 0.5); +const Interval H2_INTERVAL(0.5 + MAX_PRECISION, 1.0); + +/* + * intersection + * + * 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 describing an intersection point + * + * 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. + */ +void intersection (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) +{ +// 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; + + + 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; + + std::vector<double> bl(3); + Interval bound, dom; + + + size_t iter = 0; + while (++iter < 100 + && (dompA.extent() >= precision || dompB.extent() >= precision)) + { +// std::cerr << "iter: " << iter << std::endl; + + pick_orientation_line(bl, *C1); + fat_line_bounds(bound, *C1, bl); + clip(dom, *C2, bl, bound); + + // [1,0] is utilized to represent an empty interval + if (dom == EMPTY_INTERVAL) + { +// std::cerr << "dom: empty" << std::endl; + return; + } +// std::cerr << "dom : " << dom << std::endl; + + // all other cases where dom[0] > dom[1] are invalid + if (dom.min() > dom.max()) + { + assert(dom.min() < dom.max()); + } + + map_to(*dom2, dom); + + // it's better to stop before losing computational precision + if (dom2->extent() <= MAX_PRECISION) + { +// std::cerr << "beyond max precision limit" << std::endl; + break; + } + + portion(*C2, dom); + if (is_constant(*C2)) + { +// std::cerr << "new curve portion is constant" << std::endl; + 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) + { +// 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; + + 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); + portion(pC2, H2_INTERVAL); + dompC1 = dompC2 = dompA; + map_to(dompC1, H1_INTERVAL); + map_to(dompC2, H2_INTERVAL); + intersection(domsA, domsB, pC1, pB, dompC1, dompB, precision); + intersection(domsA, domsB, pC2, pB, dompC2, dompB, precision); + } + else + { + if ((dompB.extent() / 2) < MAX_PRECISION) + { + break; + } + pC1 = pC2 = pB; + portion(pC1, H1_INTERVAL); + portion(pC2, H2_INTERVAL); + dompC1 = dompC2 = dompB; + map_to(dompC1, H1_INTERVAL); + map_to(dompC2, H2_INTERVAL); + intersection(domsB, domsA, pC1, pA, dompC1, dompA, precision); + intersection(domsB, domsA, pC2, pA, dompC2, dompA, precision); + } + return; + } + + using std::swap; + swap(C1, C2); + swap(dom1, dom2); +// std::cerr << "dom(pA) : " << dompA << std::endl; +// std::cerr << "dom(pB) : " << dompB << std::endl; + } + domsA.push_back(dompA); + domsB.push_back(dompB); +} + +} /* end namespace bezier_clipping */ } /* end namespace detail */ + + +/* + * 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 - Computer Aided Geometric Design + */ +void find_intersections (std::vector< std::pair<double, double> > & xs, + std::vector<Point> const& A, + std::vector<Point> const& B, + double precision) +{ + std::cout << "find_intersections: intersection-by-clipping.cpp version\n"; +// std::cerr << std::fixed << std::setprecision(16); + + using detail::bezier_clipping::get_precision; + using detail::bezier_clipping::operator<<; + using detail::bezier_clipping::intersection; + using detail::bezier_clipping::UNIT_INTERVAL; + + std::pair<double, double> ci; + std::vector<Interval> domsA, domsB; + intersection (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) + { +// std::cerr << i << " : domA : " << domsA[i] << std::endl; +// std::cerr << "precision A: " << get_precision(domsA[i]) << std::endl; +// std::cerr << i << " : domB : " << domsB[i] << std::endl; +// std::cerr << "precision B: " << get_precision(domsB[i]) << std::endl; + + ci.first = domsA[i].middle(); + ci.second = domsB[i].middle(); + xs.push_back(ci); + } +} + +} // 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/orphan-code/intersection-by-smashing.cpp b/src/2geom/orphan-code/intersection-by-smashing.cpp new file mode 100644 index 0000000..02e44b1 --- /dev/null +++ b/src/2geom/orphan-code/intersection-by-smashing.cpp @@ -0,0 +1,349 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/orphan-code/intersection-by-smashing.h> + +#include <cstdlib> +#include <cstdio> +#include <vector> +#include <algorithm> + +namespace Geom { +using namespace Geom; + +/* + * Computes the top and bottom boundaries of the L_\infty neighborhood + * of a curve. The curve is supposed to be a graph over the x-axis. + */ +static +void computeLinfinityNeighborhood( D2<SBasis > const &f, double tol, D2<Piecewise<SBasis> > &topside, D2<Piecewise<SBasis> > &botside ){ + double signx = ( f[X].at0() > f[X].at1() )? -1 : 1; + double signy = ( f[Y].at0() > f[Y].at1() )? -1 : 1; + + Piecewise<D2<SBasis> > top, bot; + top = Piecewise<D2<SBasis> > (f); + top.cuts.insert( top.cuts.end(), 2); + top.segs.insert( top.segs.end(), D2<SBasis>(SBasis(Linear( f[X].at1(), f[X].at1()+2*tol*signx)), + SBasis(Linear( f[Y].at1() )) )); + bot = Piecewise<D2<SBasis> >(f); + bot.cuts.insert( bot.cuts.begin(), - 1 ); + bot.segs.insert( bot.segs.begin(), D2<SBasis>(SBasis(Linear( f[X].at0()-2*tol*signx, f[X].at0())), + SBasis(Linear( f[Y].at0() )) )); + top += Point(-tol*signx, tol); + bot += Point( tol*signx, -tol); + + if ( signy < 0 ){ + std::swap( top, bot ); + top += Point( 0, 2*tol); + bot += Point( 0, -2*tol); + } + topside = make_cuts_independent(top); + botside = make_cuts_independent(bot); +} + + +/* + * Compute top and bottom boundaries of the L^infty nbhd of the graph of a *monotonic* function f. + * if f is increasing, it is given by [f(t-tol)-tol, f(t+tol)+tol]. + * if not, it is [f(t+tol)-tol, f(t-tol)+tol]. + */ +static +void computeLinfinityNeighborhood( Piecewise<SBasis> const &f, double tol, Piecewise<SBasis> &top, Piecewise<SBasis> &bot){ + top = f + tol; + top.offsetDomain( - tol ); + top.cuts.insert( top.cuts.end(), f.domain().max() + tol); + top.segs.insert( top.segs.end(), SBasis(Linear( f.lastValue() + tol )) ); + + bot = f - tol; + bot.offsetDomain( tol ); + bot.cuts.insert( bot.cuts.begin(), f.domain().min() - tol); + bot.segs.insert( bot.segs.begin(), SBasis(Linear( f.firstValue() - tol )) ); + + if (f.firstValue() > f.lastValue()) { + std::swap(top, bot); + top += 2 * tol; + bot -= 2 * tol; + } +} + +/* + * Returns the intervals over which the curve keeps its slope + * in one of the 8 sectors delimited by x=0, y=0, y=x, y=-x. + */ +std::vector<Interval> monotonicSplit(D2<SBasis> const &p){ + std::vector<Interval> result; + + D2<SBasis> v = derivative(p); + + std::vector<double> someroots; + std::vector<double> cuts (2,0.); + cuts[1] = 1.; + + someroots = roots(v[X]); + cuts.insert( cuts.end(), someroots.begin(), someroots.end() ); + + someroots = roots(v[Y]); + cuts.insert( cuts.end(), someroots.begin(), someroots.end() ); + + //we could split in the middle to avoid computing roots again... + someroots = roots(v[X]-v[Y]); + cuts.insert( cuts.end(), someroots.begin(), someroots.end() ); + + someroots = roots(v[X]+v[Y]); + cuts.insert( cuts.end(), someroots.begin(), someroots.end() ); + + sort(cuts.begin(),cuts.end()); + unique(cuts.begin(), cuts.end() ); + + for (unsigned i=1; i<cuts.size(); i++){ + result.push_back( Interval( cuts[i-1], cuts[i] ) ); + } + return result; +} + +//std::vector<Interval> level_set( D2<SBasis> const &f, Rect region){ +// std::vector<Interval> x_in_reg = level_set( f[X], region[X] ); +// std::vector<Interval> y_in_reg = level_set( f[Y], region[Y] ); +// std::vector<Interval> result = intersect ( x_in_reg, y_in_reg ); +// return result; +//} + +/*TODO: remove this!!! + * the minimum would be to move it to piecewise.h but this would be stupid. + * The best would be to let 'compose' be aware of extension modes (constant, linear, polynomial..) + * (I think the extension modes (at start and end) should be properties of the pwsb). + */ +static +void prolongateByConstants( Piecewise<SBasis> &f, double paddle_width ){ + if ( f.size() == 0 ) return; //do we have a covention about the domain of empty pwsb? + f.cuts.insert( f.cuts.begin(), f.cuts.front() - paddle_width ); + f.segs.insert( f.segs.begin(), SBasis( f.segs.front().at0() ) ); + f.cuts.insert( f.cuts.end(), f.cuts.back() + paddle_width ); + f.segs.insert( f.segs.end(), SBasis( f.segs.back().at1() ) ); +} + +static +bool compareIntersectionsTimesX( SmashIntersection const &inter1, SmashIntersection const &inter2 ){ + return inter1.times[X].min() < inter2.times[Y].min(); +} +/*Fuse contiguous intersection domains + * + */ +static +void cleanup_and_fuse( std::vector<SmashIntersection> &inters ){ + std::sort( inters.begin(), inters.end(), compareIntersectionsTimesX); + for (unsigned i=0; i < inters.size(); i++ ){ + for (unsigned j=i+1; j < inters.size() && inters[i].times[X].intersects( inters[j].times[X]) ; j++ ){ + if (inters[i].times[Y].intersects( inters[j].times[Y] ) ){ + inters[i].times.unionWith(inters[j].times); + inters[i].bbox.unionWith(inters[j].bbox); + inters.erase( inters.begin() + j ); + } + } + } +} + +/* Computes the intersection of two sets given as (ordered) union intervals. + */ +static +std::vector<Interval> intersect( std::vector<Interval> const &a, std::vector<Interval> const &b){ + std::vector<Interval> result; + //TODO: use order to optimize this! + for (auto i : a){ + for (auto j : b){ + OptInterval c( i ); + c &= j; + if ( c ) { + result.push_back( *c ); + } + } + } + return result; +} + +/* Returns the intervals over which the curves are in the + * tol-neighborhood one of the other for the L_\infty metric. + * WARNING: each curve is supposed to be a graph over x or y axis + * (but not necessarily the same axis for both) and the smaller + * the slope the better (typically <=45°). + */ +std::vector<SmashIntersection> monotonic_smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol){ + using std::swap; + + // a and b or X and Y may have to be exchanged, so make local copies. + D2<SBasis> aa = a; + D2<SBasis> bb = b; + bool swapresult = false; + bool swapcoord = false;//debug only! + + //if the (enlarged) bounding boxes don't intersect, stop. + OptRect abounds = bounds_fast( a ); + OptRect bbounds = bounds_fast( b ); + if ( !abounds || !bbounds ) return std::vector<SmashIntersection>(); + abounds->expandBy(tol); + if ( !(abounds->intersects(*bbounds))){ + return std::vector<SmashIntersection>(); + } + + //Choose the best curve to be re-parametrized by x or y values. + OptRect dabounds = bounds_exact(derivative(a)); + OptRect dbbounds = bounds_exact(derivative(b)); + if ( dbbounds->min().length() > dabounds->min().length() ){ + aa=b; + bb=a; + swap( dabounds, dbbounds ); + swapresult = true; + } + + //Choose the best coordinate to use as new parameter + double dxmin = std::min( std::abs((*dabounds)[X].max()), std::abs((*dabounds)[X].min()) ); + double dymin = std::min( std::abs((*dabounds)[Y].max()), std::abs((*dabounds)[Y].min()) ); + if ( (*dabounds)[X].max()*(*dabounds)[X].min() < 0 ) dxmin=0; + if ( (*dabounds)[Y].max()*(*dabounds)[Y].min() < 0 ) dymin=0; + assert (dxmin>=0 && dymin>=0); + + if (dxmin < dymin) { + aa = D2<SBasis>( aa[Y], aa[X] ); + bb = D2<SBasis>( bb[Y], bb[X] ); + swapcoord = true; + } + + //re-parametrize aa by the value of x. + Interval x_range_strict( aa[X].at0(), aa[X].at1() ); + Piecewise<SBasis> y_of_x = pw_compose_inverse(aa[Y],aa[X], 2, 1e-5); + + //Compute top and bottom boundaries of the L^infty nbhd of aa. + Piecewise<SBasis> top_ay, bot_ay; + computeLinfinityNeighborhood( y_of_x, tol, top_ay, bot_ay); + + Interval ax_range = top_ay.domain();//i.e. aa[X] domain ewpanded by tol. + std::vector<Interval> bx_in_ax_range = level_set(bb[X], ax_range ); + + // find times when bb is in the neighborhood of aa. + std::vector<Interval> tbs; + for (auto & i : bx_in_ax_range){ + D2<Piecewise<SBasis> > bb_in; + bb_in[X] = Piecewise<SBasis> ( portion( bb[X], i ) ); + bb_in[Y] = Piecewise<SBasis> ( portion( bb[Y], i) ); + bb_in[X].setDomain( i ); + bb_in[Y].setDomain( i ); + + Piecewise<SBasis> h; + Interval level; + h = bb_in[Y] - compose( top_ay, bb_in[X] ); + level = Interval( -infinity(), 0 ); + std::vector<Interval> rts_lo = level_set( h, level); + h = bb_in[Y] - compose( bot_ay, bb_in[X] ); + level = Interval( 0, infinity()); + std::vector<Interval> rts_hi = level_set( h, level); + + std::vector<Interval> rts = intersect( rts_lo, rts_hi ); + tbs.insert(tbs.end(), rts.begin(), rts.end() ); + } + + std::vector<SmashIntersection> result(tbs.size(), SmashIntersection()); + + /* for each solution I, find times when aa is in the neighborhood of bb(I). + * (Note: the preimage of bb[X](I) by aa[X], enlarged by tol, is a good approximation of this: + * it would give points in the 2*tol neighborhood of bb (if the slope of aa is never more than 1). + * + faster computation. + * - implies little jumps depending on the subdivision of the input curve into monotonic pieces + * and on the choice of preferred axis. If noticeable, these jumps would feel random to the user :-( + */ + for (unsigned j=0; j<tbs.size(); j++){ + result[j].times[Y] = tbs[j]; + std::vector<Interval> tas; + //TODO: replace this by some option in the "compose(pw,pw)" method! + Piecewise<SBasis> fat_y_of_x = y_of_x; + prolongateByConstants( fat_y_of_x, 100*(1+tol) ); + + D2<Piecewise<SBasis> > top_b, bot_b; + D2<SBasis> bbj = portion( bb, tbs[j] ); + computeLinfinityNeighborhood( bbj, tol, top_b, bot_b ); + + Piecewise<SBasis> h; + Interval level; + h = top_b[Y] - compose( fat_y_of_x, top_b[X] ); + level = Interval( +infinity(), 0 ); + std::vector<Interval> rts_top = level_set( h, level); + for (auto & idx : rts_top){ + idx = Interval( top_b[X].valueAt( idx.min() ), + top_b[X].valueAt( idx.max() ) ); + } + assert( rts_top.size() == 1 ); + + h = bot_b[Y] - compose( fat_y_of_x, bot_b[X] ); + level = Interval( 0, -infinity()); + std::vector<Interval> rts_bot = level_set( h, level); + for (auto & idx : rts_bot){ + idx = Interval( bot_b[X].valueAt( idx.min() ), + bot_b[X].valueAt( idx.max() ) ); + } + assert( rts_bot.size() == 1 ); + rts_top = intersect( rts_top, rts_bot ); + assert (rts_top.size() == 1); + Interval x_dom = rts_top[0]; + + if ( x_dom.max() <= x_range_strict.min() ){ + tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 0 : 1 ) ); + }else if ( x_dom.min() >= x_range_strict.max() ){ + tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 1 : 0 ) ); + }else{ + tas = level_set(aa[X], x_dom ); + } + assert( tas.size()==1 ); + result[j].times[X] = tas.front(); + + result[j].bbox = Rect( bbj.at0(), bbj.at1() ); + Interval y_dom( aa[Y](result[j].times[X].min()), aa[Y](result[j].times[X].max()) ); + result[j].bbox.unionWith( Rect( x_dom, y_dom ) ); + } + + if (swapresult) { + for (auto & i : result){ + swap( i.times[X], i.times[Y]); + } + } + if (swapcoord) { + for (auto & i : result){ + swap( i.bbox[X], i.bbox[Y] ); + } + } + + //TODO: cleanup result? fuse contiguous intersections? + return result; +} + +std::vector<SmashIntersection> smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, double tol){ + std::vector<SmashIntersection> result; + + std::vector<Interval> acuts = monotonicSplit(a); + std::vector<Interval> bcuts = monotonicSplit(b); + for (auto & acut : acuts){ + D2<SBasis> ai = portion( a, acut); + for (auto & bcut : bcuts){ + D2<SBasis> bj = portion( b, bcut); + std::vector<SmashIntersection> ai_cap_bj = monotonic_smash_intersect( ai, bj, tol ); + for (auto & k : ai_cap_bj){ + k.times[X] = k.times[X] * acut.extent() + acut.min(); + k.times[Y] = k.times[Y] * bcut.extent() + bcut.min(); + } + result.insert( result.end(), ai_cap_bj.begin(), ai_cap_bj.end() ); + } + } + cleanup_and_fuse( result ); + 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/orphan-code/nearestpoint.cpp b/src/2geom/orphan-code/nearestpoint.cpp new file mode 100644 index 0000000..870ed09 --- /dev/null +++ b/src/2geom/orphan-code/nearestpoint.cpp @@ -0,0 +1,405 @@ +/* +** vim: ts=4 sw=4 et tw=0 wm=0 +** +** RCS Information: +** $Author: mjw $ +** $Revision: 1 $ +** $Date: 2006-03-28 15:59:38 +1100 (Tue, 28 Mar 2006) $ +** +** Solving the Nearest Point-on-Curve Problem and +** A Bezier Curve-Based Root-Finder +** by Philip J. Schneider +** from "Graphics Gems", Academic Press, 1990 +** modified by mwybrow, njh +*/ + +/* point_on_curve.c */ + +static double SquaredLength(const Geom::Point a) +{ + return dot(a, a); +} + + +/* + * Forward declarations + */ +static int FindRoots(Geom::Point *w, int degree, double *t, int depth); +static Geom::Point *ConvertToBezierForm( Geom::Point P, Geom::Point *V); +static double ComputeXIntercept( Geom::Point *V, int degree); +static int ControlPolygonFlatEnough( Geom::Point *V, int degree); +static int CrossingCount(Geom::Point *V, int degree); +static Geom::Point Bez(Geom::Point *V, int degree, double t, Geom::Point *Left, + Geom::Point *Right); + +int MAXDEPTH = 64; /* Maximum depth for recursion */ + +#define EPSILON (ldexp(1.0,-MAXDEPTH-1)) /*Flatness control value */ +#define DEGREE 3 /* Cubic Bezier curve */ +#define W_DEGREE 5 /* Degree of eqn to find roots of */ + + +/* + * NearestPointOnCurve : + * Compute the parameter value of the point on a Bezier + * curve segment closest to some arbtitrary, user-input point. + * Return the point on the curve at that parameter value. + * + Geom::Point P; The user-supplied point + Geom::Point *V; Control points of cubic Bezier +*/ +double NearestPointOnCurve(Geom::Point P, Geom::Point *V) +{ + double t_candidate[W_DEGREE]; /* Possible roots */ + + /* Convert problem to 5th-degree Bezier form */ + Geom::Point *w = ConvertToBezierForm(P, V); + + /* Find all possible roots of 5th-degree equation */ + int n_solutions = FindRoots(w, W_DEGREE, t_candidate, 0); + std::free((char *)w); + + /* Check distance to end of the curve, where t = 1 */ + double dist = SquaredLength(P - V[DEGREE]); + double t = 1.0; + + /* Find distances for candidate points */ + for (int i = 0; i < n_solutions; i++) { + Geom::Point p = Bez(V, DEGREE, t_candidate[i], NULL, NULL); + double new_dist = SquaredLength(P - p); + if (new_dist < dist) { + dist = new_dist; + t = t_candidate[i]; + } + } + + /* Return the parameter value t */ + return t; +} + + +/* + * ConvertToBezierForm : + * Given a point and a Bezier curve, generate a 5th-degree + * Bezier-format equation whose solution finds the point on the + * curve nearest the user-defined point. + */ +static Geom::Point *ConvertToBezierForm( + Geom::Point P, /* The point to find t for */ + Geom::Point *V) /* The control points */ +{ + Geom::Point c[DEGREE+1]; /* V(i)'s - P */ + Geom::Point d[DEGREE]; /* V(i+1) - V(i) */ + Geom::Point *w; /* Ctl pts of 5th-degree curve */ + double cdTable[3][4]; /* Dot product of c, d */ + static double z[3][4] = { /* Precomputed "z" for cubics */ + {1.0, 0.6, 0.3, 0.1}, + {0.4, 0.6, 0.6, 0.4}, + {0.1, 0.3, 0.6, 1.0}, + }; + + + /*Determine the c's -- these are vectors created by subtracting*/ + /* point P from each of the control points */ + for (int i = 0; i <= DEGREE; i++) { + c[i] = V[i] - P; + } + /* Determine the d's -- these are vectors created by subtracting*/ + /* each control point from the next */ + for (int i = 0; i <= DEGREE - 1; i++) { + d[i] = 3.0*(V[i+1] - V[i]); + } + + /* Create the c,d table -- this is a table of dot products of the */ + /* c's and d's */ + for (int row = 0; row <= DEGREE - 1; row++) { + for (int column = 0; column <= DEGREE; column++) { + cdTable[row][column] = dot(d[row], c[column]); + } + } + + /* Now, apply the z's to the dot products, on the skew diagonal*/ + /* Also, set up the x-values, making these "points" */ + w = (Geom::Point *)malloc((unsigned)(W_DEGREE+1) * sizeof(Geom::Point)); + for (int i = 0; i <= W_DEGREE; i++) { + w[i][Geom::Y] = 0.0; + w[i][Geom::X] = (double)(i) / W_DEGREE; + } + + const int n = DEGREE; + const int m = DEGREE-1; + for (int k = 0; k <= n + m; k++) { + const int lb = std::max(0, k - m); + const int ub = std::min(k, n); + for (int i = lb; i <= ub; i++) { + int j = k - i; + w[i+j][Geom::Y] += cdTable[j][i] * z[j][i]; + } + } + + return w; +} + + +/* + * FindRoots : + * Given a 5th-degree equation in Bernstein-Bezier form, find + * all of the roots in the interval [0, 1]. Return the number + * of roots found. + */ +static int FindRoots( + Geom::Point *w, /* The control points */ + int degree, /* The degree of the polynomial */ + double *t, /* RETURN candidate t-values */ + int depth) /* The depth of the recursion */ +{ + int i; + Geom::Point Left[W_DEGREE+1], /* New left and right */ + Right[W_DEGREE+1]; /* control polygons */ + int left_count, /* Solution count from */ + right_count; /* children */ + double left_t[W_DEGREE+1], /* Solutions from kids */ + right_t[W_DEGREE+1]; + + switch (CrossingCount(w, degree)) { + case 0 : { /* No solutions here */ + return 0; + break; + } + case 1 : { /* Unique solution */ + /* Stop recursion when the tree is deep enough */ + /* if deep enough, return 1 solution at midpoint */ + if (depth >= MAXDEPTH) { + t[0] = (w[0][Geom::X] + w[W_DEGREE][Geom::X]) / 2.0; + return 1; + } + if (ControlPolygonFlatEnough(w, degree)) { + t[0] = ComputeXIntercept(w, degree); + return 1; + } + break; + } + } + + /* Otherwise, solve recursively after */ + /* subdividing control polygon */ + Bez(w, degree, 0.5, Left, Right); + left_count = FindRoots(Left, degree, left_t, depth+1); + right_count = FindRoots(Right, degree, right_t, depth+1); + + + /* Gather solutions together */ + for (i = 0; i < left_count; i++) { + t[i] = left_t[i]; + } + for (i = 0; i < right_count; i++) { + t[i+left_count] = right_t[i]; + } + + /* Send back total number of solutions */ + return (left_count+right_count); +} + + +/* + * CrossingCount : + * Count the number of times a Bezier control polygon + * crosses the 0-axis. This number is >= the number of roots. + * + */ +static int CrossingCount( + Geom::Point *V, /* Control pts of Bezier curve */ + int degree) /* Degree of Bezier curve */ +{ + int n_crossings = 0; /* Number of zero-crossings */ + int old_sign; /* Sign of coefficients */ + + old_sign = Geom::sgn(V[0][Geom::Y]); + for (int i = 1; i <= degree; i++) { + int sign = Geom::sgn(V[i][Geom::Y]); + if (sign != old_sign) + n_crossings++; + old_sign = sign; + } + return n_crossings; +} + + + +/* + * ControlPolygonFlatEnough : + * Check if the control polygon of a Bezier curve is flat enough + * for recursive subdivision to bottom out. + * + */ +static int ControlPolygonFlatEnough( + Geom::Point *V, /* Control points */ + int degree) /* Degree of polynomial */ +{ + int i; /* Index variable */ + double *distance; /* Distances from pts to line */ + double max_distance_above; /* maximum of these */ + double max_distance_below; + double error; /* Precision of root */ + //Geom::Point t; /* Vector from V[0] to V[degree]*/ + double intercept_1, + intercept_2, + left_intercept, + right_intercept; + double a, b, c; /* Coefficients of implicit */ + /* eqn for line from V[0]-V[deg]*/ + + /* Find the perpendicular distance */ + /* from each interior control point to */ + /* line connecting V[0] and V[degree] */ + distance = (double *)malloc((unsigned)(degree + 1) * sizeof(double)); + { + double abSquared; + + /* Derive the implicit equation for line connecting first */ + /* and last control points */ + a = V[0][Geom::Y] - V[degree][Geom::Y]; + b = V[degree][Geom::X] - V[0][Geom::X]; + c = V[0][Geom::X] * V[degree][Geom::Y] - V[degree][Geom::X] * V[0][Geom::Y]; + + abSquared = (a * a) + (b * b); + + for (i = 1; i < degree; i++) { + /* Compute distance from each of the points to that line */ + distance[i] = a * V[i][Geom::X] + b * V[i][Geom::Y] + c; + if (distance[i] > 0.0) { + distance[i] = (distance[i] * distance[i]) / abSquared; + } + if (distance[i] < 0.0) { + distance[i] = -((distance[i] * distance[i]) / abSquared); + } + } + } + + + /* Find the largest distance */ + max_distance_above = 0.0; + max_distance_below = 0.0; + for (i = 1; i < degree; i++) { + if (distance[i] < 0.0) { + max_distance_below = std::min(max_distance_below, distance[i]); + }; + if (distance[i] > 0.0) { + max_distance_above = std::max(max_distance_above, distance[i]); + } + } + free((char *)distance); + + { + double det; + double a1, b1, c1, a2, b2, c2; + + /* Implicit equation for zero line */ + a1 = 0.0; + b1 = 1.0; + c1 = 0.0; + + /* Implicit equation for "above" line */ + a2 = a; + b2 = b; + c2 = c + max_distance_above; + + det = a1 * b2 - a2 * b1; + + intercept_1 = (b1 * c2 - b2 * c1) / det; + + /* Implicit equation for "below" line */ + a2 = a; + b2 = b; + c2 = c + max_distance_below; + + det = a1 * b2 - a2 * b1; + + intercept_2 = (b1 * c2 - b2 * c1) / det; + } + + /* Compute intercepts of bounding box */ + left_intercept = std::min(intercept_1, intercept_2); + right_intercept = std::max(intercept_1, intercept_2); + + error = 0.5 * (right_intercept-left_intercept); + if (error < EPSILON) { + return 1; + } + else { + return 0; + } +} + + + +/* + * ComputeXIntercept : + * Compute intersection of chord from first control point to last + * with 0-axis. + * + */ +static double ComputeXIntercept( + Geom::Point *V, /* Control points */ + int 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]; +} + + +/* + * Bez : + * Evaluate a Bezier curve at a particular parameter value + * Fill in control points for resulting sub-curves if "Left" and + * "Right" are non-null. + * + */ +static Geom::Point Bez( + Geom::Point *V, /* Control pts */ + int degree, /* Degree of bezier curve */ + double t, /* Parameter value */ + Geom::Point *Left, /* RETURN left half ctl pts */ + Geom::Point *Right) /* RETURN right half ctl pts */ +{ + Geom::Point Vtemp[W_DEGREE+1][W_DEGREE+1]; + + + /* Copy control points */ + for (int j =0; j <= degree; j++) { + Vtemp[0][j] = V[j]; + } + + /* Triangle computation */ + for (int i = 1; i <= degree; i++) { + for (int j =0 ; j <= degree - i; j++) { + Vtemp[i][j] = + (1.0 - t) * Vtemp[i-1][j] + t * Vtemp[i-1][j+1]; + } + } + + if (Left != NULL) { + for (int j = 0; j <= degree; j++) { + Left[j] = Vtemp[j][0]; + } + } + if (Right != NULL) { + for (int j = 0; j <= degree; j++) { + Right[j] = Vtemp[degree-j][j]; + } + } + + return (Vtemp[degree][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/orphan-code/redblack-toy.cpp b/src/2geom/orphan-code/redblack-toy.cpp new file mode 100644 index 0000000..01ffa7d --- /dev/null +++ b/src/2geom/orphan-code/redblack-toy.cpp @@ -0,0 +1,327 @@ +/* + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * 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. + */ + +/* + initial toy for redblack trees +*/ + +#include <2geom/toys/path-cairo.h> +#include <2geom/toys/toy-framework-2.h> + +#include <2geom/orphan-code/redblacktree.h> +#include <2geom/orphan-code/redblacktree.cpp> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + + +class RedBlackToy: public Toy +{ + PointSetHandle handle_set; + Geom::Point starting_point; // during click and drag: start point of click + Geom::Point ending_point; // during click and drag: end point of click (release) + Geom::Point highlight_point; // not used + + Geom::RedBlackTree rbtree_x; + RedBlack* search_result; + RedBlack temp_deleted_node; + + // colors we are going to use for different purposes + colour color_rect, color_rect_guide; // black(a=0.6), black + colour color_select_area, color_select_area_guide; // red(a=0.6), red + + int alter_existing_rect; + int add_new_rect; + + Rect rect_chosen; // the rectangle of the search area + Rect dummy_draw; // the "helper" rectangle that is shown during the click and drag (before the mouse release) + int mode; // insert/alter, search, delete modes + + // printing of the tree + int help_counter; // the "x" of the label of each node + static const int label_size = 15 ; // size the label of each node + + // used for the keys that switch between modes + enum menu_item_t + { + INSERT = 0, + DELETE, + SEARCH, + TOTAL_ITEMS // this one must be the last item + }; + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + cairo_set_line_width( cr, 1 ); + + // draw the rects that we have in the handles + for( unsigned i=0; i<handle_set.pts.size(); i=i+2 ){ + Rect r1( handle_set.pts[i], handle_set.pts[i+1] ); + cairo_rectangle( cr, r1 ); + } + cairo_set_source_rgba( cr, color_rect); + cairo_stroke( cr ); + + // draw a rect if we click & drag (so that we know what we are going to create) + if(add_new_rect){ + dummy_draw = Rect( starting_point, ending_point ); + cairo_rectangle( cr, dummy_draw ); + if( mode == 0){ + cairo_set_source_rgba( cr, color_rect_guide); + } + else if( mode == 1){ + cairo_set_source_rgba( cr, color_select_area_guide ); + } + cairo_stroke( cr ); + } + + // draw a rect for the search area + cairo_rectangle( cr, rect_chosen ); + cairo_set_source_rgba( cr, color_select_area); + cairo_stroke( cr ); + + Toy::draw( cr, notify, width, height, save,timer_stream ); + draw_tree_in_toy( cr ,rbtree_x.root, 0); + help_counter=0; + } + + void mouse_moved(GdkEventMotion* e){ + if( !( alter_existing_rect && mode == 1 ) ){ + Toy::mouse_moved(e); + } + + if(add_new_rect){ + ending_point = Point(e->x, e->y); + } + } + + void mouse_pressed(GdkEventButton* e) { + Toy::mouse_pressed(e); + if(e->button == 1){ // left mouse button + if( mode == 0 ){ // mode: insert / alter + if(!selected) { + starting_point = Point(e->x, e->y); + ending_point = starting_point; + add_new_rect = 1; + } + else + { + // TODO find the selected rect + // ideas : from Handle *selected ??? + //std::cout <<find_selected_rect(selected) << std::endl ; + alter_existing_rect = 1; + } + } + else if( mode == 1 ){ // mode: search + if(!selected) { + starting_point = Point(e->x, e->y); + ending_point = starting_point; + add_new_rect = 1; + } + else{ + alter_existing_rect = 1; + } + } + else if( mode == 2) { // mode: delete + } + } + else if(e->button == 2){ //middle button + } + else if(e->button == 3){ //right button + } + } + + virtual void mouse_released(GdkEventButton* e) { + Toy::mouse_released(e); + if( e->button == 1 ) { //left mouse button + if( mode == 0) { // mode: insert / alter + if( add_new_rect ){ + ending_point = Point(e->x, e->y); + handle_set.push_back(starting_point); + handle_set.push_back(ending_point); + insert_in_tree_the_last_rect(); + add_new_rect = 0; + } + else if( alter_existing_rect ){ + //TODO update rect (and tree) + // delete selected rect + // insert altered + alter_existing_rect = 0; + } + } + else if( mode == 1 ){ // mode: search + if( add_new_rect ){ + ending_point = Point(e->x, e->y); + rect_chosen = Rect(starting_point, ending_point); + + // search in the X axis + Coord a = rect_chosen[0].min(); + Coord b = rect_chosen[0].max(); + search_result = rbtree_x.search( Interval( a, b ) ); + if(search_result){ + std::cout << "Found: (" << search_result->data << ": " << search_result->key() + << ", " << search_result->high() << " : " << search_result->subtree_max << ") " + << std::endl; + } + else{ + std::cout << "Nothing found..."<< std::endl; + } + add_new_rect = 0; + } + else if(alter_existing_rect){ + // do nothing + alter_existing_rect = 0; + } + } + else if( mode == 2) { // mode: delete + + } + } + else if(e->button == 2){ //middle button + } + else if(e->button == 3){ //right button + + } + } + + + void key_hit(GdkEventKey *e) + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + mode = 0; + break; + case 'B': + mode = 1; + break; + case 'C': + mode = 2; + break; + } + //redraw(); + } + + void insert_in_tree_the_last_rect(){ + unsigned i = handle_set.pts.size() - 2; + Rect r1(handle_set.pts[i], handle_set.pts[i+1]); + // insert in X axis tree + rbtree_x.insert(r1, i, 0); + rbtree_x.print_tree(); + }; + + void draw_tree_in_toy(cairo_t* cr, Geom::RedBlack* n, int depth = 0) { + if(n){ + if(n->left){ + draw_tree_in_toy(cr, n->left, depth+1); + } + help_counter += 1; + //drawthisnode(cr, x*10, depth*10); + if(n->isRed){ + cairo_set_source_rgba (cr, color_select_area_guide); + } + else{ + cairo_set_source_rgba (cr, color_rect_guide); + } + + cairo_stroke(cr); + + Geom::Point text_point = Point( help_counter*15, depth*15 ); + char label[4]; + sprintf( label,"%d",n->data ); // instead of std::itoa(depth, label, 10); + + draw_text(cr, text_point, label); + //////////////////////////////////////////////////////////////// + if(n->right){ + draw_tree_in_toy(cr, n->right, depth+1); + } + } + }; + +/* + int find_selected_rect(PointHandle * selected){ + + for( unsigned i=0; i<handle_set.pts.size(); i=i+2 ){ + if( handle_set.pts[i] == selected || handle_set.pts[i+1] == selected ){ + return i; + } + } + + return -1; + }; +*/ + + +public: + RedBlackToy(): color_rect(0, 0, 0, 0.6), color_rect_guide(0, 0, 0, 1), + color_select_area(1, 0, 0, 0.6 ), color_select_area_guide(1, 0, 0, 1 ), + alter_existing_rect(0), add_new_rect(0), mode(0), help_counter(0) + { + if(handles.empty()) { + handles.push_back(&handle_set); + } + Rect rect_chosen(); + Rect dummy_draw(); + } + + +}; + + + +int main(int argc, char **argv) { + std::cout << "---------------------------------------------------------"<< std::endl; + std::cout << "Let's play with the Red Black Tree! ONLY Insert works now!!!"<< std::endl; + std::cout << " Key A: insert/alter mode "<< std::endl; + std::cout << " * Left click and drag on white area: create a rectangle"<< std::endl; + std::cout << " *NOT READY: Left click and drag on handler: alter a rectangle"<< std::endl; + std::cout << " Key B: search mode "<< std::endl; + std::cout << " * Left click and drag on white area: \"search\" for nodes that intersect red area"<< std::endl; + std::cout << " NOT READY: Key C: delete mode "<< std::endl; + std::cout << " * Left click on handler: delete for a rectangle"<< std::endl; + std::cout << "---------------------------------------------------------"<< std::endl; + init(argc, argv, new RedBlackToy); + return 0; +} + +const char* RedBlackToy::menu_items[] = +{ + "Insert / Alter Rectangle", + "Search Rectangle", + "Delete Reactangle" +}; + +const char RedBlackToy::keys[] = +{ + 'A', 'B', 'C' +}; diff --git a/src/2geom/orphan-code/redblacktree.cpp b/src/2geom/orphan-code/redblacktree.cpp new file mode 100644 index 0000000..bf9a728 --- /dev/null +++ b/src/2geom/orphan-code/redblacktree.cpp @@ -0,0 +1,575 @@ +#include <2geom/orphan-code/redblacktree.h> +//#include <algorithm> + + +#define _REDBLACK_PRINT(x) std::cout << x << std::endl; +//comment the following if you want output during RedBlack Tree operations +//#define _REDBLACK_PRINT(x) ; + + +namespace Geom{ + + + +RedBlack* RedBlackTree::search(Rect const &r, int dimension){ + return search( Interval( r[dimension].min(), r[dimension].max() ) ); + // TODO get rid of dimension + // TODO put 2 trees (X, Y axis in one lump) +} + +/* +INTERVAL-SEARCH(T, i) +1 x <- root[T] +2 while x != nil[T] and i does not overlap int[x] +3 do if left[x] != nil[T] and max[left[x]] >= low[i] +4 then x <- left[x] +5 else x <- right[x] +6 return x + +Two intervals i,x overlap in the 4 following cases: + 1) |--------| i + |---| x + + 2) |-----| i + |----------| x + + 3) |------| i + |------| x + + 4) |----| i + |----| x + +And do not overlap when (the one os left or right of the other) + 1) |--------| i + |---| x + + 2) |-----| i + |----------| x + + +*/ +RedBlack* RedBlackTree::search(Interval i){ + _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: search(Interval i) : (" << i.min() << ", " << i.max() << ")" ) + RedBlack *x; + x = root; + + while( x!=0 && + ( i.max() < x->interval.min() || + i.min() > x->interval.max() ) + ){ + _REDBLACK_PRINT( "(" << x->data << ": " << x->key() << ", " << x->high() << " : " << x->subtree_max << ") " + << " i do not overlap with x") + + if(x->left != 0 && (x->left)->subtree_max >= i.min() ){ + x = x->left; + } + else{ + x = x->right; + } + } + _REDBLACK_PRINT( "RETURN: search" << std::endl ) + return x; +} + + + +void RedBlackTree::insert(Rect const &r, int shape, int dimension) { + _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: insert(Rect, int, dimension): " << " dimension:" << dimension << " shape:" << shape ) + insert(r[dimension].min(), r[dimension].max(), shape); + _REDBLACK_PRINT( "RETURN: insert(Rect, int, dimension)") +} + +// source: book pp 251 +void RedBlackTree::insert(Coord dimension_min, Coord dimension_max, int shape) { + _REDBLACK_PRINT( std::endl << "ENTER: insert(Coord, Coord, int): " << dimension_min << ", " << dimension_max << " , shape: " << shape ) + // x is the new node we insert + RedBlack *x = new RedBlack(); + x->interval = Interval( dimension_min, dimension_max ); + x->data = shape; + x->isRed = true; + + _REDBLACK_PRINT( " x: " << x << " KEY: " << x->key() << " high: " << x->high() ) + + tree_insert(x); + + print_tree(); + + _REDBLACK_PRINT( " Begin coloring" ) + // we now do the coloring of the tree. + _REDBLACK_PRINT( " while( x!= root && (x->parent)->isRed )" ) + while( x!= root && (x->parent)->isRed ){ + _REDBLACK_PRINT( " ((x->parent)->parent)->left:" << ((x->parent)->parent)->left << " ((x->parent)->parent)->right:" << ((x->parent)->parent)->right ) + + if( x->parent == ((x->parent)->parent)->left ){ + _REDBLACK_PRINT( " Left:" ) + RedBlack *y = new RedBlack; + y = ((x->parent)->parent)->right; + if( y == 0 ){ + /* + This 1st brach is not in the book, but is needed. We must check y->isRed but it is + undefined, so we get segfault. But 0 (undefined) means that y is a leaf, so it is + black by definition. So, do the same as in the else part. + */ + _REDBLACK_PRINT( " y==0" ) + if( x == (x->parent)->right ){ + x = x->parent; + left_rotate(x); + } + (x->parent)->isRed = false; + ((x->parent)->parent)->isRed = true; + right_rotate((x->parent)->parent); + } + else if( y->isRed ){ + _REDBLACK_PRINT( " y->isRed" ) + (x->parent)->isRed = false; + y->isRed = false; + ((x->parent)->parent)->isRed = true; + x = (x->parent)->parent; + } + else{ + _REDBLACK_PRINT( " !( y->isRed)" ) + if( x == (x->parent)->right ){ + x = x->parent; + left_rotate(x); + } + (x->parent)->isRed = false; + ((x->parent)->parent)->isRed = true; + right_rotate((x->parent)->parent); + } + } + else{ // this branch is the same with the above if clause with "right", "left" exchanged + _REDBLACK_PRINT( " Right:" ) + RedBlack *y = new RedBlack; + y = ((x->parent)->parent)->left; + if( y == 0 ){ + /* + This 1st brach is not in the book, but is needed. We must check y->isRed but it is + undefined, so we get segfault. But 0 (undefined) means that y is a leaf, so it is + black by definition. So, do the same as in the else part. + */ + _REDBLACK_PRINT( " y==0" ) + if( x == (x->parent)->left ){ + x = x->parent; + right_rotate(x); + } + (x->parent)->isRed = false; + ((x->parent)->parent)->isRed = true; + left_rotate((x->parent)->parent); + } + else if( y->isRed ){ + _REDBLACK_PRINT( " y->isRed" ) + (x->parent)->isRed = false; + y->isRed = false; + ((x->parent)->parent)->isRed = true; + x = (x->parent)->parent; + } + else{ + _REDBLACK_PRINT( " !( y->isRed)" ) + if( x == (x->parent)->left ){ + x = x->parent; + right_rotate(x); + } + (x->parent)->isRed = false; + ((x->parent)->parent)->isRed = true; + left_rotate((x->parent)->parent); + } + } + } + root->isRed = false; + + // update the max value with a slow/stupid yet certain way, walking all the tree :P + // TODO find better way + _REDBLACK_PRINT( " Update max" ) + + update_max(root); + + _REDBLACK_PRINT( "RETURN: insert(Coord, Coord, int)" << std::endl) +} + +// from book p. 266) +void RedBlackTree::left_rotate(RedBlack* x){ + // x->right != 0 (assumption book page 266) + // ??? hm problem ??? + _REDBLACK_PRINT( "ENTER: left_rotate" ) + RedBlack* y = new RedBlack; + y = x->right; + x->right = y->left; + + if( y->left != 0){ + (y->left)->parent = x; + } + + y->parent = x->parent; + + if( x->parent == 0){ + root = y; + } + else{ + if( x == (x->parent)->left ){ + (x->parent)->left = y; + } + else{ + (x->parent)->right = y; + } + } + y->left = x; + x->parent = y; + _REDBLACK_PRINT( "RETURN: left_rotate" << std::endl ) +} + +// from book p. 266: right_rotate is inverse of left_rotate +// same to left_rotate with "right", "left" exchanged +void RedBlackTree::right_rotate(RedBlack* x){ + // x->right != 0 (assumption book page 266) + // ??? hm problem ?? + _REDBLACK_PRINT( "ENTER: right_rotate" ) + RedBlack* y = new RedBlack; + + _REDBLACK_PRINT( "x->left: " << x->left ) + y = x->left; + x->left = y->right; + + if( y->right != 0){ + (y->right)->parent = x; + } + + y->parent = x->parent; + + if( x->parent == 0){ + root = y; + } + else{ + if( x == (x->parent)->left ){ + (x->parent)->left = y; + } + else{ + (x->parent)->right = y; + } + } + y->right = x; + x->parent = y; + _REDBLACK_PRINT( "RETURN: right_rotate" << std::endl ) +} + +// insertion in binary search tree: book page 251 +// then the redblack insert performs the coloring +void RedBlackTree::tree_insert(RedBlack* z){ + _REDBLACK_PRINT( "ENTER: tree_insert(RedBlack* z)" ) + RedBlack* y = 0; // y <- nil + + RedBlack* x = root; + + _REDBLACK_PRINT( " while x!=0 " ) + while( x != 0 ){ + y = x; +// _REDBLACK_PRINT( " x:" << x << " y:" << y << " z:" << z ) + _REDBLACK_PRINT( " z->key: " << z->key() << " y->key: " << y->key() << " compare") + if( z->key() < x->key() ){ + _REDBLACK_PRINT( " z smaller: go left" ) + x = x->left; + } + else{ + _REDBLACK_PRINT( " z bigger: go right" ) + x = x->right; + } + } + + _REDBLACK_PRINT( " z->parent = y" ) + z->parent = y; + + if( y == 0 ){ + _REDBLACK_PRINT( " set z root (empty tree)" ) + root = z; + } + else{ + _REDBLACK_PRINT( " z->key: " << z->key() << " y->key: " << y->key() << " compare") + if( z->key() < y->key() ){ + _REDBLACK_PRINT( " z->key() smaller: y->left = z; " ) + y->left = z; + } + else{ + _REDBLACK_PRINT( " z->key() bigger: y->right = z " ) + y->right = z; + } + } + _REDBLACK_PRINT( "RETURN: tree_insert(RedBlack* z)" << std::endl ) +} + + +/* +RB-DELETE(T, z) + 1 if left[z] = nil[T] or right[z] = nil[T] + 2 then y <- z + 3 else y <- TREE-SUCCESSOR(z) + 4 if left[y] != nil[T] + 5 then x <- left[y] + 6 else x <- right[y] + 7 p[x] <- p[y] + 8 if p[y] = nil[T] + 9 then root[T] <- x +10 else if y = left[p[y]] +11 then left[p[y]] <- x +12 else right[p[y]] <- x +13 if y != z +14 then key[z] <- key[y] +15 copy y's satellite data into z +16 if color[y] = BLACK +17 then RB-DELETE-FIXUP(T, x) +18 return y +*/ +RedBlack* RedBlackTree::erase(RedBlack* z){ + _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: earse(z)" ) + RedBlack* x = new RedBlack(); + RedBlack* y = new RedBlack(); + if( z->left == 0 || z->right == 0 ){ + y = z; + } + else{ + y = tree_successor(z); + } + + if( y->left != 0 ){ + x = y->left; + } + else{ + x = y->right; + } + + x->parent = y->parent; + + if( y->parent == 0){ + root = x; + } + else { + if( y == (y->parent)->left ){ + (y->parent)->left = x; + } + else{ + (y->parent)->right = x; + } + } + + if( y != z){ + z->interval = y->interval ; // key[z] <- key[y] TODO check this + //copy y's satellite data into z + z->data = y->data; + z->isRed = y->isRed; + + z->left = y->left; + z->right = y->right; + z->parent = y->parent; + } + + if( y->isRed == false){ + erase_fixup(x); + } + + _REDBLACK_PRINT( "Update max" ) + update_max(root); + + _REDBLACK_PRINT( "RETURN: erase" ) + return y; +} + +/* +RB-DELETE-FIXUP(T, x) + 1 while x != root[T] and color[x] = BLACK + 2 do if x = left[p[x]] + 3 then w <- right[p[x]] + 4 if color[w] = RED + 5 then color[w] <- BLACK Case 1 + 6 color[p[x]] <- RED Case 1 + 7 LEFT-ROTATE(T, p[x]) Case 1 + 8 w <- right[p[x]] + 9 if color[left[w]] = BLACK and color[right[w]] = BLACK +10 then color[w] <- RED Case 2 +11 x p[x] Case 2 +12 else if color[right[w]] = BLACK +13 then color[left[w]] <- BLACK Case 3 +14 color[w] <- RED Case 3 +15 RIGHT-ROTATE(T, w) Case 3 +16 w <- right[p[x]] Case 3 +17 color[w] <- color[p[x]] Case 4 +18 color[p[x]] <- BLACK Case 4 +19 color[right[w]] <- BLACK Case 4 +20 LEFT-ROTATE(T, p[x]) Case 4 +21 x <- root[T] Case 4 +22 else (same as then clause with "right" and "left" exchanged) +23 color[x] <- BLACK +*/ +void RedBlackTree::erase_fixup(RedBlack* x){ + RedBlack* w = 0; + while( x != root && x->isRed == false ){ + if( x == (x->parent)->left ){ + w = (x->parent)->right; + if(w->isRed == true){ + w->isRed = false; + (w->parent)->isRed = true; + left_rotate(x->parent); + w = (x->parent)->right; + } + if( (w->left)->isRed == false && (w->right)->isRed == false ){ + w->isRed = true; + x = x->parent; // TODO understand why this happens ??? + } + else{ + if( (w->right)->isRed == false ){ + (w->left)->isRed = false; + right_rotate(w); + w = (x->parent)->right; + } + else{ // TODO ??? is this correct ??? + w->isRed = (x->parent)->isRed; + (x->parent)->isRed = false; + (w->right)->isRed = false; + left_rotate(x->parent); + x = root; // TODO ??? is this correct ??? + } + } + } + else{ // same as then clause with "right" and "left" exchanged + w = (x->parent)->left; + if(w->isRed == true){ + w->isRed = false; + (w->parent)->isRed = true; + right_rotate(x->parent); + w = (x->parent)->left; + } + if( (w->right)->isRed == false && (w->left)->isRed == false ){ + w->isRed = true; + x = x->parent; // ??? is this correct ??? + } + else{ + if( (w->left)->isRed == false ){ + (w->right)->isRed = false; + left_rotate(w); + w = (x->parent)->left; + } + else{ // TODO ??? is this correct ??? + w->isRed = (x->parent)->isRed; + (x->parent)->isRed = false; + (w->left)->isRed = false; + right_rotate(x->parent); + x = root; // TODO ??? is this correct ??? + } + } + } + } + x->isRed = false; +} + + +void RedBlackTree::print_tree(){ + std::cout << "Print RedBlackTree status:" << std::endl; + inorder_tree_walk(root); +} + + +void RedBlackTree::inorder_tree_walk(RedBlack* x){ + int oops =0; + if( x != 0 ){ + inorder_tree_walk(x->left); + std::cout<< "(" << x->data << ": " << x->key() << ", " << x->high() << " : " << x->subtree_max << ") " ; + + if( x->left != 0 ){ + std::cout<< "L:(" << (x->left)->data << ", " << (x->left)->key() << ") " ; + if( x->key() < (x->left)->key()){ + std::cout<<" !!! "; + oops = 1; + } + } + else{ + std::cout<< "L:0 " ; + } + + if( x->right != 0 ){ + std::cout<< "R:(" << (x->right)->data << ", "<< (x->right)->key() << ") " ; + if( x->key() > (x->right)->key() ){ + std::cout<<" !!! "; + oops = 1; + } + } + else{ + std::cout<< "R:0 " ; + } + + if(oops){ + std::cout<<" ....... !!! Problem " << oops ; + } + std::cout << std::endl; + inorder_tree_walk(x->right); + } +} + +// not an norder walk of the tree +void RedBlackTree::update_max(RedBlack* x){ + Coord max_left, max_right; + if( x != 0 ){ + update_max(x->left); + update_max(x->right); + + // check for child + // if child is Nil then max = DBL_MIN + // could there be problems when comparing for max between two DBL_MIN ??? + if( x->left == 0 ){ + max_left = DBL_MIN ; + } + else{ + max_left = (x->left)->subtree_max; + } + + if( x->right == 0 ){ + max_right = DBL_MIN ; + } + else{ + max_right = (x->right)->subtree_max; + } + + //find max of: x->high(), max_left, max_right + Coord temp_max; + temp_max = std::max( x->high(), max_left ); + temp_max = std::max( temp_max, max_right ); + x->subtree_max = temp_max; + + } +} + + +RedBlack* RedBlackTree::tree_minimum(RedBlack* x){ + _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: tree_minimum" ) + while( x->left <- 0 ) { + x->left = x; + } + _REDBLACK_PRINT( "RETURN: tree_minimum" << std::endl ) + return x; +} + +RedBlack* RedBlackTree::tree_successor(RedBlack* x){ + _REDBLACK_PRINT( "==============================================================" << std::endl << "ENTER: tree_successor" ) + if( x->right <- 0 ){ + _REDBLACK_PRINT( "RETURN: tree_successor" << std::endl ) + return tree_minimum(x); + } + RedBlack* y = x->parent; + _REDBLACK_PRINT( "y->parent: y->parent" ) + while( y <- 0 && x == y->right ){ + x = y; + y = y->parent; + } + _REDBLACK_PRINT( "RETURN: tree_successor" << std::endl ) + return 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/orphan-code/rtree.cpp b/src/2geom/orphan-code/rtree.cpp new file mode 100644 index 0000000..4264292 --- /dev/null +++ b/src/2geom/orphan-code/rtree.cpp @@ -0,0 +1,1350 @@ +#include <2geom/orphan-code/rtree.h> +#include <limits> + +/* +Based on source (BibTex): +@inproceedings{DBLP:conf/sigmod/Guttman84, + author = {Antonin Guttman}, + title = {R-Trees: A Dynamic Index Structure for Spatial Searching}, + booktitle = {SIGMOD Conference}, + year = {1984}, + pages = {47-57}, + ee = {http://doi.acm.org/10.1145/602259.602266, db/conf/sigmod/Guttman84.html}, +} +*/ + +/* +#define _RTREE_PRINT(x) std::cout << x << std::endl; +#define _RTREE_PRINT_TREE( x, y ) print_tree( x, y ); +#define _RTREE_PRINT_TREE_INS( x, y, z ) print_tree( x, y, z ); +*/ +//comment the following if you want output during RTree operations + + +#define _RTREE_PRINT(x) ; +#define _RTREE_PRINT_TREE( x, y ) ; +#define _RTREE_PRINT_TREE_INS( x, y, z ) ; + + + +/* +TODO 1 +some if(non_leaf) + else // leaf +could be eliminated when function starts from a leaf +do leaf action +then repeat function for non-leafs only +candidates: +- adjust_tree +- condense_tree + +TODO 2 +generalize in a different way the splitting techniques + +*/ + + +namespace Geom{ + +/*============================================================================= + insert +=============================================================================== +insert a new index entry E into the R-tree: + +I1) find position of new record: + choose_node will find a leaf node L (position) in which to place r +I2) add record to leaf node: + if L has room for another entry install E + else split_node will obtain L and LL containing E and all the old entries of L + from the available splitting strategies we chose quadtratic-cost algorithm (just to begin + with sth) + // TODO implement more of them +I3) propagate changes upward: + Invoke adjust_tree on L, also passing LL if a split was performed. +I4) grow tree taller: + if a node spilt propagation, cuased the root to split + create new root whose children are the 2 resulting nodes +*/ + +void RTree::insert( Rect const &r, unsigned shape ){ + _RTREE_PRINT("\n====================================="); + _RTREE_PRINT("insert"); + RTreeRecord_Leaf* leaf_record= new RTreeRecord_Leaf( r, shape ); + insert( *leaf_record ); +} + + + +void RTree::insert( const RTreeRecord_Leaf &leaf_record, + const bool &insert_high /* false */, + const unsigned &stop_height /* 0 */, + const RTreeRecord_NonLeaf &nonleaf_record /* 0 */ + ) +{ + _RTREE_PRINT("\n--------------"); + _RTREE_PRINT("insert private. element:" << leaf_record.data << " insert high:" << insert_high << " stop height:" << stop_height ); + RTreeNode *position = 0; + + // if tree is unused create the root Node, not described in source, stupid me :P + if(root == 0){ + root = new RTreeNode(); + } + + _RTREE_PRINT("I1"); // I1 + if( insert_high == false ){ // choose leaf node + position = choose_node( leaf_record.bounding_box ); + } + else { // choose nonleaf node + position = choose_node( nonleaf_record.bounding_box, insert_high, stop_height ); + } + _RTREE_PRINT("leaf node chosen: " ); + _RTREE_PRINT_TREE( position , 0 ); + std::pair< RTreeNode*, RTreeNode* > node_division; + + bool split_performed = false; + + if( position->children_nodes.size() > 0 ){ // non-leaf node: position + // we must reach here only to insert high non leaf node, not insert leaf node + assert( insert_high == true ); + + // put new element in node temporarily. Later on, if we need to, we will split the node. + position->children_nodes.push_back( nonleaf_record ); + if( position->children_nodes.size() <= max_records ){ + _RTREE_PRINT("I2 nonleaf: no split: " << position->children_nodes.size() ); // I2 + } + else{ + _RTREE_PRINT("I2 nonleaf: split: " << position->children_nodes.size() ); // I2 + node_division = split_node( position ); + split_performed = true; + } + + } + else { // leaf node: position: + // we must reach here only to insert leaf node, not insert high non leaf node + assert( insert_high == false ); + + + // put new element in node temporarily. Later on, if we need to, we will split the node. + position->children_leaves.push_back( leaf_record ); + if( position->children_leaves.size() <= max_records ){ + _RTREE_PRINT("I2 leaf: no split: " << position->children_leaves.size() ); // I2 + } + else{ + _RTREE_PRINT("I2 leaf: split: " << position->children_leaves.size() << " max_records:" << max_records); // I2 + node_division = split_node( position ); + split_performed = true; + + _RTREE_PRINT(" group A"); + _RTREE_PRINT_TREE( node_division.first , 3 ); + _RTREE_PRINT(" group B"); + _RTREE_PRINT_TREE( node_division.second , 3 ); + + } + + } + + _RTREE_PRINT("I3"); // I3 + bool root_split_performed = adjust_tree( position, node_division, split_performed ); + _RTREE_PRINT("root split: " << root_split_performed); + + +// _RTREE_PRINT("TREE:"); +// print_tree( root , 2 ); + + _RTREE_PRINT("I4"); // I4 + if( root_split_performed ){ + std::pair<RTreeNode*, RTreeNode*> root_division; + root_division = quadratic_split( root ); // AT5 + + Rect first_record_bounding_box; + Rect second_record_bounding_box; + + RTreeRecord_NonLeaf first_new_record = create_nonleaf_record_from_rtreenode( first_record_bounding_box, root_division.first ); + RTreeRecord_NonLeaf second_new_record = create_nonleaf_record_from_rtreenode( second_record_bounding_box, root_division.second ); + _RTREE_PRINT(" 1st:"); + _RTREE_PRINT_TREE( first_new_record.data, 5 ); + _RTREE_PRINT(" 2nd:"); + _RTREE_PRINT_TREE( second_new_record.data, 5 ); + + // *new* root is by definition non-leaf. Install the new records there + RTreeNode* new_root = new RTreeNode(); + new_root->children_nodes.push_back( first_new_record ); + new_root->children_nodes.push_back( second_new_record ); + + delete root; + + root = new_root; + tree_height++; // increse tree height + + _RTREE_PRINT_TREE( root, 5 ); + sanity_check( root, 0 ); + } + _RTREE_PRINT("done"); + + /* + the node_division.second is saved on the tree + the node_division.first was copied in existing tree in node + so we don't need this anymore + */ + delete node_division.first; +} + +/* I1 ========================================================================= + +original: choose_node will find a leaf node L in which to place r +changed to choose_node will find a node L in which to place r +the node L is: +non-leaf: if flag is set. the height of the node is insert_at_height +leaf: if flag is NOT set + +1) Initialize: set N to be the root node +2) Leaf Check: + insert_height = false + if N is leaf return N + insert_height = true +3) Choose subtree: If N not leaf OR not we are not in the proper height then + let F be an entry in N whose rect Fi needs least enlargement to include r + ties resolved with rect of smallest area +4) descend until a leaf is reached OR proper height is reached: set N to the child node pointed to by F and goto 2. +*/ + +// TODO keep stack with visited nodes + +RTreeNode* RTree::choose_node( const Rect &r, const bool &insert_high /* false */, const unsigned &stop_height /* 0 */) const { + + _RTREE_PRINT(" CL1");// CL1 + RTreeNode *pos = root; + + double min_enlargement; + double current_enlargement; + int node_min_enlargement; + unsigned current_height = 0; // zero is the root + + _RTREE_PRINT(" CL2 current_height:" << current_height << " stop_height:" << stop_height << " insert_high:" << insert_high); + // CL2 Leaf check && Height check + while( ( insert_high ? true : pos->children_nodes.size() != 0 ) + && ( insert_high ? current_height < stop_height : true ) ) + /* Leaf check, during insert leaf */ + /* node height check, during insert non-leaf */ + { + _RTREE_PRINT(" CL3 current_height:" << current_height << " stop_height:" << stop_height ); // CL3 + min_enlargement = std::numeric_limits<double>::max(); + current_enlargement = 0; + node_min_enlargement = 0; + + for(unsigned i=0; i< pos->children_nodes.size(); i++){ + current_enlargement = find_enlargement( pos->children_nodes[i].bounding_box, r ); + + // TODO tie not solved! + if( current_enlargement < min_enlargement ){ + node_min_enlargement = i; + min_enlargement = current_enlargement; + } + } + _RTREE_PRINT(" CL4"); // CL4 + // descend to the node with the min_enlargement + pos = pos->children_nodes[node_min_enlargement].data; + current_height++; // increase current visiting height + } + + return pos; +} + + +/* +find_enlargement: + +enlargement that "a" needs in order to include "b" +b is the new rect we want to insert. +a is the rect of the node we try to see if b should go in. +*/ +double RTree::find_enlargement( const Rect &a, const Rect &b ) const{ + + + Rect union_rect(a); + union_rect.unionWith(b); + + OptRect a_intersection_b = intersect( a, b ); + + // a, b do not intersect + if( a_intersection_b.empty() ){ + _RTREE_PRINT(" find_enlargement (no intersect): " << union_rect.area() - a.area() - b.area() ); + return union_rect.area() - a.area() - b.area(); + } + + // a, b intersect + + // a contains b + if( a.contains( b ) ){ + _RTREE_PRINT(" find_enlargement (intersect: a cont b): " << a.area() - b.area() ); + //return a.area() - b.area(); + return b.area() - a.area(); // enlargement is negative in this case. + } + + // b contains a + if( b.contains( a ) ){ + _RTREE_PRINT(" find_enlargement (intersect: b cont a): " << a.area() - b.area() ); + return b.area() - a.area(); + } + + // a partially cover b + _RTREE_PRINT(" find_enlargement (intersect: a partial cover b): " << union_rect.area() - a.area() - b.area() - a_intersection_b->area() ); + return union_rect.area() + - ( a.area() - a_intersection_b->area() ) + - ( b.area() - a_intersection_b->area() ); +} + + +/* I2 ========================================================================= +use one split strategy +*/ + +std::pair<RTreeNode*, RTreeNode*> RTree::split_node( RTreeNode *s ){ +/* + if( split_strategy == LINEAR_COST ){ + linear_cost_split( ............. ); + } +*/ + return quadratic_split( s ); // else QUADRATIC_SPIT +} + + +/*----------------------------------------------------------------------------- + Quadratic Split + +QS1) Pick first entry for each group: + Appy pick_seeds to choose 2 entries to be the first elements of the groups. Assign each one of + them to one group +QS2) check if done: + a) if all entries have been assinged stop + b) if one group has so few entries that all the rest must be assignmed to it, in order for it to + have the min number , assign them and stop +QS3) select entry and assign: + Inkvoke pick_next() to choose the next entry to assign. + *[in pick_next] Add it to the group whose covering rectangle will have to be enlrarged least to + accommodate it. Resolve ties by adding the entry to the group with the smaller are, then to the + one with fewer entries, then to either of the two. + goto 2. +*/ +std::pair<RTreeNode*, RTreeNode*> RTree::quadratic_split( RTreeNode *s ) { + + // s is the original leaf node or non-leaf node + RTreeNode* group_a = new RTreeNode(); // a is the 1st group + RTreeNode* group_b = new RTreeNode(); // b is the 2nd group + + + _RTREE_PRINT(" QS1"); // QS1 + std::pair<unsigned, unsigned> initial_seeds; + initial_seeds = pick_seeds(s); + + // if non-leaf node: s + if( s->children_nodes.size() > 0 ){ + _RTREE_PRINT(" non-leaf node"); + // each element is true if the node has been assinged to either "a" or "b" + std::vector<bool> assigned_v( s->children_nodes.size() ); + std::fill( assigned_v.begin(), assigned_v.end(), false ); + + group_a->children_nodes.push_back( s->children_nodes[initial_seeds.first] ); + assert(initial_seeds.first < assigned_v.size()); + assigned_v[ initial_seeds.first ] = true; + + group_b->children_nodes.push_back( s->children_nodes[initial_seeds.second] ); + assert(initial_seeds.second < assigned_v.size()); + assigned_v[ initial_seeds.second ] = true; + + _RTREE_PRINT(" QS2"); // QS2 + unsigned num_of_not_assigned = s->children_nodes.size() - 2; + // so far we have assinged 2 out of all + + while( num_of_not_assigned ){// QS2 a + _RTREE_PRINT(" QS2 b, num_of_not_assigned:" << num_of_not_assigned); // QS2 b + /* + we are on NON leaf node so children of split groups must be nodes + + Check each group to see if one group has so few entries that all the rest must + be assignmed to it, in order for it to have the min number. + */ + if( group_a->children_nodes.size() + num_of_not_assigned <= min_records ){ + // add the non-assigned to group_a + for(unsigned i = 0; i < assigned_v.size(); i++){ + if(assigned_v[i] == false){ + group_a->children_nodes.push_back( s->children_nodes[i] ); + assigned_v[i] = true; + } + } + break; + } + + if( group_b->children_nodes.size() + num_of_not_assigned <= min_records ){ + // add the non-assigned to group_b + for( unsigned i = 0; i < assigned_v.size(); i++ ){ + if( assigned_v[i] == false ){ + group_b->children_nodes.push_back( s->children_nodes[i] ); + assigned_v[i] = true; + } + } + break; + } + + _RTREE_PRINT(" QS3"); // QS3 + std::pair<unsigned, enum_add_to_group> next_element; + next_element = pick_next( group_a, group_b, s, assigned_v ); + if( next_element.second == ADD_TO_GROUP_A ){ + group_a->children_nodes.push_back( s->children_nodes[ next_element.first ] ); + } + else{ + group_b->children_nodes.push_back( s->children_nodes[ next_element.first ] ); + } + + num_of_not_assigned--; + } + } + // else leaf node: s + else{ + _RTREE_PRINT(" leaf node"); + // each element is true if the node has been assinged to either "a" or "b" + std::vector<bool> assigned_v( s->children_leaves.size() ); + std::fill( assigned_v.begin(), assigned_v.end(), false ); + + // assign 1st seed to group a + group_a->children_leaves.push_back( s->children_leaves[initial_seeds.first] ); + assert(initial_seeds.first < assigned_v.size()); + assigned_v[ initial_seeds.first ] = true; + + // assign 2nd seed to group b + group_b->children_leaves.push_back( s->children_leaves[initial_seeds.second] ); + assert(initial_seeds.second < assigned_v.size()); + assigned_v[ initial_seeds.second ] = true; + + _RTREE_PRINT(" QS2"); // QS2 + unsigned num_of_not_assigned = s->children_leaves.size() - 2; + // so far we have assinged 2 out of all + + while( num_of_not_assigned ){// QS2 a + _RTREE_PRINT(" QS2 b, num_of_not_assigned:" << num_of_not_assigned); // QS2 b + /* + we are on leaf node so children of split groups must be leaves + + Check each group to see if one group has so few entries that all the rest must + be assignmed to it, in order for it to have the min number. + */ + if( group_a->children_leaves.size() + num_of_not_assigned <= min_records ){ + _RTREE_PRINT(" add the non-assigned to group_a"); + // add the non-assigned to group_a + for( unsigned i = 0; i < assigned_v.size(); i++ ){ + if( assigned_v[i] == false ){ + group_a->children_leaves.push_back( s->children_leaves[i] ); + assigned_v[i] = true; + } + } + break; + } + + if( group_b->children_leaves.size() + num_of_not_assigned <= min_records ){ + _RTREE_PRINT(" add the non-assigned to group_b"); + // add the non-assigned to group_b + for( unsigned i = 0; i < assigned_v.size(); i++ ){ + if( assigned_v[i] == false ){ + group_b->children_leaves.push_back( s->children_leaves[i] ); + assigned_v[i] = true; + } + } + break; + } + + _RTREE_PRINT(" QS3"); // QS3 + std::pair<unsigned, enum_add_to_group> next_element; + next_element = pick_next(group_a, group_b, s, assigned_v); + if( next_element.second == ADD_TO_GROUP_A ){ + group_a->children_leaves.push_back( s->children_leaves[ next_element.first ] ); + } + else{ + group_b->children_leaves.push_back( s->children_leaves[ next_element.first ] ); + } + + num_of_not_assigned--; + } + } + assert( initial_seeds.first != initial_seeds.second ); + return std::make_pair( group_a, group_b ); +} + +/* +PS1) caclulate ineffeciency of grouping entries together: + Foreach pair of entries E1 (i), E2 (j) compose rectangle J (i_union_j) including E1, E2. + Calculate d = area(i_union_j) - area(i) - area(j) +PS2) choose the most wastefull pair: + Choose pair with largest d +*/ + +std::pair<unsigned, unsigned> RTree::pick_seeds( RTreeNode *s ) const{ + double current_d = 0; + double max_d = std::numeric_limits<double>::min(); + unsigned seed_a = 0; + unsigned seed_b = 1; + _RTREE_PRINT(" pick_seeds"); + + // if non leaf node: s + if( s->children_nodes.size() > 0 ){ + _RTREE_PRINT(" non leaf"); + _RTREE_PRINT(" PS1"); // PS1 + for( unsigned a = 0; a < s->children_nodes.size(); a++ ){ + // with j=i we check only the upper (diagonal) half + // with j=i+1 we also avoid checking for b==a (we don't need it) + for( unsigned b = a+1; b < s->children_nodes.size(); b++ ){ + _RTREE_PRINT(" PS2 " << a << " - " << b ); // PS2 + current_d = find_waste_area( s->children_nodes[a].bounding_box, s->children_nodes[b].bounding_box ); + + if( current_d > max_d ){ + max_d = current_d; + seed_a = a; + seed_b = b; + } + } + } + } + // else leaf node: s + else{ + _RTREE_PRINT(" leaf node"); + _RTREE_PRINT(" PS1"); // PS1 + for( unsigned a = 0; a < s->children_leaves.size(); a++ ){ + // with j=i we check only the upper (diagonal) half + // with j=i+1 we also avoid checking for j==i (we don't need this one) + for( unsigned b = a+1; b < s->children_leaves.size(); b++ ){ + _RTREE_PRINT(" PS2 " << s->children_leaves[a].data << ":" << s->children_leaves[a].bounding_box.area() + << " - " << s->children_leaves[b].data << ":" << s->children_leaves[b].bounding_box.area() ); // PS2 + current_d = find_waste_area( s->children_leaves[a].bounding_box, s->children_leaves[b].bounding_box ); + + if( current_d > max_d ){ + max_d = current_d; + seed_a = a; + seed_b = b; + } + } + } + } + _RTREE_PRINT(" seed_a: " << seed_a << " seed_b: " << seed_b ); + return std::make_pair( seed_a, seed_b ); +} + +/* +find_waste_area (used in pick_seeds step 1) + +for a pair A, B compose a rect union_rect that includes a and b +calculate area of union_rect - area of a - area b +*/ +double RTree::find_waste_area( const Rect &a, const Rect &b ) const{ + Rect union_rect(a); + union_rect.unionWith(b); + + return union_rect.area() - a.area() - b.area(); +} + +/* +pick_next: +select one remaining entry for classification in a group + +PN1) Determine cost of putting each entry in each group: + Foreach entry E not yet in a group, calculate + d1= area increase required in the cover rect of Group 1 to include E + d2= area increase required in the cover rect of Group 2 to include E +PN2) Find entry with greatest preference for each group: + Choose any entry with the maximum difference between d1 and d2 + +*/ + +std::pair<unsigned, enum_add_to_group> RTree::pick_next( RTreeNode* group_a, + RTreeNode* group_b, + RTreeNode* s, + std::vector<bool> &assigned_v ) +{ + double max_increase_difference = std::numeric_limits<double>::min(); + unsigned max_increase_difference_node = 0; + double current_increase_difference = 0; + + enum_add_to_group group_to_add = ADD_TO_GROUP_A; + + /* + bounding boxes of the 2 new groups. This info isn't available, since they + have no parent nodes (so that the parent node knows the bounding box). + */ + Rect bounding_box_a; + Rect bounding_box_b; + + double increase_area_a = 0; + double increase_area_b = 0; + + _RTREE_PRINT(" pick_next, assigned_v.size:" << assigned_v.size() ); + + // if non leaf node: one of the 2 groups (both groups are the same, either leaf/nonleaf) + if( group_a->children_nodes.size() > 0 ){ + _RTREE_PRINT(" non leaf"); + + // calculate the bounding boxes of the 2 new groups. + bounding_box_a = Rect( group_a->children_nodes[0].bounding_box ); + for( unsigned i = 1; i < group_a->children_nodes.size(); i++ ){ + bounding_box_a.unionWith( group_a->children_nodes[i].bounding_box ); + } + + bounding_box_b = Rect( group_b->children_nodes[0].bounding_box ); + for( unsigned i = 1; i < group_b->children_nodes.size(); i++ ){ + bounding_box_b.unionWith( group_b->children_nodes[i].bounding_box ); + } + + + _RTREE_PRINT(" PN1"); // PN1 + for( unsigned i = 0; i < assigned_v.size(); i++ ){ + _RTREE_PRINT(" i:" << i << " assigned:" << assigned_v[i]); + if( assigned_v[i] == false ){ + + increase_area_a = find_enlargement( bounding_box_a, s->children_nodes[i].bounding_box ); + increase_area_b = find_enlargement( bounding_box_b, s->children_nodes[i].bounding_box ); + + current_increase_difference = std::abs( increase_area_a - increase_area_b ); + _RTREE_PRINT(" PN2 " << i << ": " << current_increase_difference ); // PN2 + if( current_increase_difference > max_increase_difference ){ + max_increase_difference = current_increase_difference; + max_increase_difference_node = i; + + // TODO tie not solved! + if( increase_area_a < increase_area_b ){ + group_to_add = ADD_TO_GROUP_A; + } + else{ + group_to_add = ADD_TO_GROUP_B; + } + } + } + } + //assert(max_increase_difference_node >= 0); + assert(max_increase_difference_node < assigned_v.size()); + assigned_v[max_increase_difference_node] = true; + _RTREE_PRINT(" ... i:" << max_increase_difference_node << " assigned:" << assigned_v[max_increase_difference_node] ); + } + else{ // else leaf node + _RTREE_PRINT(" leaf"); + + // calculate the bounding boxes of the 2 new groups + bounding_box_a = Rect( group_a->children_leaves[0].bounding_box ); + for( unsigned i = 1; i < group_a->children_leaves.size(); i++ ){ + std::cout<< " lala"; + bounding_box_a.unionWith( group_a->children_leaves[i].bounding_box ); + } + + bounding_box_b = Rect( group_b->children_leaves[0].bounding_box ); + for( unsigned i = 1; i < group_b->children_leaves.size(); i++ ){ + std::cout<< " lala"; + bounding_box_b.unionWith( group_b->children_leaves[i].bounding_box ); + } + std::cout<< "" << std::endl; + + _RTREE_PRINT(" PN1"); // PN1 + for( unsigned i = 0; i < assigned_v.size(); i++ ){ + _RTREE_PRINT(" i:" << i << " assigned:" << assigned_v[i]); + if( assigned_v[i] == false ){ + increase_area_a = find_enlargement( bounding_box_a, s->children_leaves[i].bounding_box ); + increase_area_b = find_enlargement( bounding_box_b, s->children_leaves[i].bounding_box ); + + current_increase_difference = std::abs( increase_area_a - increase_area_b ); + _RTREE_PRINT(" PN2 " << i << ": " << current_increase_difference ); // PN2 + + if( current_increase_difference > max_increase_difference ){ + max_increase_difference = current_increase_difference; + max_increase_difference_node = i; + + // TODO tie not solved! + if( increase_area_a < increase_area_b ){ + group_to_add = ADD_TO_GROUP_A; + } + else{ + group_to_add = ADD_TO_GROUP_B; + } + } + } + } + assert(max_increase_difference_node < assigned_v.size()); + assigned_v[max_increase_difference_node] = true; + _RTREE_PRINT(" ... i:" << max_increase_difference_node << " assigned:" << assigned_v[max_increase_difference_node] ); + } + + _RTREE_PRINT(" node:" << max_increase_difference_node << " added:" << group_to_add ); + return std::make_pair( max_increase_difference_node, group_to_add ); +} + +/* I3 ========================================================================= + +adjust_tree: +Ascend from a leaf node L to root, adjusting covering rectangles and propagating node splits as +necessary + +We modified this one from the source in the step AT1 and AT5 + +AT1) Initialize: + Set N=L + IF L was spilt previously, set NN to be the resulting second node AND + (not mentioned in the original source but that's what it should mean) + Assign all entries of first node to L +AT2) check if done: + IF N is root stop +AT3) adjust covering rectangle in parent entry + 1) Let P be the parent of N + 2) Let EN be the N's entry in P + 3) Adjust EN bounding box so that it tightly enclosses all entry rectangles in N +AT4) Propagate node split upward + IF N has a partner NN resulting from an earlier split + create a new entry ENN with ENN "p" pointing to NN and ENN bounding box enclosing all + rectangles in NN + + IF there is room in P add ENN + ELSE invoke split_node to produce P an PP containing ENN and all P's old entries. +AT5) Move up to next level + Set N=P, + IF a split occurred, set NN=PP + goto AT1 (was goto AT2) +*/ + +bool RTree::adjust_tree( RTreeNode* position, + // modified: it holds the last split group + std::pair<RTreeNode*, RTreeNode*> &node_division, + bool initial_split_performed) +{ + RTreeNode* parent; + unsigned child_in_parent; // the element in parent node that points to current posistion + std::pair< RTreeNode*, bool > find_result; + bool split_performed = initial_split_performed; + bool root_split_performed = false; + + _RTREE_PRINT(" adjust_tree"); + _RTREE_PRINT(" AT1"); + + while( true ){ + _RTREE_PRINT(" ------- current tree status:"); + _RTREE_PRINT_TREE_INS(root, 2, true); + + // check for loop BREAK + if( position == root ){ + _RTREE_PRINT(" AT2: found root"); + if( split_performed ){ + root_split_performed = true; + } + break; + } + + if( split_performed ){ + copy_group_a_to_existing_node( position, node_division.first ); + } + + /* + pick randomly, let's say the 1st entry of the current node. + Search for this spatial area in the tree, and stop to the parent node. + Then find position of current node pointer, in the parent node. + */ + _RTREE_PRINT(" AT3.1"); // AT3.1 Let P be the parent of N + if( position->children_nodes.size() > 0 ){ + find_result = find_parent( root, position->children_nodes[0].bounding_box, position); + } + else{ + find_result = find_parent( root, position->children_leaves[0].bounding_box, position); + } + parent = find_result.first; + + // parent is a non-leaf, by definition + _RTREE_PRINT(" AT3.2"); // AT3.2 Let EN be the N's entry in P + for( child_in_parent = 0; child_in_parent < parent->children_nodes.size(); child_in_parent++ ){ + if( parent->children_nodes[ child_in_parent ].data == position){ + _RTREE_PRINT(" child_in_parent: " << child_in_parent); + break; + } + } + + _RTREE_PRINT(" AT3.3"); + // AT3.2 Adjust EN bounding box so that it tightly enclosses all entry rectangles in N + recalculate_bounding_box( parent, position, child_in_parent ); + + + _RTREE_PRINT(" AT4"); // AT4 + if( split_performed ){ + // create new record (from group_b) + //RTreeNode* new_node = new RTreeNode(); + Rect new_record_bounding; + + RTreeRecord_NonLeaf new_record = create_nonleaf_record_from_rtreenode( new_record_bounding, node_division.second ); + + // install new entry (group_b) + if( parent->children_nodes.size() < max_records ){ + parent->children_nodes.push_back( new_record ); + split_performed = false; + } + else{ + parent->children_nodes.push_back( new_record ); + node_division = quadratic_split( parent ); // AT5 + split_performed = true; + } + + } + _RTREE_PRINT(" AT5"); // AT5 + position = parent; + } + + return root_split_performed; +} + +/* +find_parent: +The source only mentions that we should "find" the parent. But it doesn't seay how. So we made a +modification of search. + +Initially we take the root, a rect of the node, of which the parent we look for and the node we seek + +We do a spatial search for this rect. If we find get an intersecttion with the rect we check if the +child is the one we look for. +If not we call find_parent again recursively +*/ + +std::pair< RTreeNode*, bool > RTree::find_parent( RTreeNode* subtree_root, + Rect search_area, + RTreeNode* wanted) const +{ + _RTREE_PRINT("find_parent"); + + std::pair< RTreeNode*, bool > result; + if( subtree_root->children_nodes.size() > 0 ){ + + for( unsigned i=0; i < subtree_root->children_nodes.size(); i++ ){ + if( subtree_root->children_nodes[i].data == wanted){ + _RTREE_PRINT("FOUND!!"); // non leaf node + return std::make_pair( subtree_root, true ); + } + + if( subtree_root->children_nodes[i].bounding_box.intersects( search_area ) ){ + result = find_parent( subtree_root->children_nodes[i].data, search_area, wanted); + if ( result.second ){ + break; + } + } + } + } + return result; +} + + +void RTree::copy_group_a_to_existing_node( RTreeNode *position, RTreeNode* group_a ){ + // clear position (the one that was split) and put there all the nodes of group_a + if( position->children_nodes.size() > 0 ){ + _RTREE_PRINT(" copy_group...(): install group A to existing non-leaf node"); + // non leaf-node: position + position->children_nodes.clear(); + for(auto & children_node : group_a->children_nodes){ + position->children_nodes.push_back( children_node ); + } + } + else{ + _RTREE_PRINT(" copy_group...(): install group A to existing leaf node"); + // leaf-node: positions + position->children_leaves.clear(); + for(auto & children_leave : group_a->children_leaves){ + position->children_leaves.push_back( children_leave ); + } + } +} + + + +RTreeRecord_NonLeaf RTree::create_nonleaf_record_from_rtreenode( Rect &new_entry_bounding, RTreeNode* rtreenode ){ + + if( rtreenode->children_nodes.size() > 0 ){ + // found bounding box of new entry + new_entry_bounding = Rect( rtreenode->children_nodes[0].bounding_box ); + for(unsigned i = 1; i < rtreenode->children_nodes.size(); i++ ){ + new_entry_bounding.unionWith( rtreenode->children_nodes[ i ].bounding_box ); + } + } + else{ // non leaf: rtreenode + // found bounding box of new entry + new_entry_bounding = Rect( rtreenode->children_leaves[0].bounding_box ); + for(unsigned i = 1; i < rtreenode->children_leaves.size(); i++ ){ + new_entry_bounding.unionWith( rtreenode->children_leaves[ i ].bounding_box ); + } + } + return RTreeRecord_NonLeaf( new_entry_bounding, rtreenode ); +} + + + +/* + print the elements of the tree + based on ordered tree walking +*/ +void RTree::print_tree(RTreeNode* subtree_root, int depth ) const{ + + if( subtree_root->children_nodes.size() > 0 ){ + + // descend in each one of the elements and call print_tree + for( unsigned i=0; i < subtree_root->children_nodes.size(); i++ ){ + //print spaces for indentation + for(int j=0; j < depth; j++){ + std::cout << " " ; + } + + std::cout << subtree_root->children_nodes[i].bounding_box << ", " << subtree_root->children_nodes.size() << std::endl ; + _RTREE_PRINT_TREE_INS( subtree_root->children_nodes[i].data, depth+1, used_during_insert); + } + + } + else{ + for(int j=0; j < depth; j++){ + std::cout << " " ; + } + std::cout << subtree_root->children_leaves.size() << ": " ; + + // print all the elements of the leaf node + for(auto & children_leave : subtree_root->children_leaves){ + std::cout << children_leave.data << ", " ; + } + std::cout << std::endl ; + + } +} + + +void RTree::sanity_check(RTreeNode* subtree_root, int depth, bool used_during_insert ) const{ + + if( subtree_root->children_nodes.size() > 0 ){ + // descend in each one of the elements and call sanity_check + for(auto & children_node : subtree_root->children_nodes){ + sanity_check( children_node.data, depth+1, used_during_insert); + } + + + // sanity check + if( subtree_root != root ){ + assert( subtree_root->children_nodes.size() >= min_records); + } +/* + else{ + assert( subtree_root->children_nodes.size() >= 1); + } +*/ + + if( used_during_insert ){ + // allow one more during for insert + assert( subtree_root->children_nodes.size() <= max_records + 1 ); + } + else{ + assert( subtree_root->children_nodes.size() <= max_records ); + } + + } + else{ + // sanity check + if( subtree_root != root ){ + assert( subtree_root->children_leaves.size() >= min_records); + } +/* + else{ + assert( subtree_root->children_leaves.size() >= 1); + } +*/ + + if( used_during_insert ){ + // allow one more during for insert + assert( subtree_root->children_leaves.size() <= max_records + 1 ); + } + else{ + assert( subtree_root->children_nodes.size() <= max_records ); + } + } +} + + + +/*============================================================================= + search +=============================================================================== +Given an RTree whose root node is T find all index records whose rects overlap search rect S +S1) Search subtrees: + IF T isn't a leaf, check every entry E to determine whether E I overlaps S + FOR all overlapping entries invoke Search on the tree whose root node is pointed by E P +S2) ELSE T is leaf + check all entries E to determine whether E I overlaps S + IF so E is a qualifying record +*/ + + +void RTree::search( const Rect &search_area, std::vector< int >* result, const RTreeNode* subtree ) const { + // S1 + if( subtree->children_nodes.size() > 0 ){ // non-leaf: subtree + for(const auto & children_node : subtree->children_nodes){ + if( children_node.bounding_box.intersects( search_area ) ){ + search( search_area, result, children_node.data ); + } + } + } + // S2 + else{ // leaf: subtree + for(const auto & children_leave : subtree->children_leaves){ + if( children_leave.bounding_box.intersects( search_area ) ){ + result->push_back( children_leave.data ); + } + } + } +} + + +/*============================================================================= + erase +=============================================================================== +we changed steps D2) +D1) Find node containing record + Invoke find_leaf() to locate the leaf node L containing E + IF record is found stop +D2) Delete record + Remove E from L (it happened in find_leaf step FL2) +D3) Propagate changes + Invoke condense_tree, passing L +D4) Shorten tree + If root node has only one child, after the tree was adjusted, make the child new root + +return +0 on success +1 in case no entry was found + +*/ +//int RTree::erase( const RTreeRecord_Leaf & record_to_erase ){ +int RTree::erase( const Rect &search_area, const int shape_to_delete ){ + _RTREE_PRINT("\n====================================="); + _RTREE_PRINT("erase element: " << shape_to_delete); + // D1 + D2: entry is deleted in find_leaf + _RTREE_PRINT("D1 & D2 : find and delete the leaf"); + RTreeNode* contains_record = find_leaf( root, search_area, shape_to_delete ); + if( !contains_record ){ // no entry returned from find_leaf + return 1; // no entry found + } + + // D3 + //bool root_elimination_performed = condense_tree( contains_record ); + + // D4 + + //if( root_elimination_performed ){ + if( root->children_nodes.size() > 0 ){ // non leaf: root + // D4 + if( root->children_nodes.size() == 1 ){ + _RTREE_PRINT("D4 : non leaf: "); + tree_height--; + RTreeNode* t = root; + root = root->children_nodes[0].data; + delete t; + } + + } + else { // leaf: root + // D4 + // do nothing + } + sanity_check( root, 0 ); + return 0; // success +} + + +/* + find_leaf() +Given an RTree whose root node is T find the leaf node containing index entry E + +FL1) Search subtrees + IF T is non leaf, check each entry F in T to determine if F I overlaps E I + foreach such entry invoke find_leaf on the tree whose root is pointed to by F P until E is + found or all entries have been checked +FL2) search leaf node for record + IF T is leaf, check each entry to see if it matches E + IF E is found return T + AND delete element E (step D2) +*/ + +RTreeNode* RTree::find_leaf( RTreeNode* subtree, const Rect &search_area, const int shape_to_delete ) const { + // FL1 + if( subtree->children_nodes.size() > 0 ){ // non-leaf: subtree + for(auto & children_node : subtree->children_nodes){ + if( children_node.bounding_box.intersects( search_area ) ){ + RTreeNode* t = find_leaf( children_node.data, search_area, shape_to_delete ); + if( t ){ // if search was successful terminate + return t; + } + } + } + } + // FL2 + else{ // leaf: subtree + for( std::vector< RTreeRecord_Leaf >::iterator it = subtree->children_leaves.begin(); it!=subtree->children_leaves.end(); ++it ){ + if( it->data == shape_to_delete ){ + // delete element: implement step D2) + subtree->children_leaves.erase( it ); + return subtree; + } + } + } + return 0; +} + + +/* + condense_tree() +Given a Leaf node L from which an entry has been deleted eliminate the node if it has too few entries and reallocate its entries +Propagate node elimination upwards as necessary +Adjust all covering recsts n the path to the root making them smaller if possible + +CT1) Initialize + Set N=L + Set Q the set of eliminated nodes to be empty +CT2) // Find parent entry (was there but doesn't make sense) + IF N is the root + goto CT6 + ELSE + 1) Find parent entry + 2) let P be the parent of N + 3) and let EN be N's entry in P +CT3) IF N has fewer than m entries + Eliminate underfull node + 1) delete EN from P + 2) and add N to set Q +CT4) ELSE + adjust EN I to tightly contain all entries in N +CT5) move up one level in tree + set N=P and repeat from CT2 + +CT6) Re insert orphaned entries + Re-inser all entreis of nodes in set Q + Entries from eliminated leaf nodes are re-inserted in tree leaves (like in insert) + BUT non-leaf nodes must be placed higher in the tree so that leaves of their dependent subtrees + will be on the same level as leaves of the main tree. (on the same height they originally were) + (not mentioned in the source description: the criteria for placing the node should be + again TODO ??? least enlargement) + +*/ +// TODO this can be merged with adjust_tree or refactor to reutilize some parts. less readable +bool RTree::condense_tree( RTreeNode* position ) +{ + RTreeNode* parent; + unsigned child_in_parent = 0; // the element in parent node that points to current posistion + + std::pair< RTreeNode*, bool > find_result; + bool elimination_performed = false; + bool root_elimination_performed = false; + unsigned current_height = tree_height+1; + Rect special_case_bounding_box; + _RTREE_PRINT(" condense_tree"); + _RTREE_PRINT(" CT1"); + // leaf records that were eliminated due to under-full node + std::vector< RTreeRecord_Leaf > Q_leaf_records( 0 ); + + // < non-leaf records, their height > that were eliminated due to under-full node + std::vector< std::pair< RTreeRecord_NonLeaf, unsigned > > Q_nonleaf_records( 0 ); + + + while( true ){ + + // check for loop BREAK + if( position == root ){ + _RTREE_PRINT(" CT2 position is root"); + if( elimination_performed ){ + root_elimination_performed = true; + } + break; + } + + /* + pick randomly, let's say the 1st entry of the current node. + Search for this spatial area in the tree, and stop to the parent node. + Then find position of current node pointer, in the parent node. + */ + /* + special case. if elimination due to children being underfull was performed AND + AND parent had only 1 record ,then this one record was removed. + */ + if( position->children_nodes.size() > 0 ){ + _RTREE_PRINT(" CT2.1 - 2 non leaf: find parent, P is parent"); + // CT2.1 find parent. By definition it's nonleaf + find_result = find_parent( root, position->children_nodes[0].bounding_box, position); + } + else if( position->children_nodes.size() == 0 + && position->children_leaves.size() == 0 + && elimination_performed ) + { // special case + _RTREE_PRINT(" CT2.1 - 2 special case: find parent, P is parent"); + // CT2.1 find parent. By definition it's nonleaf + find_result = find_parent( root, special_case_bounding_box, position); + } + else{ + _RTREE_PRINT(" CT2.1 - 2 leaf: find parent, P is parent"); + // CT2.1 find parent. By definition it's nonleaf + find_result = find_parent( root, position->children_leaves[0].bounding_box, position); + } + // CT2.2 Let P be the parent of N + parent = find_result.first; + + + // parent is a non-leaf, by definition. Calculate "child_in_parent" + _RTREE_PRINT(" CT2.3 find in parent, position's record EN"); + // CT2.3 Let EN be the N's entry in P + for( child_in_parent = 0; child_in_parent < parent->children_nodes.size(); child_in_parent++ ){ + if( parent->children_nodes[ child_in_parent ].data == position){ + _RTREE_PRINT(" child_in_parent: " << child_in_parent << " out of " << parent->children_nodes.size() << " (size)" ); + break; + } + } + + if( position->children_nodes.size() > 0 ){ // non leaf node: position + _RTREE_PRINT(" CT3 nonleaf: eliminate underfull node"); + // CT3 Eliminate underfull node + if( position->children_nodes.size() < min_records ){ + _RTREE_PRINT(" CT3.2 add N to Q"); + // CT3.2 add N to set Q ( EN the record that points to N ) + for(auto & children_node : position->children_nodes){ + _RTREE_PRINT(" i " << i ); + std::pair< RTreeRecord_NonLeaf, unsigned > t = std::make_pair( children_node, current_height-1); + Q_nonleaf_records.push_back( t ); + + } + special_case_bounding_box = parent->children_nodes[ child_in_parent ].bounding_box; + + _RTREE_PRINT(" CT3.1 delete in parent, position's record EN"); + // CT3.1 delete EN from P ( parent is by definition nonleaf ) + if( remove_record_from_parent( parent, position ) ){ // TODO does erase, delete to the pointer ??? + _RTREE_PRINT(" remove_record_from_parent error "); + } + elimination_performed = true; + } + else{ + _RTREE_PRINT(" CT4 "); /// CT4) if not underfull + recalculate_bounding_box( parent, position, child_in_parent ); + elimination_performed = false; + } + + } + else{ // leaf node: position + _RTREE_PRINT(" CT3 leaf: eliminate underfull node"); + // CT3 Eliminate underfull node + if( position->children_leaves.size() < min_records ){ + _RTREE_PRINT(" CT3.2 add N to Q " << position->children_leaves.size() ); + // CT3.2 add N to set Q + for(auto & children_leave : position->children_leaves){ + _RTREE_PRINT(" i " << i ); + Q_leaf_records.push_back( children_leave ); // TODO problem here + special_case_bounding_box = children_leave.bounding_box; + } + + _RTREE_PRINT(" CT3.1 delete in parent, position's record EN"); + // CT3.1 delete EN from P ( parent is by definition nonleaf ) + if( remove_record_from_parent( parent, position ) ){ + _RTREE_PRINT(" remove_record_from_parent error "); + } + elimination_performed = true; + } + else{ + _RTREE_PRINT(" CT4 "); /// CT4) if not underfull + recalculate_bounding_box( parent, position, child_in_parent ); + elimination_performed = false; + } + } + _RTREE_PRINT(" CT5 ");// CT5) move up one level in tree + position = parent; + + current_height--; + } + // CT6: reinsert + _RTREE_PRINT(" ------ Q_leaf"); + for( std::vector< RTreeRecord_Leaf >::iterator it = Q_leaf_records.begin(); it != Q_leaf_records.end(); ++it ){ + _RTREE_PRINT(" leaf:" << (*it).data); + } + _RTREE_PRINT(" ------ Q_nonleaf"); + for( std::vector< std::pair< RTreeRecord_NonLeaf, unsigned > >::iterator it = Q_nonleaf_records.begin(); it != Q_nonleaf_records.end(); ++it ){ + _RTREE_PRINT(" ------- " << it->second ); + _RTREE_PRINT_TREE( it->first.data, 0); + } + + _RTREE_PRINT(" CT6 "); + for(auto & Q_leaf_record : Q_leaf_records){ + insert( Q_leaf_record ); + _RTREE_PRINT(" inserted leaf:" << (*it).data << " ------------"); + _RTREE_PRINT_TREE( root, 0); + } + + + for(auto & Q_nonleaf_record : Q_nonleaf_records){ + insert( RTreeRecord_Leaf() , true, Q_nonleaf_record.second, Q_nonleaf_record.first ); + _RTREE_PRINT(" inserted nonleaf------------"); + _RTREE_PRINT_TREE( root, 0); + // TODO this fake RTreeRecord_Leaf() looks stupid. find better way to do this ??? + } + + return root_elimination_performed; +} + + +/* +given: +- a parent +- a child node +- and the position of the child node in the parent +recalculate the parent record's bounding box of the child, in order to ightly contain all entries of child + +NOTE! child must be indeed child of the parent, otherwise it screws up things. So find parent and child +before calling this function +*/ +void RTree::recalculate_bounding_box( RTreeNode* parent, RTreeNode* child, unsigned &child_in_parent ) { + if( child->children_nodes.size() > 0 ){ + _RTREE_PRINT(" non-leaf: recalculate bounding box of parent "); // non leaf-node: child + parent->children_nodes[ child_in_parent ].bounding_box = Rect( child->children_nodes[0].bounding_box ); + for( unsigned i=1; i < child->children_nodes.size(); i++ ){ + parent->children_nodes[ child_in_parent ].bounding_box.unionWith( child->children_nodes[i].bounding_box ); + } + } + else{ + _RTREE_PRINT(" leaf: recalculate bounding box of parent "); // leaf-node: child + parent->children_nodes[ child_in_parent ].bounding_box = Rect( child->children_leaves[0].bounding_box ); + + for( unsigned i=1; i < child->children_leaves.size(); i++ ){ + parent->children_nodes[ child_in_parent ].bounding_box.unionWith( child->children_leaves[i].bounding_box ); + } + } +} + +/* +given: +- a parent +- a child node +it removes the child record from the parent + +NOTE! child must be indeed child of the parent, otherwise it screws up things. +So find parent and child before calling this function +*/ +int RTree::remove_record_from_parent( RTreeNode* parent, RTreeNode* child ) { + _RTREE_PRINT( "remove_record_from_parent)" ); + for( std::vector< RTreeRecord_NonLeaf >::iterator it = parent->children_nodes.begin(); it!=parent->children_nodes.end(); ++it ){ + if( it->data == child ){ + // delete element: implement step D2) + parent->children_nodes.erase( it ); + return 0; // success + } + } + return 1; // failure +} + +/*============================================================================= +TODO update +=============================================================================== +*/ + + +}; + +/* + 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/parallelogram.cpp b/src/2geom/parallelogram.cpp new file mode 100644 index 0000000..b477a01 --- /dev/null +++ b/src/2geom/parallelogram.cpp @@ -0,0 +1,136 @@ +/* + * Authors: + * Thomas Holder + * Sergei Izmailov + * + * Copyright 2020 Authors + * + * SPDX-License-Identifier: LGPL-2.1 or MPL-1.1 + */ + +#include <2geom/basic-intersection.h> +#include <2geom/parallelogram.h> + +#include <cassert> + +namespace Geom { + +namespace { +/// Return true if `p` is inside a unit rectangle +inline bool unit_rect_contains(Point const &p) +{ + return 0 <= p.x() && p.x() <= 1 && // + 0 <= p.y() && p.y() <= 1; +} + +/// N'th corner of a unit rectangle +inline Point unit_rect_corner(unsigned i) +{ + assert(i < 4); + unsigned const y = i >> 1; + unsigned const x = (i & 1) ^ y; + return Point(x, y); +} +} // namespace + +Point Parallelogram::corner(unsigned i) const +{ + assert(i < 4); + return unit_rect_corner(i) * m_affine; +} + +Rect Parallelogram::bounds() const +{ + Rect rect(corner(0), corner(2)); + rect.expandTo(corner(1)); + rect.expandTo(corner(3)); + return rect; +} + +bool Parallelogram::intersects(Parallelogram const &other) const +{ + if (m_affine.isSingular() || other.m_affine.isSingular()) { + return false; + } + + auto const affine1 = other.m_affine * m_affine.inverse(); + auto const affine2 = affine1.inverse(); + + // case 1: any corner inside the other rectangle + for (unsigned i = 0; i != 4; ++i) { + auto const p = unit_rect_corner(i); + if (unit_rect_contains(p * affine1) || // + unit_rect_contains(p * affine2)) { + return true; + } + } + + // case 2: any sides intersect (check diagonals) + for (unsigned i = 0; i != 2; ++i) { + auto const A = corner(i); + auto const B = corner((i + 2) % 4); + for (unsigned j = 0; j != 2; ++j) { + auto const C = other.corner(j); + auto const D = other.corner((j + 2) % 4); + if (non_collinear_segments_intersect(A, B, C, D)) { + return true; + } + } + } + + return false; +} + +bool Parallelogram::contains(Point const &p) const +{ + return !m_affine.isSingular() && // + unit_rect_contains(p * m_affine.inverse()); +} + +bool Parallelogram::contains(Parallelogram const &other) const +{ + if (m_affine.isSingular()) { + return false; + } + + auto const inv = m_affine.inverse(); + + for (unsigned i = 0; i != 4; ++i) { + if (!unit_rect_contains(other.corner(i) * inv)) { + return false; + } + } + + return true; +} + +Coord Parallelogram::minExtent() const +{ + return std::min(m_affine.expansionX(), // + m_affine.expansionY()); +} + +Coord Parallelogram::maxExtent() const +{ + return std::max(m_affine.expansionX(), // + m_affine.expansionY()); +} + +bool Parallelogram::isSheared(Coord eps) const +{ + return !are_near(dot(m_affine.xAxis(), m_affine.yAxis()), // + 0.0, 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/parting-point.cpp b/src/2geom/parting-point.cpp new file mode 100644 index 0000000..3e3e803 --- /dev/null +++ b/src/2geom/parting-point.cpp @@ -0,0 +1,280 @@ +/** @file Implementation of parting_point(Path const&, Path const&, Coord) + */ +/* An algorithm to find the first parting point of two paths. + * + * Authors: + * RafaÅ‚ Siejakowski <rs@rs-math.net> + * + * Copyright 2022 the 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/point.h> + +namespace Geom +{ + +PathIntersection parting_point(Path const &first, Path const &second, Coord precision) +{ + Path const *paths[2] = { &first, &second }; + Point const starts[2] = { first.initialPoint(), second.initialPoint() }; + + if (!are_near(starts[0], starts[1], precision)) { + auto const invalid = PathTime(0, -1.0); + return PathIntersection(invalid, invalid, middle_point(starts[0], starts[1])); + } + + if (first.empty() || second.empty()) { + auto const start_time = PathTime(0, 0.0); + return PathIntersection(start_time, start_time, middle_point(starts[0], starts[1])); + } + + size_t const curve_count[2] = { first.size(), second.size() }; + Coord const max_time[2] = { first.timeRange().max(), second.timeRange().max() }; + + /// Curve indices up until which the paths are known to overlap + unsigned pos[2] = { 0, 0 }; + /// Curve times on the curves with indices pos[] up until which the + /// curves are known to overlap ahead of the nodes. + Coord curve_times[2] = { 0.0, 0.0 }; + + bool leg = 0; ///< Flag indicating which leg is stepping on the ladder + bool just_changed_legs = false; + + /* The ladder algorithm takes steps along the two paths, as if they the stiles of + * an imaginary ladder. Note that the nodes (X) on boths paths may not coincide: + * + * paths[0] START--------X-----------X-----------------------X---------X----> ... + * paths[1] START--------------X-----------------X-----------X--------------> ... + * + * The variables pos[0], pos[1] are the indices of the nodes we've cleared so far; + * i.e., we know that the portions before pos[] overlap. + * + * In each iteration of the loop, we move to the next node along one of the paths; + * the variable `leg` tells us which path. We find the point nearest to that node + * on the first unprocessed curve of the other path and check the curve time. + * + * Suppose the current node positions are denoted by P; one possible location of + * the nearest point (N) to the next node is: + * + * ----P------------------N--X---- paths[!leg] + * --------P--------------X------- paths[leg] (we've stepped forward from P to X) + * + * We detect this situation when we find that the curve time of N is < 1.0. + * We then create a trimmed version of the top curve so that it corresponds to + * the current bottom curve: + * + * ----P----------------------N--X---------- paths[!leg] + * [------------------] trimmed curve + * --------P------------------X------------- paths[leg] + * + * Using isNear(), we can compare the trimmed curve to the front curve (P--X) on + * paths[leg]; if they are indeed near, then pos[leg] can be incremented. + * + * Another possibility is that we overstep the end of the other curve: + * + * ----P-----------------X------------------ paths[!leg] + * N + * --------P------------------X------------- paths[leg] + * + * so the nearest point N now coincides with a node on the top path. We detect + * this situation by observing that the curve time of N is close to 1. In case + * of such overstep, we change legs by flipping the `leg` variable: + * + * ----P-----------------X------------------ paths[leg] + * --------P------------------X------------- paths[!leg] + * + * We can now continue the stepping procedure, but the next step will be taken on + * the path `paths[leg]`, so it should be a shorter step (if it isn't, the paths + * must have diverged and we're done): + * + * ----P-----------------X------------------ paths[leg] + * --------P-------------N----X------------- paths[!leg] + * + * Another piece of data we hold on to are the curve times on the current curves + * up until which the paths have been found to coincide. In other words, at every + * step of the algorithm we know that the curves agree up to the path-times + * PathTime(pos[i], curve_times[i]). + * + * In the situation mentioned just above, the times (T) will be as follows: + * + * ----P---T-------------X------------------ paths[leg] + * + * --------P-------------N----X------------- paths[!leg] + * T + * + * In this example, the time on top path is > 0.0, since the T mark is further + * ahead than P on that path. This value of the curve time is needed to correctly + * crop the top curve for the purpose of the isNear() comparison: + * + * ----P---T-------------X---------- paths[leg] + * [-------------] comparison curve (cropped from paths[leg]) + * [-------------] comparison curve (cropped from paths[!leg]) + * --------P-------------N----X----- paths[!leg] + * T + * + * In fact, the lower end of the curve time range for cropping is always + * given by curve_times[i]. + * + * The iteration ends when we find that the two paths have diverged or when we + * reach the end. When that happens, the positions and curve times will be + * the PathTime components of the actual point of divergence on both paths. + */ + + /// A closure to crop and compare the curve pieces ([----] in the diagrams above). + auto const pieces_agree = [&](Coord time_on_other) -> bool { + Curve *pieces[2]; + // The leg-side curve is always cropped to the end: + pieces[ leg] = paths[ leg]->at(pos[ leg]).portion(curve_times[ leg], 1.0); + // The other one is cropped to a variable curve time: + pieces[!leg] = paths[!leg]->at(pos[!leg]).portion(curve_times[!leg], time_on_other); + bool ret = pieces[0]->isNear(*pieces[1], precision); + delete pieces[0]; + delete pieces[1]; + return ret; + }; + + /// A closure to skip degenerate curves; returns true if we reached the end. + auto const skip_degenerates = [&](size_t which) -> bool { + while (paths[which]->at(pos[which]).isDegenerate()) { + ++pos[which]; + curve_times[which] = 0.0; + if (pos[which] == curve_count[which]) { + return true; // We've reached the end + } + } + return false; + }; + + // Main loop of the ladder algorithm. + while (pos[0] < curve_count[0] && pos[1] < curve_count[1]) { + // Skip degenerate curves if any. + if (skip_degenerates(0)) { + break; + } + if (skip_degenerates(1)) { + break; + } + + // Try to step to the next node with the current leg and see what happens. + Coord forward_coord = (Coord)(pos[leg] + 1); + if (forward_coord > max_time[leg]) { + forward_coord = max_time[leg]; + } + auto const step_point = paths[leg]->pointAt(forward_coord); + auto const time_on_other = paths[!leg]->at(pos[!leg]).nearestTime(step_point); + + if (are_near(time_on_other, 1.0, precision) && + are_near(step_point, paths[!leg]->pointAt(pos[!leg] + 1), precision)) + { // The step took us very near to the first uncertified node on the other path. + just_changed_legs = false; + // + // -------PT-----------------X---------- paths[!leg] + // --P-----T-----------------X---------- paths[leg] + // ^ + // endpoints (almost) coincide + // + // We should compare the curves cropped to the end: + // + // --------T-----------------X---------- paths[!leg] + // [-----------------] + // [-----------------] + // --------T-----------------X---------- paths[leg] + if (pieces_agree(1.0)) { + // The curves are nearly identical, so we advance both positions + // and zero out the forward curve times. + for (size_t i = 0; i < 2; i++) { + pos[i]++; + curve_times[i] = 0.0; + } + } else { // We've diverged. + break; + } + } else if (time_on_other < 1.0 - precision) { + just_changed_legs = false; + + // The other curve is longer than our step! We trim the other curve to the point + // nearest to the step point and compare the resulting pieces. + // + // --------T-----------------N--------X---- paths[!leg] + // [-----------------] + // [-----------------] + // --------T-----------------X------------- paths[leg] + // + if (pieces_agree(time_on_other)) { // The curve pieces are near to one another! + // We can advance our position and zero out the curve time: + pos[leg]++; + curve_times[leg] = 0.0; + // But on the other path, we can only advance the time, not the curve index: + curve_times[!leg] = time_on_other; + } else { // We've diverged. + break; + } + } else { + // The other curve is shorter than ours, which means that we've overstepped. + // We change legs and try to take a shorter step in the next iteration. + if (just_changed_legs) { + // We already changed legs before and it didn't help, i.e., we've diverged. + break; + } else { + leg = !leg; + just_changed_legs = true; + } + } + } + + // Compute the parting time on both paths + PathTime path_times[2]; + for (size_t i = 0; i < 2; i++) { + path_times[i] = (pos[i] == curve_count[i]) ? PathTime(curve_count[i] - 1, 1.0) + : PathTime(pos[i], curve_times[i]); + } + + // Get the parting point from the numerically nicest source + Point parting_pt; + if (curve_times[0] == 0.0) { + parting_pt = paths[0]->pointAt(path_times[0]); + } else if (curve_times[1] == 0.0) { + parting_pt = paths[1]->pointAt(path_times[1]); + } else { + parting_pt = middle_point(first.pointAt(path_times[0]), second.pointAt(path_times[1])); + } + + return PathIntersection(path_times[0], path_times[1], std::move(parting_pt)); +} + +} // 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-extrema.cpp b/src/2geom/path-extrema.cpp new file mode 100644 index 0000000..319ccec --- /dev/null +++ b/src/2geom/path-extrema.cpp @@ -0,0 +1,156 @@ +/** @file Implementation of Path::extrema() + */ +/* An algorithm to find the points on a path where a given coordinate + * attains its minimum and maximum values. + * + * Authors: + * RafaÅ‚ Siejakowski <rs@rs-math.net> + * + * Copyright 2022 the 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/path.h> +#include <2geom/point.h> + +namespace Geom { + +/** Returns +1 for positive numbers, -1 for negative numbers, and 0 otherwise. */ +inline static float sign(double number) +{ + if (number > 0.0) { + return 1.0; + } else if (number < 0.0) { + return -1.0; + } + return 0.0; +} + +/** @brief Determine whether the d-coordinate increases or decreases at the given path time. + * + * @param path A path. + * @param time A forward-normalized time on the given path. + * @param d The coordinate about which we want to know whether it increases. + * @return +1.0 if the coordinate increases, -1.0 if it decreases, 0.0 if it remains constant. +*/ +static float find_direction_of_travel(Path const &path, PathTime const &time, Dim2 d) +{ + if (time.t == 0.0) { // We're at a node point + if (time.curve_index == 0) { // Starting point of the path. + if (path.closed()) { + return sign(path.initialUnitTangent()[d] + path.finalUnitTangent()[d]); + } else { + return sign(path.initialUnitTangent()[d]); + } + } else if (time.curve_index == path.size()) { // End point of the path. + if (path.closed()) { + return sign(path.initialUnitTangent()[d] + path.finalUnitTangent()[d]); + } else { + return sign(path.finalUnitTangent()[d]); + } + } + + // Otherwise, check the average of the two unit tangent vectors. + auto const outgoing_tangent = path.curveAt(time.curve_index).unitTangentAt(0.0); + auto const incoming_tangent = path.curveAt(time.curve_index - 1).unitTangentAt(1.0); + return sign(outgoing_tangent[d] + incoming_tangent[d]); + } + // We're in the middle of a curve + return sign(path.curveAt(time.curve_index).unitTangentAt(time.t)[d]); +} + +/* Find information about the points on the path where the specified + * coordinate attains its minimum and maximum values. + */ +PathExtrema Path::extrema(Dim2 d) const +{ + auto const ZERO_TIME = PathTime(0, 0); + + // Handle the trivial case of an empty path. + if (empty()) { + auto const origin = initialPoint(); + return PathExtrema{ + .min_point = origin, + .max_point = origin, + .glance_direction_at_min = 0.0, + .glance_direction_at_max = 0.0, + .min_time = ZERO_TIME, + .max_time = ZERO_TIME + }; + } + + // Set up the simultaneous min-max search + Point min_point = initialPoint(), max_point = min_point; + auto min_time = ZERO_TIME, max_time = ZERO_TIME; + unsigned curve_counter = 0; + + /// A closure to update the current minimum and maximum values. + auto const update_minmax = [&](Point const &new_point, Coord t) { + if (new_point[d] < min_point[d]) { + min_point = new_point; + min_time = PathTime(curve_counter, t); + } else if (new_point[d] > max_point[d]) { + max_point = new_point; + max_time = PathTime(curve_counter, t); + } + }; + + // Iterate through the curves, searching for min and max. + for (auto const &curve : _data->curves) { + // Check the starting node first + update_minmax(curve.initialPoint(), 0.0); + + // Check the critical points (zeroes of the derivative). + std::unique_ptr<Curve> const derivative{curve.derivative()}; + for (auto root : derivative->roots(0.0, d)) { + update_minmax(curve.pointAt(root), root); + } + curve_counter++; + } + + auto const other = other_dimension(d); + return PathExtrema{ + .min_point = min_point, + .max_point = max_point, + .glance_direction_at_min = find_direction_of_travel(*this, min_time, other), + .glance_direction_at_max = find_direction_of_travel(*this, max_time, other), + .min_time = min_time, + .max_time = max_time + }; +} + +} // 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-intersection.cpp b/src/2geom/path-intersection.cpp new file mode 100644 index 0000000..280d7ba --- /dev/null +++ b/src/2geom/path-intersection.cpp @@ -0,0 +1,728 @@ +#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(double i : x) { + ret.push_back(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; + + int 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); + int dx = p[i].initialPoint()[X] > (spl.empty() ? p[i].finalPoint()[X] : p.valueAt(spl.front(), X)) ? 1 : 0; + int dy = p[i].initialPoint()[Y] > (spl.empty() ? p[i].finalPoint()[Y] : p.valueAt(spl.front(), Y)) ? 1 : 0; + //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(const auto & p : ps) + ret.push_back(path_mono_splits(p)); + 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.emplace_back(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(auto & i : bounds_a) bounds_a_union.push_back(union_list(i)); + for(auto & i : bounds_b) bounds_b_union.push_back(union_list(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(auto & re : res) { re.a = i; re.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(auto & re : res) { + if(re.ta != 0 && re.ta != 1 && re.tb != 0 && re.tb != 1) { + res2.push_back(re); + } + } + res = res2; + //} + offset_crossings(res, i, j); + append(ret, res); + } + } + return ret; +} + +void flip_crossings(Crossings &crs) { + for(auto & cr : crs) + cr = Crossing(cr.tb, cr.ta, cr.b, cr.a, !cr.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(auto & re : res) { re.a = re.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(auto & re : res) { re.a = i; re.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-sink.cpp b/src/2geom/path-sink.cpp new file mode 100644 index 0000000..1a22c81 --- /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 (const auto & i : pv) { + 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.cpp b/src/2geom/path.cpp new file mode 100644 index 0000000..aeff503 --- /dev/null +++ b/src/2geom/path.cpp @@ -0,0 +1,1161 @@ +/** @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> +#include <optional> + +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((to < from) ^ cross_start) +{ + if (_reverse) { + _to.normalizeForward(_path_size); + if (cross_start && _to < to) { + // Normalization made us cross start (closed path), + // so we don't need to cross the start anymore. + _cross_start = false; + } + if (_from != _to) { + _from.normalizeBackward(_path_size); + if (cross_start && _from > from) { + // Normalization backwards made us logically cross + // the start – we shouldn't cross the start again. + _cross_start = false; + } + } + } else { + _from.normalizeForward(_path_size); + if (cross_start && _from < from) { + _cross_start = false; + } + if (_from != _to) { + _to.normalizeBackward(_path_size); + if (cross_start && _to > to) { + _cross_start = false; + } + } + } + + 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 (double j : temp) + res.emplace_back(i, 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.emplace_back(&a[i], i, 0); + } + for (std::size_t i = 0; i < bsz; ++i) { + _records.emplace_back(&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 (auto & i : _active[ow]) { + if (!ii->bounds.intersects(i.bounds)) continue; + std::vector<CurveIntersection> cx = ii->curve->intersect(*i.curve, _precision); + for (auto & k : cx) { + PathTime tw(ii->index, k.first), tow(i.index, k.second); + _result.emplace_back( + w == 0 ? tw : tow, + w == 0 ? tow : tw, + 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 (auto & i : result) { + i.first.normalizeForward(asz); + 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 (double & i : all_nearest) { + i = si + 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 (double & i : all_t) { + i = ei + 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, + std::optional<Point> const &p_from, std::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()); + } +} + +Path Path::withoutDegenerateCurves() const +{ + Sequence cleaned; + cleaned.reserve(size()); + + for (auto it = begin(); it != end_open(); ++it) { + if (!it->isDegenerate()) { + cleaned.push_back(it->duplicate()); + } + } + + Path result; + result._closed = _closed; + result.do_update(result._data->curves.begin(), result._data->curves.end(), cleaned); + return result; +} + +// Replace curves between first and last with the 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/pathvector.cpp b/src/2geom/pathvector.cpp new file mode 100644 index 0000000..0683c31 --- /dev/null +++ b/src/2geom/pathvector.cpp @@ -0,0 +1,336 @@ +/** @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> +#include <optional> + +namespace Geom { + +//PathVector &PathVector::operator+=(PathVector const &other); + +PathVector::size_type PathVector::curveCount() const +{ + size_type n = 0; + for (const auto & it : *this) { + n += it.size_default(); + } + return n; +} + +void PathVector::reverse(bool reverse_paths) +{ + if (reverse_paths) { + std::reverse(begin(), end()); + } + for (auto & i : *this) { + i = i.reversed(); + } +} + +PathVector PathVector::reversed(bool reverse_paths) const +{ + PathVector ret; + for (const auto & i : *this) { + 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.emplace_back(a[i], i, 0); + } + for (std::size_t i = 0; i < b.size(); ++i) { + _records.emplace_back(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 (auto & i : _active[ow]) { + if (!ii->path->boundsFast().intersects(i.path->boundsFast())) continue; + std::vector<PathIntersection> px = ii->path->intersect(*i.path, _precision); + for (auto & k : px) { + PathVectorTime tw(ii->index, k.first), tow(i.index, k.second); + _result.emplace_back( + w == 0 ? tw : tow, + w == 0 ? tow : tw, + 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 auto & i : *this) { + if (!i.boundsFast().contains(p)) continue; + wind += i.winding(p); + } + return wind; +} + +std::optional<PathVectorTime> PathVector::nearestTime(Point const &p, Coord *dist) const +{ + std::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.emplace_back(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/piecewise.cpp b/src/2geom/piecewise.cpp new file mode 100644 index 0000000..8714bd6 --- /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 (double rt : rts){ + result.push_back(f.mapToDomain(rt, 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/planar-graph.h b/src/2geom/planar-graph.h new file mode 100644 index 0000000..fb5f1ac --- /dev/null +++ b/src/2geom/planar-graph.h @@ -0,0 +1,1252 @@ +/** @file PlanarGraph – a graph geometrically embedded in the plane. + */ +/* + * Authors: + * RafaÅ‚ Siejakowski <rs@rs-math.net> + * + * Copyright 2022 the 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. + */ + +// WARNING: This is a private header. Do not include it directly. + +#ifndef LIB2GEOM_SEEN_PLANAR_GRAPH_H +#define LIB2GEOM_SEEN_PLANAR_GRAPH_H + +#include <algorithm> +#include <iterator> +#include <list> + +#include <2geom/angle.h> +#include <2geom/coord.h> +#include <2geom/line.h> +#include <2geom/point.h> +#include <2geom/path.h> +#include <2geom/path-intersection.h> +#include <2geom/utils.h> + +namespace Geom { + +/** + * \class PlanarGraph + * \brief Planar graph - a graph geometrically embedded in the plane. + * + * A planar graph is composed of vertices with assigned locations (as points in the plane) + * and of edges (arcs), which are imagined as non-intersecting paths in the plane connecting + * the vertices. The edges can hold user-supplied labels (e.g., weights) which support event + * callbacks for when the graph is reconfigured, allowing the labels to be updated accordingly. + * + * \tparam EdgeLabel A user-supplied type; an object of this type will be attached to each + * edge of the planar graph (e.g., a "weight" of the edge). The type must + * satisfy requirements described further below. + * + * In order to construct a planar graph, you should specify the clumping precision (passed as + * a constructor argument) and then use the method insertEdge() to add edges to the graph, as + * many times as necessary. The graph will automatically figure out the locations of the + * vertices based on the endpoints of the inserted edges. Vertices will be combined into one + * when they are positioned within the distance specified as the clumping threshold, and the + * inserted edges will be attached to them accordingly. It is also possible to insert paths + * (typically, closed) not attached to any vertices, using the method insertDetached(). + * + * After the edges are inserted, the graph is in a potentially degenerate state, where several + * edges may exactly coincide in part or in full. If this is not desired, you can regularize + * the graph by calling regularize(). During the regularization process, any overlapping edges + * are combined into one. Partially overlapping edges are first split into overlapping and + * non-overlapping portions, after which the overlapping portions are combined. If the edges + * or their parts overlap but run in opposite directions, one of them will be reversed before + * being merged with the other one. The overlaps are detected using the precision setting passed + * as the clumping precision in the constructor argument. + * + * Note however that the regularization procedure does NOT detect transverse intersections + * between the edge paths: if such intersections are not desired, the user must pass non-\ + * intersecting paths to the insertEdge() method (the paths may still have common endpoints, + * which is fine: that's how common vertices are created). + * + * The insertion of new edges invalidates the regularized status, which you can check at any + * time by calling isRegularized(). + * + * The vertices stored by the graph are sorted by increasing X-coordinate, and if they have + * equal X-coordinates, by increasing Y-coordinate. Even before regularization, incidences of + * edges to each vertex are sorted by increasing azimuth of the outgoing tangent (departure + * heading, but in radians, in the interval \f$(-\pi, \pi]\f$). After regularization, the edges + * around each vertex are guaranteed to be sorted counterclockwise (when the Y-axis points up) + * by where they end up going eventually, even if they're tangent at the vertex and therefore + * have equal or nearly equal departure azimuths. + * + * \note + * Requirements on the \c EdgeLabel template parameter type. + * In order for the template to instantiate correctly, the following must be satisfied: + * \li The \c EdgeLabel type provides a method \c onReverse() which gets called whenever + * the orientation of the labeled edge is reversed. This is useful when implementing + * a directed graph, since the label can keep track of the logical direction. + * \li The \c EdgeLabel type provides a method \c onMergeWith(EdgeLabel const&) which gets + * called when the labeled edge is combined with a geometrically identical (coinciding) + * edge (both combined edges having the same orientations). The label of the edge merged + * with the current one is provided as an argument to the method. This is useful when + * implementing a graph with weights: for example, when two edges are merged, you may + * want to combine their weights in some way. + * \li There is a method \c onDetach() called when the edge is removed from the graph. The + * edge objects are never destroyed but may be disconnected from the graph when they're no + * longer needed; this allows the user to put the labels of such edges in a "dead" state. + * \li The \c EdgeLabel objects must be copy-constructible and copy-assignable. This is + * because when an edge is subdivided into two, the new edges replacing it get decorated + * with copies of the original edge's label. + */ +template<typename EdgeLabel> +#if __cplusplus >= 202002L +requires requires(EdgeLabel el, EdgeLabel const &other) { + el.onReverse(); + el.onMergeWith(other); + el.onDetach(); + el = other; +} +#endif +class PlanarGraph +{ +public: + + /** Represents the joint between an edge and a vertex. */ + struct Incidence + { + using Sign = bool; + inline static Sign const START = false; + inline static Sign const END = true; + + double azimuth; ///< Angle of the edge's departure. + unsigned index; ///< Index of the edge in the parent graph. + Sign sign; ///< Whether this is the start or end of the edge. + bool invalid = false; ///< Whether this incidence has been marked for deletion. + + Incidence(unsigned edge_index, double departure_azimuth, Sign which_end) + : azimuth{departure_azimuth} + , index{edge_index} + , sign{which_end} + { + } + ~Incidence() = default; + + /// Compare incidences based on their azimuths in radians. + inline bool operator<(Incidence const &other) const { return azimuth < other.azimuth; } + + /// Compare the azimuth of an incidence with the given angle. + inline bool operator<(double angle) const { return azimuth < angle; } + + /// Check equality (only tests edges and their ends). + inline bool operator==(Incidence const &other) const + { + return index == other.index && sign == other.sign; + } + }; + using IncIt = typename std::list<Incidence>::iterator; + using IncConstIt = typename std::list<Incidence>::const_iterator; + + /** Represents the vertex of a planar graph. */ + class Vertex + { + private: + Point const _position; ///< Geometric position of the vertex. + std::list<Incidence> _incidences; ///< List of incidences of edges to this vertex. + unsigned mutable _flags = 0; ///< User-settable flags. + + inline static Point::LexLess<X> const _cmp; ///< Point sorting function object. + + public: + Vertex(Point const &pos) + : _position{pos} + { + } + + /** Get the geometric position of the vertex. */ + Point const &point() const { return _position; } + + /** Get the list of incidences to the vertex. */ + auto const &getIncidences() const { return _incidences; } + + /** Compare vertices based on their coordinates (lexicographically). */ + bool operator<(Vertex const &other) const { return _cmp(_position, other._position); } + + unsigned flags() const { return _flags; } ///< Get the user flags. + void setFlags(unsigned flags) const { _flags = flags; } ///< Set the user flags. + + /** Get the cyclic-next incidence after the passed one, in the CCW direction. */ + IncConstIt cyclicNextIncidence(IncConstIt it) const { return cyclic_next(it, _incidences); } + + /** Get the cyclic-next incidence after the passed one, in the CW direction. */ + IncConstIt cyclicPrevIncidence(IncConstIt it) const { return cyclic_prior(it, _incidences); } + + /** The same but with pointers. */ + Incidence *cyclicNextIncidence(Incidence *from) + { + return &(*cyclicNextIncidence(_incidencePtr2It(from))); + } + Incidence *cyclicPrevIncidence(Incidence *from) + { + return &(*cyclicPrevIncidence(_incidencePtr2It(from))); + } + + private: + /** Same as above, but not const (so only for private use). */ + IncIt cyclicNextIncidence(IncIt it) { return cyclic_next(it, _incidences); } + IncIt cyclicPrevIncidence(IncIt it) { return cyclic_prior(it, _incidences); } + + /** Insert an incidence; for internal use by the PlanarGraph class. */ + Incidence &_addIncidence(unsigned edge_index, double azimuth, typename Incidence::Sign sign) + { + auto where = std::find_if(_incidences.begin(), _incidences.end(), [=](auto &inc) -> bool { + return inc.azimuth >= azimuth; + }); + return *(_incidences.emplace(where, edge_index, azimuth, sign)); + } + + /** Return a valid iterator to an incidence passed by pointer; + * if the pointer is invalid, return a start iterator. */ + IncIt _incidencePtr2It(Incidence *pointer) + { + auto it = std::find_if(_incidences.begin(), _incidences.end(), + [=](Incidence const &i) -> bool { return &i == pointer; }); + return (it == _incidences.end()) ? _incidences.begin() : it; + } + + friend class PlanarGraph<EdgeLabel>; + }; + using VertexIterator = typename std::list<Vertex>::iterator; + + /** Represents an edge of the planar graph. */ + struct Edge + { + Vertex *start = nullptr, *end = nullptr; ///< Start and end vertices. + Path path; ///< The path associated to the edge. + bool detached = false; ///< Whether the edge is detached from the graph. + bool inserted_as_detached = false; ///< Whether the edge was inserted as detached. + EdgeLabel mutable label; ///< The user-supplied label of the edge. + + /** Construct an edge with a given label. */ + Edge(Path &&movein_path, EdgeLabel &&movein_label) + : path{movein_path} + , label{movein_label} + { + } + + /** Detach the edge from the graph. */ + void detach() + { + detached = true; + label.onDetach(); + } + }; + using EdgeIterator = typename std::vector<Edge>::iterator; + using EdgeConstIterator = typename std::vector<Edge>::const_iterator; + +private: + double const _precision; ///< Numerical epsilon for vertex clumping. + std::list<Vertex> _vertices; ///< Vertices of the graph. + std::vector<Edge> _edges; ///< Edges of the graph. + std::vector< std::pair<Vertex *, Incidence *> > _junk; ///< Incidences that should be purged. + bool _regularized = true; // An empty graph is (trivially) regularized. + +public: + PlanarGraph(Coord precision = EPSILON) + : _precision{precision} + { + } + + std::list<Vertex> const &getVertices() const { return _vertices; } + std::vector<Edge> const &getEdges() const { return _edges; } + Edge const &getEdge(size_t index) const { return _edges.at(index); } + size_t getEdgeIndex(Edge const &edge) const { return &edge - _edges.data(); } + double getPrecision() const { return _precision; } + size_t numVertices() const { return _vertices.size(); } + size_t numEdges(bool include_detached = true) const + { + if (include_detached) { + return _edges.size(); + } + return std::count_if(_edges.begin(), _edges.end(), + [](Edge const &e) -> bool { return !e.detached; }); + } + + /** Check if the graph has been regularized. */ + bool isRegularized() const { return _regularized; } + + // 0x1p-50 is about twice the distance between M_PI and the next representable double. + void regularize(double angle_precision = 0x1p-50, bool remove_collapsed_loops = true); + + /** Allocate memory to store the specified number of edges. */ + void reserveEdgeCapacity(size_t capacity) { _edges.reserve(capacity); } + + unsigned insertEdge(Path &&path, EdgeLabel &&edge = EdgeLabel()); + unsigned insertDetached(Path &&path, EdgeLabel &&edge = EdgeLabel()); + + /** Edge insertion with a copy of the path. */ + unsigned insertEdge(Path const &path, EdgeLabel &&edge = EdgeLabel()) + { + return insertEdge(Path(path), std::forward<EdgeLabel>(edge)); + } + unsigned insertDetached(Path const &path, EdgeLabel &&edge = EdgeLabel()) + { + return insertDetached(Path(path), std::forward<EdgeLabel>(edge)); + } + + /** \brief Find the incidence at the specified endpoint of the edge. + * + * \param edge_index The index of the edge whose incidence we wish to query. + * \param sign Which end of the edge do we want an incidence of. + * \return A pair consisting of pointers to the vertex and the incidence. + * If not found, both pointers will be null. + */ + std::pair<Vertex *, Incidence *> + getIncidence(unsigned edge_index, typename Incidence::Sign sign) const + { + if (edge_index >= _edges.size() || _edges[edge_index].detached) { + return {nullptr, nullptr}; + } + Vertex *vertex = (sign == Incidence::START) ? _edges[edge_index].start + : _edges[edge_index].end; + if (!vertex) { + return {nullptr, nullptr}; + } + auto it = std::find(vertex->_incidences.begin(), vertex->_incidences.end(), + Incidence(edge_index, 42, sign)); // azimuth doesn't matter. + if (it == vertex->_incidences.end()) { + return {nullptr, nullptr}; + } + return {vertex, &(*it)}; + } + + /** + * \brief Go clockwise or counterclockwise around the vertex and find the next incidence. + * The notions of "clockwise"/"counterclockwise" correspond to the y-axis pointing up. + * + * \param vertex The vertex around which to orbit. + * \param incidence The incidence from which to start traversal. + * \param clockwise Whether to go clockwise instead of (default) counterclockwise. + * \return The next incidence encountered going in the specified direction. + */ + inline Incidence const &nextIncidence(VertexIterator const &vertex, IncConstIt const &incidence, + bool clockwise = false) const + { + return clockwise ? *(vertex->_cyclicPrevIncidence(incidence)) + : *(vertex->_cyclicNextIncidence(incidence)); + } + + /** As above, but taking references instead of iterators. */ + inline Incidence const &nextIncidence(Vertex const &vertex, Incidence const &incidence, + bool clockwise = false) const + { + IncConstIt it = std::find(vertex._incidences.begin(), vertex._incidences.end(), incidence); + if (it == vertex._incidences.end()) { + return incidence; + } + return clockwise ? *(vertex.cyclicPrevIncidence(it)) + : *(vertex.cyclicNextIncidence(it)); + } + + /** As above, but return an iterator to a const incidence. */ + inline IncConstIt nextIncidenceIt(Vertex const &vertex, Incidence const &incidence, + bool clockwise = false) const + { + IncConstIt it = std::find(vertex._incidences.begin(), vertex._incidences.end(), incidence); + if (it == vertex._incidences.end()) { + return vertex._incidences.begin(); + } + return clockwise ? vertex.cyclicPrevIncidence(it) + : vertex.cyclicNextIncidence(it); + } + inline IncConstIt nextIncidenceIt(Vertex const &vertex, IncConstIt const &incidence, + bool clockwise = false) const + { + return clockwise ? vertex.cyclicPrevIncidence(incidence) + : vertex.cyclicNextIncidence(incidence); + } + + /** As above, but start at the prescribed departure azimuth (in radians). + * + * \return A pointer to the incidence emanating from the vertex at or immediately after + * the specified azimuth, when going around the vertex in the specified direction. + * If the vertex has no incidences, return value is nullptr. + */ + Incidence *nextIncidence(VertexIterator const &vertex, double azimuth, + bool clockwise = false) const; + + /** Get the incident path, always oriented away from the vertex. */ + Path getOutgoingPath(Incidence const *incidence) const + { + return incidence ? _getPathImpl(incidence, Incidence::START) : Path(); + } + + /** Get the incident path, always oriented towards the vertex. */ + Path getIncomingPath(Incidence const *incidence) const + { + return incidence ? _getPathImpl(incidence, Incidence::END) : Path(); + } + +private: + inline Path _getPathImpl(Incidence const *incidence, typename Incidence::Sign origin) const + { + return (incidence->sign == origin) ? _edges[incidence->index].path + : _edges[incidence->index].path.reversed(); + } + + /** Earmark an incidence for future deletion. */ + inline void _throwAway(Vertex *vertex, Incidence *incidence) + { + if (!vertex || !incidence) { + return; + } + incidence->invalid = true; + _junk.emplace_back(vertex, incidence); + } + + // Topological reconfiguration functions; see their definitions for documentation. + bool _compareAndReglue(Vertex &vertex, Incidence *first, Incidence *second, bool deloop); + Vertex *_ensureVertexAt(Point const &position); + void _mergeCoincidingEdges(Incidence *first, Incidence *second); + void _mergeShorterLonger(Vertex &vertex, Incidence *shorter, Incidence *longer, + PathTime const &time_on_longer); + void _mergeWyeConfiguration(Vertex &vertex, Incidence *first, Incidence *second, + PathIntersection const &split); + void _purgeJunkIncidences(); + void _reglueLasso(Vertex &vertex, Incidence *first, Incidence *second, + PathIntersection const &split); + bool _reglueTeardrop(Vertex &vertex, Incidence *first, Incidence *second, bool deloop); + void _reglueTangentFan(Vertex &vertex, IncIt const &first, IncIt const &last, bool deloop); + void _regularizeVertex(Vertex &vertex, double angle_precision, bool deloop); + + // === Static stuff === + + /** Return the angle between the vector and the positive X axis or 0 if undefined. */ + inline static double _getAzimuth(Point const &vec) { return vec.isZero() ? 0.0 : atan2(vec); } + + /** Return path time corresponding to the same point on the reversed path. */ + inline static PathTime _reversePathTime(PathTime const &time, Path const &path) + { + int new_index = path.size() - time.curve_index - 1; + Coord new_time = 1.0 - time.t; + if (new_index < 0) { + new_index = 0; + new_time = 0; + } + return PathTime(new_index, new_time); + } + + /** Return path time at the end of the path. */ + inline static PathTime _pathEnd(Path const &path) { return PathTime(path.size() - 1, 1.0); } + inline static auto const PATH_START = PathTime(0, 0); + +public: + static double closedPathArea(Path const &path); + static bool deviatesLeft(Path const &first, Path const &second); +}; + +/** + * \brief Insert a new vertex or reuse an existing one. + * + * Ensures that there is a vertex at or near the specified position + * (within the distance of _precision). + * + * \param pos The desired geometric position of the new vertex. + * \return A pointer to the inserted vertex or a pre-existing vertex near the + * desired position. + */ +template<typename EL> +typename PlanarGraph<EL>::Vertex *PlanarGraph<EL>::_ensureVertexAt(Point const &pos) +{ + auto const insert_at_front = [&, this]() -> Vertex* { + _vertices.emplace_front(pos); + return &(_vertices.front()); + }; + + if (_vertices.empty()) { + return insert_at_front(); + } + + // TODO: Use a heap? + auto it = std::find_if(_vertices.begin(), _vertices.end(), [&](Vertex const &v) -> bool { + return Vertex::_cmp(pos, v._position); // existing vertex position > pos. + }); + + if (it != _vertices.end()) { + if (are_near(pos, it->_position, _precision)) { + return &(*it); // Reuse existing vertex. + } + if (it == _vertices.begin()) { + return insert_at_front(); + } + } + // Look at the previous element, reuse if near, insert before `it` otherwise. + return &(*(are_near(pos, std::prev(it)->_position, _precision) ? std::prev(it) + : _vertices.emplace(it, pos))); +} + +/** + * \brief Move-insert a new labeled edge into the planar graph. + * + * \param path The geometric path of the edge. + * \param label Optionally, the label (extra user data) associated to this edge. + * If absent, a default-constructed label will be used. + * \return The index of the inserted edge. + */ +template<typename EdgeLabel> +unsigned PlanarGraph<EdgeLabel>::insertEdge(Path &&path, EdgeLabel &&label) +{ + unsigned edge_index = _edges.size(); + auto &inserted = _edges.emplace_back(std::forward<Path>(path), + std::forward<EdgeLabel>(label)); + + // Calculate the outgoing azimuths at both endpoints. + double const start_azimuth = _getAzimuth(inserted.path.initialUnitTangent()); + double const end_azimuth = _getAzimuth(-inserted.path.finalUnitTangent()); + + // Get the endpoints into the graph. + auto start = _ensureVertexAt(inserted.path.initialPoint()); + auto end = _ensureVertexAt(inserted.path.finalPoint()); + + // Inform the edge about its endpoints. + inserted.start = start; + inserted.end = end; + + // Add incidences at the endpoints. + start->_addIncidence(edge_index, start_azimuth, Incidence::START); + end->_addIncidence(edge_index, end_azimuth, Incidence::END); + + _regularized = false; + return edge_index; +} + +/** + * \brief Move-insert a new labeled edge but do not connect it to the graph. + * + * Although the graph will hold the path data of an edge inserted in this way, the edge + * will not be connected to any vertex. This can be used to store information about closed + * paths (loops) in the instance, without having to specify starting points for them. + * + * \param path The geometric path of the edge. + * \param label Optionally, the label (extra user data) associated to this edge; if absent, + * the label will be default-constructed. + * \return The index of the inserted edge. + */ +template<typename EdgeLabel> +unsigned PlanarGraph<EdgeLabel>::insertDetached(Path &&path, EdgeLabel &&label) +{ + unsigned edge_index = _edges.size(); + auto &inserted = _edges.emplace_back(std::forward<Path>(path), + std::forward<EdgeLabel>(label)); + inserted.detached = true; + inserted.inserted_as_detached = true; + return edge_index; +} + +/** Remove incidences previously marked as junk. */ +template<typename EdgeLabel> +void PlanarGraph<EdgeLabel>::_purgeJunkIncidences() +{ + for (auto &[vertex, incidence] : _junk) { + Incidence *to_remove = incidence; + auto it = std::find_if(vertex->_incidences.begin(), vertex->_incidences.end(), + [=](Incidence const &inc) -> bool { return &inc == to_remove; }); + if (it != vertex->_incidences.end()) { + vertex->_incidences.erase(it); + } + } + _junk.clear(); +} + +/** + * \brief Merge overlapping edges or their portions, adding vertices if necessary. + * + * \param angle_precision The numerical epsilon for radian angle comparisons. + * \param remove_collapsed_loops Whether to detach edges with both ends incident to the same + * vertex (loops) when these loops don't enclose any area. + * + * This function performs the following operations: + * \li Edges that are tangent at a vertex but don't otherwise overlap are sorted correctly + * in the counterclockwise cyclic order around the vertex. + * \li Degenerate loops which don't enclose any area are removed if the argument is true. + * \li Edges that coincide completely are reversed if needed and merged into one. + * \li Edges that coincide partially are split into overlapping and non-overlapping portions. + * Any overlapping portions are oriented consistently and then merged. + * \li As a sub-case of the above, any non-degenerate loop with an initial self-everlap + * (a "lasso") is replaced with a shorter non-overlapping loop and a simple path leading + * to it. + */ +template<typename EdgeLabel> +void PlanarGraph<EdgeLabel>::regularize(double angle_precision, bool remove_collapsed_loops) +{ + for (auto it = _vertices.begin(); it != _vertices.end(); ++it) { + // Note: the list of vertices may grow during the execution of this loop, + // so don't replace it with a range-for (which stores the end iterator). + // We want the loop to carry on going over the elements it inserted. + if (it->_incidences.size() < 2) { + continue; + } + _regularizeVertex(*it, angle_precision, remove_collapsed_loops); + } + _purgeJunkIncidences(); + _regularized = true; +} + +/** + * \brief Analyze and regularize all edges emanating from a given vertex. + * + * This function goes through the list of incidences at the vertex (roughly sorted by + * azimuth, i.e., departure heading in radians), picking out runs of mutually tangent + * edges and calling _reglueTangentFan() on each run. The algorithm is quite complicated + * because the incidences have to be treated as a cyclic container and a run of mutually + * tangent edges may straddle the "end" of the list, including the possibility that the + * entire list is a single such run. + * + * \param vertex The vertex whose incidences should be analyzed. + * \param angle_precision The numerical epsilon for radian angle comparisons. + * \param deloop Whether loops that don't enclose any area should be detached. + */ +template<typename EdgeLabel> +void PlanarGraph<EdgeLabel>::_regularizeVertex(typename PlanarGraph<EdgeLabel>::Vertex &vertex, + double angle_precision, bool deloop) +{ + auto &incidences = vertex._incidences; + + /// Compare two polar angles in the interval [-Ï€, Ï€] modulo 2Ï€ to within angle_precision: + auto const angles_equal = [=](double az1, double az2) -> bool { + static double const twopi = 2.0 * M_PI; + return are_near(std::fmod(az1 + twopi, twopi), std::fmod(az2 + twopi, twopi), + angle_precision); + }; + + IncIt run_begin; // First element in the last discovered run of equal azimuths. + + /// Find and reglue runs of nearly identical azimuths in the specified range. + auto const process_runs = [&](IncIt begin, IncIt end) -> bool + { + double current_azimuth = 42; // Invalid radian angle. + bool in_a_run = false; + + for (auto it = begin; it != end; ++it) { + bool const equal = angles_equal(it->azimuth, current_azimuth); + if (equal && !in_a_run) { + run_begin = std::prev(it); // Save to enclosing scope. + in_a_run = true; + } else if (!equal && in_a_run) { + _reglueTangentFan(vertex, run_begin, std::prev(it), deloop); + in_a_run = false; + } + current_azimuth = it->azimuth; + } + return in_a_run; + }; + + double const last_azimuth = incidences.back().azimuth; + + if (angles_equal(incidences.front().azimuth, last_azimuth)) { + // The cyclic list contains a run of equal azimuths which straddles the "end". + // This means that we must skip the part of this run on the "begin" side on the + // first pass and handle it once we've traversed the remainder of the list. + + bool processed = false; ///< Whether we've cleared the straddling run. + double previous_azimuth = last_azimuth; + IncIt straddling_run_last; + + for (auto it = incidences.begin(); it != incidences.end(); ++it) { + if (!angles_equal(it->azimuth, previous_azimuth)) { + straddling_run_last = std::prev(it); + process_runs(it, incidences.end()); + processed = true; + break; + } + previous_azimuth = it->azimuth; + } + if (processed) { + // Find the first element of the straddling run. + auto it = std::prev(incidences.end()); + while (angles_equal(it->azimuth, last_azimuth)) { + --it; + } + ++it; // Now we're at the start of the straddling run. + _reglueTangentFan(vertex, it, straddling_run_last, deloop); + } else { + // We never encountered anything outside of the straddling run: reglue everything. + _reglueTangentFan(vertex, incidences.begin(), std::prev(incidences.end()), deloop); + } + } else if (process_runs(incidences.begin(), incidences.end())) { + // Our run got rudely interrupted by the end of the container; reglue till the end. + _reglueTangentFan(vertex, run_begin, std::prev(incidences.end()), deloop); + } +} + +/** + * \brief Regularize a fan of mutually tangent edges emanating from a vertex. + * + * This function compares the tangent edges pairwise and ensures that the sequence of their + * incidences to the vertex ends up being sorted by the ultimate direction in which the + * emanating edges fan out, in the counterclockwise order. + * + * If a partial or complete overlap between edges is detected, these edges are reglued. + * + * \param vertex The vertex from which the fan emanates. + * \param first An iterator pointing to the first incidence in the fan. + * \param last An iterator pointing to the last incidence in the fan. + * NOTE: This iterator must point to the actual last incidence, not "past" it. + * The reason is that we're iterating over a cyclic collection, so there + * isn't really a meaningful end. + * \param deloop Whether loops that don't enclose any area should be detached. + */ +template<typename EL> +void PlanarGraph<EL>::_reglueTangentFan(typename PlanarGraph<EL>::Vertex &vertex, + typename PlanarGraph<EL>::IncIt const &first, + typename PlanarGraph<EL>::IncIt const &last, bool deloop) +{ + // Search all pairs (triangular pattern), skipping invalid incidences. + for (auto it = first; it != last; it = vertex.cyclicNextIncidence(it)) { + if (it->invalid) { + continue; + } + for (auto is = vertex.cyclicNextIncidence(it); true; is = vertex.cyclicNextIncidence(is)) { + if (!is->invalid && _compareAndReglue(vertex, &(*it), &(*is), deloop)) { + // Swap the incidences, effectively implementing "bubble sort". + std::swap(*it, *is); + } + if (is == last) { + break; + } + } + } +} + +/** + * \brief Compare a pair of edges emanating from the same vertex in the same direction. + * + * If the edges overlap in part or in full, they get reglued, which means that the topology + * of the graph may get modified. Otherwise, if the detailed comparison shows that the edges + * aren't correctly ordered around the vertex (because the second edge deviates to the right + * instead of to the left of the first, when looking away from the vertex), then the function + * will return true, signalling that the incidences should be swapped. + * + * \param vertex The vertex where the mutually tangent paths meet. + * \param first The incidence appearing as the first one in the provisional cyclic order. + * \param second The incidence appearing as the second one in the provisional cyclic order. + * \param deloop Whether to detach collapsed loops (backtracks) which don't enclose any area. + * \return Whether the incidences should be swapped. + */ +template<typename EL> +bool PlanarGraph<EL>::_compareAndReglue(typename PlanarGraph<EL>::Vertex &vertex, + typename PlanarGraph<EL>::Incidence *first, + typename PlanarGraph<EL>::Incidence *second, bool deloop) +{ + if (first->index == second->index) { + return _reglueTeardrop(vertex, first, second, deloop); + } + + // Get paths corresponding to the edges but travelling away from the vertex. + auto first_path_out = getOutgoingPath(first); + auto second_path_out = getOutgoingPath(second); + auto split = parting_point(first_path_out, second_path_out, _precision); + + if (are_near(split.point(), vertex.point(), _precision)) { + // Paths deviate immediately, so no gluing is needed. The incidences should + // be swapped if the first edge path departs to the left of the second one. + return deviatesLeft(first_path_out, second_path_out); + } + + // Determine the nature of the initial overlap between the paths. + bool const till_end_of_1st = are_near(split.point(), first_path_out.finalPoint(), _precision); + bool const till_end_of_2nd = are_near(split.point(), second_path_out.finalPoint(), _precision); + + if (till_end_of_1st && till_end_of_2nd) { // Paths coincide completely. + _mergeCoincidingEdges(first, second); + } else if (till_end_of_1st) { + // Paths coincide until the end of the 1st one, which however isn't the end of the + // 2nd one; for example, the first one could be the vertical riser of the letter L + // whereas the second one – the entire letter stroke. + _mergeShorterLonger(vertex, first, second, split.second); + } else if (till_end_of_2nd) { + // The same but with with the second edge shorter than the first one. + _mergeShorterLonger(vertex, second, first, split.first); + } else { // A Y-shaped split. + _mergeWyeConfiguration(vertex, first, second, split); + } + return false; // We've glued so no need to swap anything. +} + +/** + * \brief Analyze a loop path a with self-tangency at the attachment point (a teardrop). + * + * The following steps are taken: + * \li If the loop encloses zero area and \c deloop is true, the loop is detached. + * \li If the two arms of the loop split out immediately, the loop is left alone and we + * only check whether the incidences should be swapped. + * \li If the loop overlaps itself near the vertex, resembling a lasso, we split it into + * a shorter simple path and a smaller loop attached to the end of the shorter path. + * + * \param vertex The vertex at which the teardrop originates. + * \param first The first incidence of the loop to the vertex. + * \param second The second incidence of the loop to the vertex. + * \param deloop Whether the loop should be removed if it doesn't enclose any area + * (i.e., the path exactly backtracks on itself). + * \return Whether the two incidences of the loop to the vertex should be swapped. + */ +template<typename EL> +bool PlanarGraph<EL>::_reglueTeardrop(typename PlanarGraph<EL>::Vertex &vertex, + typename PlanarGraph<EL>::Incidence *first, + typename PlanarGraph<EL>::Incidence *second, bool deloop) +{ + // Calculate the area enclosed by the teardrop. + // The convention is that the unit circle (cos(t), sint(t)), t from 0 to 2pi, + // encloses an area of +pi. + auto &edge = _edges[first->index]; + Path loop = edge.path; loop.close(); + double signed_area = closedPathArea(loop); + + if (deloop && are_near(signed_area, 0.0, _precision)) { + edge.detach(); + _throwAway(&vertex, first); + _throwAway(&vertex, second); + return false; + } + + auto split = parting_point(loop, loop.reversed(), _precision); + if (are_near(split.point(), vertex.point(), _precision)) { + // The loop spreads out immediately. We simply check if the incidences should be swapped. + // We want them to be ordered such that the signed area encircled by the path going out + // at the first incidence and coming back at the second (with this orientation) is > 0. + return (first->sign == Incidence::START) ^ (signed_area > 0.0); + } + + // The loop encloses a nonzero area, but the two branches don't separate at the starting + // point. Instead, they travel together for a while before they split like a lasso. + _reglueLasso(vertex, first, second, split); + return false; +} + +/** + * \brief Reglue a lasso-shaped loop, separating it into the "free rope" and the "hoop". + * + * The lasso is an edge looping back to the same vertex, where the closed path encloses + * a non-zero area, but its two branches don't separate at the starting point. Instead, + * they travel together for a while (forming the doubled-up "free rope") before they + * split like a lasso. This function cuts the lasso at the split point: + * \code{.unparsed} + * ____ ____ + * / \ / \ + * VERTEX =====< | ==> VERTEX ------ NEW + NEW < | + * \____/ (lasso) (rope) \____/ (hoop) + * + * \endcode + * + * \param vertex A reference to the vertex where the lasso is attached. + * \param first The first incidence of the lasso to the vertex. + * \param second The second incidence of the lasso to the vertex. + * \param split The point where the free rope of the lasso ends and the hoop begins. + */ +template<typename EL> +void PlanarGraph<EL>::_reglueLasso(typename PlanarGraph<EL>::Vertex &vertex, + typename PlanarGraph<EL>::Incidence *first, + typename PlanarGraph<EL>::Incidence *second, + PathIntersection const &split) +{ + unsigned lasso = first->index; + + // Create the "free rope" path. + auto rope = _edges[lasso].path.portion(PATH_START, split.first); + rope.setInitial(vertex.point()); + rope.setFinal(split.point()); + double const rope_final_backward_azimuth = _getAzimuth(-rope.finalUnitTangent()); + + // Compute the new label of the rope edge. + auto oriented_as_loop = _edges[lasso].label; + auto reversed = oriented_as_loop; reversed.onReverse(); + oriented_as_loop.onMergeWith(reversed); + + // Insert the rope and its endpoint. + unsigned const rope_index = _edges.size(); + auto &rope_edge = _edges.emplace_back(std::move(rope), std::move(oriented_as_loop)); + auto const new_split_vertex = _ensureVertexAt(split.point()); + + // Reuse lasso's first incidence as the incidence to the rope (azimuth can stay). + first->index = rope_index; + first->sign = Incidence::START; + + // Connect the rope to the newly created split vertex. + new_split_vertex->_addIncidence(rope_index, rope_final_backward_azimuth, Incidence::END); + rope_edge.start = &vertex; + rope_edge.end = new_split_vertex; + + // Insert the hoop + auto hoop = _edges[lasso].path.portion(split.first, + _reversePathTime(split.second, _edges[lasso].path)); + hoop.setInitial(split.point()); + hoop.setFinal(split.point()); + insertEdge(std::move(hoop), EL(_edges[lasso].label)); + + // Detach the original lasso edge and mark the second incidence for cleanup. + _edges[lasso].detach(); + _throwAway(&vertex, second); +} + +/** + * \brief Completely coallesce two fully overlapping edges. + * + * In practice, the first edge stays and the second one gets detached from the graph. + * + * \param first An iterator to the first edge's incidence to a common vertex. + * \param second An iterator to the second edge's incidence to a common vertex. + */ +template<typename EL> +void PlanarGraph<EL>::_mergeCoincidingEdges(typename PlanarGraph<EL>::Incidence *first, + typename PlanarGraph<EL>::Incidence *second) +{ + auto &surviver = _edges[first->index]; + auto &casualty = _edges[second->index]; + + auto other_label = casualty.label; + if (first->sign != second->sign) { // Logically reverse the label before merging. + other_label.onReverse(); + } + surviver.label.onMergeWith(other_label); + + // Mark both incidences of the second edge as junk and detach it. + auto [start_vertex, start_inc] = getIncidence(second->index, Incidence::START); + _throwAway(start_vertex, start_inc); + auto [end_vertex, end_inc] = getIncidence(second->index, Incidence::END); + _throwAway(end_vertex, end_inc); + casualty.detach(); +} + +/** + * \brief Merge a longer edge with a shorter edge that overlaps it. + * + * In practice, the shorter edge remains unchanged and the longer one is trimmed to + * become just the part extending past the shorter one. + * + * \param vertex The vertex where the overlap starts. + * \param shorter The incidence of the shorter edge to the common vertex. + * \param longer The incidence of the longer edge to the common vertex. + * \param time_on_longer The PathTime on the longer edge at which it passes through + * the endpoint of the shorter edge. + */ +template<typename EL> +void PlanarGraph<EL>::_mergeShorterLonger(typename PlanarGraph<EL>::Vertex &vertex, + typename PlanarGraph<EL>::Incidence *shorter, + typename PlanarGraph<EL>::Incidence *longer, + PathTime const &time_on_longer) +{ + auto &shorter_edge = _edges[shorter->index]; + auto &longer_edge = _edges[longer->index]; + + // Get the vertices at the far ends of both edges. + auto shorter_far_end = (shorter->sign == Incidence::START) ? shorter_edge.end + : shorter_edge.start; + /// Whether the longer edge heads out of the vertex. + bool const longer_out = (longer->sign == Incidence::START); + auto longer_far_end = longer_out ? longer_edge.end : longer_edge.start; + + // Copy the longer edge's label and merge with that of the shorter. + auto longer_label = longer_edge.label; + if (shorter->sign != longer->sign) { + longer_label.onReverse(); + } + shorter_edge.label.onMergeWith(longer_label); + + // Create the trimmed path (longer minus shorter). + Path trimmed; + double trimmed_departure_azimuth; + if (longer_out) { + trimmed = longer_edge.path.portion(time_on_longer, _pathEnd(longer_edge.path)); + longer_edge.start = shorter_far_end; + trimmed.setInitial(shorter_far_end->point()); + trimmed.setFinal(longer_far_end->point()); + trimmed_departure_azimuth = _getAzimuth(trimmed.initialUnitTangent()); + } else { + trimmed = longer_edge.path.portion(PATH_START, _reversePathTime(time_on_longer, + longer_edge.path)); + longer_edge.end = shorter_far_end; + trimmed.setInitial(longer_far_end->point()); + trimmed.setFinal(shorter_far_end->point()); + trimmed_departure_azimuth = _getAzimuth(-trimmed.finalUnitTangent()); + } + + // Set the trimmed path as the new path of the longer edge and set up the incidences: + longer_edge.path = std::move(trimmed); + shorter_far_end->_addIncidence(longer->index, trimmed_departure_azimuth, longer->sign); + + // Throw away the old start incidence of the longer edge. + _throwAway(&vertex, longer); +} + +/** + * \brief Merge a pair of partially overlapping edges, producing a Y-split at a new vertex. + * + * This topological modification is performed by inserting a new vertex at the three-way + * point (where the two paths separate) and clipping the original edges to that point. + * In this way, the original edges become the "arms" of the Y-shape. In addition, a new + * edge is inserted, forming the "stem" of the Y. + * + * \param vertex The vertex from which the partially overlapping edges originate (bottom of Y). + * \param first The incidence to the first edge (whose path is the stem and one arm of the Y). + * \param second The incidence to the second edge (stem and the other arm of the Y). + * \param fork The splitting point of the two paths. + */ +template<typename EL> +void PlanarGraph<EL>::_mergeWyeConfiguration(typename PlanarGraph<EL>::Vertex &vertex, + typename PlanarGraph<EL>::Incidence *first, + typename PlanarGraph<EL>::Incidence *second, + PathIntersection const &fork) +{ + bool const first_is_out = (first->sign == Incidence::START); + bool const second_is_out = (second->sign == Incidence::START); + + auto &first_edge = _edges[first->index]; + auto &second_edge = _edges[second->index]; + + // Calculate the path forming the stem of the Y: + auto stem_path = getOutgoingPath(first).portion(PATH_START, fork.first); + stem_path.setInitial(vertex.point()); + stem_path.setFinal(fork.point()); + + /// A closure to clip the path of an original edge to the fork point. + auto const clip_to_fork = [&](PathTime const &t, Edge &e, bool out) { + if (out) { // Trim from time to end + e.path = e.path.portion(t, _pathEnd(e.path)); + e.path.setInitial(fork.point()); + } else { // Trim from reverse-end to reverse-time + e.path = e.path.portion(PATH_START, _reversePathTime(t, e.path)); + e.path.setFinal(fork.point()); + } + }; + + /// A closure to find the departing azimuth of an edge at the fork point. + auto const departing_azimuth = [&](Edge const &e, bool out) -> double { + return _getAzimuth((out) ? e.path.initialUnitTangent() + : -e.path.finalUnitTangent()); + }; + + // Clip the paths obtaining the arms of the Y. + clip_to_fork(fork.first, first_edge, first_is_out); + clip_to_fork(fork.second, second_edge, second_is_out); + + // Create the fork vertex and set up its incidences. + auto const fork_vertex = _ensureVertexAt(fork.point()); + fork_vertex->_addIncidence(first->index, departing_azimuth(first_edge, first_is_out), + first->sign); + fork_vertex->_addIncidence(second->index, departing_azimuth(second_edge, second_is_out), + second->sign); + + // Repoint the ends of the edges that were clipped + (first_is_out ? first_edge.start : first_edge.end) = fork_vertex; + (second_is_out ? second_edge.start : second_edge.end) = fork_vertex; + + /// A closure to get a consistently oriented label of an edge. + auto upwards_oriented_label = [&](Edge const &e, bool out) -> EL { + auto label = e.label; + if (!out) { + label.onReverse(); + } + return label; + }; + + auto stem_label = upwards_oriented_label(first_edge, first_is_out); + stem_label.onMergeWith(upwards_oriented_label(second_edge, second_is_out)); + auto stem_departure_from_fork = _getAzimuth(-stem_path.finalUnitTangent()); + + // Insert the stem of the Y-configuration. + unsigned const stem_index = _edges.size(); + auto &stem_edge = _edges.emplace_back(std::move(stem_path), std::move(stem_label)); + stem_edge.start = &vertex; + stem_edge.end = fork_vertex; + + // Set up the incidences. + fork_vertex->_addIncidence(stem_index, stem_departure_from_fork, Incidence::END); + first->index = stem_index; + first->sign = Incidence::START; + _throwAway(&vertex, second); +} + +template<typename EL> +typename PlanarGraph<EL>::Incidence* +PlanarGraph<EL>::nextIncidence(typename PlanarGraph<EL>::VertexIterator const &vertex, + double azimuth, bool clockwise) const +{ + auto &incidences = vertex._incidences; + Incidence *result = nullptr; + + if (incidences.empty()) { + return result; + } + // Normalize azimuth to the interval [-pi; pi]. + auto angle = Angle(azimuth); + + if (clockwise) { // Go backwards and find a lower bound + auto it = std::find_if(incidences.rbegin(), incidences.rend(), [=](auto inc) -> bool { + return inc.azimuth <= angle; + }); + if (it == incidences.rend()) { + // azimuth is lower than the azimuths of all incidences; + // going clockwise we wrap back to the highest azimuth (last incidence). + return &incidences.back(); + } + result = &(*it); + } else { + auto it = std::find_if(incidences.begin(), incidences.end, [=](auto inc) -> bool { + return inc.azimuth >= angle; + }); + if (it == incidences.end()) { + // azimuth is higher than the azimuths of all incidences; + // going counterclockwise we wrap back to the lowest azimuth. + return &incidences.front(); + } + result = &(*it); + } + return result; +} + +/** Return the signed area enclosed by a closed path. */ +template<typename EL> +double PlanarGraph<EL>::closedPathArea(Path const &path) +{ + double area; + Point _; + centroid(path.toPwSb(), _, area); + return -area; // Our convention is that the Y-axis points up +} + +/** \brief Determine whether the first path deviates to the left of the second. + * + * The two paths are assumed to have identical or nearly identical starting points + * but not an overlapping initial portion. The concept of "left" is based on the + * y-axis pointing up. + * + * \param first The first path. + * \param second The second path. + * + * \return True if the first path deviates towards the left of the second; + * False if the first path deviates towards the right of the second. + */ +template<typename EL> +bool PlanarGraph<EL>::deviatesLeft(Path const &first, Path const &second) +{ + auto start = middle_point(first.initialPoint(), second.initialPoint()); + auto tangent_between = middle_point(first.initialUnitTangent(), second.initialUnitTangent()); + if (tangent_between.isZero()) { + return false; + } + auto tangent_line = Line::from_origin_and_vector(start, tangent_between); + + // Find the first non-degenerate curves on both paths + std::unique_ptr<Curve> c[2]; + auto const find_first_nondegen = [](std::unique_ptr<Curve> &pointer, Path const &path) { + for (auto const &c : path) { + if (!c.isDegenerate()) { + pointer.reset(c.duplicate()); + return; + } + } + }; + + find_first_nondegen(c[0], first); + find_first_nondegen(c[1], second); + if (!c[0] || !c[1]) { + return false; + } + + // Find the bounding boxes + Rect const bounding_boxes[] { + c[0]->boundsExact(), + c[1]->boundsExact() + }; + + // For a bounding box, find the corner that goes the furthest in the direction of the + // tangent vector. + auto const furthest_corner = [&](Rect const &r) -> unsigned { + Coord max_dot = dot(r.corner(0) - start, tangent_between); + unsigned result = 0; + for (unsigned i = 1; i < 4; i++) { + auto current_dot = dot(r.corner(i), tangent_between); + if (current_dot > max_dot) { + max_dot = current_dot; + result = i; + } else if (current_dot == max_dot) { + // Disambiguate based on proximity to the tangent line. + auto const offset = start + tangent_between; + if (distance(offset, r.corner(i)) < distance(offset, r.corner(result))) { + result = i; + } + } + } + return result; + }; + + // Calculate the corner points overlooking the "rift" between the paths. + Point corner_points[2]; + for (size_t i : {0, 1}) { + corner_points[i] = bounding_boxes[i].corner(furthest_corner(bounding_boxes[i])); + } + + // Find a vantage point from which we can best observe the splitting paths. + Point vantage_point; + bool found = false; + if (corner_points[0] != corner_points[1]) { + auto line_connecting_corners = Line(corner_points[0], corner_points[1]); + auto xing = line_connecting_corners.intersect(tangent_line); + if (!xing.empty()) { + vantage_point = xing[0].point(); + found = true; + } + } + if (!found) { + vantage_point = tangent_line.pointAt(tangent_line.timeAtProjection(corner_points[0])); + } + + // Move to twice as far in the direction of the vantage point. + vantage_point += vantage_point - start; + + // Find the points on both curves that are nearest to the vantage point. + Coord nearest[2]; + for (size_t i : {0, 1}) { + nearest[i] = c[i]->nearestTime(vantage_point); + } + + // Clip to the nearest points and examine the closed contour. + Path closed_contour(start); + closed_contour.setStitching(true); + closed_contour.append(c[0]->portion(0, nearest[0])); + closed_contour = closed_contour.reversed(); + closed_contour.setStitching(true); + closed_contour.append(c[1]->portion(0, nearest[1])); + closed_contour.close(); + return !path_direction(closed_contour); // Reverse to match the convention that y-axis is up. +} + +} // namespace Geom + +#endif // LIB2GEOM_SEEN_PLANAR_GRAPH_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/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/polynomial.cpp b/src/2geom/polynomial.cpp new file mode 100644 index 0000000..9737bd0 --- /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.emplace_back(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(auto & root : roots) { + if(root.imag() == 0) // should be more lenient perhaps + real_roots.push_back(root.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/rect.cpp b/src/2geom/rect.cpp new file mode 100644 index 0000000..60dcc87 --- /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: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..20b07d9 --- /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.emplace_back(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-geometric.cpp b/src/2geom/sbasis-geometric.cpp new file mode 100644 index 0000000..7039c6d --- /dev/null +++ b/src/2geom/sbasis-geometric.cpp @@ -0,0 +1,790 @@ +/** 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 (double & seg_rt : seg_rts){ + seg_rt= mapToDom(seg_rt); + } + 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 (double & rt : rts){ + rt = domain->min() + rt * 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 (double lbda0 : solns){ + 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-math.cpp b/src/2geom/sbasis-math.cpp new file mode 100644 index 0000000..547f9af --- /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 (auto & seg : f.segs){ + seg.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-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-roots.cpp b/src/2geom/sbasis-roots.cpp new file mode 100644 index 0000000..ee006d2 --- /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 (double i : extrema){ + result.expandTo(a(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 (auto & solset : solsets){ + if ( solset.size() == 0 ) continue; + std::sort( solset.begin(), solset.end(), compareIntervalMin ); + solset = fuseContiguous( solset, 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..5580956 --- /dev/null +++ b/src/2geom/sbasis-to-bezier.cpp @@ -0,0 +1,584 @@ +/* + * 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 + */ + +/** 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); + for (size_t k = 0; k < q; ++k) + { + int Tjk = 1; + for (size_t j = k; j < n-k; ++j) // j <= n-k-1 + { + bz[j] += (Tjk * sb[k][0]); + bz[n-j] += (Tjk * sb[k][1]); // n-k <-> [k][1] + // assert(Tjk == binomial(n-2*k-1, j-k)); + binomial_increment_k(Tjk, n-2*k-1, j-k); + } + } + 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 + int bcj = n; + for (size_t j = 1; j < n; ++j) + { + bz[j] /= bcj; + // assert(bcj == binomial(n, j)); + binomial_increment_k(bcj, 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 (auto i : sb[X]) { + midx += (i[0] + i[1])/div; + div *= 4; + } + + div = 2; + for (auto i : sb[Y]) { + midy += (i[0] + 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].size() > 1 ? sb[X][1][0] + sb[X][1][1] : 0; // zeroth order estimate + midy_0 = sb[Y].size() > 1 ? sb[Y][1][0] + sb[Y][1][1] : 0; + + 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 (auto i : sb[X]) { + midx += (i[1] - i[0])/div; + div *= 4; + } + midy = 0; + div = 1; + for (auto i : sb[Y]) { + midy += (i[1] - 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)); + int nck = 1; + for (size_t k = 0; k < q; ++k) + { + int Tjk = nck; + for (size_t j = k; j < q; ++j) + { + sb[j][0] += (Tjk * bz[k]); + sb[j][1] += (Tjk * bz[n-k]); // n-j <-> [j][1] + // assert(Tjk == sgn(j, k) * binomial(n-j-k, j-k) * binomial(n, k)); + binomial_increment_k(Tjk, n-j-k, j-k); + binomial_decrement_n(Tjk, n-j-k, j-k+1); + Tjk = -Tjk; + } + Tjk = -nck; + for (size_t j = k+1; j < q; ++j) + { + sb[j][0] += (Tjk * bz[n-k]); + sb[j][1] += (Tjk * bz[k]); // n-j <-> [j][1] + // assert(Tjk == sgn(j, k) * binomial(n-j-k-1, j-k-1) * binomial(n, k)); + binomial_increment_k(Tjk, n-j-k-1, j-k-1); + binomial_decrement_n(Tjk, n-j-k-1, j-k); + Tjk = -Tjk; + } + // assert(nck == binomial(n, k)); + binomial_increment_k(nck, n, k); + } + if (even) + { + int Tjk = q & 1 ? -1 : 1; + for (size_t k = 0; k < q; ++k) + { + sb[q][0] += (Tjk * (bz[k] + bz[n-k])); + // assert(Tjk == sgn(q,k) * binomial(n, k)); + binomial_increment_k(Tjk, n, k); + Tjk = -Tjk; + } + // assert(Tjk == binomial(n, q)); + sb[q][0] += Tjk * 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)); + int nck = 1; + for (size_t k = 0; k < q; ++k) + { + int Tjk = nck; + for (size_t j = k; j < q; ++j) + { + 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]); + // assert(Tjk == sgn(j, k) * binomial(n-j-k, j-k) * binomial(n, k)); + binomial_increment_k(Tjk, n-j-k, j-k); + binomial_decrement_n(Tjk, n-j-k, j-k+1); + Tjk = -Tjk; + } + Tjk = -nck; + for (size_t j = k+1; j < q; ++j) + { + 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]); + // assert(Tjk == sgn(j, k) * binomial(n-j-k-1, j-k-1) * binomial(n, k)); + binomial_increment_k(Tjk, n-j-k-1, j-k-1); + binomial_decrement_n(Tjk, n-j-k-1, j-k); + Tjk = -Tjk; + } + // assert(nck == binomial(n, k)); + binomial_increment_k(nck, n, k); + } + if (even) + { + int Tjk = q & 1 ? -1 : 1; + for (size_t k = 0; k < q; ++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])); + // assert(Tjk == sgn(q,k) * binomial(n, k)); + binomial_increment_k(Tjk, n, k); + Tjk = -Tjk; + } + // assert(Tjk == binomial(n, q)); + sb[X][q][0] += Tjk * bz[q][X]; + sb[X][q][1] = sb[X][q][0]; + sb[Y][q][0] += Tjk * 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]; +} + +} // 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.cpp b/src/2geom/sbasis.cpp new file mode 100644 index 0000000..ceaae3f --- /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(auto & 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/self-intersect.cpp b/src/2geom/self-intersect.cpp new file mode 100644 index 0000000..4fe4d9e --- /dev/null +++ b/src/2geom/self-intersect.cpp @@ -0,0 +1,313 @@ +/** + * @file Implementation of Path::intersectSelf() and PathVector::intersectSelf(). + */ +/* An algorithm for finding self-intersections of paths and path-vectors. + * + * Authors: + * RafaÅ‚ Siejakowski <rs@rs-math.net> + * + * (C) Copyright 2022 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 <list> + +#include <2geom/coord.h> +#include <2geom/curve.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/point.h> +#include <2geom/sweeper.h> + +namespace Geom { + +/** @brief The PathSelfIntersector class is a sweepset class used for intersecting curves in the + * same path with one another. It is intended to be used as the template parameter of Sweeper. + */ +class PathSelfIntersector +{ +public: + using ItemIterator = Path::iterator; + +private: + Path _path; ///< The path searched for self-crossings, cleaned of degenerate curves. + std::list<ItemIterator> _active; ///< List of active curves during the sweepline passage. + std::vector<PathIntersection> _crossings; ///< Stores the crossings found. + std::vector<size_t> _original_indices; ///< Curve indices before removal of degenerate curves. + double const _precision; ///< Numerical epsilon. + +public: + PathSelfIntersector(Path const &path, double precision) + : _path{path.initialPoint()} + , _precision{precision} + { + _original_indices.reserve(path.size()); + for (size_t i = 0; i < path.size(); i++) { + if (!path[i].isDegenerate()) { + _path.append(path[i]); + _original_indices.push_back(i); + } + } + _path.close(path.closed()); + } + + // === SweepSet API === + auto &items() { return _path; } + Interval itemBounds(ItemIterator curve) const { return curve->boundsFast()[X]; } + /// Callback for when the sweepline starts intersecting a new item. + void addActiveItem(ItemIterator incoming) + { + _intersectWithActive(incoming); + _intersectWithSelf(incoming); + _active.push_back(incoming); + } + /// Callback for when the sweepline stops intersecting an item. + void removeActiveItem(ItemIterator to_remove) + { + auto it = std::find(_active.begin(), _active.end(), to_remove); + _active.erase(it); + } + // === + + std::vector<PathIntersection> &&moveOutCrossings() { return std::move(_crossings); } + +private: + /** Find and store all intersections of a curve with itself. */ + void _intersectWithSelf(ItemIterator curve) + { + size_t const index = std::distance(_path.begin(), curve); + for (auto &&self_x : curve->intersectSelf(_precision)) { + _appendCurveCrossing(std::move(self_x), index, index); + } + } + + /** Find and store all intersections of a curve with the active curves. */ + void _intersectWithActive(ItemIterator curve) + { + size_t const index = std::distance(_path.begin(), curve); + for (auto const &other : _active) { + if (!curve->boundsFast().intersects(other->boundsFast())) { + continue; + } + + size_t const other_index = std::distance(_path.begin(), other); + auto const &[smaller, larger] = std::minmax(index, other_index); + /// Whether the curves meet at a common node in the path. + bool consecutive = smaller + 1 == larger; + /// Whether the curves meet at the closure point of the path. + bool wraparound = _path.closed() && smaller == 0 && larger + 1 == _path.size(); + for (auto &&xing : curve->intersect(*other, _precision)) { + _appendCurveCrossing(std::move(xing), index, other_index, consecutive, wraparound); + } + } + } + + /** Append a curve crossing to the store as long as it satisfies nondegeneracy criteria. */ + void _appendCurveCrossing(CurveIntersection &&xing, size_t first_index, size_t second_index, + bool consecutive = false, bool wraparound = false) + { + // Filter out crossings that aren't real but rather represent the agreement of final + // and initial points of consecutive curves – a consequence of the path's continuity. + auto const should_exclude = [&](bool flipped) -> bool { + // Filter out spurious self-intersections by using squared geometric average. + bool const first_is_first = (first_index < second_index) ^ flipped; + double const geom2 = first_is_first ? (1.0 - xing.first) * xing.second + : (1.0 - xing.second) * xing.first; + return geom2 < EPSILON; + }; + + if ((consecutive && should_exclude(false)) || (wraparound && should_exclude(true))) { + return; + } + + // Convert curve indices to the original ones (before the removal of degenerate curves). + _crossings.emplace_back(PathTime(_original_indices[first_index], xing.first), + PathTime(_original_indices[second_index], xing.second), + xing.point()); + } +}; + +// Compute all crossings of a path with itself. +std::vector<PathIntersection> Path::intersectSelf(Coord precision) const +{ + auto intersector = PathSelfIntersector(*this, precision); + Sweeper(intersector).process(); + auto result = intersector.moveOutCrossings(); + std::sort(result.begin(), result.end()); + return result; +} + +/** + * @brief The PathVectorSelfIntersector class is an implementation of a SweepSet whose intended + * use is the search for self-intersections in a single PathVector. It's designed to be used as + * the template parameter for the Sweeper class template. + */ +class PathVectorSelfIntersector +{ +public: + using ItemIterator = PathVector::const_iterator; + +private: + PathVector const &_pathvector; ///< A reference to the path-vector searched for self-crossings. + std::list<ItemIterator> _active; ///< A list of active paths during sweepline passage. + std::vector<PathVectorIntersection> _crossings; ///< Stores the crossings found. + double const _precision; ///< Numerical epsilon. + +public: + PathVectorSelfIntersector(PathVector const &subject, double precision) + : _pathvector{subject} + , _precision{precision} + { + } + + // == SweepSet API === + auto const &items() { return _pathvector; } + Interval itemBounds(ItemIterator path) + { + auto const r = path->boundsFast(); + return r ? (*r)[X] : Interval(); // Sweeplines are vertical + } + + /// Callback for when the sweepline starts intersecting a new item. + void addActiveItem(ItemIterator incoming) + { + _intersectWithActive(incoming); + _intersectWithSelf(incoming); + _active.push_back(incoming); + } + + /// Callback for when the sweepline stops intersecting an item. + void removeActiveItem(ItemIterator to_remove) + { + auto it = std::find(_active.begin(), _active.end(), to_remove); + _active.erase(it); + } + // === + + std::vector<PathVectorIntersection> &&moveOutCrossings() { return std::move(_crossings); } + +private: + /** + * @brief Find all intersections of the path pointed to by the given + * iterator with all currently active paths and store results + * in the instance of the class. + * + * @param it An iterator to a path to be intersected with the active ones. + */ + void _intersectWithActive(ItemIterator &it); + + /** + * @brief Find all intersections of the path pointed to by the given + * iterator with itself and store the results in the class instance. + * + * @param it An iterator to a path which will be intersected with itself. + */ + void _intersectWithSelf(ItemIterator &it); + + /// Append a path crossing to the store. + void _appendPathCrossing(PathIntersection const &xing, size_t first_index, size_t second_index) + { + auto const first_time = PathVectorTime(first_index, xing.first); + auto const second_time = PathVectorTime(second_index, xing.second); + _crossings.emplace_back(first_time, second_time, xing.point()); + } + +public: + + std::vector<PathVectorIntersection> + filterDeduplicate(std::vector<PathVectorIntersection> &&xings) const; +}; + +/** Remove duplicate intersections (artifacts of the path/curve crossing algorithms). */ +std::vector<PathVectorIntersection> +PathVectorSelfIntersector::filterDeduplicate(std::vector<PathVectorIntersection> &&xings) const +{ + std::vector<PathVectorIntersection> result; + result.reserve(xings.size()); + + auto const are_same_times = [&](Coord a1, Coord a2, Coord b1, Coord b2) -> bool { + return (are_near(a1, b1) && are_near(a2, b2)) || + (are_near(a1, b2) && are_near(a2, b1)); + }; + + Coord last_time_1 = -1.0, last_time_2 = -1.0; // Invalid path times + for (auto &&x : xings) { + auto const current_1 = x.first.asFlatTime(), current_2 = x.second.asFlatTime(); + if (!are_same_times(current_1, current_2, last_time_1, last_time_2)) { + result.push_back(std::move(x)); + } + last_time_1 = current_1; + last_time_2 = current_2; + } + + return result; +} + +/** Compute and store intersections of a path with all active paths. */ +void PathVectorSelfIntersector::_intersectWithActive(ItemIterator &it) +{ + auto const start = _pathvector.begin(); + for (auto &path : _active) { + if (!path->boundsFast().intersects(it->boundsFast())) { + continue; + } + for (auto &&xing : path->intersect(*it, _precision)) { + _appendPathCrossing(std::move(xing), std::distance(start, path), + std::distance(start, it)); + } + } +} + +/** Compute and store intersections of a constituent path with itself. */ +void PathVectorSelfIntersector::_intersectWithSelf(ItemIterator &it) +{ + size_t const path_index = std::distance(_pathvector.begin(), it); + for (auto &&xing : it->intersectSelf(_precision)) { + _appendPathCrossing(std::move(xing), path_index, path_index); + } +} + +// Compute self-intersections in a path-vector. +std::vector<PathVectorIntersection> PathVector::intersectSelf(Coord precision) const +{ + auto intersector = PathVectorSelfIntersector(*this, precision); + Sweeper(intersector).process(); + auto result = intersector.moveOutCrossings(); + std::sort(result.begin(), result.end()); + return (result.size() > 1) ? intersector.filterDeduplicate(std::move(result)) : result; +} + +} // 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/solve-bezier-one-d.cpp b/src/2geom/solve-bezier-one-d.cpp new file mode 100644 index 0000000..b82d20b --- /dev/null +++ b/src/2geom/solve-bezier-one-d.cpp @@ -0,0 +1,243 @@ + +#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 constexpr size_t MAX_DEPTH = 53; + size_t degree, N; + std::vector<double> &solutions; + + Bernsteins(size_t _degree, std::vector<double> &sol) + : degree(_degree), N(degree+1), solutions(sol) + { + } + + 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 + +} // 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/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/svg-path-parser.cpp b/src/2geom/svg-path-parser.cpp new file mode 100644 index 0000000..6a1cb15 --- /dev/null +++ b/src/2geom/svg-path-parser.cpp @@ -0,0 +1,1615 @@ + +#line 1 "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 "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 "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 "svg-path-parser.cpp" + { + cs = svg_path_start; + } + +#line 73 "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 "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 "svg-path-parser.rl" + { + start = p; + } + break; + case 1: +#line 213 "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 "svg-path-parser.rl" + { + _push(1.0); + } + break; + case 3: +#line 229 "svg-path-parser.rl" + { + _push(0.0); + } + break; + case 4: +#line 233 "svg-path-parser.rl" + { + _absolute = true; + } + break; + case 5: +#line 237 "svg-path-parser.rl" + { + _absolute = false; + } + break; + case 6: +#line 241 "svg-path-parser.rl" + { + _moveto_was_absolute = _absolute; + _moveTo(_pop_point()); + } + break; + case 7: +#line 246 "svg-path-parser.rl" + { + _lineTo(_pop_point()); + } + break; + case 8: +#line 250 "svg-path-parser.rl" + { + _lineTo(Point(_pop_coord(X), _current[Y])); + } + break; + case 9: +#line 254 "svg-path-parser.rl" + { + _lineTo(Point(_current[X], _pop_coord(Y))); + } + break; + case 10: +#line 258 "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 "svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + _curveTo(_cubic_tangent, c1, p); + } + break; + case 12: +#line 271 "svg-path-parser.rl" + { + Point p = _pop_point(); + Point c = _pop_point(); + _quadTo(c, p); + } + break; + case 13: +#line 277 "svg-path-parser.rl" + { + Point p = _pop_point(); + _quadTo(_quad_tangent, p); + } + break; + case 14: +#line 282 "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 "svg-path-parser.rl" + { + _closePath(); + } + break; +#line 1449 "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 "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 "svg-path-parser.rl" + { + _moveto_was_absolute = _absolute; + _moveTo(_pop_point()); + } + break; + case 7: +#line 246 "svg-path-parser.rl" + { + _lineTo(_pop_point()); + } + break; + case 8: +#line 250 "svg-path-parser.rl" + { + _lineTo(Point(_pop_coord(X), _current[Y])); + } + break; + case 9: +#line 254 "svg-path-parser.rl" + { + _lineTo(Point(_current[X], _pop_coord(Y))); + } + break; + case 10: +#line 258 "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 "svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + _curveTo(_cubic_tangent, c1, p); + } + break; + case 12: +#line 271 "svg-path-parser.rl" + { + Point p = _pop_point(); + Point c = _pop_point(); + _quadTo(c, p); + } + break; + case 13: +#line 277 "svg-path-parser.rl" + { + Point p = _pop_point(); + _quadTo(_quad_tangent, p); + } + break; + case 14: +#line 282 "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 "svg-path-parser.rl" + { + _closePath(); + } + break; +#line 1555 "svg-path-parser.cpp" + } + } + } + + _out: {} + } + +#line 435 "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=ragel:cindent:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/2geom/svg-path-parser.rl b/src/2geom/svg-path-parser.rl new file mode 100644 index 0000000..7b3eb5a --- /dev/null +++ b/src/2geom/svg-path-parser.rl @@ -0,0 +1,487 @@ +/** + * \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 { + +%%{ + machine svg_path; + write data noerror; +}%% + +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; + + %%{ + write init; + }%% +} + +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; + + %%{ + action start_number { + start = p; + } + + action push_number { + 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(); + } + } + + action push_true { + _push(1.0); + } + + action push_false { + _push(0.0); + } + + action mode_abs { + _absolute = true; + } + + action mode_rel { + _absolute = false; + } + + action moveto { + _moveto_was_absolute = _absolute; + _moveTo(_pop_point()); + } + + action lineto { + _lineTo(_pop_point()); + } + + action horizontal_lineto { + _lineTo(Point(_pop_coord(X), _current[Y])); + } + + action vertical_lineto { + _lineTo(Point(_current[X], _pop_coord(Y))); + } + + action curveto { + Point p = _pop_point(); + Point c1 = _pop_point(); + Point c0 = _pop_point(); + _curveTo(c0, c1, p); + } + + action smooth_curveto { + Point p = _pop_point(); + Point c1 = _pop_point(); + _curveTo(_cubic_tangent, c1, p); + } + + action quadratic_bezier_curveto { + Point p = _pop_point(); + Point c = _pop_point(); + _quadTo(c, p); + } + + action smooth_quadratic_bezier_curveto { + Point p = _pop_point(); + _quadTo(_quad_tangent, p); + } + + action elliptical_arc { + 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); + } + + action closepath { + _closePath(); + } + + wsp = (' ' | 9 | 10 | 13); + sign = ('+' | '-'); + digit_sequence = digit+; + exponent = ('e' | 'E') sign? digit_sequence; + fractional_constant = + digit_sequence? '.' digit_sequence + | digit_sequence '.'; + floating_point_constant = + fractional_constant exponent? + | digit_sequence exponent; + integer_constant = digit_sequence; + comma = ','; + comma_wsp = (wsp+ comma? wsp*) | (comma wsp*); + + flag = ('0' %push_false | '1' %push_true); + + number = + ( sign? integer_constant + | sign? floating_point_constant ) + >start_number %push_number; + + nonnegative_number = + ( integer_constant + | floating_point_constant) + >start_number %push_number; + + coordinate = number $(number,1) %(number,0); + coordinate_pair = (coordinate $(coordinate_pair_a,1) %(coordinate_pair_a,0) comma_wsp? coordinate $(coordinate_pair_b,1) %(coordinate_pair_b,0)) $(coordinate_pair,1) %(coordinate_pair,0); + elliptical_arc_argument = + (number $(elliptical_arg_a,1) %(elliptical_arg_a,0) comma_wsp? + number $(elliptical_arg_b,1) %(elliptical_arg_b,0) comma_wsp? + number comma_wsp + flag comma_wsp? flag comma_wsp? + coordinate_pair) + %elliptical_arc; + elliptical_arc_argument_sequence = + elliptical_arc_argument $1 %0 + (comma_wsp? elliptical_arc_argument $1 %0)*; + elliptical_arc = + ('A' %mode_abs| 'a' %mode_rel) wsp* + elliptical_arc_argument_sequence; + + smooth_quadratic_bezier_curveto_argument = + coordinate_pair %smooth_quadratic_bezier_curveto; + smooth_quadratic_bezier_curveto_argument_sequence = + smooth_quadratic_bezier_curveto_argument $1 %0 + (comma_wsp? + smooth_quadratic_bezier_curveto_argument $1 %0)*; + smooth_quadratic_bezier_curveto = + ('T' %mode_abs| 't' %mode_rel) wsp* + smooth_quadratic_bezier_curveto_argument_sequence; + + quadratic_bezier_curveto_argument = + (coordinate_pair $1 %0 comma_wsp? coordinate_pair) + %quadratic_bezier_curveto; + quadratic_bezier_curveto_argument_sequence = + quadratic_bezier_curveto_argument $1 %0 + (comma_wsp? quadratic_bezier_curveto_argument $1 %0)*; + quadratic_bezier_curveto = + ('Q' %mode_abs| 'q' %mode_rel) wsp* + quadratic_bezier_curveto_argument_sequence; + + smooth_curveto_argument = + (coordinate_pair $1 %0 comma_wsp? coordinate_pair) + %smooth_curveto; + smooth_curveto_argument_sequence = + smooth_curveto_argument $1 %0 + (comma_wsp? smooth_curveto_argument $1 %0)*; + smooth_curveto = + ('S' %mode_abs| 's' %mode_rel) + wsp* smooth_curveto_argument_sequence; + + curveto_argument = + (coordinate_pair $1 %0 comma_wsp? + coordinate_pair $1 %0 comma_wsp? + coordinate_pair) + %curveto; + curveto_argument_sequence = + curveto_argument $1 %0 + (comma_wsp? curveto_argument $1 %0)*; + curveto = + ('C' %mode_abs| 'c' %mode_rel) + wsp* curveto_argument_sequence; + + vertical_lineto_argument = coordinate %vertical_lineto; + vertical_lineto_argument_sequence = + vertical_lineto_argument $(vertical_lineto_argument_a,1) %(vertical_lineto_argument_a,0) + (comma_wsp? vertical_lineto_argument $(vertical_lineto_argument_b,1) %(vertical_lineto_argument_b,0))*; + vertical_lineto = + ('V' %mode_abs| 'v' %mode_rel) + wsp* vertical_lineto_argument_sequence; + + horizontal_lineto_argument = coordinate %horizontal_lineto; + horizontal_lineto_argument_sequence = + horizontal_lineto_argument $(horizontal_lineto_argument_a,1) %(horizontal_lineto_argument_a,0) + (comma_wsp? horizontal_lineto_argument $(horizontal_lineto_argument_b,1) %(horizontal_lineto_argument_b,0))*; + horizontal_lineto = + ('H' %mode_abs| 'h' %mode_rel) + wsp* horizontal_lineto_argument_sequence; + + lineto_argument = coordinate_pair %lineto; + lineto_argument_sequence = + lineto_argument $1 %0 + (comma_wsp? lineto_argument $1 %0)*; + lineto = + ('L' %mode_abs| 'l' %mode_rel) wsp* + lineto_argument_sequence; + + closepath = ('Z' | 'z') %closepath; + + moveto_argument = coordinate_pair %moveto; + moveto_argument_sequence = + moveto_argument $1 %0 + (comma_wsp? lineto_argument $1 %0)*; + moveto = + ('M' %mode_abs | 'm' %mode_rel) + wsp* moveto_argument_sequence; + + drawto_command = + closepath | lineto | + horizontal_lineto | vertical_lineto | + curveto | smooth_curveto | + quadratic_bezier_curveto | + smooth_quadratic_bezier_curveto | + elliptical_arc; + + drawto_commands = drawto_command (wsp* drawto_command)*; + moveto_drawto_command_group = moveto wsp* drawto_commands?; + moveto_drawto_command_groups = + moveto_drawto_command_group + (wsp* moveto_drawto_command_group)*; + + svg_path = wsp* moveto_drawto_command_groups? wsp*; + + + main := svg_path; + + write exec; + }%% + + 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=ragel:cindent:expandtab:shiftwidth=4: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..1b8cabe --- /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 (double _current_par : _current_pars) { + // TODO: optimize the use of absolute / relative coords + std::string cs = _formatCoord(_current_par); + + // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/2geom/sweep-bounds.cpp b/src/2geom/sweep-bounds.cpp new file mode 100644 index 0000000..2f31b67 --- /dev/null +++ b/src/2geom/sweep-bounds.cpp @@ -0,0 +1,154 @@ +#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.emplace_back(rs[i][d].min(), i, false); + events.emplace_back(rs[i][d].max(), i, true); + } + std::sort(events.begin(), events.end()); + + std::vector<unsigned> open; + for(auto & event : events) { + unsigned ix = event.ix; + if(event.closing) { + std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix); + //if(iter != open.end()) + open.erase(iter); + } else { + for(unsigned int jx : open) { + 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].emplace_back(r[d].min(), i, false); + events[n].emplace_back(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 int jx : open[0]) { + 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 int jx : open[1]) { + 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/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/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/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..268d4df --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,10 @@ +add_subdirectory(2geom) +add_subdirectory(performance-tests EXCLUDE_FROM_ALL) +if(GTK3_FOUND) + add_subdirectory(toys EXCLUDE_FROM_ALL) +else() + message("Not building toys as they require GTK3.") +endif() + +add_subdirectory(cython EXCLUDE_FROM_ALL) +add_subdirectory(py2geom EXCLUDE_FROM_ALL) diff --git a/src/cython/CMakeLists.txt b/src/cython/CMakeLists.txt new file mode 100644 index 0000000..9187d35 --- /dev/null +++ b/src/cython/CMakeLists.txt @@ -0,0 +1,131 @@ +#TODO - rewrite to use ALLCAPS? + +OPTION(2GEOM_CYTHON_BINDINGS + "Build a python binding with Cython." + OFF) +OPTION(2GEOM_CYTHON_BUILD_SHARED + "Build cython shared libraries." + ON) +IF(2GEOM_CYTHON_BUILD_SHARED) + SET(LIB_TYPE SHARED) + SET (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -shared") +ELSE(2GEOM_CYTHON_BUILD_SHARED) + SET(LIB_TYPE STATIC) +ENDIF(2GEOM_CYTHON_BUILD_SHARED) + + +IF(2GEOM_CYTHON_BINDINGS) + + include( UseCython ) + + # With CMake, a clean separation can be made between the source tree and the + # build tree. When all source is compiled, as with pure C/C++, the source is + # no-longer needed in the build tree. However, with pure *.py source, the + # source is processed directly. To handle this, we reproduce the availability + # of the source files in the build tree. + #add_custom_target( ReplicatePythonSourceTree ALL ${CMAKE_COMMAND} -P + # ${CMAKE_CURRENT_SOURCE_DIR}/CMakeScripts/ReplicatePythonSourceTree.cmake + # ${CMAKE_CURRENT_BINARY_DIR} + # WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + + #include_directories( ${CYTHON_CMAKE_EXAMPLE_SOURCE_DIR}/include ) + + # Process the CMakeLists.txt in the 'src' and 'bin' directory. + + set_source_files_properties( + _common_decl.pxd + _common_decl.pyx + + _cy_primitives.pxd + _cy_primitives.pyx + + _cy_rectangle.pxd + _cy_rectangle.pyx + + _cy_affine.pxd + _cy_affine.pyx + + _cy_curves.pxd + _cy_curves.pyx + + _cy_path.pxd + _cy_path.pyx + + _cy_conicsection.pxd + _cy_conicsection.pyx + + cy2geom.pyx + + PROPERTIES CYTHON_IS_CXX 1) + + # Multi-file cython modules do not appear to be working at the moment. + cython_add_module( _common_decl _common_decl.pyx) + + + cython_add_module( _cy_primitives _cy_primitives.pyx) + + cython_add_module( _cy_rectangle _cy_rectangle.pyx) + + cython_add_module( _cy_affine _cy_affine.pyx) + + cython_add_module( _cy_curves _cy_curves.pyx) + + cython_add_module( _cy_path _cy_path.pyx) + + #not finished for now + #~ cython_add_module( _cy_shape _cy_shape.pyx) + + cython_add_module( _cy_conicsection _cy_conicsection.pyx) + + target_link_libraries(_cy_primitives + #TODO! linking to static lib2geom.a gives -fPIC error, to compile + #you have to enable building dynamic library in cmake . -i + gsl gslcblas 2geom + ) + target_link_libraries(_cy_rectangle + gsl gslcblas 2geom + ) + + target_link_libraries(_cy_affine + gsl gslcblas 2geom + ) + + target_link_libraries(_cy_curves + gsl gslcblas 2geom + ) + + target_link_libraries(_cy_path + gsl gslcblas 2geom + ) + + #~ target_link_libraries(_cy_shape + #~ gsl gslcblas 2geom + #~ ) + + target_link_libraries(_cy_conicsection + gsl gslcblas 2geom + ) + + cython_add_module( cy2geom cy2geom.pyx) + + add_test(cython-primitives python2 test-primitives.py) + add_test(cython-rectangle python2 test-rectangle.py) + add_test(cython-affine python2 test-affine.py) + add_test(cython-curves python2 test-curves.py) + add_test(cython-path python2 test-path.py) + add_test(cython-conicsection python2 test-conicsection.py) + + # stuff to install the cy2geom package in the Python site-packages directory + FIND_PACKAGE(PythonLibs) + IF (WIN32) + GET_FILENAME_COMPONENT(PYTHON_LIB_INSTALL "${PYTHON_LIBRARY}" PATH) + GET_FILENAME_COMPONENT(SITEPACKAGE "${PYTHON_LIB_INSTALL}/../Lib/site-packages" ABSOLUTE) + ELSE (WIN32) + SET(PYTHON_LIB_INSTALL "/usr/local/lib/python2.7/dist-packages" CACHE STRING "Where to install the cy2geom module?") + SET(SITEPACKAGE ${PYTHON_LIB_INSTALL}) + ENDIF(WIN32) + + INSTALL(TARGETS _common_decl _cy_primitives _cy_rectangle _cy_affine _cy_curves _cy_path _cy_conicsection cy2geom + DESTINATION "${SITEPACKAGE}/cy2geom") + +ENDIF(2GEOM_CYTHON_BINDINGS) diff --git a/src/cython/README.md b/src/cython/README.md new file mode 100644 index 0000000..a4a0847 --- /dev/null +++ b/src/cython/README.md @@ -0,0 +1,29 @@ +# Installing: + +In addition to 2geom dependencies, cython bindings need `cython >= 0.16`. + +You can turn them on using cmake. Please note that you need to enable +shared library option as well. + +Building on Windows is not tested yet, should be done shortly. Extrapolating +from other projects using cython, this should not be major problem. + +# Usage: + +Bindings are almost 1-1 to 2geom, so using them is pretty straightforward. +Classes and methods are documented shortly. It's generally good idea to +look at 2geom docs and, if problems persist, at their source. + +To look at simple use cases, I suggest looking at tests and utils.py, located +in cython-bindings directory. + +# Hacking: + +cython is pretty straightforward to pick up, but its docs usually cover only +the simplest example. Looking at source of other project can be helpful +(cython bindings for SFML 2 are good example). + +Don't hesitate to contact me or 2geom mailinglist with any requests concerning +design of bindings and bug reports. + +Jan Pulmann - jan.pulmann@gmail.com diff --git a/src/cython/_common_decl.pxd b/src/cython/_common_decl.pxd new file mode 100644 index 0000000..8877048 --- /dev/null +++ b/src/cython/_common_decl.pxd @@ -0,0 +1,14 @@ +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +ctypedef double Coord +ctypedef int IntCoord + +cdef extern from "2geom/coord.h" namespace "Geom": + cdef Coord EPSILON + cdef enum Dim2: + X = 0 + Y = 1 + +cdef object wrap_vector_double(vector[double] v) +cdef vector[double] make_vector_double(object l) diff --git a/src/cython/_common_decl.pyx b/src/cython/_common_decl.pyx new file mode 100644 index 0000000..1f1544e --- /dev/null +++ b/src/cython/_common_decl.pyx @@ -0,0 +1,12 @@ +cdef object wrap_vector_double(vector[double] v): + r = [] + cdef unsigned int i + for i in range(v.size()): + r.append(v[i]) + return r + +cdef vector[double] make_vector_double(object l): + cdef vector[double] ret + for i in l: + ret.push_back( float(i) ) + return ret diff --git a/src/cython/_cy_affine.pxd b/src/cython/_cy_affine.pxd new file mode 100644 index 0000000..91eb662 --- /dev/null +++ b/src/cython/_cy_affine.pxd @@ -0,0 +1,247 @@ +from _common_decl cimport * + +from _cy_rectangle cimport Rect, cy_Rect, wrap_Rect +from _cy_primitives cimport Point, cy_Point, wrap_Point + + +cdef extern from "2geom/affine.h" namespace "Geom": + cdef cppclass Affine: + + Affine(Affine &) + Affine() + Affine(Coord, Coord, Coord, Coord, Coord, Coord) + + Coord operator[](unsigned int) + + Affine & operator*(Affine &) + Affine & operator*(Translate &) + Affine & operator*(Scale &) + Affine & operator*(Rotate &) + Affine & operator*(HShear &) + Affine & operator*(VShear &) + Affine & operator*(Zoom &) + + bint operator==(Affine &) + bint operator!=(Affine &) + + Point xAxis() + Point yAxis() + Point translation() + Coord expansionX() + Coord expansionY() + Point expansion() + + void setXAxis(Point &) + void setYAxis(Point &) + void setTranslation(Point &) + void setExpansionX(Coord) + void setExpansionY(Coord) + void setIdentity() + + bint isIdentity(Coord) + bint isTranslation(Coord) + bint isScale(Coord) + bint isUniformScale(Coord) + bint isRotation(Coord) + bint isHShear(Coord) + bint isVShear(Coord) + bint isNonzeroTranslation(Coord) + bint isNonzeroScale(Coord) + bint isNonzeroUniformScale(Coord) + bint isNonzeroRotation(Coord) + bint isNonzeroHShear(Coord) + bint isNonzeroVShear(Coord) + bint isZoom(Coord) + bint preservesArea(Coord) + bint preservesAngles(Coord) + bint preservesDistances(Coord) + bint flips() + bint isSingular(Coord) + + Affine withoutTranslation() + Affine inverse() + + Coord det() + Coord descrim2() + Coord descrim() + + bint are_near(Affine &, Affine &, Coord) + Affine a_identity "Geom::Affine::identity" () + +cdef extern from "2geom/transforms.h" namespace "Geom": + Affine reflection(Point &, Point &) + #TODO find out how cython __pow__ works + Affine pow(Affine &, int) + Translate pow(Translate &, int) + Scale pow(Scale &, int) + Rotate pow(Rotate &, int) + HShear pow(HShear &, int) + VShear pow(VShear &, int) + Zoom pow(Zoom &, int) + +cdef class cy_Affine: + cdef Affine* thisptr + +cdef cy_Affine wrap_Affine(Affine) + +#helper functions +cdef Affine get_Affine(t) +cdef bint is_transform(t) + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass Translate: + Translate(Translate &) + Translate() + Translate(Point &) + Translate(Coord, Coord) + Coord operator[](Dim2) + Coord operator[](unsigned int) + Translate & operator*(Translate &) + Affine & operator*(Affine &) + bint operator==(Translate &) + bint operator!=(Translate &) + + Affine operator() + + Point vector() + Translate inverse() + + Translate t_identity "Geom::Translate::identity" () + +cdef class cy_Translate: + cdef Translate* thisptr + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass Scale: + Scale(Scale &) + Scale() + Scale(Point &) + Scale(Coord, Coord) + Scale(Coord) + Coord operator[](Dim2) + Scale & operator*(Scale &) + Affine & operator*(Affine &) + bint operator==(Scale &) + bint operator!=(Scale &) + + Affine operator() + + Point vector() + Scale inverse() + Scale identity() + + Scale s_identity "Geom::Scale::identity" () + +cdef class cy_Scale: + cdef Scale* thisptr + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass Rotate: + Rotate(Rotate &) + Rotate() + Rotate(Coord) + Rotate(Point &) + Rotate(Coord, Coord) + Point vector() + + Coord operator[](Dim2) + Coord operator[](unsigned int) + Rotate & operator*(Rotate &) + Affine & operator*(Affine &) + bint operator==(Rotate &) + bint operator!=(Rotate &) + + Affine operator() + Rotate inverse() + + Rotate r_identity "Geom::Rotate::identity" () + +cdef extern from "2geom/transforms.h" namespace "Geom::Rotate": + Rotate from_degrees(Coord) + + +cdef class cy_Rotate: + cdef Rotate* thisptr + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass VShear: + VShear(VShear &) + VShear(Coord) + Coord factor() + void setFactor(Coord) + + VShear &operator*(VShear) + Affine & operator*(Affine &) + bint operator==(VShear &) + bint operator!=(VShear &) + Affine operator() + + VShear inverse() + + VShear vs_identity "Geom::VShear::identity"() + +cdef class cy_VShear: + cdef VShear* thisptr + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass HShear: + HShear(HShear &) + HShear(Coord) + Coord factor() + void setFactor(Coord) + HShear &operator*(HShear) + Affine & operator*(Affine &) + bint operator==(HShear &) + bint operator!=(HShear &) + Affine operator() + + HShear inverse() + + HShear hs_identity "Geom::HShear::identity"() + +cdef class cy_HShear: + cdef HShear* thisptr + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass Zoom: + Zoom(Zoom &) + Zoom(Coord) + Zoom(Translate &) + Zoom(Coord, Translate &) + + Zoom & operator*(Zoom &) + Affine & operator*(Affine &) + bint operator==(Zoom &) + bint operator!=(Zoom &) + + Affine operator() + + Coord scale() + void setScale(Coord) + Point translation() + void setTranslation(Point &) + + Zoom inverse() + + Zoom() + + Zoom z_identity "Geom::Zoom::identity" () + +cdef extern from "2geom/transforms.h" namespace "Geom::Zoom": + Zoom map_rect(Rect &, Rect &) + +cdef class cy_Zoom: + cdef Zoom* thisptr + + +cdef extern from "2geom/affine.h" namespace "Geom": + cdef cppclass Eigen: + Point *vectors + double *values + Eigen(Affine &) + Eigen(double[2][2]) diff --git a/src/cython/_cy_affine.pyx b/src/cython/_cy_affine.pyx new file mode 100644 index 0000000..19aa9ef --- /dev/null +++ b/src/cython/_cy_affine.pyx @@ -0,0 +1,736 @@ +from cython.operator cimport dereference as deref + +from numbers import Number + + +cdef class cy_Affine: + + """Class representing affine transform in 2D plane. + + Corresponds to Affine class in 2geom. + """ + + def __cinit__(self, c0=None, + Coord c1=0, + Coord c2=0, + Coord c3=1, + Coord c4=0, + Coord c5=0): + """Create Affine instance from either transform or from coefficients.""" + if c0 is None: + self.thisptr = new Affine() + elif is_transform(c0): + self.thisptr = new Affine( get_Affine(c0) ) + else: + self.thisptr = new Affine(<Coord> float(c0) ,c1 ,c2 ,c3 ,c4 ,c5) + + def __str__(self): + """str(self)""" + return "Affine({}, {}, {}, {}, {}, {})".format( self[0], + self[1], + self[2], + self[3], + self[4], + self[5], + ) + def __repr__(self): + """repr(self)""" + return str(self) + + def __dealloc__(self): + del self.thisptr + + def __getitem__(self, int i): + """Get coefficients.""" + if i >= 6: + raise IndexError("Affine has only 6 coefficients.") + return deref(self.thisptr) [i] + + def __mul__(cy_Affine self, other): + """Compose with another transformation.""" + if isinstance(other, cy_Affine): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Affine> other).thisptr ) ) + elif isinstance(other, cy_Translate): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Translate> other).thisptr ) ) + elif isinstance(other, cy_Scale): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Scale> other).thisptr ) ) + elif isinstance(other, cy_Rotate): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Rotate> other).thisptr ) ) + elif isinstance(other, cy_HShear): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_HShear> other).thisptr ) ) + elif isinstance(other, cy_VShear): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_VShear> other).thisptr ) ) + elif isinstance(other, cy_Zoom): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Zoom> other).thisptr ) ) + + def __pow__(cy_Affine self, int n, z): + """Compose with self n times.""" + return wrap_Affine(pow( deref(self.thisptr), n )) + + def __richcmp__(cy_Affine self, cy_Affine other, int op): + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + + def x_axis(self): + """Transformation of unit x vector without translation.""" + return wrap_Point(self.thisptr.xAxis()) + + def y_axis(self): + """Transformation of unit y vector without translation.""" + return wrap_Point(self.thisptr.yAxis()) + + def translation(self): + """Translation of transformation.""" + return wrap_Point(self.thisptr.translation()) + + def expansion_X(self): + """Expansion of unit x vector.""" + return self.thisptr.expansionX() + + def expansion_Y(self): + """Expansion of unit y vector.""" + return self.thisptr.expansionY() + + def expansion(self): + """Point( self.expansion_X(), self.expansion_Y() )""" + return wrap_Point(self.thisptr.expansion()) + + def set_X_axis(self, cy_Point vec): + """Set transformation of x unit vector without translation.""" + self.thisptr.setXAxis(deref( vec.thisptr )) + + def set_Y_axis(self, cy_Point vec): + """Set transformation of y unit vector without translation.""" + self.thisptr.setYAxis(deref( vec.thisptr )) + + def set_translation(self, cy_Point loc): + """Set translation of origin.""" + self.thisptr.setTranslation(deref( loc.thisptr )) + + def set_expansion_X(self, Coord val): + """Set expansion of x unit vector.""" + self.thisptr.setExpansionX(val) + + def set_expansion_Y(self, Coord val): + """Set expansion of y unit vector.""" + self.thisptr.setExpansionY(val) + + def set_identity(self): + """Set self to identity transformation.""" + self.thisptr.setIdentity() + + def is_identity(self, Coord eps=EPSILON): + """Return true if self is close to identity transform. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isIdentity(eps) + + def is_translation(self, Coord eps=EPSILON): + """Return true if self is close to transformation. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isTranslation(eps) + + def is_scale(self, Coord eps=EPSILON): + """Return true if self is close to scale. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isScale(eps) + + def is_uniform_scale(self, Coord eps=EPSILON): + """Return true if self is close to uniform scale. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isUniformScale(eps) + + def is_rotation(self, Coord eps=EPSILON): + """Return true if self is close to rotation. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isRotation(eps) + + def is_HShear(self, Coord eps=EPSILON): + """Return true if self is close to horizontal shear. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isHShear(eps) + + def is_VShear(self, Coord eps=EPSILON): + """Return true if self is close to vertical shear. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isVShear(eps) + + def is_nonzero_translation(self, Coord eps=EPSILON): + """Return true if self is close to translation and is identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroTranslation(eps) + + def is_nonzero_scale(self, Coord eps=EPSILON): + """Return true if self is close to scale and is identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroScale(eps) + + def is_nonzero_uniform_scale(self, Coord eps=EPSILON): + """Return true if self is close to scale and is identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroUniformScale(eps) + + def is_nonzero_rotation(self, Coord eps=EPSILON): + """Return true if self is close to rotation and is identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroRotation(eps) + + def is_nonzero_HShear(self, Coord eps=EPSILON): + """Return true if self is close to horizontal shear and is not identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroHShear(eps) + + def is_nonzero_VShear(self, Coord eps=EPSILON): + """Return true if self is close to vertical shear and is not identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroVShear(eps) + + def is_zoom(self, Coord eps=EPSILON): + """Return true if self is close to zoom. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isZoom(eps) + + def preserves_area(self, Coord eps=EPSILON): + """Return true if areas are preserved after transformation + + Use second argument eps to specify tolerance. + """ + return self.thisptr.preservesArea(eps) + + def preserves_angles(self, Coord eps=EPSILON): + """Return true if angles are preserved after transformation + + Use second argument eps to specify tolerance. + """ + return self.thisptr.preservesAngles(eps) + + def preserves_distances(self, Coord eps=EPSILON): + """Return true if distances are preserved after transformation + + Use second argument eps to specify tolerance. + """ + return self.thisptr.preservesDistances(eps) + + def flips(self): + """Return true if transformation flips - it has negative scaling.""" + return self.thisptr.flips() + + def is_singular(self, Coord eps=EPSILON): + """Check whether transformation matrix is singular.""" + return self.thisptr.isSingular(eps) + + def without_translation(self): + """Return transformation without translation part.""" + return wrap_Affine(self.thisptr.withoutTranslation()) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Affine(self.thisptr.inverse()) + + def det(self): + """Return determinant of transformation matrix.""" + return self.thisptr.det() + + def descrim2(self): + """Return absolute value of self.det()""" + return self.thisptr.descrim2() + + def descrim(self): + """Return square root of self.descrim2()""" + return self.thisptr.descrim() + + @classmethod + def identity(self): + """Create identity transformation.""" + return wrap_Affine(a_identity()) + + @classmethod + def are_near(cls, A, B, Coord eps=EPSILON): + """Test if two transforms are near.""" + if is_transform(A) & is_transform(B): + return are_near(get_Affine(A), get_Affine(B), eps) + + @classmethod + def reflection(cls, cy_Point vector, cy_Point origin): + """Create transformation reflecting along line specified by vector and origin.""" + return wrap_Affine( reflection( deref(vector.thisptr), deref(origin.thisptr) ) ) + +cdef cy_Affine wrap_Affine(Affine p): + cdef Affine * retp = new Affine() + retp[0] = p + cdef cy_Affine r = cy_Affine.__new__(cy_Affine) + r.thisptr = retp + return r + +cdef Affine get_Affine(t): + if isinstance(t, cy_Affine ): + return deref( (<cy_Affine> t).thisptr ) + elif isinstance(t, cy_Translate): + return <Affine> deref( (<cy_Translate> t).thisptr ) + elif isinstance(t, cy_Scale): + return <Affine> deref( (<cy_Scale> t).thisptr ) + elif isinstance(t, cy_Rotate): + return <Affine> deref( (<cy_Rotate> t).thisptr ) + elif isinstance(t, cy_HShear): + return <Affine> deref( (<cy_HShear> t).thisptr ) + elif isinstance(t, cy_VShear): + return <Affine> deref( (<cy_VShear> t).thisptr ) + elif isinstance(t, cy_Zoom): + return <Affine> deref( (<cy_Zoom> t).thisptr ) + +cdef bint is_transform(t): + return any([isinstance(t, cy_Affine), + isinstance(t, cy_Translate), + isinstance(t, cy_Scale), + isinstance(t, cy_Rotate), + isinstance(t, cy_HShear), + isinstance(t, cy_VShear), + isinstance(t, cy_Zoom) + ]) + + +cdef class cy_Translate: + + """Translation in 2D plane + + Corresponds to Translate class in 2geom. + """ + + def __cinit__(self, *args): + """Create Translate instance form point or it's two coordinates.""" + if len(args) == 0: + self.thisptr = new Translate() + elif len(args) == 1: + self.thisptr = new Translate( deref( (<cy_Point> args[0]).thisptr ) ) + elif len(args) == 2: + self.thisptr = new Translate(float(args[0]), float(args[1])) + + def __dealloc__(self): + del self.thisptr + + + def __getitem__(self, Dim2 dim): + """Get components of displacement vector.""" + return deref( self.thisptr ) [dim] + + def __mul__(cy_Translate self, o): + """Compose with another transformation.""" + if isinstance(o, cy_Translate): + return wrap_Translate(deref( self.thisptr ) * deref( (<cy_Translate>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_Translate self, int n, z): + """Compose with self n times.""" + return wrap_Translate(pow( deref(self.thisptr), n )) + + def vector(self): + """Get displacement vector.""" + return wrap_Point(self.thisptr.vector()) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Translate(self.thisptr.inverse()) + + @classmethod + def identity(self): + """Create identity translation.""" + return wrap_Translate(t_identity()) + + def __richcmp__(cy_Translate self, cy_Translate t, op): + if op == 2: + return deref(self.thisptr) == deref(t.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(t.thisptr) + +cdef cy_Translate wrap_Translate(Translate p): + cdef Translate * retp = new Translate() + retp[0] = p + cdef cy_Translate r = cy_Translate.__new__(cy_Translate) + r.thisptr = retp + return r + + +cdef class cy_Scale: + + """Scale in 2D plane. + + Corresponds to Scale in 2geom. + """ + + def __cinit__(self, *args): + """Create scale from number, point or it's two coordinates. + + One number creates uniform scale, point or two numbers create + scale with different x and y scale factor. + """ + if len(args) == 0: + self.thisptr = new Scale() + elif len(args) == 1: + if isinstance(args[0], Number): + self.thisptr = new Scale(<Coord> float(args[0])) + elif isinstance(args[0], cy_Point): + self.thisptr = new Scale( deref( (<cy_Point> args[0]).thisptr ) ) + elif len(args) == 2: + self.thisptr = new Scale(float(args[0]), float(args[1])) + + def __dealloc__(self): + del self.thisptr + + def __getitem__(self, Dim2 d): + """Get scale factors for each axis.""" + return deref( self.thisptr ) [d] + + def __mul__(cy_Scale self, o): + """Compose with another transformation.""" + if isinstance(o, cy_Scale): + return wrap_Scale(deref( self.thisptr ) * deref( (<cy_Scale>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_Scale self, int n, z): + """Compose with self n times.""" + return wrap_Scale(pow( deref(self.thisptr), n )) + + def vector(self): + """Get both scale factors as a point.""" + return wrap_Point(self.thisptr.vector()) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Scale(self.thisptr.inverse()) + + @classmethod + def identity(self): + """Create identity scale.""" + return wrap_Scale(s_identity()) + + def __richcmp__(cy_Scale self, cy_Scale s, op): + if op == 2: + return deref(self.thisptr) == deref(s.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(s.thisptr) + +cdef cy_Scale wrap_Scale(Scale p): + cdef Scale * retp = new Scale() + retp[0] = p + cdef cy_Scale r = cy_Scale.__new__(cy_Scale) + r.thisptr = retp + return r + + +cdef class cy_Rotate: + + """Rotation in 2D plane. + + Corresponds to Rotate in 2geom. + """ + + def __cinit__(self, *args): + """Create new Rotate instance, specifying angle. + + Use one number to set the angle, or point/its two coordinates, + using point's angle with x-axis as a rotation angle. + """ + if len(args) == 0: + self.thisptr = new Rotate() + elif len(args) == 1: + if isinstance(args[0], Number): + self.thisptr = new Rotate(<Coord> float(args[0])) + elif isinstance(args[0], cy_Point): + self.thisptr = new Rotate( deref( (<cy_Point> args[0]).thisptr ) ) + elif len(args) == 2: + self.thisptr = new Rotate(float(args[0]), float(args[1])) + + def __dealloc__(self): + del self.thisptr + + def vector(self): + """Return Point(1, 0)*self.""" + return wrap_Point(self.thisptr.vector()) + + def __getitem__(self, Dim2 dim): + """Get components of self.vector()""" + return deref( self.thisptr ) [dim] + + def __mul__(cy_Rotate self, o): + """Compose with another transformation.""" + if isinstance(o, cy_Rotate): + return wrap_Rotate(deref( self.thisptr ) * deref( (<cy_Rotate>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_Rotate self, int n, z): + """Compose with self n times.""" + return wrap_Rotate(pow( deref(self.thisptr), n )) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Rotate(self.thisptr.inverse()) + + @classmethod + def identity(cls): + """Create identity rotation.""" + return wrap_Rotate(r_identity()) + + @classmethod + def from_degrees(cls, Coord deg): + """Create rotation from angle in degrees.""" + return wrap_Rotate(from_degrees(deg)) + + def __richcmp__(cy_Rotate self, cy_Rotate r, op): + if op == 2: + return deref(self.thisptr) == deref(r.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(r.thisptr) + +cdef cy_Rotate wrap_Rotate(Rotate p): + cdef Rotate * retp = new Rotate() + retp[0] = p + cdef cy_Rotate r = cy_Rotate.__new__(cy_Rotate) + r.thisptr = retp + return r + + +cdef class cy_VShear: + + """Vertical shear in 2D plane + + Corresponds to VShear in 2geom. + """ + + def __cinit__(self, Coord h): + """Create VShear instance form shearing factor.""" + self.thisptr = new VShear(h) + + def __dealloc__(self): + del self.thisptr + + def factor(self): + """Get shearing factor.""" + return self.thisptr.factor() + + def set_factor(self, Coord nf): + """Set shearing factor.""" + self.thisptr.setFactor(nf) + + def __mul__(cy_VShear self, o): + """Compose with another transformation.""" + if isinstance(o, cy_VShear): + return wrap_VShear(deref( self.thisptr ) * deref( (<cy_VShear>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_VShear self, int n, z): + """Compose with self n times.""" + return wrap_VShear(pow( deref(self.thisptr), n )) + + def inverse(self): + """Return inverse transformation.""" + return wrap_VShear(self.thisptr.inverse()) + + @classmethod + def identity(cls): + """Create identity VShear.""" + return wrap_VShear( vs_identity() ) + + def __richcmp__(cy_VShear self, cy_VShear hs, op): + if op == 2: + return deref(self.thisptr) == deref(hs.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(hs.thisptr) + + + +cdef cy_VShear wrap_VShear(VShear p): + cdef VShear * retp = new VShear(0) + retp[0] = p + cdef cy_VShear r = cy_VShear.__new__(cy_VShear, 0) + r.thisptr = retp + return r + +cdef class cy_HShear: + + """Horizontal shear in 2D plane + + Corresponds to HShear in 2geom. + """ + + def __cinit__(self, Coord h): + """Create HShear instance form shearing factor.""" + self.thisptr = new HShear(h) + + def __dealloc__(self): + del self.thisptr + + def factor(self): + """Get shearing factor.""" + return self.thisptr.factor() + + def set_factor(self, Coord nf): + """Set shearing factor.""" + self.thisptr.setFactor(nf) + + def __mul__(cy_HShear self, o): + """Compose with another transformation.""" + if isinstance(o, cy_HShear): + return wrap_HShear(deref( self.thisptr ) * deref( (<cy_HShear>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_HShear self, int n, z): + """Compose with self n times.""" + return wrap_HShear(pow( deref(self.thisptr), n )) + + def inverse(self): + """Return inverse transformation.""" + return wrap_HShear(self.thisptr.inverse()) + + @classmethod + def identity(cls): + """Create identity HShear.""" + return wrap_HShear( hs_identity() ) + + def __richcmp__(cy_HShear self, cy_HShear hs, op): + if op == 2: + return deref(self.thisptr) == deref(hs.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(hs.thisptr) + +cdef cy_HShear wrap_HShear(HShear p): + cdef HShear * retp = new HShear(0) + retp[0] = p + cdef cy_HShear r = cy_HShear.__new__(cy_HShear, 0) + r.thisptr = retp + return r + + +cdef class cy_Zoom: + + """Zoom in 2D plane, consisting of uniform scale and translation. + + Corresponds to Zoom in 2geom. + """ + + def __cinit__(self, Coord scale=1, cy_Translate translate=cy_Translate()): + """Create Zoom from scale factor and translation""" + self.thisptr = new Zoom( scale, deref( translate.thisptr ) ) + + def __dealloc__(self): + del self.thisptr + + def __mul__(cy_Zoom self, cy_Zoom z): + """Compose with another transformation.""" + return wrap_Zoom( deref(self.thisptr) * deref( z.thisptr )) + + def __pow__(cy_Zoom self, int n, z): + """Compose with self n times.""" + return wrap_Zoom(pow( deref(self.thisptr), n )) + + def __richcmp__(cy_Zoom self, cy_Zoom z, op): + if op == 2: + return deref(self.thisptr) == deref(z.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(z.thisptr) + + def scale(self): + """Get scale factor.""" + return self.thisptr.scale() + + def set_scale(self, Coord s): + """Set scale factor.""" + self.thisptr.setScale(s) + + def translation(self): + """Get translation as a point.""" + return wrap_Point(self.thisptr.translation()) + + def set_translation(self, cy_Point p): + """Set translation.""" + self.thisptr.setTranslation(deref( p.thisptr )) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Zoom(self.thisptr.inverse()) + + @classmethod + def identity(cls): + """Create identity zoom.""" + return wrap_Zoom(z_identity()) + + @classmethod + def map_rect(self, cy_Rect old_r, cy_Rect new_r): + """Create zooming used to go from old rectangle to new.""" + return wrap_Zoom(map_rect(deref( old_r.thisptr ) ,deref( new_r.thisptr ))) + +cdef cy_Zoom wrap_Zoom(Zoom p): + cdef Zoom * retp = new Zoom(0) + retp[0] = p + cdef cy_Zoom r = cy_Zoom.__new__(cy_Zoom, 0) + r.thisptr = retp + return r + + +cdef class cy_Eigen: + + """Class computing eigenvalues and eigenvectors of 2x2 matrix. + + Corresponds to Eigen class in 2geom. + """ + + cdef Eigen* thisptr + + def __cinit__(self, a): + """Create Eigen form 2D transform or 2x2 list - matrix.""" + cdef Affine at + cdef double m[2][2] + if is_transform(a): + at = get_Affine(a) + self.thisptr = new Eigen(at) + else: + for i in range(2): + for j in range(2): + m[i][j] = a[i][j] + self.thisptr = new Eigen(m) + + def __dealloc__(self): + del self.thisptr + + @property + def vectors(self): + """Eigenvectors of matrix.""" + return (wrap_Point(self.thisptr.vectors[0]), wrap_Point(self.thisptr.vectors[1])) + + @property + def values(self): + """Eigenvalues of matrix.""" + return (self.thisptr.values[0], self.thisptr.values[1]) diff --git a/src/cython/_cy_conicsection.pxd b/src/cython/_cy_conicsection.pxd new file mode 100644 index 0000000..f330cd3 --- /dev/null +++ b/src/cython/_cy_conicsection.pxd @@ -0,0 +1,50 @@ +from _common_decl cimport * + +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect +from _cy_rectangle cimport cy_OptRect +from _cy_affine cimport is_transform, get_Affine, Affine +from _cy_curves cimport Curve, cy_Curve, wrap_Curve_p +from _cy_curves cimport SBasis, cy_SBasis +from _cy_curves cimport EllipticalArc, cy_EllipticalArc, wrap_EllipticalArc + +from _cy_path cimport Path, cy_Path + +from _cy_primitives cimport Point, cy_Point, wrap_Point, wrap_vector_point, make_vector_point + + +cdef extern from "2geom/circle.h" namespace "Geom": + cdef cppclass Circle: + Circle() + Circle(double, double, double) + Circle(Point, double) + Circle(double, double, double, double) + Circle(vector[Point] &) + void setCenter(Point &) + void setRadius(double) + void set(double, double, double, double) + void fit(vector[Point] &) + EllipticalArc * arc(Point &, Point &, Point &, bint) + Point center() + Coord center(Dim2) + Coord radius() + +cdef extern from "2geom/ellipse.h" namespace "Geom": + cdef cppclass Ellipse: + Ellipse() + Ellipse(double, double, double, double, double) + Ellipse(double, double, double, double, double, double) + Ellipse(vector[Point] &) + Ellipse(Circle &) + void set(double, double, double, double, double) + void set(double, double, double, double, double, double) + void set(vector[Point] &) + EllipticalArc * arc(Point &, Point &, Point &, bint) + Point center() + Coord center(Dim2) + Coord ray(Dim2) + Coord rot_angle() + vector[double] implicit_form_coefficients() + Ellipse transformed(Affine &) diff --git a/src/cython/_cy_conicsection.pyx b/src/cython/_cy_conicsection.pyx new file mode 100644 index 0000000..91bbd73 --- /dev/null +++ b/src/cython/_cy_conicsection.pyx @@ -0,0 +1,183 @@ +from cython.operator cimport dereference as deref +from numbers import Number +from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect +from _cy_rectangle cimport cy_Interval, wrap_Interval + +from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform + +from _cy_curves cimport is_Curve, get_Curve_p +from _cy_path cimport wrap_Path + + +cdef class cy_Circle: + + """Circle in 2D plane. + + Corresponds to Circle class in 2geom. + """ + + cdef Circle* thisptr + + def __cinit__(self, cy_Point center=None, double r=0): + """Create circle from center and radius.""" + if center is None: + self.thisptr = new Circle() + else: + self.thisptr = new Circle(deref( center.thisptr ), r) + + @classmethod + def from_coefficients(self, double A, double B, double C, double D): + """Create circle form implicit equation coefficients: + + Implicit equation is Ax**2 + Ay**2 + Bx + Cy + D = 0 + """ + return wrap_Circle( Circle(A, B, C, D) ) + + @classmethod + def from_points(self, points): + """Create best fitting circle from at least three points.""" + return wrap_Circle( Circle( make_vector_point(points) ) ) + + def set_center(self, cy_Point c): + """Set coordinates of center.""" + self.thisptr.setCenter(deref(c.thisptr)) + + def set_radius(self, double r): + """Set the circle's radius.""" + self.thisptr.setRadius(r) + + def set_coefficients(self, double A, double B, double C, double D): + """Set implicit equation coefficients: + + Implicit equation is Ax**2 + Ay**2 + Bx + Cy + D = 0 + """ + self.thisptr.set(A, B, C, D) + + def fit(self, points): + """Set circle to the best fit of at least three points.""" + self.thisptr.fit( make_vector_point(points) ) + + def arc(self, cy_Point initial, cy_Point inner, cy_Point final, bint _svg_compliant=True): + """Get (SVG)EllipticalArc. + + Args: + initial: Initial point of arc + inner: Inner point of arc. + final: Final point of arc. + """ + return wrap_EllipticalArc( deref(self.thisptr.arc(deref( initial.thisptr ), deref( inner.thisptr ), deref( final.thisptr ))) ) + + def center(self): + """Get center of circle in point.""" + return wrap_Point(self.thisptr.center()) + + def radius(self): + """Get radius of circle.""" + return self.thisptr.radius() + +cdef cy_Circle wrap_Circle(Circle p): + cdef Circle * retp = new Circle() + retp[0] = p + cdef cy_Circle r = cy_Circle.__new__(cy_Circle) + r.thisptr = retp + return r + +cdef class cy_Ellipse: + """Ellipse in 2D plane. + + Corresponds to Ellipse class in 2geom. + """ + + cdef Ellipse* thisptr + + def __cinit__(self, cy_Point center=None, rx=0, ry=0, double a=0): + """Create new ellipse: + + Args: + center: Center of ellipse (between foci) + rx, ry: major and minor semi-axis. + a: angle of major axis. + """ + if center is None: + self.thisptr = new Ellipse() + else: + self.thisptr = new Ellipse(center.x, center.y, rx, ry, a) + + @classmethod + def from_coefficients(cls, double A, double B, double C, double D, double E, double F): + """Create ellipse from coefficients of implicit equation. + + Implicit equation has form Ax**2 + Bxy + Cy**2 + Dx + Ey + F = 0 + """ + return wrap_Ellipse(Ellipse(A, B, C, D, E, F)) + + @classmethod + def from_circle(cls, cy_Circle c): + """Create ellipse identical to circle.""" + return wrap_Ellipse(Ellipse(deref( c.thisptr ))) + + @classmethod + def from_points(cls, points): + """Create ellipse fitting at least 5 points.""" + return wrap_Ellipse( Ellipse( make_vector_point(points) ) ) + + def set(self, cy_Point center, double rx, double ry, double a): + """Set center, rays and angle of ellipse. + + Args: + center: Center of ellipse. + rx, ry: Major and minor semi-axis. + a: angle of major axis. + self.thisptr.set(center.x, center.y, rx, ry, a) + """ + + def set_coefficients(self, double A, double B, double C, double D, double E, double F): + """Set coefficients of implicit equation. + + Implicit equation has form Ax**2 + Bxy + Cy**2 + Dx + Ey + F = 0 + """ + self.thisptr.set(A, B, C, D, E, F) + + def set_points(self, points): + """Set ellipse to the best fit of at least five points.""" + self.thisptr.set( make_vector_point(points) ) + + def arc(self, cy_Point initial, cy_Point inner, cy_Point final, bint svg_compliant=True): + """Get (SVG)EllipticalArc. + + Args: + initial: Initial point of arc + inner: Inner point of arc. + final: Final point of arc. + """ + return wrap_EllipticalArc( deref(self.thisptr.arc(deref( initial.thisptr ), deref( inner.thisptr ), deref( final.thisptr ))) ) + + def center(self): + """Get center of ellipse.""" + return wrap_Point(self.thisptr.center()) + + def ray(self, Dim2 d): + """Get major/minor semi-axis.""" + return self.thisptr.ray(d) + + def rot_angle(self): + """Get angle of major axis.""" + return self.thisptr.rot_angle() + + def implicit_form_coefficients(self): + """Get coefficients of implicit equation in list.""" + return wrap_vector_double(self.thisptr.implicit_form_coefficients()) + + def transformed(self, m): + """Return transformed ellipse.""" + cdef Affine at + if is_transform(m): + at = get_Affine(m) + return wrap_Ellipse(self.thisptr.transformed(at)) + +cdef cy_Ellipse wrap_Ellipse(Ellipse p): + cdef Ellipse * retp = new Ellipse() + retp[0] = p + cdef cy_Ellipse r = cy_Ellipse.__new__(cy_Ellipse) + r.thisptr = retp + return r diff --git a/src/cython/_cy_curves.pxd b/src/cython/_cy_curves.pxd new file mode 100644 index 0000000..dd13c06 --- /dev/null +++ b/src/cython/_cy_curves.pxd @@ -0,0 +1,421 @@ +from _common_decl cimport * + +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect +from _cy_affine cimport Affine +from _cy_primitives cimport Point, cy_Point, wrap_Point, wrap_vector_point, make_vector_point +from _cy_primitives cimport Angle, cy_Angle, wrap_Angle +from _cy_primitives cimport AngleInterval + + +cdef extern from "2geom/d2.h" namespace "Geom": + cdef cppclass D2[T]: + D2() + D2(T &, T &) + T& operator[](unsigned i) + +cdef extern from "2geom/curve.h" namespace "Geom": + cdef cppclass Curve: + Curve() + Point initialPoint() + Point finalPoint() + bint isDegenerate() + Point pointAt(Coord) + Coord valueAt(Coord, Dim2) + Point operator()(Coord) + vector[Point] pointAndDerivatives(Coord, unsigned int) + void setInitial(Point &) + void setFinal(Point &) + Rect boundsFast() + Rect boundsExact() + OptRect boundsLocal(OptInterval &, unsigned int) + OptRect boundsLocal(OptInterval &) + Curve * duplicate() + Curve * transformed(Affine &) + Curve * portion(Coord, Coord) + Curve * portion(Interval &) + Curve * reverse() + Curve * derivative() + Coord nearestTime(Point &, Coord, Coord) + Coord nearestTime(Point &, Interval &) + vector[double] allNearestTimes(Point &, Coord, Coord) + vector[double] allNearestTimes(Point &, Interval &) + Coord length(Coord) + vector[double] roots(Coord, Dim2) + int winding(Point &) + Point unitTangentAt(Coord, unsigned int) + D2[SBasis] toSBasis() + int degreesOfFreedom() + bint operator==(Curve &) + +cdef class cy_Curve: + cdef Curve* thisptr + +#~ cdef cy_Curve wrap_Curve(Curve & p) +cdef cy_Curve wrap_Curve_p(Curve * p) + + +cdef extern from "2geom/linear.h" namespace "Geom": + cdef cppclass Linear: +#~ Linear(Linear &) + Linear() + Linear(double, double) + Linear(double) + double operator[](int const) + bint isZero(double) + bint isConstant(double) + bint isFinite() + double at0() + double at1() + double valueAt(double) + double operator()(double) + SBasis toSBasis() + OptInterval bounds_exact() + OptInterval bounds_fast() + OptInterval bounds_local(double, double) + double tri() + double hat() + + bint operator==(Linear &, Linear &) + bint operator!=(Linear &, Linear &) + Linear operator*(Linear &, double) + Linear operator+(Linear &, double) + Linear operator+(Linear &, Linear &) + #cython has trouble resolving these + Linear L_neg "operator-" (Linear &) + Linear L_sub_Ld "operator-"(Linear &, double) + Linear L_sub_LL "operator-"(Linear &, Linear &) + Linear operator/(Linear &, double) + + double lerp(double, double, double) + Linear reverse(Linear &) + + +cdef extern from "2geom/sbasis.h" namespace "Geom": + cdef cppclass SBasis: + + SBasis() + SBasis(double) + SBasis(double, double) + SBasis(SBasis &) + SBasis(vector[Linear] &) + SBasis(Linear &) + + size_t size() + Linear operator[](unsigned int) + bint empty() + Linear & back() + void pop_back() + void resize(unsigned int) + void resize(unsigned int, Linear &) + void reserve(unsigned int) + void clear() +#~ void insert(::__gnu_cxx::__normal_iterator<Geom::Linear*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >, ::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >, ::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >) + Linear & at(unsigned int) + bint operator==(SBasis &) + bint operator!=(SBasis &) + + bint isZero(double) + bint isConstant(double) + bint isFinite() + double at0() + double at1() + int degreesOfFreedom() + double valueAt(double) + double operator()(double) + vector[double] valueAndDerivatives(double, unsigned int) + SBasis toSBasis() + double tailError(unsigned int) + SBasis operator()(SBasis &) + void normalize() + void truncate(unsigned int) + + SBasis operator*(SBasis &, SBasis &) + SBasis operator*(double, SBasis &) + SBasis operator*(SBasis &, double) + SBasis operator+(SBasis &, double) + SBasis operator+(SBasis &, SBasis &) + SBasis SB_sub_Sd "operator-" (SBasis &, double) + SBasis SB_sub_SS "operator-" (SBasis &, SBasis &) + SBasis SB_neg "operator-" (SBasis &) + SBasis operator/(SBasis &, double) + + SBasis sin(Linear, int) + SBasis cos(Linear, int) + SBasis sqrt(SBasis &, int) + SBasis reciprocal(Linear &, int) + SBasis shift(Linear &, int) + SBasis shift(SBasis &, int) + SBasis inverse(SBasis, int) + + SBasis portion(SBasis &, Interval) + SBasis portion(SBasis &, double, double) + SBasis compose(SBasis &, SBasis &, unsigned int) + SBasis compose(SBasis &, SBasis &) + SBasis truncate(SBasis &, unsigned int) + + unsigned int valuation(SBasis &, double) + + vector[ vector[double] ] multi_roots(SBasis &, vector[double] &, double, double, double, double) #TODO + vector[Interval] level_set(SBasis &, Interval &, double, double, double) + vector[Interval] level_set(SBasis &, double, double, double, double, double) + vector[double] roots(SBasis &, Interval const) + vector[double] roots(SBasis &) + vector[ vector[Interval] ] level_sets(SBasis &, vector[Interval] &, double, double, double) #TODO + vector[ vector[Interval] ] level_sets(SBasis &, vector[double] &, double, double, double, double) #TODO + + SBasis reverse(SBasis &) + SBasis derivative(SBasis &) + SBasis integral(SBasis &) + SBasis divide(SBasis &, SBasis &, int) + SBasis compose_inverse(SBasis &, SBasis &, unsigned int, double) + SBasis multiply(SBasis &, SBasis &) + SBasis multiply_add(SBasis &, SBasis &, SBasis) + + OptInterval bounds_exact(SBasis &) + OptInterval bounds_local(SBasis &, OptInterval &, int) + OptInterval bounds_fast(SBasis &, int) + +cdef class cy_SBasis: + cdef SBasis* thisptr + +cdef extern from "2geom/sbasis-curve.h" namespace "Geom": + cdef cppclass SBasisCurve: + SBasisCurve(D2[SBasis] &) + SBasisCurve(Curve &) + Curve * duplicate() + Point initialPoint() + Point finalPoint() + bint isDegenerate() + Point pointAt(Coord) + Point operator()(double) + vector[Point] pointAndDerivatives(Coord, unsigned int) + Coord valueAt(Coord, Dim2) + void setInitial(Point &) + void setFinal(Point &) + Rect boundsFast() + Rect boundsExact() + OptRect boundsLocal(OptInterval &, unsigned int) + vector[double] roots(Coord, Dim2) + Coord nearestTime(Point &, Coord, Coord) + vector[double] allNearestTimes(Point &, Coord, Coord) + Coord length(Coord) + Curve * portion(Coord, Coord) + Curve * transformed(Affine &) + Curve * derivative() + D2[SBasis] toSBasis() + int degreesOfFreedom() + + +cdef extern from "2geom/bezier.h" namespace "Geom": + cdef cppclass Bezier: +#~ struct Order +#~ pass + unsigned int order() + unsigned int size() + Bezier() +#~ Bezier(Bezier.Order) + Bezier(Coord) + Bezier(Coord, Coord) + Bezier(Coord, Coord, Coord) + Bezier(Coord, Coord, Coord, Coord) + void resize(unsigned int, Coord) + void clear() + unsigned int degree() + bint isZero(double) + bint isConstant(double) + bint isFinite() + Coord at0() + Coord at1() + Coord valueAt(double) + Coord operator()(double) + SBasis toSBasis() + Coord & operator[](unsigned int) + void setPoint(unsigned int, double) + vector[double] valueAndDerivatives(Coord, unsigned int) + pair[Bezier, Bezier] subdivide(Coord) + vector[double] roots() + vector[double] roots(Interval const) + Bezier forward_difference(unsigned int) + Bezier elevate_degree() + Bezier reduce_degree() + Bezier elevate_to_degree(unsigned int) + Bezier deflate() + Bezier(Coord *, unsigned int) + + Bezier operator*(Bezier &, double) + Bezier operator+(Bezier &, double) + Bezier operator-(Bezier &, double) + Bezier operator/(Bezier &, double) + + Bezier portion(Bezier &, double, double) + OptInterval bounds_local(Bezier &, OptInterval) + Coord casteljau_subidivision(Coord, Coord *, Coord *, Coord *, unsigned int) + void bezier_to_sbasis(SBasis &, Bezier &) + Bezier integral(Bezier &) + Bezier derivative(Bezier &) + OptInterval bounds_exact(Bezier &) + double bernsteinValueAt(double, double *, unsigned int) + vector[Point] bezier_points(D2[Bezier] &) + OptInterval bounds_fast(Bezier &) + Bezier multiply(Bezier &, Bezier &) + Bezier reverse(Bezier &) + +#This is ugly workaround around cython's lack of support for integer template parameters +cdef extern from *: + ctypedef int n_0 "0" + ctypedef int n_1 "1" + +cdef extern from "2geom/bezier-curve.h" namespace "Geom": + cdef cppclass BezierCurve: + unsigned int order() + vector[Point] controlPoints() + void setPoint(unsigned int, Point) + void setPoints(vector[Point] &) + Point operator[](unsigned int) + Point initialPoint() + Point finalPoint() + bint isDegenerate() + void setInitial(Point &) + void setFinal(Point &) + Rect boundsFast() + Rect boundsExact() + OptRect boundsLocal(OptInterval &, unsigned int) + Curve * duplicate() + Curve * portion(Coord, Coord) + Curve * reverse() + Curve * transformed(Affine &) + Curve * derivative() + int degreesOfFreedom() + vector[double] roots(Coord, Dim2) + Coord length(Coord) + Point pointAt(Coord) + vector[Point] pointAndDerivatives(Coord, unsigned int) + Coord valueAt(Coord, Dim2) + D2[SBasis] toSBasis() + #protected: +#~ BezierCurve() +#~ BezierCurve(D2[Bezier] &) +#~ BezierCurve(Bezier &, Bezier &) +#~ BezierCurve(vector[Point] &) + #~ Point middle_point(LineSegment &) + #~ Coord length(LineSegment &) + Coord bezier_length(Point, Point, Point, Point, Coord) + Coord bezier_length(Point, Point, Point, Coord) + Coord bezier_length(vector[Point] &, Coord) + + cdef cppclass LineSegment: + LineSegment() + LineSegment(D2[Bezier] &) + LineSegment(Bezier, Bezier) + LineSegment(vector[Point] &) + LineSegment(Point, Point) + pair[LineSegment, LineSegment] subdivide(Coord) + + Curve * duplicate() + Curve * reverse() + Curve * transformed(Affine &) + Curve * derivative() + + cdef cppclass QuadraticBezier: + QuadraticBezier() + QuadraticBezier(D2[Bezier] &) + QuadraticBezier(Bezier, Bezier) + QuadraticBezier(vector[Point] &) + QuadraticBezier(Point, Point, Point) + pair[QuadraticBezier, QuadraticBezier] subdivide(Coord) + + Curve * duplicate() + Curve * reverse() + Curve * transformed(Affine &) + Curve * derivative() + + + cdef cppclass CubicBezier: + CubicBezier() + CubicBezier(D2[Bezier] &) + CubicBezier(Bezier, Bezier) + CubicBezier(vector[Point] &) + CubicBezier(Point, Point, Point, Point) + pair[CubicBezier, CubicBezier] subdivide(Coord) + + Curve * duplicate() + Curve * reverse() + Curve * transformed(Affine &) + Curve * derivative() + +#~ cdef cppclass BezierCurveN[n_1]: +#~ BezierCurveN () +#~ BezierCurveN(Bezier &, Bezier &) +#~ #BezierCurveN(Point &, Point &) +#~ BezierCurveN(vector[Point] &) +#~ pair[BezierCurveN, BezierCurveN] subdivide(Coord) +#~ +#~ Curve * duplicate() +#~ Curve * reverse() +#~ Curve * transformed(Affine &) +#~ Curve * derivative() + +cdef class cy_BezierCurve: + cdef BezierCurve* thisptr + +cdef class cy_LineSegment(cy_BezierCurve): + pass + +cdef cy_LineSegment wrap_LineSegment(LineSegment p) + +cdef extern from "2geom/bezier-curve.h" namespace "Geom::BezierCurve": + BezierCurve * create(vector[Point] &) + +cdef extern from "2geom/elliptical-arc.h" namespace "Geom": + cdef cppclass EllipticalArc: + EllipticalArc(EllipticalArc &) + EllipticalArc() + EllipticalArc(Point, Coord, Coord, Coord, bint, bint, Point) + Interval angleInterval() + Angle rotationAngle() + Coord ray(Dim2) + Point rays() + bint largeArc() + bint sweep() + LineSegment chord() + void set(Point &, double, double, double, bint, bint, Point &) + void setExtremes(Point &, Point &) + Coord center(Dim2) + Point center() + Coord sweepAngle() + bint containsAngle(Coord) + Point pointAtAngle(Coord) + Coord valueAtAngle(Coord, Dim2) + Affine unitCircleTransform() + bint isSVGCompliant() + pair[EllipticalArc, EllipticalArc] subdivide(Coord) + Point initialPoint() + Point finalPoint() + Curve * duplicate() + void setInitial(Point &) + void setFinal(Point &) + bint isDegenerate() + Rect boundsFast() + Rect boundsExact() + OptRect boundsLocal(OptInterval &, unsigned int) + vector[double] roots(double, Dim2) + double nearestTime(Point &, double, double) + int degreesOfFreedom() + Curve * derivative() + Curve * transformed(Affine &) + vector[Point] pointAndDerivatives(Coord, unsigned int) + D2[SBasis] toSBasis() + double valueAt(Coord, Dim2) + Point pointAt(Coord) + Curve * portion(double, double) + Curve * reverse() + +cdef class cy_EllipticalArc: + cdef EllipticalArc* thisptr +cdef cy_EllipticalArc wrap_EllipticalArc(EllipticalArc p) + +cdef Curve * get_Curve_p(object c) +cdef bint is_Curve(object c) diff --git a/src/cython/_cy_curves.pyx b/src/cython/_cy_curves.pyx new file mode 100644 index 0000000..3584a87 --- /dev/null +++ b/src/cython/_cy_curves.pyx @@ -0,0 +1,1945 @@ +from numbers import Number + +from cython.operator cimport dereference as deref + +from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect +from _cy_rectangle cimport cy_Interval, wrap_Interval + +from _cy_affine cimport cy_Translate, cy_Rotate, cy_Scale +from _cy_affine cimport cy_VShear, cy_HShear, cy_Zoom +from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform + + +cdef class cy_Curve: + + """Class representing generic curve. + + Curve maps unit interval to real plane. All curves should implement + these methods. + + This class corresponds to Curve class in 2geom. It's children in cython + aren't actually derived from it, it would make code more unreadable. + """ + + def __cinit__(self): + """Create new Curve. + + You shouldn't create Curve this way, it usually wraps existing + curves (f. e. in Path). + """ + self.thisptr = <Curve *> new SBasisCurve(D2[SBasis]( SBasis(0), SBasis(0) )) + + def __call__(self, Coord t): + """Get point at time value t.""" + return wrap_Point( deref(self.thisptr)(t) ) + + def initial_point(self): + """Get self(0).""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get self(1).""" + return wrap_Point(self.thisptr.finalPoint()) + def is_degenerate(self): + """Curve is degenerate if it's length is zero.""" + return self.thisptr.isDegenerate() + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def point_and_derivatives(self, Coord t, unsigned int n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n)) + + def set_initial(self, cy_Point v): + """Set initial point of curve.""" + self.thisptr.setInitial(deref( v.thisptr )) + + def set_final(self, cy_Point v): + """Set final point of curve.""" + self.thisptr.setFinal(deref( v.thisptr )) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect(self.thisptr.boundsExact()) + + def bounds_local(self, cy_OptInterval i, unsigned int deg=0): + """Return bounding rectangle to portion of curve.""" + return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg)) + + #TODO rewrite all duplicates to copy.""" + def duplicate(self): + """Duplicate the curve.""" + return wrap_Curve_p( self.thisptr.duplicate() ) + + def transformed(self, m): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(m): + at = get_Affine(m) + return wrap_Curve_p( self.thisptr.transformed(at) ) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_Curve_p( self.thisptr.portion(deref( interval.thisptr )) ) + else: + return wrap_Curve_p( self.thisptr.portion(fr, to) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_Curve_p( self.thisptr.reverse() ) + + def derivative(self): + """Return curve's derivative.""" + return wrap_Curve_p( self.thisptr.derivative() ) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return self.thisptr.nearestTime(deref( p.thisptr ), fr, to) + else: + return self.thisptr.nearestTime(deref( p.thisptr ), deref( interval.thisptr )) + + def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return all values of t that |self(t) - point| is minimized.""" + if interval is None: + return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ), fr, to)) + else: + return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ), + deref( interval.thisptr ))) + + def length(self, Coord tolerance): + """Return length of curve, within give tolerance.""" + return self.thisptr.length(tolerance) + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double(self.thisptr.roots(v, d)) + + def winding(self, cy_Point p): + """Return winding number around specified point.""" + return self.thisptr.winding(deref( p.thisptr )) + + def unit_tangent_at(self, Coord t, unsigned int n): + """Return tangent at self(t). + + Parameter n specifies how many derivatives to take into account.""" + return wrap_Point(self.thisptr.unitTangentAt(t, n)) + + def to_SBasis(self): + """Return tuple of SBasis functions.""" + cdef D2[SBasis] ret = self.thisptr.toSBasis() + return ( wrap_SBasis(ret[0]), wrap_SBasis(ret[1]) ) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() +#~ def operator==(self, cy_Curve c): +#~ return deref( self.thisptr ) == deref( c.thisptr ) + +#~ cdef cy_Curve wrap_Curve(Curve & p): +#~ cdef Curve * retp = <Curve *> new SBasisCurve(D2[SBasis]( SBasis(), SBasis() )) +#~ retp[0] = p +#~ cdef cy_Curve r = cy_Curve.__new__(cy_Curve) +#~ r.thisptr = retp +#~ return r + +cdef cy_Curve wrap_Curve_p(Curve * p): + cdef cy_Curve r = cy_Curve.__new__(cy_Curve) + r.thisptr = p + return r + +cdef class cy_Linear: + """Function mapping linearly between two values. + + Corresponds to Linear class in 2geom. + """ + + cdef Linear* thisptr + + def __cinit__(self, aa = None, b = None): + """Create new Linear from two end values. + + No arguments create zero constant, one value creates constant. + """ + if aa is None: + self.thisptr = new Linear() + elif b is None: + self.thisptr = new Linear(float(aa)) + else: + self.thisptr = new Linear(float(aa), float(b)) + + def __dealloc__(self): + del self.thisptr + + def __call__(self, Coord t): + """Get value at time value t.""" + return deref(self.thisptr)(t) + + def __getitem__(self, i): + """Get end values.""" + return deref( self.thisptr ) [i] + + def __richcmp__(cy_Linear self, cy_Linear other, int op): + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + + + def __neg__(cy_Linear self): + """Negate all values of self.""" + return wrap_Linear( L_neg(deref(self.thisptr)) ) + + + def __add__(cy_Linear self, other): + """Add number or other linear.""" + if isinstance(other, Number): + return wrap_Linear( deref(self.thisptr) + float(other) ) + elif isinstance(other, cy_Linear): + return wrap_Linear( deref(self.thisptr) + deref( (<cy_Linear> other).thisptr ) ) + + def __sub__(cy_Linear self, other): + """Substract number or other linear.""" + if isinstance(other, Number): + return wrap_Linear( L_sub_Ld(deref(self.thisptr), float(other)) ) + elif isinstance(other, cy_Linear): + return wrap_Linear( L_sub_LL(deref(self.thisptr), deref( (<cy_Linear> other).thisptr )) ) + + + def __mul__(cy_Linear self, double b): + """Multiply linear by number.""" + return wrap_Linear(deref( self.thisptr ) * b) + + def __div__(cy_Linear self, double b): + """Divide linear by value.""" + return wrap_Linear(deref( self.thisptr ) / b) + + def is_zero(self, double eps = EPSILON): + """Test whether linear is zero within given tolerance.""" + return self.thisptr.isZero(eps) + + def is_constant(self, double eps = EPSILON): + """Test whether linear is constant within given tolerance.""" + return self.thisptr.isConstant(eps) + + def is_finite(self): + """Test whether linear is finite.""" + return self.thisptr.isFinite() + + def at0(self): + """Equivalent to self(0).""" + return self.thisptr.at0() + + def at1(self): + """Equivalent to self(1).""" + return self.thisptr.at1() + + def value_at(self, double t): + """Equivalent to self(t).""" + return self.thisptr.valueAt(t) + + def to_SBasis(self): + """Convert to SBasis.""" + return wrap_SBasis(self.thisptr.toSBasis()) + + def bounds_exact(self): + """Return exact bounding interval + + This may take a while. + """ + return wrap_OptInterval(self.thisptr.bounds_exact()) + + def bounds_fast(self): + """Return bounding interval + + This method is fast, but does not guarantee to give smallest + interval. + """ + return wrap_OptInterval(self.thisptr.bounds_fast()) + + def bounds_local(self, double u, double v): + """Return bounding interval to the portion of Linear.""" + return wrap_OptInterval(self.thisptr.bounds_local(u, v)) + + def tri(self): + """Return difference between end values.""" + return self.thisptr.tri() + + def hat(self): + """Return value at (0.5).""" + return self.thisptr.hat() + + @classmethod + def sin(cls, cy_Linear bo, int k): + """Return sine of linear.""" + return wrap_SBasis(sin(deref( bo.thisptr ), k)) + + @classmethod + def cos(cls, cy_Linear bo, int k): + """Return cosine of linear.""" + return wrap_SBasis(cos(deref( bo.thisptr ), k)) + + @classmethod + def reciprocal(cls, cy_Linear a, int k): + """Return reciprocical of linear.""" + return wrap_SBasis(reciprocal(deref( a.thisptr ), k)) + + @classmethod + def shift(cls, cy_Linear a, int sh): + """Multiply by x**sh.""" + return wrap_SBasis(shift(deref( a.thisptr ), sh)) + +#leave these in cy2geom napespace? +def cy_lerp(double t, double a, double b): + return lerp(t, a, b) + +cdef cy_Linear wrap_Linear(Linear p): + cdef Linear * retp = new Linear() + retp[0] = p + cdef cy_Linear r = cy_Linear.__new__(cy_Linear) + r.thisptr = retp + return r + +cdef vector[Linear] make_vector_linear(object l): + cdef vector[Linear] ret + for i in l: + ret.push_back( deref( (<cy_Linear> i).thisptr ) ) + return ret + + +cdef class cy_SBasis: + + """Class representing SBasis polynomial. + + Corresponds to SBasis class in 2geom.""" + + def __cinit__(self, a=None, b=None): + """Create new SBasis. + + This constructor only creates linear SBasis, specifying endpoints. + """ + if a is None: + self.thisptr = new SBasis() + elif b is None: + self.thisptr = new SBasis( float(a) ) + else: + self.thisptr = new SBasis( float(a), float(b) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_linear(cls, cy_Linear l): + """Create SBasis from Linear.""" + return wrap_SBasis( SBasis(deref( l.thisptr )) ) + + @classmethod + def from_linears(cls, lst): + """Create SBasis from list of Linears.""" + return wrap_SBasis( SBasis( make_vector_linear(lst) ) ) + + def size(self): + """Return number of linears SBasis consists of.""" + return self.thisptr.size() + + def __call__(self, o): + """Get point at time value t.""" + if isinstance(o, Number): + return deref(self.thisptr)(float(o)) + elif isinstance(self, cy_SBasis): + return wrap_SBasis(deref(self.thisptr)( deref( (<cy_SBasis> o).thisptr ) )) + + def __getitem__(self, unsigned int i): + """Get Linear at i th position.""" + if i>=self.size(): + raise IndexError + else: + return wrap_Linear(deref( self.thisptr ) [i]) + + def __neg__(self): + """Return SBasis with negated values.""" + return wrap_SBasis( SB_neg(deref(self.thisptr)) ) + + #cython doesn't use __rmul__, it switches the arguments instead + def __add__(cy_SBasis self, other): + """Add number or other SBasis to SBasis.""" + if isinstance(other, Number): + return wrap_SBasis( deref(self.thisptr) + float(other) ) + elif isinstance(other, cy_SBasis): + return wrap_SBasis( deref(self.thisptr) + deref( (<cy_SBasis> other).thisptr ) ) + + def __sub__(cy_SBasis self, other): + """Substract number or other SBasis from SBasis.""" + if isinstance(other, Number): + return wrap_SBasis( SB_sub_Sd(deref(self.thisptr), float(other) ) ) + elif isinstance(other, cy_SBasis): + return wrap_SBasis( SB_sub_SS(deref(self.thisptr), deref( (<cy_SBasis> other).thisptr ) ) ) + + def __mul__(self, other): + """Multiply SBasis by number or other SBasis.""" + if isinstance(other, Number): + return wrap_SBasis( deref( (<cy_SBasis> self).thisptr ) * float(other) ) + elif isinstance(other, cy_SBasis): + if isinstance(self, cy_SBasis): + return wrap_SBasis( deref( (<cy_SBasis> self).thisptr ) * deref( (<cy_SBasis> other).thisptr ) ) + elif isinstance(self, Number): + return wrap_SBasis( float(self) * deref( (<cy_SBasis> other).thisptr ) ) + + def __div__(cy_SBasis self, double other): + """Divide SBasis by number.""" + return wrap_SBasis( deref(self.thisptr)/other ) + + + def empty(self): + """Test whether SBasis has no linears.""" + return self.thisptr.empty() + + def back(self): + """Return last linear in SBasis.""" + return wrap_Linear(self.thisptr.back()) + + def pop_back(self): + """Remove last linear in SBasis.""" + self.thisptr.pop_back() + + def resize(self, unsigned int n, cy_Linear l = None): + """Resize SBasis, optionally filling created slots with linear.""" + if l is None: + self.thisptr.resize(n) + else: + self.thisptr.resize(n, deref( l.thisptr )) + +#~ def reserve(self, unsigned int n): +#~ self.thisptr.reserve(n) + + def clear(self): + """Make SBasis empty.""" + self.thisptr.clear() +#~ def insert(self, cy_::__gnu_cxx::__normal_iterator<Geom::Linear*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > before, cy_::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > src_begin, cy_::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > src_end): +#~ self.thisptr.insert(deref( before.thisptr ), deref( src_begin.thisptr ), deref( src_end.thisptr )) + + def at(self, unsigned int i): + """Equivalent to self[i].""" + return wrap_Linear(self.thisptr.at(i)) + + def __richcmp__(cy_SBasis self, cy_SBasis B, int op): + if op == 2: + return deref( self.thisptr ) == deref( B.thisptr ) + elif op == 3: + return deref( self.thisptr ) != deref( B.thisptr ) + + def is_zero(self, double eps = EPSILON): + """Test whether linear is zero within given tolerance.""" + return self.thisptr.isZero(eps) + + def is_constant(self, double eps = EPSILON): + """Test whether linear is constant within given tolerance.""" + return self.thisptr.isConstant(eps) + + def is_finite(self): + """Test whether linear is finite.""" + return self.thisptr.isFinite() + + def at0(self): + """Equivalent to self(0).""" + return self.thisptr.at0() + + def at1(self): + """Equivalent to self(1).""" + return self.thisptr.at1() + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() + + def value_at(self, double t): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t) + + def value_and_derivatives(self, double t, unsigned int n): + """Return value and at least n derivatives at time t.""" + return wrap_vector_double (self.thisptr.valueAndDerivatives(t, n)) + + def to_SBasis(self): + """Just return self.""" + return wrap_SBasis(self.thisptr.toSBasis()) + + def tail_error(self, unsigned int tail): + """Return largest error after truncating linears from tail.""" + return self.thisptr.tailError(tail) + + def normalize(self): + """Remove zero linears at the end.""" + self.thisptr.normalize() + + def truncate(self, unsigned int k): + """Truncate SBasis to have k elements.""" + self.thisptr.truncate(k) + + @classmethod + def sqrt(cls, cy_SBasis a, int k): + """Return square root of SBasis. + + Use k to specify degree of resulting SBasis. + """ + return wrap_SBasis(sqrt(deref( a.thisptr ), k)) + + @classmethod + def inverse(cls, cy_SBasis a, int k): + """Return inverse function to SBasis. + + Passed SBasis must be function [1-1] -> [1-1] bijection. + """ + return wrap_SBasis(inverse(deref( a.thisptr ), k)) + + @classmethod + def valuation(cls, cy_SBasis a, double tol = 0): + """Return the degree of the first non zero coefficient.""" + return valuation(deref( a.thisptr ), tol) + + #call with level_set(SBasis(1, 5), 2, a = 0.2, b = 0.4, tol = 0.02) + @classmethod + def level_set(cls, cy_SBasis f, level, a = 0, b = 1, tol = 1e-5, vtol = 1e-5): + """Return intervals where SBasis is in specified level. + + Specify range and tolerance in other arguments. + """ + if isinstance(level, cy_Interval): + return wrap_vector_interval(level_set(deref( f.thisptr ), deref( (<cy_Interval> level).thisptr ), a, b, tol)) #a, b, tol + else: + return wrap_vector_interval(level_set(deref( f.thisptr ), float(level), vtol, a, b, tol)) #vtol, a, b, tol + + @classmethod + def shift(cls, cy_SBasis a, int sh): + """Multiply by x**sh.""" + return wrap_SBasis(shift(deref( a.thisptr ), sh)) + + @classmethod + def compose(cls, cy_SBasis a, cy_SBasis b, k = None): + """Compose two SBasis. + + Specify order of resulting SBasis by parameter k. + """ + if k is None: + return wrap_SBasis(compose(deref( a.thisptr ), deref( b.thisptr ))) + else: + return wrap_SBasis(compose(deref( a.thisptr ), deref( b.thisptr ), int(k))) + + @classmethod + def roots(cls, cy_SBasis s, cy_Interval inside = None): + """Return time values where self equals 0. + + inside intervals specifies subset of domain. + """ + if inside is None: + return wrap_vector_double(roots(deref( s.thisptr ))) + else: + return wrap_vector_double(roots(deref( s.thisptr ), deref( inside.thisptr ))) + + @classmethod + def multi_roots(cls, cy_SBasis f, levels, double htol = 1e-7, double vtol = 1e-7, double a = 0, double b = 1): + """Return lists of roots for different levels.""" + cdef vector[double] l = make_vector_double(levels) + cdef vector[ vector[double] ] r = multi_roots(deref( f.thisptr ), l, htol, vtol, a, b) + lst = [] + for i in range(r.size()): + lst.append( wrap_vector_double(r[i]) ) + return lst + + @classmethod + def multiply_add(cls, cy_SBasis a, cy_SBasis b, cy_SBasis c): + """Return a*b+c.""" + return wrap_SBasis(multiply_add(deref( a.thisptr ), deref( b.thisptr ), deref( c.thisptr ))) + + @classmethod + def divide(cls, cy_SBasis a, cy_SBasis b, int k): + """Divide two SBasis functions. + + Use k to specify degree of resulting SBasis. + """ + return wrap_SBasis(divide(deref( a.thisptr ), deref( b.thisptr ), k)) + + @classmethod + def compose_inverse(cls, cy_SBasis f, cy_SBasis g, unsigned int order, double tol): + """Compose f with g's inverse. + + Requires g to be bijection g: [0, 1] -> [0, 1] + """ + return wrap_SBasis(compose_inverse(deref( f.thisptr ), deref( g.thisptr ), order, tol)) + + @classmethod + def multiply(cls, cy_SBasis a, cy_SBasis b): + """Multiply two SBasis functions.""" + return wrap_SBasis(multiply(deref( (<cy_SBasis> a).thisptr ), deref( (<cy_SBasis> b).thisptr ))) + + @classmethod + def derivative(cls, cy_SBasis a): + """Return derivative os SBasis.""" + return wrap_SBasis(derivative(deref( (<cy_SBasis> a).thisptr ))) + + @classmethod + def integral(cls, a): + """Return integral of SBasis.""" + return wrap_SBasis(integral(deref( (<cy_SBasis> a).thisptr ))) + + @classmethod + def portion(cls, cy_SBasis a, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of SBasis, specified by endpoints or interval.""" + if interval is None: + return wrap_SBasis( portion( deref( a.thisptr ), fr, to ) ) + else: + return wrap_SBasis( portion( deref( a.thisptr ), deref( interval.thisptr ) ) ) + + @classmethod + def bounds_fast(cls, cy_SBasis a, int order = 0): + """Return bounding interval + + This method is fast, but does not guarantee to give smallest + interval. + """ + return wrap_OptInterval(bounds_fast(deref( a.thisptr ), order)) + + @classmethod + def bounds_exact(cls, cy_SBasis a): + """Return exact bounding interval + + This may take a while. + """ + return wrap_OptInterval(bounds_exact(deref( a.thisptr ))) + + @classmethod + def bounds_local(cls, cy_SBasis a, cy_OptInterval t, int order = 0): + """Return bounding interval to the portion of SBasis.""" + return wrap_OptInterval(bounds_local(deref( a.thisptr ), deref( t.thisptr ), order)) + +#~ def cy_level_sets(cy_SBasis f, vector[Interval] levels, double a, double b, double tol): +#~ return wrap_::std::vector<std::vector<Geom::Interval, std::allocator<Geom::Interval> >,std::allocator<std::vector<Geom::Interval, std::allocator<Geom::Interval> > > >(level_sets(deref( f.thisptr ), deref( levels.thisptr ), a, b, tol)) +#~ def cy_level_sets(cy_SBasis f, vector[vector] levels, double a, double b, double vtol, double tol): +#~ return wrap_::std::vector<std::vector<Geom::Interval, std::allocator<Geom::Interval> >,std::allocator<std::vector<Geom::Interval, std::allocator<Geom::Interval> > > >(level_sets(deref( f.thisptr ), deref( levels.thisptr ), a, b, vtol, tol)) + +def cy_reverse(a): + if isinstance(a, cy_Linear): + return wrap_Linear( reverse(deref( (<cy_Linear> a).thisptr ))) + elif isinstance(a, cy_SBasis): + return wrap_SBasis( reverse(deref( (<cy_SBasis> a).thisptr ))) + elif isinstance(a, cy_Bezier): + return wrap_Bezier( reverse(deref( (<cy_Bezier> a).thisptr ))) + +#already implemented +#~ def cy_truncate(cy_SBasis a, unsigned int terms): +#~ return wrap_SBasis(truncate(deref( a.thisptr ), terms)) + +cdef cy_SBasis wrap_SBasis(SBasis p): + cdef SBasis * retp = new SBasis() + retp[0] = p + cdef cy_SBasis r = cy_SBasis.__new__(cy_SBasis, 0, 0) + r.thisptr = retp + return r + + +cdef class cy_SBasisCurve: + + """Curve mapping two SBasis functions to point (s1(t), s2(t)). + + Corresponds to SBasisCurve in 2geom. + """ + + cdef SBasisCurve* thisptr + +#~ def __init__(self, cy_Curve other): +#~ self.thisptr = self.thisptr.SBasisCurve(deref( other.thisptr )) + + def __cinit__(self, cy_SBasis s1, cy_SBasis s2): + """Create new SBasisCurve from two SBasis functions.""" + self.thisptr = new SBasisCurve( D2[SBasis]( + deref( s1.thisptr ), + deref( s2.thisptr ) ) ) + + def __dealloc__(self): + del self.thisptr + + def __call__(self, double t): + """Get point at time value t.""" + return wrap_Point(deref(self.thisptr)(t)) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.duplicate()) ) + + def initial_point(self): + """Get self(0).""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get self(1).""" + return wrap_Point(self.thisptr.finalPoint()) + + def is_degenerate(self): + """Curve is degenerate if it's length is zero.""" + return self.thisptr.isDegenerate() + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + def point_and_derivatives(self, Coord t, unsigned int n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def set_initial(self, cy_Point v): + """Set initial point of curve.""" + self.thisptr.setInitial(deref( v.thisptr )) + + def set_final(self, cy_Point v): + """Set final point of curve.""" + self.thisptr.setFinal(deref( v.thisptr )) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect(self.thisptr.boundsExact()) + + def bounds_local(self, cy_OptInterval i, unsigned int deg): + """Return bounding rectangle to portion of curve.""" + return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg)) + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double( self.thisptr.roots(v, d) ) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return self.thisptr.nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), deref( interval.thisptr ) ) + + def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return all values of t that |self(t) - point| is minimized.""" + if interval is None: + return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ), fr, to)) + else: + return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), + deref( interval.thisptr ) )) + + def length(self, Coord tolerance = 0.01): + """Return length of curve, within give tolerance.""" + return self.thisptr.length(tolerance) + + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.portion( fr, to ) ) ) + else: + return wrap_SBasisCurve( <SBasisCurve> + deref( (<Curve *> self.thisptr).portion( deref( interval.thisptr ))) ) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.transformed( at ))) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_SBasisCurve( <SBasisCurve> deref( (<Curve *> self.thisptr).reverse() ) ) + + def derivative(self): + """Return curve's derivative.""" + return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.derivative()) ) + + + def winding(self, cy_Point p): + """Return winding number around specified point.""" + return (<Curve *> self.thisptr).winding(deref(p.thisptr)) + + def unit_tangent_at(self, Coord t, int n = 3): + """Return tangent at self(t). + + Parameter n specifies how many derivatives to take into account.""" + return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n)) + + def to_SBasis(self): + """Return tuple containing it's SBasis functions.""" + return wrap_D2_SBasis(self.thisptr.toSBasis()) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() + +cdef object wrap_D2_SBasis(D2[SBasis] p): + return ( wrap_SBasis(p[0]), wrap_SBasis(p[1]) ) + +cdef cy_SBasisCurve wrap_SBasisCurve(SBasisCurve p): + cdef SBasisCurve * retp = new SBasisCurve(D2[SBasis]( SBasis(), SBasis() )) + retp[0] = p + cdef cy_SBasisCurve r = cy_SBasisCurve.__new__(cy_SBasisCurve, cy_SBasis(), cy_SBasis()) + r.thisptr = retp + return r + + +cdef class cy_Bezier: + + """Bezier polynomial. + + Corresponds to Bezier class in 2geom. + """ + + cdef Bezier* thisptr + + def __cinit__(self, *args): + """Create Bezier polynomial specifying it's coeffincients + + This constructor takes up to four coefficients. + """ + if len(args) == 0: + #new Bezier() causes segfault + self.thisptr = new Bezier(0) + elif len(args) == 1: + self.thisptr = new Bezier( float(args[0]) ) + elif len(args) == 2: + self.thisptr = new Bezier( float(args[0]), float(args[1]) ) + elif len(args) == 3: + self.thisptr = new Bezier( float(args[0]), float(args[1]), float(args[2]) ) + elif len(args) == 4: + self.thisptr = new Bezier( float(args[0]), float(args[1]), float(args[2]), float(args[3]) ) + else: + raise ValueError("Passed list has too many points") + + def __dealloc__(self): + del self.thisptr + + def __call__(self, double t): + """Get point at time value t.""" + return deref( self.thisptr ) (t) + + + def __getitem__(self, unsigned int ix): + """Get coefficient by accessing list.""" + if ix >= self.size(): + raise IndexError + return deref( self.thisptr ) [ix] + + def order(self): + """Return order of Bezier.""" + return self.thisptr.order() + + def size(self): + """Return number of coefficients.""" + return self.thisptr.size() + + def __mul__( cy_Bezier self, double v): + """Multiply Bezier by number.""" + return wrap_Bezier(deref( self.thisptr ) * v) + + def __add__( cy_Bezier self, double v): + """Add number to Bezier.""" + return wrap_Bezier(deref( self.thisptr ) + v) + + def __sub__( cy_Bezier self, double v): + """Substract number from Bezier.""" + return wrap_Bezier(deref( self.thisptr ) - v) + + def __div__( cy_Bezier self, double v): + """Divide Bezier number.""" + return wrap_Bezier(deref( self.thisptr ) / v) + + + def resize(self, unsigned int n, Coord v): + """Change order of Bezier.""" + self.thisptr.resize(n, v) + + def clear(self): + """Create empty Bezier.""" + self.thisptr.clear() + + def degree(self): + """Return degree of Bezier polynomial.""" + return self.thisptr.degree() + + def is_zero(self, double eps = EPSILON): + """Test whether linear is zero within given tolerance.""" + return self.thisptr.isZero(eps) + + def is_constant(self, double eps = EPSILON): + """Test whether linear is constant within given tolerance.""" + return self.thisptr.isConstant(eps) + + def is_finite(self): + """Test whether linear is finite.""" + return self.thisptr.isFinite() + + def at0(self): + """Equivalent to self(0).""" + return self.thisptr.at0() + + def at1(self): + """Equivalent to self(1).""" + return self.thisptr.at1() + + def value_at(self, double t): + """Equivalent to self(t).""" + return self.thisptr.valueAt(t) + + def to_SBasis(self): + """Convert to SBasis.""" + return wrap_SBasis(self.thisptr.toSBasis()) + + def set_point(self, unsigned int ix, double val): + """Set self[ix] to val.""" + self.thisptr.setPoint(ix, val) + + def value_and_derivatives(self, Coord t, unsigned int n_derivs): + """Return value and at least n derivatives at time t.""" + return wrap_vector_double(self.thisptr.valueAndDerivatives(t, n_derivs)) + + def subdivide(self, Coord t): + """Get two beziers, from 0 to t and from t to 1.""" + cdef pair[Bezier, Bezier] p = self.thisptr.subdivide(t) + return ( wrap_Bezier(p.first), wrap_Bezier(p.second) ) + + def roots(self, cy_Interval ivl = None): + """Find time values where self(t)[d] == v.""" + if ivl is None: + return wrap_vector_double(self.thisptr.roots()) + else: + return wrap_vector_double(self.thisptr.roots(deref( ivl.thisptr ))) + + def forward_difference(self, unsigned int k): +#TODO: ask someone what this function does. +#~ """Compute forward difference of degree k. +#~ +#~ First forward difference of B is roughly function B'(t) = B(t+h)-B(t) +#~ for fixed step h""" + return wrap_Bezier(self.thisptr.forward_difference(k)) + + def elevate_degree(self): + """Increase degree of Bezier by 1.""" + return wrap_Bezier(self.thisptr.elevate_degree()) + + def reduce_degree(self): + """Decrease degree of Bezier by 1.""" + return wrap_Bezier(self.thisptr.reduce_degree()) + + def elevate_to_degree(self, unsigned int new_degree): + """Increase degree of Bezier to new_degree.""" + return wrap_Bezier(self.thisptr.elevate_to_degree(new_degree)) + + def deflate(self): +#TODO: ask someone what this function does. + #It looks like integral(self)*self.size() + return wrap_Bezier(self.thisptr.deflate()) + + @classmethod + def bezier_points(cls, cy_Bezier a, cy_Bezier b): + """Return control points of BezierCurve consisting of two beziers. + + Passed bezier must have same degree.""" + return wrap_vector_point(bezier_points( D2[Bezier]( deref(a.thisptr), deref(b.thisptr) ) )) + + @classmethod + def multiply(cls, cy_Bezier a, cy_Bezier b): + """Multiply two Bezier functions.""" + return wrap_Bezier(multiply(deref( (<cy_Bezier> a).thisptr ), + deref( (<cy_Bezier> b).thisptr ))) + + @classmethod + def portion(cls, cy_Bezier a, Coord fr=0, Coord to=1, interval=None): + """Return portion of bezier, specified by endpoints or interval.""" + if interval is None: + return wrap_Bezier(portion(deref( a.thisptr ), fr, to)) + else: + return wrap_Bezier(portion(deref( a.thisptr ), float(interval.min()), + float(interval.max()) )) + + @classmethod + def derivative(cls, cy_Bezier a): + """Return derivative of a bezier.""" + return wrap_Bezier(derivative(deref( a.thisptr ))) + + @classmethod + def integral(cls, cy_Bezier a): + """Return derivative of a bezier.""" + return wrap_Bezier(integral(deref( a.thisptr ))) + + @classmethod + def bounds_fast(cls, cy_Bezier a): + """Return bounding interval + + This method is fast, but does not guarantee to give smallest + interval. + """ + return wrap_OptInterval(bounds_fast(deref( a.thisptr ))) + + @classmethod + def bounds_exact(cls, cy_Bezier a): + """Return exact bounding interval + + This may take a while. + """ + return wrap_OptInterval(bounds_exact(deref( a.thisptr ))) + + @classmethod + def bounds_local(cls, cy_Bezier a, cy_OptInterval t): + """Return bounding interval to the portion of bezier.""" + return wrap_OptInterval(bounds_local(deref( a.thisptr ), deref( t.thisptr ))) + +#This is the same as bz.to_SBasis() +#~ def cy_bezier_to_sbasis(cy_SBasis sb, cy_Bezier bz): +#~ bezier_to_sbasis(deref( sb.thisptr ), deref( bz.thisptr )) + +#These are look like internal functions. +#~ def cy_casteljau_subdivision(Coord t, cy_Coord * v, cy_Coord * left, cy_Coord * right, unsigned int order): +#~ return subdivideArr(t, v.thisptr, left.thisptr, right.thisptr, order) +#~ def cy_bernsteinValueAt(double t, cy_double * c_, unsigned int n): +#~ return bernsteinValueAt(t, c_.thisptr, n) + +cdef cy_Bezier wrap_Bezier(Bezier p): + cdef Bezier * retp = new Bezier() + retp[0] = p + cdef cy_Bezier r = cy_Bezier.__new__(cy_Bezier) + r.thisptr = retp + return r + + +cdef class cy_BezierCurve: + + """Bezier curve, consisting of two Bezier functions. + + Corresponds to BezierCurve class in 2geom. + """ + + #This flag is due to this class children + def __cinit__(self, *args, **kwargs): + """Don't use this constructor, use create instead.""" + pass + + def __dealloc__(self): + del self.thisptr + + def __call__(self, Coord t): + """Get point at time value t.""" + return wrap_Point(deref( <Curve *> self.thisptr )(t)) + + def __getitem__(self, unsigned int ix): + """Get control point by list access.""" + return wrap_Point(deref( self.thisptr ) [ix]) + + @classmethod + def create(cls, pts): + """Create new BezierCurve from control points.""" + return wrap_BezierCurve( deref( create( make_vector_point(pts) ) ) ) + + def order(self): + """Get order of curve.""" + return self.thisptr.order() + + def control_points(self): + """Get control points.""" + return wrap_vector_point(self.thisptr.controlPoints()) + + def set_point(self, unsigned int ix, cy_Point v): + """Set control point.""" + self.thisptr.setPoint(ix, deref( v.thisptr )) + + def set_points(self, ps): + """Set control points""" + self.thisptr.setPoints( make_vector_point(ps) ) + + def initial_point(self): + """Get self(0).""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get self(1).""" + return wrap_Point(self.thisptr.finalPoint()) + + def is_degenerate(self): + """Curve is degenerate if it's length is zero.""" + return self.thisptr.isDegenerate() + + def set_initial(self, cy_Point v): + """Set initial point of curve.""" + self.thisptr.setInitial(deref( v.thisptr )) + + def set_final(self, cy_Point v): + """Set final point of curve.""" + self.thisptr.setFinal(deref( v.thisptr )) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect(self.thisptr.boundsExact()) + + def bounds_local(cy_BezierCurve self, cy_OptInterval i, unsigned int deg): + """Return bounding rectangle to portion of curve.""" + return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg)) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), deref( interval.thisptr ) ) + + def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return all values of t that |self(t) - point| is minimized.""" + if interval is None: + return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), fr, to)) + else: + return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), + deref( interval.thisptr ) )) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_BezierCurve( <BezierCurve> deref(<BezierCurve *> + (<Curve *> self.thisptr).portion( fr, to ) + ) ) + else: + return wrap_BezierCurve( <BezierCurve> deref(<BezierCurve *> + (<Curve *> self.thisptr).portion(deref( interval.thisptr )) + ) ) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.duplicate())) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.reverse())) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.transformed( at ))) + + def derivative(self): + """Return curve's derivative.""" + return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.derivative())) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double(self.thisptr.roots(v, d)) + + def length(self, Coord tolerance = 0.01): + """Return length of curve, within give tolerance.""" + return self.thisptr.length(tolerance) + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + def point_and_derivatives(self, Coord t, unsigned int n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def to_SBasis(self): + """Convert self to pair of SBasis functions.""" + return wrap_D2_SBasis(self.thisptr.toSBasis()) + + def winding(self, cy_Point p): + """Return winding number around specified point.""" + return (<Curve *> self.thisptr).winding(deref(p.thisptr)) + + def unit_tangent_at(self, Coord t, int n = 3): + """Return tangent at self(t). + + Parameter n specifies how many derivatives to take into account.""" + return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n)) + +cdef cy_BezierCurve wrap_BezierCurve(BezierCurve p): + cdef vector[Point] points = make_vector_point([cy_Point(), cy_Point()]) + cdef BezierCurve * retp = create(p.controlPoints()) + cdef cy_BezierCurve r = cy_BezierCurve.__new__(cy_BezierCurve, [cy_Point(), cy_Point()]) + r.thisptr = retp + return r + + +cdef class cy_LineSegment(cy_BezierCurve): + + """Bezier curve with fixed order 1. + + This class inherits from BezierCurve. + + Corresponds to LineSegment in 2geom. BezierCurveN is not wrapped. + """ + + def __cinit__(self, cy_Point p0=None, + cy_Point p1=cy_Point()): + """Create new LineSegment from it's endpoints.""" + if p0 is None: + self.thisptr = <BezierCurve *> new LineSegment() + else: + self.thisptr = <BezierCurve *> new LineSegment( deref(p0.thisptr), + deref(p1.thisptr)) + + @classmethod + def from_beziers(cls, cy_Bezier b0, cy_Bezier b1): + """Create LineSegment from two linear beziers.""" + return wrap_LineSegment( LineSegment(deref(b0.thisptr), deref(b1.thisptr)) ) + + def subdivide(self, Coord t): + """Get two LineSegments, from 0 to t and from t to 1.""" + cdef pair[LineSegment, LineSegment] p = (<LineSegment *> self.thisptr).subdivide(t) + return ( wrap_LineSegment(p.first), wrap_LineSegment(p.second) ) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.duplicate())) + + def portion(self, double fr=0, double to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_LineSegment( deref( <LineSegment *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr )) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.reverse())) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.transformed( at ))) + + def derivative(self): + """Return curve's derivative.""" + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.derivative())) + +cdef cy_LineSegment wrap_LineSegment(LineSegment p): + cdef LineSegment * retp = new LineSegment() + retp[0] = p + cdef cy_LineSegment r = cy_LineSegment.__new__(cy_LineSegment) + r.thisptr = <BezierCurve* > retp + return r + + +cdef class cy_QuadraticBezier(cy_BezierCurve): + + """Bezier curve with fixed order 2. + + This class inherits from BezierCurve. + + Corresponds to QuadraticBezier in 2geom. BezierCurveN is not wrapped. + """ + + def __cinit__(self, cy_Point p0=None, + cy_Point p1=cy_Point(), + cy_Point p2=cy_Point()): + """Create new QuadraticBezier from three control points.""" + if p0 is None: + self.thisptr = <BezierCurve *> new QuadraticBezier() + else: + self.thisptr = <BezierCurve *> new QuadraticBezier( deref( p0.thisptr ), + deref( p1.thisptr ), + deref( p2.thisptr ) ) + + @classmethod + def from_beziers(cls, cy_Bezier b0, cy_Bezier b1): + """Create QuadraticBezier from two quadratic bezier functions.""" + return wrap_QuadraticBezier( QuadraticBezier(deref(b0.thisptr), deref(b1.thisptr)) ) + + def subdivide(self, Coord t): + """Get two QuadraticBeziers, from 0 to t and from t to 1.""" + cdef pair[QuadraticBezier, QuadraticBezier] p = (<QuadraticBezier *> self.thisptr).subdivide(t) + return ( wrap_QuadraticBezier(p.first), wrap_QuadraticBezier(p.second) ) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.duplicate())) + + def portion(self, double fr=0, double to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_QuadraticBezier( deref( <QuadraticBezier *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr )) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.reverse())) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.transformed( at ))) + + def derivative(self): + """Return curve's derivative.""" + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.derivative())) + +cdef cy_QuadraticBezier wrap_QuadraticBezier(QuadraticBezier p): + cdef QuadraticBezier * retp = new QuadraticBezier() + retp[0] = p + cdef cy_QuadraticBezier r = cy_QuadraticBezier.__new__(cy_QuadraticBezier) + r.thisptr = <BezierCurve* > retp + return r + +cdef class cy_CubicBezier(cy_BezierCurve): + + """Bezier curve with fixed order 2. + + This class inherits from BezierCurve. + + Corresponds to QuadraticBezier in 2geom. BezierCurveN is not wrapped. + """ + + def __cinit__(self, cy_Point p0=None, + cy_Point p1=cy_Point(), + cy_Point p2=cy_Point(), + cy_Point p3=cy_Point()): + """Create new CubicBezier from four control points.""" + if p0 is None: + self.thisptr = <BezierCurve *> new CubicBezier() + else: + self.thisptr = <BezierCurve *> new CubicBezier( deref( p0.thisptr ), + deref( p1.thisptr ), + deref( p2.thisptr ), + deref( p3.thisptr ) ) + + @classmethod + def from_beziers(cls, cy_Bezier b0, cy_Bezier b1): + """Create CubicBezier from two cubic bezier functions.""" + return wrap_CubicBezier( CubicBezier(deref(b0.thisptr), deref(b1.thisptr)) ) + + def subdivide(self, Coord t): + """Get two CubicBeziers, from 0 to t and from t to 1.""" + cdef pair[CubicBezier, CubicBezier] p = (<CubicBezier *> self.thisptr).subdivide(t) + return ( wrap_CubicBezier(p.first), wrap_CubicBezier(p.second) ) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.duplicate())) + + def portion(self, double fr=0, double to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_CubicBezier( deref( <CubicBezier *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr )) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.reverse())) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.transformed( at ))) + + def derivative(self): + """Return curve's derivative.""" + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.derivative())) + +cdef cy_CubicBezier wrap_CubicBezier(CubicBezier p): + cdef CubicBezier * retp = new CubicBezier() + retp[0] = p + cdef cy_CubicBezier r = cy_CubicBezier.__new__(cy_CubicBezier) + r.thisptr = <BezierCurve* > retp + return r + +#~ cdef class cy_BezierCurveN(cy_BezierCurve): + + +cdef class cy_HLineSegment(cy_LineSegment): + + """Horizontal line segment. + + This class corresponds to HLineSegment in 2geom. + """ + + def __cinit__(self, cy_Point p0=None, cy_Point p1=cy_Point()): + """Create HLineSegment from it's endpoints.""" + if p0 is None: + self.thisptr = <BezierCurve *> new HLineSegment() + else: + self.thisptr = <BezierCurve *> new HLineSegment( deref( p0.thisptr ), deref( p1.thisptr ) ) + + @classmethod + def from_points(cls, cy_Point p0, cy_Point p1): + """Create HLineSegment from it's endpoints.""" + return wrap_HLineSegment( HLineSegment( deref(p0.thisptr), + deref(p1.thisptr)) ) + + @classmethod + def from_point_length(cls, cy_Point p, Coord length): + return wrap_HLineSegment( HLineSegment( deref( p.thisptr ), length ) ) + + def set_initial(self, cy_Point p): + """Set initial point of curve.""" + (<AxisLineSegment_X *> self.thisptr).setInitial( deref(p.thisptr) ) + + def set_final(self, cy_Point p): + """Set final point of curve.""" + (<AxisLineSegment_X *> self.thisptr).setFinal( deref(p.thisptr) ) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect( (<AxisLineSegment_X *> self.thisptr).boundsFast() ) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect( (<AxisLineSegment_X *> self.thisptr).boundsExact() ) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return (<AxisLineSegment_X *> self.thisptr).degreesOfFreedom() + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double( (<AxisLineSegment_X *> self.thisptr).roots(v, d) ) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return (<AxisLineSegment_X *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), + deref( ( interval.thisptr ) ) ) + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point((<AxisLineSegment_X *> self.thisptr).pointAt(t)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return (<AxisLineSegment_X *> self.thisptr).valueAt(t, d) + + def point_and_derivatives(self, Coord t, unsigned n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point( (<AxisLineSegment_X *> self.thisptr).pointAndDerivatives(t, n) ) + + def get_Y(self): + """Get distance of self from y-axis.""" + return (<HLineSegment *> self.thisptr).getY() + + def set_initial_X(self, Coord x): + """Set initial point's X coordinate.""" + (<HLineSegment *> self.thisptr).setInitialX(x) + + def set_final_X(self, Coord x): + """Set final point's X coordinate.""" + (<HLineSegment *> self.thisptr).setFinalX(x) + + def set_Y(self, Coord y): + """Set Y coordinate of points.""" + (<HLineSegment *> self.thisptr).setY(y) + + def subdivide(self, Coord t): + """Return two HLineSegments subdivided at t.""" + cdef pair[HLineSegment, HLineSegment] p = (<HLineSegment *> self.thisptr).subdivide(t) + return (wrap_HLineSegment(p.first), wrap_HLineSegment(p.second)) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.duplicate()) ) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_HLineSegment( deref( <HLineSegment *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_HLineSegment( deref( <HLineSegment *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr ) ) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.reverse()) ) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_LineSegment( deref(<LineSegment *> self.thisptr.transformed( at )) ) + + def derivative(self): + """Return curve's derivative.""" + return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.derivative()) ) + +cdef cy_HLineSegment wrap_HLineSegment(HLineSegment p): + cdef HLineSegment * retp = new HLineSegment() + retp[0] = p + cdef cy_HLineSegment r = cy_HLineSegment.__new__(cy_HLineSegment) + r.thisptr = <BezierCurve *> retp + return r + +cdef class cy_VLineSegment(cy_LineSegment): + + """Vertical line segment. + + This class corresponds to HLineSegment in 2geom. + """ + + def __cinit__(self, cy_Point p0=None, cy_Point p1=cy_Point()): + """Create VLineSegment from it's endpoints.""" + if p0 is None: + self.thisptr = <BezierCurve *> new VLineSegment() + else: + self.thisptr = <BezierCurve *> new VLineSegment( deref( p0.thisptr ), deref( p1.thisptr ) ) + + @classmethod + def from_points(cls, cy_Point p0, cy_Point p1): + """Create VLineSegment from it's endpoints.""" + return wrap_VLineSegment( VLineSegment( deref(p0.thisptr), + deref(p1.thisptr)) ) + + @classmethod + def from_point_length(cls, cy_Point p, Coord length): + return wrap_VLineSegment( VLineSegment( deref( p.thisptr ), length ) ) + + def set_initial(self, cy_Point p): + """Set initial point of curve.""" + (<AxisLineSegment_Y *> self.thisptr).setInitial( deref(p.thisptr) ) + + def set_final(self, cy_Point p): + """Set final point of curve.""" + (<AxisLineSegment_Y *> self.thisptr).setFinal( deref(p.thisptr) ) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect( (<AxisLineSegment_Y *> self.thisptr).boundsFast() ) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect( (<AxisLineSegment_Y *> self.thisptr).boundsExact() ) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return (<AxisLineSegment_Y *> self.thisptr).degreesOfFreedom() + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double( (<AxisLineSegment_Y *> self.thisptr).roots(v, d) ) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return (<AxisLineSegment_Y *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), + deref( ( interval.thisptr ) ) ) + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point((<AxisLineSegment_Y *> self.thisptr).pointAt(t)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return (<AxisLineSegment_Y *> self.thisptr).valueAt(t, d) + + def point_and_derivatives(self, Coord t, unsigned n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point( (<AxisLineSegment_Y *> self.thisptr).pointAndDerivatives(t, n) ) + + def get_X(self): + return (<VLineSegment *> self.thisptr).getX() + + def set_initial_Y(self, Coord y): + (<VLineSegment *> self.thisptr).setInitialY(y) + + def set_final_Y(self, Coord y): + (<VLineSegment *> self.thisptr).setFinalY(y) + + def set_X(self, Coord x): + (<VLineSegment *> self.thisptr).setX(x) + + def subdivide(self, Coord t): + """Return two HLineSegments subdivided at t.""" + cdef pair[VLineSegment, VLineSegment] p = (<VLineSegment *> self.thisptr).subdivide(t) + return (wrap_VLineSegment(p.first), wrap_VLineSegment(p.second)) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.duplicate()) ) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_VLineSegment( deref( <VLineSegment *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_VLineSegment( deref( <VLineSegment *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr ) ) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.reverse()) ) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_LineSegment( deref(<LineSegment *> self.thisptr.transformed( at )) ) + + def derivative(self): + """Return curve's derivative.""" + return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.derivative()) ) + +cdef cy_VLineSegment wrap_VLineSegment(VLineSegment p): + cdef VLineSegment * retp = new VLineSegment() + retp[0] = p + cdef cy_VLineSegment r = cy_VLineSegment.__new__(cy_VLineSegment) + r.thisptr = <BezierCurve *> retp + return r + +cdef class cy_EllipticalArc: + + """Elliptical arc. + + Corresponds to EllipticalArc class in 2geom. + """ + + def __cinit__(self, cy_Point ip = cy_Point(0, 0), + Coord rx = 0, + Coord ry = 0, + Coord rot_angle = 0, + bint large_arc = True, + bint sweep = True, + cy_Point fp = cy_Point(0, 0)): + """Create Elliptical arc from it's major axis and rays.""" + self.thisptr = new EllipticalArc(deref( ip.thisptr ), rx, ry, rot_angle, large_arc, sweep, deref( fp.thisptr )) + + def __dealloc__(self): + del self.thisptr + + def __call__(self, Coord t): + """Get point at time value t.""" + return wrap_Point( deref(<Curve *> self.thisptr)(t) ) + #Curve methods + + def length(self, Coord tolerance = 0.01): + """Return length of curve, within give tolerance.""" + return (<Curve *> self.thisptr).length(tolerance) + + #AngleInterval methods + + def initial_angle(self): + """Get initial Angle of arc.""" + return wrap_Angle((<AngleInterval *> self.thisptr).initialAngle()) + + def final_angle(self): + """Get final Angle of arc.""" + return wrap_Angle((<AngleInterval *> self.thisptr).finalAngle()) + + def angle_at(self, Coord t): + """Get Angle from time value.""" + return wrap_Angle((<AngleInterval *> self.thisptr).angleAt(t)) + + def contains(self, cy_Angle a): + """Test whether arc contains angle.""" + return (<AngleInterval *> self.thisptr).contains(deref( a.thisptr )) + + def extent(self): + """Get extent of angle interval.""" + return (<AngleInterval *> self.thisptr).extent() + + def angle_interval(self): + """Get underlying angle Interval.""" + return wrap_Interval(self.thisptr.angleInterval()) + + def rotation_angle(self): + """Return rotation angle of major axis.""" + return wrap_Angle(self.thisptr.rotationAngle()) + + def ray(self, Dim2 d): + """Access rays with X or Y.""" + return self.thisptr.ray(d) + + def rays(self): + """Get rays as a point.""" + return wrap_Point(self.thisptr.rays()) + + def large_arc(self): + """Check if large arc flag is set.""" + return self.thisptr.largeArc() + + def sweep(self): + """Check if sweep flag is set.""" + return self.thisptr.sweep() + + def chord(self): + """Return chord of arc.""" + return wrap_LineSegment(self.thisptr.chord()) + + def set(self, cy_Point ip, double rx, double ry, double rot_angle, bint large_arc, bint sweep, cy_Point fp): + """Set arc's properties.""" + self.thisptr.set(deref( ip.thisptr ), rx, ry, rot_angle, large_arc, sweep, deref( fp.thisptr )) + + def set_extremes(self, cy_Point ip, cy_Point fp): + """Set endpoints of arc.""" + self.thisptr.setExtremes(deref( ip.thisptr ), deref( fp.thisptr )) + + def center(self, coordinate=None): + """Return center of ellipse, or it's coordinate.""" + if coordinate is None: + return wrap_Point(self.thisptr.center()) + else: + return self.thisptr.center(int(coordinate)) + + def sweep_angle(self): + """Equivalent to self.extent()""" + return self.thisptr.sweepAngle() + + def contains_angle(self, Coord angle): + """Test whether arc contains angle. + + Equivalent to self.contains(Angle(a)) + """ + return self.thisptr.containsAngle(angle) + + def point_at_angle(self, Coord a): + """Get point of arc at specified angle.""" + return wrap_Point(self.thisptr.pointAtAngle(a)) + + def value_at_angle(self, Coord a, Dim2 d): + """Equivalent to self.point_at_angle(a)[d]""" + return self.thisptr.valueAtAngle(a, d) + + def unit_circle_transform(self): + """Get Affine transform needed to transform unit circle to ellipse.""" + return wrap_Affine(self.thisptr.unitCircleTransform()) + + def is_SVG_compliant(self): + """Check whether arc is SVG compliant + + SVG has special specification for degenerated ellipse.""" + return self.thisptr.isSVGCompliant() + + def subdivide(self, Coord t): + """Return two arcs, subdivided at time t.""" + cdef pair[EllipticalArc, EllipticalArc] r = self.thisptr.subdivide(t) + return (wrap_EllipticalArc(r.first), wrap_EllipticalArc(r.second)) + + def initial_point(self): + """Get self(0).""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get self(1).""" + return wrap_Point(self.thisptr.finalPoint()) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.duplicate()) ) + + def set_initial(self, cy_Point p): + """Set initial point of curve.""" + self.thisptr.setInitial(deref( p.thisptr )) + + def set_final(self, cy_Point p): + """Set final point of curve.""" + self.thisptr.setFinal(deref( p.thisptr )) + + def is_degenerate(self): + """Curve is degenerate if its length is zero.""" + return self.thisptr.isDegenerate() + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect(self.thisptr.boundsExact()) + + def bounds_local(self, cy_OptInterval i, unsigned int deg): + """Return bounding rectangle to portion of curve.""" + return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg)) + + def roots(self, double v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double(self.thisptr.roots(v, d)) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return self.thisptr.nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), + deref( interval.thisptr ) ) + + def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return all values of t that |self(t) - point| is minimized.""" + if interval is None: + return wrap_vector_double( (<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), fr, to)) + else: + return wrap_vector_double( (<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), deref( interval.thisptr ) )) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() + + def derivative(self): + """Return curve's derivative.""" + return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.derivative()) ) + + def transformed(self, cy_Affine m): + """Transform curve by affine transform.""" + return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.transformed(deref( m.thisptr ))) ) + + def point_and_derivatives(self, Coord t, unsigned int n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n)) + + def to_SBasis(self): + """Convert to pair of SBasis polynomials.""" + cdef D2[SBasis] r = self.thisptr.toSBasis() + return ( wrap_SBasis(r[0]), wrap_SBasis(r[1]) ) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_EllipticalArc( deref( <EllipticalArc *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_EllipticalArc( deref( <EllipticalArc *> (<Curve *> self.thisptr).portion( deref( interval.thisptr ) ) ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.reverse()) ) + + def winding(self, cy_Point p): + """Return winding number around specified point.""" + return (<Curve *> self.thisptr).winding(deref(p.thisptr)) + + def unit_tangent_at(self, Coord t, int n = 3): + """Return tangent at self(t). + + Parameter n specifies how many derivatives to take into account.""" + return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n)) + +cdef cy_EllipticalArc wrap_EllipticalArc(EllipticalArc p): + cdef EllipticalArc * retp = new EllipticalArc() + retp[0] = p + cdef cy_EllipticalArc r = cy_EllipticalArc.__new__(cy_EllipticalArc) + r.thisptr = retp + return r + +#TODO move somewhere else + +cdef object wrap_vector_interval(vector[Interval] v): + r = [] + cdef unsigned int i + for i in range(v.size()): + r.append( wrap_Interval(v[i])) + return r + + +cdef bint is_Curve(object c): + return any([ + isinstance(c, cy_Curve), + isinstance(c, cy_SBasisCurve), + isinstance(c, cy_BezierCurve), + isinstance(c, cy_EllipticalArc)]) + +cdef Curve * get_Curve_p(object c): + if isinstance(c, cy_Curve): + return (<cy_Curve> c).thisptr + elif isinstance(c, cy_SBasisCurve): + return <Curve *> (<cy_SBasisCurve> c).thisptr + elif isinstance(c, cy_BezierCurve): + return <Curve *> (<cy_BezierCurve> c).thisptr + elif isinstance(c, cy_EllipticalArc): + return <Curve *> (<cy_EllipticalArc> c).thisptr + return NULL + diff --git a/src/cython/_cy_path.pxd b/src/cython/_cy_path.pxd new file mode 100644 index 0000000..2d41581 --- /dev/null +++ b/src/cython/_cy_path.pxd @@ -0,0 +1,124 @@ +from _common_decl cimport * + +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect +from _cy_affine cimport Affine +from _cy_curves cimport Curve, cy_Curve, wrap_Curve_p +from _cy_curves cimport SBasis, cy_SBasis + +from _cy_primitives cimport Point, cy_Point, wrap_Point + + +cdef extern from "2geom/d2.h" namespace "Geom": + cdef cppclass D2[T]: + D2() + D2(T &, T &) + T& operator[](unsigned i) + +#~ ctypedef int BaseIteratorConst "BaseIterator<ConstIterator, Path const>" + +cdef extern from "2geom/path.h" namespace "Geom::PathInternal": + cdef cppclass BaseIterator[C, P]: + Curve & c_item "operator*" () + C & c_next "operator++" () + C & c_next "operator++" (int) + bint operator==(BaseIterator[C, P]) + + + cdef cppclass ConstIterator: + ConstIterator() + cdef cppclass Iterator: + Iterator() + ConstIterator & operator() + + cdef cppclass BaseIteratorConst "Geom::PathInternal::BaseIterator<Geom::PathInternal::ConstIterator, Path const>": +#~ Curve & c_item "operator*" () + ConstIterator & c_next "operator++" () + ConstIterator & c_next "operator++" (int) +#~ bint operator==(BaseIterator[C, P]) + + +cdef extern from "2geom/path.h" namespace "Geom::Path": + cdef enum Stitching: + c_NO_STITCHING "Path::NO_STITCHING" = 0, + c_STITCH_DISCONTINUOUS "Path::STITCH_DISCONTINUOUS" + +cdef extern from "2geom/path.h" namespace "Geom": + cdef cppclass Path: + Path(Path &) + Path(Point) + Path(ConstIterator &, ConstIterator &, bint) + void swap(Path &) + Curve & operator[](unsigned int) +#~ Curve & at_index(unsigned int) +#~ ::boost::shared_ptr<Geom::Curve const> get_ref_at_index(unsigned int) + Curve & front() + Curve & back() + Curve & back_open() + Curve & back_closed() + Curve & back_default() + ConstIterator begin_const "begin" () + ConstIterator end() + Iterator begin() +#~ Iterator end() + ConstIterator end_open() + ConstIterator end_closed() + ConstIterator end_default() + size_t size_open() + size_t size_closed() + size_t size_default() + size_t size() + size_t max_size() + bint empty() + bint closed() + void close(bint) + OptRect boundsFast() + OptRect boundsExact() +#~ Piecewise<Geom::D2<Geom::SBasis> > toPwSb() + bint operator==(Path &) + bint operator!=(Path &) + Path operator*(Affine &) +#~ Path & operator*=(Affine &) + Point pointAt(double) + double valueAt(double, Dim2) + Point operator()(double) except + + vector[double] roots(double, Dim2) + vector[double] allNearestTimes(Point &, double, double) + vector[double] allNearestTimes(Point &) + vector[double] nearestTimePerCurve(Point &) + double nearestTime(Point &, double, double, double *) + double nearestTime(Point &, double *) + void appendPortionTo(Path &, double, double) + Path portion(double, double) + Path portion(Interval) + Path reversed() + void insert(Iterator &, Curve &, Stitching) except + + void insert(Iterator &, ConstIterator &, ConstIterator &, Stitching) + void clear() + void erase(Iterator &, Stitching) + void erase(Iterator &, Iterator &, Stitching) + void erase_last() + void replace(Iterator &, Curve &, Stitching) + void replace(Iterator &, Iterator &, Curve &, Stitching) + void replace(Iterator &, ConstIterator &, ConstIterator &, Stitching) + void replace(Iterator &, Iterator &, ConstIterator &, ConstIterator &, Stitching) + void start(Point) + Point initialPoint() + Point finalPoint() + void setInitial(Point &) + void setFinal(Point &) + void append(Curve &, Stitching) + void append(D2[SBasis] &, Stitching) + void append(Path &, Stitching) + void stitchTo(Point &) + +cdef class cy_Path: +#~ NO_STITCHING = c_NO_STITCHING +#~ STITCH_DISCONTINUOUS = c_STITCH_DISCONTINUOUS + cdef Path* thisptr + cdef ConstIterator _const_iterator_at_index(self, int i) + cdef Iterator _iterator_at_index(self, int i) + +cdef cy_Path wrap_Path(Path p) diff --git a/src/cython/_cy_path.pyx b/src/cython/_cy_path.pyx new file mode 100644 index 0000000..9f3f88e --- /dev/null +++ b/src/cython/_cy_path.pyx @@ -0,0 +1,457 @@ +from cython.operator cimport dereference as deref +from numbers import Number +from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect +from _cy_rectangle cimport cy_Interval, wrap_Interval + +from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform + +from _cy_curves cimport is_Curve, get_Curve_p + + +cdef class cy_Iterator: + cdef Iterator* thisptr + cdef ConstIterator* startptr + cdef ConstIterator* endptr + + def __cinit__(self): + self.thisptr = new Iterator() + + def __dealloc__(self): + del self.thisptr + + def __iter__(self): + return self + + def __next__(self): + if deref(<BaseIterator[ConstIterator, Path] *> self.endptr) == deref(<BaseIterator[ConstIterator, Path] *> self.thisptr): + raise StopIteration + cdef Curve * r = <Curve *> &(<BaseIterator[Iterator, Path] * > self.thisptr).c_item() + (<BaseIterator[Iterator, Path] *> self.thisptr).c_next() + return wrap_Curve_p ( r ) + + + + +cdef cy_Iterator wrap_Iterator(Iterator i, ConstIterator starti, ConstIterator endi): + cdef Iterator * retp = new Iterator() + retp[0] = i + cdef cy_Iterator r = cy_Iterator.__new__(cy_Iterator) + r.thisptr = retp + + cdef ConstIterator * endp = new ConstIterator() + endp[0] = endi + r.endptr = endp + + cdef ConstIterator * startp = new ConstIterator() + startp[0] = starti + r.startptr = startp + + return r + +cdef class cy_Path: + + """Path is a ordered sequence of curves. + + You can iterate this class, accessing curves one at time, or access + them using indices. + + Two constants, NO_STITCHING and STITCH_DISCONTINUOUS are members of + Path namespace, and are used to specify type of stitching, if + necessary. + + Path is either open or closed, but in both cases carries closing + segment, connecting last and first point. + + Corresponds to Path class in 2geom. + """ + + NO_STITCHING = c_NO_STITCHING + STITCH_DISCONTINUOUS = c_STITCH_DISCONTINUOUS + + def __cinit__(self, cy_Point p=cy_Point()): + """Create Path containing only one point.""" + self.thisptr = new Path(deref( p.thisptr )) + + @classmethod + def fromList(cls, l, Stitching stitching=NO_STITCHING, closed=False): + """Create path from list of curves. + + Specify stithing and closed flag in additional arguments. + """ + p = cy_Path() + for curve in l: + p.append_curve(curve, stitching) + p.close(closed) + return p + + @classmethod + def fromPath(cls, cy_Path p, fr=-1, to = 1, bint closed=False): + """Create path copying another's path curves. + + Either copy all curves, or ones from + fr - index of first curve copied + to + to - index of first curve not copied. + + Also takes closed flag. + """ + if fr == -1: + return wrap_Path( Path(p.thisptr.begin_const(), p.thisptr.end_default(), closed) ) + else: + return wrap_Path( Path(p._const_iterator_at_index(fr), p._const_iterator_at_index(to), closed) ) + + def __dealloc__(self): + del self.thisptr + + def __getitem__(self, unsigned int i): + """Get curve with index i.""" + cdef Curve * r = <Curve *> & deref(self.thisptr)[i] + return wrap_Curve_p(r) + + def __call__(self, double t): + """Evaluate path at time t. + + Note: t can be greater than 1 here, it can go to self.size() + """ + return wrap_Point(deref( self.thisptr ) (t) ) + + def __richcmp__(cy_Path self, cy_Path other, int op): + if op == 2: + return deref( self.thisptr ) == deref( other.thisptr ) + elif op == 3: + return deref( self.thisptr ) != deref( other.thisptr ) + + def __mul__(cy_Path self, m): + """Transform path with a transform.""" + cdef Affine at + if is_transform(m): + at = get_Affine(m) + return wrap_Path( deref(self.thisptr) * at ) + + #This is not the fastest way, but it's pretty nice from python's perspective + #Anyway, I would expect that performance hit is minimal, since i is generally really small + cdef ConstIterator _const_iterator_at_index(self, int i): + cdef ConstIterator ci = self.thisptr.begin_const() + cdef ConstIterator * cip = &ci + for ii in range(i): + (<BaseIteratorConst *> cip).c_next() + return ci + + cdef Iterator _iterator_at_index(self, int i): + cdef Iterator ci = self.thisptr.begin() + cdef Iterator * cip = &ci + for ii in range(i): + (<BaseIterator[Iterator, Path] *> cip).c_next() + return ci + + def swap(self, cy_Path other): + """Swap curves with another path.""" + self.thisptr.swap(deref( other.thisptr )) + +#This is the same as __getitem__ +#~ def at_index(self, unsigned int i): +#~ return wrap_Curve(self.thisptr.at_index(i)) + + def front(self): + """Get first curve.""" + #this is AFAIK the shortest way to do this + cdef Curve * r = <Curve *> &self.thisptr.front() + return wrap_Curve_p(r) + + def back(self): + """Same as back_open.""" + cdef Curve * r = <Curve *> &self.thisptr.back() + return wrap_Curve_p(r) + + def back_open(self): + """Get last curve, treating self as open.""" + cdef Curve * r = <Curve *> &self.thisptr.back_open() + return wrap_Curve_p(r) + + def back_closed(self): + """Get last curve, treating self as closed.""" + cdef Curve * r = <Curve *> &self.thisptr.back_closed() + return wrap_Curve_p(r) + + def back_default(self): + """Get last curve.""" + cdef Curve * r = <Curve *> &self.thisptr.back_default() + return wrap_Curve_p(r) + + def curves(self): + """Same as curves_open""" + return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end()) + + def curves_open(self): + """Return all curves as iterable, treating self as open.""" + return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_open()) + + def curves_closed(self): + """Return all curves as iterable, treating self as closed.""" + return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_closed()) + + def curves_default(self): + """Return all curves as iterable.""" + return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_default()) + + def __iter__(self): + return self.curves_default() + + def size_open(self): + """Get number of curves, treating self as open.""" + return self.thisptr.size_open() + + def size_closed(self): + """Get number of curves, treating self as closed.""" + return self.thisptr.size_closed() + + def size_default(self): + """Get number of curves.""" + return self.thisptr.size_default() + + def size(self): + """Same as size_open.""" + return self.thisptr.size() + +#Does the same as size_open, which doesn't correspond with name. +#~ def max_size(self): +#~ return self.thisptr.max_size() + + def empty(self): + """Test whether path contains no curves.""" + return self.thisptr.empty() + + def closed(self): + """Return state of closed flag.""" + return self.thisptr.closed() + + def close(self, bint closed): + """Set closed flag.""" + self.thisptr.close(closed) + + def bounds_fast(self): + """Return fast bounding rectangle for path. + + It's not guaranteed to give the tighest bound. + """ + return wrap_OptRect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Give the tighest bounding rectangle for path.""" + return wrap_OptRect(self.thisptr.boundsExact()) + +#~ def toPwSb(self): +#~ return wrap_Piecewise<Geom::D2<Geom::SBasis> >(self.thisptr.toPwSb()) + + def point_at(self, double t): + """Same as self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + def value_at(self, double t, Dim2 d): + """Same as self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def __call__(self, double t): + """Evaluate path at time t. + + Equivalent to self[floor(t)](t-floor(t)) + """ + return wrap_Point(deref( self.thisptr ) (t) ) + + def roots(self, double v, Dim2 d): + """Find time values where self(t)[d] == v""" + return wrap_vector_double(self.thisptr.roots(v, d)) + + def all_nearest_times(self, cy_Point _point, double fr=-1, double to=1): + """Return all values of t that |self(t) - point| is minimized.""" + if fr == -1: + return wrap_vector_double(self.thisptr.allNearestTimes(deref( _point.thisptr ))) + return wrap_vector_double(self.thisptr.allNearestTimes(deref( _point.thisptr ), fr, to)) + + + def nearest_time_per_curve(self, cy_Point _point): + """Find nearest points, return one time value per each curve.""" + return wrap_vector_double(self.thisptr.nearestTimePerCurve(deref( _point.thisptr ))) + + def nearest_time(self, cy_Point _point, double fr=-1, double to=1):#, cy_double * distance_squared): + """Return such t that |self(t) - point| is minimized.""" + if fr == -1: + return self.thisptr.nearestTime(deref( _point.thisptr ), NULL) + return self.thisptr.nearestTime(deref( _point.thisptr ), fr, to, NULL) + + def nearest_time_and_dist_sq(self, cy_Point _point, double fr=-1, double to=1): + """Return such t that |self(t) - point| is minimized and square of that distance.""" + cdef double t, dist + if fr == -1: + t = self.thisptr.nearestTime(deref( _point.thisptr ), &dist ) + else: + t = self.thisptr.nearestTime(deref( _point.thisptr ), fr, to, &dist) + return (t, dist) + + def append_portion_to(self, cy_Path p, double f, double t): + """Append portion of path to self.""" + self.thisptr.appendPortionTo(deref( p.thisptr ), f, t) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve between two time values. + + Alternatively use argument interval. + """ + if interval is None: + return wrap_Path(self.thisptr.portion(fr, to)) + else: + return wrap_Path(self.thisptr.portion(deref( interval.thisptr ))) + + def reversed(self): + """Return reversed curve.""" + return wrap_Path(self.thisptr.reversed()) + + def insert(self, int pos, curve, Stitching stitching=NO_STITCHING): + """Insert curve into position pos. + + Args: + pos: Position of inserted curve. + curve: Curve to insert. + stitching=NO_STITCHING + """ + cdef Curve * cptr = get_Curve_p(curve) + if cptr: + self.thisptr.insert( self._iterator_at_index(pos), deref( cptr ), stitching ) + else: + raise TypeError("passed curve is not C++ Curve") + + def insert_slice(self, int pos, cy_Path p, int first, int last, Stitching stitching=NO_STITCHING): + """Insert curves to position pos. + + Args: + pos: Position of inserted slice. + p: Path from which slice is inserted. + first: First inserted curve position (in p). + last: Fist not inserted curve position (in p). + stiching=NO_STITCHING + """ + self.thisptr.insert(self._iterator_at_index(pos), p._const_iterator_at_index(first), p._const_iterator_at_index(last), stitching) + + def clear(self): + """Clear all curves.""" + self.thisptr.clear() + + def erase(self, int pos, Stitching stitching=NO_STITCHING): + """Erase curve at position pos. + + Args: + pos: Position of erased curve. + stitching=NO_STITCHING + """ + self.thisptr.erase(self._iterator_at_index(pos), stitching) + + def erase_slice(self, int start, int end, Stitching stitching=NO_STITCHING): + """Erase curves with indices [start, end). + + Args: + start, end: Curves with indices start...end-1 are erased + stitching=NO_STITCHING + """ + self.thisptr.erase(self._iterator_at_index(start), self._iterator_at_index(end), stitching) + + def erase_last(self): + """Erase last curve.""" + self.thisptr.erase_last() + + def replace(self, int replaced, curve, Stitching stitching=NO_STITCHING): + """Replace curve at position replaced with another curve. + + Args: + replaced: Position of replaced curve. + curve: New curve. + stitching=NO_STITCHING + """ + cdef Curve * cptr = get_Curve_p(curve) + if cptr: + self.thisptr.replace(self._iterator_at_index(replaced), deref( cptr ), stitching) + else: + raise TypeError("passed curve is not C++ Curve") + + def replace_slice(self, int first_replaced, int last_replaced, curve, Stitching stitching=NO_STITCHING): + """Replace slice of curves by new curve. + + Args: + first_replaced, last_replace: Curves with indices + first_replaced ... last_replaced + curve: New curve. + stitching=NO_STITCHING + """ + cdef Curve * cptr = get_Curve_p(curve) + if cptr: + self.thisptr.replace(self._iterator_at_index(first_replaced), self._iterator_at_index(last_replaced), deref( cptr ), stitching) + else: + raise TypeError("passed curve is not C++ Curve") + +#How to implement this nicely? +#~ def replaceByList(self, int replaced, cy_ConstIterator first, cy_ConstIterator last, Stitching stitching): +#~ self.thisptr.replace(deref( replaced.thisptr ), deref( first.thisptr ), deref( last.thisptr ), stitching) +#~ def replace(self, cy_Iterator first_replaced, cy_Iterator last_replaced, cy_ConstIterator first, cy_ConstIterator last, Stitching stitching): +#~ self.thisptr.replace(deref( first_replaced.thisptr ), deref( last_replaced.thisptr ), deref( first.thisptr ), deref( last.thisptr ), stitching) + + def start(self, cy_Point p): + """Erase all curves and set first point.""" + self.thisptr.start(deref( p.thisptr )) + + def initial_point(self): + """Get initial point.""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get final point.""" + return wrap_Point(self.thisptr.finalPoint()) + + def set_initial(self, cy_Point p): + """Set initial point.""" + self.thisptr.setInitial(deref( p.thisptr )) + + def set_final(self, cy_Point p): + """Set final point.""" + self.thisptr.setFinal(deref( p.thisptr )) + + def append_curve(self, curve, Stitching stitching=NO_STITCHING): + """Append curve to path. + + Args: + curve: Curve to append. + stitching=NO_STITCHING + """ + cdef Curve * cptr = get_Curve_p(curve) + if cptr: + self.thisptr.append( deref( cptr ), stitching) + else: + raise TypeError("passed curve is not C++ Curve") + + def append_SBasis(self, cy_SBasis x, cy_SBasis y, Stitching stitching=NO_STITCHING): + """Append two SBasis functions to path. + + Args: + x, y: SBasis functions to append. + stitching=NO_STITCHING + """ + cdef D2[SBasis] sb = D2[SBasis]( deref(x.thisptr), deref(y.thisptr) ) + self.thisptr.append(sb, stitching) + + def append_path(self, cy_Path other, Stitching stitching=NO_STITCHING): + """Append another path to path. + + Args: + other: Path to append. + stitching=NO_STITCHING + """ + self.thisptr.append(deref( other.thisptr ), stitching) + + def stitch_to(self, cy_Point p): + """Set last point to p, creating stitching segment to it.""" + self.thisptr.stitchTo(deref( p.thisptr )) + +cdef cy_Path wrap_Path(Path p): + cdef Path * retp = new Path(Point()) + retp[0] = p + cdef cy_Path r = cy_Path.__new__(cy_Path) + r.thisptr = retp + return r diff --git a/src/cython/_cy_primitives.pxd b/src/cython/_cy_primitives.pxd new file mode 100644 index 0000000..87df1c6 --- /dev/null +++ b/src/cython/_cy_primitives.pxd @@ -0,0 +1,237 @@ +from _common_decl cimport * + + +cdef extern from "2geom/affine.h" namespace "Geom": + cdef cppclass Affine: + pass + cdef cppclass Translate + cdef cppclass Scale + cdef cppclass Rotate + cdef cppclass VShear + cdef cppclass HShear + cdef cppclass Zoom + + +cdef extern from "2geom/angle.h" namespace "Geom": + cdef cppclass Angle: + Angle() + Angle(Coord) + Angle(Point) + Coord radians() + Coord radians0() + Coord degrees() + Coord degreesClock() + + Coord operator() + Angle &operator+(Angle &) + Angle &operator-(Angle &) + bint operator==(Angle &) + bint operator!=(Angle &) + + Coord rad_from_deg(Coord) + Coord deg_from_rad(Coord) + +cdef extern from "2geom/angle.h" namespace "Geom::Angle": + Angle from_radians(Coord d) + Angle from_degrees(Coord d) + Angle from_degrees_clock(Coord d) + +cdef class cy_Angle: + cdef Angle* thisptr + +cdef cy_Angle wrap_Angle(Angle) + + +cdef extern from "2geom/angle.h" namespace "Geom": + cdef cppclass AngleInterval: + AngleInterval(AngleInterval &) + AngleInterval(Angle &, Angle &, bint) + AngleInterval(double, double, bint) + Angle & initialAngle() + Angle & finalAngle() + bint isDegenerate() + Angle angleAt(Coord) + Angle operator()(Coord) + bint contains(Angle &) + Coord extent() + + +cdef extern from "2geom/point.h" namespace "Geom": + cdef cppclass Point: + Point() + Point(Coord, Coord) + Coord length() + Point ccw() + Point cw() + Coord x() + Coord y() + IntPoint round() + IntPoint floor() + IntPoint ceil() + + bint isFinite() + bint isZero() + bint isNormalized(Coord) + + bint operator==(Point &) + bint operator!=(Point &) + bint operator<(Point &) + bint operator>(Point &) + bint operator<=(Point &) + bint operator>=(Point &) + + Coord &operator[](int) + Point operator-() + Point &operator+(Point &) + Point &operator-(Point &) + Point &operator*(Coord) + Point &operator/(Coord) + + Point &operator*(Affine &) + Point &operator*(Translate &) + Point &operator*(Scale &) + Point &operator*(Rotate &) + Point &operator*(HShear &) + Point &operator*(VShear &) + Point &operator*(Zoom &) + + Coord L2(Point &) + Coord L2sq(Point &) + + bint are_near(Point &, Point &, double) + + Point middle_point(Point &, Point &) + Point rot90(Point &) + Point lerp(double, Point &, Point &) + + Coord dot(Point &, Point &) + Coord cross(Point &, Point &) + Coord distance (Point &, Point &) + Coord distanceSq (Point &, Point &) + + Point unit_vector(Point &) + Coord L1(Point &) + Coord LInfty(Point &) + bint is_zero(Point &) + bint is_unit_vector(Point &) + double atan2(Point &) + double angle_between(Point &, Point &) + Point abs(Point &) + Point constrain_angle(Point &, Point &, unsigned int, Point &) + +cdef extern from "2geom/point.h" namespace "Geom::Point": + Point polar(Coord angle, Coord radius) + +cdef class cy_Point: + cdef Point* thisptr + +cdef cy_Point wrap_Point(Point p) +cdef object wrap_vector_point(vector[Point] v) +cdef vector[Point] make_vector_point(object l) + + +cdef extern from "2geom/int-point.h" namespace "Geom": + cdef cppclass IntPoint: + IntPoint() + IntPoint(IntCoord, IntCoord) + IntPoint(IntPoint &) + IntCoord operator[](unsigned int) + IntCoord x() + IntCoord y() + #why doesn't IntPoint have unary -? + IntPoint & operator+(IntPoint &) + IntPoint & operator-(IntPoint &) + bint operator==(IntPoint &) + bint operator!=(IntPoint &) + bint operator<=(IntPoint &) + bint operator>=(IntPoint &) + bint operator>(IntPoint &) + bint operator<(IntPoint &) + +cdef class cy_IntPoint: + cdef IntPoint* thisptr + +cdef cy_IntPoint wrap_IntPoint(IntPoint p) + + +cdef extern from "2geom/curve.h" namespace "Geom": + cdef cppclass Curve + +cdef extern from "2geom/bezier.h" namespace "Geom": + cdef cppclass LineSegment + +cdef extern from "2geom/line.h" namespace "Geom": + cdef cppclass Line: + Line() + Line(Point &, Coord) + Line(Point &, Point &) + + Line(LineSegment &) + Line(Ray &) + Line* duplicate() + + Point origin() + Point versor() + Coord angle() + void setOrigin(Point &) + void setVersor(Point &) + void setAngle(Coord) + void setPoints(Point &, Point &) + void setCoefficients(double, double, double) + bint isDegenerate() + Point pointAt(Coord) + Coord valueAt(Coord, Dim2) + Coord timeAt(Point &) + Coord timeAtProjection(Point &) + Coord nearestTime(Point &) + vector[Coord] roots(Coord, Dim2) + Line reverse() + Curve* portion(Coord, Coord) + LineSegment segment(Coord, Coord) + Ray ray(Coord) + Line derivative() + Line transformed(Affine &) + Point normal() + Point normalAndDist(double &) + + double distance(Point &, Line &) + bint are_near(Point &, Line &, double) + bint are_parallel(Line &, Line &, double) + bint are_same(Line &, Line &, double) + bint are_orthogonal(Line &, Line &, double) + bint are_collinear(Point &, Point &, Point &, double) + + double angle_between(Line &, Line &) + double distance(Point &, LineSegment &) + +cdef extern from "2geom/line.h" namespace "Geom::Line": + Line from_origin_and_versor(Point, Point) + Line from_normal_distance(Point, double) + + +cdef extern from "2geom/ray.h" namespace "Geom": + cdef cppclass Ray: + Ray() + Ray(Point &, Coord) + Ray(Point&, Point &) + Point origin() + Point versor() + void setOrigin(Point &) + void setVersor(Point &) + void setAngle(Coord) + Coord angle() + void setPoints(Point &, Point &) + bint isDegenerate() + Point pointAt(Coord) + Coord valueAt(Coord, Dim2) + vector[Coord] roots(Coord, Dim2) + Coord nearestTime(Point &) + Ray reverse() + Curve *portion(Coord, Coord) + LineSegment segment(Coord, Coord) + Ray transformed(Affine &) + double distance(Point &, Ray &) + bint are_near(Point &, Ray &, double) + bint are_same(Ray&, Ray &, double) + double angle_between(Ray &, Ray &, bint) + Ray make_angle_bisector_ray(Ray &, Ray&) diff --git a/src/cython/_cy_primitives.pyx b/src/cython/_cy_primitives.pyx new file mode 100644 index 0000000..62f7f41 --- /dev/null +++ b/src/cython/_cy_primitives.pyx @@ -0,0 +1,846 @@ +from numbers import Number + +from _common_decl cimport * +from cython.operator cimport dereference as deref + +from _cy_affine cimport cy_Translate, cy_Rotate, cy_Scale +from _cy_affine cimport cy_VShear, cy_HShear, cy_Zoom +from _cy_affine cimport cy_Affine, get_Affine, is_transform + +from _cy_curves cimport cy_Curve, wrap_Curve_p +from _cy_curves cimport cy_LineSegment, wrap_LineSegment + +cdef class cy_Angle: + + """Class representig angle. + + Angles can be tested for equality, but they are not ordered. + + Corresponds to Angle class in 2geom. Most members are direct + calls to Angle methods, otherwise C++ call is specified. + """ + + def __cinit__(self, double x): + """Create new angle from value in radians.""" + self.thisptr = new Angle(x) + + def __repr__(self): + """repr(self)""" + return "Angle({0:2.f})".format(self.radians()) + + def __str__(self): + """str(self)""" + return "{0:.2f} radians".format(self.radians()) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_radians(cls, rad): + """Construnct angle from radians.""" + return wrap_Angle(from_radians(rad)) + + @classmethod + def from_degrees(cls, d): + """Construnct angle from degrees.""" + return wrap_Angle(from_degrees(d)) + + @classmethod + def from_degrees_clock(cls, d): + """Construnct angle from degrees in clock convention.""" + return wrap_Angle(from_degrees_clock(d)) + + @classmethod + def from_Point(cls, cy_Point p): + """Construct angle from Point. Calls Angle(Point) in 2geom.""" + cdef Point * pp = p.thisptr + return wrap_Angle( Angle( deref(p.thisptr) )) + + def __float__(self): + """float(self)""" + return <Coord> deref(self.thisptr) + + def __add__(cy_Angle self, cy_Angle other): + """alpha + beta""" + return wrap_Angle(deref(other.thisptr) + deref(self.thisptr)) + + def __sub__(cy_Angle self, cy_Angle other): + """alpha - beta""" + return wrap_Angle(deref(other.thisptr) - deref(self.thisptr)) + + def __richcmp__(cy_Angle self, cy_Angle other, int op): + """Test equality of angles. Note: angles are not ordered.""" + if op == 2: + return deref(other.thisptr) == deref(self.thisptr) + elif op == 3: + return deref(other.thisptr) != deref(self.thisptr) + return NotImplemented + + def radians(self): + """Return the angle in radians.""" + return self.thisptr.radians() + + def radians0(self): + """Return the angle in positive radians.""" + return self.thisptr.radians0() + + def degrees(self): + """Return the angle in degrees.""" + return self.thisptr.degrees() + + def degrees_clock(self): + """Return the angle in clock convention. Calls degreesClock() in 2geom.""" + return self.thisptr.degreesClock() + + @classmethod + def rad_from_deg(cls, deg): + """Convert degrees to radians.""" + return rad_from_deg(deg) + + @classmethod + def deg_from_rad(cls, rad): + """Convert radians to degrees.""" + return deg_from_rad(rad) + +cdef cy_Angle wrap_Angle(Angle p): + cdef Angle * retp = new Angle() + retp[0] = p + cdef cy_Angle r = cy_Angle.__new__(cy_Angle, 0) + r.thisptr = retp + return r + + +cdef class cy_AngleInterval: + + """ Class representing interval of angles. + + Corresponds to AngleInterval class in 2geom. Most members are direct + calls to AngleInterval methods, otherwise C++ call is specified. + """ + + cdef AngleInterval* thisptr + + def __cinit__(self, start, end, bint cw=False): + """Create AngleInterval from starting and ending value. + + Optional argument cw specifies direction - counter-clockwise + is default. + """ + self.thisptr = new AngleInterval(float(start), float(end), cw) + + def __call__(self, Coord t): + """A(t), maps unit interval to Angle.""" + return wrap_Angle(deref( self.thisptr ) (t)) + + def initial_angle(self): + """Return initial angle as Angle instance.""" + return wrap_Angle(self.thisptr.initialAngle()) + + def final_angle(self): + """Return final angle as Angle instance.""" + return wrap_Angle(self.thisptr.finalAngle()) + + def is_degenerate(self): + """Test for empty interval.""" + return self.thisptr.isDegenerate() + + def angle_at(self, Coord t): + """A.angle_at(t) <==> A(t)""" + return wrap_Angle(self.thisptr.angleAt(t)) + + def contains(self, cy_Angle a): + """Test whether interval contains Angle.""" + return self.thisptr.contains(deref( a.thisptr )) + + def extent(self): + """Calculate interval's extent.""" + return self.thisptr.extent() + +cdef cy_AngleInterval wrap_AngleInterval(AngleInterval p): + cdef AngleInterval * retp = new AngleInterval(0, 0, 0) + retp[0] = p + cdef cy_AngleInterval r = cy_AngleInterval.__new__(cy_AngleInterval) + r.thisptr = retp + return r + + +cdef class cy_Point: + + """Represents point or vector in 2D plane. + + Points are ordered lexicographically, with y coordinate being + more significant. + + Corresponds to Point class in 2geom. Most members are direct + calls to Point methods, otherwise C++ call is specified. + """ + + def __cinit__(self, double x=0.0, double y=0.0): + """Create Point from it's cartesian coordinates.""" + self.thisptr = new Point(x, y) + + def __repr__(self): + """repr(self)""" + return "Point ({0:.3f}, {1:.3f})".format(self[0], self[1]) + + def __str__(self): + """str(self)""" + return "[{0:.3f}, {1:.3f}]".format(self[0], self[1]) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def polar(cls, Coord angle, Coord radius = 1.0): + """Create Point from polar coordinates.""" + return wrap_Point(polar(angle, radius)) + + def length(self): + """Return distance from origin or length of vector.""" + return self.thisptr.length() + + def ccw(self): + """Return point rotated counter-clockwise.""" + return wrap_Point(self.thisptr.ccw()) + + def cw(self): + """Return point rotated clockwise.""" + return wrap_Point(self.thisptr.cw()) + + def __getitem__(self, key): + """Access coordinates of point.""" + return deref(self.thisptr)[key] + + @property + def x(self): + """First coordinate of point.""" + return self.thisptr.x() + + @property + def y(self): + """Second coordinate of point.""" + return self.thisptr.y() + + def round(self): + """Create IntPoint rounding coordinates.""" + return wrap_IntPoint(self.thisptr.round()) + + def floor(self): + """Create IntPoint flooring coordinates.""" + return wrap_IntPoint(self.thisptr.floor()) + + def ceil(self): + """Create IntPoint ceiling coordinates.""" + return wrap_IntPoint(self.thisptr.ceil()) + + def __neg__(self): + """-P""" + return(wrap_Point(-deref(self.thisptr))) + + def __abs__(self): + """abs(P)""" + return self.length() + + def __add__(cy_Point self, cy_Point other): + """P + Q""" + return wrap_Point(deref(self.thisptr) + deref(other.thisptr)) + + def __sub__(cy_Point self, cy_Point other): + """P - Q""" + return wrap_Point(deref(self.thisptr) - deref(other.thisptr)) + + #TODO exceptions + def __mul__(cy_Point self, s): + """Multiply point by number or Affine transform.""" + if isinstance(s, Number): + return wrap_Point(deref(self.thisptr)* (<Coord> float(s))) + elif isinstance(s, cy_Affine): + return wrap_Point( deref(self.thisptr) * <Affine &> deref( (<cy_Affine> s).thisptr ) ) + elif isinstance(s, cy_Translate): + return wrap_Point( deref(self.thisptr) * <Translate &> deref( (<cy_Translate> s).thisptr ) ) + elif isinstance(s, cy_Scale): + return wrap_Point( deref(self.thisptr) * <Scale &> deref( (<cy_Scale> s).thisptr ) ) + elif isinstance(s, cy_Rotate): + return wrap_Point( deref(self.thisptr) * <Rotate &> deref( (<cy_Rotate> s).thisptr ) ) + elif isinstance(s, cy_HShear): + return wrap_Point( deref(self.thisptr) * <HShear &> deref( (<cy_HShear> s).thisptr ) ) + elif isinstance(s, cy_VShear): + return wrap_Point( deref(self.thisptr) * <VShear &> deref( (<cy_VShear> s).thisptr ) ) + elif isinstance(s, cy_Zoom): + return wrap_Point( deref(self.thisptr) * <Zoom &> deref( (<cy_Zoom> s).thisptr ) ) + return NotImplemented + + def __div__(cy_Point self, Coord s): + """P / s""" + return wrap_Point(deref(self.thisptr)/s) + + def __richcmp__(cy_Point self, cy_Point other, int op): + if op == 0: + return deref(self.thisptr) < deref(other.thisptr) + if op == 1: + return deref(self.thisptr) <= deref(other.thisptr) + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + if op == 3: + return deref(self.thisptr) != deref(other.thisptr) + if op == 4: + return deref(self.thisptr) > deref(other.thisptr) + if op == 5: + return deref(self.thisptr) >= deref(other.thisptr) + + def isFinite(self): + """Test whether point is finite.""" + return self.thisptr.isFinite() + + def isZero(self): + """Test whether point is origin""" + return self.thisptr.isZero() + + def isNormalized(self, eps=EPSILON): + """Test whether point's norm is close to 1.""" + return self.thisptr.isNormalized(eps) + + @classmethod + def L2(cls, cy_Point p): + """Compute L2 (Euclidean) norm of point. + + L2(P) = sqrt( P.x**2 + P.y**2 ) + """ + return L2(deref(p.thisptr)) + + @classmethod + def L2sq(cls, cy_Point p): + """Compute square of L2 (Euclidean) norm.""" + return L2sq(deref(p.thisptr)) + + @classmethod + def are_near(cls, cy_Point a, cy_Point b, double eps=EPSILON): + """Test if two points are close.""" + return are_near(deref(a.thisptr), deref(b.thisptr), eps) + + @classmethod + def middle_point(cls, cy_Point a, cy_Point b): + """Return point between two points.""" + return wrap_Point(middle_point(deref(a.thisptr), deref(b.thisptr))) + + @classmethod + def rot90(cls, cy_Point a): + """Rotate point by 90 degrees.""" + return wrap_Point(rot90(deref(a.thisptr))) + + @classmethod + def lerp(cls, double t, cy_Point a, cy_Point b): + """Linearly interpolate between too points.""" + return wrap_Point(lerp(t, deref(a.thisptr), deref(b.thisptr))) + + @classmethod + def dot(cls, cy_Point a, cy_Point b): + """Calculate dot product of two points.""" + return dot(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def cross(cls, cy_Point a, cy_Point b): + """Calculate (z-coordinate of) cross product of two points.""" + return cross(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def distance(cls, cy_Point a, cy_Point b): + """Compute distance between two points.""" + return distance(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def distanceSq(cls, cy_Point a, cy_Point b): + """Compute square of distance between two points.""" + return distanceSq(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def unit_vector(cls, cy_Point p): + """Normalise point.""" + return wrap_Point(unit_vector(deref(p.thisptr))) + + @classmethod + def L1(cls, cy_Point p): + """Compute L1 (Manhattan) norm of a point. + + L1(P) = |P.x| + |P.y| + """ + return L1(deref(p.thisptr)) + + @classmethod + def LInfty(cls, cy_Point p): + """Compute Infinity norm of a point. + + LInfty(P) = max(|P.x|, |P.y|) + """ + return LInfty(deref(p.thisptr)) + + @classmethod + def is_zero(cls, cy_Point p): + """Test whether point is origin.""" + return is_zero(deref(p.thisptr)) + + @classmethod + def is_unit_vector(cls, cy_Point p): + """Test whether point's length equal 1.""" + return is_unit_vector(deref(p.thisptr)) + + @classmethod + def atan2(cls, cy_Point p): + """Return angle between point and x-axis.""" + return atan2(deref(p.thisptr)) + + @classmethod + def angle_between(cls, cy_Point a, cy_Point b): + """Return angle between two point.""" + return angle_between(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def abs(cls, cy_Point p): + """Return length of a point.""" + return wrap_Point(abs(deref(p.thisptr))) + + @classmethod + def constrain_angle(cls, cy_Point a, cy_Point b, unsigned int n, cy_Point direction): + """Rotate B around A to have specified angle wrt. direction.""" + return wrap_Point(constrain_angle(deref(a.thisptr), deref(b.thisptr), n, deref(direction.thisptr))) + +cdef cy_Point wrap_Point(Point p): + cdef Point * retp = new Point() + retp[0] = p + cdef cy_Point r = cy_Point.__new__(cy_Point) + r.thisptr = retp + return r + +cdef object wrap_vector_point(vector[Point] v): + r = [] + cdef unsigned int i + for i in range(v.size()): + r.append( wrap_Point(v[i]) ) + return r + +cdef vector[Point] make_vector_point(object l): + cdef vector[Point] ret + for i in l: + ret.push_back( deref( (<cy_Point> i).thisptr ) ) + return ret + + +cdef class cy_IntPoint: + + """Represents point with integer coordinates + + IntPoints are ordered lexicographically, with y coordinate being + more significant. + + Corresponds to IntPoint class in 2geom. Most members are direct + calls to IntPoint methods, otherwise C++ call is specified. + """ + + def __init__(self, IntCoord x = 0, IntCoord y = 0): + """Create new IntPoint from it's cartesian coordinates.""" + self.thisptr = new IntPoint(x ,y) + + def __getitem__(self, key): + """Get coordinates of IntPoint.""" + return deref(self.thisptr)[key] + + def __repr__(self): + """repr(self)""" + return "IntPoint ({0}, {1})".format(self[0], self[1]) + + def __str__(self): + """str(self)""" + return "[{0}, {1}]".format(self[0], self[1]) + + def __dealloc__(self): + del self.thisptr + + @property + def x(self): + """First coordinate of IntPoint.""" + return self.thisptr.x() + + @property + def y(self): + """Second coordinate of IntPoint.""" + return self.thisptr.y() + + def __add__(cy_IntPoint self, cy_IntPoint o): + """P + Q""" + return wrap_IntPoint(deref(self.thisptr)+deref( o.thisptr )) + + def __sub__(cy_IntPoint self, cy_IntPoint o): + """P - Q""" + return wrap_IntPoint(deref(self.thisptr)-deref( o.thisptr )) + + def __richcmp__(cy_IntPoint self, cy_IntPoint other, int op): + if op == 0: + return deref(self.thisptr) < deref(other.thisptr) + if op == 1: + return deref(self.thisptr) <= deref(other.thisptr) + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + if op == 3: + return deref(self.thisptr) != deref(other.thisptr) + if op == 4: + return deref(self.thisptr) > deref(other.thisptr) + if op == 5: + return deref(self.thisptr) >= deref(other.thisptr) + +cdef cy_IntPoint wrap_IntPoint(IntPoint p): + cdef IntPoint * retp = new IntPoint() + retp[0] = p + cdef cy_IntPoint r = cy_IntPoint.__new__(cy_IntPoint) + r.thisptr = retp + return r + + +cdef class cy_Line: + + """Class representing line in plane. + + Corresponds to Line class in 2geom. Most members are direct + calls to Line methods, otherwise C++ call is specified. + """ + + cdef Line* thisptr + + def __cinit__(self, cy_Point cp = None, double x = 0): + """Create Line from point and angle to x-axis. + + Constructor with no arguments calls Line() in 2geom, otherwise + Line(Point &, double) is called. + """ + if cp is None: + self.thisptr = new Line() + else: + self.thisptr = new Line( deref(cp.thisptr), x) + + def __repr__(self): + """repr(self).""" + return "Line({0}, {1:3f})".format(repr(self.origin()), self.angle()) + + def __str__(self): + """str(self)""" + return repr(self) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_Point cp, cy_Point cq): + """Create Line passing through two points. + + Calls Line(Point &, Point &) in 2geom. + """ + return wrap_Line( Line( deref(cp.thisptr), deref(cq.thisptr) ) ) + + @classmethod + def from_origin_and_versor(cls, cy_Point o, cy_Point v): + """Create Line passing through point with specified versor.""" + return wrap_Line( from_origin_and_versor(deref(o.thisptr), deref(v.thisptr)) ) + + @classmethod + def from_normal_distance(cls, cy_Point normal, double dist): + """Create Line from it's normal and distance from origin.""" + return wrap_Line( from_normal_distance( deref(normal.thisptr), dist ) ) + + @classmethod + def from_LineSegment(cls, cy_LineSegment LS): + """Create Line from LineSegment. + + Calls Line(LineSegment &) in 2geom. + """ + return wrap_Line( Line( deref(<LineSegment *> LS.thisptr) ) ) + + @classmethod + def from_Ray(cls, cy_Ray R): + """Create Line from Ray. + + Calls Line(Ray &) in 2geom. + """ + return wrap_Line( Line( deref(R.thisptr) ) ) + + #maybe implement as properties. + + def origin(self): + """Return origin of line.""" + return wrap_Point(self.thisptr.origin()) + + def versor(self): + """Return versor of line.""" + return wrap_Point(self.thisptr.versor()) + + def angle(self): + """Return angle between line and x-axis.""" + return self.thisptr.angle() + + def set_origin(self, cy_Point origin): + """Set origin.""" + self.thisptr.setOrigin( deref(origin.thisptr) ) + + def set_versor(self, cy_Point versor): + """Set versor.""" + self.thisptr.setVersor( deref(versor.thisptr) ) + + def set_angle(self, Coord a): + """Set angle.""" + self.thisptr.setAngle(a) + + def set_points(self, cy_Point cp, cy_Point cq): + """Set two points line passes through.""" + self.thisptr.setPoints( deref(cp.thisptr), deref(cq.thisptr) ) + + def set_coefficients(self, a, b, c): + """Set coefficients in parametric equation of line.""" + self.thisptr.setCoefficients(a, b, c) + + def is_degenerate(self): + """Test whether line's versor is zero vector.""" + return self.thisptr.isDegenerate() + + def point_at(self, t): + """origin + t*versor""" + return wrap_Point(self.thisptr.pointAt(t)) + + def value_at(self, t, Dim2 d): + """Coordinates of point_at(t).""" + return self.thisptr.valueAt(t, d) + + def time_at(self, cy_Point cp): + """Find time value corresponding to point on line.""" + return self.thisptr.timeAt( deref(cp.thisptr) ) + + def time_at_projection(self, cy_Point cp): + """Find time value corresponding to orthogonal projection of point.""" + return self.thisptr.timeAtProjection( deref(cp.thisptr) ) + + def nearest_time(self, cy_Point cp): + """Alias for time_at_projection.""" + return self.thisptr.nearestTime( deref(cp.thisptr) ) + + def roots(self, Coord v, Dim2 d): + """Return time values where self.value_at(t, dim) == v.""" + return wrap_vector_double( self.thisptr.roots(v, d) ) + + def reverse(self): + """Reverse line.""" + return wrap_Line( self.thisptr.reverse() ) + + def derivative(self): + """Take line's derivative.""" + return wrap_Line( self.thisptr.derivative() ) + + def normal(self): + """Return line's normal.""" + return wrap_Point( self.thisptr.normal() ) + + def normal_and_dist(self): + """return tuple containing normal vector and distance from origin. + + Calls normal_and_dist(x) and return it's result and x as a tuple. + """ + cdef double x = 0 + cdef Point p = self.thisptr.normalAndDist(x) + return (wrap_Point(p), x) + + def portion(self, Coord f, Coord t): + """Return Curve corresponding to portion of line.""" + return wrap_Curve_p( self.thisptr.portion(f, t) ) + + def ray(self, Coord t): + """Return Ray continuing from time value t.""" + return wrap_Ray( self.thisptr.ray(t) ) + + def segment(self, Coord f, Coord t): + """Return LineSegment corresponding to portion of line.""" + return wrap_LineSegment( self.thisptr.segment(f, t) ) + + def transformed(self, t): + """Return line transformed by transform.""" + #doing this because transformed(t) takes reference + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_Line(self.thisptr.transformed( at )) + + @classmethod + def distance(cls, cy_Point cp, cy_Line cl): + """Calculate distance between point and line.""" + return distance( deref(cp.thisptr), deref(cl.thisptr)) + + @classmethod + def are_near(cls, cy_Point cp, cy_Line cl, double eps=EPSILON): + """Test if point is near line.""" + return are_near( deref(cp.thisptr), deref(cl.thisptr), eps) + + @classmethod + def are_parallel(cls, cy_Line cl, cy_Line ck, eps=EPSILON): + """Test if lines are almost parallel.""" + return are_parallel( deref(cl.thisptr), deref(ck.thisptr), eps) + + @classmethod + def are_same(cls, cy_Line cl, cy_Line ck, double eps=EPSILON): + """Test if lines represent the same line.""" + return are_same( deref(cl.thisptr), deref(ck.thisptr), eps) + + @classmethod + def are_orthogonal(cls, cy_Line cl, cy_Line ck, eps=EPSILON): + """Test two lines for orthogonality.""" + return are_orthogonal( deref(cl.thisptr), deref(ck.thisptr), eps) + + @classmethod + def are_collinear(cls, cy_Point cp, cy_Point cq, cy_Point cr, eps=EPSILON): + """Test for collinearity of vectors (cq-cp) and (cr-cp)""" + return are_collinear( deref(cp.thisptr), deref(cq.thisptr), deref(cr.thisptr), eps) + + @classmethod + def angle_between(cls, cy_Line cl, cy_Line ck): + """Calculate angle between two lines""" + return angle_between( deref(cl.thisptr), deref(ck.thisptr) ) + +cdef cy_Line wrap_Line(Line p): + cdef Line * retp = new Line() + retp[0] = p + cdef cy_Line r = cy_Line.__new__(cy_Line) + r.thisptr = retp + return r + +#-- Ray -- + +cdef class cy_Ray: + + """Ray represents half of line, starting at origin and going to + infinity. + + Corresponds to Ray class in 2geom. Most members are direct + calls to Ray methods, otherwise C++ call is specified. + """ + + cdef Ray* thisptr + + def __cinit__(self, cy_Point cp = None, double x = 0): + """Create Ray from origin and angle with x-axis. + + Empty constructor calls Ray() in 2geom. + """ + if cp is None: + self.thisptr = new Ray() + else: + self.thisptr = new Ray( deref(cp.thisptr), x) + + def __repr__(self): + """repr(self).""" + return "Ray({0}, {1:3f})".format(repr(self.origin()), self.angle()) + + def __str__(self): + """str(self)""" + return repr(self) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_Point cp, cy_Point cq): + """Create ray passing through two points, starting at first one.""" + return wrap_Ray( (Ray( deref(cp.thisptr), deref(cq.thisptr) )) ) + + def origin(self): + """Return origin.""" + return wrap_Point(self.thisptr.origin()) + + def versor(self): + """Return versor.""" + return wrap_Point(self.thisptr.versor()) + + def angle(self): + """Return angle between ray and x-axis.""" + return self.thisptr.angle() + + def set_origin(self, cy_Point cp): + """Set origin.""" + self.thisptr.setOrigin( deref(cp.thisptr) ) + + def set_versor(self, cy_Point cp): + """Set versor.""" + self.thisptr.setVersor( deref(cp.thisptr) ) + + def set_angle(self, Coord a): + """Set angle.""" + self.thisptr.setAngle(a) + + def set_points(self, cy_Point cp, cy_Point cq): + """Set origin and second point of ray.""" + self.thisptr.setPoints( deref(cp.thisptr), deref(cq.thisptr) ) + + def is_degenerate(self): + """Check for zero versor.""" + return self.thisptr.isDegenerate() + + def point_at(self, t): + """origin + t * versor""" + return wrap_Point(self.thisptr.pointAt(t)) + def value_at(self, t, Dim2 d): + """Access coordinates of point_at(t).""" + return self.thisptr.valueAt(t, d) + + def nearest_time(self, cy_Point cp): + """Get time value of nearest point of ray.""" + return self.thisptr.nearestTime( deref(cp.thisptr) ) + def reverse(self): + """Reverse the ray.""" + return wrap_Ray( self.thisptr.reverse() ) + + def roots(self, Coord v, Dim2 d): + """Return time values for which self.value_at(t, d) == v.""" + return wrap_vector_double( self.thisptr.roots(v, d) ) + + def transformed(self, t): + """Return ray transformed by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_Ray(self.thisptr.transformed( at )) + + def portion(self, Coord f, Coord t): + """Return Curve corresponding to portion of ray.""" + return wrap_Curve_p( self.thisptr.portion(f, t) ) + + def segment(self, Coord f, Coord t): + """Return LineSegment corresponding to portion of ray.""" + return wrap_LineSegment( self.thisptr.segment(f, t) ) + + @classmethod + def distance(cls, cy_Point cp, cy_Ray cl): + """Compute distance between point and ray.""" + return distance( deref(cp.thisptr), deref(cl.thisptr)) + + @classmethod + def are_near(cls, cy_Point cp, cy_Ray cl, double eps=EPSILON): + """Check if distance between point and ray is small.""" + return are_near( deref(cp.thisptr), deref(cl.thisptr), eps) + + @classmethod + def are_same(cls, cy_Ray cl, cy_Ray ck, double eps=EPSILON): + """Check if two ray are same.""" + return are_same( deref(cl.thisptr), deref(ck.thisptr), eps) + + @classmethod + def angle_between(cls, cy_Ray cl, cy_Ray ck, bint cw=True): + """Compute angle between two rays. + + Can specify direction using parameter cw. + """ + return angle_between( deref(cl.thisptr), deref(ck.thisptr), cw) + + @classmethod + def make_angle_bisector_ray(cls, cy_Ray cl, cy_Ray ck): + """Make ray bisecting smaller angle formed by two rays.""" + return wrap_Ray( make_angle_bisector_ray(deref(cl.thisptr), deref(ck.thisptr) )) + +cdef cy_Ray wrap_Ray(Ray p): + cdef Ray * retp = new Ray() + retp[0] = p + cdef cy_Ray r = cy_Ray.__new__(cy_Ray) + r.thisptr = retp + return r diff --git a/src/cython/_cy_rectangle.pxd b/src/cython/_cy_rectangle.pxd new file mode 100644 index 0000000..ac18169 --- /dev/null +++ b/src/cython/_cy_rectangle.pxd @@ -0,0 +1,442 @@ +from _common_decl cimport * +from cpython.ref cimport PyObject + +from _cy_primitives cimport Point, cy_Point, wrap_Point +from _cy_primitives cimport IntPoint, cy_IntPoint, wrap_IntPoint + +cdef extern from "2geom/affine.h" namespace "Geom": + cdef cppclass Affine: + pass + +cdef extern from "2geom/cython-bindings/wrapped-pyobject.h" namespace "Geom": + + cdef cppclass WrappedPyObject: + WrappedPyObject() + WrappedPyObject(object) + object getObj() + + cdef cppclass PyPoint: + PyPoint() + PyPoint(WrappedPyObject, WrappedPyObject) + WrappedPyObject operator[](int) + + +cdef extern from "2geom/generic-interval.h" namespace "Geom": + + cdef cppclass GenericInterval[C]: + + GenericInterval() + GenericInterval(C) + GenericInterval(C, C) + + C min() + C max() + C extent() + C middle() + + bint isSingular() + bint contains(C) + bint contains(GenericInterval[C] &) + bint intersects(GenericInterval[C] &) + + void setMin(C) + void setMax(C) + void expandTo(C) + void expandBy(C) + void unionWith(GenericInterval[C] &) + + GenericInterval[C] &operator+(C) + GenericInterval[C] &operator-(C) + GenericInterval[C] &operator-() + GenericInterval[C] &operator+(GenericInterval[C] &) + GenericInterval[C] &operator-(GenericInterval[C] &) + GenericInterval[C] &operator|(GenericInterval[C] &) + bint operator==(GenericInterval[C] &) + bint operator!=(GenericInterval[C] &) + + + cdef cppclass GenericOptInterval[C]: + + GenericOptInterval() + GenericOptInterval(C) + GenericOptInterval(C, C) + + GenericInterval get() + + C min() + C max() + C extent() + C middle() + + bint isEmpty() + bint isSingular() + bint contains(C) + bint contains(GenericOptInterval[C] &) + bint intersects(GenericOptInterval[C] &) + + void setMin(C) + void setMax(C) + void expandTo(C) + void expandBy(C) + void unionWith(GenericOptInterval[C] &) + void intersectWith(GenericOptInterval &) + + + GenericOptInterval[C] &operator+(C) + GenericOptInterval[C] &operator-(C) + GenericOptInterval[C] &operator-() + GenericOptInterval[C] &operator+(GenericOptInterval[C] &) + GenericOptInterval[C] &operator-(GenericOptInterval[C] &) + GenericOptInterval[C] &operator|(GenericOptInterval[C] &) + GenericOptInterval[C] &operator&(GenericOptInterval[C] &) + bint operator==(GenericOptInterval[C] &) + bint operator!=(GenericOptInterval[C] &) + + +cdef extern from "2geom/int-interval.h" namespace "Geom": + ctypedef GenericInterval[IntCoord] IntInterval + ctypedef GenericOptInterval[IntCoord] OptIntInterval + + +#redeclaring inherited methods, other option is to cast all +#pointers from Interval to GenericInterval[Coord] in extension class +cdef extern from "2geom/interval.h" namespace "Geom": + + cdef cppclass Interval: + + Interval() + Interval(Coord) + Interval(Coord, Coord) + + Coord min() + Coord max() + Coord extent() + Coord middle() + + bint isSingular() + bint contains(Coord) + bint contains(Interval &) + bint intersects(Interval &) + + void setMin(Coord) + void setMax(Coord) + void expandTo(Coord) + void expandBy(Coord) + void unionWith(Interval &) + + bint isFinite() + bint interiorContains(Coord) + bint interiorContains(Interval &) + bint interiorIntersects(Interval &) + + Interval & operator*(Coord) + Interval & operator/(Coord) + Interval & operator*(Interval &) + bint operator==(IntInterval &) + bint operator==(Interval &) + bint operator!=(Interval &) + bint operator!=(IntInterval &) + Interval &operator+(Coord) + Interval &operator-(Coord) + Interval &operator-() + Interval &operator+(Interval &) + Interval &operator-(Interval &) + Interval &operator|(Interval &) + + IntInterval roundOutwards() + OptIntInterval roundInwards() + + + cdef cppclass OptInterval: + + OptInterval(OptInterval &) + OptInterval() + OptInterval(Interval &) + OptInterval(Coord) + OptInterval(Coord, Coord) + OptInterval(GenericOptInterval[double] &) + OptInterval(IntInterval &) + OptInterval(OptIntInterval &) + + Interval get() + bint isEmpty() + void unionWith(OptInterval &) + void intersectWith(OptInterval &) + + OptInterval &operator|(OptInterval &) + OptInterval &operator&(OptInterval &) + +cdef class cy_Interval: + cdef Interval* thisptr + +cdef cy_Interval wrap_Interval(Interval p) + +cdef class cy_OptInterval: + cdef OptInterval* thisptr + +cdef cy_OptInterval wrap_OptInterval(OptInterval p) + + +cdef extern from "2geom/generic-rect.h" namespace "Geom": + cdef cppclass GenericRect[C]: + GenericRect() + GenericRect(GenericInterval[C] &, GenericInterval[C] &) + GenericRect(Point &, Point &) + GenericRect(PyPoint &, PyPoint &) + GenericRect(IntPoint &, IntPoint &) + GenericRect(C, C, C, C) + + GenericInterval[C] & operator[](Dim2) + + PyPoint min() + PyPoint max() + PyPoint corner(unsigned int) + + IntPoint i_min "min" () + IntPoint i_max "max" () + IntPoint i_corner "corner" (unsigned int) + + C top() + C bottom() + C left() + C right() + C width() + C height() + Coord aspectRatio() + + PyPoint dimensions() + PyPoint midpoint() + + IntPoint i_dimensions "dimensions" () + IntPoint i_midpoint "midpoint" () + + C area() + bint hasZeroArea() + C maxExtent() + C minExtent() + bint intersects(GenericRect[C] &) + bint contains(GenericRect[C] &) + + bint contains(PyPoint &) + bint contains(IntPoint &) + + void setLeft(C) + void setRight(C) + void setTop(C) + void setBottom(C) + + void setMin(PyPoint &) + void setMax(PyPoint &) + void expandTo(PyPoint &) + void setMin(IntPoint &) + void setMax(IntPoint &) + void expandTo(IntPoint &) + + void unionWith(GenericRect[C] &) + void expandBy(C) + void expandBy(C, C) + + void expandBy(PyPoint &) + void expandBy(IntPoint &) + + GenericRect[C] & operator+(PyPoint &) + GenericRect[C] & operator+(IntPoint &) + + GenericRect[C] & operator-(PyPoint &) + GenericRect[C] & operator-(IntPoint &) + + GenericRect[C] & operator|(GenericRect[C] &) + bint operator==(GenericRect[C] &) + bint operator!=(GenericRect[C] &) + + + cdef cppclass GenericOptRect[C]: + GenericOptRect() + GenericOptRect(GenericRect[C] &) + GenericOptRect(C, C, C, C) + GenericOptRect(Point &, Point &) + GenericOptRect(GenericOptInterval[C] &, GenericOptInterval[C] &) + + GenericRect[C] get() + + bint isEmpty() + bint intersects(GenericRect[C] &) + bint contains(GenericRect[C] &) + bint intersects(GenericOptRect[C] &) + bint contains(GenericOptRect[C] &) + + bint contains(Point &) + bint contains(IntPoint &) + + void unionWith(GenericRect[C] &) + void unionWith(GenericOptRect[C] &) + void intersectWith(GenericRect[C] &) + void intersectWith(GenericOptRect[C] &) + + void expandTo(Point &) + + GenericOptRect[C] &operator|(GenericOptRect[C] &) + GenericOptRect[C] &operator&(GenericRect[C] &) + GenericOptRect[C] &operator&(GenericOptRect[C] &) + + bint operator==(GenericOptRect[C] &) + bint operator==(GenericRect[C] &) + + bint operator!=(GenericOptRect[C] &) + bint operator!=(GenericRect[C] &) + +cdef extern from "2geom/generic-rect.h" namespace "Geom::GenericRect<Geom::WrappedPyObject>": + GenericRect[WrappedPyObject] from_xywh(WrappedPyObject, WrappedPyObject, WrappedPyObject, WrappedPyObject) + GenericRect[WrappedPyObject] from_xywh(PyPoint &, PyPoint &) + +cdef extern from "2geom/int-rect.h" namespace "Geom": + ctypedef GenericRect[IntCoord] IntRect + +cdef extern from "2geom/rect.h" namespace "Geom": + cdef cppclass Rect: + Rect() + Rect(Interval &, Interval &) + Rect(Point &, Point &) + Rect(Coord, Coord, Coord, Coord) + + Interval & operator[](Dim2) + Point min() + Point max() + Point corner(unsigned int) + Coord top() + Coord bottom() + Coord left() + Coord right() + Coord width() + Coord height() + Coord aspectRatio() + Point dimensions() + Point midpoint() + Coord area() + bint hasZeroArea() + Coord maxExtent() + Coord minExtent() + + bint intersects(Rect &) + bint contains(Rect &) + bint contains(Point &) + + void setLeft(Coord) + void setRight(Coord) + void setTop(Coord) + void setBottom(Coord) + void setMin(Point &) + void setMax(Point &) + void expandTo(Point &) + void unionWith(Rect &) + void expandBy(Coord) + void expandBy(Coord, Coord) + void expandBy(Point &) + + Rect & operator+(Point &) + Rect & operator-(Point &) + Rect & operator|(Rect &) + bint operator==(Rect &) + bint operator!=(Rect &) + bint operator==(IntRect &) + bint operator!=(IntRect &) + + bint hasZeroArea(Coord) + bint interiorIntersects(Rect &) + bint interiorContains(Point &) + bint interiorContains(Rect &) + bint interiorContains(OptRect &) + + IntRect roundOutwards() + OptIntRect roundInwards() + Rect &operator*(Affine &) + + Coord distanceSq(Point &, Rect &) + Coord distance(Point &, Rect &) + + + cdef cppclass OptRect: + OptRect() + OptRect(Rect &) + OptRect(Coord, Coord, Coord, Coord) + OptRect(Point &, Point &) + OptRect(OptInterval &, OptInterval &) + + Rect get() + + bint isEmpty() + bint intersects(Rect &) + bint contains(Rect &) + bint intersects(OptRect &) + bint contains(OptRect &) + bint contains(Point &) + + void unionWith(Rect &) + void unionWith(OptRect &) + void intersectWith(Rect &) + void intersectWith(OptRect &) + void expandTo(Point &) + + OptRect &operator|(OptRect &) + OptRect &operator&(Rect &) + OptRect &operator&(OptRect &) + + bint operator==(OptRect &) + bint operator==(Rect &) + + bint operator!=(OptRect &) + bint operator!=(Rect &) + +cdef extern from "2geom/rect.h" namespace "Geom::Rect": + Rect from_xywh(Coord, Coord, Coord, Coord) + Rect from_xywh(Point &, Point &) + Rect infinite() + +cdef class cy_Rect: + cdef Rect* thisptr + +cdef cy_Rect wrap_Rect(Rect p) + +cdef class cy_OptRect: + cdef OptRect* thisptr + +cdef cy_OptRect wrap_OptRect(OptRect p) + + +cdef extern from "2geom/int-rect.h" namespace "Geom": + #redeclaring because cython complains about ambiguous overloading otherwise + cdef cppclass OptIntRect: + OptIntRect() + OptIntRect(IntRect &) + OptIntRect(IntCoord, IntCoord, IntCoord, IntCoord) + OptIntRect(IntPoint &, IntPoint &) + OptIntRect(OptIntInterval &, OptIntInterval &) + + IntRect get() + + bint isEmpty() + bint intersects(IntRect &) + bint contains(IntRect &) + bint intersects(OptIntRect &) + bint contains(OptIntRect &) + + bint contains(Point &) + bint contains(IntPoint &) + + void unionWith(IntRect &) + void unionWith(OptIntRect &) + void intersectWith(IntRect &) + void intersectWith(OptIntRect &) + + void expandTo(IntPoint &) + + OptIntRect &operator|(OptIntRect &) + OptIntRect &operator&(IntRect &) + OptIntRect &operator&(OptIntRect &) + + bint operator==(OptIntRect &) + bint operator==(IntRect &) + + bint operator!=(OptIntRect &) + bint operator!=(IntRect &) + diff --git a/src/cython/_cy_rectangle.pyx b/src/cython/_cy_rectangle.pyx new file mode 100644 index 0000000..1d1b687 --- /dev/null +++ b/src/cython/_cy_rectangle.pyx @@ -0,0 +1,2202 @@ +from numbers import Number + +from _common_decl cimport * +from cython.operator cimport dereference as deref + +from _cy_affine cimport cy_Affine, get_Affine, is_transform + + +cdef class cy_GenericInterval: + """ + Represents all numbers between min and max. + + Corresponds to GenericInterval in 2geom. min and max can be arbitrary + python object, they just have to implement arithmetic operations and + comparison - fractions.Fraction works. This is a bit experimental, + it leak memory right now. + """ + + cdef GenericInterval[WrappedPyObject]* thisptr + + def __cinit__(self, u = 0, v = None): + """Create GenericInterval from either one or two values.""" + if v is None: + self.thisptr = new GenericInterval[WrappedPyObject]( WrappedPyObject(u) ) + else: + self.thisptr = new GenericInterval[WrappedPyObject]( WrappedPyObject(u), WrappedPyObject(v) ) + + def __str__(self): + """str(self)""" + return "[{}, {}]".format(self.min(), self.max()) + + def __repr__(self): + """repr(self)""" + if self.is_singular(): + return "GenericInterval({})".format( str(self.min()) ) + return "GenericInterval({}, {})".format( str(self.min()) , str(self.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(self, i): + """Create GenericInterval with same minimum and maximum as argument.""" + return cy_GenericInterval( i.min(), i.max() ) + + @classmethod + def from_list(self, lst): + """Create GenericInterval containing all values in list.""" + if len(lst) == 0: + return cy_GenericInterval() + ret = cy_GenericInterval(lst[0]) + for i in lst[1:]: + ret.expand_to(i) + return ret + + def min(self): + """Return minimal value of interval.""" + return self.thisptr.min().getObj() + + def max(self): + """Return maximal value of interval.""" + return self.thisptr.max().getObj() + def extent(self): + """Return difference between maximal and minimal value.""" + return self.thisptr.extent().getObj() + + def middle(self): + """Return midpoint of interval.""" + return self.thisptr.middle().getObj() + + def is_singular(self): + """Test for one-valued interval.""" + return self.thisptr.isSingular() + + def set_min(self, val): + """Set minimal value.""" + self.thisptr.setMin( WrappedPyObject(val) ) + + def set_max(self, val): + """Set maximal value.""" + self.thisptr.setMax( WrappedPyObject(val) ) + + def expand_to(self, val): + """Create smallest superset of self containing value.""" + self.thisptr.expandTo( WrappedPyObject(val) ) + + def expand_by(self, val): + """Push both boundaries by value.""" + self.thisptr.expandBy( WrappedPyObject(val) ) + def union_with(self, cy_GenericInterval interval): + """self = self | other""" + self.thisptr.unionWith( deref(interval.thisptr) ) + + def contains(self, other): + """Check if interval contains value.""" + return self.thisptr.contains( WrappedPyObject(other) ) + + def contains_interval(self, cy_GenericInterval other): + """Check if interval contains every point of interval.""" + return self.thisptr.contains( deref(other.thisptr) ) + + def intersects(self, cy_GenericInterval other): + """Check for intersecting intervals.""" + return self.thisptr.intersects(deref( other.thisptr )) + + def __neg__(self): + """Return interval with negated boundaries.""" + return wrap_GenericInterval(-deref(self.thisptr)) + + def _add_pyobj(self, X): + return wrap_GenericInterval(deref(self.thisptr) + WrappedPyObject(X) ) + def _sub_pyobj(self, X): + return wrap_GenericInterval(deref(self.thisptr) - WrappedPyObject(X) ) + + def _add_interval(self, cy_GenericInterval I): + return wrap_GenericInterval(deref(self.thisptr)+deref(I.thisptr)) + def _sub_interval(self, cy_GenericInterval I): + return wrap_GenericInterval(deref(self.thisptr)-deref(I.thisptr)) + + def __add__(cy_GenericInterval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + if isinstance(other, cy_GenericInterval): + return self._add_interval(other) + else: + return self._add_pyobj(other) + + def __sub__(cy_GenericInterval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + if isinstance(other, cy_GenericInterval): + return self._sub_interval(other) + else: + return self._sub_pyobj(other) + + def __or__(cy_GenericInterval self, cy_GenericInterval I): + """Return a union of two intervals""" + return wrap_GenericInterval(deref(self.thisptr)|deref(I.thisptr)) + + def _eq(self, cy_GenericInterval other): + return deref(self.thisptr)==deref(other.thisptr) + + def _neq(self, cy_GenericInterval other): + return deref(self.thisptr)!=deref(other.thisptr) + + def __richcmp__(cy_GenericInterval self, other, op): + """Intervals are not ordered.""" + if op == 2: + return self._eq(other) + elif op == 3: + return self._neq(other) + +cdef cy_GenericInterval wrap_GenericInterval(GenericInterval[WrappedPyObject] p): + cdef GenericInterval[WrappedPyObject] * retp = new GenericInterval[WrappedPyObject](WrappedPyObject(0)) + retp[0] = p + cdef cy_GenericInterval r = cy_GenericInterval.__new__( + cy_GenericInterval, 0, 0) + r.thisptr = retp + return r + + +cdef class cy_GenericOptInterval: + + """Class representing optionally empty interval. + + Empty interval has False bool value, and using methods that require + non-empty interval will result in ValueError. This is supposed to be + used this way: + + >>> C = A & B + >>> if C: + >>> print C.min() + + This class represents GenericOptInterval with python object boundaries. + It tries to model behaviour of std::optional. + """ + + cdef GenericOptInterval[WrappedPyObject]* thisptr + + def __cinit__(self, u = None, v = None): + """Create interval from boundaries. + + Using no arguments, you will end up with empty interval.""" + if u is None: + self.thisptr = new GenericOptInterval[WrappedPyObject]() + elif v is None: + self.thisptr = new GenericOptInterval[WrappedPyObject](WrappedPyObject(u)) + else: + self.thisptr = new GenericOptInterval[WrappedPyObject](WrappedPyObject(u), WrappedPyObject(v) ) + + def __bool__(self): + """Logical value of interval, False only for empty interval.""" + return not self.thisptr.isEmpty() + + def __str__(self): + """str(self)""" + if not self: + return "[]" + return "[{}, {}]".format(self.Interval.min(), self.Interval.max()) + + def __repr__(self): + """repr(self)""" + if not self: + return "GenericOptInterval()" + if self.Interval.isSingular(): + return "GenericOptInterval({})".format( str(self.Interval.min()) ) + return "GenericOptInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(self, i): + """Create interval from existing interval.""" + if hasattr(i, "isEmpty"): + if i.isEmpty(): + return cy_GenericOptInterval() + else: + return cy_GenericOptInterval.from_Interval(i.Interval) + return cy_GenericOptInterval( i.min(), i.max() ) + + @classmethod + def from_list(self, lst): + """Create interval containing all values in list. + + Empty list will result in empty interval.""" + if len(lst) == 0: + return cy_GenericOptInterval() + ret = cy_GenericOptInterval(lst[0]) + for i in lst[1:]: + ret.Interval.expandTo(i) + return ret + + property Interval: + """Get underlying GenericInterval.""" + def __get__(self): + if self.is_empty(): + raise ValueError("Interval is empty.") + else: + return wrap_GenericInterval(self.thisptr.get()) + + def is_empty(self): + """Check whether interval is empty set.""" + return self.thisptr.isEmpty() + + def union_with(self, cy_GenericOptInterval o): + """self = self | other""" + self.thisptr.unionWith( deref(o.thisptr) ) + + def intersect_with(cy_GenericOptInterval self, cy_GenericOptInterval o): + """self = self & other""" + self.thisptr.intersectWith( deref(o.thisptr) ) + + def __or__(cy_GenericOptInterval self, cy_GenericOptInterval o): + """Return a union of two intervals.""" + return wrap_GenericOptInterval(deref(self.thisptr) | deref(o.thisptr)) + + def __and__(cy_GenericOptInterval self, cy_GenericOptInterval o): + """Return an intersection of two intervals.""" + return wrap_GenericOptInterval(deref(self.thisptr) & deref(o.thisptr)) + + def __richcmp__(cy_GenericOptInterval self, cy_GenericOptInterval o, int op): + """Intervals are not ordered.""" + if op == 2: + return deref(self.thisptr) == deref(o.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(o.thisptr) + return NotImplemented + + + def _get_Interval_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("GenericOptInterval is empty.") + else: + return self.Interval.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + + Interval_methods = set(['contains', 'contains_interval', + 'expand_by', 'expand_to', 'extent', 'from_Interval', 'from_list', + 'intersects', 'is_singular', 'max', 'middle', 'min', 'set_max', + 'set_min', 'union_with']) + + if name in Interval_methods: + return self._get_Interval_method(name) + else: + raise AttributeError("GenericOptInterval instance has no attribute \"{}\"".format(name)) + + def _wrap_Interval_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("GenericOptInterval is empty.") + else: + return self.Interval.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __neg__(self): + """Return interval with negated boundaries.""" + return self._wrap_Interval_method("__sub__") + + def __add__(cy_Interval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + return self._wrap_Interval_method("__add__", other) + + def __sub__(cy_Interval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + return self._wrap_Interval_method("__sub__", other) + +cdef cy_GenericOptInterval wrap_GenericOptInterval(GenericOptInterval[WrappedPyObject] p): + cdef GenericOptInterval[WrappedPyObject] * retp = new GenericOptInterval[WrappedPyObject]() + retp[0] = p + cdef cy_GenericOptInterval r = cy_GenericOptInterval.__new__(cy_GenericOptInterval) + r.thisptr = retp + return r + + +cdef class cy_Interval: + + """Class representing interval on real line. + + Corresponds to Interval class in 2geom. + """ + + def __cinit__(self, u = None, v = None): + """Create interval from it's boundaries. + + One argument will create interval consisting that value, no + arguments create Interval(0). + """ + if u is None: + self.thisptr = new Interval() + elif v is None: + self.thisptr = new Interval(<Coord>float(u)) + else: + self.thisptr = new Interval(<Coord>float(u), <Coord>float(v)) + + def __str__(self): + """str(self)""" + return "[{}, {}]".format(self.min(), self.max()) + + def __repr__(self): + """repr(self)""" + if self.is_singular(): + return "Interval({})".format( str(self.min()) ) + return "Interval({}, {})".format( str(self.min()) , str(self.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(c, i): + """Create Interval with same boundaries as argument.""" + return cy_Interval( i.min(), i.max() ) + + @classmethod + def from_list(cls, lst): + """Create interval containing all values in a list.""" + if len(lst) == 0: + return cy_Interval() + ret = cy_Interval(lst[0]) + for i in lst[1:]: + ret.expand_to(i) + return ret + + def min(self): + """Return minimal boundary.""" + return self.thisptr.min() + + def max(self): + """Return maximal boundary.""" + return self.thisptr.max() + + def extent(self): + """Return length of interval.""" + return self.thisptr.extent() + + def middle(self): + """Return middle value.""" + return self.thisptr.middle() + + def set_min(self, Coord val): + """Set minimal value.""" + self.thisptr.setMin(val) + + def set_max(self, Coord val): + """Set maximal value.""" + self.thisptr.setMax(val) + + def expand_to(self, Coord val): + """Set self to smallest superset of set containing value.""" + self.thisptr.expandTo(val) + + def expand_by(self, Coord amount): + """Move both boundaries by amount.""" + self.thisptr.expandBy(amount) + + def union_with(self, cy_Interval a): + """self = self | other""" + self.thisptr.unionWith(deref( a.thisptr )) + +# Not exposing this - deprecated +# def __getitem__(self, unsigned int i): +# return deref(self.thisptr)[i] + + def is_singular(self): + """Test if interval contains only one value.""" + return self.thisptr.isSingular() + + def isFinite(self): + """Test for finiteness of interval's extent.""" + return self.thisptr.isFinite() + + def contains(cy_Interval self, other): + """Test if interval contains number.""" + return self.thisptr.contains(float(other)) + + def contains_interval(cy_Interval self, cy_Interval other): + """Test if interval contains another interval.""" + return self.thisptr.contains( deref(other.thisptr) ) + + def intersects(self, cy_Interval val): + """Test for intersection of intervals.""" + return self.thisptr.intersects(deref( val.thisptr )) + + def interior_contains(cy_Interval self, other): + """Test if interior of iterval contains number.""" + return self.thisptr.interiorContains(float(other)) + + def interior_contains_interval(cy_Interval self, cy_Interval other): + """Test if interior of interval contains another interval.""" + return self.thisptr.interiorContains( <Interval &> deref(other.thisptr) ) + + + def interior_intersects(self, cy_Interval val): + """Test for intersection of interiors of two points.""" + return self.thisptr.interiorIntersects(deref( val.thisptr )) + + def _cmp_Interval(cy_Interval self, cy_Interval other, op): + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + def _cmp_IntInterval(cy_Interval self, cy_IntInterval other, op): + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + + def __richcmp__(cy_Interval self, other, op): + """Intervals are not ordered.""" + if isinstance(other, cy_Interval): + return self._cmp_Interval(other, op) + elif isinstance(other, cy_IntInterval): + return self._cmp_IntInterval(other, op) + + def __neg__(self): + """Return interval with negated boundaries.""" + return wrap_Interval(-deref(self.thisptr)) + + def _add_number(self, Coord X): + return wrap_Interval(deref(self.thisptr)+X) + def _sub_number(self, Coord X): + return wrap_Interval(deref(self.thisptr)-X) + def _mul_number(self, Coord X): + return wrap_Interval(deref(self.thisptr)*X) + + def _add_interval(self, cy_Interval I): + return wrap_Interval(deref(self.thisptr)+deref(I.thisptr)) + def _sub_interval(self, cy_Interval I): + return wrap_Interval(deref(self.thisptr)-deref(I.thisptr)) + def _mul_interval(self, cy_Interval I): + return wrap_Interval(deref(self.thisptr)*deref(I.thisptr)) + + def __mul__(cy_Interval self, other): + """Multiply interval by interval or number. + + Multiplying by number simply multiplies boundaries, + multiplying intervals creates all values that can be written as + product i*j of i in I and j in J. + """ + if isinstance(other, Number): + return self._mul_number(float(other)) + else: + return self._mul_interval(other) + + def __add__(cy_Interval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + if isinstance(other, Number): + return self._add_number(float(other)) + else: + return self._add_interval(other) + + def __sub__(cy_Interval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + if isinstance(other, Number): + return self._sub_number(float(other)) + else: + return self._sub_interval(other) + + def __div__(cy_Interval self, Coord s): + """Divide boundaries by number.""" + return wrap_Interval(deref(self.thisptr)/s) + + def __or__(cy_Interval self, cy_Interval I): + """Return union of two intervals.""" + return wrap_Interval(deref(self.thisptr)|deref(I.thisptr)) + + def round_outwards(self): + """Create the smallest IntIterval that is superset.""" + return wrap_IntInterval(self.thisptr.roundOutwards()) + + def round_inwards(self): + """Create the largest IntInterval that is subset.""" + return wrap_OptIntInterval(self.thisptr.roundInwards()) + +cdef cy_Interval wrap_Interval(Interval p): + cdef Interval * retp = new Interval() + retp[0] = p + cdef cy_Interval r = cy_Interval.__new__(cy_Interval) + r.thisptr = retp + return r + + +cdef class cy_OptInterval: + + """Class representing optionally empty interval on real line. + + Empty interval has False bool value, and using methods that require + non-empty interval will result in ValueError. This is supposed to be + used this way: + + >>> C = A & B + >>> if C: + >>> print C.min() + + This class represents OptInterval. It tries to model behaviour of + std::optional. + """ + + def __cinit__(self, u = None, v = None): + """Create optionally empty interval form it's endpoints. + + No arguments will result in empty interval. + """ + if u is None: + self.thisptr = new OptInterval() + elif v is None: + self.thisptr = new OptInterval(<Coord>float(u)) + else: + self.thisptr = new OptInterval(<Coord>float(u), <Coord>float(v)) + + def __bool__(self): + """Only empty interval is False.""" + return not self.thisptr.isEmpty() + + def __str__(self): + """str(self)""" + if not self: + return "[]" + return "[{}, {}]".format(self.Interval.min(), self.Interval.max()) + + def __repr__(self): + """repr(self)""" + if not self: + return "OptInterval()" + if self.Interval.isSingular(): + return "OptInterval({})".format( str(self.Interval.min()) ) + return "OptInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(cls, i): + """Create interval from other (possibly empty) interval.""" + if hasattr(i, "isEmpty"): + if i.isEmpty(): + return cy_OptInterval() + else: + return cy_OptInterval.from_Interval(i.Interval) + return cy_OptInterval( i.min(), i.max() ) + + @classmethod + def from_list(self, lst): + """Create interval containing all values in list. + + Empty list will result in empty interval.""" + if len(lst) == 0: + return cy_OptInterval() + ret = cy_OptInterval(lst[0]) + for i in lst[1:]: + ret.Interval.expandTo(i) + return ret + + property Interval: + """Get underlying Interval.""" + def __get__(self): + if self.is_empty(): + raise ValueError("Interval is empty.") + else: + return wrap_Interval(self.thisptr.get()) + + def is_empty(self): + """Test for empty interval.""" + return self.thisptr.isEmpty() + + def union_with(self, cy_OptInterval o): + """self = self | other""" + self.thisptr.unionWith( deref(o.thisptr) ) + + def intersect_with(cy_OptInterval self, cy_OptInterval o): + """self = self & other""" + self.thisptr.intersectWith( deref(o.thisptr) ) + + def __or__(cy_OptInterval self, cy_OptInterval o): + """Return union of intervals.""" + return wrap_OptInterval(deref(self.thisptr) | deref(o.thisptr)) + + def __and__(cy_OptInterval self, cy_OptInterval o): + """Return intersection of intervals.""" + return wrap_OptInterval(deref(self.thisptr) & deref(o.thisptr)) + + def _get_Interval_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("OptInterval is empty.") + else: + return self.Interval.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + + Interval_methods = set(['contains', 'contains_interval', 'expand_by', + 'expand_to', 'extent', 'from_Interval', 'from_list', + 'interior_contains', 'interior_contains_interval', + 'interior_intersects', 'intersects', 'isFinite', 'is_singular', + 'max', 'middle', 'min', 'round_inwards', 'round_outwards', + 'set_max', 'set_min', 'union_with']) + + if name in Interval_methods: + return self._get_Interval_method(name) + else: + raise AttributeError("OptInterval instance has no attribute \"{}\"".format(name)) + + def _wrap_Interval_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("OptInterval is empty.") + else: + return self.Interval.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __neg__(self): + """Return interval with negated boundaries.""" + return self._wrap_Interval_method("__sub__") + + def __mul__(cy_Interval self, other): + """Multiply interval by interval or number. + + Multiplying by number simply multiplies boundaries, + multiplying intervals creates all values that can be written as + product i*j of i in I and j in J. + """ + return self._wrap_Interval_method("__mul__", other) + + def __add__(cy_Interval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + return self._wrap_Interval_method("__add__", other) + + def __sub__(cy_Interval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + return self._wrap_Interval_method("__sub__", other) + + def __div__(cy_Interval self, other): + """Divide boundaries by number.""" + return self._wrap_Interval_method("__div__", other) + +cdef cy_OptInterval wrap_OptInterval(OptInterval p): + cdef OptInterval * retp = new OptInterval() + retp[0] = p + cdef cy_OptInterval r = cy_OptInterval.__new__(cy_OptInterval) + r.thisptr = retp + return r + + +cdef class cy_IntInterval: + + """Class representing interval of integers. + + Corresponds to IntInterval class in 2geom. + """ + + cdef IntInterval* thisptr + + def __cinit__(self, u = None, v = None): + """Create interval from it's boundaries. + + One argument will create interval consisting that value, no + arguments create IntInterval(0). + """ + if u is None: + self.thisptr = new IntInterval() + elif v is None: + self.thisptr = new IntInterval(<IntCoord>int(u)) + else: + self.thisptr = new IntInterval(<IntCoord>int(u), <IntCoord>int(v)) + + def __str__(self): + """str(self)""" + return "[{}, {}]".format(self.min(), self.max()) + + def __repr__(self): + """repr(self)""" + if self.is_singular(): + return "IntInterval({})".format( str(self.min()) ) + return "IntInterval({}, {})".format( str(self.min()) , str(self.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(cls, i): + return cy_IntInterval( int(i.min()), int(i.max()) ) + + @classmethod + def from_list(cls, lst): + if len(lst) == 0: + return cy_IntInterval() + ret = cy_IntInterval(lst[0]) + for i in lst[1:]: + ret.expand_to(i) + return ret + + def min(self): + """Return minimal boundary.""" + return self.thisptr.min() + + def max(self): + """Return maximal boundary.""" + return self.thisptr.max() + + def extent(self): + """Return length of interval.""" + return self.thisptr.extent() + + def middle(self): + """Return middle value.""" + return self.thisptr.middle() + + def set_min(self, IntCoord val): + """Set minimal value.""" + self.thisptr.setMin(val) + + def set_max(self, IntCoord val): + """Set maximal value.""" + self.thisptr.setMax(val) + + def expand_to(self, IntCoord val): + """Set self to smallest superset of set containing value.""" + self.thisptr.expandTo(val) + + def expand_by(self, IntCoord amount): + """Move both boundaries by amount.""" + self.thisptr.expandBy(amount) + + def union_with(self, cy_IntInterval a): + """self = self | other""" + self.thisptr.unionWith(deref( a.thisptr )) + +# Not exposing this - deprecated +# def __getitem__(self, unsigned int i): +# return deref(self.thisptr)[i] + + def is_singular(self): + """Test if interval contains only one value.""" + return self.thisptr.isSingular() + + def contains(cy_IntInterval self, other): + """Test if interval contains number.""" + return self.thisptr.contains(<IntCoord> int(other)) + + def contains_interval(cy_IntInterval self, cy_IntInterval other): + """Test if interval contains another interval.""" + return self.thisptr.contains( deref(other.thisptr) ) + + def intersects(self, cy_IntInterval val): + """Test for intersection with other interval.""" + return self.thisptr.intersects(deref( val.thisptr )) + + def __richcmp__(cy_IntInterval self, cy_IntInterval other, op): + """Intervals are not ordered.""" + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + + def __neg__(self): + """Negate interval's endpoints.""" + return wrap_IntInterval(-deref(self.thisptr)) + + def _add_number(self, IntCoord X): + return wrap_IntInterval(deref(self.thisptr)+X) + def _sub_number(self, IntCoord X): + return wrap_IntInterval(deref(self.thisptr)-X) + + def _add_interval(self, cy_IntInterval I): + return wrap_IntInterval(deref(self.thisptr)+deref(I.thisptr)) + def _sub_interval(self, cy_IntInterval I): + return wrap_IntInterval(deref(self.thisptr)-deref(I.thisptr)) + + def __add__(cy_IntInterval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + if isinstance(other, Number): + return self._add_number(int(other)) + else: + return self._add_interval(other) + + def __sub__(cy_IntInterval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + if isinstance(other, Number): + return self._sub_number(int(other)) + else: + return self._sub_interval(other) + + def __or__(cy_IntInterval self, cy_IntInterval I): + """Return union of two intervals.""" + return wrap_IntInterval(deref(self.thisptr)|deref(I.thisptr)) + +cdef cy_IntInterval wrap_IntInterval(IntInterval p): + cdef IntInterval * retp = new IntInterval() + retp[0] = p + cdef cy_IntInterval r = cy_IntInterval.__new__(cy_IntInterval) + r.thisptr = retp + return r + +cdef class cy_OptIntInterval: + + """Class representing optionally empty interval of integers. + + Empty interval has False bool value, and using methods that require + non-empty interval will result in ValueError. This is supposed to be + used this way: + + >>> C = A & B + >>> if C: + >>> print C.min() + + This class represents OptIntInterval. It tries to model behaviour of + std::optional. + """ + + cdef OptIntInterval* thisptr + + def __cinit__(self, u = None, v = None): + """Create optionally empty interval form it's endpoints. + + No arguments will result in empty interval. + """ + if u is None: + self.thisptr = new OptIntInterval() + elif v is None: + self.thisptr = new OptIntInterval(<IntCoord>int(u)) + else: + self.thisptr = new OptIntInterval(<IntCoord>int(u), <IntCoord>int(v)) + + def __bool__(self): + """Only empty interval is False.""" + return not self.thisptr.isEmpty() + + def __str__(self): + """str(self)""" + if not self: + return "[]" + return "[{}, {}]".format(self.Interval.min(), self.Interval.max()) + + def __repr__(self): + """repr(self)""" + if not self: + return "OptIntInterval()" + if self.Interval.isSingular(): + return "OptIntInterval({})".format( str(self.Interval.min()) ) + return "OptIntInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(self, i): + """Create interval from other (possibly empty) interval.""" + if hasattr(i, "isEmpty"): + if i.isEmpty(): + return cy_OptIntInterval() + else: + return cy_OptIntInterval.from_Interval(i.Interval) + return cy_OptIntInterval( i.min(), i.max() ) + + @classmethod + def from_list(self, lst): + """Create interval containing all values in list. + + Empty list will result in empty interval.""" + if len(lst) == 0: + return cy_OptIntInterval() + ret = cy_OptIntInterval(lst[0]) + for i in lst[1:]: + ret.Interval.expandTo(i) + return ret + + property Interval: + """Get underlying interval.""" + def __get__(self): + return wrap_IntInterval(self.thisptr.get()) + + def is_empty(self): + """Test for empty interval.""" + return self.thisptr.isEmpty() + + def union_with(self, cy_OptIntInterval o): + """self = self | other""" + self.thisptr.unionWith( deref(o.thisptr) ) + + def intersect_with(cy_OptIntInterval self, cy_OptIntInterval o): + """self = self & other""" + self.thisptr.intersectWith( deref(o.thisptr) ) + + def __or__(cy_OptIntInterval self, cy_OptIntInterval o): + """Return a union of two intervals.""" + return wrap_OptIntInterval(deref(self.thisptr) | deref(o.thisptr)) + + def __and__(cy_OptIntInterval self, cy_OptIntInterval o): + """Return an intersection of two intervals.""" + return wrap_OptIntInterval(deref(self.thisptr) & deref(o.thisptr)) + + #TODO decide how to implement various combinations of comparisons! + + def _get_Interval_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("OptInterval is empty.") + else: + return self.Interval.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + + Interval_methods = set(['contains', 'contains_interval', + 'expand_by', 'expand_to', 'extent', 'from_Interval', 'from_list', + 'intersects', 'is_singular', 'max', 'middle', 'min', 'set_max', + 'set_min', 'union_with']) + + if name in Interval_methods: + return self._get_Interval_method(name) + else: + raise AttributeError("OptIntInterval instance has no attribute \"{}\"".format(name)) + + def _wrap_Interval_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("OptIntInterval is empty.") + else: + return self.Interval.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __neg__(self): + """Negate interval's endpoints.""" + return self._wrap_Interval_method("__sub__") + + + def __add__(cy_Interval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + return self._wrap_Interval_method("__add__", other) + + def __sub__(cy_Interval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + return self._wrap_Interval_method("__sub__", other) + +cdef cy_OptIntInterval wrap_OptIntInterval(OptIntInterval p): + cdef OptIntInterval * retp = new OptIntInterval() + retp[0] = p + cdef cy_OptIntInterval r = cy_OptIntInterval.__new__(cy_OptIntInterval) + r.thisptr = retp + return r + + +cdef class cy_GenericRect: + + """Class representing axis aligned rectangle, with arbitrary corners. + + Plane in which the rectangle lies can have any object as a coordinates, + as long as they implement arithmetic operations and comparison. + + This is a bit experimental, corresponds to GenericRect[C] templated + with (wrapped) python object. + """ + + cdef GenericRect[WrappedPyObject]* thisptr + + def __cinit__(self, x0=0, y0=0, x1=0, y1=0): + """Create rectangle from it's top-left and bottom-right corners.""" + self.thisptr = new GenericRect[WrappedPyObject](WrappedPyObject(x0), + WrappedPyObject(y0), + WrappedPyObject(x1), + WrappedPyObject(y1)) + + def __str__(self): + """str(self)""" + return "Rectangle with dimensions {}, topleft point {}".format( + str(self.dimensions()), + str(self.min())) + + def __repr__(self): + """repr(self)""" + return "Rect({}, {}, {}, {})".format( str(self.left()), + str(self.top()), + str(self.right()), + str(self.bottom()) ) + + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_intervals(self, I, J): + """Create rectangle from two intervals. + + First interval corresponds to side parallel with x-axis, + second one with side parallel with y-axis.""" + return cy_GenericRect(I.min(), I.max(), J.min(), J.max()) + + @classmethod + def from_list(cls, lst): + """Create rectangle containing all points in list. + + These points are represented simply by 2-tuples. + """ + ret = cy_GenericRect() + for a in lst: + ret.expand_to(a) + return ret + + @classmethod + def from_xywh(cls, x, y, w, h): + """Create rectangle from topleft point and dimensions.""" + return wrap_GenericRect( from_xywh(WrappedPyObject(x), + WrappedPyObject(y), + WrappedPyObject(w), + WrappedPyObject(h))) + + def __getitem__(self, Dim2 d): + """self[i]""" + return wrap_GenericInterval( deref(self.thisptr)[d] ) + + def min(self): + """Get top-left point.""" + return wrap_PyPoint( self.thisptr.min() ) + + def max(self): + """Get bottom-right point.""" + return wrap_PyPoint( self.thisptr.max() ) + + def corner(self, unsigned int i): + """Get corners (modulo) indexed from 0 to 3.""" + return wrap_PyPoint( self.thisptr.corner(i) ) + + def top(self): + """Get top coordinate.""" + return self.thisptr.top().getObj() + + def bottom(self): + """Get bottom coordinate.""" + return self.thisptr.bottom().getObj() + + def left(self): + """Get left coordinate.""" + return self.thisptr.left().getObj() + + def right(self): + """Get right coordinate.""" + return self.thisptr.right().getObj() + + def width(self): + """Get width.""" + return self.thisptr.width().getObj() + + def height(self): + """Get height.""" + return self.thisptr.height().getObj() + + #For some reason, Cpp aspectRatio returns Coord. + def aspectRatio(self): + """Get ratio between width and height.""" + return float(self.width())/float(self.height()) + + def dimensions(self): + """Get dimensions as tuple.""" + return wrap_PyPoint( self.thisptr.dimensions() ) + + def midpoint(self): + """Get midpoint as tuple.""" + return wrap_PyPoint( self.thisptr.midpoint() ) + + def area(self): + """Get area.""" + return self.thisptr.area().getObj() + + def has_zero_area(self): + """Test for area being zero.""" + return self.thisptr.hasZeroArea() + + def max_extent(self): + """Get bigger value from width, height.""" + return self.thisptr.maxExtent().getObj() + + def min_extent(self): + """Get smaller value from width, height.""" + return self.thisptr.minExtent().getObj() + + def intersects(self, cy_GenericRect r): + """Check if rectangle intersects another rectangle.""" + return self.thisptr.intersects(deref( r.thisptr )) + + def contains(self, r): + """Check if rectangle contains point represented as tuple.""" + if not isinstance(r, tuple): + raise TypeError("Tuple required to create point.") + return self.thisptr.contains( make_PyPoint(r) ) + + def contains_rect(self, cy_GenericRect r): + """Check if rectangle contains another rect.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def set_left(self, val): + """Set left coordinate.""" + self.thisptr.setLeft( WrappedPyObject(val) ) + + def set_right(self, val): + """Set right coordinate.""" + self.thisptr.setRight( WrappedPyObject(val) ) + + def set_top(self, val): + """Set top coordinate.""" + self.thisptr.setTop( WrappedPyObject(val) ) + + def set_bottom(self, val): + """Set bottom coordinate.""" + self.thisptr.setBottom( WrappedPyObject(val) ) + + def set_min(self, p): + """Set top-left point.""" + self.thisptr.setMin(make_PyPoint(p)) + + def set_max(self, p): + """Set bottom-right point.""" + self.thisptr.setMax(make_PyPoint(p)) + + def expand_to(self, p): + """Expand rectangle to contain point represented as tuple.""" + self.thisptr.expandTo(make_PyPoint(p)) + + def union_with(self, cy_GenericRect b): + """self = self | other.""" + self.thisptr.unionWith(deref( b.thisptr )) + + def expand_by(self, x, y = None): + """Expand both intervals. + + Either expand them both by one value, or each by different value. + """ + if y is None: + self.thisptr.expandBy(WrappedPyObject(x)) + else: + self.thisptr.expandBy(WrappedPyObject(x), + WrappedPyObject(y)) + + + def __add__(cy_GenericRect self, p): + """Offset rectangle by point.""" + return wrap_GenericRect( deref(self.thisptr) + make_PyPoint(p) ) + + def __sub__(cy_GenericRect self, p): + """Offset rectangle by -point.""" + return wrap_GenericRect( deref(self.thisptr) - make_PyPoint(p) ) + + def __or__(cy_GenericRect self, cy_GenericRect o): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_GenericRect( deref(self.thisptr) | deref( o.thisptr )) + + def __richcmp__(cy_GenericRect self, cy_GenericRect o, int op): + """Rectangles are not ordered.""" + if op == 2: + return deref(self.thisptr) == deref(o.thisptr) + if op == 3: + return deref(self.thisptr) != deref(o.thisptr) + +cdef PyPoint make_PyPoint(p): + return PyPoint( WrappedPyObject(p[0]), WrappedPyObject(p[1]) ) + +#D2[WrappedPyObject] is converted to tuple +cdef wrap_PyPoint(PyPoint p): + return (p[0].getObj(), p[1].getObj()) + +cdef cy_GenericRect wrap_GenericRect(GenericRect[WrappedPyObject] p): + cdef WrappedPyObject zero = WrappedPyObject(0) + cdef GenericRect[WrappedPyObject] * retp = new GenericRect[WrappedPyObject](zero, zero, zero, zero) + retp[0] = p + cdef cy_GenericRect r = cy_GenericRect.__new__(cy_GenericRect) + r.thisptr = retp + return r + + +cdef class cy_Rect: + + """Class representing axis-aligned rectangle in 2D real plane. + + Corresponds to Rect class in 2geom.""" + + def __cinit__(self, Coord x0=0, Coord y0=0, Coord x1=0, Coord y1=0): + """Create Rect from coordinates of its top-left and bottom-right corners.""" + self.thisptr = new Rect(x0, y0, x1, y1) + + def __str__(self): + """str(self)""" + return "Rectangle with dimensions {}, topleft point {}".format(str(self.dimensions()), str(self.min())) + + def __repr__(self): + """repr(self)""" + return "Rect({}, {}, {}, {})".format( str(self.left()), + str(self.top()), + str(self.right()), + str(self.bottom())) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_Point p0, cy_Point p1): + """Create rectangle from it's top-left and bottom-right corners.""" + return wrap_Rect( Rect(deref(p0.thisptr), deref(p1.thisptr)) ) + + @classmethod + def from_intervals(cls, I, J): + """Create rectangle from two intervals representing its sides.""" + return wrap_Rect( Rect( float(I.min()), + float(J.min()), + float(I.max()), + float(J.max()) ) ) + + @classmethod + def from_list(cls, lst): + """Create rectangle containing all points in list.""" + if lst == []: + return cy_Rect() + if len(lst) == 1: + return cy_Rect.from_points(lst[0], lst[0]) + ret = cy_Rect.from_points(lst[0], lst[1]) + for a in lst: + ret.expand_to(a) + return ret + + @classmethod + def from_xywh(cls, x, y, w, h): + """Create rectangle from it's topleft point and dimensions.""" + return wrap_Rect( from_xywh(<Coord> x, + <Coord> y, + <Coord> w, + <Coord> h) ) + + @classmethod + def infinite(self): + """Create infinite rectangle.""" + return wrap_Rect(infinite()) + + def __getitem__(self, Dim2 d): + """self[d]""" + return wrap_Interval( deref(self.thisptr)[d] ) + + def min(self): + """Get top-left point.""" + return wrap_Point( self.thisptr.min() ) + + def max(self): + """Get bottom-right point.""" + return wrap_Point( self.thisptr.max() ) + + def corner(self, unsigned int i): + """Get corners (modulo) indexed from 0 to 3.""" + return wrap_Point( self.thisptr.corner(i) ) + + def top(self): + """Get top coordinate.""" + return self.thisptr.top() + + def bottom(self): + """Get bottom coordinate.""" + return self.thisptr.bottom() + + def left(self): + """Get left coordinate.""" + return self.thisptr.left() + + def right(self): + """Get right coordinate.""" + return self.thisptr.right() + + def width(self): + """Get width.""" + return self.thisptr.width() + + def height(self): + """Get height.""" + return self.thisptr.height() + + def aspect_ratio(self): + """Get ratio between width and height.""" + return self.thisptr.aspectRatio() + + def dimensions(self): + """Get dimensions as point.""" + return wrap_Point( self.thisptr.dimensions() ) + + def midpoint(self): + """Get midpoint.""" + return wrap_Point( self.thisptr.midpoint() ) + + def area(self): + """Get area.""" + return self.thisptr.area() + + def has_zero_area(self, Coord eps = EPSILON): + """Test for area being zero.""" + return self.thisptr.hasZeroArea(eps) + + def max_extent(self): + """Get bigger value from width, height.""" + return self.thisptr.maxExtent() + + def min_extent(self): + """Get smaller value from width, height.""" + return self.thisptr.minExtent() + + def intersects(self, cy_Rect r): + """Check if rectangle intersects another rectangle.""" + return self.thisptr.intersects(deref( r.thisptr )) + + def contains(self, cy_Point r): + """Check if rectangle contains point.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def contains_rect(self, cy_Rect r): + """Check if rectangle contains another rect.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def interior_intersects(self, cy_Rect r): + """Check if interior of self intersects another rectangle.""" + return self.thisptr.interiorIntersects(deref( r.thisptr )) + + def interior_contains(self, cy_Point other): + """Check if interior of self contains point.""" + return self.thisptr.interiorContains( deref( (<cy_Point> other).thisptr ) ) + + def interior_contains_rect(self, other): + """Check if interior of self contains another rectangle.""" + if isinstance(other, cy_Rect): + return self.thisptr.interiorContains( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + return self.thisptr.interiorContains( deref( (<cy_OptRect> other).thisptr ) ) + + def set_left(self, Coord val): + """Set left coordinate.""" + self.thisptr.setLeft(val) + + def set_right(self, Coord val): + """Set right coordinate.""" + self.thisptr.setRight(val) + + def set_top(self, Coord val): + """Set top coordinate.""" + self.thisptr.setTop(val) + + def set_bottom(self, Coord val): + """Set bottom coordinate.""" + self.thisptr.setBottom(val) + + def set_min(self, cy_Point p): + """Set top-left point.""" + self.thisptr.setMin( deref( p.thisptr ) ) + + def set_max(self, cy_Point p): + """Set bottom-right point.""" + self.thisptr.setMax( deref( p.thisptr )) + + def expand_to(self, cy_Point p): + """Expand rectangle to contain point represented as tuple.""" + self.thisptr.expandTo( deref( p.thisptr ) ) + + def union_with(self, cy_Rect b): + """self = self | other.""" + self.thisptr.unionWith(deref( b.thisptr )) + + def expand_by(cy_Rect self, x, y = None): + """Expand both intervals. + + Either expand them both by one value, or each by different value. + """ + if y is None: + if isinstance(x, cy_Point): + self.thisptr.expandBy( deref( (<cy_Point> x).thisptr ) ) + else: + self.thisptr.expandBy( <Coord> x) + else: + self.thisptr.expandBy( <Coord> x, + <Coord> y) + + def __add__(cy_Rect self, cy_Point p): + """Offset rectangle by point.""" + return wrap_Rect( deref(self.thisptr) + deref( p.thisptr ) ) + + def __sub__(cy_Rect self, cy_Point p): + """Offset rectangle by -point.""" + return wrap_Rect( deref(self.thisptr) - deref( p.thisptr ) ) + + def __mul__(cy_Rect self, t): + """Apply transform to rectangle.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_Rect( deref(self.thisptr) * at ) + + def __or__(cy_Rect self, cy_Rect o): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_Rect( deref(self.thisptr) | deref( o.thisptr )) + + def __richcmp__(cy_Rect self, o, int op): + """Rectangles are not ordered.""" + if op == 2: + if isinstance(o, cy_Rect): + return deref(self.thisptr) == deref( (<cy_Rect> o).thisptr) + elif isinstance(o, cy_IntRect): + return deref(self.thisptr) == deref( (<cy_IntRect> o).thisptr) + if op == 3: + if isinstance(o, cy_Rect): + return deref(self.thisptr) != deref( (<cy_Rect> o).thisptr) + elif isinstance(o, cy_IntRect): + return deref(self.thisptr) != deref( (<cy_IntRect> o).thisptr) + + def round_inwards(self): + """Create OptIntRect rounding inwards.""" + return wrap_OptIntRect(self.thisptr.roundInwards()) + + def round_outwards(self): + """Create IntRect rounding outwards.""" + return wrap_IntRect(self.thisptr.roundOutwards()) + + @classmethod + def distanceSq(cls, cy_Point p, cy_Rect rect): + """Compute square of distance between point and rectangle.""" + return distanceSq( deref(p.thisptr), deref(rect.thisptr) ) + + @classmethod + def distance(cls, cy_Point p, cy_Rect rect): + """Compute distance between point and rectangle.""" + return distance( deref(p.thisptr), deref(rect.thisptr) ) + +cdef cy_Rect wrap_Rect(Rect p): + cdef Rect* retp = new Rect() + retp[0] = p + cdef cy_Rect r = cy_Rect.__new__(cy_Rect) + r.thisptr = retp + return r + + +cdef class cy_OptRect: + + """Class representing optionally empty rect in real plane. + + This class corresponds to OptRect in 2geom, and it tries to mimic + the behaviour of std::optional. In addition to OptRect methods, + this class passes calls for Rect methods to underlying Rect class, + or throws ValueError when it's empty. + """ + + def __cinit__(self, x0=None, y0=None, x1=None, y1=None): + """Create OptRect from coordinates of top-left and bottom-right corners. + + No arguments will result in empty rectangle. + """ + if x0 is None: + self.thisptr = new OptRect() + else: + self.thisptr = new OptRect( float(x0), + float(y0), + float(x1), + float(y1) ) + + def __str__(self): + """str(self)""" + if self.is_empty(): + return "Empty OptRect." + return "OptRect with dimensions {}, topleft point {}".format(str(self.dimensions()), str(self.min())) + + def __repr__(self): + """repr(self)""" + if self.is_empty(): + return "OptRect()" + return "OptRect({}, {}, {}, {})".format( str(self.left()), + str(self.top()), + str(self.right()), + str(self.bottom())) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_Point p0, cy_Point p1): + """Create rectangle from it's top-left and bottom-right corners.""" + return wrap_OptRect( OptRect(deref(p0.thisptr), deref(p1.thisptr)) ) + + @classmethod + def from_intervals(cls, I, J): + """Create rectangle from two intervals representing its sides.""" + if hasattr(I, "isEmpty"): + if I.isEmpty(): + return cy_OptRect() + + if hasattr(J, "isEmpty"): + if J.isEmpty(): + return cy_OptRect() + + return wrap_OptRect( OptRect( float(I.min()), + float(J.min()), + float(I.max()), + float(J.max()) ) ) + + @classmethod + def from_rect(cls, r): + """Create OptRect from other, possibly empty, rectangle.""" + if hasattr(r, "isEmpty"): + if r.isEmpty(): + return cy_OptRect() + + return cy_OptRect( r.min().x, + r.min().y, + r.max().x, + r.max().y ) + + @classmethod + def from_list(cls, lst): + """Create OptRect containing all points in the list. + + Empty list will result in empty OptRect. + """ + if lst == []: + return cy_OptRect() + if len(lst) == 1: + return cy_OptRect.from_points(lst[0], lst[0]) + ret = cy_OptRect.from_points(lst[0], lst[1]) + for a in lst: + ret.expand_to(a) + return ret + + property Rect: + """Get underlying Rect.""" + def __get__(self): + if self.is_empty(): + raise ValueError("Rect is empty.") + else: + return wrap_Rect(self.thisptr.get()) + + def __bool__(self): + """OptRect is False only when it's empty.""" + return not self.thisptr.isEmpty() + + def is_empty(self): + """Check for OptRect containing no points.""" + return self.thisptr.isEmpty() + + def intersects(self, other): + """Check if rectangle intersects another rectangle.""" + if isinstance(other, cy_Rect): + return self.thisptr.intersects( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + return self.thisptr.intersects( deref( (<cy_OptRect> other).thisptr ) ) + + def contains(self, cy_Point r): + """Check if rectangle contains point.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def contains_rect(self, other): + """Check if rectangle contains another rect.""" + if isinstance(other, cy_Rect): + return self.thisptr.contains( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + return self.thisptr.contains( deref( (<cy_OptRect> other).thisptr ) ) + + def union_with(self, other): + """self = self | other.""" + if isinstance(other, cy_Rect): + self.thisptr.unionWith( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + self.thisptr.unionWith( deref( (<cy_OptRect> other).thisptr ) ) + + def intersect_with(self, other): + """self = self & other.""" + if isinstance(other, cy_Rect): + self.thisptr.intersectWith( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + self.thisptr.intersectWith( deref( (<cy_OptRect> other).thisptr ) ) + + def expand_to(self, cy_Point p): + """Expand rectangle to contain point represented as tuple.""" + self.thisptr.expandTo( deref(p.thisptr) ) + + def __or__(cy_OptRect self, cy_OptRect other): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_OptRect( deref(self.thisptr) | deref(other.thisptr) ) + + def __and__(cy_OptRect self, other): + """Return intersection of two rectangles.""" + if isinstance(other, cy_Rect): + return wrap_OptRect( deref(self.thisptr) & deref( (<cy_Rect> other).thisptr) ) + elif isinstance(other, cy_OptRect): + return wrap_OptRect( deref(self.thisptr) & deref( (<cy_OptRect> other).thisptr) ) + + def __richcmp__(cy_OptRect self, other, op): + """Rectangles are not ordered.""" + if op == 2: + if isinstance(other, cy_Rect): + return deref(self.thisptr) == deref( (<cy_Rect> other).thisptr ) + elif isinstance(other, cy_OptRect): + return deref(self.thisptr) == deref( (<cy_OptRect> other).thisptr ) + elif op == 3: + if isinstance(other, cy_Rect): + return deref(self.thisptr) != deref( (<cy_Rect> other).thisptr ) + elif isinstance(other, cy_OptRect): + return deref(self.thisptr) != deref( (<cy_OptRect> other).thisptr ) + return NotImplemented + + def _get_Rect_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("OptRect is empty.") + else: + return self.Rect.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + Rect_methods = set(['area', 'aspectRatio', 'bottom', 'contains', + 'contains_rect', 'corner', 'dimensions', 'distance', 'distanceSq', + 'expand_by', 'expand_to', 'has_zero_area', 'height', 'infinite', + 'interior_contains', 'interior_contains_rect', + 'interior_intersects', 'intersects', 'left', 'max', 'max_extent', + 'midpoint', 'min', 'min_extent', 'right', 'round_inwards', + 'round_outwards', 'set_bottom', 'set_left', 'set_max', 'set_min', + 'set_right', 'set_top', 'top', 'union_with', 'width']) + + if name in Rect_methods: + return self._get_Rect_method(name) + else: + raise AttributeError("OptRect instance has no attribute \"{}\"".format(name)) + + def _wrap_Rect_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("OptRect is empty.") + else: + return self.Rect.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __getitem__(self, i): + """self[d]""" + return self._wrap_Rect_method("__getitem__", i) + + def __add__(self, other): + """Offset rectangle by point.""" + return self._wrap_Rect_method("__add__", other) + + def __mul__(self, other): + """Apply transform to rectangle.""" + return self._wrap_Rect_method("__mul__", other) + + def __sub__(self, other): + """Offset rectangle by -point.""" + return self._wrap_Rect_method("__sub__", other) + + +cdef cy_OptRect wrap_OptRect(OptRect p): + cdef OptRect* retp = new OptRect() + retp[0] = p + cdef cy_OptRect r = cy_OptRect.__new__(cy_OptRect) + r.thisptr = retp + return r + + + +cdef class cy_IntRect: + + """Class representing axis-aligned rectangle in 2D with integer coordinates. + + Corresponds to IntRect class (typedef) in 2geom.""" + + cdef IntRect* thisptr + + def __cinit__(self, IntCoord x0=0, IntCoord y0=0, IntCoord x1=0, IntCoord y1=0): + """Create IntRect from coordinates of its top-left and bottom-right corners.""" + self.thisptr = new IntRect(x0, y0, x1, y1) + + def __str__(self): + """str(self)""" + return "IntRect with dimensions {}, topleft point {}".format( + str(self.dimensions()), + str(self.min())) + + def __repr__(self): + """repr(self)""" + return "IntRect({}, {}, {}, {})".format( str(self.left()), + str(self.top()), + str(self.right()), + str(self.bottom())) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_IntPoint p0, cy_IntPoint p1): + """Create rectangle from it's top-left and bottom-right corners.""" + return wrap_IntRect( IntRect(deref(p0.thisptr), deref(p1.thisptr)) ) + + @classmethod + def from_intervals(cls, I, J): + """Create rectangle from two intervals representing its sides.""" + return cy_IntRect( int(I.min()), + int(J.min()), + int(I.max()), + int(J.max()) ) + + @classmethod + def from_list(cls, lst): + """Create rectangle containing all points in list.""" + if lst == []: + return cy_IntRect() + if len(lst) == 1: + return cy_IntRect(lst[0], lst[0]) + ret = cy_IntRect(lst[0], lst[1]) + for a in lst: + ret.expand_to(a) + return ret + + #didn't manage to declare from_xywh for IntRect + @classmethod + def from_xywh(cls, x, y, w, h): + """Create rectangle from it's topleft point and dimensions.""" + return cy_IntRect(int(x), + int(y), + int(x) + int(w), + int(y) + int(h) ) + + def __getitem__(self, Dim2 d): + """self[d]""" + return wrap_IntInterval( deref(self.thisptr)[d] ) + + def min(self): + """Get top-left point.""" + return wrap_IntPoint( self.thisptr.i_min() ) + + def max(self): + """Get bottom-right point.""" + return wrap_IntPoint( self.thisptr.i_max() ) + + def corner(self, unsigned int i): + """Get corners (modulo) indexed from 0 to 3.""" + return wrap_IntPoint( self.thisptr.i_corner(i) ) + + def top(self): + """Get top coordinate.""" + return self.thisptr.top() + + def bottom(self): + """Get bottom coordinate.""" + return self.thisptr.bottom() + + def left(self): + """Get left coordinate.""" + return self.thisptr.left() + + def right(self): + """Get right coordinate.""" + return self.thisptr.right() + + def width(self): + """Get width.""" + return self.thisptr.width() + + def height(self): + """Get height.""" + return self.thisptr.height() + + def aspect_ratio(self): + """Get ratio between width and height.""" + return self.thisptr.aspectRatio() + + def dimensions(self): + """Get dimensions as IntPoint.""" + return wrap_IntPoint( self.thisptr.i_dimensions() ) + + def midpoint(self): + """Get midpoint.""" + return wrap_IntPoint( self.thisptr.i_midpoint() ) + + def area(self): + """Get area.""" + return self.thisptr.area() + + def has_zero_area(self): + """Test for area being zero.""" + return self.thisptr.hasZeroArea() + + def max_extent(self): + """Get bigger value from width, height.""" + return self.thisptr.maxExtent() + + def min_extent(self): + """Get smaller value from width, height.""" + return self.thisptr.minExtent() + + def intersects(self, cy_IntRect r): + """Check if rectangle intersects another rectangle.""" + return self.thisptr.intersects(deref( r.thisptr )) + + def contains(self, cy_IntPoint r): + """Check if rectangle contains point.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def contains_rect(self, cy_IntRect r): + """Check if rectangle contains another rect.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def set_left(self, IntCoord val): + """Set left coordinate.""" + self.thisptr.setLeft(val) + + def set_right(self, IntCoord val): + """Set right coordinate.""" + self.thisptr.setRight(val) + + def set_top(self, IntCoord val): + """Set top coordinate.""" + self.thisptr.setTop(val) + + def set_bottom(self, IntCoord val): + """Set bottom coordinate.""" + self.thisptr.setBottom(val) + + def set_min(self, cy_IntPoint p): + """Set top-left point.""" + self.thisptr.setMin( deref( p.thisptr ) ) + + def set_max(self, cy_IntPoint p): + """Set bottom-right point.""" + self.thisptr.setMax( deref( p.thisptr )) + + def expand_to(self, cy_IntPoint p): + """Expand rectangle to contain point represented as tuple.""" + self.thisptr.expandTo( deref( p.thisptr ) ) + + def union_with(self, cy_IntRect b): + """self = self | other.""" + self.thisptr.unionWith(deref( b.thisptr )) + + def expand_by(cy_IntRect self, x, y = None): + """Expand both intervals. + + Either expand them both by one value, or each by different value. + """ + if y is None: + if isinstance(x, cy_IntPoint): + self.thisptr.expandBy( deref( (<cy_IntPoint> x).thisptr ) ) + else: + self.thisptr.expandBy( <IntCoord> x) + else: + self.thisptr.expandBy( <IntCoord> x, + <IntCoord> y) + + def __add__(cy_IntRect self, cy_IntPoint p): + """Offset rectangle by point.""" + return wrap_IntRect( deref(self.thisptr) + deref( p.thisptr ) ) + + def __sub__(cy_IntRect self, cy_IntPoint p): + """Offset rectangle by -point.""" + return wrap_IntRect( deref(self.thisptr) - deref( p.thisptr ) ) + + def __or__(cy_IntRect self, cy_IntRect o): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_IntRect( deref(self.thisptr) | deref( o.thisptr )) + + def __richcmp__(cy_IntRect self, cy_IntRect o, int op): + """Rectangles are not ordered.""" + if op == 2: + return deref(self.thisptr) == deref(o.thisptr) + if op == 3: + return deref(self.thisptr) != deref(o.thisptr) + +cdef cy_IntRect wrap_IntRect(IntRect p): + cdef IntRect* retp = new IntRect() + retp[0] = p + cdef cy_IntRect r = cy_IntRect.__new__(cy_IntRect) + r.thisptr = retp + return r + + + +cdef class cy_OptIntRect: + + """Class representing optionally empty rect in with integer coordinates. + + This class corresponds to OptIntRect in 2geom, and it tries to mimic + the behaviour of std::optional. In addition to OptIntRect methods, + this class passes calls for IntRect methods to underlying IntRect class, + or throws ValueError when it's empty. + """ + + cdef OptIntRect* thisptr + + def __cinit__(self, x0=None, y0=None, x1=None, y1=None): + """Create OptIntRect from coordinates of top-left and bottom-right corners. + + No arguments will result in empty rectangle. + """ + if x0 is None: + self.thisptr = new OptIntRect() + else: + self.thisptr = new OptIntRect( int(x0), + int(y0), + int(x1), + int(y1) ) + + def __str__(self): + """str(self)""" + if self.isEmpty(): + return "Empty OptIntRect" + return "OptIntRect with dimensions {}, topleft point {}".format( + str(self.Rect.dimensions()), + str(self.Rect.min())) + + def __repr__(self): + """repr(self)""" + if self.isEmpty(): + return "OptIntRect()" + return "OptIntRect({}, {}, {}, {})".format( str(self.Rect.left()), + str(self.Rect.top()), + str(self.Rect.right()), + str(self.Rect.bottom())) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_IntPoint p0, cy_IntPoint p1): + """Create rectangle from it's top-left and bottom-right corners.""" + return wrap_OptIntRect( OptIntRect(deref(p0.thisptr), deref(p1.thisptr)) ) + + @classmethod + def from_intervals(cls, I, J): + """Create rectangle from two intervals representing its sides.""" + if hasattr(I, "isEmpty"): + if I.isEmpty(): + return cy_OptIntRect() + + if hasattr(J, "isEmpty"): + if J.isEmpty(): + return cy_OptIntRect() + + return wrap_OptIntRect( OptIntRect( int(I.min()), + int(J.min()), + int(I.max()), + int(J.max()) ) ) + + @classmethod + def from_rect(cls, r): + """Create OptIntRect from other, possibly empty, rectangle.""" + if hasattr(r, "isEmpty"): + if r.isEmpty(): + return cy_OptIntRect() + return cy_OptIntRect( r.min().x, + r.min().y, + r.max().x, + r.max().y ) + + @classmethod + def from_list(cls, lst): + """Create OptIntRect containing all points in the list. + + Empty list will result in empty OptIntRect. + """ + if lst == []: + return cy_OptIntRect() + if len(lst) == 1: + return cy_OptIntRect.from_points(lst[0], lst[0]) + ret = cy_OptIntRect.from_points(lst[0], lst[1]) + for a in lst: + ret.expand_to(a) + return ret + + property Rect: + """Get underlying IntRect.""" + def __get__(self): + return wrap_IntRect(self.thisptr.get()) + + def __bool__(self): + """OptIntRect is False only when it's empty.""" + return not self.thisptr.isEmpty() + + def is_empty(self): + """Check for OptIntRect containing no points.""" + return self.thisptr.isEmpty() + + def intersects(cy_OptIntRect self, other): + """Check if rectangle intersects another rectangle.""" + if isinstance(other, cy_IntRect): + return self.thisptr.intersects( deref( (<cy_IntRect> other).thisptr ) ) + elif isinstance(other, cy_OptIntRect): + return self.thisptr.intersects( deref( (<cy_OptIntRect> other).thisptr ) ) + + def contains(self, cy_IntPoint other): + """Check if rectangle contains point.""" + return self.thisptr.contains( deref(other.thisptr) ) + + def contains_rect(cy_OptIntRect self, other): + """Check if rectangle contains another rectangle.""" + if isinstance(other, cy_IntRect): + return self.thisptr.contains( deref( (<cy_IntRect> other).thisptr ) ) + elif isinstance(other, cy_OptIntRect): + return self.thisptr.contains( deref( (<cy_OptIntRect> other).thisptr ) ) + + + def union_with(cy_OptIntRect self, other): + """self = self | other.""" + if isinstance(other, cy_IntRect): + self.thisptr.unionWith( deref( (<cy_IntRect> other).thisptr ) ) + elif isinstance(other, cy_OptIntRect): + self.thisptr.unionWith( deref( (<cy_OptIntRect> other).thisptr ) ) + + def intersect_with(cy_OptIntRect self, other): + """self = self & other.""" + if isinstance(other, cy_IntRect): + self.thisptr.intersectWith( deref( (<cy_IntRect> other).thisptr ) ) + elif isinstance(other, cy_OptIntRect): + self.thisptr.intersectWith( deref( (<cy_OptIntRect> other).thisptr ) ) + + def expand_to(self, cy_IntPoint p): + """Expand rectangle to contain point.""" + self.thisptr.expandTo( deref(p.thisptr) ) + + def __or__(cy_OptIntRect self, cy_OptIntRect other): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_OptIntRect( deref(self.thisptr) | deref(other.thisptr) ) + + def __and__(cy_OptIntRect self, other): + """Return intersection of two rectangles.""" + if isinstance(other, cy_IntRect): + return wrap_OptIntRect( deref(self.thisptr) & deref( (<cy_IntRect> other).thisptr) ) + elif isinstance(other, cy_OptIntRect): + return wrap_OptIntRect( deref(self.thisptr) & deref( (<cy_OptIntRect> other).thisptr) ) + + def __richcmp__(cy_OptIntRect self, other, op): + """Rectangles are not ordered.""" + if op == 2: + if isinstance(other, cy_IntRect): + return deref(self.thisptr) == deref( (<cy_IntRect> other).thisptr ) + elif isinstance(other, cy_OptIntRect): + return deref(self.thisptr) == deref( (<cy_OptIntRect> other).thisptr ) + elif op == 3: + if isinstance(other, cy_IntRect): + return deref(self.thisptr) != deref( (<cy_IntRect> other).thisptr ) + elif isinstance(other, cy_OptIntRect): + return deref(self.thisptr) != deref( (<cy_OptIntRect> other).thisptr ) + + def _get_Rect_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("OptIntRect is empty.") + else: + return self.Rect.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + + Rect_methods = set(['area', 'aspect_ratio', 'bottom', 'contains', + 'contains_rect', 'corner', 'dimensions', 'expand_by', 'expand_to', + 'from_intervals', 'from_list', 'from_points', 'from_xywh', + 'has_zero_area', 'height', 'intersects', 'left', 'max', + 'max_extent', 'midpoint', 'min', 'min_extent', 'right', + 'set_bottom', 'set_left', 'set_max', 'set_min', 'set_right', + 'set_top', 'top', 'union_with', 'width']) + + if name in Rect_methods: + return self._get_Rect_method(name) + else: + raise AttributeError("OptIntRect instance has no attribute \"{}\"".format(name)) + + def _wrap_Rect_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("OptIntRect is empty.") + else: + return self.Rect.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __getitem__(self, i): + """self[d]""" + return self._wrap_Rect_method("__getitem__", i) + + def __add__(self, other): + """Offset rectangle by point.""" + return self._wrap_Rect_method("__add__", other) + + def __sub__(self, other): + """Offset rectangle by -point.""" + return self._wrap_Rect_method("__sub__", other) + +cdef cy_OptIntRect wrap_OptIntRect(OptIntRect p): + cdef OptIntRect* retp = new OptIntRect() + retp[0] = p + cdef cy_OptIntRect r = cy_OptIntRect.__new__(cy_OptIntRect) + r.thisptr = retp + return r diff --git a/src/cython/cy2geom.pyx b/src/cython/cy2geom.pyx new file mode 100644 index 0000000..f1ba7a6 --- /dev/null +++ b/src/cython/cy2geom.pyx @@ -0,0 +1,71 @@ +#Axis specifiers for Dim2 +X = 0 +Y = 1 + +from _cy_primitives import cy_Angle as Angle +from _cy_primitives import cy_AngleInterval as AngleInterval +from _cy_primitives import cy_Point as Point +from _cy_primitives import cy_Line as Line +from _cy_primitives import cy_Ray as Ray +from _cy_primitives import cy_IntPoint as IntPoint + + +from _cy_rectangle import cy_Interval as Interval +from _cy_rectangle import cy_OptInterval as OptInterval +from _cy_rectangle import cy_IntInterval as IntInterval +from _cy_rectangle import cy_OptIntInterval as OptIntInterval + +from _cy_rectangle import cy_GenericInterval as GenericInterval +from _cy_rectangle import cy_GenericOptInterval as GenericOptInterval + +from _cy_rectangle import cy_GenericRect as GenericRect + +from _cy_rectangle import cy_Rect as Rect +from _cy_rectangle import cy_OptRect as OptRect +from _cy_rectangle import cy_IntRect as IntRect +from _cy_rectangle import cy_OptIntRect as OptIntRect + + +from _cy_affine import cy_Affine as Affine +from _cy_affine import cy_Translate as Translate +from _cy_affine import cy_Rotate as Rotate +from _cy_affine import cy_VShear as VShear +from _cy_affine import cy_HShear as HShear +from _cy_affine import cy_Scale as Scale +from _cy_affine import cy_Zoom as Zoom + +from _cy_affine import cy_Eigen as Eigen + +from _cy_curves import cy_Curve as Curve + +from _cy_curves import cy_Linear as Linear +from _cy_curves import cy_SBasis as SBasis +from _cy_curves import cy_SBasisCurve as SBasisCurve + +from _cy_curves import cy_Bezier as Bezier +from _cy_curves import cy_BezierCurve as BezierCurve +from _cy_curves import cy_LineSegment as LineSegment +from _cy_curves import cy_QuadraticBezier as QuadraticBezier +from _cy_curves import cy_CubicBezier as CubicBezier + +from _cy_curves import cy_HLineSegment as HLineSegment +from _cy_curves import cy_VLineSegment as VLineSegment + +from _cy_curves import cy_EllipticalArc as EllipticalArc +#Wrap this? It doesn't fit into python's dynamic nature and +#BezierCurve covers most of it's functionality +#Maybe implement constructors for BezierCurve similar to those +#seen in BezierCurveN +#TODO +#from _cy_curves import cy_BezierCurveN as BezierCurveN + +from _cy_curves import cy_lerp as lerp +from _cy_curves import cy_reverse as reverse +#~ from _cy_curves import cy_level_sets as level_sets + + +from _cy_path import cy_Path as Path + + +from _cy_conicsection import cy_Circle as Circle +from _cy_conicsection import cy_Ellipse as Ellipse diff --git a/src/cython/report.md b/src/cython/report.md new file mode 100644 index 0000000..e1f8353 --- /dev/null +++ b/src/cython/report.md @@ -0,0 +1,237 @@ +# State of cython Bindings + +This report summarizes work done on [lib2geom][2g] cython bindings during +GSoC 2012 mostly from technical standpoint. It should serve for anyone +doing any further work on these bindings, and for me to help my memory. + +Prior to the project, there were bindings using boost.python. These +bindigs, however, uncovered only part of the lib2geom functionality and +were not actively developed. I decided to go for a fresh start using +[cython][cy]. Cython is programming language capable of calling C and C++ +methods and making so-called "extension types", which act as python +classes. Reasons for choosing python include bit of experience I had with +it, its cleanliness and activity in development. + +## Overview of Bindings + +All work was done in [trunk][tr], since it mostly added new functionality +without altering existing code. Whole bindings are located in directory + + 2geom_dir/src/2geom/cython-bindings + +Code is located in files ending with `.pxd` and `.pyx`, roughly cython's +equivalent of header and implementation files. Classes in cython are +divided into logical groups, which are wrapped in corresponding classes. +cy2geom.pyx imports all classes and methods, and it's what one sees after + + >>> import cy2geom + +In addition to this, there are some C++ files, necessary to wrap some of +the code for cython, unit-tests covering most of the functionality and +wrapper.py script, used to skip tedious part of creating extension types. + +Building of bindings was integrated with cmake, which is used to build +the bindings, using [thewext's code][cm]. + +I will now describe files/logical groups of them, their significance and +state. I will try to write down most of the technical non-trivialities that +I encountered. + +Pattern used for wrapping all classes is taken from cython docs and various +other cython bindings. In .pxd file C++ classes, methods and functions are +declared. In corresponding .pyx file there is an extension type which holds +pointer (called `thisptr`) to it's underlying C++ class. Instance of this +class is created with cython's `__cinit__`, and deleted with `__dealloc__`. +Methods of class are called with `self.thisptr.methodname`, which cython +translates to `thisptr->methodname`. Argument are stripped of their python +wrapping using `deref(arg.thisptr)`, or they are translated automatically +(these are basic numerical types). + +There is a function for each class, named `wrap_classname()`, which takes +C++ instance and creates new python object around it. + +## `_common_decl` +These files (.pxd/.pyx) include basic declarations, common to all files. +Functions for creating and wrapping `vector[double]` should become redundant +in next cython release (0.17), since it should do this conversion +automatically. + +All extension types (cdef classes) have prefix `_cy_`, to avoid clash with +C++ classes. This is removed when importing to `_common_decl.pyx`, but is still +somewhat visible to the user of bindings, for example in traces. Other +option would be renaming the C++ classes. + +## `_cy_primitives` +Geometric primitives are wrapped here. These are relatively simple, few +things that are worth mentioning are: + +* Some of the methods for Line and Ray could be rewritten using properties. +* General points about exceptions, docstrings and classmethod apply here. + +## `_cy_rectangle` +These files include intervals and rectangles, which all inherit from +`GenericInterval[C]` or `GenericRect[C]`, respectively. + +For intervals, classes Interval and IntInterval are wrapped, with added +methods for interval too. Opt variants are supported by rewriting +`__getattr__` method. When calling the method that Interval provides, +OptInterval checks whether it's not empty and passes arguments to Interval's +method. If it is empty, it will raise ValueError. Due to this, Interval's +method do not appear in OptInterval's namespace, which might be not ideal. + +GenericInterval is supported by setting the template type to WrappedPyObject, +defined in `wrapped-pyobject.h`. This thin wrapper of `PyObject *` overloads +operators to support arithmetic operations, comparisons and similar. When +adding two WrappedPyObjects, these objects call corresponding addition using +Python's C-API. This is a bit unstable feature, because it still leaks these +python objects, and propagating error from Python, through C-API to cython +back to Python is not done yet correctly. However, constraints on +parameter of GenericInterval's type are pretty tight - it has to support +arithmetic operations within itself, multiplication by reals and has to be +ordered (ordered linear space with multiplication and division between its +elements), so having GenericInterval only with reals and integers covers most +cases. + +For rectangles, situations is almost identical as for intervals. GenericRect, +return 2-tuples as a points, which is a bit of disadvantage, because adding +tuples doesn't add element-wise, but append them one after another. It might +be convenient to implement PyPoint overloading this functionality. + +A bit problematic region is in comparing all the intervals and rectangles. +Should every interval be comparable to every other type, Interval with +OptIntInterval? This would probably require writing the logic in cython. + +GenericOptRect is not there yet, because I got GenericRect only working +only recently. It should be, however, quick to add. + +## `_cy_affine` +Affine and specialised transforms. This is in pretty good state. Eigenvalues +seem to be a bit off sometimes. + +## `_cy_curves` +All of the curves are wrapped here, together with functions like Linear, +SBasis and Bezier. + +Curve implements method every curve should implement, but other curve +classes do not inherit from Curve. This is mainly because of technical +details, this inheritance would only add complexity to code. However, I +might consider rewriting curves, so they would actually inherit from +Curve, if this appears to be needed. + +State of functions and classmethods is a bit strange. I tried to copy +2geom's methods, but there are differences for Beziers and SBasis +polynomials - to name a few, SBasis implements multiplication with SBasis +as a operator, but Bezier only as a function, or bounds are implemented +as instance methods for Linear, but as a module level functions for Bezier +and SBasis. In case I wasn't sure what to do with a method, I put it, as +a classmethod, to corresponding class. + +BezierCurveN is not implemented. It would be major hurdle, as cython has +problems with templated classes - one would have to declare each type +separately. First three orders are wrapped, and BezierCurve, with variable +degree, is also wrapped. + +`hacks.h` is used to go around cython's problems with integer template +parameters. + +## `_cy_path` +Path is wrapped here. Biggest problem with path was it's heavy use of +iterators, which are not the same as python's iterators. In most of the +cases, I replaced them with integers marking the position of iterator, +and go to the position step-by-step. This makes potentially O(1) operations +actually O(n) where n is size of Path. Best way to deal with this would be +to allow iterators to be shifted by arbitrary number. + +Not everything is provided, but there should be enough functionality to +have total control over Path's curves. + +## `_cy_conicsection` +Circle and ellipse classes. ratQuad and xAx from conic-section should also +go there. + +## `utils.py` +Only a simple function to draw curve/path to Tk windows and regular +N-agon creating function reside there, useful mainly for debugging + +## `wrapper.py` +Script used to cut the most trivial part of creating bindings, writing +.pxd declarations, extension type and other functions. It's pretty tailored +into 2geom, but I guess after some work one could make it more generic. + + +## General Issues +Exceptions coming from C++ code are not handled yet, they actually crash +the program. Simplest solution is adding `except +` after every method that +possibly raises exceptions, but traces won't look that nice. + +cython has some kind of problem with docstrings, it doesn't write function's +argument to them, just ellipsis. This can be addressed by specifying all +arguments in the docstring. + +Design decision has been made to put most of function not belonging to any +class to extension type's namespace using classmethods. Most apt extension +type is chosen, distance(Point, Point) goes to Point class. Other options +are making them check for all possible types (this is ugly and error-prone) +or differentiating using different names, which is more or less the same as +classmethods, just not that systematic + +## What's missing +Piecewise missing and all of crossings/boolops stuff are biggest things +that didn't make. Concerning Piecewise, I think best option is to +implement only `Piecewise<SBasis>` and `Piecewise<D2<SBasis>>` as those are +only ones actually useful (SBasis, Bezier and their D2 versions are AFAIK +only classes implementing required concepts). + +Crossings are working, boolops not so much. Work on Shape and Region has +been started. + +# Personal Notes +I am also summarizing a bit on my personal experience with GSoC. + +## What Did I Learn +I learned a lot, not only new technology, but also gained new perspective +on long-time work. + +Speaking of technology, I am much more confident with cython, and I am not +really scared of python C-API. I got a grasp of bazaar, CMake, python's +unittest module and various (mainly template) C++ features, heavily used +in 2geom. + +I got a grasp of what a polished product looks like, and I realize how +hard is to get your product to that state. I can now appreciate testing, +at first seemingly just a boring and not trivial chore, turned out to be +very helpful both for my understanding of code and for making changes more +quickly. I also read a lot about design of python API, what's idiomatic +and what does various norms (PEPs) say about python. + +I think I never did something of this size before (not that monolithic), +so I learned lot about my work habits and importance of both work ethic +and taking a rest. + +## What I Liked & Disliked +I liked mainly the process of learning new things. I disliked the really +technical scope of this project - I can imagine more exciting things to +work on than doing bindings, but I guess it doesn't hurt to have this kind +of experience. I certainly won't spend my whole life doing only exciting +things :) + +## What Would I Do Differently +I would have planned a bit more. I like to write it on account of my +inexperience, I felt many times that knowing how should final product +look like would save me tons of work - this way, I approached state +with which I was more-or-less content at the end of doing most of the tasks. +Luckily, I got time to go through the code at the end, so I ended up +correcting the largest mistakes. + +I would also discuss design of API bit more, since mapping the C++ sometimes +turned out to be cumbersome from python's perspective. Often, I would spend +lots of time thinking through some detail (Iterator for Path, std::optional, +most of the templated classes), only doing ad-hoc solution at the end in +order to I to keep progressing. + +Jan Pulmann - jan.pulmann@gmail.com + +[2g]: http://lib2geom.sourceforge.net/ +[cy]: http://www.cython.org/ +[cm]: https://github.com/thewtex/cython-cmake-example +[tr]: https://code.launchpad.net/~lib2geom-hackers/lib2geom/trunk diff --git a/src/cython/test-affine.py b/src/cython/test-affine.py new file mode 100644 index 0000000..a020d73 --- /dev/null +++ b/src/cython/test-affine.py @@ -0,0 +1,249 @@ +import unittest +from math import pi, sqrt, sin, cos +from random import randint, uniform + +import cy2geom + +from cy2geom import Point, IntPoint +from cy2geom import Line, Ray, Rect + +from cy2geom import Rect + +from cy2geom import Affine +from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom +from cy2geom import Eigen + +class TestPrimitives(unittest.TestCase): + def affine(self, A, B): + c0, c1, c2, c3, c4, c5 = A[0], A[1], A[2], A[3], A[4], A[5] + C = Affine(c0, c1, c2, c3, c4, c5) + self.assertEqual(C, A) + E = Affine.identity() + self.assertEqual(C, C*E) + self.assertEqual(E*B, B) + self.assertEqual(E.det(), 1) + + self.assertAlmostEqual(A.det(), c0*c3-c1*c2) + self.assertAlmostEqual(abs(A.det()), A.descrim2()) + self.assertAlmostEqual(abs(A.det())**0.5, A.descrim()) + #xor + self.assertFalse( A.flips() ^ (A.det() < 0) ) + + if A.is_singular(): + self.assertAlmostEqual(A.det(), 0) + else: + self.assertTrue( Affine.are_near (A*A.inverse(), E) ) + self.assertAlmostEqual(A.det(), 1/A.inverse().det()) + self.assertEqual( A.x_axis(), Point(c0, c1) ) + self.assertEqual( A.y_axis(), Point(c2, c3) ) + self.assertEqual( A.translation(), Point(c4, c5) ) + + self.assertAlmostEqual(A.expansion_X(), A.x_axis().length()) + self.assertAlmostEqual(A.expansion_Y(), A.y_axis().length()) + + if abs(A.expansion_X()) > 1e-7 and abs(A.expansion_Y()) > 1e-7: + A.set_expansion_X(2) + A.set_expansion_Y(3) + self.assertAlmostEqual(A.expansion_X(), 2) + self.assertAlmostEqual(A.expansion_Y(), 3) + + A.set_identity() + + self.assertTrue(A.is_identity()) + self.assertTrue(A.is_translation()) + self.assertFalse(A.is_nonzero_translation()) + self.assertTrue(A.is_scale()) + self.assertTrue(A.is_uniform_scale()) + self.assertFalse(A.is_nonzero_scale()) + self.assertFalse(A.is_nonzero_uniform_scale()) + self.assertTrue(A.is_rotation()) + self.assertFalse(A.is_nonzero_rotation()) + self.assertTrue(A.is_HShear()) + self.assertTrue(A.is_VShear()) + self.assertFalse(A.is_nonzero_HShear()) + self.assertFalse(A.is_nonzero_VShear()) + self.assertTrue(A.is_zoom()) + + self.assertTrue(A.preserves_area() and A.preserves_angles() and A.preserves_distances()) + + self.assertFalse( A.flips() ) + self.assertFalse( A.is_singular() ) + + A.set_X_axis(Point(c0, c1)) + A.set_Y_axis(Point(c2, c3)) + + self.assertEqual(A.without_translation(), A) + + A.set_translation(Point(c4, c5)) + self.assertEqual(C, A) + + self.assertAlmostEqual( (A*B).det(), A.det()*B.det() ) + + self.assertEqual( A.translation(), Point()*A ) + self.assertEqual( Point(1, 1)*A, Point( c0+c2+c4, c1+c3+c5 )) + + l = Line(Point(1, 1), 2) + self.assertEqual( (l.transformed(A)).origin(), l.origin()*A ) + self.assertTrue( Line.are_near( l.point_at(3)*A, l.transformed(A) ) ) + + r = Ray(Point(2, 3), 4) + self.assertEqual( (r.transformed(A)).origin(), r.origin()*A ) + self.assertTrue( Ray.are_near( r.point_at(3)*A, r.transformed(A) ) ) + + + + def test_affine(self): + al = [] + for i in range(10): + al.append(Affine( uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10))) + for A in al: + for B in al: + self.affine(A, B) + + o = Point(2, 4) + v = Point(-1, 1)/sqrt(2) + l = Line.from_origin_and_versor(o, v) + + R = Affine.reflection(v, o) + for i in range(100): + p = Point(randint(0, 100), randint(0, 100)) + self.assertAlmostEqual(Line.distance(p, l), Line.distance(p*R, l)) + self.assertTrue( Affine.are_near( R, R.inverse() ) ) + + self.affine(R, R.inverse()) + + def test_translate(self): + T = Translate() + U = Translate(Point(2, 4)) + V = Translate(1, -9) + + self.assertTrue(Affine(T).is_translation()) + self.assertTrue(Affine(U).is_nonzero_translation()) + + self.assertEqual( (U*V).vector(), U.vector()+V.vector() ) + self.assertEqual( U.inverse().vector(), -U.vector() ) + self.assertEqual(T, Translate.identity()) + self.assertEqual( U.vector(), Point(U[0], U[1]) ) + + self.affine(Affine(V), Affine(U)) + self.affine(Affine(U), Affine(V)) + + r = Rect.from_points( Point(0, 2), Point(4, 8) ) + + self.assertEqual( ( r*(U*V) ).min(), r.min()+U.vector()+V.vector()) + + def test_scale(self): + S = Scale() + T = Scale( Point (3, 8) ) + U = Scale( -3, 1) + V = Scale(sqrt(2)) + + self.assertTrue( Affine(T).is_scale() ) + self.assertTrue( Affine(T).is_nonzero_scale() ) + self.assertTrue( Affine(V).is_nonzero_uniform_scale()) + + self.assertEqual( (T*V).vector(), T.vector()*sqrt(2) ) + self.assertEqual( (T*U)[0], T[0]*U[0] ) + self.assertAlmostEqual( 1/U.inverse()[1], U[1] ) + + r = Rect.from_points( Point(0, 2), Point(4, 8) ) + self.assertAlmostEqual((r*V).area(), 2*r.area()) + self.assertFalse(Affine(U).preserves_area()) + self.assertTrue(Affine(V).preserves_angles()) + + self.affine(Affine(T), Affine(U)) + self.affine(Affine(U), Affine(V)) + self.affine(Affine(V), Affine(T)) + + def test_rotate(self): + R = Rotate() + S = Rotate(pi/3) + T = Rotate(Point( 1, 1 )) + U = Rotate( -1, 1 ) + + self.assertTrue(S.vector(), Point(cos(pi/3), sin(pi/3)) ) + self.assertEqual( Point(T[0], T[1]), T.vector() ) + self.assertTrue( Affine.are_near( Rotate.from_degrees(60), S ) ) + self.assertEqual(R, Rotate.identity()) + self.assertTrue( Point.are_near( ( S * T ).vector(), + Point( cos( pi/3 + pi/4 ), sin( pi/3 + pi/4 ) ) ) ) + + self.affine( Affine(R), Affine(S)) + self.affine( Affine(S), Affine(T)) + self.affine( Affine(T), Affine(U)) + self.affine( Affine(U), Affine(R)) + + def test_shear(self): + H = HShear(2.98) + V = VShear(-sqrt(2)) + + self.assertAlmostEqual(H.factor(), 2.98) + self.assertAlmostEqual(V.inverse().factor(), sqrt(2)) + + G = HShear.identity() + H.set_factor(0) + self.assertEqual(G, H) + + G.set_factor(2) + H.set_factor(4) + self.assertAlmostEqual((G*H).factor(), G.factor()+H.factor()) + + W = VShear.identity() + V.set_factor(0) + self.assertEqual(W, V) + + W.set_factor(-2) + V.set_factor(3) + self.assertAlmostEqual((W*V).factor(), W.factor()+V.factor()) + + def test_zoom(self): + Z = Zoom(3) + Y = Zoom(translate=Translate(3,2)) + X = Zoom(sqrt(3), Translate(-1, 3)) + + self.assertEqual( + Zoom(Z.scale(), Translate(Y.translation())), + Y*Z ) + + Z.set_translation(Y.translation()) + Y.set_scale(Z.scale()) + self.assertEqual(Z, Y) + + self.assertEqual(Y.inverse().scale(), 1/Y.scale()) + + r = Rect.from_xywh( 1, 1, 3, 6) + q = Rect.from_xywh( 0, -1, 1, 2) + W = Zoom.map_rect(r, q) + + self.assertAlmostEqual(W.scale()*r.width(), q.width()) + self.assertTrue(Point.are_near( + r.min()+W.translation(), + q.min())) + def test_eigen(self): + #TODO looks like bug in eigen - (1, 0) should be eigenvector too + #~ S = Scale(1, 2) + #~ E_S = Eigen(S) + #~ print E_S.vectors, E_S.values + #~ print Affine(S) + #~ for i in E_S.vectors: + #~ print i, i*S, Point(1, 0) * S + + B = Affine(-2, 2, 2, 1, 0, 0) + G1 = Eigen(B) + G2 = Eigen( [[-2, 2], [2, 1]] ) + + self.assertAlmostEqual(min(G1.values), min(G2.values)) + self.assertAlmostEqual(max(G1.values), max(G2.values)) + + if Point.are_near( G1.vectors[0]*G1.values[0], G1.vectors[0]*B ): + self.assertTrue( Point.are_near( G1.vectors[1]*G1.values[1], G1.vectors[1]*B ) ) + else: + self.assertTrue( Point.are_near( G1.vectors[1]*G1.values[0], G1.vectors[1]*B ) ) + self.assertTrue( Point.are_near( G1.vectors[0]*G1.values[1], G1.vectors[0]*B ) ) + +unittest.main() diff --git a/src/cython/test-conicsection.py b/src/cython/test-conicsection.py new file mode 100644 index 0000000..9fc1c20 --- /dev/null +++ b/src/cython/test-conicsection.py @@ -0,0 +1,137 @@ +import unittest +import math +from random import randint, uniform + +import cy2geom + +from cy2geom import Point, IntPoint +from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval + +from cy2geom import Affine +from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom +from cy2geom import Eigen + +from cy2geom import Curve +from cy2geom import Linear +from cy2geom import SBasis, SBasisCurve +from cy2geom import Bezier, BezierCurve + +from cy2geom import LineSegment, QuadraticBezier, CubicBezier +from cy2geom import HLineSegment, VLineSegment + +from cy2geom import EllipticalArc + +from cy2geom import Path + +from cy2geom import Circle, Ellipse + + +class TestPrimitives(unittest.TestCase): + def test_circle(self): + C = Circle() + self.assertEqual(C.center(), Point()) + + D = Circle(Point(2, 4), 2) + Dp = D.getPath() + self.assertEqual(D.center(), Point(2, 4)) + self.assertEqual(D.ray(), 2) + + for i in range(11): + t = i/10.0 + #Circle approximated by SBasis is not perfect + self.assertAlmostEqual( abs(D.center()-Dp(t)), D.ray(), delta=0.1 ) + + half_circle = D.arc(Dp(0), Dp(0.3), Dp(0.5)) + + self.assertTrue(half_circle.is_SVG_compliant()) + + self.assertAlmostEqual(Dp(0.25), half_circle(0.5), delta=0.1) + + points = [Point(2, 5), Point(1, 4), Point(9, 0)] + D.set_points(points) + for p in points: + self.assertAlmostEqual( abs(p-D.center()), D.ray() ) + Dc = Circle.from_points(points) + self.assertAlmostEqual(Dc.center(), D.center()) + self.assertAlmostEqual(Dc.ray(), D.ray()) + + coeffs = (2, 4, 1, -4) + E = Circle.from_coefficients(*coeffs) + def param(x, y): + A, B, C, D = coeffs + return A*x**2 + A*y**2 + B*x + C*y + D + Ec = E.arc(E.center()+Point(E.ray(), 0), E.center()-Point(E.ray(), 0), E.center()+Point(E.ray(), 0) ) + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(param(Ec.value_at(t, 0), Ec.value_at(t, 1)), 0) + + E.set(3, 5, 9) + self.assertAlmostEqual(E.center(), Point(3, 5)) + self.assertAlmostEqual(E.ray(), 9) + + E.set_coefficients(*coeffs) + #radius and center from parametric equation + ca = float(coeffs[1])/coeffs[0] + cb = float(coeffs[2])/coeffs[0] + cc = float(coeffs[3])/coeffs[0] + self.assertAlmostEqual( 4*E.ray()**2 , ca**2 + cb**2 -4*cc ) + self.assertAlmostEqual( E.center(), -Point(ca, cb)/2) + + def test_ellipse(self): + #TODO: maybe a bug in arc? get_curve(F) returns different ellipse than F + def get_curve(ellipse): + p = Point(ellipse.ray(0), 0)*Rotate(ellipse.rot_angle()) + return ellipse.arc(ellipse.center()+p, ellipse.center()-p, ellipse.center()+p*(1-1e-7)) + E = Ellipse() + self.assertAlmostEqual(E.center(), Point()) + self.assertAlmostEqual(E.ray(0), 0) + self.assertAlmostEqual(E.ray(1), 0) + + F = Ellipse(Point(), 3, 2, 0) + self.assertAlmostEqual(F.center(), Point()) + self.assertAlmostEqual(F.ray(0), 3) + self.assertAlmostEqual(F.ray(1), 2) + self.assertAlmostEqual(F.rot_angle(), 0) + # x**2/9 + y**2/4 = 1 + self.assertAlmostEqual(F.implicit_form_coefficients()[0], 1/9.0) + self.assertAlmostEqual(F.implicit_form_coefficients()[2], 1/4.0) + self.assertAlmostEqual(F.implicit_form_coefficients()[5], -1) + + coeffs = (1/3.0, 0, 1/16.0, 1, 0, -1/4.0) + G = Ellipse.from_coefficients(*coeffs) + self.assertAlmostEqual(G.center(), Point(-3/2.0, 0)) + self.assertAlmostEqual(G.ray(0), math.sqrt(3)) + self.assertAlmostEqual(G.ray(1), 4) + self.assertAlmostEqual(G.rot_angle(), 0) + + points = [Point(1, 2), Point(2 ,9), Point(0, 3), Point(-3, 8), Point(5, 8)] + G.set_points(points) + coeffs_G = tuple(G.implicit_form_coefficients()) + def paramG(x, y): + A, B, C, D, E, F = coeffs_G + return A*x**2 + B*x*y + C*y**2 + D*x + E*y + F + for p in points: + self.assertAlmostEqual(paramG(p.x, p.y), 0) + + G2 = Ellipse.from_points(points) + coeffs_G2 = tuple(G.implicit_form_coefficients()) + def paramG2(x, y): + A, B, C, D, E, F = coeffs_G2 + return A*x**2 + B*x*y + C*y**2 + D*x + E*y + F + for p in points: + self.assertAlmostEqual(paramG2(p.x, p.y), 0) + + E.set_coefficients(*coeffs_G2) + for a1, a2 in zip(E.implicit_form_coefficients(), G2.implicit_form_coefficients()): + self.assertAlmostEqual(a1, a2) + + H = Ellipse.from_circle(Circle(Point(2, 8), 5)) + self.assertAlmostEqual(H.center(), Point(2, 8)) + self.assertAlmostEqual(H.ray(0), 5) + self.assertAlmostEqual(H.ray(1), 5) + + Ft = F.transformed( Rotate(math.pi/2) ) + self.assertAlmostEqual(F.ray(0), Ft.ray(1)) + self.assertAlmostEqual(F.ray(1), Ft.ray(0)) + +unittest.main() diff --git a/src/cython/test-curves.py b/src/cython/test-curves.py new file mode 100644 index 0000000..8f9b870 --- /dev/null +++ b/src/cython/test-curves.py @@ -0,0 +1,458 @@ +import unittest +import math +from random import randint, uniform + +import cy2geom + +from cy2geom import Angle +from cy2geom import Point, IntPoint +from cy2geom import Line, Ray, Rect +from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval + + +from cy2geom import Affine +from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom +from cy2geom import Eigen + +from cy2geom import Linear +from cy2geom import SBasis, SBasisCurve +from cy2geom import Bezier, BezierCurve +from cy2geom import lerp + +from cy2geom import LineSegment, QuadraticBezier, CubicBezier +from cy2geom import HLineSegment, VLineSegment + +from cy2geom import EllipticalArc + +class TestPrimitives(unittest.TestCase): + def test_linear(self): + L = Linear(0, 1) + M = Linear(2) + N = Linear() + self.assertEqual( (L+M), L+2 ) + self.assertEqual( (L-M), L-2 ) + self.assertAlmostEqual(L(0.5), lerp(.5, 0, 1)) + #~ self.assertTrue(N.is_zero()) + self.assertTrue(M.is_constant()) + self.assertTrue(L.is_finite()) + self.assertAlmostEqual(L(0), L.at0()) + self.assertAlmostEqual(L(1), L.at1()) + self.assertAlmostEqual(L.value_at(0.3), L(0.3)) + self.assertTrue( isinstance(M.to_SBasis(), SBasis )) + + self.assertAlmostEqual(L.tri(), L(1) - L(0)) + self.assertAlmostEqual(L.hat(), (L(1) + L(0))/2) + + for i in range(11): + t = i/10.0 + self.assertTrue(L.bounds_exact().Interval.contains(L(t))) + self.assertTrue(L.bounds_fast().Interval.contains(L(t))) + self.assertTrue(L.bounds_local(t-0.05, t+0.05).Interval.contains(L(t))) + self.assertAlmostEqual(lerp(t, 0, 4), t*4) + self.assertAlmostEqual(L(t), cy2geom.reverse(L)(1-t)) + self.assertAlmostEqual( L(t)*t, (L*t)(t) ) + self.assertAlmostEqual( L(t)+t, (L+t)(t) ) + self.assertAlmostEqual( L(t)-t, (L-t)(t) ) + self.assertAlmostEqual( -( L(t) ), (-L)(t) ) + self.assertAlmostEqual( (L/2)(t), L(t)/2 ) + + def test_sBasis(self): + S = SBasis() + T = SBasis(2) + U = SBasis(1, 7) + V = SBasis.from_linear( Linear(2, 8) ) + + self.assertEqual(V[0], Linear(2, 8)) + self.assertEqual(V.back(), Linear(2, 8)) + + #~ self.assertTrue(S.empty()) + self.assertFalse(T.empty()) + + T.pop_back() + self.assertTrue(T.empty()) + + self.assertEqual(S.size(), 0) + self.assertEqual(U.size(), 1) + self.assertEqual((U*V).size(), 2) + + T.resize(1, Linear(2, 3)) + self.assertEqual(T[0], Linear(2, 3)) + T.clear() + self.assertTrue(T.empty()) + #TODO + #~ T.reserve(5) + #~ print T.size() + self.assertEqual(V.at(0), V[0]) + self.assertEqual(V, U+1) + self.assertNotEqual(V, U) + self.assertTrue(T.is_zero()) + self.assertTrue(SBasis(1).is_constant()) + def f(A, B): + return (-A)*(A+B*2.2)*(A*B-B*B/3) + W = f(U, V) + self.assertAlmostEqual(W(0), W.at0()) + self.assertAlmostEqual(W(1), W.at1()) + + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(W(t), W.value_at(t)) + self.assertAlmostEqual(W(t), f(U(t), V(t))) + + vd_UV = (U*V).value_and_derivatives(t, 1) + vd_U = U.value_and_derivatives(t, 1) + vd_V = V.value_and_derivatives(t, 1) + self.assertAlmostEqual( vd_UV[1], vd_U[1]*V(t)+U(t)*vd_V[1] ) + + self.assertAlmostEqual( U(V)(t), U(V(t)) ) + self.assertEqual(T.degrees_of_freedom(), 0) + self.assertEqual(U.degrees_of_freedom(), 2) + + self.assertEqual(T, T.to_SBasis()) + + U2 = SBasis(U(0), U(1)) + U2.resize(10) + self.assertNotEqual(U2, U) + U2.truncate(U.size()) + self.assertEqual(U2, U) + #TODO: normalize() + sL = Linear.sin(Linear(0, 1), 3) + cL = Linear.cos(Linear(0, 1), 3) + sqrtU = SBasis.sqrt( U, 3 ) + rL = Linear.reciprocal(Linear(1,2), 3) + # cy2geom.inverse seems to return nans for degrees > 1 + #~ asin = cy2geom.inverse( cy2geom.sqrt( SBasis(Linear(0, 1)), 3 ), 1) + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(sL(t), math.sin(t)) + self.assertAlmostEqual(cL(t), math.cos(t)) + #cy2geom.sqrt is not that precise + self.assertAlmostEqual(sqrtU(t), math.sqrt(U(t)), places = 1) + self.assertAlmostEqual(rL(t), 1/(1+t), places = 1 ) + #~ self.assertAlmostEqual( asin(t), math.asin(t) ) + self.assertAlmostEqual( SBasis.compose(U, V)(t), U(V)(t) ) + self.assertAlmostEqual( SBasis.divide(U, V, 3)(t), U(t)/V(t), places = 1) + + self.assertAlmostEqual( SBasis.derivative(SBasis.integral(W))(t), W(t)) + self.assertAlmostEqual( cy2geom.reverse(W)(t), W(1-t) ) + self.assertAlmostEqual( SBasis.multiply(U, V)(t), (U*V)(t)) + #TODO looks like bug in 2geom + #~ print cy2geom.multiply_add(U, V, W)(t), (U*V+W)(t) + self.assertAlmostEqual( SBasis.multiply_add(U, W, V)(t), (U*W+V)(t)) + + self.assertTrue( SBasis.bounds_exact(U).Interval.contains(U(t)) ) + self.assertTrue( SBasis.bounds_fast(U).Interval.contains(U(t)) ) + self.assertTrue( SBasis.bounds_local(U, OptInterval(t-0.05, t+0.05)).Interval.contains(U(t)) ) + + + for r in SBasis.roots(W): + self.assertAlmostEqual(W(r), 0) + for r in SBasis.roots(W, Interval(0, 0.7)): + self.assertAlmostEqual(W(r), 0) + self.assertTrue(Interval(0, 0.7).contains(r)) + + levels = [0, 3, 22, -21] + for i, roots in enumerate( SBasis.multi_roots(W, levels) ): + level = levels[i] + for r in roots: + self.assertAlmostEqual(W(r), level) + + self.assertEqual(SBasis.valuation(W), 0) + #TODO: why is this still 0? + #~ print cy2geom.valuation(cy2geom.shift(W, 6)) + self.assertEqual( U[0], SBasis.shift(U, 2)[2] ) + + for I in SBasis.level_set(W, 2, tol = 1e-7): + self.assertAlmostEqual( W(I.mid()), 2 ) + for I in SBasis.level_set(W, Interval(0, 1), tol = 1e-7, vtol = 1e-7): + self.assertTrue( 0 <= W(I.begin()) <= 1 ) + self.assertTrue( 0 <= W(I.mid()) <= 1 ) + self.assertTrue( 0 <= W(I.end()) <= 1 ) + + def test_bezier(self): + B = Bezier() + C = Bezier(2) + D = Bezier(2, 4) + E = Bezier(1, 3, 9) + F = Bezier(-2, 5, -1, 2) + self.assertTrue( B.is_zero() ) + self.assertTrue( C.is_constant() ) + self.assertTrue( D.is_finite() ) + C.clear() + self.assertEqual(D.degree(), 1) + self.assertEqual(E.at0(), 1) + self.assertEqual(E.at1(), 9) + self.assertEqual(E[2], 9) + for i in range(11): + t = i/10.0 + self.assertAlmostEqual( D(t), lerp(t, 2, 4) ) + self.assertAlmostEqual( D(t), D.value_at(t)) + self.assertAlmostEqual( D.value_and_derivatives(t, 0)[0], D(t) ) + self.assertAlmostEqual( D.value_and_derivatives(t, 1)[1], Bezier.derivative(D)(t) ) + self.assertAlmostEqual( Bezier.integral(D).value_and_derivatives(t, 1)[1], D(t) ) + #~ self.assertAlmostEqual( D.elevate_degree().reduce_degree()(t), D(t) ) + self.assertAlmostEqual( (D+2)(t), D(t)+2 ) + self.assertAlmostEqual( (D-1)(t), D(t)-1 ) + self.assertAlmostEqual( (D*2)(t), D(t)*2 ) + self.assertAlmostEqual( (D/4)(t), D(t)/4 ) + self.assertTrue( Bezier.bounds_fast(F).Interval.contains(F(t)) ) + self.assertTrue( Bezier.bounds_exact(F).Interval.contains(F(t)) ) + self.assertTrue( Bezier.bounds_local(F, OptInterval(t-0.05, t+0.05)).Interval.contains(F(t)) ) + for r in F.roots(): + self.assertAlmostEqual(F(r), 0) + #TODO: bug in 2geom? + #~ for r in F.roots(Interval(0.1, 0.8)): + #~ self.assertAlmostEqual(F(r), 0) + #~ self.assertTrue( 0.1 <= r <= 0.8 ) + self.assertIsInstance(F.forward_difference(1), Bezier) + self.assertIsInstance(F.elevate_degree(), Bezier) + self.assertIsInstance(E.reduce_degree(), Bezier) + #F.reduce_degree() fails with + # *** glibc detected *** python2: malloc(): memory corruption: + self.assertIsInstance(F.elevate_to_degree(4), Bezier) + self.assertIsInstance(F.deflate(), Bezier) + S = F.to_SBasis() + self.assertIsInstance(S, SBasis) + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(S(t), F(t)) + + def curve(self, C): + self.assertAlmostEqual(C.initial_point(), C(0)) + self.assertAlmostEqual(C.final_point(), C.point_at(1)) + #Doesn't have to be true + #~ if C.length() > 0.01: + #~ self.assertFalse(C.is_degenerate()) + + if C.is_degenerate(): + #trivial special case + return + + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(C(t).x, C.point_at(t).x) + self.assertAlmostEqual(C(t).y, C.value_at(t, 1)) + self.assertEqual( C(t), C.point_and_derivatives(t, 1)[0] ) + self.assertTrue( C.bounds_exact().contains(C(t)) ) + self.assertTrue( C.bounds_fast().contains(C(t)) ) + #TODO why this works only with degree = 0? + if C.bounds_local(OptInterval(t-0.05, t+0.05), 0 + ) and ( + C.bounds_local(OptInterval(t-0.05, t+0.05), 0).Rect.area() > 1e-10): + #ruling out too small rectangles, they have problems with precision + self.assertTrue( C.bounds_local( OptInterval(t-0.05, t+0.05), 0 ).Rect.contains(C(t))) + D = C.duplicate() + + D.set_initial(Point()) + self.assertAlmostEqual(D.initial_point(), Point()) + + D.set_final(Point(1, 1)) + self.assertAlmostEqual(D.final_point(), Point(1, 1)) + + A = Affine( uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10)) + E = C.transformed(A) + for i in range(11): + t = i/10.0 + # self.assertAlmostEqual( E(t), C(t)*A ) + G1 = C.portion(0.2, 0.8) + G2 = C.portion( interval=Interval(2, 8)/10 ) + self.assertAlmostEqual( G1(0), C(0.2) ) + self.assertAlmostEqual( G2(0.5), C( lerp(0.5, 0.2, 0.8) )) + self.assertAlmostEqual( G1(1), G2(1) ) + + for i in range(11): + t = i/10.0 + self.assertAlmostEqual( C.reverse()(t), C(1-t) ) + self.assertAlmostEqual( C.point_and_derivatives(0.3, 1)[1], C.derivative()(0.3) ) + + self.assertAlmostEqual( C.nearest_time(C(0)), 0 ) + self.assertAlmostEqual( C( C.nearest_time(C(0.5), interval=Interval(0.2, 0.5)) ), C(0.5) ) + self.assertAlmostEqual( C( C.nearest_time(C(0.5), 0.2, 0.5) ), C(0.5) ) + for p in C.all_nearest_times( C(0), 0, 1): + self.assertEqual(C(p), C(0)) + for p in C.all_nearest_times( C(1), interval=Interval(0, 1)): + self.assertEqual(C(p), C(1)) + for r in C.roots(0, 0): + self.assertAlmostEqual(C.value_at(r, 0), 0) + + self.assertGreaterEqual(C.length(), abs(C(1) - C(0))) + self.assertEqual(C.winding(Point()), int(C.winding(Point())) ) + self.assertAlmostEqual( C.unit_tangent_at(0.5), + Point.unit_vector(C.derivative()(0.5)) ) + self.assertTrue(isinstance(C.to_SBasis()[0], SBasis)) + + def test_sBasisCurve(self): + S = SBasisCurve(SBasis(0, 2), SBasis(3, 7)*SBasis(1, 8)) + a = SBasis(3, 9)*SBasis(4, 6) + b = SBasis(2, 0) + c = a(b) + self.curve(S) + self.curve(S.derivative()) + self.curve(S.reverse()) + self.curve(S.transformed( Scale(4) )) + self.curve(S.transformed( Zoom(9, Translate(3, 6)) )) + self.curve(SBasisCurve(a*b*c, a+b+c)) + self.curve(S.derivative().derivative()) + + def test_bezierCurve(self): + B = BezierCurve.create( [ Point(0, 5), Point(3, 65), Point(-3, 2), Point(1, 9) ] ) + C = BezierCurve.create( [ Point(0,1), Point(1, 0) ] ) + self.curve(B) + self.curve(C) + self.curve(C.reverse()) + self.curve(B.portion(0, 2)) + self.curve(B.transformed(Zoom(9, Translate(3, 6)))) + self.curve(B.derivative()) + + def ntest_lineSegment(self): + L = LineSegment(Point(2, 8), Point(1, 9)) + K = LineSegment.from_beziers(Bezier(2, 8), Bezier(-1, 9)) + self.curve(L) + self.curve(K) + self.curve(L.reverse()) + self.curve(L.portion(Interval(0.2, 0.4))) + self.curve(L.subdivide(0.3)[0]) + self.curve(L.subdivide(0.3)[1]) + self.curve(L.derivative()) + self.curve(L.transformed(Scale(30)*Translate(3, 9))) + + self.curve(LineSegment()) + + def test_quadraticBezier(self): + Q = QuadraticBezier(Point(2, 8), Point(1, 9), Point(-2, 3)) + R = QuadraticBezier.from_beziers(Bezier(2, 8, 4), Bezier(-1, 9, 9)) + self.curve(Q) + self.curve(R) + self.curve(Q.reverse()) + self.curve(Q.portion(interval=Interval(0.1, 0.9))) + self.curve(Q.subdivide(0.8)[0]) + self.curve(Q.subdivide(0.8)[1]) + self.curve(Q.derivative()) + self.curve(Q.transformed(Scale(-3)*Translate(4, 8))) + + self.curve(QuadraticBezier()) + + def test_cubicBezier(self): + C = CubicBezier(Point(2, 0), Point(-1, 2.9), Point(-2, 3), Point(3, 1)) + D = CubicBezier.from_beziers(Bezier(2, 8, 4, 7), Bezier(-1, 9, 9, 8)) + print 343 + self.curve(C) + self.curve(D) + self.curve(C.reverse()) + #Some kind of numerical instability imo + #~ self.curve(C.portion(Interval(0.1, 0.9))) + self.curve(C.subdivide(0.8)[0]) + self.curve(C.subdivide(0.8)[1]) + self.curve(C.derivative()) + self.curve(C.transformed(Scale(-3)*Translate(4, 8))) + + self.curve(CubicBezier()) + + def test_hLineSegment(self): + H = HLineSegment(Point(3, 9), Point(9, 9)) + I = HLineSegment(Point(1, 3), Point(92, 3)) + J = HLineSegment.from_point_length( Point(2, 4), 1) + self.curve( H ) + self.curve( I ) + self.curve( J ) + self.curve( H.portion(0, .25) ) + self.curve( H.derivative() ) + self.curve( H.transformed(Rotate(20)) ) + self.curve( HLineSegment() ) + self.curve( I.reverse() ) + map(self.curve, I.subdivide(0.8)) + + self.assertAlmostEqual(I.get_Y(), 3) + J.set_Y(2) + J.set_initial_X(0) + J.set_final_X(1) + self.assertAlmostEqual( J(0), Point(0, 2) ) + self.assertAlmostEqual( J(1), Point(1, 2) ) + + def test_vLineSegment(self): + V = VLineSegment(Point(2, 9), Point(2, 6)) + W = VLineSegment(Point(1, 2), Point(1, 8)) + X = VLineSegment.from_point_length( Point(2, 4), 1) + #~ self.curve( V ) + #~ self.curve( W ) + #~ self.curve( X ) + #~ self.curve( V.portion(0, .25) ) + #~ self.curve( V.derivative() ) + #~ self.curve( V.transformed(Rotate(20)) ) + #~ self.curve( VLineSegment() ) + #~ self.curve( W.reverse() ) + #~ map(self.curve, W.subdivide(0.8)) + #~ + #~ self.assertAlmostEqual(I.get_Y(), 3) + #~ X.set_Y(2) + #~ X.set_initialX(0) + #~ X.set_finalX(1) + #~ self.assertAlmostEqual( X(0), Point(0, 2) ) + #~ self.assertAlmostEqual( X(1), Point(1, 2) ) + #~ print V(0.5) + #~ print V.nearest_time(V(0.5), 0.1, 0.4 ) + #~ print V.nearest_time(V(0.5), Interval(0.2, 0.5)) + #~ print V(0.5), V(0.2) + #TODO: + #this is likely a bug in 2geom, following code + + #~ VLineSegment V(Point(0, 0), 2); + #~ printf("%f\n", V.nearest_time(V(0.5), 0.2, 0.5)); + + #prints + #0.2 + + def test_ellipticalArc(self): + E = EllipticalArc() + self.curve(E) + F = EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1)) + + self.assertTrue(F.sweep()) + self.assertTrue(F.large_arc()) + self.assertAlmostEqual(F.chord()(0), Point()) + self.assertAlmostEqual(F.chord()(1), Point(1, 1)) + + F.set_extremes(Point(1, 1), Point(-1, 1)) + self.assertAlmostEqual(F.initial_point(), Point(1, 1)) + self.assertAlmostEqual(F.final_point(), Point(-1, 1)) + self.assertEqual(F.initial_angle(), F.angle_at(0)) + self.assertEqual(F.final_angle(), F.angle_at(1)) + self.assertTrue(F.contains(F.angle_at(0.5))) + + G = EllipticalArc(Point(), 1, 1, 0, True, True, Point(2, 0)) + for i in range(11): + t = i/10.0 + print G(t) + self.assertAlmostEqual(G.extent(), math.pi) + self.assertAlmostEqual(G.extent(), G.sweep_angle()) + self.assertAlmostEqual(float(G.angle_at(0.5)), -math.pi/2) + + self.assertAlmostEqual(Point(1, 1), G.rays()) + self.assertAlmostEqual(1, G.ray(1)) + self.assertAlmostEqual(0, float(G.rotation_angle())) + + self.assertAlmostEqual(G.extent(), G.angle_interval().extent()) + + self.assertAlmostEqual(G.center(), Point(1, 0)) + #unit half-circle + U = EllipticalArc(Point(1, 0), 1, 1, 0, True, True, Point(-1, 0)) + + G.set(Point(), 1, 1, 0, True, False, Point(1, 0)) + + A = G.unit_circle_transform() + + self.assertAlmostEqual( G(0.5), U.transformed(A)(0.5) ) + self.assertAlmostEqual( G.value_at_angle(G.angle_at(0.32), 0), G.value_at(0.32, 0) ) + + self.assertTrue(G.contains_angle(Angle(math.pi/4))) + self.assertFalse(G.is_SVG_compliant()) + #~ self.curve(F) + #TODO: + #F.point_and_derivatives(t, 1)[0] differs from F(0) and F.bounds_exact, + #F.bounds_fast doesn't contain F(1) + +unittest.main() diff --git a/src/cython/test-path.py b/src/cython/test-path.py new file mode 100644 index 0000000..6ad6af4 --- /dev/null +++ b/src/cython/test-path.py @@ -0,0 +1,218 @@ +import unittest +import math +from random import randint, uniform + +import cy2geom + +from cy2geom import Point, IntPoint +from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval + +from cy2geom import Affine +from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom +from cy2geom import Eigen + +from cy2geom import Curve +from cy2geom import Linear +from cy2geom import SBasis, SBasisCurve +from cy2geom import Bezier, BezierCurve + +from cy2geom import LineSegment, QuadraticBezier, CubicBezier +from cy2geom import HLineSegment, VLineSegment + +from cy2geom import EllipticalArc + +from cy2geom import Path + +#TODO! move drawing elsewhere, it nice to see paths, but is not very suitable for automatic testing +draw = False + +try: + import utils +except ImportError: + print "No drawing with Tk" + draw = False + +class TestPrimitives(unittest.TestCase): + def curves_equal(self, C1, C2): + for i in range(101): + t = i/100.0 + self.assertAlmostEqual(C1(t), C2(t)) + def path(self, P): + for curve in P: + self.assertIsInstance(curve, Curve) + + self.assertAlmostEqual(P(0), P.front()(0)) + self.curves_equal(P.front(), P[0]) + + self.curves_equal(P.back_default(), P[P.size_default()-1]) + self.curves_equal(P.back_open(), P.back()) + self.assertEqual(P.size_open(), P.size()) + + self.assertFalse(P.empty() ^ (P.size()==0)) + + exact = P.bounds_exact().Rect + exact.expand_by(1e-5) + + fast = P.bounds_fast().Rect + fast.expand_by(1e-5) + A1 = Affine(3, 1, 8, 3, 9, 9) + A2 = Rotate(0.231) + + for i in range(100 * P.size_open() + 1): + t = i/100.0 + self.assertTrue(exact.contains(P(t))) + self.assertTrue(fast.contains(P(t))) + self.assertAlmostEqual( (P*A1)(t) , P(t)*A1 ) + self.assertAlmostEqual( (P*A2)(t) , P(t)*A2 ) + + self.assertAlmostEqual(P(t), P.point_at(t)) + self.assertAlmostEqual(P(t).x, P.value_at(t, 0)) + self.assertAlmostEqual(P(t).y, P.value_at(t, 1)) + + if P.closed(): + self.curves_equal(P.back_default(), P.back_closed()) + self.assertEqual(P.size_default(), P.size_closed()) + else: + self.curves_equal(P.back_default(), P.back_open()) + self.assertEqual(P.size_default(), P.size_open()) + + for i in range(10): + for root in P.roots(i, 0): + if root < P.size_default(): + self.assertAlmostEqual(P.value_at(root, 0), i) + for root in P.roots(i, 1): + if root < P.size_default(): + self.assertAlmostEqual(P.value_at(root, 1), i) + + for t in P.all_nearest_times(P(0)): + self.assertAlmostEqual(P(t), P(0)) + self.assertAlmostEqual(min(P.all_nearest_times( P(0) )), 0) + self.assertAlmostEqual(P.nearest_time(P(0), 0, 0.2), 0) + self.assertEqual( len(P.nearest_time_per_curve(Point())), P.size_default() ) + + t, distSq = P.nearest_time_and_dist_sq(Point(-1, -1), 0, P.size()) + self.assertAlmostEqual(distSq**0.5, abs(P(t)-Point(-1, -1)) ) + + self.assertAlmostEqual(P.portion(0.3, 0.4)(0), P(0.3)) + self.assertAlmostEqual( P.portion( interval=Interval(P.size(), P.size() * 2) / 3 )(0), + P(P.size()/3.0)) + + self.assertAlmostEqual(P(0.23), P.reverse()(P.size()-0.23)) + + self.assertAlmostEqual(P.initial_point(), P(0)) + self.assertAlmostEqual(P.final_point(), P(P.size())) + def test_path(self): + a = Path() + a.append_curve( CubicBezier( Point(-7, -3), Point(2, 8), Point(2, 1), Point(-2, 0) ) ) + + self.assertEqual(a.size(), 1) + self.assertFalse(a.closed()) + self.path(a) + + a.close(True) + self.assertTrue(a.closed()) + self.path(a) + + a.close(False) + a.append_curve( LineSegment(a.final_point(), Point(3, 5)) ) + self.assertEqual(a.size(), 2) + self.path(a) + + a.append_SBasis( SBasis(3, 6)*SBasis(1, 0), SBasis(5, 2)) + self.path(a) + + a.append_curve(EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1)), Path.STITCH_DISCONTINUOUS) + #Stitching adds new segment + self.assertEqual(a.size(), 5) + + b = Path() + for c in a: + b.append_curve(c) + + #TODO: This fails with STITCH_DISCONTINUOUS, but also does so in C++, so + #it's either correct behaviour or bug in 2geom + #~ self.path(b) + + b.insert(2, LineSegment(b[2-1](1), b[2](0))) #, Path.STITCH_DISCONTINUOUS) + self.curves_equal(LineSegment(b[2-1](1), b[2](0)), b[2]) + #TODO! fails on root finding + #self.path(b) + + b.set_initial(a[2](1)) + b.set_final(a[3](0)) + + a.insert_slice(3, b, 0, b.size()) + self.assertEqual(a.size(), b.size()*2-1) + + for i in range(b.size()): + self.curves_equal(a[3+i], b[i]) + + #Looks like bug: +# A = Path() +# A.append_curve( CubicBezier( Point(-7, -3), Point(2, 8), Point(2, 1), Point(-2, 0) ) ) +# A.append_curve(EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1)), Path.STITCH_DISCONTINUOUS) +# print A.roots(0, 1) + + #Roots are [1.0, 2.768305708350847, 3.25], Point at second root is + #Point (2.32, -0.48) + #and third root is > 3 - it corresponds to root on closing segment, but A is open, + #and computing A(3.25) results in RangeError - this might be bug or feature. + + self.path(a.portion(0.232, 3.12)) + self.path(a.portion( interval=Interval(0.1, 4.7) )) + self.path(a.portion(0.232, 3.12).reverse()) + + b.clear() + self.assertTrue(b.empty()) + + aa = Path() + for c in a: + aa.append_curve(c) + + a.erase(0) + self.assertEqual(a.size(), aa.size() - 1) + self.assertAlmostEqual(a(0), aa(1)) + + a.erase_last() + self.assertEqual(a.size(), aa.size() - 2) + self.assertAlmostEqual(a.final_point(), aa[aa.size()-2](1)) + + a.replace(3, QuadraticBezier(a(3), Point(), a(4))) + self.assertEqual(a.size(), aa.size() - 2) + + cs = [LineSegment(Point(-0.5, 0), Point(0.5, 0)).transformed( Rotate(-math.pi/3 * i)*Translate(Point(0, math.sqrt(3)/2)*Rotate(-math.pi/3 * i)) ) for i in range(6)] + + hexagon = Path.fromList(cs, stitching = Path.STITCH_DISCONTINUOUS, closed = True) + + if draw: + utils.draw(hexagon, scale = 100) + + #to = 5 because each corner contains one stitching segment + half_hexagon = Path.fromPath(hexagon, fr = 0, to = 5) + if draw: + utils.draw(half_hexagon, scale = 100) + + half_hexagon.replace_slice(1, 5, LineSegment(half_hexagon(1), half_hexagon(5))) + self.assertEqual(half_hexagon.size(), 2) + self.assertAlmostEqual(half_hexagon(1.5), Point(0.5, 0)) + + half_hexagon.stitch_to(half_hexagon(0)) + self.assertAlmostEqual(half_hexagon(2.5), Point()) + + a.start(Point(2, 2)) + a.append_SBasis( SBasis(2, 6), SBasis(1, 5)*SBasis(2, 9) ) + self.assertAlmostEqual(a(1), Point(6, 5*9)) + + l = Path.fromList([QuadraticBezier(Point(6, 5*9), Point(1, 2), Point(-2, .21))]) + a.append_path(l) + self.assertAlmostEqual(a.final_point(), l.final_point()) + + k = Path.fromList([QuadraticBezier(Point(), Point(2, 1), Point(-2, .21)).reverse()]) + k.append_portion_to(l, 0, 0.3) + self.assertAlmostEqual(l.final_point(), k(0.3)) + + def test_read_svgd(self): + p = Path.read_svgd("../toys/spiral.svgd") + if draw: + utils.draw(p[0], scale=0.4) +unittest.main() diff --git a/src/cython/test-primitives.py b/src/cython/test-primitives.py new file mode 100644 index 0000000..0edf218 --- /dev/null +++ b/src/cython/test-primitives.py @@ -0,0 +1,288 @@ +import unittest +from math import pi, sqrt + +import cy2geom +from cy2geom import Angle, AngleInterval, Point, IntPoint, Line, Ray +from cy2geom import LineSegment, Curve + +class TestPrimitives(unittest.TestCase): + + def test_angle(self): + self.assertAlmostEqual(Angle.rad_from_deg(45), pi/4) + self.assertAlmostEqual(Angle.deg_from_rad(pi/6), 30) + + p = Point(1, sqrt(3)) + alpha = Angle.from_Point(p) + self.assertAlmostEqual(alpha.degrees(), 60) + + beta = Angle.from_radians(pi/5) + gamma = Angle.from_degrees(36) + self.assertAlmostEqual(beta.radians0(), gamma.radians0()) + self.assertTrue(beta==gamma) + omega = Angle.from_degrees_clock(0) + self.assertAlmostEqual(omega.radians(), pi/2) + + delta = Angle(-pi * 0.5) + self.assertAlmostEqual(delta.degrees(), -90) + self.assertAlmostEqual(delta.radians0(), 1.5*pi) + #degreesClock roughly means [ 90 - Angle.degrees() ] mod 360 + self.assertAlmostEqual(delta.degrees_clock(), 180) + + self.assertAlmostEqual( + (beta + gamma).radians(), + beta.radians()+gamma.radians() ) + self.assertAlmostEqual( (beta - gamma).degrees(), 0) + + def test_angleInterval(self): + A = AngleInterval(Angle(pi/6), Angle(pi/4)) + B = AngleInterval( 0, pi/4, cw = True ) + self.assertEqual(A(0), Angle(pi/6)) + self.assertEqual(A(0.5), A.angle_at(0.5)) + self.assertEqual(A(0), A.initial_angle()) + self.assertEqual(B(1), B.final_angle()) + self.assertFalse(B.is_degenerate()) + self.assertTrue(B.contains(Angle(pi/6))) + self.assertTrue(A.contains(Angle(pi))) + + self.assertAlmostEqual( B.extent(), pi/4 ) + def test_point(self): + p = Point(3, 4) + q = Point(8, 16) + p_inf = Point(float('inf'), 1) + #y axis points downwards + p_ccw = Point(4, -3) + + self.assertAlmostEqual(p.length(), 5) + self.assertAlmostEqual(p.ccw(), p_ccw) + self.assertAlmostEqual(p_ccw.cw(), p) + self.assertAlmostEqual(p[0], 3) + self.assertAlmostEqual(p[1], 4) + + self.assertFalse(p_inf.isFinite()) + self.assertTrue(p.isFinite()) + self.assertFalse(p.isNormalized()) + self.assertTrue((p/p.length()).isNormalized()) + self.assertFalse(p.isZero()) + self.assertTrue((p*0).isZero()) + + self.assertTrue( (p + p.ccw().ccw()).isZero) + self.assertAlmostEqual( (q-p).length(), 13) + + self.assertGreater(q, p) + self.assertGreaterEqual(q, p) + self.assertEqual(p, p) + self.assertNotEqual(p, q) + self.assertLess( Point(1, 1), Point(1, 2) ) + self.assertLessEqual(p, p) + + self.assertTrue( Point.are_near( + Point.polar(pi/4, sqrt(2)), + Point(1, 1) )) + self.assertAlmostEqual(sqrt(2), Point.L2(Point(1, 1))) + self.assertAlmostEqual(2, Point.L2sq(Point(1, 1))) + self.assertAlmostEqual( Point.middle_point(Point(), q), q/2 ) + self.assertAlmostEqual( Point.rot90(p), p.cw() ) + self.assertAlmostEqual( + Point.lerp(0.2, Point(), Point(3,4)).length(), + 1) + self.assertAlmostEqual(Point.dot(p, p_ccw), 0) + self.assertAlmostEqual(Point.dot(p, p_inf), float('inf')) + self.assertAlmostEqual(Point.dot(p, q), 88) + #TODO this might be implemented incorrectly in lib2geom! + self.assertAlmostEqual(Point.cross(p, q), -16) + + self.assertAlmostEqual(Point.distance(p, q), 13) + self.assertAlmostEqual(Point.distanceSq(p, p_ccw), 50) + self.assertAlmostEqual(Point.unit_vector(p), p/5) + + self.assertAlmostEqual(Point.L1(p), 7) + self.assertAlmostEqual(Point.L1(p_inf), float('inf')) + self.assertAlmostEqual(Point.LInfty(q), 16) + self.assertAlmostEqual(Point.LInfty(p_inf), float('inf')) + + self.assertTrue(Point.is_zero(Point())) + self.assertFalse(Point.is_zero(p)) + self.assertTrue(Point.is_unit_vector(p/5)) + self.assertFalse(Point.is_unit_vector(q)) + + self.assertAlmostEqual(Point.atan2(Point(1, 1)), pi/4) + self.assertAlmostEqual(Point.angle_between(p, p_ccw), -pi/2) + self.assertAlmostEqual(Point.abs(-p), p) + #TODO I have no idea what should this function do + # self.assertAlmostEqual( + # Point.constrain_angle(Point(1, 0), Point(0, 1), 1, Point(sqrt(2)/2, sqrt(2)/2)), + # + # )) + + def test_intPoint(self): + p = Point(4.89, 3.21) + self.assertEqual(p.round(), IntPoint(5, 3)) + self.assertEqual(p.floor(), IntPoint(4, 3)) + self.assertEqual(p.ceil(), IntPoint(5, 4)) + + self.assertEqual(p.ceil().x, 5) + self.assertEqual(p.floor().y, 3) + self.assertEqual(IntPoint(), p.floor()-p.floor()) + + a = IntPoint(2, -5) + b = IntPoint(5, 3) + self.assertEqual(IntPoint(7, -2), a+b) + self.assertEqual(IntPoint(3, 8), b-a) + self.assertGreater(b, a) + self.assertGreaterEqual(b, b) + self.assertNotEqual(a, b) + + def test_line(self): + + l = Line(Point(), pi/4) + self.assertAlmostEqual( l.origin(), Point() ) + self.assertAlmostEqual( l.versor(), Point(1, 1)/sqrt(2) ) + self.assertAlmostEqual( l.angle(), pi/4 ) + + k = Line.from_points(Point(), Point(2, 1)) + self.assertFalse(k.is_degenerate()) + self.assertFalse(Line().is_degenerate()) + self.assertAlmostEqual( l.point_at(sqrt(2)), Point(1,1) ) + self.assertAlmostEqual( + k.point_at(43), + Point(k.value_at(43, 0), k.value_at(43, 1))) + self.assertAlmostEqual(k.time_at(Point(4, 2)), sqrt(20)) + self.assertAlmostEqual( + k.time_at_projection(Point(4, 2) + Point(2, -4)), + sqrt(20)) + self.assertAlmostEqual( + k.point_at(k.nearest_time(Point(4, 2) + Point(2, -4))), + Point(4,2)) + self.assertAlmostEqual( + k.time_at_projection(Point(3, 3)), + -k.reverse().time_at_projection(Point(3, 3))) + self.assertAlmostEqual( k.derivative().origin(), k.versor()) + self.assertAlmostEqual(k.normal(), k.versor().cw()) + + roots = k.roots( 3, 0 ) + for root in roots: + self.assertAlmostEqual( k.value_at(root, 0), 3) + + self.assertAlmostEqual(l.normal(), l.normal_and_dist()[0]) + self.assertAlmostEqual(Line.distance(Point(), l), l.normal_and_dist()[1]) + + self.assertAlmostEqual(Line.distance(Point(-1, 1), l), sqrt(2)) + self.assertTrue(Line.are_near(Point(0), l)) + self.assertFalse(Line.are_near(Point(1, 1), k)) + self.assertTrue(Line.are_near(Point(1, 1), k, 2)) + + p = Line(Point(1, 1)) + p_orto = Line(Point(2, 3), pi/2) + p_para = Line(Point(2, 3)) + p_same = Line.from_points(Point(1, 1), Point(5, 1)) + + self.assertTrue(Line.are_orthogonal(p, p_orto)) + self.assertFalse(Line.are_orthogonal(p, p_para)) + self.assertTrue(Line.are_parallel(p, p_para)) + self.assertFalse(Line.are_parallel(p, p_orto)) + self.assertTrue(Line.are_same(p, p_same)) + self.assertFalse(Line.are_same(p, p_para)) + + self.assertTrue(Line.are_collinear( + Point(1,1), + Point(2, 3), + Point(4, 7))) + self.assertAlmostEqual(Line.angle_between(p, p_orto), pi/2) + + m = Line.from_normal_distance(Point(1, -1), 1) + self.assertAlmostEqual(m.angle(), pi/4) + + m = Line.from_LineSegment( LineSegment( Point(2, 2), Point(4, 4) ) ) + self.assertAlmostEqual(m.angle(), pi/4) + + m = Line.from_Ray( Ray(Point(2, 3), 0.2) ) + self.assertAlmostEqual(m.angle(), 0.2) + self.assertAlmostEqual(m.origin(), Point(2, 3)) + + self.assertIsInstance(m.portion(2, 4), Curve) + self.assertAlmostEqual(m.portion(2, 4)(0), m.point_at(2)) + + self.assertIsInstance(m.segment(1, 5), LineSegment) + self.assertAlmostEqual(m.segment(1, 5)(1), m.point_at(5)) + + self.assertAlmostEqual(m.ray(4).origin(), m.point_at(4)) + + m.set_origin(Point()) + self.assertAlmostEqual(m.origin(), Point()) + + m.set_angle(0.2) + self.assertAlmostEqual(m.angle(), 0.2) + + m.set_versor(Point()) + self.assertTrue(m.is_degenerate()) + + m.set_points(Point(2, 9), Point(1, 8)) + self.assertAlmostEqual(m.versor(), Point.unit_vector(Point(1, 8) - Point(2, 9))) + + def test_ray(self): + r = Ray(Point(1,1), pi/4) + self.assertAlmostEqual(r.origin(), Point(1, 1)) + self.assertAlmostEqual(r.versor(), Point(1, 1)/sqrt(2)) + self.assertAlmostEqual(r.angle(), pi/4) + + r.set_origin(Point(4, 3)) + #TODO this should maybe normalize the versor! + r.set_versor(Point(1, -1)/sqrt(2)) + self.assertAlmostEqual(r.origin(), Point(4, 3)) + self.assertAlmostEqual(r.versor(), Point(1, -1)/sqrt(2)) + self.assertAlmostEqual(r.angle(), -pi/4) + + r.set_points(Point(1, 1), Point(1, 3)) + self.assertFalse(r.is_degenerate()) + self.assertFalse(Ray().is_degenerate()) + self.assertAlmostEqual(r.point_at(4), Point(1, 5)) + + #TODO I think this should be expected behaviour +# self.assertAlmostEqual( +# r.pointAt(-3), +# Point(1, 1))) + self.assertAlmostEqual(r.value_at(4, 0), 1) + self.assertAlmostEqual(r.value_at(4, 1), 5) + + roots = r.roots( 3, 1 ) + for root in roots: + self.assertAlmostEqual( r.value_at(root, 1), 3) + + self.assertAlmostEqual( + r.point_at(3) - r.origin(), + r.origin()-r.reverse().point_at(3)) + + self.assertAlmostEqual(Ray.distance(Point(), r), sqrt(2)) + self.assertAlmostEqual(Ray.distance(Point()+r.versor(), r), 1) + + self.assertTrue(Ray.are_near(Point(), r, 2)) + self.assertFalse(Ray.are_near(Point(), r)) + self.assertTrue(Ray.are_same(r, r)) + + q = Ray(r.origin(), r.angle()) + self.assertTrue(Ray.are_same(r, q)) + + q.set_origin(r.origin()+Point(0, 1)) + self.assertFalse(Ray.are_same(r, q)) + #TODO shouldn't this really be 0? + self.assertAlmostEqual(Ray.angle_between(r, q), 2*pi) + + q.set_versor(Point(1, 0)) + q.set_origin(r.origin()) + self.assertAlmostEqual( + Point(1, 1)/sqrt(2), + Ray.make_angle_bisector_ray(q, r).versor()) + + q.set_angle(pi/7) + self.assertAlmostEqual(q.angle(), pi/7) + + self.assertIsInstance(q.portion(2, 4), Curve) + self.assertAlmostEqual(q.portion(2, 4)(0), q.point_at(2)) + + self.assertIsInstance(q.segment(1, 5), LineSegment) + + self.assertAlmostEqual(q.segment(1, 5)(1), q.point_at(5)) + + +unittest.main() + diff --git a/src/cython/test-rectangle.py b/src/cython/test-rectangle.py new file mode 100644 index 0000000..51f3c50 --- /dev/null +++ b/src/cython/test-rectangle.py @@ -0,0 +1,601 @@ +import unittest +from math import pi, sqrt +from random import randint + +import cy2geom + +from cy2geom import Point, IntPoint + +from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval +from cy2geom import GenericInterval, GenericOptInterval + +from cy2geom import Rect, OptRect, IntRect, OptIntRect +from cy2geom import GenericRect + +from fractions import Fraction + + +class TestPrimitives(unittest.TestCase): + def interval_basic(self, I, J): + #for simplicity + self.assertTrue(I.min() >= 0) + self.assertTrue(J.min() >= 0) + a = I.min() + b = I.max(); + self.assertAlmostEqual(I.middle(), (a+b)/2); + self.assertAlmostEqual(I.extent(), (b-a)); + if a != b: + self.assertFalse(I.is_singular()) + else: + self.assertTrue(I.is_singular()) + + I.expand_by(a) + self.assertAlmostEqual(I.min(), 0); + self.assertAlmostEqual(I.max(), a+b) + I.set_min(a) + I.set_max(b) + + self.assertTrue(I.contains(a+ (b-a)/3 )) + self.assertTrue(I.contains(a)) + self.assertTrue(I.contains_interval(I)) + + if (not I.is_singular()) or I.min() != 0 : + pass + self.assertFalse(I.contains_interval(I+I)) + self.assertFalse(I.contains(a-1)) + + c = J.min() + d = J.max() + self.assertAlmostEqual( (I+J).min(), a+c ) + self.assertAlmostEqual((I|J).min(), min(a, c)) + J.set_min(a+2) + J.set_max(b+2) + self.assertEqual(I+2, J) + I.expand_to(2*b) + self.assertAlmostEqual(I.max(), 2*b) + + def test_interval(self): + I = Interval(1.2, 5) + J = Interval(0, 0.3) + self.interval_basic(I, J) + + self.assertTrue(I.interior_contains(I.middle())) + self.assertFalse(I.interior_contains(I.min())) + self.assertFalse(I.interior_contains_interval(I)) + self.assertTrue(I.interior_contains_interval(Interval(I.min()+1, I.max()-1))) + + self.assertTrue(I.interior_intersects(I)) + self.assertFalse(I.interior_intersects(-I)) + p = [1, 2, 3.442, 3] + K = Interval.from_list(p) + self.assertAlmostEqual(K.max(), max(p)) + self.assertAlmostEqual((K+Interval(1.0)).min(), min(p)+1) + L = Interval(10/3.0) + for i in range(3): + K+=L + self.assertAlmostEqual(K.max(), max(p)+10) + + #TODO This 2geom behaviour is a bit strange + self.assertEqual(Interval(3.0)|Interval(5.0), + Interval(3.0, 5.0)) + self.assertAlmostEqual((K-L).max(), (K-10/3.0).max()) + + self.assertAlmostEqual((K*3.4).max(), 3.4*K.max()) + self.assertAlmostEqual((K/3).extent(), K.extent()/3) + + def test_optInterval(self): + I = OptInterval(2.2, 9.3) + J = Interval(3, 13) + K = OptInterval.from_Interval(J) + self.assertEqual(K.Interval, J) + self.interval_basic(K.Interval, I.Interval) + + L = OptInterval() + + self.assertFalse(L) + self.assertTrue( (L&I).is_empty() ) + L.intersect_with(I) + self.assertFalse(L) + + L |= I + + self.assertEqual(L.Interval, I.Interval) + + self.assertEqual((I & K).Interval, Interval(3, 9.3)) + + def test_intInterval(self): + I = IntInterval(2, 6) + J = IntInterval(0, 1) + self.interval_basic(I, J) + p = [3, 2.3, 65.3, 43] + K = IntInterval.from_list(p) + self.assertAlmostEqual(K.max(), int(max(p))) + self.assertAlmostEqual(int((K+IntInterval(1.0)).min()), int(min(p)+1)) + L = IntInterval(3) + for i in range(3): + K+=L + self.assertAlmostEqual(K.max(), int(max(p))+9) + + self.assertEqual(Interval(3)|Interval(5), + Interval(3, 5)) + self.assertAlmostEqual((K-L).max(), (K-3).max()) + + def test_optIntInterval(self): + I = OptIntInterval(2, 9) + J = IntInterval(3, 13) + K = OptIntInterval.from_Interval(J) + self.assertEqual(K.Interval, J) + self.interval_basic(K.Interval, I.Interval) + + L = OptIntInterval() + + self.assertFalse(L) + self.assertTrue( (L&I).is_empty() ) + L.intersect_with(I) + self.assertFalse(L) + + L |= I + + self.assertEqual(L.Interval, I.Interval) + + self.assertEqual((I & K).Interval, IntInterval(3, 9)) + + def test_genericInterval(self): + maxv = 100000 + test_vars = [ + ( (randint(0, maxv), randint(0, maxv)), (randint(0, maxv), randint(0, maxv)) ), + ( (3,), (2, 0) ), + ( (0.0, 9), (4, 1.3)), + ((2.98, sqrt(2)), (sqrt(7),)), + ( (Fraction(1,2), Fraction(3, 7)), ( Fraction(2, 1), ) ) + ] + for a,b in test_vars: + self.interval_basic( GenericInterval(*a), GenericInterval(*b) ) + + def test_genericOptInterval(self): + test_vars = [ + ( (3,), (2, 0) ), + ( (0.0, 9), (4, 1.3)), + ((2.98, sqrt(2)), (sqrt(7),)), + ( (Fraction(1,2), Fraction(3, 7)), ( Fraction(2, 1), ) ) + ] + + for a, b in test_vars: + I = GenericOptInterval(*a) + J = GenericInterval(*b) + K = GenericOptInterval.from_Interval(J) + + self.assertEqual(I, GenericOptInterval.from_Interval(I)) + + self.assertEqual(K.Interval, J) + self.interval_basic(K.Interval, I.Interval) + + L = GenericOptInterval() + + self.assertFalse(L) + self.assertTrue( (L&I).is_empty() ) + L.intersect_with(I) + self.assertFalse(L) + + L |= I + + self.assertEqual(L.Interval, I.Interval) + + if I.intersect_with(K): + if I.Interval.min() <= K.Interval.min(): + if I.Interval.max() >= K.Interval.max(): + self.assertEqual( I & K, K) + else: + self.assertEqual( I & K, GenericInterval(K.min(), I.max())) + else: + if I.Interval.max() >= K.Interval.max(): + self.assertEqual( I & K, GenericInterval(I.min(), K.max())) + else: + self.assertEqual( I & K, I) + + def test_genericRect(self): + A = GenericRect(1, 1, 4, 4) + self.assertEqual( A.min(), (1, 1) ) + B = GenericRect(Fraction(1,4), Fraction(9, 94), Fraction(2, 3), Fraction(23, 37)) + + amin = A.min() + amax = A.max() + + self.assertAlmostEqual(amin[0], A[0].min()) + self.assertAlmostEqual(amax[1], A[1].max()) + self.assertEqual(amin, A.corner(0)) + + self.assertEqual(amin, (A.left(), A.top())) + self.assertEqual(amax, (A.right(), A.bottom())) + + self.assertAlmostEqual( A.width(), A[0].extent() ) + self.assertAlmostEqual( A.height(), A[1].extent() ) + + self.assertEqual( A.dimensions(), ( A.width(), A.height() ) ) + #~ self.assertEqual( A.midpoint(), (A.min() + A.max())/2 ) + self.assertAlmostEqual(A.area(), A.width()*A.height()) + #TODO export EPSILON from 2geom + if A.area() > 0: + self.assertFalse(A.has_zero_area()) + else: + self.assertTrue(A.has_zero_area()) + self.assertAlmostEqual(A.max_extent(), max(A.width(), A.height())) + self.assertGreaterEqual(A.max_extent(), A.min_extent()) + + bmin = B.min() + bmax = B.max() + + pdiag = sqrt((amax[0]-amin[0])**2+(amax[1]-amin[1])**2) + + B.set_min(A.midpoint()) + B.set_max(A.midpoint()) + + self.assertTrue(B.has_zero_area()) + + B.expand_by(A.min_extent()/3.0) + + self.assertTrue(A.contains_rect(B)) + self.assertTrue(A.intersects(B)) + self.assertTrue(B.intersects(A)) + self.assertFalse(B.contains_rect(A)) + + self.assertTrue(A.contains(A.midpoint())) + self.assertFalse(A.contains( (A.midpoint()[0]*3, A.midpoint()[1]*3) )) + + A.union_with(B) + + self.assertEqual( A.min(), amin ) + + B.set_left(bmin[0]) + B.set_top(bmin[1]) + B.set_right(bmax[0]) + B.set_bottom(bmax[1]) + + self.assertEqual(B.min(), bmin) + self.assertEqual(B.max(), bmax) + + B.expand_to( (0, 0) ) + self.assertEqual((0, 0), B.min()) + + B.expand_by(*bmax) + self.assertEqual(bmax, (- (B.min()[0]), - (B.min()[1])) ) + + B.expand_by(-bmax[0], -bmax[1]) + self.assertEqual(B.max(), bmax) + + self.assertEqual( (A+B.min()).max()[0], A.max()[0] + B.min()[0] ) + + #~ self.assertEqual( (A-B.max()).min(), A.min() - B.max() ) + + self.assertEqual( A|A, A ) + + self.assertFalse( A != A ) + + B.set_left(bmin[0]) + B.set_top(bmin[1]) + B.set_right(bmax[0]) + B.set_bottom(bmax[1]) + + #~ self.assertAlmostEqual(Rect.distance(Point(), A), A.min().length()) + #~ self.assertAlmostEqual(Rect.distanceSq(B.min(), A), Rect.distance(B.min(), A)**2 ) + + + + def rect_basic(self, P, Q): + pmin = P.min() + pmax = P.max() + #for simplicity + self.assertTrue(pmin.x > 0) + self.assertTrue(pmin.y > 0) + + self.assertAlmostEqual(pmin.x, P[0].min()) + self.assertAlmostEqual(pmax.y, P[1].max()) + self.assertEqual(pmin, P.corner(0)) + + self.assertEqual(pmin, Point(P.left(), P.top())) + self.assertEqual(pmax, Point(P.right(), P.bottom())) + + self.assertAlmostEqual( P.width(), P[0].extent() ) + self.assertAlmostEqual( P.height(), P[1].extent() ) + + self.assertAlmostEqual( P.aspect_ratio(), P.width()/P.height() ) + self.assertEqual( P.dimensions(), Point( P.width(), P.height() ) ) + self.assertEqual( P.midpoint(), (P.min() + P.max())/2 ) + self.assertAlmostEqual(P.area(), P.width()*P.height()) + #TODO export EPSILON from 2geom + if P.area() > 1e-7: + self.assertFalse(P.has_zero_area()) + self.assertTrue(P.has_zero_area(P.area())) + else: + self.assertTrue(P.has_zero_area()) + self.assertAlmostEqual(P.max_extent(), max(P.width(), P.height())) + self.assertGreaterEqual(P.max_extent(), P.min_extent()) + + qmin = Q.min() + qmax = Q.max() + + pdiag = (pmax-pmin).length() + + Q.set_min(P.midpoint()) + Q.set_max(P.midpoint()) + self.assertTrue(Q.has_zero_area()) + + #print P,Q + Q.expand_by(P.min_extent()/3.0) + + #print P, Q + + self.assertTrue(P.contains_rect(Q)) + self.assertTrue(P.intersects(Q)) + self.assertTrue(Q.intersects(P)) + self.assertFalse(Q.contains_rect(P)) + + self.assertTrue(P.interior_contains_rect(Q)) + self.assertFalse(P.interior_contains_rect(P)) + self.assertTrue(P.interior_intersects(Q)) + self.assertTrue(P.interior_intersects(P)) + + self.assertTrue(P.contains(P.midpoint())) + self.assertFalse(P.contains(P.midpoint()*3)) + + P.union_with(Q) + + self.assertEqual( P.min(), pmin ) + + Q.set_left(qmin.x) + Q.set_top(qmin.y) + Q.set_right(qmax.x) + Q.set_bottom(qmax.y) + + self.assertEqual(Q.min(), qmin) + self.assertEqual(Q.max(), qmax) + + Q.expand_to( Point() ) + self.assertEqual(Point(), Q.min()) + Q.expand_by(qmax) + self.assertEqual(qmax, -Q.min()) + + Q.expand_by(-qmax.x, -qmax.y) + self.assertEqual(Q.max(), qmax) + + self.assertEqual( (P+Q.min()).max(), P.max() + Q.min() ) + + self.assertEqual( (P-Q.max()).min(), P.min() - Q.max() ) + + self.assertEqual( P|P, P ) + + self.assertFalse( P != P ) + + Q.set_left(qmin.x) + Q.set_top(qmin.y) + Q.set_right(qmax.x) + Q.set_bottom(qmax.y) + + self.assertAlmostEqual(Rect.distance(Point(), P), P.min().length()) + self.assertAlmostEqual(Rect.distanceSq(Q.min(), P), Rect.distance(Q.min(), P)**2 ) + + self.assertEqual(P.round_outwards()[0], P[0].round_outwards()) + if P.round_inwards(): + self.assertEqual(P.round_inwards().Rect[1], P[1].round_inwards().Interval) + + + def intrect_basic(self, P, Q): + pmin = P.min() + pmax = P.max() + #for simplicity + self.assertTrue(pmin.x > 0) + self.assertTrue(pmin.y > 0) + + self.assertAlmostEqual(pmin.x, P[0].min()) + self.assertAlmostEqual(pmax.y, P[1].max()) + self.assertEqual(pmin, P.corner(0)) + + self.assertEqual(pmin, IntPoint(P.left(), P.top())) + self.assertEqual(pmax, IntPoint(P.right(), P.bottom())) + + self.assertAlmostEqual( P.width(), P[0].extent() ) + self.assertAlmostEqual( P.height(), P[1].extent() ) + + self.assertAlmostEqual( P.aspect_ratio(), float(P.width())/float(P.height()) ) + self.assertEqual( P.dimensions(), IntPoint( P.width(), P.height() ) ) + self.assertEqual( P.midpoint().x, (P.min() + P.max()).x/2 ) + self.assertAlmostEqual(P.area(), P.width()*P.height()) + + if P.area() > 0: + self.assertFalse(P.has_zero_area()) + else: + self.assertTrue(P.has_zero_area()) + self.assertAlmostEqual(P.max_extent(), max(P.width(), P.height())) + self.assertGreaterEqual(P.max_extent(), P.min_extent()) + + qmin = Q.min() + qmax = Q.max() + + Q.set_min(P.midpoint()) + Q.set_max(P.midpoint()) + self.assertTrue(Q.has_zero_area()) + + Q.expand_by(P.min_extent()/3.0) + + self.assertTrue(P.contains_rect(Q)) + self.assertTrue(P.intersects(Q)) + self.assertTrue(Q.intersects(P)) + self.assertFalse(Q.contains_rect(P)) + + self.assertTrue(P.contains(P.midpoint())) + self.assertFalse(P.contains(P.midpoint()+P.midpoint()+P.midpoint())) + + P.union_with(Q) + + self.assertEqual( P.min(), pmin ) + + Q.set_left(qmin.x) + Q.set_top(qmin.y) + Q.set_right(qmax.x) + Q.set_bottom(qmax.y) + + self.assertEqual(Q.min(), qmin) + self.assertEqual(Q.max(), qmax) + + Q.expand_to( IntPoint() ) + self.assertEqual(IntPoint(), Q.min()) + Q.expand_by(qmax) + self.assertEqual(qmax, IntPoint()-Q.min()) + + Q.expand_by(-qmax.x, -qmax.y) + self.assertEqual(Q.max(), qmax) + + self.assertEqual( (P+Q.min()).max(), P.max() + Q.min() ) + + self.assertEqual( (P-Q.max()).min(), P.min() - Q.max() ) + + self.assertEqual( P|P, P ) + + self.assertFalse( P != P ) + + Q.set_left(qmin.x) + Q.set_top(qmin.y) + Q.set_right(qmax.x) + Q.set_bottom(qmax.y) + + + + def test_rect(self): + + P = Rect(0.298, 2, 4, 5) + + self.interval_basic(P[0], P[1]) + G = Rect(sqrt(2), sqrt(2), sqrt(3), sqrt(3)) + H = Rect.from_xywh(3.43232, 9.23214, 21.523, -0.31232) + + self.rect_basic(P, G) + self.rect_basic(G, H) + + lst = [Point(randint(-100, 100), randint(-100, 100)) for i in range(10)] + + R = Rect.from_list(lst) + + for p in lst: + self.assertTrue(R.contains(p)) + + self.assertAlmostEqual(min(lst).y, R.min().y) + self.assertAlmostEqual(max(lst).y, R.max().y) + + + + def test_optRect(self): + + P = OptRect(0.298, 2, 4, 5) + self.interval_basic(P.Rect[0], P.Rect[1]) + + + G = Rect(sqrt(2), sqrt(2), sqrt(3), sqrt(3)) + H = OptRect.from_rect(G) + + self.rect_basic(P.Rect, G) + + lst = [Point(randint(-100, 100), randint(-100, 100)) for i in range(10)] + + R = OptRect.from_list(lst) + + for p in lst: + self.assertTrue(R.Rect.contains(p)) + + self.assertAlmostEqual(min(lst).y, R.Rect.min().y) + self.assertAlmostEqual(max(lst).y, R.Rect.max().y) + + Q = OptRect() + self.assertFalse(Q) + self.assertTrue(P) + + self.assertTrue(Q.is_empty()) + self.assertFalse(P.is_empty()) + + self.assertTrue(P.contains_rect( P )) + self.assertTrue(P.contains_rect(Q)) + self.assertFalse(Q.contains_rect(P)) + self.assertFalse(P.intersects(Q)) + self.assertTrue(P.contains_rect(P.Rect)) + self.assertTrue(P.contains(P.Rect.midpoint())) + + self.assertEqual(P, OptRect.from_rect(P)) + + P.union_with(G) + P.union_with(H) + self.assertTrue(P.contains_rect(H)) + + P.intersect_with(G) + self.assertEqual(P, G) + + self.assertEqual( P|H, G ) + self.assertEqual( (P|R).Rect.min().x , min( P.Rect.min().x, R.Rect.min().x )) + + self.assertFalse(P & Q) + self.assertEqual(P, P&P) + + self.assertEqual( P & (R | H), (P & R) | (P & H) ) + + def test_intRect(self): + A = IntRect(2, 6, 9, 23) + B = IntRect.from_intervals(IntInterval(1, 5), IntInterval(8, 9)) + C = IntRect.from_points(IntPoint(1, 8), IntPoint(5, 9)) + + self.assertEqual(B, C) + + self.intrect_basic(A, B) + self.intrect_basic(B, C) + self.intrect_basic(C, A) + + def test_optIntRect(self): + P = OptIntRect(1, 2, 4, 5) + self.interval_basic(P.Rect[0], P.Rect[1]) + + + G = IntRect(2, 2, 3, 3) + H = OptIntRect.from_rect(G) + + self.intrect_basic(P.Rect, G) + + lst = [IntPoint(randint(-100, 100), randint(-100, 100)) for i in range(10)] + + R = OptIntRect.from_list(lst) + + for p in lst: + self.assertTrue(R.Rect.contains(p)) + + self.assertAlmostEqual(min(lst).y, R.Rect.min().y) + self.assertAlmostEqual(max(lst).y, R.Rect.max().y) + + Q = OptIntRect() + self.assertFalse(Q) + self.assertTrue(P) + + self.assertTrue(Q.is_empty()) + self.assertFalse(P.is_empty()) + + self.assertTrue(P.contains_rect( P )) + self.assertTrue(P.contains_rect( P.Rect )) + self.assertTrue(P.contains_rect(Q)) + self.assertFalse(Q.contains_rect(P)) + self.assertFalse(P.intersects(Q)) + self.assertTrue(P.contains_rect(P.Rect)) + self.assertTrue(P.contains(P.Rect.midpoint())) + + self.assertEqual(P, OptIntRect.from_rect(P)) + + P.union_with(G) + P.union_with(H) + self.assertTrue(P.contains_rect(H)) + + P.intersect_with(G) + self.assertEqual(P, G) + + self.assertEqual( P|H, G ) + self.assertEqual( (P|R).Rect.min().x , min( P.Rect.min().x, R.Rect.min().x )) + + self.assertFalse(P & Q) + self.assertEqual(P, P&P) + + self.assertEqual( P & (R | H), (P & R) | (P & H) ) + +unittest.main() diff --git a/src/cython/utils.py b/src/cython/utils.py new file mode 100644 index 0000000..1115d32 --- /dev/null +++ b/src/cython/utils.py @@ -0,0 +1,52 @@ +from Tkinter import * + +import math + +import cy2geom +from cy2geom import Point, Path + + +def Nagon(N): + """Return N-agon with side of length 1.""" + side = cy2geom.LineSegment(Point(-0.5, 0), Point(0.5, 0)) + angle = 2*math.pi/N + distance_to_center = 0.5 / math.tan(math.pi/N) + return Path.fromList( + [ side.transformed( + cy2geom.Translate(Point(0, -distance_to_center))* + cy2geom.Rotate(angle*i)) + for i in range(N) + ], + stitching = Path.STITCH_DISCONTINUOUS, + closed = True ) + +def draw(c, dt=0.001, batch=10, scale=20, x_offset = 400, y_offset = 300): + """Draw curve or path.""" + master = Tk() + w = Canvas(master, width=800, height=600) + w.pack() + n = 0 + t = 0 + points = [] + + if isinstance(c, Path): + maxt = c.size_default() + else: + maxt = 1 + + while (t < maxt): + t = n*dt + n+=1 + p = c(t) + points.extend( [ p.x * scale + x_offset, p.y * scale + y_offset] ) + + while points: + draw_points = tuple(points[:batch*2]) + if len(points) == 2: + break + del points[:batch*2] + + l = w.create_line(*draw_points) + w.grid() + + master.mainloop() diff --git a/src/cython/wrapped-pyobject.h b/src/cython/wrapped-pyobject.h new file mode 100644 index 0000000..bd316fb --- /dev/null +++ b/src/cython/wrapped-pyobject.h @@ -0,0 +1,237 @@ +#include "Python.h" +#include "2geom/generic-interval.h" +#include "2geom/generic-rect.h" +#include "2geom/d2.h" + + +namespace Geom{ + +//TODO! looks like memory leak +class WrappedPyObject{ +private: + PyObject* self; +public: + //~ int code; + + WrappedPyObject(){ + //~ printf("Created empty WPO at address %p\n", this); + self = NULL; + //~ code = 0; + } + + WrappedPyObject(WrappedPyObject const &other){ + self = other.getObj(); + //~ code = other.code; + //~ printf("COPY-CONSTRUCTOR %p, this WPO %p, other WPO %p\n", self, this, &other); + Py_XINCREF(self); + } + + WrappedPyObject(PyObject* arg){ + if (arg == NULL){ + //PyErr_Print(); + //~ code = c; + } + else{ + //~ printf("CONSTRUCTOR %p\n", arg); + Py_INCREF(arg); + self = arg; + //~ code = c; + } + } + + WrappedPyObject(int c){ + self = Py_BuildValue("i", c); + //~ printf("INT-OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other); + Py_INCREF(self); + } + + + ~WrappedPyObject(){ + //TODO Leaking memory + //~ printf("DECREF %p\n", self); + //Py_DECREF(self); + } + + PyObject* getObj() const { + return self; + } + + WrappedPyObject operator=(WrappedPyObject other){ + if (this != &other){ + self = other.getObj(); + //~ printf("OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other); + Py_XINCREF(self); + } + return *this; + } + + WrappedPyObject operator=(int other){ + + self = Py_BuildValue("i", other); + //~ printf("INT-OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other); + Py_INCREF(self); + + return *this; + } + + WrappedPyObject operator-() const { + PyObject * ret; + ret = PyObject_CallMethodObjArgs(self, Py_BuildValue("s", "__neg__"), NULL); + if (ret == NULL){ + Py_INCREF(Py_None); + return WrappedPyObject(Py_None); + } + //Py_INCREF(ret); + WrappedPyObject * retw = new WrappedPyObject(ret); + //Py_DECREF(ret); + return *retw; + } + + WrappedPyObject arithmetic(PyObject* const other, std::string method, std::string rmethod) const { + PyObject * ret; + //printf("%p %p\n", self, other); + ret = PyObject_CallMethodObjArgs(self, Py_BuildValue("s", method.c_str()), other, NULL); + if (ret == NULL){ + Py_INCREF(Py_None); + return WrappedPyObject(Py_None); + } + PyObject * isNI = PyObject_RichCompare(ret, Py_NotImplemented, Py_EQ); + if ( PyInt_AsLong(isNI) ){ + ret = PyObject_CallMethodObjArgs(other, Py_BuildValue("s", rmethod.c_str()), self, NULL); + } + if (ret == NULL){ + Py_INCREF(Py_None); + return WrappedPyObject(Py_None); + } + WrappedPyObject * retw = new WrappedPyObject(ret); + return *retw; + + } + + WrappedPyObject operator+(WrappedPyObject const other) const { + return arithmetic(other.getObj(), "__add__", "__radd__"); + } + + WrappedPyObject operator-(WrappedPyObject const other){ + return arithmetic(other.getObj(), "__sub__", "__rsub__"); + } + + WrappedPyObject operator*(WrappedPyObject const other){ + return arithmetic(other.getObj(), "__mul__", "__rmul__"); + } + + WrappedPyObject operator*(int other){ + PyObject* other_obj = Py_BuildValue("i", other); + //Py_INCREF(other_obj); + WrappedPyObject ret = arithmetic(other_obj, "__mul__", "__rmul__"); + //Py_DECREF(other_obj); + return ret; + } + + WrappedPyObject operator/(int other){ + PyObject* other_obj = Py_BuildValue("i", other); + Py_INCREF(other_obj); + WrappedPyObject ret = arithmetic(other_obj, "__div__", "__rdiv__"); + Py_DECREF(other_obj); + return ret; + } + + bool richcmp(WrappedPyObject const &other, int op) const { + PyObject * ret; + long retv; + ret = PyObject_RichCompare(self, other.getObj(), op); + retv = PyInt_AsLong(ret); + return retv; + } + + bool operator<(WrappedPyObject const &other) const { + return richcmp(other, Py_LT); + } + + bool operator<=(WrappedPyObject const &other) const { + return richcmp(other, Py_LE); + } + + bool operator>=(WrappedPyObject const &other) const{ + return richcmp(other, Py_GE); + } + + bool operator>(WrappedPyObject const &other) const { + return richcmp(other, Py_GT); + } + + bool operator==(WrappedPyObject const &other) const { + return richcmp(other, Py_EQ); + } + + + bool operator<(int const c) const { + return richcmp(WrappedPyObject(c), Py_LT); + } + + bool operator<=(int const c) const { + return richcmp(WrappedPyObject(c), Py_LE); + } + + bool operator>=(int const c) const{ + return richcmp(WrappedPyObject(c), Py_GE); + } + + bool operator>(int const c) const { + return richcmp(WrappedPyObject(c), Py_GT); + } + + bool operator==(int const c) const { + return richcmp(WrappedPyObject(c), Py_EQ); + } + + + WrappedPyObject operator+=(WrappedPyObject other){ + *this = *this + other; + return *this; + + } + + WrappedPyObject operator-=(WrappedPyObject other){ + *this = *this - other; + return *this; + } +}; + +typedef GenericInterval<WrappedPyObject> PyInterval; +typedef GenericOptInterval<WrappedPyObject> PyOptInterval; + +typedef GenericRect<WrappedPyObject> PyRect; +typedef GenericOptRect<WrappedPyObject> PyOptRect; + +typedef D2<WrappedPyObject> PyPoint; + +template<> +struct CoordTraits<WrappedPyObject> { + typedef PyPoint PointType; + typedef PyInterval IntervalType; + typedef PyOptInterval OptIntervalType; + typedef PyRect RectType; + typedef PyOptRect OptRectType; + + typedef + boost::equality_comparable< PyInterval + , boost::additive< PyInterval + , boost::multipliable< PyInterval + , boost::orable< PyInterval + , boost::arithmetic< PyInterval, WrappedPyObject + > > > > > + IntervalOps; + + typedef + boost::equality_comparable< PyRect + //, boost::equality_comparable< PyRect, IntRect + , boost::orable< PyRect + , boost::orable< PyRect, PyOptRect + , boost::additive< PyRect, PyPoint + //, boost::multipliable< Rect, Affine + > > > > //> > + RectOps; +}; + +} diff --git a/src/cython/wrapper.py b/src/cython/wrapper.py new file mode 100644 index 0000000..241782a --- /dev/null +++ b/src/cython/wrapper.py @@ -0,0 +1,360 @@ +import os
+import sys
+import argparse
+
+from pygccxml import parser, declarations
+
+class wType:
+ def __init__(self, c_str):
+ self.c_str = c_str
+ self.wrapped_types = ["bint", "double", "int", "unsigned int",
+ "Coord", "IntCoord", "Dim2", "size_t"]
+
+ def get_C(self):
+ """returns plain c string"""
+ return self.c_str
+
+ def get_Cython_type(self):
+ """Returns corresponding Cython type string"""
+ typestr= self.get_C()
+ typestr= typestr.replace(" const ", " ")
+ if typestr.find("::Geom::")==0:
+ typestr = typestr[len("::Geom::"):]
+ if typestr == "bool":
+ typestr = "bint"
+ return typestr
+
+ def get_Python_function_argument(self):
+ """Returns argument type to Python function call"""
+
+ typestr = self.get_Cython_type()
+ if typestr[-1]=="&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return typestr+" "
+ return "cy_"+typestr+" "
+ def get_Python_return(self):
+ """ Returns a string used to wrap a function returning this type
+ returns a string to be called with format( 'function call to c function ')
+ """
+ typestr = self.get_Cython_type()
+
+ if typestr[-1] == "&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return "return {}"
+ if typestr == "void":
+ return "{}"
+ if typestr[-1] == "*":
+ typestr = typestr[:-2]+"_p"
+ return "return wrap_"+typestr+"({})"
+
+ def get_wrap_method(self, delim):
+ """Returns wrap method in a form of a list of lines"""
+ ret = []
+ typestr = self.get_Cython_type()
+
+ if typestr[-1] == "&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return []
+ if typestr == "void":
+ return []
+ if typestr[-1] == "*":
+ typestr = typestr[:-2]+"_p"
+ ret.append("cdef cy_{} wrap_{}({} p):".format(typestr, typestr, typestr))
+ ret.append(delim+"cdef {} * retp = new {}()".format(typestr, typestr))
+ ret.append(delim+"retp[0] = p")
+ ret.append(delim+"cdef cy_{} r = cy_{}.__new__(cy_{})".format(typestr, typestr, typestr))
+ ret.append(delim+"r.thisptr = retp")
+ ret.append(delim+"return r")
+ return ret
+
+ def get_Python_pass_argument(self):
+ """ Returns argument to be passed to C function, to be called with .format(arg_name)"""
+ typestr = self.get_Cython_type()
+ if typestr[-1]=="&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return "{}"
+ if typestr[-1] == "*":
+ return "{}.thisptr"
+ return "deref( {}.thisptr )"
+
+class CythonWrapper:
+ def __init__(self, gn):
+ self.global_namespace = gn
+ self.delim = ' '
+
+ def wrap_constructor(self, constructor, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in constructor.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+ pxd_function_str = constructor.name+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ #python function
+ arguments_python = ["self"]
+ for argument in constructor.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format("__init__")+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in constructor.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "new "+constructor.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = "self.thisptr = {}".format(function_call)
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_operator(self, operator, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in operator.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+ boost_wrapped = ["operator{}=".format(i) for i in ["+", "-", "*", "/", "|", "&"]]
+ pxd_function_str = types_dict[operator.return_type.decl_string].get_Cython_type()+\
+ " "+operator.name+pxd_argument_str
+
+ if not (operator.name in boost_wrapped):
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ else:
+ pxd_lines.append(self.delim*2+"#"+pxd_function_str)
+
+ pxd_function_str = types_dict[operator.return_type.decl_string].get_Cython_type()+\
+ " "+operator.name[:-1]+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+
+ special_methods = {
+ "+" : "__add__",
+ "-" : "__sub__",
+ "*" : "__mul__",
+ "/" : "__div__",
+ "|" : "__or__",
+ "&" : "__and__",
+ "[]": "__getitem__",
+ "()": "__call__",
+ "==": "__richcmp__",
+ "!=": "__richcmp__"
+ }
+
+ if operator.name in boost_wrapped:
+ operation = operator.name[-2]
+ else:
+ operation = operator.name[ len("operator"): ]
+
+ if operation in special_methods:
+ python_name = special_methods[operation]
+ else:
+ python_name = operator.name
+
+ arguments_python = ["self"]
+ for argument in operator.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format(python_name)+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in operator.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "deref( self.thisptr ) "+operation+" {}".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[operator.return_type.decl_string].get_Python_return().format(function_call)
+
+ if (operator.arguments == []) and (operation == '-'):
+ declaration = "def __neg__(self):"
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_method(self, method, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in method.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+
+ pxd_function_str = types_dict[method.return_type.decl_string].get_Cython_type()+\
+ " "+method.name+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ #python function
+ arguments_python = ["self"]
+ for argument in method.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format(method.name)+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in method.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "self.thisptr."+method.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[method.return_type.decl_string].get_Python_return().format(function_call)
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_free_function(self, function, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+ decl_lines = []
+
+ pxd_arguments = []
+ for argument_type in function.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+
+ pxd_function_str = types_dict[function.return_type.decl_string].get_Cython_type()+\
+ " "+function.name+pxd_argument_str
+ pxd_lines.append(self.delim+pxd_function_str)
+
+ #python function
+ arguments_python = []
+ for argument in function.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def cy_{}".format(function.name)+"({}):".format(", ".join(arguments_python))
+
+ decl_lines.append("from _cy_??? import cy_{0} as {0}".format(function.name))
+
+ arguments_cython = []
+ for argument in function.arguments:
+
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = function.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[function.return_type.decl_string].get_Python_return().format(function_call)
+ pyx_lines.append(declaration)
+ pyx_lines.append(self.delim + return_statement)
+
+ return (pxd_lines, pyx_lines, decl_lines)
+
+
+ def wrap_class(self, class_name, class_file):
+ pxd_lines = []
+ pyx_lines = []
+ decl_lines = []
+ other_lines = []
+
+ pxd_lines.append("cdef extern from \"2geom/{}\" namespace \"Geom\":".format(class_file))
+ pxd_lines.append(self.delim+"cdef cppclass {}:".format(class_name))
+ pyx_lines.append("cdef class cy_{}:".format(class_name))
+ pyx_lines.append(self.delim+"cdef {}* thisptr".format(class_name))
+ #get_types
+ types_dict = self.collect_types(class_name)
+ #parse
+ c_class = self.global_namespace.namespace(name = "Geom").class_(name=class_name)
+
+ for member in c_class.get_members():
+ if isinstance(member, declarations.calldef.constructor_t):
+ pxd, pyx = self.wrap_constructor(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+ elif isinstance(member, declarations.calldef.member_operator_t):
+ pxd, pyx = self.wrap_operator(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+ elif isinstance(member, declarations.calldef.member_function_t):
+ pxd, pyx = self.wrap_method(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+
+ pyx_lines.append("#free functions:")
+ for ff in self.global_namespace.namespace(name = "Geom").free_funs():
+ if os.path.basename(ff.location.file_name) == class_file:
+ pxd, pyx, decl = self.wrap_free_function(ff, types_dict)
+ pxd_lines += map(lambda x: x[4:], pxd)
+ pyx_lines += pyx
+ decl_lines += decl
+
+ for ff in self.global_namespace.namespace(name = "Geom").free_operators():
+ if os.path.basename(ff.location.file_name) == class_file:
+ pxd, pyx = self.wrap_operator(ff, types_dict)
+ pxd_lines += map(lambda x: x[4:], pxd)
+ pyx_lines += pyx
+
+ pyx_lines.append("")
+ for ctype in types_dict:
+ wtype = types_dict[ctype]
+ for line in wtype.get_wrap_method(self.delim):
+ pyx_lines.append(line)
+ print "------"
+ for i in pxd_lines:
+ print i
+ print "------"
+ for i in pyx_lines:
+ print i
+ print "------"
+ for i in decl_lines:
+ print i
+
+
+ def collect_types(self, class_name):
+ types_set = set()
+ c_class = self.global_namespace.namespace(name = "Geom").class_(name=class_name)
+ for member in c_class.get_members():
+ if any([isinstance(member, declarations.calldef.constructor_t),
+ isinstance(member, declarations.calldef.member_operator_t),
+ isinstance(member, declarations.calldef.member_function_t)]):
+ for a_type in member.argument_types:
+ types_set.add(a_type.decl_string)
+ if isinstance(member, declarations.calldef.member_operator_t) or isinstance(member, declarations.calldef.member_function_t):
+ types_set.add(member.return_type.decl_string)
+ for ff in list(self.global_namespace.namespace(name = "Geom").free_funs()) + list(self.global_namespace.namespace(name = "Geom").free_operators()):
+ for a_type in ff.argument_types:
+ types_set.add(a_type.decl_string)
+ types_set.add(ff.return_type.decl_string)
+ types_dict = dict()
+
+ for f_type in types_set:
+
+ types_dict[f_type] = wType(f_type)
+ return types_dict
+
+def main():
+
+ #set up argument parser
+ cmd_parser = argparse.ArgumentParser()
+ cmd_parser.add_argument('--lib2geom_dir', action = 'store')
+ cmd_parser.add_argument('--file_name', action = 'store')
+ cmd_parser.add_argument('--class_name', action = 'store')
+ cmd_args = cmd_parser.parse_args()
+
+ #set up pygccxml
+ includes = [cmd_args.lib2geom_dir,
+ os.path.join(cmd_args.lib2geom_dir, 'src'),
+ "/usr/include/boost"]
+
+ config = parser.config_t(compiler='gcc',
+ include_paths=includes,
+ cflags="")
+
+ file_to_parse = [os.path.join(cmd_args.lib2geom_dir, "src", "2geom", cmd_args.file_name)]
+
+ decls = parser.parse( file_to_parse, config )
+ global_namespace = declarations.get_global_namespace( decls )
+ wrapper = CythonWrapper(global_namespace)
+ wrapper.wrap_class(cmd_args.class_name, cmd_args.file_name)
+
+main()
+
+#run with
+#python2 wrapper.py --lib2geom_dir ../../.. --file_name int-point.h --class_name IntPoint
+#from cython-bindings directory
diff --git a/src/performance-tests/CMakeLists.txt b/src/performance-tests/CMakeLists.txt new file mode 100644 index 0000000..60ceaa3 --- /dev/null +++ b/src/performance-tests/CMakeLists.txt @@ -0,0 +1,27 @@ +SET(2GEOM_PERFORMANCE_TESTS_SRC +example-performance-test +boolops-performance-test +bendpath-test +bezier-utils-test +parse-svg-test +path-operations-test +) + +add_custom_target(perf) + +OPTION(2GEOM_PERFORMANCE_TESTS + "Build the performance tests" + ON) +IF(2GEOM_PERFORMANCE_TESTS) + FOREACH(source ${2GEOM_PERFORMANCE_TESTS_SRC}) + ADD_EXECUTABLE(${source} ${source}.cpp) + target_link_libraries(${source} 2Geom::2geom) + add_dependencies(perf ${source}) + add_custom_command(TARGET perf COMMAND ${source}) + ENDFOREACH(source) +ENDIF() + +IF(WIN32 AND 2GEOM_BUILD_SHARED) + ADD_CUSTOM_TARGET(copy-perf ALL COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/src/2geom/lib2geom.dll ${CMAKE_BINARY_DIR}/src/performance-tests/lib2geom.dll) + ADD_DEPENDENCIES(copy-perf 2geom) +ENDIF() diff --git a/src/performance-tests/bendpath-test.cpp b/src/performance-tests/bendpath-test.cpp new file mode 100644 index 0000000..30fc500 --- /dev/null +++ b/src/performance-tests/bendpath-test.cpp @@ -0,0 +1,128 @@ +/*
+ * Test performance of
+ *
+ * Copyright (C) Authors 2007-2013
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ * Steren Giannini <steren.giannini@gmail.com>
+ *
+ * the test part was taken from Inkscape's Bend Path LPE (and simplified a bit)
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <iostream>
+#include <ctime>
+
+#include "2geom/svg-path-parser.h"
+#include "2geom/pathvector.h"
+#include "2geom/path.h"
+#include "2geom/d2.h"
+#include "2geom/piecewise.h"
+#include "2geom/sbasis.h"
+using namespace Geom;
+
+static char const *path_str =
+ "M -460,523.79076 C -460.65718,514.47985 -460.79228,505.75495 -460.43635,497.52584 -460.08041,489.29674 "
+ "-459.23345,481.56342 -457.92649,474.23566 -456.61954,466.9079 -454.85259,459.9857 -452.65671,453.37885 "
+ "-450.46083,446.77199 -447.836,440.48046 -444.81328,434.41405 -441.79056,428.34764 -438.36994,422.50633 "
+ "-434.58247,416.79991 -430.79501,411.0935 -426.64069,405.52196 -422.15057,399.99508 -417.66045,394.4682 "
+ "-412.83452,388.98598 -407.70384,383.4582 -402.57316,377.93041 -397.13772,372.35705 -391.42857,366.6479 "
+ "-378.14065,353.35999 -362.09447,346.86737 -344.03764,345.52929 -325.98081,344.19121 -305.91333,348.00766 "
+ "-284.58283,355.33787 -263.25234,362.66809 -240.65882,373.51207 -217.5499,386.22904 -194.44098,398.94602 "
+ "-170.81666,413.53598 -147.42457,428.35818 -124.03248,443.18037 -100.87262,458.2348 -78.692608,471.88067 "
+ "-56.512594,485.52655 -35.312431,497.76389 -15.839741,506.95191 3.632948,516.13994 21.378163,522.27865 "
+ "36.648281,523.72729 51.918398,525.17592 64.713417,521.93448 74.285714,512.36218 76.124134,510.52376 "
+ "78.521676,506.73937 81.393111,501.43833 84.264546,496.13729 87.609874,489.31958 91.343869,481.41453 "
+ "95.077864,473.50947 99.200524,464.51706 103.62662,454.86661 108.05272,445.21615 112.78226,434.90765 "
+ "117.73001,424.37041 122.67776,413.83317 127.84371,403.0672 133.14266,392.50178 138.4416,381.93637 "
+ "143.87352,371.57153 149.3532,361.83656 154.83288,352.10159 160.36031,342.99649 165.85028,334.95058 "
+ "171.34024,326.90466 176.79273,319.91793 182.12252,314.41968 189.27211,307.04412 196.34366,301.16926 "
+ "203.28385,296.57404 210.22405,291.97882 217.03288,288.66325 223.65704,286.40628 230.2812,284.1493 "
+ "236.72068,282.95093 242.92215,282.59009 249.12362,282.22926 255.08709,282.70597 260.75924,283.79918 "
+ "266.43138,284.89239 271.8122,286.60209 276.84836,288.70724 281.88453,290.81239 286.57604,293.31298 "
+ "290.86959,295.98796 295.16313,298.66294 299.0587,301.51232 302.50297,304.31504 305.94724,307.11776 "
+ "308.94022,309.87382 311.42857,312.36218 317.70879,318.6424 325.20531,326.50894 333.6587,335.29576 "
+ "342.11209,344.08258 351.52235,353.78967 361.63006,363.75101 371.73777,373.71235 382.54293,383.92792 "
+ "393.78611,393.7317 405.02929,403.53547 416.71049,412.92744 428.57029,421.24156 440.43008,429.55569 "
+ "452.46847,436.79197 464.42603,442.28436 476.38359,447.77676 488.26032,451.52527 499.7968,452.86385 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "562.85714,426.6479";
+
+static char const *bend_str =
+ "M -489.19983,473.24818 C -478.58599,460.43092 -465.31914,447.40133 -450.91614,434.95746 -436.51315,422.51359 "
+ "-420.97403,410.65543 -405.81565,400.18102 -390.65727,389.70661 -375.87963,380.61595 -362.99962,373.70708 "
+ "-350.11961,366.79821 -339.13723,362.07112 -331.56936,360.32386 -323.49864,358.4605 -311.32246,357.96153 "
+ "-296.07049,358.53595 -280.81852,359.11038 -262.49077,360.7582 -242.11692,363.18844 -221.74308,365.61867 "
+ "-199.32313,368.83132 -175.88677,372.53539 -152.45042,376.23947 -127.99765,380.43497 -103.55815,384.83091 "
+ "-90.798706,387.12596 -78.042879,389.47564 -65.437198,391.83855 -52.831517,394.20146 -40.375984,396.57759 "
+ "-28.217127,398.92554 -16.058269,401.27349 -4.1960885,403.59326 7.2228872,405.84343 18.641863,408.09361 "
+ "29.617633,410.27419 40.00367,412.34378 50.09967,414.35557 59.638414,416.26247 68.485308,418.02645 "
+ "77.332201,419.79043 85.487246,421.41149 92.815847,422.85159 100.14445,424.29169 106.64661,425.55083 "
+ "112.18773,426.59098 117.72885,427.63113 122.30894,428.45228 125.7934,429.01641 134.20502,430.37823 "
+ "142.47705,429.25391 150.64115,426.35391 158.80525,423.45391 166.86142,418.77822 174.84134,413.03729 "
+ "182.82125,407.29637 190.72491,400.49022 198.58398,393.32929 206.44305,386.16836 214.25752,378.65265 "
+ "222.05908,371.49262 227.06421,366.89906 232.06402,362.4519 237.06688,358.33874 242.06974,354.22557 "
+ "247.07564,350.44641 252.09294,347.18884 257.11025,343.93127 262.13896,341.1953 267.18744,339.16853 "
+ "272.23593,337.14176 277.30418,335.82419 282.40056,335.40342 293.00641,334.52778 302.22965,341.02775 "
+ "310.80162,351.57579 319.37358,362.12384 327.29427,376.71996 335.29503,392.03663 343.29579,407.3533 "
+ "351.37662,423.39051 360.26886,436.82075 369.1611,450.25099 378.86474,461.07425 390.11114,465.96301 "
+ "401.98566,471.1248 423.45174,470.72496 449.88663,467.14319 476.32151,463.56143 507.72518,456.79775 "
+ "539.47487,449.23188 571.22455,441.66601 603.32025,433.29795 631.13918,426.50743 658.9581,419.7169 "
+ "688.21455,391.64677 702.85715,390.39104";
+
+
+// adapted from Inkscape's BendPath LPE
+Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in,
+ Piecewise<D2<SBasis> > const &bend_path)
+{
+ Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(bend_path, 2, .1);
+ uskeleton = remove_short_cuts(uskeleton, .01);
+ Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+ n = force_continuity(remove_short_cuts(n, .1));
+
+ D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pwd2_in);
+ Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[1]);
+ Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[0]);
+
+ Geom::OptRect bbox = bounds_exact(pwd2_in);
+ Interval bboxHorizontal = (*bbox)[Geom::X];
+ Interval bboxVertical = (*bbox)[Geom::Y];
+
+ x -= bboxHorizontal.min();
+ y -= bboxVertical.middle();
+
+ double scaling = uskeleton.cuts.back() / bboxHorizontal.extent();
+
+ if (scaling != 1.0) {
+ x *= scaling;
+ y *= scaling;
+ }
+
+ Piecewise<D2<SBasis> > output = compose(uskeleton, x) + y * compose(n, x);
+ return output;
+}
+
+
+int main()
+{
+ for (int rep = 0; rep < 3; rep++) {
+ const int num_repeats = 100;
+ PathVector path1 = parse_svg_path(path_str);
+ PathVector path2 = parse_svg_path(bend_str);
+ Piecewise<D2<SBasis> > pwd2_path = path1[0].toPwSb();
+ Piecewise<D2<SBasis> > pwd2_bend = path2[0].toPwSb();
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > result = doEffect_pwd2(pwd2_path, pwd2_bend);
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "Bend paths (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+}
+
+
+
+//
\ No newline at end of file diff --git a/src/performance-tests/bezier-utils-test.cpp b/src/performance-tests/bezier-utils-test.cpp new file mode 100644 index 0000000..111d939 --- /dev/null +++ b/src/performance-tests/bezier-utils-test.cpp @@ -0,0 +1,133 @@ +/*
+ * Test performance of bezier-util fitting code
+ *
+ * Copyright (C) authors 2013
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <iostream>
+#include <ctime>
+
+#include "2geom/bezier-utils.h"
+#include "2geom/path.h"
+using namespace Geom;
+
+//static const Point data[] = { Point(0, 0), Point(1, 0), Point( 2, 0 ), Point( 1, 1 )};
+static const Point data[] = { Point( 0, 0 ), Point( 1, 0 ), Point( 2, 0 ), Point( 1, 1 ),
+ Point( 1, 2 ), Point( 1, 3 ), Point( 3, 0 ), Point( 4, 0 ),
+ Point( 2, 0 ), Point( 1, 1 ), Point( 1, 2 ), Point( 2, 0 ),
+ Point( 1, 0 ), Point( 2, 0 ), Point( 1, 3 ), Point( 1, 4 ),
+ Point( 1, 2 ), Point( 1, 1 ), Point( 0, 0 ), Point( 1, 0 ),
+ Point( 2, 0 ), Point( 1, 1 ), Point( 1, 2 ), Point( 1, 3 ),
+ Point( 4, 0 ), Point( 2, 0 ), Point( 1, 1 ), Point( 1, 2 ),
+ Point( 2, 0 ), Point( 1, 0 ), Point( 2, 0 ), Point( 1, 3 ),
+ Point( 1, 4 ), Point( 1, 2 ), Point( 1, 1 ), Point( 2, 1 ) };
+
+static const unsigned int data_len = sizeof(data)/sizeof(Point);
+
+
+
+// code test with 2geom types
+Path interpolateToPath(std::vector<Point> const &points, double tolerance_sq, unsigned max_beziers)
+{
+ Geom::Point *b = new Geom::Point[max_beziers*4];
+ Geom::Point *points_array = new Geom::Point[4 * points.size()]; // for safety, do a copy into a vector. I think it
+ // is possible to simply pass &points[0] as a
+ // Point[], but I am not sure
+ for (size_t i = 0; i < points.size(); ++i) {
+ points_array[i] = points.at(i);
+ }
+
+ int const n_segs = Geom::bezier_fit_cubic_r(b, points_array, points.size(), tolerance_sq, max_beziers);
+
+ Geom::Path fit;
+ if (n_segs > 0) {
+ fit.start(b[0]);
+ for (int c = 0; c < n_segs; c++) {
+ fit.appendNew<Geom::CubicBezier>(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]);
+ }
+ }
+
+ delete[] b;
+ delete[] points_array;
+
+ return fit;
+};
+
+
+Path interpolateToPath2(std::vector<Point> const &points, double tolerance_sq, unsigned max_beziers)
+{
+ std::vector<Point> b(max_beziers * 4);
+
+ int const n_segs = Geom::bezier_fit_cubic_r(b.data(), points.data(), points.size(), tolerance_sq, max_beziers);
+
+ Geom::Path fit;
+ if (n_segs > 0) {
+ fit.start(b[0]);
+ for (int c = 0; c < n_segs; c++) {
+ fit.appendNew<Geom::CubicBezier>(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]);
+ }
+ }
+
+ return fit;
+};
+
+
+int main()
+{
+ std::vector<Point> data_vector;
+ for (auto i : data) {
+ data_vector.push_back(i);
+ }
+
+ const int num_repeats = 2000;
+
+ unsigned max_beziers = data_len*2;
+ double tolerance_sq = 0.01;
+
+ for (int rep = 0; rep < 3; rep++) {
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ Point *bezier = new Point[max_beziers*4]; // large array on stack = not good, so allocate on heap
+ int n_segs = bezier_fit_cubic_r(bezier, data, data_len, tolerance_sq, max_beziers);
+ (void) n_segs;
+ delete[] bezier;
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "bezier_fit_cubic_r C-array (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+
+ for (int rep = 0; rep < 3; rep++) {
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ Path path = interpolateToPath(data_vector, tolerance_sq, max_beziers);
+ int n_segs = path.size();
+ (void) n_segs;
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "bezier_fit_cubic_r 2Geom interoperability (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+
+ for (int rep = 0; rep < 3; rep++) {
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ Path path = interpolateToPath2(data_vector, tolerance_sq, max_beziers);
+ int n_segs = path.size();
+ (void) n_segs;
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "bezier_fit_cubic_r 2Geom interoperability 2nd version (" << num_repeats
+ << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms " << std::endl;
+ }
+
+}
+
+
+
+//
\ No newline at end of file diff --git a/src/performance-tests/boolops-performance-test.cpp b/src/performance-tests/boolops-performance-test.cpp new file mode 100644 index 0000000..49d87aa --- /dev/null +++ b/src/performance-tests/boolops-performance-test.cpp @@ -0,0 +1,101 @@ +/** + * \file + * \brief Performance test for Boolops + *//* + * 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/svg-path-parser.h> +#include <iostream> +#include <glib.h> + +using namespace Geom; + +int main(int argc, char **argv) +{ + if (argc != 4) { + std::cout << "boolops: wrong number of arguments; no tests run!" << std::endl; + exit(0); // TODO: add suitable arguments in CMake target / actually do some tests here + } + + PathVector a = read_svgd(argv[2]); + PathVector b = read_svgd(argv[3]); + unsigned const ops = atoi(argv[1]); + + OptRect abox = a.boundsExact(); + OptRect bbox = a.boundsExact(); + if (!abox) { + std::cout << argv[1] << " contains an empty path" << std::endl; + std::exit(1); + } + if (!bbox) { + std::cout << argv[2] << " contains an empty path" << std::endl; + std::exit(1); + } + + a *= Translate(-abox->corner(0)); + b *= Translate(-bbox->corner(0)); + + long num_intersections = 0; + long num_outcv = 0; + + // for reproducibility. + g_random_set_seed(1234); + + for (unsigned i = 0; i < ops; ++i) { + Point delta; + delta[X] = g_random_double_range(-bbox->width(), abox->width()); + delta[Y] = g_random_double_range(-bbox->height(), abox->height()); + + PathVector bt = b * Translate(delta); + + PathIntersectionGraph pig(a, bt); + PathVector x = pig.getIntersection(); + num_intersections += pig.intersectionPoints().size(); + num_outcv += x.curveCount(); + } + + std::cout << "Completed " << ops << " operations.\n" + << "Total intersections: " << num_intersections << "\n" + << "Total output curves: " << num_outcv << std::endl; + + return 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/performance-tests/example-performance-test.cpp b/src/performance-tests/example-performance-test.cpp new file mode 100644 index 0000000..0ca706d --- /dev/null +++ b/src/performance-tests/example-performance-test.cpp @@ -0,0 +1,217 @@ +#include <iostream> +#include <ctime> + +#include "2geom/svg-path-parser.h" +#include "2geom/pathvector.h" +#include "2geom/path.h" +#include "2geom/d2.h" +#include "2geom/piecewise.h" +#include "2geom/sbasis.h" +using namespace Geom; + +static char const *many_subpaths = + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 " + "M 240,495.21933 648.57143,400.93361 C 190.6809,177.20365 122.07669,234.71591 104.6,312.5 "; + + + + +int main() +{ + for (int rep = 0; rep < 3; rep++) { + const int num_repeats = 100; + PathVector path = parse_svg_path(many_subpaths); + std::clock_t start = std::clock(); + for (int i = 0; i < num_repeats; i++) { + PathVector path2 = path.reversed(); + PathVector path3 = path2.reversed().reversed(); + } + std::clock_t stop = std::clock(); + std::cout << "Reverse paths (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms " << std::endl; + } +} + + + +// diff --git a/src/performance-tests/parse-svg-test.cpp b/src/performance-tests/parse-svg-test.cpp new file mode 100644 index 0000000..5147d75 --- /dev/null +++ b/src/performance-tests/parse-svg-test.cpp @@ -0,0 +1,88 @@ +/*
+ * Test performance of
+ *
+ * Copyright (C) Authors 2007-2013
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ * Steren Giannini <steren.giannini@gmail.com>
+ *
+ * the test part was taken from Inkscape's Bend Path LPE (and simplified a bit)
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <iostream>
+#include <ctime>
+
+#include "2geom/svg-path-parser.h"
+#include "2geom/pathvector.h"
+#include "2geom/path.h"
+#include "2geom/d2.h"
+#include "2geom/piecewise.h"
+#include "2geom/sbasis.h"
+using namespace Geom;
+
+static char const *path_str =
+ "M -460,523.79076 C -460.65718,514.47985 -460.79228,505.75495 -460.43635,497.52584 -460.08041,489.29674 "
+ "-459.23345,481.56342 -457.92649,474.23566 -456.61954,466.9079 -454.85259,459.9857 -452.65671,453.37885 "
+ "-450.46083,446.77199 -447.836,440.48046 -444.81328,434.41405 -441.79056,428.34764 -438.36994,422.50633 "
+ "-434.58247,416.79991 -430.79501,411.0935 -426.64069,405.52196 -422.15057,399.99508 -417.66045,394.4682 "
+ "-412.83452,388.98598 -407.70384,383.4582 -402.57316,377.93041 -397.13772,372.35705 -391.42857,366.6479 "
+ "-378.14065,353.35999 -362.09447,346.86737 -344.03764,345.52929 -325.98081,344.19121 -305.91333,348.00766 "
+ "-284.58283,355.33787 -263.25234,362.66809 -240.65882,373.51207 -217.5499,386.22904 -194.44098,398.94602 "
+ "-170.81666,413.53598 -147.42457,428.35818 -124.03248,443.18037 -100.87262,458.2348 -78.692608,471.88067 "
+ "-56.512594,485.52655 -35.312431,497.76389 -15.839741,506.95191 3.632948,516.13994 21.378163,522.27865 "
+ "36.648281,523.72729 51.918398,525.17592 64.713417,521.93448 74.285714,512.36218 76.124134,510.52376 "
+ "78.521676,506.73937 81.393111,501.43833 84.264546,496.13729 87.609874,489.31958 91.343869,481.41453 "
+ "95.077864,473.50947 99.200524,464.51706 103.62662,454.86661 108.05272,445.21615 112.78226,434.90765 "
+ "117.73001,424.37041 122.67776,413.83317 127.84371,403.0672 133.14266,392.50178 138.4416,381.93637 "
+ "143.87352,371.57153 149.3532,361.83656 154.83288,352.10159 160.36031,342.99649 165.85028,334.95058 "
+ "171.34024,326.90466 176.79273,319.91793 182.12252,314.41968 189.27211,307.04412 196.34366,301.16926 "
+ "203.28385,296.57404 210.22405,291.97882 217.03288,288.66325 223.65704,286.40628 230.2812,284.1493 "
+ "236.72068,282.95093 242.92215,282.59009 249.12362,282.22926 255.08709,282.70597 260.75924,283.79918 "
+ "266.43138,284.89239 271.8122,286.60209 276.84836,288.70724 281.88453,290.81239 286.57604,293.31298 "
+ "290.86959,295.98796 295.16313,298.66294 299.0587,301.51232 302.50297,304.31504 305.94724,307.11776 "
+ "308.94022,309.87382 311.42857,312.36218 317.70879,318.6424 325.20531,326.50894 333.6587,335.29576 "
+ "342.11209,344.08258 351.52235,353.78967 361.63006,363.75101 371.73777,373.71235 382.54293,383.92792 "
+ "393.78611,393.7317 405.02929,403.53547 416.71049,412.92744 428.57029,421.24156 440.43008,429.55569 "
+ "452.46847,436.79197 464.42603,442.28436 476.38359,447.77676 488.26032,451.52527 499.7968,452.86385 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "562.85714,426.6479 C -478.58599,460.43092 -465.31914,447.40133 -450.91614,434.95746 -436.51315,422.51359 "
+ "-420.97403,410.65543 -405.81565,400.18102 -390.65727,389.70661 -375.87963,380.61595 -362.99962,373.70708 "
+ "-350.11961,366.79821 -339.13723,362.07112 -331.56936,360.32386 -323.49864,358.4605 -311.32246,357.96153 "
+ "-296.07049,358.53595 -280.81852,359.11038 -262.49077,360.7582 -242.11692,363.18844 -221.74308,365.61867 "
+ "-199.32313,368.83132 -175.88677,372.53539 -152.45042,376.23947 -127.99765,380.43497 -103.55815,384.83091 "
+ "-90.798706,387.12596 -78.042879,389.47564 -65.437198,391.83855 -52.831517,394.20146 -40.375984,396.57759 "
+ "-28.217127,398.92554 -16.058269,401.27349 -4.1960885,403.59326 7.2228872,405.84343 18.641863,408.09361 "
+ "29.617633,410.27419 40.00367,412.34378 50.09967,414.35557 59.638414,416.26247 68.485308,418.02645 "
+ "77.332201,419.79043 85.487246,421.41149 92.815847,422.85159 100.14445,424.29169 106.64661,425.55083 "
+ "112.18773,426.59098 117.72885,427.63113 122.30894,428.45228 125.7934,429.01641 134.20502,430.37823 "
+ "142.47705,429.25391 150.64115,426.35391 158.80525,423.45391 166.86142,418.77822 174.84134,413.03729 "
+ "182.82125,407.29637 190.72491,400.49022 198.58398,393.32929 206.44305,386.16836 214.25752,378.65265 "
+ "222.05908,371.49262 227.06421,366.89906 232.06402,362.4519 237.06688,358.33874 242.06974,354.22557 "
+ "247.07564,350.44641 252.09294,347.18884 257.11025,343.93127 262.13896,341.1953 267.18744,339.16853 "
+ "272.23593,337.14176 277.30418,335.82419 282.40056,335.40342 293.00641,334.52778 302.22965,341.02775 "
+ "310.80162,351.57579 319.37358,362.12384 327.29427,376.71996 335.29503,392.03663 343.29579,407.3533 "
+ "351.37662,423.39051 360.26886,436.82075 369.1611,450.25099 378.86474,461.07425 390.11114,465.96301 "
+ "401.98566,471.1248 423.45174,470.72496 449.88663,467.14319 476.32151,463.56143 507.72518,456.79775 "
+ "539.47487,449.23188 571.22455,441.66601 603.32025,433.29795 631.13918,426.50743 658.9581,419.7169 "
+ "688.21455,391.64677 702.85715,390.39104";
+
+int main()
+{
+ for (int rep = 0; rep < 3; rep++) {
+ const int num_repeats = 100;
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ PathVector path = parse_svg_path(path_str);
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "Parse SVG-d (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+}
+
+
+
+//
\ No newline at end of file diff --git a/src/performance-tests/path-operations-test.cpp b/src/performance-tests/path-operations-test.cpp new file mode 100644 index 0000000..c55ddda --- /dev/null +++ b/src/performance-tests/path-operations-test.cpp @@ -0,0 +1,100 @@ +/*
+ * Test performance of
+ *
+ * Copyright (C) Authors 2007-2013
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
+ * Steren Giannini <steren.giannini@gmail.com>
+ *
+ * the test part was taken from Inkscape's Bend Path LPE (and simplified a bit)
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include <iostream>
+#include <ctime>
+
+#include "2geom/svg-path-parser.h"
+#include "2geom/pathvector.h"
+#include "2geom/path.h"
+#include "2geom/d2.h"
+#include "2geom/piecewise.h"
+#include "2geom/sbasis.h"
+#include "2geom/transforms.h"
+using namespace Geom;
+
+static char const *path_str =
+ "M -460,523.79076 C -460.65718,514.47985 -460.79228,505.75495 -460.43635,497.52584 -460.08041,489.29674 "
+ "-459.23345,481.56342 -457.92649,474.23566 -456.61954,466.9079 -454.85259,459.9857 -452.65671,453.37885 "
+ "-450.46083,446.77199 -447.836,440.48046 -444.81328,434.41405 -441.79056,428.34764 -438.36994,422.50633 "
+ "-434.58247,416.79991 -430.79501,411.0935 -426.64069,405.52196 -422.15057,399.99508 -417.66045,394.4682 "
+ "-412.83452,388.98598 -407.70384,383.4582 -402.57316,377.93041 -397.13772,372.35705 -391.42857,366.6479 "
+ "-378.14065,353.35999 -362.09447,346.86737 -344.03764,345.52929 -325.98081,344.19121 -305.91333,348.00766 "
+ "-284.58283,355.33787 -263.25234,362.66809 -240.65882,373.51207 -217.5499,386.22904 -194.44098,398.94602 "
+ "-170.81666,413.53598 -147.42457,428.35818 -124.03248,443.18037 -100.87262,458.2348 -78.692608,471.88067 "
+ "-56.512594,485.52655 -35.312431,497.76389 -15.839741,506.95191 3.632948,516.13994 21.378163,522.27865 "
+ "36.648281,523.72729 51.918398,525.17592 64.713417,521.93448 74.285714,512.36218 76.124134,510.52376 "
+ "78.521676,506.73937 81.393111,501.43833 84.264546,496.13729 87.609874,489.31958 91.343869,481.41453 "
+ "95.077864,473.50947 99.200524,464.51706 103.62662,454.86661 108.05272,445.21615 112.78226,434.90765 "
+ "117.73001,424.37041 122.67776,413.83317 127.84371,403.0672 133.14266,392.50178 138.4416,381.93637 "
+ "143.87352,371.57153 149.3532,361.83656 154.83288,352.10159 160.36031,342.99649 165.85028,334.95058 "
+ "171.34024,326.90466 176.79273,319.91793 182.12252,314.41968 189.27211,307.04412 196.34366,301.16926 "
+ "203.28385,296.57404 210.22405,291.97882 217.03288,288.66325 223.65704,286.40628 230.2812,284.1493 "
+ "236.72068,282.95093 242.92215,282.59009 249.12362,282.22926 255.08709,282.70597 260.75924,283.79918 "
+ "266.43138,284.89239 271.8122,286.60209 276.84836,288.70724 281.88453,290.81239 286.57604,293.31298 "
+ "290.86959,295.98796 295.16313,298.66294 299.0587,301.51232 302.50297,304.31504 305.94724,307.11776 "
+ "308.94022,309.87382 311.42857,312.36218 317.70879,318.6424 325.20531,326.50894 333.6587,335.29576 "
+ "342.11209,344.08258 351.52235,353.78967 361.63006,363.75101 371.73777,373.71235 382.54293,383.92792 "
+ "95.077864,473.50947 99.200524,464.51706 103.62662,454.86661 108.05272,445.21615 112.78226,434.90765 "
+ "117.73001,424.37041 122.67776,413.83317 127.84371,403.0672 133.14266,392.50178 138.4416,381.93637 "
+ "143.87352,371.57153 149.3532,361.83656 154.83288,352.10159 160.36031,342.99649 165.85028,334.95058 "
+ "171.34024,326.90466 176.79273,319.91793 182.12252,314.41968 189.27211,307.04412 196.34366,301.16926 "
+ "203.28385,296.57404 210.22405,291.97882 217.03288,288.66325 223.65704,286.40628 230.2812,284.1493 "
+ "236.72068,282.95093 242.92215,282.59009 249.12362,282.22926 255.08709,282.70597 260.75924,283.79918 "
+ "266.43138,284.89239 271.8122,286.60209 276.84836,288.70724 281.88453,290.81239 286.57604,293.31298 "
+ "290.86959,295.98796 295.16313,298.66294 299.0587,301.51232 302.50297,304.31504 305.94724,307.11776 "
+ "308.94022,309.87382 311.42857,312.36218 317.70879,318.6424 325.20531,326.50894 333.6587,335.29576 "
+ "342.11209,344.08258 351.52235,353.78967 361.63006,363.75101 371.73777,373.71235 382.54293,383.92792 "
+ "393.78611,393.7317 405.02929,403.53547 416.71049,412.92744 428.57029,421.24156 440.43008,429.55569 "
+ "452.46847,436.79197 464.42603,442.28436 476.38359,447.77676 488.26032,451.52527 499.7968,452.86385 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "511.33327,454.20243 522.52948,453.13109 533.12602,448.98378 543.72255,444.83647 553.7194,437.61319 "
+ "562.85714,426.6479";
+
+
+int main()
+{
+ for (int rep = 0; rep < 3; rep++) {
+ PathVector path1 = parse_svg_path(path_str);
+ const int num_repeats = 1000;
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ path1 *= Translate(Point(3.,1.));
+ path1 *= Translate(Point(4.,1.));
+ path1 *= Translate(Point(5.,1.));
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "PathVector *= Translate (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+
+ for (int rep = 0; rep < 3; rep++) {
+ PathVector path1 = parse_svg_path(path_str);
+ const int num_repeats = 1000;
+ std::clock_t start = std::clock();
+ for (int i = 0; i < num_repeats; i++) {
+ path1 *= Affine(Translate(Point(3.,1.)));
+ path1 *= Affine(Translate(Point(4.,1.)));
+ path1 *= Affine(Translate(Point(5.,1.)));
+ }
+ std::clock_t stop = std::clock();
+ std::cout << "PathVector *= Affine (" << num_repeats << "x): " << (stop - start) * (1000. / CLOCKS_PER_SEC) << " ms "
+ << std::endl;
+ }
+}
+
+
+
+//
\ No newline at end of file diff --git a/src/py2geom/CMakeLists.txt b/src/py2geom/CMakeLists.txt new file mode 100644 index 0000000..8e21e74 --- /dev/null +++ b/src/py2geom/CMakeLists.txt @@ -0,0 +1,118 @@ +SET(2GEOM_BOOST_PYTHON_SRC +etc.cpp +point.cpp +interval.cpp +transforms.cpp +rect.cpp +line.cpp +circle.cpp +ellipse.cpp +conic.cpp +crossing.cpp +sbasis.cpp +bezier.cpp +linear.cpp +pw.cpp +d2.cpp +parser.cpp +path.cpp +ray.cpp +#convexcover.cpp +py2geom.cpp +# curves +#curve.cpp +#bezier-curve.cpp +) + +IF (WIN32) + SET(BUILD_BOOST_PYTHON_STATIC FALSE) +ELSE (WIN32) + SET(BUILD_BOOST_PYTHON_STATIC FALSE) +ENDIF (WIN32) +IF (BUILD_BOOST_PYTHON_STATIC) + SET(BOOST_PYTHON_SRC "C:/boost_1_42_0/libs/python/src") + #define BOOST_PYTHON_STATIC_LIB + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBOOST_PYTHON_STATIC_LIB") + SET(2GEOM_BOOST_PYTHON_SRC + ${2GEOM_BOOST_PYTHON_SRC} + ${BOOST_PYTHON_SRC}/dict.cpp + ${BOOST_PYTHON_SRC}/errors.cpp + ${BOOST_PYTHON_SRC}/exec.cpp + ${BOOST_PYTHON_SRC}/import.cpp + ${BOOST_PYTHON_SRC}/list.cpp + ${BOOST_PYTHON_SRC}/long.cpp + ${BOOST_PYTHON_SRC}/module.cpp + ${BOOST_PYTHON_SRC}/numeric.cpp + ${BOOST_PYTHON_SRC}/object_operators.cpp + ${BOOST_PYTHON_SRC}/object_protocol.cpp + ${BOOST_PYTHON_SRC}/slice.cpp + ${BOOST_PYTHON_SRC}/str.cpp + ${BOOST_PYTHON_SRC}/tuple.cpp + ${BOOST_PYTHON_SRC}/wrapper.cpp + + ${BOOST_PYTHON_SRC}/converter/arg_to_python_base.cpp + ${BOOST_PYTHON_SRC}/converter/builtin_converters.cpp + ${BOOST_PYTHON_SRC}/converter/from_python.cpp + ${BOOST_PYTHON_SRC}/converter/registry.cpp + ${BOOST_PYTHON_SRC}/converter/type_id.cpp + + ${BOOST_PYTHON_SRC}/object/class.cpp + ${BOOST_PYTHON_SRC}/object/enum.cpp + ${BOOST_PYTHON_SRC}/object/function.cpp + ${BOOST_PYTHON_SRC}/object/function_doc_signature.cpp + ${BOOST_PYTHON_SRC}/object/inheritance.cpp + ${BOOST_PYTHON_SRC}/object/iterator.cpp + ${BOOST_PYTHON_SRC}/object/life_support.cpp + ${BOOST_PYTHON_SRC}/object/pickle_support.cpp + ${BOOST_PYTHON_SRC}/object/stl_iterator.cpp + ) +ENDIF (BUILD_BOOST_PYTHON_STATIC) + +IF(PYCAIRO_FOUND) + SET(2GEOM_BOOST_PYTHON_SRC + ${2GEOM_BOOST_PYTHON_SRC} + cairo-helpers.cpp + ) +ENDIF(PYCAIRO_FOUND) + + +OPTION(2GEOM_BOOST_PYTHON + "Build a python binding with Boost.Python" + OFF) +IF(2GEOM_BOOST_PYTHON) + FIND_PACKAGE(Python3 COMPONENTS Development Interpreter REQUIRED) + + SET(Boost_DEBUG TRUE) + SET(Boost_REALPATH FALSE) + FIND_PACKAGE(Boost 1.42.0 REQUIRED) + FIND_PACKAGE(Boost REQUIRED COMPONENTS python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}) + + IF (WIN32) + SET_TARGET_PROPERTIES(py2geom PROPERTIES SUFFIX ".pyd") + ELSEIF (APPLE) + SET(CMAKE_MACOSX_RPATH FALSE) + SET(CMAKE_SHARED_LIBRARY_SUFFIX ".so") + ENDIF(WIN32) + + INCLUDE_DIRECTORIES( src/ ${Python3_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ) + ADD_LIBRARY(py2geom SHARED ${2GEOM_BOOST_PYTHON_SRC}) + SET_TARGET_PROPERTIES(py2geom PROPERTIES PREFIX "_") + + IF (BUILD_BOOST_PYTHON_STATIC) + TARGET_LINK_LIBRARIES(py2geom 2geom ${Python3_LIBRARIES}) + ELSE (BUILD_BOOST_PYTHON_STATIC) + TARGET_LINK_LIBRARIES(py2geom 2geom ${Boost_LIBRARIES} ${Python3_LIBRARIES}) + ENDIF (BUILD_BOOST_PYTHON_STATIC) + + set_property(TARGET 2geom PROPERTY POSITION_INDEPENDENT_CODE ON) # we need -fPIC to link py2geom against 2geom + + IF(PYCAIRO_FOUND) + TARGET_LINK_LIBRARIES(py2geom ${Cairo_LIBRARIES}) + ENDIF(PYCAIRO_FOUND) + + INSTALL(TARGETS py2geom DESTINATION "${Python3_SITEARCH}/py2geom") + INSTALL(FILES "${CMAKE_CURRENT_SOURCE_DIR}/__init__.py" DESTINATION "${Python3_SITEARCH}/py2geom") +ENDIF(2GEOM_BOOST_PYTHON) + + + diff --git a/src/py2geom/__init__.py b/src/py2geom/__init__.py new file mode 100644 index 0000000..06faf3c --- /dev/null +++ b/src/py2geom/__init__.py @@ -0,0 +1,26 @@ +# Copyright 2006, 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. + +from py2geom._py2geom import * diff --git a/src/py2geom/bezier.cpp b/src/py2geom/bezier.cpp new file mode 100644 index 0000000..f0664c1 --- /dev/null +++ b/src/py2geom/bezier.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2006, 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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/bezier.h" +#include "2geom/point.h" + +using namespace boost::python; + +double bezier_getitem(Geom::Bezier const& p, int index) +{ + int D = p.size(); + if (index < 0) + { + index = D + index; + } + if ((index < 0) || (index > (D - 1))) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + boost::python::throw_error_already_set(); + } + return p[index]; +} + +void wrap_bezier() { + //bezier.h + + class_<Geom::Bezier>("Bezier", init<double>()) + .def(init<double, double>()) + .def(init<double, double, double>()) + .def(init<double, double, double, double>()) + .def(self_ns::str(self)) + //TODO: add important vector funcs + .def("__getitem__", &bezier_getitem) + + .def("isZero", &Geom::Bezier::isZero) + .def("isFinite", &Geom::Bezier::isFinite) + .def("at0", (double (Geom::Bezier::*)() const) &Geom::Bezier::at0) + .def("at1", (double (Geom::Bezier::*)() const) &Geom::Bezier::at1) + .def("valueAt", &Geom::Bezier::valueAt) + .def("toSBasis", &Geom::Bezier::toSBasis) + + .def(self + float()) + .def(self - float()) + + .def(self * float()) + ; +}; + +/* + 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/py2geom/cairo-helpers.cpp b/src/py2geom/cairo-helpers.cpp new file mode 100644 index 0000000..c341439 --- /dev/null +++ b/src/py2geom/cairo-helpers.cpp @@ -0,0 +1,164 @@ +#include <boost/python.hpp> +#include <cairo.h> +#include <toys/path-cairo.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/utils.h> +#include <sstream> +#include <pycairo/pycairo.h> +#include "cairo-helpers.h" + +using namespace Geom; + + +void +cairo_move_to (cairo_t *cr, Geom::Point p1) { + cairo_move_to(cr, p1[0], p1[1]); +} + +void +cairo_line_to (cairo_t *cr, Geom::Point p1) { + cairo_line_to(cr, p1[0], p1[1]); +} + +void +cairo_curve_to (cairo_t *cr, Geom::Point p1, + Geom::Point p2, Geom::Point p3) { + cairo_curve_to(cr, p1[0], p1[1], + p2[0], p2[1], + p3[0], p3[1]); +} + +void cairo_rectangle(cairo_t *cr, Rect const& r) { + cairo_rectangle(cr, r.left(), r.top(), r.width(), r.height()); +} + +void cairo_convex_hull(cairo_t *cr, ConvexHull const& ch) { + if(ch.empty()) return; + cairo_move_to(cr, ch.back()); + for(unsigned i = 0; i < ch.size(); i++) { + cairo_line_to(cr, ch[i]); + } +} + +void cairo_curve(cairo_t *cr, Curve const& c) { + if(LineSegment const* line_segment = dynamic_cast<LineSegment const*>(&c)) { + cairo_line_to(cr, (*line_segment)[1][0], (*line_segment)[1][1]); + } + else if(QuadraticBezier const *quadratic_bezier = dynamic_cast<QuadraticBezier const*>(&c)) { + std::vector<Point> points = quadratic_bezier->controlPoints(); + Point b1 = points[0] + (2./3) * (points[1] - points[0]); + Point b2 = b1 + (1./3) * (points[2] - points[0]); + cairo_curve_to(cr, b1[0], b1[1], + b2[0], b2[1], + points[2][0], points[2][1]); + } + else if(CubicBezier const *cubic_bezier = dynamic_cast<CubicBezier const*>(&c)) { + std::vector<Point> points = cubic_bezier->controlPoints(); + cairo_curve_to(cr, points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]); + } +// else if(EllipticalArc const *svg_elliptical_arc = dynamic_cast<EllipticalArc *>(c)) { +// //TODO: get at the innards and spit them out to cairo +// } + else { + //this case handles sbasis as well as all other curve types + Path sbasis_path = cubicbezierpath_from_sbasis(c.toSBasis(), 0.1); + + //recurse to convert the new path resulting from the sbasis to svgd + for(Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) { + cairo_curve(cr, *iter); + } + } +} + +void cairo_path(cairo_t *cr, Path const &p) { + cairo_move_to(cr, p.initialPoint()[0], p.initialPoint()[1]); + if(p.size() == 0) { // naked moveto + cairo_move_to(cr, p.finalPoint()+Point(8,0)); + cairo_line_to(cr, p.finalPoint()+Point(-8,0)); + cairo_move_to(cr, p.finalPoint()+Point(0,8)); + cairo_line_to(cr, p.finalPoint()+Point(0,-8)); + return; + } + + for(Path::const_iterator iter(p.begin()), end(p.end()); iter != end; ++iter) { + cairo_curve(cr, *iter); + } + if(p.closed()) + cairo_close_path(cr); +} + +void cairo_path_stitches(cairo_t *cr, Path const &p) { + Path::const_iterator iter; + for ( iter = p.begin() ; iter != p.end() ; ++iter ) { + Curve const &c=*iter; + if (dynamic_cast<Path::StitchSegment const *>(&c)) { + cairo_move_to(cr, c.initialPoint()[X], c.initialPoint()[Y]); + cairo_line_to(cr, c.finalPoint()[X], c.finalPoint()[Y]); + + //std::stringstream s; + //s << L1(c.finalPoint() - c.initialPoint()); + //std::string ss = s.str(); + //draw_text(cr, c.initialPoint()+Point(5,5), ss.c_str(), false, "Serif 6"); + + //std::cout << c.finalPoint() - c.initialPoint() << std::endl; + } + } +} + +void cairo_path(cairo_t *cr, PathVector const &p) { + PathVector::const_iterator it; + for(it = p.begin(); it != p.end(); ++it) { + cairo_path(cr, *it); + } +} + +void cairo_path_stitches(cairo_t *cr, PathVector const &p) { + PathVector::const_iterator it; + for ( it = p.begin() ; it != p.end() ; ++it ) { + cairo_path_stitches(cr, *it); + } +} + + +void cairo_d2_sb(cairo_t *cr, D2<SBasis> const &B) { + cairo_path(cr, path_from_sbasis(B, 0.1)); +} + +void cairo_d2_pw_sb(cairo_t *cr, D2<Piecewise<SBasis> > const &p) { + cairo_pw_d2_sb(cr, sectionize(p)); +} + +void cairo_pw_d2_sb(cairo_t *cr, Piecewise<D2<SBasis> > const &p) { + for(unsigned i = 0; i < p.size(); i++) + cairo_d2_sb(cr, p[i]); +} + +#if PY_MAJOR_VERSION < 3 +static Pycairo_CAPI_t *Pycairo_CAPI = 0; +#endif + +cairo_t* cairo_t_from_object(boost::python::object cr) { +#if PY_MAJOR_VERSION < 3 + if(!Pycairo_CAPI) + Pycairo_IMPORT; +#else + import_cairo(); +#endif + PycairoContext* pcc = (PycairoContext*)cr.ptr(); + assert(PyObject_TypeCheck(pcc, &PycairoContext_Type)); + return PycairoContext_GET(pcc); +} + + + +/* + 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/py2geom/cairo-helpers.h b/src/py2geom/cairo-helpers.h new file mode 100644 index 0000000..daa2960 --- /dev/null +++ b/src/py2geom/cairo-helpers.h @@ -0,0 +1,27 @@ +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/d2.h> +#include <2geom/piecewise.h> +#include <2geom/path.h> +#include <2geom/convex-hull.h> +#include <vector> + +typedef struct _cairo cairo_t; + +void cairo_move_to(cairo_t *cr, Geom::Point p1); +void cairo_line_to(cairo_t *cr, Geom::Point p1); +void cairo_curve_to(cairo_t *cr, Geom::Point p1, Geom::Point p2, Geom::Point p3); + +void cairo_curve(cairo_t *cr, Geom::Curve const &c); +void cairo_rectangle(cairo_t *cr, Geom::Rect const &r); +void cairo_convex_hull(cairo_t *cr, Geom::ConvexHull const &r); +void cairo_path(cairo_t *cr, Geom::Path const &p); +void cairo_path(cairo_t *cr, Geom::PathVector const &p); +void cairo_path_stitches(cairo_t *cr, Geom::Path const &p); +void cairo_path_stitches(cairo_t *cr, Geom::PathVector const &p); + +void cairo_d2_sb(cairo_t *cr, Geom::D2<Geom::SBasis> const &p); +void cairo_d2_pw_sb(cairo_t *cr, Geom::D2<Geom::Piecewise<Geom::SBasis> > const &p); +void cairo_pw_d2_sb(cairo_t *cr, Geom::Piecewise<Geom::D2<Geom::SBasis> > const &p); + +cairo_t* cairo_t_from_object(boost::python::object cr); diff --git a/src/py2geom/circle.cpp b/src/py2geom/circle.cpp new file mode 100644 index 0000000..55e683b --- /dev/null +++ b/src/py2geom/circle.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2009 Ricardo Lafuente <r@sollec.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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/point.h" +#include "2geom/circle.h" +#include "2geom/exception.h" + +// i can't get these to work +//Geom::Point (Geom::Circle::*center_point)() = (Geom::Point (*)() const)&Geom::Circle::center; +//Geom::Coord (Geom::Circle::*center_coord)(Geom::Dim2 const& d) = &Geom::Circle::center; + +using namespace boost::python; + +void wrap_circle() { + + class_<Geom::Circle>("Circle", init<double, double, double>()) + .def(init<double, double, double, double>()) + + .def("setCoefficients", &Geom::Circle::setCoefficients) + .def("fit", &Geom::Circle::fit) + .add_property("radius", &Geom::Circle::radius) + + .add_property("center", (Geom::Point (Geom::Circle::*)() const )&Geom::Circle::center) + //.def("center", center) + // requires EllipticalArc + //.def("arc", &Geom::Circle::arc) + ; + +}; + +/* + 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/py2geom/conic.cpp b/src/py2geom/conic.cpp new file mode 100644 index 0000000..8e5360e --- /dev/null +++ b/src/py2geom/conic.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2009 Nathan Hurst <njh@njhurst.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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> +#include <optional> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/point.h" +#include "2geom/line.h" +#include "2geom/conicsec.h" + +using namespace boost::python; + +// helpers for point +static tuple xAx_to_tuple(Geom::xAx const& a) +{ + return make_tuple(a.c[0], a.c[1], a.c[2], a.c[3], a.c[4], a.c[5]); +} + +static Geom::xAx tuple_to_xAx(boost::python::tuple const& t) +{ + return Geom::xAx(extract<double>(t[0]), + extract<double>(t[1]), + extract<double>(t[2]), + extract<double>(t[3]), + extract<double>(t[4]), + extract<double>(t[5]) + ); +} + +static std::vector<double> xax_roots1(Geom::xAx const & xax, Geom::Point const &a, Geom::Point const &b) { return xax.roots(a,b); } +static std::vector<double> xax_roots2(Geom::xAx const & xax, Geom::Line const &l) { return xax.roots(l); } +static Geom::SBasis homo_eval_at(Geom::xAx const & xax, + Geom::SBasis const & x, + Geom::SBasis const & y, + Geom::SBasis const & w + ) { + return xax.evaluate_at(x, y, w); +} + +static Geom::SBasis xy_eval_at(Geom::xAx const & xax, + Geom::SBasis const & x, + Geom::SBasis const & y + ) { + return xax.evaluate_at(x, y); +} + +static Geom::D2<Geom::SBasis> wrap_rq_to_cubic_sb(Geom::RatQuad const & rq) { + return rq.toCubic().toSBasis(); +} + +static Geom::D2<Geom::SBasis> wrap_rq_to_cubic_sb_l(Geom::RatQuad const & rq, double l) { + return rq.toCubic(l).toSBasis(); +} + +static std::vector<Geom::Point> wrap_rq_to_cubic_l(Geom::RatQuad const & rq, double l) { + return rq.toCubic(l).controlPoints(); +} + +static std::vector<Geom::Point> wrap_rq_to_cubic(Geom::RatQuad const & rq) { + return wrap_rq_to_cubic_l(rq, rq.lambda()); +} + +static tuple wrap_rq_split(Geom::RatQuad const & rq) { + Geom::RatQuad a, b; + rq.split(a, b); + return make_tuple(a, b); +} + +static object wrap_xax_to_curve(Geom::xAx const & xax, Geom::Rect const & r) { + std::optional<Geom::RatQuad> oc = xax.toCurve(r); + return oc?object(*oc):object(); +} + + + +void wrap_conic() { + //conicsec.h + def("intersect", (std::vector<Geom::Point> (*)(Geom::xAx const &, Geom::xAx const &))Geom::intersect); + + class_<Geom::xAx>("xAx", init<>()) + .def(init<double, double, double, double, double, double>()) + .def(init<Geom::xAx const&>()) + .def_readonly("c", &Geom::xAx::c) + .def("tuple", xAx_to_tuple) + + .def("from_tuple", tuple_to_xAx) + .staticmethod("from_tuple") + .def("fromPoint", Geom::xAx::fromPoint) + .staticmethod("fromPoint") + .def("fromPoints", Geom::xAx::fromPoints) + .staticmethod("fromPoints") + .def("fromLine", (Geom::xAx (*)(Geom::Line l))Geom::xAx::fromLine) + .staticmethod("fromLine") + .def(self_ns::str(self)) + .def("valueAt", &Geom::xAx::valueAt) + + .def("implicit_form_coefficients", &Geom::xAx::implicit_form_coefficients) + + .def("isDegenerate", &Geom::xAx::isDegenerate) + .def("roots", &xax_roots1) + .def("roots", &xax_roots2) + .def("extrema", &Geom::xAx::extrema) + .def("gradient", &Geom::xAx::gradient) + .def("crossings", &Geom::xAx::crossings) + .def("evaluate_at", &xy_eval_at) + .def("evaluate_at", &homo_eval_at) + .def("toCurve", &wrap_xax_to_curve) + .def(self - self) + .def(self + float()) + .def(self * float()) + ; + + class_<Geom::RatQuad>("RatQuad", init<>()) + .def(init<Geom::Point, Geom::Point, Geom::Point, double>()) + .def_readonly("P", &Geom::RatQuad::P) + .def_readonly("w", &Geom::RatQuad::w) + .def_readonly("lam", &Geom::RatQuad::lambda) + //.def(self_ns::str(self)) + .def("at0", &Geom::RatQuad::at0) + .def("at1", &Geom::RatQuad::at1) + .def("pointAt", &Geom::RatQuad::pointAt) + + .def("toCubic", &wrap_rq_to_cubic) + .def("toCubic", &wrap_rq_to_cubic_l) + .def("toCubicSBasis", &wrap_rq_to_cubic_sb) + .def("toCubicSBasis", &wrap_rq_to_cubic_sb_l) + + .def("split", &wrap_rq_split) + .def("hermite", &Geom::RatQuad::hermite) + .def("homogeneous", &Geom::RatQuad::homogeneous) + .def("fromPointsTangents", &Geom::RatQuad::fromPointsTangents) + .staticmethod("fromPointsTangents") + ; + implicitly_convertible<Geom::Point,tuple>(); +}; + +/* + 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/py2geom/convexcover.cpp b/src/py2geom/convexcover.cpp new file mode 100644 index 0000000..3f0b270 --- /dev/null +++ b/src/py2geom/convexcover.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2009 Nathan Hurst <njh@njhurst.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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/point.h" +#include "2geom/exception.h" +#include "2geom/convex-cover.h" + + + +using namespace boost::python; + +PointVec ch_boundary(Geom::ConvexHull const &ch) { + return ch.boundary; +} + +int furthest_index(Geom::ConvexHull const &ch, Geom::Point const &p) { + return (int)(ch.furthest(p) - &ch.boundary[0]); +} + +void wrap_convex_cover() { + class_<Geom::ConvexHull>("ConvexHull", init<>()) + .def(init<PointVec>()) + + .def("merge", &Geom::ConvexHull::merge) + .def("contains_point", &Geom::ConvexHull::contains_point) + .def("strict_contains_point", &Geom::ConvexHull::strict_contains_point) + + .add_property("boundary", &ch_boundary) + .add_property("is_clockwise", &Geom::ConvexHull::is_clockwise) + .add_property("top_point_first", &Geom::ConvexHull::top_point_first) + .add_property("meets_invariants", &Geom::ConvexHull::meets_invariants) + .add_property("empty", &Geom::ConvexHull::empty) + + .add_property("singular", &Geom::ConvexHull::singular) + + .add_property("linear", &Geom::ConvexHull::linear) + .add_property("is_degenerate", &Geom::ConvexHull::is_degenerate) + + .def("centroid_and_area", &Geom::ConvexHull::centroid_and_area) + .def("area", &Geom::ConvexHull::area) + .def("furthest", &furthest_index) + + .def("is_left", &Geom::ConvexHull::is_left) + .def("is_strict_left", &Geom::ConvexHull::is_strict_left) + .def("find_left", &Geom::ConvexHull::find_left) + .def("find_strict_left", &Geom::ConvexHull::find_strict_left) + .def("narrowest_diameter", &Geom::ConvexHull::narrowest_diameter) + ; + + }; + +/* + 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/py2geom/crossing.cpp b/src/py2geom/crossing.cpp new file mode 100644 index 0000000..6b4f050 --- /dev/null +++ b/src/py2geom/crossing.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2009 Nathan Hurst <njh@njhurst.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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/crossing.h" +#include "2geom/point.h" + +using namespace boost::python; + +void wrap_crossing() { + //line.h + + class_<Geom::Crossing>("Crossing", init<>()) + .def(init<double, double, bool>()) + .def(init<double, double, unsigned, unsigned, bool>()) + .def_readonly("ta", &Geom::Crossing::ta) + .def_readonly("tb", &Geom::Crossing::tb) + .def_readonly("a", &Geom::Crossing::a) + .def_readonly("b", &Geom::Crossing::b) + .def_readonly("dir", &Geom::Crossing::dir) + //.def(self_ns::str(self)) + .def("getOther", &Geom::Crossing::getOther) + .def("getTime", &Geom::Crossing::getTime) + .def("getOtherTime", &Geom::Crossing::getOtherTime) + .def("onIx", &Geom::Crossing::onIx) + ; +}; + +/* + 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/py2geom/d2.cpp b/src/py2geom/d2.cpp new file mode 100644 index 0000000..d646ea5 --- /dev/null +++ b/src/py2geom/d2.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2006, 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 <boost/python.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" +#include <2geom/point.h> +#include <2geom/sbasis.h> +#include <2geom/d2.h> +#include <2geom/piecewise.h> + +using namespace boost::python; + +void wrap_d2() { + class_<Geom::D2<Geom::SBasis> >("D2SBasis", init<>()) + .def(init<Geom::SBasis,Geom::SBasis>()) + .def("__getitem__", python_getitem<Geom::D2<Geom::SBasis>,Geom::SBasis,2>) + + .def("isZero", &Geom::D2<Geom::SBasis>::isZero) + .def("isFinite", &Geom::D2<Geom::SBasis>::isFinite) + .def("at0", &Geom::D2<Geom::SBasis>::at0) + .def("at1", &Geom::D2<Geom::SBasis>::at1) + .def("pointAt", &Geom::D2<Geom::SBasis>::valueAt) + .def("valueAndDerivatives", &Geom::D2<Geom::SBasis>::valueAndDerivatives) + .def("toSBasis", &Geom::D2<Geom::SBasis>::toSBasis) + + .def(-self) + .def(self + self) + .def(self - self) + .def(self += self) + .def(self -= self) + .def(self + Geom::Point()) + .def(self - Geom::Point()) + .def(self += Geom::Point()) + .def(self -= Geom::Point()) + .def(self * Geom::Point()) + .def(self / Geom::Point()) + .def(self *= Geom::Point()) + .def(self /= Geom::Point()) + .def(self * float()) + .def(self / float()) + .def(self *= float()) + .def(self /= float()) + ; + def("reverse", ((Geom::D2<Geom::SBasis> (*)(Geom::D2<Geom::SBasis> const &b))&Geom::reverse)); + def("portion", ((Geom::D2<Geom::SBasis> (*)(Geom::D2<Geom::SBasis> const &a, Geom::Coord f, Geom::Coord t))&Geom::portion)); + //TODO: dot, rot90, cross, compose, composeEach, eval ops, derivative, integral, L2, portion, multiply ops, + + class_<Geom::D2<Geom::Piecewise<Geom::SBasis> > >("D2PiecewiseSBasis") + .def("__getitem__", python_getitem<Geom::D2<Geom::Piecewise<Geom::SBasis> >,Geom::Piecewise<Geom::SBasis>,2>) + + //.def("isZero", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::isZero) + //.def("isFinite", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::isFinite) + //.def("at0", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::at0) + //.def("at1", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::at1) + //.def("pointAt", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::valueAt) + //.def("valueAndDerivatives", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::valueAndDerivatives) + //.def("toSBasis", &Geom::D2<Geom::Piecewise<Geom::SBasis> >::toSBasis) + + ; +}; + +/* + 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/py2geom/ellipse.cpp b/src/py2geom/ellipse.cpp new file mode 100644 index 0000000..7592bd4 --- /dev/null +++ b/src/py2geom/ellipse.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2009 Ricardo Lafuente <r@sollec.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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/point.h" +#include "2geom/ellipse.h" +#include "2geom/circle.h" +#include "2geom/exception.h" +#include "2geom/d2.h" + + +void (Geom::Ellipse::*ellipse_set1)(Geom::Point const &, Geom::Point const &, double) = &Geom::Ellipse::set; +void (Geom::Ellipse::*ellipse_set2)(double, double, double, double, double) = &Geom::Ellipse::set; +std::vector<Geom::Coord> (Geom::Ellipse::*ellipse_coefficients)() const = &Geom::Ellipse::coefficients; + +// i can't get these to work +//Geom::Point (Geom::Ellipse::*center_point)() = (Geom::Point (*)() const)&Geom::Ellipse::center; +// Geom::Coord (Geom::Ellipse::*center_coord)(Geom::Dim2 const& d) = &Geom::Ellipse::center; + +using namespace boost::python; + +void wrap_ellipse() { + class_<Geom::Ellipse>("Ellipse", init<double, double, double, double, double>()) + .def(init<double, double, double, double, double, double>()) + // needs to be mapped to PointVec, but i can't figure out how + .def(init<Geom::Circle>()) + + .def("set", ellipse_set1) + .def("set", ellipse_set2) + .def("setCoefficients", &Geom::Ellipse::setCoefficients) + .def("fit", &Geom::Ellipse::fit) + + .def("center", (Geom::Point (Geom::Ellipse::*)() const) &Geom::Ellipse::center) + // .def("center", center_coord) + + .def("ray", &Geom::Ellipse::ray) + .def("rotationAngle", &Geom::Ellipse::rotationAngle) + .def("coefficients", ellipse_coefficients) + .def(self * Geom::Affine()) + .def(self *= Geom::Affine()) + // requires EllipticalArc + //.def("arc", &Geom::Ellipse::arc) + + ; + +}; + +/* + 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/py2geom/etc.cpp b/src/py2geom/etc.cpp new file mode 100644 index 0000000..57fdf4c --- /dev/null +++ b/src/py2geom/etc.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2009 Ricardo Lafuente <r@sollec.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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/point.h" +#include "2geom/sbasis.h" +#include "2geom/exception.h" + +using namespace boost::python; + +void wrap_etc() { + // needed for roots + class_<DoubleVec >("DoubleVec") + .def(vector_indexing_suite<std::vector<double> >()) + ; + class_<PointVec>("PointVec") + .def(vector_indexing_suite<std::vector<Geom::Point> >()) + ; + // sbasis is a subclass of + class_<LinearVec >("LinearVec") + .def(vector_indexing_suite<std::vector<Geom::Linear> >()) + ; + +}; + +/* + 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/py2geom/helpers.h b/src/py2geom/helpers.h new file mode 100644 index 0000000..4502fba --- /dev/null +++ b/src/py2geom/helpers.h @@ -0,0 +1,59 @@ +/* + * Copyright 2006, 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 SEEN_PY2GEOM_HELPERS_H +#define SEEN_PY2GEOM_HELPERS_H + +#include <boost/python.hpp> + +template <typename T, typename R, unsigned D> +R python_getitem(T const& p, int index) +{ + unsigned i = index; + if (index < 0) + { + i = index = D + index; + } + if ((index < 0) || (i > (D - 1))) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + boost::python::throw_error_already_set(); + } + return p[i]; +} + +#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/py2geom/interval.cpp b/src/py2geom/interval.cpp new file mode 100644 index 0000000..1ac2d60 --- /dev/null +++ b/src/py2geom/interval.cpp @@ -0,0 +1,159 @@ +/* + * Copyright 2008 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 <boost/python.hpp> +#include <boost/python/implicit.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/interval.h" + +using namespace boost::python; + + +// helpers for interval +static tuple interval_to_tuple(Geom::Interval const& p) +{ + return make_tuple(p.min(), p.max()); +} + +static Geom::Interval tuple_to_interval(boost::python::tuple const& t) +{ + return Geom::Interval(extract<double>(t[0]), extract<double>(t[1])); +} + +static str interval_repr(Geom::Interval const& p) +{ + return str("(" + str(p.min()) + ", " + str(p.max()) + ")"); +} + +static Geom::Interval from_optinterval(Geom::OptInterval const & ivl) +{ + return *ivl; +} + + +static bool wrap_contains_coord(Geom::Interval const &x, Geom::Coord val) { + return x.contains(val); +} + +static bool wrap_contains_ivl(Geom::Interval const &x, Geom::Interval val) { + return x.contains(val); +} + +static bool wrap_interiorContains_coord(Geom::Interval const &x, Geom::Coord val) { + return x.interiorContains(val); +} + +static bool wrap_interiorContains_ivl(Geom::Interval const &x, Geom::Interval val) { + return x.interiorContains(val); +} + +void wrap_interval() { + def("interval_to_tuple", interval_to_tuple); + def("tuple_to_interval", tuple_to_interval); + + //def("unify", Geom::unify(Geom::Interval const &, Geom::Interval const &)); + //def("intersect", Geom::intersect(Geom::Interval const &, Geom::Interval const &)); + + //TODO: add overloaded constructors + class_<Geom::Interval>("Interval", init<double, double>()) + .def("__str__", interval_repr) + .def("__repr__", interval_repr) + .def("tuple", interval_to_tuple) + + .def("from_tuple", tuple_to_interval) + .staticmethod("from_tuple") + + .def("min", &Geom::Interval::min) + .def("max", &Geom::Interval::max) + .def("middle", &Geom::Interval::middle) + .def("extent", &Geom::Interval::extent) + .def("isSingular", &Geom::Interval::isSingular) + //TODO: fix for overloading + .def("contains", wrap_contains_coord) + .def("contains", wrap_contains_ivl) + .def("interiorContains", wrap_interiorContains_coord) + .def("interiorContains", wrap_interiorContains_ivl) + .def("intersects", &Geom::Interval::intersects) + + .def("setMin", &Geom::Interval::setMin) + .def("setMax", &Geom::Interval::setMax) + .def("expandTo", &Geom::Interval::expandTo) + .def("from_array", &Geom::Interval::from_array) + .def("expandBy", &Geom::Interval::expandBy) + .def("unionWith", &Geom::Interval::unionWith) + + .def(self == self) + .def(self != self) + + .def(self + float()) + .def(self - float()) + .def(self += float()) + .def(self -= float()) + + .def(-self) + + .def(self * float()) + .def(self / float()) + .def(self *= float()) + .def(self /= float()) + + .def(self + self) + .def(self - self) + .def(self += self) + .def(self -= self) + .def(self * self) + .def(self *= self) + ; + class_<Geom::OptInterval>("OptInterval", init<double, double>()) + .def(init<Geom::Interval>()) + .def("unionWith", &Geom::OptInterval::unionWith) + .def("empty", &Geom::OptInterval::empty) + .def("toInterval", from_optinterval) + + .def(self == self) + .def(self != self) + ; + implicitly_convertible<Geom::Interval,tuple>(); +// TODO: is this possible? +// implicitly_convertible<tuple,Geom::Interval>(); + +}; + +/* + 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/py2geom/line.cpp b/src/py2geom/line.cpp new file mode 100644 index 0000000..0df4360 --- /dev/null +++ b/src/py2geom/line.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2009 Nathan Hurst <njh@njhurst.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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/line.h" +//#include "2geom/bezier-curve.h" +#include "2geom/point.h" + +using namespace boost::python; + +template <typename S, typename T> +object wrap_intersection(S const& a, T const& b) { + Geom::OptCrossing oc = intersection(a, b); + return oc?object(*oc):object(); +} + +std::vector<Geom::Coord> (Geom::Line::*coefficients_vec)() const = &Geom::Line::coefficients; + +void wrap_line() { + //line.h + + def("intersection", wrap_intersection<Geom::Line, Geom::Line>); + def("intersection", wrap_intersection<Geom::Line, Geom::Ray>); + //def("intersection", wrap_intersection<Geom::Line, Geom::LineSegment>); + def("intersection", wrap_intersection<Geom::Ray, Geom::Line>); + def("intersection", wrap_intersection<Geom::Ray, Geom::Ray>); + //def("intersection", wrap_intersection<Geom::Ray, Geom::LineSegment>); + //def("intersection", wrap_intersection<Geom::LineSegement, Geom::Line>); + //def("intersection", wrap_intersection<Geom::LineSegement, Geom::Ray>); + //def("intersection", wrap_intersection<Geom::LineSegement, Geom::LineSegment>); + class_<Geom::Line>("Line", init<>()) + .def(init<Geom::Point const&, Geom::Coord>()) + .def(init<Geom::Point const&, Geom::Point const&>()) + .def(init<double, double, double>()) + //.def(self_ns::str(self)) + .def("valueAt", &Geom::Line::valueAt) + + .def("coefficients", coefficients_vec) + .def("isDegenerate", &Geom::Line::isDegenerate) + .def("pointAt", &Geom::Line::pointAt) + .def("roots", &Geom::Line::roots) + .def("nearestTime", &Geom::Line::nearestTime) + .def("reverse", &Geom::Line::reverse) + //.def("portion", &Geom::Line::portion) + //.def("segment", &Geom::Line::segment) + .def("derivative", &Geom::Line::derivative) + .def("transformed", &Geom::Line::transformed) + .def("normal", &Geom::Line::normal) + .def("normalAndDist", &Geom::Line::normalAndDist) + .def("setPoints", &Geom::Line::setPoints) + .def("setCoefficients", &Geom::Line::setCoefficients) + ; + +}; + +/* + 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/py2geom/linear.cpp b/src/py2geom/linear.cpp new file mode 100644 index 0000000..419af64 --- /dev/null +++ b/src/py2geom/linear.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2006, 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 <boost/python.hpp> +#include <boost/python/implicit.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/linear.h" +#include "2geom/point.h" +#include "2geom/sbasis.h" + +using namespace boost::python; + +// helpers for bezord +tuple bezord_to_tuple(Geom::Linear const& b) +{ + return make_tuple(b[0], b[1]); +} + +Geom::Linear tuple_to_bezord(boost::python::tuple const& t) +{ + return Geom::Linear(extract<double>(t[0]), extract<double>(t[1])); +} + +str bezord_repr(Geom::Linear const& b) +{ + return str("<" + str(b[0]) + ", " + str(b[1]) + ">"); +} + +void wrap_linear() { + def("lerp", (double (*)(double, double, double))&Geom::lerp); + def("reverse", (Geom::Linear (*)(Geom::Linear const &))&Geom::reverse); + def("bounds_fast", (Geom::OptInterval (*)(Geom::Linear const &))&Geom::bounds_fast); + def("bounds_exact", (Geom::OptInterval (*)(Geom::Linear const &))&Geom::bounds_exact); + def("bounds_local", (Geom::OptInterval (*)(Geom::Linear const &))&Geom::bounds_local); + + class_<Geom::Linear>("Linear", init<double, double>()) + .def("__str__", bezord_repr) + .def("__repr__", bezord_repr) + .def("__getitem__", python_getitem<Geom::Linear,double,2>) + .def("tuple", bezord_to_tuple) + + .def("from_tuple", tuple_to_bezord) + .staticmethod("from_tuple") + + .def("isZero", &Geom::Linear::isZero) + .def("isFinite", &Geom::Linear::isFinite) + .def("at0", (double (Geom::Linear::*)() const) &Geom::Linear::at0) + .def("at1", (double (Geom::Linear::*)() const) &Geom::Linear::at1) + .def("valueAt", &Geom::Linear::valueAt) + .def("toSBasis", &Geom::Linear::toSBasis) + + .def(-self) + .def(self + self) + .def(self - self) + .def(self += self) + .def(self -= self) + .def(self + float()) + .def(self - float()) + .def(self += float()) + .def(self -= float()) + .def(self == self) + .def(self != self) + .def(self * float()) + .def(self / float()) + .def(self *= float()) + .def(self /= float()) + ; + def("reverse", ((Geom::Linear (*)(Geom::Linear const &b))&Geom::reverse)); + //TODO: reinstate + //implicitly_convertible<Geom::Linear,tuple>(); +}; + +/* + 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/py2geom/parser.cpp b/src/py2geom/parser.cpp new file mode 100644 index 0000000..8f729a1 --- /dev/null +++ b/src/py2geom/parser.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2008 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 <boost/python.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/path-sink.h" +#include "2geom/svg-path-parser.h" + + +using namespace boost::python; + +void (*parse_svg_path_str_sink) (char const *, Geom::PathSink &) = &Geom::parse_svg_path; +Geom::PathVector (*parse_svg_path_str) (char const *) = &Geom::parse_svg_path; + +void (Geom::PathSink::*feed_path)(Geom::Path const &) = &Geom::PathSink::feed; +void (Geom::PathSink::*feed_pathvector)(Geom::PathVector const &) = &Geom::PathSink::feed; + +class PathSinkWrap: public Geom::PathSink, public wrapper<Geom::PathSink> { + void moveTo(Geom::Point const &p) {this->get_override("moveTo")(p);} + void lineTo(Geom::Point const &p) {this->get_override("lineTo")(p);} + void curveTo(Geom::Point const &c0, Geom::Point const &c1, Geom::Point const &p) {this->get_override("curveTo")(c0, c1, p);} + void quadTo(Geom::Point const &c, Geom::Point const &p) {this->get_override("quadTo")(c, p);} + void arcTo(double rx, double ry, double angle, bool large_arc, bool sweep, Geom::Point const &p) {this->get_override("arcTo")(rx, ry, angle, large_arc, sweep, p);} + bool backspace() {return this->get_override("backspace")();} + void closePath() {this->get_override("closePath")();} + void flush() {this->get_override("flush")();} +}; + +void wrap_parser() { + def("parse_svg_path", parse_svg_path_str_sink); + def("parse_svg_path", parse_svg_path_str); + def("read_svgd", Geom::read_svgd); + + class_<PathSinkWrap, boost::noncopyable>("PathSink") + .def("moveTo", pure_virtual(&Geom::PathSink::moveTo)) + .def("lineTo", pure_virtual(&Geom::PathSink::lineTo)) + .def("curveTo", pure_virtual(&Geom::PathSink::curveTo)) + .def("quadTo", pure_virtual(&Geom::PathSink::quadTo)) + .def("arcTo", pure_virtual(&Geom::PathSink::arcTo)) + .def("backspace", pure_virtual(&Geom::PathSink::backspace)) + .def("closePath", pure_virtual(&Geom::PathSink::closePath)) + .def("flush", pure_virtual(&Geom::PathSink::flush)) + .def("feed", feed_path) + .def("feed", feed_pathvector) + ; +}; + +/* + 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/py2geom/path.cpp b/src/py2geom/path.cpp new file mode 100644 index 0000000..68757a6 --- /dev/null +++ b/src/py2geom/path.cpp @@ -0,0 +1,265 @@ +/* + * Python bindings for lib2geom + * + * 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 <boost/python.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "2geom/curve.h" +#include "2geom/bezier-curve.h" +#include "2geom/path.h" +#include "2geom/pathvector.h" +#include "2geom/sbasis-to-bezier.h" +#include "helpers.h" + +#include "2geom/point.h" +#include "2geom/rect.h" +#include "2geom/d2.h" + +using namespace boost::python; + +Geom::Curve const &path_getitem(Geom::Path const& p, int index) +{ + unsigned size = p.size_default(); + unsigned i = index; + if (index < 0) + { + i = index = size + index; + } + if ((index < 0) || (i > (size - 1))) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + boost::python::throw_error_already_set(); + } + return p[i]; +} + +struct CurveWrap : Geom::Curve, wrapper<Geom::Curve> +{ + Geom::Point initialPoint() const {return this->get_override("initialPoint")();} + Geom::Point finalPoint() const {return this->get_override("finalPoint")();} + bool isDegenerate() const {return this->get_override("isDegenerate")();} + CurveWrap *duplicate() const {return this->get_override("duplicate")();} + Geom::Rect boundsFast() const {return this->get_override("boundsFast")();} + Geom::Rect boundsExact() const {return this->get_override("boundsExact")();} + virtual Geom::OptRect boundsLocal(Geom::OptInterval const &i, unsigned deg) const {return this->get_override("boundsLocal")(i,deg);} + std::vector<double> roots(double v, Geom::Dim2 d) const {return this->get_override("roots")(v,d);} + + int winding(Geom::Point const &p) const { + if (override f = this->get_override("winding")) { + return f(p); + } + return Geom::Curve::winding(p); + } + int default_winding(Geom::Point p) const { return this->Geom::Curve::winding(p); } + + Geom::Curve *portion(double f, double t) const { return this->get_override("portion")(f,t); } + Geom::Curve *reverse() const { + if (override f = this->get_override("reverse")) { + return f(); + } + return Geom::Curve::reverse(); + } + Geom::Curve *default_reverse() const { return this->Geom::Curve::reverse(); } + + Geom::Curve *derivative() const { return this->get_override("derivative")(); } + + + Geom::Curve *transformed(Geom::Affine const &m) const { return this->get_override("transformed")(m); } + + Geom::Point pointAt(Geom::Coord t) const { + if (override f = this->get_override("pointAt")) { + return f(t); + } + return Geom::Curve::pointAt(t); + } + Geom::Point default_pointAt(Geom::Coord t) { return this->Geom::Curve::pointAt(t); } + std::vector<Geom::Point> pointAndDerivatives(Geom::Coord t, unsigned n) const { + return this->get_override("pointAndDerivatives")(t, n); + } + + Geom::D2<Geom::SBasis> toSBasis() const {return this->get_override("sbasis")();} +}; + + +/* pycairo stuff: */ +#ifdef HAVE_PYCAIRO + +#include "cairo-helpers.h" + +void py_cairo_curve(object cr, Geom::Curve const &c) { + cairo_curve(cairo_t_from_object(cr), c); +} +void py_cairo_rectangle(object cr, Geom::Rect const &r) { + cairo_rectangle(cairo_t_from_object(cr), r); +} + +void py_cairo_convex_hull(object cr, Geom::ConvexHull const &r) { + cairo_convex_hull(cairo_t_from_object(cr), r); +} +/*void py_cairo_path(object cr, Geom::Path const &p) { + cairo_path(cairo_t_from_object(cr), p); + }*/ + +void py_cairo_path(object cr, Geom::Path const &p) { + cairo_path(cairo_t_from_object(cr), p); +} + +void py_cairo_path(object cr, Geom::PathVector const &p) { + cairo_path(cairo_t_from_object(cr), p); +} +void py_cairo_path_stitches(object cr, Geom::Path const &p) { + cairo_path_stitches(cairo_t_from_object(cr), p); +} +void py_cairo_path_stitches(object cr, Geom::PathVector const &p) { + cairo_path_stitches(cairo_t_from_object(cr), p); +} +void (*cp_1)(object, Geom::Path const &) = &py_cairo_path; +void (*cp_2)(object, Geom::PathVector const &) = &py_cairo_path; + +void (*cps_1)(object, Geom::Path const &) = &py_cairo_path_stitches; +void (*cps_2)(object, Geom::PathVector const &) = &py_cairo_path_stitches; + + +void py_cairo_d2_sb(object cr, Geom::D2<Geom::SBasis> const &p) { + cairo_d2_sb(cairo_t_from_object(cr), p); +} + +void py_cairo_d2_pw_sb(object cr, Geom::D2<Geom::Piecewise<Geom::SBasis> > const &p) { + cairo_d2_pw_sb(cairo_t_from_object(cr), p); +} + +void py_cairo_pw_d2_sb(object cr, Geom::Piecewise<Geom::D2<Geom::SBasis> > const &p) { + cairo_pw_d2_sb(cairo_t_from_object(cr), p); +} + +#endif // HAVE_PYCAIRO + +Geom::Point (Geom::Path::*path_pointAt_time)(Geom::Coord) const = &Geom::Path::pointAt; +Geom::Coord (Geom::Path::*path_valueAt_time)(Geom::Coord, Geom::Dim2) const = &Geom::Path::valueAt; +void (Geom::Path::*appendPortionTo_time)(Geom::Path &, Geom::Coord, Geom::Coord) const = &Geom::Path::appendPortionTo; +//void (Geom::Path::*appendPortionTo_pos)(Geom::Path &, Geom::PathPosition const &, Geom::PathPosition const &, bool) const = &Geom::Path::appendPortionTo; + +void wrap_path() +{ +/* class_<CurveWrap, boost::noncopyable>("Curve") + .def("initalPoint", pure_virtual(&Geom::Curve::initialPoint)) + .def("finalPoint", pure_virtual(&Geom::Curve::finalPoint)) + .def("duplicate", pure_virtual(&Geom::Curve::duplicate), return_value_policy<manage_new_object>()) + .def("boundsFast", pure_virtual(&Geom::Curve::boundsFast)) + .def("boundsExact", pure_virtual(&Geom::Curve::boundsExact)) + //.def("pointAt", &Geom::Curve::pointAt, &CurveWrap::default_pointAt) + //.def("winding", &Geom::Curve::winding, &CurveWrap::default_winding) + .def("pointAndDerivatives", pure_virtual(&Geom::Curve::pointAndDerivatives)) + .def("toSBasis", pure_virtual(&Geom::Curve::toSBasis)) + ;*/ +/* class_<Geom::LineSegment, bases<CurveWrap> >("LineSegment") + .def("points", &Geom::LineSegment::points) + ; + class_<Geom::QuadraticBezier, bases<CurveWrap> >("QuadraticBezier") + .def("points", &Geom::QuadraticBezier::points) + ; + class_<Geom::CubicBezier, bases<CurveWrap> >("CubicBezier") + .def("points", &Geom::CubicBezier::points) + ;*/ + class_<Geom::Path>("Path") + .def("__getitem__", path_getitem, return_value_policy<copy_const_reference>()) //or return_internal_reference see http://www.boost.org/doc/libs/1_36_0/libs/python/doc/v2/faq.html#question1 + .def("empty", &Geom::Path::empty) + .def("closed", &Geom::Path::closed) + .def("close", &Geom::Path::close) + .def("boundsFast", &Geom::Path::boundsFast) + .def("boundsExact", &Geom::Path::boundsExact) + .def("toPwSb", &Geom::Path::toPwSb) + .def(self * Geom::Affine()) + .def(self *= Geom::Affine()) + .def("pointAt", path_pointAt_time) + .def("valueAt", path_valueAt_time) + .def("__call__", path_pointAt_time) + .def("roots", &Geom::Path::roots) + //.def("allNearestTimes", &Geom::Path::allNearestTimes) + //.def("nearestTime", &Geom::Path::nearestTime) + .def("appendPortionTo", appendPortionTo_time) + //.def("portion", &Geom::Path::portion) + .def("reversed", &Geom::Path::reversed) + //.def("insert", &Geom::Path::insert) + .def("clear", &Geom::Path::clear) + //.def("erase", &Geom::Path::erase) + .def("erase_last", &Geom::Path::erase_last) + //.def("replace", &Geom::Path::replace) + .def("start", &Geom::Path::start) + .def("initialPoint", &Geom::Path::initialPoint) + .def("finalPoint", &Geom::Path::finalPoint) + //.def("append", &Geom::Path::append) + //.def("appendNew", &Geom::Path::appendNew) + ; + def("paths_to_pw",Geom::paths_to_pw); + class_<Geom::PathVector >("PathVector") + .def(vector_indexing_suite<Geom::PathVector >()) + .def(self * Geom::Affine()) + .def(self *= Geom::Affine()) + .def("reversed", &Geom::PathVector::reversed) + .def("reverse", &Geom::PathVector::reverse) + .def("boundsFast", &Geom::PathVector::boundsFast) + .def("boundsExact", &Geom::PathVector::boundsExact) + ; + def("path_from_piecewise", Geom::path_from_piecewise); + def("path_from_sbasis", Geom::path_from_sbasis); + def("cubicbezierpath_from_sbasis", Geom::cubicbezierpath_from_sbasis); + +#ifdef HAVE_PYCAIRO +void cairo_move_to(cairo_t *cr, Geom::Point p1); + def("cubicbezierpath_from_sbasis", Geom::cubicbezierpath_from_sbasis); +void cairo_line_to(cairo_t *cr, Geom::Point p1); + def("cubicbezierpath_from_sbasis", Geom::cubicbezierpath_from_sbasis); +void cairo_curve_to(cairo_t *cr, Geom::Point p1, Geom::Point p2, Geom::Point p3); + def("cubicbezierpath_from_sbasis", Geom::cubicbezierpath_from_sbasis); + + //def("cairo_curve", cairo_curve); + def("cairo_convex_hull", py_cairo_convex_hull); + def("cairo_path", cp_1); + def("cairo_path", cp_2); + def("cairo_path_stitches", cps_1); + def("cairo_path_stitches", cps_2); + + def("cairo_d2_sb", py_cairo_d2_sb); + def("cairo_d2_pw_sb", py_cairo_d2_pw_sb); + def("cairo_pw_d2_sb", py_cairo_pw_d2_sb); +#endif // HAVE_PYCAIRO +} + +/* + 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/py2geom/point.cpp b/src/py2geom/point.cpp new file mode 100644 index 0000000..869240d --- /dev/null +++ b/src/py2geom/point.cpp @@ -0,0 +1,146 @@ +/* + * Copyright 2006, 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 <boost/python.hpp> +#include <boost/python/implicit.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/point.h" + +using namespace boost::python; + + +// helpers for point +tuple point_to_tuple(Geom::Point const& p) +{ + return make_tuple(p[0], p[1]); +} + +Geom::Point tuple_to_point(boost::python::tuple const& t) +{ + return Geom::Point(extract<double>(t[0]), extract<double>(t[1])); +} + +str point_repr(Geom::Point const& p) +{ + return str("(" + str(p[0]) + ", " + str(p[1]) + ")"); +} + +//Specifications of overloads +Geom::Coord (*L2_point) (Geom::Point const &) = &Geom::L2; +Geom::Point (*rot90_point)(Geom::Point const &) = &Geom::rot90; +Geom::Coord (*dot_point) (Geom::Point const &, Geom::Point const &) = &Geom::dot; +Geom::Coord (*cross_point)(Geom::Point const &, Geom::Point const &) = &Geom::cross; +Geom::Point (*lerp_point)(Geom::Coord, Geom::Point const &, Geom::Point const &) = &Geom::lerp; + +bool near_point1(Geom::Point const &a, Geom::Point const &b) { return are_near(a,b); } +bool near_point2(Geom::Point const &a, Geom::Point const &b, double eps) { return are_near(a,b,eps); } + +void wrap_point() { + def("point_to_tuple", point_to_tuple); + def("tuple_to_point", tuple_to_point); + + def("L1", Geom::L1); + def("L2", L2_point); + def("L2sq", Geom::L2sq); + def("LInfty", Geom::LInfty); + + def("unit_vector", Geom::unit_vector); + def("is_zero", Geom::is_zero); + def("is_unit_vector", Geom::is_unit_vector); + + def("dot", dot_point); + def("cross", cross_point); + def("distance", Geom::distance); + def("distanceSq", Geom::distanceSq); + def("lerp", lerp_point); + + def("atan2", Geom::atan2); + def("angle_between", Geom::angle_between); + + def("near", near_point1); + def("near", near_point2); + + def("rot90", rot90_point); + def("abs", (Geom::Point (*)(Geom::Point const&))&Geom::abs); + + class_<Geom::Point>("Point", init<double, double>()) + .def(init<>()) + + .def("__str__", point_repr) + .def("__repr__", point_repr) + .def("__getitem__", python_getitem<Geom::Point,double,2>) + .def("tuple", point_to_tuple) + + .def("from_tuple", tuple_to_point) + .staticmethod("from_tuple") + + //point.h + //.def("polar", &Geom::Point::polar) + //.staticmethod("polar") + + .def("ccw", &Geom::Point::ccw) + .def("cw", &Geom::Point::cw) + .def("round", &Geom::Point::round) + .def("normalize", &Geom::Point::normalize) + + .def("length", &Geom::Point::length) + + .def(self + self) + .def(self - self) + .def(self += self) + .def(self -= self) + + .def(-self) + .def(self * float()).def(float() * self) + .def(self / float()) + .def(self *= float()) + + .def(self == self) + .def(self != self) + + .def(self <= self) + ; + implicitly_convertible<Geom::Point,tuple>(); +// TODO: explain why this gives a compile time error +// implicitly_convertible<tuple,Geom::Point>(); + +}; + +/* + 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/py2geom/pw.cpp b/src/py2geom/pw.cpp new file mode 100644 index 0000000..bbd8f2c --- /dev/null +++ b/src/py2geom/pw.cpp @@ -0,0 +1,228 @@ +/* + * Copyright 2006, 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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "2geom/sbasis.h" +#include "2geom/piecewise.h" +#include "2geom/d2.h" +#include "2geom/sbasis-math.h" +#include "2geom/sbasis-geometric.h" + +#include "py2geom.h" +#include "helpers.h" + +using namespace boost::python; + +// helpers for point +tuple pwd2sb_centroid(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pw) +{ + Geom::Point p; + double a; + Geom::centroid(pw, p, a); + return boost::python::make_tuple(p, a); +} + + +void (Geom::Piecewise<Geom::SBasis>::*push_pwsb)(Geom::SBasis const &, double) = &Geom::Piecewise<Geom::SBasis>::push; +void (Geom::Piecewise<Geom::SBasis>::*push_seg_pwsb)(Geom::SBasis const &) = &Geom::Piecewise<Geom::SBasis>::push_seg; +Geom::Piecewise<Geom::SBasis> (*portion_pwsb)(const Geom::Piecewise<Geom::SBasis> &, double, double) = &Geom::portion; +void (Geom::Piecewise<Geom::D2<Geom::SBasis>>::*push_pwd2sb)(Geom::D2<Geom::SBasis> const &, double) = &Geom::Piecewise<Geom::D2<Geom::SBasis>>::push; +void (Geom::Piecewise<Geom::D2<Geom::SBasis>>::*push_seg_pwd2sb)(Geom::D2<Geom::SBasis> const &) = &Geom::Piecewise<Geom::D2<Geom::SBasis>>::push_seg; +Geom::Piecewise<Geom::D2<Geom::SBasis>> (*portion_pwd2sb)(const Geom::Piecewise<Geom::D2<Geom::SBasis> > &, double, double) = &Geom::portion; +std::vector<double> (*roots_pwsb)(const Geom::Piecewise<Geom::SBasis> &) = &Geom::roots; +//Geom::Piecewise<Geom::SBasis> (*multiply_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &) = &Geom::multiply; +Geom::Piecewise<Geom::SBasis> (*divide_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &, unsigned) = &Geom::divide; +Geom::Piecewise<Geom::SBasis> (*compose_pwsb_sb)(Geom::Piecewise<Geom::SBasis> const &, Geom::SBasis const &) = &Geom::compose; +Geom::Piecewise<Geom::SBasis> (*compose_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &) = &Geom::compose; + +Geom::Piecewise<Geom::SBasis> (*abs_pwsb)(Geom::Piecewise<Geom::SBasis> const &) = &Geom::abs; + +Geom::Piecewise<Geom::SBasis> (*min_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &) = &Geom::min; +Geom::Piecewise<Geom::SBasis> (*max_pwsb)(Geom::Piecewise<Geom::SBasis> const &, Geom::Piecewise<Geom::SBasis> const &) = &Geom::max; +Geom::Piecewise<Geom::SBasis> (*signSb_pwsb)(Geom::Piecewise<Geom::SBasis> const &) = &Geom::signSb; + +Geom::Piecewise<Geom::SBasis> (*sqrt_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::sqrt; + +Geom::Piecewise<Geom::SBasis> (*sin_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::sin; +Geom::Piecewise<Geom::SBasis> (*cos_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::cos; + +//Geom::Piecewise<Geom::SBasis> (*log_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::log; +Geom::Piecewise<Geom::SBasis> (*reciprocal_pwsb)(Geom::Piecewise<Geom::SBasis> const &, double, int) = &Geom::reciprocal; + +Geom::FragmentConcept<Geom::SBasis>::BoundsType (*bounds_fast_pwsb)(Geom::Piecewise<Geom::SBasis> const &) = &Geom::bounds_fast; +Geom::FragmentConcept<Geom::SBasis>::BoundsType (*bounds_exact_pwsb)(Geom::Piecewise<Geom::SBasis> const &) = &Geom::bounds_exact; +Geom::FragmentConcept<Geom::SBasis>::BoundsType (*bounds_local_pwsb)(Geom::Piecewise<Geom::SBasis> const &, const Geom::OptInterval &) = &Geom::bounds_local; + +Geom::SBasis getitem_pwsb(Geom::Piecewise<Geom::SBasis> const &p, int index) { + unsigned D = p.size(); + unsigned i = index; + if (index < 0) + { + i = index = D + index; + } + if (index < 0 || i > (D - 1)) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + boost::python::throw_error_already_set(); + } + return p[i]; +} + +Geom::Piecewise<Geom::D2<Geom::SBasis> > (*unitVector_pwd2sb)(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &, double, unsigned int) = &Geom::unitVector; + +Geom::Piecewise<Geom::SBasis> (*arcLengthSb_pwd2sb)(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &, double) = &Geom::arcLengthSb; + +Geom::Piecewise<Geom::D2<Geom::SBasis> > (*rot90_pwd2sb)(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &) = &Geom::rot90; + +void wrap_pw() { + class_<std::vector<Geom::SBasis> >("SBasisVec") + .def(vector_indexing_suite<std::vector<Geom::SBasis> >()) + ; + class_<std::vector<Geom::D2<Geom::SBasis> > >("D2SBasisVec") + .def(vector_indexing_suite<std::vector<Geom::D2<Geom::SBasis> > >()) + ; + + def("portion", portion_pwsb); + def("portion", portion_pwd2sb); + //def("partition", &partition); + def("roots", roots_pwsb); + //def("multiply", multiply_pwsb); + def("divide", divide_pwsb); + def("compose", compose_pwsb_sb); + def("compose", compose_pwsb); + def("abs", abs_pwsb); + def("min", min_pwsb); + def("max", max_pwsb); + def("signSb", signSb_pwsb); + def("sqrt", sqrt_pwsb); + def("cos", cos_pwsb); + def("sin", sin_pwsb); + //def("log", log_pwsb); + def("reciprocal", reciprocal_pwsb); + def("bounds_fast", bounds_fast_pwsb); + def("bounds_exact", bounds_exact_pwsb); + def("bounds_local", bounds_local_pwsb); + + def("derivative", (Geom::Piecewise<Geom::SBasis> (*)(Geom::Piecewise<Geom::SBasis> const & ))&Geom::derivative); + def("integral", (Geom::Piecewise<Geom::SBasis> (*)(Geom::Piecewise<Geom::SBasis> const & ))&Geom::integral); + def("derivative", (Geom::Piecewise<Geom::D2<Geom::SBasis> > (*)(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &)) &Geom::derivative); + def("rot90", rot90_pwd2sb); + def("unit_vector", unitVector_pwd2sb); + def("arcLengthSb", arcLengthSb_pwd2sb); + + class_<Geom::Piecewise<Geom::SBasis> >("PiecewiseSBasis", init<>()) + .def(init<double>()) + .def(init<Geom::SBasis>()) + .def("__getitem__", getitem_pwsb) + .def("__call__", &Geom::Piecewise<Geom::SBasis>::valueAt) + .def_readonly("cuts", &Geom::Piecewise<Geom::SBasis>::cuts) + .def_readonly("segs", &Geom::Piecewise<Geom::SBasis>::segs) + .def("at0", &Geom::Piecewise<Geom::SBasis>::firstValue) + .def("at1", &Geom::Piecewise<Geom::SBasis>::lastValue) + .def("valueAt", &Geom::Piecewise<Geom::SBasis>::valueAt) + .def("size", &Geom::Piecewise<Geom::SBasis>::size) + .def("empty", &Geom::Piecewise<Geom::SBasis>::empty) + .def("push", push_pwsb) + .def("push_cut", &Geom::Piecewise<Geom::SBasis>::push_cut) + .def("push_seg", push_seg_pwsb) + + .def("segN", &Geom::Piecewise<Geom::SBasis>::segN) + .def("segT", &Geom::Piecewise<Geom::SBasis>::segT) + .def("offsetDomain", &Geom::Piecewise<Geom::SBasis>::offsetDomain) + .def("scaleDomain", &Geom::Piecewise<Geom::SBasis>::scaleDomain) + .def("setDomain", &Geom::Piecewise<Geom::SBasis>::setDomain) + .def("concat", &Geom::Piecewise<Geom::SBasis>::concat) + .def("continuousConcat", &Geom::Piecewise<Geom::SBasis>::continuousConcat) + .def("invariants", &Geom::Piecewise<Geom::SBasis>::invariants) + + .def(self + double()) + .def(-self) + .def(self += double()) + .def(self -= double()) + .def(self /= double()) + .def(self * double()) + .def(self *= double()) + .def(self + self) + .def(self - self) + .def(self * self) + .def(self *= self) + + ; + + class_<Geom::Piecewise<Geom::D2<Geom::SBasis> > >("PiecewiseD2SBasis", init<>()) + .def(init<Geom::D2<Geom::SBasis> >()) + .def("__getitem__", getitem_pwsb) + .def("__call__", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::valueAt) + .def_readonly("cuts", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::cuts) + .def_readonly("segs", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::segs) + .def("valueAt", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::valueAt) + .def("size", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::size) + .def("empty", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::empty) + .def("push", push_pwd2sb) + .def("push_cut", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::push_cut) + .def("push_seg", push_seg_pwd2sb) + + .def("segN", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::segN) + .def("segT", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::segT) + .def("offsetDomain", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::offsetDomain) + .def("scaleDomain", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::scaleDomain) + .def("setDomain", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::setDomain) + .def("concat", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::concat) + .def("continuousConcat", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::continuousConcat) + .def("invariants", &Geom::Piecewise<Geom::D2<Geom::SBasis> >::invariants) + + //.def(self + double()) + .def(-self) + //.def(self += double()) + //.def(self -= double()) + //.def(self /= double()) + .def(self * double()) + .def(Geom::Piecewise<Geom::SBasis>() * self) + .def(self *= double()) + .def(self + self) + .def(self - self) + //.def(self * self) + //.def(self *= self) + + ; + def("centroid", pwd2sb_centroid); + def("make_cuts_independent", Geom::make_cuts_independent); + +}; + +/* + 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/py2geom/py2geom.cpp b/src/py2geom/py2geom.cpp new file mode 100644 index 0000000..b1ef493 --- /dev/null +++ b/src/py2geom/py2geom.cpp @@ -0,0 +1,85 @@ +/* + * Python bindings for lib2geom + * + * Copyright 2006, 2007 Aaron Spike <aaron@ekips.org> + * Copyright 2007 Alex Mac <ajm@cs.nott.ac.uk> + * + * 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/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include <2geom/geom.h> + +#include "py2geom.h" + +using namespace boost::python; + +BOOST_PYTHON_MODULE(_py2geom) +{ + + /*enum_<IntersectorKind>("IntersectorKind") + .value("intersects", intersects) + .value("parallel", parallel) + .value("coincident", coincident) + .value("no_intersection", no_intersection) + ; + def("segment_intersect", segment_intersect);*/ + + wrap_point(); + wrap_etc(); + wrap_interval(); + wrap_transforms(); + wrap_rect(); + wrap_circle(); + wrap_ellipse(); + wrap_sbasis(); + wrap_bezier(); + wrap_linear(); + wrap_line(); + wrap_conic(); + wrap_pw(); + wrap_d2(); + wrap_parser(); + wrap_path(); + wrap_ray(); + // wrap_shape(); + wrap_crossing(); + // wrap_convex_cover(); + +} + +/* + 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/py2geom/py2geom.h b/src/py2geom/py2geom.h new file mode 100644 index 0000000..ba9fb13 --- /dev/null +++ b/src/py2geom/py2geom.h @@ -0,0 +1,71 @@ +/* + * Copyright 2006, 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 SEEN_PY2GEOM_H +#define SEEN_PY2GEOM_H + +void wrap_point(); +void wrap_etc(); +void wrap_interval(); +void wrap_transforms(); +void wrap_rect(); +void wrap_circle(); +void wrap_ellipse(); +void wrap_sbasis(); +void wrap_bezier(); +void wrap_linear(); +void wrap_pw(); +void wrap_d2(); +void wrap_path(); +void wrap_parser(); +void wrap_ray(); +// void wrap_shape(); +void wrap_line(); +void wrap_conic(); +void wrap_crossing(); +// void wrap_convex_cover(); +namespace Geom{ +class Point; +class Linear; +}; +#include <vector> +typedef std::vector<Geom::Point > PointVec; +typedef std::vector<double > DoubleVec; +typedef std::vector<Geom::Linear> LinearVec; + +#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/py2geom/ray.cpp b/src/py2geom/ray.cpp new file mode 100644 index 0000000..9fda595 --- /dev/null +++ b/src/py2geom/ray.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2009 Ricardo Lafuente <r@sollec.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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/point.h" +#include "2geom/ray.h" +// #include "2geom/bezier_curve.h" +#include "2geom/exception.h" + + +using namespace boost::python; + +bool (*are_near_ray)(Geom::Point const& _point, Geom::Ray const& _ray, double eps) = &Geom::are_near; +double (*angle_between_ray)(Geom::Ray const& r1, Geom::Ray const& r2, bool cw) = &Geom::angle_between; + + +double angle_between_ray_def(Geom::Ray const& r1, Geom::Ray const& r2) { + return Geom::angle_between(r1, r2); +} +double (*distance_ray)(Geom::Point const& _point, Geom::Ray const& _ray) = &Geom::distance; + +// why don't these compile? +//Geom::Point (*get_ray_origin)(Geom::Ray const) = &Geom::Ray::origin; +//void (*set_ray_origin)(Geom::Ray const, Geom::Point const& _point) = &Geom::Ray::origin; + +void wrap_ray() { + def("distance", distance_ray); + def("are_near", are_near_ray); + def("are_same", Geom::are_same); + def("angle_between", angle_between_ray); + def("angle_between", angle_between_ray_def); + def("make_angle_bisector_ray", Geom::make_angle_bisector_ray); + + class_<Geom::Ray>("Ray", init<Geom::Point, Geom::Coord>()) + .def(init<Geom::Point,Geom::Point>()) + .def(init<>()) + + // TODO: overloaded + //.add_property("origin", get_ray_origin, set_ray_origin) + // .add_property("versor", &Geom::Ray::versor, &Geom::Ray::versor) + // .add_property("angle", &Geom::Ray::angle, &Geom::Ray::angle) + + .def("isDegenerate", &Geom::Ray::isDegenerate) + .def("nearestTime", &Geom::Ray::nearestTime) + .def("setBy2Points", &Geom::Ray::setPoints) + .def("valueAt", &Geom::Ray::valueAt) + .def("pointAt", &Geom::Ray::pointAt) + .def("nearestTime", &Geom::Ray::nearestTime) + .def("reverse", &Geom::Ray::reverse) + .def("roots", &Geom::Ray::roots) + .def("transformed", &Geom::Ray::transformed) + // requires Curve + // .def("portion", &Geom::Ray::portion) + .def("segment", &Geom::Ray::segment) + ; + +}; + +/* + 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/py2geom/rect.cpp b/src/py2geom/rect.cpp new file mode 100644 index 0000000..3b75625 --- /dev/null +++ b/src/py2geom/rect.cpp @@ -0,0 +1,125 @@ +/* + * Copyright 2008 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 <boost/python.hpp> +#include <boost/python/implicit.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/affine.h" +#include "2geom/d2.h" +#include "2geom/interval.h" + +using namespace boost::python; + +static bool wrap_contains_coord(Geom::Rect const &x, Geom::Point val) { + return x.contains(val); +} + +static bool wrap_contains_ivl(Geom::Rect const &x, Geom::Rect val) { + return x.contains(val); +} + +static bool wrap_interiorContains_coord(Geom::Rect const &x, Geom::Point val) { + return x.interiorContains(val); +} + +static bool wrap_interiorContains_ivl(Geom::Rect const &x, Geom::Rect val) { + return x.interiorContains(val); +} + +static void wrap_expandBy_pt(Geom::Rect &x, Geom::Point val) { + x.expandBy(val); +} + +static void wrap_expandBy(Geom::Rect &x, double val) { + x.expandBy(val); +} + +static void wrap_unionWith(Geom::Rect &x, Geom::Rect const &y) { + x.unionWith(y); +} +static bool wrap_intersects(Geom::Rect const &x, Geom::Rect const &y) { + return x.intersects(y); +} + +void wrap_rect() { + //TODO: fix overloads + //def("unify", Geom::unify); + def("union_list", Geom::union_list); + //def("intersect", Geom::intersect); + def("distanceSq", (double (*)( Geom::Point const&, Geom::Rect const& ))Geom::distanceSq); + def("distance", (double (*)( Geom::Point const&, Geom::Rect const& ))Geom::distance); + + class_<Geom::Rect>("Rect", init<Geom::Interval, Geom::Interval>()) + .def(init<Geom::Point,Geom::Point>()) + .def(init<>()) + .def(init<Geom::Rect const &>()) + + .def("__getitem__", python_getitem<Geom::Rect,Geom::Interval,2>) + + .def("min", &Geom::Rect::min) + .def("max", &Geom::Rect::max) + .def("corner", &Geom::Rect::corner) + .def("top", &Geom::Rect::top) + .def("bottom", &Geom::Rect::bottom) + .def("left", &Geom::Rect::left) + .def("right", &Geom::Rect::right) + .def("width", &Geom::Rect::width) + .def("height", &Geom::Rect::height) + .def("dimensions", &Geom::Rect::dimensions) + .def("midpoint", &Geom::Rect::midpoint) + .def("area", &Geom::Rect::area) + .def("maxExtent", &Geom::Rect::maxExtent) + .def("contains", wrap_contains_coord) + .def("contains", wrap_contains_ivl) + .def("interiorContains", wrap_interiorContains_coord) + .def("interiorContains", wrap_interiorContains_ivl) + .def("intersects", wrap_intersects) + .def("expandTo", &Geom::Rect::expandTo) + .def("unionWith", &wrap_unionWith) + // TODO: overloaded + .def("expandBy", wrap_expandBy) + .def("expandBy", wrap_expandBy_pt) + + .def(self * Geom::Affine()) + ; + +}; + +/* + 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/py2geom/sbasis.cpp b/src/py2geom/sbasis.cpp new file mode 100644 index 0000000..be547ca --- /dev/null +++ b/src/py2geom/sbasis.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2006, 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 <boost/python.hpp> +#include <boost/python/implicit.hpp> +#include <boost/python/suite/indexing/vector_indexing_suite.hpp> + +#include "py2geom.h" +#include "helpers.h" + +#include "2geom/sbasis.h" +#include "2geom/sbasis-math.h" +#include "2geom/point.h" + +using namespace boost::python; + +Geom::SBasis (*truncate_sbasis)(Geom::SBasis const &, unsigned) = &Geom::truncate; +Geom::SBasis (*multiply_sbasis)(Geom::SBasis const &, Geom::SBasis const &) = &Geom::multiply; +Geom::SBasis (*integral_sbasis)(Geom::SBasis const &) = &Geom::integral; +Geom::SBasis (*derivative_sbasis)(Geom::SBasis const &) = &Geom::derivative; + +Geom::Linear sbasis_getitem(Geom::SBasis const& p, int index) +{ + int D = p.size(); + if (index < 0) + { + index = D + index; + } + if ((index < 0) || (index > (D - 1))) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + boost::python::throw_error_already_set(); + } + return p[index]; +} + +int sbasis_len(Geom::SBasis const& p) +{ + return p.size(); +} + +#include "2geom/sbasis-to-bezier.h" +#include "2geom/bezier.h" + +Geom::Bezier sbasis_to_returned_bezier (Geom::SBasis const& sb, size_t sz = 0) { + Geom::Bezier res; + Geom::sbasis_to_bezier(res, sb, sz); + return res; +} + +/*object wrap_bounds_fast(Geom::SBasis const& sb) { + Geom::OptInterval oi = bounds_fast(sb); + return oi?object(*oi):object(); + }*/ + +template <typename T, typename target_type> +object wrap_bounds_fast(T const& sb) { + target_type oi = bounds_fast(sb); + return oi?object(*oi):object(); +} + + +object wrap_bounds_exact(Geom::SBasis const& sb) { + Geom::OptInterval oi = bounds_exact(sb); + return oi?object(*oi):object(); +} + +object wrap_bounds_local(Geom::SBasis const& sb, Geom::Interval const & iv) { + Geom::OptInterval oi = bounds_local(sb, iv); + return oi?object(*oi):object(); +} +void wrap_sbasis() { + //sbasis.h + + def("shift", (Geom::SBasis (*)(Geom::SBasis const &a, int sh))&Geom::shift); + def("truncate", truncate_sbasis); + def("multiply", multiply_sbasis); + def("compose", (Geom::SBasis (*) (Geom::SBasis const &, Geom::SBasis const &))&Geom::compose); + def("integral", integral_sbasis); + def("derivative", derivative_sbasis); + def("min", (Geom::Piecewise<Geom::SBasis> (*)(Geom::SBasis const &, Geom::SBasis const & ))&Geom::min); + def("sqrt", (Geom::SBasis (*)(Geom::SBasis const &, int ))&Geom::sqrt); + def("reciprocal", (Geom::SBasis (*)(Geom::Linear const &, int ))&Geom::reciprocal); + def("divide",(Geom::SBasis (*)(Geom::SBasis const &, Geom::SBasis const &, int )) &Geom::divide); + def("inverse", (Geom::SBasis (*)(Geom::SBasis, int ))&Geom::inverse); + //def("sin", (Geom::SBasis (*)(Geom::SBasis const &, int ))&Geom::sin); + //def("cos", (Geom::SBasis (*)(Geom::SBasis const &, int ))&Geom::cos); + def("reverse", (Geom::SBasis (*)(Geom::SBasis const &))&Geom::reverse); + def("roots", (std::vector<double> (*)(Geom::SBasis const &))&Geom::roots); + def("bounds_fast", &wrap_bounds_fast<Geom::SBasis, Geom::OptInterval>); + def("bounds_exact", &wrap_bounds_exact); + def("bounds_local", &wrap_bounds_local); + def("sbasis_to_bezier", &::sbasis_to_returned_bezier); + + class_<Geom::SBasis>("SBasis", init<double>()) + .def(init<double, double>()) + .def(init<Geom::Linear>()) + .def(self_ns::str(self)) + //TODO: add important vector funcs + .def("__getitem__", &sbasis_getitem) + .def("__len__", &sbasis_len) + + .def("isZero", &Geom::SBasis::isZero) + .def("isFinite", &Geom::SBasis::isFinite) + .def("at0", (double (Geom::SBasis::*)() const) &Geom::SBasis::at0) + .def("at1", (double (Geom::SBasis::*)() const) &Geom::SBasis::at1) + .def("valueAt", &Geom::SBasis::valueAt) + .def("toSBasis", &Geom::SBasis::toSBasis) + + .def("normalize", &Geom::SBasis::normalize) + .def("tailError", &Geom::SBasis::tailError) + .def("truncate", &Geom::SBasis::truncate) + + .def(self + self) + .def(self - self) + .def(self += self) + .def(self -= self) + + .def(self + Geom::Linear()) + .def(self - Geom::Linear()) + .def(self += Geom::Linear()) + .def(self -= Geom::Linear()) + + .def(self + float()) + .def(self - float()) + .def(self += float()) + .def(self -= float()) + + .def(-self) + .def(self * self) + .def(self *= self) + .def(self * float()) + .def(float() * self) + .def(self / float()) + .def(self *= float()) + .def(self /= float()) + ; +}; + +/* + 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/py2geom/transforms.cpp b/src/py2geom/transforms.cpp new file mode 100644 index 0000000..ea050da --- /dev/null +++ b/src/py2geom/transforms.cpp @@ -0,0 +1,107 @@ +/* + * Copyright 2006, 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 <boost/python.hpp> + +#include "py2geom.h" + +#include "2geom/transforms.h" + +using namespace boost::python; + +//TODO: properly wrap other transforms + +void wrap_transforms() { + class_<Geom::Affine>("Affine", init<double, double, double, double, double, double>()) + .def(init<>()) + .def(init<Geom::Rotate>()) + .def(init<Geom::Scale>()) + .def(init<Geom::Translate>()) + .def(self_ns::str(self)) + .add_property("xAxis",&Geom::Affine::xAxis,&Geom::Affine::setXAxis) + .add_property("yAxis",&Geom::Affine::yAxis,&Geom::Affine::setYAxis) + .add_property("translation",&Geom::Affine::translation,&Geom::Affine::setTranslation) + .def("isTranslation", &Geom::Affine::isTranslation) + .def("isRotation", &Geom::Affine::isRotation) + .def("isScale", &Geom::Affine::isScale) + .def("isUniformScale", &Geom::Affine::isUniformScale) + .def("setIdentity", &Geom::Affine::setIdentity) + .def("inverse", &Geom::Affine::inverse) + .def("det", &Geom::Affine::det) + .def("descrim2", &Geom::Affine::descrim2) + .def("descrim", &Geom::Affine::descrim) + .def("expansionX", &Geom::Affine::expansionX) + .def("expansionY", &Geom::Affine::expansionY) + .def(self * self) + .def(self * other<Geom::Translate>()) + .def(self * other<Geom::Scale>()) + .def(self * other<Geom::Rotate>()) + ; + + class_<Geom::Scale>("Scale", init<double, double>()) + .def(self == self) + .def(self != self) + .def("inverse", &Geom::Scale::inverse) + .def(Geom::Point() * self) + .def(self * self) + .def(self * Geom::Affine()) + ; + + class_<Geom::Translate>("Translate", init<double, double>()) + .def(init<Geom::Point>()) + .def(self == self) + .def(self != self) + .def("inverse", &Geom::Translate::inverse) + .def(Geom::Point() * self) + .def(self * self) + .def(self * other<Geom::Rotate>()) + .def(self * other<Geom::Scale>()) + ; + + class_<Geom::Rotate>("Rotate", init<double>()) + .def(self == self) + .def(self != self) + .def("inverse", &Geom::Rotate::inverse) + .def("from_degrees", &Geom::Rotate::from_degrees) + .staticmethod("from_degrees") + .def(Geom::Point() * self) + .def(self * self) + .def(Geom::Affine() * self) + ; +}; + +/* + 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/python/cy2geom_example.py b/src/python/cy2geom_example.py new file mode 100644 index 0000000..aca7dca --- /dev/null +++ b/src/python/cy2geom_example.py @@ -0,0 +1,10 @@ +#!/usr/bin/python + +import cy2geom +from cy2geom import * + +a = Point(1,2) +b = Point(31,2) +print a, b +print Point.dot(a,b) +print Point.unit_vector(a) diff --git a/src/python/elip.py b/src/python/elip.py new file mode 100644 index 0000000..9809db8 --- /dev/null +++ b/src/python/elip.py @@ -0,0 +1,155 @@ +#!/usr/bin/python + +import gtk,gtk.gdk,math,pango #Numeric, + +templayout = None + +def draw_spot(w, h): + x,y = h.x, h.y + g = gtk.gdk.GC(w) + w.draw_line(g, int(x), int(y), int(x), int(y)) + +def draw_handle(w, h, name = ""): + x,y = h.x, h.y + g = gtk.gdk.GC(w) + w.draw_line(g, int(x-3), int(y), int(x+3), int(y)) + w.draw_line(g, int(x), int(y-3), int(x), int(y+3)) + templayout.set_text(name) + w.draw_layout(g, x, y, templayout) + +def draw_ray(w, h, d): + x,y = h.x, h.y + g = gtk.gdk.GC(w) + w.draw_line(g, int(h.x), int(h.y), int(3*d.x-2*h.x), int(3*d.y-2*h.y)) + +def intersect(n0, d0, n1, d1): + denominator = n0.x*n1.y - n0.y*n1.x + X = n1.y * d0 - n0.y * d1 + if denominator == 0: + return None; + + Y = n0.x * d1 - n1.x * d0 + + return handle(X / denominator, Y / denominator) + +def seg(a0, a1, b0, b1): + n0 = handle(a1.y - a0.y, -a1.x + a0.x) + d0 = n0.x*a0.x + n0.y *a0.y + n1 = handle(b1.y - b0.y, -b1.x + b0.x) + d1 = n1.x*b0.x + n1.y *b0.y + + return intersect(n0, d0, n1, d1) + +def draw_elip(w, h): + g = gtk.gdk.GC(w) + w.draw_line(g, h[0].x, h[0].y, h[1].x, h[1].y) + w.draw_line(g, h[3].x, h[3].y, h[4].x, h[4].y) + w.draw_line(g, h[3].x, h[3].y, h[2].x, h[2].y) + w.draw_line(g, h[2].x, h[2].y, h[1].x, h[1].y) + + c = seg(h[0], h[1], h[3], h[4]) + draw_handle(w, c) + + if 0: + for i in range(6): + w.draw_line(g, h[i].x, h[i].y, h[(i+1)%6].x, h[(i+1)%6].y) + + + cx,cy = c.x, c.y + + ox, oy = None, None + for i in range(0, 101): + t = i/100.0 + + + nx = (1-t)*h[0].x + t*h[3].x + ny = (1-t)*h[0].y + t*h[3].y + #w.draw_line(g, 2*cx-nx, 2*cy-ny, nx, ny) + c1 = seg(handle(2*cx-nx, 2*cy-ny), handle(nx, ny), h[0], h[2]) + #draw_handle(w, c1) + c2 = seg(handle(2*cx-nx, 2*cy-ny), handle(nx, ny), h[4], h[2]) + #draw_handle(w, c2) + #draw_ray(w, h[3], c1) + #draw_ray(w, h[1], c2) + six = seg(c1, h[3], c2, h[1]) + #draw_spot(w, six) + if ox: + w.draw_line(g, ox, oy, six.x, six.y) + ox, oy = six.x, six.y + return + + r = math.hypot(h[0].x - cx, h[0].y - cy) + s = math.atan2(h[0].y - h[3].y, h[0].x - h[3].x) + e = math.atan2(h[1].y - h[4].y, h[1].x - h[4].x) + for i in range(0, 101): + t = (e-s)*i/100.0 + s + nx, ny = r*math.cos(t)+cx, r*math.sin(t)+cy + sx, sy = r*math.cos(t+math.pi)+cx, r*math.sin(t+math.pi)+cy + w.draw_line(g, sx, sy, nx, ny) + + +class handle: + def __init__(self, x, y): + self.x = x + self.y = y + def __repr__(self): + return "handle(%f, %f)" % (self.x, self.y) + +handles = [handle(145.000000, 50.000000), handle(43.000000, 69.000000), handle(26.000000, 135.000000), handle(48.000000, 189.000000), handle(248.000000, 188.000000)] + +selected_handle = None + +def display(da, ev): + g = gtk.gdk.GC(da.window) + i = 0 + + + + for h in handles: + draw_handle(da.window, h, str(i)) + i += 1 + draw_elip(da.window, handles) + +def mouse_event(w, e): + global selected_handle + if e.button == 1: + for h in handles: + if math.hypot(e.x-h.x, e.y - h.y) < 5: + selected_handle = (h, (e.x-h.x, e.y-h.y)) + +def mouse_release_event(w, e): + global selected_handle + selected_handle = None + +def mouse_motion_event(w, e): + global selected_handle + if selected_handle: + h, (ox, oy) = selected_handle + if(e.state & gtk.gdk.BUTTON1_MASK): + h.x = e.x - ox + h.y = e.y - oy + w.queue_draw() + + +win = gtk.Window() +win.set_default_size(400,400) +vb = gtk.VBox(False) + +da = gtk.DrawingArea() +templayout = da.create_pango_layout("") +da.connect("expose_event", display) +da.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.KEY_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK) +da.connect("button-press-event", mouse_event) +da.connect("button-release-event", mouse_release_event) +da.connect("motion-notify-event", mouse_motion_event) +#da.connect("key_press_event", key_event) +win.add(vb) +vb.pack_start(da) +win.connect("destroy", gtk.main_quit) + + +win.show_all() + +gtk.main() + +print handles diff --git a/src/python/exact-arc-length-quad-bez.py b/src/python/exact-arc-length-quad-bez.py new file mode 100644 index 0000000..fd2f4b3 --- /dev/null +++ b/src/python/exact-arc-length-quad-bez.py @@ -0,0 +1,16 @@ +import math + +def q(x, a, b, c): + sa = math.sqrt(a) + y = math.sqrt(a*x*x+b*x+c) + dp = 2*a*x + b + r = dp*y/(4*a) + t = abs(dp + 2*y*sa) + s = (4*a*c-b*b)/(a*sa*8) + return r+s*math.log(t) + +a = 1160390 +b = -922658 +c = 249477 +print q(1, a,b,c) - q(0,a,b,c) + diff --git a/src/python/test_py2geom.py b/src/python/test_py2geom.py new file mode 100644 index 0000000..2ad6e66 --- /dev/null +++ b/src/python/test_py2geom.py @@ -0,0 +1,41 @@ +#!/usr/bin/python + +import py2geom as g + +a = g.Point(1,2) +b = g.Point(31,2) +print a, b + +point_fns_1 = ["L1", "L2", "L2sq", "LInfty", "is_zero", "is_unit_vector", + "atan2", "rot90", + "unit_vector", "abs"] +point_fns_2 = ["dot", "angle_between", "distance", "distanceSq", "cross"] + +for i in point_fns_1: + print "%s:" % i, g.__dict__[i](a) +for i in point_fns_2: + print "%s:" % i, g.__dict__[i](a,b) +print "a == b", a == b +print "Lerp:", g.lerp(0.3, a,b) + +bo = g.BezOrd(2,3) +print bo +print bo.point_at(0.3) + +print bo.reverse() + +sn = g.sin(g.BezOrd(0.0,8.0),5) +print sn +print g.inverse(sn,10) +print list(sn) + +r_sn = g.roots(sn) +print len(r_sn) +print list(r_sn) + +bo = g.BezOrd(-1,1) +sb = g.SBasis() +print sb +print list(g.roots(sb)) +sb.append(bo) +print list(g.roots(sb)) diff --git a/src/toys/2dsb2d.cpp b/src/toys/2dsb2d.cpp new file mode 100644 index 0000000..3effd1f --- /dev/null +++ b/src/toys/2dsb2d.cpp @@ -0,0 +1,128 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/transforms.h> +#include <2geom/pathvector.h> +#include <2geom/svg-path-parser.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +class Sb2d2: public Toy { + Path path_a; + D2<SBasis2d> sb2; + Piecewise<D2<SBasis> > path_a_pw; + PointSetHandle hand; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Geom::Point dir(1,-2); + for(unsigned dim = 0; dim < 2; dim++) { + Geom::Point dir(0,0); + dir[dim] = 1; + for(unsigned vi = 0; vi < sb2[dim].vs; vi++) + for(unsigned ui = 0; ui < sb2[dim].us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) { + unsigned corner = iu + 2*iv; + unsigned i = ui + vi*sb2[dim].us; + Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + if(vi == 0 && ui == 0) { + base = Geom::Point(width/4., width/4.); + } + double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir); + sb2[dim][i][corner] = dl/(width/2)*pow(4.0,(double)ui+vi); + } + } + cairo_d2_sb2d(cr, sb2, dir*0.1, width); + cairo_set_source_rgba (cr, 0., 0., 0, 0.5); + cairo_stroke(cr); + for(unsigned i = 0; i < path_a_pw.size(); i++) { + D2<SBasis> B = path_a_pw[i]; + //const int depth = sb2[0].us*sb2[0].vs; + //const int surface_hand.pts = 4*depth; + //D2<SBasis> B = hand.pts_to_sbasis<3>(hand.pts.begin() + surface_hand.pts); + cairo_d2_sb(cr, B); + for(unsigned dim = 0; dim < 2; dim++) { + std::vector<double> r = roots(B[dim]); + for(double i : r) + draw_cross(cr, B(i)); + r = roots(Linear(width/4) - B[dim]); + for(double i : r) + draw_cross(cr, B(i)); + } + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + B *= (4./width); + D2<SBasis> tB = compose_each(sb2, B); + B = B*(width/2) + Geom::Point(width/4, width/4); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + + cairo_d2_sb(cr, tB); + } + + //*notify << "bo = " << sb2.index(0,0); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + PathVector paths_a = read_svgd(path_a_name); + assert(!paths_a.empty()); + path_a = paths_a[0]; + Rect bounds = path_a[0].boundsFast(); + std::cout << bounds.min() <<std::endl; + path_a = path_a * Affine(Translate(-bounds.min())); + double extreme = std::max(bounds.width(), bounds.height()); + path_a = path_a * Scale(40./extreme); + + path_a_pw = path_a.toPwSb(); + for(unsigned dim = 0; dim < 2; dim++) { + sb2[dim].us = 2; + sb2[dim].vs = 2; + const int depth = sb2[dim].us*sb2[dim].vs; + sb2[dim].resize(depth, Linear2d(0)); + } + + hand.pts.resize(sb2[0].vs*sb2[0].us*4); + handles.push_back(&hand); + + } + void resize_canvas(Geom::Rect const & s) override { + double width = s[0].extent(); + unsigned ii = 0; + for(unsigned vi = 0; vi < sb2[0].vs; vi++) + for(unsigned ui = 0; ui < sb2[0].us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) + hand.pts[ii++] = Geom::Point((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sb2d2); + return 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/toys/CMakeLists.txt b/src/toys/CMakeLists.txt new file mode 100644 index 0000000..c4929b7 --- /dev/null +++ b/src/toys/CMakeLists.txt @@ -0,0 +1,172 @@ +SET(2GEOM_TOY-FRAMEWORK-2_SRC +toy-framework-2.cpp +${2GEOM_INCLUDE_DIR}/toys/toy-framework-2.h +path-cairo.cpp +${2GEOM_INCLUDE_DIR}/toys/path-cairo.h +) +SET(2GEOM_LPE_TOY_FRAMEWORK_SRC +${2GEOM_TOY-FRAMEWORK-2_SRC} +lpe-framework.cpp +${2GEOM_INCLUDE_DIR}/toys/lpe-framework.h +) + +SET(2GEOM_TOYS-2_SRC +2dsb2d +aa +arc-bez +arc-length-param +auto-cross +boolops-toy +bound-path +bounds-test +box3d +center-warp +circle-fitting +circle-intersect +circle-line-intersect +circle-tangent-fitting +collinear-normal +conic-3 +conic-4 +conic-5 +conic-6 +conic-section-toy +convole +curvature-curve +curvature-test +curve-curve-distance +curve-curve-nearest-time +curve-intersection-by-bezier-clipping +curve-intersection-by-implicitization +cylinder3d +d2sbasis-fitting +d2sbasis-fitting-with-np +draw-toy +ellipse-area-minimizer +ellipse-bezier-intersect-toy +ellipse-fitting +ellipse-intersect-toy +ellipse-line-intersect-toy +elliptiarc-3point-center-fitting +elliptiarc-curve-fitting +elliptical-arc-toy +evolute +filet-minion +find-derivative +gear +#hatches +implicit-toy +ineaa +inner-product-clip +intersect-data +inverse-test +kinematic_templates +levelsets-test +line-toy +load-svgd +match-curve +mesh-grad +metro +minsb2d-solver +#normal-bundle +offset-toy +pair-intersect +paptest +parametrics +parser +path-along-path +path-effects +pencil +pencil-2 +plane3d +point-curve-nearest-time +portion-test +precise-flat +pw-compose-test +pw-funcs +pw-toy +rdm-area +rect_01 +rect_02 +rect_03 +rect-toy +root-finder-comparer +#rtree-toy +sanitize +#sb1d +sb2d +sb2d-solver +sbasisdim +sbasis-fitting +sb-math-test +sb-of-interval +sb-of-sb +sb-to-bez +sb-zeros +scribble +self-intersect +sketch-fitter +smash-intersector +squiggles +sweep +sweeper-toy +# these ones have only had a trivial rewrite to toy-2 +#uncross +winding-test +worms +) + +SET(2GEOM_LPE_TOYS_SRC +lpe-test +) + +OPTION(2GEOM_TOYS_LPE + "Build Inkscape Live Path Effect (LPE) Toy files" + ON) +IF(2GEOM_TOYS_LPE) + # make lib for lpetoy + add_library(lpetoy ${LIB_TYPE} ${2GEOM_LPE_TOY_FRAMEWORK_SRC}) + target_include_directories(lpetoy PUBLIC ${GTK3_INCLUDE_DIRS}) + target_link_libraries(lpetoy 2Geom::2geom ${GTK3_LIBRARIES}) + if(NOT WIN32 AND NOT APPLE) + target_link_libraries(lpetoy -lrt) + endif() + + FOREACH(source ${2GEOM_LPE_TOYS_SRC}) + add_executable(${source} ${source}.cpp) + target_link_libraries(${source} lpetoy 2Geom::2geom) + ENDFOREACH(source) + +ENDIF(2GEOM_TOYS_LPE) + +OPTION(2GEOM_TOYS + "Build the projects Toy files" + ON) +IF(2GEOM_TOYS) + # make lib for toy + ADD_LIBRARY(toy-2 ${LIB_TYPE} ${2GEOM_TOY-FRAMEWORK-2_SRC}) + target_include_directories(toy-2 PUBLIC ${GTK3_INCLUDE_DIRS}) + TARGET_LINK_LIBRARIES(toy-2 2Geom::2geom ${GTK3_LIBRARIES}) + if(NOT WIN32 AND NOT APPLE) + target_link_libraries(toy-2 -lrt) + endif() + + FOREACH(source ${2GEOM_TOYS-2_SRC}) + IF(${source} STREQUAL aa) + ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp) + TARGET_LINK_LIBRARIES(${source} affa) + ELSEIF(${source} STREQUAL ineaa) + ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp) + TARGET_LINK_LIBRARIES(${source} affa) + ELSEIF(${source} STREQUAL implicit-toy) + ADD_EXECUTABLE(${source} EXCLUDE_FROM_ALL ${source}.cpp) + TARGET_LINK_LIBRARIES(${source} affa) + ELSEIF(${source} STREQUAL boolops-cgal) + + ELSE(${source} STREQUAL aa) + ADD_EXECUTABLE(${source} ${source}.cpp) + ENDIF(${source} STREQUAL aa) + TARGET_LINK_LIBRARIES(${source} toy-2 2Geom::2geom ${GTK3_LIBRARIES} ) + ENDFOREACH(source) +ENDIF(2GEOM_TOYS) + diff --git a/src/toys/aa.cpp b/src/toys/aa.cpp new file mode 100644 index 0000000..8b852a2 --- /dev/null +++ b/src/toys/aa.cpp @@ -0,0 +1,520 @@ +#include <2geom/convex-hull.h> +#include <2geom/d2.h> +#include <2geom/geom.h> +#include <2geom/numeric/linear_system.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <aa.h> +#include <complex> +#include <algorithm> +#include <optional> + +using std::vector; +using namespace Geom; +using namespace std; + +//Geom::Rect zoom(Geom::Rect r, Geom::Point p, double s) { +// return p + (r - p)*s; +//} + +typedef std::complex<AAF> CAAF; + +struct PtLexCmp{ + bool operator()(const Point &a, const Point &b) { + return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1])); + } +}; + +void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) { + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + cairo_move_to(cr, (*ls)[0]); + cairo_line_to(cr, (*ls)[1]); + cairo_stroke(cr); + + } +} + +OptRect tighten(Rect &r, Point n, Interval lu) { + vector<Geom::Point> result; + Point resultp; + for(int i = 0; i < 4; i++) { + Point cnr = r.corner(i); + double z = dot(cnr, n); + if((z > lu[0]) and (z < lu[1])) + result.push_back(cnr); + } + for(int i = 0; i < 2; i++) { + double c = lu[i]; + + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + result.push_back((*ls)[0]); + result.push_back((*ls)[1]); + } + } + if(result.size() < 2) + return OptRect(); + Rect nr(result[0], result[1]); + for(size_t i = 2; i < result.size(); i++) { + nr.expandTo(result[i]); + } + return intersect(nr, r); +} + +AAF ls_sample_based(AAF x, vector<Point> pts) { + NL::Matrix m(pts.size(), 2); + NL::Vector v(pts.size()); + NL::LinearSystem ls(m, v); + + m.set_all(0); + v.set_all(0); + for (unsigned int k = 0; k < pts.size(); ++k) + { + m(k,0) += pts[k][0]; + m(k,1) += 1; + //std::cout << pts[k] << " "; + + v[k] += pts[k][1]; + //v[1] += pts[k][1]; + //v[2] += y2; + } + + ls.SV_solve(); + + double A = ls.solution()[0]; + double B = ls.solution()[1]; + // Ax + B = y + Interval bnd(0,0); + for (unsigned int k = 0; k < pts.size(); ++k) + { + bnd.extendTo(A*pts[k][0]+B - pts[k][1]); + } + //std::cout << A << "," << B << std::endl; + return AAF(x, A, B, bnd.extent(), + x.special); +} + +AAF md_sample_based(AAF x, vector<Point> pts) { + Geom::ConvexHull ch1(pts); + Point a, b, c; + double dia = ch1.narrowest_diameter(a, b, c); + Point db = c-b; + double A = db[1]/db[0]; + Point aa = db*(dot(db, a-b)/dot(db,db))+b; + Point mid = (a+aa)/2; + double B = mid[1] - A*mid[0]; + double dB = (a[1] - A*a[0]) - B; + // Ax + B = y + std::cout << A << "," << B << std::endl; + return AAF(x, A, B, dB, + x.special); +} + +AAF atan_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + + const double ea = atan(a); + const double eb = atan(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + double xs = sqrt(1/alpha-1); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,atan(xs))); + xs = -xs; + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,atan(xs))); + + return md_sample_based(x, pts); +} + +AAF log_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + AAF_TYPE type; + if(a > 0) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + + const double ea = log(a); + const double eb = log(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // dlog(xs) = alpha + double xs = 1/(alpha); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,log(xs))); + + return md_sample_based(x, pts); +} + +AAF exp_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + + const double ea = exp(a); + const double eb = exp(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // dexp(xs) = alpha + double xs = log(alpha); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,exp(xs))); + + return md_sample_based(x, pts); +} + +AAF pow_sample_based(AAF x, double p) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + AAF_TYPE type; + if(a >= 0) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + + const double ea = pow(a, p); + const double eb = pow(b, p); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // d(xs^p) = alpha + // p xs^(p-1) = alpha + // xs = (alpha/p)^(1-p) + double xs = pow(alpha/p, 1./(p-1)); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,pow(xs, p))); + xs = -xs; + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,pow(xs, p))); + + return md_sample_based(x, pts); +} + +Point origin; +double scale=100; + +AAF trial_eval(AAF x, AAF y) { + x = x-origin[0]; + y = y-origin[1]; + + x = x/scale; + y = y/scale; + + return x*x -y*y + -6*x +10*y-16; + return -y + log(sqrt(x))/log(x); + return y*y - x*(x-1)*(x+1); + + //return x*x - 1; + //return y - pow(x,3); + //return y - pow_sample_based(x,2.5); + //return y - log_sample_based(x); + //return y - log(x); + //return y - exp_sample_based(x*log(x)); + //return y - sqrt(sin(x)); + //return sqrt(y)*x - sqrt(x) - y - 1; + //return y-1/x; + //return exp(x)-y; + //return sin(x)-y; + //return exp_sample_based(x)-y; + //return atan(x)-y; + //return atan_sample_based(x)-y; + //return atanh(x)-y; + //return x*y; + //return 4*x+3*y-1; + //return x*x + y*y - 1; + //return sin(x*y) + cos(pow(x, 3)) - atan(x); + //return pow((x*x + y*y), 2) - (x*x-y*y); + return 4*(2*y-4*x)*(2*y+4*x-16)-16*y*y; + return pow((x*x + y*y), 2) - (x*x-y*y); + //return pow(x,3) - 3*x*x - 3*y*y; + return (x*x + y*y-1)*((x-1)*(x-1)+y*y-1); + //return x*x-y; + //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5; +} + +AAF xaxis(AAF x, AAF y) { + y = y-origin[1]; + y = y/scale; + return y; +} + +AAF xaxis2(AAF x, AAF y) { + y = y-origin[1]; + y = y/scale; + return y-4; +} + +AAF yaxis(AAF x, AAF y) { + x = x-origin[0]; + x = x/scale; + return x; +} + +class ConvexTest: public Toy { +public: + PointSetHandle test_window; + PointSetHandle samples; + PointHandle orig_handle; + ConvexTest () { + toggles.push_back(Toggle("Show trials", false)); + handles.push_back(&test_window); + handles.push_back(&samples); + handles.push_back(&orig_handle); + orig_handle.pos = Point(300,300); + test_window.push_back(Point(100,100)); + test_window.push_back(Point(200,200)); + for(int i = 0; i < 0; i++) { + samples.push_back(Point(i*100, i*100+25)); + } + } + int iters; + int splits[4]; + bool show_splits; + std::vector<Toggle> toggles; + AAF (*eval)(AAF, AAF); + Geom::Rect view; + void recursive_implicit(Rect r, cairo_t*cr, double w) { + if(show_splits) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + /*if(f.is_partial()) + cairo_set_source_rgba(cr, 1, 0, 1, 0.25); + else*/ + cairo_set_source_rgba(cr, 0, 1, 0, 0.25); + cairo_rectangle(cr, r); + cairo_stroke(cr); + cairo_restore(cr); + } + iters++; + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //assert(x.rad() > 0); + //assert(y.rad() > 0); + AAF f = (*eval)(x, y); + // pivot + double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if(ivl.extent() < 0.5*L2(n)) { + draw_line_in_rect(cr, r, n, ivl.middle()); + return; + } + if(!f.is_partial() and f.is_indeterminate()) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + if(f.is_infinite()) { + cairo_set_source_rgb(cr, 1, 0.5, 0.5); + } else if(f.is_nan()) { + cairo_set_source_rgb(cr, 1, 1, 0); + } else { + cairo_set_source_rgb(cr, 1, 0, 0); + } + cairo_rectangle(cr, r); + if(show_splits) { + cairo_stroke(cr); + } else { + cairo_fill(cr); + } + cairo_restore(cr); + return; + } + + if((r.width() > w) or (r.height()>w)) { + if(f.straddles_zero()) { + // Three possibilities: + // 1) the trim operation buys us enough that we should just iterate + Point c = r.midpoint(); + Rect oldr = r; + if(out) + r = *out; + if(1 && out && (r.area() < oldr.area()*0.25)) { + splits[0] ++; + recursive_implicit(r, cr, w); + // 2) one dimension is significantly smaller + } else if(1 && (r[1].extent() < oldr[1].extent()*0.5)) { + splits[1]++; + recursive_implicit(Rect(Interval(r.left(), r.right()), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(r.left(), r.right()), + Interval(c[1], r.bottom())), cr,w); + } else if(1 && (r[0].extent() < oldr[0].extent()*0.5)) { + splits[2]++; + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(r.top(), r.bottom())), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(r.top(), r.bottom())), cr,w); + // 3) to ensure progress we must do a four way split + } else { + splits[3]++; + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(c[1], r.bottom())), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(c[1], r.bottom())), cr,w); + } + } + } else { + } + } + + void key_hit(GdkEventKey *e) override { + if(e->keyval == 'w') toggles[0].toggle(); else + if(e->keyval == 'a') toggles[1].toggle(); else + if(e->keyval == 'q') toggles[2].toggle(); else + if(e->keyval == 's') toggles[3].toggle(); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + void scroll(GdkEventScroll* e) override { + if (e->direction == GDK_SCROLL_UP) { + scale /= 1.2; + } else if (e->direction == GDK_SCROLL_DOWN) { + scale *= 1.2; + } + redraw(); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + origin = orig_handle.pos; + if(1) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgb(cr, 0.5, 0.5, 1); + eval = xaxis; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + eval = xaxis2; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + eval = yaxis; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + cairo_restore(cr); + iters = 0; + for(int & split : splits) + split = 0; + show_splits = toggles[0].on; + eval = trial_eval; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + for(int split : splits) + *notify << split << " + "; + *notify << " = " << iters; + } + if(1) { + Rect r(test_window.pts[0], test_window.pts[1]); + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //AAF f = md_sample_based(x, samples.pts)-y; + if(0) { + x = x-500; + y = y-300; + x = x/200; + y = y/200; + AAF f = atan_sample_based(x)-y; + cout << f << endl; + } + AAF f = (*eval)(x, y); + double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + //cout << d << endl; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if(out) + cairo_rectangle(cr, *out); + cairo_rectangle(cr, r); + draw_line_in_rect(cr, r, n, ivl.min()); + cairo_stroke(cr); + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgb(cr, 0.5, 0.5, 0); + draw_line_in_rect(cr, r, n, ivl.middle()); + cairo_restore(cr); + draw_line_in_rect(cr, r, n, ivl.max()); + cairo_stroke(cr); + } + if(0) { + Geom::ConvexHull gm(samples.pts); + cairo_convex_hull(cr, gm); + cairo_stroke(cr); + Point a, b, c; + double dia = gm.narrowest_diameter(a, b, c); + cairo_save(cr); + cairo_set_line_width(cr, 2); + cairo_set_source_rgba(cr, 1, 0, 0, 0.5); + cairo_move_to(cr, b); + cairo_line_to(cr, c); + cairo_move_to(cr, a); + cairo_line_to(cr, (c-b)*dot(a-b, c-b)/dot(c-b,c-b)+b); + cairo_stroke(cr); + //std::cout << a << ", " << b << ", " << c << ": " << dia << "\n"; + cairo_restore(cr); + } + Toy::draw(cr, notify, width, height, save,timer_stream); + Point d(25,25); + toggles[0].bounds = Rect(Point(10, height-80)+d, + Point(10+120, height-80+d[1])+d); + + draw_toggles(cr, toggles); + } + +}; + +int main(int argc, char **argv) { + init(argc, argv, new ConvexTest()); + + return 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/toys/arc-bez.cpp b/src/toys/arc-bez.cpp new file mode 100644 index 0000000..4e9dace --- /dev/null +++ b/src/toys/arc-bez.cpp @@ -0,0 +1,129 @@ +#include <2geom/d2.h> + +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +Piecewise<SBasis> +arcLengthSb2(Piecewise<D2<SBasis> > const &M, double /*tol*/){ + Piecewise<D2<SBasis> > dM = derivative(M); + Piecewise<SBasis> length = integral(dot(dM, unitVector(dM))); + length-=length.segs.front().at0(); + return length; +} + + + +class ArcBez: public Toy { + PointSetHandle bez_handle; +public: + ArcBez() { + for(int i = 0; i < 6; i++) + bez_handle.push_back(uniform()*400, uniform()*400); + handles.push_back(&bez_handle); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timing_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + D2<SBasis> B = bez_handle.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0.25, 0.5, 0, 0.8); + + double tol = 0.01; + bool time_operations = true; + if(time_operations) { + std::string units_string("us"); + Timer tm; + tm.ask_for_timeslice(); + tm.start(); + Piecewise<SBasis> als = arcLengthSb(B, tol); + Timer::Time als_time = tm.lap(); + *timing_stream << "arcLengthSb based " + << ", time = " << als_time + << units_string << std::endl; + + tm.start(); + Piecewise<SBasis> als2 = arcLengthSb2(Piecewise<D2<SBasis> >(B), 0.01); + Timer::Time als2_time = tm.lap(); + + *timing_stream << "arcLengthSb2 based " + << ", time = " << als2_time + << units_string << std::endl; + double abs_error = 0; + double integrating_arc_length = 0; + tm.start(); + length_integrating(B, integrating_arc_length, abs_error, 1e-10); + Timer::Time li_time = tm.lap(); + + *timing_stream << "gsl integrating " + << ", time = " << li_time + << units_string << std::endl; + } + Piecewise<SBasis> als = arcLengthSb(B, tol); + Piecewise<SBasis> als2 = arcLengthSb2(Piecewise<D2<SBasis> >(B), 0.01); + + cairo_d2_pw_sb(cr, D2<Piecewise<SBasis> >(Piecewise<SBasis>(SBasis(Linear(0, width))) , Piecewise<SBasis>(Linear(height-5)) - Piecewise<SBasis>(als)) ); + + double abs_error = 0; + double integrating_arc_length = 0; + length_integrating(B, integrating_arc_length, abs_error, 1e-10); + *notify << "arc length = " << integrating_arc_length << "; abs error = " << abs_error << std::endl; + double als_arc_length = als.segs.back().at1(); + *notify << "arc length = " << als_arc_length << "; error = " << als_arc_length - integrating_arc_length << std::endl; + double als_arc_length2 = als2.segs.back().at1(); + *notify << "arc length2 = " << als_arc_length2 << "; error = " << als_arc_length2 - integrating_arc_length << std::endl; + + { + double err = fabs(als_arc_length - integrating_arc_length); + double scale = 10./err; + Piecewise<D2<SBasis> > dM = derivative(Piecewise<D2<SBasis> >(B)); + Piecewise<SBasis> ddM = dot(dM,dM); + Piecewise<SBasis> dMlength = sqrt(ddM,tol,3); + double plot_width = (width - 200); + + Point org(100,height - 200); + cairo_move_to(cr, org); + for(double t = 0; t < 1; t += 0.01) { + cairo_line_to(cr, org + Point(t*plot_width, scale*(sqrt(ddM.valueAt(t)) - dMlength.valueAt(t)))); + } + cairo_move_to(cr, org); + cairo_line_to(cr, org+Point(plot_width, 0)); + cairo_stroke(cr); + + draw_number(cr, org, scale); + + } + + + Toy::draw(cr, notify, width, height, save,timing_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new ArcBez()); + + return 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/toys/arc-length-param.cpp b/src/toys/arc-length-param.cpp new file mode 100644 index 0000000..2c7f3c9 --- /dev/null +++ b/src/toys/arc-length-param.cpp @@ -0,0 +1,101 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double space=10){ + //double dt=(M[0].cuts.back()-M[0].cuts.front())/space; + Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 2; + for( double t = M.cuts.front(); t < M.cuts.back(); t += space) { + Point pos = M(t), perp = Mperp(t); + draw_line_seg(cr, pos + perp, pos - perp); + } + cairo_pw_d2_sb(cr, M); + cairo_stroke(cr); +} + +#define SIZE 4 + +class LengthTester: public Toy { +public: + PointSetHandle b1_handle; + PointSetHandle b2_handle; + void draw(cairo_t *cr, + std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override { + + D2<SBasis> B1 = b1_handle.asBezier(); + D2<SBasis> B2 = b2_handle.asBezier(); + Piecewise<D2<SBasis> >B; + B.concat(Piecewise<D2<SBasis> >(B1)); + B.concat(Piecewise<D2<SBasis> >(B2)); + +// testing fuse_nearby_ends + std::vector< Piecewise<D2<SBasis> > > pieces; + pieces = fuse_nearby_ends(split_at_discontinuities(B),9); + Piecewise<D2<SBasis> > C; + for (auto & piece : pieces){ + C.concat(piece); + } +// testing fuse_nearby_ends + + cairo_set_line_width (cr, .5); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + //cairo_d2_sb(cr, B1); + cairo_pw_d2_sb(cr, C); + //cairo_pw_d2_sb(cr, B); + cairo_stroke(cr); + + Timer tm; + tm.ask_for_timeslice(); + tm.start(); + + Piecewise<D2<SBasis> > uniform_B = arc_length_parametrization(B); + Timer::Time als_time = tm.lap(); + *timer_stream << "arc_length_parametrization, time = " << als_time << std::endl; + + cairo_set_source_rgba (cr, 0., 0., 0.9, 1); + dot_plot(cr,uniform_B); + cairo_stroke(cr); + *notify << "pieces = " << uniform_B.size() << ";\n"; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + LengthTester(){ + for(int i = 0; i < SIZE; i++) { + b1_handle.push_back(150+uniform()*300,150+uniform()*300); + b2_handle.push_back(150+uniform()*300,150+uniform()*300); + } + b1_handle.pts[0] = Geom::Point(150,150); + b1_handle.pts[1] = Geom::Point(150,150); + b1_handle.pts[2] = Geom::Point(150,450); + b1_handle.pts[3] = Geom::Point(450,150); + handles.push_back(&b1_handle); + handles.push_back(&b2_handle); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new LengthTester); + return 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/toys/auto-cross.cpp b/src/toys/auto-cross.cpp new file mode 100644 index 0000000..00b5b25 --- /dev/null +++ b/src/toys/auto-cross.cpp @@ -0,0 +1,321 @@ +/* @brief + * A toy for playing around with Path::intersectSelf(). + * + * Authors: + * Rafał Siejakowski <rs@rs-math.net> + * + * Copyright 2022 the 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 <toys/toy-framework-2.h> +#include <2geom/path.h> +#include <2geom/elliptical-arc.h> +#include <2geom/svg-path-parser.h> + +using namespace Geom; +using Color = uint32_t; + +Color const RED = 0x80000000; +Color const GREEN = 0x00800000; +Color const BROWN = 0x90500000; +Color const BLUE = 0x0000ff00; +Color const BLACK = 0x00000000; + +static void set_cairo_rgb(cairo_t *c, Color rgb) +{ + cairo_set_source_rgba(c, (double)((rgb & 0xFF000000) >> 24) / 255.0, + (double)((rgb & 0x00FF0000) >> 16) / 255.0, + (double)((rgb & 0x0000FF00) >> 8) / 255.0, + 1.0); +} + +static void write_text(cairo_t *c, const char *text, Point const &position, Color color) +{ + cairo_move_to(c, position); + cairo_set_font_size(c, 12); + set_cairo_rgb(c, color); + cairo_show_text(c, text); +} + +static std::string format_point(Point const &pt) +{ + std::ostringstream ss; + ss.precision(4); + ss << pt; + return ss.str(); +} + +static EllipticalArc random_arc(Point from, Point to) +{ + double const dist = distance(from, to); + auto angle = atan2(to - from); + bool sweep = std::abs(angle) > M_PI_2; + angle *= 2; + angle = std::fmod(angle, 2.0 * M_PI); + return EllipticalArc(from, Point(0.5 * dist, 2.0 * dist), angle, false, sweep, to); +} + +class Item +{ +private: + Path _path; + Color _color; + std::string _d; + +public: + Item(Color color) + : _color{color} + {} + + void setPath(Path &&new_path) + { + _path = std::forward<Path>(new_path); + std::ostringstream oss; + oss << _path; + _d = oss.str(); + } + + void draw(cairo_t *cr) const + { + cairo_set_line_width(cr, 2); + set_cairo_rgb(cr, _color); + cairo_path(cr, _path); + cairo_stroke(cr); + _drawBezierTangents(cr); + _drawSelfIntersections(cr); + } + + void write(cairo_t *cr, Point const &pos) const + { + write_text(cr, _d.c_str(), pos, _color); + } + + std::string const& getSVGD() const { return _d; } + +private: + void _drawBezierTangents(cairo_t *c) const + { + cairo_set_line_width(c, 1); + set_cairo_rgb(c, 0x0000b000); + // Draw tangents for Beziers: + for (auto const &curve : _path) { + if (auto const *bezier = dynamic_cast<BezierCurve const *>(&curve)) { + if (bezier->order() > 1) { + auto points = bezier->controlPoints(); + cairo_move_to(c, points[0]); + cairo_line_to(c, points[1]); + cairo_stroke(c); + cairo_move_to(c, points.back()); + cairo_line_to(c, points[points.size() - 2]); + cairo_stroke(c); + } + } + } + } + + void _drawSelfIntersections(cairo_t *cr) const + { + set_cairo_rgb(cr, BLACK); + for (auto const &xing : _path.intersectSelf()) { + draw_cross(cr, xing.point()); + auto const coords = format_point(xing.point()); + write_text(cr, coords.c_str(), xing.point() + Point(8, -8), BLACK); + } + } +}; + +class AutoCross : public Toy +{ +public: + AutoCross() + : items{Item(RED), Item(GREEN), Item(BROWN)} + { + bezier_handles.pts = { {200, 400}, {100, 300}, {300, 400}, {300, 300}, {450, 300}, {500, 500}, {400, 400} }; + elliptical_handles.pts = { {500, 200}, {700, 400}, {600, 500} }; + mixed_handles.pts = { {100, 600}, {120, 690}, {300, 650}, {330, 600}, {500, 800} }; + handles.push_back(&bezier_handles); + handles.push_back(&elliptical_handles); + handles.push_back(&mixed_handles); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, + std::ostringstream *timer_stream) override + { + if (crashed) { + draw_error(cr, width, height); + } else { + try { + draw_impl(cr, width, height); + } catch (Exception &e) { + error = e.what(); + handles.clear(); + crashed = true; + } + } + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + void key_hit(GdkEventKey *ev) override + { + if (ev->keyval == GDK_KEY_space) { + print_path_d(); + } else if ((ev->keyval == GDK_KEY_V || ev->keyval == GDK_KEY_v) && (ev->state & GDK_CONTROL_MASK)) { + paste_d(); + } + + } + +private: + std::string error; + std::vector<Item> items; + PointSetHandle bezier_handles, elliptical_handles, mixed_handles; + bool crashed = false; + + void paste_d() + { + auto *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + auto *text = gtk_clipboard_wait_for_text(clipboard); + if (!text) { + return; + } + PathVector pv; + try { + pv = parse_svg_path(text); + } catch (SVGPathParseError &error) { + std::cerr << "Error pasting path d: " << error.what() << std::endl; + return; + } + if (pv.empty()) { + return; + } + Item paste_item{RED}; // TODO: cycle through a color palette. + paste_item.setPath(std::move(pv[0])); + items.push_back(paste_item); + redraw(); + } + + void print_path_d() + { + std::cout << "Path snapshots:\n"; + for (auto it = items.rbegin(); it != items.rend(); ++it) { + std::cout << it->getSVGD() << '\n'; + } + } + + void refresh_geometry() + { + // Construct the 2-segment Bézier path + auto const &cp = bezier_handles.pts; + Path bezier; + bezier.append(BezierCurveN<3>(cp[0].round(), cp[1].round(), cp[2].round(), cp[3].round())); + bezier.append(BezierCurveN<3>(cp[3].round(), cp[4].round(), cp[5].round(), cp[6].round())); + items[0].setPath(std::move(bezier)); + + // Construct the elliptical arcs + auto const &ae = elliptical_handles.pts; + Path elliptical; + elliptical.append(random_arc(ae[0], ae[1])); + elliptical.append(random_arc(ae[1], ae[2])); + items[1].setPath(std::move(elliptical)); + + // Construct a mixed path + auto const &mh = mixed_handles.pts; + Path mixed; + mixed.append(BezierCurveN<3>(mh[0], mh[1], mh[2], mh[3])); + mixed.append(random_arc(mh[3], mh[4])); + mixed.close(); + items[2].setPath(std::move(mixed)); + } + + void draw_impl(cairo_t *cr, int width, int height) + { + refresh_geometry(); + write_title(cr); + + auto text_pos = Point(20, height - 20); + for (auto const &item : items) { + item.draw(cr); + item.write(cr, text_pos); + text_pos -= Point(0, 20); + } + } + + void write_title(cairo_t *c) + { + cairo_move_to(c, 10, 40); + cairo_set_font_size(c, 30); + set_cairo_rgb(c, 0x0); + cairo_show_text(c, "Self-intersection of paths in lib2geom!"); + cairo_set_font_size(c, 14); + cairo_move_to(c, 10, 60); + cairo_show_text(c, "[Space]: Print SVG 'd' attributes to stdout"); + cairo_move_to(c, 10, 80); + cairo_show_text(c, "[Ctrl-V]: Paste a 'd' attribute from clipboard"); + } + + void draw_error(cairo_t *cr, int width, int height) + { + auto center = Point(0.5 * (double)width, 0.5 * (double)height); + cairo_move_to(cr, center + Point(-90, -100)); + cairo_line_to(cr, center + Point(100, 90)); + cairo_line_to(cr, center + Point(90, 100)); + cairo_line_to(cr, center + Point(-100, -90)); + cairo_close_path(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill(cr); + + cairo_move_to(cr, center + Point(90, -100)); + cairo_line_to(cr, center + Point(100, -90)); + cairo_line_to(cr, center + Point(-90, 100)); + cairo_line_to(cr, center + Point(-100, 90)); + cairo_close_path(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill(cr); + + cairo_move_to(cr, center + Point(-90, 120)); + cairo_show_text(cr, "Sorry, your toy has just broken :-/"); + cairo_move_to(cr, Point(10, center[Y] + 150)); + cairo_show_text(cr, error.c_str()); + } +}; + +int main(int argc, char **argv) +{ + auto toy = AutoCross(); + init(argc, argv, &toy, 800, 800); + return 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 :
\ No newline at end of file diff --git a/src/toys/boolops-toy.cpp b/src/toys/boolops-toy.cpp new file mode 100644 index 0000000..389fdc3 --- /dev/null +++ b/src/toys/boolops-toy.cpp @@ -0,0 +1,242 @@ +#include <2geom/d2.h> +#include <2geom/intersection-graph.h> +#include <2geom/path.h> +#include <2geom/sbasis.h> +#include <2geom/svg-path-parser.h> +#include <2geom/transforms.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <algorithm> +#include <cstdlib> + +using namespace Geom; + +class BoolOps : public Toy { + PathVector as, bs; + Line ah, bh; + PointHandle path_handles[4]; + std::vector<Toggle> togs; + bool path_handles_inited; + +public: + BoolOps() + : path_handles_inited(false) + {} + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + if (!path_handles_inited) { + Rect vp(Point(10,10), Point(width-10, height-10)); + setup_path_handles(vp); + } + + Line aht(path_handles[0].pos, path_handles[1].pos); + Line bht(path_handles[2].pos, path_handles[3].pos); + + PathVector ast = as * ah.transformTo(aht); + PathVector bst = bs * bh.transformTo(bht); + + Timer tm; + tm.start(); + + PathIntersectionGraph pig(ast, bst); + std::vector<Point> dix, ix, wpoints; + ix = pig.intersectionPoints(); + dix = pig.intersectionPoints(true); + wpoints = pig.windingPoints(); + PathVector result, f_in, f_out; + + if (pig.valid()) { + if (togs[0].on && !togs[1].on && !togs[2].on) { + result = pig.getAminusB(); + } + if (!togs[0].on && togs[1].on && !togs[2].on) { + result = pig.getIntersection(); + } + if (!togs[0].on && !togs[1].on && togs[2].on) { + result = pig.getBminusA(); + } + if (togs[0].on && togs[1].on && !togs[2].on) { + result = ast; + } + if (togs[0].on && !togs[1].on && togs[2].on) { + result = pig.getXOR(); + } + if (!togs[0].on && togs[1].on && togs[2].on) { + result = bst; + } + if (togs[0].on && togs[1].on && togs[2].on) { + result = pig.getUnion(); + } + } + + if (togs[5].on || togs[6].on) { + pig.fragments(f_in, f_out); + } + Timer::Time boolop_time = tm.lap(); + + cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); + + cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); + cairo_path(cr, result); + cairo_fill(cr); + + cairo_set_line_width(cr, 1); + cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); + cairo_path(cr, ast); + + cairo_stroke(cr); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_path(cr, bst); + cairo_stroke(cr); + + if (togs[5].on) { + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_path(cr, f_in); + cairo_stroke(cr); + } + if (togs[6].on) { + cairo_set_source_rgb(cr, 0, 0, 1); + cairo_path(cr, f_out); + cairo_stroke(cr); + } + + //cairo_set_line_width(cr, 1); + + if (togs[7].on) { + cairo_set_source_rgb(cr, 0, 1, 1); + for (auto & wpoint : wpoints) { + draw_handle(cr, wpoint); + } + cairo_stroke(cr); + } + + if (togs[3].on) { + cairo_set_source_rgb(cr, 0, 1, 0); + for (auto & i : ix) { + draw_handle(cr, i); + } + cairo_stroke(cr); + } + + if (togs[4].on) { + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : dix) { + draw_handle(cr, i); + } + cairo_stroke(cr); + } + + + double x = width - 90, y = height - 40, y2 = height - 80; + Point p(x, y), p2(x, y2), dpoint(25,25), xo(25,0); + togs[0].bounds = Rect(p, p + dpoint); + togs[1].bounds = Rect(p + xo, p + xo + dpoint); + togs[2].bounds = Rect(p + 2*xo, p + 2*xo + dpoint); + + togs[3].bounds = Rect(p2 - 2*xo, p2 - 2*xo + dpoint); + togs[4].bounds = Rect(p2 - xo, p2 - xo + dpoint); + togs[5].bounds = Rect(p2, p2 + dpoint); + togs[6].bounds = Rect(p2 + xo, p2 + xo + dpoint); + togs[7].bounds = Rect(p2 + 2*xo, p2 + 2*xo + dpoint); + draw_toggles(cr, togs); + + *notify << ix.size() << " intersections"; + if (dix.size() != 0) { + *notify << " + " << dix.size() << " defective"; + } + if (pig.valid()) { + *notify << "\nboolop time: " << boolop_time << std::endl; + } else { + *notify << "\nboolop failed, time: " << boolop_time << std::endl; + } + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void mouse_pressed(GdkEventButton* e) override { + toggle_events(togs, e); + Toy::mouse_pressed(e); + } + + void first_time(int argc, char** argv) override { + const char *path_a_name="svgd/winding.svgd"; + const char *path_b_name="svgd/star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + if(argc > 2) + path_b_name = argv[2]; + + as = read_svgd(path_a_name); + bs = read_svgd(path_b_name); + + OptRect abox = as.boundsExact(); + OptRect bbox = bs.boundsExact(); + + if (!abox) { + std::clog << "Error: path A is empty" << std::endl; + } + if (!bbox) { + std::clog << "Error: path B is empty" << std::endl; + } + if (!abox || !bbox) { + std::exit(1); + } + + std::vector<Point> anodes = as.nodes(); + std::vector<Point> bnodes = bs.nodes(); + + typedef std::vector<Point>::iterator Iter; + std::pair<Iter, Iter> apts = + std::minmax_element(anodes.begin(), anodes.end(), Point::LexLess<Y>()); + std::pair<Iter, Iter> bpts = + std::minmax_element(bnodes.begin(), bnodes.end(), Point::LexLess<Y>()); + + ah = Line(*apts.first, *apts.second); + bh = Line(*bpts.first, *bpts.second); + + togs.emplace_back("R", true); + togs.emplace_back("&", false); + togs.emplace_back("B", false); + + togs.emplace_back("X", true); + togs.emplace_back("D", true); + togs.emplace_back("I", false); + togs.emplace_back("O", false); + togs.emplace_back("W", false); + } + + void setup_path_handles(Rect const &viewport) { + Line aht = ah * as.boundsExact()->transformTo(viewport, Aspect(ALIGN_XMID_YMID)); + Line bht = bh * bs.boundsExact()->transformTo(viewport, Aspect(ALIGN_XMID_YMID)); + + path_handles[0] = PointHandle(aht.initialPoint()); + path_handles[1] = PointHandle(aht.finalPoint()); + path_handles[2] = PointHandle(bht.initialPoint()); + path_handles[3] = PointHandle(bht.finalPoint()); + + for (auto & path_handle : path_handles) { + handles.push_back(&path_handle); + } + path_handles_inited = true; + } + //virtual bool should_draw_numbers() {return false;} +}; + +int main(int argc, char **argv) { + init(argc, argv, new BoolOps()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/bound-path.cpp b/src/toys/bound-path.cpp new file mode 100644 index 0000000..39ce2a1 --- /dev/null +++ b/src/toys/bound-path.cpp @@ -0,0 +1,289 @@ +/* + * Bounds Path and PathVector + * + * 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/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +std::string option_formatter(double x) +{ + if (x == 0.0) + return std::string("CURVE"); + if (x == 1.0) + return std::string("PATH"); + if (x == 2.0) + return std::string("PATHVECTOR"); + + return std::string(""); +} + +class BoundsPath : public Toy +{ + enum { CURVE = 0, PATH, PATHVECTOR }; + enum { FAST = 0, EXACT = 1 }; + + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + m_selection_kind = (unsigned int) (sliders[0].value()); + + for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i) + { + cairo_set_source_rgba(cr, 0.0, 0.4*(i+1), 0.8/(i+1), 1.0); + for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j) + { + m_pathvector_coll[i][j].clear(); + for (unsigned int k = 0; k < m_curves_per_path; ++k) + { + PointSetHandle psh; + psh.pts.resize(m_handles_per_curve); + for (unsigned int h = 0; h < m_handles_per_curve; ++h) + { + unsigned int kk = k * (m_handles_per_curve-1) + h; + psh.pts[h] = m_pathvector_coll_handles[i][j].pts[kk]; + } + m_pathvector_coll[i][j].append(psh.asBezier()); + } + cairo_path(cr, m_pathvector_coll[i][j]); + } + cairo_stroke(cr); + } + + + Rect bound; + if ( (m_selection_kind == CURVE) && (m_selected_curve != -1) ) + { + const Curve & curve = m_pathvector_coll[m_selected_pathvector][m_selected_path][m_selected_curve]; + bound = toggles[0].on ? curve.boundsExact() + : curve.boundsFast(); + } + else if ( (m_selection_kind == PATH) && (m_selected_path != -1) ) + { + const Path & path = m_pathvector_coll[m_selected_pathvector][m_selected_path]; + bound = toggles[0].on ? *path.boundsExact() + : *path.boundsFast(); + } + else if ( (m_selection_kind == PATHVECTOR) && (m_selected_pathvector != -1) ) + { + const PathVector & pathvector = m_pathvector_coll[m_selected_pathvector]; + bound = toggles[0].on ? *bounds_exact(pathvector) + : *bounds_fast(pathvector); + } + + cairo_set_source_rgba(cr, 0.5, 0.0, 0.0, 1.0); + cairo_set_line_width (cr, 0.4); + cairo_rectangle(cr, bound.left(), bound.top(), bound.width(), bound.height()); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + + void mouse_pressed(GdkEventButton* e) override + { + Point pos(e->x, e->y); + double d, t; + double dist = 1e10; + Rect bound; + m_selected_pathvector = -1; + m_selected_path = -1; + m_selected_curve = -1; + if (m_selection_kind == CURVE) + { + for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i) + { + for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j) + { + for ( unsigned int k = 0; k < m_pathvector_coll[i][j].size(); ++k) + { + const Curve & curve = m_pathvector_coll[i][j][k]; + bound = toggles[0].on ? curve.boundsExact() + : curve.boundsFast(); + d = distanceSq(pos, bound); + if ( are_near(d, 0) ) + { + t = curve.nearestTime(pos); + d = distanceSq(pos, curve.pointAt(t)); + if (d < dist) + { + dist = d; + m_selected_pathvector = i; + m_selected_path = j; + m_selected_curve = k; + } + } + } + } + } + //std::cerr << "m_selected_path = " << m_selected_path << std::endl; + //std::cerr << "m_selected_curve = " << m_selected_curve << std::endl; + } + else if (m_selection_kind == PATH) + { + for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i) + { + for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j) + { + const Path & path = m_pathvector_coll[i][j]; + bound = toggles[0].on ? *path.boundsExact() + : *path.boundsFast(); + d = distanceSq(pos, bound); + if ( are_near(d, 0) ) + { + t = path.nearestTime(pos).asFlatTime(); + d = distanceSq(pos, path.pointAt(t)); + if (d < dist) + { + dist = d; + m_selected_pathvector = i; + m_selected_path = j; + } + } + } + } + } + else if (m_selection_kind == PATHVECTOR) + { + for (unsigned int i = 0; i < m_pathvector_coll.size(); ++i) + { + const PathVector & pathvector = m_pathvector_coll[i]; + bound = toggles[0].on ? *bounds_exact(pathvector) + : *bounds_fast(pathvector); + d = distanceSq(pos, bound); + if ( are_near(d, 0) ) + { + for (unsigned int j = 0; j < m_pathvector_coll[i].size(); ++j) + { + const Path & path = m_pathvector_coll[i][j]; + t = path.nearestTime(pos).asFlatTime(); + d = distanceSq(pos, path.pointAt(t)); + if (d < dist) + { + dist = d; + m_selected_pathvector = i; + } + } + } + } + } + + Toy::mouse_pressed(e); + } + + public: + BoundsPath() + { + m_total_pathvectors = 2; + m_paths_per_vector = 2; + m_curves_per_path = 3; + m_handles_per_curve = 4; + + m_selection_kind = CURVE; + m_selected_pathvector = -1; + m_selected_path = -1; + m_selected_curve = -1; + m_handles_per_path = m_curves_per_path * (m_handles_per_curve-1) + 1; + + m_pathvector_coll_handles.resize(m_total_pathvectors); + m_pathvector_coll.resize(m_total_pathvectors); + for (unsigned int k = 0; k < m_total_pathvectors; ++k) + { + m_pathvector_coll_handles[k].resize(m_paths_per_vector); + m_pathvector_coll[k].resize(m_paths_per_vector); + for (unsigned int j = 0; j < m_paths_per_vector; ++j) + { + m_pathvector_coll_handles[k][j].pts.resize(m_handles_per_path); + handles.push_back(&(m_pathvector_coll_handles[k][j])); + for (unsigned int i = 0; i < m_handles_per_path; ++i) + { + m_pathvector_coll_handles[k][j].pts[i] + = Point(500*uniform() + 300*k, 300*uniform() + 80 + 200*j); + } + } + } + + sliders.emplace_back(0, 2, 1, 0, "selection type"); + sliders[0].geometry(Point(10, 20), 50, X); + sliders[0].formatter(&option_formatter); + + Rect toggle_bound(Point(300,20), Point(390, 45)); + toggles.emplace_back(toggle_bound, "fast/exact", EXACT); + + handles.push_back(&(sliders[0])); + handles.push_back(&(toggles[0])); + } + + + private: + unsigned int m_total_pathvectors; + unsigned int m_paths_per_vector; + unsigned int m_curves_per_path; + unsigned int m_handles_per_curve; + unsigned int m_handles_per_path; + std::vector<PathVector> m_pathvector_coll; + std::vector< std::vector<PointSetHandle> > m_pathvector_coll_handles; +// PathVector m_pathvector; +// std::vector<PointSetHandle> m_pathvector_handles; + int m_selected_curve; + int m_selected_path; + int m_selected_pathvector; + unsigned int m_selection_kind; + std::vector<Slider> sliders; + std::vector<Toggle> toggles; +}; + + +int main(int argc, char **argv) +{ + init( argc, argv, new BoundsPath(), 800, 600 ); + return 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/toys/bounds-test.cpp b/src/toys/bounds-test.cpp new file mode 100644 index 0000000..f600162 --- /dev/null +++ b/src/toys/bounds-test.cpp @@ -0,0 +1,171 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){ + D2<SBasis> plot; + plot[0]=SBasis(Linear(150+a*300,150+b*300)); + plot[1]=B*(-vscale); + plot[1]+=300; + cairo_d2_sb(cr, plot); + cairo_stroke(cr); +} +static void plot_bar(cairo_t* cr, double height, double vscale=1,double a=0,double b=1){ + cairo_move_to(cr, Geom::Point(150+300*a,-height*vscale+300)); + cairo_line_to(cr, Geom::Point(150+300*b,-height*vscale+300)); + cairo_stroke(cr); +} + +class BoundsTester: public Toy { + unsigned size; + PointSetHandle hand; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + for (unsigned i=0;i<size;i++){ + hand.pts[i ][0]=150+15*(i-size); + hand.pts[i+size][0]=450+15*(i+1); + cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450)); + cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450)); + } + cairo_move_to(cr, Geom::Point(0,300)); + cairo_line_to(cr, Geom::Point(600,300)); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + + SBasis B(size, Linear()); + for (unsigned i=0;i<size;i++){ + B[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,(int)i), + -(hand.pts[i+size][1]-300)*std::pow(4.,(int)i) ); + } + B.normalize(); + plot(cr,B,1); + cairo_set_source_rgba (cr, 0., 0., 0.8, 1); + cairo_stroke(cr); + + Interval bnds = *bounds_local(B,Interval(0.,.5)); + plot_bar(cr,bnds.min(),1,.0,.5); + plot_bar(cr,bnds.max(),1,.0,.5); + cairo_set_source_rgba (cr, 0.4, 0., 0., 1); + cairo_stroke(cr); + bnds = *bounds_exact(B); + plot_bar(cr,bnds.min()); + plot_bar(cr,bnds.max()); + cairo_set_source_rgba (cr, 0.9, 0., 0., 1); + cairo_stroke(cr); + +/* +This is a multi-root test... +*/ + hand.pts[2*size ][0]=150; + hand.pts[2*size+1][0]=150; + hand.pts[2*size+2][0]=150; + hand.pts[2*size ][1]=std::max(hand.pts[2*size ][1],hand.pts[2*size+1][1]); + hand.pts[2*size+1][1]=std::max(hand.pts[2*size+1][1],hand.pts[2*size+2][1]); + vector<double> levels; + levels.push_back((300-hand.pts[2*size ][1])); + levels.push_back((300-hand.pts[2*size+1][1])); + levels.push_back((300-hand.pts[2*size+2][1])); + for (double level : levels) plot_bar(cr,level); + + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + + *notify<<"Use hand.pts to set the coefficients of the s-basis."<<std::endl; + + vector<double>my_roots; + +// cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1); +// for (unsigned i=0;i<levels.size();i++){ +// my_roots.clear(); +// my_roots=roots(B-Linear(levels[i])); +// for(unsigned j=0;j<my_roots.size();j++){ +// draw_cross(cr,Point(150+300*my_roots[j],300-levels[i])); +// } +// } + +// cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1); + + vector<vector<double> > sols=multi_roots(B,levels,.001,.001); + //map<double,unsigned> sols=multi_roots(B,levels); + //for(map<double,unsigned>::iterator sol=sols.begin();sol!=sols.end();sol++){ + // draw_handle(cr,Point(150+300*(*sol).first,300-levels[(*sol).second])); + //} + + for (unsigned i=0;i<sols.size();i++){ + for (unsigned j=0;j<sols[i].size();j++){ + draw_handle(cr,Point(150+300*sols[i][j],300-levels[i])); + } + } + cairo_set_source_rgba (cr, 0.9, 0., 0.8, 1); + +/* + + clock_t end_t; + unsigned iterations = 0; + + my_roots.clear(); + end_t = clock()+clock_t(0.1*CLOCKS_PER_SEC); + iterations = 0; + while(end_t > clock()) { + my_roots.clear(); + for (unsigned i=0;i<levels.size();i++){ + my_roots=roots(B-Linear(levels[i])); + } + iterations++; + } + *notify << 1000*0.1/iterations <<" ms = roots time"<< std::endl; + + sols.clear(); + end_t = clock()+clock_t(0.1*CLOCKS_PER_SEC); + iterations = 0; + while(end_t > clock()) { + sols.clear(); + sols=multi_roots(B,levels); + iterations++; + } + *notify << 1000*0.1/iterations <<" ms = multi roots time"<< std::endl; +*/ + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + BoundsTester(){ + size=5; + if(hand.pts.empty()) { + for(unsigned i = 0; i < 2*size; i++) + hand.pts.emplace_back(0,150+150+uniform()*300*0); + } + hand.pts.emplace_back(150,300+ 50+uniform()*100); + hand.pts.emplace_back(150,300- 50+uniform()*100); + hand.pts.emplace_back(150,300-150+uniform()*100); + handles.push_back(&hand); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new BoundsTester); + return 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:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/box3d.cpp b/src/toys/box3d.cpp new file mode 100644 index 0000000..06d2e6a --- /dev/null +++ b/src/toys/box3d.cpp @@ -0,0 +1,153 @@ +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +class Box3d: public Toy { + Point orig; + + double tmat[3][4]; + double c[8][4]; + Point corners[8]; + + PointHandle origin_handle; + PointSetHandle vanishing_points_handles, axes_handles; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Geom::Point dir(1,-2); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + // draw vertical lines for the VP sliders and keep the sliders at their horizontal positions + draw_slider_lines (cr); + axes_handles.pts[0][0] = 30; + axes_handles.pts[1][0] = 45; + axes_handles.pts[2][0] = 60; + + /* create the transformation matrix for the map P^3 --> P^2 that has the following effect: + (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0) + (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1) + (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2) + (0 : 0 : 0 : 1) --> origin (= handle #3) + */ + for (int j = 0; j < 4; ++j) { + tmat[0][j] = vanishing_points_handles.pts[j][0]; + tmat[1][j] = vanishing_points_handles.pts[j][1]; + tmat[2][j] = 1; + } + + *notify << "Projection matrix:" << endl; + for (auto & i : tmat) { + for (double j : i) { + *notify << j << " "; + } + *notify << endl; + } + + // draw the projective images of the box's corners + for (int i = 0; i < 8; ++i) { + corners[i] = proj_image (cr, c[i]); + } + draw_box(cr, corners); + cairo_set_line_width (cr, 2); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int /*argc*/, char** /*argv*/) override { + // Finite images of the three vanishing points and the origin + handles.push_back(&origin_handle); + handles.push_back(&vanishing_points_handles); + handles.push_back(&axes_handles); + vanishing_points_handles.push_back(550,350); + vanishing_points_handles.push_back(150,300); + vanishing_points_handles.push_back(380,40); + vanishing_points_handles.push_back(340,450); + // plane origin + origin_handle.pos = Point(180,65); + + // Handles for moving in axes directions + axes_handles.push_back(30,300); + axes_handles.push_back(45,300); + axes_handles.push_back(60,300); + + // Box corners + for (int i = 0; i < 8; ++i) { + c[i][0] = ((i & 1) ? 1 : 0); + c[i][1] = ((i & 2) ? 1 : 0); + c[i][2] = ((i & 4) ? 1 : 0); + c[i][3] = 1; + } + + orig = origin_handle.pos; + } + Geom::Point proj_image (cairo_t *cr, const double pt[4]) { + double res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = + tmat[j][0] * (pt[0] - (axes_handles.pts[0][1]-300)/100) + + tmat[j][1] * (pt[1] - (axes_handles.pts[1][1]-300)/100) + + tmat[j][2] * (pt[2] - (axes_handles.pts[2][1]-300)/100) + + tmat[j][3] * pt[3]; + } + if (fabs (res[2]) > 0.000001) { + Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]); + draw_handle(cr, result); + return result; + } + assert(0); // unclipped point + return Geom::Point(0,0); + } + + void draw_box (cairo_t *cr, Geom::Point corners[8]) { + cairo_move_to(cr,corners[0]); + cairo_line_to(cr,corners[1]); + cairo_line_to(cr,corners[3]); + cairo_line_to(cr,corners[2]); + cairo_close_path(cr); + + cairo_move_to(cr,corners[4]); + cairo_line_to(cr,corners[5]); + cairo_line_to(cr,corners[7]); + cairo_line_to(cr,corners[6]); + cairo_close_path(cr); + + for(int i = 0 ; i < 4; i++) { + cairo_move_to(cr,corners[i]); + cairo_line_to(cr,corners[i+4]); + } + } + + void draw_slider_lines (cairo_t *cr) { + cairo_move_to(cr, Geom::Point(20,300)); + cairo_line_to(cr, Geom::Point(70,300)); + + for(int i = 0; i < 3; i++) { + cairo_move_to(cr, Geom::Point(30 + 15*i,00)); + cairo_line_to(cr, Geom::Point(30 + 15*i,450)); + } + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Box3d); + return 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/toys/center-warp.cpp b/src/toys/center-warp.cpp new file mode 100644 index 0000000..4c0f750 --- /dev/null +++ b/src/toys/center-warp.cpp @@ -0,0 +1,113 @@ +#include <2geom/bezier-to-sbasis.h> +#include <2geom/d2.h> +#include <2geom/path.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/svg-path-parser.h> +#include <2geom/transforms.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = p[i]; + cairo_d2_sb(cr, B); + } +} + +class CentreWarp: public Toy { + Path path_a; + D2<SBasis2d> sb2; + Piecewise<D2<SBasis> > path_a_pw; + PointHandle brush_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Geom::Point dir(1,-2); + + D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + if(0) { + D2<Piecewise<SBasis> > tB(cos(B[0]*0.1)*(brush_handle.pos[0]/100) + B[0], + cos(B[1]*0.1)*(brush_handle.pos[1]/100) + B[1]); + + cairo_d2_pw_sb(cr, tB); + } else { + Piecewise<SBasis> r2 = (dot(path_a_pw - brush_handle.pos, path_a_pw - brush_handle.pos)); + Piecewise<SBasis> rc; + rc.push_cut(0); + rc.push(SBasis(Linear(1, 1)), 2); + rc.push(SBasis(Linear(1, 0)), 4); + rc.push(SBasis(Linear(0, 0)), 30); + rc *= 10; + rc.scaleDomain(1000); + Piecewise<SBasis> swr; + swr.push_cut(0); + swr.push(SBasis(Linear(0, 1)), 2); + swr.push(SBasis(Linear(1, 0)), 4); + swr.push(SBasis(Linear(0, 0)), 30); + swr *= 10; + swr.scaleDomain(1000); + cairo_pw(cr, swr);// + (height - 100)); + D2<Piecewise<SBasis> > uB = make_cuts_independent(unitVector(path_a_pw - brush_handle.pos)); + + D2<Piecewise<SBasis> > tB(compose(rc, (r2))*uB[0] + B[0], + compose(rc, (r2))*uB[1] + B[1]); + cairo_d2_pw_sb(cr, tB); + //path_a_pw = sectionize(tB); + } + cairo_stroke(cr); + + *notify << path_a_pw.size(); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + PathVector paths_a = read_svgd(path_a_name); + assert(!paths_a.empty()); + path_a = paths_a[0]; + + path_a.close(true); + path_a_pw = path_a.toPwSb(); + for(unsigned dim = 0; dim < 2; dim++) { + sb2[dim].us = 2; + sb2[dim].vs = 2; + const int depth = sb2[dim].us*sb2[dim].vs; + sb2[dim].resize(depth, Linear2d(0)); + } + + handles.push_back(&brush_handle); + brush_handle.pos = Point(100,100); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CentreWarp); + return 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/toys/circle-fitting.cpp b/src/toys/circle-fitting.cpp new file mode 100644 index 0000000..d51b1cf --- /dev/null +++ b/src/toys/circle-fitting.cpp @@ -0,0 +1,164 @@ +/* + * Circle and Elliptical Arc Fitting Example + * + * 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 <memory> +#include <2geom/circle.h> +#include <2geom/elliptical-arc.h> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class CircleFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + if (first_time) + { + first_time = false; + Point toggle_sp( 300, height - 50); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) ); + sliders[0].geometry(Point(50, height - 50), 100); + } + + size_t n = (size_t)(sliders[0].value()) + 3; + if (n < psh.pts.size()) + { + psh.pts.resize(n); + } + else if (n > psh.pts.size()) + { + psh.push_back(400*uniform()+50, 300*uniform()+50); + } + + try + { + c.fit(psh.pts); + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + + if (toggles[0].on) + { + try + { + std::unique_ptr<EllipticalArc> eap( c.arc(psh.pts[0], psh.pts[1], psh.pts[2]) ); + ea = *eap; + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + + std::cerr << "center = " << c.center() << " ray = " << c.radius() << std::endl; + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + if (!toggles[0].on) + { + cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI); + } + else + { + draw_text(cr, psh.pts[0], "initial"); + draw_text(cr, psh.pts[1], "inner"); + draw_text(cr, psh.pts[2], "final"); + + D2<SBasis> easb = ea.toSBasis(); + cairo_d2_sb(cr, easb); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleFitting() + { + first_time = true; + + psh.pts.resize(3); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + + + toggles.emplace_back(" arc / circle ", false); + sliders.emplace_back(0, 5, 1, 0, "more handles"); + + handles.push_back(&psh); + handles.push_back(&(toggles[0])); + handles.push_back(&(sliders[0])); + } + + private: + Circle c; + EllipticalArc ea; + bool first_time; + PointSetHandle psh; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new CircleFitting(), 600, 600 ); + return 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/toys/circle-intersect.cpp b/src/toys/circle-intersect.cpp new file mode 100644 index 0000000..d327a24 --- /dev/null +++ b/src/toys/circle-intersect.cpp @@ -0,0 +1,70 @@ +#include <toys/toy-framework-2.h> +#include <2geom/circle.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle psh[2]; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + double r1 = Geom::distance(psh[0].pts[0], psh[0].pts[1]); + double r2 = Geom::distance(psh[1].pts[0], psh[1].pts[1]); + + Circle c1(psh[0].pts[0], r1); + Circle c2(psh[1].pts[0], r2); + + std::vector<ShapeIntersection> result = c1.intersect(c2); + + cairo_set_line_width(cr, 1.0); + + // draw the circles + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_arc(cr, c1.center(X), c1.center(Y), c1.radius(), 0, 2*M_PI); + cairo_stroke(cr); + cairo_arc(cr, c2.center(X), c2.center(Y), c2.radius(), 0, 2*M_PI); + cairo_stroke(cr); + + // draw intersection points + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + + // show message + if (c1.contains(c2)) { + *notify << "Containment"; + } else if (!result.empty()) { + for (auto & i : result) { + *notify << i.point() << " "; + } + } else { + *notify << "No intersection"; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + psh[0].push_back(200,200); psh[0].push_back(250,200); + psh[1].push_back(150,150); psh[1].push_back(250,150); + handles.push_back(&psh[0]); + handles.push_back(&psh[1]); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/circle-line-intersect.cpp b/src/toys/circle-line-intersect.cpp new file mode 100644 index 0000000..3946ca1 --- /dev/null +++ b/src/toys/circle-line-intersect.cpp @@ -0,0 +1,67 @@ +#include <2geom/circle.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle psh[2]; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Rect all(Point(0, 0), Point(width, height)); + double r = Geom::distance(psh[0].pts[0], psh[0].pts[1]); + + Circle circ(psh[0].pts[0], r); + Line line(psh[1].pts[0], psh[1].pts[1]); + + std::vector<ShapeIntersection> result = circ.intersect(line); + + cairo_set_line_width(cr, 1.0); + + // draw the shapes + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_arc(cr, circ.center(X), circ.center(Y), circ.radius(), 0, 2*M_PI); + draw_line(cr, line, all); + cairo_stroke(cr); + + // draw intersection points + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + + // show message + if (!result.empty()) { + for (auto & i : result) { + *notify << i.point() << ", "; + } + } else { + *notify << "No intersection"; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + psh[0].push_back(200,200); psh[0].push_back(250,200); + psh[1].push_back(150,150); psh[1].push_back(250,150); + handles.push_back(&psh[0]); + handles.push_back(&psh[1]); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/circle-tangent-fitting.cpp b/src/toys/circle-tangent-fitting.cpp new file mode 100644 index 0000000..f0d74ee --- /dev/null +++ b/src/toys/circle-tangent-fitting.cpp @@ -0,0 +1,224 @@ +/* + * Circle and Elliptical Arc Fitting Example + * + * 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 <memory> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/circle.h> +#include <2geom/elliptical-arc.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + + +class LFMCircleTangentEquation + : public NL::LinearFittingModelWithFixedTerms<Point, double, Circle> +{ + public: + mutable unsigned count; // sigh + mutable Point pb; + vector<Point> bases; + void feed( NL::VectorView & coeff, double & fixed_term, Point const& p ) const + { + if (count >= bases.size()) { + coeff[0] = p[X]; + coeff[1] = p[Y]; + coeff[2] = 1; + fixed_term = p[X] * p[X] + p[Y] * p[Y]; + pb = p; + } else { + Point pb = bases[count]; + coeff[0] = p[X]; + coeff[1] = p[Y]; + coeff[2] = 0; + fixed_term = 2*p[X]*pb[X] + 2*p[Y]*pb[Y]; + count ++; + } + } + + size_t size() const + { + return 3; + } + void instance(Circle & c, NL::Vector const& coeff) const + { + c.setCoefficients(1, coeff[0], coeff[1], coeff[2]); + } +}; + + +class CircleFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + if (first_time) + { + first_time = false; + Point toggle_sp( 300, height - 50); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) ); + sliders[0].geometry(Point(50, height - 50), 100); + } + + try + { + vector<Point> points; + points.push_back(psh.pts[1]-psh.pts[0]); // tangents + //points.push_back(psh.pts[2]-psh.pts[3]); + points.push_back(psh.pts[3]); + points.push_back(psh.pts[0]); + size_t sz = points.size(); + if (sz < 3) + { + THROW_RANGEERROR("fitting error: too few points passed"); + } + LFMCircleTangentEquation model; + model.count = 0; + model.bases.push_back(psh.pts[0]); + //model.bases.push_back(psh.pts[3]); + NL::least_squeares_fitter<LFMCircleTangentEquation> 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(c, fitter.result(z)); + //c.set(psh.pts); + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + + if (toggles[0].on) + { + try + { + std::unique_ptr<EllipticalArc> eap( c.arc(psh.pts[0], psh.pts[1], psh.pts[3]) ); + ea = *eap; + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + cairo_move_to(cr, psh.pts[1]); + cairo_line_to(cr, 2*psh.pts[0] - psh.pts[1]); + cairo_move_to(cr, psh.pts[2]); + cairo_line_to(cr, 2*psh.pts[3] - psh.pts[2]); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + if (!toggles[0].on) + { + cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI); + } + else + { + draw_text(cr, psh.pts[0], "initial"); + draw_text(cr, psh.pts[1], "inner"); + draw_text(cr, psh.pts[2], "inner"); + draw_text(cr, psh.pts[3], "final"); + + D2<SBasis> easb = ea.toSBasis(); + cairo_d2_sb(cr, easb); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleFitting() + { + first_time = true; + + psh.pts.resize(4); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(450+100, 250+100); + psh.pts[2] = Point(250+100, 400+100); + psh.pts[3] = Point(250, 400); + + + + toggles.emplace_back(" arc / circle ", false); + sliders.emplace_back(0, 5, 1, 0, "more handles"); + + handles.push_back(&psh); + handles.push_back(&(toggles[0])); + handles.push_back(&(sliders[0])); + } + + private: + Circle c; + EllipticalArc ea; + bool first_time; + PointSetHandle psh; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new CircleFitting(), 600, 600 ); + return 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/toys/collinear-normal.cpp b/src/toys/collinear-normal.cpp new file mode 100644 index 0000000..a5e1af5 --- /dev/null +++ b/src/toys/collinear-normal.cpp @@ -0,0 +1,204 @@ +/* + * Show off collinear normals between two Bezier curves. + * The intersection points are found by using Bezier clipping. + * + * 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/d2.h> +#include <2geom/basic-intersection.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/ray.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + + + +class CurveIntersect : public Toy +{ + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.8, 0., 0, 1); + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0.0, 0., 0, 1); + D2<SBasis> B = pshB.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + draw_text(cr, A.at0(), "A"); + draw_text(cr, B.at0(), "B"); + + Timer tm; + tm.ask_for_timeslice(); + tm.start(); + + find_collinear_normal(xs, pshA.pts, pshB.pts, m_precision); + Timer::Time als_time = tm.lap(); + *timer_stream << "find_collinear_normal " << als_time << std::endl; + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.7, 1); + for (auto & x : xs) + { + Point At = A(x.first); + Point Bu = B(x.second); + draw_axis(cr, At, Bu); + draw_handle(cr, At); + draw_handle(cr, Bu); + + } + cairo_stroke(cr); + + double h_a_t = 0, h_b_t = 0; + + double h_dist = hausdorfl( A, B, m_precision, &h_a_t, &h_b_t); + { + Point At = A(h_a_t); + Point Bu = B(h_b_t); + cairo_move_to(cr, At); + cairo_line_to(cr, Bu); + draw_handle(cr, At); + draw_handle(cr, Bu); + cairo_save(cr); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + cairo_stroke(cr); + cairo_restore(cr); + } + /*h_dist = hausdorf( A, B, m_precision, &h_a_t, &h_b_t); + { + Point At = A(h_a_t); + Point Bu = B(h_b_t); + draw_axis(cr, At, Bu); + draw_handle(cr, At); + draw_handle(cr, Bu); + cairo_save(cr); + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.0, 0.7, 0.0, 1); + cairo_stroke(cr); + cairo_restore(cr); + }*/ + *notify << "Hausdorf distance = " << h_dist + << " occurring at " << h_a_t + << " B=" << h_b_t << std::endl; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) + { + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); + } + + void draw_ray(cairo_t* cr, Ray const& r) + { + double angle = r.angle(); + draw_segment(cr, r.origin(), angle, m_length); + } + + void draw_axis(cairo_t* cr, Point const& p1, Point const& p2) + { + double d = Geom::distance(p1, p2); + d = d + d/4; + Point q1 = Ray(p1, p2).pointAt(d); + Point q2 = Ray(p2, p1).pointAt(d); + draw_segment(cr, q1, q2); + } + +public: + CurveIntersect(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + handles.push_back(&pshA); + for (unsigned int i = 0; i <= A_bez_ord; ++i) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200)); + handles.push_back(&pshB); + for (unsigned int i = 0; i <= B_bez_ord; ++i) + pshB.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200)); + + m_precision = 1e-6; + } + +private: + unsigned int A_bez_ord, B_bez_ord; + PointSetHandle pshA, pshB, pshC; + std::vector< std::pair<double, double> > xs; + double m_precision; + double m_width, m_height, m_length; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord = 4; + unsigned int B_bez_ord = 6; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + + + init( argc, argv, new CurveIntersect(A_bez_ord, B_bez_ord), 800, 800); + return 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/toys/conic-3.cpp b/src/toys/conic-3.cpp new file mode 100644 index 0000000..64b2c27 --- /dev/null +++ b/src/toys/conic-3.cpp @@ -0,0 +1,96 @@ +/** + * elliptics via C-curves. (njh) + * Limited to 180 degrees (by end point and tangent matching criteria) + * Also represents cycloids + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +Linear z0(0.5,1.); + +unsigned total_pieces; + +double sinC(double t) { return t - sin(t);} +double cosC(double t) { return 1 - cos(t);} +double tanC(double t) { return sinC(t) / cosC(t);} + +class Conic3: public Toy { + PointSetHandle psh; + public: + Conic3 () { + psh.push_back(100, 500); + psh.push_back(100, 500 - 200*M_PI/2); + psh.push_back(500, 500 - 200*M_PI/2); + psh.push_back(500, 500); + handles.push_back(&psh); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 0.5); + cairo_stroke(cr); + + Geom::Point a[2] = {psh.pts[0] - psh.pts[1], + psh.pts[2] - psh.pts[1]}; + double angle = Geom::angle_between(a[0], a[1]); + double len = std::max(Geom::L2(a[0]), + Geom::L2(a[1])); + for(auto & i : a) + i = len*unit_vector(i); + *notify << "angle = " << angle; + *notify << " sinC = " << sinC(angle); + *notify << " cosC = " << cosC(angle); + *notify << " tanC = " << tanC(angle); + vector<Geom::Point> e_a_h = psh.pts; + + double alpha = M_PI; + Piecewise<SBasis> pw(Linear(0, alpha)); + Piecewise<SBasis> C = cos(pw); + Piecewise<SBasis> S = sin(pw); + Piecewise<SBasis> sinC = pw - S; + Piecewise<SBasis> cosC = Piecewise<SBasis>(1) - C; + Piecewise<SBasis> Z3 = sinC/sinC(1); + Piecewise<SBasis> Z0 = reverse(Z3); + Piecewise<SBasis> Z2 = cosC/cosC(1) - Z3; + Piecewise<SBasis> Z1 = reverse(Z2); + + Piecewise<SBasis> Z[4] = {Z0, Z1, Z2, Z3}; + + D2<Piecewise<SBasis> > B; + for(unsigned dim = 0; dim < 2; dim++) { + B[dim] = Piecewise<SBasis>(0); + for(unsigned i = 0; i < 4; i++) { + B[dim] += Z[i]*e_a_h[i][dim]; + } + } + cairo_d2_pw_sb(cr, B); + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Conic3()); + + return 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/toys/conic-4.cpp b/src/toys/conic-4.cpp new file mode 100644 index 0000000..0dbecd6 --- /dev/null +++ b/src/toys/conic-4.cpp @@ -0,0 +1,129 @@ +/** + * elliptics via 5 point w-pi basis. (njh) + * Affine, endpoint, tangent, exact circle + * full circle. Convex containment implies small circle. + * Also represents lumpy polar type curves + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +const double w = 1./3; +const double cwp = cos(w*M_PI); +const double swp = sin(w*M_PI); +/*double phi(double t, double w) { return sin(w*t) - w*sin(t); } +double phih(double t, double w) { return sin(w*t) + w*sin(t); } +double b4(double t, double w) {return phi(t/2,w)*phih(t/2,w)/(swp*swp);} +double b3(double t, double w) {return cwp*phi(t,w)/(2*swp) - cwp*cwp*b4(t,w); } +double b2(double t, double w) {return 2*w*w*sin(t/2)*sin(t/2);} +double b1(double t, double w) {return b3(2*M_PI - t, w);} +double b0(double t, double w) {return b4(2*M_PI - t, w);}*/ + +class arc_basis{ +public: + Piecewise<SBasis> basis[5]; + double w; + + Piecewise<SBasis> phi(Piecewise<SBasis> const &d, double w) { + return sin(d*w) - sin(d)*w; + } + Piecewise<SBasis> phih(Piecewise<SBasis> const &d, double w) { + return sin(d*w) + sin(d)*w; + } + Piecewise<SBasis> b4(Piecewise<SBasis> const &d, double w) { + return phi(d*.5,w)/(swp*swp)*phih(d*.5,w); + } + Piecewise<SBasis> b3(Piecewise<SBasis> const &d, double w) { + return phi(d,w)*(cwp/(2*swp)) - b4(d,w)*(cwp*cwp); + } + + Piecewise<SBasis> b2(Piecewise<SBasis> const &d, double w) { + return sin(d*.5)*(2*w*w)*sin(d*.5); + } + Piecewise<SBasis> b1(Piecewise<SBasis> const &d, double w) { + return b3(reverse(d), w); + } + Piecewise<SBasis> b0(Piecewise<SBasis> const &d, double w) { + return b4(reverse(d), w); + } + + + arc_basis(double w) { + Piecewise<SBasis> dom(Linear(0, 2*M_PI)); + basis[0] = b4(dom, w); + basis[1] = b3(dom, w); + basis[2] = b2(dom, w); + basis[3] = b1(dom, w); + basis[4] = b0(dom, w); + } + +}; + +class Conic4: public Toy { + PointSetHandle psh; + public: + Conic4 () { + double sc = 30; + Geom::Point c(6*sc, 6*sc); + psh.push_back(sc*Geom::Point(0,0)+c); + psh.push_back(sc*Geom::Point(tan(w*M_PI)/w, 0)+c); + psh.push_back(sc*Geom::Point(0, 1/(w*w))+c); + psh.push_back(sc*Geom::Point(-tan(w*M_PI)/w, 0)+c); + psh.push_back(sc*Geom::Point(0,0)+c); + handles.push_back(&psh); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + std::vector<Geom::Point> e_h = psh.pts; + for(int i = 0; i < 5; i++) { + Geom::Point p = e_h[i]; + + if(i) + cairo_line_to(cr, p); + else + cairo_move_to(cr, p); + } + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + + arc_basis ab(1./3); + D2<Piecewise<SBasis> > B; + + for(unsigned dim = 0; dim < 2; dim++) + for(unsigned i = 0; i < 5; i++) + B[dim] += ab.basis[i]*e_h[i][dim]; + + cairo_d2_pw_sb(cr, B); + cairo_set_source_rgba (cr, 1., 0.5, 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Conic4()); + + return 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/toys/conic-5.cpp b/src/toys/conic-5.cpp new file mode 100644 index 0000000..51140c1 --- /dev/null +++ b/src/toys/conic-5.cpp @@ -0,0 +1,356 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/path-intersection.h> +#include <2geom/nearest-time.h> +#include <2geom/line.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> + +#include <cstdlib> +#include <map> +#include <vector> +#include <algorithm> +#include <optional> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> + + + +#include <2geom/conicsec.h> + +using namespace Geom; +using namespace std; + + +// File: convert.h +#include <sstream> +#include <stdexcept> + +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(); +} + +void draw_hull(cairo_t*cr, RatQuad rq) { + cairo_move_to(cr, rq.P[0]); + cairo_line_to(cr, rq.P[1]); + cairo_line_to(cr, rq.P[2]); + cairo_stroke(cr); +} + + + +void draw(cairo_t* cr, xAx C, Rect bnd) { + if(bnd[1].extent() < 5) return; + vector<double> prev_rts; + double py = bnd[Y].min(); + for(int i = 0; i < 100; i++) { + double t = i/100.; + double y = bnd[Y].valueAt(t); + vector<double> rts = C.roots(Point(1, 0), Point(0, y)); + int top = 0; + for(unsigned j = 0; j < rts.size(); j++) { + if(bnd[0].contains(rts[j])) { + rts[top] = rts[j]; + top++; + } + } + rts.erase(rts.begin()+top, rts.end()); + + if(rts.size() == prev_rts.size()) { + for(unsigned j = 0; j < rts.size(); j++) { + cairo_move_to(cr, prev_rts[j], py); + cairo_line_to(cr, rts[j], y); + cairo_stroke(cr); + } + } else { + draw(cr, C, Rect(bnd[X], Interval(py, y))); + } + prev_rts = rts; + py = y; + } +} + +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])); +} + +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); +} + +void draw_ratquad(cairo_t*cr, RatQuad rq, double tol=1e-1) { + CubicBezier cb = rq.toCubic(); + // I tried using the nearest point to 0.5 for the error, but the improvement was negligible + if(L2(cb.pointAt(0.5) - rq.pointAt(0.5)) > tol) { + RatQuad a, b; + rq.split(a, b); + draw_ratquad(cr, a, tol); + draw_ratquad(cr, b, tol); + } else { + cairo_curve(cr, cb); + //draw_cross(cr, cb.pointAt(0)); + //draw_cross(cr, cb.pointAt(1)); + } +} + + +class Conic5: public Toy { + PointSetHandle path_handles; + PointHandle oncurve; + PointSetHandle cutting_plane; + std::vector<Slider> sliders; + RectHandle rh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + if(0) { + Path path; + path = Path(path_handles.pts[0]); + D2<SBasis> c = handles_to_sbasis(path_handles.pts.begin(), 2); + path.append(c); + + cairo_save(cr); + cairo_path(cr, path); + cairo_set_source_rgba (cr, 0., 1., 0, 0.3); + cairo_set_line_width (cr, 3); + cairo_stroke(cr); + cairo_restore(cr); + + //double w = exp(sliders[0].value()); + } + Point A = path_handles.pts[0]; + Point B = path_handles.pts[1]; + Point C = path_handles.pts[2]; + + if(1) { + QuadraticBezier qb(A, B, C); + //double abt = qb.nearestTime(oncurve.pos); + //oncurve.pos = qb.pointAt(abt); + + RatQuad rq = RatQuad::fromPointsTangents(A, B-A, oncurve.pos, C, B -C); //( A, B, C, w); + + cairo_save(cr); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + draw_ratquad(cr, rq); + //cairo_d2_sb(cr, rq.hermite()); + cairo_stroke(cr); + cairo_restore(cr); + } + + if(0) { + RatQuad rq = RatQuad::circularArc(A, B, C); + + cairo_save(cr); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + RatQuad a, b; + rq.split(a,b); + cairo_curve(cr, a.toCubic()); + cairo_curve(cr, b.toCubic()); + cairo_stroke(cr); + cairo_restore(cr); + } + + Rect screen_rect(Interval(10, width-10), Interval(10, height-10)); + Line cutLine(cutting_plane.pts[0], cutting_plane.pts[1]); + //double dist; + //Point norm = cutLine.normalAndDist(dist); + + const unsigned N = 3; + xAx sources[N] = { + xAx::fromPoint(A)*(exp(-sliders[0].value())), + xAx::fromPoint(B)*(exp(-sliders[1].value())), + xAx::fromPoint(C)*(exp(-sliders[2].value())) + //xAx::fromLine(Line(A, oncurve.pos)) + }; + for(unsigned i = 0; i < N; i++) { + //*notify << sources[i] << "\n"; + } + for(unsigned i = 0; i < N; i++) { + for(unsigned j = i+1; j < N; j++) { + xAx Q = sources[i]-sources[j]; + *notify << Q << " is a " << Q.categorise() << "\n"; + } + } + { + cairo_save(cr); + cairo_set_source_rgba(cr, 0, 0, 1, 0.5); + + ::draw(cr, (sources[0]-sources[1]), screen_rect); + ::draw(cr, (sources[0]-sources[2]), screen_rect); + ::draw(cr, (sources[1]-sources[2]), screen_rect); + cairo_restore(cr); + } + { + string os; + for(unsigned i = 0; i < N; i++) { + for(unsigned j = i+1; j < N; j++) { + xAx Q = sources[i]-sources[j]; + Interval iQ = Q.extrema(rh.pos); + if(iQ.contains(0)) { + os += stringify(iQ) + "\n"; + + Q.toCurve(rh.pos); + vector<Point> crs = Q.crossings(rh.pos); + for(auto & ei : crs) { + draw_cross(cr, ei); + } + + } + } + } + + draw_text(cr, rh.pos.midpoint(), + os); + } + if(1) { + xAx oxo=sources[0] - sources[2]; + Timer tm; + + tm.ask_for_timeslice(); + tm.start(); + + std::vector<Point> intrs = intersect(oxo, sources[0] - sources[1]); + Timer::Time als_time = tm.lap(); + *notify << "intersect time = " << als_time << std::endl; + for(auto & intr : intrs) { + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0,0); + draw_cross(cr, intr); + cairo_stroke(cr); + cairo_restore(cr); + } + + std::optional<RatQuad> orq = oxo.toCurve(rh.pos); + if(orq) { + RatQuad rq = *orq; + draw_hull(cr, rq); + vector<SBasis> hrq = rq.homogeneous(); + SBasis vertex_poly = (sources[0] - sources[1]).evaluate_at(hrq[0], hrq[1], hrq[2]); + //*notify << "\n0: " << hrq[0]; + //*notify << "\n1: " << hrq[1]; + //*notify << "\n2: " << hrq[2]; + vector<double> rts = roots(vertex_poly); + //*notify << "\nvertex poly:" << vertex_poly << '\n'; + for(unsigned i = 0; i < rts.size(); i++) { + draw_circ(cr, Point(rq.pointAt(rts[i]))); + *notify << "\nrq" << i << ":" << rts[i]; + } + + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + RatQuad a, b; + rq.split(a,b); + cairo_curve(cr, a.toCubic()); + cairo_curve(cr, b.toCubic()); + cairo_stroke(cr); + cairo_restore(cr); + } + } + if(0) { + RatQuad a, b; + //rq.split(a,b); + //cairo_move_to(cr, rq.toCubic().pointAt(0.5)); + cairo_line_to(cr, a.P[2]); + cairo_stroke(cr); + + cairo_curve(cr, a.toCubic()); + cairo_curve(cr, b.toCubic()); + + } + cairo_stroke(cr); + + //*notify << "w = " << w << "; lambda = " << rq.lambda() << "\n"; + Toy::draw(cr, notify, width, height, save, timer_stream); + } + +public: + Conic5() { + handles.push_back(&path_handles); + handles.push_back(&rh); + rh.pos = Rect(Point(100,100), Point(200,200)); + rh.show_center_handle = true; + handles.push_back(&oncurve); + for(int j = 0; j < 3; j++){ + path_handles.push_back(uniform()*400, 100+ uniform()*300); + } + oncurve.pos = ((path_handles.pts[0]+path_handles.pts[1]+path_handles.pts[2])/3); + handles.push_back(&cutting_plane); + for(int j = 0; j < 2; j++){ + cutting_plane.push_back(uniform()*400, 100+ uniform()*300); + } + sliders.emplace_back(0.0, 5.0, 0, 0.0, "a"); + sliders.emplace_back(0.0, 5.0, 0, 0.0, "b"); + sliders.emplace_back(0.0, 5.0, 0, 0.0, "c"); + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + } + + void first_time(int /*argc*/, char**/* argv*/) override { + + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Conic5()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/conic-6.cpp b/src/toys/conic-6.cpp new file mode 100644 index 0000000..90da671 --- /dev/null +++ b/src/toys/conic-6.cpp @@ -0,0 +1,304 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/path-intersection.h> +#include <2geom/nearest-time.h> +#include <2geom/line.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> + +#include <cstdlib> +#include <map> +#include <vector> +#include <algorithm> +#include <optional> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> + +#include <2geom/conicsec.h> + +std::vector<Geom::RatQuad> xAx_to_RatQuads(Geom::xAx const &/*C*/, Geom::Rect const &/*bnd*/) { + // find points on boundary + // if there are exactly 0 points return + // if there are exactly 2 points fit ratquad and return + // if there are an odd number, split bnd on the point with the smallest dot(unit_vector(grad), rect_edge) + // sort into clockwise order ABCD + // compute corresponding tangents + // test boundary points against the line through A + // if all on one side + // + // if A,X and Y,Z + // ratquad from A,X and Y,Z + return std::vector<Geom::RatQuad>(); +} + + + +using namespace Geom; +using namespace std; + + +// File: convert.h +#include <sstream> +#include <stdexcept> + +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(); +} + +namespace Geom{ +xAx degen; +}; + +void draw_hull(cairo_t*cr, RatQuad rq) { + cairo_move_to(cr, rq.P[0]); + cairo_line_to(cr, rq.P[1]); + cairo_line_to(cr, rq.P[2]); + cairo_stroke(cr); +} + + + +void draw(cairo_t* cr, xAx C, Rect bnd) { + if(bnd[1].extent() < 5) return; + vector<double> prev_rts; + double py = bnd[Y].min(); + for(int i = 0; i < 100; i++) { + double t = i/100.; + double y = bnd[Y].valueAt(t); + vector<double> rts = C.roots(Point(1, 0), Point(0, y)); + int top = 0; + for(unsigned j = 0; j < rts.size(); j++) { + if(bnd[0].contains(rts[j])) { + rts[top] = rts[j]; + top++; + } + } + rts.erase(rts.begin()+top, rts.end()); + + if(rts.size() == prev_rts.size()) { + for(unsigned j = 0; j < rts.size(); j++) { + cairo_move_to(cr, prev_rts[j], py); + cairo_line_to(cr, rts[j], y); + cairo_stroke(cr); + } +/* } else if(prev_rts.size() == 1) { + for(unsigned j = 0; j < rts.size(); j++) { + cairo_move_to(cr, prev_rts[0], py); + cairo_line_to(cr, rts[j], y); + cairo_stroke(cr); + } + } else if(rts.size() == 1) { + for(unsigned j = 0; j < prev_rts.size(); j++) { + cairo_move_to(cr, prev_rts[j], py); + cairo_line_to(cr, rts[0], y); + cairo_stroke(cr); + }*/ + } else { + draw(cr, C, Rect(bnd[0], Interval(py, y))); + /*for(unsigned j = 0; j < rts.size(); j++) { + cairo_move_to(cr, rts[j], y); + cairo_rel_line_to(cr, 1,1); + }*/ + } + prev_rts = rts; + py = y; + } +} + +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])); +} + +class Conic6: public Toy { + PointSetHandle C1H, C2H; + std::vector<Slider> sliders; + Point mouse_sampler; + + void mouse_moved(GdkEventMotion* e) override { + mouse_sampler = Point(e->x, e->y); + Toy::mouse_moved(e); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + Rect screen_rect(Interval(10, width-10), Interval(10, height-10)); + + Geom::xAx C1 = xAx::fromPoints(C1H.pts); + ::draw(cr, C1, screen_rect); + *notify << C1; + + Geom::xAx C2 = xAx::fromPoints(C2H.pts); + ::draw(cr, C2, screen_rect); + *notify << C2; + + + 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()) { + cairo_save(cr); + + unsigned i = 0; + double t = T.valueAt(rts[i]); + double s = S.valueAt(rts[i]); + *notify << t << "; " << s << std::endl; + /*double C0[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]}};*/ + xAx xC0 = C1*t + C2*s; + //::draw(cr, xC0, screen_rect); // degen + + std::optional<Point> oB0 = xC0.bottom(); + + Point B0 = *oB0; + //*notify << B0 << " = " << C1.gradient(B0); + draw_circ(cr, B0); + + Point n0, n1; + // Are these just the eigenvectors of A11? + 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]; + double d = std::sqrt(b*b-c); + 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]; + double d = std::sqrt(b*b-c); + n0 = Point(b+d, 1); + n1 = Point(b-d, 1); + } + cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); + + Line L0 = Line::from_origin_and_vector(B0, rot90(n0)); + draw_line(cr, L0, screen_rect); + Line L1 = Line::from_origin_and_vector(B0, rot90(n1)); + draw_line(cr, L1, screen_rect); + + cairo_set_source_rgb(cr, 1, 0., 0.); + rts = C1.roots(L0); + for(double rt : rts) { + Point P = L0.pointAt(rt); + draw_cross(cr, P); + *notify << C1.valueAt(P) << "; " << C2.valueAt(P) << "\n"; + } + rts = C1.roots(L1); + for(double rt : rts) { + Point P = L1.pointAt(rt); + draw_cross(cr, P); + *notify << C1.valueAt(P) << "; "<< C2.valueAt(P) << "\n"; + } + cairo_stroke(cr); + cairo_restore(cr); + } + + ::draw(cr, C1*sliders[0].value() + C2*sliders[1].value(), screen_rect); + + std::vector<Point> res = intersect(C1, C2); + for(auto & re : res) { + draw_circ(cr, re); + } + + cairo_stroke(cr); + + //*notify << "w = " << w << "; lambda = " << rq.lambda() << "\n"; + Toy::draw(cr, notify, width, height, save, timer_stream); + } + +public: + Conic6() { + for(int j = 0; j < 5; j++){ + C1H.push_back(uniform()*400, 100+ uniform()*300); + C2H.push_back(uniform()*400, 100+ uniform()*300); + } + handles.push_back(&C1H); + handles.push_back(&C2H); + sliders.emplace_back(-1.0, 1.0, 0, 0.0, "a"); + sliders.emplace_back(-1.0, 1.0, 0, 0.0, "b"); + sliders.emplace_back(0.0, 5.0, 0, 0.0, "c"); + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + } + + void first_time(int /*argc*/, char**/* argv*/) override { + + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Conic6()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/conic-section-toy.cpp b/src/toys/conic-section-toy.cpp new file mode 100644 index 0000000..31695be --- /dev/null +++ b/src/toys/conic-section-toy.cpp @@ -0,0 +1,813 @@ +/* + * Conic Section Toy + * + * 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. + */ + + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +//#define CLIP_WITH_CAIRO_SUPPORT +#ifdef CLIP_WITH_CAIRO_SUPPORT + #include <2geom/conic_section_clipper_cr.h> +#endif + + +#include <2geom/conicsec.h> +#include <2geom/line.h> + +//#include <iomanip> +#include <optional> + + +using namespace Geom; + + +class ConicSectionToy : public Toy +{ + enum menu_item_t + { + SHOW_MENU = 0, + TEST_VERTEX_FOCI, + TEST_FITTING, + TEST_ECCENTRICITY, + TEST_DEGENERATE, + TEST_ROOTS, + TEST_NEAREST_POINT, + TEST_BOUND, + TEST_CLIP, + TEST_TANGENT, + TEST_DUAL, + TOTAL_ITEMS // this one must be the last item + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &ConicSectionToy::draw_menu; + } + + + void init_common() + { + init_vertex_foci(); + set_control_geometry = true; + } + + void draw_common (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_vertex_foci(cr, notify, width, height, save, timer_stream); + } + + +/* + * TEST VERTEX FOCI + */ + void init_vertex_foci() + { + set_common_control_geometry = true; + handles.clear(); + + focus1.pos = Point(300, 300); + focus2.pos = Point(500, 300); + vertex.pos = Point(200, 300); + + parabola_toggle = Toggle("set/unset F2 to infinity", false); + + handles.push_back (&vertex); + handles.push_back (&focus1); + handles.push_back (&focus2); + handles.push_back (¶bola_toggle); + } + + void draw_vertex_foci (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream * /*timer_stream*/) + { + init_vertex_foci_ctrl_geom(cr, notify, width, height); + + + if (!parabola_toggle.on ) + { + if (focus2.pos == Point(infinity(),infinity())) + focus2.pos = Point(m_width/2, m_height/2); + double df1f2 = distance (focus1.pos, focus2.pos); + if (df1f2 > 20 ) + { + Line axis(focus1.pos,focus2.pos); + vertex.pos = projection (vertex.pos, axis); + } + else if (df1f2 > 1) + { + Line axis(focus1.pos,vertex.pos); + focus2.pos = projection (focus2.pos, axis); + } + else + { + Line axis(focus1.pos,vertex.pos); + focus2.pos = focus1.pos; + } + } + else + { + focus2.pos = Point(infinity(),infinity()); + } + + cs.set (vertex.pos, focus1.pos, focus2.pos); + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.5); + draw (cr, cs, m_window); + cairo_stroke(cr); + + draw_label(cr, focus1, "F1"); + if (!parabola_toggle.on) draw_label(cr, focus2, "F2"); + draw_label(cr, vertex, "V"); + cairo_stroke(cr); + + *notify << cs.categorise() << ": " << cs << std::endl; + } + + void init_vertex_foci_ctrl_geom (cairo_t* /*cr*/, + std::ostringstream* /*notify*/, + int width, int /*height*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + + Point toggle_sp( width - 200, 10); + parabola_toggle.bounds = Rect (toggle_sp, toggle_sp + Point(190,30)); + } + } + + +/* + * TEST FITTING + */ + void init_fitting() + { + set_common_control_geometry = true; + handles.clear(); + + psh.pts.resize(5); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + psh.pts[3] = Point(400, 320); + psh.pts[4] = Point(50, 250); + + fitting_slider.set (0, 5, 1, 0, "more handles"); + + handles.push_back(&psh); + handles.push_back(&fitting_slider); + } + + void draw_fitting (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream * /*timer_stream*/) + { + init_fitting_ctrl_geom(cr, notify, width, height); + + size_t n = (size_t)(fitting_slider.value()) + 5; + if (n < psh.pts.size()) + { + psh.pts.resize(n); + } + else if (n > psh.pts.size()) + { + psh.push_back (std::fabs (width - 100) * uniform() + 50, + std::fabs (height - 100) * uniform() + 50); + } + + cs.set (psh.pts); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.5); + draw (cr, cs, m_window); + cairo_stroke(cr); + *notify << cs.categorise() << ": " << cs << std::endl; + } + + void init_fitting_ctrl_geom (cairo_t* /*cr*/, + std::ostringstream* /*notify*/, + int /*width*/, int height) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + fitting_slider.geometry (Point(50, height - 50), 100); + } + } + + +/* + * TEST ECCENTRICITY + */ + void init_eccentricity() + { + set_common_control_geometry = true; + handles.clear(); + + p1 = Point (100, 100); + p2 = Point (100, 400); + focus1 = Point (300, 250); + + eccentricity_slider.set (0, 3, 0, 1, "eccentricity"); + + handles.push_back (&p1); + handles.push_back (&p2); + handles.push_back (&focus1); + handles.push_back (&eccentricity_slider); + } + + void draw_eccentricity (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream * /*timer_stream*/) + { + init_eccentricity_ctrl_geom(cr, notify, width, height); + + Line directrix (p1.pos, p2.pos); + + cs.set (focus1.pos, directrix, eccentricity_slider.value()); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.5); + draw (cr, cs, m_window); + cairo_stroke(cr); + + draw_label (cr, focus1, "F"); + draw_line (cr, directrix, m_window); + draw_label(cr, p1, "directrix"); + cairo_stroke(cr); + + *notify << cs.categorise() << ": " << cs << std::endl; + } + + void init_eccentricity_ctrl_geom (cairo_t* /*cr*/, + std::ostringstream* /*notify*/, + int /*width*/, int height) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + eccentricity_slider.geometry (Point (10, height - 50), 300); + } + } + + + +/* + * TEST DEGENERATE + */ + void init_degenerate() + { + set_common_control_geometry = true; + handles.clear(); + + psh.pts.resize(4); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + psh.pts[3] = Point(400, 320); + + + + handles.push_back(&psh); + } + + void draw_degenerate (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream * /*timer_stream*/) + { + init_degenerate_ctrl_geom(cr, notify, width, height); + + Line l1 (psh.pts[0], psh.pts[1]); + Line l2 (psh.pts[2], psh.pts[3]); + cs.set (l1, l2); + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.5); + draw (cr, cs, m_window); + cairo_stroke(cr); + + *notify << cs.categorise() << ": " << cs << std::endl; + } + + void init_degenerate_ctrl_geom (cairo_t* /*cr*/, + std::ostringstream* /*notify*/, + int /*width*/, int /*height*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + } + } + + +/* + * TEST ROOTS + */ + void init_roots() + { + init_common(); + + p1.pos = Point(180, 50); + + x_y_toggle = Toggle("X/Y roots", true); + + handles.push_back(&p1); + handles.push_back(&x_y_toggle); + } + + void draw_roots (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + init_roots_ctrl_geom(cr, notify, width, height); + + + Dim2 DIM = x_y_toggle.on ? X : Y; + Line l(p1.pos, DIM * (-M_PI/2) + M_PI/2); + + cairo_set_line_width(cr, 0.2); + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + draw_line(cr, l, m_window); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + std::vector<double> values; + try + { + cs.roots(values, p1.pos[DIM], DIM); + } + catch(Geom::Exception e) + { + std::cerr << e.what() << std::endl; + } + for (double value : values) + { + Point p(value, value); + p[DIM] = p1.pos[DIM]; + draw_handle(cr, p); + } + cairo_stroke(cr); + + *notify << " "; + for ( unsigned int i = 0; i < values.size(); ++i ) + { + *notify << "v" << i << " = " << values[i] << " "; + } + } + + void init_roots_ctrl_geom (cairo_t* /*cr*/, std::ostringstream* /*notify*/, + int width, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + Point T(width - 120, height - 60); + x_y_toggle.bounds = Rect( T, T + Point(100,25) ); + } + } + +/* + * TEST NEAREST POINT + */ + + void init_nearest_time() + { + init_common(); + p1.pos = Point(180, 50); + handles.push_back(&p1); + } + + void draw_nearest_time (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + + Point P; + try + { + P = cs.nearestTime (p1.pos); + } + catch (LogicalError e) + { + std::cerr << e.what() << std::endl; + } + + + cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0); + draw_line_seg(cr, p1.pos, P); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.1, 0.1, 0.9, 1.0); + draw_handle(cr, P); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0); + draw_label(cr, p1, "Q"); + draw_text(cr, P + Point(5, 5), "P"); + cairo_stroke(cr); + } + +/* + * TEST BOUND + */ + void init_bound() + { + init_common(); + p1.pos = Point(50, 200); + p2.pos = Point(50, 400); + p3.pos = Point(50, 500); + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + } + + void draw_bound (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + + try + { + p1.pos = cs.nearestTime (p1.pos); + p2.pos = cs.nearestTime (p2.pos); + p3.pos = cs.nearestTime (p3.pos); + } + catch (LogicalError e) + { + std::cerr << e.what() << std::endl; + } + + Rect bound = cs.arc_bound (p1.pos, p2.pos, p3.pos); + cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0); + cairo_set_line_width (cr, 0.5); + cairo_rectangle (cr, bound); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0); + draw_label (cr, p1, "initial"); + draw_label (cr, p2, "inner"); + draw_label (cr, p3, "final"); + cairo_stroke(cr); + + } + +/* + * TEST CLIP + */ + void init_clip() + { + init_common(); + } + + void draw_clip (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + //init_clip_ctrl_geom(cr, notify, width, height); + + + Rect R(Point (100, 100),Point (width-100, height-100)); + std::vector<RatQuad> rq; +#ifdef CLIP_WITH_CAIRO_SUPPORT + clipper_cr aclipper(cr, cs, R); + aclipper.clip (rq); +#else + clip (rq, cs, R); +#endif + cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0); + cairo_set_line_width (cr, 0.5); + cairo_rectangle (cr, Rect (Point (100, 100),Point (width-100, height-100))); + for (auto & i : rq) + { + cairo_d2_sb (cr, i.toCubic().toSBasis()); + } + cairo_stroke(cr); + } + +/* + * TEST TANGENT + */ + void init_tangent() + { + init_common(); + + p1.pos = Point(180, 50); + + handles.push_back(&p1); + } + + void draw_tangent (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + + p1.pos = cs.nearestTime (p1.pos); + Line l = cs.tangent(p1.pos); + + draw_label (cr, p1, "P"); + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + draw_line(cr, l, m_window); + cairo_stroke(cr); + } + +/* + * TEST DUAL + */ + void init_dual() + { + init_common(); + } + + void draw_dual (cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream * timer_stream) + { + draw_common(cr, notify, width, height, save, timer_stream); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + xAx dc = cs.dual(); + // we need some trick to make the dual visible in the window + std::string dckind = dc.categorise(); + std::optional<Point> T = dc.centre(); + if (T) dc = dc.translate (-*T); + dc = dc.scale (1e-5, 1e-5); + dc = dc.translate (Point(width/2, height/2)); + draw (cr, dc, m_window); + cairo_stroke(cr); + *notify << "\n dual: " << dckind << ": " << dc; + } + + + void draw_segment (cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw (cairo_t* cr, xAx const& cs, const Rect& _window) + { + + // offset + //Point O(400, 300); + Point O(0, 0); + std::vector<double> r1, r2; + + size_t N = (size_t)(2 *_window.width()); + double dx = 0.5;//width / N; + //std::cout << "dx = " << dx << std::endl; + double x = _window.left() - O[X]; + for (size_t i = 0; i < N; ++i, x += dx) + { + if (r1.empty()) + { + cs.roots(r1, x, X); + if (r1.size() == 1) + { + r1.push_back(r1.front()); + } + if (i != 0 && r1.size() == 2) + { + Point p1(x-dx, r1[0]); + Point p2(x-dx, r1[1]); + p1 += O; p2 += O; + if (_window.contains(p1) && _window.contains(p2)) + draw_segment(cr, p1, p2); + } + continue; + } + cs.roots(r2, x, X); + if (r2.empty()) + { + Point p1(x-dx, r1[0]); + Point p2(x-dx, r1[1]); + p1 += O; p2 += O; + if (_window.contains(p1) && _window.contains(p2)) + draw_segment(cr, p1, p2); + r1.clear(); + continue; + } + if (r2.size() == 1) + { + r2.push_back(r2.front()); + } + + Point p1(x-dx, r1[0]); + Point p2(x, r2[0]); + p1 += O; p2 += O; + if (_window.contains(p1) && _window.contains(p2)) + draw_segment(cr, p1, p2); + + p1 = Point(x-dx, r1[1]) + O; + p2 = Point(x, r2[1]) + O; + if (_window.contains(p1) && _window.contains(p2)) + draw_segment(cr, p1, p2); + + using std::swap; + swap(r1, r2); + } + } + + void draw_label(cairo_t* cr, PointHandle const& ph, const char* label) + { + draw_text(cr, ph.pos+op, label); + } + +// void draw_label(cairo_t* cr, Line const& l, const char* label) +// { +// draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label); +// } + + void init_menu() + { + handles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/ ) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &ConicSectionToy::draw_menu; + break; + case 'B': + init_common(); + draw_f = &ConicSectionToy::draw_vertex_foci; + break; + case 'C': + init_fitting(); + draw_f = &ConicSectionToy::draw_fitting; + break; + case 'D': + init_eccentricity(); + draw_f = &ConicSectionToy::draw_eccentricity; + break; + case 'E': + init_degenerate(); + draw_f = &ConicSectionToy::draw_degenerate; + break; + case 'F': + init_roots(); + draw_f = &ConicSectionToy::draw_roots; + break; + case 'G': + init_nearest_time(); + draw_f = &ConicSectionToy::draw_nearest_time; + break; + case 'H': + init_bound(); + draw_f = &ConicSectionToy::draw_bound; + break; + case 'K': + init_clip(); + draw_f = &ConicSectionToy::draw_clip; + break; + case 'J': + init_tangent(); + draw_f = &ConicSectionToy::draw_tangent; + break; + case 'I': + init_dual(); + draw_f = &ConicSectionToy::draw_dual; + break; + + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream *timer_stream ) override + { + if (timer_stream == 0) timer_stream = notify; + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + m_window = Rect (Point (0, 0), Point (m_width, m_height)); + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + +public: + ConicSectionToy() + { + op = Point(5,5); + } + +private: + typedef void (ConicSectionToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + Point op; + double m_width, m_height, m_length; + Rect m_window; + + xAx cs; + PointHandle vertex, focus1, focus2; + Toggle parabola_toggle; + + PointSetHandle psh; + Slider fitting_slider; + + PointHandle p1, p2, p3; + Toggle x_y_toggle; + + Slider eccentricity_slider; +}; + + +const char* ConicSectionToy::menu_items[] = +{ + "show this menu", + "vertex and foci", + "fitting", + "eccentricity", + "degenerate", + "roots", + "nearest point", + "bound", + "clip", + "tangent", + "dual" +}; + +const char ConicSectionToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'K', 'J', 'I' +}; + + +int main(int argc, char **argv) +{ + //std::cout.precision(20); + init( argc, argv, new ConicSectionToy(), 800, 600 ); + return 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/toys/convole.cpp b/src/toys/convole.cpp new file mode 100644 index 0000000..5f76cbc --- /dev/null +++ b/src/toys/convole.cpp @@ -0,0 +1,352 @@ +#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+SBasis toSBasis(SBasisOf<double> const &f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+SBasisOf<double> toSBasisOfDouble(SBasis const &f){
+ SBasisOf<double> result;
+ for (auto i : f){
+ result.push_back(LinearOf<double>(i[0],i[1]));
+ }
+ return result;
+}
+
+
+
+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;
+}
+template<typename T>
+SBasisOf<T> integraaal(SBasisOf<T> const &c){
+ SBasisOf<T> a;
+ a.resize(c.size() + 1, LinearOf<T>(T(0.),T(0.)));
+
+ for(unsigned k = 1; k < c.size() + 1; k++) {
+ T ahat = (c[k-1][0]-c[k-1][1])/(2*k);
+ a[k] = LinearOf<T>(ahat);
+ }
+
+ T aTri = T(0.);
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (HatOf<T>(c[k]).d + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ //a.normalize();
+ return a;
+}
+
+SBasisOf<SBasisOf<double> > integral(SBasisOf<SBasisOf<double> > const &f, unsigned var){
+ //variable of f = 1, variable of f's coefficients = 0.
+ if (var == 1) return integraaal(f);
+ SBasisOf<SBasisOf<double> > result;
+ for(unsigned i = 0; i< f.size(); i++) {
+ //result.push_back(LinearOf<SBasisOf<double> >( integral(f[i][0]),integral(f[i][1])));
+ result.push_back(LinearOf<SBasisOf<double> >( integraaal(f[i][0]),integraaal(f[i][1])));
+ }
+ return result;
+}
+
+template <typename T>
+SBasisOf<T> multi_compose(SBasisOf<double> const &f, SBasisOf<T> const &g ){
+
+ //assert( f.input_dim() <= g.size() );
+
+ SBasisOf<T> u1 = g;
+ SBasisOf<T> u0 = -g + LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1)));
+ SBasisOf<T> s = multiply(u0,u1);
+ SBasisOf<T> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ return r;
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ SBasisOf<double> const &x,
+ SBasisOf<double> const &y){
+ SBasisOf<double> y0 = -y + LinearOf<double>(1,1);
+ SBasisOf<double> s = multiply(y0,y);
+ SBasisOf<double> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + compose(f[i][0],x)*y0 + compose(f[i][1],x)*y;
+ }
+ return r;
+}
+
+Piecewise<SBasis> convole(SBasisOf<double> const &f, Interval dom_f,
+ SBasisOf<double> const &g, Interval dom_g,
+ bool f_cst_ends = false){
+
+ if ( dom_f.extent() < dom_g.extent() ) return convole(g, dom_g, f, dom_f);
+
+ Piecewise<SBasis> result;
+
+ SBasisOf<SBasisOf<double> > u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,1))));
+ v.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,0)),
+ SBasisOf<double>(LinearOf<double>(1,1))));
+ SBasisOf<SBasisOf<double> > v_u = (v - u)*(dom_f.extent()/dom_g.extent());
+ v_u += SBasisOf<SBasisOf<double> >(SBasisOf<double>(-dom_g.min()/dom_g.extent()));
+ SBasisOf<SBasisOf<double> > gg = multi_compose(g,v_u);
+ SBasisOf<SBasisOf<double> > ff = SBasisOf<SBasisOf<double> >(f);
+ SBasisOf<SBasisOf<double> > hh = integral(ff*gg,0);
+
+ result.cuts.push_back(dom_f.min()+dom_g.min());
+ //Note: we know dom_f.extent() >= dom_g.extent()!!
+ //double rho = dom_f.extent()/dom_g.extent();
+ double t0 = dom_g.min()/dom_f.extent();
+ double t1 = dom_g.max()/dom_f.extent();
+ double t2 = t0+1;
+ double t3 = t1+1;
+ SBasisOf<double> a,b,t;
+ SBasis seg;
+ a = SBasisOf<double>(LinearOf<double>(0,0));
+ b = SBasisOf<double>(LinearOf<double>(0,t1-t0));
+ t = SBasisOf<double>(LinearOf<double>(t0,t1));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.min() + dom_g.max());
+ if (dom_f.extent() > dom_g.extent()){
+ a = SBasisOf<double>(LinearOf<double>(0,t2-t1));
+ b = SBasisOf<double>(LinearOf<double>(t1-t0,1));
+ t = SBasisOf<double>(LinearOf<double>(t1,t2));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.min());
+ }
+ a = SBasisOf<double>(LinearOf<double>(t2-t1,1.));
+ b = SBasisOf<double>(LinearOf<double>(1.,1.));
+ t = SBasisOf<double>(LinearOf<double>(t2,t3));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.max());
+ result*=dom_f.extent();
+
+ //--constant ends correction--------------
+ if (f_cst_ends){
+ SBasis ig = toSBasis(integraaal(g))*dom_g.extent();
+ ig -= ig.at0();
+ Piecewise<SBasis> cor;
+ cor.cuts.push_back(dom_f.min()+dom_g.min());
+ cor.push(reverse(ig)*f.at0(),dom_f.min()+dom_g.max());
+ cor.push(Linear(0),dom_f.max()+dom_g.min());
+ cor.push(ig*f.at1(),dom_f.max()+dom_g.max());
+ result+=cor;
+ }
+ //----------------------------------------
+ return result;
+}
+
+/*static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double space=10){
+ //double dt=(M[0].cuts.back()-M[0].cuts.front())/space;
+ Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 2;
+ for( double t = M.cuts.front(); t < M.cuts.back(); t += space) {
+ Point pos = M(t), perp = Mperp(t);
+ draw_line_seg(cr, pos + perp, pos - perp);
+ }
+ cairo_pw_d2_sb(cr, M);
+ cairo_stroke(cr);
+ }*/
+
+static void plot_graph(cairo_t *cr, Piecewise<SBasis> const &f,
+ double x_scale=300,
+ double y_scale=100){
+ //double dt=(M[0].cuts.back()-M[0].cuts.front())/space;
+ D2<Piecewise<SBasis> > g;
+ g[X] = Piecewise<SBasis>( SBasis(Linear(100+f.cuts.front()*x_scale,
+ 100+f.cuts.back()*x_scale)));
+ g[X].setDomain(f.domain());
+ g[Y] = -f*y_scale+400;
+ cairo_d2_pw_sb(cr, g);
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+void
+plot3d(cairo_t *cr, SBasisOf<SBasisOf<double> > const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ SBasisOf<double> var = LinearOf<double>(0,1);
+ SBasisOf<double> cst = LinearOf<double>(i*1./NbRays,i*1./NbRays);
+ SBasis f_on_seg = toSBasis(compose(f,var,cst));
+ plot3d(cr,Linear(0,1),Linear(i*1./NbRays),f_on_seg,frame);
+ f_on_seg = toSBasis(compose(f,cst,var));
+ plot3d(cr,Linear(i*1./NbRays),Linear(0,1),f_on_seg,frame);
+ }
+}
+
+
+#define SIZE 2
+
+class ConvolutionToy: public Toy {
+
+ PointHandle adjuster, adjuster2, adjuster3;
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+ D2<Piecewise<SBasis> >B;
+ B[X].concat(Piecewise<SBasis>(B1[X]));
+ B[X].concat(Piecewise<SBasis>(B2[X]));
+ B[Y].concat(Piecewise<SBasis>(B1[Y]));
+ B[Y].concat(Piecewise<SBasis>(B2[Y]));
+
+//-----------------------------------------------------
+#if 0
+ Frame frame;
+ frame.O = Point(50,400);
+ frame.x = Point(300,0);
+ frame.y = Point(120,-75);
+ frame.z = Point(0,-300);
+ SBasisOf<SBasisOf<double> > u,v,cst;
+ cst.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1))));
+ u.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,1))));
+ v.push_back(LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(0,0)),
+ SBasisOf<double>(LinearOf<double>(1,1))));
+ plot3d(cr, integral(v,1) ,frame);
+ plot3d(cr, v ,frame);
+ cairo_set_source_rgba (cr, 0., 0.5, 0., 1);
+ cairo_stroke(cr);
+ plot3d(cr, Linear(0,1), Linear(0), Linear(0), frame);
+ plot3d(cr, Linear(0), Linear(0,1), Linear(0), frame);
+ plot3d(cr, Linear(0), Linear(0), Linear(0,1), frame);
+ plot3d(cr, Linear(0,1), Linear(1), Linear(0), frame);
+ plot3d(cr, Linear(1), Linear(0,1), Linear(0), frame);
+ cairo_set_source_rgba (cr, 0., 0.0, 0.9, 1);
+ cairo_stroke(cr);
+#endif
+//-----------------------------------------------------
+
+ SBasisOf<double> smoother;
+ smoother.push_back(LinearOf<double>(0.));
+ smoother.push_back(LinearOf<double>(0.));
+ smoother.push_back(LinearOf<double>(30.));//could be less degree hungry!
+
+ adjuster.pos[X] = 400;
+ if(adjuster.pos[Y]>400) adjuster.pos[Y] = 400;
+ if(adjuster.pos[Y]<100) adjuster.pos[Y] = 100;
+ double scale=(400.-adjuster.pos[Y])/300+.01;
+ D2<Piecewise<SBasis> > smoothB1,smoothB2;
+ smoothB1[X] = convole(toSBasisOfDouble(B1[X]),Interval(0,4),smoother/scale,Interval(-scale/2,scale/2));
+ smoothB1[Y] = convole(toSBasisOfDouble(B1[Y]),Interval(0,4),smoother/scale,Interval(-scale/2,scale/2));
+ smoothB1[X].push(Linear(0.), 8+scale/2);
+ smoothB1[Y].push(Linear(0.), 8+scale/2);
+ smoothB2[X] = Piecewise<SBasis>(Linear(0));
+ smoothB2[X].setDomain(Interval(-scale/2,4-scale/2));
+ smoothB2[Y] = smoothB2[X];
+ smoothB2[X].concat(convole(toSBasisOfDouble(B2[X]),Interval(4,8),smoother/scale,Interval(-scale/2,scale/2)));
+ smoothB2[Y].concat(convole(toSBasisOfDouble(B2[Y]),Interval(4,8),smoother/scale,Interval(-scale/2,scale/2)));
+
+ Piecewise<D2<SBasis> > smoothB;
+ smoothB = sectionize(smoothB1)+sectionize(smoothB2);
+ //cairo_d2_sb(cr, B1);
+ //cairo_d2_sb(cr, B2);
+ cairo_pw_d2_sb(cr, smoothB);
+
+ cairo_move_to(cr,100,400);
+ cairo_line_to(cr,500,400);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ Piecewise<SBasis>bx = Piecewise<SBasis>(B1[X]);
+ bx.setDomain(Interval(0,4));
+ Piecewise<SBasis>smth = Piecewise<SBasis>(toSBasis(smoother));
+ smth.setDomain(Interval(-scale/2,scale/2));
+ cairo_d2_sb(cr, B1);
+ plot_graph(cr, bx, 100, 1);
+ plot_graph(cr, smth/scale, 100, 100);
+ plot_graph(cr, smoothB1[X],100,1);
+
+ //cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(B1) );
+ //cairo_pw_d2_sb(cr, sectionize(smoothB1));
+
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0.7, 0.2, 0., 1);
+ cairo_stroke(cr);
+
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ ConvolutionToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ adjuster.pos = Geom::Point(400,100+300*uniform());
+ handles.push_back(&adjuster);
+
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new ConvolutionToy);
+ return 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/toys/curvature-curve.cpp b/src/toys/curvature-curve.cpp new file mode 100644 index 0000000..8f8f7bc --- /dev/null +++ b/src/toys/curvature-curve.cpp @@ -0,0 +1,142 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + +//----------------------------------------------- + +class CurvatureTester: public Toy { + PointSetHandle curve_handle; + Path current_curve; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 1); + current_curve = Path(); + + for(int base_i = 0; base_i < int(curve_handle.pts.size()/2) - 1; base_i++) { + for(int i = 0; i < 2; i++) { + Geom::Point center=curve_handle.pts[1+2*i+base_i*2]; + Geom::Point normal=center- curve_handle.pts[2*i+base_i*2]; + double radius = Geom::L2(normal); + *notify<<"K="<<radius<<std::endl; + if (fabs(radius)>1e-4){ + + double ang = atan2(-normal); + cairo_arc(cr, center[0], center[1],fabs(radius), ang-0.3, ang+0.3); + cairo_set_source_rgba (cr, 0.75, 0.89, 1., 1); + cairo_stroke(cr); + + + //draw_handle(cr, center); + }else{ + } + { + cairo_save(cr); + double dashes[2] = {10, 10}; + cairo_set_dash(cr, dashes, 2, 0); + cairo_move_to(cr, center); + cairo_line_to(cr, center-normal); + cairo_stroke(cr); + cairo_restore(cr); + } + } + cairo_set_source_rgba (cr, 0., 0, 0., 1); + Geom::Point A = curve_handle.pts[0+base_i*2]; + Geom::Point B = curve_handle.pts[2+base_i*2]; + D2<SBasis> best_c = D2<SBasis>(SBasis(Linear(A[X],B[X])),SBasis(Linear(A[Y],B[Y]))); + double error = -1; + for(int i = 0; i < 16; i++) { + Geom::Point dA = curve_handle.pts[1+base_i*2]-A; + Geom::Point dB = curve_handle.pts[3+base_i*2]-B; + std::vector<D2<SBasis> > candidates = + cubics_fitting_curvature(curve_handle.pts[0+base_i*2],curve_handle.pts[2+base_i*2], + (i&2)?rot90(dA):-rot90(dA), + (i&1)?rot90(dB):-rot90(dB), + ((i&4)?-1:1)*L2sq(dA), ((i&8)?-1:1)*L2sq(dB)); + + if (candidates.empty()) { + } else { + //TODO: I'm sure std algorithm could do that for me... + unsigned best = 0; + for (unsigned i=0; i<candidates.size(); i++){ + Piecewise<SBasis> K = arcLengthSb(candidates[i]); + + double l = Geom::length(candidates[i]); + //double l = K.segs.back().at1();//Geom::length(candidates[i]); + //printf("l = %g\n", l); + if ( l < error || error < 0 ){ + error = l; + best = i; + best_c = candidates[best]; + } + } + } + } + if(error >= 0) { + //cairo_d2_sb(cr, best_c); + current_curve.append(best_c); + } + } + + cairo_path(cr, current_curve); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void canvas_click(Geom::Point at, int button) override { + std::cout << "clicked at:" << at << " with button " << button << std::endl; + if(button == 1) { + double dist; + double t = current_curve.nearestTime(at, &dist).asFlatTime(); + if(dist > 5) { + curve_handle.push_back(at); + curve_handle.push_back(at+Point(100,100)); + } else { + // split around t + Piecewise<D2<SBasis> > pw = current_curve.toPwSb(); + std::vector<Point > vnd = pw.valueAndDerivatives(t, 2); + Point on_curve = current_curve(t); + Point normal = rot90(vnd[1]); + Piecewise<SBasis > K = curvature(pw); + Point ps[2] = {on_curve, on_curve+unit_vector(normal)/K(t)}; + curve_handle.pts.insert(curve_handle.pts.begin()+2*(int(t)+1), ps, ps+2); + } + } + } + +public: + CurvatureTester(){ + if(handles.empty()) { + handles.push_back(&curve_handle); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + } + } +}; + +int main(int argc, char **argv) { + std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl; + init(argc, argv, new CurvatureTester); + return 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/curvature-test.cpp b/src/toys/curvature-test.cpp new file mode 100644 index 0000000..c209fe3 --- /dev/null +++ b/src/toys/curvature-test.cpp @@ -0,0 +1,110 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + +// TODO: +// use path2 +// replace Ray stuff with path2 line segments. + +//----------------------------------------------- + +class CurvatureTester: public Toy { + PointSetHandle curve_handle; + PointHandle sample_point; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + D2<SBasis> B = curve_handle.asBezier(); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + sample_point.pos[1]=400; + sample_point.pos[0]=std::max(150.,sample_point.pos[0]); + sample_point.pos[0]=std::min(450.,sample_point.pos[0]); + cairo_move_to(cr, Geom::Point(150,400)); + cairo_line_to(cr, Geom::Point(450,400)); + cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8); + cairo_stroke(cr); + + double t=std::max(0.,std::min(1.,(sample_point.pos[0]-150)/300.)); + Timer tm; + tm.ask_for_timeslice(); + tm.start(); + + Piecewise<SBasis> K = curvature(B); + Timer::Time als_time = tm.lap(); + *timer_stream << "curvature " << als_time << std::endl; + + for(unsigned ix = 0; ix < K.segs.size(); ix++) { + D2<SBasis> Kxy; + Kxy[1] = Linear(400) - K.segs[ix]*300; + Kxy[0] = Linear(300*K.cuts[ix] + 150, 300*K.cuts[ix+1] + 150); + cairo_d2_sb(cr, Kxy); + cairo_stroke(cr); + } + + double radius = K(t); + *notify<<"K="<<radius<<std::endl; + if (fabs(radius)>1e-4){ + radius=1./radius; + Geom::Point normal=unit_vector(derivative(B)(t)); + normal=rot90(normal); + Geom::Point center=B(t)-radius*normal; + + cairo_arc(cr, center[0], center[1],fabs(radius), 0, M_PI*2); + draw_handle(cr, center); + draw_handle(cr, B(t)); + }else{ + Geom::Point p=B(t); + Geom::Point v=derivative(B)(t); + draw_handle(cr, p); + cairo_move_to(cr, p-100*v); + cairo_line_to(cr, p+100*v); + } + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + CurvatureTester(){ + if(handles.empty()) { + handles.push_back(&curve_handle); + handles.push_back(&sample_point); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + sample_point.pos = Geom::Point(250,300); + } + } +}; + +int main(int argc, char **argv) { + std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl; + init(argc, argv, new CurvatureTester); + return 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/curve-curve-distance.cpp b/src/toys/curve-curve-distance.cpp new file mode 100644 index 0000000..bca0f27 --- /dev/null +++ b/src/toys/curve-curve-distance.cpp @@ -0,0 +1,1000 @@ +/* + * curve-curve distance + * + * 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/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/angle.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/piecewise.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/nearest-time.h> +#include <2geom/numeric/linear_system.h> + +#include <algorithm> + + + +namespace Geom +{ + +namespace detail +{ + +// this wrapper class is an helper to make up a curve portion and access it +// in an homogeneous way +template< typename Curve01T > +class CurvePortion +{ + public: + CurvePortion(const Curve & curve, double from, double to) + : m_curve_ptr(curve.portion(from, to)) + { + } + + Curve01T & get_curve() + { + return *( static_cast<Curve01T*>(m_curve_ptr) ); + } + + ~CurvePortion() + { + if (m_curve_ptr != NULL) + delete m_curve_ptr; + } + + private: + Curve* m_curve_ptr; +}; + +template<> +class CurvePortion< D2<SBasis> > +{ + public: + CurvePortion< D2<SBasis> >(const D2<SBasis> & curve, double from, double to) + : m_curve(portion(curve, from, to)) + { + } + + D2<SBasis> & get_curve() + { + return m_curve; + } + + private: + D2<SBasis> m_curve; +}; + + +template< typename Curve01T, typename CurveT > +class distance_impl +{ + typedef Curve01T curveA_type; + typedef CurveT curveB_type; + // determine how near a distance sample and the value computed through + // the interpolated function have to be + double accuracy; + // determine the recursion limit + double adaptive_limit; + // pieces of the initial subdivision + unsigned int piecees; + // degree of the polynomial used to interpolate a piece + unsigned int piece_degree; + // number of coefficients = piece_degree + 1 + unsigned int piece_size; + unsigned int samples_per_piece; + // total initial samples + unsigned int N; + // a junction is a part of the previous or of the next piece + unsigned int samples_per_junction; + unsigned int samples_per_2junctions; + // number of distance samples used in the interpolation (in the general case) + unsigned int samples_per_interpolation; + + // distance between two consecutive parameters at which samples are evaluated + double step; + double half_step; + // length of the initial domain interval of a piece + double piece_step; + // length of the interval related to a junction + double junction_step; + // index of the first sample related to a piece + unsigned int interval_si; + // index of the last sample related to a piece + unsigned int interval_ei; + // index of the first sample to be evaluated for the current piece + unsigned int evaluation_si; + // index of the last sample to be evaluated for the current piece + unsigned int evaluation_ei; + // index of the first sample to be used for interpolating the current piece + unsigned int interpolation_si; + // index of the last sample to be used for interpolating the current piece + unsigned int interpolation_ei; + // number of total samples to be used for interpolating the current piece + // this is equal to samples_per_interpolation except for the first and last + // piece + unsigned int interpolation_samples; + // parameter value for the first sample related to the current piece + double interval_st; + // interval_st + piece_step + double interval_et; + // curve piece start t + double portion_st; + // curve piece end t + double portion_et; + + unsigned int rec_pieces; + unsigned int rec_N; + unsigned int shared_si; + unsigned int shared_ei; + double rec_step; + double rec_half_step; + double rec_piece_step; + double rec_piece_2steps; + unsigned int rec_total_samples; + + + void init() + { + piece_degree = 3; + piece_size = piece_degree + 1; + samples_per_piece = 4; + N = piecees * samples_per_piece; + samples_per_junction = 2; + samples_per_2junctions = 2*samples_per_junction; + samples_per_interpolation + = samples_per_piece + samples_per_2junctions; + step = 1.0 / N; + half_step = step / 2; + piece_step = samples_per_piece * step; + junction_step = samples_per_junction * step; + interval_si = samples_per_junction; + interval_ei = interval_si + samples_per_piece; + portion_st = (double)(samples_per_junction) / samples_per_interpolation; + portion_et = portion_st + + (double)(samples_per_piece) / samples_per_interpolation; + + // recursive routine parameters + rec_pieces = 2; + rec_N = rec_pieces * samples_per_piece; + rec_total_samples = 2 * samples_per_piece + 1; + shared_si = samples_per_piece - samples_per_junction; + shared_ei = samples_per_piece + samples_per_junction; + rec_step = 1.0 / rec_N; + rec_half_step = rec_step / 2; + rec_piece_step = samples_per_piece * rec_step; + rec_piece_2steps = 2 * rec_piece_step; + } + + bool check_accuracy( SBasis const& piece, + NL::Vector const& sample_distances, + double step ) + { + double t = 0; + for (unsigned int i = 0; i < sample_distances.size(); ++i) + { + if ( !are_near(piece(t), sample_distances[i], accuracy) ) + { + return false; + } + t += step; + } + return true; + } + + + void append( Piecewise<SBasis> & pwc, + Piecewise<SBasis> const& spwc, + double interval_st, + double interval_length ) + { + double cut; + for (unsigned int i = 0; i < spwc.size(); ++i) + { + cut = interval_st + spwc.cuts[i+1] * interval_length; + pwc.push(spwc.segs[i], cut); + } + } + + void init_power_matrix(NL::Matrix & power_matrix) + { + double t = 0; + double u0, u1, s; + unsigned int half_rows = power_matrix.rows() / 2; + unsigned int n = power_matrix.rows() - 1; + for (unsigned int i0 = 0, i1 = n; i0 < half_rows; ++i0, --i1) + { + u0 = 1-t; + u1 = t; + s = u0 * u1; + for (unsigned int j = 0; j < piece_size; j+=2) + { + power_matrix(i0, j) = u0; + power_matrix(i0, j+1) = u1; + power_matrix(i1, j) = u1; + power_matrix(i1, j+1) = u0; + u0 *= s; + u1 *= s; + } + t += rec_step; + } + // t = 1/2 + assert( are_near(t, 0.5) ); + u1 = 1/2.0; + s = 1/4.0; + for (unsigned int j = 0; j < piece_size; j+=2) + { + power_matrix(half_rows, j) = u1; + power_matrix(half_rows, j+1) = u1; + u1 *= s; + } + } + + void interpolate( SBasis & piece, + NL::Matrix & psdinv_matrix, + NL::Vector & sample_distances, + double interpolation_si, double interpolation_samples, + double _portion_st, double _portion_et ) + { + piece.resize(2); + + NL::VectorView v( sample_distances, + interpolation_samples, + interpolation_si ); + NL::Vector coeff = psdinv_matrix * v; + for (unsigned int i = 0, k = 0; i < piece_size; i+=2, ++k) + { + piece[k][0] = coeff[i]; + piece[k][1] = coeff[i+1]; + } + piece = portion(piece, _portion_st, _portion_et); + } + + void evaluate_samples( curveA_type const& A, + curveB_type const& B, + NL::Vector & sample_distances, + double& t ) + { + Point At; + double nptime; + for (unsigned int i = evaluation_si; i < evaluation_ei; ++i) + { + At = A(t); + nptime = nearest_time(At, B); + sample_distances[i] = distance(At, B(nptime)); + t += step; + } + } + + void evaluate_piece_rec( Piecewise<SBasis> & pwc, + curveA_type const& A, + curveB_type const& B, + NL::Matrix & psdinv_matrix, + NL::Matrix & fpi_matrix, + NL::Matrix & lpi_matrix, + NL::Vector & curr_vector, + NL::Vector & sample_distances, + bool adaptive, + double _interpolation_si, + double _interpolation_ei, + double _interval_st, + double _interval_et, + double half_real_step ) + { + SBasis piece; + double _interpolation_samples = _interpolation_ei - _interpolation_si; + interpolate( piece, psdinv_matrix, curr_vector, + _interpolation_si, _interpolation_samples, + _interval_st, _interval_et ); + if (adaptive) + { + bool good + = check_accuracy( piece, sample_distances, rec_step ); + if (!good) + { + Piecewise<SBasis> spwc; + CurvePortion<curveA_type> cp(A, _interval_st, _interval_et); + evaluate_rec( spwc, + cp.get_curve(), + B, + fpi_matrix, + lpi_matrix, + sample_distances, + half_real_step ); + append(pwc, spwc, _interval_st, rec_piece_step); + } + else + { + pwc.push(piece, _interval_et); + } + } + else + { + pwc.push(piece, _interval_et); + } + } + + + // recursive routine: if the interpolated piece is accurate enough + // it's returned in the out-parameter pwc, otherwise the computation of + // two new piecees is performed using half of the current step so the + // number of samples per piece is always the same, while the interpolation + // of one piece is split into the computation of two new piecees when + // needed. + void evaluate_rec( Piecewise<SBasis> & pwc, + curveA_type const& A, + curveB_type const& B, + NL::Matrix & fpi_matrix, + NL::Matrix & lpi_matrix, + NL::Vector & sample_distances, + double real_step ) + { + const double half_real_step = real_step / 2; + const bool adaptive = !(real_step < adaptive_limit); + static const unsigned int middle_sample_index = samples_per_piece + 1; + + pwc.clear(); + pwc.push_cut(0); + // sample_distances used to check accuracy and for the interpolation + // of the two sub-pieces when needed + NL::Vector sample_distances_1(rec_total_samples); + NL::Vector sample_distances_2(rec_total_samples); + + // view of even indexes of sample_distances_1 + NL::VectorView + sd1_view_0(sample_distances_1, middle_sample_index, 0, 2); + // view of even indexes of sample_distances_2 + NL::VectorView + sd2_view_0(sample_distances_2, middle_sample_index, 0, 2); + // view of first half (+ 1) of sample_distances + NL::VectorView + sd_view_1(sample_distances, middle_sample_index, 0); + // view of second half of sample_distances + NL::VectorView + sd_view_2(sample_distances, middle_sample_index, samples_per_piece); + + sd1_view_0 = sd_view_1; + sd2_view_0 = sd_view_2; + + // if we have to check accuracy and go on with recursion + // we need to compute the distance samples of middle points + // of all current samples, because the new step is half of + // the current one + if (adaptive) + { + Point At; + double nptime; + double t = rec_half_step; + for (unsigned int i = 1; i < sample_distances.size(); i+=2) + { + At = A(t); + nptime = nearest_time(At, B); + sample_distances_1[i] = distance(At, B(nptime)); + At = A(t + rec_piece_step); + nptime = nearest_time(At, B); + sample_distances_2[i] = distance(At, B(nptime)); + t += rec_step; + } + } + + // first piece + evaluate_piece_rec( pwc, A, B, + fpi_matrix, + fpi_matrix, + lpi_matrix, + sample_distances, + sample_distances_1, + adaptive, + 0, // interpolation_si + shared_ei, // interpolation_ei + 0, // portion_st + rec_piece_step, // portion_et + half_real_step ); + + // copy back junction parts because + // the interpolate routine modifies them + for ( unsigned int i = 0, j = samples_per_piece - 1; + i < samples_per_junction; + ++i, --j ) + { + sd_view_1[j] = sd1_view_0[j]; + sd_view_2[i] = sd2_view_0[i]; + } + + + // last piece + evaluate_piece_rec( pwc, A, B, + lpi_matrix, + fpi_matrix, + lpi_matrix, + sample_distances, + sample_distances_2, + adaptive, + shared_si, // interpolation_si + rec_total_samples, // interpolation_ei + rec_piece_step, // portion_st + rec_piece_2steps, // portion_et + half_real_step ); + } + + + void evaluate_piece( Piecewise<SBasis> & pwc, + curveA_type const& A, + curveB_type const& B, + NL::Matrix & psdinv_matrix, + NL::Matrix & fpi_matrix, + NL::Matrix & lpi_matrix, + NL::Vector & curr_vector, + NL::Vector & sample_distances, + NL::Vector & end_junction, + NL::VectorView & start_junction_view, + NL::VectorView & end_junction_view, + double & t ) + { + //static size_t index = 0; + //std::cerr << "index = " << index++ << std::endl; + bool good; + SBasis piece; + Piecewise<SBasis> spwc; + interval_et += piece_step; + //std::cerr << "interval: " << interval_st << ", " << interval_et << std::endl; + //std::cerr << "interpolation range: " << interpolation_si << ", " << interpolation_ei << std::endl; + //std::cerr << "interpolation samples = " << interpolation_samples << std::endl; + evaluate_samples( A, B, curr_vector, t ); + //std::cerr << "current vector: " << curr_vector << std::endl; + for ( unsigned int i = 0, k = interval_si; + i < sample_distances.size(); + i+=2, ++k ) + { + sample_distances[i] = curr_vector[k]; + } + Point At; + double nptime; + double tt = interval_st + half_step; + for (unsigned int i = 1; i < sample_distances.size(); i+=2) + { + At = A(tt); + nptime = nearest_time(At, B); + sample_distances[i] = distance(At, B(nptime)); + tt += step; + } + //std::cerr << "sample_distances: " << sample_distances << std::endl; + end_junction = end_junction_view; + interpolate( piece, psdinv_matrix, curr_vector, + interpolation_si, interpolation_samples, + portion_st, portion_et ); + good = check_accuracy( piece, sample_distances, rec_step ); + //std::cerr << "good: " << good << std::endl; + //good = true; + if (!good) + { + CurvePortion<curveA_type> cp(A, interval_st, interval_et); + evaluate_rec( spwc, + cp.get_curve(), + B, + fpi_matrix, + lpi_matrix, + sample_distances, + half_step ); + append(pwc, spwc, interval_st, piece_step); + } + else + { + pwc.push(piece, interval_et); + } + interval_st = interval_et; + for (unsigned int i = 0; i < samples_per_junction; ++i) + { + curr_vector[i] = start_junction_view[i]; + curr_vector[samples_per_junction + i] = end_junction[i]; + } + } + + public: + void evaluate( Piecewise<SBasis> & pwc, + curveA_type const& A, + curveB_type const& B, + unsigned int _piecees ) + { + piecees = _piecees; + init(); + assert( !(piecees & 1) ); + assert( !(piece_size & 1) ); + assert( rec_total_samples & 1); + pwc.clear(); + pwc.push_cut(0); + NL::Matrix power_matrix(rec_total_samples, piece_size); + init_power_matrix(power_matrix); + + NL::MatrixView rec_fmv( power_matrix, + 0, 0, + shared_ei, piece_size ); + NL::Matrix rec_fpim = NL::pseudo_inverse(rec_fmv); + NL::MatrixView rec_lmv( power_matrix, + shared_si, 0, + rec_total_samples - shared_si, piece_size ); + NL::Matrix rec_lpim = NL::pseudo_inverse(rec_lmv); + + + + NL::Vector curr_vector(samples_per_interpolation); + NL::Vector sample_distances(rec_total_samples); + NL::Vector end_junction(samples_per_junction); + NL::VectorView start_junction_view( + sample_distances, + samples_per_junction, + rec_total_samples - 1 - samples_per_2junctions, + 2 ); + NL::VectorView end_junction_view( + curr_vector, + samples_per_junction, + samples_per_junction + samples_per_piece ); + + double t = 0; + + // first piece + evaluation_si = interval_si; + evaluation_ei = samples_per_interpolation; + interpolation_si = evaluation_si; + interpolation_ei = evaluation_ei; + interpolation_samples = interpolation_ei - interpolation_si; + interval_st = 0; + interval_et = 0; + NL::MatrixView fmv( power_matrix, + interpolation_si, 0, + interpolation_samples, piece_size ); + NL::Matrix fpim = NL::pseudo_inverse(fmv); + + evaluate_piece( pwc, A, B, fpim, + rec_fpim, rec_lpim, + curr_vector, sample_distances, end_junction, + start_junction_view, end_junction_view, + t ); + + // general case + evaluation_si = interval_si + samples_per_junction; + evaluation_ei = samples_per_interpolation; + interpolation_si = 0; + interpolation_ei = evaluation_ei; + interpolation_samples = interpolation_ei - interpolation_si; + NL::MatrixView gmv( power_matrix, + interpolation_si, 0, + interpolation_samples, piece_size ); + NL::Matrix gpim = NL::pseudo_inverse(gmv); + + for ( unsigned int piece_index = 1; + piece_index < piecees - 1; + ++piece_index ) + { + evaluate_piece( pwc, A, B, gpim, + rec_fpim, rec_lpim, + curr_vector, sample_distances, end_junction, + start_junction_view, end_junction_view, + t ); + } + + // last piece + evaluation_si = interval_si + samples_per_junction; + evaluation_ei = interval_ei + 1; + interpolation_si = 0; + interpolation_ei = evaluation_ei; + interpolation_samples = interpolation_ei -interpolation_si; + NL::MatrixView lmv( power_matrix, + interpolation_si, 0, + interpolation_samples, piece_size ); + NL::Matrix lpim = NL::pseudo_inverse(lmv); + + evaluate_piece( pwc, A, B, lpim, + rec_fpim, rec_lpim, + curr_vector, sample_distances, end_junction, + start_junction_view, end_junction_view, + t ); + } + + distance_impl() + : accuracy(1e-3), + adaptive_limit(1e-5) + {} + + void set_accuracy(double _accuracy) + { + accuracy = _accuracy; + } + + void set_adaptive_limit(double _adaptive_limit) + { + adaptive_limit = _adaptive_limit; + } + +}; // end class distance_impl + +} // end namespace detail + +template < typename Curve01T, typename CurveT > +inline +Piecewise<SBasis> +distance( Curve01T const& A, + CurveT const& B, + unsigned int pieces = 40, + double adaptive_limit = 1e-5, + double accuracy = 1e-3 ) +{ + + detail::distance_impl<Curve01T, CurveT> dist; + dist.set_accuracy(accuracy); + dist.set_adaptive_limit(adaptive_limit); + Piecewise<SBasis> pwc; + dist.evaluate(pwc, A, B, pieces); + return pwc; +} + +template < typename CurveT > +inline +Piecewise<SBasis> +distance( Piecewise< D2<SBasis> > const& A, + CurveT const& B, + unsigned int pieces = 40, + double adaptive_limit = 1e-5, + double accuracy = 1e-3 ) +{ + Piecewise<SBasis> result; + Piecewise<SBasis> pwc; + for (unsigned int i = 0; i < A.size(); ++i) + { + pwc = distance(A[i], B, pieces, adaptive_limit, accuracy); + pwc.scaleDomain(A.cuts[i+1] - A.cuts[i]); + pwc.offsetDomain(A.cuts[i]); + result.concat(pwc); + } + return result; +} + +template < typename CurveT > +inline +Piecewise<SBasis> +distance( Path const& A, + CurveT const& B, + unsigned int pieces = 40, + double adaptive_limit = 1e-5, + double accuracy = 1e-3 ) +{ + Piecewise<SBasis> result; + Piecewise<SBasis> pwc; + unsigned int sz = A.size(); + if (A.closed()) ++sz; + for (unsigned int i = 0; i < sz; ++i) + { + pwc = distance(A[i], B, pieces, adaptive_limit, accuracy); + pwc.offsetDomain(i); + result.concat(pwc); + } + return result; +} + + +template < typename Curve01T, typename CurveT > +unsigned int dist_test( Piecewise<SBasis> const& pwc, + Curve01T const& A, + CurveT const& B, + double step ) +{ + std::cerr << "======= inside dist test =======" << std::endl; + unsigned int total_checked_values = 0; + unsigned int total_error = 0; + double nptime, sample_distance; + Point At; + for (double t = 0; t <= 1; t += step) + { + At = A(t); + nptime = nearest_time(At, B); + sample_distance = distance(At, B(nptime)); + if ( !are_near(pwc(t), sample_distance, 0.001) ) + { + ++total_error; + std::cerr << "error at t: " << t << std::endl; + } + ++total_checked_values; + } + std::cerr << " total checked values : " << total_checked_values << std::endl; + std::cerr << " total error : " << total_error << std::endl; + return total_error; +} + +} // end namespace Geom + + +using namespace Geom; + +class DCCToy : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + Point ulc(width - 300, height - 60 ); + toggles[0].bounds = Rect(ulc, ulc + Point(160,25) ); + toggles[1].bounds = Rect(ulc + Point(0,30), ulc + Point(160,55) ); + sliders[0].geometry(ulc - Point(450,0), 400); + if (toggle0_status != toggles[0].on) + { + toggle0_status = toggles[0].on; + using std::swap; + swap(sliders[0], sliders[1]); + } + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + + if (choice == 0) + { + A = single_curve_psh.asBezier(); + cairo_d2_sb(cr, A); + } + else if (choice == 1) + { + pA.clear(); + for (unsigned int k = 0; k < path_curves; ++k) + { + PointSetHandle psh; + psh.pts.resize(path_handles_per_curve); + for (unsigned int h = 0; h < path_handles_per_curve; ++h) + { + unsigned int kk = k * (path_handles_per_curve-1) + h; + psh.pts[h] = path_psh.pts[kk]; + } + pA.append(psh.asBezier()); + } + cairo_path(cr, pA); + } + else if (choice == 2) + { + for (unsigned int i = 0; i < pwc_curves; ++i) + { + pwA.segs[i] = pwc_psh[i].asBezier(); + } + cairo_pw_d2_sb(cr, pwA); + } + + D2<SBasis> B = B_psh.asBezier(); + cairo_d2_sb(cr, B); + + double t = sliders[0].value(); + Piecewise<SBasis> d; + unsigned int total_error = 0; + Point cursor; + + if (!toggles[0].on) + { + if (choice == 0) + { + cursor = A(t); + d = distance(A, B, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, A, B, 0.0004); + } + else if (choice == 1) + { + cursor = pA(t); + d = distance(pA, B, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, pA, B, 0.0004); + } + else if (choice == 2) + { + cursor = pwA(t); + d = distance(pwA, B, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, pwA, B, 0.0004); + } + + double nptB = nearest_time(cursor, B); + draw_circ(cr, cursor); + cairo_move_to(cr, cursor); + cairo_line_to(cr, B(nptB)); + cairo_stroke(cr); + } + else + { + Point np(0,0); + cursor = B(t); + if (choice == 0) + { + double nptA = nearest_time(cursor, A); + np = A(nptA); + d = distance(B, A, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, B, A, 0.0004); + } + else if (choice == 1) + { + double nptA = nearest_time(cursor, pA); + np = pA(nptA); + d = distance(B, pA, 40); + // uncomment following lines to view errors in computing the distance + //total_error = dist_test(d, B, pA, 0.0004); + } + draw_circ(cr, cursor); + cairo_move_to(cr, cursor); + cairo_line_to(cr, np); + cairo_stroke(cr); + } + + if (total_error != 0) + *notify << "total error: " << total_error << " "; + + + // draw distance function + Piecewise< D2<SBasis> > pwc; + pwc.cuts = d.cuts; + pwc.segs.resize(d.size()); + D2<SBasis> piece; + double domain_length = 800 / d.domain().extent(); + for ( unsigned int i = 0; i < d.size(); ++i ) + { + piece[X] = SBasis(Linear(20,20) + + domain_length * Linear(d.cuts[i], d.cuts[i+1])); + piece[Y] = 3 * d.segs[i]; + pwc.segs[i] = piece; + } + cairo_set_source_rgb(cr, 0.7,0,0); + cairo_pw_d2_sb(cr, pwc); + *notify << "total cuts: " << pwc.cuts.size(); + if (toggles[1].on) + { + for (unsigned int i = 0; i < pwc.cuts.size(); ++i) + { + draw_handle(cr, pwc(pwc.cuts[i])); + } + } + else + { + draw_handle(cr, pwc(0.0)); + draw_handle(cr, pwc(0.25)); + draw_handle(cr, pwc(0.5)); + draw_handle(cr, pwc(0.75)); + draw_handle(cr, pwc(1)); + } + draw_circ(cr, pwc(t)); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + + public: + DCCToy() + { + toggle0_status = false; + choice = 0; + + single_curve_handles = 6; + path_curves = 3; + path_handles_per_curve = 4; + path_total_handles = path_curves * (path_handles_per_curve - 1) + 1; + pwc_curves = 3; + pwc_handles_per_curve = 4; + pwc_total_handles = pwc_curves * pwc_handles_per_curve; + B_handles = 4; + + if (choice == 0) + { + for (unsigned int i = 0; i < single_curve_handles; ++i) + { + single_curve_psh.push_back(700*uniform(), 500*uniform()); + } + handles.push_back(&single_curve_psh); + sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); + } + else if (choice == 1) + { + for (unsigned int i = 0; i < path_total_handles; ++i) + { + path_psh.push_back(700*uniform(), 500*uniform()); + } + handles.push_back(&path_psh); + sliders.emplace_back(0.0, path_curves, 0.0, 0.0, "t"); + } + else if (choice == 2) + { + pwc_psh.resize(pwc_curves); + pwA.segs.resize(pwc_curves); + pwA.cuts.resize(pwc_curves+1); + pwA.cuts[0] = 0; + double length = 1.0 / pwc_curves; + for (unsigned int i = 0; i < pwc_curves; ++i) + { + for (unsigned int j = 0; j < pwc_handles_per_curve; ++j) + { + pwc_psh[i].push_back(700*uniform(), 500*uniform()); + } + handles.push_back(&(pwc_psh[i])); + pwA.cuts[i+1] = pwA.cuts[i] + length; + } + sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); + } + + for (unsigned int i = 0; i < B_handles; ++i) + { + B_psh.push_back(700*uniform(), 500*uniform()); + } + handles.push_back(&B_psh); + sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); + + toggles.emplace_back("d(A,B) <-> d(B,A)", false); + toggles.emplace_back("Show/Hide cuts", false); + + handles.push_back(&(toggles[0])); + handles.push_back(&(toggles[1])); + handles.push_back(&(sliders[0])); + + } + + private: + bool toggle0_status; + unsigned int choice; + unsigned int single_curve_handles, B_handles; + unsigned int path_curves, path_handles_per_curve, path_total_handles; + unsigned int pwc_curves, pwc_handles_per_curve, pwc_total_handles; + PointSetHandle single_curve_psh; + PointSetHandle path_psh; + std::vector<PointSetHandle> pwc_psh; + PointSetHandle B_psh; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + D2<SBasis> A; + Path pA; + Piecewise< D2<SBasis> > pwA; +}; + + + + +int main(int argc, char **argv) +{ + init( argc, argv, new DCCToy(), 840, 600 ); + return 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/toys/curve-curve-nearest-time.cpp b/src/toys/curve-curve-nearest-time.cpp new file mode 100644 index 0000000..30fb327 --- /dev/null +++ b/src/toys/curve-curve-nearest-time.cpp @@ -0,0 +1,609 @@ +/* + * Nearest Points Toy 3 + * + * Authors: + * Nathan Hurst <njh at njhurst.com> + * 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/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/piecewise.h> +#include <2geom/path-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <algorithm> + + +using namespace Geom; + + +class np_finder +{ +public: + np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2) + : cr(_cr), cc1(_c1), cc2(_c2), c1(_c1), c2(_c2) + { + + dc1 = derivative(_c1); + dc2 = derivative(_c2); + cd1 = dot(_c1,dc1); + cd2 = dot(_c2,dc2); + dsq = 10e30; + + Piecewise< D2<SBasis> > uv1 = unitVector(dc1, EPSILON); + Piecewise< D2<SBasis> > uv2 = unitVector(dc2, EPSILON); + + dcn1 = dot(Piecewise< D2<SBasis> >(dc1), uv1); + dcn2 = dot(Piecewise< D2<SBasis> >(dc2), uv2); + + r_dcn1 = cross(derivative(uv1), uv1); + r_dcn2 = cross(derivative(uv2), uv2); + + k1 = Geom::divide(r_dcn1, dcn1, EPSILON, 3); + k2 = Geom::divide(r_dcn2, dcn2, EPSILON, 3); + + + n1 = divide(rot90(uv1), k1, EPSILON, 3); + n2 = divide(rot90(uv2), k2, EPSILON, 3); + + std::vector<double> cuts1, cuts2; + + // add cuts at points where the curvature is discontinuos + for ( unsigned int i = 1; i < k1.size(); ++i ) + { + if( !are_near(k1[i-1].at1(), k1[i].at0()) ) + { + cuts1.push_back(k1.cuts[i]); + } + } + for ( unsigned int i = 1; i < k2.size(); ++i ) + { + if( !are_near(k2[i-1].at1(), k2[i].at0()) ) + { + cuts2.push_back(k2.cuts[i]); + } + } + + c1 = partition(c1, cuts1); + c2 = partition(c2, cuts2); + +// std::cerr << "# k1 discontinuitis" << std::endl; +// for( unsigned int i = 0; i < cuts1.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << cuts1[i] << std::endl; +// } +// std::cerr << "# k2 discontinuitis" << std::endl; +// for( unsigned int i = 0; i < cuts2.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << cuts2[i] << std::endl; +// } + + // add cuts at points were the curvature is zero + std::vector<double> k1_roots = roots(k1); + std::vector<double> k2_roots = roots(k2); + std::sort(k1_roots.begin(), k1_roots.end()); + std::sort(k2_roots.begin(), k2_roots.end()); + c1 = partition(c1, k1_roots); + c2 = partition(c2, k2_roots); + +// std::cerr << "# k1 zeros" << std::endl; +// for( unsigned int i = 0; i < k1_roots.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << k1_roots[i] << std::endl; +// } +// std::cerr << "# k2 zeros" << std::endl; +// for( unsigned int i = 0; i < k2_roots.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << k2_roots[i] << std::endl; +// } + + + cairo_set_line_width(cr, 0.2); +// cairo_set_source_rgba(cr, 0.0, 0.5, 0.0, 1.0); +// for( unsigned int i = 1; i < c1.size(); ++i ) +// { +// draw_circ(cr, c1[i].at0() ); +// } +// for( unsigned int i = 1; i < c2.size(); ++i ) +// { +// draw_circ(cr, c2[i].at0() ); +// } + + + // add cuts at nearest points to the other curve cuts points + cuts1.clear(); + cuts1.reserve(c1.size()+1); + for ( unsigned int i = 0; i < c1.size(); ++i ) + { + cuts1.push_back( nearest_time(c1[i].at0(), _c2, dc2, cd2) ); + } + cuts1.push_back( nearest_time(c1[c1.size()-1].at1(), _c2, dc2, cd2) ); + +// for ( unsigned int i = 0; i < c1.size(); ++i ) +// { +// cairo_move_to( cr, c1[i].at0() ); +// cairo_line_to(cr, c2(cuts1[i]) ); +// } +// cairo_move_to( cr, c1[c1.size()-1].at1() ); +// cairo_line_to(cr, c2(cuts1[c1.size()])); + + std::sort(cuts1.begin(), cuts1.end()); + + cuts2.clear(); + cuts2.reserve(c2.size()+1); + for ( unsigned int i = 0; i < c2.size(); ++i ) + { + cuts2.push_back( nearest_time(c2[i].at0(), _c1, dc1, cd1) ); + } + cuts2.push_back( nearest_time(c2[c2.size()-1].at1(), _c1, dc1, cd1) ); + +// for ( unsigned int i = 0; i < c2.size(); ++i ) +// { +// cairo_move_to( cr, c2[i].at0() ); +// cairo_line_to(cr, c1(cuts2[i]) ); +// } +// cairo_move_to( cr, c2[c2.size()-1].at1() ); +// cairo_line_to(cr, c1(cuts2[c2.size()])); +// cairo_stroke(cr); + + std::sort(cuts2.begin(), cuts2.end()); + + c1 = partition(c1, cuts2); + c2 = partition(c2, cuts1); + + + // copy curve to preserve cuts status + Piecewise< D2<SBasis> > pwc1 = c1; + n1 = partition(n1, pwc1.cuts); + pwc1 = partition(pwc1, n1.cuts); + r_dcn1 = partition(r_dcn1, n1.cuts); + Piecewise< D2<SBasis> > pwc2 = c2; + n2 = partition(n2, pwc2.cuts); + pwc2 = partition(pwc2, n2.cuts); + + assert( pwc1.size() == n1.size() ); + assert( pwc2.size() == n2.size() ); + assert( r_dcn1.size() == n1.size() ); + + // add cuts at curvature max and min points + Piecewise<SBasis> dk1 = derivative(k1); + Piecewise<SBasis> dk2 = derivative(k2); + std::vector<double> dk1_roots = roots(dk1); + std::vector<double> dk2_roots = roots(dk2); + std::sort(dk1_roots.begin(), dk1_roots.end()); + std::sort(dk2_roots.begin(), dk2_roots.end()); + + c1 = partition(c1, dk1_roots); + c2 = partition(c2, dk2_roots); + +// std::cerr << "# k1 min/max" << std::endl; +// for( unsigned int i = 0; i < dk1_roots.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << dk1_roots[i] << std::endl; +// } +// std::cerr << "# k2 min/max" << std::endl; +// for( unsigned int i = 0; i < dk2_roots.size(); ++i ) +// { +// std::cerr << "[" << i << "]= " << dk2_roots[i] << std::endl; +// } + +// cairo_set_source_rgba(cr, 0.0, 0.0, 0.6, 1.0); +// for( unsigned int i = 0; i < dk1_roots.size(); ++i ) +// { +// draw_handle(cr, c1(dk1_roots[i])); +// } +// for( unsigned int i = 0; i < dk2_roots.size(); ++i ) +// { +// draw_handle(cr, c2(dk2_roots[i])); +// } + + + // add cuts at nearest points to max and min curvature points + // of the other curve + cuts1.clear(); + cuts1.reserve(dk2_roots.size()); + for (double dk2_root : dk2_roots) + { + cuts1.push_back(nearest_time(_c2(dk2_root), _c1, dc1, cd1)); + } + +// for( unsigned int i = 0; i < dk2_roots.size(); ++i ) +// { +// cairo_move_to(cr, c2(dk2_roots[i])); +// cairo_line_to(cr, c1(cuts1[i])); +// } +// cairo_stroke(cr); + + std::sort(cuts1.begin(), cuts1.end()); + c1 = partition(c1, cuts1); + + + // swap normal vector direction and fill the skip list + skip_list.clear(); + skip_list.resize(c1.size(), false); + double npt; + Point p, nv; + unsigned int si; + for ( unsigned int i = 0; i < pwc1.size(); ++i ) + { + p = pwc1[i](0.5); + nv = n1[i](0.5); + npt = nearest_time(p, _c2, dc2, cd2); + if( dot( _c2(npt) - p, nv ) > 0 ) + { + if ( dot( nv, n2(npt) ) > 0 ) + { + n1[i] = -n1[i]; + r_dcn1[i] = -r_dcn1[i]; + } + else + { + si = c1.segN( n1.mapToDomain(0.5, i) ); + skip_list[si] = true; + } + } + } + + + for ( unsigned int i = 0; i < pwc2.size(); ++i ) + { + p = pwc2[i](0.5); + nv = n2[i](0.5); + npt = nearest_time(p, _c1, dc1, cd1); + if( dot( _c1(npt) - p, nv ) > 0 ) + { + if ( dot( nv, n1(npt) ) > 0 ) + { + n2[i] = -n2[i]; + } + } + } + + + evl1 = c1 + n1; + evl2 = c2 + n2; + +// cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); +// for ( unsigned int i = 0; i < c1.size(); ++i ) +// { +// double t = c1.mapToDomain(0.5, i); +// cairo_move_to(cr, c1(t)); +// cairo_line_to(cr, c1(t) + 30*unit_vector(n1(t))); +// } +// +// for ( unsigned int i = 0; i < c2.size(); ++i ) +// { +// double t = c2.mapToDomain(0.5, i); +// cairo_move_to(cr, c2(t)); +// cairo_line_to(cr, c2(t) + 30*unit_vector(n2(t))); +// } +// cairo_stroke(cr); + + std::cerr << "# skip list: "; + for( unsigned int i = 0; i < c1.cuts.size(); ++i ) + { + if ( skip_list[i] ) + std::cerr << i << " "; + } + std::cerr << std::endl; + + cairo_set_line_width(cr, 0.4); + cairo_set_source_rgba(cr, 0.6, 0.0, 0.0, 1.0); + for( unsigned int i = 0; i < c1.size(); ++i ) + { + if ( skip_list[i] ) + { + cairo_move_to(cr, c1[i].at0()); + cairo_line_to(cr, c1[i].at1()); + } + } + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0); + for( unsigned int i = 1; i < c1.size(); ++i ) + { + draw_circ(cr, c1[i].at0() ); + } + cairo_stroke(cr); + + std::cerr << "# c1 cuts: " << std::endl; + for( unsigned int i = 0; i < c1.cuts.size(); ++i ) + { + std::cerr << "c1.cuts[" << i << "]= " << c1.cuts[i] << std::endl; + } + + } + + void operator() () + { + nearest_times_impl(); + d = sqrt(dsq); + } + + Point firstPoint() const + { + return p1; + } + + Point secondPoint() const + { + return p2; + } + + double firstValue() const + { + return t1; + } + + double secondValue() const + { + return t2; + } + + double distance() const + { + return d; + } + +private: + void nearest_times_impl() + { + double t; + for ( unsigned int i = 0; i < c1.size(); ++i ) + { + if ( skip_list[i] ) continue; + std::cerr << i << " "; + t = c1.mapToDomain(0.5, i); + std::pair<double, double> npc = loc_nearest_times(t, c1.cuts[i], c1.cuts[i+1]); + if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) ) + { + t1 = npc.first; + t2 = npc.second; + p1 = c1(t1); + p2 = c2(t2); + dsq = L2sq(p1 - p2); + } + } + } + + std::pair<double, double> + loc_nearest_times( double t, double from = 0, double to = 1 ) + { + std::cerr << "[" << from << "," << to << "] t: " << t << std::endl; + unsigned int iter = 0, iter1 = 0, iter2 = 0; + std::pair<double, double> np(-1,-1); + std::pair<double, double> npf(from, -1); + std::pair<double, double> npt(to, -1); + double ct = t; + double pt = -1; + double s = nearest_time(c1(t), cc2, dc2, cd2); + cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0); + cairo_move_to(cr, c1(t)); + while( !are_near(ct, pt) && iter < 1000 ) + { + pt = ct; + double angle = angle_between( n1(ct), evl2(s) - evl1(ct) ); + assert( !std::isnan(angle) ); + angle = (angle > 0) ? angle - M_PI : angle + M_PI; + if ( std::fabs(angle) < M_PI/12 ) + { + ++iter2; +// cairo_move_to(cr, c1(ct)); +// cairo_line_to(cr, evl1(ct)); +// cairo_line_to(cr, evl2(s)); + //std::cerr << "s: " << s << std::endl; + //std::cerr << "t: " << ct << std::endl; + + ct = ct + angle / r_dcn1(ct); + s = nearest_time(c1(ct), cc2, dc2, cd2); +// angle = angle_between( n2(s), evl1(ct) - evl2(s) ); +// assert( !std::isnan(angle) ); +// angle = (angle > 0) ? angle - M_PI : angle + M_PI; +// s = s + angle / (dcn2(s) * k2(s)); + } + else + { + ++iter1; + ct = nearest_time(c2(s), cc1, dc1, cd1, from, to); + s = nearest_time(c1(ct), cc2, dc2, cd2); + } + iter = iter1 + iter2; + //std::cerr << "s: " << s << std::endl; + //std::cerr << "t: " << ct << std::endl; + //cairo_line_to(cr, c2(s)); + //cairo_line_to(cr, c1(ct)); + //std::cerr << "d(pt, ct) = " << std::fabs(ct - pt) << std::endl; + if ( ct < from ) + { + std::cerr << "break left" << std::endl; + np = npf; + break; + } + if ( ct > to ) + { + std::cerr << "break right" << std::endl; + np =npt; + break; + } + } + //std::cerr << "\n \n"; + std::cerr << "iterations: " << iter1 << " + " << iter2 << " = "<< iter << std::endl; + assert(iter < 3000); + //cairo_move_to(cr, c1(ct)); + //cairo_line_to(cr, c2(s)); + cairo_stroke(cr); + np.first = ct; + np.second = s; + return np; + } + + double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 ) + { + D2<SBasis> sbc = c - p; + SBasis dd = cd - dotp(p, dc); + std::vector<double> zeros = roots(dd); + double closest = from; + double distsq = L2sq(sbc(from)); + for (double zero : zeros) + { + if ( distsq > L2sq(sbc(zero)) ) + { + closest = zero; + distsq = L2sq(sbc(closest)); + } + } + if ( distsq > L2sq(sbc(to)) ) + closest = to; + return closest; + } + + SBasis dotp(Point const& p, D2<SBasis> const& c) + { + SBasis d; + d.resize(c[X].size()); + for ( unsigned int i = 0; i < c[0].size(); ++i ) + { + for( unsigned int j = 0; j < 2; ++j ) + d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j]; + } + return d; + } + + Piecewise< D2<SBasis> > + divide( Piecewise< D2<SBasis> > const& a, Piecewise<SBasis> const& b, double tol, unsigned int k, double zero=1.e-3) + { + D2< Piecewise<SBasis> > aa = make_cuts_independent(a); + D2< Piecewise<SBasis> > q(Geom::divide(aa[0], b, tol, k, zero), Geom::divide(aa[1], b, tol, k, zero)); + return sectionize(q); + } + + struct are_near_ + { + bool operator() (double x, double y, double eps = Geom::EPSILON ) + { + return are_near(x, y, eps); + } + }; + +private: + cairo_t* cr; + D2<SBasis> const& cc1, cc2; + Piecewise< D2<SBasis> > c1, c2; + D2<SBasis> dc1, dc2; + SBasis cd1, cd2; + Piecewise< D2<SBasis> > n1, n2, evl1, evl2; + Piecewise<SBasis> k1, k2, dcn1, dcn2, r_dcn1, r_dcn2; + double t1, t2, d, dsq; + Point p1, p2; + std::vector<bool> skip_list; +}; + + + + +class NearestPoints : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + D2<SBasis> B = pshB.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + np_finder np(cr, A, B); + Path AP, BP; + AP.append(A); BP.append(B); + Crossings ip_list = curve_sweep<SimpleCrosser>(AP, BP); + if( ip_list.empty() ) + { + np(); + cairo_set_line_width (cr, 0.4); + cairo_set_source_rgba(cr, 0.7, 0.0, 0.7, 1.0); + cairo_move_to(cr, np.firstPoint()); + cairo_line_to(cr, np.secondPoint()); + cairo_stroke(cr); + //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl; + } + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + handles.push_back(&pshA); + handles.push_back(&pshB); + for ( unsigned int i = 0; i < A_bez_ord; ++i ) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)); + for ( unsigned int i = 0; i < B_bez_ord; ++i ) + pshB.push_back(Geom::Point(uniform()*400, uniform()*400)); + + } + + private: + PointSetHandle pshA, pshB; + unsigned int A_bez_ord; + unsigned int B_bez_ord; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=8; + unsigned int B_bez_ord=5; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord)); + return 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/toys/curve-intersection-by-bezier-clipping.cpp b/src/toys/curve-intersection-by-bezier-clipping.cpp new file mode 100644 index 0000000..dfd0a56 --- /dev/null +++ b/src/toys/curve-intersection-by-bezier-clipping.cpp @@ -0,0 +1,127 @@ +/* + * Show off crossings between two Bezier curves. + * The intersection points are found by using Bezier clipping. + * + * 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 <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/d2.h> +#include <2geom/basic-intersection.h> +#include <2geom/sbasis-to-bezier.h> + + + + +using namespace Geom; + + +class CurveIntersect : public Toy +{ + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.8, 0., 0, 1); + //pshA.pts.back() = pshB.pts[0]; + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0.0, 0., 0, 1); + D2<SBasis> B = pshB.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + find_intersections_bezier_clipping(xs, pshA.pts, pshB.pts, m_precision); + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.0, 0.0, 0.7, 1); + for (auto & x : xs) + { + draw_handle(cr, A(x.first)); + draw_handle(cr, B(x.second)); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + +public: + CurveIntersect(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + handles.push_back(&pshA); + for (unsigned int i = 0; i <= A_bez_ord; ++i) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200)); + handles.push_back(&pshB); + for (unsigned int i = 0; i <= B_bez_ord; ++i) + pshB.push_back(Geom::Point(uniform()*400, uniform()*400)+Point(200,200)); + + m_precision = 1e-6; + } + +private: + unsigned int A_bez_ord, B_bez_ord; + PointSetHandle pshA, pshB, pshC; + std::vector< std::pair<double, double> > xs; + double m_precision; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord = 6; + unsigned int B_bez_ord = 8; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + + + init( argc, argv, new CurveIntersect(A_bez_ord, B_bez_ord), 800, 800); + return 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/toys/curve-intersection-by-implicitization.cpp b/src/toys/curve-intersection-by-implicitization.cpp new file mode 100644 index 0000000..e071911 --- /dev/null +++ b/src/toys/curve-intersection-by-implicitization.cpp @@ -0,0 +1,300 @@ +/* + * Show off crossings between two D2<SBasis> curves. + * The intersection points are found by using implicitization tecnique. + * + * 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 <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/d2.h> +#include <2geom/sbasis-poly.h> +#include <2geom/numeric/linear_system.h> +#include <2geom/symbolic/implicit.h> + + +using namespace Geom; + +/* + * helper routines + */ +void poly_to_mvpoly1(SL::MVPoly1 & p, Geom::Poly const& q) +{ + for (size_t i = 0; i < q.size(); ++i) + { + p.coefficient(i, q[i]); + } + p.normalize(); +} + +void mvpoly1_to_poly(Geom::Poly & p, SL::MVPoly1 const& q) +{ + p.resize(q.get_poly().size()); + for (size_t i = 0; i < q.get_poly().size(); ++i) + { + p[i] = q[i]; + } +} + + +/* + * intersection_info + * structure utilized to store intersection info + * + * p - the intersection point + * t0 - the parameter t value at which the first curve pass through p + * t1 - the parameter t value at which the first curve pass through p + */ +struct intersection_info +{ + intersection_info() + {} + + intersection_info(Point const& _p, Coord _t0, Coord _t1) + : p(_p), t0(_t0), t1(_t1) + {} + + Point p; + Coord t0, t1; +}; + +typedef std::vector<intersection_info> intersections_info; + + + +/* + * intersection algorithm + */ +void intersect(intersections_info& xs, D2<SBasis> const& A, D2<SBasis> const& B) +{ + using std::swap; + + // supposing implicitization the most expensive step + // we perform a call to intersect with curve arguments swapped + if (A[0].size() > B[0].size()) + { + intersect(xs, B, A); + for (auto & x : xs) + swap(x.t0, x.t1); + + return; + } + + // convert A from symmetric power basis to power basis + Geom::Poly A0 = sbasis_to_poly(A[0]); + Geom::Poly A1 = sbasis_to_poly(A[1]); + + // convert to MultiPoly type + SL::MVPoly1 Af, Ag; + poly_to_mvpoly1(Af, A0); + poly_to_mvpoly1(Ag, A1); + + // compute a basis of the ideal related to the curve A + // in vector form + Geom::SL::basis_type b; + // if we compute the micro-basis the bezout matrix is made up + // by one only entry so we can't do the inversion step. + if (A0.size() == 3) + { + make_initial_basis(b, Af, Ag); + } + else + { + microbasis(b, Af, Ag); + } + + // we put the basis in of the form of two independent moving line + Geom::SL::MVPoly3 p, q; + basis_to_poly(p, b[0]); + basis_to_poly(q, b[1]); + + // compute the Bezout matrix and the implicit equation of the curve A + Geom::SL::Matrix<Geom::SL::MVPoly2> BZ = make_bezout_matrix(p, q); + SL::MVPoly2 ic = determinant_minor(BZ); + ic.normalize(); + + + // convert B from symmetric power basis to power basis + Geom::Poly B0 = sbasis_to_poly(B[0]); + Geom::Poly B1 = sbasis_to_poly(B[1]); + + // convert to MultiPoly type + SL::MVPoly1 Bf, Bg; + poly_to_mvpoly1(Bf, B0); + poly_to_mvpoly1(Bg, B1); + + // evaluate the implicit equation of A on B + // so we get an s(t) polynomial that give us + // the t values for B at which intersection happens + SL::MVPoly1 s = ic(Bf, Bg); + + // convert s(t) to Poly type, in order to use the real_solve function + Geom::Poly z; + mvpoly1_to_poly(z, s); + + // compute t values for the curve B at which intersection happens + std::vector<double> sol = solve_reals(z); + + // filter the found solutions wrt the domain interval [0,1] of B + // and compute the related point coordinates + std::vector<double> pt; + pt.reserve(sol.size()); + std::vector<Point> points; + points.reserve(sol.size()); + for (double & i : sol) + { + if (i >= 0 && i <= 1) + { + pt.push_back(i); + points.push_back(B(pt.back())); + } + } + + // case: A is parametrized by polynomial of degree 1 + // we compute the t values of A at the intersection points + // and filter the results wrt the domain interval [0,1] + double t; + xs.clear(); + xs.reserve(pt.size()); + if (A0.size() == 2) + { + for (size_t i = 0; i < points.size(); ++i) + { + t = (points[i][X] - A0[0]) / A0[1]; + if (t >= 0 && t <= 1) + { + xs.push_back(intersection_info(points[i], t, pt[i])); + } + } + return; + } + + // general case + // we compute the value of the parameter t of A at each intersection point + // and we filter the final result wrt the domain interval [0,1] + // the computation is performed by using the inversion formula for each point + // As reference see: + // Sederberger - Computer Aided Geometric Design + // par 16.5 - Implicitization and Inversion + size_t n = BZ.rows(); + Geom::NL::Matrix BZN(n, n); + Geom::NL::MatrixView BZV(BZN, 0, 0, n-1, n-1); + Geom::NL::VectorView cv = BZN.column_view(n-1); + Geom::NL::VectorView bv(cv, n-1); + Geom::NL::LinearSystem ls(BZV, bv); + for (size_t i = 0; i < points.size(); ++i) + { + // evaluate the first main minor of order n-1 at each intersection point + polynomial_matrix_evaluate(BZN, BZ, points[i]); + // solve the linear system with the powers of t as unknowns + ls.SV_solve(); + // the last element contains the t value + t = -ls.solution()[n-2]; + // filter with respect to the domain of A + if (t >= 0 && t <= 1) + { + xs.push_back(intersection_info(points[i], t, pt[i])); + } + } +} + + + +class IntersectImplicit : public Toy +{ + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.8, 0., 0, 1); + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0.0, 0., 0, 1); + D2<SBasis> B = pshB.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + intersect(xs, A, B); + for (auto & x : xs) + { + draw_handle(cr, x.p); + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + +public: + IntersectImplicit(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + handles.push_back(&pshA); + for (unsigned int i = 0; i <= A_bez_ord; ++i) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)); + handles.push_back(&pshB); + for (unsigned int i = 0; i <= B_bez_ord; ++i) + pshB.push_back(Geom::Point(uniform()*400, uniform()*400)); + + } + +private: + unsigned int A_bez_ord, B_bez_ord; + PointSetHandle pshA, pshB; + intersections_info xs; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord = 4; + unsigned int B_bez_ord = 6; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + + + init( argc, argv, new IntersectImplicit(A_bez_ord, B_bez_ord)); + return 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/toys/cylinder3d.cpp b/src/toys/cylinder3d.cpp new file mode 100644 index 0000000..19f3440 --- /dev/null +++ b/src/toys/cylinder3d.cpp @@ -0,0 +1,253 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> + +#include <gsl/gsl_matrix.h> + +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = p[i]; + cairo_d2_sb(cr, B); + } +} + +Geom::Point orig; + +static void draw_box (cairo_t *cr, Geom::Point corners[8]); +static void draw_slider_lines (cairo_t *cr); +static Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &handles); + +double tmat[3][4]; +double c[8][4]; +Geom::Point corners[8]; + +class Box3d: public Toy { + std::vector<Toggle> togs; + Path path_a; + Piecewise<D2<SBasis> > path_a_pw; + PointSetHandle hand; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + orig = hand.pts[7]; + + Geom::Point dir(1,-2); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + // draw vertical lines for the VP sliders and keep the sliders at their horizontal positions + draw_slider_lines (cr); + hand.pts[4][0] = 30; + hand.pts[5][0] = 45; + hand.pts[6][0] = 60; + + // draw the curve that is supposed to be projected on the box's front face + vector<Geom::Point>::iterator it = hand.pts.begin(); + for (int j = 0; j < 7; ++j) ++it; + + /* create the transformation matrix for the map P^3 --> P^2 that has the following effect: + (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0) + (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1) + (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2) + (0 : 0 : 0 : 1) --> origin (= handle #3) + */ + for (int j = 0; j < 4; ++j) { + tmat[0][j] = hand.pts[j][0]; + tmat[1][j] = hand.pts[j][1]; + tmat[2][j] = 1; + } + + *notify << "Projection matrix:" << endl; + for (auto & i : tmat) { + for (double j : i) { + *notify << j << " "; + } + *notify << endl; + } + + // draw the projective images of the box's corners + for (int i = 0; i < 8; ++i) { + corners[i] = proj_image (cr, c[i], hand.pts); + } + draw_box(cr, corners); + cairo_set_line_width (cr, 2); + cairo_stroke(cr); + + { + D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw); + Piecewise<SBasis> preimage[4]; + + if(togs[0].on) { + preimage[0] = sin((B[0] - orig[0]) / 100); + preimage[1] = -(B[1] - orig[1]) / 100; + preimage[2] = cos((B[0] - orig[0]) / 100); + } else { //if(togs[1].state) { + Piecewise<SBasis> sphi = sin((B[0] - orig[0]) / 200); + Piecewise<SBasis> cphi = cos((B[0] - orig[0]) / 200); + + preimage[0] = -sphi*sin((B[1] - orig[1]) / 200); + preimage[1] = -sphi*cos((B[1] - orig[1]) / 200); + preimage[2] = -cphi; + } + Piecewise<SBasis> res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = + (preimage[0]) * tmat[j][0] + + (preimage[1] - ((hand.pts[5][1]-300)/100)) * tmat[j][1] + + (preimage[2] - ((hand.pts[6][1]-00)/100)) * tmat[j][2] + +( - (hand.pts[4][1]-300)/100) * tmat[j][0] + tmat[j][3]; + } + //if (fabs (res[2]) > 0.000001) { + D2<Piecewise<SBasis> > result(divide(res[0],res[2], 4), + divide(res[1],res[2], 4)); + + cairo_d2_pw_sb(cr, result); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + } + draw_toggles(cr, togs); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + PathVector paths_a = read_svgd(path_a_name); + assert(!paths_a.empty()); + path_a = paths_a[0]; + + path_a.close(true); + path_a_pw = path_a.toPwSb(); + + // Finite images of the three vanishing points and the origin + hand.pts.emplace_back(150,300); + hand.pts.emplace_back(380,40); + hand.pts.emplace_back(550,350); + hand.pts.emplace_back(340,450); + + // Hand.Pts for moving in axes directions + hand.pts.emplace_back(30,300); + hand.pts.emplace_back(45,300); + hand.pts.emplace_back(60,300); + + // Box corners + for (int i = 0; i < 8; ++i) { + c[i][0] = ((i & 1) ? 1 : 0); + c[i][1] = ((i & 2) ? 1 : 0); + c[i][2] = ((i & 4) ? 1 : 0); + c[i][3] = 1; + } + + // Origin handle + hand.pts.emplace_back(180,70); + togs.emplace_back("S", true); + handles.push_back(&hand); + } + void key_hit(GdkEventKey *e) override { + if(e->keyval == 'c') togs[0].set(1); else + if(e->keyval == 's') togs[0].set(0); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(togs, e); + Toy::mouse_pressed(e); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Box3d); + return 0; +} + +void draw_box (cairo_t *cr, Geom::Point corners[8]) { + cairo_move_to(cr,corners[0]); + cairo_line_to(cr,corners[1]); + cairo_line_to(cr,corners[3]); + cairo_line_to(cr,corners[2]); + cairo_close_path(cr); + + cairo_move_to(cr,corners[4]); + cairo_line_to(cr,corners[5]); + cairo_line_to(cr,corners[7]); + cairo_line_to(cr,corners[6]); + cairo_close_path(cr); + + cairo_move_to(cr,corners[0]); + cairo_line_to(cr,corners[4]); + + cairo_move_to(cr,corners[1]); + cairo_line_to(cr,corners[5]); + + cairo_move_to(cr,corners[2]); + cairo_line_to(cr,corners[6]); + + cairo_move_to(cr,corners[3]); + cairo_line_to(cr,corners[7]); +} + +void draw_slider_lines (cairo_t *cr) { + cairo_move_to(cr, Geom::Point(20,300)); + cairo_line_to(cr, Geom::Point(70,300)); + + cairo_move_to(cr, Geom::Point(30,00)); + cairo_line_to(cr, Geom::Point(30,450)); + + cairo_move_to(cr, Geom::Point(45,00)); + cairo_line_to(cr, Geom::Point(45,450)); + + cairo_move_to(cr, Geom::Point(60,00)); + cairo_line_to(cr, Geom::Point(60,450)); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); +} + +static Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &handles) +{ + double res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = + tmat[j][0] * (pt[0] - (handles[4][1]-300)/100) + + tmat[j][1] * (pt[1] - (handles[5][1]-300)/100) + + tmat[j][2] * (pt[2] - (handles[6][1]-300)/100) + + tmat[j][3] * pt[3]; + } + if (fabs (res[2]) > 0.000001) { + Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]); + draw_handle(cr, result); + return result; + } + assert(0); // unclipped point + return Geom::Point(0,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/toys/d2sbasis-fitting-with-np.cpp b/src/toys/d2sbasis-fitting-with-np.cpp new file mode 100644 index 0000000..05bcc6c --- /dev/null +++ b/src/toys/d2sbasis-fitting-with-np.cpp @@ -0,0 +1,147 @@ +/* + * D2<SBasis> Fitting Example + * + * 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/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class D2SBasisFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + bool changed = false; + for (size_t i = 0; i < total_handles; ++i) + { + if (psh.pts[i] != prev_pts[i]) + { + changed = true; + break; + } + } + if (changed) + { + for (size_t k = 0; k < 200; ++k) + { + lsf_2dsb.clear(); + lsf_2dsb.append(0); + lsf_2dsb.append(0.33); + double t = 0; + for (size_t i = 2; i < total_handles-2; ++i) + { + t = nearest_time(psh.pts[i], sb_curve); + lsf_2dsb.append(t); + } + lsf_2dsb.append(0.66); + lsf_2dsb.append(1); + lsf_2dsb.update(); + fmd2sb.instance(sb_curve, lsf_2dsb.result(psh.pts)); + } + prev_pts = psh.pts; + } + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + cairo_d2_sb(cr, sb_curve); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + D2SBasisFitting() + : sb_curve(), + total_handles(8), + order(total_handles / 2 - 1), + fmd2sb(order), + lsf_2dsb(fmd2sb, total_handles), + prev_pts() + { + step = 1.0 / (total_handles - 1); + for (size_t i = 0; i < total_handles; ++i) + { + psh.push_back(400*uniform() + 50, 300*uniform() + 50); + } + handles.push_back(&psh); + + double t = 0; + for (size_t i = 0; i < total_handles; ++i) + { + lsf_2dsb.append(t); + t += step; + } + prev_pts = psh.pts; + lsf_2dsb.update(); + fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts)); + } + + private: + D2<SBasis> sb_curve; + unsigned int total_handles; + unsigned int order; + NL::LFMD2SBasis fmd2sb; + NL::least_squeares_fitter<NL::LFMD2SBasis> lsf_2dsb; + std::vector<Point> prev_pts; + double step; + PointSetHandle psh; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new D2SBasisFitting(), 600, 600 ); + return 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/toys/d2sbasis-fitting.cpp b/src/toys/d2sbasis-fitting.cpp new file mode 100644 index 0000000..7e9b233 --- /dev/null +++ b/src/toys/d2sbasis-fitting.cpp @@ -0,0 +1,120 @@ +/* + * D2<SBasis> Fitting Example + * + * 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/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class D2SBasisFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts, psh.pts)); + prev_pts = psh.pts; + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + cairo_d2_sb(cr, sb_curve); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + D2SBasisFitting() + : sb_curve(), + total_handles(6), + order(total_handles / 2 - 1), + fmd2sb(order), + lsf_2dsb(fmd2sb, total_handles), + prev_pts() + { + step = 1.0 / (total_handles - 1); + for (size_t i = 0; i < total_handles; ++i) + { + psh.push_back(400*uniform() + 50, 300*uniform() + 50); + } + handles.push_back(&psh); + + double t = 0; + for (size_t i = 0; i < total_handles; ++i) + { + lsf_2dsb.append(t); + t += step; + } + prev_pts = psh.pts; + lsf_2dsb.update(); + fmd2sb.instance(sb_curve, lsf_2dsb.result(prev_pts)); + } + + private: + D2<SBasis> sb_curve; + unsigned int total_handles; + unsigned int order; + NL::LFMD2SBasis fmd2sb; + NL::least_squeares_fitter<NL::LFMD2SBasis> lsf_2dsb; + std::vector<Point> prev_pts; + double step; + PointSetHandle psh; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new D2SBasisFitting(), 600, 600 ); + return 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/toys/data/london-locations.csv b/src/toys/data/london-locations.csv new file mode 100644 index 0000000..e7299c9 --- /dev/null +++ b/src/toys/data/london-locations.csv @@ -0,0 +1,298 @@ +Harrow & Wealdstone,75.2,163.5
+Kenton,86.1,153.6
+South Kenton,91,143.5
+North Wembley,94.2,136.8
+Wembley Central,99.1,128
+Stonebridge Park,110.7,122.8
+Harlesden,120.1,116.7
+Willesden Junction,128.3,112.4
+Kensal Green,137.7,111.6
+Queen's Park,148.6,113.5
+Kilburn Park,155.2,115.1
+Maida Vale,158.7,110
+Warwick Avenue,161.1,104.9
+Paddington,164.1,98.5
+Edgware Road,168.2,103.7
+Marylebone,171,105.1
+Baker Street,175.3,105.1
+Regent's Park,181.7,103.9
+Oxford Circus,184.3,97.5
+Piccadilly Circus,188.3,92.5
+Charing Cross,192.9,91.3
+Embankment,195.2,91
+Waterloo,199.6,87.6
+Lambeth North,201.3,84.9
+Elephant & Castle,208.3,81.1
+West Ruislip,20.7,139.9
+Ruislip Gardens,35.6,133.5
+South Ruislip,42.8,130.5
+Northolt,58.8,123.9
+Greenford,72.4,119.4
+Perivale,84.3,115.6
+Hanger Lane,103,109.7
+North Acton,121.2,104.4
+Ealing Broadway,98.3,93.7
+West Acton,108.6,98.8
+East Acton,127.1,99
+White City,138.5,95.1
+Shepherd's Bush,141.7,87.7
+Holland Park,148,90.3
+Notting Hill Gate,152.6,91.8
+Queensway,158.4,92.9
+Lancaster Gate,165,94
+Marble Arch,174.8,96
+Bond Street,179.8,96.8
+Tottenham Court Road,189.9,98.4
+Holborn,195.7,99.3
+Chancery Lane,199.9,100.1
+St Paul's,207.8,97.8
+Bank,213,96.1
+Liverpool Street,216.9,101
+Bethnal Green,234.3,109.1
+Mile End,243.4,107.7
+Stratford,259.5,121.2
+Leyton,259.1,136.9
+Leytonstone,266.9,145.2
+Snaresbrook,272.5,153.7
+South Woodford,276,164.6
+Woodford,279.7,180.2
+Wanstead,277.9,150.9
+Redbridge,286.9,152.9
+Gants Hill,296.3,152.4
+Newbury Park,310.1,152
+Barkingside,309.3,160.9
+Fairlop,310.4,170.7
+Hainault,311.4,175.2
+Grange Hill,310.4,185.8
+Chigwell,299.8,190.2
+Roding Valley,285.7,190.8
+Buckhurst Hill,285.6,195.5
+Loughton,290.1,213.7
+Debden,306.5,214.6
+Theydon Bois,315.1,241.9
+Epping,320.3,264.7
+Tower Hill,220.7,94.3
+Aldgate,220.4,98.8
+Moorgate,212.5,102.2
+Barbican,208.1,102.9
+Farringdon,203.2,105
+King's Cross St Pancras,193.1,112.1
+Euston Square,188.1,107.1
+Great Portland Street,183.3,105.4
+Edgware Road,169.6,102.2
+Bayswater,158,95.3
+High Street Kensington,155.2,84.6
+Gloucester Road,161.2,78.8
+South Kensington,166.5,78.5
+Sloane Square,176.5,77.4
+Victoria,183,79.3
+St James's Park,187.9,83.1
+Westminster,193.7,85.4
+Temple,199.2,94.2
+Blackfriars,205.3,94.7
+Mansion House,210.3,94.3
+Cannon Street,213,93.8
+Monument,215.5,93.1
+Ealing Common,105.3,91.1
+Acton Town,109.2,85.1
+Chiswick Park,114.9,80.3
+Turnham Green,123.5,79.5
+Richmond,99.7,49.4
+Kew Gardens,107.8,61.9
+Gunnersbury,111.3,75.6
+Stamford Brook,128.1,79.3
+Ravenscourt Park,133.3,79.2
+Hammersmith,139.1,77.3
+Barons Court,144.1,75.1
+West Kensington,148.9,75.7
+Earl's Court,154.8,76.8
+Kensington (Olympia),146.3,82
+Wimbledon,152,15.1
+Wimbledon Park,155.2,25.7
+Southfields,150.2,36.6
+East Putney,147.5,46.4
+Putney Bridge,149.1,54.9
+Parsons Green,152.3,62.1
+Fulham Broadway,155.2,67.3
+West Brompton,154.4,72.7
+Aldgate East,222.9,99.8
+Whitechapel,228.6,102.8
+Stepney Green,236.3,105.3
+Bow Road,249.5,107.1
+Bromley-by-Bow,256.1,106.2
+West Ham,264.2,110.7
+Plaistow,271.2,113.4
+Upton Park,280.7,116
+East Ham,291.9,120.2
+Barking,305.8,121.1
+Upney,316.3,118.7
+Becontree,330.3,121.8
+Dagenham Heathway,340.3,122.7
+Dagenham East,350.3,125.4
+Elm Park,369.8,130.4
+Hornchurch,378.4,133.8
+Upminster Bridge,386.5,137.2
+Upminster,397.5,138.6
+Shoreditch,223,103.3
+Shadwell,231.3,96
+Wapping,231.8,89.2
+Rotherhithe,233.1,85.5
+Canada Water,234.3,83.5
+Surrey Quays,237.1,78
+New Cross Gate,241.3,63.5
+New Cross,245,64.3
+Hammersmith,137.7,80.1
+Goldhawk Road,136.8,85.7
+Shepherd's Bush,137,89.2
+Latimer Road,142.5,95.9
+Ladbroke Grove,145.5,99.1
+Westbourne Park,150,101.9
+Royal Oak,158.1,100.2
+Stanmore,93.9,186.2
+Canons Park,97.2,177.9
+Queensbury,101.1,165.5
+Kingsbury,106.7,156.6
+Wembley Park,106.4,139.6
+Neasden,123.8,131.6
+Dollis Hill,129.7,129.9
+Willesden Green,139.1,128.9
+Kilburn,147.4,128.2
+West Hampstead,157.2,127.2
+Finchley Road,162.4,126.9
+Swiss Cottage,164.7,123.3
+St John's Wood,164.8,115.1
+Green Park,184.1,89.6
+Southwark,204.4,88.5
+London Bridge,214.4,89.7
+Bermondsey,225,84.5
+Canary Wharf,254,88.1
+North Greenwich,269.3,88
+Canning Town,267.7,96.2
+Watford,29.9,219.2
+Croxley,15.2,210.9
+Moor Park,22,194.9
+Northwood,27.7,177.9
+Northwood Hills,35.4,169.1
+Pinner,51.6,163.4
+North Harrow,60.4,157.3
+Harrow-on-the-Hill,76,149.6
+Uxbridge,2,121.7
+Hillingdon,14,126.4
+Ickenham,17.6,133.5
+Ruislip,28.9,142.2
+Ruislip Manor,34.2,144.6
+Eastcote,43.2,147.3
+Rayners Lane,56.3,146.9
+West Harrow,66.4,150
+Northwick Park,84.8,149.9
+Preston Road,97.8,146.4
+Edgware,107.3,182.3
+Burnt Oak,113.7,173.6
+Colindale,122.1,167.5
+Hendon Central,135.2,157.6
+Bent Cross,141.9,151.7
+Golders Green,154.3,146.9
+Hampstead,164.8,135.3
+Belsize Park,171.2,129.1
+Chalk Farm,176.2,124.3
+Camden Town,182,121.2
+High Barnet,150.8,219.3
+Totteridge & Whetstone,159.7,200.8
+Woodside Park,156.8,188.9
+West Finchley,154.5,181.9
+Finchley Central,154.5,172.7
+Mill Hill East,142.9,180.2
+East Finchley,168.9,161.8
+Highgate,180.5,152.8
+Archway,186.2,144.1
+Tufnell Park,186,136.6
+Kentish Town,184.4,129.9
+Mornington Crescent,184.6,114.9
+Euston,187.9,110.1
+Angel,203.9,113.6
+Old Street,212.2,108.4
+Borough,210.5,85.8
+Kennington,204.3,74.8
+Warren Street,185.4,104.9
+Goodge Street,187.9,101.9
+Leicester Square,191.4,94.2
+Oval,200,69.1
+Stockwell,195.3,60.4
+Clapham North,191.2,52.6
+Clapham Common,186,49.2
+Clapham South,181.6,41.9
+Balham,178.6,34.2
+Tooting Bec,176.2,28.2
+Tooting Broadway,171.2,17.6
+Colliers Wood,166.8,11.2
+South Wimbledon,159.4,7.9
+Morden,155.1,1.5
+Heathrow Terminals 1-2-3,20.5,53.7
+Heathrow Terminal 4,24.4,46.8
+Hatton Cross,35,51.4
+Hounslow West,57,57
+Hounslow Central,65.2,55.4
+Hounslow East,70.8,57.8
+Osterley,74,65
+Boston Manor,87.1,76.2
+Northfields,91.8,80.2
+South Ealing,95.6,81.7
+South Harrow,67.8,137.1
+Sudbury Hill,75.6,131.2
+Sudbury Town,88.1,127
+Alperton,97.1,119
+Park Royal,107,106
+North Ealing,105.2,98.1
+Knightsbridge,173.7,85.1
+Hyde Park Corner,178.1,86.5
+Covent Garden,193.7,95.6
+Russell Square,192.9,104.7
+Caledonian Road,196,128.8
+Holloway Road,200.2,132.5
+Arsenal,202.7,137.9
+Finsbury Park,203.8,143.9
+Manor House,208.6,149.6
+Turnpike Lane,204.2,165.8
+Wood Green,200.5,172.4
+Bounds Green,190.4,181.2
+Arnos Grove,186.3,189
+Southgate,188.2,201.3
+Oakwood,186.3,216.8
+Cockfosters,177.6,220.3
+Brixton,201.7,52.2
+Vauxhall,194.7,72.4
+Pimlico,189.8,75.5
+Highbury & Islington,204.1,126
+Seven Sisters,219.2,160.5
+Tottenham Hale,228.2,164.8
+Blackhorse Road,238.5,162.7
+Walthamstow Central,253.4,161.1
+Limehouse,239.2,97.1
+Westferry,246.8,95.3
+Poplar,254.6,93.8
+All Saints,256.2,96.4
+Devons Road,253.3,103.8
+Bow Church,250.9,108.9
+Pudding Mill Lane,255.8,115.7
+West India Quay,252.2,91.3
+Heron Quay,252,86.8
+South Quay,254.1,85.5
+Crossharbour and London Arena,256.1,80.3
+Mudchute,257.1,76.5
+Island Gardens,259,74.3
+Cutty Sark,259.2,69.3
+Greenwich,256.1,65.7
+Deptford Bridge,253.9,63.7
+Elverston Road,254.6,58.3
+Lewisham,256.5,54.5
+Blackwall,258.7,93.7
+East India,263.3,94.3
+Royal Victoria,272.5,93
+Custom House,277.9,93.2
+Prince Regent,281.4,93.3
+Royal Albert,288.9,93.2
+Beckton Park,292.7,93.3
+Cyprus,296.6,93.3
+Gallions Reach,301.5,94.2
+Beckton,296,99.3
+Tower Gateway,222.7,94.7
diff --git a/src/toys/data/london.txt b/src/toys/data/london.txt new file mode 100644 index 0000000..49e6f9c --- /dev/null +++ b/src/toys/data/london.txt @@ -0,0 +1,86 @@ +Bakerloo Line: +Harrow & Wealdstone,Kenton,South Kenton,North Wembley,Wembley Central,Stonebridge Park,Harlesden,Willesden Junction,Kensal Green,Queen's Park,Kilburn Park,Maida Vale,Warwick Avenue,Paddington,Edgware Road,Marylebone,Baker Street,Regent's Park,Oxford Circus,Piccadilly Circus,Charing Cross,Embankment,Waterloo,Lambeth North,Elephant & Castle + +Central Line 1: +West Ruislip,Ruislip Gardens,South Ruislip,Northolt,Greenford,Perivale,Hanger Lane,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Snaresbrook,South Woodford,Woodford,Buckhurst Hill,Loughton,Debden,Theydon Bois,Epping + +Central Line 2: +West Ruislip,Ruislip Gardens,South Ruislip,Northolt,Greenford,Perivale,Hanger Lane,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Wanstead,Redbridge,Gants Hill,Newbury Park,Barkingside,Fairlop,Hainault,Grange Hill,Chigwell,Roding Valley,Woodford + +Central Line 3: +Ealing Broadway,West Acton,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Snaresbrook,South Woodford,Woodford,Buckhurst Hill,Loughton,Debden,Theydon Bois,Epping + +Central Line 4: +Ealing Broadway,West Acton,North Acton,East Acton,White City,Shepherd's Bush,Holland Park,Notting Hill Gate,Queensway,Lancaster Gate,Marble Arch,Bond Street,Oxford Circus,Tottenham Court Road,Holborn,Chancery Lane,St Paul's,Bank,Liverpool Street,Bethnal Green,Mile End,Stratford,Leyton,Leytonstone,Wanstead,Redbridge,Gants Hill,Newbury Park,Barkingside,Fairlop,Hainault,Grange Hill,Chigwell,Roding Valley,Woodford + +Circle Line 1: +Westminster,St James's Park,Victoria,Sloane Square,South Kensington,Gloucester Road,High Street Kensington,Notting Hill Gate,Bayswater,Paddington,Edgware Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate + +Circle Line 2: +Tower Hill,Monument,Cannon Street,Mansion House,Blackfriars,Temple,Embankment,Westminster + +Hammersmith & City Line: +Hammersmith,Goldhawk Road,Shepherd's Bush,Latimer Road,Ladbroke Grove,Westbourne Park,Royal Oak,Paddington,Edgware Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking + +District Line 1: +Ealing Broadway,Ealing Common,Acton Town,Chiswick Park,Turnham Green,Stamford Brook,Ravenscourt Park,Hammersmith,Barons Court,West Kensington,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster + +District Line 2: +Richmond,Kew Gardens,Gunnersbury,Turnham Green,Stamford Brook,Ravenscourt Park,Hammersmith,Barons Court,West Kensington,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster + +District Line 3: +Wimbledon,Wimbledon Park,Southfields,East Putney,Putney Bridge,Parsons Green,Fulham Broadway,West Brompton,Earl's Court,Gloucester Road,South Kensington,Sloane Square,Victoria,St James's Park,Westminster,Embankment,Temple,Blackfriars,Mansion House,Cannon Street,Monument,Tower Hill,Aldgate East,Whitechapel,Stepney Green,Mile End,Bow Road,Bromley-by-Bow,West Ham,Plaistow,Upton Park,East Ham,Barking,Upney,Becontree,Dagenham Heathway,Dagenham East,Elm Park,Hornchurch,Upminster Bridge,Upminster + +District Line 4: +Kensington (Olympia),Earl's Court,High Street Kensington,Notting Hill Gate,Bayswater,Paddington,Edgware Road + +East London Line 1: +Shoreditch,Whitechapel,Shadwell,Wapping,Rotherhithe,Canada Water,Surrey Quays,New Cross + +East London Line 2: +Shoreditch,Whitechapel,Shadwell,Wapping,Rotherhithe,Canada Water,Surrey Quays,New Cross Gate + +Jubilee Line: +Stratford,West Ham,Canning Town,North Greenwich,Canary Wharf,Canada Water,Bermondsey,London Bridge,Southwark,Waterloo,Westminster,Green Park,Bond Street,Baker Street,St John's Wood,Swiss Cottage,Finchley Road,West Hampstead,Kilburn,Willesden Green,Dollis Hill,Neasden,Wembley Park,Kingsbury,Queensbury,Canons Park,Stanmore + +Metropolitan Line 1: +Moor Park,Northwood,Northwood Hills,Pinner,North Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate + +Metropolitan Line 2: +Watford,Croxley,Moor Park,Northwood,Northwood Hills,Pinner,North Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate + +Metropolitan Line 3: +Uxbridge,Hillingdon,Ickenham,Ruislip,Ruislip Manor,Eastcote,Rayners Lane,West Harrow,Harrow-on-the-Hill,Northwick Park,Preston Road,Wembley Park,Finchley Road,Baker Street,Great Portland Street,Euston Square,King's Cross St Pancras,Farringdon,Barbican,Moorgate,Liverpool Street,Aldgate + +Northern Line 1: +High Barnet,Totteridge & Whetstone,Woodside Park,West Finchley,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 2: +High Barnet,Totteridge & Whetstone,Woodside Park,West Finchley,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 3: +Mill Hill East,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 4: +Mill Hill East,Finchley Central,East Finchley,Highgate,Archway,Tufnell Park,Kentish Town,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 5: +Edgware,Burnt Oak,Colindale,Hendon Central,Bent Cross,Golders Green,Hampstead,Belsize Park,Chalk Farm,Camden Town,Euston,King's Cross St Pancras,Angel,Old Street,Moorgate,Bank,London Bridge,Borough,Elephant & Castle,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Northern Line 6: +Edgware,Burnt Oak,Colindale,Hendon Central,Bent Cross,Golders Green,Hampstead,Belsize Park,Chalk Farm,Camden Town,Mornington Crescent,Euston,Warren Street,Goodge Street,Tottenham Court Road,Leicester Square,Charing Cross,Embankment,Waterloo,Kennington,Oval,Stockwell,Clapham North,Clapham Common,Clapham South,Balham,Tooting Bec,Tooting Broadway,Colliers Wood,South Wimbledon,Morden + +Piccadilly Line 1: +Uxbridge,Hillingdon,Ickenham,Ruislip,Ruislip Manor,Eastcote,Rayners Lane,South Harrow,Sudbury Hill,Sudbury Town,Alperton,Park Royal,North Ealing,Ealing Common,Acton Town,Turnham Green,Hammersmith,Barons Court,Earl's Court,Gloucester Road,South Kensington,Knightsbridge,Hyde Park Corner,Green Park,Piccadilly Circus,Leicester Square,Covent Garden,Holborn,Russell Square,King's Cross St Pancras,Caledonian Road,Holloway Road,Arsenal,Finsbury Park,Manor House,Turnpike Lane,Wood Green,Bounds Green,Arnos Grove,Southgate,Oakwood,Cockfosters + +Piccadilly Line 2: +Heathrow Terminal 4,Hatton Cross,Hounslow West,Hounslow Central,Hounslow East,Osterley,Boston Manor,Northfields,South Ealing,Acton Town,Turnham Green,Hammersmith,Barons Court,Earl's Court,Gloucester Road,South Kensington,Knightsbridge,Hyde Park Corner,Green Park,Piccadilly Circus,Leicester Square,Covent Garden,Holborn,Russell Square,King's Cross St Pancras,Caledonian Road,Holloway Road,Arsenal,Finsbury Park,Manor House,Turnpike Lane,Wood Green,Bounds Green,Arnos Grove,Southgate,Oakwood,Cockfosters + +Piccadilly Line 3: +Hatton Cross,Heathrow Terminals 1-2-3,Heathrow Terminal 4 + +Victoria Line: +Walthamstow Central,Blackhorse Road,Tottenham Hale,Seven Sisters,Finsbury Park,Highbury & Islington,King's Cross St Pancras,Euston,Warren Street,Oxford Circus,Green Park,Victoria,Pimlico,Vauxhall,Stockwell,Brixton + +Waterloo & City Line: +Waterloo,Bank diff --git a/src/toys/data/nsw-centre.txt b/src/toys/data/nsw-centre.txt new file mode 100644 index 0000000..73b6817 --- /dev/null +++ b/src/toys/data/nsw-centre.txt @@ -0,0 +1,56 @@ +Olympic Park I: +Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Olympic Park + +Olympic Park II: +Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Olympic Park + +Eastern Suburbs & Illawarra 1: +Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +Eastern Suburbs & Illawarra 2: +Cronulla,Woolooware,Caringbah,Miranda,Gymea,Kirrawee,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +Bankstown Line a: +Town Hall,Wynyard,Circular Quay,St James,Museum,Central,Redfern,Erskineville,St Peters,Sydenham,Marrickville,Dulwich Hill,Hurlstone Park,Canterbury,Campsie,Belmore,Lakemba,Wiley Park,Punchbowl,Bankstown,Yagoona,Birrong,Regents Park,Berala,Lidcombe + +Bankstown Line b: +Lidcombe,Berala,Regents Park,Birrong,Sefton,Chester Hill,Leightonfield,Villawood,Carramar,Cabramatta,Warwick Farm,Liverpool + +Inner West 1a: +Liverpool,Warwick Farm,Cabramatta,Carramar,Villawood,Leightonfield,Chester Hill,Sefton,Regents Park,Berala,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Inner West 1b: +Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood + +Inner West 2a: +Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Inner West 2b: +Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood + +Airport & East Hills 1: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Wolli Creek,International,Domestic,Mascot,Green Square,Central,Museum,St James,Circular Quay,Wynyard,Town Hall + +Airport & East Hills 2: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Museum,St James,Circular Quay,Wynyard,Town Hall + +South: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Cumberland: +Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys + +Carlingford Line: +Carlingford,Telopea,Dundas,Rydalmere,Camellia,Rosehill,Clyde + +Western 1: +Richmond,East Richmond,Clarendon,Windsor,Mulgrave,Vineyard,Riverstone,Schofields,Quakers Hill,Marayong,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +Western 2: +Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +North Shore: +Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta + +Northern: +Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney diff --git a/src/toys/data/nsw-locations.csv b/src/toys/data/nsw-locations.csv new file mode 100644 index 0000000..2cb6d13 --- /dev/null +++ b/src/toys/data/nsw-locations.csv @@ -0,0 +1,805 @@ +Aberdare Junction,151.513199,-32.773602
+Aberdeen,150.892593,-32.165401
+Adamstown,151.718307,-32.933899
+Albion Park,150.798203,-34.562698
+Albury,146.922302,-36.0863
+Alectown West,148.171906,-32.934101
+Allandale,151.416306,-32.720699
+Allawah,151.113098,-33.9692
+Alleena,147.134903,-34.072399
+Antiene,150.984695,-32.335999
+Ariah Park,147.2164,-34.344398
+Armidale,151.652206,-30.514
+Arncliffe,151.147095,-33.935299
+Artarmon,151.185593,-33.8069
+Ashfield,151.125504,-33.886799
+Ashley,149.800201,-29.297001
+Asquith,151.1082,-33.688202
+Athol,149.303604,-33.5452
+Auburn,151.032501,-33.8489
+Austinmer,150.928207,-34.305901
+Awaba,151.540604,-33.012501
+Baan Baa,149.951096,-30.599501
+Balldale,146.515106,-35.846199
+Ballina,153.554993,-28.860001
+Balmoral,150.521194,-34.305901
+Bangaroo,148.632599,-33.627602
+Banksia,151.138702,-33.944099
+Bankstown,151.034897,-33.917
+Barairmol,152.995102,-28.719601
+Barbigal,148.839905,-32.223202
+Bardwell Park,151.124405,-33.930901
+Bargo,150.579102,-34.291
+Bathurst,149.582199,-33.425999
+Beanbri,148.405701,-29.9536
+Beecroft,151.066803,-33.749199
+Beelbangera,146.099792,-34.254002
+Belford,151.274902,-32.6544
+Bell,150.278107,-33.506001
+Bellambi,150.909195,-34.362099
+Bellarwi,147.194702,-34.095001
+Bellata,149.786407,-29.918501
+Belmore,151.089203,-33.9161
+Bengerang,149.519608,-29.0714
+Beni,148.750595,-32.195099
+Berala,151.031906,-33.870998
+Beresfield,151.6586,-32.798901
+Berowra,151.153397,-33.623699
+Berry,150.695694,-34.780701
+Bethungra,147.860306,-34.763699
+Beverly Hills,151.082108,-33.9478
+Bexhill,153.346893,-28.761101
+Bexley North,151.111404,-33.937099
+Birrong,151.024399,-33.892601
+Black Springs,150.629395,-30.442801
+Blackheath,150.281998,-33.633301
+Blacktown,150.908707,-33.7668
+Blandford,150.898605,-31.788799
+Blaxland,150.608902,-33.742001
+Blayney,149.253799,-33.5261
+Bloomfield,149.105606,-33.319401
+Blue Cow,148.389801,-36.383701
+Boambee,153.1073,-30.348801
+Bogan Gate,147.801498,-33.108299
+Boggabri,150.038803,-30.702999
+Bomaderry,150.609695,-34.853298
+Bombo,150.852707,-34.658001
+Bomera,149.798798,-31.548
+Bon Accord,147.190903,-35.226501
+Bonalbo,152.623154,-28.73671
+Bondi Junction,151.246506,-33.893101
+Bonville,153.061996,-30.379
+Boomley,149.1035,-32.026798
+Booragul,151.604797,-32.970798
+Boothenba,148.689606,-32.198101
+Borenore,148.975693,-33.253101
+Bourke,145.933701,-30.094601
+Bowenfels,150.135406,-33.474998
+Bowral,150.416107,-34.4781
+Box Tank,142.2276,-32.201099
+Braemar,150.498596,-34.412498
+Branxton,151.345703,-32.6614
+Braunstone,152.961807,-29.8027
+Brightling,148.404999,-31.2185
+Broadmeadow,151.734406,-32.921902
+Broken Hill,141.466003,-31.9604
+Brolgan,148.063507,-33.142502
+Buddigower,147.008698,-34.0438
+Bullaburra,150.412796,-33.722301
+Bulli,150.9142,-34.333401
+Bulliac,152.035599,-31.914499
+Bullocks Flat,148.443207,-36.444698
+Bundanoon,150.300705,-34.655399
+Bundook,152.1259,-31.891199
+Bungabbee,153.146393,-28.780199
+Bungendore,149.444901,-35.254601
+Bunyan,149.158997,-36.1712
+Buralyang,146.760498,-33.974098
+Burgooney,146.574097,-33.385399
+Burradoo,150.397293,-34.493999
+Burrawang,150.529999,-34.581402
+Burren,148.966705,-30.1061
+Burringbar,153.471497,-28.4331
+Burwood,151.104706,-33.876301
+Buxton,150.532501,-34.2547
+Bygalorie,146.781693,-33.469002
+Byron,151.093994,-29.736401
+Cabramatta,150.938995,-33.8927
+Calleen,147.105804,-33.787701
+Calwalla,150.474304,-34.5625
+Camden,150.696899,-34.050701
+Camellia,151.025604,-33.817001
+Camira Creek,152.964203,-29.250401
+Campbelltown,150.812607,-34.063801
+Campsie,151.101303,-33.909901
+Canberra,149.150497,-35.3167
+Canberra (Civic),149.132904,-35.262299
+Canley Vale,150.944199,-33.886501
+Canterbury,151.117493,-33.91
+Capertee,149.986496,-33.149899
+Caragabal,147.740707,-33.839802
+Cardiff,151.662292,-32.941299
+Caringbah,151.121094,-34.0415
+Carlingford,151.046204,-33.780102
+Carlton,151.123795,-33.967701
+Carramar,150.960907,-33.884201
+Casino,153.037903,-28.860201
+Casula,150.910797,-33.9487
+Central,151.205704,-33.882999
+Cessnock,151.361694,-32.8419
+Chakola,149.184296,-36.099602
+Charity Creek,152.231003,-31.9016
+Chatswood,151.181,-33.796799
+Cheltenham,151.078796,-33.754601
+Chester Hill,150.996597,-33.883202
+Circular Quay,151.210907,-33.861198
+Civic,151.772903,-32.9249
+Clarendon,150.790497,-33.610001
+Clifton,150.967804,-34.2584
+Clyde,151.017197,-33.8349
+Coalcliff,150.976501,-34.241001
+Cobar,145.845398,-31.4939
+Cockle Creek,151.622406,-32.9426
+Coffs Harbour,153.138596,-30.3027
+Coledale,150.942596,-34.288799
+Collombatti,152.825699,-30.9755
+Colo Vale,150.4869,-34.3993
+Combara,148.370193,-31.1229
+Como,151.067902,-34.004799
+Compton Downs,146.571304,-30.4224
+Concord West,151.0858,-33.847599
+Condobolin,147.147995,-33.083302
+Coniston,150.884598,-34.437
+Conoble,144.707703,-32.9739
+Cooerwull,150.142105,-33.4818
+Cookamidgera,148.302902,-33.202801
+Coolalie,148.983704,-34.7854
+Coolamon,147.202194,-34.8167
+Cooma,149.133804,-36.2355
+Coombell,152.971893,-29.0159
+Coonong,146.122101,-35.1297
+Coopernook,152.600098,-31.8057
+Cootamundra,148.031006,-34.6399
+Coramba,153.019608,-30.2251
+Coreinbob,147.630905,-35.210999
+Corowa,146.384399,-35.996399
+Corrimal,150.904099,-34.374901
+Corringle,147.347397,-33.632801
+Couridjah,150.547897,-34.232601
+Cowan,151.170593,-33.591999
+Cowra,148.699005,-33.834702
+Cowra West,148.68248,-33.833015
+Crabbes Creek,153.501007,-28.457001
+Craboon,149.460297,-32.0294
+Craven,151.941498,-32.153999
+Cringila,150.877106,-34.465302
+Cronulla,151.150497,-34.056198
+Crooble,150.253998,-29.266199
+Croppa Creek,150.304703,-29.1269
+Crowther,148.502502,-34.0979
+Croydon,151.115799,-33.8829
+Cubbaroo,149.125198,-30.171499
+Culcairn,147.035797,-35.669899
+Culgoora,149.5849,-30.2798
+Cullen Bullen,150.006699,-33.3009
+Cullerin,149.407104,-34.783901
+Cullivel,146.379593,-35.259701
+Cullya,149.109695,-33.199699
+Curlewis,150.270401,-31.116699
+Currabubula,150.7341,-31.2626
+Dapto,150.790695,-34.492599
+Darnick,143.637894,-32.849602
+Daroobalgie,148.052002,-33.329899
+Deepwater,151.851593,-29.4387
+Denistone,151.087799,-33.798401
+Deringulla,149.326797,-31.3883
+Dingadee,151.7901,-32.368
+Domestic,151.179596,-33.930401
+Doonside,150.866196,-33.7631
+Dora Creek,151.500107,-33.080502
+Dorrigo,152.707199,-30.3319
+Douglas Park,150.709305,-34.182598
+Dripstone,148.990494,-32.644001
+Dubbo,148.605392,-32.245602
+Dulwich Hill,151.139801,-33.909302
+Dumaresq,151.580795,-30.459801
+Dunbible,153.401703,-28.380199
+Dundas,151.033096,-33.804001
+Dungog,151.759003,-32.398602
+Dunmore,150.839493,-34.605301
+East Hills,150.9841,-33.960602
+East Maitland,151.587494,-32.744499
+East Richmond,150.758194,-33.6017
+Eastwood,151.082794,-33.789501
+Edgecliff,151.239105,-33.879902
+Edgeroi,149.797394,-30.1171
+Elderslie,150.704895,-34.053299
+Eltham,153.3974,-28.753799
+Emerald Hill,150.104706,-30.8857
+Emu Plains,150.671799,-33.744202
+Engadine,151.013702,-34.067299
+Epping,151.082397,-33.770901
+Erigolia,146.356293,-33.8568
+Erskineville,151.184799,-33.9006
+Ettamogah,146.978806,-36.022499
+Euabalong West,146.391296,-33.054401
+Eulomogo,148.692795,-32.2887
+Eungai,152.900101,-30.848801
+Eurabba,147.8255,-34.055199
+Euratha,146.548401,-33.878799
+Eurie Eurie,148.252899,-29.964899
+Euston,142.764008,-34.569801
+Excelsior,149.974899,-33.0681
+Exeter,150.317307,-34.613499
+Fairfield,150.957703,-33.871101
+Fairview,148.159698,-32.378201
+Fairy Hill,153.008804,-28.770901
+Fairy Meadow,150.895203,-34.394699
+Farley,151.519501,-32.726398
+Fassifern,151.580902,-32.984798
+Faulconbridge,150.536194,-33.696499
+Finley,145.572998,-35.649799
+Fish River,149.3125,-34.7589
+Flemington,151.069901,-33.864101
+Forbes,148.010193,-33.374802
+Frampton,147.922501,-34.695301
+Gadara,148.158798,-35.320999
+Garah,149.634598,-29.0818
+Garema,147.937805,-33.551498
+Garoolgan,146.441101,-34.25
+Gerringong,150.8172,-34.744801
+Geurie,148.828995,-32.397301
+Gidginbung,147.4711,-34.3246
+Gidley,150.846893,-31.0065
+Girral,147.071106,-33.707699
+Glen Innes,151.726898,-29.7372
+Glenbrook,150.617905,-33.767601
+Glencoe,151.723694,-29.9231
+Glenfield,150.892105,-33.970699
+Glenlogan,148.653,-33.7691
+Glenroy,147.944107,-35.7495
+Gloucester,151.965897,-32.0042
+Gobondery,147.594193,-32.691002
+Gooloogong,148.449097,-33.567402
+Goondah,148.730499,-34.733601
+Goonumbla,148.1353,-32.9809
+Gooramma,148.622101,-34.493
+Gordon,151.155701,-33.754501
+Gosford,151.341003,-33.422699
+Gosford Racecourse,151.327194,-33.422401
+Goulburn,149.718002,-34.757801
+Grafton,152.925797,-29.683701
+Grafton City,152.941803,-29.7027
+Grahams Hill,150.730301,-34.040798
+Granville,151.012604,-33.831799
+Grasstree,150.965607,-32.285702
+Grawlin Plains,148.035995,-33.467701
+Green Square,151.1996,-33.9053
+Greenethorpe,148.401398,-33.997898
+Greenwood,151.008896,-29.731701
+Gresham,149.405197,-33.569199
+Greta,151.384201,-32.685799
+Griffith,146.045197,-34.285801
+Gubbata,146.5513,-33.637299
+Guildford,150.983795,-33.853699
+Gum Lake,143.177399,-32.679699
+Gunebang,146.682602,-33.014
+Gungal,150.500397,-32.266998
+Gunnedah,150.253098,-30.9821
+Gunning,149.262802,-34.776798
+Gunningbland,147.915207,-33.132801
+Gurley,149.799393,-29.7342
+Gurranang,152.996506,-29.459299
+Gwabegar,148.969101,-30.611
+Gymea,151.084305,-34.035
+Hadleigh,150.453598,-29.5877
+Hamilton,151.747696,-32.916901
+Hannan,146.417999,-33.6222
+Harden,148.370193,-34.5541
+Harris Park,151.007095,-33.822498
+Hartley Vale,150.261993,-33.5392
+Hawkesbury River,151.226196,-33.545399
+Hay,144.842102,-34.498299
+Hazelbrook,150.452606,-33.7229
+Heathcote,151.007706,-34.087502
+Helensburgh,150.993607,-34.1759
+Henty,147.032501,-35.516602
+Hermidale,146.723694,-31.548401
+Herons Creek,152.727295,-31.5891
+Hexham,151.684006,-32.8302
+High Street,151.561905,-32.740601
+Hilldale,151.648499,-32.504002
+Hillston,145.535507,-33.479099
+Hill Top,150.4936,-34.3540
+Holsworthy,150.955704,-33.961601
+Homebush,151.086594,-33.8661
+Hopefield,146.435806,-35.8918
+Hornsby,151.098495,-33.701302
+Horse Lake,142.065903,-32.115002
+Hurlstone Park,151.131897,-33.909698
+Hurstville,151.100601,-33.966099
+Illabo,147.741699,-34.814098
+Illalong Creek,148.656204,-34.715599
+Ingleburn,150.865494,-33.995701
+International,151.166107,-33.935001
+Ivanhoe,144.304092,-32.913502
+Jannali,151.064194,-34.015598
+Jaspers Brush,150.667007,-34.805401
+Jerilderie,145.723206,-35.361198
+Jindalee,148.088806,-34.577301
+Junee,147.582596,-34.870098
+Kadungle,147.621902,-32.758701
+Kahibah,151.718201,-32.9618
+Kamarah,146.7827,-34.324299
+Kamber,148.608795,-31.6383
+Kandos,149.968704,-32.859299
+Kankool,150.772705,-31.6975
+Kapooka,147.298492,-35.159302
+Karaak Flat,152.285004,-31.9237
+Katoomba,150.310593,-33.710701
+Kellys Plains,151.646393,-30.5667
+Kelso,149.604401,-33.422199
+Kembla Grange Racecourse,150.817795,-34.468899
+Kempsey,152.832993,-31.0765
+Kendall,152.706696,-31.6341
+Kenebri,149.0224,-30.7768
+Kentucky,151.449799,-30.7621
+Kiama,150.854095,-34.671902
+Kikoira,146.659698,-33.653198
+Kilgra,152.975906,-28.5497
+Killara,151.162598,-33.763901
+Killawarra,152.311707,-31.889999
+Kinalung,141.959396,-32.058701
+Kings Cross,151.219193,-33.8717
+Kingsgrove,151.098801,-33.940498
+Kingswood,150.719894,-33.756001
+Kirkham,150.715302,-34.046001
+Kirrawee,151.070801,-34.034599
+Kogarah,151.132095,-33.961498
+Kolodong,152.429092,-31.888399
+Komungla,149.645599,-34.864799
+Koolbury,150.902893,-32.216599
+Koolewong,151.315994,-33.464401
+Koolkhan,152.935303,-29.619699
+Koonadan,146.365097,-34.479801
+Koorakee,142.9245,-34.454498
+Kootingal,151.058502,-31.055799
+Korangi,153.045105,-30.256901
+Kotara,151.703094,-32.938599
+Kundabung,152.835495,-31.2082
+Kungala,153.008194,-29.948099
+Kurrajong,150.665604,-33.555
+Kyogle,153.002502,-28.6206
+Lake Cowal,147.357407,-33.688499
+Lakemba,151.076202,-33.919102
+Langtree,145.564499,-33.661598
+Langunyah,145.572205,-35.729198
+Lanitza,153.000305,-29.881399
+Lapstone,150.642807,-33.773499
+Larras Lee,148.859406,-33.000801
+Laureldale,153.414597,-28.7514
+Lawrance Road,153.009399,-29.3965
+Lawson,150.428101,-33.718102
+Leeton,146.395096,-34.553001
+Leeville,152.994202,-28.943399
+Leightonfield,150.9841,-33.881599
+Leniston,145.681503,-35.6506
+Lette,143.170593,-34.354198
+Leumeah,150.828903,-34.051399
+Leura,150.328201,-33.711201
+Lewisham,151.148193,-33.893101
+Leycester,153.196899,-28.7803
+Liamena,149.336304,-31.9685
+Lidcombe,151.044907,-33.862099
+Lilyvale,151.006195,-34.191002
+Limbri,151.156296,-31.033701
+Linden,150.504501,-33.713001
+Lindfield,151.169907,-33.773201
+Lisarow,151.369598,-33.3806
+Lithgow,150.156204,-33.479301
+Liverpool,150.9263,-33.923599
+Lochinvar,151.449707,-32.720299
+Loftus,151.0504,-34.044701
+Lucan,148.988998,-33.719002
+Lysaghts,150.875305,-34.453999
+Macarthur,150.795898,-34.071201
+Macdonaldtown,151.186203,-33.896301
+Macksville,152.912796,-30.7082
+Macquarie Fields,150.878098,-33.983799
+Maitland,151.550995,-32.737202
+Maldon,150.633301,-34.191299
+Marayong,150.8992,-33.7458
+Marrangaroo,150.112701,-33.449699
+Marrickville,151.155502,-33.9132
+Martin Place,151.211807,-33.8666
+Martins Creek,151.617599,-32.557899
+Marulan,150.006104,-34.7113
+Mary Vale,148.906799,-32.473999
+Mascot,151.1866,-33.921501
+Matakana,145.903595,-32.990002
+Meadowbank,151.090103,-33.815399
+Medlow Bath,150.281494,-33.674301
+Melinga,152.519104,-31.808901
+Menangle,150.743301,-34.124401
+Menangle Park,150.743805,-34.1021
+Mendooran,149.128693,-31.833401
+Menindee,142.425003,-32.3895
+Merah North,149.293793,-30.183901
+Merrylands,150.992203,-33.835602
+Merrywinebone,148.815399,-29.686001
+Metford,151.614807,-32.761501
+Michelago,149.165298,-35.710499
+Mickibri,148.194702,-32.8606
+Middlefield,147.575302,-32.4356
+Milguy,150.202805,-29.350901
+Milsons Point,151.211395,-33.8456
+Milsons Point (1st),151.209396,-33.850498
+Milsons Point (2nd),151.210297,-33.8475
+Mindaribba,151.5844,-32.6667
+Minemoorong,147.450195,-32.282299
+Minnamurra,150.8517,-34.625401
+Minto,150.8414,-34.026901
+Miowera,147.333298,-31.636499
+Miranda,151.102005,-34.036499
+Mittagong,150.447006,-34.452099
+Moleton,152.903503,-30.1607
+Molong,148.869904,-33.094299
+Molonglo,149.173996,-35.330601
+Mooball,153.492996,-28.440399
+Moombooldool,146.672897,-34.299999
+Mooren,149.336502,-31.6875
+Moppin,149.747299,-29.204901
+Moree,149.846405,-29.4736
+Morisset,151.486298,-33.110298
+Morpeth,151.626907,-32.724602
+Morpeth (1st),151.626877,-32.72459
+Morrisons Hill,148.109497,-34.533901
+Mortdale,151.0811,-33.9702
+Mortuary,151.202393,-33.885601
+Moss Vale,150.372803,-34.5452
+Moulamein,144.038895,-35.0882
+Mt Colah,151.115204,-33.6712
+Mt Druitt,150.818893,-33.768799
+Mount Gipps,141.598007,-31.898899
+Mt Kuring-gai,151.137497,-33.652302
+Mount Lion,152.9552,-28.4338
+Mount Rae,149.718201,-34.446301
+Mt Victoria,150.257599,-33.588299
+Mudgee,149.586594,-32.6012
+Mugincoble,148.225403,-33.190201
+Mulgrave,150.830597,-33.625198
+Mullion Creek,149.1185,-33.137699
+Mulwala,146.001495,-35.9809
+Mumbil,149.046295,-32.723
+Mungindi,148.973297,-28.968901
+Muronbung,148.960403,-32.174301
+Murrawal,149.336502,-31.4646
+Murrays Flats,149.787903,-34.723701
+Murrulla,150.919495,-31.8349
+Murrurundi,150.839294,-31.766399
+Museum,151.211304,-33.8755
+Muswellbrook,150.891006,-32.265202
+Myambat,150.617203,-32.3745
+Myocum,153.510803,-28.578199
+Myrtle Creek,152.945908,-29.110001
+Nambucca,152.977707,-30.628799
+Nammoona,153.022293,-28.8269
+Nana Glen,153.018097,-30.135599
+Nanardine,148.113297,-33.060799
+Napier,146.766998,-35.250401
+Narara,151.344193,-33.394001
+Narellan,150.735992,-34.039398
+Nargong,148.9366,-33.727699
+Narrabri,149.792206,-30.326
+Narrabri West,149.751205,-30.339399
+Narrandera,146.555496,-34.739799
+Narriah,146.725998,-33.948101
+Narwee,151.069595,-33.946602
+Narwonah,148.191498,-32.306599
+Neeworra,149.082504,-29.0266
+Neible,149.637207,-31.642401
+Nemingah,150.988098,-31.119101
+Nevertire,147.714005,-31.838301
+Newbridge,149.365799,-33.581799
+Newcastle,151.783905,-32.9249
+Newnes,150.237701,-33.176998
+Newtown,151.179703,-33.898201
+Niagara Park,151.352997,-33.382099
+No 1 Mortuary Station,151.047195,-33.868401
+No 2 Mortuary Station,151.048996,-33.875599
+No 3 Mortuary Station,151.054199,-33.877899
+No 4 Mortuary Station,151.058701,-33.872601
+Normanhurst,151.097504,-33.719898
+North Menangle,150.742401,-34.115601
+North Richmond,150.715805,-33.579899
+North Star,150.388702,-28.9307
+North Strathfield,151.087997,-33.858002
+North Sydney,151.205399,-33.841099
+North Wollongong,150.890594,-34.410999
+North Yathong,145.901001,-35.244301
+Norwood,149.731995,-34.680302
+Nubba,148.220398,-34.523399
+Nyngan,147.195007,-31.562901
+Nyrang Creek,148.556,-33.5467
+Oak Flats,150.815598,-34.57
+Oakey Creek,149.699493,-31.625799
+Oaklands,146.166,-35.5495
+Oatley,151.078705,-33.9799
+Oberon,149.852493,-33.701199
+Old Burren,148.905899,-29.9447
+Old Casino,153.046707,-28.854799
+Olympic Park,151.069,-33.847099
+Oolong,149.172501,-34.777
+Orange,149.1035,-33.286499
+Otford,151.005295,-34.210701
+Otford,151.005295,-34.210701
+Ourimbah,151.370102,-33.353901
+Padstow,151.031998,-33.950699
+Panania,150.996201,-33.953701
+Pangela,150.793503,-31.755199
+Parkes,148.174896,-33.141899
+Parkville,150.868393,-31.988701
+Parramatta,151.006195,-33.8167
+Paterson,151.612701,-32.602798
+Pendle Hill,150.953506,-33.800999
+Pennant Hills,151.073303,-33.735298
+Penrith,150.6922,-33.748199
+Penrose,150.210693,-34.673801
+Penshurst,151.089005,-33.965199
+Perisher,148.407501,-36.4025
+Petersham,151.155106,-33.893101
+Piambra,149.3741,-31.6329
+Picton,150.610992,-34.1782
+Pipers Flat,149.996704,-33.371201
+Pippita,151.063202,-33.8564
+Point Clare,151.328903,-33.445099
+Polona,149.205002,-33.489899
+Port Kembla,150.899597,-34.476898
+Port Kembla North,150.886505,-34.4715
+Portland,149.993393,-33.360298
+Premer,149.899597,-31.459
+Pucawan,147.349396,-34.396599
+Puggoon,149.470901,-32.250301
+Punchbowl,151.057098,-33.9245
+Pymble,151.144104,-33.7425
+Quakers Hill,150.886795,-33.726799
+Quandary,147.312607,-34.3857
+Quandialla,147.796906,-34.0117
+Quandong,148.176498,-33.9221
+Queanbeyan,149.225403,-35.3419
+Queens Wharf,151.620895,-32.7244
+Quipolly,150.655594,-31.4242
+Quirindi,150.680695,-31.504299
+Raglan,149.6510,-33.4344
+Raleigh,153.017807,-30.4541
+Ralvona,147.191101,-35.691101
+Rappville,152.953293,-29.086
+Ravensworth,151.057999,-32.439701
+Raworth,151.613998,-32.725101
+Red Bend,148.014404,-33.384102
+Redfern,151.1978,-33.8913
+Regents Park,151.025299,-33.881001
+Remep,149.845596,-31.495399
+Revesby,151.013794,-33.951401
+Rhodes,151.085403,-33.830101
+Richmond,150.753998,-33.5998
+Riverstone,150.857498,-33.678299
+Riverwood,151.050507,-33.951199
+Robertson,150.592194,-34.590599
+Rockdale,151.135406,-33.9519
+Rocky Ponds,148.503799,-34.583401
+Rookwood,151.055099,-33.863453
+Rooty Hill,150.843903,-33.770401
+Rosehill,151.022903,-33.822201
+Roseville,151.177795,-33.7817
+Rosewood,147.866196,-35.676399
+Roto,145.462402,-33.050804
+Rowena,148.908203,-29.814899
+Roxburgh,150.759003,-32.312698
+Royalla,149.149399,-35.510101
+Rutherford Racecourse,151.503006,-32.712101
+Ryan,146.910507,-35.5172
+Rydal,150.031006,-33.4846
+Rydalmere,151.0289,-33.8102
+Rylstone,149.973404,-32.7962
+Sandgate,151.702103,-32.870499
+Sandy Hollow,150.570007,-32.3382
+Sawtell,153.098007,-30.358101
+Sayers Lake,143.275299,-32.7225
+Scarborough,150.964905,-34.2635
+Schofields,150.867996,-33.6973
+Scone,150.866898,-32.045898
+Sefton,151.010895,-33.8848
+Seven Hills,150.936996,-33.7738
+Singleton,151.165695,-32.570499
+Somerset,152.164902,-31.884199
+South Gundagai,148.0979,-35.077202
+South Wyalong,147.2341,-33.934502
+Spring Ridge,150.253494,-31.396601
+Springdale,147.724701,-34.469299
+Springwood,150.563904,-33.7001
+Sproules Lagoon,147.500397,-34.387199
+St Helena,153.578903,-28.6658
+St James,151.211304,-33.870899
+St Leonards,151.195297,-33.823601
+St Marys,150.774902,-33.7612
+St Peters,151.178299,-33.906799
+Stanmore,151.163498,-33.893501
+Stanwell Park,150.978806,-34.2271
+Stokers,153.404404,-28.394501
+Stratford,151.9375,-32.118801
+Strathaird,149.746399,-34.423901
+Strathfield,151.093994,-33.871201
+Stuart Town,149.077393,-32.8008
+Summer Hill,151.139404,-33.889599
+Summervale,147.058899,-31.428499
+Sutherland,151.056396,-34.031502
+Sydenham,151.168503,-33.9133
+Tabbita,145.844803,-34.1022
+Table Top,147.001694,-35.9706
+Tahmoor,150.588501,-34.223999
+Taleeban,146.462097,-33.8699
+Tallawang,149.453293,-32.204201
+Tallimba,146.881897,-33.992298
+Tallong,150.085693,-34.7197
+Tamboolba,147.559692,-35.231499
+Tamworth,150.930603,-31.0839
+Tarago,149.649002,-35.069801
+Tarana,149.906494,-33.525101
+Tarcutta,147.710999,-35.2953
+Taree,152.458603,-31.905001
+Tarro,151.669998,-32.808201
+Tascott,151.316895,-33.448898
+Telarah,151.5392,-32.723202
+Telegraph Point,152.800507,-31.3214
+Telopea,151.042496,-33.793598
+Temora,147.5271,-34.444401
+Tempe,151.156906,-33.922901
+Tenterfield,152.005493,-29.053301
+Teralba,151.602997,-32.962299
+Tharbogang,145.992599,-34.254002
+The Gorge,141.675095,-31.9126
+The Risk,152.939407,-28.48
+The Rock,147.113907,-35.271999
+The Royal National Park,151.055603,-34.062199
+The Troffs,147.653702,-32.827599
+Thirlmere,150.570801,-34.204201
+Thirroul,150.917496,-34.318298
+Thornleigh,151.078003,-33.730598
+Thornton,151.640793,-32.783901
+Thulloo,146.768005,-33.659
+Tichborne,148.120605,-33.222099
+Tomingley West,148.152405,-32.5728
+Toolijooa,150.780807,-34.764702
+Toongabbie,150.949402,-33.786999
+Tootool,146.996994,-35.262001
+Torbane,149.989197,-33.112301
+Toronto,151.598007,-33.0154
+Tottenham,147.354797,-32.240501
+Towealgra,149.327698,-31.9305
+Town Hall,151.207108,-33.872398
+Towradgi,150.900406,-34.383801
+Trajere,148.387802,-33.492901
+Trangie,147.985001,-32.0299
+Trewilga,148.213303,-32.7883
+Trida,145.004501,-33.0182
+Trundle,147.708893,-32.916599
+Tubbul Road,147.936005,-34.255299
+Tuggerah,151.420898,-33.307201
+Tullamore,147.561996,-32.631401
+Tumblong,148.008194,-35.138802
+Tumulla,149.480606,-33.516899
+Turramurra,151.128403,-33.730099
+Turrawan,149.883804,-30.457399
+Turrella,151.139694,-33.929401
+Tweed Heads,153.5401,-28.1735
+Ulamambri,149.383194,-31.3298
+Ulinda,149.481705,-31.582199
+Umbango Creek,147.766205,-35.382702
+Unanderra,150.845795,-34.453701
+Upper Manilla,150.664001,-30.636101
+Uralla,151.505295,-30.6437
+Urana,146.273407,-35.328999
+Uranagong,146.235397,-35.4007
+Uranquinty,147.243698,-35.191399
+Urunga,153.017593,-30.4979
+Valley Heights,150.581497,-33.703602
+Victoria Street,151.592102,-32.7491
+Villawood,150.973907,-33.880402
+Vineyard,150.849792,-33.6497
+Wagga Wagga,147.366501,-35.119801
+Wahroonga,151.117401,-33.715698
+Waitara,151.104401,-33.707901
+Walcha Road,151.400497,-30.941799
+Wallangarra,151.931595,-28.9226
+Wallarobba,151.6946,-32.4951
+Wallerawang,150.0681,-33.4090
+Wallsend,151.667206,-32.902802
+Wambool,149.7789,-33.5158
+Wamboyne,147.279602,-33.5695
+Warabrook,151.709793,-32.886299
+Waratah,151.730896,-32.901001
+Wards River,151.938599,-32.217098
+Wargambeal,146.492294,-33.3606
+Wargin,147.253601,-34.115299
+Warnecliffe,149.084595,-32.987202
+Warnervale,151.447205,-33.247601
+Warragai Creek,152.988495,-29.547701
+Warral,150.860992,-31.1595
+Warrawee,151.121994,-33.722698
+Warrell Creek,152.892502,-30.7694
+Warren,147.826096,-31.697599
+Warrimoo,150.601196,-33.720001
+Warwick Farm,150.933807,-33.9147
+Waterfall,150.993393,-34.133999
+Wauchope,152.736694,-31.455099
+Waverton,151.197998,-33.8377
+Wee Elwah,145.197906,-33.045399
+Weedallion,147.936707,-34.193802
+Weemelah,149.255905,-29.0165
+Weetaliba,149.582504,-31.638901
+Weethalle,146.619904,-33.876499
+Weja,146.8228,-33.533401
+Wellington,148.945007,-32.5522
+Wentworth Falls,150.376801,-33.709099
+Wentworthville,150.971497,-33.806099
+Werai,150.345505,-34.590599
+Werrington,150.759705,-33.7589
+Werris Creek,150.646194,-31.3487
+West Ryde,151.091095,-33.805
+West Tamworth,150.912994,-31.0905
+West Wyalong,147.200195,-33.928398
+Westmead,150.986801,-33.8069
+Wickham,151.758194,-32.923
+Wiley Park,151.065994,-33.923
+Willie Ploma,148.075897,-35.093899
+Willow Tree,150.726593,-31.6476
+Wimborne,150.643494,-30.670799
+Windsor,150.809402,-33.612801
+Wingello,150.157593,-34.693298
+Wingen,150.880997,-31.8934
+Wingham,152.367004,-31.8659
+Winnunga,146.863907,-33.5965
+Wiragulla,151.742203,-32.4613
+Wirrinya,147.802994,-33.670898
+Wittenbra,149.085403,-31.044201
+Wolli Creek,151.154999,-33.925999
+Wollongong,150.887405,-34.425701
+Wollstonecraft,151.191498,-33.831299
+Wombarra,150.9524,-34.2752
+Wondabyne,151.255005,-33.491299
+Wongabinda,150.072098,-29.385401
+Wongarbon,148.759293,-32.336399
+Wongoni,149.278503,-31.871099
+Woodford,150.480698,-33.735802
+Woodlawn,153.319199,-28.7757
+Woodstock,148.8452,-33.743301
+Woolbrook,151.351898,-30.965099
+Woollahra,151.242401,-33.8848
+Woolooware,151.143494,-34.0476
+Woonona,150.913803,-34.3493
+Woy Woy,151.321899,-33.482601
+Wubbera,150.145004,-29.546301
+Wumbulgal,146.209198,-34.358601
+Wyanga,148.131104,-32.458302
+Wyangaree,152.963806,-28.5117
+Wybalena,148.163498,-35.4944
+Wyee,151.485001,-33.180401
+Wynyard,151.205795,-33.865101
+Wyong,151.426895,-33.284
+Wyrra,147.268005,-33.823002
+Wyuna Downs,146.488495,-30.5005
+Yagoona,151.023804,-33.904202
+Yallah,150.784195,-34.5364
+Yarra,149.630096,-34.786301
+Yass Junction,148.912903,-34.8088
+Yass Town,148.9086,-34.844002
+Yearinan,149.179199,-31.1819
+Yennora,150.969803,-33.8638
+Yerrinbool,150.542603,-34.3713
+Yethera,147.574799,-32.525398
+Yiddah,147.317795,-34.0364
+Yoogali,146.081497,-34.300499
+Youngareen,146.852203,-33.650398
+Yullundry,148.713898,-32.843201
+Zig Zag,150.2001,-33.4717
diff --git a/src/toys/data/nsw-london.zip b/src/toys/data/nsw-london.zip Binary files differnew file mode 100644 index 0000000..d0c0050 --- /dev/null +++ b/src/toys/data/nsw-london.zip diff --git a/src/toys/data/nsw.txt b/src/toys/data/nsw.txt new file mode 100644 index 0000000..6f33691 --- /dev/null +++ b/src/toys/data/nsw.txt @@ -0,0 +1,86 @@ +Olympic Park I: +Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Olympic Park + +Olympic Park II: +Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Olympic Park + +Eastern Suburbs & Illawarra 1: +Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +Eastern Suburbs & Illawarra 2: +Cronulla,Woolooware,Caringbah,Miranda,Gymea,Kirrawee,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +South Coast 1: +Bomaderry,Berry,Gerringong,Kiama,Bombo,Minnamurra,Dunmore,Oak Flats,Albion Park,Dapto,Kembla Grange Racecourse,Unanderra,Coniston,Wollongong,North Wollongong,Fairy Meadow,Towradgi,Corrimal,Bellambi,Woonona,Bulli,Thirroul,Austinmer,Coledale,Wombarra,Scarborough,Coalcliff,Stanwell Park,Otford,Helensburgh,Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +South Coast 2: +Port Kembla,Port Kembla North,Cringila,Lysaghts,Coniston,Wollongong,North Wollongong,Fairy Meadow,Towradgi,Corrimal,Bellambi,Woonona,Bulli,Thirroul,Austinmer,Coledale,Wombarra,Scarborough,Coalcliff,Stanwell Park,Otford,Helensburgh,Waterfall,Heathcote,Engadine,Loftus,Sutherland,Jannali,Como,Oatley,Mortdale,Penshurst,Hurstville,Allawah,Carlton,Kogarah,Rockdale,Banksia,Arncliffe,Wolli Creek,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Town Hall,Martin Place,Kings Cross,Edgecliff,Bondi Junction + +Bankstown Line a: +Town Hall,Wynyard,Circular Quay,St James,Museum,Central,Redfern,Erskineville,St Peters,Sydenham,Marrickville,Dulwich Hill,Hurlstone Park,Canterbury,Campsie,Belmore,Lakemba,Wiley Park,Punchbowl,Bankstown,Yagoona,Birrong,Regents Park,Berala,Lidcombe + +Bankstown Line b: +Lidcombe,Berala,Regents Park,Birrong,Sefton,Chester Hill,Leightonfield,Villawood,Carramar,Cabramatta,Warwick Farm,Liverpool + +Inner West 1a: +Liverpool,Warwick Farm,Cabramatta,Carramar,Villawood,Leightonfield,Chester Hill,Sefton,Regents Park,Berala,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Inner West 1b: +Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood + +Inner West 2a: +Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Inner West 2b: +Central,Town Hall,Wynyard,Milsons Point,North Sydney,Waverton,Wollstonecraft,St Leonards,Artarmon,Chatswood + +Airport & East Hills 1: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Wolli Creek,International,Domestic,Mascot,Green Square,Central,Museum,St James,Circular Quay,Wynyard,Town Hall + +Airport & East Hills 2: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Holsworthy,East Hills,Panania,Revesby,Padstow,Riverwood,Narwee,Beverly Hills,Kingsgrove,Bexley North,Bardwell Park,Turrella,Tempe,Sydenham,St Peters,Erskineville,Redfern,Central,Museum,St James,Circular Quay,Wynyard,Town Hall + +South: +Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Circular Quay,St James,Museum + +Southern Highlands: +Goulburn,Marulan,Tallong,Wingello,Penrose,Bundanoon,Exeter,Moss Vale,Burradoo,Bowral,Mittagong,Yerrinbool,Bargo,Tahmoor,Picton,Douglas Park,Menangle,Menangle Park,Macarthur,Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown + +Southern Highlands ROAD COACH: +Bowral,Mittagong,Colo Vale,Hill Top,Balmoral,Buxton,Couridjah,Thirlmere,Picton + +Cumberland: +Campbelltown,Leumeah,Minto,Ingleburn,Macquarie Fields,Glenfield,Casula,Liverpool,Warwick Farm,Cabramatta,Canley Vale,Fairfield,Yennora,Guildford,Merrylands,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys + +Carlingford Line: +Carlingford,Telopea,Dundas,Rydalmere,Camellia,Rosehill,Clyde + +Western 1: +Richmond,East Richmond,Clarendon,Windsor,Mulgrave,Vineyard,Riverstone,Schofields,Quakers Hill,Marayong,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +Western 2: +Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +Blue Mountains: +Lithgow,Zig Zag,Bell,Mt Victoria,Blackheath,Medlow Bath,Katoomba,Leura,Wentworth Falls,Bullaburra,Lawson,Hazelbrook,Woodford,Linden,Faulconbridge,Springwood,Valley Heights,Warrimoo,Blaxland,Glenbrook,Lapstone,Emu Plains,Penrith,Kingswood,Werrington,St Marys,Mt Druitt,Rooty Hill,Doonside,Blacktown,Seven Hills,Toongabbie,Pendle Hill,Wentworthville,Westmead,Parramatta,Harris Park,Granville,Clyde,Auburn,Lidcombe,Flemington,Homebush,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +North Shore: +Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central,Redfern,Macdonaldtown,Newtown,Stanmore,Petersham,Lewisham,Summer Hill,Ashfield,Croydon,Burwood,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta + +Northern: +Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central,Town Hall,Wynyard,Milsons Point,North Sydney + +Newcastle & Central Coast 1: +Newcastle,Civic,Wickham,Hamilton,Broadmeadow,Adamstown,Kotara,Cardiff,Cockle Creek,Teralba,Booragul,Fassifern,Awaba,Dora Creek,Morisset,Wyee,Warnervale,Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Waitara,Wahroonga,Warrawee,Turramurra,Pymble,Gordon,Killara,Lindfield,Roseville,Chatswood,Artarmon,St Leonards,Wollstonecraft,Waverton,North Sydney,Milsons Point,Wynyard,Town Hall,Central + +Newcastle & Central Coast 2: +Newcastle,Civic,Wickham,Hamilton,Broadmeadow,Adamstown,Kotara,Cardiff,Cockle Creek,Teralba,Booragul,Fassifern,Awaba,Dora Creek,Morisset,Wyee,Warnervale,Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Burwood,Croydon,Ashfield,Summer Hill,Lewisham,Petersham,Stanmore,Newtown,Macdonaldtown,Redfern,Central + +Wyong Line: +Wyong,Tuggerah,Ourimbah,Lisarow,Niagara Park,Narara,Gosford,Point Clare,Tascott,Koolewong,Woy Woy,Wondabyne,Hawkesbury River,Cowan,Berowra,Mt Kuring-gai,Mt Colah,Asquith,Hornsby,Normanhurst,Thornleigh,Pennant Hills,Beecroft,Cheltenham,Epping,Eastwood,Denistone,West Ryde,Meadowbank,Rhodes,Concord West,North Strathfield,Strathfield,Homebush,Flemington,Lidcombe,Auburn,Clyde,Granville,Harris Park,Parramatta,Westmead,Wentworthville,Pendle Hill,Toongabbie,Seven Hills,Blacktown,Doonside,Rooty Hill,Mt Druitt,St Marys + +Hunter 1: +Scone,Aberdeen,Muswellbrook,Singleton,Belford,Branxton,Greta,Allandale,Lochinvar,Maitland,High Street,East Maitland,Victoria Street,Metford,Thornton,Beresfield,Tarro,Hexham,Sandgate,Warabrook,Waratah,Hamilton,Wickham,Civic,Newcastle + +Hunter 2: +Dungog,Wiragulla,Wallarobba,Hilldale,Martins Creek,Paterson,Mindaribba,Telarah,Maitland,High Street,East Maitland,Victoria Street,Metford,Thornton,Beresfield,Tarro,Hexham,Sandgate,Warabrook,Waratah,Hamilton,Wickham,Civic,Newcastle diff --git a/src/toys/data/parser.cpp b/src/toys/data/parser.cpp new file mode 100644 index 0000000..cc5c6f0 --- /dev/null +++ b/src/toys/data/parser.cpp @@ -0,0 +1,47 @@ +#include <string> +#include <fstream> +#include <iostream> +#include <vector> +#include <map> +using namespace std; +typedef pair<double,double> Point; +int main(void) +{ + ifstream location_file("london-locations.csv"), path_file("london.txt"); + string id,sx,sy; + map<string,Point> idlookup; + while (getline(location_file,id,',')) + { + getline(location_file,sx,','); + getline(location_file,sy,'\n'); + char *e; + double x = strtod(sx.c_str(),&e), y = strtod(sy.c_str(),&e); + cout << id << " (" << x << "," << y << ")"<< endl; + idlookup[id]=make_pair(x,y); + } + string l; + vector<vector<Point> > paths; + while (getline(path_file,l,'\n')) { + vector<Point> ps; + if(l.size() < 2) continue; // skip blank lines + if(l.find(":",0)!=string::npos) continue; // skip labels + string::size_type p=0,q; + while((q=l.find(",",p))!=string::npos || p < l.size() && (q = l.size()-1)) { + id = l.substr(p,q-p); + cout << id << ","; + ps.push_back(idlookup[id]); + p=q+1; + } + paths.push_back(ps); + cout << "*******************************************" << endl; + } + for(unsigned i=0;i<paths.size();i++) { + vector<Point> ps=paths[i]; + for(unsigned j=0;j<ps.size();j++) { + double x=ps[j].first, y=ps[j].second; + cout << "(" << x << "," << y << ")" << ","; + } + cout << endl; + } + return(0); +} diff --git a/src/toys/differential-constraint.cpp b/src/toys/differential-constraint.cpp new file mode 100644 index 0000000..501eb76 --- /dev/null +++ b/src/toys/differential-constraint.cpp @@ -0,0 +1,132 @@ +/** Differential constraint solver hack + * based on idea from Michael Glissner + * (njh) + */ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework.h> + +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +#include <stdio.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_matrix.h> +#include <gsl/gsl_odeiv.h> + +vector<Geom::Point> *handlesptr = NULL; + +const unsigned order = 6; + +class SBez: public Toy { + static int + func (double /*t*/, const double y[], double f[], + void */*params*/) + { + //double mu = *(double *)params; + D2<SBasis> B = handles_to_sbasis(handlesptr->begin(), order); + D2<SBasis> dB = derivative(B); + Geom::Point tan = dB(y[0]);//Geom::unit_vector(); + tan /= dot(tan,tan); + Geom::Point yp = B(y[0]); + double dtau = -dot(tan, yp - (*handlesptr)[order+1]); + f[0] = dtau; + + return GSL_SUCCESS; + } + + static int + jac (double /*t*/, const double y[], double *dfdy, + double dfdt[], void *params) + { + double mu = *(double *)params; + gsl_matrix_view dfdy_mat + = gsl_matrix_view_array (dfdy, 2, 2); + gsl_matrix * m = &dfdy_mat.matrix; + gsl_matrix_set (m, 0, 0, 0.0); + gsl_matrix_set (m, 0, 1, 1.0); + gsl_matrix_set (m, 1, 0, -2.0*mu*y[0]*y[1] - 1.0); + gsl_matrix_set (m, 1, 1, -mu*(y[0]*y[0] - 1.0)); + dfdt[0] = 0.0; + dfdt[1] = 0.0; + return GSL_SUCCESS; + } + + double y[2]; + + virtual void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + handlesptr = &handles; + cairo_set_line_width (cr, 0.5); + + D2<SBasis> B = handles_to_sbasis(handles.begin(), order); + cairo_d2_sb(cr, B); + + const gsl_odeiv_step_type * T + = gsl_odeiv_step_rk8pd; + + gsl_odeiv_step * s + = gsl_odeiv_step_alloc (T, 1); + gsl_odeiv_control * c + = gsl_odeiv_control_y_new (1e-6, 0.0); + gsl_odeiv_evolve * e + = gsl_odeiv_evolve_alloc (1); + + double mu = 10; + gsl_odeiv_system sys = {func, jac, 1, &mu}; + + static double t = 0.0; + double t1 = t + 1; + double h = 1e-6; + + while (t < t1) + { + int status = gsl_odeiv_evolve_apply (e, c, s, + &sys, + &t, t1, + &h, y); + + if (status != GSL_SUCCESS) + break; + + //printf ("%.5e %.5e %.5e\n", t, y[0], y[1]); + } + + draw_cross(cr, B(y[0])); + + gsl_odeiv_evolve_free (e); + gsl_odeiv_control_free (c); + gsl_odeiv_step_free (s); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } +public: + SBez() { + y[0] = 0; + for(unsigned i = 0; i <= order+1; i++) { + handles.push_back(Geom::Point(uniform()*400, uniform()*400)); + } + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SBez()); + + return 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/toys/draw-toy.cpp b/src/toys/draw-toy.cpp new file mode 100644 index 0000000..f5bd315 --- /dev/null +++ b/src/toys/draw-toy.cpp @@ -0,0 +1,116 @@ +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +class DrawToy: public Toy { + PointSetHandle hand; + //Knot : Och : Och : Knot : Och : Och : Knot : Och : Och : ... + void draw(cairo_t *cr, std::ostringstream */*notify*/, int /*width*/, int /*height*/, bool save, std::ostringstream*) override { + if(!save) { + cairo_set_source_rgba (cr, 0, 0.5, 0, 1); + cairo_set_line_width (cr, 1); + for(unsigned i = 0; i < hand.pts.size(); i+=3) { + draw_circ(cr, hand.pts[i]); + draw_number(cr, hand.pts[i], i/3); + } + cairo_set_source_rgba (cr, 0, 0, 0.5, 1); + for(unsigned i = 2; i < hand.pts.size(); i+=3) { + draw_circ(cr, hand.pts[i]); + draw_circ(cr, hand.pts[i-1]); + } + + cairo_set_source_rgba (cr, 0.5, 0, 0, 1); + for(unsigned i = 3; i < hand.pts.size(); i+=3) { + draw_line_seg(cr, hand.pts[i-2], hand.pts[i-3]); + draw_line_seg(cr, hand.pts[i], hand.pts[i-1]); + } + } + cairo_set_source_rgba (cr, 0, 0, 0, 1); + Geom::Path pb; + if(hand.pts.size() > 3) { + pb.start(hand.pts[0]); + for(unsigned i = 1; i < hand.pts.size() - 3; i+=3) { + pb.appendNew<Geom::CubicBezier>(hand.pts[i], hand.pts[i+1], hand.pts[i+2]); + } + } + cairo_path(cr, pb); + cairo_stroke(cr); + } + void mouse_pressed(GdkEventButton* e) override { + selected = NULL; + Geom::Point mouse(e->x, e->y); + int close_i = 0; + float close_d = 1000; + for(unsigned i = 0; i < hand.pts.size(); i+=1) { + if(Geom::distance(mouse, hand.pts[i]) < close_d) { + close_d = Geom::distance(mouse, hand.pts[i]); + close_i = i; + } + } + if(close_d < 5) { + if(e->button==3) + hand.pts.erase(hand.pts.begin() + close_i); + else { + selected = &hand; + hit_data = (void*)(intptr_t)close_i; + } + } else { + if(e->button==1) { + if(hand.pts.size() > 0) { + if(hand.pts.size() == 1) { + hand.pts.push_back((hand.pts[0] * 2 + mouse) / 3); + hand.pts.push_back((hand.pts[0] + mouse * 2) / 3); + } else { + Geom::Point prev = hand.pts[hand.pts.size() - 1]; + Geom::Point curve = prev - hand.pts[hand.pts.size() - 2]; + hand.pts.push_back(prev + curve); + hand.pts.push_back(mouse + curve); + } + } + hand.pts.push_back(mouse); + } else { + selected = &hand; + hit_data = (void*)(intptr_t)close_i; + } + } + } + + void mouse_moved(GdkEventMotion* e) override { + Geom::Point mouse(e->x, e->y); + + if(e->state & (GDK_BUTTON1_MASK) && selected != NULL) { + // NOTE this is completely broken. + int hd = 0; + if (hd % 3 == 0) { + Geom::Point diff = mouse - hand.pts[hd]; + if(int(hand.pts.size() - 1) > hd) hand.pts[hd + 1] += diff; + if(hd != 0) hand.pts[hd - 1] += diff; + } + Toy::mouse_moved(e); + } + } + + bool should_draw_numbers() override { return false; } +public: + DrawToy() { + + handles.push_back(&hand); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new DrawToy()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ellipse-area-minimizer.cpp b/src/toys/ellipse-area-minimizer.cpp new file mode 100644 index 0000000..48df598 --- /dev/null +++ b/src/toys/ellipse-area-minimizer.cpp @@ -0,0 +1,352 @@ +/* + * Ellipse and Elliptical Arc Fitting Example + * + * 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/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> +#include <2geom/utils.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <stdio.h> + +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_min.h> + + + + +using namespace Geom; + + +class LFMEllipseArea + : public NL::LinearFittingModelWithFixedTerms<Point, double, Ellipse> +{ + public: + LFMEllipseArea(double coeff) + : m_coeff(coeff*coeff) + { + } + void feed( NL::VectorView & coeff, double & fixed_term, Point const& p ) const + { + coeff[0] = p[X] * p[Y]; + coeff[1] = p[X]; + coeff[2] = p[Y]; + coeff[3] = 1; + fixed_term = p[X] * p[X] + m_coeff * p[Y] * p[Y]; + } + + size_t size() const + { + return 4; + } + + void instance(Ellipse & e, NL::ConstVectorView const& coeff) const + { +// std::cerr << " B = " << coeff[0] +// << " C = " << decimal_round(m_coeff,10) +// << " D = " << coeff[1] +// << " E = " << coeff[2] +// << " F = " << coeff[3] +// << std::endl; + e.setCoefficients(1, coeff[0], m_coeff, coeff[1], coeff[2], coeff[3]); + } + + private: + double m_coeff; +}; + +inline +Ellipse fitting(std::vector<Point> const& points, double coeff) +{ + size_t sz = points.size(); + if (sz != 4) + { + THROW_RANGEERROR("fitting error: too few points passed"); + } + LFMEllipseArea model(coeff); + NL::least_squeares_fitter<LFMEllipseArea> fitter(model, sz); + + for (size_t i = 0; i < sz; ++i) + { + fitter.append(points[i]); + } + fitter.update(); + + NL::Vector z(sz, 0.0); + Ellipse e; + model.instance(e, fitter.result(z)); + return e; +} + + +inline +double area_goal(double coeff, void* params) +{ + typedef std::vector<Point> point_set_t; + const point_set_t & points = *static_cast<point_set_t*>(params); + Ellipse e; + try + { + e = fitting(points, coeff); + } + catch(LogicalError exc) + { + //std::cerr << exc.what() << std::endl; + return 1e18; + } + return e.ray(X) * e.ray(Y); +} + + +inline +double perimeter_goal(double coeff, void* params) +{ + typedef std::vector<Point> point_set_t; + const point_set_t & points = *static_cast<point_set_t*>(params); + Ellipse e; + try + { + e = fitting(points, coeff); + } + catch(LogicalError exc) + { + //std::cerr << exc.what() << std::endl; + return 1e18; + } + return e.ray(X) + e.ray(Y); +} + +void no_minimum_error_handler (const char * reason, + const char * file, + int line, + int gsl_errno) +{ + if (gsl_errno == GSL_EINVAL) + { + std::cerr << "gsl: " << file << ":" << line << " ERROR: " << reason << std::endl; + } + else + { + gsl_error(reason, file, line, gsl_errno); + } +} + +typedef double goal_function_type(double coeff, void* params); + +double minimizer (std::vector<Point> & points, goal_function_type* gf) +{ + int status; + int iter = 0, max_iter = 1000; + const gsl_min_fminimizer_type *T; + gsl_min_fminimizer *s; + double m = 1.0; + double a = 1e-2, b = 1e2; + gsl_function F; + + F.function = gf; + F.params = static_cast<void*>(&points); + + //T = gsl_min_fminimizer_goldensection; + T = gsl_min_fminimizer_brent; + s = gsl_min_fminimizer_alloc (T); + gsl_min_fminimizer_set (s, &F, m, a, b); + +// printf ("using %s method\n", +// gsl_min_fminimizer_name (s)); +// +// printf ("%5s [%9s, %9s] %9s %10s %9s\n", +// "iter", "lower", "upper", "min", +// "err", "err(est)"); +// +// printf ("%5d [%.7f, %.7f] %.7f %+.7f %.7f\n", +// iter, a, b, +// m, m - m_expected, b - a); + + do + { + iter++; + status = gsl_min_fminimizer_iterate (s); + + m = gsl_min_fminimizer_x_minimum (s); + a = gsl_min_fminimizer_x_lower (s); + b = gsl_min_fminimizer_x_upper (s); + + status + = gsl_min_test_interval (a, b, 1e-3, 0.0); + +// if (status == GSL_SUCCESS) +// printf ("Converged:\n"); +// +// printf ("%5d [%.7f, %.7f] " +// "%.7f %+.7f %.7f\n", +// iter, a, b, +// m, m - m_expected, b - a); + } + while (status == GSL_CONTINUE && iter < max_iter); + + gsl_min_fminimizer_free (s); + + if (status != GSL_SUCCESS) return 0; + + return m; +} + + + +class EllipseAreaMinimizer : public Toy +{ + public: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + Point toggle_sp( 300, height - 50); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(135,25) ); + ConvexHull ch(psh.pts); + bool non_convex = false; + for(auto & pt : psh.pts) { + if (ch.contains(pt)) + non_convex = true; + } + + if(non_convex) { + Circle circ; + std::vector<Point> boundary(ch.begin(), ch.end()); + circ.fit(boundary); + e = Ellipse(circ); + } else { + goal_function_type* gf = &area_goal; + if (!toggles[0].on) gf = &perimeter_goal; + double coeff = minimizer(psh.pts, gf); + + try + { + e = fitting(psh.pts, coeff); + } + catch(LogicalError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + draw_elliptical_arc_with_cairo( cr, + e.center(X), e.center(Y), + e.ray(X), e.ray(Y), + 0, 2*M_PI, + e.rotationAngle() ); + if (toggles[0].on) + *notify << "Area: " << e.ray(X)*e.ray(Y); + else + *notify << "Perimeter: " << 2* (e.ray(X) + e.ray(Y)); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void draw_elliptical_arc_with_cairo( + cairo_t *cr, + double _cx, double _cy, + double _rx, double _ry, + double _sa, double _ea, + double _ra = 0 + ) const + { + double cos_rot_angle = std::cos(_ra); + double sin_rot_angle = std::sin(_ra); + cairo_matrix_t transform_matrix; + cairo_matrix_init( &transform_matrix, + _rx * cos_rot_angle, _rx * sin_rot_angle, + -_ry * sin_rot_angle, _ry * cos_rot_angle, + _cx, _cy + ); + cairo_save(cr); + cairo_transform(cr, &transform_matrix); + cairo_arc(cr, 0, 0, 1, _sa, _ea); + cairo_restore(cr); + } + + public: + EllipseAreaMinimizer() + { + gsl_set_error_handler(&no_minimum_error_handler); + + first_time = true; + + psh.pts.resize(4); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + psh.pts[3] = Point(400, 320); + + handles.push_back(&psh); + + toggles.emplace_back("Area/Perimeter", true); + handles.push_back(&(toggles[0])); + } + + public: + Ellipse e; + bool first_time; + PointSetHandle psh; + std::vector<Toggle> toggles; +}; + + + + +int main(int argc, char **argv) +{ + init( argc, argv, new EllipseAreaMinimizer(), 600, 600 ); + return 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/toys/ellipse-bezier-intersect-toy.cpp b/src/toys/ellipse-bezier-intersect-toy.cpp new file mode 100644 index 0000000..5468d97 --- /dev/null +++ b/src/toys/ellipse-bezier-intersect-toy.cpp @@ -0,0 +1,74 @@ +#include <2geom/cairo-path-sink.h> +#include <2geom/ellipse.h> +#include <2geom/line.h> +#include <2geom/polynomial.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle eh, bh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Rect all(Point(0,0), Point(width, height)); + + double rx = Geom::distance(eh.pts[0], eh.pts[1]); + double ry = Geom::distance(eh.pts[0], eh.pts[2]); + double rot = Geom::atan2(eh.pts[1] - eh.pts[0]); + + Ellipse e(eh.pts[0], Point(rx, ry), rot); + D2<Bezier> b(bh.pts); + + cairo_set_line_width(cr, 1.0); + Geom::CairoPathSink cps(cr); + + // draw Bezier control polygon + cairo_set_source_rgba(cr, 0, 0, 1, 0.3); + cps.moveTo(bh.pts[0]); + for (unsigned i = 1; i < bh.pts.size(); ++i) { + cps.lineTo(bh.pts[i]); + } + cairo_stroke(cr); + + // draw Bezier curve and ellipse + cairo_set_source_rgb(cr, 0, 0, 0); + cps.feed(BezierCurve(b), true); + cps.feed(e); + cairo_stroke(cr); + + std::vector<ShapeIntersection> result = e.intersect(b); + + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + eh.push_back(300,300); eh.push_back(450,150); eh.push_back(250, 350); + bh.push_back(100,100); bh.push_back(500,100); bh.push_back(100,500); bh.push_back(500,500); + handles.push_back(&eh); + handles.push_back(&bh); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ellipse-fitting.cpp b/src/toys/ellipse-fitting.cpp new file mode 100644 index 0000000..de8c81a --- /dev/null +++ b/src/toys/ellipse-fitting.cpp @@ -0,0 +1,191 @@ +/* + * Ellipse and Elliptical Arc Fitting Example + * + * 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/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class EllipseFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + if (first_time) + { + first_time = false; + Point toggle_sp( 300, height - 50); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(120,25) ); + sliders[0].geometry(Point(50, height - 50), 100); + } + + size_t n = (size_t)(sliders[0].value()) + 5; + if (n < psh.pts.size()) + { + psh.pts.resize(n); + } + else if (n > psh.pts.size()) + { + psh.push_back(400*uniform()+50, 300*uniform()+50); + } + + try + { + e.fit(psh.pts); + } + catch(LogicalError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + + if (toggles[0].on) + { + try + { + std::unique_ptr<EllipticalArc> eap( e.arc(psh.pts[0], psh.pts[2], psh.pts[4]) ); + ea = *eap; + } + catch(RangeError exc) + { + std::cerr << exc.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + if (!toggles[0].on) + { + draw_elliptical_arc_with_cairo( cr, + e.center(X), e.center(Y), + e.ray(X), e.ray(Y), + 0, 2*M_PI, + e.rotationAngle() ); + } + else + { + draw_text(cr, psh.pts[0], "initial"); + draw_text(cr, psh.pts[2], "inner"); + draw_text(cr, psh.pts[4], "final"); + + D2<SBasis> easb = ea.toSBasis(); + cairo_d2_sb(cr, easb); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void draw_elliptical_arc_with_cairo( + cairo_t *cr, + double _cx, double _cy, + double _rx, double _ry, + double _sa, double _ea, + double _ra = 0 + ) const + { + double cos_rot_angle = std::cos(_ra); + double sin_rot_angle = std::sin(_ra); + cairo_matrix_t transform_matrix; + cairo_matrix_init( &transform_matrix, + _rx * cos_rot_angle, _rx * sin_rot_angle, + -_ry * sin_rot_angle, _ry * cos_rot_angle, + _cx, _cy + ); + cairo_save(cr); + cairo_transform(cr, &transform_matrix); + cairo_arc(cr, 0, 0, 1, _sa, _ea); + cairo_restore(cr); + } + + public: + EllipseFitting() + { + first_time = true; + + psh.pts.resize(5); + psh.pts[0] = Point(450, 250); + psh.pts[1] = Point(250, 100); + psh.pts[2] = Point(250, 400); + psh.pts[3] = Point(400, 320); + psh.pts[4] = Point(50, 250); + + + toggles.emplace_back(" arc / ellipse ", false); + sliders.emplace_back(0, 5, 1, 0, "more handles"); + + handles.push_back(&psh); + handles.push_back(&(toggles[0])); + handles.push_back(&(sliders[0])); + } + + private: + Ellipse e; + EllipticalArc ea; + bool first_time; + PointSetHandle psh; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new EllipseFitting(), 600, 600 ); + return 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/toys/ellipse-intersect-toy.cpp b/src/toys/ellipse-intersect-toy.cpp new file mode 100644 index 0000000..3d65baa --- /dev/null +++ b/src/toys/ellipse-intersect-toy.cpp @@ -0,0 +1,158 @@ +#include <2geom/cairo-path-sink.h> +#include <2geom/ellipse.h> +#include <2geom/line.h> +#include <2geom/polynomial.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle psh[2]; + Ellipse ea, eb; + Line l[6]; + + void intersect() { + // This is code is almost the same as in ellipse.cpp. + // We use it here to get the lines. + double A, B, C, D, E, F; + double a, b, c, d, e, f; + + ea.coefficients(A, E, B, C, D, F); + eb.coefficients(a, e, b, c, d, f); + + double 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<double> mu = solve_cubic(I, J, K, L); + + for (unsigned i = 0; i < mu.size(); ++i) { + double aa = mu[i] * A + a; + double bb = mu[i] * B + b; + double cc = mu[i] * C + c; + double dd = mu[i] * D + d; + double ee = mu[i] * E + e; + double ff = mu[i] * F + f; + double delta = ee*ee - 4*aa*bb; + if (delta < 0) { + continue; + } + + if (aa != 0) { + bb /= aa; cc /= aa; dd /= aa; ee /= aa; ff /= aa; + double s = (ee + std::sqrt(ee*ee - 4*bb)) / 2; + double q = ee - s; + double alpha = (dd - cc*q) / (s - q); + double beta = cc - alpha; + + l[i*2] = Line(1, q, alpha); + l[i*2+1] = Line(1, s, beta); + } else if (bb != 0) { + cc /= bb; dd /= bb; ee /= bb; ff /= bb; + double s = ee; + double q = 0; + double alpha = cc / ee; + double beta = ff * ee / cc; + + l[i*2] = Line(q, 1, alpha); + l[i*2+1] = Line(s, 1, beta); + } else { + // both aa and bb are zero + l[i*2] = Line(ee, 1, dd); + l[i*2+1] = Line(0, 1, cc/ee); + } + } + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Rect all(Point(0,0), Point(width, height)); + + double r1x = Geom::distance(psh[0].pts[0], psh[0].pts[1]); + double r1y = Geom::distance(psh[0].pts[0], psh[0].pts[2]); + double rot1 = Geom::atan2(psh[0].pts[1] - psh[0].pts[0]); + + double r2x = Geom::distance(psh[1].pts[0], psh[1].pts[1]); + double r2y = Geom::distance(psh[1].pts[0], psh[1].pts[2]); + double rot2 = Geom::atan2(psh[1].pts[1] - psh[1].pts[0]); + + ea = Ellipse(psh[0].pts[0], Point(r1x, r1y), rot1); + eb = Ellipse(psh[1].pts[0], Point(r2x, r2y), rot2); + + for (auto & i : l) { + i = Line(0, 0, 0); + } + + cairo_set_line_width(cr, 1.0); + + cairo_set_source_rgb(cr, 0, 0, 0); + Geom::CairoPathSink cps(cr); + cps.feed(ea); + cps.feed(eb); + cairo_stroke(cr); + + try { + intersect(); + std::vector<ShapeIntersection> result = ea.intersect(eb); + + if (!l[0].isDegenerate() && !l[1].isDegenerate()) { + cairo_set_source_rgba(cr, 1, 0, 0, 0.2); + draw_line(cr, l[0], all); + draw_line(cr, l[1], all); + cairo_stroke(cr); + } + if (!l[2].isDegenerate() && !l[3].isDegenerate()) { + cairo_set_source_rgba(cr, 0, 1, 0, 0.2); + draw_line(cr, l[2], all); + draw_line(cr, l[3], all); + cairo_stroke(cr); + } + if (!l[4].isDegenerate() && !l[5].isDegenerate()) { + cairo_set_source_rgba(cr, 0, 0, 1, 0.2); + draw_line(cr, l[4], all); + draw_line(cr, l[5], all); + cairo_stroke(cr); + } + + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + } catch(...) { + *notify << "Exception"; + } + + // TODO: draw_handle at intersections + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + psh[0].push_back(300,300); psh[0].push_back(450,150); psh[0].push_back(250, 250); + psh[1].push_back(350,300); psh[1].push_back(500,500); psh[1].push_back(300, 350); + handles.push_back(&psh[0]); + handles.push_back(&psh[1]); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/ellipse-line-intersect-toy.cpp b/src/toys/ellipse-line-intersect-toy.cpp new file mode 100644 index 0000000..4172852 --- /dev/null +++ b/src/toys/ellipse-line-intersect-toy.cpp @@ -0,0 +1,67 @@ +#include <2geom/cairo-path-sink.h> +#include <2geom/ellipse.h> +#include <2geom/line.h> +#include <2geom/polynomial.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class CircleIntersect : public Toy { + PointSetHandle eh, lh; + Ellipse e; + Line l; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Rect all(Point(0,0), Point(width, height)); + + double rx = Geom::distance(eh.pts[0], eh.pts[1]); + double ry = Geom::distance(eh.pts[0], eh.pts[2]); + double rot = Geom::atan2(eh.pts[1] - eh.pts[0]); + + Ellipse e(eh.pts[0], Point(rx, ry), rot); + LineSegment l(lh.pts[0], lh.pts[1]); + + cairo_set_line_width(cr, 1.0); + + cairo_set_source_rgb(cr, 0, 0, 0); + draw_line_segment(cr, l, all); + Geom::CairoPathSink cps(cr); + cps.feed(e); + cairo_stroke(cr); + + std::vector<ShapeIntersection> result = e.intersect(l); + + cairo_set_source_rgb(cr, 1, 0, 0); + for (auto & i : result) { + draw_handle(cr, i.point()); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + CircleIntersect(){ + eh.push_back(300,300); eh.push_back(450,150); eh.push_back(250, 350); + lh.push_back(280, 50); lh.push_back(320,550); + handles.push_back(&eh); + handles.push_back(&lh); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new CircleIntersect()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/elliptiarc-3point-center-fitting.cpp b/src/toys/elliptiarc-3point-center-fitting.cpp new file mode 100644 index 0000000..d2330a1 --- /dev/null +++ b/src/toys/elliptiarc-3point-center-fitting.cpp @@ -0,0 +1,266 @@ +/* + * make up an elliptical arc knowing 3 points lying on the arc + * and the ellipse centre + * + * 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 <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/elliptical-arc.h> +#include <2geom/numeric/linear_system.h> + +namespace Geom +{ + +bool make_elliptical_arc( EllipticalArc & ea, + Point const& centre, + Point const& initial, + Point const& final, + Point const& inner ) +{ + + Point p[3] = { initial, inner, final }; + double x1, x2, x3, x4; + double y1, y2, y3, y4; + double x1y1, x2y2, x3y1, x1y3; + NL::Matrix m(3,3); + NL::Vector v(3); + NL::LinearSystem ls(m, v); + + m.set_all(0); + v.set_all(0); + for (auto & k : p) + { + // init_x_y + x1 = k[X] - centre[X]; x2 = x1 * x1; x3 = x2 * x1; x4 = x3 * x1; + y1 = k[Y] - centre[Y]; y2 = y1 * y1; y3 = y2 * y1; y4 = y3 * y1; + x1y1 = x1 * y1; + x2y2 = x2 * y2; + x3y1 = x3 * y1; x1y3 = x1 * y3; + + // init linear system + m(0,0) += x4; + m(0,1) += x3y1; + m(0,2) += x2y2; + + m(1,0) += x3y1; + m(1,1) += x2y2; + m(1,2) += x1y3; + + m(2,0) += x2y2; + m(2,1) += x1y3; + m(2,2) += y4; + + v[0] += x2; + v[1] += x1y1; + v[2] += y2; + } + + ls.SV_solve(); + + double A = ls.solution()[0]; + double B = ls.solution()[1]; + double C = ls.solution()[2]; + + + //evaluate ellipse rotation angle + double rot = std::atan2( -B, -(A - C) )/2; + std::cerr << "rot = " << rot << std::endl; + bool swap_axes = false; + if ( are_near(rot, 0) ) rot = 0; + if ( are_near(rot, M_PI/2) || rot < 0 ) + { + swap_axes = true; + } + + // evaluate the length of the ellipse rays + double cosrot = std::cos(rot); + double sinrot = std::sin(rot); + double cos2 = cosrot * cosrot; + double sin2 = sinrot * sinrot; + double cossin = cosrot * sinrot; + + double den = A * cos2 + B * cossin + C * sin2; + if ( den <= 0 ) + { + std::cerr << "!(den > 0) error" << std::endl; + std::cerr << "evaluating rx" << std::endl; + return false; + } + double rx = std::sqrt(1/den); + + den = C * cos2 - B * cossin + A * sin2; + if ( den <= 0 ) + { + std::cerr << "!(den > 0) error" << std::endl; + std::cerr << "evaluating ry" << std::endl; + return false; + } + double ry = std::sqrt(1/den); + + + // the solution is not unique so we choose always the ellipse + // with a rotation angle between 0 and PI/2 + if ( swap_axes ) std::swap(rx, ry); + if ( are_near(rot, M_PI/2) + || are_near(rot, -M_PI/2) + || are_near(rx, ry) ) + { + rot = 0; + } + else if ( rot < 0 ) + { + rot += M_PI/2; + } + + std::cerr << "swap axes: " << swap_axes << std::endl; + std::cerr << "rx = " << rx << " ry = " << ry << std::endl; + std::cerr << "rot = " << deg_from_rad(rot) << std::endl; + std::cerr << "centre: " << centre << std::endl; + + + // find out how we should set the large_arc_flag and sweep_flag + bool large_arc_flag = true; + bool sweep_flag = true; + + Point sp_cp = initial - centre; + Point ep_cp = final - centre; + Point ip_cp = inner - centre; + + double angle1 = angle_between(sp_cp, ep_cp); + double angle2 = angle_between(sp_cp, ip_cp); + double angle3 = angle_between(ip_cp, ep_cp); + + if ( angle1 > 0 ) + { + if ( angle2 > 0 && angle3 > 0 ) + { + large_arc_flag = false; + sweep_flag = true; + } + else + { + large_arc_flag = true; + sweep_flag = false; + } + } + else + { + if ( angle2 < 0 && angle3 < 0 ) + { + large_arc_flag = false; + sweep_flag = false; + } + else + { + large_arc_flag = true; + sweep_flag = true; + } + } + + // finally we're going to create the elliptical arc! + try + { + ea.set( initial, rx, ry, rot, + large_arc_flag, sweep_flag, final ); + } + catch( RangeError e ) + { + std::cerr << e.what() << std::endl; + return false; + } + + return true; +} + + +} + + + +using namespace Geom; + +class ElliptiArcMaker : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgb(cr, 0,0,0.3); + draw_text(cr, O.pos, "centre"); + draw_text(cr, A.pos, "initial"); + draw_text(cr, B.pos, "final"); + draw_text(cr, C.pos, "inner"); + cairo_stroke(cr); + cairo_set_source_rgb(cr, 0.7,0,0); + bool status + = make_elliptical_arc(ea, O.pos, A.pos, B.pos, C.pos); + if (status) + { + D2<Geom::SBasis> easb = ea.toSBasis(); + cairo_d2_sb(cr, easb); + } + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + ElliptiArcMaker() + : O(443, 441), + A(516, 275), + B(222, 455), + C(190, 234) + { + handles.push_back(&O); + handles.push_back(&A); + handles.push_back(&B); + handles.push_back(&C); + } + + private: + PointHandle O, A, B, C; + EllipticalArc ea; +}; + + + + + + + + +int main(int argc, char **argv) +{ + init( argc, argv, new ElliptiArcMaker() ); + return 0; +} + diff --git a/src/toys/elliptiarc-curve-fitting.cpp b/src/toys/elliptiarc-curve-fitting.cpp new file mode 100644 index 0000000..6c47f8d --- /dev/null +++ b/src/toys/elliptiarc-curve-fitting.cpp @@ -0,0 +1,127 @@ +/* + * Elliptical Arc Fitting Toy + * + * 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 <gsl/gsl_linalg.h> + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/elliptical-arc.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + + +using namespace Geom; + + + +class EAFittingToy : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + cairo_set_line_width (cr, 0.2); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.); + //D2<SBasis> SB = handles_to_sbasis(handles.begin(), total_handles - 1); + D2<SBasis> SB = psh.asBezier(); + cairo_d2_sb(cr, SB); + cairo_stroke(cr); + + cairo_set_line_width (cr, 0.4); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.7, 1.0); + try + { + EllipticalArc EA; + if (!arc_from_sbasis(EA, SB, tolerance, 10)) { +// *notify << "distance error: " << convert.get_error() +// << " ( " << convert.get_bound() << " )" << std::endl +// << "angle error: " << convert.get_angle_error() +// << " ( " << convert.get_angle_tolerance() << " )"; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + D2<SBasis> easb = EA.toSBasis(); + cairo_d2_sb(cr, easb); + cairo_stroke(cr); + } + catch( RangeError e ) + { + std::cerr << e.what() << std::endl; + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + EAFittingToy( double _tolerance ) + : tolerance(_tolerance) + { + handles.push_back(&psh); + total_handles = 6; + for ( unsigned int i = 0; i < total_handles; ++i ) + { + psh.push_back(uniform()*400, uniform()*400); + } + } + + PointSetHandle psh; + unsigned int total_handles; + double tolerance; +}; + + + +int main(int argc, char **argv) +{ + double tolerance = 8; + if(argc > 1) + sscanf(argv[1], "%lf", &tolerance); + init( argc, argv, new EAFittingToy(tolerance) ); + return 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: +*/ diff --git a/src/toys/elliptical-arc-toy.cpp b/src/toys/elliptical-arc-toy.cpp new file mode 100644 index 0000000..e2518f0 --- /dev/null +++ b/src/toys/elliptical-arc-toy.cpp @@ -0,0 +1,903 @@ +/** @file + * @brief Demonstration of elliptical arc functions + *//* + * Authors: + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof KosiÅ„ski <tweenk.pl@gmail.com> + * Copyright 2008-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/elliptical-arc.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/cairo-path-sink.h> + +#include <vector> +#include <string> + + +using namespace Geom; + + + +std::string angle_formatter(double angle) +{ + return default_formatter(decimal_round(deg_from_rad(angle),2)); +} + + + +class EllipticalArcToy: public Toy +{ + + enum menu_item_t + { + SHOW_MENU = 0, + TEST_BASIC, + TEST_COMPARISON, + TEST_PORTION, + TEST_REVERSE, + TEST_NEAREST_POINTS, + TEST_DERIVATIVE, + TEST_ROOTS, + TEST_BOUNDS, + TEST_FITTING, + TEST_TRANSFORM, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + START_POINT = 0, + END_POINT, + POINT + }; + + enum toggle_label_t + { + LARGE_ARC_FLAG = 0, + SWEEP_FLAG, + X_Y_TOGGLE + }; + + enum slider_label_t + { + RX_SLIDER = 0, + RY_SLIDER, + ROT_ANGLE_SLIDER, + T_SLIDER, + FROM_SLIDER = T_SLIDER, + TO_SLIDER, + TM0_SLIDER = T_SLIDER, + TM1_SLIDER, + TM2_SLIDER, + TM3_SLIDER + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &EllipticalArcToy::draw_menu; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + double start_angle = (10.0/6.0) * M_PI; + double sweep_angle = (4.0/6.0) * M_PI; + double end_angle = start_angle + sweep_angle; + double rot_angle = (0.0/6.0) * M_PI; + double rx = 200; + double ry = 150; + double cx = 300; + double cy = 300; + + Point start_point( cx + rx * std::cos(start_angle), + cy + ry * std::sin(start_angle) ); + Point end_point( cx + rx * std::cos(end_angle), + cy + ry * std::sin(end_angle) ); + + bool large_arc = false; + bool sweep = true; + + + initial_point.pos = start_point; + final_point.pos = end_point; + + try + { + ea.set (initial_point.pos, + rx, ry, rot_angle, + large_arc, sweep, + final_point.pos); + } + catch (RangeError const &e) + { + no_solution = true; + std::cerr << e.what() << std::endl; + } + + sliders.clear(); + sliders.reserve(50); + sliders.emplace_back(0, 500, 0, ea.ray(X), "ray X"); + sliders.emplace_back(0, 500, 0, ea.ray(Y), "ray Y"); + sliders.emplace_back(0, 2*M_PI, 0, ea.rotationAngle(), "rot angle"); + sliders[ROT_ANGLE_SLIDER].formatter(&angle_formatter); + + toggles.clear(); + toggles.reserve(50); + toggles.emplace_back("Large Arc Flag", ea.largeArc()); + toggles.emplace_back("Sweep Flag", ea.sweep()); + + handles.clear(); + handles.push_back(&initial_point); + handles.push_back(&final_point); + handles.push_back(&(sliders[RX_SLIDER])); + handles.push_back(&(sliders[RY_SLIDER])); + handles.push_back(&(sliders[ROT_ANGLE_SLIDER])); + handles.push_back(&(toggles[LARGE_ARC_FLAG])); + handles.push_back(&(toggles[SWEEP_FLAG])); + } + + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, + std::ostringstream *timer_stream=0) + { + if(timer_stream == 0) + timer_stream = notify; + init_common_ctrl_geom(cr, width, height, notify); + + no_solution = false; + try + { + ea.set( initial_point.pos, + sliders[0].value(), + sliders[1].value(), + sliders[2].value(), + toggles[0].on, + toggles[1].on, + final_point.pos ); + } + catch (RangeError const &e) + { + no_solution = true; + std::cerr << e.what() << std::endl; + return; + } + + degenerate = ea.isDegenerate(); + + point_overlap = false; + if ( are_near(ea.initialPoint(), ea.finalPoint()) ) + { + point_overlap = true; + } + + // calculate the center of the two possible ellipse supporting the arc + std::pair<Point,Point> centers + = calculate_ellipse_centers( ea.initialPoint(), ea.finalPoint(), + ea.ray(X), ea.ray(Y), ea.rotationAngle(), + ea.largeArc(), ea.sweep() ); + + + // draw axes passing through the center of the ellipse supporting the arc + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.5); + draw_axes(cr); + + // draw the 2 ellipse with rays rx, ry passing through + // the 2 given point and with the x-axis inclined of rot_angle + if ( !(are_near(ea.ray(X), 0) || are_near(ea.ray(Y), 0)) ) + { + cairo_elliptiarc( cr, + centers.first[X], centers.first[Y], + ea.ray(X), ea.ray(Y), + 0, 2*M_PI, + ea.rotationAngle() ); + cairo_stroke(cr); + cairo_elliptiarc( cr, + centers.second[X], centers.second[Y], + ea.ray(X), ea.ray(Y), + 0, 2*M_PI, + ea.rotationAngle() ); + cairo_stroke(cr); + } + + // convert the elliptical arc to a sbasis path and draw it + D2<SBasis> easb = ea.toSBasis(); + cairo_set_line_width(cr, 0.5); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); + cairo_d2_sb(cr, easb); + cairo_stroke(cr); + + // draw initial and final point labels + draw_text(cr, ea.initialPoint() + Point(5, -15), "initial"); + draw_text(cr, ea.finalPoint() + Point(5, 0), "final"); + cairo_stroke(cr); + + // TODO re-enable this + //*notify << ea; + } + + + void draw_comparison(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + if ( no_solution || point_overlap ) return; + + // draw the arc with cairo in order to make a visual comparison + cairo_set_line_width(cr, 1); + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); + + if (ea.isDegenerate()) + { + cairo_move_to(cr, ea.initialPoint()); + cairo_line_to(cr, ea.finalPoint()); + } + else + { + if ( ea.sweep() ) + { + cairo_elliptiarc( cr, + ea.center(X), ea.center(Y), + ea.ray(X), ea.ray(Y), + ea.initialAngle(), ea.finalAngle(), + ea.rotationAngle() ); + } + else + { + cairo_elliptiarc( cr, + ea.center(X), ea.center(Y), + ea.ray(X), ea.ray(Y), + ea.finalAngle(), ea.initialAngle(), + ea.rotationAngle() ); + } + } + cairo_stroke(cr); + } + + + void init_portion() + { + init_common(); + + from_t = 0; + to_t = 1; + + sliders.emplace_back(0, 1, 0, from_t, "from"); + sliders.emplace_back(0, 1, 0, to_t, "to"); + + handles.push_back(&(sliders[FROM_SLIDER])); + handles.push_back(&(sliders[TO_SLIDER])); + } + + void draw_portion(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_portion_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + from_t = sliders[FROM_SLIDER].value(); + to_t = sliders[TO_SLIDER].value(); + + EllipticalArc* eapp + = static_cast<EllipticalArc*>(ea.portion(from_t, to_t)); + EllipticalArc& eap = *eapp; + + cairo_set_line_width(cr, 0.8); + cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 1.0); + cairo_move_to(cr, eap.center(X), eap.center(Y)); + cairo_line_to(cr, eap.initialPoint()[X], eap.initialPoint()[Y]); + cairo_move_to(cr, eap.center(X), eap.center(Y)); + cairo_line_to(cr, eap.finalPoint()[X], eap.finalPoint()[Y]); + cairo_stroke(cr); + D2<SBasis> sub_arc = eap.toSBasis(); + cairo_d2_sb(cr, sub_arc); + cairo_stroke(cr); + + delete eapp; + + } + + + void init_reverse() + { + init_common(); + time = 0; + + sliders.emplace_back(0, 1, 0, time, "t"); + handles.push_back(&(sliders[T_SLIDER])); + } + + void draw_reverse(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_reverse_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + + time = sliders[T_SLIDER].value(); + + EllipticalArc* eapp = static_cast<EllipticalArc*>(ea.reverse()); + EllipticalArc& eap = *eapp; + + cairo_set_line_width(cr, 0.8); + cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0); + + cairo_move_to(cr, eap.center(X), eap.center(Y)); + cairo_line_to(cr, eap.valueAt(time,X), eap.valueAt(time,Y)); + draw_circ(cr, eap.pointAt(time)); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.0, 1.0, 1.0, 1.0); + D2<SBasis> sub_arc = eap.toSBasis(); + cairo_d2_sb(cr, sub_arc); + cairo_stroke(cr); + + delete eapp; + + } + + + void init_np() + { + init_common(); + nph.pos = Point(10,10); + handles.push_back(&nph); + } + + void draw_np(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + if ( no_solution || point_overlap ) return; + + std::vector<double> times = ea.allNearestTimes( nph.pos ); + for (double time : times) + { + cairo_move_to(cr,nph.pos); + cairo_line_to( cr, ea.pointAt(time) ); + } + cairo_stroke(cr); + } + + + void init_derivative() + { + init_common(); + time = 0; + + sliders.emplace_back(0, 1, 0, time, "t"); + handles.push_back(&(sliders[T_SLIDER])); + } + + void draw_derivative(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_reverse_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + time = sliders[T_SLIDER].value(); + + Curve* der = ea.derivative(); + Point p = ea.pointAt(time); + Point v = der->pointAt(time) + p; + delete der; +// std::vector<Point> points = ea.pointAndDerivatives(time, 8); +// Point p = points[0]; +// Point v = points[1] + p; + cairo_move_to(cr, p); + cairo_line_to(cr, v); + cairo_stroke(cr); + } + + + void init_roots() + { + init_common(); + ph.pos = Point(10,10); + toggles.emplace_back("X/Y roots", true ); + + handles.push_back(&ph); + handles.push_back(&(toggles[X_Y_TOGGLE])); + } + + void draw_roots(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_roots_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + Dim2 DIM = toggles[X_Y_TOGGLE].on ? X : Y; + + Point p1[2] = { Point(ph.pos[X], -1000), + Point(-1000, ph.pos[Y]) }; + Point p2[2] = { Point(ph.pos[X], 1000), + Point(1000, ph.pos[Y]) }; + cairo_set_line_width(cr, 0.5); + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_move_to(cr, p1[DIM]); + cairo_line_to(cr, p2[DIM]); + + std::vector<double> times; + try + { + times = ea.roots(ph.pos[DIM], DIM); + *notify << "winding: " << ea.winding(ph.pos); + } + catch(Geom::Exception e) + { + std::cerr << e.what() << std::endl; + } + for (double time : times) + { + draw_handle(cr, ea.pointAt(time)); + } + cairo_stroke(cr); + } + + + void init_bounds() + { + init_common(); + } + + void draw_bounds(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + if ( no_solution || point_overlap ) return; + +// const char* msg[] = { "xmax", "xmin", "ymax", "ymin" }; + + Rect bb = ea.boundsFast(); + +// for ( unsigned int i = 0; i < limits.size(); ++i ) +// { +// std::cerr << "angle[" << i << "] = " << deg_from_rad(limits[i]) << std::endl; +// Point extreme = ea.pointAtAngle(limits[i]); +// draw_handle(cr, extreme ); +// draw_text(cr, extreme, msg[i]); +// } + cairo_rectangle( cr, bb.left(), bb.top(), bb.width(), bb.height() ); + cairo_stroke(cr); + } + + + void init_fitting() + { + init_common(); + } + + void draw_fitting(cairo_t * cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + if ( no_solution || point_overlap ) return; + + D2<SBasis> easb = ea.toSBasis(); + try + { + EllipticalArc earc; + if (!arc_from_sbasis(earc, easb, 0.1, 5)) return; + + D2<SBasis> arc = earc.toSBasis(); + arc[0] += Linear(50, 50); + cairo_d2_sb(cr, arc); + cairo_stroke(cr); + } + catch (RangeError const &e) + { + std::cerr << "conversion failure" << std::endl; + std::cerr << e.what() << std::endl; + return; + } + } + + void init_transform() + { + init_common(); + + double max = 4; + double min = -max; + + sliders.emplace_back(min, max, 0, 1, "TM0"); + sliders.emplace_back(min, max, 0, 0, "TM1"); + sliders.emplace_back(min, max, 0, 0, "TM2"); + sliders.emplace_back(min, max, 0, 1, "TM3"); + + handles.push_back(&(sliders[TM0_SLIDER])); + handles.push_back(&(sliders[TM1_SLIDER])); + handles.push_back(&(sliders[TM2_SLIDER])); + handles.push_back(&(sliders[TM3_SLIDER])); + } + + void draw_transform(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_transform_ctrl_geom(cr, notify, width, height); + if ( no_solution || point_overlap ) return; + + Affine TM(sliders[TM0_SLIDER].value(), sliders[TM1_SLIDER].value(), + sliders[TM2_SLIDER].value(), sliders[TM3_SLIDER].value(), + ea.center(X), ea.center(Y)); + + Affine tm( 1, 0, + 0, 1, + -ea.center(X), -ea.center(Y) ); + + + EllipticalArc* tea = static_cast<EllipticalArc*>(ea.transformed(tm)); + EllipticalArc* eat = NULL; + eat = static_cast<EllipticalArc*>(tea->transformed(TM)); + delete tea; + if (eat == NULL) + { + std::cerr << "elliptiarc transformation failed" << std::endl; + return; + } + + CairoPathSink ps(cr); + + //D2<SBasis> sb = eat->toSBasis(); + cairo_set_line_width(cr, 0.8); + cairo_set_source_rgba(cr, 0.8, 0.1, 0.1, 1.0); + //cairo_d2_sb(cr, sb); + ps.feed(*eat); + cairo_stroke(cr); + delete eat; + } + + void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int height, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + + sliders[RX_SLIDER].geometry(Point(50, height-120), 250); + sliders[RY_SLIDER].geometry(Point(50, height-85), 250); + sliders[ROT_ANGLE_SLIDER].geometry(Point(50, height-50), 180); + + toggles[LARGE_ARC_FLAG].bounds = Rect(Point(400, height-120), Point(540, height-95)); + toggles[SWEEP_FLAG].bounds = Rect(Point(400, height-70), Point(520, height-45)); + } + } + + void init_portion_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + Point from_sp = Point(600, height - 120); + Point to_sp = from_sp + Point(0,45); + double from_to_len = 100; + + sliders[FROM_SLIDER].geometry(from_sp, from_to_len); + sliders[TO_SLIDER].geometry(to_sp, from_to_len); + } + } + + void init_reverse_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + Point t_sp = Point(600, height - 120); + double t_len = 200; + + sliders[T_SLIDER].geometry(t_sp, t_len); + } + } + + void init_roots_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + Point T(600, height - 120); + toggles[X_Y_TOGGLE].bounds = Rect( T, T + Point(100,25) ); + } + } + + void init_transform_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + Point sp = Point(600, height - 140); + Point op = Point(0, 30); + double len = 200; + + sliders[TM0_SLIDER].geometry(sp, len); + sliders[TM1_SLIDER].geometry(sp += op, len); + sliders[TM2_SLIDER].geometry(sp += op, len); + sliders[TM3_SLIDER].geometry(sp += op, len); + } + } + + void init_menu() + { + handles.clear(); + sliders.clear(); + toggles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &EllipticalArcToy::draw_menu; + break; + case 'B': + init_common(); + draw_f = &EllipticalArcToy::draw_common; + break; + case 'C': + init_common(); + draw_f = &EllipticalArcToy::draw_comparison; + break; + case 'D': + draw_f = &EllipticalArcToy::draw_menu; + init_portion(); + draw_f = &EllipticalArcToy::draw_portion; + break; + case 'E': + init_reverse(); + draw_f = &EllipticalArcToy::draw_reverse; + break; + case 'F': + init_np(); + draw_f = &EllipticalArcToy::draw_np; + break; + case 'G': + init_derivative(); + draw_f = &EllipticalArcToy::draw_derivative; + break; + case 'H': + init_roots(); + draw_f = &EllipticalArcToy::draw_roots; + break; + case 'I': + init_bounds(); + draw_f = &EllipticalArcToy::draw_bounds; + break; + case 'J': + init_fitting(); + draw_f = &EllipticalArcToy::draw_fitting; + break; + case 'K': + init_transform(); + draw_f = &EllipticalArcToy::draw_transform; + break; + } + redraw(); + } + + + void draw_axes(cairo_t* cr) const + { + Point D(std::cos(ea.rotationAngle()), std::sin(ea.rotationAngle())); + Point Dx = (ea.ray(X) + 20) * D; + Point Dy = (ea.ray(Y) + 20) * D.cw(); + Point C(ea.center(X),ea.center(Y)); + Point LP = C - Dx; + Point RP = C + Dx; + Point UP = C - Dy; + Point DP = C + Dy; + + cairo_move_to(cr, LP[X], LP[Y]); + cairo_line_to(cr, RP[X], RP[Y]); + cairo_move_to(cr, UP[X], UP[Y]); + cairo_line_to(cr, DP[X], DP[Y]); + cairo_move_to(cr, 0, 0); + cairo_stroke(cr); + } + + void cairo_elliptiarc( cairo_t *cr, + double _cx, double _cy, + double _rx, double _ry, + double _sa, double _ea, + double _ra = 0 + ) const + { + double cos_rot_angle = std::cos(_ra); + double sin_rot_angle = std::sin(_ra); + cairo_matrix_t transform_matrix; + cairo_matrix_init( &transform_matrix, + _rx * cos_rot_angle, _rx * sin_rot_angle, + -_ry * sin_rot_angle, _ry * cos_rot_angle, + _cx, _cy + ); + cairo_save(cr); + cairo_transform(cr, &transform_matrix); + cairo_arc(cr, 0, 0, 1, _sa, _ea); + cairo_restore(cr); + } + + + std::pair<Point,Point> + calculate_ellipse_centers( Point _initial_point, Point _final_point, + double m_rx, double m_ry, + double m_rot_angle, + bool m_large_arc, bool m_sweep + ) + { + std::pair<Point,Point> result; + if ( _initial_point == _final_point ) + { + result.first = result.second = _initial_point; + return result; + } + + m_rx = std::fabs(m_rx); + m_ry = std::fabs(m_ry); + + Point d = _initial_point - _final_point; + + if ( are_near(m_rx, 0) || are_near(m_ry, 0) ) + { + result.first = result.second + = middle_point(_initial_point, _final_point); + return result; + } + + double sin_rot_angle = std::sin(m_rot_angle); + double cos_rot_angle = std::cos(m_rot_angle); + + + Affine m( cos_rot_angle, -sin_rot_angle, + sin_rot_angle, cos_rot_angle, + 0, 0 ); + + Point p = (d / 2) * m; + double rx2 = m_rx * m_rx; + double ry2 = m_ry * m_ry; + double rxpy = m_rx * p[Y]; + double rypx = m_ry * p[X]; + double rx2py2 = rxpy * rxpy; + double ry2px2 = rypx * rypx; + double num = rx2 * ry2; + double den = rx2py2 + ry2px2; + assert(den != 0); + double rad = num / den; + Point c(0,0); + if (rad > 1) + { + rad -= 1; + rad = std::sqrt(rad); + + if (m_large_arc == m_sweep) rad = -rad; + c = rad * Point(rxpy / m_ry, -rypx / m_rx); + + m[1] = -m[1]; + m[2] = -m[2]; + + c = c * m; + } + + d = middle_point(_initial_point, _final_point); + + result.first = c + d; + result.second = -c + d; + return result; + + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + EllipticalArcToy() {} + + private: + typedef void (EllipticalArcToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + bool no_solution, point_overlap; + bool degenerate; + PointHandle initial_point, final_point; + PointHandle nph, ph; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + EllipticalArc ea; + + double from_t; + double to_t; + double time; + +}; + + +const char* EllipticalArcToy::menu_items[] = +{ + "show this menu", + "basic", + "comparison", + "portion, pointAt", + "reverse, valueAt", + "nearest points", + "derivative", + "roots", + "bounding box", + "fitting", + "transformation" +}; + +const char EllipticalArcToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K' +}; + + +int main(int argc, char **argv) +{ + init( argc, argv, new EllipticalArcToy(), 850, 780 ); + return 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/toys/evolute.cpp b/src/toys/evolute.cpp new file mode 100644 index 0000000..a5deb08 --- /dev/null +++ b/src/toys/evolute.cpp @@ -0,0 +1,93 @@ +#include <2geom/basic-intersection.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +/* +jfb: I think the evolute goes to infinity at inflection points, in which case you cannot "join" the pieces by hand. +jfb: for the evolute toy, you could not only cut at inflection points, but event remove the domains where cross(dda,da)<c*|da|^3, where c is a small constant, as these points will be off screen anyway. +*/ + +class Evolution: public Toy { + PointSetHandle psh; +void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 0.5); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + + D2<SBasis> A(psh.asBezier()); + + D2<SBasis> dA = derivative(A); + D2<SBasis> ddA = derivative(dA); + SBasis crs = cross(ddA, dA); + cairo_d2_sb(cr, D2<SBasis>(Linear(0,1000), crs*(500./bounds_exact(crs)->extent()))); + vector<double> rts = roots(crs); + for(double rt : rts) { + draw_handle(cr, A(rt)); + } + cairo_d2_sb(cr, A); + cairo_stroke(cr); + Interval r(0, 1); + if(!rts.empty()) + r.setMax(rts[0]); + //if(rts[0] == 0) + //rts.erase(rts.begin(), rts.begin()+1); + A = portion(A, r.min(), r.max()); + dA = portion(dA, r.min(), r.max()); + ddA = portion(ddA, r.min(), r.max()); + crs = portion(crs, r.min(), r.max()); + cairo_stroke(cr); + Piecewise<SBasis> s = divide(dA[0]*dot(dA,dA), crs, 100, 1); + D2<Piecewise<SBasis> > ev4(Piecewise<SBasis>(A[0]) + divide(-dA[1]*dot(dA,dA), crs, 100, 1), + Piecewise<SBasis>(A[1]) + divide(dA[0]*dot(dA,dA), crs, 100, 1)); + cairo_d2_pw_sb(cr, ev4); + cairo_stroke(cr); + if(1) { + std::cout << "bnd" << *bounds_exact(dot(ev4, ev4)) << std::endl; + cairo_d2_pw_sb(cr, D2<Piecewise<SBasis> >(Piecewise<SBasis>(SBasis(Linear(0,1000))), dot(ev4, ev4)*1000)); + cairo_stroke(cr); + vector<double> rts = roots(dot(ev4, ev4)-1); + for(double rt : rts) { + std::cout << rt << std::endl; + draw_handle(cr, ev4(rt)); + } + } + cairo_set_source_rgba (cr, 1., 0., 1, 1); + + Toy::draw(cr, notify, width, height, save,timer_stream); +} +public: +Evolution (unsigned bez_ord) { + handles.push_back(&psh); + for(unsigned i = 0; i < bez_ord; i++) + psh.push_back(uniform()*400, uniform()*400); +} +}; + +int main(int argc, char **argv) { + unsigned bez_ord=5; + if(argc > 1) + sscanf(argv[1], "%d", &bez_ord); + init(argc, argv, new Evolution(bez_ord)); + + return 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/toys/filet-minion.cpp b/src/toys/filet-minion.cpp new file mode 100644 index 0000000..e94e354 --- /dev/null +++ b/src/toys/filet-minion.cpp @@ -0,0 +1,159 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/path-intersection.h> +#include <2geom/nearest-time.h> +#include <2geom/circle.h> + +#include <cstdlib> +#include <map> +#include <vector> +#include <algorithm> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> +using namespace Geom; +using namespace std; + +class IntersectDataTester: public Toy { + int nb_paths; + int nb_curves_per_path; + int degree; + + std::vector<PointSetHandle> paths_handles; + std::vector<Slider> sliders; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + std::vector<D2<SBasis> > pieces; + PathVector paths; + for (int i = 0; i < nb_paths; i++){ + paths.push_back(Path(paths_handles[i].pts[0])); + for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){ + D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree); + paths[i].append(c); + pieces.push_back(c); + } + } + + cairo_path(cr, paths); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + double r = sliders[0].value(); + + D2<SBasis> B = pieces[0]; + Piecewise<D2<SBasis> > offset_curve0 = Piecewise<D2<SBasis> >(pieces[0])+rot90(unitVector(derivative(pieces[0])))*(-r); + Piecewise<D2<SBasis> > offset_curve1 = Piecewise<D2<SBasis> >(pieces[1])+rot90(unitVector(derivative(pieces[1])))*(-r); + + //cairo_pw_d2_sb(cr, offset_curve0); + //cairo_pw_d2_sb(cr, offset_curve1); + //cairo_stroke(cr); + + + Path p0 = path_from_piecewise(offset_curve0, 0.1)[0]; + Path p1 = path_from_piecewise(offset_curve1, 0.1)[0]; + Crossings cs = crossings(p0, p1); + + + for(auto & c : cs) { + *notify << c.ta << ", " << c.tb << '\n'; + Point cp =p0(c.ta); + //draw_circ(cr, cp); + //cairo_stroke(cr); + double p0pt = nearest_time(cp, pieces[0]); + double p1pt = nearest_time(cp, pieces[1]); + Circle circ(cp[0], cp[1], r); + //cairo_arc(cr, circ.center(X), circ.center(Y), circ.ray(), 0, 2*M_PI); + + std::unique_ptr<EllipticalArc> eap( + circ.arc(pieces[0](p0pt), pieces[0](1), pieces[1](p1pt)) ); + D2<SBasis> easb = eap->toSBasis(); + cairo_d2_sb(cr, easb); + cairo_stroke(cr); + } + + Point ends[2]; + if (0) + for(int endi = 0; endi < 2; endi++) { + D2<SBasis> dist = pieces[endi]-pieces[0].at1(); + *notify << dist << "\n"; + vector<double> locs = roots(dot(dist,dist) - SBasis(r*r)); + for(double loc : locs) { + //draw_circ(cr, pieces[endi](locs[i])); + *notify << loc << ' '; + } + if(locs.size()) { + std::sort(locs.begin(), locs.end()); + if (endi) + ends[endi] = pieces[endi](locs[0]); + else + ends[endi] = pieces[endi](locs.back()); + draw_circ(cr, ends[endi]); + } + } + + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + IntersectDataTester(int paths, int curves_in_path, int degree) : + nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) { + for (int i = 0; i < nb_paths; i++){ + paths_handles.emplace_back(); + } + for(int i = 0; i < nb_paths; i++){ + for(int j = 0; j < (nb_curves_per_path*degree)+1; j++){ + paths_handles[i].push_back(uniform()*400, 100+ uniform()*300); + } + handles.push_back(&paths_handles[i]); + } + sliders.emplace_back(0.0, 100.0, 1, 30.0, "min radius"); + sliders.emplace_back(0.0, 100.0, 1, 0.0, "ray chooser"); + sliders.emplace_back(0.0, 100.0, 1, 0.0, "area chooser"); + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + } + + void first_time(int /*argc*/, char** /*argv*/) override { + + } +}; + +int main(int argc, char **argv) { + unsigned paths=1; + unsigned curves_in_path=2; + unsigned degree=3; + if(argc > 3) + sscanf(argv[3], "%d", °ree); + if(argc > 2) + sscanf(argv[2], "%d", &curves_in_path); + if(argc > 1) + sscanf(argv[1], "%d", &paths); + init(argc, argv, new IntersectDataTester(paths, curves_in_path, degree)); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/find-derivative.cpp b/src/toys/find-derivative.cpp new file mode 100644 index 0000000..e4c64f5 --- /dev/null +++ b/src/toys/find-derivative.cpp @@ -0,0 +1,500 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/angle.h> + +using std::vector; +using namespace Geom; +using namespace std; + +// Author: Johan Engelen, 2009 +// +// Shows how to find the locations on a path where the derivative is parallel to a certain vector. +//----------------------------------------------- + + +std::string angle_formatter(double angle) +{ + return default_formatter(decimal_round(deg_from_rad(angle),2)); +} + + + +class FindDerivatives : public Toy +{ + enum menu_item_t + { + SHOW_MENU = 0, + TEST_CREATE, + TEST_PROJECTION, + TEST_ORTHO, + TEST_DISTANCE, + TEST_POSITION, + TEST_SEG_BISEC, + TEST_ANGLE_BISEC, + TEST_COLLINEAR, + TEST_INTERSECTIONS, + TEST_COEFFICIENTS, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + }; + + enum toggle_label_t + { + }; + + enum slider_label_t + { + END_SHARED_SLIDERS = 0, + ANGLE_SLIDER = END_SHARED_SLIDERS, + A_COEFF_SLIDER = END_SHARED_SLIDERS, + B_COEFF_SLIDER, + C_COEFF_SLIDER + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + PointSetHandle curve_handle; + PointHandle sample_point; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &FindDerivatives::draw_menu; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + sliders.clear(); + toggles.clear(); + handles.clear(); + } + + + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/ ) + { + init_common_ctrl_geom(cr, width, height, notify); + } + + + void init_create() + { + init_common(); + + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + O.pos = Point(50, 400); + + sliders.emplace_back(0, 2*M_PI, 0, 0, "angle"); + sliders[ANGLE_SLIDER].formatter(&angle_formatter); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&O); + handles.push_back(&(sliders[ANGLE_SLIDER])); + } + + void draw_create(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_create_ctrl_geom(cr, notify, width, height); + + Line l1(p1.pos, p2.pos); + Line l2(O.pos, sliders[ANGLE_SLIDER].value()); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, O, "O"); + draw_label(cr, l1, "L(P1,P2)"); + draw_label(cr, l2, "L(O,angle)"); + } + + + void init_projection() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + O.pos = Point(50, 150); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&O); + } + + void draw_projection(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + LineSegment ls(p3.pos, p4.pos); + + Point np = projection(O.pos, l1); + LineSegment lsp = projection(ls, l1); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width(cr, 0.2); + draw_line(cr, l1); + draw_segment(cr, ls); + cairo_stroke(cr); + + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); + draw_segment(cr, lsp); + draw_handle(cr, lsp[0]); + draw_handle(cr, lsp[1]); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + draw_circ(cr, np); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0); + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, ls, "S"); + draw_label(cr, lsp, "prj(S)"); + draw_label(cr, O, "P"); + draw_text(cr, np, "prj(P)"); + + cairo_stroke(cr); + } + + void init_derivative() { + init_common(); + handles.push_back(&curve_handle); + handles.push_back(&sample_point); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + sample_point.pos = Geom::Point(250,300); + } + + void draw_derivative(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + + D2<SBasis> B = curve_handle.asBezier(); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + Point vector = sample_point.pos - Geom::Point(400,400); + cairo_move_to(cr, Geom::Point(400,400)); + cairo_line_to(cr, sample_point.pos); + cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8); + cairo_stroke(cr); + + // How to find location of points with certain derivative along a path: + D2<SBasis> deriv = derivative(B); + SBasis dotp = dot(deriv, rot90(vector)); + std::vector<double> sol = roots(dotp); + for (double i : sol) { + draw_handle(cr, B.valueAt(i)); // the solutions are in vector 'sol' + } + + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void init_find_tangents() { + init_common(); + handles.push_back(&curve_handle); + handles.push_back(&sample_point); + + toggles.emplace_back(" tangent / normal ", false); + handles.push_back(&(toggles[0])); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + sample_point.pos = Geom::Point(250,300); + Point toggle_sp( 30, 30); + toggles[0].bounds = Rect( toggle_sp, toggle_sp + Point(200,25) ); + } + + void draw_find_tangents(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + + D2<SBasis> B = curve_handle.asBezier(); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + std::vector<double> sol = toggles[0].on ? + find_tangents(sample_point.pos, B) + : find_normals(sample_point.pos, B); + for (double i : sol) { + draw_handle(cr, B.valueAt(i)); // the solutions are in vector 'sol' + draw_segment(cr, B.valueAt(i), sample_point.pos); + } + + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void init_ortho() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 50); + p4.pos = Point(150, 450); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + } + + void draw_ortho(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + Line l2 = make_orthogonal_line(p3.pos, l1); + Line l3 = make_parallel_line(p4.pos, l1); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + draw_line(cr, l3); + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, p3, "O1"); + draw_label(cr, p4, "O2"); + + draw_label(cr, l1, "L"); + draw_label(cr, l2, "L1 _|_ L"); + draw_label(cr, l3, "L2 // L"); + + } + + + + + void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + } + } + + void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180); + } + } + + void init_coefficients_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[A_COEFF_SLIDER].geometry(Point(50, height - 160), 400); + sliders[B_COEFF_SLIDER].geometry(Point(50, height - 110), 400); + sliders[C_COEFF_SLIDER].geometry(Point(50, height - 60), 400); + } + } + + + void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) + { + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); + } + + void draw_segment(cairo_t* cr, LineSegment const& ls) + { + draw_segment(cr, ls[0], ls[1]); + } + + void draw_ray(cairo_t* cr, Ray const& r) + { + double angle = r.angle(); + draw_segment(cr, r.origin(), angle, m_length); + } + + void draw_line(cairo_t* cr, Line const& l) + { + double angle = l.angle(); + draw_segment(cr, l.origin(), angle, m_length); + draw_segment(cr, l.origin(), angle, -m_length); + } + + void draw_label(cairo_t* cr, PointHandle const& ph, const char* label) + { + draw_text(cr, ph.pos+op, label); + } + + void draw_label(cairo_t* cr, Line const& l, const char* label) + { + draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label); + } + + void draw_label(cairo_t* cr, LineSegment const& ls, const char* label) + { + draw_text(cr, middle_point(ls[0], ls[1])+op, label); + } + + void draw_label(cairo_t* cr, Ray const& r, const char* label) + { + Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30))); + if (L2(r.origin() - prj) < 100) + { + prj = r.origin() + 100*r.vector(); + } + draw_text(cr, prj+op, label); + } + + void init_menu() + { + handles.clear(); + sliders.clear(); + toggles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/ ) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &FindDerivatives::draw_menu; + break; + case 'B': + init_derivative(); + draw_f = &FindDerivatives::draw_derivative; + break; + case 'C': + init_find_tangents(); + draw_f = &FindDerivatives::draw_find_tangents; + break; + case 'D': + init_ortho(); + draw_f = &FindDerivatives::draw_ortho; + break; + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + FindDerivatives() + { + op = Point(5,5); + } + + private: + typedef void (FindDerivatives::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + PointHandle p1, p2, p3, p4, p5, p6, O; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + Point op; + double m_width, m_height, m_length; + +}; // end class FindDerivatives + + +const char* FindDerivatives::menu_items[] = +{ + "show this menu", + "derivative matching on curve", + "find normals", + "find tangents" +}; + +const char FindDerivatives::keys[] = +{ + 'A', 'B', 'C', 'D' +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new FindDerivatives()); + return 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/gear.cpp b/src/toys/gear.cpp new file mode 100644 index 0000000..d2f2de2 --- /dev/null +++ b/src/toys/gear.cpp @@ -0,0 +1,317 @@ +/* + * GearToy - displays involute gears + * + * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com> + * Copyright 2006 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 <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +class Gear { +public: + // pitch circles touch on two properly meshed gears + // all measurements are taken from the pitch circle + double pitch_diameter() {return (_number_of_teeth * _module) / M_PI;} + double pitch_radius() {return pitch_diameter() / 2.0;} + void pitch_radius(double R) {_module = (2 * M_PI * R) / _number_of_teeth;} + + // base circle serves as the basis for the involute toothe profile + double base_diameter() {return pitch_diameter() * cos(_pressure_angle);} + double base_radius() {return base_diameter() / 2.0;} + + // diametrical pitch + double diametrical_pitch() {return _number_of_teeth / pitch_diameter();} + + // height of the tooth above the pitch circle + double addendum() {return 1.0 / diametrical_pitch();} + // depth of the tooth below the pitch circle + double dedendum() {return addendum() + _clearance;} + + // root circle specifies the bottom of the fillet between teeth + double root_radius() {return pitch_radius() - dedendum();} + double root_diameter() {return root_radius() * 2.0;} + + // outer circle is the outside diameter of the gear + double outer_radius() {return pitch_radius() + addendum();} + double outer_diameter() {return outer_radius() * 2.0;} + + // angle covered by the tooth on the pitch circle + double tooth_thickness_angle() {return M_PI / _number_of_teeth;} + + Geom::Point centre() {return _centre;} + void centre(Geom::Point c) {_centre = c;} + + double angle() {return _angle;} + void angle(double a) {_angle = a;} + + int number_of_teeth() {return _number_of_teeth;} + + Geom::Path path(); + Gear spawn(int N, double a); + + Gear(int n, double m, double phi) { + _number_of_teeth = n; + _module = m; + _pressure_angle = phi; + _clearance = 0.0; + _angle = 0.0; + _centre = Geom::Point(0.0,0.0); + } +private: + int _number_of_teeth; + double _pressure_angle; + double _module; + double _clearance; + double _angle; + Geom::Point _centre; + D2<SBasis> _involute(double start, double stop) { + D2<SBasis> B; + D2<SBasis> I; + Linear bo = Linear(start,stop); + + B[0] = cos(bo,2); + B[1] = sin(bo,2); + + I = B - Linear(0,1) * derivative(B); + I = I*base_radius() + _centre; + return I; + } + D2<SBasis> _arc(double start, double stop, double R) { + D2<SBasis> B; + Linear bo = Linear(start,stop); + + B[0] = cos(bo,2); + B[1] = sin(bo,2); + + B = B*R + _centre; + return B; + } + // angle of the base circle used to create the involute to a certain radius + double involute_swath_angle(double R) { + if (R <= base_radius()) return 0.0; + return sqrt(R*R - base_radius()*base_radius())/base_radius(); + } + + // angle of the base circle between the origin of the involute and the intersection on another radius + double involute_intersect_angle(double R) { + if (R <= base_radius()) return 0.0; + return (sqrt(R*R - base_radius()*base_radius())/base_radius()) - acos(base_radius()/R); + } +}; + +void makeContinuous(D2<SBasis> &a, Point const b) { + for(unsigned d=0;d<2;d++) + a[d][0][0] = b[d]; +} + +Geom::Path Gear::path() { + Geom::Path pb; + + // angle covered by a full tooth and fillet + double tooth_rotation = 2.0 * tooth_thickness_angle(); + // angle covered by an involute + double involute_advance = involute_intersect_angle(outer_radius()) - involute_intersect_angle(root_radius()); + // angle covered by the tooth tip + double tip_advance = tooth_thickness_angle() - (2 * (involute_intersect_angle(outer_radius()) - involute_intersect_angle(pitch_radius()))); + // angle covered by the toothe root + double root_advance = (tooth_rotation - tip_advance) - (2.0 * involute_advance); + // begin drawing the involute at t if the root circle is larger than the base circle + double involute_t = involute_swath_angle(root_radius())/involute_swath_angle(outer_radius()); + + //rewind angle to start drawing from the leading edge of the tooth + double first_tooth_angle = _angle - ((0.5 * tip_advance) + involute_advance); + + Geom::Point prev; + for (int i=0; i < _number_of_teeth; i++) + { + double cursor = first_tooth_angle + (i * tooth_rotation); + + D2<SBasis> leading_I = compose(_involute(cursor, cursor + involute_swath_angle(outer_radius())), Linear(involute_t,1)); + if(i != 0) makeContinuous(leading_I, prev); + pb.append(SBasisCurve(leading_I)); + cursor += involute_advance; + prev = leading_I.at1(); + + D2<SBasis> tip = _arc(cursor, cursor+tip_advance, outer_radius()); + makeContinuous(tip, prev); + pb.append(SBasisCurve(tip)); + cursor += tip_advance; + prev = tip.at1(); + + cursor += involute_advance; + D2<SBasis> trailing_I = compose(_involute(cursor, cursor - involute_swath_angle(outer_radius())), Linear(1,involute_t)); + makeContinuous(trailing_I, prev); + pb.append(SBasisCurve(trailing_I)); + prev = trailing_I.at1(); + + if (base_radius() > root_radius()) { + Geom::Point leading_start = trailing_I.at1(); + Geom::Point leading_end = (root_radius() * unit_vector(leading_start - _centre)) + _centre; + prev = leading_end; + pb.appendNew<LineSegment>(leading_end); + } + + D2<SBasis> root = _arc(cursor, cursor+root_advance, root_radius()); + makeContinuous(root, prev); + pb.append(SBasisCurve(root)); + cursor += root_advance; + prev = root.at1(); + + if (base_radius() > root_radius()) { + Geom::Point trailing_start = root.at1(); + Geom::Point trailing_end = (base_radius() * unit_vector(trailing_start - _centre)) + _centre; + pb.appendNew<LineSegment>(trailing_end); + prev = trailing_end; + } + } + + return pb; +} +Gear Gear::spawn(int N, double a) { + Gear gear(N, _module, _pressure_angle); + double dist = gear.pitch_radius() + pitch_radius(); + gear.centre(Geom::Point::polar(a, dist) + _centre); + double new_angle = 0.0; + if (gear.number_of_teeth() % 2 == 0) + new_angle -= gear.tooth_thickness_angle(); + new_angle -= (_angle) * (pitch_radius() / gear.pitch_radius()); + new_angle += (a) * (pitch_radius() / gear.pitch_radius()); + gear.angle(new_angle + a); + return gear; +} + +class GearToy: public Toy { + public: + PointSetHandle hand; + GearToy () { + for(unsigned i = 0; i < 4; i++) + hand.pts.emplace_back(uniform()*400, uniform()*400); + handles.push_back(&hand); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 0.5); + + //Geom::Point centre = Geom::Point(width/2,height/2); + /* draw cross hairs + double dominant_dim = std::max(width,height); + double minor_dim = std::min(width,height); + for(unsigned i = 1; i < 2; i++) { + cairo_move_to(cr, centre[0]-minor_dim/4, centre[1]); + cairo_line_to(cr, centre[0]+minor_dim/4, centre[1]); + cairo_move_to(cr, centre[0], centre[1]-minor_dim/4); + cairo_line_to(cr, centre[0], centre[1]+minor_dim/4); + } + cairo_stroke(cr);*/ + + double pressure_angle = (hand.pts[3][0] / 10) * M_PI / 180; + Gear gear(int(hand.pts[2][0] / 10),200.0,pressure_angle); + Geom::Point gear_centre = hand.pts[1]; + gear.pitch_radius(Geom::distance(gear_centre, hand.pts[0])); + gear.angle(atan2(hand.pts[0] - gear_centre)); + gear.centre(gear_centre); + + // draw radii + cairo_new_sub_path(cr); + cairo_arc(cr, gear_centre[0], gear_centre[1], gear.base_radius(), 0, M_PI*2); + cairo_set_source_rgba (cr, 0., 0., 0.5, 1); + cairo_stroke(cr); + + cairo_new_sub_path(cr); + cairo_arc(cr, gear_centre[0], gear_centre[1], gear.pitch_radius(), 0, M_PI*2); + cairo_set_source_rgba (cr, 0.5, 0., 0., 1); + cairo_stroke(cr); + + cairo_new_sub_path(cr); + cairo_arc(cr, gear_centre[0], gear_centre[1], gear.outer_radius(), 0, M_PI*2); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_stroke(cr); + + cairo_new_sub_path(cr); + cairo_arc(cr, gear_centre[0], gear_centre[1], gear.root_radius(), 0, M_PI*2); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_stroke(cr); + + //draw gear + Geom::Path p = gear.path(); + cairo_path(cr, p); + cairo_set_source_rgba (cr, 0., 0., 0., 0.5); + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); + + Gear gear2 = gear.spawn(5, -2.0 * M_PI / 8.0); + Geom::Path p2 = gear2.path(); + cairo_path(cr, p2); + cairo_set_source_rgba (cr, 0., 0., 0., 0.5); + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); + + Gear gear3 = gear2.spawn(8, 0.0 * M_PI / 8.0); + Geom::Path p3 = gear3.path(); + cairo_path(cr, p3); + cairo_set_source_rgba (cr, 0., 0., 0., 0.5); + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); + + Gear gear4 = gear.spawn(6, 3.0 * M_PI / 4.0); + Geom::Path p4 = gear4.path(); + cairo_path(cr, p4); + cairo_set_source_rgba (cr, 0., 0., 0., 0.5); + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); + + *notify << "angle = " << gear.angle(); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new GearToy()); + + return 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/toys/hatches.cpp b/src/toys/hatches.cpp new file mode 100644 index 0000000..28fe440 --- /dev/null +++ b/src/toys/hatches.cpp @@ -0,0 +1,386 @@ +#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <cstdlib>
+#include <vector>
+using std::vector;
+using namespace Geom;
+
+#define SIZE 4
+#define NB_SLIDER 8
+
+//------------------------------------------------
+// Some goodies to navigate through curve's levels.
+//------------------------------------------------
+struct LevelCrossing{
+ Point pt;
+ double t;
+ bool sign;
+ bool used;
+};
+struct LevelCrossingOrder {
+ bool operator()(LevelCrossing a, LevelCrossing b) {
+ return a.pt[Y] < b.pt[Y];
+ }
+};
+
+typedef std::vector<LevelCrossing> LevelCrossings;
+
+class LevelsCrossings: public std::vector<LevelCrossings>{
+public:
+ LevelsCrossings():std::vector<LevelCrossings>(){};
+ LevelsCrossings(std::vector<std::vector<double> > const ×,
+ Piecewise<D2<SBasis> > const &f,
+ Piecewise<SBasis> const &dx){
+ for (const auto & time : times){
+ LevelCrossings lcs;
+ for (double j : time){
+ LevelCrossing lc;
+ lc.pt = f.valueAt(j);
+ lc.t = j;
+ lc.sign = ( dx.valueAt(j)>0 );
+ lc.used = false;
+ lcs.push_back(lc);
+ }
+ std::sort(lcs.begin(), lcs.end(), LevelCrossingOrder());
+ //TODO: reverse all "in" flag if we had the wrong orientation!
+ push_back(lcs);
+ }
+ }
+ void flipInOut(){
+ for (unsigned i=0; i<size(); i++){
+ for (auto & j : (*this)){
+ j.sign = !j.sign;
+ }
+ }
+ }
+ void findFirstUnused(unsigned &level, unsigned &idx){
+ level = size();
+ idx = 0;
+ for (unsigned i=0; i<size(); i++){
+ for (unsigned j=0; j<(*this)[i].size(); j++){
+ if (!(*this)[i][j].used){
+ level = i;
+ idx = j;
+ return;
+ }
+ }
+ }
+ }
+ //set indexes to point to the next point in the "snake walk"
+ //follow_level's meaning:
+ // 0=yes upward
+ // 1=no, last move was upward,
+ // 2=yes downward
+ // 3=no, last move was downward.
+ void step(unsigned &level, unsigned &idx, int &direction){
+ std::cout << "Entering step: "<<level<<","<<idx<<", dir="<< direction<<"\n";
+
+ if ( direction % 2 == 0 ){
+ if (direction == 0) {
+ if ( idx >= (*this)[level].size()-1 || (*this)[level][idx+1].used ) {
+ level = size();
+ std::cout << "max end of level reached...\n";
+ return;
+ }
+ idx += 1;
+ }else{
+ if ( idx <= 0 || (*this)[level][idx-1].used ) {
+ level = size();
+ std::cout << "min end of level reached...\n";
+ return;
+ }
+ idx -= 1;
+ }
+ direction += 1;
+ std::cout << "exit with: "<<level<<","<<idx<<", dir="<< direction<<"\n";
+ return;
+ }
+ double t = (*this)[level][idx].t;
+ double sign = ((*this)[level][idx].sign ? 1 : -1);
+ double next_t = t;
+ level += 1;
+ direction = (direction + 1)%4;
+ if (level == size()){
+ std::cout << "max level reached\n";
+ return;
+ }
+ for (unsigned j=0; j<(*this)[level].size(); j++){
+ double tj = (*this)[level][j].t;
+ if ( sign*(tj-t) > 0 ){
+ if( next_t == t || sign*(tj-next_t)<0 ){
+ next_t = tj;
+ idx = j;
+ }
+ }
+ }
+ if ( next_t == t ){//not found.
+ level = size();
+ std::cout << "no next time found\n";
+ return;
+ }
+ //TODO: time is periodic!!!
+ //TODO: allow several components.
+ if ( (*this)[level][idx].used ) {
+ level = size();
+ std::cout << " reached a point already used\n";
+ return;
+ }
+ std::cout << "exit with: "<<level<<","<<idx<<"\n";
+ return;
+ }
+};
+
+
+//------------------------------------------------
+// Generate the levels with random, growth...
+//------------------------------------------------
+std::vector<double>generateLevels(Interval const &domain,
+ double const width,
+ double const growth,
+ double randomness){
+ std::vector<double> result;
+ std::srand(0);
+ double x = domain.min() + width/2;
+ double step = width;
+ while (x<domain.max()){
+ result.push_back(x);
+ double rdm = 1+ ( (rand() % 100) - 50) /100.*randomness;
+ x+= step*growth*rdm;
+ step*=growth;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------------
+// Walk through the intersections to create linear hatches
+//-------------------------------------------------------
+std::vector<Point> linearSnake(Piecewise<D2<SBasis> > const &f, double dy,double growth, double rdmness){
+
+ std::vector<Point> result;
+
+ Piecewise<SBasis> x = make_cuts_independent(f)[X];
+ //Rque: derivative is computed twice in the 2 lines below!!
+ Piecewise<SBasis> dx = derivative(x);
+ OptInterval range = bounds_exact(x);
+ //TODO: test range non emptyness!!
+ std::vector<double> levels = generateLevels((*range), dy, growth, rdmness);
+ std::vector<std::vector<double> > times;
+ times = multi_roots(x,levels);
+
+//TODO: fix multi_roots!!!*****************************************
+//remove doubles :-(
+ std::vector<std::vector<double> > cleaned_times(levels.size(),std::vector<double>());
+ for (unsigned i=0; i<times.size(); i++){
+ if ( times[i].size()>0 ){
+ double last_t = times[i][0]-1;//ugly hack!!
+ for (unsigned j=0; j<times[i].size(); j++){
+ if (times[i][j]-last_t >0.000001){
+ last_t = times[i][j];
+ cleaned_times[i].push_back(last_t);
+ }
+ }
+ }
+ }
+ times = cleaned_times;
+ for (unsigned i=0; i<times.size(); i++){
+ std::cout << "roots on level "<<i<<": ";
+ for (double j : times){
+ std::cout << j <<" ";
+ }
+ std::cout <<"\n";
+ }
+//*******************************************************************
+ LevelsCrossings lscs(times,f,dx);
+ unsigned i,j;
+ lscs.findFirstUnused(i,j);
+ while ( i < lscs.size() ){
+ int dir = 0;
+ while ( i < lscs.size() ){
+ result.push_back(lscs[i][j].pt);
+ lscs[i][j].used = true;
+ lscs.step(i,j, dir);
+ }
+ //TODO: handle "non convex cases" where hatches have to be restarted at some point.
+ //This needs some care in linearSnake->smoothSnake.
+ //
+ lscs.findFirstUnused(i,j);
+ }
+ return result;
+}
+
+//-------------------------------------------------------
+// Smooth the linear hatches according to params...
+//-------------------------------------------------------
+Piecewise<D2<SBasis> > smoothSnake(std::vector<Point> const &linearSnake,
+ double scale_bf = 1, double scale_bb = 1,
+ double scale_tf = 1, double scale_tb = 1){
+
+ if (linearSnake.size()<2) return Piecewise<D2<SBasis> >();
+ bool is_top = true;
+ Point last_pt = linearSnake[0];
+ Point last_hdle = linearSnake[0];
+ Path result(last_pt);
+ unsigned i=1;
+ while( i+1<linearSnake.size() ){
+ Point pt0 = linearSnake[i];
+ Point pt1 = linearSnake[i+1];
+ Point new_pt = (pt0+pt1)/2;
+ double scale = (is_top ? scale_tf : scale_bf );
+ Point new_hdle = new_pt+(pt0-new_pt)*scale;
+
+ result.appendNew<CubicBezier>(last_hdle,new_hdle,new_pt);
+
+ last_pt = new_pt;
+ scale = (is_top ? scale_tb : scale_bb );
+ last_hdle = new_pt+(pt1-new_pt)*scale;
+ i+=2;
+ is_top = !is_top;
+ }
+ if ( i<linearSnake.size() )
+ result.appendNew<CubicBezier>(last_hdle,linearSnake[i],linearSnake[i]);
+ return result.toPwSb();
+}
+
+//-------------------------------------------------------
+// Bend a path...
+//-------------------------------------------------------
+
+Piecewise<D2<SBasis> > bend(Piecewise<D2<SBasis> > const &f, Piecewise<SBasis> bending){
+ D2<Piecewise<SBasis> > ff = make_cuts_independent(f);
+ ff[X] += compose(bending, ff[Y]);
+ return sectionize(ff);
+}
+
+//-------------------------------------------------------
+// The toy!
+//-------------------------------------------------------
+class HatchesToy: public Toy {
+
+ PointHandle adjuster[NB_SLIDER];
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+ for(unsigned i=0; i<NB_SLIDER; i++){
+ adjuster[i].pos[X] = 30+i*20;
+ if (adjuster[i].pos[Y]<100) adjuster[i].pos[Y] = 100;
+ if (adjuster[i].pos[Y]>400) adjuster[i].pos[Y] = 400;
+ cairo_move_to(cr, Point(30+i*20,100));
+ cairo_line_to(cr, Point(30+i*20,400));
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ }
+ double hatch_width = (400-adjuster[0].pos[Y])/300.*50;
+ double scale_topfront = (250-adjuster[1].pos[Y])/150.*5;
+ double scale_topback = (250-adjuster[2].pos[Y])/150.*5;
+ double scale_botfront = (250-adjuster[3].pos[Y])/150.*5;
+ double scale_botback = (250-adjuster[4].pos[Y])/150.*5;
+ double growth = 1+(250-adjuster[5].pos[Y])/150.*.1;
+ double rdmness = 1+(400-adjuster[6].pos[Y])/300.*.9;
+ double bend_amount = (250-adjuster[7].pos[Y])/300.*100.;
+
+ b1_handle.pts.back() = b2_handle.pts.front();
+ b1_handle.pts.front() = b2_handle.pts.back();
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+
+ {
+ cairo_save(cr);
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_d2_sb(cr, B1);
+ cairo_d2_sb(cr, B2);
+ cairo_restore(cr);
+ }
+
+ Piecewise<D2<SBasis> >B;
+ B.concat(Piecewise<D2<SBasis> >(B1));
+ B.continuousConcat(Piecewise<D2<SBasis> >(B2));
+
+ Piecewise<SBasis> bending = Piecewise<SBasis>(shift(Linear(bend_amount),1));
+ //TODO: test optrect non empty!!
+ bending.setDomain((*bounds_exact(B))[Y]);
+ Piecewise<D2<SBasis> >bentB = bend(B, bending);
+
+ std::vector<Point> snakePoints;
+ snakePoints = linearSnake(bentB, hatch_width, growth, rdmness);
+ Piecewise<D2<SBasis> >smthSnake = smoothSnake(snakePoints,
+ scale_topfront,
+ scale_topback,
+ scale_botfront,
+ scale_botback);
+
+ smthSnake = bend(smthSnake, -bending);
+ cairo_pw_d2_sb(cr, smthSnake);
+ cairo_set_line_width (cr, 1.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ if ( snakePoints.size() > 0 ){
+ Path snake(snakePoints.front());
+ for (unsigned i=1; i<snakePoints.size(); i++){
+ snake.appendNew<LineSegment>(snakePoints[i]);
+ }
+ //cairo_pw_d2_sb(cr, snake.toPwSb() );
+ }
+
+ //cairo_pw_d2_sb(cr, B);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0.7, 0.2, 0., 1);
+ cairo_stroke(cr);
+
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ HatchesToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ b1_handle.pts[0] = Geom::Point(400,300);
+ b1_handle.pts[1] = Geom::Point(400,400);
+ b1_handle.pts[2] = Geom::Point(100,400);
+ b1_handle.pts[3] = Geom::Point(100,300);
+
+ b2_handle.pts[0] = Geom::Point(100,300);
+ b2_handle.pts[1] = Geom::Point(100,200);
+ b2_handle.pts[2] = Geom::Point(400,200);
+ b2_handle.pts[3] = Geom::Point(400,300);
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ for(unsigned i = 0; i < NB_SLIDER; i++) {
+ adjuster[i].pos = Geom::Point(30+i*20,250);
+ handles.push_back(&(adjuster[i]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new HatchesToy);
+ return 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/toys/implicit-toy.cpp b/src/toys/implicit-toy.cpp new file mode 100644 index 0000000..c90c082 --- /dev/null +++ b/src/toys/implicit-toy.cpp @@ -0,0 +1,510 @@ +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/geom.h> +#include <2geom/d2.h> +#include <2geom/polynomial.h> +#include <2geom/sbasis-poly.h> +#include <2geom/transforms.h> + +#include <2geom/symbolic/implicit.h> + +#include <aa.h> + +#include <algorithm> +#include <ctime> +#include <functional> + + +using namespace Geom; + + + +struct PtLexCmp{ + bool operator()(const Point &a, const Point &b) { + return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1])); + } +}; + +//typedef AAF (*implicit_curve_t)(AAF, AAF); +typedef std::function<AAF (AAF const&, AAF const&)> implicit_curve_t; + +// draw ax + by + c = 0 +void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) +{ + vector<Geom::Point> result; + Point resultp; + if(intersects == line_intersection(Point(1, 0), r.left(), + n, c, + resultp) && r[1].contains(resultp[1])) + result.push_back(resultp); + if(intersects == line_intersection(Point(1, 0), r.right(), + n, c, + resultp) && r[1].contains(resultp[1])) + result.push_back(resultp); + if(intersects == line_intersection(Point(0, 1), r.top(), + n, c, + resultp) && r[0].contains(resultp[0])) + result.push_back(resultp); + if(intersects == line_intersection(Point(0, 1), r.bottom(), + n, c, + resultp) && r[0].contains(resultp[0])) + result.push_back(resultp); + if(result.size() > 2) { + std::sort(result.begin(), result.end(), PtLexCmp()); + vector<Geom::Point>::iterator new_end = std::unique(result.begin(), result.end()); + result.resize(new_end-result.begin()); + } + if(result.size() == 2) + { + cairo_move_to(cr, result[0]); + cairo_line_to(cr, result[1]); + cairo_stroke(cr); + } +} + +OptRect tighten(Rect const&r, Point n, Interval lu) +{ + vector<Geom::Point> result; + Point resultp; + for(int i = 0; i < 4; i++) + { + Point cnr = r.corner(i); + double z = dot(cnr, n); + if ((z > lu[0]) && (z < lu[1])) + result.push_back(cnr); + } + for(int i = 0; i < 2; i++) + { + double c = lu[i]; + if(intersects == line_intersection(Point(1, 0), r.left(), + n, c, + resultp) && r[1].contains(resultp[1])) + result.push_back(resultp); + if(intersects == line_intersection(Point(1, 0), r.right(), + n, c, + resultp) && r[1].contains(resultp[1])) + result.push_back(resultp); + if(intersects == line_intersection(Point(0, 1), r.top(), + n, c, + resultp) && r[0].contains(resultp[0])) + result.push_back(resultp); + if(intersects == line_intersection(Point(0, 1), r.bottom(), + n, c, + resultp) && r[0].contains(resultp[0])) + result.push_back(resultp); + } + if(result.size() < 2) + return OptRect; + Rect nr(result[0], result[1]); + for(size_t i = 2; i < result.size(); i++) + { + nr.expandTo(result[i]); + } + return intersect(nr, r); +} + +static const unsigned int DEG = 5; +double bvp[DEG+1][DEG+1] + = {{-1, 0.00945115, -4.11799e-05, 1.01365e-07, -1.35037e-10, 7.7868e-14}, + {0.00837569, -6.24676e-05, 1.96093e-07, -3.09683e-10, 1.95681e-13, 0}, + {-2.39448e-05, 1.3331e-07, -2.65787e-10, 1.96698e-13, 0, 0}, + {2.76173e-08, -1.01069e-10, 9.88596e-14, 0, 0, 0}, + {-1.43584e-11, 2.48433e-14, 0, 0, 0, 0}, {2.49723e-15, 0, 0, 0, 0, 0}}; + + +AAF trial_eval(AAF const& x, AAF const& y) { +// AAF x = _x/100; +// AAF y = _y/100; + //return x*x - 1; + //return y - pow(x,3); + //return y - pow_sample_based(x,2.5); + //return y - log_sample_based(x); + //return y - log(x); + //return y - exp_sample_based(x*log(x)); + //return y - sqrt(sin(x)); + //return sqrt(y)*x - sqrt(x) - y - 1; + //return y-1/x; + //return exp(x)-y; + //return sin(x)-y; + //return exp_sample_based(x)-y; + //return atan(x)-y; + //return atan_sample_based(x)-y; + //return atanh(x)-y; + //return x*y; + //return 4*x+3*y-1; + //return x*x + y*y - 1; + //return sin(x*y) + cos(pow(x, 3)) - atan(x); + //return pow((x*x + y*y), 2) - (x*x-y*y); + //return x*x-y; + //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5; +// return -120.75 +(-64.4688 +(-16.6875 +(0.53125 -0.00390625*y)*y)*y)*y +// + (-15.9375 + ( 1.5 +( 4.375 -0.0625*y)*y)*y +// + (17 +( 9.5 -0.375*y)*y + (2 + -1*y -1*x)*x)*x)*x; + +// AAF v(0); +// for (size_t i = DEG; i > 0; --i) +// { +// AAF vy(0); +// for (size_t j = DEG - i; j > 0; --j) +// { +// vy += bvp[i][j]; +// vy *= y; +// } +// vy += bvp[i][0]; +// v += vy; +// v *= x; +// } +// AAF vy(0); +// for (size_t j = DEG; j > 0; --j) +// { +// vy += bvp[0][j]; +// vy *= y; +// } +// vy += bvp[0][0]; +// v += vy; +// return v; + + int i = DEG; + int j = DEG - i; + AAF vy(bvp[i][j]); + --j; + for (; j >= 0; --j) + { + vy *= y; + vy += bvp[DEG][j]; + } + AAF v(vy); + --i; + for (; i >= 0; --i) + { + int j = DEG - i; + AAF vy(bvp[i][j]); + --j; + for (; j >= 0; --j) + { + vy *= y; + vy += bvp[i][j]; + } + v *= x; + v += vy; + } + return v; + +// return +// -1 +( 0.00945115 +( -4.11799e-05 +( 1.01365e-07 +( -1.35037e-10 + 7.7868e-14*y)*y)*y)*y)*y +// + (0.00837569 +( -6.24676e-05 +( 1.96093e-07 +( -3.09683e-10 + 1.95681e-13*y)*y)*y)*y +// + (-2.39448e-05 +( 1.3331e-07 +( -2.65787e-10 + 1.96698e-13*y)*y)*y +// + (2.76173e-08 +( -1.01069e-10 + 9.88596e-14*y)*y +// + (-1.43584e-11 + 2.48433e-14*y + 2.49723e-15*x)*x)*x)*x)*x; +} + + + +double max_modulus (SL::MVPoly2 const& p) +{ + double a, m = 1; + + for (size_t i = 0; i < p.get_poly().size(); ++i) + for (double j : p) + { + a = std::abs(j); + if (m < a) m = a; + } + return m; +} + +void poly_to_mvpoly1(SL::MVPoly1& p, Geom::Poly const& q) +{ + for (size_t i = 0; i < q.size(); ++i) + { + p.coefficient(i, q[i]); + } + p.normalize(); +} + +void make_implicit_curve (SL::MVPoly2& ic, D2<SBasis> const& pc) +{ + Geom::Poly pc0 = sbasis_to_poly(pc[0]); + Geom::Poly pc1 = sbasis_to_poly(pc[1]); + +// std::cerr << "parametrization: \n"; +// std::cerr << "pc0 = " << pc0 << std::endl; +// std::cerr << "pc1 = " << pc1 << "\n\n"; + + SL::MVPoly1 f, g; + poly_to_mvpoly1(f, pc0); + poly_to_mvpoly1(g, pc1); + +// std::cerr << "parametrization: \n"; +// std::cerr << "f = " << f << std::endl; +// std::cerr << "g = " << g << "\n\n"; + + Geom::SL::basis_type b; + microbasis(b, f, g); + + Geom::SL::MVPoly3 p, q; + basis_to_poly(p, b[0]); + basis_to_poly(q, b[1]); + +// std::cerr << "generators as polynomial in R[t,x,y] : \n"; +// std::cerr << "p = " << p << std::endl; +// std::cerr << "q = " << q << "\n\n"; + + + Geom::SL::Matrix<Geom::SL::MVPoly2> B = make_bezout_matrix(p, q); + ic = determinant_minor(B); + ic.normalize(); + double m = max_modulus(ic); + ic /= m; + +// std::cerr << "Bezout matrix: (entries are bivariate polynomials) \n"; +// std::cerr << "B = " << B << "\n\n"; +// std::cerr << "determinant: \n"; +// std::cerr << "r(x, y) = " << ic << "\n\n"; + +} + +//namespace Geom{ namespace SL{ +// +//template<> +//struct zero<AAF, false> +//{ +// AAF operator() () const +// { +// return AAF(0); +// } +//}; +// +//} } + +class ImplicitToy : public Toy +{ + bool contains_zero (implicit_curve_t const& eval, + Rect r, double w=1e-5) + { + ++iters; + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + AAF f = eval(x, y); + double a = f.index_coeff(x.get_index(0)) / x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0)) / y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if (f.straddles_zero()) + { + if ((r.width() > w) || (r.height() > w)) + { + Point c = r.midpoint(); + Rect oldr = r; + if (out) r = *out; + // Three possibilities: + // 1) the trim operation buys us enough that we should just iterate + if (1 && (r.area() < oldr.area()*0.25)) + { + return contains_zero(eval, r, w); + } + // 2) one dimension is significantly smaller + else if (1 && (r[1].extent() < oldr[1].extent()*0.5)) + { + return contains_zero (eval, + Rect(Interval(r.left(), r.right()), + Interval(r.top(), c[1])), w) + || contains_zero (eval, + Rect(Interval(r.left(), r.right()), + Interval(c[1], r.bottom())), w); + } + else if (1 && (r[0].extent() < oldr[0].extent()*0.5)) + { + return contains_zero (eval, + Rect(Interval(r.left(), c[0]), + Interval(r.top(), r.bottom())), w) + || contains_zero (eval, + Rect(Interval(c[0], r.right()), + Interval(r.top(), r.bottom())), w); + } + // 3) to ensure progress we must do a four way split + else + { + return contains_zero (eval, + Rect(Interval(r.left(), c[0]), + Interval(r.top(), c[1])), w) + || contains_zero (eval, + Rect(Interval(c[0], r.right()), + Interval(r.top(), c[1])), w) + || contains_zero (eval, + Rect(Interval(r.left(), c[0]), + Interval(c[1], r.bottom())), w) + || contains_zero (eval, + Rect(Interval(c[0], r.right()), + Interval(c[1], r.bottom())), w); + } + } + //std::cout << w << " < " << r.width() << " , " << r.height() << std::endl; + //std::cout << r.min() << " - " << r.max() << std::endl; + return true; + } + return false; + } // end recursive_implicit + + + void draw_implicit_curve (cairo_t*cr, implicit_curve_t const& eval, + Point const& origin, Rect r, double w) + { + ++iters; + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //assert(x.rad() > 0); + //assert(y.rad() > 0); +// time(&t0); + AAF f = eval(x-origin[X], y-origin[Y]); +// time(&t1); +// d1 += std::difftime(t1, t0); + // pivot +// time(&t2); + double a = f.index_coeff(x.get_index(0)) / x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0)) / y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if (ivl.extent() < 0.5*L2(n)) + { + draw_line_in_rect(cr, r, n, ivl.middle()); + return; + } +// time(&t3); +// d2 += std::difftime(t3, t2); + if ((r.width() > w) || (r.height() > w)) + { + if (f.straddles_zero()) + { + Point c = r.midpoint(); + Rect oldr = r; + if (out) r = *out; + // Three possibilities: + // 1) the trim operation buys us enough that we should just iterate + if (1 && (r.area() < oldr.area()*0.25)) + { + draw_implicit_curve(cr, eval, origin, r, w); + } + // 2) one dimension is significantly smaller + else if (1 && (r[1].extent() < oldr[1].extent()*0.5)) + { + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), r.right()), + Interval(r.top(), c[1])), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), r.right()), + Interval(c[1], r.bottom())), w); + } + else if (1 && (r[0].extent() < oldr[0].extent()*0.5)) + { + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), c[0]), + Interval(r.top(), r.bottom())), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(c[0], r.right()), + Interval(r.top(), r.bottom())), w); + } + // 3) to ensure progress we must do a four way split + else + { + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), c[0]), + Interval(r.top(), c[1])), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(c[0], r.right()), + Interval(r.top(), c[1])), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(r.left(), c[0]), + Interval(c[1], r.bottom())), w); + draw_implicit_curve (cr, eval, origin, + Rect(Interval(c[0], r.right()), + Interval(c[1], r.bottom())), w); + } + } + } else { + if(contains_zero(eval, r*Geom::Translate(-origin))) { + cairo_save(cr); + cairo_set_source_rgb(cr, 0,0.5,0); + cairo_rectangle(cr, r); + cairo_fill(cr); + cairo_restore(cr); + } + } + } // end recursive_implicit + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + iters = 0; + d1 = d2 = 0; + cairo_set_line_width (cr, 0.3); + D2<SBasis> A = pshA.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + + SL::MVPoly2 ic; + make_implicit_curve(ic, A); + + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 0.8); + draw_implicit_curve (cr, ic, orig_handle.pos, + Rect(Interval(0,width), Interval(0, height)), 1); + cairo_stroke(cr); + +// std::cerr << "D1 = " << d1 << std::endl; +// std::cerr << "D2 = " << d2 << std::endl; + + *notify << "iter: " << iters; + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + +public: + ImplicitToy(unsigned int _A_bez_ord) + : A_bez_ord(_A_bez_ord) + { + handles.push_back(&orig_handle); + orig_handle.pos = Point(0,0); //Point(300,300); + + handles.push_back(&pshA); + for (unsigned int i = 0; i < A_bez_ord; ++i) + pshA.push_back(Geom::Point(uniform()*400, uniform()*400)); + } + +private: + unsigned int A_bez_ord; + PointHandle orig_handle; + PointSetHandle pshA; + time_t t0, t1, t2, t3; + double d1, d2; + unsigned int iters; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=5; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + + init( argc, argv, new ImplicitToy(A_bez_ord)); + return 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/toys/ineaa.cpp b/src/toys/ineaa.cpp new file mode 100644 index 0000000..75b9d41 --- /dev/null +++ b/src/toys/ineaa.cpp @@ -0,0 +1,655 @@ +#include <2geom/convex-hull.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/d2.h> +#include <2geom/geom.h> +#include <2geom/numeric/linear_system.h> + +#include <aa.h> +#include <complex> +#include <algorithm> +#include <optional> + +using std::vector; +using namespace Geom; +using namespace std; + +typedef std::complex<AAF> CAAF; + +struct PtLexCmp{ + bool operator()(const Point &a, const Point &b) { + return (a[0] < b[0]) || ((a[0] == b[0]) and (a[1] < b[1])); + } +}; + +// draw ax + by + c = 0 +void draw_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) { + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + cairo_move_to(cr, (*ls)[0]); + cairo_line_to(cr, (*ls)[1]); + cairo_stroke(cr); + + } +} + +void fill_line_in_rect(cairo_t*cr, Rect &r, Point n, double c) { + ConvexHull ch; + + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + ch.boundary.push_back((*ls)[0]); + ch.boundary.push_back((*ls)[1]); + } + for(int i = 0; i < 4; i++) { + Point p = r.corner(i); + if(dot(n,p) < c) { + ch.boundary.push_back(p); + } + } + ch.graham(); + cairo_convex_hull(cr, ch.boundary); +} + +OptRect tighten(Rect &r, Point n, Interval lu) { + vector<Geom::Point> result; + Point resultp; + for(int i = 0; i < 4; i++) { + Point cnr = r.corner(i); + double z = dot(cnr, n); + if((z > lu[0]) and (z < lu[1])) + result.push_back(cnr); + } + for(int i = 0; i < 2; i++) { + double c = lu[i]; + + std::optional<Geom::LineSegment> ls = + rect_line_intersect(r, Line::fromNormalDistance(n, c)); + + if(ls) { + result.push_back((*ls)[0]); + result.push_back((*ls)[1]); + } + } + if(result.size() < 2) + return OptRect(); + Rect nr(result[0], result[1]); + for(size_t i = 2; i < result.size(); i++) { + nr.expandTo(result[i]); + } + return intersect(nr, r); +} + +AAF ls_sample_based(AAF x, vector<Point> pts) { + NL::Matrix m(pts.size(), 2); + NL::Vector v(pts.size()); + NL::LinearSystem ls(m, v); + + m.set_all(0); + v.set_all(0); + for (unsigned int k = 0; k < pts.size(); ++k) + { + m(k,0) += pts[k][0]; + m(k,1) += 1; + //std::cout << pts[k] << " "; + + v[k] += pts[k][1]; + //v[1] += pts[k][1]; + //v[2] += y2; + } + + ls.SV_solve(); + + double A = ls.solution()[0]; + double B = ls.solution()[1]; + // Ax + B = y + Interval bnd(0,0); + for (unsigned int k = 0; k < pts.size(); ++k) + { + bnd.extendTo(A*pts[k][0]+B - pts[k][1]); + } + //std::cout << A << "," << B << std::endl; + return AAF(x, A, B, bnd.extent(), + x.special); +} + +AAF md_sample_based(AAF x, vector<Point> pts) { + Geom::ConvexHull ch1(pts); + Point a, b, c; + ch1.narrowest_diameter(a, b, c); + Point db = c-b; + double A = db[1]/db[0]; + Point aa = db*(dot(db, a-b)/dot(db,db))+b; + Point mid = (a+aa)/2; + double B = mid[1] - A*mid[0]; + double dB = (a[1] - A*a[0]) - B; + // Ax + B = y + //std::cout << A << "," << B << std::endl; + return AAF(x, A, B, dB, + x.special); +} + +AAF atan_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + + const double ea = atan(a); + const double eb = atan(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + double xs = sqrt(1/alpha-1); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,atan(xs))); + xs = -xs; + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,atan(xs))); + + return md_sample_based(x, pts); +} + +AAF log_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + AAF_TYPE type; + if(a > 0) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + + const double ea = log(a); + const double eb = log(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // dlog(xs) = alpha + double xs = 1/(alpha); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,log(xs))); + + return md_sample_based(x, pts); +} + +AAF exp_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + + const double ea = exp(a); + const double eb = exp(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // dexp(xs) = alpha + double xs = log(alpha); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,exp(xs))); + + return md_sample_based(x, pts); +} + +double +desy_lambert_W(double x) { + if (x <= 500.0) { + double lx1 = log(x + 1.0); + return 0.665 * (1 + 0.0195 * lx1) * lx1 + 0.04; + } + return log(x - 4.0) - (1.0 - 1.0/log(x)) * log(log(x)); +} + +double lambertW(double x, double prec = 1E-12, int maxiters = 100) { + double w = desy_lambert_W(x); + const double e = exp(1); + for(int i = 0; i < maxiters; i++) { + double we = w * pow(e,w); + double w1e = (w + 1) * pow(e,w); + if( prec > abs((x - we) / w1e)) + return w; + w -= (we - x) / (w1e - (w+2) * (we-x) / (2*w+2)); + } + //raise ValueError("W doesn't converge fast enough for abs(z) = %f" % abs(x)) + return 0./0.; +} + +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_min.h> + +typedef struct{ + double a, b; +} param_W; + +double fn1 (double x, void * params) +{ + param_W *pw = (param_W*)params; + return (pw->a*x+pw->b) - lambertW(x); +} + +double optimise(void * params, double a, double b) { + int status; + //param_W *pw = (param_W*)params; + int iter = 0, max_iter = 100; + double m = (a+b)/2; + gsl_function F; + + F.function = &fn1; + F.params = params; + + const gsl_min_fminimizer_type *T = gsl_min_fminimizer_brent; + gsl_min_fminimizer *s = gsl_min_fminimizer_alloc (T); + if(a+1e-10 >= b) return m; +#if 0 + cout << a << " " << b << " " << m << endl; + cout << "fn:" << fn1(a, params) << " " << fn1(b, params) << " " << fn1(m, params) << endl; + cout << "fn:" << (pw->a*a+pw->b) << " " << (pw->a*b+pw->b) << " " << (pw->a*m+pw->b) << endl; +#endif + gsl_min_fminimizer_set (s, &F, m, a, b); + do + { + iter++; + status = gsl_min_fminimizer_iterate (s); + + m = gsl_min_fminimizer_x_minimum (s); + a = gsl_min_fminimizer_x_lower (s); + b = gsl_min_fminimizer_x_upper (s); + + status + = gsl_min_test_interval (a, b, 0.001, 0.0); + + } + while (status == GSL_CONTINUE && iter < max_iter); + + gsl_min_fminimizer_free (s); + + return m; +} + + +AAF W_sample_based(AAF x) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + const double e = exp(1); + AAF_TYPE type; + if(a >= -1./e) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + const double ea = lambertW(a); + const double eb = lambertW(b); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // d(W(xs)) = alpha + // W = + param_W pw; + pw.a = alpha; + pw.b = ea - alpha*a; + if(a < b) { + double xs = optimise(&pw, a, b); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,lambertW(xs))); + } + return md_sample_based(x, pts); +} + +AAF pow_sample_based(AAF x, double p) { + interval ab(x); + const double a = ab.min(); // [a,b] is our interval + const double b = ab.max(); + AAF_TYPE type; + if(floor(p) != p) { + if(a >= 0) + type = AAF_TYPE_AFFINE; + else if(b < 0) { // no point in continuing + type = AAF_TYPE_NAN; + return AAF(type); + } + else if(a <= 0) { // undefined, can we do better? + type = (AAF_TYPE)(AAF_TYPE_AFFINE | AAF_TYPE_NAN); + return AAF(type); + // perhaps we should make a = 0+eps and try to continue? + } + } + const double ea = pow(a, p); + const double eb = pow(b, p); + vector<Point> pts; + pts.push_back(Point(a,ea)); + pts.push_back(Point(b,eb)); + const double alpha = (eb-ea)/(b-a); + // d(xs^p) = alpha + // p xs^(p-1) = alpha + // xs = (alpha/p)^(1-p) + double xs = pow(alpha/p, 1./(p-1)); + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,pow(xs, p))); + xs = -xs; + if((a < xs) and (xs < b)) + pts.push_back(Point(xs,pow(xs, p))); + + return md_sample_based(x, pts); +} + +Point origin; +AAF trial_eval(AAF x, AAF y) { + x = x-origin[0]; + y = y-origin[1]; + x = x/200; + y = y/200; + AAF x2 = pow_sample_based(x,2); + AAF y2 = pow_sample_based(y,2); + //return x2 + y2 - 1; + //return y - pow(x,3); + return y + W_sample_based(x); + //return y - pow_sample_based(x,2.5); + //return y - log_sample_based(x); + //return y - log(x); + //return -y + -exp_sample_based(x*log(x)); + return -x + -exp_sample_based(y*log(y)); + //return y - sqrt(sin(x)); + //return sqrt(y)*x - sqrt(x) - y - 1; + //return y-1/x; + //return exp(x)-y; + //return sin(x)-y; + //return exp_sample_based(x)-y; + //return atan(x)-y; + //return atan_sample_based(x)-y; + //return atanh(x)-y; + //return x*y; + //return 4*x+3*y-1; + //return x*x + y*y - 1; + return pow_sample_based((x2 + y2), 2) - (x2-y2); + //return x*x-y; + //return (x*x*x-y*x)*sin(x) + (x-y*y)*cos(y)-0.5; +} + +AAF xaxis(AAF x, AAF y) { + (void)x; + y = y-origin[1]; + y = y/200; + return y; +} + +AAF yaxis(AAF x, AAF y) { + (void)y; + x = x-origin[0]; + x = x/200; + return x; +} + +class ConvexTest: public Toy { +public: + PointSetHandle test_window; + PointSetHandle samples; + PointHandle orig_handle; + ConvexTest () { + toggles.push_back(Toggle("Show trials", false)); + handles.push_back(&test_window); + handles.push_back(&samples); + handles.push_back(&orig_handle); + orig_handle.pos = Point(00,300); + test_window.push_back(Point(100,100)); + test_window.push_back(Point(200,200)); + for(int i = 0; i < 0; i++) { + samples.push_back(Point(i*100, i*100+25)); + } + } + int iters; + int splits[4]; + bool show_splits; + std::vector<Toggle> toggles; + AAF (*eval)(AAF, AAF); + void recursive_implicit(Rect r, cairo_t*cr, double w) { + if(show_splits) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + /*if(f.is_partial()) + cairo_set_source_rgba(cr, 1, 0, 1, 0.25); + else*/ + cairo_set_source_rgba(cr, 0, 1, 0, 0.25); + cairo_rectangle(cr, r); + cairo_stroke(cr); + cairo_restore(cr); + } + iters++; + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //assert(x.rad() > 0); + //assert(y.rad() > 0); + AAF f = (*eval)(x, y); + double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if(ivl.extent() < 0.5*L2(n)) { + + cairo_save(cr); + cairo_set_source_rgba(cr, 0,1,0,0.125); + fill_line_in_rect(cr, r, n, ivl.middle()); + cairo_fill(cr); + cairo_restore(cr); + draw_line_in_rect(cr, r, n, ivl.middle()); + return; + } + if(f.strictly_neg()) { + cairo_save(cr); + cairo_set_source_rgba(cr, 0,1,0,0.125); + cairo_rectangle(cr, r); + cairo_fill(cr); + cairo_restore(cr); + return; + } + if(!f.is_partial() and f.is_indeterminate()) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + if(f.is_infinite()) { + cairo_set_source_rgb(cr, 1, 0.5, 0.5); + } else if(f.is_nan()) { + cairo_set_source_rgb(cr, 1, 1, 0); + } else { + cairo_set_source_rgb(cr, 1, 0, 0); + } + cairo_rectangle(cr, r); + if(show_splits) { + cairo_stroke(cr); + } else { + cairo_fill(cr); + } + cairo_restore(cr); + return; + } + + if((r.width() > w) or (r.height()>w)) { + if(f.strictly_neg() or f.straddles_zero()) { + // Three possibilities: + // 1) the trim operation buys us enough that we should just iterate + Point c = r.midpoint(); + Rect oldr = r; + if(1 && out) { + r = *out; + for(int i = 0; i < 4; i++) { + Point p = oldr.corner(i); + if(dot(n,p) < ivl.middle()) { + r.expandTo(p); + } + } + } + if(1 && out && (r.area() < oldr.area()*0.25)) { + splits[0] ++; + recursive_implicit(r, cr, w); + // 2) one dimension is significantly smaller + } else if(1 && (r[1].extent() < oldr[1].extent()*0.5)) { + splits[1]++; + recursive_implicit(Rect(Interval(r.left(), r.right()), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(r.left(), r.right()), + Interval(c[1], r.bottom())), cr,w); + } else if(1 && (r[0].extent() < oldr[0].extent()*0.5)) { + splits[2]++; + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(r.top(), r.bottom())), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(r.top(), r.bottom())), cr,w); + // 3) to ensure progress we must do a four way split + } else { + splits[3]++; + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(r.top(), c[1])), cr,w); + recursive_implicit(Rect(Interval(r.left(), c[0]), + Interval(c[1], r.bottom())), cr,w); + recursive_implicit(Rect(Interval(c[0], r.right()), + Interval(c[1], r.bottom())), cr,w); + } + } + } else { + } + } + + void key_hit(GdkEventKey *e) override { + if(e->keyval == 'w') toggles[0].toggle(); else + if(e->keyval == 'a') toggles[1].toggle(); else + if(e->keyval == 'q') toggles[2].toggle(); else + if(e->keyval == 's') toggles[3].toggle(); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + origin = orig_handle.pos; + if(1) { + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgb(cr, 0.5, 0.5, 1); + eval = xaxis; + //recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + eval = yaxis; + //recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + cairo_restore(cr); + iters = 0; + for(int & split : splits) + split = 0; + show_splits = toggles[0].on; + eval = trial_eval; + recursive_implicit(Rect(Interval(0,width), Interval(0, height)), cr, 3); + for(int split : splits) + *notify << split << " + "; + *notify << " = " << iters; + } + if(1) { + Rect r(test_window.pts[0], test_window.pts[1]); + AAF x(interval(r.left(), r.right())); + AAF y(interval(r.top(), r.bottom())); + //AAF f = md_sample_based(x, samples.pts)-y; + if(0) { + x = x-500; + y = y-300; + x = x/200; + y = y/200; + AAF f = atan_sample_based(x)-y; + cout << f << endl; + } + AAF f = (*eval)(x, y); + double a = f.index_coeff(x.get_index(0))/x.index_coeff(x.get_index(0)); + double b = f.index_coeff(y.get_index(0))/y.index_coeff(y.get_index(0)); + AAF d = a*x + b*y - f; + //cout << d << endl; + interval ivl(d); + Point n(a,b); + OptRect out = tighten(r, n, Interval(ivl.min(), ivl.max())); + if(out) + cairo_rectangle(cr, *out); + cairo_rectangle(cr, r); + draw_line_in_rect(cr, r, n, ivl.min()); + if(f.strictly_neg()) { + cairo_save(cr); + cairo_set_source_rgba(cr, .5, 0.5, 0, 0.125); + cairo_fill(cr); + cairo_restore(cr); + } else + cairo_stroke(cr); + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgb(cr, 0.5, 0.5, 0); + draw_line_in_rect(cr, r, n, ivl.middle()); + cairo_stroke(cr); + cairo_restore(cr); + //fill_line_in_rect(cr, r, n, c); + draw_line_in_rect(cr, r, n, ivl.max()); + cairo_stroke(cr); + } + if(0) { + Geom::ConvexHull gm(samples.pts); + cairo_convex_hull(cr, gm); + cairo_stroke(cr); + Point a, b, c; + gm.narrowest_diameter(a, b, c); + cairo_save(cr); + cairo_set_line_width(cr, 2); + cairo_set_source_rgba(cr, 1, 0, 0, 0.5); + cairo_move_to(cr, b); + cairo_line_to(cr, c); + cairo_move_to(cr, a); + cairo_line_to(cr, (c-b)*dot(a-b, c-b)/dot(c-b,c-b)+b); + cairo_stroke(cr); + //std::cout << a << ", " << b << ", " << c << ": " << dia << "\n"; + cairo_restore(cr); + } + Toy::draw(cr, notify, width, height, save,timer_stream); + Point d(25,25); + toggles[0].bounds = Rect(Point(10, height-80)+d, + Point(10+120, height-80+d[1])+d); + + draw_toggles(cr, toggles); + } + +}; + +int main(int argc, char **argv) { + init(argc, argv, new ConvexTest()); + + return 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/toys/inner-product-clip.cpp b/src/toys/inner-product-clip.cpp new file mode 100644 index 0000000..9ada56b --- /dev/null +++ b/src/toys/inner-product-clip.cpp @@ -0,0 +1,174 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> + +#include <gsl/gsl_matrix.h> + +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = p[i]; + cairo_d2_sb(cr, B); + } +} + +void draw_line(cairo_t* cr, Geom::Point n, double d) { + cairo_move_to(cr, d*n + rot90(n)*1000); + cairo_line_to(cr, d*n - rot90(n)*1000); + cairo_move_to(cr, d*n); + cairo_line_to(cr, (d+10)*n); +} + +class InnerProductClip: public Toy { + Path path_a; + Piecewise<D2<SBasis> > path_a_pw; + std::vector<Toggle> togs; + PointHandle start_handle, end_handle; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw); + + Point n; + double d; + { + double x = width - 60, y = height - 60; + Point p(x, y), dpoint(25,25), xo(25,0), yo(0,25); + togs[0].bounds = Rect(p, p + dpoint); + togs[1].bounds = Rect(p + xo, p + xo + dpoint); + draw_toggles(cr, togs); + } + if(togs[0].on) { + d = L2(end_handle.pos - start_handle.pos); + cairo_save(cr); + cairo_set_line_width(cr, 0.3); + cairo_new_sub_path(cr); + cairo_arc(cr, start_handle.pos[0], start_handle.pos[1], d, 0, M_PI*2); + cairo_stroke(cr); + cairo_restore(cr); + } else { + n = unit_vector(rot90(end_handle.pos - start_handle.pos)); + d = dot(n, start_handle.pos); + draw_line(cr, n, d); + } + //printf("%g\n", d); + + vector<double> all_roots; + for(unsigned i = 0; i <= path_a.size(); i++) { + //deriv = p[i].derivative(); + D2<SBasis> curpw = path_a[i].toSBasis(); + SBasis inner; + if(togs[0].on) { + D2<SBasis> test = curpw - start_handle.pos; + inner = test[0]*test[0] + test[0]*test[1] + 2*test[1]*test[1] - d*d; + } else { + inner = n[0]*curpw[0] + n[1]*curpw[1] - d; + } + vector<double> lr = roots(inner); + all_roots.insert(all_roots.end(), lr.begin(), lr.end()); + for(double i : lr) + draw_handle(cr, curpw(i)); + sort(lr.begin(), lr.end()); + lr.insert(lr.begin(), 0); + lr.insert(lr.end(), 1); + Path out; + for(unsigned j = 0; j < lr.size()-1; j++) { + //Point s = curpw(lr[j]); + Point m = curpw((lr[j] + lr[j+1])/2); + if(togs[0].on) + m -= start_handle.pos; + //Point e = curpw(lr[j+1]); + double dd; + if(togs[0].on) + //dd = dot(m, m) - d*d; + dd = m[0]*m[0] + m[0]*m[1] + 2*m[1]*m[1] - d*d; + else + dd = dot(n, m) - d; + if(togs[1].on) + dd = -dd; + //printf("%d [%g, %g] %g (%g, %g) (%g, %g)\n", + // i, lr[j], lr[j+1], dd, s[0], s[1], e[0], e[1]); + if(0 > dd) { + //Curve * cv = path_a[i].portion(lr[j], lr[j+1]); + cairo_d2_sb(cr, portion(curpw, lr[j], lr[j+1])); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + /*cairo_curve(cr, path_a[i]); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr);*/ + } + + } + } + + //cairo_pw_d2_sb(cr, path_a_pw); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void key_hit(GdkEventKey *e) override { + if(e->keyval == 's') togs[1].toggle(); else + if(e->keyval == 'c') togs[0].toggle(); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(togs, e); + Toy::mouse_pressed(e); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="star.svgd"; + if(argc > 1) + path_a_name = argv[1]; + PathVector paths_a = read_svgd(path_a_name); + assert(paths_a.size() > 0); + path_a = paths_a[0]; + + path_a.close(true); + path_a_pw = path_a.toPwSb(); + + // Finite images of the three vanishing points and the origin + handles.push_back(&start_handle); + handles.push_back(&end_handle); + togs.emplace_back("C", true); + togs.emplace_back("S", true); + } +public: + InnerProductClip() : start_handle(150,300), + end_handle(380,40) {} +}; + +int main(int argc, char **argv) { + init(argc, argv, new InnerProductClip); + return 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/toys/intersect-data.cpp b/src/toys/intersect-data.cpp new file mode 100644 index 0000000..d262684 --- /dev/null +++ b/src/toys/intersect-data.cpp @@ -0,0 +1,436 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> + +#include <cstdlib> +#include <cstdio> +#include <set> +#include <vector> +#include <algorithm> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> + +#include "topology.cpp" + + +static double exp_rescale(double x){ return pow(10, x);} +std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));} + + +class IntersectDataTester: public Toy { + unsigned nb_paths; + unsigned nb_curves_per_path; + unsigned degree; + double tol; + + PathVector cmd_line_paths; + + std::vector<PointSetHandle> paths_handles; + std::vector<Slider> sliders; + int nb_steps; + + Topology topo; + + //TODO conversions to path should be owned by the relevant classes. + Path edgeToPath(Topology::OrientedEdge o_edge){ + Topology::Edge e = topo.edges[o_edge.edge]; + D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + p = portion(p, dom); + if ( o_edge.reversed ){ + p = compose( p, Linear(1.,0.) ); + } + Path ret; + ret.setStitching(true); + Point center; + unsigned c_idx = topo.source(o_edge, true); + if ( c_idx == NULL_IDX ){ + ret.append(p); + }else{ + center = topo.vertices[c_idx].bounds.midpoint(); + ret = Path(center); + ret.append(p); + } + c_idx = topo.target(o_edge, true); + if ( c_idx == NULL_IDX ){ + return ret; + }else{ + center = topo.vertices[c_idx].bounds.midpoint(); + if ( center != p.at1() ) ret.appendNew<LineSegment>(center); + return ret; + } + } + + Path boundaryToPath(Topology::Boundary b){ + Point pt; + Path bndary; + bndary.setStitching(true); + + if (b.size()==0){ return Path(); } + + Topology::OrientedEdge o_edge = b.front(); + unsigned first_v = topo.source(o_edge, true); + if ( first_v != NULL_IDX ){ + pt = topo.vertices[first_v].bounds.midpoint(); + bndary = Path(pt); + } + + for (unsigned i = 0; i < b.size(); i++){ + bndary.append( edgeToPath(b[i])); + } + bndary.close(); + return bndary; + } + + //TODO:this should return a path vector, but we glue the components for easy drawing in the toy. + Path areaToPath(unsigned a){ + Path bndary; + bndary.setStitching(true); + if ( topo.areas[a].boundary.size()==0 ){//this is the unbounded component... + OptRect bbox = bounds_fast( topo.input_paths ); + if (!bbox ){return Path();}//??? + bbox->expandBy(50); + bndary = Path(bbox->corner(0)); + bndary.appendNew<LineSegment>(bbox->corner(1)); + bndary.appendNew<LineSegment>(bbox->corner(2)); + bndary.appendNew<LineSegment>(bbox->corner(3)); + bndary.appendNew<LineSegment>(bbox->corner(0)); + }else{ + bndary = boundaryToPath(topo.areas[a].boundary); + } + for (auto & inner_boundarie : topo.areas[a].inner_boundaries){ + bndary.append( boundaryToPath(inner_boundarie)); + bndary.appendNew<LineSegment>( bndary.initialPoint() ); + } + bndary.close(); + return bndary; + } + void drawAreas( cairo_t *cr, Topology const &topo, bool fill=true ){ + //don't draw the first one... + for (unsigned a=0; a<topo.areas.size(); a++){ + drawArea(cr, topo, a, fill); + } + } + void drawArea( cairo_t *cr, Topology const &topo, unsigned a, bool fill=true ){ + if (a>=topo.areas.size()) return; + Path bndary = areaToPath(a); + cairo_path(cr, bndary); + double r,g,b; + + int winding = 0; + for (int k : topo.areas[a].windings){ + winding += k; + } + + //convertHSVtoRGB(0, 1., .5 + winding/10, r,g,b); + //convertHSVtoRGB(360*a/topo.areas.size(), 1., .5, r,g,b); + convertHSVtoRGB(180+30*winding, 1., .5, r,g,b); + cairo_set_source_rgba (cr, r, g, b, 1); + //cairo_set_source_rgba (cr, 1., 0., 1., .3); + + if (fill){ + cairo_fill(cr); + }else{ + cairo_set_line_width (cr, 5); + cairo_stroke(cr); + } + } + + void highlightRay( cairo_t *cr, Topology &topo, unsigned b, unsigned r ){ + if (b>=topo.vertices.size()) return; + if (r>=topo.vertices[b].boundary.size()) return; + Rect box = topo.vertices[b].bounds; + //box.expandBy(2); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, 1.0); + cairo_set_line_width (cr, 1); + cairo_fill(cr); + unsigned eidx = topo.vertices[b].boundary[r].edge; + Topology::Edge e = topo.edges[eidx]; + D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + if (topo.vertices[b].boundary[r].reversed){ + //dom[0] += e.portion.extent()*2./3; + cairo_set_source_rgba (cr, 0., 1., 0., 1.0); + }else{ + //dom[1] -= e.portion.extent()*2./3; + cairo_set_source_rgba (cr, 0., 0., 1., 1.0); + } + p = portion(p, dom); + cairo_d2_sb(cr, p); + cairo_set_source_rgba (cr, 1., 0., 0, 1.0); + cairo_set_line_width (cr, 5); + cairo_stroke(cr); + } + + void drawEdge( cairo_t *cr, Topology const &topo, unsigned eidx ){ + if (eidx>=topo.edges.size()) return; + Topology::Edge e = topo.edges[eidx]; + D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + p = portion(p, dom); + cairo_d2_sb(cr, p); + if (e.start == NULL_IDX || e.end == NULL_IDX ) + cairo_set_source_rgba (cr, 0., 1., 0, 1.0); + else + cairo_set_source_rgba (cr, 0., 0., 0, 1.0); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + } + void drawEdges( cairo_t *cr, Topology const &topo ){ + for (unsigned e=0; e<topo.edges.size(); e++){ + drawEdge(cr, topo, e); + } + } + void drawKnownEdges( cairo_t *cr, Topology const &topo ){ + for (unsigned v=0; v<topo.vertices.size(); v++){ + for (unsigned e=0; e<topo.vertices[v].boundary.size(); e++){ + drawEdge(cr, topo, topo.vertices[v].boundary[e].edge); + } + } + } + + + void drawBox( cairo_t *cr, Topology const &topo, unsigned b ){ + if (b>=topo.vertices.size()) return; + Rect box = topo.vertices[b].bounds; + //box.expandBy(5); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, .5); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, .2); + cairo_fill(cr); + +// //std::cout<<"\nintersection boundary:\n"; +// for (unsigned i = 0; i < topo.vertices[b].boundary.size(); i++){ +// unsigned eidx = topo.vertices[b].boundary[i].edge; +// Topology::Edge e = topo.edges[eidx]; +// D2<SBasis> p = topo.input_paths[e.path][e.curve].toSBasis(); +// Interval dom = e.portion; +// if (topo.vertices[b].boundary[i].reversed){ +// dom[0] += e.portion.extent()*2./3; +// cairo_set_source_rgba (cr, 0., 1., .5, 1); +// }else{ +// dom[1] -= e.portion.extent()*2./3; +// cairo_set_source_rgba (cr, 0., .5, 1., 1); +// } +// p = portion(p, dom); +// cairo_d2_sb(cr, p); +// cairo_set_line_width (cr, 2); +// cairo_stroke(cr); +// } + } + void drawBoxes( cairo_t *cr, Topology const &topo ){ + for (unsigned b=0; b<topo.vertices.size(); b++){ + drawBox(cr, topo, b); + } + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + *notify<<"line command args: svgd file or (nb paths, nb curves/path, degree of curves).\n"; + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + PathVector paths; + if (!cmd_line_paths.empty()){ + paths = cmd_line_paths; + for (unsigned i = 0; i < paths.size(); i++){ + paths[i] *= Translate( paths_handles[i].pts[0] - paths[i].initialPoint() ); + } + }else{ + for (unsigned i = 0; i < nb_paths; i++){ + paths_handles[i].pts.back()=paths_handles[i].pts.front(); + paths.push_back(Path(paths_handles[i].pts[0])); + for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){ + D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree); + if ( j + degree == paths_handles[i].size()-1 ){ + c[X].at(0)[1] = paths_handles[i].pts.front()[X]; + c[Y].at(0)[1] = paths_handles[i].pts.front()[Y]; + } + paths[i].append(c); + } + paths[i].close(); + } + } + *notify<<"Use '<' and '>' keys to move backward/forward in the sweep: (currently doing "<<nb_steps<<" steps)\n"; + *notify<<"nb_steps: "<<nb_steps<<"\n"; + + +#if 0 + cairo_path(cr, paths); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); +#endif + + tol = exp_rescale( sliders[3].value() ); + topo = Topology(paths, cr, tol, nb_steps ); + +#if 1 + unsigned v = (unsigned)(sliders[0].value()*(double(topo.vertices.size()))); + unsigned r = (unsigned)(sliders[1].value()*(double(topo.vertices[v].boundary.size()))); + unsigned a = (unsigned)(sliders[2].value()*(double(topo.areas.size()))); + if( v == topo.vertices.size() ) v--; + if( r == topo.vertices[v].boundary.size()) r--; + if( a == topo.areas.size()) a--; + drawAreas(cr, topo); + drawKnownEdges(cr, topo); + //drawArea(cr, topo, a, false); + //highlightRay(cr, topo, v, r ); + //*notify<<"highlighted edge: "<< topo.vertices[v].boundary[r].edge<<"\n"; + + //drawBox(cr,topo, unsigned(sliders[0].value())); + drawBoxes(cr,topo); +#endif + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + + void initSliders(){ + sliders.emplace_back(0.0, 1, 0, 0.0, "intersection chooser"); + sliders.emplace_back(0.0, 1, 0, 0.0, "ray chooser"); + sliders.emplace_back(0.0, 1, 0, 0.0, "area chooser"); + sliders.emplace_back(-5.0, 2, 0, 0.0, "tolerance chooser"); + + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + handles.push_back(&(sliders[3])); + + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + sliders[3].geometry(Point(50, 110), 250); + sliders[3].formatter( &exp_formatter ); + + } + + public: + IntersectDataTester(PathVector input_paths){ + cmd_line_paths = input_paths; + //nb_paths=0; nb_curves_per_path = 0; degree = 0;//meaningless + paths_handles = std::vector<PointSetHandle>( cmd_line_paths.size(), PointSetHandle() ); + for(unsigned i = 0; i < cmd_line_paths.size(); i++){ + //TODO: use path iterators to deal with closed/open paths!!! + //cmd_line_paths[i].close(); + if ( cmd_line_paths[i].closed() ){ + cmd_line_paths[i].appendNew<LineSegment>(cmd_line_paths[i].initialPoint() ); + } + Point p = cmd_line_paths[i].initialPoint(); + paths_handles.emplace_back(); + paths_handles[i].push_back(p); + handles.push_back( &paths_handles[i] ); + } + initSliders(); + } + + IntersectDataTester(unsigned paths, unsigned curves_in_path, unsigned degree) : + nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) { + + paths_handles = std::vector<PointSetHandle>( nb_paths, PointSetHandle() ); + for(unsigned i = 0; i < nb_paths; i++){ + for(unsigned j = 0; j < (nb_curves_per_path*degree)+1; j++){ + paths_handles[i].push_back(uniform()*400, 100+ uniform()*300); + } + handles.push_back(&paths_handles[i]); + } + initSliders(); + } + + IntersectDataTester(){ + nb_paths=3; nb_curves_per_path = 5; degree = 1; + + paths_handles.emplace_back(); + paths_handles[0].push_back(100,100); + paths_handles[0].push_back(100,200); + paths_handles[0].push_back(300,200); + paths_handles[0].push_back(300,100); + paths_handles[0].push_back(100,100); + + paths_handles.emplace_back(); + paths_handles[1].push_back(120,190); + paths_handles[1].push_back(200,210); + paths_handles[1].push_back(280,190); + paths_handles[1].push_back(200,300); + paths_handles[1].push_back(120,190); + + paths_handles.emplace_back(); + paths_handles[2].push_back(180,150); + paths_handles[2].push_back(200,140); + paths_handles[2].push_back(220,150); + paths_handles[2].push_back(300,160); + paths_handles[2].push_back(180,150); + + handles.push_back(&paths_handles[0]); + handles.push_back(&paths_handles[1]); + handles.push_back(&paths_handles[2]); + + initSliders(); + } + + + void first_time(int /*argc*/, char** /*argv*/) override { + nb_steps = -1; + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case '>': + nb_steps++; + break; + case '<': + if ( nb_steps > -1 ) nb_steps--; + break; + } + redraw(); + } + +}; + +int main(int argc, char **argv) { + if(argc == 2){ + const char *path_name = argv[1]; + PathVector cmd_line_paths = read_svgd(path_name); //* Scale(3); + OptRect bounds = bounds_exact(cmd_line_paths); + if (bounds) { + cmd_line_paths *= Translate(Point(10,10) - bounds->min()); + } + init(argc, argv, new IntersectDataTester(cmd_line_paths)); + }else{ + unsigned nb_paths=3, nb_curves_per_path = 5, degree = 1; + if(argc > 3) + sscanf(argv[3], "%d", °ree); + if(argc > 2) + sscanf(argv[2], "%d", &nb_curves_per_path); + if(argc > 1){ + sscanf(argv[1], "%d", &nb_paths); + init(argc, argv, new IntersectDataTester( nb_paths, nb_curves_per_path, degree ) ); + }else{ + init(argc, argv, new IntersectDataTester()); + } + } + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/inverse-test.cpp b/src/toys/inverse-test.cpp new file mode 100644 index 0000000..c339419 --- /dev/null +++ b/src/toys/inverse-test.cpp @@ -0,0 +1,174 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + +static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){ + D2<SBasis> plot; + plot[0]=SBasis(Linear(150+a*300,150+b*300)); + plot[1]=B*-vscale; + plot[1]+=450; + cairo_d2_sb(cr, plot); + cairo_stroke(cr); +} +static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){ + for (unsigned i=0;i<f.size();i++){ + plot(cr,f.segs[i],vscale,f.cuts[i],f.cuts[i+1]); + draw_cross(cr,Geom::Point(f.cuts[i]*300 + 150, f.segs[i][0][0]*(-vscale) + 450)); + } +} + +static SBasis my_inverse(SBasis f, int order){ + double a0 = f[0][0]; + if(a0 != 0) { + f -= a0; + } + double a1 = f[0][1]; + if(a1 == 0) + THROW_NOTINVERTIBLE(); + //assert(a1 != 0);// not invertible. + if(a1 != 1) { + f /= a1; + } + + SBasis g=SBasis(order, Linear()); + g[0] = Linear(0,1); + double df0=derivative(f)(0); + double df1=derivative(f)(1); + SBasis r = Linear(0,1)-g(f); + + for(int i=1; i<order; i++){ + //std::cout<<"i: "<<i<<std::endl; + r=Linear(0,1)-g(f); + //std::cout<<"t-gof="<<r<<std::endl; + r.normalize(); + if (r.size()==0) return(g); + double a=r[i][0]/std::pow(df0,i); + double b=r[i][1]/std::pow(df1,i); + g[i] = Linear(a,b); + } + + return(g); +} + +static Piecewise<SBasis> pw_inverse(SBasis const &f, int order,double tol=.1,int depth=0){ + SBasis g=SBasis(Linear(0,1)),r; + Piecewise<SBasis> res,res1,res2; + + //std::cout<<"depth: "<<depth<<std::endl; + g=my_inverse(f,order); + r=g(f); + //std::cout<<"error: "<<g.tail_error(1)<<std::endl; + if (g.tailError(1)<tol){ + res.segs.push_back(g); + res.cuts.push_back(f[0][0]); + res.cuts.push_back(f[0][1]); + }else if (depth<200){ + SBasis ff; + ff=f(Linear(0,.5)); + res=pw_inverse(ff,order,tol,depth+1); + for (unsigned i=0;i<res.size();i++){ + res.segs[i]*=.5; + } + ff=f(Linear(.5,1)); + res1=pw_inverse(ff,order,tol,depth+1); + for (unsigned i=0;i<res1.size();i++){ + res1.segs[i]*=.5; + res1.segs[i]+=.5; + } + res.concat(res1); + } + return(res); +} + + + +class InverseTester: public Toy { + int size; + PointSetHandle hand; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + for (int i=0;i<size;i++){ + hand.pts[i ][0]=150+15*(i-size); + hand.pts[i+size][0]=450+15*(i+1); + cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450)); + cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450)); + } + cairo_move_to(cr, Geom::Point(0,300)); + cairo_line_to(cr, Geom::Point(600,300)); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + + SBasis f(size, Linear());//=SBasis(Linear(0,.5)); + for (int i=0;i<size;i++){ + f[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,i)/150, + -(hand.pts[i+size][1]-300)*std::pow(4.,i)/150 ); + } + plot(cr,Linear(0,1),300); + + f.normalize(); + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 0., 0., 0.8, 1); + plot(cr,f,300); + + *notify<<"Use hand.pts to set the coefficients of the (blue) s-basis."<<std::endl; + *notify<<" (keep it monotonic!)"<<std::endl; + *notify<<"red=flipped inverse; should be the same as the blue one."<<std::endl; + + try { + Piecewise<SBasis> g=pw_inverse(f,3); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.8, 0., 0., 1); + plot(cr,g,300); + Piecewise<SBasis> h=compose(g,f); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.8, 0., 1); + plot(cr,h,300); + *notify<<g.size()<<" segments."; + } catch(NotInvertible) { + *notify << "function not invertible!" << std::endl; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + InverseTester() { + size=4; + if(hand.pts.empty()) { + for(int i = 0; i < 2*size; i++) + hand.pts.emplace_back(0,150+150+uniform()*300*0); + } + hand.pts[0][1]=300; + hand.pts[size][1]=150; + handles.push_back(&hand); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new InverseTester); + return 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:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/kinematic_templates.cpp b/src/toys/kinematic_templates.cpp new file mode 100644 index 0000000..0089a1a --- /dev/null +++ b/src/toys/kinematic_templates.cpp @@ -0,0 +1,365 @@ +#include <toys/toy-framework-2.h> + +/* + * Copyright cilix42 + * Kinematic template toy. The aim is to manipulate the cursor movement + * so that it stays closer to a given shape (e.g., a circle or a line). + * For details, see http://hci.uwaterloo.ca/Publications/Papers/uist222-fung.pdf + * + * Each kinematic template has a radius of action outside of which it + * has no effect (this is indicated by a red circle). + */ + +#include <vector> +#include <2geom/point.h> +#include <2geom/transforms.h> + +using std::vector; +using namespace Geom; +using namespace std; + +// I feel a little uneasy using a Point for polar coords. +Point cartesian_to_polar(Point const &pt, Point const ¢er = Point(0,0)) { + Point rvec = pt - center; + // use atan2 unless you want to measure between two vectors + return Point(L2(rvec), atan2(rvec)); +} + +Point polar_to_cartesian(Point const &pt, Point const ¢er = Point(0,0)) { + return center + Point(pt[0],0) * Rotate(pt[1]); +} + +class KinematicTemplate { +public: + KinematicTemplate(double const sx = 0.0, double const sy = 0.0, double const cx = 0.0, double const cy = 0.0); + ~KinematicTemplate(); + + /* + * To facilitate the creation of templates, we can use different coordinates at each point + * (e.g., radial coordinates around a fixed center) + */ + virtual std::pair<Point, Point> local_coordinate_system(Point const &/*at*/) { + // Return standard cartesian coordinates + return std::make_pair(Point(1,0), Point(0,1)); + } + + virtual Point next_point(Point const &at, Point const &delta);// { return at; } + virtual void draw_visual_cue(cairo_t *cr); + + Point const get_center() { return center; } + void set_center(Point const &pos) { center = pos; } + + double get_radius_of_action() { return radius; } + void set_radius_of_action(double const r) { radius = r; } + void enlarge_radius_of_action(double const by) { + if (radius > -by) + radius += by; + else + radius = 0; + } + + +protected: + double sx, sy, cx, cy; + Point center; + double radius; +}; + +KinematicTemplate::KinematicTemplate(double const sx, double const sy, double const cx, double const cy) + : sx(sx), + sy(sy), + cx(cx), + cy(cy), + center(300,300), + radius(100) +{ +} + +KinematicTemplate::~KinematicTemplate() +{ +} + +Point +KinematicTemplate::next_point(Point const &last_pt, Point const &delta) +{ +// Point new_pt = last_pushed + kinematic_delta(last_pushed, delta, 0); + + /* Compute the "relative" coordinates w.r.t. the "local coordinate system" at the current point */ + Point v = local_coordinate_system(last_pt).first; + Point w = local_coordinate_system(last_pt).second; + double dotv = dot(v, delta); + double dotw = dot(w, delta); + + Point new_delta; + if (L2(last_pt + delta - center) < radius) { + /* + * We are within the radius of action of the kinematic template. + * Compute displacement w.r.t. the v/w-coordinate system. + */ + new_delta = (dotv*sx + cx)*v + (dotw*sy + cy)*w; + } else { + new_delta = delta; + } + + return last_pt + new_delta; +} + +void +KinematicTemplate::draw_visual_cue(cairo_t *cr) { + cairo_set_source_rgba (cr, 1, 0, 0, 1); + cairo_set_line_width (cr, 0.5); + cairo_new_sub_path(cr); + cairo_arc(cr, center[X], center[Y], radius, 0, M_PI*2); + cairo_stroke(cr); +} + +class RadialKinematicTemplate : public KinematicTemplate { +public: + RadialKinematicTemplate(Point const ¢er, double const sx, double const sy, double const cx, double const cy); + + std::pair<Point, Point> local_coordinate_system(Point const &at) override { + /* Return 'radial' coordinates around polar_center */ + Point v = unit_vector(at - center); + return std::make_pair(v, rot90(v)); + } + +private: + Point radial_center; +}; + +RadialKinematicTemplate::RadialKinematicTemplate(Point const ¢er, double const sx, double const sy, + double const cx = 0.0, double const cy = 0.0) + : KinematicTemplate(sx, sy, cx, cy) +{ + radial_center = center; +} + +class GridKinematicTemplate : public KinematicTemplate { +public: + GridKinematicTemplate(double const sx = 0.0, double const sy = 0.0, double const cx = 0.0, double const cy = 0.0) + : KinematicTemplate(sx, sy, cx, cy) {}; + Point next_point(Point const &at, Point const &delta) override;// { return at; } +}; + +Point +GridKinematicTemplate::next_point(Point const &at, Point const &delta) { + if (L2(at + delta - center) < radius) { + // we are within the radius of action + Point new_delta = delta; + + if (fabs(delta[0]) > fabs(delta[1])) + new_delta[1] *= sy; + else + new_delta[0] *= sx; + + return at + new_delta; + } else { + return at + delta; + } +} + + +// My idea was to compute the gradient of an arbitrary potential function as the transform. Probably the right way to do this is to use the hessian as the integrand -- njh +class ImplicitKinematicTemplate : public KinematicTemplate { +public: + ImplicitKinematicTemplate() {} + + Point next_point(Point const &at, Point const &delta) override { + if (L2(at + delta - center) < radius) { + // we are within the radius of action + + // the 0.7dx+1 includes a weakened version of the constraining force + // I can't help but think this is really a form of differential constraint solver, let's discuss. + return at + delta*Scale(0.7*sin(at[0]/10.0)+1, 0.7*cos(at[1]/10.0)+1); + } else { + return at + delta; + } + } +}; + + + +vector<KinematicTemplate*> kin; +KinematicTemplate *cur_kin; +std::string cur_choice = "A"; + +class KinematicTemplatesToy : public Toy { + + enum menu_item_t + { + KT_HORIZONTAL = 0, + KT_VERTICAL, + KT_GRID, + KT_CIRCLE, + KT_RADIAL, + KT_CONVEYOR, + KT_IMP, + TOTAL_ITEMS // this one must be the last item + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + Point cur, last_pushed; + vector<vector<Point>*> pts; + + bool dragging_center; // to prevent drawing while dragging the center + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/ ) + { + *notify << std::endl; + for (int i = KT_HORIZONTAL; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + *notify << "+/- - enlarge/shrink radius of action" << endl << endl << endl << endl; + *notify << "Current choice: " << cur_choice << endl; + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_set_line_width (cr, 1); + + //draw_handle(cr, cur_kin->get_center()); + draw_menu(cr, notify, width, height, save, timer_stream); + + // draw all points accumulated so far + for (auto & pt : pts) { + if (pt->size() > 0) { + cairo_move_to(cr, (*pt)[0]); + } + for (auto & j : *pt) { + //cout << " --> drawing line to point #" << j << endl; + cairo_line_to(cr, j); + } + } + + cairo_stroke(cr); + + cur_kin->draw_visual_cue(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void first_time(int /*argc*/, char** /*argv*/) override { + p1.pos = Point(200, 200); + handles.push_back(&p1); + + pts.clear(); + kin.push_back(new KinematicTemplate(1.0, 0.1)); // horizontal lines + kin.push_back(new KinematicTemplate(0.1, 1.0)); // horizontal lines + kin.push_back(new GridKinematicTemplate(0.1, 0.1)); + kin.push_back(new RadialKinematicTemplate(p1.pos, 0.1, 1.0)); + kin.push_back(new RadialKinematicTemplate(p1.pos, 1.0, 0.1)); + kin.push_back(new KinematicTemplate(1.0, 0.1, 1, 0)); // horiz conveyor + kin.push_back(new ImplicitKinematicTemplate()); + cur_kin = kin[0]; + cur_kin->set_center(p1.pos); + + dragging_center = false; + } + + void mouse_pressed(GdkEventButton *e) override { + Point at(e->x, e->y); + + if(L2(at - p1.pos) < 5) { + dragging_center = true; + } else { + if(e->button == 1) { + vector<Point> *vec = new vector<Point>; + vec->clear(); + vec->push_back(at); + last_pushed = at; + pts.push_back(vec); + } + } + + Toy::mouse_pressed(e); + } + + void mouse_released(GdkEventButton */*e*/) override { + dragging_center = false; + } + + void mouse_moved(GdkEventMotion* e) override { + if (!dragging_center) { + Point at(e->x, e->y); + + Point delta = at - cur; + //cout << "Mouse moved to: " << at << " (difference: " << delta << ")" << endl; + if(e->state & GDK_BUTTON1_MASK) { + Point new_pt = cur_kin->next_point(last_pushed, delta); + + pts.back()->push_back(new_pt); + last_pushed = new_pt; + } + cur = at; + } else { + cur_kin->set_center(p1.pos); + } + + Toy::mouse_moved(e); + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + // No need to copy and paste code + if(choice >= 'A' and choice < 'A' + TOTAL_ITEMS) { + cur_kin = kin[choice - 'A']; + cur_choice = choice; + + } else + switch (choice) + { + case '+': + cur_kin->enlarge_radius_of_action(5); + break; + case '-': + cur_kin->enlarge_radius_of_action(-5); + break; + default: + break; + } + p1.pos = cur_kin->get_center(); + redraw(); + } + +private: + PointHandle p1; +}; + +const char* KinematicTemplatesToy::menu_items[] = +{ + "horizontal", + "vertical", + "grid", + "circular", + "radial", + "conveyor", + "implicit" +}; + +const char KinematicTemplatesToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G' +}; + +int main(int argc, char **argv) { + init(argc, argv, new KinematicTemplatesToy); + return 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/toys/levelsets-test.cpp b/src/toys/levelsets-test.cpp new file mode 100644 index 0000000..cd5874e --- /dev/null +++ b/src/toys/levelsets-test.cpp @@ -0,0 +1,155 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace std; +using namespace Geom; + +static double exp_rescale(double x){ return std::pow(10, x);} +std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));} + + +static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){ + D2<SBasis> plot; + plot[0]=SBasis(Linear(150+a*300,150+b*300)); + plot[1]=B*(-vscale); + plot[1]+=300; + cairo_d2_sb(cr, plot); + cairo_stroke(cr); +} +static void plot_bar(cairo_t* cr, double height, double vscale=1,double a=0,double b=1){ + cairo_move_to(cr, Geom::Point(150+300*a,-height*vscale+300)); + cairo_line_to(cr, Geom::Point(150+300*b,-height*vscale+300)); +} +static void plot_bar(cairo_t* cr, Interval height, double vscale=1,double a=0,double b=1){ + Point A(150+300*a,-height.max()*vscale+300); + Point B(150+300*b,-height.min()*vscale+300); + cairo_rectangle(cr, Rect(A,B) ); +} + +class BoundsTester: public Toy { + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + for (unsigned i=0;i<size;i++){ + hand.pts[i ][0]=150+15*(i-size); + hand.pts[i+size][0]=450+15*(i+1); + cairo_move_to(cr, Geom::Point(hand.pts[i ][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i ][0],450)); + cairo_move_to(cr, Geom::Point(hand.pts[i+size][0],150)); + cairo_line_to(cr, Geom::Point(hand.pts[i+size][0],450)); + } + cairo_move_to(cr, Geom::Point(0,300)); + cairo_line_to(cr, Geom::Point(600,300)); + + cairo_set_line_width (cr, .3); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + + SBasis B(size, Linear()); + for (unsigned i=0;i<size;i++){ + B[i] = Linear(-(hand.pts[i ][1]-300)*std::pow(4.,(int)i), + -(hand.pts[i+size][1]-300)*std::pow(4.,(int)i) ); + } + B.normalize(); + plot(cr,B,1); + cairo_set_source_rgba (cr, 0., 0., 0.8, 1); + cairo_stroke(cr); + + double vtol = exp_rescale(slider.value()); + if (vtol<1e-4) vtol=0; + + hand.pts[2*size ][X]=150; + hand.pts[2*size+1][X]=150; + hand.pts[2*size+2][X]=150; + hand.pts[2*size+1][Y]=std::max(hand.pts[2*size+1][Y],hand.pts[2*size+2][Y]+2*vtol); + hand.pts[2*size ][Y]=std::max(hand.pts[2*size ][Y],hand.pts[2*size+1][Y]+2*vtol); + + vector<Interval> levels; + levels.emplace_back(300-(hand.pts[2*size ][Y]-vtol), 300-(hand.pts[2*size ][Y]+vtol) ); + levels.emplace_back(300-(hand.pts[2*size+1][Y]-vtol), 300-(hand.pts[2*size+1][Y]+vtol) ); + levels.emplace_back(300-(hand.pts[2*size+2][Y]-vtol), 300-(hand.pts[2*size+2][Y]+vtol) ); + + for (auto & level : levels) plot_bar(cr,level.middle()); + cairo_set_source_rgba( cr, 1., 0., 0., 1); + cairo_stroke(cr); + for (auto & level : levels) plot_bar(cr,level); + cairo_set_source_rgba( cr, 1., 0., 0., .2); + cairo_fill(cr); + + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + + *notify<<"Use hand.pts to set the coefficients of the s-basis."<<std::endl; + + vector<vector<Interval> > sols=level_sets(B,levels,0,1); + for (unsigned i=0;i<sols.size();i++){ + for (unsigned j=0;j<sols[i].size();j++){ + Interval ys = levels[i]; + ys.expandTo(0.); + cairo_set_line_width (cr, .3); + plot_bar(cr,ys, 1., sols[i][j].min(), sols[i][j].max()); + cairo_set_source_rgba( cr, 0., 0., 1., .3); + cairo_fill(cr); + plot_bar(cr,ys, 1., sols[i][j].min(), sols[i][j].max()); + cairo_set_source_rgba( cr, 0., 0., 1., .3); + cairo_stroke(cr); + cairo_set_line_width (cr, 1.6); + cairo_set_source_rgba( cr, 0., 0., 1, 1); + plot_bar(cr,0., 1., sols[i][j].min(), sols[i][j].max()); + Point sol ( 150 + 300 * sols[i][j].middle(), 300); + draw_cross(cr, sol); + cairo_stroke(cr); + } + } + + cairo_set_source_rgba( cr, 0., 0., 1., .5); + cairo_fill(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + +public: + BoundsTester(){ + size=5; + if(hand.pts.empty()) { + for(unsigned i = 0; i < 2*size; i++) + hand.pts.emplace_back(0,150+150+uniform()*300*0); + } + hand.pts.emplace_back(150,300+ 50+uniform()*100); + hand.pts.emplace_back(150,300- 50+uniform()*100); + hand.pts.emplace_back(150,300-150+uniform()*100); + handles.push_back(&hand); + slider = Slider(-5, 2, 0, 0.5, "tolerance"); + slider.geometry(Point(50, 20), 250); + slider.formatter(&exp_formatter); + handles.push_back(&slider); + } + + +private: + unsigned size; + PointSetHandle hand; + Slider slider; + +}; + +int main(int argc, char **argv) { + init(argc, argv, new BoundsTester); + return 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:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/line-toy.cpp b/src/toys/line-toy.cpp new file mode 100644 index 0000000..0090d3a --- /dev/null +++ b/src/toys/line-toy.cpp @@ -0,0 +1,916 @@ +/* + * Line Toy + * + * 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/line.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/angle.h> + +#include <vector> +#include <string> +#include <optional> + +using namespace Geom; + + + +std::string angle_formatter(double angle) +{ + return default_formatter(decimal_round(deg_from_rad(angle),2)); +} + + + +class LineToy : public Toy +{ + enum menu_item_t + { + SHOW_MENU = 0, + TEST_CREATE, + TEST_PROJECTION, + TEST_ORTHO, + TEST_DISTANCE, + TEST_POSITION, + TEST_SEG_BISEC, + TEST_ANGLE_BISEC, + TEST_COLLINEAR, + TEST_INTERSECTIONS, + TEST_COEFFICIENTS, + TEST_SEGMENT_INSIDE, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + }; + + enum toggle_label_t + { + }; + + enum slider_label_t + { + END_SHARED_SLIDERS = 0, + ANGLE_SLIDER = END_SHARED_SLIDERS, + A_COEFF_SLIDER = END_SHARED_SLIDERS, + B_COEFF_SLIDER, + C_COEFF_SLIDER + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &LineToy::draw_menu; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + sliders.clear(); + toggles.clear(); + handles.clear(); + } + + + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/ ) + { + init_common_ctrl_geom(cr, width, height, notify); + } + + + void init_create() + { + init_common(); + + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + O.pos = Point(50, 400); + + sliders.emplace_back(0, 2*M_PI, 0, 0, "angle"); + sliders[ANGLE_SLIDER].formatter(&angle_formatter); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&O); + handles.push_back(&(sliders[ANGLE_SLIDER])); + } + + void draw_create(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_create_ctrl_geom(cr, notify, width, height); + + Line l1(p1.pos, p2.pos); + Line l2(O.pos, sliders[ANGLE_SLIDER].value()); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, O, "O"); + draw_label(cr, l1, "L(P1,P2)"); + draw_label(cr, l2, "L(O,angle)"); + } + + + void init_projection() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + O.pos = Point(50, 150); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&O); + } + + void draw_projection(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + LineSegment ls(p3.pos, p4.pos); + + Point np = projection(O.pos, l1); + LineSegment lsp = projection(ls, l1); + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width(cr, 0.2); + draw_line(cr, l1); + draw_segment(cr, ls); + cairo_stroke(cr); + + cairo_set_line_width(cr, 0.3); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); + draw_segment(cr, lsp); + draw_handle(cr, lsp[0]); + draw_handle(cr, lsp[1]); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + draw_circ(cr, np); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0); + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, ls, "S"); + draw_label(cr, lsp, "prj(S)"); + draw_label(cr, O, "P"); + draw_text(cr, np, "prj(P)"); + + cairo_stroke(cr); + } + + + void init_ortho() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 50); + p4.pos = Point(150, 450); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + } + + void draw_ortho(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + Line l2 = make_orthogonal_line(p3.pos, l1); + Line l3 = make_parallel_line(p4.pos, l1); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + draw_line(cr, l3); + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + draw_label(cr, p3, "O1"); + draw_label(cr, p4, "O2"); + + draw_label(cr, l1, "L"); + draw_label(cr, l2, "L1 _|_ L"); + draw_label(cr, l3, "L2 // L"); + + } + + + void init_distance() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + p5.pos = Point(50, 150); + p6.pos = Point(480, 60); + O.pos = Point(300, 300); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&p5); + handles.push_back(&p6); + handles.push_back(&O); + } + + void draw_distance(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + LineSegment ls(p3.pos, p4.pos); + Ray r1(p5.pos, p6.pos); + + Point q1 = l1.pointAt(l1.nearestTime(O.pos)); + Point q2 = ls.pointAt(ls.nearestTime(O.pos)); + Point q3 = r1.pointAt(r1.nearestTime(O.pos)); + + double d1 = distance(O.pos, l1); + double d2 = distance(O.pos, ls); + double d3 = distance(O.pos, r1); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_segment(cr, ls); + draw_ray(cr, r1); + cairo_stroke(cr); + + + draw_label(cr, l1, "L"); + draw_label(cr, ls, "S"); + draw_label(cr, r1, "R"); + draw_label(cr, O, "P"); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.5, 0.5, 0.8, 1.0); + cairo_set_line_width(cr, 0.2); + draw_segment(cr, O.pos, q1); + draw_segment(cr, O.pos, q2); + draw_segment(cr, O.pos, q3); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.8, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_handle(cr, q1); + draw_handle(cr, q2); + draw_handle(cr, q3); + cairo_stroke(cr); + + *notify << " distance(P,L) = " << d1 << std::endl; + *notify << " distance(P,S) = " << d2 << std::endl; + *notify << " distance(P,R) = " << d3 << std::endl; + } + + + void init_position() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 50); + p4.pos = Point(150, 450); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + } + + void draw_position(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + Line l2(p3.pos, p4.pos); + + bool b1 = are_same(l1, l2, 0.01); + bool b2 = are_parallel(l1, l2, 0.01); + bool b3 = are_orthogonal(l1, l2, 0.01); + + double a = angle_between(l1,l2); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_line(cr, l2); + cairo_stroke(cr); + + draw_label(cr, l1, "A"); + draw_label(cr, l2, "B"); + cairo_stroke(cr); + + if (b1) + { + *notify << " A is coincident with B" << std::endl; + } + else if (b2) + { + *notify << " A is parallel to B" << std::endl; + } + else if (b3) + { + *notify << " A is orthogonal to B" << std::endl; + } + else + { + *notify << " A is incident with B: angle(A,B) = " << angle_formatter(a) << std::endl; + } + } + + + void init_seg_bisec() + { + init_common(); + p1.pos = Point(100, 50); + p2.pos = Point(150, 450); + + handles.push_back(&p1); + handles.push_back(&p2); + } + + void draw_seg_bisec(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + LineSegment ls(p1.pos, p2.pos); + Line l = make_bisector_line(ls); + Point M = middle_point(p1.pos, p2.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_label(cr, p1, "P"); + draw_label(cr, p2, "Q"); + draw_label(cr, M, "M"); + draw_segment(cr, ls); + draw_line(cr, l); + cairo_stroke(cr); + + draw_label(cr, l, "axis"); + cairo_stroke(cr); + } + + + void init_angle_bisec() + { + init_common(); + p1.pos = Point(100, 50); + p2.pos = Point(150, 450); + O.pos = Point(50, 200); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&O); + } + + void draw_angle_bisec(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Ray r1(O.pos, p1.pos); + Ray r2(O.pos, p2.pos); + Ray rb = make_angle_bisector_ray(r1, r2); + + double a1 = angle_between(r1,rb); + double a2 = angle_between(rb,r2); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_label(cr, O, "O"); + draw_ray(cr, r1); + draw_ray(cr, r2); + draw_ray(cr, rb); + cairo_stroke(cr); + + draw_label(cr, r1, "R1"); + draw_label(cr, r2, "R2"); + draw_label(cr, rb, "bisector ray"); + + *notify << " angle(R1, bisector ray) = " << angle_formatter(a1) + << " angle(bisector ray, R2) = " << angle_formatter(a2) + << std::endl; + } + + + void init_collinear() + { + init_common(); + p1.pos = Point(100, 50); + p2.pos = Point(450, 450); + p3.pos = Point(400, 50); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + } + + void draw_collinear(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Point A = p1.pos; + Point B = p2.pos; + Point C = p3.pos; + + LineSegment AB(A, B); + LineSegment BC(B, C); + Line l(A, C); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_label(cr, p1, "A"); + draw_label(cr, p2, "B"); + draw_label(cr, p3, "C"); + cairo_stroke(cr); + + + double turn = cross(C, B) - cross(C, A) + cross(B, A); + //*notify << " turn: " << turn << std::endl; + + bool collinear = are_collinear(A, B, C, 200); + if (collinear) + { + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l); + cairo_stroke(cr); + *notify << " A,B,C are collinear!" << std::endl; + } + else + { + cairo_set_source_rgba(cr, 0.5, 0.5, 0.8, 1.0); + cairo_set_line_width(cr, 0.2); + draw_segment(cr, AB); + draw_segment(cr, BC); + cairo_stroke(cr); + if (turn < 0) + *notify << " A,B,C is a left turn: " << turn << std::endl; + else + *notify << " A,B,C is a right turn: " << turn << std::endl; + } + + } + + + void init_intersections() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + p5.pos = Point(50, 150); + p6.pos = Point(480, 60); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&p5); + handles.push_back(&p6); + } + + void draw_intersections(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Line l1(p1.pos, p2.pos); + Ray r1(p3.pos, p4.pos); + LineSegment s1(p5.pos, p6.pos); + + std::vector<ShapeIntersection> cl1r1 = l1.intersect(r1); + std::vector<ShapeIntersection> cl1s1 = l1.intersect(s1); + std::vector<ShapeIntersection> cr1s1 = Line(r1).intersect(s1); + filter_ray_intersections(cr1s1, true, false); + + std::vector<Point> ip; + + if (!cl1r1.empty()) { + ip.push_back(l1.pointAt(cl1r1[0].first)); + } + if (!cl1s1.empty()) { + ip.push_back(l1.pointAt(cl1s1[0].first)); + } + if (!cr1s1.empty()) { + ip.push_back(r1.pointAt(cr1s1[0].first)); + } + + cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + draw_ray(cr, r1); + draw_segment(cr, s1); + cairo_stroke(cr); + + + draw_label(cr, l1, "L"); + draw_label(cr, r1, "R"); + draw_label(cr, s1, "S"); + cairo_stroke(cr); + + std::string label("A"); + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.5); + for (auto & i : ip) + { + draw_handle(cr, i); + draw_text(cr, i+op, label.c_str()); + label[0] += 1; + } + cairo_stroke(cr); + + } + + + void init_coefficients() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + + Line l(p1.pos, p2.pos); + std::vector<double> coeff = l.coefficients(); + sliders.emplace_back(-1, 1, 0, coeff[0], "A"); + sliders.emplace_back(-1, 1, 0, coeff[1], "B"); + sliders.emplace_back(-500, 500, 0, coeff[2], "C"); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&(sliders[A_COEFF_SLIDER])); + handles.push_back(&(sliders[B_COEFF_SLIDER])); + handles.push_back(&(sliders[C_COEFF_SLIDER])); + } + + void draw_coefficients(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_coefficients_ctrl_geom(cr, notify, width, height); + + Line l1(p1.pos, p2.pos); + std::vector<double> coeff1 = l1.coefficients(); + Line l2(sliders[A_COEFF_SLIDER].value(), + sliders[B_COEFF_SLIDER].value(), + sliders[C_COEFF_SLIDER].value()); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.8); + draw_line(cr, l1); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.4); + draw_line(cr, l2); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + draw_label(cr, p1, "P"); + draw_label(cr, p2, "Q"); + //draw_label(cr, l1, "L(P,Q)"); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1.0); + draw_label(cr, l2, "L(A, B, C)"); + cairo_stroke(cr); + + *notify << " L(P,Q): a = " << coeff1[0] + << ", b = " << coeff1[1] + << ", c = " << coeff1[2] << std::endl; + + } + + void init_segment_inside() + { + init_common(); + p1.pos = Point(200, 300); + p2.pos = Point(400, 200); + p3.pos = Point(250, 200); + p4.pos = Point(350, 300); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + } + + void draw_segment_inside(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + Line l(p1.pos, p2.pos); + Rect r(p3.pos, p4.pos); + + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_set_line_width(cr, 0.5); + draw_line(cr, l); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0, 0, 1, 1); + cairo_rectangle(cr, r); + cairo_stroke(cr); + + std::optional<LineSegment> seg = l.clip(r); + if (seg) { + cairo_set_source_rgba(cr, 1, 0, 0, 1); + draw_line_seg(cr, seg->initialPoint(), seg->finalPoint()); + cairo_stroke(cr); + } + } + + + void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + } + } + + void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180); + } + } + + void init_coefficients_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[A_COEFF_SLIDER].geometry(Point(50, height - 160), 400); + sliders[B_COEFF_SLIDER].geometry(Point(50, height - 110), 400); + sliders[C_COEFF_SLIDER].geometry(Point(50, height - 60), 400); + } + } + + + void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) + { + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); + } + + void draw_segment(cairo_t* cr, LineSegment const& ls) + { + draw_segment(cr, ls[0], ls[1]); + } + + void draw_ray(cairo_t* cr, Ray const& r) + { + double angle = r.angle(); + draw_segment(cr, r.origin(), angle, m_length); + } + + void draw_line(cairo_t* cr, Line const& l) + { + double angle = l.angle(); + draw_segment(cr, l.origin(), angle, m_length); + draw_segment(cr, l.origin(), angle, -m_length); + } + + void draw_label(cairo_t* cr, PointHandle const& ph, const char* label) + { + draw_text(cr, ph.pos+op, label); + } + + void draw_label(cairo_t* cr, Line const& l, const char* label) + { + draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label); + } + + void draw_label(cairo_t* cr, LineSegment const& ls, const char* label) + { + draw_text(cr, middle_point(ls[0], ls[1])+op, label); + } + + void draw_label(cairo_t* cr, Ray const& r, const char* label) + { + Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30))); + if (L2(r.origin() - prj) < 100) + { + prj = r.origin() + 100*r.vector(); + } + draw_text(cr, prj+op, label); + } + + void init_menu() + { + handles.clear(); + sliders.clear(); + toggles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/ ) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &LineToy::draw_menu; + break; + case 'B': + init_create(); + draw_f = &LineToy::draw_create; + break; + case 'C': + init_projection(); + draw_f = &LineToy::draw_projection; + break; + case 'D': + init_ortho(); + draw_f = &LineToy::draw_ortho; + break; + case 'E': + init_distance(); + draw_f = &LineToy::draw_distance; + break; + case 'F': + init_position(); + draw_f = &LineToy::draw_position; + break; + case 'G': + init_seg_bisec(); + draw_f = &LineToy::draw_seg_bisec; + break; + case 'H': + init_angle_bisec(); + draw_f = &LineToy::draw_angle_bisec; + break; + case 'I': + init_collinear(); + draw_f = &LineToy::draw_collinear; + break; + case 'J': + init_intersections(); + draw_f = &LineToy::draw_intersections; + break; + case 'K': + init_coefficients(); + draw_f = &LineToy::draw_coefficients; + break; + case 'L': + init_segment_inside(); + draw_f = &LineToy::draw_segment_inside; + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + LineToy() + { + op = Point(5,5); + } + + private: + typedef void (LineToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + PointHandle p1, p2, p3, p4, p5, p6, O; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + Point op; + double m_width, m_height, m_length; + +}; // end class LineToy + + +const char* LineToy::menu_items[] = +{ + "show this menu", + "line generation", + "projection on a line", + "make orthogonal/parallel", + "distance", + "position", + "segment bisector", + "angle bisector", + "collinear", + "intersection", + "coefficients", + "segment inside" +}; + +const char LineToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L' +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new LineToy()); + return 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/toys/load-svgd.cpp b/src/toys/load-svgd.cpp new file mode 100644 index 0000000..68b1495 --- /dev/null +++ b/src/toys/load-svgd.cpp @@ -0,0 +1,76 @@ +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/svg-path-parser.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/cairo-path-sink.h> +#include <2geom/svg-path-writer.h> + +#include <cstdlib> + +using namespace Geom; + +/** + * @brief SVG path data loading toy. + * + * A very simple toy that loads a file containing raw SVG path data + * and displays it scaled so that it fits inside the window. + * + * Use this toy to see what the path data looks like without + * pasting it into the d= attribute of a path in Inkscape. + */ +class LoadSVGD: public Toy { + PathVector pv; + OptRect bounds; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Rect viewport(Point(10, 10), Point(width-10, height-10)); + PathVector res = pv * bounds->transformTo(viewport, Aspect(ALIGN_XMID_YMID)); + + CairoPathSink sink(cr); + sink.feed(res); + + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill_preserve(cr); + cairo_set_line_width(cr, 1); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_set_source_rgb(cr, 0,0,0); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + public: + LoadSVGD() {} + + void first_time(int argc, char** argv) override { + const char *path_b_name="star.svgd"; + if (argc > 1) + path_b_name = argv[1]; + pv = read_svgd(path_b_name); + bounds = pv.boundsExact(); + if (!bounds) { + std::cerr << "Empty path, aborting" << std::endl; + std::exit(1); + } + } +}; + +int main(int argc, char **argv) { + LoadSVGD x; + init(argc, argv, &x); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/load-svgd.py b/src/toys/load-svgd.py new file mode 100644 index 0000000..26bcef3 --- /dev/null +++ b/src/toys/load-svgd.py @@ -0,0 +1,68 @@ +#!/usr/bin/python + +import py2geom +import toyframework +import random,gtk +from py2geom_glue import * + +def cairo_region(cr, r): + cr.save() + cr.set_source_rgba(0, 0, 0, 1); + if not r.isFill(): + pass#cr.set_dash([]1, 0) + cairo_path(cr, r) + cr.fill() + cr.restore() + +def cairo_regions(cr, p): + for j in p: + cairo_region(cr, j) + +def cairo_shape(cr, s): + cairo_regions(cr, s.getContent()) + + + +def cleanup(ps): + pw = py2geom.paths_to_pw(ps) + centre, area = py2geom.centroid(pw) + if(area > 1): + return py2geom.sanitize(ps) * py2geom.Translate(-centre) + else: + return py2geom.sanitize(ps) + +class LoadSVGD(toyframework.Toy): + def __init__(self): + toyframework.Toy.__init__(self) + self.bs = [] + self.offset_handle = toyframework.PointHandle(0,0) + self.handles.append(self.offset_handle) + def draw(self, cr, pos, save): + t = py2geom.Translate(*self.offset_handle.pos) + #self.paths_b[0] * t + m = py2geom.Matrix() + m.setIdentity() + bst = self.bs * (m * t) + #bt = Region(b * t, b.isFill()) + + cr.set_line_width(1) + + cairo_shape(cr, bst) + + toyframework.Toy.draw(self, cr, pos, save) + + def first_time(self, argv): + path_b_name="star.svgd" + if len(argv) > 1: + path_b_name = argv[1] + self.paths_b = py2geom.read_svgd(path_b_name) + + bounds = py2geom.bounds_exact(self.paths_b) + self.offset_handle.pos = bounds.midpoint() - bounds.corner(0) + + self.bs = cleanup(self.paths_b) +t = LoadSVGD() +import sys + +toyframework.init(sys.argv, t, 500, 500) + diff --git a/src/toys/lpe-framework.cpp b/src/toys/lpe-framework.cpp new file mode 100644 index 0000000..19f7267 --- /dev/null +++ b/src/toys/lpe-framework.cpp @@ -0,0 +1,128 @@ +/* + * A framework for writing an Inkscape Live Path Effect (LPE) toy. + * + * Copyright 2009 Johan Engelen <goejendaagh@zonnet.nl> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#include <string.h> +#include <stdint.h> +#include <toys/lpe-framework.h> + +#include <2geom/sbasis-to-bezier.h> +#include <2geom/affine.h> +#include <2geom/pathvector.h> + +#define LPE_CONVERSION_TOLERANCE 0.01 + +/** + * When the input path handles are moved, this method is called to re-execute the LPE and draw the result. + */ +void LPEToy::draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) +{ + using namespace Geom; + + Piecewise<D2<SBasis> > pwd2(curve_handle.asBezier()); + PathVector A = Geom::path_from_piecewise( pwd2, LPE_CONVERSION_TOLERANCE); + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 1., 0.0, 0., 1); + cairo_path(cr, A); + cairo_stroke(cr); + + // perform the effect: + PathVector B = doEffect_path(A); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.0, 0., 1); + cairo_path(cr, B); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); +} + +/** + * Initializes the LPE toy, and sets a simple default input path. + */ +LPEToy::LPEToy(){ + if(handles.empty()) { + handles.push_back(&curve_handle); + for(unsigned i = 0; i < 4; i++) { + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + } + } +} + +/* + * Here be the doEffect function chain: (this is copied code from Inkscape) + */ +Geom::PathVector +LPEToy::doEffect_path (Geom::PathVector const &path_in) +{ + Geom::PathVector path_out; + + if ( !concatenate_before_pwd2 ) { + // default behavior + for (const auto & i : path_in) { + Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = i.toPwSb(); + Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in); + Geom::PathVector path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); + // add the output path vector to the already accumulated vector: + for (const auto & j : path) { + path_out.push_back(j); + } + } + } else { + // concatenate the path into possibly discontinuous pwd2 + Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in; + for (const auto & i : path_in) { + pwd2_in.concat( i.toPwSb() ); + } + Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in); + path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); + } + + return path_out; +} + +Geom::Piecewise<Geom::D2<Geom::SBasis> > +LPEToy::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) +{ + // default effect does nothing + return pwd2_in; +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/lpe-test.cpp b/src/toys/lpe-test.cpp new file mode 100644 index 0000000..ff397c0 --- /dev/null +++ b/src/toys/lpe-test.cpp @@ -0,0 +1,93 @@ +/** + * \file lpe-test.cpp + * \brief Example file showing how to write an Inkscape Live Path Effect toy. + */ +/* + * Copyright 2009 Johan Engelen <goejendaagh@zonnet.nl> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#include <toys/lpe-framework.h> + +// This is usually very bad practice: don't do it in your LPE +using std::vector; +using namespace Geom; +using namespace std; + +class LPETest: public LPEToy { +public: + LPETest() { + concatenate_before_pwd2 = false; + } + + Geom::Piecewise<Geom::D2<Geom::SBasis> > + doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) override + { + using namespace Geom; + + Piecewise<D2<SBasis> > pwd2_out = pwd2_in; + + Point vector(50,100); + // generate extrusion bottom: (just a copy of original path, displaced a bit) + pwd2_out.concat( pwd2_in + vector ); + + // generate connecting lines (the 'sides' of the extrusion) + Path path(Point(0.,0.)); + path.appendNew<Geom::LineSegment>( vector ); + Piecewise<D2<SBasis> > connector = path.toPwSb(); + // connecting lines should be put at cusps + Piecewise<D2<SBasis> > deriv = derivative(pwd2_in); + std::vector<double> cusps; // = roots(deriv); + for (double cusp : cusps) { + pwd2_out.concat( connector + pwd2_in.valueAt(cusp) ); + } + // connecting lines should be put where the tangent of the path equals the extrude_vector in direction + std::vector<double> rts = roots(dot(deriv, rot90(vector))); + for (double rt : rts) { + pwd2_out.concat( connector + pwd2_in.valueAt(rt) ); + } + + return pwd2_out; + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new LPETest); + return 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/match-curve.cpp b/src/toys/match-curve.cpp new file mode 100644 index 0000000..60e81a5 --- /dev/null +++ b/src/toys/match-curve.cpp @@ -0,0 +1,162 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZROOTS_TEST 0 +#if ZROOTS_TEST +#include <2geom/zroots.c> +#endif + +#include <vector> +using std::vector; +using namespace Geom; + +//#define HAVE_GSL + +template <typename T> +void shift(T &a, T &b, T const &c) { + a = b; + b = c; +} +template <typename T> +void shift(T &a, T &b, T &c, T const &d) { + a = b; + b = c; + c = d; +} + +extern unsigned total_steps, total_subs; + +class MatchCurve: public Toy { +public: + double timer_precision; + double units; + PointSetHandle psh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 1); + cairo_set_source_rgb(cr, 0,0,0); + std::vector<Geom::Point> trans; + trans.resize(psh.size()); + for(unsigned i = 0; i < psh.size(); i++) { + trans[i] = psh.pts[i] - Geom::Point(0, 3*width/4); + } + + std::vector<double> solutions; + + D2<SBasis> test_sb = psh.asBezier(); + + + D2<SBasis> B = psh.asBezier(); + Geom::Path pb; + pb.append(B); + pb.close(false); + cairo_path(cr, pb); + cairo_stroke(cr); + + D2<SBasis> m; + D2<SBasis> dB = derivative(B); + D2<SBasis> ddB = derivative(dB); + D2<SBasis> dddB = derivative(ddB); + + Geom::Point pt = B(0); + Geom::Point tang = dB(0); + Geom::Point dtang = ddB(0); + Geom::Point ddtang = dddB(0); + for(int dim = 0; dim < 2; dim++) { + m[dim] = Linear(pt[dim],pt[dim]+tang[dim]); + m[dim] += Linear(0, 1)*Linear(0, 1*dtang[dim])/2; + m[dim] += Linear(0, 1)*Linear(0, 1)*Linear(0, ddtang[dim])/6; + } + + double lo = 0, hi = 1; + double eps = 5; + while(hi - lo > 0.0001) { + double mid = (hi + lo)/2; + //double Bmid = (Bhi + Blo)/2; + + m = truncate(compose(B, Linear(0, mid)), 2); + // perform golden section search + double best_f = 0, best_x = 1; + for(int n = 2; n < 4; n++) { + Geom::Point m_pt = m(double(n)/6); + double x0 = 0, x3 = 1.; // just a guess! + const double R = 0.61803399; + const double C = 1 - R; + double x1 = C*x0 + R*x3; + double x2 = C*x1 + R*x3; + double f1 = Geom::distance(m_pt, B(x1)); + double f2 = Geom::distance(m_pt, B(x2)); + while(fabs(x3 - x0) > 1e-3*(fabs(x1) + fabs(x2))) { + if(f2 < f1) { + shift(x0, x1, x2, R*x1 + C*x3); + shift(f1, f2, Geom::distance(m_pt, B(x2))); + } else { + shift(x3, x2, x1, R*x2 + C*x0); + shift(f2, f1, Geom::distance(m_pt, B(x2))); + } + std::cout << x0 << "," + << x1 << "," + << x2 << "," + << x3 << "," + << std::endl; + } + if(f2 < f1) { + f1 = f2; + x1 = x2; + } + if(f1 > best_f) { + best_f = f1; + best_x = x1; + } + } + std::cout << mid << ":" << best_x << "->" << best_f << std::endl; + //draw_cross(cr, point_at(B, x1)); + + if(best_f > eps) { + hi = mid; + } else { + lo = mid; + } + } + std::cout << std::endl; + //draw_cross(cr, point_at(B, hi)); + draw_circ(cr, m(hi)); + { + Geom::Path pb; + pb.append(m); + pb.close(false); + cairo_path(cr, pb); + } + + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + MatchCurve() : timer_precision(0.1), units(1e6) // microseconds + { + handles.push_back(&psh); + for(int i = 0; i < 6; i++) + psh.push_back(uniform()*400, uniform()*400); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new MatchCurve()); + + return 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/toys/mesh-grad.cpp b/src/toys/mesh-grad.cpp new file mode 100644 index 0000000..c71f5f0 --- /dev/null +++ b/src/toys/mesh-grad.cpp @@ -0,0 +1,134 @@ +/** + * Generate approximate mesh gradients for blurring technique suggested by bbyak. + * (njh) + */ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +#include <vector> +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +const double u_subs = 5, + v_subs = 5, + fudge = .01; + +const double inv_u_subs = 1 / u_subs, + inv_v_subs = 1 / v_subs; + +class Sb2d2: public Toy { +public: + PointSetHandle hand; + Sb2d2() { + handles.push_back(&hand); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + D2<SBasis2d> sb2; + for(unsigned dim = 0; dim < 2; dim++) { + sb2[dim].us = 2; + sb2[dim].vs = 2; + const int depth = sb2[dim].us*sb2[dim].vs; + sb2[dim].resize(depth, Linear2d(0)); + } + Geom::Point dir(1,-2); + if(hand.pts.empty()) { + for(unsigned vi = 0; vi < sb2[0].vs; vi++) + for(unsigned ui = 0; ui < sb2[0].us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) + hand.pts.emplace_back((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + + } + + for(int dim = 0; dim < 2; dim++) { + Geom::Point dir(0,0); + dir[dim] = 1; + for(unsigned vi = 0; vi < sb2[dim].vs; vi++) + for(unsigned ui = 0; ui < sb2[dim].us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) { + unsigned corner = iu + 2*iv; + unsigned i = ui + vi*sb2[dim].us; + Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + if(vi == 0 && ui == 0) { + base = Geom::Point(width/4., width/4.); + } + double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir); + sb2[dim][i][corner] = dl/(width/2)*pow(4.0,(double)ui+vi); + } + } + cairo_d2_sb2d(cr, sb2, dir*0.1, width); + cairo_set_source_rgba (cr, 0., 0., 0, 0.5); + cairo_stroke(cr); + for(unsigned vi = 0; vi < v_subs; vi++) { + double tv = vi * inv_v_subs; + for(unsigned ui = 0; ui < u_subs; ui++) { + double tu = ui * inv_u_subs; + + Geom::Path pb; + D2<SBasis> B; + D2<SBasis> tB; + + B[0] = Linear(tu-fudge, tu+fudge + inv_u_subs ); + B[1] = Linear(tv-fudge, tv-fudge); + tB = compose_each(sb2, B); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + pb.append(tB); + + B[0] = Linear(tu+fudge + inv_u_subs , tu+fudge + inv_u_subs); + B[1] = Linear(tv-fudge, tv+fudge + inv_v_subs); + tB = compose_each(sb2, B); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + pb.append(tB); + + B[0] = Linear(tu+fudge + inv_u_subs, tu-fudge); + B[1] = Linear(tv+fudge + inv_v_subs, tv+fudge + inv_v_subs); + tB = compose_each(sb2, B); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + pb.append(tB); + + B[0] = Linear(tu-fudge, tu-fudge); + B[1] = Linear(tv+fudge + inv_v_subs, tv-fudge); + tB = compose_each(sb2, B); + tB = tB*(width/2) + Geom::Point(width/4, width/4); + pb.append(tB); + + cairo_path(cr, pb); + + //std::cout << pb.peek().end() - pb.peek().begin() << std::endl; + cairo_set_source_rgba (cr, tu, tv, 0, 1); + cairo_fill(cr); + } + } + //*notify << "bo = " << sb2.index(0,0); + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sb2d2); + return 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/toys/metro.cpp b/src/toys/metro.cpp new file mode 100644 index 0000000..5d2804c --- /dev/null +++ b/src/toys/metro.cpp @@ -0,0 +1,922 @@ +/** Generates approximate metromap lines + * Copyright njh + * Copyright Tim Dwyer + * Published in ISVC 2008, Las Vegas, Nevada, USA + */ + +#include <cstdio> +#include <cstring> +#include <cstdlib> +#include <cmath> + +#include <gtk/gtk.h> +#include <cassert> +#include <algorithm> +#include <sstream> +#include <iostream> +#include <fstream> +#include <vector> +#include <string> +#include <map> +#include <cairo-pdf.h> +#include <2geom/point.h> +#include <2geom/geom.h> +#include <toys/toy-framework-2.h> + +using std::string; +using std::vector; +using std::pair; +using std::make_pair; +using std::ifstream; +using std::map; +using std::cout; +using std::endl; +using namespace Geom; + +vector<vector<Point> > paths; + +class sufficient_stats{ +public: + double Sx, Sy, Sxx, Syy, Sxy; + double n; + + sufficient_stats() : Sx(0), Sy(0), Sxx(0), Syy(0), Sxy(0), n(0) {} + void + operator+=(Point p) { + Sx += p[0]; + Sy += p[1]; + Sxx += p[0]*p[0]; + Syy += p[1]*p[1]; + Sxy += p[0]*p[1]; + n += 1.0; + } + void + operator-=(Point p) { + Sx -= p[0]; + Sy -= p[1]; + Sxx -= p[0]*p[0]; + Syy -= p[1]*p[1]; + Sxy -= p[0]*p[1]; + n -= 1.0; + } + /*** What is the best regression we can do? . */ + Point best_normal() { + return rot90(unit_vector(Point(n*Sxx - Sx*Sx, + n*Sxy - Sx*Sy))); + } + /*** Compute the best line for the points, given normal. */ + double best_line(Point normal, const double dist, + double & mean) { + mean = (normal[0]*Sx + normal[1]*Sy); + return normal[0]*normal[0]*Sxx + + normal[1]*normal[1]*Syy + + 2*normal[0]*normal[1]*Sxy + - 2*dist*mean + n*dist*dist; + } + /*** Returns the index to the angle in angles that has the line of best fit + * passing through mean */ + unsigned best_schematised_line(vector<Point>& angles, Point p, + double & /*mean*/, double & cost) { + cost = DBL_MAX; + unsigned bestAngle = 0; + for(unsigned i=0;i<angles.size();i++) { + Point n = unit_vector(rot90(angles[i])); + double dist = dot(n, p); + double mean; + double bl = best_line(n, dist, mean); + if(bl < cost) { + cost = bl; + bestAngle = i; + } + } + return bestAngle; + } + /*** Compute the best line for the points, given normal. */ + double best_angled_line(Point normal, + double & mean) { + mean = (normal[0]*Sx + normal[1]*Sy); + double dist = mean/n; + return normal[0]*normal[0]*Sxx + + normal[1]*normal[1]*Syy + + 2*normal[0]*normal[1]*Sxy + - 2*dist*mean + n*dist*dist; + } +}; + +sufficient_stats +operator+(sufficient_stats const & a, sufficient_stats const &b) { + sufficient_stats ss; + ss.Sx = a.Sx + b.Sx; + ss.Sy = a.Sy + b.Sy; + ss.Sxx = a.Sxx + b.Sxx; + ss.Sxy = a.Sxy + b.Sxy; + ss.Syy = a.Syy + b.Syy; + ss.n = a.n + b.n; + return ss; +} + +sufficient_stats +operator-(sufficient_stats const & a, sufficient_stats const &b) { + sufficient_stats ss; + ss.Sx = a.Sx - b.Sx; + ss.Sy = a.Sy - b.Sy; + ss.Sxx = a.Sxx - b.Sxx; + ss.Sxy = a.Sxy - b.Sxy; + ss.Syy = a.Syy - b.Syy; + ss.n = a.n - b.n; + return ss; +} + +inline std::ostream &operator<< (std::ostream &out_file, const sufficient_stats &s) { + out_file << "Sx: " << s.Sx + << "Sy: " << s.Sy + << "Sxx: " << s.Sxx + << "Sxy: " << s.Sxy + << "Syy: " << s.Syy + << "n: " << s.n; + return out_file; +} + + + +class fit{ +public: + vector<Point> input; + vector<Point> solution; + + vector<pair<Point,Point> > lines; + vector<int> thickness; + + void + draw(cairo_t* cr) { + /* + if(solution.size() > 1) { + //cairo_set_line_width (cr, 1); + cairo_move_to(cr, solution[0]); + for(unsigned i = 1; i < solution.size(); i++) { + cairo_line_to(cr, solution[i]); + } + } + */ + //cairo_stroke(cr); + for(unsigned i = 0;i<lines.size();i++) { + if(thickness.size()>i) { + cairo_set_line_width (cr, thickness[i]); + } + cairo_move_to(cr, lines[i].first); + cairo_line_to(cr, lines[i].second); + cairo_stroke(cr); + } + } + + void endpoints() { + solution.push_back(input[0]); + solution.push_back(input.back()); + } + + void arbitrary(); + void linear_reg(); + + // sufficient stats from start to each point + vector<sufficient_stats> ac_ss; + + /*** Compute the least squares error for a line between two points on the line. */ + double measure(unsigned from, unsigned to, double & mean) { + sufficient_stats ss = ac_ss[to+1] - ac_ss[from]; + + Point n = unit_vector(rot90(input[to] - input[from])); + double dist = dot(n, input[from]); + return ss.best_line(n, dist, mean); + } + + /*** Compute the best line for the points, given normal. */ + double best_angled_line(unsigned from, unsigned to, + Point n, + double & mean) { + sufficient_stats ss = ac_ss[to+1] - ac_ss[from]; + return ss.best_angled_line(n, mean); + } + + double simple_measure(unsigned from, unsigned to, double & mean) { + Point n = unit_vector(rot90(input[to] - input[from])); + double dist = dot(n, input[from]); // distance from origin + double error = 0; + mean = 0; + for(unsigned l = from; l <= to; l++) { + double d = dot(input[l], n) - dist; + mean += dot(input[l], n); + error += d*d; + } + return error; + } + + void simple_dp(); + void C_simple_dp(); + + void C_endpoint(); + + vector<Geom::Point> angles; + fit(vector<Point> const & in) + :input(in) { + /* + Geom::Point as[] = {Point(0,1), Point(1,0), Point(1,1), Point(1,-1)}; + for(unsigned c = 0; c < 4; c++) { + angles.push_back(unit_vector(as[c])); + } + */ + sufficient_stats ss; + ss.Sx = ss.Sy = ss.Sxx = ss.Sxy = ss.Syy = 0; + ac_ss.push_back(ss); + for(auto & l : input) { + ss += l; + ac_ss.push_back(ss); + } + } + + class block{ + public: + unsigned next; + unsigned angle; + sufficient_stats ss; + double cost; + }; + vector<block> blocks; + void test(); + void merging_version(); + void schematised_merging(unsigned number_of_directions); + + double get_block_line(block& b, Point& d, Point& n, Point& c) { + n = unit_vector(rot90(d)); + c = Point(b.ss.Sx/b.ss.n,b.ss.Sy/b.ss.n); + return 0; + } +}; + +class build_bounds{ +public: + int total_n; + Point starting[2]; + Rect combined; + void add_point(Point const &P) { + if(total_n < 2) { + starting[total_n] = P; + total_n += 1; + if(total_n == 2) + combined = Rect(starting[0], starting[1]); + } else { + combined.expandTo(P); + total_n += 1; + } + } + OptRect result() const { + if(total_n > 1) + return combined; + return OptRect(); + } + build_bounds() : total_n(0) {} +}; + +/** + * returns a point which is portionally between topleft and bottomright in the same way that p was + * in src. + */ +Point map_point(Point p, Rect src, Point topleft, Point bottomright) { + p -= src.min(); + p[0] /= src[0].extent(); + p[1] /= src[1].extent(); + //cout << p << endl; + return Point(topleft[0]*(1-p[0]) + bottomright[0]*p[0], + topleft[1]*(1-p[1]) + bottomright[1]*p[1]); +} + +void parse_data(vector<vector<Point> >& paths, + string location_file_name, + string path_file_name) { + ifstream location_file(location_file_name.c_str()), + path_file(path_file_name.c_str()); + string id,sx,sy; + map<string,Point> idlookup; + build_bounds bld_bounds; + while (getline(location_file,id,',')) + { + getline(location_file,sx,','); + getline(location_file,sy,'\n'); + char *e; + // negative for coordinate system + Point p(strtod(sx.c_str(),&e), strtod(sy.c_str(),&e)); + //cout << id << p << endl; + idlookup[id] = p; + } + + + string l; + while (getline(path_file,l,'\n')) { + vector<Point> ps; + if(l.size() < 2) continue; // skip blank lines + if(l.find(":",0)!=string::npos) continue; // skip labels + string::size_type p=0,q; + while((q=l.find(",",p))!=string::npos || (p < l.size() && (q = l.size()))) { + id = l.substr(p,q-p); + //cout << id << endl; + Point pt = 2*idlookup[id]; + //cout << pt << endl; + bld_bounds.add_point(pt); + ps.push_back(pt); + p=q+1; + } + paths.push_back(ps); + //cout << "*******************************************" << endl; + } + Rect bounds = *bld_bounds.result(); + //cout << bounds.min() << " - " << bounds.max() << endl; + for(auto & path : paths) { + for(auto & j : path) { + j = map_point(j, bounds, + Point(0,512), Point(512*bounds[0].extent()/bounds[1].extent(),0)); + } + } + /* + for(map<string,Point>::iterator it = idlookup.begin(); + it != idlookup.end(); it++) { + (*it).second = map_point((*it).second, bounds, + Point(0,0), Point(100,100)); + //Point(0, 512), Point(512,0)); + cout << (*it).first << ":" << (*it).second << endl; + }*/ + /* + unsigned biggest = 0, biggest_i; + for(unsigned i=0;i<paths.size();i++) { + vector<Point> ps=paths[i]; + if(ps.size()>biggest) { + biggest_i=i; + biggest = ps.size(); + } + for(unsigned j=0;j<ps.size();j++) { + double x=ps[j][0], y=ps[j][1]; + cout << "(" << x << "," << y << ")" << ","; + } + cout << endl; + } + */ +} + +void extremePoints(vector<Point> const & pts, Point const & dir, + Point & min, Point & max) { + double minProj = DBL_MAX, maxProj = -DBL_MAX; + for(auto pt : pts) { + double p = dot(pt,dir); + if(p < minProj) { + minProj = p; + min = pt; + } + if(p > maxProj) { + maxProj = p; + max = pt; + } + } +} + +void fit::test() { + sufficient_stats ss; + const unsigned N = input.size(); + for(unsigned i = 0; i < N; i++) { + ss += input[i]; + } + double best_bl = DBL_MAX; + unsigned best = 0; + for(unsigned i=0;i<angles.size();i++) { + Point n = unit_vector(rot90(angles[i])); + double dist = dot(n, input[0]); + double mean; + double bl = ss.best_line(n, dist, mean); + if(bl < best_bl) { + best = i; + best_bl = bl; + } + mean/=N; + Point d = angles[i]; + Point a = mean*n; + Point min, max; + extremePoints(input,d,min,max); + Point b = dot(min,d)*d; + Point c = dot(max,d)*d; + Point start = a+b; + Point end = a+c; + lines.emplace_back(start,end); + thickness.push_back(1); + } + thickness[best] = 4; +} + +void fit::schematised_merging(unsigned number_of_directions) { + const double link_cost = 0; + const unsigned N = input.size()-1; + blocks.resize(N); + for(unsigned i = 0; i<number_of_directions ; i++) { + double t = M_PI*i/float(number_of_directions); + angles.emplace_back(cos(t),sin(t)); + } + // pairs + for(unsigned i = 0; i < N; i++) { + block b; + sufficient_stats ss; + ss += input[i]; + ss += input[i+1]; + b.ss = ss; + double mean, newcost; + b.angle = ss.best_schematised_line(angles, input[i], mean, newcost); + b.cost = link_cost + newcost; + b.next = i+1; + blocks[i] = b; + //std::cout << ss + //<< std::endl; + } + + // merge(a,b) + while(N>1) + { + block best_block; + unsigned best_idx = 0; + double best = 0; + unsigned beg = 0; + unsigned middle = blocks[beg].next; + unsigned end = blocks[middle].next; + while(middle < N) { + sufficient_stats ss = blocks[beg].ss + blocks[middle].ss; + //ss -= input[middle]; + double mean, newcost; + unsigned bestAngle = ss.best_schematised_line(angles, input[beg], mean, newcost); + double deltaCost = -link_cost - blocks[beg].cost - blocks[middle].cost + + newcost; + /*std::cout << beg << ", " + << middle << ", " + << end << ", " + << deltaCost <<"; " + << newcost <<"; " + << mean << ": " + << ss + << std::endl;*/ + //if(deltaCost < best) { + if(blocks[beg].angle==blocks[middle].angle) { + best = deltaCost; + best = -1; + best_idx = beg; + best_block.ss = ss; + best_block.cost = newcost; + best_block.next = end; + best_block.angle = bestAngle; + } + beg = middle; + middle = end; + end = blocks[end].next; + } + if(best < 0) + blocks[best_idx] = best_block; + else // no improvement possible + break; + } + { + solution.resize(0); // empty list + unsigned beg = 0; + unsigned prev = 0; + while(beg < N) { + block b = blocks[beg]; + { + Point n, c; + Point n1, c1; + Point d = angles[b.angle]; + get_block_line(b,d,n,c); + Point start = c, end = c+10*angles[b.angle]; + Line ln = Line::from_normal_distance(n, dot(c,n)); + if(beg==0) { + //start = intersection of b.line and + // line through input[0] orthogonal to b.line + OptCrossing c = intersection(ln, + Line::from_normal_distance(d, dot(d,input[0]))); + assert(c); + start = ln.pointAt(c->ta); + //line_intersection(n, dot(c,n), d, dot(d,input[0]), start); + } else { + //start = intersection of b.line and blocks[prev].line + block p = blocks[prev]; + if(b.angle!=p.angle) { + get_block_line(p,angles[p.angle],n1,c1); + //line_intersection(n, dot(c,n), n1, dot(c1,n1), start); + OptCrossing c = intersection(ln, + Line::from_normal_distance(n1, dot(c1,n1))); + assert(c); + start = ln.pointAt(c->ta); + } + } + + if (b.next < N) { + //end = intersection of b.line and blocks[b.next].line + block next = blocks[b.next]; + if(b.angle!=next.angle) { + get_block_line(next,angles[next.angle],n1,c1); + //line_intersection(n, dot(c,n), n1, dot(c1,n1), end); + OptCrossing c = intersection(ln, + Line::from_normal_distance(n1, dot(c1,n1))); + assert(c); + end = ln.pointAt(c->ta); + } + } else { + //end = intersection of b.line and + // line through input[N-1] orthogonal to b.line + //line_intersection(n, dot(c,n), d, dot(d,input[N]), end); + OptCrossing c = intersection(ln, + Line::from_normal_distance(d, dot(d,input[N]))); + assert(c); + end = ln.pointAt(c->ta); + } + lines.emplace_back(start,end); + } + prev = beg; + beg = b.next; + } + } +} +void fit::merging_version() { + const double link_cost = 100; + const unsigned N = input.size(); + blocks.resize(N); + // pairs + for(unsigned i = 0; i < N; i++) { + block b; + sufficient_stats ss; + ss.Sx = ss.Sy = ss.Sxx = ss.Sxy = ss.Syy = 0; + ss.n = 0; + ss += input[i]; + ss += input[i+1]; + b.ss = ss; + b.cost = link_cost; + b.next = i+1; + blocks[i] = b; + //std::cout << ss + //<< std::endl; + } + + // merge(a,b) + while(1) + { + block best_block; + unsigned best_idx = 0; + double best = 0; + unsigned beg = 0; + unsigned middle = blocks[beg].next; + unsigned end = blocks[middle].next; + while(end != N) { + sufficient_stats ss = blocks[beg].ss + blocks[middle].ss; + ss -= input[middle]; + double mean; + Point normal = unit_vector(rot90(input[end] - input[beg])); + double dist = dot(normal, input[beg]); + double newcost = ss.best_line(normal, dist, mean); + double deltaCost = -link_cost - blocks[beg].cost - blocks[middle].cost + + newcost; + /*std::cout << beg << ", " + << middle << ", " + << end << ", " + << deltaCost <<"; " + << newcost <<"; " + << mean << ": " + << ss + << std::endl;*/ + if(deltaCost < best) { + best = deltaCost; + best_idx = beg; + best_block.ss = ss; + best_block.cost = newcost; + best_block.next = end; + } + beg = middle; + middle = end; + end = blocks[end].next; + } + if(best < 0) + blocks[best_idx] = best_block; + else // no improvement possible + break; + } + { + solution.resize(0); // empty list + unsigned beg = 0; + while(beg != N) { + solution.push_back(input[beg]); + beg = blocks[beg].next; + } + } +} + + +void fit::arbitrary() { + /*solution.resize(input.size()); + copy(input.begin(), input.end(), solution.begin());*/ + // normals + + double best_error = INFINITY; + double best_mean = 0; + unsigned best_angle = 0; + for(unsigned i = 0; i < angles.size(); i++) { + Point angle = angles[i]; + double mean = 0; + double error = 0; + for(unsigned l = 0; l < input.size(); l++) { + mean += dot(input[i], angle); + } + mean /= input.size(); + for(unsigned l = 0; l < input.size(); l++) { + double d = dot(input[i], angle) - mean; + error += d*d; + } + if(error < best_error) { + best_mean = mean; + best_error = error; + best_angle = i; + } + } + Point angle = angles[best_angle]; + solution.push_back(angle*best_mean + dot(input[0], rot90(angle))*rot90(angle)); + solution.push_back(angle*best_mean + dot(input.back(), rot90(angle))*rot90(angle)); +} + +class reg_line{ +public: + Point parallel, centre, normal; + double Sr, Srr; + unsigned n; +}; + +template<class T> +reg_line +line_best_fit(T b, T e) { + double Sx = 0, + Sy = 0, + Sxx = 0, + Syy = 0, + Sxy = 0; + unsigned n = e - b; + reg_line rl; + rl.n = n; + for(T p = b; p != e; p++) { + Sx += (*p)[0]; + Sy += (*p)[1]; + Sxx += (*p)[0]*(*p)[0]; + Syy += (*p)[1]*(*p)[1]; + Sxy += (*p)[0]*(*p)[1]; + } + + rl.parallel = unit_vector(Point(n*Sxx - Sx*Sx, + n*Sxy - Sx*Sy)); + rl.normal = rot90(rl.parallel); + rl.centre = Point(Sx/n, Sy/n); + rl.Sr = rl.Srr = 0; + for(T p = b; p != e; p++) { + double r = dot(rl.parallel, (*p) - rl.centre); + rl.Sr += fabs(r); + rl.Srr += r*r; + } + return rl; +} + +void fit::linear_reg() { + reg_line rl = line_best_fit(input.begin(), + input.end()); + solution.push_back(rl.centre + dot(rl.parallel, input[0] - rl.centre)*rl.parallel); + solution.push_back(rl.centre + dot(rl.parallel, input.back() - rl.centre)*rl.parallel); +} + +void fit::simple_dp() { + const unsigned N = input.size(); + vector<unsigned> prev(N); + vector<double> penalty(N); + const double bend_pen = 100; + + for(unsigned i = 1; i < input.size(); i++) { + double mean; + double best = measure(0, i, mean); + unsigned best_prev = 0; + for(unsigned j = 1; j < i; j++) { + double err = penalty[j] + bend_pen + measure(j, i, mean); + if(err < best) { + best = err; + best_prev = j; + } + } + penalty[i] = best; + prev[i] = best_prev; + } + + unsigned i = prev.size()-1; + while(i > 0) { + solution.push_back(input[i]); + i = prev[i]; + } + solution.push_back(input[i]); + reverse(solution.begin(), solution.end()); +} + +void fit::C_endpoint() { + const unsigned N = input.size(); + + double best_mean; + double best = best_angled_line(0, N-1, angles[0], best_mean); + unsigned best_dir = 0; + for(unsigned c = 1; c < angles.size(); c++) { + double m; + double err = best_angled_line(0, N-1, angles[c], m); + if(err < best) { + best = err; + best_mean = m; + best_dir = c; + } + + } + Point dir = angles[best_dir]; + Point dirT = rot90(dir); + Point centre = dir*best_mean/N; + + solution.push_back(centre + dot(dirT, input[0] - centre)*dirT); + solution.push_back(centre + dot(dirT, input.back() - centre)*dirT); +} + +void fit::C_simple_dp() { + const unsigned N = input.size(); + + vector<int> prev(N); + vector<double> penalty(N); + vector<unsigned> dir(N); + vector<double> mean(N); + const double bend_pen = 0; + + for(unsigned i = 1; i < input.size(); i++) { + double best_mean; + double best = best_angled_line(0, i, angles[0], best_mean); + unsigned best_prev = 0; + unsigned best_dir = 0; + for(unsigned c = 1; c < angles.size(); c++) { + double m; + double err = best_angled_line(0, i, angles[c], m); + if(err < best) { + best = err; + best_mean = m; + best_dir = c; + best_prev = 0; + } + + } + for(unsigned j = 1; j < i; j++) { + for(unsigned c = 0; c < angles.size(); c++) { + double m; + if(c == dir[j]) + continue; + double err = penalty[j] + bend_pen + + best_angled_line(j, i, angles[c], m); + if(err < best) { + best = err; + best_mean = m; + best_dir = c; + best_prev = j; + } + + } + } + penalty[i] = best; + prev[i] = best_prev; + dir[i] = best_dir; + mean[i] = best_mean; + } + + prev[0] = -1; + unsigned i = prev.size()-1; + unsigned pi = i; + while(i > 0) { + Point bdir = angles[dir[i]]; + Point bdirT = rot90(bdir); + Point centre = bdir*mean[i]/N; + solution.push_back(centre + dot(bdirT, input[i] - centre)*bdirT); + solution.push_back(centre + dot(bdirT, input[pi] - centre)*bdirT); + pi = i; + i = prev[i]; + } + /*Point a = angles[dir[i]]; + Point aT = rot90(a); + solution.push_back(a*mean[i] + + dot(input[i], aT)*aT);*/ + reverse(solution.begin(), solution.end()); +} + + + + + + +class MetroMap: public Toy { +public: + vector<PointSetHandle> metro_lines; + PointHandle directions; + + bool should_draw_numbers() override { return false; } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + double slider_margin = 20; + double slider_top = 20; + double slider_bot = 200; + directions.pos[X] = slider_margin; + if (directions.pos[Y]<slider_top) directions.pos[Y] = slider_top; + if (directions.pos[Y]>slider_bot) directions.pos[Y] = slider_bot; + + unsigned num_directions = 2 + 15*(slider_bot-directions.pos[Y])/(slider_bot-slider_top); + + cairo_move_to(cr,Geom::Point(slider_margin,slider_bot)); + cairo_line_to(cr,Geom::Point(slider_margin,slider_top)); + cairo_set_line_width(cr,.5); + cairo_set_source_rgba (cr, 0., 0.3, 0., 1.); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0.5, 0, 1); + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 1); + + unsigned N= paths.size(); + for(unsigned i=0;i<N;i++) { + double R,G,B; + convertHSVtoRGB(360.*double(i)/double(N),1,0.75,R,G,B); + metro_lines[i].rgb[0] = R; + metro_lines[i].rgb[1] = G; + metro_lines[i].rgb[2] = B; + cairo_set_source_rgba (cr, R, G, B, 0.8); + fit f(metro_lines[i].pts); + f.schematised_merging(num_directions); + f.draw(cr); + cairo_stroke(cr); + } + cairo_set_source_rgba (cr, 0., 0., 0, 1); + { + PangoLayout* layout = pango_cairo_create_layout (cr); + pango_layout_set_text(layout, + notify->str().c_str(), -1); + + PangoFontDescription *font_desc = pango_font_description_new(); + pango_font_description_set_family(font_desc, "Sans"); + const unsigned size_px = 10; + pango_font_description_set_absolute_size(font_desc, size_px * 1024.0); + pango_layout_set_font_description(layout, font_desc); + PangoRectangle logical_extent; + pango_layout_get_pixel_extents(layout, + NULL, + &logical_extent); + cairo_move_to(cr, 0, height-logical_extent.height); + pango_cairo_show_layout(cr, layout); + } + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void first_time(int argc, char** argv) override { + string location_file_name("data/london-locations.csv"); + string path_file_name("data/london.txt"); + if(argc > 2) { + location_file_name = argv[1]; + path_file_name = argv[2]; + } + cout << location_file_name << ", " << path_file_name << endl; + parse_data(paths, location_file_name, path_file_name); + for(auto & path : paths) { + metro_lines.emplace_back(); + for(auto & j : path) { + metro_lines.back().push_back(j); + } + } + for(auto & metro_line : metro_lines) { + handles.push_back(&metro_line); + } + handles.push_back(&directions); + } + + MetroMap() { + } + +}; + + + + + +int main(int argc, char **argv) { + init(argc, argv, new MetroMap()); + + return 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/toys/minsb2d-solver.cpp b/src/toys/minsb2d-solver.cpp new file mode 100644 index 0000000..f2df9d9 --- /dev/null +++ b/src/toys/minsb2d-solver.cpp @@ -0,0 +1,376 @@ +#include <2geom/sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+using std::vector;
+using namespace Geom;
+
+
+//see a sb2d as an sb of u with coef in sbasis of v.
+void
+u_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.vs, Linear());
+ b = SBasis(f.vs, Linear());
+ for (unsigned v=0; v<f.vs; v++){
+ a[v] = Linear(f.index(deg,v)[0], f.index(deg,v)[2]);
+ b[v] = Linear(f.index(deg,v)[1], f.index(deg,v)[3]);
+ }
+}
+void
+v_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.us, Linear());
+ b = SBasis(f.us, Linear());
+ for (unsigned u=0; u<f.us; u++){
+ a[u] = Linear(f.index(deg,u)[0], f.index(deg,u)[1]);
+ b[u] = Linear(f.index(deg,u)[2], f.index(deg,u)[3]);
+ }
+}
+
+
+
+//TODO: implement sb2d algebra!!
+SBasis2d y_x2(){
+ SBasis2d result(Linear2d(0,-1,1,0));
+ result.push_back(Linear2d(1,1,1,1));
+ result.us = 2;
+ result.vs = 1;
+ return result;
+}
+
+SBasis2d x2_plus_y2_1(){
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-4,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ D2<SBasis> seg(SBasis(0.,1.), SBasis(i*1./NbRays, i*1./NbRays));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ for (int i=0; i<NbRays; i++){
+ D2<SBasis> seg(SBasis(i*1./NbRays, i*1./NbRays), SBasis(0.,1.));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+}
+
+void
+plot3d_top(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ for(int j=0; j<2; j++){
+ D2<SBasis> seg;
+ if (j==0){
+ seg = D2<SBasis>(SBasis(0.,1.), SBasis(i*1./NbRays, i*1./NbRays));
+ }else{
+ seg = D2<SBasis>(SBasis(i*1./NbRays,i*1./NbRays), SBasis(0.,1.));
+ }
+ SBasis f_on_seg = compose(f,seg);
+ std::vector<double> rts = roots(f_on_seg);
+ if (rts.size()==0||rts.back()<1) rts.push_back(1.);
+ double t1,t0 = 0;
+ for (unsigned i=(rts.front()<=0?1:0); i<rts.size(); i++){
+ t1 = rts[i];
+ if (f_on_seg((t0+t1)/2)>0)
+ plot3d(cr,seg[X](Linear(t0,t1)),seg[Y](Linear(t0,t1)),f_on_seg(Linear(t0,t1)),frame);
+ t0=t1;
+ }
+ //plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ }
+}
+#include <gsl/gsl_multimin.h>
+
+class Sb2dSolverToy: public Toy {
+public:
+ PointSetHandle hand;
+ Sb2dSolverToy() {
+ handles.push_back(&hand);
+ }
+
+ class bits_n_bobs{
+ public:
+ SBasis2d * ff;
+ Point A, B;
+ Point dA, dB;
+ };
+ static double
+ my_f (const gsl_vector *v, void *params)
+ {
+ double x, y;
+ bits_n_bobs* bnb = (bits_n_bobs *)params;
+
+ x = gsl_vector_get(v, 0);
+ y = gsl_vector_get(v, 1);
+ Bezier b0(bnb->B[0], bnb->B[0]+bnb->dB[0]*x, bnb->A[0]+bnb->dA[0]*y, bnb->A[0]);
+ Bezier b1(bnb->B[1], bnb->B[1]+bnb->dB[1]*x, bnb->A[1]+bnb->dA[1]*y, bnb->A[1]);
+
+ D2<SBasis> zeroset(b0.toSBasis(), b1.toSBasis());
+
+ SBasis comp = compose((*bnb->ff),zeroset);
+ Interval bounds = *bounds_fast(comp);
+ double error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ //printf("error = %g %g %g\n", bounds.max(), bounds.min(), error);
+ return error*error;
+ }
+
+
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+/*
+ SBasis2d f = y_x2();
+ D2<SBasis> true_solution(Linear(0,1),Linear(0,1));
+ true_solution[Y].push_back(Linear(-1,-1));
+ SBasis zero = SBasis(Linear(0.));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+*/
+
+ SBasis2d f = x2_plus_y2_1();
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.141592/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.141592/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ //Geom::Point A(cos(tA*M_PI/2), sin(tA*M_PI/2));// = true_solution(tA);
+ //Geom::Point B(cos(tB*M_PI/2), sin(tB*M_PI/2));// = true_solution(tB);
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+ Point dA(-sin(tA*M_PI/2), cos(tA*M_PI/2));
+ Geom::Point dB(-sin(tB*M_PI/2), cos(tB*M_PI/2));
+ 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]));
+ dA = rot90(dfA);
+ dB = rot90(dfB);
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ plot3d_top(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ plot3d(cr, true_solution[X], true_solution[Y], zero, frame);
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+ double error;
+ for(int degree = 2; degree < 2; degree++) {
+ D2<SBasis> zeroset = sb2dsolve(f,A,B,degree);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ }
+ if (1) {
+
+ bits_n_bobs par = {&f, A, B, dA, dB};
+ bits_n_bobs* bnb = ∥
+ std::cout << f[0] << "= initial f \n";
+ const gsl_multimin_fminimizer_type *T =
+ gsl_multimin_fminimizer_nmsimplex;
+ gsl_multimin_fminimizer *s = NULL;
+ gsl_vector *ss, *x;
+ gsl_multimin_function minex_func;
+
+ size_t iter = 0;
+ int status;
+ double size;
+
+ /* Starting point */
+ x = gsl_vector_alloc (2);
+ gsl_vector_set (x, 0, 0.552); // magic number for quarter circle
+ gsl_vector_set (x, 1, 0.552);
+
+ /* Set initial step sizes to 1 */
+ ss = gsl_vector_alloc (2);
+ gsl_vector_set_all (ss, 0.1);
+
+ /* Initialize method and iterate */
+ minex_func.n = 2;
+ minex_func.f = &my_f;
+ minex_func.params = (void *)∥
+
+ s = gsl_multimin_fminimizer_alloc (T, 2);
+ gsl_multimin_fminimizer_set (s, &minex_func, x, ss);
+
+ do
+ {
+ iter++;
+ status = gsl_multimin_fminimizer_iterate(s);
+
+ if (status)
+ break;
+
+ size = gsl_multimin_fminimizer_size (s);
+ status = gsl_multimin_test_size (size, 1e-7);
+
+ if (status == GSL_SUCCESS)
+ {
+ printf ("converged to minimum at\n");
+ }
+
+ }
+ while (status == GSL_CONTINUE && iter < 100);
+ printf ("%5lu %g %gf f() = %g size = %g\n",
+ iter,
+ gsl_vector_get (s->x, 0),
+ gsl_vector_get (s->x, 1),
+ s->fval, size);
+ {
+ double x = gsl_vector_get(s->x, 0);
+ double y = gsl_vector_get(s->x, 1);
+ Bezier b0(bnb->B[0], bnb->B[0]+bnb->dB[0]*x, bnb->A[0]+bnb->dA[0]*y, bnb->A[0]);
+ Bezier b1(bnb->B[1], bnb->B[1]+bnb->dB[1]*x, bnb->A[1]+bnb->dA[1]*y, bnb->A[1]);
+
+ D2<SBasis> zeroset(b0.toSBasis(), b1.toSBasis());
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+
+ }
+
+ gsl_vector_free(x);
+ gsl_vector_free(ss);
+ gsl_multimin_fminimizer_free (s);
+ }
+ *notify << "Gray: f-graph and true solution,\n";
+ *notify << "Red: solver solution,\n";
+ *notify << "Purple: value of f over solver solution.\n";
+ *notify << " error: "<< error <<".\n";
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2dSolverToy());
+ return 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/toys/nasty.svg b/src/toys/nasty.svg new file mode 100644 index 0000000..4457044 --- /dev/null +++ b/src/toys/nasty.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.45" + sodipodi:modified="true"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35" + inkscape:cx="375" + inkscape:cy="481.62572" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="910" + inkscape:window-height="626" + inkscape:window-x="5" + inkscape:window-y="49" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 387.63906,267.9784 L 106.72661,469.82569 L 106.72661,267.9784 L 387.63906,469.82569 L 387.63906,267.9784 z " + id="path2160" + sodipodi:nodetypes="ccccc" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 540.4278,232.25903 C 440.92573,232.25903 646.18067,452.19982 540.4278,452.19982 C 437.36839,452.19982 639.73953,232.25903 540.4278,232.25903 z " + id="path2162" + sodipodi:nodetypes="csz" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 282.85714,578.07647 C 74.285714,832.36218 165.33193,1033.3172 288.57143,900.93361 C 410.47619,769.98377 73.333332,831.45467 282.85714,578.07647 z " + id="path2182" + sodipodi:nodetypes="czc" /> + <path + id="path2186" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 548.57143,662.36218 C 615.72948,662.36218 625.35777,906.64794 728.57143,906.64794 C 832.06154,906.64794 637.327,662.36218 548.57143,662.36218 C 459.95393,662.36218 291.30957,906.64794 428.57143,906.64794 C 534.44005,906.64794 481.41338,662.36218 548.57143,662.36218 z " + sodipodi:nodetypes="czzsz" /> + </g> +</svg> diff --git a/src/toys/nearest-times.cpp b/src/toys/nearest-times.cpp new file mode 100644 index 0000000..66803d6 --- /dev/null +++ b/src/toys/nearest-times.cpp @@ -0,0 +1,258 @@ +/* + * Nearest Points Toy + * + * Authors: + * Nathan Hurst <njh at njhurst.com> + * 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/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework.h> + + +using namespace Geom; + + +class np_finder +{ +public: + np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2) + : cr(_cr), c1(_c1), c2(_c2) + { + dc1 = derivative(c1); + dc2 = derivative(c2); + cd1 = dot(c1,dc1); + cd2 = dot(c2,dc2); + dsq = 10e30; + } + + void operator() () + { + nearest_times_impl(0.5, 0, 1); + d = sqrt(dsq); + } + + Point firstPoint() const + { + return p1; + } + + Point secondPoint() const + { + return p2; + } + + double firstValue() const + { + return t1; + } + + double secondValue() const + { + return t2; + } + + double distance() const + { + return d; + } + +private: + void nearest_times_impl( double t, double from = 0, double to = 1 ) + { + //std::cerr << "[" << from << "," << to << "] t: " << t << std::endl; + + double st = t, et = t; + std::pair<double, double> npc = loc_nearest_times(t, from, to); + //std::cerr << "(" << npc.first << "," << npc.second << ")" << std::endl; + if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) ) + { + t1 = npc.first; + t2 = npc.second; + p1 = c1(t1); + p2 = c2(t2); + dsq = L2sq(p1 - p2); + } + if (npc.first < t) + st = npc.first; + else + et = npc.first; + //std::cerr << "[" << st << "," << et << "]" << std::endl; + double d1 = std::fabs(st - from); + double d2 = std::fabs(to - et); + if ( d1 > EPSILON ) + nearest_times_impl(from + d1/2, from, st); + if ( d2 > EPSILON ) + nearest_times_impl(et + d2/2, et, to); + } + + std::pair<double, double> + loc_nearest_times( double t, double from = 0, double to = 1 ) + { + unsigned int i = 0; + std::pair<double, double> np(-1,-1); + std::pair<double, double> npf(from, -1); + std::pair<double, double> npt(to, -1); + double ct = t; + double pt = -1; + double s; + //cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0); + //cairo_move_to(cr, c1(t)); + while( !are_near(ct, pt) ) + { + ++i; + pt = ct; + s = nearest_time(c1(ct), c2, dc2, cd2); + //std::cerr << "s: " << s << std::endl; + //cairo_line_to(cr, c2(s)); + ct = nearest_time(c2(s), c1, dc1, cd1, from, to); + //std::cerr << "t: " << t1 << std::endl; + //cairo_line_to(cr, c1(ct)); + if ( ct < from ) return npf; + if ( ct > to ) return npt; + } + //std::cerr << "\n \n"; + //std::cerr << "iterations: " << i << std::endl; + cairo_stroke(cr); + np.first = ct; + np.second = s; + return np; + } + + double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 ) + { + D2<SBasis> sbc = c - p; + SBasis dd = cd - dotp(p, dc); + std::vector<double> zeros = roots(dd); + double closest = from; + double distsq = L2sq(sbc(from)); + for ( unsigned int i = 0; i < zeros.size(); ++i ) + { + if ( distsq > L2sq(sbc(zeros[i])) ) + { + closest = zeros[i]; + distsq = L2sq(sbc(closest)); + } + } + if ( distsq > L2sq(sbc(to)) ) + closest = to; + return closest; + } + + SBasis dotp(Point const& p, D2<SBasis> const& c) + { + SBasis d; + d.resize(c[X].size()); + for ( unsigned int i = 0; i < c[0].size(); ++i ) + { + for( unsigned int j = 0; j < 2; ++j ) + d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j]; + } + return d; + } + +private: + static const Coord EPSILON = 10e-3; + cairo_t* cr; + D2<SBasis> const& c1, c2; + D2<SBasis> dc1, dc2; + SBasis cd1, cd2; + double t1, t2, d, dsq; + Point p1, p2; +}; + + + + +class NearestPoints : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) + { + cairo_set_line_width (cr, 0.2); + D2<SBasis> A = handles_to_sbasis(handles.begin(), A_bez_ord-1); + cairo_d2_sb(cr, A); + D2<SBasis> B = handles_to_sbasis(handles.begin()+A_bez_ord, B_bez_ord-1); + cairo_d2_sb(cr, B); + + + np_finder np(cr, A, B); + np(); + cairo_move_to(cr, np.firstPoint()); + cairo_line_to(cr, np.secondPoint()); + cairo_stroke(cr); + //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + unsigned int total_handles = A_bez_ord + B_bez_ord; + for ( unsigned int i = 0; i < total_handles; ++i ) + handles.push_back(Geom::Point(uniform()*400, uniform()*400)); + } + + private: + unsigned int A_bez_ord; + unsigned int B_bez_ord; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=8; + unsigned int B_bez_ord=5; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord)); + return 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/toys/nearest-times2.cpp b/src/toys/nearest-times2.cpp new file mode 100644 index 0000000..04cd9e6 --- /dev/null +++ b/src/toys/nearest-times2.cpp @@ -0,0 +1,313 @@ +/* + * Nearest Points Toy 2 + * + * Authors: + * Nathan Hurst <njh at njhurst.com> + * 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/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework.h> + +#include <algorithm> + + +using namespace Geom; + + +class np_finder +{ +public: + np_finder(cairo_t* _cr, D2<SBasis> const& _c1, D2<SBasis> const& _c2) + : cr(_cr), c1(_c1), c2(_c2) + { + dc1 = derivative(c1); + dc2 = derivative(c2); + cd1 = dot(c1,dc1); + cd2 = dot(c2,dc2); + dsq = 10e30; + + Piecewise<SBasis> k1 = curvature(c1, EPSILON); + Piecewise<SBasis> k2 = curvature(c2, EPSILON); + Piecewise<SBasis> dk1 = derivative(k1); + Piecewise<SBasis> dk2 = derivative(k2); + std::vector<double> k1_roots = roots(k1); + std::vector<double> k2_roots = roots(k2); + std::vector<double> dk1_roots = roots(dk1); + std::vector<double> dk2_roots = roots(dk2); + tlist.clear(); + tlist.resize(k1_roots.size() + k2_roots.size() + dk1_roots.size() + dk2_roots.size() + 4); + tlist.push_back(0); + tlist.insert(tlist.end(), dk1_roots.begin(), dk1_roots.end()); + tlist.insert(tlist.end(), k1_roots.begin(), k1_roots.end()); +// std::cerr << "dk1 roots: "; +// for ( unsigned int i = 0; i < dk1_roots.size(); ++i ) +// { +// std::cerr << dk1_roots[i] << " "; +// } +// std::cerr << "\n"; + for ( unsigned int i = 0; i < k2_roots.size(); ++i ) + { + tlist.push_back(nearest_time(c2(k2_roots[i]), c1, dc1, cd1)); + } + + for ( unsigned int i = 0; i < dk2_roots.size(); ++i ) + { + tlist.push_back(nearest_time(c2(dk2_roots[i]), c1, dc1, cd1)); + } + tlist.push_back(nearest_time(c2(0), c1, dc1, cd1)); + tlist.push_back(nearest_time(c2(1), c1, dc1, cd1)); + tlist.push_back(1); + std::sort(tlist.begin(), tlist.end()); + std::vector<double>::iterator pos + = std::unique(tlist.begin(), tlist.end(), are_near_() ); + if (pos != tlist.end()) + { + tlist.erase(pos, tlist.end()); + } + for( unsigned int i = 0; i < tlist.size(); ++i ) + { + draw_circ(cr, c1(tlist[i]) ); + } + } + + void operator() () + { + //nearest_times_impl( tlist.size() / 2, 0, tlist.size() - 1 ); + nearest_times_impl(); + d = sqrt(dsq); + } + + Point firstPoint() const + { + return p1; + } + + Point secondPoint() const + { + return p2; + } + + double firstValue() const + { + return t1; + } + + double secondValue() const + { + return t2; + } + + double distance() const + { + return d; + } + +private: + void nearest_times_impl() + { + double t; + double from = tlist[0]; + double to; + for ( unsigned int i = 1; i < tlist.size(); ++i ) + { + to = tlist[i]; + t = from + (to - from) / 2 ; + std::pair<double, double> npc = loc_nearest_times(t, from, to); + if ( npc.second != -1 && dsq > L2sq(c1(npc.first) - c2(npc.second)) ) + { + t1 = npc.first; + t2 = npc.second; + p1 = c1(t1); + p2 = c2(t2); + dsq = L2sq(p1 - p2); + } + from = tlist[i]; + } + } + + std::pair<double, double> + loc_nearest_times( double t, double from = 0, double to = 1 ) + { + //std::cerr << "[" << from << "," << to << "] t: " << t << std::endl; + unsigned int i = 0; + std::pair<double, double> np(-1,-1); + std::pair<double, double> npf(from, -1); + std::pair<double, double> npt(to, -1); + double ct = t; + double pt = -1; + double s; + //cairo_set_source_rgba(cr, 1/(t+1), t*t, t, 1.0); + cairo_move_to(cr, c1(t)); + while( !are_near(ct, pt) ) + { + ++i; + pt = ct; + s = nearest_time(c1(ct), c2, dc2, cd2); + //std::cerr << "s: " << s << std::endl; + //cairo_line_to(cr, c2(s)); + ct = nearest_time(c2(s), c1, dc1, cd1, from, to); + //std::cerr << "t: " << ct << std::endl; + //cairo_line_to(cr, c1(ct)); + //std::cerr << "d(pt, ct) = " << std::fabs(ct - pt) << std::endl; + if ( ct < from ) return npf; + if ( ct > to ) return npt; + } + //std::cerr << "\n \n"; + std::cerr << "iterations: " << i << std::endl; + //cairo_move_to(cr, c1(ct)); + //cairo_line_to(cr, c2(s)); + cairo_stroke(cr); + np.first = ct; + np.second = s; + return np; + } + + double nearest_time( Point const& p, D2<SBasis> const&c, D2<SBasis> const& dc, SBasis const& cd, double from = 0, double to = 1 ) + { + D2<SBasis> sbc = c - p; + SBasis dd = cd - dotp(p, dc); + std::vector<double> zeros = roots(dd); + double closest = from; + double distsq = L2sq(sbc(from)); + for ( unsigned int i = 0; i < zeros.size(); ++i ) + { + if ( distsq > L2sq(sbc(zeros[i])) ) + { + closest = zeros[i]; + distsq = L2sq(sbc(closest)); + } + } + if ( distsq > L2sq(sbc(to)) ) + closest = to; + return closest; + } + + SBasis dotp(Point const& p, D2<SBasis> const& c) + { + SBasis d; + d.resize(c[X].size()); + for ( unsigned int i = 0; i < c[0].size(); ++i ) + { + for( unsigned int j = 0; j < 2; ++j ) + d[i][j] = p[X] * c[X][i][j] + p[Y] * c[Y][i][j]; + } + return d; + } + + struct are_near_ + { + bool operator() (double x, double y, double eps = Geom::EPSILON ) + { + return are_near(x, y, eps); + } + }; + +private: + static const Coord EPSILON = 10e-5; + cairo_t* cr; + D2<SBasis> const& c1, c2; + D2<SBasis> dc1, dc2; + SBasis cd1, cd2; + double t1, t2, d, dsq; + Point p1, p2; + std::vector<double> tlist; +}; + + + + +class NearestPoints : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) + { + cairo_set_line_width (cr, 0.2); + D2<SBasis> A = handles_to_sbasis(handles.begin(), A_bez_ord-1); + cairo_d2_sb(cr, A); + D2<SBasis> B = handles_to_sbasis(handles.begin()+A_bez_ord, B_bez_ord-1); + cairo_d2_sb(cr, B); + + + np_finder np(cr, A, B); + np(); + cairo_move_to(cr, np.firstPoint()); + cairo_line_to(cr, np.secondPoint()); + cairo_stroke(cr); + //std::cerr << "np: (" << np.firstValue() << "," << np.secondValue() << ")" << std::endl; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + NearestPoints(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + unsigned int total_handles = A_bez_ord + B_bez_ord; + for ( unsigned int i = 0; i < total_handles; ++i ) + handles.push_back(Geom::Point(uniform()*400, uniform()*400)); + } + + private: + unsigned int A_bez_ord; + unsigned int B_bez_ord; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=8; + unsigned int B_bez_ord=5; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + init( argc, argv, new NearestPoints(A_bez_ord, B_bez_ord)); + return 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/toys/normal-bundle.cpp b/src/toys/normal-bundle.cpp new file mode 100644 index 0000000..efe7536 --- /dev/null +++ b/src/toys/normal-bundle.cpp @@ -0,0 +1,230 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +class NormalBundle : public std::vector<D2<SBasis2d> >{ +public: + vector<double> lengths; + NormalBundle(){lengths.push_back(0.);} + void setBase(D2<SBasis> const &B, double tol); + void draw(cairo_t* cr, unsigned NbSections =5,unsigned NbFibre=5); +}; +vector<D2<SBasis> > compose(NormalBundle const &NB, + D2<SBasis> const &Binit, + Geom::Point Origin=Geom::Point(0,0)); + +//-------------------------------------------- + +void SBasis1d_to_2d(D2<SBasis> C0, + D2<SBasis> C1, + D2<SBasis2d> &S){ + for(unsigned dim = 0; dim < 2; dim++) { +//**** C0 and C1 should have the same size... + for (unsigned i=C0[dim].size();i<C1[dim].size(); i++) + C0[dim].push_back(Linear(0)); + for (unsigned i=C1[dim].size();i<C0[dim].size(); i++) + C1[dim].push_back(Linear(0)); + S[dim].clear(); + S[dim].us = C0[dim].size(); + S[dim].vs = 1; + for(unsigned v = 0; v < S[dim].vs; v++) + for(unsigned u = 0; u < S[dim].us; u++) + S[dim].push_back(Linear2d(C0[dim][u][0],C0[dim][u][1], + C1[dim][u][0],C1[dim][u][1])); + } +} + +void NormalBundle::setBase(D2<SBasis> const &B, double tol=0.01) { + + D2<SBasis> dB = derivative(B); + vector<double> cuts; + Piecewise<D2<SBasis> > unitV=unitVector(dB); + + //TODO: clean this up, use arc_length_parametrization... + cuts=unitV.cuts; + double t0=0,t1,L=0; + for(unsigned i=1;i<cuts.size();i++){ + t1=cuts[i]; + D2<SBasis> subB=compose(B,Linear(t0,t1)); + D2<SBasis2d> S; + SBasis1d_to_2d(subB, subB+rot90(unitV[i]), S); + push_back(S); + + SBasis s=integral(dot(compose(dB,Linear(t0,t1)),unitV[i])); + L+=(s(1)-s(0))*(t1-t0); + lengths.push_back(L); + + t0=t1; + } +} + +void NormalBundle::draw(cairo_t *cr, unsigned NbLi, unsigned NbCol) { + D2<SBasis> B; + vector<D2<SBasis> > tB; + //Geom::Point Seg[2]; + B[1]=Linear(-100,100); + double width=*(lengths.rbegin()); + if (NbCol>0) + for(unsigned ui = 0; ui <= NbCol; ui++) { + B[0]=Linear(ui*width/NbCol); + tB = compose(*this,B); + if (tB.size()>0) cairo_d2_sb(cr, tB[0]); + } + + B[0]=SBasis(Linear(0,1)); + for(unsigned ui = 0; ui <= NbLi; ui++) { + B[1]=Linear(-100+ui*200/NbLi); + for(unsigned i = 0; i <size(); i++) { + D2<SBasis> section=composeEach((*this)[i],B); + cairo_d2_sb(cr, section); + } + } +} + + +vector<D2<SBasis> > compose(NormalBundle const &NB, + D2<SBasis> const &Binit, + Geom::Point Origin){ + vector<D2<SBasis> > result; + D2<SBasis> B=Binit; + D2<SBasis> Bcut; + vector<double> Roots; + std::map<double,unsigned> Cuts; + unsigned idx; + + B = B + (-Origin); + + //--Find intersections with fibers over segment ends. + for(unsigned i=0; i<=NB.size();i++){ + Roots=roots(B[0]); + for (vector<double>::iterator root=Roots.begin(); + root!=Roots.end();root++) + Cuts[*root]=i; + if((Cuts.count(0.)==0) and + ((B[0].valueAt(0.)<=0) or i==NB.size())) + Cuts[0.]=i; + if((Cuts.count(1.)==0) and + ((B[0].valueAt(1.)<=0) or i==NB.size())) + Cuts[1.]=i; + if (i<NB.size()) + B[0]-=(NB.lengths[i+1]-NB.lengths[i]); + } + B[0]+=*(NB.lengths.rbegin()); + + //-- Compose each piece with the relevant sbasis2d. + // TODO: use a uniform parametrization of the base. + std::map<double,unsigned>::iterator cut=Cuts.begin(); + std::map<double,unsigned>::iterator next=cut; next++; + while(next!=Cuts.end()){ + double t0=(*cut).first; + unsigned idx0=(*cut).second; + double t1=(*next).first; + unsigned idx1=(*next).second; + if (idx0 != idx1){ + idx=std::min(idx0,idx1); + } else if(B[0]((t0+t1)/2) < NB.lengths[idx0]) { // we have a left 'bump', + idx=idx0-1; + } else if(B[0]((t0+t1)/2) == NB.lengths[idx0]) { //we have a vertical segment!... + idx= (idx0==NB.size())? idx0-1:idx0; + } else //we have a right 'bump'. + idx=idx0; + + //--trim version... + if (idx>=0 and idx<NB.size()) { + for (unsigned dim=0;dim<2;dim++) + Bcut[dim]=compose(B[dim], Linear(t0,t1)); + double width=NB.lengths[idx+1]-NB.lengths[idx]; + Bcut[0]=compose(Linear(-NB.lengths[idx]/width, + (1-NB.lengths[idx])/width),Bcut[0]); + Bcut = composeEach(NB[idx], Bcut); + result.push_back(Bcut); + } + cut++; + next++; + } + return(result); +} + + + +class NormalBundleToy: public Toy { + PointSetHandle B_handle; + PointSetHandle P_handle; + PointHandle O_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + D2<SBasis> B = B_handle.asBezier(); + D2<SBasis> P = P_handle.asBezier(); + Geom::Point O = O_handle.pos; + + NormalBundle NBdle; + NBdle.setBase(B); + Geom::Point Oo(O[0]+*(NBdle.lengths.rbegin()),O[1]); + + vector<D2<SBasis> > Q=compose(NBdle,P,O); + + cairo_set_line_width (cr, 0.5); + //Base lines + cairo_set_source_rgba (cr, 0.9, 0., 0., 1); + cairo_d2_sb(cr, B); + draw_line_seg(cr, O, Oo); + cairo_stroke(cr); + + //Sections + cairo_set_source_rgba (cr, 0, 0, 0.9, 1); + cairo_d2_sb(cr, P); + for (unsigned i=0;i<Q.size();i++){ + cairo_d2_sb(cr, Q[i]); + } + cairo_stroke(cr); + + //Normal bundle + cairo_set_source_rgba (cr, 0., 0., 0., 1); + NBdle.draw(cr,3,5); + cairo_stroke(cr); + + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + NormalBundleToy(){ + if(handles.empty()) { + handles.push_back(&B_handle); + handles.push_back(&P_handle); + handles.push_back(&O_handle); + for(unsigned i = 0; i < 4; i++) + B_handle.push_back(200+50*i,400); + for(unsigned i = 0; i < 4; i++) + P_handle.push_back(100+uniform()*400, + 150+uniform()*100); + O_handle.pos = Geom::Point(200,200); + } + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new NormalBundleToy); + return 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/toys/offset-toy.cpp b/src/toys/offset-toy.cpp new file mode 100644 index 0000000..4b3e617 --- /dev/null +++ b/src/toys/offset-toy.cpp @@ -0,0 +1,156 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-math.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/path-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <sstream> + +using std::vector; +using namespace Geom; +using namespace std; + +// TODO: +// use path2 +// replace Ray stuff with path2 line segments. + +//----------------------------------------------- + +static void +plot_offset(cairo_t* cr, D2<SBasis> const &M, + Coord offset = 10, + unsigned NbPts = 10){ + D2<SBasis> dM = derivative(M); + for (unsigned i = 0;i < NbPts;i++){ + double t = i*1./NbPts; + Point V = dM(t); + V = offset*rot90(unit_vector(V)); + draw_handle(cr, M(t)+V); + } +} + +static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){ + D2<Piecewise<SBasis> > plot; + plot[1]=-f*vscale; + plot[1]+=450; + + plot[0].cuts.push_back(f.cuts.front()); + plot[0].cuts.push_back(f.cuts.back()); + plot[0].segs.emplace_back(Linear(150,450)); + + for (unsigned i=1; i<f.size(); i++){ + double t=f.cuts[i],ft=f.segs[i].at0(); + cairo_move_to(cr, Point(150+t*300, 450)); + cairo_line_to(cr, Point(150+t*300, 450-ft*vscale)); + } + cairo_d2_pw_sb(cr, plot); +} + + + +class OffsetTester: public Toy { + PointSetHandle psh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + D2<SBasis> B = psh.asBezier(); + *notify << "Curve offset:" << endl; + *notify << " -blue: pointwise plotted offset," << endl; + *notify << " -red: rot90(unitVector(derivative(.)))+rays at cut" << endl; + *notify << " -gray: cos(atan2),sin(atan2)" << endl; + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + Coord offset = -100; + plot_offset(cr,B,offset,11); + cairo_set_source_rgba (cr, 0, 0, 1, 1); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B))); + Piecewise<D2<SBasis> > offset_curve = Piecewise<D2<SBasis> >(B)+n*offset; + PathVector offset_path = path_from_piecewise(offset_curve, 0.1); + + cairo_path(cr, offset_path); + cairo_stroke(cr); + for(const auto & pi : offset_path) { + Crossings cs = self_crossings(pi); + for(auto & c : cs) { + draw_cross(cr, pi.pointAt(c.ta)); + std::stringstream s; + Point Pa = pi.pointAt(c.ta); + Point Pb = pi.pointAt(c.tb); + s << L1(Pa - Pb) << std::endl; + std::string ss = s.str(); + draw_text(cr, Pa+Point(3,3), ss.c_str(), false, "Serif 6"); + + } + } + + for(unsigned i = 0; i < n.size()+1;i++){ + Point ptA=B(n.cuts[i]), ptB; + if (i==n.size()) + ptB=ptA+n.segs[i-1].at1()*offset; + else + ptB=ptA+n.segs[i].at0()*offset; + cairo_move_to(cr,ptA); + cairo_line_to(cr,ptB); + cairo_set_source_rgba (cr, 1, 0, 0, 1); + cairo_stroke(cr); + } + + Piecewise<SBasis> alpha = atan2(derivative(B),1e-2,3); + plot(cr,alpha,75/M_PI); + + Piecewise<D2<SBasis> >n2 = sectionize(D2<Piecewise<SBasis> >(sin(alpha),cos(alpha))); + cairo_pw_d2_sb(cr,Piecewise<D2<SBasis> >(B)+n2*offset*.9); + cairo_set_source_rgba (cr, 0.5, 0.2, 0.5, 0.8); + cairo_stroke(cr); + + Piecewise<SBasis> k = curvature(B); + cairo_pw_d2_sb(cr,Piecewise<D2<SBasis> >(B)+k*n*100); + cairo_set_source_rgba (cr, 0.5, 0.2, 0.5, 0.8); + cairo_stroke(cr); + + *notify << "Total length: " << length(B) << endl; + *notify << "(nb of cuts of unitVector: " << n.size()-1 << ")" << endl; + *notify << "(nb of cuts of cos,sin(atan2): " << n2.size()-1 << ")" << endl; + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + OffsetTester(int order) { + handles.push_back(&psh); + for(int i = 0; i < order; i++) + psh.push_back(200+50*i,300+70*uniform()); + } +}; + +int main(int argc, char **argv) { + int A_bez_ord = 6; + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + init(argc, argv, new OffsetTester(A_bez_ord)); + return 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:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/pair-intersect.cpp b/src/toys/pair-intersect.cpp new file mode 100644 index 0000000..9cc01ca --- /dev/null +++ b/src/toys/pair-intersect.cpp @@ -0,0 +1,147 @@ +#include <2geom/basic-intersection.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/path-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +class PairIntersect: public Toy { + PointSetHandle A_handles; + PointSetHandle B_handles; + std::vector<Toggle> toggles; + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + draw_toggles(cr, toggles); + cairo_save(cr); + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_set_line_width (cr, 0.5); + D2<SBasis> A = A_handles.asBezier(); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.0, 0, 0.8, 1.0); + cairo_set_line_width (cr, 0.5); + D2<SBasis> B = B_handles.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + cairo_save(cr); + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_set_line_width (cr, 0.5); + SBasis crs (cross(A - A(0), derivative(A))); + crs = shift(crs*Linear(-1, 0)*Linear(-1, 0), -2); + crs = crs * (300/(*bounds_exact(crs)).extent()); + vector<double> rts = roots(crs); + for(double t : rts) { + cairo_move_to(cr, A(0)); + cairo_line_to(cr, A(t)); + cairo_stroke(cr); + } + cairo_restore(cr); + cairo_move_to(cr, 0, 300); + cairo_line_to(cr, width, 300); + crs += 300; + D2<SBasis > are_graph(SBasis(Linear(0, width)), crs ); + cairo_save(cr); + cairo_d2_sb(cr, are_graph); + cairo_set_line_width (cr, .5); + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_stroke(cr); + cairo_restore(cr); + + Path PB; + PB.append(B); + Path PA; + PA.append(A); + + if (toggles[0].on) { + PathVector ps; + ps.push_back(PA); + ps.push_back(PB); + CrossingSet cs = crossings_among(ps); + *notify << "total intersections: " << cs.size() << '\n'; + cairo_stroke(cr); + cairo_set_source_rgba (cr, 1., 0., 0, 0.8); + for(unsigned i = 0; i < cs.size(); i++) { + Crossings section = cs[i]; + *notify << "section " << i << ": " << section.size() << '\n'; + for(auto & j : section) { + draw_handle(cr, A(j.ta)); + *notify << Geom::distance(A(j.ta), B(j.tb)) + << std::endl; + } + } + + cairo_stroke(cr); + } else { + vector<Geom::Point> Ab = A_handles.pts, Bb = B_handles.pts; + std::vector<std::pair<double, double> > section; + find_intersections( section, A, B); + std::vector<std::pair<double, double> > polished_section = section; + *notify << "total intersections: " << section.size(); + polish_intersections( polished_section, A, B); + cairo_stroke(cr); + cairo_set_source_rgba (cr, 1., 0., 0, 0.8); + for(unsigned i = 0; i < section.size(); i++) { + draw_handle(cr, A(section[i].first)); + *notify << Geom::distance(A(section[i].first), B(section[i].second)) + << " polished " + << Geom::distance(A(polished_section[i].first), B(polished_section[i].second)) + << std::endl; + } + + cairo_stroke(cr); + } + cairo_restore(cr); + + + Toy::draw(cr, notify, width, height, save,timer_stream); +} +public: + PairIntersect (unsigned A_bez_ord, unsigned B_bez_ord) { + toggles.emplace_back("Path", true); + toggles[0].bounds = Rect(Point(10,100), Point(100, 130)); + //toggles.push_back(Toggle("Curve", true)); + //toggles[1].bounds = Rect(Point(10,130), Point(100, 160)); + handles.push_back(&A_handles); + handles.push_back(&B_handles); + A_handles.name = "A"; + B_handles.name = "B"; + for(unsigned i = 0; i < A_bez_ord; i++) + A_handles.push_back(uniform()*400, uniform()*400); + for(unsigned i = 0; i < B_bez_ord; i++) + B_handles.push_back(uniform()*400, uniform()*400); +} +}; + +int main(int argc, char **argv) { +unsigned A_bez_ord=10; +unsigned B_bez_ord=3; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + init(argc, argv, new PairIntersect(A_bez_ord, B_bez_ord)); + + return 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/toys/paptest.cpp b/src/toys/paptest.cpp new file mode 100644 index 0000000..4ea2cd2 --- /dev/null +++ b/src/toys/paptest.cpp @@ -0,0 +1,107 @@ +/*
+ * A simple toy to test the path along path
+ *
+ * Copyright 2007 Johan Engelen
+ *
+ * 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 <iostream>
+#include <2geom/path-sink.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> >
+doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in, Geom::Piecewise<Geom::D2<Geom::SBasis> > & pattern)
+{
+ using namespace Geom;
+
+ Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(pwd2_in, 2, .1);
+ uskeleton = remove_short_cuts(uskeleton,.01);
+ Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+ n = force_continuity(remove_short_cuts(n,.1));
+
+ D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pattern);
+ Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
+ Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
+ Interval pattBnds = *bounds_exact(x);
+ x -= pattBnds.min();
+ Interval pattBndsY = *bounds_exact(y);
+ y -= (pattBndsY.max()+pattBndsY.min())/2;
+
+ int nbCopies = int(uskeleton.cuts.back()/pattBnds.extent());
+ double scaling = 1;
+
+ double pattWidth = pattBnds.extent() * scaling;
+
+ if (scaling != 1.0) {
+ x*=scaling;
+ }
+
+ double offs = 0;
+ Piecewise<D2<SBasis> > output;
+ for (int i=0; i<nbCopies; i++){
+ output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
+ offs+=pattWidth;
+ }
+
+ return output;
+}
+
+int main(int argc, char **argv) {
+ if (argc > 1) {
+ Geom::PathVector originald = Geom::parse_svg_path(&*argv[1]);
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > originaldpwd2;
+ for (const auto & i : originald) {
+ originaldpwd2.concat( i.toPwSb() );
+ }
+
+ Geom::PathVector pattern = Geom::parse_svg_path(&*argv[2]);
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > patternpwd2;
+ for (const auto & i : pattern) {
+ patternpwd2.concat( i.toPwSb() );
+ }
+
+ doEffect_pwd2(originaldpwd2, patternpwd2);
+ }
+ return 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/toys/parametrics.cpp b/src/toys/parametrics.cpp new file mode 100644 index 0000000..2d2538c --- /dev/null +++ b/src/toys/parametrics.cpp @@ -0,0 +1,229 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/affine.h> + +#include <glib.h> +#include <vector> +#include <iostream> +using std::vector; +using namespace Geom; + +int mode; + +static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double max, double space=10){ + Piecewise<D2<SBasis> > Mperp = rot90(derivative(M)) * 3; + for( double t = M.cuts.front(); t < max; t += space) { + Point pos = M(t), perp = Mperp(t); + draw_line_seg(cr, pos + perp, pos - perp); + } + cairo_stroke(cr); +} + +static void draw_axis(cairo_t *cr, Piecewise<D2<SBasis> > const &pw, unsigned d, Affine m) { + double mult; + if(abs(mode)==1) mult = 20; + if(abs(mode)==2) mult = 1; + if(abs(mode)==3) mult = 100; + if(abs(mode)==4) mult = 20; + if(abs(mode)==5) mult = 20; + if(abs(mode)==6) mult = 100; + for(unsigned i = 0; i < pw.size(); i++) { + cairo_d2_sb(cr, D2<SBasis>(SBasis(pw.cuts[i]-pw.cuts[0], pw.cuts[i+1]-pw.cuts[0])*mult, SBasis(pw[i][d]))*m); + } +} +/* +void dump_latex(PathVector ps) { + for(unsigned d = 0; d < 2; d++) { + std::cout << "$$\n" << (d?"y":"x") << "(t) = \\left\\{\n\\begin{array}{ll}\n"; + int seg = 0; + for(unsigned i = 0; i < ps.size(); i++) + for(unsigned j = 0; j < ps[i].size(); j++) { + Bezier<3> &b = dynamic_cast<Bezier<3>& >(const_cast<Curve&>(ps[i][j])); + std::cout << b[0][d] << "(" << seg+1 << "-t)^3 + " + << 3*b[1][d] << "t(" << seg+1 << "-t)^2 + " + << 3*b[2][d] << "t^2(" << seg+1 << "-t) + " + << b[3][d] << "t^3,& " << seg << "\\leq t < " << seg+1 << "\\\\\n"; + seg++; + } + std::cout << "\\end{array}\n$$\n"; + } +} +*/ +class Parametrics: public Toy { + Piecewise<D2<SBasis> > cat, alcat, box, arc, monk, traj; +#ifdef USE_TIME + GTimer* time; + bool st; +#endif + double waitt; + double t; + int count; + void draw(cairo_t *cr, + std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override { + //double t = handles[0][0] / 20.; + +#ifdef USE_TIME + gulong* foo = 0; + t = g_timer_elapsed(time, foo) * 100; +#else + double inc; + if(mode==1) inc = .1; + if(mode==2) inc = 5; + if(mode==3) inc = .01; + if(mode==4) inc = .04; + if(mode==5) inc = .1; + if(mode==6) inc = .01; + if(mode<0) inc = .01*M_PI; + if(!save && !waitt) { + t += inc; + } + if(waitt) waitt += 1; + if(waitt>20) waitt = 0; +#endif + Piecewise<D2<SBasis> > obj; + if(abs(mode)==1) obj = cat; + if(abs(mode)==2) obj = alcat; + if(abs(mode)==3) obj = arc; + if(abs(mode)==4) obj = box; + if(abs(mode)==5) obj = monk; + if(abs(mode)==6) obj = traj; + if(t==obj.cuts.back()) t += inc/2; + cairo_set_source_rgb(cr, 1,1,1); + if(save) { + cairo_rectangle(cr, 0, 0, width, height); + cairo_fill(cr); + } + Piecewise<D2<SBasis> > port, rport; + if(mode>0) { + port = portion(obj, 0, t); + rport = mode>0? portion(obj, t, obj.cuts[obj.size()]) : obj; + cairo_set_source_rgba (cr, 0., 0., 0., 1); + Point curpt = rport[0].at0(); + if(t<obj.cuts.back()) { + draw_line_seg(cr, curpt, Point(curpt[0], 350)); + draw_line_seg(cr, curpt, Point(350, curpt[1])); + cairo_stroke(cr); + } + + char tlab[64]; + sprintf(tlab, "t=%.02f", t); + draw_text(cr, curpt, tlab , true); + + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_pw_d2_sb(cr, port); + cairo_stroke(cr); + } + if(mode>=0 && t>=obj.cuts.back()+inc) t = 0; + cairo_set_source_rgba (cr, 0.9, 0., 0., 1); + if(mode<0) { + draw_axis(cr, obj, 0, from_basis(Point(cos(t),sin(t)),Point(sin(t),-cos(t)),Point(0, 350))); + if(cos(t) <= 0) { + mode = -mode; + t = 0; + waitt = 1; + } + } else + draw_axis(cr, rport, 0, from_basis(Point(0,1),Point(1,0),Point(0, 350))); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0., 0.9, 1); + if(mode<0) + draw_axis(cr, obj, 1, from_basis(Point(1,0),Point(0,1),Point(350*t/M_PI*2, 0))); + else + draw_axis(cr, rport, 1, from_basis(Point(1,0),Point(0,1),Point(350, 0))); + cairo_stroke(cr); + + if(mode==2 && t>0) { + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0., 0.9, 1); + dot_plot(cr, port, t); + cairo_stroke(cr); + } + + if(!save) { + char file[100]; + sprintf(file, "output/%04d.png", count); + //take_screenshot(file); + count++; + } + // *notify << "pieces = " << alcat.size() << ";\n"; + + Toy::draw(cr, notify, width, height, save,timer_stream); + redraw(); + } + +#ifdef USE_TIME + virtual void mouse_moved(GdkEventMotion* e) { + if(st) { + g_timer_start(time); + st = false; + } + Toy::mouse_moved(e); + } +#endif + + public: + Parametrics(){ + mode = 2; + PathVector cp = read_svgd("cat.svgd"); + //dump_latex(cp); + cat = paths_to_pw(cp); + cat *= .3; + cat += Point(50, 50); + alcat = arc_length_parametrization(cat); + + monk = paths_to_pw(read_svgd("monkey.svgd")); + //monk *= .3; + monk += Point(50,50); + + arc = sectionize(D2<Piecewise<SBasis> >(cos(Linear(0,M_PI))*120, sin(Linear(0,M_PI))*-120)); + arc += Point(200, 200); + + box = Piecewise<D2<SBasis> >(); + box.push_cut(0); + box.push(D2<SBasis>(SBasis(100.,300.), SBasis(100.)), 1); + box.push(D2<SBasis>(SBasis(300.), SBasis(100.,300.)), 2); + box.push(D2<SBasis>(SBasis(300.,100.), SBasis(300.)), 3); + box.push(D2<SBasis>(SBasis(100.), SBasis(300.,100.)), 4); + //handles.push_back(Point(100, 100)); + traj = Piecewise<D2<SBasis> >(); + SBasis quad = Linear(0,1)*Linear(0,1)*256-Linear(0,256)+200; + traj.push_cut(0); + traj.push(D2<SBasis>(SBasis(100.,300.),SBasis(quad)), 1); +#ifdef USE_TIME + time = g_timer_new(); + g_timer_reset(time); + st = true; +#endif + waitt = 0; + count = 0; + t = 0; + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Parametrics, 720, 480); + return 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/toys/parser.cpp b/src/toys/parser.cpp new file mode 100644 index 0000000..c7950e5 --- /dev/null +++ b/src/toys/parser.cpp @@ -0,0 +1,108 @@ +/* + * A simple toy to test the parser + * + * 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 <iostream> +#include <2geom/path-sink.h> +#include <2geom/svg-path-parser.h> + +class SVGPathTestPrinter : public Geom::PathSink { +public: + void moveTo(Geom::Point const &p) override { + std::cout << "M " << p << std::endl; + } + + void hlineTo(Geom::Coord v) { + std::cout << "H " << v << std::endl; + } + + void vlineTo(Geom::Coord v) { + std::cout << "V " << v << std::endl; + } + + void lineTo(Geom::Point const &p) override { + std::cout << "L " << p << std::endl; + } + + void curveTo(Geom::Point const &c0, Geom::Point const &c1, Geom::Point const &p) override { + std::cout << "C " << c0 << " " << c1 << " " << p << std::endl; + } + + void quadTo(Geom::Point const &c, Geom::Point const &p) override { + std::cout << "Q " << c << " " << p << std::endl; + } + + void arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Geom::Point const &p) override + { + std::cout << "A " << rx << " " << ry << " " << angle << " " << large_arc << " " << sweep << " " << p << std::endl; + } + + bool backspace() override + { + //std::cout << "[remove last segment]" << std::endl; + return false; + } + + void closePath() override { + std::cout << "Z" << std::endl; + } + + void flush() override { + ; + } + +}; + + +int main(int argc, char **argv) { + if (argc > 1) { + SVGPathTestPrinter sink; + Geom::parse_svg_path(&*argv[1], sink); + std::cout << "Try real pathsink:" << std::endl; + Geom::PathVector testpath = Geom::parse_svg_path(&*argv[1]); + std::cout << "Geom::PathVector length: " << testpath.size() << std::endl; + if ( !testpath.empty() ) + std::cout << "Path curves: " << testpath.front().size() << std::endl; + std::cout << "success!" << std::endl; + } + return 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/toys/path-along-path.cpp b/src/toys/path-along-path.cpp new file mode 100644 index 0000000..a2a6a1b --- /dev/null +++ b/src/toys/path-along-path.cpp @@ -0,0 +1,112 @@ +#include <2geom/d2.h> +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <algorithm> +using std::vector; +using namespace Geom; + +class PathAlongPathToy: public Toy { + PointSetHandle skel_handles, pat_handles; + PointHandle origin_handle; + bool should_draw_numbers() override{return false;} + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + D2<SBasis> skeleton = skel_handles.asBezier(); + D2<SBasis> pattern = pat_handles.asBezier(); + + + cairo_set_line_width(cr,1.); + cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(skeleton)); + cairo_set_source_rgba(cr,0.0,0.0,1.0,1.0); + cairo_stroke(cr); + + cairo_pw_d2_sb(cr, Piecewise<D2<SBasis> >(pattern)); + cairo_set_source_rgba(cr,1.0,0.0,1.0,1.0); + cairo_stroke(cr); + + origin_handle.pos[0]=150; + Geom::Point origin = origin_handle.pos; + + Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(Piecewise<D2<SBasis> >(skeleton),2,.1); + uskeleton = remove_short_cuts(uskeleton,.01); + Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton)); + n = force_continuity(remove_short_cuts(n,.1)); + + Piecewise<SBasis> x=Piecewise<SBasis>(pattern[0]-origin[0]); + Piecewise<SBasis> y=Piecewise<SBasis>(pattern[1]-origin[1]); + Interval pattBnds = *bounds_exact(x); + int nbCopies = int(uskeleton.cuts.back()/pattBnds.extent()); + + //double pattWidth = uskeleton.cuts.back()/nbCopies; + double pattWidth = pattBnds.extent(); + + double offs = 0; + x-=pattBnds.min(); + //x*=pattWidth/pattBnds.extent(); + + Piecewise<D2<SBasis> >output; + for (int i=0; i<nbCopies; i++){ + output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs)); + offs+=pattWidth; + } + + //Perform cut for last segment + double tt = uskeleton.cuts.back() - offs; + if(tt > 0.) { + vector<double> rs = roots(x - tt); + rs.push_back(0); rs.push_back(1); //regard endpoints + std::sort(rs.begin(), rs.end()); + std::unique(rs.begin(), rs.end()); + //enumerate indices of sections to the left of the line + for(unsigned i = (x[0].at0()>tt ? 1 : 0); i < rs.size()-1; i+=2) { + Piecewise<SBasis> port = portion(x+offs, rs[i], rs[i+1]); + output.concat(compose(uskeleton,port)+portion(y, rs[i], rs[i+1])*compose(n,port)); + } + } + + cairo_pw_d2_sb(cr, output); + cairo_set_source_rgba(cr,1.0,0.0,1.0,1.0); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + PathAlongPathToy() : origin_handle(150,150) { + if(handles.empty()) { + handles.push_back(&skel_handles); + handles.push_back(&pat_handles); + for(int i = 0; i < 8; i++) + skel_handles.push_back(200+50*i,400); + for(int i = 0; i < 4; i++) + pat_handles.push_back(100+uniform()*400, + 150+uniform()*100); + + handles.push_back(&origin_handle); + } + } +}; + + +int main(int argc, char **argv) { + init(argc, argv, new PathAlongPathToy); + return 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/toys/path-cairo.cpp b/src/toys/path-cairo.cpp new file mode 100644 index 0000000..73c2a59 --- /dev/null +++ b/src/toys/path-cairo.cpp @@ -0,0 +1,342 @@ +#include <cairo.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/utils.h> +#include <sstream> +#include <optional> + +using namespace Geom; + +void cairo_rectangle(cairo_t *cr, Rect const& r) { + cairo_rectangle(cr, r.left(), r.top(), r.width(), r.height()); +} + +void cairo_convex_hull(cairo_t *cr, ConvexHull const& ch) { + if(ch.empty()) return; + cairo_move_to(cr, ch[ch.size()-1]); + for(auto i : ch) { + cairo_line_to(cr, i); + } +} + +void cairo_curve(cairo_t *cr, Curve const& c) { + if(!cairo_has_current_point(cr)) + cairo_move_to(cr, c.initialPoint()); + + if(LineSegment const* line_segment = dynamic_cast<LineSegment const*>(&c)) { + cairo_line_to(cr, (*line_segment)[1][0], (*line_segment)[1][1]); + } + else if(QuadraticBezier const *quadratic_bezier = dynamic_cast<QuadraticBezier const*>(&c)) { + std::vector<Point> points = quadratic_bezier->controlPoints(); + Point b1 = points[0] + (2./3) * (points[1] - points[0]); + Point b2 = b1 + (1./3) * (points[2] - points[0]); + cairo_curve_to(cr, b1[0], b1[1], + b2[0], b2[1], + points[2][0], points[2][1]); + } + else if(CubicBezier const *cubic_bezier = dynamic_cast<CubicBezier const*>(&c)) { + std::vector<Point> points = cubic_bezier->controlPoints(); + cairo_curve_to(cr, points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]); + } +// else if(EllipticalArc const *svg_elliptical_arc = dynamic_cast<EllipticalArc *>(c)) { +// //TODO: get at the innards and spit them out to cairo +// } + else { + //this case handles sbasis as well as all other curve types + Path sbasis_path = cubicbezierpath_from_sbasis(c.toSBasis(), 0.1); + + //recurse to convert the new path resulting from the sbasis to svgd + for(const auto & iter : sbasis_path) { + cairo_curve(cr, iter); + } + } +} + +void cairo_path(cairo_t *cr, Path const &p) { + cairo_move_to(cr, p.initialPoint()[0], p.initialPoint()[1]); + if(p.size() == 0) { // naked moveto + cairo_move_to(cr, p.finalPoint()+Point(8,0)); + cairo_line_to(cr, p.finalPoint()+Point(-8,0)); + cairo_move_to(cr, p.finalPoint()+Point(0,8)); + cairo_line_to(cr, p.finalPoint()+Point(0,-8)); + return; + } + + for(const auto & iter : p) { + cairo_curve(cr, iter); + } + if(p.closed()) + cairo_close_path(cr); +} + +void cairo_path_stitches(cairo_t *cr, Path const &p) { + Path::const_iterator iter; + for ( iter = p.begin() ; iter != p.end() ; ++iter ) { + Curve const &c=*iter; + if (dynamic_cast<Path::StitchSegment const *>(&c)) { + cairo_move_to(cr, c.initialPoint()[X], c.initialPoint()[Y]); + cairo_line_to(cr, c.finalPoint()[X], c.finalPoint()[Y]); + + std::stringstream s; + s << L1(c.finalPoint() - c.initialPoint()); + std::string ss = s.str(); + draw_text(cr, c.initialPoint()+Point(5,5), ss.c_str(), false, "Serif 6"); + + //std::cout << c.finalPoint() - c.initialPoint() << std::endl; + } + } +} + +void cairo_path_handles(cairo_t */*cr*/, Path const &/*p*/) { + //TODO +} + +void cairo_path(cairo_t *cr, PathVector const &p) { + PathVector::const_iterator it; + for(it = p.begin(); it != p.end(); ++it) { + cairo_path(cr, *it); + } +} + +void cairo_path_stitches(cairo_t *cr, PathVector const &p) { + PathVector::const_iterator it; + for ( it = p.begin() ; it != p.end() ; ++it ) { + cairo_path_stitches(cr, *it); + } +} + +void cairo_d2_sb(cairo_t *cr, D2<SBasis> const &B) { + cairo_path(cr, path_from_sbasis(B, 0.1)); +} + +void cairo_d2_sb2d(cairo_t* cr, D2<SBasis2d> const &sb2, Point /*dir*/, double width) { + D2<SBasis> B; + for(int ui = 0; ui <= 10; ui++) { + double u = ui/10.; + B[0] = extract_u(sb2[0], u);// + Linear(u); + B[1] = extract_u(sb2[1], u); + for(unsigned i = 0; i < 2; i ++) { + B[i] = B[i]*(width/2) + Linear(width/4); + } + cairo_d2_sb(cr, B); + } + for(int vi = 0; vi <= 10; vi++) { + double v = vi/10.; + B[1] = extract_v(sb2[1], v);// + Linear(v); + B[0] = extract_v(sb2[0], v); + for(unsigned i = 0; i < 2; i ++) { + B[i] = B[i]*(width/2) + Linear(width/4); + } + cairo_d2_sb(cr, B); + } +} + +void cairo_sb2d(cairo_t* cr, SBasis2d const &sb2, Point dir, double width) { + D2<SBasis> B; + for(int ui = 0; ui <= 10; ui++) { + double u = ui/10.; + B[0] = extract_u(sb2, u)*dir[0] + Linear(u); + B[1] = SBasis(Linear(0,1)) + extract_u(sb2, u)*dir[1]; + for(unsigned i = 0; i < 2; i ++) { + B[i] = B[i]*(width/2) + Linear(width/4); + } + cairo_d2_sb(cr, B); + } + for(int vi = 0; vi <= 10; vi++) { + double v = vi/10.; + B[1] = extract_v(sb2, v)*dir[1] + Linear(v); + B[0] = SBasis(Linear(0,1)) + extract_v(sb2, v)*dir[0]; + for(unsigned i = 0; i < 2; i ++) { + B[i] = B[i]*(width/2) + Linear(width/4); + } + cairo_d2_sb(cr, B); + } +} + +void cairo_d2_pw_sb(cairo_t *cr, D2<Piecewise<SBasis> > const &p) { + cairo_pw_d2_sb(cr, sectionize(p)); +} + +void cairo_pw_d2_sb(cairo_t *cr, Piecewise<D2<SBasis> > const &p) { + for(unsigned i = 0; i < p.size(); i++) + cairo_d2_sb(cr, p[i]); +} + + +void draw_line_seg(cairo_t *cr, Geom::Point a, Geom::Point b) { + cairo_move_to(cr, a[0], a[1]); + cairo_line_to(cr, b[0], b[1]); + cairo_stroke(cr); +} + +void draw_spot(cairo_t *cr, Geom::Point h) { + draw_line_seg(cr, h, h); +} + +void draw_handle(cairo_t *cr, Geom::Point h) { + double x = h[Geom::X]; + double y = h[Geom::Y]; + cairo_move_to(cr, x-3, y); + cairo_line_to(cr, x+3, y); + cairo_move_to(cr, x, y-3); + cairo_line_to(cr, x, y+3); +} + +void draw_cross(cairo_t *cr, Geom::Point h) { + double x = h[Geom::X]; + double y = h[Geom::Y]; + cairo_move_to(cr, x-3, y-3); + cairo_line_to(cr, x+3, y+3); + cairo_move_to(cr, x+3, y-3); + cairo_line_to(cr, x-3, y+3); +} + +void draw_circ(cairo_t *cr, Geom::Point h) { + int x = int(h[Geom::X]); + int y = int(h[Geom::Y]); + cairo_new_sub_path(cr); + cairo_arc(cr, x, y, 3, 0, M_PI*2); + cairo_stroke(cr); +} + +void draw_ray(cairo_t *cr, Geom::Point h, Geom::Point dir) { + draw_line_seg(cr, h, h+dir); + Point unit = 3*unit_vector(dir), + rot = rot90(unit); + draw_line_seg(cr, h+dir, h + dir - unit + rot); + draw_line_seg(cr, h+dir, h + dir - unit - rot); +} + + +void +cairo_move_to (cairo_t *cr, Geom::Point p1) { + cairo_move_to(cr, p1[0], p1[1]); +} + +void +cairo_line_to (cairo_t *cr, Geom::Point p1) { + cairo_line_to(cr, p1[0], p1[1]); +} + +void +cairo_curve_to (cairo_t *cr, Geom::Point p1, + Geom::Point p2, Geom::Point p3) { + cairo_curve_to(cr, p1[0], p1[1], + p2[0], p2[1], + p3[0], p3[1]); +} +/* + void draw_string(GtkWidget *widget, string s, int x, int y) { + PangoLayout *layout = gtk_widget_create_pango_layout(widget, s.c_str()); + cairo_t* cr = gdk_cairo_create (widget->window); + cairo_move_to(cr, x, y); + pango_cairo_show_layout(cr, layout); + cairo_destroy (cr); + }*/ + +// H in [0,360) +// S, V, R, G, B in [0,1] +void convertHSVtoRGB(const double H, const double S, const double V, + double& R, double& G, double& B) { + int Hi = int(floor(H/60.)) % 6; + double f = H/60. - Hi; + double p = V*(1-S); + double q = V*(1-f*S); + double t = V*(1-(1-f)*S); + switch(Hi) { + case 0: R=V, G=t, B=p; break; + case 1: R=q, G=V, B=p; break; + case 2: R=p, G=V, B=t; break; + case 3: R=p, G=q, B=V; break; + case 4: R=t, G=p, B=V; break; + case 5: R=V, G=p, B=q; break; + } +} + +void draw_line(cairo_t *cr, double a, double b, double c, const Geom::Rect& r) { + Geom::Line l(a, b, c); + std::optional<Geom::LineSegment> seg = l.clip(r); + if (seg) { + cairo_move_to(cr, seg->initialPoint()); + cairo_line_to(cr, seg->finalPoint()); + cairo_stroke(cr); + } +} + + +void draw_line(cairo_t* cr, const Geom::Line& l, const Geom::Rect& r) +{ + std::vector<double> coeff = l.coefficients(); + draw_line (cr, coeff[0], coeff[1], coeff[2], r); +} + + +void draw_line(cairo_t *cr, Geom::Point n, double dist, Geom::Rect r) { + draw_line(cr, n[0], n[1], dist, r); +} + + +void draw_ray(cairo_t *cr, const Geom::Ray& ray, const Geom::Rect& r) +{ + LineSegment ls; + + for (size_t i = 0; i < 4; ++i) + { + ls.setInitial (r.corner(i)); + ls.setFinal (r.corner(i+1)); + OptCrossing cx = intersection (ls, ray); + if (cx) + { + Point P = ray.pointAt ((*cx).tb); + draw_line_seg (cr, ray.origin(), P); + break; + } + } +} + +void draw_line_segment(cairo_t *cr, const Geom::LineSegment& ls, const Geom::Rect& r) +{ + if(r.contains(ls[0])) { + if(r.contains(ls[1])) { + draw_line_seg(cr, ls[0], ls[1]); + } else { + draw_ray(cr, Geom::Ray(ls[0], ls[1]), r); + } + + } else { + if(r.contains(ls[1])) { + draw_ray(cr, Geom::Ray(ls[1], ls[0]), r); + } else { + draw_line(cr, Geom::Line(ls[0], ls[1]), r); + } + + } +} + +void draw_line_seg_with_arrow(cairo_t *cr, Geom::Point a, Geom::Point b, double dangle, double radius) { + double angle = atan2(a-b); + cairo_move_to(cr, a); + cairo_line_to(cr, b); + + cairo_move_to(cr, b); + cairo_line_to(cr, Point::polar(angle + dangle, radius) + b); + cairo_move_to(cr, b); + cairo_line_to(cr, Point::polar(angle - dangle, radius) + b); + cairo_stroke(cr); +} + + + + +/* + 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/toys/path-effects.cpp b/src/toys/path-effects.cpp new file mode 100644 index 0000000..fdd7ef0 --- /dev/null +++ b/src/toys/path-effects.cpp @@ -0,0 +1,140 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/svg-path-parser.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/sbasis-math.h> + +#include <cstdlib> + +using namespace Geom; + +Piecewise<SBasis > sore_tooth(Interval intv) { + Piecewise<SBasis > out; + double t = intv.min(); + Point p(0,0); + out.push_cut(0); + double r = 20; + double dir = 0.5; + while(t < intv.max()) { + double nt = t + 10; + if(nt > intv.max()) + nt = intv.max(); + SBasis zag(r*Linear(dir,-dir)); + out.push(zag, nt); + t = nt; + dir = -dir; + } + return out; +} + +Piecewise< D2<SBasis> > zaggy(Interval intv, double dt, double radius) { + Piecewise<D2<SBasis> > out; + double t = intv.min(); + Point p(0,0); + out.push_cut(0); + while(t < intv.max()) { + double nt = t + uniform()*dt; + if(nt > intv.max()) + nt = intv.max(); + Point np = Point((uniform()-0.5)*2*radius, (uniform()-0.5)*2*radius); + D2<SBasis> zag(SBasis(p[0],np[0]), SBasis(p[1],np[1])); + p = np; + //std::cout << t <<","<< nt << p << np << std::endl; + out.push(zag, nt); + t = nt; + } + return out; +} + + +class BoolOps: public Toy { + PathVector pv; + PointHandle offset_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + Geom::Translate t(offset_handle.pos); + + cairo_set_line_width(cr, 1); + cairo_set_source_rgb(cr, 0.75,0.75,1); + + //cairo_shape(cr, bst); + cairo_path(cr, pv*t); + cairo_stroke(cr); + + cairo_set_source_rgb(cr, 0,0,0); + for(const auto & i : pv) { + Piecewise<D2<SBasis> > B = i.toPwSb(); + Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B))); + Piecewise<SBasis > al = arcLengthSb(B); + +#if 0 + Piecewise<D2<SBasis> > offset_curve = Piecewise<D2<SBasis> >(B)+n*offset; + PathVector offset_path = path_from_piecewise(offset_curve, 0.1); + + cairo_path(cr, offset_path*t); + cairo_stroke(cr); +#endif + //Piecewise<D2<SBasis> > zz_curve = B+zaggy(B.domain(), 0.1, 20);//al*n; + //Piecewise<D2<SBasis> > zz_curve = Piecewise<D2<SBasis> >(B)+ + // compose(sore_tooth(Interval(al.firstValue(),al.lastValue())), al)*n; + Piecewise<D2<SBasis> > zz_curve = Piecewise<D2<SBasis> >(B)+ + sin(al*0.1)*10*n; + PathVector zz_path = path_from_piecewise(zz_curve, 0.1); + + cairo_path(cr, zz_path*t); + cairo_stroke(cr); + } + for(const auto & i : pv) { + if(i.size() == 0) { + *notify << "naked moveto;"; + } else + for(const auto & j : i) { + const Curve* c = &j; + *notify << typeid(*c).name() << ';' ; + } + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + public: + BoolOps () {} + + void first_time(int argc, char** argv) override { + const char *path_b_name="star.svgd"; + if(argc > 1) + path_b_name = argv[1]; + pv = read_svgd(path_b_name); + std::cout << pv.size() << "\n"; + std::cout << pv[0].size() << "\n"; + pv *= Translate(-pv[0].initialPoint()); + + Rect bounds = *pv[0].boundsExact(); + handles.push_back(&offset_handle); + offset_handle.pos = bounds.midpoint() - bounds.corner(0); + + //bs = cleanup(pv); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new BoolOps()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/path-toy.py b/src/toys/path-toy.py new file mode 100644 index 0000000..631482f --- /dev/null +++ b/src/toys/path-toy.py @@ -0,0 +1,41 @@ +#!/usr/bin/python + +import py2geom +import toyframework +import random,gtk +from py2geom_glue import * + +class PathToy(toyframework.Toy): + def __init__(self): + toyframework.Toy.__init__(self) + self.handles.append(toyframework.PointHandle(200, 200)) + self.path_b_name="star.svgd" + self.pv = py2geom.read_svgd(self.path_b_name); + centr = py2geom.Point() + for p in self.pv: + c,area = py2geom.centroid(p.toPwSb()) + centr += c + self.pv = self.pv*py2geom.Matrix(py2geom.Translate(-centr)) + def draw(self, cr, pos, save): + cr.set_source_rgba (0., 0., 0., 1) + cr.set_line_width (1) + + + B = (self.pv[0]*py2geom.Matrix(py2geom.Translate(*self.handles[0].pos))).toPwSb(); + n = py2geom.rot90(py2geom.unit_vector(py2geom.derivative(B), 0.01, 3)); + al = py2geom.arcLengthSb(B, 0.1); + offset = 10. + + offset_curve = B+py2geom.sin(al*0.1, 0.01, 2)*n*10. + offset_path = py2geom.path_from_piecewise(offset_curve, 0.1, True) + + py2geom.cairo_path(cr, offset_path) + cr.stroke() + + self.notify = '' + toyframework.Toy.draw(self, cr, pos, save) + +t = PathToy() +import sys + +toyframework.init(sys.argv, t, 500, 500) diff --git a/src/toys/pencil-2.cpp b/src/toys/pencil-2.cpp new file mode 100644 index 0000000..a312083 --- /dev/null +++ b/src/toys/pencil-2.cpp @@ -0,0 +1,1133 @@ +/* + * pencil-2 Toy - point fitting. + * + * 2009 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. + * + */ + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/basic-intersection.h> +#include <2geom/math-utils.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define SP_HUGE 1e5 +#define noBEZIER_DEBUG + +#ifdef HAVE_IEEEFP_H +# include <ieeefp.h> +#endif + +namespace Geom{ + +namespace BezierFitter{ + +typedef Point BezierCurve[]; + +/* 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[], CubicBezier const & bezCurve); +static double NewtonRaphsonRootFind(CubicBezier 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, + BezierCurve const bezCurve, double tolerance, + unsigned *splitPoint); +static double compute_hook(Point const &a, Point const &b, double const u, + CubicBezier const & bezCurve, + double const tolerance); +static double compute_hook(Point const &a, Point const &b, double const u, + BezierCurve const bezCurve, + double const tolerance) { + CubicBezier cb(bezCurve[0], bezCurve[1], bezCurve[2], bezCurve[3]); + return compute_hook(a, b, u, cb, tolerance); + +} + + +static void reparameterize_pts(Point const d[], unsigned len, double u[], BezierCurve const bezCurve) { + CubicBezier cb(bezCurve[0], bezCurve[1], bezCurve[2], bezCurve[3]); + reparameterize(d, len, u, cb); +} + + + +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 + + +Point +bezier_pt(unsigned const degree, Point const V[], double const t) +{ + return bernstein_value_at(t, V, degree); + +} + +/* + * 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; +} + + +int +bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers); + +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); + + +/** + * 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) +{ + int const maxIterations = 4; /* std::max times to try iterating */ + + 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_pts(data, len, u, bezier); + + /* Find max deviation of points to fitted curve. */ + double const tolerance = std::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 ) { + for (int i = 0; i < maxIterations; i++) { + generate_bezier(bezier, data, u, len, tHat1, tHat2, error); + reparameterize_pts(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[], + CubicBezier 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(CubicBezier const &Q, Point const &P, double const u) +{ + assert( 0.0 <= u ); + assert( u <= 1.0 ); + + std::vector<Point> Q_u = Q.pointAndDerivatives(u, 2); + + /* 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[0] - P; + double numerator = dot(diff, Q_u[1]); + double denominator = dot(Q_u[1], Q_u[1]) + dot(diff, Q_u[2]); + + 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( Q.pointAt(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; +} + + +/** + * 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, + BezierCurve 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 = std::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, CubicBezier const & bezCurve, + double const tolerance) +{ + Point const P = bezCurve.pointAt(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. + */ +} + +} + +} + +#include <2geom/bezier-utils.h> + + +using std::vector; +using namespace Geom; +using namespace std; + +class PointToBezierTester: public Toy { + //std::vector<Slider> sliders; + PointHandle adjuster, adjuster2, adjuster3; + std::vector<Toggle> toggles; + Piecewise<D2<SBasis > > stroke; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_save(cr); + + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 0.5); + adjuster2.pos[0]=150; + adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.); + cairo_move_to(cr, 150, 150); + cairo_line_to(cr, 150, 450); + cairo_stroke(cr); + ostringstream val_s; + double scale0=(450-adjuster2.pos[1])/300; + double curve_precision = pow(10, scale0*5-2); + val_s << curve_precision; + draw_text(cr, adjuster2.pos, val_s.str().c_str()); + cairo_restore(cr); + + cairo_save(cr); + + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 0.5); + + if(!mouses.empty()) { + cairo_move_to(cr, mouses[0]); + for(auto & mouse : mouses) { + cairo_line_to(cr, mouse); + } + cairo_stroke(cr); + } + + if(!mouses.empty()) { + Point bezier[1000]; + int segsgenerated; + { + Timer tm; + + tm.ask_for_timeslice(); + tm.start(); + segsgenerated = bezier_fit_cubic_r(bezier, &mouses[0], + mouses.size(), curve_precision, 240); + + Timer::Time als_time = tm.lap(); + *notify << "original time = " << als_time << std::endl; + } + + if(1) { + cairo_save(cr); + cairo_set_source_rgba (cr, 0., 1., 0., 1); + cairo_move_to(cr, bezier[0]); + int bezi=1; + for(int i = 0; i < segsgenerated; i ++) { + cairo_curve_to(cr, bezier[bezi], bezier[bezi+1], bezier[bezi+2]); + bezi += 4; + } + cairo_stroke(cr); + cairo_restore(cr); + + } + { + Timer tm; + + tm.ask_for_timeslice(); + tm.start(); + segsgenerated = Geom::BezierFitter::bezier_fit_cubic_r(bezier, &mouses[0], + mouses.size(), curve_precision, 240); + + Timer::Time als_time = tm.lap(); + *notify << "experimental version time = " << als_time << std::endl; + } + + if (1) { + cairo_save(cr); + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_move_to(cr, bezier[0]); + int bezi=1; + for(int i = 0; i < segsgenerated; i ++) { + cairo_curve_to(cr, bezier[bezi], bezier[bezi+1], bezier[bezi+2]); + bezi += 4; + } + cairo_stroke(cr); + cairo_restore(cr); + } + *notify << "segments : "<< segsgenerated <<"\n"; + } + cairo_restore(cr); + + /* + Point p(25, height - 50), d(50,25); + toggles[0].bounds = Rect(p, p + d); + p+= Point(75, 0); + toggles[1].bounds = Rect(p, p + d); + draw_toggles(cr, toggles); + */ + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + void key_hit(GdkEventKey *e) override { + if(e->keyval == 's') toggles[0].toggle(); + redraw(); + } + vector<Point> mouses; + int mouse_drag; + + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + if(!selected) { + mouse_drag = 1; + mouses.clear(); + } + } + + + void mouse_moved(GdkEventMotion* e) override { + if(mouse_drag) { + mouses.emplace_back(e->x, e->y); + redraw(); + } else { + Toy::mouse_moved(e); + } + } + + void mouse_released(GdkEventButton* e) override { + mouse_drag = 0; + stroke.clear(); + stroke.push_cut(0); + Path pth; + for(unsigned i = 2; i < mouses.size(); i+=2) { + pth.append(QuadraticBezier(mouses[i-2], mouses[i-1], mouses[i])); + } + stroke = pth.toPwSb(); + Toy::mouse_released(e); + } + + PointToBezierTester() { + adjuster2.pos = Geom::Point(150,300); + handles.push_back(&adjuster2); + toggles.emplace_back("Seq", false); + toggles.emplace_back("Linfty", true); + //} + //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t")); + //handles.push_back(&(sliders[0])); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new PointToBezierTester); + return 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:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/pencil.cpp b/src/toys/pencil.cpp new file mode 100644 index 0000000..1ac3587 --- /dev/null +++ b/src/toys/pencil.cpp @@ -0,0 +1,374 @@ +/* + * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier. + * + * Copyright 2007 jf barraud. + * 2008 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. + * + */ + +// mainly experimental atm... +// do not expect to find anything understandable here atm. + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/basic-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZERO 1e-7 + +using std::vector; +using namespace Geom; +using namespace std; + +#include <stdio.h> +#include <gsl/gsl_poly.h> + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p, double hscale=1., double vscale=1.) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(150+p.cuts[i]*hscale, 150+p.cuts[i+1]*hscale); + B[1] = Linear(450) - p[i]*vscale; + cairo_d2_sb(cr, B); + } +} + +//=================================================================================== + +D2<SBasis> +naive_sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){ + + Piecewise<D2<SBasis> > dM = derivative(M); + Point M0 = M(t0); + Point dM0 = dM(t0)*(t1-t0); + Point M1 = M(t1); + Point dM1 = dM(t1)*(t1-t0); + D2<SBasis> result; + for (unsigned dim=0; dim<2; dim++){ + SBasis r(2, Linear()); + r[0] = Linear(M0[dim],M1[dim]); + r[1] = Linear(M0[dim]-M1[dim]+dM0[dim],-(M0[dim]-M1[dim]+dM1[dim])); + result[dim] = r; + } + return result; +} + +D2<SBasis> +sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){ + Point M0,dM0,d2M0,M1,dM1,d2M1,A0,V0,A1,V1; + Piecewise<D2<SBasis> > dM,d2M; + dM=derivative(M); + d2M=derivative(dM); + M0 =M(t0); + M1 =M(t1); + dM0 =dM(t0); + dM1 =dM(t1); + d2M0=d2M(t0); + d2M1=d2M(t1); + A0=M(t0); + A1=M(t1); + + std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0,d2M1); + if (candidates.size()==0){ + return D2<SBasis>(SBasis(M0[X],M1[X]),SBasis(M0[Y],M1[Y])) ; + } + double maxlength = -1; + unsigned best = 0; + for (unsigned i=0; i<candidates.size(); i++){ + double l = length(candidates[i]); + if ( l < maxlength || maxlength < 0 ){ + maxlength = l; + best = i; + } + } + return candidates[best]; +} +#include <2geom/sbasis-to-bezier.h> + +int goal_function_type = 0; + +double goal_function(Piecewise<D2<SBasis> >const &A, + Piecewise<D2<SBasis> >const&B) { + if(goal_function_type) { + OptInterval bnds = bounds_fast(dot(derivative(A), rot90(derivative(B)))); + //double h_dist = bnds.dimensions().length(); +//0 is in the rect!, TODO:gain factor ~2 for free. +// njh: not really, the benefit is actually rather small. + double h_dist = 0; + if(bnds) + h_dist = bnds->extent(); + return h_dist ; + } else { + Rect bnds = *bounds_fast(A - B); + return max(bnds.min().length(), bnds.max().length()); + } +} + +int recursive_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) { + if (t0>=t1) return 0;//TODO: fix me... + if (t0+0.001>=t1) return 0;//TODO: fix me... + + //TODO: don't re-compute derivative(f) at each try!! + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1); + + if(k_bez[0].size() > 1 and k_bez[1].size() > 1) { + Piecewise<SBasis> s = arcLengthSb(k_bez); + s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1(); + s += t0; + double h_dist = goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez)); + if(h_dist < precision) { + cairo_save(cr); + cairo_set_line_width (cr, 0.93); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + draw_handle(cr, k_bez.at0()); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + cairo_restore(cr); + return 1; + } + } + //TODO: find a better place where to cut (at the worst fit?). + return recursive_curvature_fitter(cr, f, t0, (t0+t1)/2, precision) + + recursive_curvature_fitter(cr, f, (t0+t1)/2, t1, precision); +} + +double single_curvature_fitter(Piecewise<D2<SBasis> > const &f, double t0, double t1) { + if (t0>=t1) return 0;//TODO: fix me... + if (t0+0.001>=t1) return 0;//TODO: fix me... + + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1); + + if(k_bez[0].size() > 1 and k_bez[1].size() > 1) { + Piecewise<SBasis> s = arcLengthSb(k_bez); + s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1(); + s += t0; + return goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez)); + } + return 1e100; +} + +struct quadratic_params +{ + Piecewise<D2<SBasis> > const *f; + double t0, precision; +}; + + +double quadratic (double x, void *params) { + struct quadratic_params *p + = (struct quadratic_params *) params; + + return single_curvature_fitter(*p->f, p->t0, x) - p->precision; +} + +#include <stdio.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_roots.h> + + +int sequential_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) { + if(t0 >= t1) return 0; + + double r = t1; + if(single_curvature_fitter(f, t0, t1) > precision) { + int status; + int iter = 0, max_iter = 100; + const gsl_root_fsolver_type *T; + gsl_root_fsolver *s; + gsl_function F; + struct quadratic_params params = {&f, t0, precision}; + + F.function = &quadratic; + F.params = ¶ms; + + T = gsl_root_fsolver_brent; + s = gsl_root_fsolver_alloc (T); + gsl_root_fsolver_set (s, &F, t0, t1); + + do + { + iter++; + status = gsl_root_fsolver_iterate (s); + r = gsl_root_fsolver_root (s); + double x_lo = gsl_root_fsolver_x_lower (s); + double x_hi = gsl_root_fsolver_x_upper (s); + status = gsl_root_test_interval (x_lo, x_hi, + 0, 0.001); + + + } + while (status == GSL_CONTINUE && iter < max_iter); + + double x_lo = gsl_root_fsolver_x_lower (s); + double x_hi = gsl_root_fsolver_x_upper (s); + printf ("%5d [%.7f, %.7f] %.7f %.7f\n", + iter, x_lo, x_hi, + r, + x_hi - x_lo); + gsl_root_fsolver_free (s); + } + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,r); + + cairo_save(cr); + cairo_set_line_width (cr, 0.93); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + draw_handle(cr, k_bez.at0()); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + cairo_restore(cr); + + if(r < t1) + return sequential_curvature_fitter(cr, f, r, t1, precision) + 1; + return 1; +} + + +class SbToBezierTester: public Toy { + //std::vector<Slider> sliders; + PointHandle adjuster, adjuster2, adjuster3; + std::vector<Toggle> toggles; + Piecewise<D2<SBasis > > stroke; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_save(cr); + + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 0.5); + + if(!mouses.empty()) { + cairo_move_to(cr, mouses[0]); + for(auto & mouse : mouses) { + cairo_line_to(cr, mouse); + } + cairo_stroke(cr); + } + adjuster2.pos[0]=150; + adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.); + cairo_move_to(cr, 150, 150); + cairo_line_to(cr, 150, 450); + cairo_stroke(cr); + ostringstream val_s; + double scale0=(450-adjuster2.pos[1])/300; + double curve_precision = pow(10, scale0*5-2); + val_s << curve_precision; + draw_text(cr, adjuster2.pos, val_s.str().c_str()); + + + Piecewise<D2<SBasis> > f_as_pw = stroke; + cairo_pw_d2_sb(cr, f_as_pw); + cairo_stroke(cr); + if(!stroke.empty()) { + f_as_pw = arc_length_parametrization(f_as_pw); + int segs = 0; + goal_function_type = toggles[1].on; + if(toggles[0].on) + segs = sequential_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(), curve_precision); + else { + segs = recursive_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(),curve_precision); + } + *notify << " total segments: "<< segs <<"\n"; + } + cairo_restore(cr); + Point p(25, height - 50), d(50,25); + toggles[0].bounds = Rect(p, p + d); + p+= Point(75, 0); + toggles[1].bounds = Rect(p, p + d); + draw_toggles(cr, toggles); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + void key_hit(GdkEventKey *e) override { + if(e->keyval == 's') toggles[0].toggle(); + redraw(); + } + vector<Point> mouses; + int mouse_drag; + + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + if(!selected) { + mouse_drag = 1; + mouses.clear(); + } + } + + + void mouse_moved(GdkEventMotion* e) override { + if(mouse_drag) { + mouses.emplace_back(e->x, e->y); + redraw(); + } else { + Toy::mouse_moved(e); + } + } + + void mouse_released(GdkEventButton* e) override { + mouse_drag = 0; + stroke.clear(); + stroke.push_cut(0); + Path pth; + for(unsigned i = 2; i < mouses.size(); i+=2) { + pth.append(QuadraticBezier(mouses[i-2], mouses[i-1], mouses[i])); + } + stroke = pth.toPwSb(); + Toy::mouse_released(e); + } + + SbToBezierTester() { + adjuster.pos = Geom::Point(150+300*uniform(),150+300*uniform()); + handles.push_back(&adjuster); + adjuster2.pos = Geom::Point(150,300); + handles.push_back(&adjuster2); + adjuster3.pos = Geom::Point(450,300); + handles.push_back(&adjuster3); + toggles.emplace_back("Seq", false); + toggles.emplace_back("Linfty", true); + //} + //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t")); + //handles.push_back(&(sliders[0])); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SbToBezierTester); + return 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:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/plane3d.cpp b/src/toys/plane3d.cpp new file mode 100644 index 0000000..8710869 --- /dev/null +++ b/src/toys/plane3d.cpp @@ -0,0 +1,130 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> + +#include <gsl/gsl_matrix.h> + +#include <vector> +using std::vector; +using namespace Geom; +using namespace std; + +class Box3d: public Toy { + double tmat[3][4]; + PointHandle origin_handle; + PointSetHandle vanishing_points_handles; + PathVector paths_a; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + Geom::Point orig = origin_handle.pos; + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + + /* create the transformation matrix for the map P^3 --> P^2 that has the following effect: + (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0) + (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1) + (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2) + (0 : 0 : 0 : 1) --> origin (= handle #3) + */ + for (int j = 0; j < 4; ++j) { + tmat[0][j] = vanishing_points_handles.pts[j][0]; + tmat[1][j] = vanishing_points_handles.pts[j][1]; + tmat[2][j] = 1; + } + + *notify << "Projection matrix:" << endl; + for (auto & i : tmat) { + for (double j : i) { + *notify << j << " "; + } + *notify << endl; + } + + for(const auto & i : paths_a) { + Piecewise<D2<SBasis> > path_a_pw = i.toPwSb(); + + D2<Piecewise<SBasis> > B = make_cuts_independent(path_a_pw); + Piecewise<SBasis> preimage[4]; + + preimage[0] = (B[0] - orig[0]) / 100; + preimage[1] = -(B[1] - orig[1]) / 100; + Piecewise<SBasis> res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = preimage[0] * tmat[j][0] + + preimage[1] * tmat[j][1] + + tmat[j][3]; + } + + //if (fabs (res[2]) > 0.000001) { + D2<Piecewise<SBasis> > result(divide(res[0],res[2], 2), + divide(res[1],res[2], 2)); + + cairo_d2_pw_sb(cr, result); + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + void first_time(int argc, char** argv) override { + const char *path_a_name="ptitle.svgd"; + if(argc > 1) + path_a_name = argv[1]; + paths_a = read_svgd(path_a_name); + assert(paths_a.size() > 0); + + // Finite images of the three vanishing points and the origin + handles.push_back(&origin_handle); + handles.push_back(&vanishing_points_handles); + vanishing_points_handles.push_back(550,350); + vanishing_points_handles.push_back(150,300); + vanishing_points_handles.push_back(380,40); + vanishing_points_handles.push_back(340,450); + // plane origin + origin_handle.pos = Point(180,65); + } + //int should_draw_bounds() {return 1;} + + Geom::Point proj_image (cairo_t *cr, const double pt[4], const vector<Geom::Point> &/*handles*/) + { + double res[3]; + for (int j = 0; j < 3; ++j) { + res[j] = 0; + for (int i = 0; i < 3; ++i) + res[j] += tmat[j][i] * pt[i]; + } + if (fabs (res[2]) > 0.000001) { + Geom::Point result = Geom::Point (res[0]/res[2], res[1]/res[2]); + draw_handle(cr, result); + return result; + } + assert(0); // unclipped point + return Geom::Point(0,0); + } + +}; + +int main(int argc, char **argv) { + init(argc, argv, new Box3d); + return 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/toys/plane3d.py b/src/toys/plane3d.py new file mode 100644 index 0000000..0ffcb51 --- /dev/null +++ b/src/toys/plane3d.py @@ -0,0 +1,78 @@ +#!/usr/bin/python + +import py2geom +import toyframework +import random,gtk +import numpy +from py2geom_glue import * + +class Box3d(toyframework.Toy): + def __init__(self): + toyframework.Toy.__init__(self) + self.tmat = numpy.zeros([3,4]) + # plane origin + self.origin_handle = toyframework.PointHandle(180,65) + self.handles.append(self.origin_handle) + self.vanishing_points_handles = toyframework.PointSetHandle() + path_a_name="ptitle.svgd" + import sys + if len(sys.argv) > 1: + path_a_name = sys.argv[1] + self.paths_a = py2geom.read_svgd(path_a_name) + + + # Finite images of the three vanishing points and the origin + self.handles.append(self.vanishing_points_handles) + self.vanishing_points_handles.append(550,350) + self.vanishing_points_handles.append(150,300) + self.vanishing_points_handles.append(380,40) + self.vanishing_points_handles.append(340,450) + def draw(self, cr, pos, save): + orig = self.origin_handle.pos; + cr.set_source_rgba (0., 0.125, 0, 1) + + # create the transformation matrix for the map P^3 --> P^2 that has the following effect: + # (1 : 0 : 0 : 0) --> vanishing point in x direction (= handle #0) + # (0 : 1 : 0 : 0) --> vanishing point in y direction (= handle #1) + # (0 : 0 : 1 : 0) --> vanishing point in z direction (= handle #2) + # (0 : 0 : 0 : 1) --> origin (= handle #3) + + tmat = numpy.zeros([3,4]) + for j in range(4): + tmat[0][j] = self.vanishing_points_handles.pts[j][0] + tmat[1][j] = self.vanishing_points_handles.pts[j][1] + tmat[2][j] = 1 + + self.notify = "Projection matrix:\n" + for i in range(3): + for j in range(4): + self.notify += str(tmat[i][j]) + " " + self.notify += '\n' + + for p in self.paths_a: + B = py2geom.make_cuts_independant(p.toPwSb()) + preimage = [None]*4 + + preimage[0] = (B[0] - orig[0]) / 100; + preimage[1] = -(B[1] - orig[1]) / 100; + Piecewise<SBasis> res[3]; + for j in range(3): + res[j] = (preimage[0] * tmat[j][0] + + preimage[1] * tmat[j][1] + + + tmat[j][3]) + + result = D2PiecewiseSBasis(divide(res[0],res[2], 2), + divide(res[1],res[2], 2)) + + toyframework.cairo_d2_pw(cr, result) + cr.set_source_rgba (0., 0.125, 0, 1) + cr.stroke() + + toyframework.Toy.draw(self, cr, pos, save) + +t = Box3d() +import sys + +toyframework.init(sys.argv, t, 500, 500) + + diff --git a/src/toys/point-curve-nearest-time.cpp b/src/toys/point-curve-nearest-time.cpp new file mode 100644 index 0000000..0067920 --- /dev/null +++ b/src/toys/point-curve-nearest-time.cpp @@ -0,0 +1,397 @@ +/* + * point-curve nearest point routines testing + * + * 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/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/curves.h> +#include <2geom/angle.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/piecewise.h> + +#include <2geom/svg-path-parser.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <2geom/transforms.h> +#include <2geom/pathvector.h> + + +#include <algorithm> + +using namespace Geom; + +std::ostream& +operator<< (std::ostream &out, PathVectorTime const &pvp) +{ + return out << pvp.path_index << "." << pvp.curve_index << "." << pvp.t; +} + +class NearestPoints : public Toy +{ + enum menu_item_t + { + FIRST_ITEM = 1, + LINE_SEGMENT = FIRST_ITEM, + ELLIPTICAL_ARC, + SBASIS_CURVE, + PIECEWISE, + PATH, + PATH_SVGD, + TOTAL_ITEMS + }; + + static const char* menu_items[TOTAL_ITEMS]; + +private: + PathVector paths_b; + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + + Point p = ph.pos; + Point np = p; + std::vector<Point> nps; + + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgb(cr, 0,0,0); + switch ( choice ) + { + case '1': + { + LineSegment seg(psh.pts[0], psh.pts[1]); + cairo_move_to(cr, psh.pts[0]); + cairo_curve(cr, seg); + double t = seg.nearestTime(p); + np = seg.pointAt(t); + if ( toggles[0].on ) + { + nps.push_back(np); + } + break; + } + case '2': + { + EllipticalArc earc; + bool earc_constraints_satisfied = true; + try + { + earc.set(psh.pts[0], 200, 150, 0, true, true, psh.pts[1]); + } + catch( RangeError e ) + { + earc_constraints_satisfied = false; + } + if ( earc_constraints_satisfied ) + { + cairo_d2_sb(cr, earc.toSBasis()); + if ( toggles[0].on ) + { + std::vector<double> t = earc.allNearestTimes(p); + for (double i : t) + nps.push_back(earc.pointAt(i)); + } + else + { + double t = earc.nearestTime(p); + np = earc.pointAt(t); + } + } + break; + } + case '3': + { + D2<SBasis> A = psh.asBezier(); + cairo_d2_sb(cr, A); + if ( toggles[0].on ) + { + std::vector<double> t = Geom::all_nearest_times(p, A); + for (double i : t) + nps.push_back(A(i)); + } + else + { + double t = nearest_time(p, A); + np = A(t); + } + break; + } + case '4': + { + D2<SBasis> A = handles_to_sbasis(psh.pts.begin(), 3); + D2<SBasis> B = handles_to_sbasis(psh.pts.begin() + 3, 3); + D2<SBasis> C = handles_to_sbasis(psh.pts.begin() + 6, 3); + D2<SBasis> D = handles_to_sbasis(psh.pts.begin() + 9, 3); + cairo_d2_sb(cr, A); + cairo_d2_sb(cr, B); + cairo_d2_sb(cr, C); + cairo_d2_sb(cr, D); + Piecewise< D2<SBasis> > pwc; + pwc.push_cut(0); + pwc.push_seg(A); + pwc.push_cut(0.25); + pwc.push_seg(B); + pwc.push_cut(0.50); + pwc.push_seg(C); + pwc.push_cut(0.75); + pwc.push_seg(D); + pwc.push_cut(1); + if ( toggles[0].on ) + { + std::vector<double> t = Geom::all_nearest_times(p, pwc); + for (double i : t) + nps.push_back(pwc(i)); + } + else + { + double t = Geom::nearest_time(p, pwc); + np = pwc(t); + } + break; + } + case '5': + { + closed_toggle = true; + BezierCurveN<3> A(psh.pts[0], psh.pts[1], psh.pts[2], psh.pts[3]); + BezierCurveN<2> B(psh.pts[3], psh.pts[4], psh.pts[5]); + BezierCurveN<3> C(psh.pts[5], psh.pts[6], psh.pts[7], psh.pts[8]); + Path path; + path.append(A); + path.append(B); + path.append(C); + EllipticalArc D; + bool earc_constraints_satisfied = true; + try + { + D.set(psh.pts[8], 160, 80, 0, true, true, psh.pts[9]); + } + catch( RangeError e ) + { + earc_constraints_satisfied = false; + } + if ( earc_constraints_satisfied ) path.append(D); + if ( toggles[1].on ) path.close(true); + + cairo_path(cr, path); + + if ( toggles[0].on ) + { + std::vector<double> t = path.allNearestTimes(p); + for (double i : t) + nps.push_back(path.pointAt(i)); + } + else + { + PathTime pt = path.nearestTime(p); + np = path.pointAt(pt); + } + break; + } + case '6': + { + closed_toggle = true; + PathVector pathv = paths_b*Translate(psh.pts[0]-paths_b[0][0].initialPoint()); + //std::cout << pathv.size() << std::endl; + OptRect optRect = bounds_fast(pathv); + + cairo_rectangle(cr, *optRect); + cairo_stroke(cr); + + cairo_path(cr, pathv); + + if ( toggles[0].on ) + { + std::vector<PathVectorTime> t = pathv.allNearestTimes(p); + for (auto & i : t) + nps.push_back(pathv.pointAt(i)); + } + else + { + //std::optional<PathVectorTime> + double s = 0, e = 1; + draw_cross(cr, pathv[0].pointAt(s)); + draw_cross(cr, pathv[0].pointAt(e)); + double t = pathv[0][0].nearestTime(p, 0, 1); + if(t) { + *notify << p+psh.pts[0] << std::endl; + *notify << t << std::endl; + np = pathv[0].pointAt(t); + } + } + break; + } + default: + { + *notify << std::endl; + for (int i = FIRST_ITEM; i < TOTAL_ITEMS; ++i) + { + *notify << " " << i << " - " << menu_items[i] << std::endl; + } + Toy::draw(cr, notify, width, height, save,timer_stream); + return; + } + } + + if ( toggles[0].on ) + { + for (auto & np : nps) + { + cairo_move_to(cr, p); + cairo_line_to(cr, np); + } + } + else + { + cairo_move_to(cr, p); + cairo_line_to(cr, np); + } + cairo_stroke(cr); + + toggles[0].bounds = Rect( Point(10, height - 50), Point(10, height - 50) + Point(80,25) ); + toggles[0].draw(cr); + if ( closed_toggle ) + { + toggles[1].bounds = Rect( Point(100, height - 50), Point(100, height - 50) + Point(80,25) ); + toggles[1].draw(cr); + closed_toggle = false; + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + void key_hit(GdkEventKey *e) override + { + choice = e->keyval; + switch ( choice ) + { + case '1': + total_handles = 2; + break; + case '2': + total_handles = 2; + break; + case '3': + total_handles = 6; + break; + case '4': + total_handles = 13; + break; + case '5': + total_handles = 10; + break; + case '6': + total_handles = 1; + break; + default: + total_handles = 0; + } + psh.pts.clear(); + psh.push_back( 262.6037,35.824151); + psh.pts[0] += Point(300,300); + psh.push_back(0,0); + psh.pts[1] += psh.pts[0]; + psh.push_back(-92.64892,-187.405851); + psh.pts[2] += psh.pts[0]; + psh.push_back(30,-149.999981); + psh.pts[3] += psh.pts[0]; + for ( unsigned int i = 0; i < total_handles; ++i ) + { + psh.push_back(uniform()*400, uniform()*400); + } + ph.pos = Point(uniform()*400, uniform()*400); + redraw(); + } + + void mouse_pressed(GdkEventButton* e) override + { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + +public: + void first_time(int argc, char** argv) override { + const char *path_b_name="star.svgd"; + if(argc > 1) + path_b_name = argv[1]; + paths_b = read_svgd(path_b_name); + } + NearestPoints() + : total_handles(0), choice('0'), closed_toggle(false) + { + handles.push_back(&psh); + handles.push_back(&ph); + ph.pos = Point(uniform()*400, uniform()*400); + toggles.emplace_back("ALL NP", false ); + toggles.emplace_back("CLOSED", false ); + } + +private: + PointSetHandle psh; + PointHandle ph; + std::vector<Toggle> toggles; + unsigned int total_handles; + char choice; + bool closed_toggle; +}; + +const char* NearestPoints::menu_items[] = +{ + "", + "LineSegment", + "EllipticalArc", + "SBasisCurve", + "Piecewise", + "Path", + "Path from SVGD" +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new NearestPoints() ); + return 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/toys/portion-test.cpp b/src/toys/portion-test.cpp new file mode 100644 index 0000000..bc64db3 --- /dev/null +++ b/src/toys/portion-test.cpp @@ -0,0 +1,105 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +using std::vector; +using namespace Geom; +using namespace std; + +// TODO: +// use path2 +// replace Ray stuff with path2 line segments. + +//----------------------------------------------- + +class PortionTester: public Toy { + PointSetHandle curve_handle; + PointHandle sample_point1, sample_point2; + std::vector<Toggle> toggles; + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + draw_toggles(cr, toggles); + D2<SBasis> B = curve_handle.asBezier(); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + sample_point1.pos[1]=400; + sample_point1.pos[0]=std::max(150.,sample_point1.pos[0]); + sample_point1.pos[0]=std::min(450.,sample_point1.pos[0]); + sample_point2.pos[1]=400; + sample_point2.pos[0]=std::max(150.,sample_point2.pos[0]); + sample_point2.pos[0]=std::min(450.,sample_point2.pos[0]); + cairo_move_to(cr, Geom::Point(150,400)); + cairo_line_to(cr, Geom::Point(450,400)); + cairo_set_source_rgba (cr, 0., 0., 0.5, 0.8); + cairo_stroke(cr); + + double t0=std::max(0.,std::min(1.,(sample_point1.pos[0]-150)/300.)); + double t1=std::max(0.,std::min(1.,(sample_point2.pos[0]-150)/300.)); + + Path P; + P.append(B); + + if (toggles[0].on) { + if (toggles[1].on) + cairo_curve(cr, P.portion(t0,t1)[0]); + else + cairo_path(cr, P.portion(t0,t1)); + } else + cairo_d2_sb(cr, portion(B,t0,t1)); + + + cairo_set_source_rgba (cr, 0.5, 0.2, 0., 0.8); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + PortionTester(){ + toggles.emplace_back("Path", true); + toggles[0].bounds = Rect(Point(10,100), Point(100, 130)); + toggles.emplace_back("Curve", true); + toggles[1].bounds = Rect(Point(10,130), Point(100, 160)); + if(handles.empty()) { + handles.push_back(&curve_handle); + handles.push_back(&sample_point1); + handles.push_back(&sample_point2); + for(unsigned i = 0; i < 4; i++) + curve_handle.push_back(150+uniform()*300,150+uniform()*300); + sample_point1.pos = Geom::Point(250,300); + sample_point2.pos = Geom::Point(350,300); + } + } +}; + +int main(int argc, char **argv) { + std::cout << "testing unit_normal(multidim_sbasis) based offset." << std::endl; + init(argc, argv, new PortionTester); + return 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:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/precise-flat.cpp b/src/toys/precise-flat.cpp new file mode 100644 index 0000000..0113619 --- /dev/null +++ b/src/toys/precise-flat.cpp @@ -0,0 +1,86 @@ +/** + * efficient and precise flattening of curves. + * incomplete rewrite (njh) + */ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +class PreciseFlat: public Toy { + PointSetHandle hand; +void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 0.5); + + D2<SBasis> B = hand.asBezier(); + D2<SBasis> dB = derivative(B); + D2<SBasis> ddB = derivative(dB); + cairo_set_source_rgb(cr, 0,0,0); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + // draw the longest chord that is no worse than tol from the curve. + + Geom::Point st = unit_vector(dB(0)); + double s3 = fabs(dot(hand.pts[2] - hand.pts[0], rot90(st))); + + SBasis inflect = dot(dB, rot90(ddB)); + std::vector<double> rts = roots(inflect); + double f = 3; + for(double rt : rts) { + draw_handle(cr, B(rt)); + + double tp = rt; + Geom::Point st = unit_vector(dB(tp)); + Geom::Point O = B(tp); + double s4 = fabs(dot(hand.pts[3] - O, rot90(st))); + double tf = pow(f/s4, 1./3); + Geom::Point t1p = B(tp + tf*(1-tp)); + Geom::Point t1m = B(tp - tf*(1-tp)); + cairo_move_to(cr, t1m); + cairo_line_to(cr, t1p); + cairo_stroke(cr); + //std::cout << tp << ", " << t1m << ", " << t1p << std::endl; + } + + cairo_move_to(cr, B(0)); + double t0 = 2*sqrt(f/(3*s3)); + //std::cout << t0 << std::endl; + cairo_line_to(cr, B(t0)); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); +} + +public: +PreciseFlat () { + for(unsigned i = 0; i < 4; i++) + hand.pts.emplace_back(uniform()*400, uniform()*400); + handles.push_back(&hand); +} + +}; + +int main(int argc, char **argv) { + init(argc, argv, new PreciseFlat()); + + return 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/toys/pw-compose-test.cpp b/src/toys/pw-compose-test.cpp new file mode 100644 index 0000000..a7e9438 --- /dev/null +++ b/src/toys/pw-compose-test.cpp @@ -0,0 +1,98 @@ +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class PwToy: public Toy { +public: + vector<PointSetHandle*> pw_handles; + PointSetHandle slids; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0.5, 0, 1); + cairo_set_line_width (cr, 1); + + D2<Piecewise<SBasis> > pws; + for(unsigned i = 0; i < pw_handles.size(); i++) { + D2<SBasis> foo = pw_handles[i]->asBezier(); + cairo_d2_sb(cr, foo); + for(unsigned d = 0; d < 2; d++) { + pws[d].cuts.push_back(150*i); + pws[d].segs.push_back(foo[d]); + } + } + for(unsigned d = 0; d < 2; d++) + pws[d].cuts.push_back(150*pw_handles.size()); + + slids.pts[0][1]=450; + slids.pts[1][1]=450; + slids.pts[2][1]=450; + slids.pts[3][1]=450; + + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + D2<SBasis> foo = slids.asBezier(); + SBasis g = foo[0] - Linear(150); + cairo_d2_sb(cr, foo); + for(unsigned i=0;i<20;i++){ + double t=i/20.; + draw_handle(cr, foo(t)); + } + cairo_stroke(cr); + foo[1]=foo[0]; + foo[0]=Linear(150,450); + cairo_d2_sb(cr, foo); + + cairo_d2_pw_sb(cr, pws); + + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0.9, 0., 0., 1); + D2<Piecewise<SBasis> > res = compose(pws, Piecewise<SBasis>(g)); + cairo_d2_pw_sb(cr, res); + for(unsigned i=0;i<20;i++){ + double t=(res[0].cuts.back()-res[0].cuts.front())*i/20.; + draw_handle(cr, Point(res[0](t),res[1](t))); + } + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + bool should_draw_numbers() override { return false; } + + public: + PwToy () { + unsigned segs = 5; + unsigned handles_per_seg = 4; + double x = 150; + for(unsigned a = 0; a < segs; a++) { + PointSetHandle* psh = new PointSetHandle; + + for(unsigned i = 0; i < handles_per_seg; i++, x+= 25) + psh->push_back(Point(x, uniform() * 150)); + pw_handles.push_back(psh); + handles.push_back(psh); + } + for(unsigned i = 0; i < 4; i++) + slids.push_back(Point(150 + segs*50*i,100)); + handles.push_back(&slids); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new PwToy()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/pw-funcs.cpp b/src/toys/pw-funcs.cpp new file mode 100644 index 0000000..f4c6471 --- /dev/null +++ b/src/toys/pw-funcs.cpp @@ -0,0 +1,100 @@ +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> + +using namespace Geom; +using namespace std; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = p[i]; + cairo_d2_sb(cr, B); + } +} + +void cairo_horiz(cairo_t *cr, double y, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, i, y); + cairo_rel_line_to(cr, 0, 10); + } +} + +void cairo_vert(cairo_t *cr, double x, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, x, i); + cairo_rel_line_to(cr, 10, 0); + } +} + +Piecewise<SBasis> log(Interval in) { + Piecewise<SBasis> I = integral(Geom::reciprocal(Linear(in.min(), in.max()))); + return I + Piecewise<SBasis> (-I.segs[0][0] + log(in.min())); +} + +Piecewise<SBasis> xlogx(Interval in) { + Piecewise<SBasis> I = integral(log(in) + Piecewise<SBasis>(1)); + return I + Piecewise<SBasis> (-I.segs[0][0] + in.min()*log(in.min())); +} + +class PwToy: public Toy { + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 1); + + Piecewise<SBasis> pws; + + //pws = Geom::cos(Linear(0,100)) + 3; + pws = Geom::sqrt(Linear(0,100)); + //pws = log(Interval(1,8)); + //Piecewise<SBasis> l(Linear(-100,100)); + //Piecewise<SBasis> one(Linear(1,1)); + //pws = Geom::reciprocal(l*l + one)*l + l; + //pws = xlogx(Interval(0.5,3)); + //pws = Geom::reciprocal(pws); + //pws = -integral(Geom::reciprocal(Linear(1,2)))*Piecewise<SBasis>(Linear(1,2)); + + pws = -pws*width/4 + width/2; + pws.scaleDomain(width/2); + pws.offsetDomain(width/4); + + cairo_pw(cr, pws); + + cairo_stroke(cr); + cairo_set_source_rgba (cr, 0., 0., .5, 1.); + cairo_horiz(cr, 500, pws.cuts); + cairo_stroke(cr); + + *notify << "total pieces: " << pws.size(); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + bool should_draw_numbers() override { return false; } + int should_draw_bounds() override { return 2; } + public: + PwToy () {} +}; + +int main(int argc, char **argv) { + init(argc, argv, new PwToy()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/pw-toy.cpp b/src/toys/pw-toy.cpp new file mode 100644 index 0000000..93ca8ea --- /dev/null +++ b/src/toys/pw-toy.cpp @@ -0,0 +1,117 @@ +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> + +using namespace Geom; +using namespace std; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = Linear(150) + p[i]; + cairo_d2_sb(cr, B); + } +} + +void cairo_horiz(cairo_t *cr, double y, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, i, y); + cairo_rel_line_to(cr, 0, 10); + } +} + +void cairo_vert(cairo_t *cr, double x, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, x, i); + cairo_rel_line_to(cr, 10, 0); + } +} + +#include "pwsbhandle.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file) + +class PwToy: public Toy { + unsigned segs, handles_per_curve, curves; + PWSBHandle pwsbh[2]; + PointHandle interval_test[2]; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 1); + + std::vector<Piecewise<SBasis> > pws(curves); + for(unsigned a = 0; a < curves; a++) { + pws[a] = pwsbh[a].value(); + cairo_pw(cr, pws[a]); + } + cairo_stroke(cr); + + Piecewise<SBasis> pw_out = pws[0] + pws[1]; + + cairo_set_source_rgba (cr, 0., 0., .5, 1.); + cairo_horiz(cr, 500, pw_out.cuts); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0., .5, 1.); + cairo_pw(cr, pws[0] + pws[1]); + cairo_stroke(cr); + + Interval bs = *bounds_local(pw_out, Interval(interval_test[0].pos[0], + interval_test[1].pos[0])); + vector<double> vec; + vec.push_back(bs.min() + 150); vec.push_back(bs.max() + 150); + cairo_set_source_rgba (cr, .5, 0., 0., 1.); + cairo_vert(cr, 100, vec); + cairo_stroke(cr); + + /* Portion demonstration + Piecewise<SBasis> pw_out = portion(pws[0], handles[handles.size() - 2][0], handles[handles.size() - 1][0]); + cairo_set_source_rgba (cr, 0, .5, 0, .25); + cairo_set_line_width(cr, 3); + cairo_pw(cr, pw_out); + cairo_stroke(cr); + */ + + *notify << pws[0].segN(interval_test[0].pos[0]) << "; " << pws[0].segT(interval_test[0].pos[0]); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + bool should_draw_numbers() override { return false; } + + public: + PwToy () { + segs = 3; + handles_per_curve = 4 * segs; + curves = 2; + for(unsigned a = 0; a < curves; a++) { + pwsbh[a] = PWSBHandle(4, 3); + handles.push_back(&pwsbh[a]); + for(unsigned i = 0; i < handles_per_curve; i++) + pwsbh[a].push_back(150 + 300*i/(4*segs), uniform() * 150 + 150 - 50 * a); + } + interval_test[0].pos = Point(150, 400); + interval_test[1].pos = Point(300, 400); + handles.push_back(&interval_test[0]); + handles.push_back(&interval_test[1]); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new PwToy()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/pw-toy.py b/src/toys/pw-toy.py new file mode 100644 index 0000000..011c917 --- /dev/null +++ b/src/toys/pw-toy.py @@ -0,0 +1,180 @@ +#!/usr/bin/python + +import py2geom +import toyframework +import random,gtk +from py2geom_glue import * + +def cairo_pw(cr, p): + for i in range(p.size()): + a,b = p.cuts[i], p.cuts[i+1] + bez = sbasis_to_bezier(p[i], 0) + cr.move_to(a, bez[0]) + cr.curve_to(lerp(a, b, 1./3), bez[1], + lerp(a,b, 2./3), bez[2], + b, bez[3]) + +def cairo_horiz(cr, y, ps): + for p in ps: + cr.move_to(p, y); + cr.rel_line_to(0, 10) + +def cairo_vert(cr, x, ps): + for p in ps: + cr.move_to(x, p) + cr.rel_line_to(10, 0) + +def l2s(l): + sb = py2geom.SBasis(l) + return sb + +def constant(l): + pws = py2geom.PiecewiseSBasis() + + pws.push_cut(0) + pws.push_seg(l2s(py2geom.Linear(l,l)) ) + pws.push_cut(1000) + return pws + +X = l2s(py2geom.Linear(0, 1)) +OmX = l2s(py2geom.Linear(1, 0)) +def bezier_to_sbasis(handles, order): + if(order == 0): + return l2s(py2geom.Linear(handles[0])) + elif(order == 1): + return l2s(py2geom.Linear(handles[0], handles[1])) + else: + return (py2geom.multiply(OmX, bezier_to_sbasis(handles[:-1], order-1)) + + py2geom.multiply(X, bezier_to_sbasis(handles[1:], order-1))) + + +def bez_to_sbasis(handles, order): + return bezier_to_sbasis([h[1] for h in handles], order) + +class PWSBHandle(toyframework.Handle): + def __init__(self, cs, segs): + self.handles_per_curve = cs*segs + self.curve_size = cs + self.segs = segs + self.pts = [] + def append(self, x, y): + self.pts.append(py2geom.Point(x,y)) + def value(self, y_0=0): + pws = py2geom.PiecewiseSBasis() + for i in range(0, self.handles_per_curve, self.curve_size): + pws.push_cut(self.pts[i][0]); + for j in range(i, i + self.curve_size): + self.pts[j] = py2geom.Point(self.pts[j][0], self.pts[j][1] - y_0) + hnd = self.pts[i:i+self.curve_size] + pws.push_seg( bez_to_sbasis(hnd, self.curve_size-1)); + for j in range(i, i + self.curve_size): + self.pts[j] = py2geom.Point(self.pts[j][0], self.pts[j][1] + y_0); + + pws.push_cut(self.pts[self.handles_per_curve - 1][0]); + assert(pws.invariants()); + return pws + + def draw(self, cr, annotes): + for p in self.pts: + toyframework.draw_circ(cr, p) + + def hit(self, mouse): + for i,p in enumerate(self.pts): + if(py2geom.distance(py2geom.Point(*mouse), p) < 5): + return i + return None + + def move_to(self, hit, om, m): + om = py2geom.Point(*om) + m = py2geom.Point(*m) + if hit != None: + i,hand = hit + self.pts[hand] = m + for i in range(self.curve_size, self.handles_per_curve, self.curve_size): + self.pts[i-1] = py2geom.Point(self.pts[i][0],self.pts[i-1][1]) + + for i in range(0, self.handles_per_curve, self.curve_size): + for j in range(1, (self.curve_size-1)): + t = float(j)/(self.curve_size-1) + x = lerp(self.pts[i][0], self.pts[i+self.curve_size-1][0],t) + self.pts[i+j] = py2geom.Point(x, self.pts[i+j][1]) + + + +class PwToy(toyframework.Toy): + def __init__(self): + toyframework.Toy.__init__(self) + self.segs = 2 + self.handles_per_curve = 4 * self.segs + self.curves = 2 + self.pwsbh = [] + self.interval_test = [] + for a in range(self.curves): + self.pwsbh.append(PWSBHandle(4, self.curves)) + self.handles.append(self.pwsbh[a]) + for i in range(self.handles_per_curve): + t = 150 + 300*i/(4*self.segs) + self.pwsbh[a].append(t, random.uniform(0,1) * 150 + 150 - 50 * a) + self.interval_test.append(toyframework.PointHandle(150, 400)) + self.interval_test.append(toyframework.PointHandle(300, 400)) + self.handles.append(self.interval_test[0]) + self.handles.append(self.interval_test[1]) + self.func = "pws[0] + pws[1]" + def gtk_ready(self): + import gtk + self.sb_entry = gtk.Entry() + self.sb_entry.connect("changed", self.sb_changed) + toyframework.get_vbox().add(self.sb_entry) + toyframework.get_vbox().show_all() + self.sb_entry.set_text(self.func) + def sb_changed(self, sb): + self.func = sb.get_text() + self.redraw() + def draw(self, cr, pos, save): + cr.set_source_rgba (0., 0., 0., 1) + cr.set_line_width (1) + + pws = [self.pwsbh[i].value() for i in range(self.curves)] + for p in pws: + cairo_pw(cr, p) + cr.stroke() + + d = locals().copy() + for i in dir(py2geom): + d[i] = py2geom.__dict__[i] + d['l2s'] = l2s + d['constant'] = constant + pw_out = eval(self.func, d) + + bs = py2geom.bounds_local(pw_out, py2geom.OptInterval( + py2geom.Interval(self.interval_test[0].pos[0], + self.interval_test[1].pos[0]))); + if not bs.isEmpty(): + bs = bs.toInterval() + for ph in self.interval_test: + ph.pos= py2geom.Point(ph.pos[0], bs.middle()) + cr.save() + cr.set_source_rgba (.0, 0.25, 0.5, 1.) + cr.rectangle(self.interval_test[0].pos[0], bs.min(), + self.interval_test[1].pos[0]-self.interval_test[0].pos[0], bs.extent()) + cr.stroke() + bs = py2geom.bounds_exact(pw_out); + cr.set_source_rgba (0.25, 0.25, .5, 1.); + if not bs.isEmpty(): + bs = bs.toInterval() + cairo_horiz(cr, bs.middle(), pw_out.cuts); + cr.stroke() + cr.restore() + + cr.set_source_rgba (0., 0., .5, 1.); + cairo_pw(cr, pw_out) + cr.stroke() + + + self.notify = str(bs) + toyframework.Toy.draw(self, cr, pos, save) + +t = PwToy() +import sys + +toyframework.init(sys.argv, t, 500, 500) diff --git a/src/toys/pwsbhandle.cpp b/src/toys/pwsbhandle.cpp new file mode 100644 index 0000000..ce4f21e --- /dev/null +++ b/src/toys/pwsbhandle.cpp @@ -0,0 +1,77 @@ +class PWSBHandle : public Handle{ +public: + unsigned handles_per_curve, curve_size, segs; + PWSBHandle() {} + PWSBHandle(unsigned cs, unsigned segs) :handles_per_curve(cs*segs),curve_size(cs), segs(segs) {} + std::vector<Geom::Point> pts; + virtual void draw(cairo_t *cr, bool annotes = false); + + virtual void* hit(Geom::Point mouse); + virtual void move_to(void* hit, Geom::Point om, Geom::Point m); + void push_back(double x, double y) {pts.push_back(Geom::Point(x,y));} + Piecewise<SBasis> value(double y_0=150) { + Piecewise<SBasis> pws; + Point* base = &pts[0]; + for(unsigned i = 0; i < handles_per_curve; i+=curve_size) { + pws.push_cut(base[i][0]); + //Bad hack to move 0 to 150 + for(unsigned j = i; j < i + curve_size; j++) + base[j] = Point(base[j][0], base[j][1] - y_0); + pws.push_seg( Geom::handles_to_sbasis(base+i, curve_size-1)[1]); + for(unsigned j = i; j < i + curve_size; j++) + base[j] = Point(base[j][0], base[j][1] + y_0); + } + pws.push_cut(base[handles_per_curve - 1][0]); + assert(pws.invariants()); + return pws; + } + virtual void load(FILE* f); + virtual void save(FILE* f); +}; + +void PWSBHandle::draw(cairo_t *cr, bool /*annotes*/) { + for(auto & pt : pts) { + draw_circ(cr, pt); + } +} + +void* PWSBHandle::hit(Geom::Point mouse) { + for(auto & pt : pts) { + if(Geom::distance(mouse, pt) < 5) + return (void*)(&pt); + } + return 0; +} + +void PWSBHandle::move_to(void* hit, Geom::Point /*om*/, Geom::Point m) { + if(hit) { + *(Geom::Point*)hit = m; + Point* base = &pts[0]; + for(unsigned i = curve_size; i < handles_per_curve; i+=curve_size) { + base[i-1][0] = base[i][0]; + } + for(unsigned i = 0; i < handles_per_curve; i+=curve_size) { + for(unsigned j = 1; j < (curve_size-1); j++) { + double t = float(j)/(curve_size-1); + base[i+j][0] = (1 - t)*base[i][0] + t*base[i+curve_size-1][0]; + } + } + } +} + +void PWSBHandle::load(FILE* f) { + unsigned n = 0; + assert(3 == fscanf(f, "%d %d %d\n", &curve_size, &segs, &n)); + assert(n == curve_size*segs); + pts.clear(); + for(unsigned i = 0; i < n; i++) { + pts.push_back(read_point(f)); + } +} + +void PWSBHandle::save(FILE* f) { + fprintf(f, "%d %d %lu\n", curve_size, segs, pts.size()); + for(auto & pt : pts) { + fprintf(f, "%lf %lf\n", pt[0], pt[1]); + } +} diff --git a/src/toys/py2geom_glue.py b/src/toys/py2geom_glue.py new file mode 100644 index 0000000..fa2b5ae --- /dev/null +++ b/src/toys/py2geom_glue.py @@ -0,0 +1,42 @@ +#!/usr/bin/python + +def choose(n, k): + r = 1 + for i in range(1, k+1): + r = (r*(n-k+i))/i + return r + +def W(n, j, k): + q = (n+1)/2. + if((n & 1) == 0 and j == q and k == q): + return 1 + if(k > n-k): + return W(n, n-j, n-k) + assert((k <= q)) + if(k >= q): + return 0 + # assert(!(j >= n-k)); + if(j >= n-k): + return 0 + # assert(!(j < k)); + if(j < k): + return 0 + return float(choose(n-2*k-1, j-k)) / choose(n,j); + +# this produces a degree 2q bezier from a degree k sbasis +def sbasis_to_bezier(B, q): + if(q == 0): + q = len(B) + n = q*2 + result = [0.0 for i in range(n)] + if(q > len(B)): + q = len(B) + n -= 1 + for k in range(q): + for j in range(n-k+1): + result[j] += (W(n, j, k)*B[k][0] + + W(n, n-j, k)*B[k][1]) + return result + +def lerp(a, b, t): + return (1-t)*a + t*b diff --git a/src/toys/ray_test.py b/src/toys/ray_test.py new file mode 100644 index 0000000..6ec8b01 --- /dev/null +++ b/src/toys/ray_test.py @@ -0,0 +1,20 @@ +# test of 2geom ray bindings + +import py2geom as g + +# find one point along a ray +a = g.Point(0,0) +b = g.Point(2,2) + +r = g.Ray(a,b) +from math import sqrt +print r.pointAt(sqrt(2)) + +# measure the angle between two rays +c = g.Point(2,-2) +r2 = g.Ray(a,c) +from math import degrees +# FIXME: the third argument (clockwise) ought to be optional, but has to be supplied +print degrees(g.angle_between(r, r2, True)) +print degrees(g.angle_between(r, r2)) + diff --git a/src/toys/rdm-area.cpp b/src/toys/rdm-area.cpp new file mode 100644 index 0000000..22cc104 --- /dev/null +++ b/src/toys/rdm-area.cpp @@ -0,0 +1,479 @@ +#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-geometric.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <cstdlib>
+#include <vector>
+#include <list>
+#include <algorithm>
+using std::vector;
+using namespace Geom;
+
+#define SIZE 4
+#define NB_SLIDER 2
+
+struct Triangle{
+ Point a,b,c;
+ double area;
+};
+
+//TODO: this would work only for C1 pw<d2<sb>> input. Split the input at corners to work with pwd2sb...
+//TODO: for more general purpose, return a path...
+void toPoly(D2<SBasis> const &f, std::list<Point> &p, double tol, bool include_first=true){
+ D2<SBasis> df = derivative(f);
+ D2<SBasis> d2f = derivative(df);
+ double t=0;
+ if ( include_first ){ p.push_back( f.at0() );}
+ while (t<1){
+ Point v = unit_vector(df.valueAt(t));
+ //OptInterval bounds = bounds_local(df[X]*v[Y]-df[Y]*v[X], Interval(t,1));
+ OptInterval bounds = bounds_local(d2f[X]*v[Y]-d2f[Y]*v[X], Interval(t,1));
+ if (bounds) {
+ double bds_max = (-bounds->min()>bounds->max() ? -bounds->min() : bounds->max());
+ double dt;
+ //if (bds_max<tol) dt = 1;
+ //else dt = tol/bds_max;
+ if (bds_max<tol/4) dt = 1;
+ else dt = 2*std::sqrt( tol / bds_max );
+ t+=dt*5;
+ if (t>1) t = 1;
+ }else{
+ t = 1;
+ }
+ p.push_back( f.valueAt(t) );
+ }
+ return;
+}
+
+std::list<Point> toPoly(std::vector<Piecewise<D2<SBasis> > > f, double tol){
+ assert ( f.size() >0 && f[0].size() >0 );
+ std::list<Point> res;
+
+ for (unsigned i = 0; i<f.size(); i++){
+ for (unsigned j = 0; j<f[i].size(); j++){
+ toPoly(f[i][j],res,tol, j==0);
+ }
+ if ( f[i].segs.front().at0() != f[i].segs.back().at1() ){
+ res.push_back( f[i].segs.front().at0() );
+ }
+ if ( i>0 ) res.push_back( f[0][0].at0() );
+ }
+ return res;
+}
+
+//TODO: this is an ugly hack, use path intersection instead!!
+bool intersect(Point const &a0, Point const &b0, Point const &a1, Point const &b1, Point &c, double tol=.0001){
+ double abaa1 = cross( b0-a0, a1-a0);
+ double abab1 = cross( b0-a0, b1-a0);
+ double abaa0 = cross( b1-a1, a0-a1);
+ double abab0 = cross( b1-a1, b0-a1);
+ if ( abaa1 * abab1 < -tol && abaa0 * abab0 < -tol ){
+ c = a1 - (b1-a1) * abaa1/(abab1-abaa1);
+ return true;
+ }
+#if 1
+ return false;//TODO: handle limit cases!!
+#else
+ if ( abaa1 == 0 && dot( a0-a1, b0-a1 ) < 0 ) {
+ c = a1;
+ return true;
+ }
+ if ( abab1 == 0 && dot( a0-b1, b0-b1 ) < 0 ) {
+ c = b1;
+ return true;
+ }
+ if ( abaa0 == 0 && dot( a1-a0, b1-a0 ) < 0 ) {
+ c = a0;
+ return true;
+ }
+ if ( abab0 == 0 && dot( a1-b0, b1-b0 ) < 0 ) {
+ c = b0;
+ return true;
+ }
+ return false;
+#endif
+}
+
+//TODO: use path intersection stuff!
+void uncross(std::list<Point> &loop){
+ std::list<Point>::iterator b0 = loop.begin(),a0,b1,a1;
+ if ( b0 == loop.end() ) return;
+ a0 = b0;
+ ++b0;
+ if ( b0 == loop.end() ) return;
+ //now a0,b0 are 2 consecutive points.
+ while ( b0 != loop.end() ){
+ b1 = b0;
+ ++b1;
+ if ( b1 != loop.end() ) {
+ a1 = b1;
+ ++b1;
+ if ( b1 != loop.end() ) {
+ //now a0,b0,a1,b1 are 4 consecutive points.
+ Point c;
+ while ( b1 != loop.end() ){
+ if ( intersect(*a0,*b0,*a1,*b1,c) ){
+ if ( c != (*a0) && c != (*b0) ){
+ loop.insert(b1,c);
+ loop.insert(b0,c);
+ ++a1;
+ std::list<Point> loop_piece;
+ loop_piece.insert(loop_piece.begin(), b0, a1 );
+ loop_piece.reverse();
+ loop.erase( b0, a1 );
+ loop.splice( a1, loop_piece );
+ b0 = a0;
+ ++b0;
+ //a1 = b1; a1--;//useless
+ }else{
+ //TODO: handle degenerated crossings...
+ }
+ }else{
+ a1=b1;
+ ++b1;
+ }
+ }
+ }
+ }
+ a0 = b0;
+ ++b0;
+ }
+ return;//We should never reach this point.
+}
+//------------------------------------------------------------
+//------------------------------------------------------------
+//------------------------------------------------------------
+void triangulate(std::list<Point> &pts, std::vector<Triangle> &tri, bool clockwise = false, double tol=.001){
+ pts.unique();
+ while ( !pts.empty() && pts.front() == pts.back() ){ pts.pop_back(); }
+ if ( pts.size() < 3 ) return;
+ //cycle by 1 to have a better looking output...
+ pts.push_back(pts.front()); pts.pop_front();
+ std::list<Point>::iterator a,b,c,m;
+ int sign = (clockwise ? -1 : 1 );
+ a = pts.end(); --a;
+ b = pts.begin();
+ c = b; ++c;
+ //now a,b,c are 3 consecutive points.
+ if ( pts.size() == 3 ) {
+ Triangle abc;
+ abc.a = (*a);
+ abc.b = (*b);
+ abc.c = (*c);
+ abc.area = sign *( cross((*b) - (*a),(*c) - (*b))/2) ;
+ if ( abc.area >0 ){
+ tri.push_back(abc);
+ pts.clear();
+ }
+ return;
+ }
+ bool found = false;
+ while( c != pts.end() ){
+ double abac = cross((*b)-(*a),(*c)-(*a));
+ if ( fabs(abac)<tol && dot( *b-*a, *c-*b ) <= 0) {
+ //this is a degenerated triangle. Remove it and continue.
+ pts.erase(b);
+ triangulate(pts,tri,clockwise);
+ return;
+ }
+ m = c;
+ ++m;
+ while ( m != pts.end() && !found && m!=a){
+ bool pointing_inside;
+ double abam = cross((*b)-(*a),(*m)-(*a));
+ double bcbm = cross((*c)-(*b),(*m)-(*b));
+ if ( sign * abac > 0 ){
+ pointing_inside = ( sign * abam >= 0 ) && ( sign * bcbm >= 0 );
+ }else {
+ pointing_inside = ( sign * abam >=0 ) || ( sign * bcbm >=0);
+ }
+ if ( pointing_inside ){
+ std::list<Point>::iterator p=c,q=++p;
+ Point inter;
+ while ( q != pts.end() && !intersect(*b,*m,*p,*q,inter) ){
+ p=q;
+ ++q;
+ }
+ if ( q == pts.end() ){
+ found = true;
+ }else{
+ ++m;
+ }
+ }else{
+ ++m;
+ }
+ }
+ if ( found ){
+ std::list<Point>pts_beg;
+ pts.insert(b,*b);
+ pts.insert(m,*m);
+ pts_beg.splice(pts_beg.begin(), pts, b, m);
+ triangulate(pts_beg,tri,clockwise);
+ triangulate(pts,tri,clockwise);
+ return;
+ }else{
+ a = b;
+ b = c;
+ ++c;
+ }
+ }
+ //we should never reach this point.
+}
+
+double
+my_rand_generator(){
+ double x = std::rand();
+ return x/RAND_MAX;
+}
+
+class RandomGenerator {
+public:
+ RandomGenerator();
+ RandomGenerator(Piecewise<D2<SBasis> >f_in, double tol=.1);
+ ~RandomGenerator(){};
+ void setDomain(Piecewise<D2<SBasis> >f_in, double tol=.1);
+ void set_generator(double (*rand_func)());
+ void resetRandomizer();
+ Point pt();
+ double area();
+
+protected:
+ double (*rand)();//set this to your favorite generator of numbers in [0,1] (an inkscape param for instance!)
+ long start_seed;
+ long seed;
+ std::vector<Triangle> triangles;
+ std::vector<double> areas;
+};
+
+RandomGenerator::RandomGenerator(){
+ seed = start_seed = 10;
+ rand = &my_rand_generator;//set this to your favorite generator of numbers in [0,1]!
+}
+RandomGenerator::RandomGenerator(Piecewise<D2<SBasis> >f_in, double tol){
+ seed = start_seed = 10;
+ rand = &my_rand_generator;//set this to your favorite generator of numbers in [0,1]!
+ setDomain(f_in, tol);
+}
+void RandomGenerator::setDomain(Piecewise<D2<SBasis> >f_in, double tol){
+ std::vector<Piecewise<D2<SBasis> > >f = split_at_discontinuities(f_in);
+ std::list<Point> p = toPoly( f, tol);
+ uncross(p);
+ if ( p.size()<3) return;
+ double tot_area = 0;
+ std::list<Point>::iterator a = p.begin(), b=a;
+ ++b;
+ while(b!=p.end()){
+ tot_area += ((*b)[X]-(*a)[X]) * ((*b)[Y]+(*a)[Y])/2;
+ ++a;++b;
+ }
+ bool clockwise = tot_area < 0;
+ triangles = std::vector<Triangle>();
+ triangulate(p,triangles,clockwise);
+ areas = std::vector<double>(triangles.size(),0.);
+ double cumul = 0;
+ for (unsigned i = 0; i<triangles.size(); i++){
+ cumul += triangles[i].area;
+ areas[i] = cumul;
+ }
+}
+
+void RandomGenerator::resetRandomizer(){
+ seed = start_seed;
+}
+Point RandomGenerator::pt(){
+ if (areas.empty()) return Point(0,0);
+ double pick_area = rand()*areas.back();
+ std::vector<double>::iterator picked = std::lower_bound( areas.begin(), areas.end(), pick_area);
+ unsigned i = picked - areas.begin();
+ double x = (*rand)();
+ double y = (*rand)();
+ if ( x+y > 1) {
+ x = 1-x;
+ y = 1-y;
+ }
+ //x=.3; y=.3;
+ Point res;
+ res = triangles[i].a;
+ res += x * ( triangles[i].b - triangles[i].a );
+ res += y * ( triangles[i].c - triangles[i].a );
+ return res;
+}
+double RandomGenerator::area(){
+ if (areas.empty()) return 0;
+ return areas.back();
+}
+void RandomGenerator::set_generator(double (*f)()){
+ rand = f;//set this to your favorite generator of numbers in [0,1]!
+}
+
+
+
+
+
+
+//-------------------------------------------------------
+// The toy!
+//-------------------------------------------------------
+class RandomToy: public Toy {
+
+ PointHandle adjuster[NB_SLIDER];
+
+public:
+ PointSetHandle b1_handle;
+ PointSetHandle b2_handle;
+ void draw(cairo_t *cr,
+ std::ostringstream *notify,
+ int width, int height, bool save, std::ostringstream *timer_stream) override {
+ srand(10);
+ for(unsigned i=0; i<NB_SLIDER; i++){
+ adjuster[i].pos[X] = 30+i*20;
+ if (adjuster[i].pos[Y]<100) adjuster[i].pos[Y] = 100;
+ if (adjuster[i].pos[Y]>400) adjuster[i].pos[Y] = 400;
+ cairo_move_to(cr, Point(30+i*20,100));
+ cairo_line_to(cr, Point(30+i*20,400));
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ }
+ double tol = (400-adjuster[0].pos[Y])/300.*5+0.05;
+ double tau = (400-adjuster[1].pos[Y])/300.;
+// double scale_topback = (250-adjuster[2].pos[Y])/150.*5;
+// double scale_botfront = (250-adjuster[3].pos[Y])/150.*5;
+// double scale_botback = (250-adjuster[4].pos[Y])/150.*5;
+// double growth = 1+(250-adjuster[5].pos[Y])/150.*.1;
+// double rdmness = 1+(400-adjuster[6].pos[Y])/300.*.9;
+// double bend_amount = (250-adjuster[7].pos[Y])/300.*100.;
+
+ b1_handle.pts.back() = b2_handle.pts.front();
+ b1_handle.pts.front() = b2_handle.pts.back();
+ D2<SBasis> B1 = b1_handle.asBezier();
+ D2<SBasis> B2 = b2_handle.asBezier();
+
+ cairo_set_line_width(cr, 0.3);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ cairo_d2_sb(cr, B1);
+ cairo_d2_sb(cr, B2);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+
+ Piecewise<D2<SBasis> >B;
+ B.concat(Piecewise<D2<SBasis> >(B1));
+ B.continuousConcat(Piecewise<D2<SBasis> >(B2));
+
+ Piecewise<SBasis> are;
+
+ Point centroid_tmp(0,0);
+ are = integral(dot(B, rot90(derivative(B))))*0.5;
+ are = (are - are.firstValue())*(height/10) / (are.lastValue() - are.firstValue());
+
+ D2<Piecewise<SBasis> > are_graph(Piecewise<SBasis>(Linear(0, width)), are );
+ std::cout << are.firstValue() << "," << are.lastValue() << std::endl;
+ cairo_save(cr);
+ cairo_d2_pw_sb(cr, are_graph);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+
+#if 0
+ std::vector<Piecewise<D2<SBasis> > >f = split_at_discontinuities(B);
+ std::list<Point> p = toPoly( f, tol);
+ uncross(p);
+ cairo_move_to(cr, p.front());
+ for (std::list<Point>::iterator pt = p.begin(); pt!=p.end(); ++pt){
+ cairo_line_to(cr, *pt);
+ //if (i++>p.size()*tau) break;
+ }
+ cairo_set_line_width (cr, 3);
+ cairo_set_source_rgba (cr, 1., 0., 0., .5);
+ cairo_stroke(cr);
+
+ if ( p.size()<3) return;
+ double tot_area = 0;
+ std::list<Point>::iterator a = p.begin(), b=a;
+ b++;
+ while(b!=p.end()){
+ tot_area += ((*b)[X]-(*a)[X]) * ((*b)[Y]+(*a)[Y])/2;
+ a++;b++;
+ }
+ bool clockwise = tot_area < 0;
+
+ std::vector<Triangle> tri;
+ int nbiter =0;
+ triangulate(p,tri,clockwise);
+ cairo_set_source_rgba (cr, 1., 1., 0., 1);
+ cairo_stroke(cr);
+ for (unsigned i=0; i<tri.size(); i++){
+ cairo_move_to(cr, tri[i].a);
+ cairo_line_to(cr, tri[i].b);
+ cairo_line_to(cr, tri[i].c);
+ cairo_line_to(cr, tri[i].a);
+ cairo_set_line_width (cr, .5);
+ cairo_set_source_rgba (cr, 0., 0., .9, .5);
+ cairo_stroke(cr);
+ cairo_move_to(cr, tri[i].a);
+ cairo_line_to(cr, tri[i].b);
+ cairo_line_to(cr, tri[i].c);
+ cairo_line_to(cr, tri[i].a);
+ cairo_set_source_rgba (cr, 0.5, 0., .9, .1);
+ cairo_fill(cr);
+ }
+#endif
+
+ RandomGenerator rdm = RandomGenerator(B, tol);
+ for(int i = 0; i < rdm.area()/5*tau; i++) {
+ draw_handle(cr, rdm.pt());
+ }
+ cairo_set_source_rgba (cr, 0., 0., 0., 1);
+ cairo_stroke(cr);
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ RandomToy(){
+ for(int i = 0; i < SIZE; i++) {
+ b1_handle.push_back(150+uniform()*300,150+uniform()*300);
+ b2_handle.push_back(150+uniform()*300,150+uniform()*300);
+ }
+ b1_handle.pts[0] = Geom::Point(400,300);
+ b1_handle.pts[1] = Geom::Point(400,400);
+ b1_handle.pts[2] = Geom::Point(100,400);
+ b1_handle.pts[3] = Geom::Point(100,300);
+
+ b2_handle.pts[0] = Geom::Point(100,300);
+ b2_handle.pts[1] = Geom::Point(100,200);
+ b2_handle.pts[2] = Geom::Point(400,200);
+ b2_handle.pts[3] = Geom::Point(400,300);
+ handles.push_back(&b1_handle);
+ handles.push_back(&b2_handle);
+
+ for(unsigned i = 0; i < NB_SLIDER; i++) {
+ adjuster[i].pos = Geom::Point(30+i*20,250);
+ handles.push_back(&(adjuster[i]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new RandomToy);
+ return 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/toys/rect-toy.cpp b/src/toys/rect-toy.cpp new file mode 100644 index 0000000..929afb7 --- /dev/null +++ b/src/toys/rect-toy.cpp @@ -0,0 +1,395 @@ +/* + * Rect Toy + * + * Copyright 2008 njh <> + * multitoy approach + * Copyright 2008 Marco <> + * + * 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/line.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/transforms.h> + +#include <2geom/angle.h> + +#include <vector> +#include <string> +#include <optional> + +using namespace Geom; + + + +std::string angle_formatter(double angle) +{ + return default_formatter(decimal_round(deg_from_rad(angle),2)); +} + +class LineToy : public Toy +{ + enum menu_item_t + { + SHOW_MENU = 0, + TEST_CREATE, + TEST_PROJECTION, + TEST_ORTHO, + TEST_DISTANCE, + TEST_POSITION, + TEST_SEG_BISEC, + TEST_ANGLE_BISEC, + TEST_COLLINEAR, + TEST_INTERSECTIONS, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + }; + + enum toggle_label_t + { + }; + + enum slider_label_t + { + ANGLE_SLIDER = 0, + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &LineToy::draw_menu; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + sliders.clear(); + toggles.clear(); + handles.clear(); + } + + + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/ ) + { + init_common_ctrl_geom(cr, width, height, notify); + } + + + void init_create() + { + init_common(); + + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + + sliders.emplace_back(0, 2*M_PI, 0, 0, "angle"); + sliders[ANGLE_SLIDER].formatter(&angle_formatter); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&(sliders[ANGLE_SLIDER])); + } + + void draw_create(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + init_create_ctrl_geom(cr, notify, width, height); + + Rect r1(p1.pos, p2.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + + Affine rot = Translate(-r1.midpoint())*Rotate(sliders[ANGLE_SLIDER].value())*Translate(r1.midpoint()); + cairo_rectangle(cr, r1*rot); + cairo_move_to(cr, r1.corner(3)*rot); + for(int i = 0; i < 4; i++) { + cairo_line_to(cr, r1.corner(i)*rot); + } + + cairo_stroke(cr); + + draw_label(cr, p1, "P1"); + draw_label(cr, p2, "P2"); + } + + + + void init_intersections() + { + init_common(); + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + p3.pos = Point(100, 250); + p4.pos = Point(200, 450); + p5.pos = Point(50, 150); + p6.pos = Point(480, 60); + + handles.push_back(&p1); + handles.push_back(&p2); + handles.push_back(&p3); + handles.push_back(&p4); + handles.push_back(&p5); + handles.push_back(&p6); + } + + void draw_intersections(cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, + std::ostringstream */*timer_stream*/) + { + draw_common(cr, notify, width, height, save); + + Rect r1(p1.pos, p2.pos); + Rect r2(p3.pos, p4.pos); + Line l1(p5.pos, p6.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.0, 0.3, 0.0, 1.0); + cairo_rectangle(cr, r2); + cairo_stroke(cr); + + OptRect r1xr2 = intersect(r1, r2); + if(r1xr2) { + cairo_set_source_rgba(cr, 1.0, 0.7, 0.7, 1.0); + cairo_rectangle(cr, *r1xr2); + cairo_fill(cr); + } + + cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0); + cairo_set_line_width(cr, 0.3); + draw_line(cr, l1); + cairo_stroke(cr); + + + std::optional<LineSegment> ls = rect_line_intersect(r1, LineSegment(p5.pos, p6.pos)); + *notify << "intersects: " << ((ls)?"true":"false") << std::endl; + if(ls) { + draw_handle(cr, (*ls)[0]); + draw_handle(cr, (*ls)[1]); + cairo_save(cr); + cairo_set_line_width(cr, 2); + cairo_move_to(cr, (*ls)[0]); + cairo_line_to(cr, (*ls)[1]); + cairo_stroke(cr); + cairo_restore(cr); + } + + + } + + void init_common_ctrl_geom(cairo_t* /*cr*/, int /*width*/, int /*height*/, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + } + } + + void init_create_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ) + { + set_control_geometry = false; + + sliders[ANGLE_SLIDER].geometry(Point(50, height - 50), 180); + } + } + + + + void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) + { + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); + } + + void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) + { + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); + } + + void draw_segment(cairo_t* cr, LineSegment const& ls) + { + draw_segment(cr, ls[0], ls[1]); + } + + void draw_ray(cairo_t* cr, Ray const& r) + { + double angle = r.angle(); + draw_segment(cr, r.origin(), angle, m_length); + } + + void draw_line(cairo_t* cr, Line const& l) + { + double angle = l.angle(); + draw_segment(cr, l.origin(), angle, m_length); + draw_segment(cr, l.origin(), angle, -m_length); + } + + void draw_label(cairo_t* cr, PointHandle const& ph, const char* label) + { + draw_text(cr, ph.pos+op, label); + } + + void draw_label(cairo_t* cr, Line const& l, const char* label) + { + draw_text(cr, projection(Point(m_width/2-30, m_height/2-30), l)+op, label); + } + + void draw_label(cairo_t* cr, LineSegment const& ls, const char* label) + { + draw_text(cr, middle_point(ls[0], ls[1])+op, label); + } + + void draw_label(cairo_t* cr, Ray const& r, const char* label) + { + Point prj = r.pointAt(r.nearestTime(Point(m_width/2-30, m_height/2-30))); + if (L2(r.origin() - prj) < 100) + { + prj = r.origin() + 100*r.vector(); + } + draw_text(cr, prj+op, label); + } + + void init_menu() + { + handles.clear(); + sliders.clear(); + toggles.clear(); + } + + void draw_menu( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, + std::ostringstream */*timer_stream*/) + { + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &LineToy::draw_menu; + break; + case 'B': + init_create(); + draw_f = &LineToy::draw_create; + break; + case 'C': + init_intersections(); + draw_f = &LineToy::draw_intersections; + break; + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + LineToy() + { + op = Point(5,5); + } + + private: + typedef void (LineToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + bool set_common_control_geometry; + bool set_control_geometry; + PointHandle p1, p2, p3, p4, p5, p6, O; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + Point op; + double m_width, m_height, m_length; + +}; // end class LineToy + + +const char* LineToy::menu_items[] = +{ + "show this menu", + "rect generation", + "intersection with a line" +}; + +const char LineToy::keys[] = +{ + 'A', 'B', 'C' +}; + + + +int main(int argc, char **argv) +{ + init( argc, argv, new LineToy()); + return 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/toys/rect_01.cpp b/src/toys/rect_01.cpp new file mode 100644 index 0000000..333843b --- /dev/null +++ b/src/toys/rect_01.cpp @@ -0,0 +1,98 @@ +/* + * SimpleRect examples + * + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * 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. + */ + +/* +Very Simple example of a toy that creates a rectangle on screen + +I am still very inexperienced with lib2geom +so don't use these examples as a reference :) +*/ + + + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +#include <2geom/transforms.h> + +using std::vector; +using namespace Geom; + + + + +class SimpleRect: public Toy { + PointSetHandle psh; + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + { + cairo_save(cr); + Path p1; + p1.appendNew<LineSegment>(Point(0, 0)); + p1.appendNew<LineSegment>(Point(100, 0)); + p1.appendNew<LineSegment>(Point(100, 100)); + p1.appendNew<LineSegment>(Point(0, 100)); + p1.appendNew<LineSegment>(Point(0, 0)); + p1.close(); + + Path p2 = p1 * Rotate::from_degrees(45); // + + cairo_set_source_rgb(cr, 0,0,0); + cairo_path(cr, p1); + cairo_path(cr, p2); + cairo_stroke(cr); + cairo_restore(cr); + } + PointHandle p1, p2; + p1.pos = Point(300, 50); + p2.pos = Point(450, 450); + + Rect r1(p1.pos, p2.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + SimpleRect(){ + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SimpleRect()); + + return 0; +} + diff --git a/src/toys/rect_02.cpp b/src/toys/rect_02.cpp new file mode 100644 index 0000000..a903e2d --- /dev/null +++ b/src/toys/rect_02.cpp @@ -0,0 +1,81 @@ +/* + * SimpleRect examples + * + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * 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. + */ + +/* +Simple example of a toy that creates a rectangle on screen. +We also add two handles (that we don't use in our program) + +I am still very inexperienced with lib2geom +so don't use these examples as a reference :) +*/ + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + + +class SimpleRect: public Toy { + PointSetHandle psh; + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + + PointHandle p1, p2; + p1.pos = Point(400, 50); + p2.pos = Point(450, 450); + + Rect r1(p1.pos, p2.pos); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save, timer_stream); + } + public: + SimpleRect (unsigned no_of_handles) { + handles.push_back(&psh); + for(unsigned i = 0; i < no_of_handles; i++) + psh.push_back( 200 + ( i * 10 ), 300 + ( i * 10 ) ); + } +}; + +int main(int argc, char **argv) { + unsigned no_of_handles=2; + if(argc > 1) + sscanf(argv[1], "%d", &no_of_handles); + init(argc, argv, new SimpleRect(no_of_handles)); + + return 0; +} + diff --git a/src/toys/rect_03.cpp b/src/toys/rect_03.cpp new file mode 100644 index 0000000..89be320 --- /dev/null +++ b/src/toys/rect_03.cpp @@ -0,0 +1,78 @@ +/* + * SimpleRect examples + * + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * 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. + */ + +/* +Simple example of a toy that creates a rectangle on screen. +Now we use the 2 handles to define the rect + +I am still very inexperienced with lib2geom +so don't use these examples as a reference :) +*/ + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + + + +class SimpleRect: public Toy { + PointSetHandle psh; + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + Rect r1(psh.pts[0], psh.pts[1]); + + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width(cr, 0.3); + cairo_rectangle(cr, r1); + + + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save, timer_stream); +} + public: + SimpleRect (unsigned no_of_handles) { + handles.push_back(&psh); + for(unsigned i = 0; i < no_of_handles; i++) + psh.push_back( 200 + ( i * 20 ), 300 + ( i * 20 ) ); + } +}; + +int main(int argc, char **argv) { + unsigned no_of_handles=2; + if(argc > 1) + sscanf(argv[1], "%d", &no_of_handles); + init(argc, argv, new SimpleRect(no_of_handles)); + + return 0; +} + diff --git a/src/toys/root-finder-comparer.cpp b/src/toys/root-finder-comparer.cpp new file mode 100644 index 0000000..3952d7c --- /dev/null +++ b/src/toys/root-finder-comparer.cpp @@ -0,0 +1,247 @@ +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/solver.h> +#include <2geom/sbasis-poly.h> +#include "../2geom/orphan-code/nearestpoint.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file) +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZROOTS_TEST 0 +#if ZROOTS_TEST +#include <2geom/zroots.c> +#endif + +using std::swap; + +namespace Geom{ +extern void subdiv_sbasis(SBasis const & s, + std::vector<double> & roots, + double left, double right); +}; + +double eval_bernstein(double* w, double t, unsigned N) { + double Vtemp[2*N]; + //const int degree = N-1; + for (unsigned i = 0; i < N; i++) + Vtemp[i] = w[i]; + + /* Triangle computation */ + const double omt = (1-t); + //Left[0] = Vtemp[0]; + //Right[degree] = Vtemp[degree]; + double *prev_row = Vtemp; + double *row = Vtemp + N; + for (unsigned i = 1; i < N; i++) { + for (unsigned j = 0; j < N - i; j++) { + row[j] = omt*prev_row[j] + t*prev_row[j+1]; + } + //Left[i] = row[0]; + //Right[degree-i] = row[degree-i]; + swap(prev_row, row); + } + return prev_row[0]; +} + +#include <vector> +using std::vector; +using namespace Geom; + +#ifdef HAVE_GSL +#include <complex> +using std::complex; +#endif + +class RootFinderComparer: public Toy { +public: + PointSetHandle psh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + std::vector<Geom::Point> trans; + trans.resize(psh.size()); + for(unsigned i = 0; i < handles.size(); i++) { + trans[i] = psh.pts[i] - Geom::Point(0, height/2); + } + + Timer tm; + + + std::vector<double> solutions; + solutions.resize(6); + + tm.ask_for_timeslice(); + tm.start(); + FindRoots(&trans[0], 5, &solutions[0], 0); + Timer::Time als_time = tm.lap(); + *notify << "original time = " << als_time << std::endl; + + D2<SBasis> test_sb = psh.asBezier();//handles_to_sbasis(handles.begin(),5); + Interval bs = *bounds_exact(test_sb[1]); + cairo_move_to(cr, test_sb[0](0), bs.min()); + cairo_line_to(cr, test_sb[0](1), bs.min()); + cairo_move_to(cr, test_sb[0](0), bs.max()); + cairo_line_to(cr, test_sb[0](1), bs.max()); + cairo_stroke(cr); + *notify << "sb bounds = "<<bs.min()<< ", " <<bs.max()<<std::endl; + Poly ply = sbasis_to_poly(test_sb[1]); + ply = Poly(height/2) - ply; + cairo_move_to(cr, 0, height/2); + cairo_line_to(cr, width, height/2); + cairo_stroke(cr); +#ifdef HAVE_GSL + vector<complex<double> > complex_solutions; + complex_solutions = solve(ply); +#if 1 + *notify << "gsl: "; + std::copy(complex_solutions.begin(), complex_solutions.end(), std::ostream_iterator<std::complex<double> >(*notify, ",\t")); + *notify << std::endl; +#endif +#endif + +#if ZROOTS_TEST + fcomplex a[ply.size()]; + for(unsigned i = 0; i < ply.size(); i++) { + a[i] = ply[i]; + } + //copy(a, a+ply.size(), ply.begin()); + fcomplex rts[ply.size()]; + + zroots(a, ply.size(), rts, true); + for(unsigned i = 0; i < ply.size(); i++) { + if(! a[i].imag()) + solutions[i] = a[i].real(); + } +#endif + +#ifdef HAVE_GSL + + tm.ask_for_timeslice(); + tm.start(); + solve(ply); + als_time = tm.lap(); + *timer_stream << "gsl poly = " << als_time << std::endl; +#endif + + #if ZROOTS_TEST + tm.ask_for_timeslice(); + tm.start(); + zroots(a, ply.size(), rts, false); + als_time = tm.lap(); + *timer_stream << "zroots poly = " << als_time << std::endl; + #endif + + #if LAGUERRE_TEST + tm.ask_for_timeslice(); + tm.start(); + Laguerre(ply); + als_time = tm.lap(); + *timer_stream << "Laguerre poly = " << als_time << std::endl; + complex_solutions = Laguerre(ply); + + #endif + + #define SBASIS_SUBDIV_TEST 0 + #if SBASIS_SUBDIV_TEST + tm.ask_for_timeslice(); + tm.start(); + subdiv_sbasis(-test_sb[1] + Linear(3*width/4), + rts, 0, 1); + als_time = tm.lap(); + *timer_stream << "sbasis subdivision = " << als_time << std::endl; + #endif + #if 0 + tm.ask_for_timeslice(); + tm.start(); + solutions.resize(0); + find_parametric_bezier_roots(&trans[0], 5, solutions, 0); + als_time = tm.lap(); + *timer_stream << "solver parametric = " << als_time << std::endl; + #endif + double ys[trans.size()]; + for(unsigned i = 0; i < trans.size(); i++) { + ys[i] = trans[i][1]; + double x = double(i)/(trans.size()-1); + x = (1-x)*height/4 + x*height*3/4; + draw_handle(cr, Geom::Point(x, height/2 + ys[i])); + } + + solutions.resize(0); + tm.ask_for_timeslice(); + tm.start(); + find_bernstein_roots(ys, 5, solutions, 0, 0, 1, false); + als_time = tm.lap(); + *notify << "found sub solutions at:\n"; + std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ",")); + *notify << "solver 1d bernstein subdivision n_slns = " << solutions.size() + << ", time = " << als_time << std::endl; + + solutions.resize(0); + tm.ask_for_timeslice(); + tm.start(); + find_bernstein_roots(ys, 5, solutions, 0, 0, 1, true); + als_time = tm.lap(); + + *notify << "solver 1d bernstein secant subdivision slns" << solutions.size() + << ", time = " << als_time << std::endl; + *notify << "found secant solutions at:\n"; + std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ",")); + *notify << "solver 1d bernstein subdivision accuracy:" + << std::endl; + for(double solution : solutions) { + *notify << solution << ":" << eval_bernstein(ys, solution, trans.size()) << ","; + } + tm.ask_for_timeslice(); + tm.start(); + solutions = roots( -test_sb[1] + Linear(height/2)); + als_time = tm.lap(); +#if 1 + *notify << "sbasis roots: "; + std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double>(*notify, ",\t")); + *notify << "\n time = " << als_time << std::endl; +#endif + for(double solution : solutions) { + double x = test_sb[0](solution); + draw_cross(cr, Geom::Point(x, height/2)); + + } + + *notify << "found " << solutions.size() << "solutions at:\n"; + std::copy(solutions.begin(), solutions.end(), std::ostream_iterator<double >(*notify, ",")); + + D2<SBasis> B = psh.asBezier();//handles_to_sbasis(handles.begin(), 5); + Geom::Path pb; + pb.append(B); + pb.close(false); + cairo_path(cr, pb); + + B[0] = Linear(width/4, 3*width/4); + cairo_d2_sb(cr, B); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + RootFinderComparer(unsigned degree) + { + for(unsigned i = 0; i < degree; i++) psh.push_back(Geom::Point(uniform()*400, uniform()*400)); + handles.push_back(&psh); + } +}; + +int main(int argc, char **argv) { + unsigned bez_ord = 6; + if(argc > 1) + sscanf(argv[1], "%d", &bez_ord); + init(argc, argv, new RootFinderComparer(bez_ord)); + + return 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/toys/rtree-toy.cpp b/src/toys/rtree-toy.cpp new file mode 100644 index 0000000..b7872fa --- /dev/null +++ b/src/toys/rtree-toy.cpp @@ -0,0 +1,573 @@ +/* + * Copyright 2009 Evangelos Katsikaros <vkatsikaros at yahoo dot gr> + * + * 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. + */ + +/* + initial toy for redblack trees +*/ + + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <time.h> +#include <vector> +#include <sstream> +#include <getopt.h> + +#include <2geom/orphan-code/rtree.h> +#include "../2geom/orphan-code/rtree.cpp" + + +//using std::vector; +using namespace Geom; +using namespace std; + +// make sure that in RTreeToy() constructor you assign the same number of colors +// otherwise, they extra will be black :P +const int no_of_colors = 8; +const string search_str = "Mode: Search (Area: Click whitespace and Drag)"; +const string update_str = "Mode: Update (Click Bounding Box (dark gray) and Drag) NOT implemented"; +const string insert_str = "Mode: Insert (Click whitespace and Drag)" ; +const string erase_str = "Mode: Delete (Click on Bounding Box (dark gray))"; +const string help_str = "'I': Insert, 'U': Update, 'S': Search, 'D': Delete"; + + +const char* program_name; + +class RTreeToy: public Toy +{ + +// PointSetHandle handle_set; +// std::vector< Handle > rectangle_handles; + std::vector< RectHandle > rectangles; + + Geom::Point starting_point; // during click and drag: start point of click + Geom::Point ending_point; // during click and drag: end point of click (release) + Geom::Point highlight_point; // not used + + // colors we are going to use for different purposes + colour color_shape, color_shape_guide; + colour color_select_area, color_select_area_guide; // red(a=0.6), red + + bool add_new_rect; + bool delete_rect; + + Rect rect_chosen; // the rectangle of the search area + Rect dummy_draw; // the "helper" rectangle that is shown during the click and drag (before the mouse release) + + // save the bounding boxes of the tree in here + std::vector< std::vector< Rect > > rects_level; + std::vector<colour> color_rtree_level; + unsigned drawBB_color; + bool drawBB_color_all; + + enum the_modes { + INSERT_MODE = 0, + UPDATE_MODE, + DELETE_MODE, + SEARCH_MODE, + } mode; // insert/alter, search, delete modes + bool drawBB; // draw bounding boxes of RTree + string out_str, drawBB_str, drawBB_color_str; + + // printing of the tree + //int help_counter; // the "x" of the label of each node + static const int label_size = 15 ; // size the label of each node + + Geom::RTree rtree; + + void * hit; + unsigned rect_id; + + + // used for the keys that switch between modes + enum menu_item_t + { + INSERT_I = 0, + UPDATE_U, + DELETE_D, + SEARCH_S, + BB_TOGGLE_T, + BB_DRAW_0, + BB_DRAW_1, + BB_DRAW_2, + BB_DRAW_3, + BB_DRAW_4, + BB_DRAW_5, + BB_DRAW_ALL_O, + TOTAL_ITEMS // this one must be the last item + }; + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + + + void draw( cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream ) override { + cairo_set_line_width( cr, 1 ); + cairo_set_source_rgba( cr, color_shape ); + cairo_stroke( cr ); + + // draw the shapes we save in the rtree + for(auto & rectangle : rectangles){ + rectangle.draw( cr, true ); + } + cairo_set_source_rgba( cr, color_shape ); + cairo_stroke( cr ); + + // draw a rect if we did click & drag (so that we know what we are going to create) + if( add_new_rect ){ + dummy_draw = Rect( starting_point, ending_point ); + cairo_rectangle( cr, dummy_draw ); + if( mode == INSERT_MODE ){ + cairo_set_source_rgba( cr, color_shape_guide ); + } + else if( mode == SEARCH_MODE ){ + cairo_set_source_rgba( cr, color_select_area_guide ); + } + cairo_stroke( cr ); + } + + // draw a rect for the search area + cairo_rectangle( cr, rect_chosen ); + cairo_set_source_rgba( cr, color_select_area ); + cairo_stroke( cr ); + + *notify << help_str << "\n" + << "'T': Bounding Boxes: " << drawBB_str << ", '0'-'" << no_of_colors << "', 'P': Show Layer: " << drawBB_color_str << "\n" + << out_str; + + if( drawBB ){ + for(unsigned color = 0 ; color < rects_level.size() ; color++ ){ + if( drawBB_color == color || drawBB_color_all ){ + for(auto & j : rects_level){ + cairo_rectangle( cr, j ); + } + cairo_set_source_rgba( cr, color_rtree_level[color] ); + cairo_stroke( cr ); + } + } + } + + Toy::draw( cr, notify, width, height, save,timer_stream ); + } + + void mouse_moved( GdkEventMotion* e ) override{ + if(add_new_rect && + ( mode == INSERT_MODE || mode == SEARCH_MODE ) ) + { + Toy::mouse_moved( e ); + ending_point = Point( e->x, e->y ); + } + } + + void mouse_pressed( GdkEventButton* e ) override { + Toy::mouse_pressed( e ); + if(e->button == 1){ // left mouse button + if( mode == INSERT_MODE ){ + starting_point = Point( e->x, e->y ); + ending_point = starting_point; + add_new_rect = true; + } + else if( mode == SEARCH_MODE ){ + starting_point = Point( e->x, e->y ); + ending_point = starting_point; + add_new_rect = true; + } + else if( mode == DELETE_MODE ) { + Geom::Point mouse(e->x, e->y); + unsigned i = 0; + for( i = 0; i < rectangles.size(); i++) { + hit = rectangles[i].hit(mouse); + if( hit ) { + break; + } + } + if( hit ){ + // erase specific element + stringstream shape_id( rectangles[i].name ); + unsigned shape_id_int; + shape_id >> shape_id_int; + + rtree.erase( rectangles[i].pos, shape_id_int ); + rectangles.erase( rectangles.begin() + i ); +// check_if_deleted( ); +// check_if_duplicates( ); + delete_rect = true; + } + hit = NULL; + } + } + else if( e->button == 2 ){ //middle button + } + else if( e->button == 3 ){ //right button + } + } + + void mouse_released( GdkEventButton* e ) override { + Toy::mouse_released( e ); + if( e->button == 1 ) { //left mouse button + if( mode == INSERT_MODE ) { + if( add_new_rect ){ + ending_point = Point( e->x, e->y ); + RectHandle t( Rect(starting_point, ending_point), false ); + + std::stringstream out; + out << rect_id;; + t.name = out.str(); + rectangles.push_back( t ); + rect_id++; + + insert_in_tree_the_last_rect(); + find_rtree_subtrees_bounding_boxes( rtree ); + add_new_rect = false; + } + } + else if( mode == SEARCH_MODE ){ + if( add_new_rect ){ + ending_point = Point( e->x, e->y ); + rect_chosen = Rect( starting_point, ending_point ); + + std::vector< int > result(0); + + if( rtree.root ){ + rtree.search( rect_chosen, &result, rtree.root ); + } + std::cout << "Search results: " << result.size() << std::endl; + for(int i : result){ + std::cout << i << ", " ; + } + std::cout << std::endl; + + add_new_rect = false; + } + } + else if( mode == DELETE_MODE ) { // mode: delete + if( delete_rect ){ + delete_rect = false; + if( rtree.root ){ + find_rtree_subtrees_bounding_boxes( rtree ); + } + std::cout << " \nTree:\n" << std::endl; + rtree.print_tree( rtree.root, 0 ); + std::cout << "...done\n" << std::endl; + } + } + } + else if( e->button == 2 ){ //middle button + } + else if( e->button == 3 ){ //right button + } + } + + + void key_hit( GdkEventKey *e ) override + { + char choice = std::toupper( e->keyval ); + switch ( choice ) + { + case 'I': + mode = INSERT_MODE; + out_str = insert_str; + break; + case 'S': + mode = SEARCH_MODE; + out_str = search_str; + break; + case 'D': + mode = DELETE_MODE; + out_str = erase_str; + break; + case 'U': + mode = UPDATE_MODE; + out_str = update_str; + break; + case 'T': + if( drawBB ){ + drawBB = false; + drawBB_str = "OFF"; + } + else{ + drawBB = true; + drawBB_str = "ON"; + } + break; + case 'P': + drawBB_color_all = true; + drawBB_color = 9; + drawBB_color_str = "all"; + break; + case '0': + drawBB_color_all = false; + drawBB_color = 0; + drawBB_color_str = "0"; + break; + case '1': + drawBB_color_all = false; + drawBB_color = 1; + drawBB_color_str = "1"; + break; + case '2': + drawBB_color_all = false; + drawBB_color = 2; + drawBB_color_str = "2"; + break; + case '3': + drawBB_color_all = false; + drawBB_color = 3; + drawBB_color_str = "3"; + break; + case '4': + drawBB_color_all = false; + drawBB_color = 4; + drawBB_color_str = "4"; + break; + case '5': + drawBB_color_all = false; + drawBB_color = 5; + drawBB_color_str = "5"; + break; + case '6': + drawBB_color_all = false; + drawBB_color = 6; + drawBB_color_str = "6"; + break; + case '7': + drawBB_color_all = false; + drawBB_color = 7; + drawBB_color_str = "7"; + break; + } + redraw(); + } + + + void insert_in_tree_the_last_rect(){ + unsigned i = rectangles.size() - 1; + Rect r1 = rectangles[i].pos; + + stringstream shape_id( rectangles[i].name ); + unsigned shape_id_int; + shape_id >> shape_id_int; + + // insert in R tree + rtree.insert( r1, shape_id_int ); + std::cout << " \nTree:\n" << std::endl; + rtree.print_tree( rtree.root, 0 ); + std::cout << "...done\n" << std::endl; + }; + + + void find_rtree_subtrees_bounding_boxes( Geom::RTree tree ){ + if( tree.root ){ + // clear existing bounding boxes + for(auto & color : rects_level){ + color.clear(); + } + save_bb( tree.root, 0); + } + }; + + // TODO fix this. + void save_bb( Geom::RTreeNode* subtree_root, int depth ) + { + if( subtree_root->children_nodes.size() > 0 ){ + + // descend in each one of the elements and call print_tree + for(auto & children_node : subtree_root->children_nodes){ + Rect r1( children_node.bounding_box ); + rects_level[ depth ].push_back( r1 ); + + if( depth == no_of_colors - 1 ){ // if we reached Nth levels of colors, roll back to color 0 + save_bb( children_node.data, 0); + } + else{ + save_bb( children_node.data, depth+1); + } + } + } + // else do nothing, entries are the rects themselves... + }; + + + +public: + RTreeToy(unsigned rmin, unsigned rmax, char /*handlefile*/): + rectangles(0), + color_shape(0, 0, 0, 0.9), color_shape_guide(1, 0, 0, 1), + color_select_area(1, 0, 0, 0.6 ), color_select_area_guide(1, 0, 0, 1 ), //1, 0, 0, 1 + add_new_rect( false ), delete_rect( false ), + rect_chosen(), dummy_draw(), + rects_level( no_of_colors ), + color_rtree_level( no_of_colors, colour(0, 0, 0, 0) ), + drawBB_color(9), drawBB_color_all(true), + mode( INSERT_MODE ), drawBB(true), + out_str( insert_str ), + drawBB_str("ON"), drawBB_color_str("all"), + rtree( rmin, rmax, QUADRATIC_SPIT ), + hit( 0 ), rect_id( 0 ) + { + // only "bright" colors + color_rtree_level[0] = colour(0, 0.80, 1, 1); // cyan + color_rtree_level[1] = colour(0, 0.85, 0, 1); // green + color_rtree_level[2] = colour(0.75, 0, 0.75, 1); // purple + color_rtree_level[3] = colour(0, 0, 1, 1); // blue + color_rtree_level[4] = colour(1, 0.62, 0, 1); // orange + color_rtree_level[5] = colour(1, 0, 0.8, 1); // pink + color_rtree_level[6] = colour(0.47, 0.26, 0.12, 1); + color_rtree_level[7] = colour(1, 0.90, 0, 1); // yellow + } + +}; + + + +int main(int argc, char **argv) { + + char* min_arg = NULL; + char* max_arg = NULL; + + int set_min_max = 0; + + int c; + + while (1) + { + static struct option long_options[] = + { + /* These options set a flag. */ + /* These options don't set a flag. + We distinguish them by their indices. */ + {"min-nodes", required_argument, 0, 'n'}, + {"max-nodes", required_argument, 0, 'm'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long (argc, argv, "n:m:h", + long_options, &option_index); + + /* Detect the end of the options. */ + if (c == -1){ + break; + } + + switch (c) + { + case 'n': + min_arg = optarg; + set_min_max += 1; + break; + + + case 'm': + max_arg = optarg; + set_min_max += 2; + break; + + + case 'h': + std::cerr << "Usage: " << argv[0] << " options\n" << std::endl ; + std::cerr << + " -n --min-nodes=NUMBER minimum number in node.\n" << + " -m --max-nodes=NUMBER maximum number in node.\n" << + " -h --help Print this help.\n" << std::endl; + exit(1); + break; + + + case '?': + /* getopt_long already printed an error message. */ + break; + + default: + abort (); + } + } + + unsigned rmin = 0; + unsigned rmax = 0; + + if( set_min_max == 3 ){ + stringstream s1( min_arg ); + s1 >> rmin; + + stringstream s2( max_arg ); + s2 >> rmax; + if( rmax <= rmin || rmax < 2 || rmin < 1 ){ + std::cerr << "Rtree set to 2, 3" << std::endl ; + rmin = 2; + rmax = 3; + } + } + else{ + std::cerr << "Rtree set to 2, 3 ." << std::endl ; + rmin = 2; + rmax = 3; + } + + + char handlefile = 'T'; + std::cout << "rmin: " << rmin << " rmax:" << rmax << std::endl; + init(argc, argv, new RTreeToy( rmin, rmax, handlefile) ); + + return 0; +} + + + +const char* RTreeToy::menu_items[] = +{ + "Insert / Alter Rectangle", + "Search Rectangle", + "Delete Reactangle", + "Toggle" +}; + +const char RTreeToy::keys[] = +{ + 'I', 'U', 'S', 'D', 'T', + '0', '1', '2', '3', '4', '5', 'P' +}; + + + + + + +/* +intersection test + Rect r1( Point(100, 100), Point(150, 150)), + r2( Point(200, 200), Point(250, 250)), + r3( Point(50, 50), Point(100, 100)); + OptRect a_intersection_b; + a_intersection_b = intersect( r1, r2 ); + std::cout << "r1, r2 " << a_intersection_b.empty() << std::endl; + a_intersection_b = intersect( r1, r3 ); + std::cout << "r1, r3 " << a_intersection_b.empty() << std::endl; +*/ diff --git a/src/toys/sanitize.cpp b/src/toys/sanitize.cpp new file mode 100644 index 0000000..1d9a81f --- /dev/null +++ b/src/toys/sanitize.cpp @@ -0,0 +1,204 @@ +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/svg-path-parser.h> +#include <2geom/utils.h> +#include <cstdlib> +#include <2geom/crossing.h> +#include <2geom/path-intersection.h> +#include <2geom/transforms.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/pathvector.h> + +using namespace Geom; + +struct EndPoint { + public: + Point point, norm; + double time; + EndPoint() : time(0) { } + EndPoint(Point p, Point n, double t) : point(p), norm(n), time(t) { } +}; + +struct Edge { + public: + EndPoint from, to; + int ix; + bool cw; + Edge() { } + Edge(EndPoint f, EndPoint t, int i, bool c) : from(f), to(t), ix(i), cw(c) { } + bool operator==(Edge const &other) { return from.time == other.from.time && to.time == other.to.time; } +}; + +typedef std::vector<Edge> Edges; + +Edges edges(Path const &p, Crossings const &cr, unsigned ix) { + Edges ret = Edges(); + EndPoint prev; + for(unsigned i = 0; i <= cr.size(); i++) { + double t = cr[i == cr.size() ? 0 : i].getTime(ix); + Point pnt = p.pointAt(t); + Point normal = p.pointAt(t+0.01) - pnt; + normal.normalize(); + std::cout << pnt << "\n"; + EndPoint cur(pnt, normal, t); + if(i == 0) { prev = cur; continue; } + ret.push_back(Edge(prev, cur, ix, false)); + ret.push_back(Edge(prev, cur, ix, true)); + prev = cur; + } + return ret; +} + +template<class T> +void append(std::vector<T> &vec, std::vector<T> const &other) { + vec.insert(vec.end(),other.begin(), other.end()); +} + +Edges edges(PathVector const &ps, CrossingSet const &crs) { + Edges ret = Edges(); + for(unsigned i = 0; i < crs.size(); i++) { + Edges temp = edges(ps[i], crs[i], i); + append(ret, temp); + } + return ret; +} + +PathVector edges_to_paths(Edges const &es, PathVector const &ps) { + PathVector ret; + for(const auto & e : es) { + ret.push_back(ps[e.ix].portion(e.from.time, e.to.time)); + } + return ret; +} + +void draw_cell(cairo_t *cr, Edges const &es, PathVector const &ps) { + cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), 0.5); + cairo_set_line_width(cr, uniform() * 10); + PathVector paths = edges_to_paths(es, ps); + Piecewise<D2<SBasis> > pw = paths_to_pw(paths); + double area; + Point centre; + Geom::centroid(pw, centre, area); + cairo_path(cr, paths); //* (Translate(-centre) * Scale(0.2) * Translate(centre*2))); + cairo_stroke(cr); +} + +//Only works for normal +double ang(Point n1, Point n2) { + return (dot(n1, n2)+1) * (cross(n1, n2) < 0 ? -1 : 1); +} + +template<class T> +void remove(std::vector<T> &vec, T const &val) { + for (typename std::vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) { + if(*it == val) { + vec.erase(it); + return; + } + } +} + +std::vector<Edges> cells(cairo_t */*cr*/, PathVector const &ps) { + CrossingSet crs = crossings_among(ps); + Edges es = edges(ps, crs); + std::vector<Edges> ret = std::vector<Edges>(); + + while(!es.empty()) { + std::cout << "hello!\n"; + Edge start = es.front(); + Path p = Path(); + Edge cur = start; + bool rev = false; + Edges cell = Edges(); + do { + std::cout << rev << " " << cur.from.time << ", " << cur.to.time << "\n"; + double a = 0; + Edge was = cur; + EndPoint curpnt = rev ? cur.from : cur.to; + Point norm = rev ? -curpnt.norm : curpnt.norm; + //Point to = curpnt.point + norm *20; + + //std::cout << norm; + for(auto & e : es) { + if(e == was || e.cw != start.cw) continue; + if((!are_near(curpnt.time, e.from.time)) && + are_near(curpnt.point, e.from.point, 0.1)) { + double v = ang(norm, e.from.norm); + //draw_line_seg(cr, curpnt.point, to); + //draw_line_seg(cr, to, es[i].from.point + es[i].from.norm*30); + //std::cout << v << "\n"; + if(start.cw ? v < a : v > a ) { + a = v; + cur = e; + rev = false; + } + } + if((!are_near(curpnt.time, e.to.time)) && + are_near(curpnt.point, e.to.point, 0.1)) { + double v = ang(norm, -e.to.norm); + if(start.cw ? v < a : v > a) { + a = v; + cur = e; + rev = true; + } + } + } + cell.push_back(cur); + remove(es, cur); + if(cur == was) break; + } while(!(cur == start)); + if(are_near(start.to.point, rev ? cur.from.point : cur.to.point)) { + ret.push_back(cell); + } + } + return ret; +} + +int cellWinding(Edges const &/*es*/, PathVector const &/*ps*/) { + return 0; +} + +class Sanitize: public Toy { + PathVector paths; + std::vector<Edges> es; + PointSetHandle angh; + PointSetHandle pathix; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, + std::ostringstream *timer_stream) override + { + int ix = pathix.pts[0][X] / 10; + es = cells(cr, paths); + draw_cell(cr, es[ix], paths); + + cairo_set_source_rgba(cr, 0, 0, 0, 1); + //cairo_path(cr, paths); + //cairo_stroke(cr); + + Point ap = angh.pts[1] - angh.pts[0], bp = angh.pts[2] - angh.pts[0]; + ap.normalize(); bp.normalize(); + *notify << ang(ap, bp); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + Sanitize () {} + void first_time(int argc, char** argv) override { + const char *path_name="sanitize_examples.svgd"; + if(argc > 1) + path_name = argv[1]; + paths = read_svgd(path_name); + + handles.push_back(&angh); handles.push_back(&pathix); + angh.push_back(100, 100); + angh.push_back(80, 100); + angh.push_back(100, 80); + pathix.push_back(30, 200); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sanitize()); + return 0; +} diff --git a/src/toys/sb-math-test.cpp b/src/toys/sb-math-test.cpp new file mode 100644 index 0000000..9964e32 --- /dev/null +++ b/src/toys/sb-math-test.cpp @@ -0,0 +1,164 @@ +/* + * sb-math-test.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. + */ + +#include <2geom/sbasis-math.h> +#include <2geom/piecewise.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> + +#include <2geom/path.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +#define ZERO 1e-3 + +using std::vector; +using namespace Geom; +using namespace std; + +#include <stdio.h> +#include <math.h> + +#include "pwsbhandle.cpp" // FIXME: This looks like it may give problems later, (including a .cpp file) + +//-Plot--------------------------------------------------------------- +static void plot(cairo_t* cr, double (*f)(double), Piecewise<SBasis> const &x, double vscale=1){ + int NbPts=40; + for(int i=0; i<NbPts; i++){ + double t=double(i)/NbPts; + t=x.cuts.front()*(1-t) + x.cuts.back()*t; + draw_handle(cr, Point(150+i*300./NbPts,300-(*f)(x(t))*vscale)); + cairo_stroke(cr); + } +} + +static void plot(cairo_t* cr, Piecewise<SBasis> const &f,double vscale=1){ + D2<Piecewise<SBasis> > plot; + plot[1]=-f; + plot[1]*=vscale; + plot[1]+=300; + + plot[0].cuts.push_back(f.cuts.front()); + plot[0].cuts.push_back(f.cuts.back()); + plot[0].segs.emplace_back(Linear(150,450)); + + cairo_d2_pw_sb(cr, plot); + + for (unsigned i=1; i<f.size(); i++){ + cairo_move_to(cr, Point(150+f.cuts[i]*300,300)); + cairo_line_to(cr, Point(150+f.cuts[i]*300,300-vscale*f.segs[i].at0())); + } +} + +double my_inv(double x){return (1./x);} + +#define SIZE 4 + +class SbCalculusToy: public Toy { + PWSBHandle pwsbh; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + + //Let the user input sbasis coefs. + cairo_move_to(cr, Geom::Point(0,300)); + cairo_line_to(cr, Geom::Point(600,300)); + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1); + cairo_stroke(cr); + + Piecewise<SBasis> f = pwsbh.value(300); + + cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .8); + plot(cr,f,1); + cairo_stroke(cr); + +/* cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8); + plot(cr,abs(f),1); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8); + plot(cr,signSb(f),75); + cairo_stroke(cr);*/ + +// cairo_set_source_rgba (cr, 0.3, 0.3, 0.3,.8); +// plot(cr,maxSb(f, -f+50),1); +// cairo_stroke(cr); + +// cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1); +// plot(cr,sqrt(abs(f),.01,3),10); +// plot(cr,&sqrt,abs(f),10); +// cairo_stroke(cr); + +// cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 1); +// plot(cr,cos(f,.01,3),75); +// plot(cr,&cos,f,75); +// cairo_stroke(cr); + +// cairo_set_source_rgba (cr, 0.9, 0.0, 0.7, 1); +// plot(cr,sin(f,.01,3),75); +// plot(cr,&sin,f,75); +// cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0.9, 0.0, 0.7, 1); + plot(cr,divide(Linear(1),f,.01,3),2); + plot(cr,&my_inv,f,10); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + SbCalculusToy() : pwsbh(4,1){ + for(int i = 0; i < 2*SIZE; i++) + pwsbh.push_back(i*100,150+150+uniform()*300*0); + handles.push_back(&pwsbh); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SbCalculusToy); + return 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:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/sb-of-interval.cpp b/src/toys/sb-of-interval.cpp new file mode 100644 index 0000000..3044620 --- /dev/null +++ b/src/toys/sb-of-interval.cpp @@ -0,0 +1,187 @@ +#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+#include <vector>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+using std::vector;
+using namespace Geom;
+using namespace std;
+
+#define SIZE 4
+
+static void plot(cairo_t* cr, SBasis const &B,double vscale=1,double a=0,double b=1){
+ D2<SBasis> plot;
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=B*(-vscale);
+ plot[1]+=300;
+ cairo_d2_sb(cr, plot);
+// cairo_stroke(cr);
+}
+static void plot(cairo_t* cr, SBasisOf<Interval> const &f, double vscale=1,double /*dx*/=.05, double a=0,double b=1){
+ cairo_save(cr);
+#if 0
+ double t = a;
+ while (t<=b){
+ Interval i = f.valueAt(t);
+ std::cout<<i.min()<<","<<i.max()<<"\n";
+ draw_cross(cr, Geom::Point( 150+t*300, 300-i.min()*vscale ) );
+ draw_cross(cr, Geom::Point( 150+t*300, 300-i.max()*vscale ) );
+ t+=dx;
+ }
+#endif
+ D2<SBasis> plot;
+ Path pth;
+ pth.setStitching(true);
+ SBasis fmin(f.size(), Linear());
+ SBasis fmax(f.size(), Linear());
+ for(unsigned i = 0; i < f.size(); i++) {
+ for(unsigned j = 0; j < 2; j++) {
+ fmin[i][j] = f[i][j].min();
+ fmax[i][j] = f[i][j].max();
+ }
+ }
+ plot[0]=SBasis(Linear(150+a*300,150+b*300));
+ plot[1]=fmin*(-vscale);
+ plot[1]+=300;
+ pth.append(plot);
+ plot[1]=fmax*(-vscale);
+ plot[1]+=300;
+ pth.append(reverse(plot));
+ cairo_path(cr, pth);
+
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.1);
+ cairo_fill(cr);
+ cairo_restore(cr);
+}
+
+
+
+class SbOfInterval: public Toy {
+
+ unsigned size;
+ PointHandle adjuster_a[3*SIZE];
+ PointHandle adjuster_b[3*SIZE];
+
+ void drawSliders(cairo_t *cr, PointHandle adjuster[], double y_min, double y_max){
+ for (unsigned i=0; i < size; i++){
+ cairo_move_to(cr, Geom::Point(adjuster[3*i].pos[X],y_min));
+ cairo_line_to(cr, Geom::Point(adjuster[3*i].pos[X],y_max));
+ }
+ }
+ void drawIntervals(cairo_t *cr, PointHandle adjuster[], double /*x*/, double /*dx*/, double /*y_min*/, double /*y_max*/){
+ for (unsigned i=0; i < size; i++){
+ cairo_move_to(cr, adjuster[3*i+1].pos);
+ cairo_line_to(cr, adjuster[3*i+2].pos);
+ }
+ }
+ void setupSliders(PointHandle adjuster[], double x, double dx, double y_min, double y_max){
+ for (unsigned i=0; i < size; i++){
+ for (unsigned j=0; j < 3; j++){
+ adjuster[3*i+j].pos[X] = x + dx*i;
+ if (adjuster[3*i+j].pos[Y] < y_min) adjuster[3*i+j].pos[Y] = y_min;
+ if (adjuster[3*i+j].pos[Y] > y_max) adjuster[3*i+j].pos[Y] = y_max;
+ }
+ if (adjuster[3*i+1].pos[Y] < adjuster[3*i+2].pos[Y]) adjuster[3*i+1].pos[Y] = adjuster[3*i+2].pos[Y];
+ if (adjuster[3*i ].pos[Y] < adjuster[3*i+2].pos[Y]) adjuster[3*i ].pos[Y] = adjuster[3*i+2].pos[Y];
+ if (adjuster[3*i ].pos[Y] > adjuster[3*i+1].pos[Y]) adjuster[3*i ].pos[Y] = adjuster[3*i+1].pos[Y];
+ }
+ }
+
+ PointSetHandle hand;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ SBasisOf<Interval> f;
+ double min=150, max=450;
+ setupSliders(adjuster_a, 100, 15, min, max);
+ setupSliders(adjuster_b, 500, 15, min, max);
+ drawSliders(cr, adjuster_a, min, max);
+ drawSliders(cr, adjuster_b, min, max);
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+ drawIntervals(cr, adjuster_a, 100, 15, min, max);
+ drawIntervals(cr, adjuster_b, 500, 15, min, max);
+ cairo_set_line_width (cr, 3);
+ cairo_set_source_rgba (cr, 0.8, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ cairo_move_to(cr, Geom::Point(150,300));
+ cairo_line_to(cr, Geom::Point(450,300));
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1);
+ cairo_stroke(cr);
+
+ for (unsigned i=0; i < size; i++){
+ double amin = (max+min)/2 - adjuster_a[3*i+1].pos[Y];
+ double amax = (max+min)/2 - adjuster_a[3*i+2].pos[Y];
+ Interval ai(amin*std::pow(4.,(int)i), amax*std::pow(4.,(int)i));
+ double bmin = (max+min)/2 - adjuster_b[3*i+1].pos[Y];
+ double bmax = (max+min)/2 - adjuster_b[3*i+2].pos[Y];
+ Interval bi(bmin*std::pow(4.,(int)i), bmax*std::pow(4.,(int)i));
+ f.push_back(LinearOf<Interval>(ai,bi));
+ }
+ SBasis f_dble(size, Linear());
+ for (unsigned i=0; i < size; i++){
+ double ai = (max+min)/2 - adjuster_a[3*i].pos[Y];
+ double bi = (max+min)/2 - adjuster_b[3*i].pos[Y];
+ f_dble[i] = Linear(ai*std::pow(4.,(int)i),bi*std::pow(4.,(int)i));
+ }
+
+ plot(cr,f_dble);
+ plot(cr,f);
+ cairo_set_source_rgba (cr, 0., 0., 0.8, 1);
+ cairo_stroke(cr);
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SbOfInterval(){
+ size=SIZE;
+ for(unsigned i=0; i < size; i++){
+ adjuster_a[3*i].pos[X] = 0;
+ adjuster_a[3*i].pos[Y] = 300;
+ adjuster_a[3*i+1].pos[X] = 0;
+ adjuster_a[3*i+1].pos[Y] = 350;
+ adjuster_a[3*i+2].pos[X] = 0;
+ adjuster_a[3*i+2].pos[Y] = 250;
+ adjuster_b[3*i].pos[X] = 0;
+ adjuster_b[3*i].pos[Y] = 300;
+ adjuster_b[3*i+1].pos[X] = 0;
+ adjuster_b[3*i+1].pos[Y] = 350;
+ adjuster_b[3*i+2].pos[X] = 0;
+ adjuster_b[3*i+2].pos[Y] = 250;
+ }
+
+ for(unsigned i = 0; i < size; i++) {
+ handles.push_back(&(adjuster_a[3*i]));
+ handles.push_back(&(adjuster_a[3*i+1]));
+ handles.push_back(&(adjuster_a[3*i+2]));
+ handles.push_back(&(adjuster_b[3*i]));
+ handles.push_back(&(adjuster_b[3*i+1]));
+ handles.push_back(&(adjuster_b[3*i+2]));
+ }
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SbOfInterval);
+ return 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/sb-of-sb.cpp b/src/toys/sb-of-sb.cpp new file mode 100644 index 0000000..d2ee2e6 --- /dev/null +++ b/src/toys/sb-of-sb.cpp @@ -0,0 +1,478 @@ +#include <time.h>
+#include <vector>
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <2geom/orphan-code/sbasis-of.h>
+
+using namespace Geom;
+using namespace std;
+
+SBasis toSBasis(SBasisOf<double> const &f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+SBasisOf<double> toSBasisOfDouble(SBasis const &f){
+ SBasisOf<double> result;
+ for (auto i : f){
+ result.push_back(LinearOf<double>(i[0],i[1]));
+ }
+ return result;
+}
+
+
+#if 0
+template<unsigned dim>
+class LinearDim;
+template<unsigned dim>
+class SBasisDim;
+
+template<unsigned dim>
+class LinearDim : public LinearOf<SBasisDim<dim-1> >{
+public:
+ LinearDim() : LinearOf<SBasisDim<dim-1> >() {};
+ LinearDim(SBasisDim <dim-1> const &a, SBasisDim<dim-1> const &b ) : LinearOf<SBasisDim<dim-1> >(a,b) {};
+ LinearDim(LinearDim <dim-1> const &a, LinearDim<dim-1> const &b ) :
+ LinearOf<SBasisDim<dim-1> >(SBasisDim<dim-1>(a),SBasisDim<dim-1>(b)) {};
+};
+
+template<>
+class LinearDim<1> : public LinearOf<double>{
+public:
+ LinearDim() : LinearOf<double>() {};
+ LinearDim(double const &a, double const &b ) : LinearOf<double>(a,b) {};
+};
+
+
+template<unsigned dim>
+class SBasisDim : public SBasisOf<SBasisDim<dim-1> >{
+public:
+ SBasisDim() : SBasisOf<SBasisDim<dim-1> >() {};
+ SBasisDim(LinearDim<dim> const &lin) :
+ SBasisOf<SBasisDim<dim-1> >(LinearOf<SBasisDim<dim-1> >(lin[0],lin[1])) {};
+};
+
+template<>
+class SBasisDim<1> : public SBasisOf<double>{
+/*
+public:
+ SBasisDim<1>() : SBasisOf<double>() {};
+ SBasisDim(SBasisOf<double> other) : SBasisOf<double>(other) {};
+ SBasisDim(LinearOf<double> other) : SBasisOf<double>(other) {};
+*/
+};
+
+SBasis toSBasis(SBasisDim<1> f){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0],f[i][1]);
+ }
+ return result;
+}
+
+template<unsigned dim_f,unsigned dim_g>
+SBasisDim<dim_g> compose(SBasisDim<dim_f> const &f, std::vector<SBasisDim<dim_g> > const &g ){
+
+ assert( dim_f <= g.size() );
+
+ SBasisDim<dim_g> u0 = g[dim_f-1];
+ SBasisDim<dim_g> u1 = -u0 + 1;
+ SBasisDim<dim_g> s = multiply(u0,u1);
+ SBasisDim<dim_g> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ if ( dim_f>1 ){
+ r = s*r + compose(f[i][0],g)*u0 + compose(f[i][1],g)*u1;
+ }else{
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ }
+ return r;
+}
+
+#endif
+
+template <typename T>
+SBasisOf<T> multi_compose(SBasisOf<double> const &f, SBasisOf<T> const &g ){
+
+ //assert( f.input_dim() <= g.size() );
+
+ SBasisOf<T> u0 = g;
+ SBasisOf<T> u1 = -u0 + LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(1,1)));
+ SBasisOf<T> s = multiply(u0,u1);
+ SBasisOf<T> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + f[i][0]*u0 + f[i][1]*u1;
+ }
+ return r;
+}
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ SBasisOf<double> const &x,
+ SBasisOf<double> const &y){
+ SBasisOf<double> y0 = -y + LinearOf<double>(1,1);
+ SBasisOf<double> s = multiply(y0,y);
+ SBasisOf<double> r;
+
+ for(int i = f.size()-1; i >= 0; i--) {
+ r = s*r + compose(f[i][0],x)*y0 + compose(f[i][1],x)*y;
+ }
+ return r;
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ D2<SBasisOf<double> > const &X){
+ return compose(f, X[0], X[1]);
+}
+
+SBasisOf<double> compose(SBasisOf<SBasisOf<double> > const &f,
+ D2<SBasis> const &X){
+ return compose(f, toSBasisOfDouble(X[0]), toSBasisOfDouble(X[1]));
+}
+
+/*
+static
+SBasis eval_v(SBasisOf<SBasis> const &f, double v){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0].valueAt(v),f[i][1].valueAt(v));
+ }
+ return result;
+}
+static
+SBasis eval_v(SBasisOf<SBasisOf<double> > const &f, double v){
+ SBasis result(f.size(), Linear());
+ for (unsigned i=0; i<f.size(); i++){
+ result[i] = Linear(f[i][0].valueAt(v),f[i][1].valueAt(v));
+ }
+ return result;
+}*/
+static
+SBasisOf<double> eval_dim(SBasisOf<SBasisOf<double> > const &f, double t, unsigned dim){
+ if (dim == 1) return f.valueAt(t);
+ SBasisOf<double> result;
+ for (unsigned i=0; i<f.size(); i++){
+ result.push_back(LinearOf<double>(f[i][0].valueAt(t),f[i][1].valueAt(t)));
+ }
+ return result;
+}
+
+/*
+static
+SBasis eval_v(SBasisDim<2> const &f, double v){
+ SBasis result;
+ for (unsigned i=0; i<f.size(); i++){
+ result.push_back(Linear(f[i][0].valueAt(v),f[i][1].valueAt(v)));
+ }
+ return result;
+}
+*/
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+ // find the point on the x,y plane that projects to P
+ Point unproject(Point P) {
+ return P * from_basis(x, y, O).inverse();
+ }
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasisOf<double> const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + toSBasis(z)*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasisOf<SBasisOf<double> > const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ plot3d(cr, Linear(0,1), Linear(i/(iMax-1.)), eval_dim(f, i/(iMax-1.), 0), frame);
+ plot3d(cr, Linear(i/(iMax-1.)), Linear(0,1), eval_dim(f, i/(iMax-1.), 1), frame);
+ }
+}
+
+SBasisOf<SBasisOf<double> > integral(SBasisOf<SBasisOf<double> > const &f, unsigned var){
+ //variable of f = 1, variable of f's coefficients = 0.
+ if (var == 1) return integral(f);
+ SBasisOf<SBasisOf<double> > result;
+ for(unsigned i = 0; i< f.size(); i++) {
+ result.push_back(LinearOf<SBasisOf<double> >( integral(f[i][0]),integral(f[i][1])));
+ }
+ return result;
+}
+
+Piecewise<SBasis> convole(SBasisOf<double> const &f, Interval dom_f,
+ SBasisOf<double> const &g, Interval dom_g){
+
+ if ( dom_f.extent() < dom_g.extent() ) return convole(g, dom_g, f, dom_f);
+
+ SBasisOf<SBasisOf<double> > u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(0,1),
+ LinearOf<double>(0,1)));
+ v.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(0,0),
+ LinearOf<double>(1,1)));
+ SBasisOf<SBasisOf<double> > v_u = v - u*(dom_f.extent()/dom_g.extent());
+ v_u += SBasisOf<SBasisOf<double> >(SBasisOf<double>(dom_f.min()/dom_g.extent()));
+ SBasisOf<SBasisOf<double> > gg = multi_compose(g,(v - u*(dom_f.extent()/dom_g.extent())));
+ SBasisOf<SBasisOf<double> > ff = SBasisOf<SBasisOf<double> >(f);
+ SBasisOf<SBasisOf<double> > hh = integral(ff*gg,0);
+
+ Piecewise<SBasis> result;
+ result.cuts.push_back(dom_f.min()+dom_g.min());
+ //Note: we know dom_f.extent() >= dom_g.extent()!!
+ double rho = dom_f.extent()/dom_g.extent();
+ SBasisOf<double> a,b,t;
+ SBasis seg;
+ a = SBasisOf<double>(LinearOf<double>(0.,0.));
+ b = SBasisOf<double>(LinearOf<double>(0.,1/rho));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.min()/dom_g.extent(),dom_f.min()/dom_g.extent()+1));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.min() + dom_g.max());
+ if (dom_f.extent() > dom_g.extent()){
+ a = SBasisOf<double>(LinearOf<double>(0.,1-1/rho));
+ b = SBasisOf<double>(LinearOf<double>(1/rho,1.));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.min()/dom_g.extent()+1, dom_f.max()/dom_g.extent() ));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.min());
+ }
+ a = SBasisOf<double>(LinearOf<double>(1.-1/rho,1.));
+ b = SBasisOf<double>(LinearOf<double>(1.,1.));
+ t = SBasisOf<double>(LinearOf<double>(dom_f.max()/dom_g.extent(), dom_f.max()/dom_g.extent()+1 ));
+ seg = toSBasis(compose(hh,b,t)-compose(hh,a,t));
+ result.push(seg,dom_f.max() + dom_g.max());
+ return result;
+}
+
+template <typename T>
+SBasisOf<T> subderivative(SBasisOf<T> const& f) {
+ SBasisOf<T> res;
+ for(unsigned i = 0; i < f.size(); i++) {
+ res.push_back(LinearOf<T>(derivative(f[i][0]), derivative(f[i][1])));
+ }
+ return res;
+}
+
+OptInterval bounds_fast(SBasisOf<double> const &f) {
+ return bounds_fast(toSBasis(f));
+}
+
+/**
+ * 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>
+sbofsb_cubic_solve(SBasisOf<SBasisOf<double> > 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]));
+
+ SBasisOf<SBasisOf<double> > f_u = derivative(f);
+ SBasisOf<SBasisOf<double> > f_v = subderivative(f);
+ SBasisOf<SBasisOf<double> > f_uu = derivative(f_u);
+ SBasisOf<SBasisOf<double> > f_uv = derivative(f_v);
+ SBasisOf<SBasisOf<double> > f_vv = subderivative(f_v);
+
+ Geom::Point dfA(f_u.valueAt(A[X]).valueAt(A[Y]),f_v.valueAt(A[X]).valueAt(A[Y]));
+ Geom::Point dfB(f_u.valueAt(B[X]).valueAt(B[Y]),f_v.valueAt(B[X]).valueAt(B[Y]));
+
+ Geom::Point V0 = rot90(dfA);
+ Geom::Point V1 = rot90(dfB);
+
+ double D2fVV0 = f_uu.valueAt(A[X]).valueAt(A[Y])*V0[X]*V0[X]+
+ 2*f_uv.valueAt(A[X]).valueAt(A[Y])*V0[X]*V0[Y]+
+ f_vv.valueAt(A[X]).valueAt(A[Y])*V0[Y]*V0[Y];
+ double D2fVV1 = f_uu.valueAt(B[X]).valueAt(B[Y])*V1[X]*V1[X]+
+ 2*f_uv.valueAt(B[X]).valueAt(B[Y])*V1[X]*V1[Y]+
+ f_vv.valueAt(B[X]).valueAt(B[Y])*V1[Y]*V1[Y];
+
+ std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(A,B,V0,V1,D2fVV0,D2fVV1);
+ if (candidates.size()==0) {
+ return D2<SBasis>(SBasis(A[X],B[X]),SBasis(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];
+}
+
+class SBasis0fSBasisToy: public Toy {
+ PointSetHandle hand;
+ PointSetHandle cut_hand;
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ //double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ //double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+
+
+ SBasisOf<SBasisOf<double> > f,u,v;
+ u.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(-1,-1),LinearOf<double>(1,1)));
+ v.push_back(LinearOf<SBasisOf<double> >(LinearOf<double>(-1,1),LinearOf<double>(-1,1)));
+#if 1
+ f = u*u + v*v - LinearOf<SBasisOf<double> >(LinearOf<double>(1,1),LinearOf<double>(1,1));
+ //*notify << "input dim = " << f.input_dim() <<"\n";
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ LineSegment ls(frame.unproject(cut_hand.pts[0]),
+ frame.unproject(cut_hand.pts[1]));
+ SBasis cutting = toSBasis(compose(f, ls.toSBasis()));
+ //cairo_sb(cr, cutting);
+ //cairo_stroke(cr);
+ plot3d(cr, ls.toSBasis()[0], ls.toSBasis()[1], SBasis(0.0), frame);
+ vector<double> rts = roots(cutting);
+ if(rts.size() >= 2) {
+ Geom::Point A = ls.pointAt(rts[0]);
+ Geom::Point B = ls.pointAt(rts[1]);
+
+ //Geom::Point A(1,0.5);
+ //Geom::Point B(0.5,1);
+ D2<SBasis> zeroset = sbofsb_cubic_solve(f,A,B);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+ }
+#else
+
+ SBasisOf<SBasisOf<double> > g = u - v ;
+ g += LinearOf<SBasisOf<double> >(SBasisOf<double>(LinearOf<double>(.5,.5)));
+ SBasisOf<double> h;
+ h.push_back(LinearOf<double>(0,0));
+ h.push_back(LinearOf<double>(0,0));
+ h.push_back(LinearOf<double>(1,1));
+
+ f = multi_compose(h,g);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .75, 0.25, 0.25, 1.);
+ cairo_stroke(cr);
+/*
+ SBasisDim<1> g = SBasisOf<double>(LinearOf<double>(0,1));
+ g.push_back(LinearOf<double>(-1,-1));
+ std::vector<SBasisDim<2> > vars;
+ vars.push_back(ff);
+ plot3d(cr,compose(g,vars),frame);
+ cairo_set_source_rgba (cr, .5, 0.9, 0.5, 1.);
+ cairo_stroke(cr);
+*/
+#endif
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SBasis0fSBasisToy(){
+ handles.push_back(&hand);
+ handles.push_back(&cut_hand);
+ cut_hand.push_back(100,100);
+ cut_hand.push_back(500,500);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SBasis0fSBasisToy);
+ return 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/sb-to-bez.cpp b/src/toys/sb-to-bez.cpp new file mode 100644 index 0000000..337180f --- /dev/null +++ b/src/toys/sb-to-bez.cpp @@ -0,0 +1,404 @@ +/* + * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier. + * + * 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, 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. + * + */ + +// mainly experimental atm... +// do not expect to find anything understandable here atm. + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/basic-intersection.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZERO 1e-7 + +using std::vector; +using namespace Geom; +using namespace std; + +#include <stdio.h> +#include <gsl/gsl_poly.h> + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p, double hscale=1., double vscale=1.) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(150+p.cuts[i]*hscale, 150+p.cuts[i+1]*hscale); + B[1] = Linear(450) - p[i]*vscale; + cairo_d2_sb(cr, B); + } +} + +//=================================================================================== + +D2<SBasis> +naive_sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){ + + Piecewise<D2<SBasis> > dM = derivative(M); + Point M0 = M(t0); + Point dM0 = dM(t0)*(t1-t0); + Point M1 = M(t1); + Point dM1 = dM(t1)*(t1-t0); + D2<SBasis> result; + for (unsigned dim=0; dim<2; dim++){ + SBasis r(2, Linear()); + r[0] = Linear(M0[dim],M1[dim]); + r[1] = Linear(M0[dim]-M1[dim]+dM0[dim],-(M0[dim]-M1[dim]+dM1[dim])); + result[dim] = r; + } + return result; +} + +D2<SBasis> +sb_seg_to_bez(Piecewise<D2<SBasis> > const &M,double t0,double t1){ + Point M0,dM0,d2M0,M1,dM1,d2M1,A0,V0,A1,V1; + Piecewise<D2<SBasis> > dM,d2M; + dM=derivative(M); + d2M=derivative(dM); + M0 =M(t0); + M1 =M(t1); + dM0 =dM(t0); + dM1 =dM(t1); + d2M0=d2M(t0); + d2M1=d2M(t1); + A0=M(t0); + A1=M(t1); + + std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(M0,M1,dM0,dM1,d2M0,d2M1); + if (candidates.empty()){ + return D2<SBasis>(SBasis(M0[X],M1[X]),SBasis(M0[Y],M1[Y])) ; + } + double maxlength = -1; + unsigned best = 0; + for (unsigned i=0; i<candidates.size(); i++){ + double l = length(candidates[i]); + if ( l < maxlength || maxlength < 0 ){ + maxlength = l; + best = i; + } + } + return candidates[best]; +} +#include <2geom/sbasis-to-bezier.h> + +int goal_function_type = 0; + +double goal_function(Piecewise<D2<SBasis> >const &A, + Piecewise<D2<SBasis> >const&B) { + if(goal_function_type) { + OptInterval bnds = bounds_fast(dot(derivative(A), rot90(derivative(B)))); + //double h_dist = bnds.dimensions().length(); +//0 is in the rect!, TODO:gain factor ~2 for free. +// njh: not really, the benefit is actually rather small. + double h_dist = 0; + if(bnds) + h_dist = bnds->extent(); + return h_dist ; + } else { + Rect bnds = *bounds_fast(A - B); + return max(bnds.min().length(), bnds.max().length()); + } +} + +int recursive_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) { + if (t0>=t1) return 0;//TODO: fix me... + if (t0+0.001>=t1) return 0;//TODO: fix me... + + //TODO: don't re-compute derivative(f) at each try!! + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1); + + if(k_bez[0].size() > 1 and k_bez[1].size() > 1) { + Piecewise<SBasis> s = arcLengthSb(k_bez); + s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1(); + s += t0; + double h_dist = goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez)); + if(h_dist < precision) { + cairo_save(cr); + cairo_set_line_width (cr, 0.93); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + draw_handle(cr, k_bez.at0()); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + cairo_restore(cr); + return 1; + } + } + //TODO: find a better place where to cut (at the worst fit?). + return recursive_curvature_fitter(cr, f, t0, (t0+t1)/2, precision) + + recursive_curvature_fitter(cr, f, (t0+t1)/2, t1, precision); +} + +double single_curvature_fitter(Piecewise<D2<SBasis> > const &f, double t0, double t1) { + if (t0>=t1) return 0;//TODO: fix me... + if (t0+0.001>=t1) return 0;//TODO: fix me... + + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,t1); + + if(k_bez[0].size() > 1 and k_bez[1].size() > 1) { + Piecewise<SBasis> s = arcLengthSb(k_bez); + s *= (t1-t0)/arcLengthSb(k_bez).segs.back().at1(); + s += t0; + return goal_function(compose(f,s), Piecewise<D2<SBasis> >(k_bez)); + } + return 1e100; +} + +struct quadratic_params +{ + Piecewise<D2<SBasis> > const *f; + double t0, precision; +}; + + +double quadratic (double x, void *params) { + struct quadratic_params *p + = (struct quadratic_params *) params; + + return single_curvature_fitter(*p->f, p->t0, x) - p->precision; +} + +#include <stdio.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_math.h> +#include <gsl/gsl_roots.h> + + +int sequential_curvature_fitter(cairo_t* cr, Piecewise<D2<SBasis> > const &f, double t0, double t1, double precision) { + if(t0 >= t1) return 0; + + double r = t1; + if(single_curvature_fitter(f, t0, t1) > precision) { + int status; + int iter = 0, max_iter = 100; + const gsl_root_fsolver_type *T; + gsl_root_fsolver *s; + gsl_function F; + struct quadratic_params params = {&f, t0, precision}; + + F.function = &quadratic; + F.params = ¶ms; + + T = gsl_root_fsolver_brent; + s = gsl_root_fsolver_alloc (T); + gsl_root_fsolver_set (s, &F, t0, t1); + + do + { + iter++; + status = gsl_root_fsolver_iterate (s); + r = gsl_root_fsolver_root (s); + double x_lo = gsl_root_fsolver_x_lower (s); + double x_hi = gsl_root_fsolver_x_upper (s); + status = gsl_root_test_interval (x_lo, x_hi, + 0, 0.001); + + + } + while (status == GSL_CONTINUE && iter < max_iter); + + double x_lo = gsl_root_fsolver_x_lower (s); + double x_hi = gsl_root_fsolver_x_upper (s); + printf ("%5d [%.7f, %.7f] %.7f %.7f\n", + iter, x_lo, x_hi, + r, + x_hi - x_lo); + gsl_root_fsolver_free (s); + } + D2<SBasis> k_bez = sb_seg_to_bez(f,t0,r); + + cairo_save(cr); + cairo_set_line_width (cr, 0.93); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + draw_handle(cr, k_bez.at0()); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + cairo_restore(cr); + + if(r < t1) + return sequential_curvature_fitter(cr, f, r, t1, precision) + 1; + return 1; +} + + +class SbToBezierTester: public Toy { + //std::vector<Slider> sliders; + std::vector<PointSetHandle*> path_psh; + PointHandle adjuster, adjuster2; + std::vector<Toggle> toggles; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_save(cr); + for(unsigned i = 1; i < path_psh.size(); i++) + path_psh[i-1]->pts.back() = path_psh[i]->pts[0]; + Piecewise<D2<SBasis> > f_as_pw(path_psh[0]->asBezier()); + for(unsigned i = 1; i < path_psh.size(); i++) { + f_as_pw.push_seg(path_psh[i]->asBezier()); + } + //f=handles_to_sbasis(handles.begin(), SIZE-1); + adjuster.pos[1]=450; + adjuster.pos[0]=std::max(adjuster.pos[0],150.); + adjuster.pos[0]=std::min(adjuster.pos[0],450.); + double t0=0;//(adjuster.pos[0]-150)/300; + double t1=(adjuster.pos[0]-150)/300; + //if (t0>t1) {double temp=t0;t0=t1;t1=temp;} + + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 0.5); + cairo_pw_d2_sb(cr, f_as_pw); + cairo_stroke(cr); + if (t0==t1) return;//TODO: fix me... +#if 0 + if(0) { + Piecewise<D2<SBasis> > g = f_as_pw; + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0., 0., 0.9, .7); + double error=0; + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.9, 0., 0., .7); + D2<SBasis> naive_bez = naive_sb_seg_to_bez(g,0,t1); + cairo_d2_sb(cr, naive_bez); + cairo_stroke(cr); + + adjuster2.pos[0]=150; + adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.); + + double scale0=(450-adjuster2.pos[1])/150; + + cairo_set_line_width (cr, 1); + cairo_set_source_rgba (cr, 0.7, 0., 0.7, .7); + D2<SBasis> k_bez = sb_seg_to_bez(g,t0,t1); + cairo_d2_sb(cr, k_bez); + cairo_stroke(cr); + double h_a_t = 0, h_b_t = 0; + + double h_dist = hausdorfl( k_bez, f, 1e-6, &h_a_t, &h_b_t); + { + Point At = k_bez(h_a_t); + Point Bu = f(h_b_t); + cairo_move_to(cr, At); + cairo_line_to(cr, Bu); + draw_handle(cr, At); + draw_handle(cr, Bu); + cairo_save(cr); + cairo_set_line_width (cr, 0.3); + cairo_set_source_rgba (cr, 0.7, 0.0, 0.0, 1); + cairo_stroke(cr); + cairo_restore(cr); + } + *notify << "Move handle 6 to set the segment to be approximated by cubic bezier.\n"; + *notify << " -red: bezier approx derived from parametrization.\n"; + *notify << " -blue: bezier approx derived from curvature.\n"; + *notify << " max distance (to original): "<<h_dist<<"\n"; + } +#endif + + + f_as_pw = arc_length_parametrization(f_as_pw); + adjuster2.pos[0]=150; + adjuster2.pos[1]=std::min(std::max(adjuster2.pos[1],150.),450.); + cairo_move_to(cr, 150, 150); + cairo_line_to(cr, 150, 450); + cairo_stroke(cr); + ostringstream val_s; + double scale0=(450-adjuster2.pos[1])/300; + double curve_precision = pow(10, scale0*5-2); + val_s << curve_precision; + draw_text(cr, adjuster2.pos, val_s.str().c_str()); + + int segs = 0; + goal_function_type = toggles[1].on; + if(toggles[0].on) + segs = sequential_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(), curve_precision); + else { + segs = recursive_curvature_fitter(cr, f_as_pw, 0, f_as_pw.cuts.back(),curve_precision); + } + Geom::PathVector vpt = path_from_piecewise(f_as_pw, curve_precision, true); + unsigned default_number_curves = 0; + for(const auto & i : vpt) { + default_number_curves += i.size(); + } + + *notify << " segments from default algorithm: "<< default_number_curves <<"\n"; + *notify << " total segments: "<< segs <<"\n"; + cairo_restore(cr); + Point p(25, height - 100), d(50,25); + toggles[0].bounds = Rect(p, p + d); + p+= Point(75, 0); + toggles[1].bounds = Rect(p, p + d); + draw_toggles(cr, toggles); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + void key_hit(GdkEventKey *e) override { + if(e->keyval == 's') toggles[0].toggle(); + redraw(); + } + void mouse_pressed(GdkEventButton* e) override { + toggle_events(toggles, e); + Toy::mouse_pressed(e); + } + SbToBezierTester() { + //if(handles.empty()) { + for(int j = 0; j < 3; j++) { + path_psh.push_back(new PointSetHandle()); + for(unsigned i = 0; i < 6; i++) + path_psh.back()->push_back(150+300*uniform(),150+300*uniform()); + handles.push_back(path_psh.back()); + } + adjuster.pos = Geom::Point(150+300*uniform(),150+300*uniform()); + handles.push_back(&adjuster); + adjuster2.pos = Geom::Point(150,300); + handles.push_back(&adjuster2); + toggles.emplace_back("Seq", true); + toggles.emplace_back("Linfty", true); + //} + //sliders.push_back(Slider(0.0, 1.0, 0.0, 0.0, "t")); + //handles.push_back(&(sliders[0])); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SbToBezierTester); + return 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:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/sb-zeros.cpp b/src/toys/sb-zeros.cpp new file mode 100644 index 0000000..ebe3aea --- /dev/null +++ b/src/toys/sb-zeros.cpp @@ -0,0 +1,63 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +#define SIZE 4 + +class SBZeros: public Toy { + PointSetHandle pB1, pB2; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + D2<SBasis> B1 = pB1.asBezier(); + D2<SBasis> B2 = pB2.asBezier(); + Piecewise<D2<SBasis> >B; + B.concat(Piecewise<D2<SBasis> >(B1)); + B.concat(Piecewise<D2<SBasis> >(B2)); + std::vector<Point> e; + std::vector<Piecewise<D2<SBasis> > > s; + s.push_back(derivative(B)); + for(int j = 0; j < 5; j++) s.push_back(derivative(s.back())); + for(int j = 0; j <= 5; j++) { + for(unsigned d = 0; d < 2; d++) { + std::vector<double> r = roots(make_cuts_independent(s[j])[d]); + for(double k : r) e.push_back(B.valueAt(k)); + } + } + for(auto & i : e) draw_cross(cr, i); + + cairo_set_line_width (cr, .5); + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + cairo_pw_d2_sb(cr, B); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + SBZeros () { + for(unsigned i = 0; i < SIZE; i++) + pB1.push_back(150+uniform()*300,150+uniform()*300); + for(unsigned i = 0; i < SIZE; i++) + pB2.push_back(150+uniform()*300,150+uniform()*300); + handles.push_back(&pB1); + handles.push_back(&pB2); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new SBZeros()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sb1d.cpp b/src/toys/sb1d.cpp new file mode 100644 index 0000000..dabbebb --- /dev/null +++ b/src/toys/sb1d.cpp @@ -0,0 +1,125 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/choose.h> +#include <2geom/convex-hull.h> + +#include <2geom/path.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> +using std::vector; +using namespace Geom; + +extern unsigned total_steps, total_subs; + +double& handle_to_sb(unsigned i, unsigned n, SBasis &sb) { + assert(i < n); + assert(n <= sb.size()*2); + unsigned k = i; + if(k >= n/2) { + k = n - k - 1; + return sb[k][1]; + } else + return sb[k][0]; +} + +double handle_to_sb_t(unsigned i, unsigned n) { + double k = i; + if(i >= n/2) + k = n - k - 1; + double t = k/(2*k+1); + if(i >= n/2) + return 1 - t; + else + return t; +} + +class Sb1d: public Toy { +public: + PointSetHandle hand; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0.5, 0, 1); + cairo_set_line_width (cr, 1); + + if(!save) { + for(unsigned i = 0; i < hand.pts.size(); i++) { + hand.pts[i][0] = width*handle_to_sb_t(i, hand.pts.size())/2 + width/4; + if(i) + cairo_line_to(cr, hand.pts[i]); + else + cairo_move_to(cr, hand.pts[i]); + } + } + + D2<SBasis> B; + B[0] = Linear(width/4, 3*width/4); + B[1].resize(hand.pts.size()/2); + for(auto & i : B) { + i = Linear(0); + } + for(unsigned i = 0; i < hand.pts.size(); i++) { + handle_to_sb(i, hand.pts.size(), B[1]) = 3*width/4 - hand.pts[i][1]; + } + for(unsigned i = 1; i < B[1].size(); i++) { + B[1][i] = B[1][i]*choose<double>(2*i+1, i); + } + + Interval bs = *bounds_fast(B[1]); + double lo, hi; + lo = 3*width/4 - bs.min(); + hi = 3*width/4 - bs.max(); + cairo_move_to(cr, B[0](0), lo); + cairo_line_to(cr, B[0](1), lo); + cairo_move_to(cr, B[0](0), hi); + cairo_line_to(cr, B[0](1), hi); + cairo_stroke(cr); + *notify << "sb bounds = "<<lo << ", " <<hi<<std::endl; + //B[1] = SBasis(Linear(3*width/4)) - B[1]; + *notify << B[0] << ", "; + *notify << B[1]; + Geom::Path pb; + B[1] = SBasis(Linear(3*width/4)) - B[1]; + pb.append(B); + pb.close(false); + cairo_path(cr, pb); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + + Geom::ConvexHull ch(hand.pts); + + cairo_move_to(cr, ch.back()); + for(auto i : ch) { + cairo_line_to(cr, i); + } + Toy::draw(cr, notify, width, height, save,timer_stream); + } +public: +Sb1d () { + hand.pts.emplace_back(0,450); + for(unsigned i = 0; i < 4; i++) + hand.pts.emplace_back(uniform()*400, uniform()*400); + hand.pts.emplace_back(0,450); + handles.push_back(&hand); +} +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sb1d()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sb2d-solver.cpp b/src/toys/sb2d-solver.cpp new file mode 100644 index 0000000..a60f8cd --- /dev/null +++ b/src/toys/sb2d-solver.cpp @@ -0,0 +1,282 @@ +#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-2d.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <gsl/gsl_poly.h>
+
+using std::vector;
+using namespace Geom;
+
+//see a sb2d as an sb of u with coef in sbasis of v.
+void
+u_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.vs, Linear());
+ b = SBasis(f.vs, Linear());
+ for (unsigned v=0; v<f.vs; v++){
+ a[v] = Linear(f.index(deg,v)[0], f.index(deg,v)[2]);
+ b[v] = Linear(f.index(deg,v)[1], f.index(deg,v)[3]);
+ }
+}
+void
+v_coef(SBasis2d f, unsigned deg, SBasis &a, SBasis &b) {
+ a = SBasis(f.us, Linear());
+ b = SBasis(f.us, Linear());
+ for (unsigned u=0; u<f.us; u++){
+ a[u] = Linear(f.index(deg,u)[0], f.index(deg,u)[1]);
+ b[u] = Linear(f.index(deg,u)[2], f.index(deg,u)[3]);
+ }
+}
+
+
+
+//TODO: implement sb2d algebra!!
+SBasis2d y_x2(){
+ SBasis2d result(Linear2d(0,-1,1,0));
+ result.push_back(Linear2d(1,1,1,1));
+ result.us = 2;
+ result.vs = 1;
+ return result;
+}
+
+SBasis2d x2_plus_y2_1(){
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+SBasis2d conic_sb2d(vector<double> /*coeff*/) {
+/*TODO: implement sb2d algebra!!
+ SBasis2d one(Linear2d(1,1,1,1));
+ SBasis2d u(Linear2d(0,1,0,1));
+ SBasis2d v(Linear2d(0,0,1,1));
+ return(u*u+v*v-one);
+*/
+ SBasis2d result(Linear2d(-1,0,0,1));//x+y-1
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(-1,-1,-1,-1));
+ result.push_back(Linear2d(0,0,0,0));
+ result.us = 2;
+ result.vs = 2;
+ return result;
+}
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr,
+ Piecewise<SBasis> const &x,
+ Piecewise<SBasis> const &y,
+ Piecewise<SBasis> const &z, Frame frame){
+
+ Piecewise<SBasis> xx = partition(x,y.cuts);
+ Piecewise<SBasis> xxx = partition(xx,z.cuts);
+ Piecewise<SBasis> yyy = partition(y,xxx.cuts);
+ Piecewise<SBasis> zzz = partition(z,xxx.cuts);
+
+ for (unsigned i=0; i<xxx.size(); i++){
+ plot3d(cr, xxx[i], yyy[i], zzz[i], frame);
+ }
+}
+
+void
+plot3d(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ D2<SBasis> seg(SBasis(0.,1.), SBasis(i*1./NbRays,i*1./NbRays));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ for (int i=0; i<NbRays; i++){
+ D2<SBasis> seg(SBasis(i*1./NbRays, i*1./NbRays), SBasis(0.,1.));
+ SBasis f_on_seg = compose(f,seg);
+ plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+}
+
+void
+plot3d_top(cairo_t *cr, SBasis2d const &f, Frame frame, int NbRays=5){
+ for (int i=0; i<=NbRays; i++){
+ for(int j=0; j<2; j++){
+ D2<SBasis> seg;
+ if (j==0){
+ seg = D2<SBasis>(SBasis(0.,1.), SBasis(i*1./NbRays,i*1./NbRays));
+ }else{
+ seg = D2<SBasis>(SBasis(i*1./NbRays,i*1./NbRays), SBasis(0.,1.));
+ }
+ SBasis f_on_seg = compose(f,seg);
+ std::vector<double> rts = roots(f_on_seg);
+ if (rts.empty()||rts.back()<1) rts.push_back(1.);
+ double t1,t0 = 0;
+ for (unsigned i=(rts.front()<=0?1:0); i<rts.size(); i++){
+ t1 = rts[i];
+ if (f_on_seg((t0+t1)/2)>0)
+ plot3d(cr,seg[X](Linear(t0,t1)),seg[Y](Linear(t0,t1)),f_on_seg(Linear(t0,t1)),frame);
+ t0=t1;
+ }
+ //plot3d(cr,seg[X],seg[Y],f_on_seg,frame);
+ }
+ }
+}
+
+class Sb2dSolverToy: public Toy {
+public:
+ PointSetHandle hand;
+ Sb2dSolverToy() {
+ handles.push_back(&hand);
+ }
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+#if 0
+ SBasis2d f = y_x2();
+ D2<SBasis> true_solution(Linear(0,1),Linear(0,1));
+ true_solution[Y].push_back(Linear(-1,-1));
+ SBasis zero = SBasis(Linear(0.));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+
+#elif 0
+ SBasis2d f = x2_plus_y2_1();
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.14/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.14/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+#else
+ SBasis2d f = conic_sb2d(vector<double>());
+ D2<Piecewise<SBasis> > true_solution;
+ true_solution[X] = cos(SBasis(Linear(0,3.14/2)));
+ true_solution[Y] = sin(SBasis(Linear(0,3.14/2)));
+ Piecewise<SBasis> zero = Piecewise<SBasis>(SBasis(Linear(0.)));
+ Geom::Point A = true_solution(tA);
+ Geom::Point B = true_solution(tB);
+#endif
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ plot3d_top(cr,f,frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+ plot3d(cr,f,frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, .5, 0.5, 0.5, 1.);
+ cairo_stroke(cr);
+
+ plot3d(cr, true_solution[X], true_solution[Y], zero, frame);
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+ double error;
+ for(int degree = 1; degree < 4; degree++) {
+ //D2<SBasis> zeroset = sb2dsolve(f,A,B,degree);
+ D2<SBasis> zeroset = sb2d_cubic_solve(f,A,B);
+ plot3d(cr, zeroset[X], zeroset[Y], SBasis(Linear(0.)),frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0.9, 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasis comp = compose(f,zeroset);
+ plot3d(cr, zeroset[X], zeroset[Y], comp, frame);
+ cairo_set_source_rgba (cr, 0.7, 0., 0.7, 1.);
+ cairo_stroke(cr);
+ //Fix Me: bounds_exact does not work here?!?!
+ Interval bounds = *bounds_fast(comp);
+ error = (bounds.max()>-bounds.min() ? bounds.max() : -bounds.min() );
+ }
+ *notify << "Gray: f-graph and true solution,\n";
+ *notify << "Red: solver solution,\n";
+ *notify << "Purple: value of f over solver solution.\n";
+ *notify << " error: "<< error <<".\n";
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new Sb2dSolverToy());
+ return 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/toys/sb2d.cpp b/src/toys/sb2d.cpp new file mode 100644 index 0000000..b449d01 --- /dev/null +++ b/src/toys/sb2d.cpp @@ -0,0 +1,83 @@ +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +unsigned total_pieces_sub; +unsigned total_pieces_inc; + +class Sb2d: public Toy { +public: + PointSetHandle hand; + Sb2d() { + handles.push_back(&hand); + } + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + SBasis2d sb2; + sb2.us = 2; + sb2.vs = 2; + const int depth = sb2.us*sb2.vs; + const int surface_handles = 4*depth; + sb2.resize(depth, Linear2d(0)); + vector<Geom::Point> display_handles(surface_handles); + Geom::Point dir(1,-2); + if(hand.pts.empty()) { + for(unsigned vi = 0; vi < sb2.vs; vi++) + for(unsigned ui = 0; ui < sb2.us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) + hand.pts.emplace_back((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + + hand.pts.push_back(Geom::Point(3*width/4., width/4.) + 30*dir); + } + dir = (hand.pts[surface_handles] - Geom::Point(3*width/4., width/4.)) / 30; + if(!save) { + cairo_move_to(cr, 3*width/4., width/4.); + cairo_line_to(cr, hand.pts[surface_handles]); + } + for(unsigned vi = 0; vi < sb2.vs; vi++) + for(unsigned ui = 0; ui < sb2.us; ui++) + for(unsigned iv = 0; iv < 2; iv++) + for(unsigned iu = 0; iu < 2; iu++) { + unsigned corner = iu + 2*iv; + unsigned i = ui + vi*sb2.us; + Geom::Point base((2*(iu+ui)/(2.*ui+1)+1)*width/4., + (2*(iv+vi)/(2.*vi+1)+1)*width/4.); + double dl = dot((hand.pts[corner+4*i] - base), dir)/dot(dir,dir); + display_handles[corner+4*i] = dl*dir + base; + sb2[i][corner] = dl*10/(width/2)*pow(4.,(double)ui+vi); + } + cairo_sb2d(cr, sb2, dir*0.1, width); + + *notify << "bo = " << sb2.index(0,0); + + cairo_set_source_rgba (cr, 0., 0.125, 0, 1); + cairo_stroke(cr); + if(!save) + for(auto & display_handle : display_handles) + draw_circ(cr, display_handle); + Toy::draw(cr, notify, width, height, save,timer_stream); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sb2d()); + return 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/toys/sbasis-fitting.cpp b/src/toys/sbasis-fitting.cpp new file mode 100644 index 0000000..9963790 --- /dev/null +++ b/src/toys/sbasis-fitting.cpp @@ -0,0 +1,214 @@ +/* + * SBasis Fitting Example + * + * 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/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> + +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + + +using namespace Geom; + + +class SBasisFitting : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + sliders[0].geometry(Point(50, 30), 100); + + size_t value = (size_t)(sliders[0].value()); + + if (degree != value) + { + degree = value; + total_handles = degree + 1; + order = total_handles/2 - 1; + dstep = interval_length/degree; + step = 1.0/degree; + + double x = sx; + psh.pts.clear(); + for (size_t i = 0; i < total_handles; ++i) + { + psh.push_back(x, 300*uniform()+50); + x += dstep; + } + + handles[0] = &psh; + + if (fmsb != NULL) delete fmsb; + fmsb = new NL::LFMSBasis(order); + assert(fmsb != NULL); + if (lsf_sb != NULL) delete lsf_sb; + lsf_sb = new NL::least_squeares_fitter<NL::LFMSBasis>(*fmsb, 25); + assert(lsf_sb != NULL); + + double t = 0; + for (size_t i = 0; i < total_handles; ++i) + { + lsf_sb->append(t); + t += step; + } + lsf_sb->update(); + + curr_ys.clear(); + curr_ys.resize(total_handles); + for (size_t i = 0; i < total_handles; ++i) + { + curr_ys[i] = psh.pts[i][Y]; + } + prev_ys = curr_ys; + + fmsb->instance(sb_curve, lsf_sb->result(curr_ys)); + } + else + { + double x = sx; + for (size_t i = 0; i < total_handles; ++i) + { + psh.pts[i][X] = x; + curr_ys[i] = psh.pts[i][Y]; + x += dstep; + } + fmsb->instance(sb_curve, lsf_sb->result(prev_ys, curr_ys)); + prev_ys = curr_ys; + } + + + D2<SBasis> curve; + curve[X] = SBasis(Linear(sx,sx) + interval_length * Linear(0, 1)); + curve[Y] = sb_curve; + + cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); + cairo_set_line_width (cr, 0.3); + cairo_d2_sb(cr, curve); + cairo_stroke(cr); + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + SBasisFitting() + : degree(3), + total_handles(degree+1), + order(total_handles/2 - 1), + interval_length(400), + dstep(interval_length/degree), + step(1.0/degree) + { + sx = 50; + double x = sx; + for (size_t i = 0; i < total_handles; ++i) + { + psh.push_back(x, 300*uniform()+50); + x += dstep; + } + + handles.push_back(&psh); + + fmsb = new NL::LFMSBasis(order); + assert(fmsb != NULL); + lsf_sb = new NL::least_squeares_fitter<NL::LFMSBasis>(*fmsb, 25); + assert(lsf_sb != NULL); + + double t = 0; + for (size_t i = 0; i < total_handles; ++i) + { + lsf_sb->append(t); + t += step; + } + lsf_sb->update(); + + curr_ys.clear(); + curr_ys.resize(total_handles); + for (size_t i = 0; i < total_handles; ++i) + { + curr_ys[i] = psh.pts[i][Y]; + } + prev_ys = curr_ys; + + fmsb->instance(sb_curve, lsf_sb->result(curr_ys)); + + + sliders.emplace_back(1, 11, 2, 3, "degree"); + handles.push_back(&(sliders[0])); + } + + ~SBasisFitting() override + { + if (fmsb != NULL) delete fmsb; + if (lsf_sb != NULL) delete lsf_sb; + } + + private: + size_t degree; + size_t total_handles; + size_t order; + double interval_length; + double dstep; + double step; + double sx; + std::vector<double> curr_ys, prev_ys; + SBasis sb_curve; + NL::LFMSBasis* fmsb; + NL::least_squeares_fitter<NL::LFMSBasis>* lsf_sb; + PointSetHandle psh; + std::vector<Slider> sliders; +}; + + + + +int main(int argc, char **argv) +{ + init( argc, argv, new SBasisFitting(), 600, 600 ); + return 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/toys/sbasisdim.cpp b/src/toys/sbasisdim.cpp new file mode 100644 index 0000000..3804090 --- /dev/null +++ b/src/toys/sbasisdim.cpp @@ -0,0 +1,262 @@ +#include <iostream>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+
+#include <toys/path-cairo.h>
+#include <toys/toy-framework-2.h>
+
+#include <time.h>
+#include <vector>
+
+#include <2geom/orphan-code/linearN.h>
+#include <2geom/orphan-code/sbasisN.h>
+
+using namespace Geom;
+using namespace std;
+
+
+struct Frame
+{
+ Geom::Point O;
+ Geom::Point x;
+ Geom::Point y;
+ Geom::Point z;
+};
+
+void
+plot3d(cairo_t *cr, double x, double y, double z, Frame frame){
+ Point p;
+ for (unsigned dim=0; dim<2; dim++){
+ p[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ p[dim] += frame.O[dim];
+ }
+ draw_cross(cr, p);
+}
+void
+plot3d(cairo_t *cr, SBasis const &x, SBasis const &y, SBasis const &z, Frame frame){
+ D2<SBasis> curve;
+ for (unsigned dim=0; dim<2; dim++){
+ curve[dim] = x*frame.x[dim] + y*frame.y[dim] + z*frame.z[dim];
+ curve[dim] += frame.O[dim];
+ }
+ cairo_d2_sb(cr, curve);
+}
+
+void
+plot3d(cairo_t *cr, LinearN<2> const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ double t = i/(iMax-1.);
+ plot3d(cr, Linear(0,1), Linear(t), toLinear(f.partialEval(t, 1)), frame);
+ plot3d(cr, Linear(t), Linear(0,1), toLinear(f.partialEval(t, 0)), frame);
+ }
+}
+void
+plot3d(cairo_t *cr, SBasisN<2> const &f, Frame frame){
+ int iMax = 5;
+ for (int i=0; i<iMax; i++){
+ double t = i/(iMax-1.);
+ plot3d(cr, Linear(0,1), Linear(t), toSBasis(f.partialEval(t, 1)), frame);
+ plot3d(cr, Linear(t), Linear(0,1), toSBasis(f.partialEval(t, 0)), frame);
+ }
+}
+void
+dot_plot3d(cairo_t *cr, SBasisN<2> const &f, Frame frame){
+ int iMax = 15;
+ double t[2];
+ for (int i=0; i<iMax; i++){
+ t[0] = i/(iMax-1.);
+ for (int j=0; j<iMax; j++){
+ t[1] = j/(iMax-1.);
+ plot3d(cr, t[0], t[1], f.valueAt(t), frame);
+ }
+ }
+}
+
+
+class SBasisDimToy: public Toy {
+ PointSetHandle hand;
+
+ void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override {
+
+ double slider_top = width/4.;
+ double slider_bot = width*3./4.;
+ double slider_margin = width/8.;
+ if(hand.pts.empty()) {
+ hand.pts.emplace_back(width*3./16., 3*width/4.);
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/2., 0));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(width/8., -width/12.));
+ hand.pts.push_back(hand.pts[0] + Geom::Point(0,-width/4.));
+ hand.pts.emplace_back(slider_margin,slider_bot);
+ hand.pts.emplace_back(width-slider_margin,slider_top);
+ }
+
+ hand.pts[4][X] = slider_margin;
+ if (hand.pts[4][Y]<slider_top) hand.pts[4][Y] = slider_top;
+ if (hand.pts[4][Y]>slider_bot) hand.pts[4][Y] = slider_bot;
+ hand.pts[5][X] = width-slider_margin;
+ if (hand.pts[5][Y]<slider_top) hand.pts[5][Y] = slider_top;
+ if (hand.pts[5][Y]>slider_bot) hand.pts[5][Y] = slider_bot;
+
+ double tA = (slider_bot-hand.pts[4][Y])/(slider_bot-slider_top);
+ double tB = (slider_bot-hand.pts[5][Y])/(slider_bot-slider_top);
+
+ cairo_move_to(cr,Geom::Point(slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(slider_margin,slider_top));
+ cairo_move_to(cr,Geom::Point(width-slider_margin,slider_bot));
+ cairo_line_to(cr,Geom::Point(width-slider_margin,slider_top));
+ cairo_set_line_width(cr,.5);
+ cairo_set_source_rgba (cr, 0., 0.3, 0., 1.);
+ cairo_stroke(cr);
+
+ Frame frame;
+ frame.O = hand.pts[0];//
+ frame.x = hand.pts[1]-hand.pts[0];//
+ frame.y = hand.pts[2]-hand.pts[0];//
+ frame.z = hand.pts[3]-hand.pts[0];//
+
+ plot3d(cr,Linear(0,1),Linear(0,0),Linear(0,0),frame);
+ plot3d(cr,Linear(0,1),Linear(1,1),Linear(0,0),frame);
+ plot3d(cr,Linear(0,0),Linear(0,1),Linear(0,0),frame);
+ plot3d(cr,Linear(1,1),Linear(0,1),Linear(0,0),frame);
+ cairo_set_line_width(cr,.2);
+ cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+ cairo_stroke(cr);
+
+ SBasisN<1> t = LinearN<1>(0,1);
+
+ LinearN<2> u,v;
+ setToVariable(u,0);
+ setToVariable(v,1);
+ SBasisN<2> f, x = u, y = v; //x,y are used for conversion :-(
+
+
+//--------------------
+//Basic MultiDegree<2> tests...
+//--------------------
+#if 0
+ unsigned sizes[2];
+ sizes[0] = 4;
+ sizes[1] = 3;
+ MultiDegree<2> d0;
+ d0.p[0]=3;
+ d0.p[1]=2;
+ std::cout<<"(3,2)->"<< d0.asIdx(sizes) <<"\n";
+ MultiDegree<2> d1(11,sizes);
+ std::cout<<"11->"<< d1.p[0] <<","<<d1.p[1] <<"\n";
+#endif
+
+//--------------------
+//Basic LinearN tests
+//--------------------
+#if 0
+ plot3d(cr, u, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, .75, 0., 0., 1.);
+ cairo_stroke(cr);
+ plot3d(cr, v, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0., 0.75, 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+//Basic SBasisN tests
+//--------------------
+#if 1
+ f = x*x + y*y;//(x-one*.5)*(x-one*.5);
+ std::cout<<"\nf: "<<f<<"\n";
+ std::cout<<"Degrees:\n";
+ std::cout<<"quick_deg: "<< f.quick_degree(0)<<", "<<f.quick_degree(1)<<"\n";
+ std::cout<<"real s_deg: "<< f.degree(0)<<", "<<f.degree(1)<<"\n";
+ std::cout<<"real t_deg: "<< f.real_t_degree(0)<<", "<<f.real_t_degree(1)<<"\n";
+ plot3d(cr, f, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0.75, 0., 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+// SBasisOf<SBasisOf<double> > simulation tests
+//--------------------
+#if 1
+ SBasisN<1> y1d = LinearN<1>(0,1);
+ SBasisN<2> g,g1;
+ g.appendCoef(LinearN<1>(0.), y1d*y1d , 0);
+ g.appendCoef(y1d + 1, y1d, 0);
+ g1 = x*y*y + x*(-x+1)*( (-x+1)*(y+1) + x*y );
+ plot3d(cr, g-g1, frame);
+ cairo_set_line_width(cr,1);
+ cairo_set_source_rgba (cr, 0., 0.75, 0., 1.);
+ cairo_stroke(cr);
+#endif
+//--------------------
+// SBasisN composition tests
+//--------------------
+#if 1
+ SBasisN<1> z;
+ std::vector<SBasisN<1> > var;
+ t -=.5;
+ var.push_back( t*t + tA);
+ var.push_back( (t+.3)*t*(t-.3) + tB);
+ z = compose(f,var);
+ cairo_set_line_width(cr,1);
+ plot3d(cr, toSBasis(var[0]), toSBasis(var[1]), Linear(0.), frame);
+ cairo_set_source_rgba (cr, 0., 0., 0.75, 1.);
+ cairo_stroke(cr);
+ plot3d(cr, toSBasis(var[0]), toSBasis(var[1]), toSBasis(z), frame);
+ cairo_set_source_rgba (cr, 0.75, 0., 0., 1.);
+ cairo_stroke(cr);
+#endif
+
+//--------------------
+//Some timing. TODO: Compare to SBasisOf<SBasisOf<double> >
+//--------------------
+#if 0
+ double units = 1e6;
+ std::string units_string("us");
+ double timer_precision = 0.1;
+ clock_t end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ // Base loop to remove overhead
+ end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ long iterations = 0;
+ while(end_t > clock()) {
+ iterations++;
+ }
+ double overhead = timer_precision*units/iterations;
+
+ end_t = clock()+clock_t(timer_precision*CLOCKS_PER_SEC);
+ iterations = 0;
+ while(end_t > clock()) {
+ f.valueAt(t);
+ iterations++;
+ }
+ *notify << "recursive eval: "
+ << ", time = " << timer_precision*units/iterations-overhead
+ << units_string << std::endl;
+#endif
+
+ Toy::draw(cr, notify, width, height, save,timer_stream);
+ }
+
+public:
+ SBasisDimToy(){
+ handles.push_back(&hand);
+ }
+};
+
+int main(int argc, char **argv) {
+ init(argc, argv, new SBasisDimToy);
+ return 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:encoding = utf-8:textwidth = 99 :
diff --git a/src/toys/scribble.cpp b/src/toys/scribble.cpp new file mode 100644 index 0000000..afaff65 --- /dev/null +++ b/src/toys/scribble.cpp @@ -0,0 +1,366 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-math.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/path-intersection.h> +#include <2geom/bezier-curve.h> +#include <2geom/transforms.h> +#include <2geom/angle.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <sstream> + +using std::vector; +using namespace Geom; +using namespace std; + +// TODO: +// use path2 +// replace Ray stuff with path2 line segments. + +//----------------------------------------------- + +void draw_segment(cairo_t* cr, Point const& p1, Point const& p2) +{ + cairo_move_to(cr, p1); + cairo_line_to(cr, p2); +} + +void draw_segment(cairo_t* cr, Point const& p1, double angle, double length) +{ + Point p2; + p2[X] = length * std::cos(angle); + p2[Y] = length * std::sin(angle); + p2 += p1; + draw_segment(cr, p1, p2); +} + +void draw_segment(cairo_t* cr, LineSegment const& ls) +{ + draw_segment(cr, ls[0], ls[1]); +} + +int num = 30; +vector<double> c1_bot; +vector<double> c1_top; +vector<double> c2_bot; +vector<double> c2_top; +vector<double> c3_bot; +vector<double> c3_top; +vector<double> c4_bot; +vector<double> c4_top; + +CubicBezier create_bezier(Point const &anchor, double angle /* in degrees */, + double length, double dx1, double dx2, cairo_t *cr = NULL) { + Point A = anchor; + Point dir = Point(1.0, 0) * Rotate(-angle) * length; + Point B = anchor + dir; + + Point C = A - Point(1.0, 0) * dx1; + Point D = B + Point(1.0, 0) * dx1; + Point E = A + Point(1.0, 0) * dx2; + Point F = B - Point(1.0, 0) * dx2; + + if (cr) { + draw_cross(cr, A); + draw_cross(cr, B); + draw_cross(cr, C); + draw_cross(cr, D); + draw_cross(cr, E); + draw_cross(cr, F); + } + + return CubicBezier(C, E, F, D); +} + +/* + * Draws a single "scribble segment" (we use many of these to cover the whole curve). + * + * Let I1, I2 be two adjacent intervals (bounded by the points A1, A2, A3) on the lower and J1, J2 + * two adjacent intervals (bounded by B1, B2, B3) on the upper parallel. Then we specify: + * + * - The point in I1 where the scribble line starts (given by a value in [0,1]) + * - The point in J2 where the scribble line ends (given by a value in [0,1]) + * - A point in I2 (1st intermediate point of the Bezier curve) + * - A point in J1 (2nd intermediate point of the Bezier curve) + * + */ +CubicBezier +create_bezier_again(Point const &anchor1, Point const &anchor2, Point const &dir1, Point const &dir2, + double /*c1*/, double /*c2*/, double c3, double c4, double mu, cairo_t *cr = NULL) { + Point A = anchor1;// - dir * c1; + Point B = anchor1 + dir1 * (c3 + mu); + Point C = anchor2 - dir2 * (c4 + mu); + Point D = anchor2;// + dir * c2; + + if (cr) { + draw_cross(cr, A); + //draw_cross(cr, B); + //draw_cross(cr, C); + //draw_cross(cr, D); + } + + return CubicBezier(A, B, C, D); +} + +CubicBezier +create_bezier_along_curve(Piecewise<D2<SBasis> > const &curve1, + Piecewise<D2<SBasis> > const &curve2, + double segdist, + Coord const t1, Coord const t2, Point const &n, + double c1, double c2, double /*c3*/, double /*c4*/, double /*mu*/, cairo_t *cr = NULL) { + cout << "create_bezier_along_curve -- start" << endl; + /* + Point A = curve1.valueAt(t1 - c1); + Point B = curve1.valueAt(t1) + n * (c3 + mu); + Point C = curve2.valueAt(t2) - n * (c4 + mu); + Point D = curve2.valueAt(t2 + c2); + */ + Point A = curve1.valueAt(t1 - c1 * segdist); + Point B = curve1.valueAt(t1) + n * 0.1; + Point C = curve2.valueAt(t2) - n * 0.1; + Point D = curve2.valueAt(t2 + c2 * segdist); + + if (cr) { + draw_cross(cr, A); + //draw_cross(cr, B); + //draw_cross(cr, C); + //draw_cross(cr, D); + } + + cout << "create_bezier_along_curve -- end" << endl; + return CubicBezier(A, B, C, D); +} + +class OffsetTester: public Toy { + PointSetHandle psh; + PointSetHandle psh_rand; + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + double w = 600; + double slider_top = w/4.; + double slider_bot = w*3./4.; + double slider_margin = 40; + double slider_middle = (slider_top + slider_bot) / 2; + + if(psh.pts.empty()) { + psh.pts.emplace_back(200,300); + psh.pts.emplace_back(350,250); + psh.pts.emplace_back(500,280); + psh.pts.emplace_back(700,300); + + psh.pts.emplace_back(400,300); + psh.pts.emplace_back(550,250); + psh.pts.emplace_back(700,280); + psh.pts.emplace_back(900,300); + + psh.pts.emplace_back(900,500); + psh.pts.emplace_back(700,480); + psh.pts.emplace_back(550,450); + psh.pts.emplace_back(400,500); + + psh_rand.pts.emplace_back(slider_margin,slider_bot); + psh_rand.pts.emplace_back(slider_margin,slider_top); + psh_rand.pts.emplace_back(slider_margin,slider_top); + psh_rand.pts.emplace_back(slider_margin,slider_top); + psh_rand.pts.emplace_back(slider_margin,slider_top); + psh_rand.pts.emplace_back(slider_margin,slider_bot); + psh_rand.pts.emplace_back(slider_margin,slider_middle); + } + + psh_rand.pts[0][X] = slider_margin; + if (psh_rand.pts[0][Y]<slider_top) psh_rand.pts[0][Y] = slider_top; + if (psh_rand.pts[0][Y]>slider_bot) psh_rand.pts[0][Y] = slider_bot; + psh_rand.pts[1][X] = slider_margin + 15; + if (psh_rand.pts[1][Y]<slider_top) psh_rand.pts[1][Y] = slider_top; + if (psh_rand.pts[1][Y]>slider_bot) psh_rand.pts[1][Y] = slider_bot; + psh_rand.pts[2][X] = slider_margin + 30; + if (psh_rand.pts[2][Y]<slider_top) psh_rand.pts[2][Y] = slider_top; + if (psh_rand.pts[2][Y]>slider_bot) psh_rand.pts[2][Y] = slider_bot; + psh_rand.pts[3][X] = slider_margin + 45; + if (psh_rand.pts[3][Y]<slider_top) psh_rand.pts[3][Y] = slider_top; + if (psh_rand.pts[3][Y]>slider_bot) psh_rand.pts[3][Y] = slider_bot; + psh_rand.pts[4][X] = slider_margin + 60; + if (psh_rand.pts[4][Y]<slider_top) psh_rand.pts[4][Y] = slider_top; + if (psh_rand.pts[4][Y]>slider_bot) psh_rand.pts[4][Y] = slider_bot; + psh_rand.pts[5][X] = slider_margin + 75; + if (psh_rand.pts[5][Y]<slider_top) psh_rand.pts[5][Y] = slider_top; + if (psh_rand.pts[5][Y]>slider_bot) psh_rand.pts[5][Y] = slider_bot; + psh_rand.pts[6][X] = slider_margin + 90; + if (psh_rand.pts[6][Y]<slider_top) psh_rand.pts[6][Y] = slider_top; + if (psh_rand.pts[6][Y]>slider_bot) psh_rand.pts[6][Y] = slider_bot; + + *notify << "Sliders:" << endl << endl << endl << endl; + *notify << "0 - segment distance" << endl; + *notify << "1 - start anchor randomization" << endl; + *notify << "2 - end anchor randomization" << endl; + *notify << "3 - start rounding randomization" << endl; + *notify << "4 - end rounding randomization" << endl; + *notify << "5 - start/end rounding increase randomization" << endl; + *notify << "6 - additional offset of the upper anchors (to modify the segment angle)" << endl; + + for(unsigned i = 0; i < psh_rand.size(); ++i) { + cairo_move_to(cr,Geom::Point(slider_margin + 15.0 * i, slider_bot)); + cairo_line_to(cr,Geom::Point(slider_margin + 15.0 * i, slider_top)); + } + cairo_set_line_width(cr,.5); + cairo_set_source_rgba (cr, 0., 0.3, 0., 1.); + cairo_stroke(cr); + + cairo_set_line_width (cr, 2); + cairo_set_source_rgba (cr, 0., 0., 0.8, 1); + + // Draw the curve and its offsets + D2<SBasis> B = psh.asBezier(); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + Coord offset = 30; + Piecewise<D2<SBasis> > n = rot90(unitVector(derivative(B))); + Piecewise<D2<SBasis> > offset_curve1 = Piecewise<D2<SBasis> >(B)+n*offset; + Piecewise<D2<SBasis> > offset_curve2 = Piecewise<D2<SBasis> >(B)-n*offset; + PathVector offset_path1 = path_from_piecewise(offset_curve1, 0.1); + PathVector offset_path2 = path_from_piecewise(offset_curve2, 0.1); + Piecewise<D2<SBasis> > tangent1 = unitVector(derivative(offset_curve1)); + Piecewise<D2<SBasis> > tangent2 = unitVector(derivative(offset_curve2)); + cairo_set_line_width (cr, 1); + cairo_path(cr, offset_path1); + cairo_path(cr, offset_path2); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0.5, 0., 1); + + double lambda1 = 1.0 - (psh_rand.pts[1][Y] - slider_top) * 2.0/w; + double lambda2 = 1.0 - (psh_rand.pts[2][Y] - slider_top) * 2.0/w; + double lambda3 = 1.0 - (psh_rand.pts[3][Y] - slider_top) * 2.0/w; + double lambda4 = 1.0 - (psh_rand.pts[4][Y] - slider_top) * 2.0/w; + double mu = 1.0 - (psh_rand.pts[5][Y] - slider_top) * 2.0/w; + //Point dir = Point(1,0) * (slider_bot - psh_rand.pts[0][Y]) / 2.5; + double off = 0.5 - (psh_rand.pts[6][Y] - slider_top) * 2.0/w; + + double segdist = (slider_bot - psh_rand.pts[0][Y]) / (slider_bot - slider_top) * 0.1; + if (segdist < 0.01) { + segdist = 0.01; + } + + vector<Point> pts_bot; + vector<Point> pts_top; + vector<Point> dirs_bot; + vector<Point> dirs_top; + int counter = 0; + for(double i = 0.0; i < 1.0; i += segdist) { + draw_cross(cr, offset_curve1.valueAt(i)); + pts_bot.push_back(offset_curve1.valueAt(i + segdist * c1_bot[counter] * lambda1)); + pts_top.push_back(offset_curve2.valueAt(i + segdist * (c2_top[counter] * lambda2 + 1/2.0) + off)); + dirs_bot.push_back(tangent1.valueAt(i) * 20); + dirs_top.push_back(tangent2.valueAt(i) * 20); + ++counter; + } + + for(int i = 0; i < num; ++i) { + cout << "c1_bot[" << i << "]: " << c1_bot[i] << endl; + } + + for (int i = 0; i < num-1; ++i) { + Path path1; + //cout << "dirs_bot[" << i << "]: " << dirs_bot[i] << endl; + cout << "c3_bot[" << i << "]: " << c3_bot[i] << endl; + CubicBezier bc = create_bezier_again(pts_bot[i], pts_top[i], + dirs_bot[i], dirs_top[i], + 0, 0, c3_bot[i] * lambda3, c4_top[i] * lambda4, mu, cr); + //c1_bot[i] * lambda1, + //c2_top[i] * lambda2, + //c3_bot[i] * lambda3, + //c4_top[i] * lambda4, mu, cr); + + path1.append(bc); + cairo_path(cr, path1); + + Path path2; + bc = create_bezier_again(pts_top[i], pts_bot[i+1], + dirs_top[i], dirs_bot[i+1], + 0, 0, c4_bot[i] * lambda4, c3_top[i] * lambda3, mu, cr); + /* + bc = create_bezier_again(pts_top[i+1], pts_bot[i], dir, + 1.0 - c2_top[i] * lambda2, + 1.0 - c1_bot[i+1] * lambda1, + c3_top[i] * lambda3, + c4_bot[i] * lambda4, mu, cr); + */ + path2.append(bc); + cairo_path(cr, path2); + } + + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + +public: + OffsetTester() { + handles.push_back(&psh); + handles.push_back(&psh_rand); + /* + psh.pts.clear(); + Point A(100,300); + //Point B(100,200); + //Point C(200,330); + Point D(200,100); + psh.push_back(A); + //psh.push_back(B); + //psh.push_back(C); + psh.push_back(D); + psh.push_back(Geom::Point(slider_margin,slider_bot)); + */ + + for (int i = 0; i < num; ++i) { + c1_bot.push_back(uniform() - 0.5); + c1_top.push_back(uniform() - 0.5); + //c1_bot.push_back(1.0); + //c1_top.push_back(1.0); + c2_bot.push_back(uniform() - 0.5); + c2_top.push_back(uniform() - 0.5); + + c3_bot.push_back(uniform()); + c3_top.push_back(uniform()); + c4_bot.push_back(uniform()); + c4_top.push_back(uniform()); + } + + /* + for (int i = 0; i < num; ++i) { + c1_bot[i] = c1_bot[i] / 10.0; + c2_bot[i] = c2_bot[i] / 10.0; + c3_bot[i] = c3_bot[i] / 10.0; + c4_bot[i] = c4_bot[i] / 10.0; + + c1_top[i] = c1_top[i] / 10.0; + c2_top[i] = c2_top[i] / 10.0; + c3_top[i] = c3_top[i] / 10.0; + c4_top[i] = c4_top[i] / 10.0; + } + */ + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new OffsetTester); + return 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:encoding = utf-8:textwidth = 99 : + + diff --git a/src/toys/self-intersect.cpp b/src/toys/self-intersect.cpp new file mode 100644 index 0000000..840e058 --- /dev/null +++ b/src/toys/self-intersect.cpp @@ -0,0 +1,67 @@ +#include <2geom/basic-intersection.h> +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/bezier-to-sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using std::vector; +using namespace Geom; + +class SelfIntersect: public Toy { + PointSetHandle psh; +void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_line_width (cr, 0.5); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + + D2<SBasis> A = psh.asBezier(); + //Rect Ar = *bounds_fast(A); + cairo_d2_sb(cr, A); + cairo_stroke(cr); + + std::vector<std::pair<double, double> > all_si; + + find_self_intersections(all_si, A); + + cairo_stroke(cr); + cairo_set_source_rgba (cr, 1., 0., 1, 1); + for(auto & i : all_si) { + draw_handle(cr, A(i.first)); + } + cairo_stroke(cr); + + *notify << "total intersections: " << all_si.size(); + + Toy::draw(cr, notify, width, height, save,timer_stream); +} +public: +SelfIntersect (unsigned bez_ord) { + handles.push_back(&psh); + for(unsigned i = 0; i < bez_ord; i++) + psh.push_back(uniform()*400, uniform()*400); +} +}; + +int main(int argc, char **argv) { + unsigned bez_ord=5; + if(argc > 1) + sscanf(argv[1], "%d", &bez_ord); + init(argc, argv, new SelfIntersect(bez_ord)); + + return 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/toys/sketch-fitter.cpp b/src/toys/sketch-fitter.cpp new file mode 100644 index 0000000..2a74924 --- /dev/null +++ b/src/toys/sketch-fitter.cpp @@ -0,0 +1,923 @@ +/* + * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier. + * + * Copyright 2007 jf barraud. + * 2008 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. + * + */ + +// mainly experimental atm... +// do not expect to find anything understandable here atm. + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-math.h> +#include <2geom/basic-intersection.h> +#include <2geom/bezier-utils.h> + +#include <2geom/circle.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#define ZERO 1e-7 + +using std::vector; +using namespace Geom; +using namespace std; + +#include <stdio.h> +#include <gsl/gsl_poly.h> + +std::vector<Point> neighbors(std::vector<Point> const &pts, unsigned idx, double radius){ + std::vector<Point> res; + Point p0 = pts[idx]; + for (auto p : pts){ + if ( L2(p-p0) < radius ) res.push_back(p); + } + return res; +} + +double curvature(Point const &a, Point const &b, Point const &c){ + Line med_ab = Line( (a+b)/2, (a+b)/2+rot90(b-a) ); + Line med_bc = Line( (b+c)/2, (b+c)/2+rot90(c-b) ); + OptCrossing o = intersection(med_ab, med_bc); + if (o){ + Point oo = med_ab.pointAt(o->ta); + return(1./L2(oo-a)); + } + else + return 0; +} + +double avarageCurvature(std::vector<Point> const &pts, unsigned idx, double radius){ + std::vector<Point> ngbrs = neighbors(pts, idx, radius); + if (ngbrs.size()<3) return 0; + double k=0; + double mass = 0; + for (unsigned i=0; i<5; i++){ + unsigned ia = 0, ib = 0, ic = 0; + ia = rand()%ngbrs.size(); + while (ib == ia) + ib = rand()%ngbrs.size(); + while (ic == ia || ic == ib) + ic = rand()%ngbrs.size(); + k += curvature(pts[ia],pts[ib],pts[ic]); + mass += 1; //smaller mass to closer triplets? + } + k /= mass; + return k; +} + +Point massCenter(std::vector<Point> const &pts){ + Point g = Point(0,0); + for (unsigned i=0; i<pts.size(); i++){ + g += pts[i]/pts.size(); + } + return g; +} + +Line meanSquareLine(std::vector<Point> const &pts){ + Point g = massCenter(pts); + double a = 0, b = 0, c = 0; + for (auto pt : pts){ + a += (pt[Y]-g[Y])*(pt[Y]-g[Y]); + b +=-(pt[X]-g[X])*(pt[Y]-g[Y]); + c += (pt[X]-g[X])*(pt[X]-g[X]); + } + double eigen = ( (a+c) - sqrt((a-c)*(a-c)+4*b*b) )/2; + Point u(-b,a-eigen); + return Line(g, g+u); +} + +void tighten(std::vector<Point> &pts, double radius, bool linear){ + for (unsigned i=0; i<pts.size(); i++){ + std::vector<Point> ngbrs = neighbors(pts,i,radius); + if (linear){ + Line d = meanSquareLine(ngbrs); + Point proj = projection( pts[i], d ); + double t = 2./3.; + pts[i] = pts[i]*(1-t) + proj*t; + }else if (ngbrs.size()>=3) { + Circle c; + c.fit(ngbrs); + Point o = c.center(); + double r = c.radius(); + pts[i] = o + unit_vector(pts[i]-o)*r; + } + } +} + +double dist_to(std::vector<Point> const &pts, Point const &p, unsigned *idx=NULL){ + double d,d_min = std::numeric_limits<float>::infinity(); + if (idx) *idx = pts.size(); + for (unsigned i = 0; i<pts.size(); i++){ + d = L2(pts[i]-p); + if ( d < d_min ){ + d_min = d; + if (idx) *idx = i; + } + } + return d_min; +} + +void fuse_close_points(std::vector<Point> &pts, double dist_min){ + if (pts.size()==0) return; + std::vector<Point> reduced_pts; + reduced_pts.push_back(pts[0]); + for (auto & pt : pts){ + double d = dist_to(reduced_pts, pt); + if ( d > dist_min ) reduced_pts.push_back(pt); + } + pts = reduced_pts; + return; +} + + +unsigned nearest_after(std::vector<Point>const &pts, unsigned idx, double *dist = NULL){ + if ( idx >= pts.size()-1 ) return pts.size(); + Point p = pts[idx]; + unsigned res = idx+1; + double d_min = L2(p-pts[res]); + for (unsigned i=idx+2; i<pts.size(); i++){ + double d = L2(p-pts[i]); + if (d < d_min) { + d_min = d; + res = i; + } + } + if (dist) *dist = d_min; + return res; +} + +//TEST ME: use direction information to separate exaeco? +void sort_nearest(std::vector<Point> &pts, double no_longer_than = 0){ + double d; + Point p; + for (unsigned i=0; i<pts.size()-1; i++){ + unsigned j = nearest_after(pts,i,&d); + if (no_longer_than >0.1 && d > no_longer_than){ + pts.erase(pts.begin()+i+1, pts.end()); + return; + } + p = pts[i+1]; + pts[i+1] = pts[j]; + pts[j] = p; + } +} + +//FIXME: optimize me if further used... +void sort_nearest_bis(std::vector<Point> &pts, double radius){ + double d; + Point p; + for (unsigned i=0; i<pts.size()-1; i++){ + bool already_visited = true; + unsigned next = 0; // silence warning + while ( i < pts.size()-1 && already_visited ){ + next = nearest_after(pts,i,&d); + already_visited = false; + for (unsigned k=0; k<i; k++){ + double d_k_next = L2( pts[next] - pts[k]); + if ( d_k_next < d && d_k_next < radius ){ + already_visited = true; + pts.erase(pts.begin()+next); + break; + } + } + } + if (!already_visited){ + p = pts[i+1]; + pts[i+1] = pts[next]; + pts[next] = p; + } + } +} + +Path ordered_fit(std::vector<Point> &pts, double tol){ + unsigned n_points = pts.size(); + Geom::Point * b = g_new(Geom::Point, 4*n_points); + Geom::Point * points = g_new(Geom::Point, 4*n_points); + for (unsigned int i = 0; i < pts.size(); i++) { + points[i] = pts[i]; + } + int max_segs = 4*n_points; + int const n_segs = bezier_fit_cubic_r(b, points, n_points, + tol*tol, max_segs); + Path res; + if ( n_segs > 0){ + res = Path(b[0]); + for (int i=0; i<n_segs; i++){ + res.appendNew<CubicBezier>(b[4*i+1],b[4*i+2],b[4*i+3]); + } + } + g_free(b); + g_free(points); + return res; +} + +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- + + +std::vector<Point> eat(std::vector<Point> const &pts, double sampling){ + std::vector<bool> visited(pts.size(),false); + std::vector<Point> res; + Point p = pts.front(); + //Point q = p; + res.push_back(p); + while(true){ + double num_nghbrs = 0; + Point next(0,0); + for(unsigned i = 0; i < pts.size(); i++) { + if (!visited[i] && L2(pts[i]-p)<sampling){ + //TODO: rotate pts[i] so that last step was in dir -pi... + //dir += atan2(pts[i]-p); + visited[i] = true; + next+= pts[i]-p; + num_nghbrs += 1; + } + } + if (num_nghbrs == 0) break; + //q=p; + next *= 1./num_nghbrs; + p += next; + res.push_back(p); + } + return res; +} + + + + + +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- + +double exp_rescale(double x) +{ + return pow(10, x*5-2); +} +std::string exp_formatter(double x) +{ + return default_formatter(exp_rescale(x)); +} + +class SketchFitterToy: public Toy { + + enum menu_item_t + { + SHOW_MENU = 0, + TEST_TIGHTEN, + TEST_EAT_BY_STEP, + TEST_TIGHTEN_EAT, + TEST_CURVATURE, + TEST_SORT, + TEST_NUMERICAL, + SHOW_HELP, + TOTAL_ITEMS // this one must be the last item + }; + + enum handle_label_t + { + }; + + enum toggle_label_t + { + DRAW_MOUSES = 0, + DRAW_IMPROVED_MOUSES, + DRAW_STROKE, + TIGHTEN_USE_CIRCLE, + SORT_BIS, + TOTAL_TOGGLES // this one must be the last item + }; + + enum slider_label_t + { + TIGHTEN_NBHD_SIZE = 0, + TIGHTEN_ITERRATIONS, + EAT_NBHD_SIZE, + SORT_RADIUS, + FUSE_RADIUS, + INTERPOLATE_RADIUS, + CURVATURE_NBHD_SIZE, + POINT_CHOOSER, + TOTAL_SLIDERS // this one must be the last item + }; + + static const char* menu_items[TOTAL_ITEMS]; + static const char keys[TOTAL_ITEMS]; + + void fit_empty(){} + void first_time(int /*argc*/, char** /*argv*/) override + { + draw_f = &SketchFitterToy::draw_menu; + fit_f = &SketchFitterToy::fit_empty; + } + + void init_common() + { + set_common_control_geometry = true; + set_control_geometry = true; + + handles.clear(); + handles.push_back(&(toggles[DRAW_MOUSES])); + handles.push_back(&(toggles[DRAW_IMPROVED_MOUSES])); + handles.push_back(&(toggles[DRAW_STROKE])); + + //sliders.clear(); + //toggles.clear(); + //handles.clear(); + } + void init_common_ctrl_geom(cairo_t* /*cr*/, int width, int /*height*/, std::ostringstream* /*notify*/) + { + if ( set_common_control_geometry ) + { + set_common_control_geometry = false; + Point p(10, 20), d(width/3-20,25); + toggles[DRAW_MOUSES].bounds = Rect(p, p + d); + p += Point ((width)/3, 0); + toggles[DRAW_IMPROVED_MOUSES].bounds = Rect(p, p + d); + p += Point ((width)/3, 0); + toggles[DRAW_STROKE].bounds = Rect(p, p + d); + } + } + virtual void draw_common( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool /*save*/, std::ostringstream */*timer_stream*/) + { + init_common_ctrl_geom(cr, width, height, notify); + if(!mouses.empty() && toggles[DRAW_MOUSES].on ) { + //cairo_move_to(cr, mouses[0]); + //for(unsigned i = 0; i < mouses.size(); i++) { + // cairo_line_to(cr, mouses[i]); + //} + for(auto & mouse : mouses) { + draw_cross(cr, mouse); + } + cairo_set_source_rgba (cr, 0., 0., 0., .25); + cairo_set_line_width (cr, 0.5); + cairo_stroke(cr); + } + + if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) { + cairo_move_to(cr, improved_mouses[0]); + for(auto & improved_mouse : improved_mouses) { + draw_cross(cr, improved_mouse); + } + cairo_set_source_rgba (cr, 1., 0., 0., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + + if(!stroke.empty() && toggles[DRAW_STROKE].on) { + cairo_pw_d2_sb(cr, stroke); + cairo_set_source_rgba (cr, 0., 0., 1., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + + *notify << "Press SHIFT to continue sketching. 'Z' to apply changes"; + } + + +//----------------------------------------------------------------------------------------- +// Tighten: tries to move the points toward the common curve +//----------------------------------------------------------------------------------------- + void init_tighten() + { + init_common(); + handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE])); + handles.push_back(&(sliders[TIGHTEN_ITERRATIONS])); + handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE])); + } + void init_tighten_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180); + sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180); + + Point p(width-250, height - 50), d(225,25); + toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d); + } + } + void fit_tighten(){ + improved_mouses = mouses; + double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value()); + for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){ + tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on); + } + } + void draw_tighten(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_tighten_ctrl_geom(cr, notify, width, height); + } + +//----------------------------------------------------------------------------------------- +// Eat by step: eats the curve moving at each step in the average direction of the neighbors. +//----------------------------------------------------------------------------------------- + void init_eat() + { + init_common(); + handles.push_back(&(sliders[EAT_NBHD_SIZE])); + } + void init_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[EAT_NBHD_SIZE].geometry(Point(50, height - 35*(0+2)), 180); + } + } + void fit_eat(){ + double radius = exp_rescale(sliders[EAT_NBHD_SIZE].value()); + improved_mouses = mouses; + + tighten(improved_mouses, 20, true); + + stroke = Piecewise<D2<SBasis> >(); + improved_mouses = eat(improved_mouses, radius); + Path p(improved_mouses[0]); + for(unsigned i = 1; i < improved_mouses.size(); i++) { + p.appendNew<LineSegment>(improved_mouses[i]); + } + stroke = p.toPwSb(); + } + void draw_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_eat_ctrl_geom(cr, notify, width, height); + } + +//----------------------------------------------------------------------------------------- +// Tighten + Eat +//----------------------------------------------------------------------------------------- + void init_tighten_eat() + { + init_common(); + handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE])); + handles.push_back(&(sliders[TIGHTEN_ITERRATIONS])); + handles.push_back(&(sliders[EAT_NBHD_SIZE])); + } + void init_tighten_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180); + sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180); + sliders[EAT_NBHD_SIZE ].geometry(Point(50, height - 35*4), 180); + } + } + void fit_tighten_eat(){ + improved_mouses = mouses; + double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value()); + for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){ + tighten(improved_mouses, radius, toggles[0].on); + } + stroke = Piecewise<D2<SBasis> >(); + radius = exp_rescale(sliders[EAT_NBHD_SIZE].value()); + improved_mouses = eat(improved_mouses, radius); + Path p(improved_mouses[0]); + for(unsigned i = 1; i < improved_mouses.size(); i++) { + p.appendNew<LineSegment>(improved_mouses[i]); + } + stroke = p.toPwSb(); + } + void draw_tighten_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_tighten_eat_ctrl_geom(cr, notify, width, height); + } + +//----------------------------------------------------------------------------------------- +// Sort: tighten, then sort and eventually fuse. +//----------------------------------------------------------------------------------------- + void init_sort() + { + init_common(); + handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE])); + handles.push_back(&(sliders[TIGHTEN_ITERRATIONS])); + handles.push_back(&(sliders[SORT_RADIUS])); + handles.push_back(&(sliders[FUSE_RADIUS])); + handles.push_back(&(sliders[INTERPOLATE_RADIUS])); + handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE])); + handles.push_back(&(toggles[SORT_BIS])); + } + void init_sort_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[TIGHTEN_NBHD_SIZE].geometry(Point(50, height - 35*2), 180); + sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180); + sliders[SORT_RADIUS].geometry(Point(50, height - 35*4), 180); + sliders[FUSE_RADIUS].geometry(Point(50, height - 35*5), 180); + sliders[INTERPOLATE_RADIUS].geometry(Point(50, height - 35*6), 180); + + Point p(width-250, height - 50), d(225,25); + toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d); + p += Point(0,-30); + toggles[SORT_BIS].bounds = Rect(p, p + d); + } + } + void fit_sort(){ + improved_mouses = mouses; + double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value()); + for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){ + tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on); + } + double max_jump = exp_rescale(sliders[SORT_RADIUS].value()); + if (toggles[SORT_BIS].on){ + sort_nearest_bis(improved_mouses, max_jump); + }else{ + sort_nearest(improved_mouses, max_jump); + } + radius = exp_rescale(sliders[FUSE_RADIUS].value()); + fuse_close_points(improved_mouses, radius); + + radius = exp_rescale(sliders[INTERPOLATE_RADIUS].value()); + Path p = ordered_fit(improved_mouses, radius/5); + stroke = p.toPwSb(); + } + void draw_sort(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_sort_ctrl_geom(cr, notify, width, height); + + if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) { + cairo_move_to(cr, improved_mouses[0]); + for(unsigned i = 1; i < improved_mouses.size(); i++) { + cairo_line_to(cr, improved_mouses[i]); + } + cairo_set_source_rgba (cr, 1., 0., 0., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + } + +//----------------------------------------------------------------------------------------- +// Average curvature. +//----------------------------------------------------------------------------------------- + void init_curvature() + { + init_common(); + handles.push_back(&(sliders[CURVATURE_NBHD_SIZE])); + handles.push_back(&(sliders[POINT_CHOOSER])); + } + void init_curvature_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) + { + if ( set_control_geometry ){ + set_control_geometry = false; + sliders[CURVATURE_NBHD_SIZE].geometry(Point(50, height - 60), 180); + sliders[POINT_CHOOSER ].geometry(Point(50, height - 90), 180); + } + } + //just for fun! + void fit_curvature(){ + std::vector<double> curvatures(mouses.size(),0); + std::vector<double> lengths(mouses.size(),0); + for (unsigned i=0; i<mouses.size(); i++){ + double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value()); + std::vector<Point> ngbrs = neighbors(mouses,i,radius); + if ( ngbrs.size()>2 ){ + Circle c; + c.fit(ngbrs); + curvatures[i] = 1./c.radius(); + Point v = (i<mouses.size()-1) ? mouses[i+1]-mouses[i] : mouses[i]-mouses[i-1]; + if (cross(v, c.center()-mouses[i]) > 0 ) + curvatures[i] *= -1; + }else{ + curvatures[i] = 0; + } + if (i>0){ + lengths[i] = lengths[i-1] + L2(mouses[i]-mouses[i-1]); + } + } + Piecewise<SBasis> k = interpolate( lengths, curvatures , 1); + Piecewise<SBasis> alpha = integral(k); + Piecewise<D2<SBasis> > v = sectionize(tan2(alpha)); + stroke = integral(v) + mouses[0]; + + Point sp = stroke.lastValue()-stroke.firstValue(); + Point mp = mouses.back()-mouses.front(); + Affine mat1 = Affine(sp[X], sp[Y], -sp[Y], sp[X], stroke.firstValue()[X], stroke.firstValue()[Y]); + Affine mat2 = Affine(mp[X], mp[Y], -mp[Y], mp[X], mouses[0][X], mouses[0][Y]); + mat1 = mat1.inverse()*mat2; + stroke = stroke*mat1; + + } + void draw_curvature(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_curvature_ctrl_geom(cr, notify, width, height); + if(!mouses.empty()) { + double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value()); + unsigned i = unsigned( (mouses.size()-1)*sliders[POINT_CHOOSER].value()/100. ); + std::vector<Point> ngbrs = neighbors(mouses,i,radius); + if ( ngbrs.size()>2 ){ + draw_cross(cr, mouses[i]); + Circle c; + c.fit(ngbrs); + cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI); + cairo_set_source_rgba (cr, 1., 0., 0., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + cairo_pw_d2_sb(cr, stroke); + } + } + +//----------------------------------------------------------------------------------------- +// Brutal optimization, number of segment fixed. +//----------------------------------------------------------------------------------------- + void init_numerical() + { + init_common(); + //sliders.push_back(Slider(0, 10, 1, 1, "Number of curves")); + //handles.push_back(&(sliders[0])); + } + void init_numerical_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int /*height*/) + { + if ( set_control_geometry ){ + set_control_geometry = false; + //sliders[0].geometry(Point(50, height - 35*(0+2)), 180); + } + } + void fit_numerical(){ + //Not implemented + } + void draw_numerical(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + draw_common(cr, notify, width, height, save, timer_stream); + init_numerical_ctrl_geom(cr, notify, width, height); + if(!mouses.empty()) { + cairo_pw_d2_sb(cr, stroke); + cairo_set_source_rgba (cr, 1., 0., 0., 1); + cairo_set_line_width (cr, .75); + cairo_stroke(cr); + } + } +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- + + void init_help() + { + handles.clear(); + //sliders.clear(); + //toggles.clear(); + } + void draw_help( cairo_t * /*cr*/, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/) + { + *notify << "Tighten:\n"; + *notify << " move points toward local\n"; + *notify << " mean square line (or circle).\n"; + *notify << "Eat:\n"; + *notify << " eat points like a pacman; at each step, move to the\n"; + *notify << " average of the not already visited neighbor points.\n"; + *notify << "Sort:\n"; + *notify << " move from one point to the nearest one.\n"; + *notify << " Stop at the first jump longer than sort-radius\n"; + *notify << "Sort-bis:\n"; + *notify << " move from one point to the nearest one,\n"; + *notify << " unless it was 'already visited' (i.e. it is closer to\n"; + *notify << " an already sorted point with distance < sort-radius.\n"; + *notify << "Fuse: \n"; + *notify << " start from first point, remove all points closer to it\n"; + *notify << " than fuse-radius, move to the first one that is not, and repeat.\n"; + *notify << "Curvature: \n"; + *notify << " Compute the curvature at a given point from the circle fitting the\n"; + *notify << " nearby points (just for fun: the stroke is the 'integral' of this\n"; + *notify << " average curvature)\n"; + *notify << "Numerical: \n"; + *notify << " still waiting for someone to implement me ;-)\n\n"; + *notify << std::endl; + } + +//----------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------- + +public: + vector<Point> mouses; + int mouse_drag; + vector<Point> improved_mouses; + Piecewise<D2<SBasis > > stroke; + + void mouse_pressed(GdkEventButton* e) override { + //toggle_events(toggles, e); + Toy::mouse_pressed(e); + if(!selected) { + mouse_drag = 1; + if (!(e->state & (GDK_SHIFT_MASK))){ + mouses.clear(); + } + } + } + + void mouse_moved(GdkEventMotion* e) override { + if(mouse_drag) { + mouses.emplace_back(e->x, e->y); + redraw(); + } else { + Toy::mouse_moved(e); + } + } + + void mouse_released(GdkEventButton* e) override { + mouse_drag = 0; + if(!mouses.empty()) { + (this->*fit_f)(); + } + Toy::mouse_released(e); + } + + void init_menu() + { + handles.clear(); + //sliders.clear(); + //toggles.clear(); + } + void draw_menu( cairo_t * cr, std::ostringstream *notify, + int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/) + { + *notify << "Sketch some shape on canvas (press SHIFT to use several 'strokes')\n"; + *notify << "Each menu below will transform your input.\n"; + *notify << "Press 'Z' to make the result the new input\n"; + *notify << " \n \n \n"; + *notify << std::endl; + for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) + { + *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; + } + if(!mouses.empty()) { + cairo_move_to(cr, mouses[0]); + for(auto & mouse : mouses) { + cairo_line_to(cr, mouse); + } + for(auto & mouse : mouses) { + draw_cross(cr, mouse); + } + cairo_set_source_rgba (cr, 0., 0., 0., .25); + cairo_set_line_width (cr, 0.5); + cairo_stroke(cr); + } + } + + void key_hit(GdkEventKey *e) override + { + char choice = std::toupper(e->keyval); + switch ( choice ) + { + case 'A': + init_menu(); + draw_f = &SketchFitterToy::draw_menu; + break; + case 'B': + init_tighten(); + fit_f = &SketchFitterToy::fit_tighten; + draw_f = &SketchFitterToy::draw_tighten; + break; + case 'C': + init_eat(); + fit_f = &SketchFitterToy::fit_eat; + draw_f = &SketchFitterToy::draw_eat; + break; + case 'D': + init_tighten_eat(); + fit_f = &SketchFitterToy::fit_tighten_eat; + draw_f = &SketchFitterToy::draw_tighten_eat; + break; + case 'E': + init_sort(); + fit_f = &SketchFitterToy::fit_sort; + draw_f = &SketchFitterToy::draw_sort; + break; + case 'F': + init_curvature(); + fit_f = &SketchFitterToy::fit_curvature; + draw_f = &SketchFitterToy::draw_curvature; + break; + case 'G': + init_numerical(); + fit_f = &SketchFitterToy::fit_numerical; + draw_f = &SketchFitterToy::draw_numerical; + break; + case 'H': + init_help(); + draw_f = &SketchFitterToy::draw_help; + break; + case 'Z': + mouses = improved_mouses; + break; + } + redraw(); + } + + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream ) override + { + m_width = width; + m_height = height; + m_length = (m_width > m_height) ? m_width : m_height; + m_length *= 2; + (this->*draw_f)(cr, notify, width, height, save, timer_stream); + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + + public: + SketchFitterToy() + { + srand ( time(NULL) ); + sliders = std::vector<Slider>(TOTAL_SLIDERS, Slider(0., 1., 0, 0., "")); + + sliders[TIGHTEN_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "neighborhood size"); + sliders[TIGHTEN_NBHD_SIZE ].formatter(&exp_formatter); + sliders[TIGHTEN_ITERRATIONS] = Slider(0, 10, 1, 3, "iterrations"); + sliders[EAT_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "eating neighborhood size"); + sliders[EAT_NBHD_SIZE ].formatter(&exp_formatter); + sliders[SORT_RADIUS ] = Slider(0., 1., 0, 0.65, "sort radius"); + sliders[SORT_RADIUS ].formatter(&exp_formatter); + sliders[FUSE_RADIUS ] = Slider(0., 1., 0, 0.65, "fuse radius"); + sliders[FUSE_RADIUS ].formatter(&exp_formatter); + sliders[INTERPOLATE_RADIUS ] = Slider(0., 1., 0, 0.65, "intrepolate precision"); + sliders[INTERPOLATE_RADIUS ].formatter(&exp_formatter); + sliders[CURVATURE_NBHD_SIZE] = Slider(0., 1., 0, 0.65, "curvature nbhd size"); + sliders[CURVATURE_NBHD_SIZE].formatter(&exp_formatter); + sliders[POINT_CHOOSER ] = Slider(0, 100, 0, 50, "Point chooser(%)"); + + toggles = std::vector<Toggle>(TOTAL_TOGGLES, Toggle("",true)); + toggles[DRAW_MOUSES] = Toggle("Draw mouses",true); + toggles[DRAW_IMPROVED_MOUSES] = Toggle("Draw new mouses",true); + toggles[DRAW_STROKE] = Toggle("Draw stroke",true); + toggles[TIGHTEN_USE_CIRCLE] = Toggle("Tighten: use circle",false); + toggles[SORT_BIS ] = Toggle("Sort: bis",false); + } + + private: + typedef void (SketchFitterToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); + draw_func_t draw_f; + typedef void (SketchFitterToy::* fit_func_t) (); + fit_func_t fit_f; + bool set_common_control_geometry; + bool set_control_geometry; + std::vector<Toggle> toggles; + std::vector<Slider> sliders; + double m_width, m_height, m_length; + +}; // end class SketchFitterToy + + +const char* SketchFitterToy::menu_items[] = +{ + "show this menu", + "tighten", + "eat points step by step", + "tighten + eat", + "tighten + sort + fuse", + "curvature", + "numerical", + "help", +}; + +const char SketchFitterToy::keys[] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' +}; + +int main(int argc, char **argv) { + init(argc, argv, new SketchFitterToy); + return 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:encoding = utf-8:textwidth = 99 : diff --git a/src/toys/smash-intersector.cpp b/src/toys/smash-intersector.cpp new file mode 100644 index 0000000..b01acfb --- /dev/null +++ b/src/toys/smash-intersector.cpp @@ -0,0 +1,583 @@ +/* + * Diffeomorphism-based intersector: given two curves + * M(t)=(x(t),y(t)) and N(u)=(X(u),Y(u)) + * and supposing M is a graph over the x-axis, we compute y(x) and solve + * Y(u) - y(X(u)) = 0 + * to get the intersections of the two curves... + * + * Notice the result can be far from intuitive because of the choice we have + * to make to consider a curve as a graph over x or y. For instance the two + * branches of xy=eps are never close from this point of view (!)... + * + * Authors: + * J.-F. Barraud <jfbarraud at gmail.com> + * Copyright 2010 authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + + +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/path.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <cstdlib> +#include <cstdio> +#include <set> +#include <vector> +#include <algorithm> + +#include <2geom/orphan-code/intersection-by-smashing.h> +#include "../2geom/orphan-code/intersection-by-smashing.cpp" + +using namespace Geom; + +#define VERBOSE 0 + +static double exp_rescale(double x){ return ::pow(10, x);} +std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));} + + + +#if 0 +//useless here; +Piecewise<D2<SBasis> > linearizeCusps( D2<SBasis> f, double tol){ + D2<SBasis> df = derivative( f ); + std::vector<Interval> xdoms = level_set( df[X], 0., tol); + std::vector<Interval> ydoms = level_set( df[Y], 0., tol); + std::vector<Interval> doms; + //TODO: use order!! + for ( unsigned i=0; i<xdoms.size(); i++ ){ + OptInterval inter = xdoms[i]; + for ( unsigned j=0; j<ydoms.size(); j++ ){ + inter &= ydoms[j]; + } + if (inter) { + doms.push_back( *inter ); + } + } + Piecewise<D2<SBasis> > result; + if (doms.size() == 0 ) return Piecewise<D2<SBasis> >(f); + if (doms[0].min() > 0 ){ + result.cuts.push_back( 0 ); + result.cuts.push_back( doms[0].min() ); + result.segs.push_back( portion( f, Interval( 0, doms[0].min() ) ) ); + } + for ( unsigned i=0; i<doms.size(); i++ ){ + Point a = result.segs.back().at1(); + Point b = f.valueAt( doms[i].middle() ); + Point c = f.valueAt( doms[i].max() ); + result.cuts.push_back( doms[i].middle() ); + result.segs.push_back( D2<SBasis>( Linear( a[X], b[X] ), Linear( a[Y], b[Y] ) ) ); + result.cuts.push_back( doms[i].max() ); + result.segs.push_back( D2<SBasis>( Linear( b[X], c[X] ), Linear( b[Y], c[Y] ) ) ); + double t = ( i+1 == doms.size() )? 1 : doms[i+1].min(); + result.cuts.push_back( t ); + result.segs.push_back( portion( f, Interval( doms[i].max(), t ) ) ); + } + return result; +} +#endif + +#if 0 +/* Computes the intersection of two sets given as (ordered) union intervals. + */ +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; +} + +/* Computes the top and bottom boundaries of the L_\infty neighborhood + * of a curve. The curve is supposed to be a graph over the x-axis. + */ +void computeLinfinityNeighborhood( D2<SBasis > const &f, double tol, D2<Piecewise<SBasis> > &topside, D2<Piecewise<SBasis> > &botside ){ + double signx = ( f[X].at0() > f[X].at1() )? -1 : 1; + double signy = ( f[Y].at0() > f[Y].at1() )? -1 : 1; + + Piecewise<D2<SBasis> > top, bot; + top = Piecewise<D2<SBasis> > (f); + top.cuts.insert( top.cuts.end(), 2); + top.segs.insert( top.segs.end(), D2<SBasis>(Linear( f[X].at1(), f[X].at1()+2*tol*signx), + Linear( f[Y].at1() )) ); + bot = Piecewise<D2<SBasis> >(f); + bot.cuts.insert( bot.cuts.begin(), - 1 ); + bot.segs.insert( bot.segs.begin(), D2<SBasis>(Linear( f[X].at0()-2*tol*signx, f[X].at0()), + Linear( f[Y].at0() )) ); + top += Point(-tol*signx, tol); + bot += Point( tol*signx, -tol); + + if ( signy < 0 ){ + swap( top, bot ); + top += Point( 0, 2*tol); + bot += Point( 0, -2*tol); + } + topside = make_cuts_independent(top); + botside = make_cuts_independent(bot); +} + + +/*Compute top and bottom boundaries of the L^infty nbhd of the graph of a *monotonic* function f. + * if f is increasing, it is given by [f(t-tol)-tol, f(t+tol)+tol]. + * if not, it is [f(t+tol)-tol, f(t-tol)+tol]. + */ +void computeLinfinityNeighborhood( Piecewise<SBasis> const &f, double tol, Piecewise<SBasis> &top, Piecewise<SBasis> &bot){ + top = f + tol; + top.offsetDomain( - tol ); + top.cuts.insert( top.cuts.end(), f.domain().max() + tol); + top.segs.insert( top.segs.end(), SBasis(Linear( f.lastValue() + tol )) ); + + bot = f - tol; + bot.offsetDomain( tol ); + bot.cuts.insert( bot.cuts.begin(), f.domain().min() - tol); + bot.segs.insert( bot.segs.begin(), SBasis(Linear( f.firstValue() - tol )) ); + + if ( f.firstValue() > f.lastValue() ){ + swap( top, bot ); + top += 2*tol; + bot -= 2*tol; + } +} + +std::vector<Interval> level_set( D2<SBasis> const &f, Rect region){ + std::vector<Interval> x_in_reg = level_set( f[X], region[X] ); + std::vector<Interval> y_in_reg = level_set( f[Y], region[Y] ); + std::vector<Interval> result = intersect ( x_in_reg, y_in_reg ); + return result; +} + +void prolongateByConstants( Piecewise<SBasis> &f, double paddle_width ){ + if ( f.size() == 0 ) return; //do we have a covention about the domain of empty pwsb? + f.cuts.insert( f.cuts.begin(), f.cuts.front() - paddle_width ); + f.segs.insert( f.segs.begin(), SBasis( f.segs.front().at0() ) ); + f.cuts.insert( f.cuts.end(), f.cuts.back() + paddle_width ); + f.segs.insert( f.segs.end(), SBasis( f.segs.back().at1() ) ); +} + + + +/* Returns the intervals over which the curve keeps its slope + * in one of the 8 sectors delimited by x=0, y=0, y=x, y=-x. + * WARNING: both curves are supposed to be a graphs over x or y axis, + * and the smaller the slopes the better (typically <=45°). + */ +std::vector<std::pair<Interval, Interval> > smash_intersect( D2<SBasis> const &a, D2<SBasis> const &b, + double tol, cairo_t *cr , bool draw_more_stuff=false ){ + + std::vector<std::pair<Interval, Interval> > res; + + // a and b or X and Y may have to be exchanged, so make local copies. + D2<SBasis> aa = a; + D2<SBasis> bb = b; + bool swapresult = false; + bool swapcoord = false;//debug only! + + if ( draw_more_stuff ){ + cairo_set_line_width (cr, 3); + cairo_set_source_rgba(cr, .5, .9, .7, 1 ); + cairo_d2_sb(cr, aa); + cairo_d2_sb(cr, bb); + cairo_stroke(cr); + } + +#if 1 + //if the (enlarged) bounding boxes don't intersect, stop. + if ( !draw_more_stuff ){ + OptRect abounds = bounds_fast( a ); + OptRect bbounds = bounds_fast( b ); + if ( !abounds || !bbounds ) return res; + abounds->expandBy(tol); + if ( !(abounds->intersects(*bbounds))){ + return res; + } + } +#endif + + //Choose the best curve to be re-parametrized by x or y values. + OptRect dabounds = bounds_exact(derivative(a)); + OptRect dbbounds = bounds_exact(derivative(b)); + if ( dbbounds->min().length() > dabounds->min().length() ){ + aa=b; + bb=a; + swap( dabounds, dbbounds ); + swapresult = true; + } + + //Choose the best coordinate to use as new parameter + double dxmin = std::min( abs((*dabounds)[X].max()), abs((*dabounds)[X].min()) ); + double dymin = std::min( abs((*dabounds)[Y].max()), abs((*dabounds)[Y].min()) ); + if ( (*dabounds)[X].max()*(*dabounds)[X].min() < 0 ) dxmin=0; + if ( (*dabounds)[Y].max()*(*dabounds)[Y].min() < 0 ) dymin=0; + assert (dxmin>=0 && dymin>=0); + + if (dxmin < dymin) { + aa = D2<SBasis>( aa[Y], aa[X] ); + bb = D2<SBasis>( bb[Y], bb[X] ); + swapcoord = true; + } + + //re-parametrize aa by the value of x. + Interval x_range_strict( aa[X].at0(), aa[X].at1() ); + Piecewise<SBasis> y_of_x = pw_compose_inverse(aa[Y],aa[X], 2, 1e-5); + + //Compute top and bottom boundaries of the L^infty nbhd of aa. + Piecewise<SBasis> top_ay, bot_ay; + computeLinfinityNeighborhood( y_of_x, tol, top_ay, bot_ay); + + Interval ax_range = top_ay.domain();//i.e. aa[X] domain ewpanded by tol. + + if ( draw_more_stuff ){ + Piecewise<SBasis> dbg_x( SBasis( Linear( top_ay.domain().min(), top_ay.domain().max() ) ) ); + dbg_x.setDomain( top_ay.domain() ); + D2<Piecewise<SBasis> > dbg_side ( Piecewise<SBasis>( SBasis( Linear( 0 ) ) ), + Piecewise<SBasis>( SBasis( Linear( 0, 2*tol) ) ) ); + + D2<Piecewise<SBasis> > dbg_rgn; + unsigned h = ( swapcoord ) ? Y : X; + dbg_rgn[h].concat ( dbg_x ); + dbg_rgn[h].concat ( dbg_side[X] + dbg_x.lastValue() ); + dbg_rgn[h].concat ( reverse(dbg_x) ); + dbg_rgn[h].concat ( dbg_side[X] + dbg_x.firstValue() ); + + dbg_rgn[1-h].concat ( bot_ay ); + dbg_rgn[1-h].concat ( dbg_side[Y] + bot_ay.lastValue() ); + dbg_rgn[1-h].concat ( reverse(top_ay) ); + dbg_rgn[1-h].concat ( reverse( dbg_side[Y] ) + bot_ay.firstValue() ); + + cairo_set_line_width (cr, 1.); + cairo_set_source_rgba(cr, 0., 1., 0., .75 ); + cairo_d2_pw_sb(cr, dbg_rgn ); + cairo_stroke(cr); + + D2<SBasis> bbb = bb; + if ( swapcoord ) swap( bbb[X], bbb[Y] ); + //Piecewise<D2<SBasis> > dbg_rgnB = neighborhood( bbb, tol ); + D2<Piecewise<SBasis> > dbg_topB, dbg_botB; + computeLinfinityNeighborhood( bbb, tol, dbg_topB, dbg_botB ); + cairo_set_line_width (cr, 1.); + cairo_set_source_rgba(cr, .2, 8., .2, .4 ); +// cairo_pw_d2_sb(cr, dbg_rgnB ); + cairo_d2_pw_sb(cr, dbg_topB ); + cairo_d2_pw_sb(cr, dbg_botB ); + cairo_stroke(cr); + } + + std::vector<Interval> bx_in_ax_range = level_set(bb[X], ax_range ); + + // find times when bb is in the neighborhood of aa. + std::vector<Interval> tbs; + for (unsigned i=0; i<bx_in_ax_range.size(); i++){ + D2<Piecewise<SBasis> > bb_in; + bb_in[X] = Piecewise<SBasis> ( portion( bb[X], bx_in_ax_range[i] ) ); + bb_in[Y] = Piecewise<SBasis> ( portion( bb[Y], bx_in_ax_range[i]) ); + bb_in[X].setDomain( bx_in_ax_range[i] ); + bb_in[Y].setDomain( bx_in_ax_range[i] ); + + Piecewise<SBasis> h; + Interval level; + h = bb_in[Y] - compose( top_ay, bb_in[X] ); + level = Interval( -infinity(), 0 ); + std::vector<Interval> rts_lo = level_set( h, level); + h = bb_in[Y] - compose( bot_ay, bb_in[X] ); + level = Interval( 0, infinity()); + std::vector<Interval> rts_hi = level_set( h, level); + + std::vector<Interval> rts = intersect( rts_lo, rts_hi ); + tbs.insert(tbs.end(), rts.begin(), rts.end() ); + } + + std::vector<std::pair<Interval, Interval> > result(tbs.size(),std::pair<Interval,Interval>()); + + /* for each solution I, find times when aa is in the neighborhood of bb(I). + * (Note: the preimage of bb[X](I) by aa[X], enlarged by tol, is a good approximation of this: + * it would give points in the 2*tol neighborhood of bb (if the slope of aa is never more than 1). + * + faster computation. + * - implies little jumps depending on the subdivision of the input curve into monotonic pieces + * and on the choice of preferred axis. If noticeable, these jumps would feel random to the user :-( + */ + for (unsigned j=0; j<tbs.size(); j++){ + result[j].second = tbs[j]; + std::vector<Interval> tas; + Piecewise<SBasis> fat_y_of_x = y_of_x; + prolongateByConstants( fat_y_of_x, 100*(1+tol) ); + + D2<Piecewise<SBasis> > top_b, bot_b; + D2<SBasis> bbj = portion( bb, tbs[j] ); + computeLinfinityNeighborhood( bbj, tol, top_b, bot_b ); + + Piecewise<SBasis> h; + Interval level; + h = top_b[Y] - compose( fat_y_of_x, top_b[X] ); + level = Interval( +infinity(), 0 ); + std::vector<Interval> rts_top = level_set( h, level); + for (unsigned idx=0; idx < rts_top.size(); idx++){ + rts_top[idx] = Interval( top_b[X].valueAt( rts_top[idx].min() ), + top_b[X].valueAt( rts_top[idx].max() ) ); + } + assert( rts_top.size() == 1 ); + + h = bot_b[Y] - compose( fat_y_of_x, bot_b[X] ); + level = Interval( 0, -infinity()); + std::vector<Interval> rts_bot = level_set( h, level); + for (unsigned idx=0; idx < rts_bot.size(); idx++){ + rts_bot[idx] = Interval( bot_b[X].valueAt( rts_bot[idx].min() ), + bot_b[X].valueAt( rts_bot[idx].max() ) ); + } + assert( rts_bot.size() == 1 ); + +#if VERBOSE + printf("range(aa[X]) = [%f, %f];\n", y_of_x.domain().min(), y_of_x.domain().max()); + printf("range(bbj[X]) = [%f, %f]; tol= %f\n", bbj[X].at0(), bbj[X].at1(), tol); + + printf("rts_top = "); + for (unsigned dbgi=0; dbgi<rts_top.size(); dbgi++){ + printf("[%f,%f]U", rts_top[dbgi].min(), rts_top[dbgi].max() ); + } + printf("\n"); + printf("rts_bot = "); + for (unsigned dbgi=0; dbgi<rts_bot.size(); dbgi++){ + printf("[%f,%f]U", rts_bot[dbgi].min(), rts_bot[dbgi].max() ); + } + printf("\n"); +#endif + rts_top = intersect( rts_top, rts_bot ); +#if VERBOSE + printf("intersection = "); + for (unsigned dbgi=0; dbgi<rts_top.size(); dbgi++){ + printf("[%f,%f]U", rts_top[dbgi].min(), rts_top[dbgi].max() ); + } + printf("\n\n"); + + if (rts_top.size() != 1){ + printf("!!!!!!!!!!!!!!!!!!!!!!\n!!!!!!!!!!!!!!!!!!!!!!\n"); + rts_top[0].unionWith( rts_top[1] ); + assert( false ); + } +#endif + assert (rts_top.size() == 1); + Interval x_dom = rts_top[0]; + + if ( x_dom.max() <= x_range_strict.min() ){ + tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 0 : 1 ) ); + }else if ( x_dom.min() >= x_range_strict.max() ){ + tas.push_back( Interval ( ( aa[X].at0() < aa[X].at1() ) ? 1 : 0 ) ); + }else{ + tas = level_set(aa[X], x_dom ); + } + +#if VERBOSE + if ( tas.size() != 1 ){ + printf("Error: preimage of [%f, %f] by x:[0,1]->[%f, %f] is ", + x_dom.min(), x_dom.max(), x_range_strict.min(), x_range_strict.max()); + if ( tas.size() == 0 ){ + printf( "empty.\n"); + }else{ + printf("\n [%f,%f]", tas[0].min(), tas[0].max() ); + for (unsigned toto=1; toto<tas.size(); toto++){ + printf(" U [%f,%f]", tas[toto].min(), tas[toto].max() ); + } + } + } +#endif + assert( tas.size()==1 ); + result[j].first = tas.front(); + } + + if (swapresult) { + for ( unsigned i=0; i<result.size(); i++){ + Interval temp = result[i].first; + result[i].first = result[i].second; + result[i].second = temp; + } + } + return result; +} + +#endif + +class Intersector : public Toy +{ + private: + void draw( cairo_t *cr, std::ostringstream *notify, + int width, int height, bool save, std::ostringstream *timer_stream) override + { + double tol = exp_rescale(slider.value()); + D2<SBasis> A = handles_to_sbasis(psh.pts.begin(), A_bez_ord-1); + D2<SBasis> B = handles_to_sbasis(psh.pts.begin()+A_bez_ord, B_bez_ord-1); + cairo_set_line_width (cr, .8); + cairo_set_source_rgba(cr,0.,0.,0.,.6); + cairo_d2_sb(cr, A); + cairo_d2_sb(cr, B); + cairo_stroke(cr); + + Rect tolbytol( anchor.pos, anchor.pos ); + tolbytol.expandBy( tol ); + cairo_rectangle(cr, tolbytol); + cairo_stroke(cr); +/* + Piecewise<D2<SBasis> > smthA = linearizeCusps(A+Point(0,10), tol); + cairo_set_line_width (cr, 1.); + cairo_set_source_rgba(cr, 1., 0., 1., 1. ); + cairo_pw_d2_sb(cr, smthA); + cairo_stroke(cr); +*/ + + std::vector<Interval> Acuts = monotonicSplit(A); + std::vector<Interval> Bcuts = monotonicSplit(B); + +#if 0 + for (unsigned i=0; i<Acuts.size(); i++){ + D2<SBasis> Ai = portion( A, Acuts[i]); + cairo_set_line_width (cr, .2); + cairo_set_source_rgba(cr, 0., 0., 0., 1. ); + draw_cross(cr, Ai.at0()); + cairo_stroke(cr); + for (unsigned j=0; j<Bcuts.size(); j++){ + std::vector<std::pair<Interval, Interval> > my_intersections; + D2<SBasis> Bj = portion( B, Bcuts[j]); + cairo_set_line_width (cr, .2); + cairo_set_source_rgba(cr, 0., 0., 0., 1. ); + draw_cross(cr, Bj.at0()); + cairo_stroke(cr); + } + } +#endif + + std::vector<SmashIntersection> my_intersections; + my_intersections = smash_intersect( A, B, tol ); + + for (auto & my_intersection : my_intersections){ + cairo_set_line_width (cr, 2.5); + cairo_set_source_rgba(cr, 1., 0., 0., .8 ); + cairo_d2_sb(cr, portion( A, my_intersection.times[X])); + cairo_stroke(cr); + cairo_set_line_width (cr, 2.5); + cairo_set_source_rgba(cr, 0., 0., 1., .8 ); + cairo_d2_sb(cr, portion( B, my_intersection.times[Y])); + cairo_stroke(cr); + } +#if 0 + + unsigned apiece( slidera.value()/100. * Acuts.size() ); + unsigned bpiece( sliderb.value()/100. * Bcuts.size() ); + + + for (unsigned i=0; i<Acuts.size(); i++){ + D2<SBasis> Ai = portion( A, Acuts[i]); + for (unsigned j=0; j<Bcuts.size(); j++){ + if ( toggle.on && (i != apiece || j != bpiece) ) continue; + + std::vector<SmashIntersection> my_intersections; + D2<SBasis> Bj = portion( B, Bcuts[j]); + bool draw_more = toggle.on && i == apiece && j == bpiece; +// my_intersections = smash_intersect( Ai, Bj, tol, cr, draw_more ); + my_intersections = monotonic_smash_intersect( Ai, Bj, tol ); + + for (unsigned k=0; k<my_intersections.size(); k++){ + cairo_set_line_width (cr, 2.5); + cairo_set_source_rgba(cr, 1., 0., 0., .8 ); + cairo_d2_sb(cr, portion( Ai, my_intersections[k].times[X])); + cairo_stroke(cr); + cairo_set_line_width (cr, 2.5); + cairo_set_source_rgba(cr, 0., 0., 1., .8 ); + cairo_d2_sb(cr, portion( Bj, my_intersections[k].times[Y])); + cairo_stroke(cr); + } + } + } +#endif + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + Intersector(unsigned int _A_bez_ord, unsigned int _B_bez_ord) + : A_bez_ord(_A_bez_ord), B_bez_ord(_B_bez_ord) + { + unsigned int total_handles = A_bez_ord + B_bez_ord; + for ( unsigned int i = 0; i < total_handles; ++i ) + psh.push_back(Geom::Point(uniform()*400, uniform()*400)); + handles.push_back(&psh); + slider = Slider(-4, 2, 0, 1.2, "tolerance"); + slider.geometry(Point(30, 20), 250); + slider.formatter(&exp_formatter); + handles.push_back(&slider); + slidera = Slider(0, 100, 1, 0., "piece on A"); + slidera.geometry(Point(300, 50), 250); + handles.push_back(&slidera); + sliderb = Slider(0, 100, 1, 0., "piece on B"); + sliderb.geometry(Point(300, 80), 250); + handles.push_back(&sliderb); + toggle = Toggle( Rect(Point(300,10), Point(440,30)), "Piece by piece", false ); + handles.push_back(&toggle); + anchor = PointHandle ( Point(100, 100 ) ); + handles.push_back(&anchor); + } + + private: + unsigned int A_bez_ord; + unsigned int B_bez_ord; + PointSetHandle psh; + PointHandle anchor; + Slider slider,slidera,sliderb; + Toggle toggle; +}; + + +int main(int argc, char **argv) +{ + unsigned int A_bez_ord=4; + unsigned int B_bez_ord=4; + if(argc > 2) + sscanf(argv[2], "%d", &B_bez_ord); + if(argc > 1) + sscanf(argv[1], "%d", &A_bez_ord); + + init( argc, argv, new Intersector(A_bez_ord, B_bez_ord)); + return 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/toys/squiggles.cpp b/src/toys/squiggles.cpp new file mode 100644 index 0000000..eef33ad --- /dev/null +++ b/src/toys/squiggles.cpp @@ -0,0 +1,216 @@ +#include <2geom/piecewise.h> +#include <2geom/sbasis.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-math.h> +#include <2geom/sbasis-geometric.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +#include <vector> + +#define NB_CTL_PTS 6 +#define K_SCALE .002 + +using namespace Geom; +using namespace std; + +void cairo_pw(cairo_t *cr, Piecewise<SBasis> p) { + for(unsigned i = 0; i < p.size(); i++) { + D2<SBasis> B; + B[0] = Linear(p.cuts[i], p.cuts[i+1]); + B[1] = Linear(150) + p[i]; + cairo_d2_sb(cr, B); + } +} + +void cairo_horiz(cairo_t *cr, double y, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, i, y); + cairo_rel_line_to(cr, 0, 10); + } +} + +void cairo_vert(cairo_t *cr, double x, vector<double> p) { + for(double i : p) { + cairo_move_to(cr, x, i); + cairo_rel_line_to(cr, 10, 0); + } +} + +/* +Piecewise<SBasis> interpolate(std::vector<double> values, std::vector<double> times){ + assert ( values.size() == times.size() ); + if ( values.size() == 0 ) return Piecewise<SBasis>(); + if ( values.size() == 1 ) return Piecewise<SBasis>(values[0]);//what about time?? + + SBasis bump_in = Linear(0,1);//Enough for piecewise linear interpolation. + //bump_in.push_back(Linear(-1,1));//uncomment for C^1 interpolation + SBasis bump_out = Linear(1,0); + //bump_out.push_back(Linear(1,-1)); + + 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; +} +*/ + +//#include <toys/pwsbhandle.cpp // FIXME: This looks like it may give problems later, (including a .cpp file) + +class Squiggles: public Toy { + PointSetHandle hand; + unsigned current_ctl_pt; + Point current_pos; + Point current_dir; + std::vector<double> curvatures; + std::vector<double> times; + Piecewise<D2<SBasis> > curve; + double tot_length; + int mode; //0=set curvature, 1=set curv.+rotation, 2=translate, 3=slide time. + + void mouse_moved(GdkEventMotion* e) override{ + mode = 0; + if((e->state & (GDK_SHIFT_MASK)) && + (e->state & (GDK_CONTROL_MASK))) { + mode = 3; + }else if(e->state & (GDK_CONTROL_MASK)) { + mode = 1; + }else if(e->state & (GDK_SHIFT_MASK)) { + mode = 2; + } + Toy::mouse_moved(e); + } + + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0., 1); + cairo_set_line_width (cr, 1); + + *notify << "Drag to set curvature,\n"; + *notify << "SHIFT-Drag to move curve,\n"; + *notify << "CTRL-Drag to rotate,\n"; + *notify << "SHIFT-CTRL-Drag to slide handles."; + //Get user input + if (mouse_down && selected) { + for(unsigned i = 0; i < handles.size(); i++) { + if(selected == handles[i]){ + current_ctl_pt = i; + break; + } + } + double time = times[current_ctl_pt]; + current_pos = curve.valueAt(time); + current_dir = derivative(curve).valueAt(time);//*This should be a unit vector!* + Point hdle = dynamic_cast<PointHandle*>(handles[current_ctl_pt])->pos; + if (mode == 0){ + curvatures[current_ctl_pt] = cross(hdle - current_pos,current_dir)*K_SCALE; + }else if (mode == 1){//Rotate + double sign = ( curvatures[current_ctl_pt]>=0 ? 1 : -1 ); + //curvatures[current_ctl_pt] = sign*L2(hdle - current_pos)*K_SCALE; + current_dir = -sign*unit_vector(rot90(hdle - current_pos)); + }else if (mode == 2){//Translate + Point old_pos = current_pos + curvatures[current_ctl_pt]/K_SCALE*rot90(current_dir); + current_pos += hdle - old_pos; + curve += hdle - old_pos; + }else if (mode == 3){//Slide time + Point old_pos = current_pos + curvatures[current_ctl_pt]/K_SCALE*rot90(current_dir); + double delta = dot(hdle - old_pos,current_dir); + double epsilon = 2; + if (current_ctl_pt>0 && times[current_ctl_pt]+delta < times[current_ctl_pt-1]+epsilon){ + delta = times[current_ctl_pt-1] + epsilon - times[current_ctl_pt]; + } + if (current_ctl_pt<times.size()-1 && times[current_ctl_pt]+delta > times[current_ctl_pt+1]-epsilon){ + delta = times[current_ctl_pt+1] - epsilon - times[current_ctl_pt]; + } + times[current_ctl_pt] += delta; + current_pos += delta*current_dir; + } + } + + //Compute new curve + + Piecewise<SBasis> curvature = interpolate( times, curvatures , 1); + Piecewise<SBasis> alpha = integral(curvature); + Piecewise<D2<SBasis> > v = sectionize(tan2(alpha)); + curve = integral(v)+Point(100,100); + + //transform to keep current point in place + double time = times[current_ctl_pt]; + Point new_pos = curve.valueAt(time); + Point new_dir = v.valueAt(time); + Affine mat1 = Affine( new_dir[X], new_dir[Y], -new_dir[Y], new_dir[X], new_pos[X], new_pos[Y]); + Affine mat2 = Affine(current_dir[X],current_dir[Y],-current_dir[Y],current_dir[X],current_pos[X],current_pos[Y]); + mat1 = mat1.inverse()*mat2; + curve = curve*mat1; + v = v*mat1.withoutTranslation(); + + //update handles + cairo_save(cr); + double dashes[2] = {3, 2}; + cairo_set_dash(cr, dashes, 2, 0); + cairo_set_line_width(cr, .5); + cairo_set_source_rgba (cr, 0., 0., 0.5, 1); + for(unsigned i = 0; i < NB_CTL_PTS; i++) { + Point m = curve.valueAt(times[i]); + dynamic_cast<PointHandle*>(handles[i])->pos = m + + curvatures[i]/K_SCALE*rot90(v.valueAt(times[i])); + draw_handle(cr, m); + cairo_move_to(cr, m); + cairo_line_to(cr, dynamic_cast<PointHandle*>(handles[i])->pos); + } + +#if 0 + D2<Piecewise<SBasis> > graphe; + graphe[X] = Piecewise<SBasis>(Linear(100,300)); + graphe[Y] = -curvature/K_SCALE+400; + graphe[X].setDomain(graphe[Y].domain()); + cairo_d2_pw_sb(cr, graphe); +#endif + + cairo_stroke(cr); + cairo_restore(cr); + + cairo_pw_d2_sb(cr, curve); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + bool should_draw_numbers() override { return false; } + +public: + Squiggles () { + current_ctl_pt = 0; + current_dir = Point(1,0); + current_pos = Point(100,100); + tot_length = 300; + + curve = Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(100.,300.),SBasis(100.,100.))); + for(unsigned i = 0; i < NB_CTL_PTS; i++) { + curvatures.push_back(0); + times.push_back(i*tot_length/(NB_CTL_PTS-1)); + PointHandle *pt_hdle = new PointHandle(Geom::Point(100+i*tot_length/(NB_CTL_PTS-1), 100.)); + handles.push_back(pt_hdle); + } + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Squiggles()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/star-gap.hand b/src/toys/star-gap.hand new file mode 100644 index 0000000..6506970 --- /dev/null +++ b/src/toys/star-gap.hand @@ -0,0 +1 @@ +300.000000 300.000000 diff --git a/src/toys/svgd/2rect.svgd b/src/toys/svgd/2rect.svgd new file mode 100644 index 0000000..2d8cdc2 --- /dev/null +++ b/src/toys/svgd/2rect.svgd @@ -0,0 +1 @@ +M 111.42857,92.362183 L 494.28572,92.362183 L 494.28572,275.21933 L 111.42857,275.21933 L 111.42857,92.362183 zM 217.14285,192.36218 L 660,192.36218 L 660,389.50504 L 217.14285,389.50504 L 217.14285,192.36218 z
\ No newline at end of file diff --git a/src/toys/svgd/4rect.svgd b/src/toys/svgd/4rect.svgd new file mode 100644 index 0000000..af15296 --- /dev/null +++ b/src/toys/svgd/4rect.svgd @@ -0,0 +1 @@ +M 92.73692,69.488149 L 412.73692,69.488149 L 412.73692,266.63103 L 92.73692,266.63103 L 92.73692,69.488149 z M 430.12021,178.5218 L 564.40593,178.5218 L 564.40593,275.66466 L 430.12021,275.66466 L 430.12021,178.5218 z M 430.12021,286.66478 L 564.40593,286.66478 L 564.40593,383.80764 L 430.12021,383.80764 L 430.12021,286.66478 z M 430.12021,69.488149 L 564.40593,69.488149 L 564.40593,166.63101 L 430.12021,166.63101 L 430.12021,69.488149 z
\ No newline at end of file diff --git a/src/toys/svgd/ant.svgd b/src/toys/svgd/ant.svgd new file mode 100644 index 0000000..a92d01a --- /dev/null +++ b/src/toys/svgd/ant.svgd @@ -0,0 +1 @@ +M 60.03,2.56 C 58.71,2.43 57.16,15.65 57.09,16.44 C 56.11,27.39 60.84,32.53 60.56,34.03 C 60.29,35.53 60.27,41.43 64.63,47.53 C 63.29,48.91 62.08,50.27 61.0,51.63 C 58.67,47.53 55.81,43.15 53.78,41.13 C 51.65,38.99 42.4,33.78 40.5,33.09 C 39.83,32.85 31.78,24.38 30.88,25.28 C 29.97,26.19 37.86,34.75 38.66,34.63 C 41.17,38.09 51.17,45.0 51.91,46.06 C 52.64,47.13 57.55,56.07 57.59,56.13 C 55.72,59.04 55.17,61.1 54.47,63.59 C 51.13,60.05 41.55,57.28 34.66,57.28 C 28.94,57.28 19.55,57.14 17.81,57.44 C 11.32,58.55 0.64,71.64 2.13,73.13 C 3.3,74.3 11.14,60.86 17.63,59.75 C 19.5,59.43 34.08,62.18 36.25,62.03 C 38.42,61.88 45.45,65.59 49.38,66.16 C 45.65,65.36 41.81,66.1 38.66,69.25 C 26.96,80.95 28.35,91.77 32.88,96.41 C 32.88,96.41 32.87,96.43 32.88,96.44 C 32.89,96.45 32.9,96.46 32.91,96.47 C 32.92,96.48 32.93,96.49 32.94,96.5 C 32.95,96.51 32.96,96.52 32.97,96.53 C 37.61,101.05 48.41,102.4 60.09,90.72 C 63.03,87.79 63.88,84.23 63.34,80.75 C 64.22,84.76 67.48,91.09 67.34,93.13 C 67.2,95.3 69.91,109.84 69.59,111.72 C 68.49,118.21 55.04,126.07 56.22,127.25 C 57.7,128.73 70.8,118.05 71.91,111.56 C 72.2,109.82 72.06,100.44 72.06,94.72 C 72.06,87.82 69.3,78.25 65.75,74.91 C 68.24,74.21 70.33,73.62 73.25,71.75 C 73.3,71.79 82.21,76.73 83.28,77.47 C 84.35,78.21 91.25,88.21 94.72,90.72 C 94.59,91.52 103.16,99.41 104.06,98.5 C 104.97,97.6 96.49,89.51 96.25,88.84 C 95.57,86.94 90.39,77.73 88.25,75.59 C 86.22,73.57 81.81,70.68 77.72,68.34 C 79.07,67.27 80.46,66.09 81.84,64.75 C 87.95,69.1 93.85,69.09 95.34,68.81 C 96.84,68.54 101.98,73.27 112.94,72.28 C 113.72,72.21 126.95,70.66 126.81,69.34 C 126.65,67.79 114.41,69.33 113.59,69.72 C 111.42,68.99 99.94,66.69 98.78,66.19 C 97.62,65.68 91.92,62.98 84.78,61.19 C 86.71,57.89 86.76,54.39 85.59,51.34 C 88.51,54.25 91.2,55.8 94.41,52.59 C 97.92,49.08 100.48,45.17 101.94,41.44 C 106.66,42.93 110.64,43.45 111.56,43.88 C 112.49,44.3 113.68,43.63 114.75,40.0 C 116.5,34.03 117.56,28.4 116.88,28.09 C 116.19,27.79 110.98,37.62 111.34,41.22 C 108.58,39.29 106.28,38.51 102.97,37.91 C 103.76,34.04 103.18,30.66 101.03,28.44 C 101.03,28.43 101.04,28.41 101.03,28.41 C 101.01,28.39 100.99,28.36 100.97,28.34 C 98.75,26.18 95.31,25.58 91.44,26.38 C 90.83,23.07 90.08,20.76 88.16,18.0 C 91.75,18.37 101.59,13.18 101.28,12.5 C 100.97,11.82 95.35,12.84 89.38,14.59 C 85.74,15.66 85.07,16.89 85.5,17.81 C 85.93,18.74 86.45,22.72 87.94,27.44 C 84.21,28.89 80.29,31.46 76.78,34.97 C 73.58,38.17 75.12,40.87 78.03,43.78 C 74.98,42.62 71.48,42.67 68.19,44.59 C 66.4,37.45 63.66,31.75 63.16,30.59 C 62.65,29.43 60.39,17.92 59.66,15.75 C 60.05,14.94 61.59,2.72 60.03,2.56 z
\ No newline at end of file diff --git a/src/toys/svgd/arcs.svgd b/src/toys/svgd/arcs.svgd new file mode 100644 index 0000000..8a69290 --- /dev/null +++ b/src/toys/svgd/arcs.svgd @@ -0,0 +1 @@ +M50,50 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25 diff --git a/src/toys/svgd/banana.svgd b/src/toys/svgd/banana.svgd new file mode 100644 index 0000000..37a385e --- /dev/null +++ b/src/toys/svgd/banana.svgd @@ -0,0 +1 @@ +M 265.71429,158.07648 C 190.6809,177.20365 122.07669,234.71591 104.63056,312.25563 C 77.156999,399.83651 70.794763,500.27985 114.77866,583.69036 C 144.53063,634.44937 199.36676,669.20393 257.74302,675.53709 C 270.81001,702.04856 314.06907,671.3079 286.33697,654.18295 C 255.1688,600.99542 216.46623,548.3017 211.13947,484.83476 C 202.31209,421.03768 205.02251,351.85571 239.06833,295.30186 C 257.00587,263.44474 279.73092,234.67917 301.98406,205.79097 C 300.02661,180.33683 358.06043,134.08979 326.58199,129.37189 C 312.92978,127.95692 277.30454,151.73686 265.71429,158.07648 z diff --git a/src/toys/svgd/cat.svgd b/src/toys/svgd/cat.svgd new file mode 100644 index 0000000..557f13d --- /dev/null +++ b/src/toys/svgd/cat.svgd @@ -0,0 +1 @@ +M 153.04,308.88 C 67.07,342.82 57.97,453.92 130.83,506.41 C 245.3,553.77 426.19,542.77 445.18,392.24 C 446.78,351.09 426.76,300.40 382.24,291.24 M 338.24,282.24 C 285.72,273.19442 231.95477,285.06235 181.03,297.88016 M 308.58,391.49 C 327.09,380.94 348.61,376.39128 369.74,375.64253 M 118.31907,386.96814 C 134.17,392.253 150.03,397.53 165.88,402.82 M 206.65,418.65109 C 211.53912,410.76 255.11,411.00542 258.75668,418.65109 C 262.66,426.86 238.25,466.21 229.31,466.21866 C 220.88,466.21866 201.72358,426.62348 206.65,418.65109 z M 270.08,465.68 C 345.58,465.84 421.09,466.04 496.59,466.21 M 276.87,445.83 C 346.34,439.03 415.80,432.24 485.26,425.44 M 202.12,459.95 C 159.09,469.59 116.05,479.23 73.01,488.86 M 196.66,443.29 C 148.65,444.89 100.64,446.49 52.63,448.09769 M 205.04,480.06 C 221.65,480.065 238.26,480.06 254.87,480.06 M 95.66,350.69 C 91.13,315.21 86.60,279.72 82.07,244.23 C 133.11,252.96 156.26,279.88 190.80,312.19 M 338.03,289.53 C 360.08,241.80 427.37,231.27 452.87,221.21 C 450.44,257.57 440.48,289.67 428.64,323.51 M 196.76,290.19 C 201.26,252.13 242.44,234.63 272.70,219.28 C 417.48,158.60 583.94,152.87 733.23,201.03 C 853.85,234.84 966.06,304.90 931.20,445.90 C 850.68,533.28 734.22,618.52 613.28,632.83 C 572.66,633.17 536.29,602.83 578.98,592.96 C 664.47,600.17 743.16,553.80 807.85,502.83 C 831.31,480.70 855.70,455.39 863.54,423.18 M 742.13,302.52 C 696.70,300.78 645.69,308.71 616,347.22 C 578.46,386.94 583.35,447.38 587.45,497.88 C 584.24,518.69 614.07,549.65 579.61,545.01 C 533.59,551.10 487.13,554.76 441.40,562.48 C 397.18,588.94 462.84,613.68 489.95,609.31 C 514.10,610.80 538.52,610.13 562.38,605.92 M 211.18,531.90 C 167.07,548.50 234.68,593.43 260.49,576.39 C 326.21,562.34 388.19,535.46 447.53,504.56 C 489.62,482.86 548.71,461.63 557.51,412.45 C 563.23,380.53 543.26,346.99 536.94,314.20 C 532.43,276.36 513.09,264.81 475.14,265.25 M 450.71,504.40 C 494.88,523 543.39,525.09 590.45,519.27 M 100.78,485.00 C 87.77,505.40 71.18,555.90 114.73,539.59 C 131.70,538.38 137.48,525.86 139.17,510.47 diff --git a/src/toys/svgd/circle.svgd b/src/toys/svgd/circle.svgd new file mode 100644 index 0000000..fb276fa --- /dev/null +++ b/src/toys/svgd/circle.svgd @@ -0,0 +1 @@ +M 0 0 A 100 100 0 0 0 200 0 A 100 100 0 0 0 0 0 z
\ No newline at end of file diff --git a/src/toys/svgd/degenerate-line.svgd b/src/toys/svgd/degenerate-line.svgd new file mode 100644 index 0000000..832bb17 --- /dev/null +++ b/src/toys/svgd/degenerate-line.svgd @@ -0,0 +1 @@ +m 23,42 0,0 diff --git a/src/toys/svgd/diederik.svgd b/src/toys/svgd/diederik.svgd new file mode 100644 index 0000000..7e422ab --- /dev/null +++ b/src/toys/svgd/diederik.svgd @@ -0,0 +1 @@ +m 262.6037,35.824151 c 0,0 -92.64892,-187.405851 30,-149.999981 104.06976,31.739531 170,109.9999815 170,109.9999815 l -10,-59.9999905 c 0,0 40,79.99999 -40,79.99999 -80,0 -70,-129.999981 -70,-129.999981 l 50,0 C 435.13571,-131.5667 652.76275,126.44872 505.74322,108.05672 358.73876,89.666591 292.6037,-14.175849 292.6037,15.824151 c 0,30 -30,20 -30,20 z
\ No newline at end of file diff --git a/src/toys/svgd/diederik1.svgd b/src/toys/svgd/diederik1.svgd new file mode 100644 index 0000000..be181f5 --- /dev/null +++ b/src/toys/svgd/diederik1.svgd @@ -0,0 +1 @@ +m 262.6037,35.824151 c 0,0 -92.64892,-187.405851 30,-149.999981
\ No newline at end of file diff --git a/src/toys/svgd/double-move.svgd b/src/toys/svgd/double-move.svgd new file mode 100644 index 0000000..d2be8c3 --- /dev/null +++ b/src/toys/svgd/double-move.svgd @@ -0,0 +1 @@ +m 23,42 m 10,0 diff --git a/src/toys/svgd/ellipses.svgd b/src/toys/svgd/ellipses.svgd new file mode 100644 index 0000000..c0f3a2c --- /dev/null +++ b/src/toys/svgd/ellipses.svgd @@ -0,0 +1 @@ +M 310 640 A 290 320 0 0 1 310 0 A 290 320 0 0 1 310 640 z M 310 635 A 285 315 0 0 0 310 5 A 285 315 0 0 0 310 635 z M 310 630 A 280 310 0 0 1 310 10 A 280 310 0 0 1 310 630 z M 310 625 A 275 305 0 0 0 310 15 A 275 305 0 0 0 310 625 z M 310 620 A 270 300 0 0 1 310 20 A 270 300 0 0 1 310 620 z M 310 615 A 265 295 0 0 0 310 25 A 265 295 0 0 0 310 615 z M 310 610 A 260 290 0 0 1 310 30 A 260 290 0 0 1 310 610 z M 310 605 A 255 285 0 0 0 310 35 A 255 285 0 0 0 310 605 z M 310 600 A 250 280 0 0 1 310 40 A 250 280 0 0 1 310 600 z M 310 595 A 245 275 0 0 0 310 45 A 245 275 0 0 0 310 595 z M 310 590 A 240 270 0 0 1 310 50 A 240 270 0 0 1 310 590 z M 310 585 A 235 265 0 0 0 310 55 A 235 265 0 0 0 310 585 z M 310 580 A 230 260 0 0 1 310 60 A 230 260 0 0 1 310 580 z M 310 575 A 225 255 0 0 0 310 65 A 225 255 0 0 0 310 575 z M 310 570 A 220 250 0 0 1 310 70 A 220 250 0 0 1 310 570 z M 310 565 A 215 245 0 0 0 310 75 A 215 245 0 0 0 310 565 z M 310 560 A 210 240 0 0 1 310 80 A 210 240 0 0 1 310 560 z M 310 555 A 205 235 0 0 0 310 85 A 205 235 0 0 0 310 555 z M 310 550 A 200 230 0 0 1 310 90 A 200 230 0 0 1 310 550 z M 310 545 A 195 225 0 0 0 310 95 A 195 225 0 0 0 310 545 z M 310 540 A 190 220 0 0 1 310 100 A 190 220 0 0 1 310 540 z M 310 535 A 185 215 0 0 0 310 105 A 185 215 0 0 0 310 535 z M 310 530 A 180 210 0 0 1 310 110 A 180 210 0 0 1 310 530 z M 310 525 A 175 205 0 0 0 310 115 A 175 205 0 0 0 310 525 z M 310 520 A 170 200 0 0 1 310 120 A 170 200 0 0 1 310 520 z M 310 515 A 165 195 0 0 0 310 125 A 165 195 0 0 0 310 515 z M 310 510 A 160 190 0 0 1 310 130 A 160 190 0 0 1 310 510 z M 310 505 A 155 185 0 0 0 310 135 A 155 185 0 0 0 310 505 z M 310 500 A 150 180 0 0 1 310 140 A 150 180 0 0 1 310 500 z M 310 495 A 145 175 0 0 0 310 145 A 145 175 0 0 0 310 495 z M 310 490 A 140 170 0 0 1 310 150 A 140 170 0 0 1 310 490 z M 310 485 A 135 165 0 0 0 310 155 A 135 165 0 0 0 310 485 z M 310 480 A 130 160 0 0 1 310 160 A 130 160 0 0 1 310 480 z M 310 475 A 125 155 0 0 0 310 165 A 125 155 0 0 0 310 475 z M 310 470 A 120 150 0 0 1 310 170 A 120 150 0 0 1 310 470 z M 310 465 A 115 145 0 0 0 310 175 A 115 145 0 0 0 310 465 z M 310 460 A 110 140 0 0 1 310 180 A 110 140 0 0 1 310 460 z M 310 455 A 105 135 0 0 0 310 185 A 105 135 0 0 0 310 455 z M 310 450 A 100 130 0 0 1 310 190 A 100 130 0 0 1 310 450 z M 310 445 A 95 125 0 0 0 310 195 A 95 125 0 0 0 310 445 z M 310 440 A 90 120 0 0 1 310 200 A 90 120 0 0 1 310 440 z M 310 435 A 85 115 0 0 0 310 205 A 85 115 0 0 0 310 435 z M 310 430 A 80 110 0 0 1 310 210 A 80 110 0 0 1 310 430 z M 310 425 A 75 105 0 0 0 310 215 A 75 105 0 0 0 310 425 z M 310 420 A 70 100 0 0 1 310 220 A 70 100 0 0 1 310 420 z M 310 415 A 65 95 0 0 0 310 225 A 65 95 0 0 0 310 415 z M 310 410 A 60 90 0 0 1 310 230 A 60 90 0 0 1 310 410 z M 310 405 A 55 85 0 0 0 310 235 A 55 85 0 0 0 310 405 z M 310 400 A 50 80 0 0 1 310 240 A 50 80 0 0 1 310 400 z M 310 395 A 45 75 0 0 0 310 245 A 45 75 0 0 0 310 395 z M 310 390 A 40 70 0 0 1 310 250 A 40 70 0 0 1 310 390 z M 310 385 A 35 65 0 0 0 310 255 A 35 65 0 0 0 310 385 z M 310 380 A 30 60 0 0 1 310 260 A 30 60 0 0 1 310 380 z M 310 375 A 25 55 0 0 0 310 265 A 25 55 0 0 0 310 375 z M 310 370 A 20 50 0 0 1 310 270 A 20 50 0 0 1 310 370 z M 310 365 A 15 45 0 0 0 310 275 A 15 45 0 0 0 310 365 z M 310 360 A 10 40 0 0 1 310 280 A 10 40 0 0 1 310 360 z M 310 355 A 5 35 0 0 0 310 285 A 5 35 0 0 0 310 355 z
\ No newline at end of file diff --git a/src/toys/svgd/emptyset.svgd b/src/toys/svgd/emptyset.svgd new file mode 100644 index 0000000..7e91eeb --- /dev/null +++ b/src/toys/svgd/emptyset.svgd @@ -0,0 +1 @@ +M 551.42857,252.36218 52.857143,750.93361 M 440,495.21933 c 0,77.31986 -62.68014,140 -140,140 -77.31986,0 -140,-62.68014 -140,-140 0,-77.31987 62.68014,-140 140,-140 77.31986,0 140,62.68013 140,140 z
\ No newline at end of file diff --git a/src/toys/svgd/fan.svgd b/src/toys/svgd/fan.svgd new file mode 100644 index 0000000..940fb71 --- /dev/null +++ b/src/toys/svgd/fan.svgd @@ -0,0 +1 @@ + M 470 350 L 750 350 L 749.5904551097586 364.7203022982254 L 469.92772737231036 352.9440604596451 L 469.7110836003318 355.88102841977366 L 748.362807068547 379.4051420988682 L 746.3200133880255 394.01914233660864 L 469.3505905978869 358.80382846732175 L 468.84711682419385 361.70541932096774 L 743.4669953370983 408.5270966048386 L 739.810626086145 422.8940539709793 L 468.2018751916726 364.5788107941959 L 467.41642014393256 367.4170806352678 L 735.3597141489511 437.0854031763389 L 730.1249821622271 451.06695601766626 L 466.49264391098126 370.21339120353326 L 465.4327719506772 372.96100594190546 L 724.1190410538375 464.80502970952716 L 717.3563596619706 478.2665280290849 L 464.2393575874066 375.65330560581697 L 462.91527586090126 378.28380420955995 L 709.8532298784406 491.4190210477996 L 701.6277274000925 504.2308232579669 L 461.4637166000163 380.84616465159337 L 459.88817673815265 383.3342139811762 L 692.6996681828653 516.671069905881 L 683.090560703419 528.7097913477304 L 458.19245188883866 385.7419582695461 L 456.3806272017642 388.0635970498188 L 672.8235541433303 540.317985249094 L 661.9233826206857 551.4676864541059 L 454.4570675212975 390.2935372908212 L 452.4264068711928 392.4264068711929 L 650.4163056034258 562.1320343559646 L 638.3300446479859 572.2853376064882 L 450.29353729082106 394.45706752129763 L 448.06359704981867 396.3806272017643 L 625.693716615639 581.9031360088215 L 612.5377635274268 590.9622594441939 L 445.74195826954593 398.1924518888388 L 443.33421398117605 399.8881767381528 L 598.8938792266642 599.4408836907639 L 584.7949330256947 607.3185830000821 L 440.8461646515932 401.4637166000164 L 438.2838042095597 402.9152758609014 L 570.2748905208385 614.5763793045069 L 555.3687317662951 621.1967879370334 L 435.6533056058168 404.2393575874067 L 432.9610059419052 405.4327719506773 L 540.1123670041297 627.1638597533864 L 524.5425501533539 632.4632195549066 L 430.21339120353304 406.4926439109813 L 427.41708063526755 407.4164201439326 L 508.69679026651625 637.082100719663 L 492.6132611671087 641.0093759583635 L 424.57881079419565 408.2018751916727 L 421.7054193209675 408.8471168241939 L 476.33070948548254 644.2355841209694 L 459.8883613148219 646.7529529894346 L 418.8038284673215 409.35059059788694 L 415.88102841977343 409.71108360033185 L 443.3258277120495 648.5554180016593 L 426.68300927132094 649.638636861552 L 412.94406045964485 409.92772737231036 L 409.9999999999998 410 L 409.99999999999875 650.0000000000001 L 393.3169907286766 649.6386368615517 L 407.0559395403547 409.92772737231036 L 404.1189715802261 409.7110836003318 L 376.67417228794807 648.5554180016591 L 360.11163868517565 646.7529529894343 L 401.1961715326781 409.3505905978869 L 398.29458067903204 408.8471168241938 L 343.669290514515 644.235584120969 L 327.38673883288885 641.0093759583631 L 395.4211892058039 408.2018751916726 L 392.582919364732 407.4164201439325 L 311.30320973348137 637.0821007196624 L 295.4574498466437 632.4632195549059 L 389.78660879646657 406.4926439109812 L 387.0389940580944 405.43277195067714 L 279.887632995868 627.1638597533856 L 264.6312682337026 621.1967879370326 L 384.3466943941828 404.2393575874065 L 381.7161957904399 402.9152758609012 L 249.72510947915927 614.5763793045061 L 235.2050669743031 607.3185830000812 L 379.1538353484064 401.4637166000162 L 376.6657860188236 399.8881767381526 L 221.1061207733337 599.440883690763 L 207.4622364725711 590.9622594441928 L 374.2580417304537 398.19245188883855 L 371.936402950181 396.38062720176407 L 194.30628338435898 581.9031360088203 L 181.6699553520122 572.2853376064868 L 369.7064627091786 394.45706752129735 L 367.57359312880686 392.4264068711926 L 169.58369439657233 562.1320343559632 L 158.07661737931238 551.4676864541045 L 365.5429324787022 390.2935372908209 L 363.61937279823553 388.0635970498185 L 147.17644585666795 540.3179852490925 L 136.9094392965793 528.7097913477288 L 361.80754811116105 385.74195826954576 L 360.111823261847 383.3342139811759 L 127.30033181713321 516.6710699058793 L 118.37227259990618 504.2308232579651 L 358.53628339998346 380.846164651593 L 357.08472413909845 378.28380420955955 L 110.14677012155806 491.4190210477978 L 102.64364033802809 478.266528029083 L 355.7606424125932 375.6533056058166 L 354.5672280493226 372.96100594190506 L 95.8809589461614 464.8050297095253 L 89.87501783777185 451.0669560176643 L 353.5073560890186 370.21339120353286 L 352.5835798560673 367.4170806352674 L 84.64028585104796 437.0854031763369 L 80.18937391385413 422.8940539709773 L 351.7981248083272 364.5788107941955 L 351.15288317580604 361.70541932096734 L 76.53300466290085 408.5270966048366 L 73.67998661197373 394.01914233660654 L 350.649409402113 358.8038284673213 L 350.2889163996681 355.8810284197732 L 71.63719293145243 379.4051420988661 L 70.40954489024085 364.7203022982233 L 350.0722726276896 352.9440604596447 L 349.99999999999994 349.99999999999955 L 69.99999999999955 349.99999999999784 L 70.40954489024102 335.27969770177236 L 350.0722726276896 347.0559395403545 L 350.28891639966815 344.1189715802259 L 71.63719293145283 320.59485790112956 L 73.67998661197436 305.98085766338914 L 350.6494094021131 341.19617153267785 L 351.1528831758062 338.2945806790318 L 76.5330046629017 291.47290339515916 L 80.1893739138552 277.10594602901847 L 351.7981248083274 335.42118920580367 L 352.5835798560675 332.58291936473177 L 84.64028585104933 262.9145968236589 L 89.87501783777338 248.93304398233158 L 353.50735608901886 329.78660879646634 L 354.5672280493229 327.03899405809415 L 95.88095894616305 235.19497029047068 L 102.64364033802997 221.73347197091297 L 355.76064241259354 324.3466943941826 L 357.08472413909885 321.71619579043966 L 110.14677012156017 208.58097895219828 L 118.37227259990851 195.76917674203108 L 358.53628339998386 319.15383534840623 L 360.11182326184746 316.6657860188234 L 127.30033181713577 183.32893009411694 L 136.90943929658204 171.29020865226764 L 361.8075481111615 314.25804173045356 L 363.61937279823604 311.9364029501808 L 147.1764458566709 159.68201475090402 L 158.07661737931554 148.5323135458922 L 365.5429324787027 309.7064627091784 L 367.5735931288075 307.5735931288067 L 169.5836943965756 137.8679656440335 L 181.66995535201565 127.71466239351008 L 369.7064627091792 305.542932478702 L 371.9364029501816 303.61937279823536 L 194.3062833843626 118.09686399117678 L 207.46223647257486 109.03774055580445 L 374.2580417304544 301.8075481111609 L 376.6657860188243 300.1118232618469 L 221.10612077333755 100.55911630923444 L 235.20506697430704 92.68141699991645 L 379.15383534840714 298.5362833999833 L 381.71619579044057 297.08472413909834 L 249.72510947916334 85.4236206954917 L 264.6312682337068 78.80321206296531 L 384.34669439418354 295.76064241259303 L 387.0389940580951 294.56722804932247 L 279.8876329958723 72.83614024661233 L 295.4574498466481 67.53678044509223 L 389.7866087964673 293.50735608901846 L 392.5829193647328 292.58357985606716 L 311.3032097334858 62.91789928033586 L 327.3867388328934 58.99062404163544 L 395.4211892058047 291.7981248083271 L 398.2945806790329 291.1528831758059 L 343.6692905145196 55.76441587902963 L 360.11163868518037 53.247047010564586 L 401.1961715326789 290.64940940211295 L 404.11897158022697 290.288916399668 L 376.67417228795284 51.44458199833997 L 393.3169907286814 50.36136313844747 L 407.05593954035555 290.0722726276895 L 410.0000000000006 289.9999999999999 L 410.0000000000036 49.99999999999932 L 426.6830092713258 50.36136313844776 L 412.94406045964575 290.0722726276896 L 415.8810284197743 290.2889163996681 L 443.3258277120543 51.444581998340595 L 459.8883613148268 53.24704701056555 L 418.8038284673224 290.6494094021131 L 421.70541932096836 291.15288317580615 L 476.33070948548743 55.76441587903088 L 492.6132611671136 58.99062404163698 L 424.5788107941965 291.7981248083274 L 427.4170806352684 292.58357985606756 L 508.69679026652113 62.91789928033768 L 524.5425501533587 67.53678044509428 L 430.2133912035339 293.50735608901886 L 432.9610059419061 294.5672280493229 L 540.1123670041344 72.83614024661466 L 555.3687317662998 78.80321206296787 L 435.6533056058176 295.7606424125936 L 438.28380420956057 297.0847241390989 L 570.2748905208431 85.42362069549449 L 584.7949330256993 92.68141699991958 L 440.846164651594 298.5362833999839 L 443.3342139811768 300.1118232618476 L 598.8938792266686 100.5591163092378 L 612.5377635274311 109.03774055580809 L 445.74195826954667 301.8075481111616 L 448.0635970498194 303.61937279823616 L 625.6937166156432 118.09686399118067 L 638.3300446479899 127.7146623935142 L 450.29353729082175 305.5429324787028 L 452.4264068711935 307.57359312880754 L 650.4163056034297 137.86796564403784 L 661.9233826206896 148.53231354589673 L 454.45706752129814 309.70646270917933 L 456.3806272017648 311.9364029501818 L 672.8235541433339 159.6820147509088 L 683.0905607034226 171.2902086522726 L 458.1924518888393 314.2580417304545 L 459.8881767381533 316.6657860188244 L 692.6996681828685 183.32893009412211 L 701.6277274000955 195.76917674203642 L 461.4637166000169 319.1538353484073 L 462.91527586090183 321.71619579044074 L 709.8532298784436 208.5809789522038 L 717.3563596619734 221.7334719709186 L 464.2393575874071 324.3466943941837 L 465.43277195067765 327.0389940580953 L 724.1190410538401 235.19497029047645 L 730.1249821622295 248.9330439823375 L 466.49264391098166 329.7866087964675 L 467.4164201439329 332.582919364733 L 735.3597141489532 262.91459682366497 L 739.8106260861471 277.1059460290246 L 468.201875191673 335.4211892058049 L 468.84711682419413 338.29458067903306 L 743.4669953371001 291.4729033951654 L 746.3200133880272 305.98085766339545 L 469.35059059788716 341.1961715326791 L 469.7110836003321 344.1189715802272 L 748.3628070685484 320.5948579011359 L 749.5904551097599 335.2796977017788 L 469.9277273723105 347.0559395403558 z diff --git a/src/toys/svgd/lotsarect.svgd b/src/toys/svgd/lotsarect.svgd new file mode 100644 index 0000000..ab2d186 --- /dev/null +++ b/src/toys/svgd/lotsarect.svgd @@ -0,0 +1 @@ +M 39.75993,80.899849 L 359.75993,80.899849 L 359.75993,278.04273 L 39.75993,278.04273 L 39.75993,80.899849 zM 377.14322,406.21942 L 511.42894,406.21942 L 511.42894,503.36228 L 377.14322,503.36228 L 377.14322,406.21942 zM 377.14322,189.9335 L 511.42894,189.9335 L 511.42894,287.07636 L 377.14322,287.07636 L 377.14322,189.9335 zM 377.14322,298.07648 L 511.42894,298.07648 L 511.42894,395.21934 L 377.14322,395.21934 L 377.14322,298.07648 zM 377.14322,80.899849 L 511.42894,80.899849 L 511.42894,178.04271 L 377.14322,178.04271 L 377.14322,80.899849 zM 377.14322,514.36237 L 511.42894,514.36237 L 511.42894,611.50523 L 377.14322,611.50523 L 377.14322,514.36237 zM 49.495197,612.79767 L 359.4294,612.79767 L 359.4294,622.22533 L 49.495197,622.22533 L 49.495197,612.79767 zM 374.74936,625.76074 L 512.62885,625.76074 L 512.62885,757.74794 L 374.74936,757.74794 L 374.74936,625.76074 z
\ No newline at end of file diff --git a/src/toys/svgd/monkey.svgd b/src/toys/svgd/monkey.svgd new file mode 100644 index 0000000..9bdbbc3 --- /dev/null +++ b/src/toys/svgd/monkey.svgd @@ -0,0 +1 @@ +M 5.9562726,54.79058 C 13.541683,37.50935 29.441883,27.76099 41.447803,33.03085 c 12.00591,5.26987 15.59357,23.57221 8.00816,40.85344 -7.58541,17.28124 -9.90029,25.53083 -32.70294,16.30215 C 4.5991026,85.26753 -1.6291374,72.07182 5.9562726,54.79058 z M 59.77575,139.78799 c 17.6347,24.11274 67.29975,30.95068 91.77238,0.71978 m -34.6436,-37.40404 c 0,3.47102 -5.75618,6.2881 -12.84862,6.2881 -7.09242,0 -12.848597,-2.81708 -12.848597,-6.2881 0,-3.471028 5.756177,-6.288105 12.848597,-6.288105 7.09244,0 12.84862,2.817077 12.84862,6.288105 z m 8.28521,-17.104633 c -2.97718,0.127204 -5.59649,-4.52155 -5.84666,-10.376686 -0.25017,-5.855125 1.96307,-10.710355 4.94026,-10.837559 2.97718,-0.127204 5.59649,4.52155 5.84665,10.376676 0.25017,5.855135 -1.96307,10.710365 -4.94025,10.837569 z M 83.28913,64.783523 c 2.977573,-0.11776 5.582124,4.539282 5.813719,10.395185 0.231594,5.855893 -1.997037,10.704081 -4.97461,10.821841 -2.977572,0.11776 -5.582124,-4.539282 -5.813718,-10.395175 -0.231594,-5.855903 1.997036,-10.704092 4.974609,-10.821851 z M 45.217471,85.556076 c 0.822591,11.141413 3.633402,22.142554 10.599469,31.583504 0,0 -15.4694,5.45785 -13.31599,26.99188 2.87914,28.79134 33.63174,40.93745 66.22007,40.66777 27.38883,-0.21003 58.61221,-13.31662 59.02225,-39.2282 0.29666,-23.0823 -6.83795,-24.11275 -12.95611,-28.79134 6.43867,-10.04933 9.12075,-19.882931 9.80868,-31.216258 m -7.09398,-26.793307 c -5.29439,-5.661365 -13.31177,-9.59154 -23.94831,-11.089645 -10.28695,-1.43893 -26.99188,5.03849 -29.15123,5.03849 -1.79946,0 -23.47674,-7.30799 -34.5496,-4.6786 -6.377516,1.507416 -11.596875,4.060909 -15.573817,7.756776 M 47.116071,35.239853 C 60.692058,16.061922 81.318474,3.8164724 104.40235,3.8164724 c 24.26701,0 45.81819,13.5328816 59.31755,34.4261516 m 14.55081,56.509427 c -0.76518,14.403499 -4.61365,27.847529 -10.79081,39.459189 M 42.237336,135.88313 C 35.362956,123.6046 31.132611,109.16372 30.481189,93.641902 M 204.76611,54.79058 C 197.1807,37.50935 181.2805,27.76099 169.27458,33.03085 c -12.00591,5.26987 -15.59357,23.57221 -8.00816,40.85344 7.58541,17.28124 9.90029,25.53083 32.70294,16.30215 12.15392,-4.91891 18.38216,-18.11462 10.79675,-35.39586 z diff --git a/src/toys/svgd/nasty.svgd b/src/toys/svgd/nasty.svgd new file mode 100644 index 0000000..2a26bd2 --- /dev/null +++ b/src/toys/svgd/nasty.svgd @@ -0,0 +1,3 @@ +M 387.63906,267.9784 L 106.72661,469.82569 L 106.72661,267.9784 L 387.63906,469.82569 L 387.63906,267.9784 z M 540.4278,232.25903 C 440.92573,232.25903 646.18067,452.19982 540.4278,452.19982 C 437.36839,452.19982 639.73953,232.25903 540.4278,232.25903 z +M 282.85714,578.07647 C 74.285714,832.36218 165.33193,1033.3172 288.57143,900.93361 C 410.47619,769.98377 73.333332,831.45467 282.85714,578.07647 z +M 548.57143,662.36218 C 615.72948,662.36218 625.35777,906.64794 728.57143,906.64794 C 832.06154,906.64794 637.327,662.36218 548.57143,662.36218 C 459.95393,662.36218 291.30957,906.64794 428.57143,906.64794 C 534.44005,906.64794 481.41338,662.36218 548.57143,662.36218 z diff --git a/src/toys/svgd/onlyarcs.svgd b/src/toys/svgd/onlyarcs.svgd new file mode 100644 index 0000000..b8fe8d1 --- /dev/null +++ b/src/toys/svgd/onlyarcs.svgd @@ -0,0 +1,10 @@ +M 0,0 +A 40,20 0 0 0 100,0 + 40,20 0 0 0 200,0 + 40,20 90 0 0 200,100 + 40,20 90 0 0 200,200 + 40,20 0 0 0 100,200 + 40,20 0 0 0 0,200 + 40,20 90 0 0 0,100 + 40,20 90 0 0 0,0 + z diff --git a/src/toys/svgd/ptitle.svgd b/src/toys/svgd/ptitle.svgd new file mode 100644 index 0000000..2df8860 --- /dev/null +++ b/src/toys/svgd/ptitle.svgd @@ -0,0 +1 @@ +M 115.1808,25.588188 C 127.80826,26.557245 141.79964,22.715822 153.00893,30.053032 C 167.1951,42.822758 151.75887,64.216819 134.75853,59.865532 C 126.06809,57.038779 129.18533,66.019697 128.71596,71.414009 C 131.4913,80.259661 123.09665,77.935329 117.16748,78.076469 C 112.56276,76.886302 116.47422,67.179968 115.1808,62.6785 C 115.1808,50.315063 115.1808,37.951625 115.1808,25.588188 z M 131.60308,50.056938 C 148.57642,53.813235 146.23212,30.658565 130.43,35.396782 C 127.29211,38.94083 127.23081,46.355022 131.60308,50.056938 z M 183.06752,60.357719 C 165.63447,60.295503 183.24033,80.518401 189.00011,65.31476 C 192.06883,58.337714 187.11171,60.778459 183.06752,60.357719 z M 202.29799,55.611626 C 202.29799,63.099907 202.29799,70.588188 202.29799,78.076469 C 197.62085,76.91918 187.20205,81.503563 189.60658,73.264604 C 182.8671,85.729177 152.16107,75.907493 165.13563,57.951724 C 168.48007,47.659856 195.18552,59.239016 187.25111,47.525688 C 177.86756,43.881758 158.53169,53.222176 167.93494,39.294753 C 181.3394,36.315116 203.97321,35.500521 202.29799,55.611626 z M 243.32533,49.424126 C 229.50261,42.165491 224.74932,58.12986 226.66127,68.597202 C 228.09365,76.076455 226.04486,80.074546 217.99991,78.076469 C 210.48863,79.788485 215.52906,68.948786 214.07533,64.603246 C 214.07533,55.969321 214.07533,47.335395 214.07533,38.701469 C 218.71947,39.856601 229.10169,35.273643 226.66127,43.513335 C 227.95656,41.232134 248.09079,31.257919 243.31691,46.694818 C 243.31972,47.604587 243.32252,48.514356 243.32533,49.424126 z M 267.30191,60.357719 C 249.86884,60.295482 267.47469,80.518412 273.23449,65.31476 C 276.30322,58.33771 271.34611,60.778461 267.30191,60.357719 z M 286.53235,55.611626 C 286.53235,63.099907 286.53235,70.588188 286.53235,78.076469 C 281.85522,76.919181 271.43643,81.503563 273.84097,73.264604 C 267.10148,85.729174 236.39544,75.907495 249.37001,57.951724 C 252.71445,47.659856 279.41992,59.239015 271.48549,47.525688 C 262.10194,43.88176 242.76606,53.222171 252.16932,39.294753 C 265.57378,36.315114 288.20758,35.500525 286.53236,55.611626 M 334.80191,45.240532 C 345.49046,28.171278 366.87108,43.167474 361.59096,59.874146 C 361.59097,65.941587 361.59097,72.009028 361.59097,78.076469 C 356.92475,76.919987 346.51835,81.503682 348.93472,73.264604 C 348.50839,65.231031 349.97235,56.898734 347.77455,49.142876 C 335.14645,42.504341 335.807,61.093114 336.24329,69.326636 C 337.9621,76.849177 335.16475,79.855557 327.58193,78.076469 C 320.00816,79.827535 325.02155,68.991435 323.58704,64.603246 C 326.31279,55.038882 318.58105,38.272161 311.38782,53.827464 C 310.41918,61.855715 311.09896,69.997063 310.89566,78.076469 C 306.22944,76.919987 295.82304,81.503682 298.23941,73.264604 C 298.23941,61.743559 298.23941,50.222514 298.23941,38.701469 C 302.90563,39.857951 313.31203,35.274257 310.89566,43.513335 C 314.6641,37.643381 330.44834,33.922886 334.8019,45.240532 M 412.70816,58.283501 C 413.74716,65.329673 402.8183,60.407999 398.27256,61.869438 C 392.57307,62.588118 377.07559,58.69943 386.48158,68.513969 C 394.57691,78.279659 414.21137,57.534567 410.84485,73.681704 C 404.09284,81.288741 385.15052,81.142742 376.35658,73.681938 C 363.9865,62.168354 371.67984,37.148572 389.80164,37.838507 C 401.97647,35.889384 413.6668,45.751205 412.70815,58.283501 M 399.77066,54.099907 C 393.61653,30.04231 370.10862,61.928589 399.77066,54.099907 z M 435.94644,27.521782 C 435.96201,33.478312 433.93472,41.4777 442.68305,38.701469 C 449.91823,34.769304 451.1939,46.589839 446.99432,47.701469 C 441.05173,47.687042 433.16506,45.728538 435.94644,54.438081 C 435.39823,61.49483 434.91359,71.865095 445.20998,69.076469 C 450.19974,68.732922 449.24732,82.168413 442.05502,78.076469 C 430.09932,80.727323 420.36946,71.930596 423.36049,59.588823 C 422.71262,54.349502 426.24292,44.932013 417.58627,47.701469 C 415.00348,41.807039 418.08141,37.377201 423.3605,37.739096 C 422.6103,31.016795 422.60176,25.036149 431.05949,27.521782 C 432.68847,27.521782 434.31746,27.521782 435.94644,27.521782 z M 485.90347,49.424126 C 472.08074,42.165489 467.32744,58.129851 469.2394,68.597202 C 470.67179,76.076455 468.62301,80.074546 460.57805,78.076469 C 453.06677,79.788485 458.1072,68.948786 456.65347,64.603246 C 456.65347,55.969321 456.65347,47.335395 456.65347,38.701469 C 461.29761,39.856601 471.67983,35.273643 469.23941,43.513335 C 470.53469,41.232124 490.6689,31.257926 485.89505,46.694818 C 485.89785,47.604587 485.90066,48.514356 485.90347,49.424126 z M 492.23157,38.701469 C 496.8757,39.856601 507.25793,35.273643 504.8175,43.513335 C 504.8175,55.03438 504.8175,66.555425 504.8175,78.076469 C 500.17337,76.921338 489.79114,81.504296 492.23157,73.264604 C 492.23157,61.743559 492.23157,50.222514 492.23157,38.701469 z M 492.23157,23.373344 C 496.8757,24.528476 507.25793,19.945518 504.8175,28.18521 C 507.42558,36.781485 497.41711,32.761224 492.30665,33.638969 C 492.13675,30.373397 492.27768,26.760041 492.23157,23.373344 z M 548.79797,39.931938 C 552.97763,56.979712 538.66501,41.438745 530.05971,49.846001 C 518.41065,63.70638 539.40292,76.166974 548.79799,67.577905 C 553.09286,81.766505 534.23374,80.517459 525.20155,76.9674 C 507.36245,69.798238 511.84153,38.785564 531.6681,38.095047 C 537.4159,37.270461 543.34813,37.942021 548.79799,39.931938 M 590.49329,39.931938 C 594.79689,57.039848 577.52986,40.882708 568.9513,48.158523 C 572.15538,57.881524 601.33708,52.315812 591.96984,71.985653 C 585.95746,81.25109 561.1725,82.610066 558.00892,72.785571 C 554.84332,59.391839 574.62439,78.16019 582.36692,66.985848 C 570.73978,64.945587 547.5903,55.865125 561.70033,40.775688 C 570.39079,35.821962 581.23811,37.826772 590.4933,39.931938 diff --git a/src/toys/svgd/rect.svgd b/src/toys/svgd/rect.svgd new file mode 100644 index 0000000..29ee442 --- /dev/null +++ b/src/toys/svgd/rect.svgd @@ -0,0 +1 @@ +M 120,98.076469 L 551.42856,98.076469 L 551.42856,323.79075 L 120,323.79075 L 120,98.076469 z
\ No newline at end of file diff --git a/src/toys/svgd/sanitize-examples.svgd b/src/toys/svgd/sanitize-examples.svgd new file mode 100644 index 0000000..486188d --- /dev/null +++ b/src/toys/svgd/sanitize-examples.svgd @@ -0,0 +1 @@ +m 103.3207,92.186815 c 61.67475,0 77.09344,123.349515 138.76821,123.349515 92.51214,0 92.51214,-123.349515 0,-123.349515 -61.67476,0 -77.09345,123.349515 -138.76821,123.349515 -92.512144,0 -92.512144,-123.349515 0,-123.349515 m 172.70652,592.36216 c -116.436115,0 -206.29787,-170 -79.999995,-170 119.740085,0 239.999995,110 79.999995,110 -159.999995,0 -39.74009,-110 80,-110 126.29788,0 36.43613,170 -80,170
\ No newline at end of file diff --git a/src/toys/svgd/scribble.svgd b/src/toys/svgd/scribble.svgd new file mode 100644 index 0000000..206563d --- /dev/null +++ b/src/toys/svgd/scribble.svgd @@ -0,0 +1 @@ +M 205.71429,509.50504 C 191.27639,435.33396 124.96983,282.9985 237.14286,240.93361 C 298.33002,217.98843 410.42812,416.03951 425.71429,458.07647 C 456.36209,542.35792 211.91703,513.49252 165.71429,483.79075 C 20.478436,390.42485 89.745454,432.24443 157.14286,589.50504 C 207.33723,706.62525 264.31061,792.65288 391.42857,818.07647 C 466.24819,833.04039 419.17914,844.21704 428.57143,806.6479 C 455.45597,699.10972 205.78614,535.50673 188.57143,466.6479 C 186.58441,458.69982 179.04762,453.31456 174.28571,446.6479 C 173.27225,445.22905 206.50053,444.8262 242.85714,426.6479 C 302.27468,396.93913 311.42857,331.66273 311.42857,272.36218 C 311.42857,232.18181 300.5556,209.69024 257.14286,195.21933 C 217.28192,181.93235 106.94618,129.21667 94.285714,123.79075 C 79.18517,117.31909 126.94244,129.50504 168.57143,129.50504 C 301.00757,129.50504 430.66079,118.07647 562.85714,118.07647 C 597.38432,118.07647 452.97541,72.362183 425.71429,72.362183 C 289.53604,72.362183 290.63424,45.059235 377.14286,218.07647 C 439.49432,342.7794 589.94279,380.17165 680,455.21933 C 715.67553,484.94893 625.63444,427.43 577.14286,406.6479 C 390.81914,326.79487 363.97733,373.48237 502.85714,512.36218 C 519.79024,529.29528 656.23138,719.67741 540,680.93361 C 493.67741,665.49275 225.40147,535.28189 382.85714,503.79075 C 399.28655,500.50487 561.71346,472.36218 517.14286,472.36218 C 410.68431,472.36218 181.50374,350.75815 111.42857,292.36218 C 21.960157,217.80517 195.28155,363.95164 220,378.07647 C 486.98782,530.64094 333.26177,540.93361 142.85714,540.93361 C 33.897473,540.93361 -105.50257,492.78561 -34.285714,635.21933 C 4.462011,712.71478 133.2726,753.67122 208.57143,783.79075 C 234.8422,794.29906 219.78391,839.52186 208.57143,855.21933 C 168.50791,911.30825 225.30435,836.44417 274.28571,826.6479 C 322.06141,817.09276 466.28439,799.2233 485.71429,740.93361 C 493.89344,716.39615 516.95233,712.23517 474.28571,683.79075 C 439.34382,660.49616 441.2221,580.11609 397.14286,558.07647 C 373.83188,546.42098 339.15563,564.21294 322.85714,572.36218 C 305.88053,580.85049 256.57431,543.50648 240,535.21933 C 222.14649,526.29257 195.2496,535.66674 205.71429,509.50504 C 206.5716,507.36174 165.95426,541.31306 205.71429,509.50504 C 209.43271,506.5303 205.71429,519.02885 205.71429,523.79075 C 205.71429,528.55266 205.71429,514.26694 205.71429,509.50504 z
\ No newline at end of file diff --git a/src/toys/svgd/spiral.svgd b/src/toys/svgd/spiral.svgd new file mode 100644 index 0000000..cf096f9 --- /dev/null +++ b/src/toys/svgd/spiral.svgd @@ -0,0 +1 @@ +M 425.71429,452.36218 C 428.61778,445.53492 436.34979,452.9077 437.06166,457.18797 C 438.99077,468.78726 426.04452,475.99734 416.06271,475.05691 C 398.20761,473.37469 388.47706,454.23374 391.67221,437.88481 C 396.36121,413.89211 422.40756,401.33715 445.01745,406.97273 C 475.15289,414.48407 490.6018,447.65442 482.45111,476.49113 C 472.1943,512.7792 431.80767,531.14987 396.75955,520.44636 C 354.31159,507.48297 333.00474,459.83266 346.28275,418.58165 C 361.93043,369.96877 416.872,345.71719 464.32061,361.58328 C 519.10192,379.9013 546.30379,442.1516 527.84056,495.79429 C 506.86147,556.74653 437.29069,586.90252 377.45639,565.83581 C 310.33136,542.20215 277.21849,465.30257 300.8933,399.27849 C 327.17683,325.97925 411.41129,289.90746 483.62377,316.19383
\ No newline at end of file diff --git a/src/toys/svgd/star.svg b/src/toys/svgd/star.svg new file mode 100644 index 0000000..113f523 --- /dev/null +++ b/src/toys/svgd/star.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.45.1" + sodipodi:docbase="/home/njh/svn/lib2geom/src/toys" + sodipodi:docname="star.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35" + inkscape:cx="375" + inkscape:cy="520" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="910" + inkscape:window-height="631" + inkscape:window-x="0" + inkscape:window-y="25" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:#ffff00;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 268.57143,258.07648 L 377.39465,340.01528 L 414.5031,208.94498 L 454.38051,339.19955 L 561.44306,254.97326 L 517.14286,383.79077 L 653.26525,378.58005 L 541.7086,456.7566 L 654.89671,532.55178 L 518.69446,530.22658 L 565.71428,658.07648 L 456.89106,576.13768 L 419.78261,707.20797 L 379.9052,576.95341 L 272.84264,661.1797 L 317.14285,532.36219 L 181.02045,537.5729 L 292.57711,459.39635 L 179.38899,383.60117 L 315.59124,385.92637 L 268.57143,258.07648 z " + id="path3134" /> + </g> +</svg> diff --git a/src/toys/svgd/star.svgd b/src/toys/svgd/star.svgd new file mode 100644 index 0000000..c6eb2d4 --- /dev/null +++ b/src/toys/svgd/star.svgd @@ -0,0 +1 @@ +M 268.57143,258.07648 L 377.39465,340.01528 L 414.5031,208.94498 L 454.38051,339.19955 L 561.44306,254.97326 L 517.14286,383.79077 L 653.26525,378.58005 L 541.7086,456.7566 L 654.89671,532.55178 L 518.69446,530.22658 L 565.71428,658.07648 L 456.89106,576.13768 L 419.78261,707.20797 L 379.9052,576.95341 L 272.84264,661.1797 L 317.14285,532.36219 L 181.02045,537.5729 L 292.57711,459.39635 L 179.38899,383.60117 L 315.59124,385.92637 L 268.57143,258.07648 z
\ No newline at end of file diff --git a/src/toys/svgd/tadpole.svgd b/src/toys/svgd/tadpole.svgd new file mode 100644 index 0000000..8895582 --- /dev/null +++ b/src/toys/svgd/tadpole.svgd @@ -0,0 +1 @@ +m 371.42857,212.36218 c -32.48101,-26.68142 -91.58939,10.54383 -64.64475,49.96244 5.57459,42.57277 70.53374,68.98077 78.10803,11.90685 14.41443,-52.55004 -40.97633,-99.82411 -91.27953,-91.66162 -45.76637,3.77115 -95.66566,43.4207 -88.25336,93.06866 8.10715,43.84559 57.30139,51.48387 83.49912,79.21169 62.5378,56.33364 174.00162,56.411 226.54697,-13.71412 33.15536,-44.32089 27.37475,-103.8585 15.40989,-154.44966 C 518.94598,147.80633 481.97543,121.49829 442.21462,117.80826 360.77206,103.76604 275.59142,128.77168 196.14437,100.04722 138.26682,85.91059 75.980997,126.30734 62.339274,183.76981 43.90764,241.6785 65.723336,304.74593 104.98132,348.74653 179.31517,452.93484 311.47528,524.69632 441.07626,499.66492 521.15261,482.71575 593.45793,431.55112 633.57498,359.85355 666.74394,302.92229 686.82287,232.82246 669.17746,167.67963 652.45446,117.60598 601.61006,86.174529 550.15809,84.370797 388.03362,61.260938 223.67779,65.532229 60.679408,54.420286 c -85.3275,5.078677 -174.754978,36.378483 -230.507428,103.801754 -26.33214,35.75102 -22.70039,88.23391 8.40942,120.02678 31.2681,37.67952 76.41934,59.44396 112.847171,91.25622
\ No newline at end of file diff --git a/src/toys/svgd/touchboxes.svgd b/src/toys/svgd/touchboxes.svgd new file mode 100644 index 0000000..55e6f9d --- /dev/null +++ b/src/toys/svgd/touchboxes.svgd @@ -0,0 +1 @@ +m 350,380 200,0 0,300 -200,0 0,-300 z M 80,180 l 270,0 0,400 -270,0 0,-400 z
\ No newline at end of file diff --git a/src/toys/svgd/toy.svgd b/src/toys/svgd/toy.svgd new file mode 100644 index 0000000..9983796 --- /dev/null +++ b/src/toys/svgd/toy.svgd @@ -0,0 +1 @@ +M 685.71429 132.36218 L 337.14286+238.07647 608.57143,298.07647 C 608.57143,298.07647 685.71429,543.79075 517.14286,552.36218 348.57143,560.93361 280,858.07647 422.85714,852.36218 565.71429,846.6479 691.42857,612.36218 691.42857,612.36218 Q 348.57143,560.93361 685.71429,132.36218 Z diff --git a/src/toys/svgd/triarrange.svgd b/src/toys/svgd/triarrange.svgd new file mode 100644 index 0000000..f5d82ee --- /dev/null +++ b/src/toys/svgd/triarrange.svgd @@ -0,0 +1 @@ +M 495,400 45,380 M 210,230 450,460 M 100,440 370,230
\ No newline at end of file diff --git a/src/toys/svgd/tricky.svgd b/src/toys/svgd/tricky.svgd new file mode 100644 index 0000000..1b2fa9a --- /dev/null +++ b/src/toys/svgd/tricky.svgd @@ -0,0 +1 @@ +M 458.65625,31.34375 L 455.0625,31.4375 L 451.6875,32.65625 C 434.07714,39.013007 420.84501,52.305976 414.03125,67.25 C 407.21749,82.194024 405.96292,98.410997 408.28125,113.8125 C 412.4771,141.68706 428.51783,169.60215 456.40625,181.6875 C 538.70194,312.80904 554.38602,481.52856 510.34375,629.90625 L 509.8125,631.71875 L 509.5625,633.59375 C 506.87733,655.34401 495.49324,679.1849 479.46875,695.25 C 463.70165,711.05705 445.14595,719.37248 423.84375,716.625 C 403.50709,711.46674 391.72366,697.93145 383.4375,677.25 C 375.01895,656.2381 372.48837,628.78961 374.375,606.03125 C 385.44095,485.85838 388.01837,349.21084 313,239 C 270.11084,152.28462 127.55134,177.04972 109.25,269.96875 L 108.5,273.6875 L 109.03125,277.4375 C 117.41998,339.02101 116.06431,402.35901 112.375,466.09375 L 112.21875,469.125 L 112.84375,472.09375 C 117.1301,492.47365 108.37169,516.28486 92.03125,532.75 C 78.58177,546.30212 61.816491,553.83892 44.78125,552.625 C 26.079739,485.94082 42.403549,411.80134 67.125,342.90625 L 24.3125,327.53125 C -3.3082192,404.50618 -24.482689,494.297 5.71875,580.03125 L 9.5625,590.90625 L 20.5625,594.25 C 59.671156,606.15028 98.586445,590.76637 124.34375,564.8125 C 149.33808,539.62742 164.42386,503.26605 157.9375,466.125 C 161.49046,403.72415 162.85377,340.00411 154.65625,276.1875 C 166.8006,229.69869 250.73728,213.68098 272.65625,260.03125 L 273.46875,261.6875 L 274.5,263.21875 C 340.18519,358.54848 339.90385,483.98933 329.03125,602.0625 L 329.03125,602.15625 L 329.03125,602.25 C 326.66071,630.84572 329.10103,663.98958 341.1875,694.15625 C 353.27397,724.32292 377.30928,752.4189 414.28125,761.1875 L 415.28125,761.40625 L 416.25,761.5625 C 453.7973,767.02344 487.87231,751.21921 511.65625,727.375 C 534.86042,704.11203 549.85821,673.25525 554.3125,641.53125 C 602.10825,479.2135 584.5466,295.0552 490.8125,150.71875 L 486.125,143.53125 L 477.90625,141.21875 C 466.97267,138.13618 455.72384,123.25825 453.28125,107.03125 C 452.05996,98.91775 453.07904,91.297618 455.4375,86.125 C 457.36175,81.904709 459.73336,79.094036 464.03125,76.90625 C 495.13555,76.980202 529.29685,90.275009 566.03125,101.59375 C 610.4676,115.59228 646.8459,148.41525 668.6875,190.40625 L 668.8125,190.65625 L 668.9375,190.875 C 697.92762,243.68554 717.84744,301.48146 706.25,356.90625 L 706.21875,357.15625 L 706.15625,357.40625 C 694.18886,421.72169 679.2601,488.02392 680.625,557.96875 C 678.68079,606.53403 686.86775,651.99143 686.21875,693.84375 C 670.05571,787.71759 590.84823,878.44142 494,879.59375 L 493.96875,879.59375 L 493.9375,879.59375 C 395.82741,881.09756 293.40089,802.00173 289.84375,701.78125 L 289.8125,700.875 L 289.71875,700 C 282.30077,635.83434 290.72304,569.30623 297.15625,501.21875 L 298.0625,491.5625 L 291.75,484.21875 C 284.80041,476.14619 277.49477,469.76476 268.21875,465.84375 C 258.94273,461.92274 247.2182,461.69521 238.125,465.375 C 219.9386,472.73458 212.83021,487.30247 206.46875,501.0625 C 200.10729,514.82253 195.45919,529.80289 191.34375,543.03125 C 187.22831,556.25961 183.18481,568.24935 181.34375,571.875 L 181.28125,572.03125 L 181.21875,572.1875 C 142.65707,651.07426 94.549143,727.5865 58.8125,812.3125 C 49.294957,829.15506 36.457563,832.16994 22.90625,829.09375 C 9.2095614,825.98456 -3.2515511,815.31431 -6,797.40625 L -6.28125,795.53125 L -6.875,793.71875 C -20.296536,753.1447 -21.015532,704.32135 -39.625,654.96875 C -73.90942,524.31702 -90.602855,380.79943 -28.71875,262.375 C 1.9598227,205.26365 67.457357,164.07188 103.28125,90.9375 L 62.4375,70.9375 C 33.995201,129.00238 -30.775413,170.01981 -68.9375,241.0625 L -69,241.1875 L -69.0625,241.28125 C -139.47511,376.02623 -119.00391,531.98526 -83.5,667.0625 L -83.1875,668.28125 L -82.71875,669.46875 C -67.458776,708.96795 -66.587036,756.94282 -50.5625,806.375 C -44.27864,841.61077 -17.523667,866.57525 12.84375,873.46875 C 43.80863,880.49788 80.448846,867.38803 98.96875,833.78125 L 99.5625,832.71875 L 100.03125,831.59375 C 133.90634,750.79708 181.84088,674.53399 222.09375,592.1875 C 227.62217,581.30025 230.72079,569.58288 234.78125,556.53125 C 238.84171,543.47962 243.26444,529.92624 247.78125,520.15625 C 248.49865,518.60449 249.23407,517.44847 249.96875,516.1875 C 243.90866,577.21519 237.04535,640.29773 244.46875,704.90625 C 244.48076,705.01077 244.48792,705.11422 244.5,705.21875 C 250.176,833.46768 372.77047,926.96151 494.625,925.09375 C 619.20181,923.6115 712.56579,813.78448 731.40625,699.875 L 731.65625,698.28125 L 731.6875,696.65625 C 732.75618,647.53405 724.20107,601.36335 726.09375,558.53125 L 726.125,557.8125 L 726.125,557.09375 C 724.89958,494.29702 738.71436,431.07277 750.875,365.71875 C 765.84238,294.18884 740.23516,226.21058 708.8125,168.96875 C 681.8812,117.19267 636.59507,76.122901 579.5625,58.15625 L 579.5,58.125 L 579.4375,58.125 C 544.17995,47.261312 504.09579,29.800274 458.65625,31.34375 z
\ No newline at end of file diff --git a/src/toys/svgd/winding.svgd b/src/toys/svgd/winding.svgd new file mode 100644 index 0000000..e0d9b59 --- /dev/null +++ b/src/toys/svgd/winding.svgd @@ -0,0 +1 @@ +M 237.8317,-0.00079806585 C 209.05129,2.0908553 194.49466,33.03178 168.59187,39.198673 C 144.83493,42.756195 133.04439,64.090923 112.23618,72.228008 C 96.390481,93.25412 79.059139,109.36615 55.144754,121.38967 C 28.073728,135.55225 28.749612,175.16363 61.142614,182.29572 C 59.032188,207.27758 51.935234,245.41728 19.327194,232.92932 C -15.949549,243.07701 3.1745321,287.497 13.519564,310.39525 C 17.791104,331.61525 13.405542,357.01005 26.625846,375.92214 C 24.121233,402.01179 53.313158,415.34704 52.967142,440.29059 C 53.481143,471.00236 85.579755,514.66901 114.61929,479.98667 C 130.54404,473.62405 176.35717,503.20612 155.39995,524.08371 C 144.17379,562.84736 195.91996,565.77913 221.73596,560.74671 C 247.29609,564.51298 273.27675,571.81621 298.92308,570.02889 C 324.04728,573.62413 345.47989,554.14368 370.64594,564.03304 C 402.54533,573.02054 442.7131,544.20356 415.3454,511.64988 C 431.35565,493.15457 459.52837,465.86203 478.13411,494.82269 C 512.36196,508.17908 524.21953,461.82677 528.84872,436.94541 C 539.13223,415.75136 555.80382,396.04476 559.09515,372.30135 C 573.74637,351.25358 558.73087,324.13051 575.26348,304.02273 C 593.5935,277.76628 586.3979,225.87087 544.09643,239.90933 C 527.05985,225.53034 511.89014,182.31837 545.28987,174.97401 C 567.85671,145.99111 526.28917,121.32068 504.46509,108.82388 C 488.46914,94.238944 477.21301,71.049919 455.38968,63.601767 C 442.2435,41.019814 410.90027,47.427299 396.59221,27.25649 C 378.5237,4.3455996 329.19512,-15.054231 323.43731,28.290685 C 321.44896,48.974453 272.63938,43.051302 266.79567,31.393533 C 265.68977,16.25095 255.66504,-2.8664064 237.8317,-0.00079806585 z M 214.63232,41.285294 C 229.03834,39.461472 281.82707,44.654146 243.16774,50.095915 C 215.41861,55.13544 186.824,66.887416 160.08963,70.064158 C 171.40031,49.377676 189.54369,36.977634 214.63232,41.285294 z M 364.21881,43.044488 C 389.3526,38.086584 423.75203,50.784111 426.68647,74.129598 C 394.45708,65.080185 361.92396,54.41391 328.76005,47.772323 C 335.0792,39.285706 353.90158,45.472989 364.21881,43.044488 z M 299.89827,49.421568 C 334.38633,45.796396 318.92415,87.04827 327.35648,107.9555 C 332.43473,138.01064 340.0154,167.56295 347.72637,197.02897 C 326.17931,210.45393 292.14883,198.94677 265.91081,202.13508 C 245.36668,206.92129 230.45935,196.7415 243.8515,176.32177 C 254.2743,135.80305 265.45003,94.976539 267.46313,52.939957 C 278.24641,51.687903 288.9237,47.465671 299.89827,49.421568 z M 137.22777,69.707278 C 166.23618,69.494992 120.49304,93.04016 137.22777,69.707278 z M 451.29893,73.445566 C 466.93112,101.11163 425.11973,69.858477 451.29893,73.445566 z M 161.69157,80.207469 C 195.12998,110.69979 212.21109,153.72778 229.83786,194.32162 C 218.52396,208.95034 195.22647,168.57531 181.29522,159.91955 C 163.37572,141.33152 146.8742,120.6359 137.28274,96.425042 C 142.33212,87.653771 151.47682,79.604025 161.69157,80.207469 z M 432.38759,84.605455 C 469.44406,97.732695 430.28001,132.80731 416.73442,150.68472 C 399.36069,169.02554 380.60198,186.72201 360.97525,202.252 C 349.31821,186.47906 377.05703,160.7322 382.09772,141.74406 C 393.9733,119.90955 408.28746,98.903859 427.49484,82.84626 L 432.38759,84.605455 z M 128.54175,91.752182 C 115.85675,118.87612 92.85066,143.11942 78.60652,170.73021 C 62.000206,188.85762 85.067031,127.73706 91.166453,114.95867 C 96.261463,99.592362 115.43004,97.567564 128.54175,91.752182 z M 461.79912,96.095193 C 494.61461,101.4137 503.85761,132.95303 510.27958,161.07074 C 516.23377,173.44499 515.53354,194.75452 505.81343,172.70604 C 492.0604,146.83354 474.56757,123.01553 458.88546,98.239211 C 458.10505,96.914376 460.38765,93.356115 461.79912,96.095193 z M 79.944004,189.00264 C 125.91433,201.82926 173.80854,204.82394 221.2293,207.58413 C 227.40504,232.22763 205.75574,261.03157 200.77991,287.0056 C 198.98453,308.21225 184.56547,318.80777 169.17926,299.94722 C 133.95987,277.46442 98.659981,254.15242 59.27347,239.46953 C 61.480079,223.18569 64.663812,195.41065 79.944004,189.00264 z M 511.49636,197.57872 C 525.19335,217.70705 535.43047,253.2069 500.46557,254.90752 C 464.15836,271.3807 430.27042,292.57043 396.81888,314.12534 C 377.50066,297.83942 377.83971,261.83829 366.73344,237.87322 C 355.70058,219.68486 361.03872,202.51102 384.63974,208.83172 C 426.3772,206.34293 468.63407,204.30758 509.18742,193.18073 L 511.49636,197.57872 z M 254.26917,235.40139 C 267.88776,248.222 300.90986,260.92788 269.02803,272.58167 C 260.2717,268.87591 255.10883,237.44794 254.26917,235.40139 z M 331.50879,237.05064 C 324.01248,255.18663 320.23797,289.128 300.05044,261.46924 C 304.69039,252.09852 327.86234,239.96464 331.50879,237.05064 z M 50.312574,243.81254 C 48.701291,276.28009 44.929165,308.91572 44.438642,341.59724 C 38.920345,352.62589 18.235924,313.83416 27.328216,302.38831 C 33.148841,283.15159 39.24851,257.94992 50.312574,243.81254 z M 536.45493,250.2446 C 543.88366,277.15913 566.74879,308.63556 551.43159,335.47436 C 546.44721,350.66062 532.20173,359.53588 537.73084,335.75981 C 539.32919,307.71959 531.27597,272.91081 536.06286,249.15412 L 536.45493,250.2446 z M 263.28504,288.34215 C 282.51337,317.65756 244.52942,307.7959 226.78176,309.61741 C 238.1088,304.60141 254.71447,286.66664 263.28504,288.34215 z M 326.06629,289.99139 C 335.50507,298.75069 371.96349,315.98071 340.11655,310.44928 C 318.61751,314.37824 306.58789,306.07219 322.5447,287.44802 L 326.06629,289.99139 z M 281.9215,316.10443 C 315.80881,306.99659 294.53894,340.10829 290.93737,357.61043 C 289.75062,345.2197 277.62158,323.68751 281.9215,316.10443 z M 184.01134,318.7982 C 187.74469,337.09704 148.67935,342.5761 134.89818,354.17134 C 111.56567,365.55352 86.689349,375.07418 60.537891,376.68669 C 34.857135,351.83888 71.020358,335.11628 93.620682,331.96846 C 123.12694,324.08505 153.65422,321.18138 184.01134,318.7982 z M 384.50452,320.1176 C 389.61697,323.73995 377.26894,318.84237 384.50452,320.1176 z M 398.90793,321.38202 C 443.1239,326.51593 489.45667,330.51222 528.63386,353.62958 C 538.96391,391.76884 496.17809,381.49967 476.30611,371.10904 C 448.0456,360.17677 421.89729,344.64344 396.10421,328.91357 C 393.51601,325.98521 395.11468,321.97061 398.90793,321.38202 z M 203.30751,332.26703 C 227.72727,353.07009 264.71284,370.58598 280.20641,394.90874 C 262.93503,412.3019 241.56903,425.95006 223.50819,442.94849 C 205.20907,458.95886 187.67677,475.97038 172.41166,494.93753 C 148.89686,486.37063 113.76917,464.1279 143.2293,439.75157 C 162.91673,405.20393 177.76059,368.20734 192.14762,331.22251 C 195.29635,327.04196 200.53018,328.93547 203.30751,332.26703 z M 386.48362,331.16754 C 400.18586,350.95872 404.22928,376.64387 415.28715,398.36989 C 425.20431,421.90616 436.33007,445.05076 450.41934,466.4056 C 434.12186,488.50845 401.66036,511.25603 387.04573,475.11066 C 360.70616,446.5202 330.78928,421.55644 300.72289,397.02737 C 310.13036,373.45503 344.5899,362.82508 363.91108,344.81642 C 371.25297,341.05885 379.09963,331.94719 386.48362,331.16754 z M 42.781023,362.72308 C 57.078154,387.91587 19.840772,365.01162 42.781023,362.72308 z M 542.33724,366.4064 C 556.06367,380.56137 523.55694,385.37945 539.8395,367.13493 C 539.57763,366.48977 542.65437,362.78508 542.33724,366.4064 z M 53.336189,383.50357 C 75.268719,404.32092 91.325113,433.37386 113.18698,455.63259 C 124.54299,476.44851 74.161701,435.968 63.852016,426.25618 C 50.708583,416.70395 54.964638,397.77227 53.336189,383.50357 z M 528.09876,391.41994 C 533.1255,424.20402 506.16623,442.7593 481.40591,457.52287 C 471.49835,466.88801 451.01521,473.12226 468.89427,456.9389 C 489.1958,435.80533 506.44563,411.83156 525.1851,389.27592 C 526.23268,388.092 530.2919,389.29239 528.09876,391.41994 z M 294.45576,401.04054 C 302.97032,444.77201 314.18465,490.50238 303.45686,534.78351 C 269.8116,555.70792 267.45319,510.26453 271.11797,487.87885 C 272.91786,458.43851 279.8073,429.65527 286.31949,400.98556 C 288.31716,397.74595 292.52433,397.64886 294.45576,401.04054 z M 180.49295,506.64717 C 207.67798,522.26301 246.52699,528.98157 268.55252,545.52857 C 242.42325,558.17886 217.83828,546.5235 198.29377,527.97033 C 194.69189,524.69972 159.29181,498.82287 180.49295,506.64717 z M 404.51536,507.63672 C 390.01309,525.27528 368.18947,538.74082 347.86304,550.23549 C 335.91524,557.74156 287.21939,542.66729 323.67227,537.66117 C 350.78566,529.02491 379.06974,516.04027 404.51536,507.63672 z M 292.69657,548.86784 C 304.93678,562.57073 267.95665,553.48912 288.01486,547.90629 L 290.34982,548.11682 L 292.69657,548.86784 z diff --git a/src/toys/sweep.cpp b/src/toys/sweep.cpp new file mode 100644 index 0000000..4f26b81 --- /dev/null +++ b/src/toys/sweep.cpp @@ -0,0 +1,89 @@ +#include <2geom/sweep-bounds.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +using namespace Geom; + +class Sweep: public Toy { +public: + PointSetHandle hand; + unsigned count_a, count_b; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + std::vector<Rect> rects_a, rects_b; + cairo_set_source_rgb(cr, 0,0,0); + + for(unsigned i = 0; i < count_a; i++) + rects_a.emplace_back(hand.pts[i*2], hand.pts[i*2+1]); + + for(unsigned i = 0; i < count_b; i++) + rects_b.emplace_back(hand.pts[i*2 + count_a*2], hand.pts[i*2+1 + count_a*2]); + + { + std::vector<std::vector<unsigned> > res = sweep_bounds(rects_a); + cairo_set_line_width(cr,0.5); + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + for(unsigned i = 0; i < res.size(); i++) { + for(unsigned j = 0; j < res[i].size(); j++) { + draw_line_seg(cr, rects_a[i].midpoint(), rects_a[res[i][j]].midpoint()); + cairo_stroke(cr); + } + } + cairo_restore(cr); + }{ + std::vector<std::vector<unsigned> > res = sweep_bounds(rects_a, rects_b); + cairo_set_line_width(cr,0.5); + cairo_save(cr); + cairo_set_source_rgb(cr, 0.5, 0, 0.5); + for(unsigned i = 0; i < res.size(); i++) { + for(unsigned j = 0; j < res[i].size(); j++) { + draw_line_seg(cr, rects_a[i].midpoint(), rects_b[res[i][j]].midpoint()); + cairo_stroke(cr); + } + } + cairo_restore(cr); + } + cairo_set_line_width(cr,3); + cairo_set_source_rgba(cr,1,0,0,1); + for(unsigned i = 0; i < count_a; i++) + cairo_rectangle(cr, rects_a[i].left(), rects_a[i].top(), rects_a[i].width(), rects_a[i].height()); + cairo_stroke(cr); + + cairo_set_source_rgba(cr,0,0,1,1); + for(unsigned i = 0; i < count_b; i++) + cairo_rectangle(cr, rects_b[i].left(), rects_b[i].top(), rects_b[i].width(), rects_b[i].height()); + cairo_stroke(cr); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + bool should_draw_numbers() override { return false; } + public: + Sweep () { + count_a = 20; + count_b = 10; + for(unsigned i = 0; i < (count_a + count_b); i++) { + Point dim(uniform() * 90 + 10, uniform() * 90 + 10), + pos(uniform() * 500 + 50, uniform() * 500 + 50); + hand.pts.push_back(pos - dim/2); + hand.pts.push_back(pos + dim/2); + } + handles.push_back(&hand); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Sweep()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sweeper-toy.cpp b/src/toys/sweeper-toy.cpp new file mode 100644 index 0000000..5ca28db --- /dev/null +++ b/src/toys/sweeper-toy.cpp @@ -0,0 +1,170 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> + + +#include <cstdlib> +#include <set> +#include <vector> +#include <algorithm> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/ord.h> + +using namespace Geom; +using namespace std; + +#include "sweeper.cpp" + +double exp_rescale(double x){ return pow(10, x);} +std::string exp_formatter(double x){ return default_formatter(exp_rescale(x));} + + +class SweeperToy: public Toy { + int nb_paths; + int nb_curves_per_path; + int degree; + + std::vector<PointSetHandle> paths_handles; + std::vector<Slider> sliders; + Sweeper sweeper; + + void drawTile( cairo_t *cr, unsigned idx , unsigned line_width=1){ + if (idx>=sweeper.tiles_data.size()) return; + Rect box; + box = sweeper.tiles_data[idx].fbox; + box[X].expandBy(1); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0., .5); + cairo_set_line_width (cr, line_width); + cairo_stroke(cr); + box = sweeper.tiles_data[idx].tbox; + box[Y].expandBy(1); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 0., 0., 1., .5); + cairo_set_line_width (cr, line_width); + cairo_stroke(cr); + + Sweeper::Tile tile = sweeper.tiles_data[idx]; + D2<SBasis> p = sweeper.paths[tile.path][tile.curve].toSBasis(); + Interval dom = Interval(tile.f,tile.t); + cairo_set_source_rgba (cr, 0., 1., .5, .8); + p = portion(p, dom); + cairo_d2_sb(cr, p); + cairo_set_line_width (cr, line_width); + cairo_stroke(cr); + } + + void drawTiles( cairo_t *cr ){ + for (unsigned i=0; i<sweeper.tiles_data.size(); i++){ + drawTile( cr, i ); + } + +// for (unsigned i=0; i<sweeper.vtxboxes.size(); i++){ +// cairo_rectangle(cr, sweeper.vtxboxes[i]); +// cairo_set_source_rgba (cr, 0., 0., 0, 1); +// cairo_set_line_width (cr, 1); +// cairo_stroke(cr); +// } + } + + void enlightTile( cairo_t *cr, unsigned idx){ + drawTile(cr, idx, 4); + } + + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + + PathVector paths; + for (int i = 0; i < nb_paths; i++){ + paths_handles[i].pts.back()=paths_handles[i].pts.front(); + paths.push_back(Path(paths_handles[i].pts[0])); + for (unsigned j = 0; j+degree < paths_handles[i].size(); j+=degree){ + D2<SBasis> c = handles_to_sbasis(paths_handles[i].pts.begin()+j, degree); + paths[i].append(c); + } + paths[i].close(); + } + + //cairo_path(cr, paths); + cairo_set_source_rgba (cr, 0., 0., 0, 1); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + + double tol = exp_rescale(sliders[3].value()); + Rect tolbytol( Point(50,110), Point(50,110) ); + tolbytol.expandBy( tol ); + cairo_rectangle(cr, tolbytol); + cairo_stroke(cr); + + sweeper = Sweeper(paths,X, tol); + unsigned idx = (unsigned)(sliders[0].value()*(sweeper.tiles_data.size()-1)); + drawTiles(cr); + enlightTile(cr, idx); + + Toy::draw(cr, notify, width, height, save, timer_stream); + } + + public: + SweeperToy(int paths, int curves_in_path, int degree) : + nb_paths(paths), nb_curves_per_path(curves_in_path), degree(degree) { + for (int i = 0; i < nb_paths; i++){ + paths_handles.emplace_back(); + } + for(int i = 0; i < nb_paths; i++){ + for(int j = 0; j < (nb_curves_per_path*degree)+1; j++){ + paths_handles[i].push_back(uniform()*400, 100+ uniform()*300); + } + handles.push_back(&paths_handles[i]); + } + sliders.emplace_back(0.0, 1, 0, 0.0, "intersection chooser"); + sliders.emplace_back(0.0, 1, 0, 0.0, "ray chooser"); + sliders.emplace_back(0.0, 1, 0, 0.0, "area chooser"); + sliders.emplace_back(-5.0, 2, 0, 0.0, "tolerance chooser"); + handles.push_back(&(sliders[0])); + handles.push_back(&(sliders[1])); + handles.push_back(&(sliders[2])); + handles.push_back(&(sliders[3])); + sliders[0].geometry(Point(50, 20), 250); + sliders[1].geometry(Point(50, 50), 250); + sliders[2].geometry(Point(50, 80), 250); + sliders[3].geometry(Point(50, 110), 250); + sliders[3].formatter(&exp_formatter); + } + + void first_time(int /*argc*/, char** /*argv*/) override { + + } +}; + +int main(int argc, char **argv) { + unsigned paths=10; + unsigned curves_in_path=3; + unsigned degree=1; + if(argc > 3) + sscanf(argv[3], "%d", °ree); + if(argc > 2) + sscanf(argv[2], "%d", &curves_in_path); + if(argc > 1) + sscanf(argv[1], "%d", &paths); + init(argc, argv, new SweeperToy(paths, curves_in_path, degree)); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/sweeper.cpp b/src/toys/sweeper.cpp new file mode 100644 index 0000000..7dae586 --- /dev/null +++ b/src/toys/sweeper.cpp @@ -0,0 +1,1135 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> + +#include <cstdlib> +#include <cstdio> +#include <set> +#include <vector> +#include <algorithm> + +#include <limits.h> +#define NULL_IDX UINT_MAX + +#include <2geom/orphan-code/intersection-by-smashing.h> +#include "../2geom/orphan-code/intersection-by-smashing.cpp" + +using namespace Geom; +using namespace std; + + +/* +The sweeper class takes a PathVector as input and generates "events" to let clients construct the relevant graph. + +The basic strategy is the following: +The path is split into "tiles": a tile consists in 2 boxes related by a (monotonic) curve. + +The tiles are created at the very beginning, using a sweep, but *no care* is taken to topology +information at this step! All the boxes of all the tiles are then enlarged so that they are +either equal or disjoint. +[TODO: we should look for curves traversing boxes, split them and repeat the process...] + +The sweeper maintains a virtual sweepline, that is the limit of the "known area". The tiles can have 2 states: +open if they have one end in the known area, and one in the unknown, closed otherwise. +[TODO: open/close should belong to tiles pointers, not tiles...] + +The sorted list of open tiles intersecting the sweep line is called the "context". +*!*WARNING*!*: because the sweep line is not straight, closed tiles can still be in the context!! +they can only be removed once the end of the last box is reached. + +The events are changes in the context when the sweep line crosses boxes. +They are obtained by sorting the tiles according to one or the other of theire end boxes depending +on the open/close state. + +A "big" event happens when the sweep line reaches a new 'box'. After such a "big" event, the sweep +line goes round the new box along it's 3 other sides. +N.B.: in an ideal world, all tiles ending at one box would be on one side, all the tiles starting +there on the other. Unfortunately, because we have boxes as vertices, things are not that nice: +open/closed tiles can appear in any order around a vertex, even in the monotonic case(!). Morover, +our fat vertices have a non zero "duration", during which many things can happen: this is why we +have to keep closed edges in the context until both ends of theire boxes are reached... + + +To keep things uniform, such "big" events are split into elementary ones: opening/closing of a single +edge. One such event is generated for each tile around the current 'box', in CCW order (geometrically, +the sweepline is deformed in a neighborhood of the box to go round it for a certain amount, enter the +box and come back inside the box; the piece inside the box is a "virtual edge" that is not added for +good but that we keep track of). The event knows if it's the last one in such a sequence, so that the +client knows when to do the additional work required to "close" the vertex construction. Hmmm. It's +hard to explain the moves without a drawing here...(see sweep.svg in the doc dir). There are + +*Closings: insert a new the relevant tile in the context with a "exit" flag. + +*Openings: insert a new the relevant tile in the context with a "entry" flag. + +At the end of a box, the relevant exit/entries are purged from the context. + + +N.B. I doubt we can do boolops without building the full graph, i.e. having different clients to obtain +different outputs. So splitting sweeper/grpah builder is maybe not so relevant w/r to functionality +(only code organization). +*/ + + +//TODO: decline intersections algorithms for each kind of curves... +//TODO: write an intersector that can work on sub domains. +//TODO: factor computation of derivative and the like out. +std::vector<SmashIntersection> monotonic_smash_intersect( Curve const &a, Interval a_dom, + Curve const &b, Interval b_dom, double tol){ + std::vector<SmashIntersection> result; + D2<SBasis> asb = a.toSBasis(); + asb = portion( asb, a_dom ); + D2<SBasis> bsb = b.toSBasis(); + bsb = portion( bsb, b_dom ); + result = monotonic_smash_intersect(asb, bsb, tol ); + for (auto & i : result){ + i.times[X] *= a_dom.extent(); + i.times[X] += a_dom.min(); + i.times[Y] *= b_dom.extent(); + i.times[Y] += b_dom.min(); + } + return result; +} + + + +class Sweeper{ +public: + + //--------------------------- + // utils... + //--------------------------- + + //near predicate utilized in process_splits + template<typename T> + struct NearPredicate { + double tol; + NearPredicate(double eps):tol(eps){} + NearPredicate(){tol = EPSILON;}//??? + bool operator()(T x, T y) { return are_near(x, y, tol); } }; + + // ensures that f and t are elements of a vector, sorts and uniqueifies + // also asserts that no values fall outside of f and t + // if f is greater than t, the sort is in reverse + void process_splits(std::vector<double> &splits, double f, double t, double tol=EPSILON) { + //splits.push_back(f); + //splits.push_back(t); + std::sort(splits.begin(), splits.end()); + std::vector<double>::iterator end = std::unique(splits.begin(), splits.end(), NearPredicate<double>(tol)); + splits.resize(end - splits.begin()); + + //remove any splits which fall outside t / f + while(!splits.empty() && splits.front() < f+tol) splits.erase(splits.begin()); + splits.insert(splits.begin(), f); + //splits[0] = f; + while(!splits.empty() && splits.back() > t-tol) splits.erase(splits.end() - 1); + splits.push_back(t); + //splits.back() = t; + } + + struct IntersectionMinTimeOrder { + unsigned which; + IntersectionMinTimeOrder (unsigned idx) : which(idx) {} + bool operator()(SmashIntersection const &a, SmashIntersection const &b) const { + return a.times[which].min() < b.times[which].min(); + } + }; + + // ensures that f and t are elements of a vector, sorts and uniqueifies + // also asserts that no values fall outside of f and t + // if f is greater than t, the sort is in reverse + std::vector<std::pair<Interval, Rect> > + process_intersections(std::vector<SmashIntersection> &inters, unsigned which, unsigned tileidx) { + std::vector<std::pair<Interval, Rect> > result; + std::pair<Interval, Rect> apair; + Interval dom ( tiles_data[tileidx].f, tiles_data[tileidx].t ); + apair.first = Interval( dom.min() ); + apair.second = tiles_data[tileidx].fbox; + result.push_back( apair ); + + std::sort(inters.begin(), inters.end(), IntersectionMinTimeOrder(which) ); + for (auto & inter : inters){ + if ( !inter.times[which].intersects( dom ) )//this should never happen. + continue; + if ( result.back().first.intersects( inter.times[which] ) ){ + result.back().first.unionWith( inter.times[which] ); + result.back().second.unionWith( inter.bbox ); + }else{ + apair.first = inter.times[which]; + apair.second = inter.bbox; + result.push_back( apair ); + } + } + apair.first = Interval( dom.max() ); + apair.second = tiles_data[tileidx].tbox; + if ( result.size() > 1 && result.back().first.intersects( apair.first ) ){ + result.back().first.unionWith( apair.first ); + result.back().second.unionWith( apair.second ); + }else{ + result.push_back( apair ); + } + return result; + } + + + //--------------------------- + // Tiles. + //--------------------------- + + //A tile is a "light edge": just two boxes, joint by a curve. + //it is open iff intersected by the sweepline. + class Tile{ + public: + unsigned path; + unsigned curve; + double f; + double t; + Rect fbox, tbox; + bool reversed;//with respect to sweep direction. Flip f/t instead? + bool open;//means sweepline currently cuts it (i.e. one end in the known area, the other in the unknown). + int state;//-1: both ends in unknown area, 0:one end in each, 1: both in known area. + //Warning: we can not delete a tile immediately when it's past(=closed again), only when the end of it's tbox is!. + Rect bbox(){Rect b = fbox; b.unionWith(tbox); return b;} + Point min(){return ( bbox().min() ); } + Point max(){return ( bbox().max() ); } +// Rect cur_box() const {return ((open)^(reversed) ) ? tbox : fbox; } + Rect cur_box() const { return ((state>=0)^(reversed) ) ? tbox : fbox; } + Rect first_box() const {return ( reversed ) ? tbox : fbox; } + Rect last_box() const {return ( reversed ) ? fbox : tbox; } + }; + + D2<SBasis> tileToSB(Tile const &tile){ + //TODO: don't convert each time!!!!!! + assert( tile.path < paths.size() ); + assert( tile.curve < paths[tile.path].size() ); + D2<SBasis> c = paths[tile.path][tile.curve].toSBasis(); + c = portion( c, Interval( tile.f, tile.t ) ); + return c; + } + + //SweepOrder for Rects or Tiles. + class SweepOrder{ + public: + Dim2 dim; + SweepOrder(Dim2 d) : dim(d) {} + bool operator()(const Rect &a, const Rect &b) const { + return Point::LexLessRt(dim)(a.min(), b.min()); + } + bool operator()(const Tile &a, const Tile &b) const { + return Point::LexLessRt(dim)(a.cur_box().min(), b.cur_box().min()); + } + }; + + class PtrSweepOrder{ + public: + Dim2 dim; + std::vector<Tile>::iterator const begin; + PtrSweepOrder(std::vector<Tile>::iterator const beg, Dim2 d) : dim(d), begin(beg){} + bool operator()(const unsigned a, const unsigned b) const { + return Point::LexLessRt(dim)((begin+a)->cur_box().min(), (begin+b)->cur_box().min()); + } + }; + + + //--------------------------- + // Vertices. + //--------------------------- + + //A ray is nothing but an edge ending or starting at a given vertex, + some info about when/where it exited a "separating" box; + struct Ray{ + public: + unsigned tile; + bool centrifuge;//true if the intrinsic orientation of curve points away from the vertex. + //exit info: + unsigned exit_side;//0:y=min; 1:x=max; 2:y=max; 3:x=min. + double exit_place; //x or y value on the exit line. + double exit_time; //exit time on curve. + Ray(){tile = NULL_IDX; exit_side = 4;} + Ray(unsigned tile_idx, unsigned s, double p, double t){ + tile = tile_idx; + exit_side =s; + exit_place = p; + exit_time = t; + } + Ray(unsigned tile_idx, bool outward){ + tile = tile_idx; + exit_side = 4; + centrifuge = outward; + exit_time = (centrifuge) ? 2 : -1 ; + } + void setExitInfo( unsigned side, double place, double time){ + exit_side = side; + exit_place = place; + exit_time = time; + } + }; + + class FatVertex : public Rect{ + public: + std::vector<Ray> rays; + FatVertex(const Rect &r, unsigned const tile, bool centrifuge) : Rect(r){ + rays.push_back( Ray(tile, centrifuge) ); + } + FatVertex(Rect r) : Rect(r){} + FatVertex() : Rect(){} + void erase(unsigned from, unsigned to){ + unsigned size = to-from; + from = from % rays.size(); + to = from + size; + + if (to >= rays.size() ){ + to = to % rays.size(); + rays.erase( rays.begin()+from, rays.end() ); + rays.erase( rays.begin(), rays.begin()+to ); + }else{ + rays.erase( rays.begin()+from, rays.begin()+to ); + } + + } + }; + + //--------------------------- + // Context related stuff. + //--------------------------- + + class Event{ + public: + bool opening;//true means an edge is added, otherwise an edge is removed from context. + unsigned tile;//which tile to open/close. + unsigned insert_at;//where to insert the next tile in the context. + //unsigned erase_at;//idx of the tile to close in the context. = context.find(tile). + bool to_be_continued; + bool empty(){ + return tile==NULL_IDX; + } + Event(){ + opening = false; + insert_at = 0; + //erase_at = 0; + tile = NULL_IDX; + to_be_continued = false; + } + }; + + void printEvent(Event const &e){ + std::printf("Event: "); + std::printf("%s, ", e.opening?"opening":"closing"); + std::printf("insert_at:%u, ", e.insert_at); + //std::printf("erase_at:%u, ", e.erase_at); + std::printf("tile:%u.\n", e.tile); + } + + class Context : public std::vector<std::pair<unsigned,bool> >{//first = tile, second = true if it's a birth (+). + public: + Point last_pos; + FatVertex pending_vertex; + Event pending_event; + + unsigned find(unsigned const tile, bool positive_only=false){ + for (unsigned i=0; i<size(); i++){ + if ( (*this)[i].first == tile ){ + if ( (*this)[i].second || !positive_only ) return i; + } + } + return (*this).size(); + } + }; + void printContext(){ + std::printf("context:["); + for (unsigned i=0; i<context.size(); i++){ + unsigned tile = context[i].first; + assert( tile<tiles_data.size() ); + std::printf(" %s%u%s", (tiles_data[ tile ].reversed)?"-":"+", tile, (context[i].second)?"o":"c"); +// assert( context[i].second || !tiles_data[ tile ].open); + assert( context[i].second || tiles_data[ tile ].state==1); + } + std::printf("]\n"); + } + + + + //---- + //This is the heart of it all!! Take particular care to non linear sweep line... + //---- + //Given a point on the sweep line, (supposed to be the min() of a vertex not yet connected to the already known part), + //find the first edge "after" it in the context. Pretty tricky atm :-( + //TODO: implement this as a lower_bound (?). + + unsigned contextRanking(Point const &pt){ + +// std::printf("contextRanking:------------------------------------\n"); + + unsigned rank = context.size(); + std::vector<unsigned> unmatched_closed_tiles = std::vector<unsigned>(); + +// std::printf("Scan context.\n"); + + for (unsigned i=0; i<context.size(); i++){ + + unsigned tile_idx = context[i].first; + assert( tile_idx < tiles_data.size() ); +// std::printf("testing %u (e=%u),", i, tile_idx); + + Tile tile = tiles_data[tile_idx]; + assert( tile.state >= 0 ); + + //if the tile is open (i.e. not both ends in the known area) and point is below/above the tile's bbox: + if ( tile.state == 0 ){ +// std::printf("opened tile, "); + if (pt[1-dim] < tile.min()[1-dim] ) { +// printContext(); +// std::printf("below bbox %u!\n", i); + rank = i; + break; + } + if (pt[1-dim] > tile.max()[1-dim] ){ +// std::printf("above bbox %u!\n", i); + continue; + } + + //TODO: don't convert each time!!!!!! + D2<SBasis> c = tileToSB( tile ); + + std::vector<double> times = roots(c[dim]-pt[dim]); + if (times.size()==0){ + assert( tile.first_box()[dim].contains(pt[dim]) ); + if ( pt[1-dim] < tile.first_box()[1-dim].min() ){ +// std::printf("open+hit box %u!\n", i); + rank = i; + break; + }else{ + continue; + } + } + if ( pt[1-dim] < c[1-dim](times.front()) ){ +// std::printf("open+hit curve %u!\n", i); + rank = i; + break; + } + } + +// std::printf("closed tile, "); + + + //At this point, the tile is closed (i.e. both ends are in the known area) + //Such tiles do 'nested parens' like travels in the unknown area. + //We are interested in the second occurrence only (to give a chance to open tiles to exist in between). + if ( unmatched_closed_tiles.size()==0 || tile_idx != unmatched_closed_tiles.back() ){ + unmatched_closed_tiles.push_back( tile_idx ); +// std::printf("open paren %u\n",tile_idx); + continue; + } + unmatched_closed_tiles.pop_back(); + +// std::printf("close paren, "); + + if ( !tile.bbox().contains( pt ) ){ + continue; + } + +// std::printf("in bbox, "); + + //At least one of fbox[dim], tbox[dim] has to contain the pt[dim]: assert it? + + //Find intersection with the hline(vline if dim=Y) through the point + double hit_place; + //TODO: don't convert each time!!!!!! + D2<SBasis> c = tileToSB( tile ); + std::vector<double> times = roots(c[1-dim]-pt[1-dim]); + if ( times.size()>0 ){ +// std::printf("hit curve,"); + hit_place = c[dim](times.front()); + }else{ +// std::printf("hit box, "); + //if there was no intersection, the line went through the first_box + assert( tile.first_box()[1-dim].contains(pt[1-dim]) ); + continue; + } + + if ( pt[dim] > hit_place ){ +// std::printf("wrong side, "); + continue; + } +// std::printf("good side, "); + rank = i; + break; + } + +// std::printf("rank %u.\n", rank); +// printContext(); + assert( rank<=tiles_data.size() ); + return rank; + } + + //TODO: optimize this. + //it's done the slow way for debugging purpose... + void purgeDeadTiles(){ + //std::printf("purge "); + //printContext(); + for (unsigned i=0; i<context.size(); i++){ + assert( context[i].first<tiles_data.size() ); + Tile tile = tiles_data[context[i].first]; + if (tile.state==1 && Point::LexLessRt(dim)( tile.fbox.max(), context.last_pos ) && Point::LexLessRt(dim)( tile.tbox.max(), context.last_pos ) ){ +// if (!tile.open && Point::LexLessRt(dim)( tile.fbox.max(), context.last_pos ) && Point::LexLessRt(dim)( tile.tbox.max(), context.last_pos ) ){ + unsigned j; + for (j=i+1; j<context.size() && context[j].first != context[i].first; j++){} + assert ( j < context.size() ); + if ( context[j].first == context[i].first){ + assert ( context[j].second == !context[i].second ); + context.erase(context.begin()+j); + context.erase(context.begin()+i); +// printContext(); + i--; + } + } + } + return; + } + + void applyEvent(Event event){ +// std::printf("Apply event : "); + if(event.empty()){ +// std::printf("empty event!\n"); + return; + } + +// printEvent(event); +// std::printf(" old "); +// printContext(); + + assert ( context.begin() + event.insert_at <= context.end() ); + + if (!event.opening){ +// unsigned idx = event.erase_at; +// assert( idx == context.find(event.tile) ); +// assert( context[idx].first == event.tile); + tiles_data[event.tile].open = false; + tiles_data[event.tile].state = 1; + //context.erase(context.begin()+idx); + unsigned idx = event.insert_at; + context.insert(context.begin()+idx, std::pair<unsigned, bool>(event.tile, false) ); + }else{ + unsigned idx = event.insert_at; + tiles_data[event.tile].open = true; + tiles_data[event.tile].state = 0; + context.insert(context.begin()+idx, std::pair<unsigned, bool>(event.tile, true) ); + sortTiles(); + } + context.last_pos = context.pending_vertex.min(); + context.last_pos[1-dim] = context.pending_vertex.max()[1-dim]; + +// std::printf(" new "); +// printContext(); +// std::printf("\n"); + //context.pending_event = Event();is this a good idea? + } + + + + //--------------------------- + // Sweeper. + //--------------------------- + + PathVector paths; + std::vector<Tile> tiles_data; + std::vector<unsigned> tiles; + std::vector<Rect> vtxboxes; + Context context; + double tol; + Dim2 dim; + + + //------------------------------- + //-- Tiles preparation. + //------------------------------- + + //split input paths into monotonic pieces... + void createMonotonicTiles(){ + for ( unsigned i=0; i<paths.size(); i++){ + for ( unsigned j=0; j<paths[i].size(); j++){ + //find the points where slope is 0°, 45°, 90°, 135°... + D2<SBasis> deriv = derivative( paths[i][j].toSBasis() ); + std::vector<double> splits0 = roots( deriv[X] ); + std::vector<double> splits90 = roots( deriv[Y] ); + std::vector<double> splits45 = roots( deriv[X]- deriv[Y] ); + std::vector<double> splits135 = roots( deriv[X] + deriv[Y] ); + std::vector<double> splits; + splits.insert(splits.begin(), splits0.begin(), splits0.end() ); + splits.insert(splits.begin(), splits90.begin(), splits90.end() ); + splits.insert(splits.begin(), splits45.begin(), splits45.end() ); + splits.insert(splits.begin(), splits135.begin(), splits135.end() ); + process_splits(splits,0,1); + + for(unsigned k = 1; k < splits.size(); k++){ + Tile tile; + tile.path = i; + tile.curve = j; + tile.f = splits[k-1]; + tile.t = splits[k]; + //TODO: use meaningful tolerance here!! + Point fp = paths[i][j].pointAt(tile.f); + Point tp = paths[i][j].pointAt(tile.t); + tile.fbox = Rect(fp, fp ); + tile.tbox = Rect(tp, tp ); + tile.open = false; + tile.state = -1; + tile.reversed = Point::LexLessRt(dim)(tp, fp); + + tiles_data.push_back(tile); + } + } + } + std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); + } + + void splitTile(unsigned i, double t, double tolerance=0, bool sort = true){ + assert( i<tiles_data.size() ); + Tile newtile = tiles_data[i]; + assert( newtile.f < t && t < newtile.t ); + newtile.f = t; + //newtile.fbox = fatPoint(paths[newtile.path][newtile.curve].pointAt(t), tolerance ); + Point p = paths[newtile.path][newtile.curve].pointAt(t); + newtile.fbox = Rect(p, p); + newtile.fbox.expandBy( tolerance ); + tiles_data[i].tbox = newtile.fbox; + tiles_data[i].t = t; + tiles_data.insert(tiles_data.begin()+i+1, newtile); + if (sort) + std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); + } + void splitTile(unsigned i, SmashIntersection inter, unsigned which,bool sort = true){ + double t = inter.times[which].middle(); + assert( i<tiles_data.size() ); + Tile newtile = tiles_data[i]; + assert( newtile.f < t && t < newtile.t ); + newtile.f = t; + newtile.fbox = inter.bbox; + tiles_data[i].tbox = newtile.fbox; + tiles_data[i].t = t; + tiles_data.insert(tiles_data.begin()+i+1, newtile); + if (sort) + std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); + } +#if 0 + void splitTile(unsigned i, std::vector<double> const ×, double tolerance=0, bool sort = true){ + if ( times.size()<3 ) return; + assert( i<tiles_data.size() ); + std::vector<Tile> pieces ( times.size()-2, tiles_data[i] ); + Rect prevbox = tiles_data[i].fbox; + for (unsigned k=0; k < times.size()-2; k++){ + pieces[k].f = times[k]; + pieces[k].t = times[k+1]; + pieces[k].fbox = prevbox; + //TODO: use relevant precision here. + prevbox = fatPoint(paths[tiles_data[i].path][tiles_data[i].curve].pointAt(times[k+1]), tolerance ); + pieces[k].tbox = prevbox; + } + tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end() ); + unsigned newi = i + times.size()-2; + assert( newi<tiles_data.size() ); + assert( newi>=1 ); + tiles_data[newi].f = tiles_data[newi-1].t; + tiles_data[newi].fbox = tiles_data[newi-1].tbox; + + if (sort) + std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) ); + } +#else + void splitTile(unsigned i, std::vector<std::pair<Interval,Rect> > const &cuts, bool sort = true){ + assert ( cuts.size() >= 2 ); + assert( i<tiles_data.size() ); + std::vector<Tile> pieces ( cuts.size()-1, tiles_data[i] ); + for (unsigned k=1; k+1 < cuts.size(); k++){ + pieces[k-1].t = cuts[k].first.middle(); + pieces[k ].f = cuts[k].first.middle(); + pieces[k-1].tbox = cuts[k].second; + pieces[k ].fbox = cuts[k].second; + } + pieces.front().fbox.unionWith( cuts[0].second ); + pieces.back().tbox.unionWith( cuts.back().second ); + + tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end()-1 ); + unsigned newi = i + cuts.size()-2; + assert( newi < tiles_data.size() ); + tiles_data[newi] = pieces.back(); + + if (sort) + std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) ); + } +#endif + + //TODO: maybe not optimal. For a fully optimized sweep, it would be nice to have + //an efficient way to way find *only the first* intersection (in sweep direction)... + void splitIntersectingTiles(){ + //make sure it is sorted, but should be ok. (remove sorting at the end of monotonic tiles creation? + std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); + +// std::printf("\nFind intersections: tiles_data.size():%u\n", tiles_data.size() ); + + for (unsigned i=0; i+1<tiles_data.size(); i++){ + //std::printf("\ni=%u (%u([%f,%f]))\n", i, tiles_data[i].curve, tiles_data[i].f, tiles_data[i].t ); + std::vector<SmashIntersection> inters_on_i; + for (unsigned j=i+1; j<tiles_data.size(); j++){ + //std::printf(" j=%u (%u)\n", j,tiles_data[j].curve ); + if ( Point::LexLessRt(dim)(tiles_data[i].max(), tiles_data[j].min()) ) break; + + unsigned pi = tiles_data[i].path; + unsigned ci = tiles_data[i].curve; + unsigned pj = tiles_data[j].path; + unsigned cj = tiles_data[j].curve; + std::vector<SmashIntersection> intersections; + + intersections = monotonic_smash_intersect(paths[pi][ci], Interval(tiles_data[i].f, tiles_data[i].t), + paths[pj][cj], Interval(tiles_data[j].f, tiles_data[j].t), tol ); + inters_on_i.insert( inters_on_i.end(), intersections.begin(), intersections.end() ); + std::vector<std::pair<Interval, Rect> > cuts = process_intersections(intersections, 1, j); + +// std::printf(" >|%u/%u|=%u. times_j:%u", i, j, crossings.size(), times_j.size() ); + + splitTile(j, cuts, false); + j += cuts.size()-2; + } + + //process_splits(times_i, tiles_data[i].f, tiles_data[i].t); + //assert(times_i.size()>=2); + //splitTile(i, times_i, tol, false); + //i+=times_i.size()-2; + std::vector<std::pair<Interval, Rect> > cuts_on_i = process_intersections(inters_on_i, 0, i); + splitTile(i, cuts_on_i, false); + i += cuts_on_i.size()-2; + //std::printf("new i:%u, tiles_data: %u\n",i ,tiles_data.size()); + //std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); + std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); + } + //this last sorting should be useless!! + std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); + } + + void sortTiles(){ + std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) ); + } + + + //------------------------------- + //-- Vertices boxes cookup. + //------------------------------- + + void fuseInsert(const Rect &b, std::vector<Rect> &boxes, Dim2 dim){ + //TODO: this can be optimized... + for (unsigned i=0; i<boxes.size(); i++){ + if ( Point::LexLessRt(dim)( b.max(), boxes[i].min() ) ) break; + if ( b.intersects( boxes[i] ) ){ + Rect bigb = b; + bigb.unionWith( boxes[i] ); + boxes.erase( boxes.begin()+i ); + fuseInsert( bigb, boxes, dim); + return; + } + } + std::vector<Rect>::iterator pos = std::lower_bound(boxes.begin(), boxes.end(), b, SweepOrder(dim) ); + boxes.insert( pos, b ); + } + + //debug only!! + bool isContained(Rect b, std::vector<Rect> const &boxes ){ + for (const auto & boxe : boxes){ + if ( boxe.contains(b) ) return true; + } + return false; + } + + //Collect vertex boxes. Fuse overlapping ones. + //NB: enlarging a vertex may create intersection with already scanned ones... + std::vector<Rect> collectBoxes(){ + std::vector<Rect> ret; + for (auto & i : tiles_data){ + fuseInsert(i.fbox, ret, dim); + fuseInsert(i.tbox, ret, dim); + } + return ret; + } + + //enlarge tiles ends to match the vertices bounding boxes. + //remove edges fully contained in one vertex bbox. + void enlargeTilesEnds(const std::vector<Rect> &boxes ){ + for (unsigned i=0; i<tiles_data.size(); i++){ + std::vector<Rect>::const_iterator f_it; + f_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].fbox, SweepOrder(dim) ); + if ( f_it==boxes.end() ) f_it--; + while (!(*f_it).contains(tiles_data[i].fbox) && f_it != boxes.begin()){ + f_it--; + } + assert( (*f_it).contains(tiles_data[i].fbox) ); + tiles_data[i].fbox = *f_it; + + std::vector<Rect>::const_iterator t_it; + t_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].tbox, SweepOrder(dim) ); + if ( t_it==boxes.end() ) t_it--; + while (!(*t_it).contains(tiles_data[i].tbox) && t_it != boxes.begin()){ + t_it--; + } + assert( (*t_it).contains(tiles_data[i].tbox) ); + tiles_data[i].tbox = *t_it; + + //NB: enlarging the ends may swapp their sweep order!!! + tiles_data[i].reversed = Point::LexLessRt(dim)( tiles_data[i].tbox.min(), tiles_data[i].fbox.min()); + + if ( f_it==t_it ){ + tiles_data.erase(tiles_data.begin()+i); + i-=1; + } + } + } + + //Make sure tiles stop at vertices. Split them if needed. + //Returns true if at least one tile was split. + bool splitTilesThroughFatPoints(std::vector<Rect> &boxes ){ + + std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) ); + std::sort(boxes.begin(), boxes.end(), SweepOrder(dim) ); + + bool result = false; + for (unsigned i=0; i<tiles_data.size(); i++){ + for (auto & boxe : boxes){ + if ( Point::LexLessRt(dim)( tiles_data[i].max(), boxe.min()) ) break; + if ( Point::LexLessRt(dim)( boxe.max(), tiles_data[i].min()) ) continue; + if ( !boxe.intersects( tiles_data[i].bbox() ) ) continue; + if ( tiles_data[i].fbox.intersects( boxe ) ) continue; + if ( tiles_data[i].tbox.intersects( boxe ) ) continue; + + //at this point box[k] intersects the curve bbox away from the fbox and tbox. + + D2<SBasis> c = tileToSB( tiles_data[i] ); +//----------> use level-set!! + for (unsigned corner=0; corner<4; corner++){ + unsigned D = corner % 2; + double val = boxe.corner(corner)[D]; + std::vector<double> times = roots( c[D] - val ); + if ( times.size()>0 ){ + double t = lerp( times.front(), tiles_data[i].f, tiles_data[i].t ); + double hit_place = c[1-D](times.front()); + if ( boxe[1-D].contains(hit_place) ){ + result = true; + //taking a point on the boundary is dangerous!! + //Either use >0 tolerance here, or find 2 intersection points and split in between. + splitTile( i, t, tol, false); + break; + } + } + } + } + } + return result; + } + + + + + + //TODO: rewrite all this!... + //------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------- + //------------------------------- + //-- ccw Sorting of rays around a vertex. + //------------------------------- + //------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------- + //returns an (infinite) rect around "a" separating it from "b". Nota: 3 sides are infinite! + //TODO: place the cut where there is most space... + OptRect separate(Rect const &a, Rect const &b){ + Rect ret ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) ); + double gap = 0; + unsigned dir = 4; + if (b[X].min() - a[X].max() > gap){ + gap = b[X].min() - a[X].max(); + dir = 0; + } + if (a[X].min() - b[X].max() > gap){ + gap = a[X].min() - b[X].max(); + dir = 1; + } + if (b[Y].min() - a[Y].max() > gap){ + gap = b[Y].min() - a[Y].max(); + dir = 2; + } + if (a[Y].min() - b[Y].max() > gap){ + gap = a[Y].min() - b[Y].max(); + dir = 3; + } + switch (dir) { + case 0: ret[X].setMax(( a.max()[X] + b.min()[X] )/ 2); break; + case 1: ret[X].setMin(( b.max()[X] + a.min()[X] )/ 2); break; + case 2: ret[Y].setMax(( a.max()[Y] + b.min()[Y] )/ 2); break; + case 3: ret[Y].setMin(( b.max()[Y] + a.min()[Y] )/ 2); break; + case 4: return OptRect(); + } + return OptRect(ret); + } + + //Find 4 lines (returned as a Rect sides) that cut all the rays (=edges). *!* some side might be infinite. + OptRect isolateVertex(Rect const &box){ + OptRect sep ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) ); + //separate this vertex from the others. Find a better way. + for (auto & vtxboxe : vtxboxes){ + if ( Point::LexLessRt(dim)( sep->max(), vtxboxe.min() ) ){ + break; + } + if ( vtxboxe!=box ){//&& !vtxboxes[i].intersects(box) ){ + OptRect sepi = separate(box, vtxboxe); + if ( sep && sepi ){ + sep = intersect(*sep, *sepi); + }else{ + std::cout<<"box="<<box<<"\n"; + std::cout<<"vtxboxes[i]="<<vtxboxe<<"\n"; + assert(sepi); + } + } + } + if (!sep) THROW_EXCEPTION("Invalid intersection data."); + return sep; + } + + //TODO: argh... rewrite to have "dim"=min first place. + struct ExitPoint{ + public: + unsigned side; //0:y=min; 1:x=max; 2:y=max; 3:x=min. + double place; //x or y value on the exit line. + unsigned ray_idx; + double time; //exit time on curve. + ExitPoint(){} + ExitPoint(unsigned s, double p, unsigned r, double t){ + side =s; + place = p; + ray_idx = r; + time = t; + } + }; + + + class ExitOrder{ + public: + bool operator()(Ray a, Ray b) const { + if ( a.exit_side < b.exit_side ) return true; + if ( a.exit_side > b.exit_side ) return false; + if ( a.exit_side <= 1) { + return ( a.exit_place < b.exit_place ); + } + return ( a.exit_place > b.exit_place ); + } + }; + + void printRay(Ray const &r){ + std::printf("Ray: tile=%u, centrifuge=%u, side=%u, place=%f\n", + r.tile, r.centrifuge, r.exit_side, r.exit_place); + } + void printVertex(FatVertex const &v){ + std::printf("Vertex: [%f,%f]x[%f,%f]\n", v[X].min(),v[X].max(),v[Y].min(),v[Y].max() ); + for (const auto & ray : v.rays){ + printRay(ray); + } + } + + //TODO: use a partial order on input coming from the context + Try quadrant order just in case it's enough. + //TODO: use monotonic assumption. + void sortRays( FatVertex &v ){ + OptRect sep = isolateVertex(v); + + for (unsigned i=0; i < v.rays.size(); i++){ + v.rays[i].centrifuge = (tiles_data[ v.rays[i].tile ].fbox == v); + v.rays[i].exit_time = v.rays[i].centrifuge ? 1 : 0 ; + } + + for (unsigned i=0; i < v.rays.size(); i++){ + //TODO: don't convert each time!!! + assert( v.rays[i].tile < tiles_data.size() ); + D2<SBasis> c = tileToSB( tiles_data[ v.rays[i].tile ] ); + + for (unsigned side=0; side<4; side++){//scan X or Y direction, on level min or max... + double level = sep->corner( side )[1-side%2]; + if (level != infinity() && level != -infinity() ){ + std::vector<double> times = roots(c[1-side%2]-level); + if ( times.size() > 0 ) { + double t; + assert( v.rays[i].tile < tiles_data.size() ); + if (tiles_data[ v.rays[i].tile ].fbox == v){ + t = times.front(); + if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time > t ){ + v.rays[i].setExitInfo( side, c[side%2](t), t); + } + }else{ + t = times.back(); + if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time < t ){ + v.rays[i].setExitInfo( side, c[side%2](t), t); + } + } + } + } + } + } + + //Rk: at this point, side == 4 means the edge is contained in the intersection box (?)...; + std::sort( v.rays.begin(), v.rays.end(), ExitOrder() ); + } + + + //------------------------------- + //-- initialize all data. + //------------------------------- + + Sweeper(){} + Sweeper(PathVector const &input_paths, Dim2 sweep_dir, double tolerance=1e-5){ + paths = input_paths;//use a ptr... + dim = sweep_dir; + tol = tolerance; + + //split paths into monotonic tiles + createMonotonicTiles(); + + //split at tiles intersections + splitIntersectingTiles(); + + //handle overlapping end boxes/and tiles traversing boxes. + do{ + vtxboxes = collectBoxes(); + }while ( splitTilesThroughFatPoints(vtxboxes) ); + + enlargeTilesEnds(vtxboxes); + + //now create the pointers to the tiles. + tiles = std::vector<unsigned>(tiles_data.size(), 0); + for (unsigned i=0; i<tiles_data.size(); i++){ + tiles[i] = i; + } + sortTiles(); + + //initialize the context. + if (tiles_data.size()>0){ + context.clear(); + context.last_pos = tiles_data[tiles.front()].min(); + context.last_pos[dim] -= 1; + context.pending_vertex = FatVertex(); + context.pending_event = Event(); + } + +// std::printf("Sweeper initialized (%u tiles)\n", tiles_data.size()); + } + + + //------------------------------- + //-- Event walk. + //------------------------------- + + + Event getNextEvent(){ +// std::printf("getNextEvent():\n"); + +// std::printf("initial contex:\n"); +// printContext(); + Event old_event = context.pending_event, event; +// std::printf("apply old event\n"); + applyEvent(context.pending_event); +// printContext(); + + if (context.pending_vertex.rays.size()== 0){ +// std::printf("cook up a new vertex\n"); + + //find the edges at the next vertex. + //TODO: implement this as a lower bound!! + std::vector<unsigned>::iterator low, high; + //Warning: bad looking test, but make sure we advance even in case of 0 width boxes... + for ( low = tiles.begin(); low != tiles.end() && + ( tiles_data[*low].state==1 || Point::LexLessRt(dim)(tiles_data[*low].cur_box().min(), context.last_pos) ); low++){} + + if ( low == tiles.end() ){ +// std::printf("no more event found\n"); + return(Event()); + } + Rect pos = tiles_data[ *low ].cur_box(); + context.last_pos = pos.min(); + context.last_pos[1-dim] = pos.max()[1-dim]; + +// printContext(); +// std::printf("purgeDeadTiles\n"); + purgeDeadTiles(); +// printContext(); + + FatVertex v(pos); + high = low; + do{ +// v.rays.push_back( Ray(*high, !tiles_data[*high].open) ); + v.rays.push_back( Ray(*high, tiles_data[*high].state!=0) ); + high++; + }while( high != tiles.end() && tiles_data[ *high ].cur_box()==pos ); + +// std::printf("sortRays\n"); + sortRays(v); + + //Look for an opened tile + unsigned i=0; + + for( i=0; i<v.rays.size(); ++i){assert( v.rays[i].tile<tiles_data.size() );} +// for( i=0; i<v.rays.size() && !tiles_data[ v.rays[i].tile ].open; ++i){} + for( i=0; i<v.rays.size() && tiles_data[ v.rays[i].tile ].state!=0; ++i){} + + //if there are only openings: + if (i == v.rays.size() ){ +// std::printf("only openings!\n"); + event.insert_at = contextRanking(pos.min()); + } + //if there is at least one closing: make it first, and catch 'insert_at'. + else{ +// std::printf("not only openings\n"); + if( i > 0 ){ + std::vector<Ray> head; + head.assign ( v.rays.begin(), v.rays.begin()+i ); + v.rays.erase ( v.rays.begin(), v.rays.begin()+i ); + v.rays.insert( v.rays.end(), head.begin(), head.end()); + } + //assert( tiles_data[ v.rays[0].tile ].open ); + assert( tiles_data[ v.rays[0].tile ].state==0 ); + event.insert_at = context.find( v.rays.front().tile )+1; +// event.erase_at = context.find( v.rays.front().tile ); +// std::printf("at least one closing!\n"); + } + context.pending_vertex = v; + }else{ +// std::printf("continue biting exiting vertex\n"); + event.tile = context.pending_vertex.rays.front().tile; + event.insert_at = old_event.insert_at; +// if (old_event.opening){ +// event.insert_at++; +// }else{ +// if (old_event.erase_at < old_event.insert_at){ +// event.insert_at--; +// } +// } + event.insert_at++; + } + event.tile = context.pending_vertex.rays.front().tile; +// event.opening = !tiles_data[ event.tile ].open; + event.opening = tiles_data[ event.tile ].state!=0; + event.to_be_continued = context.pending_vertex.rays.size()>1; +// if ( !event.opening ) event.erase_at = context.find(event.tile); + + context.pending_vertex.rays.erase(context.pending_vertex.rays.begin()); + context.pending_event = event; +// printEvent(event); + return event; + } +}; + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/topology.cpp b/src/toys/topology.cpp new file mode 100644 index 0000000..25830ce --- /dev/null +++ b/src/toys/topology.cpp @@ -0,0 +1,668 @@ +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/exception.h> + +#include <vector> +#include <algorithm> +#include "sweeper.cpp" + +/* +Topology Class: +This class mainly consists in 3 vectors: vertices, edges, and areas. +-edges: have start/end, left/right pointing to vertices or areas. +-vertices: have a "boundary"= the sequence of edges sorted in CCW order. +-areas: have one outer "boundary" + a vector of inner boundaries, which are + sequence of edges. + +To build this data, the strategy is to let a line sweep the plane (from left +to right, say) and consider the topology of what is on the left of the sweep line. +Topology changes are called events, and we call an external "sweeper" to generate +them for us. + +So we start with an empty data, and respond to events to always describe the +topology of what is on the left of the sweep line. [more precisely, we start with +one region that has empty boundary, and since the external sweeper knows how many +edges we'll have at the end, so we create them from scratch, leaving their ends +as "unknown"] + +Note: see the sweeper for more info about events; they are essentially generated +when the sweep line crosses a vertex (which is in fact a box), but are in fact split +into smaller events, one for each edge around the vertex... + + +The code is using a lot of vectors: unsing pointers instead of vectors could speed +things up (?), but vector indices are easier to debug than memory addresses.:P +*/ + +using namespace Geom; +using namespace std; + +class Topology { +public: + + // -!- convention: + // In a boundary, reversed edges point away from the vertex or CW around the area. + struct OrientedEdge{ + unsigned edge; //edge index. + bool reversed; //true if the intrinsic edge orientation points away (from vertex) or backward (along area boundary) + OrientedEdge(unsigned edge_idx, bool o){ + edge = edge_idx; + reversed = o; + } + OrientedEdge(){ + edge = NULL_IDX; + reversed = false; + } + bool operator == ( OrientedEdge const &other) const { + return (edge == other.edge && edge!=NULL_IDX && reversed == other.reversed); + } + }; + + class Boundary : public std::vector<OrientedEdge>{ + public: + bool of_area;//true if this is the boundary of an area. Fix this with templates? + Boundary(bool area_type): of_area(area_type){} + }; + + class Vertex{ + public: + Boundary boundary; // list of edges in CCW order around the vertex + Geom::Rect bounds; + Vertex():boundary(false){} + }; + + class Area {//an area is a connected comp of the complement of the graph. . + public: + Boundary boundary; // outermost boundary component, CCW oriented (i.e. area is on the left of the boundary). + std::vector<Boundary> inner_boundaries;//same conventions, area on the left, so this gives the CW orientation for inner components. + std::vector<int> windings;//one winding number for each input path. + Area(unsigned size): boundary(true), windings(size, 0){} + }; + + class Edge { + public: + unsigned left, right;// the indices of the areas on the left and on the right this edge. + unsigned start, end; // the indices of vertices at start and at end of this edge. + Geom::Interval portion; + unsigned path; + unsigned curve; + Edge(){ + left = NULL_IDX; + right =NULL_IDX; + start = NULL_IDX; + end = NULL_IDX; + portion = Interval(); + path = NULL_IDX; + curve = NULL_IDX; + } + }; + + vector<Area> areas; + vector<Edge> edges; + vector<Vertex> vertices; + + PathVector input_paths;//we don't need our own copy... + cairo_t* cr; + + //debug only!! + int steps_max; + //---------- + + + //---------------------------------------------------- + //-- utils... + //---------------------------------------------------- + + void printIdx(unsigned idx){ (idx == NULL_IDX)? std::printf("?") : std::printf("%u", idx); } + void printVertex(unsigned i){ + std::printf("vertex %u: ", i); + printBoundary(vertices[i].boundary); + std::printf("\n"); + } + void printEdge(unsigned i){ + std::printf("edge %u: ", i); + printIdx(edges[i].start); + std::printf(" -> "); + printIdx(edges[i].end); + std::printf(" ^"); + printIdx(edges[i].left); + std::printf(" _"); + printIdx(edges[i].right); + std::printf("\n"); + } + void printArea(unsigned i){ + std::printf("area %u: ", i); + printBoundary(areas[i].boundary); + for (auto & inner_boundarie : areas[i].inner_boundaries){ + std::printf(", "); + printBoundary(inner_boundarie); + } + std::printf("\n"); + } + + void printOrientedEdge(OrientedEdge const &f){ + ( f.reversed ) ? std::printf("-") : std::printf("+"); + printIdx(f.edge); + std::printf(" "); + } + void printBoundary(Boundary const &bndry){ + (bndry.of_area) ? std::printf("[") : std::printf("<"); + for (unsigned i=0; i<bndry.size(); i++){ + printOrientedEdge(bndry[i]); + } + (bndry.of_area) ? std::printf("]") : std::printf(">"); + } + + void print(){ + std::cout<<"\nCrossing Data:\n"; + for (unsigned i=0; i<vertices.size(); i++){ + printVertex(i); + } + std::cout<<"\n"; + for (unsigned i=0; i<edges.size(); i++){ + printEdge(i); + } + std::cout<<"\n"; + for (unsigned i=0; i<areas.size(); i++){ + printArea(i); + } + } + + D2<SBasis> edgeAsSBasis(unsigned e){ + //beurk! optimize me. + D2<SBasis> c = input_paths[edges[e].path][edges[e].curve].toSBasis(); + return portion(c, edges[e].portion); + } + + Path edgeToPath(Topology::OrientedEdge o_edge){ + Topology::Edge e = edges[o_edge.edge]; + D2<SBasis> p = input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + p = portion(p, dom); + if ( o_edge.reversed ){ + p = compose( p, Linear(1.,0.) ); + } + Path ret; + ret.setStitching(true); + Point center; + unsigned c_idx = source(o_edge, true); + if ( c_idx == NULL_IDX ){ + ret.append(p); + }else{ + center = vertices[c_idx].bounds.midpoint(); + ret = Path(center); + ret.append(p); + } + c_idx = target(o_edge, true); + if ( c_idx == NULL_IDX ){ + return ret; + }else{ + center = vertices[c_idx].bounds.midpoint(); + if ( center != p.at1() ) ret.appendNew<LineSegment>(center); + return ret; + } + } + + Path boundaryToPath(Topology::Boundary b){ + Point pt; + Path bndary; + bndary.setStitching(true); + + if (b.size()==0){ return Path(); } + + Topology::OrientedEdge o_edge = b.front(); + unsigned first_v = source(o_edge, true); + if ( first_v != NULL_IDX ){ + pt = vertices[first_v].bounds.midpoint(); + bndary = Path(pt); + } + + for (unsigned i = 0; i < b.size(); i++){ + bndary.append( edgeToPath(b[i])); + } + bndary.close(); + return bndary; + } + + + + //---------------------------------------------------- + //-- Boundary Navigation/Modification + //---------------------------------------------------- + + //TODO: this should be an OrientedEdge method, be requires access to the edges. + unsigned source(OrientedEdge const &f, bool as_area_bndry){ + unsigned prev; + if (f.reversed ) + prev = (as_area_bndry)? edges[f.edge].end : edges[f.edge].right; + else + prev = (as_area_bndry)? edges[f.edge].start : edges[f.edge].left; + return prev; + } + unsigned target(OrientedEdge const &f, bool as_area_bndry){ + unsigned prev; + if (f.reversed ) + prev = (as_area_bndry)? edges[f.edge].start : edges[f.edge].left; + else + prev = (as_area_bndry)? edges[f.edge].end : edges[f.edge].right; + return prev; + } + + //TODO: this should be a Boundary method, but access to the full data is required... + bool prolongate( Boundary &bndry, OrientedEdge const &f){ + if ( bndry.empty() ){ + bndry.push_back(f); + return true; + } + unsigned src = source(f, bndry.of_area); + if ( src == target( bndry.back(), bndry.of_area ) && src != NULL_IDX ){ + bndry.push_back(f); + return true; + } + unsigned tgt = target( f, bndry.of_area ); + if ( tgt == source( bndry.front(), bndry.of_area ) && tgt != NULL_IDX ){ + bndry.insert( bndry.begin(), f); + return true; + } + return false; + } + + bool prolongate(Boundary &a, Boundary &b){ + if (a.size()==0 || b.size()==0 || (a.of_area != b.of_area) ) return false; + unsigned src; + src = source(a.front(), a.of_area); + +// unsigned af = a.front().edge, as=source(a.front(), a.of_area), ab=a.back().edge, at=target(a.back(), a.of_area); +// unsigned bf = b.front().edge, bs=source(b.front(), b.of_area), bb=b.back().edge, bt=target(b.back(), b.of_area); +// std::printf("a=%u(%u)...(%u)%u\n", as, af,ab,at); +// std::printf("b=%u(%u)...(%u)%u\n", bs, bf,bb,bt); + +// std::printf("%u == %u?\n", src, target( b.back(), b.of_area )); + if ( src == target( b.back(), b.of_area ) && src != NULL_IDX ){ + a.insert( a.begin(), b.begin(), b.end() ); +// std::printf("boundaries fused!!\n"); + return true; + } + src = source(b.front(), b.of_area); + if ( src == target( a.back(), a.of_area ) && src != NULL_IDX ){ + a.insert( a.end(), b.begin(), b.end() ); + return true; + } + return false; + } + + //TODO: this should be a Boundary or Area method, but requires access to the full data... + //TODO: systematically check for connected boundaries before returning? + void addAreaBoundaryPiece(unsigned a, OrientedEdge const &f){ + if ( areas[a].boundary.size()>0 && prolongate( areas[a].boundary, f ) ) return; + for (auto & inner_boundarie : areas[a].inner_boundaries){ +// printBoundary(areas[a].inner_boundaries[i]); +// printf(" matches "); +// printOrientedEdge(f); +// printf("?"); + if ( inner_boundarie.size()>0 && prolongate( inner_boundarie, f) ) return; +// printf("no. (%u vs %u)", target(areas[a].inner_boundaries[i].back(), true), source(f, true)); + } + Boundary new_comp(true); + new_comp.push_back(f); + areas[a].inner_boundaries.push_back(new_comp); + } + + + bool fuseConnectedBoundaries(unsigned a){ +// std::printf(" fuseConnectedBoundaries %u\n",a); + + bool ret = false; + if ( areas[a].boundary.size()>0 ){ + for ( unsigned i=0; i<areas[a].inner_boundaries.size(); i++){ + if ( prolongate( areas[a].boundary, areas[a].inner_boundaries[i] ) ){ + areas[a].inner_boundaries.erase(areas[a].inner_boundaries.begin()+i); + i--; + ret = true; + } + } + } + for ( unsigned i=0; i<areas[a].inner_boundaries.size(); i++){ + for ( unsigned j=i+1; j<areas[a].inner_boundaries.size(); j++){ + if ( prolongate( areas[a].inner_boundaries[i], areas[a].inner_boundaries[j] ) ){ + areas[a].inner_boundaries.erase(areas[a].inner_boundaries.begin()+j); + j--; + ret = true; + } + } + } + return ret; + } + + //------------------------------- + //-- Some basic area manipulation. + //------------------------------- + + void renameArea(unsigned oldi, unsigned newi){ + for (auto & edge : edges){ + if ( edge.left == oldi ) edge.left = newi; + if ( edge.right == oldi ) edge.right = newi; + } + } + void deleteArea(unsigned a0){//ptrs would definitely be helpful here... + assert(a0<areas.size()); + for (unsigned a=a0+1; a<areas.size(); a++){ + renameArea(a,a-1); + } + areas.erase(areas.begin()+a0); + } + + //fuse open(=not finished!) areas. The boundaries are supposed to match. true on success. + void fuseAreas(unsigned a, unsigned b){ +// std::printf("fuse Areas %u and %u\n", a, b); + if (a==b) return; + if (a>b) swap(a,b);//this is important to keep track of the outermost component!! + + areas[a].inner_boundaries.push_back(areas[b].boundary); + for (unsigned i=0; i<areas[b].inner_boundaries.size(); i++){ + areas[a].inner_boundaries.push_back(areas[b].inner_boundaries[i]); + } + renameArea(b,a); + deleteArea(b); + assert( fuseConnectedBoundaries(a) ); + return; + } + + PathVector areaToPath(unsigned a){ + PathVector bndary; + if ( areas[a].boundary.size()!=0 ){//this is not the unbounded component... + bndary.push_back( boundaryToPath(areas[a].boundary ) ); + } + for (auto & inner_boundarie : areas[a].inner_boundaries){ + bndary.push_back( boundaryToPath(inner_boundarie) ); + } + return bndary; + } + + //DEBUG ONLY: we add a rect round the unbounded comp, and glue the bndries + //for easy drawing in the toys... + Path glued_areaToPath(unsigned a){ + Path bndary; + if ( areas[a].boundary.size()==0 ){//this is the unbounded component... + OptRect bbox = bounds_fast( input_paths ); + if (!bbox ){return Path();}//??? + bbox->expandBy(50); + bndary = Path(bbox->corner(0)); + bndary.appendNew<LineSegment>(bbox->corner(1)); + bndary.appendNew<LineSegment>(bbox->corner(2)); + bndary.appendNew<LineSegment>(bbox->corner(3)); + bndary.appendNew<LineSegment>(bbox->corner(0)); + }else{ + bndary = boundaryToPath(areas[a].boundary); + } + for (auto & inner_boundarie : areas[a].inner_boundaries){ + bndary.append( boundaryToPath(inner_boundarie)); + bndary.appendNew<LineSegment>( bndary.initialPoint() ); + } + bndary.close(); + return bndary; + } + + void drawAreas( cairo_t *cr, bool fill=true ){ + //don't draw the first one... + for (unsigned a=0; a<areas.size(); a++){ + drawArea(cr, a, fill); + } + } + void drawArea( cairo_t *cr, unsigned a, bool fill=true ){ + if (a>=areas.size()) return; + Path bndary = glued_areaToPath(a); + cairo_path(cr, bndary); + if (fill){ + cairo_fill(cr); + }else{ + cairo_stroke(cr); + } + } + void highlightRay( cairo_t *cr, unsigned b, unsigned r ){ + if (b>=vertices.size()) return; + if (r>=vertices[b].boundary.size()) return; + Rect box = vertices[b].bounds; + //box.expandBy(2); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, 1.0); + cairo_set_line_width (cr, 1); + cairo_fill(cr); + unsigned eidx = vertices[b].boundary[r].edge; + Topology::Edge e = edges[eidx]; + D2<SBasis> p = input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + if (vertices[b].boundary[r].reversed){ + //dom[0] += e.portion.extent()*2./3; + cairo_set_source_rgba (cr, 0., 1., 0., 1.0); + }else{ + //dom[1] -= e.portion.extent()*2./3; + cairo_set_source_rgba (cr, 0., 0., 1., 1.0); + } + p = portion(p, dom); + cairo_d2_sb(cr, p); + cairo_set_source_rgba (cr, 1., 0., 0, 1.0); + cairo_set_line_width (cr, 5); + cairo_stroke(cr); + } + + void drawEdge( cairo_t *cr, unsigned eidx ){ + if (eidx>=edges.size()) return; + Topology::Edge e = edges[eidx]; + D2<SBasis> p = input_paths[e.path][e.curve].toSBasis(); + Interval dom = e.portion; + p = portion(p, dom); + cairo_d2_sb(cr, p); + if (e.start == NULL_IDX || e.end == NULL_IDX ) + cairo_set_source_rgba (cr, 0., 1., 0, 1.0); + else + cairo_set_source_rgba (cr, 0., 0., 0, 1.0); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + } + void drawEdges( cairo_t *cr){ + for (unsigned e=0; e<edges.size(); e++){ + drawEdge(cr, e); + } + } + void drawKnownEdges( cairo_t *cr){ + for (auto & vertice : vertices){ + for (unsigned e=0; e<vertice.boundary.size(); e++){ + drawEdge(cr, vertice.boundary[e].edge); + } + } + } + + + void drawBox( cairo_t *cr, unsigned b ){ + if (b>=vertices.size()) return; + Rect box = vertices[b].bounds; + //box.expandBy(5); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, .5); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + cairo_rectangle(cr, box); + cairo_set_source_rgba (cr, 1., 0., 0, .2); + cairo_fill(cr); + } + + void drawBoxes( cairo_t *cr){ + for (unsigned b=0; b<vertices.size(); b++){ + drawBox(cr, b); + } + } + + + + + + + + + + + //---------------------------------------------------- + //-- Fill data using a sweeper... + //---------------------------------------------------- + + Topology(){} + ~Topology(){} + Topology(PathVector const &paths, cairo_t* cairo, double tol=EPSILON, int stepsmax=-1){ +// std::printf("\n---------------------\n---------------------\n---------------------\n"); +// std::printf("Topology creation\n"); + cr = cairo; + + //debug only: + steps_max = stepsmax; + //------------- + + input_paths = paths; + + vertices.clear(); + edges.clear(); + areas.clear(); + Area empty( input_paths.size() ); + areas.push_back(empty); + + Sweeper sweeper( paths, X, tol ); + + edges = std::vector<Edge>( sweeper.tiles_data.size(), Edge() ); + for (unsigned i=0; i<edges.size(); i++){ + edges[i].path = sweeper.tiles_data[i].path; + edges[i].curve = sweeper.tiles_data[i].curve; + edges[i].portion = Interval(sweeper.tiles_data[i].f, sweeper.tiles_data[i].t); + } + + //std::printf("entering event loop:\n"); + int step=0; + for(Sweeper::Event event = sweeper.getNextEvent(); ; event = sweeper.getNextEvent() ){ +// std::printf(" new event received: "); + //print(); + //debug only!!! + if ( steps_max >= 0 && step > steps_max ){ + break; + }else{ + step++; + } + //--------- + + if (event.empty()){ + //std::printf(" empty event received\n"); + break; + } + + //std::printf(" non empty event received:"); + //sweeper.printEvent(event); + + //is this a new event or the continuation of an old one? + unsigned v; + Rect r = sweeper.context.pending_vertex; + if (vertices.empty() || !r.intersects( vertices.back().bounds ) ){ + v = vertices.size(); + vertices.push_back(Vertex()); + vertices[v].bounds = r; +// std::printf(" new intersection created (%u).\n",v); + }else{ + v = vertices.size()-1; +// std::printf(" continue last intersection (%u).\n",v); + } + + //--Closing an edge:------------- + if( !event.opening ){ + unsigned e = event.tile, a, b; +// std::printf(" closing edge %u\n", e); + bool reversed = sweeper.tiles_data[e].reversed;//Warning: true means v==e.start + if (reversed){ + edges[e].start = v; + a = edges[e].right; + b = edges[e].left; + }else{ + edges[e].end = v; + a = edges[e].left; + b = edges[e].right; + } + OrientedEdge vert_edge(e, reversed); + if (vertices[v].boundary.size()>0){//Make sure areas are compatible (only relevant if the last event was an opening). + fuseAreas ( a, target( vertices[v].boundary.back(), false ) ); + } + assert( prolongate( vertices[v].boundary, vert_edge) ); + fuseConnectedBoundaries(a);//there is no doing both: tests are performed twice but for 2 areas. + fuseConnectedBoundaries(b);// + }else{ + //--Opening an edge:------------- + unsigned e = event.tile; +// std::printf(" opening edge %u\n", e); + bool reversed = !sweeper.tiles_data[e].reversed;//Warning: true means v==start. + + //--Find first and last area around this vertex:------------- + unsigned cur_a; + if ( vertices[v].boundary.size() > 0 ){ + cur_a = target( vertices[v].boundary.back(), false ); + }else{//this vertex is empty + if ( event.insert_at < sweeper.context.size() ){ + unsigned upper_tile = sweeper.context[event.insert_at].first; + cur_a = (sweeper.tiles_data[upper_tile].reversed) ? edges[upper_tile].left : edges[upper_tile].right; + }else{ + cur_a = 0; + } + } + + unsigned new_a = areas.size(); + + Area new_area(paths.size()); + new_area.boundary.push_back( OrientedEdge(e, !reversed ) ); + new_area.windings = areas[cur_a].windings;//FIXME: escape boundary cases!!! + if ( input_paths[edges[e].path].closed() ){ + new_area.windings[edges[e].path] += (reversed) ? +1 : -1; + } + areas.push_back(new_area); + + //update edge + if (reversed){ + edges[e].start = v; + edges[e].left = new_a; + edges[e].right = cur_a; + }else{ + edges[e].end = v; + edges[e].left = cur_a; + edges[e].right = new_a; + } + //update vertex + OrientedEdge f(e, reversed); + assert( prolongate( vertices[v].boundary, f) ); + addAreaBoundaryPiece(cur_a, OrientedEdge(e, reversed) ); + } + if (!event.to_be_continued && vertices[v].boundary.size()>0){ + unsigned first_a = source( vertices[v].boundary.front(), false ); + unsigned last_a = target( vertices[v].boundary.back(), false ); + fuseAreas(first_a, last_a); + } + +// this->print(); +// std::printf("----------------\n"); + //std::printf("\n"); + } + } + + + + //---------------------------------------------------- + //-- done. + //---------------------------------------------------- +}; + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/toy-framework-2.cpp b/src/toys/toy-framework-2.cpp new file mode 100644 index 0000000..54166cf --- /dev/null +++ b/src/toys/toy-framework-2.cpp @@ -0,0 +1,972 @@ +#include <cstring> +#include <cstdint> +#include <typeinfo> +#include <cairo.h> +#include <gtk/gtk.h> +#include <toys/toy-framework-2.h> + +#include <cairo-features.h> +#if CAIRO_HAS_PDF_SURFACE +#include <cairo-pdf.h> +#endif +#if CAIRO_HAS_SVG_SURFACE +#include <cairo-svg.h> +#endif + +GtkApplicationWindow* the_window = nullptr; +static GtkWidget *the_canvas = nullptr; +Toy* the_toy = nullptr; +int the_requested_height = 0; +int the_requested_width = 0; +gchar **the_emulated_argv = nullptr; + +gchar *arg_spool_filename = nullptr; +gchar *arg_handles_filename = nullptr; +gchar *arg_screenshot_filename = nullptr; +gchar **arg_extra_files = nullptr; + +//Utility functions + +double uniform() { + return double(rand()) / RAND_MAX; +} + +colour colour::from_hsv( float H, // hue shift (in degrees) + float S, // saturation shift (scalar) + float V, // value multiplier (scalar) + float A + ) +{ + double inr = 1; + double ing = 0; + double inb = 0; + float k = V/3; + float a = V*S*cos(H)/3; + float b = V*S*sin(H)/3; + + return colour( + (k+2*a)*inr - 2*b*ing + (k-a-b)*inb, + (-k+a+3*b)*inr + (3*a-b)*ing + (-k+a+2*b)*inb, + (2*k-2*a)*inr + 2*b*ing + (2*k+a+b)*inb, + A); +} + + // Given H,S,L in range of 0-1 + + // Returns a Color (RGB struct) in range of 0-255 + +colour colour::from_hsl(float h, float sl, float l, float a) { + h /= M_PI*2; + colour rgba(l,l,l,a); // default to gray + + double v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl); + + if (v > 0) { + double m; + double sv; + int sextant; + double fract, vsf, mid1, mid2; + + m = l + l - v; + sv = (v - m ) / v; + h *= 6.0; + sextant = (int)h; + fract = h - sextant; + vsf = v * sv * fract; + mid1 = m + vsf; + mid2 = v - vsf; + switch (sextant%6) { + case 0: + rgba.r = v; + rgba.g = mid1; + rgba.b = m; + break; + + case 1: + rgba.r = mid2; + rgba.g = v; + rgba.b = m; + break; + + case 2: + rgba.r = m; + rgba.g = v; + rgba.b = mid1; + break; + + case 3: + rgba.r = m; + rgba.g = mid2; + rgba.b = v; + break; + + case 4: + rgba.r = mid1; + rgba.g = m; + rgba.b = v; + break; + + case 5: + rgba.r = v; + rgba.g = m; + rgba.b = mid2; + break; + } + } + return rgba; +} + +void cairo_set_source_rgba(cairo_t* cr, colour c) { + cairo_set_source_rgba(cr, c.r, c.g, c.b, c.a); +} + +void draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom, const char* fontdesc) { + PangoLayout* layout = pango_cairo_create_layout (cr); + pango_layout_set_text(layout, txt, -1); + PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc); + pango_layout_set_font_description(layout, font_desc); + pango_font_description_free (font_desc); + PangoRectangle logical_extent; + pango_layout_get_pixel_extents(layout, NULL, &logical_extent); + cairo_move_to(cr, loc - Geom::Point(0, bottom ? logical_extent.height : 0)); + pango_cairo_show_layout(cr, layout); +} + +void draw_text(cairo_t *cr, Geom::Point loc, const std::string& txt, bool bottom, const std::string& fontdesc) { + draw_text(cr, loc, txt.c_str(), bottom, fontdesc.c_str()); +} + +void draw_number(cairo_t *cr, Geom::Point pos, int num, std::string name, bool bottom) { + std::ostringstream number; + if (name.size()) + number << name; + number << num; + draw_text(cr, pos, number.str().c_str(), bottom); +} + +void draw_number(cairo_t *cr, Geom::Point pos, unsigned num, std::string name, bool bottom) { + std::ostringstream number; + if (name.size()) + number << name; + number << num; + draw_text(cr, pos, number.str().c_str(), bottom); +} + +void draw_number(cairo_t *cr, Geom::Point pos, double num, std::string name, bool bottom) { + std::ostringstream number; + if (name.size()) + number << name; + number << num; + draw_text(cr, pos, number.str().c_str(), bottom); +} + +//Framework Accessors +void redraw() { gtk_widget_queue_draw(GTK_WIDGET(the_window)); } + +void Toy::draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool /*save*/, std::ostringstream *timer_stream) +{ + if(should_draw_bounds() == 1) { + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 0.5); + for(unsigned i = 1; i < 4; i+=2) { + cairo_move_to(cr, 0, i*width/4); + cairo_line_to(cr, width, i*width/4); + cairo_move_to(cr, i*width/4, 0); + cairo_line_to(cr, i*width/4, height); + } + } + else if(should_draw_bounds() == 2) { + cairo_set_source_rgba (cr, 0., 0., 0, 0.8); + cairo_set_line_width (cr, 0.5); + cairo_move_to(cr, 0, width/2); + cairo_line_to(cr, width, width/2); + cairo_move_to(cr, width/2, 0); + cairo_line_to(cr, width/2, height); + } + + cairo_set_line_width (cr, 1); + for(auto & handle : handles) { + cairo_set_source_rgb (cr, handle->rgb[0], handle->rgb[1], handle->rgb[2]); + handle->draw(cr, should_draw_numbers()); + } + + cairo_set_source_rgba (cr, 0.5, 0, 0, 1); + if(selected && mouse_down == true) + selected->draw(cr, should_draw_numbers()); + + cairo_set_source_rgba (cr, 0.5, 0.25, 0, 1); + cairo_stroke(cr); + + cairo_set_source_rgba (cr, 0., 0.5, 0, 0.8); + { + *notify << std::ends; + draw_text(cr, Geom::Point(0, height-notify_offset), notify->str().c_str(), true); + } + if(show_timings) { + *timer_stream << std::ends; + draw_text(cr, Geom::Point(0, notify_offset), timer_stream->str().c_str(), false); + } +} + +void Toy::mouse_moved(GdkEventMotion* e) +{ + Geom::Point mouse(e->x, e->y); + + if(e->state & (GDK_BUTTON1_MASK | GDK_BUTTON3_MASK)) { + if(selected) + selected->move_to(hit_data, old_mouse_point, mouse); + } + old_mouse_point = mouse; + redraw(); +} + +void Toy::mouse_pressed(GdkEventButton* e) { + Geom::Point mouse(e->x, e->y); + selected = NULL; + hit_data = NULL; + canvas_click_button = e->button; + if(e->button == 1) { + for(auto & handle : handles) { + void * hit = handle->hit(mouse); + if(hit) { + selected = handle; + hit_data = hit; + } + } + mouse_down = true; + } + old_mouse_point = mouse; + redraw(); +} + +void Toy::scroll(GdkEventScroll* /*e*/) { +} + +void Toy::canvas_click(Geom::Point at, int button) { + (void)at; + (void)button; +} + +void Toy::mouse_released(GdkEventButton* e) { + if(selected == NULL) { + Geom::Point mouse(e->x, e->y); + canvas_click(mouse, canvas_click_button); + canvas_click_button = 0; + } + selected = NULL; + hit_data = NULL; + if(e->button == 1) + mouse_down = false; + redraw(); +} + +void Toy::load(FILE* f) { + char data[1024]; + if (fscanf(f, "%1024s", data)) { + name = data; + } + for(auto & handle : handles) { + handle->load(f); + } +} + +void Toy::save(FILE* f) { + fprintf(f, "%s\n", name.c_str()); + for(auto & handle : handles) + handle->save(f); +} + +//Gui Event Callbacks + +void show_about_dialog(GSimpleAction *, GVariant *, gpointer) { + GtkWidget* about_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(about_window), "About"); + gtk_window_set_resizable(GTK_WINDOW(about_window), FALSE); + + GtkWidget* about_text = gtk_text_view_new(); + GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(about_text)); + gtk_text_buffer_set_text(buf, "Toy lib2geom application", -1); + gtk_container_add(GTK_CONTAINER(about_window), about_text); + + gtk_widget_show_all(about_window); +} + +void quit(GSimpleAction *, GVariant *, gpointer) { + g_application_quit(g_application_get_default()); +} + +Geom::Point read_point(FILE* f) { + Geom::Point p; + for(unsigned i = 0; i < 2; i++) + assert(fscanf(f, " %lf ", &p[i])); + return p; +} + +Geom::Interval read_interval(FILE* f) { + Geom::Interval p; + Geom::Coord a, b; + assert(fscanf(f, " %lf ", &a)); + assert(fscanf(f, " %lf ", &b)); + p.setEnds(a, b); + return p; +} + +void open_handles(GSimpleAction *, GVariant *, gpointer) { + if (!the_toy) return; + GtkWidget* d = gtk_file_chooser_dialog_new( + "Open handle configuration", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_OPEN, + "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL); + if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) { + const char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d)); + FILE* f = fopen(filename, "r"); + the_toy->load(f); + fclose(f); + } + gtk_widget_destroy(d); +} + +void save_handles(GSimpleAction *, GVariant *, gpointer) { + if (!the_toy) return; + GtkWidget* d = gtk_file_chooser_dialog_new( + "Save handle configuration", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_SAVE, + "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL); + if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) { + const char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d)); + FILE* f = fopen(filename, "w"); + the_toy->save(f); + fclose(f); + } + gtk_widget_destroy(d); +} + +void write_image(const char* filename) { + cairo_surface_t* cr_s; + unsigned l = strlen(filename); + int width = gdk_window_get_width(gtk_widget_get_window(the_canvas)); + int height = gdk_window_get_height(gtk_widget_get_window(the_canvas)); + bool save_png = false; + + if (l >= 4 && strcmp(filename + l - 4, ".png") == 0) { + cr_s = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32, width, height ); + save_png = true; + } + +#if CAIRO_HAS_PDF_SURFACE + else if (l >= 4 && strcmp(filename + l - 4, ".pdf") == 0) + cr_s = cairo_pdf_surface_create(filename, width, height); +#endif +#if CAIRO_HAS_SVG_SURFACE +#if CAIRO_HAS_PDF_SURFACE + else +#endif + cr_s = cairo_svg_surface_create(filename, width, height); +#endif + cairo_t* cr = cairo_create(cr_s); + + if(save_png) { + cairo_save(cr); + cairo_set_source_rgb(cr, 1,1,1); + cairo_paint(cr); + cairo_restore(cr); + } + if(the_toy != NULL) { + std::ostringstream * notify = new std::ostringstream; + std::ostringstream * timer_stream = new std::ostringstream; + the_toy->draw(cr, notify, width, height, true, timer_stream); + delete notify; + delete timer_stream; + } + + cairo_show_page(cr); + if(save_png) + cairo_surface_write_to_png(cr_s, filename); + cairo_destroy (cr); + cairo_surface_destroy (cr_s); +} + +void save_cairo(GSimpleAction *, GVariant *, gpointer) { + GtkWidget* d = gtk_file_chooser_dialog_new( + "Save file as svg, pdf or png", GTK_WINDOW(the_window), GTK_FILE_CHOOSER_ACTION_SAVE, + "Cancel", GTK_RESPONSE_CANCEL, "Save", GTK_RESPONSE_ACCEPT, NULL); + if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) { + const gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d)); + write_image(filename); + } + gtk_widget_destroy(d); +} + +static gint delete_event(GtkWidget*, GdkEventAny*, gpointer) { + quit(nullptr, nullptr, nullptr); + return FALSE; +} + + +static void toggle_action(GSimpleAction *action, GVariant *, gpointer) { + GVariant *state = g_action_get_state(G_ACTION(action)); + g_action_change_state(G_ACTION(action), g_variant_new_boolean(!g_variant_get_boolean(state))); + g_variant_unref(state); +} + +static void set_show_timings(GSimpleAction *action, GVariant *variant, gpointer) { + the_toy->show_timings = g_variant_get_boolean(variant); + g_simple_action_set_state(action, variant); +} + +static gboolean draw_callback(GtkWidget *widget, cairo_t *cr) +{ + int width = gdk_window_get_width(gtk_widget_get_window(widget)); + int height = gdk_window_get_height(gtk_widget_get_window(widget)); + + std::ostringstream notify; + + static bool resized = false; + if(!resized) { + Geom::Rect alloc_size(Geom::Interval(0, width), + Geom::Interval(0, height)); + if(the_toy != NULL) + the_toy->resize_canvas(alloc_size); + resized = true; + } + cairo_rectangle(cr, 0, 0, width, height); + cairo_set_source_rgba(cr,1,1,1,1); + cairo_fill(cr); + if (the_toy != NULL) { + std::ostringstream * timer_stream = new std::ostringstream; + + if (the_toy->spool_file) { + the_toy->save(the_toy->spool_file); + } + + the_toy->draw(cr, ¬ify, width, height, false, timer_stream); + delete timer_stream; + } + + return TRUE; +} + +static gint mouse_motion_event(GtkWidget* widget, GdkEventMotion* e, gpointer data) { + (void)(data); + (void)(widget); + + if(the_toy != NULL) the_toy->mouse_moved(e); + + return FALSE; +} + +static gint mouse_event(GtkWidget* widget, GdkEventButton* e, gpointer data) { + (void)(data); + (void)(widget); + + if(the_toy != NULL) the_toy->mouse_pressed(e); + + return FALSE; +} + +static gint scroll_event(GtkWidget* widget, GdkEventScroll* e, gpointer data) { + (void)(data); + (void)(widget); + if(the_toy != NULL) the_toy->scroll(e); + + return FALSE; +} + +static gint mouse_release_event(GtkWidget* widget, GdkEventButton* e, gpointer data) { + (void)(data); + (void)(widget); + + if(the_toy != NULL) the_toy->mouse_released(e); + + return FALSE; +} + +static gint key_press_event(GtkWidget *widget, GdkEventKey *e, gpointer data) { + (void)(data); + (void)(widget); + + if(the_toy != NULL) the_toy->key_hit(e); + + return FALSE; +} + +static gint size_allocate_event(GtkWidget* widget, GtkAllocation *allocation, gpointer data) { + (void)(data); + (void)(widget); + + Geom::Rect alloc_size(Geom::Interval(allocation->x, allocation->x+ allocation->width), + Geom::Interval(allocation->y, allocation->y+allocation->height)); + if(the_toy != NULL) the_toy->resize_canvas(alloc_size); + + return FALSE; +} + + +const char *the_builder_xml = R"xml( +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <menu id="menu"> + <submenu> + <attribute name="label">File</attribute> + <section> + <item> + <attribute name="label">Open Handles...</attribute> + <attribute name="action">app.open-handles</attribute> + </item> + <item> + <attribute name="label">Save Handles...</attribute> + <attribute name="action">app.save-handles</attribute> + </item> + </section> + <section> + <item> + <attribute name="label">Save as SVG of PDF...</attribute> + <attribute name="action">app.save-image</attribute> + </item> + </section> + <section> + <item> + <attribute name="label">Show Timings</attribute> + <attribute name="action">app.show-timings</attribute> + </item> + <item> + <attribute name="label">Quit</attribute> + <attribute name="action">app.quit</attribute> + </item> + </section> + </submenu> + <submenu> + <attribute name="label">Help</attribute> + <item> + <attribute name="label">About...</attribute> + <attribute name="action">app.about</attribute> + </item> + </submenu> + </menu> +</interface> +)xml"; + +static GActionEntry the_actions[] = +{ + {"open-handles", open_handles, nullptr, nullptr, nullptr}, + {"save-handles", save_handles, nullptr, nullptr, nullptr}, + {"save-image", save_cairo, nullptr, nullptr, nullptr}, + {"show-timings", toggle_action, nullptr, "false", set_show_timings}, + {"quit", quit, nullptr, nullptr, nullptr}, + {"about", show_about_dialog, nullptr, nullptr, nullptr}, +}; + +static GOptionEntry const the_options[] = { + {"handles", 'h', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_handles_filename, + "Load handle positions from given file", "FILE"}, + {"spool", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_spool_filename, + "Record all interaction to the given file", "FILE"}, + {"screenshot", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &arg_screenshot_filename, + "Take screenshot and exit", nullptr}, + {G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &arg_extra_files, + "Additional data files", "FILES..."}, + {nullptr, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr}, +}; + +static void activate(GApplication *app, gpointer); +static void startup(GApplication *app, gpointer); + +void init(int argc, char **argv, Toy* t, int width, int height) { + the_toy = t; + the_requested_width = width; + the_requested_height = height; + + std::string app_name = "org.inkscape.lib2geom.toy."; + char const *dir_pos = strrchr(argv[0], G_DIR_SEPARATOR); + std::string argv_name = dir_pos ? dir_pos + 1 : argv[0]; + + // Erase extension for Windows + size_t dot_pos = argv_name.rfind('.'); + if (dot_pos != std::string::npos) { + argv_name.erase(dot_pos); + } + the_toy->name = argv_name; + app_name += argv_name; + + GtkApplication* app = gtk_application_new(app_name.c_str(), G_APPLICATION_FLAGS_NONE); + g_application_add_main_option_entries(G_APPLICATION(app), the_options); + g_action_map_add_action_entries(G_ACTION_MAP(app), the_actions, G_N_ELEMENTS(the_actions), nullptr); + g_signal_connect(G_OBJECT(app), "startup", G_CALLBACK(startup), nullptr); + g_signal_connect(G_OBJECT(app), "activate", G_CALLBACK(activate), nullptr); + + g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); +} + +static void startup(GApplication *app, gpointer) { + GtkBuilder *builder = gtk_builder_new_from_string(the_builder_xml, -1); + GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu")); + gtk_application_set_menubar(GTK_APPLICATION(app), menu); + g_object_unref(builder); +} + +static void activate(GApplication *app, gpointer) { + if (arg_spool_filename) { + the_toy->spool_file = fopen(arg_spool_filename, "w"); + } + + int const emulated_argc = arg_extra_files ? g_strv_length(arg_extra_files) + 1 : 1; + gchar const **emulated_argv = new gchar const*[emulated_argc]; + emulated_argv[0] = the_toy->name.c_str(); + for (int i = 1; i < emulated_argc; ++i) { + emulated_argv[i] = arg_extra_files[i-1]; + } + the_toy->first_time(emulated_argc, const_cast<char**>(emulated_argv)); + delete[] emulated_argv; + + if (arg_handles_filename) { + FILE *handles_file = fopen(arg_handles_filename, "r"); + the_toy->load(handles_file); + fclose(handles_file); + } + + if (arg_screenshot_filename) { + write_image(arg_screenshot_filename); + g_application_quit(app); + return; + } + + the_window = GTK_APPLICATION_WINDOW(gtk_application_window_new(GTK_APPLICATION(g_application_get_default()))); + gtk_window_set_title(GTK_WINDOW(the_window), the_toy->name.c_str()); + g_signal_connect(G_OBJECT(the_window), "delete_event", G_CALLBACK(delete_event), NULL); + + the_canvas = gtk_drawing_area_new(); + gtk_widget_add_events(the_canvas, (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK)); + g_signal_connect(G_OBJECT(the_canvas), "draw", G_CALLBACK(draw_callback), 0); + g_signal_connect(G_OBJECT(the_canvas), "scroll-event", G_CALLBACK(scroll_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "button-press-event", G_CALLBACK(mouse_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "button-release-event", G_CALLBACK(mouse_release_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "motion-notify-event", G_CALLBACK(mouse_motion_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "key-press-event", G_CALLBACK(key_press_event), 0); + g_signal_connect(G_OBJECT(the_canvas), "size-allocate", G_CALLBACK(size_allocate_event), 0); + + gtk_container_add(GTK_CONTAINER(the_window), the_canvas); + gtk_window_set_default_size(GTK_WINDOW(the_window), the_requested_width, the_requested_height); + gtk_widget_show_all(GTK_WIDGET(the_window)); + + // Make sure the canvas can receive key press events. + gtk_widget_set_can_focus(the_canvas, TRUE); + gtk_widget_grab_focus(the_canvas); + assert(gtk_widget_is_focus(the_canvas)); +} + + +void Toggle::draw(cairo_t *cr, bool /*annotes*/) { + cairo_pattern_t* source = cairo_get_source(cr); + double rc, gc, bc, aa; + cairo_pattern_get_rgba(source, &rc, &gc, &bc, &aa); + cairo_set_source_rgba(cr,0,0,0,1); + cairo_rectangle(cr, bounds.left(), bounds.top(), + bounds.width(), bounds.height()); + if(on) { + cairo_fill(cr); + cairo_set_source_rgba(cr,1,1,1,1); + } //else cairo_stroke(cr); + cairo_stroke(cr); + draw_text(cr, bounds.corner(0) + Geom::Point(5,2), text); + cairo_set_source_rgba(cr, rc, gc, bc, aa); +} + +void Toggle::toggle() { + on = !on; +} +void Toggle::set(bool state) { + on = state; +} + + +void Toggle::handle_click(GdkEventButton* e) { + if(bounds.contains(Geom::Point(e->x, e->y)) && e->button == 1) toggle(); +} + +void* Toggle::hit(Geom::Point mouse) +{ + if (bounds.contains(mouse)) + { + toggle(); + return this; + } + return 0; +} + +void toggle_events(std::vector<Toggle> &ts, GdkEventButton* e) { + for(auto & t : ts) t.handle_click(e); +} + +void draw_toggles(cairo_t *cr, std::vector<Toggle> &ts) { + for(auto & t : ts) t.draw(cr); +} + + + +Slider::value_type Slider::value() const +{ + Slider::value_type v = m_handle.pos[m_dir] - m_pos[m_dir]; + v = ((m_max - m_min) / m_length) * v; + //std::cerr << "v : " << v << std::endl; + if (m_step != 0) + { + int k = std::floor(v / m_step); + v = k * m_step; + } + v = v + m_min; + //std::cerr << "v : " << v << std::endl; + return v; +} + +void Slider::value(Slider::value_type _value) +{ + if ( _value < m_min ) _value = m_min; + if ( _value > m_max ) _value = m_max; + if (m_step != 0) + { + _value = _value - m_min; + int k = std::floor(_value / m_step); + _value = k * m_step + m_min; + } + m_handle.pos[m_dir] + = (m_length / (m_max - m_min)) * (_value - m_min) + m_pos[m_dir]; +} + +void Slider::max_value(Slider::value_type _value) +{ + Slider::value_type v = value(); + m_max = _value; + value(v); +} + +void Slider::min_value(Slider::value_type _value) +{ + Slider::value_type v = value(); + m_min = _value; + value(v); +} + +// dir = X horizontal slider dir = Y vertical slider +void Slider::geometry( Geom::Point _pos, + Slider::value_type _length, + Geom::Dim2 _dir ) +{ + Slider::value_type v = value(); + m_pos = _pos; + m_length = _length; + m_dir = _dir; + Geom::Dim2 fix_dir = static_cast<Geom::Dim2>( (m_dir + 1) % 2 ); + m_handle.pos[fix_dir] = m_pos[fix_dir]; + value(v); +} + +void Slider::draw(cairo_t* cr, bool annotate) +{ + cairo_pattern_t* source = cairo_get_source(cr); + double rc, gc, bc, aa; + cairo_pattern_get_rgba(source, &rc, &gc, &bc, &aa); + double lw = cairo_get_line_width(cr); + std::ostringstream os; + os << m_label << ": " << (*m_formatter)(value()); + cairo_set_source_rgba(cr, 0.1, 0.1, 0.7, 1.0); + cairo_set_line_width(cr, 0.7); + m_handle.draw(cr, annotate); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1.0); + cairo_set_line_width(cr, 0.4); + m_handle.draw(cr, annotate); + cairo_move_to(cr, m_pos[Geom::X], m_pos[Geom::Y]); + Geom::Point offset; + if ( m_dir == Geom::X ) + { + cairo_rel_line_to(cr, m_length, 0); + offset = Geom::Point(0,5); + } + else + { + cairo_rel_line_to(cr, 0, m_length); + offset = Geom::Point(5,0); + } + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + draw_text(cr, m_pos + offset, os.str().c_str()); + cairo_set_source_rgba(cr, rc, gc, bc, aa); + cairo_set_line_width(cr, lw); +} + +void Slider::move_to(void* hit, Geom::Point om, Geom::Point m) +{ + // fix_dir == ! m_dir + Geom::Dim2 fix_dir = static_cast<Geom::Dim2>( (m_dir + 1) % 2 ); + m[fix_dir] = m_pos[fix_dir]; + double diff = m[m_dir] - m_pos[m_dir]; +// if (m_step != 0) +// { +// double step = (m_step * m_length) / (m_max - m_min) ; +// int k = std::floor(diff / step); +// double v = k * step; +// m[m_dir] = v + m_pos[m_dir]; +// } + if ( diff < 0 ) m[m_dir] = m_pos[m_dir]; + if ( diff > m_length ) m[m_dir] = m_pos[m_dir] + m_length; + m_handle.move_to(hit, om, m); +} + + + +void PointHandle::draw(cairo_t *cr, bool /*annotes*/) { + draw_circ(cr, pos); +} + +void* PointHandle::hit(Geom::Point mouse) { + if(Geom::distance(mouse, pos) < 5) + return this; + return 0; +} + +void PointHandle::move_to(void* /*hit*/, Geom::Point /*om*/, Geom::Point m) { + pos = m; +} + +void PointHandle::load(FILE* f) { + pos = read_point(f); +} + +void PointHandle::save(FILE* f) { + fprintf(f, "%lf %lf\n", pos[0], pos[1]); +} + +void PointSetHandle::draw(cairo_t *cr, bool annotes) { + for(unsigned i = 0; i < pts.size(); i++) { + draw_circ(cr, pts[i]); + if(annotes) draw_number(cr, pts[i], i, name); + } +} + +void* PointSetHandle::hit(Geom::Point mouse) { + for(auto & pt : pts) { + if(Geom::distance(mouse, pt) < 5) + return (void*)(&pt); + } + return 0; +} + +void PointSetHandle::move_to(void* hit, Geom::Point /*om*/, Geom::Point m) { + if(hit) { + *(Geom::Point*)hit = m; + } +} + +void PointSetHandle::load(FILE* f) { + int n = 0; + assert(1 == fscanf(f, "%d\n", &n)); + pts.clear(); + for(int i = 0; i < n; i++) { + pts.push_back(read_point(f)); + } +} + +void PointSetHandle::save(FILE* f) { + fprintf(f, "%d\n", (int)pts.size()); + for(auto & pt : pts) { + fprintf(f, "%lf %lf\n", pt[0], pt[1]); + } +} + +#include <2geom/bezier-to-sbasis.h> + +Geom::D2<Geom::SBasis> PointSetHandle::asBezier() { + return handles_to_sbasis(pts.begin(), size()-1); +} + +void RectHandle::draw(cairo_t *cr, bool /*annotes*/) { + cairo_rectangle(cr, pos); + cairo_stroke(cr); + if(show_center_handle) { + draw_circ(cr, pos.midpoint()); + } + draw_text(cr, pos.corner(0), name); +} + +void* RectHandle::hit(Geom::Point mouse) { + if(show_center_handle) { + if(Geom::distance(mouse, pos.midpoint()) < 5) + return (void*)(intptr_t)1; + } + for(int i = 0; i < 4; i++) { + if(Geom::distance(mouse, pos.corner(i)) < 5) + return (void*)(intptr_t)(2+i); + } + for(int i = 0; i < 4; i++) { + Geom::LineSegment ls(pos.corner(i), pos.corner(i+1)); + if(Geom::distance(ls.pointAt(ls.nearestTime(mouse)),mouse) < 5) + return (void*)(intptr_t)(6+i); + } + return 0; + +} + +void RectHandle::move_to(void* hit, Geom::Point om, Geom::Point m) { + using Geom::X; + using Geom::Y; + + unsigned h = (unsigned)(uintptr_t)(hit); + if(h == 1) + pos += (m-om); + else if(h >= 2 and h <= 5) {// corners + int xi = (h-2)& 1; + int yi = (h-2)&2; + if(yi) + xi = 1-xi; // clockwise + if (xi) { + pos[X].setMax(m[0]); + } else { + pos[X].setMin(m[0]); + } + if (yi/2) { + pos[Y].setMax(m[1]); + } else { + pos[Y].setMax(m[1]); + } + } else if(h >= 6 and h <= 9) {// edges + int side, d; + switch(h-6) { + case 0: d = 1; side = 0; break; + case 1: d = 0; side = 1; break; + case 2: d = 1; side = 1; break; + case 3: d = 0; side = 0; break; + } + if (side) { + pos[d].setMax(m[d]); + } else { + pos[d].setMin(m[d]); + } + } +} + +void RectHandle::load(FILE* f) { + assert(0 == fscanf(f, "r\n")); + for(int i = 0; i < 2; i++) { + pos[i] = read_interval(f); + } + +} + +void RectHandle::save(FILE* f) { + fprintf(f, "r\n"); + for(unsigned i = 0; i < 2; i++) { + fprintf(f, "%lf %lf\n", pos[i].min(), pos[i].max()); + } +} + + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/toy-template.cpp b/src/toys/toy-template.cpp new file mode 100644 index 0000000..f7e5090 --- /dev/null +++ b/src/toys/toy-template.cpp @@ -0,0 +1,35 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework.h> + +using namespace Geom; + +class MyToy: public Toy { + virtual void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { + //draw code here + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + MyToy () { + //Initialization here + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new MyToy()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/toyframework.py b/src/toys/toyframework.py new file mode 100644 index 0000000..c580c1e --- /dev/null +++ b/src/toys/toyframework.py @@ -0,0 +1,435 @@ +#!/usr/bin/python + +import gtk,math +import pangocairo,cairo +import gobject + +# def draw_text(cr, pos, txt, bottom = False): +# def draw_number(cr, pos, num): + +def draw_circ(cr, (x, y)): + cr.new_sub_path() + cr.arc(x, y, 3, 0, math.pi*2) + cr.stroke() +def draw_cross(cr, (x, y)): + cr.move_to(x-3, y-3) + cr.line_to(x+3, y+3) + cr.move_to(x-3, y+3) + cr.line_to(x+3, y-3) + cr.stroke() + +class Handle: + def __init__(self): + pass + def draw(self, cr, annotes): + pass + def hit(self, (x, y)): + return None + def move_to(self, hit, om, m): + pass + def scroll(self, pos, dir): + pass + +class PointHandle(Handle): + def __init__(self, x, y, name=""): + Handle.__init__(self) + self.pos = (x,y) + self.name = name + def draw(self, cr, annotes): + draw_circ(cr, self.pos) + if annotes: + draw_text(cr, self.pos, str(self.name)) + def hit(self, mouse): + if math.hypot(mouse[0] - self.pos[0], mouse[1] - self.pos[1]) < 5: + return 0 + return None + def move_to(self, hit, om, m): + self.pos = m + +class PointSetHandle(Handle): + def __init__(self, pts=None, name=""): + Handle.__init__(self) + self.pts = pts or [] + self.name = name + def draw(self, cr, annotes): + for p in self.pts: + draw_circ(cr, p) + if annotes: + draw_text(cr, p, str(self.name)) + def hit(self, mouse): + for i,p in enumerate(self.pts): + if math.hypot(mouse[0] - p[0], mouse[1] - p[1]) < 5: + return i + return None + def move_to(self, hit, om, m): + self.pts[hit[1]] = m + def append(self, x,y): + self.pts.append((x,y)) + +class Toy: + def __init__(self): + self.handles = [] + self.mouse_down = False + self.old_mouse = None + self.selected = None + self.notify = "notify" + self.origin = [0,0] + self.interactive_level = 0 + self.transform = None + + def draw(self, cr, (width, height), save): + bounds = self.should_draw_bounds() + self.transform = cr.get_matrix() + self.transform.invert() + if bounds == 1: + cr.set_source_rgba(0., 0., 0, 0.8) + cr.set_line_width (0.5) + for i in [1,3]: + cr.move_to(0, i*width/4) + cr.line_to(width, i*width/4) + cr.move_to(i*width/4, 0) + cr.line_to(i*width/4, height) + elif bounds == 2: + cr.set_source_rgba (0., 0., 0, 0.8) + cr.set_line_width (0.5) + cr.move_to(0, width/2) + cr.line_to(width, width/2) + cr.move_to(width/2, 0) + cr.line_to(width/2, height) + + cr.set_source_rgba (0., 0.5, 0, 1) + cr.set_line_width (1) + annotes = self.should_draw_numbers() + for i,h in enumerate(self.handles): + cr.save() + if self.selected and i == self.selected[0]: + cr.set_source_rgba (0.5, 0, 0, 1) + h.draw(cr, annotes) + cr.restore() + + cr.set_source_rgba (0., 0.5, 0, 0.8) + if self.notify: + cr.save() + cr.identity_matrix() + bnds = draw_text(cr, (0, height), self.notify, True) + l,t,w,h = bnds[1] + cr.set_source_rgba(1,1,1,0.9) + cr.rectangle(l,t+height-h, w, h) + cr.fill() + cr.set_source_rgba(0,0,0,1) + draw_text(cr, (0, height), self.notify, True) + cr.restore() + + def mouse_moved(self, e): + mouse = (e.x, e.y) + if self.transform != None: + mouse = self.transform.transform_point(e.x, e.y) + + if e.state & (gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK): + self.interactive_level = 2 + if self.selected: + self.handles[self.selected[0]].move_to(self.selected, self.old_mouse, mouse) + self.old_mouse = mouse + self.canvas.queue_draw() + #self.redraw() + def mouse_pressed(self, e): + self.interactive_level = 1 + mouse = (e.x, e.y) + if self.transform != None: + mouse = self.transform.transform_point(e.x, e.y) + if e.button == 1: + for i,h in enumerate(self.handles): + hit = h.hit(mouse) + if hit != None: + self.selected = (i, hit) + self.mouse_down = True + self.old_mouse = mouse + self.canvas.queue_draw() + #self.redraw() + + def mouse_released(self, e): + self.interactive_level = 1 + self.selected = None + if e.button == 1: + self.mouse_down = False + self.canvas.queue_draw() + #self.redraw() + + def scroll_event(self, da, ev): + self.interactive_level = 1 + #print 'scroll:'+'\n'.join([str((x, getattr(ev, x))) for x in dir(ev)]) + #print 'end' + da.queue_draw() + hit = None + for i,h in enumerate(self.handles): + hit = h.scroll((ev.x,ev.y), ev.direction) + if hit != None: + break + return hit != None + + def key_hit(self, e): + pass + + def should_draw_numbers(self): + return True + def should_draw_bounds(self): + return 0 + + def first_time(self, argv): + pass + + def gtk_ready(self): + pass + + def resize_canvas(self, s): + pass + def redraw(self): + self.window.queue_draw() + def get_save_size(self): + return (0,0)+self.window.window.get_size() + def delete_event(self, window, e): + gtk.main_quit() + return False + + def expose_event(self, widget, event): + cr = widget.window.cairo_create() + + width, height = widget.window.get_size() + global resized + + if not resized: + alloc_size = ((0, width), (0, height)) + self.resize_canvas(alloc_size) + resized = True + cr.translate(self.origin[0], self.origin[1]) + self.draw(cr, (width, height), False) + + return True + + def mouse_motion_event(self, widget, e): + e.x -= self.origin[0] + e.y -= self.origin[1] + self.mouse_moved(e) + + return False + + def mouse_event(self, widget, e): + e.x -= self.origin[0] + e.y -= self.origin[1] + self.mouse_pressed(e) + + return False + + def mouse_release_event(self, widget, e): + e.x -= self.origin[0] + e.y -= self.origin[1] + self.mouse_released(e) + + return False + + def key_release_event(self, widget, e): + self.key_hit(e) + + return False + + def size_allocate_event(self, widget, allocation): + alloc_size = ((allocation.x, allocation.x + allocation.width), + (allocation.y, allocation.y+allocation.height)) + self.resize_canvas(alloc_size) + + return False + + def relax_interaction_timeout(self): + if self.interactive_level > 0: + self.interactive_level -= 1 + self.canvas.queue_draw() + return True + + +class Toggle: + def __init__(self): + self.bounds = (0,0,0,0) + self.text = "" + self.on = False + def draw(self, cr): + cr.set_source_rgba(0,0,0,1) + cr.rectangle(bounds.left(), bounds.top(), + bounds.width(), bounds.height()) + if(on): + cr.fill() + cr.set_source_rgba(1,1,1,1) + else: + cr.stroke() + draw_text(cr, bounds.corner(0) + (5,2), text) + + def toggle(self): + self.on = not self.on + def set(self, state): + self.on = state + def handle_click(self, e): + if bounds.contains((e.x, e,y)) and e.button == 1: + toggle() + +def toggle_events(toggles, e): + for t in toggles: + t.handle_click(e) + +def draw_toggles(cr, toggles): + for t in toggles: + t.draw(cr) + + +current_toys = [] + +def draw_text(cr, loc, txt, bottom = False, font="Sans 12"): + import pango + layout = pangocairo.CairoContext.create_layout (cr) + layout.set_font_description (pango.FontDescription(font)) + + layout.set_text(txt) + bounds = layout.get_pixel_extents() + cr.move_to(loc[0], loc[1]) + if bottom: + if bottom == "topright": + cr.move_to(loc[0] - bounds[1][2], + loc[1]) + elif bottom == "bottomright": + cr.move_to(loc[0] - bounds[1][2], + loc[1] - bounds[1][3]) + elif bottom == "topleft": + cr.move_to(loc[0], + loc[1]) + else: + cr.move_to(loc[0], loc[1] - bounds[1][3]) + cr.show_layout(layout) + return bounds + +# Framework Accessors + +# Gui Event Callbacks + +def make_about(evt): + about_window = gtk.Window(gtk.WINDOW_TOPLEVEL) + about_window.set_title("About") + about_window.set_resizable(True) + + about_text = gtk.TextView() + buf = about_text.get_buffer() + buf.set_text("Toy lib2geom application", -1) + about_window.add(about_text) + + about_window.show_all() + +def save_cairo(evt): + d = gtk.FileChooserDialog("Save file as svg, png or pdf", + None, + gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) + + if(d.run() == gtk.RESPONSE_ACCEPT): + filename = d.get_filename() + cr_s = None + left, top, width, height = current_toys[0].get_save_size() + + if filename[-4:] == ".pdf": + cr_s = cairo.PDFSurface(filename, width, height) + elif filename[-4:] == ".png": + cr_s = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height ) + else: + cr_s = cairo.SVGSurface(filename, width, height) + cr = cairo.Context(cr_s) + cr = pangocairo.CairoContext(cairo.Context(cr_s)) + cr.translate(-left, -top) + current_toys[0].draw(cr, (width, height), True) + + cr.show_page() + del cr + del cr_s + d.destroy() + +resized = False + + +def FileMenuAction(): + pass + +ui = """ +<ui> + <menubar> + <menu name="FileMenu" action="FileMenuAction"> + <menuitem name="Save" action="save_cairo" /> + <menuitem name="Quit" action="gtk.main_quit" /> + <placeholder name="FileMenuAdditions" /> + </menu> + <menu name="HelpMenu" action="HelpMenuAction"> + <menuitem name="About" action="make_about" /> + </menu> + </menubar> +</ui> +""" + +def init(argv, t, width, height): + global current_toys + current_toys.append(t) + + t.first_time(argv) + + t.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + t.window.set_title("title") + +# Creates the menu from the menu data above + uim = gtk.UIManager() + ag = gtk.ActionGroup('menu_actiongroup') + ag.add_actions([('FileMenuAction', None, 'File', None, None, None), + ('save_cairo', None, 'Save screenshot', None, None, save_cairo), + ('gtk.main_quit', None, 'Quit', None, None, gtk.main_quit), + ('HelpMenuAction', None, 'Help', None, None, None), + ('make_about', None, 'About', None, None, make_about) + ]) + uim.insert_action_group(ag, 0) + uim.add_ui_from_string(ui) + menu = uim.get_widget("/ui/menubar") + # Creates the menu from the menu data above + #uim = gtk.UIManager() + #uim.add_ui_from_string(ui) + #menu = uim.get_widget("ui/menubar") + + t.window.connect("delete_event", t.delete_event) + + t.canvas = gtk.DrawingArea() + + t.canvas.add_events((gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.KEY_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.SCROLL_MASK)) + + t.canvas.connect("expose_event", t.expose_event) + t.canvas.connect("button_press_event", t.mouse_event) + t.canvas.connect("button_release_event", t.mouse_release_event) + t.canvas.connect("motion_notify_event", t.mouse_motion_event) + t.canvas.connect("key_press_event", t.key_release_event) + t.canvas.connect("size-allocate", t.size_allocate_event) + t.canvas.connect("scroll_event", t.scroll_event) + + t.vbox = gtk.VBox(False, 0) + t.window.add(t.vbox) + + t.vbox.pack_start (menu, False, False, 0) + + pain = gtk.VPaned() + t.vbox.pack_start(pain, True, True, 0) + pain.add1(t.canvas) + + t.canvas.set_size_request(width, height) + t.window.show_all() + + # Make sure the canvas can receive key press events. + t.canvas.set_flags(gtk.CAN_FOCUS) + t.canvas.grab_focus() + assert(t.canvas.is_focus()) + + t.gtk_ready() + gobject.timeout_add(1000, t.relax_interaction_timeout) + + gtk.main() + +def get_vbox(): + return current_toys[0].vbox diff --git a/src/toys/uncross.cpp b/src/toys/uncross.cpp new file mode 100644 index 0000000..aea5438 --- /dev/null +++ b/src/toys/uncross.cpp @@ -0,0 +1,500 @@ +#include <iostream> +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> +#include <2geom/basic-intersection.h> +#include <2geom/pathvector.h> + +#include <cstdlib> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/ord.h> +using namespace Geom; +using namespace std; + +void draw_rect(cairo_t *cr, Point tl, Point br) { + cairo_move_to(cr, tl[X], tl[Y]); + cairo_line_to(cr, br[X], tl[Y]); + cairo_line_to(cr, br[X], br[Y]); + cairo_line_to(cr, tl[X], br[Y]); + cairo_close_path(cr); +} + +void draw_bounds(cairo_t *cr, PathVector ps) { + srand(0); + vector<Rect> bnds; + for(auto & p : ps) { + for(const auto & it : p) { + Rect bounds = (it.boundsFast()); + bnds.push_back(bounds); + cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5); + //draw_rect(cr, bounds.min(), bounds.max()); + cairo_stroke(cr); + } + } + { + std::vector<std::vector<unsigned> > res = sweep_bounds(bnds); + cairo_set_line_width(cr,0.5); + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + for(unsigned i = 0; i < res.size(); i++) { + for(unsigned j = 0; j < res[i].size(); j++) { + draw_line_seg(cr, bnds[i].midpoint(), bnds[res[i][j]].midpoint()); + cairo_stroke(cr); + } + } + cairo_restore(cr); + } +} + +void mark_verts(cairo_t *cr, PathVector ps) { + for(auto & p : ps) + for(const auto & it : p) + draw_cross(cr, it.initialPoint()); +} + +int winding(PathVector ps, Point p) { + int wind = 0; + for(const auto & i : ps) + wind += winding(i,p); + return wind; +} + +template<typename T> +//std::vector<T>::iterator +T* insort(std::vector<T> &v, const T& val) +{ + T* iter = upper_bound(v.begin(), v.end(), val); + + unsigned offset = iter - v.begin(); + v.insert(iter, val); + + return offset + v.begin(); +} + + +class Uncross{ +public: + class Piece{ + public: + Rect bounds; + Interval parameters; + Curve const* curve; + D2<SBasis> sb; + int mark; + int id; + }; + class Crossing{ + public: + vector<int> joins; + //double crossing_product; // first cross(d^nA, d^nB) for n that is non-zero, or 0 if the two curves are the same + }; + vector<Rect> rs; + PathVector* pths; + cairo_t* cr; + std::vector<Piece> pieces; + + Uncross(PathVector &pt, cairo_t* cr):pths(&pt), cr(cr) {} + + void build() { + cairo_save(cr); + PathVector &ps(*pths); + for(auto & p : ps) { + for(const auto & it : p) { + Rect bounds = (it.boundsExact()); + rs.push_back(bounds); + //cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5); + //draw_rect(cr, bounds.min(), bounds.max()); + cairo_stroke(cr); + pieces.emplace_back(); + pieces.back().bounds = bounds; + pieces.back().curve = ⁢ + pieces.back().parameters = Interval(0,1); + pieces.back().sb = it.toSBasis(); + } + } + cairo_restore(cr); + } + + 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; + } + + }; + + std::vector<Event> events; + std::vector<unsigned> open; + + int cmpy(Piece* a, Piece* b, Interval X) { + if(a->bounds[Y].max() < b->bounds[Y].min()) { // bounds are strictly ordered + return -1; + } + if(a->bounds[Y].min() > b->bounds[Y].max()) { // bounds are strictly ordered + return 1; + } + std::vector<std::pair<double, double> > xs; + find_intersections(xs, a->sb, b->sb); + if(!xs.empty()) { + polish_intersections( xs, a->sb, b->sb); + // must split around these points to make new Pieces + for(auto & x : xs) { + std::cout << "cross:" << x.first << " , " << x.second << "\n"; + + cairo_save(cr); + draw_circ(cr, a->sb(x.first)); + cairo_stroke(cr); + cairo_restore(cr); + int ix = events[0].ix; + if(0){ + pop_heap(events.begin(), events.end()); + events.pop_back(); + std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix); + open.erase(iter); + std::cout << "kill\n"; + } + + } + } else { // any point gives an order + vector<double> ar = roots(a->sb[0] - X.middle()); + vector<double> br = roots(b->sb[0] - X.middle()); + if ((ar.size() == 1) and (br.size() == 1)) + + return a->sb[1](ar[0]) < b->sb[1](br[0]); + } + return 0; // FIXME + } + + + static void + draw_interval(cairo_t* cr, Interval I, Point origin, Point /*dir*/) { + cairo_save(cr); + cairo_set_line_width(cr, 0.5); + + cairo_move_to(cr, Point(I.min(), -3) + origin); + cairo_line_to(cr, Point(I.min(), +3) + origin); + cairo_move_to(cr, Point(I.max(), -3) + origin); + cairo_line_to(cr, Point(I.max(), +3) + origin); + + cairo_move_to(cr, Point(I.min(), 0) + origin); + cairo_line_to(cr, Point(I.min(), 0) + origin); + cairo_stroke(cr); + cairo_restore(cr); + } + void broke_sweep_bounds() { + cairo_save(cr); + + cairo_set_source_rgb(cr, 1, 0, 0); + events.reserve(rs.size()*2); + std::vector<std::vector<unsigned> > pairs(rs.size()); + + for(unsigned i = 0; i < rs.size(); i++) { + events.emplace_back(rs[i].left(), i, false); + events.emplace_back(rs[i].right(), i, true); + } + //std::sort(events.begin(), events.end()); + std::make_heap(events.begin(), events.end()); + + //for(unsigned i = 0; i < events.size(); i++) { + + int i = 0; + while(!events.empty()) { + unsigned ix = events[0].ix; + if(events[0].closing) { + std::vector<unsigned>::iterator iter = std::find(open.begin(), open.end(), ix); + if(iter != open.end()) { + cairo_save(cr); + cairo_set_source_rgb(cr, 0, 1, 0); + cairo_set_line_width(cr, 0.25); + cairo_rectangle(cr, rs[*iter]); + cairo_stroke(cr); + cairo_restore(cr); + open.erase(iter);} + } else { + draw_interval(cr, rs[ix][0], Point(0,5*i+10), Point(0, 1)); + for(unsigned int jx : open) { + OptInterval oiy = intersect(rs[ix][Y], rs[jx][Y]); + if(oiy) { + pairs[jx].push_back(ix); + std::cout << "oiy:" << *oiy << std::endl; + OptInterval oix = intersect(rs[ix][X], rs[jx][X]); + if(oix) { + std::cout << *oix; + std::cout << cmpy(&pieces[ix], &pieces[jx], *oix); + continue; + } + //draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint()); + cairo_stroke(cr); + } + } + open.push_back(ix); + } + pop_heap(events.begin(), events.end()); + events.pop_back(); + i++; + } + //return pairs; + cairo_restore(cr); + } + + void sweep_bounds() { + cairo_save(cr); + + cairo_set_source_rgb(cr, 1, 0, 0); + events.reserve(rs.size()*2); + std::vector<std::vector<unsigned> > pairs(rs.size()); + + for(unsigned i = 0; i < rs.size(); i++) { + events.emplace_back(rs[i].left(), i, false); + events.emplace_back(rs[i].right(), i, true); + } + std::sort(events.begin(), events.end()); + + 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()) { + cairo_save(cr); + cairo_set_source_rgb(cr, 0, 1, 0); + cairo_set_line_width(cr, 0.25); + cairo_rectangle(cr, rs[*iter]); + cairo_stroke(cr); + cairo_restore(cr); + open.erase(iter); + } + } else { + draw_interval(cr, rs[ix][0], Point(0,5*i+10), Point(0, 1)); + for(unsigned int jx : open) { + OptInterval oiy = intersect(rs[ix][Y], rs[jx][Y]); + if(oiy) { + pairs[jx].push_back(ix); + std::cout << "oiy:" << *oiy << std::endl; + OptInterval oix = intersect(rs[ix][X], rs[jx][X]); + if(oix) { + std::cout << *oix; + std::cout << cmpy(&pieces[ix], &pieces[jx], *oix); + continue; + } + //draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint()); + cairo_stroke(cr); + } + } + open.push_back(ix); + } + } + + cairo_restore(cr); + } + + +}; + +class WindingTest: public Toy { + PathVector path; + PointHandle test_pt_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_set_line_width(cr, 0.5); + cairo_save(cr); + cairo_path(cr, path); + cairo_set_source_rgb(cr, 1, 1, 0); + cairo_stroke(cr); + cairo_restore(cr); + //mark_verts(cr, path); + cairo_stroke(cr); + cairo_set_line_width(cr, 1); + //draw_bounds(cr, path); + if(0) { + Uncross uc(path, cr); + uc.build(); + + uc.sweep_bounds(); + + //draw_bounds(cr, path); mark_verts(cr, path); + } + + cairo_save(cr); + PathVector &ps(path); + vector<Rect> rs; + std::vector<Uncross::Piece> pieces; + std::vector<Uncross::Crossing> crosses; + int id_counter = 0; + for(auto & p : ps) { + int piece_start = pieces.size(); + for(Path::iterator it = p.begin(); it != p.end(); ++it) { + Rect bounds = (it->boundsExact()); + rs.push_back(bounds); + /*cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5); + draw_rect(cr, bounds.min(), bounds.max()); + cairo_stroke(cr);*/ + pieces.emplace_back(); + pieces.back().bounds = bounds; + pieces.back().curve = &*it; + pieces.back().parameters = Interval(0,1); + pieces.back().sb = it->toSBasis(); + pieces.back().mark = 0; + pieces.back().id = id_counter++; + if(it != p.begin() and !crosses.empty()) + crosses.back().joins.push_back(pieces.back().id); + crosses.emplace_back(); + crosses.back().joins.push_back(pieces.back().id); + } + crosses.back().joins.push_back(pieces[piece_start].id); + //crosses[cross_start].joins.push_back(pieces.back().id); + } + cairo_restore(cr); + std::vector<std::vector<unsigned> > prs = sweep_bounds(rs); + + cairo_save(cr); + std::vector<Uncross::Piece> new_pieces; + for(unsigned i = 0; i < prs.size(); i++) { + int ix = i; + Uncross::Piece& A = pieces[ix]; + for(int jx : prs) { + Uncross::Piece& B = pieces[jx]; + cairo_set_source_rgb(cr, 0, 1, 0); + draw_line_seg(cr, rs[ix].midpoint(), rs[jx].midpoint()); + cairo_stroke(cr); + cout << ix << ", " << jx << endl; + std::vector<std::pair<double, double> > xs; + find_intersections(xs, A.sb, B.sb); + if(not xs.empty()) { + polish_intersections( xs, A.sb, B.sb); + // must split around these points to make new Pieces + double A_t_prev = 0; + double B_t_prev = 0; + int A_prec_id = A.id; + int B_prec_id = B.id; + for(unsigned cv_idx = 0; cv_idx <= xs.size(); cv_idx++) { + double A_t = 1; + double B_t = 1; + if(cv_idx < xs.size()) { + A_t = xs[cv_idx].first; + B_t = xs[cv_idx].second; + } + + cairo_save(cr); + draw_circ(cr, A.sb(xs[cv_idx].first)); + cairo_stroke(cr); + cairo_restore(cr); + + Interval A_slice(A_t_prev, A_t); + Interval B_slice(B_t_prev, B_t); + if((A_slice.extent() > 0) or (B_slice.extent() > 0)) { + cout << "Aslice" <<A_slice << endl; + D2<SBasis> Asb = portion(A.sb, A_slice); + OptRect Abnds = bounds_exact(Asb); + if(Abnds) { + new_pieces.emplace_back(); + new_pieces.back().bounds = *Abnds; + new_pieces.back().curve = A.curve; + new_pieces.back().parameters = A_slice; + new_pieces.back().sb = Asb; + new_pieces.back().id = id_counter++; + crosses.emplace_back(); + crosses.back().joins.push_back(A_prec_id); + crosses.back().joins.push_back(new_pieces.back().id); + A.mark = 1; + A_prec_id = new_pieces.back().id; + } + + cout << "Bslice" <<B_slice << endl; + D2<SBasis> Bsb = portion(B.sb, B_slice); + OptRect Bbnds = bounds_exact(Bsb); + if(Bbnds) { + new_pieces.emplace_back(); + new_pieces.back().bounds = *Bbnds; + new_pieces.back().curve = B.curve; + new_pieces.back().parameters = B_slice; + new_pieces.back().sb = Bsb; + new_pieces.back().id = id_counter++; + crosses.emplace_back(); + crosses.back().joins.push_back(B_prec_id); + crosses.back().joins.push_back(new_pieces.back().id); + B.mark = 1; + B_prec_id = new_pieces.back().id; + } + } + A_t_prev = A_t; + B_t_prev = B_t; + } + } + } + } + if(1)for(unsigned i = 0; i < prs.size(); i++) { + if(not pieces[i].mark) + new_pieces.push_back(pieces[i]); + } + cairo_restore(cr); + + for(auto & new_piece : new_pieces) { + cout << new_piece.parameters << ", " <<new_piece.id <<endl; + cairo_save(cr); + cairo_rectangle(cr, new_piece.bounds); + cairo_set_source_rgba(cr, 0,1,0,0.1); + cairo_fill(cr); + cairo_set_source_rgba(cr, 0.3,0.3,0,0.1); + cairo_rectangle(cr, new_piece.bounds); + cairo_stroke(cr); + cairo_restore(cr); + cairo_d2_sb(cr, new_piece.sb); + cairo_stroke(cr); + } + + + cout << "crossings:"; + for(auto & cr : crosses) { + for(int join : cr.joins) { + cout << join << ", "; + } + cout << endl; + } + + std::streambuf* cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(notify->rdbuf()); + *notify << "\nwinding:" << winding(path, test_pt_handle.pos) << "\n"; + std::cout.rdbuf(cout_buffer); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + WindingTest () : test_pt_handle(300,300) {} + void first_time(int argc, char** argv) override { + const char *path_name="winding.svgd"; + if(argc > 1) + path_name = argv[1]; + path = read_svgd(path_name); + OptRect bounds = bounds_exact(path); + if (bounds) { + path *= Translate(Point(10,10) - bounds->min()); + } + + handles.push_back(&test_pt_handle); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new WindingTest()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/winding-test.cpp b/src/toys/winding-test.cpp new file mode 100644 index 0000000..a0f7608 --- /dev/null +++ b/src/toys/winding-test.cpp @@ -0,0 +1,107 @@ +#include <2geom/path.h> +#include <2geom/svg-path-parser.h> +#include <2geom/path-intersection.h> + +#include <iostream> +#include <cstdlib> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> +#include <2geom/ord.h> +using namespace Geom; + +void draw_rect(cairo_t *cr, Point tl, Point br) { + cairo_move_to(cr, tl[X], tl[Y]); + cairo_line_to(cr, br[X], tl[Y]); + cairo_line_to(cr, br[X], br[Y]); + cairo_line_to(cr, tl[X], br[Y]); + cairo_close_path(cr); +} + +void draw_bounds(cairo_t *cr, PathVector ps) { + srand(0); + vector<Rect> bnds; + for(auto & p : ps) { + for(const auto & it : p) { + Rect bounds = (it.boundsFast()); + bnds.push_back(bounds); + cairo_set_source_rgba(cr, uniform(), uniform(), uniform(), .5); + //draw_rect(cr, bounds.min(), bounds.max()); + cairo_stroke(cr); + } + } + { + std::vector<std::vector<unsigned> > res = sweep_bounds(bnds); + cairo_set_line_width(cr,0.5); + cairo_save(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + for(unsigned i = 0; i < res.size(); i++) { + for(unsigned j = 0; j < res[i].size(); j++) { + draw_line_seg(cr, bnds[i].midpoint(), bnds[res[i][j]].midpoint()); + cairo_stroke(cr); + } + } + cairo_restore(cr); + } +} + +void mark_verts(cairo_t *cr, PathVector ps) { + for(auto & p : ps) + for(const auto & it : p) + draw_cross(cr, it.initialPoint()); +} + +int winding(PathVector ps, Point p) { + int wind = 0; + for(const auto & i : ps) + wind += winding(i,p); + return wind; +} + +class WindingTest: public Toy { + PathVector path; + PointHandle test_pt_handle; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_path(cr, path); + cairo_stroke(cr); + mark_verts(cr, path); + draw_bounds(cr, path); + + //draw_bounds(cr, path); mark_verts(cr, path); + + std::streambuf* cout_buffer = std::cout.rdbuf(); + std::cout.rdbuf(notify->rdbuf()); + *notify << "\nwinding:" << winding(path, test_pt_handle.pos) << "\n"; + std::cout.rdbuf(cout_buffer); + + Toy::draw(cr, notify, width, height, save,timer_stream); + } + + public: + WindingTest () : test_pt_handle(300,300) {} + void first_time(int argc, char** argv) override { + const char *path_name="winding.svgd"; + if(argc > 1) + path_name = argv[1]; + path = read_svgd(path_name); + + handles.push_back(&test_pt_handle); + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new WindingTest()); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/toys/worms.cpp b/src/toys/worms.cpp new file mode 100644 index 0000000..81e7558 --- /dev/null +++ b/src/toys/worms.cpp @@ -0,0 +1,138 @@ +#include <2geom/d2.h> +#include <2geom/sbasis.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/svg-path-parser.h> +#include <2geom/sbasis-math.h> + +#include <toys/path-cairo.h> +#include <toys/toy-framework-2.h> + +//Random walkers toy, written by mgsloan, initially for a school video proj. + +using namespace Geom; + +static void dot_plot(cairo_t *cr, Piecewise<D2<SBasis> > const &M, double min, double max, double space=10){ + for( double t = min; t < max; t += space) { + Point pos = M(t), perp = M.valueAndDerivatives(t, 2)[1].cw() * 3; + draw_line_seg(cr, pos + perp, pos - perp); + } + cairo_stroke(cr); +} + +D2<SBasis> random_d2() { + D2<SBasis> ret(SBasis(6, Linear()), + SBasis(6, Linear())); + ret[0][0] = Linear(uniform()*720, uniform()*720); + ret[1][0] = Linear(uniform()*480, uniform()*480); + + int mul = 1; + for(int i = 1; i < 6; i++) { + ret[0][i] = Linear(uniform()*2000*mul - 1000, uniform()*2000*mul - 1000); + ret[1][i] = Linear(uniform()*2000*mul - 1000, uniform()*2000*mul - 1000); + mul*=2; + } + return ret; +} + +class Worm { + Piecewise<D2<SBasis> > path; + int spawn_time, last_time; + double red, green, blue, length; + public: + void tele(int t) { + Piecewise<D2<SBasis> > new_path(portion(path, 0, t - last_time)); + new_path.push(random_d2(), path.domain().max()+1); + path = arc_length_parametrization(new_path); + } + void add_section(const D2<SBasis> x) { + Piecewise<D2<SBasis> > new_path(path); + D2<SBasis> seg(x); + seg[0][0][0] = path.segs.back()[0][0][1]; + seg[1][0][0] = path.segs.back()[1][0][1]; + new_path.push(seg, path.domain().max()+1); + path = arc_length_parametrization(new_path); + } + Worm (int t, double r, double g, double b, double l) : spawn_time(t), last_time(t), red(r), green(g), blue(b), length(l) { + path = Piecewise<D2<SBasis> >(random_d2()); + add_section(random_d2()); + } + void draw(cairo_t *cr, int t) { + if(t - last_time > path.domain().max()) add_section(random_d2()); + if(t - last_time - length > path.cuts[1]) { + Piecewise<D2<SBasis> > new_path; + new_path.push_cut(0); + for(unsigned i = 1; i < path.size(); i++) { + new_path.push(path[i], path.cuts[i+1] - path.cuts[1]); + } + last_time = t - length; + path = new_path; + } + cairo_set_source_rgb(cr, red, green, blue); + Piecewise<D2<SBasis> > port = portion(path, std::max(t - last_time - length, 0.), t - last_time); + cairo_pw_d2_sb(cr, port); + cairo_stroke(cr); + + double d = 4; + cairo_set_dash(cr, &d, 1, 0); + for(unsigned i = 1; i < path.size(); i++) { + if(path[i].at0() != path[i-1].at1()) { + draw_line_seg(cr, path[i].at0(), path[i-1].at1()); + } + } + cairo_stroke(cr); + cairo_set_dash(cr, &d, 0, 0); + + cairo_set_source_rgb(cr, 0., 0., 1.); + dot_plot(cr, path, std::max(t - last_time - length, 0.), t - last_time); + } + void reverse_direction(int t) { + path = portion(path, 0, t - last_time); + D2<SBasis> seg = random_d2(), last = path[path.size()-1]; + for(unsigned c = 0; c < 2; c++) + for(unsigned d = 1; d < seg[c].size() && d < last[c].size(); d++) + seg[c][d][0] = -last[c][d][1]; + add_section(seg); + } +}; + +class Intro: public Toy { + int t; + vector<Worm> worms; + void draw(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { + t++; + if(t < 40 && t % 2 == 0) { + worms.emplace_back(t, uniform(), uniform(), uniform(), uniform() * 200 + 50); + } + + for(auto & worm : worms) { + worm.draw(cr, t); + if(uniform() > .999) worm.tele(t); + } + + Toy::draw(cr, notify, width, height, save,timer_stream); + redraw(); + } + + int should_draw_bounds() override { return 0; } + + public: + Intro () { + t = 0; + } +}; + +int main(int argc, char **argv) { + init(argc, argv, new Intro(), 720, 480); + return 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=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : |