From d6947a79d60849b263cf289d4ffcc208ff6cc66f Mon Sep 17 00:00:00 2001 From: tovjemam Date: Fri, 27 Feb 2026 18:32:20 +0100 Subject: [PATCH] Destructible objs pt. 1 --- CMakeLists.txt | 4 + src/assets/map.cpp | 70 ++---------------- src/assets/map.hpp | 16 ++-- src/assets/model.cpp | 32 ++++++++ src/assets/model.hpp | 1 + src/collision/dynamicsworld.cpp | 44 ----------- src/collision/dynamicsworld.hpp | 22 ------ src/collision/object_type.hpp | 16 ++++ src/game/mapinstance.cpp | 122 +++++++++++++++++++++++++++++++ src/game/mapinstance.hpp | 62 ++++++++++++++++ src/game/openworld.cpp | 4 +- src/game/player.cpp | 31 +++++--- src/game/player.hpp | 2 + src/game/vehicle.cpp | 2 +- src/game/world.cpp | 96 +++++++++++++++++++++++- src/game/world.hpp | 26 +++++-- src/gameview/client_session.cpp | 12 +-- src/gameview/mapinstanceview.cpp | 94 ++++++++++++++++++++++++ src/gameview/mapinstanceview.hpp | 30 ++++++++ src/gameview/worldview.cpp | 43 +++++++++-- src/gameview/worldview.hpp | 7 +- src/net/defs.hpp | 16 +++- 22 files changed, 573 insertions(+), 179 deletions(-) create mode 100644 src/collision/object_type.hpp create mode 100644 src/game/mapinstance.cpp create mode 100644 src/game/mapinstance.hpp create mode 100644 src/gameview/mapinstanceview.cpp create mode 100644 src/gameview/mapinstanceview.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a37ed7..897487a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" diff --git a/src/assets/map.cpp b/src/assets/map.cpp index 2446c49..8dd6e7f 100644 --- a/src/assets/map.cpp +++ b/src/assets/map.cpp @@ -2,9 +2,6 @@ #include -#define GLM_ENABLE_EXPERIMENTAL -#include - #include "cache.hpp" #include "cmdfile.hpp" #include "utils/files.hpp" @@ -65,7 +62,7 @@ std::shared_ptr 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 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 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 diff --git a/src/assets/map.hpp b/src/assets/map.hpp index 5e8a814..21ac9d6 100644 --- a/src/assets/map.hpp +++ b/src/assets/map.hpp @@ -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 surfaces; - std::vector objs; + size_t first_obj = 0; + size_t num_objs = 0; }; struct MapGraphNode @@ -61,16 +58,13 @@ public: const std::shared_ptr& GetBaseModel() const { return basemodel_; } const std::vector& GetChunks() const { return chunks_; } + const std::vector& 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 basemodel_; std::vector chunks_; + std::vector objs_; std::map graphs_; }; diff --git a/src/assets/model.cpp b/src/assets/model.cpp index 0482e73..f76aa36 100644 --- a/src/assets/model.cpp +++ b/src/assets/model.cpp @@ -12,6 +12,7 @@ std::shared_ptr assets::Model::LoadFromFile(const std::stri CLIENT_ONLY(MeshBuilder mb(gfx::MF_NONE);) std::unique_ptr temp_hull; + std::unique_ptr compound; LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) { if (command == "v") @@ -129,6 +130,33 @@ std::shared_ptr 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(); + } + + if (shape_type == "box") + { + auto box_shape = std::make_unique(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 assets::Model::LoadFromFile(const std::stri model->cshape_ = std::make_unique((btScalar*)shape_hull->getVertexPointer(), shape_hull->numVertices(), sizeof(btVector3)); } + else + { + model->cshape_ = std::move(compound); + } return model; } diff --git a/src/assets/model.hpp b/src/assets/model.hpp index 30825c4..3295e98 100644 --- a/src/assets/model.hpp +++ b/src/assets/model.hpp @@ -51,6 +51,7 @@ public: private: std::unique_ptr cmesh_; // std::vector cshapes_; + std::vector> subshapes_; std::unique_ptr cshape_; std::shared_ptr skeleton_; diff --git a/src/collision/dynamicsworld.cpp b/src/collision/dynamicsworld.cpp index 60f7fc2..beb88b5 100644 --- a/src/collision/dynamicsworld.cpp +++ b/src/collision/dynamicsworld.cpp @@ -10,47 +10,3 @@ collision::DynamicsWorld::DynamicsWorld() bt_broadphase_.getOverlappingPairCache()->setInternalGhostPairCallback(&bt_ghost_pair_cb_); } - -void collision::DynamicsWorld::AddMapCollision(std::shared_ptr 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(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 -} diff --git a/src/collision/dynamicsworld.hpp b/src/collision/dynamicsworld.hpp index fd06478..45469de 100644 --- a/src/collision/dynamicsworld.hpp +++ b/src/collision/dynamicsworld.hpp @@ -10,38 +10,16 @@ namespace collision { -// struct StaticObjectInstance -// { -// std::unique_ptr shape; -// btRigidBody body; - -// StaticObjectInstance(std::unique_ptr shape) : body() - -// } - - - - class DynamicsWorld { public: DynamicsWorld(); - - void AddMapCollision(std::shared_ptr 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 map_; - std::vector> static_objs_; - // ^----- - btDefaultCollisionConfiguration bt_cfg_; btCollisionDispatcher bt_dispatcher_; btGhostPairCallback bt_ghost_pair_cb_; diff --git a/src/collision/object_type.hpp b/src/collision/object_type.hpp new file mode 100644 index 0000000..4cd35e9 --- /dev/null +++ b/src/collision/object_type.hpp @@ -0,0 +1,16 @@ +#pragma once + +namespace collision +{ + +enum ObjectType +{ + OT_UNDEFINED = 0, + OT_MAP_STATIC = 1, + OT_MAP_DESTRUCTIBLE = 2, + + +}; + + +} \ No newline at end of file diff --git a/src/game/mapinstance.cpp b/src/game/mapinstance.cpp new file mode 100644 index 0000000..3e59473 --- /dev/null +++ b/src/game/mapinstance.cpp @@ -0,0 +1,122 @@ +#include "mapinstance.hpp" + +#include + +#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(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(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(objnum); + + if (obj_cols_[i]) + return; // already spawned + + const auto& objs = map_->GetStaticObjects(); + obj_cols_[i] = std::make_unique(world_, objs[i].model, static_cast(i), objs[i].node.local, MAPOBJ_DESTRUCTIBLE); +} + +std::unique_ptr game::MapInstance::DestroyObj(net::ObjNum objnum) +{ + size_t i = static_cast(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 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::btRigidBodyConstructionInfo(mass, nullptr, cshape, local_inertia)); + } + else if (cmesh) + { + body_ = std::make_unique( + btRigidBody::btRigidBodyConstructionInfo(mass, nullptr, cmesh->GetShape(), local_inertia)); + } + + body_->setWorldTransform(trans.ToBtTransform()); + body_->setUserIndex(static_cast(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(collision::OT_UNDEFINED)); +} + +game::MapObjectCollision::~MapObjectCollision() +{ + if (body_) + world_.GetBtWorld().removeRigidBody(body_.get()); +} diff --git a/src/game/mapinstance.hpp b/src/game/mapinstance.hpp new file mode 100644 index 0000000..9c1cce5 --- /dev/null +++ b/src/game/mapinstance.hpp @@ -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 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 model_; + net::ObjNum num_; + std::unique_ptr 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 DestroyObj(net::ObjNum objnum); + +private: + +private: + collision::DynamicsWorld& world_; + std::string mapname_; + std::shared_ptr map_; + + std::unique_ptr basemodel_col_; + std::vector> obj_cols_; + +}; + + +} \ No newline at end of file diff --git a/src/game/openworld.cpp b/src/game/openworld.cpp index 57ef0da..689c91d 100644 --- a/src/game/openworld.cpp +++ b/src/game/openworld.cpp @@ -1,6 +1,5 @@ #include "openworld.hpp" -#include #include #include "player.hpp" @@ -24,6 +23,9 @@ game::OpenWorld::OpenWorld() : World("openworld") { SpawnBot(); } + + auto& veh = Spawn("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) diff --git a/src/game/player.cpp b/src/game/player.cpp index 03c24c2..78f24c8 100644 --- a/src/game/player.cpp +++ b/src/game/player.cpp @@ -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(); diff --git a/src/game/player.hpp b/src/game/player.hpp index 70f9875..b0e046a 100644 --- a/src/game/player.hpp +++ b/src/game/player.hpp @@ -37,7 +37,9 @@ public: ~Player(); private: + // world sync void SendWorldMsg(); + void SendWorldUpdateMsg(); // entities sync void SyncEntities(); diff --git a/src/game/vehicle.cpp b/src/game/vehicle.cpp index aafa4db..1a1e1a5 100644 --- a/src/game/vehicle.cpp +++ b/src/game/vehicle.cpp @@ -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()); } diff --git a/src/game/world.cpp b/src/game/world.cpp index 42cd632..bbeb507 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -1,14 +1,25 @@ #include "world.hpp" #include +#include #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(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 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(contactManifold->getBody0()); + const btRigidBody* bodyB = static_cast(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(destructibleBody->getUserPointer()); + to_destroy.push_back(obj_col->GetNum()); + + const btRigidBody* otherBody = (destructibleBody == bodyA) ? bodyB : bodyA; + btRigidBody* otherBodyNonConst = const_cast(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); +} diff --git a/src/game/world.hpp b/src/game/world.hpp index 5680d0c..6051fd0 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -1,8 +1,9 @@ #pragma once #include +#include -#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 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 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>& GetEntities() const { return ents_; } const int64_t& GetTime() const { return time_ms_; } virtual ~World() = default; private: - std::shared_ptr 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 destroyed_objs_; + std::map> ents_; net::EntNum last_entnum_ = 0; diff --git a/src/gameview/client_session.cpp b/src/gameview/client_session.cpp index 921e922..46c4468 100644 --- a/src/gameview/client_session.cpp +++ b/src/gameview/client_session.cpp @@ -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(*this, msg); + } + catch (const EntityInitError&) + { return false; - - // TODO: pass mapname - world_ = std::make_unique(*this); + } return true; } diff --git a/src/gameview/mapinstanceview.cpp b/src/gameview/mapinstanceview.cpp new file mode 100644 index 0000000..fb3b216 --- /dev/null +++ b/src/gameview/mapinstanceview.cpp @@ -0,0 +1,94 @@ +#include "mapinstanceview.hpp" +#include "assets/cache.hpp" + +#define GLM_ENABLE_EXPERIMENTAL +#include + +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(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); + } + } +} diff --git a/src/gameview/mapinstanceview.hpp b/src/gameview/mapinstanceview.hpp new file mode 100644 index 0000000..aaa1147 --- /dev/null +++ b/src/gameview/mapinstanceview.hpp @@ -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 map_; + std::vector objs_visible_; + +}; + + + +} \ No newline at end of file diff --git a/src/gameview/worldview.cpp b/src/gameview/worldview.cpp index 8699eee..04dde08 100644 --- a/src/gameview/worldview.cpp +++ b/src/gameview/worldview.cpp @@ -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; +} diff --git a/src/gameview/worldview.hpp b/src/gameview/worldview.hpp index 59bb84d..af690bf 100644 --- a/src/gameview/worldview.hpp +++ b/src/gameview/worldview.hpp @@ -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 map_; + MapInstanceView map_; std::map> ents_; float time_ = 0.0f; diff --git a/src/net/defs.hpp b/src/net/defs.hpp index 1b114ab..2a059ce 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -31,6 +31,9 @@ enum MessageType : uint8_t // CHWORLD MSG_CHWORLD, + // CAM + MSG_CAM, + /*~~~~~~~~ Entity ~~~~~~~~*/ // ENTSPAWN data... MSG_ENTSPAWN, @@ -39,8 +42,12 @@ enum MessageType : uint8_t // ENTDESTROY MSG_ENTDESTROY, - // CAM - MSG_CAM, + /*~~~~~~~~ Destructibles ~~~~~~~~*/ + // OBJDESTROY + MSG_OBJDESTROY, + // OBJRESPAWN + MSG_OBJRESPAWN, + /*~~~~~~~~~~~~~~~~*/ MSG_COUNT, @@ -58,6 +65,7 @@ constexpr long long PI_D = 78256779; using ViewYawQ = Quantized; using ViewPitchQ = Quantized; +// entities using EntNum = uint16_t; enum EntType : uint8_t @@ -107,4 +115,8 @@ using AnimTimeQ = Quantized; using NumClothes = uint8_t; using ClothesName = FixedStr<32>; +// destructibles +using ObjNum = uint16_t; +using ObjCount = ObjNum; + } // namespace net \ No newline at end of file