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