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