зеркало из https://github.com/mozilla/gecko-dev.git
296 строки
8.7 KiB
C++
296 строки
8.7 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* 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 "Matrix.h"
|
|
#include "mozilla/Move.h"
|
|
#include "nsTArray.h"
|
|
#include "Point.h"
|
|
#include "Triangle.h"
|
|
|
|
#include <initializer_list>
|
|
|
|
namespace mozilla {
|
|
namespace gfx {
|
|
|
|
// Polygon3DTyped stores the points of a convex planar polygon.
|
|
template<class Units>
|
|
class Polygon3DTyped {
|
|
public:
|
|
Polygon3DTyped() {}
|
|
|
|
explicit Polygon3DTyped(const std::initializer_list<Point3DTyped<Units>>& aPoints,
|
|
Point3DTyped<Units> aNormal =
|
|
Point3DTyped<Units>(0.0f, 0.0f, 1.0f))
|
|
: mNormal(aNormal), mPoints(aPoints)
|
|
{
|
|
#ifdef DEBUG
|
|
EnsurePlanarPolygon();
|
|
#endif
|
|
}
|
|
|
|
explicit Polygon3DTyped(nsTArray<Point3DTyped<Units>>&& aPoints,
|
|
Point3DTyped<Units> aNormal =
|
|
Point3DTyped<Units>(0.0f, 0.0f, 1.0f))
|
|
: mNormal(aNormal), mPoints(Move(aPoints))
|
|
{
|
|
#ifdef DEBUG
|
|
EnsurePlanarPolygon();
|
|
#endif
|
|
}
|
|
|
|
explicit Polygon3DTyped(const nsTArray<Point3DTyped<Units>>& aPoints,
|
|
Point3DTyped<Units> aNormal =
|
|
Point3DTyped<Units>(0.0f, 0.0f, 1.0f))
|
|
: mNormal(aNormal), mPoints(aPoints)
|
|
{
|
|
#ifdef DEBUG
|
|
EnsurePlanarPolygon();
|
|
#endif
|
|
}
|
|
|
|
RectTyped<Units> BoundingBox() const
|
|
{
|
|
float minX, maxX, minY, maxY;
|
|
minX = maxX = mPoints[0].x;
|
|
minY = maxY = mPoints[0].y;
|
|
|
|
for (const Point3DTyped<Units>& 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);
|
|
}
|
|
|
|
nsTArray<float>
|
|
CalculateDotProducts(const Polygon3DTyped<Units>& 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 Point3DTyped<Units>& planeNormal = aPlane.GetNormal();
|
|
const Point3DTyped<Units>& planePoint = aPlane[0];
|
|
|
|
aPos = aNeg = 0;
|
|
nsTArray<float> dotProducts;
|
|
for (const Point3DTyped<Units>& 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.
|
|
Polygon3DTyped<Units> ClipPolygon(const RectTyped<Units>& aRect) const
|
|
{
|
|
Polygon3DTyped<Units> polygon(mPoints, mNormal);
|
|
|
|
// Left edge
|
|
ClipPolygonWithEdge(polygon, aRect.BottomLeft(), aRect.TopLeft());
|
|
|
|
// Bottom edge
|
|
ClipPolygonWithEdge(polygon, aRect.BottomRight(), aRect.BottomLeft());
|
|
|
|
// Right edge
|
|
ClipPolygonWithEdge(polygon, aRect.TopRight(), aRect.BottomRight());
|
|
|
|
// Top edge
|
|
ClipPolygonWithEdge(polygon, aRect.TopLeft(), aRect.TopRight());
|
|
|
|
return polygon;
|
|
}
|
|
|
|
const Point3DTyped<Units>& GetNormal() const
|
|
{
|
|
return mNormal;
|
|
}
|
|
|
|
const nsTArray<Point3DTyped<Units>>& GetPoints() const
|
|
{
|
|
return mPoints;
|
|
}
|
|
|
|
const Point3DTyped<Units>& operator[](size_t aIndex) const
|
|
{
|
|
MOZ_ASSERT(mPoints.Length() > aIndex);
|
|
return mPoints[aIndex];
|
|
}
|
|
|
|
void SplitPolygon(const Polygon3DTyped<Units>& aSplittingPlane,
|
|
const nsTArray<float>& aDots,
|
|
nsTArray<Point3DTyped<Units>>& aBackPoints,
|
|
nsTArray<Point3DTyped<Units>>& aFrontPoints) const
|
|
{
|
|
static const auto Sign = [](const float& f) {
|
|
if (f > 0.0f) return 1;
|
|
if (f < 0.0f) return -1;
|
|
return 0;
|
|
};
|
|
|
|
const Point3DTyped<Units>& normal = aSplittingPlane.GetNormal();
|
|
const size_t pointCount = mPoints.Length();
|
|
|
|
for (size_t i = 0; i < pointCount; ++i) {
|
|
size_t j = (i + 1) % pointCount;
|
|
|
|
const Point3DTyped<Units>& a = mPoints[i];
|
|
const Point3DTyped<Units>& 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 Point3DTyped<Units> ab = b - a;
|
|
const float dotAB = ab.DotProduct(normal);
|
|
const float t = -dotA / dotAB;
|
|
const Point3DTyped<Units> p = a + (ab * t);
|
|
|
|
// Add the intersection point to both polygons.
|
|
aBackPoints.AppendElement(p);
|
|
aFrontPoints.AppendElement(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
nsTArray<TriangleTyped<Units>> ToTriangles() const
|
|
{
|
|
nsTArray<TriangleTyped<Units>> triangles;
|
|
|
|
if (mPoints.Length() < 3) {
|
|
return triangles;
|
|
}
|
|
|
|
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(Move(triangle));
|
|
}
|
|
|
|
return triangles;
|
|
}
|
|
|
|
void TransformToLayerSpace(const Matrix4x4Typed<Units, Units>& aTransform)
|
|
{
|
|
TransformPoints(aTransform);
|
|
mNormal = Point3DTyped<Units>(0.0f, 0.0f, 1.0f);
|
|
}
|
|
|
|
void TransformToScreenSpace(const Matrix4x4Typed<Units, Units>& aTransform)
|
|
{
|
|
TransformPoints(aTransform);
|
|
|
|
// Normal vectors should be transformed using inverse transpose.
|
|
mNormal = aTransform.Inverse().Transpose().TransformPoint(mNormal);
|
|
}
|
|
|
|
private:
|
|
void ClipPolygonWithEdge(Polygon3DTyped<Units>& aPolygon,
|
|
const PointTyped<Units>& aFirst,
|
|
const PointTyped<Units>& aSecond) const
|
|
{
|
|
const Point3DTyped<Units> a(aFirst.x, aFirst.y, 0.0f);
|
|
const Point3DTyped<Units> b(aSecond.x, aSecond.y, 0.0f);
|
|
const Point3DTyped<Units> normal(b.y - a.y, a.x - b.x, 0.0f);
|
|
Polygon3DTyped<Units> plane({a, b}, normal);
|
|
|
|
size_t pos, neg;
|
|
nsTArray<float> dots = aPolygon.CalculateDotProducts(plane, pos, neg);
|
|
|
|
nsTArray<Point3DTyped<Units>> backPoints, frontPoints;
|
|
aPolygon.SplitPolygon(plane, dots, backPoints, frontPoints);
|
|
|
|
// Only use the points that are behind the clipping plane.
|
|
aPolygon = Polygon3DTyped<Units>(Move(backPoints), aPolygon.GetNormal());
|
|
}
|
|
|
|
#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.
|
|
Point3DTyped<Units> normal;
|
|
|
|
for (size_t i = 1; i < mPoints.Length() - 1; ++i) {
|
|
normal +=
|
|
(mPoints[i] - mPoints[0]).CrossProduct(mPoints[i + 1] - mPoints[0]);
|
|
}
|
|
|
|
// 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 Point3DTyped<Units>& point : mPoints) {
|
|
float d = normal.DotProduct(point - mPoints[0]);
|
|
MOZ_ASSERT(std::abs(d) < epsilon);
|
|
}
|
|
}
|
|
#endif
|
|
void TransformPoints(const Matrix4x4Typed<Units, Units>& aTransform)
|
|
{
|
|
for (Point3DTyped<Units>& point : mPoints) {
|
|
point = aTransform.TransformPoint(point);
|
|
}
|
|
}
|
|
|
|
Point3DTyped<Units> mNormal;
|
|
nsTArray<Point3DTyped<Units>> mPoints;
|
|
};
|
|
|
|
typedef Polygon3DTyped<UnknownUnits> Polygon3D;
|
|
|
|
} // namespace gfx
|
|
} // namespace mozilla
|
|
|
|
#endif /* MOZILLA_GFX_POLYGON_H */
|