Big amount of stuff but especially map partitioning, better bot AI and nametags

This commit is contained in:
tovjemam 2026-01-31 17:57:26 +01:00
parent 7d490d20f3
commit 4b04a77e7e
28 changed files with 658 additions and 180 deletions

View File

@ -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"

View File

@ -27,6 +27,8 @@ std::shared_ptr<const assets::Map> 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<const assets::Map> 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<const assets::Map> 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<const assets::Map> 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
#endif // CLIENT

View File

@ -1,27 +1,45 @@
#pragma once
#include <map>
#include <memory>
#include <string>
#include <map>
#include <vector>
#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<const Model> 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<ChunkSurfaceRange> surfaces;
std::vector<ChunkStaticObject> objs;
};
struct MapGraphNode
{
glm::vec3 position = glm::vec3(0.0f);
@ -42,14 +60,17 @@ public:
static std::shared_ptr<const Map> LoadFromFile(const std::string& filename);
const std::shared_ptr<const Model>& GetBaseModel() const { return basemodel_; }
const std::vector<MapStaticObject>& GetStaticObjects() const { return static_objects_; }
const std::vector<Chunk>& 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<const Model> basemodel_;
std::vector<MapStaticObject> static_objects_;
std::vector<Chunk> chunks_;
std::map<std::string, MapGraph> graphs_;
};

View File

@ -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);
}

View File

@ -1,54 +0,0 @@
#pragma once
#include <glm/glm.hpp>
#include <limits>
namespace collision
{
template <size_t dim>
struct AABB
{
static constexpr float C_INFINITY = std::numeric_limits<float>::infinity();
using Vec = glm::vec<dim, float, glm::defaultp>;
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<dim>& other) const;
AABB<dim> Intersection(const AABB<dim>& other) const
{
Vec new_min = glm::max(min, other.min);
Vec new_max = glm::min(max, other.max);
return AABB<dim>(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>;
}

View File

@ -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)

View File

@ -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()

View File

@ -3,7 +3,6 @@
#include <vector>
#include <span>
#include <glm/glm.hpp>
#include "aabb.hpp"
#include <memory>

View File

@ -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);

View File

@ -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;
};
}

View File

@ -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<size_t> 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<BotThinkState> 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<glm::vec3, 8> 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<float>() / 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<BotThinkState> 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<BotThinkState> 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<BotThinkState> s)
{
std::string nametag;
nametag += "s=" + std::to_string(static_cast<int>(s->vehicle.GetSpeed())) + "/" + std::to_string(static_cast<int>(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<Vehicle>(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<Vehicle>(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<BotThinkState>(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>(vehicle_name, color);
auto& vehicle = Spawn<Vehicle>(vehicle_name, GetRandomColor());
vehicle.SetNametag("player (" + std::to_string(vehicle.GetEntNum()) + ")");
vehicle.SetPosition({ 100.0f, 100.0f, 5.0f });
player.SetCamera(vehicle.GetEntNum());

View File

@ -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

View File

@ -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

View File

@ -1,11 +1,27 @@
#include "entityview.hpp"
#include "worldview.hpp"
#include <iostream>
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);
}

View File

@ -2,9 +2,10 @@
#include <stdexcept>
#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

View File

@ -6,9 +6,19 @@
#include <iostream>
game::view::VehicleView::VehicleView(WorldView& world, std::shared_ptr<const assets::VehicleModel> 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<const ass
color_ = glm::vec4(color, 1.0f);
if (!ReadState(msg))
throw EntityInitError();
snd_accel_ = assets::CacheManager::GetSound("data/auto.snd");
// sync state
net::DecodePosition(sync_.pos, root_.local.position);
net::DecodeRotation(sync_.rot, root_.local.rotation);
}
std::unique_ptr<game::view::VehicleView> 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<VehicleView>(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);
}
}
}

View File

@ -23,12 +23,12 @@ class VehicleView : public EntityView
{
using Super = EntityView;
public:
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);
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);

View File

@ -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<VehicleView>(*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)

View File

@ -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);

View File

@ -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;

79
src/gfx/frustum.cpp Normal file
View File

@ -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;
}

22
src/gfx/frustum.hpp Normal file
View File

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

View File

@ -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());

View File

@ -16,11 +16,13 @@ public:
const std::shared_ptr<const Font>& GetFont() const { return font_; }
const VertexArray& GetVA() const { return va_; }
const glm::vec2& GetSize() const { return size_; }
private:
std::shared_ptr<const Font> font_;
VertexArray va_;
uint32_t color_;
glm::vec2 size_; // size of the text in pixels
};
} // namespace gfx

View File

@ -71,6 +71,7 @@ enum EntMsgType : uint8_t
{
EMSG_NONE,
EMSG_NAMETAG,
EMSG_UPDATE,
};
@ -93,4 +94,6 @@ using RotationSpeedQ = Quantized<uint16_t, -300, 300, 1>;
using ColorQ = Quantized<uint8_t, 0, 1>;
using NameTag = FixedStr<64>;
} // namespace net

View File

@ -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();
}

49
src/utils/aabb.hpp Normal file
View File

@ -0,0 +1,49 @@
#pragma once
#include <glm/glm.hpp>
#include <limits>
template <size_t dim>
struct AABB
{
static constexpr float C_INFINITY = std::numeric_limits<float>::infinity();
using Vec = glm::vec<dim, float, glm::defaultp>;
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<dim>& other) const;
AABB<dim> Intersection(const AABB<dim>& other) const
{
Vec new_min = glm::max(min, other.min);
Vec new_max = glm::min(max, other.max);
return AABB<dim>(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>;

9
src/utils/sphere.hpp Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include <glm/glm.hpp>
struct Sphere
{
glm::vec3 center;
float radius;
};