Improve physical geometry, fluid solvers, audio, and profiling
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
#include "FluidSim/DynoCurve.hpp"
|
||||
#include "FluidSim/Desktop/PedalControl.hpp"
|
||||
#include "FluidSim/Desktop/EngineRenderer3D.hpp"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <SFML/Graphics.hpp>
|
||||
namespace fluidsim::port::desktop {
|
||||
struct PlaybackInfo {
|
||||
@@ -24,8 +26,17 @@ public:
|
||||
bool handle_event(const sf::Event&,sf::Vector2u);
|
||||
AudioListener listener(const ScenarioSnapshot&,sf::Vector2u);
|
||||
void draw(sf::RenderWindow&,const sf::Font*,const ScenarioSnapshot&,const PlaybackInfo&);
|
||||
void configure_resolution(float density) { resolution_=density; }
|
||||
std::optional<float> take_resolution_request() {auto value=resolution_request_;resolution_request_.reset();return value;}
|
||||
void resolution_started() {resolution_busy_=true;resolution_status_="Building mesh...";}
|
||||
void resolution_finished(float density,const std::string& message) {resolution_=density;resolution_busy_=false;resolution_status_=message;}
|
||||
static std::array<sf::FloatRect,2> resolution_buttons(sf::Vector2u size);
|
||||
private:
|
||||
DynoCurve dyno_;
|
||||
float resolution_{20};
|
||||
bool resolution_busy_{};
|
||||
std::optional<float> resolution_request_;
|
||||
std::string resolution_status_;
|
||||
EngineRenderer3D renderer_;
|
||||
OrbitCamera camera_;
|
||||
bool three_d_{true},camera_fitted_{},pan_drag_{},pedal_drag_{};
|
||||
@@ -34,5 +45,8 @@ private:
|
||||
sf::Vector2i previous_mouse_{};
|
||||
bool dragging_{};
|
||||
float zoom_{1};
|
||||
float camera_aspect_{};
|
||||
WorkspaceLayout workspace_layout(sf::Vector2u size) const;
|
||||
void fit_camera(float aspect,const ScenarioSnapshot&);
|
||||
};
|
||||
}
|
||||
|
||||
47
include/FluidSim/Desktop/EngineEditor.hpp
Normal file
47
include/FluidSim/Desktop/EngineEditor.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include "FluidSim/ScenarioCatalog.hpp"
|
||||
#include "FluidSim/ScenarioSnapshot.hpp"
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace fluidsim::port::desktop {
|
||||
// Owns an editable design only. Simulation state is exchanged through snapshots
|
||||
// and explicit actions; mouse handlers never touch running components.
|
||||
class EngineEditor {
|
||||
public:
|
||||
void configure(const ScenarioExample& definition);
|
||||
bool handle_event(const sf::Event& event,sf::Vector2u size);
|
||||
void draw(sf::RenderWindow& window,const sf::Font* font,const ScenarioSnapshot& state) const;
|
||||
bool close();
|
||||
bool visible() const { return visible_; }
|
||||
const EngineConfig& draft() const { return draft_; }
|
||||
std::optional<EngineConfig> take_live_update();
|
||||
std::optional<EngineConfig> take_rebuild_request();
|
||||
bool take_save_request();
|
||||
void finish_rebuild(bool success,const std::string& message);
|
||||
void set_status(std::string message) { status_=std::move(message); }
|
||||
static sf::FloatRect bounds(sf::Vector2u size);
|
||||
static sf::FloatRect toggle_bounds(sf::Vector2u size);
|
||||
private:
|
||||
enum class Tab { Valves, Fuel, Pipes };
|
||||
enum class Key { Mass,Spring,Damping,Preload,SoundArea,SoundDamping,Ignition,Idle,Pilot,Main,Needle,Stoich,Power,Length,Diameter };
|
||||
struct Field { Key key;std::string label,unit;float value,minimum,maximum;int precision; };
|
||||
bool supported_{},visible_{},exhaust_{},live_changed_{},rebuild_requested_{},save_requested_{},busy_{};
|
||||
Tab tab_{Tab::Valves};
|
||||
EngineConfig original_,draft_,rebuilding_;
|
||||
std::vector<PipeConfig> applied_pipes_;
|
||||
std::vector<EngineConfig> undo_,redo_;
|
||||
std::size_t pipe_{};
|
||||
int dragging_field_{-1},dragging_point_{-1};
|
||||
std::string status_{"Drag a cam point or slider. W/S controls the throttle."};
|
||||
std::vector<Field> fields() const;
|
||||
void set_field(const Field& field,float value);
|
||||
void begin_edit();
|
||||
void undo();
|
||||
void redo();
|
||||
void reset();
|
||||
void drag(sf::Vector2f position,sf::Vector2u size);
|
||||
sf::FloatRect graph_bounds(sf::Vector2u size) const;
|
||||
sf::FloatRect field_bounds(sf::Vector2u size,std::size_t index) const;
|
||||
};
|
||||
}
|
||||
@@ -7,8 +7,7 @@
|
||||
#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.
|
||||
// Piecewise straight centerline; knot distances are measured from its geometry.
|
||||
class PipePath {
|
||||
public:
|
||||
explicit PipePath(std::vector<PipeAnchor> anchors):anchors_(std::move(anchors)) {
|
||||
@@ -24,10 +23,8 @@ public:
|
||||
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)};
|
||||
const float t=(distance-a.distance)/(b.distance-a.distance);
|
||||
return {std::lerp(a.position.x,b.position.x,t),std::lerp(a.position.y,b.position.y,t),std::lerp(a.position.z,b.position.z,t)};
|
||||
}
|
||||
Vec3 tangent(float distance) const {
|
||||
const float epsilon=anchors_.back().distance*.0001f;
|
||||
@@ -35,12 +32,21 @@ public:
|
||||
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};
|
||||
Vec3 miter_offset(float distance,Vec3 radial) const {
|
||||
const auto unit=[](Vec3 v){const float n=std::sqrt(v.x*v.x+v.y*v.y+v.z*v.z);return Vec3{v.x/n,v.y/n,v.z/n};};
|
||||
for(std::size_t i=1;i+1<anchors_.size();++i)if(std::abs(distance-anchors_[i].distance)<1e-6f) {
|
||||
const auto a=anchors_[i-1].position,b=anchors_[i].position,c=anchors_[i+1].position;
|
||||
const auto u=unit({b.x-a.x,b.y-a.y,b.z-a.z}),v=unit({c.x-b.x,c.y-b.y,c.z-b.z});
|
||||
const float cosine=std::clamp(u.x*v.x+u.y*v.y+u.z*v.z,-1.f,1.f);
|
||||
if(cosine>.99999f)return radial;
|
||||
const auto bend=unit({u.x-v.x,u.y-v.y,u.z-v.z});
|
||||
const float scale=std::min(3.f,1/std::sqrt(std::max(.0001f,(1+cosine)*.5f)));
|
||||
const float extension=(radial.x*bend.x+radial.y*bend.y+radial.z*bend.z)*(scale-1);
|
||||
return {radial.x+bend.x*extension,radial.y+bend.y*extension,radial.z+bend.z*extension};
|
||||
}
|
||||
return radial;
|
||||
}
|
||||
private:
|
||||
std::vector<PipeAnchor> anchors_;
|
||||
};
|
||||
}
|
||||
|
||||
24
include/FluidSim/Desktop/PlaybackControl.hpp
Normal file
24
include/FluidSim/Desktop/PlaybackControl.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <SFML/Window/Event.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
namespace fluidsim::port::desktop {
|
||||
class PlaybackControl {
|
||||
public:
|
||||
bool realtime() const {return realtime_;}
|
||||
double target_speed() const {return realtime_?1.0:manual_speed_;}
|
||||
bool handle_event(const sf::Event& event,bool shift=false) {
|
||||
if(const auto* key=event.getIf<sf::Event::KeyPressed>()) {
|
||||
if(key->code==sf::Keyboard::Key::Space){realtime_=!realtime_;return true;}
|
||||
if(!realtime_&&key->code==sf::Keyboard::Key::PageUp){adjust(1);return true;}
|
||||
if(!realtime_&&key->code==sf::Keyboard::Key::PageDown){adjust(-1);return true;}
|
||||
}
|
||||
if(const auto* wheel=event.getIf<sf::Event::MouseWheelScrolled>();wheel&&!realtime_&&shift) {adjust(wheel->delta);return true;}
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
bool realtime_{true};
|
||||
double manual_speed_{1};
|
||||
void adjust(double steps){manual_speed_=std::clamp(manual_speed_*std::exp(steps*.12),.0001,1.0);}
|
||||
};
|
||||
}
|
||||
@@ -3,10 +3,29 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
namespace fluidsim::port::desktop {
|
||||
// Fixed absolute scale: vacuum blue, atmosphere green, >=1 MPa red.
|
||||
inline Vec3 pressure_tint(float pressure) {
|
||||
// Keep the established absolute vacuum palette. Above atmosphere, use a
|
||||
// narrower positive range so modest pulses stand out from the green baseline.
|
||||
inline Vec3 pressure_tint(float pressure, bool exhaust=false, float temperature=300.f) {
|
||||
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};
|
||||
const float temperature_boost=std::sqrt(std::clamp(temperature/300.f,.25f,9.f));
|
||||
const float pulse=(pressure-101325.f)*temperature_boost;
|
||||
// Exhaust reaches the warm colors sooner, while using the same continuous
|
||||
// ramp so the atmosphere boundary and pulse edges stay smooth.
|
||||
const float scale=exhaust?45000.f:150000.f;
|
||||
const float t=pulse/(pulse+scale);
|
||||
const auto mix=[](Vec3 a,Vec3 b,float u) {
|
||||
u=u*u*(3.f-2.f*u);
|
||||
return Vec3{a.x+(b.x-a.x)*u,a.y+(b.y-a.y)*u,a.z+(b.z-a.z)*u};
|
||||
};
|
||||
constexpr Vec3 green{.05f,.85f,.05f},yellow{1.f,1.f,.03f},orange{1.f,.52f,.02f},red{1.f,0.f,0.f};
|
||||
if(t<.35f)return mix(green,yellow,t/.35f);
|
||||
if(t<.68f)return mix(yellow,orange,(t-.35f)/.33f);
|
||||
return mix(orange,red,(t-.68f)/.32f);
|
||||
}
|
||||
inline float pressure_expansion(float pressure,float temperature=300.f) {
|
||||
if(pressure<=101325.f)return 0.f;
|
||||
const float temperature_boost=std::sqrt(std::clamp(temperature/300.f,.25f,9.f));
|
||||
const float pulse=(pressure-101325.f)*temperature_boost;
|
||||
return .10f*pulse/(pulse+80000.f);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user