aboutsummaryrefslogtreecommitdiff
path: root/apps/openmb/renderer
diff options
context:
space:
mode:
authorEthan Morgan <ethan@gweithio.com>2026-02-14 16:44:06 +0000
committerEthan Morgan <ethan@gweithio.com>2026-02-14 16:44:06 +0000
commit54409423f767d8b1cf30cb7d0efca6b4ca138823 (patch)
treed915ac7828703ce4b963efdd9728a1777ba18c1e /apps/openmb/renderer
move to own git serverHEADmaster
Diffstat (limited to 'apps/openmb/renderer')
-rw-r--r--apps/openmb/renderer/DirectionalLight.cpp39
-rw-r--r--apps/openmb/renderer/DirectionalLight.hpp28
-rw-r--r--apps/openmb/renderer/EditorHelpers.cpp148
-rw-r--r--apps/openmb/renderer/EditorHelpers.hpp20
-rw-r--r--apps/openmb/renderer/GLHelpers.cpp30
-rw-r--r--apps/openmb/renderer/GLHelpers.hpp14
-rw-r--r--apps/openmb/renderer/Mesh.cpp147
-rw-r--r--apps/openmb/renderer/Mesh.hpp41
-rw-r--r--apps/openmb/renderer/Model.cpp227
-rw-r--r--apps/openmb/renderer/Model.hpp51
-rw-r--r--apps/openmb/renderer/SSAORenderer.cpp319
-rw-r--r--apps/openmb/renderer/SSAORenderer.hpp64
-rw-r--r--apps/openmb/renderer/Shader.cpp137
-rw-r--r--apps/openmb/renderer/Shader.hpp31
-rw-r--r--apps/openmb/renderer/Skybox.cpp155
-rw-r--r--apps/openmb/renderer/Skybox.hpp34
-rw-r--r--apps/openmb/renderer/Texture.cpp53
-rw-r--r--apps/openmb/renderer/Texture.hpp28
-rw-r--r--apps/openmb/renderer/TextureManager.cpp163
-rw-r--r--apps/openmb/renderer/TextureManager.hpp64
-rw-r--r--apps/openmb/renderer/primitives.cpp595
-rw-r--r--apps/openmb/renderer/primitives.hpp22
22 files changed, 2410 insertions, 0 deletions
diff --git a/apps/openmb/renderer/DirectionalLight.cpp b/apps/openmb/renderer/DirectionalLight.cpp
new file mode 100644
index 0000000..28fe281
--- /dev/null
+++ b/apps/openmb/renderer/DirectionalLight.cpp
@@ -0,0 +1,39 @@
+#include "DirectionalLight.hpp"
+
+namespace renderer {
+DirectionalLight::DirectionalLight ()
+ : mDirection( 0.3f, 1.0f, 0.5f ), mColor( 1.0f, 1.0f, 1.0f ), mIntensity( 1.0f ) {
+}
+
+void DirectionalLight::setDirection ( const glm::vec3& dir ) {
+ mDirection = dir;
+}
+
+void DirectionalLight::setColor ( const glm::vec3& c ) {
+ mColor = c;
+}
+
+void DirectionalLight::setIntensity ( float i ) {
+ mIntensity = i;
+}
+
+const glm::vec3& DirectionalLight::getDirection () const {
+ return mDirection;
+}
+
+const glm::vec3& DirectionalLight::getColor () const {
+ return mColor;
+}
+
+float DirectionalLight::getIntensity () const {
+ return mIntensity;
+}
+
+void DirectionalLight::applyToShader ( const Shader& shader, const std::string& uniformName ) const {
+
+ shader.setVec3( uniformName + ".direction", mDirection );
+ shader.setVec3( uniformName + ".color", mColor );
+ shader.setFloat( uniformName + ".intensity", mIntensity );
+}
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/DirectionalLight.hpp b/apps/openmb/renderer/DirectionalLight.hpp
new file mode 100644
index 0000000..31e914e
--- /dev/null
+++ b/apps/openmb/renderer/DirectionalLight.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "Shader.hpp"
+#include <glm/glm.hpp>
+
+namespace renderer {
+
+class DirectionalLight {
+ public:
+ DirectionalLight();
+
+ void setDirection( const glm::vec3& dir );
+ void setColor( const glm::vec3& c );
+ void setIntensity( float i );
+
+ const glm::vec3& getDirection() const;
+ const glm::vec3& getColor() const;
+ float getIntensity() const;
+
+ void applyToShader( const Shader& shader, const std::string& uniformName = "dirLight" ) const;
+
+ private:
+ glm::vec3 mDirection;
+ glm::vec3 mColor;
+ float mIntensity;
+};
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/EditorHelpers.cpp b/apps/openmb/renderer/EditorHelpers.cpp
new file mode 100644
index 0000000..9531fb0
--- /dev/null
+++ b/apps/openmb/renderer/EditorHelpers.cpp
@@ -0,0 +1,148 @@
+#include "EditorHelpers.hpp"
+#include <cmath>
+
+#include <glm/gtc/matrix_inverse.hpp>
+#include <vector>
+
+namespace renderer {
+namespace editor {
+Mesh makeWireCube ( float size ) {
+ float h = size * 0.5f;
+ std::vector<float> verts = {
+ -h, -h, -h, h, -h, -h, h, -h, -h, h, -h, h, h, -h, h, -h, -h, h, -h, -h, h, -h, -h, -h,
+
+ -h, h, -h, h, h, -h, h, h, -h, h, h, h, h, h, h, -h, h, h, -h, h, h, -h, h, -h,
+
+ -h, -h, -h, -h, h, -h, h, -h, -h, h, h, -h, h, -h, h, h, h, h, -h, -h, h, -h, h, h };
+
+ Mesh m;
+ m.createFromPositions( verts, true );
+ return m;
+}
+
+Mesh makeCircleWire ( float radius, int segments ) {
+ if( segments < 3 )
+ segments = 3;
+
+ std::vector<float> verts;
+ verts.reserve( segments * 3 );
+
+ const float twoPi = 6.28318530717958647692f;
+
+ for( int i = 0; i < segments; ++i ) {
+ int ni = ( i + 1 ) % segments;
+ float ti = (float)i / (float)segments;
+ float angi = ti * twoPi;
+ float xi = cosf( angi ) * radius;
+ float zi = sinf( angi ) * radius;
+
+ float tni = (float)ni / (float)segments;
+ float angni = tni * twoPi;
+ float xni = cosf( angni ) * radius;
+ float zni = sinf( angni ) * radius;
+
+ verts.push_back( xi );
+ verts.push_back( 0.0f );
+ verts.push_back( zi );
+
+ verts.push_back( xni );
+ verts.push_back( 0.0f );
+ verts.push_back( zni );
+ }
+
+ Mesh m;
+ m.createFromPositions( verts, true );
+ return m;
+}
+
+Mesh makeCircleFilled ( float radius, int segments ) {
+ if( segments < 3 )
+ segments = 3;
+
+ std::vector<float> data;
+ data.reserve( ( segments + 2 ) * 5 );
+
+ const float twoPi = 6.28318530717958647692f;
+
+ float cx = 0.0f, cz = 0.0f;
+ float cu = 0.5f, cv = 0.5f;
+
+ for( int i = 0; i < segments; ++i ) {
+ int i1 = i;
+ int i2 = ( i + 1 ) % segments;
+
+ float t1 = (float)i1 / (float)segments;
+ float ang1 = t1 * twoPi;
+ float x1 = cosf( ang1 ) * radius;
+ float z1 = sinf( ang1 ) * radius;
+ float u1 = ( x1 / ( radius * 2.0f ) ) + 0.5f;
+ float v1 = ( z1 / ( radius * 2.0f ) ) + 0.5f;
+
+ float t2 = (float)i2 / (float)segments;
+ float ang2 = t2 * twoPi;
+ float x2 = cosf( ang2 ) * radius;
+ float z2 = sinf( ang2 ) * radius;
+ float u2 = ( x2 / ( radius * 2.0f ) ) + 0.5f;
+ float v2 = ( z2 / ( radius * 2.0f ) ) + 0.5f;
+
+ data.push_back( cx );
+ data.push_back( 0.0f );
+ data.push_back( cz );
+ data.push_back( cu );
+ data.push_back( cv );
+
+ data.push_back( x1 );
+ data.push_back( 0.0f );
+ data.push_back( z1 );
+ data.push_back( u1 );
+ data.push_back( v1 );
+
+ data.push_back( x2 );
+ data.push_back( 0.0f );
+ data.push_back( z2 );
+ data.push_back( u2 );
+ data.push_back( v2 );
+ }
+
+ Mesh m;
+ m.createFromPosTex( data );
+ return m;
+}
+
+bool makeRayFromMouse ( const glm::vec2& mousePx, int fbw, int fbh, const glm::mat4& view,
+ const glm::mat4& proj, const glm::vec3& camPos, glm::vec3& outOrigin,
+ glm::vec3& outDir ) {
+ if( fbw <= 0 || fbh <= 0 )
+ return false;
+
+ float ndcX = ( mousePx.x / (float)fbw ) * 2.0f - 1.0f;
+ float ndcY = 1.0f - ( mousePx.y / (float)fbh ) * 2.0f;
+
+ glm::vec4 nearPointNDC( ndcX, ndcY, -1.0f, 1.0f );
+ glm::vec4 farPointNDC( ndcX, ndcY, 1.0f, 1.0f );
+
+ glm::mat4 invPV = glm::inverse( proj * view );
+
+ glm::vec4 nearWorld = invPV * nearPointNDC;
+ glm::vec4 farWorld = invPV * farPointNDC;
+ if( nearWorld.w == 0.0f || farWorld.w == 0.0f )
+ return false;
+
+ nearWorld /= nearWorld.w;
+ farWorld /= farWorld.w;
+
+ glm::vec3 nearPos = glm::vec3( nearWorld );
+ glm::vec3 farPos = glm::vec3( farWorld );
+
+ outOrigin = nearPos;
+ outDir = glm::normalize( farPos - nearPos );
+
+ if( !isfinite( outDir.x ) || !isfinite( outDir.y ) || !isfinite( outDir.z ) ) {
+ outOrigin = camPos;
+ outDir = glm::normalize( glm::vec3( 0.0f, 0.0f, -1.0f ) );
+ }
+
+ return true;
+}
+} // namespace editor
+} // namespace renderer
diff --git a/apps/openmb/renderer/EditorHelpers.hpp b/apps/openmb/renderer/EditorHelpers.hpp
new file mode 100644
index 0000000..8156675
--- /dev/null
+++ b/apps/openmb/renderer/EditorHelpers.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "Mesh.hpp"
+#include <glm/glm.hpp>
+
+namespace renderer {
+namespace editor {
+
+Mesh makeWireCube ( float size = 1.0f );
+
+Mesh makeCircleWire ( float radius = 1.0f, int segments = 64 );
+
+Mesh makeCircleFilled ( float radius = 1.0f, int segments = 64 );
+
+bool makeRayFromMouse ( const glm::vec2& mousePx, int fbw, int fbh,
+ const glm::mat4& view, const glm::mat4& proj,
+ const glm::vec3& camPos, glm::vec3& outOrigin,
+ glm::vec3& outDir );
+} // namespace editor
+} // namespace renderer
diff --git a/apps/openmb/renderer/GLHelpers.cpp b/apps/openmb/renderer/GLHelpers.cpp
new file mode 100644
index 0000000..892fec0
--- /dev/null
+++ b/apps/openmb/renderer/GLHelpers.cpp
@@ -0,0 +1,30 @@
+#include "GLHelpers.hpp"
+
+#include <iostream>
+
+namespace renderer {
+static void glDebugOutput ( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
+ const GLchar* message, const void* userParam ) {
+ (void)source;
+ (void)type;
+ (void)id;
+ (void)severity;
+ (void)length;
+ (void)userParam;
+
+ std::cerr << "GL Debug: " << message << std::endl;
+}
+
+bool initGL () {
+
+ glEnable( GL_DEPTH_TEST );
+ glDepthFunc( GL_LESS );
+
+ setupDebugCallback();
+
+ return true;
+}
+
+void setupDebugCallback () {}
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/GLHelpers.hpp b/apps/openmb/renderer/GLHelpers.hpp
new file mode 100644
index 0000000..a566f51
--- /dev/null
+++ b/apps/openmb/renderer/GLHelpers.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#endif
+
+namespace renderer {
+
+bool initGL();
+
+void setupDebugCallback();
+} // namespace renderer
diff --git a/apps/openmb/renderer/Mesh.cpp b/apps/openmb/renderer/Mesh.cpp
new file mode 100644
index 0000000..4fb34ee
--- /dev/null
+++ b/apps/openmb/renderer/Mesh.cpp
@@ -0,0 +1,147 @@
+#include "Mesh.hpp"
+
+namespace renderer {
+Mesh::Mesh () : mVAO( 0 ), mVBO( 0 ), mEBO( 0 ), mVertexCount( 0 ), mIndexCount( 0 ), mMode( GL_TRIANGLES ) {}
+
+Mesh::~Mesh () {
+ if( mVBO ) {
+ glDeleteBuffers( 1, &mVBO );
+ }
+ if( mEBO ) {
+ glDeleteBuffers( 1, &mEBO );
+ }
+ if( mVAO ) {
+ glDeleteVertexArrays( 1, &mVAO );
+ }
+}
+
+bool Mesh::createFromPositions ( const std::vector<float>& positions, bool lines ) {
+ if( positions.empty() ) {
+ return false;
+ }
+
+ mMode = lines ? GL_LINES : GL_TRIANGLES;
+ mVertexCount = static_cast<GLsizei>( positions.size() / 3 );
+
+ glGenVertexArrays( 1, &mVAO );
+ glGenBuffers( 1, &mVBO );
+
+ glBindVertexArray( mVAO );
+ glBindBuffer( GL_ARRAY_BUFFER, mVBO );
+ glBufferData( GL_ARRAY_BUFFER, positions.size() * sizeof( float ), positions.data(), GL_STATIC_DRAW );
+
+ glEnableVertexAttribArray( 0 );
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof( float ), (void*)0 );
+
+ glBindVertexArray( 0 );
+
+ return true;
+}
+
+bool Mesh::createFromPosTex ( const std::vector<float>& data ) {
+ if( data.empty() )
+ return false;
+
+ const size_t strideFloats = 5;
+ mMode = GL_TRIANGLES;
+ mVertexCount = static_cast<GLsizei>( data.size() / strideFloats );
+
+ glGenVertexArrays( 1, &mVAO );
+ glGenBuffers( 1, &mVBO );
+
+ glBindVertexArray( mVAO );
+ glBindBuffer( GL_ARRAY_BUFFER, mVBO );
+ glBufferData( GL_ARRAY_BUFFER, data.size() * sizeof( float ), data.data(), GL_STATIC_DRAW );
+
+ glEnableVertexAttribArray( 0 );
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, strideFloats * sizeof( float ), (void*)0 );
+
+ glEnableVertexAttribArray( 1 );
+ glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, strideFloats * sizeof( float ),
+ (void*)( 3 * sizeof( float ) ) );
+
+ glBindVertexArray( 0 );
+ return true;
+}
+
+bool Mesh::createFromPosTexNormal ( const std::vector<float>& data ) {
+ if( data.empty() )
+ return false;
+
+ const size_t strideFloats = 8;
+ mMode = GL_TRIANGLES;
+ mVertexCount = static_cast<GLsizei>( data.size() / strideFloats );
+
+ glGenVertexArrays( 1, &mVAO );
+ glGenBuffers( 1, &mVBO );
+
+ glBindVertexArray( mVAO );
+ glBindBuffer( GL_ARRAY_BUFFER, mVBO );
+ glBufferData( GL_ARRAY_BUFFER, data.size() * sizeof( float ), data.data(), GL_STATIC_DRAW );
+
+ glEnableVertexAttribArray( 0 );
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, strideFloats * sizeof( float ), (void*)0 );
+
+ glEnableVertexAttribArray( 1 );
+ glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, strideFloats * sizeof( float ),
+ (void*)( 3 * sizeof( float ) ) );
+
+ glEnableVertexAttribArray( 2 );
+ glVertexAttribPointer( 2, 3, GL_FLOAT, GL_FALSE, strideFloats * sizeof( float ),
+ (void*)( 5 * sizeof( float ) ) );
+
+ glBindVertexArray( 0 );
+ return true;
+}
+
+bool Mesh::createFromPosTexNormalIndexed ( const std::vector<float>& data,
+ const std::vector<unsigned int>& indices ) {
+ if( data.empty() || indices.empty() )
+ return false;
+
+ const size_t strideFloats = 8;
+ mMode = GL_TRIANGLES;
+ mVertexCount = static_cast<GLsizei>( data.size() / strideFloats );
+ mIndexCount = static_cast<GLsizei>( indices.size() );
+
+ glGenVertexArrays( 1, &mVAO );
+ glGenBuffers( 1, &mVBO );
+ glGenBuffers( 1, &mEBO );
+
+ glBindVertexArray( mVAO );
+ glBindBuffer( GL_ARRAY_BUFFER, mVBO );
+ glBufferData( GL_ARRAY_BUFFER, data.size() * sizeof( float ), data.data(), GL_STATIC_DRAW );
+
+ glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mEBO );
+ glBufferData( GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof( unsigned int ), indices.data(),
+ GL_STATIC_DRAW );
+
+ glEnableVertexAttribArray( 0 );
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, strideFloats * sizeof( float ), (void*)0 );
+
+ glEnableVertexAttribArray( 1 );
+ glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, strideFloats * sizeof( float ),
+ (void*)( 3 * sizeof( float ) ) );
+
+ glEnableVertexAttribArray( 2 );
+ glVertexAttribPointer( 2, 3, GL_FLOAT, GL_FALSE, strideFloats * sizeof( float ),
+ (void*)( 5 * sizeof( float ) ) );
+
+ glBindVertexArray( 0 );
+ return true;
+}
+
+void Mesh::draw () const {
+ if( mVAO == 0 )
+ return;
+
+ glBindVertexArray( mVAO );
+ if( mEBO && mIndexCount > 0 ) {
+ glDrawElements( mMode, mIndexCount, GL_UNSIGNED_INT, 0 );
+ } else {
+ glDrawArrays( mMode, 0, mVertexCount );
+ }
+ glBindVertexArray( 0 );
+}
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/Mesh.hpp b/apps/openmb/renderer/Mesh.hpp
new file mode 100644
index 0000000..de0a8fa
--- /dev/null
+++ b/apps/openmb/renderer/Mesh.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#else
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#endif
+#endif
+#include <vector>
+
+namespace renderer
+{
+ class Mesh
+ {
+ public:
+ Mesh();
+ ~Mesh();
+
+ bool createFromPositions( const std::vector< float > &positions, bool lines = false );
+
+ bool createFromPosTex( const std::vector< float > &data );
+
+ bool createFromPosTexNormal( const std::vector< float > &data );
+ bool createFromPosTexNormalIndexed( const std::vector< float > &data,
+ const std::vector< unsigned int > &indices );
+
+ void draw() const;
+
+ private:
+ GLuint mVAO;
+ GLuint mVBO;
+ GLuint mEBO;
+ GLsizei mVertexCount;
+ GLsizei mIndexCount;
+ GLenum mMode;
+ };
+
+} // namespace renderer
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
diff --git a/apps/openmb/renderer/Model.hpp b/apps/openmb/renderer/Model.hpp
new file mode 100644
index 0000000..e20c523
--- /dev/null
+++ b/apps/openmb/renderer/Model.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "Mesh.hpp"
+#include "Shader.hpp"
+#include "Texture.hpp"
+
+#include <assimp/scene.h>
+#include <glm/glm.hpp>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace renderer
+{
+ class Model
+ {
+ public:
+ Model();
+ ~Model() = default;
+
+ bool loadFromFile( const std::string &path );
+
+ size_t meshCount() const { return mMeshes.size(); }
+
+ const glm::vec3 &getBoundsMin() const { return mBoundsMin; }
+ const glm::vec3 &getBoundsMax() const { return mBoundsMax; }
+
+ const std::vector< std::unique_ptr< Mesh > > &getMeshes() const { return mMeshes; }
+ const std::vector< Texture > &getTextures() const { return mTextures; }
+ const std::vector< Texture > &getNormalTextures() const { return mNormalTextures; }
+ const std::vector< int > &getMeshTextureIndex() const { return mMeshToTex; }
+ const std::vector< int > &getMeshNormalIndex() const { return mMeshToNormal; }
+
+ private:
+ bool processNode( aiNode *node, const aiScene *scene, const std::string &baseDir );
+ bool processMesh( aiMesh *mesh, const aiScene *scene, const std::string &baseDir, int &outTexIndex,
+ int &outNormalIndex );
+
+ std::vector< std::unique_ptr< Mesh > > mMeshes;
+ std::vector< Texture > mTextures;
+ std::vector< Texture > mNormalTextures;
+
+ std::vector< int > mMeshToTex;
+ std::vector< int > mMeshToNormal;
+ int mFallbackAlbedoIndex;
+ int mFallbackNormalIndex;
+ glm::vec3 mBoundsMin;
+ glm::vec3 mBoundsMax;
+ };
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/SSAORenderer.cpp b/apps/openmb/renderer/SSAORenderer.cpp
new file mode 100644
index 0000000..8493529
--- /dev/null
+++ b/apps/openmb/renderer/SSAORenderer.cpp
@@ -0,0 +1,319 @@
+#include "SSAORenderer.hpp"
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#endif
+#include <glm/gtc/type_ptr.hpp>
+#include <iostream>
+#include <random>
+
+namespace renderer {
+
+SSAORenderer::SSAORenderer ()
+ : mWidth( 0 ), mHeight( 0 ), mHalfRes( true ), mKernelSize( 32 ), mGBufferFBO( 0 ), mGNormal( 0 ), mGDepth( 0 ),
+ mSSAOFBO( 0 ), mSSAOTexture( 0 ), mSSAOBlurFBO( 0 ), mSSAOBlurTexture( 0 ), mNoiseTexture( 0 ), mQuadVAO( 0 ),
+ mQuadVBO( 0 ) {
+}
+
+SSAORenderer::~SSAORenderer () {
+ if( mGNormal )
+ glDeleteTextures( 1, &mGNormal );
+ if( mGDepth )
+ glDeleteTextures( 1, &mGDepth );
+ if( mGBufferFBO )
+ glDeleteFramebuffers( 1, &mGBufferFBO );
+ if( mSSAOTexture )
+ glDeleteTextures( 1, &mSSAOTexture );
+ if( mSSAOBlurTexture )
+ glDeleteTextures( 1, &mSSAOBlurTexture );
+ if( mSSAOFBO )
+ glDeleteFramebuffers( 1, &mSSAOFBO );
+ if( mSSAOBlurFBO )
+ glDeleteFramebuffers( 1, &mSSAOBlurFBO );
+ if( mNoiseTexture )
+ glDeleteTextures( 1, &mNoiseTexture );
+ if( mQuadVBO )
+ glDeleteBuffers( 1, &mQuadVBO );
+ if( mQuadVAO )
+ glDeleteVertexArrays( 1, &mQuadVAO );
+}
+
+bool SSAORenderer::init ( int width, int height, bool halfRes ) {
+ mWidth = width;
+ mHeight = height;
+ mHalfRes = halfRes;
+
+ if( !mGBufferShader.fromFiles( "apps/openmb/resources/shaders/gbuffer.vert",
+ "apps/openmb/resources/shaders/gbuffer.frag" ) ) {
+ std::cerr << "Failed to load gbuffer shader" << std::endl;
+ return false;
+ }
+ if( !mSSAOShader.fromFiles( "apps/openmb/resources/shaders/ssao.vert",
+ "apps/openmb/resources/shaders/ssao.frag" ) ) {
+ std::cerr << "Failed to load ssao shader" << std::endl;
+ return false;
+ }
+ if( !mBlurShader.fromFiles( "apps/openmb/resources/shaders/ssao.vert",
+ "apps/openmb/resources/shaders/ssao_blur.frag" ) ) {
+ std::cerr << "Failed to load ssao blur shader" << std::endl;
+ return false;
+ }
+
+ initBuffers();
+ initKernelAndNoise();
+
+ float quadVertices[] = {
+
+ -1.0f,
+ -1.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f,
+ -1.0f,
+ 1.0f,
+ 0.0f,
+ -1.0f,
+ 1.0f,
+ 0.0f,
+ 1.0f,
+ -1.0f,
+ 1.0f,
+ 0.0f,
+ 1.0f,
+ 1.0f,
+ -1.0f,
+ 1.0f,
+ 0.0f,
+ 1.0f,
+ 1.0f,
+ 1.0f,
+ 1.0f,
+ };
+
+ glGenVertexArrays( 1, &mQuadVAO );
+ glGenBuffers( 1, &mQuadVBO );
+ glBindVertexArray( mQuadVAO );
+ glBindBuffer( GL_ARRAY_BUFFER, mQuadVBO );
+ glBufferData( GL_ARRAY_BUFFER, sizeof( quadVertices ), quadVertices, GL_STATIC_DRAW );
+ glEnableVertexAttribArray( 0 );
+ glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof( float ), (void*)0 );
+ glEnableVertexAttribArray( 1 );
+ glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof( float ), (void*)( 2 * sizeof( float ) ) );
+ glBindVertexArray( 0 );
+
+ return true;
+}
+
+void SSAORenderer::initBuffers () {
+
+ if( mGBufferFBO == 0 )
+ glGenFramebuffers( 1, &mGBufferFBO );
+ glBindFramebuffer( GL_FRAMEBUFFER, mGBufferFBO );
+
+ if( mGNormal )
+ glDeleteTextures( 1, &mGNormal );
+ glGenTextures( 1, &mGNormal );
+ glBindTexture( GL_TEXTURE_2D, mGNormal );
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB16F, mWidth, mHeight, 0, GL_RGB, GL_FLOAT, nullptr );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mGNormal, 0 );
+
+ if( mGDepth )
+ glDeleteTextures( 1, &mGDepth );
+ glGenTextures( 1, &mGDepth );
+ glBindTexture( GL_TEXTURE_2D, mGDepth );
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, mWidth, mHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE );
+ glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, mGDepth, 0 );
+
+ GLenum attachments[1] = { GL_COLOR_ATTACHMENT0 };
+ glDrawBuffers( 1, attachments );
+
+ if( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE )
+ std::cerr << "GBuffer not complete" << std::endl;
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+
+ if( mSSAOFBO == 0 )
+ glGenFramebuffers( 1, &mSSAOFBO );
+ glBindFramebuffer( GL_FRAMEBUFFER, mSSAOFBO );
+ if( mSSAOTexture )
+ glDeleteTextures( 1, &mSSAOTexture );
+ glGenTextures( 1, &mSSAOTexture );
+ glBindTexture( GL_TEXTURE_2D, mSSAOTexture );
+ int w = mHalfRes ? std::max( 1, mWidth / 2 ) : mWidth;
+ int h = mHalfRes ? std::max( 1, mHeight / 2 ) : mHeight;
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mSSAOTexture, 0 );
+ if( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE )
+ std::cerr << "SSAO FBO not complete" << std::endl;
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+
+ if( mSSAOBlurFBO == 0 )
+ glGenFramebuffers( 1, &mSSAOBlurFBO );
+ glBindFramebuffer( GL_FRAMEBUFFER, mSSAOBlurFBO );
+ if( mSSAOBlurTexture )
+ glDeleteTextures( 1, &mSSAOBlurTexture );
+ glGenTextures( 1, &mSSAOBlurTexture );
+ glBindTexture( GL_TEXTURE_2D, mSSAOBlurTexture );
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mSSAOBlurTexture, 0 );
+ if( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE )
+ std::cerr << "SSAO Blur FBO not complete" << std::endl;
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+}
+
+void SSAORenderer::initKernelAndNoise () {
+ mKernel.clear();
+ std::uniform_real_distribution<float> randomFloats( 0.0f, 1.0f );
+ std::mt19937 gen;
+
+ for( int i = 0; i < mKernelSize; ++i ) {
+ glm::vec3 sample( randomFloats( gen ) * 2.0f - 1.0f, randomFloats( gen ) * 2.0f - 1.0f,
+ randomFloats( gen ) );
+ sample = glm::normalize( sample );
+ sample *= randomFloats( gen );
+ float scale = float( i ) / float( mKernelSize );
+ scale = glm::mix( 0.1f, 1.0f, scale * scale );
+ sample *= scale;
+ mKernel.push_back( sample );
+ }
+
+ mSSAOShader.use();
+ for( int i = 0; i < mKernelSize; ++i ) {
+ mSSAOShader.setVec3( std::string( "samples[" ) + std::to_string( i ) + std::string( "]" ), mKernel[i] );
+ }
+ mSSAOShader.setInt( "kernelSize", mKernelSize );
+
+ std::vector<float> noise;
+ for( int i = 0; i < 16; ++i ) {
+ float x = randomFloats( gen ) * 2.0f - 1.0f;
+ float y = randomFloats( gen ) * 2.0f - 1.0f;
+ noise.push_back( x );
+ noise.push_back( y );
+ noise.push_back( 0.0f );
+ }
+ if( mNoiseTexture )
+ glDeleteTextures( 1, &mNoiseTexture );
+ glGenTextures( 1, &mNoiseTexture );
+ glBindTexture( GL_TEXTURE_2D, mNoiseTexture );
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB32F, 4, 4, 0, GL_RGB, GL_FLOAT, noise.data() );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+}
+
+void SSAORenderer::resize ( int width, int height ) {
+ if( width == mWidth && height == mHeight )
+ return;
+ mWidth = width;
+ mHeight = height;
+ initBuffers();
+}
+
+void SSAORenderer::bindGBuffer () {
+ glBindFramebuffer( GL_FRAMEBUFFER, mGBufferFBO );
+ glViewport( 0, 0, mWidth, mHeight );
+ glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+}
+
+void SSAORenderer::unbind () {
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+}
+
+void SSAORenderer::computeSSAO ( const glm::mat4& proj, const glm::mat4& invProj, float radius, float bias,
+ float power ) {
+ int w = mHalfRes ? std::max( 1, mWidth / 2 ) : mWidth;
+ int h = mHalfRes ? std::max( 1, mHeight / 2 ) : mHeight;
+
+ glBindFramebuffer( GL_FRAMEBUFFER, mSSAOFBO );
+ glViewport( 0, 0, w, h );
+ glClear( GL_COLOR_BUFFER_BIT );
+
+ mSSAOShader.use();
+ mSSAOShader.setMat4( "proj", proj );
+ mSSAOShader.setMat4( "invProj", invProj );
+ mSSAOShader.setFloat( "radius", radius );
+ mSSAOShader.setFloat( "bias", bias );
+ mSSAOShader.setFloat( "power", power );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, mGDepth );
+ mSSAOShader.setInt( "gDepth", 0 );
+
+ glActiveTexture( GL_TEXTURE1 );
+ glBindTexture( GL_TEXTURE_2D, mGNormal );
+ mSSAOShader.setInt( "gNormal", 1 );
+
+ glActiveTexture( GL_TEXTURE2 );
+ glBindTexture( GL_TEXTURE_2D, mNoiseTexture );
+ mSSAOShader.setInt( "texNoise", 2 );
+
+ float noiseScaleVal = float( w ) / 4.0f;
+ mSSAOShader.setInt( "gDepth", 0 );
+ mSSAOShader.setInt( "gNormal", 1 );
+ mSSAOShader.setInt( "texNoise", 2 );
+ mSSAOShader.setFloat( "noiseScale", noiseScaleVal );
+ glBindVertexArray( mQuadVAO );
+ glDrawArrays( GL_TRIANGLES, 0, 6 );
+ glBindVertexArray( 0 );
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+}
+
+void SSAORenderer::blurSSAO () {
+ int w = mHalfRes ? std::max( 1, mWidth / 2 ) : mWidth;
+ int h = mHalfRes ? std::max( 1, mHeight / 2 ) : mHeight;
+
+ glBindFramebuffer( GL_FRAMEBUFFER, mSSAOBlurFBO );
+ glViewport( 0, 0, w, h );
+ glClear( GL_COLOR_BUFFER_BIT );
+
+ mBlurShader.use();
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, mSSAOTexture );
+ mBlurShader.setInt( "ssaoInput", 0 );
+ mBlurShader.setInt( "horizontal", 1 );
+ mBlurShader.setVec2( "texelSize", glm::vec2( 1.0f / float( w ), 1.0f / float( h ) ) );
+ glBindVertexArray( mQuadVAO );
+ glDrawArrays( GL_TRIANGLES, 0, 6 );
+ glBindVertexArray( 0 );
+
+ glBindFramebuffer( GL_FRAMEBUFFER, mSSAOFBO );
+ glViewport( 0, 0, w, h );
+ glClear( GL_COLOR_BUFFER_BIT );
+
+ mBlurShader.use();
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_2D, mSSAOBlurTexture );
+ mBlurShader.setInt( "ssaoInput", 0 );
+ mBlurShader.setInt( "horizontal", 0 );
+ mBlurShader.setVec2( "texelSize", glm::vec2( 1.0f / float( w ), 1.0f / float( h ) ) );
+ glBindVertexArray( mQuadVAO );
+ glDrawArrays( GL_TRIANGLES, 0, 6 );
+ glBindVertexArray( 0 );
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+}
+
+unsigned int SSAORenderer::getSSAOTextureID () const {
+ return mSSAOTexture;
+}
+
+void SSAORenderer::bindSSAOTextureToUnit ( int unit, renderer::Shader& shader, const std::string& uniformName ) const {
+ glActiveTexture( GL_TEXTURE0 + unit );
+ glBindTexture( GL_TEXTURE_2D, getSSAOTextureID() );
+ shader.setInt( uniformName, unit );
+}
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/SSAORenderer.hpp b/apps/openmb/renderer/SSAORenderer.hpp
new file mode 100644
index 0000000..3075252
--- /dev/null
+++ b/apps/openmb/renderer/SSAORenderer.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "Shader.hpp"
+#include <glm/glm.hpp>
+#include <vector>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#endif
+
+namespace renderer {
+class SSAORenderer {
+ public:
+ SSAORenderer();
+ ~SSAORenderer();
+
+ bool init( int width, int height, bool halfRes = true );
+ void resize( int width, int height );
+
+ void bindGBuffer();
+ void unbind();
+
+ void computeSSAO( const glm::mat4& proj, const glm::mat4& invProj, float radius, float bias, float power );
+ void blurSSAO();
+
+ unsigned int getSSAOTextureID() const;
+ void bindSSAOTextureToUnit( int unit, renderer::Shader& shader, const std::string& uniformName = "ssao" ) const;
+
+ renderer::Shader& getGBufferShader () { return mGBufferShader; }
+
+ private:
+ void initBuffers();
+ void initKernelAndNoise();
+
+ int mWidth;
+ int mHeight;
+ bool mHalfRes;
+
+ GLuint mGBufferFBO;
+ GLuint mGNormal;
+ GLuint mGDepth;
+
+ GLuint mSSAOFBO;
+ GLuint mSSAOTexture;
+
+ GLuint mSSAOBlurFBO;
+ GLuint mSSAOBlurTexture;
+
+ GLuint mNoiseTexture;
+
+ GLuint mQuadVAO;
+ GLuint mQuadVBO;
+
+ std::vector<glm::vec3> mKernel;
+ int mKernelSize;
+
+ renderer::Shader mGBufferShader;
+ renderer::Shader mSSAOShader;
+ renderer::Shader mBlurShader;
+};
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/Shader.cpp b/apps/openmb/renderer/Shader.cpp
new file mode 100644
index 0000000..a7c94e6
--- /dev/null
+++ b/apps/openmb/renderer/Shader.cpp
@@ -0,0 +1,137 @@
+#include "Shader.hpp"
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#endif
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+namespace renderer {
+Shader::Shader () : mID( 0 ) {}
+
+Shader::~Shader () {
+ if( mID ) {
+ glDeleteProgram( mID );
+ }
+}
+
+bool Shader::fromSource ( const std::string& vertexSrc, const std::string& fragmentSrc ) {
+ return compileShader( vertexSrc.c_str(), fragmentSrc.c_str() );
+}
+
+bool Shader::fromFiles ( const std::string& vertexPath, const std::string& fragmentPath ) {
+ std::string vs = readFile( vertexPath );
+ std::string fs = readFile( fragmentPath );
+ if( vs.empty() || fs.empty() ) {
+ return false;
+ }
+ return compileShader( vs.c_str(), fs.c_str() );
+}
+
+void Shader::use () const {
+ glUseProgram( mID );
+}
+
+unsigned int Shader::id () const {
+ return mID;
+}
+
+void Shader::setInt ( const std::string& name, int value ) const {
+ glUniform1i( glGetUniformLocation( mID, name.c_str() ), value );
+}
+
+void Shader::setFloat ( const std::string& name, float value ) const {
+ glUniform1f( glGetUniformLocation( mID, name.c_str() ), value );
+}
+
+void Shader::setVec3 ( const std::string& name, const glm::vec3& v ) const {
+ glUniform3f( glGetUniformLocation( mID, name.c_str() ), v.x, v.y, v.z );
+}
+
+void Shader::setVec2 ( const std::string& name, const glm::vec2& v ) const {
+ glUniform2f( glGetUniformLocation( mID, name.c_str() ), v.x, v.y );
+}
+
+void Shader::setMat4 ( const std::string& name, const glm::mat4& m ) const {
+ glUniformMatrix4fv( glGetUniformLocation( mID, name.c_str() ), 1, GL_FALSE, &m[0][0] );
+}
+
+bool Shader::compileShader ( const char* vSrc, const char* fSrc ) {
+ GLuint vert = glCreateShader( GL_VERTEX_SHADER );
+ glShaderSource( vert, 1, &vSrc, nullptr );
+ glCompileShader( vert );
+
+ GLint success = 0;
+ glGetShaderiv( vert, GL_COMPILE_STATUS, &success );
+ if( success == GL_FALSE ) {
+ GLint len = 0;
+ glGetShaderiv( vert, GL_INFO_LOG_LENGTH, &len );
+ std::string log( len, '\0' );
+ glGetShaderInfoLog( vert, len, &len, &log[0] );
+ std::cerr << "Vertex shader compile error:\n"
+ << log << std::endl;
+ std::cerr << "[Shader] Vertex shader ID = " << vert << std::endl;
+ glDeleteShader( vert );
+ return false;
+ }
+
+ GLuint frag = glCreateShader( GL_FRAGMENT_SHADER );
+
+ glShaderSource( frag, 1, &fSrc, nullptr );
+ glCompileShader( frag );
+
+ glGetShaderiv( frag, GL_COMPILE_STATUS, &success );
+ if( success == GL_FALSE ) {
+ GLint len = 0;
+ glGetShaderiv( frag, GL_INFO_LOG_LENGTH, &len );
+ std::string log( len, '\0' );
+ glGetShaderInfoLog( frag, len, &len, &log[0] );
+ std::cerr << "Fragment shader compile error:\n"
+ << log << std::endl;
+ glDeleteShader( frag );
+ glDeleteShader( vert );
+ return false;
+ }
+
+ mID = glCreateProgram();
+ glAttachShader( mID, vert );
+ glAttachShader( mID, frag );
+ glLinkProgram( mID );
+
+ glGetProgramiv( mID, GL_LINK_STATUS, &success );
+ if( success == GL_FALSE ) {
+ GLint len = 0;
+ glGetProgramiv( mID, GL_INFO_LOG_LENGTH, &len );
+ std::string log( len, '\0' );
+ glGetProgramInfoLog( mID, len, &len, &log[0] );
+ std::cerr << "Shader link error:\n"
+ << log << std::endl;
+ glDeleteProgram( mID );
+ mID = 0;
+ glDeleteShader( vert );
+ glDeleteShader( frag );
+ return false;
+ }
+
+ glDetachShader( mID, vert );
+ glDetachShader( mID, frag );
+ glDeleteShader( vert );
+ glDeleteShader( frag );
+
+ return true;
+}
+
+std::string Shader::readFile ( const std::string& path ) const {
+ std::ifstream in( path );
+ if( !in ) {
+ return std::string();
+ }
+ std::stringstream ss;
+ ss << in.rdbuf();
+ return ss.str();
+}
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/Shader.hpp b/apps/openmb/renderer/Shader.hpp
new file mode 100644
index 0000000..260e67e
--- /dev/null
+++ b/apps/openmb/renderer/Shader.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <glm/glm.hpp>
+#include <string>
+
+namespace renderer {
+class Shader {
+ public:
+ Shader();
+ ~Shader();
+
+ bool fromSource( const std::string& vertexSrc, const std::string& fragmentSrc );
+ bool fromFiles( const std::string& vertexPath, const std::string& fragmentPath );
+
+ void use() const;
+
+ unsigned int id() const;
+
+ void setInt( const std::string& name, int value ) const;
+ void setFloat( const std::string& name, float value ) const;
+ void setVec3( const std::string& name, const glm::vec3& v ) const;
+ void setVec2( const std::string& name, const glm::vec2& v ) const;
+ void setMat4( const std::string& name, const glm::mat4& m ) const;
+
+ private:
+ unsigned int mID;
+ bool compileShader( const char* vSrc, const char* fSrc );
+ std::string readFile( const std::string& path ) const;
+};
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/Skybox.cpp b/apps/openmb/renderer/Skybox.cpp
new file mode 100644
index 0000000..fea0021
--- /dev/null
+++ b/apps/openmb/renderer/Skybox.cpp
@@ -0,0 +1,155 @@
+#include "Skybox.hpp"
+
+#include "Shader.hpp"
+#include <stb_image.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <iostream>
+
+namespace Fs = std::filesystem;
+
+namespace renderer {
+Skybox::Skybox () : mTexID( 0 ), mVAO( 0 ), mVBO( 0 ), mInitialized( false ) {}
+
+Skybox::~Skybox () {
+ if( mVBO )
+ glDeleteBuffers( 1, &mVBO );
+ if( mVAO )
+ glDeleteVertexArrays( 1, &mVAO );
+ if( mTexID )
+ glDeleteTextures( 1, &mTexID );
+}
+
+static std::string toLower ( const std::string& s ) {
+ std::string out = s;
+ std::transform( out.begin(), out.end(), out.begin(), [] ( unsigned char c ) { return std::tolower( c ); } );
+ return out;
+}
+
+bool Skybox::loadFromDirectory ( const std::string& dirPath ) {
+
+ std::vector<std::string> faceTokens = { "right", "left", "top", "bottom", "front", "back" };
+ std::vector<std::string> faces( 6 );
+
+ if( !Fs::exists( dirPath ) || !Fs::is_directory( dirPath ) ) {
+ std::cerr << "Skybox directory does not exist: " << dirPath << std::endl;
+ return false;
+ }
+
+ for( auto& entry : Fs::directory_iterator( dirPath ) ) {
+ if( !entry.is_regular_file() )
+ continue;
+ auto name = entry.path().filename().string();
+ std::string lname = toLower( name );
+ for( size_t i = 0; i < faceTokens.size(); ++i ) {
+ if( lname.find( faceTokens[i] ) != std::string::npos ) {
+ faces[i] = entry.path().string();
+ }
+ }
+ }
+
+ for( size_t i = 0; i < faces.size(); ++i ) {
+ if( faces[i].empty() ) {
+ std::cerr << "Missing skybox face for token: " << faceTokens[i] << std::endl;
+ return false;
+ }
+ }
+
+ return loadFaces( faces );
+}
+
+bool Skybox::loadFaces ( const std::vector<std::string>& faces ) {
+ glGenTextures( 1, &mTexID );
+ glBindTexture( GL_TEXTURE_CUBE_MAP, mTexID );
+
+ stbi_set_flip_vertically_on_load( false );
+ for( unsigned i = 0; i < faces.size(); ++i ) {
+ int w, h, ch;
+ unsigned char* data = stbi_load( faces[i].c_str(), &w, &h, &ch, 0 );
+ if( !data ) {
+ std::cerr << "Failed to load skybox face: " << faces[i] << std::endl;
+ glBindTexture( GL_TEXTURE_CUBE_MAP, 0 );
+ return false;
+ }
+ GLenum format = ( ch == 4 ) ? GL_RGBA : GL_RGB;
+ glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, data );
+ stbi_image_free( data );
+ }
+
+ glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+ glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE );
+
+ glBindTexture( GL_TEXTURE_CUBE_MAP, 0 );
+
+ initMesh();
+ mInitialized = true;
+ return true;
+}
+
+void Skybox::initMesh () {
+
+ float skyboxVertices[] = { -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
+
+ -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f,
+
+ 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
+
+ -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f,
+
+ -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f,
+
+ -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f };
+
+ glGenVertexArrays( 1, &mVAO );
+ glGenBuffers( 1, &mVBO );
+ glBindVertexArray( mVAO );
+ glBindBuffer( GL_ARRAY_BUFFER, mVBO );
+ glBufferData( GL_ARRAY_BUFFER, sizeof( skyboxVertices ), &skyboxVertices, GL_STATIC_DRAW );
+ glEnableVertexAttribArray( 0 );
+ glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof( float ), (void*)0 );
+ glBindVertexArray( 0 );
+}
+
+void Skybox::draw ( const glm::mat4& view, const glm::mat4& proj ) {
+ if( !mInitialized )
+ return;
+
+ static Shader s;
+ static bool shaderLoaded = false;
+ if( !shaderLoaded ) {
+ s.fromFiles( "apps/openmb/resources/shaders/skybox.vert", "apps/openmb/resources/shaders/skybox.frag" );
+ shaderLoaded = true;
+ }
+
+ glDepthFunc( GL_LEQUAL );
+ glDepthMask( GL_FALSE );
+
+ s.use();
+
+ glm::mat4 viewNoTrans = glm::mat4( glm::mat3( view ) );
+ s.setMat4( "view", viewNoTrans );
+ s.setMat4( "proj", proj );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glBindTexture( GL_TEXTURE_CUBE_MAP, mTexID );
+ s.setInt( "skybox", 0 );
+
+ glBindVertexArray( mVAO );
+ glDrawArrays( GL_TRIANGLES, 0, 36 );
+ glBindVertexArray( 0 );
+
+ glDepthMask( GL_TRUE );
+ glDepthFunc( GL_LESS );
+}
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/Skybox.hpp b/apps/openmb/renderer/Skybox.hpp
new file mode 100644
index 0000000..656494f
--- /dev/null
+++ b/apps/openmb/renderer/Skybox.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <glm/glm.hpp>
+#include <string>
+#include <vector>
+
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#else
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+#endif
+
+namespace renderer {
+class Skybox {
+ public:
+ Skybox();
+ ~Skybox();
+
+ bool loadFromDirectory( const std::string& dirPath );
+
+ void draw( const glm::mat4& view, const glm::mat4& proj );
+
+ private:
+ bool loadFaces( const std::vector<std::string>& faces );
+ void initMesh();
+
+ GLuint mTexID;
+ GLuint mVAO;
+ GLuint mVBO;
+ bool mInitialized;
+};
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/Texture.cpp b/apps/openmb/renderer/Texture.cpp
new file mode 100644
index 0000000..e551f86
--- /dev/null
+++ b/apps/openmb/renderer/Texture.cpp
@@ -0,0 +1,53 @@
+#define STB_IMAGE_IMPLEMENTATION
+#include <stb_image.h>
+
+#include "Texture.hpp"
+
+#include <iostream>
+
+namespace renderer {
+Texture::Texture () : mID( 0 ) {}
+
+Texture::~Texture () {
+ if( mID )
+ glDeleteTextures( 1, &mID );
+}
+
+bool Texture::loadFromFile ( const std::string& path, bool flip ) {
+ stbi_set_flip_vertically_on_load( flip );
+ int w, h, channels;
+ unsigned char* data = stbi_load( path.c_str(), &w, &h, &channels, 0 );
+ if( !data ) {
+ std::cerr << "Failed to load texture: " << path << std::endl;
+ return false;
+ }
+
+ GLenum format = GL_RGB;
+ if( channels == 1 )
+ format = GL_RED;
+ else if( channels == 3 )
+ format = GL_RGB;
+ else if( channels == 4 )
+ format = GL_RGBA;
+
+ glGenTextures( 1, &mID );
+ glBindTexture( GL_TEXTURE_2D, mID );
+ glTexImage2D( GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, data );
+ glGenerateMipmap( GL_TEXTURE_2D );
+
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+
+ stbi_image_free( data );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+ return true;
+}
+
+void Texture::bind ( unsigned unit ) const {
+ glActiveTexture( GL_TEXTURE0 + unit );
+ glBindTexture( GL_TEXTURE_2D, mID );
+}
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/Texture.hpp b/apps/openmb/renderer/Texture.hpp
new file mode 100644
index 0000000..9acb5cd
--- /dev/null
+++ b/apps/openmb/renderer/Texture.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#else
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+#ifdef __APPLE__
+#include <OpenGL/gl3.h>
+#endif
+#endif
+#include <string>
+
+namespace renderer {
+class Texture {
+ public:
+ Texture();
+ ~Texture();
+
+ bool loadFromFile( const std::string& path, bool flip = true );
+ void bind( unsigned unit = 0 ) const;
+ GLuint id () const { return mID; }
+
+ private:
+ GLuint mID;
+};
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/TextureManager.cpp b/apps/openmb/renderer/TextureManager.cpp
new file mode 100644
index 0000000..bfd7e15
--- /dev/null
+++ b/apps/openmb/renderer/TextureManager.cpp
@@ -0,0 +1,163 @@
+#include "TextureManager.hpp"
+
+#include <algorithm>
+#include <iostream>
+
+namespace renderer {
+TextureManager::TextureManager ()
+ : mBasePath(), mTextures(), mCurrentCategory(), mCurrentSubcategory(), mCurrentTextureName(),
+ mCurrentTexture( nullptr ), mCurrentTextureId( 0 ), mTextureIdToPath(), mNextTextureId( 1 ) {
+}
+
+void TextureManager::scanDirectory ( const std::string& basePath ) {
+ mBasePath = basePath;
+ mTextures.clear();
+
+ if( !std::filesystem::exists( basePath ) ) {
+ std::cerr << "TextureManager: Base path does not exist: " << basePath << std::endl;
+ return;
+ }
+
+ for( const auto& categoryEntry : std::filesystem::directory_iterator( basePath ) ) {
+ if( !categoryEntry.is_directory() )
+ continue;
+
+ std::string categoryName = categoryEntry.path().filename().string();
+
+ for( const auto& subcategoryEntry : std::filesystem::directory_iterator( categoryEntry.path() ) ) {
+ if( !subcategoryEntry.is_directory() )
+ continue;
+
+ std::string subcategoryName = subcategoryEntry.path().filename().string();
+
+ for( const auto& fileEntry : std::filesystem::directory_iterator( subcategoryEntry.path() ) ) {
+ if( !fileEntry.is_regular_file() )
+ continue;
+
+ std::string fileName = fileEntry.path().filename().string();
+ std::string extension = fileEntry.path().extension().string();
+
+ if( extension != ".png" && extension != ".jpg" && extension != ".jpeg" && extension != ".bmp" &&
+ extension != ".tga" )
+ continue;
+
+ TextureEntry entry;
+ entry.mFullPath = fileEntry.path().string();
+ entry.mLoaded = false;
+
+ mTextures[categoryName][subcategoryName][fileName] = entry;
+
+ int textureId = mNextTextureId++;
+ mTextureIdToPath[textureId] = std::make_tuple( categoryName, subcategoryName, fileName );
+ }
+ }
+ }
+
+ std::cout << "TextureManager: Scanned " << mTextures.size() << " categories" << std::endl;
+}
+
+std::vector<std::string> TextureManager::getCategories () const {
+ std::vector<std::string> categories;
+ for( const auto& [category, _] : mTextures ) {
+ categories.push_back( category );
+ }
+ std::sort( categories.begin(), categories.end() );
+ return categories;
+}
+
+std::vector<std::string> TextureManager::getSubcategories ( const std::string& category ) const {
+ std::vector<std::string> subcategories;
+ auto catIt = mTextures.find( category );
+ if( catIt != mTextures.end() ) {
+ for( const auto& [subcategory, _] : catIt->second ) {
+ subcategories.push_back( subcategory );
+ }
+ std::sort( subcategories.begin(), subcategories.end() );
+ }
+ return subcategories;
+}
+
+std::vector<std::string> TextureManager::getTextureNames ( const std::string& category,
+ const std::string& subcategory ) const {
+ std::vector<std::string> textureNames;
+ auto catIt = mTextures.find( category );
+ if( catIt != mTextures.end() ) {
+ auto subIt = catIt->second.find( subcategory );
+ if( subIt != catIt->second.end() ) {
+ for( const auto& [textureName, _] : subIt->second ) {
+ textureNames.push_back( textureName );
+ }
+ std::sort( textureNames.begin(), textureNames.end() );
+ }
+ }
+ return textureNames;
+}
+
+Texture* TextureManager::getTexture ( const std::string& category, const std::string& subcategory,
+ const std::string& textureName ) {
+ auto catIt = mTextures.find( category );
+ if( catIt == mTextures.end() )
+ return nullptr;
+
+ auto subIt = catIt->second.find( subcategory );
+ if( subIt == catIt->second.end() )
+ return nullptr;
+
+ auto texIt = subIt->second.find( textureName );
+ if( texIt == subIt->second.end() )
+ return nullptr;
+
+ TextureEntry& entry = texIt->second;
+
+ if( !entry.mLoaded ) {
+ if( entry.mTexture.loadFromFile( entry.mFullPath ) ) {
+ entry.mLoaded = true;
+ } else {
+ std::cerr << "TextureManager: Failed to load texture: " << entry.mFullPath << std::endl;
+ return nullptr;
+ }
+ }
+
+ return &entry.mTexture;
+}
+
+Texture* TextureManager::getCurrentTexture () {
+ if( mCurrentTexture )
+ return mCurrentTexture;
+
+ if( !mCurrentCategory.empty() && !mCurrentSubcategory.empty() && !mCurrentTextureName.empty() ) {
+ mCurrentTexture = getTexture( mCurrentCategory, mCurrentSubcategory, mCurrentTextureName );
+ }
+
+ return mCurrentTexture;
+}
+
+void TextureManager::setCurrentTexture ( const std::string& category, const std::string& subcategory,
+ const std::string& textureName ) {
+ mCurrentCategory = category;
+ mCurrentSubcategory = subcategory;
+ mCurrentTextureName = textureName;
+ mCurrentTexture = getTexture( category, subcategory, textureName );
+
+ for( const auto& [id, path] : mTextureIdToPath ) {
+ if( std::get<0>( path ) == category && std::get<1>( path ) == subcategory &&
+ std::get<2>( path ) == textureName ) {
+ mCurrentTextureId = id;
+ break;
+ }
+ }
+}
+
+int TextureManager::getCurrentTextureId () const {
+ return mCurrentTextureId;
+}
+
+Texture* TextureManager::getTextureById ( int textureId ) {
+ auto it = mTextureIdToPath.find( textureId );
+ if( it == mTextureIdToPath.end() )
+ return nullptr;
+
+ const auto& [category, subcategory, textureName] = it->second;
+ return getTexture( category, subcategory, textureName );
+}
+} // namespace renderer
diff --git a/apps/openmb/renderer/TextureManager.hpp b/apps/openmb/renderer/TextureManager.hpp
new file mode 100644
index 0000000..f3e183f
--- /dev/null
+++ b/apps/openmb/renderer/TextureManager.hpp
@@ -0,0 +1,64 @@
+#ifndef OPENMB_APPS_OPENMB_RENDERER_TEXTUREMANAGER_H
+#define OPENMB_APPS_OPENMB_RENDERER_TEXTUREMANAGER_H
+
+#include "Texture.hpp"
+
+#include <filesystem>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace renderer {
+
+class TextureManager {
+ public:
+ TextureManager();
+ ~TextureManager() = default;
+
+ void scanDirectory( const std::string& basePath );
+
+ std::vector<std::string> getCategories() const;
+
+ std::vector<std::string> getSubcategories( const std::string& category ) const;
+
+ std::vector<std::string> getTextureNames( const std::string& category, const std::string& subcategory ) const;
+
+ Texture* getTexture( const std::string& category, const std::string& subcategory,
+ const std::string& textureName );
+
+ Texture* getCurrentTexture();
+
+ void setCurrentTexture( const std::string& category, const std::string& subcategory,
+ const std::string& textureName );
+
+ const std::string& getCurrentCategory () const { return mCurrentCategory; }
+ const std::string& getCurrentSubcategory () const { return mCurrentSubcategory; }
+ const std::string& getCurrentTextureName () const { return mCurrentTextureName; }
+
+ int getCurrentTextureId() const;
+
+ Texture* getTextureById( int textureId );
+
+ private:
+ struct TextureEntry {
+ std::string mFullPath;
+ Texture mTexture;
+ bool mLoaded;
+ };
+
+ std::string mBasePath;
+
+ std::map<std::string, std::map<std::string, std::map<std::string, TextureEntry>>> mTextures;
+
+ std::string mCurrentCategory;
+ std::string mCurrentSubcategory;
+ std::string mCurrentTextureName;
+ Texture* mCurrentTexture;
+ int mCurrentTextureId;
+
+ std::map<int, std::tuple<std::string, std::string, std::string>> mTextureIdToPath;
+ int mNextTextureId;
+};
+} // namespace renderer
+
+#endif
diff --git a/apps/openmb/renderer/primitives.cpp b/apps/openmb/renderer/primitives.cpp
new file mode 100644
index 0000000..245f7e1
--- /dev/null
+++ b/apps/openmb/renderer/primitives.cpp
@@ -0,0 +1,595 @@
+#include "primitives.hpp"
+
+#include <vector>
+
+#include <glm/glm.hpp>
+
+namespace renderer
+{
+ namespace primitives
+ {
+ Mesh makeCube()
+ {
+
+ std::vector< float > verts = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f,
+
+ -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f,
+
+ -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f,
+ -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f,
+
+ 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f,
+
+ -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f,
+
+ -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f,
+ 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f };
+
+ Mesh m;
+ m.createFromPositions( verts, false );
+ return m;
+ }
+
+ Mesh makeTexturedCube()
+ {
+ std::vector< float > verts;
+
+ verts.insert( verts.end(), { -0.5f, -0.5f, 0.5f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, 0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, 0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, 0.5f, 0.0f, 0.0f } );
+
+ verts.insert( verts.end(), { 0.5f, -0.5f, -0.5f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, -0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, -0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, -0.5f, 0.0f, 0.0f } );
+
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, 0.5f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, -0.5f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f } );
+
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, -0.5f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, 0.5f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 0.0f, 0.0f } );
+
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, 0.5f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, -0.5f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 0.0f, 0.0f } );
+
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, -0.5f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 1.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, 0.5f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f } );
+
+ Mesh m;
+ m.createFromPosTex( verts );
+ return m;
+ }
+
+ Mesh makeGrid( int halfSize, float spacing )
+ {
+ std::vector< float > verts;
+ for ( int i = -halfSize; i <= halfSize; ++i )
+ {
+ float x = i * spacing;
+ verts.push_back( x );
+ verts.push_back( 0.0f );
+ verts.push_back( -halfSize * spacing );
+ verts.push_back( x );
+ verts.push_back( 0.0f );
+ verts.push_back( halfSize * spacing );
+ }
+
+ for ( int j = -halfSize; j <= halfSize; ++j )
+ {
+ float z = j * spacing;
+ verts.push_back( -halfSize * spacing );
+ verts.push_back( 0.0f );
+ verts.push_back( z );
+ verts.push_back( halfSize * spacing );
+ verts.push_back( 0.0f );
+ verts.push_back( z );
+ }
+
+ Mesh m;
+ m.createFromPositions( verts, true );
+ return m;
+ }
+
+ Mesh makeTexturedGrid( int width, int depth, float tileSize )
+ {
+ std::vector< float > verts;
+
+ for ( int z = 0; z < depth; ++z )
+ {
+ for ( int x = 0; x < width; ++x )
+ {
+ float x0 = ( x - width * 0.5f ) * tileSize;
+ float z0 = ( z - depth * 0.5f ) * tileSize;
+ float x1 = x0 + tileSize;
+ float z1 = z0 + tileSize;
+
+ verts.push_back( x0 );
+ verts.push_back( 0.0f );
+ verts.push_back( z0 );
+ verts.push_back( 0.0f );
+ verts.push_back( 0.0f );
+
+ verts.push_back( x1 );
+ verts.push_back( 0.0f );
+ verts.push_back( z0 );
+ verts.push_back( 1.0f );
+ verts.push_back( 0.0f );
+
+ verts.push_back( x0 );
+ verts.push_back( 0.0f );
+ verts.push_back( z1 );
+ verts.push_back( 0.0f );
+ verts.push_back( 1.0f );
+
+ verts.push_back( x0 );
+ verts.push_back( 0.0f );
+ verts.push_back( z1 );
+ verts.push_back( 0.0f );
+ verts.push_back( 1.0f );
+
+ verts.push_back( x1 );
+ verts.push_back( 0.0f );
+ verts.push_back( z0 );
+ verts.push_back( 1.0f );
+ verts.push_back( 0.0f );
+
+ verts.push_back( x1 );
+ verts.push_back( 0.0f );
+ verts.push_back( z1 );
+ verts.push_back( 1.0f );
+ verts.push_back( 1.0f );
+ }
+ }
+
+ Mesh m;
+ m.createFromPosTex( verts );
+ return m;
+ }
+
+ Mesh makeTexturedCubeGrid( int width, int depth, float tileSize )
+ {
+ std::vector< float > verts;
+
+ auto pushFace = [&]( const std::array< float, 18 > &pos, const std::array< float, 12 > &uv, float cx,
+ float cy, float cz, float scale )
+ {
+ for ( int i = 0; i < 6; ++i )
+ {
+ int pi = i * 3;
+ int ui = i * 2;
+ verts.push_back( cx + pos[pi + 0] * scale );
+ verts.push_back( cy + pos[pi + 1] * scale );
+ verts.push_back( cz + pos[pi + 2] * scale );
+ verts.push_back( uv[ui + 0] );
+ verts.push_back( uv[ui + 1] );
+ }
+ };
+
+ const std::array< float, 18 > topPos = { -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f };
+ const std::array< float, 12 > topUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > bottomPos = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > bottomUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > frontPos = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > frontUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > backPos = { 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f,
+ -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f };
+ const std::array< float, 12 > backUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > leftPos = { -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f };
+ const std::array< float, 12 > leftUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > rightPos = { 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > rightUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ for ( int z = 0; z < depth; ++z )
+ {
+ for ( int x = 0; x < width; ++x )
+ {
+ float cx = ( x - width * 0.5f ) * tileSize + tileSize * 0.5f;
+ float cz = ( z - depth * 0.5f ) * tileSize + tileSize * 0.5f;
+
+ float cy = -0.5f * tileSize;
+ float scale = tileSize;
+
+ pushFace( topPos, topUV, cx, cy, cz, scale );
+ pushFace( bottomPos, bottomUV, cx, cy, cz, scale );
+ pushFace( frontPos, frontUV, cx, cy, cz, scale );
+ pushFace( backPos, backUV, cx, cy, cz, scale );
+ pushFace( leftPos, leftUV, cx, cy, cz, scale );
+ pushFace( rightPos, rightUV, cx, cy, cz, scale );
+ }
+ }
+
+ Mesh m;
+ m.createFromPosTex( verts );
+ return m;
+ }
+
+ Mesh makeTexturedWall( int length, int height, float tileSize, bool alongX, float fixedCoord )
+ {
+ std::vector< float > verts;
+
+ auto pushFace = [&]( const std::array< float, 18 > &pos, const std::array< float, 12 > &uv, float cx,
+ float cy, float cz, float scale )
+ {
+ for ( int i = 0; i < 6; ++i )
+ {
+ int pi = i * 3;
+ int ui = i * 2;
+ verts.push_back( cx + pos[pi + 0] * scale );
+ verts.push_back( cy + pos[pi + 1] * scale );
+ verts.push_back( cz + pos[pi + 2] * scale );
+ verts.push_back( uv[ui + 0] );
+ verts.push_back( uv[ui + 1] );
+ }
+ };
+
+ const std::array< float, 18 > topPos = { -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f };
+ const std::array< float, 12 > topUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > bottomPos = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > bottomUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > frontPos = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > frontUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > backPos = { 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f,
+ -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f };
+ const std::array< float, 12 > backUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > leftPos = { -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f };
+ const std::array< float, 12 > leftUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > rightPos = { 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > rightUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ if ( alongX )
+ {
+
+ for ( int lx = 0; lx < length; ++lx )
+ {
+ float cx = ( lx - length * 0.5f ) * tileSize + tileSize * 0.5f;
+ float cz = fixedCoord;
+ for ( int hy = 0; hy < height; ++hy )
+ {
+ float cy = 0.02f * tileSize + hy * tileSize;
+ float scale = tileSize;
+ pushFace( topPos, topUV, cx, cy, cz, scale );
+ pushFace( bottomPos, bottomUV, cx, cy, cz, scale );
+ pushFace( frontPos, frontUV, cx, cy, cz, scale );
+ pushFace( backPos, backUV, cx, cy, cz, scale );
+ pushFace( leftPos, leftUV, cx, cy, cz, scale );
+ pushFace( rightPos, rightUV, cx, cy, cz, scale );
+ }
+ }
+ }
+ else
+ {
+
+ for ( int lz = 0; lz < length; ++lz )
+ {
+ float cz = ( lz - length * 0.5f ) * tileSize + tileSize * 0.5f;
+ float cx = fixedCoord;
+ for ( int hy = 0; hy < height; ++hy )
+ {
+ float cy = 0.02f * tileSize + hy * tileSize;
+ float scale = tileSize;
+ pushFace( topPos, topUV, cx, cy, cz, scale );
+ pushFace( bottomPos, bottomUV, cx, cy, cz, scale );
+ pushFace( frontPos, frontUV, cx, cy, cz, scale );
+ pushFace( backPos, backUV, cx, cy, cz, scale );
+ pushFace( leftPos, leftUV, cx, cy, cz, scale );
+ pushFace( rightPos, rightUV, cx, cy, cz, scale );
+ }
+ }
+ }
+
+ Mesh m;
+ m.createFromPosTex( verts );
+ return m;
+ }
+
+ Mesh makeTexturedCubeWithNormals()
+ {
+ std::vector< float > verts;
+
+ verts.insert( verts.end(), { -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } );
+
+ verts.insert( verts.end(), { 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, -1.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, -1.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f } );
+
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, 0.5f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f } );
+
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f } );
+
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f } );
+
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f } );
+ verts.insert( verts.end(), { 0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f } );
+ verts.insert( verts.end(), { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f } );
+
+ Mesh m;
+ m.createFromPosTexNormal( verts );
+ return m;
+ }
+
+ Mesh makeTexturedGridWithNormals( int width, int depth, float tileSize )
+ {
+ std::vector< float > verts;
+
+ for ( int z = 0; z < depth; ++z )
+ {
+ for ( int x = 0; x < width; ++x )
+ {
+ float x0 = ( x - width * 0.5f ) * tileSize;
+ float z0 = ( z - depth * 0.5f ) * tileSize;
+ float x1 = x0 + tileSize;
+ float z1 = z0 + tileSize;
+
+ verts.insert( verts.end(), { x0, 0.0f, z0, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { x1, 0.0f, z0, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { x0, 0.0f, z1, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f } );
+
+ verts.insert( verts.end(), { x0, 0.0f, z1, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { x1, 0.0f, z0, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f } );
+ verts.insert( verts.end(), { x1, 0.0f, z1, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f } );
+ }
+ }
+
+ Mesh m;
+ m.createFromPosTexNormal( verts );
+ return m;
+ }
+
+ Mesh makeTexturedCubeGridWithNormals( int width, int depth, float tileSize )
+ {
+ std::vector< float > verts;
+
+ auto pushFaceWithNormal = [&]( const std::array< float, 18 > &pos, const std::array< float, 12 > &uv,
+ const glm::vec3 &normal, float cx, float cy, float cz, float scale )
+ {
+ for ( int i = 0; i < 6; ++i )
+ {
+ int pi = i * 3;
+ int ui = i * 2;
+ verts.push_back( cx + pos[pi + 0] * scale );
+ verts.push_back( cy + pos[pi + 1] * scale );
+ verts.push_back( cz + pos[pi + 2] * scale );
+ verts.push_back( uv[ui + 0] );
+ verts.push_back( uv[ui + 1] );
+ verts.push_back( normal.x );
+ verts.push_back( normal.y );
+ verts.push_back( normal.z );
+ }
+ };
+
+ const std::array< float, 18 > topPos = { -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f };
+ const std::array< float, 12 > topUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > bottomPos = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > bottomUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > frontPos = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > frontUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > backPos = { 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f,
+ -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f };
+ const std::array< float, 12 > backUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > leftPos = { -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f };
+ const std::array< float, 12 > leftUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > rightPos = { 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > rightUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ for ( int z = 0; z < depth; ++z )
+ {
+ for ( int x = 0; x < width; ++x )
+ {
+ float cx = ( x - width * 0.5f ) * tileSize + tileSize * 0.5f;
+ float cz = ( z - depth * 0.5f ) * tileSize + tileSize * 0.5f;
+
+ float cy = -0.5f * tileSize;
+ float scale = tileSize;
+
+ pushFaceWithNormal( topPos, topUV, glm::vec3( 0.0f, 1.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( bottomPos, bottomUV, glm::vec3( 0.0f, -1.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( frontPos, frontUV, glm::vec3( 0.0f, 0.0f, 1.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( backPos, backUV, glm::vec3( 0.0f, 0.0f, -1.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( leftPos, leftUV, glm::vec3( -1.0f, 0.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( rightPos, rightUV, glm::vec3( 1.0f, 0.0f, 0.0f ), cx, cy, cz, scale );
+ }
+ }
+
+ Mesh m;
+ m.createFromPosTexNormal( verts );
+ return m;
+ }
+
+ Mesh makeTexturedWallWithNormals( int length, int height, float tileSize, bool alongX, float fixedCoord )
+ {
+ std::vector< float > verts;
+
+ auto pushFaceWithNormal = [&]( const std::array< float, 18 > &pos, const std::array< float, 12 > &uv,
+ const glm::vec3 &normal, float cx, float cy, float cz, float scale )
+ {
+ for ( int i = 0; i < 6; ++i )
+ {
+ int pi = i * 3;
+ int ui = i * 2;
+ verts.push_back( cx + pos[pi + 0] * scale );
+ verts.push_back( cy + pos[pi + 1] * scale );
+ verts.push_back( cz + pos[pi + 2] * scale );
+ verts.push_back( uv[ui + 0] );
+ verts.push_back( uv[ui + 1] );
+ verts.push_back( normal.x );
+ verts.push_back( normal.y );
+ verts.push_back( normal.z );
+ }
+ };
+
+ const std::array< float, 18 > topPos = { -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f };
+ const std::array< float, 12 > topUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > bottomPos = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > bottomUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > frontPos = { -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > frontUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > backPos = { 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f,
+ -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f };
+ const std::array< float, 12 > backUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > leftPos = { -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f };
+ const std::array< float, 12 > leftUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ const std::array< float, 18 > rightPos = { 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f };
+ const std::array< float, 12 > rightUV = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f };
+
+ if ( alongX )
+ {
+ for ( int lx = 0; lx < length; ++lx )
+ {
+ float cx = ( lx - length * 0.5f ) * tileSize + tileSize * 0.5f;
+ float cz = fixedCoord;
+ for ( int hy = 0; hy < height; ++hy )
+ {
+ float cy = hy * tileSize + tileSize * 0.5f;
+ float scale = tileSize;
+ pushFaceWithNormal( topPos, topUV, glm::vec3( 0.0f, 1.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( bottomPos, bottomUV, glm::vec3( 0.0f, -1.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( frontPos, frontUV, glm::vec3( 0.0f, 0.0f, 1.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( backPos, backUV, glm::vec3( 0.0f, 0.0f, -1.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( leftPos, leftUV, glm::vec3( -1.0f, 0.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( rightPos, rightUV, glm::vec3( 1.0f, 0.0f, 0.0f ), cx, cy, cz, scale );
+ }
+ }
+ }
+ else
+ {
+ for ( int lz = 0; lz < length; ++lz )
+ {
+ float cz = ( lz - length * 0.5f ) * tileSize + tileSize * 0.5f;
+ float cx = fixedCoord;
+ for ( int hy = 0; hy < height; ++hy )
+ {
+ float cy = hy * tileSize + tileSize * 0.5f;
+ float scale = tileSize;
+ pushFaceWithNormal( topPos, topUV, glm::vec3( 0.0f, 1.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( bottomPos, bottomUV, glm::vec3( 0.0f, -1.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( frontPos, frontUV, glm::vec3( 0.0f, 0.0f, 1.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( backPos, backUV, glm::vec3( 0.0f, 0.0f, -1.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( leftPos, leftUV, glm::vec3( -1.0f, 0.0f, 0.0f ), cx, cy, cz, scale );
+ pushFaceWithNormal( rightPos, rightUV, glm::vec3( 1.0f, 0.0f, 0.0f ), cx, cy, cz, scale );
+ }
+ }
+ }
+
+ Mesh m;
+ m.createFromPosTexNormal( verts );
+ return m;
+ }
+
+ } // namespace primitives
+
+} // namespace renderer
diff --git a/apps/openmb/renderer/primitives.hpp b/apps/openmb/renderer/primitives.hpp
new file mode 100644
index 0000000..bd07c02
--- /dev/null
+++ b/apps/openmb/renderer/primitives.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "Mesh.hpp"
+
+namespace renderer
+{
+ namespace primitives
+ {
+ Mesh makeCube();
+ Mesh makeTexturedCube();
+ Mesh makeTexturedCubeWithNormals();
+ Mesh makeGrid( int halfSize, float spacing );
+ Mesh makeTexturedGrid( int width, int depth, float tileSize );
+ Mesh makeTexturedGridWithNormals( int width, int depth, float tileSize );
+ Mesh makeTexturedCubeGrid( int width, int depth, float tileSize );
+ Mesh makeTexturedCubeGridWithNormals( int width, int depth, float tileSize );
+ Mesh makeTexturedWall( int length, int height, float tileSize, bool alongX = true, float fixedCoord = 0.0f );
+ Mesh makeTexturedWallWithNormals( int length, int height, float tileSize, bool alongX = true,
+ float fixedCoord = 0.0f );
+ } // namespace primitives
+
+} // namespace renderer