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// Qt-Security score:significant reason:default
4
5
6#include "qssgmesh_p.h"
7
8#include <QtCore/QVector>
9#include <QtQuick3DUtils/private/qssgdataref_p.h>
10#include <QtQuick3DUtils/private/qssglightmapuvgenerator_p.h>
11
12#include "meshoptimizer.h"
13
14#include <algorithm>
15
16QT_BEGIN_NAMESPACE
17
18namespace QSSGMesh {
19
20// fileId, fileVersion, offset, count
22
23// meshOffset, meshId, padding
25
26// fileId, fileVersion, flags, size
28
29// vertexBuffer, indexBuffer, subsets, joints, drawMode, winding
30static const size_t MESH_STRUCT_SIZE = 56;
31
32// vertex buffer entry list: nameOffset, componentType, componentCount, offset
34
35// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength
37// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength, lightmapSizeWidth, lightmapSizeHeight
38static const size_t SUBSET_STRUCT_SIZE_V5 = 48;
39// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength, lightmapSizeWidth, lightmapSizeHeight, lodCount
40static const size_t SUBSET_STRUCT_SIZE_V6 = 52;
41
42//lod entry: count, offset, distance
43static const size_t LOD_STRUCT_SIZE = 12;
44
78
96
98{
99 static char alignPadding[4] = {};
100
105
107 if (!header->isValid()) {
108 qWarning() << "Mesh data invalid";
111 qWarning() << "File version " << header->fileVersion << " newer than " << MeshDataHeader::FILE_VERSION;
113 qWarning() << "File version " << header->fileVersion << " older than " << MeshDataHeader::LEGACY_MESH_FILE_VERSION;
114 } else {
115 qWarning() << "Invalid file ID" << header->fileId;
116 }
117 return 0;
118 }
119
122
132
136 }
137
145
150
151 quint32 jointsOffsets; // unused
152 quint32 jointsCount; // unused
159
161
163 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
166 quint32 nameOffset; // unused
174 }
176 if (alignAmount)
178
179 // vertex buffer entry names
181 // used for recording the target attributes supported by the mesh
182 // and re-construting it when meeting attr_unsupported
184 for (auto &entry : mesh->m_vertexBuffer.entries) {
191 if (alignAmount)
193 // Old morph meshes' target attributes were appended sequentially
194 // behind vertex attributes. However, since the number of targets are restricted by 8
195 // the other attributes were named by "attr_unsupported"
196 // So just checking numTargets is safe with the above assumption and
197 // it will try to reconstruct the unsupported attributes.
198 if (numTargets > 0 || (!header->hasSeparateTargetBuffer() && entry.name.startsWith("attr_t"))) {
199 if (entry.name.sliced(6).startsWith("pos")) {
200 const quint32 targetId = entry.name.mid(9).toUInt();
201 // All the attributes of the first target should be recorded correctly.
202 if (targetId == 0)
208 } else if (entry.name.sliced(6).startsWith("norm")) {
209 const quint32 targetId = entry.name.mid(10).toUInt();
210 if (targetId == 0)
216 } else if (entry.name.sliced(6).startsWith("tan")) {
217 const quint32 targetId = entry.name.mid(9).toUInt();
218 if (targetId == 0)
224 } else if (entry.name.sliced(6).startsWith("binorm")) {
225 const quint32 targetId = entry.name.mid(12).toUInt();
226 if (targetId == 0)
232 } else if (entry.name.startsWith("attr_unsupported")) {
233 // Reconstruct
237 }
238 }
239 }
240
243 if (alignAmount)
245
248 if (alignAmount)
250
253 for (quint32 i = 0; i < subsetsCount; ++i) {
255 float minX;
256 float minY;
257 float minZ;
258 float maxX;
259 float maxY;
260 float maxZ;
261 quint32 nameOffset; // unused
263 >> subset.offset
264 >> minX
265 >> minY
266 >> minZ
267 >> maxX
268 >> maxY
269 >> maxZ
270 >> nameOffset
275 quint32 width = 0;
276 quint32 height = 0;
279 if (header->hasLodDataHint()) {
280 quint32 lodCount = 0;
284 } else {
286 }
287 } else {
290 }
292
293 }
295 if (alignAmount)
297
301 if (alignAmount)
303 }
304
308 // Read Level of Detail data here
309 for (auto &lod : meshSubset.lods) {
310 quint32 count = 0;
311 quint32 offset = 0;
312 float distance = 0.0;
314 lod.count = count;
315 lod.offset = offset;
318 }
319
321 }
323 if (alignAmount)
325
326
327 // Data for morphTargets
328 if (targetBufferEntriesCount > 0) {
330 entriesByteSize = 0;
331 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
334 quint32 nameOffset; // unused
342 }
344 if (alignAmount)
346
347 for (auto &entry : mesh->m_targetBuffer.entries) {
354 if (alignAmount)
356 }
357
359 } else {
360 // remove target entries from vertexbuffer entries
368 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
374 for (quint32 j = 0; j < vertexCount; ++j) {
375 // The number of old target components is fixed as 3
376 memcpy(dstBuf + j * 4 * sizeof(float),
378 3 * sizeof(float));
379 }
381 }
382 // now we don't need to have redundant targetbuffer entries
385 }
386 }
387
388 return header->sizeInBytes;
389}
390
399
400// The legacy, now-removed, insane mesh code used to use a "serialization"
401// strategy with dumping memory, yet combined with with an in-memory layout
402// that is different from what's in the file. In version 4 we no longer write
403// out valid offset values (see the // legacy offset comments), because the new
404// loader does not need them, and calculating them is not sensible, especially
405// due to the different layouts. We still do the alignment padding, even though
406// that's also legacy nonsense, but having that allows the reader not have to
407// branch based on the version.
408
410{
411 static const char alignPadding[4] = {};
412
416
417 const qint64 startPos = device->pos();
420
431
435 outputStream << quint32(0) // legacy offset
437
441 << subsetsCount;
442
443 outputStream << quint32(0) // legacy offset
444 << quint32(0); // legacy jointsCount
445
449 << winding;
450
452
454 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
458 const quint32 offset = entry.offset;
459 outputStream << quint32(0) // legacy offset
462 << offset;
464 }
466 if (alignAmount)
468
469 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
471 const quint32 nameLength = entry.name.size() + 1;
473 device->write(entry.name.constData(), nameLength); // with zero terminator included
475 if (alignAmount)
477 }
478
481 if (alignAmount)
483
486 if (alignAmount)
488
490 for (quint32 i = 0; i < subsetsCount; ++i) {
491 const Mesh::Subset &subset(mesh.m_subsets[i]);
494 const float minX = subset.bounds.min.x();
495 const float minY = subset.bounds.min.y();
496 const float minZ = subset.bounds.min.z();
497 const float maxX = subset.bounds.max.x();
498 const float maxY = subset.bounds.max.y();
499 const float maxZ = subset.bounds.max.z();
500 const quint32 nameLength = subset.name.size() + 1;
503 const quint32 lodCount = subset.lods.size();
505 << subsetOffset
506 << minX
507 << minY
508 << minZ
509 << maxX
510 << maxY
511 << maxZ;
512 outputStream << quint32(0) // legacy offset
513 << nameLength;
518 }
520 if (alignAmount)
522
523 for (quint32 i = 0; i < subsetsCount; ++i) {
524 const Mesh::Subset &subset(mesh.m_subsets[i]);
525 const char *utf16Name = reinterpret_cast<const char *>(subset.name.utf16());
526 const quint32 nameByteSize = (subset.name.size() + 1) * 2;
529 if (alignAmount)
531 }
532
533 // LOD data
535 for (quint32 i = 0; i < subsetsCount; ++i) {
536 const Mesh::Subset &subset(mesh.m_subsets[i]);
537 for (auto lod : subset.lods) {
538 const quint32 count = lod.count;
539 const quint32 offset = lod.offset;
540 const float distance = lod.distance;
543 }
544 }
546 if (alignAmount)
548
549 // Data for morphTargets
550 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
554 const quint32 offset = entry.offset;
555 outputStream << quint32(0) // legacy offset
558 << offset;
560 }
562 if (alignAmount)
564
565 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
567 const quint32 nameLength = entry.name.size() + 1;
569 device->write(entry.name.constData(), nameLength); // with zero terminator included
571 if (alignAmount)
573 }
574
576
577 const quint32 endPos = device->pos();
580 return sizeInBytes;
581}
582
601
603{
608 Mesh mesh;
610 if (size)
611 meshes.insert(it.key(), mesh);
612 else
613 qWarning("Failed to find mesh #%u", it.key());
614 }
615 return meshes;
616}
617
618static inline quint32 getAlignedOffset(quint32 offset, quint32 align)
619{
620 Q_ASSERT(align > 0);
621 const quint32 leftover = (align > 0) ? offset % align : 0;
622 if (leftover)
623 return offset + (align - leftover);
624 return offset;
625}
626
633{
634 Mesh mesh;
637 quint32 numItems = 0;
638 bool ok = true;
639
643
645 for (const AssetVertexEntry &entry : vbufEntries) {
646 // Ignore entries with no data.
647 if (entry.data.isEmpty())
648 continue;
649
654
655 if (entry.morphTargetId < 0) {
658
659 if (entry.data.size() % alignment != 0) {
660 Q_ASSERT(false);
661 ok = false;
662 }
663
665 if (numItems == 0) {
667 } else if (numItems != localNumItems) {
668 Q_ASSERT(false);
669 ok = false;
671 }
672
675
680 } else {
681 if (!targetCompStride) {
686 }
687
688 // At assets, these entries are appended sequentially from target 0 to target N - 1
689 // It is safe to calculate the offset by the data size
694
695 // Note: the targetBuffer will not be interleaved,
696 // data will be just appended in order and used for a texture array.
697 if (entry.morphTargetId == 0)
699
701 }
702 }
703
704 if (!ok)
705 return Mesh();
706
708
709 // Packed interleave the data.
710 for (quint32 idx = 0; idx < numItems; ++idx) {
712 for (const AssetVertexEntry &entry : vEntries) {
713 if (entry.data.isEmpty())
714 continue;
715
718 const quint32 offset = byteSize * idx;
720 if (newOffset != dataOffset) {
723 }
724
727 }
729 }
730
733
734 for (const AssetMeshSubset &subset : subsets) {
739
740 // TODO: QTBUG-102026
749 subset.offset);
752 }
753
756
758 }
759
762
763 return mesh;
764}
765
767{
768 if (data.m_vertexBuffer.size() == 0) {
769 *error = QObject::tr("Vertex buffer empty");
770 return Mesh();
771 }
772 if (data.m_attributeCount == 0) {
773 *error = QObject::tr("No attributes defined");
774 return Mesh();
775 }
776
777 Mesh mesh;
780
781 for (int i = 0; i < data.m_attributeCount; ++i) {
785 } else {
786 const char *name = nullptr;
787 switch (att.semantic) {
790 break;
793 break;
796 break;
799 break;
802 break;
805 break;
808 break;
811 break;
814 break;
815 default:
816 *error = QObject::tr("Warning: Invalid attribute semantic: %1")
817 .arg(att.semantic);
818 return Mesh();
819 }
820
825 entry.name = name;
827 }
828 }
829
831 // Only interleaved vertex attribute packing is supported, both internally
832 // and in the QQuick3DGeometry API, hence the per-vertex buffer stride.
836
837 if (!data.m_targetBuffer.isEmpty()) {
842
848 return (a.targetId == b.targetId) ? a.attr.semantic < b.attr.semantic :
849 a.targetId < b.targetId; });
850 for (int i = 0; i < data.m_targetAttributeCount; ++i) {
852 const int stride = (sortedAttribs[i].stride < 1) ? att.componentCount() * sizeof(float)
854 const char *name = nullptr;
855 switch (att.semantic) {
858 break;
861 break;
864 break;
867 break;
870 break;
873 break;
877 *error = QObject::tr("Warning: Invalid target attribute semantic: %1")
878 .arg(att.semantic);
879 continue;
882 break;
883 default:
884 *error = QObject::tr("Warning: Invalid target attribute semantic: %1")
885 .arg(att.semantic);
886 return Mesh();
887 }
889 const char *srcBuf = data.m_targetBuffer.constData() + att.offset;
891 if (stride == 4 * sizeof(float)) {
893 } else {
894 for (quint32 j = 0; j < vertexCount; ++j) {
895 memcpy(dstBuf + j * 4 * sizeof(float),
896 srcBuf + j * stride,
897 att.componentCount() * sizeof(float));
898 }
899 }
900
901 if (sortedAttribs[i].targetId == 0) {
906 entry.name = name;
908 }
909 }
911 }
912 return mesh;
913}
914
916{
918 quint32 newId = 1;
920
921 if (device->size() > 0) {
923 if (!header.isValid()) {
924 qWarning("There is existing data, but mesh file header is invalid; cannot save");
925 return 0;
926 }
927 for (auto it = header.meshEntries.cbegin(), end = header.meshEntries.cend(); it != end; ++it) {
928 if (id) {
929 Q_ASSERT(id != it.key());
930 newId = id;
931 } else {
932 newId = qMax(newId, it.key() + 1);
933 }
934 }
936 } else {
938 }
939
940 // the new mesh data overwrites the entry list and file header
942 const qint64 meshOffset = device->pos();
944
946 // skip the space for the mesh header for now
949 // now the mesh header is ready to be written out
953 // write out new entry list and file header
955
956 return newId;
957}
958
966{
969 Q_ASSERT(false);
970 return result;
971 }
972
975 Q_ASSERT(false);
976 return result;
977 }
978
982 const char *indexSrcPtr = indexBufferData.constData();
983
984 for (quint32 idx = 0, numItems = subsetCount; idx < numItems; ++idx) {
986 continue;
987
988 quint32 vertexIdx = 0;
989 switch (indexComponentByteSize) {
990 case 2:
991 vertexIdx = reinterpret_cast<const quint16 *>(indexSrcPtr)[idx + subsetOffset];
992 break;
993 case 4:
994 vertexIdx = reinterpret_cast<const quint32 *>(indexSrcPtr)[idx + subsetOffset];
995 break;
996 default:
998 break;
999 }
1000
1002 float v[3];
1003 if (finalOffset + sizeof(v) <= vertexBufferByteSize) {
1004 memcpy(v, vertexSrcPtr + finalOffset, sizeof(v));
1005 result.include(QVector3D(v[0], v[1], v[2]));
1006 } else {
1007 Q_ASSERT(false);
1008 }
1009 }
1010
1011 return result;
1012}
1013
1015{
1018 if (vbe.name == lightmapAttrName)
1019 return true;
1020 }
1021 return false;
1022}
1023
1025{
1028 const char *uvAttrName = MeshInternal::getUV0AttrName();
1030
1031 // this function should do nothing if there is already an attr_lightmapuv
1033 return true;
1034
1037 if (!srcVertexStride) {
1038 qWarning("Lightmap UV unwrapping encountered a Mesh with 0 vertex stride, this cannot happen");
1039 return false;
1040 }
1041 if (m_indexBuffer.data.isEmpty()) {
1042 qWarning("Lightmap UV unwrapping encountered a Mesh without index data, this cannot happen");
1043 return false;
1044 }
1045
1049
1051 if (vbe.name == posAttrName) {
1052 if (vbe.componentCount != 3) {
1053 qWarning("Lightmap UV unwrapping encountered a Mesh non-float3 position data, this cannot happen");
1054 return false;
1055 }
1057 } else if (vbe.name == normalAttrName) {
1058 if (vbe.componentCount != 3) {
1059 qWarning("Lightmap UV unwrapping encountered a Mesh non-float3 normal data, this cannot happen");
1060 return false;
1061 }
1063 } else if (vbe.name == uvAttrName) {
1064 if (vbe.componentCount != 2) {
1065 qWarning("Lightmap UV unwrapping encountered a Mesh non-float2 UV0 data, this cannot happen");
1066 return false;
1067 }
1069 }
1070 }
1071
1072 if (positionOffset == UINT32_MAX) {
1073 qWarning("Lightmap UV unwrapping encountered a Mesh without vertex positions, this cannot happen");
1074 return false;
1075 }
1076 // normal and uv0 are optional
1077
1079 QByteArray positionData(vertexCount * 3 * sizeof(float), Qt::Uninitialized);
1080 float *posPtr = reinterpret_cast<float *>(positionData.data());
1081 for (qsizetype i = 0; i < vertexCount; ++i) {
1082 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1083 const float *srcPos = reinterpret_cast<const float *>(vertexBasePtr + positionOffset);
1085 srcV.setX(*srcPos++);
1086 srcV.setY(*srcPos++);
1087 srcV.setZ(*srcPos++);
1088 // We scale the positions here, but not on the source mesh, so that the uv unwrapper works on
1089 // the positions that the model will have in the scene after its scaling has been applied. This
1090 // way the texels-per-unit will be correct.
1091 srcV = scale.map(srcV);
1092 *posPtr++ = srcV.x();
1093 *posPtr++ = srcV.y();
1094 *posPtr++ = srcV.z();
1095 }
1096
1098 if (normalOffset != UINT32_MAX) {
1099 normalData.resize(vertexCount * 3 * sizeof(float));
1100 float *normPtr = reinterpret_cast<float *>(normalData.data());
1101 for (qsizetype i = 0; i < vertexCount; ++i) {
1102 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1103 const float *srcNormal = reinterpret_cast<const float *>(vertexBasePtr + normalOffset);
1104 *normPtr++ = *srcNormal++;
1105 *normPtr++ = *srcNormal++;
1106 *normPtr++ = *srcNormal++;
1107 }
1108 }
1109
1111 if (uvOffset != UINT32_MAX) {
1112 uvData.resize(vertexCount * 2 * sizeof(float));
1113 float *uvPtr = reinterpret_cast<float *>(uvData.data());
1114 for (qsizetype i = 0; i < vertexCount; ++i) {
1115 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1116 const float *srcUv = reinterpret_cast<const float *>(vertexBasePtr + uvOffset);
1117 *uvPtr++ = *srcUv++;
1118 *uvPtr++ = *srcUv++;
1119 }
1120 }
1121
1126 if (!r.isValid())
1127 return false;
1128
1129 // the result can have more (but never less) vertices than the input
1130 const int newVertexCount = r.vertexMap.size();
1131
1132 // r.indexData contains the new index data that has the same number of elements as before
1133 const quint32 *newIndex = reinterpret_cast<const quint32 *>(r.indexData.constData());
1135 if (r.indexData.size() != m_indexBuffer.data.size()) {
1136 qWarning("Index buffer size mismatch after lightmap UV unwrapping");
1137 return false;
1138 }
1139 quint32 *indexDst = reinterpret_cast<quint32 *>(m_indexBuffer.data.data());
1141 } else {
1142 if (r.indexData.size() != m_indexBuffer.data.size() * 2) {
1143 qWarning("Index buffer size mismatch after lightmap UV unwrapping");
1144 return false;
1145 }
1146 quint16 *indexDst = reinterpret_cast<quint16 *>(m_indexBuffer.data.data());
1147 for (size_t i = 0, count = m_indexBuffer.data.size() / sizeof(quint16); i != count; ++i)
1148 *indexDst++ = *newIndex++;
1149 }
1150
1153
1157 char *dst = data.data();
1158 for (qsizetype i = 0; i < vertexCount; ++i) {
1160 dst += byteSize;
1161 }
1162 switch (vbe.componentType) {
1165 break;
1166 case ComponentType::Int8:
1168 break;
1171 break;
1172 case ComponentType::Int16:
1174 break;
1177 break;
1178 case ComponentType::Int32:
1180 break;
1183 break;
1184 case ComponentType::Int64:
1186 break;
1187 case ComponentType::Float16:
1189 break;
1190 case ComponentType::Float32:
1192 break;
1193 case ComponentType::Float64:
1195 break;
1196 }
1197 }
1198
1204
1207
1209 for (int vertexIdx = 0; vertexIdx < newVertexCount; ++vertexIdx) {
1210 quint32 dataOffset = 0;
1211 for (int vbIdx = 0, end = m_vertexBuffer.entries.size(); vbIdx != end; ++vbIdx) {
1213
1218
1219 if (newOffset != dataOffset) {
1222 }
1223
1224 if (vertexIdx == 0)
1226
1229 }
1230
1231 const quint32 byteSize = 2 * sizeof(float);
1233 if (newOffset != dataOffset) {
1236 }
1237
1238 if (vertexIdx == 0)
1240
1243
1244 if (vertexIdx == 0)
1246 }
1247
1249
1251
1253 for (Subset &subset : m_subsets)
1255
1256 return true;
1257}
1258
1259size_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)
1260{
1261 return meshopt_simplify(destination, indices, indexCount, vertexPositions, vertexCount, vertexPositionsStride, targetIndexCount, targetError, options, resultError);
1262}
1263
1264float simplifyScale(const float *vertexPositions, size_t vertexCount, size_t vertexPositionsStride)
1265{
1266 return meshopt_simplifyScale(vertexPositions, vertexCount, vertexPositionsStride);
1267}
1268
1269void optimizeVertexCache(unsigned int *destination, const unsigned int *indices, size_t indexCount, size_t vertexCount)
1270{
1271 meshopt_optimizeVertexCache(destination, indices, indexCount, vertexCount);
1272}
1273
1274} // namespace QSSGMesh
1275
1276QT_END_NAMESPACE
float simplifyScale(const float *vertexPositions, size_t vertexCount, size_t vertexPositionsStride)
static const size_t MESH_STRUCT_SIZE
Definition qssgmesh.cpp:30
static const size_t SUBSET_STRUCT_SIZE_V3_V4
Definition qssgmesh.cpp:36
static const size_t VERTEX_BUFFER_ENTRY_STRUCT_SIZE
Definition qssgmesh.cpp:33
static const size_t MULTI_ENTRY_STRUCT_SIZE
Definition qssgmesh.cpp:24
static const size_t SUBSET_STRUCT_SIZE_V6
Definition qssgmesh.cpp:40
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:21
static const size_t LOD_STRUCT_SIZE
Definition qssgmesh.cpp:43
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:27
static const size_t SUBSET_STRUCT_SIZE_V5
Definition qssgmesh.cpp:38
static quint32 getAlignedOffset(quint32 offset, quint32 align)
Definition qssgmesh.cpp:618