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 = &sub;
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