[Add] 3D orbit camera and refactor renderer.

This commit is contained in:
Ritchie Cunningham 2025-09-14 22:17:47 +01:00
parent a6646820c5
commit 9bf8b277ff
7 changed files with 139 additions and 9 deletions

View File

@ -6,4 +6,8 @@ struct Vec3 {
float x, y , z; float x, y , z;
}; };
inline Vec3 operator+(const Vec3& a, const Vec3& b) {
return {a.x + b.x, a.y + b.y, a.z + b.z};
}
} /* namespace BettolaMath. */ } /* namespace BettolaMath. */

View File

@ -2,6 +2,8 @@
#include <SDL3/SDL_error.h> #include <SDL3/SDL_error.h>
/* FINE LSP!! I'll play your games!!!! */ /* FINE LSP!! I'll play your games!!!! */
#include <SDL3/SDL_events.h> /* ~HJAPPY?!?!?! */ #include <SDL3/SDL_events.h> /* ~HJAPPY?!?!?! */
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_oldnames.h>
#include <SDL3/SDL_stdinc.h> #include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_timer.h> #include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h> #include <SDL3/SDL_video.h>
@ -10,6 +12,7 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <cstdlib> #include <cstdlib>
#include <cstdio> #include <cstdio>
#include "game/player.h"
#include "bettola.h" #include "bettola.h"
@ -78,6 +81,11 @@ void Bettola::process_events(void) {
while(SDL_PollEvent(&event)) { while(SDL_PollEvent(&event)) {
if(event.type == SDL_EVENT_QUIT) { if(event.type == SDL_EVENT_QUIT) {
_is_running = false; _is_running = false;
} else if(event.type == SDL_EVENT_MOUSE_MOTION) {
/* Y is inverted. */
_camera.process_mouse_movement((float)event.motion.xrel, (float)-event.motion.yrel);
} else if(event.type == SDL_EVENT_MOUSE_WHEEL) {
_camera.process_mouse_scroll((float)event.wheel.y);
} else if(event.type == SDL_EVENT_KEY_DOWN) { } else if(event.type == SDL_EVENT_KEY_DOWN) {
switch(event.key.key) { switch(event.key.key) {
case SDLK_W: _input.up = true; break; case SDLK_W: _input.up = true; break;
@ -102,6 +110,11 @@ void Bettola::update(double dt) {
float dir_y = (_input.down ? 1.0f : 0.0f) - (_input.up ? 1.0f : 0.0f); float dir_y = (_input.down ? 1.0f : 0.0f) - (_input.up ? 1.0f : 0.0f);
_game_client.get_player_for_write().set_velocity_direction(dir_x, dir_y); _game_client.get_player_for_write().set_velocity_direction(dir_x, dir_y);
/* Update camera to follow the player. */
const auto& player = _game_client.get_player();
BettolaMath::Vec3 player_pos = { player.get_x(), 0.0f, player.get_y() };
_camera.update(player_pos);
/* Process network messages and send input to the server. */ /* Process network messages and send input to the server. */
_game_client.process_network_messages(); _game_client.process_network_messages();
_game_client.send_input(_input, dt); _game_client.send_input(_input, dt);
@ -111,13 +124,15 @@ void Bettola::update(double dt) {
} }
void Bettola::render(void) { void Bettola::render(void) {
_renderer.render(_game_client.get_player(), _game_client.get_remote_players()); _renderer.render(_camera.get_view_matrix(), _game_client.get_player(),
_game_client.get_remote_players());
SDL_GL_SwapWindow(_window); SDL_GL_SwapWindow(_window);
} }
bool Bettola::create_window(void) { bool Bettola::create_window(void) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
/* Don't you dare f.ckin fail to open!!!!! */ /* Don't you dare f.ckin fail to open!!!!! */
@ -129,6 +144,9 @@ bool Bettola::create_window(void) {
return false; return false;
} }
/* Capture mouse cursor. */
SDL_SetWindowRelativeMouseMode(_window, true);
return true; return true;
} }

View File

@ -4,6 +4,7 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "graphics/renderer.h" #include "graphics/renderer.h"
#include "graphics/camera.h"
#include "game_client.h" #include "game_client.h"
class Bettola { class Bettola {
@ -29,5 +30,6 @@ private:
Renderer _renderer; Renderer _renderer;
GameClient _game_client; GameClient _game_client;
Camera _camera;
Player::InputState _input; Player::InputState _input;
}; };

76
src/graphics/camera.cpp Normal file
View File

@ -0,0 +1,76 @@
#include <cmath>
#include "math/mat4.h"
#include "math/vec3.h"
#include "camera.h"
Camera::Camera(void) :
_position({0.0f, 0.0f, 0.0f}),
_front({0.0f, 0.0f, -1.0f}),
_up({0.0f, 1.0f, 0.0f}),
_right({0.0f, 0.0f, 0.0f}),
_world_up({0.0f, 1.0f, -1.0f}),
_yaw(-90.0f),
_pitch(0.0f),
_distance(10.0f),
_mouse_sensitivity(0.1f),
_zoom_sensitivity(0.5f) {
_update_camera_vectors();
}
void Camera::update(const BettolaMath::Vec3& target_pos) {
_position.x = target_pos.x - _front.x * _distance;
_position.y = target_pos.y - _front.y * _distance;
_position.z = target_pos.z - _front.z * _distance;
}
void Camera::process_mouse_movement(float x_offset, float y_offset) {
_yaw += x_offset * _mouse_sensitivity;
_pitch += y_offset * _mouse_sensitivity;
/* Constrain pitch. */
if(_pitch > 89.0f) _pitch = 89.0f;
if(_pitch < -89.0f) _pitch = -89.0f;
_update_camera_vectors();
}
void Camera::process_mouse_scroll(float y_offset) {
_distance -= y_offset * _zoom_sensitivity;
if(_distance < 2.0f) _distance = 2.0f;
if(_distance > 20.0f) _distance = 20.0f;
}
BettolaMath::Mat4 Camera::get_view_matrix(void) const {
return BettolaMath::Mat4::look_at(_position, _position + _front, _up);
}
void Camera::_update_camera_vectors(void) {
BettolaMath::Vec3 front;
float yaw_rad = _yaw*M_PI/180.0f;
float pitch_rad = _pitch*M_PI/180.0f;
front.x = cos(yaw_rad) * cos(pitch_rad);
front.y = sin(pitch_rad);
front.z = sin(yaw_rad) * cos(pitch_rad);
/* Normalise the front vector. */
float f_mag = sqrt(front.x*front.x + front.y*front.y + front.z*front.z);
_front.x = front.x / f_mag;
_front.y = front.y / f_mag;
_front.z = front.z / f_mag;
_right.x = _front.y * _world_up.z - _front.z * _world_up.y;
_right.y = _front.z * _world_up.x - _front.x * _world_up.z;
_right.z = _front.x * _world_up.y - _front.y * _world_up.x;
float r_mag = sqrt(_right.x*_right.x + _right.y*_right.y + _right.z*_right.z);
_right.x /= r_mag; _right.y /= r_mag; _right.z /= r_mag;
/* Up vector. */
_up.x = _right.y * _front.z - _right.z * _front.y;
_up.y = _right.z * _front.x - _right.x * _front.z;
_up.z = _right.x * _front.y - _right.y * _front.x;
}

32
src/graphics/camera.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include "math/mat4.h"
#include "math/vec3.h"
class Camera {
public:
Camera(void);
void update(const BettolaMath::Vec3& target_pos);
void process_mouse_movement(float x_offset, float y_offset);
void process_mouse_scroll(float y_offset);
BettolaMath::Mat4 get_view_matrix(void) const;
private:
void _update_camera_vectors(void);
float _yaw;
float _pitch;
float _distance;
BettolaMath::Vec3 _position;
BettolaMath::Vec3 _front;
BettolaMath::Vec3 _up;
BettolaMath::Vec3 _right;
BettolaMath::Vec3 _world_up;
float _mouse_sensitivity;
float _zoom_sensitivity;
};

View File

@ -1,6 +1,7 @@
#include <GL/glew.h> #include <GL/glew.h>
#include <cstdio> #include <cstdio>
#include <cmath> #include <cmath>
#include "game/player.h"
#include <SDL3/SDL_timer.h> #include <SDL3/SDL_timer.h>
#ifndef M_PI #ifndef M_PI
@ -134,7 +135,8 @@ bool Renderer::init(int screen_width, int screen_height) {
return true; return true;
} }
void Renderer::render(const Player& player, const std::vector<RemotePlayer>& remote_players) { void Renderer::render(const BettolaMath::Mat4& view_matrix, const Player& player,
const std::vector<RemotePlayer>& remote_players) {
glClearColor(0.1f, 0.1f, 0.3f, 1.0f); glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Need to clear depth buffer too. */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Need to clear depth buffer too. */
GL_CHECK_ERROR(); GL_CHECK_ERROR();
@ -142,11 +144,6 @@ void Renderer::render(const Player& player, const std::vector<RemotePlayer>& rem
_shader.use(); _shader.use();
GL_CHECK_ERROR(); GL_CHECK_ERROR();
/* Make the camera stalk the player. */
BettolaMath::Vec3 camera_pos = { player.get_x(), 5.0f, player.get_y() + 8.0f };
BettolaMath::Vec3 player_pos = { player.get_x(), 0.0f, player.get_y() };
BettolaMath::Mat4 view = BettolaMath::Mat4::look_at(camera_pos, player_pos, {0.0f,1.0f,0.0f});
BettolaMath::Mat4 projection = BettolaMath::Mat4::perspective((45.0f * M_PI) / 180.0f, 800.0f/600.0f, 0.1f, 100.0f); BettolaMath::Mat4 projection = BettolaMath::Mat4::perspective((45.0f * M_PI) / 180.0f, 800.0f/600.0f, 0.1f, 100.0f);
GLint view_loc = glGetUniformLocation(_shader.get_id(), "view"); GLint view_loc = glGetUniformLocation(_shader.get_id(), "view");
@ -154,7 +151,7 @@ void Renderer::render(const Player& player, const std::vector<RemotePlayer>& rem
GLint model_loc = glGetUniformLocation(_shader.get_id(), "model"); GLint model_loc = glGetUniformLocation(_shader.get_id(), "model");
GLint color_loc = glGetUniformLocation(_shader.get_id(), "overrideColor"); GLint color_loc = glGetUniformLocation(_shader.get_id(), "overrideColor");
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view.get_ptr()); glUniformMatrix4fv(view_loc, 1, GL_FALSE, view_matrix.get_ptr());
GL_CHECK_ERROR(); GL_CHECK_ERROR();
glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection.get_ptr()); glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection.get_ptr());
GL_CHECK_ERROR(); GL_CHECK_ERROR();

View File

@ -12,7 +12,8 @@ public:
~Renderer(void); ~Renderer(void);
bool init(int screen_width, int screen_height); bool init(int screen_width, int screen_height);
void render(const Player& player, const std::vector<RemotePlayer>& remote_players); void render(const BettolaMath::Mat4& view_matrix, const Player& player,
const std::vector<RemotePlayer>& remote_players);
private: private:
bool _init_shaders(); bool _init_shaders();