/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once #include "application/applicationmode.h" #include "application/editoruipanels.h" #include "input/editormouseinput.h" #include "input/editorkeyboardinput.h" #include "vehicle/Camera.h" #include "scene/sceneeditor.h" #include "scene/scenenode.h" #include "editor/editorTerrain.hpp" #include "editor/editorTerrainStreamer.hpp" #include "editor/editorOrthophoto.hpp" #include "editor/editorTrack.hpp" #include "editor/editorAlignment.hpp" #include "editor/editorSpeed.hpp" #include "editor/editorRoad.hpp" #include "editor/editorProfile.hpp" #include "editor/editorInfra.hpp" #include "editor/editorGauge.hpp" #include #include #include #include #include #include class TAnimModel; class TTrack; class editor_mode : public application_mode, private editor_track::observer { public: // model entering the structure gauge, at its deepest place struct gauge_hit { std::string model; glm::dvec3 point{0.0}; double depth{0.0}; // m TTrack const *track{nullptr}; // nearest one, may be gone by now int path{0}; glm::dvec2 direction{0.0, 1.0}; // of the track there, in the plan }; // constructors editor_mode(); ~editor_mode() override; // methods // initializes internal data structures of the mode. returns: true on success, false otherwise bool init() override; // mode-specific update of simulation data. returns: false on error, true otherwise bool update() override; // maintenance method, called when the mode is activated void enter() override; // maintenance method, called when the mode is deactivated void exit() override; // input handlers void on_key(int Key, int Scancode, int Action, int Mods) override; void on_cursor_pos(double Horizontal, double Vertical) override; void on_mouse_button(int Button, int Action, int Mods) override; void on_scroll(double const Xoffset, double const Yoffset) override; void on_window_resize(int w, int h) override { ; } void on_event_poll() override; bool is_command_processor() const override; void undo_last(); static bool focus_active(); static void set_focus_active(bool isActive); static TCamera& get_camera() { return Camera; } static glm::mat4 projection_matrix(float const Aspect); void toggle_ortho(); static bool change_history() { return m_change_history; } static void set_change_history(bool enabled) { m_change_history = enabled; } static bool settings_open() { return m_settings_open; } static void set_settings_open(bool enabled) { m_settings_open = enabled; } private: // types struct editormode_input { editormouse_input mouse; editorkeyboard_input keyboard; bool init(); void poll(); }; struct state_backup { TCamera camera; bool freefly; bool picking; }; struct stored_profile; struct EditorSnapshot { enum class Action { Move, Rotate, Scale, Add, Delete, TrackEdit, Other, RoadEdit }; Action action{Action::Other}; std::string node_name; // node identifier (basic_node::name()) // direct pointer to node when available; used for in-memory undo/redo lookup scene::basic_node *node_ptr{nullptr}; std::string serialized; // full text for recreate (used for Add/Delete) glm::dvec3 position{0.0, 0.0, 0.0}; glm::vec3 rotation{0.0f, 0.0f, 0.0f}; glm::vec3 scale{1.0f, 1.0f, 1.0f}; UID uuid; // node UUID for reference, used as fallback lookup for deleted/recreated nodes scene::layer_handle layer{null_handle}; // scenery layer of the node, so a recreated node returns to it scene::instance_handle instance{0}; // include of a scenery template which was placed or removed, instead of a node std::vector> tracks; std::vector created; std::vector removed; editor_road::record roads; // what a change of the roads comes to std::vector infra; // objects which followed the paths bool profiles{false}; // the stored grade line designs changed too, these were the ones before std::vector profile_library; }; void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string()); std::vector m_history; // history of changes to nodes, used for undo functionality std::vector g_redo; // methods void update_camera(double const Deltatime); editor_ui *ui() const; void redo_last(); void handle_brush_mouse_hold(int Action, int Button); void apply_rotation_for_new_node(scene::basic_node *node, int rotation_mode, float fixed_rotation_value); // members state_backup m_statebackup; // helper, cached variables to be restored on mode exit editormode_input m_input; static TCamera Camera; // focus (smooth camera fly-to) state static bool m_focus_active; glm::dvec3 m_focus_start_pos{0.0,0.0,0.0}; glm::dvec3 m_focus_target_pos{0.0,0.0,0.0}; glm::vec3 m_focus_start_angle{0.0f}; // camera pitch/yaw/roll at focus start glm::vec3 m_focus_target_angle{0.0f}; // camera pitch/yaw/roll facing the focused object double m_focus_time{0.0}; double m_focus_duration{0.6}; double fTime50Hz{0.0}; // bufor czasu dla komunikacji z PoKeys scene::basic_editor m_editor; scene::basic_node *m_node{nullptr}; // currently selected scene node scene::instance_handle m_instance{0}; // currently selected include of a scenery template; these are handled as a whole include_selection m_include; // parameters of the selected include, changed with the gizmo and in the node properties bool m_include_gesture{false}; // a change of the selected include is under way, and has its undo snapshot already std::vector> m_retired; // models replaced after changes of includes, with the frames left until they're destroyed bool m_takesnapshot{true}; // helper, hints whether snapshot of selected node(s) should be taken before modification bool m_dragging = false; glm::dvec3 oldPos; // world position of the last brush placement bool m_brush_has_last{false}; // false at the start of a brush stroke, so the first placement ignores spacing bool mouseHold{false}; float kMaxPlacementDistance = 200.0f; static bool m_change_history; static bool m_settings_open; double m_ortho_pitch{0.0}; // camera fly-mode (right mouse button held); used to flush motion when it's released command_relay m_camera_relay; bool m_camera_flying{false}; // UI/history settings int m_max_history_size{200}; int m_selected_history_idx{-1}; glm::dvec3 clamp_mouse_offset_to_max(const glm::dvec3 &offset); // the ground under a point the cursor landed on, for things which are to stand on the ground glm::dvec3 placement_on_ground(glm::dvec3 Location); // puts every instance of the model the selected node shows on the ground under it void drop_model_instances(); void render_object_menu(); // focus camera smoothly on specified node void start_focus(scene::basic_node *node, double duration = 0.6); // drops the node straight down onto the nearest surface below (terrain or another object) void snap_to_ground(scene::basic_node *node); // editable terrain patches created in the editor void render_terrain_ui(); // creates a large terrain as a grid of adjacent chunks (each its own editable patch) void create_chunked_terrain(); // manual grid-aligned chunks: add/remove single chunks for fine control float chunk_grid_size() const { return m_terrain_cells * m_terrain_cellsize; } void add_grid_chunk(int Cx, int Cz); void remove_grid_chunk(int Cx, int Cz); // handles a click in chunk-edit mode (add a neighbour, or Shift = delete the clicked chunk) void handle_chunk_edit_click(bool DeleteMode); // commits authored terrain to disk, enables streaming, and exports the scenery void save_scene_with_terrain(); // commits authored terrain to disk and enables streaming void commit_terrain(); // writes the changes to the files of scenery opened for editing (Ctrl+S) void save(); // places include of specified scenery template at the cursor, in the active layer void place_include(std::string const &File, int RotationMode, float FixedRotation); // undo and redo of placing or removing an include: brings removed include back, or removes it void toggle_include(scene::instance_handle Instance); // makes specified include the selected one, 0 clears the selection void select_include(scene::instance_handle Instance); // writes parameter values of the selected include to its directive, and shows the outcome in the scene void apply_include(); // replaces directive of specified include, and the models shown for it void set_include_directive(scene::instance_handle Instance, std::string const &Directive); // undo and redo of an include snapshot void restore_include(EditorSnapshot &Snapshot); // gizmo for the selected include, limited to what the parameters of its template can express void render_include_gizmo(glm::mat4 const &View, glm::mat4 const &Projection, glm::dvec3 const &Camerapos); // exports the scenery in legacy (text) format, with layers hidden in the editor left out of the picture void export_scenery(); // raises/lowers terrain under the cursor while the left mouse button is held in sculpt mode void handle_terrain_sculpt(double Deltatime); // returns the terrain patch (if any) whose footprint covers the given world point editor_terrain *terrain_at(double X, double Z); // gathers every active terrain patch: manually-created ones plus streamed chunks std::vector active_terrains(); // samples the selected model instance's geometry into a new editable terrain patch, then removes it void capture_terrain(); std::vector> m_terrains; // grid-aligned manual chunks, keyed by (cx,cz) on the global chunk grid std::map, std::unique_ptr> m_grid_chunks; bool m_terrain_sculpt{false}; // when true, LMB sculpts terrain instead of picking bool m_chunk_edit{false}; // when true, LMB adds/removes whole chunks int m_terrain_cells{32}; // grid resolution (quads per side) int m_terrain_chunks{4}; // chunks per side for a chunked terrain float m_terrain_cellsize{2.0f}; // metres per quad float m_terrain_baseheight{0.0f}; // flat starting height float m_terrain_brush_radius{12.0f}; float m_terrain_brush_strength{4.0f}; // metres per second while held (one-shot for the buttons) float m_terrain_simplify_error{0.5f}; // flatness tolerance (m) for mesh simplification bool m_terrain_auto_optimize{false}; // auto-simplify edited chunks after sculpting settles double m_terrain_idle{0.0}; // seconds since the last sculpt edit (debounce timer) char m_terrain_texture[128]{""}; // optional ground texture name // streaming terrain that follows the camera (open-world); the editor shares the single // simulation-level instance so authored terrain also renders in the driver / other modes terrain_streamer &m_streamer{EditorTerrain}; int m_stream_radius{2}; bool m_stream_persist{true}; // save edited chunks to disk and load them back // geoportal orthophoto layer drawn under the other viewport overlays void render_orthophoto_ui(); void draw_orthophoto(); // picks up the stored settings and the origin of the current scenery void load_orthophoto_settings(); void save_orthophoto_settings(); editor_orthophoto m_orthophoto; std::string m_orthophoto_scenery; // scenery whose origin is loaded glm::dvec2 m_orthophoto_origin_edit{0.0}; // origin being typed in (northing, easting); applied when editing ends // hierarchy management void add_to_hierarchy(scene::basic_node *node); void remove_from_hierarchy(scene::basic_node *node); scene::basic_node* find_in_hierarchy(const std::string &uuid_str); scene::basic_node* find_node_by_any(scene::basic_node *node_ptr, const std::string &uuid_str, const std::string &name); // clear history/redo pointers that reference the given node (prevent dangling pointers) void nullify_history_pointers(scene::basic_node *node); void render_change_history(); void render_settings(); // area fill: scatters models from a package over a polygon outlined in the viewport void render_area_fill(); void draw_area_fill_outline() const; void add_area_fill_point(); void run_area_fill(); void undo_last_area_fill(); std::vector m_fill_points; // outline vertices (world space) in click order std::vector m_fill_last; // instances created by the most recent fill, for "Undo last fill" std::vector m_fill_custom; // user-assembled package of node templates int m_fill_custom_idx{-1}; model_set_ref m_fill_source; // manual = m_fill_custom, otherwise a user set or node bank group float m_fill_density{200.0f}; // objects per hectare float m_fill_min_spacing{2.0f}; // minimal distance between placed objects (m) bool m_fill_random_rotation{true}; // random yaw; otherwise the Functions panel rotation settings apply float m_fill_scale_min{1.0f}; float m_fill_scale_max{1.0f}; bool m_fill_models_as_ground{true}; // large model instances (terrain tiles) count as ground std::string m_fill_status; // result of the last fill, shown in the panel // ImGuizmo-based transform gizmo for the selected node enum class gizmo_operation { translate, rotate, scale }; void render_gizmo(); // gizmo settings, drawn in the toolset window void render_gizmo_options(); bool m_gizmo_enabled{true}; // master switch for the in-viewport gizmo bool m_gizmo_using{false}; // tracks an ongoing drag, so a single undo snapshot is taken per drag bool m_gizmo_local{false}; // manipulate in the object's local space instead of world space gizmo_operation m_gizmo_op{gizmo_operation::translate}; // current transform mode (translate/rotate/scale) float m_gizmo_snap{1.0f}; // translation snap step (metres) applied while Ctrl is held TTrack *selected_track() const; void render_path_ui(); void draw_track_overlay() const; bool pick_track_handle(); void select_track(scene::basic_node *Node); void render_track_gizmo(); void commit_track_drag(bool const Force); void cancel_track_tools(); void push_track_snapshot(std::vector> States, std::vector Created = {}, std::vector Removed = {}); // false, with the reason, when one of the paths can't be changed, its file not taking the changes back static bool tracks_editable(std::vector const &Tracks, std::string &Reason); void trim_history(); void restore_track_snapshot(EditorSnapshot const &Snapshot, std::vector &Opposite, bool const Undo); editor_track::point_ref m_track_point; std::vector m_track_drag; std::vector> m_track_drag_points; std::vector m_track_joints; // of the neighbours, following the whole track dragged editor_track::snap_target m_track_snap; glm::dvec3 m_track_pivot{0.0}; glm::mat4 m_track_gizmo{1.0f}; bool m_track_gizmo_using{false}; bool m_track_dirty{false}; std::chrono::steady_clock::time_point m_track_last_commit; editor_track::state m_track_field_before; std::array, 3> m_track_materials{}; std::array m_track_material_edited{}; // path whose texture name is being typed // typed names of the track circuits, the sleeper model and its skin, and the event to add, for the path TTrack const *m_track_names_for{nullptr}; std::array m_track_isolated{}; std::array m_track_sleeper_model{}; std::array m_track_sleeper_skin{}; std::array m_track_event{}; int m_track_event_list{0}; std::string m_track_events_missing; void render_path_circuits(TTrack &Track); std::string m_track_mode_notice; // structure gauge along the line of the selected path, shown in the 3d view struct gauge_state { bool open{false}; // window bool enabled{true}; // tunnel along the selected path std::vector profiles; int profile{0}; // railway int road{-1}; TTrack const *track{nullptr}; // scanned std::size_t history{0}; bool pending{false}; // rescan once the edit ends struct { std::vector surface; // triangles of the tunnel std::vector edges; // lines std::vector hits; } line; // along the selected path struct { bool scanned{false}; std::size_t history{0}; std::string profile; std::vector hits; } map; // whole scenery struct { std::vector surface; std::vector edges; } spot; // tunnel around the hit the camera went to int current{-1}; // hit the camera went to bool published{false}; // overlay up to date std::shared_ptr scan; // of the whole scenery, done in slices over the frames } m_gauge; void gauge_load(); // the chosen railway and road outlines gauge::choice gauge_choice() const; std::vector const &gauge_hits() const; void update_gauge(); void scan_gauge(TTrack &Track); void scan_gauge_map(); void step_gauge_map(); void finish_gauge_map(); void gauge_publish(); void gauge_focus(int Index); void gauge_spot(gauge_hit const &Hit); void render_gauge_window(); void render_gauge_hits(); bool render_gauge_choice(char const *Label, int &Index, bool Road); bool render_gauge_outline(int Index); struct route_design { TTrack *from{nullptr}; TTrack *to{nullptr}; editor_track::chain chain; std::string error; std::string status; alignment::design design; alignment::result result; int vertex{-1}; int grip{-1}; bool single{true}; // one curve between its straights, instead of the whole line }; void render_route_ui(); bool render_route_parameters(); bool render_route_vertices(); bool render_route_vertex(int const Index); void render_route_result(); void route_reset(); void route_bind_ends(); bool route_from_curve(TTrack &Track); bool route_from_line(TTrack &Track); alignment::vertex route_vertex_of(editor_track::curve const &Curve) const; bool route_load(TTrack &Track) { return m_route.single ? route_from_curve(Track) : route_from_line(Track); } void route_update() { m_route.result = alignment::compute(m_route.design); } void route_apply(); bool route_relay_through_switches(editor_track::chain const &Chain, std::vector> &States, std::vector &Created); void route_recommend(alignment::vertex &Vertex) const; void draw_route_overlay() const; bool pick_route_vertex(); void render_route_gizmo(); glm::dvec3 route_vertex_position(int const Vertex) const; bool route_active() const; route_design m_route; struct straights_state { editor_track::straight_tolerance tolerance; double minimum_length{50.0}; std::vector found; int listed{-1}; TTrack *current_for{nullptr}; editor_track::straight current; std::vector neighbours; std::vector spacings{3.5, 4.0, 4.5, 4.75, 5.0, 5.5, 6.0}; char filter[32]{}; int handle{-1}; bool dragging{false}; editor_track::straight drag_line; std::vector set; std::vector drag_lines; bool fitting{false}; editor_track::curve fit_before; editor_track::curve fit_after; std::vector> drag_states; glm::dvec3 preview_start{0.0}; glm::dvec3 preview_end{0.0}; glm::mat4 gizmo{1.0f}; glm::dvec3 edit_start{0.0}; glm::dvec3 edit_end{0.0}; double edit_length{0.0}; double edit_azimuth{0.0}; double distance{4.0}; double curve_radius{0.0}; bool auto_transitions{true}; double transition{0.0}; int tool{0}; bool tool_mouse{false}; std::vector detour; editor_track::straight detour_line; glm::dvec3 detour_mouse{0.0}; bool tool_placed{false}; bool tool_dragging{false}; double tool_at{0.0}; glm::dvec3 tool_point{0.0}; glm::dvec3 tool_handle{0.0}; editor_track::straight tool_line; glm::mat4 tool_gizmo{1.0f}; std::string status; }; void render_straights_ui(); void draw_straights_overlay() const; editor_track::straight const ¤t_straight(); bool straights_active(); bool pick_straight_handle(); void render_straight_gizmo(); void straight_apply(editor_track::straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End); void straights_apply(std::vector const &Lines, std::vector> const &Ends); bool in_straight_set(editor_track::straight const &Line) const; bool place_straight_tool(); void render_straight_tool_gizmo(); void apply_straight_tool(); double straight_tool_radius(editor_track::straight const &Line) const; void straight_reshape(editor_track::straight const &Line, double const From, double const To, std::function const &Tail, double const Radius); void straight_refresh(); bool start_curve_fit(editor_track::straight const &Line); void update_curve_fit(glm::dvec3 const &Start, glm::dvec3 const &End); void add_detour_point(); void apply_detour(); std::vector detour_outline() const; void find_neighbour_straights(); glm::dvec3 snap_straight_offset(editor_track::straight const &Line, glm::dvec3 const &Offset) const; glm::dvec3 snap_straight_direction(glm::dvec3 const &Pivot, glm::dvec3 const &Moved) const; straights_state m_straights; struct curve_sample { double station{0.0}; glm::dvec3 position{0.0}; glm::dvec2 tangent{0.0, 1.0}; float roll{0.f}; float radius{0.f}; }; struct switch_tool { bool curved{false}; std::vector frame; std::vector frame_tracks; std::vector templates; bool collected{false}; int armed{-1}; int last{0}; // template armed the last time bool placing{false}; double along{0.0}; glm::dvec3 point{0.0}; glm::dvec3 mouse{0.0}; editor_track::straight line; std::string status; }; switch_tool m_switch; int m_turnout_template{0}; struct extend_tool { bool active{false}; TTrack *track{nullptr}; glm::dvec3 point{0.0}; glm::dvec2 direction{0.0, 1.0}; double grade{0.0}; glm::dvec3 mouse{0.0}; int path{0}; bool atend{true}; }; extend_tool m_extend; // lays new track through the clicked points, with no path needed to start from struct lay_tool { bool active{false}; std::vector points; editor_track::snap_target start; // free end the track starts from, if any glm::dvec3 mouse{0.0}; editor_track::snap_target mouse_snap; std::vector preview; std::string preview_error; double preview_length{0.0}; double radius{500.0}; bool transitions{true}; double piece_length{100.0}; bool copy_selected{false}; editor_track::path_style style; std::string status; }; lay_tool m_lay; void render_lay_ui(); void render_lay_status(); void lay_click(); void lay_finish(editor_track::snap_target const &End); void lay_cancel(); std::vector lay_pieces(std::vector const &Points, editor_track::snap_target const &End, double &Length, std::string &Error) const; void render_switch_ui(); bool start_switch_placement(); void finish_switch_placement(); std::vector switch_preview() const; void insert_switch(editor_track::straight const &Line, double const Along, int const Direction, int const Side); static std::vector sample_chain(editor_track::chain const &Chain); static curve_sample sample_at(std::vector const &Frame, double const Station); static double nearest_station(std::vector const &Frame, glm::dvec3 const &Point); static std::vector curved_switch_paths(editor_track::switch_template const &Shape, std::vector const &Frame, double const Station, int const Direction, int const Side); void insert_curved_switch(int const Direction, int const Side); bool place_switch_on_straight(editor_track::straight const &Line, editor_track::switch_template const &Shape, TTrack const *Style, double const Along, int const Direction, int const Side, bool const Snap, std::vector> &States, std::vector &Created, std::vector &Removed); struct crossover_plan { editor_track::straight target; editor_track::switch_template shape; double along{0.0}; int direction{1}; int side{1}; segment_data insert; std::vector paths; }; bool plan_crossover(crossover_plan &Plan) const; bool cut_straight(editor_track::straight const &Line, double const From, double const To, std::vector> &States, std::vector &Created, std::vector &Removed, TTrack **Style); bool insert_double_slip(editor_track::straight const &Line, glm::dvec3 const &Point, editor_track::switch_template const &Shape); bool replace_double_slip(TTrack &Part, editor_track::switch_template const &Shape); std::vector build_double_slip(glm::dvec3 const &PointA, glm::dvec3 const &PointB, glm::dvec3 const &PointC, glm::dvec3 const &PointD, glm::dvec2 const &Crossing, double const Height, glm::dvec2 const &First, glm::dvec2 const &Second, double const Angle, double const Radius, TTrack &Style); bool start_extend(); void finish_extend(); std::vector extend_pieces() const; // the free end of the path as the start of a new track; false if it isn't free or has no direction static bool free_end(TTrack &Track, int const Path, bool const Atend, extend_tool &Tool); // the free end new pieces are added at: the selected point if it's one, else the end, else the start bool chosen_free_end(extend_tool &Tool); // adds a straight (Side 0) or a curve to the left (1) or the right (-1) at the free end of the selected track void extend_add(int const Side); void render_extend_ui(); bool m_extend_freehand{false}; // dragging the ring of a free end draws a curve through the cursor, not a straight double m_extend_length{25.0}; double m_extend_radius{300.0}; bool extend_snap(editor_track::snap_target &Target) const; void update_build_tools(); void draw_build_overlay() const; // modes of the track editor, one at a time: path is the plain selection enum class track_tab { straights, route, path, turnout, profile, speed, infra, lay }; struct track_mode { char const *label; char const *key; track_tab tab; char const *tooltip; }; static std::array const &track_modes(); void render_track_menu(); void arm_switch(); bool m_track_window_open{false}; // the inspector is pinned to the right edge of the screen, the profile strip to the bottom one, both resized from the inner edge void render_track_inspector(); void render_track_modes(TTrack *Track); void show_track_tab(track_tab const Tab); bool track_analysis_tab() const { return m_track_tab == track_tab::profile || m_track_tab == track_tab::speed || m_track_tab == track_tab::infra; } bool track_shortcut(int const Key); bool track_busy() const; // something going on in the mode, which Esc drops before it leaves the mode std::string track_mode_name() const; void render_turnout_ui(); void render_path_parameters(TTrack &Track); void render_straight_ui(); track_tab m_track_tab{track_tab::path}; void delete_selected_track(); void start_straight_gesture(int const Tool); void finish_straight_gesture(); void toggle_straight_set(TTrack &Track); struct key_hint { char const *key; std::string action; }; std::vector track_key_hints(bool const All) const; void render_track_guide(); std::string track_readout() const; void draw_track_hints(); bool m_route_tab{true}; // vertical profile (grade line) along a route struct profile_state { bool open{false}; TTrack *from{nullptr}; TTrack *to{nullptr}; TTrack *picked_from{nullptr}; TTrack *picked_to{nullptr}; double run_length{3000.0}; editor_track::route route; std::vector samples; std::vector terrain; // NaN where there's no terrain profile::line line; profile::context context; std::vector issues; double origin{0.0}; // chainage of the start of the route double departure{0.0}; // largest difference between the grade line and the track double view_from{0.0}; double view_to{100.0}; double view_centre{0.0}; float exaggeration{20.0f}; bool show_terrain{true}; bool show_track{true}; bool show_plan{true}; int selected{-1}; int dragging{-1}; int curve_grip{-1}; bool panning{false}; bool changed{false}; double hover{-1.0}; // under the cursor in the strip int hover_point{-1}; int hover_grip{-1}; bool hover_line{false}; int shown{-2}; // point in the side panel std::string status; std::string error; }; profile_state m_profile; // grade lines designed in the editor, kept in the scenery files as "//$p" comment lines struct stored_profile { scene::layer_handle layer{null_handle}; double length{0.0}; glm::dvec3 start{0.0}, end{0.0}, middle{0.0}; // of the route, world space double origin{0.0}; profile::line line; }; std::vector m_profiles; bool m_profiles_loaded{false}; void profile_library_load(); void profile_library_mark(std::vector Layers); int profile_find(bool &Reversed) const; bool profile_restore(); void profile_store(); void profile_forget(); void profile_open_stored(std::size_t const Index); void profile_edited(); void render_profile_body(); void render_profile_strip(); void render_profile_toolbar(); void render_profile_points(); void render_profile_source(); bool render_profile_parameters(); void render_profile_issues(); void profile_open(TTrack *From, TTrack *To); void profile_open_run(TTrack &Track); void profile_take(editor_track::route Route, TTrack *From, TTrack *To); void profile_resample(); void profile_recognize(); void profile_check(); void profile_apply(); void profile_fit_view(); void profile_after_undo(); struct profile_view; void render_profile_canvas(float const Height); void profile_fit_exaggeration(profile_view const &View); void profile_navigate(profile_view const &View, bool const Hovered); bool profile_canvas_edit(profile_view const &View, bool const Hovered); void draw_profile_canvas(profile_view const &View, ImDrawList &Draw) const; void profile_canvas_tooltip() const; void render_profile_point(); void draw_profile_overlay() const; // infrastructure along the track: objects bound to the paths follow their changes. bindings are kept in the // scenery files as "//$b" comment lines struct infra_candidate { infra::binding binding; double offset{0.0}; // sideways from the axis std::string reason; bool chosen{false}; bool bound{false}; }; struct infra_window { bool open{false}; int scope{1}; // 0: the selected path, 1: the line through it, 2: the route of the vertical profile float reach{1500.0f}; float corridor{8.0f}; bool follow{true}; bool show{true}; std::vector rules{infra::default_rules()}; std::vector tracks; // of the scope std::vector candidates; int hovered{-1}; std::string status; std::string error; }; infra_window m_infra; std::vector m_bindings; std::vector> m_bindings_unresolved; // lines kept as they are bool m_bindings_loaded{false}; std::vector m_infra_buffer; // states of the objects before the change in progress bool m_infra_suspended{false}; void infra_load(); void infra_store(); std::vector infra_scope() const; void infra_recognize(); void infra_bind_chosen(); void infra_unbind(std::vector Indices); infra::binding *infra_find(infra::binding const &Binding); infra::object_state infra_state_of(infra::binding const &Binding) const; void infra_restore_state(infra::object_state const &State); void infra_buffer(infra::binding const &Binding, bool const Refresh); // editor_track::observer void track_captured(TTrack const &Track) override; void tracks_committed(std::vector const &Tracks) override; void track_retired(TTrack &Track) override; void track_split(TTrack &Original, TTrack &Created, segment_data const &First) override; void infra_move(infra::binding &Binding); void infra_attach(EditorSnapshot &Snapshot); void render_infra_body(); // speed limits of the paths checked against what their geometry allows struct speed_state { bool open{false}; int scope{0}; // 0: the line through the selected path, 1: the whole scenery speed_check::options options; std::vector> queue; std::size_t next{0}; std::vector results; bool show_all{false}; int limits{0}; // 0: all the paths, 1: only those with a speed limit set, 2: only those without int selected{-1}; std::size_t history{0}; // size of the undo history at the check std::string status; }; speed_state m_speed; void render_speed_body(); void speed_start(); void speed_step(); void speed_focus(int const Index); // with Branches the limits of the switch branches count too, otherwise they're only warnings void speed_apply(std::vector const &Indices, bool const Branches); std::vector speed_listed() const; void draw_speed_overlay() const; void infra_rebase(); void render_infra_search(); void render_infra_candidates(); void render_infra_bound(); void draw_infra_overlay() const; bool m_route_gizmo_using{false}; glm::mat4 m_route_gizmo{1.0f}; float m_track_snap_radius{1.0f}; bool m_track_align_tangent{true}; bool m_track_drag_connected{true}; // roads: a menu and a window of their own, independent from the edit modes of the toolset // what a click of the build tool would make struct road_plan { std::vector pieces; // axes of the pieces, in the order they're laid editor_road::loose_end snap; // loose end of a road or arm of a junction the road is led to editor_road::branch start; // junction made, or given another arm, for a road led out of the side of a road or out of a junction bool hasstart{false}; editor_road::branch end; // the same for a road led into the side of a road or into a junction bool hasend{false}; std::string error; // why there's nothing to make }; struct road_tool { bool window{false}; // the road window is open, the left mouse button belongs to its tools bool lanes{true}; // lanes of the roads are drawn as trajectories int tool{0}; // 0: select, 1: build, 2: place road_node *selected{nullptr}; junction_node *junction{nullptr}; // selected junction roadpoint_node *marker{nullptr}; // selected level crossing or traffic point std::vector points; // selected points where the pieces meet or end road_node::state settings; // layout shown in the window: of the selected piece, or of the pieces about to be built bool whole{true}; // layout changes go to every piece of the road the selected one belongs to bool varied{false}; // the selected piece is shown with lanes of its own for its end bool chain{false}; // the point to build from is set glm::dvec3 point{0.0}; // where the next piece starts glm::dvec2 direction{0.0, 1.0}; // the way it has to leave that point bool hasdirection{false}; bool reversed{false}; // the pieces are laid from their ends, to carry on a road from its start double grade{0.0}; // slope the next piece has to start with, to carry on what it's attached to bool hasgrade{false}; // the road being built leaves the side of a road, or a junction between its roads. the point to build from is the centre // of the junction, which gets made, or gets another arm, along with the first piece road_node *branchroad{nullptr}; double branchat{0.0}; // value of the curve parameter of the axis of that road junction_node *branchjunction{nullptr}; float offset{0.1f}; // height above the ground the roads are built at bool follow{true}; // built roads are led over the ground between the clicked points float crossingspeed{30.f}; // speed limit on the junctions being made float heightstep{0.1f}; // how much the selected points are raised or lowered at a time bool ring{false}; // the build tool makes roundabouts float ringradius{15.f}; // of the middle of the road going around std::string island{"none"}; // what the middle of a roundabout is covered with float bankgrade{1.5f}; // metres a bank led to the ground runs for each metre it drops // level crossings and traffic points int placekind{0}; // what the place tool puts down: 0: level crossing, 1: spawn point, 2: removal point, 3: pedestrian crossing roadpoint_node::state placing[4]; // what the next point of each kind is going to be like float stopmargin{5.f}; // distance from the outermost track of a new crossing to the places the vehicles stop at roadpoint_node::state target; // the point a click of the place tool would make at the moment bool hastarget{false}; glm::dvec3 targetdirection{0.0}; // the way the traffic goes there std::string targetnote; // what's worth knowing about that point, or why there's none glm::dvec3 aimed{0.0}; // where the cursor was when the target was worked out, and for what kind of point int aimedkind{-1}; char vehicles[4096]{}; // vehicles to add to the set of a spawn point, written the way they're put in a scenery road_plan plan; // what a click would make at the moment, worked out once a frame for display glm::dvec3 hover{0.0}; // point of a road or a junction under the cursor a road can be led out of bool hashover{false}; glm::dvec3 mouse{0.0}; std::string status; char surface[128]{}; char sides[2][128]{}; char filter[64]{"asph"}; char kerbtext[128]{}; char mediantext[128]{}; char banktext[128]{}; char islandtext[128]{"none"}; }; void render_road_menu(); void render_road_window(); bool render_road_layout(road_node::state &State); bool render_road_material(char const *Label, char *Buffer, std::size_t const Size, std::string &Value); void render_junction_layout(junction_node &Junction); void junction_apply(junction_node &Junction, junction_node::state const &State); // leads the banks of the corners of a junction to the ground beside it void junction_bank_to_ground(junction_node &Junction); // how far from specified point, and how far down, a slope of the grade set for the banks gets to the ground. // Outwards: the way the slope leads, seen from above std::pair bank_reach(glm::dvec3 const &Edge, glm::dvec3 const &Outwards); void draw_road_overlay() const; void update_road_tool(); void road_click(); void road_select(road_node *Road); void junction_select(junction_node *Junction); void road_apply(); bool road_delete(); bool road_split(); void road_cancel(); // sets the width of the road at the selected points, or the height of each of them void road_point_width(float const Width); void road_point_heights(std::vector const &Heights); bool road_points_apply(std::vector> const &Changes, std::vector> const &Junctions); // takes the selected points out of their roads bool road_points_delete(); // puts a roundabout around specified point void road_roundabout(glm::dvec3 const &Ground); // leads the banks of the selected piece, or of the whole road, to the ground beside it void road_bank_to_ground(); // level crossings and traffic points void roadpoint_select(roadpoint_node *Point); // works out the point a click of the place tool would make. Fresh: even if the cursor is where it was the last time void roadpoint_aim(bool const Fresh); void roadpoint_place(); void roadpoint_apply(roadpoint_node &Point, roadpoint_node::state const &State); // Point: the point the controls are for, nullptr for the one about to be made bool render_roadpoint_layout(roadpoint_node::state &State, roadpoint_node const *Point); // Fresh: the ground which wasn't looked up yet is gathered on the spot, instead of a bit of it each frame road_plan road_preview(bool const Fresh); // Firstgrade, Lastgrade: slopes the road has to start and end with, nullptr for none void road_profile(std::vector &Pieces, double const *Firstgrade, double const *Lastgrade, bool const Fresh); // height of the ground under each of the points, with the roads not taken for the ground. a point with nothing under it keeps its height // Found: set for the points which have the ground under them std::vector ground_heights(std::vector const &Points, bool const Fresh, std::vector *Found = nullptr); void push_road_snapshot(editor_road::record Record); void restore_road_snapshot(EditorSnapshot const &Snapshot, std::vector &Opposite, bool const Undo); road_tool m_roadtool; };