Add machine workshop, typed logic, mechanics and sensor-driven ECUs
This commit is contained in:
45
include/FluidSim/Desktop/LogicEditor.hpp
Normal file
45
include/FluidSim/Desktop/LogicEditor.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "FluidSim/Machine.hpp"
|
||||
#include "FluidSim/Desktop/WorkshopLayout.hpp"
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace fluidsim::port::desktop {
|
||||
struct LogicEditorHooks {
|
||||
MachineDocument& document;
|
||||
const MachineSnapshot* live{};
|
||||
bool shift{};
|
||||
std::function<bool(const std::function<void()>&)> edit;
|
||||
std::function<void(std::uint32_t,bool)> boolean_control;
|
||||
std::function<void(std::uint32_t,double)> number_control;
|
||||
std::function<void()> release_buttons;
|
||||
std::function<void(std::uint32_t)> select_physical,focus_physical;
|
||||
std::function<void(const std::string&)> status;
|
||||
std::function<void()> example;
|
||||
};
|
||||
class LogicEditor {
|
||||
public:
|
||||
LogicEditor();
|
||||
~LogicEditor();
|
||||
void reset_document();
|
||||
void deactivate(const LogicEditorHooks&);
|
||||
bool editing_text()const;
|
||||
bool active_gesture()const;
|
||||
std::uint32_t selected_node()const;
|
||||
void select_node(std::uint32_t,const MachineDocument&);
|
||||
void select_part_reference(std::uint32_t,const MachineDocument&);
|
||||
void fit(const LogicGraph&,sf::FloatRect,bool selection_only=false);
|
||||
void reveal(std::uint32_t,const MachineDocument&,sf::FloatRect);
|
||||
sf::Vector2f project(LogicPosition,sf::FloatRect)const;
|
||||
LogicPosition unproject(sf::Vector2f,sf::FloatRect)const;
|
||||
sf::FloatRect node_rect(const LogicNode&,sf::FloatRect)const;
|
||||
sf::Vector2f pin_position(const LogicNode&,const std::string&,sf::FloatRect)const;
|
||||
// Panels can be owned independently in split view; the canvas is always live.
|
||||
bool event(const sf::Event&,const LogicEditorHooks&,const WorkshopLayout&,sf::FloatRect canvas,bool panels);
|
||||
void draw(sf::RenderWindow&,const sf::Font*,const LogicEditorHooks&,const WorkshopLayout&,sf::FloatRect canvas,bool panels);
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
}
|
||||
57
include/FluidSim/Desktop/MachineBuilder.hpp
Normal file
57
include/FluidSim/Desktop/MachineBuilder.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include "FluidSim/Machine.hpp"
|
||||
#include "FluidSim/Desktop/OrbitCamera.hpp"
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace fluidsim::port::desktop {
|
||||
struct BuilderRay {Vec3 origin,direction;};
|
||||
BuilderRay builder_ray(const OrbitCamera&,sf::FloatRect,sf::Vector2f);
|
||||
std::optional<sf::Vector2f> builder_project(const OrbitCamera&,sf::FloatRect,Vec3);
|
||||
// Returns metres along the ray; OBBs and duct centerlines use the rendered dimensions.
|
||||
std::optional<float> builder_pick_part(const MachinePart&,BuilderRay,float tolerance=0);
|
||||
Vec3 builder_rotate_world(Vec3 euler,unsigned axis,float degrees);
|
||||
struct BuilderPipeLocation {std::size_t segment{};Vec3 local_point;float screen_distance{};};
|
||||
std::optional<BuilderPipeLocation> builder_pipe_location(const OrbitCamera&,sf::FloatRect,const MachinePart&,sf::Vector2f);
|
||||
struct MachinePlaybackStats {double simulation_seconds{};std::uint64_t underruns{};std::size_t buffered{};double speed{1};bool paused{};};
|
||||
enum class WorkshopView {ThreeD,Logic,Split};
|
||||
class MachineBuilder {
|
||||
public:
|
||||
MachineBuilder();
|
||||
~MachineBuilder();
|
||||
const MachineDocument& document()const;
|
||||
std::vector<std::uint32_t> selected_parts()const;
|
||||
void set_document(MachineDocument,std::filesystem::path source={});
|
||||
bool load_file(const std::filesystem::path&);
|
||||
void start_test();
|
||||
void stop_test();
|
||||
bool testing()const;
|
||||
MachinePlaybackStats playback_stats()const;
|
||||
void set_test_speed(double);
|
||||
void pause_test();
|
||||
void step_test();
|
||||
std::vector<MachineMeasurement> measurements()const;
|
||||
bool export_measurements(const std::filesystem::path&);
|
||||
bool finished()const;
|
||||
bool wants_close_window()const;
|
||||
bool handle_event(const sf::Event&,sf::Vector2u);
|
||||
void update();
|
||||
void draw(sf::RenderWindow&,const sf::Font*);
|
||||
static sf::FloatRect viewport(sf::Vector2u);
|
||||
sf::FloatRect workspace_viewport(sf::Vector2u)const;
|
||||
sf::FloatRect logic_viewport(sf::Vector2u)const;
|
||||
WorkshopView workspace_view()const;
|
||||
void set_workspace_view(WorkshopView);
|
||||
std::uint32_t selected_logic_node()const;
|
||||
std::vector<LogicNodeState> logic_values()const;
|
||||
sf::FloatRect logic_node_rectangle(std::uint32_t,sf::Vector2u)const;
|
||||
sf::Vector2f logic_pin_position(std::uint32_t,const std::string&,sf::Vector2u)const;
|
||||
void restore_workspace_preferences();
|
||||
const OrbitCamera& camera()const;
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
void run_machine_builder(sf::RenderWindow&,const sf::Font*,const std::filesystem::path& file={},bool play=false);
|
||||
}
|
||||
21
include/FluidSim/Desktop/MachineRenderer3D.hpp
Normal file
21
include/FluidSim/Desktop/MachineRenderer3D.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "FluidSim/Machine.hpp"
|
||||
#include "FluidSim/Desktop/OrbitCamera.hpp"
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <memory>
|
||||
namespace fluidsim::port::desktop {
|
||||
struct MachineRenderStats {std::uint64_t pipe_mesh_builds{},icon_atlas_builds{};std::size_t cached_pipes{};};
|
||||
class MachineRenderer3D {
|
||||
public:
|
||||
MachineRenderer3D();
|
||||
~MachineRenderer3D();
|
||||
MachineRenderer3D(const MachineRenderer3D&)=delete;
|
||||
MachineRenderer3D& operator=(const MachineRenderer3D&)=delete;
|
||||
void draw(sf::RenderWindow&,const MachineDocument&,const MachineSnapshot*,sf::FloatRect,const OrbitCamera&,std::uint32_t selected,const MachinePart* ghost=nullptr,const std::vector<std::uint32_t>& selection={});
|
||||
void draw_icon(sf::RenderWindow&,MachinePartKind,sf::FloatRect);
|
||||
MachineRenderStats stats()const;
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
}
|
||||
10
include/FluidSim/Desktop/PortAppearance.hpp
Normal file
10
include/FluidSim/Desktop/PortAppearance.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "FluidSim/MachinePorts.hpp"
|
||||
#include <SFML/Graphics/Color.hpp>
|
||||
namespace fluidsim::port::desktop {
|
||||
inline sf::Color port_color(PortDomain domain){
|
||||
switch(domain){case PortDomain::Boolean:return {235,92,102};case PortDomain::Number:return {87,201,128};case PortDomain::Gas:return {189,200,214};case PortDomain::Rotational:return {229,184,91};case PortDomain::Translational:return {177,139,220};case PortDomain::EcuAssignment:return {91,161,242};}
|
||||
return sf::Color::White;
|
||||
}
|
||||
inline bool filled_port(const MachinePortDescriptor& descriptor){return descriptor.direction==PortDirection::Output;}
|
||||
}
|
||||
@@ -2,6 +2,26 @@
|
||||
#include "FluidSim/ScenarioCatalog.hpp"
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
namespace fluidsim::port::desktop {
|
||||
std::optional<std::size_t> choose_scenario(sf::RenderWindow&,const sf::Font*,const std::vector<ScenarioExample>&);
|
||||
struct ScenarioMenuChoice {
|
||||
std::optional<std::size_t> example;
|
||||
std::filesystem::path machine;
|
||||
bool play{};
|
||||
};
|
||||
enum class MenuScreen {Home,Workshop,Scenarios};
|
||||
class ScenarioMenu {
|
||||
public:
|
||||
ScenarioMenu(std::vector<ScenarioExample>,std::vector<std::filesystem::path> machines={});
|
||||
~ScenarioMenu();
|
||||
bool handle_event(const sf::Event&,sf::Vector2u);
|
||||
void draw(sf::RenderWindow&,const sf::Font*);
|
||||
bool finished()const;
|
||||
MenuScreen screen()const;
|
||||
std::optional<ScenarioMenuChoice> take_choice();
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
std::optional<ScenarioMenuChoice> choose_scenario(sf::RenderWindow&,const sf::Font*,const std::vector<ScenarioExample>&);
|
||||
}
|
||||
|
||||
8
include/FluidSim/Desktop/WindowPresentation.hpp
Normal file
8
include/FluidSim/Desktop/WindowPresentation.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <SFML/Graphics/RenderWindow.hpp>
|
||||
|
||||
namespace fluidsim::port::desktop {
|
||||
// Maximize the decorated OS window; retain normal restore/minimize controls.
|
||||
void maximize_window(sf::RenderWindow&);
|
||||
bool window_is_maximized(const sf::RenderWindow&);
|
||||
}
|
||||
30
include/FluidSim/Desktop/WorkshopLayout.hpp
Normal file
30
include/FluidSim/Desktop/WorkshopLayout.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <SFML/Graphics/Rect.hpp>
|
||||
#include <SFML/System/Vector2.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace fluidsim::port::desktop {
|
||||
// One geometry source for drawing, picking and viewport projection.
|
||||
struct WorkshopLayout {
|
||||
sf::FloatRect left, right, viewport, library, search;
|
||||
float inspector_x{}, inspector_width{}, height{}, width{};
|
||||
static constexpr float field_top=302, field_height=52;
|
||||
int field_rows()const{return std::max(1,int((height-164-field_top)/field_height));}
|
||||
int library_rows()const{return std::max(1,int(library.size.y/36));}
|
||||
sf::FloatRect field_row(int row)const{return {{inspector_x+12,field_top+row*field_height},{inspector_width-24,48}};}
|
||||
sf::FloatRect field_value(int row)const{auto r=field_row(row);return {{r.position.x,r.position.y+21},{r.size.x-68,26}};}
|
||||
static WorkshopLayout calculate(sf::Vector2u size,float left_width=260,float right_width=340,bool left_closed=false,bool right_closed=false){
|
||||
WorkshopLayout l;l.width=float(size.x);l.height=float(size.y);
|
||||
const float available=std::max(480.f,l.width-440);
|
||||
float lw=left_closed?28.f:std::clamp(left_width,220.f,420.f),rw=right_closed?28.f:std::clamp(right_width,280.f,520.f);
|
||||
if(lw+rw>available){const float excess=lw+rw-available;rw=std::max(right_closed?28.f:280.f,rw-excess);lw=std::max(left_closed?28.f:180.f,std::min(lw,available-rw));}
|
||||
l.left={{8,96},{lw,std::max(100.f,l.height-154)}};
|
||||
l.right={{l.width-rw-8,96},{rw,std::max(100.f,l.height-154)}};
|
||||
l.inspector_x=l.right.position.x;l.inspector_width=rw;
|
||||
l.viewport={{l.left.position.x+lw+8,96},{std::max(100.f,l.right.position.x-lw-24),std::max(100.f,l.height-154)}};
|
||||
l.search={{20,152},{lw-24,30}};
|
||||
l.library={{20,230},{lw-24,std::max(36.f,l.height-474)}};
|
||||
return l;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user