#pragma once #include #include #include #include namespace fluidsim::port { struct DynoPoint { float power_kw{}; float torque_nm{}; }; // Shared rolling peak curve from the C# Scenario base class. Each bin stores // the greatest observed power and torque independently, with missing bins // interpolated when the engine crosses an RPM range too quickly to sample it. class DynoCurve { public: static constexpr float rpm_bin_size = 50.0f; void reset() { bins_.clear(); last_bin_ = invalid_bin; } void update(float rpm, float power_kw, float torque_nm) { if (!std::isfinite(rpm) || rpm <= 0.0f || !std::isfinite(power_kw) || !std::isfinite(torque_nm)) return; const auto bin = static_cast(rpm / rpm_bin_size); if (bin >= bins_.max_size()) return; while (bins_.size() <= bin) bins_.push_back({}); if (last_bin_ != invalid_bin && bin > last_bin_ + 1) { const float last_power = bins_[last_bin_].power_kw > 0.0f ? bins_[last_bin_].power_kw : 0.0f; const float last_torque = bins_[last_bin_].torque_nm > 0.0f ? bins_[last_bin_].torque_nm : 0.0f; for (std::size_t missing = last_bin_ + 1; missing < bin; ++missing) { const float t = static_cast(missing - last_bin_) / static_cast(bin - last_bin_); const float interpolated_power = last_power + (power_kw - last_power) * t; const float interpolated_torque = last_torque + (torque_nm - last_torque) * t; auto& point = bins_[missing]; if (interpolated_power > point.power_kw || point.power_kw <= 0.0f) point.power_kw = interpolated_power; if (interpolated_torque > point.torque_nm || point.torque_nm <= 0.0f) point.torque_nm = interpolated_torque; } } auto& current = bins_[bin]; if (power_kw > current.power_kw || current.power_kw <= 0.0f) current.power_kw = power_kw; if (torque_nm > current.torque_nm || current.torque_nm <= 0.0f) current.torque_nm = torque_nm; last_bin_ = bin; } const std::vector& bins() const { return bins_; } private: static constexpr std::size_t invalid_bin = static_cast(-1); std::vector bins_; std::size_t last_bin_{invalid_bin}; }; } // namespace fluidsim::port