Add American V8 preset, carb fuel transport, and adaptive desktop audio
This commit is contained in:
@@ -37,6 +37,7 @@ public:
|
||||
p.owner_energy = &energy_;
|
||||
p.owner_air_mass = &air_mass_;
|
||||
p.owner_exhaust_mass = &exhaust_mass_;
|
||||
p.owner_fuel_mass = &fuel_mass_;
|
||||
sync_port(p);
|
||||
return p;
|
||||
}
|
||||
@@ -44,19 +45,30 @@ public:
|
||||
float density() const { return mass() / std::max(volume_, 1e-12f); }
|
||||
float temperature() const { return pressure() / std::max(density() * gas_constant, 1e-9f); }
|
||||
float volume() const { return volume_; }
|
||||
float mass() const { return air_mass_ + exhaust_mass_; }
|
||||
float mass() const { return air_mass_ + exhaust_mass_ + fuel_mass_; }
|
||||
float internal_energy() const { return energy_; }
|
||||
float air_fraction() const { return air_mass_ / std::max(mass(), 1e-12f); }
|
||||
float fuel_fraction() const { return fuel_mass_ / std::max(mass(), 1e-12f); }
|
||||
float fuel_mass() const { return fuel_mass_; }
|
||||
void add_fuel_mass(float value, float temperature = 300.0f) {
|
||||
if (!std::isfinite(value) || value < 0.0f || !std::isfinite(temperature) || temperature <= 0.0f)
|
||||
throw std::invalid_argument("invalid fuel addition");
|
||||
fuel_mass_ += value;
|
||||
energy_ += value * gas_constant / (gamma - 1.0f) * temperature;
|
||||
sync_ports();
|
||||
}
|
||||
void set_pressure(float value, float temp = -1.0f) {
|
||||
if (!std::isfinite(value) || value <= 0 || !std::isfinite(temp))
|
||||
throw std::invalid_argument("invalid volume pressure/temperature");
|
||||
const float target_t = temp > 0.0f ? temp : temperature();
|
||||
const float total = value * volume_ / (gas_constant * target_t);
|
||||
const float fraction = air_fraction();
|
||||
const float fuel = fuel_fraction();
|
||||
air_mass_ = total * fraction;
|
||||
exhaust_mass_ = total * (1.0f - fraction);
|
||||
fuel_mass_ = total * fuel;
|
||||
exhaust_mass_ = total * std::max(1.0f - fraction - fuel, 0.0f);
|
||||
energy_ = value * volume_ / (gamma - 1.0f);
|
||||
air_roundoff_ = exhaust_roundoff_ = energy_roundoff_ = 0;
|
||||
air_roundoff_ = exhaust_roundoff_ = fuel_roundoff_ = energy_roundoff_ = 0;
|
||||
sync_ports();
|
||||
}
|
||||
void update(float dt) {
|
||||
@@ -64,16 +76,20 @@ public:
|
||||
throw std::invalid_argument("invalid volume timestep");
|
||||
if (dt == 0)
|
||||
return;
|
||||
double d_air = 0, d_exhaust = 0, d_energy = 0;
|
||||
double d_air = 0, d_exhaust = 0, d_fuel = 0, d_energy = 0;
|
||||
const float fraction = air_fraction();
|
||||
const float fuel = fuel_fraction();
|
||||
for (const Port &p : ports_) {
|
||||
const float flow_fraction = p.mass_flow >= 0.0f ? p.air_fraction : fraction;
|
||||
const float flow_fuel = p.mass_flow >= 0.0f ? p.fuel_fraction : fuel;
|
||||
d_air += double(p.mass_flow) * flow_fraction;
|
||||
d_exhaust += double(p.mass_flow) * (1.0f - flow_fraction);
|
||||
d_fuel += double(p.mass_flow) * flow_fuel;
|
||||
d_exhaust += double(p.mass_flow) * std::max(1.0f - flow_fraction - flow_fuel, 0.0f);
|
||||
d_energy += double(p.mass_flow) * p.enthalpy;
|
||||
}
|
||||
accumulate(air_mass_, air_roundoff_, d_air * dt);
|
||||
accumulate(exhaust_mass_, exhaust_roundoff_, d_exhaust * dt);
|
||||
accumulate(fuel_mass_, fuel_roundoff_, d_fuel * dt);
|
||||
accumulate(energy_, energy_roundoff_, d_energy * dt);
|
||||
const float new_volume = volume_ + volume_change_rate * dt;
|
||||
if (!(new_volume > 0) || !std::isfinite(new_volume))
|
||||
@@ -93,10 +109,11 @@ public:
|
||||
if (!(mass() > 0) || !(energy_ > 0) || !std::isfinite(mass()) || !std::isfinite(energy_))
|
||||
throw std::runtime_error(
|
||||
"nonphysical volume state; reduce timestep or check external port fluxes");
|
||||
if (air_mass_ < -1e-12f || exhaust_mass_ < -1e-12f)
|
||||
if (air_mass_ < -1e-12f || exhaust_mass_ < -1e-12f || fuel_mass_ < -1e-12f)
|
||||
throw std::runtime_error("negative volume species mass");
|
||||
air_mass_ = std::max(air_mass_, 0.0f);
|
||||
exhaust_mass_ = std::max(exhaust_mass_, 0.0f);
|
||||
fuel_mass_ = std::max(fuel_mass_, 0.0f);
|
||||
sync_ports();
|
||||
}
|
||||
void update_state(float dt) override { update(dt); }
|
||||
@@ -110,9 +127,9 @@ public:
|
||||
const std::deque<Port> &ports() const { return ports_; }
|
||||
|
||||
private:
|
||||
float volume_, air_mass_{}, exhaust_mass_{}, energy_;
|
||||
float volume_, air_mass_{}, exhaust_mass_{}, fuel_mass_{}, energy_;
|
||||
// Preserve sub-ulp transfers without changing the float port/budget ABI.
|
||||
double air_roundoff_{}, exhaust_roundoff_{}, energy_roundoff_{};
|
||||
double air_roundoff_{}, exhaust_roundoff_{}, fuel_roundoff_{}, energy_roundoff_{};
|
||||
static void accumulate(float& value, double& roundoff, double increment) {
|
||||
const double precise = double(value)+roundoff+increment;
|
||||
value = float(precise);
|
||||
@@ -127,6 +144,7 @@ private:
|
||||
p.temperature = temperature();
|
||||
p.enthalpy = gamma / (gamma - 1.0f) * p.pressure / std::max(p.density, 1e-12f);
|
||||
p.air_fraction = air_fraction();
|
||||
p.fuel_fraction = fuel_fraction();
|
||||
}
|
||||
void sync_ports() {
|
||||
for (Port &p : ports_)
|
||||
@@ -138,16 +156,18 @@ struct GasCell {
|
||||
float rho{1.225f}, momentum{};
|
||||
float energy{1.225f * (101325.0f / (pipe_gm1 * 1.225f))};
|
||||
float air{1.0f};
|
||||
float fuel{};
|
||||
};
|
||||
struct GasState {
|
||||
float rho{1.225f}, velocity{}, pressure{101325.0f}, air{1.0f};
|
||||
float fuel{};
|
||||
};
|
||||
|
||||
class Pipe1D {
|
||||
public:
|
||||
// Integrated face fluxes: kg/s, N, W, kg/s of fresh air, positive along +x.
|
||||
// Integrated face fluxes: kg/s, N, W, kg/s of fresh air and fuel, positive along +x.
|
||||
struct Flux {
|
||||
float m{}, p{}, e{}, y{};
|
||||
float m{}, p{}, e{}, y{}, fuel{};
|
||||
};
|
||||
float area{}, dx{};
|
||||
float damping_multiplier{0.0f}, energy_relaxation_rate{0.0f}, ambient_pressure{101325.0f};
|
||||
@@ -221,7 +241,9 @@ public:
|
||||
}
|
||||
static GasState primitive(const GasCell &c) {
|
||||
const float u = c.momentum / std::max(c.rho, 1e-12f);
|
||||
return {c.rho, u, pipe_gm1 * (c.energy - .5f * c.momentum * u), std::clamp(c.air, 0.0f, 1.0f)};
|
||||
const float air = std::clamp(c.air, 0.0f, 1.0f);
|
||||
return {c.rho, u, pipe_gm1 * (c.energy - .5f * c.momentum * u), air,
|
||||
std::clamp(c.fuel, 0.0f, 1.0f - air)};
|
||||
}
|
||||
static float wave(const GasState &s) {
|
||||
return std::abs(s.velocity) + std::sqrt(gamma * s.pressure / s.rho);
|
||||
@@ -253,7 +275,9 @@ public:
|
||||
float mom = cells[i].momentum - factor * (fr.p - fl.p - states_[i].pressure * (ar - al));
|
||||
float en = cells[i].energy - factor * (fr.e - fl.e);
|
||||
const float air = cells[i].rho * cells[i].air - factor * (fr.y - fl.y);
|
||||
if (!std::isfinite(rho) || !std::isfinite(mom) || !std::isfinite(en) || !std::isfinite(air))
|
||||
const float fuel = cells[i].rho * cells[i].fuel - factor * (fr.fuel - fl.fuel);
|
||||
if (!std::isfinite(rho) || !std::isfinite(mom) || !std::isfinite(en) ||
|
||||
!std::isfinite(air) || !std::isfinite(fuel))
|
||||
throw std::runtime_error("nonfinite finite-volume update");
|
||||
if (rho < 1e-12f) {
|
||||
rho = 1e-12f;
|
||||
@@ -273,14 +297,18 @@ public:
|
||||
// than 1.18e-38 of the gas mass. Avoid repeatedly transporting
|
||||
// subnormal concentrations; total gas mass and energy are unchanged.
|
||||
const float fraction = std::clamp(air / rho, 0.0f, 1.0f);
|
||||
const float fuel_fraction = std::clamp(fuel / rho, 0.0f, 1.0f - fraction);
|
||||
next_cells_[i] = {rho, mom, en,
|
||||
fraction < std::numeric_limits<float>::min() ? 0.0f : fraction};
|
||||
fraction < std::numeric_limits<float>::min() ? 0.0f : fraction,
|
||||
fuel_fraction < std::numeric_limits<float>::min() ? 0.0f : fuel_fraction};
|
||||
}
|
||||
cells.swap(next_cells_);
|
||||
prepared_ = false;
|
||||
left_override_ = right_override_ = false;
|
||||
}
|
||||
static Flux scaled(Flux f, float a) { return {f.m * a, f.p * a, f.e * a, f.y * a}; }
|
||||
static Flux scaled(Flux f, float a) {
|
||||
return {f.m * a, f.p * a, f.e * a, f.y * a, f.fuel * a};
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<GasCell> next_cells_;
|
||||
@@ -301,7 +329,8 @@ private:
|
||||
return {.5f * (ma + mb - c * (b.rho - a.rho)),
|
||||
.5f * (ma * a.velocity + a.pressure + mb * b.velocity + b.pressure - c * (mb - ma)),
|
||||
.5f * ((ea + a.pressure) * a.velocity + (eb + b.pressure) * b.velocity - c * (eb - ea)),
|
||||
.5f * (ma * a.air + mb * b.air - c * (b.rho * b.air - a.rho * a.air))};
|
||||
.5f * (ma * a.air + mb * b.air - c * (b.rho * b.air - a.rho * a.air)),
|
||||
.5f * (ma * a.fuel + mb * b.fuel - c * (b.rho * b.fuel - a.rho * a.fuel))};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -329,7 +358,8 @@ public:
|
||||
[&](const Budget &b) { return b.air == port.owner_air_mass; });
|
||||
if (it == budgets_.end()) {
|
||||
o.budget = budgets_.size();
|
||||
budgets_.push_back({port.owner_air_mass, port.owner_exhaust_mass, port.owner_energy});
|
||||
budgets_.push_back({port.owner_air_mass, port.owner_exhaust_mass,
|
||||
port.owner_fuel_mass, port.owner_energy});
|
||||
} else
|
||||
o.budget = static_cast<std::size_t>(it - budgets_.begin());
|
||||
}
|
||||
@@ -385,7 +415,7 @@ public:
|
||||
if (!std::isfinite(dt) || dt <= 0)
|
||||
throw std::invalid_argument("boundary timestep must be positive");
|
||||
for (auto &b : budgets_) {
|
||||
b.mass = .25f * (*b.air + (b.exhaust ? *b.exhaust : 0));
|
||||
b.mass = .25f * (*b.air + (b.exhaust ? *b.exhaust : 0) + (b.fuel ? *b.fuel : 0));
|
||||
b.heat = b.energy ? .25f * (*b.energy) : std::numeric_limits<float>::max();
|
||||
}
|
||||
for (auto &o : orifices_)
|
||||
@@ -398,7 +428,7 @@ public:
|
||||
|
||||
private:
|
||||
struct Budget {
|
||||
float *air{}, *exhaust{}, *energy{};
|
||||
float *air{}, *exhaust{}, *fuel{}, *energy{};
|
||||
float mass{}, heat{};
|
||||
};
|
||||
struct Orifice {
|
||||
@@ -587,15 +617,18 @@ private:
|
||||
o.length > 0 ? .5f * (v.pressure + s.pressure) : static_cast<float>(duct_pressure);
|
||||
const float direction = o.left ? 1.f : -1.f;
|
||||
const float species = from ? v.air_fraction : s.air;
|
||||
const float fuel = from ? v.fuel_fraction : s.fuel;
|
||||
const float signed_mass = direction * mdot;
|
||||
p.set_boundary_flux(o.left, {signed_mass,
|
||||
mdot * velocity + face_pressure * area + s.pressure * (face_area - area),
|
||||
signed_mass * h, signed_mass * species});
|
||||
ghost(p, o.left, {density, direction * velocity, face_pressure, species});
|
||||
signed_mass * h, signed_mass * species, signed_mass * fuel});
|
||||
ghost(p, o.left, {density, direction * velocity, face_pressure, species, fuel});
|
||||
v.mass_flow = -mdot;
|
||||
v.enthalpy = h;
|
||||
if (!from)
|
||||
v.air_fraction = s.air;
|
||||
if (!from)
|
||||
v.fuel_fraction = s.fuel;
|
||||
}
|
||||
void resolve_open(Open &o) {
|
||||
auto &p = pipes_[o.pipe];
|
||||
@@ -611,7 +644,9 @@ private:
|
||||
const float u =
|
||||
o.left ? s.velocity - 2 * c / gm1 + 2 * cb / gm1 : s.velocity + 2 * c / gm1 - 2 * cb / gm1;
|
||||
const bool out = o.left ? s.velocity <= -c : s.velocity >= c;
|
||||
GasState g = out ? s : GasState{rho, u, o.pressure, (o.left ? u >= 0 : u <= 0) ? 1.f : s.air};
|
||||
const bool ambient_inflow = o.left ? u >= 0 : u <= 0;
|
||||
GasState g = out ? s : GasState{rho, u, o.pressure, ambient_inflow ? 1.f : s.air,
|
||||
ambient_inflow ? 0.f : s.fuel};
|
||||
ghost(p, o.left, g);
|
||||
const float a = std::min(o.area, p.cell_area(i));
|
||||
auto f = Pipe1D::scaled(o.left ? Pipe1D::riemann(g, s) : Pipe1D::riemann(s, g), a);
|
||||
@@ -631,9 +666,11 @@ private:
|
||||
const float area = std::min(a.cell_area(ia), b.cell_area(ib));
|
||||
auto f = Pipe1D::scaled(Pipe1D::riemann(sa, sb), area);
|
||||
a.set_boundary_flux(o.left_a,
|
||||
{da * f.m, f.p + sa.pressure * (a.cell_area(ia) - area), da * f.e, da * f.y});
|
||||
{da * f.m, f.p + sa.pressure * (a.cell_area(ia) - area), da * f.e,
|
||||
da * f.y, da * f.fuel});
|
||||
b.set_boundary_flux(o.left_b,
|
||||
{db * f.m, f.p + sb.pressure * (b.cell_area(ib) - area), db * f.e, db * f.y});
|
||||
{db * f.m, f.p + sb.pressure * (b.cell_area(ib) - area), db * f.e,
|
||||
db * f.y, db * f.fuel});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user