22#include <assimp/Importer.hpp>
23#include <assimp/scene.h>
24#include <assimp/postprocess.h>
25#include <assimp/material.h>
26#include <assimp/config.h>
34#include <unordered_map>
46#include "spdlog/spdlog.h"
64 static_assert(
sizeof(
PackedVertex) == 56,
"PackedVertex must be 56 bytes (14 floats)");
68 void generateTangents(std::vector<PackedVertex>& vertices,
const std::vector<uint32_t>& indices)
70 const size_t vertexCount = vertices.size();
71 if (vertexCount == 0)
return;
73 std::vector<Vector3> tan1(vertexCount,
Vector3(0.0f, 0.0f, 0.0f));
74 std::vector<Vector3> tan2(vertexCount,
Vector3(0.0f, 0.0f, 0.0f));
76 auto accumulateTriangle = [&](uint32_t i0, uint32_t i1, uint32_t i2) {
77 if (i0 >= vertexCount || i1 >= vertexCount || i2 >= vertexCount)
return;
79 const auto& v0 = vertices[i0];
80 const auto& v1 = vertices[i1];
81 const auto& v2 = vertices[i2];
83 const float du1 = v1.u - v0.u;
84 const float dv1 = v1.v - v0.v;
85 const float du2 = v2.u - v0.u;
86 const float dv2 = v2.v - v0.v;
88 const float det = du1 * dv2 - dv1 * du2;
89 if (std::abs(det) <= 1e-8f)
return;
91 const float invDet = 1.0f / det;
92 const Vector3 e1(v1.px - v0.px, v1.py - v0.py, v1.pz - v0.pz);
93 const Vector3 e2(v2.px - v0.px, v2.py - v0.py, v2.pz - v0.pz);
95 const Vector3 sdir = (e1 * dv2 - e2 * dv1) * invDet;
96 const Vector3 tdir = (e2 * du1 - e1 * du2) * invDet;
98 tan1[i0] += sdir; tan1[i1] += sdir; tan1[i2] += sdir;
99 tan2[i0] += tdir; tan2[i1] += tdir; tan2[i2] += tdir;
102 if (!indices.empty()) {
103 for (
size_t i = 0; i + 2 < indices.size(); i += 3)
104 accumulateTriangle(indices[i], indices[i + 1], indices[i + 2]);
106 for (uint32_t i = 0; i + 2 <
static_cast<uint32_t
>(vertexCount); i += 3)
107 accumulateTriangle(i, i + 1, i + 2);
110 for (
size_t i = 0; i < vertexCount; ++i) {
111 const Vector3 n(vertices[i].nx, vertices[i].ny, vertices[i].nz);
113 if (t.lengthSquared() <= 1e-8f) {
114 t = std::abs(n.getY()) < 0.999f
115 ? n.cross(
Vector3(0.0f, 1.0f, 0.0f))
116 : n.cross(
Vector3(1.0f, 0.0f, 0.0f));
120 const float handedness = (n.cross(t).dot(tan2[i]) < 0.0f) ? -1.0f : 1.0f;
122 vertices[i].tx = t.getX();
123 vertices[i].ty = t.getY();
124 vertices[i].tz = t.getZ();
125 vertices[i].tw = handedness;
131 void tangentFromNormal(
float nx,
float ny,
float nz,
132 float& tx,
float& ty,
float& tz,
float& tw)
145 std::shared_ptr<Texture> loadAssimpTexture(
146 const aiMaterial* aiMat,
148 const aiScene* scene,
149 const std::filesystem::path& basedir,
151 std::unordered_map<std::string, std::shared_ptr<Texture>>& cache)
154 if (aiGetMaterialTexture(aiMat, type, 0, &texPath) != AI_SUCCESS)
157 std::string pathStr = texPath.C_Str();
162 auto cacheIt = cache.find(pathStr);
163 if (cacheIt != cache.end())
164 return cacheIt->second;
166 int w = 0, h = 0, channels = 0;
167 stbi_uc* pixels =
nullptr;
169 if (pathStr[0] ==
'*') {
171 int texIndex = std::atoi(pathStr.c_str() + 1);
172 if (texIndex < 0 || texIndex >=
static_cast<int>(scene->mNumTextures)) {
173 spdlog::warn(
"Assimp: embedded texture index out of range: {}", pathStr);
174 cache[pathStr] =
nullptr;
177 const aiTexture* aiTex = scene->mTextures[texIndex];
178 if (aiTex->mHeight == 0) {
180 stbi_set_flip_vertically_on_load(
false);
181 pixels = stbi_load_from_memory(
182 reinterpret_cast<const unsigned char*
>(aiTex->pcData),
183 static_cast<int>(aiTex->mWidth),
184 &w, &h, &channels, 4);
187 w =
static_cast<int>(aiTex->mWidth);
188 h =
static_cast<int>(aiTex->mHeight);
189 const size_t pixelCount =
static_cast<size_t>(w) * h;
190 pixels =
static_cast<stbi_uc*
>(malloc(pixelCount * 4));
191 for (
size_t i = 0; i < pixelCount; ++i) {
192 const auto& texel = aiTex->pcData[i];
193 pixels[i * 4 + 0] = texel.r;
194 pixels[i * 4 + 1] = texel.g;
195 pixels[i * 4 + 2] = texel.b;
196 pixels[i * 4 + 3] = texel.a;
202 std::filesystem::path fullPath;
203 std::filesystem::path texFilePath(pathStr);
204 if (texFilePath.is_absolute()) {
205 fullPath = texFilePath;
207 fullPath = basedir / texFilePath;
209 if (!std::filesystem::exists(fullPath)) {
210 spdlog::warn(
"Assimp texture not found: {}", fullPath.string());
211 cache[pathStr] =
nullptr;
215 stbi_set_flip_vertically_on_load(
false);
216 pixels = stbi_load(fullPath.string().c_str(), &w, &h, &channels, 4);
219 if (!pixels || w <= 0 || h <= 0) {
220 spdlog::warn(
"Assimp texture decode failed: {}", pathStr);
221 if (pixels) stbi_image_free(pixels);
222 cache[pathStr] =
nullptr;
227 opts.
width =
static_cast<uint32_t
>(w);
228 opts.height =
static_cast<uint32_t
>(h);
230 opts.mipmaps =
false;
236 auto texture = std::make_shared<Texture>(device, opts);
237 const size_t dataSize =
static_cast<size_t>(w) * h * 4;
238 texture->setLevelData(0, pixels, dataSize);
239 stbi_image_free(pixels);
242 spdlog::info(
"Assimp texture loaded: {} ({}x{})", pathStr, w, h);
243 cache[pathStr] = texture;
249 void convertPbrMaterial(
250 const aiMaterial* aiMat,
251 const aiScene* scene,
252 const std::filesystem::path& basedir,
255 std::unordered_map<std::string, std::shared_ptr<Texture>>& texCache,
256 std::vector<std::shared_ptr<Texture>>& ownedTextures)
258 auto loadAndOwn = [&](aiTextureType type) ->
Texture* {
259 auto tex = loadAssimpTexture(aiMat, type, scene, basedir, device, texCache);
260 if (tex) ownedTextures.push_back(tex);
265 aiColor4D baseColor(1.0f, 1.0f, 1.0f, 1.0f);
266 aiGetMaterialColor(aiMat, AI_MATKEY_BASE_COLOR, &baseColor);
267 material.setDiffuse(
Color(baseColor.r, baseColor.g, baseColor.b, baseColor.a));
268 material.setBaseColorFactor(
Color(baseColor.r, baseColor.g, baseColor.b, baseColor.a));
269 material.setOpacity(baseColor.a);
272 float metallic = 0.0f, roughness = 1.0f;
273 aiGetMaterialFloat(aiMat, AI_MATKEY_METALLIC_FACTOR, &metallic);
274 aiGetMaterialFloat(aiMat, AI_MATKEY_ROUGHNESS_FACTOR, &roughness);
275 material.setMetalness(metallic);
276 material.setMetallicFactor(metallic);
277 material.setGloss(1.0f - roughness);
278 material.setRoughnessFactor(roughness);
279 material.setUseMetalness(
true);
282 aiColor4D emissive(0.0f, 0.0f, 0.0f, 1.0f);
283 aiGetMaterialColor(aiMat, AI_MATKEY_COLOR_EMISSIVE, &emissive);
284 if (emissive.r > 0.0f || emissive.g > 0.0f || emissive.b > 0.0f) {
285 material.setEmissive(
Color(emissive.r, emissive.g, emissive.b, 1.0f));
286 material.setEmissiveFactor(
Color(emissive.r, emissive.g, emissive.b, 1.0f));
290 float opacity = 1.0f;
291 aiGetMaterialFloat(aiMat, AI_MATKEY_OPACITY, &opacity);
292 material.setOpacity(opacity);
293 if (opacity < 0.99f) {
295 material.setTransparent(
true);
299 if (
auto* tex = loadAndOwn(aiTextureType_BASE_COLOR)) {
300 material.setDiffuseMap(tex);
301 material.setBaseColorTexture(tex);
302 material.setHasBaseColorTexture(
true);
303 }
else if (
auto* tex2 = loadAndOwn(aiTextureType_DIFFUSE)) {
304 material.setDiffuseMap(tex2);
305 material.setBaseColorTexture(tex2);
306 material.setHasBaseColorTexture(
true);
309 if (
auto* tex = loadAndOwn(aiTextureType_NORMALS)) {
310 material.setNormalMap(tex);
311 material.setNormalTexture(tex);
312 material.setHasNormalTexture(
true);
316 if (
auto* tex = loadAndOwn(aiTextureType_UNKNOWN)) {
317 material.setMetallicRoughnessTexture(tex);
318 material.setHasMetallicRoughnessTexture(
true);
322 if (
auto* tex = loadAndOwn(aiTextureType_LIGHTMAP)) {
323 material.setAoMap(tex);
324 material.setOcclusionTexture(tex);
325 material.setHasOcclusionTexture(
true);
329 if (
auto* tex = loadAndOwn(aiTextureType_EMISSIVE)) {
330 material.setEmissiveMap(tex);
331 material.setEmissiveTexture(tex);
332 material.setHasEmissiveTexture(
true);
338 void convertLegacyMaterial(
339 const aiMaterial* aiMat,
340 const aiScene* scene,
341 const std::filesystem::path& basedir,
344 std::unordered_map<std::string, std::shared_ptr<Texture>>& texCache,
345 std::vector<std::shared_ptr<Texture>>& ownedTextures)
347 auto loadAndOwn = [&](aiTextureType type) ->
Texture* {
348 auto tex = loadAssimpTexture(aiMat, type, scene, basedir, device, texCache);
349 if (tex) ownedTextures.push_back(tex);
354 aiColor4D diffuse(0.8f, 0.8f, 0.8f, 1.0f);
355 aiGetMaterialColor(aiMat, AI_MATKEY_COLOR_DIFFUSE, &diffuse);
356 material.setDiffuse(
Color(diffuse.r, diffuse.g, diffuse.b, diffuse.a));
357 material.setBaseColorFactor(
Color(diffuse.r, diffuse.g, diffuse.b, diffuse.a));
360 aiColor4D specular(0.0f, 0.0f, 0.0f, 1.0f);
361 aiGetMaterialColor(aiMat, AI_MATKEY_COLOR_SPECULAR, &specular);
362 material.setSpecular(
Color(specular.r, specular.g, specular.b, 1.0f));
365 float shininess = 0.0f;
366 aiGetMaterialFloat(aiMat, AI_MATKEY_SHININESS, &shininess);
367 float roughness = (shininess > 0.0f)
368 ? std::sqrt(2.0f / (shininess + 2.0f))
370 material.setGloss(1.0f - roughness);
371 material.setRoughnessFactor(roughness);
374 float kdLum = 0.2126f * diffuse.r + 0.7152f * diffuse.g + 0.0722f * diffuse.b;
375 float ksLum = 0.2126f * specular.r + 0.7152f * specular.g + 0.0722f * specular.b;
376 float metalness = 0.0f;
377 if (kdLum < 0.04f && ksLum > 0.5f) {
379 }
else if (ksLum > 0.25f) {
380 float ksMax = std::max({specular.r, specular.g, specular.b});
381 float ksMin = std::min({specular.r, specular.g, specular.b});
382 float sat = (ksMax > 0.001f) ? (ksMax - ksMin) / ksMax : 0.0f;
383 if (sat > 0.2f) metalness = 0.8f;
385 material.setMetalness(metalness);
386 material.setMetallicFactor(metalness);
387 material.setUseMetalness(
true);
390 aiColor4D emissive(0.0f, 0.0f, 0.0f, 1.0f);
391 aiGetMaterialColor(aiMat, AI_MATKEY_COLOR_EMISSIVE, &emissive);
392 if (emissive.r > 0.0f || emissive.g > 0.0f || emissive.b > 0.0f) {
393 material.setEmissive(
Color(emissive.r, emissive.g, emissive.b, 1.0f));
394 material.setEmissiveFactor(
Color(emissive.r, emissive.g, emissive.b, 1.0f));
398 float opacity = 1.0f;
399 aiGetMaterialFloat(aiMat, AI_MATKEY_OPACITY, &opacity);
400 material.setOpacity(opacity);
401 if (opacity < 0.99f) {
403 material.setTransparent(
true);
409 if (
auto* tex = loadAndOwn(aiTextureType_DIFFUSE)) {
410 material.setDiffuseMap(tex);
411 material.setBaseColorTexture(tex);
412 material.setHasBaseColorTexture(
true);
416 if (
auto* tex = loadAndOwn(aiTextureType_NORMALS)) {
417 material.setNormalMap(tex);
418 material.setNormalTexture(tex);
419 material.setHasNormalTexture(
true);
420 }
else if (
auto* tex2 = loadAndOwn(aiTextureType_HEIGHT)) {
421 material.setNormalMap(tex2);
422 material.setNormalTexture(tex2);
423 material.setHasNormalTexture(
true);
424 material.setBumpiness(0.5f);
425 material.setNormalScale(0.5f);
429 if (
auto* tex = loadAndOwn(aiTextureType_AMBIENT)) {
430 material.setAoMap(tex);
431 material.setOcclusionTexture(tex);
432 material.setHasOcclusionTexture(
true);
436 if (
auto* tex = loadAndOwn(aiTextureType_EMISSIVE)) {
437 material.setEmissiveMap(tex);
438 material.setEmissiveTexture(tex);
439 material.setHasEmissiveTexture(
true);
443 if (
auto* tex = loadAndOwn(aiTextureType_OPACITY)) {
444 material.setOpacityMap(tex);
451 uint64_t computeShaderVariantKey(
const Material& material)
453 uint64_t variant = 1;
454 if (material.hasBaseColorTexture()) variant |= (1ull << 1);
456 else if (material.alphaMode() ==
AlphaMode::MASK) variant |= (1ull << 3);
457 if (material.hasNormalTexture()) variant |= (1ull << 4);
458 if (material.hasMetallicRoughnessTexture()) variant |= (1ull << 5);
459 if (material.hasOcclusionTexture()) variant |= (1ull << 6);
460 if (material.hasEmissiveTexture()) variant |= (1ull << 7);
466 std::shared_ptr<Mesh> convertAssimpMesh(
468 const std::shared_ptr<VertexFormat>& vertexFormat,
469 const std::shared_ptr<GraphicsDevice>& device,
472 const unsigned int vertexCount = aiM->mNumVertices;
473 if (vertexCount == 0)
return nullptr;
475 std::vector<PackedVertex> vertices(vertexCount);
477 float minX = std::numeric_limits<float>::max();
478 float minY = std::numeric_limits<float>::max();
479 float minZ = std::numeric_limits<float>::max();
480 float maxX = std::numeric_limits<float>::lowest();
481 float maxY = std::numeric_limits<float>::lowest();
482 float maxZ = std::numeric_limits<float>::lowest();
484 const bool hasNormals = aiM->HasNormals();
485 const bool hasUVs = aiM->HasTextureCoords(0);
486 const bool hasUV1 = aiM->HasTextureCoords(1);
487 const bool hasTangents = aiM->HasTangentsAndBitangents();
489 for (
unsigned int i = 0; i < vertexCount; ++i) {
490 float px = aiM->mVertices[i].x;
491 float py = aiM->mVertices[i].y;
492 float pz = aiM->mVertices[i].z;
495 px *= config.uniformScale;
496 py *= config.uniformScale;
497 pz *= config.uniformScale;
503 float nx = 0.0f, ny = 1.0f, nz = 0.0f;
505 nx = aiM->mNormals[i].x;
506 ny = aiM->mNormals[i].y;
507 nz = aiM->mNormals[i].z;
515 float u = 0.0f, v = 0.0f;
517 u = aiM->mTextureCoords[0][i].x;
518 v = aiM->mTextureCoords[0][i].y;
521 float u1 = u, v1 = v;
523 u1 = aiM->mTextureCoords[1][i].x;
524 v1 = aiM->mTextureCoords[1][i].y;
527 float tx = 0.0f, ty = 0.0f, tz = 0.0f, tw = 1.0f;
529 tx = aiM->mTangents[i].x;
530 ty = aiM->mTangents[i].y;
531 tz = aiM->mTangents[i].z;
539 float bx = aiM->mBitangents[i].x;
540 float by = aiM->mBitangents[i].y;
541 float bz = aiM->mBitangents[i].z;
547 tw = (n.cross(t).dot(b) < 0.0f) ? -1.0f : 1.0f;
549 tangentFromNormal(nx, ny, nz, tx, ty, tz, tw);
560 minX = std::min(minX, px); minY = std::min(minY, py); minZ = std::min(minZ, pz);
561 maxX = std::max(maxX, px); maxY = std::max(maxY, py); maxZ = std::max(maxZ, pz);
565 std::vector<uint32_t> indices;
566 indices.reserve(
static_cast<size_t>(aiM->mNumFaces) * 3);
567 for (
unsigned int f = 0; f < aiM->mNumFaces; ++f) {
568 const aiFace& face = aiM->mFaces[f];
569 if (face.mNumIndices == 3) {
570 if (config.flipWinding) {
571 indices.push_back(face.mIndices[0]);
572 indices.push_back(face.mIndices[2]);
573 indices.push_back(face.mIndices[1]);
575 indices.push_back(face.mIndices[0]);
576 indices.push_back(face.mIndices[1]);
577 indices.push_back(face.mIndices[2]);
582 if (indices.empty())
return nullptr;
585 if (!hasTangents && hasUVs && config.generateTangents) {
586 generateTangents(vertices, indices);
590 const int vCount =
static_cast<int>(vertices.size());
591 std::vector<uint8_t> vertexBytes(vertices.size() *
sizeof(
PackedVertex));
592 std::memcpy(vertexBytes.data(), vertices.data(), vertexBytes.size());
596 vbOpts.data = std::move(vertexBytes);
597 auto vb = device->createVertexBuffer(vertexFormat, vCount, vbOpts);
600 const int indexCount =
static_cast<int>(indices.size());
603 std::vector<uint8_t> indexBytes;
605 indexBytes.resize(indices.size() *
sizeof(uint16_t));
606 auto* dst =
reinterpret_cast<uint16_t*
>(indexBytes.data());
607 for (
size_t i = 0; i < indices.size(); ++i) {
608 dst[i] =
static_cast<uint16_t
>(indices[i]);
611 indexBytes.resize(indices.size() *
sizeof(uint32_t));
612 std::memcpy(indexBytes.data(), indices.data(), indexBytes.size());
614 auto ib = device->createIndexBuffer(idxFmt, indexCount, indexBytes);
617 auto meshResource = std::make_shared<Mesh>();
618 meshResource->setVertexBuffer(vb);
619 meshResource->setIndexBuffer(ib, 0);
625 prim.count = indexCount;
627 meshResource->setPrimitive(prim, 0);
630 if (aiM->mAABB.mMin.x <= aiM->mAABB.mMax.x &&
631 aiM->mAABB.mMin.y <= aiM->mAABB.mMax.y &&
632 aiM->mAABB.mMin.z <= aiM->mAABB.mMax.z &&
633 config.uniformScale == 1.0f && !config.flipYZ)
635 minX = aiM->mAABB.mMin.x; minY = aiM->mAABB.mMin.y; minZ = aiM->mAABB.mMin.z;
636 maxX = aiM->mAABB.mMax.x; maxY = aiM->mAABB.mMax.y; maxZ = aiM->mAABB.mMax.z;
641 (minX + maxX) * 0.5f,
642 (minY + maxY) * 0.5f,
643 (minZ + maxZ) * 0.5f);
644 bounds.setHalfExtents(
645 (maxX - minX) * 0.5f,
646 (maxY - minY) * 0.5f,
647 (maxZ - minZ) * 0.5f);
648 meshResource->setAabb(bounds);
658 const std::string& path,
659 const std::shared_ptr<GraphicsDevice>& device,
663 spdlog::error(
"Assimp parse failed: graphics device is null");
669 Assimp::Importer importer;
670 importer.SetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, config.
smoothingAngle);
671 importer.SetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, 45.0f);
674 importer.SetPropertyFloat(AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, config.
uniformScale);
679 unsigned int processFlags =
680 aiProcess_Triangulate |
681 aiProcess_GenSmoothNormals |
682 aiProcess_JoinIdenticalVertices |
684 aiProcess_ImproveCacheLocality |
685 aiProcess_RemoveRedundantMaterials |
686 aiProcess_FindDegenerates |
687 aiProcess_FindInvalidData |
688 aiProcess_GenUVCoords |
689 aiProcess_SortByPType |
690 aiProcess_GenBoundingBoxes;
693 processFlags |= aiProcess_CalcTangentSpace;
696 processFlags |= aiProcess_OptimizeMeshes;
699 processFlags |= aiProcess_GlobalScale;
704 const aiScene* scene = importer.ReadFile(path, processFlags);
705 if (!scene || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !scene->mRootNode) {
706 spdlog::error(
"Assimp parse failed [{}]: {}", path, importer.GetErrorString());
710 const std::filesystem::path modelPath(path);
711 const std::filesystem::path basedir = modelPath.parent_path();
713 auto container = std::make_unique<GlbContainerResource>();
714 auto vertexFormat = std::make_shared<VertexFormat>(
719 std::unordered_map<std::string, std::shared_ptr<Texture>> texCache;
720 std::vector<std::shared_ptr<Texture>> ownedTextures;
721 std::vector<std::shared_ptr<Material>> materials;
723 for (
unsigned int i = 0; i < scene->mNumMaterials; ++i) {
724 const aiMaterial* aiMat = scene->mMaterials[i];
725 auto material = std::make_shared<StandardMaterial>();
729 if (aiGetMaterialString(aiMat, AI_MATKEY_NAME, &matName) == AI_SUCCESS
730 && matName.length > 0) {
731 material->setName(matName.C_Str());
733 material->setName(
"assimp-material-" + std::to_string(i));
737 int shadingModel = 0;
738 aiGetMaterialInteger(aiMat, AI_MATKEY_SHADING_MODEL, &shadingModel);
740 if (shadingModel == aiShadingMode_PBR_BRDF) {
741 convertPbrMaterial(aiMat, scene, basedir, device.get(),
742 *material, texCache, ownedTextures);
744 convertLegacyMaterial(aiMat, scene, basedir, device.get(),
745 *material, texCache, ownedTextures);
750 if (aiGetMaterialInteger(aiMat, AI_MATKEY_TWOSIDED, &twoSided) == AI_SUCCESS && twoSided) {
755 material->setShaderVariantKey(computeShaderVariantKey(*material));
757 materials.push_back(material);
761 if (materials.empty()) {
762 auto mat = std::make_shared<StandardMaterial>();
763 mat->setName(
"assimp-default");
764 mat->setDiffuse(
Color(0.8f, 0.8f, 0.8f, 1.0f));
765 mat->setBaseColorFactor(
Color(0.8f, 0.8f, 0.8f, 1.0f));
766 mat->setMetalness(0.0f);
767 mat->setMetallicFactor(0.0f);
769 mat->setRoughnessFactor(0.5f);
770 mat->setUseMetalness(
true);
771 mat->setShaderVariantKey(1);
772 materials.push_back(mat);
778 std::vector<size_t> meshToPayloadIndex(scene->mNumMeshes, SIZE_MAX);
779 size_t nextPayloadIndex = 0;
781 for (
unsigned int i = 0; i < scene->mNumMeshes; ++i) {
782 const aiMesh* aiM = scene->mMeshes[i];
785 if (aiM->mPrimitiveTypes != aiPrimitiveType_TRIANGLE)
788 auto meshResource = convertAssimpMesh(aiM, vertexFormat, device, config);
793 payload.
mesh = meshResource;
794 unsigned int matIdx = aiM->mMaterialIndex;
795 payload.
material = (matIdx < materials.size())
798 container->addMeshPayload(payload);
799 meshToPayloadIndex[i] = nextPayloadIndex++;
805 std::vector<const aiNode*> allNodes;
806 std::unordered_map<const aiNode*, int> nodeIndexMap;
808 std::function<void(
const aiNode*)> collectNodes = [&](
const aiNode* node) {
809 int idx =
static_cast<int>(allNodes.size());
810 allNodes.push_back(node);
811 nodeIndexMap[node] = idx;
812 for (
unsigned int c = 0; c < node->mNumChildren; ++c) {
813 collectNodes(node->mChildren[c]);
816 collectNodes(scene->mRootNode);
819 for (
size_t i = 0; i < allNodes.size(); ++i) {
820 const aiNode* node = allNodes[i];
822 nodePayload.
name = node->mName.C_Str();
825 aiVector3D scaling, position;
826 aiQuaternion rotation;
827 node->mTransformation.Decompose(scaling, rotation, position);
831 nodePayload.
scale =
Vector3(scaling.x, scaling.y, scaling.z);
834 for (
unsigned int m = 0; m < node->mNumMeshes; ++m) {
835 unsigned int meshIdx = node->mMeshes[m];
836 if (meshIdx < meshToPayloadIndex.size() && meshToPayloadIndex[meshIdx] != SIZE_MAX) {
842 for (
unsigned int c = 0; c < node->mNumChildren; ++c) {
843 auto childIt = nodeIndexMap.find(node->mChildren[c]);
844 if (childIt != nodeIndexMap.end()) {
845 nodePayload.
children.push_back(childIt->second);
849 container->addNodePayload(nodePayload);
853 container->addRootNodeIndex(0);
856 for (
auto& tex : ownedTextures) {
857 container->addOwnedTexture(tex);
860 spdlog::info(
"Assimp parse complete [{}]: {} meshes, {} materials, {} nodes, {} textures",
861 path, nextPayloadIndex, materials.size(), allNodes.size(), ownedTextures.size());
static std::unique_ptr< GlbContainerResource > parse(const std::string &path, const std::shared_ptr< GraphicsDevice > &device, const AssimpParserConfig &config=AssimpParserConfig{})
Axis-Aligned Bounding Box defined by center and half-extents.
void setCenter(const Vector3 ¢er)
Abstract GPU interface for resource creation, state management, and draw submission.
Base class for GPU materials — owns uniform data, texture bindings, blend/depth state,...
Full PBR material with metalness/roughness workflow and advanced surface features.
GPU texture resource supporting 2D, cubemap, volume, and array formats with mipmap management.
Configuration options for Assimp-based model loading.
float uniformScale
Uniform scale applied to all vertex positions (e.g., 0.01 for cm -> m).
bool optimizeMeshes
Merge small meshes sharing the same material to reduce draw calls.
RGBA color with floating-point components in [0, 1].
std::shared_ptr< Mesh > mesh
std::shared_ptr< Material > material
std::vector< int > children
std::vector< size_t > meshPayloadIndices
Describes how vertex and index data should be interpreted for a draw call.
Unit quaternion for rotation representation with SIMD-accelerated slerp and multiply.
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
Vector3 cross(const Vector3 &other) const
float dot(const Vector3 &other) const