Revision control

Copy as Markdown

// 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