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 "planegeometry_p.h"
#include <limits>
#if QT_CONFIG(concurrent)
#include <QtConcurrentRun>
#endif
QT_BEGIN_NAMESPACE
/*!
\qmltype PlaneGeometry
\inqmlmodule QtQuick3D.Helpers
\inherits Geometry
\since 6.9
\brief Provides geometry for a plane.
PlaneGeometry is a geometry type that represents a plane. The plane's size is
defined by its height and width properties. The topology of the plane is defined by
the meshResolution property. The orientation of the plane is defined by the plane property.
*/
/*!
\qmlproperty real PlaneGeometry::width
The width of the plane. The default value is 100.0.
*/
/*!
\qmlproperty real PlaneGeometry::height
The height of the plane. The default value is 100.0.
*/
/*!
\qmlproperty size PlaneGeometry::meshResolution
The resolution of the plane. The default value is QSize(2, 2).
*/
/*!
\qmlproperty PlaneGeometry::Plane PlaneGeometry::plane
The orientation of the plane. The default value is PlaneGeometry.XY.
All geometry will be created along the selected plane, and the front
face and normal will point towards the remaining positive axis, unless
reversed is true.
\value PlaneGeometry.XY The plane is oriented along the XY plane.
\value PlaneGeometry.XZ The plane is oriented along the XZ plane.
\value PlaneGeometry.ZY The plane is oriented along the ZY plane.
*/
/*!
\qmlproperty bool PlaneGeometry::reversed
This property holds whether the plane is flipped. This changes both the normal as
well as the winding order of the plane. The default value is false, which means that
when a Plane is created with the XY orientation, the normal will point in the positive
Z direction and the winding order will be counter-clockwise. When reversed is true,
the normal will point in the negative Z direction and the winding order will be clockwise.
*/
/*!
\qmlproperty bool PlaneGeometry::mirrored
This property holds whether the UV coordinates of the plane are flipped vertically.
*/
/*!
\qmlproperty bool PlaneGeometry::asynchronous
This property holds whether the geometry generation should be asynchronous.
*/
/*!
\qmlproperty bool PlaneGeometry::status
\readonly
This property holds the status of the geometry generation when asynchronous is true.
\value PlaneGeometry.Null The geometry generation has not started
\value PlaneGeometry.Ready The geometry generation is complete.
\value PlaneGeometry.Loading The geometry generation is in progress.
\value PlaneGeometry.Error The geometry generation failed.
*/
PlaneGeometry::PlaneGeometry(QQuick3DObject *parent)
: QQuick3DGeometry(parent)
{
#if QT_CONFIG(concurrent)
connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished, this, &PlaneGeometry::requestFinished);
#endif
scheduleGeometryUpdate();
}
PlaneGeometry::~PlaneGeometry()
{
}
float PlaneGeometry::width() const
{
return m_width;
}
void PlaneGeometry::setWidth(float newWidth)
{
if (qFuzzyCompare(m_width, newWidth))
return;
m_width = newWidth;
emit widthChanged();
scheduleGeometryUpdate();
}
float PlaneGeometry::height() const
{
return m_height;
}
void PlaneGeometry::setHeight(float newHeight)
{
if (qFuzzyCompare(m_height, newHeight))
return;
m_height = newHeight;
emit heightChanged();
scheduleGeometryUpdate();
}
QSize PlaneGeometry::meshResolution() const
{
return m_meshResolution;
}
void PlaneGeometry::setMeshResolution(const QSize &newMeshResolution)
{
if (m_meshResolution == newMeshResolution)
return;
m_meshResolution = newMeshResolution;
emit meshResolutionChanged();
scheduleGeometryUpdate();
}
PlaneGeometry::Plane PlaneGeometry::plane() const
{
return m_plane;
}
void PlaneGeometry::setPlane(Plane newPlane)
{
if (m_plane == newPlane)
return;
m_plane = newPlane;
emit planeChanged();
scheduleGeometryUpdate();
}
bool PlaneGeometry::reversed() const
{
return m_reversed;
}
void PlaneGeometry::setReversed(bool newReversed)
{
if (m_reversed == newReversed)
return;
m_reversed = newReversed;
emit reversedChanged();
scheduleGeometryUpdate();
}
bool PlaneGeometry::mirrored() const
{
return m_mirrored;
}
void PlaneGeometry::setMirrored(bool newMirrored)
{
if (m_mirrored == newMirrored)
return;
m_mirrored = newMirrored;
emit mirroredChanged();
scheduleGeometryUpdate();
}
bool PlaneGeometry::asynchronous() const
{
return m_asynchronous;
}
void PlaneGeometry::setAsynchronous(bool newAsynchronous)
{
if (m_asynchronous == newAsynchronous)
return;
m_asynchronous = newAsynchronous;
emit asynchronousChanged();
}
PlaneGeometry::Status PlaneGeometry::status() const
{
return m_status;
}
void PlaneGeometry::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_width <= 0 || m_height <= 0 || m_meshResolution.width() <= 0 || m_meshResolution.height() <= 0) {
clear();
update();
return;
}
#if QT_CONFIG(concurrent)
if (m_asynchronous) {
m_geometryDataFuture = QtConcurrent::run(generatePlaneGeometryAsync,
m_width,
m_height,
m_meshResolution,
m_plane,
m_reversed,
m_mirrored);
m_geometryDataWatcher.setFuture(m_geometryDataFuture);
m_status = Status::Loading;
Q_EMIT statusChanged();
} else {
#else
{
#endif // QT_CONFIG(concurrent)
updateGeometry(generatePlaneGeometry(m_width, m_height, m_meshResolution, m_plane, m_reversed, m_mirrored));
}
}
void PlaneGeometry::requestFinished()
{
#if QT_CONFIG(concurrent)
const auto output = m_geometryDataFuture.takeResult();
updateGeometry(output);
#endif
}
void PlaneGeometry::scheduleGeometryUpdate()
{
if (!m_geometryUpdateRequested) {
QMetaObject::invokeMethod(this, "doUpdateGeometry", Qt::QueuedConnection);
m_geometryUpdateRequested = true;
}
}
void PlaneGeometry::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();
}
PlaneGeometry::GeometryData PlaneGeometry::generatePlaneGeometry(float width, float height, QSize meshResolution, Plane plane, bool reversed, bool mirrored)
{
GeometryData geometryData;
int quadsX = meshResolution.width();
int quadsY = meshResolution.height();
const int numVertices = (quadsX + 1) * (quadsY + 1);
const int numIndices = quadsX * quadsY * 6; // Two triangles per quad
const int vertexStride = sizeof(float) * (3 + 2 + 3); // vec3 (position), vec2 (uv), vec3 (normal)
const int indexStride = sizeof(uint16_t); // 16-bit index
geometryData.vertexData.resize(numVertices * vertexStride);
geometryData.indexData.resize(numIndices * indexStride);
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());
float* vertexPtr = reinterpret_cast<float*>(geometryData.vertexData.data());
uint16_t* indexPtr = reinterpret_cast<uint16_t*>(geometryData.indexData.data());
QVector3D normal;
switch (plane) {
case Plane::XY:
normal = QVector3D(0, 0, 1);
break;
case Plane::XZ:
normal = QVector3D(0, 1, 0);
break;
case Plane::ZY:
normal = QVector3D(1, 0, 0);
break;
}
// Flip normal if the plane is reversed
if (reversed)
normal = -normal;
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;
// Position in local space based on plane orientation
float posX = width * (u - 0.5f);
float posY = height * (v - 0.5f);
if (mirrored)
v = 1.0f - v;
if (reversed)
u = 1.0f - u;
QVector3D position;
switch (plane) {
case Plane::XY:
position = QVector3D(posX, posY, 0.0f);
break;
case Plane::XZ:
position = QVector3D(posX, 0.0f, -posY);
break;
case Plane::ZY:
position = QVector3D(0.0f, posY, -posX);
break;
}
// 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();
// Update bounds
boundsMin.setX(std::min(boundsMin.x(), position.x()));
boundsMin.setY(std::min(boundsMin.y(), position.y()));
boundsMin.setZ(std::min(boundsMin.z(), position.z()));
boundsMax.setX(std::max(boundsMax.x(), position.x()));
boundsMax.setY(std::max(boundsMax.y(), position.y()));
boundsMax.setZ(std::max(boundsMax.z(), position.z()));
}
}
// Generate indices
for (int y = 0; y < quadsY; ++y) {
for (int x = 0; x < quadsX; ++x) {
uint16_t a = 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 (reversed) {
// Reverse the triangle winding order
*indexPtr++ = a;
*indexPtr++ = b;
*indexPtr++ = d;
*indexPtr++ = b;
*indexPtr++ = c;
*indexPtr++ = d;
} else {
// Normal winding order
*indexPtr++ = a;
*indexPtr++ = d;
*indexPtr++ = b;
*indexPtr++ = b;
*indexPtr++ = d;
*indexPtr++ = c;
}
}
}
// Return the geometry data
geometryData.boundsMax = boundsMax;
geometryData.boundsMin = boundsMin;
return geometryData;
}
#if QT_CONFIG(concurrent)
void PlaneGeometry::generatePlaneGeometryAsync(QPromise<PlaneGeometry::GeometryData> &promise,
float width,
float height,
QSize meshResolution,
Plane plane,
bool reversed,
bool mirrored)
{
auto output = generatePlaneGeometry(width, height, meshResolution, plane, reversed, mirrored);
promise.addResult(output);
}
#endif
QT_END_NAMESPACE