blob: 8df7f0efe618e03e422de9eb5c8308cc1649cc01 [file] [log] [blame]
Brian Salomonab664fa2017-03-24 16:07:20 +00001/*
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 "SkInsetConvexPolygon.h"
9
10#include "SkTemplates.h"
11
12struct InsetSegment {
13 SkPoint fP0;
14 SkPoint fP1;
15};
16
17// Computes perpDot for point compared to segment.
18// A positive value means the point is to the left of the segment,
19// negative is to the right, 0 is collinear.
20static int compute_side(const SkPoint& s0, const SkPoint& s1, const SkPoint& p) {
21 SkVector v0 = s1 - s0;
22 SkVector v1 = p - s0;
23 SkScalar perpDot = v0.cross(v1);
24 if (!SkScalarNearlyZero(perpDot)) {
25 return ((perpDot > 0) ? 1 : -1);
26 }
27
28 return 0;
29}
30
31// returns 1 for ccw, -1 for cw and 0 if degenerate
32static int get_winding(const SkPoint* polygonVerts, int polygonSize) {
33 SkPoint p0 = polygonVerts[0];
34 SkPoint p1 = polygonVerts[1];
35
36 for (int i = 2; i < polygonSize; ++i) {
37 SkPoint p2 = polygonVerts[i];
38
39 // determine if cw or ccw
40 int side = compute_side(p0, p1, p2);
41 if (0 != side) {
42 return ((side > 0) ? 1 : -1);
43 }
44
45 // if nearly collinear, treat as straight line and continue
46 p1 = p2;
47 }
48
49 return 0;
50}
51
52// Perpendicularly offset line segment p0-p1 'distance' units in the direction specified by 'dir'
53static void inset_edge(const SkPoint& p0, const SkPoint& p1, SkScalar distance, int dir,
54 InsetSegment* inset) {
55 SkASSERT(dir == -1 || dir == 1);
56 // compute perpendicular
57 SkVector perp;
58 perp.fX = p0.fY - p1.fY;
59 perp.fY = p1.fX - p0.fX;
60 perp.setLength(distance*dir);
61 inset->fP0 = p0 + perp;
62 inset->fP1 = p1 + perp;
63}
64
65// Compute the intersection 'p' between segments s0 and s1, if any.
66// 's' is the parametric value for the intersection along 's0' & 't' is the same for 's1'.
67// Returns false if there is no intersection.
68static bool compute_intersection(const InsetSegment& s0, const InsetSegment& s1,
69 SkPoint* p, SkScalar* s, SkScalar* t) {
70 SkVector v0 = s0.fP1 - s0.fP0;
71 SkVector v1 = s1.fP1 - s1.fP0;
72
73 SkScalar perpDot = v0.cross(v1);
74 if (SkScalarNearlyZero(perpDot)) {
75 // segments are parallel
76 // check if endpoints are touching
77 if (s0.fP1.equalsWithinTolerance(s1.fP0)) {
78 *p = s0.fP1;
79 *s = SK_Scalar1;
80 *t = 0;
81 return true;
82 }
83 if (s1.fP1.equalsWithinTolerance(s0.fP0)) {
84 *p = s1.fP1;
85 *s = 0;
86 *t = SK_Scalar1;
87 return true;
88 }
89
90 return false;
91 }
92
93 SkVector d = s1.fP0 - s0.fP0;
94 SkScalar localS = d.cross(v1) / perpDot;
95 if (localS < 0 || localS > SK_Scalar1) {
96 return false;
97 }
98 SkScalar localT = d.cross(v0) / perpDot;
99 if (localT < 0 || localT > SK_Scalar1) {
100 return false;
101 }
102
103 v0 *= localS;
104 *p = s0.fP0 + v0;
105 *s = localS;
106 *t = localT;
107
108 return true;
109}
110
111// The objective here is to inset all of the edges by the given distance, and then
112// remove any invalid inset edges by detecting right-hand turns. In a ccw polygon,
113// we should only be making left-hand turns (for cw polygons, we use the winding
114// parameter to reverse this). We detect this by checking whether the second intersection
115// on an edge is closer to its tail than the first one.
116//
117// We might also have the case that there is no intersection between two neighboring inset edges.
118// In this case, one edge will lie to the right of the other and should be discarded along with
119// its previous intersection (if any).
120//
121// Note: the assumption is that inputPolygon is convex and has no coincident points.
122//
123bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize,
124 SkScalar insetDistance, SkTDArray<SkPoint>* insetPolygon) {
125 if (inputPolygonSize < 3) {
126 return false;
127 }
128
129 int winding = get_winding(inputPolygonVerts, inputPolygonSize);
130 if (0 == winding) {
131 return false;
132 }
133
134 // set up
135 struct EdgeData {
136 InsetSegment fInset;
137 SkPoint fIntersection;
138 SkScalar fTValue;
139 bool fValid;
140 };
141
142 SkAutoSTMalloc<64, EdgeData> edgeData(inputPolygonSize);
143 for (int i = 0; i < inputPolygonSize; ++i) {
144 edgeData[i].fValid = true;
145 int j = (i + 1) % inputPolygonSize;
146 inset_edge(inputPolygonVerts[i], inputPolygonVerts[j], insetDistance, winding,
147 &edgeData[i].fInset);
148 edgeData[i].fTValue = SK_ScalarMin;
149 }
150
151 int prevIndex = inputPolygonSize - 1;
152 int currIndex = 0;
153 int insetVertexCount = inputPolygonSize;
154 while (prevIndex != currIndex) {
155 if (!edgeData[prevIndex].fValid) {
156 prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
157 continue;
158 }
159
160 SkScalar s, t;
161 SkPoint intersection;
162 if (compute_intersection(edgeData[prevIndex].fInset, edgeData[currIndex].fInset,
163 &intersection, &s, &t)) {
164 // if new intersection is further back on previous inset from the prior intersection
165 if (s < edgeData[prevIndex].fTValue) {
166 // no point in considering this one again
167 edgeData[prevIndex].fValid = false;
168 --insetVertexCount;
169 // go back one segment
170 prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
171 // we've already considered this intersection, we're done
172 } else if (edgeData[currIndex].fTValue > SK_ScalarMin &&
173 intersection.equalsWithinTolerance(edgeData[currIndex].fIntersection,
174 1.0e-6f)) {
175 break;
176 } else {
177 // add intersection
178 edgeData[currIndex].fIntersection = intersection;
179 edgeData[currIndex].fTValue = t;
180
181 // go to next segment
182 prevIndex = currIndex;
183 currIndex = (currIndex + 1) % inputPolygonSize;
184 }
185 } else {
186 // if prev to right side of curr
187 int side = winding*compute_side(edgeData[currIndex].fInset.fP0,
188 edgeData[currIndex].fInset.fP1,
189 edgeData[prevIndex].fInset.fP1);
190 if (side < 0 && side == winding*compute_side(edgeData[currIndex].fInset.fP0,
191 edgeData[currIndex].fInset.fP1,
192 edgeData[prevIndex].fInset.fP0)) {
193 // no point in considering this one again
194 edgeData[prevIndex].fValid = false;
195 --insetVertexCount;
196 // go back one segment
197 prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
198 } else {
199 // move to next segment
200 edgeData[currIndex].fValid = false;
201 --insetVertexCount;
202 currIndex = (currIndex + 1) % inputPolygonSize;
203 }
204 }
205 }
206
207 // store all the valid intersections
208 insetPolygon->reset();
209 insetPolygon->setReserve(insetVertexCount);
210 for (int i = 0; i < inputPolygonSize; ++i) {
211 if (edgeData[i].fValid) {
212 *insetPolygon->push() = edgeData[i].fIntersection;
213 }
214 }
215
216#ifdef SK_DEBUG
217 bool convex = true;
218 for (int i = 0; i < insetPolygon->count(); ++i) {
219 int j = (i + 1) % insetPolygon->count();
220 int k = (i + 2) % insetPolygon->count();
221
222 int side = winding*compute_side((*insetPolygon)[i], (*insetPolygon)[j],
223 (*insetPolygon)[k]);
224 if (side < 0) {
225 convex = false;
226 break;
227 }
228 }
229 SkASSERT(convex);
230#endif
231
232 return (insetPolygon->count() >= 3);
233}