Revision control

Copy as Markdown

// 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 "cuboidgeometry_p.h"
#if QT_CONFIG(concurrent)
#include <QtConcurrentRun>
#endif
QT_BEGIN_NAMESPACE
/*!
\qmltype CuboidGeometry
\inqmlmodule QtQuick3D.Helpers
\inherits Geometry
\since 6.9
\brief Provides geometry for a cuboid.
CuboidGeometry is a geometry type that represents a cuboid. The cuboid's size is
defined by its xExtent, yExtent, and zExtent properties. The topology of the plane is defined by
the yzMeshResolution, xzMeshResolution, and xyMeshResolution properties.
*/
/*!
\qmlproperty real CuboidGeometry::xExtent
The x extent of the cuboid. The default value is 100.0.
*/
/*!
\qmlproperty real CuboidGeometry::yExtent
The y extent of the cuboid. The default value is 100.0.
*/
/*!
\qmlproperty real CuboidGeometry::zExtent
The z extent of the cuboid. The default value is 100.0.
*/
/*!
\qmlproperty size CuboidGeometry::yzMeshResolution
The number of segments in the y and z direction. The default value is 2x2.
*/
/*!
\qmlproperty size CuboidGeometry::xzMeshResolution
The number of segments in the x and z direction. The default value is 2x2.
*/
/*!
\qmlproperty size CuboidGeometry::xyMeshResolution
The number of segments in the x and y direction. The default value is 2x2.
*/
/*!
\qmlproperty bool CuboidGeometry::asynchronous
This property holds whether the geometry generation should be asynchronous.
*/
/*!
\qmlproperty bool CuboidGeometry::status
\readonly
This property holds the status of the geometry generation when asynchronous is true.
\value CuboidGeometry.Null The geometry generation has not started
\value CuboidGeometry.Ready The geometry generation is complete.
\value CuboidGeometry.Loading The geometry generation is in progress.
\value CuboidGeometry.Error The geometry generation failed.
*/
CuboidGeometry::CuboidGeometry(QQuick3DObject *parent)
: QQuick3DGeometry(parent)
{
#if QT_CONFIG(concurrent)
connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished, this, &CuboidGeometry::requestFinished);
#endif
scheduleGeometryUpdate();
}
CuboidGeometry::~CuboidGeometry()
{
}
float CuboidGeometry::xExtent() const
{
return m_xExtent;
}
void CuboidGeometry::setXExtent(float newXExtent)
{
if (qFuzzyCompare(m_xExtent, newXExtent))
return;
m_xExtent = newXExtent;
emit xExtentChanged();
scheduleGeometryUpdate();
}
float CuboidGeometry::yExtent() const
{
return m_yExtent;
}
void CuboidGeometry::setYExtent(float newYExtent)
{
if (qFuzzyCompare(m_yExtent, newYExtent))
return;
m_yExtent = newYExtent;
emit yExtentChanged();
scheduleGeometryUpdate();
}
float CuboidGeometry::zExtent() const
{
return m_zExtent;
}
void CuboidGeometry::setZExtent(float newZExtent)
{
if (qFuzzyCompare(m_zExtent, newZExtent))
return;
m_zExtent = newZExtent;
emit zExtentChanged();
scheduleGeometryUpdate();
}
QSize CuboidGeometry::yzMeshResolution() const
{
return m_yzMeshResolution;
}
void CuboidGeometry::setYzMeshResolution(const QSize &newYzMeshResolution)
{
if (m_yzMeshResolution == newYzMeshResolution)
return;
m_yzMeshResolution = newYzMeshResolution;
emit yzMeshResolutionChanged();
scheduleGeometryUpdate();
}
QSize CuboidGeometry::xzMeshResolution() const
{
return m_xzMeshResolution;
}
void CuboidGeometry::setXzMeshResolution(const QSize &newXzMeshResolution)
{
if (m_xzMeshResolution == newXzMeshResolution)
return;
m_xzMeshResolution = newXzMeshResolution;
emit xzMeshResolutionChanged();
scheduleGeometryUpdate();
}
QSize CuboidGeometry::xyMeshResolution() const
{
return m_xyMeshResolution;
}
void CuboidGeometry::setXyMeshResolution(const QSize &newXyMeshResolution)
{
if (m_xyMeshResolution == newXyMeshResolution)
return;
m_xyMeshResolution = newXyMeshResolution;
emit xyMeshResolutionChanged();
scheduleGeometryUpdate();
}
bool CuboidGeometry::asynchronous() const
{
return m_asynchronous;
}
void CuboidGeometry::setAsynchronous(bool newAsynchronous)
{
if (m_asynchronous == newAsynchronous)
return;
m_asynchronous = newAsynchronous;
emit asynchronousChanged();
}
CuboidGeometry::Status CuboidGeometry::status() const
{
return m_status;
}
void CuboidGeometry::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_xExtent <= 0 || m_yExtent <= 0 || m_yExtent <= 0) {
clear();
update();
return;
}
#if QT_CONFIG(concurrent)
if (m_asynchronous) {
m_geometryDataFuture = QtConcurrent::run(generateCuboidGeometryAsync,
m_xExtent,
m_yExtent,
m_zExtent,
m_yzMeshResolution,
m_xzMeshResolution,
m_xyMeshResolution);
m_geometryDataWatcher.setFuture(m_geometryDataFuture);
m_status = Status::Loading;
Q_EMIT statusChanged();
} else {
#else
{
#endif // QT_CONFIG(concurrent)
updateGeometry(generateCuboidGeometry(m_xExtent,
m_yExtent,
m_zExtent,
m_yzMeshResolution,
m_xzMeshResolution,
m_xyMeshResolution));
}
}
void CuboidGeometry::requestFinished()
{
#if QT_CONFIG(concurrent)
const auto output = m_geometryDataFuture.takeResult();
updateGeometry(output);
#endif
}
void CuboidGeometry::scheduleGeometryUpdate()
{
if (!m_geometryUpdateRequested) {
QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
m_geometryUpdateRequested = true;
}
}
void CuboidGeometry::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();
}
CuboidGeometry::GeometryData CuboidGeometry::generateCuboidGeometry(float xExtent, float yExtent, float zExtent, QSize yzMeshResolution, QSize xzMeshResolution, QSize xyMeshResolution)
{
GeometryData geometryData;
const float halfXExtent = xExtent / 2.0f;
const float halfYExtent = yExtent / 2.0f;
const float halfZExtent = zExtent / 2.0f;
geometryData.boundsMin = QVector3D(-halfXExtent, -halfYExtent, -halfZExtent);
geometryData.boundsMax = QVector3D(halfXExtent, halfYExtent, halfZExtent);
// Total number of vertices and indices required
int numVertices = (yzMeshResolution.width() + 1) * (yzMeshResolution.height() + 1) * 2 +
(xzMeshResolution.width() + 1) * (xzMeshResolution.height() + 1) * 2 +
(xyMeshResolution.width() + 1) * (xyMeshResolution.height() + 1) * 2;
int numIndices = yzMeshResolution.width() * yzMeshResolution.height() * 6 * 2 +
xzMeshResolution.width() * xzMeshResolution.height() * 6 * 2 +
xyMeshResolution.width() * xyMeshResolution.height() * 6 * 2;
const int vertexStride = sizeof(float) * (3 + 2 + 3); // vec3 (position), vec2 (uv), vec3 (normal)
const int indexStride = sizeof(uint16_t); // 16-bit index
// Allocate memory for vertex and index data
geometryData.vertexData.resize(numVertices * vertexStride);
geometryData.indexData.resize(numIndices * indexStride);
// Set up raw pointers for direct memory manipulation
float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
// Inline lambda for plane generation
auto generatePlane = [](float* &vertexPtr, uint16_t* &indexPtr, uint16_t &indexOffset, float width, float height, const QVector3D& origin, const QVector3D& right, const QVector3D& up, const QVector3D& normal, QSize resolution, bool flipWinding = false) {
const int quadsX = resolution.width();
const int quadsY = resolution.height();
const float halfWidth = width / 2.0f;
const float halfHeight = height / 2.0f;
quint16 vertexCount = 0;
// Generate vertices
for (int y = 0; y <= quadsY; ++y) {
for (int x = 0; x <= quadsX; ++x) {
// Normalized UV coordinates
float u = static_cast<float>(x) / quadsX;
float v = static_cast<float>(y) / quadsY;
// Compute the position of the vertex
QVector3D position = origin + right * (u * width - halfWidth) + up * (v * height - halfHeight);
// Write position
*vertexPtr++ = position.x();
*vertexPtr++ = position.y();
*vertexPtr++ = position.z();
// Write UV coordinates
*vertexPtr++ = u;
*vertexPtr++ = v;
// Write normal
*vertexPtr++ = normal.x();
*vertexPtr++ = normal.y();
*vertexPtr++ = normal.z();
++vertexCount;
}
}
// Generate indices
for (int y = 0; y < quadsY; ++y) {
for (int x = 0; x < quadsX; ++x) {
uint16_t a = indexOffset + static_cast<uint16_t>(y * (quadsX + 1) + x);
uint16_t b = static_cast<uint16_t>(a + quadsX + 1);
uint16_t c = static_cast<uint16_t>(b + 1);
uint16_t d = static_cast<uint16_t>(a + 1);
if (!flipWinding) {
// First triangle (a, d, b)
*indexPtr++ = a;
*indexPtr++ = d;
*indexPtr++ = b;
// Second triangle (b, d, c)
*indexPtr++ = b;
*indexPtr++ = d;
*indexPtr++ = c;
} else {
*indexPtr++ = a;
*indexPtr++ = b;
*indexPtr++ = d;
*indexPtr++ = b;
*indexPtr++ = c;
*indexPtr++ = d;
}
}
}
indexOffset += vertexCount;
};
// Generate the 6 faces of the cuboid
// Right and Left (YZ plane)
uint16_t indexOffset = 0;
generatePlane(vertexPtr, indexPtr, indexOffset, zExtent, yExtent, QVector3D(halfXExtent, 0, 0), QVector3D(0, 0, -1), QVector3D(0, 1, 0), QVector3D(1, 0, 0), yzMeshResolution);
generatePlane(vertexPtr, indexPtr, indexOffset, zExtent, yExtent, QVector3D(-halfXExtent, 0, 0), QVector3D(0, 0, 1), QVector3D(0, 1, 0), QVector3D(-1, 0, 0), yzMeshResolution);
// Top and Bottom (XZ plane)
generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, zExtent, QVector3D(0, halfYExtent, 0), QVector3D(-1, 0, 0), QVector3D(0, 0, 1), QVector3D(0, 1, 0), xzMeshResolution);
generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, zExtent, QVector3D(0, -halfYExtent, 0), QVector3D(1, 0, 0), QVector3D(0, 0, 1), QVector3D(0, -1, 0), xzMeshResolution);
// Front and Back (XY plane)
generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, yExtent, QVector3D(0, 0, halfZExtent), QVector3D(1, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 0, 1), xyMeshResolution);
generatePlane(vertexPtr, indexPtr, indexOffset, xExtent, yExtent, QVector3D(0, 0, -halfZExtent), QVector3D(-1, 0, 0), QVector3D(0, 1, 0), QVector3D(0, 0, -1), xyMeshResolution);
// Return the geometry data
return geometryData;
}
#if QT_CONFIG(concurrent)
void CuboidGeometry::generateCuboidGeometryAsync(QPromise<CuboidGeometry::GeometryData> &promise,
float xExtent,
float yExtent,
float zExtent,
QSize yzMeshResolution,
QSize xzMeshResolution,
QSize xyMeshResolution)
{
auto output = generateCuboidGeometry(xExtent, yExtent, zExtent, yzMeshResolution, xzMeshResolution, xyMeshResolution);
promise.addResult(output);
}
#endif
QT_END_NAMESPACE