Port FluidSim core and scenarios to C++20

This commit is contained in:
maxwes08
2026-10-22 23:52:20 +02:00
commit a859740a95
47 changed files with 3851 additions and 0 deletions

222
src/scenario.cpp Normal file
View File

@@ -0,0 +1,222 @@
#include "FluidSim/Scenario.hpp"
#include <algorithm>
#include <cmath>
namespace fluidsim::port {
FourStrokeTwinScenario::FourStrokeTwinScenario(int sample_rate)
: pipes(create_pipes()),
dt_(1.0f / static_cast<float>(std::max(sample_rate, 1))),
boundaries_(pipes), solver_(pipes, boundaries_),
intake_radiation_(sample_rate), exhaust_radiation_(sample_rate),
reverb_(sample_rate), mechanical_sound_(sample_rate) {
crankshaft.omega = 2000.0f * 2.0f * pi / 60.0f;
crankshaft.inertia = 0.04f;
crankshaft.friction_constant = 5.0f;
crankshaft.friction_viscous = 0.003f;
vehicle.set_neutral();
vehicle.clutch_input = clutch;
cylinder1.intake_valve_diameter = 0.030f;
cylinder1.intake_valve_lift = 0.0080f;
cylinder1.exhaust_valve_diameter = 0.026f;
cylinder1.exhaust_valve_lift = 0.0075f;
cylinder2.phase = pi;
cylinder2.intake_valve_diameter = 0.030f;
cylinder2.intake_valve_lift = 0.0080f;
cylinder2.exhaust_valve_diameter = 0.026f;
cylinder2.exhaust_valve_lift = 0.0075f;
cylinder1.rev_limit_enabled = cylinder2.rev_limit_enabled = true;
cylinder1.rev_limit_rpm = cylinder2.rev_limit_rpm = 10000.0f;
cylinder1.rev_limit_cut_time = cylinder2.rev_limit_cut_time = 0.020f;
cylinder1.rev_limit_hysteresis = cylinder2.rev_limit_hysteresis = 250.0f;
cylinder1.initialize(crankshaft);
cylinder2.initialize(crankshaft);
const float intake_area = area_for_diameter(0.0275f);
const float exhaust_area = area_for_diameter(0.028f);
runner_area_ = intake_area;
header_area_ = tail_area_ = exhaust_area;
for (auto& pipe : pipes) {
pipe.damping_multiplier = 1.0f;
pipe.energy_relaxation_rate = 0.5f;
pipe.ambient_pressure = 101325.0f;
}
throttle_port_ = &intake_plenum.create_port();
runner_feed1_ = &intake_plenum.create_port();
runner_feed2_ = &intake_plenum.create_port();
collector_in1_ = &exhaust_collector.create_port();
collector_in2_ = &exhaust_collector.create_port();
collector_out_ = &exhaust_collector.create_port();
intake_radiation_.pipe_end = {-0.20f, 0.0f, 0.0f};
intake_radiation_.pipe_axis = {-1.0f, 0.0f, 0.0f};
intake_radiation_.gain = 30.0f;
intake_radiation_.directivity_balance = 0.30f;
intake_radiation_.flow_tau = 0.005f;
intake_radiation_.smooth_tau = 0.020f;
exhaust_radiation_.pipe_end = {0.55f, 0.0f, 0.0f};
exhaust_radiation_.pipe_axis = {1.0f, 0.0f, 0.0f};
exhaust_radiation_.gain = 25.0f;
exhaust_radiation_.directivity_balance = 0.30f;
exhaust_radiation_.flow_tau = 0.005f;
exhaust_radiation_.smooth_tau = 0.020f;
reverb_.dry_mix = 0.0f;
reverb_.early_mix = 0.30f;
reverb_.tail_mix = 0.22f;
reverb_.transient_feedback_boost = 0.40f;
reverb_.transient_hf_boost = 3.5f;
reverb_.saturation_drive = 1.5f;
solver_.add_component(cylinder1);
solver_.add_component(cylinder2);
solver_.add_component(intake_plenum);
solver_.add_component(exhaust_collector);
boundaries_.add_open_end(0, true, 101325.0f, intake_area);
boundaries_.add_open_end(5, false, 101325.0f, exhaust_area);
boundaries_.add_orifice(*throttle_port_, 0, false, throttle_area_, 0.7f);
boundaries_.add_orifice(*runner_feed1_, 1, true, runner_area_);
boundaries_.add_orifice(*runner_feed2_, 2, true, runner_area_);
boundaries_.add_orifice(cylinder1.intake, 1, false, valve1_in_);
boundaries_.add_orifice(cylinder2.intake, 2, false, valve2_in_);
boundaries_.add_orifice(cylinder1.exhaust, 3, true, valve1_out_);
boundaries_.add_orifice(cylinder2.exhaust, 4, true, valve2_out_);
boundaries_.add_orifice(*collector_in1_, 3, false, header_area_);
boundaries_.add_orifice(*collector_in2_, 4, false, header_area_);
boundaries_.add_orifice(*collector_out_, 5, true, tail_area_);
}
AudioFrame FourStrokeTwinScenario::process() {
const std::lock_guard<std::mutex> guard(mutex_);
crankshaft.load_torque = std::clamp(load, 0.0f, 1.0f) * 30.0f;
vehicle.clutch_input = std::clamp(clutch, 0.0f, 1.0f);
crankshaft.step(dt_);
crankshaft.drivetrain_torque = vehicle.update(crankshaft.omega, crankshaft.inertia, dt_);
cylinder1.pre_step(crankshaft, dt_, throttle);
cylinder2.pre_step(crankshaft, dt_, throttle);
const float throttle_fraction = std::clamp(throttle, 0.0f, 1.0f);
throttle_area_ = throttle_fraction <= 0.001f ? 0.0f : area_for_diameter(0.026f) * throttle_fraction;
const auto cycle_degrees = [this](float phase) {
float value = std::fmod((crankshaft.angle + phase) * 180.0f / pi, 720.0f);
return value < 0.0f ? value + 720.0f : value;
};
valve1_in_ = cylinder1.valve_area(cycle_degrees(cylinder1.phase), true);
valve2_in_ = cylinder2.valve_area(cycle_degrees(cylinder2.phase), true);
valve1_out_ = cylinder1.valve_area(cycle_degrees(cylinder1.phase), false);
valve2_out_ = cylinder2.valve_area(cycle_degrees(cylinder2.phase), false);
solver_.step(dt_);
intake_flow = boundaries_.open_end_mass_flow(0);
exhaust_flow = boundaries_.open_end_mass_flow(1);
const AudioFrame intake_audio = intake_radiation_.process(intake_flow);
const AudioFrame exhaust_audio = exhaust_radiation_.process(exhaust_flow);
const float mechanical = mechanical_sound_enabled
? mechanical_sound_.process(crankshaft, cylinder1, load) * mechanical_mix : 0.0f;
const float reverb_input = intake_radiation_.last_mono_pressure() * intake_send +
exhaust_radiation_.last_mono_pressure() * exhaust_send +
mechanical * mechanical_send;
const AudioFrame wet = reverb_.process_stereo(reverb_input);
const float flow_left = std::tanh(intake_audio.left + exhaust_audio.left + wet.left * reverb_send_gain);
const float flow_right = std::tanh(intake_audio.right + exhaust_audio.right + wet.right * reverb_send_gain);
last_mix_l = std::isfinite(flow_left + mechanical)
? std::clamp(flow_left + mechanical, -1.0f, 1.0f) : 0.0f;
last_mix_r = std::isfinite(flow_right + mechanical)
? std::clamp(flow_right + mechanical, -1.0f, 1.0f) : 0.0f;
++sample_count;
return {last_mix_l, last_mix_r};
}
void FourStrokeTwinScenario::set_controls(float throttle_value, float load_value, float clutch_value) {
const std::lock_guard<std::mutex> guard(mutex_);
throttle = std::clamp(throttle_value, 0.0f, 1.0f);
load = std::clamp(load_value, 0.0f, 1.0f);
clutch = std::clamp(clutch_value, 0.0f, 1.0f);
}
void FourStrokeTwinScenario::toggle_mechanical_sound() {
const std::lock_guard<std::mutex> guard(mutex_);
mechanical_sound_enabled = !mechanical_sound_enabled;
}
void FourStrokeTwinScenario::shift_up() {
const std::lock_guard<std::mutex> guard(mutex_);
vehicle.shift_up();
}
void FourStrokeTwinScenario::shift_down() {
const std::lock_guard<std::mutex> guard(mutex_);
vehicle.shift_down();
}
void FourStrokeTwinScenario::set_neutral() {
const std::lock_guard<std::mutex> guard(mutex_);
vehicle.set_neutral();
}
void FourStrokeTwinScenario::set_first_gear() {
const std::lock_guard<std::mutex> guard(mutex_);
vehicle.set_first_gear();
}
ScenarioSnapshot FourStrokeTwinScenario::snapshot() const {
const std::lock_guard<std::mutex> guard(mutex_);
ScenarioSnapshot result;
result.rpm = crankshaft.omega * 60.0f / (2.0f * pi);
result.speed_kmh = vehicle.speed * 3.6f;
result.kinetic_energy = vehicle.kinetic_energy();
result.average_power = crankshaft.average_power;
result.average_torque = crankshaft.average_torque;
result.cylinder_pressure1 = cylinder1.pressure;
result.cylinder_pressure2 = cylinder2.pressure;
result.cylinder_temperature1 = cylinder1.temperature();
result.cylinder_temperature2 = cylinder2.temperature();
result.cylinder_piston_fraction1 = (cylinder1.volume - cylinder1.clearance_volume()) / cylinder1.swept_volume();
result.cylinder_piston_fraction2 = (cylinder2.volume - cylinder2.clearance_volume()) / cylinder2.swept_volume();
result.valve1_intake_area = valve1_in_;
result.valve1_exhaust_area = valve1_out_;
result.valve2_intake_area = valve2_in_;
result.valve2_exhaust_area = valve2_out_;
result.intake_pressure = intake_plenum.pressure();
result.exhaust_pressure = exhaust_collector.pressure();
result.throttle = throttle;
result.clutch = clutch;
result.intake_flow = intake_flow;
result.exhaust_flow = exhaust_flow;
result.gear = vehicle.current_gear;
result.rev_limit1 = cylinder1.rev_limit_active;
result.rev_limit2 = cylinder2.rev_limit_active;
result.mechanical_sound_enabled = mechanical_sound_enabled;
for (std::size_t pipe_index = 0; pipe_index < pipes.size(); ++pipe_index) {
auto& states = result.pipe_states[pipe_index];
states.reserve(pipes[pipe_index].cells.size());
for (std::size_t cell = 0; cell < pipes[pipe_index].cells.size(); ++cell)
states.push_back(pipes[pipe_index].state(static_cast<int>(cell)));
}
return result;
}
float FourStrokeTwinScenario::area_for_diameter(float diameter) {
return pi * diameter * diameter * 0.25f;
}
std::vector<Pipe1D> FourStrokeTwinScenario::create_pipes() {
const float intake_area = area_for_diameter(0.0275f);
const float exhaust_area = area_for_diameter(0.028f);
std::vector<Pipe1D> result;
result.reserve(6);
result.emplace_back(intake_area, 0.06f, 10);
result.emplace_back(intake_area, 0.12f, 10);
result.emplace_back(intake_area, 0.12f, 10);
result.emplace_back(exhaust_area, 0.20f, 6);
result.emplace_back(exhaust_area, 0.20f, 6);
result.emplace_back(exhaust_area, 0.40f, 10);
return result;
}
} // namespace fluidsim::port