diff --git a/gfx/2d/Polygon.h b/gfx/2d/Polygon.h index 14bf1c5fa692..5920e97ea280 100644 --- a/gfx/2d/Polygon.h +++ b/gfx/2d/Polygon.h @@ -27,11 +27,15 @@ CalculateEdgeIntersect(const Point4DTyped& aFirst, return aFirst + (aSecond - aFirst) * t; } +/** + * Clips the polygon defined by |aPoints| so that there are no points with + * w <= 0. + */ template nsTArray> -ClipHomogeneous(const nsTArray>& aPoints) +ClipPointsAtInfinity(const nsTArray>& aPoints) { - nsTArray> outPoints; + nsTArray> outPoints(aPoints.Length()); const size_t pointCount = aPoints.Length(); for (size_t i = 0; i < pointCount; ++i) { @@ -56,16 +60,90 @@ ClipHomogeneous(const nsTArray>& aPoints) } template -nsTArray> -ToPoints4D(const nsTArray>& aPoints) +nsTArray +CalculatePointPlaneDistances(const nsTArray>& aPoints, + const Point4DTyped& aPlaneNormal, + const Point4DTyped& aPlanePoint, + size_t& aPos, size_t& aNeg) { - nsTArray> points; + // Point classification might produce incorrect results due to numerical + // inaccuracies. Using an epsilon value makes the splitting plane "thicker". + const float epsilon = 0.05f; - for (const Point3DTyped& point : aPoints) { - points.AppendElement(Point4DTyped(point)); + aPos = aNeg = 0; + nsTArray distances(aPoints.Length()); + + for (const Point4DTyped& 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 points; + 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 +void +ClipPointsWithPlane(const nsTArray>& aPoints, + const Point4DTyped& aNormal, + const nsTArray& aDots, + nsTArray>& aBackPoints, + nsTArray>& 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& a = aPoints[i]; + const Point4DTyped& 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 ab = b - a; + const float dotAB = ab.DotProduct(aNormal); + const float t = -dotA / dotAB; + const Point4DTyped 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. @@ -89,9 +167,9 @@ public: const Point4DType& aNormal = DefaultNormal()) : mNormal(aNormal), mPoints(aPoints) { - #ifdef DEBUG +#ifdef DEBUG EnsurePlanarPolygon(); - #endif +#endif } RectTyped BoundingBox() const @@ -115,38 +193,6 @@ public: return RectTyped(minX, minY, maxX - minX, maxY - minY); } - nsTArray CalculateDotProducts(const PolygonTyped& aPlane, - size_t& aPos, size_t& aNeg) const - { - // Point classification might produce incorrect results due to numerical - // inaccuracies. Using an epsilon value makes the splitting plane "thicker". - const float epsilon = 0.05f; - - MOZ_ASSERT(!aPlane.GetPoints().IsEmpty()); - const Point4DType& planeNormal = aPlane.GetNormal(); - const Point4DType& planePoint = aPlane[0]; - - aPos = aNeg = 0; - nsTArray dotProducts; - - for (const Point4DType& point : mPoints) { - float dot = (point - planePoint).DotProduct(planeNormal); - - if (dot > epsilon) { - aPos++; - } else if (dot < -epsilon) { - aNeg++; - } else { - // The point is within the thick plane. - dot = 0.0f; - } - - dotProducts.AppendElement(dot); - } - - return dotProducts; - } - // Clips the polygon against the given 2D rectangle. PolygonTyped ClipPolygon(const RectTyped& aRect) const { @@ -157,7 +203,7 @@ public: return ClipPolygon(FromRect(aRect)); } - // Clips the polygon against the given polygon in 2D. + // Clips this polygon against the given polygon in 2D. PolygonTyped ClipPolygon(const PolygonTyped& aPolygon) const { const nsTArray& points = aPolygon.GetPoints(); @@ -166,25 +212,43 @@ public: return PolygonTyped(); } - PolygonTyped polygon(mPoints, mNormal); + nsTArray clippedPoints(mPoints); + size_t pos, neg; + nsTArray 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); - const PolygonTyped plane({p1, p2}, normal); - ClipPolygonWithPlane(polygon, plane); + // Calculate the distances between the points of the polygon and the + // plane defined by |aPolygon|. + const nsTArray distances = + CalculatePointPlaneDistances(clippedPoints, normal, p1, pos, neg); - if (polygon.IsEmpty()) { + 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 = Move(backPoints); + + if (clippedPoints.Length() < 3) { // The clipping created a polygon with no area. return PolygonTyped(); } } - return polygon; + return PolygonTyped(Move(clippedPoints), mNormal); } static PolygonTyped FromRect(const RectTyped& aRect) @@ -209,65 +273,12 @@ public: return mPoints; } - const Point4DType& operator[](size_t aIndex) const - { - MOZ_ASSERT(mPoints.Length() > aIndex); - return mPoints[aIndex]; - } - bool IsEmpty() const { // If the polygon has less than three points, it has no visible area. return mPoints.Length() < 3; } - void SplitPolygon(const Point4DType& aNormal, - const nsTArray& aDots, - nsTArray& aBackPoints, - nsTArray& aFrontPoints) const - { - 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 = mPoints.Length(); - for (size_t i = 0; i < pointCount; ++i) { - size_t j = (i + 1) % pointCount; - - const Point4DType& a = mPoints[i]; - const Point4DType& b = mPoints[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 Point4DType ab = b - a; - const float dotAB = ab.DotProduct(aNormal); - const float t = -dotA / dotAB; - const Point4DType p = a + (ab * t); - - // Add the intersection point to both polygons. - aBackPoints.AppendElement(p); - aFrontPoints.AppendElement(p); - } - } - } - nsTArray> ToTriangles() const { nsTArray> triangles; @@ -297,27 +308,16 @@ public: MOZ_ASSERT(!aTransform.IsSingular()); TransformPoints(aTransform, false); - mPoints = ClipHomogeneous(mPoints); + + // 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 = aTransform.Inverse().Transpose().TransformPoint(mNormal); } private: - void ClipPolygonWithPlane(PolygonTyped& aPolygon, - const PolygonTyped& aPlane) const - { - size_t pos, neg; - const nsTArray dots = - aPolygon.CalculateDotProducts(aPlane, pos, neg); - - nsTArray backPoints, frontPoints; - aPolygon.SplitPolygon(aPlane.GetNormal(), dots, backPoints, frontPoints); - - // Only use the points that are behind the clipping plane. - aPolygon = PolygonTyped(Move(backPoints), aPolygon.GetNormal()); - } - static Point4DType DefaultNormal() { return Point4DType(0.0f, 0.0f, 1.0f, 0.0f); diff --git a/gfx/layers/BSPTree.cpp b/gfx/layers/BSPTree.cpp index a7d68eb96554..bfca2ac2405d 100644 --- a/gfx/layers/BSPTree.cpp +++ b/gfx/layers/BSPTree.cpp @@ -57,13 +57,19 @@ BSPTree::BuildTree(BSPTreeNode* aRoot, } const gfx::Polygon& plane = aRoot->First(); + MOZ_ASSERT(!plane.IsEmpty()); + + const gfx::Point4D& planeNormal = plane.GetNormal(); + const gfx::Point4D& planePoint = plane.GetPoints()[0]; std::list backLayers, frontLayers; for (LayerPolygon& layerPolygon : aLayers) { - const Maybe& geometry = layerPolygon.geometry; + const nsTArray& geometry = layerPolygon.geometry->GetPoints(); + // Calculate the plane-point distances for the polygon classification. size_t pos = 0, neg = 0; - nsTArray dots = geometry->CalculateDotProducts(plane, pos, neg); + nsTArray distances = + CalculatePointPlaneDistances(geometry, planeNormal, planePoint, pos, neg); // Back polygon if (pos == 0 && neg > 0) { @@ -80,10 +86,13 @@ BSPTree::BuildTree(BSPTreeNode* aRoot, // Polygon intersects with the splitting plane. else if (pos > 0 && neg > 0) { nsTArray backPoints, frontPoints; - geometry->SplitPolygon(plane.GetNormal(), dots, backPoints, frontPoints); + // Clip the polygon against the plane. We reuse the previously calculated + // distances to find the plane-edge intersections. + ClipPointsWithPlane(geometry, planeNormal, distances, + backPoints, frontPoints); - const gfx::Point4D& normal = geometry->GetNormal(); - Layer *layer = layerPolygon.layer; + const gfx::Point4D& normal = layerPolygon.geometry->GetNormal(); + Layer* layer = layerPolygon.layer; if (backPoints.Length() >= 3) { backLayers.emplace_back(layer, Move(backPoints), normal);