/work/obj-fuzz/dist/include/mozilla/gfx/Polygon.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #ifndef MOZILLA_GFX_POLYGON_H |
8 | | #define MOZILLA_GFX_POLYGON_H |
9 | | |
10 | | #include "Matrix.h" |
11 | | #include "mozilla/Move.h" |
12 | | #include "nsTArray.h" |
13 | | #include "Point.h" |
14 | | #include "Triangle.h" |
15 | | |
16 | | #include <initializer_list> |
17 | | |
18 | | namespace mozilla { |
19 | | namespace gfx { |
20 | | |
21 | | /** |
22 | | * Calculates the w = 0 intersection point for the edge defined by |
23 | | * |aFirst| and |aSecond|. |
24 | | */ |
25 | | template<class Units> |
26 | | Point4DTyped<Units> |
27 | | CalculateEdgeIntersect(const Point4DTyped<Units>& aFirst, |
28 | | const Point4DTyped<Units>& aSecond) |
29 | 0 | { |
30 | 0 | static const float w = 0.00001f; |
31 | 0 | const float t = (w - aFirst.w) / (aSecond.w - aFirst.w); |
32 | 0 | return aFirst + (aSecond - aFirst) * t; |
33 | 0 | } |
34 | | |
35 | | /** |
36 | | * Clips the polygon defined by |aPoints| so that there are no points with |
37 | | * w <= 0. |
38 | | */ |
39 | | template<class Units> |
40 | | nsTArray<Point4DTyped<Units>> |
41 | | ClipPointsAtInfinity(const nsTArray<Point4DTyped<Units>>& aPoints) |
42 | 0 | { |
43 | 0 | nsTArray<Point4DTyped<Units>> outPoints(aPoints.Length()); |
44 | 0 |
|
45 | 0 | const size_t pointCount = aPoints.Length(); |
46 | 0 | for (size_t i = 0; i < pointCount; ++i) { |
47 | 0 | const Point4DTyped<Units>& first = aPoints[i]; |
48 | 0 | const Point4DTyped<Units>& second = aPoints[(i + 1) % pointCount]; |
49 | 0 |
|
50 | 0 | if (!first.w || !second.w) { |
51 | 0 | // Skip edges at infinity. |
52 | 0 | continue; |
53 | 0 | } |
54 | 0 | |
55 | 0 | if (first.w > 0.0f) { |
56 | 0 | outPoints.AppendElement(first); |
57 | 0 | } |
58 | 0 |
|
59 | 0 | if ((first.w <= 0.0f) ^ (second.w <= 0.0f)) { |
60 | 0 | outPoints.AppendElement(CalculateEdgeIntersect(first, second)); |
61 | 0 | } |
62 | 0 | } |
63 | 0 |
|
64 | 0 | return outPoints; |
65 | 0 | } |
66 | | |
67 | | /** |
68 | | * Calculates the distances between the points in |aPoints| and the plane |
69 | | * defined by |aPlaneNormal| and |aPlanePoint|. |
70 | | */ |
71 | | template<class Units> |
72 | | nsTArray<float> |
73 | | CalculatePointPlaneDistances(const nsTArray<Point4DTyped<Units>>& aPoints, |
74 | | const Point4DTyped<Units>& aPlaneNormal, |
75 | | const Point4DTyped<Units>& aPlanePoint, |
76 | | size_t& aPos, size_t& aNeg) |
77 | 0 | { |
78 | 0 | // Point classification might produce incorrect results due to numerical |
79 | 0 | // inaccuracies. Using an epsilon value makes the splitting plane "thicker". |
80 | 0 | const float epsilon = 0.05f; |
81 | 0 |
|
82 | 0 | aPos = aNeg = 0; |
83 | 0 | nsTArray<float> distances(aPoints.Length()); |
84 | 0 |
|
85 | 0 | for (const Point4DTyped<Units>& point : aPoints) { |
86 | 0 | float dot = (point - aPlanePoint).DotProduct(aPlaneNormal); |
87 | 0 |
|
88 | 0 | if (dot > epsilon) { |
89 | 0 | aPos++; |
90 | 0 | } else if (dot < -epsilon) { |
91 | 0 | aNeg++; |
92 | 0 | } else { |
93 | 0 | // The point is within the thick plane. |
94 | 0 | dot = 0.0f; |
95 | 0 | } |
96 | 0 |
|
97 | 0 | distances.AppendElement(dot); |
98 | 0 | } |
99 | 0 |
|
100 | 0 | return distances; |
101 | 0 | } |
102 | | |
103 | | /** |
104 | | * Clips the polygon defined by |aPoints|. The clipping uses previously |
105 | | * calculated plane to point distances and the plane normal |aNormal|. |
106 | | * The result of clipping is stored in |aBackPoints| and |aFrontPoints|. |
107 | | */ |
108 | | template<class Units> |
109 | | void |
110 | | ClipPointsWithPlane(const nsTArray<Point4DTyped<Units>>& aPoints, |
111 | | const Point4DTyped<Units>& aNormal, |
112 | | const nsTArray<float>& aDots, |
113 | | nsTArray<Point4DTyped<Units>>& aBackPoints, |
114 | | nsTArray<Point4DTyped<Units>>& aFrontPoints) |
115 | 0 | { |
116 | 0 | static const auto Sign = [](const float& f) { |
117 | 0 | if (f > 0.0f) return 1; |
118 | 0 | if (f < 0.0f) return -1; |
119 | 0 | return 0; |
120 | 0 | }; |
121 | 0 |
|
122 | 0 | const size_t pointCount = aPoints.Length(); |
123 | 0 | for (size_t i = 0; i < pointCount; ++i) { |
124 | 0 | size_t j = (i + 1) % pointCount; |
125 | 0 |
|
126 | 0 | const Point4DTyped<Units>& a = aPoints[i]; |
127 | 0 | const Point4DTyped<Units>& b = aPoints[j]; |
128 | 0 | const float dotA = aDots[i]; |
129 | 0 | const float dotB = aDots[j]; |
130 | 0 |
|
131 | 0 | // The point is in front of or on the plane. |
132 | 0 | if (dotA >= 0) { |
133 | 0 | aFrontPoints.AppendElement(a); |
134 | 0 | } |
135 | 0 |
|
136 | 0 | // The point is behind or on the plane. |
137 | 0 | if (dotA <= 0) { |
138 | 0 | aBackPoints.AppendElement(a); |
139 | 0 | } |
140 | 0 |
|
141 | 0 | // If the sign of the dot products changes between two consecutive |
142 | 0 | // vertices, then the plane intersects with the polygon edge. |
143 | 0 | // The case where the polygon edge is within the plane is handled above. |
144 | 0 | if (Sign(dotA) && Sign(dotB) && Sign(dotA) != Sign(dotB)) { |
145 | 0 | // Calculate the line segment and plane intersection point. |
146 | 0 | const Point4DTyped<Units> ab = b - a; |
147 | 0 | const float dotAB = ab.DotProduct(aNormal); |
148 | 0 | const float t = -dotA / dotAB; |
149 | 0 | const Point4DTyped<Units> p = a + (ab * t); |
150 | 0 |
|
151 | 0 | // Add the intersection point to both polygons. |
152 | 0 | aBackPoints.AppendElement(p); |
153 | 0 | aFrontPoints.AppendElement(p); |
154 | 0 | } |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | /** |
159 | | * PolygonTyped stores the points of a convex planar polygon. |
160 | | */ |
161 | | template<class Units> |
162 | | class PolygonTyped { |
163 | | typedef Point3DTyped<Units> Point3DType; |
164 | | typedef Point4DTyped<Units> Point4DType; |
165 | | |
166 | | public: |
167 | 0 | PolygonTyped() {} |
168 | | |
169 | | explicit PolygonTyped(const nsTArray<Point4DType>& aPoints, |
170 | | const Point4DType& aNormal = DefaultNormal()) |
171 | | : mNormal(aNormal), mPoints(aPoints) {} |
172 | | |
173 | | explicit PolygonTyped(nsTArray<Point4DType>&& aPoints, |
174 | | const Point4DType& aNormal = DefaultNormal()) |
175 | 0 | : mNormal(aNormal), mPoints(std::move(aPoints)) {} |
176 | | |
177 | | explicit PolygonTyped(const std::initializer_list<Point4DType>& aPoints, |
178 | | const Point4DType& aNormal = DefaultNormal()) |
179 | | : mNormal(aNormal), mPoints(aPoints) |
180 | | { |
181 | | #ifdef DEBUG |
182 | | EnsurePlanarPolygon(); |
183 | | #endif |
184 | | } |
185 | | |
186 | | /** |
187 | | * Returns the smallest 2D rectangle that can fully cover the polygon. |
188 | | */ |
189 | | RectTyped<Units> BoundingBox() const |
190 | 0 | { |
191 | 0 | if (mPoints.IsEmpty()) { |
192 | 0 | return RectTyped<Units>(); |
193 | 0 | } |
194 | 0 | |
195 | 0 | float minX, maxX, minY, maxY; |
196 | 0 | minX = maxX = mPoints[0].x; |
197 | 0 | minY = maxY = mPoints[0].y; |
198 | 0 |
|
199 | 0 | for (const Point4DType& point : mPoints) { |
200 | 0 | minX = std::min(point.x, minX); |
201 | 0 | maxX = std::max(point.x, maxX); |
202 | 0 |
|
203 | 0 | minY = std::min(point.y, minY); |
204 | 0 | maxY = std::max(point.y, maxY); |
205 | 0 | } |
206 | 0 |
|
207 | 0 | return RectTyped<Units>(minX, minY, maxX - minX, maxY - minY); |
208 | 0 | } |
209 | | |
210 | | /** |
211 | | * Clips the polygon against the given 2D rectangle. |
212 | | */ |
213 | | PolygonTyped<Units> ClipPolygon(const RectTyped<Units>& aRect) const |
214 | 0 | { |
215 | 0 | if (aRect.IsEmpty()) { |
216 | 0 | return PolygonTyped<Units>(); |
217 | 0 | } |
218 | 0 | |
219 | 0 | return ClipPolygon(FromRect(aRect)); |
220 | 0 | } |
221 | | |
222 | | /** |
223 | | * Clips this polygon against |aPolygon| in 2D and returns a new polygon. |
224 | | */ |
225 | | PolygonTyped<Units> ClipPolygon(const PolygonTyped<Units>& aPolygon) const |
226 | 0 | { |
227 | 0 | const nsTArray<Point4DType>& points = aPolygon.GetPoints(); |
228 | 0 |
|
229 | 0 | if (mPoints.IsEmpty() || points.IsEmpty()) { |
230 | 0 | return PolygonTyped<Units>(); |
231 | 0 | } |
232 | 0 | |
233 | 0 | nsTArray<Point4DType> clippedPoints(mPoints); |
234 | 0 |
|
235 | 0 | size_t pos, neg; |
236 | 0 | nsTArray<Point4DType> backPoints(4), frontPoints(4); |
237 | 0 |
|
238 | 0 | // Iterate over all the edges of the clipping polygon |aPolygon| and clip |
239 | 0 | // this polygon against the edges. |
240 | 0 | const size_t pointCount = points.Length(); |
241 | 0 | for (size_t i = 0; i < pointCount; ++i) { |
242 | 0 | const Point4DType p1 = points[(i + 1) % pointCount]; |
243 | 0 | const Point4DType p2 = points[i]; |
244 | 0 |
|
245 | 0 | // Calculate the normal for the edge defined by |p1| and |p2|. |
246 | 0 | const Point4DType normal(p2.y - p1.y, p1.x - p2.x, 0.0f, 0.0f); |
247 | 0 |
|
248 | 0 | // Calculate the distances between the points of the polygon and the |
249 | 0 | // plane defined by |aPolygon|. |
250 | 0 | const nsTArray<float> distances = |
251 | 0 | CalculatePointPlaneDistances(clippedPoints, normal, p1, pos, neg); |
252 | 0 |
|
253 | 0 | backPoints.ClearAndRetainStorage(); |
254 | 0 | frontPoints.ClearAndRetainStorage(); |
255 | 0 |
|
256 | 0 | // Clip the polygon points using the previously calculated distances. |
257 | 0 | ClipPointsWithPlane(clippedPoints, normal, distances, |
258 | 0 | backPoints, frontPoints); |
259 | 0 |
|
260 | 0 | // Only use the points behind the clipping plane. |
261 | 0 | clippedPoints = std::move(backPoints); |
262 | 0 |
|
263 | 0 | if (clippedPoints.Length() < 3) { |
264 | 0 | // The clipping created a polygon with no area. |
265 | 0 | return PolygonTyped<Units>(); |
266 | 0 | } |
267 | 0 | } |
268 | 0 |
|
269 | 0 | return PolygonTyped<Units>(std::move(clippedPoints), mNormal); |
270 | 0 | } |
271 | | |
272 | | /** |
273 | | * Returns a new polygon containing the bounds of the given 2D rectangle. |
274 | | */ |
275 | | static PolygonTyped<Units> FromRect(const RectTyped<Units>& aRect) |
276 | 0 | { |
277 | 0 | nsTArray<Point4DType> points { |
278 | 0 | Point4DType(aRect.X(), aRect.Y(), 0.0f, 1.0f), |
279 | 0 | Point4DType(aRect.X(), aRect.YMost(), 0.0f, 1.0f), |
280 | 0 | Point4DType(aRect.XMost(), aRect.YMost(), 0.0f, 1.0f), |
281 | 0 | Point4DType(aRect.XMost(), aRect.Y(), 0.0f, 1.0f) |
282 | 0 | }; |
283 | 0 |
|
284 | 0 | return PolygonTyped<Units>(std::move(points)); |
285 | 0 | } |
286 | | |
287 | | const Point4DType& GetNormal() const |
288 | 0 | { |
289 | 0 | return mNormal; |
290 | 0 | } |
291 | | |
292 | | const nsTArray<Point4DType>& GetPoints() const |
293 | 0 | { |
294 | 0 | return mPoints; |
295 | 0 | } |
296 | | |
297 | | bool IsEmpty() const |
298 | 0 | { |
299 | 0 | // If the polygon has less than three points, it has no visible area. |
300 | 0 | return mPoints.Length() < 3; |
301 | 0 | } |
302 | | |
303 | | /** |
304 | | * Returns a list of triangles covering the polygon. |
305 | | */ |
306 | | nsTArray<TriangleTyped<Units>> ToTriangles() const |
307 | 0 | { |
308 | 0 | nsTArray<TriangleTyped<Units>> triangles; |
309 | 0 |
|
310 | 0 | if (IsEmpty()) { |
311 | 0 | return triangles; |
312 | 0 | } |
313 | 0 | |
314 | 0 | // This fan triangulation method only works for convex polygons. |
315 | 0 | for (size_t i = 1; i < mPoints.Length() - 1; ++i) { |
316 | 0 | TriangleTyped<Units> triangle(Point(mPoints[0].x, mPoints[0].y), |
317 | 0 | Point(mPoints[i].x, mPoints[i].y), |
318 | 0 | Point(mPoints[i + 1].x, mPoints[i + 1].y)); |
319 | 0 | triangles.AppendElement(std::move(triangle)); |
320 | 0 | } |
321 | 0 |
|
322 | 0 | return triangles; |
323 | 0 | } |
324 | | |
325 | | void TransformToLayerSpace(const Matrix4x4Typed<Units, Units>& aTransform) |
326 | 0 | { |
327 | 0 | TransformPoints(aTransform, true); |
328 | 0 | mNormal = DefaultNormal(); |
329 | 0 | } |
330 | | |
331 | | void TransformToScreenSpace(const Matrix4x4Typed<Units, Units>& aTransform, |
332 | | const Matrix4x4Typed<Units, Units>& aInverseTransform) |
333 | 0 | { |
334 | 0 | TransformPoints(aTransform, false); |
335 | 0 |
|
336 | 0 | // Perspective projection transformation might produce points with w <= 0, |
337 | 0 | // so we need to clip these points. |
338 | 0 | mPoints = ClipPointsAtInfinity(mPoints); |
339 | 0 |
|
340 | 0 | // Normal vectors should be transformed using inverse transpose. |
341 | 0 | mNormal = aInverseTransform.TransposeTransform4D(mNormal); |
342 | 0 | } |
343 | | |
344 | | |
345 | | void TransformToScreenSpace(const Matrix4x4Typed<Units, Units>& aTransform) |
346 | 0 | { |
347 | 0 | MOZ_ASSERT(!aTransform.IsSingular()); |
348 | 0 |
|
349 | 0 | TransformToScreenSpace(aTransform, aTransform.Inverse()); |
350 | 0 | } |
351 | | |
352 | | private: |
353 | | static Point4DType DefaultNormal() |
354 | 0 | { |
355 | 0 | return Point4DType(0.0f, 0.0f, 1.0f, 0.0f); |
356 | 0 | } |
357 | | |
358 | | #ifdef DEBUG |
359 | | void EnsurePlanarPolygon() const |
360 | | { |
361 | | if (mPoints.Length() <= 3) { |
362 | | // Polygons with three or less points are guaranteed to be planar. |
363 | | return; |
364 | | } |
365 | | |
366 | | // This normal calculation method works only for planar polygons. |
367 | | // The resulting normal vector will point towards the viewer when the |
368 | | // polygon has a counter-clockwise winding order from the perspective |
369 | | // of the viewer. |
370 | | Point3DType normal; |
371 | | const Point3DType p0 = mPoints[0].As3DPoint(); |
372 | | |
373 | | for (size_t i = 1; i < mPoints.Length() - 1; ++i) { |
374 | | const Point3DType p1 = mPoints[i].As3DPoint(); |
375 | | const Point3DType p2 = mPoints[i + 1].As3DPoint(); |
376 | | |
377 | | normal += (p1 - p0).CrossProduct(p2 - p0); |
378 | | } |
379 | | |
380 | | // Ensure that at least one component is greater than zero. |
381 | | // This avoids division by zero when normalizing the vector. |
382 | | bool hasNonZeroComponent = std::abs(normal.x) > 0.0f || |
383 | | std::abs(normal.y) > 0.0f || |
384 | | std::abs(normal.z) > 0.0f; |
385 | | |
386 | | MOZ_ASSERT(hasNonZeroComponent); |
387 | | |
388 | | normal.Normalize(); |
389 | | |
390 | | // Ensure that the polygon is planar. |
391 | | // http://mathworld.wolfram.com/Point-PlaneDistance.html |
392 | | const float epsilon = 0.01f; |
393 | | for (const Point4DType& point : mPoints) { |
394 | | const Point3DType p1 = point.As3DPoint(); |
395 | | const float d = normal.DotProduct(p1 - p0); |
396 | | |
397 | | MOZ_ASSERT(std::abs(d) < epsilon); |
398 | | } |
399 | | } |
400 | | #endif |
401 | | |
402 | | void TransformPoints(const Matrix4x4Typed<Units, Units>& aTransform, |
403 | | const bool aDivideByW) |
404 | 0 | { |
405 | 0 | for (Point4DType& point : mPoints) { |
406 | 0 | point = aTransform.TransformPoint(point); |
407 | 0 |
|
408 | 0 | if (aDivideByW && point.w > 0.0f) { |
409 | 0 | point = point / point.w; |
410 | 0 | } |
411 | 0 | } |
412 | 0 | } |
413 | | |
414 | | Point4DType mNormal; |
415 | | nsTArray<Point4DType> mPoints; |
416 | | }; |
417 | | |
418 | | typedef PolygonTyped<UnknownUnits> Polygon; |
419 | | |
420 | | } // namespace gfx |
421 | | } // namespace mozilla |
422 | | |
423 | | #endif /* MOZILLA_GFX_POLYGON_H */ |