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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | namespace QSSGMesh { |
17 | |
18 | // fileId, fileVersion, offset, count |
19 | static const size_t = 16; |
20 | |
21 | // meshOffset, meshId, padding |
22 | static const size_t MULTI_ENTRY_STRUCT_SIZE = 16; |
23 | |
24 | // fileId, fileVersion, flags, size |
25 | static const size_t = 12; |
26 | |
27 | // vertexBuffer, indexBuffer, subsets, joints, drawMode, winding |
28 | static const size_t MESH_STRUCT_SIZE = 56; |
29 | |
30 | // vertex buffer entry list: nameOffset, componentType, componentCount, offset |
31 | static const size_t VERTEX_BUFFER_ENTRY_STRUCT_SIZE = 16; |
32 | |
33 | // subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength |
34 | static const size_t SUBSET_STRUCT_SIZE_V3_V4 = 40; |
35 | // subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength, lightmapSizeWidth, lightmapSizeHeight |
36 | static const size_t SUBSET_STRUCT_SIZE_V5 = 48; |
37 | // subset list: count, offset, minXYZ, maxXYZ, nameOffset, nameLength, lightmapSizeWidth, lightmapSizeHeight, lodCount |
38 | static const size_t SUBSET_STRUCT_SIZE_V6 = 52; |
39 | |
40 | //lod entry: count, offset, distance |
41 | static const size_t LOD_STRUCT_SIZE = 12; |
42 | |
43 | MeshInternal::MultiMeshInfo MeshInternal::(QIODevice *device) |
44 | { |
45 | const qint64 = 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 | |
77 | void MeshInternal::(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 | |
95 | quint64 MeshInternal::(QIODevice *device, quint64 offset, Mesh *mesh, MeshDataHeader *) |
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 | |
389 | void MeshInternal::(QIODevice *device, const MeshDataHeader &) |
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 | |
407 | quint64 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 | |
581 | Mesh Mesh::loadMesh(QIODevice *device, quint32 id) |
582 | { |
583 | MeshInternal::MeshDataHeader ; |
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 | |
600 | QMap<quint32, Mesh> Mesh::loadAll(QIODevice *device) |
601 | { |
602 | MeshInternal::MeshDataHeader ; |
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 | |
616 | static 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 | |
625 | Mesh 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 | |
764 | Mesh 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 | |
913 | quint32 Mesh::save(QIODevice *device, quint32 id) const |
914 | { |
915 | qint64 newMeshStartPosFromEnd = 0; |
916 | quint32 newId = 1; |
917 | MeshInternal::MultiMeshInfo ; |
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 = 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 | |
957 | QSSGBounds3 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 | |
1012 | bool 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 | |
1022 | bool 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 | |
1249 | 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) |
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 | |
1254 | float 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 | |
1259 | void 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 | |
1266 | QT_END_NAMESPACE |
1267 | |