From a2bef731913df6b994f0d58adf4f2adebfd71566 Mon Sep 17 00:00:00 2001 From: tovjemam Date: Fri, 29 Aug 2025 22:22:02 +0200 Subject: [PATCH] Prepare for mesh rendering --- CMakeLists.txt | 10 ++- src/assets/animation.cpp | 2 +- src/assets/animation.hpp | 2 +- src/assets/mesh.cpp | 56 +++++++++++---- src/assets/mesh.hpp | 18 +++-- src/assets/sectordef.cpp | 2 +- src/assets/sectordef.hpp | 6 +- src/assets/skeleton.cpp | 60 +++++++++++----- src/assets/skeleton.hpp | 12 +++- src/game/meshinstance.cpp | 137 ++++++++++++++++++++++++++++++++++++ src/game/meshinstance.hpp | 45 ++++++++++++ src/game/player.cpp | 32 ++++++++- src/game/player.hpp | 8 +++ src/game/sector.hpp | 4 +- src/game/transform_node.hpp | 29 ++++++++ src/game/world.hpp | 4 ++ src/gfx/renderer.cpp | 105 +++++++++++++++------------ src/gfx/renderer.hpp | 21 +++++- src/gfx/shader.cpp | 12 ++++ src/gfx/shader.hpp | 3 + src/gfx/shader_sources.cpp | 58 ++++++++++++++- src/gfx/shader_sources.hpp | 3 + src/utils/transform.hpp | 21 ++++-- 23 files changed, 542 insertions(+), 108 deletions(-) create mode 100644 src/game/meshinstance.cpp create mode 100644 src/game/meshinstance.hpp create mode 100644 src/game/transform_node.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a2c620..3f5c272 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,13 +12,19 @@ add_executable(PortalGame "src/app.cpp" "src/gl.hpp" "src/utils.hpp" - "src/assets/sectordef.hpp" - "src/assets/sectordef.cpp" + "src/assets/animation.hpp" + "src/assets/animation.cpp" "src/assets/mesh.hpp" "src/assets/mesh.cpp" + "src/assets/sectordef.hpp" + "src/assets/sectordef.cpp" + "src/assets/skeleton.hpp" + "src/assets/skeleton.cpp" "src/collision/trianglemesh.cpp" "src/game/entity.hpp" "src/game/entity.cpp" + "src/game/meshinstance.hpp" + "src/game/meshinstance.cpp" "src/game/player_input.hpp" "src/game/player.hpp" "src/game/player.cpp" diff --git a/src/assets/animation.cpp b/src/assets/animation.cpp index 37d2ef9..91adef8 100644 --- a/src/assets/animation.cpp +++ b/src/assets/animation.cpp @@ -5,7 +5,7 @@ #include #include -std::shared_ptr assets::Animation::LoadFromFile(const std::string& filename, const Skeleton* skeleton) +std::shared_ptr assets::Animation::LoadFromFile(const std::string& filename, const Skeleton* skeleton) { std::ifstream ifs(filename, std::ios::binary); diff --git a/src/assets/animation.hpp b/src/assets/animation.hpp index 8388bbd..494272d 100644 --- a/src/assets/animation.hpp +++ b/src/assets/animation.hpp @@ -34,7 +34,7 @@ namespace assets size_t GetNumChannels() const { return channels_.size(); } const AnimationChannel& GetChannel(int index) const { return channels_[index]; } - static std::shared_ptr LoadFromFile(const std::string& filename, const Skeleton* skeleton); + static std::shared_ptr LoadFromFile(const std::string& filename, const Skeleton* skeleton); }; diff --git a/src/assets/mesh.cpp b/src/assets/mesh.cpp index cac229a..546a5be 100644 --- a/src/assets/mesh.cpp +++ b/src/assets/mesh.cpp @@ -5,7 +5,8 @@ #include #include -std::map> assets::Mesh::s_texture_cache; +std::map> assets::Mesh::s_texture_cache; +std::map> assets::Mesh::s_skeleton_cache; static int GetVertexAttrFlags(int mesh_flags) { @@ -31,8 +32,9 @@ static void BufferPut(std::vector& buffer, const T& val) buffer.insert(buffer.end(), data, data + sizeof(T)); } -assets::Mesh::Mesh(std::span verts, std::span tris, std::span materials, int flags) : - va_(GetVertexAttrFlags(flags), gfx::VF_CREATE_EBO) +assets::Mesh::Mesh(std::span verts, std::span tris, std::span materials, int flags, std::shared_ptr skeleton) : + va_(GetVertexAttrFlags(flags), gfx::VF_CREATE_EBO), + skeleton_(std::move(skeleton)) { verts_.assign(verts.begin(), verts.end()); tris_.assign(tris.begin(), tris.end()); @@ -72,7 +74,7 @@ assets::Mesh::Mesh(std::span verts, std::span tris, st va_.SetIndices(reinterpret_cast(tris.data()), tris.size() * 3); } -std::shared_ptr assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision, const Skeleton* skeleton) +std::shared_ptr assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision) { std::shared_ptr collision_mesh; @@ -87,6 +89,8 @@ std::shared_ptr assets::Mesh::LoadFromFile(const std::string& file } int flags = 0; + std::shared_ptr skeleton; + std::vector verts; std::vector tris; std::vector materials; @@ -184,6 +188,11 @@ std::shared_ptr assets::Mesh::LoadFromFile(const std::string& file } else if (command == "d") { + if (!skeleton) + { + throw std::runtime_error("Bone definition found in non-skeletal mesh file: " + filename); + } + std::string bone_name; iss >> bone_name; int idx = skeleton->GetBoneIndex(bone_name); @@ -204,19 +213,21 @@ std::shared_ptr assets::Mesh::LoadFromFile(const std::string& file throw std::runtime_error("Lightmap UV flag must be specified before vertex definitions in mesh file: " + filename); } } - else if (command == "skeletal") + else if (command == "skeleton") { - flags |= MF_IS_SKELETAL; - - if (!skeleton) - { - throw std::runtime_error("Loading a skeletal mesh with no skeleton provided: " + filename); - } - if (verts.size() > 0) { throw std::runtime_error("Skeletal flag must be specified before vertex definitions in mesh file: " + filename); } + + flags |= MF_IS_SKELETAL; + + std::string skel_filename; + iss >> skel_filename; + + // load skeleton + skeleton = LoadSkeleton("data/" + skel_filename + ".skel"); + } else { @@ -251,7 +262,7 @@ std::shared_ptr assets::Mesh::LoadFromFile(const std::string& file } // Create the mesh object - std::shared_ptr mesh = std::make_shared(verts, tris, materials, flags); + std::shared_ptr mesh = std::make_shared(verts, tris, materials, flags, skeleton); if (load_collision) { @@ -261,7 +272,7 @@ std::shared_ptr assets::Mesh::LoadFromFile(const std::string& file return mesh; } -std::shared_ptr assets::Mesh::LoadTexture(const std::string& filename) +std::shared_ptr assets::Mesh::LoadTexture(const std::string& filename) { auto it = s_texture_cache.find(filename); if (it != s_texture_cache.end()) @@ -277,3 +288,20 @@ std::shared_ptr assets::Mesh::LoadTexture(const std::string& filen return texture; } + +std::shared_ptr assets::Mesh::LoadSkeleton(const std::string& filename) +{ + auto it = s_skeleton_cache.find(filename); + if (it != s_skeleton_cache.end()) + { + if (auto skel = it->second.lock()) + { + return skel; // Return cached skeleton + } + } + + auto skel = Skeleton::LoadFromFile(filename); + s_skeleton_cache[filename] = skel; // Cache the skeleton + + return skel; +} diff --git a/src/assets/mesh.hpp b/src/assets/mesh.hpp index 83236b5..871f49d 100644 --- a/src/assets/mesh.hpp +++ b/src/assets/mesh.hpp @@ -36,7 +36,7 @@ namespace assets struct MeshMaterial { std::string name; - std::shared_ptr texture; + std::shared_ptr texture; size_t first_tri = 0; size_t num_tris = 0; }; @@ -57,8 +57,10 @@ namespace assets gfx::VertexArray va_; std::shared_ptr collision_mesh_; + std::shared_ptr skeleton_; + public: - Mesh(std::span verts, std::span tris, std::span materials, int flags); + Mesh(std::span verts, std::span tris, std::span materials, int flags, std::shared_ptr skeleton); void SetCollisionMesh(std::shared_ptr mesh) { collision_mesh_ = std::move(mesh); @@ -68,16 +70,20 @@ namespace assets return collision_mesh_; } - const gfx::VertexArray& GetVA() { return va_; } + const gfx::VertexArray& GetVA() const { return va_; } + const std::shared_ptr& GetSkeleton() const { return skeleton_; } std::span GetVertices() const { return verts_; } std::span GetTriangles() const { return tris_; } std::span GetMaterials() const { return materials_; } - static std::shared_ptr LoadFromFile(const std::string& filename, bool load_collision, const Skeleton* skeleton = nullptr); + static std::shared_ptr LoadFromFile(const std::string& filename, bool load_collision); private: - static std::map> s_texture_cache; - static std::shared_ptr LoadTexture(const std::string& filename); + static std::map> s_texture_cache; + static std::shared_ptr LoadTexture(const std::string& filename); + + static std::map> s_skeleton_cache; + static std::shared_ptr LoadSkeleton(const std::string& filename); }; } diff --git a/src/assets/sectordef.cpp b/src/assets/sectordef.cpp index cfe4b8e..3e5fbdd 100644 --- a/src/assets/sectordef.cpp +++ b/src/assets/sectordef.cpp @@ -5,7 +5,7 @@ std::map assets::PortalDef::portal_defs; -assets::SectorDef::SectorDef(std::shared_ptr mesh, std::span portals) +assets::SectorDef::SectorDef(std::shared_ptr mesh, std::span portals) { mesh_ = std::move(mesh); portals_.assign(portals.begin(), portals.end()); diff --git a/src/assets/sectordef.hpp b/src/assets/sectordef.hpp index 69068e7..02be55b 100644 --- a/src/assets/sectordef.hpp +++ b/src/assets/sectordef.hpp @@ -28,13 +28,13 @@ namespace assets class SectorDef { - std::shared_ptr mesh_; + std::shared_ptr mesh_; std::vector portals_; public: - SectorDef(std::shared_ptr mesh, std::span portals); + SectorDef(std::shared_ptr mesh, std::span portals); - const std::shared_ptr& GetMesh() const { return mesh_; } + const std::shared_ptr& GetMesh() const { return mesh_; } std::span GetPortals() const { return portals_; } static std::shared_ptr LoadFromFile(const std::string& base_name); diff --git a/src/assets/skeleton.cpp b/src/assets/skeleton.cpp index fde68c3..e36ac6c 100644 --- a/src/assets/skeleton.cpp +++ b/src/assets/skeleton.cpp @@ -27,7 +27,16 @@ int assets::Skeleton::GetBoneIndex(const std::string& name) const return -1; } -std::shared_ptr assets::Skeleton::LoadFromFile(const std::string& filename) +const assets::Animation* assets::Skeleton::GetAnimation(const std::string& name) const +{ + auto it = anims_.find(name); + if (it != anims_.end()) { + return it->second.get(); + } + return nullptr; +} + +std::shared_ptr assets::Skeleton::LoadFromFile(const std::string& filename) { std::ifstream ifs(filename, std::ios::binary); if (!ifs.is_open()) { @@ -48,26 +57,41 @@ std::shared_ptr assets::Skeleton::LoadFromFile(const std::stri std::string command; iss >> command; - if (command != "b") - continue; - - Transform t; - glm::vec3 angles; - std::string bone_name, parent_name; - - iss >> bone_name >> parent_name; - iss >> t.position.x >> t.position.y >> t.position.z; - iss >> angles.x >> angles.y >> angles.z; - iss >> t.scale; - - if (iss.fail()) + if (command == "b") { - throw std::runtime_error("Failed to parse bone definition in file: " + filename); + Transform t; + glm::vec3 angles; + std::string bone_name, parent_name; + + iss >> bone_name >> parent_name; + iss >> t.position.x >> t.position.y >> t.position.z; + iss >> angles.x >> angles.y >> angles.z; + iss >> t.scale; + + if (iss.fail()) + { + throw std::runtime_error("Failed to parse bone definition in file: " + filename); + } + + t.SetAngles(angles); + + skeleton->AddBone(bone_name, parent_name, t); + } + else if (command == "anim") + { + std::string anim_name, anim_filename; + iss >> anim_name >> anim_filename; + + if (iss.fail()) + { + throw std::runtime_error("Failed to parse animation definition in file: " + filename); + } + + std::shared_ptr anim = Animation::LoadFromFile("data/" + anim_filename + ".anim", skeleton.get()); + skeleton->AddAnimation(anim_name, anim); } - t.SetAngles(angles); - - skeleton->AddBone(bone_name, parent_name, t); + } return skeleton; diff --git a/src/assets/skeleton.hpp b/src/assets/skeleton.hpp index 5ad5dc7..e8ed7f2 100644 --- a/src/assets/skeleton.hpp +++ b/src/assets/skeleton.hpp @@ -23,6 +23,8 @@ namespace assets std::vector bones_; std::map bone_map_; + std::map> anims_; + public: Skeleton() = default; @@ -30,9 +32,15 @@ namespace assets int GetBoneIndex(const std::string& name) const; - const Bone& GetBone(int index) const { return bones_[index]; } + size_t GetNumBones() const { return bones_.size(); } + const Bone& GetBone(size_t idx) const { return bones_[idx]; } - static std::shared_ptr LoadFromFile(const std::string& filename); + const Animation* GetAnimation(const std::string& name) const; + + static std::shared_ptr LoadFromFile(const std::string& filename); + + private: + void AddAnimation(const std::string& name, const std::shared_ptr& anim) { anims_[name] = anim; } }; diff --git a/src/game/meshinstance.cpp b/src/game/meshinstance.cpp new file mode 100644 index 0000000..7147d9a --- /dev/null +++ b/src/game/meshinstance.cpp @@ -0,0 +1,137 @@ +#include "meshinstance.hpp" + +#include + +game::MeshInstance::MeshInstance(std::shared_ptr mesh, const TransformNode* root_node, const float* time_ptr) : + mesh_(std::move(mesh)), + root_node_(root_node), + time_ptr_(time_ptr) +{ + if (IsSkeletal()) + { + SetupBoneNodes(); + } +} + +void game::MeshInstance::SetupBoneNodes() +{ + const auto& skeleton = mesh_->GetSkeleton(); + + size_t num_bones = skeleton->GetNumBones(); + bone_nodes_.resize(num_bones); + + for (size_t i = 0; i < num_bones; ++i) + { + const auto& bone = skeleton->GetBone(i); + TransformNode& node = bone_nodes_[i]; + + node.local_transform = bone.bind_transform; + + if (bone.parent_idx >= 0) + { + node.parent = &bone_nodes_[bone.parent_idx]; + } + else + { + node.parent = root_node_; + } + } + + UpdateBoneMatrices(); +} + +const game::TransformNode* game::MeshInstance::GetBoneNodeByName(const std::string& name) const +{ + int bone_idx = mesh_->GetSkeleton()->GetBoneIndex(name); + + if (bone_idx < 0) + { + return nullptr; // Bone not found in skeleton + } + + return &bone_nodes_[bone_idx]; +} + +void game::MeshInstance::UpdateBoneMatrices() +{ + for (TransformNode& node : bone_nodes_) + { + node.UpdateMatrix(); + } +} + +void game::MeshInstance::PlayAnim(const std::string& name) +{ + if (!IsSkeletal()) + { + throw std::runtime_error("Cannot play animation on non-skeletal mesh"); + } + + const auto& skeleton = mesh_->GetSkeleton(); + + current_anim_ = skeleton->GetAnimation(name); // Can be nullptr + anim_time_ = 0.0f; +} + +void game::MeshInstance::Update() +{ + if (IsSkeletal()) + { + ApplyAnimFrame(); + UpdateBoneMatrices(); + } +} + +void game::MeshInstance::ApplyAnimFrame() +{ + ApplySkelAnim(current_anim_, anim_time_, 1.0f); +} + +void game::MeshInstance::ApplySkelAnim(const assets::Animation* anim, float time, float weight) +{ + float anim_frame = (*time_ptr_ - time) * anim->GetTPS(); + + if (anim_frame < 0.0f) + { + anim_frame = 0.0f; + } + + size_t num_anim_frames = anim->GetNumFrames(); + + size_t frame_i = static_cast(anim_frame); + size_t frame0 = frame_i % num_anim_frames; + size_t frame1 = (frame_i + 1) % num_anim_frames; + float t = anim_frame - static_cast(frame_i); + + for (size_t i = 0; i < anim->GetNumChannels(); ++i) + { + const assets::AnimationChannel& channel = anim->GetChannel(i); + TransformNode& node = bone_nodes_[channel.bone_index]; + + const Transform* t0 = channel.frames[frame0]; + const Transform* t1 = channel.frames[frame1]; + + Transform anim_transform; + + if (t0 != t1) + { + anim_transform = Transform::Lerp(*t0, *t1, t); // Frames are different, interpolate + } + else + { + anim_transform = *t0; // Frames are the same, no interpolation needed + } + + if (weight < 1.0f) + { + // blend with existing transform + node.local_transform = Transform::Lerp(node.local_transform, anim_transform, weight); + } + else + { + node.local_transform = anim_transform; + } + + } +} + diff --git a/src/game/meshinstance.hpp b/src/game/meshinstance.hpp new file mode 100644 index 0000000..bd8c192 --- /dev/null +++ b/src/game/meshinstance.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "assets/mesh.hpp" +#include "transform_node.hpp" + +namespace game +{ + class MeshInstance + { + std::shared_ptr mesh_; + + const TransformNode* root_node_ = nullptr; + std::vector bone_nodes_; // Only used if the mesh is skeletal + + const float* time_ptr_ = nullptr; // Pointer to game time variable + + const assets::Animation* current_anim_ = nullptr; + float anim_time_ = 0.0f; + + public: + MeshInstance(std::shared_ptr mesh, const TransformNode* root_node, const float* time_ptr); + + bool IsSkeletal() const { return (bool)mesh_->GetSkeleton(); } + + void PlayAnim(const std::string& name); + + void Update(); + + const std::shared_ptr& GetMesh() const { return mesh_; } + const TransformNode* GetRootNode() const { return root_node_; } + const TransformNode& GetBoneNode(size_t index) const { return bone_nodes_[index]; } + const TransformNode* GetBoneNodeByName(const std::string& name) const; + + private: + void SetupBoneNodes(); + void UpdateBoneMatrices(); + + protected: + virtual void ApplyAnimFrame(); + + private: + void ApplySkelAnim(const assets::Animation* anim, float time, float weight); + }; + +} \ No newline at end of file diff --git a/src/game/player.cpp b/src/game/player.cpp index 339d5e5..2f7456e 100644 --- a/src/game/player.cpp +++ b/src/game/player.cpp @@ -9,9 +9,17 @@ game::Player::Player(World* world, size_t sector_idx, const glm::vec3& position) input_(0), cam_forward_(0.0f), cam_up_(0.0f), - cam_right_(0.0f) -{ + cam_right_(0.0f), + vm_hands_(assets::Mesh::LoadFromFile("data/hands.mesh", false), &vm_node_, world->GetTimePtr()) +{ + vm_weapon_ = std::make_unique( + assets::Mesh::LoadFromFile("data/knife.mesh", false), + vm_hands_.GetBoneNodeByName("weapon"), + world->GetTimePtr() + ); + + vm_hands_.PlayAnim("knife_attack1"); } void game::Player::Rotate(float delta_yaw, float delta_pitch) @@ -28,6 +36,15 @@ void game::Player::Rotate(float delta_yaw, float delta_pitch) void game::Player::Update(float dt) { + time_ += dt; + + vm_hands_.Update(); + + if (vm_weapon_) + { + vm_weapon_->Update(); + } + float yaw_cos = glm::cos(yaw_); float yaw_sin = glm::sin(yaw_); float pitch_cos = glm::cos(pitch_); @@ -103,7 +120,6 @@ void game::Player::Update(float dt) cam_right_ = glm::normalize(glm::cross(cam_forward_, cam_up_)) * scale; } - time_ += dt; } void game::Player::GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& forward, glm::vec3& up) const @@ -134,6 +150,16 @@ void game::Player::GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& fo } } +void game::Player::GetViewMeshes(std::vector& out_meshes) const +{ + out_meshes.push_back(&vm_hands_); + + if (vm_weapon_) + { + out_meshes.push_back(vm_weapon_.get()); + } +} + glm::vec2 game::Player::GetBobbingOffset(float t, float amplitude) { // Frequency and amplitude can be adjusted to tweak the effect diff --git a/src/game/player.hpp b/src/game/player.hpp index 88e366d..e8259fa 100644 --- a/src/game/player.hpp +++ b/src/game/player.hpp @@ -2,6 +2,7 @@ #include "entity.hpp" #include "player_input.hpp" +#include "meshinstance.hpp" namespace game { @@ -25,6 +26,11 @@ namespace game float time_ = 0.0f; float current_speed_ = 0.0f; + // Viewmodel stuff + TransformNode vm_node_; // Affected by bobbing + MeshInstance vm_hands_; + std::unique_ptr vm_weapon_; // Can be null + public: Player(World* world, size_t sector_idx, const glm::vec3& position); @@ -37,6 +43,8 @@ namespace game void GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& forward, glm::vec3& up) const; + void GetViewMeshes(std::vector& out_meshes) const; + private: static glm::vec2 GetBobbingOffset(float t, float amplitude); diff --git a/src/game/sector.hpp b/src/game/sector.hpp index 2ed26ec..654168f 100644 --- a/src/game/sector.hpp +++ b/src/game/sector.hpp @@ -68,7 +68,7 @@ namespace game size_t idx_; std::shared_ptr def_; - std::shared_ptr mesh_; + std::shared_ptr mesh_; std::vector lights_; // Light in this sector std::vector all_lights_; // Lights in this sector and linked sectors @@ -96,7 +96,7 @@ namespace game void AddLight(const glm::vec3& position, const glm::vec3& color, float radius); size_t GetIndex() const { return idx_; } - const std::shared_ptr& GetMesh() const { return mesh_; } + const std::shared_ptr& GetMesh() const { return mesh_; } const std::shared_ptr& GetLightmap() const { return lightmap_; } btCollisionWorld& GetBtWorld() { return bt_world_; } diff --git a/src/game/transform_node.hpp b/src/game/transform_node.hpp new file mode 100644 index 0000000..05261dd --- /dev/null +++ b/src/game/transform_node.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "utils/transform.hpp" + +namespace game +{ + struct TransformNode + { + const TransformNode* parent = nullptr; + Transform local_transform; + glm::mat4 matrix; // Global + + TransformNode() + { + UpdateMatrix(); + } + + void UpdateMatrix() + { + matrix = local_transform.ToMatrix(); + + if (parent) + { + matrix = parent->matrix * matrix; + } + } + }; + +} \ No newline at end of file diff --git a/src/game/world.hpp b/src/game/world.hpp index 955d5b6..8eb66b3 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -14,6 +14,8 @@ namespace game std::vector> sectors_; std::vector> entities_; + float time_ = 0.0f; // Game time + public: World(); @@ -34,6 +36,8 @@ namespace game Sector& GetSector(size_t idx) { return *sectors_[idx]; } const Sector& GetSector(size_t idx) const { return *sectors_[idx]; } + const float* GetTimePtr() const { return &time_; } + void Bake(); }; diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp index a66b9f9..ed6569c 100644 --- a/src/gfx/renderer.cpp +++ b/src/gfx/renderer.cpp @@ -9,8 +9,9 @@ gfx::Renderer::Renderer() { - ShaderSources::MakeShader(sector_shader_, SS_SECTOR_MESH_VERT, SS_SECTOR_MESH_FRAG); - ShaderSources::MakeShader(portal_shader_, SS_PORTAL_VERT, SS_PORTAL_FRAG); + ShaderSources::MakeShader(sector_shader_.shader, SS_SECTOR_MESH_VERT, SS_SECTOR_MESH_FRAG); + ShaderSources::MakeShader(portal_shader_.shader, SS_PORTAL_VERT, SS_PORTAL_FRAG); + ShaderSources::MakeShader(mesh_shader_.shader, SS_MESH_VERT, SS_MESH_FRAG); proj_ = glm::mat4(1.0f); // Initialize projection matrix to identity SetupPortalVAO(); @@ -74,9 +75,13 @@ void gfx::Renderer::DrawWorld( glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glStencilMask(0xFF); + last_sector_id = 0; + UnreadyShaders(); + const game::Sector& sector = world.GetSector(sector_idx); DrawSectorParams params; + params.draw_id = ++last_sector_id; params.sector = §or; params.entry_portal = nullptr; // No entry portal for the first sector params.clip_plane = glm::vec4(0.0f, 0.0f, 1.0f, 1000.0f); // Default clip plane @@ -94,57 +99,68 @@ void gfx::Renderer::DrawWorld( } +void gfx::Renderer::UnreadyShaders() +{ + portal_shader_.setup_sector = 0; + sector_shader_.setup_sector = 0; +} + +void gfx::Renderer::SetupSectorShader(const DrawSectorParams& params, SectorShader& sshader) +{ + const Shader& shader = *sshader.shader; + + if (current_shader_ != &shader) + { + glUseProgram(shader.GetId()); + current_shader_ = &shader; + } + + if (sshader.setup_sector == params.draw_id) + { + return; // Shader is already set up for this sector + } + + glUniformMatrix4fv(shader.U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, ¶ms.view_proj[0][0]); + glUniform4fv(shader.U(gfx::SU_CLIP_PLANE), 1, ¶ms.clip_plane[0]); + + sshader.setup_sector = params.draw_id; +} + +static void BindTexture(const gfx::Texture* tex) +{ + GLuint id = 0; + if (tex) + { + id = tex->GetId(); + } + + glBindTexture(GL_TEXTURE_2D, id); +} + void gfx::Renderer::DrawSector(const DrawSectorParams& params) { - assets::Mesh& mesh = *params.sector->GetMesh(); + const assets::Mesh& mesh = *params.sector->GetMesh(); - glm::mat4 model = glm::mat4(1.0f); // Identity matrix for model - - - -// #ifndef PG_GLES -// glEnable(GL_CLIP_DISTANCE0); -// #else -// glEnable(GL_CLIP_DISTANCE0_EXT); -// #endif - - glUseProgram(sector_shader_->GetId()); - glUniformMatrix4fv(sector_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, ¶ms.view_proj[0][0]); - glUniformMatrix4fv(sector_shader_->U(gfx::SU_MODEL), 1, GL_FALSE, &model[0][0]); - glUniform4fv(sector_shader_->U(gfx::SU_CLIP_PLANE), 1, ¶ms.clip_plane[0]); + SetupSectorShader(params, sector_shader_); glActiveTexture(GL_TEXTURE1); - glUniform1i(sector_shader_->U(gfx::SU_LIGHTMAP_TEX), 1); // Bind lightmap to sampler - const auto& lightmap = params.sector->GetLightmap(); - if (lightmap) - { - glBindTexture(GL_TEXTURE_2D, lightmap->GetId()); - } - else - { - glBindTexture(GL_TEXTURE_2D, 0); // No lightmap - } + BindTexture(params.sector->GetLightmap().get()); glBindVertexArray(mesh.GetVA().GetVAOId()); + glActiveTexture(GL_TEXTURE0); + for (auto mesh_materials = mesh.GetMaterials(); const auto& mat : mesh_materials) { - glActiveTexture(GL_TEXTURE0); - if (mat.texture) { - glBindTexture(GL_TEXTURE_2D, mat.texture->GetId()); - glUniform1i(sector_shader_->U(gfx::SU_TEX), 0); // Bind texture to sampler - } - else { - glBindTexture(GL_TEXTURE_2D, 0); // No texture - } + BindTexture(mat.texture.get()); // diffuse texture glDrawElements(GL_TRIANGLES, mat.num_tris * 3, GL_UNSIGNED_INT, (void*)(mat.first_tri * 3 * sizeof(uint32_t))); } glBindVertexArray(0); + // Draw portals for (size_t i = 0; i < params.sector->GetNumPortals(); ++i) { const game::Portal& portal = params.sector->GetPortal(i); DrawPortal(params, portal); } - } void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Portal& portal) @@ -211,9 +227,6 @@ void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Porta const game::Sector& other_sector = *other_portal.sector; - - - // Open portal glStencilFunc(GL_EQUAL, params.recursion, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); @@ -227,6 +240,7 @@ void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Porta glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); DrawSectorParams new_params; + new_params.draw_id = ++last_sector_id; new_params.sector = &other_sector; new_params.entry_portal = &other_portal; new_params.clip_plane = new_clip_plane; // Use the portal plane as the clip plane @@ -353,18 +367,17 @@ void gfx::Renderer::DrawPortalPlane(const DrawSectorParams& params, const game:: { if (!clear_depth) // first draw { - glUseProgram(portal_shader_->GetId()); - glUniformMatrix4fv(portal_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, ¶ms.view_proj[0][0]); - glUniformMatrix4fv(portal_shader_->U(gfx::SU_MODEL), 1, GL_FALSE, &trans[0][0]); - glUniform4fv(portal_shader_->U(gfx::SU_CLIP_PLANE), 1, ¶ms.clip_plane[0]); - glUniform2fv(portal_shader_->U(gfx::SU_PORTAL_SIZE), 1, &portal.def->size[0]); - glUniform1i(portal_shader_->U(gfx::SU_CLEAR_DEPTH), 0); + SetupSectorShader(params, portal_shader_); + + glUniformMatrix4fv(portal_shader_.shader->U(gfx::SU_MODEL), 1, GL_FALSE, &trans[0][0]); + glUniform2fv(portal_shader_.shader->U(gfx::SU_PORTAL_SIZE), 1, &portal.def->size[0]); + glUniform1i(portal_shader_.shader->U(gfx::SU_CLEAR_DEPTH), 0); glBindVertexArray(portal_vao_->GetVAOId()); } else // assume uniforms are already set from previous draw { - glUniform1i(portal_shader_->U(gfx::SU_CLEAR_DEPTH), 1); + glUniform1i(portal_shader_.shader->U(gfx::SU_CLEAR_DEPTH), 1); } glDrawArrays(GL_TRIANGLE_FAN, 0, 4); // Draw the portal as a quad } diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp index 011ab56..27919f4 100644 --- a/src/gfx/renderer.hpp +++ b/src/gfx/renderer.hpp @@ -4,11 +4,13 @@ #include "gfx/shader.hpp" #include "game/world.hpp" #include "game/sector.hpp" +#include "game/meshinstance.hpp" namespace gfx { struct DrawSectorParams { + size_t draw_id; const game::Sector* sector; const game::Portal* entry_portal; glm::vec4 clip_plane; @@ -19,6 +21,12 @@ namespace gfx glm::vec3 eye; }; + struct SectorShader + { + std::unique_ptr shader; + size_t setup_sector = 0; + }; + class Renderer { public: @@ -36,8 +44,9 @@ namespace gfx float fov); private: - std::unique_ptr sector_shader_; - std::unique_ptr portal_shader_; + SectorShader sector_shader_; + SectorShader portal_shader_; + SectorShader mesh_shader_; std::shared_ptr portal_vao_; void SetupPortalVAO(); @@ -49,12 +58,20 @@ namespace gfx float min_portal_distance_; glm::mat4 proj_; + size_t last_sector_id; + const Shader* current_shader_; + + void UnreadyShaders(); + void SetupSectorShader(const DrawSectorParams& params, SectorShader& sshader); void DrawSector(const DrawSectorParams& params); void DrawPortal(const DrawSectorParams& params, const game::Portal& portal); static bool ComputeQuadScreenAABB(const glm::vec3* verts, const glm::mat4 view_proj, collision::AABB2& aabb); void DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, const glm::mat4& trans, bool clear_depth); + + void DrawMeshInstance(const DrawSectorParams& params, const game::MeshInstance& mesh_instance, const glm::mat4& model); + }; } \ No newline at end of file diff --git a/src/gfx/shader.cpp b/src/gfx/shader.cpp index 4c25c5d..3cbd35a 100644 --- a/src/gfx/shader.cpp +++ b/src/gfx/shader.cpp @@ -80,9 +80,21 @@ gfx::Shader::Shader(const char* vert_src, const char* frag_src) { // Ziskani lokaci uniformnich promennych for (size_t i = 0; i < SU_COUNT; i++) m_uni[i] = glGetUniformLocation(m_id, s_uni_names[i]); + + SetupTextureBindings(); } gfx::Shader::~Shader() { // Smazani programu glDeleteProgram(m_id); } + +void gfx::Shader::SetupTextureBindings() +{ + glUseProgram(m_id); + + glUniform1i(m_uni[SU_TEX], 0); + glUniform1i(m_uni[SU_LIGHTMAP_TEX], 1); + + glUseProgram(0); +} diff --git a/src/gfx/shader.hpp b/src/gfx/shader.hpp index 7888a75..5a5d38b 100644 --- a/src/gfx/shader.hpp +++ b/src/gfx/shader.hpp @@ -48,5 +48,8 @@ namespace gfx */ GLuint GetId() const { return m_id; } + private: + void SetupTextureBindings(); + }; } diff --git a/src/gfx/shader_sources.cpp b/src/gfx/shader_sources.cpp index 373fdc4..95703c1 100644 --- a/src/gfx/shader_sources.cpp +++ b/src/gfx/shader_sources.cpp @@ -22,7 +22,6 @@ layout (location = 1) in vec3 a_normal; layout (location = 3) in vec2 a_uv; layout (location = 4) in vec2 a_lightmap_uv; -uniform mat4 u_model; uniform mat4 u_view_proj; uniform vec4 u_clip_plane; @@ -31,7 +30,7 @@ out vec2 v_lightmap_uv; out float v_clip_distance; void main() { - vec4 sector_pos = u_model * vec4(a_pos, 1.0); + vec4 sector_pos = vec4(a_pos, 1.0); gl_Position = u_view_proj * sector_pos; // Clip against the plane @@ -113,6 +112,61 @@ void main() { )GLSL", +// SS_MESH_VERT +GLSL_VERSION +R"GLSL( +layout (location = 0) in vec3 a_pos; +layout (location = 1) in vec3 a_normal; +layout (location = 3) in vec2 a_uv; + +uniform mat4 u_view_proj; +uniform vec4 u_clip_plane; + +uniform mat4 u_model; // Transform matrix + +out vec2 v_uv; +out float v_clip_distance; + +out vec3 v_color; + +void main() { + vec4 sector_pos = u_model * vec4(a_pos, 1.0); + gl_Position = u_view_proj * sector_pos; + + // Clip against the plane + v_clip_distance = dot(sector_pos, u_clip_plane); + + //v_normal = mat3(u_model) * a_normal; + v_uv = vec2(a_uv.x, 1.0 - a_uv.y); + + v_color = vec3(1.0, 1.0, 1.0); +} +)GLSL", + +// SS_MESH_FRAG +GLSL_VERSION +R"GLSL( +in vec2 v_uv; +in float v_clip_distance; + +in vec3 v_color; + +uniform sampler2D u_tex; + +layout (location = 0) out vec4 o_color; + +void main() { + if (v_clip_distance < 0.0) { + discard; // Discard fragment if it is outside the clip plane + } + + o_color = vec4(texture(u_tex, v_uv)); + o_color.rgb *= v_color; // Apply vertex color + //o_color = vec4(1.0, 0.0, 0.0, 1.0); +} + +)GLSL", + }; // Vrati zdrojovy kod shaderu diff --git a/src/gfx/shader_sources.hpp b/src/gfx/shader_sources.hpp index 2f8e8c9..9992794 100644 --- a/src/gfx/shader_sources.hpp +++ b/src/gfx/shader_sources.hpp @@ -13,6 +13,9 @@ namespace gfx SS_PORTAL_VERT, SS_PORTAL_FRAG, + SS_MESH_VERT, + SS_MESH_FRAG, + }; class ShaderSources diff --git a/src/utils/transform.hpp b/src/utils/transform.hpp index b66bab2..259e7fb 100644 --- a/src/utils/transform.hpp +++ b/src/utils/transform.hpp @@ -5,11 +5,12 @@ struct Transform { - glm::vec3 position; - glm::quat rotation; - float scale; + glm::vec3 position = glm::vec3(0.0f); + glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // Identity quaternion + float scale = 1.0f; - glm::mat4 ToMatrix() const { + glm::mat4 ToMatrix() const + { // TODO: Optimize matrix construction glm::mat4 translation_mat = glm::translate(glm::mat4(1.0f), position); glm::mat4 rotation_mat = glm::mat4_cast(rotation); @@ -17,8 +18,18 @@ struct Transform return translation_mat * rotation_mat * scaling_mat; } - void SetAngles(const glm::vec3& angles_deg) { + void SetAngles(const glm::vec3& angles_deg) + { glm::vec3 angles_rad = glm::radians(angles_deg); rotation = glm::quat(angles_rad); } + + static Transform Lerp(const Transform& a, const Transform& b, float t) + { + Transform result; + result.position = glm::mix(a.position, b.position, t); + result.rotation = glm::slerp(a.rotation, b.rotation, t); + result.scale = glm::mix(a.scale, b.scale, t); + return result; + } }; \ No newline at end of file