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