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
cylindergeometry.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
6#include <limits>
7
8
9#if QT_CONFIG(concurrent)
10#include <QtConcurrentRun>
11#endif
12
13
14QT_BEGIN_NAMESPACE
15
16namespace {
17
18void createCylinderSidesVertices(float *&verticesPtr,
19 int rings,
20 int slices,
21 double topRadius,
22 double bottomRadius,
23 double length)
24{
25 const float dY = length / static_cast<float>(rings - 1);
26 const float dTheta = (M_PI * 2) / static_cast<float>(slices);
27
28 for (int ring = 0; ring < rings; ++ring) {
29 const float y = -length / 2.0f + static_cast<float>(ring) * dY;
30
31 const float t = (y + length / 2) / length;
32 const float radius = (bottomRadius * (1 - t)) + (t * topRadius);
33
34 for (int slice = 0; slice <= slices; ++slice) {
35 const float theta = static_cast<float>(slice) * dTheta;
36 const float ta = std::tan((M_PI/2) - std::atan(length / (bottomRadius - topRadius)));
37 const float ct = std::cos(theta);
38 const float st = std::sin(theta);
39
40 // Position
41 *verticesPtr++ = radius * ct;
42 *verticesPtr++ = y;
43 *verticesPtr++ = radius * st;
44
45 // UV Coordinates
46 float v = 0.5f + (y + length / 2.0f) / length / 2.0f;
47 *verticesPtr++ = static_cast<float>(slice) / static_cast<float>(slices); // U-coordinate (theta / 2π)
48 *verticesPtr++ = v; // V-coordinate mapped between [0.5, 1]
49
50 // Normal
51 QVector3D n(ct, ta, st);
52 n.normalize();
53 *verticesPtr++ = n.x();
54 *verticesPtr++ = n.y();
55 *verticesPtr++ = n.z();
56 }
57 }
58}
59
60void createCylinderSidesIndices(quint16 *&indicesPtr, int rings, int slices)
61{
62 for (int ring = 0; ring < rings-1; ++ring) {
63 const int ringIndexStart = ring * (slices + 1);
64 const int nextRingIndexStart = (ring + 1) * (slices + 1);
65
66 for (int slice = 0; slice <= slices; ++slice) {
67 if (slice == slices)
68 continue;
69
70 const int nextSlice = slice + 1;
71
72 *indicesPtr++ = (ringIndexStart + slice);
73 *indicesPtr++ = (nextRingIndexStart + slice);
74 *indicesPtr++ = (ringIndexStart + nextSlice);
75 *indicesPtr++ = (ringIndexStart + nextSlice);
76 *indicesPtr++ = (nextRingIndexStart + slice);
77 *indicesPtr++ = (nextRingIndexStart + nextSlice);
78 }
79 }
80}
81
82void createCylinderDiscVertices(float *&verticesPtr,
83 int slices,
84 double topRadius,
85 double bottomRadius,
86 double length,
87 double yPosition)
88{
89 const float dTheta = (M_PI * 2) / static_cast<float>(slices);
90 const double yNormal = (yPosition < 0.0f) ? -1.0f : 1.0f;
91
92 *verticesPtr++ = 0.0f;
93 *verticesPtr++ = yPosition;
94 *verticesPtr++ = 0.0f;
95
96 if (yPosition < 0.0f) {
97 // Bottom Cap
98 *verticesPtr++ = 0.75f; // Center vertex UV (bottom cap)
99 *verticesPtr++ = 0.25f;
100 } else {
101 // Top Cap
102 *verticesPtr++ = 0.25f; // Center vertex UV (top cap)
103 *verticesPtr++ = 0.25f;
104 }
105
106
107 *verticesPtr++ = 0.0f;
108 *verticesPtr++ = yNormal;
109 *verticesPtr++ = 0.0f;
110
111
112 for (int slice = 0; slice <= slices; ++slice)
113 {
114 const float theta = static_cast<float>(slice) * dTheta;
115 const float ct = std::cos(theta);
116 const float st = std::sin(theta);
117
118 const float t = (yPosition + length / 2) / length;
119 const float radius = (bottomRadius * (1 - t)) + (t * topRadius);
120
121 // Position
122 *verticesPtr++ = radius * ct;
123 *verticesPtr++ = yPosition;
124 *verticesPtr++ = radius * st;
125
126 // UV Coordinates
127 if (yPosition < 0.0f) {
128 // Bottom cap UVs: Map to range (0, 0.5) to (1, 1)
129 *verticesPtr++ = 0.75f + 0.25f * ct;
130 *verticesPtr++ = 0.25f + 0.25f * st;
131 } else {
132 // Top cap UVs: Map to range (0, 0.5)
133 *verticesPtr++ = 0.25f + 0.25f * ct;
134 *verticesPtr++ = 0.25f + 0.25f * -st;
135 }
136
137 // Normal
138 *verticesPtr++ = 0.0f;
139 *verticesPtr++ = yNormal;
140 *verticesPtr++ = 0.0f;
141 }
142}
143
144void createCylinderDiscIndices(quint16 *&indicesPtr,
145 int discCenterIndex,
146 int slices,
147 bool isTopCap)
148{
149 if ( !isTopCap ) {
150 for ( int i = slices - 1 ; i >= 0 ; --i )
151 {
152 if ( i != 0 ) {
153 *indicesPtr++ = discCenterIndex;
154 *indicesPtr++ = discCenterIndex + i + 1;
155 *indicesPtr++ = discCenterIndex + i;
156 } else {
157 *indicesPtr++ = discCenterIndex;
158 *indicesPtr++ = discCenterIndex + i + 1;
159 *indicesPtr++ = discCenterIndex + slices;
160 }
161 }
162 } else {
163 for ( int i = 0 ; i < slices; ++i )
164 {
165 if ( i != slices - 1 ) {
166 *indicesPtr++ = discCenterIndex;
167 *indicesPtr++ = discCenterIndex + i + 1;
168 *indicesPtr++ = discCenterIndex + i + 2;
169 } else {
170 *indicesPtr++ = discCenterIndex;
171 *indicesPtr++ = discCenterIndex + i + 1;
172 *indicesPtr++ = discCenterIndex + 1;
173 }
174 }
175 }
176}
177
178} // namespace
179
180/*!
181 \qmltype CylinderGeometry
182 \inqmlmodule QtQuick3D.Helpers
183 \inherits Geometry
184 \since 6.9
185 \brief Provides geometry for a cylinder.
186
187 CylinderGeometry is a geometry type that generates a cylinder shape. The cylinder's
188 shape is defined by it's radius and length properties. The topology of the cylinder
189 is defined by the number of segments and rings.
190*/
191
192/*!
193 \qmlproperty real CylinderGeometry::radius
194
195 This property holds the radius of the cylinder. This property must be greater than 0
196 to generate a valid cylinder.
197*/
198
199/*!
200 \qmlproperty real CylinderGeometry::length
201
202 This property holds the length of the cylinder. This property must be greather than 0
203 to generate a valid cylinder.
204*/
205
206/*!
207 \qmlproperty int CylinderGeometry::segments
208
209 This property holds the number of segments in the cylinder. The segments are the
210 radial divisions of the cylinder. The minimum number of segments is 3.
211*/
212
213/*!
214 \qmlproperty int CylinderGeometry::rings
215
216 This property holds the number of rings in the cylinder. The rings are the lengthwise
217 divisions of the cylinder. The minimum number of rings is 0.
218*/
219
220/*!
221 \qmlproperty bool CylinderGeometry::asynchronous
222
223 This property holds whether the geometry generation should be asynchronous.
224*/
225
226/*!
227 \qmlproperty bool CylinderGeometry::status
228 \readonly
229
230 This property holds the status of the geometry generation when asynchronous is true.
231
232 \value CylinderGeometry.Null The geometry generation has not started
233 \value CylinderGeometry.Ready The geometry generation is complete.
234 \value CylinderGeometry.Loading The geometry generation is in progress.
235 \value CylinderGeometry.Error The geometry generation failed.
236*/
237
238CylinderGeometry::CylinderGeometry(QQuick3DObject *parent)
239 : QQuick3DGeometry(parent)
240{
241#if QT_CONFIG(concurrent)
242 connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished, this, &CylinderGeometry::requestFinished);
243#endif
244 scheduleGeometryUpdate();
245}
246
247CylinderGeometry::~CylinderGeometry()
248{
249
250}
251
252float CylinderGeometry::radius() const
253{
254 return m_radius;
255}
256
257void CylinderGeometry::setRadius(float newRadius)
258{
259 if (qFuzzyCompare(m_radius, newRadius))
260 return;
261 m_radius = newRadius;
262 emit radiusChanged();
263 scheduleGeometryUpdate();
264}
265
266float CylinderGeometry::length() const
267{
268 return m_length;
269}
270
271void CylinderGeometry::setLength(float newLength)
272{
273 if (qFuzzyCompare(m_length, newLength))
274 return;
275 m_length = newLength;
276 emit lengthChanged();
277 scheduleGeometryUpdate();
278}
279
280int CylinderGeometry::rings() const
281{
282 return m_rings;
283}
284
285void CylinderGeometry::setRings(int newRings)
286{
287 if (m_rings == newRings)
288 return;
289 m_rings = newRings;
290 emit ringsChanged();
291 scheduleGeometryUpdate();
292}
293
294int CylinderGeometry::segments() const
295{
296 return m_segments;
297}
298
299void CylinderGeometry::setSegments(int newSegments)
300{
301 if (m_segments == newSegments)
302 return;
303 m_segments = newSegments;
304 emit segmentsChanged();
305 scheduleGeometryUpdate();
306}
307
308bool CylinderGeometry::asynchronous() const
309{
310 return m_asynchronous;
311}
312
313void CylinderGeometry::setAsynchronous(bool newAsynchronous)
314{
315 if (m_asynchronous == newAsynchronous)
316 return;
317 m_asynchronous = newAsynchronous;
318 emit asynchronousChanged();
319}
320
321CylinderGeometry::Status CylinderGeometry::status() const
322{
323 return m_status;
324}
325
326void CylinderGeometry::doUpdateGeometry()
327{
328 // reset the flag since we are processing the update
329 m_geometryUpdateRequested = false;
330
331#if QT_CONFIG(concurrent)
332 if (m_geometryDataFuture.isRunning()) {
333 m_pendingAsyncUpdate = true;
334 return;
335 }
336#endif
337
338 // Check validity of the geometry parameters
339 if (m_radius <= 0 || m_length <= 0 || m_rings < 0 || m_segments < 3) {
340 clear();
341 update();
342 return;
343 }
344
345#if QT_CONFIG(concurrent)
346 if (m_asynchronous) {
347 m_geometryDataFuture = QtConcurrent::run(generateCylinderGeometryAsync,
348 m_radius,
349 m_length,
350 m_rings,
351 m_segments);
352 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
353 m_status = Status::Loading;
354 Q_EMIT statusChanged();
355 } else {
356#else
357 {
358
359#endif // QT_CONFIG(concurrent)
360 updateGeometry(generateCylinderGeometry(m_radius, m_length, m_rings, m_segments));
361 }
362}
363
364void CylinderGeometry::requestFinished()
365{
366#if QT_CONFIG(concurrent)
367 const auto output = m_geometryDataFuture.takeResult();
368 updateGeometry(output);
369#endif
370}
371
372void CylinderGeometry::scheduleGeometryUpdate()
373{
374 if (!m_geometryUpdateRequested) {
375 QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
376 m_geometryUpdateRequested = true;
377 }
378}
379
380void CylinderGeometry::updateGeometry(const GeometryData &geometryData)
381{
382 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
383 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
384 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
385 0,
386 QQuick3DGeometry::Attribute::F32Type);
387 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
388 3 * sizeof(float),
389 QQuick3DGeometry::Attribute::F32Type);
390 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
391 5 * sizeof(float),
392 QQuick3DGeometry::Attribute::F32Type);
393 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
394 0,
395 QQuick3DGeometry::Attribute::U16Type);
396
397 setBounds(geometryData.boundsMin, geometryData.boundsMax);
398 setVertexData(geometryData.vertexData);
399 setIndexData(geometryData.indexData);
400
401 // If the geometry update was requested while the geometry was being generated asynchronously,
402 // we need to schedule another geometry update now that the geometry is ready.
403 if (m_pendingAsyncUpdate) {
404 m_pendingAsyncUpdate = false;
405 scheduleGeometryUpdate();
406 } else {
407 m_status = Status::Ready;
408 Q_EMIT statusChanged();
409 }
410 update();
411}
412
413CylinderGeometry::GeometryData CylinderGeometry::generateCylinderGeometry(float radius, float length, int rings, int slices)
414{
415 GeometryData geomData;
416
417 rings += 2; // Add two extra rings for the top and bottom caps
418
419 // Cap count: if top and/or bottom radius is greater than zero
420 int capCount = 2;
421
422 // Compute total number of vertices and indices
423 int totalFaces = (slices * 2) * (rings - 1) + slices * capCount;
424 int totalVertices = (slices + 1) * rings + capCount * (slices + 2);
425 int totalIndices = totalFaces * 3;
426
427 // Resize QByteArray to hold vertex and index data
428 geomData.vertexData.resize(totalVertices * 8 * sizeof(float)); // Each vertex has 8 attributes (3 position, 2 UV, 3 normal)
429 geomData.indexData.resize(totalIndices * sizeof(quint16)); // Each index is a 16-bit unsigned integer
430
431 // Get pointers to raw data in QByteArray
432 float* verticesPtr = reinterpret_cast<float*>(geomData.vertexData.data());
433 quint16* indicesPtr = reinterpret_cast<quint16*>(geomData.indexData.data());
434
435 // Cylinder vertices and indices
436
437 createCylinderSidesVertices(verticesPtr, rings, slices, radius, radius, length);
438 createCylinderSidesIndices(indicesPtr, rings, slices);
439 int bottomCenterIndex = rings * (slices + 1);
440 createCylinderDiscVertices(verticesPtr, slices, radius, radius, length, -length / 2);
441 createCylinderDiscIndices(indicesPtr, bottomCenterIndex, slices, true);
442 int topCenterIndex = radius > 0 ? rings * (slices + 1) + (slices + 2) : rings * (slices + 1);
443 createCylinderDiscVertices(verticesPtr, slices, radius, radius, length, length / 2);
444 createCylinderDiscIndices(indicesPtr, topCenterIndex, slices, false);
445
446
447 // Calculate bounding box (min and max)
448 float* vertexData = reinterpret_cast<float*>(geomData.vertexData.data());
449 QVector3D boundsMin(std::numeric_limits<float>::max(),
450 std::numeric_limits<float>::max(),
451 std::numeric_limits<float>::max());
452 QVector3D boundsMax(std::numeric_limits<float>::lowest(),
453 std::numeric_limits<float>::lowest(),
454 std::numeric_limits<float>::lowest());
455 for (int i = 0; i < totalVertices; ++i) {
456 QVector3D pos(vertexData[i * 8], vertexData[i * 8 + 1], vertexData[i * 8 + 2]);
457 boundsMin.setX(qMin(boundsMin.x(), pos.x()));
458 boundsMin.setY(qMin(boundsMin.y(), pos.y()));
459 boundsMin.setZ(qMin(boundsMin.z(), pos.z()));
460
461 boundsMax.setX(qMax(boundsMax.x(), pos.x()));
462 boundsMax.setY(qMax(boundsMax.y(), pos.y()));
463 boundsMax.setZ(qMax(boundsMax.z(), pos.z()));
464 }
465 geomData.boundsMin = boundsMin;
466 geomData.boundsMax = boundsMax;
467
468 return geomData;
469}
470
471#if QT_CONFIG(concurrent)
472void CylinderGeometry::generateCylinderGeometryAsync(QPromise<CylinderGeometry::GeometryData> &promise,
473 float radius,
474 float length,
475 int rings,
476 int segments)
477{
478 auto output = generateCylinderGeometry(radius, length, rings, segments);
479 promise.addResult(output);
480}
481#endif
482
483QT_END_NAMESPACE
void createCylinderSidesIndices(quint16 *&indicesPtr, int rings, int slices)
void createCylinderDiscVertices(float *&verticesPtr, int slices, double topRadius, double bottomRadius, double length, double yPosition)
void createCylinderSidesVertices(float *&verticesPtr, int rings, int slices, double topRadius, double bottomRadius, double length)
void createCylinderDiscIndices(quint16 *&indicesPtr, int discCenterIndex, int slices, bool isTopCap)