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