all of the changes

This commit is contained in:
2026-10-23 18:29:51 +02:00
parent 4c435fdd76
commit 5607dcf2b5
73 changed files with 4365 additions and 2059 deletions

View File

@@ -0,0 +1,37 @@
#pragma once
#include "FluidSim/Scenario.hpp"
#include "FluidSim/DynoCurve.hpp"
#include "FluidSim/Desktop/PedalControl.hpp"
#include "FluidSim/Desktop/EngineRenderer3D.hpp"
#include <SFML/Graphics.hpp>
namespace fluidsim::port::desktop {
struct PlaybackInfo {
float pedal_target{},load{};
double speed{1};
std::size_t buffered{},capacity{};
std::uint64_t underruns{};
bool auto_gain{},time_warp{};
};
struct WorkspaceLayout {
sf::FloatRect sidebar,viewport,chart,inspector,pedal;
static WorkspaceLayout calculate(sf::Vector2u size);
};
class DesktopUi {
public:
float pedal() const {return pedal_.value();}
void nudge_pedal(float amount) {pedal_.set(pedal_.value()+amount);}
void update_pedal(float dt,bool raise,bool lower) {if(!pedal_drag_)pedal_.advance(dt,raise,lower);}
bool handle_event(const sf::Event&,sf::Vector2u);
void draw(sf::RenderWindow&,const sf::Font*,const ScenarioSnapshot&,const PlaybackInfo&);
private:
DynoCurve dyno_;
EngineRenderer3D renderer_;
OrbitCamera camera_;
bool three_d_{true},camera_fitted_{},pan_drag_{},pedal_drag_{};
PedalControl pedal_;
sf::Vector2f pan_{};
sf::Vector2i previous_mouse_{};
bool dragging_{};
float zoom_{1};
};
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include "FluidSim/DynoCurve.hpp"
#include <SFML/Graphics.hpp>
namespace fluidsim::port::desktop {
void draw_dyno_curve(sf::RenderWindow&,const sf::Font*,const DynoCurve&,float,float,float,float,float,float);
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "FluidSim/Scenario.hpp"
#include "FluidSim/Desktop/OrbitCamera.hpp"
#include <SFML/Graphics.hpp>
#include <memory>
namespace fluidsim::port::desktop {
// Visualization consumes snapshots only; it never accesses mutable solver state.
class EngineRenderer3D {
public:
EngineRenderer3D();
~EngineRenderer3D();
EngineRenderer3D(const EngineRenderer3D&)=delete;
EngineRenderer3D& operator=(const EngineRenderer3D&)=delete;
void draw(sf::RenderWindow&,const ScenarioSnapshot&,sf::FloatRect,const OrbitCamera&);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include "FluidSim/Scenario.hpp"
#include <SFML/Graphics.hpp>
namespace fluidsim::port::desktop {
void draw_schematic(sf::RenderWindow&, const sf::Font*, const ScenarioSnapshot&);
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include "FluidSim/AudioProcessing.hpp"
#include <SFML/System/Vector2.hpp>
#include <algorithm>
#include <cmath>
namespace fluidsim::port::desktop {
struct OrbitCamera {
Vec3 target{.12f,.25f,0};
float yaw{-28},pitch{24},distance{2.2f};
void reset(float aspect) {target={.12f,.25f,0};yaw=-28;pitch=24;distance=std::max(.65f,1.6f/aspect)/.414214f;}
void orbit(sf::Vector2i delta) {yaw+=delta.x*.35f;pitch=std::clamp(pitch+delta.y*.3f,-75.f,80.f);}
void pan(sf::Vector2i delta,float height) {
const float y=yaw*.017453293f,p=pitch*.017453293f,k=distance*.828427f/std::max(height,1.f);
target.x+=k*(-delta.x*std::cos(y)+delta.y*std::sin(p)*std::sin(y));
target.y+=k*delta.y*std::cos(p);
target.z+=k*(-delta.x*std::sin(y)-delta.y*std::sin(p)*std::cos(y));
}
void zoom(float delta) {distance=std::clamp(distance*std::exp(-delta*.12f),.35f,12.f);}
};
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <algorithm>
namespace fluidsim::port::desktop {
// Keyboard input travel is separate from the ECU actuator response.
class PedalControl {
public:
float value() const {return value_;}
void set(float value) {value_=std::clamp(value,0.f,1.f);}
void advance(float dt,bool raise,bool lower) {
if(dt>0) set(value_+(raise && !lower?2.f:-2.f)*dt);
}
private:
float value_{};
};
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include "FluidSim/AudioProcessing.hpp"
#include <algorithm>
#include <cmath>
#include <stdexcept>
#include <vector>
#include <utility>
namespace fluidsim::port::desktop {
struct PipeAnchor {float distance;Vec3 position;};
// Knots are distances along the simulated pipe, in metres. Display positions
// define its schematic route; changing them never changes fluid cell lengths.
class PipePath {
public:
explicit PipePath(std::vector<PipeAnchor> anchors):anchors_(std::move(anchors)) {
if(anchors_.size()<2 || anchors_.front().distance!=0)throw std::invalid_argument("pipe path needs origin and endpoint");
for(std::size_t i=0;i<anchors_.size();++i) {
const auto& p=anchors_[i];
if(!std::isfinite(p.distance) || !std::isfinite(p.position.x) || !std::isfinite(p.position.y) || !std::isfinite(p.position.z) ||
(i && !(p.distance>anchors_[i-1].distance))) throw std::invalid_argument("invalid distance-referenced pipe path");
}
}
Vec3 position(float distance) const {
distance=std::clamp(distance,0.f,anchors_.back().distance);
auto it=std::upper_bound(anchors_.begin(),anchors_.end(),distance,[](float s,const PipeAnchor& a){return s<a.distance;});
std::size_t i=std::clamp<std::size_t>(it-anchors_.begin(),1,anchors_.size()-1)-1;
const auto& a=anchors_[i];const auto& b=anchors_[i+1];
const float span=b.distance-a.distance,t=(distance-a.distance)/span,t2=t*t,t3=t2*t;
const auto ma=tangent_at(i),mb=tangent_at(i+1);
auto coordinate=[&](float pa,float pb,float ta,float tb){return (2*t3-3*t2+1)*pa+(t3-2*t2+t)*span*ta+(-2*t3+3*t2)*pb+(t3-t2)*span*tb;};
return {coordinate(a.position.x,b.position.x,ma.x,mb.x),coordinate(a.position.y,b.position.y,ma.y,mb.y),coordinate(a.position.z,b.position.z,ma.z,mb.z)};
}
Vec3 tangent(float distance) const {
const float epsilon=anchors_.back().distance*.0001f;
const auto a=position(distance-epsilon),b=position(distance+epsilon);
const float x=b.x-a.x,y=b.y-a.y,z=b.z-a.z,n=std::sqrt(x*x+y*y+z*z);
return n>1e-8f?Vec3{x/n,y/n,z/n}:Vec3{1,0,0};
}
private:
Vec3 tangent_at(std::size_t i) const {
const auto& a=anchors_[i?i-1:0];const auto& b=anchors_[std::min(i+1,anchors_.size()-1)];
const float d=b.distance-a.distance;
return {(b.position.x-a.position.x)/d,(b.position.y-a.position.y)/d,(b.position.z-a.position.z)/d};
}
std::vector<PipeAnchor> anchors_;
};
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "FluidSim/AudioProcessing.hpp"
#include <algorithm>
#include <cmath>
namespace fluidsim::port::desktop {
// Fixed absolute scale: vacuum blue, atmosphere green, >=1 MPa red.
inline Vec3 pressure_tint(float pressure) {
if(pressure<=101325) {float t=std::clamp(pressure/101325,0.f,1.f);return {.05f,.15f+.70f*t,1-.95f*t};}
float t=std::sqrt(std::clamp((pressure-101325)/(1000000-101325),0.f,1.f));
return {.05f+.95f*t,.85f-.80f*t,.05f-.03f*t};
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "FluidSim/ScenarioCatalog.hpp"
#include <SFML/Graphics.hpp>
#include <optional>
namespace fluidsim::port::desktop {
std::optional<std::size_t> choose_scenario(sf::RenderWindow&,const sf::Font*,const std::vector<ScenarioExample>&);
}