Prepare for mesh rendering
This commit is contained in:
parent
2b69893841
commit
a2bef73191
@ -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"
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
std::shared_ptr<assets::Animation> assets::Animation::LoadFromFile(const std::string& filename, const Skeleton* skeleton)
|
||||
std::shared_ptr<const assets::Animation> assets::Animation::LoadFromFile(const std::string& filename, const Skeleton* skeleton)
|
||||
{
|
||||
std::ifstream ifs(filename, std::ios::binary);
|
||||
|
||||
|
||||
@ -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<Animation> LoadFromFile(const std::string& filename, const Skeleton* skeleton);
|
||||
static std::shared_ptr<const Animation> LoadFromFile(const std::string& filename, const Skeleton* skeleton);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
std::map<std::string, std::weak_ptr<gfx::Texture>> assets::Mesh::s_texture_cache;
|
||||
std::map<std::string, std::weak_ptr<const gfx::Texture>> assets::Mesh::s_texture_cache;
|
||||
std::map<std::string, std::weak_ptr<const assets::Skeleton>> assets::Mesh::s_skeleton_cache;
|
||||
|
||||
static int GetVertexAttrFlags(int mesh_flags)
|
||||
{
|
||||
@ -31,8 +32,9 @@ static void BufferPut(std::vector<char>& buffer, const T& val)
|
||||
buffer.insert(buffer.end(), data, data + sizeof(T));
|
||||
}
|
||||
|
||||
assets::Mesh::Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials, int flags) :
|
||||
va_(GetVertexAttrFlags(flags), gfx::VF_CREATE_EBO)
|
||||
assets::Mesh::Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials, int flags, std::shared_ptr<const Skeleton> 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<MeshVertex> verts, std::span<MeshTriangle> tris, st
|
||||
va_.SetIndices(reinterpret_cast<const GLuint*>(tris.data()), tris.size() * 3);
|
||||
}
|
||||
|
||||
std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision, const Skeleton* skeleton)
|
||||
std::shared_ptr<const assets::Mesh> assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision)
|
||||
{
|
||||
std::shared_ptr<collision::TriangleMesh> collision_mesh;
|
||||
|
||||
@ -87,6 +89,8 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
std::shared_ptr<const Skeleton> skeleton;
|
||||
|
||||
std::vector<MeshVertex> verts;
|
||||
std::vector<MeshTriangle> tris;
|
||||
std::vector<MeshMaterial> materials;
|
||||
@ -184,6 +188,11 @@ std::shared_ptr<assets::Mesh> 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> 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> assets::Mesh::LoadFromFile(const std::string& file
|
||||
}
|
||||
// Create the mesh object
|
||||
|
||||
std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>(verts, tris, materials, flags);
|
||||
std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>(verts, tris, materials, flags, skeleton);
|
||||
|
||||
if (load_collision)
|
||||
{
|
||||
@ -261,7 +272,7 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
|
||||
return mesh;
|
||||
}
|
||||
|
||||
std::shared_ptr<gfx::Texture> assets::Mesh::LoadTexture(const std::string& filename)
|
||||
std::shared_ptr<const gfx::Texture> 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<gfx::Texture> assets::Mesh::LoadTexture(const std::string& filen
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
std::shared_ptr<const assets::Skeleton> 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;
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ namespace assets
|
||||
struct MeshMaterial
|
||||
{
|
||||
std::string name;
|
||||
std::shared_ptr<gfx::Texture> texture;
|
||||
std::shared_ptr<const gfx::Texture> texture;
|
||||
size_t first_tri = 0;
|
||||
size_t num_tris = 0;
|
||||
};
|
||||
@ -57,8 +57,10 @@ namespace assets
|
||||
gfx::VertexArray va_;
|
||||
std::shared_ptr<collision::TriangleMesh> collision_mesh_;
|
||||
|
||||
std::shared_ptr<const Skeleton> skeleton_;
|
||||
|
||||
public:
|
||||
Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials, int flags);
|
||||
Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials, int flags, std::shared_ptr<const Skeleton> skeleton);
|
||||
|
||||
void SetCollisionMesh(std::shared_ptr<collision::TriangleMesh> 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<const Skeleton>& GetSkeleton() const { return skeleton_; }
|
||||
|
||||
std::span<const MeshVertex> GetVertices() const { return verts_; }
|
||||
std::span<const MeshTriangle> GetTriangles() const { return tris_; }
|
||||
std::span<const MeshMaterial> GetMaterials() const { return materials_; }
|
||||
|
||||
static std::shared_ptr<Mesh> LoadFromFile(const std::string& filename, bool load_collision, const Skeleton* skeleton = nullptr);
|
||||
static std::shared_ptr<const Mesh> LoadFromFile(const std::string& filename, bool load_collision);
|
||||
|
||||
private:
|
||||
static std::map<std::string, std::weak_ptr<gfx::Texture>> s_texture_cache;
|
||||
static std::shared_ptr<gfx::Texture> LoadTexture(const std::string& filename);
|
||||
static std::map<std::string, std::weak_ptr<const gfx::Texture>> s_texture_cache;
|
||||
static std::shared_ptr<const gfx::Texture> LoadTexture(const std::string& filename);
|
||||
|
||||
static std::map<std::string, std::weak_ptr<const Skeleton>> s_skeleton_cache;
|
||||
static std::shared_ptr<const Skeleton> LoadSkeleton(const std::string& filename);
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
std::map<std::string, assets::PortalDef> assets::PortalDef::portal_defs;
|
||||
|
||||
assets::SectorDef::SectorDef(std::shared_ptr<Mesh> mesh, std::span<SectorPortalDef> portals)
|
||||
assets::SectorDef::SectorDef(std::shared_ptr<const Mesh> mesh, std::span<SectorPortalDef> portals)
|
||||
{
|
||||
mesh_ = std::move(mesh);
|
||||
portals_.assign(portals.begin(), portals.end());
|
||||
|
||||
@ -28,13 +28,13 @@ namespace assets
|
||||
|
||||
class SectorDef
|
||||
{
|
||||
std::shared_ptr<Mesh> mesh_;
|
||||
std::shared_ptr<const Mesh> mesh_;
|
||||
std::vector<SectorPortalDef> portals_;
|
||||
|
||||
public:
|
||||
SectorDef(std::shared_ptr<Mesh> mesh, std::span<SectorPortalDef> portals);
|
||||
SectorDef(std::shared_ptr<const Mesh> mesh, std::span<SectorPortalDef> portals);
|
||||
|
||||
const std::shared_ptr<Mesh>& GetMesh() const { return mesh_; }
|
||||
const std::shared_ptr<const Mesh>& GetMesh() const { return mesh_; }
|
||||
std::span<const SectorPortalDef> GetPortals() const { return portals_; }
|
||||
|
||||
static std::shared_ptr<SectorDef> LoadFromFile(const std::string& base_name);
|
||||
|
||||
@ -27,7 +27,16 @@ int assets::Skeleton::GetBoneIndex(const std::string& name) const
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::shared_ptr<assets::Skeleton> 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<const assets::Skeleton> 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> 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<const Animation> 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;
|
||||
|
||||
@ -23,6 +23,8 @@ namespace assets
|
||||
std::vector<Bone> bones_;
|
||||
std::map<std::string, int> bone_map_;
|
||||
|
||||
std::map<std::string, std::shared_ptr<const Animation>> 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<Skeleton> LoadFromFile(const std::string& filename);
|
||||
const Animation* GetAnimation(const std::string& name) const;
|
||||
|
||||
static std::shared_ptr<const Skeleton> LoadFromFile(const std::string& filename);
|
||||
|
||||
private:
|
||||
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim) { anims_[name] = anim; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
137
src/game/meshinstance.cpp
Normal file
137
src/game/meshinstance.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
#include "meshinstance.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
game::MeshInstance::MeshInstance(std::shared_ptr<const assets::Mesh> 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<size_t>(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<float>(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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
45
src/game/meshinstance.hpp
Normal file
45
src/game/meshinstance.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "assets/mesh.hpp"
|
||||
#include "transform_node.hpp"
|
||||
|
||||
namespace game
|
||||
{
|
||||
class MeshInstance
|
||||
{
|
||||
std::shared_ptr<const assets::Mesh> mesh_;
|
||||
|
||||
const TransformNode* root_node_ = nullptr;
|
||||
std::vector<TransformNode> 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<const assets::Mesh> 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<const assets::Mesh>& 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);
|
||||
};
|
||||
|
||||
}
|
||||
@ -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<MeshInstance>(
|
||||
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<const MeshInstance*>& 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
|
||||
|
||||
@ -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<MeshInstance> 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<const MeshInstance*>& out_meshes) const;
|
||||
|
||||
private:
|
||||
static glm::vec2 GetBobbingOffset(float t, float amplitude);
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ namespace game
|
||||
size_t idx_;
|
||||
std::shared_ptr<assets::SectorDef> def_;
|
||||
|
||||
std::shared_ptr<assets::Mesh> mesh_;
|
||||
std::shared_ptr<const assets::Mesh> mesh_;
|
||||
|
||||
std::vector<Light> lights_; // Light in this sector
|
||||
std::vector<Light> 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<assets::Mesh>& GetMesh() const { return mesh_; }
|
||||
const std::shared_ptr<const assets::Mesh>& GetMesh() const { return mesh_; }
|
||||
const std::shared_ptr<gfx::Texture>& GetLightmap() const { return lightmap_; }
|
||||
btCollisionWorld& GetBtWorld() { return bt_world_; }
|
||||
|
||||
|
||||
29
src/game/transform_node.hpp
Normal file
29
src/game/transform_node.hpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@ -14,6 +14,8 @@ namespace game
|
||||
std::vector<std::unique_ptr<Sector>> sectors_;
|
||||
std::vector<std::unique_ptr<Entity>> 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();
|
||||
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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> shader;
|
||||
size_t setup_sector = 0;
|
||||
};
|
||||
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
@ -36,8 +44,9 @@ namespace gfx
|
||||
float fov);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Shader> sector_shader_;
|
||||
std::unique_ptr<Shader> portal_shader_;
|
||||
SectorShader sector_shader_;
|
||||
SectorShader portal_shader_;
|
||||
SectorShader mesh_shader_;
|
||||
|
||||
std::shared_ptr<VertexArray> 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);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -48,5 +48,8 @@ namespace gfx
|
||||
*/
|
||||
GLuint GetId() const { return m_id; }
|
||||
|
||||
private:
|
||||
void SetupTextureBindings();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -13,6 +13,9 @@ namespace gfx
|
||||
SS_PORTAL_VERT,
|
||||
SS_PORTAL_FRAG,
|
||||
|
||||
SS_MESH_VERT,
|
||||
SS_MESH_FRAG,
|
||||
|
||||
};
|
||||
|
||||
class ShaderSources
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user