CubicToQuadratics.cpp revision 6d0032a8ec680221c2a704cac2391f2a2d77546f
16d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com/* 26d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comhttp://stackoverflow.com/questions/2009160/how-do-i-convert-the-2-control-points-of-a-cubic-curve-to-the-single-control-poi 36d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com*/ 46d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 56d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com/* 66d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comLet's call the control points of the cubic Q0..Q3 and the control points of the quadratic P0..P2. 76d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comThen for degree elevation, the equations are: 86d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 96d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comQ0 = P0 106d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comQ1 = 1/3 P0 + 2/3 P1 116d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comQ2 = 2/3 P1 + 1/3 P2 126d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comQ3 = P2 136d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comIn your case you have Q0..Q3 and you're solving for P0..P2. There are two ways to compute P1 from 146d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com the equations above: 156d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 166d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comP1 = 3/2 Q1 - 1/2 Q0 176d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comP1 = 3/2 Q2 - 1/2 Q3 186d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comIf this is a degree-elevated cubic, then both equations will give the same answer for P1. Since 196d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com it's likely not, your best bet is to average them. So, 206d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 216d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comP1 = -1/4 Q0 + 3/4 Q1 + 3/4 Q2 - 1/4 Q3 226d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 236d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 246d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comCubic defined by: P1/2 - anchor points, C1/C2 control points 256d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com|x| is the euclidean norm of x 266d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.commid-point approx of cubic: a quad that shares the same anchors with the cubic and has the 276d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com control point at C = (3·C2 - P2 + 3·C1 - P1)/4 286d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 296d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comAlgorithm 306d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 316d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.compick an absolute precision (prec) 326d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comCompute the Tdiv as the root of (cubic) equation 336d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comsqrt(3)/18 · |P2 - 3·C2 + 3·C1 - P1|/2 · Tdiv ^ 3 = prec 346d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comif Tdiv < 0.5 divide the cubic at Tdiv. First segment [0..Tdiv] can be approximated with by a 356d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com quadratic, with a defect less than prec, by the mid-point approximation. 366d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com Repeat from step 2 with the second resulted segment (corresponding to 1-Tdiv) 376d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com0.5<=Tdiv<1 - simply divide the cubic in two. The two halves can be approximated by the mid-point 386d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com approximation 396d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comTdiv>=1 - the entire cubic can be approximated by the mid-point approximation 406d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 416d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comconfirmed by (maybe stolen from) 426d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comhttp://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html 436d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 446d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com*/ 456d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 466d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com#include "CubicUtilities.h" 476d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com#include "CurveIntersection.h" 486d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 496d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comstatic double calcTDiv(const Cubic& cubic, double start) { 506d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com const double adjust = sqrt(3) / 36; 516d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com Cubic sub; 526d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com const Cubic* cPtr; 536d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com if (start == 0) { 546d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com cPtr = &cubic; 556d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com } else { 566d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com // OPTIMIZE: special-case half-split ? 576d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com sub_divide(cubic, start, 1, sub); 586d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com cPtr = ⊂ 596d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com } 606d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com const Cubic& c = *cPtr; 616d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com double dx = c[3].x - 3 * (c[2].x - c[1].x) - c[0].x; 626d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com double dy = c[3].y - 3 * (c[2].y - c[1].y) - c[0].y; 636d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com double dist = sqrt(dx * dx + dy * dy); 646d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com double tDiv3 = FLT_EPSILON / (adjust * dist); 656d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com return cube_root(tDiv3); 666d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com} 676d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 686d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comstatic void demote(const Cubic& cubic, Quadratic& quad) { 696d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com quad[0] = cubic[0]; 706d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com quad[1].x = (cubic[3].x - 3 * (cubic[2].x - cubic[1].x) - cubic[0].x) / 4; 716d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com quad[1].y = (cubic[3].y - 3 * (cubic[2].y - cubic[1].y) - cubic[0].y) / 4; 726d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com quad[2] = cubic[3]; 736d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com} 746d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com 756d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.comint cubic_to_quadratics(const Cubic& cubic, SkTDArray<Quadratic>& quadratics) { 766d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com quadratics.setCount(1); // FIXME: every place I have setCount(), I also want setReserve() 776d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com Cubic reduced; 786d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com int order = reduceOrder(cubic, reduced, kReduceOrder_QuadraticsAllowed); 796d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com if (order < 3) { 806d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com memcpy(quadratics[0], reduced, order * sizeof(_Point)); 816d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com return order; 826d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com } 836d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com double tDiv = calcTDiv(cubic, 0); 846d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com if (approximately_greater_than_one(tDiv)) { 856d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com demote(cubic, quadratics[0]); 866d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com return 2; 876d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com } 886d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com if (tDiv >= 0.5) { 896d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com CubicPair pair; 906d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com chop_at(cubic, pair, 0.5); 916d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com quadratics.setCount(2); 926d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com demote(pair.first(), quadratics[0]); 936d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com demote(pair.second(), quadratics[1]); 946d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com return 2; 956d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com } 966d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com double start = 0; 976d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com int index = -1; 986d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com // if we iterate but make little progress, check to see if the curve is flat 996d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com // or if the control points are tangent to each other 1006d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com do { 1016d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com Cubic part; 1026d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com sub_divide(part, start, tDiv, part); 1036d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com quadratics.append(); 1046d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com demote(part, quadratics[++index]); 1056d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com if (tDiv == 1) { 1066d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com break; 1076d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com } 1086d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com start = tDiv; 1096d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com tDiv = calcTDiv(cubic, start); 1106d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com if (tDiv > 1) { 1116d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com tDiv = 1; 1126d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com } 1136d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com } while (true); 1146d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com return 2; 1156d0032a8ec680221c2a704cac2391f2a2d77546fcaryclark@google.com} 116