blob: a2c08908bf3e5ade71c43b2dab5d0df108765f70 [file] [log] [blame]
Chris Dalton419a94d2017-08-28 10:24:22 -06001/*
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 Daltonc1e59632017-09-05 00:30:07 -060011#include "SkGeometry.h"
Chris Dalton419a94d2017-08-28 10:24:22 -060012#include "SkPoint.h"
Chris Daltonc1e59632017-09-05 00:30:07 -060013#include "../pathops/SkPathOpsCubic.h"
Chris Dalton419a94d2017-08-28 10:24:22 -060014#include <algorithm>
15#include <cmath>
16#include <cstdlib>
17
18// We convert between SkPoint and Sk2f freely throughout this file.
19GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT);
20GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint));
21GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX));
22
Chris Daltonc1e59632017-09-05 00:30:07 -060023void GrCCPRGeometry::beginPath() {
24 SkASSERT(!fBuildingContour);
25 fVerbs.push_back(Verb::kBeginPath);
26}
27
28void 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
43void 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 Dalton419a94d2017-08-28 10:24:22 -060050static inline Sk2f normalize(const Sk2f& n) {
51 Sk2f nn = n*n;
52 return n * (nn + SkNx_shuffle<1,0>(nn)).rsqrt();
53}
54
55static 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].
62static 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
74static inline Sk2f lerp(const Sk2f& a, const Sk2f& b, const Sk2f& t) {
75 return SkNx_fma(t, b - a, a);
76}
77
Chris Daltonc1e59632017-09-05 00:30:07 -060078void 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 Dalton419a94d2017-08-28 10:24:22 -060085
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 Daltonc1e59632017-09-05 00:30:07 -060090 this->appendMonotonicQuadratic(p1, p2);
91 return;
Chris Dalton419a94d2017-08-28 10:24:22 -060092 }
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 Daltonc1e59632017-09-05 00:30:07 -0600118 this->appendMonotonicQuadratic(p01, p012);
119 this->appendMonotonicQuadratic(p12, p2);
120}
Chris Dalton419a94d2017-08-28 10:24:22 -0600121
Chris Daltonc1e59632017-09-05 00:30:07 -0600122inline 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
129void 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
190static SkPoint to_skpoint(const SkDPoint& dpoint) {
191 return {static_cast<SkScalar>(dpoint.fX), static_cast<SkScalar>(dpoint.fY)};
192}
193
194inline 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
207GrCCPRGeometry::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 Dalton419a94d2017-08-28 10:24:22 -0600225}