Files
LearningOpenGL/Player.cpp
2025-08-15 16:12:18 -04:00

303 lines
8.1 KiB
C++

#include "Player.h"
#include "Camera.h"
#include "LevelMap.h"
#include "PlayerInputComponent.h"
#include <iostream>
Player::Player(glm::vec3 position, glm::vec3 rotation, std::vector<Sector>& sectors, GLFWwindow* Window)
: GameObject(position, rotation, glm::vec3(1.0f, 1.0f, 1.0f)),
PlayerCamera(std::make_unique<Camera>(Window)),
cPlayerInput(std::make_unique<PlayerInputComponent>(Window, *this)),
LevelSectors(sectors),
CurrentSector(&sectors[0])
{
PlayerCamera->SetOffset(glm::vec3(0.0f, 1.0f, 0.0f));
PlayerCamera->MoveCamera(Position);
PlayerCamera->Rotation = Rotation;
}
void Player::Update(float DeltaTime)
{
GameObject::Update(DeltaTime);
cPlayerInput->Update(DeltaTime);
if (bJumping)
{
Position.y += JumpVelocity * DeltaTime;
JumpVelocity -= GRAVITY * DeltaTime;
if (Position.y <= CurrentSector->floor_height)
{
JumpVelocity = 0.0f;
Position.y = CurrentSector->floor_height;
bJumping = false;
std::cout << "STOP JUMPING" << std::endl;
}
}
else if (Position.y > CurrentSector->floor_height)
{
Position.y -= GRAVITY * DeltaTime;
}
else
{
Position.y = CurrentSector->floor_height;
bJumping = false;
}
PlayerCamera->MoveCamera(Position);
}
void Player::Move(EInputDirection Direction, float Delta)
{
float Velocity = bIsRunning ? RunSpeed : WalkSpeed;
Velocity = Velocity * Delta;
glm::vec3 newPosition = Position;
if (Direction == FORWARD)
newPosition += Front * Velocity;
if (Direction == BACKWARD)
newPosition -= Front * Velocity;
if (Direction == LEFT)
newPosition -= Right * Velocity;
if (Direction == RIGHT)
newPosition += Right * Velocity;
if (!CrossingWall(newPosition))
{
Position = newPosition;
}
for (Sector& Sector : LevelSectors)
{
glm::vec2 playerPoint = { Position.x, Position.z };
if (PointInPolygon(playerPoint, Sector.walls))
{
/*if (!bJumping)
Position.y = Sector.floor_height;*/
if (CurrentSector != &Sector)
{
std::cout << "Updating CurrentSector ("
<< (CurrentSector ? CurrentSector->index : -1)
<< ") to Sector (" << Sector.index << ")" << std::endl;
CurrentSector = &Sector;
}
break;
// TO DO: Pretty sure this limits me for sectors that contain sectors, i.e. if I want multiple floor/ceiling heights within a sector it will have to be made of multiple sectors
//std::cout << "Player in: " << sector.index << " | Player Y: " << sector.floor_height << std::endl;
}
}
}
void Player::Jump()
{
if (bJumping) return;
//std::cout << "JUMP!" << std::endl;
bJumping = true;
JumpVelocity = JumpForce;
}
void Player::Sprint()
{
bIsRunning = true;
}
void Player::StopSprint()
{
bIsRunning = false;
}
void Player::Look(float Yaw, float Pitch, GLboolean bConstrainPitch)
{
Yaw *= MOUSE_SENSITIVITY;
Pitch *= MOUSE_SENSITIVITY;
Rotation.y += Yaw;
PlayerCamera->RotateCamera(Yaw, Pitch);
UpdateVectors();
}
glm::mat4 Player::GetViewMatrix() const
{
return PlayerCamera->GetViewMatrix();
}
bool Player::CrossingWall(glm::vec3 NewPosition)
{
// Convert 3D positions to 2D (XZ plane) for wall collision
glm::vec2 CurrentPosition = glm::vec2(Position.x, Position.z);
glm::vec2 NextPosition = glm::vec2(NewPosition.x, NewPosition.z);
//bool bCrossed = false;
// Check each wall in the sector
for (const Wall& Wall : CurrentSector->walls)
{
glm::vec2 WallStart = Wall.a;
glm::vec2 WallEnd = Wall.b;
// Check if movement line segment intersects with wall line segment
if (LineSegmentsIntersect(CurrentPosition, NextPosition, WallStart, WallEnd))
{
if (Wall.portal == 0)
{
return true; // Collision detected
}
else if (CanFitInNextSector(LevelSectors[Wall.portal-1]))// check if valid portal (floor height < player y and floor and ceiling tall enough for player)
{
CurrentSector = &LevelSectors[Wall.portal - 1];
return false;
}
else
{
return true;
}
//return true; // Collision detected
}
}
//if (bCrossed) return bCrossed; // breakout early, attempting to move through wall
// Check outward facing sectors to ensure not moving into pillars
for (const Sector& Sector : LevelSectors)
{
if (!Sector.outward_facing)
{
continue;
}
// Check each wall in the sector
for (const Wall& Wall : Sector.walls)
{
glm::vec2 WallStart = Wall.a;
glm::vec2 WallEnd = Wall.b;
// Check if movement line segment intersects with wall line segment
if (LineSegmentsIntersect(CurrentPosition, NextPosition, WallStart, WallEnd))
{
//if (Wall.portal != 0)
//{
// return false; // Collision but to another sector
//}
return true; // Collision detected
}
}
}
return false; // No collisions detected
}
bool Player::LineSegmentsIntersect(const glm::vec2& StartPosition, const glm::vec2& EndPosition, const glm::vec2& WallPointA, const glm::vec2& WallPointB)
{
// Implementation of the cross product test for line segment intersection
auto cross = [](const glm::vec2& a, const glm::vec2& b) {
return a.x * b.y - a.y * b.x;
};
glm::vec2 PlayerNormal = EndPosition - StartPosition;
glm::vec2 WallNormal = WallPointB - WallPointA;
glm::vec2 WallToPlayerNormal = WallPointA - StartPosition;
float PlayerWallCross = cross(PlayerNormal, WallNormal);
// If lines are not parallel
if (std::abs(PlayerWallCross) >= std::numeric_limits<float>::epsilon())
{
float t = cross(WallToPlayerNormal, PlayerNormal) / PlayerWallCross;
float u = cross(WallToPlayerNormal, WallNormal) / PlayerWallCross;
// If intersection point is on both segments
if ((t >= 0 && t <= 1) && (u >= 0 && u <= 1))
return true;
}
// If no intersection, check distance from EndPosition to wall segment
return PointToLineSegmentDistance(EndPosition, WallPointA, WallPointB) <= 1.0f;
}
float Player::PointToLineSegmentDistance(const glm::vec2& point, const glm::vec2& segA, const glm::vec2& segB)
{
glm::vec2 segVec = segB - segA;
float segLengthSquared = glm::dot(segVec, segVec);
// If segment is actually a point
if (segLengthSquared < std::numeric_limits<float>::epsilon())
return glm::distance(point, segA);
// Project point onto the segment
float t = glm::dot(point - segA, segVec) / segLengthSquared;
t = glm::clamp(t, 0.0f, 1.0f);
glm::vec2 projection = segA + t * segVec;
return glm::distance(point, projection);
}
// Checking if a point is inside a polygon
bool Player::PointInPolygon(glm::vec2 point, std::vector<Wall> polygon)
{
int num_vertices = polygon.size();
double x = point.x, y = point.y;
bool inside = false;
// Store the first point in the polygon and initialize
// the second point
glm::vec2 p1 = polygon[0].a, p2;
// Loop through each edge in the polygon
for (int i = 1; i <= num_vertices; i++) {
// Get the next point in the polygon
p2 = polygon[i % num_vertices].a;
// Check if the point is above the minimum y
// coordinate of the edge
if (y > std::min(p1.y, p2.y)) {
// Check if the point is below the maximum y
// coordinate of the edge
if (y <= std::max(p1.y, p2.y)) {
// Check if the point is to the left of the
// maximum x coordinate of the edge
if (x <= std::max(p1.x, p2.x)) {
// Calculate the x-intersection of the
// line connecting the point to the edge
double x_intersection
= (y - p1.y) * (p2.x - p1.x)
/ (p2.y - p1.y)
+ p1.x;
// Check if the point is on the same
// line as the edge or to the left of
// the x-intersection
if (p1.x == p2.x
|| x <= x_intersection) {
// Flip the inside flag
inside = !inside;
}
}
}
}
// Store the current point as the first point for
// the next iteration
p1 = p2;
}
// Return the value of the inside flag
return inside;
}
bool Player::CanFitInNextSector(Sector Sector)
{
std::cout << "Checking sector " << Sector.index << " | Floor height : " << Sector.floor_height << " | Player Y : " << Position.y << " | Sector height : " << Sector.ceiling_height - Sector.floor_height << std::endl;
if (Position.y < Sector.floor_height - STEP_HEIGHT)
{
return false;
}
if (Sector.ceiling_height - Sector.floor_height < PLAYER_HEIGHT)
{
return false;
}
return true;
}