Coverage Report

Created: 2018-09-25 14:53

/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 */