Load engines from TOML and improve scenario audio and carb tuning
Migrate 32 engine definitions into validated startup-loaded TOML files and separate configuration and snapshots from scenario assembly. Add camera-based stereo propagation from shared pipe geometry, preserve exhaust character, tune carburetor mixtures and idle, use ignition cuts for carb engines, and support returning to the scenario menu. Validation: full Windows build and all 14 CTest checks passed.
This commit is contained in:
313
src/scenario.cpp
313
src/scenario.cpp
@@ -1,141 +1,62 @@
|
||||
#include "FluidSim/Scenario.hpp"
|
||||
#include "FluidSim/Constants.hpp"
|
||||
#include "FluidSim/ScenarioGeometry.hpp"
|
||||
#include "FluidSim/ScenarioCatalog.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace fluidsim::port {
|
||||
FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset preset)
|
||||
: FourStrokeScenario(sample_rate,preset_engine_definition(preset)) {}
|
||||
|
||||
FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selected_preset, ExhaustConfig exhaust, V8ExhaustConfig v8_exhaust, EngineConfig engine)
|
||||
: preset(selected_preset), exhaust_config(exhaust), v8_exhaust_config(v8_exhaust), engine_config(engine),
|
||||
intake_plenum(engine.cylinders ? .001f*engine.cylinders : selected_preset == FourStrokePreset::AmericanV8 ? 4e-3f : selected_preset == FourStrokePreset::SaabB205 ? 3e-3f : 1e-3f, 101325, 300),
|
||||
exhaust_collector(engine.cylinders ? .00025f*engine.cylinders : selected_preset == FourStrokePreset::AmericanV8 ? 1.5e-3f : selected_preset == FourStrokePreset::SaabB205 ? .6e-3f : .15e-3f, 101325, 700),
|
||||
pipes(create_pipes(selected_preset, exhaust, v8_exhaust, engine)), dt_(1.f / std::max(sample_rate, 1)),
|
||||
boundaries_(pipes), solver_(pipes, boundaries_), audio_(sample_rate, [&] {
|
||||
FourStrokeScenario::FourStrokeScenario(int sample_rate,const ScenarioExample& definition)
|
||||
: FourStrokeScenario(sample_rate,definition.engine,definition.exhaust,definition.junctions) {}
|
||||
|
||||
FourStrokeScenario::FourStrokeScenario(int sample_rate, EngineConfig configuration, ExhaustConfig exhaust, JunctionConfig fittings)
|
||||
: exhaust_config(exhaust), junction_config(fittings), engine_config(std::move(configuration)),
|
||||
intake_plenum(engine_config.plenum_volume,101325,300),
|
||||
exhaust_collector(engine_config.collector_volume,101325,700),
|
||||
pipes(create_pipes(engine_config,exhaust)), dt_(1.f/std::max(sample_rate,1)),
|
||||
boundaries_(pipes), solver_(pipes,boundaries_), audio_(sample_rate,[&] {
|
||||
OutdoorAudioConfig config;
|
||||
config.dual_exhaust = selected_preset == FourStrokePreset::AmericanV8;
|
||||
config.dual_exhaust=engine_config.dual_outlets();
|
||||
return config;
|
||||
}()) {
|
||||
const bool saab = preset == FourStrokePreset::SaabB205;
|
||||
const bool v8 = preset == FourStrokePreset::AmericanV8;
|
||||
const int count = cylinder_count();
|
||||
if(engine.cylinders<0 || engine.cylinders>12 || (engine.layout!=EngineLayout::Inline && engine.cylinders%2)) throw std::invalid_argument("invalid engine cylinder layout");
|
||||
starter_torque = v8 ? 180.f : saab ? 65.f : 18.f;
|
||||
crankshaft.omega = (saab ? 1200 : 2000) * 2 * pi / 60;
|
||||
crankshaft.inertia = saab ? .18f : .04f;
|
||||
crankshaft.friction_constant = saab ? 12.f : 5.f;
|
||||
crankshaft.friction_viscous = saab ? .012f : .003f;
|
||||
if (saab) {
|
||||
solver_.minimum_substeps = 2; // Acoustic CFL may subdivide further.
|
||||
// Rough 9-5 wagon with driver; FM55 B05 overall reductions from WIS.
|
||||
vehicle.mass = 1600 + 75;
|
||||
vehicle.primary_reduction = 1;
|
||||
vehicle.final_drive_ratio = 4.05f;
|
||||
vehicle.gear_ratios = {13.70f/4.05f, 7.12f/4.05f, 4.77f/4.05f, 3.62f/4.05f, 2.67f/4.05f};
|
||||
vehicle.wheel_radius = .317f;
|
||||
vehicle.rotational_inertia = 3.6f;
|
||||
vehicle.drag_coefficient = .32f;
|
||||
vehicle.frontal_area = 2.2f;
|
||||
vehicle.clutch_capacity = 300;
|
||||
ecu.idle_rpm = 850;
|
||||
ecu.idle_spark_gain = .025f;
|
||||
ecu.idle_minimum_advance = -10;
|
||||
ecu.rev_limit_rpm = 6500;
|
||||
ecu.rev_resume_rpm = 6250;
|
||||
ecu.overrun_cut_rpm = 2000;
|
||||
ecu.overrun_resume_rpm = 1300;
|
||||
}
|
||||
if (v8) {
|
||||
// Estimated mild 1980s 302-class pushrod small block and manual coupe.
|
||||
crankshaft.omega = 1000 * 2*pi/60;
|
||||
crankshaft.inertia = 1.1f;
|
||||
crankshaft.friction_constant = 22;
|
||||
crankshaft.friction_viscous = .025f;
|
||||
vehicle.mass = 3000;
|
||||
vehicle.primary_reduction = 1;
|
||||
vehicle.final_drive_ratio = 6.16f / 1.7f; // 1.7x taller gearing in every gear.
|
||||
vehicle.gear_ratios = {3.35f, 1.93f, 1.29f, 1.f, .68f};
|
||||
vehicle.wheel_radius = .32f;
|
||||
vehicle.clutch_capacity = 1100; // Hold V8 torque with the heavy flywheel/car.
|
||||
vehicle.drag_coefficient = .4f;
|
||||
vehicle.frontal_area = 2.f;
|
||||
ecu.idle_rpm = 750;
|
||||
ecu.idle_base_opening = .08f;
|
||||
ecu.idle_spark_gain = .02f;
|
||||
ecu.ignition_map = {{0, 10}, {750, 12}, {2500, 28}, {4000, 32}, {6000, 30}};
|
||||
ecu.rev_limit_rpm = 7000;
|
||||
ecu.rev_resume_rpm = 6750;
|
||||
ecu.overrun_cut_rpm = 2000;
|
||||
ecu.overrun_resume_rpm = 1100;
|
||||
// Port-coupled fittings use one minimum substep; acoustic CFL may subdivide further.
|
||||
solver_.minimum_substeps = 1;
|
||||
}
|
||||
if(engine.cylinders) {
|
||||
solver_.minimum_substeps=1;
|
||||
const float litres=count*pi*.25f*engine.bore*engine.bore*engine.stroke*1000;
|
||||
crankshaft.omega=engine.idle_rpm*1.4f*2*pi/60;crankshaft.inertia=std::max(.04f,litres*.12f);
|
||||
crankshaft.friction_constant=litres*4;crankshaft.friction_viscous=litres*.004f;
|
||||
starter_torque=litres*25;vehicle.mass=count<=2?240:1400;vehicle.clutch_capacity=litres*220;
|
||||
ecu.idle_rpm=engine.idle_rpm;ecu.rev_limit_rpm=engine.rev_limit;ecu.rev_resume_rpm=engine.rev_limit-250;
|
||||
ecu.idle_spark_gain=.02f;ecu.idle_base_opening=.05f;
|
||||
if(engine.carbureted) {
|
||||
ecu.idle_control=Ecu::IdleControl::FixedOpening;ecu.idle_base_opening=engine.idle_opening;
|
||||
ecu.overrun_fuel_cut_enabled=false;
|
||||
carb_=std::make_unique<Carburetor>(.085f);
|
||||
const float jet_scale=engine.carb_jet_scale*std::sqrt(litres/.5f);
|
||||
carb_->pilot_jet_diameter*=jet_scale*engine.carb_pilot_scale;carb_->main_jet_diameter*=jet_scale;carb_->needle_jet_diameter*=jet_scale;
|
||||
carb_->bowl_capacity=.1f;carb_->bowl_refill_rate=.1f;
|
||||
intake_plenum.add_fuel_mass(intake_plenum.mass()/14.7f);
|
||||
}
|
||||
const auto& engine=engine_config;
|
||||
const auto& junctions=junction_config;
|
||||
const bool v8=engine.american_headers(),staged=engine.staged_headers();
|
||||
const int count=cylinder_count();
|
||||
static_cast<CrankshaftConfig&>(crankshaft)=engine.crankshaft;
|
||||
static_cast<EcuConfig&>(ecu)=engine.ecu;
|
||||
static_cast<VehicleConfig&>(vehicle)=engine.vehicle;
|
||||
crankshaft.omega=engine.initial_rpm*2*pi/60;
|
||||
starter_torque=engine.starter_torque;
|
||||
solver_.minimum_substeps=engine.minimum_substeps;
|
||||
if(engine.carbureted) {
|
||||
carb_=std::make_unique<Carburetor>(engine.throttle_diameter);
|
||||
static_cast<CarburetorConfig&>(*carb_)=engine.fuel;
|
||||
// Carburetors keep metering fuel during both limiter and overrun cuts.
|
||||
ecu.rev_limit_fuel_cut_enabled=false;
|
||||
ecu.overrun_fuel_cut_enabled=false;
|
||||
ecu.overrun_ignition_cut_enabled=true;
|
||||
intake_plenum.add_fuel_mass(intake_plenum.mass()/14.7f);
|
||||
}
|
||||
vehicle.set_neutral();
|
||||
vehicle.clutch_input = clutch;
|
||||
const auto engines = cylinders();
|
||||
const std::array<float, 8> phases{0, pi, 3*pi, 2*pi}; // I4 firing: 1-3-4-2
|
||||
const std::array<int, 8> firing_order{1, 5, 4, 2, 6, 3, 7, 8};
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto& c = *engines[i];
|
||||
c.phase = phases[i%4];
|
||||
if (v8) {
|
||||
const auto position = std::find(firing_order.begin(), firing_order.end(), i+1) - firing_order.begin();
|
||||
c.phase = std::fmod(4*pi - float(position)*pi*.5f, 4*pi);
|
||||
c.bore = .1016f;
|
||||
c.stroke = .0762f;
|
||||
c.rod = .129f;
|
||||
c.compression = 7.0f;
|
||||
c.intake_open = 350; c.intake_close = 570;
|
||||
c.exhaust_open = 140; c.exhaust_close = 370;
|
||||
c.burn_duration = 65;
|
||||
}
|
||||
if (saab) {
|
||||
c.bore = .090f;
|
||||
c.stroke = .078f;
|
||||
c.rod = .153f; // estimated, not a verified B205 rod measurement
|
||||
c.compression = 8.8f;
|
||||
c.intake_valve_count = c.exhaust_valve_count = 2;
|
||||
}
|
||||
c.intake_valve_diameter = saab ? .032f : .030f;
|
||||
c.intake_valve_lift = saab ? .0085f : .008f;
|
||||
c.exhaust_valve_diameter = saab ? .028f : .026f;
|
||||
c.exhaust_valve_lift = saab ? .008f : .0075f;
|
||||
if (v8) {
|
||||
c.intake_valve_diameter = .045f; c.exhaust_valve_diameter = .037f;
|
||||
c.intake_valve_lift = .010f; c.exhaust_valve_lift = .010f;
|
||||
}
|
||||
if(engine.cylinders) {
|
||||
c.phase=engine.custom_phases?engine.phase_degrees[i]*pi/180:std::fmod(4*pi-i*4*pi/count,4*pi);
|
||||
c.bore=engine.bore;c.stroke=engine.stroke;c.rod=engine.rod;c.compression=engine.compression;
|
||||
c.intake_valve_diameter=engine.bore*.38f;c.exhaust_valve_diameter=engine.bore*.32f;
|
||||
c.intake_valve_lift=engine.bore*.10f;c.exhaust_valve_lift=engine.bore*.09f;
|
||||
c.transported_fuel=engine.carbureted;
|
||||
}
|
||||
c.rev_limit_enabled = true;
|
||||
for (int i=0;i<count;++i) {
|
||||
auto& c=*engines[i];
|
||||
static_cast<CylinderConfig&>(c)=engine.cylinder;
|
||||
c.phase=engine.phase_degrees.at(i)*pi/180;
|
||||
c.transported_fuel=engine.carbureted;
|
||||
c.rev_limit_enabled = ecu.rev_limit_enabled;
|
||||
c.rev_limit_rpm = ecu.rev_limit_rpm;
|
||||
c.rev_limit_hysteresis = 250;
|
||||
c.rev_limit_cut_time = .020f;
|
||||
c.ecu = &ecu;
|
||||
c.initialize(crankshaft);
|
||||
if (saab || v8 || engine.cylinders) {
|
||||
if (engine.initialize_phased_charge) {
|
||||
// Initialize each phased cylinder at its actual piston position.
|
||||
c.volume = c.compute_volume(c.phase);
|
||||
c.air_mass = ambient_density * c.volume;
|
||||
@@ -148,13 +69,13 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
||||
header_area_ = pipes[1+count].area;
|
||||
tail_area_ = pipes[1+2*count].area;
|
||||
for (auto& pipe : pipes) {
|
||||
pipe.damping_multiplier = saab ? 0.f : 1.f;
|
||||
pipe.damping_multiplier = engine.pipe_damping;
|
||||
pipe.energy_relaxation_rate = 0;
|
||||
}
|
||||
auto& throttle_port = intake_plenum.create_port();
|
||||
throttle_port_=&throttle_port;
|
||||
solver_.add_component(intake_plenum);
|
||||
if(!v8) solver_.add_component(exhaust_collector);
|
||||
if(!v8 && !staged) solver_.add_component(exhaust_collector);
|
||||
boundaries_.add_orifice(throttle_port, 0, false, throttle_area_, .7f);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto& feed = intake_plenum.create_port();
|
||||
@@ -164,11 +85,11 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
||||
boundaries_.add_orifice(feed, 1+i, true, runner_area_);
|
||||
boundaries_.add_orifice(engines[i]->intake, 1+i, false, intake_valve_areas_[i]);
|
||||
boundaries_.add_orifice(engines[i]->exhaust, 1+count+i, true, exhaust_valve_areas_[i]);
|
||||
if (!v8) boundaries_.add_orifice(exhaust_collector.create_port(), 1+count+i, false, header_area_);
|
||||
if (!v8 && !staged) boundaries_.add_orifice(exhaust_collector.create_port(), 1+count+i, false, header_area_);
|
||||
}
|
||||
if(v8) {
|
||||
auto junction=[&](std::initializer_list<JunctionEnd> ends) {
|
||||
auto fitting=std::make_unique<Junction>(v8_exhaust.junction_volume,101325,700);
|
||||
auto fitting=std::make_unique<Junction>(junctions.junction_volume,101325,700);
|
||||
fitting->connect(boundaries_,pipes,std::span(ends.begin(),ends.size()));
|
||||
solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting));
|
||||
};
|
||||
@@ -179,7 +100,7 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
||||
junction({{14,false},{16,false},{20,true}});
|
||||
junction({{17,false},{18,false},{21,true}});
|
||||
junction({{19,false},{20,false},{22,true}});
|
||||
if(v8_exhaust.h_pipe) {
|
||||
if(junctions.h_pipe) {
|
||||
junction({{21,false},{23,true},{25,true}});
|
||||
junction({{22,false},{24,true},{25,false}});
|
||||
} else {
|
||||
@@ -189,6 +110,33 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
||||
}
|
||||
boundaries_.add_radiating_open_end(23,false,101325,pipes[23].area);
|
||||
boundaries_.add_radiating_open_end(24,false,101325,pipes[24].area);
|
||||
} else if(staged) {
|
||||
const int exit=1+2*count;
|
||||
const bool dual=engine.dual_outlets();
|
||||
const int groups=engine.header_groups(), secondary=exit+(dual?2:1);
|
||||
auto junction=[&](const std::vector<JunctionEnd>& ends) {
|
||||
auto fitting=std::make_unique<Junction>(junctions.junction_volume,101325,700);
|
||||
fitting->connect(boundaries_,pipes,ends);
|
||||
solver_.add_component(*fitting);exhaust_junctions_.push_back(std::move(fitting));
|
||||
};
|
||||
for(int group=0;group<groups;++group) {
|
||||
std::vector<JunctionEnd> ends;
|
||||
for(int i=0;i<count;++i)
|
||||
if(groups==1 || (i<(count+1)/2?0:1)==group) ends.push_back({1+count+i,false});
|
||||
ends.push_back({secondary+group,true});junction(ends);
|
||||
}
|
||||
if(dual) {
|
||||
// Preserve independent bank pulse trains all the way to their mouths.
|
||||
for(int bank=0;bank<2;++bank) {
|
||||
junction({{secondary+bank,false},{exit+bank,true}});
|
||||
boundaries_.add_radiating_open_end(exit+bank,false,101325,pipes[exit+bank].area);
|
||||
}
|
||||
} else {
|
||||
std::vector<JunctionEnd> ends{{exit,true}};
|
||||
for(int group=0;group<groups;++group) ends.push_back({secondary+group,false});
|
||||
junction(ends);
|
||||
boundaries_.add_radiating_open_end(exit,false,101325,pipes[exit].area);
|
||||
}
|
||||
} else {
|
||||
auto& collector_out = exhaust_collector.create_port();
|
||||
const int exit=1+2*count;
|
||||
@@ -196,11 +144,12 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
||||
boundaries_.add_radiating_open_end(exit,false,101325,pipes[exit].area);
|
||||
}
|
||||
boundaries_.add_radiating_open_end(0, true, 101325, pipes[0].area);
|
||||
audio_.set_source_positions(scenario_sound_sources(snapshot()));
|
||||
}
|
||||
|
||||
AudioFrame FourStrokeScenario::process() {
|
||||
const std::lock_guard guard(mutex_);
|
||||
step_motorcycle(crankshaft, dt_, preset == FourStrokePreset::AmericanV8 ? 300.f : preset == FourStrokePreset::SaabB205 ? 200.f : 30.f);
|
||||
step_motorcycle(crankshaft, dt_, engine_config.maximum_load_torque);
|
||||
ecu.step(dt_, throttle, crankshaft.omega * 60 / (2*pi));
|
||||
const auto engines = cylinders();
|
||||
for (int i = 0; i < cylinder_count(); ++i) {
|
||||
@@ -212,21 +161,21 @@ AudioFrame FourStrokeScenario::process() {
|
||||
}
|
||||
const float opening = ecu.throttle_opening();
|
||||
const float angle = opening * pi * .5f;
|
||||
throttle_area_ = area_for_diameter(engine_config.cylinders ? (engine_config.carbureted?.085f:.065f) : preset == FourStrokePreset::AmericanV8 ? .065f : preset == FourStrokePreset::SaabB205 ? .052f : .026f) * (1-std::cos(angle));
|
||||
throttle_area_ = area_for_diameter(engine_config.throttle_diameter) * (1-std::cos(angle));
|
||||
if(carb_) {
|
||||
const auto inlet=pipes[0].state(int(pipes[0].cells.size())-1);
|
||||
carb_->step(dt_,throttle,opening,crankshaft.omega*60/(2*pi),std::max(throttle_port_->mass_flow,0.f),inlet.rho,inlet.pressure,intake_plenum.pressure());
|
||||
intake_plenum.add_fuel_mass(carb_->fuel_flow()*dt_);
|
||||
}
|
||||
solver_.step(dt_);
|
||||
intake_flow = boundaries_.open_end_mass_flow(preset == FourStrokePreset::AmericanV8 ? 2 : 1);
|
||||
intake_flow = boundaries_.open_end_mass_flow(engine_config.dual_outlets() ? 2 : 1);
|
||||
exhaust_flow = boundaries_.open_end_mass_flow(0);
|
||||
const float other_exhaust = preset == FourStrokePreset::AmericanV8 ? boundaries_.open_end_mass_flow(1) : 0.f;
|
||||
const float other_exhaust = engine_config.dual_outlets() ? boundaries_.open_end_mass_flow(1) : 0.f;
|
||||
// Compact mouth radiation responds to volume velocity. Use ambient-equivalent
|
||||
// mass flow so hot/cold donor-density changes do not enter the derivative.
|
||||
const float intake_source = ambient_density * boundaries_.open_end_volume_flow(preset == FourStrokePreset::AmericanV8 ? 2 : 1);
|
||||
const float intake_source = ambient_density * boundaries_.open_end_volume_flow(engine_config.dual_outlets() ? 2 : 1);
|
||||
const float exhaust_source = ambient_density * boundaries_.open_end_volume_flow(0);
|
||||
const float other_source = preset == FourStrokePreset::AmericanV8 ? ambient_density * boundaries_.open_end_volume_flow(1) : 0.f;
|
||||
const float other_source = engine_config.dual_outlets() ? ambient_density * boundaries_.open_end_volume_flow(1) : 0.f;
|
||||
const auto rendered = audio_.process(intake_source, exhaust_source, other_source);
|
||||
exhaust_flow += other_exhaust;
|
||||
++sample_count;
|
||||
@@ -241,6 +190,9 @@ void FourStrokeScenario::set_pipe_solver(PipeSolver solver) {
|
||||
void FourStrokeScenario::set_audio_scene(OutdoorScene scene) {
|
||||
const std::lock_guard<std::mutex> guard(mutex_); audio_.set_scene(scene);
|
||||
}
|
||||
void FourStrokeScenario::set_listener(AudioListener listener) {
|
||||
const std::lock_guard guard(mutex_); audio_.set_listener(listener);
|
||||
}
|
||||
void FourStrokeScenario::set_monitor_filter(bool enabled) {
|
||||
const std::lock_guard<std::mutex> guard(mutex_); audio_.set_monitor_filter(enabled);
|
||||
}
|
||||
@@ -283,107 +235,18 @@ void FourStrokeScenario::set_first_gear() {
|
||||
vehicle.set_first_gear();
|
||||
}
|
||||
|
||||
ScenarioSnapshot FourStrokeScenario::snapshot() const {
|
||||
const std::lock_guard<std::mutex> guard(mutex_);
|
||||
ScenarioSnapshot result;
|
||||
result.layout=engine_config.cylinders?engine_config.layout:preset==FourStrokePreset::AmericanV8?EngineLayout::V:EngineLayout::Inline;
|
||||
result.bank_angle=engine_config.cylinders?engine_config.bank_angle:56;
|
||||
result.v8_headers=preset==FourStrokePreset::AmericanV8;
|
||||
result.silencers = preset == FourStrokePreset::SaabB205 && exhaust_config.enabled;
|
||||
result.exhaust_config = exhaust_config;
|
||||
result.h_pipe = preset==FourStrokePreset::AmericanV8 && v8_exhaust_config.h_pipe;
|
||||
result.crossover_pressures = preset==FourStrokePreset::AmericanV8 ? std::array<float,2>{pipes[21].state(0).pressure,pipes[22].state(0).pressure} : std::array<float,2>{};
|
||||
result.pipe_solver = pipes.front().solver;
|
||||
result.solver_substeps = solver_.substep_count();
|
||||
result.boundary_iterations = boundaries_.nonlinear_iterations;
|
||||
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 = intake_valve_areas_[0];
|
||||
result.valve1_exhaust_area = exhaust_valve_areas_[0];
|
||||
result.valve2_intake_area = intake_valve_areas_[1];
|
||||
result.valve2_exhaust_area = exhaust_valve_areas_[1];
|
||||
result.intake_pressure = intake_plenum.pressure();
|
||||
result.exhaust_pressure = preset==FourStrokePreset::AmericanV8 ? pipes[21].state(0).pressure : exhaust_collector.pressure();
|
||||
result.second_bank_pressure = preset==FourStrokePreset::AmericanV8 ? pipes[22].state(0).pressure : 101325;
|
||||
result.throttle = ecu.throttle_opening();
|
||||
result.pedal = throttle;
|
||||
result.target_afr = carb_?carb_->indicated_afr():ecu.target_afr();
|
||||
result.idle_control = ecu.idle_active();
|
||||
result.fuel_cut = ecu.fuel_cut();
|
||||
result.audio_scene = audio_.scene();
|
||||
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.cylinder_count = cylinder_count();
|
||||
result.rev_limit_rpm = ecu.rev_limit_rpm;
|
||||
const std::array<const FourStrokeCylinder*, 12> engines{&cylinder1, &cylinder2, &cylinder3, &cylinder4,
|
||||
&cylinder5, &cylinder6, &cylinder7, &cylinder8,&cylinder9,&cylinder10,&cylinder11,&cylinder12};
|
||||
for (int i = 0; i < cylinder_count(); ++i) {
|
||||
result.cylinder_angles[i] = crankshaft.angle + engines[i]->phase;
|
||||
result.cylinder_pressures[i] = engines[i]->pressure;
|
||||
result.piston_fractions[i] = (engines[i]->volume-engines[i]->clearance_volume())/engines[i]->swept_volume();
|
||||
result.intake_areas[i] = intake_valve_areas_[i];
|
||||
result.exhaust_areas[i] = exhaust_valve_areas_[i];
|
||||
}
|
||||
result.capture_pipes(pipes);
|
||||
return result;
|
||||
}
|
||||
|
||||
float FourStrokeScenario::area_for_diameter(float diameter) {
|
||||
return pi * diameter * diameter * 0.25f;
|
||||
}
|
||||
|
||||
std::vector<Pipe1D> FourStrokeScenario::create_pipes(FourStrokePreset preset, const ExhaustConfig& exhaust, const V8ExhaustConfig& v8_exhaust, const EngineConfig& engine) {
|
||||
if(engine.cylinders) {
|
||||
if(engine.cylinders<1||engine.cylinders>12||engine.bore<=0||engine.stroke<=0||engine.rod<=engine.stroke*.5f)throw std::invalid_argument("invalid custom engine dimensions");
|
||||
std::vector<Pipe1D> result;
|
||||
result.emplace_back(area_for_diameter(.075f),.20f,4);
|
||||
for(int i=0;i<engine.cylinders;++i)result.emplace_back(area_for_diameter(.035f),.25f,5);
|
||||
for(int i=0;i<engine.cylinders;++i)result.emplace_back(area_for_diameter(.038f),.65f,13);
|
||||
result.emplace_back(area_for_diameter(.060f),1.5f,30);
|
||||
return result;
|
||||
std::vector<Pipe1D> FourStrokeScenario::create_pipes(const EngineConfig& engine,const ExhaustConfig& exhaust) {
|
||||
validate_engine_config(engine);
|
||||
std::vector<Pipe1D> pipes;
|
||||
for(const auto& pipe:engine.pipes)for(int i=0;i<pipe.count;++i) {
|
||||
if(pipe.silenced)pipes.push_back(make_silenced_exhaust(exhaust));
|
||||
else pipes.emplace_back(area_for_diameter(pipe.diameter),pipe.length,pipe.cells);
|
||||
}
|
||||
const bool saab = preset == FourStrokePreset::SaabB205;
|
||||
if (preset == FourStrokePreset::AmericanV8) {
|
||||
// One inlet/throttle -> shared plenum -> short individual runners.
|
||||
// Bank collectors feed twin pipes joined by a finite-length H crossover.
|
||||
// Cells stay long enough to avoid routine
|
||||
// extra CFL substeps during high-vacuum coast-down.
|
||||
std::vector<Pipe1D> result;
|
||||
result.emplace_back(area_for_diameter(.070f), .18f, 3);
|
||||
for (int i = 0; i < 8; ++i)
|
||||
result.emplace_back(area_for_diameter(.038f), .12f, 2);
|
||||
for (int i = 0; i < 8; ++i)
|
||||
result.emplace_back(area_for_diameter(v8_exhaust.primary_diameter),v8_exhaust.primary_length,int(std::ceil(v8_exhaust.primary_length/.05f)));
|
||||
for(int i=0;i<4;++i) result.emplace_back(area_for_diameter(.045f),.40f,8);
|
||||
for(int i=0;i<2;++i) result.emplace_back(area_for_diameter(.050f),.55f,11);
|
||||
for(int i=0;i<2;++i) result.emplace_back(area_for_diameter(.050f),1.25f,25);
|
||||
result.emplace_back(area_for_diameter(.038f),.78f,16);
|
||||
return result;
|
||||
}
|
||||
const int count = saab ? 4 : 2;
|
||||
std::vector<Pipe1D> result;
|
||||
result.reserve(4+2*count);
|
||||
result.emplace_back(area_for_diameter(saab ? .060f : .0275f), saab ? .15f : .06f, saab ? 8 : 10);
|
||||
for (int i = 0; i < count; ++i)
|
||||
result.emplace_back(area_for_diameter(saab ? .032f : .0275f), saab ? .25f : .12f, 10);
|
||||
for (int i = 0; i < count; ++i)
|
||||
result.emplace_back(area_for_diameter(saab ? .034f : .028f), saab ? .28f : .20f, saab ? 10 : 6);
|
||||
result.emplace_back(area_for_diameter(saab ? .060f : .028f), saab ? 1.4f : .40f, saab ? 40 : 10);
|
||||
if (saab) result.back() = make_silenced_exhaust(exhaust);
|
||||
return result;
|
||||
return pipes;
|
||||
}
|
||||
|
||||
} // namespace fluidsim::port
|
||||
|
||||
Reference in New Issue
Block a user