1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2020 Google Inc.
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#include "samplecode/Sample.h"
9cb93a386Sopenharmony_ci
10cb93a386Sopenharmony_ci#include "include/core/SkCanvas.h"
11cb93a386Sopenharmony_ci#include "include/core/SkFont.h"
12cb93a386Sopenharmony_ci#include "include/core/SkPaint.h"
13cb93a386Sopenharmony_ci#include "include/core/SkPath.h"
14cb93a386Sopenharmony_ci#include <tuple>
15cb93a386Sopenharmony_ci
16cb93a386Sopenharmony_ci// Math constants are not always defined.
17cb93a386Sopenharmony_ci#ifndef M_PI
18cb93a386Sopenharmony_ci#define M_PI 3.14159265358979323846264338327950288
19cb93a386Sopenharmony_ci#endif
20cb93a386Sopenharmony_ci
21cb93a386Sopenharmony_ci#ifndef M_SQRT2
22cb93a386Sopenharmony_ci#define M_SQRT2 1.41421356237309504880168872420969808
23cb93a386Sopenharmony_ci#endif
24cb93a386Sopenharmony_ci
25cb93a386Sopenharmony_ciconstexpr static int kCenterX = 300;
26cb93a386Sopenharmony_ciconstexpr static int kCenterY = 325;
27cb93a386Sopenharmony_ciconstexpr static int kRadius = 250;
28cb93a386Sopenharmony_ci
29cb93a386Sopenharmony_ci// This sample fits a cubic to the arc between two interactive points on a circle. It also finds the
30cb93a386Sopenharmony_ci// T-coordinate of max error, and outputs it and its value in pixels. (It turns out that max error
31cb93a386Sopenharmony_ci// always occurs at T=0.21132486540519.)
32cb93a386Sopenharmony_ci//
33cb93a386Sopenharmony_ci// Press 'E' to iteratively cut the arc in half and report the improvement in max error after each
34cb93a386Sopenharmony_ci// halving. (It turns out that max error improves by exactly 64x on every halving.)
35cb93a386Sopenharmony_ciclass SampleFitCubicToCircle : public Sample {
36cb93a386Sopenharmony_ci    SkString name() override { return SkString("FitCubicToCircle"); }
37cb93a386Sopenharmony_ci    void onOnceBeforeDraw() override { this->fitCubic(); }
38cb93a386Sopenharmony_ci    void fitCubic();
39cb93a386Sopenharmony_ci    void onDrawContent(SkCanvas*) override;
40cb93a386Sopenharmony_ci    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
41cb93a386Sopenharmony_ci    bool onClick(Sample::Click*) override;
42cb93a386Sopenharmony_ci    bool onChar(SkUnichar) override;
43cb93a386Sopenharmony_ci
44cb93a386Sopenharmony_ci    // Coordinates of two points on the unit circle. These are the two endpoints of the arc we fit.
45cb93a386Sopenharmony_ci    double fEndptsX[2] = {0, 1};
46cb93a386Sopenharmony_ci    double fEndptsY[2] = {-1, 0};
47cb93a386Sopenharmony_ci
48cb93a386Sopenharmony_ci    // Fitted cubic and info, set by fitCubic().
49cb93a386Sopenharmony_ci    double fControlLength;  // Length of (p1 - p0) and/or (p3 - p2) in unit circle space.
50cb93a386Sopenharmony_ci    double fMaxErrorT;  // T value where the cubic diverges most from the true arc.
51cb93a386Sopenharmony_ci    std::array<double, 4> fCubicX;  // Screen space cubic control points.
52cb93a386Sopenharmony_ci    std::array<double, 4> fCubicY;
53cb93a386Sopenharmony_ci    double fMaxError;  // Max error (in pixels) between the cubic and the screen-space arc.
54cb93a386Sopenharmony_ci    double fTheta;  // Angle of the arc. This is only used for informational purposes.
55cb93a386Sopenharmony_ci    SkTArray<SkString> fInfoStrings;
56cb93a386Sopenharmony_ci
57cb93a386Sopenharmony_ci    class Click;
58cb93a386Sopenharmony_ci};
59cb93a386Sopenharmony_ci
60cb93a386Sopenharmony_ci// Fits a cubic to an arc on the unit circle with endpoints (x0, y0) and (x1, y1). Using the
61cb93a386Sopenharmony_ci// following 3 constraints, we arrive at the formula used in the method:
62cb93a386Sopenharmony_ci//
63cb93a386Sopenharmony_ci//   1) The endpoints and tangent directions at the endpoints must match the arc.
64cb93a386Sopenharmony_ci//   2) The cubic must be symmetric (i.e., length(p1 - p0) == length(p3 - p2)).
65cb93a386Sopenharmony_ci//   3) The height of the cubic must match the height of the arc.
66cb93a386Sopenharmony_ci//
67cb93a386Sopenharmony_ci// Returns the "control length", or length of (p1 - p0) and/or (p3 - p2).
68cb93a386Sopenharmony_cistatic float fit_cubic_to_unit_circle(double x0, double y0, double x1, double y1,
69cb93a386Sopenharmony_ci                                      std::array<double, 4>* X, std::array<double, 4>* Y) {
70cb93a386Sopenharmony_ci    constexpr static double kM = -4.0/3;
71cb93a386Sopenharmony_ci    constexpr static double kA = 4*M_SQRT2/3;
72cb93a386Sopenharmony_ci    double d = x0*x1 + y0*y1;
73cb93a386Sopenharmony_ci    double c = (std::sqrt(1 + d) * kM + kA) / std::sqrt(1 - d);
74cb93a386Sopenharmony_ci    *X = {x0, x0 - y0*c, x1 + y1*c, x1};
75cb93a386Sopenharmony_ci    *Y = {y0, y0 + x0*c, y1 - x1*c, y1};
76cb93a386Sopenharmony_ci    return c;
77cb93a386Sopenharmony_ci}
78cb93a386Sopenharmony_ci
79cb93a386Sopenharmony_cistatic double lerp(double x, double y, double T) {
80cb93a386Sopenharmony_ci    return x + T*(y - x);
81cb93a386Sopenharmony_ci}
82cb93a386Sopenharmony_ci
83cb93a386Sopenharmony_ci// Evaluates the cubic and 1st and 2nd derivatives at T.
84cb93a386Sopenharmony_cistatic std::tuple<double, double, double> eval_cubic(double x[], double T) {
85cb93a386Sopenharmony_ci    // Use De Casteljau's algorithm for better accuracy and stability.
86cb93a386Sopenharmony_ci    double ab = lerp(x[0], x[1], T);
87cb93a386Sopenharmony_ci    double bc = lerp(x[1], x[2], T);
88cb93a386Sopenharmony_ci    double cd = lerp(x[2], x[3], T);
89cb93a386Sopenharmony_ci    double abc = lerp(ab, bc, T);
90cb93a386Sopenharmony_ci    double bcd = lerp(bc, cd, T);
91cb93a386Sopenharmony_ci    double abcd = lerp(abc, bcd, T);
92cb93a386Sopenharmony_ci    return {abcd, 3 * (bcd - abc) /*1st derivative.*/, 6 * (cd - 2*bc + ab) /*2nd derivative.*/};
93cb93a386Sopenharmony_ci}
94cb93a386Sopenharmony_ci
95cb93a386Sopenharmony_ci// Uses newton-raphson convergence to find the point where the provided cubic diverges most from the
96cb93a386Sopenharmony_ci// unit circle. i.e., the point where the derivative of error == 0. For error we use:
97cb93a386Sopenharmony_ci//
98cb93a386Sopenharmony_ci//     error = x^2 + y^2 - 1
99cb93a386Sopenharmony_ci//     error' = 2xx' + 2yy'
100cb93a386Sopenharmony_ci//     error'' = 2xx'' + 2yy'' + 2x'^2 + 2y'^2
101cb93a386Sopenharmony_ci//
102cb93a386Sopenharmony_cidouble find_max_error_T(double cubicX[4], double cubicY[4]) {
103cb93a386Sopenharmony_ci    constexpr static double kInitialT = .25;
104cb93a386Sopenharmony_ci    double T = kInitialT;
105cb93a386Sopenharmony_ci    for (int i = 0; i < 64; ++i) {
106cb93a386Sopenharmony_ci        auto [x, dx, ddx] = eval_cubic(cubicX, T);
107cb93a386Sopenharmony_ci        auto [y, dy, ddy] = eval_cubic(cubicY, T);
108cb93a386Sopenharmony_ci        double dError = 2*(x*dx + y*dy);
109cb93a386Sopenharmony_ci        double ddError = 2*(x*ddx + y*ddy + dx*dx + dy*dy);
110cb93a386Sopenharmony_ci        T -= dError / ddError;
111cb93a386Sopenharmony_ci    }
112cb93a386Sopenharmony_ci    return T;
113cb93a386Sopenharmony_ci}
114cb93a386Sopenharmony_ci
115cb93a386Sopenharmony_civoid SampleFitCubicToCircle::fitCubic() {
116cb93a386Sopenharmony_ci    fInfoStrings.reset();
117cb93a386Sopenharmony_ci
118cb93a386Sopenharmony_ci    std::array<double, 4> X, Y;
119cb93a386Sopenharmony_ci    // "Control length" is the length of (p1 - p0) and/or (p3 - p2) in unit circle space.
120cb93a386Sopenharmony_ci    fControlLength = fit_cubic_to_unit_circle(fEndptsX[0], fEndptsY[0], fEndptsX[1], fEndptsY[1],
121cb93a386Sopenharmony_ci                                              &X, &Y);
122cb93a386Sopenharmony_ci    fInfoStrings.push_back().printf("control length=%0.14f", fControlLength);
123cb93a386Sopenharmony_ci
124cb93a386Sopenharmony_ci    fMaxErrorT = find_max_error_T(X.data(), Y.data());
125cb93a386Sopenharmony_ci    fInfoStrings.push_back().printf("max error T=%0.14f", fMaxErrorT);
126cb93a386Sopenharmony_ci
127cb93a386Sopenharmony_ci    for (int i = 0; i < 4; ++i) {
128cb93a386Sopenharmony_ci        fCubicX[i] = X[i] * kRadius + kCenterX;
129cb93a386Sopenharmony_ci        fCubicY[i] = Y[i] * kRadius + kCenterY;
130cb93a386Sopenharmony_ci    }
131cb93a386Sopenharmony_ci    double errX = std::get<0>(eval_cubic(fCubicX.data(), fMaxErrorT)) - kCenterX;
132cb93a386Sopenharmony_ci    double errY = std::get<0>(eval_cubic(fCubicY.data(), fMaxErrorT)) - kCenterY;
133cb93a386Sopenharmony_ci    fMaxError = std::sqrt(errX*errX + errY*errY) - kRadius;
134cb93a386Sopenharmony_ci    fInfoStrings.push_back().printf("max error=%.5gpx", fMaxError);
135cb93a386Sopenharmony_ci
136cb93a386Sopenharmony_ci    fTheta = std::atan2(fEndptsY[1], fEndptsX[1]) - std::atan2(fEndptsY[0], fEndptsX[0]);
137cb93a386Sopenharmony_ci    fTheta = std::abs(fTheta * 180/M_PI);
138cb93a386Sopenharmony_ci    if (fTheta > 180) {
139cb93a386Sopenharmony_ci        fTheta = 360 - fTheta;
140cb93a386Sopenharmony_ci    }
141cb93a386Sopenharmony_ci    fInfoStrings.push_back().printf("(theta=%.2f)", fTheta);
142cb93a386Sopenharmony_ci
143cb93a386Sopenharmony_ci    SkDebugf("\n");
144cb93a386Sopenharmony_ci    for (const SkString& infoString : fInfoStrings) {
145cb93a386Sopenharmony_ci        SkDebugf("%s\n", infoString.c_str());
146cb93a386Sopenharmony_ci    }
147cb93a386Sopenharmony_ci}
148cb93a386Sopenharmony_ci
149cb93a386Sopenharmony_civoid SampleFitCubicToCircle::onDrawContent(SkCanvas* canvas) {
150cb93a386Sopenharmony_ci    canvas->clear(SK_ColorBLACK);
151cb93a386Sopenharmony_ci
152cb93a386Sopenharmony_ci    SkPaint circlePaint;
153cb93a386Sopenharmony_ci    circlePaint.setColor(0x80ffffff);
154cb93a386Sopenharmony_ci    circlePaint.setStyle(SkPaint::kStroke_Style);
155cb93a386Sopenharmony_ci    circlePaint.setStrokeWidth(0);
156cb93a386Sopenharmony_ci    circlePaint.setAntiAlias(true);
157cb93a386Sopenharmony_ci    canvas->drawArc(SkRect::MakeXYWH(kCenterX - kRadius, kCenterY - kRadius, kRadius * 2,
158cb93a386Sopenharmony_ci                                     kRadius * 2), 0, 360, false, circlePaint);
159cb93a386Sopenharmony_ci
160cb93a386Sopenharmony_ci    SkPaint cubicPaint;
161cb93a386Sopenharmony_ci    cubicPaint.setColor(SK_ColorGREEN);
162cb93a386Sopenharmony_ci    cubicPaint.setStyle(SkPaint::kStroke_Style);
163cb93a386Sopenharmony_ci    cubicPaint.setStrokeWidth(10);
164cb93a386Sopenharmony_ci    cubicPaint.setAntiAlias(true);
165cb93a386Sopenharmony_ci    SkPath cubicPath;
166cb93a386Sopenharmony_ci    cubicPath.moveTo(fCubicX[0], fCubicY[0]);
167cb93a386Sopenharmony_ci    cubicPath.cubicTo(fCubicX[1], fCubicY[1], fCubicX[2], fCubicY[2], fCubicX[3], fCubicY[3]);
168cb93a386Sopenharmony_ci    canvas->drawPath(cubicPath, cubicPaint);
169cb93a386Sopenharmony_ci
170cb93a386Sopenharmony_ci    SkPaint endpointsPaint;
171cb93a386Sopenharmony_ci    endpointsPaint.setColor(SK_ColorBLUE);
172cb93a386Sopenharmony_ci    endpointsPaint.setStrokeWidth(8);
173cb93a386Sopenharmony_ci    endpointsPaint.setAntiAlias(true);
174cb93a386Sopenharmony_ci    SkPoint points[2] = {{(float)fCubicX[0], (float)fCubicY[0]},
175cb93a386Sopenharmony_ci                         {(float)fCubicX[3], (float)fCubicY[3]}};
176cb93a386Sopenharmony_ci    canvas->drawPoints(SkCanvas::kPoints_PointMode, 2, points, endpointsPaint);
177cb93a386Sopenharmony_ci
178cb93a386Sopenharmony_ci    SkPaint textPaint;
179cb93a386Sopenharmony_ci    textPaint.setColor(SK_ColorWHITE);
180cb93a386Sopenharmony_ci    constexpr static float kInfoTextSize = 16;
181cb93a386Sopenharmony_ci    SkFont font(nullptr, kInfoTextSize);
182cb93a386Sopenharmony_ci    int infoY = 10 + kInfoTextSize;
183cb93a386Sopenharmony_ci    for (const SkString& infoString : fInfoStrings) {
184cb93a386Sopenharmony_ci        canvas->drawString(infoString.c_str(), 10, infoY, font, textPaint);
185cb93a386Sopenharmony_ci        infoY += kInfoTextSize * 3/2;
186cb93a386Sopenharmony_ci    }
187cb93a386Sopenharmony_ci}
188cb93a386Sopenharmony_ci
189cb93a386Sopenharmony_ciclass SampleFitCubicToCircle::Click : public Sample::Click {
190cb93a386Sopenharmony_cipublic:
191cb93a386Sopenharmony_ci    Click(int ptIdx) : fPtIdx(ptIdx) {}
192cb93a386Sopenharmony_ci
193cb93a386Sopenharmony_ci    void doClick(SampleFitCubicToCircle* that) {
194cb93a386Sopenharmony_ci        double dx = fCurr.fX - kCenterX;
195cb93a386Sopenharmony_ci        double dy = fCurr.fY - kCenterY;
196cb93a386Sopenharmony_ci        double l = std::sqrt(dx*dx + dy*dy);
197cb93a386Sopenharmony_ci        that->fEndptsX[fPtIdx] = dx/l;
198cb93a386Sopenharmony_ci        that->fEndptsY[fPtIdx] = dy/l;
199cb93a386Sopenharmony_ci        if (that->fEndptsX[0] * that->fEndptsY[1] - that->fEndptsY[0] * that->fEndptsX[1] < 0) {
200cb93a386Sopenharmony_ci            std::swap(that->fEndptsX[0], that->fEndptsX[1]);
201cb93a386Sopenharmony_ci            std::swap(that->fEndptsY[0], that->fEndptsY[1]);
202cb93a386Sopenharmony_ci            fPtIdx = 1 - fPtIdx;
203cb93a386Sopenharmony_ci        }
204cb93a386Sopenharmony_ci        that->fitCubic();
205cb93a386Sopenharmony_ci    }
206cb93a386Sopenharmony_ci
207cb93a386Sopenharmony_ciprivate:
208cb93a386Sopenharmony_ci    int fPtIdx;
209cb93a386Sopenharmony_ci};
210cb93a386Sopenharmony_ci
211cb93a386Sopenharmony_ciSample::Click* SampleFitCubicToCircle::onFindClickHandler(SkScalar x, SkScalar y,
212cb93a386Sopenharmony_ci                                                          skui::ModifierKey) {
213cb93a386Sopenharmony_ci    double dx0 = x - fCubicX[0];
214cb93a386Sopenharmony_ci    double dy0 = y - fCubicY[0];
215cb93a386Sopenharmony_ci    double dx3 = x - fCubicX[3];
216cb93a386Sopenharmony_ci    double dy3 = y - fCubicY[3];
217cb93a386Sopenharmony_ci    if (dx0*dx0 + dy0*dy0 < dx3*dx3 + dy3*dy3) {
218cb93a386Sopenharmony_ci        return new Click(0);
219cb93a386Sopenharmony_ci    } else {
220cb93a386Sopenharmony_ci        return new Click(1);
221cb93a386Sopenharmony_ci    }
222cb93a386Sopenharmony_ci}
223cb93a386Sopenharmony_ci
224cb93a386Sopenharmony_cibool SampleFitCubicToCircle::onClick(Sample::Click* click) {
225cb93a386Sopenharmony_ci    Click* myClick = (Click*)click;
226cb93a386Sopenharmony_ci    myClick->doClick(this);
227cb93a386Sopenharmony_ci    return true;
228cb93a386Sopenharmony_ci}
229cb93a386Sopenharmony_ci
230cb93a386Sopenharmony_cibool SampleFitCubicToCircle::onChar(SkUnichar unichar) {
231cb93a386Sopenharmony_ci    if (unichar == 'E') {
232cb93a386Sopenharmony_ci        constexpr static double kMaxErrorT = 0.21132486540519;  // Always the same.
233cb93a386Sopenharmony_ci        // Split the arc in half until error =~0, and report the improvement after each halving.
234cb93a386Sopenharmony_ci        double lastError = -1;
235cb93a386Sopenharmony_ci        for (double theta = fTheta; lastError != 0; theta /= 2) {
236cb93a386Sopenharmony_ci            double rads = theta * M_PI/180;
237cb93a386Sopenharmony_ci            std::array<double, 4> X, Y;
238cb93a386Sopenharmony_ci            fit_cubic_to_unit_circle(1, 0, std::cos(rads), std::sin(rads), &X, &Y);
239cb93a386Sopenharmony_ci            auto [x, dx, ddx] = eval_cubic(X.data(), kMaxErrorT);
240cb93a386Sopenharmony_ci            auto [y, dy, ddy] = eval_cubic(Y.data(), kMaxErrorT);
241cb93a386Sopenharmony_ci            double error = std::sqrt(x*x + y*y) * kRadius - kRadius;
242cb93a386Sopenharmony_ci            if ((float)error <= 0) {
243cb93a386Sopenharmony_ci                error = 0;
244cb93a386Sopenharmony_ci            }
245cb93a386Sopenharmony_ci            SkDebugf("%6.2f degrees:   error= %10.5gpx", theta, error);
246cb93a386Sopenharmony_ci            if (lastError > 0) {
247cb93a386Sopenharmony_ci                SkDebugf(" (%17.14fx improvement)", lastError / error);
248cb93a386Sopenharmony_ci            }
249cb93a386Sopenharmony_ci            SkDebugf("\n");
250cb93a386Sopenharmony_ci            lastError = error;
251cb93a386Sopenharmony_ci        }
252cb93a386Sopenharmony_ci        return true;
253cb93a386Sopenharmony_ci    }
254cb93a386Sopenharmony_ci    return false;
255cb93a386Sopenharmony_ci}
256cb93a386Sopenharmony_ci
257cb93a386Sopenharmony_ciDEF_SAMPLE(return new SampleFitCubicToCircle;)
258