379{
380 const auto* mesh = static_cast<const aiMesh*>(ai_mesh_ptr);
381
382 using V = Nodes::MeshVertex;
383
384 std::vector<V> verts;
385 verts.reserve(mesh->mNumVertices);
386
387 for (unsigned int v = 0; v < mesh->mNumVertices; ++v) {
389
390 vert.position = {
391 mesh->mVertices[v].x,
392 mesh->mVertices[v].y,
393 mesh->mVertices[v].z
394 };
395
396 if (mesh->HasNormals()) {
397 vert.normal = {
398 mesh->mNormals[v].x,
399 mesh->mNormals[v].y,
400 mesh->mNormals[v].z
401 };
402 } else {
403 vert.normal = { 0.0F, 1.0F, 0.0F };
404 }
405
406 if (mesh->HasTangentsAndBitangents()) {
407 vert.tangent = {
408 mesh->mTangents[v].x,
409 mesh->mTangents[v].y,
410 mesh->mTangents[v].z
411 };
412 } else {
413 vert.tangent = { 1.0F, 0.0F, 0.0F };
414 }
415
416 if (mesh->HasTextureCoords(0)) {
417 vert.uv = {
418 mesh->mTextureCoords[0][v].x,
419 mesh->mTextureCoords[0][v].y
420 };
421 } else {
422 vert.uv = { 0.0F, 0.0F };
423 }
424
425 if (mesh->HasVertexColors(0)) {
426 vert.color = {
427 mesh->mColors[0][v].r,
428 mesh->mColors[0][v].g,
429 mesh->mColors[0][v].b
430 };
431 } else {
432 vert.color = { 0.8F, 0.8F, 0.8F };
433 }
434
435 vert.weight = 0.0F;
436 verts.push_back(vert);
437 }
438
439 std::vector<uint32_t> indices;
440 indices.reserve(static_cast<size_t>(mesh->mNumFaces) * 3);
441
442 for (unsigned int f = 0; f < mesh->mNumFaces; ++f) {
443 const aiFace& face = mesh->mFaces[f];
444 if (face.mNumIndices != 3) {
445 continue;
446 }
447 indices.push_back(face.mIndices[0]);
448 indices.push_back(face.mIndices[1]);
449 indices.push_back(face.mIndices[2]);
450 }
451
452 if (verts.empty() || indices.empty()) {
453 set_error(
"Mesh has no usable geometry after extraction");
454 return {};
455 }
456
458 Kakshya::MeshInsertion ins(data.vertex_variant, data.index_variant);
459 ins.insert_flat(
460 std::span<const uint8_t>(
461 reinterpret_cast<const uint8_t*>(verts.data()),
462 verts.size() *
sizeof(
V)),
463 std::span<const uint32_t>(indices),
465
466 auto access = ins.build();
467 if (!access) {
468 set_error(
"MeshInsertion::build() failed");
469 return {};
470 }
471 data.layout = access->layout;
472
473 Kakshya::MeshSubrange sub;
474 sub.index_start = 0;
475 sub.index_count = static_cast<uint32_t>(indices.size());
476 sub.vertex_offset = 0;
477 sub.name = std::string(mesh_name);
478 sub.material_name = std::string(material_name);
479
480 const auto* s = static_cast<const aiScene*>(ai_scene);
481
482 if (s->mNumMaterials > 0 && mesh->mMaterialIndex < s->mNumMaterials) {
483 aiString tex_path;
484 const aiMaterial* mat = s->mMaterials[mesh->mMaterialIndex];
485 if (mat->GetTexture(aiTextureType_DIFFUSE, 0, &tex_path) == AI_SUCCESS) {
486 sub.diffuse_path = std::filesystem::path(tex_path.C_Str()).generic_string();
487 sub.diffuse_embedded = (!sub.diffuse_path.empty()
488 && sub.diffuse_path[0] == '*');
489
491 "ModelReader: mesh '{}' diffuse={} embedded={}",
492 std::string(mesh_name),
493 sub.diffuse_path,
494 sub.diffuse_embedded);
495 }
496 }
497
498 Kakshya::RegionGroup rg("submeshes");
499 rg.add_region(sub.to_region());
500 data.submeshes = std::move(rg);
501
503 "ModelReader: extracted mesh '{}' mat='{}' — {} verts, {} indices",
504 mesh_name.empty() ? "<unnamed>" : mesh_name,
505 material_name.empty() ? "<none>" : material_name,
507
508 return data;
509}
#define MF_DEBUG(comp, ctx,...)
void set_error(std::string msg) const
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
static MeshData empty()
Construct an empty MeshData with the canonical 60-byte mesh layout.
static VertexLayout for_meshes(uint32_t stride=60)
Factory: layout for MeshVertex (position, color, weight, uv, normal, tangent)