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
qssgrenderray.cpp
Go to the documentation of this file.
1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
6
8
9#include <QtQuick3DUtils/private/qssgplane_p.h>
10#include <QtQuick3DUtils/private/qssgutils_p.h>
11#include <QtQuick3DUtils/private/qssgmeshbvh_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrendermesh_p.h>
13
14#include <optional>
15
17
18// http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm
19
20std::optional<QVector3D> QSSGRenderRay::intersect(const QSSGPlane &inPlane, const QSSGRenderRay &ray)
21{
22 float Vd = QVector3D::dotProduct(inPlane.n, ray.direction);
23 if (std::abs(Vd) < .0001f)
24 return std::nullopt;
25 float V0 = -1.0f * (QVector3D::dotProduct(inPlane.n, ray.origin) + inPlane.d);
26 float t = V0 / Vd;
27 return ray.origin + (ray.direction * t);
28}
29
30QSSGRenderRay::RayData QSSGRenderRay::createRayData(const QMatrix4x4 &globalTransform,
31 const QSSGRenderRay &ray)
32{
33 using DirectionOp = RayData::DirectionOp;
34 QMatrix4x4 originTransform = globalTransform.inverted();
35 QVector3D transformedOrigin = QSSGUtils::mat44::transform(originTransform, ray.origin);
36 float *outOriginTransformPtr(originTransform.data());
37 outOriginTransformPtr[12] = outOriginTransformPtr[13] = outOriginTransformPtr[14] = 0.0f;
38 const QVector3D &transformedDirection = QSSGUtils::mat44::rotate(originTransform, ray.direction).normalized();
39 static auto getInverseAndDirOp = [](const QVector3D &dir, QVector3D &invDir, DirectionOp (&dirOp)[3]) {
40 for (int i = 0; i != 3; ++i) {
41 const float axisDir = dir[i];
42 dirOp[i] = qFuzzyIsNull(axisDir) ? DirectionOp::Zero : ((axisDir < -std::numeric_limits<float>::epsilon())
43 ? DirectionOp::Swap
44 : DirectionOp::Normal);
45 invDir[i] = qFuzzyIsNull(axisDir) ? 0.0f : (1.0f / axisDir);
46 }
47 };
48 DirectionOp dirOp[3];
49 QVector3D transformedDirectionInvers;
50 getInverseAndDirOp(transformedDirection, transformedDirectionInvers, dirOp);
51 return RayData{ globalTransform, ray, transformedOrigin, transformedDirectionInvers,
52 transformedDirection, { dirOp[0], dirOp[1], dirOp[2] } };
53}
54
55QSSGRenderRay::IntersectionResult QSSGRenderRay::createIntersectionResult(const QSSGRenderRay::RayData &data,
56 const HitResult &hit)
57{
58 Q_ASSERT(hit.intersects());
59 Q_ASSERT(hit.bounds != nullptr);
60 const QSSGBounds3 &bounds = *hit.bounds;
61 // Local postion
62 const QVector3D &scaledDir = data.direction * hit.min;
63 const QVector3D &localPosition = scaledDir + data.origin;
64 // ray length squared
65 const QVector3D &globalPosition = QSSGUtils::mat44::transform(data.globalTransform, localPosition);
66 const QVector3D &cameraToLocal = data.ray.origin - globalPosition;
67 const float rayLenSquared = QSSGUtils::vec3::magnitudeSquared(cameraToLocal);
68 // UV coordinates
69 const auto &boundsMin = bounds.minimum;
70 const auto &boundsMax = bounds.maximum;
71 const float xRange = boundsMax.x() - boundsMin.x();
72 const float yRange = boundsMax.y() - boundsMin.y();
73 const QVector2D uvCoords{((localPosition[0] - boundsMin.x()) / xRange), ((localPosition[1] - boundsMin.y()) / yRange)};
74
75 // Since we just intersected with a bounding box, there is no face normal
76 return IntersectionResult(rayLenSquared, uvCoords, globalPosition, localPosition, QVector3D(), QVector3D());
77}
78
79QSSGRenderRay::HitResult QSSGRenderRay::intersectWithAABBv2(const QSSGRenderRay::RayData &data,
80 const QSSGBounds3 &bounds)
81{
82 // Intersect the origin with the AABB described by bounds.
83
84 // Scan each axis separately. This code basically finds the distance
85 // from the origin to the near and far bbox planes for a given
86 // axis. It then divides this distance by the direction for that axis to
87 // get a range of t [near,far] that the ray intersects assuming the ray is
88 // described via origin + t*(direction). Running through all three axis means
89 // that you need to min/max those ranges together to find a global min/max
90 // that the pick could possibly be in.
91 float tmax = std::numeric_limits<float>::max();
92 float tmin = std::numeric_limits<float>::min();
93 float origin;
94 const QVector3D *const barray[] { &bounds.minimum, &bounds.maximum };
95
96 for (int axis = 0; axis != 3; ++axis) {
97 origin = data.origin[axis];
98 const bool zeroDir = (data.dirOp[axis] == RayData::DirectionOp::Zero);
99 if (zeroDir && (origin < bounds.minimum[axis] || origin > bounds.maximum[axis])) {
100 // Pickray is roughly parallel to the plane of the slab
101 // so, if the origin is not in the range, we have no intersection
102 return { -1.0f, -1.0f, nullptr };
103 }
104 if (!zeroDir) {
105 // Shrink the intersections to find the closest hit
106 tmax = std::min(((*barray[1-quint8(data.dirOp[axis])])[axis] - origin) * data.directionInvers[axis], tmax);
107 tmin = std::max(((*barray[quint8(data.dirOp[axis])])[axis] - origin) * data.directionInvers[axis], tmin);
108 }
109 }
110
111 return { tmin, tmax, &bounds };
112}
113
114// Möller-Trumbore ray-triangle intersection
115// https://www.graphics.cornell.edu/pubs/1997/MT97.pdf
116// https://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/raytri/
117bool QSSGRenderRay::triangleIntersect(const QSSGRenderRay &ray,
118 const QVector3D &v0,
119 const QVector3D &v1,
120 const QVector3D &v2,
121 float &u,
122 float &v,
123 QVector3D &normal,
124 QSSGCullFaceMode cullMode)
125{
126 const float epsilon = std::numeric_limits<float>::epsilon();
127
128 // Compute the Triangle's Edges
129 const QVector3D edge1 = v1 - v0;
130 const QVector3D edge2 = v2 - v0;
131
132 // Compute the vector P as the cross product of the ray direction and edge2
133 const QVector3D P = QVector3D::crossProduct(ray.direction, edge2);
134
135 // Compute the determinant
136 const float determinant = QVector3D::dotProduct(edge1, P);
137
138 // If the determinant is near zero the ray lies in the plane of the triangle.
139 if (std::abs(determinant) < epsilon)
140 return false;
141
142 // A positive determinant means the ray hit the front face
143 const bool frontFacing = determinant > 0.0f;
144 switch (cullMode) {
145 case QSSGCullFaceMode::Unknown:
146 case QSSGCullFaceMode::Back:
147 if (!frontFacing)
148 return false;
149 break;
150 case QSSGCullFaceMode::Front:
151 if (frontFacing)
152 return false;
153 break;
154 case QSSGCullFaceMode::FrontAndBack:
155 return false;
156 case QSSGCullFaceMode::Disabled:
157 break;
158 }
159
160 const float invDeterminant = 1.0f / determinant;
161
162 // Compute the vector T from the ray origin to the first vertex of the triangle
163 const QVector3D T = ray.origin - v0;
164
165 // Calculate coordinate u and test bounds
166 u = QVector3D::dotProduct(T, P) * invDeterminant;
167 if (u < 0.0f || u > 1.0f)
168 return false;
169
170 // Compute the vector Q as the cross product of vector T and edge1
171 const QVector3D Q = QVector3D::crossProduct(T, edge1);
172
173 // Calculate coordinate v and test bounds
174 v = QVector3D::dotProduct(ray.direction, Q) * invDeterminant;
175 if (v < 0.0f || ((u + v) > 1.0f))
176 return false;
177
178 // Calculate the value of t, the parameter of the intersection point along the ray
179 const float t = QVector3D::dotProduct(edge2, Q) * invDeterminant;
180
181 if (t > epsilon) {
182 normal = QVector3D::crossProduct(edge1, edge2).normalized();
183 return true;
184 }
185
186 return false;
187}
188
189
190void QSSGRenderRay::intersectWithBVH(const RayData &data,
191 const QSSGMeshBVHNode *bvh,
192 const QSSGRenderMesh *mesh,
193 QVector<IntersectionResult> &intersections,
194 QSSGCullFaceMode cullMode,
195 int depth)
196{
197 if (!bvh || !mesh || !mesh->bvh)
198 return;
199
200 // If this is a leaf node, process it's triangles
201 if (bvh->count != 0) {
202 // If there is an intersection on a leaf node, then test against geometry
203 auto results = intersectWithBVHTriangles(data, mesh->bvh->triangles(), bvh->offset, bvh->count, cullMode);
204 if (!results.isEmpty())
205 intersections.append(results);
206 return;
207 }
208
209 auto hit = QSSGRenderRay::intersectWithAABBv2(data, bvh->left->boundingData);
210 if (hit.intersects())
211 intersectWithBVH(data, static_cast<const QSSGMeshBVHNode *>(bvh->left), mesh, intersections, cullMode, depth + 1);
212
213 hit = QSSGRenderRay::intersectWithAABBv2(data, bvh->right->boundingData);
214 if (hit.intersects())
215 intersectWithBVH(data, static_cast<const QSSGMeshBVHNode *>(bvh->right), mesh, intersections, cullMode, depth + 1);
216}
217
218
219
220QVector<QSSGRenderRay::IntersectionResult> QSSGRenderRay::intersectWithBVHTriangles(const RayData &data,
221 const QSSGMeshBVHTriangles &bvhTriangles,
222 int triangleOffset,
223 int triangleCount,
224 QSSGCullFaceMode cullMode)
225{
226 Q_ASSERT(bvhTriangles.size() >= size_t(triangleOffset + triangleCount));
227
228 QVector<QSSGRenderRay::IntersectionResult> results;
229
230 for (int i = triangleOffset; i < triangleCount + triangleOffset; ++i) {
231 const auto &triangle = bvhTriangles[i];
232
233 QSSGRenderRay relativeRay(data.origin, data.direction);
234
235 // Use Barycentric Coordinates to get the intersection values
236 float u = 0.f;
237 float v = 0.f;
238 QVector3D normal;
239 const bool intersects = triangleIntersect(relativeRay,
240 triangle.vertex1,
241 triangle.vertex2,
242 triangle.vertex3,
243 u,
244 v,
245 normal,
246 cullMode);
247 if (intersects) {
248 const float w = 1.0f - u - v;
249 const QVector3D localIntersectionPoint = w * triangle.vertex1 +
250 u * triangle.vertex2 +
251 v * triangle.vertex3;
252
253 const QVector2D uvCoordinate = w * triangle.uvCoord1 +
254 u * triangle.uvCoord2 +
255 v * triangle.uvCoord3;
256 // Get the intersection point in scene coordinates
257 const QVector3D sceneIntersectionPos = QSSGUtils::mat44::transform(data.globalTransform,
258 localIntersectionPoint);
259 const QVector3D hitVector = data.ray.origin - sceneIntersectionPos;
260 // Get the magnitude of the hit vector
261 const float rayLengthSquared = QSSGUtils::vec3::magnitudeSquared(hitVector);
262
263 // Get the normal vector in scene coordinates
264 const QMatrix3x3 normalMatrix = data.globalTransform.normalMatrix();
265 const QVector3D sceneNormal = QSSGUtils::mat33::transform(normalMatrix, normal);
266
267 results.append(IntersectionResult(rayLengthSquared,
268 uvCoordinate,
269 sceneIntersectionPos,
270 localIntersectionPoint,
271 normal,
272 sceneNormal));
273 }
274 }
275
276 // Does not intersect with any of the triangles
277 return results;
278}
279
280std::optional<QVector2D> QSSGRenderRay::relative(const QMatrix4x4 &inGlobalTransform,
281 const QSSGBounds3 &inBounds,
282 QSSGRenderBasisPlanes inPlane) const
283{
284 QMatrix4x4 theOriginTransform = inGlobalTransform.inverted();
285
286 QVector3D theTransformedOrigin = QSSGUtils::mat44::transform(theOriginTransform, origin);
287 float *outOriginTransformPtr(theOriginTransform.data());
288 outOriginTransformPtr[12] = outOriginTransformPtr[13] = outOriginTransformPtr[14] = 0.0f;
289 QVector3D theTransformedDirection = QSSGUtils::mat44::rotate(theOriginTransform, direction);
290
291 // The XY plane is going to be a plane with either positive or negative Z direction that runs
292 // through
293 QVector3D theDirection(0, 0, 1);
294 QVector3D theRight(1, 0, 0);
295 QVector3D theUp(0, 1, 0);
296 switch (inPlane) {
297 case QSSGRenderBasisPlanes::XY:
298 break;
299 case QSSGRenderBasisPlanes::XZ:
300 theDirection = QVector3D(0, 1, 0);
301 theUp = QVector3D(0, 0, 1);
302 break;
303 case QSSGRenderBasisPlanes::YZ:
304 theDirection = QVector3D(1, 0, 0);
305 theRight = QVector3D(0, 0, 1);
306 break;
307 }
308 QSSGPlane thePlane(theDirection,
309 QVector3D::dotProduct(theDirection, theTransformedDirection) > 0.0f
310 ? QVector3D::dotProduct(theDirection, inBounds.maximum)
311 : QVector3D::dotProduct(theDirection, inBounds.minimum));
312
313 const QSSGRenderRay relativeRay(theTransformedOrigin, theTransformedDirection);
314 std::optional<QVector3D> localIsect = QSSGRenderRay::intersect(thePlane, relativeRay);
315 if (localIsect.has_value()) {
316 float xRange = QVector3D::dotProduct(theRight, inBounds.maximum) - QVector3D::dotProduct(theRight, inBounds.minimum);
317 float yRange = QVector3D::dotProduct(theUp, inBounds.maximum) - QVector3D::dotProduct(theUp, inBounds.minimum);
318 float xOrigin = xRange / 2.0f + QVector3D::dotProduct(theRight, inBounds.minimum);
319 float yOrigin = yRange / 2.0f + QVector3D::dotProduct(theUp, inBounds.minimum);
320 return QVector2D((QVector3D::dotProduct(theRight, *localIsect) - xOrigin) / xRange,
321 (QVector3D::dotProduct(theUp, *localIsect) - yOrigin) / yRange);
322 }
323 return std::nullopt;
324}
325
326QT_END_NAMESPACE
Combined button and popup list for selecting options.