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