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
cuboidgeometry.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
8#if QT_CONFIG(concurrent)
9#include <QtConcurrentRun>
10#endif
11
13
14/*!
15 \qmltype CuboidGeometry
16 \inqmlmodule QtQuick3D.Helpers
17 \inherits Geometry
18 \since 6.9
19 \brief Provides geometry for a cuboid.
20
21 CuboidGeometry is a geometry type that represents a cuboid. The cuboid's size is
22 defined by its xExtent, yExtent, and zExtent properties. The topology of the plane is defined by
23 the yzMeshResolution, xzMeshResolution, and xyMeshResolution properties.
24*/
25
26/*!
27 \qmlproperty real CuboidGeometry::xExtent
28 The x extent of the cuboid. The default value is 100.0.
29*/
30
31/*!
32 \qmlproperty real CuboidGeometry::yExtent
33 The y extent of the cuboid. The default value is 100.0.
34*/
35
36/*!
37 \qmlproperty real CuboidGeometry::zExtent
38 The z extent of the cuboid. The default value is 100.0.
39*/
40
41/*!
42 \qmlproperty size CuboidGeometry::yzMeshResolution
43 The number of segments in the y and z direction. The default value is 2x2.
44*/
45
46/*!
47 \qmlproperty size CuboidGeometry::xzMeshResolution
48 The number of segments in the x and z direction. The default value is 2x2.
49*/
50
51/*!
52 \qmlproperty size CuboidGeometry::xyMeshResolution
53 The number of segments in the x and y direction. The default value is 2x2.
54*/
55
56/*!
57 \qmlproperty bool CuboidGeometry::asynchronous
58
59 This property holds whether the geometry generation should be asynchronous.
60*/
61
62/*!
63 \qmlproperty bool CuboidGeometry::status
64 \readonly
65
66 This property holds the status of the geometry generation when asynchronous is true.
67
68 \value CuboidGeometry.Null The geometry generation has not started
69 \value CuboidGeometry.Ready The geometry generation is complete.
70 \value CuboidGeometry.Loading The geometry generation is in progress.
71 \value CuboidGeometry.Error The geometry generation failed.
72*/
73
74
75CuboidGeometry::CuboidGeometry(QQuick3DObject *parent)
76 : QQuick3DGeometry(parent)
77{
78#if QT_CONFIG(concurrent)
79 connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished, this, &CuboidGeometry::requestFinished);
80#endif
81 scheduleGeometryUpdate();
82}
83
84CuboidGeometry::~CuboidGeometry()
85{
86
87}
88
89float CuboidGeometry::xExtent() const
90{
91 return m_xExtent;
92}
93
94void CuboidGeometry::setXExtent(float newXExtent)
95{
96 if (qFuzzyCompare(m_xExtent, newXExtent))
97 return;
98 m_xExtent = newXExtent;
99 emit xExtentChanged();
100 scheduleGeometryUpdate();
101}
102
103float CuboidGeometry::yExtent() const
104{
105 return m_yExtent;
106}
107
108void CuboidGeometry::setYExtent(float newYExtent)
109{
110 if (qFuzzyCompare(m_yExtent, newYExtent))
111 return;
112 m_yExtent = newYExtent;
113 emit yExtentChanged();
114 scheduleGeometryUpdate();
115}
116
117float CuboidGeometry::zExtent() const
118{
119 return m_zExtent;
120}
121
122void CuboidGeometry::setZExtent(float newZExtent)
123{
124 if (qFuzzyCompare(m_zExtent, newZExtent))
125 return;
126 m_zExtent = newZExtent;
127 emit zExtentChanged();
128 scheduleGeometryUpdate();
129}
130
131QSize CuboidGeometry::yzMeshResolution() const
132{
133 return m_yzMeshResolution;
134}
135
136void CuboidGeometry::setYzMeshResolution(const QSize &newYzMeshResolution)
137{
138 if (m_yzMeshResolution == newYzMeshResolution)
139 return;
140 m_yzMeshResolution = newYzMeshResolution;
141 emit yzMeshResolutionChanged();
142 scheduleGeometryUpdate();
143}
144
145QSize CuboidGeometry::xzMeshResolution() const
146{
147 return m_xzMeshResolution;
148}
149
150void CuboidGeometry::setXzMeshResolution(const QSize &newXzMeshResolution)
151{
152 if (m_xzMeshResolution == newXzMeshResolution)
153 return;
154 m_xzMeshResolution = newXzMeshResolution;
155 emit xzMeshResolutionChanged();
156 scheduleGeometryUpdate();
157}
158
159QSize CuboidGeometry::xyMeshResolution() const
160{
161 return m_xyMeshResolution;
162}
163
164void CuboidGeometry::setXyMeshResolution(const QSize &newXyMeshResolution)
165{
166 if (m_xyMeshResolution == newXyMeshResolution)
167 return;
168 m_xyMeshResolution = newXyMeshResolution;
169 emit xyMeshResolutionChanged();
170 scheduleGeometryUpdate();
171}
172
173bool CuboidGeometry::asynchronous() const
174{
175 return m_asynchronous;
176}
177
178void CuboidGeometry::setAsynchronous(bool newAsynchronous)
179{
180 if (m_asynchronous == newAsynchronous)
181 return;
182 m_asynchronous = newAsynchronous;
183 emit asynchronousChanged();
184}
185
186CuboidGeometry::Status CuboidGeometry::status() const
187{
188 return m_status;
189}
190
191void CuboidGeometry::doUpdateGeometry()
192{
193 // reset the flag since we are processing the update
194 m_geometryUpdateRequested = false;
195
196#if QT_CONFIG(concurrent)
197 if (m_geometryDataFuture.isRunning()) {
198 m_pendingAsyncUpdate = true;
199 return;
200 }
201#endif
202
203
204
205 // Check validity of the geometry parameters
206 if (m_xExtent <= 0 || m_yExtent <= 0 || m_yExtent <= 0) {
207 clear();
208 update();
209 return;
210 }
211
212#if QT_CONFIG(concurrent)
213 // without threads, QtConcurrent::run() would never run the task
214 if (m_asynchronous && QT_CONFIG(thread)) {
215 m_geometryDataFuture = QtConcurrent::run(generateCuboidGeometryAsync,
216 m_xExtent,
217 m_yExtent,
218 m_zExtent,
219 m_yzMeshResolution,
220 m_xzMeshResolution,
221 m_xyMeshResolution);
222 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
223 m_status = Status::Loading;
224 Q_EMIT statusChanged();
225 } else {
226#else
227 {
228
229#endif // QT_CONFIG(concurrent)
230 updateGeometry(generateCuboidGeometry(m_xExtent,
231 m_yExtent,
232 m_zExtent,
233 m_yzMeshResolution,
234 m_xzMeshResolution,
235 m_xyMeshResolution));
236 }
237}
238
239void CuboidGeometry::requestFinished()
240{
241#if QT_CONFIG(concurrent)
242 const auto output = m_geometryDataFuture.takeResult();
243 updateGeometry(output);
244#endif
245}
246
247void CuboidGeometry::scheduleGeometryUpdate()
248{
249 if (!m_geometryUpdateRequested) {
250 QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
251 m_geometryUpdateRequested = true;
252 }
253}
254
255void CuboidGeometry::updateGeometry(const GeometryData &geometryData)
256{
257 clear();
258 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
259 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
260 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
261 0,
262 QQuick3DGeometry::Attribute::F32Type);
263 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
264 3 * sizeof(float),
265 QQuick3DGeometry::Attribute::F32Type);
266 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
267 5 * sizeof(float),
268 QQuick3DGeometry::Attribute::F32Type);
269 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
270 0,
271 QQuick3DGeometry::Attribute::U16Type);
272
273 setBounds(geometryData.boundsMin, geometryData.boundsMax);
274 setVertexData(geometryData.vertexData);
275 setIndexData(geometryData.indexData);
276
277 // If the geometry update was requested while the geometry was being generated asynchronously,
278 // we need to schedule another geometry update now that the geometry is ready.
279 if (m_pendingAsyncUpdate) {
280 m_pendingAsyncUpdate = false;
281 scheduleGeometryUpdate();
282 } else {
283 m_status = Status::Ready;
284 Q_EMIT statusChanged();
285 }
286 update();
287 emit geometryChanged();
288}
289
290CuboidGeometry::GeometryData CuboidGeometry::generateCuboidGeometry(float xExtent, float yExtent, float zExtent, QSize yzMeshResolution, QSize xzMeshResolution, QSize xyMeshResolution)
291{
292 GeometryData geometryData;
293
294 const float halfXExtent = xExtent / 2.0f;
295 const float halfYExtent = yExtent / 2.0f;
296 const float halfZExtent = zExtent / 2.0f;
297 geometryData.boundsMin = QVector3D(-halfXExtent, -halfYExtent, -halfZExtent);
298 geometryData.boundsMax = QVector3D(halfXExtent, halfYExtent, halfZExtent);
299
300 // Total number of vertices and indices required
301 int numVertices = (yzMeshResolution.width() + 1) * (yzMeshResolution.height() + 1) * 2 +
302 (xzMeshResolution.width() + 1) * (xzMeshResolution.height() + 1) * 2 +
303 (xyMeshResolution.width() + 1) * (xyMeshResolution.height() + 1) * 2;
304
305 int numIndices = yzMeshResolution.width() * yzMeshResolution.height() * 6 * 2 +
306 xzMeshResolution.width() * xzMeshResolution.height() * 6 * 2 +
307 xyMeshResolution.width() * xyMeshResolution.height() * 6 * 2;
308
309 const int vertexStride = sizeof(float) * (3 + 2 + 3); // vec3 (position), vec2 (uv), vec3 (normal)
310 const int indexStride = sizeof(uint16_t); // 16-bit index
311
312 // Allocate memory for vertex and index data
313 geometryData.vertexData.resize(numVertices * vertexStride);
314 geometryData.indexData.resize(numIndices * indexStride);
315
316 // Set up raw pointers for direct memory manipulation
317 float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
318 uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
319
320 // Inline lambda for plane generation
321 auto generatePlane = [](float* &vertexPtr, uint16_t* &indexPtr, uint16_t &indexOffset, float width, float height, const QVector3D& origin, const QVector3D& right, const QVector3D& up, const QVector3D& normal, QSize resolution, bool flipWinding = false) {
322 const int quadsX = resolution.width();
323 const int quadsY = resolution.height();
324 const float halfWidth = width / 2.0f;
325 const float halfHeight = height / 2.0f;
326 quint16 vertexCount = 0;
327
328 // Generate vertices
329 for (int y = 0; y <= quadsY; ++y) {
330 for (int x = 0; x <= quadsX; ++x) {
331 // Normalized UV coordinates
332 float u = static_cast<float>(x) / quadsX;
333 float v = static_cast<float>(y) / quadsY;
334
335 // Compute the position of the vertex
336 QVector3D position = origin + right * (u * width - halfWidth) + up * (v * height - halfHeight);
337
338 // Write position
339 *vertexPtr++ = position.x();
340 *vertexPtr++ = position.y();
341 *vertexPtr++ = position.z();
342
343 // Write UV coordinates
344 *vertexPtr++ = u;
345 *vertexPtr++ = v;
346
347 // Write normal
348 *vertexPtr++ = normal.x();
349 *vertexPtr++ = normal.y();
350 *vertexPtr++ = normal.z();
351
352 ++vertexCount;
353 }
354 }
355
356 // Generate indices
357 for (int y = 0; y < quadsY; ++y) {
358 for (int x = 0; x < quadsX; ++x) {
359 uint16_t a = indexOffset + static_cast<uint16_t>(y * (quadsX + 1) + x);
360 uint16_t b = static_cast<uint16_t>(a + quadsX + 1);
361 uint16_t c = static_cast<uint16_t>(b + 1);
362 uint16_t d = static_cast<uint16_t>(a + 1);
363
364 if (!flipWinding) {
365 // First triangle (a, d, b)
366 *indexPtr++ = a;
367 *indexPtr++ = d;
368 *indexPtr++ = b;
369
370 // Second triangle (b, d, c)
371 *indexPtr++ = b;
372 *indexPtr++ = d;
373 *indexPtr++ = c;
374 } else {
375 *indexPtr++ = a;
376 *indexPtr++ = b;
377 *indexPtr++ = d;
378
379 *indexPtr++ = b;
380 *indexPtr++ = c;
381 *indexPtr++ = d;
382 }
383 }
384 }
385 indexOffset += vertexCount;
386 };
387
388 // Generate the 6 faces of the cuboid
389 // Right and Left (YZ plane)
390 uint16_t indexOffset = 0;
391 generatePlane(vertexPtr, indexPtr, indexOffset, zExtent, yExtent, QVector3D(halfXExtent, 0, 0), QVector3D(0, 0, -1), QVector3D(0, 1, 0), QVector3D(1, 0, 0), yzMeshResolution);
392 generatePlane(vertexPtr, indexPtr, indexOffset, zExtent, yExtent, QVector3D(-halfXExtent, 0, 0), QVector3D(0, 0, 1), QVector3D(0, 1, 0), QVector3D(-1, 0, 0), yzMeshResolution);
393
394
395 // Top and Bottom (XZ plane)
396 generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, zExtent, QVector3D(0, halfYExtent, 0), QVector3D(-1, 0, 0), QVector3D(0, 0, 1), QVector3D(0, 1, 0), xzMeshResolution);
397 generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, zExtent, QVector3D(0, -halfYExtent, 0), QVector3D(1, 0, 0), QVector3D(0, 0, 1), QVector3D(0, -1, 0), xzMeshResolution);
398
399 // Front and Back (XY plane)
400 generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, yExtent, QVector3D(0, 0, halfZExtent), QVector3D(1, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 0, 1), xyMeshResolution);
401 generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, yExtent, QVector3D(0, 0, -halfZExtent), QVector3D(-1, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 0, -1), xyMeshResolution);
402
403 // Return the geometry data
404 return geometryData;
405}
406
407#if QT_CONFIG(concurrent)
408void CuboidGeometry::generateCuboidGeometryAsync(QPromise<CuboidGeometry::GeometryData> &promise,
409 float xExtent,
410 float yExtent,
411 float zExtent,
412 QSize yzMeshResolution,
413 QSize xzMeshResolution,
414 QSize xyMeshResolution)
415{
416 auto output = generateCuboidGeometry(xExtent, yExtent, zExtent, yzMeshResolution, xzMeshResolution, xyMeshResolution);
417 promise.addResult(output);
418}
419#endif
420
421QT_END_NAMESPACE
Combined button and popup list for selecting options.