Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
assimputils.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
6#include "assimputils.h"
7
8#include <assimp/Importer.hpp>
9#include <assimp/scene.h>
10#include <assimp/Logger.hpp>
11#include <assimp/DefaultLogger.hpp>
12#include <assimp/postprocess.h>
13#include <assimp/importerdesc.h>
14
15#include <QtQuick3DUtils/private/qssgutils_p.h>
16
17#include <QtCore/qstring.h>
18#include <QtCore/QHash>
19#include <QtCore/QSet>
20#include <QtCore/QVarLengthArray>
21
22QT_BEGIN_NAMESPACE
23
24namespace
25{
26
35
37 qint32 x = 0;
38 qint32 y = 0;
39 qint32 z = 0;
40 qint32 w = 0;
41};
42
52
59
61 bool needsPositionData = false;
62 bool needsNormalData = false;
63 bool needsTangentData = false;
65 unsigned uv0Components = 0;
66 unsigned uv1Components = 0;
67 bool needsUV0Data = false;
68 bool needsUV1Data = false;
69 bool needsBones = false;
71
73 // All the target mesh will have the same components
74 // Target texture coords will be recored as 3 components.
75 // even if we are using just 2 components now.
80 bool needsTargetUV0Data = false;
81 bool needsTargetUV1Data = false;
82
83 void collectRequirmentsForMesh(const aiMesh *mesh) {
84 uv0Components = qMax(mesh->mNumUVComponents[0], uv0Components);
85 uv1Components = qMax(mesh->mNumUVComponents[1], uv1Components);
86 needsUV0Data |= mesh->HasTextureCoords(0);
87 needsUV1Data |= mesh->HasTextureCoords(1);
88 needsPositionData |= mesh->HasPositions();
89 needsNormalData |= mesh->HasNormals();
90 needsTangentData |= mesh->HasTangentsAndBitangents();
91 needsVertexColorData |=mesh->HasVertexColors(0);
92 needsBones |= mesh->HasBones();
93 numMorphTargets = mesh->mNumAnimMeshes;
94 if (numMorphTargets && mesh->mAnimMeshes) {
95 for (uint i = 0; i < numMorphTargets; ++i) {
96 auto animMesh = mesh->mAnimMeshes[i];
97 needsTargetPositionData |= animMesh->HasPositions();
98 needsTargetNormalData |= animMesh->HasNormals();
99 needsTargetTangentData |= animMesh->HasTangentsAndBitangents();
100 needsTargetVertexColorData |= animMesh->HasVertexColors(0);
101 needsTargetUV0Data |= animMesh->HasTextureCoords(0);
102 needsTargetUV1Data |= animMesh->HasTextureCoords(1);
103 }
104 }
105 }
106};
107
109{
110 QVector<VertexAttributeDataExt> vertexAttributes;
111
112 vertexAttributes.resize(mesh->mNumVertices);
113
114 // Positions
115 if (mesh->HasPositions()) {
116 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
117 const auto vertex = mesh->mVertices[index];
118 vertexAttributes[index].aData.position = QVector3D(vertex.x, vertex.y, vertex.z);
119 }
120 }
121
122 // Normals
123 if (mesh->HasNormals()) {
124 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
125 const auto normal = mesh->mNormals[index];
126 vertexAttributes[index].aData.normal = QVector3D(normal.x, normal.y, normal.z);
127 }
128 }
129
130 // UV0
131 if (mesh->HasTextureCoords(0)) {
132 const auto texCoords = mesh->mTextureCoords[0];
133 if (requirments.uv0Components == 2) {
134 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
135 const auto uv = texCoords[index];
136 vertexAttributes[index].aData.uv0 = QVector3D(uv.x, uv.y, 0.0f);
137 }
138 } else if (requirments.uv0Components == 3) {
139 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
140 const auto uv = texCoords[index];
141 vertexAttributes[index].aData.uv0 = QVector3D(uv.x, uv.y, uv.z);
142 }
143 }
144 }
145
146 // UV1
147 if (mesh->HasTextureCoords(1)) {
148 const auto texCoords = mesh->mTextureCoords[1];
149 if (requirments.uv1Components == 2) {
150 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
151 const auto uv = texCoords[index];
152 vertexAttributes[index].aData.uv1 = QVector3D(uv.x, uv.y, 0.0f);
153 }
154 } else if (requirments.uv1Components == 3) {
155 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
156 const auto uv = texCoords[index];
157 vertexAttributes[index].aData.uv1 = QVector3D(uv.x, uv.y, uv.z);
158 }
159 }
160 }
161
162 // Tangents and Binormals
163 if (mesh->HasTangentsAndBitangents()) {
164 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
165 const auto tangent = mesh->mTangents[index];
166 const auto binormal = mesh->mBitangents[index];
167 vertexAttributes[index].aData.tangent = QVector3D(tangent.x, tangent.y, tangent.z);
168 vertexAttributes[index].aData.binormal = QVector3D(binormal.x, binormal.y, binormal.z);
169 }
170 }
171
172 // Vertex Colors
173 if (mesh->HasVertexColors(0)) {
174 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
175 const auto color = mesh->mColors[0][index];
176 vertexAttributes[index].aData.color = QVector4D(color.r, color.g, color.b, color.a);
177 }
178 }
179
180 // Bones + Weights
181 if (mesh->HasBones()) {
182 for (uint i = 0; i < mesh->mNumBones; ++i) {
183 const uint vId = i;
184 for (uint j = 0; j < mesh->mBones[i]->mNumWeights; ++j) {
185 quint32 vertexId = mesh->mBones[i]->mWeights[j].mVertexId;
186 float weight = mesh->mBones[i]->mWeights[j].mWeight;
187
188 // skip a bone transform having small weight
189 if (weight <= 0.01f)
190 continue;
191
192 // if any vertex has more weights than 4, it will be ignored
193 if (vertexAttributes[vertexId].boneWeights.x() == 0.0f) {
194 vertexAttributes[vertexId].boneIndexes.x = qint32(vId);
195 vertexAttributes[vertexId].boneWeights.setX(weight);
196 } else if (vertexAttributes[vertexId].boneWeights.y() == 0.0f) {
197 vertexAttributes[vertexId].boneIndexes.y = qint32(vId);
198 vertexAttributes[vertexId].boneWeights.setY(weight);
199 } else if (vertexAttributes[vertexId].boneWeights.z() == 0.0f) {
200 vertexAttributes[vertexId].boneIndexes.z = qint32(vId);
201 vertexAttributes[vertexId].boneWeights.setZ(weight);
202 } else if (vertexAttributes[vertexId].boneWeights.w() == 0.0f) {
203 vertexAttributes[vertexId].boneIndexes.w = qint32(vId);
204 vertexAttributes[vertexId].boneWeights.setW(weight);
205 } else {
206 qWarning("vertexId %d has already 4 weights and index %d's weight %f will be ignored.", vertexId, vId, weight);
207 }
208 }
209 }
210 }
211
212 // Morph Targets
213 if (requirments.numMorphTargets > 0) {
214 for (unsigned int index = 0; index < mesh->mNumVertices; ++index) {
215 vertexAttributes[index].targetAData.resize(requirments.numMorphTargets);
216
217 for (uint i = 0; i < requirments.numMorphTargets; ++i) {
218 if (i >= mesh->mNumAnimMeshes)
219 continue;
220
221 auto animMesh = mesh->mAnimMeshes[i];
222 if (animMesh->HasPositions()) {
223 const auto vertex = animMesh->mVertices[index];
224 vertexAttributes[index].targetAData[i].position = QVector3D(vertex.x, vertex.y, vertex.z);
225 }
226 if (animMesh->HasNormals()) {
227 const auto normal = animMesh->mNormals[index];
228 vertexAttributes[index].targetAData[i].normal = QVector3D(normal.x, normal.y, normal.z);
229 }
230 if (animMesh->HasTangentsAndBitangents()) {
231 const auto tangent = animMesh->mTangents[index];
232 const auto binormal = animMesh->mBitangents[index];
233 vertexAttributes[index].targetAData[i].tangent = QVector3D(tangent.x, tangent.y, tangent.z);
234 vertexAttributes[index].targetAData[i].binormal = QVector3D(binormal.x, binormal.y, binormal.z);
235 }
236 if (animMesh->HasTextureCoords(0)) {
237 const auto texCoords = animMesh->mTextureCoords[0];
238 const auto uv = texCoords[index];
239 vertexAttributes[index].targetAData[i].uv0 = QVector3D(uv.x, uv.y, uv.z);
240 }
241 if (animMesh->HasTextureCoords(1)) {
242 const auto texCoords = animMesh->mTextureCoords[1];
243 const auto uv = texCoords[index];
244 vertexAttributes[index].targetAData[i].uv1 = QVector3D(uv.x, uv.y, uv.z);
245 }
246 if (animMesh->HasVertexColors(0)) {
247 const auto color = animMesh->mColors[0][index];
248 vertexAttributes[index].targetAData[i].color = QVector4D(color.r, color.g, color.b, color.a);
249 }
250 }
251 }
252 }
253
254 return vertexAttributes;
255}
256
266
272
274 {
275 // Position
276 if (requirments.needsPositionData)
277 vData.positionData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.aData.position), sizeof(QVector3D));
278 // Normal
279 if (requirments.needsNormalData)
280 vData.normalData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.aData.normal), sizeof(QVector3D));
281 // UV0
282
283 if (requirments.needsUV0Data) {
284 if (requirments.uv0Components == 2) {
285 const QVector2D uv(vertex.aData.uv0.x(), vertex.aData.uv0.y());
286 vData.uv0Data += QByteArray::fromRawData(reinterpret_cast<const char *>(&uv), sizeof(QVector2D));
287 } else {
288 vData.uv0Data += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.aData.uv0), sizeof(QVector3D));
289 }
290 }
291
292 // UV1
293 if (requirments.needsUV1Data) {
294 if (requirments.uv1Components == 2) {
295 const QVector2D uv(vertex.aData.uv1.x(), vertex.aData.uv1.y());
296 vData.uv1Data += QByteArray::fromRawData(reinterpret_cast<const char *>(&uv), sizeof(QVector2D));
297 } else {
298 vData.uv1Data += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.aData.uv1), sizeof(QVector3D));
299 }
300 }
301
302 // Tangent
303 // Binormal
304 if (requirments.needsTangentData) {
305 vData.tangentData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.aData.tangent), sizeof(QVector3D));
306 vData.binormalData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.aData.binormal), sizeof(QVector3D));
307 }
308
309 // Color
310 if (requirments.needsVertexColorData)
311 vData.vertexColorData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.aData.color), sizeof(QVector4D));
312
313 // Bone Indexes
314 // Bone Weights
315 if (requirments.needsBones) {
316 if (requirments.useFloatJointIndices) {
317 const QVector4D fBoneIndex(float(vertex.boneIndexes.x), float(vertex.boneIndexes.y), float(vertex.boneIndexes.z), float(vertex.boneIndexes.w));
318 boneIndexData += QByteArray::fromRawData(reinterpret_cast<const char *>(&fBoneIndex), sizeof(QVector4D));
319 } else {
320 boneIndexData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.boneIndexes), sizeof(IntVector4D));
321 }
322 boneWeightData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.boneWeights), sizeof(QVector4D));
323 }
324
325 // Morph Targets
326 for (uint i = 0; i < requirments.numMorphTargets; ++i) {
327 if (requirments.needsTargetPositionData) {
328 targetVData[i].positionData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.targetAData[i].position), sizeof(QVector3D));
329 targetVData[i].positionData.append(sizeof(float), '\0');
330 }
331 if (requirments.needsTargetNormalData) {
332 targetVData[i].normalData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.targetAData[i].normal), sizeof(QVector3D));
333 targetVData[i].normalData.append(sizeof(float), '\0');
334 }
335 if (requirments.needsTargetTangentData) {
336 targetVData[i].tangentData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.targetAData[i].tangent), sizeof(QVector3D));
337 targetVData[i].tangentData.append(sizeof(float), '\0');
338 targetVData[i].binormalData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.targetAData[i].binormal), sizeof(QVector3D));
339 targetVData[i].binormalData.append(sizeof(float), '\0');
340 }
341 if (requirments.needsTargetUV0Data) {
342 targetVData[i].uv0Data += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.targetAData[i].uv0), sizeof(QVector3D));
343 targetVData[i].uv0Data.append(sizeof(float), '\0');
344 }
345 if (requirments.needsTargetUV1Data) {
346 targetVData[i].uv1Data += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.targetAData[i].uv1), sizeof(QVector3D));
347 targetVData[i].uv1Data.append(sizeof(float), '\0');
348 }
349 if (requirments.needsTargetVertexColorData) {
350 targetVData[i].vertexColorData += QByteArray::fromRawData(reinterpret_cast<const char *>(&vertex.targetAData[i].color), sizeof(QVector4D));
351 }
352 }
353 }
354
356 QVector<QSSGMesh::AssetVertexEntry> entries;
357 if (vData.positionData.size() > 0) {
358 entries.append({
359 QSSGMesh::MeshInternal::getPositionAttrName(),
360 vData.positionData,
361 QSSGMesh::Mesh::ComponentType::Float32,
362 3
363 });
364 }
365 if (vData.normalData.size() > 0) {
366 entries.append({
367 QSSGMesh::MeshInternal::getNormalAttrName(),
368 vData.normalData,
369 QSSGMesh::Mesh::ComponentType::Float32,
370 3
371 });
372 }
373 if (vData.uv0Data.size() > 0) {
374 entries.append({
375 QSSGMesh::MeshInternal::getUV0AttrName(),
376 vData.uv0Data,
377 QSSGMesh::Mesh::ComponentType::Float32,
378 requirments.uv0Components
379 });
380 }
381 if (vData.uv1Data.size() > 0) {
382 entries.append({
383 QSSGMesh::MeshInternal::getUV1AttrName(),
384 vData.uv1Data,
385 QSSGMesh::Mesh::ComponentType::Float32,
386 requirments.uv1Components
387 });
388 }
389
390 if (vData.tangentData.size() > 0) {
391 entries.append({
392 QSSGMesh::MeshInternal::getTexTanAttrName(),
393 vData.tangentData,
394 QSSGMesh::Mesh::ComponentType::Float32,
395 3
396 });
397 }
398
399 if (vData.binormalData.size() > 0) {
400 entries.append({
401 QSSGMesh::MeshInternal::getTexBinormalAttrName(),
402 vData.binormalData,
403 QSSGMesh::Mesh::ComponentType::Float32,
404 3
405 });
406 }
407
408 if (vData.vertexColorData.size() > 0) {
409 entries.append({
410 QSSGMesh::MeshInternal::getColorAttrName(),
411 vData.vertexColorData,
412 QSSGMesh::Mesh::ComponentType::Float32,
413 4
414 });
415 }
416
417 if (boneIndexData.size() > 0) {
418 entries.append({
419 QSSGMesh::MeshInternal::getJointAttrName(),
420 boneIndexData,
421 requirments.useFloatJointIndices ? QSSGMesh::Mesh::ComponentType::Float32 : QSSGMesh::Mesh::ComponentType::Int32,
422 4
423 });
424 entries.append({
425 QSSGMesh::MeshInternal::getWeightAttrName(),
426 boneWeightData,
427 QSSGMesh::Mesh::ComponentType::Float32,
428 4
429 });
430 }
431 for (int i = 0; i < int(requirments.numMorphTargets); ++i) {
432 if (targetVData[i].positionData.size() > 0) {
433 entries.append({
434 QSSGMesh::MeshInternal::getPositionAttrName(),
435 targetVData[i].positionData,
436 QSSGMesh::Mesh::ComponentType::Float32,
437 3,
438 i
439 });
440 }
441 if (targetVData[i].normalData.size() > 0) {
442 entries.append({
443 QSSGMesh::MeshInternal::getNormalAttrName(),
444 targetVData[i].normalData,
445 QSSGMesh::Mesh::ComponentType::Float32,
446 3,
447 i
448 });
449 }
450 if (targetVData[i].tangentData.size() > 0) {
451 entries.append({
452 QSSGMesh::MeshInternal::getTexTanAttrName(),
453 targetVData[i].tangentData,
454 QSSGMesh::Mesh::ComponentType::Float32,
455 3,
456 i
457 });
458 }
459 if (targetVData[i].binormalData.size() > 0) {
460 entries.append({
461 QSSGMesh::MeshInternal::getTexBinormalAttrName(),
462 targetVData[i].binormalData,
463 QSSGMesh::Mesh::ComponentType::Float32,
464 3,
465 i
466 });
467 }
468 if (targetVData[i].uv0Data.size() > 0) {
469 entries.append({
470 QSSGMesh::MeshInternal::getUV0AttrName(),
471 targetVData[i].uv0Data,
472 QSSGMesh::Mesh::ComponentType::Float32,
473 3,
474 i
475 });
476 }
477 if (targetVData[i].uv1Data.size() > 0) {
478 entries.append({
479 QSSGMesh::MeshInternal::getUV1AttrName(),
480 targetVData[i].uv1Data,
481 QSSGMesh::Mesh::ComponentType::Float32,
482 3,
483 i
484 });
485 }
486 if (targetVData[i].vertexColorData.size() > 0) {
487 entries.append({
488 QSSGMesh::MeshInternal::getColorAttrName(),
489 targetVData[i].vertexColorData,
490 QSSGMesh::Mesh::ComponentType::Float32,
491 4,
492 i
493 });
494 }
495 }
496 return entries;
497 }
498};
499
500QVector<QPair<float, QVector<quint32>>> generateMeshLevelsOfDetail(QVector<VertexAttributeDataExt> &vertexAttributes, QVector<quint32> &indexes, float normalMergeAngle = 60.0f, float normalSplitAngle = 25.0f)
501{
502 // If both normalMergeAngle and normalSplitAngle are 0.0, then don't recalculate normals
503 const bool recalculateNormals = !(qFuzzyIsNull(normalMergeAngle) && qFuzzyIsNull(normalSplitAngle));
504 const float normalMergeThreshold = qCos(qDegreesToRadians(normalMergeAngle));
505 const float normalSplitThreshold = qCos(qDegreesToRadians(normalSplitAngle));
506
507 QVector<QVector3D> positions;
508 positions.reserve(vertexAttributes.size());
509 QVector<QVector3D> normals;
510 normals.reserve(vertexAttributes.size());
511 for (const auto &vertex : std::as_const(vertexAttributes)) {
512 positions.append(vertex.aData.position);
513 normals.append(vertex.aData.normal);
514 }
515
516 QVector<QVector3D> splitVertexNormals;
517 QVector<quint32> splitVertexIndices;
518 quint32 splitVertexCount = vertexAttributes.size();
519
520 if (positions.isEmpty() || indexes.isEmpty())
521 return {};
522
523 // An edge can only be collapsed when its vertices are shared by the faces
524 // around it, so a vertex that appears more than once in the vertex buffer
525 // pins every edge that touches it. aiProcess_JoinIdenticalVertices only
526 // merges vertices that match in every attribute, so hard edges and UV chart
527 // borders still leave a position split, and an asset exported with one
528 // vertex per triangle corner keeps every one of them - such a mesh has no
529 // collapsible edge anywhere, so simplification returns the input unchanged
530 // and not a single level is produced. Weld by position to recover the real
531 // topology, simplify that, and translate the result back to the original
532 // vertex numbering afterwards, since that is what the vertex buffer being
533 // written out uses.
534 //
535 // Only the normals are rewritten below, so a position split to carry
536 // different normals can be welded, but one split to carry a different
537 // normal that has to be *kept* cannot: every face around it would be left
538 // reading whichever of them the weld happened to pick. So when the stored
539 // normals are to be preserved they join the weld key, and a hard edge stays
540 // pinned, at the cost of fewer levels for such a mesh.
541 QVector<quint32> weldRemap(positions.size());
542 QVarLengthArray<QSSGMesh::MeshVertexStream, 2> streams;
543 streams.append({ positions.constData(), sizeof(QVector3D), sizeof(QVector3D) });
544 if (!recalculateNormals)
545 streams.append({ normals.constData(), sizeof(QVector3D), sizeof(QVector3D) });
546 const quint32 weldedVertexCount = QSSGMesh::generateVertexRemap(weldRemap.data(), indexes.constData(),
547 indexes.size(), positions.size(),
548 streams.constData(), size_t(streams.size()));
549 QVector<quint32> weldedIndexes(indexes.size());
550 QSSGMesh::remapIndexBuffer(weldedIndexes.data(), indexes.constData(), indexes.size(), weldRemap.constData());
551 QVector<QVector3D> weldedPositions(weldedVertexCount);
552 QSSGMesh::remapVertexBuffer(weldedPositions.data(), positions.constData(), positions.size(),
553 sizeof(QVector3D), weldRemap.constData());
554
555 // Pick one original vertex to stand for each welded one, so the simplified
556 // indexes can be mapped back. Every vertex welded together agrees on the
557 // attributes the weld key covers, so the choice only matters for the rest:
558 // where a position was split across a UV chart border the LOD has to settle
559 // on one of the charts either way.
560 constexpr quint32 unusedVertex = std::numeric_limits<quint32>::max();
561 QVector<quint32> weldedToOriginal(weldedVertexCount, unusedVertex);
562 for (quint32 i = 0, end = quint32(positions.size()); i < end; ++i) {
563 const quint32 welded = weldRemap.at(i);
564 // Vertices the index buffer never references are left unmapped
565 if (welded != unusedVertex && weldedToOriginal.at(welded) == unusedVertex)
566 weldedToOriginal[welded] = i;
567 }
568
569 const float targetError = std::numeric_limits<float>::max(); // error doesn't matter, index count is more important
570 const float *vertexData = reinterpret_cast<const float *>(weldedPositions.constData());
571 const float scaleFactor = QSSGMesh::simplifyScale(vertexData, weldedVertexCount, sizeof(QVector3D));
572 const quint32 indexCount = indexes.size();
573 quint32 indexTarget = 12;
574 quint32 lastIndexCount = 0;
575 QVector<QPair<float, QVector<quint32>>> lods;
576
577 while (indexTarget < indexCount) {
578 float error;
579 QVector<quint32> newIndexes;
580 newIndexes.resize(indexCount); // Must be the same size as the original indexes to pass to simplifyMesh
581 size_t newLength = QSSGMesh::simplifyMesh(newIndexes.data(), weldedIndexes.constData(), weldedIndexes.size(), vertexData, weldedVertexCount, sizeof(QVector3D), indexTarget, targetError, 0, &error);
582
583 // Not good enough, try again
584 if (newLength < lastIndexCount * 1.5f) {
585 indexTarget = indexTarget * 1.5f;
586 continue;
587 }
588
589 // We are done
590 if (newLength == 0 || (newLength >= (indexCount * 0.75f)))
591 break;
592
593 newIndexes.resize(newLength);
594
595 // Back to the original vertex numbering, which everything below - and
596 // the returned levels - is expressed in
597 for (quint32 &index : newIndexes)
598 index = weldedToOriginal.at(index);
599
600 // LOD Normal Correction
601 if (recalculateNormals) {
602 // Cull any new degenerate triangles and get the new face normals
603 QVector<QVector3D> faceNormals;
604 {
605 QVector<quint32> culledIndexes;
606 for (quint32 j = 0; j < newIndexes.size(); j += 3) {
607 const QVector3D &v0 = positions[newIndexes[j]];
608 const QVector3D &v1 = positions[newIndexes[j + 1]];
609 const QVector3D &v2 = positions[newIndexes[j + 2]];
610
611 QVector3D faceNormal = QVector3D::crossProduct(v1 - v0, v2 - v0);
612 // This normalizes the vector in place and returns the magnitude
613 const float faceArea = QSSGUtils::vec3::normalize(faceNormal);
614 // It is possible that the simplifyMesh process gave us a degenerate triangle
615 // (all three at the same point, or on the same line) or such a small triangle
616 // that a float value doesn't have enough resolution. In that case cull the
617 // "face" since it would not get rendered in a meaningful way anyway
618 if (faceArea != 0.0f) {
619 faceNormals.append(faceNormal);
620 faceNormals.append(faceNormal);
621 faceNormals.append(faceNormal);
622 culledIndexes.append({newIndexes[j], newIndexes[j + 1], newIndexes[j + 2]});
623 }
624 }
625
626 if (newIndexes.size() != culledIndexes.size())
627 newIndexes = culledIndexes;
628 }
629
630 // Group all shared vertices together by position. We need to know adjacent faces
631 // to do vertex normal remapping in the next step.
632 QHash<QVector3D, QVector<quint32>> positionHash;
633 for (quint32 i = 0; i < newIndexes.size(); ++i) {
634 const quint32 index = newIndexes[i];
635 const QVector3D position = vertexAttributes[index].aData.position;
636 positionHash[position].append(i);
637 }
638
639 // Go through each vertex and calculate the normals by checking each
640 // adjacent face that share the same vertex position, and create a smoothed
641 // normal if the angle between thew face normals is less than the the
642 // normalMergeAngle passed to this function (>= since this is cos(radian(angle)) )
643 QVector<QPair<quint32, quint32>> remapIndexes;
644 for (quint32 positionIndex = 0; positionIndex < newIndexes.size(); ++positionIndex) {
645 const quint32 index = newIndexes[positionIndex];
646 const QVector3D &position = vertexAttributes[index].aData.position;
647 const QVector3D &faceNormal = faceNormals[positionIndex];
648 QVector3D newNormal;
649 // Find all vertices that share the same position
650 const auto &sharedPositions = positionHash.value(position);
651 for (const auto positionIndex2 : sharedPositions) {
652 if (positionIndex == positionIndex2) {
653 // Don't test against the current face under test
654 newNormal += faceNormal;
655 } else {
656 const QVector3D &faceNormal2 = faceNormals[positionIndex2];
657 if (QVector3D::dotProduct(faceNormal2, faceNormal) >= normalMergeThreshold)
658 newNormal += faceNormal2;
659 }
660 }
661
662 // By normalizing here we get an averaged value of all smoothed normals
663 QSSGUtils::vec3::normalize(newNormal);
664
665 // Now that we know what the smoothed normal would be, check how differnt
666 // that normal is from the normal that is already stored in the current
667 // index. If the angle delta is greater than normalSplitAngle then we need
668 // to create a new vertex entry (making a copy of the current one) and set
669 // the new normal value, and reassign the current index to point to that new
670 // vertex. Generally the LOD simplification process is such that the existing
671 // normal will already be ideal until we start getting to the very low lod levels
672 // which changes the topology in such a way that the original normal doesn't
673 // make sense anymore, thus the need to provide a more reasonable value.
674 const QVector3D &originalNormal = vertexAttributes[index].aData.normal;
675 const float theta = QVector3D::dotProduct(originalNormal, newNormal);
676 if (theta < normalSplitThreshold) {
677 splitVertexIndices.append(index);
678 splitVertexNormals.append(newNormal.normalized());
679 remapIndexes.append({positionIndex, splitVertexCount++});
680 }
681 }
682
683 // Do index remap now that all new normals have been calculated
684 for (const auto &pair : std::as_const(remapIndexes))
685 newIndexes[pair.first] = pair.second;
686 }
687
688 lods.append({error * scaleFactor, newIndexes});
689 indexTarget = qMax(newLength, indexTarget) * 2;
690 lastIndexCount = newLength;
691
692 if (error == 0.0f)
693 break;
694 }
695 // Here we need to add the new index and vertex values from
696 // splitVertexIndices and splitVertexNormals
697 for (quint32 i = 0; i < splitVertexIndices.size(); ++i) {
698 quint32 index = splitVertexIndices[i];
699 QVector3D newNormal = splitVertexNormals[i];
700 auto newVertex = vertexAttributes[index];
701 newVertex.aData.normal = newNormal;
702 vertexAttributes.append(newVertex);
703 }
704
705 return lods;
706}
707
708}
709
711 const MeshList &meshes,
712 bool useFloatJointIndices,
713 bool generateLevelsOfDetail,
714 float normalMergeAngle,
715 float normalSplitAngle,
716 QString &errorString)
717{
718 Q_UNUSED(errorString);
719
720 // All Mesh subsets are stored in the same Vertex Buffer so we need to make
721 // sure that all attributes from each subset have common data by potentially
722 // adding placeholder data or doing conversions as necessary.
723 // So we need to walk through each subset first and see what the requirments are
724 VertexDataRequirments requirments;
725 requirments.useFloatJointIndices = useFloatJointIndices;
726 for (const auto *mesh : meshes)
727 requirments.collectRequirmentsForMesh(mesh);
728
729 // This is the actual data we will pass to the QSSGMesh that will get filled by
730 // each of the subset meshes
731 QByteArray indexBufferData;
732 VertexBufferDataExt vertexBufferData;
733 QVector<SubsetEntryData> subsetData;
734
735 // Since the vertex data of subsets are stored one after the other, the values in
736 // the index buffer need to be augmented to reflect this offset. baseIndex is used
737 // to track the new 0 value of a subset by keeping track of the current vertex
738 // count as each new subset is added
739 quint32 baseIndex = 0;
740
741 // Always use 32-bit indices. Metal has a requirement of 4 byte alignment
742 // for index buffer offsets, and we cannot risk hitting that.
743 const QSSGMesh::Mesh::ComponentType indexType = QSSGMesh::Mesh::ComponentType::UnsignedInt32;
744
745 for (const auto *mesh : meshes) {
746 // Get the index values for just this mesh
747 // The index values should be relative to this meshes
748 // vertices and will later need to be corrected using
749 // baseIndex to be relative to our combined vertex data
750 QVector<quint32> indexes;
751 indexes.reserve(mesh->mNumFaces * 3);
752 for (unsigned int faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) {
753 const auto face = mesh->mFaces[faceIndex];
754 // Faces should always have 3 indices
755 Q_ASSERT(face.mNumIndices == 3);
756 // Index data for now is relative to the local vertex locations
757 // This must be corrected for later to be global
758 indexes.append(quint32(face.mIndices[0]));
759 indexes.append(quint32(face.mIndices[1]));
760 indexes.append(quint32(face.mIndices[2]));
761 }
762
763 // Get the Vertex Attribute Data for this mesh
764 auto vertexAttributes = getVertexAttributeData(mesh, requirments);
765
766 // Starting point for index buffer offsets
767 quint32 baseIndexOffset = indexBufferData.size() / QSSGMesh::MeshInternal::byteSizeForComponentType(indexType);
768 QVector<quint32> lodIndexes;
769 QVector<QSSGMesh::Mesh::Lod> meshLods;
770
771 // Generate Automatic Mesh Levels of Detail
772 if (generateLevelsOfDetail) {
773 // Returns a list of lod pairs <distance, lodIndexList> sorted from smallest
774 // to largest as this is how they are stored in the index buffer. We still need to
775 // populate meshLods with push_front though because subset lod data is sorted from
776 // highest detail to lowest
777 auto lods = generateMeshLevelsOfDetail(vertexAttributes, indexes, normalMergeAngle, normalSplitAngle);
778 for (const auto &lodPair : std::as_const(lods)) {
779 QSSGMesh::Mesh::Lod lod;
780 lod.offset = baseIndexOffset;
781 lod.count = lodPair.second.size();
782 lod.distance = lodPair.first;
783 meshLods.push_front(lod);
784 baseIndexOffset += lod.count;
785 // Optimize the vertex cache for this lod level
786 auto currentLodIndexes = lodPair.second;
787 QSSGMesh::optimizeVertexCache(currentLodIndexes.data(), currentLodIndexes.data(), currentLodIndexes.size(), vertexAttributes.size());
788 lodIndexes += currentLodIndexes;
789 }
790 }
791
792 // Write the results to the Global Index/Vertex/SubsetData buffers
793 // Optimize the vertex chache for the original index values
794 QSSGMesh::optimizeVertexCache(indexes.data(), indexes.data(), indexes.size(), vertexAttributes.size());
795
796 // Write Index Buffer Data
797 QVector<quint32> combinedIndexValues = lodIndexes + indexes;
798 // Set the absolute index relative to the larger vertex buffer
799 for (auto &index : combinedIndexValues)
800 index += baseIndex;
801 indexBufferData += QByteArray(reinterpret_cast<const char *>(combinedIndexValues.constData()),
802 combinedIndexValues.size() * QSSGMesh::MeshInternal::byteSizeForComponentType(indexType));
803
804 // Index Data is setup such that LOD indexes will come first
805 // from lowest quality to original
806 // | LOD3 | LOD2 | LOD1 | Original |
807 // If there were no LOD levels then indexOffset just points to that here
808 // baseIndexOffset has already been calculated to be correct at this point
809 SubsetEntryData subsetEntry;
810 subsetEntry.indexOffset = baseIndexOffset; // baseIndexOffset will be after lod indexes if available
811 subsetEntry.indexLength = indexes.size(); // Yes, only original index values, because this is for the non-lod indexes
812 subsetEntry.name = QString::fromUtf8(scene.mMaterials[mesh->mMaterialIndex]->GetName().C_Str());
813 subsetEntry.lightmapWidth = 0;
814 subsetEntry.lightmapHeight = 0;
815 subsetEntry.lods = meshLods;
816 subsetData.append(subsetEntry);
817
818 // Fill the rest of the vertex data
819 baseIndex += vertexAttributes.size(); // Final count of vertices added
820 // Increase target buffers before adding data
821 vertexBufferData.targetVData.resize(requirments.numMorphTargets);
822 for (const auto &vertex : std::as_const(vertexAttributes))
823 vertexBufferData.addVertexAttributeData(vertex, requirments);
824
825 }
826
827 // Now that we have all the data for the mesh, generate the entries list
828 QVector<QSSGMesh::AssetVertexEntry> entries = vertexBufferData.createEntries(requirments);
829
830 QVector<QSSGMesh::AssetMeshSubset> subsets;
831 for (const SubsetEntryData &subset : subsetData) {
832 subsets.append({
833 subset.name,
834 quint32(subset.indexLength),
835 quint32(subset.indexOffset),
836 0, // the builder will calculate the bounds from the position data
837 subset.lightmapWidth,
838 subset.lightmapHeight,
839 subset.lods
840 });
841 }
842
843 auto numTargetComponents = [](VertexDataRequirments req) {
844 int num = 0;
846 ++num;
848 ++num;
850 num += 2; // tangent and binormal
852 ++num;
853 if (req.needsTargetUV0Data)
854 ++num;
855 if (req.needsTargetUV1Data)
856 ++num;
857 return num;
858 };
859
860 QSSGMesh::Mesh mesh = QSSGMesh::Mesh::fromAssetData(entries,
861 indexBufferData,
862 indexType,
863 subsets,
864 requirments.numMorphTargets,
865 numTargetComponents(requirments));
866 return mesh;
867}
868
869QT_END_NAMESPACE
QSSGMesh::Mesh generateMeshData(const aiScene &scene, const MeshList &meshes, bool useFloatJointIndices, bool generateLevelsOfDetail, float normalMergeAngle, float normalSplitAngle, QString &errorString)
QVector< QPair< float, QVector< quint32 > > > generateMeshLevelsOfDetail(QVector< VertexAttributeDataExt > &vertexAttributes, QVector< quint32 > &indexes, float normalMergeAngle=60.0f, float normalSplitAngle=25.0f)
QVector< VertexAttributeDataExt > getVertexAttributeData(const aiMesh *mesh, const VertexDataRequirments &requirments)
QVector< QSSGMesh::Mesh::Lod > lods
QVector< VertexAttributeData > targetAData
void addVertexAttributeData(const VertexAttributeDataExt &vertex, const VertexDataRequirments &requirments)
QVector< QSSGMesh::AssetVertexEntry > createEntries(const VertexDataRequirments &requirments)
QVector< VertexBufferData > targetVData
void collectRequirmentsForMesh(const aiMesh *mesh)