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