Improve physical geometry, fluid solvers, audio, and profiling

This commit is contained in:
max
2026-10-30 18:57:56 +01:00
parent bf8621ad39
commit eb2b6366c2
130 changed files with 14649 additions and 862 deletions

View File

@@ -0,0 +1,79 @@
#include "FluidSim/DesktopScenario.hpp"
#include <atomic>
#include <chrono>
#include <cmath>
#include <iostream>
#include <limits>
#include <numeric>
#include <stdexcept>
#include <thread>
using namespace fluidsim::port;
namespace {
void check(bool value,const char* message) {if(!value)throw std::runtime_error(message);}
bool near(float a,float b) {return std::abs(a-b)<1e-5f;}
struct TempDirectory {
std::filesystem::path path=std::filesystem::temp_directory_path()/
("fluidsim-live-tuning-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
TempDirectory(){std::filesystem::create_directory(path);}
~TempDirectory(){std::error_code ignored;std::filesystem::remove_all(path,ignored);}
};
}
int main() {
const auto& source=scenario_example("single-450");
FourStrokeScenario simulation(44100,source);
for(int i=0;i<1024;++i)simulation.process();
const float rpm=simulation.crankshaft.omega,angle=simulation.crankshaft.angle;
const float pressure=simulation.cylinder1.pressure,lift=simulation.cylinder1.intake_lift();
const auto samples=simulation.sample_count;
auto tuned=source.engine;
tuned.intake_valve.mass_kg=.047f;tuned.intake_valve.spring_rate_n_per_m=45000;
tuned.intake_valve.damping_n_s_per_m=32;tuned.valve_sound.radiation_area=.0005f;
tuned.ecu.idle_base_opening=.055f;tuned.fuel.pilot_jet_diameter*=1.1f;
for(auto& p:tuned.intake_cam.points)p.lift_metres*=.8f;
for(auto& p:tuned.ecu.ignition_map)p.degrees_btdc+=4;
simulation.set_tuning(tuned);
check(simulation.crankshaft.omega==rpm && simulation.crankshaft.angle==angle && simulation.sample_count==samples,"live edit restarted the engine");
check(simulation.cylinder1.pressure==pressure && simulation.cylinder1.intake_lift()==lift,"live edit reset cylinder or valve state");
const auto snapshot=simulation.snapshot();
check(near(snapshot.intake_cam_lifts[120],FourStrokeCylinder::cam_lift_at(tuned.intake_cam,480)),"live cam profile was not published");
check(near(simulation.ecu.idle_base_opening,.055f),"ECU calibration was not applied");
for(int i=0;i<2048;++i) {
const auto frame=simulation.process();
check(std::isfinite(frame.left) && std::isfinite(frame.right),"live tuning produced nonfinite audio");
}
auto invalid=tuned;invalid.pipes[0].length*=1.5f;
bool rejected=false;try {simulation.set_tuning(invalid);}catch(const std::invalid_argument&){rejected=true;}
check(rejected,"mesh edits must require a rebuild");
invalid=tuned;invalid.intake_valve.mass_kg=std::numeric_limits<float>::quiet_NaN();
rejected=false;try {simulation.set_tuning(invalid);}catch(const std::exception&){rejected=true;}
check(rejected,"invalid calibration accepted");
TempDirectory temp;
const auto saved=save_tuned_engine(source,tuned,temp.path);
const auto loaded=load_engine_file(saved);
check(loaded.id=="single-450-tuned" && loaded.source_path!=source.source_path,"export replaced source identity");
check(near(loaded.engine.intake_valve.mass_kg,tuned.intake_valve.mass_kg),"valve mass export changed units");
check(near(loaded.engine.fuel.pilot_jet_diameter,tuned.fuel.pilot_jet_diameter),"carburetor edit was not exported");
check(near(loaded.engine.ecu.ignition_map[0].degrees_btdc,tuned.ecu.ignition_map[0].degrees_btdc),"ignition trim was not exported");
check(near(loaded.engine.intake_cam.points[10].lift_metres,tuned.intake_cam.points[10].lift_metres),"cam profile export changed units");
auto at_limit=tuned;at_limit.intake_cam.points[10].lift_metres=at_limit.intake_valve.maximum_lift_m;
check(save_tuned_engine(source,at_limit,temp.path)!=saved,"repeat export overwrote an engine");
DesktopScenario desktop(source);
desktop.set_controls(.2f,.1f,.6f);desktop.set_pipe_solver(PipeSolver::Hlle);desktop.set_audio_scene(OutdoorScene::Dry);
auto pipes=tuned;auto& route=pipes.geometry.pipes[0];const auto end=route.points.back(),start=route.points.front();
route.points.front()={end.x+(start.x-end.x)*1.2f,end.y+(start.y-end.y)*1.2f,end.z+(start.z-end.z)*1.2f};
pipes.pipes[0].length=route.length();
desktop.rebuild_engine(pipes);
const auto rebuilt=desktop.snapshot();
const float length=std::accumulate(rebuilt.pipe_lengths[0].begin(),rebuilt.pipe_lengths[0].end(),0.f);
check(near(length,pipes.pipes[0].length),"rebuilt mesh uses old dimensions");
check(rebuilt.pipe_solver==PipeSolver::Hlle && rebuilt.audio_scene==OutdoorScene::Dry && near(rebuilt.clutch,.6f),"rebuild lost scene or controls");
std::atomic<bool> finite{true};
std::jthread worker([&]{for(int i=0;i<4096;++i) {auto f=desktop.process();if(!std::isfinite(f.left)||!std::isfinite(f.right))finite=false;}});
for(int i=0;i<16;++i) {auto live=pipes;live.intake_valve.mass_kg+=float(i)*.0001f;desktop.set_tuning(live);desktop.snapshot();}
desktop.rebuild_engine(pipes);
worker.join();check(finite,"concurrent editing or rebuild corrupted the simulation");
std::cout<<"Live tuning state, export round trip, mesh rebuild and concurrent publication passed\n";
}