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