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
spheregeometry.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#include <limits>
8
9#if QT_CONFIG(concurrent)
10#include <QtConcurrentRun>
11#endif
12
13QT_BEGIN_NAMESPACE
14
15/*!
16 \qmltype SphereGeometry
17 \inqmlmodule QtQuick3D.Helpers
18 \inherits Geometry
19 \since 6.9
20 \brief Provides geometry for a sphere.
21
22 SphereGeometry is a geometry type that represents a sphere. The sphere's size is
23 defined by its radius. The topology of the sphere is defined by the number of
24 rings and segments.
25*/
26
27/*!
28 \qmlproperty real SphereGeometry::radius
29 The radius of the sphere. The default value is 100.0.
30*/
31
32/*!
33 \qmlproperty int SphereGeometry::rings
34 The number of rings in the sphere. The default value is 16.
35*/
36
37/*!
38 \qmlproperty int SphereGeometry::segments
39 The number of segments in the sphere. The default value is 32.
40*/
41
42/*!
43 \qmlproperty bool SphereGeometry::asynchronous
44
45 This property holds whether the geometry generation should be asynchronous.
46*/
47
48/*!
49 \qmlproperty bool SphereGeometry::status
50 \readonly
51
52 This property holds the status of the geometry generation when asynchronous is true.
53
54 \value SphereGeometry.Null The geometry generation has not started
55 \value SphereGeometry.Ready The geometry generation is complete.
56 \value SphereGeometry.Loading The geometry generation is in progress.
57 \value SphereGeometry.Error The geometry generation failed.
58*/
59
60SphereGeometry::SphereGeometry(QQuick3DObject *parent)
61 : QQuick3DGeometry(parent)
62{
63#if QT_CONFIG(concurrent)
64 connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished, this, &SphereGeometry::requestFinished);
65#endif
66 scheduleGeometryUpdate();
67}
68
69SphereGeometry::~SphereGeometry()
70{
71
72}
73
74float SphereGeometry::radius() const
75{
76 return m_radius;
77}
78
79void SphereGeometry::setRadius(float newRadius)
80{
81 if (qFuzzyCompare(m_radius, newRadius))
82 return;
83 m_radius = newRadius;
84 emit radiusChanged();
85 scheduleGeometryUpdate();
86}
87
88int SphereGeometry::rings() const
89{
90 return m_rings;
91}
92
93void SphereGeometry::setRings(int newRings)
94{
95 if (m_rings == newRings)
96 return;
97 m_rings = newRings;
98 emit ringsChanged();
99 scheduleGeometryUpdate();
100}
101
102int SphereGeometry::segments() const
103{
104 return m_segments;
105}
106
107void SphereGeometry::setSegments(int newSegments)
108{
109 if (m_segments == newSegments)
110 return;
111 m_segments = newSegments;
112 emit segmentsChanged();
113 scheduleGeometryUpdate();
114}
115
116bool SphereGeometry::asynchronous() const
117{
118 return m_asynchronous;
119}
120
121void SphereGeometry::setAsynchronous(bool newAsynchronous)
122{
123 if (m_asynchronous == newAsynchronous)
124 return;
125 m_asynchronous = newAsynchronous;
126 emit asynchronousChanged();
127}
128
129SphereGeometry::Status SphereGeometry::status() const
130{
131 return m_status;
132}
133
134void SphereGeometry::doUpdateGeometry()
135{
136 // reset the flag since we are processing the update
137 m_geometryUpdateRequested = false;
138
139#if QT_CONFIG(concurrent)
140 if (m_geometryDataFuture.isRunning()) {
141 m_pendingAsyncUpdate = true;
142 return;
143 }
144#endif
145
146 // Check validity of the geometry parameters
147 if (m_radius < 0 || m_rings < 1 || m_segments < 3) {
148 clear();
149 update();
150 return;
151 }
152
153#if QT_CONFIG(concurrent)
154 // without threads, QtConcurrent::run() would never run the task
155 if (m_asynchronous && QT_CONFIG(thread)) {
156 m_geometryDataFuture = QtConcurrent::run(generateSphereGeometryAsync,
157 m_radius,
158 m_rings,
159 m_segments);
160 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
161 m_status = Status::Loading;
162 Q_EMIT statusChanged();
163 } else {
164#else
165 {
166
167#endif // QT_CONFIG(concurrent)
168 updateGeometry(generateSphereGeometry(m_radius, m_rings, m_segments));
169 }
170}
171
172void SphereGeometry::requestFinished()
173{
174#if QT_CONFIG(concurrent)
175 const auto output = m_geometryDataFuture.takeResult();
176 updateGeometry(output);
177#endif
178}
179
180void SphereGeometry::scheduleGeometryUpdate()
181{
182 if (!m_geometryUpdateRequested) {
183 QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
184 m_geometryUpdateRequested = true;
185 }
186}
187
188void SphereGeometry::updateGeometry(const GeometryData &geometryData)
189{
190 clear();
191 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
192 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
193 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
194 0,
195 QQuick3DGeometry::Attribute::F32Type);
196 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
197 3 * sizeof(float),
198 QQuick3DGeometry::Attribute::F32Type);
199 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
200 5 * sizeof(float),
201 QQuick3DGeometry::Attribute::F32Type);
202 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
203 0,
204 QQuick3DGeometry::Attribute::U16Type);
205
206 setBounds(geometryData.boundsMin, geometryData.boundsMax);
207 setVertexData(geometryData.vertexData);
208 setIndexData(geometryData.indexData);
209
210 // If the geometry update was requested while the geometry was being generated asynchronously,
211 // we need to schedule another geometry update now that the geometry is ready.
212 if (m_pendingAsyncUpdate) {
213 m_pendingAsyncUpdate = false;
214 scheduleGeometryUpdate();
215 } else {
216 m_status = Status::Ready;
217 Q_EMIT statusChanged();
218 }
219 update();
220 emit geometryChanged();
221}
222
223SphereGeometry::GeometryData SphereGeometry::generateSphereGeometry(float radius, int rings, int segments)
224{
225 GeometryData geometryData;
226
227 // Pre-compute the size of the vertex and index data
228 const int numVertices = (rings + 1) * (segments + 1);
229 const int numIndices = rings * segments * 6;
230 const int vertexStride = sizeof(float) * (3 + 2 + 3); // 3 for position, 2 for uv, 3 for normal
231 const int indexStride = sizeof(uint16_t);
232
233 // Resize the QByteArrays to fit all the vertex and index data
234 geometryData.vertexData.resize(numVertices * vertexStride);
235 geometryData.indexData.resize(numIndices * indexStride);
236
237
238 // Bounds initialization using std::numeric_limits<float>::max() and lowest()
239 QVector3D boundsMin(std::numeric_limits<float>::max(),
240 std::numeric_limits<float>::max(),
241 std::numeric_limits<float>::max());
242
243 QVector3D boundsMax(std::numeric_limits<float>::lowest(),
244 std::numeric_limits<float>::lowest(),
245 std::numeric_limits<float>::lowest());
246
247 // Temporary pointers for direct writing into the QByteArray
248 float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
249 uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
250
251 // Loop through rings and segments to generate the vertex data
252 for (int i = 0; i <= rings; ++i) {
253 float phi = M_PI * i / rings; // from 0 to PI
254 float y = radius * std::cos(phi);
255 float ringRadius = radius * std::sin(phi);
256
257 for (int j = 0; j <= segments; ++j) {
258 float theta = 2 * M_PI * j / segments; // from 0 to 2PI
259 float x = ringRadius * std::cos(theta);
260 float z = ringRadius * std::sin(theta);
261
262 // Position (vec3)
263 *vertexPtr++ = x;
264 *vertexPtr++ = y;
265 *vertexPtr++ = z;
266
267 // UV coordinates (vec2)
268 *vertexPtr++ = 1.0f - static_cast<float>(j) / segments;
269 *vertexPtr++ = 1.0f - static_cast<float>(i) / rings;
270
271 // Normalized normal vector (vec3)
272 QVector3D normal(x, y, z);
273 normal.normalize();
274 *vertexPtr++ = normal.x();
275 *vertexPtr++ = normal.y();
276 *vertexPtr++ = normal.z();
277
278 // Update bounds
279 boundsMin.setX(std::min(boundsMin.x(), x));
280 boundsMin.setY(std::min(boundsMin.y(), y));
281 boundsMin.setZ(std::min(boundsMin.z(), z));
282 boundsMax.setX(std::max(boundsMax.x(), x));
283 boundsMax.setY(std::max(boundsMax.y(), y));
284 boundsMax.setZ(std::max(boundsMax.z(), z));
285 }
286 }
287
288 // Loop through rings and segments to generate the index data
289 for (int i = 0; i < rings; ++i) {
290 for (int j = 0; j < segments; ++j) {
291 uint16_t a = static_cast<uint16_t>(i * (segments + 1) + j);
292 uint16_t b = static_cast<uint16_t>(a + segments + 1);
293 uint16_t c = static_cast<uint16_t>(b + 1);
294 uint16_t d = static_cast<uint16_t>(a + 1);
295
296 // First triangle (a, d, b)
297 *indexPtr++ = a;
298 *indexPtr++ = d;
299 *indexPtr++ = b;
300
301 // Second triangle (b, d, c)
302 *indexPtr++ = b;
303 *indexPtr++ = d;
304 *indexPtr++ = c;
305 }
306 }
307
308 geometryData.boundsMin = boundsMin;
309 geometryData.boundsMax = boundsMax;
310
311 // Return the geometry data
312 return geometryData;
313}
314
315#if QT_CONFIG(concurrent)
316void SphereGeometry::generateSphereGeometryAsync(QPromise<SphereGeometry::GeometryData> &promise,
317 float radius,
318 int rings,
319 int segments)
320{
321 auto output = generateSphereGeometry(radius, rings, segments);
322 promise.addResult(output);
323}
324#endif
325
326QT_END_NAMESPACE