From b81cee948d6a5c5ae294832f9df1446372c65072 Mon Sep 17 00:00:00 2001 From: tovjemam Date: Tue, 12 Aug 2025 20:39:42 +0200 Subject: [PATCH] Add better movement and teleporting --- CMakeLists.txt | 3 ++ src/app.cpp | 95 ++++++++-------------------------- src/app.hpp | 5 +- src/game/entity.cpp | 45 ++++++++++++---- src/game/entity.hpp | 6 ++- src/game/player.cpp | 88 ++++++++++++++++++++++++++++++++ src/game/player.hpp | 36 +++++++++++++ src/game/sector.cpp | 119 ++++++++++++++++++++++++++++++++----------- src/game/sector.hpp | 18 ++++--- src/game/world.hpp | 6 ++- src/gfx/renderer.cpp | 41 +++++++++++---- src/gfx/renderer.hpp | 6 ++- 12 files changed, 328 insertions(+), 140 deletions(-) create mode 100644 src/game/player.cpp create mode 100644 src/game/player.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ae3c9f..99aa548 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,9 @@ add_executable(PortalGame "src/collision/trianglemesh.cpp" "src/game/entity.hpp" "src/game/entity.cpp" + "src/game/player_input.hpp" + "src/game/player.hpp" + "src/game/player.cpp" "src/game/sector.hpp" "src/game/sector.cpp" "src/game/world.hpp" diff --git a/src/app.cpp b/src/app.cpp index 0be14ab..be355ae 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -22,11 +22,7 @@ App::App() world_.LinkPortals(s1, "NDoor", s2, "WDoor"); world_.LinkPortals(s2, "NDoor", s2, "SDoor"); - game::CapsuleShape capsule_shape; - capsule_shape.radius = 0.2f; // 20cm radius - capsule_shape.height = 0.3f; // 1.8m height - - player_ = world_.Spawn(s1, capsule_shape, glm::vec3(0.0f, 0.0f, 1.0f)); + player_ = world_.Spawn(s1, glm::vec3(0.0f, 0.0f, 1.0f)); } void App::Frame() @@ -47,85 +43,34 @@ void App::Frame() renderer_.Begin(viewport_size_.x, viewport_size_.y); - //float cameraDistance = 2.0f; - //float angle = time_ * 0.1f; // Rotate over time - // - //glm::vec3 cameraPos = glm::vec3( - // cameraDistance * cos(angle), - // cameraDistance * sin(angle), - // cameraDistance * 0.13f - //); - - //glm::vec3 center(0.0f, 0.0f, 1.5f); - //glm::vec3 up(0.0f, 0.0f, 1.0f); - - //renderer_.DrawWorld(world_, 1, center + cameraPos, -cameraPos, up, aspect, 60.0f); - - glm::vec3 velocity(0.0f, 0.0f, 0.0f); - - if (input_ & game::PI_FORWARD) - velocity.y += 1.0f; - - if (input_ & game::PI_BACKWARD) - velocity.y -= 1.0f; - - if (input_ & game::PI_LEFT) - velocity.x -= 1.0f; - - if (input_ & game::PI_RIGHT) - velocity.x += 1.0f; - - if (input_ & game::PI_JUMP) - velocity.z += 1.0f; - - if (input_ & game::PI_CROUCH) - velocity.z -= 1.0f; - - glm::vec3 forward = glm::vec3( - cos(angles_.x) * cos(angles_.y), - sin(angles_.x) * cos(angles_.y), - sin(angles_.y) - ); - - velocity.z -= 0.1f * delta_time; // Apply gravity - - glm::vec3 forward_xy = glm::normalize(glm::vec3(forward.x, forward.y, 0.0f)); - glm::vec3 right = glm::normalize(glm::vec3(forward.y, -forward.x, 0.0f)); - velocity = glm::normalize(velocity.x * right + velocity.y * forward_xy + velocity.z * glm::vec3(0.0f, 0.0f, 1.0f)); - - //if (glm::length(velocity) > 0.1f) - //{ - // velocity = glm::normalize(velocity); // Normalize to prevent faster diagonal movement - - // glm::vec3 u = velocity * delta_time * 2.0f; - // //bool hit = world_.GetSector(0).SweepCapsule(position, position + u); - - // //if (!hit) - // //{ - // // position += u; - // //} - //} - - player_->SetVelocity(velocity); + player_->SetInput(input_); player_->Update(delta_time); - const auto& position = player_->GetOccurrence().GetPosition(); + //const auto& position = player_->GetOccurrence().GetPosition(); + size_t sector_idx; + glm::vec3 position, forward, up; + player_->GetPOV(sector_idx, position, forward, up); - renderer_.DrawWorld(world_, 0, position, forward, glm::vec3(0.0f, 0.0f, 1.0f), aspect, 60.0f); + renderer_.DrawWorld(world_, sector_idx, position, forward, up, aspect, 60.0f); } void App::MouseMove(const glm::vec2& delta) { float sensitivity = 0.002f; // Sensitivity factor for mouse movement - angles_.x -= delta.x * sensitivity; // Yaw - angles_.y -= delta.y * sensitivity; // Pitch + //angles_.x -= delta.x * sensitivity; // Yaw + //angles_.y -= delta.y * sensitivity; // Pitch - // Clamp pitch to avoid gimbal lock - if (angles_.y > glm::radians(89.0f)) { - angles_.y = glm::radians(89.0f); - } else if (angles_.y < glm::radians(-89.0f)) { - angles_.y = glm::radians(-89.0f); - } + //// Clamp pitch to avoid gimbal lock + //if (angles_.y > glm::radians(89.0f)) { + // angles_.y = glm::radians(89.0f); + //} else if (angles_.y < glm::radians(-89.0f)) { + // angles_.y = glm::radians(-89.0f); + //} + + float delta_yaw = delta.x * sensitivity; + float delta_pitch = -delta.y * sensitivity; + + player_->Rotate(delta_yaw, delta_pitch); } App::~App() diff --git a/src/app.hpp b/src/app.hpp index 29e880d..65f5170 100644 --- a/src/app.hpp +++ b/src/app.hpp @@ -4,6 +4,7 @@ #include "game/world.hpp" #include "game/player_input.hpp" +#include "game/player.hpp" #include "gfx/renderer.hpp" class App @@ -15,9 +16,7 @@ class App float prev_time_ = 0.0f; game::World world_; - game::Entity* player_ = nullptr; - - glm::vec2 angles_ = { 0.0f, 0.0f }; // Pitch and yaw angles in radians + game::Player* player_ = nullptr; gfx::Renderer renderer_; diff --git a/src/game/entity.cpp b/src/game/entity.cpp index d751a33..4dff5bb 100644 --- a/src/game/entity.cpp +++ b/src/game/entity.cpp @@ -4,7 +4,8 @@ game::Entity::Entity(World* world, size_t sector_idx, const CapsuleShape& capsule_shape, const glm::vec3& position) : world_(world), capsule_(capsule_shape), - velocity_(0.0f) + velocity_(0.0f), + touching_portal_(nullptr) { CreateOccurrenceParams occu_params; occu_params.sector = &world->GetSector(sector_idx); @@ -23,7 +24,7 @@ void game::Entity::Move(glm::vec3& velocity, float dt) const int MAX_ITERS = 4; for (size_t i = 0; i < MAX_ITERS && glm::dot(u, u) > 0.0f; ++i) { - printf("Entity::Move: Iteration %zu, u = (%f, %f, %f)\n", i, u.x, u.y, u.z); + //printf("Entity::Move: Iteration %zu, u = (%f, %f, %f)\n", i, u.x, u.y, u.z); // offset in occu's sector space glm::vec3 occu_offset = occu_->basis_ * u; @@ -37,6 +38,7 @@ 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) @@ -85,15 +87,21 @@ void game::Entity::Move(glm::vec3& velocity, float dt) // Update the position based on the hit fraction occu_->position_ += hit_fraction * occu_offset; - if (any_hit) - { - occu_->position_ += hit_normal * 0.00001f; - } - + //if (any_hit) + //{ + // occu_->position_ += hit_normal * 0.00001f; + //} if (other_occu_ && touching_portal_) { other_occu_->position_ = touching_portal_->tr_position * glm::vec4(occu_->position_, 1.0f); + + // TELEPORT + float sd = glm::dot(glm::vec3(touching_portal_->plane), occu_->position_) + touching_portal_->plane.w; + if (sd < 0.0f) + { + std::swap(occu_, other_occu_); // Swap occurrences + } } if (!any_hit) @@ -102,7 +110,7 @@ void game::Entity::Move(glm::vec3& velocity, float dt) } //hit_normal *= -1.0f; // Invert the normal to point outwards - printf("Entity::Move: Hit detected, hit_fraction = %f, hit_normal = (%f, %f, %f)\n", hit_fraction, hit_normal.x, hit_normal.y, hit_normal.z); + //printf("Entity::Move: Hit detected, hit_fraction = %f, hit_normal = (%f, %f, %f)\n", hit_fraction, hit_normal.x, hit_normal.y, hit_normal.z); u -= hit_fraction * u; // Reduce the movement vector by the hit fraction u -= glm::dot(u, hit_normal) * hit_normal; // Reflect the velocity along the hit normal @@ -118,6 +126,14 @@ void game::Entity::Update(float dt) void game::Entity::CreateOtherOccurence(const Portal& portal) { + other_occu_.reset(); + + if (!portal.link) + { + // No linked sector, no occurence :( + return; + } + CreateOccurrenceParams other_occu_params; other_occu_params.sector = portal.link->sector; other_occu_params.position = portal.tr_position * glm::vec4(occu_->position_, 1.0f); @@ -141,13 +157,22 @@ game::EntityOccurrence::EntityOccurrence(Entity* entity, const CreateOccurrenceP bool game::EntityOccurrence::Sweep(const glm::vec3& target_position, float& hit_fraction, glm::vec3& hit_normal, const Portal** hit_portal) { + if (hit_portal) + { + *hit_portal = sector_->TestPortalContact(bt_capsule_, basis_, position_); + + if (!*hit_portal) + { + *hit_portal = sector_->TestPortalContact(bt_capsule_, basis_, target_position); + } + } + return sector_->SweepCapsule( bt_capsule_, basis_, position_, target_position, hit_fraction, - hit_normal, - hit_portal + hit_normal ); } diff --git a/src/game/entity.hpp b/src/game/entity.hpp index 309674a..04e07aa 100644 --- a/src/game/entity.hpp +++ b/src/game/entity.hpp @@ -45,10 +45,14 @@ namespace game const Sector& GetSector() const { return *sector_; } const glm::vec3& GetPosition() const { return position_; } + + const glm::mat3& GetBasis() const { return basis_; } + const glm::mat3& GetInvBasis() const { return inv_basis_; } }; class Entity { + protected: World* world_; CapsuleShape capsule_; @@ -65,7 +69,7 @@ namespace game // offset in entity space void Move(glm::vec3& velocity, float dt); - void Update(float dt); + virtual void Update(float dt); void SetVelocity(const glm::vec3& velocity) { velocity_ = velocity; } diff --git a/src/game/player.cpp b/src/game/player.cpp new file mode 100644 index 0000000..cc50bb6 --- /dev/null +++ b/src/game/player.cpp @@ -0,0 +1,88 @@ +#include "player.hpp" +#include "world.hpp" +#include "sector.hpp" + +game::Player::Player(World* world, size_t sector_idx, const glm::vec3& position) : + Super(world, sector_idx, { 0.3f, 1.2f }, position), + yaw_(0.0f), + pitch_(0.0f), + input_(0), + cam_forward_(0.0f), + cam_up_(0.0f) +{ + +} + +void game::Player::Rotate(float delta_yaw, float delta_pitch) +{ + yaw_ += delta_yaw; + pitch_ += delta_pitch; + // Clamp pitch to avoid gimbal lock + if (pitch_ > glm::radians(89.0f)) { + pitch_ = glm::radians(89.0f); + } else if (pitch_ < glm::radians(-89.0f)) { + pitch_ = glm::radians(-89.0f); + } +} + +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_); + float pitch_sin = glm::sin(pitch_); + + // MOVEMENT + glm::vec2 velocity_xy(0.0f); + if (input_ & PI_FORWARD) + velocity_xy.y += 1.0f; + + if (input_ & PI_BACKWARD) + velocity_xy.y -= 1.0f; + + if (input_ & PI_LEFT) + velocity_xy.x -= 1.0f; + + if (input_ & PI_RIGHT) + velocity_xy.x += 1.0f; + + glm::mat2 movement_basis( + yaw_cos, -yaw_sin, + yaw_sin, yaw_cos + ); + + float speed = 3.0f; // Base speed + + glm::vec2 normalized_velocity_xy(0.0f); + if (glm::length(velocity_xy) > 0.01f) + { + normalized_velocity_xy = glm::normalize(movement_basis * velocity_xy); + } + + glm::vec3 velocity = glm::vec3( + normalized_velocity_xy * speed, + -1.0f // No vertical movement for now + ); + + velocity_ = velocity; + + Super::Update(dt); + + { + // Occurrence could have changed during update !!!!1!1! + 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 + } +} + +void game::Player::GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& forward, glm::vec3& up) const +{ + sector_idx = occu_->GetSector().GetIndex(); + position = occu_->GetPosition() + cam_up_ * 0.7f; + + forward = cam_forward_; + up = cam_up_; +} \ No newline at end of file diff --git a/src/game/player.hpp b/src/game/player.hpp new file mode 100644 index 0000000..40e08a4 --- /dev/null +++ b/src/game/player.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "entity.hpp" +#include "player_input.hpp" + +namespace game +{ + class Player : public Entity + { + using Super = Entity; + + float yaw_, pitch_; + PlayerInputFlags input_; + + // in occu's sector space + glm::vec3 cam_forward_; + glm::vec3 cam_up_; + + public: + Player(World* world, size_t sector_idx, const glm::vec3& position); + + // angles in radians + void Rotate(float delta_yaw, float delta_pitch); + + void SetInput(PlayerInputFlags input) { input_ = input; } + + virtual void Update(float dt) override; + + void GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& forward, glm::vec3& up) const; + + + }; + + + +} diff --git a/src/game/sector.cpp b/src/game/sector.cpp index ab1a45c..5d483eb 100644 --- a/src/game/sector.cpp +++ b/src/game/sector.cpp @@ -14,6 +14,10 @@ game::Sector::Sector(World* world, size_t idx, std::shared_ptrGetCollisionMesh()->GetShape()); + bt_world_.addCollisionObject(&bt_mesh_col_obj_); + auto def_portals = def_->GetPortals(); size_t num_portals = def_portals.size(); @@ -26,7 +30,9 @@ game::Sector::Sector(World* world, size_t idx, std::shared_ptr( + btVector3(portal.def->size.x * portal.scale * 0.5f, 0.1f, portal.def->size.y * portal.scale * 0.5f) + ); + + portal.bt_col_obj = std::make_unique(); + portal.bt_col_obj->setCollisionShape(portal.bt_col_shape.get()); + + btQuaternion bt_rotation(angles_rad.x, angles_rad.y, angles_rad.z); + btVector3 bt_position(def_portal.origin.x, def_portal.origin.y, def_portal.origin.z); + btTransform bt_transform(bt_rotation, bt_position); + portal.bt_col_obj->setWorldTransform(bt_transform); + + portal.col_data.type = CO_PORTAL; + portal.col_data.portal = &portal; + portal.bt_col_obj->setUserPointer(&portal.col_data); + + bt_world_.addCollisionObject(portal.bt_col_obj.get()); } - - bt_mesh_col_obj_.setCollisionShape(mesh_->GetCollisionMesh()->GetShape()); - bt_world_.addCollisionObject(&bt_mesh_col_obj_); } void game::Sector::ComputePortalVertex(Portal& portal, size_t idx, const glm::vec2& base_vert) @@ -82,7 +104,7 @@ static void LinkPortal(game::Portal& p1, game::Portal& p2) { // get rid of scale for (size_t i = 0; i < 3 ; i++) - p1.tr_orientation[i] = glm::normalize(p1.tr_basis[i]); + p1.tr_basis[i] = glm::normalize(p1.tr_basis[i]); p1.tr_scale = p2.scale / p1.scale; } @@ -108,18 +130,15 @@ void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2) namespace game { - struct PortalDetectingClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback + struct PortalIgnoringClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { using Super = btCollisionWorld::ClosestConvexResultCallback; - PortalDetectingClosestConvexResultCallback(const btVector3& convexFromWorld, const btVector3& convexToWorld) : + PortalIgnoringClosestConvexResultCallback(const btVector3& convexFromWorld, const btVector3& convexToWorld) : Super(convexFromWorld, convexToWorld) { } - const Portal* hit_portal = nullptr; - float hit_portal_fraction = std::numeric_limits::max(); - virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) override { void* ptr = convexResult.m_hitCollisionObject->getUserPointer(); @@ -127,21 +146,13 @@ namespace game if (data && data->type == CO_PORTAL) { - if (convexResult.m_hitFraction < hit_portal_fraction) - { - hit_portal = data->portal; - hit_portal_fraction = convexResult.m_hitFraction; - } + // Ignore portal hits + return m_closestHitFraction; } - else - { - Super::addSingleResult(convexResult, normalInWorldSpace); - } - - return 1.0f; // Continue processing other results + + return Super::addSingleResult(convexResult, normalInWorldSpace); } }; - } bool game::Sector::SweepCapsule( @@ -150,8 +161,7 @@ bool game::Sector::SweepCapsule( const glm::vec3& start, const glm::vec3& end, float& hit_fraction, - glm::vec3& hit_normal, - const Portal** hit_portal) + glm::vec3& hit_normal) { //const btVector3* bt_start = reinterpret_cast(&start); //const btVector3* bt_end = reinterpret_cast(&end); @@ -168,14 +178,9 @@ bool game::Sector::SweepCapsule( btTransform start_transform(bt_basis, bt_start); btTransform end_transform(bt_basis, bt_end); - PortalDetectingClosestConvexResultCallback result_callback(bt_start, bt_end); + PortalIgnoringClosestConvexResultCallback result_callback(bt_start, bt_end); - bt_world_.convexSweepTest(&capsule, start_transform, end_transform, result_callback); - - if (hit_portal) - { - *hit_portal = result_callback.hit_portal; - } + bt_world_.convexSweepTest(&capsule, start_transform, end_transform, result_callback, 0.0f); if (result_callback.hasHit()) { @@ -191,3 +196,55 @@ bool game::Sector::SweepCapsule( return false; } + +namespace game +{ + struct PortalContactResultCallback : public btCollisionWorld::ContactResultCallback + { + const game::Portal* portal = nullptr; + + PortalContactResultCallback() + { + } + + virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, + const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override + { + void* ptr0 = colObj0Wrap->getCollisionObject()->getUserPointer(); + void* ptr1 = colObj1Wrap->getCollisionObject()->getUserPointer(); + void* ptr = ptr0 ? ptr0 : ptr1; + CollisionObjectData* data = static_cast(ptr); + + if (data && data->type == CO_PORTAL) + { + // We hit a portal, store it + portal = data->portal; + } + + return 0.0f; // No specific action needed for contact results + } + }; +} + +const game::Portal* game::Sector::TestPortalContact(btCapsuleShapeZ& capsule, const glm::mat3& basis, const glm::vec3& pos) +{ + btCollisionObject capsule_obj; + capsule_obj.setCollisionShape(&capsule); + btTransform capsule_transform; + + btVector3 bt_pos(pos.x, pos.y, pos.z); + btMatrix3x3 bt_basis( + basis[0][0], basis[0][1], basis[0][2], + basis[1][0], basis[1][1], basis[1][2], + basis[2][0], basis[2][1], basis[2][2] + ); + + btTransform bt_transform(bt_basis, bt_pos); + + capsule_obj.setWorldTransform(bt_transform); + + PortalContactResultCallback result_callback; + bt_world_.contactTest(&capsule_obj, result_callback); + + return result_callback.portal; +} diff --git a/src/game/sector.hpp b/src/game/sector.hpp index b445c33..93a7692 100644 --- a/src/game/sector.hpp +++ b/src/game/sector.hpp @@ -7,9 +7,9 @@ namespace game { + class World; class Sector; class Portal; - class World; enum CollisionObjectType { @@ -41,8 +41,11 @@ namespace game glm::mat4 tr_position; // this sector space to other sector space glm::mat3 tr_basis; - glm::mat3 tr_orientation; float tr_scale; + + std::unique_ptr bt_col_shape; // Bullet collision shape + std::unique_ptr bt_col_obj; // Bullet collision object + CollisionObjectData col_data; }; class Sector @@ -56,13 +59,13 @@ namespace game std::vector portals_; std::map portal_map_; // Maps portal name to index in portals_ + btCollisionObject bt_mesh_col_obj_; + btDefaultCollisionConfiguration bt_col_cfg_; btCollisionDispatcher bt_col_dispatcher_; btDbvtBroadphase bt_broad_phase_; btCollisionWorld bt_world_; - btCollisionObject bt_mesh_col_obj_; - public: Sector(World* world, size_t idx, std::shared_ptr def); @@ -70,6 +73,7 @@ namespace game void ComputePortalVertex(Portal& portal, size_t idx, const glm::vec2& base_vert); public: + size_t GetIndex() const { return idx_; } const std::shared_ptr& GetMesh() const { return mesh_; } int GetPortalIndex(const std::string& name) const; @@ -84,8 +88,10 @@ namespace game const glm::vec3& start, const glm::vec3& end, float& hit_fraction, - glm::vec3& hit_normal, - const Portal** hit_portal); + glm::vec3& hit_normal); + + const Portal* TestPortalContact(btCapsuleShapeZ& capsule, const glm::mat3& basis, const glm::vec3& pos); + }; diff --git a/src/game/world.hpp b/src/game/world.hpp index 2501fa4..216965d 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -25,8 +25,10 @@ namespace game template T* Spawn(TArgs&&... args) { - auto& entity = entities_.emplace_back(std::make_unique(this, std::forward(args)...)); - return entity.get(); + auto entity = std::make_unique(this, std::forward(args)...); + T* entity_ptr = entity.get(); + entities_.push_back(std::move(entity)); + return entity_ptr; } Sector& GetSector(size_t idx) { return *sectors_[idx]; } diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp index 57f7204..525ef74 100644 --- a/src/gfx/renderer.cpp +++ b/src/gfx/renderer.cpp @@ -45,11 +45,14 @@ void gfx::Renderer::DrawWorld( float aspect, float fov) { + float near_plane = 0.001f; // Near plane distance + float far_plane = 500.0f; // Far plane distance + proj_ = glm::perspective( glm::radians(fov), // FOV aspect, // Aspect ratio - 0.01f, // Near plane - 1000.0f // Far plane + near_plane, // Near plane + far_plane // Far plane ); glm::mat4 view = glm::lookAt( @@ -58,6 +61,8 @@ void gfx::Renderer::DrawWorld( up // Up vector ); + min_portal_distance_ = near_plane * 2.0f; + glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); @@ -166,25 +171,39 @@ void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Porta return; // Portal AABB does not intersect with the screen AABB, skip } + const game::Portal& other_portal = *portal.link; + const game::Sector& other_sector = *other_portal.sector; + + glm::mat4 portal_mesh_trans = portal.trans; + glm::vec4 new_clip_plane = other_portal.plane; + + if (eye_plane_sd < min_portal_distance_) + { + float how_much_shift = min_portal_distance_ - eye_plane_sd; + + portal_mesh_trans = glm::translate(portal_mesh_trans, + glm::vec3(0.0f, how_much_shift, 0.0f)); // Shift portal mesh to avoid clipping + + new_clip_plane.w += how_much_shift; // Shift the clip plane as well + } + + // Open portal glStencilFunc(GL_EQUAL, params.recursion, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - DrawPortalPlane(params, portal, false); // Mark the portal plane in stencil buffer + DrawPortalPlane(params, portal, portal_mesh_trans, false); // Mark the portal plane in stencil buffer glStencilFunc(GL_EQUAL, params.recursion + 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glDepthFunc(GL_ALWAYS); - DrawPortalPlane(params, portal, true); // Clear the depth buffer for nested sector + DrawPortalPlane(params, portal, portal_mesh_trans, true); // Clear the depth buffer for nested sector glDepthFunc(GL_LESS); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - const game::Portal& other_portal = *portal.link; - const game::Sector& other_sector = *other_portal.sector; - DrawSectorParams new_params; new_params.sector = &other_sector; new_params.entry_portal = &other_portal; - new_params.clip_plane = other_portal.plane; // Use the portal plane as the clip plane + new_params.clip_plane = new_clip_plane; // Use the portal plane as the clip plane // Compute the new screen AABB based on the portal AABB and the current screen AABB new_params.screen_aabb = params.screen_aabb.Intersection(portal_aabb); @@ -201,7 +220,7 @@ void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Porta glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glStencilFunc(GL_EQUAL, params.recursion + 1, 0xFF); - DrawPortalPlane(params, portal, false); + DrawPortalPlane(params, portal, portal_mesh_trans, false); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } @@ -240,13 +259,13 @@ bool gfx::Renderer::ComputePortalScreenAABB(const game::Portal& portal, const gl return true; } -void gfx::Renderer::DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, bool clear_depth) +void gfx::Renderer::DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, const glm::mat4& trans, bool clear_depth) { 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, &portal.trans[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); diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp index b2699aa..97582c8 100644 --- a/src/gfx/renderer.hpp +++ b/src/gfx/renderer.hpp @@ -44,13 +44,17 @@ namespace gfx size_t max_portal_recursion_ = 24; + // Minimum distance to portal mesh from eye + // If the distance is smaller, portal plane is shifted to avoid clipping by near plane + float min_portal_distance_ = 0.01f; + glm::mat4 proj_; void DrawSector(const DrawSectorParams& params); void DrawPortal(const DrawSectorParams& params, const game::Portal& portal); static bool ComputePortalScreenAABB(const game::Portal& portal, const glm::mat4 view_proj, collision::AABB2& aabb); - void DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, bool clear_depth); + void DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, const glm::mat4& trans, bool clear_depth); }; } \ No newline at end of file