Some sound effects

This commit is contained in:
tovjemam 2026-02-28 19:35:14 +01:00
parent 986cbc12a6
commit 55efcceaf0
28 changed files with 318 additions and 82 deletions

View File

@ -13,6 +13,8 @@
#include "gfx/font.hpp"
#endif
#include <iostream>
namespace assets
{
@ -33,6 +35,7 @@ public:
}
}
std::cout << "loading " << key << "..." << std::endl;
PtrType obj = Load(key);
cache_[key] = obj; // Cache the loaded object
return obj;

View File

@ -180,13 +180,16 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
iss >> com.x >> com.y >> com.z;
model->col_offset_ = com;
}
else if (command == "param")
{
std::string key, val;
iss >> key >> val;
model->params_[key] = val;
}
else
{
throw std::runtime_error("Unknown command in model file: " + command);
}
// TODO: skeleton
});
CLIENT_ONLY(
@ -215,3 +218,29 @@ std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::stri
return model;
}
const std::string* assets::Model::GetParam(const std::string& key) const
{
auto it = params_.find(key);
if (it == params_.end())
return nullptr;
return &it->second;
}
bool assets::Model::GetParamFloat(const std::string& key, float& out) const
{
auto str_val = GetParam(key);
if (!str_val)
return false;
std::string str = *str_val;
auto dashpos = str.find(',');
if (dashpos != std::string::npos)
str[dashpos] = '.';
out = std::strtof(str.c_str(), nullptr);
return true;
}

View File

@ -34,7 +34,6 @@ namespace assets
// };
// };
class Model
{
public:
@ -51,6 +50,9 @@ public:
CLIENT_ONLY(const std::shared_ptr<const Mesh>& GetMesh() const { return mesh_; })
const AABB3& GetAABB() const { return aabb_; }
const std::string* GetParam(const std::string& key) const;
bool GetParamFloat(const std::string& key, float& out) const;
private:
std::string name_;
glm::vec3 col_offset_ = glm::vec3(0.0f);
@ -63,6 +65,8 @@ private:
CLIENT_ONLY(std::shared_ptr<const Mesh> mesh_;);
AABB3 aabb_;
std::map<std::string, std::string> params_;
};
}

View File

@ -3,6 +3,8 @@
#include <AL/al.h>
#include <AL/alc.h>
#include <iostream>
audio::SoundSource::SoundSource(Player* player, std::shared_ptr<const Sound> sound)
: Super(sound->GetCategoryName(), player), sound_(std::move(sound))
{
@ -26,6 +28,7 @@ void audio::SoundSource::SetPitch(float pitch)
void audio::SoundSource::SetVolume(float volume)
{
Super::SetSourceVolume(sound_->GetVolume() * volume);
//std::cout << "src volume " << sound_->GetVolume() * volume << std::endl;
}
void audio::SoundSource::Update()

View File

@ -12,9 +12,9 @@ App::App()
std::cout << "Initializing App..." << std::endl;
#ifndef EMSCRIPTEN
audiomaster_.SetMasterVolume(0.2f);
audiomaster_.SetMasterVolume(1.0f);
#else
audiomaster_.SetMasterVolume(0.8f);
audiomaster_.SetMasterVolume(2.0f);
#endif
font_ = assets::CacheManager::GetFont("data/comic32.font");

View File

@ -73,9 +73,9 @@ static void InitSDL()
#ifndef PG_GLES
static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
//if (severity == 0x826b)
// return;
//
if (severity == 0x826b)
return;
////std::cout << message << std::endl;
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
(type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),

View File

@ -0,0 +1,49 @@
#pragma once
#include <cstdint>
#include <btBulletDynamicsCommon.h>
namespace collision
{
enum ObjectType : int
{
OT_UNDEFINED,
OT_MAP_OBJECT,
OT_ENTITY,
};
using ObjectFlags = int;
enum ObjectFlag : ObjectFlags
{
OF_DESTRUCTIBLE = 0x01,
OF_NOTIFY_CONTACT = 0x02,
};
class ObjectCallback
{
public:
ObjectCallback() = default;
virtual void OnContact(float impulse) {}
virtual ~ObjectCallback() = default;
};
inline void SetObjectInfo(btCollisionObject* obj, ObjectType type, ObjectFlags flags, ObjectCallback* callback)
{
obj->setUserIndex(static_cast<int>(type));
obj->setUserIndex2(static_cast<int>(flags));
obj->setUserPointer(callback);
}
inline void GetObjectInfo(const btCollisionObject* obj, ObjectType& type, ObjectFlags& flags, ObjectCallback*& callback)
{
type = static_cast<ObjectType>(obj->getUserIndex());
flags = static_cast<ObjectFlags>(obj->getUserIndex2());
callback = static_cast<ObjectCallback*>(obj->getUserPointer());
}
}

View File

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

View File

@ -1,11 +1,19 @@
#include "destroyed_object.hpp"
#include "utils/random.hpp"
game::DestroyedObject::DestroyedObject(World& world, std::unique_ptr<MapObjectCollision> col)
: Super(world, col->GetModel()->GetName()), col_(std::move(col))
{
// remove after 30s
Schedule(30000, [this]()
auto destr_snd_str = col_->GetModel()->GetParam("destr_snd");
if (destr_snd_str)
{
Schedule(1, [this, destr_snd_str] {
PlaySound(*destr_snd_str, RandomFloat(0.8f, 1.2f), RandomFloat(0.8f, 1.2f));
});
}
// remove after 30s
Schedule(30000, [this] {
Remove();
});
}

View File

@ -1,5 +1,6 @@
#include "drivable_vehicle.hpp"
#include "player_character.hpp"
#include "utils/random.hpp"
game::DrivableVehicle::DrivableVehicle(World& world, std::string model_name, const glm::vec3& color)
: Vehicle(world, std::move(model_name), color)
@ -13,6 +14,8 @@ void game::DrivableVehicle::Use(PlayerCharacter& character, uint32_t target_id)
return;
character.SetVehicle(this, target_id); // seat idx is same as target_id
PlaySound("cardoor", 1.0f, RandomFloat(0.9f, 1.1f));
}
bool game::DrivableVehicle::SetPassenger(uint32_t seat_idx, ControllableCharacter* character)

View File

@ -52,6 +52,14 @@ void game::Entity::Attach(net::EntNum parentnum)
SendAttachMsg();
}
void game::Entity::PlaySound(const std::string& name, float volume, float pitch)
{
auto msg = BeginEntMsg(net::EMSG_PLAYSOUND);
msg.Write(net::SoundName(name));
msg.Write<net::SoundVolumeQ>(volume);
msg.Write<net::SoundPitchQ>(pitch);
}
void game::Entity::WriteNametag(net::OutMessage& msg) const
{
msg.Write(net::NameTag{nametag_});

View File

@ -6,6 +6,7 @@
#include "transform_node.hpp"
#include "utils/defs.hpp"
#include "utils/scheduler.hpp"
#include "collision/object_info.hpp"
namespace game
{
@ -13,7 +14,7 @@ namespace game
class World;
class Player;
class Entity : public net::MsgProducer, public Scheduler
class Entity : public net::MsgProducer, public Scheduler, public collision::ObjectCallback
{
public:
Entity(World& world, net::EntType viewtype);
@ -33,11 +34,14 @@ public:
void Attach(net::EntNum parentnum);
net::EntNum GetParentNum() const { return parentnum_; }
void PlaySound(const std::string& name, float volume = 1.0f, float pitch = 1.0f);
void Remove() { removed_ = true; }
bool IsRemoved() const { return removed_; }
const TransformNode& GetRoot() const { return root_; }
const Transform& GetRootTransform() const { return root_.local; }
float GetMaxDistance() const { return max_distance_; }
virtual ~Entity() = default;

View File

@ -3,7 +3,6 @@
#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))
@ -67,14 +66,13 @@ game::MapObjectCollision::MapObjectCollision(collision::DynamicsWorld& world,
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;
collision::ObjectFlags oflags = 0;
if (destructible)
{
obj_type = collision::OT_MAP_DESTRUCTIBLE;
oflags |= collision::OF_DESTRUCTIBLE;
}
// prefer simple cshape which allow destruction
@ -93,8 +91,8 @@ game::MapObjectCollision::MapObjectCollision(collision::DynamicsWorld& world,
offset_trans.position += trans.rotation * model_->GetColOffset();
body_->setWorldTransform(offset_trans.ToBtTransform());
body_->setUserIndex(static_cast<int>(obj_type));
body_->setUserPointer(this);
collision::SetObjectInfo(body_.get(), collision::OT_MAP_OBJECT, oflags, this);
// world_.GetBtWorld().addRigidBody(body_.get(), btBroadphaseProxy::StaticFilter, btBroadphaseProxy::AllFilter);
world_.GetBtWorld().addRigidBody(body_.get());
@ -119,7 +117,9 @@ void game::MapObjectCollision::Break()
// make new
body_ = std::make_unique<btRigidBody>(btRigidBody::btRigidBodyConstructionInfo(mass, nullptr, shape, local_inertia));
body_->setWorldTransform(trans);
body_->setUserIndex(static_cast<int>(collision::OT_UNDEFINED));
collision::SetObjectInfo(body_.get(), collision::OT_UNDEFINED, 0, this);
world_.GetBtWorld().addRigidBody(body_.get());
}

View File

@ -3,6 +3,7 @@
#include "assets/map.hpp"
#include "collision/dynamicsworld.hpp"
#include "net/defs.hpp"
#include "collision/object_info.hpp"
namespace game
{
@ -14,7 +15,7 @@ enum MapObjectCollisionFlag : MapObjectCollisionFlags
MAPOBJ_DESTRUCTIBLE = 0x01,
};
class MapObjectCollision
class MapObjectCollision : public collision::ObjectCallback
{
public:
MapObjectCollision(collision::DynamicsWorld& world, std::shared_ptr<const assets::Model> model,
@ -29,8 +30,7 @@ public:
btRigidBody& GetBtBody() { return *body_; }
net::ObjNum GetNum() const { return num_; }
~MapObjectCollision();
virtual ~MapObjectCollision() override;
private:
collision::DynamicsWorld& world_;

View File

@ -82,10 +82,9 @@ void game::OpenWorld::DestructibleDestroyed(net::ObjNum num, std::unique_ptr<Map
{
auto& destroyed_obj = Spawn<DestroyedObject>(std::move(col));
// Schedule(100000, [this, objnum = num]()
// {
// RespawnObj(objnum);
// });
Schedule(10000, [this, num] {
RespawnObj(num);
});
}
std::optional<std::pair<game::Usable&, const game::UseTarget&>> game::OpenWorld::GetBestUseTarget(const glm::vec3& pos) const

View File

@ -4,6 +4,7 @@
#include "net/utils.hpp"
#include "player.hpp"
#include "player_input.hpp"
#include "utils/random.hpp"
#include <iostream>
@ -33,6 +34,8 @@ game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& co
body_ = std::make_unique<btRigidBody>(rb_info);
body_->setActivationState(DISABLE_DEACTIVATION);
collision::SetObjectInfo(body_.get(), collision::OT_ENTITY, collision::OF_NOTIFY_CONTACT, this);
// setup vehicle
btRaycastVehicle::btVehicleTuning tuning;
vehicle_ = std::make_unique<btRaycastVehicle>(tuning, body_.get(), &world_.GetVehicleRaycaster());
@ -100,6 +103,7 @@ void game::Vehicle::Update()
root_.UpdateMatrix();
flags_ = 0;
UpdateCrash();
ProcessInput();
UpdateWheels();
@ -123,6 +127,27 @@ void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const
msg.WriteAt(fields_pos, fields);
}
void game::Vehicle::OnContact(float impulse)
{
Super::OnContact(impulse);
crash_intensity_ += impulse;
if (impulse < 1000.0f)
return;
if (window_health_ > 0.0f)
{
window_health_ -= impulse;
if (window_health_ <= 0.0f) // just broken
{
PlaySound("breakwindow", 1.0f, 1.0f);
}
}
}
void game::Vehicle::SetInput(VehicleInputType type, bool enable)
{
if (enable)
@ -293,6 +318,47 @@ void game::Vehicle::ProcessInput()
flags_ |= VF_BREAKING;
}
void game::Vehicle::UpdateCrash()
{
if (window_health_ <= 0.0f)
flags_ |= VF_BROKENWINDOWS;
if (no_crash_frames_)
{
--no_crash_frames_;
}
else
{
if (crash_intensity_ > 1000.0f)
{
float volume = RandomFloat(0.9f, 1.2f);
float pitch = RandomFloat(1.0f, 1.3f);
if (crash_intensity_ > 12000.0f)
{
volume *= 1.7f;
pitch *= 0.8f;
}
if (crash_intensity_ > 4000.0f)
{
volume *= 1.3f;
pitch *= 0.8f;
}
else
{
volume *= 0.8f;
pitch *= 1.2f;
}
PlaySound("crash", volume, pitch);
no_crash_frames_ = 7 + rand() % 10;
}
}
crash_intensity_ = 0.0f;
}
void game::Vehicle::UpdateWheels()
{
for (size_t i = 0; i < num_wheels_; ++i)

View File

@ -40,6 +40,8 @@ public:
virtual void Update() override;
virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
virtual void OnContact(float impulse) override;
void SetInput(VehicleInputType type, bool enable);
void SetInputs(VehicleInputFlags inputs) { in_ = inputs; }
@ -59,6 +61,7 @@ public:
private:
void ProcessInput();
void UpdateCrash();
void UpdateWheels();
void UpdateSyncState();
VehicleSyncFieldFlags WriteState(net::OutMessage& msg, const VehicleSyncState& base) const;
@ -86,6 +89,11 @@ private:
size_t sync_current_ = 0;
VehicleInputFlags in_ = 0;
float window_health_ = 10000.0f;
float crash_intensity_ = 0.0f;
size_t no_crash_frames_ = 0;
};
} // namespace game

View File

@ -14,8 +14,9 @@ using VehicleFlags = uint8_t;
enum VehicleFlag : VehicleFlags
{
VF_NONE,
VF_ACCELERATING = 1,
VF_BREAKING = 2,
VF_ACCELERATING = 0x01,
VF_BREAKING = 0x02,
VF_BROKENWINDOWS = 0x04,
};
struct VehicleSyncState

View File

@ -5,7 +5,7 @@
#include "assets/cache.hpp"
#include "utils/allocnum.hpp"
#include "collision/object_type.hpp"
#include "collision/object_info.hpp"
game::World::World(std::string mapname) : Scheduler(time_ms_), map_(*this, std::move(mapname))
{
@ -49,7 +49,7 @@ 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();
HandleContacts();
RunTasks();
@ -96,7 +96,7 @@ void game::World::RespawnObj(net::ObjNum objnum)
}
}
void game::World::DetectDestructibleCollisions()
void game::World::HandleContacts()
{
auto& bt_world = GetBtWorld();
int numManifolds = bt_world.getDispatcher()->getNumManifolds();
@ -104,41 +104,48 @@ void game::World::DetectDestructibleCollisions()
static std::vector<net::ObjNum> to_destroy;
to_destroy.clear();
auto ProcessContact = [&](btRigidBody* body, btRigidBody* other_body, btManifoldPoint& pt) {
collision::ObjectType type;
collision::ObjectFlags flags;
collision::ObjectCallback* cb;
collision::GetObjectInfo(body, type, flags, cb);
if (cb && (flags & collision::OF_NOTIFY_CONTACT))
{
cb->OnContact(pt.getAppliedImpulse());
}
if (type == collision::OT_MAP_OBJECT && (flags & collision::OF_DESTRUCTIBLE))
{
auto col = dynamic_cast<MapObjectCollision*>(cb);
if (!col)
return;
const float break_threshold = 100.0f; // TODO: per-object threshold
if (pt.getAppliedImpulse() > break_threshold)
{
to_destroy.push_back(col->GetNum());
other_body->applyCentralImpulse(pt.m_normalWorldOnB * pt.getAppliedImpulse() * 0.5f);
}
return;
}
};
// 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;
btRigidBody* body0 = const_cast<btRigidBody*>(static_cast<const btRigidBody*>(contactManifold->getBody0()));
btRigidBody* body1 = const_cast<btRigidBody*>(static_cast<const btRigidBody*>(contactManifold->getBody1()));
for (int j = 0; j < contactManifold->getNumContacts(); j++)
{
const float break_threshold = 100.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);
}
ProcessContact(body0, body1, pt);
ProcessContact(body1, body0, pt);
}
}

View File

@ -56,7 +56,7 @@ public:
virtual ~World() = default;
private:
void DetectDestructibleCollisions();
void HandleContacts();
void DestroyObject(net::ObjNum objnum);

View File

@ -23,6 +23,8 @@ bool game::view::EntityView::ProcessMsg(net::EntMsgType type, net::InMessage& ms
return ReadNametag(msg);
case net::EMSG_ATTACH:
return ReadAttach(msg);
case net::EMSG_PLAYSOUND:
return ProcessPlaySoundMsg(msg);
default:
return false;
}
@ -82,6 +84,21 @@ bool game::view::EntityView::ReadAttach(net::InMessage& msg)
return msg.Read(parentnum_);
}
bool game::view::EntityView::ProcessPlaySoundMsg(net::InMessage& msg)
{
net::SoundName name;
float volume, pitch;
if (!msg.Read(name) || !msg.Read<net::SoundVolumeQ>(volume) || !msg.Read<net::SoundPitchQ>(pitch))
return false;
auto sound = assets::CacheManager::GetSound("data/" + std::string(name) + ".snd");
auto snd = audioplayer_.PlaySound(sound, &root_.local.position);
snd->SetVolume(volume);
snd->SetPitch(pitch);
return true;
}
void game::view::EntityView::DrawNametag(const DrawArgs& args)
{
if (nametag_.empty())

View File

@ -50,6 +50,7 @@ public:
private:
bool ReadNametag(net::InMessage& msg);
bool ReadAttach(net::InMessage& msg);
bool ProcessPlaySoundMsg(net::InMessage& msg);
void DrawNametag(const DrawArgs& args);
void DrawAxes(const DrawArgs& args);

View File

@ -15,7 +15,7 @@ game::view::VehicleView::VehicleView(WorldView& world, net::InMessage& msg)
throw EntityInitError();
model_ = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh");
mesh_ = *model_->GetModel()->GetMesh();
auto& modelwheels = model_->GetWheels();
wheels_.resize(modelwheels.size());
@ -102,17 +102,26 @@ void game::view::VehicleView::Update(const UpdateInfo& info)
snd_accel_src_->Delete();
snd_accel_src_ = nullptr;
}
// update windows
if ((flags_ & VF_BROKENWINDOWS) && !windows_broken_)
{
windows_broken_ = true;
auto it = mesh_.surface_names.find("carwindows");
if (it != mesh_.surface_names.end())
{
size_t idx = it->second;
mesh_.surfaces[idx].texture = assets::CacheManager::GetTexture("data/carbrokenwindows.png");
}
}
}
void game::view::VehicleView::Draw(const DrawArgs& args)
{
Super::Draw(args);
// TOOD: chceck and fix
const auto& model = *model_->GetModel();
const auto& mesh = *model.GetMesh();
for (const auto& surface : mesh.surfaces)
for (const auto& surface : mesh_.surfaces)
{
gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface;

View File

@ -36,6 +36,7 @@ private:
private:
std::shared_ptr<const assets::VehicleModel> model_;
assets::Mesh mesh_;
glm::vec4 color_;
game::VehicleSyncState sync_;
@ -48,6 +49,8 @@ private:
std::shared_ptr<const audio::Sound> snd_accel_;
audio::SoundSource* snd_accel_src_ = nullptr;
bool windows_broken_ = false;
};
}

View File

@ -27,6 +27,12 @@ game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) :
map_.EnableObj(objnum, false);
}
// cache common snds and stuff
Cache(assets::CacheManager::GetSound("data/breaksign.snd"));
Cache(assets::CacheManager::GetSound("data/breakpatnik.snd"));
Cache(assets::CacheManager::GetSound("data/crash.snd"));
Cache(assets::CacheManager::GetSound("data/breakwindow.snd"));
}
bool game::view::WorldView::ProcessMsg(net::MessageType type, net::InMessage& msg)
@ -185,3 +191,8 @@ bool game::view::WorldView::ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, b
map_.EnableObj(objnum, enable);
return true;
}
void game::view::WorldView::Cache(std::any val)
{
cache_.emplace_back(std::move(val));
}

View File

@ -1,5 +1,7 @@
#pragma once
#include <any>
#include "assets/map.hpp"
#include "draw_args.hpp"
#include "net/defs.hpp"
@ -38,6 +40,8 @@ private:
bool ProcessObjDestroyOrRespawnMsg(net::InMessage& msg, bool enable);
void Cache(std::any val);
private:
ClientSession& session_;
@ -47,6 +51,8 @@ private:
float time_ = 0.0f;
audio::Master& audiomaster_;
std::vector<std::any> cache_;
};
} // namespace game::view

View File

@ -86,6 +86,7 @@ enum EntMsgType : uint8_t
EMSG_NAMETAG,
EMSG_ATTACH,
EMSG_UPDATE,
EMSG_PLAYSOUND,
};
using PositionElemQ = Quantized<uint32_t, -10000, 10000, 1>;
@ -110,6 +111,10 @@ using ColorQ = Quantized<uint8_t, 0, 1>;
using NameTag = FixedStr<64>;
using SoundName = FixedStr<64>;
using SoundVolumeQ = Quantized<uint8_t, 0, 2>;
using SoundPitchQ = Quantized<uint8_t, 0, 2>;
using AnimBlendQ = Quantized<uint8_t, 0, 1>;
using AnimTimeQ = Quantized<uint8_t, 0, 1>;

8
src/utils/random.hpp Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <cstdlib>
inline float RandomFloat(float min, float max)
{
return min + (max - min) * static_cast<float>(rand() % 100) * 0.01f;
}