blob: e4b5249cef759059a39c1c6aae1396f8bc222958 [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
Jim Van Verth0513f142017-03-24 14:28:57 -0400111#ifdef SK_DEBUG
112static bool is_convex(const SkTDArray<SkPoint>& poly) {
113 if (poly.count() <= 3) {
114 return true;
115 }
116
117 SkVector v0 = poly[0] - poly[poly.count() - 1];
118 SkVector v1 = poly[1] - poly[poly.count() - 1];
119 SkScalar winding = v0.cross(v1);
120
121 for (int i = 0; i < poly.count() - 1; ++i) {
122 int j = i + 1;
123 int k = (i + 2) % poly.count();
124
125 SkVector v0 = poly[j] - poly[i];
126 SkVector v1 = poly[k] - poly[i];
127 SkScalar perpDot = v0.cross(v1);
128 int side = winding*perpDot;
129 if (side < 0) {
130 return false;
131 }
132 }
133
134 return true;
135}
136#endif
137
Brian Salomonab664fa2017-03-24 16:07:20 +0000138// The objective here is to inset all of the edges by the given distance, and then
139// remove any invalid inset edges by detecting right-hand turns. In a ccw polygon,
140// we should only be making left-hand turns (for cw polygons, we use the winding
141// parameter to reverse this). We detect this by checking whether the second intersection
142// on an edge is closer to its tail than the first one.
143//
144// We might also have the case that there is no intersection between two neighboring inset edges.
145// In this case, one edge will lie to the right of the other and should be discarded along with
146// its previous intersection (if any).
147//
148// Note: the assumption is that inputPolygon is convex and has no coincident points.
149//
150bool SkInsetConvexPolygon(const SkPoint* inputPolygonVerts, int inputPolygonSize,
151 SkScalar insetDistance, SkTDArray<SkPoint>* insetPolygon) {
152 if (inputPolygonSize < 3) {
153 return false;
154 }
155
156 int winding = get_winding(inputPolygonVerts, inputPolygonSize);
157 if (0 == winding) {
158 return false;
159 }
160
161 // set up
162 struct EdgeData {
163 InsetSegment fInset;
164 SkPoint fIntersection;
165 SkScalar fTValue;
166 bool fValid;
167 };
168
169 SkAutoSTMalloc<64, EdgeData> edgeData(inputPolygonSize);
170 for (int i = 0; i < inputPolygonSize; ++i) {
Brian Salomonab664fa2017-03-24 16:07:20 +0000171 int j = (i + 1) % inputPolygonSize;
172 inset_edge(inputPolygonVerts[i], inputPolygonVerts[j], insetDistance, winding,
173 &edgeData[i].fInset);
Jim Van Verthdc276f92017-03-24 12:10:48 -0400174 edgeData[i].fIntersection = edgeData[i].fInset.fP0;
Brian Salomonab664fa2017-03-24 16:07:20 +0000175 edgeData[i].fTValue = SK_ScalarMin;
Jim Van Verthdc276f92017-03-24 12:10:48 -0400176 edgeData[i].fValid = true;
Brian Salomonab664fa2017-03-24 16:07:20 +0000177 }
178
179 int prevIndex = inputPolygonSize - 1;
180 int currIndex = 0;
181 int insetVertexCount = inputPolygonSize;
182 while (prevIndex != currIndex) {
183 if (!edgeData[prevIndex].fValid) {
184 prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
185 continue;
186 }
187
188 SkScalar s, t;
189 SkPoint intersection;
190 if (compute_intersection(edgeData[prevIndex].fInset, edgeData[currIndex].fInset,
191 &intersection, &s, &t)) {
192 // if new intersection is further back on previous inset from the prior intersection
193 if (s < edgeData[prevIndex].fTValue) {
194 // no point in considering this one again
195 edgeData[prevIndex].fValid = false;
196 --insetVertexCount;
197 // go back one segment
198 prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
199 // we've already considered this intersection, we're done
200 } else if (edgeData[currIndex].fTValue > SK_ScalarMin &&
201 intersection.equalsWithinTolerance(edgeData[currIndex].fIntersection,
202 1.0e-6f)) {
203 break;
204 } else {
205 // add intersection
206 edgeData[currIndex].fIntersection = intersection;
207 edgeData[currIndex].fTValue = t;
208
209 // go to next segment
210 prevIndex = currIndex;
211 currIndex = (currIndex + 1) % inputPolygonSize;
212 }
213 } else {
214 // if prev to right side of curr
215 int side = winding*compute_side(edgeData[currIndex].fInset.fP0,
216 edgeData[currIndex].fInset.fP1,
217 edgeData[prevIndex].fInset.fP1);
218 if (side < 0 && side == winding*compute_side(edgeData[currIndex].fInset.fP0,
219 edgeData[currIndex].fInset.fP1,
220 edgeData[prevIndex].fInset.fP0)) {
221 // no point in considering this one again
222 edgeData[prevIndex].fValid = false;
223 --insetVertexCount;
224 // go back one segment
225 prevIndex = (prevIndex + inputPolygonSize - 1) % inputPolygonSize;
226 } else {
227 // move to next segment
228 edgeData[currIndex].fValid = false;
229 --insetVertexCount;
230 currIndex = (currIndex + 1) % inputPolygonSize;
231 }
232 }
233 }
234
235 // store all the valid intersections
236 insetPolygon->reset();
237 insetPolygon->setReserve(insetVertexCount);
238 for (int i = 0; i < inputPolygonSize; ++i) {
239 if (edgeData[i].fValid) {
240 *insetPolygon->push() = edgeData[i].fIntersection;
241 }
242 }
Jim Van Verth0513f142017-03-24 14:28:57 -0400243 SkASSERT(is_convex(*insetPolygon));
Brian Salomonab664fa2017-03-24 16:07:20 +0000244
245 return (insetPolygon->count() >= 3);
246}