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 ¢er, 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 ¢er, 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 ¢er, int width, int height, double rotationDegrees) 0088 { 0089 computeVertices(m_Vertices, center, width, height, rotationDegrees); 0090 } 0091 0092 bool RectangleOverlap::intersects(const QPointF ¢er, 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 }