Destructible objs pt. 1

This commit is contained in:
tovjemam 2026-02-27 18:32:20 +01:00
parent 0e7e80ee79
commit d6947a79d6
22 changed files with 573 additions and 179 deletions

View File

@ -77,6 +77,8 @@ set(CLIENT_ONLY_SOURCES
"src/gameview/client_session.cpp"
"src/gameview/entityview.hpp"
"src/gameview/entityview.cpp"
"src/gameview/mapinstanceview.hpp"
"src/gameview/mapinstanceview.cpp"
"src/gameview/skinning_ubo.hpp"
"src/gameview/skinning_ubo.cpp"
"src/gameview/vehicleview.hpp"
@ -120,6 +122,8 @@ set(SERVER_ONLY_SOURCES
"src/game/entity.cpp"
"src/game/game.hpp"
"src/game/game.cpp"
"src/game/mapinstance.hpp"
"src/game/mapinstance.cpp"
"src/game/npc_character.hpp"
"src/game/npc_character.cpp"
"src/game/openworld.hpp"

View File

@ -2,9 +2,6 @@
#include <algorithm>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
#include "cache.hpp"
#include "cmdfile.hpp"
#include "utils/files.hpp"
@ -65,7 +62,7 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
if (!chunk)
throw std::runtime_error("static in map without chunk");
ChunkStaticObject obj;
MapStaticObject obj;
std::string model_name;
iss >> model_name;
@ -90,7 +87,8 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
}
}
chunk->objs.push_back(std::move(obj));
map->objs_.push_back(std::move(obj));
chunk->num_objs++;
}
else if (command == "chunk")
{
@ -99,6 +97,8 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
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();
}
else if (command == "surface")
{
@ -177,63 +177,3 @@ const assets::MapGraph* assets::Map::GetGraph(const std::string& name) const
return &it->second;
return nullptr;
}
#ifdef CLIENT
void assets::Map::Draw(const game::view::DrawArgs& args) const
{
if (!basemodel_ || !basemodel_->GetMesh())
return;
const auto& mesh = *basemodel_->GetMesh();
const float max_dist = args.render_distance + 200.0f;
const float max_dist2 = max_dist * max_dist;
for (auto& chunk : chunks_)
{
glm::vec3 center = (chunk.aabb.min + chunk.aabb.max) * 0.5f;
if (glm::distance2(args.eye, center) > max_dist2)
continue;
if (!args.frustum.IsAABBVisible(chunk.aabb))
continue;
DrawChunk(args, mesh, chunk);
}
}
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)
{
gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface;
cmd.matrices = &obj.node.matrix;
// cmd.color_mod = glm::vec4(obj.color, 1.0f);
args.dlist.AddSurface(cmd);
}
}
}
#endif // CLIENT

View File

@ -9,14 +9,10 @@
#include "model.hpp"
#include "utils/aabb.hpp"
#ifdef CLIENT
#include "gameview/draw_args.hpp"
#endif // CLIENT
namespace assets
{
struct ChunkStaticObject
struct MapStaticObject
{
game::TransformNode node;
AABB3 aabb;
@ -37,7 +33,8 @@ struct Chunk
{
AABB3 aabb;
std::vector<ChunkSurfaceRange> surfaces;
std::vector<ChunkStaticObject> objs;
size_t first_obj = 0;
size_t num_objs = 0;
};
struct MapGraphNode
@ -61,16 +58,13 @@ public:
const std::shared_ptr<const Model>& GetBaseModel() const { return basemodel_; }
const std::vector<Chunk>& GetChunks() const { return chunks_; }
const std::vector<MapStaticObject>& GetStaticObjects() const { return objs_; }
const MapGraph* GetGraph(const std::string& name) 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<Chunk> chunks_;
std::vector<MapStaticObject> objs_;
std::map<std::string, MapGraph> graphs_;
};

View File

@ -12,6 +12,7 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
CLIENT_ONLY(MeshBuilder mb(gfx::MF_NONE);)
std::unique_ptr<btConvexHullShape> temp_hull;
std::unique_ptr<btCompoundShape> compound;
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
if (command == "v")
@ -129,6 +130,33 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
model->skeleton_ = CacheManager::GetSkeleton("data/" + skel_name + ".sk");
CLIENT_ONLY(mb.SetMeshFlag(gfx::MF_SKELETAL));
}
else if (command == "col")
{
std::string shape_type;
Transform trans;
float sy, sz;
iss >> shape_type;
ParseTransform(iss, trans);
iss >> sy >> sz;
glm::vec3 scale(trans.scale, sy, sz);
trans.scale = 1.0f;
if (!compound)
{
compound = std::make_unique<btCompoundShape>();
}
if (shape_type == "box")
{
auto box_shape = std::make_unique<btBoxShape>(btVector3(scale.x, scale.y, scale.z));
compound->addChildShape(trans.ToBtTransform(), box_shape.get());
model->subshapes_.push_back(std::move(box_shape));
}
else
{
throw std::runtime_error("Unknown collision shape type: " + shape_type);
}
}
else
{
throw std::runtime_error("Unknown command in model file: " + command);
@ -157,6 +185,10 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
model->cshape_ = std::make_unique<btConvexHullShape>((btScalar*)shape_hull->getVertexPointer(), shape_hull->numVertices(), sizeof(btVector3));
}
else
{
model->cshape_ = std::move(compound);
}
return model;
}

View File

@ -51,6 +51,7 @@ public:
private:
std::unique_ptr<collision::TriangleMesh> cmesh_;
// std::vector<ModelCollisionShape> cshapes_;
std::vector<std::unique_ptr<btCollisionShape>> subshapes_;
std::unique_ptr<btCollisionShape> cshape_;
std::shared_ptr<const Skeleton> skeleton_;

View File

@ -10,47 +10,3 @@ collision::DynamicsWorld::DynamicsWorld()
bt_broadphase_.getOverlappingPairCache()->setInternalGhostPairCallback(&bt_ghost_pair_cb_);
}
void collision::DynamicsWorld::AddMapCollision(std::shared_ptr<const assets::Map> map)
{
if (!map)
return;
map_ = std::move(map);
// add basemodel
const auto& basemodel = map_->GetBaseModel();
if (basemodel)
{
Transform identity;
AddModelInstance(*basemodel, identity);
}
// add static objects
for (const auto& chunks = map_->GetChunks(); const auto& chunk : chunks)
{
for (const auto& obj : chunk.objs)
{
AddModelInstance(*obj.model, obj.node.local);
}
}
}
void collision::DynamicsWorld::AddModelInstance(const assets::Model& model, const Transform& trans)
{
if (auto cmesh = model.GetColMesh(); cmesh)
{
// create trimesh object
btRigidBody::btRigidBodyConstructionInfo rbInfo(0.0f, nullptr, cmesh->GetShape(), btVector3(0,0,0));
auto obj = std::make_unique<btRigidBody>(rbInfo);
// set transform
obj->setWorldTransform(trans.ToBtTransform());
// add to world
bt_world_.addRigidBody(obj.get());
static_objs_.emplace_back(std::move(obj));
}
// TODO: add shape
}

View File

@ -10,38 +10,16 @@
namespace collision
{
// struct StaticObjectInstance
// {
// std::unique_ptr<btCollisionShape> shape;
// btRigidBody body;
// StaticObjectInstance(std::unique_ptr<btCollisionShape> shape) : body()
// }
class DynamicsWorld
{
public:
DynamicsWorld();
void AddMapCollision(std::shared_ptr<const assets::Map> map);
btDynamicsWorld& GetBtWorld() { return bt_world_; }
const btDynamicsWorld& GetBtWorld() const { return bt_world_; }
btVehicleRaycaster& GetVehicleRaycaster() { return bt_veh_raycaster_; }
private:
void AddModelInstance(const assets::Model& model, const Transform& trans);
private:
// this is BEFORE bt_world_!!!
std::shared_ptr<const assets::Map> map_;
std::vector<std::unique_ptr<btRigidBody>> static_objs_;
// ^-----
btDefaultCollisionConfiguration bt_cfg_;
btCollisionDispatcher bt_dispatcher_;
btGhostPairCallback bt_ghost_pair_cb_;

View File

@ -0,0 +1,16 @@
#pragma once
namespace collision
{
enum ObjectType
{
OT_UNDEFINED = 0,
OT_MAP_STATIC = 1,
OT_MAP_DESTRUCTIBLE = 2,
};
}

122
src/game/mapinstance.cpp Normal file
View File

@ -0,0 +1,122 @@
#include "mapinstance.hpp"
#include <stdexcept>
#include "assets/cache.hpp"
#include "collision/object_type.hpp"
game::MapInstance::MapInstance(collision::DynamicsWorld& world, std::string mapname)
: world_(world), mapname_(std::move(mapname))
{
map_ = assets::CacheManager::GetMap("data/" + mapname_ + ".map");
// add basemodel col
const auto& basemodel = map_->GetBaseModel();
if (basemodel)
{
Transform identity;
basemodel_col_ = std::make_unique<MapObjectCollision>(world_, basemodel, 0, identity, 0);
}
// add static objects
const auto& objs = map_->GetStaticObjects();
obj_cols_.resize(objs.size());
for (size_t i = 0; i < objs.size(); ++i)
{
SpawnObj(static_cast<net::ObjNum>(i));
}
}
void game::MapInstance::SpawnObj(net::ObjNum objnum)
{
if (objnum >= obj_cols_.size())
throw std::runtime_error("Invalid object number");
size_t i = static_cast<size_t>(objnum);
if (obj_cols_[i])
return; // already spawned
const auto& objs = map_->GetStaticObjects();
obj_cols_[i] = std::make_unique<MapObjectCollision>(world_, objs[i].model, static_cast<net::ObjNum>(i), objs[i].node.local, MAPOBJ_DESTRUCTIBLE);
}
std::unique_ptr<game::MapObjectCollision> game::MapInstance::DestroyObj(net::ObjNum objnum)
{
size_t i = static_cast<size_t>(objnum);
if (i >= obj_cols_.size())
throw std::runtime_error("Invalid object number");
auto obj = std::move(obj_cols_[i]);
if (obj)
{
obj->Break();
}
return obj;
}
game::MapObjectCollision::MapObjectCollision(collision::DynamicsWorld& world,
std::shared_ptr<const assets::Model> model, net::ObjNum num,
const Transform& trans, MapObjectCollisionFlags flags)
: world_(world), model_(std::move(model)), num_(num)
{
auto cshape = model_->GetColShape();
auto cmesh = model_->GetColMesh();
const bool destructible = (flags & MAPOBJ_DESTRUCTIBLE) != 0 && cshape != nullptr;
collision::ObjectType obj_type = collision::OT_MAP_STATIC;
btVector3 local_inertia(0, 0, 0);
float mass = 0.0f;
if (destructible)
{
obj_type = collision::OT_MAP_DESTRUCTIBLE;
}
// prefer simple cshape which allow destruction
if (cshape)
{
body_ = std::make_unique<btRigidBody>(
btRigidBody::btRigidBodyConstructionInfo(mass, nullptr, cshape, local_inertia));
}
else if (cmesh)
{
body_ = std::make_unique<btRigidBody>(
btRigidBody::btRigidBodyConstructionInfo(mass, nullptr, cmesh->GetShape(), local_inertia));
}
body_->setWorldTransform(trans.ToBtTransform());
body_->setUserIndex(static_cast<int>(obj_type));
body_->setUserPointer(this);
// world_.GetBtWorld().addRigidBody(body_.get(), btBroadphaseProxy::StaticFilter, btBroadphaseProxy::AllFilter);
world_.GetBtWorld().addRigidBody(body_.get());
}
void game::MapObjectCollision::Break()
{
if (!body_)
return;
// body_->setCollisionFlags(body_->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT);
float mass = 10.0f;
btVector3 local_inertia(0, 0, 0);
body_->getCollisionShape()->calculateLocalInertia(mass, local_inertia);
body_->setMassProps(mass, local_inertia);
body_->forceActivationState(ACTIVE_TAG);
// set to undefined to avoid breaking again
body_->setUserIndex(static_cast<int>(collision::OT_UNDEFINED));
}
game::MapObjectCollision::~MapObjectCollision()
{
if (body_)
world_.GetBtWorld().removeRigidBody(body_.get());
}

62
src/game/mapinstance.hpp Normal file
View File

@ -0,0 +1,62 @@
#pragma once
#include "assets/map.hpp"
#include "collision/dynamicsworld.hpp"
#include "net/defs.hpp"
namespace game
{
using MapObjectCollisionFlags = uint32_t;
enum MapObjectCollisionFlag : MapObjectCollisionFlags
{
MAPOBJ_DESTRUCTIBLE = 0x01,
};
class MapObjectCollision
{
public:
MapObjectCollision(collision::DynamicsWorld& world, std::shared_ptr<const assets::Model> model,
net::ObjNum num, const Transform& trans, MapObjectCollisionFlags flags);
DELETE_COPY_MOVE(MapObjectCollision)
void Break();
btRigidBody& GetBtBody() { return *body_; }
net::ObjNum GetNum() const { return num_; }
~MapObjectCollision();
private:
collision::DynamicsWorld& world_;
std::shared_ptr<const assets::Model> model_;
net::ObjNum num_;
std::unique_ptr<btRigidBody> body_;
};
class MapInstance
{
public:
MapInstance(collision::DynamicsWorld& world, std::string mapname);
const assets::Map& GetMap() const { return *map_; }
const std::string& GetName() const { return mapname_; }
void SpawnObj(net::ObjNum objnum);
std::unique_ptr<MapObjectCollision> DestroyObj(net::ObjNum objnum);
private:
private:
collision::DynamicsWorld& world_;
std::string mapname_;
std::shared_ptr<const assets::Map> map_;
std::unique_ptr<MapObjectCollision> basemodel_col_;
std::vector<std::unique_ptr<MapObjectCollision>> obj_cols_;
};
}

View File

@ -1,6 +1,5 @@
#include "openworld.hpp"
#include <coroutine>
#include <iostream>
#include "player.hpp"
@ -24,6 +23,9 @@ game::OpenWorld::OpenWorld() : World("openworld")
{
SpawnBot();
}
auto& veh = Spawn<game::DrivableVehicle>("twingo", glm::vec3{0.8f, 0.1f, 0.1f});
veh.SetPosition({110.0f, 100.0f, 5.0f});
}
void game::OpenWorld::Update(int64_t delta_time)

View File

@ -38,15 +38,7 @@ void game::Player::Update()
if (world_)
{
if (cam_ent_)
{
auto cam_ent = world_->GetEntity(cam_ent_);
if (cam_ent)
{
cull_pos_ = cam_ent->GetRoot().GetGlobalPosition();
}
}
SendWorldUpdateMsg();
SyncEntities();
}
}
@ -90,11 +82,30 @@ void game::Player::SendWorldMsg()
{
MSGDEBUG(std::cout << "seding CHWORLD" << std::endl;)
auto msg = BeginMsg(net::MSG_CHWORLD);
msg.Write(net::MapName(world_->GetMapName()));
world_->SendInitData(*this, msg);
}
void game::Player::SendWorldUpdateMsg()
{
if (!world_)
return;
auto msg = BeginMsg(); // no CMD here, included in world payload
msg.Write(world_->GetMsg());
}
void game::Player::SyncEntities()
{
// update cull pos
if (cam_ent_)
{
auto cam_ent = world_->GetEntity(cam_ent_);
if (cam_ent)
{
cull_pos_ = cam_ent->GetRoot().GetGlobalPosition();
}
}
const auto& ents = world_->GetEntities();
auto ent_it = ents.begin();

View File

@ -37,7 +37,9 @@ public:
~Player();
private:
// world sync
void SendWorldMsg();
void SendWorldUpdateMsg();
// entities sync
void SyncEntities();

View File

@ -89,7 +89,7 @@ game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& co
}
auto& bt_world = world_.GetBtWorld();
bt_world.addRigidBody(body_.get());
bt_world.addRigidBody(body_.get(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::AllFilter);
bt_world.addAction(vehicle_.get());
}

View File

@ -1,14 +1,25 @@
#include "world.hpp"
#include <stdexcept>
#include <iostream>
#include "assets/cache.hpp"
#include "utils/allocnum.hpp"
#include "collision/object_type.hpp"
game::World::World(std::string mapname) : mapname_(std::move(mapname))
game::World::World(std::string mapname) : map_(*this, std::move(mapname))
{
map_ = assets::CacheManager::GetMap("data/" + mapname_ + ".map");
AddMapCollision(map_);
}
void game::World::SendInitData(Player& player, net::OutMessage& msg)
{
msg.Write(net::MapName(map_.GetName()));
msg.Write<net::ObjCount>(destroyed_objs_.size());
for (auto objnum : destroyed_objs_)
{
msg.Write(objnum);
}
}
net::EntNum game::World::GetNewEntnum()
@ -38,6 +49,8 @@ void game::World::Update(int64_t delta_time)
// GetBtWorld().stepSimulation(delta_s, 1, delta_s);
GetBtWorld().stepSimulation(delta_s, 2, delta_s * 0.5f);
DetectDestructibleCollisions();
// update entities
for (auto it = ents_.begin(); it != ents_.end();)
{
@ -68,3 +81,80 @@ game::Entity* game::World::GetEntity(net::EntNum entnum)
return it->second.get();
}
void game::World::DetectDestructibleCollisions()
{
auto& bt_world = GetBtWorld();
int numManifolds = bt_world.getDispatcher()->getNumManifolds();
static std::vector<net::ObjNum> to_destroy;
to_destroy.clear();
// std::cout << "Checking " << numManifolds << " manifolds for destructible collisions..." << std::endl;
for (int i = 0; i < numManifolds; i++)
{
btPersistentManifold* contactManifold = bt_world.getDispatcher()->getManifoldByIndexInternal(i);
const btRigidBody* bodyA = static_cast<const btRigidBody*>(contactManifold->getBody0());
const btRigidBody* bodyB = static_cast<const btRigidBody*>(contactManifold->getBody1());
const btRigidBody* destructibleBody = nullptr;
if (bodyA->getUserIndex() == collision::OT_MAP_DESTRUCTIBLE)
destructibleBody = bodyA;
else if (bodyB->getUserIndex() == collision::OT_MAP_DESTRUCTIBLE)
destructibleBody = bodyB;
if (!destructibleBody)
continue;
for (int j = 0; j < contactManifold->getNumContacts(); j++)
{
const float break_threshold = 3000.0f; // TODO: per-object threshold
btManifoldPoint& pt = contactManifold->getContactPoint(j);
if (pt.getAppliedImpulse() > break_threshold)
{
std::cout << "Destructible collision detected: impulse = " << pt.getAppliedImpulse() << std::endl;
MapObjectCollision* obj_col = static_cast<MapObjectCollision*>(destructibleBody->getUserPointer());
to_destroy.push_back(obj_col->GetNum());
const btRigidBody* otherBody = (destructibleBody == bodyA) ? bodyB : bodyA;
btRigidBody* otherBodyNonConst = const_cast<btRigidBody*>(otherBody);
otherBodyNonConst->applyCentralImpulse(pt.m_normalWorldOnB * pt.getAppliedImpulse() * 0.5f);
}
}
}
// destroy objs outside the loop to avoid corruption of the manifold list
for (auto objnum : to_destroy)
{
DestroyObject(objnum);
}
}
void game::World::DestroyObject(net::ObjNum objnum)
{
SendObjDestroyedMsg(objnum);
destroyed_objs_.insert(objnum);
auto col = map_.DestroyObj(objnum);
if (col)
{
DestructibleDestroyed(objnum, std::move(col));
}
}
void game::World::SendObjDestroyedMsg(net::ObjNum objnum)
{
auto msg = BeginMsg(net::MSG_OBJDESTROY);
msg.Write(objnum);
}
void game::World::SendObjRespawnedMsg(net::ObjNum objnum)
{
auto msg = BeginMsg(net::MSG_OBJRESPAWN);
msg.Write(objnum);
}

View File

@ -1,8 +1,9 @@
#pragma once
#include <concepts>
#include <set>
#include "assets/map.hpp"
#include "mapinstance.hpp"
#include "collision/dynamicsworld.hpp"
#include "entity.hpp"
#include "net/defs.hpp"
@ -11,12 +12,14 @@
namespace game
{
class World : public collision::DynamicsWorld
class World : public collision::DynamicsWorld, public net::MsgProducer
{
public:
World(std::string mapname);
DELETE_COPY_MOVE(World)
void SendInitData(Player& player, net::OutMessage& msg);
// spawn entity of type T
template <std::derived_from<Entity> T, typename... TArgs>
T& Spawn(TArgs&&... args)
@ -39,18 +42,29 @@ public:
virtual void PlayerViewAnglesChanged(Player& player, float yaw, float pitch) {}
virtual void PlayerLeft(Player& player) {}
virtual void DestructibleDestroyed(net::ObjNum num, std::unique_ptr<MapObjectCollision> col) {}
Entity* GetEntity(net::EntNum entnum);
const assets::Map& GetMap() const { return *map_; }
const std::string& GetMapName() const { return mapname_; }
const assets::Map& GetMap() const { return map_.GetMap(); }
const std::string& GetMapName() const { return map_.GetName(); }
const std::map<net::EntNum, std::unique_ptr<Entity>>& GetEntities() const { return ents_; }
const int64_t& GetTime() const { return time_ms_; }
virtual ~World() = default;
private:
std::shared_ptr<const assets::Map> map_;
std::string mapname_;
void DetectDestructibleCollisions();
void DestroyObject(net::ObjNum objnum);
void SendObjDestroyedMsg(net::ObjNum objnum);
void SendObjRespawnedMsg(net::ObjNum objnum);
private:
MapInstance map_;
std::set<net::ObjNum> destroyed_objs_;
std::map<net::EntNum, std::unique_ptr<Entity>> ents_;
net::EntNum last_entnum_ = 0;

View File

@ -117,12 +117,14 @@ audio::Master& game::view::ClientSession::GetAudioMaster() const
bool game::view::ClientSession::ProcessWorldMsg(net::InMessage& msg)
{
net::MapName mapname;
if (!msg.Read(mapname))
try
{
world_ = std::make_unique<WorldView>(*this, msg);
}
catch (const EntityInitError&)
{
return false;
// TODO: pass mapname
world_ = std::make_unique<WorldView>(*this);
}
return true;
}

View File

@ -0,0 +1,94 @@
#include "mapinstanceview.hpp"
#include "assets/cache.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
game::view::MapInstanceView::MapInstanceView(const std::string& map_name)
{
map_ = assets::CacheManager::GetMap("data/" + map_name + ".map");
objs_visible_.resize(map_->GetStaticObjects().size(), true);
}
void game::view::MapInstanceView::Draw(const game::view::DrawArgs& args) const
{
const auto& basemodel = map_->GetBaseModel();
if (!basemodel || !basemodel->GetMesh())
return;
const auto& mesh = *basemodel->GetMesh();
const float max_dist = args.render_distance + 200.0f;
const float max_dist2 = max_dist * max_dist;
for (const auto& chunks = map_->GetChunks(); const auto& chunk : chunks)
{
glm::vec3 center = (chunk.aabb.min + chunk.aabb.max) * 0.5f;
if (glm::distance2(args.eye, center) > max_dist2)
continue;
if (!args.frustum.IsAABBVisible(chunk.aabb))
continue;
DrawChunk(args, mesh, chunk);
}
}
void game::view::MapInstanceView::EnableObj(net::ObjNum num, bool enable)
{
size_t i = static_cast<size_t>(num);
if (i >= objs_visible_.size())
return;
objs_visible_[i] = enable;
}
void game::view::MapInstanceView::DrawChunk(const game::view::DrawArgs& args, const assets::Mesh& basemesh,
const assets::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);
}
const auto& objs = map_->GetStaticObjects();
for (size_t i = 0; i < chunk.num_objs; ++i)
{
size_t abs_i = chunk.first_obj + i;
if (abs_i >= objs.size())
continue;
if (!objs_visible_[abs_i])
continue;
const auto& obj = objs[abs_i];
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)
{
gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface;
cmd.matrices = &obj.node.matrix;
// cmd.color_mod = glm::vec4(obj.color, 1.0f);
args.dlist.AddSurface(cmd);
}
}
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "assets/map.hpp"
#include "draw_args.hpp"
#include "net/defs.hpp"
namespace game::view
{
class MapInstanceView
{
public:
MapInstanceView(const std::string& map_name);
void Draw(const game::view::DrawArgs& args) const;
void EnableObj(net::ObjNum num, bool enable);
private:
void DrawChunk(const game::view::DrawArgs& args, const assets::Mesh& basemesh, const assets::Chunk& chunk) const;
private:
std::shared_ptr<const assets::Map> map_;
std::vector<bool> objs_visible_;
};
}

View File

@ -6,12 +6,26 @@
#include "vehicleview.hpp"
#include "client_session.hpp"
game::view::WorldView::WorldView(ClientSession& session) :
session_(session),
audiomaster_(session_.GetAudioMaster())
game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) :
session_(session), audiomaster_(session_.GetAudioMaster()), map_("openworld")
{
map_ = assets::CacheManager::GetMap("data/openworld.map");
AddMapCollision(map_);
net::MapName mapname;
if (!msg.Read(mapname))
throw EntityInitError();
// init destroyed objs
net::ObjCount objcount;
if (!msg.Read(objcount))
throw EntityInitError();
for (net::ObjCount i = 0; i < objcount; ++i)
{
net::ObjNum objnum;
if (!msg.Read(objnum))
throw EntityInitError();
map_.EnableObj(objnum, false);
}
}
bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& msg)
@ -27,6 +41,12 @@ bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& ms
case net::MSG_ENTDESTROY:
return ProcessEntDestroyMsg(msg);
case net::MSG_OBJDESTROY:
return ProcessObjDestroyOrRespawnMsg(msg, false);
case net::MSG_OBJRESPAWN:
return ProcessObjDestroyOrRespawnMsg(msg, true);
default:
return false;
}
@ -44,8 +64,7 @@ void game::view::WorldView::Update(const UpdateInfo& info)
void game::view::WorldView::Draw(const DrawArgs& args) const
{
if (map_)
map_->Draw(args);
map_.Draw(args);
for (const auto& [entnum, ent] : ents_)
{
@ -150,3 +169,13 @@ bool game::view::WorldView::ProcessEntDestroyMsg(net::InMessage& msg)
ents_.erase(entnum);
return true;
}
bool game::view::WorldView::ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable)
{
net::ObjNum objnum;
if (!msg.Read(objnum))
return false;
map_.EnableObj(objnum, enable);
return true;
}

View File

@ -6,6 +6,7 @@
#include "net/inmessage.hpp"
#include "collision/dynamicsworld.hpp"
#include "entityview.hpp"
#include "mapinstanceview.hpp"
namespace game::view
{
@ -15,7 +16,7 @@ class ClientSession;
class WorldView : public collision::DynamicsWorld
{
public:
WorldView(ClientSession& session);
WorldView(ClientSession& session, net::InMessage& msg);
bool ProcessMsg(net::MessageType type, net::InMessage& msg);
@ -35,10 +36,12 @@ private:
bool ProcessEntMsgMsg(net::InMessage& msg);
bool ProcessEntDestroyMsg(net::InMessage& msg);
bool ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable);
private:
ClientSession& session_;
std::shared_ptr<const assets::Map> map_;
MapInstanceView map_;
std::map<net::EntNum, std::unique_ptr<EntityView>> ents_;
float time_ = 0.0f;

View File

@ -31,6 +31,9 @@ enum MessageType : uint8_t
// CHWORLD <MapName>
MSG_CHWORLD,
// CAM <EntNum>
MSG_CAM,
/*~~~~~~~~ Entity ~~~~~~~~*/
// ENTSPAWN <EntNum> <EntType> data...
MSG_ENTSPAWN,
@ -39,8 +42,12 @@ enum MessageType : uint8_t
// ENTDESTROY <EntNum>
MSG_ENTDESTROY,
// CAM <EntNum>
MSG_CAM,
/*~~~~~~~~ Destructibles ~~~~~~~~*/
// OBJDESTROY <ObjNum>
MSG_OBJDESTROY,
// OBJRESPAWN <ObjNum>
MSG_OBJRESPAWN,
/*~~~~~~~~~~~~~~~~*/
MSG_COUNT,
@ -58,6 +65,7 @@ constexpr long long PI_D = 78256779;
using ViewYawQ = Quantized<uint16_t, 0, 2 * PI_N, PI_D>;
using ViewPitchQ = Quantized<uint16_t, -PI_N, PI_N, PI_D>;
// entities
using EntNum = uint16_t;
enum EntType : uint8_t
@ -107,4 +115,8 @@ using AnimTimeQ = Quantized<uint8_t, 0, 1>;
using NumClothes = uint8_t;
using ClothesName = FixedStr<32>;
// destructibles
using ObjNum = uint16_t;
using ObjCount = ObjNum;
} // namespace net