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