QGIS API Documentation 3.99.0-Master (752b475928d)
Loading...
Searching...
No Matches
qgsraycastingutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsraycastingutils_h.cpp
3 --------------------------------------
4 Date : June 2018
5 Copyright : (C) 2018 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsraycastingutils.h"
17
18#include "qgsaabb.h"
19#include "qgslogger.h"
20#include "qgsray3d.h"
21
22#include <Qt3DRender/QGeometryRenderer>
23
24#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
25#include <Qt3DRender/QAttribute>
26#include <Qt3DRender/QBuffer>
27#include <Qt3DRender/QGeometry>
28typedef Qt3DRender::QAttribute Qt3DQAttribute;
29typedef Qt3DRender::QBuffer Qt3DQBuffer;
30typedef Qt3DRender::QGeometry Qt3DQGeometry;
31#else
32#include <Qt3DCore/QAttribute>
33#include <Qt3DCore/QBuffer>
34#include <Qt3DCore/QGeometry>
35typedef Qt3DCore::QAttribute Qt3DQAttribute;
36typedef Qt3DCore::QBuffer Qt3DQBuffer;
37typedef Qt3DCore::QGeometry Qt3DQGeometry;
38#endif
39
41
42
43namespace QgsRayCastingUtils
44{
45 bool rayBoxIntersection( const QgsRay3D &ray, const QgsAABB &nodeBbox )
46 {
47 // https://tavianator.com/fast-branchless-raybounding-box-intersections/
48 // https://tavianator.com/fast-branchless-raybounding-box-intersections-part-2-nans/
49
50 const QVector3D dirInv = ray.directionInversed();
51
52 double boxXMax = nodeBbox.xMax;
53 double boxYMax = nodeBbox.yMax;
54 double boxZMax = nodeBbox.zMax;
55
56 // intersection() does not like yMin==yMax (excludes borders)
57 if ( boxXMax == nodeBbox.xMin )
58 boxXMax += 0.1;
59 if ( boxYMax == nodeBbox.yMin )
60 boxYMax += 0.1;
61 if ( boxZMax == nodeBbox.zMin )
62 boxZMax += 0.1;
63
64
65 double t1 = ( nodeBbox.xMin - ray.origin().x() ) * dirInv.x();
66 double t2 = ( boxXMax - ray.origin().x() ) * dirInv.x();
67 double tmin = std::min( t1, t2 );
68 double tmax = std::max( t1, t2 );
69
70 t1 = ( nodeBbox.yMin - ray.origin().y() ) * dirInv.y();
71 t2 = ( boxYMax - ray.origin().y() ) * dirInv.y();
72 tmin = std::max( tmin, std::min( std::min( t1, t2 ), tmax ) );
73 tmax = std::min( tmax, std::max( std::max( t1, t2 ), tmin ) );
74
75 t1 = ( nodeBbox.zMin - ray.origin().z() ) * dirInv.z();
76 t2 = ( boxZMax - ray.origin().z() ) * dirInv.z();
77 tmin = std::max( tmin, std::min( std::min( t1, t2 ), tmax ) );
78 tmax = std::min( tmax, std::max( std::max( t1, t2 ), tmin ) );
79
80 return tmax > std::max( tmin, 0.0 );
81 }
82
83
84 // copied from intersectsSegmentTriangle() from qt3d/src/render/backend/triangleboundingvolume.cpp
85 // by KDAB, licensed under the terms of LGPL
86 bool rayTriangleIntersection( const QgsRay3D &ray, float maxDist, const QVector3D &a, const QVector3D &b, const QVector3D &c, QVector3D &uvw, float &t )
87 {
88 // Note: a, b, c in clockwise order
89 // RealTime Collision Detection page 192
90
91 const QVector3D ab = b - a;
92 const QVector3D ac = c - a;
93 const QVector3D qp = ( ray.origin() - ray.point( maxDist ) );
94
95 const QVector3D n = QVector3D::crossProduct( ab, ac );
96 const float d = QVector3D::dotProduct( qp, n );
97
98 if ( d <= 0.0f || std::isnan( d ) )
99 return false;
100
101 const QVector3D ap = ray.origin() - a;
102 t = QVector3D::dotProduct( ap, n );
103
104 if ( t < 0.0f || t > d )
105 return false;
106
107 const QVector3D e = QVector3D::crossProduct( qp, ap );
108 uvw.setY( QVector3D::dotProduct( ac, e ) );
109
110 if ( uvw.y() < 0.0f || uvw.y() > d )
111 return false;
112
113 uvw.setZ( -QVector3D::dotProduct( ab, e ) );
114
115 if ( uvw.z() < 0.0f || uvw.y() + uvw.z() > d )
116 return false;
117
118 const float ood = 1.0f / d;
119 t *= ood;
120 uvw.setY( uvw.y() * ood );
121 uvw.setZ( uvw.z() * ood );
122 uvw.setX( 1.0f - uvw.y() - uvw.z() );
123
124 return true;
125 }
126
127 bool rayMeshIntersection( Qt3DRender::QGeometryRenderer *geometryRenderer, const QgsRay3D &r, float maxDist, const QMatrix4x4 &worldTransform, QVector3D &intPt, int &triangleIndex )
128 {
129 if ( geometryRenderer->primitiveType() != Qt3DRender::QGeometryRenderer::Triangles )
130 {
131 QgsDebugError( QString( "Unsupported primitive type for intersection: " ).arg( geometryRenderer->primitiveType() ) );
132 return false;
133 }
134 if ( geometryRenderer->instanceCount() != 1 || geometryRenderer->indexOffset() != 0 || geometryRenderer->indexBufferByteOffset() != 0 || geometryRenderer->firstVertex() != 0 || geometryRenderer->firstInstance() != 0 )
135 {
136 QgsDebugError( QString( "Unsupported geometry renderer for intersection." ) );
137 return false;
138 }
139
140 Qt3DQGeometry *geometry = geometryRenderer->geometry();
141
142 Qt3DQAttribute *positionAttr = nullptr;
143 Qt3DQAttribute *indexAttr = nullptr;
144 for ( Qt3DQAttribute *attr : geometry->attributes() )
145 {
146 if ( attr->name() == Qt3DQAttribute::defaultPositionAttributeName() )
147 {
148 positionAttr = attr;
149 }
150 else if ( attr->attributeType() == Qt3DQAttribute::IndexAttribute )
151 {
152 indexAttr = attr;
153 }
154 }
155
156 if ( !positionAttr )
157 {
158 QgsDebugError( "Could not find position attribute!" );
159 return false;
160 }
161
162 if ( positionAttr->vertexBaseType() != Qt3DQAttribute::Float || positionAttr->vertexSize() != 3 )
163 {
164 QgsDebugError( QString( "Unsupported position attribute: base type %1, vertex size %2" ).arg( positionAttr->vertexBaseType() ).arg( positionAttr->vertexSize() ) );
165 return false;
166 }
167
168 const QByteArray vertexBuf = positionAttr->buffer()->data();
169 const char *vertexPtr = vertexBuf.constData();
170 vertexPtr += positionAttr->byteOffset();
171 int vertexByteStride = positionAttr->byteStride() == 0 ? 3 * sizeof( float ) : positionAttr->byteStride();
172
173 const uchar *indexPtrUChar = nullptr;
174 const ushort *indexPtrUShort = nullptr;
175 const uint *indexPtrUInt = nullptr;
176 if ( indexAttr )
177 {
178 if ( indexAttr->byteStride() != 0 || indexAttr->vertexSize() != 1 )
179 {
180 QgsDebugError( QString( "Unsupported index attribute: stride %1, vertex size %2" ).arg( indexAttr->byteStride() ).arg( indexAttr->vertexSize() ) );
181 return false;
182 }
183
184 const QByteArray indexBuf = indexAttr->buffer()->data();
185 if ( indexAttr->vertexBaseType() == Qt3DQAttribute::UnsignedByte )
186 {
187 indexPtrUChar = reinterpret_cast<const uchar *>( indexBuf.constData() + indexAttr->byteOffset() );
188 }
189 else if ( indexAttr->vertexBaseType() == Qt3DQAttribute::UnsignedShort )
190 {
191 indexPtrUShort = reinterpret_cast<const ushort *>( indexBuf.constData() + indexAttr->byteOffset() );
192 }
193 else if ( indexAttr->vertexBaseType() == Qt3DQAttribute::UnsignedInt )
194 {
195 indexPtrUInt = reinterpret_cast<const uint *>( indexBuf.constData() + indexAttr->byteOffset() );
196 }
197 else
198 {
199 QgsDebugError( QString( "Unsupported index attribute: base type %1" ).arg( indexAttr->vertexBaseType() ) );
200 return false;
201 }
202 }
203
204 int vertexCount = geometryRenderer->vertexCount();
205 if ( vertexCount == 0 && indexAttr )
206 {
207 vertexCount = indexAttr->count();
208 }
209 if ( vertexCount == 0 )
210 {
211 vertexCount = positionAttr->count();
212 }
213
214 QVector3D intersectionPt, minIntersectionPt;
215 float minDistance = -1;
216
217 for ( int i = 0; i < vertexCount; i += 3 )
218 {
219 int v0index = 0, v1index = 0, v2index = 0;
220 if ( !indexAttr )
221 {
222 v0index = i;
223 v1index = i + 1;
224 v2index = i + 2;
225 }
226 else if ( indexPtrUShort )
227 {
228 v0index = indexPtrUShort[i];
229 v1index = indexPtrUShort[i + 1];
230 v2index = indexPtrUShort[i + 2];
231 }
232 else if ( indexPtrUChar )
233 {
234 v0index = indexPtrUChar[i];
235 v1index = indexPtrUChar[i + 1];
236 v2index = indexPtrUChar[i + 2];
237 }
238 else if ( indexPtrUInt )
239 {
240 v0index = indexPtrUInt[i];
241 v1index = indexPtrUInt[i + 1];
242 v2index = indexPtrUInt[i + 2];
243 }
244 else
245 Q_ASSERT( false );
246
247 const float *v0ptr = reinterpret_cast<const float *>( vertexPtr + v0index * vertexByteStride );
248 const float *v1ptr = reinterpret_cast<const float *>( vertexPtr + v1index * vertexByteStride );
249 const float *v2ptr = reinterpret_cast<const float *>( vertexPtr + v2index * vertexByteStride );
250
251 const QVector3D a( v0ptr[0], v0ptr[1], v0ptr[2] );
252 const QVector3D b( v1ptr[0], v1ptr[1], v1ptr[2] );
253 const QVector3D c( v2ptr[0], v2ptr[1], v2ptr[2] );
254
255 // Currently the worldTransform only has vertical offset, so this could be optimized by applying the transform
256 // to the ray and the resulting intersecting point instead of all triangles
257 // Need to check for potential performance gains.
258 const QVector3D tA = worldTransform * a;
259 const QVector3D tB = worldTransform * b;
260 const QVector3D tC = worldTransform * c;
261
262 QVector3D uvw;
263 float t = 0;
264
265 // We're testing both triangle orientations here and ignoring the culling mode.
266 // We should probably respect the culling mode used for the entity and perform a
267 // single test using the properly oriented triangle.
268 if ( QgsRayCastingUtils::rayTriangleIntersection( r, maxDist, tA, tB, tC, uvw, t ) || QgsRayCastingUtils::rayTriangleIntersection( r, maxDist, tA, tC, tB, uvw, t ) )
269 {
270 intersectionPt = r.point( t * maxDist );
271 const float distance = r.projectedDistance( intersectionPt );
272
273 // we only want the first intersection of the ray with the mesh (closest to the ray origin)
274 if ( minDistance == -1 || distance < minDistance )
275 {
276 triangleIndex = static_cast<int>( i / 3 );
277 minDistance = distance;
278 minIntersectionPt = intersectionPt;
279 }
280 }
281 }
282
283 if ( minDistance != -1 )
284 {
285 intPt = minIntersectionPt;
286 return true;
287 }
288 else
289 return false;
290 }
291} // namespace QgsRayCastingUtils
292
293
float yMax
Definition qgsaabb.h:102
float xMax
Definition qgsaabb.h:101
float xMin
Definition qgsaabb.h:98
float zMax
Definition qgsaabb.h:103
float yMin
Definition qgsaabb.h:99
float zMin
Definition qgsaabb.h:100
float projectedDistance(const QVector3D &point) const
Returns the distance of the projection of a point to the ray.
Definition qgsray3d.cpp:47
QVector3D origin() const
Returns the origin of the ray.
Definition qgsray3d.h:44
QVector3D directionInversed() const
Returns a vector with the direction components inversed ( 1/x, 1/y, 1/z) This can be used as an optim...
Definition qgsray3d.h:57
QVector3D point(float distance) const
Returns the point along the ray with the specified distance from the ray's origin.
Definition qgsray3d.cpp:68
bool rayBoxIntersection(const QgsRay3D &ray, const QgsAABB &nodeBbox)
Tests whether an axis aligned box is intersected by a ray.
bool rayMeshIntersection(Qt3DRender::QGeometryRenderer *geometryRenderer, const QgsRay3D &r, float maxDist, const QMatrix4x4 &worldTransform, QVector3D &intPt, int &triangleIndex)
Tests whether a triangular mesh is intersected by a ray.
bool rayTriangleIntersection(const QgsRay3D &ray, float maxDist, const QVector3D &a, const QVector3D &b, const QVector3D &c, QVector3D &uvw, float &t)
Tests whether a triangle is intersected by a ray.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QGeometry Qt3DQGeometry
#define QgsDebugError(str)
Definition qgslogger.h:57