Basic vehicle tuning

This commit is contained in:
tovjemam 2026-03-20 20:36:33 +01:00
parent 1c136bcc74
commit 627beffc65
22 changed files with 248 additions and 46 deletions

View File

@ -18,6 +18,8 @@ set(COMMON_SOURCES
"src/assets/model.cpp" "src/assets/model.cpp"
"src/assets/skeleton.hpp" "src/assets/skeleton.hpp"
"src/assets/skeleton.cpp" "src/assets/skeleton.cpp"
"src/assets/vehicle_tuning_list.hpp"
"src/assets/vehicle_tuning_list.cpp"
"src/assets/vehiclemdl.hpp" "src/assets/vehiclemdl.hpp"
"src/assets/vehiclemdl.cpp" "src/assets/vehiclemdl.cpp"
"src/collision/dynamicsworld.hpp" "src/collision/dynamicsworld.hpp"

View File

@ -23,3 +23,30 @@ void assets::LoadCMDFile(const std::string& filename,
} }
} }
std::string assets::ParseString(std::istringstream& iss)
{
std::string str;
iss >> str;
if (!str.starts_with('"'))
return str;
str = str.substr(1);
while (iss)
{
std::string tmp;
iss >> tmp;
str += ' ';
str += tmp;
if (str.ends_with('"'))
{
str.pop_back();
break;
}
}
return str;
}

View File

@ -21,4 +21,6 @@ inline void ParseTransform(std::istringstream& iss, Transform& trans)
iss >> trans.scale; iss >> trans.scale;
} }
std::string ParseString(std::istringstream& iss);
} // namespace assets } // namespace assets

View File

@ -0,0 +1,24 @@
#include "vehicle_tuning_list.hpp"
#include "cmdfile.hpp"
std::unique_ptr<const assets::VehicleTuningList> assets::VehicleTuningList::LoadFromFile(const std::string& filename)
{
auto tuninglist = std::make_unique<VehicleTuningList>();
if (!fs::FileExists(filename))
return tuninglist; // empty
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
if (command == "wheel")
{
VehicleWheelPreset wheel{};
wheel.displayname = ParseString(iss);
iss >> wheel.price >> wheel.model;
tuninglist->wheels.emplace_back(wheel);
}
});
return tuninglist;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
namespace assets
{
struct VehicleWheelPreset
{
uint32_t price;
std::string displayname;
std::string model;
};
struct VehicleTuningList
{
std::vector<VehicleWheelPreset> wheels;
static std::unique_ptr<const VehicleTuningList> LoadFromFile(const std::string& filename);
};
}

View File

@ -48,6 +48,9 @@ std::shared_ptr<const assets::VehicleModel> assets::VehicleModel::LoadFromFile(c
} }
}); });
// tuning list
veh->tuninglist_ = VehicleTuningList::LoadFromFile(filename + ".tun");
return veh; return veh;
} }

View File

@ -2,6 +2,7 @@
#include "model.hpp" #include "model.hpp"
#include "utils/transform.hpp" #include "utils/transform.hpp"
#include "vehicle_tuning_list.hpp"
namespace assets namespace assets
{ {
@ -35,11 +36,13 @@ public:
const std::shared_ptr<const Model>& GetModel() const { return basemodel_; } const std::shared_ptr<const Model>& GetModel() const { return basemodel_; }
const std::vector<VehicleWheel>& GetWheels() const { return wheels_; } const std::vector<VehicleWheel>& GetWheels() const { return wheels_; }
const Transform* GetLocation(const std::string& name) const; const Transform* GetLocation(const std::string& name) const;
const VehicleTuningList& GetTuningList() const { return *tuninglist_; }
private: private:
std::shared_ptr<const Model> basemodel_; std::shared_ptr<const Model> basemodel_;
std::vector<VehicleWheel> wheels_; std::vector<VehicleWheel> wheels_;
std::map<std::string, Transform> locations_; std::map<std::string, Transform> locations_;
std::unique_ptr<const VehicleTuningList> tuninglist_;
}; };

View File

@ -2,8 +2,7 @@
#include "player_character.hpp" #include "player_character.hpp"
#include "utils/random.hpp" #include "utils/random.hpp"
game::DrivableVehicle::DrivableVehicle(World& world, std::string model_name, const glm::vec3& color) game::DrivableVehicle::DrivableVehicle(World& world, const VehicleTuning& tuning) : Vehicle(world, tuning)
: Vehicle(world, std::move(model_name), color)
{ {
InitSeats(); InitSeats();
} }

View File

@ -16,7 +16,7 @@ struct VehicleSeat
class DrivableVehicle : public Vehicle, public Usable class DrivableVehicle : public Vehicle, public Usable
{ {
public: public:
DrivableVehicle(World& world, std::string model_name, const glm::vec3& color); DrivableVehicle(World& world, const VehicleTuning& tuning);
virtual void Use(PlayerCharacter& character, uint32_t target_id) override; virtual void Use(PlayerCharacter& character, uint32_t target_id) override;

View File

@ -36,6 +36,14 @@ static glm::vec3 GetRandomColor()
return color; return color;
} }
static uint32_t GetRandomColor24()
{
uint8_t r,g,b;
r = rand() % 256;
g = rand() % 256;
b = rand() % 256;
return (b << 16) | (g << 8) | r;
}
game::OpenWorld::OpenWorld() : World("openworld") game::OpenWorld::OpenWorld() : World("openworld")
{ {
@ -47,7 +55,14 @@ game::OpenWorld::OpenWorld() : World("openworld")
SpawnBot(); SpawnBot();
} }
auto& veh = Spawn<game::DrivableVehicle>("twingo", glm::vec3{1.0f, 0.8f, 0.1f}); // initial twingo
VehicleTuning twingo_tuning;
twingo_tuning.model = "twingo";
twingo_tuning.primary_color = 0x0077FF;
twingo_tuning.wheels_idx = 1; // enkei
twingo_tuning.wheel_color = 0x00FF00;
auto& veh = Spawn<game::DrivableVehicle>(twingo_tuning);
veh.SetPosition({110.0f, 100.0f, 5.0f}); veh.SetPosition({110.0f, 100.0f, 5.0f});
constexpr size_t in_row = 20; constexpr size_t in_row = 20;
@ -59,7 +74,7 @@ game::OpenWorld::OpenWorld() : World("openworld")
size_t row = i / in_row; size_t row = i / in_row;
glm::vec3 pos(62.0f + static_cast<float>(col) * 4.0f, 165.0f + static_cast<float>(row) * 7.0f, 7.0f); glm::vec3 pos(62.0f + static_cast<float>(col) * 4.0f, 165.0f + static_cast<float>(row) * 7.0f, 7.0f);
auto& veh = Spawn<game::DrivableVehicle>(GetRandomCarModel(), GetRandomColor()); auto& veh = SpawnRandomVehicle();
veh.SetPosition(pos); veh.SetPosition(pos);
}); });
} }
@ -157,29 +172,32 @@ void game::OpenWorld::RemovePlayerCharacter(Player& player)
} }
} }
static game::DrivableVehicle& SpawnRandomVehicle(game::World& world) game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle()
{ {
auto roads = world.GetMap().GetGraph("roads"); game::VehicleTuning tuning;
tuning.model = GetRandomCarModel();
tuning.primary_color = GetRandomColor24();
if (!roads) auto& vehicle = Spawn<game::DrivableVehicle>(tuning);
{
throw std::runtime_error("SpawnRandomVehicle: no roads graph in map");
}
size_t start_node = rand() % roads->nodes.size();
// auto color = glm::vec3{0.3f, 0.3f, 0.3f};
auto color = GetRandomColor();
auto& vehicle = world.Spawn<game::DrivableVehicle>(GetRandomCarModel(), color);
// vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")"); // vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")");
vehicle.SetPosition(roads->nodes[start_node].position + glm::vec3{0.0f, 0.0f, 5.0f});
return vehicle; return vehicle;
} }
void game::OpenWorld::SpawnBot() void game::OpenWorld::SpawnBot()
{ {
auto& vehicle = SpawnRandomVehicle(*this); auto roads = GetMap().GetGraph("roads");
auto& driver = SpawnRandomCharacter<NpcCharacter>(*this);
if (!roads)
{
throw std::runtime_error("SpawnBot: no roads graph in map");
}
size_t start_node = rand() % roads->nodes.size();
auto& vehicle = SpawnRandomVehicle();
vehicle.SetPosition(roads->nodes[start_node].position + glm::vec3{0.0f, 0.0f, 5.0f});
auto& driver = SpawnRandomCharacter<NpcCharacter>(*this);
driver.SetVehicle(&vehicle, 0); driver.SetVehicle(&vehicle, 0);
} }

View File

@ -4,6 +4,7 @@
#include "vehicle.hpp" #include "vehicle.hpp"
#include "character.hpp" #include "character.hpp"
#include "usable.hpp" #include "usable.hpp"
#include "drivable_vehicle.hpp"
#include <optional> #include <optional>
@ -31,6 +32,7 @@ private:
void CreatePlayerCharacter(Player& player); void CreatePlayerCharacter(Player& player);
void RemovePlayerCharacter(Player& player); void RemovePlayerCharacter(Player& player);
game::DrivableVehicle& SpawnRandomVehicle();
void SpawnBot(); void SpawnBot();
private: private:

View File

@ -13,10 +13,9 @@ static std::shared_ptr<const assets::VehicleModel> LoadVehicleModelByName(const
return assets::CacheManager::GetVehicleModel("data/" + model_name + ".veh"); return assets::CacheManager::GetVehicleModel("data/" + model_name + ".veh");
} }
game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& color) game::Vehicle::Vehicle(World& world, const VehicleTuning& tuning)
: Entity(world, net::ET_VEHICLE), model_name_(model_name), model_(LoadVehicleModelByName(model_name)), : Entity(world, net::ET_VEHICLE), tuning_(tuning), model_(LoadVehicleModelByName(tuning.model)),
motion_(root_.local), motion_(root_.local)
color_(color)
{ {
root_.local.position.z = 10.0f; root_.local.position.z = 10.0f;
@ -37,8 +36,8 @@ game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& co
collision::SetObjectInfo(body_.get(), collision::OT_ENTITY, collision::OF_NOTIFY_CONTACT, this); collision::SetObjectInfo(body_.get(), collision::OT_ENTITY, collision::OF_NOTIFY_CONTACT, this);
// setup vehicle // setup vehicle
btRaycastVehicle::btVehicleTuning tuning; btRaycastVehicle::btVehicleTuning bt_tuning;
vehicle_ = std::make_unique<collision::RaycastVehicle>(tuning, body_.get(), &world_.GetVehicleRaycaster()); vehicle_ = std::make_unique<collision::RaycastVehicle>(bt_tuning, body_.get(), &world_.GetVehicleRaycaster());
vehicle_->setCoordinateSystem(0, 2, 1); vehicle_->setCoordinateSystem(0, 2, 1);
// setup wheels // setup wheels
@ -76,7 +75,7 @@ game::Vehicle::Vehicle(World& world, std::string model_name, const glm::vec3& co
btVector3 wheel_pos(wheeldef.position.x, wheeldef.position.y, wheeldef.position.z + wheel_z_offset_); btVector3 wheel_pos(wheeldef.position.x, wheeldef.position.y, wheeldef.position.z + wheel_z_offset_);
auto& wi = vehicle_->addWheel(wheel_pos, wheelDirectionCS0, wheelAxleCS, suspensionRestLength, wheelRadius, auto& wi = vehicle_->addWheel(wheel_pos, wheelDirectionCS0, wheelAxleCS, suspensionRestLength, wheelRadius,
tuning, is_front); bt_tuning, is_front);
wi.m_suspensionStiffness = suspensionStiffness; wi.m_suspensionStiffness = suspensionStiffness;
@ -126,10 +125,9 @@ void game::Vehicle::SendInitData(Player& player, net::OutMessage& msg) const
{ {
Super::SendInitData(player, msg); Super::SendInitData(player, msg);
net::ModelName name(model_name_); msg.Write(net::ModelName(tuning_.model));
msg.Write(name); WriteTuning(msg);
net::WriteRGB(msg, color_); // primary color
// write state against default // write state against default
static const VehicleSyncState default_state; static const VehicleSyncState default_state;
size_t fields_pos = msg.Reserve<VehicleSyncFieldFlags>(); size_t fields_pos = msg.Reserve<VehicleSyncFieldFlags>();
@ -553,3 +551,12 @@ void game::Vehicle::SendDeformMsg(const net::PositionQ& pos, const net::Position
net::WritePositionQ(msg, pos); net::WritePositionQ(msg, pos);
net::WritePositionQ(msg, deform); net::WritePositionQ(msg, deform);
} }
void game::Vehicle::WriteTuning(net::OutMessage& msg) const
{
net::WriteRGB(msg, tuning_.primary_color);
// wheels
msg.Write<net::TuningPartIdx>(tuning_.wheels_idx);
net::WriteRGB(msg, tuning_.wheel_color);
}

View File

@ -10,6 +10,7 @@
#include "world.hpp" #include "world.hpp"
#include "vehicle_sync.hpp" #include "vehicle_sync.hpp"
#include "deform_grid.hpp" #include "deform_grid.hpp"
#include "vehicle_tuning.hpp"
namespace game namespace game
{ {
@ -37,7 +38,7 @@ class Vehicle : public Entity
public: public:
using Super = Entity; using Super = Entity;
Vehicle(World& world, std::string model_name, const glm::vec3& color); Vehicle(World& world, const VehicleTuning& tuning);
virtual void Update() override; virtual void Update() override;
virtual void SendInitData(Player& player, net::OutMessage& msg) const override; virtual void SendInitData(Player& player, net::OutMessage& msg) const override;
@ -55,9 +56,8 @@ public:
void SetSteering(bool analog, float value = 0.0f); void SetSteering(bool analog, float value = 0.0f);
const std::string& GetModelName() const { return model_name_; } const std::string& GetModelName() const { return tuning_.model; }
const std::shared_ptr<const assets::VehicleModel>& GetModel() const { return model_; } const std::shared_ptr<const assets::VehicleModel>& GetModel() const { return model_; }
const glm::vec3& GetColor() const { return color_; }
virtual ~Vehicle(); virtual ~Vehicle();
@ -74,10 +74,11 @@ private:
void Deform(const glm::vec3& pos, const glm::vec3& deform, float radius); void Deform(const glm::vec3& pos, const glm::vec3& deform, float radius);
void SendDeformMsg(const net::PositionQ& pos, const net::PositionQ& deform); void SendDeformMsg(const net::PositionQ& pos, const net::PositionQ& deform);
void WriteTuning(net::OutMessage& msg) const;
private: private:
std::string model_name_; VehicleTuning tuning_;
std::shared_ptr<const assets::VehicleModel> model_; std::shared_ptr<const assets::VehicleModel> model_;
glm::vec3 color_;
collision::MotionState motion_; collision::MotionState motion_;
std::unique_ptr<btRigidBody> body_; std::unique_ptr<btRigidBody> body_;

View File

@ -0,0 +1,19 @@
#pragma once
#include <string>
namespace game
{
struct VehicleTuning
{
std::string model;
uint32_t primary_color = 0xFFFFFFFF;
size_t wheels_idx = 0;
uint32_t wheel_color = 0xFFFFFFFF;
};
}

View File

@ -12,8 +12,7 @@ game::view::VehicleView::VehicleView(WorldView& world, net::InMessage& msg)
: EntityView(world, msg) : EntityView(world, msg)
{ {
net::ModelName modelname; net::ModelName modelname;
glm::vec3 color; if (!msg.Read(modelname))
if (!msg.Read(modelname) || !net::ReadRGB(msg, color))
throw EntityInitError(); throw EntityInitError();
model_ = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh"); model_ = assets::CacheManager::GetVehicleModel("data/" + std::string(modelname) + ".veh");
@ -28,14 +27,9 @@ game::view::VehicleView::VehicleView(WorldView& world, net::InMessage& msg)
wheels_[i].node.parent = &root_; wheels_[i].node.parent = &root_;
} }
color_ = glm::vec4(color, 1.0f); if (!ReadTuning(msg) || !ReadState(&msg) || !ReadDeformSync(msg))
throw EntityInitError();
if (!ReadState(&msg))
throw EntityInitError();
if (!ReadDeformSync(msg))
throw EntityInitError();
// init the other transform to identical // init the other transform to identical
root_trans_[0] = root_trans_[1]; root_trans_[0] = root_trans_[1];
root_.local = root_trans_[0]; root_.local = root_trans_[0];
@ -139,13 +133,14 @@ void game::view::VehicleView::Draw(const DrawArgs& args)
const auto& wheels = model_->GetWheels(); const auto& wheels = model_->GetWheels();
for (size_t i = 0; i < wheels.size(); ++i) for (size_t i = 0; i < wheels.size(); ++i)
{ {
const auto& mesh = *wheels[i].model->GetMesh(); const auto& mesh = *wheels_[i].model->GetMesh();
for (const auto& surface : mesh.surfaces) for (const auto& surface : mesh.surfaces)
{ {
gfx::DrawSurfaceCmd cmd; gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface; cmd.surface = &surface;
cmd.matrices = &wheels_[i].node.matrix; cmd.matrices = &wheels_[i].node.matrix;
cmd.color = &wheels_[i].color;
args.dlist.AddSurface(cmd); args.dlist.AddSurface(cmd);
} }
} }
@ -189,6 +184,35 @@ void game::view::VehicleView::InitMesh()
deform_->tex->SetData(deform_->grid.GetData()); deform_->tex->SetData(deform_->grid.GetData());
} }
bool game::view::VehicleView::ReadTuning(net::InMessage& msg)
{
uint32_t color, wheel_color;
net::TuningPartIdx wheel_idx;
const auto& tuninglist = model_->GetTuningList();
if (!net::ReadRGB(msg, color))
return false;
color_ = glm::unpackUnorm4x8(color);
// wheels
if (!msg.Read(wheel_idx) || !net::ReadRGB(msg, wheel_color))
return false;
auto wheelmodel = wheel_idx < tuninglist.wheels.size() ? assets::CacheManager::GetModel("data/" + tuninglist.wheels[wheel_idx].model + ".mdl") : nullptr;
glm::vec3 wheelcolor = glm::unpackUnorm4x8(wheel_color);
for (size_t i = 0; i < wheels_.size(); ++i)
{
auto& wheel = wheels_[i];
wheel.model = wheelmodel ? wheelmodel : model_->GetWheels()[i].model;
wheel.color = glm::vec4(wheelcolor, 1.0f);
}
return true;
}
bool game::view::VehicleView::ReadState(net::InMessage* msg) bool game::view::VehicleView::ReadState(net::InMessage* msg)
{ {
root_trans_[0] = root_.local; root_trans_[0] = root_.local;

View File

@ -13,6 +13,9 @@ namespace game::view
struct VehicleWheelViewInfo struct VehicleWheelViewInfo
{ {
std::shared_ptr<const assets::Model> model;
glm::vec4 color;
TransformNode node; TransformNode node;
float steering = 0.0f; float steering = 0.0f;
float z_offset = 0.0f; float z_offset = 0.0f;
@ -43,6 +46,7 @@ public:
private: private:
void InitMesh(); void InitMesh();
bool ReadTuning(net::InMessage& msg);
bool ReadState(net::InMessage* msg); bool ReadState(net::InMessage* msg);
bool ReadDeformSync(net::InMessage& msg); bool ReadDeformSync(net::InMessage& msg);

View File

@ -135,4 +135,8 @@ using NumTexels = uint16_t;
using Version = uint32_t; using Version = uint32_t;
// tuning
using TuningPartIdx = uint8_t;
} // namespace net } // namespace net

View File

@ -126,6 +126,25 @@ inline bool ReadRGB(InMessage& msg, glm::vec3& color)
return msg.Read<ColorQ>(color.r) && msg.Read<ColorQ>(color.g) && msg.Read<ColorQ>(color.b); return msg.Read<ColorQ>(color.r) && msg.Read<ColorQ>(color.g) && msg.Read<ColorQ>(color.b);
} }
// COLOR 24bit
inline void WriteRGB(OutMessage& msg, uint32_t color)
{
msg.Write<uint8_t>(color & 0xFF);
msg.Write<uint8_t>((color >> 8) & 0xFF);
msg.Write<uint8_t>((color >> 16) & 0xFF);
}
inline bool ReadRGB(InMessage& msg, uint32_t& color)
{
uint8_t r, g, b;
if (!msg.Read(r) || !msg.Read(g) || !msg.Read(b))
return false;
color = (b << 16) | (g << 8) | r;
return true;
}
// DELTA // DELTA
template <std::unsigned_integral T> requires (sizeof(T) == 1) template <std::unsigned_integral T> requires (sizeof(T) == 1)
inline void WriteDelta(OutMessage& msg, T previous, T current) inline void WriteDelta(OutMessage& msg, T previous, T current)

View File

@ -31,3 +31,13 @@ std::istringstream fs::ReadFileAsStream(const std::string& path)
std::string content = ReadFileAsString(path); std::string content = ReadFileAsString(path);
return std::istringstream(content); return std::istringstream(content);
} }
bool fs::FileExists(const std::string& path)
{
SDL_RWops *rw = SDL_RWFromFile(path.c_str(), "rb");
if (!rw)
return false;
SDL_RWclose(rw);
return true;
}

View File

@ -4,6 +4,7 @@
namespace fs namespace fs
{ {
bool FileExists(const std::string& path);
std::string ReadFileAsString(const std::string& path); std::string ReadFileAsString(const std::string& path);
std::istringstream ReadFileAsStream(const std::string& path); std::istringstream ReadFileAsStream(const std::string& path);
} }

View File

@ -1,6 +1,7 @@
#include "files.hpp" #include "files.hpp"
#include <fstream> #include <fstream>
#include <filesystem>
std::string fs::ReadFileAsString(const std::string& path) std::string fs::ReadFileAsString(const std::string& path)
{ {
@ -22,3 +23,8 @@ std::istringstream fs::ReadFileAsStream(const std::string& path)
std::string content = ReadFileAsString(path); std::string content = ReadFileAsString(path);
return std::istringstream(content); return std::istringstream(content);
} }
bool fs::FileExists(const std::string& path)
{
return std::filesystem::exists(path);
}

View File

@ -1,3 +1,3 @@
#pragma once #pragma once
#define FEKAL_VERSION 2026031401 #define FEKAL_VERSION 2026032001