Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgmesh.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qssgmesh_p.h"
5
6#include <QtCore/QVector>
7#include <QtQuick3DUtils/private/qssgdataref_p.h>
8#include <QtQuick3DUtils/private/qssglightmapuvgenerator_p.h>
9
10#include "meshoptimizer.h"
11
12#include <algorithm>
13
14QT_BEGIN_NAMESPACE
15
16namespace QSSGMesh {
17
18// fileId, fileVersion, offset, count
20
21// meshOffset, meshId, padding
23
24// fileId, fileVersion, flags, size
26
27// vertexBuffer, indexBuffer, subsets, joints, drawMode, winding
28static const size_t MESH_STRUCT_SIZE = 56;
29
30// vertex buffer entry list: nameOffset, componentType, componentCount, offset
32
33// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength
35// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength, lightmapSizeWidth, lightmapSizeHeight
36static const size_t SUBSET_STRUCT_SIZE_V5 = 48;
37// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength, lightmapSizeWidth, lightmapSizeHeight, lodCount
38static const size_t SUBSET_STRUCT_SIZE_V6 = 52;
39
40//lod entry: count, offset, distance
41static const size_t LOD_STRUCT_SIZE = 12;
42
76
94
96{
97 static char alignPadding[4] = {};
98
103
105 if (!header->isValid()) {
106 qWarning() << "Mesh data invalid";
109 qWarning() << "File version " << header->fileVersion << " newer than " << MeshDataHeader::FILE_VERSION;
111 qWarning() << "File version " << header->fileVersion << " older than " << MeshDataHeader::LEGACY_MESH_FILE_VERSION;
112 } else {
113 qWarning() << "Invalid file ID" << header->fileId;
114 }
115 return 0;
116 }
117
120
130
134 }
135
143
148
149 quint32 jointsOffsets; // unused
150 quint32 jointsCount; // unused
157
159
161 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
164 quint32 nameOffset; // unused
172 }
174 if (alignAmount)
176
177 // vertex buffer entry names
179 // used for recording the target attributes supported by the mesh
180 // and re-construting it when meeting attr_unsupported
182 for (auto &entry : mesh->m_vertexBuffer.entries) {
189 if (alignAmount)
191 // Old morph meshes' target attributes were appended sequentially
192 // behind vertex attributes. However, since the number of targets are restricted by 8
193 // the other attributes were named by "attr_unsupported"
194 // So just checking numTargets is safe with the above assumption and
195 // it will try to reconstruct the unsupported attributes.
196 if (numTargets > 0 || (!header->hasSeparateTargetBuffer() && entry.name.startsWith("attr_t"))) {
197 if (entry.name.sliced(6).startsWith("pos")) {
198 const quint32 targetId = entry.name.mid(9).toUInt();
199 // All the attributes of the first target should be recorded correctly.
200 if (targetId == 0)
206 } else if (entry.name.sliced(6).startsWith("norm")) {
207 const quint32 targetId = entry.name.mid(10).toUInt();
208 if (targetId == 0)
214 } else if (entry.name.sliced(6).startsWith("tan")) {
215 const quint32 targetId = entry.name.mid(9).toUInt();
216 if (targetId == 0)
222 } else if (entry.name.sliced(6).startsWith("binorm")) {
223 const quint32 targetId = entry.name.mid(12).toUInt();
224 if (targetId == 0)
230 } else if (entry.name.startsWith("attr_unsupported")) {
231 // Reconstruct
235 }
236 }
237 }
238
241 if (alignAmount)
243
246 if (alignAmount)
248
251 for (quint32 i = 0; i < subsetsCount; ++i) {
253 float minX;
254 float minY;
255 float minZ;
256 float maxX;
257 float maxY;
258 float maxZ;
259 quint32 nameOffset; // unused
261 >> subset.offset
262 >> minX
263 >> minY
264 >> minZ
265 >> maxX
266 >> maxY
267 >> maxZ
268 >> nameOffset
273 quint32 width = 0;
274 quint32 height = 0;
277 if (header->hasLodDataHint()) {
278 quint32 lodCount = 0;
282 } else {
284 }
285 } else {
288 }
290
291 }
293 if (alignAmount)
295
299 if (alignAmount)
301 }
302
306 // Read Level of Detail data here
307 for (auto &lod : meshSubset.lods) {
308 quint32 count = 0;
309 quint32 offset = 0;
310 float distance = 0.0;
312 lod.count = count;
313 lod.offset = offset;
316 }
317
319 }
321 if (alignAmount)
323
324
325 // Data for morphTargets
326 if (targetBufferEntriesCount > 0) {
328 entriesByteSize = 0;
329 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
332 quint32 nameOffset; // unused
340 }
342 if (alignAmount)
344
345 for (auto &entry : mesh->m_targetBuffer.entries) {
352 if (alignAmount)
354 }
355
357 } else {
358 // remove target entries from vertexbuffer entries
366 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
372 for (quint32 j = 0; j < vertexCount; ++j) {
373 // The number of old target components is fixed as 3
374 memcpy(dstBuf + j * 4 * sizeof(float),
376 3 * sizeof(float));
377 }
379 }
380 // now we don't need to have redundant targetbuffer entries
383 }
384 }
385
386 return header->sizeInBytes;
387}
388
397
398// The legacy, now-removed, insane mesh code used to use a "serialization"
399// strategy with dumping memory, yet combined with with an in-memory layout
400// that is different from what's in the file. In version 4 we no longer write
401// out valid offset values (see the // legacy offset comments), because the new
402// loader does not need them, and calculating them is not sensible, especially
403// due to the different layouts. We still do the alignment padding, even though
404// that's also legacy nonsense, but having that allows the reader not have to
405// branch based on the version.
406
408{
409 static const char alignPadding[4] = {};
410
414
415 const qint64 startPos = device->pos();
418
429
433 outputStream << quint32(0) // legacy offset
435
439 << subsetsCount;
440
441 outputStream << quint32(0) // legacy offset
442 << quint32(0); // legacy jointsCount
443
447 << winding;
448
450
452 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
456 const quint32 offset = entry.offset;
457 outputStream << quint32(0) // legacy offset
460 << offset;
462 }
464 if (alignAmount)
466
467 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
469 const quint32 nameLength = entry.name.size() + 1;
471 device->write(entry.name.constData(), nameLength); // with zero terminator included
473 if (alignAmount)
475 }
476
479 if (alignAmount)
481
484 if (alignAmount)
486
488 for (quint32 i = 0; i < subsetsCount; ++i) {
489 const Mesh::Subset &subset(mesh.m_subsets[i]);
492 const float minX = subset.bounds.min.x();
493 const float minY = subset.bounds.min.y();
494 const float minZ = subset.bounds.min.z();
495 const float maxX = subset.bounds.max.x();
496 const float maxY = subset.bounds.max.y();
497 const float maxZ = subset.bounds.max.z();
498 const quint32 nameLength = subset.name.size() + 1;
501 const quint32 lodCount = subset.lods.size();
503 << subsetOffset
504 << minX
505 << minY
506 << minZ
507 << maxX
508 << maxY
509 << maxZ;
510 outputStream << quint32(0) // legacy offset
511 << nameLength;
516 }
518 if (alignAmount)
520
521 for (quint32 i = 0; i < subsetsCount; ++i) {
522 const Mesh::Subset &subset(mesh.m_subsets[i]);
523 const char *utf16Name = reinterpret_cast<const char *>(subset.name.utf16());
524 const quint32 nameByteSize = (subset.name.size() + 1) * 2;
527 if (alignAmount)
529 }
530
531 // LOD data
533 for (quint32 i = 0; i < subsetsCount; ++i) {
534 const Mesh::Subset &subset(mesh.m_subsets[i]);
535 for (auto lod : subset.lods) {
536 const quint32 count = lod.count;
537 const quint32 offset = lod.offset;
538 const float distance = lod.distance;
541 }
542 }
544 if (alignAmount)
546
547 // Data for morphTargets
548 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
552 const quint32 offset = entry.offset;
553 outputStream << quint32(0) // legacy offset
556 << offset;
558 }
560 if (alignAmount)
562
563 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
565 const quint32 nameLength = entry.name.size() + 1;
567 device->write(entry.name.constData(), nameLength); // with zero terminator included
569 if (alignAmount)
571 }
572
574
575 const quint32 endPos = device->pos();
578 return sizeInBytes;
579}
580
599
601{
606 Mesh mesh;
608 if (size)
609 meshes.insert(it.key(), mesh);
610 else
611 qWarning("Failed to find mesh #%u", it.key());
612 }
613 return meshes;
614}
615
616static inline quint32 getAlignedOffset(quint32 offset, quint32 align)
617{
618 Q_ASSERT(align > 0);
619 const quint32 leftover = (align > 0) ? offset % align : 0;
620 if (leftover)
621 return offset + (align - leftover);
622 return offset;
623}
624
631{
632 Mesh mesh;
635 quint32 numItems = 0;
636 bool ok = true;
637
641
643 for (const AssetVertexEntry &entry : vbufEntries) {
644 // Ignore entries with no data.
645 if (entry.data.isEmpty())
646 continue;
647
652
653 if (entry.morphTargetId < 0) {
656
657 if (entry.data.size() % alignment != 0) {
658 Q_ASSERT(false);
659 ok = false;
660 }
661
663 if (numItems == 0) {
665 } else if (numItems != localNumItems) {
666 Q_ASSERT(false);
667 ok = false;
669 }
670
673
678 } else {
679 if (!targetCompStride) {
684 }
685
686 // At assets, these entries are appended sequentially from target 0 to target N - 1
687 // It is safe to calculate the offset by the data size
692
693 // Note: the targetBuffer will not be interleaved,
694 // data will be just appended in order and used for a texture array.
695 if (entry.morphTargetId == 0)
697
699 }
700 }
701
702 if (!ok)
703 return Mesh();
704
706
707 // Packed interleave the data.
708 for (quint32 idx = 0; idx < numItems; ++idx) {
710 for (const AssetVertexEntry &entry : vEntries) {
711 if (entry.data.isEmpty())
712 continue;
713
716 const quint32 offset = byteSize * idx;
718 if (newOffset != dataOffset) {
721 }
722
725 }
727 }
728
731
732 for (const AssetMeshSubset &subset : subsets) {
737
738 // TODO: QTBUG-102026
747 subset.offset);
750 }
751
754
756 }
757
760
761 return mesh;
762}
763
765{
766 if (data.m_vertexBuffer.size() == 0) {
767 *error = QObject::tr("Vertex buffer empty");
768 return Mesh();
769 }
770 if (data.m_attributeCount == 0) {
771 *error = QObject::tr("No attributes defined");
772 return Mesh();
773 }
774
775 Mesh mesh;
778
779 for (int i = 0; i < data.m_attributeCount; ++i) {
783 } else {
784 const char *name = nullptr;
785 switch (att.semantic) {
788 break;
791 break;
794 break;
797 break;
800 break;
803 break;
806 break;
809 break;
812 break;
813 default:
814 *error = QObject::tr("Warning: Invalid attribute semantic: %1")
815 .arg(att.semantic);
816 return Mesh();
817 }
818
823 entry.name = name;
825 }
826 }
827
829 // Only interleaved vertex attribute packing is supported, both internally
830 // and in the QQuick3DGeometry API, hence the per-vertex buffer stride.
834
835 if (!data.m_targetBuffer.isEmpty()) {
840
846 return (a.targetId == b.targetId) ? a.attr.semantic < b.attr.semantic :
847 a.targetId < b.targetId; });
848 for (int i = 0; i < data.m_targetAttributeCount; ++i) {
850 const int stride = (sortedAttribs[i].stride < 1) ? att.componentCount() * sizeof(float)
852 const char *name = nullptr;
853 switch (att.semantic) {
856 break;
859 break;
862 break;
865 break;
868 break;
871 break;
875 *error = QObject::tr("Warning: Invalid target attribute semantic: %1")
876 .arg(att.semantic);
877 continue;
880 break;
881 default:
882 *error = QObject::tr("Warning: Invalid target attribute semantic: %1")
883 .arg(att.semantic);
884 return Mesh();
885 }
887 const char *srcBuf = data.m_targetBuffer.constData() + att.offset;
889 if (stride == 4 * sizeof(float)) {
891 } else {
892 for (quint32 j = 0; j < vertexCount; ++j) {
893 memcpy(dstBuf + j * 4 * sizeof(float),
894 srcBuf + j * stride,
895 att.componentCount() * sizeof(float));
896 }
897 }
898
899 if (sortedAttribs[i].targetId == 0) {
904 entry.name = name;
906 }
907 }
909 }
910 return mesh;
911}
912
914{
916 quint32 newId = 1;
918
919 if (device->size() > 0) {
921 if (!header.isValid()) {
922 qWarning("There is existing data, but mesh file header is invalid; cannot save");
923 return 0;
924 }
925 for (auto it = header.meshEntries.cbegin(), end = header.meshEntries.cend(); it != end; ++it) {
926 if (id) {
927 Q_ASSERT(id != it.key());
928 newId = id;
929 } else {
930 newId = qMax(newId, it.key() + 1);
931 }
932 }
934 } else {
936 }
937
938 // the new mesh data overwrites the entry list and file header
940 const qint64 meshOffset = device->pos();
942
944 // skip the space for the mesh header for now
947 // now the mesh header is ready to be written out
951 // write out new entry list and file header
953
954 return newId;
955}
956
964{
967 Q_ASSERT(false);
968 return result;
969 }
970
973 Q_ASSERT(false);
974 return result;
975 }
976
980 const char *indexSrcPtr = indexBufferData.constData();
981
982 for (quint32 idx = 0, numItems = subsetCount; idx < numItems; ++idx) {
984 continue;
985
986 quint32 vertexIdx = 0;
987 switch (indexComponentByteSize) {
988 case 2:
989 vertexIdx = reinterpret_cast<const quint16 *>(indexSrcPtr)[idx + subsetOffset];
990 break;
991 case 4:
992 vertexIdx = reinterpret_cast<const quint32 *>(indexSrcPtr)[idx + subsetOffset];
993 break;
994 default:
996 break;
997 }
998
1000 float v[3];
1001 if (finalOffset + sizeof(v) <= vertexBufferByteSize) {
1002 memcpy(v, vertexSrcPtr + finalOffset, sizeof(v));
1003 result.include(QVector3D(v[0], v[1], v[2]));
1004 } else {
1005 Q_ASSERT(false);
1006 }
1007 }
1008
1009 return result;
1010}
1011
1013{
1016 if (vbe.name == lightmapAttrName)
1017 return true;
1018 }
1019 return false;
1020}
1021
1023{
1026 const char *uvAttrName = MeshInternal::getUV0AttrName();
1028
1029 // this function should do nothing if there is already an attr_lightmapuv
1031 return true;
1032
1035 if (!srcVertexStride) {
1036 qWarning("Lightmap UV unwrapping encountered a Mesh with 0 vertex stride, this cannot happen");
1037 return false;
1038 }
1039 if (m_indexBuffer.data.isEmpty()) {
1040 qWarning("Lightmap UV unwrapping encountered a Mesh without index data, this cannot happen");
1041 return false;
1042 }
1043
1047
1049 if (vbe.name == posAttrName) {
1050 if (vbe.componentCount != 3) {
1051 qWarning("Lightmap UV unwrapping encountered a Mesh non-float3 position data, this cannot happen");
1052 return false;
1053 }
1055 } else if (vbe.name == normalAttrName) {
1056 if (vbe.componentCount != 3) {
1057 qWarning("Lightmap UV unwrapping encountered a Mesh non-float3 normal data, this cannot happen");
1058 return false;
1059 }
1061 } else if (vbe.name == uvAttrName) {
1062 if (vbe.componentCount != 2) {
1063 qWarning("Lightmap UV unwrapping encountered a Mesh non-float2 UV0 data, this cannot happen");
1064 return false;
1065 }
1067 }
1068 }
1069
1070 if (positionOffset == UINT32_MAX) {
1071 qWarning("Lightmap UV unwrapping encountered a Mesh without vertex positions, this cannot happen");
1072 return false;
1073 }
1074 // normal and uv0 are optional
1075
1077 QByteArray positionData(vertexCount * 3 * sizeof(float), Qt::Uninitialized);
1078 float *posPtr = reinterpret_cast<float *>(positionData.data());
1079 for (qsizetype i = 0; i < vertexCount; ++i) {
1080 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1081 const float *srcPos = reinterpret_cast<const float *>(vertexBasePtr + positionOffset);
1083 srcV.setX(*srcPos++);
1084 srcV.setY(*srcPos++);
1085 srcV.setZ(*srcPos++);
1086 // We scale the positions here, but not on the source mesh, so that the uv unwrapper works on
1087 // the positions that the model will have in the scene after its scaling has been applied. This
1088 // way the texels-per-unit will be correct.
1089 srcV = scale.map(srcV);
1090 *posPtr++ = srcV.x();
1091 *posPtr++ = srcV.y();
1092 *posPtr++ = srcV.z();
1093 }
1094
1096 if (normalOffset != UINT32_MAX) {
1097 normalData.resize(vertexCount * 3 * sizeof(float));
1098 float *normPtr = reinterpret_cast<float *>(normalData.data());
1099 for (qsizetype i = 0; i < vertexCount; ++i) {
1100 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1101 const float *srcNormal = reinterpret_cast<const float *>(vertexBasePtr + normalOffset);
1102 *normPtr++ = *srcNormal++;
1103 *normPtr++ = *srcNormal++;
1104 *normPtr++ = *srcNormal++;
1105 }
1106 }
1107
1109 if (uvOffset != UINT32_MAX) {
1110 uvData.resize(vertexCount * 2 * sizeof(float));
1111 float *uvPtr = reinterpret_cast<float *>(uvData.data());
1112 for (qsizetype i = 0; i < vertexCount; ++i) {
1113 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1114 const float *srcUv = reinterpret_cast<const float *>(vertexBasePtr + uvOffset);
1115 *uvPtr++ = *srcUv++;
1116 *uvPtr++ = *srcUv++;
1117 }
1118 }
1119
1124 if (!r.isValid())
1125 return false;
1126
1127 // the result can have more (but never less) vertices than the input
1128 const int newVertexCount = r.vertexMap.size();
1129
1130 // r.indexData contains the new index data that has the same number of elements as before
1131 const quint32 *newIndex = reinterpret_cast<const quint32 *>(r.indexData.constData());
1133 if (r.indexData.size() != m_indexBuffer.data.size()) {
1134 qWarning("Index buffer size mismatch after lightmap UV unwrapping");
1135 return false;
1136 }
1137 quint32 *indexDst = reinterpret_cast<quint32 *>(m_indexBuffer.data.data());
1139 } else {
1140 if (r.indexData.size() != m_indexBuffer.data.size() * 2) {
1141 qWarning("Index buffer size mismatch after lightmap UV unwrapping");
1142 return false;
1143 }
1144 quint16 *indexDst = reinterpret_cast<quint16 *>(m_indexBuffer.data.data());
1145 for (size_t i = 0, count = m_indexBuffer.data.size() / sizeof(quint16); i != count; ++i)
1146 *indexDst++ = *newIndex++;
1147 }
1148
1151
1155 char *dst = data.data();
1156 for (qsizetype i = 0; i < vertexCount; ++i) {
1158 dst += byteSize;
1159 }
1160 switch (vbe.componentType) {
1163 break;
1164 case ComponentType::Int8:
1166 break;
1169 break;
1170 case ComponentType::Int16:
1172 break;
1175 break;
1176 case ComponentType::Int32:
1178 break;
1181 break;
1182 case ComponentType::Int64:
1184 break;
1185 case ComponentType::Float16:
1187 break;
1188 case ComponentType::Float32:
1190 break;
1191 case ComponentType::Float64:
1193 break;
1194 }
1195 }
1196
1202
1205
1207 for (int vertexIdx = 0; vertexIdx < newVertexCount; ++vertexIdx) {
1208 quint32 dataOffset = 0;
1209 for (int vbIdx = 0, end = m_vertexBuffer.entries.size(); vbIdx != end; ++vbIdx) {
1211
1216
1217 if (newOffset != dataOffset) {
1220 }
1221
1222 if (vertexIdx == 0)
1224
1227 }
1228
1229 const quint32 byteSize = 2 * sizeof(float);
1231 if (newOffset != dataOffset) {
1234 }
1235
1236 if (vertexIdx == 0)
1238
1241
1242 if (vertexIdx == 0)
1244 }
1245
1247
1249
1251 for (Subset &subset : m_subsets)
1253
1254 return true;
1255}
1256
1257size_t 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)
1258{
1259 return meshopt_simplify(destination, indices, indexCount, vertexPositions, vertexCount, vertexPositionsStride, targetIndexCount, targetError, options, resultError);
1260}
1261
1262float simplifyScale(const float *vertexPositions, size_t vertexCount, size_t vertexPositionsStride)
1263{
1264 return meshopt_simplifyScale(vertexPositions, vertexCount, vertexPositionsStride);
1265}
1266
1267void optimizeVertexCache(unsigned int *destination, const unsigned int *indices, size_t indexCount, size_t vertexCount)
1268{
1269 meshopt_optimizeVertexCache(destination, indices, indexCount, vertexCount);
1270}
1271
1272} // namespace QSSGMesh
1273
1274QT_END_NAMESPACE
float simplifyScale(const float *vertexPositions, size_t vertexCount, size_t vertexPositionsStride)
static const size_t MESH_STRUCT_SIZE
Definition qssgmesh.cpp:28
static const size_t SUBSET_STRUCT_SIZE_V3_V4
Definition qssgmesh.cpp:34
static const size_t VERTEX_BUFFER_ENTRY_STRUCT_SIZE
Definition qssgmesh.cpp:31
static const size_t MULTI_ENTRY_STRUCT_SIZE
Definition qssgmesh.cpp:22
static const size_t SUBSET_STRUCT_SIZE_V6
Definition qssgmesh.cpp:38
size_t 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)
static const size_t MULTI_HEADER_STRUCT_SIZE
Definition qssgmesh.cpp:19
static const size_t LOD_STRUCT_SIZE
Definition qssgmesh.cpp:41
void optimizeVertexCache(unsigned int *destination, const unsigned int *indices, size_t indexCount, size_t vertexCount)
static const size_t MESH_HEADER_STRUCT_SIZE
Definition qssgmesh.cpp:25
static const size_t SUBSET_STRUCT_SIZE_V5
Definition qssgmesh.cpp:36
static quint32 getAlignedOffset(quint32 offset, quint32 align)
Definition qssgmesh.cpp:616