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
19static const size_t MULTI_HEADER_STRUCT_SIZE = 16;
20
21// meshOffset, meshId, padding
22static const size_t MULTI_ENTRY_STRUCT_SIZE = 16;
23
24// fileId, fileVersion, flags, size
25static const size_t MESH_HEADER_STRUCT_SIZE = 12;
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
31static const size_t VERTEX_BUFFER_ENTRY_STRUCT_SIZE = 16;
32
33// subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength
34static const size_t SUBSET_STRUCT_SIZE_V3_V4 = 40;
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
43MeshInternal::MultiMeshInfo MeshInternal::readFileHeader(QIODevice *device)
44{
45 const qint64 multiHeaderStartOffset = device->size() - qint64(MULTI_HEADER_STRUCT_SIZE);
46
47 device->seek(pos: multiHeaderStartOffset);
48 QDataStream inputStream(device);
49 inputStream.setByteOrder(QDataStream::LittleEndian);
50 inputStream.setFloatingPointPrecision(QDataStream::SinglePrecision);
51
52 MultiMeshInfo meshFileInfo;
53 inputStream >> meshFileInfo.fileId >> meshFileInfo.fileVersion;
54
55 if (!meshFileInfo.isValid()) {
56 qWarning(msg: "Mesh file invalid");
57 return {};
58 }
59
60 quint32 multiEntriesOffset; // unused, the entry list is right before the header
61 quint32 meshCount;
62 inputStream >> multiEntriesOffset >> meshCount;
63
64 for (quint32 i = 0; i < meshCount; ++i) {
65 device->seek(pos: multiHeaderStartOffset
66 - (qint64(MULTI_ENTRY_STRUCT_SIZE) * meshCount)
67 + (qint64(MULTI_ENTRY_STRUCT_SIZE) * i));
68 quint64 offset;
69 quint32 id;
70 inputStream >> offset >> id;
71 meshFileInfo.meshEntries.insert(key: id, value: offset);
72 }
73
74 return meshFileInfo;
75}
76
77void MeshInternal::writeFileHeader(QIODevice *device, const MeshInternal::MultiMeshInfo &meshFileInfo)
78{
79 QDataStream outputStream(device);
80 outputStream.setByteOrder(QDataStream::LittleEndian);
81 outputStream.setFloatingPointPrecision(QDataStream::SinglePrecision);
82
83 const quint32 multiEntriesOffset = device->pos();
84 for (auto it = meshFileInfo.meshEntries.cbegin(), end = meshFileInfo.meshEntries.cend(); it != end; ++it) {
85 const quint32 id = it.key();
86 const quint64 offset = it.value();
87 const quint32 padding = 0;
88 outputStream << offset << id << padding;
89 }
90
91 const quint32 meshCount = meshFileInfo.meshEntries.size();
92 outputStream << meshFileInfo.fileId << meshFileInfo.fileVersion << multiEntriesOffset << meshCount;
93}
94
95quint64 MeshInternal::readMeshData(QIODevice *device, quint64 offset, Mesh *mesh, MeshDataHeader *header)
96{
97 static char alignPadding[4] = {};
98
99 device->seek(pos: offset);
100 QDataStream inputStream(device);
101 inputStream.setByteOrder(QDataStream::LittleEndian);
102 inputStream.setFloatingPointPrecision(QDataStream::SinglePrecision);
103
104 inputStream >> header->fileId >> header->fileVersion >> header->flags >> header->sizeInBytes;
105 if (!header->isValid()) {
106 qWarning() << "Mesh data invalid";
107 if (header->fileId == MeshDataHeader::FILE_ID) {
108 if (header->fileVersion > MeshDataHeader::FILE_VERSION)
109 qWarning() << "File version " << header->fileVersion << " newer than " << MeshDataHeader::FILE_VERSION;
110 if (header->fileVersion < MeshDataHeader::LEGACY_MESH_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
118 MeshInternal::MeshOffsetTracker offsetTracker(offset + MESH_HEADER_STRUCT_SIZE);
119 Q_ASSERT(offsetTracker.offset() == device->pos());
120
121 quint32 targetBufferEntriesCount;
122 quint32 vertexBufferEntriesCount;
123 quint32 targetBufferDataSize;
124 quint32 vertexBufferDataSize;
125 inputStream >> targetBufferEntriesCount
126 >> vertexBufferEntriesCount
127 >> mesh->m_vertexBuffer.stride
128 >> targetBufferDataSize
129 >> vertexBufferDataSize;
130
131 if (!header->hasSeparateTargetBuffer()) {
132 targetBufferEntriesCount = 0;
133 targetBufferDataSize = 0;
134 }
135
136 quint32 indexBufferComponentType;
137 quint32 indexBufferDataOffset;
138 quint32 indexBufferDataSize;
139 inputStream >> indexBufferComponentType
140 >> indexBufferDataOffset
141 >> indexBufferDataSize;
142 mesh->m_indexBuffer.componentType = Mesh::ComponentType(indexBufferComponentType);
143
144 quint32 targetCount;
145 quint32 subsetsCount;
146 inputStream >> targetCount >> subsetsCount;
147 mesh->m_targetBuffer.numTargets = targetCount;
148
149 quint32 jointsOffsets; // unused
150 quint32 jointsCount; // unused
151 inputStream >> jointsOffsets >> jointsCount;
152 quint32 drawMode;
153 quint32 winding;
154 inputStream >> drawMode >> winding;
155 mesh->m_drawMode = Mesh::DrawMode(drawMode);
156 mesh->m_winding = Mesh::Winding(winding);
157
158 offsetTracker.advance(advanceAmount: MESH_STRUCT_SIZE);
159
160 quint32 entriesByteSize = 0;
161 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
162 Mesh::VertexBufferEntry vertexBufferEntry;
163 quint32 componentType;
164 quint32 nameOffset; // unused
165 inputStream >> nameOffset
166 >> componentType
167 >> vertexBufferEntry.componentCount
168 >> vertexBufferEntry.offset;
169 vertexBufferEntry.componentType = Mesh::ComponentType(componentType);
170 mesh->m_vertexBuffer.entries.append(t: vertexBufferEntry);
171 entriesByteSize += VERTEX_BUFFER_ENTRY_STRUCT_SIZE;
172 }
173 quint32 alignAmount = offsetTracker.alignedAdvance(advanceAmount: entriesByteSize);
174 if (alignAmount)
175 device->read(data: alignPadding, maxlen: alignAmount);
176
177 // vertex buffer entry names
178 quint32 numTargets = 0;
179 // used for recording the target attributes supported by the mesh
180 // and re-construting it when meeting attr_unsupported
181 QList<QByteArray> attrNames;
182 for (auto &entry : mesh->m_vertexBuffer.entries) {
183 quint32 nameLength;
184 inputStream >> nameLength;
185 offsetTracker.advance(advanceAmount: sizeof(quint32));
186 const QByteArray nameWithZeroTerminator = device->read(maxlen: nameLength);
187 entry.name = QByteArray(nameWithZeroTerminator.constData(), qMax(a: 0, b: nameWithZeroTerminator.size() - 1));
188 alignAmount = offsetTracker.alignedAdvance(advanceAmount: nameLength);
189 if (alignAmount)
190 device->read(data: alignPadding, maxlen: 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(bv: "attr_t"))) {
197 if (entry.name.sliced(pos: 6).startsWith(bv: "pos")) {
198 const quint32 targetId = entry.name.mid(index: 9).toUInt();
199 // All the attributes of the first target should be recorded correctly.
200 if (targetId == 0)
201 attrNames.append(t: MeshInternal::getPositionAttrName());
202 numTargets = qMax(a: numTargets, b: targetId + 1);
203 entry.name = MeshInternal::getPositionAttrName();
204 mesh->m_targetBuffer.entries.append(t: entry);
205 targetBufferEntriesCount++;
206 } else if (entry.name.sliced(pos: 6).startsWith(bv: "norm")) {
207 const quint32 targetId = entry.name.mid(index: 10).toUInt();
208 if (targetId == 0)
209 attrNames.append(t: MeshInternal::getNormalAttrName());
210 numTargets = qMax(a: numTargets, b: targetId + 1);
211 entry.name = MeshInternal::getNormalAttrName();
212 mesh->m_targetBuffer.entries.append(t: entry);
213 targetBufferEntriesCount++;
214 } else if (entry.name.sliced(pos: 6).startsWith(bv: "tan")) {
215 const quint32 targetId = entry.name.mid(index: 9).toUInt();
216 if (targetId == 0)
217 attrNames.append(t: MeshInternal::getTexTanAttrName());
218 numTargets = qMax(a: numTargets, b: targetId + 1);
219 entry.name = MeshInternal::getTexTanAttrName();
220 mesh->m_targetBuffer.entries.append(t: entry);
221 targetBufferEntriesCount++;
222 } else if (entry.name.sliced(pos: 6).startsWith(bv: "binorm")) {
223 const quint32 targetId = entry.name.mid(index: 12).toUInt();
224 if (targetId == 0)
225 attrNames.append(t: MeshInternal::getTexBinormalAttrName());
226 numTargets = qMax(a: numTargets, b: targetId + 1);
227 entry.name = MeshInternal::getTexBinormalAttrName();
228 mesh->m_targetBuffer.entries.append(t: entry);
229 targetBufferEntriesCount++;
230 } else if (entry.name.startsWith(bv: "attr_unsupported")) {
231 // Reconstruct
232 entry.name = attrNames[targetBufferEntriesCount % attrNames.size()];
233 mesh->m_targetBuffer.entries.append(t: entry);
234 targetBufferEntriesCount++;
235 }
236 }
237 }
238
239 mesh->m_vertexBuffer.data = device->read(maxlen: vertexBufferDataSize);
240 alignAmount = offsetTracker.alignedAdvance(advanceAmount: vertexBufferDataSize);
241 if (alignAmount)
242 device->read(data: alignPadding, maxlen: alignAmount);
243
244 mesh->m_indexBuffer.data = device->read(maxlen: indexBufferDataSize);
245 alignAmount = offsetTracker.alignedAdvance(advanceAmount: indexBufferDataSize);
246 if (alignAmount)
247 device->read(data: alignPadding, maxlen: alignAmount);
248
249 quint32 subsetByteSize = 0;
250 QVector<MeshInternal::Subset> internalSubsets;
251 for (quint32 i = 0; i < subsetsCount; ++i) {
252 MeshInternal::Subset subset;
253 float minX;
254 float minY;
255 float minZ;
256 float maxX;
257 float maxY;
258 float maxZ;
259 quint32 nameOffset; // unused
260 inputStream >> subset.count
261 >> subset.offset
262 >> minX
263 >> minY
264 >> minZ
265 >> maxX
266 >> maxY
267 >> maxZ
268 >> nameOffset
269 >> subset.nameLength;
270 subset.bounds.min = QVector3D(minX, minY, minZ);
271 subset.bounds.max = QVector3D(maxX, maxY, maxZ);
272 if (header->hasLightmapSizeHint()) {
273 quint32 width = 0;
274 quint32 height = 0;
275 inputStream >> width >> height;
276 subset.lightmapSizeHint = QSize(width, height);
277 if (header->hasLodDataHint()) {
278 quint32 lodCount = 0;
279 inputStream >> lodCount;
280 subset.lodCount = lodCount;
281 subsetByteSize += SUBSET_STRUCT_SIZE_V6;
282 } else {
283 subsetByteSize += SUBSET_STRUCT_SIZE_V5;
284 }
285 } else {
286 subset.lightmapSizeHint = QSize(0, 0);
287 subsetByteSize += SUBSET_STRUCT_SIZE_V3_V4;
288 }
289 internalSubsets.append(t: subset);
290
291 }
292 alignAmount = offsetTracker.alignedAdvance(advanceAmount: subsetByteSize);
293 if (alignAmount)
294 device->read(data: alignPadding, maxlen: alignAmount);
295
296 for (MeshInternal::Subset &internalSubset : internalSubsets) {
297 internalSubset.rawNameUtf16 = device->read(maxlen: internalSubset.nameLength * 2); //UTF_16_le
298 alignAmount = offsetTracker.alignedAdvance(advanceAmount: internalSubset.nameLength * 2);
299 if (alignAmount)
300 device->read(data: alignPadding, maxlen: alignAmount);
301 }
302
303 quint32 lodByteSize = 0;
304 for (const MeshInternal::Subset &internalSubset : internalSubsets) {
305 auto meshSubset = internalSubset.toMeshSubset();
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;
311 inputStream >> count >> offset >> distance;
312 lod.count = count;
313 lod.offset = offset;
314 lod.distance = distance;
315 lodByteSize += LOD_STRUCT_SIZE;
316 }
317
318 mesh->m_subsets.append(t: meshSubset);
319 }
320 alignAmount = offsetTracker.alignedAdvance(advanceAmount: lodByteSize);
321 if (alignAmount)
322 device->read(data: alignPadding, maxlen: alignAmount);
323
324
325 // Data for morphTargets
326 if (targetBufferEntriesCount > 0) {
327 if (header->hasSeparateTargetBuffer()) {
328 entriesByteSize = 0;
329 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
330 Mesh::VertexBufferEntry targetBufferEntry;
331 quint32 componentType;
332 quint32 nameOffset; // unused
333 inputStream >> nameOffset
334 >> componentType
335 >> targetBufferEntry.componentCount
336 >> targetBufferEntry.offset;
337 targetBufferEntry.componentType = Mesh::ComponentType(componentType);
338 mesh->m_targetBuffer.entries.append(t: targetBufferEntry);
339 entriesByteSize += VERTEX_BUFFER_ENTRY_STRUCT_SIZE;
340 }
341 alignAmount = offsetTracker.alignedAdvance(advanceAmount: entriesByteSize);
342 if (alignAmount)
343 device->read(data: alignPadding, maxlen: alignAmount);
344
345 for (auto &entry : mesh->m_targetBuffer.entries) {
346 quint32 nameLength;
347 inputStream >> nameLength;
348 offsetTracker.advance(advanceAmount: sizeof(quint32));
349 const QByteArray nameWithZeroTerminator = device->read(maxlen: nameLength);
350 entry.name = QByteArray(nameWithZeroTerminator.constData(), qMax(a: 0, b: nameWithZeroTerminator.size() - 1));
351 alignAmount = offsetTracker.alignedAdvance(advanceAmount: nameLength);
352 if (alignAmount)
353 device->read(data: alignPadding, maxlen: alignAmount);
354 }
355
356 mesh->m_targetBuffer.data = device->read(maxlen: targetBufferDataSize);
357 } else {
358 // remove target entries from vertexbuffer entries
359 mesh->m_vertexBuffer.entries.remove(i: vertexBufferEntriesCount - targetBufferEntriesCount,
360 n: targetBufferEntriesCount);
361 const quint32 vertexCount = vertexBufferDataSize / mesh->m_vertexBuffer.stride;
362 const quint32 targetEntryTexWidth = qCeil(v: qSqrt(v: vertexCount));
363 const quint32 targetCompStride = targetEntryTexWidth * targetEntryTexWidth * 4 * sizeof(float);
364 mesh->m_targetBuffer.data.resize(size: targetCompStride * targetBufferEntriesCount);
365 const quint32 numComps = targetBufferEntriesCount / numTargets;
366 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
367 auto &entry = mesh->m_targetBuffer.entries[i];
368 char *dstBuf = mesh->m_targetBuffer.data.data()
369 + (i / numComps) * targetCompStride
370 + (i % numComps) * (targetCompStride * numTargets);
371 const char *srcBuf = mesh->m_vertexBuffer.data.constData() + entry.offset;
372 for (quint32 j = 0; j < vertexCount; ++j) {
373 // The number of old target components is fixed as 3
374 memcpy(dest: dstBuf + j * 4 * sizeof(float),
375 src: srcBuf + j * mesh->m_vertexBuffer.stride,
376 n: 3 * sizeof(float));
377 }
378 entry.offset = i * targetCompStride;
379 }
380 // now we don't need to have redundant targetbuffer entries
381 mesh->m_targetBuffer.entries.remove(i: numComps, n: targetBufferEntriesCount - numComps);
382 mesh->m_targetBuffer.numTargets = numTargets;
383 }
384 }
385
386 return header->sizeInBytes;
387}
388
389void MeshInternal::writeMeshHeader(QIODevice *device, const MeshDataHeader &header)
390{
391 QDataStream outputStream(device);
392 outputStream.setByteOrder(QDataStream::LittleEndian);
393 outputStream.setFloatingPointPrecision(QDataStream::SinglePrecision);
394
395 outputStream << header.fileId << header.fileVersion << header.flags << header.sizeInBytes;
396}
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
407quint64 MeshInternal::writeMeshData(QIODevice *device, const Mesh &mesh)
408{
409 static const char alignPadding[4] = {};
410
411 QDataStream outputStream(device);
412 outputStream.setByteOrder(QDataStream::LittleEndian);
413 outputStream.setFloatingPointPrecision(QDataStream::SinglePrecision);
414
415 const qint64 startPos = device->pos();
416 MeshInternal::MeshOffsetTracker offsetTracker(startPos);
417 Q_ASSERT(offsetTracker.offset() == device->pos());
418
419 const quint32 vertexBufferEntriesCount = mesh.m_vertexBuffer.entries.size();
420 const quint32 vertexBufferDataSize = mesh.m_vertexBuffer.data.size();
421 const quint32 vertexBufferStride = mesh.m_vertexBuffer.stride;
422 const quint32 targetBufferEntriesCount = mesh.m_targetBuffer.entries.count();
423 const quint32 targetBufferDataSize = mesh.m_targetBuffer.data.size();
424 outputStream << targetBufferEntriesCount
425 << vertexBufferEntriesCount
426 << vertexBufferStride;
427 outputStream << targetBufferDataSize
428 << vertexBufferDataSize;
429
430 const quint32 indexBufferDataSize = mesh.m_indexBuffer.data.size();
431 const quint32 indexComponentType = quint32(mesh.m_indexBuffer.componentType);
432 outputStream << indexComponentType;
433 outputStream << quint32(0) // legacy offset
434 << indexBufferDataSize;
435
436 const quint32 targetCount = mesh.m_targetBuffer.numTargets;
437 const quint32 subsetsCount = mesh.m_subsets.size();
438 outputStream << targetCount
439 << subsetsCount;
440
441 outputStream << quint32(0) // legacy offset
442 << quint32(0); // legacy jointsCount
443
444 const quint32 drawMode = quint32(mesh.m_drawMode);
445 const quint32 winding = quint32(mesh.m_winding);
446 outputStream << drawMode
447 << winding;
448
449 offsetTracker.advance(advanceAmount: MESH_STRUCT_SIZE);
450
451 quint32 entriesByteSize = 0;
452 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
453 const Mesh::VertexBufferEntry &entry(mesh.m_vertexBuffer.entries[i]);
454 const quint32 componentType = quint32(entry.componentType);
455 const quint32 componentCount = entry.componentCount;
456 const quint32 offset = entry.offset;
457 outputStream << quint32(0) // legacy offset
458 << componentType
459 << componentCount
460 << offset;
461 entriesByteSize += VERTEX_BUFFER_ENTRY_STRUCT_SIZE;
462 }
463 quint32 alignAmount = offsetTracker.alignedAdvance(advanceAmount: entriesByteSize);
464 if (alignAmount)
465 device->write(data: alignPadding, len: alignAmount);
466
467 for (quint32 i = 0; i < vertexBufferEntriesCount; ++i) {
468 const Mesh::VertexBufferEntry &entry(mesh.m_vertexBuffer.entries[i]);
469 const quint32 nameLength = entry.name.size() + 1;
470 outputStream << nameLength;
471 device->write(data: entry.name.constData(), len: nameLength); // with zero terminator included
472 alignAmount = offsetTracker.alignedAdvance(advanceAmount: sizeof(quint32) + nameLength);
473 if (alignAmount)
474 device->write(data: alignPadding, len: alignAmount);
475 }
476
477 device->write(data: mesh.m_vertexBuffer.data.constData(), len: vertexBufferDataSize);
478 alignAmount = offsetTracker.alignedAdvance(advanceAmount: vertexBufferDataSize);
479 if (alignAmount)
480 device->write(data: alignPadding, len: alignAmount);
481
482 device->write(data: mesh.m_indexBuffer.data.constData(), len: indexBufferDataSize);
483 alignAmount = offsetTracker.alignedAdvance(advanceAmount: indexBufferDataSize);
484 if (alignAmount)
485 device->write(data: alignPadding, len: alignAmount);
486
487 quint32 subsetByteSize = 0;
488 for (quint32 i = 0; i < subsetsCount; ++i) {
489 const Mesh::Subset &subset(mesh.m_subsets[i]);
490 const quint32 subsetCount = subset.count;
491 const quint32 subsetOffset = subset.offset;
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;
499 const quint32 lightmapSizeHintWidth = qMax(a: 0, b: subset.lightmapSizeHint.width());
500 const quint32 lightmapSizeHintHeight = qMax(a: 0, b: subset.lightmapSizeHint.height());
501 const quint32 lodCount = subset.lods.size();
502 outputStream << subsetCount
503 << subsetOffset
504 << minX
505 << minY
506 << minZ
507 << maxX
508 << maxY
509 << maxZ;
510 outputStream << quint32(0) // legacy offset
511 << nameLength;
512 outputStream << lightmapSizeHintWidth
513 << lightmapSizeHintHeight;
514 outputStream << lodCount;
515 subsetByteSize += SUBSET_STRUCT_SIZE_V6;
516 }
517 alignAmount = offsetTracker.alignedAdvance(advanceAmount: subsetByteSize);
518 if (alignAmount)
519 device->write(data: alignPadding, len: 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;
525 device->write(data: utf16Name, len: nameByteSize);
526 alignAmount = offsetTracker.alignedAdvance(advanceAmount: nameByteSize);
527 if (alignAmount)
528 device->write(data: alignPadding, len: alignAmount);
529 }
530
531 // LOD data
532 quint32 lodDataByteSize = 0;
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;
539 outputStream << count << offset << distance;
540 lodDataByteSize += LOD_STRUCT_SIZE;
541 }
542 }
543 alignAmount = offsetTracker.alignedAdvance(advanceAmount: lodDataByteSize);
544 if (alignAmount)
545 device->write(data: alignPadding, len: alignAmount);
546
547 // Data for morphTargets
548 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
549 const Mesh::VertexBufferEntry &entry(mesh.m_targetBuffer.entries[i]);
550 const quint32 componentType = quint32(entry.componentType);
551 const quint32 componentCount = entry.componentCount;
552 const quint32 offset = entry.offset;
553 outputStream << quint32(0) // legacy offset
554 << componentType
555 << componentCount
556 << offset;
557 entriesByteSize += VERTEX_BUFFER_ENTRY_STRUCT_SIZE;
558 }
559 alignAmount = offsetTracker.alignedAdvance(advanceAmount: entriesByteSize);
560 if (alignAmount)
561 device->write(data: alignPadding, len: alignAmount);
562
563 for (quint32 i = 0; i < targetBufferEntriesCount; ++i) {
564 const Mesh::VertexBufferEntry &entry(mesh.m_targetBuffer.entries[i]);
565 const quint32 nameLength = entry.name.size() + 1;
566 outputStream << nameLength;
567 device->write(data: entry.name.constData(), len: nameLength); // with zero terminator included
568 alignAmount = offsetTracker.alignedAdvance(advanceAmount: sizeof(quint32) + nameLength);
569 if (alignAmount)
570 device->write(data: alignPadding, len: alignAmount);
571 }
572
573 device->write(data: mesh.m_targetBuffer.data.constData(), len: targetBufferDataSize);
574
575 const quint32 endPos = device->pos();
576 const quint32 sizeInBytes = endPos - startPos;
577 device->seek(pos: endPos);
578 return sizeInBytes;
579}
580
581Mesh Mesh::loadMesh(QIODevice *device, quint32 id)
582{
583 MeshInternal::MeshDataHeader header;
584 const MeshInternal::MultiMeshInfo meshFileInfo = MeshInternal::readFileHeader(device);
585 auto it = meshFileInfo.meshEntries.constFind(key: id);
586 if (it != meshFileInfo.meshEntries.constEnd()) {
587 Mesh mesh;
588 quint64 size = MeshInternal::readMeshData(device, offset: *it, mesh: &mesh, header: &header);
589 if (size)
590 return mesh;
591 } else if (id == 0 && !meshFileInfo.meshEntries.isEmpty()) {
592 Mesh mesh;
593 quint64 size = MeshInternal::readMeshData(device, offset: *meshFileInfo.meshEntries.cbegin(), mesh: &mesh, header: &header);
594 if (size)
595 return mesh;
596 }
597 return Mesh();
598}
599
600QMap<quint32, Mesh> Mesh::loadAll(QIODevice *device)
601{
602 MeshInternal::MeshDataHeader header;
603 const MeshInternal::MultiMeshInfo meshFileInfo = MeshInternal::readFileHeader(device);
604 QMap<quint32, Mesh> meshes;
605 for (auto it = meshFileInfo.meshEntries.cbegin(), end = meshFileInfo.meshEntries.cend(); it != end; ++it) {
606 Mesh mesh;
607 quint64 size = MeshInternal::readMeshData(device, offset: *it, mesh: &mesh, header: &header);
608 if (size)
609 meshes.insert(key: it.key(), value: mesh);
610 else
611 qWarning(msg: "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
625Mesh Mesh::fromAssetData(const QVector<AssetVertexEntry> &vbufEntries,
626 const QByteArray &indexBufferData,
627 ComponentType indexComponentType,
628 const QVector<AssetMeshSubset> &subsets,
629 quint32 numTargets,
630 quint32 numTargetComps)
631{
632 Mesh mesh;
633 quint32 currentOffset = 0;
634 quint32 bufferAlignment = 0;
635 quint32 numItems = 0;
636 bool ok = true;
637
638 mesh.m_targetBuffer.numTargets = numTargets;
639 quint32 targetCurrentComp = 0;
640 quint32 targetCompStride = 0;
641
642 QVector<AssetVertexEntry> vEntries;
643 for (const AssetVertexEntry &entry : vbufEntries) {
644 // Ignore entries with no data.
645 if (entry.data.isEmpty())
646 continue;
647
648 VertexBufferEntry meshEntry;
649 meshEntry.componentType = entry.componentType;
650 meshEntry.componentCount = entry.componentCount;
651 meshEntry.name = entry.name;
652
653 if (entry.morphTargetId < 0) {
654 const quint32 alignment = MeshInternal::byteSizeForComponentType(componentType: entry.componentType);
655 const quint32 byteSize = alignment * entry.componentCount;
656
657 if (entry.data.size() % alignment != 0) {
658 Q_ASSERT(false);
659 ok = false;
660 }
661
662 quint32 localNumItems = entry.data.size() / byteSize;
663 if (numItems == 0) {
664 numItems = localNumItems;
665 } else if (numItems != localNumItems) {
666 Q_ASSERT(false);
667 ok = false;
668 numItems = qMin(a: numItems, b: localNumItems);
669 }
670
671 currentOffset = getAlignedOffset(offset: currentOffset, align: alignment);
672 meshEntry.offset = currentOffset;
673
674 mesh.m_vertexBuffer.entries.append(t: meshEntry);
675 currentOffset += byteSize;
676 bufferAlignment = qMax(a: bufferAlignment, b: alignment);
677 vEntries.append(t: entry);
678 } else {
679 if (!targetCompStride) {
680 const quint32 targetEntrySize = entry.data.size();
681 quint32 targetEntryTexWidth = qCeil(v: qSqrt(v: ((targetEntrySize + 15) >> 4)));
682 targetCompStride = targetEntryTexWidth * targetEntryTexWidth * 4 * sizeof(float);
683 mesh.m_targetBuffer.data.resize(size: targetCompStride * numTargets * numTargetComps);
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
688 meshEntry.offset = (targetCurrentComp * numTargets + entry.morphTargetId)
689 * targetCompStride;
690 memcpy(dest: mesh.m_targetBuffer.data.data() + meshEntry.offset,
691 src: entry.data.constData(), n: entry.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)
696 mesh.m_targetBuffer.entries.append(t: meshEntry);
697
698 targetCurrentComp = (targetCurrentComp + 1 < numTargetComps) ? targetCurrentComp + 1 : 0;
699 }
700 }
701
702 if (!ok)
703 return Mesh();
704
705 mesh.m_vertexBuffer.stride = getAlignedOffset(offset: currentOffset, align: bufferAlignment);
706
707 // Packed interleave the data.
708 for (quint32 idx = 0; idx < numItems; ++idx) {
709 quint32 dataOffset = 0;
710 for (const AssetVertexEntry &entry : vEntries) {
711 if (entry.data.isEmpty())
712 continue;
713
714 const quint32 alignment = MeshInternal::byteSizeForComponentType(componentType: entry.componentType);
715 const quint32 byteSize = alignment * entry.componentCount;
716 const quint32 offset = byteSize * idx;
717 const quint32 newOffset = getAlignedOffset(offset: dataOffset, align: alignment);
718 if (newOffset != dataOffset) {
719 QByteArray filler(newOffset - dataOffset, '\0');
720 mesh.m_vertexBuffer.data.append(a: filler);
721 }
722
723 mesh.m_vertexBuffer.data.append(s: entry.data.constData() + offset, len: byteSize);
724 dataOffset = newOffset + byteSize;
725 }
726 Q_ASSERT(dataOffset == mesh.m_vertexBuffer.stride);
727 }
728
729 mesh.m_indexBuffer.componentType = indexComponentType;
730 mesh.m_indexBuffer.data = indexBufferData;
731
732 for (const AssetMeshSubset &subset : subsets) {
733 Mesh::Subset meshSubset;
734 meshSubset.name = subset.name;
735 meshSubset.count = subset.count;
736 meshSubset.offset = subset.offset;
737
738 // TODO: QTBUG-102026
739 if (subset.boundsPositionEntryIndex != std::numeric_limits<quint32>::max()) {
740 const QSSGBounds3 bounds = MeshInternal::calculateSubsetBounds(
741 entry: mesh.m_vertexBuffer.entries[subset.boundsPositionEntryIndex],
742 vertexBufferData: mesh.m_vertexBuffer.data,
743 vertexBufferStride: mesh.m_vertexBuffer.stride,
744 indexBufferData: mesh.m_indexBuffer.data,
745 indexComponentType: mesh.m_indexBuffer.componentType,
746 subsetCount: subset.count,
747 subsetOffset: subset.offset);
748 meshSubset.bounds.min = bounds.minimum;
749 meshSubset.bounds.max = bounds.maximum;
750 }
751
752 meshSubset.lightmapSizeHint = QSize(subset.lightmapWidth, subset.lightmapHeight);
753 meshSubset.lods = subset.lods;
754
755 mesh.m_subsets.append(t: meshSubset);
756 }
757
758 mesh.m_drawMode = DrawMode::Triangles;
759 mesh.m_winding = Winding::CounterClockwise;
760
761 return mesh;
762}
763
764Mesh Mesh::fromRuntimeData(const RuntimeMeshData &data, QString *error)
765{
766 if (data.m_vertexBuffer.size() == 0) {
767 *error = QObject::tr(s: "Vertex buffer empty");
768 return Mesh();
769 }
770 if (data.m_attributeCount == 0) {
771 *error = QObject::tr(s: "No attributes defined");
772 return Mesh();
773 }
774
775 Mesh mesh;
776 mesh.m_drawMode = data.m_primitiveType;
777 mesh.m_winding = Winding::CounterClockwise;
778
779 for (int i = 0; i < data.m_attributeCount; ++i) {
780 const RuntimeMeshData::Attribute &att = data.m_attributes[i];
781 if (att.semantic == RuntimeMeshData::Attribute::IndexSemantic) {
782 mesh.m_indexBuffer.componentType = att.componentType;
783 } else {
784 const char *name = nullptr;
785 switch (att.semantic) {
786 case RuntimeMeshData::Attribute::PositionSemantic:
787 name = MeshInternal::getPositionAttrName();
788 break;
789 case RuntimeMeshData::Attribute::NormalSemantic:
790 name = MeshInternal::getNormalAttrName();
791 break;
792 case RuntimeMeshData::Attribute::TexCoord0Semantic:
793 name = MeshInternal::getUV0AttrName();
794 break;
795 case RuntimeMeshData::Attribute::TexCoord1Semantic:
796 name = MeshInternal::getUV1AttrName();
797 break;
798 case RuntimeMeshData::Attribute::TangentSemantic:
799 name = MeshInternal::getTexTanAttrName();
800 break;
801 case RuntimeMeshData::Attribute::BinormalSemantic:
802 name = MeshInternal::getTexBinormalAttrName();
803 break;
804 case RuntimeMeshData::Attribute::JointSemantic:
805 name = MeshInternal::getJointAttrName();
806 break;
807 case RuntimeMeshData::Attribute::WeightSemantic:
808 name = MeshInternal::getWeightAttrName();
809 break;
810 case RuntimeMeshData::Attribute::ColorSemantic:
811 name = MeshInternal::getColorAttrName();
812 break;
813 default:
814 *error = QObject::tr(s: "Warning: Invalid attribute semantic: %1")
815 .arg(a: att.semantic);
816 return Mesh();
817 }
818
819 VertexBufferEntry entry;
820 entry.componentType = att.componentType;
821 entry.componentCount = att.componentCount();
822 entry.offset = att.offset;
823 entry.name = name;
824 mesh.m_vertexBuffer.entries.append(t: entry);
825 }
826 }
827
828 mesh.m_vertexBuffer.data = data.m_vertexBuffer;
829 // Only interleaved vertex attribute packing is supported, both internally
830 // and in the QQuick3DGeometry API, hence the per-vertex buffer stride.
831 mesh.m_vertexBuffer.stride = data.m_stride;
832 mesh.m_subsets = data.m_subsets;
833 mesh.m_indexBuffer.data = data.m_indexBuffer;
834
835 if (!data.m_targetBuffer.isEmpty()) {
836 const quint32 vertexCount = data.m_vertexBuffer.size() / data.m_stride;
837 const quint32 targetEntryTexWidth = qCeil(v: qSqrt(v: vertexCount));
838 const quint32 targetCompStride = targetEntryTexWidth * targetEntryTexWidth * 4 * sizeof(float);
839 mesh.m_targetBuffer.data.resize(size: targetCompStride * data.m_targetAttributeCount);
840
841 QVarLengthArray<RuntimeMeshData::TargetAttribute> sortedAttribs(
842 data.m_targetAttributes,
843 data.m_targetAttributes + data.m_targetAttributeCount);
844 std::sort(first: sortedAttribs.begin(), last: sortedAttribs.end(),
845 comp: [] (RuntimeMeshData::TargetAttribute a, RuntimeMeshData::TargetAttribute b) {
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) {
849 const RuntimeMeshData::Attribute &att = sortedAttribs[i].attr;
850 const int stride = (sortedAttribs[i].stride < 1) ? att.componentCount() * sizeof(float)
851 : sortedAttribs[i].stride;
852 const char *name = nullptr;
853 switch (att.semantic) {
854 case RuntimeMeshData::Attribute::PositionSemantic:
855 name = MeshInternal::getPositionAttrName();
856 break;
857 case RuntimeMeshData::Attribute::NormalSemantic:
858 name = MeshInternal::getNormalAttrName();
859 break;
860 case RuntimeMeshData::Attribute::TexCoord0Semantic:
861 name = MeshInternal::getUV0AttrName();
862 break;
863 case RuntimeMeshData::Attribute::TexCoord1Semantic:
864 name = MeshInternal::getUV1AttrName();
865 break;
866 case RuntimeMeshData::Attribute::TangentSemantic:
867 name = MeshInternal::getTexTanAttrName();
868 break;
869 case RuntimeMeshData::Attribute::BinormalSemantic:
870 name = MeshInternal::getTexBinormalAttrName();
871 break;
872 case RuntimeMeshData::Attribute::IndexSemantic:
873 case RuntimeMeshData::Attribute::JointSemantic:
874 case RuntimeMeshData::Attribute::WeightSemantic:
875 *error = QObject::tr(s: "Warning: Invalid target attribute semantic: %1")
876 .arg(a: att.semantic);
877 continue;
878 case RuntimeMeshData::Attribute::ColorSemantic:
879 name = MeshInternal::getColorAttrName();
880 break;
881 default:
882 *error = QObject::tr(s: "Warning: Invalid target attribute semantic: %1")
883 .arg(a: att.semantic);
884 return Mesh();
885 }
886 char *dstBuf = mesh.m_targetBuffer.data.data() + i * targetCompStride;
887 const char *srcBuf = data.m_targetBuffer.constData() + att.offset;
888 Q_ASSERT(att.componentType == Mesh::ComponentType::Float32);
889 if (stride == 4 * sizeof(float)) {
890 memcpy(dest: dstBuf, src: srcBuf, n: vertexCount * stride);
891 } else {
892 for (quint32 j = 0; j < vertexCount; ++j) {
893 memcpy(dest: dstBuf + j * 4 * sizeof(float),
894 src: srcBuf + j * stride,
895 n: att.componentCount() * sizeof(float));
896 }
897 }
898
899 if (sortedAttribs[i].targetId == 0) {
900 VertexBufferEntry entry;
901 entry.componentType = att.componentType;
902 entry.componentCount = att.componentCount();
903 entry.offset = i * targetCompStride;
904 entry.name = name;
905 mesh.m_targetBuffer.entries.append(t: entry);
906 }
907 }
908 mesh.m_targetBuffer.numTargets = data.m_targetAttributeCount / mesh.m_targetBuffer.entries.size();
909 }
910 return mesh;
911}
912
913quint32 Mesh::save(QIODevice *device, quint32 id) const
914{
915 qint64 newMeshStartPosFromEnd = 0;
916 quint32 newId = 1;
917 MeshInternal::MultiMeshInfo header;
918
919 if (device->size() > 0) {
920 header = MeshInternal::readFileHeader(device);
921 if (!header.isValid()) {
922 qWarning(msg: "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(a: newId, b: it.key() + 1);
931 }
932 }
933 newMeshStartPosFromEnd = MULTI_HEADER_STRUCT_SIZE + header.meshEntries.size() * MULTI_ENTRY_STRUCT_SIZE;
934 } else {
935 header = MeshInternal::MultiMeshInfo::withDefaults();
936 }
937
938 // the new mesh data overwrites the entry list and file header
939 device->seek(pos: device->size() - newMeshStartPosFromEnd);
940 const qint64 meshOffset = device->pos();
941 header.meshEntries.insert(key: newId, value: meshOffset);
942
943 MeshInternal::MeshDataHeader meshHeader = MeshInternal::MeshDataHeader::withDefaults();
944 // skip the space for the mesh header for now
945 device->seek(pos: device->pos() + MESH_HEADER_STRUCT_SIZE);
946 meshHeader.sizeInBytes = MeshInternal::writeMeshData(device, mesh: *this);
947 // now the mesh header is ready to be written out
948 device->seek(pos: meshOffset);
949 MeshInternal::writeMeshHeader(device, header: meshHeader);
950 device->seek(pos: meshOffset + MESH_HEADER_STRUCT_SIZE + meshHeader.sizeInBytes);
951 // write out new entry list and file header
952 MeshInternal::writeFileHeader(device, meshFileInfo: header);
953
954 return newId;
955}
956
957QSSGBounds3 MeshInternal::calculateSubsetBounds(const Mesh::VertexBufferEntry &entry,
958 const QByteArray &vertexBufferData,
959 quint32 vertexBufferStride,
960 const QByteArray &indexBufferData,
961 Mesh::ComponentType indexComponentType,
962 quint32 subsetCount,
963 quint32 subsetOffset)
964{
965 QSSGBounds3 result;
966 if (entry.componentType != Mesh::ComponentType::Float32 || entry.componentCount != 3) {
967 Q_ASSERT(false);
968 return result;
969 }
970
971 const int indexComponentByteSize = byteSizeForComponentType(componentType: indexComponentType);
972 if (indexComponentByteSize != 2 && indexComponentByteSize != 4) {
973 Q_ASSERT(false);
974 return result;
975 }
976
977 const quint32 indexBufferCount = indexBufferData.size() / indexComponentByteSize;
978 const quint32 vertexBufferByteSize = vertexBufferData.size();
979 const char *vertexSrcPtr = vertexBufferData.constData();
980 const char *indexSrcPtr = indexBufferData.constData();
981
982 for (quint32 idx = 0, numItems = subsetCount; idx < numItems; ++idx) {
983 if (idx + subsetOffset >= indexBufferCount)
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:
995 Q_UNREACHABLE();
996 break;
997 }
998
999 const quint32 finalOffset = entry.offset + (vertexIdx * vertexBufferStride);
1000 float v[3];
1001 if (finalOffset + sizeof(v) <= vertexBufferByteSize) {
1002 memcpy(dest: v, src: vertexSrcPtr + finalOffset, n: sizeof(v));
1003 result.include(v: QVector3D(v[0], v[1], v[2]));
1004 } else {
1005 Q_ASSERT(false);
1006 }
1007 }
1008
1009 return result;
1010}
1011
1012bool Mesh::hasLightmapUVChannel() const
1013{
1014 const char *lightmapAttrName = MeshInternal::getLightmapUVAttrName();
1015 for (const VertexBufferEntry &vbe : std::as_const(t: m_vertexBuffer.entries)) {
1016 if (vbe.name == lightmapAttrName)
1017 return true;
1018 }
1019 return false;
1020}
1021
1022bool Mesh::createLightmapUVChannel(uint lightmapBaseResolution)
1023{
1024 const char *posAttrName = MeshInternal::getPositionAttrName();
1025 const char *normalAttrName = MeshInternal::getNormalAttrName();
1026 const char *uvAttrName = MeshInternal::getUV0AttrName();
1027 const char *lightmapAttrName = MeshInternal::getLightmapUVAttrName();
1028
1029 // this function should do nothing if there is already an attr_lightmapuv
1030 if (hasLightmapUVChannel())
1031 return true;
1032
1033 const char *srcVertexData = m_vertexBuffer.data.constData();
1034 const quint32 srcVertexStride = m_vertexBuffer.stride;
1035 if (!srcVertexStride) {
1036 qWarning(msg: "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(msg: "Lightmap UV unwrapping encountered a Mesh without index data, this cannot happen");
1041 return false;
1042 }
1043
1044 quint32 positionOffset = UINT32_MAX;
1045 quint32 normalOffset = UINT32_MAX;
1046 quint32 uvOffset = UINT32_MAX;
1047
1048 for (const VertexBufferEntry &vbe : std::as_const(t&: m_vertexBuffer.entries)) {
1049 if (vbe.name == posAttrName) {
1050 if (vbe.componentCount != 3) {
1051 qWarning(msg: "Lightmap UV unwrapping encountered a Mesh non-float3 position data, this cannot happen");
1052 return false;
1053 }
1054 positionOffset = vbe.offset;
1055 } else if (vbe.name == normalAttrName) {
1056 if (vbe.componentCount != 3) {
1057 qWarning(msg: "Lightmap UV unwrapping encountered a Mesh non-float3 normal data, this cannot happen");
1058 return false;
1059 }
1060 normalOffset = vbe.offset;
1061 } else if (vbe.name == uvAttrName) {
1062 if (vbe.componentCount != 2) {
1063 qWarning(msg: "Lightmap UV unwrapping encountered a Mesh non-float2 UV0 data, this cannot happen");
1064 return false;
1065 }
1066 uvOffset = vbe.offset;
1067 }
1068 }
1069
1070 if (positionOffset == UINT32_MAX) {
1071 qWarning(msg: "Lightmap UV unwrapping encountered a Mesh without vertex positions, this cannot happen");
1072 return false;
1073 }
1074 // normal and uv0 are optional
1075
1076 const qsizetype vertexCount = m_vertexBuffer.data.size() / srcVertexStride;
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);
1082 *posPtr++ = *srcPos++;
1083 *posPtr++ = *srcPos++;
1084 *posPtr++ = *srcPos++;
1085 }
1086
1087 QByteArray normalData;
1088 if (normalOffset != UINT32_MAX) {
1089 normalData.resize(size: vertexCount * 3 * sizeof(float));
1090 float *normPtr = reinterpret_cast<float *>(normalData.data());
1091 for (qsizetype i = 0; i < vertexCount; ++i) {
1092 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1093 const float *srcNormal = reinterpret_cast<const float *>(vertexBasePtr + normalOffset);
1094 *normPtr++ = *srcNormal++;
1095 *normPtr++ = *srcNormal++;
1096 *normPtr++ = *srcNormal++;
1097 }
1098 }
1099
1100 QByteArray uvData;
1101 if (uvOffset != UINT32_MAX) {
1102 uvData.resize(size: vertexCount * 2 * sizeof(float));
1103 float *uvPtr = reinterpret_cast<float *>(uvData.data());
1104 for (qsizetype i = 0; i < vertexCount; ++i) {
1105 const char *vertexBasePtr = srcVertexData + i * srcVertexStride;
1106 const float *srcUv = reinterpret_cast<const float *>(vertexBasePtr + uvOffset);
1107 *uvPtr++ = *srcUv++;
1108 *uvPtr++ = *srcUv++;
1109 }
1110 }
1111
1112 QSSGLightmapUVGenerator uvGen;
1113 QSSGLightmapUVGeneratorResult r = uvGen.run(positions: positionData, normals: normalData, uv0: uvData,
1114 index: m_indexBuffer.data, indexComponentType: m_indexBuffer.componentType,
1115 baseResolution: lightmapBaseResolution);
1116 if (!r.isValid())
1117 return false;
1118
1119 // the result can have more (but never less) vertices than the input
1120 const int newVertexCount = r.vertexMap.size();
1121
1122 // r.indexData contains the new index data that has the same number of elements as before
1123 const quint32 *newIndex = reinterpret_cast<const quint32 *>(r.indexData.constData());
1124 if (m_indexBuffer.componentType == QSSGMesh::Mesh::ComponentType::UnsignedInt32) {
1125 if (r.indexData.size() != m_indexBuffer.data.size()) {
1126 qWarning(msg: "Index buffer size mismatch after lightmap UV unwrapping");
1127 return false;
1128 }
1129 quint32 *indexDst = reinterpret_cast<quint32 *>(m_indexBuffer.data.data());
1130 memcpy(dest: indexDst, src: newIndex, n: m_indexBuffer.data.size());
1131 } else {
1132 if (r.indexData.size() != m_indexBuffer.data.size() * 2) {
1133 qWarning(msg: "Index buffer size mismatch after lightmap UV unwrapping");
1134 return false;
1135 }
1136 quint16 *indexDst = reinterpret_cast<quint16 *>(m_indexBuffer.data.data());
1137 for (size_t i = 0, count = m_indexBuffer.data.size() / sizeof(quint16); i != count; ++i)
1138 *indexDst++ = *newIndex++;
1139 }
1140
1141 QVarLengthArray<QByteArray, 8> newData;
1142 newData.reserve(sz: m_vertexBuffer.entries.size());
1143
1144 for (const VertexBufferEntry &vbe : std::as_const(t&: m_vertexBuffer.entries)) {
1145 const qsizetype byteSize = vbe.componentCount * MeshInternal::byteSizeForComponentType(componentType: vbe.componentType);
1146 QByteArray data(byteSize * vertexCount, Qt::Uninitialized);
1147 char *dst = data.data();
1148 for (qsizetype i = 0; i < vertexCount; ++i) {
1149 memcpy(dest: dst, src: srcVertexData + i * srcVertexStride + vbe.offset, n: byteSize);
1150 dst += byteSize;
1151 }
1152 switch (vbe.componentType) {
1153 case ComponentType::UnsignedInt8:
1154 newData.append(t: QSSGLightmapUVGenerator::remap<quint8>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1155 break;
1156 case ComponentType::Int8:
1157 newData.append(t: QSSGLightmapUVGenerator::remap<qint8>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1158 break;
1159 case ComponentType::UnsignedInt16:
1160 newData.append(t: QSSGLightmapUVGenerator::remap<quint16>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1161 break;
1162 case ComponentType::Int16:
1163 newData.append(t: QSSGLightmapUVGenerator::remap<qint16>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1164 break;
1165 case ComponentType::UnsignedInt32:
1166 newData.append(t: QSSGLightmapUVGenerator::remap<quint32>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1167 break;
1168 case ComponentType::Int32:
1169 newData.append(t: QSSGLightmapUVGenerator::remap<qint32>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1170 break;
1171 case ComponentType::UnsignedInt64:
1172 newData.append(t: QSSGLightmapUVGenerator::remap<quint64>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1173 break;
1174 case ComponentType::Int64:
1175 newData.append(t: QSSGLightmapUVGenerator::remap<qint64>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1176 break;
1177 case ComponentType::Float16:
1178 newData.append(t: QSSGLightmapUVGenerator::remap<qfloat16>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1179 break;
1180 case ComponentType::Float32:
1181 newData.append(t: QSSGLightmapUVGenerator::remap<float>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1182 break;
1183 case ComponentType::Float64:
1184 newData.append(t: QSSGLightmapUVGenerator::remap<double>(source: data, vertexMap: r.vertexMap, componentCount: vbe.componentCount));
1185 break;
1186 }
1187 }
1188
1189 VertexBufferEntry lightmapUVEntry;
1190 lightmapUVEntry.componentType = ComponentType::Float32;
1191 lightmapUVEntry.componentCount = 2;
1192 lightmapUVEntry.offset = 0;
1193 lightmapUVEntry.name = lightmapAttrName;
1194
1195 QByteArray newVertexBuffer;
1196 newVertexBuffer.reserve(asize: newVertexCount * (srcVertexStride + 8));
1197
1198 quint32 bufferAlignment = 0;
1199 for (int vertexIdx = 0; vertexIdx < newVertexCount; ++vertexIdx) {
1200 quint32 dataOffset = 0;
1201 for (int vbIdx = 0, end = m_vertexBuffer.entries.size(); vbIdx != end; ++vbIdx) {
1202 VertexBufferEntry &vbe(m_vertexBuffer.entries[vbIdx]);
1203
1204 const quint32 alignment = MeshInternal::byteSizeForComponentType(componentType: vbe.componentType);
1205 bufferAlignment = qMax(a: bufferAlignment, b: alignment);
1206 const quint32 byteSize = alignment * vbe.componentCount;
1207 const quint32 newOffset = getAlignedOffset(offset: dataOffset, align: alignment);
1208
1209 if (newOffset != dataOffset) {
1210 QByteArray filler(newOffset - dataOffset, '\0');
1211 newVertexBuffer.append(a: filler);
1212 }
1213
1214 if (vertexIdx == 0)
1215 vbe.offset = newVertexBuffer.size();
1216
1217 newVertexBuffer.append(s: newData[vbIdx].constData() + byteSize * vertexIdx, len: byteSize);
1218 dataOffset = newOffset + byteSize;
1219 }
1220
1221 const quint32 byteSize = 2 * sizeof(float);
1222 const quint32 newOffset = getAlignedOffset(offset: dataOffset, align: byteSize);
1223 if (newOffset != dataOffset) {
1224 QByteArray filler(newOffset - dataOffset, '\0');
1225 newVertexBuffer.append(a: filler);
1226 }
1227
1228 if (vertexIdx == 0)
1229 lightmapUVEntry.offset = newVertexBuffer.size();
1230
1231 newVertexBuffer.append(s: r.lightmapUVChannel.constData() + byteSize * vertexIdx, len: byteSize);
1232 dataOffset = newOffset + byteSize;
1233
1234 if (vertexIdx == 0)
1235 m_vertexBuffer.stride = getAlignedOffset(offset: dataOffset, align: bufferAlignment);
1236 }
1237
1238 m_vertexBuffer.entries.append(t: lightmapUVEntry);
1239
1240 m_vertexBuffer.data = newVertexBuffer;
1241
1242 const QSize lightmapSizeHint(r.lightmapWidth, r.lightmapHeight);
1243 for (Subset &subset : m_subsets)
1244 subset.lightmapSizeHint = lightmapSizeHint;
1245
1246 return true;
1247}
1248
1249size_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)
1250{
1251 return meshopt_simplify(destination, indices, index_count: indexCount, vertex_positions: vertexPositions, vertex_count: vertexCount, vertex_positions_stride: vertexPositionsStride, target_index_count: targetIndexCount, target_error: targetError, options, result_error: resultError);
1252}
1253
1254float simplifyScale(const float *vertexPositions, size_t vertexCount, size_t vertexPositionsStride)
1255{
1256 return meshopt_simplifyScale(vertex_positions: vertexPositions, vertex_count: vertexCount, vertex_positions_stride: vertexPositionsStride);
1257}
1258
1259void optimizeVertexCache(unsigned int *destination, const unsigned int *indices, size_t indexCount, size_t vertexCount)
1260{
1261 meshopt_optimizeVertexCache(destination, indices, index_count: indexCount, vertex_count: vertexCount);
1262}
1263
1264} // namespace QSSGMesh
1265
1266QT_END_NAMESPACE
1267

source code of qtquick3d/src/utils/qssgmesh.cpp