65c642aface1e4afaa4a10fe18a5f298ec6a8918
1 /** @file
2 * @brief LPE knot effect implementation
3 */
4 /* Authors:
5 * Jean-Francois Barraud <jf.barraud@gmail.com>
6 *
7 * Copyright (C) 2007 Authors
8 *
9 * Released under GNU GPL, read the file 'COPYING' for more information
10 */
12 #include "sp-shape.h"
13 #include "display/curve.h"
14 #include "live_effects/lpe-knot.h"
15 #include "svg/svg.h"
16 #include "style.h"
18 #include <2geom/sbasis-to-bezier.h>
19 #include <2geom/sbasis.h>
20 #include <2geom/d2.h>
21 #include <2geom/d2-sbasis.h>
22 #include <2geom/path.h>
23 //#include <2geom/crossing.h>
24 #include <2geom/bezier-to-sbasis.h>
25 #include <2geom/basic-intersection.h>
26 #include <2geom/exception.h>
28 #include <exception>
30 namespace Inkscape {
31 namespace LivePathEffect {
33 class KnotHolderEntityCrossingSwitcher : public LPEKnotHolderEntity
34 {
35 public:
36 virtual ~KnotHolderEntityCrossingSwitcher() {}
38 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
39 virtual Geom::Point knot_get();
40 virtual void knot_click(guint state);
41 };
44 //---------------------------------------------------------------------------
45 //LPEKnot specific Interval manipulation.
46 //---------------------------------------------------------------------------
48 //remove an interval from an union of intervals.
49 //TODO: is it worth moving it to 2Geom?
50 static
51 std::vector<Geom::Interval> complementOf(Geom::Interval I, std::vector<Geom::Interval> domain){
52 std::vector<Geom::Interval> ret;
53 double min = domain.front().min();
54 double max = domain.back().max();
55 Geom::Interval I1 = Geom::Interval(min,I.min());
56 Geom::Interval I2 = Geom::Interval(I.max(),max);
58 for (unsigned i = 0; i<domain.size(); i++){
59 boost::optional<Geom::Interval> I1i = intersect(domain.at(i),I1);
60 if (I1i && !I1i->isSingular()) ret.push_back(I1i.get());
61 boost::optional<Geom::Interval> I2i = intersect(domain.at(i),I2);
62 if (I2i && !I2i->isSingular()) ret.push_back(I2i.get());
63 }
64 return ret;
65 }
67 //find the time interval during which patha is hidden by pathb near a given crossing.
68 // Warning: not accurate!
69 static
70 Geom::Interval
71 findShadowedTime(Geom::Path const &patha, std::vector<Geom::Point> const &pt_and_dir,
72 double const ta, double const width){
73 using namespace Geom;
74 Point T = unit_vector(pt_and_dir[1]);
75 Point N = T.cw();
76 Point A = pt_and_dir[0]-3*width*T, B = A+6*width*T;
78 Matrix mat = from_basis( T, N, pt_and_dir[0] );
79 mat = mat.inverse();
80 Path p = patha * mat;
82 std::vector<double> times;
84 //TODO: explore the path fwd/backward from ta (worth?)
85 for (unsigned i=0; i<patha.size(); i++){
86 D2<SBasis> f = p[i].toSBasis();
87 std::vector<double> times_i, temptimes;
88 temptimes = roots(f[Y]-width);
89 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
90 temptimes = roots(f[Y]+width);
91 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
92 temptimes = roots(f[X]-3*width);
93 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
94 temptimes = roots(f[X]+3*width);
95 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
96 for (unsigned k=0; k<times_i.size(); k++){
97 times_i[k]+=i;
98 }
99 times.insert(times.end(), times_i.begin(), times_i.end() );
100 }
101 std::sort( times.begin(), times.end() );
102 std::vector<double>::iterator new_end = std::unique( times.begin(), times.end() );
103 times.resize( new_end - times.begin() );
105 double tmin = 0, tmax = patha.size();
106 double period = patha.size();//hm... Should this be patha.size()+1?
107 if (times.size()>0){
108 unsigned rk = upper_bound( times.begin(), times.end(), ta ) - times.begin();
109 if ( rk < times.size() )
110 tmax = times[rk];
111 else if ( patha.closed() )
112 tmax = times[0]+period;
114 if ( rk > 0 )
115 tmin = times[rk-1];
116 else if ( patha.closed() )
117 tmin = times.back()-period;
118 }
119 return Interval(tmin,tmax);
120 }
122 //---------------------------------------------------------------------------
123 //LPEKnot specific Crossing Data manipulation.
124 //---------------------------------------------------------------------------
126 //Yet another crossing data representation.
127 // an CrossingPoint stores
128 // -an intersection point
129 // -the involved path components
130 // -for each component, the time at which this crossing occurs + the order of this crossing along the component (when starting from 0).
132 namespace LPEKnotNS {//just in case...
133 CrossingPoints::CrossingPoints(std::vector<Geom::Path> const &paths) : std::vector<CrossingPoint>(){
134 // std::cout<<"\nCrossingPoints creation from path vector\n";
135 for( unsigned i=0; i<paths.size(); i++){
136 for( unsigned ii=0; ii<paths[i].size(); ii++){
137 for( unsigned j=i; j<paths.size(); j++){
138 for( unsigned jj=(i==j?ii:0); jj<paths[j].size(); jj++){
139 std::vector<std::pair<double,double> > times;
140 if ( i==j && ii==jj){
142 // std::cout<<"--(self int)\n";
143 // std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
144 // std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
146 find_self_intersections( times, paths[i][ii].toSBasis() );
147 }else{
148 // std::cout<<"--(pair int)\n";
149 // std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
150 // std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
151 // std::cout<<"with\n";
152 // std::cout << paths[j][jj].toSBasis()[Geom::X] <<"\n";
153 // std::cout << paths[j][jj].toSBasis()[Geom::Y] <<"\n";
155 find_intersections( times, paths[i][ii].toSBasis(), paths[j][jj].toSBasis() );
156 }
157 for (unsigned k=0; k<times.size(); k++){
158 //std::cout<<"intersection "<<i<<"["<<ii<<"]("<<times[k].first<<")= "<<j<<"["<<jj<<"]("<<times[k].second<<")\n";
159 if (times[k].first == times[k].first && times[k].second == times[k].second ){//is this the way to test NaN?
160 double zero = 1e-4;
161 if ( i==j && fabs(times[k].first+ii - times[k].second-jj)<=zero ){//this is just end=start of successive curves in a path.
162 continue;
163 }
164 if ( i==j && ii == 0 && jj==paths[i].size()-1 &&
165 paths[i].closed() &&
166 fabs(times[k].first) <= zero &&
167 fabs(times[k].second - 1) <= zero ){//this is just end=start of a closed path.
168 continue;
169 }
170 CrossingPoint cp;
171 cp.pt = paths[i][ii].pointAt(times[k].first);
172 cp.sign = 1;
173 cp.i = i;
174 cp.j = j;
175 cp.ni = 0; cp.nj=0;//not set yet
176 cp.ti = times[k].first + ii;
177 cp.tj = times[k].second + jj;
178 push_back(cp);
179 }else{
180 std::cout<<"ooops: find_(self)_intersections returned NaN:";
181 //std::cout<<"intersection "<<i<<"["<<ii<<"](NaN)= "<<j<<"["<<jj<<"](NaN)\n";
182 }
183 }
184 }
185 }
186 }
187 }
188 for( unsigned i=0; i<paths.size(); i++){
189 std::map < double, unsigned > cuts;
190 for( unsigned k=0; k<size(); k++){
191 CrossingPoint cp = (*this)[k];
192 if (cp.i == i) cuts[cp.ti] = k;
193 if (cp.j == i) cuts[cp.tj] = k;
194 }
195 unsigned count = 0;
196 for ( std::map < double, unsigned >::iterator m=cuts.begin(); m!=cuts.end(); m++ ){
197 if ( (*this)[m->second].i == i && (*this)[m->second].ti == m->first ){
198 (*this)[m->second].ni = count;
199 }else{
200 (*this)[m->second].nj = count;
201 }
202 count++;
203 }
204 }
205 }
207 CrossingPoints::CrossingPoints(std::vector<double> const &input) : std::vector<CrossingPoint>()
208 {
209 if (input.size()>0 && input.size()%9 ==0){
210 using namespace Geom;
211 for( unsigned n=0; n<input.size(); ){
212 CrossingPoint cp;
213 cp.pt[X] = input[n++];
214 cp.pt[Y] = input[n++];
215 cp.i = input[n++];
216 cp.j = input[n++];
217 cp.ni = input[n++];
218 cp.nj = input[n++];
219 cp.ti = input[n++];
220 cp.tj = input[n++];
221 cp.sign = input[n++];
222 push_back(cp);
223 }
224 }
225 }
227 std::vector<double>
228 CrossingPoints::to_vector()
229 {
230 using namespace Geom;
231 std::vector<double> result;
232 for( unsigned n=0; n<size(); n++){
233 CrossingPoint cp = (*this)[n];
234 result.push_back(cp.pt[X]);
235 result.push_back(cp.pt[Y]);
236 result.push_back(double(cp.i));
237 result.push_back(double(cp.j));
238 result.push_back(double(cp.ni));
239 result.push_back(double(cp.nj));
240 result.push_back(double(cp.ti));
241 result.push_back(double(cp.tj));
242 result.push_back(double(cp.sign));
243 }
244 return result;
245 }
247 //FIXME: rewrite to check success: return bool, put result in arg.
248 CrossingPoint
249 CrossingPoints::get(unsigned const i, unsigned const ni)
250 {
251 for (unsigned k=0; k<size(); k++){
252 if (
253 ((*this)[k].i==i && (*this)[k].ni==ni) ||
254 ((*this)[k].j==i && (*this)[k].nj==ni)
255 ) return (*this)[k];
256 }
257 g_warning("LPEKnotNS::CrossingPoints::get error. %uth crossing along string %u not found.",ni,i);
258 assert(false);//debug purpose...
259 return CrossingPoint();
260 }
262 unsigned
263 idx_of_nearest(CrossingPoints const &cpts, Geom::Point const &p)
264 {
265 double dist=-1;
266 unsigned result = cpts.size();
267 for (unsigned k=0; k<cpts.size(); k++){
268 double dist_k = Geom::L2(p-cpts[k].pt);
269 if (dist<0 || dist>dist_k){
270 result = k;
271 dist = dist_k;
272 }
273 }
274 return result;
275 }
277 //TODO: Find a way to warn the user when the topology changes.
278 //TODO: be smarter at guessing the signs when the topology changed?
279 void
280 CrossingPoints::inherit_signs(CrossingPoints const &other, int default_value)
281 {
282 bool topo_changed = false;
283 for (unsigned n=0; n<size(); n++){
284 if ( n<other.size() &&
285 other[n].i == (*this)[n].i &&
286 other[n].j == (*this)[n].j &&
287 other[n].ni == (*this)[n].ni &&
288 other[n].nj == (*this)[n].nj )
289 {
290 (*this)[n].sign = other[n].sign;
291 }else{
292 topo_changed = true;
293 break;
294 }
295 }
296 if (topo_changed){
297 //TODO: Find a way to warn the user!!
298 // std::cout<<"knot topolgy changed!\n";
299 for (unsigned n=0; n<size(); n++){
300 Geom::Point p = (*this)[n].pt;
301 unsigned idx = idx_of_nearest(other,p);
302 if (idx<other.size()){
303 (*this)[n].sign = other[idx].sign;
304 }else{
305 (*this)[n].sign = default_value;
306 }
307 }
308 }
309 }
311 }
313 //---------------------------------------------------------------------------
314 //---------------------------------------------------------------------------
315 //LPEKnot effect.
316 //---------------------------------------------------------------------------
317 //---------------------------------------------------------------------------
320 LPEKnot::LPEKnot(LivePathEffectObject *lpeobject) :
321 Effect(lpeobject),
322 // initialise your parameters here:
323 interruption_width(_("Interruption width"), _("Size of hidden region of lower string"), "interruption_width", &wr, this, 3),
324 prop_to_stroke_width(_("unit of stroke width"), _("Consider 'Interruption width' as a ratio of stroke width."), "prop_to_stroke_width", &wr, this, true),
325 add_stroke_width(_("add stroke width to interruption size"), _("Add the stroke width to the interruption size."), "add_stroke_width", &wr, this, true),
326 add_other_stroke_width(_("add other's stroke width to interruption size"), _("Add crossed stroke width to the interruption size."), "add_other_stroke_width", &wr, this, true),
327 switcher_size(_("Switcher size"), _("Orientation indicator/switcher size"), "switcher_size", &wr, this, 15),
328 crossing_points_vector(_("Crossing Signs"), _("Crossings signs"), "crossing_points_vector", &wr, this),
329 gpaths(),gstroke_widths()
330 {
331 // register all your parameters here, so Inkscape knows which parameters this effect has:
332 registerParameter( dynamic_cast<Parameter *>(&interruption_width) );
333 registerParameter( dynamic_cast<Parameter *>(&prop_to_stroke_width) );
334 registerParameter( dynamic_cast<Parameter *>(&add_stroke_width) );
335 registerParameter( dynamic_cast<Parameter *>(&add_other_stroke_width) );
336 registerParameter( dynamic_cast<Parameter *>(&switcher_size) );
337 registerParameter( dynamic_cast<Parameter *>(&crossing_points_vector) );
339 registerKnotHolderHandle(new KnotHolderEntityCrossingSwitcher(), _("Drag to select a crossing, click to flip it"));
340 crossing_points = LPEKnotNS::CrossingPoints();
341 selectedCrossing = 0;
342 switcher = Geom::Point(0,0);
343 }
345 LPEKnot::~LPEKnot()
346 {
348 }
350 void
351 LPEKnot::updateSwitcher(){
352 if (selectedCrossing < crossing_points.size()){
353 switcher = crossing_points[selectedCrossing].pt;
354 //std::cout<<"placing switcher at "<<switcher<<" \n";
355 }else if (crossing_points.size()>0){
356 selectedCrossing = 0;
357 switcher = crossing_points[selectedCrossing].pt;
358 //std::cout<<"placing switcher at "<<switcher<<" \n";
359 }else{
360 //std::cout<<"hiding switcher!\n";
361 //TODO: is there a way to properly hide the helper.
362 //switcher = Geom::Point(Geom::infinity(),Geom::infinity());
363 switcher = Geom::Point(1e10,1e10);
364 }
365 }
367 std::vector<Geom::Path>
368 LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in)
369 {
370 using namespace Geom;
371 std::vector<Geom::Path> path_out;
373 if (gpaths.size()==0){
374 return path_in;
375 }
377 for (unsigned comp=0; comp<path_in.size(); comp++){
379 //find the relevant path component in gpaths (required to allow groups!)
380 //Q: do we always recieve the group members in the same order? can we rest on that?
381 unsigned i0 = 0;
382 for (i0=0; i0<gpaths.size(); i0++){
383 if (path_in[comp]==gpaths[i0]) break;
384 }
385 if (i0 == gpaths.size() ) {THROW_EXCEPTION("lpe-knot error: group member not recognized");}// this should not happen...
387 std::vector<Interval> dom;
388 dom.push_back(Interval(0.,gpaths[i0].size()));
389 for (unsigned p = 0; p < crossing_points.size(); p++){
390 if (crossing_points[p].i == i0 || crossing_points[p].j == i0){
391 unsigned i = crossing_points[p].i;
392 unsigned j = crossing_points[p].j;
393 double ti = crossing_points[p].ti;
394 double tj = crossing_points[p].tj;
396 double curveidx, t;
398 t = modf(ti, &curveidx);
399 if(curveidx == gpaths[i].size() ) { curveidx--; t = 1.;}
400 assert(curveidx >= 0 && curveidx < gpaths[i].size());
401 std::vector<Point> flag_i = gpaths[i][curveidx].pointAndDerivatives(t,1);
403 t = modf(tj, &curveidx);
404 if(curveidx == gpaths[j].size() ) { curveidx--; t = 1.;}
405 assert(curveidx >= 0 && curveidx < gpaths[j].size());
406 std::vector<Point> flag_j = gpaths[j][curveidx].pointAndDerivatives(t,1);
409 int geom_sign = ( cross(flag_i[1],flag_j[1]) > 0 ? 1 : -1);
411 bool i0_is_under = false;
412 if ( crossing_points[p].sign * geom_sign > 0 ){
413 i0_is_under = ( i == i0 );
414 }else if ( crossing_points[p].sign * geom_sign < 0 ){
415 if (j == i0){
416 std::swap( i, j);
417 std::swap(ti, tj);
418 std::swap(flag_i,flag_j);
419 i0_is_under = true;
420 }
421 }
422 if (i0_is_under){
423 double width = interruption_width;
424 if ( prop_to_stroke_width.get_value() ) {
425 width *= gstroke_widths[i];
426 }
427 if ( add_stroke_width.get_value() ) {
428 width += gstroke_widths[i];
429 }
430 if ( add_other_stroke_width.get_value() ) {
431 width += gstroke_widths[j];
432 }
433 Interval hidden = findShadowedTime(gpaths[i0], flag_j, ti, width/2);
434 double period = gpaths[i0].size();//hm... Should this be gpaths[i0].size()+1?
435 if (hidden.max() > period ) hidden -= period;
436 if (hidden.min()<0){
437 dom = complementOf( Interval(0,hidden.max()) ,dom);
438 dom = complementOf( Interval(hidden.min()+period, period) ,dom);
439 }else{
440 dom = complementOf(hidden,dom);
441 }
442 }
443 }
444 }
446 //If the all component is hidden, continue.
447 if ( dom.size() == 0){
448 continue;
449 }
451 //If the current path is closed and the last/first point is still there, glue first and last piece.
452 unsigned beg_comp = 0, end_comp = dom.size();
453 if ( gpaths[i0].closed() && dom.front().min() == 0 && dom.back().max() == gpaths[i0].size() ){
454 if ( dom.size() == 1){
455 path_out.push_back(gpaths[i0]);
456 continue;
457 }else{
458 // std::cout<<"fusing first and last component\n";
459 beg_comp++;
460 end_comp--;
461 Path first = gpaths[i0].portion(dom.back());
462 //FIXME: STITCH_DISCONTINUOUS should not be necessary (?!?)
463 first.append(gpaths[i0].portion(dom.front()), Path::STITCH_DISCONTINUOUS);
464 path_out.push_back(first);
465 }
466 }
467 for (unsigned comp = beg_comp; comp < end_comp; comp++){
468 assert(dom.at(comp).min() >=0 and dom.at(comp).max() <= gpaths.at(i0).size());
469 path_out.push_back(gpaths[i0].portion(dom.at(comp)));
470 }
471 }
472 return path_out;
473 }
477 //recursively collect gpaths and stroke widths (stolen from "sp-lpe_item.cpp").
478 void collectPathsAndWidths (SPLPEItem const *lpeitem, std::vector<Geom::Path> &paths, std::vector<double> &stroke_widths){
479 if (SP_IS_GROUP(lpeitem)) {
480 GSList const *item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
481 for ( GSList const *iter = item_list; iter; iter = iter->next ) {
482 SPObject *subitem = static_cast<SPObject *>(iter->data);
483 if (SP_IS_LPE_ITEM(subitem)) {
484 collectPathsAndWidths(SP_LPE_ITEM(subitem), paths, stroke_widths);
485 }
486 }
487 }
488 else if (SP_IS_SHAPE(lpeitem)) {
489 SPCurve * c = sp_shape_get_curve(SP_SHAPE(lpeitem));
490 if (c) {
491 Geom::PathVector subpaths = c->get_pathvector();
492 for (unsigned i=0; i<subpaths.size(); i++){
493 paths.push_back(subpaths[i]);
494 //FIXME: do we have to be more carefull when trying to access stroke width?
495 stroke_widths.push_back(SP_ITEM(lpeitem)->style->stroke_width.computed);
496 }
497 }
498 }
499 }
502 void
503 LPEKnot::doBeforeEffect (SPLPEItem *lpeitem)
504 {
505 using namespace Geom;
506 original_bbox(lpeitem);
508 gpaths = std::vector<Geom::Path>();
509 gstroke_widths = std::vector<double>();
510 collectPathsAndWidths(lpeitem, gpaths, gstroke_widths);
512 // std::cout<<"\nPaths on input:\n";
513 // for (unsigned i=0; i<gpaths.size(); i++){
514 // for (unsigned ii=0; ii<gpaths[i].size(); ii++){
515 // std::cout << gpaths[i][ii].toSBasis()[Geom::X] <<"\n";
516 // std::cout << gpaths[i][ii].toSBasis()[Geom::Y] <<"\n";
517 // std::cout<<"--\n";
518 // }
519 // }
521 std::cout<<"crossing_pts_vect: "<<crossing_points_vector.param_getSVGValue()<<".\n";
522 std::cout<<"prop_to_stroke_width: "<<prop_to_stroke_width.param_getSVGValue()<<".\n";
524 LPEKnotNS::CrossingPoints old_crdata(crossing_points_vector.data());
526 // std::cout<<"\nVectorParam size:"<<crossing_points_vector.data().size()<<"\n";
528 // std::cout<<"\nOld crdata ("<<old_crdata.size()<<"): \n";
529 // for (unsigned toto=0; toto<old_crdata.size(); toto++){
530 // std::cout<<"(";
531 // std::cout<<old_crdata[toto].i<<",";
532 // std::cout<<old_crdata[toto].j<<",";
533 // std::cout<<old_crdata[toto].ni<<",";
534 // std::cout<<old_crdata[toto].nj<<",";
535 // std::cout<<old_crdata[toto].ti<<",";
536 // std::cout<<old_crdata[toto].tj<<",";
537 // std::cout<<old_crdata[toto].sign<<"),";
538 // }
540 //if ( old_crdata.size() > 0 ) std::cout<<"first crossing sign = "<<old_crdata[0].sign<<".\n";
541 //else std::cout<<"old data is empty!!\n";
542 crossing_points = LPEKnotNS::CrossingPoints(gpaths);
543 // std::cout<<"\nNew crdata ("<<crossing_points.size()<<"): \n";
544 // for (unsigned toto=0; toto<crossing_points.size(); toto++){
545 // std::cout<<"(";
546 // std::cout<<crossing_points[toto].i<<",";
547 // std::cout<<crossing_points[toto].j<<",";
548 // std::cout<<crossing_points[toto].ni<<",";
549 // std::cout<<crossing_points[toto].nj<<",";
550 // std::cout<<crossing_points[toto].ti<<",";
551 // std::cout<<crossing_points[toto].tj<<",";
552 // std::cout<<crossing_points[toto].sign<<"),";
553 // }
554 crossing_points.inherit_signs(old_crdata);
555 crossing_points_vector.param_set_and_write_new_value(crossing_points.to_vector());
556 updateSwitcher();
557 }
560 static LPEKnot *
561 get_effect(SPItem *item)
562 {
563 Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
564 if (effect->effectType() != KNOT) {
565 g_print ("Warning: Effect is not of type LPEKnot!\n");
566 return NULL;
567 }
568 return static_cast<LPEKnot *>(effect);
569 }
571 void
572 LPEKnot::addCanvasIndicators(SPLPEItem */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
573 {
574 using namespace Geom;
575 double r = switcher_size*.1;
576 char const * svgd;
577 //TODO: use a nice path!
578 if (selectedCrossing >= crossing_points.size()||crossing_points[selectedCrossing].sign > 0){
579 //svgd = "M -10,0 A 10 10 0 1 0 0,-10 l 5,-1 -1,2";
580 svgd = "m -7.07,7.07 c 3.9,3.91 10.24,3.91 14.14,0 3.91,-3.9 3.91,-10.24 0,-14.14 -3.9,-3.91 -10.24,-3.91 -14.14,0 l 2.83,-4.24 0.7,2.12";
581 }else if (crossing_points[selectedCrossing].sign < 0){
582 //svgd = "M 10,0 A 10 10 0 1 1 0,-10 l -5,-1 1,2";
583 svgd = "m 7.07,7.07 c -3.9,3.91 -10.24,3.91 -14.14,0 -3.91,-3.9 -3.91,-10.24 0,-14.14 3.9,-3.91 10.24,-3.91 14.14,0 l -2.83,-4.24 -0.7,2.12";
584 }else{
585 //svgd = "M 10,0 A 10 10 0 1 0 -10,0 A 10 10 0 1 0 10,0 ";
586 svgd = "M 10,0 C 10,5.52 5.52,10 0,10 -5.52,10 -10,5.52 -10,0 c 0,-5.52 4.48,-10 10,-10 5.52,0 10,4.48 10,10 z";
587 }
588 PathVector pathv = sp_svg_read_pathv(svgd);
589 pathv *= Matrix(r,0,0,r,0,0);
590 pathv+=switcher;
591 hp_vec.push_back(pathv);
592 }
594 void
595 KnotHolderEntityCrossingSwitcher::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
596 {
597 LPEKnot* lpe = get_effect(item);
599 lpe->selectedCrossing = idx_of_nearest(lpe->crossing_points,p);
600 lpe->updateSwitcher();
601 // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
602 sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
603 }
605 Geom::Point
606 KnotHolderEntityCrossingSwitcher::knot_get()
607 {
608 LPEKnot* lpe = get_effect(item);
609 return snap_knot_position(lpe->switcher);
610 }
612 void
613 KnotHolderEntityCrossingSwitcher::knot_click(guint state)
614 {
615 LPEKnot* lpe = get_effect(item);
616 unsigned s = lpe->selectedCrossing;
617 if (s < lpe->crossing_points.size()){
618 if (state & GDK_SHIFT_MASK){
619 lpe->crossing_points[s].sign = 1;
620 }else{
621 int sign = lpe->crossing_points[s].sign;
622 lpe->crossing_points[s].sign = ((sign+2)%3)-1;
623 //std::cout<<"crossing set to"<<lpe->crossing_points[s].sign<<".\n";
624 }
625 lpe->crossing_points_vector.param_set_and_write_new_value(lpe->crossing_points.to_vector());
627 // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
628 sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
629 }
630 }
633 /* ######################## */
635 } // namespace LivePathEffect
636 } // namespace Inkscape
638 /*
639 Local Variables:
640 mode:c++
641 c-file-style:"stroustrup"
642 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
643 indent-tabs-mode:nil
644 fill-column:99
645 End:
646 */
647 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :