aboutsummaryrefslogtreecommitdiff
path: root/apps/openmb/renderer/Model.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'apps/openmb/renderer/Model.cpp')
-rw-r--r--apps/openmb/renderer/Model.cpp227
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