index 3e87bc51bbf03bb03d8548f9f2364e316ae314a5..94ced04ae3fb25f54a995bc016068003b3a0cfcb 100644 (file)
-#define INKSCAPE_LPE_KNOT_CPP\r
-/** \file\r
- * LPE <knot> implementation\r
- */\r
-/*\r
- * Authors:\r
- * Johan Engelen\r
-*\r
-* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>\r
- *\r
- * Released under GNU GPL, read the file 'COPYING' for more information\r
- */\r
-\r
-#include "live_effects/lpe-knot.h"\r
-#include "display/curve.h"\r
-#include <libnr/n-art-bpath.h>\r
-\r
-// You might need to include other 2geom files. You can add them here:\r
-#include <2geom/path.h>\r
-#include <2geom/sbasis.h>\r
-#include <2geom/sbasis-geometric.h>\r
-#include <2geom/bezier-to-sbasis.h>\r
-#include <2geom/sbasis-to-bezier.h>\r
-#include <2geom/d2.h>\r
-#include <2geom/sbasis-math.h>\r
-#include <2geom/piecewise.h>\r
-#include <2geom/crossing.h>\r
-#include <2geom/path-intersection.h>\r
-\r
-namespace Inkscape {\r
-namespace LivePathEffect {\r
-\r
-LPEKnot::LPEKnot(LivePathEffectObject *lpeobject) :\r
- Effect(lpeobject),\r
- // initialise your parameters here:\r
- interruption_width(_("Interruption width"), _("Howmuch the lower strand is obscured by the upper."), "interruption_width", &wr, this, 1.2)\r
-{\r
- // register all your parameters here, so Inkscape knows which parameters this effect has:\r
- registerParameter( dynamic_cast<Parameter *>(&interruption_width) );\r
-}\r
-\r
-LPEKnot::~LPEKnot()\r
-{\r
-\r
-}\r
-\r
-\r
-/* ########################\r
- * Choose to implement one of the doEffect functions. You can delete or comment out the others.\r
-*/\r
-\r
-/*\r
-void\r
-LPEKnot::doEffect (SPCurve * curve)\r
-{\r
- // spice this up to make the effect actually *do* something!\r
-}\r
-\r
-NArtBpath *\r
-LPEKnot::doEffect_nartbpath (NArtBpath * path_in)\r
-{\r
- NArtBpath *path_out;\r
- unsigned ret = 0;\r
- while ( path_in[ret].code != NR_END ) {\r
- ++ret;\r
- }\r
- unsigned len = ++ret;\r
- path_out = g_new(NArtBpath, len);\r
-\r
- memcpy(path_out, path_in, len * sizeof(NArtBpath)); // spice this up to make the effect actually *do* something!\r
-\r
- return path_out;\r
-}\r
-*/\r
-\r
-std::vector<Geom::Interval> complementOf(Geom::Interval I, std::vector<Geom::Interval> domain){\r
- std::vector<Geom::Interval> ret;\r
- double min = domain.front().min();\r
- double max = domain.back().max();\r
- Geom::Interval I1 = Geom::Interval(min,I.min());\r
- Geom::Interval I2 = Geom::Interval(I.max(),max);\r
-\r
- for (unsigned i = 0; i<domain.size(); i++){\r
- boost::optional<Geom::Interval> I1i = intersect(domain.at(i),I1);\r
- if (I1i) ret.push_back(I1i.get());\r
- boost::optional<Geom::Interval> I2i = intersect(domain.at(i),I2);\r
- if (I2i) ret.push_back(I2i.get());\r
- }\r
- return ret;\r
-}\r
-\r
-Geom::Interval\r
-findShadowedTime(Geom::Path &patha, \r
- Geom::Path &pathb, \r
- Geom::Crossing crossing, \r
- unsigned idx, double width){\r
- using namespace Geom;\r
- double curveidx, timeoncurve = modf(crossing.getOtherTime(idx),&curveidx);\r
- if(curveidx == pathb.size() && timeoncurve == 0) { curveidx--; timeoncurve = 0.99999;}\r
- assert(curveidx >= 0 && curveidx < pathb.size());\r
- \r
- std::vector<Point> MV = pathb[unsigned(curveidx)].pointAndDerivatives(timeoncurve,2);\r
- Point T = unit_vector(MV.at(1));\r
- Point N = T.cw();\r
- Point A = MV.at(0)-10*width*T, B = MV.at(0)+10*width*T;\r
- \r
- std::vector<Geom::Path> cutter;\r
- Geom::Path cutterLeft = Geom::Path();\r
- Geom::Path cutterRight = Geom::Path();\r
- cutterLeft.append (LineSegment (A-width*N, B-width*N));\r
- cutterRight.append(LineSegment (A+width*N, B+width*N));\r
- cutter.push_back(cutterLeft);\r
- cutter.push_back(cutterRight);\r
- \r
- std::vector<Geom::Path> patha_as_vect = std::vector<Geom::Path>(1,patha);\r
- \r
- CrossingSet crossingTable = crossings (patha_as_vect, cutter);\r
- double t0 = crossing.getTime(idx);\r
- double tmin = 0,tmax = patha.size()-0.0001;\r
- assert(crossingTable.size()>=1);\r
- for (unsigned c=0; c<crossingTable.front().size(); c++){\r
- double t = crossingTable.front().at(c).ta;\r
- assert(crossingTable.front().at(c).a==0);\r
- if (t>tmin and t<t0) tmin = t;\r
- if (t<tmax and t>t0) tmax = t;\r
- }\r
- //return Interval(t0-0.1,t0+0.1);\r
- return Interval(tmin,tmax);\r
-} \r
-\r
-\r
-std::vector<Geom::Path>\r
-LPEKnot::doEffect_path (std::vector<Geom::Path> & path_in)\r
-{\r
- using namespace Geom;\r
- std::vector<Geom::Path> path_out;\r
- double width = interruption_width;\r
- \r
- CrossingSet crossingTable = crossings_among(path_in);\r
- for (unsigned i = 0; i < crossingTable.size(); i++){\r
- std::vector<Interval> dom;\r
- dom.push_back(Interval(0.,path_in.at(i).size()-0.00001));\r
- //TODO: handle closed curves...\r
- for (unsigned crs = 0; crs < crossingTable.at(i).size(); crs++){\r
- Crossing crossing = crossingTable.at(i).at(crs);\r
- unsigned j = crossing.getOther(i);\r
- //TODO: select dir according to a parameter...\r
- if ((crossing.dir and crossing.a==i) or (not crossing.dir and crossing.b==i) or (i==j)){\r
- if (i==j and not crossing.dir) {\r
- double temp = crossing.ta;\r
- crossing.ta = crossing.tb; \r
- crossing.tb = temp; \r
- crossing.dir = not crossing.dir;\r
- }\r
- Interval hidden = findShadowedTime(path_in.at(i),path_in.at(j),crossing,i,width); \r
- dom = complementOf(hidden,dom);\r
- }\r
- }\r
- for (unsigned comp = 0; comp < dom.size(); comp++){\r
- assert(dom.at(comp).min() >=0 and dom.at(comp).max() < path_in.at(i).size());\r
- path_out.push_back(path_in.at(i).portion(dom.at(comp)));\r
- }\r
- \r
-// std::vector<Point> MV = path_in[0][0].pointAndDerivatives(crossingTable[0][0].getTime(0),2);\r
-// Point U = unit_vector(MV[1]);\r
-// Point N = U.cw();\r
-// Point A = MV[0]-10.*width*U, B = MV[0]+10*width*U;\r
- \r
-// Geom::Path cutter;\r
-// cutter = Geom::Path();\r
-// cutter.append( LineSegment(A+width*N,B+width*N));\r
-// path_out.push_back(cutter);\r
-// cutter = Geom::Path();\r
-// cutter.append( LineSegment(A-width*N,B-width*N));\r
-// path_out.push_back(cutter);\r
-\r
- } \r
- return path_out;\r
-}\r
-\r
-\r
-/*\r
-Geom::Piecewise<Geom::D2<Geom::SBasis> >\r
-addLinearEnds (Geom::Piecewise<Geom::D2<Geom::SBasis> > & m){\r
- using namespace Geom;\r
- Piecewise<D2<SBasis> > output;\r
- Piecewise<D2<SBasis> > start;\r
- Piecewise<D2<SBasis> > end;\r
- double x,y,vx,vy;\r
-\r
- x = m.segs.front()[0].at0();\r
- y = m.segs.front()[1].at0();\r
- vx = m.segs.front()[0][1][0]+Tri(m.segs.front()[0][0]);\r
- vy = m.segs.front()[1][1][0]+Tri(m.segs.front()[1][0]);\r
- start = Piecewise<D2<SBasis> >(D2<SBasis>(Linear (x-vx,x),Linear (y-vy,y)));\r
- start.offsetDomain(m.cuts.front()-1.);\r
-\r
- x = m.segs.back()[0].at1();\r
- y = m.segs.back()[1].at1();\r
- vx = -m.segs.back()[0][1][1]+Tri(m.segs.back()[0][0]);;\r
- vy = -m.segs.back()[1][1][1]+Tri(m.segs.back()[1][0]);;\r
- end = Piecewise<D2<SBasis> >(D2<SBasis>(Linear (x,x+vx),Linear (y,y+vy)));\r
- //end.offsetDomain(m.cuts.back());\r
-\r
- output = start;\r
- output.concat(m);\r
- output.concat(end);\r
- return output;\r
-}\r
-\r
-Geom::Piecewise<Geom::D2<Geom::SBasis> >\r
-LPEKnot::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)\r
-{\r
- using namespace Geom;\r
-\r
-\r
- Piecewise<D2<SBasis> > output;\r
- Piecewise<D2<SBasis> > m = addLinearEnds(pwd2_in);\r
-\r
- Piecewise<D2<SBasis> > v = derivative(pwd2_in);\r
- Piecewise<D2<SBasis> > n = unitVector(v);\r
-\r
-// // -------- Pleins et delies vs courbure ou direction...\r
-// Piecewise<D2<SBasis> > a = derivative(v);\r
-// Piecewise<SBasis> a_cross_n = cross(a,n);\r
-// Piecewise<SBasis> v_dot_n = dot(v,n);\r
-// //Piecewise<D2<SBasis> > rfrac = sectionize(D2<Piecewise<SBasis> >(a_cross_n,v_dot_n));\r
-// //Piecewise<SBasis> h = atan2(rfrac)*interruption_width;\r
-// Piecewise<SBasis> h = reciprocal(curvature(pwd2_in))*interruption_width;\r
-//\r
-// // Piecewise<D2<SBasis> > dir = Piecewise<D2<SBasis> >(D2<SBasis>(Linear(0),Linear(-1)));\r
-// // Piecewise<SBasis> h = dot(n,dir)+1.;\r
-// // h *= h*(interruption_width/4.);\r
-//\r
-// n = rot90(n);\r
-// output = pwd2_in+h*n;\r
-// output.concat(pwd2_in-h*n);\r
-//\r
-// //-----------\r
-\r
- //output.concat(m);\r
- return output;\r
-}\r
-*/\r
-\r
-/* ######################## */\r
-\r
-} //namespace LivePathEffect (setq default-directory "c:/Documents And Settings/jf/Mes Documents/InkscapeSVN")\r
-} /* namespace Inkscape */\r
-\r
-/*\r
- Local Variables:\r
- mode:c++\r
- c-file-style:"stroustrup"\r
- c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
- indent-tabs-mode:nil\r
- fill-column:99\r
- End:\r
-*/\r
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :\r
+/** @file
+ * @brief LPE knot effect implementation
+ */
+/* Authors:
+ * Jean-Francois Barraud <jf.barraud@gmail.com>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "sp-shape.h"
+#include "sp-path.h"
+#include "display/curve.h"
+#include "live_effects/lpe-knot.h"
+#include "svg/svg.h"
+#include "style.h"
+#include "knot-holder-entity.h"
+
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/sbasis.h>
+#include <2geom/d2.h>
+#include <2geom/d2-sbasis.h>
+#include <2geom/path.h>
+//#include <2geom/crossing.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/basic-intersection.h>
+#include <2geom/exception.h>
+
+// for change crossing undo
+#include "verbs.h"
+#include "document.h"
+
+#include <exception>
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+class KnotHolderEntityCrossingSwitcher : public LPEKnotHolderEntity
+{
+public:
+ virtual ~KnotHolderEntityCrossingSwitcher() {}
+
+ virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
+ virtual Geom::Point knot_get();
+ virtual void knot_click(guint state);
+};
+
+
+//---------------------------------------------------------------------------
+//LPEKnot specific Interval manipulation.
+//---------------------------------------------------------------------------
+
+//remove an interval from an union of intervals.
+//TODO: is it worth moving it to 2Geom?
+static
+std::vector<Geom::Interval> complementOf(Geom::Interval I, std::vector<Geom::Interval> domain){
+ std::vector<Geom::Interval> ret;
+ if (!domain.empty()) {
+ double min = domain.front().min();
+ double max = domain.back().max();
+ Geom::Interval I1 = Geom::Interval(min,I.min());
+ Geom::Interval I2 = Geom::Interval(I.max(),max);
+
+ for (unsigned i = 0; i<domain.size(); i++){
+ boost::optional<Geom::Interval> I1i = intersect(domain.at(i),I1);
+ if (I1i && !I1i->isSingular()) ret.push_back(I1i.get());
+ boost::optional<Geom::Interval> I2i = intersect(domain.at(i),I2);
+ if (I2i && !I2i->isSingular()) ret.push_back(I2i.get());
+ }
+ }
+ return ret;
+}
+
+//find the time interval during which patha is hidden by pathb near a given crossing.
+// Warning: not accurate!
+static
+Geom::Interval
+findShadowedTime(Geom::Path const &patha, std::vector<Geom::Point> const &pt_and_dir,
+ double const ta, double const width){
+ using namespace Geom;
+ Point T = unit_vector(pt_and_dir[1]);
+ Point N = T.cw();
+ Point A = pt_and_dir[0]-3*width*T, B = A+6*width*T;
+
+ Matrix mat = from_basis( T, N, pt_and_dir[0] );
+ mat = mat.inverse();
+ Path p = patha * mat;
+
+ std::vector<double> times;
+
+ //TODO: explore the path fwd/backward from ta (worth?)
+ for (unsigned i=0; i<patha.size(); i++){
+ D2<SBasis> f = p[i].toSBasis();
+ std::vector<double> times_i, temptimes;
+ temptimes = roots(f[Y]-width);
+ times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
+ temptimes = roots(f[Y]+width);
+ times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
+ temptimes = roots(f[X]-3*width);
+ times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
+ temptimes = roots(f[X]+3*width);
+ times_i.insert(times_i.end(), temptimes.begin(), temptimes.end() );
+ for (unsigned k=0; k<times_i.size(); k++){
+ times_i[k]+=i;
+ }
+ times.insert(times.end(), times_i.begin(), times_i.end() );
+ }
+ std::sort( times.begin(), times.end() );
+ std::vector<double>::iterator new_end = std::unique( times.begin(), times.end() );
+ times.resize( new_end - times.begin() );
+
+ double tmin = 0, tmax = patha.size();
+ double period = patha.size();//hm... Should this be patha.size()+1?
+ if (times.size()>0){
+ unsigned rk = upper_bound( times.begin(), times.end(), ta ) - times.begin();
+ if ( rk < times.size() )
+ tmax = times[rk];
+ else if ( patha.closed() )
+ tmax = times[0]+period;
+
+ if ( rk > 0 )
+ tmin = times[rk-1];
+ else if ( patha.closed() )
+ tmin = times.back()-period;
+ }
+ return Interval(tmin,tmax);
+}
+
+//---------------------------------------------------------------------------
+//LPEKnot specific Crossing Data manipulation.
+//---------------------------------------------------------------------------
+
+//Yet another crossing data representation.
+// an CrossingPoint stores
+// -an intersection point
+// -the involved path components
+// -for each component, the time at which this crossing occurs + the order of this crossing along the component (when starting from 0).
+
+namespace LPEKnotNS {//just in case...
+CrossingPoints::CrossingPoints(std::vector<Geom::Path> const &paths) : std::vector<CrossingPoint>(){
+// std::cout<<"\nCrossingPoints creation from path vector\n";
+ for( unsigned i=0; i<paths.size(); i++){
+ for( unsigned ii=0; ii<paths[i].size(); ii++){
+ for( unsigned j=i; j<paths.size(); j++){
+ for( unsigned jj=(i==j?ii:0); jj<paths[j].size(); jj++){
+ std::vector<std::pair<double,double> > times;
+ if ( i==j && ii==jj){
+
+// std::cout<<"--(self int)\n";
+// std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
+// std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
+
+ find_self_intersections( times, paths[i][ii].toSBasis() );
+ }else{
+// std::cout<<"--(pair int)\n";
+// std::cout << paths[i][ii].toSBasis()[Geom::X] <<"\n";
+// std::cout << paths[i][ii].toSBasis()[Geom::Y] <<"\n";
+// std::cout<<"with\n";
+// std::cout << paths[j][jj].toSBasis()[Geom::X] <<"\n";
+// std::cout << paths[j][jj].toSBasis()[Geom::Y] <<"\n";
+
+ find_intersections( times, paths[i][ii].toSBasis(), paths[j][jj].toSBasis() );
+ }
+ for (unsigned k=0; k<times.size(); k++){
+ //std::cout<<"intersection "<<i<<"["<<ii<<"]("<<times[k].first<<")= "<<j<<"["<<jj<<"]("<<times[k].second<<")\n";
+ if (times[k].first == times[k].first && times[k].second == times[k].second ){//is this the way to test NaN?
+ double zero = 1e-4;
+ if ( i==j && fabs(times[k].first+ii - times[k].second-jj)<=zero ){//this is just end=start of successive curves in a path.
+ continue;
+ }
+ if ( i==j && ii == 0 && jj==paths[i].size()-1 &&
+ paths[i].closed() &&
+ fabs(times[k].first) <= zero &&
+ fabs(times[k].second - 1) <= zero ){//this is just end=start of a closed path.
+ continue;
+ }
+ CrossingPoint cp;
+ cp.pt = paths[i][ii].pointAt(times[k].first);
+ cp.sign = 1;
+ cp.i = i;
+ cp.j = j;
+ cp.ni = 0; cp.nj=0;//not set yet
+ cp.ti = times[k].first + ii;
+ cp.tj = times[k].second + jj;
+ push_back(cp);
+ }else{
+ std::cout<<"ooops: find_(self)_intersections returned NaN:";
+ //std::cout<<"intersection "<<i<<"["<<ii<<"](NaN)= "<<j<<"["<<jj<<"](NaN)\n";
+ }
+ }
+ }
+ }
+ }
+ }
+ for( unsigned i=0; i<paths.size(); i++){
+ std::map < double, unsigned > cuts;
+ for( unsigned k=0; k<size(); k++){
+ CrossingPoint cp = (*this)[k];
+ if (cp.i == i) cuts[cp.ti] = k;
+ if (cp.j == i) cuts[cp.tj] = k;
+ }
+ unsigned count = 0;
+ for ( std::map < double, unsigned >::iterator m=cuts.begin(); m!=cuts.end(); m++ ){
+ if ( (*this)[m->second].i == i && (*this)[m->second].ti == m->first ){
+ (*this)[m->second].ni = count;
+ }else{
+ (*this)[m->second].nj = count;
+ }
+ count++;
+ }
+ }
+}
+
+CrossingPoints::CrossingPoints(std::vector<double> const &input) : std::vector<CrossingPoint>()
+{
+ if (input.size()>0 && input.size()%9 ==0){
+ using namespace Geom;
+ for( unsigned n=0; n<input.size(); ){
+ CrossingPoint cp;
+ cp.pt[X] = input[n++];
+ cp.pt[Y] = input[n++];
+ cp.i = input[n++];
+ cp.j = input[n++];
+ cp.ni = input[n++];
+ cp.nj = input[n++];
+ cp.ti = input[n++];
+ cp.tj = input[n++];
+ cp.sign = input[n++];
+ push_back(cp);
+ }
+ }
+}
+
+std::vector<double>
+CrossingPoints::to_vector()
+{
+ using namespace Geom;
+ std::vector<double> result;
+ for( unsigned n=0; n<size(); n++){
+ CrossingPoint cp = (*this)[n];
+ result.push_back(cp.pt[X]);
+ result.push_back(cp.pt[Y]);
+ result.push_back(double(cp.i));
+ result.push_back(double(cp.j));
+ result.push_back(double(cp.ni));
+ result.push_back(double(cp.nj));
+ result.push_back(double(cp.ti));
+ result.push_back(double(cp.tj));
+ result.push_back(double(cp.sign));
+ }
+ return result;
+}
+
+//FIXME: rewrite to check success: return bool, put result in arg.
+CrossingPoint
+CrossingPoints::get(unsigned const i, unsigned const ni)
+{
+ for (unsigned k=0; k<size(); k++){
+ if (
+ ((*this)[k].i==i && (*this)[k].ni==ni) ||
+ ((*this)[k].j==i && (*this)[k].nj==ni)
+ ) return (*this)[k];
+ }
+ g_warning("LPEKnotNS::CrossingPoints::get error. %uth crossing along string %u not found.",ni,i);
+ assert(false);//debug purpose...
+ return CrossingPoint();
+}
+
+unsigned
+idx_of_nearest(CrossingPoints const &cpts, Geom::Point const &p)
+{
+ double dist=-1;
+ unsigned result = cpts.size();
+ for (unsigned k=0; k<cpts.size(); k++){
+ double dist_k = Geom::L2(p-cpts[k].pt);
+ if (dist<0 || dist>dist_k){
+ result = k;
+ dist = dist_k;
+ }
+ }
+ return result;
+}
+
+//TODO: Find a way to warn the user when the topology changes.
+//TODO: be smarter at guessing the signs when the topology changed?
+void
+CrossingPoints::inherit_signs(CrossingPoints const &other, int default_value)
+{
+ bool topo_changed = false;
+ for (unsigned n=0; n<size(); n++){
+ if ( n<other.size() &&
+ other[n].i == (*this)[n].i &&
+ other[n].j == (*this)[n].j &&
+ other[n].ni == (*this)[n].ni &&
+ other[n].nj == (*this)[n].nj )
+ {
+ (*this)[n].sign = other[n].sign;
+ }else{
+ topo_changed = true;
+ break;
+ }
+ }
+ if (topo_changed){
+ //TODO: Find a way to warn the user!!
+// std::cout<<"knot topolgy changed!\n";
+ for (unsigned n=0; n<size(); n++){
+ Geom::Point p = (*this)[n].pt;
+ unsigned idx = idx_of_nearest(other,p);
+ if (idx<other.size()){
+ (*this)[n].sign = other[idx].sign;
+ }else{
+ (*this)[n].sign = default_value;
+ }
+ }
+ }
+}
+
+}
+
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+//LPEKnot effect.
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+
+
+LPEKnot::LPEKnot(LivePathEffectObject *lpeobject) :
+ Effect(lpeobject),
+ // initialise your parameters here:
+ interruption_width(_("Fixed width:"), _("Size of hidden region of lower string"), "interruption_width", &wr, this, 3),
+ prop_to_stroke_width(_("In units of stroke width"), _("Consider 'Interruption width' as a ratio of stroke width"), "prop_to_stroke_width", &wr, this, true),
+ add_stroke_width(_("Stroke width"), _("Add the stroke width to the interruption size"), "add_stroke_width", &wr, this, true),
+ add_other_stroke_width(_("Crossing path stroke width"), _("Add crossed stroke width to the interruption size"), "add_other_stroke_width", &wr, this, true),
+ switcher_size(_("Switcher size:"), _("Orientation indicator/switcher size"), "switcher_size", &wr, this, 15),
+ crossing_points_vector(_("Crossing Signs"), _("Crossings signs"), "crossing_points_vector", &wr, this),
+ gpaths(),gstroke_widths()
+{
+ // register all your parameters here, so Inkscape knows which parameters this effect has:
+ registerParameter( dynamic_cast<Parameter *>(&interruption_width) );
+ registerParameter( dynamic_cast<Parameter *>(&prop_to_stroke_width) );
+ registerParameter( dynamic_cast<Parameter *>(&add_stroke_width) );
+ registerParameter( dynamic_cast<Parameter *>(&add_other_stroke_width) );
+ registerParameter( dynamic_cast<Parameter *>(&switcher_size) );
+ registerParameter( dynamic_cast<Parameter *>(&crossing_points_vector) );
+
+ registerKnotHolderHandle(new KnotHolderEntityCrossingSwitcher(), _("Drag to select a crossing, click to flip it"));
+ crossing_points = LPEKnotNS::CrossingPoints();
+ selectedCrossing = 0;
+ switcher = Geom::Point(0,0);
+}
+
+LPEKnot::~LPEKnot()
+{
+
+}
+
+void
+LPEKnot::updateSwitcher(){
+ if (selectedCrossing < crossing_points.size()){
+ switcher = crossing_points[selectedCrossing].pt;
+ //std::cout<<"placing switcher at "<<switcher<<" \n";
+ }else if (crossing_points.size()>0){
+ selectedCrossing = 0;
+ switcher = crossing_points[selectedCrossing].pt;
+ //std::cout<<"placing switcher at "<<switcher<<" \n";
+ }else{
+ //std::cout<<"hiding switcher!\n";
+ //TODO: is there a way to properly hide the helper.
+ //switcher = Geom::Point(Geom::infinity(),Geom::infinity());
+ switcher = Geom::Point(1e10,1e10);
+ }
+}
+
+std::vector<Geom::Path>
+LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in)
+{
+ using namespace Geom;
+ std::vector<Geom::Path> path_out;
+
+ if (gpaths.size()==0){
+ return path_in;
+ }
+
+ for (unsigned comp=0; comp<path_in.size(); comp++){
+
+ //find the relevant path component in gpaths (required to allow groups!)
+ //Q: do we always recieve the group members in the same order? can we rest on that?
+ unsigned i0 = 0;
+ for (i0=0; i0<gpaths.size(); i0++){
+ if (path_in[comp]==gpaths[i0]) break;
+ }
+ if (i0 == gpaths.size() ) {THROW_EXCEPTION("lpe-knot error: group member not recognized");}// this should not happen...
+
+ std::vector<Interval> dom;
+ dom.push_back(Interval(0.,gpaths[i0].size()));
+ for (unsigned p = 0; p < crossing_points.size(); p++){
+ if (crossing_points[p].i == i0 || crossing_points[p].j == i0){
+ unsigned i = crossing_points[p].i;
+ unsigned j = crossing_points[p].j;
+ double ti = crossing_points[p].ti;
+ double tj = crossing_points[p].tj;
+
+ double curveidx, t;
+
+ t = modf(ti, &curveidx);
+ if(curveidx == gpaths[i].size() ) { curveidx--; t = 1.;}
+ assert(curveidx >= 0 && curveidx < gpaths[i].size());
+ std::vector<Point> flag_i = gpaths[i][curveidx].pointAndDerivatives(t,1);
+
+ t = modf(tj, &curveidx);
+ if(curveidx == gpaths[j].size() ) { curveidx--; t = 1.;}
+ assert(curveidx >= 0 && curveidx < gpaths[j].size());
+ std::vector<Point> flag_j = gpaths[j][curveidx].pointAndDerivatives(t,1);
+
+
+ int geom_sign = ( cross(flag_i[1],flag_j[1]) > 0 ? 1 : -1);
+
+ bool i0_is_under = false;
+ if ( crossing_points[p].sign * geom_sign > 0 ){
+ i0_is_under = ( i == i0 );
+ }else if ( crossing_points[p].sign * geom_sign < 0 ){
+ if (j == i0){
+ std::swap( i, j);
+ std::swap(ti, tj);
+ std::swap(flag_i,flag_j);
+ i0_is_under = true;
+ }
+ }
+ if (i0_is_under){
+ double width = interruption_width;
+ if ( prop_to_stroke_width.get_value() ) {
+ width *= gstroke_widths[i];
+ }
+ if ( add_stroke_width.get_value() ) {
+ width += gstroke_widths[i];
+ }
+ if ( add_other_stroke_width.get_value() ) {
+ width += gstroke_widths[j];
+ }
+ Interval hidden = findShadowedTime(gpaths[i0], flag_j, ti, width/2);
+ double period = gpaths[i0].size();//hm... Should this be gpaths[i0].size()+1?
+ if (hidden.max() > period ) hidden -= period;
+ if (hidden.min()<0){
+ dom = complementOf( Interval(0,hidden.max()) ,dom);
+ dom = complementOf( Interval(hidden.min()+period, period) ,dom);
+ }else{
+ dom = complementOf(hidden,dom);
+ }
+ }
+ }
+ }
+
+ //If the all component is hidden, continue.
+ if ( dom.size() == 0){
+ continue;
+ }
+
+ //If the current path is closed and the last/first point is still there, glue first and last piece.
+ unsigned beg_comp = 0, end_comp = dom.size();
+ if ( gpaths[i0].closed() && dom.front().min() == 0 && dom.back().max() == gpaths[i0].size() ){
+ if ( dom.size() == 1){
+ path_out.push_back(gpaths[i0]);
+ continue;
+ }else{
+// std::cout<<"fusing first and last component\n";
+ beg_comp++;
+ end_comp--;
+ Path first = gpaths[i0].portion(dom.back());
+ //FIXME: STITCH_DISCONTINUOUS should not be necessary (?!?)
+ first.append(gpaths[i0].portion(dom.front()), Path::STITCH_DISCONTINUOUS);
+ path_out.push_back(first);
+ }
+ }
+ for (unsigned comp = beg_comp; comp < end_comp; comp++){
+ assert(dom.at(comp).min() >=0 and dom.at(comp).max() <= gpaths.at(i0).size());
+ path_out.push_back(gpaths[i0].portion(dom.at(comp)));
+ }
+ }
+ return path_out;
+}
+
+
+
+//recursively collect gpaths and stroke widths (stolen from "sp-lpe_item.cpp").
+void collectPathsAndWidths (SPLPEItem const *lpeitem, std::vector<Geom::Path> &paths, std::vector<double> &stroke_widths){
+ if (SP_IS_GROUP(lpeitem)) {
+ GSList const *item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
+ for ( GSList const *iter = item_list; iter; iter = iter->next ) {
+ SPObject *subitem = static_cast<SPObject *>(iter->data);
+ if (SP_IS_LPE_ITEM(subitem)) {
+ collectPathsAndWidths(SP_LPE_ITEM(subitem), paths, stroke_widths);
+ }
+ }
+ }
+ else if (SP_IS_SHAPE(lpeitem)) {
+ SPCurve * c = NULL;
+ if (SP_IS_PATH(lpeitem)) {
+ c = sp_path_get_curve_for_edit(SP_PATH(lpeitem));
+ } else {
+ c = sp_shape_get_curve(SP_SHAPE(lpeitem));
+ }
+ if (c) {
+ Geom::PathVector subpaths = c->get_pathvector();
+ for (unsigned i=0; i<subpaths.size(); i++){
+ paths.push_back(subpaths[i]);
+ //FIXME: do we have to be more carefull when trying to access stroke width?
+ stroke_widths.push_back(SP_ITEM(lpeitem)->style->stroke_width.computed);
+ }
+ }
+ }
+}
+
+
+void
+LPEKnot::doBeforeEffect (SPLPEItem *lpeitem)
+{
+ using namespace Geom;
+ original_bbox(lpeitem);
+
+ gpaths = std::vector<Geom::Path>();
+ gstroke_widths = std::vector<double>();
+ collectPathsAndWidths(lpeitem, gpaths, gstroke_widths);
+
+// std::cout<<"\nPaths on input:\n";
+// for (unsigned i=0; i<gpaths.size(); i++){
+// for (unsigned ii=0; ii<gpaths[i].size(); ii++){
+// std::cout << gpaths[i][ii].toSBasis()[Geom::X] <<"\n";
+// std::cout << gpaths[i][ii].toSBasis()[Geom::Y] <<"\n";
+// std::cout<<"--\n";
+// }
+// }
+
+ //std::cout<<"crossing_pts_vect: "<<crossing_points_vector.param_getSVGValue()<<".\n";
+ //std::cout<<"prop_to_stroke_width: "<<prop_to_stroke_width.param_getSVGValue()<<".\n";
+
+ LPEKnotNS::CrossingPoints old_crdata(crossing_points_vector.data());
+
+// std::cout<<"\nVectorParam size:"<<crossing_points_vector.data().size()<<"\n";
+
+// std::cout<<"\nOld crdata ("<<old_crdata.size()<<"): \n";
+// for (unsigned toto=0; toto<old_crdata.size(); toto++){
+// std::cout<<"(";
+// std::cout<<old_crdata[toto].i<<",";
+// std::cout<<old_crdata[toto].j<<",";
+// std::cout<<old_crdata[toto].ni<<",";
+// std::cout<<old_crdata[toto].nj<<",";
+// std::cout<<old_crdata[toto].ti<<",";
+// std::cout<<old_crdata[toto].tj<<",";
+// std::cout<<old_crdata[toto].sign<<"),";
+// }
+
+ //if ( old_crdata.size() > 0 ) std::cout<<"first crossing sign = "<<old_crdata[0].sign<<".\n";
+ //else std::cout<<"old data is empty!!\n";
+ crossing_points = LPEKnotNS::CrossingPoints(gpaths);
+// std::cout<<"\nNew crdata ("<<crossing_points.size()<<"): \n";
+// for (unsigned toto=0; toto<crossing_points.size(); toto++){
+// std::cout<<"(";
+// std::cout<<crossing_points[toto].i<<",";
+// std::cout<<crossing_points[toto].j<<",";
+// std::cout<<crossing_points[toto].ni<<",";
+// std::cout<<crossing_points[toto].nj<<",";
+// std::cout<<crossing_points[toto].ti<<",";
+// std::cout<<crossing_points[toto].tj<<",";
+// std::cout<<crossing_points[toto].sign<<"),";
+// }
+ crossing_points.inherit_signs(old_crdata);
+ crossing_points_vector.param_set_and_write_new_value(crossing_points.to_vector());
+ updateSwitcher();
+}
+
+
+static LPEKnot *
+get_effect(SPItem *item)
+{
+ Effect *effect = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(item));
+ if (effect->effectType() != KNOT) {
+ g_print ("Warning: Effect is not of type LPEKnot!\n");
+ return NULL;
+ }
+ return static_cast<LPEKnot *>(effect);
+}
+
+void
+LPEKnot::addCanvasIndicators(SPLPEItem */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
+{
+ using namespace Geom;
+ double r = switcher_size*.1;
+ char const * svgd;
+ //TODO: use a nice path!
+ if (selectedCrossing >= crossing_points.size()||crossing_points[selectedCrossing].sign > 0){
+ //svgd = "M -10,0 A 10 10 0 1 0 0,-10 l 5,-1 -1,2";
+ 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";
+ }else if (crossing_points[selectedCrossing].sign < 0){
+ //svgd = "M 10,0 A 10 10 0 1 1 0,-10 l -5,-1 1,2";
+ 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";
+ }else{
+ //svgd = "M 10,0 A 10 10 0 1 0 -10,0 A 10 10 0 1 0 10,0 ";
+ 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";
+ }
+ PathVector pathv = sp_svg_read_pathv(svgd);
+ pathv *= Matrix(r,0,0,r,0,0);
+ pathv+=switcher;
+ hp_vec.push_back(pathv);
+}
+
+void
+KnotHolderEntityCrossingSwitcher::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/)
+{
+ LPEKnot* lpe = get_effect(item);
+
+ lpe->selectedCrossing = idx_of_nearest(lpe->crossing_points,p);
+ lpe->updateSwitcher();
+ // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
+ sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
+}
+
+Geom::Point
+KnotHolderEntityCrossingSwitcher::knot_get()
+{
+ LPEKnot* lpe = get_effect(item);
+ return snap_knot_position(lpe->switcher);
+}
+
+void
+KnotHolderEntityCrossingSwitcher::knot_click(guint state)
+{
+ LPEKnot* lpe = get_effect(item);
+ unsigned s = lpe->selectedCrossing;
+ if (s < lpe->crossing_points.size()){
+ if (state & GDK_SHIFT_MASK){
+ lpe->crossing_points[s].sign = 1;
+ }else{
+ int sign = lpe->crossing_points[s].sign;
+ lpe->crossing_points[s].sign = ((sign+2)%3)-1;
+ //std::cout<<"crossing set to"<<lpe->crossing_points[s].sign<<".\n";
+ }
+ lpe->crossing_points_vector.param_set_and_write_new_value(lpe->crossing_points.to_vector());
+ sp_document_done(lpe->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, /// @todo Is this the right verb?
+ _("Change knot crossing"));
+
+ // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
+// sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
+ }
+}
+
+
+/* ######################## */
+
+} // 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:encoding=utf-8:textwidth=99 :