Revision control
Copy as Markdown
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#ifndef QSSGMESHUTILITIES_P_H
#define QSSGMESHUTILITIES_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtQuick3DUtils/private/qtquick3dutilsglobal_p.h>
#include <QtQuick3DUtils/private/qssgbounds3_p.h>
#include <QtQuick3DUtils/private/qssgrenderbasetypes_p.h>
#include <QtCore/qstring.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qiodevice.h>
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
#include <QtGui/qvectornd.h>
QT_BEGIN_NAMESPACE
struct QSSGRenderVertexBufferEntry
{
QByteArray m_name;
/** Datatype of the this entry points to in the buffer */
QSSGRenderComponentType m_componentType;
/** Number of components of each data member. 1,2,3, or 4. Don't be stupid.*/
quint32 m_numComponents;
/** Offset from the beginning of the buffer of the first item */
quint32 m_firstItemOffset;
QSSGRenderVertexBufferEntry(const QByteArray &nm,
QSSGRenderComponentType type,
quint32 numComponents,
quint32 firstItemOffset = 0)
: m_name(nm), m_componentType(type), m_numComponents(numComponents), m_firstItemOffset(firstItemOffset)
{
}
QSSGRenderVertexBufferEntry()
: m_componentType(QSSGRenderComponentType::Float32), m_numComponents(0), m_firstItemOffset(0)
{
}
QSSGRenderVertexBufferEntry(const QSSGRenderVertexBufferEntry &inOther)
: m_name(inOther.m_name)
, m_componentType(inOther.m_componentType)
, m_numComponents(inOther.m_numComponents)
, m_firstItemOffset(inOther.m_firstItemOffset)
{
}
QSSGRenderVertexBufferEntry &operator=(const QSSGRenderVertexBufferEntry &inOther)
{
if (this != &inOther) {
m_name = inOther.m_name;
m_componentType = inOther.m_componentType;
m_numComponents = inOther.m_numComponents;
m_firstItemOffset = inOther.m_firstItemOffset;
}
return *this;
}
};
namespace QSSGMesh {
struct AssetVertexEntry;
struct AssetMeshSubset;
struct RuntimeMeshData;
struct AssetLodEntry;
class Q_QUICK3DUTILS_EXPORT Mesh
{
public:
using DrawMode = QSSGRenderDrawMode;
using Winding = QSSGRenderWinding;
using ComponentType = QSSGRenderComponentType;
struct VertexBufferEntry {
ComponentType componentType = ComponentType::Float32;
quint32 componentCount = 0;
quint32 offset = 0;
QByteArray name;
QSSGRenderVertexBufferEntry toRenderVertexBufferEntry() const {
return QSSGRenderVertexBufferEntry(name,
QSSGRenderComponentType(componentType),
componentCount,
offset);
}
};
struct VertexBuffer {
quint32 stride = 0;
QVector<VertexBufferEntry> entries;
QByteArray data;
};
struct IndexBuffer {
ComponentType componentType = ComponentType::UnsignedInt32;
QByteArray data;
};
struct TargetBuffer {
quint32 numTargets = 0;
QVector<VertexBufferEntry> entries;
QByteArray data;
};
struct SubsetBounds {
QVector3D min;
QVector3D max;
};
struct Lod {
quint32 count = 0;
quint32 offset = 0;
float distance = 0.0f;
};
struct Subset {
QString name;
SubsetBounds bounds;
quint32 count = 0;
quint32 offset = 0;
QSize lightmapSizeHint;
QVector<Lod> lods;
};
// can just return by value (big data is all implicitly shared)
VertexBuffer vertexBuffer() const { return m_vertexBuffer; }
IndexBuffer indexBuffer() const { return m_indexBuffer; }
TargetBuffer targetBuffer() const { return m_targetBuffer; }
QVector<Subset> subsets() const { return m_subsets; }
// id 0 == first, otherwise has to match
static Mesh loadMesh(QIODevice *device, quint32 id = 0);
static QMap<quint32, Mesh> loadAll(QIODevice *device);
static Mesh fromAssetData(const QVector<AssetVertexEntry> &vbufEntries,
const QByteArray &indexBufferData,
ComponentType indexComponentType,
const QVector<AssetMeshSubset> &subsets,
quint32 numTargets = 0, quint32 numTargetComps = 0);
static Mesh fromRuntimeData(const RuntimeMeshData &data,
QString *error);
bool isValid() const { return !m_subsets.isEmpty(); }
DrawMode drawMode() const { return m_drawMode; }
Winding winding() const { return m_winding; }
// id 0 == generate new id; otherwise uses it as-is, and must be an unused one
quint32 save(QIODevice *device, quint32 id = 0) const;
bool hasLightmapUVChannel() const;
bool createLightmapUVChannel(float texelsPerUnit, const QMatrix4x4 &scale);
private:
DrawMode m_drawMode = DrawMode::Triangles;
Winding m_winding = Winding::CounterClockwise;
VertexBuffer m_vertexBuffer;
IndexBuffer m_indexBuffer;
TargetBuffer m_targetBuffer;
QVector<Subset> m_subsets;
friend struct MeshInternal;
};
struct Q_QUICK3DUTILS_EXPORT AssetVertexEntry // for asset importer plugins (Assimp, FBX)
{
QByteArray name;
QByteArray data;
Mesh::ComponentType componentType = Mesh::ComponentType::Float32;
quint32 componentCount = 0;
qint32 morphTargetId = -1; // -1 menas that this entry belongs to the original mesh.
};
struct Q_QUICK3DUTILS_EXPORT AssetMeshSubset // for asset importer plugins (Assimp, FBX)
{
QString name;
quint32 count = 0;
quint32 offset = 0;
quint32 boundsPositionEntryIndex = std::numeric_limits<quint32>::max();
quint32 lightmapWidth = 0;
quint32 lightmapHeight = 0;
QVector<Mesh::Lod> lods;
};
struct Q_QUICK3DUTILS_EXPORT RuntimeMeshData // for custom geometry (QQuick3DGeometry, QSSGRenderGeometry)
{
struct Attribute {
enum Semantic {
IndexSemantic = 0,
PositionSemantic, // attr_pos
NormalSemantic, // attr_norm
TexCoordSemantic, // attr_uv0
TangentSemantic, // attr_textan
BinormalSemantic, // attr_binormal
JointSemantic, // attr_joints
WeightSemantic, // attr_weights
ColorSemantic, // attr_color
TexCoord1Semantic, // attr_uv1
TexCoord0Semantic = TexCoordSemantic // attr_uv0
};
Semantic semantic = PositionSemantic;
Mesh::ComponentType componentType = Mesh::ComponentType::Float32;
int offset = 0;
int componentCount() const {
switch (semantic) {
case IndexSemantic: return 1;
case PositionSemantic: return 3;
case NormalSemantic: return 3;
case TexCoord0Semantic: return 2;
case TexCoord1Semantic: return 2;
case TangentSemantic: return 3;
case BinormalSemantic: return 3;
case JointSemantic: return 4;
case WeightSemantic: return 4;
case ColorSemantic: return 4;
}
Q_UNREACHABLE_RETURN(0);
}
};
struct TargetAttribute {
Attribute attr;
int targetId;
int stride;
};
static const int MAX_ATTRIBUTES = 16;
static const int MAX_TARGET_ATTRIBUTES = 32;
void clear()
{
clearVertexAndIndex();
clearTarget();
}
void clearVertexAndIndex()
{
m_vertexBuffer.clear();
m_indexBuffer.clear();
m_subsets.clear();
m_attributeCount = 0;
m_primitiveType = Mesh::DrawMode::Triangles;
}
void clearTarget()
{
m_targetBuffer.clear();
m_targetAttributeCount = 0;
}
QByteArray m_vertexBuffer;
QByteArray m_indexBuffer;
QByteArray m_targetBuffer;
QVector<Mesh::Subset> m_subsets;
Attribute m_attributes[MAX_ATTRIBUTES];
int m_attributeCount = 0;
TargetAttribute m_targetAttributes[MAX_TARGET_ATTRIBUTES];
int m_targetAttributeCount = 0;
Mesh::DrawMode m_primitiveType = Mesh::DrawMode::Triangles;
int m_stride = 0;
};
struct Q_QUICK3DUTILS_EXPORT MeshInternal
{
struct MultiMeshInfo {
quint32 fileId = 0;
quint32 fileVersion = 0;
QMap<quint32, quint64> meshEntries;
static const quint32 FILE_ID = 555777497;
static const quint32 FILE_VERSION = 1;
bool isValid() const {
return fileId == FILE_ID && fileVersion == FILE_VERSION;
}
static MultiMeshInfo withDefaults() {
return { FILE_ID, FILE_VERSION, {} };
}
};
struct MeshDataHeader {
quint32 fileId = 0;
quint16 fileVersion = 0;
quint16 flags = 0;
quint32 sizeInBytes = 0;
static const quint32 FILE_ID = 3365961549;
// Version 3 and 4 is only different in the "offset" values that are
// all zeroes in version 4 because they are not used by the new mesh
// reader. So to support both with the new loader, no branching is
// needed at all, it just needs to accept both versions.
static const quint32 LEGACY_MESH_FILE_VERSION = 3;
// Version 5 differs from 4 with the added lightmapSizeHint per subset.
// This needs branching in the deserializer.
// Version 6 differs from 5 with additional lodCount per subset as well
// as a list of Level of Detail data after the subset names.
// Version 7 will split the morph target data
static const quint32 FILE_VERSION = 7;
static MeshDataHeader withDefaults() {
return { FILE_ID, FILE_VERSION, 0, 0 };
}
bool isValid() const {
return fileId == FILE_ID
&& fileVersion <= FILE_VERSION
&& fileVersion >= LEGACY_MESH_FILE_VERSION;
}
bool hasLightmapSizeHint() const {
return fileVersion >= 5;
}
bool hasLodDataHint() const {
return fileVersion >= 6;
}
bool hasSeparateTargetBuffer() const {
return fileVersion >= 7;
}
};
struct MeshOffsetTracker {
quint32 startOffset = 0;
quint32 byteCounter = 0;
MeshOffsetTracker(int offset)
: startOffset(offset) {}
int offset() {
return startOffset + byteCounter;
}
quint32 alignedAdvance(int advanceAmount) {
advance(advanceAmount);
quint32 alignmentAmount = 4 - (byteCounter % 4);
byteCounter += alignmentAmount;
return alignmentAmount;
}
void advance(int advanceAmount) {
byteCounter += advanceAmount;
}
};
struct Subset {
QByteArray rawNameUtf16;
quint32 nameLength = 0;
Mesh::SubsetBounds bounds;
quint32 offset = 0;
quint32 count = 0;
QSize lightmapSizeHint;
quint32 lodCount = 0;
Mesh::Subset toMeshSubset() const {
Mesh::Subset subset;
// Only as many units as were actually read, less the terminator.
const qsizetype units = qMin(qsizetype(nameLength), rawNameUtf16.size() / 2);
if (units > 0)
subset.name = QString::fromUtf16(reinterpret_cast<const char16_t *>(rawNameUtf16.constData()), units - 1);
subset.bounds.min = bounds.min;
subset.bounds.max = bounds.max;
subset.count = count;
subset.offset = offset;
subset.lightmapSizeHint = lightmapSizeHint;
subset.lods.resize(lodCount);
return subset;
}
};
static MultiMeshInfo readFileHeader(QIODevice *device);
static void writeFileHeader(QIODevice *device, const MultiMeshInfo &meshFileInfo);
static quint64 readMeshData(QIODevice *device, quint64 offset, Mesh *mesh, MeshDataHeader *header);
static void writeMeshHeader(QIODevice *device, const MeshDataHeader &header);
static quint64 writeMeshData(QIODevice *device, const Mesh &mesh);
static quint32 byteSizeForComponentType(Mesh::ComponentType componentType) { return quint32(QSSGBaseTypeHelpers::getSizeOfType(componentType)); }
static const char *getPositionAttrName() { return "attr_pos"; }
static const char *getNormalAttrName() { return "attr_norm"; }
static const char *getUV0AttrName() { return "attr_uv0"; }
static const char *getUV1AttrName() { return "attr_uv1"; }
static const char *getLightmapUVAttrName() { return "attr_lightmapuv"; }
static const char *getTexTanAttrName() { return "attr_textan"; }
static const char *getTexBinormalAttrName() { return "attr_binormal"; }
static const char *getColorAttrName() { return "attr_color"; }
static const char *getJointAttrName() { return "attr_joints"; }
static const char *getWeightAttrName() { return "attr_weights"; }
static QSSGBounds3 calculateSubsetBounds(const Mesh::VertexBufferEntry &entry,
const QByteArray &vertexBufferData,
quint32 vertexBufferStride,
const QByteArray &indexBufferData,
Mesh::ComponentType indexComponentType,
quint32 subsetCount,
quint32 subsetOffset);
};
size_t Q_QUICK3DUTILS_EXPORT simplifyMesh(unsigned int* destination,
const unsigned int* indices,
size_t indexCount,
const float* vertexPositions,
size_t vertexCount,
size_t vertexPositionsStride,
size_t targetIndexCount,
float targetError,
unsigned int options,
float* resultError);
float Q_QUICK3DUTILS_EXPORT simplifyScale(const float* vertexPositions,
size_t vertexCount,
size_t vertexPositionsStride);
void Q_QUICK3DUTILS_EXPORT optimizeVertexCache(unsigned int* destination,
const unsigned int* indices,
size_t indexCount,
size_t vertexCount);
// A vertex attribute stream for welding; mirrors meshopt_Stream
struct MeshVertexStream
{
const void *data;
size_t elementSize;
size_t stride;
};
// The largest number of attribute streams generateVertexRemap() accepts
constexpr size_t maxVertexStreams = 16;
// Builds a remap table that welds bitwise-identical vertices across all
// given attribute streams, returning the unique vertex count. streamCount
// must be between 1 and maxVertexStreams, and every stream's elementSize
// must be no larger than its stride.
size_t Q_QUICK3DUTILS_EXPORT generateVertexRemap(unsigned int *destination,
const unsigned int *indices,
size_t indexCount,
size_t vertexCount,
const MeshVertexStream *streams,
size_t streamCount);
void Q_QUICK3DUTILS_EXPORT remapVertexBuffer(void *destination,
const void *vertices,
size_t vertexCount,
size_t vertexSize,
const unsigned int *remap);
void Q_QUICK3DUTILS_EXPORT remapIndexBuffer(unsigned int *destination,
const unsigned int *indices,
size_t indexCount,
const unsigned int *remap);
// destination takes indexCount * 4 floats: tangent xyz and bitangent sign
void Q_QUICK3DUTILS_EXPORT generateTangents(float *destination,
const unsigned int *indices,
size_t indexCount,
const float *vertexPositions,
size_t vertexCount,
size_t vertexPositionsStride,
const float *vertexNormals,
size_t vertexNormalsStride,
const float *vertexUvs,
size_t vertexUvsStride);
struct MeshLevelOfDetail
{
float distance = 0.0f;
QVector<quint32> indexes;
};
struct MeshVertexSplit
{
quint32 sourceIndex = 0;
QVector3D normal;
};
// Generates simplified index buffers (levels of detail) for a triangle mesh,
// returned from smallest to largest index count. When normals are
// recalculated, vertices whose new normal deviates more than
// normalSplitAngle from the original are split: the returned LOD indexes
// reference new vertices starting at positions.size(), and the caller must
// append a copy of each reported source vertex with the given normal, in
// order. splitVertices is cleared on entry, so it always describes only the
// call that produced it.
//
// Passing 0.0 for both angles disables normal recalculation, which is also
// what happens when normals is empty. Otherwise normals must be the same
// length as positions. Note that preserving the normals this way also keeps
// vertices that only differ in their normal from being welded together, so a
// mesh with hard edges yields fewer levels, or none at all.
QVector<MeshLevelOfDetail> Q_QUICK3DUTILS_EXPORT generateMeshLevelsOfDetail(
const QVector<QVector3D> &positions,
const QVector<QVector3D> &normals,
const QVector<quint32> &indexes,
QVector<MeshVertexSplit> &splitVertices,
float normalMergeAngle = 60.0f,
float normalSplitAngle = 25.0f);
} // namespace QSSGMesh
QT_END_NAMESPACE
#endif // QSSGMESHUTILITIES_P_H