1 #define INKSCAPE_LPE_SPIRO_C
3 /*
4 * Released under GNU GPL, read the file 'COPYING' for more information
5 */
7 #include "live_effects/lpe-spiro.h"
9 #include "display/curve.h"
10 #include "nodepath.h"
11 #include <typeinfo>
12 #include <2geom/pathvector.h>
13 #include <2geom/matrix.h>
14 #include <2geom/bezier-curve.h>
15 #include <2geom/hvlinesegment.h>
16 #include "helper/geom-nodetype.h"
18 #include "live_effects/bezctx.h"
19 #include "live_effects/bezctx_intf.h"
20 #include "live_effects/spiro.h"
22 // For handling un-continuous paths:
23 #include "message-stack.h"
24 #include "inkscape.h"
25 #include "desktop.h"
27 typedef struct {
28 bezctx base;
29 SPCurve *curve;
30 int is_open;
31 } bezctx_ink;
33 void bezctx_ink_moveto(bezctx *bc, double x, double y, int /*is_open*/)
34 {
35 bezctx_ink *bi = (bezctx_ink *) bc;
36 bi->curve->moveto(x, y);
37 }
39 void bezctx_ink_lineto(bezctx *bc, double x, double y)
40 {
41 bezctx_ink *bi = (bezctx_ink *) bc;
42 bi->curve->lineto(x, y);
43 }
45 void bezctx_ink_quadto(bezctx *bc, double xm, double ym, double x3, double y3)
46 {
47 bezctx_ink *bi = (bezctx_ink *) bc;
49 double x0, y0;
50 double x1, y1;
51 double x2, y2;
53 NR::Point last = bi->curve->last_point();
54 x0 = last[NR::X];
55 y0 = last[NR::Y];
56 x1 = xm + (1./3) * (x0 - xm);
57 y1 = ym + (1./3) * (y0 - ym);
58 x2 = xm + (1./3) * (x3 - xm);
59 y2 = ym + (1./3) * (y3 - ym);
61 bi->curve->curveto(x1, y1, x2, y2, x3, y3);
62 }
64 void bezctx_ink_curveto(bezctx *bc, double x1, double y1, double x2, double y2,
65 double x3, double y3)
66 {
67 bezctx_ink *bi = (bezctx_ink *) bc;
68 bi->curve->curveto(x1, y1, x2, y2, x3, y3);
69 }
71 bezctx *
72 new_bezctx_ink(SPCurve *curve) {
73 bezctx_ink *result = g_new(bezctx_ink, 1);
74 result->base.moveto = bezctx_ink_moveto;
75 result->base.lineto = bezctx_ink_lineto;
76 result->base.quadto = bezctx_ink_quadto;
77 result->base.curveto = bezctx_ink_curveto;
78 result->base.mark_knot = NULL;
79 result->curve = curve;
80 return &result->base;
81 }
86 namespace Inkscape {
87 namespace LivePathEffect {
89 LPESpiro::LPESpiro(LivePathEffectObject *lpeobject) :
90 Effect(lpeobject)
91 {
92 }
94 LPESpiro::~LPESpiro()
95 {
96 }
98 void
99 LPESpiro::setup_nodepath(Inkscape::NodePath::Path *np)
100 {
101 sp_nodepath_show_handles(np, false);
102 // sp_nodepath_show_helperpath(np, false);
103 }
105 void
106 LPESpiro::doEffect(SPCurve * curve)
107 {
108 using Geom::X;
109 using Geom::Y;
111 // Make copy of old path as it is changed during processing
112 Geom::PathVector const original_pathv = curve->get_pathvector();
113 guint len = curve->get_segment_count() + 2;
115 curve->reset();
116 bezctx *bc = new_bezctx_ink(curve);
117 spiro_cp *path = g_new (spiro_cp, len);
118 int ip = 0;
120 for(Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) {
121 if (path_it->empty())
122 continue;
124 // start of path
125 {
126 Geom::Point p = path_it->front().pointAt(0);
127 path[ip].x = p[X];
128 path[ip].y = p[Y];
129 path[ip].ty = path_it->closed() ? 'c' : '{' ; // for closed paths, this might change depending on whether the closing is smooth or not
130 ip++;
131 }
133 // midpoints
134 Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve
135 Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve
137 Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop
138 if (path_it->closed()) {
139 // if the path is closed, maybe we have to stop a bit earlier because the closing line segment has zerolength.
140 if (path_it->back_closed().isDegenerate()) {
141 // the closing line segment has zero-length. So stop before that one!
142 curve_endit = path_it->end_open();
143 }
144 }
146 while ( curve_it2 != curve_endit )
147 {
148 /* This deals with the node between curve_it1 and curve_it2.
149 * Loop to end_default (so without last segment), loop ends when curve_it2 hits the end
150 * and then curve_it1 points to end or closing segment */
151 Geom::Point p = curve_it1->finalPoint();
152 path[ip].x = p[X];
153 path[ip].y = p[Y];
155 // Determine type of spiro node this is, determined by the tangents (angles) of the curves
156 // TODO: see if this can be simplified by using /helpers/geom-nodetype.cpp:get_nodetype
157 bool this_is_line = ( dynamic_cast<Geom::LineSegment const *>(&*curve_it1) ||
158 dynamic_cast<Geom::HLineSegment const *>(&*curve_it1) ||
159 dynamic_cast<Geom::VLineSegment const *>(&*curve_it1) );
160 bool next_is_line = ( dynamic_cast<Geom::LineSegment const *>(&*curve_it2) ||
161 dynamic_cast<Geom::HLineSegment const *>(&*curve_it2) ||
162 dynamic_cast<Geom::VLineSegment const *>(&*curve_it2) );
163 Geom::Point deriv_1 = curve_it1->unitTangentAt(1);
164 Geom::Point deriv_2 = curve_it2->unitTangentAt(0);
165 double this_angle_L2 = Geom::L2(deriv_1);
166 double next_angle_L2 = Geom::L2(deriv_2);
167 double both_angles_L2 = Geom::L2(deriv_1 + deriv_2);
168 if ( (this_angle_L2 > 1e-6) &&
169 (next_angle_L2 > 1e-6) &&
170 ((this_angle_L2 + next_angle_L2 - both_angles_L2) < 1e-3) )
171 {
172 if (this_is_line && !next_is_line) {
173 path[ip].ty = ']';
174 } else if (next_is_line && !this_is_line) {
175 path[ip].ty = '[';
176 } else {
177 path[ip].ty = 'c';
178 }
179 } else {
180 path[ip].ty = 'v';
181 }
183 ++curve_it1;
184 ++curve_it2;
185 ip++;
186 }
188 // add last point to the spiropath
189 Geom::Point p = curve_it1->finalPoint();
190 path[ip].x = p[X];
191 path[ip].y = p[Y];
192 if (path_it->closed()) {
193 // curve_it1 points to the (visually) closing segment. determine the match between first and this last segment (the closing node)
194 Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, path_it->front());
195 switch (nodetype) {
196 case Geom::NODE_NONE: // can't happen! but if it does, it means the path isn't closed :-)
197 path[ip].ty = '}';
198 ip++;
199 break;
200 case Geom::NODE_CUSP:
201 path[0].ty = path[ip].ty = 'v';
202 break;
203 case Geom::NODE_SMOOTH:
204 case Geom::NODE_SYMM:
205 path[0].ty = path[ip].ty = 'c';
206 break;
207 }
208 } else {
209 // set type to path closer
210 path[ip].ty = '}';
211 ip++;
212 }
214 // run subpath through spiro
215 int sp_len = ip;
216 spiro_seg *s = run_spiro(path, sp_len);
217 spiro_to_bpath(s, sp_len, bc);
218 free(s);
219 ip = 0;
220 }
222 g_free (path);
224 // FIXME: refactor the spiro code such that it cannot generate non-continous paths!
225 // sometimes, the code above generates a path that is not continuous: caused by chaotic algorithm?
226 // The continuity error always happens after a lot of curveto calls (a big path probably that spins to infinity?)
227 // if so, undo the effect by resetting the original path.
228 try {
229 curve->get_pathvector() * Geom::identity(); // tests for continuity, this makes LPE Spiro slower of course :-(
230 }
231 catch (Geom::ContinuityError & e) {
232 g_warning("Exception during LPE Spiro execution. \n %s", e.what());
233 SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
234 _("An exception occurred during execution of the Spiro Path Effect.") );
235 curve->set_pathvector(original_pathv);
236 }
237 }
239 }; //namespace LivePathEffect
240 }; /* namespace Inkscape */
242 /*
243 Local Variables:
244 mode:c++
245 c-file-style:"stroustrup"
246 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
247 indent-tabs-mode:nil
248 fill-column:99
249 End:
250 */
251 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :