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