Various stuff 14.1.

This commit is contained in:
tovjemam 2026-01-14 23:33:53 +01:00
parent 59707f1339
commit dd48dcaed6
31 changed files with 757 additions and 72 deletions

View File

@ -75,6 +75,9 @@ set(CLIENT_ONLY_SOURCES
"src/gfx/buffer_object.cpp" "src/gfx/buffer_object.cpp"
"src/gfx/buffer_object.hpp" "src/gfx/buffer_object.hpp"
"src/gfx/draw_list.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.hpp"
"src/gfx/renderer.cpp" "src/gfx/renderer.cpp"
"src/gfx/shader_defs.hpp" "src/gfx/shader_defs.hpp"
@ -83,6 +86,8 @@ set(CLIENT_ONLY_SOURCES
"src/gfx/shader.hpp" "src/gfx/shader.hpp"
"src/gfx/shader.cpp" "src/gfx/shader.cpp"
"src/gfx/surface.hpp" "src/gfx/surface.hpp"
"src/gfx/text.hpp"
"src/gfx/text.cpp"
"src/gfx/texture.cpp" "src/gfx/texture.cpp"
"src/gfx/texture.hpp" "src/gfx/texture.hpp"
"src/gfx/uniform_buffer.hpp" "src/gfx/uniform_buffer.hpp"

View File

@ -7,3 +7,4 @@ assets::VehicleCache assets::CacheManager::vehicle_cache_;
CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;) CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;)
CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;) CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;)
CLIENT_ONLY(assets::FontCache assets::CacheManager::font_cache_;)

View File

@ -10,6 +10,7 @@
#ifdef CLIENT #ifdef CLIENT
#include "audio/sound.hpp" #include "audio/sound.hpp"
#include "gfx/texture.hpp" #include "gfx/texture.hpp"
#include "gfx/font.hpp"
#endif #endif
namespace assets namespace assets
@ -56,6 +57,12 @@ class SoundCache final : public Cache<audio::Sound>
protected: protected:
PtrType Load(const std::string& key) override { return audio::Sound::LoadFromFile(key); } PtrType Load(const std::string& key) override { return audio::Sound::LoadFromFile(key); }
}; };
class FontCache final : public Cache<gfx::Font>
{
protected:
PtrType Load(const std::string& key) override { return gfx::Font::LoadFromFile(key); }
};
#endif // CLIENT #endif // CLIENT
class SkeletonCache final : public Cache<Skeleton> class SkeletonCache final : public Cache<Skeleton>
@ -109,6 +116,11 @@ public:
{ {
return sound_cache_.Get(filename); return sound_cache_.Get(filename);
} }
static std::shared_ptr<const gfx::Font> GetFont(const std::string& filename)
{
return font_cache_.Get(filename);
}
#endif #endif
private: private:
@ -118,6 +130,7 @@ private:
static VehicleCache vehicle_cache_; static VehicleCache vehicle_cache_;
CLIENT_ONLY(static TextureCache texture_cache_;) CLIENT_ONLY(static TextureCache texture_cache_;)
CLIENT_ONLY(static SoundCache sound_cache_;) CLIENT_ONLY(static SoundCache sound_cache_;)
CLIENT_ONLY(static FontCache font_cache_;)
}; };
} // namespace assets } // namespace assets

View File

@ -2,6 +2,8 @@
#include <stdexcept> #include <stdexcept>
#include "utils/bufferput.hpp"
assets::MeshBuilder::MeshBuilder(gfx::MeshFlags mflags) : mflags_(mflags) {} assets::MeshBuilder::MeshBuilder(gfx::MeshFlags mflags) : mflags_(mflags) {}
void assets::MeshBuilder::BeginSurface(gfx::SurfaceFlags sflags, const std::string& name, std::shared_ptr<const gfx::Texture> texture) void assets::MeshBuilder::BeginSurface(gfx::SurfaceFlags sflags, const std::string& name, std::shared_ptr<const gfx::Texture> texture)
@ -35,13 +37,6 @@ void assets::MeshBuilder::AddTriangle(const MeshTriangle& t)
tris_.push_back(t); tris_.push_back(t);
} }
template <class T>
static void BufferPut(std::vector<char>& buffer, const T& val)
{
const char* data = reinterpret_cast<const char*>(&val);
buffer.insert(buffer.end(), data, data + sizeof(T));
}
static int GetVertexAttrFlags(gfx::MeshFlags mflags) static int GetVertexAttrFlags(gfx::MeshFlags mflags)
{ {
int attrs = gfx::VA_POSITION | gfx::VA_NORMAL | gfx::VA_UV; int attrs = gfx::VA_POSITION | gfx::VA_NORMAL | gfx::VA_UV;

View File

@ -25,6 +25,8 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
iss >> v.normal.x >> v.normal.y >> v.normal.z; iss >> v.normal.x >> v.normal.y >> v.normal.z;
iss >> v.uv.x >> v.uv.y; iss >> v.uv.x >> v.uv.y;
v.uv.y = 1.0f - v.uv.y; // FLIP FOR GL
// TODO: LUV & bone data // TODO: LUV & bone data
mb.AddVertex(v); mb.AddVertex(v);
@ -70,9 +72,9 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
{ {
iss >> texture_name; 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") else if (flag == "+transparent")
{ {

View File

@ -8,12 +8,12 @@ namespace assets
enum VehicleWheelType enum VehicleWheelType
{ {
WHEEL_REAR = 1, WHEEL_RIGHT = 1,
WHEEL_RIGHT = 2, WHEEL_REAR = 2,
WHEEL_FL = 0, WHEEL_FL = 0,
WHEEL_FR = WHEEL_RIGHT, WHEEL_FR = WHEEL_RIGHT,
WHEEL_RL = WHEEL_REAR | WHEEL_RIGHT, WHEEL_RL = WHEEL_REAR,
WHEEL_RR = WHEEL_REAR | WHEEL_RIGHT, WHEEL_RR = WHEEL_REAR | WHEEL_RIGHT,
}; };

View File

@ -4,7 +4,7 @@
#include "net/defs.hpp" #include "net/defs.hpp"
#include "net/outmessage.hpp" #include "net/outmessage.hpp"
#include "assets/cache.hpp"
#include "gameview/worldview.hpp" #include "gameview/worldview.hpp"
App::App() App::App()
@ -12,6 +12,11 @@ App::App()
std::cout << "Initializing App..." << std::endl; std::cout << "Initializing App..." << std::endl;
audiomaster_.SetMasterVolume(0.2f); audiomaster_.SetMasterVolume(0.2f);
font_ = assets::CacheManager::GetFont("data/comic32.font");
InitChat();
AddChatMessage("Test!");
} }
void App::Frame() void App::Frame()
@ -47,6 +52,9 @@ void App::Frame()
renderer_.ClearDepth(); renderer_.ClearDepth();
dlist_.Clear(); dlist_.Clear();
gfx::DrawListParams params;
params.screen_width = viewport_size_.x;
params.screen_height = viewport_size_.y;
const game::view::WorldView* world; const game::view::WorldView* world;
if (session_ && (world = session_->GetWorld())) if (session_ && (world = session_->GetWorld()))
@ -57,15 +65,18 @@ void App::Frame()
glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 3000.0f); glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 3000.0f);
glm::mat4 view = session_->GetViewMatrix(); glm::mat4 view = session_->GetViewMatrix();
gfx::DrawListParams params;
params.view_proj = proj * view; params.view_proj = proj * view;
renderer_.DrawList(dlist_, params);
glm::mat4 camera_world = glm::inverse(view); glm::mat4 camera_world = glm::inverse(view);
audiomaster_.SetListenerOrientation(camera_world); audiomaster_.SetListenerOrientation(camera_world);
} }
// draw chat
UpdateChat();
DrawChat(dlist_);
renderer_.DrawList(dlist_, params);
if (time_ - last_send_time_ > 0.040f) if (time_ - last_send_time_ > 0.040f)
{ {
auto msg = BeginMsg(net::MSG_IN); auto msg = BeginMsg(net::MSG_IN);
@ -79,6 +90,7 @@ void App::Frame()
void App::Connected() void App::Connected()
{ {
std::cout << "WS connected" << std::endl; std::cout << "WS connected" << std::endl;
AddChatMessagePrefix("WebSocket", "^7f7připojeno");
// init session // init session
session_ = std::make_unique<game::view::ClientSession>(*this); session_ = std::make_unique<game::view::ClientSession>(*this);
@ -94,12 +106,17 @@ void App::ProcessMessage(net::InMessage& msg)
if (!session_) if (!session_)
return; return;
//size_t s = msg.End() - msg.Ptr();
// AddChatMessage("recvd: ^f00;" + std::to_string(s));
session_->ProcessMessage(msg); session_->ProcessMessage(msg);
} }
void App::Disconnected(const std::string& reason) void App::Disconnected(const std::string& reason)
{ {
std::cout << "WS disconnected" << std::endl; std::cout << "WS disconnected" << std::endl;
AddChatMessagePrefix("WebSocket", "^f77spojení je píči");
// close session // close session
session_.reset(); session_.reset();
@ -116,7 +133,71 @@ void App::MouseMove(const glm::vec2& delta)
session_->ProcessMouseMove(delta_yaw, delta_pitch); 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<gfx::Text>(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<char> 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);
}
}

View File

@ -1,15 +1,24 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <deque>
#include "game/player_input.hpp" #include "game/player_input.hpp"
#include "gfx/renderer.hpp" #include "gfx/renderer.hpp"
#include "gfx/text.hpp"
#include "audio/master.hpp" #include "audio/master.hpp"
#include "net/msg_producer.hpp" #include "net/msg_producer.hpp"
#include "net/inmessage.hpp" #include "net/inmessage.hpp"
#include "gameview/client_session.hpp" #include "gameview/client_session.hpp"
struct ChatMessage
{
std::unique_ptr<gfx::Text> text;
float timeout = 0.0f;
glm::vec4 color = glm::vec4(1.0f);
};
class App : public net::MsgProducer class App : public net::MsgProducer
{ {
@ -32,11 +41,18 @@ public:
audio::Master& GetAudioMaster() { return audiomaster_; } audio::Master& GetAudioMaster() { return audiomaster_; }
void AddChatMessage(const std::string& text);
void AddChatMessagePrefix(const std::string& prefix, const std::string& text);
~App(); ~App();
private: private:
void Send(std::vector<char> data); void Send(std::vector<char> data);
void InitChat();
void UpdateChat();
void DrawChat(gfx::DrawList& dlist);
private: private:
float time_ = 0.0f; float time_ = 0.0f;
float last_send_time_ = 0.0f; float last_send_time_ = 0.0f;
@ -53,4 +69,9 @@ private:
audio::Master audiomaster_; audio::Master audiomaster_;
std::unique_ptr<game::view::ClientSession> session_; std::unique_ptr<game::view::ClientSession> session_;
std::shared_ptr<const gfx::Font> font_;
std::deque<ChatMessage> chat_;
std::vector<gfx::HudPosition> chatpos_;
}; };

View File

@ -198,7 +198,7 @@ static EM_BOOL OnWSError(int type, const EmscriptenWebSocketErrorEvent *ev, void
return EM_TRUE; return EM_TRUE;
} }
static bool WSInit() static bool WSInit(const char* url)
{ {
if (!emscripten_websocket_is_supported()) if (!emscripten_websocket_is_supported())
{ {
@ -206,7 +206,7 @@ static bool WSInit()
return false; return false;
} }
EmscriptenWebSocketCreateAttributes ws_attrs = { EmscriptenWebSocketCreateAttributes ws_attrs = {
WS_URL, url,
NULL, NULL,
EM_TRUE EM_TRUE
}; };
@ -234,7 +234,7 @@ using namespace easywsclient;
static std::unique_ptr<WebSocket> s_ws; static std::unique_ptr<WebSocket> s_ws;
static bool WSInit() static bool WSInit(const char* url)
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -249,7 +249,7 @@ static bool WSInit()
} }
#endif #endif
s_ws = std::unique_ptr<WebSocket>(WebSocket::from_url(WS_URL)); s_ws = std::unique_ptr<WebSocket>(WebSocket::from_url(url));
if (!s_ws) if (!s_ws)
return false; return false;
@ -368,7 +368,7 @@ static void Frame()
} }
static void Main() { static void Main() {
if (!WSInit()) if (!WSInit(WS_URL))
return; return;
InitSDL(); InitSDL();
@ -386,6 +386,7 @@ static void Main() {
SDL_SetRelativeMouseMode(SDL_TRUE); SDL_SetRelativeMouseMode(SDL_TRUE);
s_app = std::make_unique<App>(); s_app = std::make_unique<App>();
s_app->AddChatMessagePrefix("WebSocket", "připojování na " + std::string(WS_URL));
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
emscripten_set_main_loop(Frame, 0, true); emscripten_set_main_loop(Frame, 0, true);

View File

@ -8,7 +8,20 @@ game::OpenWorld::OpenWorld() : World("openworld") {}
void game::OpenWorld::PlayerJoined(Player& player) void game::OpenWorld::PlayerJoined(Player& player)
{ {
// spawn him car // spawn him car
auto& vehicle = Spawn<Vehicle>("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>(vehicle_name, color);
player.Control(&vehicle); player.Control(&vehicle);
player_vehicles_[&player] = vehicle.GetEntNum(); player_vehicles_[&player] = vehicle.GetEntNum();

View File

@ -12,32 +12,24 @@ static std::shared_ptr<const assets::VehicleModel> LoadVehicleModelByName(const
return assets::CacheManager::GetVehicleModel("data/" + model_name + ".veh"); return assets::CacheManager::GetVehicleModel("data/" + model_name + ".veh");
} }
struct Shape game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& color)
{
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)
: Entity(world, net::ET_VEHICLE), model_name_(model_name), model_(LoadVehicleModelByName(model_name)), : 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; root_.local.position.z = 10.0f;
// setup chassis rigidbody // setup chassis rigidbody
float mass = 1300.0f; 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); 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<btRigidBody>(rb_info); body_ = std::make_unique<btRigidBody>(rb_info);
body_->setActivationState(DISABLE_DEACTIVATION); body_->setActivationState(DISABLE_DEACTIVATION);
@ -52,7 +44,7 @@ game::Vehicle::Vehicle(World& world, std::string model_name)
btVector3 wheelDirectionCS0(0, 0, -1); btVector3 wheelDirectionCS0(0, 0, -1);
btVector3 wheelAxleCS(1, 0, 0); btVector3 wheelAxleCS(1, 0, 0);
wheel_z_offset_ = 0.4f; wheel_z_offset_ = 0.5f;
const auto& wheels = model_->GetWheels(); const auto& wheels = model_->GetWheels();
@ -63,10 +55,10 @@ game::Vehicle::Vehicle(World& world, std::string model_name)
for (const auto& wheeldef : wheels) for (const auto& wheeldef : wheels)
{ {
float wheelRadius = .35f; float wheelRadius = wheeldef.radius;
float friction = 5.0f; float friction = 2.0f; // 5.0f;
float suspensionStiffness = 60.0f; float suspensionStiffness = 50.0f;
// float suspensionDamping = 2.3f; // float suspensionDamping = 2.3f;
// float suspensionCompression = 4.4f; // float suspensionCompression = 4.4f;
float suspensionRestLength = 0.6f; float suspensionRestLength = 0.6f;
@ -116,6 +108,7 @@ void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const
{ {
net::ModelName name(model_name_); net::ModelName name(model_name_);
msg.Write(name); msg.Write(name);
net::WriteRGB(msg, color_); // primary color
} }
game::Vehicle::~Vehicle() game::Vehicle::~Vehicle()

View File

@ -27,7 +27,7 @@ class Vehicle : public Entity, public Controllable
public: public:
using Super = Entity; 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 Update() override;
virtual void SendInitData(Player& player, net::OutMessage& msg) const override; virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
@ -45,6 +45,7 @@ private:
private: private:
std::string model_name_; std::string model_name_;
std::shared_ptr<const assets::VehicleModel> model_; std::shared_ptr<const assets::VehicleModel> model_;
glm::vec3 color_;
collision::MotionState motion_; collision::MotionState motion_;
std::unique_ptr<btRigidBody> body_; std::unique_ptr<btRigidBody> body_;

View File

@ -6,7 +6,7 @@
#include <iostream> #include <iostream>
game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr<const assets::VehicleModel> model) game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr<const assets::VehicleModel> model, const glm::vec3& color)
: EntityView(world), model_(std::move(model)) : EntityView(world), model_(std::move(model))
{ {
auto& modelwheels = model_->GetWheels(); auto& modelwheels = model_->GetWheels();
@ -17,18 +17,21 @@ game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr<const ass
wheels_[i].node.parent = &root_; wheels_[i].node.parent = &root_;
} }
color_ = glm::vec4(color, 1.0f);
snd_accel_ = assets::CacheManager::GetSound("data/auto.snd"); snd_accel_ = assets::CacheManager::GetSound("data/auto.snd");
} }
std::unique_ptr<game::view::VehicleView> game::view::VehicleView::InitFromMsg(WorldView& world, net::InMessage& msg) std::unique_ptr<game::view::VehicleView> game::view::VehicleView::InitFromMsg(WorldView& world, net::InMessage& msg)
{ {
net::ModelName modelname; net::ModelName modelname;
if (!msg.Read(modelname)) glm::vec3 color;
if (!msg.Read(modelname) || !net::ReadRGB(msg, color))
return nullptr; return nullptr;
auto model = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh"); auto model = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh");
return std::make_unique<VehicleView>(world, std::move(model)); return std::make_unique<VehicleView>(world, std::move(model), color);
} }
bool game::view::VehicleView::ProcessMsg(net::EntMsgType type, net::InMessage& msg) bool game::view::VehicleView::ProcessMsg(net::EntMsgType type, net::InMessage& msg)
@ -70,6 +73,10 @@ void game::view::VehicleView::Update(const UpdateInfo& info)
wheeltrans.rotation = glm::rotate(wheeltrans.rotation, wheelstate.steering, glm::vec3(0, 0, 1)); 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)); 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(); wheels_[i].node.UpdateMatrix();
} }
@ -99,6 +106,7 @@ void game::view::VehicleView::Draw(gfx::DrawList& dlist)
gfx::DrawSurfaceCmd cmd; gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface; cmd.surface = &surface;
cmd.matrices = &root_.matrix; cmd.matrices = &root_.matrix;
cmd.color = &color_;
dlist.AddSurface(cmd); dlist.AddSurface(cmd);
} }

View File

@ -23,7 +23,7 @@ class VehicleView : public EntityView
{ {
using Super = EntityView; using Super = EntityView;
public: public:
VehicleView(WorldView& world, std::shared_ptr<const assets::VehicleModel> model); VehicleView(WorldView& world, std::shared_ptr<const assets::VehicleModel> model, const glm::vec3& color);
static std::unique_ptr<VehicleView> InitFromMsg(WorldView& world, net::InMessage& msg); static std::unique_ptr<VehicleView> InitFromMsg(WorldView& world, net::InMessage& msg);
virtual bool ProcessMsg(net::EntMsgType type, net::InMessage& msg) override; virtual bool ProcessMsg(net::EntMsgType type, net::InMessage& msg) override;
@ -35,6 +35,7 @@ private:
private: private:
std::shared_ptr<const assets::VehicleModel> model_; std::shared_ptr<const assets::VehicleModel> model_;
glm::vec4 color_;
std::vector<VehicleWheelViewInfo> wheels_; std::vector<VehicleWheelViewInfo> wheels_;

View File

@ -4,6 +4,7 @@
#include "assets/skeleton.hpp" #include "assets/skeleton.hpp"
#include "surface.hpp" #include "surface.hpp"
#include "hud.hpp"
namespace gfx namespace gfx
{ {
@ -18,13 +19,27 @@ struct DrawSurfaceCmd
float dist = 0.0f; // distance to camera - for transparnt sorting 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 struct DrawList
{ {
std::vector<DrawSurfaceCmd> surfaces; std::vector<DrawSurfaceCmd> surfaces;
std::vector<DrawHudCmd> huds;
void AddSurface(const DrawSurfaceCmd& cmd) { surfaces.emplace_back(cmd); } 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 } // namespace gfx

46
src/gfx/font.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "font.hpp"
#include "assets/cache.hpp"
#include "assets/cmdfile.hpp"
std::shared_ptr<const gfx::Font> gfx::Font::LoadFromFile(const std::string& path)
{
auto font = std::make_shared<Font>();
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;
}

101
src/gfx/font.hpp Normal file
View File

@ -0,0 +1,101 @@
#pragma once
#include <array>
#include <map>
#include <memory>
#include <glm/glm.hpp>
#include "texture.hpp"
namespace gfx
{
using Codepoint = uint32_t;
template <class T>
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<ArrayItem, MAX_ARRAY_CODEPOINT> array_; // For common ASCII codepoints
std::map<Codepoint, T> 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<const Font> 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<const Texture>& GetTexture() const { return texture_; }
float GetLineHeight() const { return line_height_; }
private:
std::shared_ptr<const Texture> texture_; // Texture atlas for the font
CodepointMap<FontGlyphData> glyphs_;
float line_height_ = 0.0f; // Line height in pixels, used for text layout
};
} // namespace gfx

15
src/gfx/hud.hpp Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <glm/glm.hpp>
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);
};
}

View File

@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <ranges> #include <ranges>
#include <stdexcept>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
@ -15,10 +16,12 @@ gfx::Renderer::Renderer()
ShaderSources::MakeShader(mesh_shader_.shader, SS_MESH_VERT, SS_MESH_FRAG); 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(skel_mesh_shader_.shader, SS_SKEL_MESH_VERT, SS_SKEL_MESH_FRAG);
ShaderSources::MakeShader(solid_shader_, SS_SOLID_VERT, SS_SOLID_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) void gfx::Renderer::Begin(size_t width, size_t height)
{ {
current_shader_ = nullptr;
glViewport(0, 0, width, height); glViewport(0, 0, width, height);
} }
@ -36,6 +39,7 @@ void gfx::Renderer::ClearDepth()
void gfx::Renderer::DrawList(gfx::DrawList& list, const DrawListParams& params) void gfx::Renderer::DrawList(gfx::DrawList& list, const DrawListParams& params)
{ {
DrawSurfaceList(list.surfaces, params); DrawSurfaceList(list.surfaces, params);
DrawHudList(list.huds, params);
} }
void gfx::Renderer::InvalidateShaders() void gfx::Renderer::InvalidateShaders()
@ -106,12 +110,14 @@ void gfx::Renderer::DrawSurfaceList(std::span<DrawSurfaceCmd> list, const DrawLi
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
glCullFace(GL_BACK); glCullFace(GL_BACK);
glDisable(GL_BLEND);
InvalidateShaders(); InvalidateShaders();
// cache to eliminate fake state changes // cache to eliminate fake state changes
const gfx::Texture* last_texture = nullptr; const gfx::Texture* last_texture = nullptr;
const gfx::VertexArray* last_vao = nullptr; const gfx::VertexArray* last_vao = nullptr;
bool last_double_sided = false; bool last_twosided = false;
glActiveTexture(GL_TEXTURE0); // for all future bindings glActiveTexture(GL_TEXTURE0); // for all future bindings
@ -122,19 +128,19 @@ void gfx::Renderer::DrawSurfaceList(std::span<DrawSurfaceCmd> list, const DrawLi
// mesh flags // mesh flags
const bool skeletal_flag = surface->mflags & MF_SKELETAL; const bool skeletal_flag = surface->mflags & MF_SKELETAL;
// surface flags // 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 transparent_flag = surface->sflags & SF_TRANSPARENT;
const bool object_color_flag = surface->sflags & SF_OBJECT_COLOR; const bool object_color_flag = surface->sflags & SF_OBJECT_COLOR;
// adjust state // sync 2sided
if (last_double_sided != double_sided_flag) if (last_twosided != twosided_flag)
{ {
if (double_sided_flag) if (twosided_flag)
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
else else
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
last_double_sided = double_sided_flag; last_twosided = twosided_flag;
} }
// select shader // select shader
@ -144,19 +150,36 @@ void gfx::Renderer::DrawSurfaceList(std::span<DrawSurfaceCmd> list, const DrawLi
// set model matrix // set model matrix
if (cmd.matrices) 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 else
{ // use identity if no matrix provided { // use identity if no matrix provided
static const glm::mat4 identity(1.0f); 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 // 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) 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; mshader.color = color;
} }
@ -186,3 +209,75 @@ void gfx::Renderer::DrawSurfaceList(std::span<DrawSurfaceCmd> list, const DrawLi
} }
void gfx::Renderer::DrawHudList(std::span<DrawHudCmd> 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<float>(params.screen_width);
float h = static_cast<float>(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<GLsizei>(cmd.va->GetNumIndices()), GL_UNSIGNED_INT, NULL);
}
}

View File

@ -11,6 +11,8 @@ namespace gfx
struct DrawListParams struct DrawListParams
{ {
glm::mat4 view_proj; glm::mat4 view_proj;
size_t screen_width = 0;
size_t screen_height = 0;
}; };
struct MeshShader struct MeshShader
@ -20,6 +22,7 @@ namespace gfx
// cached state to avoid redundant uniform updates which are expensive especially on WebGL // cached state to avoid redundant uniform updates which are expensive especially on WebGL
bool global_setup = false; bool global_setup = false;
glm::vec4 color = glm::vec4(-1.0f); // invalid to force initial setup glm::vec4 color = glm::vec4(-1.0f); // invalid to force initial setup
bool cull_alpha = false;
}; };
class Renderer class Renderer
@ -38,6 +41,7 @@ namespace gfx
MeshShader mesh_shader_; MeshShader mesh_shader_;
MeshShader skel_mesh_shader_; MeshShader skel_mesh_shader_;
std::unique_ptr<Shader> solid_shader_; std::unique_ptr<Shader> solid_shader_;
std::unique_ptr<Shader> hud_shader_;
const Shader* current_shader_ = nullptr; const Shader* current_shader_ = nullptr;
@ -46,6 +50,7 @@ namespace gfx
void SetupMeshShader(MeshShader& mshader, const DrawListParams& params); void SetupMeshShader(MeshShader& mshader, const DrawListParams& params);
void DrawSurfaceList(std::span<DrawSurfaceCmd> queue, const DrawListParams& params); void DrawSurfaceList(std::span<DrawSurfaceCmd> queue, const DrawListParams& params);
void DrawHudList(std::span<DrawHudCmd> queue, const DrawListParams& params);
}; };

View File

@ -8,7 +8,8 @@ static const char* const s_uni_names[] = {
"u_model", // SU_MODEL "u_model", // SU_MODEL
"u_view_proj", // SU_VIEW_PROJ "u_view_proj", // SU_VIEW_PROJ
"u_tex", // SU_TEX "u_tex", // SU_TEX
"u_color" // SU_COLOR "u_color", // SU_COLOR
"u_cull_alpha", // SU_COLOR
}; };
// Vytvori shader z daneho zdroje // Vytvori shader z daneho zdroje

View File

@ -14,6 +14,7 @@ namespace gfx
SU_VIEW_PROJ, SU_VIEW_PROJ,
SU_TEX, SU_TEX,
SU_COLOR, SU_COLOR,
SU_CULL_ALPHA,
SU_COUNT SU_COUNT
}; };

View File

@ -79,7 +79,7 @@ SHADER_HEADER
R"GLSL( R"GLSL(
layout (location = 0) in vec3 a_pos; layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec3 a_normal; 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; layout (location = 3) in vec2 a_uv;
)GLSL" )GLSL"
@ -96,8 +96,8 @@ void main() {
vec3 world_normal = normalize(mat3(u_model) * a_normal); vec3 world_normal = normalize(mat3(u_model) * a_normal);
gl_Position = u_view_proj * world_pos; 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) * a_color; v_color = ComputeLights(world_pos.xyz, world_normal) * a_color.rgb;
} }
)GLSL", )GLSL",
@ -108,11 +108,25 @@ in vec2 v_uv;
in vec3 v_color; in vec3 v_color;
uniform sampler2D u_tex; uniform sampler2D u_tex;
uniform vec4 u_color;
uniform bool u_cull_alpha;
layout (location = 0) out vec4 o_color; layout (location = 0) out vec4 o_color;
void main() { void main() {
o_color = vec4(texture(u_tex, v_uv)); 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.rgb *= v_color; // Apply vertex color
//o_color = vec4(1.0, 0.0, 0.0, 1.0); //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); vec3 world_normal = normalize(mat3(u_model) * mat3(bone_transform) * a_normal);
gl_Position = u_view_proj * world_pos; 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); v_color = ComputeLights(world_pos.xyz, world_normal);
} }
@ -209,6 +223,44 @@ void main() {
)GLSL", )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",
}; };

View File

@ -16,6 +16,9 @@ namespace gfx
SS_SOLID_VERT, SS_SOLID_VERT,
SS_SOLID_FRAG, SS_SOLID_FRAG,
SS_HUD_VERT,
SS_HUD_FRAG,
}; };
class ShaderSources class ShaderSources

View File

@ -20,7 +20,7 @@ using SurfaceFlags = uint8_t;
enum SurfaceFlag : SurfaceFlags enum SurfaceFlag : SurfaceFlags
{ {
SF_NONE = 0x00, SF_NONE = 0x00,
SF_DOUBLE_SIDED = 0x01, SF_2SIDED = 0x01,
SF_TRANSPARENT = 0x02, SF_TRANSPARENT = 0x02,
SF_OBJECT_COLOR = 0x08, // use object level tint SF_OBJECT_COLOR = 0x08, // use object level tint
}; };

162
src/gfx/text.cpp Normal file
View File

@ -0,0 +1,162 @@
#include "text.hpp"
#include "utils/bufferput.hpp"
gfx::Text::Text(std::shared_ptr<const Font> 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<TextVertex>& 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<TextVertex> vertices;
static std::vector<uint32_t> 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());
}

26
src/gfx/text.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include "font.hpp"
#include "vertex_array.hpp"
namespace gfx
{
class Text
{
public:
Text(std::shared_ptr<const Font> font, uint32_t color);
void SetText(const char* text);
void SetText(const std::string& text) { SetText(text.c_str()); }
const std::shared_ptr<const Font>& GetFont() const { return font_; }
const VertexArray& GetVA() const { return va_; }
private:
std::shared_ptr<const Font> font_;
VertexArray va_;
uint32_t color_;
};
} // namespace gfx

View File

@ -25,7 +25,7 @@ static void GetGLFilterModes(bool linear, bool mipmaps, GLenum& filter_min, GLen
if (mipmaps) if (mipmaps)
{ {
// Mipmaps always linear // Mipmaps always linear
filter_min = GL_NEAREST_MIPMAP_LINEAR; filter_min = GL_LINEAR_MIPMAP_LINEAR;
} }
} }
} }

View File

@ -76,4 +76,6 @@ using QuatQ = Quantized<uint16_t, -1, 1, 1>;
using WheelZOffsetQ = Quantized<uint8_t, -1, 1, 1>; using WheelZOffsetQ = Quantized<uint8_t, -1, 1, 1>;
using RotationSpeedQ = Quantized<uint16_t, -300, 300, 1>; using RotationSpeedQ = Quantized<uint16_t, -300, 300, 1>;
using ColorQ = Quantized<uint8_t, 0, 1>;
} // namespace net } // namespace net

View File

@ -8,6 +8,8 @@
namespace net namespace net
{ {
/// TRANSFORMS
inline void WritePosition(OutMessage& msg, const glm::vec3& pos) inline void WritePosition(OutMessage& msg, const glm::vec3& pos)
{ {
msg.Write<PositionQ>(pos.x); msg.Write<PositionQ>(pos.x);
@ -54,4 +56,18 @@ inline bool ReadTransform(InMessage& msg, Transform& trans)
return ReadPosition(msg, trans.position) && ReadRotation(msg, trans.rotation); return ReadPosition(msg, trans.position) && ReadRotation(msg, trans.rotation);
} }
/// COLOR
inline void WriteRGB(OutMessage& msg, const glm::vec3& color)
{
msg.Write<ColorQ>(color.r);
msg.Write<ColorQ>(color.g);
msg.Write<ColorQ>(color.b);
}
inline bool ReadRGB(InMessage& msg, glm::vec3& color)
{
return msg.Read<ColorQ>(color.r) && msg.Read<ColorQ>(color.g) && msg.Read<ColorQ>(color.b);
}
} }

10
src/utils/bufferput.hpp Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <vector>
template <class T>
inline void BufferPut(std::vector<char>& buffer, const T& val)
{
const char* data = reinterpret_cast<const char*>(&val);
buffer.insert(buffer.end(), data, data + sizeof(T));
}