#pragma once #include "FluidSim/Engine.hpp" #include "FluidSim/PhysicalGeometry.hpp" #include "FluidSim/PipeSolver.hpp" #include "FluidSim/OutdoorAudio.hpp" #include "FluidSim/Ecu.hpp" #include "FluidSim/Carburetor.hpp" #include "FluidSim/FuelInjector.hpp" #include "FluidSim/PipeProfile.hpp" #include "FluidSim/MachinePorts.hpp" #include "FluidSim/Mechanics.hpp" #include "FluidSim/MachineSensors.hpp" #include "FluidSim/MachineLogic.hpp" #include #include #include #include #include #include #include namespace fluidsim::port { namespace machine_math { Vec3 add(Vec3,Vec3); Vec3 subtract(Vec3,Vec3); Vec3 multiply(Vec3,float); float dot(Vec3,Vec3); Vec3 cross(Vec3,Vec3); float length(Vec3); Vec3 unit(Vec3); // Intrinsic XYZ Euler angles, in degrees. Rendering and picking use this transform. Vec3 rotate(Vec3,Vec3 degrees); Vec3 inverse_rotate(Vec3,Vec3 degrees); Vec3 rotate_euler_world(Vec3,unsigned axis,float degrees); } enum class MachinePartKind { Pipe, Chamber, Junction, Throttle, CheckValve, Cylinder, PistonPump, Reservoir, Cap, Carburetor, Ecu, Shaft, Flywheel, DriveMotor, Brake }; inline constexpr int machine_part_kind_count=15; enum class MachineConnectionKind { Orifice, Inertance, Characteristic, Legacy, Control, Mechanical }; inline constexpr int machine_connection_kind_count=6; const char* machine_part_name(MachinePartKind); const char* machine_part_key(MachinePartKind); const char* machine_connection_name(MachineConnectionKind); const char* machine_connection_key(MachineConnectionKind); struct MachineShaftConfig { float inertia{.15f},initial_speed{},initial_angle{}; float friction_constant{},friction_viscous{},friction_quadratic{},starter_torque{}; }; struct MachineDriveConfig {float target_speed{100},torque{1};bool speed_control{true},enabled{true};}; struct MachineBrakeConfig {float constant{1},viscous{},quadratic{};bool enabled{true};}; struct MachineSensorEcuConfig { bool enabled{}; // False preserves the explicitly labeled legacy ECU. SensorEcuConfig::Strategy strategy{SensorEcuConfig::Strategy::Ignition}; std::vector fuel_rpm{0,6000},fuel_tps{0,1}; std::vector fuel_per_cycle{.000002f,.00002f,.000002f,.00002f}; float displacement{.0003f},air_gas_constant{287},target_afr{14.7f}; std::vector ve_rpm{0,6000},ve_pressure{20000,100000},volumetric_efficiency{.9f,.9f,.9f,.9f}; bool operator==(const MachineSensorEcuConfig&)const=default; }; struct MachineInjectorConfig { std::uint32_t ecu{}; std::string ecu_label; int cylinders{1}; // Number of calibrated cylinder doses delivered here. float maximum_flow{.001f},initial_mass{.1f},temperature{300}; bool finite_supply{true}; bool operator==(const MachineInjectorConfig&)const=default; }; struct MachinePart { std::uint32_t id{}; MachinePartKind kind{MachinePartKind::Pipe}; std::string name; Vec3 position{0,.25f,0}, rotation{}, size{.12f,.12f,.12f}; // Pipe centerline in part-local metres. Length is always derived from it. std::vector points{{-.15f,0,0},{.15f,0,0}}; // One diameter per route point, linearly interpolated along each segment. // Empty profiles are legacy/programmatic pipes using the two scalar values. std::vector point_diameters; float diameter{.04f}, outlet_diameter{.04f}; float pressure{101325}, temperature{300}, opening{1}, discharge{.8f}; bool pedal_control{true}; float cracking_pressure{100}, phase_degrees{}, maximum_lift{.008f}; float intake_open{350}, intake_close{580}, exhaust_open{130}, exhaust_close{370}; CylinderConfig cylinder{}; FourStrokeCylinder::ValveDynamics valve{}; FourStrokeCylinder::ValveDynamics exhaust_valve{}; FourStrokeCylinder::CamProfile intake_profile,exhaust_profile; // Normalized body coordinates preserve imported port locations when resized. std::vector port_coordinates; // Stable identities paired with custom chamber/junction port coordinates. std::vector port_keys; EcuConfig ecu; MachineSensorEcuConfig sensor_ecu; std::optional injector; // Explicit chamber attachment. float ignition_tdc_degrees{}; // Calibrated crank pickup angle, 0..360. CarburetorConfig carburetor; bool injection_enabled{true}; // Junction method 0 stores gas in its visible box; 1/2 are zero-volume nodes. int junction_method{}; float turning_loss{}; MachineShaftConfig shaft; MachineDriveConfig drive; MachineBrakeConfig brake; }; struct MachinePort { std::uint32_t part{}; unsigned port{}; bool operator==(const MachinePort&) const = default; }; SensorEcuConfig machine_sensor_ecu_config(const MachinePart&); struct MachineLink { MachinePort a,b; MachineConnectionKind kind{MachineConnectionKind::Orifice}; float discharge{1}, inertance_length{.01f}, loss{}; }; enum class MachineQuantity {Pressure,Temperature,MassFlow,AirFuelRatio,Lambda,ChargeAfr,Rpm,Torque,Power}; inline constexpr std::size_t machine_probe_limit=8; inline constexpr float machine_afr_reading_limit=1000.f; enum class MachineReadingState {Valid,NoFuel,LeanLimit,FuelCut,AwaitingCharge}; const char* machine_reading_state_name(MachineReadingState); const char* machine_quantity_name(MachineQuantity); const char* machine_quantity_unit(MachineQuantity); float machine_quantity_scale(MachineQuantity); bool machine_quantity_supported(const MachinePart&,MachineQuantity); struct MachineProbe { std::uint32_t id{},part{}; std::string name; MachineQuantity quantity{MachineQuantity::Pressure}; float position{.5f}; // fraction of pipe arclength; lumped parts ignore it float reference_afr{14.7f}; // stoichiometric AFR for the selected fuel bool generated_output{}; // Migrated legacy shaft torque/power excludes bench motor/load. bool operator==(const MachineProbe&)const=default; }; struct MachineMeasurement { double time{}; std::array values{}; // SI; undefined readings are NaN std::array states{}; std::size_t count{}; }; struct MachineDocument { std::string name{"Untitled machine"}; float cells_per_meter{40}, drive_rpm{}, shaft_inertia{.15f}, load_torque{}; bool driven{}; // Deprecated calibration fields above are read only by legacy migration. std::uint32_t primary_shaft{}; float friction_constant{.15f},friction_viscous{.002f},friction_quadratic{},starter_torque{15}; PipeSolver solver{PipeSolver::Rusanov}; OutdoorScene scene{OutdoorScene::Wall}; std::uint32_t next_id{1}; std::vector parts; std::vector links; std::vector probes; std::vector sensors; std::uint32_t next_sensor_id{1}; LogicGraph logic; MachinePart* find(std::uint32_t); const MachinePart* find(std::uint32_t) const; MachinePart& add(MachinePartKind,Vec3 position={0,.25f,0}); MachineSensor& add_sensor(MachineSensorKind,std::uint32_t host,float position=.5f); MachineSensor* find_sensor(std::uint32_t); const MachineSensor* find_sensor(std::uint32_t)const; void remove_sensor(std::uint32_t); void remove(std::uint32_t); void disconnect(MachinePort); // At least one endpoint must be a pipe. Its centerline snaps to the other port. void connect(MachinePort,MachinePort,MachineConnectionKind=MachineConnectionKind::Orifice); void sync_connections(std::uint32_t moved_part=0); void transform_selection(const std::vector& ids,Vec3 pivot,Vec3 translation,unsigned rotation_axis=0,float degrees=0); }; bool machine_sensor_supported(const MachinePart&,MachineSensorKind,bool reference=false); MachineSensorStatus machine_sensor_binding(const MachineDocument&,const MachineSensor&); std::optional machine_sensor_position(const MachineDocument&,const MachineSensorTap&); void validate_machine_sensors(const MachineDocument&); bool machine_control_port(const MachinePart&,unsigned); MachinePortDescriptor machine_port_descriptor(const MachinePart&,unsigned); unsigned machine_port_index(const MachinePart&,const std::string& key); PortDomain machine_link_domain(const MachineDocument&,const MachineLink&); bool machine_rotational_part(MachinePartKind); std::uint32_t machine_shaft_for(const MachineDocument&,std::uint32_t part); std::uint32_t machine_primary_shaft(const MachineDocument&); bool machine_shaft_driven(const MachineDocument&,std::uint32_t shaft); // Convert an old shared-shaft document to visible mechanical parts once. void upgrade_machine_mechanics(MachineDocument&); bool machine_port_available(const MachineDocument&,MachinePort); unsigned machine_port_count(const MachinePart&); std::string machine_port_name(const MachinePart&,unsigned); Vec3 machine_local_port(const MachinePart&,unsigned); Vec3 machine_world_point(const MachinePart&,Vec3); Vec3 machine_local_point(const MachinePart&,Vec3); Vec3 machine_port_position(const MachinePart&,unsigned); std::vector machine_pipe_path(const MachinePart&); float machine_pipe_length(const MachinePart&); std::vector machine_pipe_diameters(const MachinePart&); std::vector machine_pipe_sections(const MachinePart&); float machine_pipe_diameter(const MachinePart&,float distance); void machine_insert_pipe_point(MachinePart&,std::size_t segment,Vec3 local_point); void machine_remove_pipe_point(MachinePart&,std::size_t point); float machine_part_volume(const MachinePart&); Vec3 machine_part_size(const MachinePart&); // Drafts may contain unconnected ports. Unconnected duct ends are explicitly capped at test time. void validate_machine(const MachineDocument&); std::vector machine_diagnostics(const MachineDocument&); struct MachineConnectionPreview { bool allowed{}; std::string message; std::optional pipe; }; // Dry-run the actual connection and validation without changing the design. MachineConnectionPreview machine_connection_preview(const MachineDocument&,MachinePort,MachinePort); bool machine_has_fuel_source(const MachineDocument&,std::uint32_t cylinder); struct MachineCheck { std::uint32_t part{}; bool warning{}; std::string title,advice; std::uint32_t sensor{}; std::uint32_t logic_node{}; }; std::vector machine_checks(const MachineDocument&); std::filesystem::path machine_directory(); std::vector machine_files(); std::filesystem::path machine_assembly_directory(); std::vector machine_assembly_files(); // Center of the selected world geometry, including routed pipes. Vec3 machine_selection_center(const MachineDocument&,const std::vector&); // Stores only selected parts, internal links and their probes, relative to the center. MachineDocument machine_extract_assembly(const MachineDocument&,const std::vector&,std::string name={}); // Inserts an independent copy atomically; returned IDs follow assembly.parts order. std::vector machine_insert_assembly(MachineDocument&,const MachineDocument&,Vec3 center); MachineDocument load_machine(const std::filesystem::path&); void save_machine(const MachineDocument&,const std::filesystem::path&); MachineDocument machine_pump_example(); MachineDocument machine_engine_example(); MachineDocument machine_logic_example(); MachineDocument machine_sensor_engine_example(bool injection=false); MachineDocument machine_speed_density_example(); struct ScenarioExample; MachineDocument machine_from_engine(const ScenarioExample&); struct MachinePartState { std::uint32_t id{}; float pressure{101325}, temperature{300}, volume{}, flow{}, piston_fraction{}, intake_lift{}, exhaust_lift{}, opening{}; std::vector pipe_pressures; float fuel_mass{},fuel_flow{},afr{},burn_fraction{}; bool combustion{},fuel_cut{},ignition_cut{},idle_control{},valve_float{},fuel_source{}; bool actuator_enabled{},signal_controlled{}; double command_target{}; double injector_remaining{},injector_delivered{}; bool sensor_fault{},rev_limited{},injector_fault{},injector_empty{}; float spark_advance{}; std::uint32_t shaft{}; double shaft_angle{},shaft_speed{},rotational_inertia{},torque{},power{},kinetic_energy{},mechanical_work{},mechanical_loss{}; }; struct MachineSnapshot { double time{}; float rpm{}, torque{}, power{}; std::uint64_t cells{},substeps{},positivity_repairs{}; float minimum_cell_length{}; std::uint32_t minimum_cell_part{}; std::vector parts; std::uint32_t primary_shaft{}; std::vector shafts; std::vector sensors; std::vector logic; }; // Owns the compiled graph. Editing never mutates a running solver; Test compiles a fresh graph. class MachineSimulation { public: explicit MachineSimulation(const MachineDocument&,int sample_rate=44100); ~MachineSimulation(); MachineSimulation(const MachineSimulation&)=delete; MachineSimulation& operator=(const MachineSimulation&)=delete; AudioFrame process(); void set_controls(float pedal,bool starter=false); void set_listener(AudioListener); MachineSnapshot snapshot() const; MachineMeasurement measure() const; // Latest sample-boundary values, independent of recorded instrument channels. MachineSensorState sense(std::uint32_t sensor)const; void set_logic_control(std::uint32_t node,bool); void set_logic_control(std::uint32_t node,double); void release_logic_buttons(); // Applies safe tuning and instrument changes between samples, preserving state. // Geometry, topology, initial conditions and compiled boundary settings must match. void apply_tuning(const MachineDocument&); private: struct Impl; std::unique_ptr impl_; }; }