#pragma once #include "FluidSim/Component.hpp" #include "FluidSim/Constants.hpp" #include #include #include #include #include #include namespace fluidsim::port { class Ecu; struct CrankshaftConfig { float inertia{0.2f}; float friction_constant{}, friction_viscous{}; float friction_quadratic{}; // Nm / (rad/s)^2, dissipative windage term }; class Crankshaft : public CrankshaftConfig { public: float omega{400.0f * 2.0f * pi / 60.0f}; float angle{}, previous_angle{}; float load_torque{}, drivetrain_torque{}; float average_power{}, average_torque{}; void add_torque(float torque); float take_torque(); // Consume gas torque when an external shaft integrator owns motion. void record_output(float torque,float power); void step(float dt); private: float external_torque_{}; std::array power_buffer_{}, torque_buffer_{}; std::size_t power_index_{}, power_count_{}, torque_index_{}, torque_count_{}; float power_sum_{}, torque_sum_{}; }; struct CylinderConfig { float bore{0.075f}, stroke{0.068f}, rod{0.112f}, compression{11.5f}; float intake_valve_diameter{0.030f}, exhaust_valve_diameter{0.028f}; int intake_valve_count{1}, exhaust_valve_count{1}; float spark_advance{20.0f}, wiebe_a{5.0f}, wiebe_m{2.0f}, burn_duration{60.0f}, burn_start{5.0f}; float afr{14.7f}, fuel_lhv{44.0e6f}, energy_variation{0.05f}, misfire_probability{}; float wall_area{0.02f}, heat_transfer{100.0f}, ambient_temperature{300.0f}; }; class EngineCylinder : public Component, public CylinderConfig { public: Ecu* ecu{}; bool transported_fuel{}; float phase{}; bool rev_limit_enabled{}; float rev_limit_rpm{7500.0f}, rev_limit_cut_time{0.05f}, rev_limit_hysteresis{200.0f}; float air_mass{}, exhaust_mass{}, energy{}, volume{}, pressure{101325.0f}; float fuel_mass{}, burn_fraction{}; float fuel_equivalent_mass{}; // passive fuel-origin tracer, unaffected by combustion bool combustion_active{}, fuel_injected{}, rev_limit_active{}; Port intake{}, exhaust{}; std::vector ports() override { return {&intake, &exhaust}; } float swept_volume() const; float clearance_volume() const; float total_mass() const; float temperature() const; float air_fraction() const; float compute_volume(float theta) const; void initialize(const Crankshaft& crank); virtual float valve_area(float cycle_degrees, bool inlet) const = 0; virtual void pre_step(Crankshaft& crank, float dt, float throttle) = 0; void update_state(float dt) override; protected: float energy_factor_{1.0f}; float active_fuel_mass_{}; float rev_limit_timer_{}; std::mt19937 random_{0xF17D51u}; std::uniform_real_distribution unit_{0.0f, 1.0f}; float random_unit(); static float wrap_degrees(float value, float cycle_degrees = 720.0f); static bool crossed_angle(float previous, float current, float event) { return current >= previous ? previous < event && current >= event : event > previous || event <= current; } void update_rev_limiter(float rpm, float dt); void sync_ports(); }; class FourStrokeCylinder final : public EngineCylinder { public: struct IgnitionCommand {bool fire{},cut{true};}; // Present selects external timing exclusively. A fire pulse is consumed // once; missing subsequent commands cannot recreate internal timed sparks. // Requires physically transported fuel, including carburetor operation. std::optional ignition_command; // Once per cycle, before ignition consumes fuel. Independent of spark cut. float charge_afr{std::numeric_limits::quiet_NaN()}; bool charge_sampled{}; struct CamPoint { float angle_degrees{}, lift_metres{}; }; struct ValveDynamics { float mass_kg{0.035f}; float spring_rate_n_per_m{30000.f}; float damping_n_s_per_m{24.f}; float seat_force_n{100.f}; float maximum_lift_m{0.015f}; float seat_restitution{0.005f}, cam_restitution{0.01f}, stop_restitution{0.01f}; }; struct CamProfile { std::vector points; }; void set_valve_profiles(const CamProfile& intake_cam, const ValveDynamics& intake_valve, const CamProfile& exhaust_cam, const ValveDynamics& exhaust_valve); float valve_area(float cycle_degrees, bool inlet) const override; void pre_step(Crankshaft& crank, float dt, float throttle) override; float take_contact_impulse() { const float value=contact_impulse_;contact_impulse_=0;return value; } bool valve_floating() const { return intake_floating_ || exhaust_floating_; } float intake_lift() const { return intake_lift_; } float exhaust_lift() const { return exhaust_lift_; } static float cam_lift_at(const CamProfile& profile,float degrees) { return sample_cam(profile,degrees).lift_metres; } private: float external_burn_angle_{}; bool external_charge_ready_{}; struct ValveState { float lift{},velocity{};bool floating{}; }; const CamProfile* intake_cam_{}; const CamProfile* exhaust_cam_{}; ValveDynamics intake_dynamics_{},exhaust_dynamics_{}; ValveState intake_state_{},exhaust_state_{}; float intake_lift_{},exhaust_lift_{},contact_impulse_{}; bool intake_floating_{},exhaust_floating_{}; static CamPoint sample_cam(const CamProfile& profile,float degrees); float advance_valve(ValveState& state,const CamProfile& cam,const ValveDynamics& dynamics, bool inlet,const Crankshaft& crank,float dt); }; } // namespace fluidsim::port