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 clear();
431 setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
432 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
433 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
434 0,
435 QQuick3DGeometry::Attribute::F32Type);
436 addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
437 3 * sizeof(float),
438 QQuick3DGeometry::Attribute::F32Type);
439 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
440 5 * sizeof(float),
441 QQuick3DGeometry::Attribute::F32Type);
442 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
443 0,
444 QQuick3DGeometry::Attribute::U16Type);
445
446 setBounds(geometryData.boundsMin, geometryData.boundsMax);
447 setVertexData(geometryData.vertexData);
448 setIndexData(geometryData.indexData);
449
450 // If the geometry update was requested while the geometry was being generated asynchronously,
451 // we need to schedule another geometry update now that the geometry is ready.
452 if (m_pendingAsyncUpdate) {
453 m_pendingAsyncUpdate = false;
454 scheduleGeometryUpdate();
455 } else {
456 m_status = Status::Ready;
457 Q_EMIT statusChanged();
458 }
459 update();
460 emit geometryChanged();
461}
462
463ConeGeometry::GeometryData ConeGeometry::generateConeGeometry(float topRadius, float bottomRadius, float length, int rings, int slices)
464{
465 GeometryData geomData;
466
467 rings += 2; // Add two extra rings for the top and bottom caps
468
469 // Cap count: if top and/or bottom radius is greater than zero
470 int capCount = (topRadius > 0 ? 1 : 0) + (bottomRadius > 0 ? 1 : 0);
471
472 // Compute total number of vertices and indices
473 int totalFaces = (slices * 2) * (rings - 1) + slices * capCount;
474 int totalVertices = (slices + 1) * rings + capCount * (slices + 2);
475 int totalIndices = totalFaces * 3;
476
477 // Resize QByteArray to hold vertex and index data
478 geomData.vertexData.resize(totalVertices * 8 * sizeof(float)); // Each vertex has 8 attributes (3 position, 2 UV, 3 normal)
479 geomData.indexData.resize(totalIndices * sizeof(quint16)); // Each index is a 16-bit unsigned integer
480
481 // Get pointers to raw data in QByteArray
482 float* verticesPtr = reinterpret_cast<float*>(geomData.vertexData.data());
483 quint16* indicesPtr = reinterpret_cast<quint16*>(geomData.indexData.data());
484
485 // Truncated Cone
486 if (bottomRadius > 0 && topRadius > 0) {
487 createConeSidesVertices(verticesPtr, rings, slices, topRadius, bottomRadius, length);
488 createConeSidesIndices(indicesPtr, rings, slices);
489 int bottomCenterIndex = rings * (slices + 1);
490 createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, -length / 2);
491 createDiscIndices(indicesPtr, bottomCenterIndex, slices, true);
492 int topCenterIndex = bottomRadius > 0 ? rings * (slices + 1) + (slices + 2) : rings * (slices + 1);
493 createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, length / 2);
494 createDiscIndices(indicesPtr, topCenterIndex, slices, false);
495 } else {
496 // Cone Facing up
497 if (topRadius > 0) {
498 // Create the Cone Shape
499 createConeSidesVertices(verticesPtr, rings, slices, topRadius, bottomRadius, length);
500 createConeSidesIndices(indicesPtr, rings, slices);
501
502 // Create flat disk at the bottom of the cone
503 int topCenterIndex = rings * (slices + 1);
504 createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, length / 2);
505 createDiscIndices(indicesPtr, topCenterIndex, slices, false);
506 }
507
508 // Cone Facing down
509 if (bottomRadius > 0) {
510 // Create the Cone shape
511 createConeSidesVertices(verticesPtr, rings, slices, topRadius, bottomRadius, length);
512 createConeSidesIndices(indicesPtr, rings, slices);
513
514 // Create flat disk at the top of the cone
515 int bottomCenterIndex = rings * (slices + 1);
516 createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, -length / 2);
517 createDiscIndices(indicesPtr, bottomCenterIndex, slices, true);
518 }
519 }
520
521 // Calculate bounding box (min and max)
522 float* vertexData = reinterpret_cast<float*>(geomData.vertexData.data());
523 QVector3D boundsMin(std::numeric_limits<float>::max(),
524 std::numeric_limits<float>::max(),
525 std::numeric_limits<float>::max());
526 QVector3D boundsMax(std::numeric_limits<float>::lowest(),
527 std::numeric_limits<float>::lowest(),
528 std::numeric_limits<float>::lowest());
529 for (int i = 0; i < totalVertices; ++i) {
530 QVector3D pos(vertexData[i * 8], vertexData[i * 8 + 1], vertexData[i * 8 + 2]);
531 boundsMin.setX(qMin(boundsMin.x(), pos.x()));
532 boundsMin.setY(qMin(boundsMin.y(), pos.y()));
533 boundsMin.setZ(qMin(boundsMin.z(), pos.z()));
534
535 boundsMax.setX(qMax(boundsMax.x(), pos.x()));
536 boundsMax.setY(qMax(boundsMax.y(), pos.y()));
537 boundsMax.setZ(qMax(boundsMax.z(), pos.z()));
538 }
539 geomData.boundsMin = boundsMin;
540 geomData.boundsMax = boundsMax;
541
542 return geomData;
543}
544
545#if QT_CONFIG(concurrent)
546void ConeGeometry::generateConeGeometryAsync(QPromise<ConeGeometry::GeometryData> &promise,
547 float topRadius,
548 float bottomRadius,
549 float length,
550 int rings,
551 int segments)
552{
553 auto output = generateConeGeometry(topRadius, bottomRadius, length, rings, segments);
554 promise.addResult(output);
555}
556#endif
557
558QT_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)