mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
feat: player rendering (#27)
* refactor(player): extract player rendering into PlayerRenderer class Move player rendering code from Renderer to new PlayerRenderer class. Add normal and tangent vertex attributes for improved per-pixel lighting. * feat(player-renderer): add shadow rendering and include local player data * feat(gameplay): add player yaw/pitch synchronization and rendering * refactor(player_renderer): split rendering into per-body-part VAOs and VBOs Move `PlayerVertex` struct to header and replace single VAO/VBO with arrays of 6 parts. Implement head pitch rotation in `render()` and `shadow_render()` by applying a separate model matrix for the head (index 0). Remove old `vao()` and `sum()` accessors. Adjust model translation for consistency. * feat(player): add texture mapping to player model using skin * fix(player-renderer): correct player rendering rotation and texture coordinates * feat(gameplay): add player gait system with limb animation * fix(player): update leg UVs and remove yaw wrap Update left and right leg UV coordinates to align with updated player texture. Remove yaw modulo operation to allow unrestricted yaw rotation beyond 360 degrees. * feat(camera): add third-person perspective and walk animation - Introduce Perspective enum (FIRST_PERSON, THIRD_PERSON_BACK, THIRD_PERSON_FRONT) and m_front member for camera direction. - Handle F5 key to cycle perspectives. - Add angle and walk_time to ClientPlayer for walking animation. - Update player rendering to skip local player only in first person and fix rotation sign. - Remove redundant player info reporting; update player data with animation angle. * fix(camera): replace magic number with constant and fix missing break * fix(client-player): stop sprinting on forward key release * feat: add camera collision and sphere collision for world * fix(skin): update player001 texture * refactor(gameplay): improve thread safety and remove debug logging * refactor(shaders): extract shadow and normal logic into include files and add lighting to player shaders * chore(shader): remove unused uniforms cameraPos and specularStrength * fix: inlcude <array> header
This commit is contained in:
@@ -12,11 +12,21 @@ class ClientPlayer;
|
||||
|
||||
class Camera {
|
||||
private:
|
||||
enum class Perspective {
|
||||
FIRST_PERSON,
|
||||
THIRD_PERSON_BACK,
|
||||
THIRD_PERSON_FRONT,
|
||||
};
|
||||
|
||||
bool m_firse_mouse = true;
|
||||
ClientPlayer* m_player;
|
||||
float m_last_mouse_x, m_last_mouse_y;
|
||||
glm::vec3 m_camera_pos;
|
||||
bool m_under_water = false;
|
||||
Perspective m_perspective = Perspective::FIRST_PERSON;
|
||||
glm::vec3 m_front;
|
||||
glm::vec3 camera_collision(glm::vec3 start, glm::vec3 end,
|
||||
float radius = 0.2f);
|
||||
|
||||
public:
|
||||
Camera();
|
||||
@@ -33,6 +43,8 @@ public:
|
||||
|
||||
bool is_under_water() const;
|
||||
glm::vec3 get_camera_front() const;
|
||||
void change_perspective();
|
||||
bool is_first_person() const;
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Cubed/gameplay/block.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/game_mode.hpp"
|
||||
#include "Cubed/gameplay/player.hpp"
|
||||
#include "Cubed/input.hpp"
|
||||
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
@@ -11,7 +12,7 @@
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
namespace Cubed {
|
||||
enum class Gait { WALK = 0, RUN };
|
||||
|
||||
class ClientWorld;
|
||||
class ClientPlayer {
|
||||
public:
|
||||
@@ -25,7 +26,7 @@ public:
|
||||
|
||||
static AABB get_aabb(const glm::vec3& pos);
|
||||
const glm::vec3& get_front() const;
|
||||
const Gait& get_gait() const;
|
||||
Gait get_gait() const;
|
||||
const std::optional<LookBlock>& get_look_block_pos() const;
|
||||
// thread safe
|
||||
glm::vec3 get_player_pos() const;
|
||||
@@ -50,17 +51,25 @@ public:
|
||||
|
||||
unsigned place_block() const;
|
||||
|
||||
Gait& gait();
|
||||
void set_gait(Gait gait);
|
||||
GameMode& game_mode();
|
||||
|
||||
const ClientWorld& get_world() const;
|
||||
|
||||
void set_uuid(std::string_view uuid);
|
||||
const std::string& get_uuid() const;
|
||||
std::string get_uuid() const;
|
||||
const std::string& get_name() const;
|
||||
|
||||
void init(std::string_view name);
|
||||
|
||||
float yaw() const;
|
||||
float pitch() const;
|
||||
float& angle();
|
||||
float& walk_time();
|
||||
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
|
||||
glm::ivec3& block_pos, glm::vec3& normal,
|
||||
float distance = 4.0f);
|
||||
|
||||
private:
|
||||
using enum GameMode;
|
||||
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
|
||||
@@ -70,8 +79,8 @@ private:
|
||||
float m_g = DEFAULT_G;
|
||||
constexpr static float MAX_SPACE_ON_TIME = 0.3f;
|
||||
|
||||
float m_yaw = 0.0f;
|
||||
float m_pitch = 0.0f;
|
||||
std::atomic<float> m_yaw = 0.0f;
|
||||
std::atomic<float> m_pitch = 0.0f;
|
||||
|
||||
float m_sensitivity = 0.15f;
|
||||
|
||||
@@ -88,6 +97,9 @@ private:
|
||||
|
||||
unsigned m_place_block = 1;
|
||||
|
||||
bool m_moving = false;
|
||||
bool m_sprinting = false;
|
||||
|
||||
glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
glm::vec3 move_distance{0.0f, 0.0f, 0.0f};
|
||||
// player is tow block tall, the pos is the lower pos
|
||||
@@ -99,20 +111,21 @@ private:
|
||||
glm::vec3 m_right{0, 0, 0};
|
||||
static constexpr glm::vec3 M_SIZE{0.6f, 1.8f, 0.6f};
|
||||
|
||||
Gait m_gait = Gait::WALK;
|
||||
std::atomic<Gait> m_gait = Gait::STOP;
|
||||
MoveState m_move_state{};
|
||||
GameMode m_game_mode = CREATIVE;
|
||||
std::optional<LookBlock> m_look_block = std::nullopt;
|
||||
std::string m_name{};
|
||||
mutable std::shared_mutex m_uuid_mutex;
|
||||
std::string m_uuid;
|
||||
ClientWorld& m_world;
|
||||
|
||||
float m_angle{0.0f};
|
||||
float m_walk_time{0.0f};
|
||||
|
||||
mutable std::shared_mutex m_player_pos_mutex;
|
||||
mutable std::shared_mutex m_chunk_pos_mutex;
|
||||
ChunkPosSet m_player_chunk_pos_set;
|
||||
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
|
||||
glm::ivec3& block_pos, glm::vec3& normal,
|
||||
float distance = 4.0f);
|
||||
|
||||
void update_direction();
|
||||
void update_lookup_block();
|
||||
@@ -121,5 +134,6 @@ private:
|
||||
void update_y_move(glm::vec3& player_pos);
|
||||
void update_z_move(glm::vec3& player_pos);
|
||||
void update_player_chunk();
|
||||
Gait compute_gait() const;
|
||||
};
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -14,15 +14,28 @@
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
namespace Cubed {
|
||||
|
||||
struct RemotePlayerInfo {
|
||||
struct PlayerInfo {
|
||||
std::string name;
|
||||
std::string uuid;
|
||||
glm::vec3 render_pos;
|
||||
glm::vec3 target_pos;
|
||||
float render_yaw;
|
||||
float yaw;
|
||||
float render_pitch;
|
||||
float pitch;
|
||||
Gait gait;
|
||||
float angle = 0.0f;
|
||||
float walk_time = 0.0f;
|
||||
};
|
||||
|
||||
struct RemotePlayerRenderData {
|
||||
struct PlayerRenderData {
|
||||
std::string name;
|
||||
std::string uuid;
|
||||
glm::vec3 render_pos;
|
||||
float yaw;
|
||||
float pitch;
|
||||
Gait gait;
|
||||
float angle;
|
||||
};
|
||||
|
||||
class ClientWorld {
|
||||
@@ -34,6 +47,7 @@ public:
|
||||
void update(float delta_time);
|
||||
const std::optional<LookBlock>& get_look_block_pos() const;
|
||||
ClientPlayer& get_player();
|
||||
const ClientPlayer& get_player() const;
|
||||
int get_block(const glm::ivec3& block_pos) const;
|
||||
bool is_solid(const glm::ivec3& block_pos) const;
|
||||
bool can_pass_block(const glm::ivec3& block_pos) const;
|
||||
@@ -65,13 +79,17 @@ public:
|
||||
void request_chunk();
|
||||
std::vector<glm::vec4>& planes();
|
||||
const std::vector<const ChunkRenderSnapshot*>& render_snapshots() const;
|
||||
const std::vector<RemotePlayerRenderData>& render_player_data() const;
|
||||
const std::vector<PlayerRenderData>& render_player_data() const;
|
||||
std::vector<PlayerRenderData>& render_player_data();
|
||||
|
||||
glm::vec3 sunlight_dir() const;
|
||||
bool sphere_collide_world(glm::vec3 center, float radius) const;
|
||||
void receive_chunk(std::vector<uint8_t> data, PacketHeader header);
|
||||
void request_exit();
|
||||
bool is_receive_exit();
|
||||
int chunk_size() const;
|
||||
static AABB get_block_aabb(const glm::ivec3& pos);
|
||||
|
||||
template <typename Fn>
|
||||
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
||||
m_timers.emplace(std::piecewise_construct,
|
||||
@@ -86,21 +104,20 @@ private:
|
||||
ChunkPos::TBBHash>;
|
||||
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
|
||||
using ChunkPosVector = std::vector<ChunkPos>;
|
||||
using OtherPlayerHashMap =
|
||||
std::unordered_map<std::string, RemotePlayerInfo>;
|
||||
using OtherPlayerHashMap = std::unordered_map<std::string, PlayerInfo>;
|
||||
using chunk_acc = ChunkHashMap::accessor;
|
||||
using chunk_cacc = ChunkHashMap::const_accessor;
|
||||
static constexpr int WORLD_EXIT_TIMEOUT = 200;
|
||||
static constexpr int MAX_UPLOAD_CHUNK_SUM = 16;
|
||||
ClientPlayer m_player;
|
||||
OtherPlayerHashMap m_other_players;
|
||||
OtherPlayerHashMap m_player_info;
|
||||
ChunkHashMap m_chunks;
|
||||
std::vector<glm::vec4> m_planes;
|
||||
std::jthread m_client_thread;
|
||||
|
||||
std::mutex m_delete_vbo_mutex;
|
||||
std::mutex m_delete_vao_mutex;
|
||||
mutable std::shared_mutex m_other_players_mutex;
|
||||
mutable std::shared_mutex m_player_info_mutex;
|
||||
|
||||
tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue;
|
||||
tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue;
|
||||
@@ -110,7 +127,7 @@ private:
|
||||
|
||||
std::deque<ChunkPos> m_dirty_queue;
|
||||
std::vector<const ChunkRenderSnapshot*> m_render_snapshots;
|
||||
std::vector<RemotePlayerRenderData> m_render_player_data;
|
||||
std::vector<PlayerRenderData> m_render_player_data;
|
||||
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
||||
std::atomic<bool> m_game_running{false};
|
||||
std::atomic<bool> m_receive_exit{false};
|
||||
@@ -127,9 +144,7 @@ private:
|
||||
|
||||
void client_run(std::stop_token token);
|
||||
|
||||
void set_player_pos();
|
||||
|
||||
void report_player_pos();
|
||||
void report_player_info();
|
||||
|
||||
void set_block(const glm::ivec3& pos, unsigned id);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ enum class PacketEnum : uint16_t {
|
||||
LOGOUT_REQ = 1003,
|
||||
LOGOUT_RSP = 1004,
|
||||
PLAYER_INFO = 2001,
|
||||
PLAYER_POS = 2002,
|
||||
C2S_PLAYER_INFO = 2002,
|
||||
PLAYER_INFO_RSP = 2003,
|
||||
CHUNK_DATA_REQ = 3001,
|
||||
CHUNK_DATA_RSP = 3002,
|
||||
@@ -87,8 +87,8 @@ template <> constexpr uint16_t get_packet_id<LogoutRsp>() {
|
||||
template <> constexpr uint16_t get_packet_id<PlayerInfo>() {
|
||||
return std::to_underlying(PacketEnum::PLAYER_INFO);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<PlayerPos>() {
|
||||
return std::to_underlying(PacketEnum::PLAYER_POS);
|
||||
template <> constexpr uint16_t get_packet_id<C2S_PlayerInfo>() {
|
||||
return std::to_underlying(PacketEnum::C2S_PLAYER_INFO);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<PlayerInfoRsp>() {
|
||||
return std::to_underlying(PacketEnum::PLAYER_INFO_RSP);
|
||||
|
||||
21
include/Cubed/gameplay/player.hpp
Normal file
21
include/Cubed/gameplay/player.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
namespace Cubed {
|
||||
enum class Gait { STOP = 0, WALK = 1, RUN = 2 };
|
||||
constexpr int get_gait_id(Gait gait) { return std::to_underlying(gait); }
|
||||
|
||||
inline Gait get_gait_from_id(int id) {
|
||||
switch (id) {
|
||||
case std::to_underlying(Gait::STOP):
|
||||
return Gait::STOP;
|
||||
case std::to_underlying(Gait::WALK):
|
||||
return Gait::WALK;
|
||||
case std::to_underlying(Gait::RUN):
|
||||
return Gait::RUN;
|
||||
default:
|
||||
throw std::runtime_error("Unknown Gait");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/player.hpp"
|
||||
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <atomic>
|
||||
@@ -38,6 +39,14 @@ public:
|
||||
const ChunkPosSet& get_chunk_pos_set() const;
|
||||
ChunkPosSet& get_chunk_pos_set();
|
||||
|
||||
void set_yaw(float yaw);
|
||||
void set_pitch(float pitch);
|
||||
float yaw() const;
|
||||
float pitch() const;
|
||||
|
||||
Gait gait() const;
|
||||
void set_gait(Gait gait);
|
||||
|
||||
private:
|
||||
static constexpr TickType TIMEOUT = 200;
|
||||
std::string m_name;
|
||||
@@ -45,9 +54,12 @@ private:
|
||||
glm::vec3 m_pos{0.0f};
|
||||
ServerWorld& m_world;
|
||||
ChunkPos m_last_chunk_pos{0, 0};
|
||||
std::shared_ptr<Session> m_session;
|
||||
std::atomic<std::shared_ptr<Session>> m_session;
|
||||
std::atomic<TickType> m_last_gametick{0};
|
||||
std::atomic<int> m_chunk_task_id{0};
|
||||
std::atomic<float> m_yaw{0.0f};
|
||||
std::atomic<float> m_pitch{0.0f};
|
||||
std::atomic<Gait> m_gait;
|
||||
mutable std::shared_mutex m_chunk_pos_mutex;
|
||||
ChunkPosSet m_player_chunk_pos_set;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Cubed/gameplay/cave_carver.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/packet.hpp" // IWYU pragma: keep
|
||||
#include "Cubed/gameplay/river_worm.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
#include "Cubed/gameplay/server_player.hpp"
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
|
||||
bool set_block(const glm::ivec3& block_pos, unsigned id);
|
||||
|
||||
void sync_player_pos(const std::string& uuid, float x, float y, float z);
|
||||
void sync_player_pos(const C2S_PlayerInfo& rsp);
|
||||
void handle_player_login(const std::string& player_name,
|
||||
std::shared_ptr<Session> session);
|
||||
glm::vec3 get_player_pos(const std::string& uuid) const;
|
||||
@@ -136,7 +137,7 @@ private:
|
||||
std::atomic<int> m_gen_pool_threads{0};
|
||||
std::atomic<int> m_net_pool_threads{0};
|
||||
std::atomic<int> m_max_threads{1};
|
||||
|
||||
std::atomic<size_t> m_player_sum{0};
|
||||
std::atomic<TickType> m_game_ticks{0};
|
||||
std::atomic<TickType> m_day_tick{6000};
|
||||
std::atomic<bool> m_tick_running{true};
|
||||
|
||||
32
include/Cubed/player_renderer.hpp
Normal file
32
include/Cubed/player_renderer.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "Cubed/shader.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace Cubed {
|
||||
class Renderer;
|
||||
class PlayerRenderer {
|
||||
public:
|
||||
static constexpr int BODY_PART_NUM = 6;
|
||||
PlayerRenderer(Renderer& renderer);
|
||||
~PlayerRenderer();
|
||||
void init();
|
||||
void render(const Shader& shader);
|
||||
void shadow_render(const Shader& shader, glm::mat4& light_matrix);
|
||||
|
||||
private:
|
||||
struct PlayerVertex {
|
||||
|
||||
float x = 0.0f, y = 0.0f, z = 0.0f;
|
||||
float s = 0.0f, t = 0.0f;
|
||||
float nx = 0.0f, ny = 0.0f, nz = 0.0f;
|
||||
float tx = 0.0f, ty = 0.0f, tz = 0.0f;
|
||||
};
|
||||
Renderer& m_renderer;
|
||||
std::array<GLuint, BODY_PART_NUM> m_vao;
|
||||
std::array<GLuint, BODY_PART_NUM> m_vbo;
|
||||
bool m_inited{false};
|
||||
std::array<std::vector<PlayerVertex>, BODY_PART_NUM> m_vertices;
|
||||
};
|
||||
} // namespace Cubed
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/constants.hpp"
|
||||
#include "Cubed/player_renderer.hpp"
|
||||
#include "Cubed/primitive_data.hpp"
|
||||
#include "Cubed/shader.hpp"
|
||||
#include "Cubed/ui/text.hpp"
|
||||
@@ -50,6 +51,14 @@ public:
|
||||
float& underwater_fog_density();
|
||||
float& water_density();
|
||||
|
||||
const Camera& camera() const;
|
||||
const ClientWorld& world() const;
|
||||
ClientWorld& world();
|
||||
const glm::mat4& proj_mat() const;
|
||||
const TextureManager& texture_mamger() const;
|
||||
|
||||
float delta_time() const;
|
||||
|
||||
private:
|
||||
struct ParallelLight {
|
||||
glm::vec3 sundir; // direction from sun to vertex
|
||||
@@ -92,7 +101,7 @@ private:
|
||||
DevPanel& m_dev_panel;
|
||||
const TextureManager& m_texture_manager;
|
||||
ClientWorld& m_world;
|
||||
|
||||
PlayerRenderer m_player_renderer;
|
||||
bool m_discard_tranparent = true;
|
||||
bool m_shader_on = true;
|
||||
bool m_water_perturb = true;
|
||||
@@ -174,7 +183,6 @@ private:
|
||||
2 - outline vao
|
||||
3 - ui vao
|
||||
4 - text vao
|
||||
5 - player vao
|
||||
*/
|
||||
std::vector<GLuint> m_vao;
|
||||
std::vector<Vertex2D> m_ui;
|
||||
|
||||
@@ -16,6 +16,8 @@ private:
|
||||
GLuint m_normal_texture_array = 0;
|
||||
GLfloat m_max_aniso = 0.0f;
|
||||
|
||||
GLuint m_skin = 0;
|
||||
|
||||
int m_aniso = 1;
|
||||
|
||||
std::vector<GLuint> m_item_textures;
|
||||
@@ -30,6 +32,7 @@ private:
|
||||
void init_block();
|
||||
void init_ui();
|
||||
void init_block_status();
|
||||
void init_skin();
|
||||
void hot_reload();
|
||||
|
||||
public:
|
||||
@@ -43,6 +46,7 @@ public:
|
||||
GLuint get_ui_array() const;
|
||||
GLuint get_pbr_texture() const;
|
||||
const std::vector<GLuint>& item_textures() const;
|
||||
GLuint get_skin() const;
|
||||
// Must call after MapTable::init_map() and glfwMakeContextCurrent(window);
|
||||
void init_texture();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user