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