Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgshadowmaphelpers.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
10
11#include <QVector3D>
12#include <QMatrix4x4>
13#include <QVector4D>
14#include <cmath>
15
16QT_BEGIN_NAMESPACE
17
18static QMatrix4x4 buildOrthonormalBasis(const QVector3D &normal)
19{
20 // Assume normal is already normalized
21 const QVector3D forward = normal;
22
23 // Pick the axis least aligned with forward to use as a reference
24 QVector3D ref;
25 float absX = std::fabs(forward.x());
26 float absY = std::fabs(forward.y());
27 float absZ = std::fabs(forward.z());
28
29 if (absX <= absY && absX <= absZ)
30 ref = QVector3D(1, 0, 0);
31 else if (absY <= absZ)
32 ref = QVector3D(0, 1, 0);
33 else
34 ref = QVector3D(0, 0, 1);
35
36 const QVector3D right = QVector3D::crossProduct(ref, forward).normalized();
37 const QVector3D up = QVector3D::crossProduct(forward, right);
38
39 QMatrix4x4 transform(Qt::Uninitialized);
40 transform.setRow(0, QVector4D(right, 0.0f));
41 transform.setRow(1, QVector4D(up, 0.0f));
42 transform.setRow(2, QVector4D(forward, 0.0f));
43 transform.setRow(3, QVector4D(0.0f, 0.0f, 0.0f, 1.0f));
44 return transform;
45}
46
47// These indices need to match the order in QSSGBounds3::toQSSGBoxPointsNoEmptyCheck()
48static constexpr std::array<std::array<int, 2>, 12> BOX_LINE_INDICES = {
49 std::array<int, 2> { 0, 1 }, std::array<int, 2> { 1, 2 }, std::array<int, 2> { 2, 3 }, std::array<int, 2> { 3, 0 },
50 std::array<int, 2> { 4, 5 }, std::array<int, 2> { 5, 6 }, std::array<int, 2> { 6, 7 }, std::array<int, 2> { 7, 4 },
51 std::array<int, 2> { 0, 4 }, std::array<int, 2> { 1, 5 }, std::array<int, 2> { 2, 6 }, std::array<int, 2> { 3, 7 },
52};
53
54void ShadowmapHelpers::addDebugBox(const QSSGBoxPoints &box, const QColor &color, QSSGDebugDrawSystem *debugDrawSystem)
55{
56 if (!debugDrawSystem)
57 return;
58
59 for (const auto line : BOX_LINE_INDICES)
60 debugDrawSystem->drawLine(box[line[0]], box[line[1]], color);
61
62 debugDrawSystem->setEnabled(true);
63}
64
65void ShadowmapHelpers::addDebugFrustum(const QSSGBoxPoints &frustumPoints, const QColor &color, QSSGDebugDrawSystem *debugDrawSystem)
66{
67 ShadowmapHelpers::addDebugBox(frustumPoints, color, debugDrawSystem);
68}
69
70void ShadowmapHelpers::addDirectionalLightDebugBox(const QSSGBoxPoints &box, QSSGDebugDrawSystem *debugDrawSystem)
71{
72 if (!debugDrawSystem)
73 return;
74
75 constexpr QColor colorBox = QColorConstants::Yellow;
76
77 // .7------6
78 // .' | .'|
79 // 3---+--2' |
80 // | | | |
81 // | .4--+---5
82 // |.' | .'
83 // 0------1'
84
85 // Find shortest axis line
86 float shortestAxisSq = (box[0] - box[1]).lengthSquared();
87 shortestAxisSq = std::min(shortestAxisSq, (box[0] - box[4]).lengthSquared());
88 shortestAxisSq = std::min(shortestAxisSq, (box[0] - box[3]).lengthSquared());
89 const float axisLength = qSqrt(shortestAxisSq) * 0.5f;
90
91 auto drawAxisLine = [&](const QVector3D &from, const QVector3D &to, const QColor &axisColor) {
92 const QVector3D dir = (to - from).normalized();
93 const QVector3D mid = from + dir * axisLength;
94 debugDrawSystem->drawLine(from, mid, axisColor);
95 debugDrawSystem->drawLine(mid, to, colorBox);
96 };
97
98 // Draw x,y,z axis in r,g,b color and rest in box color
99
100 // Closest to light (front)
101 drawAxisLine(box[0], box[1], QColorConstants::Red);
102 debugDrawSystem->drawLine(box[1], box[2], colorBox);
103 drawAxisLine(box[0], box[3], QColorConstants::Green);
104 debugDrawSystem->drawLine(box[2], box[3], colorBox);
105
106 // Lines parallel to light direction
107 drawAxisLine(box[0], box[4], QColorConstants::Blue);
108 debugDrawSystem->drawLine(box[1], box[5], colorBox);
109 debugDrawSystem->drawLine(box[2], box[6], colorBox);
110 debugDrawSystem->drawLine(box[3], box[7], colorBox);
111
112 // Furthest from light (back)
113 debugDrawSystem->drawLine(box[4], box[5], colorBox);
114 debugDrawSystem->drawLine(box[5], box[6], colorBox);
115 debugDrawSystem->drawLine(box[6], box[7], colorBox);
116 debugDrawSystem->drawLine(box[7], box[4], colorBox);
117
118 debugDrawSystem->setEnabled(true);
119}
120
121static bool lineLineIntersection(QVector2D a, QVector2D b, QVector2D c, QVector2D d)
122{
123 // Line AB represented as a1x + b1y = c1
124 double a1 = b.y() - a.y();
125 double b1 = a.x() - b.x();
126 double c1 = a1 * (a.x()) + b1 * (a.y());
127
128 // Line CD represented as a2x + b2y = c2
129 double a2 = d.y() - c.y();
130 double b2 = c.x() - d.x();
131 double c2 = a2 * (c.x()) + b2 * (c.y());
132
133 double determinant = a1 * b2 - a2 * b1;
134
135 if (qFuzzyCompare(determinant, 0)) {
136 // The lines are parallel.
137 return false;
138 }
139
140 double x = (b2 * c1 - b1 * c2) / determinant;
141 double y = (a1 * c2 - a2 * c1) / determinant;
142 const QVector2D min = QVector2D(qMin(a.x(), b.x()), qMin(a.y(), b.y()));
143 const QVector2D max = QVector2D(qMax(a.x(), b.x()), qMax(a.y(), b.y()));
144 return x > min.x() && x < max.x() && y > min.y() && y < max.y();
145}
146
147struct Vertex
148{
150 std::array<int, 3> neighbors = { -1, -1, -1 };
151 bool active = true;
152 bool borked = false;
153
154 void addNeighbor(int neighbor)
155 {
156 if (neighbor == neighbors[0] || neighbor == neighbors[1] || neighbor == neighbors[2]) {
157 borked = true;
158 return;
159 }
160
161 if (neighbors[0] == -1) {
162 neighbors[0] = neighbor;
163 } else if (neighbors[1] == -1) {
164 neighbors[1] = neighbor;
165 } else if (neighbors[2] == -1) {
166 neighbors[2] = neighbor;
167 } else {
168 borked = true;
169 }
170 }
171
172 void removeNeighbor(int neighbor)
173 {
174 if (neighbors[0] == neighbor) {
175 neighbors[0] = -1;
176 } else if (neighbors[1] == neighbor) {
177 neighbors[1] = -1;
178 } else if (neighbors[2] == neighbor) {
179 neighbors[2] = -1;
180 } else {
181 borked = true;
182 }
183 }
184
185 bool allNeighbors() const { return neighbors[0] != -1 && neighbors[1] != -1 && neighbors[2] != -1; }
186};
187
188static QList<QVector3D> sliceBoxByPlanes(const QList<std::array<QVector3D, 2>> &planes,
189 const QSSGBoxPoints &castingBox,
190 QSSGDebugDrawSystem *debugDrawSystem,
191 const QColor &color)
192{
193
194 QList<Vertex> vertices;
195 vertices.reserve(castingBox.size());
196 for (const auto &p : castingBox) {
197 Vertex point;
198 point.position = p;
199 vertices.push_back(point);
200 }
201
202 for (auto line : BOX_LINE_INDICES) {
203 vertices[line[0]].addNeighbor(line[1]);
204 vertices[line[1]].addNeighbor(line[0]);
205 }
206
207 QList<QVector2D> planePoints;
208 QList<QPair<quint8, quint8>> lines;
209 QList<bool> intersecting;
210 QList<quint8> newVertexIndices;
211 QList<Vertex> verticesPrev;
212
213 for (const auto &plane : planes) {
214 const QVector3D center = plane[0];
215 const QVector3D normal = plane[1];
216 newVertexIndices.clear();
217 verticesPrev = vertices;
218 for (int vertIndexI = 0, vertEnd = vertices.length(); vertIndexI < vertEnd; ++vertIndexI) {
219 Vertex vertex = vertices[vertIndexI];
220 if (!vertex.active)
221 continue;
222 // Check if 'p' is lying above or below plane
223 // above = inside frustum, below = outside frustum
224 if (QVector3D::dotProduct(vertex.position - center, normal) >= 0) {
225 continue;
226 // 'p' lies outside frustum so project 'p' onto the plane so it lies on the edge of the frustum
227 }
228
229 // Disable and remove vertex for neighbors
230 vertices[vertIndexI].active = false; // disable
231 for (int neighborIndex : vertex.neighbors) {
232 if (neighborIndex == -1)
233 continue;
234 vertices[neighborIndex].removeNeighbor(vertIndexI);
235 }
236
237 for (int neighborIndex : vertex.neighbors) {
238 if (neighborIndex == -1)
239 continue;
240
241 const quint32 idx0 = vertIndexI;
242 const quint32 idx1 = neighborIndex;
243
244 // non-intersecting line, skip
245 if (QVector3D::dotProduct(vertices[idx1].position - center, normal) < 0)
246 continue;
247
248 QVector3D planePoint = center;
249 QVector3D planeNormal = normal;
250 QVector3D linePoint = vertices[idx0].position;
251 QVector3D lineDirection = vertices[idx0].position - vertices[idx1].position;
252 QSSGPlane plane(planePoint, planeNormal);
253 QSSGRenderRay ray(linePoint, lineDirection);
254
255 if (auto intersect = QSSGRenderRay::intersect(plane, ray); intersect.has_value()) {
256 int addedIdx = vertices.length();
257 Q_ASSERT(addedIdx <= 255);
258 Vertex p;
259 p.position = intersect.value();
260 p.addNeighbor(idx1);
261 vertices[idx1].addNeighbor(addedIdx);
262 vertices.push_back(p);
263 newVertexIndices.push_back(addedIdx);
264 }
265 }
266 }
267
268 if (newVertexIndices.isEmpty())
269 continue;
270
271 // Create rotation matrix from plane
272 const QMatrix4x4 transform = buildOrthonormalBasis(normal);
273 planePoints.clear();
274 planePoints.reserve(newVertexIndices.length());
275 for (auto &p0 : newVertexIndices) {
276 QVector3D p = transform.map(vertices[p0].position);
277 planePoints.push_back(QVector2D(p.x(), p.y()));
278 }
279
280 // Create all possible lines from point
281 // num lines = (num_points/2)^2
282 lines.clear();
283 lines.reserve((planePoints.length() * planePoints.length()) / 4);
284 for (int i = 0, length = planePoints.length(); i < length; ++i) {
285 for (int j = i + 1; j < length; ++j) {
286 lines.push_back({ i, j });
287 }
288 }
289
290 // O((num_lines/2)^2)
291 intersecting.clear();
292 intersecting.resize(lines.length(), false);
293 for (int i = 0, length = lines.length(); i < length; ++i) {
294 QPair<quint8, quint8> lineI = lines[i];
295 QVector2D a = planePoints[lineI.first];
296 QVector2D b = planePoints[lineI.second];
297 for (int j = i + 1; j < length; ++j) {
298 QPair<quint8, quint8> lineJ = lines[j];
299
300 // Skip connected lines
301 if (lineJ.first == lineI.first || lineJ.first == lineI.second || lineJ.second == lineI.first
302 || lineJ.second == lineI.second)
303 continue;
304
305 QVector2D c = planePoints[lineJ.first];
306 QVector2D d = planePoints[lineJ.second];
307 if (lineLineIntersection(a, b, c, d)) {
308 intersecting[i] = true;
309 intersecting[j] = true;
310 }
311 }
312 }
313
314 for (int i = 0, length = lines.length(); i < length; ++i) {
315 if (intersecting[i]) {
316 continue;
317 }
318
319 QPair<quint8, quint8> lineI = lines[i];
320 int a = newVertexIndices[lineI.first];
321 int b = newVertexIndices[lineI.second];
322 vertices[a].addNeighbor(b);
323 vertices[b].addNeighbor(a);
324 }
325
326 // Sanity check and revert if any point is borked
327 for (const Vertex &point : std::as_const(vertices)) {
328 if (point.active && (point.borked || !point.allNeighbors())) {
329 vertices = verticesPrev;
330 break;
331 }
332 }
333 }
334
335 QList<QVector3D> result;
336 result.reserve(vertices.length());
337 for (const Vertex &vertex : std::as_const(vertices)) {
338 if (vertex.active)
339 result.push_back(vertex.position);
340 }
341
342 if (debugDrawSystem) {
343 debugDrawSystem->setEnabled(true);
344 for (int i = 0; i < vertices.length(); i++) {
345 Vertex point = vertices[i];
346 if (!point.active)
347 continue;
348
349 QVector3D position = vertices[i].position;
350 debugDrawSystem->drawLine(position, vertices[point.neighbors[0]].position, color);
351 debugDrawSystem->drawLine(position, vertices[point.neighbors[1]].position, color);
352 debugDrawSystem->drawLine(position, vertices[point.neighbors[2]].position, color);
353 }
354 }
355
356 return result;
357}
358
359QList<QVector3D> ShadowmapHelpers::intersectBoxByFrustum(const std::array<QVector3D, 8> &frustumPoints,
360 const QSSGBoxPoints &box,
361 QSSGDebugDrawSystem *debugDrawSystem,
362 const QColor &color)
363{
364 static std::array<std::array<int, 4>, 6> faceIndices = {
365 std::array<int, 4> { 0, 1, 2, 3 }, std::array<int, 4> { 0, 3, 7, 4 }, std::array<int, 4> { 3, 2, 6, 7 },
366 std::array<int, 4> { 7, 6, 5, 4 }, std::array<int, 4> { 4, 5, 1, 0 }, std::array<int, 4> { 2, 1, 5, 6 },
367 };
368
369 QList<std::array<QVector3D, 2>> faces;
370 faces.resize(faceIndices.size());
371 for (int i = 0; i < int(faceIndices.size()); ++i) {
372 std::array<int, 4> face = faceIndices[i];
373 QVector3D center = frustumPoints[face[0]]; // Since the plane is infinite we can take any point
374 QVector3D b = frustumPoints[face[1]] - frustumPoints[face[0]];
375 QVector3D a = frustumPoints[face[2]] - frustumPoints[face[0]];
376 QVector3D n = QVector3D::crossProduct(a, b).normalized();
377 faces[i] = { center, n };
378 }
379
380 return sliceBoxByPlanes(faces, box, debugDrawSystem, color);
381}
382
383QList<QVector3D> ShadowmapHelpers::intersectBoxByBox(const QSSGBounds3 &boxBounds, const QSSGBoxPoints &box)
384{
385 const float minX = boxBounds.minimum.x();
386 const float minY = boxBounds.minimum.y();
387 const float minZ = boxBounds.minimum.z();
388 const float maxX = boxBounds.maximum.x();
389 const float maxY = boxBounds.maximum.y();
390 const float maxZ = boxBounds.maximum.z();
391
392 QSSGBoxPoints points;
393 points[0] = QVector3D(minX, minY, minZ);
394 points[1] = QVector3D(maxX, minY, minZ);
395 points[2] = QVector3D(maxX, maxY, minZ);
396 points[3] = QVector3D(minX, maxY, minZ);
397 points[4] = QVector3D(minX, minY, maxZ);
398 points[5] = QVector3D(maxX, minY, maxZ);
399 points[6] = QVector3D(maxX, maxY, maxZ);
400 points[7] = QVector3D(minX, maxY, maxZ);
401
402 // constexpr std::array<int, 4> NEAR = { 0, 1, 2, 3 };
403 // constexpr std::array<int, 4> FAR = { 7, 6, 5, 4 };
404 static constexpr std::array<int, 4> LEFT = { 0, 3, 7, 4 };
405 static constexpr std::array<int, 4> TOP = { 3, 2, 6, 7 };
406 static constexpr std::array<int, 4> BOTTOM = { 4, 5, 1, 0 };
407 static constexpr std::array<int, 4> RIGHT = { 2, 1, 5, 6 };
408 static constexpr std::array<std::array<int, 4>, 4> faceIndices = { LEFT, TOP, BOTTOM, RIGHT };
409
410 QList<std::array<QVector3D, 2>> faces;
411 faces.resize(faceIndices.size());
412 for (int i = 0; i < int(faceIndices.size()); ++i) {
413 std::array<int, 4> face = faceIndices[i];
414 QVector3D center = points[face[0]]; // Since the plane is infinite we can take any point
415 QVector3D b = points[face[1]] - points[face[0]];
416 QVector3D a = points[face[2]] - points[face[0]];
417 QVector3D n = -QVector3D::crossProduct(a, b).normalized();
418 faces[i] = { center, n };
419 }
420
421 return sliceBoxByPlanes(faces, box, nullptr, QColorConstants::Black);
422}
423
424QT_END_NAMESPACE
void addDebugFrustum(const QSSGBoxPoints &frustumPoints, const QColor &color, QSSGDebugDrawSystem *debugDrawSystem)
QList< QVector3D > intersectBoxByFrustum(const QSSGBoxPoints &frustumPoints, const QSSGBoxPoints &box, QSSGDebugDrawSystem *debugDrawSystem, const QColor &color)
void addDebugBox(const QSSGBoxPoints &boxUnsorted, const QColor &color, QSSGDebugDrawSystem *debugDrawSystem)
QList< QVector3D > intersectBoxByBox(const QSSGBounds3 &boxBounds, const QSSGBoxPoints &box)
void addDirectionalLightDebugBox(const QSSGBoxPoints &box, QSSGDebugDrawSystem *debugDrawSystem)
static constexpr std::array< std::array< int, 2 >, 12 > BOX_LINE_INDICES
static bool lineLineIntersection(QVector2D a, QVector2D b, QVector2D c, QVector2D d)
static QList< QVector3D > sliceBoxByPlanes(const QList< std::array< QVector3D, 2 > > &planes, const QSSGBoxPoints &castingBox, QSSGDebugDrawSystem *debugDrawSystem, const QColor &color)
bool allNeighbors() const
void removeNeighbor(int neighbor)
std::array< int, 3 > neighbors
void addNeighbor(int neighbor)