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