From dd48dcaed67b02dd385babb9b30c13fb2c12e4dd Mon Sep 17 00:00:00 2001 From: tovjemam Date: Wed, 14 Jan 2026 23:33:53 +0100 Subject: [PATCH] Various stuff 14.1. --- CMakeLists.txt | 5 ++ src/assets/cache.cpp | 3 +- src/assets/cache.hpp | 13 +++ src/assets/mesh_builder.cpp | 9 +- src/assets/model.cpp | 6 +- src/assets/vehiclemdl.hpp | 6 +- src/client/app.cpp | 95 ++++++++++++++++++-- src/client/app.hpp | 21 +++++ src/client/main.cpp | 11 +-- src/game/openworld.cpp | 15 +++- src/game/vehicle.cpp | 35 +++----- src/game/vehicle.hpp | 3 +- src/gameview/vehicleview.cpp | 18 ++-- src/gameview/vehicleview.hpp | 3 +- src/gfx/draw_list.hpp | 17 +++- src/gfx/font.cpp | 46 ++++++++++ src/gfx/font.hpp | 101 ++++++++++++++++++++++ src/gfx/hud.hpp | 15 ++++ src/gfx/renderer.cpp | 115 ++++++++++++++++++++++--- src/gfx/renderer.hpp | 5 ++ src/gfx/shader.cpp | 3 +- src/gfx/shader.hpp | 1 + src/gfx/shader_sources.cpp | 60 ++++++++++++- src/gfx/shader_sources.hpp | 3 + src/gfx/surface.hpp | 2 +- src/gfx/text.cpp | 162 +++++++++++++++++++++++++++++++++++ src/gfx/text.hpp | 26 ++++++ src/gfx/texture.cpp | 2 +- src/net/defs.hpp | 2 + src/net/utils.hpp | 16 ++++ src/utils/bufferput.hpp | 10 +++ 31 files changed, 757 insertions(+), 72 deletions(-) create mode 100644 src/gfx/font.cpp create mode 100644 src/gfx/font.hpp create mode 100644 src/gfx/hud.hpp create mode 100644 src/gfx/text.cpp create mode 100644 src/gfx/text.hpp create mode 100644 src/utils/bufferput.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f70968..3195948 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,9 @@ set(CLIENT_ONLY_SOURCES "src/gfx/buffer_object.cpp" "src/gfx/buffer_object.hpp" "src/gfx/draw_list.hpp" + "src/gfx/font.hpp" + "src/gfx/font.cpp" + "src/gfx/hud.hpp" "src/gfx/renderer.hpp" "src/gfx/renderer.cpp" "src/gfx/shader_defs.hpp" @@ -83,6 +86,8 @@ set(CLIENT_ONLY_SOURCES "src/gfx/shader.hpp" "src/gfx/shader.cpp" "src/gfx/surface.hpp" + "src/gfx/text.hpp" + "src/gfx/text.cpp" "src/gfx/texture.cpp" "src/gfx/texture.hpp" "src/gfx/uniform_buffer.hpp" diff --git a/src/assets/cache.cpp b/src/assets/cache.cpp index e6e2953..f30a86b 100644 --- a/src/assets/cache.cpp +++ b/src/assets/cache.cpp @@ -6,4 +6,5 @@ assets::MapCache assets::CacheManager::map_cache_; assets::VehicleCache assets::CacheManager::vehicle_cache_; CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;) -CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;) \ No newline at end of file +CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;) +CLIENT_ONLY(assets::FontCache assets::CacheManager::font_cache_;) \ No newline at end of file diff --git a/src/assets/cache.hpp b/src/assets/cache.hpp index d13aa5b..2a705ce 100644 --- a/src/assets/cache.hpp +++ b/src/assets/cache.hpp @@ -10,6 +10,7 @@ #ifdef CLIENT #include "audio/sound.hpp" #include "gfx/texture.hpp" +#include "gfx/font.hpp" #endif namespace assets @@ -56,6 +57,12 @@ class SoundCache final : public Cache protected: PtrType Load(const std::string& key) override { return audio::Sound::LoadFromFile(key); } }; + +class FontCache final : public Cache +{ +protected: + PtrType Load(const std::string& key) override { return gfx::Font::LoadFromFile(key); } +}; #endif // CLIENT class SkeletonCache final : public Cache @@ -109,6 +116,11 @@ public: { return sound_cache_.Get(filename); } + + static std::shared_ptr GetFont(const std::string& filename) + { + return font_cache_.Get(filename); + } #endif private: @@ -118,6 +130,7 @@ private: static VehicleCache vehicle_cache_; CLIENT_ONLY(static TextureCache texture_cache_;) CLIENT_ONLY(static SoundCache sound_cache_;) + CLIENT_ONLY(static FontCache font_cache_;) }; } // namespace assets \ No newline at end of file diff --git a/src/assets/mesh_builder.cpp b/src/assets/mesh_builder.cpp index 525ce3d..3a9c4c8 100644 --- a/src/assets/mesh_builder.cpp +++ b/src/assets/mesh_builder.cpp @@ -2,6 +2,8 @@ #include +#include "utils/bufferput.hpp" + assets::MeshBuilder::MeshBuilder(gfx::MeshFlags mflags) : mflags_(mflags) {} void assets::MeshBuilder::BeginSurface(gfx::SurfaceFlags sflags, const std::string& name, std::shared_ptr texture) @@ -35,13 +37,6 @@ void assets::MeshBuilder::AddTriangle(const MeshTriangle& t) tris_.push_back(t); } -template -static void BufferPut(std::vector& buffer, const T& val) -{ - const char* data = reinterpret_cast(&val); - buffer.insert(buffer.end(), data, data + sizeof(T)); -} - static int GetVertexAttrFlags(gfx::MeshFlags mflags) { int attrs = gfx::VA_POSITION | gfx::VA_NORMAL | gfx::VA_UV; diff --git a/src/assets/model.cpp b/src/assets/model.cpp index dcebb39..abb124c 100644 --- a/src/assets/model.cpp +++ b/src/assets/model.cpp @@ -25,6 +25,8 @@ std::shared_ptr assets::Model::LoadFromFile(const std::stri iss >> v.normal.x >> v.normal.y >> v.normal.z; iss >> v.uv.x >> v.uv.y; + v.uv.y = 1.0f - v.uv.y; // FLIP FOR GL + // TODO: LUV & bone data mb.AddVertex(v); @@ -70,9 +72,9 @@ std::shared_ptr assets::Model::LoadFromFile(const std::stri { iss >> texture_name; } - else if (flag == "+doublesided") + else if (flag == "+2sided") { - CLIENT_ONLY(sflags |= gfx::SF_DOUBLE_SIDED;) + CLIENT_ONLY(sflags |= gfx::SF_2SIDED;) } else if (flag == "+transparent") { diff --git a/src/assets/vehiclemdl.hpp b/src/assets/vehiclemdl.hpp index c770a74..f0afcac 100644 --- a/src/assets/vehiclemdl.hpp +++ b/src/assets/vehiclemdl.hpp @@ -8,12 +8,12 @@ namespace assets enum VehicleWheelType { - WHEEL_REAR = 1, - WHEEL_RIGHT = 2, + WHEEL_RIGHT = 1, + WHEEL_REAR = 2, WHEEL_FL = 0, WHEEL_FR = WHEEL_RIGHT, - WHEEL_RL = WHEEL_REAR | WHEEL_RIGHT, + WHEEL_RL = WHEEL_REAR, WHEEL_RR = WHEEL_REAR | WHEEL_RIGHT, }; diff --git a/src/client/app.cpp b/src/client/app.cpp index bacdbe5..681c4b3 100644 --- a/src/client/app.cpp +++ b/src/client/app.cpp @@ -4,7 +4,7 @@ #include "net/defs.hpp" #include "net/outmessage.hpp" - +#include "assets/cache.hpp" #include "gameview/worldview.hpp" App::App() @@ -12,6 +12,11 @@ App::App() std::cout << "Initializing App..." << std::endl; audiomaster_.SetMasterVolume(0.2f); + + font_ = assets::CacheManager::GetFont("data/comic32.font"); + + InitChat(); + AddChatMessage("Test!"); } void App::Frame() @@ -47,7 +52,10 @@ void App::Frame() renderer_.ClearDepth(); dlist_.Clear(); - + gfx::DrawListParams params; + params.screen_width = viewport_size_.x; + params.screen_height = viewport_size_.y; + const game::view::WorldView* world; if (session_ && (world = session_->GetWorld())) { @@ -57,14 +65,17 @@ void App::Frame() glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 3000.0f); glm::mat4 view = session_->GetViewMatrix(); - gfx::DrawListParams params; params.view_proj = proj * view; - - renderer_.DrawList(dlist_, params); - + glm::mat4 camera_world = glm::inverse(view); audiomaster_.SetListenerOrientation(camera_world); } + + // draw chat + UpdateChat(); + DrawChat(dlist_); + + renderer_.DrawList(dlist_, params); if (time_ - last_send_time_ > 0.040f) { @@ -79,6 +90,7 @@ void App::Frame() void App::Connected() { std::cout << "WS connected" << std::endl; + AddChatMessagePrefix("WebSocket", "^7f7připojeno"); // init session session_ = std::make_unique(*this); @@ -94,12 +106,17 @@ void App::ProcessMessage(net::InMessage& msg) if (!session_) return; + //size_t s = msg.End() - msg.Ptr(); + // AddChatMessage("recvd: ^f00;" + std::to_string(s)); + session_->ProcessMessage(msg); } void App::Disconnected(const std::string& reason) { std::cout << "WS disconnected" << std::endl; + AddChatMessagePrefix("WebSocket", "^f77spojení je píči"); + // close session session_.reset(); @@ -116,7 +133,71 @@ void App::MouseMove(const glm::vec2& delta) session_->ProcessMouseMove(delta_yaw, delta_pitch); } -App::~App() +void App::AddChatMessage(const std::string& text) +{ + auto& ch = chat_.emplace_back(); + ch.timeout = time_ + 10.0f; + ch.text = std::make_unique(font_, 0xFF'FF'FF'FF); + ch.text->SetText(text); + UpdateChat(); +} + +void App::AddChatMessagePrefix(const std::string& prefix, const std::string& text) +{ + AddChatMessage("^aaa[^ddd" + prefix + "^aaa]^r " + text); +} + +App::~App() {} + +void App::Send(std::vector data) { } + +void App::InitChat() +{ + chatpos_.resize(30); // max messages on screen + const float chat_scale = 1.0f; + for (size_t i = 0; i < chatpos_.size(); ++i) + { + auto& hudp = chatpos_[i]; + hudp.pos.y = (i+1) * font_->GetLineHeight() * chat_scale; + hudp.scale.x = chat_scale; + hudp.scale.y = -chat_scale; + } + +} + +void App::UpdateChat() +{ + // remove expired or over the limit messages + while (!chat_.empty() && (chat_.size() > chatpos_.size() ||chat_[0].timeout < time_)) + { + chat_.pop_front(); + } +} + +void App::DrawChat(gfx::DrawList& dlist) +{ + for (size_t i = 0; i < chat_.size(); ++i) + { + const auto& va = chat_[i].text->GetVA(); + + if (va.GetNumIndices() < 1) + continue; + + float t_rem = chat_[i].timeout - time_; + const float fade = 1.0f; + if (t_rem < fade) + { + chat_[i].color.a = t_rem / fade; + } + + gfx::DrawHudCmd cmd; + cmd.pos = &chatpos_[i]; + cmd.texture = chat_[i].text->GetFont()->GetTexture().get(); + cmd.va = &va; + cmd.color = &chat_[i].color; + dlist.AddHUD(cmd); + } +} diff --git a/src/client/app.hpp b/src/client/app.hpp index b678a7e..3c9c5ef 100644 --- a/src/client/app.hpp +++ b/src/client/app.hpp @@ -1,15 +1,24 @@ #pragma once #include +#include #include "game/player_input.hpp" #include "gfx/renderer.hpp" +#include "gfx/text.hpp" #include "audio/master.hpp" #include "net/msg_producer.hpp" #include "net/inmessage.hpp" #include "gameview/client_session.hpp" +struct ChatMessage +{ + std::unique_ptr text; + float timeout = 0.0f; + glm::vec4 color = glm::vec4(1.0f); +}; + class App : public net::MsgProducer { @@ -32,11 +41,18 @@ public: audio::Master& GetAudioMaster() { return audiomaster_; } + void AddChatMessage(const std::string& text); + void AddChatMessagePrefix(const std::string& prefix, const std::string& text); + ~App(); private: void Send(std::vector data); + void InitChat(); + void UpdateChat(); + void DrawChat(gfx::DrawList& dlist); + private: float time_ = 0.0f; float last_send_time_ = 0.0f; @@ -53,4 +69,9 @@ private: audio::Master audiomaster_; std::unique_ptr session_; + + std::shared_ptr font_; + + std::deque chat_; + std::vector chatpos_; }; diff --git a/src/client/main.cpp b/src/client/main.cpp index 92476ce..9cee36d 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -198,7 +198,7 @@ static EM_BOOL OnWSError(int type, const EmscriptenWebSocketErrorEvent *ev, void return EM_TRUE; } -static bool WSInit() +static bool WSInit(const char* url) { if (!emscripten_websocket_is_supported()) { @@ -206,7 +206,7 @@ static bool WSInit() return false; } EmscriptenWebSocketCreateAttributes ws_attrs = { - WS_URL, + url, NULL, EM_TRUE }; @@ -234,7 +234,7 @@ using namespace easywsclient; static std::unique_ptr s_ws; -static bool WSInit() +static bool WSInit(const char* url) { #ifdef _WIN32 @@ -249,7 +249,7 @@ static bool WSInit() } #endif - s_ws = std::unique_ptr(WebSocket::from_url(WS_URL)); + s_ws = std::unique_ptr(WebSocket::from_url(url)); if (!s_ws) return false; @@ -368,7 +368,7 @@ static void Frame() } static void Main() { - if (!WSInit()) + if (!WSInit(WS_URL)) return; InitSDL(); @@ -386,6 +386,7 @@ static void Main() { SDL_SetRelativeMouseMode(SDL_TRUE); s_app = std::make_unique(); + s_app->AddChatMessagePrefix("WebSocket", "připojování na " + std::string(WS_URL)); #ifdef EMSCRIPTEN emscripten_set_main_loop(Frame, 0, true); diff --git a/src/game/openworld.cpp b/src/game/openworld.cpp index 97b49c3..aa75718 100644 --- a/src/game/openworld.cpp +++ b/src/game/openworld.cpp @@ -8,7 +8,20 @@ game::OpenWorld::OpenWorld() : World("openworld") {} void game::OpenWorld::PlayerJoined(Player& player) { // spawn him car - auto& vehicle = Spawn("pickup"); + // random model + const char* vehicles[] = { "pickup", "passat" }; + auto vehicle_name = vehicles[rand() % (sizeof(vehicles) / sizeof(vehicles[0]))]; + + // ranodm color + glm::vec3 color; + for (int i = 0; i < 3; ++i) + { + net::ColorQ qcol; + qcol.value = rand() % 256; + color[i] = qcol.Decode(); + } + + auto& vehicle = Spawn(vehicle_name, color); player.Control(&vehicle); player_vehicles_[&player] = vehicle.GetEntNum(); diff --git a/src/game/vehicle.cpp b/src/game/vehicle.cpp index 54cb9d3..309748f 100644 --- a/src/game/vehicle.cpp +++ b/src/game/vehicle.cpp @@ -12,32 +12,24 @@ static std::shared_ptr LoadVehicleModelByName(const return assets::CacheManager::GetVehicleModel("data/" + model_name + ".veh"); } -struct Shape -{ - btBoxShape box; - btCompoundShape compound; - - Shape() : box(btVector3(1, 1, 0.1)) - { - btTransform t(btQuaternion(0, 0, 0), btVector3(0, 0, 2)); - compound.addChildShape(t, &box); - } -}; - -game::Vehicle::Vehicle(World& world, std::string model_name) +game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& color) : Entity(world, net::ET_VEHICLE), model_name_(model_name), model_(LoadVehicleModelByName(model_name)), - motion_(root_.local) + motion_(root_.local), + color_(color) { root_.local.position.z = 10.0f; // setup chassis rigidbody float mass = 1300.0f; - static Shape shape; + + btCollisionShape* shape = model_->GetModel()->GetColShape(); + if (!shape) + throw std::runtime_error("Making vehicle with no shape"); btVector3 local_inertia(0, 0, 0); - shape.compound.calculateLocalInertia(mass, local_inertia); + shape->calculateLocalInertia(mass, local_inertia); - btRigidBody::btRigidBodyConstructionInfo rb_info(mass, &motion_, &shape.compound, local_inertia); + btRigidBody::btRigidBodyConstructionInfo rb_info(mass, &motion_, shape, local_inertia); body_ = std::make_unique(rb_info); body_->setActivationState(DISABLE_DEACTIVATION); @@ -52,7 +44,7 @@ game::Vehicle::Vehicle(World& world, std::string model_name) btVector3 wheelDirectionCS0(0, 0, -1); btVector3 wheelAxleCS(1, 0, 0); - wheel_z_offset_ = 0.4f; + wheel_z_offset_ = 0.5f; const auto& wheels = model_->GetWheels(); @@ -63,10 +55,10 @@ game::Vehicle::Vehicle(World& world, std::string model_name) for (const auto& wheeldef : wheels) { - float wheelRadius = .35f; + float wheelRadius = wheeldef.radius; - float friction = 5.0f; - float suspensionStiffness = 60.0f; + float friction = 2.0f; // 5.0f; + float suspensionStiffness = 50.0f; // float suspensionDamping = 2.3f; // float suspensionCompression = 4.4f; float suspensionRestLength = 0.6f; @@ -116,6 +108,7 @@ void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const { net::ModelName name(model_name_); msg.Write(name); + net::WriteRGB(msg, color_); // primary color } game::Vehicle::~Vehicle() diff --git a/src/game/vehicle.hpp b/src/game/vehicle.hpp index 3f230e0..5da256f 100644 --- a/src/game/vehicle.hpp +++ b/src/game/vehicle.hpp @@ -27,7 +27,7 @@ class Vehicle : public Entity, public Controllable public: using Super = Entity; - Vehicle(World& world, std::string model_name); + Vehicle(World& world, std::string model_name, const glm::vec3& color); virtual void Update() override; virtual void SendInitData(Player& player, net::OutMessage& msg) const override; @@ -45,6 +45,7 @@ private: private: std::string model_name_; std::shared_ptr model_; + glm::vec3 color_; collision::MotionState motion_; std::unique_ptr body_; diff --git a/src/gameview/vehicleview.cpp b/src/gameview/vehicleview.cpp index 86bc8c2..a6d6670 100644 --- a/src/gameview/vehicleview.cpp +++ b/src/gameview/vehicleview.cpp @@ -6,7 +6,7 @@ #include -game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr model) +game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr model, const glm::vec3& color) : EntityView(world), model_(std::move(model)) { auto& modelwheels = model_->GetWheels(); @@ -17,18 +17,21 @@ game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr game::view::VehicleView::InitFromMsg(WorldView& world, net::InMessage& msg) { net::ModelName modelname; - if (!msg.Read(modelname)) + glm::vec3 color; + if (!msg.Read(modelname) || !net::ReadRGB(msg, color)) return nullptr; auto model = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh"); - return std::make_unique(world, std::move(model)); + return std::make_unique(world, std::move(model), color); } bool game::view::VehicleView::ProcessMsg(net::EntMsgType type, net::InMessage& msg) @@ -66,10 +69,14 @@ void game::view::VehicleView::Update(const UpdateInfo& info) // rotate wheelstate.rotation += info.delta_time * wheelstate.speed; - wheeltrans.rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + wheeltrans.rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); wheeltrans.rotation = glm::rotate(wheeltrans.rotation, wheelstate.steering, glm::vec3(0, 0, 1)); wheeltrans.rotation = glm::rotate(wheeltrans.rotation, wheelstate.rotation, glm::vec3(1, 0, 0)); - + + const auto& type = wheels[i].type; + if (type == assets::WHEEL_FL || type == assets::WHEEL_RL) + wheeltrans.rotation = glm::rotate(wheeltrans.rotation, glm::radians(180.0f), glm::vec3(0, 1, 0)); + wheels_[i].node.UpdateMatrix(); } @@ -99,6 +106,7 @@ void game::view::VehicleView::Draw(gfx::DrawList& dlist) gfx::DrawSurfaceCmd cmd; cmd.surface = &surface; cmd.matrices = &root_.matrix; + cmd.color = &color_; dlist.AddSurface(cmd); } diff --git a/src/gameview/vehicleview.hpp b/src/gameview/vehicleview.hpp index a047545..40a8736 100644 --- a/src/gameview/vehicleview.hpp +++ b/src/gameview/vehicleview.hpp @@ -23,7 +23,7 @@ class VehicleView : public EntityView { using Super = EntityView; public: - VehicleView(WorldView& world, std::shared_ptr model); + VehicleView(WorldView& world, std::shared_ptr model, const glm::vec3& color); static std::unique_ptr InitFromMsg(WorldView& world, net::InMessage& msg); virtual bool ProcessMsg(net::EntMsgType type, net::InMessage& msg) override; @@ -35,6 +35,7 @@ private: private: std::shared_ptr model_; + glm::vec4 color_; std::vector wheels_; diff --git a/src/gfx/draw_list.hpp b/src/gfx/draw_list.hpp index 751d5c6..9e7c7a3 100644 --- a/src/gfx/draw_list.hpp +++ b/src/gfx/draw_list.hpp @@ -4,6 +4,7 @@ #include "assets/skeleton.hpp" #include "surface.hpp" +#include "hud.hpp" namespace gfx { @@ -18,13 +19,27 @@ struct DrawSurfaceCmd float dist = 0.0f; // distance to camera - for transparnt sorting }; +struct DrawHudCmd +{ + const VertexArray* va = nullptr; + const Texture* texture = nullptr; + const HudPosition* pos = nullptr; + const glm::vec4* color = nullptr; +}; + struct DrawList { std::vector surfaces; + std::vector huds; void AddSurface(const DrawSurfaceCmd& cmd) { surfaces.emplace_back(cmd); } + void AddHUD(const DrawHudCmd& cmd) { huds.emplace_back(cmd); } - void Clear() { surfaces.clear(); } + void Clear() + { + surfaces.clear(); + huds.clear(); + } }; } // namespace gfx \ No newline at end of file diff --git a/src/gfx/font.cpp b/src/gfx/font.cpp new file mode 100644 index 0000000..fbef9f0 --- /dev/null +++ b/src/gfx/font.cpp @@ -0,0 +1,46 @@ +#include "font.hpp" + +#include "assets/cache.hpp" +#include "assets/cmdfile.hpp" + +std::shared_ptr gfx::Font::LoadFromFile(const std::string& path) +{ + auto font = std::make_shared(); + + glm::vec2 size(1.0f); + + auto PxToUv = [&](const glm::vec2& pos_px) { return pos_px / size; }; + + assets::LoadCMDFile(path, [&](const std::string& cmd, std::istringstream& iss) { + if (cmd == "c") + { + Codepoint cp = 0; + glm::vec2 c_pos(0.0f); + glm::vec2 c_size(0.0f); + glm::vec3 c_offset(0.0f); + float xadv = 0.0f; + + iss >> cp >> c_pos.x >> c_pos.y >> c_size.x >> c_size.y >> c_offset.x >> c_offset.y >> xadv; + + auto& glyph = font->glyphs_.Alloc(cp); + glyph.uv0 = PxToUv(c_pos); + glyph.uv1 = PxToUv(c_pos + c_size); + glyph.offset = c_offset; + glyph.size = c_size; + glyph.advance = xadv; + } + else if (cmd == "texture") + { + std::string tex_name; + iss >> tex_name >> size.x >> size.y; + + font->texture_ = assets::CacheManager::GetTexture("data/" + tex_name + ".png"); + } + else if (cmd == "size") + { + iss >> font->line_height_; + } + }); + + return font; +} diff --git a/src/gfx/font.hpp b/src/gfx/font.hpp new file mode 100644 index 0000000..02c1420 --- /dev/null +++ b/src/gfx/font.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include + +#include + +#include "texture.hpp" + +namespace gfx +{ + +using Codepoint = uint32_t; + +template +class CodepointMap +{ + static constexpr size_t MAX_ARRAY_CODEPOINT = 128; + + struct ArrayItem + { + T data; + bool used = false; + }; + +public: + CodepointMap() = default; + + T& Alloc(Codepoint codepoint) + { + if (codepoint < MAX_ARRAY_CODEPOINT) + { + array_[codepoint].used = true; + return array_[codepoint].data; + } + else + { + return map_[codepoint]; // Use map for larger codepoints + } + } + + const T* Get(Codepoint codepoint) const + { + if (codepoint < MAX_ARRAY_CODEPOINT) + { + if (array_[codepoint].used) + { + return &array_[codepoint].data; + } + } + else + { + auto it = map_.find(codepoint); + if (it != map_.end()) + { + return &it->second; + } + } + + return nullptr; // Not found + } + +private: + std::array array_; // For common ASCII codepoints + std::map map_; +}; + +struct FontGlyphData +{ + glm::vec2 uv0; + glm::vec2 uv1; + glm::vec2 offset; + glm::vec2 size; + float advance = 0.0f; // Advance width for the codepoint +}; + +class Font +{ +public: + Font() = default; + static std::shared_ptr LoadFromFile(const std::string& path); + + const FontGlyphData* GetCodepointGlyph(Codepoint codepoint) const + { + const FontGlyphData* data = glyphs_.Get(codepoint); + return data ? data : glyphs_.Get(0); // try return missing codepoint glyph if missing + } + + const std::shared_ptr& GetTexture() const { return texture_; } + + float GetLineHeight() const { return line_height_; } + +private: + std::shared_ptr texture_; // Texture atlas for the font + CodepointMap glyphs_; + + float line_height_ = 0.0f; // Line height in pixels, used for text layout +}; + +} // namespace gfx \ No newline at end of file diff --git a/src/gfx/hud.hpp b/src/gfx/hud.hpp new file mode 100644 index 0000000..bfab10e --- /dev/null +++ b/src/gfx/hud.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace gfx +{ + +struct HudPosition +{ + glm::vec2 anchor = glm::vec2(0.0f); // <0;1> + glm::vec2 pos = glm::vec2(0.0f); // px or px multiplies (scale != 1) + glm::vec2 scale = glm::vec2(1.0f); +}; + +} \ No newline at end of file diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp index 5e8f3fd..87e97a4 100644 --- a/src/gfx/renderer.cpp +++ b/src/gfx/renderer.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -15,10 +16,12 @@ gfx::Renderer::Renderer() ShaderSources::MakeShader(mesh_shader_.shader, SS_MESH_VERT, SS_MESH_FRAG); ShaderSources::MakeShader(skel_mesh_shader_.shader, SS_SKEL_MESH_VERT, SS_SKEL_MESH_FRAG); ShaderSources::MakeShader(solid_shader_, SS_SOLID_VERT, SS_SOLID_FRAG); + ShaderSources::MakeShader(hud_shader_, SS_HUD_VERT, SS_HUD_FRAG); } void gfx::Renderer::Begin(size_t width, size_t height) { + current_shader_ = nullptr; glViewport(0, 0, width, height); } @@ -36,6 +39,7 @@ void gfx::Renderer::ClearDepth() void gfx::Renderer::DrawList(gfx::DrawList& list, const DrawListParams& params) { DrawSurfaceList(list.surfaces, params); + DrawHudList(list.huds, params); } void gfx::Renderer::InvalidateShaders() @@ -106,12 +110,14 @@ void gfx::Renderer::DrawSurfaceList(std::span list, const DrawLi glEnable(GL_CULL_FACE); glCullFace(GL_BACK); + glDisable(GL_BLEND); + InvalidateShaders(); // cache to eliminate fake state changes const gfx::Texture* last_texture = nullptr; const gfx::VertexArray* last_vao = nullptr; - bool last_double_sided = false; + bool last_twosided = false; glActiveTexture(GL_TEXTURE0); // for all future bindings @@ -122,19 +128,19 @@ void gfx::Renderer::DrawSurfaceList(std::span list, const DrawLi // mesh flags const bool skeletal_flag = surface->mflags & MF_SKELETAL; // surface flags - const bool double_sided_flag = surface->sflags & SF_DOUBLE_SIDED; + const bool twosided_flag = surface->sflags & SF_2SIDED; const bool transparent_flag = surface->sflags & SF_TRANSPARENT; const bool object_color_flag = surface->sflags & SF_OBJECT_COLOR; - // adjust state - if (last_double_sided != double_sided_flag) + // sync 2sided + if (last_twosided != twosided_flag) { - if (double_sided_flag) + if (twosided_flag) glDisable(GL_CULL_FACE); else glEnable(GL_CULL_FACE); - last_double_sided = double_sided_flag; + last_twosided = twosided_flag; } // select shader @@ -144,19 +150,36 @@ void gfx::Renderer::DrawSurfaceList(std::span list, const DrawLi // set model matrix if (cmd.matrices) { - glUniformMatrix4fv(mshader.shader->U(gfx::SU_MODEL), 1, GL_FALSE, &cmd.matrices[0][0][0]); + glUniformMatrix4fv(mshader.shader->U(SU_MODEL), 1, GL_FALSE, &cmd.matrices[0][0][0]); } else { // use identity if no matrix provided static const glm::mat4 identity(1.0f); - glUniformMatrix4fv(mshader.shader->U(gfx::SU_MODEL), 1, GL_FALSE, &identity[0][0]); + glUniformMatrix4fv(mshader.shader->U(SU_MODEL), 1, GL_FALSE, &identity[0][0]); } // set color - glm::vec4 color = (object_color_flag && cmd.color) ? glm::vec4(*cmd.color) : glm::vec4(1.0f); + bool cull_alpha = true; + glm::vec4 color = glm::vec4(1.0f); + + if (object_color_flag && cmd.color) + { + // use object color and disable alpha cull + cull_alpha = false; + color = glm::vec4(*cmd.color); + } + + // sync cull_alpha + if (mshader.cull_alpha != cull_alpha) + { + glUniform1i(mshader.shader->U(SU_CULL_ALPHA), cull_alpha ? 1 : 0); + mshader.cull_alpha = cull_alpha; + } + + // sync color if (mshader.color != color) { - glUniform4fv(mshader.shader->U(gfx::SU_COLOR), 1, &color[0]); + glUniform4fv(mshader.shader->U(SU_COLOR), 1, &color[0]); mshader.color = color; } @@ -186,3 +209,75 @@ void gfx::Renderer::DrawSurfaceList(std::span list, const DrawLi } + +void gfx::Renderer::DrawHudList(std::span queue, const DrawListParams& params) +{ + // cannot sort anything here, must be drawn in FIFO order for correct overlay + + Shader* shader = hud_shader_.get(); + current_shader_ = shader; + glUseProgram(shader->GetId()); + + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + float w = static_cast(params.screen_width); + float h = static_cast(params.screen_height); + glm::vec2 screen_size_px(w, h); + glm::vec2 ndc_scale(2.0f / screen_size_px.x, -2.0f / screen_size_px.y); + constexpr glm::vec2 ndc_offset(-1.0f, 1.0f); + + const gfx::Texture* last_texture = nullptr; + const gfx::VertexArray* last_vao = nullptr; + glm::vec4 last_color = glm::vec4(-1.0f); + + for (const auto& cmd : queue) + { + if (!cmd.va || !cmd.texture || !cmd.pos) + { + throw std::runtime_error("invalid hud draw"); + } + + // calculate transform + const auto& hp = *cmd.pos; + + glm::vec2 pos_px = hp.anchor * screen_size_px + hp.pos; + glm::vec2 trans_ndc = ndc_offset + pos_px * ndc_scale; + + glm::mat3 matrix(1.0f); + matrix[0][0] = hp.scale.x * ndc_scale.x; + matrix[1][1] = hp.scale.y * ndc_scale.y; + matrix[2][0] = trans_ndc.x; + matrix[2][1] = trans_ndc.y; + + glUniformMatrix3fv(shader->U(SU_MODEL), 1, GL_FALSE, &matrix[0][0]); + + //sync color + glm::vec4 color = cmd.color ? *cmd.color : glm::vec4(1.0f); + if (last_color != color) + { + glUniform4fv(shader->U(SU_COLOR), 1, &color[0]); + last_color = color; + } + + // bind texture + if (last_texture != cmd.texture) + { + GLuint tex_id = cmd.texture ? cmd.texture->GetId() : 0; + glBindTexture(GL_TEXTURE_2D, tex_id); + last_texture = cmd.texture; + } + + // bind vao + if (last_vao = cmd.va) + { + glBindVertexArray(cmd.va->GetVAOId()); + last_vao = cmd.va; + } + + glDrawElements(GL_TRIANGLES, static_cast(cmd.va->GetNumIndices()), GL_UNSIGNED_INT, NULL); + } +} diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp index 53cd8d8..536f631 100644 --- a/src/gfx/renderer.hpp +++ b/src/gfx/renderer.hpp @@ -11,6 +11,8 @@ namespace gfx struct DrawListParams { glm::mat4 view_proj; + size_t screen_width = 0; + size_t screen_height = 0; }; struct MeshShader @@ -20,6 +22,7 @@ namespace gfx // cached state to avoid redundant uniform updates which are expensive especially on WebGL bool global_setup = false; glm::vec4 color = glm::vec4(-1.0f); // invalid to force initial setup + bool cull_alpha = false; }; class Renderer @@ -38,6 +41,7 @@ namespace gfx MeshShader mesh_shader_; MeshShader skel_mesh_shader_; std::unique_ptr solid_shader_; + std::unique_ptr hud_shader_; const Shader* current_shader_ = nullptr; @@ -46,6 +50,7 @@ namespace gfx void SetupMeshShader(MeshShader& mshader, const DrawListParams& params); void DrawSurfaceList(std::span queue, const DrawListParams& params); + void DrawHudList(std::span queue, const DrawListParams& params); }; diff --git a/src/gfx/shader.cpp b/src/gfx/shader.cpp index be85c3e..b8aba82 100644 --- a/src/gfx/shader.cpp +++ b/src/gfx/shader.cpp @@ -8,7 +8,8 @@ static const char* const s_uni_names[] = { "u_model", // SU_MODEL "u_view_proj", // SU_VIEW_PROJ "u_tex", // SU_TEX - "u_color" // SU_COLOR + "u_color", // SU_COLOR + "u_cull_alpha", // SU_COLOR }; // Vytvori shader z daneho zdroje diff --git a/src/gfx/shader.hpp b/src/gfx/shader.hpp index abf4b4c..ff8514a 100644 --- a/src/gfx/shader.hpp +++ b/src/gfx/shader.hpp @@ -14,6 +14,7 @@ namespace gfx SU_VIEW_PROJ, SU_TEX, SU_COLOR, + SU_CULL_ALPHA, SU_COUNT }; diff --git a/src/gfx/shader_sources.cpp b/src/gfx/shader_sources.cpp index fef383f..1c64ad1 100644 --- a/src/gfx/shader_sources.cpp +++ b/src/gfx/shader_sources.cpp @@ -79,7 +79,7 @@ SHADER_HEADER R"GLSL( layout (location = 0) in vec3 a_pos; layout (location = 1) in vec3 a_normal; -layout (location = 2) in vec3 a_color; +layout (location = 2) in vec4 a_color; layout (location = 3) in vec2 a_uv; )GLSL" @@ -96,8 +96,8 @@ void main() { vec3 world_normal = normalize(mat3(u_model) * a_normal); gl_Position = u_view_proj * world_pos; - v_uv = vec2(a_uv.x, 1.0 - a_uv.y); - v_color = ComputeLights(world_pos.xyz, world_normal) * a_color; + v_uv = a_uv; + v_color = ComputeLights(world_pos.xyz, world_normal) * a_color.rgb; } )GLSL", @@ -108,11 +108,25 @@ in vec2 v_uv; in vec3 v_color; uniform sampler2D u_tex; +uniform vec4 u_color; +uniform bool u_cull_alpha; layout (location = 0) out vec4 o_color; void main() { o_color = vec4(texture(u_tex, v_uv)); + + if (u_cull_alpha) + { + if (o_color.a < 0.5) + discard; + } + else + { + // blend with bg + o_color = mix(u_color, o_color, o_color.a); + } + o_color.rgb *= v_color; // Apply vertex color //o_color = vec4(1.0, 0.0, 0.0, 1.0); } @@ -155,7 +169,7 @@ void main() { vec3 world_normal = normalize(mat3(u_model) * mat3(bone_transform) * a_normal); gl_Position = u_view_proj * world_pos; - v_uv = vec2(a_uv.x, 1.0 - a_uv.y); + v_uv = a_uv; v_color = ComputeLights(world_pos.xyz, world_normal); } @@ -209,6 +223,44 @@ void main() { )GLSL", +// SS_HUD_VERT +SHADER_HEADER +R"GLSL( +layout (location = 0) in vec3 a_pos; +layout (location = 2) in vec4 a_color; +layout (location = 3) in vec2 a_uv; + +uniform mat3 u_model; +uniform vec4 u_color; + +out vec4 v_color; +out vec2 v_uv; + +void main() { + vec3 pos2d = u_model * vec3(a_pos.xy, 1.0); + gl_Position = vec4(pos2d.xy, 0.0, 1.0); + v_color = a_color * u_color; + v_uv = a_uv; +} +)GLSL", + +// SS_HUD_FRAG +SHADER_HEADER +R"GLSL( + +in vec4 v_color; +in vec2 v_uv; + +uniform sampler2D u_tex; + +layout (location = 0) out vec4 o_color; + +void main() { + o_color = texture(u_tex, v_uv); + o_color *= v_color; +} + +)GLSL", }; diff --git a/src/gfx/shader_sources.hpp b/src/gfx/shader_sources.hpp index 3b92885..a4f11d4 100644 --- a/src/gfx/shader_sources.hpp +++ b/src/gfx/shader_sources.hpp @@ -16,6 +16,9 @@ namespace gfx SS_SOLID_VERT, SS_SOLID_FRAG, + SS_HUD_VERT, + SS_HUD_FRAG, + }; class ShaderSources diff --git a/src/gfx/surface.hpp b/src/gfx/surface.hpp index 44a32e7..b064f5d 100644 --- a/src/gfx/surface.hpp +++ b/src/gfx/surface.hpp @@ -20,7 +20,7 @@ using SurfaceFlags = uint8_t; enum SurfaceFlag : SurfaceFlags { SF_NONE = 0x00, - SF_DOUBLE_SIDED = 0x01, + SF_2SIDED = 0x01, SF_TRANSPARENT = 0x02, SF_OBJECT_COLOR = 0x08, // use object level tint }; diff --git a/src/gfx/text.cpp b/src/gfx/text.cpp new file mode 100644 index 0000000..d70fd19 --- /dev/null +++ b/src/gfx/text.cpp @@ -0,0 +1,162 @@ +#include "text.hpp" + +#include "utils/bufferput.hpp" + +gfx::Text::Text(std::shared_ptr font, uint32_t color) + : font_(std::move(font)), va_(VA_POSITION | VA_UV | VA_COLOR, VF_CREATE_EBO | VF_DYNAMIC), color_(color) +{ +} + +static uint32_t DecodeUTF8Codepoint(const char*& p) +{ + if (!*p) + return 0; + + if ((*p & 0b10000000) == 0) + { // 1-byte sequence + return *p++; + } + + uint32_t codepoint = 0; + + if ((*p & 0b11100000) == 0b11000000) + { // 2-byte seq + codepoint = (*p++ & 0b00011111) << 6; + if (!*p) + return 0; + codepoint |= (*p++ & 0b00111111); + } + else if ((*p & 0b11110000) == 0b11100000) + { // 3-byte seq + codepoint = (*p++ & 0b00001111) << 12; + if (!*p) + return 0; + codepoint |= (*p++ & 0b00111111) << 6; + if (!*p) + return 0; + codepoint |= (*p++ & 0b00111111); + } + else if ((*p & 0b11111000) == 0b11110000) + { // 4-byte seq + codepoint = (*p++ & 0b00000111) << 18; + if (!*p) + return 0; + codepoint |= (*p++ & 0b00111111) << 12; + if (!*p) + return 0; + codepoint |= (*p++ & 0b00111111) << 6; + if (!*p) + return 0; + codepoint |= (*p++ & 0b00111111); + } + + return codepoint; +} + + +struct TextVertex +{ + glm::vec3 pos; + uint32_t color; + glm::vec2 uv; +}; + +static void PutVertex(std::vector& buf, const glm::vec3 pos, const glm::vec2& uv, uint32_t color) +{ + //BufferPut(buf, pos); + //BufferPut(buf, color); + //BufferPut(buf, uv); + buf.emplace_back(TextVertex{pos, color, uv}); +} + +void gfx::Text::SetText(const char* text) +{ + static std::vector vertices; + static std::vector indices; + vertices.clear(); + indices.clear(); + + float space_size = font_->GetLineHeight() * 0.3f; + + glm::vec2 cursor(0.0f, 0.0f); + + uint32_t cp = 0; + const char* p = text; + + uint32_t color = color_; + + while (cp = DecodeUTF8Codepoint(p)) + { + if (cp == ' ') + { + cursor.x += space_size; // Move cursor for space + continue; + } + else if (cp == '^') + { + if (!(cp = DecodeUTF8Codepoint(p))) + break; + + if (cp == 'r') // reset color + { + color = color_; + continue; + } + + color = 0; + + // parse color + for (size_t i = 0; i < 3; ++i) + { + color >>= 8; + uint32_t ch; + if (cp >= '0' && cp <= '9') + ch = cp - '0'; + else if (cp >= 'a' && cp <= 'f') + ch = cp - 'a' + 10; + else + break; + + color |= (ch << 16); + color |= (ch << 20); + + if (!(cp = DecodeUTF8Codepoint(p))) + break; + } + + color |= 0xFF000000; // alpha=1 + + //if (cp != ';') + // break; + + //continue; + } + + const FontGlyphData* glyph = font_->GetCodepointGlyph(cp); + + if (!glyph) + continue; // Dont even have "missing" glyph, font is shit + + glm::vec2 p0 = cursor + glyph->offset; + glm::vec2 p1 = p0 + glyph->size; + + uint32_t base_index = vertices.size(); + + PutVertex(vertices, glm::vec3(p0.x, -p0.y, 0.0f), glyph->uv0, color); + PutVertex(vertices, glm::vec3(p1.x, -p0.y, 0.0f), glm::vec2(glyph->uv1.x, glyph->uv0.y), color); + PutVertex(vertices, glm::vec3(p1.x, -p1.y, 0.0f), glyph->uv1, color); + PutVertex(vertices, glm::vec3(p0.x, -p1.y, 0.0f), glm::vec2(glyph->uv0.x, glyph->uv1.y), color); + + indices.push_back(base_index + 0); + indices.push_back(base_index + 1); + indices.push_back(base_index + 2); + indices.push_back(base_index + 0); + indices.push_back(base_index + 2); + indices.push_back(base_index + 3); + + cursor.x += glyph->advance; + } + + va_.SetVBOData(vertices.data(), vertices.size() * sizeof(TextVertex)); + va_.SetIndices(indices.data(), indices.size()); +} diff --git a/src/gfx/text.hpp b/src/gfx/text.hpp new file mode 100644 index 0000000..9162303 --- /dev/null +++ b/src/gfx/text.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "font.hpp" +#include "vertex_array.hpp" + +namespace gfx +{ + +class Text +{ +public: + Text(std::shared_ptr font, uint32_t color); + + void SetText(const char* text); + void SetText(const std::string& text) { SetText(text.c_str()); } + + const std::shared_ptr& GetFont() const { return font_; } + const VertexArray& GetVA() const { return va_; } + +private: + std::shared_ptr font_; + VertexArray va_; + uint32_t color_; +}; + +} // namespace gfx \ No newline at end of file diff --git a/src/gfx/texture.cpp b/src/gfx/texture.cpp index 9044de9..8772c92 100644 --- a/src/gfx/texture.cpp +++ b/src/gfx/texture.cpp @@ -25,7 +25,7 @@ static void GetGLFilterModes(bool linear, bool mipmaps, GLenum& filter_min, GLen if (mipmaps) { // Mipmaps always linear - filter_min = GL_NEAREST_MIPMAP_LINEAR; + filter_min = GL_LINEAR_MIPMAP_LINEAR; } } } diff --git a/src/net/defs.hpp b/src/net/defs.hpp index bd4124f..785e76f 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -76,4 +76,6 @@ using QuatQ = Quantized; using WheelZOffsetQ = Quantized; using RotationSpeedQ = Quantized; +using ColorQ = Quantized; + } // namespace net \ No newline at end of file diff --git a/src/net/utils.hpp b/src/net/utils.hpp index 26c3622..04bb61a 100644 --- a/src/net/utils.hpp +++ b/src/net/utils.hpp @@ -8,6 +8,8 @@ namespace net { +/// TRANSFORMS + inline void WritePosition(OutMessage& msg, const glm::vec3& pos) { msg.Write(pos.x); @@ -54,4 +56,18 @@ inline bool ReadTransform(InMessage& msg, Transform& trans) return ReadPosition(msg, trans.position) && ReadRotation(msg, trans.rotation); } +/// COLOR + +inline void WriteRGB(OutMessage& msg, const glm::vec3& color) +{ + msg.Write(color.r); + msg.Write(color.g); + msg.Write(color.b); +} + +inline bool ReadRGB(InMessage& msg, glm::vec3& color) +{ + return msg.Read(color.r) && msg.Read(color.g) && msg.Read(color.b); +} + } \ No newline at end of file diff --git a/src/utils/bufferput.hpp b/src/utils/bufferput.hpp new file mode 100644 index 0000000..11084f7 --- /dev/null +++ b/src/utils/bufferput.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +template +inline void BufferPut(std::vector& buffer, const T& val) +{ + const char* data = reinterpret_cast(&val); + buffer.insert(buffer.end(), data, data + sizeof(T)); +}