diff --git a/src/assets/cmdfile.cpp b/src/assets/cmdfile.cpp index e468ba7..ff1c854 100644 --- a/src/assets/cmdfile.cpp +++ b/src/assets/cmdfile.cpp @@ -1,12 +1,13 @@ #include "cmdfile.hpp" -void assets::LoadCMDFile(const std::string& filename, - const std::function& handler) +void assets::LoadCMDStream(std::istream& is, CmdCallback handler) { - std::istringstream file = fs::ReadFileAsStream(filename); + std::string line, command; - std::string line; - while (std::getline(file, line)) + if (is.eof()) + return; + + while (std::getline(is, line)) { if (line.empty() || line[0] == '#') // Skip empty lines and comments continue; @@ -15,13 +16,20 @@ void assets::LoadCMDFile(const std::string& filename, line.erase(0, line.find_first_not_of(" \t")); std::istringstream iss(line); - - std::string command; iss >> command; - handler(command, iss); + if (!handler(command, iss)) + return; } +} +void assets::LoadCMDFile(const std::string& filename, CmdCallbackVoid handler) +{ + std::istringstream file = fs::ReadFileAsStream(filename); + LoadCMDStream(file, [handler](const std::string& command, std::istringstream& iss) { + handler(command, iss); + return true; + }); } std::string assets::ParseString(std::istringstream& iss) diff --git a/src/assets/cmdfile.hpp b/src/assets/cmdfile.hpp index dd6c6b8..d020561 100644 --- a/src/assets/cmdfile.hpp +++ b/src/assets/cmdfile.hpp @@ -10,9 +10,11 @@ namespace assets { -void LoadCMDFile(const std::string& filename, - const std::function& handler); +using CmdCallback = std::function; +using CmdCallbackVoid = std::function; +void LoadCMDStream(std::istream& is, CmdCallback handler); +void LoadCMDFile(const std::string& filename, CmdCallbackVoid handler); inline void ParseTransform(std::istringstream& iss, Transform& trans) { diff --git a/src/assets/map.cpp b/src/assets/map.cpp index 8dd6e7f..21c7762 100644 --- a/src/assets/map.cpp +++ b/src/assets/map.cpp @@ -28,8 +28,131 @@ static AABB3 TransformAABB(const AABB3& aabb, const glm::mat4& mat) std::shared_ptr assets::Map::LoadFromFile(const std::string& filename) { - auto map = std::make_shared(); + MapLoader loader(filename); + while (loader.Next()) {} + return loader.GetMap(); +} +const assets::MapGraph* assets::Map::GetGraph(const std::string& name) const +{ + auto it = graphs_.find(name); + if (it != graphs_.end()) + return &it->second; + return nullptr; +} + +// MapLoader + +assets::MapLoader::MapLoader(const std::string& filename) + : map_iss_(fs::ReadFileAsStream(filename)), map_(std::make_shared()) +{ +} + +bool assets::MapLoader::Next() +{ + switch (state_) + { + case ML_INIT: + state_ = ML_READ_MODELS; + return true; + + case ML_READ_MODELS: + ReadModels(); + state_ = ML_LOAD_BASEMODEL; + return true; + + case ML_LOAD_BASEMODEL: + LoadBaseModel(); + state_ = ML_LOAD_MODELS; + return true; + + case ML_LOAD_MODELS: + if (LoadNextModel()) + return true; + + state_ = ML_STRUCTS; + return true; + + case ML_STRUCTS: + LoadStructs(); + state_ = ML_FINISHED; + return true; + + default: + return false; + } +} + +int assets::MapLoader::GetPercent() const +{ + switch (state_) + { + case ML_INIT: + case ML_READ_MODELS: + return 0; + + case ML_LOAD_BASEMODEL: + return 10; + + case ML_LOAD_MODELS: + return 60 + models_.size() * 30 / model_names_.size(); + + case ML_STRUCTS: + return 90; + + default: + return 100; + } +} + +std::shared_ptr assets::MapLoader::GetMap() const +{ + if (state_ != ML_FINISHED) + return nullptr; + + return map_; +} + +void assets::MapLoader::ReadModels() +{ + LoadCMDStream(map_iss_, [&](const std::string& command, std::istringstream& iss) { + if (command == "basemodel") + { + iss >> basemodel_name_; + } + else if (command == "model") + { + std::string model_name; + iss >> model_name; + model_names_.emplace_back(std::move(model_name)); + } + else if (command == "endmodels") + { + return false; + } + + return true; + }); +} + +void assets::MapLoader::LoadBaseModel() +{ + map_->basemodel_ = CacheManager::GetModel("data/" + basemodel_name_ + ".mdl"); +} + +bool assets::MapLoader::LoadNextModel() +{ + if (models_.size() >= model_names_.size()) + return false; + + const auto& model_name = model_names_[models_.size()]; + models_.push_back(assets::CacheManager::GetModel("data/" + model_name + ".mdl")); + + return true; +} + +void assets::MapLoader::LoadStructs() +{ MapGraph* graph = nullptr; std::vector> graph_edges; @@ -49,24 +172,20 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& Chunk* chunk = nullptr; - LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) { - if (command == "basemodel") - { - std::string model_name; - iss >> model_name; - - map->basemodel_ = CacheManager::GetModel("data/" + model_name + ".mdl"); - } - else if (command == "static") + LoadCMDStream(map_iss_, [&](const std::string& command, std::istringstream& iss) { + if (command == "static") { if (!chunk) throw std::runtime_error("static in map without chunk"); MapStaticObject obj; - std::string model_name; - iss >> model_name; + size_t model_idx; + iss >> model_idx; - obj.model = assets::CacheManager::GetModel("data/" + model_name + ".mdl"); + if (model_idx >= models_.size()) + throw std::runtime_error("static in map with out of range model idx"); + + obj.model = models_[model_idx]; glm::vec3 angles; @@ -87,18 +206,18 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& } } - map->objs_.push_back(std::move(obj)); + map_->objs_.push_back(std::move(obj)); chunk->num_objs++; } else if (command == "chunk") { glm::ivec2 coord; - chunk = &map->chunks_.emplace_back(); + chunk = &map_->chunks_.emplace_back(); iss >> coord.x >> coord.y; iss >> chunk->aabb.min.x >> chunk->aabb.min.y >> chunk->aabb.min.z; iss >> chunk->aabb.max.x >> chunk->aabb.max.y >> chunk->aabb.max.z; - chunk->first_obj = map->objs_.size(); + chunk->first_obj = map_->objs_.size(); } else if (command == "surface") { @@ -110,10 +229,10 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& throw std::runtime_error("surface in map without chunk"); #ifdef CLIENT - if (!map->basemodel_) + if (!map_->basemodel_) throw std::runtime_error("surface in map with no basemodel"); - auto mesh = map->basemodel_->GetMesh(); + auto mesh = map_->basemodel_->GetMesh(); if (!mesh) throw std::runtime_error("surface in map with no basemodel mesh"); @@ -138,7 +257,7 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& std::string graph_name; iss >> graph_name; - graph = &map->graphs_[graph_name]; + graph = &map_->graphs_[graph_name]; graph_edges.clear(); } else if (command == "n") @@ -162,18 +281,11 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& graph_edges.emplace_back(from_idx, to_idx); } + + return true; }); if (graph) ProcessGraph(); - return map; -} - -const assets::MapGraph* assets::Map::GetGraph(const std::string& name) const -{ - auto it = graphs_.find(name); - if (it != graphs_.end()) - return &it->second; - return nullptr; } diff --git a/src/assets/map.hpp b/src/assets/map.hpp index 21ac9d6..c9ae60e 100644 --- a/src/assets/map.hpp +++ b/src/assets/map.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "game/transform_node.hpp" #include "model.hpp" @@ -66,6 +67,46 @@ private: std::vector chunks_; std::vector objs_; std::map graphs_; + + friend class MapLoader; +}; + +enum MapLoadingState +{ + ML_INIT, + ML_READ_MODELS, + ML_LOAD_BASEMODEL, + ML_LOAD_MODELS, + ML_STRUCTS, + ML_FINISHED, +}; + +class MapLoader +{ +public: + MapLoader(const std::string& filename); + + bool Next(); + int GetPercent() const; + + std::shared_ptr GetMap() const; + +private: + void ReadModels(); + void LoadBaseModel(); + bool LoadNextModel(); + void LoadStructs(); + +private: + std::istringstream map_iss_; + + MapLoadingState state_ = ML_INIT; + + std::string basemodel_name_; + std::vector model_names_; + std::vector> models_; + + std::shared_ptr map_; }; } // namespace assets \ No newline at end of file diff --git a/src/client/app.cpp b/src/client/app.cpp index 56c146b..ea7202d 100644 --- a/src/client/app.cpp +++ b/src/client/app.cpp @@ -38,16 +38,12 @@ void App::Frame() session_->Update(updinfo); } - - renderer_.Begin(viewport_size_.x, viewport_size_.y); - renderer_.ClearColor(glm::vec3(0.5f, 0.7f, 1.0f)); - renderer_.ClearDepth(); - - dlist_.Clear(); - gfx::DrawListParams params; + gfx::DrawListParams params{}; params.screen_width = viewport_size_.x; params.screen_height = viewport_size_.y; + params.env.clear_color = glm::vec3(0.1f); + dlist_.Clear(); gui_.Begin(); // draw session diff --git a/src/gameview/client_session.cpp b/src/gameview/client_session.cpp index 862f786..9fdb2db 100644 --- a/src/gameview/client_session.cpp +++ b/src/gameview/client_session.cpp @@ -182,7 +182,7 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar // glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view; - game::view::DrawArgs draw_args(dlist, gui, params.view_proj, eye, glm::ivec2(params.screen_width, params.screen_height), 500.0f); + game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, eye, glm::ivec2(params.screen_width, params.screen_height), 500.0f); world_->Draw(draw_args); glm::mat4 camera_world = glm::inverse(view); diff --git a/src/gameview/draw_args.hpp b/src/gameview/draw_args.hpp index 3cc0dc8..7374242 100644 --- a/src/gameview/draw_args.hpp +++ b/src/gameview/draw_args.hpp @@ -1,6 +1,7 @@ #pragma once #include "gfx/draw_list.hpp" +#include "gfx/renderer.hpp" #include "gfx/frustum.hpp" #include "gui/context.hpp" @@ -10,16 +11,19 @@ namespace game::view struct DrawArgs { gfx::DrawList& dlist; + gfx::DrawListEnvironmentParams& env; gui::Context& gui; - + const glm::mat4 view_proj; const glm::vec3 eye; const gfx::Frustum frustum; const glm::ivec2 screen_size; const float render_distance; - DrawArgs(gfx::DrawList& dlist, gui::Context& gui, const glm::mat4& view_proj, const glm::vec3& eye, const glm::ivec2& screen_size, float render_distance) - : dlist(dlist), gui(gui), view_proj(view_proj), eye(eye), frustum(view_proj), screen_size(screen_size), render_distance(render_distance) + DrawArgs(gfx::DrawList& dlist, gfx::DrawListEnvironmentParams& env, gui::Context& gui, const glm::mat4& view_proj, + const glm::vec3& eye, const glm::ivec2& screen_size, float render_distance) + : dlist(dlist), env(env), gui(gui), view_proj(view_proj), eye(eye), frustum(view_proj), + screen_size(screen_size), render_distance(render_distance) { } }; diff --git a/src/gameview/mapinstanceview.cpp b/src/gameview/mapinstanceview.cpp index fb3b216..f91deaa 100644 --- a/src/gameview/mapinstanceview.cpp +++ b/src/gameview/mapinstanceview.cpp @@ -6,13 +6,36 @@ game::view::MapInstanceView::MapInstanceView(const std::string& map_name) { - map_ = assets::CacheManager::GetMap("data/" + map_name + ".map"); + loader_ = std::make_unique("data/" + map_name + ".map"); +} +void game::view::MapInstanceView::LoadNext() +{ + if (IsLoaded()) + return; + + if (loader_->Next()) + return; + + // just loaded + map_ = loader_->GetMap(); objs_visible_.resize(map_->GetStaticObjects().size(), true); + loader_.reset(); +} + +int game::view::MapInstanceView::GetLoadingPercent() const +{ + if (!loader_) + return 100; + + return loader_->GetPercent(); } void game::view::MapInstanceView::Draw(const game::view::DrawArgs& args) const { + if (!map_) + return; + const auto& basemodel = map_->GetBaseModel(); if (!basemodel || !basemodel->GetMesh()) @@ -40,8 +63,10 @@ void game::view::MapInstanceView::Draw(const game::view::DrawArgs& args) const void game::view::MapInstanceView::EnableObj(net::ObjNum num, bool enable) { size_t i = static_cast(num); + + // map may be not loaded yet, in that case make the visible flag fit if (i >= objs_visible_.size()) - return; + objs_visible_.resize(i + 1, true); objs_visible_[i] = enable; } diff --git a/src/gameview/mapinstanceview.hpp b/src/gameview/mapinstanceview.hpp index aaa1147..9f33017 100644 --- a/src/gameview/mapinstanceview.hpp +++ b/src/gameview/mapinstanceview.hpp @@ -12,6 +12,10 @@ class MapInstanceView public: MapInstanceView(const std::string& map_name); + void LoadNext(); + bool IsLoaded() const { return loader_.get() == nullptr; } + int GetLoadingPercent() const; + void Draw(const game::view::DrawArgs& args) const; void EnableObj(net::ObjNum num, bool enable); @@ -20,6 +24,7 @@ private: void DrawChunk(const game::view::DrawArgs& args, const assets::Mesh& basemesh, const assets::Chunk& chunk) const; private: + std::unique_ptr loader_; std::shared_ptr map_; std::vector objs_visible_; diff --git a/src/gameview/worldview.cpp b/src/gameview/worldview.cpp index 1fd3946..d22de02 100644 --- a/src/gameview/worldview.cpp +++ b/src/gameview/worldview.cpp @@ -6,6 +6,7 @@ #include "characterview.hpp" #include "vehicleview.hpp" #include "client_session.hpp" +#include "draw_args.hpp" game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) : session_(session), audiomaster_(session_.GetAudioMaster()), map_("openworld") @@ -68,6 +69,9 @@ void game::view::WorldView::Update(const UpdateInfo& info) { time_ = info.time; + if (!map_.IsLoaded()) + map_.LoadNext(); + for (const auto& [entnum, ent] : ents_) { ent->TryUpdate(info); @@ -76,6 +80,14 @@ void game::view::WorldView::Update(const UpdateInfo& info) void game::view::WorldView::Draw(const DrawArgs& args) const { + if (!map_.IsLoaded()) + { + DrawLoadingScreen(args); + return; + } + + args.env.clear_color = glm::vec3(0.5f, 0.7f, 1.0f); + map_.Draw(args); for (const auto& [entnum, ent] : ents_) @@ -119,6 +131,22 @@ game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum) return nullptr; } +void game::view::WorldView::DrawLoadingScreen(const DrawArgs& args) const +{ + float margin = 50.0f; + glm::vec2 size(400.0f, 15.0f); + glm::vec2 pos(margin, args.screen_size.y - margin - size.y); + + int loaded_percent = map_.GetLoadingPercent(); + float loaded = static_cast(loaded_percent) * 0.01f; + + args.gui.DrawRect(pos, pos + size, 0x77FFFFFF); + args.gui.DrawRect(pos, pos + glm::vec2(size.x * loaded, size.y), 0xFF00FFFF); + + std::string load_text = std::to_string(loaded_percent) + "%"; + args.gui.DrawTextAligned(load_text, pos + glm::vec2(size.x + 50.0f, size.y * 0.5f), glm::vec2(-0.5f, -0.5f)); +} + bool game::view::WorldView::ProcessEntSpawnMsg(net::InMessage& msg) { net::EntNum entnum; diff --git a/src/gameview/worldview.hpp b/src/gameview/worldview.hpp index b15b33a..52382e4 100644 --- a/src/gameview/worldview.hpp +++ b/src/gameview/worldview.hpp @@ -33,6 +33,8 @@ public: audio::Master& GetAudioMaster() const { return audiomaster_; } private: + void DrawLoadingScreen(const DrawArgs& args) const; + // msg handlers bool ProcessEntSpawnMsg(net::InMessage& msg); bool ProcessEntMsgMsg(net::InMessage& msg); diff --git a/src/gfx/renderer.cpp b/src/gfx/renderer.cpp index cc33f9c..832d414 100644 --- a/src/gfx/renderer.cpp +++ b/src/gfx/renderer.cpp @@ -24,27 +24,13 @@ gfx::Renderer::Renderer() SetupBeamVA(); } -void gfx::Renderer::Begin(size_t width, size_t height) -{ - current_shader_ = nullptr; - glViewport(0, 0, width, height); - - //glEnable(GL_MULTISAMPLE); -} - -void gfx::Renderer::ClearColor(const glm::vec3& color) -{ - glClearColor(color.r, color.g, color.b, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); -} - -void gfx::Renderer::ClearDepth() -{ - glClear(GL_DEPTH_BUFFER_BIT); -} - void gfx::Renderer::DrawList(gfx::DrawList& list, const DrawListParams& params) { + current_shader_ = nullptr; + glViewport(0, 0, params.screen_width, params.screen_height); + glClearColor(params.env.clear_color.r, params.env.clear_color.g, params.env.clear_color.b, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + DrawSurfaceList(list.surfaces, params); DrawBeamList(list.beams, params); DrawHudList(list.huds, params); diff --git a/src/gfx/renderer.hpp b/src/gfx/renderer.hpp index 9d0b0fd..01612d0 100644 --- a/src/gfx/renderer.hpp +++ b/src/gfx/renderer.hpp @@ -3,67 +3,66 @@ #include #include -#include "shader.hpp" #include "draw_list.hpp" +#include "shader.hpp" namespace gfx { - struct DrawListParams - { - glm::vec3 cam_pos; - glm::mat4 view_proj; - size_t screen_width = 0; - size_t screen_height = 0; - }; - struct MeshShader - { - std::unique_ptr shader; +struct DrawListEnvironmentParams +{ + glm::vec3 clear_color; +}; - // 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 - int flags = 0; - }; +struct DrawListParams +{ + DrawListEnvironmentParams env; + glm::vec3 cam_pos; + glm::mat4 view_proj; + size_t screen_width = 0; + size_t screen_height = 0; +}; - class Renderer - { - public: - Renderer(); +struct MeshShader +{ + std::unique_ptr shader; - void Begin(size_t width, size_t height); + // 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 + int flags = 0; +}; - void ClearColor(const glm::vec3& color); - void ClearDepth(); +class Renderer +{ +public: + Renderer(); + void DrawList(gfx::DrawList& list, const DrawListParams& params); - void DrawList(gfx::DrawList& list, const DrawListParams& params); +private: + void SetupBeamVA(); - private: - void SetupBeamVA(); + void InvalidateShaders(); + void InvalidateMeshShader(MeshShader& mshader); + void SetupMeshShader(MeshShader& mshader, const DrawListParams& params); - void InvalidateShaders(); - void InvalidateMeshShader(MeshShader& mshader); - void SetupMeshShader(MeshShader& mshader, const DrawListParams& params); + void DrawSurfaceList(std::span queue, const DrawListParams& params); + void DrawBeamList(std::span queue, const DrawListParams& params); + void DrawHudList(std::span queue, const DrawListParams& params); - void DrawSurfaceList(std::span queue, const DrawListParams& params); - void DrawBeamList(std::span queue, const DrawListParams& params); - void DrawHudList(std::span queue, const DrawListParams& params); +private: + MeshShader mesh_shader_; + MeshShader skel_mesh_shader_; + MeshShader deform_mesh_shader_; + std::unique_ptr solid_shader_; - private: - MeshShader mesh_shader_; - MeshShader skel_mesh_shader_; - MeshShader deform_mesh_shader_; - std::unique_ptr solid_shader_; + std::unique_ptr beam_segments_vbo_; + std::unique_ptr beam_va_; + std::unique_ptr beam_shader_; - std::unique_ptr beam_segments_vbo_; - std::unique_ptr beam_va_; - std::unique_ptr beam_shader_; + std::unique_ptr hud_shader_; - std::unique_ptr hud_shader_; + const Shader* current_shader_ = nullptr; +}; - const Shader* current_shader_ = nullptr; - - - }; - -} \ No newline at end of file +} // namespace gfx \ No newline at end of file