File indexing completed on 2024-04-28 03:42:46

0001 /*
0002     SPDX-FileCopyrightText: 2023 Hy Murveit <hy@murveit.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006     Based on https://github.com/lennig/rectangleOverlap
0007     with permission from Matt Lennig
0008 */
0009 
0010 #include "rectangleoverlap.h"
0011 #include "math.h"
0012 
0013 namespace
0014 {
0015 
0016 // Rotates in place the points in vertices by rotationDegrees around the center point.
0017 void rotate(QVector<QPointF> &vertices, const QPointF &center, double rotationDegrees)
0018 {
0019     constexpr double convertDegreesToRadians =  M_PI / 180.0;
0020     const double cosAngle = cos(rotationDegrees * convertDegreesToRadians);
0021     const double sinAngle = sin(rotationDegrees * convertDegreesToRadians);
0022 
0023     for (int i = 0; i < vertices.size(); i++)
0024     {
0025         // Translate the vertices so that center is at 0,0.
0026         const double x = vertices[i].x() - center.x();
0027         const double y = vertices[i].y() - center.y();
0028         // Rotate the points around the origin, then translate them back.
0029         vertices[i] = QPointF(center.x() + x * cosAngle - y * sinAngle,
0030                               center.y() + x * sinAngle + y * cosAngle);
0031     }
0032 }
0033 
0034 // Compute the vertices (corner points) of a possibly rotated rectangle
0035 // with the given center point, width and height.
0036 // Points are returned in the input QVector in counter-clockwise order.
0037 void computeVertices(
0038     QVector<QPointF> &vertices, const QPointF &center, int width,
0039     int height, double rotationDegrees)
0040 {
0041     // Initialize the rectangle unrotated, then rotate if necessary.
0042     const double dw = width / 2.0;
0043     const double dh = height / 2.0;
0044     vertices.push_back(QPoint(center.x() - dw, center.y() - dh));
0045     vertices.push_back(QPoint(center.x() + dw, center.y() - dh));
0046     vertices.push_back(QPoint(center.x() + dw, center.y() + dh));
0047     vertices.push_back(QPoint(center.x() - dw, center.y() + dh));
0048     if (rotationDegrees != 0.0)
0049         rotate(vertices, center, rotationDegrees);
0050 }
0051 
0052 // Returns the slope of the line between the two points
0053 // Slope returned is guaranteed to be finite--it is 0 for horizontal
0054 // or vertical lines, which works for this code.
0055 double finiteSlope(const QPointF &p1, const QPointF &p2)
0056 {
0057     const double dx = p2.x() - p1.x();
0058     if (dx == 0) return 0.0;
0059     return (p2.y() - p1.y()) / dx;
0060 }
0061 
0062 // Returns the axes of the rectangle.
0063 // Axes are lines coming from the origin that are parallel to the sides of the rectangle.
0064 // As this is a rectangle, getting the slope between its first two points
0065 // assuming they are given clockwise or counter-clockwise, will tell us
0066 // all we need to know about the axes.  That is, the rest of the axes are either
0067 // parallel or perpendicular. Similarly, as finiteSlope() returns 0 for horizontal
0068 // or vertical lines, in either case it will give us the axes of the rectangle.
0069 void addAxes(QVector<QPointF> &axes, const QVector<QPointF> &vertices)
0070 {
0071     const double s = finiteSlope(vertices[0], vertices[1]);
0072     if (s != 0)
0073     {
0074         // Projection axes are not parallel to main axes
0075         axes.push_back(QPointF(1, s));
0076         axes.push_back(QPointF(1, -1 / s));
0077     }
0078     else
0079     {
0080         // Projection axes are parallel to main axes
0081         axes.push_back(QPointF(1, 0));
0082         axes.push_back(QPointF(0, 1));
0083     }
0084 }
0085 }  // namespace
0086 
0087 RectangleOverlap::RectangleOverlap(const QPointF &center, int width, int height, double rotationDegrees)
0088 {
0089     computeVertices(m_Vertices, center, width, height, rotationDegrees);
0090 }
0091 
0092 bool RectangleOverlap::intersects(const QPointF &center, int width, int height, double rotationDegrees) const
0093 {
0094     // Compute the vertices of the test rectangle
0095     QVector<QPointF> testVertices;
0096     computeVertices(testVertices, center, width, height, rotationDegrees);
0097 
0098     QVector<QPointF> axes;
0099     addAxes(axes, m_Vertices);
0100     addAxes(axes, testVertices);
0101 
0102     // According to the Separating Axis Theorum, two rectangles do not overlap if none of their axes
0103     // separates the projections of the points from the two rectangles. To phrase that differently,
0104     // if the projections of one rectangle's vertices onto an axis overlaps with the projections
0105     // of the other rectangle's vertices onto that axis, then that axis does not rule out an overlap.
0106     // If none of the axes rule out an overlap, then the rectangles overlap.
0107 
0108     for (const auto &axis : axes)
0109     {
0110         // Compute min and max projections of the reference rectangle's vertices of onto an axis.
0111         double minRefProjection = std::numeric_limits<double>::max();
0112         double maxRefProjection = std::numeric_limits<double>::lowest();
0113         for (const auto &vertex : m_Vertices)
0114         {
0115             // Compute the dot-product projection.
0116             const double projection = vertex.x() * axis.x() + vertex.y() * axis.y();
0117             if (projection > maxRefProjection) maxRefProjection = projection;
0118             if (projection < minRefProjection) minRefProjection = projection;
0119         };
0120 
0121         // Compute min and max projections of the test rectangle's vertices of onto an axis.
0122         double minTestProjection =  std::numeric_limits<double>::max();
0123         double maxTextProjection = std::numeric_limits<double>::lowest();
0124         for (const auto &vertex : testVertices)
0125         {
0126             const double projection = vertex.x() * axis.x() + vertex.y() * axis.y();
0127             if (projection > maxTextProjection) maxTextProjection = projection;
0128             if (projection < minTestProjection) minTestProjection = projection;
0129         }
0130 
0131         bool separated = minRefProjection > maxTextProjection || minTestProjection > maxRefProjection;
0132         if (separated)
0133             return false;
0134     }
0135     // None of the axes separate the rectangles' vertices, so they are overlapped.
0136     return true;
0137 }