/** * @file * LPE knot effect implementation. */ /* Authors: * Jean-Francois Barraud * Abhishek Sharma * Johan Engelen * * Copyright (C) 2007-2012 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 "style.h" #include "knot-holder-entity.h" #include "knotholder.h" #include #include <2geom/sbasis-to-bezier.h> #include <2geom/bezier-to-sbasis.h> #include <2geom/basic-intersection.h> #include "helper/geom.h" // for change crossing undo #include "verbs.h" #include "document.h" #include "document-undo.h" // TODO due to internal breakage in glibmm headers, this must be last: #include namespace Inkscape { namespace LivePathEffect { class KnotHolderEntityCrossingSwitcher : public LPEKnotHolderEntity { public: KnotHolderEntityCrossingSwitcher(LPEKnot *effect) : LPEKnotHolderEntity(effect) {}; virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state); virtual Geom::Point knot_get() const; virtual void knot_click(guint state); }; static Geom::Path::size_type size_nondegenerate(Geom::Path const &path) { Geom::Path::size_type retval = path.size_open(); // if path is closed and closing segment is not degenerate if (path.closed() && !path.back_closed().isDegenerate()) { retval = path.size_closed(); } return retval; } //--------------------------------------------------------------------------- //LPEKnot specific Interval manipulation. //--------------------------------------------------------------------------- //remove an interval from an union of intervals. //TODO: is it worth moving it to 2Geom? static std::vector complementOf(Geom::Interval I, std::vector domain){ std::vector 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 I1i = intersect(domain.at(i),I1); if (I1i && !I1i->isSingular()) ret.push_back(I1i.get()); boost::optional 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 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; //Point B = A+6*width*T; Affine mat = from_basis( T, N, pt_and_dir[0] ); mat = mat.inverse(); Geom::Path p = patha * mat; std::vector times; //TODO: explore the path fwd/backward from ta (worth?) for (unsigned i = 0; i < size_nondegenerate(patha); i++){ D2 f = p[i].toSBasis(); std::vector 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::iterator new_end = std::unique( times.begin(), times.end() ); times.resize( new_end - times.begin() ); double tmin = 0, tmax = size_nondegenerate(patha); double period = size_nondegenerate(patha); if (!times.empty()){ 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(Geom::PathVector const &paths) : std::vector(){ // std::cout<<"\nCrossingPoints creation from path vector\n"; for( unsigned i=0; i > 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 cuts; for( unsigned k=0; k::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 const &input) : std::vector() { if ( (input.size() > 0) && (input.size()%9 == 0) ){ using namespace Geom; for( unsigned n=0; n CrossingPoints::to_vector() { using namespace Geom; std::vector result; for( unsigned n=0; n 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(_("Fi_xed 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(_("St_roke 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(_("S_witcher size:"), _("Orientation indicator/switcher size"), "switcher_size", &wr, this, 15), crossing_points_vector(_("Crossing Signs"), _("Crossings signs"), "crossing_points_vector", &wr, this), crossing_points(), gpaths(), gstroke_widths(), selectedCrossing(0), switcher(0.,0.) { // register all your parameters here, so Inkscape knows which parameters this effect has: registerParameter(&interruption_width); registerParameter(&prop_to_stroke_width); registerParameter(&add_stroke_width); registerParameter(&add_other_stroke_width); registerParameter(&switcher_size); registerParameter(&crossing_points_vector); _provides_knotholder_entities = true; } LPEKnot::~LPEKnot() { } void LPEKnot::updateSwitcher(){ if (selectedCrossing < crossing_points.size()){ switcher = crossing_points[selectedCrossing].pt; //std::cout<<"placing switcher at "<0){ selectedCrossing = 0; switcher = crossing_points[selectedCrossing].pt; //std::cout<<"placing switcher at "< dom; dom.push_back(Interval(0., size_nondegenerate(gpaths[i0]))); 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 == size_nondegenerate(gpaths[i]) ) { curveidx--; t = 1.;} assert(curveidx >= 0 && curveidx < size_nondegenerate(gpaths[i])); std::vector flag_i = gpaths[i][curveidx].pointAndDerivatives(t,1); t = modf(tj, &curveidx); if(curveidx == size_nondegenerate(gpaths[j]) ) { curveidx--; t = 1.;} assert(curveidx >= 0 && curveidx < size_nondegenerate(gpaths[j])); std::vector 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 = size_nondegenerate(gpaths[i0]); 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.empty()){ 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() == size_nondegenerate(gpaths[i0])) ) { if ( dom.size() == 1){ path_out.push_back(gpaths[i0]); continue; }else{ // std::cout<<"fusing first and last component\n"; ++beg_comp; --end_comp; Geom::Path first = gpaths[i0].portion(dom.back()); //FIXME: stitching should not be necessary (?!?) first.setStitching(true); first.append(gpaths[i0].portion(dom.front())); path_out.push_back(first); } } for (unsigned comp = beg_comp; comp < end_comp; comp++){ assert(dom.at(comp).min() >=0 && dom.at(comp).max() <= size_nondegenerate(gpaths.at(i0))); 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"). static void collectPathsAndWidths (SPLPEItem const *lpeitem, Geom::PathVector &paths, std::vector &stroke_widths){ if (SP_IS_GROUP(lpeitem)) { std::vector item_list = sp_item_group_item_list(SP_GROUP(lpeitem)); for ( std::vector::const_iterator iter = item_list.begin(); iter != item_list.end(); ++iter) { SPObject *subitem = *iter; 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(lpeitem)->get_curve_for_edit(); } else { c = SP_SHAPE(lpeitem)->getCurve(); } if (c) { Geom::PathVector subpaths = pathv_to_linear_and_cubic_beziers(c->get_pathvector()); for (unsigned i=0; istyle->stroke_width.computed); } } } } void LPEKnot::doBeforeEffect (SPLPEItem const* lpeitem) { using namespace Geom; original_bbox(lpeitem); if (SP_IS_PATH(lpeitem)) { supplied_path = SP_PATH(lpeitem)->getCurve()->get_pathvector(); } gpaths.clear(); gstroke_widths.clear(); collectPathsAndWidths(lpeitem, gpaths, gstroke_widths); // std::cout<<"\nPaths on input:\n"; // for (unsigned i=0; i 0 ) std::cout<<"first crossing sign = "< &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 *= Affine(r,0,0,r,0,0) * Translate(switcher); hp_vec.push_back(pathv); } void LPEKnot::addKnotHolderEntities(KnotHolder *knotholder, SPItem *item) { KnotHolderEntity *e = new KnotHolderEntityCrossingSwitcher(this); e->create( NULL, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, _("Drag to select a crossing, click to flip it") ); knotholder->add(e); }; void KnotHolderEntityCrossingSwitcher::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/) { LPEKnot* lpe = dynamic_cast(_effect); 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() const { LPEKnot const *lpe = dynamic_cast(_effect); return lpe->switcher; } void KnotHolderEntityCrossingSwitcher::knot_click(guint state) { LPEKnot* lpe = dynamic_cast(_effect); 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"<crossing_points[s].sign<<".\n"; } lpe->crossing_points_vector.param_set_and_write_new_value(lpe->crossing_points.to_vector()); DocumentUndo::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:fileencoding=utf-8:textwidth=99 :