diff options
| author | Ethan Morgan <ethan@gweithio.com> | 2026-02-14 16:44:06 +0000 |
|---|---|---|
| committer | Ethan Morgan <ethan@gweithio.com> | 2026-02-14 16:44:06 +0000 |
| commit | 54409423f767d8b1cf30cb7d0efca6b4ca138823 (patch) | |
| tree | d915ac7828703ce4b963efdd9728a1777ba18c1e /apps/openmb/renderer/Model.cpp | |
Diffstat (limited to 'apps/openmb/renderer/Model.cpp')
| -rw-r--r-- | apps/openmb/renderer/Model.cpp | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/apps/openmb/renderer/Model.cpp b/apps/openmb/renderer/Model.cpp new file mode 100644 index 0000000..d183bbc --- /dev/null +++ b/apps/openmb/renderer/Model.cpp @@ -0,0 +1,227 @@ +#include "Model.hpp" + +#include <algorithm> +#include <assimp/Importer.hpp> +#include <assimp/postprocess.h> +#include <assimp/scene.h> +#include <cctype> +#include <cfloat> +#include <filesystem> +#include <glm/glm.hpp> +#include <iostream> + +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 |