Physics materials & FX

This commit is contained in:
tovjemam 2026-06-15 12:53:41 +02:00
parent c549890d2c
commit 8c52678e79
19 changed files with 601 additions and 28 deletions

View File

@ -58,6 +58,8 @@ set(COMMON_SOURCES
)
set(CLIENT_ONLY_SOURCES
"src/assets/effect.hpp"
"src/assets/effect.cpp"
"src/assets/mesh_builder.hpp"
"src/assets/mesh_builder.cpp"
"src/audio/defs.hpp"
@ -88,6 +90,8 @@ set(CLIENT_ONLY_SOURCES
"src/gameview/mapinstanceview.cpp"
"src/gameview/markerview.hpp"
"src/gameview/markerview.cpp"
"src/gameview/particle_emitter.hpp"
"src/gameview/particle_emitter.cpp"
"src/gameview/remote_menu_view.hpp"
"src/gameview/remote_menu_view.cpp"
"src/gameview/simple_entity_view.hpp"

View File

@ -9,3 +9,4 @@ assets::ItemCache assets::CacheManager::item_cache_;
CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;)
CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;)
CLIENT_ONLY(assets::FontCache assets::CacheManager::font_cache_;)
CLIENT_ONLY(assets::EffectCache assets::CacheManager::effect_cache_;)

View File

@ -12,6 +12,7 @@
#include "audio/sound.hpp"
#include "gfx/texture.hpp"
#include "gui/font.hpp"
#include "effect.hpp"
#endif
#include <iostream>
@ -67,6 +68,12 @@ class FontCache final : public Cache<gui::Font>
protected:
PtrType Load(const std::string& key) override { return gui::Font::LoadFromFile(key); }
};
class EffectCache final : public Cache<Effect>
{
protected:
PtrType Load(const std::string& key) override { return Effect::LoadFromFile(key); }
};
#endif // CLIENT
class SkeletonCache final : public Cache<Skeleton>
@ -136,6 +143,11 @@ public:
{
return font_cache_.Get(filename);
}
static std::shared_ptr<const Effect> GetEffect(const std::string& filename)
{
return effect_cache_.Get(filename);
}
#endif
private:
@ -147,6 +159,7 @@ private:
CLIENT_ONLY(static TextureCache texture_cache_;)
CLIENT_ONLY(static SoundCache sound_cache_;)
CLIENT_ONLY(static FontCache font_cache_;)
CLIENT_ONLY(static EffectCache effect_cache_;)
};
} // namespace assets

78
src/assets/effect.cpp Normal file
View File

@ -0,0 +1,78 @@
#include "effect.hpp"
#include "cmdfile.hpp"
#include "assets/cache.hpp"
std::shared_ptr<const assets::Effect> assets::Effect::LoadFromFile(const std::string& path)
{
auto fx = std::make_shared<Effect>();
ParticleDef* particle = nullptr;
LoadCMDFile(path, [&](const std::string& command, std::istringstream& iss) {
if (command == "sound")
{
std::string sound_name;
iss >> sound_name;
fx->sounds_.emplace_back(assets::CacheManager::GetSound("data/" + sound_name + ".snd"));
}
else if (command == "particle")
{
particle = &fx->particle_defs_.emplace_back();
}
else if (particle)
{
if (command == "texture")
{
std::string texture_name;
iss >> texture_name;
particle->texture = assets::CacheManager::GetTexture("data/" + texture_name + ".png");
}
else if (command == "blend")
{
std::string blend_str;
iss >> blend_str;
if (blend_str == "normal")
particle->blend = PTB_BLEND_NORMAL;
else if (blend_str == "additive")
particle->blend = PTB_BLEND_ADDITIVE;
}
else if (command == "size")
{
iss >> particle->size_min >> particle->size_max;
}
else if (command == "count")
{
iss >> particle->count_min >> particle->count_max;
}
else if (command == "velocity")
{
iss >> particle->velocity_min >> particle->velocity_max;
}
else if (command == "dispersion")
{
iss >> particle->max_dispersion;
}
else if (command == "gravity")
{
iss >> particle->gravity_min >> particle->gravity_max;
}
else if (command == "lifetime")
{
iss >> particle->lifetime_min >> particle->lifetime_max;
}
else if (command == "fadetime")
{
iss >> particle->fadetime_min >> particle->fadetime_max;
}
}
else
{
throw std::runtime_error("Unknown or unexpected command in effect: " + command);
}
});
return fx;
}

55
src/assets/effect.hpp Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#include <vector>
#include "gfx/texture.hpp"
#include "audio/sound.hpp"
namespace assets
{
enum ParticleBlendType
{
PTB_NONE,
PTB_BLEND_NORMAL,
PTB_BLEND_ADDITIVE,
};
struct ParticleDef
{
std::shared_ptr<const gfx::Texture> texture;
ParticleBlendType blend = PTB_NONE;
float size_min = 1.0f;
float size_max = 1.0f;
size_t count_min = 1;
size_t count_max = 1;
float velocity_min = 1.0f;
float velocity_max = 1.0f;
float max_dispersion = 0.0f;
float gravity_min = 1.0f;
float gravity_max = 1.0f;
float lifetime_min = 1.0f;
float lifetime_max = 1.0f;
float fadetime_min = 1.0f;
float fadetime_max = 1.0f;
};
class Effect
{
public:
Effect() = default;
static std::shared_ptr<const Effect> LoadFromFile(const std::string& path);
const std::vector<ParticleDef>& GetParticleDefs() const { return particle_defs_; }
const std::vector<std::shared_ptr<const audio::Sound>>& GetSounds() const { return sounds_; }
private:
std::vector<ParticleDef> particle_defs_;
std::vector<std::shared_ptr<const audio::Sound>> sounds_;
};
}

View File

@ -5,6 +5,26 @@
#include <BulletCollision/CollisionShapes/btShapeHull.h>
static collision::Material GetMaterialByName(const std::string& name)
{
if (name == "stone")
return collision::PM_STONE;
else if (name == "dirt")
return collision::PM_DIRT;
else if (name == "grass")
return collision::PM_GRASS;
else if (name == "wood")
return collision::PM_WOOD;
else if (name == "metal")
return collision::PM_METAL;
else if (name == "glass")
return collision::PM_GLASS;
else if (name == "flesh")
return collision::PM_FLESH;
else
return collision::PM_STONE;
}
std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::string& filename)
{
auto model = std::make_shared<Model>();
@ -15,6 +35,8 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
std::unique_ptr<btConvexHullShape> temp_hull;
std::unique_ptr<btCompoundShape> compound;
bool current_collision = true;
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
if (command == "v")
{
@ -69,7 +91,7 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
mb.AddTriangle(t);
)
if (model->cmesh_)
if (current_collision && model->cmesh_)
{
glm::vec3 p[3];
for (size_t i = 0; i < 3; ++i)
@ -199,6 +221,23 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
iss >> key >> val;
model->params_[key] = val;
}
else if (command == "pm")
{
std::string pm_name;
iss >> pm_name;
if (pm_name == "none")
{
current_collision = false;
}
else
{
current_collision = true;
if (model->cmesh_)
{
model->cmesh_->BeginMaterial(GetMaterialByName(pm_name));
}
}
}
else
{
throw std::runtime_error("Unknown command in model file: " + command);

View File

@ -1,6 +1,7 @@
#include "skeleton.hpp"
#include "cmdfile.hpp"
#include "collision/shape_info.hpp"
#include <stdexcept>
@ -62,6 +63,8 @@ std::shared_ptr<const assets::Skeleton> assets::Skeleton::LoadFromFile(const std
{
throw std::runtime_error("Unknown hitbone shape: " + shape_name);
}
collision::SetShapeMaterial(*hitbone.col_shape, collision::PM_FLESH);
}
});

View File

@ -0,0 +1,47 @@
#pragma once
#include <cstdint>
#include <btBulletDynamicsCommon.h>
#include <span>
namespace collision
{
enum Material : uint8_t
{
PM_STONE,
PM_DIRT,
PM_GRASS,
PM_WOOD,
PM_METAL,
PM_GLASS,
PM_PLASTIC,
PM_FLESH,
};
struct ShapeInfo
{
std::span<Material> triangle_materials;
};
inline void SetShapeMaterial(btCollisionShape& shape, Material material)
{
shape.setUserIndex(material);
}
inline Material GetShapeMaterial(const btCollisionShape& shape)
{
return static_cast<Material>(shape.getUserIndex());
}
inline void SetShapeInfo(btCollisionShape& shape, const ShapeInfo* info)
{
shape.setUserPointer(const_cast<ShapeInfo*>(info));
}
inline const ShapeInfo* GetShapeInfo(const btCollisionShape& shape)
{
return reinterpret_cast<ShapeInfo*>(shape.getUserPointer());
}
}

View File

@ -2,7 +2,11 @@
collision::TriangleMesh::TriangleMesh()
{
}
void collision::TriangleMesh::BeginMaterial(Material material)
{
current_material_ = material;
}
void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2)
@ -11,10 +15,13 @@ void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3&
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, false);
tri_materials_.push_back(current_material_);
}
void collision::TriangleMesh::Build()
{
bt_shape_ = std::make_unique<btBvhTriangleMeshShape>(&bt_mesh_, true, true);
shape_info_.triangle_materials = tri_materials_;
SetShapeInfo(*bt_shape_, &shape_info_);
}

View File

@ -1,26 +1,36 @@
#pragma once
#include <vector>
#include <span>
#include <glm/glm.hpp>
#include <memory>
#include <span>
#include <vector>
#include <btBulletCollisionCommon.h>
#include <glm/glm.hpp>
#include "shape_info.hpp"
#include "utils/defs.hpp"
namespace collision
{
class TriangleMesh
{
btTriangleMesh bt_mesh_;
std::unique_ptr<btBvhTriangleMeshShape> bt_shape_;
public:
TriangleMesh();
class TriangleMesh
{
public:
TriangleMesh();
DELETE_COPY_MOVE(TriangleMesh)
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
void Build();
void BeginMaterial(Material material);
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
void Build();
btBvhTriangleMeshShape* GetShape() const { return bt_shape_.get(); }
};
}
btBvhTriangleMeshShape* GetShape() const { return bt_shape_.get(); }
private:
Material current_material_ = PM_STONE;
btTriangleMesh bt_mesh_;
std::unique_ptr<btBvhTriangleMeshShape> bt_shape_;
std::vector<Material> tri_materials_;
ShapeInfo shape_info_;
};
} // namespace collision

View File

@ -108,7 +108,7 @@ void game::PlayerCharacter::UpdateAimTarget()
SetAimTarget(target);
// GetWorld().Beam(eye, target, 0xFFFF00, 1.0 / 25.0f);
GetWorld().BeamBox(target - 0.05f, target + 0.05f, 0xFFFF00, 1.5f / 25.0f);
// GetWorld().BeamBox(target - 0.05f, target + 0.05f, 0xFFFF00, 1.5f / 25.0f);
}
void game::PlayerCharacter::UpdateUseTarget()

View File

@ -179,19 +179,74 @@ bool game::World::TraceBullet(const glm::vec3& start, const glm::vec3& end, game
return TraceBulletInternal(start, end, shooter, out_hit_pos) != nullptr;
}
static uint32_t GetMaterialColor(collision::Material material)
{
switch (material)
{
case collision::PM_STONE:
return 0xFFFFFF;
case collision::PM_DIRT:
return 0x224488;
case collision::PM_GRASS:
return 0x00FF00;
case collision::PM_WOOD:
return 0x0000FF;
case collision::PM_METAL:
return 0x0077FF;
case collision::PM_GLASS:
return 0xFF7700;
case collision::PM_FLESH:
return 0xFF00FF;
default:
return 0xFFFFFF;
}
}
static std::string GetMaterialImpactFx(collision::Material material)
{
switch (material)
{
// case collision::PM_STONE:
// return "impact_stone";
// case collision::PM_DIRT:
// return "impact_dirt";
case collision::PM_GRASS:
return "impact_grass";
// case collision::PM_WOOD:
// // return "impact_wood";
// case collision::PM_METAL:
// return "impact_metal";
// case collision::PM_GLASS:
// return "impact_glass";
// case collision::PM_FLESH:
// return "impact_organic";
default:
return "impact_metal";
}
}
void game::World::FireBullet(const BulletInfo& bullet)
{
glm::vec3 hit_pos;
auto hit_obj = TraceBulletInternal(bullet.start, bullet.end, bullet.shooter, hit_pos);
glm::vec3 hit_pos, hit_normal;
collision::Material material;
auto hit_obj = TraceBulletInternal(bullet.start, bullet.end, bullet.shooter, hit_pos, &hit_normal, &material);
if (!hit_obj)
{
// Beam(bullet.start, bullet.end, 0x0044DD, 0.04f);
return;
}
auto obj_cb = collision::GetObjectCallback(hit_obj);
obj_cb->OnBulletHit(bullet, hit_obj);
// TODO: remove
const float box_extent = 0.1f;
BeamBox(hit_pos - box_extent, hit_pos + box_extent, 0x0077FF, 1.0f);
// const float box_extent = 0.1f;
// BeamBox(hit_pos - box_extent, hit_pos + box_extent, GetMaterialColor(material), 1.0f);
// Beam(bullet.start, hit_pos, 0x0044DD, 0.04f);
// effect
Effect(GetMaterialImpactFx(material), hit_pos, glm::normalize(hit_normal));
}
void game::World::Beam(const glm::vec3& start, const glm::vec3& end, uint32_t color, float time)
@ -230,6 +285,16 @@ void game::World::BeamBox(const glm::vec3& min, const glm::vec3& max, uint32_t c
Beam(p3, p7, color, time);
}
void game::World::Effect(const std::string& name, const glm::vec3& pos, const glm::vec3& dir)
{
auto msg = BeginLocalMsg(pos, 400.0f, net::MSG_FX);
msg.Write(net::ModelName(name));
net::WritePosition(msg, pos);
msg.Write<net::DirQ>(dir.x);
msg.Write<net::DirQ>(dir.y);
msg.Write<net::DirQ>(dir.z);
}
void game::World::SendChat(const std::string& text)
{
auto msg = BeginMsg(net::MSG_CHAT);
@ -375,18 +440,23 @@ struct NotMeNotMyRideAndNotOtherPassengersOfMyRideClosestRayResultCallback : pub
}
game::HumanCharacter* me = nullptr;
int triangle_idx = 0;
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override
{
if (IsMeOrMyRideOrOtherPassengerOfMyRide(me, rayResult.m_collisionObject))
return rayResult.m_hitFraction;
triangle_idx = rayResult.m_localShapeInfo ? rayResult.m_localShapeInfo->m_triangleIndex : -1;
return Super::addSingleResult(rayResult, normalInWorldSpace);
}
};
const btCollisionObject* game::World::TraceBulletInternal(const glm::vec3& start, const glm::vec3& end,
game::HumanCharacter* shooter, glm::vec3& out_hit_pos)
game::HumanCharacter* shooter, glm::vec3& out_hit_pos,
glm::vec3* out_hit_normal,
collision::Material* out_hit_material)
{
btVector3 bt_start(start.x, start.y, start.z);
btVector3 bt_end(end.x, end.y, end.z);
@ -417,5 +487,32 @@ const btCollisionObject* game::World::TraceBulletInternal(const glm::vec3& start
return nullptr;
out_hit_pos = glm::vec3(cb.m_hitPointWorld.x(), cb.m_hitPointWorld.y(), cb.m_hitPointWorld.z());
if (out_hit_normal)
{
*out_hit_normal = glm::vec3(cb.m_hitNormalWorld.x(), cb.m_hitNormalWorld.y(), cb.m_hitNormalWorld.z());
}
// get material
if (out_hit_material)
{
*out_hit_material = collision::PM_STONE;
auto shape = cb.m_collisionObject->getCollisionShape();
if (shape)
{
*out_hit_material = collision::GetShapeMaterial(*shape);
// try to get triangle material
if (cb.triangle_idx >= 0)
{
auto shape_info = collision::GetShapeInfo(*shape);
if (shape_info && shape_info->triangle_materials.size() > cb.triangle_idx)
{
*out_hit_material = shape_info->triangle_materials[cb.triangle_idx];
}
}
}
}
return cb.m_collisionObject;
}

View File

@ -68,6 +68,8 @@ public:
void Beam(const glm::vec3& start, const glm::vec3& end, uint32_t color, float time);
void BeamBox(const glm::vec3& min, const glm::vec3& max, uint32_t color, float time);
void Effect(const std::string& name, const glm::vec3& pos, const glm::vec3& dir);
void SendChat(const std::string& text);
virtual ~World() = default;
@ -81,7 +83,9 @@ private:
void SendObjRespawnedMsg(net::ObjNum objnum);
const btCollisionObject* TraceBulletInternal(const glm::vec3& start, const glm::vec3& end,
game::HumanCharacter* shooter, glm::vec3& out_hit_pos);
game::HumanCharacter* shooter, glm::vec3& out_hit_pos,
glm::vec3* out_hit_normal = nullptr,
collision::Material* out_hit_material = nullptr);
private:
MapInstance map_;

View File

@ -0,0 +1,114 @@
#include "particle_emitter.hpp"
#include "assets/cache.hpp"
#include "utils/random.hpp"
game::view::ParticleEmitter::ParticleEmitter(audio::Player* audioplayer) : audioplayer_(audioplayer)
{
quad_model_ = assets::CacheManager::GetModel("data/quad.mdl");
}
void game::view::ParticleEmitter::Update(float delta_time)
{
for (auto& particle : particles_)
{
particle.time += delta_time;
particle.velocity.z -= delta_time * particle.gravity;
particle.position += particle.velocity * delta_time;
if (particle.time > particle.fade_start)
{
float opacity = 1.0f - glm::clamp((particle.time - particle.fade_start) / (particle.lifetime - particle.fade_start), 0.0f, 1.0f);
particle.color.a = opacity;
}
}
// erase expired particles
particles_.erase(std::remove_if(particles_.begin(), particles_.end(),
[this](const Particle& particle) { return particle.lifetime < particle.time; }),
particles_.end());
}
void game::view::ParticleEmitter::Draw(const DrawArgs& args)
{
for (auto& particle : particles_)
{
// calc matrixa
auto forward = args.eye - particle.position;
auto right = glm::normalize(glm::cross(forward, glm::vec3(0.0f, 0.0f, 1.0f)));
auto up = normalize(glm::cross(right, forward));
particle.matrix = glm::rotate(glm::mat4(
glm::vec4(right * particle.size, 0.0f),
glm::vec4(forward, 0.0f),
glm::vec4(up * particle.size, 0.0f),
glm::vec4(particle.position, 1.0f)
), particle.rotation, glm::vec3(0.0f, 1.0f, 0.0f));
gfx::DrawSurfaceCmd cmd{};
cmd.surface = &particle.surface;
cmd.matrices = &particle.matrix;
cmd.color = &particle.color;
cmd.dist = glm::dot(forward, forward);
args.dlist.AddSurface(cmd);
}
}
void game::view::ParticleEmitter::Emit(const std::shared_ptr<const assets::Effect>& fx, const glm::vec3& pos,
const glm::vec3& dir)
{
// spawn particles
for (const auto& def : fx->GetParticleDefs())
{
auto count = RandomInt(def.count_min, def.count_max);
if (count <= 0)
continue;
// steal a quad surface from quad model
gfx::Surface surface = quad_model_->GetMesh()->surfaces[0];
surface.texture = def.texture;
surface.sflags = gfx::SF_2SIDED | gfx::SF_OBJECT_COLOR | gfx::SF_OBJECT_COLOR_MULT;
// setup blending
if (def.blend == assets::PTB_BLEND_NORMAL)
surface.sflags |= gfx::SF_BLEND;
else if (def.blend == assets::PTB_BLEND_ADDITIVE)
surface.sflags |= gfx::SF_BLEND | gfx::SF_BLEND_ADDITIVE | gfx::SF_UNLIT;
for (int i = 0; i < count; ++i)
{
auto& particle = particles_.emplace_back();
particle.surface = surface;
particle.time = 0.0f;
particle.position = pos;
particle.rotation = RandomFloat(0.0f, glm::two_pi<float>());
particle.size = RandomFloat(def.size_min, def.size_max);
float dispersion = RandomFloat(0.0f, def.max_dispersion);
float speed = RandomFloat(def.velocity_min, def.velocity_max);
particle.velocity = ApplyRandomDispersion(dir, dispersion) * speed;
particle.gravity = RandomFloat(def.gravity_min, def.gravity_max);
particle.lifetime = RandomFloat(def.lifetime_min, def.lifetime_max);
particle.fade_start = particle.lifetime - RandomFloat(def.fadetime_min, def.fadetime_max);
particle.color = glm::vec4(1.0f);
}
}
// play sounds
const auto& sounds = fx->GetSounds();
if (audioplayer_ && sounds.size() > 0)
{
auto sound_idx = rand() % sounds.size();
auto snd = audioplayer_->PlaySound(sounds[sound_idx], nullptr);
snd->SetPosition(pos);
snd->SetVolume(RandomFloat(0.9f, 1.1f));
// snd->SetPitch(RandomFloat(0.9f, 1.1f));
}
}

View File

@ -0,0 +1,51 @@
#pragma once
#include "assets/effect.hpp"
#include "assets/model.hpp"
#include "draw_args.hpp"
#include "audio/player.hpp"
namespace game::view
{
struct Particle
{
std::shared_ptr<const assets::Effect> fx; // to keep resources alive
gfx::Surface surface;
// instance specific
float time;
glm::vec3 position;
float rotation;
float size;
glm::vec3 velocity;
float gravity;
float lifetime;
float fade_start;
// for drawing
glm::mat4 matrix;
glm::vec4 color;
};
class ParticleEmitter
{
public:
ParticleEmitter(audio::Player* audioplayer);
void Update(float delta_time);
void Draw(const DrawArgs& args);
void Emit(const std::shared_ptr<const assets::Effect>& fx, const glm::vec3& pos, const glm::vec3& dir);
private:
audio::Player* audioplayer_;
std::shared_ptr<const assets::Model> quad_model_; // to steal quad VAO from
std::vector<Particle> particles_;
};
}

View File

@ -11,7 +11,7 @@
#include "net/utils.hpp"
game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) :
session_(session), audiomaster_(session_.GetAudioMaster())
session_(session), audiomaster_(session_.GetAudioMaster()), audioplayer_(audiomaster_), emitter_(&audioplayer_)
{
net::MapName mapname;
if (!msg.Read(mapname))
@ -73,6 +73,9 @@ bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& ms
case net::MSG_BEAM:
return ProcessBeamMsg(msg);
case net::MSG_FX:
return ProcessFxMsg(msg);
default:
return false;
}
@ -92,9 +95,11 @@ void game::view::WorldView::Update(const UpdateInfo& info)
UpdateEnv();
UpdateBeams();
audioplayer_.Update();
emitter_.Update(info.delta_time);
}
void game::view::WorldView::Draw(const DrawArgs& args) const
void game::view::WorldView::Draw(const DrawArgs& args)
{
if (!map_->IsLoaded())
{
@ -113,6 +118,7 @@ void game::view::WorldView::Draw(const DrawArgs& args) const
}
DrawBeams(args);
emitter_.Draw(args);
}
game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum)
@ -334,6 +340,23 @@ bool game::view::WorldView::ProcessBeamMsg(net::InMessage& msg)
return true;
}
bool game::view::WorldView::ProcessFxMsg(net::InMessage& msg)
{
net::ModelName name;
glm::vec3 pos, dir;
if (!msg.Read(name) || !net::ReadPosition(msg, pos) || !msg.Read<net::DirQ>(dir.x) || !msg.Read<net::DirQ>(dir.y) ||
!msg.Read<net::DirQ>(dir.z))
return false;
if (glm::length2(dir) < 0.3f)
return true;// weird
emitter_.Emit(assets::CacheManager::GetEffect("data/" + std::string(name) + ".fx"), pos, glm::normalize(dir));
return true;
}
void game::view::WorldView::Cache(std::any val)
{
cache_.emplace_back(std::move(val));

View File

@ -10,6 +10,7 @@
#include "entityview.hpp"
#include "mapinstanceview.hpp"
#include "worldenv.hpp"
#include "particle_emitter.hpp"
namespace game::view
{
@ -33,7 +34,7 @@ public:
bool ProcessMsg(net::MessageType type, net::InMessage& msg);
void Update(const UpdateInfo& info);
void Draw(const DrawArgs& args) const;
void Draw(const DrawArgs& args);
EntityView* GetEntity(net::EntNum entnum);
@ -56,6 +57,7 @@ private:
bool ProcessEntDestroyMsg(net::InMessage& msg);
bool ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable);
bool ProcessBeamMsg(net::InMessage& msg);
bool ProcessFxMsg(net::InMessage& msg);
void Cache(std::any val);
@ -76,10 +78,14 @@ private:
float env_msg_time_ = 0.0f;
audio::Master& audiomaster_;
audio::Player audioplayer_; // for non-entity sounds
std::vector<std::any> cache_;
std::vector<BeamView> beams_;
ParticleEmitter emitter_;
};
} // namespace game::view

View File

@ -66,6 +66,9 @@ enum MessageType : uint8_t
// BEAM <Position start> <Position end> <Color> <BeamTime>
MSG_BEAM,
// FX <ModelName effect> <Position> <Dir x> <Dir y> <Dir z>
MSG_FX,
/*~~~~~~~~~~~~~~~~*/
MSG_COUNT,
};
@ -199,4 +202,6 @@ using MenuSelectDir = uint8_t; // 0=left, 1=right
using BeamTimeQ = Quantized<uint8_t, 0, 10>;
using DirQ = Quantized<uint8_t, -1, 1>;
} // namespace net

View File

@ -6,3 +6,19 @@ inline float RandomFloat(float min, float max)
{
return min + (max - min) * static_cast<float>(rand() % 100) * 0.01f;
}
inline int RandomInt(int min, int max)
{
return min + rand() % (max - min + 1);
}
inline glm::vec3 ApplyRandomDispersion(const glm::vec3& dir, float dispersion)
{
float rand_rotation = RandomFloat(0.0f, glm::radians(360.0f));
float rand_dispersion = RandomFloat(0.0f, 0.01f) * dispersion; // dispersion in m/100m
auto right = glm::normalize(glm::cross(dir, glm::vec3(0.0f, 0.0f, 1.0f)));
auto up = glm::normalize(glm::cross(right, dir));
return dir + (right * glm::sin(rand_rotation) + up * glm::cos(rand_rotation)) * rand_dispersion;
}