/* * Author(s): * Jabiertxo Arraiza Cenoz * * Copyright (C) 2014 Author(s) * * Special thanks to Johan Engelen for the base of the effect -powerstroke- * Also to ScislaC for point me to the idea * Also su_v for his construvtive feedback and time * Also to Mc- (IRC nick) for his important contribution to find real time * values based on * and finaly to Liam P. White for his big help on coding, that save me a lot of hours * * Released under GNU GPL, read the file 'COPYING' for more information */ #include "live_effects/lpe-fillet-chamfer.h" #include #include #include <2geom/pointwise.h> #include <2geom/satellite.h> #include <2geom/satellite-enum.h> #include <2geom/svg-elliptical-arc.h> #include "helper/geom-nodetype.h" #include "helper/geom-curves.h" #include "helper/geom.h" #include "display/curve.h" #include // TODO due to internal breakage in glibmm headers, this must be last: #include using namespace Geom; namespace Inkscape { namespace LivePathEffect { static const Util::EnumData FilletMethodData[FM_END] = { { FM_AUTO, N_("Auto"), "auto" }, { FM_ARC, N_("Force arc"), "arc" }, { FM_BEZIER, N_("Force bezier"), "bezier" } }; static const Util::EnumDataConverter FMConverter(FilletMethodData, FM_END); LPEFilletChamfer::LPEFilletChamfer(LivePathEffectObject *lpeobject) : Effect(lpeobject), satellitepairarrayparam_values(_("Fillet point"), _("Fillet point"), "satellitepairarrayparam_values", &wr, this), method(_("Method:"), _("Fillets methods"), "method", FMConverter, &wr, this, FM_AUTO) { registerParameter(&satellitepairarrayparam_values); registerParameter(&method); } LPEFilletChamfer::~LPEFilletChamfer() {} void LPEFilletChamfer::doOnApply(SPLPEItem const *lpeItem) { SPLPEItem * splpeitem = const_cast(lpeItem); SPShape * shape = dynamic_cast(splpeitem); if (shape) { PathVector const &original_pathv = pathv_to_linear_and_cubic_beziers(shape->getCurve()->get_pathvector()); std::vector > satellites; Piecewise > pwd2_in = paths_to_pw(original_pathv); pwd2_in = remove_short_cuts(pwd2_in, .01); satellitepairarrayparam_values.set_pwd2(pwd2_in); int counterTotal = 0; for (PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) { if (path_it->empty()){ continue; } Geom::Path::const_iterator curve_it1 = path_it->begin(); Geom::Path::const_iterator curve_endit = path_it->end_default(); if (path_it->closed()) { const Curve &closingline = path_it->back_closed(); // the closing line segment is always of type // LineSegment. if (are_near(closingline.initialPoint(), closingline.finalPoint())) { // closingline.isDegenerate() did not work, because it only checks for // *exact* zero length, which goes wrong for relative coordinates and // rounding errors... // the closing line segment has zero-length. So stop before that one! curve_endit = path_it->end_open(); } } Geom::Path::const_iterator curve_end = curve_endit; --curve_end; int counter = 0; while (curve_it1 != curve_endit) { Satellite satellite(F, true, true, false, false, 0.0, 0.0); Geom::NodeType nodetype; if (counter==0) { if (path_it->closed()) { nodetype = get_nodetype(*curve_end, *curve_it1); } else { nodetype = NODE_NONE; } } else { nodetype = get_nodetype((*path_it)[counter - 1], *curve_it1); } if (nodetype == NODE_CUSP) { satellites.push_back(std::make_pair(counterTotal, satellite)); } ++curve_it1; counter++; counterTotal++; } } satellitepairarrayparam_values.param_set_and_write_new_value(satellites); } else { g_warning("LPE Fillet/Chamfer can only be applied to shapes (not groups)."); SPLPEItem * item = const_cast(lpeItem); item->removeCurrentPathEffect(false); } } void LPEFilletChamfer::doBeforeEffect(SPLPEItem const *lpeItem) { SPLPEItem * splpeitem = const_cast(lpeItem); SPShape * shape = dynamic_cast(splpeitem); if (shape) { SPCurve *c = shape->getCurve(); SPPath * path = dynamic_cast(shape); if(path){ c = path->get_original_curve(); } PathVector const &original_pathv = pathv_to_linear_and_cubic_beziers(c->get_pathvector()); Piecewise > pwd2_in = paths_to_pw(original_pathv); pwd2_in = remove_short_cuts(pwd2_in, .01); satellitepairarrayparam_values.set_pwd2(pwd2_in); } else { g_warning("LPE Fillet can only be applied to shapes (not groups)."); } } std::vector LPEFilletChamfer::doEffect_path(std::vector const &path_in) { const double gapHelper = 0.00001; std::vector pathvector_out; Piecewise > pwd2_in = paths_to_pw(pathv_to_linear_and_cubic_beziers(path_in)); pwd2_in = remove_short_cuts(pwd2_in, .01); satellitepairarrayparam_values.set_pwd2(pwd2_in); std::vector > filletChamferData = satellitepairarrayparam_values.data(); Pointwise *pointwise = new Pointwise( pwd2_in, filletChamferData); unsigned int counter = 0; const double K = (4.0 / 3.0) * (sqrt(2.0) - 1.0); std::vector path_in_processed = pathv_to_linear_and_cubic_beziers(path_in); for (PathVector::const_iterator path_it = path_in_processed.begin(); path_it != path_in_processed.end(); ++path_it) { if (path_it->empty()) continue; Geom::Path path_out; Geom::Path::const_iterator curve_it1 = path_it->begin(); Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); Geom::Path::const_iterator curve_endit = path_it->end_default(); if (path_it->closed()) { const Geom::Curve &closingline = path_it->back_closed(); // the closing line segment is always of type // Geom::LineSegment. if (are_near(closingline.initialPoint(), closingline.finalPoint())) { // closingline.isDegenerate() did not work, because it only checks for // *exact* zero length, which goes wrong for relative coordinates and // rounding errors... // the closing line segment has zero-length. So stop before that one! curve_endit = path_it->end_open(); } } unsigned int counterCurves = 0; unsigned int first = counter; double time0 = 0; while (curve_it1 != curve_endit) { std::vector satVector; Satellite sat(F, true, true, false, false, 0.0, 0.0); Curve *curve_it2Fixed = (*path_it->begin()).duplicate(); if(!path_it->closed() || curve_it2 != curve_endit){ curve_it2Fixed = (*curve_it2).duplicate(); satVector = pointwise->findSatellites(counter+1,1); if(satVector.size()>0){ sat = satVector[0]; } } else { satVector = pointwise->findSatellites(first,1); if(satVector.size()>0){ sat = satVector[0]; } } if(first == counter){ satVector = pointwise->findSatellites(first,1); if(satVector.size()>0){ time0 = satVector[0].getTime(); } } bool last = curve_it2 == curve_endit; double time1 = sat.getOpositeTime((*curve_it1).toSBasis()); double time2 = sat.getTime(); if(time1 <= time0){ time1 = time0 + gapHelper; } if(time0 == 1){ time0 = time0 - gapHelper; } if(time2 == 1){ time2 = time2 - gapHelper; } std::vector times; times.push_back(time0); times.push_back(time1); times.push_back(time2); Curve *knotCurve1 = curve_it1->portion(times[0], times[1]); if (counterCurves > 0) { knotCurve1->setInitial(path_out.finalPoint()); } else { path_out.start((*curve_it1).pointAt(times[0])); } Curve *knotCurve2 = curve_it2Fixed->portion(times[2], 1); Point startArcPoint = knotCurve1->finalPoint(); Point endArcPoint = curve_it2Fixed->pointAt(times[2]); double k1 = distance(startArcPoint, curve_it1->finalPoint()) * K; double k2 = distance(endArcPoint, curve_it1->finalPoint()) * K; Geom::CubicBezier const *cubic1 = dynamic_cast(&*knotCurve1); Ray ray1(startArcPoint, curve_it1->finalPoint()); if (cubic1) { ray1.setPoints((*cubic1)[2], startArcPoint); } Point handle1 = Point::polar(ray1.angle(),k1) + startArcPoint; if(time0 == 1){ handle1 = startArcPoint; } Geom::CubicBezier const *cubic2 = dynamic_cast(&*knotCurve2); Ray ray2(curve_it1->finalPoint(), endArcPoint); if (cubic2) { ray2.setPoints(endArcPoint, (*cubic2)[1]); } Point handle2 = endArcPoint - Point::polar(ray2.angle(),k2); bool ccwToggle = cross(curve_it1->finalPoint() - startArcPoint, endArcPoint - startArcPoint) < 0; double angle = angle_between(ray1, ray2, ccwToggle); double handleAngle = ray1.angle() - angle; if (ccwToggle) { handleAngle = ray1.angle() + angle; } Point inverseHandle1 = Point::polar(handleAngle,k1) + startArcPoint; if(time0 == 1){ inverseHandle1 = startArcPoint; } handleAngle = ray2.angle() + angle; if (ccwToggle) { handleAngle = ray2.angle() - angle; } Point inverseHandle2 = endArcPoint - Point::polar(handleAngle,k2); Line const x_line(Geom::Point(0,0),Geom::Point(1,0)); Line const angled_line(startArcPoint,endArcPoint); double angleArc = Geom::angle_between( x_line,angled_line); double radius = Geom::distance(startArcPoint,middle_point(startArcPoint,endArcPoint))/sin(angle/2.0); Coord rx = radius; Coord ry = rx; if (times[1] != 1 || (times[1] == 1 && times[0] == times[1] - gapHelper )) { if (times[1] != times[0] + gapHelper) { path_out.append(*knotCurve1); } SatelliteType type = F; type = sat.getSatelliteType(); if(are_near(middle_point(startArcPoint,endArcPoint),curve_it1->finalPoint(), 0.0001)){ //path_out.appendNew(endArcPoint); } else if (type == C) { /* unsigned int chamferSubs = type-3000; Geom::Path path_chamfer; path_chamfer.start(path_out.finalPoint()); if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ path_chamfer.appendNew(rx, ry, angleArc, 0, ccwToggle, endArcPoint); } else { path_chamfer.appendNew(handle1, handle2, endArcPoint); } double chamfer_stepsTime = 1.0/chamferSubs; for(unsigned int i = 1; i < chamferSubs; i++){ Geom::Point chamferStep = path_chamfer.pointAt(chamfer_stepsTime * i); path_out.appendNew(chamferStep); } path_out.appendNew(endArcPoint); /*/ } else if (type == IC) { /* unsigned int chamferSubs = type-4000; Geom::Path path_chamfer; path_chamfer.start(path_out.finalPoint()); if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ ccwToggle = ccwToggle?0:1; path_chamfer.appendNew(rx, ry, angleArc, 0, ccwToggle, endArcPoint); }else{ path_chamfer.appendNew(inverseHandle1, inverseHandle2, endArcPoint); } double chamfer_stepsTime = 1.0/chamferSubs; for(unsigned int i = 1; i < chamferSubs; i++){ Geom::Point chamferStep = path_chamfer.pointAt(chamfer_stepsTime * i); path_out.appendNew(chamferStep); } path_out.appendNew(endArcPoint); */ } else if (type == IF) { if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ ccwToggle = ccwToggle?0:1; path_out.appendNew(rx, ry, angleArc, 0, ccwToggle, endArcPoint); }else{ path_out.appendNew(inverseHandle1, inverseHandle2, endArcPoint); } } else if (type == F){ if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ path_out.appendNew(rx, ry, angleArc, 0, ccwToggle, endArcPoint); } else { path_out.appendNew(handle1, handle2, endArcPoint); } } } else { path_out.append(*knotCurve1); } if (path_it->closed() && last) { path_out.close(); } ++curve_it1; if (curve_it2 != curve_endit) { ++curve_it2; } counter++; counterCurves++; time0 = times[2]; } pathvector_out.push_back(path_out); } return pathvector_out; } void LPEFilletChamfer::adjustForNewPath(std::vector const &path_in) { if (!path_in.empty()) { //satellitepairarrayparam_values.recalculate_controlpoints_for_new_pwd2(pathv_to_linear_and_cubic_beziers(path_in)[0].toPwSb()); } } }; //namespace LivePathEffect }; /* namespace Inkscape */ /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offset:((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 :