From 4b04a77e7e509278e673a2e75312bf8bea71bf1a Mon Sep 17 00:00:00 2001 From: tovjemam Date: Sat, 31 Jan 2026 17:57:26 +0100 Subject: [PATCH] Big amount of stuff but especially map partitioning, better bot AI and nametags --- CMakeLists.txt | 5 +- src/assets/map.cpp | 87 +++++++++++-- src/assets/map.hpp | 35 ++++-- src/client/app.cpp | 11 +- src/collision/aabb.hpp | 54 -------- src/collision/dynamicsworld.cpp | 9 +- src/collision/trianglemesh.cpp | 2 +- src/collision/trianglemesh.hpp | 1 - src/game/entity.cpp | 22 ++++ src/game/entity.hpp | 11 +- src/game/openworld.cpp | 213 +++++++++++++++++++++++++------- src/game/vehicle.cpp | 2 + src/gameview/draw_args.hpp | 20 +++ src/gameview/entityview.cpp | 69 ++++++++++- src/gameview/entityview.hpp | 30 +++-- src/gameview/vehicleview.cpp | 45 ++++--- src/gameview/vehicleview.hpp | 6 +- src/gameview/worldview.cpp | 29 +++-- src/gameview/worldview.hpp | 4 +- src/gfx/buffer_object.cpp | 2 +- src/gfx/frustum.cpp | 79 ++++++++++++ src/gfx/frustum.hpp | 22 ++++ src/gfx/text.cpp | 15 ++- src/gfx/text.hpp | 2 + src/net/defs.hpp | 3 + src/server/server.cpp | 2 +- src/utils/aabb.hpp | 49 ++++++++ src/utils/sphere.hpp | 9 ++ 28 files changed, 658 insertions(+), 180 deletions(-) delete mode 100644 src/collision/aabb.hpp create mode 100644 src/gameview/draw_args.hpp create mode 100644 src/gfx/frustum.cpp create mode 100644 src/gfx/frustum.hpp create mode 100644 src/utils/aabb.hpp create mode 100644 src/utils/sphere.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 55587cb..fa63b2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,6 @@ set(COMMON_SOURCES "src/assets/skeleton.cpp" "src/assets/vehiclemdl.hpp" "src/assets/vehiclemdl.cpp" - "src/collision/aabb.hpp" "src/collision/dynamicsworld.hpp" "src/collision/dynamicsworld.cpp" "src/collision/motionstate.hpp" @@ -37,10 +36,12 @@ set(COMMON_SOURCES "src/net/outmessage.hpp" "src/net/quantized.hpp" "src/net/utils.hpp" + "src/utils/aabb.hpp" "src/utils/allocnum.hpp" "src/utils/defs.hpp" "src/utils/files.hpp" "src/utils/scheduler.hpp" + "src/utils/sphere.hpp" "src/utils/transform.hpp" "src/utils/validate.hpp" ) @@ -79,6 +80,8 @@ set(CLIENT_ONLY_SOURCES "src/gfx/draw_list.hpp" "src/gfx/font.hpp" "src/gfx/font.cpp" + "src/gfx/frustum.hpp" + "src/gfx/frustum.cpp" "src/gfx/hud.hpp" "src/gfx/renderer.hpp" "src/gfx/renderer.cpp" diff --git a/src/assets/map.cpp b/src/assets/map.cpp index d6afc05..533fa75 100644 --- a/src/assets/map.cpp +++ b/src/assets/map.cpp @@ -27,6 +27,8 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& } }; + Chunk* chunk = nullptr; + LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) { if (command == "basemodel") { @@ -37,8 +39,10 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& } else if (command == "static") { - MapStaticObject obj; + if (!chunk) + throw std::runtime_error("static in map without chunk"); + ChunkStaticObject obj; std::string model_name; iss >> model_name; @@ -55,6 +59,9 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& obj.node.UpdateMatrix(); + obj.aabb.min = trans->position - glm::vec3(1.0f); + obj.aabb.max = trans->position + glm::vec3(1.0f); + std::string flag; while (iss >> flag) { @@ -64,8 +71,47 @@ std::shared_ptr assets::Map::LoadFromFile(const std::string& } } - map->static_objects_.push_back(std::move(obj)); + chunk->objs.push_back(std::move(obj)); } + else if (command == "chunk") + { + glm::ivec2 coord; + 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; + + } + else if (command == "surface") + { + std::string name; + size_t first, count; + iss >> name >> first >> count; + + if (!chunk) + throw std::runtime_error("surface in map without chunk"); + +#ifdef CLIENT + if (!map->basemodel_) + throw std::runtime_error("surface in map with no basemodel"); + + auto mesh = map->basemodel_->GetMesh(); + + if (!mesh) + throw std::runtime_error("surface in map with no basemodel mesh"); + + auto it = mesh->surface_names.find(name); + if (it == mesh->surface_names.end()) + throw std::runtime_error("surface name not found"); + + if (first + count > mesh->surfaces[it->second].count) + throw std::runtime_error("surface invalid range"); + + chunk->surfaces.emplace_back(it->second, first, count); + +#endif /* CLIENT */ + } + else if (command == "graph") { if (graph) @@ -115,25 +161,42 @@ const assets::MapGraph* assets::Map::GetGraph(const std::string& name) const } #ifdef CLIENT -void assets::Map::Draw(gfx::DrawList& dlist) const +void assets::Map::Draw(const game::view::DrawArgs& args) const { if (!basemodel_ || !basemodel_->GetMesh()) return; - const auto& surfaces = basemodel_->GetMesh()->surfaces; + const auto& mesh = *basemodel_->GetMesh(); - for (const auto& surface : surfaces) + for (auto& chunk : chunks_) { - gfx::DrawSurfaceCmd cmd; - cmd.surface = &surface; - dlist.AddSurface(cmd); + if (args.frustum.IsAABBVisible(chunk.aabb)) + DrawChunk(args, mesh, chunk); } - for (const auto& obj : static_objects_) +} + +void assets::Map::DrawChunk(const game::view::DrawArgs& args, const Mesh& basemesh, const Chunk& chunk) const +{ + for (const auto& surface_range : chunk.surfaces) + { + auto& surface = basemesh.surfaces[surface_range.idx]; + + gfx::DrawSurfaceCmd cmd; + cmd.surface = &surface; + cmd.first = surface_range.first; + cmd.count = surface_range.count; + args.dlist.AddSurface(cmd); + } + + for (const auto& obj : chunk.objs) { if (!obj.model || !obj.model->GetMesh()) continue; + if (!args.frustum.IsAABBVisible(obj.aabb)) + continue; + const auto& surfaces = obj.model->GetMesh()->surfaces; for (const auto& surface : surfaces) @@ -142,8 +205,10 @@ void assets::Map::Draw(gfx::DrawList& dlist) const cmd.surface = &surface; cmd.matrices = &obj.node.matrix; // cmd.color_mod = glm::vec4(obj.color, 1.0f); - dlist.AddSurface(cmd); + args.dlist.AddSurface(cmd); } } } -#endif // CLIENT \ No newline at end of file + +#endif // CLIENT + diff --git a/src/assets/map.hpp b/src/assets/map.hpp index e683916..5e8a814 100644 --- a/src/assets/map.hpp +++ b/src/assets/map.hpp @@ -1,27 +1,45 @@ #pragma once +#include #include #include -#include #include -#include "model.hpp" #include "game/transform_node.hpp" +#include "model.hpp" +#include "utils/aabb.hpp" #ifdef CLIENT -#include "gfx/draw_list.hpp" +#include "gameview/draw_args.hpp" #endif // CLIENT namespace assets { -struct MapStaticObject +struct ChunkStaticObject { game::TransformNode node; + AABB3 aabb; std::shared_ptr model; glm::vec3 color = glm::vec3(1.0f); }; +struct ChunkSurfaceRange +{ + size_t idx = 0; + size_t first = 0; + size_t count = 0; + + ChunkSurfaceRange(size_t idx, size_t first, size_t count) : idx(idx), first(first), count(count) {} +}; + +struct Chunk +{ + AABB3 aabb; + std::vector surfaces; + std::vector objs; +}; + struct MapGraphNode { glm::vec3 position = glm::vec3(0.0f); @@ -42,14 +60,17 @@ public: static std::shared_ptr LoadFromFile(const std::string& filename); const std::shared_ptr& GetBaseModel() const { return basemodel_; } - const std::vector& GetStaticObjects() const { return static_objects_; } + const std::vector& GetChunks() const { return chunks_; } const MapGraph* GetGraph(const std::string& name) const; - CLIENT_ONLY(void Draw(gfx::DrawList& dlist) const;) + CLIENT_ONLY(void Draw(const game::view::DrawArgs& args) const;) + +private: + CLIENT_ONLY(void DrawChunk(const game::view::DrawArgs& args, const Mesh& basemesh, const Chunk& chunk) const;) private: std::shared_ptr basemodel_; - std::vector static_objects_; + std::vector chunks_; std::map graphs_; }; diff --git a/src/client/app.cpp b/src/client/app.cpp index 8d0e1a9..5d5a188 100644 --- a/src/client/app.cpp +++ b/src/client/app.cpp @@ -80,14 +80,15 @@ void App::Frame() const game::view::WorldView* world; if (session_ && (world = session_->GetWorld())) { - world->Draw(dlist_); - // glm::mat4 view = glm::lookAt(glm::vec3(15.0f, 0.0f, 1.0f), glm::vec3(0.0f, 0.0f, -13.0f), glm::vec3(0.0f, 0.0f, 1.0f)); glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 3000.0f); glm::mat4 view = session_->GetViewMatrix(); params.view_proj = proj * view; - + + game::view::DrawArgs draw_args(dlist_, params.view_proj, viewport_size_); + world->Draw(draw_args); + glm::mat4 camera_world = glm::inverse(view); audiomaster_.SetListenerOrientation(camera_world); } @@ -127,9 +128,11 @@ void App::ProcessMessage(net::InMessage& msg) if (!session_) return; - //size_t s = msg.End() - msg.Ptr(); + size_t s = msg.End() - msg.Ptr(); // AddChatMessage("recvd: ^f00;" + std::to_string(s)); + // std::cout << "App::ProcessMessage: received message of size " << s << " bytes" << std::endl; + session_->ProcessMessage(msg); } diff --git a/src/collision/aabb.hpp b/src/collision/aabb.hpp deleted file mode 100644 index 04aa18e..0000000 --- a/src/collision/aabb.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include - -namespace collision -{ - template - struct AABB - { - static constexpr float C_INFINITY = std::numeric_limits::infinity(); - - using Vec = glm::vec; - - Vec min = Vec(C_INFINITY); - Vec max = Vec(-C_INFINITY); - - AABB() = default; - AABB(const Vec& min, const Vec& max) : min(min), max(max) {} - - void AddPoint(const Vec& point) - { - min = glm::min(min, point); - max = glm::max(max, point); - } - - bool CollidesWith(const AABB& other) const; - - AABB Intersection(const AABB& other) const - { - Vec new_min = glm::max(min, other.min); - Vec new_max = glm::min(max, other.max); - return AABB(new_min, new_max); - } - }; - - template <> - inline bool AABB<2>::CollidesWith(const AABB<2>& other) const - { - return (min.x <= other.max.x && max.x >= other.min.x) && - (min.y <= other.max.y && max.y >= other.min.y); - } - - template <> - inline bool AABB<3>::CollidesWith(const AABB<3>& other) const - { - return (min.x <= other.max.x && max.x >= other.min.x) && - (min.y <= other.max.y && max.y >= other.min.y) && - (min.z <= other.max.z && max.z >= other.min.z); - } - - using AABB2 = AABB<2>; - using AABB3 = AABB<3>; -} \ No newline at end of file diff --git a/src/collision/dynamicsworld.cpp b/src/collision/dynamicsworld.cpp index 326a57f..bcdc0df 100644 --- a/src/collision/dynamicsworld.cpp +++ b/src/collision/dynamicsworld.cpp @@ -36,10 +36,11 @@ void collision::DynamicsWorld::AddMapCollision() } // add static objects - for (const auto& sobjs = map_->GetStaticObjects(); const auto& sobj : sobjs) - { - AddModelInstance(*sobj.model, sobj.node.local); - } + + // for (const auto& sobjs = map_->GetStaticObjects(); const auto& sobj : sobjs) + // { + // AddModelInstance(*sobj.model, sobj.node.local); + // } } void collision::DynamicsWorld::AddModelInstance(const assets::Model& model, const Transform& trans) diff --git a/src/collision/trianglemesh.cpp b/src/collision/trianglemesh.cpp index 661e7d7..3069593 100644 --- a/src/collision/trianglemesh.cpp +++ b/src/collision/trianglemesh.cpp @@ -10,7 +10,7 @@ void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& btVector3 bt_v0(v0.x, v0.y, v0.z); btVector3 bt_v1(v1.x, v1.y, v1.z); btVector3 bt_v2(v2.x, v2.y, v2.z); - bt_mesh_.addTriangle(bt_v0, bt_v1, bt_v2, true); + bt_mesh_.addTriangle(bt_v0, bt_v1, bt_v2, false); } void collision::TriangleMesh::Build() diff --git a/src/collision/trianglemesh.hpp b/src/collision/trianglemesh.hpp index e021324..c946ad9 100644 --- a/src/collision/trianglemesh.hpp +++ b/src/collision/trianglemesh.hpp @@ -3,7 +3,6 @@ #include #include #include -#include "aabb.hpp" #include diff --git a/src/game/entity.cpp b/src/game/entity.cpp index f6d28ce..7d12c58 100644 --- a/src/game/entity.cpp +++ b/src/game/entity.cpp @@ -10,6 +10,28 @@ void game::Entity::Update() RunTasks(); } +void game::Entity::SendInitData(Player& player, net::OutMessage& msg) const +{ + WriteNametag(msg); +} + +void game::Entity::SetNametag(const std::string& nametag) +{ + nametag_ = nametag; + SendNametagMsg(); // notify viewers +} + +void game::Entity::WriteNametag(net::OutMessage& msg) const +{ + msg.Write(net::NameTag{nametag_}); +} + +void game::Entity::SendNametagMsg() +{ + auto msg = BeginEntMsg(net::EMSG_NAMETAG); + WriteNametag(msg); +} + net::OutMessage game::Entity::BeginEntMsg(net::EntMsgType type) { auto msg = BeginMsg(net::MSG_ENTMSG); diff --git a/src/game/entity.hpp b/src/game/entity.hpp index cc93e69..3836460 100644 --- a/src/game/entity.hpp +++ b/src/game/entity.hpp @@ -23,13 +23,20 @@ public: net::EntType GetViewType() const { return viewtype_; } virtual void Update(); - virtual void SendInitData(Player& player, net::OutMessage& msg) const {} + virtual void SendInitData(Player& player, net::OutMessage& msg) const; + + void SetNametag(const std::string& nametag); void Remove() { removed_ = true; } bool IsRemoved() const { return removed_; } virtual ~Entity() = default; +private: + void WriteNametag(net::OutMessage& msg) const; + + void SendNametagMsg(); + protected: net::OutMessage BeginEntMsg(net::EntMsgType type); @@ -39,8 +46,10 @@ protected: const net::EntType viewtype_; TransformNode root_; + std::string nametag_; bool removed_ = false; + }; } \ No newline at end of file diff --git a/src/game/openworld.cpp b/src/game/openworld.cpp index fb5edc8..91ae68b 100644 --- a/src/game/openworld.cpp +++ b/src/game/openworld.cpp @@ -94,7 +94,7 @@ game::OpenWorld::OpenWorld() : World("openworld") // } // spawn bots - for (size_t i = 0; i < 20; ++i) + for (size_t i = 0; i < 30; ++i) { SpawnBot(); } @@ -221,25 +221,27 @@ struct BotThinkState { game::Vehicle& vehicle; const assets::MapGraph& roads; - size_t node = 0; + glm::vec3 seg_start; + std::deque path; bool gas = false; size_t stuck_counter = 0; glm::vec3 last_pos = glm::vec3(0.0f); + float speed_limit = 0.0f; + const char* state_str = "init"; BotThinkState(game::Vehicle& v, const assets::MapGraph& g, size_t n) - : vehicle(v), roads(g), node(n) {} + : vehicle(v), roads(g), path({n}) + { + seg_start = v.GetPosition(); + } }; - -static float GetSteeringAngle(const glm::vec3& pos, const glm::quat& rot, const glm::vec3& target) +static float GetTurnAngle2D(const glm::vec2& forward, const glm::vec2& to_target) { - glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f}; - glm::vec2 forward_xy = glm::normalize(glm::vec2{forward.x, forward.y}); - glm::vec3 to_target = target - pos; - glm::vec2 to_target_xy = glm::normalize(glm::vec2{to_target.x, to_target.y}); + glm::vec2 forward_xy = glm::normalize(forward); + glm::vec2 to_target_xy = glm::normalize(to_target); float dot = glm::dot(forward_xy, to_target_xy); float cross = forward_xy.x * to_target_xy.y - forward_xy.y * to_target_xy.x; - float angle = acosf(glm::clamp(dot, -1.0f, 1.0f)); // in [0, pi] if (cross < 0) @@ -248,22 +250,130 @@ static float GetSteeringAngle(const glm::vec3& pos, const glm::quat& rot, const return angle; // in [-pi, pi] } +static float GetTurnAngle(const glm::vec3& pos, const glm::quat& rot, const glm::vec3& target) +{ + glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f}; + glm::vec3 to_target = target - pos; + glm::vec2 forward_xy = glm::vec2{forward.x, forward.y}; + glm::vec2 to_target_xy = glm::vec2{to_target.x, to_target.y}; + return GetTurnAngle2D(forward_xy, to_target_xy); +} + +static void SelectNextNode(BotThinkState& s) +{ + size_t node = s.path.back(); + s.path.push_back(s.roads.nbs[s.roads.nodes[node].nbs + (rand() % s.roads.nodes[node].num_nbs)]); +} + static void BotThink(std::shared_ptr s) { + s->state_str = "path"; + glm::vec3 pos = s->vehicle.GetPosition(); glm::quat rot = s->vehicle.GetRotation(); + glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f}; - glm::vec3 target = s->roads.nodes[s->node].position; - float angle = GetSteeringAngle(pos, rot, target); + //glm::vec3 target = s->roads.nodes[s->node].position; + // + std::array waypoints; + + while (s->path.size() < waypoints.size() - 1) + { + SelectNextNode(*s); + } + + glm::vec3 node_pos = s->roads.nodes[s->path[0]].position; + if (glm::distance(glm::vec2(pos), glm::vec2(node_pos)) < 6.0f && s->path.size() > 1) + { + s->seg_start = node_pos; + s->path.pop_front(); + SelectNextNode(*s); + } + + glm::vec3 target_node_pos = s->roads.nodes[s->path[0]].position; + + waypoints[0] = pos - glm::normalize(forward) * 3.0f; + waypoints[1] = pos; + + // find closest point on segment [seg_start -> target_node_pos] + glm::vec3 seg_end = target_node_pos; + glm::vec3 seg_dir = seg_end - s->seg_start; + float seg_len = glm::length(seg_dir); + if (seg_len > 5.0f) + { + glm::vec3 seg_dir_norm = seg_dir / seg_len; + float t = glm::clamp(glm::dot(pos - s->seg_start, seg_dir_norm) / seg_len, 0.0f, 1.0f); + waypoints[2] = s->seg_start + t * seg_dir; + if (glm::distance(waypoints[1], target_node_pos) > 10.0f) + { + waypoints[2] += seg_dir_norm * 10.0f; // look a bit ahead on segment + } + else + { + waypoints[2] = target_node_pos; + } + + } + else + { + waypoints[2] = target_node_pos; + } + + for (size_t i = 3; i < waypoints.size(); ++i) + { + size_t path_idx = glm::min(i - 3, s->path.size() - 1); + waypoints[i] = s->roads.nodes[s->path[path_idx]].position; + } + + // decrease speed based on curvature + const float base_speed = 100.0f; + float target_speed = base_speed; + float dist_accum = 0.0f; + for (size_t i = 1; i < waypoints.size() - 1; ++i) + { + glm::vec3 dir1 = waypoints[i] - waypoints[i - 1]; + glm::vec3 dir2 = waypoints[i + 1] - waypoints[i]; + float dist = glm::length(dir1); + dist_accum += dist; + + glm::vec2 dir1_xy = glm::vec2{dir1.x, dir1.y}; + glm::vec2 dir2_xy = glm::vec2{dir2.x, dir2.y}; + + const float min_dir_length = 0.001f; + float angle = glm::length(dir1_xy) > min_dir_length && glm::length(dir2_xy) > min_dir_length ? GetTurnAngle2D(dir1_xy, dir2_xy) : 0.0f; + // std::cout << "angle: " << angle << "\n"; + float abs_angle = fabsf(angle); + if (abs_angle > glm::radians(7.0f)) + { + // float speed_limit = 50.0f / abs_angle; // sharper turn -> lower speed + // speed_limit *= dist_accum / 20.0f; // more distance to turn -> higher speed + // speed_limit = glm::max(speed_limit, 20.0f); + // max_speed = glm::min(max_speed, speed_limit); + target_speed -= abs_angle * (base_speed / glm::pi() / 2.0f) * 50.0f / glm::max(dist_accum - 1.0f, 1.0f); + + } + + if (dist_accum > 200.0f) + break; + } + + target_speed = glm::clamp(target_speed, 25.0f, 100.0f); + s->speed_limit = target_speed; + + // std::cout << "target speed: " << target_speed << "\n"; + + float angle = GetTurnAngle(pos, rot, waypoints[2]); + if (glm::distance(pos, s->last_pos) < 2.0f) { s->stuck_counter++; if (s->stuck_counter > 20) { + s->state_str = "stuck (reverse)"; s->stuck_counter = 0; s->vehicle.SetSteering(true, -angle); // try turn away - + s->vehicle.SetInputs(0); // stop // stuck, go reverse for a while s->vehicle.SetInput(game::VIN_BACKWARD, true); @@ -279,18 +389,17 @@ static void BotThink(std::shared_ptr s) s->stuck_counter = 0; s->last_pos = pos; } - + s->vehicle.SetSteering(true, angle); game::VehicleInputFlags vin = 0; float speed = s->vehicle.GetSpeed(); - float target_speed = 50.0f; - if (glm::distance(pos, target) < 10.0f) - { - target_speed = 20.0f; - } + // if (glm::distance(pos, target) < 10.0f) + // { + // target_speed = 20.0f; + // } if (speed < target_speed * 0.9f && !s->gas) { @@ -313,31 +422,50 @@ static void BotThink(std::shared_ptr s) s->vehicle.SetInputs(vin); - float dist_to_node = glm::distance(pos, s->roads.nodes[s->node].position); - if (dist_to_node < 5.0f) - { - // advance to next node - const auto& graph = s->roads; - const auto& current_node = graph.nodes[s->node]; - - if (current_node.num_nbs > 0) - { - size_t next_idx = rand() % current_node.num_nbs; - s->node = s->roads.nbs[current_node.nbs + next_idx]; - } - } - s->vehicle.Schedule(rand() % 120 + 40, [s]() { BotThink(s); } ); } +static void BotNametagThink(std::shared_ptr s) +{ + std::string nametag; + nametag += "s=" + std::to_string(static_cast(s->vehicle.GetSpeed())) + "/" + std::to_string(static_cast(s->speed_limit)) + " "; + nametag += "sc=" + std::to_string(s->stuck_counter) + " "; + nametag += "st=" + std::string(s->state_str); // + " "; + + // nametag += "path="; + // for (auto n : path) + // { + // nametag += std::to_string(n) + " "; + // } + s->vehicle.SetNametag(nametag); + + s->vehicle.Schedule(240, [s]() { + BotNametagThink(s); + } ); +} + static const char* GetRandomCarModel() { const char* vehicles[] = {"pickup_hd", "passat", "twingo", "polskifiat"}; return vehicles[rand() % (sizeof(vehicles) / sizeof(vehicles[0]))]; } +static glm::vec3 GetRandomColor() +{ + glm::vec3 color; + // shittiest way to do it + for (int i = 0; i < 3; ++i) + { + net::ColorQ qcol; + qcol.value = rand() % 256; + color[i] = qcol.Decode(); + } + + return color; +} + void game::OpenWorld::SpawnBot() { auto roads = GetMap()->GetGraph("roads"); @@ -349,11 +477,17 @@ void game::OpenWorld::SpawnBot() } size_t start_node = rand() % roads->nodes.size(); - auto& vehicle = Spawn(GetRandomCarModel(), glm::vec3{0.3f, 0.3f, 0.3f}); + // auto color = glm::vec3{0.3f, 0.3f, 0.3f}; + auto color = GetRandomColor(); + auto& vehicle = Spawn(GetRandomCarModel(), color); + vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")"); vehicle.SetPosition(roads->nodes[start_node].position + glm::vec3{0.0f, 0.0f, 5.0f}); auto think_state = std::make_shared(vehicle, *roads, start_node); BotThink(think_state); + vehicle.Schedule(rand() % 500, [think_state]() { + BotNametagThink(think_state); + } ); } void game::OpenWorld::SpawnVehicle(Player& player) @@ -362,16 +496,11 @@ void game::OpenWorld::SpawnVehicle(Player& player) // spawn him car // 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_name = GetRandomCarModel(); - auto& vehicle = Spawn(vehicle_name, color); + auto& vehicle = Spawn(vehicle_name, GetRandomColor()); + vehicle.SetNametag("player (" + std::to_string(vehicle.GetEntNum()) + ")"); vehicle.SetPosition({ 100.0f, 100.0f, 5.0f }); player.SetCamera(vehicle.GetEntNum()); diff --git a/src/game/vehicle.cpp b/src/game/vehicle.cpp index cf57233..c7b76ca 100644 --- a/src/game/vehicle.cpp +++ b/src/game/vehicle.cpp @@ -108,6 +108,8 @@ void game::Vehicle::Update() void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const { + Super::SendInitData(player, msg); + net::ModelName name(model_name_); msg.Write(name); net::WriteRGB(msg, color_); // primary color diff --git a/src/gameview/draw_args.hpp b/src/gameview/draw_args.hpp new file mode 100644 index 0000000..0a5f770 --- /dev/null +++ b/src/gameview/draw_args.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "gfx/draw_list.hpp" +#include "gfx/frustum.hpp" + +namespace game::view +{ +struct DrawArgs +{ + gfx::DrawList& dlist; + const glm::mat4& view_proj; + gfx::Frustum frustum; + glm::ivec2 screen_size; + + DrawArgs(gfx::DrawList& dlist, const glm::mat4& view_proj, glm::ivec2 screen_size) + : dlist(dlist), view_proj(view_proj), frustum(view_proj), screen_size(screen_size) + { + } +}; +} // namespace game::view \ No newline at end of file diff --git a/src/gameview/entityview.cpp b/src/gameview/entityview.cpp index b091b46..7027619 100644 --- a/src/gameview/entityview.cpp +++ b/src/gameview/entityview.cpp @@ -1,11 +1,27 @@ #include "entityview.hpp" -#include "worldview.hpp" +#include -game::view::EntityView::EntityView(WorldView& world) : world_(world), audioplayer_(world_.GetAudioMaster()) {} +#include "worldview.hpp" +#include "assets/cache.hpp" + +game::view::EntityView::EntityView(WorldView& world, net::InMessage& msg) : + world_(world), + audioplayer_(world_.GetAudioMaster()), + nametag_text_(assets::CacheManager::GetFont("data/comic32.font"), 0xFFFFFFFF) +{ + if (!ReadNametag(msg)) + throw EntityInitError(); +} bool game::view::EntityView::ProcessMsg(net::EntMsgType type, net::InMessage& msg) { + switch (type) + { + case net::EMSG_NAMETAG: + return ReadNametag(msg); + } + return false; } @@ -14,4 +30,51 @@ void game::view::EntityView::Update(const UpdateInfo& info) audioplayer_.Update(); } -void game::view::EntityView::Draw(gfx::DrawList& dlist) {} +void game::view::EntityView::Draw(const DrawArgs& args) +{ + // std::cout << "TODO draw entity nametag: " << nametag_ << std::endl; + + if (nametag_.empty()) + return; + + // calc screen position + glm::vec4 world_pos = glm::vec4(root_.local.position + glm::vec3(0.0f, 0.0f, 2.0f), 1.0f); + glm::vec4 clip_pos = args.view_proj * world_pos; + if (clip_pos.w == 0.0f) + return; + + glm::vec3 ndc_pos = glm::vec3(clip_pos) / clip_pos.w; + + if (ndc_pos.z < -1.0f || ndc_pos.z > 1.0f) + return; // behind camera + + nametag_pos_.anchor.x = ndc_pos.x * 0.5f + 0.5f; + nametag_pos_.anchor.y = 1.0f - (ndc_pos.y * 0.5f + 0.5f); // flip y + + // center + float scale = 0.7f; + nametag_pos_.scale = glm::vec2(scale, -scale); + nametag_pos_.pos = nametag_text_.GetSize() * glm::vec2(-0.5f, -1.0f) * scale; + + // static const glm::vec4 nametag_color = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + + gfx::DrawHudCmd hudcmd; + hudcmd.va = &nametag_text_.GetVA(); + hudcmd.texture = nametag_text_.GetFont()->GetTexture().get(); + hudcmd.pos = &nametag_pos_; + // hudcmd.color = &nametag_color; + args.dlist.AddHUD(hudcmd); + // std::cout << "at position: " << root_.local.position.x << ", " << root_.local.position.y << ", " << root_.local.position.z << std::endl; + +} + +bool game::view::EntityView::ReadNametag(net::InMessage& msg) +{ + // read nametag + net::NameTag nametag; + if (!msg.Read(nametag)) + return false; + + nametag_ = nametag; + nametag_text_.SetText(nametag); +} diff --git a/src/gameview/entityview.hpp b/src/gameview/entityview.hpp index 5794d4a..8680482 100644 --- a/src/gameview/entityview.hpp +++ b/src/gameview/entityview.hpp @@ -2,9 +2,10 @@ #include -#include "game/transform_node.hpp" -#include "gfx/draw_list.hpp" #include "audio/player.hpp" +#include "draw_args.hpp" +#include "game/transform_node.hpp" +#include "gfx/text.hpp" #include "net/defs.hpp" #include "net/inmessage.hpp" @@ -14,6 +15,12 @@ namespace game::view { +class EntityInitError : public std::runtime_error +{ +public: + explicit EntityInitError() : std::runtime_error("Could not initialize entity from message") {} +}; + class WorldView; struct UpdateInfo @@ -25,24 +32,33 @@ struct UpdateInfo class EntityView { public: - EntityView(WorldView& world); + EntityView(WorldView& world, net::InMessage& msg); DELETE_COPY_MOVE(EntityView) virtual bool ProcessMsg(net::EntMsgType type, net::InMessage& msg); virtual void Update(const UpdateInfo& info); - virtual void Draw(gfx::DrawList& dlist); + virtual void Draw(const DrawArgs& args); + + Sphere GetBoundingSphere() const { return Sphere{root_.local.position, radius_}; } const TransformNode& GetRoot() const { return root_; } virtual ~EntityView() = default; +private: + bool ReadNametag(net::InMessage& msg); + protected: WorldView& world_; - + TransformNode root_; - bool visible_ = false; - + float radius_ = 1.0f; + audio::Player audioplayer_; + + std::string nametag_; + gfx::Text nametag_text_; + gfx::HudPosition nametag_pos_; }; } // namespace game::view \ No newline at end of file diff --git a/src/gameview/vehicleview.cpp b/src/gameview/vehicleview.cpp index 2f4f41f..c0543e6 100644 --- a/src/gameview/vehicleview.cpp +++ b/src/gameview/vehicleview.cpp @@ -6,9 +6,19 @@ #include -game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr model, const glm::vec3& color) - : EntityView(world), model_(std::move(model)) +game::view::VehicleView::VehicleView(WorldView& world, net::InMessage& msg) + : EntityView(world, msg) { + net::ModelName modelname; + glm::vec3 color; + if (!msg.Read(modelname) || !net::ReadRGB(msg, color)) + throw EntityInitError(); + + model_ = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh"); + + // init the other transform to identical + root_trans_[0] = root_trans_[1]; + auto& modelwheels = model_->GetWheels(); wheels_.resize(modelwheels.size()); @@ -19,30 +29,17 @@ game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr game::view::VehicleView::InitFromMsg(WorldView& world, net::InMessage& msg) -{ - net::ModelName modelname; - glm::vec3 color; - if (!msg.Read(modelname) || !net::ReadRGB(msg, color)) - return nullptr; - - auto model = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh"); - - auto vehicle = std::make_unique(world, std::move(model), color); - if (!vehicle->ReadState(msg)) - return nullptr; - - vehicle->root_trans_[0] = vehicle->root_trans_[1]; - - return vehicle; + radius_ = 3.0f; } bool game::view::VehicleView::ProcessMsg(net::EntMsgType type, net::InMessage& msg) @@ -53,7 +50,7 @@ bool game::view::VehicleView::ProcessMsg(net::EntMsgType type, net::InMessage& m return ProcessUpdateMsg(msg); default: - return false; + return Super::ProcessMsg(type, msg); } } @@ -106,8 +103,10 @@ void game::view::VehicleView::Update(const UpdateInfo& info) } } -void game::view::VehicleView::Draw(gfx::DrawList& dlist) +void game::view::VehicleView::Draw(const DrawArgs& args) { + Super::Draw(args); + // TOOD: chceck and fix const auto& model = *model_->GetModel(); const auto& mesh = *model.GetMesh(); @@ -118,7 +117,7 @@ void game::view::VehicleView::Draw(gfx::DrawList& dlist) cmd.surface = &surface; cmd.matrices = &root_.matrix; cmd.color = &color_; - dlist.AddSurface(cmd); + args.dlist.AddSurface(cmd); } const auto& wheels = model_->GetWheels(); @@ -131,7 +130,7 @@ void game::view::VehicleView::Draw(gfx::DrawList& dlist) gfx::DrawSurfaceCmd cmd; cmd.surface = &surface; cmd.matrices = &wheels_[i].node.matrix; - dlist.AddSurface(cmd); + args.dlist.AddSurface(cmd); } } } diff --git a/src/gameview/vehicleview.hpp b/src/gameview/vehicleview.hpp index e64b93f..d1f4723 100644 --- a/src/gameview/vehicleview.hpp +++ b/src/gameview/vehicleview.hpp @@ -23,12 +23,12 @@ class VehicleView : public EntityView { using Super = EntityView; public: - VehicleView(WorldView& world, std::shared_ptr model, const glm::vec3& color); - static std::unique_ptr InitFromMsg(WorldView& world, net::InMessage& msg); + VehicleView(WorldView& world, net::InMessage& msg); + DELETE_COPY_MOVE(VehicleView) virtual bool ProcessMsg(net::EntMsgType type, net::InMessage& msg) override; virtual void Update(const UpdateInfo& info) override; - virtual void Draw(gfx::DrawList& dlist) override; + virtual void Draw(const DrawArgs& args) override; private: bool ReadState(net::InMessage& msg); diff --git a/src/gameview/worldview.cpp b/src/gameview/worldview.cpp index c9e1569..a6640d1 100644 --- a/src/gameview/worldview.cpp +++ b/src/gameview/worldview.cpp @@ -40,14 +40,15 @@ void game::view::WorldView::Update(const UpdateInfo& info) } } -void game::view::WorldView::Draw(gfx::DrawList& dlist) const +void game::view::WorldView::Draw(const DrawArgs& args) const { if (map_) - map_->Draw(dlist); + map_->Draw(args); for (const auto& [entnum, ent] : ents_) { - ent->Draw(dlist); + if (args.frustum.IsSphereVisible(ent->GetBoundingSphere())) + ent->Draw(args); } } @@ -72,23 +73,25 @@ bool game::view::WorldView::ProcessEntSpawnMsg(net::InMessage& msg) if (entslot) entslot.reset(); - switch (type) + try { - case net::ET_VEHICLE: - entslot = VehicleView::InitFromMsg(*this, msg); - break; + switch (type) + { + case net::ET_VEHICLE: + entslot = std::make_unique(*this, msg); + break; - default: - return false; - } + default: + return false; // unknown type + } - if (!entslot) // init failed + return true; + + } catch (const EntityInitError& e) // failed { ents_.erase(entnum); return false; } - - return true; } bool game::view::WorldView::ProcessEntMsgMsg(net::InMessage& msg) diff --git a/src/gameview/worldview.hpp b/src/gameview/worldview.hpp index 84dbd56..4fbfa0f 100644 --- a/src/gameview/worldview.hpp +++ b/src/gameview/worldview.hpp @@ -1,7 +1,7 @@ #pragma once #include "assets/map.hpp" -#include "gfx/draw_list.hpp" +#include "draw_args.hpp" #include "net/defs.hpp" #include "net/inmessage.hpp" @@ -20,7 +20,7 @@ public: bool ProcessMsg(net::MessageType type, net::InMessage& msg); void Update(const UpdateInfo& info); - void Draw(gfx::DrawList& dlist) const; + void Draw(const DrawArgs& args) const; EntityView* GetEntity(net::EntNum entnum); diff --git a/src/gfx/buffer_object.cpp b/src/gfx/buffer_object.cpp index 0964b96..9ae528d 100644 --- a/src/gfx/buffer_object.cpp +++ b/src/gfx/buffer_object.cpp @@ -22,7 +22,7 @@ void gfx::BufferObject::SetData(const void* data, size_t size) { if (size > m_size) glBufferData(m_target, size, data, m_usage); - else + else if (size > 0) glBufferSubData(m_target, 0, size, data); m_size = size; diff --git a/src/gfx/frustum.cpp b/src/gfx/frustum.cpp new file mode 100644 index 0000000..403d799 --- /dev/null +++ b/src/gfx/frustum.cpp @@ -0,0 +1,79 @@ +#include "frustum.hpp" + +gfx::Frustum::Frustum(const glm::mat4& vp) +{ + for (int i = 0; i < 4; ++i) + { + planes_[0][i] = vp[i][3] + vp[i][0]; + planes_[1][i] = vp[i][3] - vp[i][0]; + planes_[2][i] = vp[i][3] + vp[i][1]; + planes_[3][i] = vp[i][3] - vp[i][1]; + planes_[4][i] = vp[i][3] + vp[i][2]; + planes_[5][i] = vp[i][3] - vp[i][2]; + } + + // normalize the planes + for (int i = 0; i < 6; i++) + { + auto len = glm::length(glm::vec3(planes_[i])); + planes_[i] /= len; + } + + //glm::mat4 inv_vp = glm::inverse(m_vp); + + //m_min = glm::vec3(FLT_MAX); + //m_max = glm::vec3(-FLT_MAX); + + //for (int x = 0; x < 2; ++x) + //{ + // for (int y = 0; y < 2; ++y) + // { + // for (int z = 0; z < 2; ++z) + // { + // glm::vec4 pt = inv_vp * glm::vec4(2.0f * x - 1.0f, 2.0f * y - 1.0f, 2.0f * z - 1.0f, 1.0f); + + // pt /= pt.w; + + // m_min = glm::min(m_min, glm::vec3(pt)); + // m_max = glm::max(m_max, glm::vec3(pt)); + // } + // } + //} + +} + +bool gfx::Frustum::IsAABBVisible(const AABB3& aabb) const +{ + glm::vec3 extents = (aabb.max - aabb.min) * 0.5f; + glm::vec3 center = aabb.min + extents; + + for (int i = 0; i < 6; i++) + { + const auto& plane = planes_[i]; + const glm::vec3 normal(plane); + + const float r = glm::dot(extents, glm::abs(normal)); + const float d = glm::dot(normal, center) + plane.w; + + if (d + r < 0.0f) + return false; + } + + return true; +} + +bool gfx::Frustum::IsSphereVisible(const Sphere& sphere) const +{ + for (int i = 0; i < 6; ++i) + { + const glm::vec4 plane = planes_[i]; + const glm::vec3 normal(plane); + + const float distance = glm::dot(normal, sphere.center) + plane.w; + + if (distance < -sphere.radius) + return false; + } + + return true; +} diff --git a/src/gfx/frustum.hpp b/src/gfx/frustum.hpp new file mode 100644 index 0000000..2f67fa2 --- /dev/null +++ b/src/gfx/frustum.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include "utils/aabb.hpp" +#include "utils/sphere.hpp" + +namespace gfx +{ + +class Frustum +{ +public: + Frustum(const glm::mat4& vp); + + bool IsAABBVisible(const AABB3& aabb) const; + bool IsSphereVisible(const Sphere& sphere) const; + +private: + glm::vec4 planes_[6]; +}; + +} // namespace gfx diff --git a/src/gfx/text.cpp b/src/gfx/text.cpp index d70fd19..a94aae6 100644 --- a/src/gfx/text.cpp +++ b/src/gfx/text.cpp @@ -78,13 +78,16 @@ void gfx::Text::SetText(const char* text) float space_size = font_->GetLineHeight() * 0.3f; - glm::vec2 cursor(0.0f, 0.0f); + const float line_height = font_->GetLineHeight(); + size_ = glm::vec2(0.0f); + glm::vec2 cursor(0.0f); uint32_t cp = 0; const char* p = text; uint32_t color = color_; + while (cp = DecodeUTF8Codepoint(p)) { if (cp == ' ') @@ -92,6 +95,12 @@ void gfx::Text::SetText(const char* text) cursor.x += space_size; // Move cursor for space continue; } + else if (cp == '\n') + { + cursor.x = 0.0f; + cursor.y += line_height; + continue; + } else if (cp == '^') { if (!(cp = DecodeUTF8Codepoint(p))) @@ -155,7 +164,11 @@ void gfx::Text::SetText(const char* text) indices.push_back(base_index + 3); cursor.x += glyph->advance; + + size_.x = glm::max(size_.x, cursor.x); } + + size_.y = cursor.y + line_height; 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 index 9162303..4f120fd 100644 --- a/src/gfx/text.hpp +++ b/src/gfx/text.hpp @@ -16,11 +16,13 @@ public: const std::shared_ptr& GetFont() const { return font_; } const VertexArray& GetVA() const { return va_; } + const glm::vec2& GetSize() const { return size_; } private: std::shared_ptr font_; VertexArray va_; uint32_t color_; + glm::vec2 size_; // size of the text in pixels }; } // namespace gfx \ No newline at end of file diff --git a/src/net/defs.hpp b/src/net/defs.hpp index 93df648..ca5ab58 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -71,6 +71,7 @@ enum EntMsgType : uint8_t { EMSG_NONE, + EMSG_NAMETAG, EMSG_UPDATE, }; @@ -93,4 +94,6 @@ using RotationSpeedQ = Quantized; using ColorQ = Quantized; +using NameTag = FixedStr<64>; + } // namespace net \ No newline at end of file diff --git a/src/server/server.cpp b/src/server/server.cpp index 53c53a1..6f70c9a 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -35,7 +35,7 @@ void sv::Server::Run() { time_ += 40; Update(); - std::cout << "Time: " << time_ << " ms, Clients: " << clients_.size() << std::endl; + // std::cout << "Time: " << time_ << " ms, Clients: " << clients_.size() << std::endl; t_next += 40ms; t_now = std::chrono::steady_clock::now(); } diff --git a/src/utils/aabb.hpp b/src/utils/aabb.hpp new file mode 100644 index 0000000..fff5086 --- /dev/null +++ b/src/utils/aabb.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +template +struct AABB +{ + static constexpr float C_INFINITY = std::numeric_limits::infinity(); + + using Vec = glm::vec; + + Vec min = Vec(C_INFINITY); + Vec max = Vec(-C_INFINITY); + + AABB() = default; + AABB(const Vec& min, const Vec& max) : min(min), max(max) {} + + void AddPoint(const Vec& point) + { + min = glm::min(min, point); + max = glm::max(max, point); + } + + bool CollidesWith(const AABB& other) const; + + AABB Intersection(const AABB& other) const + { + Vec new_min = glm::max(min, other.min); + Vec new_max = glm::min(max, other.max); + return AABB(new_min, new_max); + } +}; + +template <> +inline bool AABB<2>::CollidesWith(const AABB<2>& other) const +{ + return (min.x <= other.max.x && max.x >= other.min.x) && (min.y <= other.max.y && max.y >= other.min.y); +} + +template <> +inline bool AABB<3>::CollidesWith(const AABB<3>& other) const +{ + return (min.x <= other.max.x && max.x >= other.min.x) && (min.y <= other.max.y && max.y >= other.min.y) && + (min.z <= other.max.z && max.z >= other.min.z); +} + +using AABB2 = AABB<2>; +using AABB3 = AABB<3>; diff --git a/src/utils/sphere.hpp b/src/utils/sphere.hpp new file mode 100644 index 0000000..d0a51cb --- /dev/null +++ b/src/utils/sphere.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct Sphere +{ + glm::vec3 center; + float radius; +};