Code

remove more NR:: from live_effects code
[inkscape.git] / src / live_effects / lpe-spiro.cpp
index 0925becbb55d6dde15287b91df1d68394b2149be..afa5dbc4d3f89d3ee469ff99f8c45d194a2bc6d3 100644 (file)
@@ -5,29 +5,41 @@
  */
 
 #include "live_effects/lpe-spiro.h"
+
 #include "display/curve.h"
-#include <libnr/n-art-bpath.h>
+#include "nodepath.h"
+#include <typeinfo>
+#include <2geom/pathvector.h>
+#include <2geom/matrix.h>
+#include <2geom/bezier-curve.h>
+#include <2geom/hvlinesegment.h>
+#include "helper/geom-nodetype.h"
 
 #include "live_effects/bezctx.h"
 #include "live_effects/bezctx_intf.h"
 #include "live_effects/spiro.h"
 
+// For handling un-continuous paths:
+#include "message-stack.h"
+#include "inkscape.h"
+#include "desktop.h"
+
 typedef struct {
     bezctx base;
     SPCurve *curve;
     int is_open;
 } bezctx_ink;
 
-void bezctx_ink_moveto(bezctx *bc, double x, double y, int is_open)
+void bezctx_ink_moveto(bezctx *bc, double x, double y, int /*is_open*/)
 {
     bezctx_ink *bi = (bezctx_ink *) bc;
-    sp_curve_moveto(bi->curve, x, y);
+    bi->curve->moveto(x, y);
 }
 
 void bezctx_ink_lineto(bezctx *bc, double x, double y)
 {
     bezctx_ink *bi = (bezctx_ink *) bc;
-    sp_curve_lineto(bi->curve, x, y);
+    bi->curve->lineto(x, y);
 }
 
 void bezctx_ink_quadto(bezctx *bc, double xm, double ym, double x3, double y3)
@@ -38,22 +50,22 @@ void bezctx_ink_quadto(bezctx *bc, double xm, double ym, double x3, double y3)
     double x1, y1;
     double x2, y2;
 
-    NR::Point last = sp_curve_last_point(bi->curve);
-    x0 = last[NR::X];
-    y0 = last[NR::Y];
+    Geom::Point last = bi->curve->last_point();
+    x0 = last[Geom::X];
+    y0 = last[Geom::Y];
     x1 = xm + (1./3) * (x0 - xm);
     y1 = ym + (1./3) * (y0 - ym);
     x2 = xm + (1./3) * (x3 - xm);
     y2 = ym + (1./3) * (y3 - ym);
 
-    sp_curve_curveto(bi->curve, x1, y1, x2, y2, x3, y3);
+    bi->curve->curveto(x1, y1, x2, y2, x3, y3);
 }
 
 void bezctx_ink_curveto(bezctx *bc, double x1, double y1, double x2, double y2,
                    double x3, double y3)
 {
     bezctx_ink *bi = (bezctx_ink *) bc;
-    sp_curve_curveto(bi->curve, x1, y1, x2, y2, x3, y3);
+    bi->curve->curveto(x1, y1, x2, y2, x3, y3);
 }
 
 bezctx *
@@ -86,104 +98,142 @@ LPESpiro::~LPESpiro()
 void
 LPESpiro::setup_nodepath(Inkscape::NodePath::Path *np)
 {
-} 
+    sp_nodepath_show_handles(np, false);
+//    sp_nodepath_show_helperpath(np, false);
+}
 
 void
 LPESpiro::doEffect(SPCurve * curve)
 {
-    SPCurve *csrc = sp_curve_copy(curve);
-    sp_curve_reset(curve);
+    using Geom::X;
+    using Geom::Y;
+
+    // Make copy of old path as it is changed during processing
+    Geom::PathVector const original_pathv = curve->get_pathvector();
+    guint len = curve->get_segment_count() + 2;
+
+    curve->reset();
     bezctx *bc = new_bezctx_ink(curve);
-    int len = SP_CURVE_LENGTH(csrc);
-    spiro_cp *path = g_new (spiro_cp, len + 1);
-    NArtBpath *bpath = csrc->_bpath;
-    int ib = 0;
+    spiro_cp *path = g_new (spiro_cp, len);
     int ip = 0;
-    bool closed = false;
-    NR::Point pt(0, 0);
-    NArtBpath *first_in_subpath = NULL;
-    while(ib <= len) {
-        path [ip].x = bpath[ib].x3;
-        path [ip].y = bpath[ib].y3;
-        // std::cout << "==" << bpath[ib].code << "   ip" << ip << "  ib" << ib << "\n";
-        if (bpath[ib].code == NR_END || bpath[ib].code == NR_MOVETO_OPEN || bpath[ib].code == NR_MOVETO) {
-            if (ip != 0) { // run prev subpath
-                int sp_len = 0;
-                if (!closed) {
-                     path[ip - 1].ty = '}';
-                    sp_len = ip;
+
+    for(Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) {
+        if (path_it->empty())
+            continue;
+
+        // start of path
+        {
+            Geom::Point p = path_it->front().pointAt(0);
+            path[ip].x = p[X];
+            path[ip].y = p[Y];
+            path[ip].ty = path_it->closed() ? 'c' : '{' ;  // for closed paths, this might change depending on whether the closing is smooth or not
+            ip++;
+        }
+
+        // midpoints
+        Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
+        Geom::Path::const_iterator curve_it2 = ++(path_it->begin());         // outgoing curve
+
+        Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop
+        if (path_it->closed()) {
+            // if the path is closed, maybe we have to stop a bit earlier because the closing line segment has zerolength.
+            if (path_it->back_closed().isDegenerate()) {
+                // the closing line segment has zero-length. So stop before that one!
+                curve_endit = path_it->end_open();
+            }
+        }
+
+        while ( curve_it2 != curve_endit )
+        {
+            /* This deals with the node between curve_it1 and curve_it2.
+             * Loop to end_default (so without last segment), loop ends when curve_it2 hits the end
+             * and then curve_it1 points to end or closing segment */
+            Geom::Point p = curve_it1->finalPoint();
+            path[ip].x = p[X];
+            path[ip].y = p[Y];
+
+            // Determine type of spiro node this is, determined by the tangents (angles) of the curves
+            // TODO: see if this can be simplified by using /helpers/geom-nodetype.cpp:get_nodetype
+            bool this_is_line = ( dynamic_cast<Geom::LineSegment const *>(&*curve_it1)  ||
+                                  dynamic_cast<Geom::HLineSegment const *>(&*curve_it1) ||
+                                  dynamic_cast<Geom::VLineSegment const *>(&*curve_it1) );
+            bool next_is_line = ( dynamic_cast<Geom::LineSegment const *>(&*curve_it2)  ||
+                                  dynamic_cast<Geom::HLineSegment const *>(&*curve_it2) ||
+                                  dynamic_cast<Geom::VLineSegment const *>(&*curve_it2) );
+            Geom::Point deriv_1 = curve_it1->unitTangentAt(1);
+            Geom::Point deriv_2 = curve_it2->unitTangentAt(0);
+            double this_angle_L2 = Geom::L2(deriv_1);
+            double next_angle_L2 = Geom::L2(deriv_2);
+            double both_angles_L2 = Geom::L2(deriv_1 + deriv_2);
+            if ( (this_angle_L2 > 1e-6) &&
+                 (next_angle_L2 > 1e-6) &&
+                 ((this_angle_L2 + next_angle_L2 - both_angles_L2) < 1e-3) )
+            {
+                if (this_is_line && !next_is_line) {
+                    path[ip].ty = ']';
+                } else if (next_is_line && !this_is_line) {
+                    path[ip].ty = '[';
                 } else {
-                    sp_len = ip - 1;
+                    path[ip].ty = 'c';
                 }
-                spiro_seg *s = NULL;
-                //for (int j = 0; j <= sp_len; j ++) printf ("%c\n", path[j].ty);
-                s = run_spiro(path, sp_len);
-                spiro_to_bpath(s, sp_len, bc);
-                free(s);
-                path[0].x = path[ip].x;
-                path[0].y = path[ip].y;
-                ip = 0;
-            }
-            if (bpath[ib].code == NR_MOVETO_OPEN) {
-                closed = false;
-                path[ip].ty = '{';
             } else {
-                closed = true;
-                if (ib  < len)
-                    first_in_subpath = &(bpath[ib + 1]);
-                path[ip].ty = 'c';
+                path[ip].ty = 'v';
+            }
+
+            ++curve_it1;
+            ++curve_it2;
+            ip++;
+        }
+
+        // add last point to the spiropath
+        Geom::Point p = curve_it1->finalPoint();
+        path[ip].x = p[X];
+        path[ip].y = p[Y];
+        if (path_it->closed()) {
+            // curve_it1 points to the (visually) closing segment. determine the match between first and this last segment (the closing node)
+            Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, path_it->front());
+            switch (nodetype) {
+                case Geom::NODE_NONE: // can't happen! but if it does, it means the path isn't closed :-)
+                    path[ip].ty = '}';
+                    ip++;
+                    break;
+                case Geom::NODE_CUSP:
+                    path[0].ty = path[ip].ty = 'v';
+                    break;
+                case Geom::NODE_SMOOTH:
+                case Geom::NODE_SYMM:
+                    path[0].ty = path[ip].ty = 'c';
+                    break;
             }
         } else {
-                // this point is not last, so makes sense to find a proper type for it
-                NArtBpath *next = NULL;
-                if (ib < len && (bpath[ib+1].code == NR_END || bpath[ib+1].code == NR_MOVETO_OPEN || bpath[ib+1].code == NR_MOVETO)) { // end of subpath
-                    if (closed) 
-                        next = first_in_subpath;
-                } else {
-                    if (ib < len)
-                        next = &(bpath[ib+1]);
-                    else if (closed)
-                        next = first_in_subpath;
-                }
-                if (next) {
-                    bool this_is_line = bpath[ib].code == NR_LINETO || 
-                        (NR::L2(NR::Point(bpath[ib].x3, bpath[ib].y3) - NR::Point(bpath[ib].x2, bpath[ib].y2)) < 0.001);
-                    bool next_is_line = next->code == NR_LINETO || 
-                        (NR::L2(NR::Point(bpath[ib].x3, bpath[ib].y3) - NR::Point(next->x1, next->y1)) < 0.001);
-                    double this_angle = NR_HUGE;
-                    if (this_is_line) {
-                        this_angle = atan2 (bpath[ib].x3 - pt[NR::X], bpath[ib].y3 - pt[NR::Y]);
-                    } else if (bpath[ib].code == NR_CURVETO) {
-                        this_angle = atan2 (bpath[ib].x3 - bpath[ib].x2, bpath[ib].y3 - bpath[ib].y2);
-                    } 
-                    double next_angle = NR_HUGE;
-                    if (next_is_line) {
-                        next_angle = atan2 (next->x3 - bpath[ib].x3, next->y3 - bpath[ib].y3);
-                    } else if (next->code == NR_CURVETO) {
-                        next_angle = atan2 (next->x1 - bpath[ib].x3, next->y1 - bpath[ib].y3);
-                    } 
-                    if (this_angle != NR_HUGE && next_angle != NR_HUGE && fabs(this_angle - next_angle) < 0.001) {
-                        if (this_is_line && !next_is_line) {
-                            path[ip].ty = ']';
-                        } else if (next_is_line && !this_is_line) {
-                            path[ip].ty = '[';
-                        } else {
-                            path[ip].ty = 'c';
-                        }
-                    } else {
-                        path[ip].ty = 'v';
-
-                    }
-                    if (closed && next == first_in_subpath) {
-                        path[0].ty = path[ip].ty;
-                    }
-                }
+            // set type to path closer
+            path[ip].ty = '}';
+            ip++;
         }
-        pt  = NR::Point(bpath[ib].x3, bpath[ib].y3);
-        ip++;
-        ib++;
+
+        // run subpath through spiro
+        int sp_len = ip;
+        spiro_seg *s = run_spiro(path, sp_len);
+        spiro_to_bpath(s, sp_len, bc);
+        free(s);
+        ip = 0;
     }
+
     g_free (path);
+
+    // FIXME: refactor the spiro code such that it cannot generate non-continous paths!
+    // sometimes, the code above generates a path that is not continuous: caused by chaotic algorithm?
+    // The continuity error always happens after a lot of curveto calls (a big path probably that spins to infinity?)
+    // if so, undo the effect by resetting the original path.
+    try {
+        curve->get_pathvector() * Geom::identity(); // tests for continuity, this makes LPE Spiro slower of course :-(
+    }
+    catch (Geom::ContinuityError & e) {
+        g_warning("Exception during LPE Spiro execution. \n %s", e.what());
+        SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
+            _("An exception occurred during execution of the Spiro Path Effect.") );
+        curve->set_pathvector(original_pathv);
+    }
 }
 
 }; //namespace LivePathEffect