Chris Dalton | 419a94d | 2017-08-28 10:24:22 -0600 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2017 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
| 8 | #include "GrCCPRGeometry.h" |
| 9 | |
| 10 | #include "GrTypes.h" |
Chris Dalton | c1e5963 | 2017-09-05 00:30:07 -0600 | [diff] [blame^] | 11 | #include "SkGeometry.h" |
Chris Dalton | 419a94d | 2017-08-28 10:24:22 -0600 | [diff] [blame] | 12 | #include "SkPoint.h" |
Chris Dalton | c1e5963 | 2017-09-05 00:30:07 -0600 | [diff] [blame^] | 13 | #include "../pathops/SkPathOpsCubic.h" |
Chris Dalton | 419a94d | 2017-08-28 10:24:22 -0600 | [diff] [blame] | 14 | #include <algorithm> |
| 15 | #include <cmath> |
| 16 | #include <cstdlib> |
| 17 | |
| 18 | // We convert between SkPoint and Sk2f freely throughout this file. |
| 19 | GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); |
| 20 | GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint)); |
| 21 | GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX)); |
| 22 | |
Chris Dalton | c1e5963 | 2017-09-05 00:30:07 -0600 | [diff] [blame^] | 23 | void GrCCPRGeometry::beginPath() { |
| 24 | SkASSERT(!fBuildingContour); |
| 25 | fVerbs.push_back(Verb::kBeginPath); |
| 26 | } |
| 27 | |
| 28 | void GrCCPRGeometry::beginContour(const SkPoint& devPt) { |
| 29 | SkASSERT(!fBuildingContour); |
| 30 | |
| 31 | fCurrFanPoint = fCurrAnchorPoint = devPt; |
| 32 | |
| 33 | // Store the current verb count in the fTriangles field for now. When we close the contour we |
| 34 | // will use this value to calculate the actual number of triangles in its fan. |
| 35 | fCurrContourTallies = {fVerbs.count(), 0, 0, 0}; |
| 36 | |
| 37 | fPoints.push_back(devPt); |
| 38 | fVerbs.push_back(Verb::kBeginContour); |
| 39 | |
| 40 | SkDEBUGCODE(fBuildingContour = true;) |
| 41 | } |
| 42 | |
| 43 | void GrCCPRGeometry::lineTo(const SkPoint& devPt) { |
| 44 | SkASSERT(fBuildingContour); |
| 45 | fCurrFanPoint = devPt; |
| 46 | fPoints.push_back(devPt); |
| 47 | fVerbs.push_back(Verb::kLineTo); |
| 48 | } |
| 49 | |
Chris Dalton | 419a94d | 2017-08-28 10:24:22 -0600 | [diff] [blame] | 50 | static inline Sk2f normalize(const Sk2f& n) { |
| 51 | Sk2f nn = n*n; |
| 52 | return n * (nn + SkNx_shuffle<1,0>(nn)).rsqrt(); |
| 53 | } |
| 54 | |
| 55 | static inline float dot(const Sk2f& a, const Sk2f& b) { |
| 56 | float product[2]; |
| 57 | (a * b).store(product); |
| 58 | return product[0] + product[1]; |
| 59 | } |
| 60 | |
| 61 | // Returns whether the (convex) curve segment is monotonic with respect to [endPt - startPt]. |
| 62 | static inline bool is_convex_curve_monotonic(const Sk2f& startPt, const Sk2f& startTan, |
| 63 | const Sk2f& endPt, const Sk2f& endTan) { |
| 64 | Sk2f v = endPt - startPt; |
| 65 | float dot0 = dot(startTan, v); |
| 66 | float dot1 = dot(endTan, v); |
| 67 | |
| 68 | // A small, negative tolerance handles floating-point error in the case when one tangent |
| 69 | // approaches 0 length, meaning the (convex) curve segment is effectively a flat line. |
| 70 | float tolerance = -std::max(std::abs(dot0), std::abs(dot1)) * SK_ScalarNearlyZero; |
| 71 | return dot0 >= tolerance && dot1 >= tolerance; |
| 72 | } |
| 73 | |
| 74 | static inline Sk2f lerp(const Sk2f& a, const Sk2f& b, const Sk2f& t) { |
| 75 | return SkNx_fma(t, b - a, a); |
| 76 | } |
| 77 | |
Chris Dalton | c1e5963 | 2017-09-05 00:30:07 -0600 | [diff] [blame^] | 78 | void GrCCPRGeometry::quadraticTo(const SkPoint& devP0, const SkPoint& devP1) { |
| 79 | SkASSERT(fBuildingContour); |
| 80 | |
| 81 | Sk2f p0 = Sk2f::Load(&fCurrFanPoint); |
| 82 | Sk2f p1 = Sk2f::Load(&devP0); |
| 83 | Sk2f p2 = Sk2f::Load(&devP1); |
| 84 | fCurrFanPoint = devP1; |
Chris Dalton | 419a94d | 2017-08-28 10:24:22 -0600 | [diff] [blame] | 85 | |
| 86 | Sk2f tan0 = p1 - p0; |
| 87 | Sk2f tan1 = p2 - p1; |
| 88 | // This should almost always be this case for well-behaved curves in the real world. |
| 89 | if (is_convex_curve_monotonic(p0, tan0, p2, tan1)) { |
Chris Dalton | c1e5963 | 2017-09-05 00:30:07 -0600 | [diff] [blame^] | 90 | this->appendMonotonicQuadratic(p1, p2); |
| 91 | return; |
Chris Dalton | 419a94d | 2017-08-28 10:24:22 -0600 | [diff] [blame] | 92 | } |
| 93 | |
| 94 | // Chop the curve into two segments with equal curvature. To do this we find the T value whose |
| 95 | // tangent is perpendicular to the vector that bisects tan0 and -tan1. |
| 96 | Sk2f n = normalize(tan0) - normalize(tan1); |
| 97 | |
| 98 | // This tangent can be found where (dQ(t) dot n) = 0: |
| 99 | // |
| 100 | // 0 = (dQ(t) dot n) = | 2*t 1 | * | p0 - 2*p1 + p2 | * | n | |
| 101 | // | -2*p0 + 2*p1 | | . | |
| 102 | // |
| 103 | // = | 2*t 1 | * | tan1 - tan0 | * | n | |
| 104 | // | 2*tan0 | | . | |
| 105 | // |
| 106 | // = 2*t * ((tan1 - tan0) dot n) + (2*tan0 dot n) |
| 107 | // |
| 108 | // t = (tan0 dot n) / ((tan0 - tan1) dot n) |
| 109 | Sk2f dQ1n = (tan0 - tan1) * n; |
| 110 | Sk2f dQ0n = tan0 * n; |
| 111 | Sk2f t = (dQ0n + SkNx_shuffle<1,0>(dQ0n)) / (dQ1n + SkNx_shuffle<1,0>(dQ1n)); |
| 112 | t = Sk2f::Min(Sk2f::Max(t, 0), 1); // Clamp for FP error. |
| 113 | |
| 114 | Sk2f p01 = SkNx_fma(t, tan0, p0); |
| 115 | Sk2f p12 = SkNx_fma(t, tan1, p1); |
| 116 | Sk2f p012 = lerp(p01, p12, t); |
| 117 | |
Chris Dalton | c1e5963 | 2017-09-05 00:30:07 -0600 | [diff] [blame^] | 118 | this->appendMonotonicQuadratic(p01, p012); |
| 119 | this->appendMonotonicQuadratic(p12, p2); |
| 120 | } |
Chris Dalton | 419a94d | 2017-08-28 10:24:22 -0600 | [diff] [blame] | 121 | |
Chris Dalton | c1e5963 | 2017-09-05 00:30:07 -0600 | [diff] [blame^] | 122 | inline void GrCCPRGeometry::appendMonotonicQuadratic(const Sk2f& p1, const Sk2f& p2) { |
| 123 | p1.store(&fPoints.push_back()); |
| 124 | p2.store(&fPoints.push_back()); |
| 125 | fVerbs.push_back(Verb::kMonotonicQuadraticTo); |
| 126 | ++fCurrContourTallies.fQuadratics; |
| 127 | } |
| 128 | |
| 129 | void GrCCPRGeometry::cubicTo(const SkPoint& devP1, const SkPoint& devP2, const SkPoint& devP3) { |
| 130 | SkASSERT(fBuildingContour); |
| 131 | |
| 132 | SkPoint P[4] = {fCurrFanPoint, devP1, devP2, devP3}; |
| 133 | double t[2], s[2]; |
| 134 | SkCubicType type = SkClassifyCubic(P, t, s); |
| 135 | |
| 136 | if (SkCubicType::kLineOrPoint == type) { |
| 137 | this->lineTo(P[3]); |
| 138 | return; |
| 139 | } |
| 140 | |
| 141 | if (SkCubicType::kQuadratic == type) { |
| 142 | SkPoint quadP1 = (devP1 + devP2) * .75f - (fCurrFanPoint + devP3) * .25f; |
| 143 | this->quadraticTo(quadP1, devP3); |
| 144 | return; |
| 145 | } |
| 146 | |
| 147 | fCurrFanPoint = devP3; |
| 148 | |
| 149 | SkDCubic C; |
| 150 | C.set(P); |
| 151 | |
| 152 | for (int x = 0; x <= 1; ++x) { |
| 153 | if (t[x] * s[x] <= 0) { // This is equivalent to tx/sx <= 0. |
| 154 | // This technically also gets taken if tx/sx = infinity, but the code still does |
| 155 | // the right thing in that edge case. |
| 156 | continue; // Don't increment x0. |
| 157 | } |
| 158 | if (fabs(t[x]) >= fabs(s[x])) { // tx/sx >= 1. |
| 159 | break; |
| 160 | } |
| 161 | |
| 162 | const double chopT = double(t[x]) / double(s[x]); |
| 163 | SkASSERT(chopT >= 0 && chopT <= 1); |
| 164 | if (chopT <= 0 || chopT >= 1) { // floating-point error. |
| 165 | continue; |
| 166 | } |
| 167 | |
| 168 | SkDCubicPair chopped = C.chopAt(chopT); |
| 169 | |
| 170 | // Ensure the double points are identical if this is a loop (more workarounds for FP error). |
| 171 | if (SkCubicType::kLoop == type && 0 == t[0]) { |
| 172 | chopped.pts[3] = chopped.pts[0]; |
| 173 | } |
| 174 | |
| 175 | // (This might put ts0/ts1 out of order, but it doesn't matter anymore at this point.) |
| 176 | this->appendConvexCubic(type, chopped.first()); |
| 177 | t[x] = 0; |
| 178 | s[x] = 1; |
| 179 | |
| 180 | const double r = s[1 - x] * chopT; |
| 181 | t[1 - x] -= r; |
| 182 | s[1 - x] -= r; |
| 183 | |
| 184 | C = chopped.second(); |
| 185 | } |
| 186 | |
| 187 | this->appendConvexCubic(type, C); |
| 188 | } |
| 189 | |
| 190 | static SkPoint to_skpoint(const SkDPoint& dpoint) { |
| 191 | return {static_cast<SkScalar>(dpoint.fX), static_cast<SkScalar>(dpoint.fY)}; |
| 192 | } |
| 193 | |
| 194 | inline void GrCCPRGeometry::appendConvexCubic(SkCubicType type, const SkDCubic& C) { |
| 195 | fPoints.push_back(to_skpoint(C[1])); |
| 196 | fPoints.push_back(to_skpoint(C[2])); |
| 197 | fPoints.push_back(to_skpoint(C[3])); |
| 198 | if (SkCubicType::kLoop != type) { |
| 199 | fVerbs.push_back(Verb::kConvexSerpentineTo); |
| 200 | ++fCurrContourTallies.fSerpentines; |
| 201 | } else { |
| 202 | fVerbs.push_back(Verb::kConvexLoopTo); |
| 203 | ++fCurrContourTallies.fLoops; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | GrCCPRGeometry::PrimitiveTallies GrCCPRGeometry::endContour() { |
| 208 | SkASSERT(fBuildingContour); |
| 209 | SkASSERT(fVerbs.count() >= fCurrContourTallies.fTriangles); |
| 210 | |
| 211 | // The fTriangles field currently contains this contour's starting verb index. We can now |
| 212 | // use it to calculate the size of the contour's fan. |
| 213 | int fanSize = fVerbs.count() - fCurrContourTallies.fTriangles; |
| 214 | if (fCurrFanPoint == fCurrAnchorPoint) { |
| 215 | --fanSize; |
| 216 | fVerbs.push_back(Verb::kEndClosedContour); |
| 217 | } else { |
| 218 | fVerbs.push_back(Verb::kEndOpenContour); |
| 219 | } |
| 220 | |
| 221 | fCurrContourTallies.fTriangles = SkTMax(fanSize - 2, 0); |
| 222 | |
| 223 | SkDEBUGCODE(fBuildingContour = false;) |
| 224 | return fCurrContourTallies; |
Chris Dalton | 419a94d | 2017-08-28 10:24:22 -0600 | [diff] [blame] | 225 | } |