Revision control
++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "spheregeometry_p.h"
++ #include <limits>
++
++ #if QT_CONFIG(concurrent)
++ #include <QtConcurrentRun>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype SphereGeometry
++ \inqmlmodule QtQuick3D.Helpers
++ \inherits Geometry
++ \since 6.9
++ \brief Provides geometry for a sphere.
++
++ SphereGeometry is a geometry type that represents a sphere. The sphere's size is
++ defined by its radius. The topology of the sphere is defined by the number of
++ rings and segments.
++ */
++
++ /*!
++ \qmlproperty real SphereGeometry::radius
++ The radius of the sphere. The default value is 100.0.
++ */
++
++ /*!
++ \qmlproperty int SphereGeometry::rings
++ The number of rings in the sphere. The default value is 16.
++ */
++
++ /*!
++ \qmlproperty int SphereGeometry::segments
++ The number of segments in the sphere. The default value is 32.
++ */
++
++ /*!
++ \qmlproperty bool SphereGeometry::asynchronous
++
++ This property holds whether the geometry generation should be asynchronous.
++ */
++
++ /*!
++ \qmlproperty bool SphereGeometry::status
++ \readonly
++
++ This property holds the status of the geometry generation when asynchronous is true.
++
++ \value SphereGeometry.Null The geometry generation has not started
++ \value SphereGeometry.Ready The geometry generation is complete.
++ \value SphereGeometry.Loading The geometry generation is in progress.
++ \value SphereGeometry.Error The geometry generation failed.
++ */
++
++ SphereGeometry::SphereGeometry(QQuick3DObject *parent)
++ : QQuick3DGeometry(parent)
++ {
++ #if QT_CONFIG(concurrent)
++ connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished, this, &SphereGeometry::requestFinished);
++ #endif
++ scheduleGeometryUpdate();
++ }
++
++ SphereGeometry::~SphereGeometry()
++ {
++
++ }
++
++ float SphereGeometry::radius() const
++ {
++ return m_radius;
++ }
++
++ void SphereGeometry::setRadius(float newRadius)
++ {
++ if (qFuzzyCompare(m_radius, newRadius))
++ return;
++ m_radius = newRadius;
++ emit radiusChanged();
++ scheduleGeometryUpdate();
++ }
++
++ int SphereGeometry::rings() const
++ {
++ return m_rings;
++ }
++
++ void SphereGeometry::setRings(int newRings)
++ {
++ if (m_rings == newRings)
++ return;
++ m_rings = newRings;
++ emit ringsChanged();
++ scheduleGeometryUpdate();
++ }
++
++ int SphereGeometry::segments() const
++ {
++ return m_segments;
++ }
++
++ void SphereGeometry::setSegments(int newSegments)
++ {
++ if (m_segments == newSegments)
++ return;
++ m_segments = newSegments;
++ emit segmentsChanged();
++ scheduleGeometryUpdate();
++ }
++
++ bool SphereGeometry::asynchronous() const
++ {
++ return m_asynchronous;
++ }
++
++ void SphereGeometry::setAsynchronous(bool newAsynchronous)
++ {
++ if (m_asynchronous == newAsynchronous)
++ return;
++ m_asynchronous = newAsynchronous;
++ emit asynchronousChanged();
++ }
++
++ SphereGeometry::Status SphereGeometry::status() const
++ {
++ return m_status;
++ }
++
++ void SphereGeometry::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_radius < 0 || m_rings < 1 || m_segments < 3) {
++ clear();
++ update();
++ return;
++ }
++
++ #if QT_CONFIG(concurrent)
++ if (m_asynchronous) {
++ m_geometryDataFuture = QtConcurrent::run(generateSphereGeometryAsync,
++ m_radius,
++ m_rings,
++ m_segments);
++ m_geometryDataWatcher.setFuture(m_geometryDataFuture);
++ m_status = Status::Loading;
++ Q_EMIT statusChanged();
++ } else {
++ #else
++ {
++
++ #endif // QT_CONFIG(concurrent)
++ updateGeometry(generateSphereGeometry(m_radius, m_rings, m_segments));
++ }
++ }
++
++ void SphereGeometry::requestFinished()
++ {
++ #if QT_CONFIG(concurrent)
++ const auto output = m_geometryDataFuture.takeResult();
++ updateGeometry(output);
++ #endif
++ }
++
++ void SphereGeometry::scheduleGeometryUpdate()
++ {
++ if (!m_geometryUpdateRequested) {
++ QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
++ m_geometryUpdateRequested = true;
++ }
++ }
++
++ void SphereGeometry::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();
++ }
++
++ SphereGeometry::GeometryData SphereGeometry::generateSphereGeometry(float radius, int rings, int segments)
++ {
++ GeometryData geometryData;
++
++ // Pre-compute the size of the vertex and index data
++ const int numVertices = (rings + 1) * (segments + 1);
++ const int numIndices = rings * segments * 6;
++ const int vertexStride = sizeof(float) * (3 + 2 + 3); // 3 for position, 2 for uv, 3 for normal
++ const int indexStride = sizeof(uint16_t);
++
++ // Resize the QByteArrays to fit all the vertex and index data
++ geometryData.vertexData.resize(numVertices * vertexStride);
++ geometryData.indexData.resize(numIndices * indexStride);
++
++
++ // Bounds initialization using std::numeric_limits<float>::max() and lowest()
++ 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());
++
++ // Temporary pointers for direct writing into the QByteArray
++ float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
++ uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
++
++ // Loop through rings and segments to generate the vertex data
++ for (int i = 0; i <= rings; ++i) {
++ float phi = M_PI * i / rings; // from 0 to PI
++ float y = radius * std::cos(phi);
++ float ringRadius = radius * std::sin(phi);
++
++ for (int j = 0; j <= segments; ++j) {
++ float theta = 2 * M_PI * j / segments; // from 0 to 2PI
++ float x = ringRadius * std::cos(theta);
++ float z = ringRadius * std::sin(theta);
++
++ // Position (vec3)
++ *vertexPtr++ = x;
++ *vertexPtr++ = y;
++ *vertexPtr++ = z;
++
++ // UV coordinates (vec2)
++ *vertexPtr++ = 1.0f - static_cast<float>(j) / segments;
++ *vertexPtr++ = 1.0f - static_cast<float>(i) / rings;
++
++ // Normalized normal vector (vec3)
++ QVector3D normal(x, y, z);
++ normal.normalize();
++ *vertexPtr++ = normal.x();
++ *vertexPtr++ = normal.y();
++ *vertexPtr++ = normal.z();
++
++ // Update bounds
++ boundsMin.setX(std::min(boundsMin.x(), x));
++ boundsMin.setY(std::min(boundsMin.y(), y));
++ boundsMin.setZ(std::min(boundsMin.z(), z));
++ boundsMax.setX(std::max(boundsMax.x(), x));
++ boundsMax.setY(std::max(boundsMax.y(), y));
++ boundsMax.setZ(std::max(boundsMax.z(), z));
++ }
++ }
++
++ // Loop through rings and segments to generate the index data
++ for (int i = 0; i < rings; ++i) {
++ for (int j = 0; j < segments; ++j) {
++ uint16_t a = static_cast<uint16_t>(i * (segments + 1) + j);
++ uint16_t b = static_cast<uint16_t>(a + segments + 1);
++ uint16_t c = static_cast<uint16_t>(b + 1);
++ uint16_t d = static_cast<uint16_t>(a + 1);
++
++ // First triangle (a, d, b)
++ *indexPtr++ = a;
++ *indexPtr++ = d;
++ *indexPtr++ = b;
++
++ // Second triangle (b, d, c)
++ *indexPtr++ = b;
++ *indexPtr++ = d;
++ *indexPtr++ = c;
++ }
++ }
++
++ geometryData.boundsMin = boundsMin;
++ geometryData.boundsMax = boundsMax;
++
++ // Return the geometry data
++ return geometryData;
++ }
++
++ #if QT_CONFIG(concurrent)
++ void SphereGeometry::generateSphereGeometryAsync(QPromise<SphereGeometry::GeometryData> &promise,
++ float radius,
++ int rings,
++ int segments)
++ {
++ auto output = generateSphereGeometry(radius, rings, segments);
++ promise.addResult(output);
++ }
++ #endif
++
++ QT_END_NAMESPACE