forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			396 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			396 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #ifndef MOZILLA_GFX_POLYGON_H
 | |
| #define MOZILLA_GFX_POLYGON_H
 | |
| 
 | |
| #include <initializer_list>
 | |
| #include <utility>
 | |
| 
 | |
| #include "Matrix.h"
 | |
| #include "Point.h"
 | |
| #include "Triangle.h"
 | |
| #include "nsTArray.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace gfx {
 | |
| 
 | |
| /**
 | |
|  * Calculates the w = 0 intersection point for the edge defined by
 | |
|  * |aFirst| and |aSecond|.
 | |
|  */
 | |
| template <class Units>
 | |
| Point4DTyped<Units> CalculateEdgeIntersect(const Point4DTyped<Units>& aFirst,
 | |
|                                            const Point4DTyped<Units>& aSecond) {
 | |
|   static const float w = 0.00001f;
 | |
|   const float t = (w - aFirst.w) / (aSecond.w - aFirst.w);
 | |
|   return aFirst + (aSecond - aFirst) * t;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Clips the polygon defined by |aPoints| so that there are no points with
 | |
|  * w <= 0.
 | |
|  */
 | |
| template <class Units>
 | |
| nsTArray<Point4DTyped<Units>> ClipPointsAtInfinity(
 | |
|     const nsTArray<Point4DTyped<Units>>& aPoints) {
 | |
|   nsTArray<Point4DTyped<Units>> outPoints(aPoints.Length());
 | |
| 
 | |
|   const size_t pointCount = aPoints.Length();
 | |
|   for (size_t i = 0; i < pointCount; ++i) {
 | |
|     const Point4DTyped<Units>& first = aPoints[i];
 | |
|     const Point4DTyped<Units>& second = aPoints[(i + 1) % pointCount];
 | |
| 
 | |
|     if (!first.w || !second.w) {
 | |
|       // Skip edges at infinity.
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (first.w > 0.0f) {
 | |
|       outPoints.AppendElement(first);
 | |
|     }
 | |
| 
 | |
|     if ((first.w <= 0.0f) ^ (second.w <= 0.0f)) {
 | |
|       outPoints.AppendElement(CalculateEdgeIntersect(first, second));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return outPoints;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Calculates the distances between the points in |aPoints| and the plane
 | |
|  * defined by |aPlaneNormal| and |aPlanePoint|.
 | |
|  */
 | |
| template <class Units>
 | |
| nsTArray<float> CalculatePointPlaneDistances(
 | |
|     const nsTArray<Point4DTyped<Units>>& aPoints,
 | |
|     const Point4DTyped<Units>& aPlaneNormal,
 | |
|     const Point4DTyped<Units>& aPlanePoint, size_t& aPos, size_t& aNeg) {
 | |
|   // Point classification might produce incorrect results due to numerical
 | |
|   // inaccuracies. Using an epsilon value makes the splitting plane "thicker".
 | |
|   const float epsilon = 0.05f;
 | |
| 
 | |
|   aPos = aNeg = 0;
 | |
|   nsTArray<float> distances(aPoints.Length());
 | |
| 
 | |
|   for (const Point4DTyped<Units>& point : aPoints) {
 | |
|     float dot = (point - aPlanePoint).DotProduct(aPlaneNormal);
 | |
| 
 | |
|     if (dot > epsilon) {
 | |
|       aPos++;
 | |
|     } else if (dot < -epsilon) {
 | |
|       aNeg++;
 | |
|     } else {
 | |
|       // The point is within the thick plane.
 | |
|       dot = 0.0f;
 | |
|     }
 | |
| 
 | |
|     distances.AppendElement(dot);
 | |
|   }
 | |
| 
 | |
|   return distances;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Clips the polygon defined by |aPoints|. The clipping uses previously
 | |
|  * calculated plane to point distances and the plane normal |aNormal|.
 | |
|  * The result of clipping is stored in |aBackPoints| and |aFrontPoints|.
 | |
|  */
 | |
| template <class Units>
 | |
| void ClipPointsWithPlane(const nsTArray<Point4DTyped<Units>>& aPoints,
 | |
|                          const Point4DTyped<Units>& aNormal,
 | |
|                          const nsTArray<float>& aDots,
 | |
|                          nsTArray<Point4DTyped<Units>>& aBackPoints,
 | |
|                          nsTArray<Point4DTyped<Units>>& aFrontPoints) {
 | |
|   static const auto Sign = [](const float& f) {
 | |
|     if (f > 0.0f) return 1;
 | |
|     if (f < 0.0f) return -1;
 | |
|     return 0;
 | |
|   };
 | |
| 
 | |
|   const size_t pointCount = aPoints.Length();
 | |
|   for (size_t i = 0; i < pointCount; ++i) {
 | |
|     size_t j = (i + 1) % pointCount;
 | |
| 
 | |
|     const Point4DTyped<Units>& a = aPoints[i];
 | |
|     const Point4DTyped<Units>& b = aPoints[j];
 | |
|     const float dotA = aDots[i];
 | |
|     const float dotB = aDots[j];
 | |
| 
 | |
|     // The point is in front of or on the plane.
 | |
|     if (dotA >= 0) {
 | |
|       aFrontPoints.AppendElement(a);
 | |
|     }
 | |
| 
 | |
|     // The point is behind or on the plane.
 | |
|     if (dotA <= 0) {
 | |
|       aBackPoints.AppendElement(a);
 | |
|     }
 | |
| 
 | |
|     // If the sign of the dot products changes between two consecutive
 | |
|     // vertices, then the plane intersects with the polygon edge.
 | |
|     // The case where the polygon edge is within the plane is handled above.
 | |
|     if (Sign(dotA) && Sign(dotB) && Sign(dotA) != Sign(dotB)) {
 | |
|       // Calculate the line segment and plane intersection point.
 | |
|       const Point4DTyped<Units> ab = b - a;
 | |
|       const float dotAB = ab.DotProduct(aNormal);
 | |
|       const float t = -dotA / dotAB;
 | |
|       const Point4DTyped<Units> p = a + (ab * t);
 | |
| 
 | |
|       // Add the intersection point to both polygons.
 | |
|       aBackPoints.AppendElement(p);
 | |
|       aFrontPoints.AppendElement(p);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * PolygonTyped stores the points of a convex planar polygon.
 | |
|  */
 | |
| template <class Units>
 | |
| class PolygonTyped {
 | |
|   typedef Point3DTyped<Units> Point3DType;
 | |
|   typedef Point4DTyped<Units> Point4DType;
 | |
| 
 | |
|  public:
 | |
|   PolygonTyped() = default;
 | |
| 
 | |
|   explicit PolygonTyped(const nsTArray<Point4DType>& aPoints,
 | |
|                         const Point4DType& aNormal = DefaultNormal())
 | |
|       : mNormal(aNormal), mPoints(aPoints) {}
 | |
| 
 | |
|   explicit PolygonTyped(nsTArray<Point4DType>&& aPoints,
 | |
|                         const Point4DType& aNormal = DefaultNormal())
 | |
|       : mNormal(aNormal), mPoints(std::move(aPoints)) {}
 | |
| 
 | |
|   explicit PolygonTyped(const std::initializer_list<Point4DType>& aPoints,
 | |
|                         const Point4DType& aNormal = DefaultNormal())
 | |
|       : mNormal(aNormal), mPoints(aPoints) {
 | |
| #ifdef DEBUG
 | |
|     EnsurePlanarPolygon();
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the smallest 2D rectangle that can fully cover the polygon.
 | |
|    */
 | |
|   RectTyped<Units> BoundingBox() const {
 | |
|     if (mPoints.IsEmpty()) {
 | |
|       return RectTyped<Units>();
 | |
|     }
 | |
| 
 | |
|     float minX, maxX, minY, maxY;
 | |
|     minX = maxX = mPoints[0].x;
 | |
|     minY = maxY = mPoints[0].y;
 | |
| 
 | |
|     for (const Point4DType& point : mPoints) {
 | |
|       minX = std::min(point.x, minX);
 | |
|       maxX = std::max(point.x, maxX);
 | |
| 
 | |
|       minY = std::min(point.y, minY);
 | |
|       maxY = std::max(point.y, maxY);
 | |
|     }
 | |
| 
 | |
|     return RectTyped<Units>(minX, minY, maxX - minX, maxY - minY);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Clips the polygon against the given 2D rectangle.
 | |
|    */
 | |
|   PolygonTyped<Units> ClipPolygon(const RectTyped<Units>& aRect) const {
 | |
|     if (aRect.IsEmpty()) {
 | |
|       return PolygonTyped<Units>();
 | |
|     }
 | |
| 
 | |
|     return ClipPolygon(FromRect(aRect));
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Clips this polygon against |aPolygon| in 2D and returns a new polygon.
 | |
|    */
 | |
|   PolygonTyped<Units> ClipPolygon(const PolygonTyped<Units>& aPolygon) const {
 | |
|     const nsTArray<Point4DType>& points = aPolygon.GetPoints();
 | |
| 
 | |
|     if (mPoints.IsEmpty() || points.IsEmpty()) {
 | |
|       return PolygonTyped<Units>();
 | |
|     }
 | |
| 
 | |
|     nsTArray<Point4DType> clippedPoints(mPoints.Clone());
 | |
| 
 | |
|     size_t pos, neg;
 | |
|     nsTArray<Point4DType> backPoints(4), frontPoints(4);
 | |
| 
 | |
|     // Iterate over all the edges of the clipping polygon |aPolygon| and clip
 | |
|     // this polygon against the edges.
 | |
|     const size_t pointCount = points.Length();
 | |
|     for (size_t i = 0; i < pointCount; ++i) {
 | |
|       const Point4DType p1 = points[(i + 1) % pointCount];
 | |
|       const Point4DType p2 = points[i];
 | |
| 
 | |
|       // Calculate the normal for the edge defined by |p1| and |p2|.
 | |
|       const Point4DType normal(p2.y - p1.y, p1.x - p2.x, 0.0f, 0.0f);
 | |
| 
 | |
|       // Calculate the distances between the points of the polygon and the
 | |
|       // plane defined by |aPolygon|.
 | |
|       const nsTArray<float> distances =
 | |
|           CalculatePointPlaneDistances(clippedPoints, normal, p1, pos, neg);
 | |
| 
 | |
|       backPoints.ClearAndRetainStorage();
 | |
|       frontPoints.ClearAndRetainStorage();
 | |
| 
 | |
|       // Clip the polygon points using the previously calculated distances.
 | |
|       ClipPointsWithPlane(clippedPoints, normal, distances, backPoints,
 | |
|                           frontPoints);
 | |
| 
 | |
|       // Only use the points behind the clipping plane.
 | |
|       clippedPoints = std::move(backPoints);
 | |
| 
 | |
|       if (clippedPoints.Length() < 3) {
 | |
|         // The clipping created a polygon with no area.
 | |
|         return PolygonTyped<Units>();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return PolygonTyped<Units>(std::move(clippedPoints), mNormal);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns a new polygon containing the bounds of the given 2D rectangle.
 | |
|    */
 | |
|   static PolygonTyped<Units> FromRect(const RectTyped<Units>& aRect) {
 | |
|     nsTArray<Point4DType> points{
 | |
|         Point4DType(aRect.X(), aRect.Y(), 0.0f, 1.0f),
 | |
|         Point4DType(aRect.X(), aRect.YMost(), 0.0f, 1.0f),
 | |
|         Point4DType(aRect.XMost(), aRect.YMost(), 0.0f, 1.0f),
 | |
|         Point4DType(aRect.XMost(), aRect.Y(), 0.0f, 1.0f)};
 | |
| 
 | |
|     return PolygonTyped<Units>(std::move(points));
 | |
|   }
 | |
| 
 | |
|   const Point4DType& GetNormal() const { return mNormal; }
 | |
| 
 | |
|   const nsTArray<Point4DType>& GetPoints() const { return mPoints; }
 | |
| 
 | |
|   bool IsEmpty() const {
 | |
|     // If the polygon has less than three points, it has no visible area.
 | |
|     return mPoints.Length() < 3;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns a list of triangles covering the polygon.
 | |
|    */
 | |
|   nsTArray<TriangleTyped<Units>> ToTriangles() const {
 | |
|     nsTArray<TriangleTyped<Units>> triangles;
 | |
| 
 | |
|     if (IsEmpty()) {
 | |
|       return triangles;
 | |
|     }
 | |
| 
 | |
|     // This fan triangulation method only works for convex polygons.
 | |
|     for (size_t i = 1; i < mPoints.Length() - 1; ++i) {
 | |
|       TriangleTyped<Units> triangle(Point(mPoints[0].x, mPoints[0].y),
 | |
|                                     Point(mPoints[i].x, mPoints[i].y),
 | |
|                                     Point(mPoints[i + 1].x, mPoints[i + 1].y));
 | |
|       triangles.AppendElement(std::move(triangle));
 | |
|     }
 | |
| 
 | |
|     return triangles;
 | |
|   }
 | |
| 
 | |
|   void TransformToLayerSpace(const Matrix4x4Typed<Units, Units>& aTransform) {
 | |
|     TransformPoints(aTransform, true);
 | |
|     mNormal = DefaultNormal();
 | |
|   }
 | |
| 
 | |
|   void TransformToScreenSpace(
 | |
|       const Matrix4x4Typed<Units, Units>& aTransform,
 | |
|       const Matrix4x4Typed<Units, Units>& aInverseTransform) {
 | |
|     TransformPoints(aTransform, false);
 | |
| 
 | |
|     // Perspective projection transformation might produce points with w <= 0,
 | |
|     // so we need to clip these points.
 | |
|     mPoints = ClipPointsAtInfinity(mPoints);
 | |
| 
 | |
|     // Normal vectors should be transformed using inverse transpose.
 | |
|     mNormal = aInverseTransform.TransposeTransform4D(mNormal);
 | |
|   }
 | |
| 
 | |
|   void TransformToScreenSpace(const Matrix4x4Typed<Units, Units>& aTransform) {
 | |
|     MOZ_ASSERT(!aTransform.IsSingular());
 | |
| 
 | |
|     TransformToScreenSpace(aTransform, aTransform.Inverse());
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   static Point4DType DefaultNormal() {
 | |
|     return Point4DType(0.0f, 0.0f, 1.0f, 0.0f);
 | |
|   }
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   void EnsurePlanarPolygon() const {
 | |
|     if (mPoints.Length() <= 3) {
 | |
|       // Polygons with three or less points are guaranteed to be planar.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // This normal calculation method works only for planar polygons.
 | |
|     // The resulting normal vector will point towards the viewer when the
 | |
|     // polygon has a counter-clockwise winding order from the perspective
 | |
|     // of the viewer.
 | |
|     Point3DType normal;
 | |
|     const Point3DType p0 = mPoints[0].As3DPoint();
 | |
| 
 | |
|     for (size_t i = 1; i < mPoints.Length() - 1; ++i) {
 | |
|       const Point3DType p1 = mPoints[i].As3DPoint();
 | |
|       const Point3DType p2 = mPoints[i + 1].As3DPoint();
 | |
| 
 | |
|       normal += (p1 - p0).CrossProduct(p2 - p0);
 | |
|     }
 | |
| 
 | |
|     // Ensure that at least one component is greater than zero.
 | |
|     // This avoids division by zero when normalizing the vector.
 | |
|     bool hasNonZeroComponent = std::abs(normal.x) > 0.0f ||
 | |
|                                std::abs(normal.y) > 0.0f ||
 | |
|                                std::abs(normal.z) > 0.0f;
 | |
| 
 | |
|     MOZ_ASSERT(hasNonZeroComponent);
 | |
| 
 | |
|     normal.Normalize();
 | |
| 
 | |
|     // Ensure that the polygon is planar.
 | |
|     // http://mathworld.wolfram.com/Point-PlaneDistance.html
 | |
|     const float epsilon = 0.01f;
 | |
|     for (const Point4DType& point : mPoints) {
 | |
|       const Point3DType p1 = point.As3DPoint();
 | |
|       const float d = normal.DotProduct(p1 - p0);
 | |
| 
 | |
|       MOZ_ASSERT(std::abs(d) < epsilon);
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   void TransformPoints(const Matrix4x4Typed<Units, Units>& aTransform,
 | |
|                        const bool aDivideByW) {
 | |
|     for (Point4DType& point : mPoints) {
 | |
|       point = aTransform.TransformPoint(point);
 | |
| 
 | |
|       if (aDivideByW && point.w > 0.0f) {
 | |
|         point = point / point.w;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Point4DType mNormal;
 | |
|   CopyableTArray<Point4DType> mPoints;
 | |
| };
 | |
| 
 | |
| typedef PolygonTyped<UnknownUnits> Polygon;
 | |
| 
 | |
| }  // namespace gfx
 | |
| }  // namespace mozilla
 | |
| 
 | |
| #endif /* MOZILLA_GFX_POLYGON_H */
 | 
