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 clear();
385 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
386 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
387 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
388 0,
389 QQuick3DGeometry::Attribute::F32Type);
390 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
391 3 * sizeof(float),
392 QQuick3DGeometry::Attribute::F32Type);
393 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
394 5 * sizeof(float),
395 QQuick3DGeometry::Attribute::F32Type);
396 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
397 0,
398 QQuick3DGeometry::Attribute::U16Type);
399
400 setBounds(geometryData.boundsMin, geometryData.boundsMax);
401 setVertexData(geometryData.vertexData);
402 setIndexData(geometryData.indexData);
403
404 // If the geometry update was requested while the geometry was being generated asynchronously,
405 // we need to schedule another geometry update now that the geometry is ready.
406 if (m_pendingAsyncUpdate) {
407 m_pendingAsyncUpdate = false;
408 scheduleGeometryUpdate();
409 } else {
410 m_status = Status::Ready;
411 Q_EMIT statusChanged();
412 }
413 update();
414 emit geometryChanged();
415}
416
417CylinderGeometry::GeometryData CylinderGeometry::generateCylinderGeometry(float radius, float length, int rings, int slices)
418{
419 GeometryData geomData;
420
421 rings += 2; // Add two extra rings for the top and bottom caps
422
423 // Cap count: if top and/or bottom radius is greater than zero
424 int capCount = 2;
425
426 // Compute total number of vertices and indices
427 int totalFaces = (slices * 2) * (rings - 1) + slices * capCount;
428 int totalVertices = (slices + 1) * rings + capCount * (slices + 2);
429 int totalIndices = totalFaces * 3;
430
431 // Resize QByteArray to hold vertex and index data
432 geomData.vertexData.resize(totalVertices * 8 * sizeof(float)); // Each vertex has 8 attributes (3 position, 2 UV, 3 normal)
433 geomData.indexData.resize(totalIndices * sizeof(quint16)); // Each index is a 16-bit unsigned integer
434
435 // Get pointers to raw data in QByteArray
436 float* verticesPtr = reinterpret_cast<float*>(geomData.vertexData.data());
437 quint16* indicesPtr = reinterpret_cast<quint16*>(geomData.indexData.data());
438
439 // Cylinder vertices and indices
440
441 createCylinderSidesVertices(verticesPtr, rings, slices, radius, radius, length);
442 createCylinderSidesIndices(indicesPtr, rings, slices);
443 int bottomCenterIndex = rings * (slices + 1);
444 createCylinderDiscVertices(verticesPtr, slices, radius, radius, length, -length / 2);
445 createCylinderDiscIndices(indicesPtr, bottomCenterIndex, slices, true);
446 int topCenterIndex = radius > 0 ? rings * (slices + 1) + (slices + 2) : rings * (slices + 1);
447 createCylinderDiscVertices(verticesPtr, slices, radius, radius, length, length / 2);
448 createCylinderDiscIndices(indicesPtr, topCenterIndex, slices, false);
449
450
451 // Calculate bounding box (min and max)
452 float* vertexData = reinterpret_cast<float*>(geomData.vertexData.data());
453 QVector3D boundsMin(std::numeric_limits<float>::max(),
454 std::numeric_limits<float>::max(),
455 std::numeric_limits<float>::max());
456 QVector3D boundsMax(std::numeric_limits<float>::lowest(),
457 std::numeric_limits<float>::lowest(),
458 std::numeric_limits<float>::lowest());
459 for (int i = 0; i < totalVertices; ++i) {
460 QVector3D pos(vertexData[i * 8], vertexData[i * 8 + 1], vertexData[i * 8 + 2]);
461 boundsMin.setX(qMin(boundsMin.x(), pos.x()));
462 boundsMin.setY(qMin(boundsMin.y(), pos.y()));
463 boundsMin.setZ(qMin(boundsMin.z(), pos.z()));
464
465 boundsMax.setX(qMax(boundsMax.x(), pos.x()));
466 boundsMax.setY(qMax(boundsMax.y(), pos.y()));
467 boundsMax.setZ(qMax(boundsMax.z(), pos.z()));
468 }
469 geomData.boundsMin = boundsMin;
470 geomData.boundsMax = boundsMax;
471
472 return geomData;
473}
474
475#if QT_CONFIG(concurrent)
476void CylinderGeometry::generateCylinderGeometryAsync(QPromise<CylinderGeometry::GeometryData> &promise,
477 float radius,
478 float length,
479 int rings,
480 int segments)
481{
482 auto output = generateCylinderGeometry(radius, length, rings, segments);
483 promise.addResult(output);
484}
485#endif
486
487QT_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)