// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qquick3dgeometry_p.h" #include "qquick3dscenemanager_p.h" #include /*! \qmltype Geometry \inherits Object3D \inqmlmodule QtQuick3D \nativetype QQuick3DGeometry \brief Base type for custom geometry. Custom geometry allows using application-generated vertex and index data, that can possibly change dynamically as well. To use custom geometry, do not assign a \c{.mesh} file as the \l{Model::source}{source} to a Model. Instead, set its \l{Model::geometry}{geometry} property to reference a Geometry object. A typical way of implementing custom geometry is by creating a \l QQuick3DGeometry subclass in C++ and registering the new type for use with QML. It is also possible to use the built-in custom geometry provider \l GridGeometry in the \c Helpers module. The following is an example of \l GridGeometry. Any application-provided Geometry subclass can be taken into use in the same manner. \code import QtQuick3D.Helpers Model { geometry: GridGeometry { } materials: [ DefaultMaterial { diffuseColor: "white" lighting: DefaultMaterial.NoLighting } ] } \endcode \sa {Qt Quick 3D - Custom Geometry Example}, Model, QQuick3DGeometry */ /*! \class QQuick3DGeometry \inmodule QtQuick3D \inherits QQuick3DObject \since 5.15 \brief Base class for defining custom geometry. The QQuick3DGeometry can be used to specify custom geometry for a Model in the Qt Quick 3D scene. While not strictly required, the typical usage is to inherit from this class. The subclass is then exposed to QML by registering it to the type system. The \l{Model::geometry}{geometry} property of a Model can then be set to reference an instance of the registered type. The high-level structure of such a class is typically similar to the following: \code class CustomGeometry : public QQuick3DGeometry { public: CustomGeometry() { rebuildGeometry(); } void setSomething() { // Change relevant internal data. // ... // Then rebuild the vertex and index data and pass it to QQuick3DGeometry. rebuildGeometry(); // Finally, trigger an update. This is relevant in case nothing else // is changing in the scene; this way we make sure a new frame will // be rendered. update(); } private: void rebuildGeometry() { QByteArray vertices; QByteArray indices; ... setPrimitiveType(Lines); setVertexBuffer(vertices); setIndexBuffer(indices); setStride(3 * sizeof(float)); // e.g. when having 3 components per vertex setBounds(...); // minimum and maximum extents, for picking addAttribute(PositionSemantic, 0, F32Type); ... } }; \endcode This class can then be registered as a QML type and used with \l {QtQuick3D::Model}{Model}. In Qt 5 type registration happened with qmlRegisterType: \code qmlRegisterType("Example", 1, 0, "CustomGeometry"); \endcode In Qt 6 the default approach is to use automatic registration with the help of the build system. Instead of calling qmlRegisterType, the \c{.pro} file can now contain: \code CONFIG += qmltypes QML_IMPORT_NAME = Example QML_IMPORT_MAJOR_VERSION = 1 \endcode With CMake, automatic registration is the default behavior, so no special settings are needed beyond basic QML module setup: \code qt_add_qml_module(application URI Example VERSION 1.0 ) \endcode The class implementation should add QML_NAMED_ELEMENT: \code class CustomGeometry : public QQuick3DGeometry { Q_OBJECT QML_NAMED_ELEMENT(CustomGeometry) ... }; \endcode The QML code can then use the custom type: \code import Example 1.0 Model { id: customModel geometry: CustomGeometry { } } \endcode At minimum, a custom geometry should have the following specified: \list \li vertex data, \li vertex stride, \li primitive type, \li an attribute with PositionSemantic. \endlist These are sufficient to render the mesh. For indexed drawing, the index buffer data and an attribute with IndexSemantic needs to be specified as well. In order to support picking (input), the class must specify the bounding volume using setBounds(). For proper lighting, an attribute with NormalSemantic is needed. When the material uses texturing, at least one set of UV coordinates must be provided and described in an TexCoord0Semantic or TexCoord1Semantic attribute. Some materials may require tangents and binormals as well. As a concrete, minimal example, the following class would provide geometry for a single triangle: \code class ExampleGeometry : public QQuick3DGeometry { Q_OBJECT QML_NAMED_ELEMENT(ExampleGeometry) public: ExampleGeometry(); private: void updateData(); }; ExampleGeometry::ExampleGeometry() { updateData(); } void ExampleGeometry::updateData() { QByteArray v; v.resize(3 * 3 * sizeof(float)); float *p = reinterpret_cast(v.data()); // a triangle, front face = counter-clockwise *p++ = -1.0f; *p++ = -1.0f; *p++ = 0.0f; *p++ = 1.0f; *p++ = -1.0f; *p++ = 0.0f; *p++ = 0.0f; *p++ = 1.0f; *p++ = 0.0f; setVertexData(v); setStride(3 * sizeof(float)); setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles); addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type); } \endcode Depending on the lighting in the scene, the result of referencing this geometry from a Model: \image customgeometry.jpg \note Vertex data is expected to follow OpenGL conventions. This means the data must be provided with the assumption that the Y axis is pointing up in the normalized device coordinate system, and that front faces have a counter clockwise winding. \sa Model, Geometry */ QT_BEGIN_NAMESPACE QQuick3DGeometryPrivate::QQuick3DGeometryPrivate() : QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::Geometry) { } QQuick3DGeometry::QQuick3DGeometry(QQuick3DObject *parent) : QQuick3DObject(*new QQuick3DGeometryPrivate, parent) { } QQuick3DGeometry::~QQuick3DGeometry() { } /*! Returns the vertex buffer data set by setVertexData. */ QByteArray QQuick3DGeometry::vertexData() const { const Q_D(QQuick3DGeometry); return d->m_vertexBuffer; } /*! \since 6.6 Returns the target buffer data set by setTargetData. */ QByteArray QQuick3DGeometry::targetData() const { const Q_D(QQuick3DGeometry); return d->m_targetBuffer; } /*! Returns the index buffer data. */ QByteArray QQuick3DGeometry::indexData() const { const Q_D(QQuick3DGeometry); return d->m_indexBuffer; } /*! Returns the number of attributes defined for this geometry. \sa attribute */ int QQuick3DGeometry::attributeCount() const { const Q_D(QQuick3DGeometry); return d->m_attributeCount; } /*! Returns attribute definition number \a index The attribute definitions are numbered from 0 to \c {attributeCount() - 1} */ QQuick3DGeometry::Attribute QQuick3DGeometry::attribute(int index) const { const Q_D(QQuick3DGeometry); return d->m_attributes[index]; } /*! \since 6.6 Returns the number of morph target attributes defined for this geometry. \sa targetAttribute */ int QQuick3DGeometry::targetAttributeCount() const { const Q_D(QQuick3DGeometry); return d->m_targetAttributeCount; } /*! \since 6.6 Returns morph target attribute definition number \a index The attribute definitions are numbered from 0 to \c {attributeCount() - 1} */ QQuick3DGeometry::TargetAttribute QQuick3DGeometry::targetAttribute(int index) const { const Q_D(QQuick3DGeometry); return d->m_targetAttributes[index]; } /*! Returns the primitive type used when rendering. The default is \c Triangles. \sa setPrimitiveType */ QQuick3DGeometry::PrimitiveType QQuick3DGeometry::primitiveType() const { const Q_D(QQuick3DGeometry); return d->m_primitiveType; } /*! Returns the minimum coordinate of the bounding volume. \sa setBounds */ QVector3D QQuick3DGeometry::boundsMin() const { const Q_D(QQuick3DGeometry); return d->m_min; } /*! Returns the maximum coordinate of the bounding volume. \sa setBounds */ QVector3D QQuick3DGeometry::boundsMax() const { const Q_D(QQuick3DGeometry); return d->m_max; } /*! Returns the byte stride of the vertex buffer. \sa setStride */ int QQuick3DGeometry::stride() const { const Q_D(QQuick3DGeometry); return d->m_stride; } void QQuick3DGeometry::markAllDirty() { QQuick3DObject::markAllDirty(); } /*! Sets the vertex buffer \a data. The buffer should hold all the vertex data packed in the array, as described by the attribute definitions. Note that this does not include attributes with \c IndexSemantic, which belong in the index buffer. \sa addAttribute, setStride, setIndexData */ void QQuick3DGeometry::setVertexData(const QByteArray &data) { Q_D(QQuick3DGeometry); d->m_vertexBuffer = data; d->m_geometryChanged = true; } /*! \overload Updates a subset of the vertex buffer. \a offset specifies the offset in bytes, \a data specifies the size and the data. This function will not resize the buffer. If \c {offset + data.size()} is greater than the current size of the buffer, the overshooting data will be ignored. \note The partial update functions for vertex, index and morph target data do not offer any guarantee on how such changes are implemented internally. depending on the underlying implementation, even partial changes may lead to updating the entire graphics resource. */ void QQuick3DGeometry::setVertexData(int offset, const QByteArray &data) { Q_D(QQuick3DGeometry); if (offset >= d->m_vertexBuffer.size()) return; const size_t len = qMin(d->m_vertexBuffer.size() - offset, data.size()); memcpy(d->m_vertexBuffer.data() + offset, data.data(), len); d->m_geometryChanged = true; } /*! \since 6.6 Sets the morph target buffer \a data. The buffer should hold all the morph target data. \sa addTargetAttribute */ void QQuick3DGeometry::setTargetData(const QByteArray &data) { Q_D(QQuick3DGeometry); d->m_targetBuffer = data; d->m_targetChanged = true; } /*! \since 6.6 \overload Updates a subset of the morph target buffer. \a offset specifies the offset in bytes, \a data specifies the size and the data. This function will not resize the buffer. If \c {offset + data.size()} is greater than the current size of the buffer, the overshooting data will be ignored. \note The partial update functions for vertex, index and morph target data do not offer any guarantee on how such changes are implemented internally. Depending on the underlying implementation, even partial changes may lead to updating the entire graphics resource. */ void QQuick3DGeometry::setTargetData(int offset, const QByteArray &data) { Q_D(QQuick3DGeometry); if (offset >= d->m_targetBuffer.size()) return; const size_t len = qMin(d->m_targetBuffer.size() - offset, data.size()); memcpy(d->m_targetBuffer.data() + offset, data.data(), len); d->m_targetChanged = true; } /*! Sets the index buffer to \a data. To use indexed drawing, add an attribute with \c IndexSemantic \sa addAttribute */ void QQuick3DGeometry::setIndexData(const QByteArray &data) { Q_D(QQuick3DGeometry); d->m_indexBuffer = data; d->m_geometryChanged = true; } /*! \overload Updates a subset of the index buffer. \a offset specifies the offset in bytes, \a data specifies the size and the data. This function will not resize the buffer. If \c {offset + data.size()} is greater than the current size of the buffer, the overshooting data will be ignored. \note The partial update functions for vertex, index and morph target data do not offer any guarantee on how such changes are implemented internally. Depending on the underlying implementation, even partial changes may lead to updating the entire graphics resource. */ void QQuick3DGeometry::setIndexData(int offset, const QByteArray &data) { Q_D(QQuick3DGeometry); if (offset >= d->m_indexBuffer.size()) return; const size_t len = qMin(d->m_indexBuffer.size() - offset, data.size()); memcpy(d->m_indexBuffer.data() + offset, data.data(), len); d->m_geometryChanged = true; } /*! Sets the stride of the vertex buffer to \a stride, measured in bytes. This is the distance between two consecutive vertices in the buffer. For example, a tightly packed, interleaved vertex buffer for a geometry using \c PositionSemantic, \c IndexSemantic, and \c ColorSemantic will have a stride of \c 28 (Seven floats in total: Three for position, four for color, and none for indexes, which do not go in the vertex buffer.) \note QQuick3DGeometry expects, and works only with, vertex data with an interleaved attribute layout. \sa addAttribute */ void QQuick3DGeometry::setStride(int stride) { Q_D(QQuick3DGeometry); if (stride != d->m_stride) { d->m_stride = stride; d->m_geometryChanged = true; } } /*! Sets the bounding volume of the geometry to the cube defined by the points \a min and \a max. This is used for \l {View3D::pick}{picking}. */ void QQuick3DGeometry::setBounds(const QVector3D &min, const QVector3D &max) { Q_D(QQuick3DGeometry); d->m_max = max; d->m_min = min; d->m_geometryBoundsChanged = true; } /*! Sets the primitive type used for rendering to \a type. \value Points The primitives are points. \value LineStrip The primitives are lines in a strip. \value Lines The primitives are lines in a list. \value TriangleStrip The primitives are triangles in a strip. \value TriangleFan The primitives are triangles in a fan. Be aware that triangle fans may not be supported at run time, depending on the underlying graphics API. \value Triangles The primitives are triangles in a list. The initial value is \c Triangles. \note Be aware that triangle fans (TriangleFan) may not be supported at run time, depending on the underlying graphics API. For example, with Direct 3D this topology will not be functional at all. \note The point size for Points and the line width for Lines and LineStrip are controlled by the \l{PrincipledMaterial::pointSize}{material}. Be aware however that sizes other than 1 may not be supported at run time, depending on the underlying graphics API. */ void QQuick3DGeometry::setPrimitiveType(PrimitiveType type) { Q_D(QQuick3DGeometry); if (d->m_primitiveType != type) { d->m_primitiveType = type; d->m_geometryChanged = true; } } /*! Adds vertex attribute description. Each attribute has a \a semantic, which specifies the usage of the attribute and the number of components it has, an \a offset from the beginning to the vertex to the attribute location inside a vertex and a \a componentType specifying the datatype and size of the attribute. The semantic can be one of the following: \value PositionSemantic The attribute is a position. 3 components: \e x, \e y, and \e z \value NormalSemantic The attribute is a normal vector. 3 components: \e x, \e y, and \e z \value TexCoord0Semantic The attribute is a texture coordinate. 2 components: \e u and \e v \value TexCoord1Semantic The attribute is a texture coordinate. 2 components: \e u and \e v \value TangentSemantic The attribute is a tangent vector. 3 components: \e x, \e y, and \e z \value BinormalSemantic The attribute is a binormal vector. 3 components: \e x, \e y, and \e z \value JointSemantic The attribute is a joint index vector for \l {Vertex Skinning}{skinning}. 4 components: joint index 1-4 \value WeightSemantic The attribute is a weight vector for \l {Vertex Skinning}{skinning}. 4 components: joint weight 1-4 \value ColorSemantic The attribute is a vertex color vector. 4 components: \e r, \e g, \e b, and \e a \value TargetPositionSemantic The attribute is a position for the first \l {Morphing Animation}{morph target}. 3 components: \e x, \e y, and \e z \value TargetNormalSemantic The attribute is a normal vector for the first \l {Morphing Animation}{morph target}. 3 components: \e x, \e y, and \e z \value TargetTangentSemantic The attribute is a tangent vector for the first \l {Morphing Animation}{morph target}. 3 components: \e x, \e y, and \e z \value TargetBinormalSemantic The attribute is a binormal vector for the first \l {Morphing Animation}{morph target}. 3 components: \e x, \e y, and \e z In addition, \a semantic can be \c IndexSemantic. In this case the attribute does not represent an entry in the vertex buffer, but rather describes the index data in the index buffer. Since there is always just one index per vertex, \a offset makes no sense for the index buffer, and should be left at zero. The component type can be one of the following: \value U16Type The index component type is unsigned 16-bit integer. Only supported for \c IndexSemantic. \value U32Type The attribute (or index component) is an unsigned 32-bit integer. \value I32Type The attribute is a signed 32-bit integer. Be aware that old OpenGL versions (such as, 2.1 or OpenGL ES 2.0) may not support this data type. \value F32Type The attribute is a single-precision float. \note The joint index data is typically \c I32Type. \c F32Type is also supported in order to enable functioning with APIs, such as OpenGL ES 2.0, that do not support integer vertex input attributes. \note For index data (\c IndexSemantic) only U16Type and U32Type are sensible and supported. \note TargetXXXSemantics will be deprecated. \l addTargetAttribute can be used for the morph targets. Now these semantics are just supported for backward compatibility. If they are mixed-used with addTargetAttribute and setTargetData, the result cannot be quaranteed. */ void QQuick3DGeometry::addAttribute(Attribute::Semantic semantic, int offset, Attribute::ComponentType componentType) { Q_D(QQuick3DGeometry); if (semantic != Attribute::TargetPositionSemantic && semantic != Attribute::TargetNormalSemantic && semantic != Attribute::TargetTangentSemantic && semantic != Attribute::TargetBinormalSemantic) { if (d->m_attributeCount >= QQuick3DGeometryPrivate::MAX_ATTRIBUTE_COUNT) return; d->m_attributes[d->m_attributeCount].semantic = semantic; d->m_attributes[d->m_attributeCount].offset = offset; d->m_attributes[d->m_attributeCount].componentType = componentType; d->m_attributeCount++; d->m_geometryChanged = true; } else { if (d->m_targetAttributeCount >= QQuick3DGeometryPrivate::MAX_TARGET_ATTRIBUTE_COUNT) return; d->m_targetAttributes[d->m_targetAttributeCount].targetId = 0; d->m_targetAttributes[d->m_targetAttributeCount].attr.semantic = semantic; d->m_targetAttributes[d->m_targetAttributeCount].attr.offset = offset; // m_stride and m_vertexBuffer will be used for targetBuffer. d->m_targetAttributeCount++; d->m_targetChanged = true; d->m_usesOldTargetSemantics = true; } } /*! \overload Adds vertex attribute description. Each attribute has a semantic, which specifies the usage of the attribute and the number of components it has, an offset from the beginning to the vertex to the attribute location inside a vertex and a componentType specifying the datatype and size of the attribute. */ void QQuick3DGeometry::addAttribute(const Attribute &attribute) { Q_D(QQuick3DGeometry); if (d->m_attributeCount >= QQuick3DGeometryPrivate::MAX_ATTRIBUTE_COUNT) return; d->m_attributes[d->m_attributeCount++] = attribute; d->m_geometryChanged = true; } /*! \since 6.6 Adds morph target attribute description. Each attribute has a \a targetId which the attribute belongs to, a \a semantic, which specifies the usage of the attribute and the number of components it has, an \a offset from the beginning to the vertex to the attribute location inside a vertex, and a \a stride which is a byte size between the elements. \note The targetId should be increased from 0 without skipping any number and all the targets should have the same attributes. \note The semantic is the same as the vertex attribute but IndexSemantic, JointSementic and WeightSemantic are not allowed for target attributes. \note The componentTypes of all the target attributes must be F32Type. \note If the stride is not given or less than or equal to zero, the attribute is considered to be tightly packed. \sa addAttribute */ void QQuick3DGeometry::addTargetAttribute(quint32 targetId, Attribute::Semantic semantic, int offset, int stride) { Q_D(QQuick3DGeometry); if (d->m_targetAttributeCount >= QQuick3DGeometryPrivate::MAX_TARGET_ATTRIBUTE_COUNT) return; if (semantic == Attribute::IndexSemantic || semantic == Attribute::JointSemantic || semantic == Attribute::WeightSemantic) return; d->m_targetAttributes[d->m_targetAttributeCount].targetId = targetId; d->m_targetAttributes[d->m_targetAttributeCount].attr.semantic = semantic; d->m_targetAttributes[d->m_targetAttributeCount].attr.offset = offset; d->m_targetAttributes[d->m_targetAttributeCount].stride = stride; d->m_targetAttributeCount++; d->m_targetChanged = true; } /*! \since 6.6 \overload Adds morph target attribute description. Each attribute has a targetId which the attribute belongs to, a semantic, which specifies the usage of the attribute and the number of components it has, an offset from the beginning to the vertex to the attribute location inside a vertex, and a stride which is a byte size between the elements. */ void QQuick3DGeometry::addTargetAttribute(const TargetAttribute &attribute) { Q_D(QQuick3DGeometry); if (d->m_targetAttributeCount >= QQuick3DGeometryPrivate::MAX_TARGET_ATTRIBUTE_COUNT) return; if (attribute.attr.semantic == Attribute::IndexSemantic || attribute.attr.semantic == Attribute::JointSemantic || attribute.attr.semantic == Attribute::WeightSemantic) return; d->m_targetAttributes[d->m_targetAttributeCount++] = attribute; d->m_targetChanged = true; } /*! Resets the geometry to its initial state, clearing previously set vertex and index data as well as attributes. */ void QQuick3DGeometry::clear() { Q_D(QQuick3DGeometry); d->m_vertexBuffer.clear(); d->m_targetBuffer.clear(); d->m_indexBuffer.clear(); d->m_attributeCount = 0; d->m_targetAttributeCount = 0; d->m_subsets.clear(); d->m_primitiveType = PrimitiveType::Triangles; d->m_geometryChanged = true; d->m_targetChanged = true; d->m_min = {}; d->m_max = {}; } /*! Returns the number of subsets. */ int QQuick3DGeometry::subsetCount() const { Q_D(const QQuick3DGeometry); return d->m_subsets.size(); } /*! Returns the number of minimum bounds of a \a subset. \sa subsetBoundsMax */ QVector3D QQuick3DGeometry::subsetBoundsMin(int subset) const { Q_D(const QQuick3DGeometry); if (subset >= 0 && subset < d->m_subsets.size()) return d->m_subsets[subset].boundsMin; return {}; } /*! Returns the number of maximum bounds of a \a subset. \sa subsetBoundsMin */ QVector3D QQuick3DGeometry::subsetBoundsMax(int subset) const { Q_D(const QQuick3DGeometry); if (subset >= 0 && subset < d->m_subsets.size()) return d->m_subsets[subset].boundsMax; return {}; } /*! Returns the \a subset offset to the vertex or index buffer. \sa subsetCount */ int QQuick3DGeometry::subsetOffset(int subset) const { Q_D(const QQuick3DGeometry); if (subset >= 0 && subset < d->m_subsets.size()) return d->m_subsets[subset].offset; return 0; } /*! Returns the subset primitive count. \sa subsetOffset */ int QQuick3DGeometry::subsetCount(int subset) const { Q_D(const QQuick3DGeometry); if (subset >= 0 && subset < d->m_subsets.size()) return d->m_subsets[subset].count; return 0; } /*! Returns the \a subset name. */ QString QQuick3DGeometry::subsetName(int subset) const { Q_D(const QQuick3DGeometry); if (subset >= 0 && subset < d->m_subsets.size()) return d->m_subsets[subset].name; return {}; } /*! Adds new subset to the geometry. Subsets allow rendering parts of the geometry with different materials. The materials are specified in the \l {Model::materials}{model}. If the geometry has index buffer, then the \a offset and \a count are the primitive offset and count of indices in the subset. If the geometry has only vertex buffer, the offset is the vertex offset and count is the number of vertices in the subset. The bounds \a boundsMin and \a boundsMax should enclose the subset just like geometry bounds. Also the subset can have a \a name. */ void QQuick3DGeometry::addSubset(int offset, int count, const QVector3D &boundsMin, const QVector3D &boundsMax, const QString &name) { Q_D(QQuick3DGeometry); d->m_subsets.append({name, boundsMin, boundsMax, quint32(offset), quint32(count)}); d->m_geometryChanged = true; } static inline QSSGMesh::Mesh::DrawMode mapPrimitiveType(QQuick3DGeometry::PrimitiveType t) { switch (t) { case QQuick3DGeometry::PrimitiveType::Points: return QSSGMesh::Mesh::DrawMode::Points; case QQuick3DGeometry::PrimitiveType::LineStrip: return QSSGMesh::Mesh::DrawMode::LineStrip; case QQuick3DGeometry::PrimitiveType::Lines: return QSSGMesh::Mesh::DrawMode::Lines; case QQuick3DGeometry::PrimitiveType::TriangleStrip: return QSSGMesh::Mesh::DrawMode::TriangleStrip; case QQuick3DGeometry::PrimitiveType::TriangleFan: return QSSGMesh::Mesh::DrawMode::TriangleFan; case QQuick3DGeometry::PrimitiveType::Triangles: return QSSGMesh::Mesh::DrawMode::Triangles; } Q_UNREACHABLE_RETURN(QSSGMesh::Mesh::DrawMode::Triangles); } static inline QSSGMesh::RuntimeMeshData::Attribute::Semantic mapSemantic(QQuick3DGeometry::Attribute::Semantic s) { switch (s) { case QQuick3DGeometry::Attribute::IndexSemantic: return QSSGMesh::RuntimeMeshData::Attribute::IndexSemantic; case QQuick3DGeometry::Attribute::PositionSemantic: return QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic; case QQuick3DGeometry::Attribute::NormalSemantic: return QSSGMesh::RuntimeMeshData::Attribute::NormalSemantic; case QQuick3DGeometry::Attribute::TexCoord0Semantic: return QSSGMesh::RuntimeMeshData::Attribute::TexCoord0Semantic; case QQuick3DGeometry::Attribute::TexCoord1Semantic: return QSSGMesh::RuntimeMeshData::Attribute::TexCoord1Semantic; case QQuick3DGeometry::Attribute::TangentSemantic: return QSSGMesh::RuntimeMeshData::Attribute::TangentSemantic; case QQuick3DGeometry::Attribute::BinormalSemantic: return QSSGMesh::RuntimeMeshData::Attribute::BinormalSemantic; case QQuick3DGeometry::Attribute::JointSemantic: return QSSGMesh::RuntimeMeshData::Attribute::JointSemantic; case QQuick3DGeometry::Attribute::WeightSemantic: return QSSGMesh::RuntimeMeshData::Attribute::WeightSemantic; case QQuick3DGeometry::Attribute::ColorSemantic: return QSSGMesh::RuntimeMeshData::Attribute::ColorSemantic; case QQuick3DGeometry::Attribute::TargetPositionSemantic: return QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic; case QQuick3DGeometry::Attribute::TargetNormalSemantic: return QSSGMesh::RuntimeMeshData::Attribute::NormalSemantic; case QQuick3DGeometry::Attribute::TargetTangentSemantic: return QSSGMesh::RuntimeMeshData::Attribute::TangentSemantic; case QQuick3DGeometry::Attribute::TargetBinormalSemantic: return QSSGMesh::RuntimeMeshData::Attribute::BinormalSemantic; } Q_UNREACHABLE_RETURN(QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic); } static inline QSSGMesh::Mesh::ComponentType mapComponentType(QQuick3DGeometry::Attribute::ComponentType t) { switch (t) { case QQuick3DGeometry::Attribute::U16Type: return QSSGMesh::Mesh::ComponentType::UnsignedInt16; case QQuick3DGeometry::Attribute::U32Type: return QSSGMesh::Mesh::ComponentType::UnsignedInt32; case QQuick3DGeometry::Attribute::I32Type: return QSSGMesh::Mesh::ComponentType::Int32; case QQuick3DGeometry::Attribute::F32Type: return QSSGMesh::Mesh::ComponentType::Float32; } Q_UNREACHABLE_RETURN(QSSGMesh::Mesh::ComponentType::Float32); } /*! \internal */ QSSGRenderGraphObject *QQuick3DGeometry::updateSpatialNode(QSSGRenderGraphObject *node) { Q_D(QQuick3DGeometry); if (!node) { markAllDirty(); node = new QSSGRenderGeometry(); emit geometryNodeDirty(); } QQuick3DObject::updateSpatialNode(node); QSSGRenderGeometry *geometry = static_cast(node); if (d->m_geometryChanged) { geometry->clearVertexAndIndex(); geometry->setBounds(d->m_min, d->m_max); geometry->setStride(d->m_stride); // If there is vertex data but no stride is set, the user likely forgot to set the stride. if (d->m_stride < 1 && !d->m_vertexBuffer.isEmpty()) qWarning("%d is an invalid stride, was QQuick3DGeometry::setStride() called?", d->m_stride); geometry->setIndexData(d->m_indexBuffer); geometry->setVertexData(d->m_vertexBuffer); geometry->setPrimitiveType(mapPrimitiveType(d->m_primitiveType)); quint32 indexBufferComponentSize = 0; for (int i = 0; i < d->m_attributeCount; ++i) { const auto componentType = mapComponentType(d->m_attributes[i].componentType); geometry->addAttribute(mapSemantic(d->m_attributes[i].semantic), d->m_attributes[i].offset, componentType); if (d->m_attributes[i].semantic == Attribute::IndexSemantic) { if (componentType != QSSGMesh::Mesh::ComponentType::UnsignedInt16 && componentType != QSSGMesh::Mesh::ComponentType::UnsignedInt32) { qWarning("Index data can only be uint16 or uint32"); } indexBufferComponentSize = QSSGMesh::MeshInternal::byteSizeForComponentType(componentType); } else if (componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt16) { qWarning("Attributes cannot be uint16, only index data"); } } if (!d->m_indexBuffer.isEmpty() && !indexBufferComponentSize) { qWarning("IndexData has been set, but no index attribute found."); geometry->setIndexData({}); } // Implicitely add subset if none set for backwards compatibility if (d->m_subsets.isEmpty()) { quint32 offset = 0; quint32 count = 0; if (!d->m_indexBuffer.isEmpty() && indexBufferComponentSize) count = d->m_indexBuffer.size() / indexBufferComponentSize; else if (d->m_stride) count = d->m_vertexBuffer.size() / d->m_stride; geometry->addSubset(offset, count, d->m_min, d->m_max); } else { for (auto &s : d->m_subsets) geometry->addSubset(s.offset, s.count, s.boundsMin, s.boundsMax, s.name); } d->m_geometryChanged = false; emit geometryChanged(); } if (d->m_geometryBoundsChanged) { geometry->setBounds(d->m_min, d->m_max); emit geometryNodeDirty(); d->m_geometryBoundsChanged = false; } if (d->m_targetChanged) { geometry->clearTarget(); geometry->setTargetData(d->m_usesOldTargetSemantics ? d->m_vertexBuffer : d->m_targetBuffer); for (int i = 0; i < d->m_targetAttributeCount; ++i) { geometry->addTargetAttribute(d->m_targetAttributes[i].targetId, mapSemantic(d->m_targetAttributes[i].attr.semantic), d->m_targetAttributes[i].attr.offset, d->m_usesOldTargetSemantics ? d->m_stride : d->m_targetAttributes[i].stride); } d->m_targetChanged = false; } DebugViewHelpers::ensureDebugObjectName(geometry, this); return node; } QQuick3DGeometry::Attribute::Semantic QQuick3DGeometryPrivate::semanticFromName(const QByteArray &name) { static QMap semanticMap; if (semanticMap.isEmpty()) { semanticMap[QSSGMesh::MeshInternal::getPositionAttrName()] = QQuick3DGeometry::Attribute::PositionSemantic; semanticMap[QSSGMesh::MeshInternal::getNormalAttrName()] = QQuick3DGeometry::Attribute::NormalSemantic; semanticMap[QSSGMesh::MeshInternal::getUV0AttrName()] = QQuick3DGeometry::Attribute::TexCoord0Semantic; semanticMap[QSSGMesh::MeshInternal::getUV1AttrName()] = QQuick3DGeometry::Attribute::TexCoord1Semantic; semanticMap[QSSGMesh::MeshInternal::getTexTanAttrName()] = QQuick3DGeometry::Attribute::TangentSemantic; semanticMap[QSSGMesh::MeshInternal::getTexBinormalAttrName()] = QQuick3DGeometry::Attribute::BinormalSemantic; semanticMap[QSSGMesh::MeshInternal::getColorAttrName()] = QQuick3DGeometry::Attribute::ColorSemantic; semanticMap[QSSGMesh::MeshInternal::getWeightAttrName()] = QQuick3DGeometry::Attribute::WeightSemantic; semanticMap[QSSGMesh::MeshInternal::getJointAttrName()] = QQuick3DGeometry::Attribute::JointSemantic; } return semanticMap[name]; } QQuick3DGeometry::Attribute::ComponentType QQuick3DGeometryPrivate::toComponentType(QSSGMesh::Mesh::ComponentType ctype) { switch (ctype) { case QSSGMesh::Mesh::ComponentType::Float32: return QQuick3DGeometry::Attribute::F32Type; case QSSGMesh::Mesh::ComponentType::Int32: return QQuick3DGeometry::Attribute::I32Type; case QSSGMesh::Mesh::ComponentType::UnsignedInt16: return QQuick3DGeometry::Attribute::U16Type; case QSSGMesh::Mesh::ComponentType::UnsignedInt32: return QQuick3DGeometry::Attribute::U32Type; case QSSGMesh::Mesh::ComponentType::Float16: case QSSGMesh::Mesh::ComponentType::Float64: case QSSGMesh::Mesh::ComponentType::UnsignedInt8: case QSSGMesh::Mesh::ComponentType::Int8: case QSSGMesh::Mesh::ComponentType::Int16: case QSSGMesh::Mesh::ComponentType::UnsignedInt64: case QSSGMesh::Mesh::ComponentType::Int64: default: Q_ASSERT_X(0, "Incorrect datatype", "QQuick3DGeometryPrivate::toComponentType"); break; } return QQuick3DGeometry::Attribute::F32Type; } QT_END_NAMESPACE