File indexing completed on 2024-05-19 05:32:19

0001 /*
0002     SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
0003     SPDX-FileCopyrightText: 2022 Arjen Hiemstra <ahiemstra@heimr.nl>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "scene/itemgeometry.h"
0009 #include "effect/globals.h"
0010 
0011 #include <QMatrix4x4>
0012 
0013 namespace KWin
0014 {
0015 
0016 WindowQuad WindowQuad::makeSubQuad(double x1, double y1, double x2, double y2) const
0017 {
0018     Q_ASSERT(x1 < x2 && y1 < y2 && x1 >= left() && x2 <= right() && y1 >= top() && y2 <= bottom());
0019     WindowQuad ret(*this);
0020     // vertices are clockwise starting from topleft
0021     ret.verts[0].px = x1;
0022     ret.verts[3].px = x1;
0023     ret.verts[1].px = x2;
0024     ret.verts[2].px = x2;
0025     ret.verts[0].py = y1;
0026     ret.verts[1].py = y1;
0027     ret.verts[2].py = y2;
0028     ret.verts[3].py = y2;
0029 
0030     const double xOrigin = left();
0031     const double yOrigin = top();
0032 
0033     const double widthReciprocal = 1 / (right() - xOrigin);
0034     const double heightReciprocal = 1 / (bottom() - yOrigin);
0035 
0036     for (int i = 0; i < 4; ++i) {
0037         const double w1 = (ret.verts[i].px - xOrigin) * widthReciprocal;
0038         const double w2 = (ret.verts[i].py - yOrigin) * heightReciprocal;
0039 
0040         // Use bilinear interpolation to compute the texture coords.
0041         ret.verts[i].tx = (1 - w1) * (1 - w2) * verts[0].tx + w1 * (1 - w2) * verts[1].tx + w1 * w2 * verts[2].tx + (1 - w1) * w2 * verts[3].tx;
0042         ret.verts[i].ty = (1 - w1) * (1 - w2) * verts[0].ty + w1 * (1 - w2) * verts[1].ty + w1 * w2 * verts[2].ty + (1 - w1) * w2 * verts[3].ty;
0043     }
0044 
0045     return ret;
0046 }
0047 
0048 WindowQuadList WindowQuadList::splitAtX(double x) const
0049 {
0050     WindowQuadList ret;
0051     ret.reserve(count());
0052     for (const WindowQuad &quad : *this) {
0053         bool wholeleft = true;
0054         bool wholeright = true;
0055         for (int i = 0; i < 4; ++i) {
0056             if (quad[i].x() < x) {
0057                 wholeright = false;
0058             }
0059             if (quad[i].x() > x) {
0060                 wholeleft = false;
0061             }
0062         }
0063         if (wholeleft || wholeright) { // is whole in one split part
0064             ret.append(quad);
0065             continue;
0066         }
0067         if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size
0068             ret.append(quad);
0069             continue;
0070         }
0071         ret.append(quad.makeSubQuad(quad.left(), quad.top(), x, quad.bottom()));
0072         ret.append(quad.makeSubQuad(x, quad.top(), quad.right(), quad.bottom()));
0073     }
0074     return ret;
0075 }
0076 
0077 WindowQuadList WindowQuadList::splitAtY(double y) const
0078 {
0079     WindowQuadList ret;
0080     ret.reserve(count());
0081     for (const WindowQuad &quad : *this) {
0082         bool wholetop = true;
0083         bool wholebottom = true;
0084         for (int i = 0; i < 4; ++i) {
0085             if (quad[i].y() < y) {
0086                 wholebottom = false;
0087             }
0088             if (quad[i].y() > y) {
0089                 wholetop = false;
0090             }
0091         }
0092         if (wholetop || wholebottom) { // is whole in one split part
0093             ret.append(quad);
0094             continue;
0095         }
0096         if (quad.top() == quad.bottom() || quad.left() == quad.right()) { // quad has no size
0097             ret.append(quad);
0098             continue;
0099         }
0100         ret.append(quad.makeSubQuad(quad.left(), quad.top(), quad.right(), y));
0101         ret.append(quad.makeSubQuad(quad.left(), y, quad.right(), quad.bottom()));
0102     }
0103     return ret;
0104 }
0105 
0106 WindowQuadList WindowQuadList::makeGrid(int maxQuadSize) const
0107 {
0108     if (empty()) {
0109         return *this;
0110     }
0111 
0112     // Find the bounding rectangle
0113     double left = first().left();
0114     double right = first().right();
0115     double top = first().top();
0116     double bottom = first().bottom();
0117 
0118     for (const WindowQuad &quad : std::as_const(*this)) {
0119         left = std::min(left, quad.left());
0120         right = std::max(right, quad.right());
0121         top = std::min(top, quad.top());
0122         bottom = std::max(bottom, quad.bottom());
0123     }
0124 
0125     WindowQuadList ret;
0126 
0127     for (const WindowQuad &quad : std::as_const(*this)) {
0128         const double quadLeft = quad.left();
0129         const double quadRight = quad.right();
0130         const double quadTop = quad.top();
0131         const double quadBottom = quad.bottom();
0132 
0133         // sanity check, see BUG 390953
0134         if (quadLeft == quadRight || quadTop == quadBottom) {
0135             ret.append(quad);
0136             continue;
0137         }
0138 
0139         // Compute the top-left corner of the first intersecting grid cell
0140         const double xBegin = left + qFloor((quadLeft - left) / maxQuadSize) * maxQuadSize;
0141         const double yBegin = top + qFloor((quadTop - top) / maxQuadSize) * maxQuadSize;
0142 
0143         // Loop over all intersecting cells and add sub-quads
0144         for (double y = yBegin; y < quadBottom; y += maxQuadSize) {
0145             const double y0 = std::max(y, quadTop);
0146             const double y1 = std::min(quadBottom, y + maxQuadSize);
0147 
0148             for (double x = xBegin; x < quadRight; x += maxQuadSize) {
0149                 const double x0 = std::max(x, quadLeft);
0150                 const double x1 = std::min(quadRight, x + maxQuadSize);
0151 
0152                 ret.append(quad.makeSubQuad(x0, y0, x1, y1));
0153             }
0154         }
0155     }
0156 
0157     return ret;
0158 }
0159 
0160 WindowQuadList WindowQuadList::makeRegularGrid(int xSubdivisions, int ySubdivisions) const
0161 {
0162     if (empty()) {
0163         return *this;
0164     }
0165 
0166     // Find the bounding rectangle
0167     double left = first().left();
0168     double right = first().right();
0169     double top = first().top();
0170     double bottom = first().bottom();
0171 
0172     for (const WindowQuad &quad : *this) {
0173         left = std::min(left, quad.left());
0174         right = std::max(right, quad.right());
0175         top = std::min(top, quad.top());
0176         bottom = std::max(bottom, quad.bottom());
0177     }
0178 
0179     double xIncrement = (right - left) / xSubdivisions;
0180     double yIncrement = (bottom - top) / ySubdivisions;
0181 
0182     WindowQuadList ret;
0183 
0184     for (const WindowQuad &quad : *this) {
0185         const double quadLeft = quad.left();
0186         const double quadRight = quad.right();
0187         const double quadTop = quad.top();
0188         const double quadBottom = quad.bottom();
0189 
0190         // sanity check, see BUG 390953
0191         if (quadLeft == quadRight || quadTop == quadBottom) {
0192             ret.append(quad);
0193             continue;
0194         }
0195 
0196         // Compute the top-left corner of the first intersecting grid cell
0197         const double xBegin = left + qFloor((quadLeft - left) / xIncrement) * xIncrement;
0198         const double yBegin = top + qFloor((quadTop - top) / yIncrement) * yIncrement;
0199 
0200         // Loop over all intersecting cells and add sub-quads
0201         for (double y = yBegin; y < quadBottom; y += yIncrement) {
0202             const double y0 = std::max(y, quadTop);
0203             const double y1 = std::min(quadBottom, y + yIncrement);
0204 
0205             for (double x = xBegin; x < quadRight; x += xIncrement) {
0206                 const double x0 = std::max(x, quadLeft);
0207                 const double x1 = std::min(quadRight, x + xIncrement);
0208 
0209                 ret.append(quad.makeSubQuad(x0, y0, x1, y1));
0210             }
0211         }
0212     }
0213 
0214     return ret;
0215 }
0216 
0217 void RenderGeometry::copy(std::span<GLVertex2D> destination)
0218 {
0219     Q_ASSERT(int(destination.size()) >= size());
0220     std::copy(cbegin(), cend(), destination.begin());
0221 }
0222 
0223 void RenderGeometry::appendWindowVertex(const WindowVertex &windowVertex, qreal deviceScale)
0224 {
0225     GLVertex2D glVertex;
0226     switch (m_vertexSnappingMode) {
0227     case VertexSnappingMode::None:
0228         glVertex.position = QVector2D(windowVertex.x(), windowVertex.y()) * deviceScale;
0229         break;
0230     case VertexSnappingMode::Round:
0231         glVertex.position = roundVector(QVector2D(windowVertex.x(), windowVertex.y()) * deviceScale);
0232         break;
0233     }
0234     glVertex.texcoord = QVector2D(windowVertex.u(), windowVertex.v());
0235     append(glVertex);
0236 }
0237 
0238 void RenderGeometry::appendWindowQuad(const WindowQuad &quad, qreal deviceScale)
0239 {
0240     // Geometry assumes we're rendering triangles, so add the quad's
0241     // vertices as two triangles. Vertex order is top-left, bottom-left,
0242     // top-right followed by top-right, bottom-left, bottom-right.
0243     appendWindowVertex(quad[0], deviceScale);
0244     appendWindowVertex(quad[3], deviceScale);
0245     appendWindowVertex(quad[1], deviceScale);
0246 
0247     appendWindowVertex(quad[1], deviceScale);
0248     appendWindowVertex(quad[3], deviceScale);
0249     appendWindowVertex(quad[2], deviceScale);
0250 }
0251 
0252 void RenderGeometry::appendSubQuad(const WindowQuad &quad, const QRectF &subquad, qreal deviceScale)
0253 {
0254     std::array<GLVertex2D, 4> vertices;
0255     vertices[0].position = QVector2D(subquad.topLeft());
0256     vertices[1].position = QVector2D(subquad.topRight());
0257     vertices[2].position = QVector2D(subquad.bottomRight());
0258     vertices[3].position = QVector2D(subquad.bottomLeft());
0259 
0260     const auto deviceQuad = QRectF{QPointF(std::round(quad.left() * deviceScale), std::round(quad.top() * deviceScale)),
0261                                    QPointF(std::round(quad.right() * deviceScale), std::round(quad.bottom() * deviceScale))};
0262 
0263     const QPointF origin = deviceQuad.topLeft();
0264     const QSizeF size = deviceQuad.size();
0265 
0266 #pragma GCC unroll 4
0267     for (int i = 0; i < 4; ++i) {
0268         const double weight1 = (vertices[i].position.x() - origin.x()) / size.width();
0269         const double weight2 = (vertices[i].position.y() - origin.y()) / size.height();
0270         const double oneMinW1 = 1.0 - weight1;
0271         const double oneMinW2 = 1.0 - weight2;
0272 
0273         const float u = oneMinW1 * oneMinW2 * quad[0].u() + weight1 * oneMinW2 * quad[1].u()
0274             + weight1 * weight2 * quad[2].u() + oneMinW1 * weight2 * quad[3].u();
0275         const float v = oneMinW1 * oneMinW2 * quad[0].v() + weight1 * oneMinW2 * quad[1].v()
0276             + weight1 * weight2 * quad[2].v() + oneMinW1 * weight2 * quad[3].v();
0277         vertices[i].texcoord = QVector2D(u, v);
0278     }
0279 
0280     append(vertices[0]);
0281     append(vertices[3]);
0282     append(vertices[1]);
0283 
0284     append(vertices[1]);
0285     append(vertices[3]);
0286     append(vertices[2]);
0287 }
0288 
0289 void RenderGeometry::postProcessTextureCoordinates(const QMatrix4x4 &textureMatrix)
0290 {
0291     if (!textureMatrix.isIdentity()) {
0292         const QVector2D coeff(textureMatrix(0, 0), textureMatrix(1, 1));
0293         const QVector2D offset(textureMatrix(0, 3), textureMatrix(1, 3));
0294 
0295         for (auto &vertex : (*this)) {
0296             vertex.texcoord = vertex.texcoord * coeff + offset;
0297         }
0298     }
0299 }
0300 
0301 } // namespace KWin