summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: a7ea25e)
raw | patch | inline | side by side (parent: a7ea25e)
author | pjrm <pjrm@users.sourceforge.net> | |
Tue, 7 Apr 2009 06:32:25 +0000 (06:32 +0000) | ||
committer | pjrm <pjrm@users.sourceforge.net> | |
Tue, 7 Apr 2009 06:32:25 +0000 (06:32 +0000) |
index c8d99c4308fbcdb22c7918627d774205b0144ab3..96a06376cb43a1b9a2299f2bf27e08290fc0d75d 100644 (file)
-/*\r
- * Implement the Bezier clipping algorithm for finding\r
- * Bezier curve intersection points and collinear normals\r
- *\r
- * Authors:\r
- * Marco Cecchetti <mrcekets at gmail.com>\r
- *\r
- * Copyright 2008 authors\r
- *\r
- * This library is free software; you can redistribute it and/or\r
- * modify it either under the terms of the GNU Lesser General Public\r
- * License version 2.1 as published by the Free Software Foundation\r
- * (the "LGPL") or, at your option, under the terms of the Mozilla\r
- * Public License Version 1.1 (the "MPL"). If you do not alter this\r
- * notice, a recipient may use your version of this file under either\r
- * the MPL or the LGPL.\r
- *\r
- * You should have received a copy of the LGPL along with this library\r
- * in the file COPYING-LGPL-2.1; if not, write to the Free Software\r
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
- * You should have received a copy of the MPL along with this library\r
- * in the file COPYING-MPL-1.1\r
- *\r
- * The contents of this file are subject to the Mozilla Public License\r
- * Version 1.1 (the "License"); you may not use this file except in\r
- * compliance with the License. You may obtain a copy of the License at\r
- * http://www.mozilla.org/MPL/\r
- *\r
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY\r
- * OF ANY KIND, either express or implied. See the LGPL or the MPL for\r
- * the specific language governing rights and limitations.\r
- */\r
-\r
-\r
-\r
-\r
-#include <2geom/basic-intersection.h>\r
-#include <2geom/choose.h>\r
-#include <2geom/point.h>\r
-#include <2geom/interval.h>\r
-#include <2geom/bezier.h>\r
-//#include <2geom/convex-cover.h>\r
-#include <2geom/numeric/matrix.h>\r
-\r
-#include <cassert>\r
-#include <vector>\r
-#include <algorithm>\r
-#include <utility>\r
-//#include <iomanip>\r
-\r
-\r
-\r
-\r
-#define VERBOSE 0\r
-#define CHECK 0\r
-\r
-\r
-namespace Geom {\r
-\r
-namespace detail { namespace bezier_clipping {\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// for debugging\r
-//\r
-\r
-inline\r
-void print(std::vector<Point> const& cp, const char* msg = "")\r
-{\r
- std::cerr << msg << std::endl;\r
- for (size_t i = 0; i < cp.size(); ++i)\r
- std::cerr << i << " : " << cp[i] << std::endl;\r
-}\r
-\r
-template< class charT >\r
-inline\r
-std::basic_ostream<charT> &\r
-operator<< (std::basic_ostream<charT> & os, const Interval & I)\r
-{\r
- os << "[" << I.min() << ", " << I.max() << "]";\r
- return os;\r
-}\r
-\r
-inline\r
-double angle (std::vector<Point> const& A)\r
-{\r
- size_t n = A.size() -1;\r
- double a = std::atan2(A[n][Y] - A[0][Y], A[n][X] - A[0][X]);\r
- return (180 * a / M_PI);\r
-}\r
-\r
-inline\r
-size_t get_precision(Interval const& I)\r
-{\r
- double d = I.extent();\r
- double e = 0.1, p = 10;\r
- int n = 0;\r
- while (n < 16 && d < e)\r
- {\r
- p *= 10;\r
- e = 1/p;\r
- ++n;\r
- }\r
- return n;\r
-}\r
-\r
-inline\r
-void range_assertion(int k, int m, int n, const char* msg)\r
-{\r
- if ( k < m || k > n)\r
- {\r
- std::cerr << "range assertion failed: \n"\r
- << msg << std::endl\r
- << "value: " << k\r
- << " range: " << m << ", " << n << std::endl;\r
- assert (k >= m && k <= n);\r
- }\r
-}\r
-\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// convex hull\r
-\r
-/*\r
- * return true in case the oriented polyline p0, p1, p2 is a right turn\r
- */\r
-inline\r
-bool is_a_right_turn (Point const& p0, Point const& p1, Point const& p2)\r
-{\r
- if (p1 == p2) return false;\r
- Point q1 = p1 - p0;\r
- Point q2 = p2 - p0;\r
- if (q1 == -q2) return false;\r
- return (cross (q1, q2) < 0);\r
-}\r
-\r
-/*\r
- * return true if p < q wrt the lexicographyc order induced by the coordinates\r
- */\r
-struct lex_less\r
-{\r
- bool operator() (Point const& p, Point const& q)\r
- {\r
- return ((p[X] < q[X]) || (p[X] == q[X] && p[Y] < q[Y]));\r
- }\r
-};\r
-\r
-/*\r
- * return true if p > q wrt the lexicographyc order induced by the coordinates\r
- */\r
-struct lex_greater\r
-{\r
- bool operator() (Point const& p, Point const& q)\r
- {\r
- return ((p[X] > q[X]) || (p[X] == q[X] && p[Y] > q[Y]));\r
- }\r
-};\r
-\r
-/*\r
- * Compute the convex hull of a set of points.\r
- * The implementation is based on the Andrew's scan algorithm\r
- * note: in the Bezier clipping for collinear normals it seems\r
- * to be more stable wrt the Graham's scan algorithm and in general\r
- * a bit quikier\r
- */\r
-void convex_hull (std::vector<Point> & P)\r
-{\r
- size_t n = P.size();\r
- if (n < 2) return;\r
- std::sort(P.begin(), P.end(), lex_less());\r
- if (n < 4) return;\r
- // upper hull\r
- size_t u = 2;\r
- for (size_t i = 2; i < n; ++i)\r
- {\r
- while (u > 1 && !is_a_right_turn(P[u-2], P[u-1], P[i]))\r
- {\r
- --u;\r
- }\r
- std::swap(P[u], P[i]);\r
- ++u;\r
- }\r
- std::sort(P.begin() + u, P.end(), lex_greater());\r
- std::rotate(P.begin(), P.begin() + 1, P.end());\r
- // lower hull\r
- size_t l = u;\r
- size_t k = u - 1;\r
- for (size_t i = l; i < n; ++i)\r
- {\r
- while (l > k && !is_a_right_turn(P[l-2], P[l-1], P[i]))\r
- {\r
- --l;\r
- }\r
- std::swap(P[l], P[i]);\r
- ++l;\r
- }\r
- P.resize(l);\r
-}\r
-\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// numerical routines\r
-\r
-/*\r
- * Compute the binomial coefficient (n, k)\r
- */\r
-inline\r
-double binomial(unsigned int n, unsigned int k)\r
-{\r
- return choose<double>(n, k);\r
-}\r
-\r
-/*\r
- * Compute the determinant of the 2x2 matrix with column the point P1, P2\r
- */\r
-inline\r
-double det(Point const& P1, Point const& P2)\r
-{\r
- return P1[X]*P2[Y] - P1[Y]*P2[X];\r
-}\r
-\r
-/*\r
- * Solve the linear system [P1,P2] * P = Q\r
- * in case there isn't exactly one solution the routine returns false\r
- */\r
-inline\r
-bool solve(Point & P, Point const& P1, Point const& P2, Point const& Q)\r
-{\r
- double d = det(P1, P2);\r
- if (d == 0) return false;\r
- d = 1 / d;\r
- P[X] = det(Q, P2) * d;\r
- P[Y] = det(P1, Q) * d;\r
- return true;\r
-}\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// interval routines\r
-\r
-/*\r
- * Map the sub-interval I in [0,1] into the interval J and assign it to J\r
- */\r
-inline\r
-void map_to(Interval & J, Interval const& I)\r
-{\r
- double length = J.extent();\r
- J[1] = I.max() * length + J[0];\r
- J[0] = I.min() * length + J[0];\r
-}\r
-\r
-/*\r
- * The interval [1,0] is used to represent the empty interval, this routine\r
- * is just an helper function for creating such an interval\r
- */\r
-inline\r
-Interval make_empty_interval()\r
-{\r
- Interval I(0);\r
- I[0] = 1;\r
- return I;\r
-}\r
-\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// bezier curve routines\r
-\r
-/*\r
- * Return true if all the Bezier curve control points are near,\r
- * false otherwise\r
- */\r
-inline\r
-bool is_constant(std::vector<Point> const& A, double precision = EPSILON)\r
-{\r
- for (unsigned int i = 1; i < A.size(); ++i)\r
- {\r
- if(!are_near(A[i], A[0], precision))\r
- return false;\r
- }\r
- return true;\r
-}\r
-\r
-/*\r
- * Compute the hodograph of the bezier curve B and return it in D\r
- */\r
-inline\r
-void derivative(std::vector<Point> & D, std::vector<Point> const& B)\r
-{\r
- D.clear();\r
- size_t sz = B.size();\r
- if (sz == 0) return;\r
- if (sz == 1)\r
- {\r
- D.resize(1, Point(0,0));\r
- return;\r
- }\r
- size_t n = sz-1;\r
- D.reserve(n);\r
- for (size_t i = 0; i < n; ++i)\r
- {\r
- D.push_back(n*(B[i+1] - B[i]));\r
- }\r
-}\r
-\r
-/*\r
- * Compute the hodograph of the Bezier curve B rotated of 90 degree\r
- * and return it in D; we have N(t) orthogonal to B(t) for any t\r
- */\r
-inline\r
-void normal(std::vector<Point> & N, std::vector<Point> const& B)\r
-{\r
- derivative(N,B);\r
- for (size_t i = 0; i < N.size(); ++i)\r
- {\r
- N[i] = rot90(N[i]);\r
- }\r
-}\r
-\r
-/*\r
- * Compute the portion of the Bezier curve "B" wrt the interval [0,t]\r
- */\r
-inline\r
-void left_portion(Coord t, std::vector<Point> & B)\r
-{\r
- size_t n = B.size();\r
- for (size_t i = 1; i < n; ++i)\r
- {\r
- for (size_t j = n-1; j > i-1 ; --j)\r
- {\r
- B[j] = lerp(t, B[j-1], B[j]);\r
- }\r
- }\r
-}\r
-\r
-/*\r
- * Compute the portion of the Bezier curve "B" wrt the interval [t,1]\r
- */\r
-inline\r
-void right_portion(Coord t, std::vector<Point> & B)\r
-{\r
- size_t n = B.size();\r
- for (size_t i = 1; i < n; ++i)\r
- {\r
- for (size_t j = 0; j < n-i; ++j)\r
- {\r
- B[j] = lerp(t, B[j], B[j+1]);\r
- }\r
- }\r
-}\r
-\r
-/*\r
- * Compute the portion of the Bezier curve "B" wrt the interval "I"\r
- */\r
-inline\r
-void portion (std::vector<Point> & B , Interval const& I)\r
-{\r
- if (I.min() == 0)\r
- {\r
- if (I.max() == 1) return;\r
- left_portion(I.max(), B);\r
- return;\r
- }\r
- right_portion(I.min(), B);\r
- if (I.max() == 1) return;\r
- double t = I.extent() / (1 - I.min());\r
- left_portion(t, B);\r
-}\r
-\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// tags\r
-\r
-struct intersection_point_tag;\r
-struct collinear_normal_tag;\r
-template <typename Tag>\r
-void clip(Interval & dom,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B);\r
-template <typename Tag>\r
-void iterate(std::vector<Interval>& domsA,\r
- std::vector<Interval>& domsB,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B,\r
- Interval const& domA,\r
- Interval const& domB,\r
- double precision );\r
-\r
-\r
-////////////////////////////////////////////////////////////////////////////////\r
-// intersection\r
-\r
-/*\r
- * Make up an orientation line using the control points c[i] and c[j]\r
- * the line is returned in the output parameter "l" in the form of a 3 element\r
- * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized.\r
- */\r
-inline\r
-void orientation_line (std::vector<double> & l,\r
- std::vector<Point> const& c,\r
- size_t i, size_t j)\r
-{\r
- l[0] = c[j][Y] - c[i][Y];\r
- l[1] = c[i][X] - c[j][X];\r
- l[2] = cross(c[i], c[j]);\r
- double length = std::sqrt(l[0] * l[0] + l[1] * l[1]);\r
- assert (length != 0);\r
- l[0] /= length;\r
- l[1] /= length;\r
- l[2] /= length;\r
-}\r
-\r
-/*\r
- * Pick up an orientation line for the Bezier curve "c" and return it in\r
- * the output parameter "l"\r
- */\r
-inline\r
-void pick_orientation_line (std::vector<double> & l,\r
- std::vector<Point> const& c)\r
-{\r
- size_t i = c.size();\r
- while (--i > 0 && are_near(c[0], c[i]))\r
- {}\r
- if (i == 0)\r
- {\r
- // this should never happen because when a new curve portion is created\r
- // we check that it is not constant;\r
- // however this requires that the precision used in the is_constant\r
- // routine has to be the same used here in the are_near test\r
- assert(i != 0);\r
- }\r
- orientation_line(l, c, 0, i);\r
- //std::cerr << "i = " << i << std::endl;\r
-}\r
-\r
-/*\r
- * Make up an orientation line for constant bezier curve;\r
- * the orientation line is made up orthogonal to the other curve base line;\r
- * the line is returned in the output parameter "l" in the form of a 3 element\r
- * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized.\r
- */\r
-inline\r
-void orthogonal_orientation_line (std::vector<double> & l,\r
- std::vector<Point> const& c,\r
- Point const& p)\r
-{\r
- if (is_constant(c))\r
- {\r
- // this should never happen\r
- assert(!is_constant(c));\r
- }\r
- std::vector<Point> ol(2);\r
- ol[0] = p;\r
- ol[1] = (c.back() - c.front()).cw() + p;\r
- orientation_line(l, ol, 0, 1);\r
-}\r
-\r
-/*\r
- * Compute the signed distance of the point "P" from the normalized line l\r
- */\r
-inline\r
-double distance (Point const& P, std::vector<double> const& l)\r
-{\r
- return l[X] * P[X] + l[Y] * P[Y] + l[2];\r
-}\r
-\r
-/*\r
- * Compute the min and max distance of the control points of the Bezier\r
- * curve "c" from the normalized orientation line "l".\r
- * This bounds are returned through the output Interval parameter"bound".\r
- */\r
-inline\r
-void fat_line_bounds (Interval& bound,\r
- std::vector<Point> const& c,\r
- std::vector<double> const& l)\r
-{\r
- bound[0] = 0;\r
- bound[1] = 0;\r
- double d;\r
- for (size_t i = 0; i < c.size(); ++i)\r
- {\r
- d = distance(c[i], l);\r
- if (bound[0] > d) bound[0] = d;\r
- if (bound[1] < d) bound[1] = d;\r
- }\r
-}\r
-\r
-/*\r
- * return the x component of the intersection point between the line\r
- * passing through points p1, p2 and the line Y = "y"\r
- */\r
-inline\r
-double intersect (Point const& p1, Point const& p2, double y)\r
-{\r
- // we are sure that p2[Y] != p1[Y] because this routine is called\r
- // only when the lower or the upper bound is crossed\r
- double dy = (p2[Y] - p1[Y]);\r
- double s = (y - p1[Y]) / dy;\r
- return (p2[X]-p1[X])*s + p1[X];\r
-}\r
-\r
-/*\r
- * Clip the Bezier curve "B" wrt the fat line defined by the orientation\r
- * line "l" and the interval range "bound", the new parameter interval for\r
- * the clipped curve is returned through the output parameter "dom"\r
- */\r
-void clip_interval (Interval& dom,\r
- std::vector<Point> const& B,\r
- std::vector<double> const& l,\r
- Interval const& bound)\r
-{\r
- double n = B.size() - 1; // number of sub-intervals\r
- std::vector<Point> D; // distance curve control points\r
- D.reserve (B.size());\r
- double d;\r
- for (size_t i = 0; i < B.size(); ++i)\r
- {\r
- d = distance (B[i], l);\r
- D.push_back (Point(i/n, d));\r
- }\r
- //print(D);\r
-\r
- convex_hull(D);\r
- std::vector<Point> & p = D;\r
- //print(p);\r
-\r
- bool plower, phigher;\r
- bool clower, chigher;\r
- double t, tmin = 1, tmax = 0;\r
-// std::cerr << "bound : " << bound << std::endl;\r
-\r
- plower = (p[0][Y] < bound.min());\r
- phigher = (p[0][Y] > bound.max());\r
- if (!(plower || phigher)) // inside the fat line\r
- {\r
- if (tmin > p[0][X]) tmin = p[0][X];\r
- if (tmax < p[0][X]) tmax = p[0][X];\r
-// std::cerr << "0 : inside " << p[0]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;\r
- }\r
-\r
- for (size_t i = 1; i < p.size(); ++i)\r
- {\r
- clower = (p[i][Y] < bound.min());\r
- chigher = (p[i][Y] > bound.max());\r
- if (!(clower || chigher)) // inside the fat line\r
- {\r
- if (tmin > p[i][X]) tmin = p[i][X];\r
- if (tmax < p[i][X]) tmax = p[i][X];\r
-// std::cerr << i << " : inside " << p[i]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax\r
-// << std::endl;\r
- }\r
- if (clower != plower) // cross the lower bound\r
- {\r
- t = intersect(p[i-1], p[i], bound.min());\r
- if (tmin > t) tmin = t;\r
- if (tmax < t) tmax = t;\r
- plower = clower;\r
-// std::cerr << i << " : lower " << p[i]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax\r
-// << std::endl;\r
- }\r
- if (chigher != phigher) // cross the upper bound\r
- {\r
- t = intersect(p[i-1], p[i], bound.max());\r
- if (tmin > t) tmin = t;\r
- if (tmax < t) tmax = t;\r
- phigher = chigher;\r
-// std::cerr << i << " : higher " << p[i]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax\r
-// << std::endl;\r
- }\r
- }\r
-\r
- // we have to test the closing segment for intersection\r
- size_t last = p.size() - 1;\r
- clower = (p[0][Y] < bound.min());\r
- chigher = (p[0][Y] > bound.max());\r
- if (clower != plower) // cross the lower bound\r
- {\r
- t = intersect(p[last], p[0], bound.min());\r
- if (tmin > t) tmin = t;\r
- if (tmax < t) tmax = t;\r
-// std::cerr << "0 : lower " << p[0]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;\r
- }\r
- if (chigher != phigher) // cross the upper bound\r
- {\r
- t = intersect(p[last], p[0], bound.max());\r
- if (tmin > t) tmin = t;\r
- if (tmax < t) tmax = t;\r
-// std::cerr << "0 : higher " << p[0]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;\r
- }\r
-\r
- dom[0] = tmin;\r
- dom[1] = tmax;\r
-}\r
-\r
-/*\r
- * Clip the Bezier curve "B" wrt the Bezier curve "A" for individuating\r
- * intersection points the new parameter interval for the clipped curve\r
- * is returned through the output parameter "dom"\r
- */\r
-template <>\r
-inline\r
-void clip<intersection_point_tag> (Interval & dom,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B)\r
-{\r
- std::vector<double> bl(3);\r
- Interval bound;\r
- if (is_constant(A))\r
- {\r
- Point M = middle_point(A.front(), A.back());\r
- orthogonal_orientation_line(bl, B, M);\r
- }\r
- else\r
- {\r
- pick_orientation_line(bl, A);\r
- }\r
- fat_line_bounds(bound, A, bl);\r
- clip_interval(dom, B, bl, bound);\r
-}\r
-\r
-\r
-///////////////////////////////////////////////////////////////////////////////\r
-// collinear normal\r
-\r
-/*\r
- * Compute a closed focus for the Bezier curve B and return it in F\r
- * A focus is any curve through which all lines perpendicular to B(t) pass.\r
- */\r
-inline\r
-void make_focus (std::vector<Point> & F, std::vector<Point> const& B)\r
-{\r
- assert (B.size() > 2);\r
- size_t n = B.size() - 1;\r
- normal(F, B);\r
- Point c(1, 1);\r
-#if VERBOSE\r
- if (!solve(c, F[0], -F[n-1], B[n]-B[0]))\r
- {\r
- std::cerr << "make_focus: unable to make up a closed focus" << std::endl;\r
- }\r
-#else\r
- solve(c, F[0], -F[n-1], B[n]-B[0]);\r
-#endif\r
-// std::cerr << "c = " << c << std::endl;\r
-\r
-\r
- // B(t) + c(t) * N(t)\r
- double n_inv = 1 / (double)(n);\r
- Point c0ni;\r
- F.push_back(c[1] * F[n-1]);\r
- F[n] += B[n];\r
- for (size_t i = n-1; i > 0; --i)\r
- {\r
- F[i] *= -c[0];\r
- c0ni = F[i];\r
- F[i] += (c[1] * F[i-1]);\r
- F[i] *= (i * n_inv);\r
- F[i] -= c0ni;\r
- F[i] += B[i];\r
- }\r
- F[0] *= c[0];\r
- F[0] += B[0];\r
-}\r
-\r
-/*\r
- * Compute the projection on the plane (t, d) of the control points\r
- * (t, u, D(t,u)) where D(t,u) = <(B(t) - F(u)), B'(t)> with 0 <= t, u <= 1\r
- * B is a Bezier curve and F is a focus of another Bezier curve.\r
- * See Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping.\r
- */\r
-void distance_control_points (std::vector<Point> & D,\r
- std::vector<Point> const& B,\r
- std::vector<Point> const& F)\r
-{\r
- assert (B.size() > 1);\r
- assert (F.size() > 0);\r
- const size_t n = B.size() - 1;\r
- const size_t m = F.size() - 1;\r
- const size_t r = 2 * n - 1;\r
- const double r_inv = 1 / (double)(r);\r
- D.clear();\r
- D.reserve (B.size() * F.size());\r
-\r
- std::vector<Point> dB;\r
- dB.reserve(n);\r
- for (size_t k = 0; k < n; ++k)\r
- {\r
- dB.push_back (B[k+1] - B[k]);\r
- }\r
- NL::Matrix dBB(n,B.size());\r
- for (size_t i = 0; i < n; ++i)\r
- for (size_t j = 0; j < B.size(); ++j)\r
- dBB(i,j) = dot (dB[i], B[j]);\r
- NL::Matrix dBF(n, F.size());\r
- for (size_t i = 0; i < n; ++i)\r
- for (size_t j = 0; j < F.size(); ++j)\r
- dBF(i,j) = dot (dB[i], F[j]);\r
-\r
- size_t k0, kn, l;\r
- double bc, bri;\r
- Point dij;\r
- std::vector<double> d(F.size());\r
- for (size_t i = 0; i <= r; ++i)\r
- {\r
- for (size_t j = 0; j <= m; ++j)\r
- {\r
- d[j] = 0;\r
- }\r
- k0 = std::max(i, n) - n;\r
- kn = std::min(i, n-1);\r
- bri = n / binomial(r,i);\r
- for (size_t k = k0; k <= kn; ++k)\r
- {\r
- //if (k > i || (i-k) > n) continue;\r
- l = i - k;\r
-#if CHECK\r
- assert (l <= n);\r
-#endif\r
- bc = bri * binomial(n,l) * binomial(n-1, k);\r
- for (size_t j = 0; j <= m; ++j)\r
- {\r
- //d[j] += bc * dot(dB[k], B[l] - F[j]);\r
- d[j] += bc * (dBB(k,l) - dBF(k,j));\r
- }\r
- }\r
- double dmin, dmax;\r
- dmin = dmax = d[m];\r
- for (size_t j = 0; j < m; ++j)\r
- {\r
- if (dmin > d[j]) dmin = d[j];\r
- if (dmax < d[j]) dmax = d[j];\r
- }\r
- dij[0] = i * r_inv;\r
- dij[1] = dmin;\r
- D.push_back (dij);\r
- dij[1] = dmax;\r
- D.push_back (dij);\r
- }\r
-}\r
-\r
-/*\r
- * Clip the Bezier curve "B" wrt the focus "F"; the new parameter interval for\r
- * the clipped curve is returned through the output parameter "dom"\r
- */\r
-void clip_interval (Interval& dom,\r
- std::vector<Point> const& B,\r
- std::vector<Point> const& F)\r
-{\r
- std::vector<Point> D; // distance curve control points\r
- distance_control_points(D, B, F);\r
- //print(D, "D");\r
-// ConvexHull chD(D);\r
-// std::vector<Point>& p = chD.boundary; // convex hull vertices\r
-\r
- convex_hull(D);\r
- std::vector<Point> & p = D;\r
- //print(p, "CH(D)");\r
-\r
- bool plower, clower;\r
- double t, tmin = 1, tmax = 0;\r
-\r
- plower = (p[0][Y] < 0);\r
- if (p[0][Y] == 0) // on the x axis\r
- {\r
- if (tmin > p[0][X]) tmin = p[0][X];\r
- if (tmax < p[0][X]) tmax = p[0][X];\r
-// std::cerr << "0 : on x axis " << p[0]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;\r
- }\r
-\r
- for (size_t i = 1; i < p.size(); ++i)\r
- {\r
- clower = (p[i][Y] < 0);\r
- if (p[i][Y] == 0) // on x axis\r
- {\r
- if (tmin > p[i][X]) tmin = p[i][X];\r
- if (tmax < p[i][X]) tmax = p[i][X];\r
-// std::cerr << i << " : on x axis " << p[i]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax\r
-// << std::endl;\r
- }\r
- else if (clower != plower) // cross the x axis\r
- {\r
- t = intersect(p[i-1], p[i], 0);\r
- if (tmin > t) tmin = t;\r
- if (tmax < t) tmax = t;\r
- plower = clower;\r
-// std::cerr << i << " : lower " << p[i]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax\r
-// << std::endl;\r
- }\r
- }\r
-\r
- // we have to test the closing segment for intersection\r
- size_t last = p.size() - 1;\r
- clower = (p[0][Y] < 0);\r
- if (clower != plower) // cross the x axis\r
- {\r
- t = intersect(p[last], p[0], 0);\r
- if (tmin > t) tmin = t;\r
- if (tmax < t) tmax = t;\r
-// std::cerr << "0 : lower " << p[0]\r
-// << " : tmin = " << tmin << ", tmax = " << tmax << std::endl;\r
- }\r
- dom[0] = tmin;\r
- dom[1] = tmax;\r
-}\r
-\r
-/*\r
- * Clip the Bezier curve "B" wrt the Bezier curve "A" for individuating\r
- * points which have collinear normals; the new parameter interval\r
- * for the clipped curve is returned through the output parameter "dom"\r
- */\r
-template <>\r
-inline\r
-void clip<collinear_normal_tag> (Interval & dom,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B)\r
-{\r
- std::vector<Point> F;\r
- make_focus(F, A);\r
- clip_interval(dom, B, F);\r
-}\r
-\r
-\r
-\r
-const double MAX_PRECISION = 1e-8;\r
-const double MIN_CLIPPED_SIZE_THRESHOLD = 0.8;\r
-const Interval UNIT_INTERVAL(0,1);\r
-const Interval EMPTY_INTERVAL = make_empty_interval();\r
-const Interval H1_INTERVAL(0, 0.5);\r
-const Interval H2_INTERVAL(0.5 + MAX_PRECISION, 1.0);\r
-\r
-/*\r
- * iterate\r
- *\r
- * input:\r
- * A, B: control point sets of two bezier curves\r
- * domA, domB: real parameter intervals of the two curves\r
- * precision: required computational precision of the returned parameter ranges\r
- * output:\r
- * domsA, domsB: sets of parameter intervals\r
- *\r
- * The parameter intervals are computed by using a Bezier clipping algorithm,\r
- * in case the clipping doesn't shrink the initial interval more than 20%,\r
- * a subdivision step is performed.\r
- * If during the computation both curves collapse to a single point\r
- * the routine exits indipendently by the precision reached in the computation\r
- * of the curve intervals.\r
- */\r
-template <>\r
-void iterate<intersection_point_tag> (std::vector<Interval>& domsA,\r
- std::vector<Interval>& domsB,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B,\r
- Interval const& domA,\r
- Interval const& domB,\r
- double precision )\r
-{\r
- // in order to limit recursion\r
- static size_t counter = 0;\r
- if (domA.extent() == 1 && domB.extent() == 1) counter = 0;\r
- if (++counter > 100) return;\r
-#if VERBOSE\r
- std::cerr << std::fixed << std::setprecision(16);\r
- std::cerr << ">> curve subdision performed <<" << std::endl;\r
- std::cerr << "dom(A) : " << domA << std::endl;\r
- std::cerr << "dom(B) : " << domB << std::endl;\r
-// std::cerr << "angle(A) : " << angle(A) << std::endl;\r
-// std::cerr << "angle(B) : " << angle(B) << std::endl;\r
-#endif\r
-\r
- if (precision < MAX_PRECISION)\r
- precision = MAX_PRECISION;\r
-\r
- std::vector<Point> pA = A;\r
- std::vector<Point> pB = B;\r
- std::vector<Point>* C1 = &pA;\r
- std::vector<Point>* C2 = &pB;\r
-\r
- Interval dompA = domA;\r
- Interval dompB = domB;\r
- Interval* dom1 = &dompA;\r
- Interval* dom2 = &dompB;\r
-\r
- Interval dom;\r
-\r
- if ( is_constant(A) && is_constant(B) ){\r
- Point M1 = middle_point(C1->front(), C1->back());\r
- Point M2 = middle_point(C2->front(), C2->back());\r
- if (are_near(M1,M2)){\r
- domsA.push_back(domA);\r
- domsB.push_back(domB);\r
- }\r
- return;\r
- }\r
-\r
- size_t iter = 0;\r
- while (++iter < 100\r
- && (dompA.extent() >= precision || dompB.extent() >= precision))\r
- {\r
-#if VERBOSE\r
- std::cerr << "iter: " << iter << std::endl;\r
-#endif\r
- clip<intersection_point_tag>(dom, *C1, *C2);\r
-\r
- // [1,0] is utilized to represent an empty interval\r
- if (dom == EMPTY_INTERVAL)\r
- {\r
-#if VERBOSE\r
- std::cerr << "dom: empty" << std::endl;\r
-#endif\r
- return;\r
- }\r
-#if VERBOSE\r
- std::cerr << "dom : " << dom << std::endl;\r
-#endif\r
- // all other cases where dom[0] > dom[1] are invalid\r
- if (dom.min() > dom.max())\r
- {\r
- assert(dom.min() < dom.max());\r
- }\r
-\r
- map_to(*dom2, dom);\r
-\r
- portion(*C2, dom);\r
- if (is_constant(*C2) && is_constant(*C1))\r
- {\r
- Point M1 = middle_point(C1->front(), C1->back());\r
- Point M2 = middle_point(C2->front(), C2->back());\r
-#if VERBOSE\r
- std::cerr << "both curves are constant: \n"\r
- << "M1: " << M1 << "\n"\r
- << "M2: " << M2 << std::endl;\r
- print(*C2, "C2");\r
- print(*C1, "C1");\r
-#endif\r
- if (are_near(M1,M2))\r
- break; // append the new interval\r
- else\r
- return; // exit without appending any new interval\r
- }\r
-\r
-\r
- // if we have clipped less than 20% than we need to subdive the curve\r
- // with the largest domain into two sub-curves\r
- if ( dom.extent() > MIN_CLIPPED_SIZE_THRESHOLD)\r
- {\r
-#if VERBOSE\r
- std::cerr << "clipped less than 20% : " << dom.extent() << std::endl;\r
- std::cerr << "angle(pA) : " << angle(pA) << std::endl;\r
- std::cerr << "angle(pB) : " << angle(pB) << std::endl;\r
-#endif\r
- std::vector<Point> pC1, pC2;\r
- Interval dompC1, dompC2;\r
- if (dompA.extent() > dompB.extent())\r
- {\r
- pC1 = pC2 = pA;\r
- portion(pC1, H1_INTERVAL);\r
- portion(pC2, H2_INTERVAL);\r
- dompC1 = dompC2 = dompA;\r
- map_to(dompC1, H1_INTERVAL);\r
- map_to(dompC2, H2_INTERVAL);\r
- iterate<intersection_point_tag>(domsA, domsB, pC1, pB,\r
- dompC1, dompB, precision);\r
- iterate<intersection_point_tag>(domsA, domsB, pC2, pB,\r
- dompC2, dompB, precision);\r
- }\r
- else\r
- {\r
- pC1 = pC2 = pB;\r
- portion(pC1, H1_INTERVAL);\r
- portion(pC2, H2_INTERVAL);\r
- dompC1 = dompC2 = dompB;\r
- map_to(dompC1, H1_INTERVAL);\r
- map_to(dompC2, H2_INTERVAL);\r
- iterate<intersection_point_tag>(domsB, domsA, pC1, pA,\r
- dompC1, dompA, precision);\r
- iterate<intersection_point_tag>(domsB, domsA, pC2, pA,\r
- dompC2, dompA, precision);\r
- }\r
- return;\r
- }\r
-\r
- std::swap(C1, C2);\r
- std::swap(dom1, dom2);\r
-#if VERBOSE\r
- std::cerr << "dom(pA) : " << dompA << std::endl;\r
- std::cerr << "dom(pB) : " << dompB << std::endl;\r
-#endif\r
- }\r
- domsA.push_back(dompA);\r
- domsB.push_back(dompB);\r
-}\r
-\r
-\r
-/*\r
- * iterate\r
- *\r
- * input:\r
- * A, B: control point sets of two bezier curves\r
- * domA, domB: real parameter intervals of the two curves\r
- * precision: required computational precision of the returned parameter ranges\r
- * output:\r
- * domsA, domsB: sets of parameter intervals\r
- *\r
- * The parameter intervals are computed by using a Bezier clipping algorithm,\r
- * in case the clipping doesn't shrink the initial interval more than 20%,\r
- * a subdivision step is performed.\r
- * If during the computation one of the two curve interval length becomes less\r
- * than MAX_PRECISION the routine exits indipendently by the precision reached\r
- * in the computation of the other curve interval.\r
- */\r
-template <>\r
-void iterate<collinear_normal_tag> (std::vector<Interval>& domsA,\r
- std::vector<Interval>& domsB,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B,\r
- Interval const& domA,\r
- Interval const& domB,\r
- double precision)\r
-{\r
- // in order to limit recursion\r
- static size_t counter = 0;\r
- if (domA.extent() == 1 && domB.extent() == 1) counter = 0;\r
- if (++counter > 100) return;\r
-#if VERBOSE\r
- std::cerr << std::fixed << std::setprecision(16);\r
- std::cerr << ">> curve subdision performed <<" << std::endl;\r
- std::cerr << "dom(A) : " << domA << std::endl;\r
- std::cerr << "dom(B) : " << domB << std::endl;\r
-// std::cerr << "angle(A) : " << angle(A) << std::endl;\r
-// std::cerr << "angle(B) : " << angle(B) << std::endl;\r
-#endif\r
-\r
- if (precision < MAX_PRECISION)\r
- precision = MAX_PRECISION;\r
-\r
- std::vector<Point> pA = A;\r
- std::vector<Point> pB = B;\r
- std::vector<Point>* C1 = &pA;\r
- std::vector<Point>* C2 = &pB;\r
-\r
- Interval dompA = domA;\r
- Interval dompB = domB;\r
- Interval* dom1 = &dompA;\r
- Interval* dom2 = &dompB;\r
-\r
- Interval dom;\r
-\r
- size_t iter = 0;\r
- while (++iter < 100\r
- && (dompA.extent() >= precision || dompB.extent() >= precision))\r
- {\r
-#if VERBOSE\r
- std::cerr << "iter: " << iter << std::endl;\r
-#endif\r
- clip<collinear_normal_tag>(dom, *C1, *C2);\r
-\r
- // [1,0] is utilized to represent an empty interval\r
- if (dom == EMPTY_INTERVAL)\r
- {\r
-#if VERBOSE\r
- std::cerr << "dom: empty" << std::endl;\r
-#endif\r
- return;\r
- }\r
-#if VERBOSE\r
- std::cerr << "dom : " << dom << std::endl;\r
-#endif\r
- // all other cases where dom[0] > dom[1] are invalid\r
- if (dom.min() > dom.max())\r
- {\r
- assert(dom.min() < dom.max());\r
- }\r
-\r
- map_to(*dom2, dom);\r
-\r
- // it's better to stop before losing computational precision\r
- if (iter > 1 && (dom2->extent() <= MAX_PRECISION))\r
- {\r
-#if VERBOSE\r
- std::cerr << "beyond max precision limit" << std::endl;\r
-#endif\r
- break;\r
- }\r
-\r
- portion(*C2, dom);\r
- if (iter > 1 && is_constant(*C2))\r
- {\r
-#if VERBOSE\r
- std::cerr << "new curve portion pC1 is constant" << std::endl;\r
-#endif\r
- break;\r
- }\r
-\r
-\r
- // if we have clipped less than 20% than we need to subdive the curve\r
- // with the largest domain into two sub-curves\r
- if ( dom.extent() > MIN_CLIPPED_SIZE_THRESHOLD)\r
- {\r
-#if VERBOSE\r
- std::cerr << "clipped less than 20% : " << dom.extent() << std::endl;\r
- std::cerr << "angle(pA) : " << angle(pA) << std::endl;\r
- std::cerr << "angle(pB) : " << angle(pB) << std::endl;\r
-#endif\r
- std::vector<Point> pC1, pC2;\r
- Interval dompC1, dompC2;\r
- if (dompA.extent() > dompB.extent())\r
- {\r
- if ((dompA.extent() / 2) < MAX_PRECISION)\r
- {\r
- break;\r
- }\r
- pC1 = pC2 = pA;\r
- portion(pC1, H1_INTERVAL);\r
- if (false && is_constant(pC1))\r
- {\r
-#if VERBOSE\r
- std::cerr << "new curve portion pC1 is constant" << std::endl;\r
-#endif\r
- break;\r
- }\r
- portion(pC2, H2_INTERVAL);\r
- if (is_constant(pC2))\r
- {\r
-#if VERBOSE\r
- std::cerr << "new curve portion pC2 is constant" << std::endl;\r
-#endif\r
- break;\r
- }\r
- dompC1 = dompC2 = dompA;\r
- map_to(dompC1, H1_INTERVAL);\r
- map_to(dompC2, H2_INTERVAL);\r
- iterate<collinear_normal_tag>(domsA, domsB, pC1, pB,\r
- dompC1, dompB, precision);\r
- iterate<collinear_normal_tag>(domsA, domsB, pC2, pB,\r
- dompC2, dompB, precision);\r
- }\r
- else\r
- {\r
- if ((dompB.extent() / 2) < MAX_PRECISION)\r
- {\r
- break;\r
- }\r
- pC1 = pC2 = pB;\r
- portion(pC1, H1_INTERVAL);\r
- if (is_constant(pC1))\r
- {\r
-#if VERBOSE\r
- std::cerr << "new curve portion pC1 is constant" << std::endl;\r
-#endif\r
- break;\r
- }\r
- portion(pC2, H2_INTERVAL);\r
- if (is_constant(pC2))\r
- {\r
-#if VERBOSE\r
- std::cerr << "new curve portion pC2 is constant" << std::endl;\r
-#endif\r
- break;\r
- }\r
- dompC1 = dompC2 = dompB;\r
- map_to(dompC1, H1_INTERVAL);\r
- map_to(dompC2, H2_INTERVAL);\r
- iterate<collinear_normal_tag>(domsB, domsA, pC1, pA,\r
- dompC1, dompA, precision);\r
- iterate<collinear_normal_tag>(domsB, domsA, pC2, pA,\r
- dompC2, dompA, precision);\r
- }\r
- return;\r
- }\r
-\r
- std::swap(C1, C2);\r
- std::swap(dom1, dom2);\r
-#if VERBOSE\r
- std::cerr << "dom(pA) : " << dompA << std::endl;\r
- std::cerr << "dom(pB) : " << dompB << std::endl;\r
-#endif\r
- }\r
- domsA.push_back(dompA);\r
- domsB.push_back(dompB);\r
-}\r
-\r
-\r
-/*\r
- * get_solutions\r
- *\r
- * input: A, B - set of control points of two Bezier curve\r
- * input: precision - required precision of computation\r
- * input: clip - the routine used for clipping\r
- * output: xs - set of pairs of parameter values\r
- * at which the clipping algorithm converges\r
- *\r
- * This routine is based on the Bezier Clipping Algorithm,\r
- * see: Sederberg - Computer Aided Geometric Design\r
- */\r
-template <typename Tag>\r
-void get_solutions (std::vector< std::pair<double, double> >& xs,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B,\r
- double precision)\r
-{\r
- std::pair<double, double> ci;\r
- std::vector<Interval> domsA, domsB;\r
- iterate<Tag> (domsA, domsB, A, B, UNIT_INTERVAL, UNIT_INTERVAL, precision);\r
- if (domsA.size() != domsB.size())\r
- {\r
- assert (domsA.size() == domsB.size());\r
- }\r
- xs.clear();\r
- xs.reserve(domsA.size());\r
- for (size_t i = 0; i < domsA.size(); ++i)\r
- {\r
-#if VERBOSE\r
- std::cerr << i << " : domA : " << domsA[i] << std::endl;\r
- std::cerr << "extent A: " << domsA[i].extent() << " ";\r
- std::cerr << "precision A: " << get_precision(domsA[i]) << std::endl;\r
- std::cerr << i << " : domB : " << domsB[i] << std::endl;\r
- std::cerr << "extent B: " << domsB[i].extent() << " ";\r
- std::cerr << "precision B: " << get_precision(domsB[i]) << std::endl;\r
-#endif\r
- ci.first = domsA[i].middle();\r
- ci.second = domsB[i].middle();\r
- xs.push_back(ci);\r
- }\r
-}\r
-\r
-} /* end namespace bezier_clipping */ } /* end namespace detail */\r
-\r
-\r
-/*\r
- * find_collinear_normal\r
- *\r
- * input: A, B - set of control points of two Bezier curve\r
- * input: precision - required precision of computation\r
- * output: xs - set of pairs of parameter values\r
- * at which there are collinear normals\r
- *\r
- * This routine is based on the Bezier Clipping Algorithm,\r
- * see: Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping\r
- */\r
-void find_collinear_normal (std::vector< std::pair<double, double> >& xs,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B,\r
- double precision)\r
-{\r
- using detail::bezier_clipping::get_solutions;\r
- using detail::bezier_clipping::collinear_normal_tag;\r
- get_solutions<collinear_normal_tag>(xs, A, B, precision);\r
-}\r
-\r
-\r
-/*\r
- * find_intersections_bezier_clipping\r
- *\r
- * input: A, B - set of control points of two Bezier curve\r
- * input: precision - required precision of computation\r
- * output: xs - set of pairs of parameter values\r
- * at which crossing happens\r
- *\r
- * This routine is based on the Bezier Clipping Algorithm,\r
- * see: Sederberg, Nishita, 1990 - Curve intersection using Bezier clipping\r
- */\r
-void find_intersections_bezier_clipping (std::vector< std::pair<double, double> >& xs,\r
- std::vector<Point> const& A,\r
- std::vector<Point> const& B,\r
- double precision)\r
-{\r
- using detail::bezier_clipping::get_solutions;\r
- using detail::bezier_clipping::intersection_point_tag;\r
- get_solutions<intersection_point_tag>(xs, A, B, precision);\r
-}\r
-\r
-} // end namespace Geom\r
-\r
-\r
-\r
-\r
-/*\r
- Local Variables:\r
- mode:c++\r
- c-file-style:"stroustrup"\r
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
- indent-tabs-mode:nil\r
- fill-column:99\r
- End:\r
-*/\r
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r
+/*
+ * 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/convex-cover.h>
+#include <2geom/numeric/matrix.h>
+
+#include <cassert>
+#include <vector>
+#include <algorithm>
+#include <utility>
+//#include <iomanip>
+
+
+
+
+#define VERBOSE 0
+#define CHECK 0
+
+
+namespace Geom {
+
+namespace detail { namespace bezier_clipping {
+
+////////////////////////////////////////////////////////////////////////////////
+// for debugging
+//
+
+inline
+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 >
+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 = 0.1, p = 10;
+ int n = 0;
+ while (n < 16 && d < e)
+ {
+ p *= 10;
+ e = 1/p;
+ ++n;
+ }
+ return n;
+}
+
+inline
+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);
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// convex hull
+
+/*
+ * return true in case the oriented polyline p0, p1, p2 is a right turn
+ */
+inline
+bool is_a_right_turn (Point const& p0, Point const& p1, Point const& p2)
+{
+ if (p1 == p2) return false;
+ Point q1 = p1 - p0;
+ Point q2 = p2 - p0;
+ if (q1 == -q2) return false;
+ return (cross (q1, q2) < 0);
+}
+
+/*
+ * return true if p < q wrt the lexicographyc order induced by the coordinates
+ */
+struct lex_less
+{
+ bool operator() (Point const& p, Point const& q)
+ {
+ return ((p[X] < q[X]) || (p[X] == q[X] && p[Y] < q[Y]));
+ }
+};
+
+/*
+ * return true if p > q wrt the lexicographyc order induced by the coordinates
+ */
+struct lex_greater
+{
+ bool operator() (Point const& p, Point const& q)
+ {
+ return ((p[X] > q[X]) || (p[X] == q[X] && p[Y] > q[Y]));
+ }
+};
+
+/*
+ * Compute the convex hull of a set of points.
+ * The implementation is based on the Andrew's scan algorithm
+ * note: in the Bezier clipping for collinear normals it seems
+ * to be more stable wrt the Graham's scan algorithm and in general
+ * a bit quikier
+ */
+void convex_hull (std::vector<Point> & P)
+{
+ size_t n = P.size();
+ if (n < 2) return;
+ std::sort(P.begin(), P.end(), lex_less());
+ if (n < 4) return;
+ // upper hull
+ size_t u = 2;
+ for (size_t i = 2; i < n; ++i)
+ {
+ while (u > 1 && !is_a_right_turn(P[u-2], P[u-1], P[i]))
+ {
+ --u;
+ }
+ std::swap(P[u], P[i]);
+ ++u;
+ }
+ std::sort(P.begin() + u, P.end(), lex_greater());
+ std::rotate(P.begin(), P.begin() + 1, P.end());
+ // lower hull
+ size_t l = u;
+ size_t k = u - 1;
+ for (size_t i = l; i < n; ++i)
+ {
+ while (l > k && !is_a_right_turn(P[l-2], P[l-1], P[i]))
+ {
+ --l;
+ }
+ std::swap(P[l], P[i]);
+ ++l;
+ }
+ P.resize(l);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// numerical routines
+
+/*
+ * Compute the binomial coefficient (n, k)
+ */
+inline
+double binomial(unsigned int n, unsigned int k)
+{
+ return choose<double>(n, k);
+}
+
+/*
+ * Compute the determinant of the 2x2 matrix with column the point P1, P2
+ */
+inline
+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
+ */
+inline
+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
+ */
+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;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// bezier curve routines
+
+/*
+ * 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;
+}
+
+/*
+ * Compute the hodograph of the bezier curve B and return it in D
+ */
+inline
+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
+ */
+inline
+void normal(std::vector<Point> & N, std::vector<Point> const& B)
+{
+ derivative(N,B);
+ for (size_t i = 0; i < N.size(); ++i)
+ {
+ N[i] = rot90(N[i]);
+ }
+}
+
+/*
+ * Compute the portion of the Bezier curve "B" wrt the interval [0,t]
+ */
+inline
+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]
+ */
+inline
+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"
+ */
+inline
+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>
+void clip(Interval & dom,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B);
+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.
+ */
+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;
+}
+
+/*
+ * 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.
+ */
+inline
+void orthogonal_orientation_line (std::vector<double> & l,
+ std::vector<Point> const& c,
+ Point const& p)
+{
+ if (is_constant(c))
+ {
+ // this should never happen
+ assert(!is_constant(c));
+ }
+ std::vector<Point> ol(2);
+ ol[0] = p;
+ ol[1] = (c.back() - c.front()).cw() + p;
+ orientation_line(l, ol, 0, 1);
+}
+
+/*
+ * 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 (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);
+
+ convex_hull(D);
+ std::vector<Point> & p = 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;
+ }
+
+ dom[0] = tmin;
+ dom[1] = 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 <>
+inline
+void clip<intersection_point_tag> (Interval & dom,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B)
+{
+ std::vector<double> bl(3);
+ Interval bound;
+ if (is_constant(A))
+ {
+ Point M = middle_point(A.front(), A.back());
+ orthogonal_orientation_line(bl, B, M);
+ }
+ else
+ {
+ pick_orientation_line(bl, A);
+ }
+ fat_line_bounds(bound, A, bl);
+ clip_interval(dom, 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.
+ */
+inline
+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.size() > 0);
+ 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 k0, kn, l;
+ double bc, bri;
+ Point dij;
+ std::vector<double> d(F.size());
+ for (size_t i = 0; i <= r; ++i)
+ {
+ for (size_t j = 0; j <= m; ++j)
+ {
+ d[j] = 0;
+ }
+ k0 = std::max(i, n) - n;
+ kn = std::min(i, n-1);
+ bri = n / binomial(r,i);
+ for (size_t k = k0; k <= kn; ++k)
+ {
+ //if (k > i || (i-k) > n) continue;
+ l = i - k;
+#if CHECK
+ assert (l <= n);
+#endif
+ bc = bri * binomial(n,l) * binomial(n-1, k);
+ for (size_t j = 0; j <= m; ++j)
+ {
+ //d[j] += bc * dot(dB[k], B[l] - F[j]);
+ d[j] += bc * (dBB(k,l) - dBF(k,j));
+ }
+ }
+ double dmin, dmax;
+ dmin = dmax = d[m];
+ for (size_t j = 0; j < m; ++j)
+ {
+ if (dmin > d[j]) dmin = d[j];
+ if (dmax < d[j]) dmax = d[j];
+ }
+ dij[0] = i * r_inv;
+ dij[1] = dmin;
+ D.push_back (dij);
+ dij[1] = dmax;
+ D.push_back (dij);
+ }
+}
+
+/*
+ * Clip the Bezier curve "B" wrt the focus "F"; the new parameter interval for
+ * the clipped curve is returned through the output parameter "dom"
+ */
+void clip_interval (Interval& dom,
+ 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
+
+ convex_hull(D);
+ std::vector<Point> & p = 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;
+ }
+ dom[0] = tmin;
+ dom[1] = 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 <>
+inline
+void clip<collinear_normal_tag> (Interval & dom,
+ std::vector<Point> const& A,
+ std::vector<Point> const& B)
+{
+ std::vector<Point> F;
+ make_focus(F, A);
+ clip_interval(dom, B, F);
+}
+
+
+
+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);
+
+/*
+ * 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 indipendently 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;
+
+ Interval dom;
+
+ if ( is_constant(A) && is_constant(B) ){
+ 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
+ clip<intersection_point_tag>(dom, *C1, *C2);
+
+ // [1,0] is utilized to represent an empty interval
+ if (dom == EMPTY_INTERVAL)
+ {
+#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
+ if (dom.min() > dom.max())
+ {
+ assert(dom.min() < dom.max());
+ }
+
+ map_to(*dom2, dom);
+
+ portion(*C2, dom);
+ if (is_constant(*C2) && is_constant(*C1))
+ {
+ 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;
+ }
+
+ std::swap(C1, C2);
+ std::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 indipendently 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;
+
+ Interval dom;
+
+ size_t iter = 0;
+ while (++iter < 100
+ && (dompA.extent() >= precision || dompB.extent() >= precision))
+ {
+#if VERBOSE
+ std::cerr << "iter: " << iter << std::endl;
+#endif
+ clip<collinear_normal_tag>(dom, *C1, *C2);
+
+ // [1,0] is utilized to represent an empty interval
+ if (dom == EMPTY_INTERVAL)
+ {
+#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
+ if (dom.min() > dom.max())
+ {
+ 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))
+ {
+#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))
+ {
+#if VERBOSE
+ std::cerr << "new curve portion pC1 is constant" << std::endl;
+#endif
+ break;
+ }
+ portion(pC2, H2_INTERVAL);
+ if (is_constant(pC2))
+ {
+#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))
+ {
+#if VERBOSE
+ std::cerr << "new curve portion pC1 is constant" << std::endl;
+#endif
+ break;
+ }
+ portion(pC2, H2_INTERVAL);
+ if (is_constant(pC2))
+ {
+#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;
+ }
+
+ std::swap(C1, C2);
+ std::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:encoding=utf-8:textwidth=99 :
index 4ba3e2325a3b2c1b362da8059e1fe88e4f3c1e03..447c5183f920aeab0913118741f7b20b5dc84d77 100644 (file)
--- a/src/2geom/chebyshev.cpp
+++ b/src/2geom/chebyshev.cpp
-#include <2geom/chebyshev.h>\r
-\r
-#include <2geom/sbasis.h>\r
-#include <2geom/sbasis-poly.h>\r
-\r
-#include <vector>\r
-using std::vector;\r
-\r
-#include <gsl/gsl_math.h>\r
-#include <gsl/gsl_chebyshev.h>\r
-\r
-namespace Geom{\r
-\r
-SBasis cheb(unsigned n) {\r
- static std::vector<SBasis> basis;\r
- if(basis.empty()) {\r
- basis.push_back(Linear(1,1));\r
- basis.push_back(Linear(0,1));\r
- }\r
- for(unsigned i = basis.size(); i <= n; i++) {\r
- basis.push_back(Linear(0,2)*basis[i-1] - basis[i-2]);\r
- }\r
- \r
- return basis[n];\r
-}\r
-\r
-SBasis cheb_series(unsigned n, double* cheb_coeff) {\r
- SBasis r;\r
- for(unsigned i = 0; i < n; i++) {\r
- double cof = cheb_coeff[i];\r
- //if(i == 0)\r
- //cof /= 2;\r
- r += cheb(i)*cof;\r
- }\r
- \r
- return r;\r
-}\r
-\r
-SBasis clenshaw_series(unsigned m, double* cheb_coeff) {\r
- /** b_n = a_n\r
- b_n-1 = 2*x*b_n + a_n-1\r
- b_n-k = 2*x*b_{n-k+1} + a_{n-k} - b_{n - k + 2}\r
- b_0 = x*b_1 + a_0 - b_2\r
- */\r
- \r
- double a = -1, b = 1;\r
- SBasis d, dd;\r
- SBasis y = (Linear(0, 2) - (a+b)) / (b-a);\r
- SBasis y2 = 2*y;\r
- for(int j = m - 1; j >= 1; j--) {\r
- SBasis sv = d;\r
- d = y2*d - dd + cheb_coeff[j];\r
- dd = sv;\r
- }\r
- \r
- return y*d - dd + 0.5*cheb_coeff[0];\r
-}\r
-\r
-SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p) {\r
- gsl_cheb_series *cs = gsl_cheb_alloc (order+2);\r
-\r
- gsl_function F;\r
-\r
- F.function = f;\r
- F.params = p;\r
-\r
- gsl_cheb_init (cs, &F, in[0], in[1]);\r
- \r
- SBasis r = compose(clenshaw_series(order, cs->c), Linear(-1,1));\r
- \r
- gsl_cheb_free (cs);\r
- return r;\r
-}\r
-\r
-struct wrap {\r
- double (*f)(double,void*);\r
- void* pp;\r
- double fa, fb;\r
- Interval in;\r
-};\r
-\r
-double f_interp(double x, void* p) {\r
- struct wrap *wr = (struct wrap *)p;\r
- double z = (x - wr->in[0]) / (wr->in[1] - wr->in[0]);\r
- return (wr->f)(x, wr->pp) - ((1 - z)*wr->fa + z*wr->fb);\r
-}\r
-\r
-SBasis chebyshev_approximant_interpolating (double (*f)(double,void*), \r
- int order, Interval in, void* p) {\r
- double fa = f(in[0], p);\r
- double fb = f(in[1], p);\r
- struct wrap wr;\r
- wr.fa = fa;\r
- wr.fb = fb;\r
- wr.in = in;\r
- printf("%f %f\n", fa, fb);\r
- wr.f = f;\r
- wr.pp = p;\r
- return compose(Linear(in[0], in[1]), Linear(fa, fb)) + chebyshev_approximant(f_interp, order, in, &wr) + Linear(fa, fb);\r
-}\r
-\r
-SBasis chebyshev(unsigned n) {\r
- static std::vector<SBasis> basis;\r
- if(basis.empty()) {\r
- basis.push_back(Linear(1,1));\r
- basis.push_back(Linear(0,1));\r
- }\r
- for(unsigned i = basis.size(); i <= n; i++) {\r
- basis.push_back(Linear(0,2)*basis[i-1] - basis[i-2]);\r
- }\r
- \r
- return basis[n];\r
-}\r
-\r
-};\r
-\r
-/*\r
- Local Variables:\r
- mode:c++\r
- c-file-style:"stroustrup"\r
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
- indent-tabs-mode:nil\r
- fill-column:99\r
- End:\r
-*/\r
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/chebyshev.h b/src/2geom/chebyshev.h
index 309e09b3e788533c187d8cea4f436540f493e02e..6de9e9cc0ed1f742e3182ee2dd3939ec56f60c9f 100644 (file)
--- a/src/2geom/chebyshev.h
+++ b/src/2geom/chebyshev.h
-#ifndef _CHEBYSHEV\r
-#define _CHEBYSHEV\r
-\r
-#include <2geom/sbasis.h>\r
-#include <2geom/interval.h>\r
-\r
-/*** Conversion between Chebyshev approximation and SBasis.\r
- * \r
- */\r
-\r
-namespace Geom{\r
-\r
-SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p=0);\r
-SBasis chebyshev_approximant_interpolating (double (*f)(double,void*), int order, Interval in, void* p=0);\r
-SBasis chebyshev(unsigned n);\r
-\r
-};\r
-\r
-/*\r
- Local Variables:\r
- mode:c++\r
- c-file-style:"stroustrup"\r
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
- indent-tabs-mode:nil\r
- fill-column:99\r
- End:\r
-*/\r
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r
-\r
-#endif\r
+#ifndef _CHEBYSHEV
+#define _CHEBYSHEV
+
+#include <2geom/sbasis.h>
+#include <2geom/interval.h>
+
+/*** Conversion between Chebyshev approximation and SBasis.
+ *
+ */
+
+namespace Geom{
+
+SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p=0);
+SBasis chebyshev_approximant_interpolating (double (*f)(double,void*), int order, Interval in, void* p=0);
+SBasis chebyshev(unsigned n);
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+
+#endif
diff --git a/src/2geom/utils.cpp b/src/2geom/utils.cpp
index f0f42ce0179e8ac33ed7a5e085207e0298e394e3..579718553503dd13f0509e839468dff1cadbe44f 100644 (file)
--- a/src/2geom/utils.cpp
+++ b/src/2geom/utils.cpp
-/** Various utility functions.\r
- *\r
- * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com>\r
- * Copyright 2007 Johan Engelen <goejendaagh@zonnet.nl>\r
- * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>\r
- *\r
- * This library is free software; you can redistribute it and/or\r
- * modify it either under the terms of the GNU Lesser General Public\r
- * License version 2.1 as published by the Free Software Foundation\r
- * (the "LGPL") or, at your option, under the terms of the Mozilla\r
- * Public License Version 1.1 (the "MPL"). If you do not alter this\r
- * notice, a recipient may use your version of this file under either\r
- * the MPL or the LGPL.\r
- *\r
- * You should have received a copy of the LGPL along with this library\r
- * in the file COPYING-LGPL-2.1; if not, write to the Free Software\r
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
- * You should have received a copy of the MPL along with this library\r
- * in the file COPYING-MPL-1.1\r
- *\r
- * The contents of this file are subject to the Mozilla Public License\r
- * Version 1.1 (the "License"); you may not use this file except in\r
- * compliance with the License. You may obtain a copy of the License at\r
- * http://www.mozilla.org/MPL/\r
- *\r
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY\r
- * OF ANY KIND, either express or implied. See the LGPL or the MPL for\r
- * the specific language governing rights and limitations.\r
- *\r
- */\r
-\r
-\r
-#include <2geom/utils.h>\r
-\r
-\r
-namespace Geom \r
-{\r
-\r
-// return a vector that contains all the binomial coefficients of degree n \r
-void binomial_coefficients(std::vector<size_t>& bc, size_t n)\r
-{\r
- size_t s = n+1;\r
- bc.clear();\r
- bc.resize(s);\r
- bc[0] = 1;\r
- size_t k;\r
- for (size_t i = 1; i < n; ++i)\r
- {\r
- k = i >> 1;\r
- if (i & 1u)\r
- {\r
- bc[k+1] = bc[k] << 1;\r
- }\r
- for (size_t j = k; j > 0; --j)\r
- {\r
- bc[j] += bc[j-1];\r
- }\r
- }\r
- s >>= 1;\r
- for (size_t i = 0; i < s; ++i)\r
- {\r
- bc[n-i] = bc[i];\r
- }\r
-}\r
-\r
-} // end namespace Geom\r
-\r
-\r
-\r
-\r
-\r
-\r
-\r
-\r
-\r
-\r
-\r
-/*\r
- Local Variables:\r
- mode:c++\r
- c-file-style:"stroustrup"\r
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
- indent-tabs-mode:nil\r
- fill-column:99\r
- End:\r
-*/\r
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r
+/** 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, size_t n)
+{
+ size_t s = n+1;
+ bc.clear();
+ bc.resize(s);
+ bc[0] = 1;
+ size_t k;
+ for (size_t i = 1; i < n; ++i)
+ {
+ 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:encoding=utf-8:textwidth=99 :
index fd28011c353c553e93f89abc3dc830b10046b51a..0a1108b1ebbafb5eeaa70067ac7cee061fbe49ab 100644 (file)
-/*\r
- * A simple utility for exporting Inkscape svg Shapes as JavaFX paths.\r
- *\r
- * For information on the JavaFX file format, see:\r
- * https://openjfx.dev.java.net/\r
- *\r
- * Authors:\r
- * Bob Jamison <ishmal@inkscape.org>\r
- * Silveira Neto <silveiraneto@gmail.com>\r
- * Jim Clarke <Jim.Clarke@sun.com>\r
- *\r
- * Copyright (C) 2008 Authors\r
- *\r
- * Released under GNU GPL, read the file 'COPYING' for more information\r
- */\r
-\r
-\r
-#ifdef HAVE_CONFIG_H\r
-# include <config.h>\r
-#endif\r
-#include "javafx-out.h"\r
-#include <inkscape.h>\r
-#include <inkscape-version.h>\r
-#include <sp-path.h>\r
-#include <sp-linear-gradient.h>\r
-#include <sp-radial-gradient.h>\r
-#include <style.h>\r
-#include <display/curve.h>\r
-#include <display/canvas-bpath.h>\r
-#include <svg/svg.h>\r
-#include <extension/system.h>\r
-#include <2geom/pathvector.h>\r
-#include <2geom/rect.h>\r
-#include <2geom/bezier-curve.h>\r
-#include <2geom/hvlinesegment.h>\r
-#include "helper/geom.h"\r
-#include "helper/geom-curves.h"\r
-#include <io/sys.h>\r
-\r
-\r
-#include <string>\r
-#include <stdio.h>\r
-#include <stdarg.h>\r
-\r
-\r
-namespace Inkscape\r
-{\r
-namespace Extension\r
-{\r
-namespace Internal\r
-{\r
-\r
-\r
-\r
-\r
-//########################################################################\r
-//# M E S S A G E S\r
-//########################################################################\r
-\r
-static void err(const char *fmt, ...)\r
-{\r
- va_list args;\r
- g_log(NULL, G_LOG_LEVEL_WARNING, "javafx-out err: ");\r
- va_start(args, fmt);\r
- g_logv(NULL, G_LOG_LEVEL_WARNING, fmt, args);\r
- va_end(args);\r
- g_log(NULL, G_LOG_LEVEL_WARNING, "\n");\r
-}\r
-\r
-\r
-//########################################################################\r
-//# U T I L I T Y\r
-//########################################################################\r
-\r
-/**\r
- * Got this method from Bulia, and modified it a bit. It basically\r
- * starts with this style, gets its SPObject parent, walks up the object\r
- * tree and finds all of the opacities and multiplies them.\r
- *\r
- * We use this for our "flat" object output. If the code is modified\r
- * to reflect a tree of <groups>, then this will be unneccessary.\r
- */\r
-static double effective_opacity(const SPStyle *style)\r
-{\r
- double val = 1.0;\r
- for (SPObject const *obj = style->object; obj ; obj = obj->parent)\r
- {\r
- style = SP_OBJECT_STYLE(obj);\r
- if (style)\r
- val *= SP_SCALE24_TO_FLOAT(style->opacity.value);\r
- }\r
- return val;\r
-}\r
-\r
-//########################################################################\r
-//# OUTPUT FORMATTING\r
-//########################################################################\r
-\r
-\r
-/**\r
- * We want to control floating output format.\r
- * Especially to avoid localization. (decimal ',' for example)\r
- */\r
-static JavaFXOutput::String dstr(double d)\r
-{\r
- char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1];\r
- g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE,\r
- "%.8f", (gdouble)d);\r
- JavaFXOutput::String s = dbuf;\r
- return s;\r
-}\r
-\r
-#define DSTR(d) (dstr(d).c_str())\r
-\r
-\r
-/**\r
- * Format a double as an integer\r
- */\r
-static JavaFXOutput::String istr(double d)\r
-{\r
- char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1];\r
- g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE,\r
- "%.0f", (gdouble)d);\r
- JavaFXOutput::String s = dbuf;\r
- return s;\r
-}\r
-\r
-#define ISTR(d) (istr(d).c_str())\r
-\r
-\r
-/**\r
- * Format an rgba() string\r
- */\r
-static JavaFXOutput::String rgba(guint32 rgba)\r
-{\r
- unsigned int r = SP_RGBA32_R_U(rgba);\r
- unsigned int g = SP_RGBA32_G_U(rgba);\r
- unsigned int b = SP_RGBA32_B_U(rgba);\r
- unsigned int a = SP_RGBA32_A_U(rgba);\r
- char buf[80];\r
- snprintf(buf, 79, "Color.rgb(0x%02x, 0x%02x, 0x%02x, %s)",\r
- r, g, b, DSTR((double)a/256.0));\r
- JavaFXOutput::String s = buf;\r
- return s;\r
-}\r
-\r
-\r
-/**\r
- * Format an rgba() string for a color and a 0.0-1.0 alpha\r
- */\r
-static JavaFXOutput::String rgba(SPColor color, gdouble alpha)\r
-{\r
- return rgba(color.toRGBA32(alpha));\r
-}\r
-\r
-/**\r
- * Map Inkscape linecap styles to JavaFX\r
- */\r
-static JavaFXOutput::String getStrokeLineCap(unsigned value) {\r
- switch(value) {\r
- case SP_STROKE_LINECAP_BUTT:\r
- return "StrokeLineCap.BUTT";\r
- case SP_STROKE_LINECAP_ROUND:\r
- return "StrokeLineCap.ROUND";\r
- case SP_STROKE_LINECAP_SQUARE:\r
- return "StrokeLineCap.SQUARE";\r
- default:\r
- return "INVALID LINE CAP";\r
- }\r
-}\r
-\r
-\r
-/**\r
- * Map Inkscape linejoin styles to JavaFX\r
- */\r
-static JavaFXOutput::String getStrokeLineJoin(unsigned value) {\r
- switch(value) {\r
- case SP_STROKE_LINEJOIN_MITER:\r
- return "StrokeLineJoin.MITER";\r
- case SP_STROKE_LINEJOIN_ROUND:\r
- return "StrokeLineJoin.ROUND";\r
- case SP_STROKE_LINEJOIN_BEVEL:\r
- return "StrokeLineJoin.BEVEL";\r
- default:\r
- return "INVALID LINE JOIN";\r
- }\r
-}\r
-\r
-\r
-/**\r
- * Replace illegal characters for JavaFX for a underscore.\r
- */\r
-static JavaFXOutput::String sanatize(const JavaFXOutput::String &badstr){\r
- JavaFXOutput::String good(badstr);\r
- for (int pos = 0; pos < badstr.length(); ++pos )\r
- if((badstr.at(pos)=='-')||(badstr.at(pos)==' '))\r
- good.replace(pos, 1, "_");\r
- return good;\r
-}\r
-\r
-/**\r
- * Output data to the buffer, printf()-style\r
- */\r
-void JavaFXOutput::out(const char *fmt, ...)\r
-{\r
- va_list args;\r
- va_start(args, fmt);\r
- gchar *output = g_strdup_vprintf(fmt, args);\r
- va_end(args);\r
- outbuf.append(output);\r
- g_free(output);\r
-}\r
-\r
-\r
-\r
-/**\r
- * Output the file header\r
- */\r
-bool JavaFXOutput::doHeader()\r
-{\r
- time_t tim = time(NULL);\r
- out("/*###################################################################\n");\r
- out("### This JavaFX document was generated by Inkscape\n");\r
- out("### http://www.inkscape.org\n");\r
- out("### Created: %s", ctime(&tim));\r
- out("### Version: %s\n", Inkscape::version_string);\r
- out("#####################################################################\n");\r
- out("### NOTES:\n");\r
- out("### ============\n");\r
- out("### JavaFX information can be found at\n");\r
- out("### hhttps://openjfx.dev.java.net\n");\r
- out("###\n");\r
- out("### If you have any problems with this output, please see the\n");\r
- out("### Inkscape project at http://www.inkscape.org, or visit\n");\r
- out("### the #inkscape channel on irc.freenode.net . \n");\r
- out("###\n");\r
- out("###################################################################*/\n");\r
- out("\n\n");\r
- out("/*###################################################################\n");\r
- out("## Exports in this file\n");\r
- out("##==========================\n");\r
- out("## Shapes : %d\n", nrShapes);\r
- out("## Nodes : %d\n", nrNodes);\r
- out("###################################################################*/\n");\r
- out("\n\n");\r
-\r
- // import javafx libraries we can need\r
- out("import javafx.application.*;\n");\r
- out("import javafx.scene.*;\n");\r
- out("import javafx.scene.geometry.*;\n");\r
- out("import javafx.scene.transform.*;\n");\r
- out("import javafx.scene.paint.*;\n");\r
- out("\n");\r
-\r
- out("\n\n");\r
-\r
- // Creates a class extended from CustomNode\r
- out("public class %s extends CustomNode {\n", name.c_str());\r
-\r
- return true;\r
-}\r
-\r
-\r
-\r
-/**\r
- * Output the file footer\r
- */\r
-bool JavaFXOutput::doTail()\r
-{\r
- float border = 25.0;\r
-\r
- // Write the tail of CustomNode\r
- out(" ] // content\n");\r
- out(" transform: Translate { x : %s, y : %s }\n",\r
- DSTR((-minx) + border), DSTR((-miny) + border) );\r
- out(" } // Group\n");\r
- out(" } // function create()\n");\r
- out("} // class %s\n", name.c_str());\r
- out("\n");\r
-\r
- // Frame\r
- out("Frame {\n");\r
- out(" title: \"%s\"\n", name.c_str());\r
- out(" width: %s\n", ISTR(maxx-minx + border * 2.0));\r
- out(" height: %s\n", ISTR(maxy-miny + border * 2.0));\r
- out(" visible: true\n");\r
-\r
- // Stage\r
- out(" stage: Stage {\n");\r
- out(" content: %s{}\n", name.c_str());\r
- out(" } // Stage\n");\r
-\r
- out("} // Frame\n");\r
-\r
- out("\n");\r
-\r
- out("/*###################################################################\n");\r
- out("### E N D C L A S S %s\n", name.c_str());\r
- out("###################################################################*/\n");\r
- out("\n\n");\r
- return true;\r
-}\r
-\r
-\r
-\r
-/**\r
- * Output gradient information to the buffer\r
- */\r
-bool JavaFXOutput::doGradient(SPGradient *grad, const String &id)\r
-{\r
- String jfxid = sanatize(id);\r
-\r
- if (SP_IS_LINEARGRADIENT(grad))\r
- {\r
- SPLinearGradient *g = SP_LINEARGRADIENT(grad);\r
- out(" /* create LinearGradient for %s */\n", jfxid.c_str());\r
- out(" private function %s(): LinearGradient {\n", jfxid.c_str());\r
- out(" LinearGradient {\n");\r
- std::vector<SPGradientStop> stops = g->vector.stops;\r
- if (stops.size() > 0)\r
- {\r
- out(" stops:\n");\r
- out(" [\n");\r
- for (unsigned int i = 0 ; i<stops.size() ; i++)\r
- {\r
- SPGradientStop stop = stops[i];\r
- out(" Stop {\n");\r
- out(" offset: %s\n", DSTR(stop.offset));\r
- out(" color: %s\n", rgba(stop.color, stop.opacity).c_str());\r
- out(" },\n");\r
- }\r
- out(" ]\n");\r
- }\r
- out(" };\n");\r
- out(" } // end LinearGradient: %s\n", jfxid.c_str());\r
- out("\n\n");\r
- }\r
- else if (SP_IS_RADIALGRADIENT(grad))\r
- {\r
- SPRadialGradient *g = SP_RADIALGRADIENT(grad);\r
- out(" /* create RadialGradient for %s */\n", jfxid.c_str());\r
- out(" private function %s() {\n", jfxid.c_str());\r
- out(" RadialGradient {\n");\r
- out(" centerX: %s\n", DSTR(g->cx.value));\r
- out(" centerY: %s\n", DSTR(g->cy.value));\r
- out(" focusX: %s\n", DSTR(g->fx.value));\r
- out(" focusY: %s\n", DSTR(g->fy.value));\r
- out(" radius: %s\n", DSTR(g->r.value ));\r
- std::vector<SPGradientStop> stops = g->vector.stops;\r
- if (stops.size() > 0)\r
- {\r
- out(" stops:\n");\r
- out(" [\n");\r
- for (unsigned int i = 0 ; i<stops.size() ; i++)\r
- {\r
- SPGradientStop stop = stops[i];\r
- out(" Stop {\n");\r
- out(" offset: %s\n", DSTR(stop.offset));\r
- out(" color: %s\n", rgba(stop.color, stop.opacity).c_str());\r
- out(" },\n");\r
- }\r
- out(" ]\n");\r
- }\r
- out(" };\n");\r
- out(" } // end RadialGradient: %s\n", jfxid.c_str());\r
- out("\n\n");\r
- }\r
- else\r
- {\r
- err("Unknown gradient type for '%s'\n", jfxid.c_str());\r
- return false;\r
- }\r
-\r
-\r
- return true;\r
-}\r
-\r
-\r
-\r
-\r
-/**\r
- * Output an element's style attribute\r
- */\r
-bool JavaFXOutput::doStyle(SPStyle *style)\r
-{\r
- if (!style)\r
- return true;\r
-\r
- out(" opacity: %s\n", DSTR(effective_opacity(style)));\r
-\r
- /**\r
- * Fill\r
- */\r
- SPIPaint fill = style->fill;\r
- if (fill.isColor())\r
- {\r
- // see color.h for how to parse SPColor\r
- out(" fill: %s\n",\r
- rgba(fill.value.color, SP_SCALE24_TO_FLOAT(style->fill_opacity.value)).c_str());\r
- }\r
- else if (fill.isPaintserver()){\r
- if (fill.value.href && fill.value.href->getURI() ){\r
- String uri = fill.value.href->getURI()->toString();\r
- /* trim the anchor '#' from the front */\r
- if (uri.size() > 0 && uri[0]=='#')\r
- uri = uri.substr(1);\r
- out(" fill: %s()\n", sanatize(uri).c_str());\r
- }\r
- }\r
-\r
-\r
- /**\r
- * Stroke\r
- */\r
- /**\r
- *NOTE: Things in style we can use:\r
- * SPIPaint stroke;\r
- * SPILength stroke_width;\r
- * SPIEnum stroke_linecap;\r
- * SPIEnum stroke_linejoin;\r
- * SPIFloat stroke_miterlimit;\r
- * NRVpathDash stroke_dash;\r
- * unsigned stroke_dasharray_set : 1;\r
- * unsigned stroke_dasharray_inherit : 1;\r
- * unsigned stroke_dashoffset_set : 1;\r
- * SPIScale24 stroke_opacity;\r
- */\r
- if (style->stroke_opacity.value > 0)\r
- {\r
- SPIPaint stroke = style->stroke;\r
- out(" stroke: %s\n",\r
- rgba(stroke.value.color, SP_SCALE24_TO_FLOAT(style->stroke_opacity.value)).c_str());\r
- double strokewidth = style->stroke_width.value;\r
- unsigned linecap = style->stroke_linecap.value;\r
- unsigned linejoin = style->stroke_linejoin.value;\r
- out(" strokeWidth: %s\n", DSTR(strokewidth));\r
- out(" strokeLineCap: %s\n", getStrokeLineCap(linecap).c_str());\r
- out(" strokeLineJoin: %s\n", getStrokeLineJoin(linejoin).c_str());\r
- out(" strokeMiterLimit: %s\n", DSTR(style->stroke_miterlimit.value));\r
- if(style->stroke_dasharray_set) {\r
- if(style->stroke_dashoffset_set) {\r
- out(" strokeDashOffset: %s\n", DSTR(style->stroke_dash.offset));\r
- }\r
- out(" strokeDashArray: [ ");\r
- for(int i = 0; i < style->stroke_dash.n_dash; i++ ) {\r
- if(i > 0) {\r
- out(", %.2lf", style->stroke_dash.dash[i]);\r
- }else {\r
- out(" %.2lf", style->stroke_dash.dash[i]);\r
- }\r
- }\r
- out(" ]\n");\r
- }\r
-\r
- }\r
-\r
- return true;\r
-}\r
-\r
-\r
-#if 1\r
-\r
-/**\r
- * Output the curve data to buffer\r
- */\r
-bool JavaFXOutput::doCurve(SPItem *item, const String &id)\r
-{\r
- using Geom::X;\r
- using Geom::Y;\r
-\r
- String jfxid = sanatize(id);\r
-\r
- //### Get the Shape\r
- if (!SP_IS_SHAPE(item))//Bulia's suggestion. Allow all shapes\r
- return true;\r
-\r
- SPShape *shape = SP_SHAPE(item);\r
- SPCurve *curve = shape->curve;\r
- if (curve->is_empty())\r
- return true;\r
-\r
- nrShapes++;\r
-\r
- out(" /** path %s */\n", jfxid.c_str());\r
- out(" private function %s() : Path {\n",jfxid.c_str());\r
- out(" Path {\n");\r
- out(" id: \"%s\"\n", jfxid.c_str());\r
-\r
- /**\r
- * Output the style information\r
- */\r
- if (!doStyle(SP_OBJECT_STYLE(shape)))\r
- return false;\r
-\r
- // convert the path to only lineto's and cubic curveto's:\r
- Geom::Scale yflip(1.0, -1.0);\r
- Geom::Matrix tf = sp_item_i2d_affine(item) * yflip;\r
- Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf );\r
-\r
- //Count the NR_CURVETOs/LINETOs (including closing line segment)\r
- guint segmentCount = 0;\r
- for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) {\r
- segmentCount += (*it).size();\r
- if (it->closed())\r
- segmentCount += 1;\r
- }\r
-\r
- out(" elements: [\n");\r
-\r
- unsigned int segmentNr = 0;\r
-\r
- nrNodes += segmentCount;\r
-\r
- Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() );\r
-\r
- /**\r
- * For all Subpaths in the <path>\r
- */\r
- for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit)\r
- {\r
- Geom::Point p = pit->front().initialPoint();\r
- cminmax.expandTo(p);\r
- out(" MoveTo {\n");\r
- out(" x: %s\n", DSTR(p[X]));\r
- out(" y: %s\n", DSTR(p[Y]));\r
- out(" },\n");\r
-\r
- /**\r
- * For all segments in the subpath\r
- */\r
- for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit)\r
- {\r
- //### LINE\r
- if( dynamic_cast<Geom::LineSegment const *> (&*cit) ||\r
- dynamic_cast<Geom::HLineSegment const *> (&*cit) ||\r
- dynamic_cast<Geom::VLineSegment const *> (&*cit) )\r
- {\r
- Geom::Point p = cit->finalPoint();\r
- out(" LineTo {\n");\r
- out(" x: %s\n", DSTR(p[X]));\r
- out(" y: %s\n", DSTR(p[Y]));\r
- out(" },\n");\r
- nrNodes++;\r
- }\r
- //### BEZIER\r
- else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit))\r
- {\r
- std::vector<Geom::Point> points = cubic->points();\r
- Geom::Point p1 = points[1];\r
- Geom::Point p2 = points[2];\r
- Geom::Point p3 = points[3];\r
- out(" CurveTo {\n");\r
- out(" controlX1: %s\n", DSTR(p1[X]));\r
- out(" controlY1: %s\n", DSTR(p1[Y]));\r
- out(" controlX2: %s\n", DSTR(p2[X]));\r
- out(" controlY2: %s\n", DSTR(p2[Y]));\r
- out(" x: %s\n", DSTR(p3[X]));\r
- out(" y: %s\n", DSTR(p3[Y]));\r
- out(" },\n");\r
- nrNodes++;\r
- }\r
- else\r
- {\r
- g_error ("logical error, because pathv_to_linear_and_cubic_beziers was used");\r
- }\r
- segmentNr++;\r
- cminmax.expandTo(cit->finalPoint());\r
- }\r
- if (pit->closed())\r
- {\r
- out(" ClosePath {},\n");\r
- }\r
- }\r
-\r
- out(" ] // elements\n");\r
- out(" }; // Path\n");\r
- out(" } // end path %s\n\n", jfxid.c_str());\r
-\r
- double cminx = cminmax.min()[X];\r
- double cmaxx = cminmax.max()[X];\r
- double cminy = cminmax.min()[Y];\r
- double cmaxy = cminmax.max()[Y];\r
-\r
- if (cminx < minx)\r
- minx = cminx;\r
- if (cmaxx > maxx)\r
- maxx = cmaxx;\r
- if (cminy < miny)\r
- miny = cminy;\r
- if (cmaxy > maxy)\r
- maxy = cmaxy;\r
-\r
- return true;\r
-}\r
-\r
-\r
-\r
-#else\r
-\r
-/**\r
- * Output the curve data to buffer\r
- */\r
-bool JavaFXOutput::doCurve(SPItem *item, const String &id)\r
-{\r
- using Geom::X;\r
- using Geom::Y;\r
-\r
- //### Get the Shape\r
- if (!SP_IS_SHAPE(item))//Bulia's suggestion. Allow all shapes\r
- return true;\r
-\r
- SPShape *shape = SP_SHAPE(item);\r
- SPCurve *curve = shape->curve;\r
- if (curve->is_empty())\r
- return true;\r
-\r
- nrShapes++;\r
-\r
- out(" SVGPath \n");\r
- out(" {\n");\r
- out(" id: \"%s\"\n", id.c_str());\r
-\r
- /**\r
- * Output the style information\r
- */\r
- if (!doStyle(SP_OBJECT_STYLE(shape)))\r
- return false;\r
-\r
- // convert the path to only lineto's and cubic curveto's:\r
- Geom::Scale yflip(1.0, -1.0);\r
- Geom::Matrix tf = sp_item_i2d_affine(item) * yflip;\r
- Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf );\r
- \r
- //Count the NR_CURVETOs/LINETOs (including closing line segment)\r
- nrNodes = 0;\r
- for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) {\r
- nrNodes += (*it).size();\r
- if (it->closed())\r
- nrNodes += 1;\r
- }\r
-\r
- char *dataStr = sp_svg_write_path(pathv);\r
- out(" content: \"%s\"\n", dataStr);\r
- free(dataStr);\r
-\r
- Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() ); \r
-\r
- /**\r
- * Get the Min and Max X and Y extends for the Path. \r
- * ....For all Subpaths in the <path>\r
- */ \r
- for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit)\r
- {\r
- cminmax.expandTo(pit->front().initialPoint());\r
- /**\r
- * For all segments in the subpath\r
- */ \r
- for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit)\r
- {\r
- cminmax.expandTo(cit->finalPoint());\r
- }\r
- }\r
-\r
- out(" },\n");\r
-\r
- double cminx = cminmax.min()[X];\r
- double cmaxx = cminmax.max()[X];\r
- double cminy = cminmax.min()[Y];\r
- double cmaxy = cminmax.max()[Y];\r
-\r
- if (cminx < minx)\r
- minx = cminx;\r
- if (cmaxx > maxx)\r
- maxx = cmaxx;\r
- if (cminy < miny)\r
- miny = cminy;\r
- if (cmaxy > maxy)\r
- maxy = cmaxy;\r
-\r
- return true;\r
-}\r
-\r
-\r
-\r
-#endif /* #if o */\r
-\r
-\r
-\r
-/**\r
- * Output the tree data to buffer\r
- */\r
-bool JavaFXOutput::doTreeRecursive(SPDocument *doc, SPObject *obj)\r
-{\r
- /**\r
- * Check the type of node and process\r
- */\r
- String id;\r
- if (!obj->id)\r
- {\r
- char buf[16];\r
- sprintf(buf, "id%d", idindex++);\r
- id = buf;\r
- }\r
- else\r
- {\r
- id = obj->id;\r
- }\r
- if (SP_IS_ITEM(obj))\r
- {\r
- SPItem *item = SP_ITEM(obj);\r
- if (!doCurve(item, id))\r
- return false;\r
- }\r
- else if (SP_IS_GRADIENT(obj))\r
- {\r
- SPGradient *grad = SP_GRADIENT(obj);\r
- if (!doGradient(grad, id))\r
- return false;\r
- }\r
-\r
- /**\r
- * Descend into children\r
- */ \r
- for (SPObject *child = obj->firstChild() ; child ; child = child->next)\r
- {\r
- if (!doTreeRecursive(doc, child))\r
- return false;\r
- }\r
-\r
- return true;\r
-}\r
-\r
-\r
-/**\r
- * Output the curve data to buffer\r
- */\r
-bool JavaFXOutput::doTree(SPDocument *doc)\r
-{\r
-\r
- double bignum = 1000000.0;\r
- minx = bignum;\r
- maxx = -bignum;\r
- miny = bignum;\r
- maxy = -bignum;\r
-\r
- if (!doTreeRecursive(doc, doc->root))\r
- return false;\r
-\r
- return true;\r
-\r
-}\r
-\r
-\r
-bool JavaFXOutput::doBody(SPDocument *doc, SPObject *obj)\r
-{\r
- /**\r
- * Check the type of node and process\r
- */\r
- String id;\r
- if (!obj->id)\r
- {\r
- char buf[16];\r
- sprintf(buf, "id%d", idindex++);\r
- id = buf;\r
- }\r
- else\r
- {\r
- id = obj->id;\r
- }\r
-\r
- if (SP_IS_ITEM(obj)) {\r
- SPItem *item = SP_ITEM(obj);\r
- //### Get the Shape\r
- if (SP_IS_SHAPE(item)) {//Bulia's suggestion. Allow all shapes\r
- SPShape *shape = SP_SHAPE(item);\r
- SPCurve *curve = shape->curve;\r
- if (!curve->is_empty())\r
- out(" %s(),\n", id.c_str());\r
- }\r
- }\r
- else if (SP_IS_GRADIENT(obj)) {\r
- //TODO: what to do with Gradient within body?????\r
- //SPGradient *grad = SP_GRADIENT(reprobj);\r
- //if (!doGradient(grad, id))\r
- // return false;\r
- }\r
-\r
- /**\r
- * Descend into children\r
- */\r
- for (SPObject *child = obj->firstChild() ; child ; child = child->next)\r
- {\r
- if (!doBody(doc, child))\r
- return false;\r
- }\r
-\r
- return true;\r
-}\r
-\r
-\r
-\r
-//########################################################################\r
-//# M A I N O U T P U T\r
-//########################################################################\r
-\r
-\r
-\r
-/**\r
- * Set values back to initial state\r
- */\r
-void JavaFXOutput::reset()\r
-{\r
- nrNodes = 0;\r
- nrShapes = 0;\r
- idindex = 0;\r
- name.clear();\r
- outbuf.clear();\r
- foutbuf.clear();\r
-}\r
-\r
-\r
-\r
-/**\r
- * Saves the <paths> of an Inkscape SVG file as JavaFX spline definitions\r
- */\r
-bool JavaFXOutput::saveDocument(SPDocument *doc, gchar const *filename_utf8)\r
-{\r
- reset();\r
-\r
-\r
- name = Glib::path_get_basename(filename_utf8);\r
- int pos = name.find('.');\r
- if (pos > 0)\r
- name = name.substr(0, pos);\r
-\r
-\r
- //###### SAVE IN JAVAFX FORMAT TO BUFFER\r
- //# Lets do the curves first, to get the stats\r
-\r
- if (!doTree(doc))\r
- return false;\r
- String curveBuf = outbuf;\r
- outbuf.clear();\r
-\r
- if (!doHeader())\r
- return false;\r
-\r
- outbuf.append(curveBuf);\r
-\r
-#ifdef JAVAFX_SDK_1_0\r
- out(" override function create(): Node {\n");\r
-#else\r
- out(" public function create(): Node {\n");\r
-#endif\r
- out(" Group {\n");\r
- out(" content: [\n");\r
- idindex = 0;\r
-\r
- doBody(doc, doc->root);\r
-\r
- if (!doTail())\r
- return false;\r
-\r
-\r
-\r
- //###### WRITE TO FILE\r
- FILE *f = Inkscape::IO::fopen_utf8name(filename_utf8, "w");\r
- if (!f)\r
- {\r
- err("Could open JavaFX file '%s' for writing", filename_utf8);\r
- return false;\r
- }\r
-\r
- for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); iter++)\r
- {\r
- fputc(*iter, f);\r
- }\r
- \r
- fclose(f);\r
- \r
- return true;\r
-}\r
-\r
-\r
-\r
-\r
-//########################################################################\r
-//# EXTENSION API\r
-//########################################################################\r
-\r
-\r
-\r
-#include "clear-n_.h"\r
-\r
-\r
-\r
-/**\r
- * API call to save document\r
-*/\r
-void\r
-JavaFXOutput::save(Inkscape::Extension::Output */*mod*/,\r
- SPDocument *doc, gchar const *filename_utf8)\r
-{\r
- /* N.B. The name `filename_utf8' represents the fact that we want it to be in utf8; whereas in\r
- * fact we know that some callers of Extension::save pass something in the filesystem's\r
- * encoding, while others do g_filename_to_utf8 before calling.\r
- *\r
- * In terms of safety, it's best to make all callers pass actual filenames, since in general\r
- * one can't round-trip from filename to utf8 back to the same filename. Whereas the argument\r
- * for passing utf8 filenames is one of convenience: we often want to pass to g_warning or\r
- * store as a string (rather than a byte stream) in XML, or the like. */\r
- if (!saveDocument(doc, filename_utf8))\r
- {\r
- g_warning("Could not save JavaFX file '%s'", filename_utf8);\r
- }\r
-}\r
-\r
-\r
-\r
-/**\r
- * Make sure that we are in the database\r
- */\r
-bool JavaFXOutput::check (Inkscape::Extension::Extension */*module*/)\r
-{\r
- /* We don't need a Key\r
- if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_JFX))\r
- return FALSE;\r
- */\r
-\r
- return true;\r
-}\r
-\r
-\r
-\r
-/**\r
- * This is the definition of JavaFX output. This function just\r
- * calls the extension system with the memory allocated XML that\r
- * describes the data.\r
-*/\r
-void\r
-JavaFXOutput::init()\r
-{\r
- Inkscape::Extension::build_from_mem(\r
- "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"\r
- "<name>" N_("JavaFX Output") "</name>\n"\r
- "<id>org.inkscape.output.jfx</id>\n"\r
- "<output>\n"\r
- "<extension>.fx</extension>\n"\r
- "<mimetype>text/x-javafx-script</mimetype>\n"\r
- "<filetypename>" N_("JavaFX (*.fx)") "</filetypename>\n"\r
- "<filetypetooltip>" N_("JavaFX Raytracer File") "</filetypetooltip>\n"\r
- "</output>\n"\r
- "</inkscape-extension>",\r
- new JavaFXOutput());\r
-}\r
-\r
-\r
-\r
-\r
-\r
-} // namespace Internal\r
-} // namespace Extension\r
-} // namespace Inkscape\r
-\r
-\r
-/*\r
- Local Variables:\r
- mode:c++\r
- c-file-style:"stroustrup"\r
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
- indent-tabs-mode:nil\r
- fill-column:99\r
- End:\r
-*/\r
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r
+/*
+ * A simple utility for exporting Inkscape svg Shapes as JavaFX paths.
+ *
+ * For information on the JavaFX file format, see:
+ * https://openjfx.dev.java.net/
+ *
+ * Authors:
+ * Bob Jamison <ishmal@inkscape.org>
+ * Silveira Neto <silveiraneto@gmail.com>
+ * Jim Clarke <Jim.Clarke@sun.com>
+ *
+ * Copyright (C) 2008 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "javafx-out.h"
+#include <inkscape.h>
+#include <inkscape-version.h>
+#include <sp-path.h>
+#include <sp-linear-gradient.h>
+#include <sp-radial-gradient.h>
+#include <style.h>
+#include <display/curve.h>
+#include <display/canvas-bpath.h>
+#include <svg/svg.h>
+#include <extension/system.h>
+#include <2geom/pathvector.h>
+#include <2geom/rect.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/hvlinesegment.h>
+#include "helper/geom.h"
+#include "helper/geom-curves.h"
+#include <io/sys.h>
+
+
+#include <string>
+#include <stdio.h>
+#include <stdarg.h>
+
+
+namespace Inkscape
+{
+namespace Extension
+{
+namespace Internal
+{
+
+
+
+
+//########################################################################
+//# M E S S A G E S
+//########################################################################
+
+static void err(const char *fmt, ...)
+{
+ va_list args;
+ g_log(NULL, G_LOG_LEVEL_WARNING, "javafx-out err: ");
+ va_start(args, fmt);
+ g_logv(NULL, G_LOG_LEVEL_WARNING, fmt, args);
+ va_end(args);
+ g_log(NULL, G_LOG_LEVEL_WARNING, "\n");
+}
+
+
+//########################################################################
+//# U T I L I T Y
+//########################################################################
+
+/**
+ * Got this method from Bulia, and modified it a bit. It basically
+ * starts with this style, gets its SPObject parent, walks up the object
+ * tree and finds all of the opacities and multiplies them.
+ *
+ * We use this for our "flat" object output. If the code is modified
+ * to reflect a tree of <groups>, then this will be unneccessary.
+ */
+static double effective_opacity(const SPStyle *style)
+{
+ double val = 1.0;
+ for (SPObject const *obj = style->object; obj ; obj = obj->parent)
+ {
+ style = SP_OBJECT_STYLE(obj);
+ if (style)
+ val *= SP_SCALE24_TO_FLOAT(style->opacity.value);
+ }
+ return val;
+}
+
+//########################################################################
+//# OUTPUT FORMATTING
+//########################################################################
+
+
+/**
+ * We want to control floating output format.
+ * Especially to avoid localization. (decimal ',' for example)
+ */
+static JavaFXOutput::String dstr(double d)
+{
+ char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1];
+ g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.8f", (gdouble)d);
+ JavaFXOutput::String s = dbuf;
+ return s;
+}
+
+#define DSTR(d) (dstr(d).c_str())
+
+
+/**
+ * Format a double as an integer
+ */
+static JavaFXOutput::String istr(double d)
+{
+ char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1];
+ g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.0f", (gdouble)d);
+ JavaFXOutput::String s = dbuf;
+ return s;
+}
+
+#define ISTR(d) (istr(d).c_str())
+
+
+/**
+ * Format an rgba() string
+ */
+static JavaFXOutput::String rgba(guint32 rgba)
+{
+ unsigned int r = SP_RGBA32_R_U(rgba);
+ unsigned int g = SP_RGBA32_G_U(rgba);
+ unsigned int b = SP_RGBA32_B_U(rgba);
+ unsigned int a = SP_RGBA32_A_U(rgba);
+ char buf[80];
+ snprintf(buf, 79, "Color.rgb(0x%02x, 0x%02x, 0x%02x, %s)",
+ r, g, b, DSTR((double)a/256.0));
+ JavaFXOutput::String s = buf;
+ return s;
+}
+
+
+/**
+ * Format an rgba() string for a color and a 0.0-1.0 alpha
+ */
+static JavaFXOutput::String rgba(SPColor color, gdouble alpha)
+{
+ return rgba(color.toRGBA32(alpha));
+}
+
+/**
+ * Map Inkscape linecap styles to JavaFX
+ */
+static JavaFXOutput::String getStrokeLineCap(unsigned value) {
+ switch(value) {
+ case SP_STROKE_LINECAP_BUTT:
+ return "StrokeLineCap.BUTT";
+ case SP_STROKE_LINECAP_ROUND:
+ return "StrokeLineCap.ROUND";
+ case SP_STROKE_LINECAP_SQUARE:
+ return "StrokeLineCap.SQUARE";
+ default:
+ return "INVALID LINE CAP";
+ }
+}
+
+
+/**
+ * Map Inkscape linejoin styles to JavaFX
+ */
+static JavaFXOutput::String getStrokeLineJoin(unsigned value) {
+ switch(value) {
+ case SP_STROKE_LINEJOIN_MITER:
+ return "StrokeLineJoin.MITER";
+ case SP_STROKE_LINEJOIN_ROUND:
+ return "StrokeLineJoin.ROUND";
+ case SP_STROKE_LINEJOIN_BEVEL:
+ return "StrokeLineJoin.BEVEL";
+ default:
+ return "INVALID LINE JOIN";
+ }
+}
+
+
+/**
+ * Replace illegal characters for JavaFX for a underscore.
+ */
+static JavaFXOutput::String sanatize(const JavaFXOutput::String &badstr){
+ JavaFXOutput::String good(badstr);
+ for (int pos = 0; pos < badstr.length(); ++pos )
+ if((badstr.at(pos)=='-')||(badstr.at(pos)==' '))
+ good.replace(pos, 1, "_");
+ return good;
+}
+
+/**
+ * Output data to the buffer, printf()-style
+ */
+void JavaFXOutput::out(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ gchar *output = g_strdup_vprintf(fmt, args);
+ va_end(args);
+ outbuf.append(output);
+ g_free(output);
+}
+
+
+
+/**
+ * Output the file header
+ */
+bool JavaFXOutput::doHeader()
+{
+ time_t tim = time(NULL);
+ out("/*###################################################################\n");
+ out("### This JavaFX document was generated by Inkscape\n");
+ out("### http://www.inkscape.org\n");
+ out("### Created: %s", ctime(&tim));
+ out("### Version: %s\n", Inkscape::version_string);
+ out("#####################################################################\n");
+ out("### NOTES:\n");
+ out("### ============\n");
+ out("### JavaFX information can be found at\n");
+ out("### hhttps://openjfx.dev.java.net\n");
+ out("###\n");
+ out("### If you have any problems with this output, please see the\n");
+ out("### Inkscape project at http://www.inkscape.org, or visit\n");
+ out("### the #inkscape channel on irc.freenode.net . \n");
+ out("###\n");
+ out("###################################################################*/\n");
+ out("\n\n");
+ out("/*###################################################################\n");
+ out("## Exports in this file\n");
+ out("##==========================\n");
+ out("## Shapes : %d\n", nrShapes);
+ out("## Nodes : %d\n", nrNodes);
+ out("###################################################################*/\n");
+ out("\n\n");
+
+ // import javafx libraries we can need
+ out("import javafx.application.*;\n");
+ out("import javafx.scene.*;\n");
+ out("import javafx.scene.geometry.*;\n");
+ out("import javafx.scene.transform.*;\n");
+ out("import javafx.scene.paint.*;\n");
+ out("\n");
+
+ out("\n\n");
+
+ // Creates a class extended from CustomNode
+ out("public class %s extends CustomNode {\n", name.c_str());
+
+ return true;
+}
+
+
+
+/**
+ * Output the file footer
+ */
+bool JavaFXOutput::doTail()
+{
+ float border = 25.0;
+
+ // Write the tail of CustomNode
+ out(" ] // content\n");
+ out(" transform: Translate { x : %s, y : %s }\n",
+ DSTR((-minx) + border), DSTR((-miny) + border) );
+ out(" } // Group\n");
+ out(" } // function create()\n");
+ out("} // class %s\n", name.c_str());
+ out("\n");
+
+ // Frame
+ out("Frame {\n");
+ out(" title: \"%s\"\n", name.c_str());
+ out(" width: %s\n", ISTR(maxx-minx + border * 2.0));
+ out(" height: %s\n", ISTR(maxy-miny + border * 2.0));
+ out(" visible: true\n");
+
+ // Stage
+ out(" stage: Stage {\n");
+ out(" content: %s{}\n", name.c_str());
+ out(" } // Stage\n");
+
+ out("} // Frame\n");
+
+ out("\n");
+
+ out("/*###################################################################\n");
+ out("### E N D C L A S S %s\n", name.c_str());
+ out("###################################################################*/\n");
+ out("\n\n");
+ return true;
+}
+
+
+
+/**
+ * Output gradient information to the buffer
+ */
+bool JavaFXOutput::doGradient(SPGradient *grad, const String &id)
+{
+ String jfxid = sanatize(id);
+
+ if (SP_IS_LINEARGRADIENT(grad))
+ {
+ SPLinearGradient *g = SP_LINEARGRADIENT(grad);
+ out(" /* create LinearGradient for %s */\n", jfxid.c_str());
+ out(" private function %s(): LinearGradient {\n", jfxid.c_str());
+ out(" LinearGradient {\n");
+ std::vector<SPGradientStop> stops = g->vector.stops;
+ if (stops.size() > 0)
+ {
+ out(" stops:\n");
+ out(" [\n");
+ for (unsigned int i = 0 ; i<stops.size() ; i++)
+ {
+ SPGradientStop stop = stops[i];
+ out(" Stop {\n");
+ out(" offset: %s\n", DSTR(stop.offset));
+ out(" color: %s\n", rgba(stop.color, stop.opacity).c_str());
+ out(" },\n");
+ }
+ out(" ]\n");
+ }
+ out(" };\n");
+ out(" } // end LinearGradient: %s\n", jfxid.c_str());
+ out("\n\n");
+ }
+ else if (SP_IS_RADIALGRADIENT(grad))
+ {
+ SPRadialGradient *g = SP_RADIALGRADIENT(grad);
+ out(" /* create RadialGradient for %s */\n", jfxid.c_str());
+ out(" private function %s() {\n", jfxid.c_str());
+ out(" RadialGradient {\n");
+ out(" centerX: %s\n", DSTR(g->cx.value));
+ out(" centerY: %s\n", DSTR(g->cy.value));
+ out(" focusX: %s\n", DSTR(g->fx.value));
+ out(" focusY: %s\n", DSTR(g->fy.value));
+ out(" radius: %s\n", DSTR(g->r.value ));
+ std::vector<SPGradientStop> stops = g->vector.stops;
+ if (stops.size() > 0)
+ {
+ out(" stops:\n");
+ out(" [\n");
+ for (unsigned int i = 0 ; i<stops.size() ; i++)
+ {
+ SPGradientStop stop = stops[i];
+ out(" Stop {\n");
+ out(" offset: %s\n", DSTR(stop.offset));
+ out(" color: %s\n", rgba(stop.color, stop.opacity).c_str());
+ out(" },\n");
+ }
+ out(" ]\n");
+ }
+ out(" };\n");
+ out(" } // end RadialGradient: %s\n", jfxid.c_str());
+ out("\n\n");
+ }
+ else
+ {
+ err("Unknown gradient type for '%s'\n", jfxid.c_str());
+ return false;
+ }
+
+
+ return true;
+}
+
+
+
+
+/**
+ * Output an element's style attribute
+ */
+bool JavaFXOutput::doStyle(SPStyle *style)
+{
+ if (!style)
+ return true;
+
+ out(" opacity: %s\n", DSTR(effective_opacity(style)));
+
+ /**
+ * Fill
+ */
+ SPIPaint fill = style->fill;
+ if (fill.isColor())
+ {
+ // see color.h for how to parse SPColor
+ out(" fill: %s\n",
+ rgba(fill.value.color, SP_SCALE24_TO_FLOAT(style->fill_opacity.value)).c_str());
+ }
+ else if (fill.isPaintserver()){
+ if (fill.value.href && fill.value.href->getURI() ){
+ String uri = fill.value.href->getURI()->toString();
+ /* trim the anchor '#' from the front */
+ if (uri.size() > 0 && uri[0]=='#')
+ uri = uri.substr(1);
+ out(" fill: %s()\n", sanatize(uri).c_str());
+ }
+ }
+
+
+ /**
+ * Stroke
+ */
+ /**
+ *NOTE: Things in style we can use:
+ * SPIPaint stroke;
+ * SPILength stroke_width;
+ * SPIEnum stroke_linecap;
+ * SPIEnum stroke_linejoin;
+ * SPIFloat stroke_miterlimit;
+ * NRVpathDash stroke_dash;
+ * unsigned stroke_dasharray_set : 1;
+ * unsigned stroke_dasharray_inherit : 1;
+ * unsigned stroke_dashoffset_set : 1;
+ * SPIScale24 stroke_opacity;
+ */
+ if (style->stroke_opacity.value > 0)
+ {
+ SPIPaint stroke = style->stroke;
+ out(" stroke: %s\n",
+ rgba(stroke.value.color, SP_SCALE24_TO_FLOAT(style->stroke_opacity.value)).c_str());
+ double strokewidth = style->stroke_width.value;
+ unsigned linecap = style->stroke_linecap.value;
+ unsigned linejoin = style->stroke_linejoin.value;
+ out(" strokeWidth: %s\n", DSTR(strokewidth));
+ out(" strokeLineCap: %s\n", getStrokeLineCap(linecap).c_str());
+ out(" strokeLineJoin: %s\n", getStrokeLineJoin(linejoin).c_str());
+ out(" strokeMiterLimit: %s\n", DSTR(style->stroke_miterlimit.value));
+ if(style->stroke_dasharray_set) {
+ if(style->stroke_dashoffset_set) {
+ out(" strokeDashOffset: %s\n", DSTR(style->stroke_dash.offset));
+ }
+ out(" strokeDashArray: [ ");
+ for(int i = 0; i < style->stroke_dash.n_dash; i++ ) {
+ if(i > 0) {
+ out(", %.2lf", style->stroke_dash.dash[i]);
+ }else {
+ out(" %.2lf", style->stroke_dash.dash[i]);
+ }
+ }
+ out(" ]\n");
+ }
+
+ }
+
+ return true;
+}
+
+
+#if 1
+
+/**
+ * Output the curve data to buffer
+ */
+bool JavaFXOutput::doCurve(SPItem *item, const String &id)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ String jfxid = sanatize(id);
+
+ //### Get the Shape
+ if (!SP_IS_SHAPE(item))//Bulia's suggestion. Allow all shapes
+ return true;
+
+ SPShape *shape = SP_SHAPE(item);
+ SPCurve *curve = shape->curve;
+ if (curve->is_empty())
+ return true;
+
+ nrShapes++;
+
+ out(" /** path %s */\n", jfxid.c_str());
+ out(" private function %s() : Path {\n",jfxid.c_str());
+ out(" Path {\n");
+ out(" id: \"%s\"\n", jfxid.c_str());
+
+ /**
+ * Output the style information
+ */
+ if (!doStyle(SP_OBJECT_STYLE(shape)))
+ return false;
+
+ // convert the path to only lineto's and cubic curveto's:
+ Geom::Scale yflip(1.0, -1.0);
+ Geom::Matrix tf = sp_item_i2d_affine(item) * yflip;
+ Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf );
+
+ //Count the NR_CURVETOs/LINETOs (including closing line segment)
+ guint segmentCount = 0;
+ for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) {
+ segmentCount += (*it).size();
+ if (it->closed())
+ segmentCount += 1;
+ }
+
+ out(" elements: [\n");
+
+ unsigned int segmentNr = 0;
+
+ nrNodes += segmentCount;
+
+ Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() );
+
+ /**
+ * For all Subpaths in the <path>
+ */
+ for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit)
+ {
+ Geom::Point p = pit->front().initialPoint();
+ cminmax.expandTo(p);
+ out(" MoveTo {\n");
+ out(" x: %s\n", DSTR(p[X]));
+ out(" y: %s\n", DSTR(p[Y]));
+ out(" },\n");
+
+ /**
+ * For all segments in the subpath
+ */
+ for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit)
+ {
+ //### LINE
+ if( dynamic_cast<Geom::LineSegment const *> (&*cit) ||
+ dynamic_cast<Geom::HLineSegment const *> (&*cit) ||
+ dynamic_cast<Geom::VLineSegment const *> (&*cit) )
+ {
+ Geom::Point p = cit->finalPoint();
+ out(" LineTo {\n");
+ out(" x: %s\n", DSTR(p[X]));
+ out(" y: %s\n", DSTR(p[Y]));
+ out(" },\n");
+ nrNodes++;
+ }
+ //### BEZIER
+ else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit))
+ {
+ std::vector<Geom::Point> points = cubic->points();
+ Geom::Point p1 = points[1];
+ Geom::Point p2 = points[2];
+ Geom::Point p3 = points[3];
+ out(" CurveTo {\n");
+ out(" controlX1: %s\n", DSTR(p1[X]));
+ out(" controlY1: %s\n", DSTR(p1[Y]));
+ out(" controlX2: %s\n", DSTR(p2[X]));
+ out(" controlY2: %s\n", DSTR(p2[Y]));
+ out(" x: %s\n", DSTR(p3[X]));
+ out(" y: %s\n", DSTR(p3[Y]));
+ out(" },\n");
+ nrNodes++;
+ }
+ else
+ {
+ g_error ("logical error, because pathv_to_linear_and_cubic_beziers was used");
+ }
+ segmentNr++;
+ cminmax.expandTo(cit->finalPoint());
+ }
+ if (pit->closed())
+ {
+ out(" ClosePath {},\n");
+ }
+ }
+
+ out(" ] // elements\n");
+ out(" }; // Path\n");
+ out(" } // end path %s\n\n", jfxid.c_str());
+
+ double cminx = cminmax.min()[X];
+ double cmaxx = cminmax.max()[X];
+ double cminy = cminmax.min()[Y];
+ double cmaxy = cminmax.max()[Y];
+
+ if (cminx < minx)
+ minx = cminx;
+ if (cmaxx > maxx)
+ maxx = cmaxx;
+ if (cminy < miny)
+ miny = cminy;
+ if (cmaxy > maxy)
+ maxy = cmaxy;
+
+ return true;
+}
+
+
+
+#else
+
+/**
+ * Output the curve data to buffer
+ */
+bool JavaFXOutput::doCurve(SPItem *item, const String &id)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ //### Get the Shape
+ if (!SP_IS_SHAPE(item))//Bulia's suggestion. Allow all shapes
+ return true;
+
+ SPShape *shape = SP_SHAPE(item);
+ SPCurve *curve = shape->curve;
+ if (curve->is_empty())
+ return true;
+
+ nrShapes++;
+
+ out(" SVGPath \n");
+ out(" {\n");
+ out(" id: \"%s\"\n", id.c_str());
+
+ /**
+ * Output the style information
+ */
+ if (!doStyle(SP_OBJECT_STYLE(shape)))
+ return false;
+
+ // convert the path to only lineto's and cubic curveto's:
+ Geom::Scale yflip(1.0, -1.0);
+ Geom::Matrix tf = sp_item_i2d_affine(item) * yflip;
+ Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf );
+
+ //Count the NR_CURVETOs/LINETOs (including closing line segment)
+ nrNodes = 0;
+ for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) {
+ nrNodes += (*it).size();
+ if (it->closed())
+ nrNodes += 1;
+ }
+
+ char *dataStr = sp_svg_write_path(pathv);
+ out(" content: \"%s\"\n", dataStr);
+ free(dataStr);
+
+ Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() );
+
+ /**
+ * Get the Min and Max X and Y extends for the Path.
+ * ....For all Subpaths in the <path>
+ */
+ for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit)
+ {
+ cminmax.expandTo(pit->front().initialPoint());
+ /**
+ * For all segments in the subpath
+ */
+ for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit)
+ {
+ cminmax.expandTo(cit->finalPoint());
+ }
+ }
+
+ out(" },\n");
+
+ double cminx = cminmax.min()[X];
+ double cmaxx = cminmax.max()[X];
+ double cminy = cminmax.min()[Y];
+ double cmaxy = cminmax.max()[Y];
+
+ if (cminx < minx)
+ minx = cminx;
+ if (cmaxx > maxx)
+ maxx = cmaxx;
+ if (cminy < miny)
+ miny = cminy;
+ if (cmaxy > maxy)
+ maxy = cmaxy;
+
+ return true;
+}
+
+
+
+#endif /* #if o */
+
+
+
+/**
+ * Output the tree data to buffer
+ */
+bool JavaFXOutput::doTreeRecursive(SPDocument *doc, SPObject *obj)
+{
+ /**
+ * Check the type of node and process
+ */
+ String id;
+ if (!obj->id)
+ {
+ char buf[16];
+ sprintf(buf, "id%d", idindex++);
+ id = buf;
+ }
+ else
+ {
+ id = obj->id;
+ }
+ if (SP_IS_ITEM(obj))
+ {
+ SPItem *item = SP_ITEM(obj);
+ if (!doCurve(item, id))
+ return false;
+ }
+ else if (SP_IS_GRADIENT(obj))
+ {
+ SPGradient *grad = SP_GRADIENT(obj);
+ if (!doGradient(grad, id))
+ return false;
+ }
+
+ /**
+ * Descend into children
+ */
+ for (SPObject *child = obj->firstChild() ; child ; child = child->next)
+ {
+ if (!doTreeRecursive(doc, child))
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Output the curve data to buffer
+ */
+bool JavaFXOutput::doTree(SPDocument *doc)
+{
+
+ double bignum = 1000000.0;
+ minx = bignum;
+ maxx = -bignum;
+ miny = bignum;
+ maxy = -bignum;
+
+ if (!doTreeRecursive(doc, doc->root))
+ return false;
+
+ return true;
+
+}
+
+
+bool JavaFXOutput::doBody(SPDocument *doc, SPObject *obj)
+{
+ /**
+ * Check the type of node and process
+ */
+ String id;
+ if (!obj->id)
+ {
+ char buf[16];
+ sprintf(buf, "id%d", idindex++);
+ id = buf;
+ }
+ else
+ {
+ id = obj->id;
+ }
+
+ if (SP_IS_ITEM(obj)) {
+ SPItem *item = SP_ITEM(obj);
+ //### Get the Shape
+ if (SP_IS_SHAPE(item)) {//Bulia's suggestion. Allow all shapes
+ SPShape *shape = SP_SHAPE(item);
+ SPCurve *curve = shape->curve;
+ if (!curve->is_empty())
+ out(" %s(),\n", id.c_str());
+ }
+ }
+ else if (SP_IS_GRADIENT(obj)) {
+ //TODO: what to do with Gradient within body?????
+ //SPGradient *grad = SP_GRADIENT(reprobj);
+ //if (!doGradient(grad, id))
+ // return false;
+ }
+
+ /**
+ * Descend into children
+ */
+ for (SPObject *child = obj->firstChild() ; child ; child = child->next)
+ {
+ if (!doBody(doc, child))
+ return false;
+ }
+
+ return true;
+}
+
+
+
+//########################################################################
+//# M A I N O U T P U T
+//########################################################################
+
+
+
+/**
+ * Set values back to initial state
+ */
+void JavaFXOutput::reset()
+{
+ nrNodes = 0;
+ nrShapes = 0;
+ idindex = 0;
+ name.clear();
+ outbuf.clear();
+ foutbuf.clear();
+}
+
+
+
+/**
+ * Saves the <paths> of an Inkscape SVG file as JavaFX spline definitions
+ */
+bool JavaFXOutput::saveDocument(SPDocument *doc, gchar const *filename_utf8)
+{
+ reset();
+
+
+ name = Glib::path_get_basename(filename_utf8);
+ int pos = name.find('.');
+ if (pos > 0)
+ name = name.substr(0, pos);
+
+
+ //###### SAVE IN JAVAFX FORMAT TO BUFFER
+ //# Lets do the curves first, to get the stats
+
+ if (!doTree(doc))
+ return false;
+ String curveBuf = outbuf;
+ outbuf.clear();
+
+ if (!doHeader())
+ return false;
+
+ outbuf.append(curveBuf);
+
+#ifdef JAVAFX_SDK_1_0
+ out(" override function create(): Node {\n");
+#else
+ out(" public function create(): Node {\n");
+#endif
+ out(" Group {\n");
+ out(" content: [\n");
+ idindex = 0;
+
+ doBody(doc, doc->root);
+
+ if (!doTail())
+ return false;
+
+
+
+ //###### WRITE TO FILE
+ FILE *f = Inkscape::IO::fopen_utf8name(filename_utf8, "w");
+ if (!f)
+ {
+ err("Could open JavaFX file '%s' for writing", filename_utf8);
+ return false;
+ }
+
+ for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); iter++)
+ {
+ fputc(*iter, f);
+ }
+
+ fclose(f);
+
+ return true;
+}
+
+
+
+
+//########################################################################
+//# EXTENSION API
+//########################################################################
+
+
+
+#include "clear-n_.h"
+
+
+
+/**
+ * API call to save document
+*/
+void
+JavaFXOutput::save(Inkscape::Extension::Output */*mod*/,
+ SPDocument *doc, gchar const *filename_utf8)
+{
+ /* N.B. The name `filename_utf8' represents the fact that we want it to be in utf8; whereas in
+ * fact we know that some callers of Extension::save pass something in the filesystem's
+ * encoding, while others do g_filename_to_utf8 before calling.
+ *
+ * In terms of safety, it's best to make all callers pass actual filenames, since in general
+ * one can't round-trip from filename to utf8 back to the same filename. Whereas the argument
+ * for passing utf8 filenames is one of convenience: we often want to pass to g_warning or
+ * store as a string (rather than a byte stream) in XML, or the like. */
+ if (!saveDocument(doc, filename_utf8))
+ {
+ g_warning("Could not save JavaFX file '%s'", filename_utf8);
+ }
+}
+
+
+
+/**
+ * Make sure that we are in the database
+ */
+bool JavaFXOutput::check (Inkscape::Extension::Extension */*module*/)
+{
+ /* We don't need a Key
+ if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_JFX))
+ return FALSE;
+ */
+
+ return true;
+}
+
+
+
+/**
+ * This is the definition of JavaFX output. This function just
+ * calls the extension system with the memory allocated XML that
+ * describes the data.
+*/
+void
+JavaFXOutput::init()
+{
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("JavaFX Output") "</name>\n"
+ "<id>org.inkscape.output.jfx</id>\n"
+ "<output>\n"
+ "<extension>.fx</extension>\n"
+ "<mimetype>text/x-javafx-script</mimetype>\n"
+ "<filetypename>" N_("JavaFX (*.fx)") "</filetypename>\n"
+ "<filetypetooltip>" N_("JavaFX Raytracer File") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>",
+ new JavaFXOutput());
+}
+
+
+
+
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 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 :
index 691812ee250a372f258ccc4282d482f3975ab012..9c1c8778ba0489ab798a00e00fbcf126375bae45 100644 (file)
-/*\r
- * A simple utility for exporting an Inkscape svg image as a JavaFX\r
- * scene tree.\r
- *\r
- * Authors:\r
- * Bob Jamison <ishmal@inkscape.org>\r
- * Silveira Neto <silveiraneto@gmail.com>\r
- * Jim Clarke <Jim.Clarke@sun.com>\r
- *\r
- * Copyright (C) 2008 Authors\r
- *\r
- * Released under GNU GPL, read the file 'COPYING' for more information\r
- */\r
-\r
-#ifndef EXTENSION_INTERNAL_JAVAFX_OUT_H\r
-#define EXTENSION_INTERNAL_JAVAFX_OUT_H\r
-\r
-#include <glib.h>\r
-#include "extension/implementation/implementation.h"\r
-#include <document.h>\r
-#include <sp-gradient.h>\r
-\r
-namespace Inkscape\r
-{\r
-namespace Extension\r
-{\r
-namespace Internal\r
-{\r
-\r
-\r
-\r
-/**\r
- * Output the current svg document in JavaFX format.\r
- * \r
- * For information, @see: \r
- * https://openjfx.dev.java.net/\r
- */ \r
-class JavaFXOutput : public Inkscape::Extension::Implementation::Implementation\r
-{\r
-\r
-\r
-public:\r
-\r
- /**\r
- * Our internal String definition\r
- */\r
- typedef Glib::ustring String;\r
-\r
-\r
- /**\r
- * Check whether we can actually output using this module\r
- */\r
- virtual bool check (Inkscape::Extension::Extension * module);\r
-\r
- /**\r
- * API call to perform the output to a file\r
- */\r
- virtual void save(Inkscape::Extension::Output *mod,\r
- SPDocument *doc, gchar const *filename);\r
-\r
- /**\r
- * Inkscape runtime startup call.\r
- */\r
- static void init(void);\r
- \r
- /**\r
- * Reset variables to initial state\r
- */\r
- void reset();\r
- \r
-private:\r
-\r
- //output class name\r
- String name;\r
-\r
- //For formatted output\r
- String outbuf; //main output buffer\r
- String foutbuf; //header function buffer\r
-\r
-\r
- /**\r
- * Format text to our output buffer\r
- */\r
- void out(const char *fmt, ...) G_GNUC_PRINTF(2,3);\r
-\r
- /**\r
- * Format text to our function output buffer\r
- */\r
- void fout(const char *fmt, ...) G_GNUC_PRINTF(2,3);\r
-\r
- //Output the parts of the file\r
-\r
- /**\r
- * Output the file header\r
- */\r
- bool doHeader();\r
-\r
- /**\r
- * Output gradient information to the buffer\r
- */\r
- bool doGradient(SPGradient *grad, const String &id);\r
-\r
- /**\r
- * Output an element's style attribute\r
- */\r
- bool doStyle(SPStyle *style);\r
-\r
- /**\r
- * Output the SVG document's curve data as JavaFX geometry types\r
- */\r
- bool doCurve(SPItem *item, const String &id);\r
- bool doTreeRecursive(SPDocument *doc, SPObject *obj);\r
- bool doTree(SPDocument *doc);\r
-\r
- bool doBody(SPDocument *doc, SPObject *obj);\r
-\r
- /**\r
- * Output the file footer\r
- */\r
- bool doTail();\r
-\r
-\r
-\r
- /**\r
- * Actual method to save document\r
- */\r
- bool saveDocument(SPDocument *doc, gchar const *filename);\r
-\r
- //For statistics\r
- int nrNodes;\r
- int nrShapes;\r
- \r
- int idindex;\r
-\r
- double minx;\r
- double miny;\r
- double maxx;\r
- double maxy;\r
- \r
-\r
-};\r
-\r
-\r
-\r
-\r
-} // namespace Internal\r
-} // namespace Extension\r
-} // namespace Inkscape\r
-\r
-\r
-\r
-#endif /* EXTENSION_INTERNAL_POV_OUT_H */\r
-\r
+/*
+ * A simple utility for exporting an Inkscape svg image as a JavaFX
+ * scene tree.
+ *
+ * Authors:
+ * Bob Jamison <ishmal@inkscape.org>
+ * Silveira Neto <silveiraneto@gmail.com>
+ * Jim Clarke <Jim.Clarke@sun.com>
+ *
+ * Copyright (C) 2008 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef EXTENSION_INTERNAL_JAVAFX_OUT_H
+#define EXTENSION_INTERNAL_JAVAFX_OUT_H
+
+#include <glib.h>
+#include "extension/implementation/implementation.h"
+#include <document.h>
+#include <sp-gradient.h>
+
+namespace Inkscape
+{
+namespace Extension
+{
+namespace Internal
+{
+
+
+
+/**
+ * Output the current svg document in JavaFX format.
+ *
+ * For information, @see:
+ * https://openjfx.dev.java.net/
+ */
+class JavaFXOutput : public Inkscape::Extension::Implementation::Implementation
+{
+
+
+public:
+
+ /**
+ * Our internal String definition
+ */
+ typedef Glib::ustring String;
+
+
+ /**
+ * Check whether we can actually output using this module
+ */
+ virtual bool check (Inkscape::Extension::Extension * module);
+
+ /**
+ * API call to perform the output to a file
+ */
+ virtual void save(Inkscape::Extension::Output *mod,
+ SPDocument *doc, gchar const *filename);
+
+ /**
+ * Inkscape runtime startup call.
+ */
+ static void init(void);
+
+ /**
+ * Reset variables to initial state
+ */
+ void reset();
+
+private:
+
+ //output class name
+ String name;
+
+ //For formatted output
+ String outbuf; //main output buffer
+ String foutbuf; //header function buffer
+
+
+ /**
+ * Format text to our output buffer
+ */
+ void out(const char *fmt, ...) G_GNUC_PRINTF(2,3);
+
+ /**
+ * Format text to our function output buffer
+ */
+ void fout(const char *fmt, ...) G_GNUC_PRINTF(2,3);
+
+ //Output the parts of the file
+
+ /**
+ * Output the file header
+ */
+ bool doHeader();
+
+ /**
+ * Output gradient information to the buffer
+ */
+ bool doGradient(SPGradient *grad, const String &id);
+
+ /**
+ * Output an element's style attribute
+ */
+ bool doStyle(SPStyle *style);
+
+ /**
+ * Output the SVG document's curve data as JavaFX geometry types
+ */
+ bool doCurve(SPItem *item, const String &id);
+ bool doTreeRecursive(SPDocument *doc, SPObject *obj);
+ bool doTree(SPDocument *doc);
+
+ bool doBody(SPDocument *doc, SPObject *obj);
+
+ /**
+ * Output the file footer
+ */
+ bool doTail();
+
+
+
+ /**
+ * Actual method to save document
+ */
+ bool saveDocument(SPDocument *doc, gchar const *filename);
+
+ //For statistics
+ int nrNodes;
+ int nrShapes;
+
+ int idindex;
+
+ double minx;
+ double miny;
+ double maxx;
+ double maxy;
+
+
+};
+
+
+
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+
+
+#endif /* EXTENSION_INTERNAL_POV_OUT_H */
+
index daf208e789787e84d91e31fd2ca2dcf4f1bc0468..f3dc364f2cb44410e46b322a55114e6837886e33 100644 (file)
--- a/src/helper/geom-curves.h
+++ b/src/helper/geom-curves.h
-#ifndef INKSCAPE_HELPER_GEOM_CURVES_H\r
-#define INKSCAPE_HELPER_GEOM_CURVES_H\r
-\r
-/**\r
- * Specific curve type functions for Inkscape, not provided by lib2geom.\r
- *\r
- * Author:\r
- * Johan Engelen <goejendaagh@zonnet.nl>\r
- *\r
- * Copyright (C) 2008 Johan Engelen\r
- *\r
- * Released under GNU GPL\r
- */\r
-\r
-#include <2geom/hvlinesegment.h>\r
-\r
-inline bool is_straight_curve(Geom::Curve const & c) {\r
- if( dynamic_cast<Geom::LineSegment const*>(&c) ||\r
- dynamic_cast<Geom::HLineSegment const*>(&c) ||\r
- dynamic_cast<Geom::VLineSegment const*>(&c) )\r
- {\r
- return true;\r
- } else {\r
- return false;\r
- }\r
-}\r
-\r
-#endif // INKSCAPE_HELPER_GEOM_CURVES_H\r
-\r
-/*\r
- Local Variables:\r
- mode:c++\r
- c-file-style:"stroustrup"\r
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
- indent-tabs-mode:nil\r
- fill-column:99\r
- End:\r
-*/\r
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :\r
+#ifndef INKSCAPE_HELPER_GEOM_CURVES_H
+#define INKSCAPE_HELPER_GEOM_CURVES_H
+
+/**
+ * Specific curve type functions for Inkscape, not provided by lib2geom.
+ *
+ * Author:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * Copyright (C) 2008 Johan Engelen
+ *
+ * Released under GNU GPL
+ */
+
+#include <2geom/hvlinesegment.h>
+
+inline bool is_straight_curve(Geom::Curve const & c) {
+ if( dynamic_cast<Geom::LineSegment const*>(&c) ||
+ dynamic_cast<Geom::HLineSegment const*>(&c) ||
+ dynamic_cast<Geom::VLineSegment const*>(&c) )
+ {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+#endif // INKSCAPE_HELPER_GEOM_CURVES_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
index d75941b122d55151658dca43b96fe29293d98449..4cf3e35e67123254141b69dcb8a4ccfc89c353ce 100644 (file)
-#define INKSCAPE_LPE_ROUGH_HATCHES_CPP\r
-/** \file\r
- * LPE Curve Stitching implementation, used as an example for a base starting class\r
- * when implementing new LivePathEffects.\r
- *\r
- */\r
-/*\r
- * Authors:\r
- * JF Barraud.\r
-*\r
-* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>\r
- *\r
- * Released under GNU GPL, read the file 'COPYING' for more information\r
- */\r
-\r
-#include "live_effects/lpe-rough-hatches.h"\r
-\r
-#include "sp-item.h"\r
-#include "sp-path.h"\r
-#include "svg/svg.h"\r
-#include "xml/repr.h"\r
-\r
-#include <2geom/path.h>\r
-#include <2geom/piecewise.h>\r
-#include <2geom/sbasis.h>\r
-#include <2geom/sbasis-math.h>\r
-#include <2geom/sbasis-geometric.h>\r
-#include <2geom/bezier-to-sbasis.h>\r
-#include <2geom/sbasis-to-bezier.h>\r
-#include <2geom/d2.h>\r
-#include <2geom/matrix.h>\r
-\r
-#include "ui/widget/scalar.h"\r
-#include "libnr/nr-values.h"\r
-\r
-namespace Inkscape {\r
-namespace LivePathEffect {\r
-\r
-using namespace Geom;\r
-\r
-//------------------------------------------------\r
-// Some goodies to navigate through curve's levels.\r
-//------------------------------------------------\r
-struct LevelCrossing{\r
- Point pt;\r
- double t;\r
- bool sign;\r
- bool used;\r
- std::pair<unsigned,unsigned> next_on_curve;\r
- std::pair<unsigned,unsigned> prev_on_curve;\r
-};\r
-struct LevelCrossingOrder {\r
- bool operator()(LevelCrossing a, LevelCrossing b) {\r
- return ( a.pt[Y] < b.pt[Y] );// a.pt[X] == b.pt[X] since we are supposed to be on the same level...\r
- //return ( a.pt[X] < b.pt[X] || ( a.pt[X] == b.pt[X] && a.pt[Y] < b.pt[Y] ) );\r
- }\r
-};\r
-struct LevelCrossingInfo{\r
- double t;\r
- unsigned level;\r
- unsigned idx;\r
-};\r
-struct LevelCrossingInfoOrder {\r
- bool operator()(LevelCrossingInfo a, LevelCrossingInfo b) {\r
- return a.t < b.t;\r
- }\r
-};\r
-\r
-typedef std::vector<LevelCrossing> LevelCrossings;\r
-\r
-std::vector<double>\r
-discontinuities(Piecewise<D2<SBasis> > const &f){\r
- std::vector<double> result;\r
- if (f.size()==0) return result;\r
- result.push_back(f.cuts[0]);\r
- Point prev_pt = f.segs[0].at1();\r
- //double old_t = f.cuts[0];\r
- for(unsigned i=1; i<f.size(); i++){\r
- if ( f.segs[i].at0()!=prev_pt){\r
- result.push_back(f.cuts[i]);\r
- //old_t = f.cuts[i];\r
- //assert(f.segs[i-1].at1()==f.valueAt(old_t));\r
- }\r
- prev_pt = f.segs[i].at1();\r
- }\r
- result.push_back(f.cuts.back());\r
- //assert(f.segs.back().at1()==f.valueAt(old_t));\r
- return result;\r
-}\r
-\r
-class LevelsCrossings: public std::vector<LevelCrossings>{\r
-public:\r
- LevelsCrossings():std::vector<LevelCrossings>(){};\r
- LevelsCrossings(std::vector<std::vector<double> > const ×,\r
- Piecewise<D2<SBasis> > const &f,\r
- Piecewise<SBasis> const &dx){\r
- \r
- for (unsigned i=0; i<times.size(); i++){\r
- LevelCrossings lcs;\r
- for (unsigned j=0; j<times[i].size(); j++){\r
- LevelCrossing lc;\r
- lc.pt = f.valueAt(times[i][j]);\r
- lc.t = times[i][j];\r
- lc.sign = ( dx.valueAt(times[i][j])>0 );\r
- lc.used = false;\r
- lcs.push_back(lc);\r
- }\r
- std::sort(lcs.begin(), lcs.end(), LevelCrossingOrder());\r
- push_back(lcs);\r
- }\r
- //Now create time ordering.\r
- std::vector<LevelCrossingInfo>temp;\r
- for (unsigned i=0; i<size(); i++){\r
- for (unsigned j=0; j<(*this)[i].size(); j++){\r
- LevelCrossingInfo elem;\r
- elem.t = (*this)[i][j].t;\r
- elem.level = i;\r
- elem.idx = j;\r
- temp.push_back(elem);\r
- }\r
- }\r
- std::sort(temp.begin(),temp.end(),LevelCrossingInfoOrder());\r
- std::vector<double> jumps = discontinuities(f);\r
- unsigned jump_idx = 0;\r
- unsigned first_in_comp = 0;\r
- for (unsigned i=0; i<temp.size(); i++){\r
- unsigned lvl = temp[i].level, idx = temp[i].idx;\r
- if ( i == temp.size()-1 || temp[i+1].t > jumps[jump_idx+1]){\r
- std::pair<unsigned,unsigned>next_data(temp[first_in_comp].level,temp[first_in_comp].idx);\r
- (*this)[lvl][idx].next_on_curve = next_data;\r
- first_in_comp = i+1;\r
- jump_idx += 1;\r
- }else{\r
- std::pair<unsigned,unsigned> next_data(temp[i+1].level,temp[i+1].idx);\r
- (*this)[lvl][idx].next_on_curve = next_data;\r
- }\r
- }\r
-\r
- for (unsigned i=0; i<size(); i++){\r
- for (unsigned j=0; j<(*this)[i].size(); j++){\r
- std::pair<unsigned,unsigned> next = (*this)[i][j].next_on_curve;\r
- (*this)[next.first][next.second].prev_on_curve = std::pair<unsigned,unsigned>(i,j);\r
- }\r
- }\r
- }\r
-\r
- void findFirstUnused(unsigned &level, unsigned &idx){\r
- level = size();\r
- idx = 0;\r
- for (unsigned i=0; i<size(); i++){\r
- for (unsigned j=0; j<(*this)[i].size(); j++){\r
- if (!(*this)[i][j].used){\r
- level = i;\r
- idx = j;\r
- return;\r
- }\r
- }\r
- }\r
- }\r
- //set indexes to point to the next point in the "snake walk"\r
- //follow_level's meaning: \r
- // 0=yes upward\r
- // 1=no, last move was upward,\r
- // 2=yes downward\r
- // 3=no, last move was downward.\r
- void step(unsigned &level, unsigned &idx, int &direction){\r
- if ( direction % 2 == 0 ){\r
- if (direction == 0) {\r
- if ( idx >= (*this)[level].size()-1 || (*this)[level][idx+1].used ) {\r
- level = size();\r
- return;\r
- }\r
- idx += 1;\r
- }else{\r
- if ( idx <= 0 || (*this)[level][idx-1].used ) {\r
- level = size();\r
- return;\r
- }\r
- idx -= 1;\r
- }\r
- direction += 1;\r
- return;\r
- }\r
- double t = (*this)[level][idx].t;\r
- double sign = ((*this)[level][idx].sign ? 1 : -1);\r
- //---double next_t = t;\r
- //level += 1;\r
- direction = (direction + 1)%4;\r
- if (level == size()){\r
- return;\r
- }\r
-\r
- std::pair<unsigned,unsigned> next;\r
- if ( sign > 0 ){\r
- next = (*this)[level][idx].next_on_curve;\r
- }else{\r
- next = (*this)[level][idx].prev_on_curve;\r
- }\r
-\r
- if ( level+1 != next.first || (*this)[next.first][next.second].used ) {\r
- level = size();\r
- return;\r
- }\r
- level = next.first;\r
- idx = next.second;\r
- return;\r
- }\r
-};\r
-\r
-//-------------------------------------------------------\r
-// Bend a path...\r
-//-------------------------------------------------------\r
-\r
-Piecewise<D2<SBasis> > bend(Piecewise<D2<SBasis> > const &f, Piecewise<SBasis> bending){\r
- D2<Piecewise<SBasis> > ff = make_cuts_independent(f);\r
- ff[X] += compose(bending, ff[Y]);\r
- return sectionize(ff);\r
-}\r
-\r
-//--------------------------------------------------------\r
-// The RoughHatches lpe.\r
-//--------------------------------------------------------\r
-LPERoughHatches::LPERoughHatches(LivePathEffectObject *lpeobject) :\r
- Effect(lpeobject),\r
- direction(_("Hatches width and dir"), _("Defines hatches frequency and direction"), "direction", &wr, this, Geom::Point(50,0)),\r
- dist_rdm(_("Frequency randomness"), _("Variation of dist between hatches, in %."), "dist_rdm", &wr, this, 75),\r
- growth(_("Growth"), _("Growth of distance between hatches."), "growth", &wr, this, 0.),\r
-//FIXME: top/bottom names are inverted in the UI/svg and in the code!!\r
- scale_tf(_("Half turns smoothness: 1st side, in"), _("Set smoothness/sharpness of path when reaching a 'bottom' halfturn. 0=sharp, 1=default"), "scale_bf", &wr, this, 1.),\r
- scale_tb(_("1st side, out"), _("Set smoothness/sharpness of path when leaving a 'bottom' halfturn. 0=sharp, 1=default"), "scale_bb", &wr, this, 1.),\r
- scale_bf(_("2nd side, in "), _("Set smoothness/sharpness of path when reaching a 'top' halfturn. 0=sharp, 1=default"), "scale_tf", &wr, this, 1.),\r
- scale_bb(_("2nd side, out"), _("Set smoothness/sharpness of path when leaving a 'top' halfturn. 0=sharp, 1=default"), "scale_tb", &wr, this, 1.),\r
- top_smth_variation(_("variance: 1st side"), _("Randomness of 'bottom' halfturns smoothness"), "top_smth_variation", &wr, this, 0),\r
- bot_smth_variation(_("2nd side"), _("Randomness of 'top' halfturns smoothness"), "bottom_smth_variation", &wr, this, 0),\r
-//\r
- top_edge_variation(_("Magnitude jitter: 1st side"), _("Randomly moves 'bottom' halfsturns to produce magnitude variations."), "bottom_edge_variation", &wr, this, 0),\r
- bot_edge_variation(_("2nd side"), _("Randomly moves 'top' halfsturns to produce magnitude variations."), "top_edge_variation", &wr, this, 0),\r
- top_tgt_variation(_("Parallelism jitter: 1st side"), _("Add direction randomness by moving 'bottom' halfsturns tangentially to the boundary."), "bottom_tgt_variation", &wr, this, 0),\r
- bot_tgt_variation(_("2nd side"), _("Add direction randomness by randomly moving 'top' halfsturns tangentially to the boundary."), "top_tgt_variation", &wr, this, 0),\r
-//\r
- do_bend(_("Bend hatches"), _("Add a global bend to the hatches (slower)"), "do_bend", &wr, this, true),\r
- //bender(_("Global bending"), _("Relative position to ref point defines global bending direction and amount"), "bender", &wr, this, NULL, Geom::Point(-5,0)),\r
- bender(_("Global bending"), _("Relative position to ref point defines global bending direction and amount"), "bender", &wr, this, Geom::Point(-5,0)),\r
-//\r
- fat_output(_("Generate thick/thin path"), _("Simulate a stroke of varrying width"), "fat_output", &wr, this, true),\r
- stroke_width_top(_("Thikness: at 1st side"), _("Width at 'bottom' half turns"), "stroke_width_top", &wr, this, 1.),\r
- stroke_width_bot(_("at 2nd side"), _("Width at 'top' halfturns"), "stroke_width_bottom", &wr, this, 1.),\r
- front_thickness(_("from 2nd to 1st side"), _("Width of paths from 'top' to 'bottom' halfturns"), "front_thickness", &wr, this, 1.),\r
- back_thickness(_("from 1st to 2nd side"), _("Width of paths from 'top' to 'bottom' halfturns"), "back_thickness", &wr, this, .25)\r
-{\r
- registerParameter( dynamic_cast<Parameter *>(&direction) );\r
- registerParameter( dynamic_cast<Parameter *>(&dist_rdm) );\r
- registerParameter( dynamic_cast<Parameter *>(&growth) );\r
- registerParameter( dynamic_cast<Parameter *>(&do_bend) );\r
- registerParameter( dynamic_cast<Parameter *>(&bender) );\r
- registerParameter( dynamic_cast<Parameter *>(&top_edge_variation) );\r
- registerParameter( dynamic_cast<Parameter *>(&bot_edge_variation) );\r
- registerParameter( dynamic_cast<Parameter *>(&top_tgt_variation) );\r
- registerParameter( dynamic_cast<Parameter *>(&bot_tgt_variation) );\r
- registerParameter( dynamic_cast<Parameter *>(&scale_tf) );\r
- registerParameter( dynamic_cast<Parameter *>(&scale_tb) );\r
- registerParameter( dynamic_cast<Parameter *>(&scale_bf) );\r
- registerParameter( dynamic_cast<Parameter *>(&scale_bb) );\r
- registerParameter( dynamic_cast<Parameter *>(&top_smth_variation) );\r
- registerParameter( dynamic_cast<Parameter *>(&bot_smth_variation) );\r
- registerParameter( dynamic_cast<Parameter *>(&fat_output) );\r
- registerParameter( dynamic_cast<Parameter *>(&stroke_width_top) );\r
- registerParameter( dynamic_cast<Parameter *>(&stroke_width_bot) );\r
- registerParameter( dynamic_cast<Parameter *>(&front_thickness) );\r
- registerParameter( dynamic_cast<Parameter *>(&back_thickness) );\r
-\r
- //hatch_dist.param_set_range(0.1, NR_HUGE);\r
- growth.param_set_range(0, NR_HUGE);\r
- dist_rdm.param_set_range(0, 99.);\r
- stroke_width_top.param_set_range(0, NR_HUGE);\r
- stroke_width_bot.param_set_range(0, NR_HUGE);\r
- front_thickness.param_set_range(0, NR_HUGE);\r
- back_thickness.param_set_range(0, NR_HUGE);\r
-\r
- concatenate_before_pwd2 = false;\r
- show_orig_path = true;\r
-}\r
-\r
-LPERoughHatches::~LPERoughHatches()\r
-{\r
-\r
-}\r
-\r
-Geom::Piecewise<Geom::D2<Geom::SBasis> > \r
-LPERoughHatches::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in){\r
-\r
- //std::cout<<"doEffect_pwd2:\n";\r
-\r
- Piecewise<D2<SBasis> > result;\r
- \r
- Piecewise<D2<SBasis> > transformed_pwd2_in = pwd2_in;\r
- Point start = pwd2_in.segs.front().at0();\r
- Point end = pwd2_in.segs.back().at1();\r
- if (end != start ){\r
- transformed_pwd2_in.push_cut( transformed_pwd2_in.cuts.back() + 1 );\r
- D2<SBasis> stitch( SBasis( 1, Linear(end[X],start[X]) ), SBasis( 1, Linear(end[Y],start[Y]) ) );\r
- transformed_pwd2_in.push_seg( stitch );\r
- }\r
- Point transformed_org = direction.getOrigin();\r
- Piecewise<SBasis> tilter;//used to bend the hatches\r
- Matrix bend_mat;//used to bend the hatches\r
-\r
- if (do_bend.get_value()){\r
- Point bend_dir = -rot90(unit_vector(bender.getVector()));\r
- double bend_amount = L2(bender.getVector());\r
- bend_mat = Matrix(-bend_dir[Y], bend_dir[X], bend_dir[X], bend_dir[Y],0,0);\r
- transformed_pwd2_in = transformed_pwd2_in * bend_mat;\r
- tilter = Piecewise<SBasis>(shift(Linear(-bend_amount),1));\r
- OptRect bbox = bounds_exact( transformed_pwd2_in );\r
- if (not(bbox)) return pwd2_in;\r
- tilter.setDomain((*bbox)[Y]);\r
- transformed_pwd2_in = bend(transformed_pwd2_in, tilter);\r
- transformed_pwd2_in = transformed_pwd2_in * bend_mat.inverse();\r
- }\r
- hatch_dist = Geom::L2(direction.getVector())/5;\r
- Point hatches_dir = rot90(unit_vector(direction.getVector()));\r
- Matrix mat(-hatches_dir[Y], hatches_dir[X], hatches_dir[X], hatches_dir[Y],0,0);\r
- transformed_pwd2_in = transformed_pwd2_in * mat;\r
- transformed_org *= mat;\r
- \r
- std::vector<std::vector<Point> > snakePoints;\r
- snakePoints = linearSnake(transformed_pwd2_in, transformed_org);\r
- if ( snakePoints.size() > 0 ){\r
- Piecewise<D2<SBasis> >smthSnake = smoothSnake(snakePoints); \r
- smthSnake = smthSnake*mat.inverse();\r
- if (do_bend.get_value()){\r
- smthSnake = smthSnake*bend_mat;\r
- smthSnake = bend(smthSnake, -tilter);\r
- smthSnake = smthSnake*bend_mat.inverse();\r
- }\r
- return (smthSnake);\r
- }\r
- return pwd2_in;\r
-}\r
-\r
-//------------------------------------------------\r
-// Generate the levels with random, growth...\r
-//------------------------------------------------\r
-std::vector<double>\r
-LPERoughHatches::generateLevels(Interval const &domain, double x_org){\r
- std::vector<double> result;\r
- int n = int((domain.min()-x_org)/hatch_dist);\r
- double x = x_org + n * hatch_dist;\r
- //double x = domain.min() + double(hatch_dist)/2.;\r
- double step = double(hatch_dist);\r
- double scale = 1+(hatch_dist*growth/domain.extent());\r
- while (x < domain.max()){\r
- result.push_back(x);\r
- double rdm = 1;\r
- if (dist_rdm.get_value() != 0) \r
- rdm = 1.+ double((2*dist_rdm - dist_rdm.get_value()))/100.;\r
- x+= step*rdm;\r
- step*=scale;//(1.+double(growth));\r
- }\r
- return result;\r
-}\r
-\r
-\r
-//-------------------------------------------------------\r
-// Walk through the intersections to create linear hatches\r
-//-------------------------------------------------------\r
-std::vector<std::vector<Point> > \r
-LPERoughHatches::linearSnake(Piecewise<D2<SBasis> > const &f, Point const &org){\r
-\r
- //std::cout<<"linearSnake:\n";\r
- std::vector<std::vector<Point> > result;\r
- Piecewise<SBasis> x = make_cuts_independent(f)[X];\r
- //Remark: derivative is computed twice in the 2 lines below!!\r
- Piecewise<SBasis> dx = derivative(x);\r
- OptInterval range = bounds_exact(x);\r
-\r
- if (not range) return result;\r
- std::vector<double> levels = generateLevels(*range, org[X]);\r
- std::vector<std::vector<double> > times;\r
- times = multi_roots(x,levels);\r
-//TODO: fix multi_roots!!!*****************************************\r
-//remove doubles :-(\r
- std::vector<std::vector<double> > cleaned_times(levels.size(),std::vector<double>());\r
- for (unsigned i=0; i<times.size(); i++){\r
- if ( times[i].size()>0 ){\r
- double last_t = times[i][0]-1;//ugly hack!!\r
- for (unsigned j=0; j<times[i].size(); j++){\r
- if (times[i][j]-last_t >0.000001){\r
- last_t = times[i][j];\r
- cleaned_times[i].push_back(last_t);\r
- }\r
- }\r
- }\r
- }\r
- times = cleaned_times;\r
-//*******************************************************************\r
-\r
- LevelsCrossings lscs(times,f,dx);\r
-\r
- unsigned i,j;\r
- lscs.findFirstUnused(i,j);\r
- \r
- std::vector<Point> result_component;\r
- int n = int((range->min()-org[X])/hatch_dist);\r
- \r
- while ( i < lscs.size() ){ \r
- int dir = 0;\r
- //switch orientation of first segment according to starting point.\r
- if (i % 2 == n%2 && j < lscs[i].size()-1 && !lscs[i][j].used){\r
- j += 1;\r
- dir = 2;\r
- }\r
-\r
- while ( i < lscs.size() ){\r
- result_component.push_back(lscs[i][j].pt);\r
- lscs[i][j].used = true;\r
- lscs.step(i,j, dir);\r
- }\r
- result.push_back(result_component);\r
- result_component = std::vector<Point>();\r
- lscs.findFirstUnused(i,j);\r
- }\r
- return result;\r
-}\r
-\r
-//-------------------------------------------------------\r
-// Smooth the linear hatches according to params...\r
-//-------------------------------------------------------\r
-Piecewise<D2<SBasis> > \r
-LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake){\r
-\r
- Piecewise<D2<SBasis> > result;\r
- for (unsigned comp=0; comp<linearSnake.size(); comp++){\r
- if (linearSnake[comp].size()>=2){\r
- Point last_pt = linearSnake[comp][0];\r
- Point last_top = linearSnake[comp][0];\r
- Point last_bot = linearSnake[comp][0];\r
- Point last_hdle = linearSnake[comp][0];\r
- Point last_top_hdle = linearSnake[comp][0];\r
- Point last_bot_hdle = linearSnake[comp][0];\r
- Geom::Path res_comp(last_pt);\r
- Geom::Path res_comp_top(last_pt);\r
- Geom::Path res_comp_bot(last_pt);\r
- unsigned i=1;\r
- //bool is_top = true;//Inversion here; due to downward y? \r
- bool is_top = ( linearSnake[comp][0][Y] < linearSnake[comp][1][Y] );\r
-\r
- while( i+1<linearSnake[comp].size() ){\r
- Point pt0 = linearSnake[comp][i];\r
- Point pt1 = linearSnake[comp][i+1];\r
- Point new_pt = (pt0+pt1)/2;\r
- double scale_in = (is_top ? scale_tf : scale_bf );\r
- double scale_out = (is_top ? scale_tb : scale_bb );\r
- if (is_top){\r
- if (top_edge_variation.get_value() != 0) \r
- new_pt[Y] += double(top_edge_variation)-top_edge_variation.get_value()/2.;\r
- if (top_tgt_variation.get_value() != 0) \r
- new_pt[X] += double(top_tgt_variation)-top_tgt_variation.get_value()/2.;\r
- if (top_smth_variation.get_value() != 0) {\r
- scale_in*=(100.-double(top_smth_variation))/100.;\r
- scale_out*=(100.-double(top_smth_variation))/100.;\r
- }\r
- }else{\r
- if (bot_edge_variation.get_value() != 0) \r
- new_pt[Y] += double(bot_edge_variation)-bot_edge_variation.get_value()/2.;\r
- if (bot_tgt_variation.get_value() != 0) \r
- new_pt[X] += double(bot_tgt_variation)-bot_tgt_variation.get_value()/2.;\r
- if (bot_smth_variation.get_value() != 0) {\r
- scale_in*=(100.-double(bot_smth_variation))/100.;\r
- scale_out*=(100.-double(bot_smth_variation))/100.;\r
- }\r
- }\r
- Point new_hdle_in = new_pt + (pt0-pt1) * (scale_in /2.);\r
- Point new_hdle_out = new_pt - (pt0-pt1) * (scale_out/2.);\r
- \r
- if ( fat_output.get_value() ){\r
- //double scaled_width = double((is_top ? stroke_width_top : stroke_width_bot))/(pt1[X]-pt0[X]);\r
- double scaled_width = 1./(pt1[X]-pt0[X]);\r
- Point hdle_offset = (pt1-pt0)*scaled_width;\r
- Point inside = new_pt;\r
- Point inside_hdle_in;\r
- Point inside_hdle_out;\r
- inside[Y]+= double((is_top ? -stroke_width_top : stroke_width_bot));\r
- inside_hdle_in = inside + (new_hdle_in -new_pt);// + hdle_offset * double((is_top ? front_thickness : back_thickness));\r
- inside_hdle_out = inside + (new_hdle_out-new_pt);// - hdle_offset * double((is_top ? back_thickness : front_thickness));\r
-\r
- inside_hdle_in += (pt1-pt0)/2*( double((is_top ? front_thickness : back_thickness)) / (pt1[X]-pt0[X]) );\r
- inside_hdle_out -= (pt1-pt0)/2*( double((is_top ? back_thickness : front_thickness)) / (pt1[X]-pt0[X]) );\r
-\r
- new_hdle_in -= (pt1-pt0)/2*( double((is_top ? front_thickness : back_thickness)) / (pt1[X]-pt0[X]) );\r
- new_hdle_out += (pt1-pt0)/2*( double((is_top ? back_thickness : front_thickness)) / (pt1[X]-pt0[X]) );\r
- //TODO: find a good way to handle limit cases (small smthness, large stroke).\r
- //if (inside_hdle_in[X] > inside[X]) inside_hdle_in = inside;\r
- //if (inside_hdle_out[X] < inside[X]) inside_hdle_out = inside;\r
- \r
- if (is_top){\r
- res_comp_top.appendNew<CubicBezier>(last_top_hdle,new_hdle_in,new_pt);\r
- res_comp_bot.appendNew<CubicBezier>(last_bot_hdle,inside_hdle_in,inside);\r
- last_top_hdle = new_hdle_out;\r
- last_bot_hdle = inside_hdle_out;\r
- }else{\r
- res_comp_top.appendNew<CubicBezier>(last_top_hdle,inside_hdle_in,inside);\r
- res_comp_bot.appendNew<CubicBezier>(last_bot_hdle,new_hdle_in,new_pt);\r
- last_top_hdle = inside_hdle_out;\r
- last_bot_hdle = new_hdle_out;\r
- }\r
- }else{\r
- res_comp.appendNew<CubicBezier>(last_hdle,new_hdle_in,new_pt);\r
- }\r
- \r
- last_hdle = new_hdle_out;\r
- i+=2;\r
- is_top = !is_top;\r
- }\r
- if ( i<linearSnake[comp].size() ){\r
- if ( fat_output.get_value() ){\r
- res_comp_top.appendNew<CubicBezier>(last_top_hdle,linearSnake[comp][i],linearSnake[comp][i]);\r
- res_comp_bot.appendNew<CubicBezier>(last_bot_hdle,linearSnake[comp][i],linearSnake[comp][i]);\r
- }else{\r
- res_comp.appendNew<CubicBezier>(last_hdle,linearSnake[comp][i],linearSnake[comp][i]);\r
- }\r
- }\r
- if ( fat_output.get_value() ){\r
- res_comp = res_comp_bot;\r
- res_comp.append(res_comp_top.reverse(),Geom::Path::STITCH_DISCONTINUOUS);\r
- } \r
- result.concat(res_comp.toPwSb());\r
- }\r
- }\r
- return result;\r
-}\r
-\r
-void\r
-LPERoughHatches::doBeforeEffect (SPLPEItem */*lpeitem*/)\r
-{\r
- using namespace Geom;\r
- top_edge_variation.resetRandomizer();\r
- bot_edge_variation.resetRandomizer();\r
- top_tgt_variation.resetRandomizer();\r
- bot_tgt_variation.resetRandomizer();\r
- top_smth_variation.resetRandomizer();\r
- bot_smth_variation.resetRandomizer();\r
- dist_rdm.resetRandomizer();\r
-\r
- //original_bbox(lpeitem);\r
-}\r
-\r
-\r
-void\r
-LPERoughHatches::resetDefaults(SPItem * item)\r
-{\r
- Effect::resetDefaults(item);\r
-\r
- Geom::OptRect bbox = item->getBounds(Geom::identity(), SPItem::GEOMETRIC_BBOX);\r
- Geom::Point origin(0.,0.);\r
- Geom::Point vector(50.,0.);\r
- if (bbox) {\r
- origin = bbox->midpoint();\r
- vector = Geom::Point((*bbox)[X].extent()/4, 0.);\r
- top_edge_variation.param_set_value( (*bbox)[Y].extent()/10, 0 );\r
- bot_edge_variation.param_set_value( (*bbox)[Y].extent()/10, 0 );\r
- top_edge_variation.write_to_SVG();\r
- bot_edge_variation.write_to_SVG();\r
- }\r
- //direction.set_and_write_new_values(origin, vector);\r
- //bender.param_set_and_write_new_value( origin + Geom::Point(5,0) );\r
- direction.set_and_write_new_values(origin + Geom::Point(0,-5), vector);\r
- bender.set_and_write_new_values( origin, Geom::Point(5,0) );\r
- hatch_dist = Geom::L2(vector)/2;\r
-}\r
-\r
-\r
-} //namespace LivePathEffect\r
-} /* namespace Inkscape */\r
-\r
-/*\r
- Local Variables:\r
- mode:c++\r
- c-file-style:"stroustrup"\r
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
- indent-tabs-mode:nil\r
- fill-column:99\r
- End:\r
-*/\r
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :\r
+#define INKSCAPE_LPE_ROUGH_HATCHES_CPP
+/** \file
+ * LPE Curve Stitching implementation, used as an example for a base starting class
+ * when implementing new LivePathEffects.
+ *
+ */
+/*
+ * Authors:
+ * JF Barraud.
+*
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/lpe-rough-hatches.h"
+
+#include "sp-item.h"
+#include "sp-path.h"
+#include "svg/svg.h"
+#include "xml/repr.h"
+
+#include <2geom/path.h>
+#include <2geom/piecewise.h>
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-math.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/d2.h>
+#include <2geom/matrix.h>
+
+#include "ui/widget/scalar.h"
+#include "libnr/nr-values.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+using namespace Geom;
+
+//------------------------------------------------
+// Some goodies to navigate through curve's levels.
+//------------------------------------------------
+struct LevelCrossing{
+ Point pt;
+ double t;
+ bool sign;
+ bool used;
+ std::pair<unsigned,unsigned> next_on_curve;
+ std::pair<unsigned,unsigned> prev_on_curve;
+};
+struct LevelCrossingOrder {
+ bool operator()(LevelCrossing a, LevelCrossing b) {
+ return ( a.pt[Y] < b.pt[Y] );// a.pt[X] == b.pt[X] since we are supposed to be on the same level...
+ //return ( a.pt[X] < b.pt[X] || ( a.pt[X] == b.pt[X] && a.pt[Y] < b.pt[Y] ) );
+ }
+};
+struct LevelCrossingInfo{
+ double t;
+ unsigned level;
+ unsigned idx;
+};
+struct LevelCrossingInfoOrder {
+ bool operator()(LevelCrossingInfo a, LevelCrossingInfo b) {
+ return a.t < b.t;
+ }
+};
+
+typedef std::vector<LevelCrossing> LevelCrossings;
+
+std::vector<double>
+discontinuities(Piecewise<D2<SBasis> > const &f){
+ std::vector<double> result;
+ if (f.size()==0) return result;
+ result.push_back(f.cuts[0]);
+ Point prev_pt = f.segs[0].at1();
+ //double old_t = f.cuts[0];
+ for(unsigned i=1; i<f.size(); i++){
+ if ( f.segs[i].at0()!=prev_pt){
+ result.push_back(f.cuts[i]);
+ //old_t = f.cuts[i];
+ //assert(f.segs[i-1].at1()==f.valueAt(old_t));
+ }
+ prev_pt = f.segs[i].at1();
+ }
+ result.push_back(f.cuts.back());
+ //assert(f.segs.back().at1()==f.valueAt(old_t));
+ return result;
+}
+
+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 (unsigned i=0; i<times.size(); i++){
+ LevelCrossings lcs;
+ for (unsigned j=0; j<times[i].size(); j++){
+ LevelCrossing lc;
+ lc.pt = f.valueAt(times[i][j]);
+ lc.t = times[i][j];
+ lc.sign = ( dx.valueAt(times[i][j])>0 );
+ lc.used = false;
+ lcs.push_back(lc);
+ }
+ std::sort(lcs.begin(), lcs.end(), LevelCrossingOrder());
+ push_back(lcs);
+ }
+ //Now create time ordering.
+ std::vector<LevelCrossingInfo>temp;
+ for (unsigned i=0; i<size(); i++){
+ for (unsigned j=0; j<(*this)[i].size(); j++){
+ LevelCrossingInfo elem;
+ elem.t = (*this)[i][j].t;
+ elem.level = i;
+ elem.idx = j;
+ temp.push_back(elem);
+ }
+ }
+ std::sort(temp.begin(),temp.end(),LevelCrossingInfoOrder());
+ std::vector<double> jumps = discontinuities(f);
+ unsigned jump_idx = 0;
+ unsigned first_in_comp = 0;
+ for (unsigned i=0; i<temp.size(); i++){
+ unsigned lvl = temp[i].level, idx = temp[i].idx;
+ if ( i == temp.size()-1 || temp[i+1].t > jumps[jump_idx+1]){
+ std::pair<unsigned,unsigned>next_data(temp[first_in_comp].level,temp[first_in_comp].idx);
+ (*this)[lvl][idx].next_on_curve = next_data;
+ first_in_comp = i+1;
+ jump_idx += 1;
+ }else{
+ std::pair<unsigned,unsigned> next_data(temp[i+1].level,temp[i+1].idx);
+ (*this)[lvl][idx].next_on_curve = next_data;
+ }
+ }
+
+ for (unsigned i=0; i<size(); i++){
+ for (unsigned j=0; j<(*this)[i].size(); j++){
+ std::pair<unsigned,unsigned> next = (*this)[i][j].next_on_curve;
+ (*this)[next.first][next.second].prev_on_curve = std::pair<unsigned,unsigned>(i,j);
+ }
+ }
+ }
+
+ 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){
+ if ( direction % 2 == 0 ){
+ if (direction == 0) {
+ if ( idx >= (*this)[level].size()-1 || (*this)[level][idx+1].used ) {
+ level = size();
+ return;
+ }
+ idx += 1;
+ }else{
+ if ( idx <= 0 || (*this)[level][idx-1].used ) {
+ level = size();
+ return;
+ }
+ idx -= 1;
+ }
+ direction += 1;
+ 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()){
+ return;
+ }
+
+ std::pair<unsigned,unsigned> next;
+ if ( sign > 0 ){
+ next = (*this)[level][idx].next_on_curve;
+ }else{
+ next = (*this)[level][idx].prev_on_curve;
+ }
+
+ if ( level+1 != next.first || (*this)[next.first][next.second].used ) {
+ level = size();
+ return;
+ }
+ level = next.first;
+ idx = next.second;
+ return;
+ }
+};
+
+//-------------------------------------------------------
+// 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 RoughHatches lpe.
+//--------------------------------------------------------
+LPERoughHatches::LPERoughHatches(LivePathEffectObject *lpeobject) :
+ Effect(lpeobject),
+ direction(_("Hatches width and dir"), _("Defines hatches frequency and direction"), "direction", &wr, this, Geom::Point(50,0)),
+ dist_rdm(_("Frequency randomness"), _("Variation of dist between hatches, in %."), "dist_rdm", &wr, this, 75),
+ growth(_("Growth"), _("Growth of distance between hatches."), "growth", &wr, this, 0.),
+//FIXME: top/bottom names are inverted in the UI/svg and in the code!!
+ scale_tf(_("Half turns smoothness: 1st side, in"), _("Set smoothness/sharpness of path when reaching a 'bottom' halfturn. 0=sharp, 1=default"), "scale_bf", &wr, this, 1.),
+ scale_tb(_("1st side, out"), _("Set smoothness/sharpness of path when leaving a 'bottom' halfturn. 0=sharp, 1=default"), "scale_bb", &wr, this, 1.),
+ scale_bf(_("2nd side, in "), _("Set smoothness/sharpness of path when reaching a 'top' halfturn. 0=sharp, 1=default"), "scale_tf", &wr, this, 1.),
+ scale_bb(_("2nd side, out"), _("Set smoothness/sharpness of path when leaving a 'top' halfturn. 0=sharp, 1=default"), "scale_tb", &wr, this, 1.),
+ top_smth_variation(_("variance: 1st side"), _("Randomness of 'bottom' halfturns smoothness"), "top_smth_variation", &wr, this, 0),
+ bot_smth_variation(_("2nd side"), _("Randomness of 'top' halfturns smoothness"), "bottom_smth_variation", &wr, this, 0),
+//
+ top_edge_variation(_("Magnitude jitter: 1st side"), _("Randomly moves 'bottom' halfsturns to produce magnitude variations."), "bottom_edge_variation", &wr, this, 0),
+ bot_edge_variation(_("2nd side"), _("Randomly moves 'top' halfsturns to produce magnitude variations."), "top_edge_variation", &wr, this, 0),
+ top_tgt_variation(_("Parallelism jitter: 1st side"), _("Add direction randomness by moving 'bottom' halfsturns tangentially to the boundary."), "bottom_tgt_variation", &wr, this, 0),
+ bot_tgt_variation(_("2nd side"), _("Add direction randomness by randomly moving 'top' halfsturns tangentially to the boundary."), "top_tgt_variation", &wr, this, 0),
+//
+ do_bend(_("Bend hatches"), _("Add a global bend to the hatches (slower)"), "do_bend", &wr, this, true),
+ //bender(_("Global bending"), _("Relative position to ref point defines global bending direction and amount"), "bender", &wr, this, NULL, Geom::Point(-5,0)),
+ bender(_("Global bending"), _("Relative position to ref point defines global bending direction and amount"), "bender", &wr, this, Geom::Point(-5,0)),
+//
+ fat_output(_("Generate thick/thin path"), _("Simulate a stroke of varrying width"), "fat_output", &wr, this, true),
+ stroke_width_top(_("Thikness: at 1st side"), _("Width at 'bottom' half turns"), "stroke_width_top", &wr, this, 1.),
+ stroke_width_bot(_("at 2nd side"), _("Width at 'top' halfturns"), "stroke_width_bottom", &wr, this, 1.),
+ front_thickness(_("from 2nd to 1st side"), _("Width of paths from 'top' to 'bottom' halfturns"), "front_thickness", &wr, this, 1.),
+ back_thickness(_("from 1st to 2nd side"), _("Width of paths from 'top' to 'bottom' halfturns"), "back_thickness", &wr, this, .25)
+{
+ registerParameter( dynamic_cast<Parameter *>(&direction) );
+ registerParameter( dynamic_cast<Parameter *>(&dist_rdm) );
+ registerParameter( dynamic_cast<Parameter *>(&growth) );
+ registerParameter( dynamic_cast<Parameter *>(&do_bend) );
+ registerParameter( dynamic_cast<Parameter *>(&bender) );
+ registerParameter( dynamic_cast<Parameter *>(&top_edge_variation) );
+ registerParameter( dynamic_cast<Parameter *>(&bot_edge_variation) );
+ registerParameter( dynamic_cast<Parameter *>(&top_tgt_variation) );
+ registerParameter( dynamic_cast<Parameter *>(&bot_tgt_variation) );
+ registerParameter( dynamic_cast<Parameter *>(&scale_tf) );
+ registerParameter( dynamic_cast<Parameter *>(&scale_tb) );
+ registerParameter( dynamic_cast<Parameter *>(&scale_bf) );
+ registerParameter( dynamic_cast<Parameter *>(&scale_bb) );
+ registerParameter( dynamic_cast<Parameter *>(&top_smth_variation) );
+ registerParameter( dynamic_cast<Parameter *>(&bot_smth_variation) );
+ registerParameter( dynamic_cast<Parameter *>(&fat_output) );
+ registerParameter( dynamic_cast<Parameter *>(&stroke_width_top) );
+ registerParameter( dynamic_cast<Parameter *>(&stroke_width_bot) );
+ registerParameter( dynamic_cast<Parameter *>(&front_thickness) );
+ registerParameter( dynamic_cast<Parameter *>(&back_thickness) );
+
+ //hatch_dist.param_set_range(0.1, NR_HUGE);
+ growth.param_set_range(0, NR_HUGE);
+ dist_rdm.param_set_range(0, 99.);
+ stroke_width_top.param_set_range(0, NR_HUGE);
+ stroke_width_bot.param_set_range(0, NR_HUGE);
+ front_thickness.param_set_range(0, NR_HUGE);
+ back_thickness.param_set_range(0, NR_HUGE);
+
+ concatenate_before_pwd2 = false;
+ show_orig_path = true;
+}
+
+LPERoughHatches::~LPERoughHatches()
+{
+
+}
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> >
+LPERoughHatches::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in){
+
+ //std::cout<<"doEffect_pwd2:\n";
+
+ Piecewise<D2<SBasis> > result;
+
+ Piecewise<D2<SBasis> > transformed_pwd2_in = pwd2_in;
+ Point start = pwd2_in.segs.front().at0();
+ Point end = pwd2_in.segs.back().at1();
+ if (end != start ){
+ transformed_pwd2_in.push_cut( transformed_pwd2_in.cuts.back() + 1 );
+ D2<SBasis> stitch( SBasis( 1, Linear(end[X],start[X]) ), SBasis( 1, Linear(end[Y],start[Y]) ) );
+ transformed_pwd2_in.push_seg( stitch );
+ }
+ Point transformed_org = direction.getOrigin();
+ Piecewise<SBasis> tilter;//used to bend the hatches
+ Matrix bend_mat;//used to bend the hatches
+
+ if (do_bend.get_value()){
+ Point bend_dir = -rot90(unit_vector(bender.getVector()));
+ double bend_amount = L2(bender.getVector());
+ bend_mat = Matrix(-bend_dir[Y], bend_dir[X], bend_dir[X], bend_dir[Y],0,0);
+ transformed_pwd2_in = transformed_pwd2_in * bend_mat;
+ tilter = Piecewise<SBasis>(shift(Linear(-bend_amount),1));
+ OptRect bbox = bounds_exact( transformed_pwd2_in );
+ if (not(bbox)) return pwd2_in;
+ tilter.setDomain((*bbox)[Y]);
+ transformed_pwd2_in = bend(transformed_pwd2_in, tilter);
+ transformed_pwd2_in = transformed_pwd2_in * bend_mat.inverse();
+ }
+ hatch_dist = Geom::L2(direction.getVector())/5;
+ Point hatches_dir = rot90(unit_vector(direction.getVector()));
+ Matrix mat(-hatches_dir[Y], hatches_dir[X], hatches_dir[X], hatches_dir[Y],0,0);
+ transformed_pwd2_in = transformed_pwd2_in * mat;
+ transformed_org *= mat;
+
+ std::vector<std::vector<Point> > snakePoints;
+ snakePoints = linearSnake(transformed_pwd2_in, transformed_org);
+ if ( snakePoints.size() > 0 ){
+ Piecewise<D2<SBasis> >smthSnake = smoothSnake(snakePoints);
+ smthSnake = smthSnake*mat.inverse();
+ if (do_bend.get_value()){
+ smthSnake = smthSnake*bend_mat;
+ smthSnake = bend(smthSnake, -tilter);
+ smthSnake = smthSnake*bend_mat.inverse();
+ }
+ return (smthSnake);
+ }
+ return pwd2_in;
+}
+
+//------------------------------------------------
+// Generate the levels with random, growth...
+//------------------------------------------------
+std::vector<double>
+LPERoughHatches::generateLevels(Interval const &domain, double x_org){
+ std::vector<double> result;
+ int n = int((domain.min()-x_org)/hatch_dist);
+ double x = x_org + n * hatch_dist;
+ //double x = domain.min() + double(hatch_dist)/2.;
+ double step = double(hatch_dist);
+ double scale = 1+(hatch_dist*growth/domain.extent());
+ while (x < domain.max()){
+ result.push_back(x);
+ double rdm = 1;
+ if (dist_rdm.get_value() != 0)
+ rdm = 1.+ double((2*dist_rdm - dist_rdm.get_value()))/100.;
+ x+= step*rdm;
+ step*=scale;//(1.+double(growth));
+ }
+ return result;
+}
+
+
+//-------------------------------------------------------
+// Walk through the intersections to create linear hatches
+//-------------------------------------------------------
+std::vector<std::vector<Point> >
+LPERoughHatches::linearSnake(Piecewise<D2<SBasis> > const &f, Point const &org){
+
+ //std::cout<<"linearSnake:\n";
+ std::vector<std::vector<Point> > result;
+ Piecewise<SBasis> x = make_cuts_independent(f)[X];
+ //Remark: derivative is computed twice in the 2 lines below!!
+ Piecewise<SBasis> dx = derivative(x);
+ OptInterval range = bounds_exact(x);
+
+ if (not range) return result;
+ std::vector<double> levels = generateLevels(*range, org[X]);
+ 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;
+//*******************************************************************
+
+ LevelsCrossings lscs(times,f,dx);
+
+ unsigned i,j;
+ lscs.findFirstUnused(i,j);
+
+ std::vector<Point> result_component;
+ int n = int((range->min()-org[X])/hatch_dist);
+
+ while ( i < lscs.size() ){
+ int dir = 0;
+ //switch orientation of first segment according to starting point.
+ if (i % 2 == n%2 && j < lscs[i].size()-1 && !lscs[i][j].used){
+ j += 1;
+ dir = 2;
+ }
+
+ while ( i < lscs.size() ){
+ result_component.push_back(lscs[i][j].pt);
+ lscs[i][j].used = true;
+ lscs.step(i,j, dir);
+ }
+ result.push_back(result_component);
+ result_component = std::vector<Point>();
+ lscs.findFirstUnused(i,j);
+ }
+ return result;
+}
+
+//-------------------------------------------------------
+// Smooth the linear hatches according to params...
+//-------------------------------------------------------
+Piecewise<D2<SBasis> >
+LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake){
+
+ Piecewise<D2<SBasis> > result;
+ for (unsigned comp=0; comp<linearSnake.size(); comp++){
+ if (linearSnake[comp].size()>=2){
+ Point last_pt = linearSnake[comp][0];
+ Point last_top = linearSnake[comp][0];
+ Point last_bot = linearSnake[comp][0];
+ Point last_hdle = linearSnake[comp][0];
+ Point last_top_hdle = linearSnake[comp][0];
+ Point last_bot_hdle = linearSnake[comp][0];
+ Geom::Path res_comp(last_pt);
+ Geom::Path res_comp_top(last_pt);
+ Geom::Path res_comp_bot(last_pt);
+ unsigned i=1;
+ //bool is_top = true;//Inversion here; due to downward y?
+ bool is_top = ( linearSnake[comp][0][Y] < linearSnake[comp][1][Y] );
+
+ while( i+1<linearSnake[comp].size() ){
+ Point pt0 = linearSnake[comp][i];
+ Point pt1 = linearSnake[comp][i+1];
+ Point new_pt = (pt0+pt1)/2;
+ double scale_in = (is_top ? scale_tf : scale_bf );
+ double scale_out = (is_top ? scale_tb : scale_bb );
+ if (is_top){
+ if (top_edge_variation.get_value() != 0)
+ new_pt[Y] += double(top_edge_variation)-top_edge_variation.get_value()/2.;
+ if (top_tgt_variation.get_value() != 0)
+ new_pt[X] += double(top_tgt_variation)-top_tgt_variation.get_value()/2.;
+ if (top_smth_variation.get_value() != 0) {
+ scale_in*=(100.-double(top_smth_variation))/100.;
+ scale_out*=(100.-double(top_smth_variation))/100.;
+ }
+ }else{
+ if (bot_edge_variation.get_value() != 0)
+ new_pt[Y] += double(bot_edge_variation)-bot_edge_variation.get_value()/2.;
+ if (bot_tgt_variation.get_value() != 0)
+ new_pt[X] += double(bot_tgt_variation)-bot_tgt_variation.get_value()/2.;
+ if (bot_smth_variation.get_value() != 0) {
+ scale_in*=(100.-double(bot_smth_variation))/100.;
+ scale_out*=(100.-double(bot_smth_variation))/100.;
+ }
+ }
+ Point new_hdle_in = new_pt + (pt0-pt1) * (scale_in /2.);
+ Point new_hdle_out = new_pt - (pt0-pt1) * (scale_out/2.);
+
+ if ( fat_output.get_value() ){
+ //double scaled_width = double((is_top ? stroke_width_top : stroke_width_bot))/(pt1[X]-pt0[X]);
+ double scaled_width = 1./(pt1[X]-pt0[X]);
+ Point hdle_offset = (pt1-pt0)*scaled_width;
+ Point inside = new_pt;
+ Point inside_hdle_in;
+ Point inside_hdle_out;
+ inside[Y]+= double((is_top ? -stroke_width_top : stroke_width_bot));
+ inside_hdle_in = inside + (new_hdle_in -new_pt);// + hdle_offset * double((is_top ? front_thickness : back_thickness));
+ inside_hdle_out = inside + (new_hdle_out-new_pt);// - hdle_offset * double((is_top ? back_thickness : front_thickness));
+
+ inside_hdle_in += (pt1-pt0)/2*( double((is_top ? front_thickness : back_thickness)) / (pt1[X]-pt0[X]) );
+ inside_hdle_out -= (pt1-pt0)/2*( double((is_top ? back_thickness : front_thickness)) / (pt1[X]-pt0[X]) );
+
+ new_hdle_in -= (pt1-pt0)/2*( double((is_top ? front_thickness : back_thickness)) / (pt1[X]-pt0[X]) );
+ new_hdle_out += (pt1-pt0)/2*( double((is_top ? back_thickness : front_thickness)) / (pt1[X]-pt0[X]) );
+ //TODO: find a good way to handle limit cases (small smthness, large stroke).
+ //if (inside_hdle_in[X] > inside[X]) inside_hdle_in = inside;
+ //if (inside_hdle_out[X] < inside[X]) inside_hdle_out = inside;
+
+ if (is_top){
+ res_comp_top.appendNew<CubicBezier>(last_top_hdle,new_hdle_in,new_pt);
+ res_comp_bot.appendNew<CubicBezier>(last_bot_hdle,inside_hdle_in,inside);
+ last_top_hdle = new_hdle_out;
+ last_bot_hdle = inside_hdle_out;
+ }else{
+ res_comp_top.appendNew<CubicBezier>(last_top_hdle,inside_hdle_in,inside);
+ res_comp_bot.appendNew<CubicBezier>(last_bot_hdle,new_hdle_in,new_pt);
+ last_top_hdle = inside_hdle_out;
+ last_bot_hdle = new_hdle_out;
+ }
+ }else{
+ res_comp.appendNew<CubicBezier>(last_hdle,new_hdle_in,new_pt);
+ }
+
+ last_hdle = new_hdle_out;
+ i+=2;
+ is_top = !is_top;
+ }
+ if ( i<linearSnake[comp].size() ){
+ if ( fat_output.get_value() ){
+ res_comp_top.appendNew<CubicBezier>(last_top_hdle,linearSnake[comp][i],linearSnake[comp][i]);
+ res_comp_bot.appendNew<CubicBezier>(last_bot_hdle,linearSnake[comp][i],linearSnake[comp][i]);
+ }else{
+ res_comp.appendNew<CubicBezier>(last_hdle,linearSnake[comp][i],linearSnake[comp][i]);
+ }
+ }
+ if ( fat_output.get_value() ){
+ res_comp = res_comp_bot;
+ res_comp.append(res_comp_top.reverse(),Geom::Path::STITCH_DISCONTINUOUS);
+ }
+ result.concat(res_comp.toPwSb());
+ }
+ }
+ return result;
+}
+
+void
+LPERoughHatches::doBeforeEffect (SPLPEItem */*lpeitem*/)
+{
+ using namespace Geom;
+ top_edge_variation.resetRandomizer();
+ bot_edge_variation.resetRandomizer();
+ top_tgt_variation.resetRandomizer();
+ bot_tgt_variation.resetRandomizer();
+ top_smth_variation.resetRandomizer();
+ bot_smth_variation.resetRandomizer();
+ dist_rdm.resetRandomizer();
+
+ //original_bbox(lpeitem);
+}
+
+
+void
+LPERoughHatches::resetDefaults(SPItem * item)
+{
+ Effect::resetDefaults(item);
+
+ Geom::OptRect bbox = item->getBounds(Geom::identity(), SPItem::GEOMETRIC_BBOX);
+ Geom::Point origin(0.,0.);
+ Geom::Point vector(50.,0.);
+ if (bbox) {
+ origin = bbox->midpoint();
+ vector = Geom::Point((*bbox)[X].extent()/4, 0.);
+ top_edge_variation.param_set_value( (*bbox)[Y].extent()/10, 0 );
+ bot_edge_variation.param_set_value( (*bbox)[Y].extent()/10, 0 );
+ top_edge_variation.write_to_SVG();
+ bot_edge_variation.write_to_SVG();
+ }
+ //direction.set_and_write_new_values(origin, vector);
+ //bender.param_set_and_write_new_value( origin + Geom::Point(5,0) );
+ direction.set_and_write_new_values(origin + Geom::Point(0,-5), vector);
+ bender.set_and_write_new_values( origin, Geom::Point(5,0) );
+ hatch_dist = Geom::L2(vector)/2;
+}
+
+
+} //namespace LivePathEffect
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
index 816955e4d1050b4164efbf7b85cc205d0e334bdd..cbc3c3b26d9d19debeb7064d45daecd1eaa7b4ea 100644 (file)
-#ifndef INKSCAPE_LPE_ROUGH_HATCHES_H\r
-#define INKSCAPE_LPE_ROUGH_HATCHES_H\r
-\r
-/** \file\r
- * Fills an area with rough hatches.\r
- */\r
-\r
-/*\r
- * Authors:\r
- * JFBarraud\r
- *\r
- * Copyright (C) JF Barraud 2008.\r
- *\r
- * Released under GNU GPL, read the file 'COPYING' for more information\r
- */\r
-\r
-#include "live_effects/effect.h"\r
-#include "live_effects/parameter/point.h"\r
-#include "live_effects/parameter/parameter.h"\r
-#include "live_effects/parameter/bool.h"\r
-#include "live_effects/parameter/random.h"\r
-#include "live_effects/parameter/vector.h"\r
-\r
-namespace Inkscape {\r
-namespace LivePathEffect {\r
-\r
-class LPERoughHatches : public Effect {\r
-public:\r
- LPERoughHatches(LivePathEffectObject *lpeobject);\r
- virtual ~LPERoughHatches();\r
-\r
- virtual Geom::Piecewise<Geom::D2<Geom::SBasis> >\r
- doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in);\r
-\r
- virtual void resetDefaults(SPItem * item);\r
-\r
- virtual void doBeforeEffect(SPLPEItem * item);\r
-\r
- std::vector<double>\r
- generateLevels(Geom::Interval const &domain, double x_org);\r
-\r
- std::vector<std::vector<Geom::Point> >\r
- linearSnake(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &f, Geom::Point const &org);\r
-\r
- Geom::Piecewise<Geom::D2<Geom::SBasis> > \r
- smoothSnake(std::vector<std::vector<Geom::Point> > const &linearSnake);\r
- \r
-private:\r
- double hatch_dist;\r
- RandomParam dist_rdm;\r
- ScalarParam growth;\r
- //topfront,topback,bottomfront,bottomback handle scales.\r
- ScalarParam scale_tf, scale_tb, scale_bf, scale_bb;\r
-\r
- RandomParam top_edge_variation;\r
- RandomParam bot_edge_variation;\r
- RandomParam top_tgt_variation;\r
- RandomParam bot_tgt_variation;\r
- RandomParam top_smth_variation;\r
- RandomParam bot_smth_variation;\r
-\r
- BoolParam fat_output, do_bend;\r
- ScalarParam stroke_width_top;\r
- ScalarParam stroke_width_bot;\r
- ScalarParam front_thickness, back_thickness;\r
-\r
- //PointParam bender;\r
- VectorParam direction;\r
- VectorParam bender;\r
-\r
- LPERoughHatches(const LPERoughHatches&);\r
- LPERoughHatches& operator=(const LPERoughHatches&);\r
-};\r
-\r
-} //namespace LivePathEffect\r
-} //namespace Inkscape\r
-\r
-#endif\r
+#ifndef INKSCAPE_LPE_ROUGH_HATCHES_H
+#define INKSCAPE_LPE_ROUGH_HATCHES_H
+
+/** \file
+ * Fills an area with rough hatches.
+ */
+
+/*
+ * Authors:
+ * JFBarraud
+ *
+ * Copyright (C) JF Barraud 2008.
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/effect.h"
+#include "live_effects/parameter/point.h"
+#include "live_effects/parameter/parameter.h"
+#include "live_effects/parameter/bool.h"
+#include "live_effects/parameter/random.h"
+#include "live_effects/parameter/vector.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+class LPERoughHatches : public Effect {
+public:
+ LPERoughHatches(LivePathEffectObject *lpeobject);
+ virtual ~LPERoughHatches();
+
+ virtual Geom::Piecewise<Geom::D2<Geom::SBasis> >
+ doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in);
+
+ virtual void resetDefaults(SPItem * item);
+
+ virtual void doBeforeEffect(SPLPEItem * item);
+
+ std::vector<double>
+ generateLevels(Geom::Interval const &domain, double x_org);
+
+ std::vector<std::vector<Geom::Point> >
+ linearSnake(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &f, Geom::Point const &org);
+
+ Geom::Piecewise<Geom::D2<Geom::SBasis> >
+ smoothSnake(std::vector<std::vector<Geom::Point> > const &linearSnake);
+
+private:
+ double hatch_dist;
+ RandomParam dist_rdm;
+ ScalarParam growth;
+ //topfront,topback,bottomfront,bottomback handle scales.
+ ScalarParam scale_tf, scale_tb, scale_bf, scale_bb;
+
+ RandomParam top_edge_variation;
+ RandomParam bot_edge_variation;
+ RandomParam top_tgt_variation;
+ RandomParam bot_tgt_variation;
+ RandomParam top_smth_variation;
+ RandomParam bot_smth_variation;
+
+ BoolParam fat_output, do_bend;
+ ScalarParam stroke_width_top;
+ ScalarParam stroke_width_bot;
+ ScalarParam front_thickness, back_thickness;
+
+ //PointParam bender;
+ VectorParam direction;
+ VectorParam bender;
+
+ LPERoughHatches(const LPERoughHatches&);
+ LPERoughHatches& operator=(const LPERoughHatches&);
+};
+
+} //namespace LivePathEffect
+} //namespace Inkscape
+
+#endif