MapGuide Open Source:  Home |  Download |  Internals

Changeset 3480

Show
Ignore:
Timestamp:
11/21/08 23:30:29 (20 hours ago)
Author:
waltweltonlair
Message:

Fix #777 (Improve arc tessellation in LineBuffer?)

This submission improves / simplifies the LineBuffer::ArcTo tessellation algorithm, as described
in the ticket.

In the case where the drawing scale is defined, we compute the required number of segments based
on a tolerance of 0.25 pixels. We further limit the maximum number of segments to 1000. This
still allows arcs up to around 50000 pixels to be drawn to 0.25 pixel accuracy. In the case
where the drawing scale is not defined, we use a default value of 100 segments.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/MgDev/Common/Stylization/LineBuffer.cpp

    r3451 r3480  
    2222 
    2323 
    24 // For point reduction loop -- points will be dropped if distance 
     24// For point reduction loop -- points will be dropped if the distance 
    2525// between them squared is more than 1.96 (i.e. 1.4 pixels). 
    2626// Dave said 1.4 is a good number. 
     
    270270        Resize(); 
    271271 
    272     // Check to see if incoming points are 2D (no Z) and need to be transfomed 
     272    // Check to see if incoming points are 2D (no Z) and need to be transformed 
    273273    // back into 3D space.  This is used by 3D circular arc tessellation. 
    274274    if (m_bTransform2DPoints) 
     
    536536 
    537537    // NOTE: This is really an error condition.  The user has specified that 
    538     // LineBuffer should have Z, but is now calling the circular arc routine 
     538    // the LineBuffer should have Z, but is now calling the circular arc routine 
    539539    // with no Z value.  Assert and draw nothing. 
    540540    _ASSERT(!m_bProcessZ); 
     
    707707    double endAngle = atan2(y2 - cy, x2 - cx); 
    708708 
    709     // now fix the start and end angles so that the cubic approximation 
    710     // tracks the arc in the correct direction 
     709    // now fix the start and end angles so that they track the arc 
     710    // in the correct direction 
    711711    if (area < 0.0) // CW 
    712712    { 
     
    722722 
    723723    ArcTo(cx, cy, r, r, startAngle, endAngle); 
    724 } 
    725  
    726  
    727 // computes cubic spline parameter to be used when approximating an elliptical arc 
    728 double LineBuffer::CubicApproxParameter(double halfAngle) 
    729 { 
    730     // small angle case 
    731     if (fabs(halfAngle) < 5.0e-4) 
    732         return (2.0/3.0) * halfAngle; 
    733  
    734     // otherwise use sin/cos 
    735     return (4.0/3.0) * (1.0 - cos(halfAngle)) / sin(halfAngle); 
    736724} 
    737725 
     
    747735    m_arcs_sp[++m_cur_arcs_sp] = m_cur_types - 1; 
    748736 
    749     double extentA = fabs(extent); 
    750     if (extentA > PI2) 
    751     { 
    752         extent  = PI2;  // sign doesn't matter anymore 
    753         extentA = PI2; 
    754     } 
    755  
    756     // we will approximate one full ellipse by 8 cubics, so here compute 
    757     // what portion of a full ellipse we have so that we know how many 
    758     // cubics will be needed 
    759     int num_segs = (int)ceil(8.0 * extentA / PI2); 
    760     double increment = extent / num_segs; 
    761  
    762     double angle = startRad; 
    763     double ellx = cos(angle); 
    764     double elly = sin(angle); 
    765  
    766     double Ke = CubicApproxParameter(increment*0.5); 
    767  
    768     double c0x, c0y, c3x(0.0), c3y(0.0); 
    769     for (int i=0; i<num_segs; ++i) 
    770     { 
    771         // move to start point 
    772         if (i==0) 
    773         { 
    774             c0x = cx + a * ellx; 
    775             c0y = cy + b * elly; 
    776             LineTo(c0x, c0y); 
    777         } 
    778         else 
    779         { 
    780             c0x = c3x; 
    781             c0y = c3y; 
    782         } 
    783  
    784         double c1x = cx + a*(ellx - Ke * elly); 
    785         double c1y = cy + b*(elly + Ke * ellx); 
    786  
    787         angle += increment; 
    788         ellx = cos(angle); 
    789         elly = sin(angle); 
    790  
    791         double c2x = cx + a * (ellx + Ke * elly); 
    792         double c2y = cy + b * (elly - Ke * ellx); 
    793         c3x = cx + ellx * a; 
    794         c3y = cy + elly * b; 
    795  
    796         TessellateCubicTo(c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y); 
     737    int nSegs = ARC_TESSELLATION_SEGMENTS;  // default 
     738    if (m_drawingScale != 0.0) 
     739    { 
     740        // Compute the required angular separation between points which gives 
     741        // the desired tolerance.  Just base it off the max radius and use the 
     742        // formula for a circle with 0.25 pixel tolerance.  Note that we don't 
     743        // need to take into account any transformation in the case of 3D arcs 
     744        // (see CircularArcTo3D).  The case requiring the most segments is when 
     745        // the plane of the 3D arc is normal to the Z axis.  Since CircularArcTo3D 
     746        // does the tessellation in this plane, this already corresponds to the 
     747        // worst case. 
     748        double maxRadius = rs_max(a, b); 
     749        double dRadReq = sqrt(2.0 * m_drawingScale / maxRadius); 
     750 
     751        // using the angular separation we can compute the minimum number 
     752        // of segments 
     753        nSegs = 1 + (int)(fabs(extent) / dRadReq); 
     754 
     755        // the number of segments can get big for large arcs displayed at high 
     756        // zoom levels, so limit the number of segments 
     757        nSegs = rs_min(nSegs, 10 * ARC_TESSELLATION_SEGMENTS); 
     758    } 
     759 
     760    // get the angular separation corresponding to the number of segments 
     761    double dRad = extent / nSegs; 
     762 
     763    // add the segments 
     764    EnsurePoints(nSegs); 
     765    for (int i=1; i<=nSegs; ++i) 
     766    { 
     767        double ang = startRad + i*dRad; 
     768        double tx = a * cos(ang); 
     769        double ty = b * sin(ang); 
     770 
     771        double x = cx + tx; 
     772        double y = cy + ty; 
     773 
     774        LineTo(x, y); 
    797775    } 
    798776 
    799777    // store off arc end point index (want index of start point of last seg) 
    800778    m_arcs_sp[++m_cur_arcs_sp] = m_cur_types - 2; 
    801 } 
    802  
    803  
    804 void LineBuffer::TessellateCubicTo(double px1, double py1, double px2, double py2, double px3, double py3, double px4, double py4) 
    805 { 
    806     double w = m_bounds.width(); 
    807     double h = m_bounds.height(); 
    808     double minSegLen = 0.0; 
    809     double dt = INV_TESSELLATION_ITERATIONS; //= 1.0 / 100.0 Iterate 100 times through loop 
    810  
    811     if (m_drawingScale != 0.0) 
    812     { 
    813         // If we have drawing scale available, use drawing scale to make sure 
    814         // the minimum segment length is equal to # of screen units occupied 
    815         // by one pixel.  This ensures no "flat" segments are visible along 
    816         // the curve. 
    817 //      minSegLen = m_drawingScale; 
    818  
    819         // Setting the minimum segment length to the drawing scale creates so 
    820         // many segments in dense drawings at high zoom levels that we run out 
    821         // of memory.  Use the larger of the drawing scale and one two hundredth 
    822         // of the "size" of the arc we are tessellating.  This compromise seems 
    823         // to work in all "reasonable" cases. 
    824  
    825         // Compute bounding box of the four control points of the curve 
    826         double minx = min4(px1, px2, px3, px4); 
    827         double miny = min4(py1, py2, py3, py4); 
    828         double maxx = max4(px1, px2, px3, px4); 
    829         double maxy = max4(py1, py2, py3, py4); 
    830  
    831         double deltax = maxx - minx; 
    832         double deltay = maxy - miny; 
    833  
    834         double maxlength = rs_max(deltax, deltay); 
    835  
    836         // 0.005 (1/200) is an arbitrary number, but it seems to work well 
    837         minSegLen = rs_max(m_drawingScale, 0.005*maxlength); 
    838  
    839         // This code will compute the # of pixels occupied by the curve segment. 
    840         // It can then reduce the # of iterations in the forward differencing 
    841         // loop to that # of pixels.  This reduces the time taken to generate 
    842         // each segment of the tessellation. 
    843  
    844         // Figure out how many pixels are covered by that max length. 
    845         double numPixels = maxlength / m_drawingScale; 
    846  
    847         // Ensure that the # of loop iterations is at most 100 or # of pixels 
    848         // covered (whichever is smaller). 
    849         dt = rs_max(INV_TESSELLATION_ITERATIONS, (1.0 / numPixels)); 
    850     } 
    851     else 
    852     { 
    853         // We will base the max number of segments to use for approximation 
    854         // on the bounds of the full line buffer contents. 
    855         // TODO: as an improvement we could take the bounds of this particular 
    856         // curve with respect to the full bounds of the line buffer data. 
    857         double maxdim = rs_max(w, h); 
    858  
    859         // minimum length of tessellation segment, set to 1/100 of the bounds 
    860         minSegLen = maxdim * 0.01; 
    861     } 
    862  
    863 //  double dt2 = dt*dt; 
    864     double dt3 = dt * dt * dt; 
    865  
    866     double pre1 = 3.0 * dt; 
    867     double pre2 = pre1 * dt; 
    868     double pre3 = pre2 + pre2; 
    869     double pre4 = 6.0 * dt3; 
    870  
    871     double temp1x = px1 - 2.0 * px2 + px3; 
    872     double temp1y = py1 - 2.0 * py2 + py3; 
    873     double temp2x = 3.0 * (px2 - px3) - px1 + px4; 
    874     double temp2y = 3.0 * (py2 - py3) - py1 + py4; 
    875  
    876     double fx    = px1; 
    877     double fy    = py1; 
    878     double dfx   = (px2 - px1) * pre1 + temp1x * pre2 + temp2x * dt3; 
    879     double dfy   = (py2 - py1) * pre1 + temp1y * pre2 + temp2y * dt3; 
    880     double ddfx  = temp1x * pre3 + temp2x * pre4; 
    881     double ddfy  = temp1y * pre3 + temp2y * pre4; 
    882     double dddfx = temp2x * pre4; 
    883     double dddfy = temp2y * pre4; 
    884  
    885     double error = 0.0; 
    886  
    887     // forward differencing loop 
    888     int tMax = (int)(1.0/dt - 0.5); 
    889     for (int t=0; t<tMax; ++t) 
    890     { 
    891         fx   += dfx; 
    892         fy   += dfy; 
    893         dfx  += ddfx; 
    894         dfy  += ddfy; 
    895         ddfy += dddfy; 
    896         ddfx += dddfx; 
    897  
    898         // slow but accurate for flattening 
    899 //      error += sqrt(dfx*dfx + dfy*dfy); 
    900  
    901         // faster but less accurate error estimate 
    902         w = fabs(dfx); 
    903         h = fabs(dfy); 
    904         error += rs_max(w, h); 
    905         if (error >= minSegLen) // add segment only if we have reached threshold length 
    906         { 
    907             // line to current 
    908             LineTo(fx, fy); 
    909             error = 0.0; 
    910         } 
    911     } 
    912  
    913     LineTo(px4, py4); 
    914 } 
    915  
    916  
    917 void LineBuffer::TessellateQuadTo(double px1, double py1, double px2, double py2, double px3, double py3) 
    918 { 
    919     // We will base the max number of segments to use for approximation 
    920     // on the bounds of the full line buffer contents. 
    921     // TODO: as an improvement we could take the bounds of this particular curve 
    922     //       with respect to the full bounds of the line buffer data. 
    923     double w = m_bounds.width(); 
    924     double h = m_bounds.height(); 
    925     double maxdim = rs_max(w, h); 
    926  
    927     // minimum length of tessellation segment set to 1/100 of the bounds 
    928     double minSegLen = maxdim * 0.01; 
    929  
    930     /* 
    931     // if we know the pixels per mapping unit ratio, we can use 
    932     // this code to determine how many times at most 
    933     // we want to loop in the forward difference loop 
    934     double invscale = 1.0 / pixelsperunit; 
    935     double dt = rs_max(1.0e-2, invscale / diff); // * m_invscale; 
    936     */ 
    937  
    938     // but for now we will iterate 100 times 
    939     double dt = INV_TESSELLATION_ITERATIONS; 
    940  
    941     double dt2 = dt*dt; 
    942  
    943     double ax = px1 - 2.0*px2 + px3;    // replace 2* by addition? 
    944     double ay = py1 - 2.0*py2 + py3;    // replace 2* by addition? 
    945  
    946     double bx = 2.0*(px2-px1); 
    947     double by = 2.0*(py2-py1); 
    948  
    949     double fx   = px1; 
    950     double fy   = py1; 
    951     double dfx  = bx*dt + ax*dt2; 
    952     double dfy  = by*dt + ay*dt2; 
    953     double ddfx = 2.0*ax*dt2; 
    954     double ddfy = 2.0*ay*dt2; 
    955  
    956     double error = 0.0; 
    957  
    958     //forward differencing loop 
    959     int tMax = (int)(1.0/dt - 0.5); 
    960     for (int t=0; t<tMax; ++t) 
    961     { 
    962         fx   += dfx; 
    963         fy   += dfy; 
    964         dfx  += ddfx; 
    965         dfy  += ddfy; 
    966  
    967         // slow but accurate 
    968 //      error += sqrt(dfx*dfx + dfy*dfy); 
    969  
    970         // faster but less accurate error estimate 
    971         w = fabs(dfx); 
    972         h = fabs(dfy); 
    973         error += rs_max(w, h); 
    974  
    975         if (error >= minSegLen)  // how many pixels should each line be? 
    976         { 
    977             LineTo(fx, fy); 
    978             error = 0.0; 
    979         } 
    980     } 
    981  
    982     LineTo(px3, py3); 
    983779} 
    984780 
  • trunk/MgDev/Common/Stylization/LineBuffer.h

    r3453 r3480  
    3737#endif 
    3838 
    39 #define PI2 6.283185307179586476925286766559    //2*pi 
    40  
    41 //defines how many iterations to use when tessellating 
    42 //qudratics, cubics and spirals. We use up to 100 iterations 
    43 const int TESSELLATION_ITERATIONS = 100; 
    44 const double INV_TESSELLATION_ITERATIONS = 1.0 / TESSELLATION_ITERATIONS; 
     39// defines how many segments to use when tessellating arcs 
     40const int ARC_TESSELLATION_SEGMENTS = 100; 
    4541 
    4642class LineBufferPool; 
     
    249245    double PolylineLengthSqr(int cntr); 
    250246 
    251     double CubicApproxParameter(double halfAngle); 
    252     void TessellateCubicTo(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4); 
    253     void TessellateQuadTo(double x1, double y1, double x2, double y2, double x3, double y3); 
    254  
    255247    void ResizePoints(int n);    // new size of array # of points 
    256248    void ResizeContours(int n);