Add machine workshop, typed logic, mechanics and sensor-driven ECUs
This commit is contained in:
288
include/FluidSim/Machine.hpp
Normal file
288
include/FluidSim/Machine.hpp
Normal file
@@ -0,0 +1,288 @@
|
||||
#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 <cstdint>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<float> fuel_rpm{0,6000},fuel_tps{0,1};
|
||||
std::vector<float> fuel_per_cycle{.000002f,.00002f,.000002f,.00002f};
|
||||
float displacement{.0003f},air_gas_constant{287},target_afr{14.7f};
|
||||
std::vector<float> 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<Vec3> 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<float> 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<Vec3> port_coordinates;
|
||||
// Stable identities paired with custom chamber/junction port coordinates.
|
||||
std::vector<std::string> port_keys;
|
||||
EcuConfig ecu;
|
||||
MachineSensorEcuConfig sensor_ecu;
|
||||
std::optional<MachineInjectorConfig> 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<float,machine_probe_limit> values{}; // SI; undefined readings are NaN
|
||||
std::array<MachineReadingState,machine_probe_limit> 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<MachinePart> parts;
|
||||
std::vector<MachineLink> links;
|
||||
std::vector<MachineProbe> probes;
|
||||
std::vector<MachineSensor> 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<std::uint32_t>& 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<Vec3> 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<Vec3> machine_pipe_path(const MachinePart&);
|
||||
float machine_pipe_length(const MachinePart&);
|
||||
std::vector<float> machine_pipe_diameters(const MachinePart&);
|
||||
std::vector<PipeSection> 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<std::string> machine_diagnostics(const MachineDocument&);
|
||||
struct MachineConnectionPreview {
|
||||
bool allowed{};
|
||||
std::string message;
|
||||
std::optional<MachinePart> 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<MachineCheck> machine_checks(const MachineDocument&);
|
||||
std::filesystem::path machine_directory();
|
||||
std::vector<std::filesystem::path> machine_files();
|
||||
std::filesystem::path machine_assembly_directory();
|
||||
std::vector<std::filesystem::path> machine_assembly_files();
|
||||
// Center of the selected world geometry, including routed pipes.
|
||||
Vec3 machine_selection_center(const MachineDocument&,const std::vector<std::uint32_t>&);
|
||||
// Stores only selected parts, internal links and their probes, relative to the center.
|
||||
MachineDocument machine_extract_assembly(const MachineDocument&,const std::vector<std::uint32_t>&,std::string name={});
|
||||
// Inserts an independent copy atomically; returned IDs follow assembly.parts order.
|
||||
std::vector<std::uint32_t> 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<float> 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<MachinePartState> parts;
|
||||
std::uint32_t primary_shaft{};
|
||||
std::vector<ShaftState> shafts;
|
||||
std::vector<MachineSensorState> sensors;
|
||||
std::vector<LogicNodeState> 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> impl_;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user