diff options
| author | Peter Moulder <peter.moulder@monash.edu> | 2009-04-07 06:32:25 +0000 |
|---|---|---|
| committer | pjrm <pjrm@users.sourceforge.net> | 2009-04-07 06:32:25 +0000 |
| commit | 4f0165ebdaf2a07079d6b9f166a585fd89e8b064 (patch) | |
| tree | b56a880772fe7c03e4c9957d54da53bc9cfc85f5 /src | |
| parent | noop: svg/svg-path-geom-test.h: Change to consistent end-of-line separators, ... (diff) | |
| download | inkscape-4f0165ebdaf2a07079d6b9f166a585fd89e8b064.tar.gz inkscape-4f0165ebdaf2a07079d6b9f166a585fd89e8b064.zip | |
noop: Set svn:eol-style to native on all .cpp and .h files under src. (find \( -name '*.cpp' -o -name '*.h' \) -print0 | xargs -0 svn propset svn:eol-style native)
(bzr r7649)
Diffstat (limited to 'src')
| -rw-r--r-- | src/2geom/bezier-clipping.cpp | 2584 | ||||
| -rw-r--r-- | src/2geom/chebyshev.cpp | 252 | ||||
| -rw-r--r-- | src/2geom/chebyshev.h | 60 | ||||
| -rw-r--r-- | src/2geom/utils.cpp | 174 | ||||
| -rw-r--r-- | src/extension/internal/javafx-out.cpp | 1950 | ||||
| -rw-r--r-- | src/extension/internal/javafx-out.h | 306 | ||||
| -rw-r--r-- | src/helper/geom-curves.h | 78 | ||||
| -rw-r--r-- | src/live_effects/lpe-rough-hatches.cpp | 1170 | ||||
| -rw-r--r-- | src/live_effects/lpe-rough-hatches.h | 156 |
9 files changed, 3365 insertions, 3365 deletions
diff --git a/src/2geom/bezier-clipping.cpp b/src/2geom/bezier-clipping.cpp index c8d99c430..96a06376c 100644 --- a/src/2geom/bezier-clipping.cpp +++ b/src/2geom/bezier-clipping.cpp @@ -1,1292 +1,1292 @@ -/*
- * 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 :
+/* + * 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 : diff --git a/src/2geom/chebyshev.cpp b/src/2geom/chebyshev.cpp index 4ba3e2325..447c5183f 100644 --- a/src/2geom/chebyshev.cpp +++ b/src/2geom/chebyshev.cpp @@ -1,126 +1,126 @@ -#include <2geom/chebyshev.h>
-
-#include <2geom/sbasis.h>
-#include <2geom/sbasis-poly.h>
-
-#include <vector>
-using std::vector;
-
-#include <gsl/gsl_math.h>
-#include <gsl/gsl_chebyshev.h>
-
-namespace Geom{
-
-SBasis cheb(unsigned n) {
- static std::vector<SBasis> basis;
- if(basis.empty()) {
- basis.push_back(Linear(1,1));
- basis.push_back(Linear(0,1));
- }
- for(unsigned i = basis.size(); i <= n; i++) {
- basis.push_back(Linear(0,2)*basis[i-1] - basis[i-2]);
- }
-
- return basis[n];
-}
-
-SBasis cheb_series(unsigned n, double* cheb_coeff) {
- SBasis r;
- for(unsigned i = 0; i < n; i++) {
- double cof = cheb_coeff[i];
- //if(i == 0)
- //cof /= 2;
- r += cheb(i)*cof;
- }
-
- return r;
-}
-
-SBasis clenshaw_series(unsigned m, double* cheb_coeff) {
- /** b_n = a_n
- b_n-1 = 2*x*b_n + a_n-1
- b_n-k = 2*x*b_{n-k+1} + a_{n-k} - b_{n - k + 2}
- b_0 = x*b_1 + a_0 - b_2
- */
-
- double a = -1, b = 1;
- SBasis d, dd;
- SBasis y = (Linear(0, 2) - (a+b)) / (b-a);
- SBasis y2 = 2*y;
- for(int j = m - 1; j >= 1; j--) {
- SBasis sv = d;
- d = y2*d - dd + cheb_coeff[j];
- dd = sv;
- }
-
- return y*d - dd + 0.5*cheb_coeff[0];
-}
-
-SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p) {
- gsl_cheb_series *cs = gsl_cheb_alloc (order+2);
-
- gsl_function F;
-
- F.function = f;
- F.params = p;
-
- gsl_cheb_init (cs, &F, in[0], in[1]);
-
- SBasis r = compose(clenshaw_series(order, cs->c), Linear(-1,1));
-
- gsl_cheb_free (cs);
- return r;
-}
-
-struct wrap {
- double (*f)(double,void*);
- void* pp;
- double fa, fb;
- Interval in;
-};
-
-double f_interp(double x, void* p) {
- struct wrap *wr = (struct wrap *)p;
- double z = (x - wr->in[0]) / (wr->in[1] - wr->in[0]);
- return (wr->f)(x, wr->pp) - ((1 - z)*wr->fa + z*wr->fb);
-}
-
-SBasis chebyshev_approximant_interpolating (double (*f)(double,void*),
- int order, Interval in, void* p) {
- double fa = f(in[0], p);
- double fb = f(in[1], p);
- struct wrap wr;
- wr.fa = fa;
- wr.fb = fb;
- wr.in = in;
- printf("%f %f\n", fa, fb);
- wr.f = f;
- wr.pp = p;
- return compose(Linear(in[0], in[1]), Linear(fa, fb)) + chebyshev_approximant(f_interp, order, in, &wr) + Linear(fa, fb);
-}
-
-SBasis chebyshev(unsigned n) {
- static std::vector<SBasis> basis;
- if(basis.empty()) {
- basis.push_back(Linear(1,1));
- basis.push_back(Linear(0,1));
- }
- for(unsigned i = basis.size(); i <= n; i++) {
- basis.push_back(Linear(0,2)*basis[i-1] - basis[i-2]);
- }
-
- return basis[n];
-}
-
-};
-
-/*
- Local Variables:
- mode:c++
- c-file-style:"stroustrup"
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
- indent-tabs-mode:nil
- fill-column:99
- End:
-*/
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+#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 309e09b3e..6de9e9cc0 100644 --- a/src/2geom/chebyshev.h +++ b/src/2geom/chebyshev.h @@ -1,30 +1,30 @@ -#ifndef _CHEBYSHEV
-#define _CHEBYSHEV
-
-#include <2geom/sbasis.h>
-#include <2geom/interval.h>
-
-/*** Conversion between Chebyshev approximation and SBasis.
- *
- */
-
-namespace Geom{
-
-SBasis chebyshev_approximant (double (*f)(double,void*), int order, Interval in, void* p=0);
-SBasis chebyshev_approximant_interpolating (double (*f)(double,void*), int order, Interval in, void* p=0);
-SBasis chebyshev(unsigned n);
-
-};
-
-/*
- Local Variables:
- mode:c++
- c-file-style:"stroustrup"
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
- indent-tabs-mode:nil
- fill-column:99
- End:
-*/
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
-
-#endif
+#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 f0f42ce01..579718553 100644 --- a/src/2geom/utils.cpp +++ b/src/2geom/utils.cpp @@ -1,87 +1,87 @@ -/** 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 :
+/** 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 : diff --git a/src/extension/internal/javafx-out.cpp b/src/extension/internal/javafx-out.cpp index fd28011c3..0a1108b1e 100644 --- a/src/extension/internal/javafx-out.cpp +++ b/src/extension/internal/javafx-out.cpp @@ -1,975 +1,975 @@ -/*
- * 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 :
+/* + * 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 : diff --git a/src/extension/internal/javafx-out.h b/src/extension/internal/javafx-out.h index 691812ee2..9c1c8778b 100644 --- a/src/extension/internal/javafx-out.h +++ b/src/extension/internal/javafx-out.h @@ -1,153 +1,153 @@ -/*
- * 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 */
-
+/* + * 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 */ + diff --git a/src/helper/geom-curves.h b/src/helper/geom-curves.h index daf208e78..f3dc364f2 100644 --- a/src/helper/geom-curves.h +++ b/src/helper/geom-curves.h @@ -1,39 +1,39 @@ -#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 :
+#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 : diff --git a/src/live_effects/lpe-rough-hatches.cpp b/src/live_effects/lpe-rough-hatches.cpp index d75941b12..4cf3e35e6 100644 --- a/src/live_effects/lpe-rough-hatches.cpp +++ b/src/live_effects/lpe-rough-hatches.cpp @@ -1,585 +1,585 @@ -#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 :
+#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 : diff --git a/src/live_effects/lpe-rough-hatches.h b/src/live_effects/lpe-rough-hatches.h index 816955e4d..cbc3c3b26 100644 --- a/src/live_effects/lpe-rough-hatches.h +++ b/src/live_effects/lpe-rough-hatches.h @@ -1,78 +1,78 @@ -#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
+#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 |
