Added a slower but more accurate find_closest() routine to _bezier.h. Added a parame...
authordooglus <dooglus@1f10aa63-cdf2-0310-b900-c93c546f37ac>
Wed, 18 Apr 2007 09:14:33 +0000 (09:14 +0000)
committerdooglus <dooglus@1f10aa63-cdf2-0310-b900-c93c546f37ac>
Wed, 18 Apr 2007 09:14:33 +0000 (09:14 +0000)
git-svn-id: http://svn.voria.com/code@472 1f10aa63-cdf2-0310-b900-c93c546f37ac

ETL/trunk/ETL/_bezier.h
ETL/trunk/NEWS
synfig-core/trunk/NEWS
synfig-core/trunk/src/modules/mod_gradient/curvegradient.cpp

index 78ae632..7e26796 100644 (file)
 
 /* === M A C R O S ========================================================= */
 
+#define MAXDEPTH 64    /*  Maximum depth for recursion */
+
+/* take binary sign of a, either -1, or 1 if >= 0 */
+#define SGN(a)         (((a)<0) ? -1 : 1)
+
+/* find minimum of a and b */
+#ifndef MIN
+#define MIN(a,b)       (((a)<(b))?(a):(b))
+#endif
+
+/* find maximum of a and b */
+#ifndef MAX
+#define MAX(a,b)       (((a)>(b))?(a):(b))
+#endif
+
+#define        BEZIER_EPSILON  (ldexp(1.0,-MAXDEPTH-1)) /*Flatness control value */
+//#define      BEZIER_EPSILON  0.00005 /*Flatness control value */
+#define        DEGREE  3                       /*  Cubic Bezier curve          */
+#define        W_DEGREE 5                      /*  Degree of eqn to find roots of */
+
 /* === T Y P E D E F S ===================================================== */
 
 /* === C L A S S E S & S T R U C T S ======================================= */
@@ -522,21 +542,36 @@ public:
        const_iterator begin()const;
        const_iterator end()const;
 
-       time_type find_closest(const value_type& x, int i=7, time_type r=(0), time_type s=(1))const
+       time_type find_closest(bool fast, const value_type& x, int i=7)const
        {
-               float t((r+s)*0.5);
-               for(;i;i--)
-               {
-                       if(dist(operator()((s-r)*(1.0/3.0)+r),x) < dist(operator()((s-r)*(2.0/3.0)+r),x))
-                               s=t;
-                       else
-                               r=t;
-                       t=((r+s)*0.5);
+           if (!fast)
+           {
+                       value_type array[4] = {
+                               bezier<V,T>::operator[](0),
+                               bezier<V,T>::operator[](1),
+                               bezier<V,T>::operator[](2),
+                               bezier<V,T>::operator[](3)};
+                       return NearestPointOnCurve(x, array);
+           }
+           else
+           {
+                       time_type r(0), s(1);
+                       float t((r+s)*0.5); /* half way between r and s */
+
+                       for(;i;i--)
+                       {
+                               // compare 33% of the way between r and s with 67% of the way between r and s
+                               if(dist(operator()((s-r)*(1.0/3.0)+r), x) <
+                                  dist(operator()((s-r)*(2.0/3.0)+r), x))
+                                       s=t;
+                               else
+                                       r=t;
+                               t=((r+s)*0.5);
+                       }
+                       return t;
                }
-               return t;
        }
 
-
        distance_type find_distance(time_type r, time_type s, int steps=7)const
        {
                const time_type inc((s-r)/steps);
@@ -630,6 +665,319 @@ public:
                f = affine_func(p1,p2,t);
                df = (p2-p1)*3;
        }
+
+private:
+       /*
+        *  Bezier :
+        *      Evaluate a Bezier curve at a particular parameter value
+        *      Fill in control points for resulting sub-curves if "Left" and
+        *      "Right" are non-null.
+        *
+        *    int                       degree;         Degree of bezier curve
+        *    value_type        *VT;            Control pts
+        *    time_type         t;                      Parameter value
+        *    value_type        *Left;          RETURN left half ctl pts
+        *    value_type        *Right;         RETURN right half ctl pts
+        */
+       static value_type Bezier(value_type *VT, int degree, time_type t, value_type *Left, value_type *Right)
+       {
+               int             i, j;           /* Index variables      */
+               value_type      Vtemp[W_DEGREE+1][W_DEGREE+1];
+
+               /* Copy control points  */
+               for (j = 0; j <= degree; j++)
+                       Vtemp[0][j] = VT[j];
+
+               /* Triangle computation */
+               for (i = 1; i <= degree; i++)
+                       for (j =0 ; j <= degree - i; j++)
+                       {
+                               Vtemp[i][j][0] = (1.0 - t) * Vtemp[i-1][j][0] + t * Vtemp[i-1][j+1][0];
+                               Vtemp[i][j][1] = (1.0 - t) * Vtemp[i-1][j][1] + t * Vtemp[i-1][j+1][1];
+                       }
+
+               if (Left != NULL)
+                       for (j = 0; j <= degree; j++)
+                               Left[j]  = Vtemp[j][0];
+
+               if (Right != NULL)
+                       for (j = 0; j <= degree; j++)
+                               Right[j] = Vtemp[degree-j][j];
+
+               return (Vtemp[degree][0]);
+       }
+
+       /*
+        * CrossingCount :
+        *      Count the number of times a Bezier control polygon
+        *      crosses the 0-axis. This number is >= the number of roots.
+        *
+        *    value_type        *VT;                    Control pts of Bezier curve
+        */
+       static int CrossingCount(value_type *VT)
+       {
+               int     i;
+               int     n_crossings = 0;        /*  Number of zero-crossings    */
+               int             sign, old_sign;         /*  Sign of coefficients                */
+
+               sign = old_sign = SGN(VT[0][1]);
+               for (i = 1; i <= W_DEGREE; i++)
+               {
+                       sign = SGN(VT[i][1]);
+                       if (sign != old_sign) n_crossings++;
+                       old_sign = sign;
+               }
+
+               return n_crossings;
+       }
+
+       /*
+        *  ControlPolygonFlatEnough :
+        *      Check if the control polygon of a Bezier curve is flat enough
+        *      for recursive subdivision to bottom out.
+        *
+        *    value_type        *VT;            Control points
+        */
+       static int ControlPolygonFlatEnough(value_type *VT)
+       {
+               int                     i;                                      /* Index variable                                       */
+               distance_type   distance[W_DEGREE];     /* Distances from pts to line           */
+               distance_type   max_distance_above;     /* maximum of these                                     */
+               distance_type   max_distance_below;
+               time_type               intercept_1, intercept_2, left_intercept, right_intercept;
+               distance_type   a, b, c;                        /* Coefficients of implicit                     */
+               /* eqn for line from VT[0]-VT[deg]                      */
+               /* Find the  perpendicular distance                     */
+               /* from each interior control point to          */
+               /* line connecting VT[0] and VT[W_DEGREE]       */
+               {
+                       distance_type   abSquared;
+
+                       /* Derive the implicit equation for line connecting first *
+                        *  and last control points */
+                       a = VT[0][1] - VT[W_DEGREE][1];
+                       b = VT[W_DEGREE][0] - VT[0][0];
+                       c = VT[0][0] * VT[W_DEGREE][1] - VT[W_DEGREE][0] * VT[0][1];
+
+                       abSquared = (a * a) + (b * b);
+
+                       for (i = 1; i < W_DEGREE; i++)
+                       {
+                               /* Compute distance from each of the points to that line        */
+                               distance[i] = a * VT[i][0] + b * VT[i][1] + c;
+                               if (distance[i] > 0.0) distance[i] =  (distance[i] * distance[i]) / abSquared;
+                               if (distance[i] < 0.0) distance[i] = -(distance[i] * distance[i]) / abSquared;
+                       }
+               }
+
+               /* Find the largest distance */
+               max_distance_above = max_distance_below = 0.0;
+
+               for (i = 1; i < W_DEGREE; i++)
+               {
+                       if (distance[i] < 0.0) max_distance_below = MIN(max_distance_below, distance[i]);
+                       if (distance[i] > 0.0) max_distance_above = MAX(max_distance_above, distance[i]);
+               }
+
+               /* Implicit equation for "above" line */
+               intercept_1 = -(c + max_distance_above)/a;
+
+               /*  Implicit equation for "below" line */
+               intercept_2 = -(c + max_distance_below)/a;
+
+               /* Compute intercepts of bounding box   */
+               left_intercept = MIN(intercept_1, intercept_2);
+               right_intercept = MAX(intercept_1, intercept_2);
+
+               return 0.5 * (right_intercept-left_intercept) < BEZIER_EPSILON ? 1 : 0;
+       }
+
+       /*
+        *  ComputeXIntercept :
+        *      Compute intersection of chord from first control point to last
+        *  with 0-axis.
+        *
+        *    value_type        *VT;                    Control points
+        */
+       static time_type ComputeXIntercept(value_type *VT)
+       {
+               distance_type YNM = VT[W_DEGREE][1] - VT[0][1];
+               return (YNM*VT[0][0] - (VT[W_DEGREE][0] - VT[0][0])*VT[0][1]) / YNM;
+       }
+
+       /*
+        *  FindRoots :
+        *      Given a 5th-degree equation in Bernstein-Bezier form, find
+        *      all of the roots in the interval [0, 1].  Return the number
+        *      of roots found.
+        *
+        *    value_type        *w;                             The control points
+        *    time_type         *t;                             RETURN candidate t-values
+        *    int                       depth;                  The depth of the recursion
+        */
+       static int FindRoots(value_type *w, time_type *t, int depth)
+       {
+               int             i;
+               value_type      Left[W_DEGREE+1];       /* New left and right   */
+               value_type      Right[W_DEGREE+1];      /* control polygons             */
+               int             left_count;                     /* Solution count from  */
+               int                     right_count;            /* children                             */
+               time_type       left_t[W_DEGREE+1];     /* Solutions from kids  */
+               time_type       right_t[W_DEGREE+1];
+
+               switch (CrossingCount(w))
+               {
+                       case 0 :
+                       {       /* No solutions here    */
+                               return 0;
+                       }
+                       case 1 :
+                       {       /* Unique solution      */
+                               /* Stop recursion when the tree is deep enough          */
+                               /* if deep enough, return 1 solution at midpoint        */
+                               if (depth >= MAXDEPTH)
+                               {
+                                       t[0] = (w[0][0] + w[W_DEGREE][0]) / 2.0;
+                                       return 1;
+                               }
+                               if (ControlPolygonFlatEnough(w))
+                               {
+                                       t[0] = ComputeXIntercept(w);
+                                       return 1;
+                               }
+                               break;
+                       }
+               }
+
+               /* Otherwise, solve recursively after   */
+               /* subdividing control polygon                  */
+               Bezier(w, W_DEGREE, 0.5, Left, Right);
+               left_count  = FindRoots(Left,  left_t,  depth+1);
+               right_count = FindRoots(Right, right_t, depth+1);
+
+               /* Gather solutions together    */
+               for (i = 0; i < left_count;  i++) t[i] = left_t[i];
+               for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i];
+
+               /* Send back total number of solutions  */
+               return (left_count+right_count);
+       }
+
+       /*
+        *  ConvertToBezierForm :
+        *              Given a point and a Bezier curve, generate a 5th-degree
+        *              Bezier-format equation whose solution finds the point on the
+        *      curve nearest the user-defined point.
+        *
+        *    value_type&       P;                              The point to find t for
+        *    value_type        *VT;                    The control points
+        */
+       static void ConvertToBezierForm(const value_type& P, value_type *VT, value_type w[W_DEGREE+1])
+       {
+               int     i, j, k, m, n, ub, lb;
+               int     row, column;                            /* Table indices                                */
+               value_type      c[DEGREE+1];                    /* VT(i)'s - P                                  */
+               value_type      d[DEGREE];                              /* VT(i+1) - VT(i)                              */
+               distance_type   cdTable[3][4];          /* Dot product of c, d                  */
+               static distance_type z[3][4] = {        /* Precomputed "z" for cubics   */
+                       {1.0, 0.6, 0.3, 0.1},
+                       {0.4, 0.6, 0.6, 0.4},
+                       {0.1, 0.3, 0.6, 1.0}};
+
+               /* Determine the c's -- these are vectors created by subtracting */
+               /* point P from each of the control points                                               */
+               for (i = 0; i <= DEGREE; i++)
+                       c[i] = VT[i] - P;
+
+               /* Determine the d's -- these are vectors created by subtracting */
+               /* each control point from the next                                                              */
+               for (i = 0; i <= DEGREE - 1; i++)
+                       d[i] = (VT[i+1] - VT[i]) * 3.0;
+
+               /* Create the c,d table -- this is a table of dot products of the */
+               /* c's and d's                                                                                                    */
+               for (row = 0; row <= DEGREE - 1; row++)
+                       for (column = 0; column <= DEGREE; column++)
+                               cdTable[row][column] = d[row] * c[column];
+
+               /* Now, apply the z's to the dot products, on the skew diagonal */
+               /* Also, set up the x-values, making these "points"                             */
+               for (i = 0; i <= W_DEGREE; i++)
+               {
+                       w[i][0] = (distance_type)(i) / W_DEGREE;
+                       w[i][1] = 0.0;
+               }
+
+               n = DEGREE;
+               m = DEGREE-1;
+               for (k = 0; k <= n + m; k++)
+               {
+                       lb = MAX(0, k - m);
+                       ub = MIN(k, n);
+                       for (i = lb; i <= ub; i++)
+                       {
+                               j = k - i;
+                               w[i+j][1] += cdTable[j][i] * z[j][i];
+                       }
+               }
+       }
+
+       /*
+        *  NearestPointOnCurve :
+        *      Compute the parameter value of the point on a Bezier
+        *              curve segment closest to some arbtitrary, user-input point.
+        *              Return the point on the curve at that parameter value.
+        *
+        *    value_type&       P;                      The user-supplied point
+        *    value_type        *VT;            Control points of cubic Bezier
+        */
+       static time_type NearestPointOnCurve(const value_type& P, value_type VT[4])
+       {
+               value_type      w[W_DEGREE+1];                  /* Ctl pts of 5th-degree curve  */
+               time_type       t_candidate[W_DEGREE];  /* Possible roots                                */
+               int             n_solutions;                    /* Number of roots found                 */
+               time_type       t;                                              /* Parameter value of closest pt */
+
+               /*  Convert problem to 5th-degree Bezier form */
+               ConvertToBezierForm(P, VT, w);
+
+               /* Find all possible roots of 5th-degree equation */
+               n_solutions = FindRoots(w, t_candidate, 0);
+
+               /* Compare distances of P to all candidates, and to t=0, and t=1 */
+               {
+                       distance_type   dist, new_dist;
+                       value_type              p, v;
+                       int                             i;
+
+                       /* Check distance to beginning of curve, where t = 0    */
+                       dist = (P - VT[0]).mag_squared();
+                       t = 0.0;
+
+                       /* Find distances for candidate points  */
+                       for (i = 0; i < n_solutions; i++)
+                       {
+                               p = Bezier(VT, DEGREE, t_candidate[i], (value_type *)NULL, (value_type *)NULL);
+                               new_dist = (P - p).mag_squared();
+                               if (new_dist < dist)
+                               {
+                                       dist = new_dist;
+                                       t = t_candidate[i];
+                               }
+                       }
+
+                       /* Finally, look at distance to end point, where t = 1.0 */
+                       new_dist = (P - VT[DEGREE]).mag_squared();
+                       if (new_dist < dist)
+                       {
+                               dist = new_dist;
+                               t = 1.0;
+                       }
+               }
+
+               /*  Return the point on the curve at parameter value t */
+               return t;
+       }
 };
 
 _ETL_END_NAMESPACE
index 10a72ff..f6a6a9f 100644 (file)
@@ -5,6 +5,7 @@
   * Fix amd64 issue
   * Some tests fixes
   * Misc bug fixes
+  * Add better code for finding closest point to a bezier  (#1672033)
 
  0.04.08 (SVN 139) - February 27, 2006 - Bug fixes
 
index 767dc3c..deed4e4 100644 (file)
@@ -12,6 +12,7 @@
   * Some MacOS fixes
   * Misc bug fixes
   * Fix random number generation for 64 bit CPUs (#1698604)
+  * Add parameter 'fast' to curve gradients allowing choice between fast or accurate rendering (#1672033)
 
  0.61.05 (SVN 126) - February 27, 2005 - Misc fixes
 
index 623074f..9ae4af2 100644 (file)
@@ -105,7 +105,7 @@ inline float calculate_distance(const std::vector<synfig::BLinePoint>& bline)
 }
 
 std::vector<synfig::BLinePoint>::const_iterator
-find_closest(const std::vector<synfig::BLinePoint>& bline,const Point& p,bool loop=false,float *bline_dist_ret=0)
+find_closest(bool fast, const std::vector<synfig::BLinePoint>& bline,const Point& p,float& t,bool loop=false,float *bline_dist_ret=0)
 {
        std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
        std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
@@ -118,6 +118,7 @@ find_closest(const std::vector<synfig::BLinePoint>& bline,const Point& p,bool lo
        float best_bline_dist(0);
        float best_bline_len(0);
        float total_bline_dist(0);
+       float best_pos(0);
        etl::hermite<Vector> best_curve;
 
        if(loop)
@@ -149,23 +150,43 @@ find_closest(const std::vector<synfig::BLinePoint>& bline,const Point& p,bool lo
                        len=curve.length();
                }
 
+               if (fast)
+               {
 #define POINT_CHECK(x) bp=curve(x);    thisdist=(bp-p).mag_squared(); if(thisdist<dist) { ret=iter; dist=thisdist; best_bline_dist=total_bline_dist; best_bline_len=len; best_curve=curve; }
-
-               POINT_CHECK(0.0001);
-               POINT_CHECK((1.0/6.0));
-               POINT_CHECK((2.0/6.0));
-               POINT_CHECK((3.0/6.0));
-               POINT_CHECK((4.0/6.0));
-               POINT_CHECK((5.0/6.0));
-               POINT_CHECK(0.9999);
+                       POINT_CHECK(0.0001);
+                       POINT_CHECK((1.0/6.0));
+                       POINT_CHECK((2.0/6.0));
+                       POINT_CHECK((3.0/6.0));
+                       POINT_CHECK((4.0/6.0));
+                       POINT_CHECK((5.0/6.0));
+                       POINT_CHECK(0.9999);
+               }
+               else
+               {
+                       float pos = curve.find_closest(fast, p);
+                       thisdist=(curve(pos)-p).mag_squared();
+                       if(thisdist<dist)
+                       {
+                               ret=iter;
+                               dist=thisdist;
+                               best_bline_dist=total_bline_dist;
+                               best_bline_len=len;
+                               best_curve=curve;
+                               best_pos = pos;
+                       }
+               }
 
                total_bline_dist+=len;
        }
 
+       t = best_pos;
+
        if(bline_dist_ret)
        {
-               *bline_dist_ret=best_bline_dist+best_curve.find_distance(0,best_curve.find_closest(p));
-//             *bline_dist_ret=best_bline_dist+best_curve.find_closest(p)*best_bline_len;
+               //! \todo is this a redundant call to find_closest()?
+               // note bline_dist_ret is null except when 'perpendicular' is true
+               *bline_dist_ret=best_bline_dist+best_curve.find_distance(0,best_curve.find_closest(fast, p));
+//             *bline_dist_ret=best_bline_dist+best_curve.find_closest(fast, p)*best_bline_len;
        }
 
        return ret;
@@ -186,7 +207,8 @@ CurveGradient::CurveGradient():
        gradient(Color::black(), Color::white()),
        loop(false),
        zigzag(false),
-       perpendicular(false)
+       perpendicular(false),
+       fast(true)
 {
        bline.push_back(BLinePoint());
        bline.push_back(BLinePoint());
@@ -226,6 +248,7 @@ CurveGradient::color_func(const Point &point_, int quality, float supersample)co
        }
        else
        {
+               float t;
                Point point(point_-offset);
 
                std::vector<synfig::BLinePoint>::const_iterator iter,next;
@@ -234,12 +257,12 @@ CurveGradient::color_func(const Point &point_, int quality, float supersample)co
                // Taking into account looping.
                if(perpendicular)
                {
-                       next=find_closest(bline,point,bline_loop,&perp_dist);
+                       next=find_closest(fast,bline,point,t,bline_loop,&perp_dist);
                        perp_dist/=curve_length_;
                }
                else
                {
-                       next=find_closest(bline,point,bline_loop);
+                       next=find_closest(fast,bline,point,t,bline_loop);
                }
 
                iter=next++;
@@ -276,7 +299,8 @@ CurveGradient::color_func(const Point &point_, int quality, float supersample)co
                }
 
                // Figure out the closest point on the curve
-               const float t(curve.find_closest(point,search_iterations));
+               if (fast)
+                       t = curve.find_closest(fast, point,search_iterations);
 
 
                // Calculate our values
@@ -386,6 +410,7 @@ CurveGradient::set_param(const String & param, const ValueBase &value)
 
        IMPORT(offset);
        IMPORT(perpendicular);
+       IMPORT(fast);
 
        if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
        {
@@ -413,6 +438,7 @@ CurveGradient::get_param(const String & param)const
        EXPORT(zigzag);
        EXPORT(width);
        EXPORT(perpendicular);
+       EXPORT(fast);
 
        EXPORT_NAME();
        EXPORT_VERSION();
@@ -446,6 +472,8 @@ CurveGradient::get_param_vocab()const
                                  .set_local_name(_("ZigZag")));
        ret.push_back(ParamDesc("perpendicular")
                                  .set_local_name(_("Perpendicular")));
+       ret.push_back(ParamDesc("fast")
+                                 .set_local_name(_("Fast")));
 
        return ret;
 }