New vehicle tuning

This commit is contained in:
tovjemam 2026-04-12 22:17:43 +02:00
parent 26689b77a5
commit bb81cd3605
19 changed files with 692 additions and 320 deletions

View File

@ -18,8 +18,6 @@ set(COMMON_SOURCES
"src/assets/model.cpp"
"src/assets/skeleton.hpp"
"src/assets/skeleton.cpp"
"src/assets/vehicle_tuning_list.hpp"
"src/assets/vehicle_tuning_list.cpp"
"src/assets/vehiclemdl.hpp"
"src/assets/vehiclemdl.cpp"
"src/collision/dynamicsworld.hpp"
@ -157,6 +155,8 @@ set(SERVER_ONLY_SOURCES
"src/game/tuning_world.hpp"
"src/game/tuning_world.cpp"
"src/game/usable.hpp"
"src/game/vehicle_tuning.hpp"
"src/game/vehicle_tuning.cpp"
"src/game/vehicle.hpp"
"src/game/vehicle.cpp"
"src/game/world.hpp"

View File

@ -9,12 +9,12 @@ void assets::LoadCMDStream(std::istream& is, CmdCallback handler)
while (std::getline(is, line))
{
// skip whitespace
line.erase(0, line.find_first_not_of(" \t\r\n"));
if (line.empty() || line[0] == '#') // Skip empty lines and comments
continue;
// skip whitespace
line.erase(0, line.find_first_not_of(" \t"));
std::istringstream iss(line);
iss >> command;

View File

@ -1,24 +0,0 @@
#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

@ -1,27 +0,0 @@
#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,9 +48,6 @@ std::shared_ptr<const assets::VehicleModel> assets::VehicleModel::LoadFromFile(c
}
});
// tuning list
veh->tuninglist_ = VehicleTuningList::LoadFromFile(filename + ".tun");
return veh;
}

View File

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

View File

@ -5,9 +5,13 @@
game::DrivableVehicle::DrivableVehicle(World& world, const VehicleTuning& tuning) : Vehicle(world, tuning), Usable(GetRoot().matrix)
{
InitSeats();
OnPhysicsChanged();
}
void game::DrivableVehicle::OnPhysicsChanged()
{
// make body usable
collision::AddObjectFlags(&GetBtBody(), collision::OF_USABLE);
collision::AddObjectFlags(&GetPhysics()->GetBtBody(), collision::OF_USABLE);
}
bool game::DrivableVehicle::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res)
@ -90,7 +94,7 @@ static std::string GetColorTextPrefix(uint32_t color)
void game::DrivableVehicle::InitSeats()
{
uint32_t color = GetTuning().primary_color;
uint32_t color = GetTuningResult().colors[0];
std::string prefix = "vlízt do " + GetColorTextPrefix(color) + GetModelName() + "^r";
const auto& veh = *GetModel();

View File

@ -20,6 +20,8 @@ public:
DrivableVehicle(World& world, const VehicleTuning& tuning);
virtual void OnPhysicsChanged() override;
virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;

View File

@ -21,7 +21,7 @@ void game::NpcCharacter::VehicleChanged()
{
roads_ = world_.GetMap().GetGraph("roads");
seg_start_ = GetVehicle()->GetPosition();
seg_start_ = GetVehicle()->GetRootTransform().position;
size_t start_node = 0;
float min_dist = std::numeric_limits<float>().infinity();
@ -87,8 +87,10 @@ void game::NpcCharacter::VehicleThink()
if (!IsDriver() || !GetVehicle() || !roads_)
return;
glm::vec3 pos = GetVehicle()->GetPosition();
glm::quat rot = GetVehicle()->GetRotation();
const auto& vehicle_trans = GetVehicle()->GetRootTransform();
const glm::vec3& pos = vehicle_trans.position;
const glm::quat& rot = vehicle_trans.rotation;
glm::vec3 forward = rot * glm::vec3{0.0f, 1.0f, 0.0f};
// glm::vec3 target = s->roads.nodes[s->node].position;

View File

@ -56,9 +56,10 @@ game::OpenWorld::OpenWorld() : EnterableWorld("openworld")
// 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;
twingo_tuning.parts["primarycolor"] = "orange";
// 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});
@ -83,11 +84,23 @@ game::DrivableVehicle& game::OpenWorld::SpawnRandomVehicle()
{
game::VehicleTuning tuning;
tuning.model = GetRandomCarModel();
tuning.primary_color = GetRandomColor24();
// tuning.primary_color = GetRandomColor24();
auto& vehicle = Spawn<game::DrivableVehicle>(tuning);
// vehicle.SetNametag("bot (" + std::to_string(vehicle.GetEntNum()) + ")");
auto& tuning_list = vehicle.GetTuningList();
auto& colors = tuning_list->groups[0].parts;
size_t random_color = rand() % colors.size();
auto item = colors.begin();
std::advance( item, random_color);
tuning.parts["primarycolor"] = item->second.id;
vehicle.SetTuning(tuning);
return vehicle;
}

View File

@ -1,7 +1,6 @@
#include "tuning_world.hpp"
#include "player_character.hpp"
#include "utils/colors.hpp"
#include "assets/vehicle_tuning_list.hpp"
#include "game.hpp"
game::TuningWorld::TuningWorld(Game& game, EnterableWorld& exit_world, const glm::vec3& exit_pos, float exit_yaw, std::string mapname) :
@ -32,7 +31,7 @@ void game::TuningWorld::OnVehicleJoined(DrivableVehicle& vehicle)
throw std::runtime_error("vehicle joined to tuning without ACTIVE player driver??");
vehicle_ = &vehicle;
tuning_list_ = &vehicle.GetModel()->GetTuningList();
tuning_list_ = vehicle.GetTuningList().get();
player_ = player;
Setup();
@ -54,73 +53,133 @@ const std::string& game::TuningWorld::GetOccupantName() const
void game::TuningWorld::Setup()
{
tuning_ = vehicle_->GetTuning();
UpdateTuningVals();
// UpdateTuningVals();
DisplayTuningMenu();
}
void game::TuningWorld::UpdateTuningVals()
// void game::TuningWorld::UpdateTuningVals()
// {
// tun_primary_color_ = ColorU32ToU8Vec3(tuning_.primary_color);
// tun_wheel_idx_ = tuning_.wheels_idx;
// tun_wheel_color_ = ColorU32ToU8Vec3(tuning_.wheel_color);
// }
// void game::TuningWorld::UpdateTuning()
// {
// tuning_.primary_color = ColorU8Vec3ToU32(tun_primary_color_);
// tuning_.wheels_idx = tun_wheel_idx_;
// tuning_.wheel_color = ColorU8Vec3ToU32(tun_wheel_color_);
// vehicle_->SetTuning(tuning_);
// }
// static void AddColorChannelSlider(game::RemoteMenu& menu, uint8_t& ch, std::string name, std::function<void()> on_change)
// {
// auto& slider = menu.AddItem(game::RM_SELECT, std::move(name));
// auto on_select = [&slider, &ch, on_change = std::move(on_change)] (int dir) {
// int new_val = ch + dir * 5;
// if (new_val < 0)
// ch = 0;
// else if (new_val > 255)
// ch = 255;
// else
// ch = new_val;
// slider.SetSelection(std::to_string(ch));
// on_change();
// };
// on_select(0);
// slider.SetOnSelect(on_select);
// }
// static void AddColorSliders(game::RemoteMenu& menu, glm::u8vec3& color, std::string name, std::function<void()> on_change)
// {
// AddColorChannelSlider(menu, color.r, name + " ^f00R", on_change);
// AddColorChannelSlider(menu, color.g, name + " ^0f0G", on_change);
// AddColorChannelSlider(menu, color.b, name + " ^00fB", on_change);
// }
// static void AddWheelTypeSlider(game::RemoteMenu& menu, const assets::VehicleTuningList& tuning_list, size_t& idx, std::function<void()> on_change)
// {
// auto& slider = menu.AddItem(game::RM_SELECT, "kola");
// auto on_select = [&slider, &idx, &tuning_list, on_change = std::move(on_change)] (int dir) {
// auto& wheels = tuning_list.wheels;
// if (dir < 0 && idx == 0)
// return;
// if (dir > 0 && (idx + 1) >= wheels.size())
// return;
// idx += dir;
// slider.SetSelection(tuning_list.wheels[idx].displayname);
// on_change();
// };
// on_select(0);
// slider.SetOnSelect(on_select);
// }
void game::TuningWorld::AddTuningGroupSelect(game::RemoteMenu& menu, const VehicleTuningGroup& group)
{
tun_primary_color_ = ColorU32ToU8Vec3(tuning_.primary_color);
size_t num_parts = group.parts.size();
if (num_parts == 0)
return;
tun_wheel_idx_ = tuning_.wheels_idx;
tun_wheel_color_ = ColorU32ToU8Vec3(tuning_.wheel_color);
}
void game::TuningWorld::UpdateTuning()
{
tuning_.primary_color = ColorU8Vec3ToU32(tun_primary_color_);
tuning_.wheels_idx = tun_wheel_idx_;
tuning_.wheel_color = ColorU8Vec3ToU32(tun_wheel_color_);
vehicle_->SetTuning(tuning_);
}
static void AddColorChannelSlider(game::RemoteMenu& menu, uint8_t& ch, std::string name, std::function<void()> on_change)
{
auto& slider = menu.AddItem(game::RM_SELECT, std::move(name));
auto on_select = [&slider, &ch, on_change = std::move(on_change)] (int dir) {
int new_val = ch + dir * 5;
if (new_val < 0)
ch = 0;
else if (new_val > 255)
ch = 255;
else
ch = new_val;
slider.SetSelection(std::to_string(ch));
on_change();
struct SliderState
{
const VehicleTuningGroup* group;
int idx = 0;
std::vector<std::string> ids;
};
on_select(0);
slider.SetOnSelect(on_select);
}
std::string current_part;
static void AddColorSliders(game::RemoteMenu& menu, glm::u8vec3& color, std::string name, std::function<void()> on_change)
{
AddColorChannelSlider(menu, color.r, name + " ^f00R", on_change);
AddColorChannelSlider(menu, color.g, name + " ^0f0G", on_change);
AddColorChannelSlider(menu, color.b, name + " ^00fB", on_change);
}
auto current_it = tuning_.parts.find(group.id);
if (current_it != tuning_.parts.end())
{
current_part = current_it->second;
}
static void AddWheelTypeSlider(game::RemoteMenu& menu, const assets::VehicleTuningList& tuning_list, size_t& idx, std::function<void()> on_change)
{
auto& slider = menu.AddItem(game::RM_SELECT, "kola");
auto state = std::make_shared<SliderState>();
state->group = &group;
auto on_select = [&slider, &idx, &tuning_list, on_change = std::move(on_change)] (int dir) {
auto& wheels = tuning_list.wheels;
state->ids.reserve(num_parts);
size_t i = 0;
for (const auto& part : group.parts)
{
state->ids.push_back(part.first);
if (dir < 0 && idx == 0)
return;
if (part.first == current_part)
state->idx = i;
if (dir > 0 && (idx + 1) >= wheels.size())
return;
++i;
}
idx += dir;
auto& slider = menu.AddItem(game::RM_SELECT, group.displayname);
auto on_select = [&slider, this, state](int dir) mutable {
if (dir < 0 && state->idx == 0)
state->idx = state->ids.size() - 1;
else if (dir > 0 && (state->idx + 1) >= state->ids.size())
state->idx = 0;
else
state->idx += dir;
auto& part_id = state->ids[state->idx];
tuning_.parts[state->group->id] = part_id;
slider.SetSelection(state->group->parts.at(part_id).displayname);
if (dir != 0)
vehicle_->SetTuning(tuning_);
slider.SetSelection(tuning_list.wheels[idx].displayname);
on_change();
};
on_select(0);
@ -133,14 +192,19 @@ void game::TuningWorld::DisplayTuningMenu()
auto& menu = player_->DisplayMenu("tuning");
menu_ = &menu;
auto on_change = [this] { UpdateTuning(); };
// auto on_change = [this] { UpdateTuning(); };
AddColorSliders(menu, tun_primary_color_, "primární", on_change);
// AddColorSliders(menu, tun_primary_color_, "primární", on_change);
if (!tuning_list_->wheels.empty())
// if (!tuning_list_->wheels.empty())
// {
// AddWheelTypeSlider(menu, *tuning_list_, tun_wheel_idx_, on_change);
// AddColorSliders(menu, tun_wheel_color_, "kola", on_change);
// }
for (const auto& group : tuning_list_->groups)
{
AddWheelTypeSlider(menu, *tuning_list_, tun_wheel_idx_, on_change);
AddColorSliders(menu, tun_wheel_color_, "kola", on_change);
AddTuningGroupSelect(menu, group);
}
auto& exit_btn = menu.AddItem(RM_BUTTON, "vylézt");

View File

@ -26,9 +26,7 @@ public:
private:
void Setup();
void UpdateTuningVals();
void UpdateTuning();
void AddTuningGroupSelect(game::RemoteMenu& menu, const VehicleTuningGroup& group);
void DisplayTuningMenu();
void Reset();
@ -42,17 +40,13 @@ private:
// active player
Player* player_ = nullptr;
DrivableVehicle* vehicle_ = nullptr;
const assets::VehicleTuningList* tuning_list_ = nullptr;
const VehicleTuningList* tuning_list_ = nullptr;
game::RemoteMenu* menu_ = nullptr;
VehicleTuning tuning_;
glm::u8vec3 tun_primary_color_;
size_t tun_wheel_idx_;
glm::u8vec3 tun_wheel_color_;
};
}

View File

@ -15,87 +15,13 @@ static std::shared_ptr<const assets::VehicleModel> LoadVehicleModelByName(const
game::Vehicle::Vehicle(World& world, const VehicleTuning& tuning)
: Entity(world, net::ET_VEHICLE), tuning_(tuning), model_(LoadVehicleModelByName(tuning.model)),
motion_(root_.local)
tuninglist_(VehicleTuningList::LoadFromFile("data/" + tuning.model + ".tun"))
{
root_.local.position.z = 10.0f;
// setup chassis rigidbody
float mass = 1000.0f;
wheels_.resize(model_->GetWheels().size());
btCollisionShape* shape = model_->GetModel()->GetColShape();
if (!shape)
throw std::runtime_error("Making vehicle with no shape");
btVector3 local_inertia(0, 0, 0);
shape->calculateLocalInertia(mass, local_inertia);
btRigidBody::btRigidBodyConstructionInfo rb_info(mass, &motion_, shape, local_inertia);
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 bt_tuning;
vehicle_ = std::make_unique<collision::RaycastVehicle>(bt_tuning, body_.get(), &world_.GetVehicleRaycaster());
vehicle_->setCoordinateSystem(0, 2, 1);
// setup wheels
// btVector3 wheelDirectionCS0(0, -1, 0);
// btVector3 wheelAxleCS(-1, 0, 0);
btVector3 wheelDirectionCS0(0, 0, -1);
btVector3 wheelAxleCS(1, 0, 0);
wheel_z_offset_ = 0.2f;
const auto& wheels = model_->GetWheels();
if (wheels.size() > MAX_WHEELS)
throw std::runtime_error("Max wheels exceeded");
num_wheels_ = wheels.size();
for (const auto& wheeldef : wheels)
{
float wheelRadius = wheeldef.radius;
float friction = 2.2f; // 5.0f;
float suspensionStiffness = 50.0f;
// float suspensionDamping = 2.3f;
// float suspensionCompression = 4.4f;
float suspensionRestLength = 0.3f;
float rollInfluence = 0.3f;
float maxSuspensionForce = 90000.0f;
float maxSuspensionTravelCm = 15.0f;
float k = 0.2f;
const bool is_front = !(wheeldef.type & assets::WHEEL_REAR);
if (!is_front)
friction *= 1.5f;
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,
bt_tuning, is_front);
wi.m_suspensionStiffness = suspensionStiffness;
wi.m_wheelsDampingCompression = k * 2.0 * btSqrt(suspensionStiffness); // vehicleTuning.suspensionCompression;
wi.m_wheelsDampingRelaxation = k * 3.3 * btSqrt(suspensionStiffness); // vehicleTuning.suspensionDamping;
wi.m_frictionSlip = friction;
// if (wi.m_bIsFrontWheel) wi.m_frictionSlip = vehicleTuning.friction * 1.4f;
wi.m_rollInfluence = rollInfluence;
wi.m_maxSuspensionForce = maxSuspensionForce;
wi.m_maxSuspensionTravelCm = maxSuspensionTravelCm;
}
auto& bt_world = world_.GetBtWorld();
bt_world.addRigidBody(body_.get(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::AllFilter);
bt_world.addAction(vehicle_.get());
ApplyTuning(tuning);
// init deform
gfx::DeformGridInfo info{};
@ -163,7 +89,6 @@ void game::Vehicle::OnContact(const collision::ContactInfo& info)
{
Deform(info.pos, -glm::normalize(info.normal) * 0.1f, 1.0f);
}
}
void game::Vehicle::SetInput(VehicleInputType type, bool enable)
@ -174,28 +99,18 @@ void game::Vehicle::SetInput(VehicleInputType type, bool enable)
in_ &= ~(1 << type);
}
glm::vec3 game::Vehicle::GetPosition() const
{
btVector3 pos = body_->getWorldTransform().getOrigin();
return glm::vec3(pos.x(), pos.y(), pos.z());
}
void game::Vehicle::SetPosition(const glm::vec3& pos)
{
auto t = body_->getWorldTransform();
t.setOrigin(btVector3(pos.x, pos.y, pos.z));
body_->setWorldTransform(t);
}
auto& body = physics_->GetBtBody();
glm::quat game::Vehicle::GetRotation() const
{
btQuaternion rot = body_->getWorldTransform().getRotation();
return glm::quat(rot.w(), rot.x(), rot.y(), rot.z());
auto t = body.getWorldTransform();
t.setOrigin(btVector3(pos.x, pos.y, pos.z));
body.setWorldTransform(t);
}
float game::Vehicle::GetSpeed() const
{
return vehicle_->getCurrentSpeedKmHour();
return physics_->GetBtVehicle().getCurrentSpeedKmHour();
}
void game::Vehicle::SetSteering(bool analog, float value)
@ -206,54 +121,48 @@ void game::Vehicle::SetSteering(bool analog, float value)
void game::Vehicle::SetTuning(const VehicleTuning& tuning)
{
tuning_ = tuning;
ApplyTuning(tuning);
// send to clients
auto msg = BeginEntMsg(net::EMSG_TUNING);
WriteTuning(msg);
}
game::Vehicle::~Vehicle()
{
auto& bt_world = world_.GetBtWorld();
bt_world.removeRigidBody(body_.get());
bt_world.removeAction(vehicle_.get());
}
void game::Vehicle::ProcessInput()
{
// TODO: totally fix
//std::string nt = "";
// std::string nt = "";
if (in_) {
//nt += "in ";
if (in_)
{
// nt += "in ";
// body_->setActivationState(ACTIVE_TAG);
body_->activate();
physics_->GetBtBody().activate();
}
else
{
//nt += "no in";
// nt += "no in";
}
//nt += std::to_string(body_->getActivationState());
//SetNametag(nt);
// nt += std::to_string(body_->getActivationState());
// SetNametag(nt);
float t_delta = 1.0f / 25.0f;
// float steeringIncrement = .04 * 60;
// float steeringClamp = .5;
// float maxEngineForce = 7000;
float maxEngineForce = 3200;
float maxBreakingForce = 400;
float maxEngineForce = tuning_ctx_.engine_force;
float maxBreakingForce = tuning_ctx_.braking_force;
float speed = vehicle_->getCurrentSpeedKmHour();
float speed = GetSpeed();
if (glm::abs(speed) > 200.0f)
maxEngineForce = 100.0f;
float engineForce = 0;
float breakingForce = 0;
float maxsc = .5f;
float minsc = .08f;
float sl = 130.f;
@ -263,7 +172,6 @@ void game::Vehicle::ProcessInput()
float steeringSpeed = steeringClamp * 5.0f;
float steeringInc = steeringSpeed * t_delta;
const bool in_forward = in_ & (1 << VIN_FORWARD);
const bool in_backward = in_ & (1 << VIN_BACKWARD);
const bool in_left = in_ & (1 << VIN_LEFT);
@ -287,7 +195,7 @@ void game::Vehicle::ProcessInput()
// idle breaking
if (!in_forward && !in_backward)
{
breakingForce = maxBreakingForce * 0.05f;
breakingForce = 20.0f;
}
if (!steering_analog_)
@ -341,16 +249,29 @@ void game::Vehicle::ProcessInput()
steering_ = -steeringClamp;
}
vehicle_->applyEngineForce(engineForce, 0);
vehicle_->applyEngineForce(engineForce, 1);
auto& vehicle = physics_->GetBtVehicle();
vehicle_->setBrake(breakingForce * 0.1f, 0);
vehicle_->setBrake(breakingForce * 0.1f, 1);
vehicle_->setBrake(breakingForce, 2);
vehicle_->setBrake(breakingForce, 3);
for (size_t i = 0; i < wheels_.size(); ++i)
{
float engine_factor = tuning_ctx_.wheels[i].engine_factor;
if (engine_factor > 0.0001f)
{
vehicle.applyEngineForce(engine_factor * engineForce, i);
}
float braking_factor = tuning_ctx_.wheels[i].braking_factor;
if (braking_factor > 0.0001f)
{
vehicle.setBrake(braking_factor * breakingForce, i);
}
float steering_factor = tuning_ctx_.wheels[i].steering_factor;
if (std::abs(steering_factor) > 0.0001f)
{
vehicle.setSteeringValue(steering_factor * steering_, i);
}
}
vehicle_->setSteeringValue(steering_, 0);
vehicle_->setSteeringValue(steering_, 1);
if (glm::abs(engineForce) > 0)
flags_ |= VF_ACCELERATING;
@ -394,7 +315,6 @@ void game::Vehicle::UpdateCrash()
PlaySound("crash", volume, pitch);
no_crash_frames_ = 7 + rand() % 10;
}
}
crash_intensity_ = 0.0f;
@ -402,12 +322,14 @@ void game::Vehicle::UpdateCrash()
void game::Vehicle::UpdateWheels()
{
for (size_t i = 0; i < num_wheels_; ++i)
auto& vehicle = physics_->GetBtVehicle();
for (size_t i = 0; i < wheels_.size(); ++i)
{
auto& bt_wheel = vehicle_->getWheelInfo(i);
auto& bt_wheel = vehicle.getWheelInfo(i);
wheels_[i].speed = -(bt_wheel.m_rotation - wheels_[i].rotation) * 25.0f;
wheels_[i].rotation = bt_wheel.m_rotation;
wheels_[i].z_offset = wheel_z_offset_ - bt_wheel.m_raycastInfo.m_suspensionLength;
wheels_[i].z_offset = tuning_ctx_.wheels[i].z_offset - bt_wheel.m_raycastInfo.m_suspensionLength;
}
}
@ -422,7 +344,7 @@ void game::Vehicle::UpdateSyncState()
state.steering.Encode(steering_);
for (size_t i = 0; i < num_wheels_; ++i)
for (size_t i = 0; i < wheels_.size(); ++i)
{
auto& wheel = wheels_[i];
state.wheels[i].z_offset.Encode(wheel.z_offset);
@ -441,8 +363,7 @@ game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, cons
msg.Write(curr.flags);
}
if (curr.pos.x.value != base.pos.x.value ||
curr.pos.y.value != base.pos.y.value ||
if (curr.pos.x.value != base.pos.x.value || curr.pos.y.value != base.pos.y.value ||
curr.pos.z.value != base.pos.z.value)
{
fields |= VSF_POSITION;
@ -452,8 +373,7 @@ game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, cons
net::WriteDelta(msg, curr.pos.z, base.pos.z);
}
if (curr.rot.x.value != base.rot.x.value ||
curr.rot.y.value != base.rot.y.value ||
if (curr.rot.x.value != base.rot.x.value || curr.rot.y.value != base.rot.y.value ||
curr.rot.z.value != base.rot.z.value)
{
fields |= VSF_ROTATION;
@ -471,7 +391,7 @@ game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, cons
}
bool wheels_changed = false;
for (size_t i = 0; i < num_wheels_; ++i)
for (size_t i = 0; i < wheels_.size(); ++i)
{
if (curr.wheels[i].z_offset.value != base.wheels[i].z_offset.value ||
curr.wheels[i].speed.value != base.wheels[i].speed.value)
@ -485,7 +405,7 @@ game::VehicleSyncFieldFlags game::Vehicle::WriteState(net::OutMessage& msg, cons
{
fields |= VSF_WHEELS;
for (size_t i = 0; i < num_wheels_; ++i)
for (size_t i = 0; i < wheels_.size(); ++i)
{
net::WriteDelta(msg, curr.wheels[i].z_offset, base.wheels[i].z_offset);
net::WriteDelta(msg, curr.wheels[i].speed, base.wheels[i].speed);
@ -563,11 +483,147 @@ void game::Vehicle::SendDeformMsg(const net::PositionQ& pos, const net::Position
net::WritePositionQ(msg, deform);
}
void game::Vehicle::ApplyTuning(const VehicleTuning& tuning)
{
tuning_ = tuning;
tuning_ctx_ = VehicleTuningContext{};
// setup wheels
const auto& model_wheels = model_->GetWheels();
tuning_ctx_.wheels.resize(model_wheels.size());
for (size_t i = 0; i < model_wheels.size(); ++i)
{
tuning_ctx_.wheels[i].front = !(model_wheels[i].type & assets::WHEEL_REAR);
}
// apply tunning to ctx
for (const auto& func : tuninglist_->default_funcs)
{
func(tuning_ctx_);
}
for (const auto& group : tuninglist_->groups)
{
auto group_it = tuning_.parts.find(group.id);
if (group_it == tuning_.parts.end())
continue;
const auto& part_name = group_it->second;
const auto& part = group.parts.at(part_name);
for (const auto& func : part.funcs)
{
func(tuning_ctx_);
}
}
// (re)create physics
physics_.reset();
physics_ = std::make_unique<VehiclePhysics>(world_, root_.local, *this, *model_, tuning_ctx_);
OnPhysicsChanged();
}
void game::Vehicle::WriteTuning(net::OutMessage& msg) const
{
net::WriteRGB(msg, tuning_.primary_color);
// write colors
for (const auto& color : tuning_ctx_.colors)
{
net::WriteRGB(msg, color);
}
// wheels
msg.Write<net::TuningPartIdx>(tuning_.wheels_idx);
net::WriteRGB(msg, tuning_.wheel_color);
// write wheel models
for (const auto& wheel : tuning_ctx_.wheels)
{
msg.Write(net::ModelName(wheel.modelname));
}
}
// PHYSICS
game::VehiclePhysics::VehiclePhysics(collision::DynamicsWorld& world, Transform& transform,
collision::ObjectCallback& obj_cb, const assets::VehicleModel& model,
const VehicleTuningContext& tuning)
: world_(world), motion_(transform)
{
// setup chassis rigidbody
btCollisionShape* shape = model.GetModel()->GetColShape();
if (!shape)
throw std::runtime_error("Making vehicle with no shape");
btVector3 local_inertia(0, 0, 0);
shape->calculateLocalInertia(tuning.mass, local_inertia);
btRigidBody::btRigidBodyConstructionInfo rb_info(tuning.mass, &motion_, shape, local_inertia);
body_ = std::make_unique<btRigidBody>(rb_info);
// body_->setActivationState(DISABLE_DEACTIVATION);
collision::SetObjectInfo(body_.get(), collision::OT_ENTITY, collision::OF_NOTIFY_CONTACT, &obj_cb);
// setup vehicle
btRaycastVehicle::btVehicleTuning bt_tuning;
vehicle_ = std::make_unique<collision::RaycastVehicle>(bt_tuning, body_.get(), &world_.GetVehicleRaycaster());
vehicle_->setCoordinateSystem(0, 2, 1);
// setup wheels
// btVector3 wheelDirectionCS0(0, -1, 0);
// btVector3 wheelAxleCS(-1, 0, 0);
btVector3 wheelDirectionCS0(0, 0, -1);
btVector3 wheelAxleCS(1, 0, 0);
const auto& model_wheels = model.GetWheels();
size_t num_wheels = model_wheels.size();
for (size_t i = 0; i < num_wheels; ++i)
{
const auto& wheel_mdl = model_wheels[i];
const auto& wheel_tun = tuning.wheels[i];
// float wheelRadius = wheel_tun.radius;
// float friction = wheel_tun.friction
// float suspensionStiffness = 50.0f;
// // float suspensionDamping = 2.3f;
// // float suspensionCompression = 4.4f;
// float suspensionRestLength = 0.3f;
// float rollInfluence = 0.3f;
// float maxSuspensionForce = 90000.0f;
// float maxSuspensionTravelCm = 15.0f;
float k = 0.2f;
// if (!is_front)
// friction *= 1.5f;
btVector3 wheel_pos(wheel_mdl.position.x, wheel_mdl.position.y, wheel_mdl.position.z + wheel_tun.z_offset);
auto& wi = vehicle_->addWheel(wheel_pos, wheelDirectionCS0, wheelAxleCS, wheel_tun.suspension_rest_length, wheel_tun.radius,
bt_tuning, wheel_tun.front);
wi.m_suspensionStiffness = wheel_tun.suspension_stiffness;
wi.m_wheelsDampingCompression = k * 2.0 * btSqrt(wi.m_suspensionStiffness); // vehicleTuning.suspensionCompression;
wi.m_wheelsDampingRelaxation = k * 3.3 * btSqrt(wi.m_suspensionStiffness); // vehicleTuning.suspensionDamping;
wi.m_frictionSlip = wheel_tun.friction;
wi.m_rollInfluence = wheel_tun.roll_influence;
wi.m_maxSuspensionForce = wheel_tun.suspension_max_force;
wi.m_maxSuspensionTravelCm = wheel_tun.suspension_travel * 100.0f;
}
auto& bt_world = world_.GetBtWorld();
bt_world.addRigidBody(body_.get(), btBroadphaseProxy::DefaultFilter, btBroadphaseProxy::AllFilter);
bt_world.addAction(vehicle_.get());
}
game::VehiclePhysics::~VehiclePhysics()
{
auto& bt_world = world_.GetBtWorld();
bt_world.removeRigidBody(body_.get());
bt_world.removeAction(vehicle_.get());
}

View File

@ -6,11 +6,11 @@
#include "assets/vehiclemdl.hpp"
#include "collision/motionstate.hpp"
#include "collision/raycastvehicle.hpp"
#include "entity.hpp"
#include "world.hpp"
#include "vehicle_sync.hpp"
#include "deform_grid.hpp"
#include "entity.hpp"
#include "vehicle_sync.hpp"
#include "vehicle_tuning.hpp"
#include "world.hpp"
namespace game
{
@ -33,6 +33,26 @@ enum VehicleInputType
VIN_HANDBRAKE,
};
class VehiclePhysics
{
public:
VehiclePhysics(collision::DynamicsWorld& world, Transform& transform, collision::ObjectCallback& obj_cb,
const assets::VehicleModel& model, const VehicleTuningContext& tuning);
DELETE_COPY_MOVE(VehiclePhysics)
btRigidBody& GetBtBody() { return *body_; }
collision::RaycastVehicle& GetBtVehicle() { return *vehicle_; }
~VehiclePhysics();
private:
collision::DynamicsWorld& world_;
collision::MotionState motion_;
std::unique_ptr<btRigidBody> body_;
std::unique_ptr<collision::RaycastVehicle> vehicle_;
};
class Vehicle : public Entity
{
public:
@ -48,10 +68,8 @@ public:
void SetInput(VehicleInputType type, bool enable);
void SetInputs(VehicleInputFlags inputs) { in_ = inputs; }
glm::vec3 GetPosition() const;
void SetPosition(const glm::vec3& pos);
glm::quat GetRotation() const;
float GetSpeed() const;
void SetSteering(bool analog, float value = 0.0f);
@ -60,9 +78,10 @@ public:
const std::string& GetModelName() const { return tuning_.model; }
const std::shared_ptr<const assets::VehicleModel>& GetModel() const { return model_; }
const VehicleTuning& GetTuning() const { return tuning_; }
virtual ~Vehicle();
const VehicleTuning& GetTuning() const { return tuning_; }
const std::shared_ptr<const VehicleTuningList>& GetTuningList() const { return tuninglist_; }
const VehicleTuningContext& GetTuningResult() const { return tuning_ctx_; }
private:
void ProcessInput();
@ -77,26 +96,28 @@ private:
void Deform(const glm::vec3& pos, const glm::vec3& deform, float radius);
void SendDeformMsg(const net::PositionQ& pos, const net::PositionQ& deform);
void ApplyTuning(const VehicleTuning& tuning);
void WriteTuning(net::OutMessage& msg) const;
protected:
btRigidBody& GetBtBody() { return *body_; }
VehiclePhysics* GetPhysics() { return physics_.get(); }
virtual void OnPhysicsChanged() {}
private:
VehicleTuning tuning_;
std::shared_ptr<const assets::VehicleModel> model_;
std::shared_ptr<const VehicleTuningList> tuninglist_;
collision::MotionState motion_;
std::unique_ptr<btRigidBody> body_;
std::unique_ptr<collision::RaycastVehicle> vehicle_;
VehicleTuningContext tuning_ctx_;
std::unique_ptr<VehiclePhysics> physics_;
float steering_ = 0.0f;
bool steering_analog_ = false;
float target_steering_ = 0.0f;
float wheel_z_offset_ = 0.0f;
size_t num_wheels_ = 0;
std::array<VehicleWheelState, MAX_WHEELS> wheels_;
std::vector<VehicleWheelState> wheels_;
VehicleFlags flags_ = VF_NONE;
VehicleSyncState sync_[2];

218
src/game/vehicle_tuning.cpp Normal file
View File

@ -0,0 +1,218 @@
#include "vehicle_tuning.hpp"
#include "assets/cmdfile.hpp"
#include <iomanip>
static float game::VehicleTuningContext::* GetCtxVariablePointer(const std::string& name)
{
if (name == "mass")
return &game::VehicleTuningContext::mass;
if (name == "engine_force")
return &game::VehicleTuningContext::engine_force;
if (name == "braking_force")
return &game::VehicleTuningContext::braking_force;
throw std::runtime_error("tuning list: invalid variable " + name);
}
static float game::VehicleWheelTuningContext::* GetWheelVariablePointer(const std::string& name)
{
if (name == "radius")
return &game::VehicleWheelTuningContext::radius;
if (name == "z_offset")
return &game::VehicleWheelTuningContext::z_offset;
if (name == "friction")
return &game::VehicleWheelTuningContext::friction;
if (name == "suspension_stiffness")
return &game::VehicleWheelTuningContext::suspension_stiffness;
if (name == "suspension_max_force")
return &game::VehicleWheelTuningContext::suspension_max_force;
if (name == "suspension_rest_length")
return &game::VehicleWheelTuningContext::suspension_rest_length;
if (name == "suspension_travel")
return &game::VehicleWheelTuningContext::suspension_travel;
if (name == "roll_influence")
return &game::VehicleWheelTuningContext::roll_influence;
if (name == "steering_factor")
return &game::VehicleWheelTuningContext::steering_factor;
if (name == "braking_factor")
return &game::VehicleWheelTuningContext::braking_factor;
if (name == "engine_factor")
return &game::VehicleWheelTuningContext::engine_factor;
throw std::runtime_error("tuning list: invalid wheel variable " + name);
}
static void ApplyOp(float& var, const std::string& op, float value)
{
if (op == "+=")
var += value;
else if (op == "-=")
var -= value;
else if (op == "*=")
var *= value;
else if (op == "/=")
var /= value;
else
var = value;
}
static bool CheckWheelCond(const game::VehicleWheelTuningContext& wheel_ctx, const std::string& cond)
{
if (cond == "front" && !wheel_ctx.front)
return false;
if (cond == "rear" && wheel_ctx.front)
return false;
return true;
}
static uint32_t ParseColor(uint32_t c)
{
auto r = (c >> 16) & 0xFF;
auto g = (c >> 8) & 0xFF;
auto b = c & 0xFF;
return 0xFF000000 | (b << 16) | (g << 8) | r;
}
static game::VehicleTuningFunction ParseTuningFunction(std::istringstream& iss)
{
std::string func_name;
iss >> func_name;
if (func_name == "set")
{
std::string var_name, op;
float value;
iss >> var_name >> op >> value;
auto var_ptr = GetCtxVariablePointer(var_name);
return [var_ptr, op, value](game::VehicleTuningContext& ctx) {
ApplyOp(ctx.*var_ptr, op, value);
};
}
else if (func_name == "setcolor")
{
size_t color_idx;
uint32_t color;
iss >> color_idx >> std::hex >> color >> std::dec;
if (color_idx >= 4)
throw std::runtime_error("tuning list: invalid color index");
color = ParseColor(color);
return [color_idx, color](game::VehicleTuningContext& ctx) {
ctx.colors[color_idx] = color;
};
}
else if (func_name == "setwheel")
{
std::string wheel_cond, var_name, op;
float value;
iss >> wheel_cond >> var_name >> op >> value;
auto var_ptr = GetWheelVariablePointer(var_name);
return [wheel_cond, var_ptr, op, value](game::VehicleTuningContext& ctx) {
for (auto& wheel : ctx.wheels)
{
if (CheckWheelCond(wheel, wheel_cond))
ApplyOp(wheel.*var_ptr, op, value);
}
};
}
else if (func_name == "setwheelmodel")
{
std::string wheel_cond, modelname;
iss >> wheel_cond >> modelname;
return [wheel_cond, modelname](game::VehicleTuningContext& ctx) {
for (auto& wheel : ctx.wheels)
{
if (CheckWheelCond(wheel, wheel_cond))
wheel.modelname = modelname;
}
};
}
else
{
throw std::runtime_error("tuning list: unknown function " + func_name);
}
}
std::unique_ptr<const game::VehicleTuningList> game::VehicleTuningList::LoadFromFile(const std::string& filename)
{
auto tuninglist = std::make_unique<VehicleTuningList>();
if (!fs::FileExists(filename))
return tuninglist; // empty
VehicleTuningGroup* current_group = nullptr;
VehicleTuningPart* current_part = nullptr;
auto process_command = [&](const std::string& command, std::istringstream& iss) {
if (command == "group")
{
VehicleTuningGroup group;
iss >> group.id;
group.displayname = assets::ParseString(iss);
tuninglist->groups.emplace_back(std::move(group));
current_group = &tuninglist->groups.back();
return true;
}
else if (command == "part")
{
if (!current_group)
throw std::runtime_error("tuning list: part without active group");
VehicleTuningPart part;
iss >> part.id >> part.price;
part.displayname = assets::ParseString(iss);
current_part = &(current_group->parts[part.id] = std::move(part));
return true;
}
else if (command == "default")
{
tuninglist->default_funcs.emplace_back(ParseTuningFunction(iss));
return true;
}
else if (command == "mod")
{
if (!current_part)
throw std::runtime_error("tuning list: mod without active part");
current_part->funcs.emplace_back(ParseTuningFunction(iss));
return true;
}
return false;
};
assets::LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
if (process_command(command, iss))
return;
if (command == "include")
{
std::string include_name;
iss >> include_name;
assets::LoadCMDFile("data/" + include_name + ".tun", [&](const std::string& command, std::istringstream& iss) {
process_command(command, iss);
});
}
});
return tuninglist;
}

View File

@ -1,18 +1,72 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
#include <functional>
#include <map>
#include <array>
namespace game
{
struct VehicleWheelTuningContext
{
bool front;
std::string modelname;
float radius;
float z_offset;
float friction;
float suspension_stiffness;
float suspension_max_force;
float suspension_rest_length;
float suspension_travel;
float roll_influence;
float steering_factor;
float braking_factor;
float engine_factor;
};
struct VehicleTuningContext
{
std::array<uint32_t, 4> colors;
float mass;
float engine_force;
float braking_force;
std::vector<VehicleWheelTuningContext> wheels;
};
using VehicleTuningFunction = std::function<void(VehicleTuningContext&)>;
struct VehicleTuningPart
{
std::string id;
std::string displayname;
uint32_t price;
std::vector<VehicleTuningFunction> funcs;
};
struct VehicleTuningGroup
{
std::string id;
std::string displayname;
std::map<std::string, VehicleTuningPart> parts;
};
struct VehicleTuningList
{
std::vector<VehicleTuningFunction> default_funcs;
std::vector<VehicleTuningGroup> groups;
static std::unique_ptr<const VehicleTuningList> LoadFromFile(const std::string& filename);
};
struct VehicleTuning
{
std::string model;
uint32_t primary_color = 0xFFFFFFFF;
size_t wheels_idx = 0;
uint32_t wheel_color = 0xFFFFFFFF;
std::map<std::string, std::string> parts; // group : part
};

View File

@ -128,7 +128,7 @@ void game::view::VehicleView::Draw(const DrawArgs& args)
gfx::DrawSurfaceCmd cmd;
cmd.surface = &surface;
cmd.matrices = &root_.matrix;
cmd.color = &color_;
cmd.color = &colors_[0];
args.dlist.AddSurface(cmd);
}
@ -188,28 +188,29 @@ void game::view::VehicleView::InitMesh()
bool game::view::VehicleView::ReadTuning(net::InMessage& msg)
{
uint32_t color, wheel_color;
net::TuningPartIdx wheel_idx;
const auto& tuninglist = model_->GetTuningList();
// read colors
for (size_t i = 0; i < 4; ++i)
{
uint32_t color;
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);
colors_[i] = glm::unpackUnorm4x8(color);
}
// read wheel models
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);
net::ModelName wheelmodel_fixed;
if (!msg.Read(wheelmodel_fixed))
return false;
std::string wheel_model_name = wheelmodel_fixed;
wheels_[i].model = !wheel_model_name.empty() ? assets::CacheManager::GetModel("data/" + wheel_model_name + ".mdl") : model_->GetWheels()[i].model;
wheels_[i].color = colors_[1]; // TODO: dynamic?;
}
return true;

View File

@ -55,7 +55,7 @@ private:
private:
std::shared_ptr<const assets::VehicleModel> model_;
assets::Mesh mesh_;
glm::vec4 color_;
glm::vec4 colors_[4];
game::VehicleSyncState sync_;
std::vector<VehicleWheelViewInfo> wheels_;

View File

@ -1,3 +1,3 @@
#pragma once
#define FEKAL_VERSION 2026032901
#define FEKAL_VERSION 2026041201