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