diff --git a/src/app.cpp b/src/app.cpp index be355ae..2adfecb 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -16,13 +16,22 @@ App::App() auto room001 = assets::SectorDef::LoadFromFile("data/room_001"); - size_t s1 = world_.AddSector(room001); - size_t s2 = world_.AddSector(room001); + size_t s1i = world_.AddSector(room001); + game::Sector& s1 = world_.GetSector(s1i); + s1.AddLight(glm::vec3(0.0f, 0.0f, 1.8f), glm::vec3(0.0f, 1.0f, 1.0f), 5.0f); + + size_t s2i = world_.AddSector(room001); + game::Sector& s2 = world_.GetSector(s2i); + s2.AddLight(glm::vec3(0.0f, 0.0f, 1.8f), glm::vec3(1.0f, 1.0f, 0.9f), 6.0f); - world_.LinkPortals(s1, "NDoor", s2, "WDoor"); - world_.LinkPortals(s2, "NDoor", s2, "SDoor"); + world_.LinkPortals(s1i, "NDoor", s2i, "WDoor", 0); + world_.LinkPortals(s2i, "NDoor", s2i, "SDoor", 0); + world_.LinkPortals(s2i, "EDoor", s2i, "EDoor", game::LINK_ROTATE180); + + world_.Bake(); + + player_ = world_.Spawn(s1i, glm::vec3(0.0f, 0.0f, 1.0f)); - player_ = world_.Spawn(s1, glm::vec3(0.0f, 0.0f, 1.0f)); } void App::Frame() diff --git a/src/assets/mesh.cpp b/src/assets/mesh.cpp index 2a4a708..509e61a 100644 --- a/src/assets/mesh.cpp +++ b/src/assets/mesh.cpp @@ -8,9 +8,12 @@ assets::Mesh::Mesh(std::span verts, std::span tris, std::span materials) : va_(gfx::VA_POSITION | gfx::VA_NORMAL | gfx::VA_UV | gfx::VA_LIGHTMAP_UV, gfx::VF_CREATE_EBO) { + verts_.assign(verts.begin(), verts.end()); + tris_.assign(tris.begin(), tris.end()); + materials_.assign(materials.begin(), materials.end()); + va_.SetVBOData(verts.data(), verts.size_bytes()); va_.SetIndices(reinterpret_cast(tris.data()), tris.size() * 3); - materials_.assign(materials.begin(), materials.end()); } std::shared_ptr assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision) diff --git a/src/assets/mesh.hpp b/src/assets/mesh.hpp index 536fb8e..3801c8c 100644 --- a/src/assets/mesh.hpp +++ b/src/assets/mesh.hpp @@ -34,8 +34,11 @@ namespace assets class Mesh { - gfx::VertexArray va_; + std::vector verts_; + std::vector tris_; std::vector materials_; + + gfx::VertexArray va_; std::shared_ptr collision_mesh_; public: @@ -50,7 +53,10 @@ namespace assets } const gfx::VertexArray& GetVA() { return va_; } - const std::span GetMaterials() { return materials_; } + + 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); }; diff --git a/src/collision/aabb.hpp b/src/collision/aabb.hpp index ace5bc3..04aa18e 100644 --- a/src/collision/aabb.hpp +++ b/src/collision/aabb.hpp @@ -9,7 +9,6 @@ namespace collision struct AABB { static constexpr float C_INFINITY = std::numeric_limits::infinity(); - static constexpr float C_DEFAULT_MARGIN = 0.01f; // Margin for AABBs using Vec = glm::vec; @@ -19,10 +18,10 @@ namespace collision AABB() = default; AABB(const Vec& min, const Vec& max) : min(min), max(max) {} - void AddPoint(const Vec& point, float margin = C_DEFAULT_MARGIN) + void AddPoint(const Vec& point) { - min = glm::min(min, point - margin); - max = glm::max(max, point + margin); + min = glm::min(min, point); + max = glm::max(max, point); } bool CollidesWith(const AABB& other) const; diff --git a/src/game/entity.cpp b/src/game/entity.cpp index 4dff5bb..2973ba2 100644 --- a/src/game/entity.cpp +++ b/src/game/entity.cpp @@ -38,7 +38,6 @@ void game::Entity::Move(glm::vec3& velocity, float dt) if (hit_portal != touching_portal_) { - printf("Entity::Move: Touching portal changed from %p to %p\n", touching_portal_, hit_portal); touching_portal_ = hit_portal; if (hit_portal) diff --git a/src/game/player.cpp b/src/game/player.cpp index 1d90a56..67ff3f3 100644 --- a/src/game/player.cpp +++ b/src/game/player.cpp @@ -28,8 +28,6 @@ void game::Player::Rotate(float delta_yaw, float delta_pitch) void game::Player::Update(float dt) { - const glm::mat3& occu_basis = occu_->GetBasis(); - float yaw_cos = glm::cos(yaw_); float yaw_sin = glm::sin(yaw_); float pitch_cos = glm::cos(pitch_); @@ -101,7 +99,8 @@ void game::Player::Update(float dt) const glm::mat3& occu_basis = occu_->GetBasis(); cam_forward_ = occu_basis * glm::vec3(yaw_sin * pitch_cos, yaw_cos * pitch_cos, pitch_sin); cam_up_ = occu_basis[2]; // Up vector is always the Z axis in sector space - cam_right_ = glm::cross(cam_forward_, cam_up_); + float scale = glm::length(cam_forward_); + cam_right_ = glm::normalize(glm::cross(cam_forward_, cam_up_)) * scale; } time_ += dt; @@ -109,12 +108,10 @@ void game::Player::Update(float dt) void game::Player::GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& forward, glm::vec3& up) const { - float scale = 1.0f; - sector_idx = occu_->GetSector().GetIndex(); glm::vec2 bobbing_offset = GetBobbingOffset(time_, 0.01f * current_speed_); - glm::vec3 offset = ((cam_up_ * (0.7f + bobbing_offset.y)) + cam_right_ * bobbing_offset.x) * scale; + glm::vec3 offset = cam_up_ * (0.7f + bobbing_offset.y) + cam_right_ * bobbing_offset.x; position = occu_->GetPosition() + offset; diff --git a/src/game/sector.cpp b/src/game/sector.cpp index 5d483eb..ebb748b 100644 --- a/src/game/sector.cpp +++ b/src/game/sector.cpp @@ -78,6 +78,15 @@ void game::Sector::ComputePortalVertex(Portal& portal, size_t idx, const glm::ve portal.verts[idx] = glm::vec3(portal.trans * glm::vec4(vert, 1.0f)); } +void game::Sector::AddLight(const glm::vec3& position, const glm::vec3& color, float radius) +{ + Light light; + light.position = position; + light.color = color; + light.radius = radius; + lights_.push_back(light); +} + int game::Sector::GetPortalIndex(const std::string& name) const { auto it = portal_map_.find(name); @@ -89,9 +98,15 @@ int game::Sector::GetPortalIndex(const std::string& name) const return -1; } -static void LinkPortal(game::Portal& p1, game::Portal& p2) { +static void LinkPortal(game::Portal& p1, game::Portal& p2, int flags) { // Calculate the traverse transformation glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0, 0, 1)); + + if (flags & game::LINK_ROTATE180) + { + rotation = glm::rotate(rotation, glm::radians(180.0f), glm::vec3(0, 1, 0)); + } + glm::mat4 p1_to_p2 = p2.trans * rotation * glm::inverse(p1.trans); p1.link = &p2; @@ -102,14 +117,14 @@ static void LinkPortal(game::Portal& p1, game::Portal& p2) { // Transforms directions from p1's sector space to p2's sector space p1.tr_basis = glm::mat3(p1.tr_position); - // get rid of scale - for (size_t i = 0; i < 3 ; i++) - p1.tr_basis[i] = glm::normalize(p1.tr_basis[i]); + //// get rid of scale + //for (size_t i = 0; i < 3 ; i++) + // p1.tr_basis[i] = glm::normalize(p1.tr_basis[i]); p1.tr_scale = p2.scale / p1.scale; } -void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2) +void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, int flags) { Portal& p1 = s1.portals_[idx1]; Portal& p2 = s2.portals_[idx2]; @@ -119,11 +134,11 @@ void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2) throw std::runtime_error("One of the portals is already linked"); } - LinkPortal(p1, p2); + LinkPortal(p1, p2, flags); if (&p1 != &p2) { - LinkPortal(p2, p1); + LinkPortal(p2, p1, flags); } } @@ -248,3 +263,154 @@ const game::Portal* game::Sector::TestPortalContact(btCapsuleShapeZ& capsule, co return result_callback.portal; } + +static bool PointIsOnRight(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& p) +{ + // Check if point p is on the right side of the line segment p0 -> p1 + return (p1.x - p0.x) * (p.y - p0.y) - (p1.y - p0.y) * (p.x - p0.x) < 0.0f; +} + +static float SignedDistanceToLine(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& p) +{ + // Calculate the signed distance from point p to the line segment p0 -> p1 + return (p1.x - p0.x) * (p.y - p0.y) - (p1.y - p0.y) * (p.x - p0.x); +} + +void game::Sector::BakeLightmap() +{ + const size_t lightmap_size = 256; + const glm::vec3 ambient_light(0.2f); // Ambient light color + const float margin = 2.0f; + + std::span mesh_verts = mesh_->GetVertices(); + std::span mesh_tris = mesh_->GetTriangles(); + + struct LightmapTexel + { + bool used = false; + glm::vec3 color = glm::vec3(0.0f); + }; + + std::vector lightmap(lightmap_size * lightmap_size); + + for (const assets::MeshTriangle& tri : mesh_tris) + { + //const assets::MeshVertex* verts[3]; + glm::vec2 verts[3]; + glm::vec3 vert_pos[3]; + glm::vec3 vert_norm[3]; + + collision::AABB2 tri_aabb; + + for (size_t i = 0; i < 3; ++i) + { + const assets::MeshVertex& vert = mesh_verts[tri.vert[i]]; + glm::vec2 pos_lightmap = vert.lightmap_uv * (float)lightmap_size; + verts[i] = pos_lightmap; + tri_aabb.AddPoint(pos_lightmap); + + vert_pos[i] = vert.pos; + vert_norm[i] = vert.normal; + } + + // Clamp to lightmap size + int y0 = glm::max(0, (int)(tri_aabb.min.y - margin)); + int y1 = glm::min((int)lightmap_size, (int)(tri_aabb.max.y + 1.0f + margin)); + int x0 = glm::max(0, (int)(tri_aabb.min.x - margin)); + int x1 = glm::min((int)lightmap_size, (int)(tri_aabb.max.x + 1.0f + margin)); + + for (int y = y0; y < y1; ++y) + { + for (int x = x0; x < x1; ++x) + { + LightmapTexel& texel = lightmap[y * lightmap_size + x]; + + if (texel.used) + { + continue; + } + + glm::vec2 texel_pos((float)x + 0.5f, (float)y + 0.5f); // Center of the texel + + //if (PointIsOnRight(verts[0], verts[1], texel_pos) || + // PointIsOnRight(verts[1], verts[2], texel_pos) || + // PointIsOnRight(verts[2], verts[0], texel_pos)) + //{ + // continue; // Texel is outside the triangle + //} + + float sd = SignedDistanceToLine(verts[0], verts[1], texel_pos); + sd = glm::min(sd, SignedDistanceToLine(verts[1], verts[2], texel_pos)); + sd = glm::min(sd, SignedDistanceToLine(verts[2], verts[0], texel_pos)); + + if (sd > margin) + { + continue; // Texel is outside the triangle + } + + texel.used = sd < 0.0f; + + // Compute barycentric coordinates + glm::vec2 v0 = verts[1] - verts[0]; + glm::vec2 v1 = verts[2] - verts[0]; + glm::vec2 v2 = texel_pos - verts[0]; + float d00 = glm::dot(v0, v0); + float d01 = glm::dot(v0, v1); + float d11 = glm::dot(v1, v1); + float d20 = glm::dot(v2, v0); + float d21 = glm::dot(v2, v1); + float denom = d00 * d11 - d01 * d01; + if (denom == 0.0f) + { + continue; // Degenerate triangle, skip + } + + float v = (d11 * d20 - d01 * d21) / denom; + float w = (d00 * d21 - d01 * d20) / denom; + float u = 1.0f - v - w; + + glm::vec3 texel_pos_ws = u * vert_pos[0] + v * vert_pos[1] + w * vert_pos[2]; + glm::vec3 texel_norm_ws = glm::normalize(u * vert_norm[0] + v * vert_norm[1] + w * vert_norm[2]); + + glm::vec3 light_color = ambient_light; + + for (const Light& light : lights_) + { + glm::vec3 light_dir = glm::normalize(light.position - texel_pos_ws); + float dot = glm::dot(texel_norm_ws, light_dir); + if (dot > 0.0f) + { + float dist = glm::length(light.position - texel_pos_ws); + float attenuation = glm::clamp(1.0f - (dist / light.radius), 0.0f, 1.0f); + light_color += light.color * dot * attenuation; + } + } + + // Store the light color in the lightmap + texel.color = light_color; + + } + } + } + + std::vector lightmap_data(lightmap_size * lightmap_size * 3); + for (size_t i = 0; i < lightmap.size(); ++i) + { + const LightmapTexel& texel = lightmap[i]; + glm::vec3 color = glm::clamp(texel.color * 0.5f, glm::vec3(0.0f), glm::vec3(1.0f)); + lightmap_data[i * 3 + 0] = static_cast(color.r * 255.0f); + lightmap_data[i * 3 + 1] = static_cast(color.g * 255.0f); + lightmap_data[i * 3 + 2] = static_cast(color.b * 255.0f); + } + + + lightmap_ = std::make_shared( + lightmap_size, lightmap_size, + lightmap_data.data(), + GL_RGB, + GL_RGB, + GL_UNSIGNED_BYTE, + GL_LINEAR + ); + +} diff --git a/src/game/sector.hpp b/src/game/sector.hpp index 93a7692..723efeb 100644 --- a/src/game/sector.hpp +++ b/src/game/sector.hpp @@ -48,6 +48,18 @@ namespace game CollisionObjectData col_data; }; + enum LinkPortalFlags + { + LINK_ROTATE180 = 1 << 0, + }; + + struct Light + { + glm::vec3 position; + glm::vec3 color; + float radius; + }; + class Sector { World* world_; @@ -56,6 +68,9 @@ namespace game std::shared_ptr mesh_; + std::vector lights_; + std::shared_ptr lightmap_; + std::vector portals_; std::map portal_map_; // Maps portal name to index in portals_ @@ -73,14 +88,18 @@ namespace game void ComputePortalVertex(Portal& portal, size_t idx, const glm::vec2& base_vert); public: + + 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& GetLightmap() const { return lightmap_; } int GetPortalIndex(const std::string& name) const; const Portal& GetPortal(size_t idx) const { return portals_[idx]; } const size_t GetNumPortals() const { return portals_.size(); } - static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2); + static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, int flags); bool SweepCapsule( const btCapsuleShapeZ& capsule, @@ -92,6 +111,7 @@ namespace game const Portal* TestPortalContact(btCapsuleShapeZ& capsule, const glm::mat3& basis, const glm::vec3& pos); + void BakeLightmap(); }; diff --git a/src/game/world.cpp b/src/game/world.cpp index c15a3c4..ee996c8 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -13,7 +13,7 @@ size_t game::World::AddSector(std::shared_ptr def) return idx; } -void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, size_t sector2, const std::string& portal_name2) +void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, size_t sector2, const std::string& portal_name2, int flags) { Sector& s1 = *sectors_[sector1]; Sector& s2 = *sectors_[sector2]; @@ -25,5 +25,13 @@ void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, s throw std::runtime_error("Invalid portal names for linking"); } - Sector::LinkPortals(s1, p1, s2, p2); + Sector::LinkPortals(s1, p1, s2, p2, flags); +} + +void game::World::Bake() +{ + for (auto& sector : sectors_) + { + sector->BakeLightmap(); + } } diff --git a/src/game/world.hpp b/src/game/world.hpp index 216965d..955d5b6 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -20,7 +20,7 @@ namespace game size_t AddSector(std::shared_ptr def); void LinkPortals(size_t sector1, const std::string& portal_name1, - size_t sector2, const std::string& portal_name2); + size_t sector2, const std::string& portal_name2, int flags); template T* Spawn(TArgs&&... args) @@ -34,6 +34,8 @@ namespace game Sector& GetSector(size_t idx) { return *sectors_[idx]; } const Sector& GetSector(size_t idx) const { return *sectors_[idx]; } + void Bake(); + }; } \ No newline at end of file diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp index 525ef74..5a99d64 100644 --- a/src/gfx/renderer.cpp +++ b/src/gfx/renderer.cpp @@ -113,6 +113,18 @@ void gfx::Renderer::DrawSector(const DrawSectorParams& params) 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]); + 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 + } + glBindVertexArray(mesh.GetVA().GetVAOId()); for (auto mesh_materials = mesh.GetMaterials(); const auto& mat : mesh_materials) { glActiveTexture(GL_TEXTURE0); @@ -242,7 +254,7 @@ bool gfx::Renderer::ComputePortalScreenAABB(const game::Portal& portal, const gl glm::vec2 ndc = clip; ndc /= clip.w; - aabb.AddPoint(ndc, 0.0f); + aabb.AddPoint(ndc); } if (num_behind == 4) diff --git a/src/gfx/shader.cpp b/src/gfx/shader.cpp index 41dd607..4c25c5d 100644 --- a/src/gfx/shader.cpp +++ b/src/gfx/shader.cpp @@ -11,6 +11,7 @@ static const char* const s_uni_names[] = { "u_clip_plane", // SU_CLIP_PLANE "u_portal_size", // SU_PORTAL_SIZE "u_clear_depth", // SU_CLEAR_DEPTH + "u_lightmap_tex", // SU_LIGHTMAP_TEX }; // Vytvori shader z daneho zdroje diff --git a/src/gfx/shader.hpp b/src/gfx/shader.hpp index 868a10e..7888a75 100644 --- a/src/gfx/shader.hpp +++ b/src/gfx/shader.hpp @@ -16,6 +16,7 @@ namespace gfx SU_CLIP_PLANE, SU_PORTAL_SIZE, SU_CLEAR_DEPTH, + SU_LIGHTMAP_TEX, SU_COUNT }; diff --git a/src/gfx/shader_sources.cpp b/src/gfx/shader_sources.cpp index 0cb1563..373fdc4 100644 --- a/src/gfx/shader_sources.cpp +++ b/src/gfx/shader_sources.cpp @@ -20,12 +20,14 @@ R"GLSL( layout (location = 0) in vec3 a_pos; 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; out vec2 v_uv; +out vec2 v_lightmap_uv; out float v_clip_distance; void main() { @@ -37,6 +39,7 @@ void main() { //v_normal = mat3(u_model) * a_normal; v_uv = vec2(a_uv.x, 1.0 - a_uv.y); + v_lightmap_uv = a_lightmap_uv; } )GLSL", @@ -44,9 +47,11 @@ void main() { GLSL_VERSION R"GLSL( in vec2 v_uv; +in vec2 v_lightmap_uv; in float v_clip_distance; uniform sampler2D u_tex; +uniform sampler2D u_lightmap_tex; layout (location = 0) out vec4 o_color; @@ -56,6 +61,7 @@ void main() { } o_color = vec4(texture(u_tex, v_uv)); + o_color.rgb *= texture(u_lightmap_tex, v_lightmap_uv).rgb * 2.0; // Apply lightmap //o_color = vec4(1.0, 0.0, 0.0, 1.0); } diff --git a/src/gfx/texture.cpp b/src/gfx/texture.cpp index b3e42c1..27d8d9f 100644 --- a/src/gfx/texture.cpp +++ b/src/gfx/texture.cpp @@ -16,8 +16,10 @@ gfx::Texture::Texture(GLuint width, GLuint height, const void* data, GLint inter glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } @@ -37,7 +39,7 @@ std::shared_ptr gfx::Texture::LoadFromFile(const std::string& file std::shared_ptr texture; try { - texture = std::make_shared(width, height, data, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR); + texture = std::make_shared(width, height, data, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR_MIPMAP_LINEAR); } catch (const std::exception& e) { stbi_image_free(data); throw; // Rethrow the exception after freeing the data