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:critical reason:data-parser
4
5
6#include "qssgmesh_p.h"
7
8#include <QtCore/QVector>
9#include <QtCore/qhash.h>
10#include <QtCore/qvarlengtharray.h>
11#include <QtCore/qmath.h>
12#include <QtQuick3DUtils/private/qssgdataref_p.h>
13#include <QtQuick3DUtils/private/qssglightmapuvgenerator_p.h>
14#include <QtQuick3DUtils/private/qssgutils_p.h>
15
16#include "meshoptimizer.h"
17
18#include <algorithm>
19
20QT_BEGIN_NAMESPACE
21
22namespace QSSGMesh {
23
24// fileId, fileVersion, offset, count
26
27// meshOffset, meshId, padding
29
30// fileId, fileVersion, flags, size
32
33// vertexBuffer, indexBuffer, subsets, joints, drawMode, winding
34static const size_t MESH_STRUCT_SIZE = 56;
35
36// vertex buffer entry list: nameOffset, componentType, componentCount, offset
38
39// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength
41// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength, lightmapSizeWidth, lightmapSizeHeight
42static const size_t SUBSET_STRUCT_SIZE_V5 = 48;
43// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength, lightmapSizeWidth, lightmapSizeHeight, lodCount
44static const size_t SUBSET_STRUCT_SIZE_V6 = 52;
45
46//lod entry: count, offset, distance
47static const size_t LOD_STRUCT_SIZE = 12;
48
49// joint list: jointID, parentID, invBindPose, localToGlobalBoneSpace
50static const size_t JOINT_STRUCT_SIZE = 136;
51
52// getSizeOfType() is Q_UNREACHABLE outside its enumeration, so a component type
53// from the file has to be checked before it gets there.
54// Spelled out so that a new component type fails to compile until handled.
55static bool isKnownComponentType(quint32 value)
56{
57 switch (Mesh::ComponentType(value)) {
58 case Mesh::ComponentType::UnsignedInt8:
59 case Mesh::ComponentType::Int8:
60 case Mesh::ComponentType::UnsignedInt16:
61 case Mesh::ComponentType::Int16:
62 case Mesh::ComponentType::UnsignedInt32:
63 case Mesh::ComponentType::Int32:
64 case Mesh::ComponentType::UnsignedInt64:
65 case Mesh::ComponentType::Int64:
66 case Mesh::ComponentType::Float16:
67 case Mesh::ComponentType::Float32:
68 case Mesh::ComponentType::Float64:
69 return true;
70 }
71 return false;
72}
73
74// Bounds a count by the file rather than by a chosen cap, so the limit scales
75// with the file and cannot be set wrong.
76static bool canRead(QIODevice *device, quint64 count, quint64 itemSize)
77{
78 quint64 bytes = 0;
79 if (qMulOverflow(count, itemSize, &bytes))
80 return false;
81 const qint64 remaining = device->size() - device->pos();
82 return remaining >= 0 && bytes <= quint64(remaining);
83}
84
86{
88 if (multiHeaderStartOffset < 0) {
89 qWarning("Mesh file is too small to hold a header");
90 return {};
91 }
92
97
100
101 if (!meshFileInfo.isValid()) {
102 qWarning("Mesh file invalid");
103 return {};
104 }
105
106 quint32 multiEntriesOffset; // unused, the entry list is right before the header
109
110 // The entry list sits immediately before the header, so it bounds the count.
112 qWarning("Mesh file declares %u meshes, which do not fit in it", meshCount);
113 return {};
114 }
115
116 for (quint32 i = 0; i < meshCount; ++i) {
121 quint32 id;
122 inputStream >> offset >> id;
124 }
125
126 return meshFileInfo;
127}
128
146
148{
149 static char alignPadding[4] = {};
150
155
157 if (!header->isValid()) {
158 qWarning() << "Mesh data invalid";
161 qWarning() << "File version " << header->fileVersion << " newer than " << MeshDataHeader::FILE_VERSION;
163 qWarning() << "File version " << header->fileVersion << " older than " << MeshDataHeader::LEGACY_MESH_FILE_VERSION;
164 } else {
165 qWarning() << "Invalid file ID" << header->fileId;
166 }
167 return 0;
168 }
169
172
182
186 }
187
195 qWarning("Mesh index buffer has unknown component type %u", indexBufferComponentType);
196 return 0;
197 }
199
203 // Before version 7 this field is a leftover subset offset, not a count;
204 // the legacy path below works the count out from the attribute names.
207
208 quint32 jointsOffsets; // unused, see the format documentation
216
218
220 qWarning("Mesh declares %u vertex buffer entries, which do not fit in the file",
222 return 0;
223 }
224
226 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
229 quint32 nameOffset; // unused
235 qWarning("Mesh attribute %u has unknown component type %u", i, componentType);
236 return 0;
237 }
241 }
243 if (alignAmount)
245
246 // vertex buffer entry names
248 // used for recording the target attributes supported by the mesh
249 // and re-construting it when meeting attr_unsupported
251 for (auto &entry : mesh->m_vertexBuffer.entries) {
255 if (!canRead(device, nameLength, 1)) {
256 qWarning("Mesh declares a %u byte attribute name, which does not fit in the file",
257 nameLength);
258 return 0;
259 }
263 if (alignAmount)
265 // Old morph meshes' target attributes were appended sequentially
266 // behind vertex attributes. However, since the number of targets are restricted by 8
267 // the other attributes were named by "attr_unsupported"
268 // So just checking numTargets is safe with the above assumption and
269 // it will try to reconstruct the unsupported attributes.
270 if (numTargets > 0 || (!header->hasSeparateTargetBuffer() && entry.name.startsWith("attr_t"))) {
271 // Any later name lands here once a target has been seen, including
272 // short ones, and sliced() asserts where mid() would not.
273 const QByteArray suffix = entry.name.size() > 6 ? entry.name.sliced(6) : QByteArray();
274 if (suffix.startsWith("pos")) {
275 const quint32 targetId = entry.name.mid(9).toUInt();
276 // All the attributes of the first target should be recorded correctly.
277 if (targetId == 0)
283 } else if (suffix.startsWith("norm")) {
284 const quint32 targetId = entry.name.mid(10).toUInt();
285 if (targetId == 0)
291 } else if (suffix.startsWith("tan")) {
292 const quint32 targetId = entry.name.mid(9).toUInt();
293 if (targetId == 0)
299 } else if (suffix.startsWith("binorm")) {
300 const quint32 targetId = entry.name.mid(12).toUInt();
301 if (targetId == 0)
307 } else if (entry.name.startsWith("attr_unsupported")) {
308 // Reconstruct, if target 0 recorded a layout to do it from.
309 if (attrNames.isEmpty()) {
310 qWarning("Mesh has an unsupported morph target attribute with no target 0 to "
311 "take the layout from");
312 return 0;
313 }
317 }
318 }
319 }
320
322 qWarning("Mesh declares %u bytes of vertex data, which do not fit in the file",
324 return 0;
325 }
328 if (alignAmount)
330
332 qWarning("Mesh declares %u bytes of index data, which do not fit in the file",
334 return 0;
335 }
338 if (alignAmount)
340
345 qWarning("Mesh declares %u subsets, which do not fit in the file", subsetsCount);
346 return 0;
347 }
348
351 for (quint32 i = 0; i < subsetsCount; ++i) {
353 float minX;
354 float minY;
355 float minZ;
356 float maxX;
357 float maxY;
358 float maxZ;
359 quint32 nameOffset; // unused
361 >> subset.offset
362 >> minX
363 >> minY
364 >> minZ
365 >> maxX
366 >> maxY
367 >> maxZ
368 >> nameOffset
373 quint32 width = 0;
374 quint32 height = 0;
377 if (header->hasLodDataHint()) {
378 quint32 lodCount = 0;
382 } else {
384 }
385 } else {
388 }
390
391 }
393 if (alignAmount)
395
398 qWarning("Mesh subset declares a %u character name, which does not fit in the file",
400 return 0;
401 }
404 if (alignAmount)
406 }
407
410 // The subsets share these bytes, so check them as they are consumed.
412 qWarning("Mesh subset declares %u levels of detail, which do not fit in the file",
414 return 0;
415 }
417 // Read Level of Detail data here
418 for (auto &lod : meshSubset.lods) {
419 quint32 count = 0;
420 quint32 offset = 0;
421 float distance = 0.0;
423 lod.count = count;
424 lod.offset = offset;
427 }
428
430 }
432 if (alignAmount)
434
435 // Unused, but the morph target entries come after them.
436 if (jointsCount) {
438 qWarning("Mesh declares %u joints, which do not fit in the file", jointsCount);
439 return 0;
440 }
444 }
445
446 // Data for morphTargets
447 if (targetBufferEntriesCount > 0) {
450 qWarning("Mesh declares %u morph target entries, which do not fit in the file",
452 return 0;
453 }
454 entriesByteSize = 0;
455 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
458 quint32 nameOffset; // unused
464 qWarning("Mesh morph target attribute %u has unknown component type %u", i,
466 return 0;
467 }
471 }
473 if (alignAmount)
475
476 for (auto &entry : mesh->m_targetBuffer.entries) {
480 if (!canRead(device, nameLength, 1)) {
481 qWarning("Mesh declares a %u byte morph target name, which does not fit in "
482 "the file", nameLength);
483 return 0;
484 }
488 if (alignAmount)
490 }
491
493 qWarning("Mesh declares %u bytes of morph target data, which do not fit in the "
494 "file", targetBufferDataSize);
495 return 0;
496 }
498 } else {
499 // The target buffer is sized by the product of these two, and the
500 // layout has only position, normal, tangent and binormal per target.
501 constexpr quint32 maxTargets = 8;
502 constexpr quint32 maxComponentsPerTarget = 4;
503 if (mesh->m_vertexBuffer.stride == 0 || numTargets == 0
509 qWarning("Mesh morph target layout is inconsistent: stride %u, %u targets, %u "
510 "entries of %u",
513 return 0;
514 }
515 // remove target entries from vertexbuffer entries
518 // At least one vertex, so vertexCount - 1 below does not wrap.
520 // The least an entry can need, checked before sizing from vertexCount.
522 + 3 * qsizetype(sizeof(float))
524 qWarning("Mesh morph target source does not fit a %u byte vertex buffer of stride "
526 return 0;
527 }
536 qWarning("Mesh morph target buffer for %u vertices is too large", vertexCount);
537 return 0;
538 }
541 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
545 // Three floats per vertex, so it is the last twelve byte copy
546 // that has to fit.
547 const qsizetype copyBytes = 3 * qsizetype(sizeof(float));
549 + qsizetype(vertexCount - 1) * qsizetype(4 * sizeof(float)) + copyBytes;
552 + copyBytes;
555 qWarning("Mesh morph target entry %u does not fit its buffer", i);
556 return 0;
557 }
560 for (quint32 j = 0; j < vertexCount; ++j) {
561 memcpy(dstBuf + j * 4 * sizeof(float),
563 3 * sizeof(float));
564 }
566 }
567 // now we don't need to have redundant targetbuffer entries
570 }
571 }
572
573 // These go straight to drawIndexed() or draw(), so a range outside the
574 // buffer it indexes has to be caught here rather than by the driver.
579 else if (mesh->m_vertexBuffer.stride)
581
582 for (const Mesh::Subset &subset : std::as_const(mesh->m_subsets)) {
584 qWarning("Mesh subset draws %u from %u, but only %llu are available", subset.count,
586 return 0;
587 }
588 for (const Mesh::Lod &lod : subset.lods) {
590 qWarning("Mesh level of detail draws %u from %u, but only %llu are available",
592 return 0;
593 }
594 }
595 }
596
597 return header->sizeInBytes;
598}
599
608
609// The legacy, now-removed, insane mesh code used to use a "serialization"
610// strategy with dumping memory, yet combined with with an in-memory layout
611// that is different from what's in the file. In version 4 we no longer write
612// out valid offset values (see the // legacy offset comments), because the new
613// loader does not need them, and calculating them is not sensible, especially
614// due to the different layouts. We still do the alignment padding, even though
615// that's also legacy nonsense, but having that allows the reader not have to
616// branch based on the version.
617
619{
620 static const char alignPadding[4] = {};
621
625
626 // The buffers bypass the stream, so one flag covers their writes and is
627 // tested at the end with the stream's own status.
628 bool writesOk = true;
629 auto writeBytes = [&](const char *data, qint64 size) {
630 if (size > 0 && device->write(data, size) != size)
631 writesOk = false;
632 };
633
634 const qint64 startPos = device->pos();
637
648
652 outputStream << quint32(0) // legacy offset
654
658 << subsetsCount;
659
660 outputStream << quint32(0) // legacy offset
661 << quint32(0); // legacy jointsCount
662
666 << winding;
667
669
671 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
675 const quint32 offset = entry.offset;
676 outputStream << quint32(0) // legacy offset
679 << offset;
681 }
683 if (alignAmount)
685
686 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
688 const quint32 nameLength = entry.name.size() + 1;
690 writeBytes(entry.name.constData(), nameLength); // with zero terminator included
692 if (alignAmount)
694 }
695
698 if (alignAmount)
700
703 if (alignAmount)
705
707 for (quint32 i = 0; i < subsetsCount; ++i) {
708 const Mesh::Subset &subset(mesh.m_subsets[i]);
711 const float minX = subset.bounds.min.x();
712 const float minY = subset.bounds.min.y();
713 const float minZ = subset.bounds.min.z();
714 const float maxX = subset.bounds.max.x();
715 const float maxY = subset.bounds.max.y();
716 const float maxZ = subset.bounds.max.z();
717 const quint32 nameLength = subset.name.size() + 1;
720 const quint32 lodCount = subset.lods.size();
722 << subsetOffset
723 << minX
724 << minY
725 << minZ
726 << maxX
727 << maxY
728 << maxZ;
729 outputStream << quint32(0) // legacy offset
730 << nameLength;
735 }
737 if (alignAmount)
739
740 for (quint32 i = 0; i < subsetsCount; ++i) {
741 const Mesh::Subset &subset(mesh.m_subsets[i]);
742 const char *utf16Name = reinterpret_cast<const char *>(subset.name.utf16());
743 const quint32 nameByteSize = (subset.name.size() + 1) * 2;
746 if (alignAmount)
748 }
749
750 // LOD data
752 for (quint32 i = 0; i < subsetsCount; ++i) {
753 const Mesh::Subset &subset(mesh.m_subsets[i]);
754 for (auto lod : subset.lods) {
755 const quint32 count = lod.count;
756 const quint32 offset = lod.offset;
757 const float distance = lod.distance;
760 }
761 }
763 if (alignAmount)
765
766 // Data for morphTargets
767 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
771 const quint32 offset = entry.offset;
772 outputStream << quint32(0) // legacy offset
775 << offset;
777 }
779 if (alignAmount)
781
782 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
784 const quint32 nameLength = entry.name.size() + 1;
786 writeBytes(entry.name.constData(), nameLength); // with zero terminator included
788 if (alignAmount)
790 }
791
793
794 if (!writesOk || outputStream.status() != QDataStream::Ok) {
795 qWarning("Failed to write mesh data");
796 return 0;
797 }
798
799 const quint32 endPos = device->pos();
802 return sizeInBytes;
803}
804
823
825{
830 Mesh mesh;
832 if (size)
833 meshes.insert(it.key(), mesh);
834 else
835 qWarning("Failed to find mesh #%u", it.key());
836 }
837 return meshes;
838}
839
840static inline quint32 getAlignedOffset(quint32 offset, quint32 align)
841{
842 Q_ASSERT(align > 0);
843 const quint32 leftover = (align > 0) ? offset % align : 0;
844 if (leftover)
845 return offset + (align - leftover);
846 return offset;
847}
848
855{
856 Mesh mesh;
859 quint32 numItems = 0;
860 bool ok = true;
861
865
867 for (const AssetVertexEntry &entry : vbufEntries) {
868 // Ignore entries with no data.
869 if (entry.data.isEmpty())
870 continue;
871
876
877 if (entry.morphTargetId < 0) {
880
881 if (entry.data.size() % alignment != 0) {
882 Q_ASSERT(false);
883 ok = false;
884 }
885
887 if (numItems == 0) {
889 } else if (numItems != localNumItems) {
890 Q_ASSERT(false);
891 ok = false;
893 }
894
897
902 } else {
903 if (!targetCompStride) {
908 }
909
910 // At assets, these entries are appended sequentially from target 0 to target N - 1
911 // It is safe to calculate the offset by the data size
916
917 // Note: the targetBuffer will not be interleaved,
918 // data will be just appended in order and used for a texture array.
919 if (entry.morphTargetId == 0)
921
923 }
924 }
925
926 if (!ok)
927 return Mesh();
928
930
931 // Packed interleave the data.
932 for (quint32 idx = 0; idx < numItems; ++idx) {
934 for (const AssetVertexEntry &entry : vEntries) {
935 if (entry.data.isEmpty())
936 continue;
937
940 const quint32 offset = byteSize * idx;
942 if (newOffset != dataOffset) {
945 }
946
949 }
951 }
952
955
956 for (const AssetMeshSubset &subset : subsets) {
961
962 // TODO: QTBUG-102026
971 subset.offset);
974 }
975
978
980 }
981
984
985 return mesh;
986}
987
989{
990 if (data.m_vertexBuffer.size() == 0) {
991 *error = QObject::tr("Vertex buffer empty");
992 return Mesh();
993 }
994 if (data.m_attributeCount == 0) {
995 *error = QObject::tr("No attributes defined");
996 return Mesh();
997 }
998
999 Mesh mesh;
1002
1003 for (int i = 0; i < data.m_attributeCount; ++i) {
1007 } else {
1008 const char *name = nullptr;
1009 switch (att.semantic) {
1012 break;
1015 break;
1018 break;
1021 break;
1024 break;
1027 break;
1030 break;
1033 break;
1036 break;
1037 default:
1038 *error = QObject::tr("Warning: Invalid attribute semantic: %1")
1039 .arg(att.semantic);
1040 return Mesh();
1041 }
1042
1047 entry.name = name;
1049 }
1050 }
1051
1053 // Only interleaved vertex attribute packing is supported, both internally
1054 // and in the QQuick3DGeometry API, hence the per-vertex buffer stride.
1058
1059 if (!data.m_targetBuffer.isEmpty()) {
1064
1070 return (a.targetId == b.targetId) ? a.attr.semantic < b.attr.semantic :
1071 a.targetId < b.targetId; });
1072 for (int i = 0; i < data.m_targetAttributeCount; ++i) {
1074 const int stride = (sortedAttribs[i].stride < 1) ? att.componentCount() * sizeof(float)
1076 const char *name = nullptr;
1077 switch (att.semantic) {
1080 break;
1083 break;
1086 break;
1089 break;
1092 break;
1095 break;
1099 *error = QObject::tr("Warning: Invalid target attribute semantic: %1")
1100 .arg(att.semantic);
1101 continue;
1104 break;
1105 default:
1106 *error = QObject::tr("Warning: Invalid target attribute semantic: %1")
1107 .arg(att.semantic);
1108 return Mesh();
1109 }
1111 const char *srcBuf = data.m_targetBuffer.constData() + att.offset;
1113 if (stride == 4 * sizeof(float)) {
1115 } else {
1116 for (quint32 j = 0; j < vertexCount; ++j) {
1117 memcpy(dstBuf + j * 4 * sizeof(float),
1118 srcBuf + j * stride,
1119 att.componentCount() * sizeof(float));
1120 }
1121 }
1122
1123 if (sortedAttribs[i].targetId == 0) {
1128 entry.name = name;
1130 }
1131 }
1133 }
1134 return mesh;
1135}
1136
1138{
1140 quint32 newId = 1;
1142
1143 if (device->size() > 0) {
1145 if (!header.isValid()) {
1146 qWarning("There is existing data, but mesh file header is invalid; cannot save");
1147 return 0;
1148 }
1149 for (auto it = header.meshEntries.cbegin(), end = header.meshEntries.cend(); it != end; ++it) {
1150 if (id) {
1151 Q_ASSERT(id != it.key());
1152 newId = id;
1153 } else {
1154 newId = qMax(newId, it.key() + 1);
1155 }
1156 }
1158 } else {
1160 }
1161
1162 // the new mesh data overwrites the entry list and file header
1164 const qint64 meshOffset = device->pos();
1166
1168 // skip the space for the mesh header for now
1171 if (meshHeader.sizeInBytes == 0)
1172 return 0;
1173 // now the mesh header is ready to be written out
1177 // write out new entry list and file header
1179
1180 return newId;
1181}
1182
1190{
1193 Q_ASSERT(false);
1194 return result;
1195 }
1196
1199 Q_ASSERT(false);
1200 return result;
1201 }
1202
1205 const char *vertexSrcPtr = vertexBufferData.constData();
1206 const char *indexSrcPtr = indexBufferData.constData();
1207
1208 for (quint32 idx = 0, numItems = subsetCount; idx < numItems; ++idx) {
1210 continue;
1211
1212 quint32 vertexIdx = 0;
1213 switch (indexComponentByteSize) {
1214 case 2:
1215 vertexIdx = reinterpret_cast<const quint16 *>(indexSrcPtr)[idx + subsetOffset];
1216 break;
1217 case 4:
1218 vertexIdx = reinterpret_cast<const quint32 *>(indexSrcPtr)[idx + subsetOffset];
1219 break;
1220 default:
1221 Q_UNREACHABLE();
1222 break;
1223 }
1224
1226 float v[3];
1227 if (finalOffset + sizeof(v) <= vertexBufferByteSize) {
1228 memcpy(v, vertexSrcPtr + finalOffset, sizeof(v));
1229 result.include(QVector3D(v[0], v[1], v[2]));
1230 } else {
1231 Q_ASSERT(false);
1232 }
1233 }
1234
1235 return result;
1236}
1237
1239{
1242 if (vbe.name == lightmapAttrName)
1243 return true;
1244 }
1245 return false;
1246}
1247
1249{
1252 const char *uvAttrName = MeshInternal::getUV0AttrName();
1254
1255 // this function should do nothing if there is already an attr_lightmapuv
1257 return true;
1258
1261 if (!srcVertexStride) {
1262 qWarning("Lightmap UV unwrapping encountered a Mesh with 0 vertex stride, this cannot happen");
1263 return false;
1264 }
1265 if (m_indexBuffer.data.isEmpty()) {
1266 qWarning("Lightmap UV unwrapping encountered a Mesh without index data, this cannot happen");
1267 return false;
1268 }
1269
1273
1275 if (vbe.name == posAttrName) {
1276 if (vbe.componentCount != 3) {
1277 qWarning("Lightmap UV unwrapping encountered a Mesh non-float3 position data, this cannot happen");
1278 return false;
1279 }
1281 } else if (vbe.name == normalAttrName) {
1282 if (vbe.componentCount != 3) {
1283 qWarning("Lightmap UV unwrapping encountered a Mesh non-float3 normal data, this cannot happen");
1284 return false;
1285 }
1287 } else if (vbe.name == uvAttrName) {
1288 if (vbe.componentCount != 2) {
1289 qWarning("Lightmap UV unwrapping encountered a Mesh non-float2 UV0 data, this cannot happen");
1290 return false;
1291 }
1293 }
1294 }
1295
1296 if (positionOffset == UINT32_MAX) {
1297 qWarning("Lightmap UV unwrapping encountered a Mesh without vertex positions, this cannot happen");
1298 return false;
1299 }
1300 // normal and uv0 are optional
1301
1303 QByteArray positionData(vertexCount * 3 * sizeof(float), Qt::Uninitialized);
1304 float *posPtr = reinterpret_cast<float *>(positionData.data());
1305 for (qsizetype i = 0; i < vertexCount; ++i) {
1306 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1307 const float *srcPos = reinterpret_cast<const float *>(vertexBasePtr + positionOffset);
1309 srcV.setX(*srcPos++);
1310 srcV.setY(*srcPos++);
1311 srcV.setZ(*srcPos++);
1312 // We scale the positions here, but not on the source mesh, so that the uv unwrapper works on
1313 // the positions that the model will have in the scene after its scaling has been applied. This
1314 // way the texels-per-unit will be correct.
1315 srcV = scale.map(srcV);
1316 *posPtr++ = srcV.x();
1317 *posPtr++ = srcV.y();
1318 *posPtr++ = srcV.z();
1319 }
1320
1322 if (normalOffset != UINT32_MAX) {
1323 normalData.resize(vertexCount * 3 * sizeof(float));
1324 float *normPtr = reinterpret_cast<float *>(normalData.data());
1325 for (qsizetype i = 0; i < vertexCount; ++i) {
1326 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1327 const float *srcNormal = reinterpret_cast<const float *>(vertexBasePtr + normalOffset);
1328 *normPtr++ = *srcNormal++;
1329 *normPtr++ = *srcNormal++;
1330 *normPtr++ = *srcNormal++;
1331 }
1332 }
1333
1335 if (uvOffset != UINT32_MAX) {
1336 uvData.resize(vertexCount * 2 * sizeof(float));
1337 float *uvPtr = reinterpret_cast<float *>(uvData.data());
1338 for (qsizetype i = 0; i < vertexCount; ++i) {
1339 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1340 const float *srcUv = reinterpret_cast<const float *>(vertexBasePtr + uvOffset);
1341 *uvPtr++ = *srcUv++;
1342 *uvPtr++ = *srcUv++;
1343 }
1344 }
1345
1350 if (!r.isValid())
1351 return false;
1352
1353 // the result can have more (but never less) vertices than the input
1354 const int newVertexCount = r.vertexMap.size();
1355
1356 // r.indexData contains the new index data that has the same number of elements as before
1357 const quint32 *newIndex = reinterpret_cast<const quint32 *>(r.indexData.constData());
1359 if (r.indexData.size() != m_indexBuffer.data.size()) {
1360 qWarning("Index buffer size mismatch after lightmap UV unwrapping");
1361 return false;
1362 }
1363 quint32 *indexDst = reinterpret_cast<quint32 *>(m_indexBuffer.data.data());
1365 } else {
1366 if (r.indexData.size() != m_indexBuffer.data.size() * 2) {
1367 qWarning("Index buffer size mismatch after lightmap UV unwrapping");
1368 return false;
1369 }
1370 quint16 *indexDst = reinterpret_cast<quint16 *>(m_indexBuffer.data.data());
1371 for (size_t i = 0, count = m_indexBuffer.data.size() / sizeof(quint16); i != count; ++i)
1372 *indexDst++ = *newIndex++;
1373 }
1374
1377
1381 char *dst = data.data();
1382 for (qsizetype i = 0; i < vertexCount; ++i) {
1384 dst += byteSize;
1385 }
1386 switch (vbe.componentType) {
1389 break;
1390 case ComponentType::Int8:
1392 break;
1395 break;
1396 case ComponentType::Int16:
1398 break;
1401 break;
1402 case ComponentType::Int32:
1404 break;
1407 break;
1408 case ComponentType::Int64:
1410 break;
1411 case ComponentType::Float16:
1413 break;
1414 case ComponentType::Float32:
1416 break;
1417 case ComponentType::Float64:
1419 break;
1420 }
1421 }
1422
1428
1431
1433 for (int vertexIdx = 0; vertexIdx < newVertexCount; ++vertexIdx) {
1434 quint32 dataOffset = 0;
1435 for (int vbIdx = 0, end = m_vertexBuffer.entries.size(); vbIdx != end; ++vbIdx) {
1437
1442
1443 if (newOffset != dataOffset) {
1446 }
1447
1448 if (vertexIdx == 0)
1450
1453 }
1454
1455 const quint32 byteSize = 2 * sizeof(float);
1457 if (newOffset != dataOffset) {
1460 }
1461
1462 if (vertexIdx == 0)
1464
1467
1468 if (vertexIdx == 0)
1470 }
1471
1473
1475
1477 for (Subset &subset : m_subsets)
1479
1480 return true;
1481}
1482
1483size_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)
1484{
1485 return meshopt_simplify(destination, indices, indexCount, vertexPositions, vertexCount, vertexPositionsStride, targetIndexCount, targetError, options, resultError);
1486}
1487
1488float simplifyScale(const float *vertexPositions, size_t vertexCount, size_t vertexPositionsStride)
1489{
1490 return meshopt_simplifyScale(vertexPositions, vertexCount, vertexPositionsStride);
1491}
1492
1493void optimizeVertexCache(unsigned int *destination, const unsigned int *indices, size_t indexCount, size_t vertexCount)
1494{
1495 meshopt_optimizeVertexCache(destination, indices, indexCount, vertexCount);
1496}
1497
1498size_t generateVertexRemap(unsigned int *destination,
1499 const unsigned int *indices,
1500 size_t indexCount,
1501 size_t vertexCount,
1502 const MeshVertexStream *streams,
1503 size_t streamCount)
1504{
1505 Q_ASSERT(streamCount > 0 && streamCount <= maxVertexStreams);
1506 QVarLengthArray<meshopt_Stream, 16> meshoptStreams;
1507 for (size_t i = 0; i < streamCount; ++i)
1508 meshoptStreams.append({ streams[i].data, streams[i].elementSize, streams[i].stride });
1509 return meshopt_generateVertexRemapMulti(destination, indices, indexCount, vertexCount,
1510 meshoptStreams.constData(), streamCount);
1511}
1512
1513void remapVertexBuffer(void *destination,
1514 const void *vertices,
1515 size_t vertexCount,
1516 size_t vertexSize,
1517 const unsigned int *remap)
1518{
1519 meshopt_remapVertexBuffer(destination, vertices, vertexCount, vertexSize, remap);
1520}
1521
1522void remapIndexBuffer(unsigned int *destination,
1523 const unsigned int *indices,
1524 size_t indexCount,
1525 const unsigned int *remap)
1526{
1527 meshopt_remapIndexBuffer(destination, indices, indexCount, remap);
1528}
1529
1530QVector<MeshLevelOfDetail> generateMeshLevelsOfDetail(const QVector<QVector3D> &positions,
1531 const QVector<QVector3D> &normals,
1532 const QVector<quint32> &indexes,
1533 QVector<MeshVertexSplit> &splitVertices,
1534 float normalMergeAngle,
1535 float normalSplitAngle)
1536{
1537 // The split vertices are reported positionally, starting at
1538 // positions.size(), so stale entries would make the caller's append loop
1539 // line up with the wrong vertices
1540 splitVertices.clear();
1541
1542 if (positions.isEmpty() || indexes.isEmpty())
1543 return {};
1544
1545 // Correcting normals needs one per position. Both angles being 0.0 also
1546 // means the caller does not want them recalculated at all.
1547 const bool recalculateNormals = normals.size() == positions.size()
1548 && !(qFuzzyIsNull(normalMergeAngle) && qFuzzyIsNull(normalSplitAngle));
1549 const float normalMergeThreshold = qCos(qDegreesToRadians(normalMergeAngle));
1550 const float normalSplitThreshold = qCos(qDegreesToRadians(normalSplitAngle));
1551
1552 quint32 splitVertexCount = positions.size();
1553
1554 // An edge can only be collapsed when its vertices are shared by the faces
1555 // around it, so a vertex that appears more than once in the vertex buffer
1556 // pins every edge that touches it. Assets are very commonly authored with
1557 // one vertex per triangle corner - faceted normals and UV chart borders
1558 // both force a split, and some exporters simply never weld - and such a
1559 // mesh has no collapsible edge anywhere, so simplification returns the
1560 // input unchanged and not a single level is produced. Weld by position to
1561 // recover the real topology, simplify that, and translate the result back
1562 // to the caller's vertex numbering afterwards, so the vertex buffer they
1563 // hold is still the one the returned indexes refer to.
1564 //
1565 // Only the normals are rewritten below, so a position split to carry
1566 // different normals can be welded, but one split to carry a different
1567 // normal that has to be *kept* cannot: every face around it would be left
1568 // reading whichever of them the weld happened to pick. So when the caller
1569 // asked for the stored normals to be preserved, they join the weld key and
1570 // a hard edge stays pinned, at the cost of fewer levels for such a mesh.
1571 QVector<quint32> weldRemap(positions.size());
1572 QVarLengthArray<MeshVertexStream, 2> streams;
1573 streams.append({ positions.constData(), sizeof(QVector3D), sizeof(QVector3D) });
1574 if (!recalculateNormals && normals.size() == positions.size())
1575 streams.append({ normals.constData(), sizeof(QVector3D), sizeof(QVector3D) });
1576 const quint32 weldedVertexCount = generateVertexRemap(weldRemap.data(), indexes.constData(), indexes.size(),
1577 positions.size(), streams.constData(),
1578 size_t(streams.size()));
1579 QVector<quint32> weldedIndexes(indexes.size());
1580 remapIndexBuffer(weldedIndexes.data(), indexes.constData(), indexes.size(), weldRemap.constData());
1581 QVector<QVector3D> weldedPositions(weldedVertexCount);
1582 remapVertexBuffer(weldedPositions.data(), positions.constData(), positions.size(), sizeof(QVector3D),
1583 weldRemap.constData());
1584
1585 // Pick one original vertex to stand for each welded one, so the simplified
1586 // indexes can be mapped back. Every vertex welded together agrees on the
1587 // attributes the weld key covers, so the choice only matters for the rest:
1588 // where a position was split across a UV chart border the LOD has to settle
1589 // on one of the charts either way.
1590 constexpr quint32 unusedVertex = std::numeric_limits<quint32>::max();
1591 QVector<quint32> weldedToOriginal(weldedVertexCount, unusedVertex);
1592 for (quint32 i = 0, end = quint32(positions.size()); i < end; ++i) {
1593 const quint32 welded = weldRemap.at(i);
1594 // Vertices the index buffer never references are left unmapped
1595 if (welded != unusedVertex && weldedToOriginal.at(welded) == unusedVertex)
1596 weldedToOriginal[welded] = i;
1597 }
1598
1599 const float targetError = std::numeric_limits<float>::max(); // error doesn't matter, index count is more important
1600 const float *vertexData = reinterpret_cast<const float *>(weldedPositions.constData());
1601 const float scaleFactor = simplifyScale(vertexData, weldedVertexCount, sizeof(QVector3D));
1602 const quint32 indexCount = indexes.size();
1603 // A level is kept when it is at least half again the previous one and still
1604 // under three quarters of the mesh; simplifyMesh() rounds up to whole
1605 // triangles, so a larger target only yields levels that get thrown away.
1606 const quint32 maxLevelIndexes = indexCount - indexCount / 4 - 1;
1607 const quint32 maxIndexTarget = maxLevelIndexes - maxLevelIndexes % 3;
1608 quint32 indexTarget = 12;
1609 quint32 lastIndexCount = 0;
1610 QVector<MeshLevelOfDetail> lods;
1611
1612 while (indexTarget < indexCount) {
1613 float error;
1614 QVector<quint32> newIndexes;
1615 newIndexes.resize(indexCount); // Must be the same size as the original indexes to pass to simplifyMesh
1616 size_t newLength = simplifyMesh(newIndexes.data(), weldedIndexes.constData(), weldedIndexes.size(),
1617 vertexData, weldedVertexCount, sizeof(QVector3D), indexTarget,
1618 targetError, 0, &error);
1619
1620 // Not good enough, try again
1621 if (newLength < lastIndexCount + (lastIndexCount + 1) / 2) {
1622 const quint32 nextTarget = qMin(indexTarget + indexTarget / 2, maxIndexTarget);
1623 if (nextTarget <= indexTarget)
1624 break;
1625 indexTarget = nextTarget;
1626 continue;
1627 }
1628
1629 // We are done
1630 if (newLength == 0 || newLength > maxLevelIndexes)
1631 break;
1632
1633 newIndexes.resize(newLength);
1634
1635 // Back to the caller's vertex numbering, which everything below - and
1636 // the returned levels - is expressed in
1637 for (quint32 &index : newIndexes)
1638 index = weldedToOriginal.at(index);
1639
1640 // LOD Normal Correction
1641 if (recalculateNormals) {
1642 // Cull any new degenerate triangles and get the new face normals
1643 QVector<QVector3D> faceNormals;
1644 {
1645 QVector<quint32> culledIndexes;
1646 for (quint32 j = 0; j < quint32(newIndexes.size()); j += 3) {
1647 const QVector3D &v0 = positions[newIndexes[j]];
1648 const QVector3D &v1 = positions[newIndexes[j + 1]];
1649 const QVector3D &v2 = positions[newIndexes[j + 2]];
1650
1651 QVector3D faceNormal = QVector3D::crossProduct(v1 - v0, v2 - v0);
1652 // This normalizes the vector in place and returns the magnitude
1653 const float faceArea = QSSGUtils::vec3::normalize(faceNormal);
1654 // It is possible that the simplifyMesh process gave us a degenerate triangle
1655 // (all three at the same point, or on the same line) or such a small triangle
1656 // that a float value doesn't have enough resolution. In that case cull the
1657 // "face" since it would not get rendered in a meaningful way anyway
1658 if (faceArea != 0.0f) {
1659 faceNormals.append(faceNormal);
1660 faceNormals.append(faceNormal);
1661 faceNormals.append(faceNormal);
1662 culledIndexes.append({newIndexes[j], newIndexes[j + 1], newIndexes[j + 2]});
1663 }
1664 }
1665
1666 if (newIndexes.size() != culledIndexes.size())
1667 newIndexes = culledIndexes;
1668 }
1669
1670 // Group all shared vertices together by position. We need to know adjacent faces
1671 // to do vertex normal remapping in the next step.
1672 const quint32 newIndexCount = quint32(newIndexes.size());
1673 QHash<QVector3D, QVector<quint32>> positionHash;
1674 for (quint32 i = 0; i < newIndexCount; ++i) {
1675 const quint32 index = newIndexes[i];
1676 const QVector3D position = positions[index];
1677 positionHash[position].append(i);
1678 }
1679
1680 // Go through each vertex and calculate the normals by checking each
1681 // adjacent face that share the same vertex position, and create a smoothed
1682 // normal if the angle between thew face normals is less than the the
1683 // normalMergeAngle passed to this function (>= since this is cos(radian(angle)) )
1684 QVector<QPair<quint32, quint32>> remapIndexes;
1685 for (quint32 positionIndex = 0; positionIndex < newIndexCount; ++positionIndex) {
1686 const quint32 index = newIndexes[positionIndex];
1687 const QVector3D &position = positions[index];
1688 const QVector3D &faceNormal = faceNormals[positionIndex];
1689 QVector3D newNormal;
1690 // Find all vertices that share the same position
1691 const auto &sharedPositions = positionHash.value(position);
1692 for (const auto positionIndex2 : sharedPositions) {
1693 if (positionIndex == positionIndex2) {
1694 // Don't test against the current face under test
1695 newNormal += faceNormal;
1696 } else {
1697 const QVector3D &faceNormal2 = faceNormals[positionIndex2];
1698 if (QVector3D::dotProduct(faceNormal2, faceNormal) >= normalMergeThreshold)
1699 newNormal += faceNormal2;
1700 }
1701 }
1702
1703 // By normalizing here we get an averaged value of all smoothed normals
1704 QSSGUtils::vec3::normalize(newNormal);
1705
1706 // Now that we know what the smoothed normal would be, check how differnt
1707 // that normal is from the normal that is already stored in the current
1708 // index. If the angle delta is greater than normalSplitAngle then we need
1709 // to create a new vertex entry (making a copy of the current one) and set
1710 // the new normal value, and reassign the current index to point to that new
1711 // vertex. Generally the LOD simplification process is such that the existing
1712 // normal will already be ideal until we start getting to the very low lod levels
1713 // which changes the topology in such a way that the original normal doesn't
1714 // make sense anymore, thus the need to provide a more reasonable value.
1715 const QVector3D &originalNormal = normals[index];
1716 const float theta = QVector3D::dotProduct(originalNormal, newNormal);
1717 if (theta < normalSplitThreshold) {
1718 splitVertices.append({ index, newNormal.normalized() });
1719 remapIndexes.append({positionIndex, splitVertexCount++});
1720 }
1721 }
1722
1723 // Do index remap now that all new normals have been calculated
1724 for (const auto &pair : std::as_const(remapIndexes))
1725 newIndexes[pair.first] = pair.second;
1726 }
1727
1728 lods.append({error * scaleFactor, newIndexes});
1729 lastIndexCount = newLength;
1730
1731 // No level can be both big enough and small enough any more
1732 if (lastIndexCount + (lastIndexCount + 1) / 2 > maxLevelIndexes)
1733 break;
1734
1735 const size_t doubled = qMax(newLength, size_t(indexTarget)) * 2; // may exceed quint32
1736 const quint32 nextTarget = quint32(qMin<size_t>(doubled, maxIndexTarget));
1737 if (nextTarget <= indexTarget)
1738 break;
1739 indexTarget = nextTarget;
1740
1741 if (error == 0.0f)
1742 break;
1743 }
1744
1745 return lods;
1746}
1747
1748} // namespace QSSGMesh
1749
1750QT_END_NAMESPACE
float simplifyScale(const float *vertexPositions, size_t vertexCount, size_t vertexPositionsStride)
static const size_t MESH_STRUCT_SIZE
Definition qssgmesh.cpp:34
void remapVertexBuffer(void *destination, const void *vertices, size_t vertexCount, size_t vertexSize, const unsigned int *remap)
static bool isKnownComponentType(quint32 value)
Definition qssgmesh.cpp:55
static const size_t SUBSET_STRUCT_SIZE_V3_V4
Definition qssgmesh.cpp:40
static const size_t VERTEX_BUFFER_ENTRY_STRUCT_SIZE
Definition qssgmesh.cpp:37
static const size_t MULTI_ENTRY_STRUCT_SIZE
Definition qssgmesh.cpp:28
static const size_t SUBSET_STRUCT_SIZE_V6
Definition qssgmesh.cpp:44
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 JOINT_STRUCT_SIZE
Definition qssgmesh.cpp:50
static const size_t MULTI_HEADER_STRUCT_SIZE
Definition qssgmesh.cpp:25
size_t generateVertexRemap(unsigned int *destination, const unsigned int *indices, size_t indexCount, size_t vertexCount, const MeshVertexStream *streams, size_t streamCount)
static const size_t LOD_STRUCT_SIZE
Definition qssgmesh.cpp:47
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:31
static const size_t SUBSET_STRUCT_SIZE_V5
Definition qssgmesh.cpp:42
static quint32 getAlignedOffset(quint32 offset, quint32 align)
Definition qssgmesh.cpp:840
void remapIndexBuffer(unsigned int *destination, const unsigned int *indices, size_t indexCount, const unsigned int *remap)
static bool canRead(QIODevice *device, quint64 count, quint64 itemSize)
Definition qssgmesh.cpp:76
QVector< MeshLevelOfDetail > generateMeshLevelsOfDetail(const QVector< QVector3D > &positions, const QVector< QVector3D > &normals, const QVector< quint32 > &indexes, QVector< MeshVertexSplit > &splitVertices, float normalMergeAngle, float normalSplitAngle)