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 | |
Diffstat (limited to 'apps/openmb/renderer')
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 |