| 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 | |
| 30 | QT_BEGIN_NAMESPACE |
| 31 | |
| 32 | namespace |
| 33 | { |
| 34 | |
| 35 | struct SubsetEntryData { |
| 36 | QString name; |
| 37 | int indexLength; |
| 38 | int indexOffset; |
| 39 | quint32 lightmapWidth; |
| 40 | quint32 lightmapHeight; |
| 41 | QVector<QSSGMesh::Mesh::Lod> lods; |
| 42 | }; |
| 43 | |
| 44 | struct IntVector4D { |
| 45 | qint32 x = 0; |
| 46 | qint32 y = 0; |
| 47 | qint32 z = 0; |
| 48 | qint32 w = 0; |
| 49 | }; |
| 50 | |
| 51 | struct VertexAttributeData { |
| 52 | QVector3D position; |
| 53 | QVector3D normal; |
| 54 | QVector3D uv0; |
| 55 | QVector3D uv1; |
| 56 | QVector3D tangent; |
| 57 | QVector3D binormal; |
| 58 | QVector4D color; |
| 59 | }; |
| 60 | |
| 61 | struct VertexAttributeDataExt { |
| 62 | VertexAttributeData aData; |
| 63 | IntVector4D boneIndexes; |
| 64 | QVector4D boneWeights; |
| 65 | QVector<VertexAttributeData> targetAData; |
| 66 | }; |
| 67 | |
| 68 | struct VertexDataRequirments { |
| 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(a: mesh->mNumUVComponents[0], b: uv0Components); |
| 93 | uv1Components = qMax(a: mesh->mNumUVComponents[1], b: uv1Components); |
| 94 | needsUV0Data |= mesh->HasTextureCoords(index: 0); |
| 95 | needsUV1Data |= mesh->HasTextureCoords(index: 1); |
| 96 | needsPositionData |= mesh->HasPositions(); |
| 97 | needsNormalData |= mesh->HasNormals(); |
| 98 | needsTangentData |= mesh->HasTangentsAndBitangents(); |
| 99 | needsVertexColorData |=mesh->HasVertexColors(index: 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(pIndex: 0); |
| 109 | needsTargetUV0Data |= animMesh->HasTextureCoords(pIndex: 0); |
| 110 | needsTargetUV1Data |= animMesh->HasTextureCoords(pIndex: 1); |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | }; |
| 115 | |
| 116 | QVector<VertexAttributeDataExt> getVertexAttributeData(const aiMesh *mesh, const VertexDataRequirments &requirments) |
| 117 | { |
| 118 | QVector<VertexAttributeDataExt> vertexAttributes; |
| 119 | |
| 120 | vertexAttributes.resize(size: 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(index: 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(index: 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(index: 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(msg: "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(size: 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(pIndex: 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(pIndex: 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(pIndex: 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 | |
| 265 | struct VertexBufferData { |
| 266 | QByteArray positionData; |
| 267 | QByteArray normalData; |
| 268 | QByteArray uv0Data; |
| 269 | QByteArray uv1Data; |
| 270 | QByteArray tangentData; |
| 271 | QByteArray binormalData; |
| 272 | QByteArray vertexColorData; |
| 273 | }; |
| 274 | |
| 275 | struct VertexBufferDataExt { |
| 276 | VertexBufferData vData; |
| 277 | QByteArray boneIndexData; |
| 278 | QByteArray boneWeightData; |
| 279 | QVector<VertexBufferData> targetVData; |
| 280 | |
| 281 | void addVertexAttributeData(const VertexAttributeDataExt &vertex, const VertexDataRequirments &requirments) |
| 282 | { |
| 283 | // Position |
| 284 | if (requirments.needsPositionData) |
| 285 | vData.positionData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.aData.position), size: sizeof(QVector3D)); |
| 286 | // Normal |
| 287 | if (requirments.needsNormalData) |
| 288 | vData.normalData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.aData.normal), size: 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(data: reinterpret_cast<const char *>(&uv), size: sizeof(QVector2D)); |
| 295 | } else { |
| 296 | vData.uv0Data += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.aData.uv0), size: 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(data: reinterpret_cast<const char *>(&uv), size: sizeof(QVector2D)); |
| 305 | } else { |
| 306 | vData.uv1Data += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.aData.uv1), size: sizeof(QVector3D)); |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | // Tangent |
| 311 | // Binormal |
| 312 | if (requirments.needsTangentData) { |
| 313 | vData.tangentData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.aData.tangent), size: sizeof(QVector3D)); |
| 314 | vData.binormalData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.aData.binormal), size: sizeof(QVector3D)); |
| 315 | } |
| 316 | |
| 317 | // Color |
| 318 | if (requirments.needsVertexColorData) |
| 319 | vData.vertexColorData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.aData.color), size: 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(data: reinterpret_cast<const char *>(&fBoneIndex), size: sizeof(QVector4D)); |
| 327 | } else { |
| 328 | boneIndexData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.boneIndexes), size: sizeof(IntVector4D)); |
| 329 | } |
| 330 | boneWeightData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.boneWeights), size: 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(data: reinterpret_cast<const char *>(&vertex.targetAData[i].position), size: sizeof(QVector3D)); |
| 337 | targetVData[i].positionData.append(n: sizeof(float), ch: '\0'); |
| 338 | } |
| 339 | if (requirments.needsTargetNormalData) { |
| 340 | targetVData[i].normalData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.targetAData[i].normal), size: sizeof(QVector3D)); |
| 341 | targetVData[i].normalData.append(n: sizeof(float), ch: '\0'); |
| 342 | } |
| 343 | if (requirments.needsTargetTangentData) { |
| 344 | targetVData[i].tangentData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.targetAData[i].tangent), size: sizeof(QVector3D)); |
| 345 | targetVData[i].tangentData.append(n: sizeof(float), ch: '\0'); |
| 346 | targetVData[i].binormalData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.targetAData[i].binormal), size: sizeof(QVector3D)); |
| 347 | targetVData[i].binormalData.append(n: sizeof(float), ch: '\0'); |
| 348 | } |
| 349 | if (requirments.needsTargetUV0Data) { |
| 350 | targetVData[i].uv0Data += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.targetAData[i].uv0), size: sizeof(QVector3D)); |
| 351 | targetVData[i].uv0Data.append(n: sizeof(float), ch: '\0'); |
| 352 | } |
| 353 | if (requirments.needsTargetUV1Data) { |
| 354 | targetVData[i].uv1Data += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.targetAData[i].uv1), size: sizeof(QVector3D)); |
| 355 | targetVData[i].uv1Data.append(n: sizeof(float), ch: '\0'); |
| 356 | } |
| 357 | if (requirments.needsTargetVertexColorData) { |
| 358 | targetVData[i].vertexColorData += QByteArray::fromRawData(data: reinterpret_cast<const char *>(&vertex.targetAData[i].color), size: 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(t: { |
| 367 | .name: QSSGMesh::MeshInternal::getPositionAttrName(), |
| 368 | .data: vData.positionData, |
| 369 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 370 | .componentCount: 3 |
| 371 | }); |
| 372 | } |
| 373 | if (vData.normalData.size() > 0) { |
| 374 | entries.append(t: { |
| 375 | .name: QSSGMesh::MeshInternal::getNormalAttrName(), |
| 376 | .data: vData.normalData, |
| 377 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 378 | .componentCount: 3 |
| 379 | }); |
| 380 | } |
| 381 | if (vData.uv0Data.size() > 0) { |
| 382 | entries.append(t: { |
| 383 | .name: QSSGMesh::MeshInternal::getUV0AttrName(), |
| 384 | .data: vData.uv0Data, |
| 385 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 386 | .componentCount: requirments.uv0Components |
| 387 | }); |
| 388 | } |
| 389 | if (vData.uv1Data.size() > 0) { |
| 390 | entries.append(t: { |
| 391 | .name: QSSGMesh::MeshInternal::getUV1AttrName(), |
| 392 | .data: vData.uv1Data, |
| 393 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 394 | .componentCount: requirments.uv1Components |
| 395 | }); |
| 396 | } |
| 397 | |
| 398 | if (vData.tangentData.size() > 0) { |
| 399 | entries.append(t: { |
| 400 | .name: QSSGMesh::MeshInternal::getTexTanAttrName(), |
| 401 | .data: vData.tangentData, |
| 402 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 403 | .componentCount: 3 |
| 404 | }); |
| 405 | } |
| 406 | |
| 407 | if (vData.binormalData.size() > 0) { |
| 408 | entries.append(t: { |
| 409 | .name: QSSGMesh::MeshInternal::getTexBinormalAttrName(), |
| 410 | .data: vData.binormalData, |
| 411 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 412 | .componentCount: 3 |
| 413 | }); |
| 414 | } |
| 415 | |
| 416 | if (vData.vertexColorData.size() > 0) { |
| 417 | entries.append(t: { |
| 418 | .name: QSSGMesh::MeshInternal::getColorAttrName(), |
| 419 | .data: vData.vertexColorData, |
| 420 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 421 | .componentCount: 4 |
| 422 | }); |
| 423 | } |
| 424 | |
| 425 | if (boneIndexData.size() > 0) { |
| 426 | entries.append(t: { |
| 427 | .name: QSSGMesh::MeshInternal::getJointAttrName(), |
| 428 | .data: boneIndexData, |
| 429 | .componentType: requirments.useFloatJointIndices ? QSSGMesh::Mesh::ComponentType::Float32 : QSSGMesh::Mesh::ComponentType::Int32, |
| 430 | .componentCount: 4 |
| 431 | }); |
| 432 | entries.append(t: { |
| 433 | .name: QSSGMesh::MeshInternal::getWeightAttrName(), |
| 434 | .data: boneWeightData, |
| 435 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 436 | .componentCount: 4 |
| 437 | }); |
| 438 | } |
| 439 | for (int i = 0; i < int(requirments.numMorphTargets); ++i) { |
| 440 | if (targetVData[i].positionData.size() > 0) { |
| 441 | entries.append(t: { |
| 442 | .name: QSSGMesh::MeshInternal::getPositionAttrName(), |
| 443 | .data: targetVData[i].positionData, |
| 444 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 445 | .componentCount: 3, |
| 446 | .morphTargetId: i |
| 447 | }); |
| 448 | } |
| 449 | if (targetVData[i].normalData.size() > 0) { |
| 450 | entries.append(t: { |
| 451 | .name: QSSGMesh::MeshInternal::getNormalAttrName(), |
| 452 | .data: targetVData[i].normalData, |
| 453 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 454 | .componentCount: 3, |
| 455 | .morphTargetId: i |
| 456 | }); |
| 457 | } |
| 458 | if (targetVData[i].tangentData.size() > 0) { |
| 459 | entries.append(t: { |
| 460 | .name: QSSGMesh::MeshInternal::getTexTanAttrName(), |
| 461 | .data: targetVData[i].tangentData, |
| 462 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 463 | .componentCount: 3, |
| 464 | .morphTargetId: i |
| 465 | }); |
| 466 | } |
| 467 | if (targetVData[i].binormalData.size() > 0) { |
| 468 | entries.append(t: { |
| 469 | .name: QSSGMesh::MeshInternal::getTexBinormalAttrName(), |
| 470 | .data: targetVData[i].binormalData, |
| 471 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 472 | .componentCount: 3, |
| 473 | .morphTargetId: i |
| 474 | }); |
| 475 | } |
| 476 | if (targetVData[i].uv0Data.size() > 0) { |
| 477 | entries.append(t: { |
| 478 | .name: QSSGMesh::MeshInternal::getUV0AttrName(), |
| 479 | .data: targetVData[i].uv0Data, |
| 480 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 481 | .componentCount: 3, |
| 482 | .morphTargetId: i |
| 483 | }); |
| 484 | } |
| 485 | if (targetVData[i].uv1Data.size() > 0) { |
| 486 | entries.append(t: { |
| 487 | .name: QSSGMesh::MeshInternal::getUV1AttrName(), |
| 488 | .data: targetVData[i].uv1Data, |
| 489 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 490 | .componentCount: 3, |
| 491 | .morphTargetId: i |
| 492 | }); |
| 493 | } |
| 494 | if (targetVData[i].vertexColorData.size() > 0) { |
| 495 | entries.append(t: { |
| 496 | .name: QSSGMesh::MeshInternal::getColorAttrName(), |
| 497 | .data: targetVData[i].vertexColorData, |
| 498 | .componentType: QSSGMesh::Mesh::ComponentType::Float32, |
| 499 | .componentCount: 4, |
| 500 | .morphTargetId: i |
| 501 | }); |
| 502 | } |
| 503 | } |
| 504 | return entries; |
| 505 | } |
| 506 | }; |
| 507 | |
| 508 | QVector<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(f: normalMergeAngle) && qFuzzyIsNull(f: normalSplitAngle)); |
| 512 | const float normalMergeThreshold = qCos(v: qDegreesToRadians(degrees: normalMergeAngle)); |
| 513 | const float normalSplitThreshold = qCos(v: qDegreesToRadians(degrees: normalSplitAngle)); |
| 514 | |
| 515 | QVector<QVector3D> positions; |
| 516 | positions.reserve(asize: vertexAttributes.size()); |
| 517 | QVector<QVector3D> normals; |
| 518 | normals.reserve(asize: vertexAttributes.size()); |
| 519 | for (const auto &vertex : vertexAttributes) { |
| 520 | positions.append(t: vertex.aData.position); |
| 521 | normals.append(t: 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(vertexPositions: vertexData, vertexCount: positions.size(), vertexPositionsStride: 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(size: indexCount); // Must be the same size as the original indexes to pass to simplifyMesh |
| 540 | size_t newLength = QSSGMesh::simplifyMesh(destination: newIndexes.data(), indices: indexes.constData(), indexCount: indexes.size(), vertexPositions: vertexData, vertexCount: positions.size(), vertexPositionsStride: sizeof(QVector3D), targetIndexCount: indexTarget, targetError, options: 0, resultError: &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(size: 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: v1 - v0, v2: v2 - v0); |
| 566 | // This normalizes the vector in place and returns the magnitude |
| 567 | const float faceArea = QSSGUtils::vec3::normalize(v&: 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(t: faceNormal); |
| 574 | faceNormals.append(t: faceNormal); |
| 575 | faceNormals.append(t: faceNormal); |
| 576 | culledIndexes.append(other: {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(t: 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(key: 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(v1: faceNormal2, v2: faceNormal) >= normalMergeThreshold) |
| 612 | newNormal += faceNormal2; |
| 613 | } |
| 614 | } |
| 615 | |
| 616 | // By normalizing here we get an averaged value of all smoothed normals |
| 617 | QSSGUtils::vec3::normalize(v&: newNormal); |
| 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(v1: originalNormal, v2: newNormal); |
| 630 | if (theta < normalSplitThreshold) { |
| 631 | splitVertexIndices.append(t: index); |
| 632 | splitVertexNormals.append(t: newNormal.normalized()); |
| 633 | remapIndexes.append(t: {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(t: {error * scaleFactor, newIndexes}); |
| 643 | indexTarget = qMax(a: newLength, b: 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(t: newVertex); |
| 657 | } |
| 658 | |
| 659 | return lods; |
| 660 | } |
| 661 | |
| 662 | } |
| 663 | |
| 664 | QSSGMesh::Mesh AssimpUtils::generateMeshData(const aiScene &scene, |
| 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(asize: 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(t: quint32(face.mIndices[0])); |
| 713 | indexes.append(t: quint32(face.mIndices[1])); |
| 714 | indexes.append(t: 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(componentType: 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) { |
| 733 | QSSGMesh::Mesh::Lod lod; |
| 734 | lod.offset = baseIndexOffset; |
| 735 | lod.count = lodPair.second.size(); |
| 736 | lod.distance = lodPair.first; |
| 737 | meshLods.push_front(t: lod); |
| 738 | baseIndexOffset += lod.count; |
| 739 | // Optimize the vertex cache for this lod level |
| 740 | auto currentLodIndexes = lodPair.second; |
| 741 | QSSGMesh::optimizeVertexCache(destination: currentLodIndexes.data(), indices: currentLodIndexes.data(), indexCount: currentLodIndexes.size(), vertexCount: 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(destination: indexes.data(), indices: indexes.data(), indexCount: indexes.size(), vertexCount: 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(componentType: 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(utf8: scene.mMaterials[mesh->mMaterialIndex]->GetName().C_Str()); |
| 767 | subsetEntry.lightmapWidth = 0; |
| 768 | subsetEntry.lightmapHeight = 0; |
| 769 | subsetEntry.lods = meshLods; |
| 770 | subsetData.append(t: 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(size: 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(t: { |
| 787 | .name: subset.name, |
| 788 | .count: quint32(subset.indexLength), |
| 789 | .offset: quint32(subset.indexOffset), |
| 790 | .boundsPositionEntryIndex: 0, // the builder will calculate the bounds from the position data |
| 791 | .lightmapWidth: subset.lightmapWidth, |
| 792 | .lightmapHeight: subset.lightmapHeight, |
| 793 | .lods: 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(vbufEntries: entries, indexBufferData, indexComponentType: indexType, |
| 815 | subsets, numTargets: requirments.numMorphTargets, |
| 816 | numTargetComps: numTargetComponents(requirments)); |
| 817 | } |
| 818 | |
| 819 | QT_END_NAMESPACE |
| 820 | |