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 if (m_asynchronous) {
214 m_geometryDataFuture = QtConcurrent::run(generateCuboidGeometryAsync,
215 m_xExtent,
216 m_yExtent,
217 m_zExtent,
218 m_yzMeshResolution,
219 m_xzMeshResolution,
220 m_xyMeshResolution);
221 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
222 m_status = Status::Loading;
223 Q_EMIT statusChanged();
224 } else {
225#else
226 {
227
228#endif // QT_CONFIG(concurrent)
229 updateGeometry(generateCuboidGeometry(m_xExtent,
230 m_yExtent,
231 m_zExtent,
232 m_yzMeshResolution,
233 m_xzMeshResolution,
234 m_xyMeshResolution));
235 }
236}
237
238void CuboidGeometry::requestFinished()
239{
240#if QT_CONFIG(concurrent)
241 const auto output = m_geometryDataFuture.takeResult();
242 updateGeometry(output);
243#endif
244}
245
246void CuboidGeometry::scheduleGeometryUpdate()
247{
248 if (!m_geometryUpdateRequested) {
249 QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
250 m_geometryUpdateRequested = true;
251 }
252}
253
254void CuboidGeometry::updateGeometry(const GeometryData &geometryData)
255{
256 clear();
257 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
258 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
259 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
260 0,
261 QQuick3DGeometry::Attribute::F32Type);
262 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
263 3 * sizeof(float),
264 QQuick3DGeometry::Attribute::F32Type);
265 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
266 5 * sizeof(float),
267 QQuick3DGeometry::Attribute::F32Type);
268 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
269 0,
270 QQuick3DGeometry::Attribute::U16Type);
271
272 setBounds(geometryData.boundsMin, geometryData.boundsMax);
273 setVertexData(geometryData.vertexData);
274 setIndexData(geometryData.indexData);
275
276 // If the geometry update was requested while the geometry was being generated asynchronously,
277 // we need to schedule another geometry update now that the geometry is ready.
278 if (m_pendingAsyncUpdate) {
279 m_pendingAsyncUpdate = false;
280 scheduleGeometryUpdate();
281 } else {
282 m_status = Status::Ready;
283 Q_EMIT statusChanged();
284 }
285 update();
286 emit geometryChanged();
287}
288
289CuboidGeometry::GeometryData CuboidGeometry::generateCuboidGeometry(float xExtent, float yExtent, float zExtent, QSize yzMeshResolution, QSize xzMeshResolution, QSize xyMeshResolution)
290{
291 GeometryData geometryData;
292
293 const float halfXExtent = xExtent / 2.0f;
294 const float halfYExtent = yExtent / 2.0f;
295 const float halfZExtent = zExtent / 2.0f;
296 geometryData.boundsMin = QVector3D(-halfXExtent, -halfYExtent, -halfZExtent);
297 geometryData.boundsMax = QVector3D(halfXExtent, halfYExtent, halfZExtent);
298
299 // Total number of vertices and indices required
300 int numVertices = (yzMeshResolution.width() + 1) * (yzMeshResolution.height() + 1) * 2 +
301 (xzMeshResolution.width() + 1) * (xzMeshResolution.height() + 1) * 2 +
302 (xyMeshResolution.width() + 1) * (xyMeshResolution.height() + 1) * 2;
303
304 int numIndices = yzMeshResolution.width() * yzMeshResolution.height() * 6 * 2 +
305 xzMeshResolution.width() * xzMeshResolution.height() * 6 * 2 +
306 xyMeshResolution.width() * xyMeshResolution.height() * 6 * 2;
307
308 const int vertexStride = sizeof(float) * (3 + 2 + 3); // vec3 (position), vec2 (uv), vec3 (normal)
309 const int indexStride = sizeof(uint16_t); // 16-bit index
310
311 // Allocate memory for vertex and index data
312 geometryData.vertexData.resize(numVertices * vertexStride);
313 geometryData.indexData.resize(numIndices * indexStride);
314
315 // Set up raw pointers for direct memory manipulation
316 float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
317 uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
318
319 // Inline lambda for plane generation
320 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) {
321 const int quadsX = resolution.width();
322 const int quadsY = resolution.height();
323 const float halfWidth = width / 2.0f;
324 const float halfHeight = height / 2.0f;
325 quint16 vertexCount = 0;
326
327 // Generate vertices
328 for (int y = 0; y <= quadsY; ++y) {
329 for (int x = 0; x <= quadsX; ++x) {
330 // Normalized UV coordinates
331 float u = static_cast<float>(x) / quadsX;
332 float v = static_cast<float>(y) / quadsY;
333
334 // Compute the position of the vertex
335 QVector3D position = origin + right * (u * width - halfWidth) + up * (v * height - halfHeight);
336
337 // Write position
338 *vertexPtr++ = position.x();
339 *vertexPtr++ = position.y();
340 *vertexPtr++ = position.z();
341
342 // Write UV coordinates
343 *vertexPtr++ = u;
344 *vertexPtr++ = v;
345
346 // Write normal
347 *vertexPtr++ = normal.x();
348 *vertexPtr++ = normal.y();
349 *vertexPtr++ = normal.z();
350
351 ++vertexCount;
352 }
353 }
354
355 // Generate indices
356 for (int y = 0; y < quadsY; ++y) {
357 for (int x = 0; x < quadsX; ++x) {
358 uint16_t a = indexOffset + static_cast<uint16_t>(y * (quadsX + 1) + x);
359 uint16_t b = static_cast<uint16_t>(a + quadsX + 1);
360 uint16_t c = static_cast<uint16_t>(b + 1);
361 uint16_t d = static_cast<uint16_t>(a + 1);
362
363 if (!flipWinding) {
364 // First triangle (a, d, b)
365 *indexPtr++ = a;
366 *indexPtr++ = d;
367 *indexPtr++ = b;
368
369 // Second triangle (b, d, c)
370 *indexPtr++ = b;
371 *indexPtr++ = d;
372 *indexPtr++ = c;
373 } else {
374 *indexPtr++ = a;
375 *indexPtr++ = b;
376 *indexPtr++ = d;
377
378 *indexPtr++ = b;
379 *indexPtr++ = c;
380 *indexPtr++ = d;
381 }
382 }
383 }
384 indexOffset += vertexCount;
385 };
386
387 // Generate the 6 faces of the cuboid
388 // Right and Left (YZ plane)
389 uint16_t indexOffset = 0;
390 generatePlane(vertexPtr, indexPtr, indexOffset, zExtent, yExtent, QVector3D(halfXExtent, 0, 0), QVector3D(0, 0, -1), QVector3D(0, 1, 0), QVector3D(1, 0, 0), yzMeshResolution);
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
393
394 // Top and Bottom (XZ plane)
395 generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, zExtent, QVector3D(0, halfYExtent, 0), QVector3D(-1, 0, 0), QVector3D(0, 0, 1), QVector3D(0, 1, 0), xzMeshResolution);
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
398 // Front and Back (XY plane)
399 generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, yExtent, QVector3D(0, 0, halfZExtent), QVector3D(1, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 0, 1), xyMeshResolution);
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
402 // Return the geometry data
403 return geometryData;
404}
405
406#if QT_CONFIG(concurrent)
407void CuboidGeometry::generateCuboidGeometryAsync(QPromise<CuboidGeometry::GeometryData> &promise,
408 float xExtent,
409 float yExtent,
410 float zExtent,
411 QSize yzMeshResolution,
412 QSize xzMeshResolution,
413 QSize xyMeshResolution)
414{
415 auto output = generateCuboidGeometry(xExtent, yExtent, zExtent, yzMeshResolution, xzMeshResolution, xyMeshResolution);
416 promise.addResult(output);
417}
418#endif
419
420QT_END_NAMESPACE
Combined button and popup list for selecting options.