Revision control
++ // Copyright (C) 2024 The Qt Company Ltd.
++ // Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "conegeometry_p.h"
++ #include <limits>
++
++ #if QT_CONFIG(concurrent)
++ #include <QtConcurrentRun>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ namespace {
++
++ void createConeSidesVertices(float *&verticesPtr,
++ int rings,
++ int slices,
++ double topRadius,
++ double bottomRadius,
++ double length)
++ {
++ const float dY = length / static_cast<float>(rings - 1);
++ const float dTheta = (M_PI * 2) / static_cast<float>(slices);
++
++ // Determine the cone type
++ bool isTruncatedCone = (topRadius > 0 && bottomRadius > 0);
++ bool isUpwardCone = (topRadius > 0 && bottomRadius == 0);
++ bool isDownwardCone = (topRadius == 0 && bottomRadius > 0);
++
++ for (int ring = 0; ring < rings; ++ring) {
++ const float y = -length / 2.0f + static_cast<float>(ring) * dY;
++
++ const float t = (y + length / 2) / length;
++ const float radius = (bottomRadius * (1 - t)) + (t * topRadius);
++
++ for (int slice = 0; slice <= slices; ++slice) {
++ const float theta = static_cast<float>(slice) * dTheta;
++ const float ta = std::tan((M_PI/2) - std::atan(length / (bottomRadius - topRadius)));
++ const float ct = std::cos(theta);
++ const float st = std::sin(theta);
++
++ // Position
++ *verticesPtr++ = radius * ct;
++ *verticesPtr++ = y;
++ *verticesPtr++ = radius * st;
++
++ // UV Coordinates
++ if (isTruncatedCone) {
++ // Case 1: Truncated Cone
++ // Map 'y' to V coordinate range [0.5, 1] (top to bottom of truncated cone)
++ float v = 0.5f + (y + length / 2.0f) / length / 2.0f;
++ *verticesPtr++ = static_cast<float>(slice) / static_cast<float>(slices); // U-coordinate (theta / 2π)
++ *verticesPtr++ = v; // V-coordinate mapped between [0.5, 1]
++
++ } else if (isDownwardCone) {
++ // Case 2: Downward Facing Cone
++ // The top ring (ring 0) should be at UV coordinate (0.25, 0.25)
++ // We want to map the top ring to a circle around this point
++ float rUV = 1.0f - static_cast<float>(ring) / static_cast<float>(rings - 1);
++ float u = 0.25f + rUV * ct * 0.25f; // Mapping to a circle around (0.25, 0.25)
++ float v = 0.25f + -rUV * st * 0.25f;
++ *verticesPtr++ = u;
++ *verticesPtr++ = v;
++
++ } else if (isUpwardCone) {
++ // Case 3: Upward Facing Cone
++ // The bottom ring (ring 0) should be at UV coordinate (0.75, 0.25)
++ // We want to map the bottom ring to a circle around this point
++ float rUV = static_cast<float>(ring) / static_cast<float>(rings - 1); // Normalized distance from center
++ float u = 0.75f + rUV * ct * 0.25f; // Mapping to a circle around (0.75, 0.25)
++ float v = 0.25f + rUV * st * 0.25f;
++ *verticesPtr++ = u;
++ *verticesPtr++ = v;
++ }
++
++ // Normal
++ QVector3D n(ct, ta, st);
++ n.normalize();
++ *verticesPtr++ = n.x();
++ *verticesPtr++ = n.y();
++ *verticesPtr++ = n.z();
++ }
++ }
++ }
++
++ void createConeSidesIndices(quint16 *&indicesPtr, int rings, int slices)
++ {
++ for (int ring = 0; ring < rings-1; ++ring) {
++ const int ringIndexStart = ring * (slices + 1);
++ const int nextRingIndexStart = (ring + 1) * (slices + 1);
++
++ for (int slice = 0; slice <= slices; ++slice) {
++ if (slice == slices)
++ continue;
++
++ const int nextSlice = slice + 1;
++
++ *indicesPtr++ = (ringIndexStart + slice);
++ *indicesPtr++ = (nextRingIndexStart + slice);
++ *indicesPtr++ = (ringIndexStart + nextSlice);
++ *indicesPtr++ = (ringIndexStart + nextSlice);
++ *indicesPtr++ = (nextRingIndexStart + slice);
++ *indicesPtr++ = (nextRingIndexStart + nextSlice);
++ }
++ }
++ }
++
++ void createConeDiscVertices(float *&verticesPtr,
++ int slices,
++ double topRadius,
++ double bottomRadius,
++ double length,
++ double yPosition)
++ {
++ const float dTheta = (M_PI * 2) / static_cast<float>(slices);
++ const double yNormal = (yPosition < 0.0f) ? -1.0f : 1.0f;
++
++ *verticesPtr++ = 0.0f;
++ *verticesPtr++ = yPosition;
++ *verticesPtr++ = 0.0f;
++
++ if (yPosition < 0.0f) {
++ // Bottom Cap
++ *verticesPtr++ = 0.75f; // Center vertex UV (bottom cap)
++ *verticesPtr++ = 0.25f;
++ } else {
++ // Top Cap
++ *verticesPtr++ = 0.25f; // Center vertex UV (top cap)
++ *verticesPtr++ = 0.25f;
++ }
++
++
++ *verticesPtr++ = 0.0f;
++ *verticesPtr++ = yNormal;
++ *verticesPtr++ = 0.0f;
++
++
++ for (int slice = 0; slice <= slices; ++slice)
++ {
++ const float theta = static_cast<float>(slice) * dTheta;
++ const float ct = std::cos(theta);
++ const float st = std::sin(theta);
++
++ const float t = (yPosition + length / 2) / length;
++ const float radius = (bottomRadius * (1 - t)) + (t * topRadius);
++
++ // Position
++ *verticesPtr++ = radius * ct;
++ *verticesPtr++ = yPosition;
++ *verticesPtr++ = radius * st;
++
++ // UV Coordinates
++ if (yPosition < 0.0f) {
++ // Bottom cap UVs: Map to range (0, 0.5) to (1, 1)
++ *verticesPtr++ = 0.75f + 0.25f * ct;
++ *verticesPtr++ = 0.25f + 0.25f * st;
++ } else {
++ // Top cap UVs: Map to range (0, 0.5)
++ *verticesPtr++ = 0.25f + 0.25f * ct;
++ *verticesPtr++ = 0.25f + 0.25f * -st;
++ }
++
++ // Normal
++ *verticesPtr++ = 0.0f;
++ *verticesPtr++ = yNormal;
++ *verticesPtr++ = 0.0f;
++ }
++ }
++
++ void createDiscIndices(quint16 *&indicesPtr,
++ int discCenterIndex,
++ int slices,
++ bool isTopCap)
++ {
++ if ( !isTopCap ) {
++ for ( int i = slices - 1 ; i >= 0 ; --i )
++ {
++ if ( i != 0 ) {
++ *indicesPtr++ = discCenterIndex;
++ *indicesPtr++ = discCenterIndex + i + 1;
++ *indicesPtr++ = discCenterIndex + i;
++ } else {
++ *indicesPtr++ = discCenterIndex;
++ *indicesPtr++ = discCenterIndex + i + 1;
++ *indicesPtr++ = discCenterIndex + slices;
++ }
++ }
++ } else {
++ for ( int i = 0 ; i < slices; ++i )
++ {
++ if ( i != slices - 1 ) {
++ *indicesPtr++ = discCenterIndex;
++ *indicesPtr++ = discCenterIndex + i + 1;
++ *indicesPtr++ = discCenterIndex + i + 2;
++ } else {
++ *indicesPtr++ = discCenterIndex;
++ *indicesPtr++ = discCenterIndex + i + 1;
++ *indicesPtr++ = discCenterIndex + 1;
++ }
++ }
++ }
++ }
++
++ }
++
++ /*!
++ \qmltype ConeGeometry
++ \inqmlmodule QtQuick3D.Helpers
++ \inherits Geometry
++ \since 6.9
++ \brief Provides geometry for a cone.
++
++ ConeGeometry is a geometry type that represents a cone. The cone's size is
++ defined by its top radius, bottom radius, and length. The topology of the
++ cone is defined by the number of rings and segments.
++ */
++
++ /*!
++ \qmlproperty real ConeGeometry::topRadius
++ This property holds the top radius of the cone. This value must be 0 or greater.
++
++ \note topRadius and bottomRadius can not both be \c 0.0
++ */
++
++ /*!
++ \qmlproperty real ConeGeometry::bottomRadius
++ This property holds the bottom radius of the cone.
++
++ \note topRadius and bottomRadius can not both be \c 0.0
++ */
++
++ /*!
++ \qmlproperty real ConeGeometry::length
++ This property holds the length of the cone. This value must be greater than 0 to
++ generate a cone.
++ */
++
++ /*!
++ \qmlproperty int ConeGeometry::rings
++ This property holds the number of rings of the cone, which are vertical subdivisions
++ of the cone. The minimum value is 0, which means the cone has no vertical subdivisions.
++ */
++
++ /*!
++ \qmlproperty int ConeGeometry::segments
++ This property holds the number of segments of the cone. The minimum value is 3, and any value
++ lower will not generate any geometry.
++ */
++
++ /*!
++ \qmlproperty bool ConeGeometry::asynchronous
++
++ This property holds whether the geometry generation should be asynchronous.
++ */
++
++ /*!
++ \qmlproperty bool ConeGeometry::status
++ \readonly
++
++ This property holds the status of the geometry generation when asynchronous is true.
++
++ \value ConeGeometry.Null The geometry generation has not started
++ \value ConeGeometry.Ready The geometry generation is complete.
++ \value ConeGeometry.Loading The geometry generation is in progress.
++ \value ConeGeometry.Error The geometry generation failed.
++ */
++
++ ConeGeometry::ConeGeometry(QQuick3DObject *parent)
++ : QQuick3DGeometry(parent)
++ {
++ #if QT_CONFIG(concurrent)
++ connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished, this, &ConeGeometry::requestFinished);
++ #endif
++ scheduleGeometryUpdate();
++ }
++
++ ConeGeometry::~ConeGeometry()
++ {
++
++ }
++
++ float ConeGeometry::topRadius() const
++ {
++ return m_topRadius;
++ }
++
++ void ConeGeometry::setTopRadius(float newTopRadius)
++ {
++ if (qFuzzyCompare(m_topRadius, newTopRadius))
++ return;
++ m_topRadius = newTopRadius;
++ emit topRadiusChanged();
++ scheduleGeometryUpdate();
++ }
++
++ float ConeGeometry::bottomRadius() const
++ {
++ return m_bottomRadius;
++ }
++
++ void ConeGeometry::setBottomRadius(float newBottomRadius)
++ {
++ if (qFuzzyCompare(m_bottomRadius, newBottomRadius))
++ return;
++ m_bottomRadius = newBottomRadius;
++ emit bottomRadiusChanged();
++ scheduleGeometryUpdate();
++ }
++
++ float ConeGeometry::length() const
++ {
++ return m_length;
++ }
++
++ void ConeGeometry::setLength(float newLength)
++ {
++ if (qFuzzyCompare(m_length, newLength))
++ return;
++ m_length = newLength;
++ emit lengthChanged();
++ scheduleGeometryUpdate();
++ }
++
++ int ConeGeometry::rings() const
++ {
++ return m_rings;
++ }
++
++ void ConeGeometry::setRings(int newRings)
++ {
++ if (m_rings == newRings)
++ return;
++ m_rings = newRings;
++ emit ringsChanged();
++ scheduleGeometryUpdate();
++ }
++
++ int ConeGeometry::segments() const
++ {
++ return m_segments;
++ }
++
++ void ConeGeometry::setSegments(int newSegments)
++ {
++ if (m_segments == newSegments)
++ return;
++ m_segments = newSegments;
++ emit segmentsChanged();
++ scheduleGeometryUpdate();
++ }
++
++ bool ConeGeometry::asynchronous() const
++ {
++ return m_asynchronous;
++ }
++
++ void ConeGeometry::setAsynchronous(bool newAsynchronous)
++ {
++ if (m_asynchronous == newAsynchronous)
++ return;
++ m_asynchronous = newAsynchronous;
++ emit asynchronousChanged();
++ }
++
++ ConeGeometry::Status ConeGeometry::status() const
++ {
++ return m_status;
++ }
++
++ void ConeGeometry::doUpdateGeometry()
++ {
++ // reset the flag since we are processing the update
++ m_geometryUpdateRequested = false;
++
++ #if QT_CONFIG(concurrent)
++ if (m_geometryDataFuture.isRunning()) {
++ m_pendingAsyncUpdate = true;
++ return;
++ }
++ #endif
++
++ // Check validity of the geometry parameters
++ if (m_topRadius < 0 || m_bottomRadius < 0 || (m_topRadius <= 0 && m_bottomRadius <= 0) || m_length <= 0 || m_rings < 0 || m_segments < 3) {
++ clear();
++ update();
++ return;
++ }
++
++ #if QT_CONFIG(concurrent)
++ if (m_asynchronous) {
++ m_geometryDataFuture = QtConcurrent::run(generateConeGeometryAsync,
++ m_topRadius,
++ m_bottomRadius,
++ m_length,
++ m_rings,
++ m_segments);
++ m_geometryDataWatcher.setFuture(m_geometryDataFuture);
++ m_status = Status::Loading;
++ Q_EMIT statusChanged();
++ } else {
++ #else
++ {
++
++ #endif // QT_CONFIG(concurrent)
++ updateGeometry(generateConeGeometry(m_topRadius, m_bottomRadius, m_length, m_rings, m_segments));
++ }
++ }
++
++ void ConeGeometry::requestFinished()
++ {
++ #if QT_CONFIG(concurrent)
++ const auto output = m_geometryDataFuture.takeResult();
++ updateGeometry(output);
++ #endif
++ }
++
++ void ConeGeometry::scheduleGeometryUpdate()
++ {
++ if (!m_geometryUpdateRequested) {
++ QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
++ m_geometryUpdateRequested = true;
++ }
++ }
++
++ void ConeGeometry::updateGeometry(const GeometryData &geometryData)
++ {
++ clear();
++ setStride(sizeof(float) * 8); // 3 for position, 2 for uv0, 3 for normal
++ setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
++ addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
++ 0,
++ QQuick3DGeometry::Attribute::F32Type);
++ addAttribute(QQuick3DGeometry::Attribute::TexCoord0Semantic,
++ 3 * sizeof(float),
++ QQuick3DGeometry::Attribute::F32Type);
++ addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
++ 5 * sizeof(float),
++ QQuick3DGeometry::Attribute::F32Type);
++ addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
++ 0,
++ QQuick3DGeometry::Attribute::U16Type);
++
++ setBounds(geometryData.boundsMin, geometryData.boundsMax);
++ setVertexData(geometryData.vertexData);
++ setIndexData(geometryData.indexData);
++
++ // If the geometry update was requested while the geometry was being generated asynchronously,
++ // we need to schedule another geometry update now that the geometry is ready.
++ if (m_pendingAsyncUpdate) {
++ m_pendingAsyncUpdate = false;
++ scheduleGeometryUpdate();
++ } else {
++ m_status = Status::Ready;
++ Q_EMIT statusChanged();
++ }
++ update();
++ emit geometryChanged();
++ }
++
++ ConeGeometry::GeometryData ConeGeometry::generateConeGeometry(float topRadius, float bottomRadius, float length, int rings, int slices)
++ {
++ GeometryData geomData;
++
++ rings += 2; // Add two extra rings for the top and bottom caps
++
++ // Cap count: if top and/or bottom radius is greater than zero
++ int capCount = (topRadius > 0 ? 1 : 0) + (bottomRadius > 0 ? 1 : 0);
++
++ // Compute total number of vertices and indices
++ int totalFaces = (slices * 2) * (rings - 1) + slices * capCount;
++ int totalVertices = (slices + 1) * rings + capCount * (slices + 2);
++ int totalIndices = totalFaces * 3;
++
++ // Resize QByteArray to hold vertex and index data
++ geomData.vertexData.resize(totalVertices * 8 * sizeof(float)); // Each vertex has 8 attributes (3 position, 2 UV, 3 normal)
++ geomData.indexData.resize(totalIndices * sizeof(quint16)); // Each index is a 16-bit unsigned integer
++
++ // Get pointers to raw data in QByteArray
++ float* verticesPtr = reinterpret_cast<float*>(geomData.vertexData.data());
++ quint16* indicesPtr = reinterpret_cast<quint16*>(geomData.indexData.data());
++
++ // Truncated Cone
++ if (bottomRadius > 0 && topRadius > 0) {
++ createConeSidesVertices(verticesPtr, rings, slices, topRadius, bottomRadius, length);
++ createConeSidesIndices(indicesPtr, rings, slices);
++ int bottomCenterIndex = rings * (slices + 1);
++ createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, -length / 2);
++ createDiscIndices(indicesPtr, bottomCenterIndex, slices, true);
++ int topCenterIndex = bottomRadius > 0 ? rings * (slices + 1) + (slices + 2) : rings * (slices + 1);
++ createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, length / 2);
++ createDiscIndices(indicesPtr, topCenterIndex, slices, false);
++ } else {
++ // Cone Facing up
++ if (topRadius > 0) {
++ // Create the Cone Shape
++ createConeSidesVertices(verticesPtr, rings, slices, topRadius, bottomRadius, length);
++ createConeSidesIndices(indicesPtr, rings, slices);
++
++ // Create flat disk at the bottom of the cone
++ int topCenterIndex = rings * (slices + 1);
++ createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, length / 2);
++ createDiscIndices(indicesPtr, topCenterIndex, slices, false);
++ }
++
++ // Cone Facing down
++ if (bottomRadius > 0) {
++ // Create the Cone shape
++ createConeSidesVertices(verticesPtr, rings, slices, topRadius, bottomRadius, length);
++ createConeSidesIndices(indicesPtr, rings, slices);
++
++ // Create flat disk at the top of the cone
++ int bottomCenterIndex = rings * (slices + 1);
++ createConeDiscVertices(verticesPtr, slices, topRadius, bottomRadius, length, -length / 2);
++ createDiscIndices(indicesPtr, bottomCenterIndex, slices, true);
++ }
++ }
++
++ // Calculate bounding box (min and max)
++ float* vertexData = reinterpret_cast<float*>(geomData.vertexData.data());
++ QVector3D boundsMin(std::numeric_limits<float>::max(),
++ std::numeric_limits<float>::max(),
++ std::numeric_limits<float>::max());
++ QVector3D boundsMax(std::numeric_limits<float>::lowest(),
++ std::numeric_limits<float>::lowest(),
++ std::numeric_limits<float>::lowest());
++ for (int i = 0; i < totalVertices; ++i) {
++ QVector3D pos(vertexData[i * 8], vertexData[i * 8 + 1], vertexData[i * 8 + 2]);
++ boundsMin.setX(qMin(boundsMin.x(), pos.x()));
++ boundsMin.setY(qMin(boundsMin.y(), pos.y()));
++ boundsMin.setZ(qMin(boundsMin.z(), pos.z()));
++
++ boundsMax.setX(qMax(boundsMax.x(), pos.x()));
++ boundsMax.setY(qMax(boundsMax.y(), pos.y()));
++ boundsMax.setZ(qMax(boundsMax.z(), pos.z()));
++ }
++ geomData.boundsMin = boundsMin;
++ geomData.boundsMax = boundsMax;
++
++ return geomData;
++ }
++
++ #if QT_CONFIG(concurrent)
++ void ConeGeometry::generateConeGeometryAsync(QPromise<ConeGeometry::GeometryData> &promise,
++ float topRadius,
++ float bottomRadius,
++ float length,
++ int rings,
++ int segments)
++ {
++ auto output = generateConeGeometry(topRadius, bottomRadius, length, rings, segments);
++ promise.addResult(output);
++ }
++ #endif
++
++ QT_END_NAMESPACE