Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
lpe-knot.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Jean-Francois Barraud <jf.barraud@gmail.com>
8 * Abhishek Sharma
9 * Johan Engelen
10 *
11 * Copyright (C) 2007-2012 Authors
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16#include "lpe-knot.h"
17
18#include <optional>
19
20#include <gdk/gdk.h>
21#include <glibmm/i18n.h>
22
24
25#include "preferences.h"
26#include "style.h"
27
28#include "display/curve.h"
29#include "helper/geom.h"
31#include "object/sp-path.h"
32#include "object/sp-shape.h"
33#include "ui/knot/knot-holder.h"
35#include "svg/svg.h"
36
37namespace Inkscape {
38namespace LivePathEffect {
39
40class KnotHolderEntityCrossingSwitcher : public LPEKnotHolderEntity {
41public:
42 KnotHolderEntityCrossingSwitcher(LPEKnot *effect) : LPEKnotHolderEntity(effect) {};
43 void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) override;
44 Geom::Point knot_get() const override;
45 void knot_click(guint state) override;
46};
47
48
50 Geom::Path::size_type retval = path.size_default();
51 const Geom::Curve &closingline = path.back_closed();
52 // the closing line segment is always of type
53 // Geom::LineSegment.
54 if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
55 // closingline.isDegenerate() did not work, because it only checks for
56 // *exact* zero length, which goes wrong for relative coordinates and
57 // rounding errors...
58 // the closing line segment has zero-length. So stop before that one!
59 retval = path.size_open();
60 }
61 return retval;
62}
63
64//---------------------------------------------------------------------------
65//LPEKnot specific Interval manipulation.
66//---------------------------------------------------------------------------
67
68//remove an interval from an union of intervals.
69//TODO: is it worth moving it to 2Geom?
70static
71std::vector<Geom::Interval> complementOf(Geom::Interval I, std::vector<Geom::Interval> domain){
72 std::vector<Geom::Interval> ret;
73 if (!domain.empty()) {
74 double min = domain.front().min();
75 double max = domain.back().max();
76 Geom::Interval I1 = Geom::Interval(min,I.min());
77 Geom::Interval I2 = Geom::Interval(I.max(),max);
78
79 for (auto i : domain){
80 std::optional<Geom::Interval> I1i = intersect(i,I1);
81 if (I1i && !I1i->isSingular()) ret.push_back(*I1i);
82 std::optional<Geom::Interval> I2i = intersect(i,I2);
83 if (I2i && !I2i->isSingular()) ret.push_back(*I2i);
84 }
85 }
86 return ret;
87}
88
89//find the time interval during which patha is hidden by pathb near a given crossing.
90// Warning: not accurate!
91static
93findShadowedTime(Geom::Path const &patha, std::vector<Geom::Point> const &pt_and_dir,
94 double const ta, double const width){
95 using namespace Geom;
96 Point T = unit_vector(pt_and_dir[1]);
97 Point N = T.cw();
98 //Point A = pt_and_dir[0] - 3 * width * T;
99 //Point B = A+6*width*T;
100
101 Affine mat = from_basis( T, N, pt_and_dir[0] );
102 mat = mat.inverse();
103 Geom::Path p = patha * mat;
104
105 std::vector<double> times;
106
107 //TODO: explore the path fwd/backward from ta (worth?)
108 for (unsigned i = 0; i < size_nondegenerate(patha); i++){
109 D2<SBasis> f = p[i].toSBasis();
110 std::vector<double> times_i, temptimes;
111 temptimes = roots(f[Y]-width);
112 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
113 temptimes = roots(f[Y]+width);
114 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
115 temptimes = roots(f[X]-3*(width?width:EPSILON/2.0));
116 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
117 temptimes = roots(f[X]+3*(width?width:EPSILON/2.0));
118 times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
119 for (double & k : times_i){
120 k+=i;
121 }
122 times.insert(times.end(), times_i.begin(), times_i.end() );
123 }
124 std::sort( times.begin(), times.end() );
125 std::vector<double>::iterator new_end = std::unique( times.begin(), times.end() );
126 times.resize( new_end - times.begin() );
127
128 double tmin = 0, tmax = size_nondegenerate(patha);
129 double period = size_nondegenerate(patha);
130 if (!times.empty()){
131 unsigned rk = upper_bound( times.begin(), times.end(), ta ) - times.begin();
132 if ( rk < times.size() )
133 tmax = times[rk];
134 else if ( patha.closed() )
135 tmax = times[0]+period;
136
137 if ( rk > 0 )
138 tmin = times[rk-1];
139 else if ( patha.closed() )
140 tmin = times.back()-period;
141 }
142 return Interval(tmin,tmax);
143}
144
145//---------------------------------------------------------------------------
146//LPEKnot specific Crossing Data manipulation.
147//---------------------------------------------------------------------------
148
149//Yet another crossing data representation.
150// an CrossingPoint stores
151// -an intersection point
152// -the involved path components
153// -for each component, the time at which this crossing occurs + the order of this crossing along the component (when starting from 0).
154
155namespace LPEKnotNS {//just in case...
157// std::cout<<"\nCrossingPoints creation from path vector\n";
158 for( unsigned i=0; i<paths.size(); i++){
159 for( unsigned ii=0; ii < size_nondegenerate(paths[i]); ii++){
160 for( unsigned j=i; j<paths.size(); j++){
161 for( unsigned jj=(i==j?ii:0); jj < size_nondegenerate(paths[j]); jj++){
162 std::vector<std::pair<double,double> > times;
163 if ( (i==j) && (ii==jj) ) {
164
165// std::cout<<"--(self int)\n";
166// std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
167// std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
168
169 find_self_intersections( times, paths[i][ii].toSBasis() );
170 } else {
171// std::cout<<"--(pair int)\n";
172// std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
173// std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
174// std::cout<<"with\n";
175// std::cout << paths[j][jj].toSBasis()[Geom::X] <<"\n";
176// std::cout << paths[j][jj].toSBasis()[Geom::Y] <<"\n";
177
178 find_intersections( times, paths[i][ii].toSBasis(), paths[j][jj].toSBasis() );
179 }
180 for (auto & time : times){
181 //std::cout<<"intersection "<<i<<"["<<ii<<"]("<<times[k].first<<")= "<<j<<"["<<jj<<"]("<<times[k].second<<")\n";
182 if ( !std::isnan(time.first) && !std::isnan(time.second) ){
183 double zero = 1e-4;
184 if ( (i==j) && (fabs(time.first+ii - time.second-jj) <= zero) )
185 { //this is just end=start of successive curves in a path.
186 continue;
187 }
188 if ( (i==j) && (ii == 0) && (jj == size_nondegenerate(paths[i])-1)
189 && paths[i].closed()
190 && (fabs(time.first) <= zero)
191 && (fabs(time.second - 1) <= zero) )
192 {//this is just end=start of a closed path.
193 continue;
194 }
195 CrossingPoint cp;
196 cp.pt = paths[i][ii].pointAt(time.first);
197 cp.sign = 1;
198 cp.i = i;
199 cp.j = j;
200 cp.ni = 0; cp.nj=0;//not set yet
201 cp.ti = time.first + ii;
202 cp.tj = time.second + jj;
203 push_back(cp);
204 }else{
205 std::cerr<<"ooops: find_(self)_intersections returned NaN:" << std::endl;
206 //std::cout<<"intersection "<<i<<"["<<ii<<"](NaN)= "<<j<<"["<<jj<<"](NaN)\n";
207 }
208 }
209 }
210 }
211 }
212 }
213 for( unsigned i=0; i<paths.size(); i++){
214 std::map < double, unsigned > cuts;
215 for( unsigned k=0; k<size(); k++){
216 CrossingPoint cp = (*this)[k];
217 if (cp.i == i) cuts[cp.ti] = k;
218 if (cp.j == i) cuts[cp.tj] = k;
219 }
220 unsigned count = 0;
221 for (auto & cut : cuts){
222 if ( ((*this)[cut.second].i == i) && ((*this)[cut.second].ti == cut.first) ){
223 (*this)[cut.second].ni = count;
224 }else{
225 (*this)[cut.second].nj = count;
226 }
227 count++;
228 }
229 }
230}
231
232CrossingPoints::CrossingPoints(std::vector<double> const &input) : std::vector<CrossingPoint>()
233{
234 if ( (input.size() > 0) && (input.size()%9 == 0) ){
235 using namespace Geom;
236 for( unsigned n=0; n<input.size(); ){
237 CrossingPoint cp;
238 cp.pt[X] = input[n++];
239 cp.pt[Y] = input[n++];
240 cp.i = input[n++];
241 cp.j = input[n++];
242 cp.ni = input[n++];
243 cp.nj = input[n++];
244 cp.ti = input[n++];
245 cp.tj = input[n++];
246 cp.sign = input[n++];
247 push_back(cp);
248 }
249 }
250}
251
252std::vector<double>
254{
255 using namespace Geom;
256 std::vector<double> result;
257 for( unsigned n=0; n<size(); n++){
258 CrossingPoint cp = (*this)[n];
259 result.push_back(cp.pt[X]);
260 result.push_back(cp.pt[Y]);
261 result.push_back(double(cp.i));
262 result.push_back(double(cp.j));
263 result.push_back(double(cp.ni));
264 result.push_back(double(cp.nj));
265 result.push_back(double(cp.ti));
266 result.push_back(double(cp.tj));
267 result.push_back(double(cp.sign));
268 }
269 return result;
270}
271
272//FIXME: rewrite to check success: return bool, put result in arg.
274CrossingPoints::get(unsigned const i, unsigned const ni)
275{
276 for (unsigned k=0; k<size(); k++){
277 if ( ( ((*this)[k].i==i) && ((*this)[k].ni==ni) )
278 || ( ((*this)[k].j==i) && ((*this)[k].nj==ni) ) )
279 {
280 return (*this)[k];
281 }
282 }
283 g_warning("LPEKnotNS::CrossingPoints::get error. %uth crossing along string %u not found.",ni,i);
284 assert(false);//debug purpose...
285 return CrossingPoint();
286}
287
288static unsigned
290{
291 double dist=-1;
292 unsigned result = cpts.size();
293 for (unsigned k=0; k<cpts.size(); k++){
294 double dist_k = Geom::L2(p-cpts[k].pt);
295 if ( (dist < 0) || (dist > dist_k) ) {
296 result = k;
297 dist = dist_k;
298 }
299 }
300 return result;
301}
302
303//TODO: Find a way to warn the user when the topology changes.
304//TODO: be smarter at guessing the signs when the topology changed?
305void
306CrossingPoints::inherit_signs(CrossingPoints const &other, int default_value)
307{
308 bool topo_changed = false;
309 for (unsigned n=0; n < size(); n++){
310 if ( (n < other.size())
311 && (other[n].i == (*this)[n].i)
312 && (other[n].j == (*this)[n].j)
313 && (other[n].ni == (*this)[n].ni)
314 && (other[n].nj == (*this)[n].nj) )
315 {
316 (*this)[n].sign = other[n].sign;
317 } else {
318 topo_changed = true;
319 break;
320 }
321 }
322 if (topo_changed) {
323 //TODO: Find a way to warn the user!!
324// std::cout<<"knot topolgy changed!\n";
325 for (unsigned n=0; n < size(); n++){
326 Geom::Point p = (*this)[n].pt;
327 unsigned idx = idx_of_nearest(other,p);
328 if (idx < other.size()) {
329 (*this)[n].sign = other[idx].sign;
330 } else {
331 (*this)[n].sign = default_value;
332 }
333 }
334 }
335}
336
337}
338
339//---------------------------------------------------------------------------
340//---------------------------------------------------------------------------
341//LPEKnot effect.
342//---------------------------------------------------------------------------
343//---------------------------------------------------------------------------
344
345
347 : Effect(lpeobject)
348 ,
349 // initialise your parameters here:
350 interruption_width(_("_Gap length:"), _("Size of hidden region of lower string"), "interruption_width", &wr, this,
351 3)
352 , prop_to_stroke_width(
353 _("_In units of stroke width"),
354 _("Gap width is given in multiples of stroke width. When unchecked, document units are used."),
355 "prop_to_stroke_width", &wr, this, true)
356 , both(_("_Gaps in both"), _("At path intersections, both parts will have a gap"), "both", &wr, this, false)
357 , inverse_width(_("_Groups: Inverse"), _("Use other stroke width, useful in groups with different stroke widths"),
358 "inverse_width", &wr, this, false)
359 , add_stroke_width("St_roke width", "Add the stroke width to the gap size", "add_stroke_width", &wr, this,
360 "inkscape_1.0_and_up", true)
361 , add_other_stroke_width("_Crossing path stroke width", "Add crossed stroke width to the gap size",
362 "add_other_stroke_width", &wr, this, "inkscape_1.0_and_up", true)
363 , switcher_size(_("S_witcher size:"), _("Orientation indicator/switcher size"), "switcher_size", &wr, this, 15)
364 , crossing_points_vector(_("Crossing Signs"), _("Crossing signs"), "crossing_points_vector", &wr, this)
365 , crossing_points()
366 , gpaths()
367 , gstroke_widths()
368 , selectedCrossing(0)
369 , switcher(0., 0.)
370{
371 // register all your parameters here, so Inkscape knows which parameters this effect has:
380
382}
383
384LPEKnot::~LPEKnot() = default;
385
386void
388 if (selectedCrossing < crossing_points.size()){
390 //std::cout<<"placing switcher at "<<switcher<<" \n";
391 }else if (crossing_points.size()>0){
394 //std::cout<<"placing switcher at "<<switcher<<" \n";
395 }else{
396 //std::cout<<"hiding switcher!\n";
398 }
399}
400
403{
404 using namespace Geom;
405 Geom::PathVector path_out;
406
407 if (gpaths.size()==0){
408 return path_in;
409 }
410 Geom::PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(path_in);
411 for (const auto & comp : original_pathv){
412
413 //find the relevant path component in gpaths (required to allow groups!)
414 //Q: do we always receive the group members in the same order? can we rest on that?
415 unsigned i0 = 0;
417 gint precision = prefs->getInt("/options/svgoutput/numericprecision");
418 prefs->setInt("/options/svgoutput/numericprecision", 4); // I think this is enough for minor differences
419 for (i0=0; i0<gpaths.size(); i0++){
421 break;
422 }
423 prefs->setInt("/options/svgoutput/numericprecision", precision);
424 if (i0 == gpaths.size() ) {THROW_EXCEPTION("lpe-knot error: group member not recognized");}// this should not happen...
425
426 std::vector<Interval> dom;
427 dom.emplace_back(0., size_nondegenerate(gpaths[i0]));
428 for (auto const &crossing_point : crossing_points) {
429 if ((crossing_point.i == i0) || (crossing_point.j == i0)) {
430 unsigned i = crossing_point.i;
431 unsigned j = crossing_point.j;
432 double ti = crossing_point.ti;
433 double tj = crossing_point.tj;
434
435 double curveidx, t;
436
437 t = modf(ti, &curveidx);
438 if(curveidx == size_nondegenerate(gpaths[i]) ) { curveidx--; t = 1.;}
439 assert(curveidx >= 0 && curveidx < size_nondegenerate(gpaths[i]));
440 std::vector<Point> flag_i = gpaths[i][curveidx].pointAndDerivatives(t,1);
441
442 t = modf(tj, &curveidx);
443 if(curveidx == size_nondegenerate(gpaths[j]) ) { curveidx--; t = 1.;}
444 assert(curveidx >= 0 && curveidx < size_nondegenerate(gpaths[j]));
445 std::vector<Point> flag_j = gpaths[j][curveidx].pointAndDerivatives(t,1);
446
447
448 int geom_sign = ( cross(flag_i[1], flag_j[1]) < 0 ? 1 : -1);
449 bool i0_is_under = false;
450 double width = interruption_width;
451 if ( crossing_point.sign * geom_sign > 0 ){
452 i0_is_under = ( i == i0 );
453 }
454 else if (crossing_point.sign * geom_sign < 0) {
455 if (j == i0){
456 i0_is_under = true;
457 }
458 }
459 i0_is_under = crossing_point.sign != 0 && both ? true : i0_is_under;
460 if (i0_is_under && j == i0) {
461 // last check of sign makes sure we get different outputs when
462 // path components are part of the same subpath (i == j)
463 if (!(i == j && !both && crossing_point.sign * geom_sign > 0)) {
464 std::swap(i, j);
465 std::swap(ti, tj);
466 std::swap(flag_i, flag_j);
467 }
468 }
469 if (i0_is_under){
471 if (inverse_width) {
472 width *= gstroke_widths[j];
473 }
474 else {
475 width *= gstroke_widths[i];
476 }
477 }
478 if (add_stroke_width.get_value() == "true") {
479 width += gstroke_widths[i];
480 }
481 if (add_other_stroke_width.get_value() == "true") {
482 width += gstroke_widths[j];
483 }
484 Interval hidden = findShadowedTime(gpaths[i0], flag_j, ti, width/2);
485 double period = size_nondegenerate(gpaths[i0]);
486 if (hidden.max() > period ) hidden -= period;
487 if (hidden.min()<0){
488 dom = complementOf( Interval(0,hidden.max()) ,dom);
489 dom = complementOf( Interval(hidden.min()+period, period) ,dom);
490 }else{
491 dom = complementOf(hidden,dom);
492 }
493 if (crossing_point.i == i0 && crossing_point.j == i0 && crossing_point.sign != 0 &&
494 both) {
495 hidden = findShadowedTime(gpaths[i0], flag_i, tj, width / 2);
496 period = size_nondegenerate(gpaths[i0]);
497 if (hidden.max() > period)
498 hidden -= period;
499 if (hidden.min() < 0) {
500 dom = complementOf(Interval(0, hidden.max()), dom);
501 dom = complementOf(Interval(hidden.min() + period, period), dom);
502 }
503 else {
504 dom = complementOf(hidden, dom);
505 }
506 }
507 }
508 }
509 }
510
511 //If the all component is hidden, continue.
512 if (dom.empty()){
513 continue;
514 }
515
516 //If the current path is closed and the last/first point is still there, glue first and last piece.
517 unsigned beg_comp = 0, end_comp = dom.size();
518 if ( gpaths[i0].closed() && (dom.front().min() == 0) && (dom.back().max() == size_nondegenerate(gpaths[i0])) ) {
519 if ( dom.size() == 1){
520 path_out.push_back(gpaths[i0]);
521 continue;
522 }else{
523 // std::cout<<"fusing first and last component\n";
524 ++beg_comp;
525 --end_comp;
526 Geom::Path first = gpaths[i0].portion(dom.back());
527 //FIXME: stitching should not be necessary (?!?)
528 first.setStitching(true);
529 first.append(gpaths[i0].portion(dom.front()));
530 path_out.push_back(first);
531 }
532 }
533 for (unsigned comp = beg_comp; comp < end_comp; comp++){
534 assert(dom.at(comp).min() >=0 && dom.at(comp).max() <= size_nondegenerate(gpaths.at(i0)));
535 path_out.push_back(gpaths[i0].portion(dom.at(comp)));
536 }
537 }
538 return path_out;
539}
540
541
542
543//recursively collect gpaths and stroke widths (stolen from "sp-lpe_item.cpp").
544static void
545collectPathsAndWidths (SPLPEItem const *lpeitem, Geom::PathVector &paths, std::vector<double> &stroke_widths){
546 auto lpeitem_mutable = const_cast<SPLPEItem *>(lpeitem);
547
548 if (auto group = cast<SPGroup>(lpeitem_mutable)) {
549 std::vector<SPItem*> item_list = group->item_list();
550 for (auto subitem : item_list) {
551 if (is<SPLPEItem>(subitem)) {
552 collectPathsAndWidths(cast<SPLPEItem>(subitem), paths, stroke_widths);
553 }
554 }
555 } else if (auto shape = cast<SPShape>(lpeitem)) {
556 SPCurve const *c = shape->curve();
557 if (c) {
558 Geom::PathVector subpaths = pathv_to_linear_and_cubic_beziers(c->get_pathvector());
559 for (const auto & subpath : subpaths){
560 paths.push_back(subpath);
561 //FIXME: do we have to be more careful when trying to access stroke width?
562 stroke_widths.push_back(lpeitem->style->stroke_width.computed);
563 }
564 }
565 }
566}
567
568
569void
571{
572 using namespace Geom;
573 original_bbox(lpeitem);
574
575 if (is<SPPath>(lpeitem)) {
576 supplied_path = cast<SPPath>(lpeitem)->curve()->get_pathvector();
577 }
578
579 gpaths.clear();
580 gstroke_widths.clear();
581
583
584// std::cout<<"\nPaths on input:\n";
585// for (unsigned i=0; i<gpaths.size(); i++){
586// for (unsigned ii=0; ii<gpaths[i].size(); ii++){
587// std::cout << gpaths[i][ii].toSBasis()[Geom::X] <<"\n";
588// std::cout << gpaths[i][ii].toSBasis()[Geom::Y] <<"\n";
589// std::cout<<"--\n";
590// }
591// }
592
593 //std::cout<<"crossing_pts_vect: "<<crossing_points_vector.param_getSVGValue()<<".\n";
594 //std::cout<<"prop_to_stroke_width: "<<prop_to_stroke_width.param_getSVGValue()<<".\n";
595
597
598// std::cout<<"\nVectorParam size:"<<crossing_points_vector.data().size()<<"\n";
599
600// std::cout<<"\nOld crdata ("<<old_crdata.size()<<"): \n";
601// for (unsigned toto=0; toto<old_crdata.size(); toto++){
602// std::cout<<"(";
603// std::cout<<old_crdata[toto].i<<",";
604// std::cout<<old_crdata[toto].j<<",";
605// std::cout<<old_crdata[toto].ni<<",";
606// std::cout<<old_crdata[toto].nj<<",";
607// std::cout<<old_crdata[toto].ti<<",";
608// std::cout<<old_crdata[toto].tj<<",";
609// std::cout<<old_crdata[toto].sign<<"),";
610// }
611
612 //if ( old_crdata.size() > 0 ) std::cout<<"first crossing sign = "<<old_crdata[0].sign<<".\n";
613 //else std::cout<<"old data is empty!!\n";
615// std::cout<<"\nNew crdata ("<<crossing_points.size()<<"): \n";
616// for (unsigned toto=0; toto<crossing_points.size(); toto++){
617// std::cout<<"(";
618// std::cout<<crossing_points[toto].i<<",";
619// std::cout<<crossing_points[toto].j<<",";
620// std::cout<<crossing_points[toto].ni<<",";
621// std::cout<<crossing_points[toto].nj<<",";
622// std::cout<<crossing_points[toto].ti<<",";
623// std::cout<<crossing_points[toto].tj<<",";
624// std::cout<<crossing_points[toto].sign<<"),";
625// }
626 crossing_points.inherit_signs(old_crdata);
627
628 // Don't write to XML here, only store it in the param itself. Will be written to SVG later
630
632}
633
634void
635LPEKnot::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
636{
637 using namespace Geom;
638 double r = switcher_size*.1;
639 char const * svgd;
640 //TODO: use a nice path!
641 if ( (selectedCrossing >= crossing_points.size()) || (crossing_points[selectedCrossing].sign > 0) ) {
642 //svgd = "M -10,0 A 10 10 0 1 0 0,-10 l 5,-1 -1,2";
643 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";
644 } else if (crossing_points[selectedCrossing].sign < 0) {
645 //svgd = "M 10,0 A 10 10 0 1 1 0,-10 l -5,-1 1,2";
646 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";
647 } else {
648 //svgd = "M 10,0 A 10 10 0 1 0 -10,0 A 10 10 0 1 0 10,0 ";
649 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";
650 }
651 PathVector pathv = sp_svg_read_pathv(svgd);
652 pathv *= Affine(r,0,0,r,0,0) * Translate(switcher);
653 hp_vec.push_back(pathv);
654}
655
657{
659 e->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:CrossingSwitcher",
660 _("Drag to select a crossing, click to flip it, Shift + click to change all crossings, Ctrl + click to "
661 "reset and change all crossings"));
662 knotholder->add(e);
663};
664
665
666void
667KnotHolderEntityCrossingSwitcher::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
668{
669 LPEKnot* lpe = dynamic_cast<LPEKnot *>(_effect);
670
671 lpe->selectedCrossing = idx_of_nearest(lpe->crossing_points,p);
672 lpe->updateSwitcher();
673 // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
674 sp_lpe_item_update_patheffect (cast<SPLPEItem>(item), false, true);
675}
676
678KnotHolderEntityCrossingSwitcher::knot_get() const
679{
680 LPEKnot const *lpe = dynamic_cast<LPEKnot const*>(_effect);
681 return lpe->switcher;
682}
683
684void
685KnotHolderEntityCrossingSwitcher::knot_click(guint state)
686{
687 LPEKnot* lpe = dynamic_cast<LPEKnot *>(_effect);
688 unsigned s = lpe->selectedCrossing;
689 if (s < lpe->crossing_points.size()){
690 if (state & GDK_SHIFT_MASK){
691 for (auto &crossing_point : lpe->crossing_points) {
692 crossing_point.sign = ((crossing_point.sign + 2) % 3) - 1;
693 }
694 }
695 else if (state & GDK_CONTROL_MASK) {
696 int sign = lpe->crossing_points[s].sign;
697 for (auto &crossing_point : lpe->crossing_points) {
698 crossing_point.sign = ((sign + 2) % 3) - 1;
699 }
700 }else{
701 int sign = lpe->crossing_points[s].sign;
702 lpe->crossing_points[s].sign = ((sign+2)%3)-1;
703 //std::cout<<"crossing set to"<<lpe->crossing_points[s].sign<<".\n";
704 }
705 lpe->crossing_points_vector.param_set_and_write_new_value(lpe->crossing_points.to_vector());
706 lpe->makeUndoDone(_("Change knot crossing"));
707 // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
708// sp_lpe_item_update_patheffect (cast<SPLPEItem>(item), false, true);
709 }
710}
711
712
713/* ######################## */
714
715} // namespace LivePathEffect
716} // namespace Inkscape
717
718/*
719 Local Variables:
720 mode:c++
721 c-file-style:"stroustrup"
722 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
723 indent-tabs-mode:nil
724 fill-column:99
725 End:
726*/
727// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
728
Point origin
Definition aa.cpp:227
Basic intersection routines.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
virtual Point initialPoint() const =0
Retrieve the start of the curve.
virtual Point finalPoint() const =0
Retrieve the end of the curve.
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
constexpr C min() const
constexpr C max() const
Range of real numbers that is never empty.
Definition interval.h:59
Sequence of subpaths.
Definition pathvector.h:122
size_type size() const
Get the number of paths in the vector.
Definition pathvector.h:147
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
Path & at(size_type index)
Definition pathvector.h:161
void clear()
Remove all paths from the vector.
Definition pathvector.h:195
Sequence of contiguous curves, aka spline.
Definition path.h:353
bool closed() const
Check whether the path is closed.
Definition path.h:503
void setStitching(bool x)
Enable or disable the throwing of exceptions when stitching discontinuities.
Definition path.h:827
Curve const & back_closed() const
Definition path.h:452
size_type size_open() const
Size without the closing segment, even if the path is closed.
Definition path.h:476
size_type size_default() const
Natural size of the path.
Definition path.h:486
Sequence::size_type size_type
Definition path.h:359
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Point cw() const
Return a point like this point but rotated +90 degrees.
Definition point.h:137
Translation by a vector.
Definition transforms.h:115
void param_setValue(std::vector< StorageType > const &new_vector)
Definition array.h:84
std::vector< StorageType > const & data() const
Definition array.h:48
void registerParameter(Parameter *param)
Definition effect.cpp:1710
void original_bbox(SPLPEItem const *lpeitem, bool absolute=false, bool clip_mask=false, Geom::Affine base_transform=Geom::identity())
const Glib::ustring get_value() const
Definition hidden.h:50
void inherit_signs(CrossingPoints const &from_other, int default_value=1)
Definition lpe-knot.cpp:306
CrossingPoint get(unsigned const i, unsigned const ni)
Definition lpe-knot.cpp:274
Geom::PathVector supplied_path
Definition lpe-knot.h:72
friend class KnotHolderEntityCrossingSwitcher
Definition lpe-knot.h:67
LPEKnotNS::CrossingPoints crossing_points
Definition lpe-knot.h:88
LPEKnot(LivePathEffectObject *lpeobject)
Definition lpe-knot.cpp:346
void doBeforeEffect(SPLPEItem const *lpeitem) override
Is performed each time before the effect is updated.
Definition lpe-knot.cpp:570
void addKnotHolderEntities(KnotHolder *knotholder, SPItem *item) override
Definition lpe-knot.cpp:656
void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector< Geom::PathVector > &hp_vec) override
Add possible canvas indicators (i.e., helperpaths other than the original path) to hp_vec This functi...
Definition lpe-knot.cpp:635
ArrayParam< double > crossing_points_vector
Definition lpe-knot.h:86
std::vector< double > gstroke_widths
Definition lpe-knot.h:91
Geom::PathVector doEffect_path(Geom::PathVector const &input_path) override
Definition lpe-knot.cpp:402
Preference storage class.
Definition preferences.h:66
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
KnotHolderEntity definition.
virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned state)=0
void create(SPDesktop *desktop, SPItem *item, KnotHolder *parent, Inkscape::CanvasItemCtrlType type=Inkscape::CANVAS_ITEM_CTRL_TYPE_DEFAULT, Glib::ustring const &name="unknown", char const *tip="", uint32_t color=0xffffff00)
virtual void knot_click(unsigned)
void add(KnotHolderEntity *e)
Wrapper around a Geom::PathVector object.
Definition curve.h:26
Base class for visual SVG elements.
Definition sp-item.h:109
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
T< SPAttr::STROKE_WIDTH, SPILength > stroke_width
stroke-width
Definition style.h:249
Css & result
double c[8][4]
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Geom::PathVector pathv_to_linear_and_cubic_beziers(Geom::PathVector const &pathv)
Definition geom.cpp:588
Specific geometry functions for Inkscape, not provided my lib2geom.
SPItem * item
LPE knot effect implementation, see lpe-knot.cpp.
vector< vector< Point > > paths
Definition metro.cpp:36
Various utility functions.
Definition affine.h:22
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
Affine from_basis(const Point &x_basis, const Point &y_basis, const Point &offset=Point(0, 0))
Creates a Affine given an axis and origin point.
Definition affine.cpp:26
static float sign(double number)
Returns +1 for positive numbers, -1 for negative numbers, and 0 otherwise.
std::vector< double > roots(SBasis const &s)
void find_intersections(std::vector< std::pair< double, double > > &xs, D2< Bezier > const &A, D2< Bezier > const &B, double precision=EPSILON)
Bezier portion(const Bezier &a, double from, double to)
Definition bezier.cpp:250
Piecewise< SBasis > cross(Piecewise< D2< SBasis > > const &a, Piecewise< D2< SBasis > > const &b)
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
SBasis toSBasis(SBasisN< 1 > f)
Definition sbasisN.h:640
Point unit_vector(Point const &a)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
std::vector< Point > intersect(const xAx &C1, const xAx &C2)
Definition conicsec.cpp:361
void find_self_intersections(std::vector< std::pair< double, double > > &xs, D2< SBasis > const &A, double precision=EPSILON)
static unsigned idx_of_nearest(CrossingPoints const &cpts, Geom::Point const &p)
Definition lpe-knot.cpp:289
static void collectPathsAndWidths(SPLPEItem const *lpeitem, Geom::PathVector &paths, std::vector< double > &stroke_widths)
Definition lpe-knot.cpp:545
static std::vector< Geom::Interval > complementOf(Geom::Interval I, std::vector< Geom::Interval > domain)
Definition lpe-knot.cpp:71
static Geom::Interval findShadowedTime(Geom::Path const &patha, std::vector< Geom::Point > const &pt_and_dir, double const ta, double const width)
Definition lpe-knot.cpp:93
static Geom::Path::size_type size_nondegenerate(Geom::Path const &path)
Definition lpe-knot.cpp:49
Helper class to stream background task notifications as a series of messages.
@ CANVAS_ITEM_CTRL_TYPE_LPE
STL namespace.
Singleton class to access the preferences file in a convenient way.
size_t N
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
SPStyle - a style object for SPItem objects.
Geom::PathVector sp_svg_read_pathv(char const *str)
Definition svg-path.cpp:37
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
double width