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 if (m_asynchronous) {
155 m_geometryDataFuture = QtConcurrent::run(generateSphereGeometryAsync,
156 m_radius,
157 m_rings,
158 m_segments);
159 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
160 m_status = Status::Loading;
161 Q_EMIT statusChanged();
162 } else {
163#else
164 {
165
166#endif // QT_CONFIG(concurrent)
167 updateGeometry(generateSphereGeometry(m_radius, m_rings, m_segments));
168 }
169}
170
171void SphereGeometry::requestFinished()
172{
173#if QT_CONFIG(concurrent)
174 const auto output = m_geometryDataFuture.takeResult();
175 updateGeometry(output);
176#endif
177}
178
179void SphereGeometry::scheduleGeometryUpdate()
180{
181 if (!m_geometryUpdateRequested) {
182 QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
183 m_geometryUpdateRequested = true;
184 }
185}
186
187void SphereGeometry::updateGeometry(const GeometryData &geometryData)
188{
189 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
190 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
191 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
192 0,
193 QQuick3DGeometry::Attribute::F32Type);
194 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
195 3 * sizeof(float),
196 QQuick3DGeometry::Attribute::F32Type);
197 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
198 5 * sizeof(float),
199 QQuick3DGeometry::Attribute::F32Type);
200 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
201 0,
202 QQuick3DGeometry::Attribute::U16Type);
203
204 setBounds(geometryData.boundsMin, geometryData.boundsMax);
205 setVertexData(geometryData.vertexData);
206 setIndexData(geometryData.indexData);
207
208 // If the geometry update was requested while the geometry was being generated asynchronously,
209 // we need to schedule another geometry update now that the geometry is ready.
210 if (m_pendingAsyncUpdate) {
211 m_pendingAsyncUpdate = false;
212 scheduleGeometryUpdate();
213 } else {
214 m_status = Status::Ready;
215 Q_EMIT statusChanged();
216 }
217 update();
218 emit geometryChanged();
219}
220
221SphereGeometry::GeometryData SphereGeometry::generateSphereGeometry(float radius, int rings, int segments)
222{
223 GeometryData geometryData;
224
225 // Pre-compute the size of the vertex and index data
226 const int numVertices = (rings + 1) * (segments + 1);
227 const int numIndices = rings * segments * 6;
228 const int vertexStride = sizeof(float) * (3 + 2 + 3); // 3 for position, 2 for uv, 3 for normal
229 const int indexStride = sizeof(uint16_t);
230
231 // Resize the QByteArrays to fit all the vertex and index data
232 geometryData.vertexData.resize(numVertices * vertexStride);
233 geometryData.indexData.resize(numIndices * indexStride);
234
235
236 // Bounds initialization using std::numeric_limits<float>::max() and lowest()
237 QVector3D boundsMin(std::numeric_limits<float>::max(),
238 std::numeric_limits<float>::max(),
239 std::numeric_limits<float>::max());
240
241 QVector3D boundsMax(std::numeric_limits<float>::lowest(),
242 std::numeric_limits<float>::lowest(),
243 std::numeric_limits<float>::lowest());
244
245 // Temporary pointers for direct writing into the QByteArray
246 float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
247 uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
248
249 // Loop through rings and segments to generate the vertex data
250 for (int i = 0; i <= rings; ++i) {
251 float phi = M_PI * i / rings; // from 0 to PI
252 float y = radius * std::cos(phi);
253 float ringRadius = radius * std::sin(phi);
254
255 for (int j = 0; j <= segments; ++j) {
256 float theta = 2 * M_PI * j / segments; // from 0 to 2PI
257 float x = ringRadius * std::cos(theta);
258 float z = ringRadius * std::sin(theta);
259
260 // Position (vec3)
261 *vertexPtr++ = x;
262 *vertexPtr++ = y;
263 *vertexPtr++ = z;
264
265 // UV coordinates (vec2)
266 *vertexPtr++ = 1.0f - static_cast<float>(j) / segments;
267 *vertexPtr++ = 1.0f - static_cast<float>(i) / rings;
268
269 // Normalized normal vector (vec3)
270 QVector3D normal(x, y, z);
271 normal.normalize();
272 *vertexPtr++ = normal.x();
273 *vertexPtr++ = normal.y();
274 *vertexPtr++ = normal.z();
275
276 // Update bounds
277 boundsMin.setX(std::min(boundsMin.x(), x));
278 boundsMin.setY(std::min(boundsMin.y(), y));
279 boundsMin.setZ(std::min(boundsMin.z(), z));
280 boundsMax.setX(std::max(boundsMax.x(), x));
281 boundsMax.setY(std::max(boundsMax.y(), y));
282 boundsMax.setZ(std::max(boundsMax.z(), z));
283 }
284 }
285
286 // Loop through rings and segments to generate the index data
287 for (int i = 0; i < rings; ++i) {
288 for (int j = 0; j < segments; ++j) {
289 uint16_t a = static_cast<uint16_t>(i * (segments + 1) + j);
290 uint16_t b = static_cast<uint16_t>(a + segments + 1);
291 uint16_t c = static_cast<uint16_t>(b + 1);
292 uint16_t d = static_cast<uint16_t>(a + 1);
293
294 // First triangle (a, d, b)
295 *indexPtr++ = a;
296 *indexPtr++ = d;
297 *indexPtr++ = b;
298
299 // Second triangle (b, d, c)
300 *indexPtr++ = b;
301 *indexPtr++ = d;
302 *indexPtr++ = c;
303 }
304 }
305
306 geometryData.boundsMin = boundsMin;
307 geometryData.boundsMax = boundsMax;
308
309 // Return the geometry data
310 return geometryData;
311}
312
313#if QT_CONFIG(concurrent)
314void SphereGeometry::generateSphereGeometryAsync(QPromise<SphereGeometry::GeometryData> &promise,
315 float radius,
316 int rings,
317 int segments)
318{
319 auto output = generateSphereGeometry(radius, rings, segments);
320 promise.addResult(output);
321}
322#endif
323
324QT_END_NAMESPACE