Physics materials & FX
This commit is contained in:
parent
c549890d2c
commit
8c52678e79
@ -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"
|
||||
|
||||
@ -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_;)
|
||||
|
||||
@ -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
78
src/assets/effect.cpp
Normal 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
55
src/assets/effect.hpp
Normal 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_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
47
src/collision/shape_info.hpp
Normal file
47
src/collision/shape_info.hpp
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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_);
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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()
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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_;
|
||||
|
||||
114
src/gameview/particle_emitter.cpp
Normal file
114
src/gameview/particle_emitter.cpp
Normal 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));
|
||||
}
|
||||
}
|
||||
51
src/gameview/particle_emitter.hpp
Normal file
51
src/gameview/particle_emitter.hpp
Normal 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_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@ -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));
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user