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 "include/utils/SkRandom.h"
9cb93a386Sopenharmony_ci#include "src/core/SkGeometry.h"
10cb93a386Sopenharmony_ci#include "src/gpu/geometry/GrPathUtils.h"
11cb93a386Sopenharmony_ci#include "tests/Test.h"
12cb93a386Sopenharmony_ci
13cb93a386Sopenharmony_cistatic bool is_linear(SkPoint p0, SkPoint p1, SkPoint p2) {
14cb93a386Sopenharmony_ci    return SkScalarNearlyZero((p0 - p1).cross(p2 - p1));
15cb93a386Sopenharmony_ci}
16cb93a386Sopenharmony_ci
17cb93a386Sopenharmony_cistatic bool is_linear(const SkPoint p[4]) {
18cb93a386Sopenharmony_ci    return is_linear(p[0],p[1],p[2]) && is_linear(p[0],p[2],p[3]) && is_linear(p[1],p[2],p[3]);
19cb93a386Sopenharmony_ci}
20cb93a386Sopenharmony_ci
21cb93a386Sopenharmony_cistatic void check_cubic_convex_180(skiatest::Reporter* r, const SkPoint p[4]) {
22cb93a386Sopenharmony_ci    bool areCusps = false;
23cb93a386Sopenharmony_ci    float inflectT[2], convex180T[2];
24cb93a386Sopenharmony_ci    if (int inflectN = SkFindCubicInflections(p, inflectT)) {
25cb93a386Sopenharmony_ci        // The curve has inflections. findCubicConvex180Chops should return the inflection
26cb93a386Sopenharmony_ci        // points.
27cb93a386Sopenharmony_ci        int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T, &areCusps);
28cb93a386Sopenharmony_ci        REPORTER_ASSERT(r, inflectN == convex180N);
29cb93a386Sopenharmony_ci        if (!areCusps) {
30cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, inflectN == 1 ||
31cb93a386Sopenharmony_ci                            fabsf(inflectT[0] - inflectT[1]) >= SK_ScalarNearlyZero);
32cb93a386Sopenharmony_ci        }
33cb93a386Sopenharmony_ci        for (int i = 0; i < convex180N; ++i) {
34cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, SkScalarNearlyEqual(inflectT[i], convex180T[i]));
35cb93a386Sopenharmony_ci        }
36cb93a386Sopenharmony_ci    } else {
37cb93a386Sopenharmony_ci        float totalRotation = SkMeasureNonInflectCubicRotation(p);
38cb93a386Sopenharmony_ci        int convex180N = GrPathUtils::findCubicConvex180Chops(p, convex180T, &areCusps);
39cb93a386Sopenharmony_ci        SkPoint chops[10];
40cb93a386Sopenharmony_ci        SkChopCubicAt(p, chops, convex180T, convex180N);
41cb93a386Sopenharmony_ci        float radsSum = 0;
42cb93a386Sopenharmony_ci        for (int i = 0; i <= convex180N; ++i) {
43cb93a386Sopenharmony_ci            float rads = SkMeasureNonInflectCubicRotation(chops + i*3);
44cb93a386Sopenharmony_ci            SkASSERT(rads < SK_ScalarPI + SK_ScalarNearlyZero);
45cb93a386Sopenharmony_ci            radsSum += rads;
46cb93a386Sopenharmony_ci        }
47cb93a386Sopenharmony_ci        if (totalRotation < SK_ScalarPI - SK_ScalarNearlyZero) {
48cb93a386Sopenharmony_ci            // The curve should never chop if rotation is <180 degrees.
49cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, convex180N == 0);
50cb93a386Sopenharmony_ci        } else if (!is_linear(p)) {
51cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, SkScalarNearlyEqual(radsSum, totalRotation));
52cb93a386Sopenharmony_ci            if (totalRotation > SK_ScalarPI + SK_ScalarNearlyZero) {
53cb93a386Sopenharmony_ci                REPORTER_ASSERT(r, convex180N == 1);
54cb93a386Sopenharmony_ci                // This works because cusps take the "inflection" path above, so we don't get
55cb93a386Sopenharmony_ci                // non-lilnear curves that lose rotation when chopped.
56cb93a386Sopenharmony_ci                REPORTER_ASSERT(r, SkScalarNearlyEqual(
57cb93a386Sopenharmony_ci                    SkMeasureNonInflectCubicRotation(chops), SK_ScalarPI));
58cb93a386Sopenharmony_ci                REPORTER_ASSERT(r, SkScalarNearlyEqual(
59cb93a386Sopenharmony_ci                    SkMeasureNonInflectCubicRotation(chops + 3), totalRotation - SK_ScalarPI));
60cb93a386Sopenharmony_ci            }
61cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, !areCusps);
62cb93a386Sopenharmony_ci        } else {
63cb93a386Sopenharmony_ci            REPORTER_ASSERT(r, areCusps);
64cb93a386Sopenharmony_ci        }
65cb93a386Sopenharmony_ci    }
66cb93a386Sopenharmony_ci}
67cb93a386Sopenharmony_ci
68cb93a386Sopenharmony_ciDEF_TEST(GrPathUtils_findCubicConvex180Chops, r) {
69cb93a386Sopenharmony_ci    // Test all combinations of corners from the square [0,0,1,1]. This covers every cubic type as
70cb93a386Sopenharmony_ci    // well as a wide variety of special cases for cusps, lines, loops, and inflections.
71cb93a386Sopenharmony_ci    for (int i = 0; i < (1 << 8); ++i) {
72cb93a386Sopenharmony_ci        SkPoint p[4] = {SkPoint::Make((i>>0)&1, (i>>1)&1),
73cb93a386Sopenharmony_ci                        SkPoint::Make((i>>2)&1, (i>>3)&1),
74cb93a386Sopenharmony_ci                        SkPoint::Make((i>>4)&1, (i>>5)&1),
75cb93a386Sopenharmony_ci                        SkPoint::Make((i>>6)&1, (i>>7)&1)};
76cb93a386Sopenharmony_ci        check_cubic_convex_180(r, p);
77cb93a386Sopenharmony_ci    }
78cb93a386Sopenharmony_ci
79cb93a386Sopenharmony_ci    {
80cb93a386Sopenharmony_ci        // This cubic has a convex-180 chop at T=1-"epsilon"
81cb93a386Sopenharmony_ci        static const uint32_t hexPts[] = {0x3ee0ac74, 0x3f1e061a, 0x3e0fc408, 0x3f457230,
82cb93a386Sopenharmony_ci                                          0x3f42ac7c, 0x3f70d76c, 0x3f4e6520, 0x3f6acafa};
83cb93a386Sopenharmony_ci        SkPoint p[4];
84cb93a386Sopenharmony_ci        memcpy(p, hexPts, sizeof(p));
85cb93a386Sopenharmony_ci        check_cubic_convex_180(r, p);
86cb93a386Sopenharmony_ci    }
87cb93a386Sopenharmony_ci
88cb93a386Sopenharmony_ci    // Now test an exact quadratic.
89cb93a386Sopenharmony_ci    SkPoint quad[4] = {{0,0}, {2,2}, {4,2}, {6,0}};
90cb93a386Sopenharmony_ci    float T[2];
91cb93a386Sopenharmony_ci    bool areCusps;
92cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, GrPathUtils::findCubicConvex180Chops(quad, T, &areCusps) == 0);
93cb93a386Sopenharmony_ci
94cb93a386Sopenharmony_ci    // Now test that cusps and near-cusps get flagged as cusps.
95cb93a386Sopenharmony_ci    SkPoint cusp[4] = {{0,0}, {1,1}, {1,0}, {0,1}};
96cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, GrPathUtils::findCubicConvex180Chops(cusp, T, &areCusps) == 1);
97cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, areCusps == true);
98cb93a386Sopenharmony_ci
99cb93a386Sopenharmony_ci    // Find the height of the right side of "cusp" at which the distance between its inflection
100cb93a386Sopenharmony_ci    // points is kEpsilon (in parametric space).
101cb93a386Sopenharmony_ci    constexpr static double kEpsilon = 1.0 / (1 << 11);
102cb93a386Sopenharmony_ci    constexpr static double kEpsilonSquared = kEpsilon * kEpsilon;
103cb93a386Sopenharmony_ci    double h = (1 - kEpsilonSquared) / (3 * kEpsilonSquared + 1);
104cb93a386Sopenharmony_ci    double dy = (1 - h) / 2;
105cb93a386Sopenharmony_ci    cusp[1].fY = (float)(1 - dy);
106cb93a386Sopenharmony_ci    cusp[2].fY = (float)(0 + dy);
107cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkFindCubicInflections(cusp, T) == 2);
108cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(T[1] - T[0], (float)kEpsilon, (float)kEpsilonSquared));
109cb93a386Sopenharmony_ci
110cb93a386Sopenharmony_ci    // Ensure two inflection points barely more than kEpsilon apart do not get flagged as cusps.
111cb93a386Sopenharmony_ci    cusp[1].fY = (float)(1 - 1.1 * dy);
112cb93a386Sopenharmony_ci    cusp[2].fY = (float)(0 + 1.1 * dy);
113cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, GrPathUtils::findCubicConvex180Chops(cusp, T, &areCusps) == 2);
114cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, areCusps == false);
115cb93a386Sopenharmony_ci
116cb93a386Sopenharmony_ci    // Ensure two inflection points barely less than kEpsilon apart do get flagged as cusps.
117cb93a386Sopenharmony_ci    cusp[1].fY = (float)(1 - .9 * dy);
118cb93a386Sopenharmony_ci    cusp[2].fY = (float)(0 + .9 * dy);
119cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, GrPathUtils::findCubicConvex180Chops(cusp, T, &areCusps) == 1);
120cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, areCusps == true);
121cb93a386Sopenharmony_ci}
122cb93a386Sopenharmony_ci
123cb93a386Sopenharmony_ciDEF_TEST(GrPathUtils_convertToCubic, r) {
124cb93a386Sopenharmony_ci    SkPoint cubic[4];
125cb93a386Sopenharmony_ci    skgpu::VertexWriter cubicWriter(cubic);
126cb93a386Sopenharmony_ci    GrPathUtils::writeLineAsCubic({0,0}, {3,6}, &cubicWriter);
127cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, cubic[0] == SkPoint::Make(0,0));
128cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(cubic[1].fX, 1));
129cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(cubic[1].fY, 2));
130cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(cubic[2].fX, 2));
131cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(cubic[2].fY, 4));
132cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, cubic[3] == SkPoint::Make(3,6));
133cb93a386Sopenharmony_ci
134cb93a386Sopenharmony_ci    SkPoint quad[3] = {{0,0}, {3,3}, {6,0}};
135cb93a386Sopenharmony_ci    GrPathUtils::convertQuadToCubic(quad, cubic);
136cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, cubic[0] == SkPoint::Make(0,0));
137cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(cubic[1].fX, 2));
138cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(cubic[1].fY, 2));
139cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(cubic[2].fX, 4));
140cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, SkScalarNearlyEqual(cubic[2].fY, 2));
141cb93a386Sopenharmony_ci    REPORTER_ASSERT(r, cubic[3] == SkPoint::Make(6,0));
142cb93a386Sopenharmony_ci}
143