#include "Model.hpp" #include #include #include #include #include #include #include #include #include namespace renderer { Model::Model() {} static glm::vec3 vecMin( const glm::vec3 &a, const glm::vec3 &b ) { return glm::vec3( std::min( a.x, b.x ), std::min( a.y, b.y ), std::min( a.z, b.z ) ); } static glm::vec3 vecMax( const glm::vec3 &a, const glm::vec3 &b ) { return glm::vec3( std::max( a.x, b.x ), std::max( a.y, b.y ), std::max( a.z, b.z ) ); } bool Model::loadFromFile( const std::string &path ) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile( path, aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_ImproveCacheLocality | aiProcess_JoinIdenticalVertices ); if ( !scene || !scene->mRootNode ) { std::cerr << "Failed to load model: " << path << " (" << importer.GetErrorString() << ")" << std::endl; return false; } std::filesystem::path p( path ); std::string baseDir = p.parent_path().string(); mMeshes.clear(); mTextures.clear(); mNormalTextures.clear(); mMeshToTex.clear(); mMeshToNormal.clear(); mFallbackAlbedoIndex = -1; mFallbackNormalIndex = -1; mBoundsMin = glm::vec3( FLT_MAX ); mBoundsMax = glm::vec3( -FLT_MAX ); if ( std::filesystem::exists( baseDir ) && std::filesystem::is_directory( baseDir ) ) { for ( const auto &e : std::filesystem::directory_iterator( baseDir ) ) { if ( !e.is_regular_file() ) continue; auto ext = e.path().extension().string(); std::string extLower = ext; std::transform( extLower.begin(), extLower.end(), extLower.begin(), []( unsigned char c ) { return std::tolower( c ); } ); if ( extLower != ".png" && extLower != ".jpg" && extLower != ".jpeg" && extLower != ".tga" ) continue; std::string fname = e.path().filename().string(); std::string fnameLower = fname; std::transform( fnameLower.begin(), fnameLower.end(), fnameLower.begin(), []( unsigned char c ) { return std::tolower( c ); } ); if ( fnameLower.find( "base" ) != std::string::npos || fnameLower.find( "albedo" ) != std::string::npos || fnameLower.find( "diff" ) != std::string::npos ) { Texture t; if ( t.loadFromFile( e.path().string() ) ) { mFallbackAlbedoIndex = static_cast< int >( mTextures.size() ); mTextures.push_back( std::move( t ) ); std::cerr << "Model: found fallback albedo: " << e.path().string() << " (index " << mFallbackAlbedoIndex << ")\n"; } } else if ( fnameLower.find( "normal" ) != std::string::npos || fnameLower.find( "nrm" ) != std::string::npos || fnameLower.find( "bump" ) != std::string::npos ) { Texture tn; if ( tn.loadFromFile( e.path().string() ) ) { mFallbackNormalIndex = static_cast< int >( mNormalTextures.size() ); mNormalTextures.push_back( std::move( tn ) ); std::cerr << "Model: found fallback normal: " << e.path().string() << " (index " << mFallbackNormalIndex << ")\n"; } } } } return processNode( scene->mRootNode, scene, baseDir ); } bool Model::processNode( aiNode *node, const aiScene *scene, const std::string &baseDir ) { for ( unsigned int i = 0; i < node->mNumMeshes; ++i ) { aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; int texIndex = -1; int normalIndex = -1; if ( !processMesh( mesh, scene, baseDir, texIndex, normalIndex ) ) return false; if ( texIndex == -1 && mFallbackAlbedoIndex != -1 ) texIndex = mFallbackAlbedoIndex; if ( normalIndex == -1 && mFallbackNormalIndex != -1 ) normalIndex = mFallbackNormalIndex; mMeshToTex.push_back( texIndex ); mMeshToNormal.push_back( normalIndex ); std::cerr << "Model: mesh " << i << " -> albedo index " << texIndex << ", normal index " << normalIndex << "\n"; } for ( unsigned int i = 0; i < node->mNumChildren; ++i ) { if ( !processNode( node->mChildren[i], scene, baseDir ) ) return false; } return true; } bool Model::processMesh( aiMesh *mesh, const aiScene *scene, const std::string &baseDir, int &outTexIndex, int &outNormalIndex ) { std::vector< float > data; data.reserve( mesh->mNumFaces * 3 * 8 ); glm::vec3 localMin( FLT_MAX ); glm::vec3 localMax( -FLT_MAX ); for ( unsigned int f = 0; f < mesh->mNumFaces; ++f ) { aiFace &face = mesh->mFaces[f]; for ( unsigned int vi = 0; vi < face.mNumIndices; ++vi ) { unsigned int idx = face.mIndices[vi]; aiVector3D pos = mesh->mVertices[idx]; aiVector3D normal = mesh->HasNormals() ? mesh->mNormals[idx] : aiVector3D( 0.0f, 1.0f, 0.0f ); aiVector3D texcoord = mesh->HasTextureCoords( 0 ) ? mesh->mTextureCoords[0][idx] : aiVector3D( 0.0f, 0.0f, 0.0f ); data.push_back( pos.x ); data.push_back( pos.y ); data.push_back( pos.z ); data.push_back( texcoord.x ); data.push_back( texcoord.y ); data.push_back( normal.x ); data.push_back( normal.y ); data.push_back( normal.z ); } } for ( size_t i = 0; i + 7 < data.size(); i += 8 ) { glm::vec3 p( data[i + 0], data[i + 1], data[i + 2] ); localMin = vecMin( localMin, p ); localMax = vecMax( localMax, p ); } if ( localMin.x <= localMax.x ) { mBoundsMin = vecMin( mBoundsMin, localMin ); mBoundsMax = vecMax( mBoundsMax, localMax ); } auto m = std::make_unique< Mesh >(); if ( !m->createFromPosTexNormal( data ) ) { std::cerr << "Failed to create mesh for model" << std::endl; return false; } mMeshes.push_back( std::move( m ) ); outTexIndex = -1; outNormalIndex = -1; if ( mesh->mMaterialIndex >= 0 && scene->mMaterials && mesh->mMaterialIndex < scene->mNumMaterials ) { aiMaterial *mat = scene->mMaterials[mesh->mMaterialIndex]; aiString texPath; if ( AI_SUCCESS == mat->GetTexture( aiTextureType_DIFFUSE, 0, &texPath ) ) { std::string texStr = texPath.C_Str(); std::filesystem::path full = std::filesystem::path( baseDir ) / texStr; Texture t; if ( t.loadFromFile( full.string() ) ) { outTexIndex = static_cast< int >( mTextures.size() ); mTextures.push_back( std::move( t ) ); } else { std::cerr << "Warning: failed to load texture: " << full.string() << std::endl; } } if ( AI_SUCCESS == mat->GetTexture( aiTextureType_NORMALS, 0, &texPath ) || AI_SUCCESS == mat->GetTexture( aiTextureType_HEIGHT, 0, &texPath ) ) { std::string texStrN = texPath.C_Str(); std::filesystem::path fullN = std::filesystem::path( baseDir ) / texStrN; Texture tn; if ( tn.loadFromFile( fullN.string() ) ) { outNormalIndex = static_cast< int >( mNormalTextures.size() ); mNormalTextures.push_back( std::move( tn ) ); } else { std::cerr << "Warning: failed to load normal map: " << fullN.string() << std::endl; } } } return true; } } // namespace renderer