| 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 | |
| 5 | #include "conegeometry_p.h" |
| 6 | #include <limits> |
| 7 | |
| 8 | #if QT_CONFIG(concurrent) |
| 9 | #include <QtConcurrentRun> |
| 10 | #endif |
| 11 | |
| 12 | QT_BEGIN_NAMESPACE |
| 13 | |
| 14 | namespace { |
| 15 | |
| 16 | void 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(x: (M_PI/2) - std::atan(x: length / (bottomRadius - topRadius))); |
| 40 | const float ct = std::cos(x: theta); |
| 41 | const float st = std::sin(x: 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 | |
| 87 | void 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 | |
| 109 | void 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(x: theta); |
| 143 | const float st = std::sin(x: 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 | |
| 171 | void 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 | |
| 269 | ConeGeometry::ConeGeometry(QQuick3DObject *parent) |
| 270 | : QQuick3DGeometry(parent) |
| 271 | { |
| 272 | #if QT_CONFIG(concurrent) |
| 273 | connect(sender: &m_geometryDataWatcher, signal: &QFutureWatcher<GeometryData>::finished, context: this, slot: &ConeGeometry::requestFinished); |
| 274 | #endif |
| 275 | scheduleGeometryUpdate(); |
| 276 | } |
| 277 | |
| 278 | ConeGeometry::~ConeGeometry() |
| 279 | { |
| 280 | |
| 281 | } |
| 282 | |
| 283 | float ConeGeometry::topRadius() const |
| 284 | { |
| 285 | return m_topRadius; |
| 286 | } |
| 287 | |
| 288 | void ConeGeometry::setTopRadius(float newTopRadius) |
| 289 | { |
| 290 | if (qFuzzyCompare(p1: m_topRadius, p2: newTopRadius)) |
| 291 | return; |
| 292 | m_topRadius = newTopRadius; |
| 293 | emit topRadiusChanged(); |
| 294 | scheduleGeometryUpdate(); |
| 295 | } |
| 296 | |
| 297 | float ConeGeometry::bottomRadius() const |
| 298 | { |
| 299 | return m_bottomRadius; |
| 300 | } |
| 301 | |
| 302 | void ConeGeometry::setBottomRadius(float newBottomRadius) |
| 303 | { |
| 304 | if (qFuzzyCompare(p1: m_bottomRadius, p2: newBottomRadius)) |
| 305 | return; |
| 306 | m_bottomRadius = newBottomRadius; |
| 307 | emit bottomRadiusChanged(); |
| 308 | scheduleGeometryUpdate(); |
| 309 | } |
| 310 | |
| 311 | float ConeGeometry::length() const |
| 312 | { |
| 313 | return m_length; |
| 314 | } |
| 315 | |
| 316 | void ConeGeometry::setLength(float newLength) |
| 317 | { |
| 318 | if (qFuzzyCompare(p1: m_length, p2: newLength)) |
| 319 | return; |
| 320 | m_length = newLength; |
| 321 | emit lengthChanged(); |
| 322 | scheduleGeometryUpdate(); |
| 323 | } |
| 324 | |
| 325 | int ConeGeometry::rings() const |
| 326 | { |
| 327 | return m_rings; |
| 328 | } |
| 329 | |
| 330 | void ConeGeometry::setRings(int newRings) |
| 331 | { |
| 332 | if (m_rings == newRings) |
| 333 | return; |
| 334 | m_rings = newRings; |
| 335 | emit ringsChanged(); |
| 336 | scheduleGeometryUpdate(); |
| 337 | } |
| 338 | |
| 339 | int ConeGeometry::segments() const |
| 340 | { |
| 341 | return m_segments; |
| 342 | } |
| 343 | |
| 344 | void ConeGeometry::setSegments(int newSegments) |
| 345 | { |
| 346 | if (m_segments == newSegments) |
| 347 | return; |
| 348 | m_segments = newSegments; |
| 349 | emit segmentsChanged(); |
| 350 | scheduleGeometryUpdate(); |
| 351 | } |
| 352 | |
| 353 | bool ConeGeometry::asynchronous() const |
| 354 | { |
| 355 | return m_asynchronous; |
| 356 | } |
| 357 | |
| 358 | void ConeGeometry::setAsynchronous(bool newAsynchronous) |
| 359 | { |
| 360 | if (m_asynchronous == newAsynchronous) |
| 361 | return; |
| 362 | m_asynchronous = newAsynchronous; |
| 363 | emit asynchronousChanged(); |
| 364 | } |
| 365 | |
| 366 | ConeGeometry::Status ConeGeometry::status() const |
| 367 | { |
| 368 | return m_status; |
| 369 | } |
| 370 | |
| 371 | void 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(f&: generateConeGeometryAsync, |
| 393 | args&: m_topRadius, |
| 394 | args&: m_bottomRadius, |
| 395 | args&: m_length, |
| 396 | args&: m_rings, |
| 397 | args&: 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(geometryData: generateConeGeometry(topRadius: m_topRadius, bottomRadius: m_bottomRadius, length: m_length, rings: m_rings, slices: m_segments)); |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | void ConeGeometry::requestFinished() |
| 411 | { |
| 412 | #if QT_CONFIG(concurrent) |
| 413 | const auto output = m_geometryDataFuture.takeResult(); |
| 414 | updateGeometry(geometryData: output); |
| 415 | #endif |
| 416 | } |
| 417 | |
| 418 | void ConeGeometry::scheduleGeometryUpdate() |
| 419 | { |
| 420 | if (!m_geometryUpdateRequested) { |
| 421 | QMetaObject::invokeMethod(obj: this, member: "doUpdateGeometry" , c: Qt::QueuedConnection); |
| 422 | m_geometryUpdateRequested = true; |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | void 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(semantic: QQuick3DGeometry::Attribute::PositionSemantic, |
| 431 | offset: 0, |
| 432 | componentType: QQuick3DGeometry::Attribute::F32Type); |
| 433 | addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic, |
| 434 | offset: 3 * sizeof(float), |
| 435 | componentType: QQuick3DGeometry::Attribute::F32Type); |
| 436 | addAttribute(semantic: QQuick3DGeometry::Attribute::NormalSemantic, |
| 437 | offset: 5 * sizeof(float), |
| 438 | componentType: QQuick3DGeometry::Attribute::F32Type); |
| 439 | addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic, |
| 440 | offset: 0, |
| 441 | componentType: QQuick3DGeometry::Attribute::U16Type); |
| 442 | |
| 443 | setBounds(min: geometryData.boundsMin, max: 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 | |
| 459 | ConeGeometry::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(size: totalVertices * 8 * sizeof(float)); // Each vertex has 8 attributes (3 position, 2 UV, 3 normal) |
| 475 | geomData.indexData.resize(size: 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, yPosition: -length / 2); |
| 487 | createDiscIndices(indicesPtr, discCenterIndex: bottomCenterIndex, slices, isTopCap: true); |
| 488 | int topCenterIndex = bottomRadius > 0 ? rings * (slices + 1) + (slices + 2) : rings * (slices + 1); |
| 489 | createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, yPosition: length / 2); |
| 490 | createDiscIndices(indicesPtr, discCenterIndex: topCenterIndex, slices, isTopCap: 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, yPosition: length / 2); |
| 501 | createDiscIndices(indicesPtr, discCenterIndex: topCenterIndex, slices, isTopCap: 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, yPosition: -length / 2); |
| 513 | createDiscIndices(indicesPtr, discCenterIndex: bottomCenterIndex, slices, isTopCap: 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(a: boundsMin.x(), b: pos.x())); |
| 528 | boundsMin.setY(qMin(a: boundsMin.y(), b: pos.y())); |
| 529 | boundsMin.setZ(qMin(a: boundsMin.z(), b: pos.z())); |
| 530 | |
| 531 | boundsMax.setX(qMax(a: boundsMax.x(), b: pos.x())); |
| 532 | boundsMax.setY(qMax(a: boundsMax.y(), b: pos.y())); |
| 533 | boundsMax.setZ(qMax(a: boundsMax.z(), b: pos.z())); |
| 534 | } |
| 535 | geomData.boundsMin = boundsMin; |
| 536 | geomData.boundsMax = boundsMax; |
| 537 | |
| 538 | return geomData; |
| 539 | } |
| 540 | |
| 541 | #if QT_CONFIG(concurrent) |
| 542 | void 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, slices: segments); |
| 550 | promise.addResult(result&: output); |
| 551 | } |
| 552 | #endif |
| 553 | |
| 554 | QT_END_NAMESPACE |
| 555 | |