17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-30 08:30:28 +01:00

Merge branch 'feat/modern-editor' of https://github.com/MaSzyna-EU07/maszyna into feat/modern-editor

This commit is contained in:
2026-10-04 21:58:19 +02:00
32 changed files with 7287 additions and 739 deletions

View File

@@ -476,6 +476,7 @@ editor_mode::editor_mode() {
ui()->set_gizmo_options([this]() { render_gizmo_options(); });
ui()->set_file_actions([this]() { save(); }, [this]() { export_scenery(); });
ui()->set_track_options([this]() { render_track_ui(); });
ui()->set_gauge_window(&m_gauge.open);
ui()->set_menu_options([this]() {
render_object_menu();
render_road_menu();
@@ -487,8 +488,14 @@ editor_mode::editor_mode() {
m_orthophoto.ground_source([](glm::dvec2 const &Min, glm::dvec2 const &Max, std::vector<world_triangle> &Out) {
gather_ground_triangles(Min, Max, true, 50.0f, Out, true);
});
editor_track::observe(this);
}
editor_mode::~editor_mode()
{
editor_track::observe(nullptr);
}
std::vector<double> editor_mode::ground_heights(std::vector<glm::dvec3> const &Points, bool const Fresh)
{
std::vector<double> heights;
@@ -1091,6 +1098,9 @@ bool editor_mode::update()
simulation::State.update_clocks();
simulation::Environment.update();
// bindings are matched with the objects by their locations, before anything gets moved
if (false == m_bindings_loaded && simulation::is_ready)
infra_load();
auto const deltarealtime = Timer::GetDeltaRenderTime();
@@ -1243,7 +1253,13 @@ bool editor_mode::update()
draw_build_overlay();
draw_track_hints();
render_track_window();
draw_profile_overlay();
render_profile_window();
draw_infra_overlay();
render_infra_window();
}
update_gauge();
render_gauge_window();
// roads: trajectories of the lanes, and the tools of the road window
update_road_tool();
@@ -1865,6 +1881,9 @@ void editor_mode::save()
}
}
if (m_profile.changed)
profile_store();
infra_store();
auto const result = scene::Layers.save(rootstatements);
if (result.success && false == rootstatements.empty())
{

View File

@@ -22,6 +22,9 @@ http://mozilla.org/MPL/2.0/.
#include "editor/editorTrack.hpp"
#include "editor/editorAlignment.hpp"
#include "editor/editorRoad.hpp"
#include "editor/editorProfile.hpp"
#include "editor/editorInfra.hpp"
#include "editor/editorGauge.hpp"
#include <array>
#include <chrono>
@@ -33,12 +36,23 @@ http://mozilla.org/MPL/2.0/.
class TAnimModel;
class TTrack;
class editor_mode : public application_mode
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;
@@ -108,7 +122,7 @@ class editor_mode : public application_mode
std::vector<TTrack *> created;
std::vector<TTrack *> removed;
editor_road::record roads; // what a change of the roads comes to
std::vector<infra::object_state> infra; // objects which followed the paths
};
void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string());
@@ -293,7 +307,8 @@ class editor_mode : public application_mode
void select_track(scene::basic_node *Node);
void render_track_gizmo();
void commit_track_drag(bool const Force);
void push_track_snapshot(std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created = {});
void push_track_snapshot(std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created = {}, std::vector<TTrack *> Removed = {});
void trim_history();
void restore_track_snapshot(EditorSnapshot const &Snapshot, std::vector<EditorSnapshot> &Opposite, bool const Undo);
editor_track::point_ref m_track_point;
std::vector<TTrack *> m_track_drag;
@@ -306,6 +321,57 @@ class editor_mode : public application_mode
std::chrono::steady_clock::time_point m_track_last_commit;
editor_track::state m_track_field_before;
std::array<std::array<char, 256>, 3> m_track_materials{};
std::array<TTrack const *, 3> m_track_material_edited{}; // path whose texture name is being typed
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<gauge::profile> 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<glm::dvec3> surface; // triangles of the tunnel
std::vector<glm::dvec3> edges; // lines
std::vector<gauge_hit> hits;
} line; // along the selected path
struct
{
bool scanned{false};
std::size_t history{0};
std::string profile;
std::vector<gauge_hit> hits;
} map; // whole scenery
struct
{
std::vector<glm::dvec3> surface;
std::vector<glm::dvec3> 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<struct gauge_scan> 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<gauge_hit> 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};
@@ -319,7 +385,13 @@ class editor_mode : public application_mode
int grip{-1};
};
void render_route_ui();
void render_route_ends();
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);
void route_update() { m_route.result = alignment::compute(m_route.design); }
void route_apply();
@@ -472,17 +544,156 @@ class editor_mode : public application_mode
enum class track_tab { straights, route, path, turnout };
bool m_track_window_open{false};
void render_track_window();
void render_track_modes(TTrack &Track);
void render_turnout_ui();
void render_path_parameters(TTrack &Track);
void render_straight_ui();
track_tab m_track_tab{track_tab::straights};
int m_track_tab_request{-1};
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<key_hint> track_key_hints() const;
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<editor_track::route_sample> samples;
std::vector<double> terrain; // NaN where there's no terrain
profile::line line;
profile::context context;
std::vector<profile::issue> 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};
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<stored_profile> m_profiles;
bool m_profiles_loaded{false};
void profile_library_load();
void profile_library_mark(std::vector<scene::layer_handle> 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_window();
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();
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<infra::rule> rules{infra::default_rules()};
std::vector<TTrack *> tracks; // of the scope
std::vector<infra_candidate> candidates;
int hovered{-1};
std::string status;
std::string error;
};
infra_window m_infra;
std::vector<infra::binding> m_bindings;
std::vector<std::pair<scene::layer_handle, std::string>> m_bindings_unresolved; // lines kept as they are
bool m_bindings_loaded{false};
std::vector<infra::object_state> 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<TTrack *> infra_scope() const;
void infra_recognize();
void infra_bind_chosen();
void infra_unbind(std::vector<std::size_t> 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<TTrack *> 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_window();
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};

View File

@@ -0,0 +1,798 @@
/*
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/.
*/
#include "stdafx.h"
#include "application/editormode.h"
#include "application/editoruilayer.h"
#include "editor/editorFormat.hpp"
#include "editor/editorGauge.hpp"
#include "editor/editorGeometry.hpp"
#include "model/AnimModel.h"
#include "model/Model3d.h"
#include "rendering/editoroverlay.h"
#include "rendering/renderer.h"
#include "simulation/simulation.h"
#include "utilities/Logs.h"
#include "world/Track.h"
#include "imgui/imgui.h"
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <algorithm>
#include <chrono>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <optional>
#include <unordered_map>
#include <unordered_set>
using geometry::plan_of;
namespace
{
char const *const gauge_file{"editor_structure_gauges.txt"};
// past this distance of the camera from the origin of the overlay it's rebuilt, to keep its float precision
double const gauge_origin_range{2000.0};
// length of the line shown each way from the selected path, and around a hit
double const gauge_reach{500.0};
double const gauge_spot_reach{60.0};
// time spent on the scan of the whole map in a frame
auto const gauge_slice{std::chrono::milliseconds(8)};
// distance of the camera from the place of a hit, along the track
double const gauge_view_distance{4.0};
// sampled line of the tracks with the widening of the gauge along it
struct gauge_line
{
gauge::profile const *profile{nullptr};
editor_track::route route;
std::vector<editor_track::route_sample> samples;
std::vector<gauge::section> sections;
};
// half of the carriageway of a road at the sample, it narrows linearly to the width at the end
double carriageway(editor_track::route_span const &Span, double const Chainage)
{
auto const &track{*Span.track};
auto const end{track.fTrackWidth2 > 0.f ? track.fTrackWidth2 : track.fTrackWidth};
auto t{Span.to > Span.from ? std::clamp((Chainage - Span.from) / (Span.to - Span.from), 0.0, 1.0) : 0.0};
if (false == Span.forward)
t = 1.0 - t;
return 0.5 * std::abs(track.fTrackWidth + (end - track.fTrackWidth) * t);
}
gauge_line make_line(editor_track::route &Route, gauge::profile const &Profile)
{
gauge_line line;
line.profile = &Profile;
line.samples = editor_track::sample_route(Route, 1.0);
line.route = Route;
bool const road{Profile.kind == gauge::kind::road};
std::vector<double> chainage, curvature, cant;
for (auto &sample : line.samples)
{
if (false == road)
sample.position.y += gauge::rail_height;
chainage.push_back(sample.chainage);
curvature.push_back(sample.curvature);
cant.push_back(sample.cant);
}
line.sections = gauge::sections(Profile.kind, chainage, curvature, cant);
if (road)
for (std::size_t i = 0; i < line.samples.size(); ++i)
line.sections[i].base = carriageway(line.route.spans[line.samples[i].span], line.samples[i].chainage);
return line;
}
// outline checked along the path, none on the pavements, which are the roads with the pavement texture
gauge::profile const *outline_of(gauge::choice const &Choice, TTrack const &Track)
{
auto const *profile{Choice.of(Track.iCategoryFlag)};
if (profile == nullptr || profile->outline.size() < 2)
return nullptr;
if (profile == Choice.road)
{
auto name{editor_track::material_name(Track.m_material1)};
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char const C) { return static_cast<char>(std::tolower(C)); });
if (name.find("chodnik") != std::string::npos)
return nullptr;
}
return profile;
}
// the line along the route from the path with the outline of its kind, none without the outline or the length
std::optional<gauge_line> track_line(gauge::choice const &Choice, TTrack const &Track, editor_track::route &Route)
{
auto const *profile{outline_of(Choice, Track)};
if (profile == nullptr)
return std::nullopt;
auto line{make_line(Route, *profile)};
if (line.samples.size() < 2)
return std::nullopt;
return line;
}
// lines through all paths of the scenery, each path in one of them at least
std::vector<gauge_line> scenery_lines(gauge::choice const &Choice)
{
std::vector<gauge_line> lines;
std::unordered_set<TTrack const *> visited;
auto const seen{[&](editor_track::route_span const &Span) { return visited.count(Span.track) > 0; }};
auto const checked{[&](TTrack const *Track) { return Track != nullptr && editor_track::is_supported(*Track) && outline_of(Choice, *Track) != nullptr; }};
for (auto *track : simulation::Paths.sequence())
{
if (false == checked(track) || visited.count(track) > 0)
continue;
auto route{editor_track::run_route(*track, 1e7)};
// the line may run on over the paths already covered, one of them is enough for the widening ahead of a curve
auto &spans{route.spans};
while (spans.size() > 2 && seen(spans[0]) && seen(spans[1]))
spans.erase(spans.begin());
while (spans.size() > 2 && seen(spans[spans.size() - 1]) && seen(spans[spans.size() - 2]))
spans.pop_back();
for (auto const &span : spans)
visited.insert(span.track);
lines.push_back(make_line(route, *outline_of(Choice, *track)));
}
// diverging paths of the switches
for (auto *track : simulation::Paths.sequence())
if (checked(track) && track->eType == tt_Switch)
{
editor_track::route route;
route.spans.push_back({track, 1, true});
lines.push_back(make_line(route, *outline_of(Choice, *track)));
}
return lines;
}
// samples of the lines, found by the position in the plan
class corridor
{
public:
explicit corridor(std::vector<gauge_line> const &Lines) : m_lines{Lines}
{
for (std::uint32_t line = 0; line < Lines.size(); ++line)
for (std::uint32_t i = 0; i < Lines[line].samples.size(); ++i)
{
// with the room for the widening in the sharpest curves on the largest cant
m_margin = std::max(m_margin, gauge::widest(*Lines[line].profile) + Lines[line].sections[i].base + 1.2);
auto const point{plan_of(Lines[line].samples[i].position)};
m_cells[key(point, cell)].push_back({line, i});
m_regions.insert(key(point, region));
}
}
static glm::dvec2 normal(editor_track::route_sample const &Sample)
{
return {-Sample.direction.y, Sample.direction.x};
}
// whether the sphere may reach the gauge of a line sampled in the cells around it
bool close(glm::dvec3 const &Center, double const Radius) const
{
auto const center{plan_of(Center)};
auto const reach{Radius + m_margin};
auto const from{index(center - reach, cell)};
auto const to{index(center + reach, cell)};
for (auto x = from.x; x <= to.x; ++x)
for (auto y = from.y; y <= to.y; ++y)
if (m_cells.count(key(x, y)) > 0)
return true;
return false;
}
// whether the sphere may reach the gauge, roughly
bool nearby(glm::dvec3 const &Center, double const Radius) const
{
auto const center{plan_of(Center)};
auto const reach{Radius + m_margin + region};
auto const from{index(center - reach, region)};
auto const to{index(center + reach, region)};
for (auto x = from.x; x <= to.x; ++x)
for (auto y = from.y; y <= to.y; ++y)
if (m_regions.count(key(x, y)) > 0)
return true;
return false;
}
struct hit
{
double depth{0.0}; // not positive outside of the gauge
editor_track::route_span const *span{nullptr}; // of the line the point enters
glm::dvec2 direction{0.0}; // of the line there, in the plan
};
// deepest intrusion of the point into the gauge of the lines nearby
hit intrusion(glm::dvec3 const &Point) const
{
auto const point{plan_of(Point)};
auto const center{index(point, cell)};
// nearest sample of every line around
std::vector<std::pair<entry, double>> nearest;
for (auto x = center.x - 1; x <= center.x + 1; ++x)
for (auto y = center.y - 1; y <= center.y + 1; ++y)
{
auto const found{m_cells.find(key(x, y))};
if (found == m_cells.end())
continue;
for (auto const &candidate : found->second)
{
auto const distance{glm::length2(plan_of(sample(candidate).position) - point)};
if (distance > m_margin * m_margin)
continue;
auto const same{std::find_if(nearest.begin(), nearest.end(), [&](auto const &Other) { return Other.first.line == candidate.line; })};
if (same == nearest.end())
nearest.push_back({candidate, distance});
else if (distance < same->second)
*same = {candidate, distance};
}
}
hit deepest;
for (auto const &[found, distance] : nearest)
{
auto const &line{m_lines[found.line]};
auto const &sample{line.samples[found.index]};
auto const offset{point - plan_of(sample.position)};
auto const along{glm::dot(offset, sample.direction)};
if ((found.index == 0 && along < -0.6) || (found.index + 1 == line.samples.size() && along > 0.6))
continue;
gauge::ring(*line.profile, line.sections[found.index], m_ring);
auto const depth{gauge::intrusion(m_ring, {glm::dot(offset, normal(sample)), Point.y - (sample.position.y + sample.grade * along)})};
if (depth > deepest.depth)
deepest = {depth, &line.route.spans[sample.span], sample.direction};
}
return deepest;
}
private:
static constexpr double cell{8.0};
static constexpr double region{64.0};
struct entry
{
std::uint32_t line;
std::uint32_t index;
};
static glm::i64vec2 index(glm::dvec2 const &Point, double const Size)
{
return {static_cast<std::int64_t>(std::floor(Point.x / Size)), static_cast<std::int64_t>(std::floor(Point.y / Size))};
}
static std::uint64_t key(std::int64_t const X, std::int64_t const Y)
{
return (static_cast<std::uint64_t>(X) << 32) ^ static_cast<std::uint32_t>(Y);
}
static std::uint64_t key(glm::dvec2 const &Point, double const Size)
{
auto const cell{index(Point, Size)};
return key(cell.x, cell.y);
}
editor_track::route_sample const &sample(entry const &Entry) const
{
return m_lines[Entry.line].samples[Entry.index];
}
std::vector<gauge_line> const &m_lines;
double m_margin{0.0};
mutable std::vector<gauge::place> m_ring; // reused by the checks
std::unordered_map<std::uint64_t, std::vector<entry>> m_cells;
std::unordered_set<std::uint64_t> m_regions;
};
glm::dmat4 placement(TAnimModel const &Instance)
{
auto const angles{Instance.Angles()};
auto matrix{glm::translate(glm::dmat4(1.0), Instance.location())};
matrix = glm::rotate(matrix, glm::radians(static_cast<double>(angles.y)), glm::dvec3(0.0, 1.0, 0.0));
matrix = glm::rotate(matrix, glm::radians(static_cast<double>(angles.x)), glm::dvec3(1.0, 0.0, 0.0));
matrix = glm::rotate(matrix, glm::radians(static_cast<double>(angles.z)), glm::dvec3(0.0, 0.0, 1.0));
return glm::scale(matrix, glm::dvec3(Instance.Scale()));
}
// calls the visitor with the world corners of the visible triangles of the submodel, its siblings and children
template <typename Visitor> void visit_triangles(TSubModel *Submodel, glm::dmat4 const &Matrix, Visitor const &Visit)
{
for (auto *submodel = Submodel; submodel != nullptr; submodel = submodel->NextGet())
{
if (submodel->iVisible == 0)
continue;
auto matrix{Matrix};
if ((submodel->iFlags & 0xC000) && submodel->GetMatrix() != nullptr)
matrix *= glm::dmat4(glm::make_mat4(submodel->GetMatrix()->readArray()));
if (submodel->eType == GL_TRIANGLES && submodel->m_geometry.handle != null_handle)
{
auto const &vertices{GfxRenderer->Vertices(submodel->m_geometry.handle)};
auto const &indices{GfxRenderer->Indices(submodel->m_geometry.handle)};
auto const corner{[&](std::size_t const Index) { return glm::dvec3(matrix * glm::dvec4(glm::dvec3(vertices[Index].position), 1.0)); }};
auto const count{indices.empty() ? vertices.size() : indices.size()};
for (std::size_t i = 0; i + 2 < count; i += 3)
{
if (indices.empty())
Visit(corner(i), corner(i + 1), corner(i + 2));
else
Visit(corner(indices[i]), corner(indices[i + 1]), corner(indices[i + 2]));
}
}
visit_triangles(submodel->ChildGet(), matrix, Visit);
}
}
// the deepest place of the model in the gauge, added to the hits when it enters it
void scan_model(corridor const &Space, TAnimModel &Instance, std::vector<editor_mode::gauge_hit> &Hits)
{
if (false == Instance.visible() || Instance.Model() == nullptr || Instance.Model()->GetSMRoot() == nullptr)
return;
if (false == Space.nearby(Instance.location(), Instance.radius()))
return;
editor_mode::gauge_hit hit{Instance.name(), Instance.location()};
// the triangle enters the gauge when any point of a grid spread over it does
visit_triangles(Instance.Model()->GetSMRoot(), placement(Instance), [&](glm::dvec3 const &A, glm::dvec3 const &B, glm::dvec3 const &C) {
auto const middle{(A + B + C) / 3.0};
if (false == Space.close(middle, std::max({glm::distance(middle, A), glm::distance(middle, B), glm::distance(middle, C)})))
return;
auto const edge{std::max({glm::distance(A, B), glm::distance(B, C), glm::distance(C, A)})};
auto const steps{std::clamp(static_cast<int>(std::ceil(edge / 0.5)), 1, 24)};
for (int u = 0; u <= steps; ++u)
for (int v = 0; u + v <= steps; ++v)
{
auto const point{A + (B - A) * (static_cast<double>(u) / steps) + (C - A) * (static_cast<double>(v) / steps)};
auto const found{Space.intrusion(point)};
if (found.depth <= 0.0)
continue;
if (found.depth > hit.depth)
{
hit.depth = found.depth;
hit.point = point;
hit.track = found.span->track;
hit.path = found.span->path;
hit.direction = found.direction;
}
return;
}
});
if (hit.depth > 0.0)
Hits.push_back(std::move(hit));
}
void sort_hits(std::vector<editor_mode::gauge_hit> &Hits)
{
std::sort(Hits.begin(), Hits.end(), [](auto const &Left, auto const &Right) { return Left.depth > Right.depth; });
}
// the models entering the gauge, the deepest first
void scan_models(corridor const &Space, std::vector<editor_mode::gauge_hit> &Hits)
{
for (auto *instance : simulation::Instances.sequence())
if (instance != nullptr)
scan_model(Space, *instance, Hits);
sort_hits(Hits);
}
// translucent tunnel of the gauge along the line, with the rings every 10 m and the lines along its corners
void build_tunnel(gauge_line const &Line, std::vector<glm::dvec3> &Surface, std::vector<glm::dvec3> &Edges)
{
std::vector<gauge::place> places;
auto const ring{[&](std::size_t const Index) {
gauge::ring(*Line.profile, Line.sections[Index], places);
auto const &sample{Line.samples[Index]};
auto const normal{corridor::normal(sample)};
std::vector<glm::dvec3> points;
for (auto const &place : places)
points.push_back(sample.position + glm::dvec3(normal.x * place.lateral, place.height, normal.y * place.lateral));
return points;
}};
std::vector<glm::dvec3> previous;
double lastring{-1e9};
for (std::size_t i = 0; i < Line.samples.size(); i += 2)
{
auto const current{ring(i)};
if (false == previous.empty())
for (std::size_t j = 1; j < current.size(); ++j)
{
Surface.insert(Surface.end(), {previous[j - 1], previous[j], current[j], previous[j - 1], current[j], current[j - 1]});
Edges.insert(Edges.end(), {previous[j], current[j]});
}
if (Line.samples[i].chainage - lastring >= 10.0 || i + 2 >= Line.samples.size())
{
lastring = Line.samples[i].chainage;
for (std::size_t j = 1; j < current.size(); ++j)
Edges.insert(Edges.end(), {current[j - 1], current[j]});
}
previous = current;
}
}
// table of the points of the outline with the inserting and removing of the points, whether it changed
bool edit_points(std::vector<gauge::profile::point> &Points)
{
bool edited{false};
bool inserted{false};
int remove{-1};
ImGui::Columns(3, "gaugeoutline", false);
ImGui::SetColumnWidth(0, 110.0f);
ImGui::SetColumnWidth(1, 110.0f);
ImGui::TextDisabled("Half-width");
ImGui::NextColumn();
ImGui::TextDisabled("Height");
ImGui::NextColumn();
ImGui::NextColumn();
for (int i = 0; i < static_cast<int>(Points.size()); ++i)
{
ImGui::PushID(i);
auto &point{Points[i]};
ImGui::SetNextItemWidth(-1.0f);
ImGui::InputDouble("##width", &point.half_width, 0.0, 0.0, "%.3f");
edited |= ImGui::IsItemDeactivatedAfterEdit();
ImGui::NextColumn();
ImGui::SetNextItemWidth(-1.0f);
ImGui::InputDouble("##height", &point.height, 0.0, 0.0, "%.3f");
edited |= ImGui::IsItemDeactivatedAfterEdit();
ImGui::NextColumn();
if (ImGui::SmallButton("+"))
{
auto const &next{Points[std::min<std::size_t>(i + 1, Points.size() - 1)]};
Points.insert(Points.begin() + i + 1, {0.5 * (point.half_width + next.half_width), 0.5 * (point.height + next.height)});
inserted = edited = true;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Insert a point after this one");
ImGui::SameLine();
if (Points.size() > 2 && ImGui::SmallButton("x"))
remove = i;
ImGui::NextColumn();
ImGui::PopID();
if (inserted)
break;
}
ImGui::Columns(1);
if (remove >= 0)
{
Points.erase(Points.begin() + remove);
edited = true;
}
return edited;
}
} // namespace
struct gauge_scan
{
std::optional<gauge::profile> railway;
std::optional<gauge::profile> road;
std::vector<gauge_line> lines;
std::optional<corridor> space; // of the lines
std::size_t next{0}; // instance to check
float progress{0.f};
std::chrono::steady_clock::time_point started;
};
void editor_mode::gauge_load()
{
if (m_gauge.profiles.empty())
m_gauge.profiles = gauge::load_profiles(gauge_file);
// the chosen railway and road outlines, the first ones of the kind when there's none
auto const fits{[&](int const Index, bool const Road) {
return Index >= 0 && Index < static_cast<int>(m_gauge.profiles.size()) && (m_gauge.profiles[Index].kind == gauge::kind::road) == Road;
}};
for (auto [index, road] : {std::pair<int *, bool>{&m_gauge.profile, false}, std::pair<int *, bool>{&m_gauge.road, true}})
if (false == fits(*index, road))
{
*index = -1;
for (int i = 0; i < static_cast<int>(m_gauge.profiles.size()) && *index < 0; ++i)
if (fits(i, road))
*index = i;
}
}
gauge::choice editor_mode::gauge_choice() const
{
auto const pick{[&](int const Index) { return Index >= 0 ? &m_gauge.profiles[Index] : nullptr; }};
return {pick(m_gauge.profile), pick(m_gauge.road)};
}
std::vector<editor_mode::gauge_hit> const &editor_mode::gauge_hits() const
{
return m_gauge.map.scanned ? m_gauge.map.hits : m_gauge.line.hits;
}
void editor_mode::update_gauge()
{
if (m_gauge.scan)
step_gauge_map();
if (false == m_gauge.open)
{
if (false == EditorOverlay.batches.empty())
EditorOverlay.clear();
m_gauge.published = false;
return;
}
auto *track{m_gauge.enabled ? selected_track() : nullptr};
// the geometry is rescanned once the edit ends
if (ImGui::IsMouseDown(0) || m_track_gizmo_using || m_dragging)
m_gauge.pending = true;
else if (track == nullptr)
{
if (m_gauge.track != nullptr)
{
m_gauge.line = {};
m_gauge.track = nullptr;
m_gauge.published = false;
}
}
else if (track != m_gauge.track || m_history.size() != m_gauge.history || m_gauge.pending)
{
m_gauge.track = track;
m_gauge.history = m_history.size();
m_gauge.pending = false;
scan_gauge(*track);
m_gauge.published = false;
}
if (false == m_gauge.published || glm::distance(Camera.Pos, EditorOverlay.origin) > gauge_origin_range)
gauge_publish();
}
void editor_mode::scan_gauge(TTrack &Track)
{
gauge_load();
m_gauge.line = {};
auto route{editor_track::run_route(Track, gauge_reach)};
auto line{track_line(gauge_choice(), Track, route)};
if (false == line.has_value())
return;
std::vector<gauge_line> const lines{std::move(*line)};
corridor const space{lines};
build_tunnel(lines.front(), m_gauge.line.surface, m_gauge.line.edges);
scan_models(space, m_gauge.line.hits);
if (false == m_gauge.map.scanned)
m_gauge.current = -1;
}
void editor_mode::scan_gauge_map()
{
gauge_load();
auto scan{std::make_shared<gauge_scan>()};
scan->started = std::chrono::steady_clock::now();
auto const chosen{gauge_choice()};
// copies, the outlines may be edited while it runs
scan->railway = chosen.railway != nullptr ? std::optional<gauge::profile>{*chosen.railway} : std::nullopt;
scan->road = chosen.road != nullptr ? std::optional<gauge::profile>{*chosen.road} : std::nullopt;
scan->lines = scenery_lines({scan->railway ? &*scan->railway : nullptr, scan->road ? &*scan->road : nullptr});
scan->space.emplace(scan->lines);
m_gauge.map = {};
m_gauge.map.scanned = true;
m_gauge.map.history = m_history.size();
auto const name{[](gauge::profile const *Profile) { return Profile != nullptr ? Profile->name : std::string{"-"}; }};
m_gauge.map.profile = name(chosen.railway) + ", " + name(chosen.road);
m_gauge.current = -1;
m_gauge.published = false;
m_gauge.scan = std::move(scan);
}
void editor_mode::step_gauge_map()
{
auto &scan{*m_gauge.scan};
auto const &instances{simulation::Instances.sequence()};
auto const until{std::chrono::steady_clock::now() + gauge_slice};
while (scan.next < instances.size() && std::chrono::steady_clock::now() < until)
{
if (auto *instance{instances[scan.next++]})
scan_model(*scan.space, *instance, m_gauge.map.hits);
}
scan.progress = instances.empty() ? 1.f : static_cast<float>(scan.next) / instances.size();
if (scan.next >= instances.size())
finish_gauge_map();
}
void editor_mode::finish_gauge_map()
{
auto const scan{std::move(m_gauge.scan)};
auto &hits{m_gauge.map.hits};
// the hit the camera went to stays chosen
auto const chosen{m_gauge.current >= 0 && m_gauge.current < static_cast<int>(hits.size()) ? std::optional<glm::dvec3>{hits[m_gauge.current].point} : std::nullopt};
sort_hits(hits);
if (chosen)
m_gauge.current = static_cast<int>(std::find_if(hits.begin(), hits.end(), [&](auto const &Hit) { return Hit.point == *chosen; }) - hits.begin());
WriteLog("Structure gauge (" + m_gauge.map.profile + "): " + std::to_string(hits.size()) + " models enter it along " + std::to_string(scan->lines.size()) + " lines, scanned in " +
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - scan->started).count()) + " ms");
if (false == chosen.has_value() && false == hits.empty())
gauge_focus(0);
}
void editor_mode::gauge_publish()
{
m_gauge.published = true;
EditorOverlay.clear();
EditorOverlay.origin = Camera.Pos;
auto const batch{[](glm::vec4 const &Color, unsigned int const Type, std::vector<glm::dvec3> const &Points) {
gfx::editor_overlay::batch result{Color, Type};
result.points.reserve(Points.size());
for (auto const &point : Points)
result.points.emplace_back(point - EditorOverlay.origin);
return result;
}};
std::vector<glm::dvec3> marker;
auto const &hits{gauge_hits()};
if (m_gauge.current >= 0 && m_gauge.current < static_cast<int>(hits.size()))
{
auto const point{hits[m_gauge.current].point};
marker = {point - glm::dvec3(0.0, 3.0, 0.0), point + glm::dvec3(0.0, 3.0, 0.0), point - glm::dvec3(1.0, 0.0, 0.0), point + glm::dvec3(1.0, 0.0, 0.0),
point - glm::dvec3(0.0, 0.0, 1.0), point + glm::dvec3(0.0, 0.0, 1.0)};
}
EditorOverlay.batches = {batch({0.2f, 0.9f, 0.3f, 0.12f}, GL_TRIANGLES, m_gauge.line.surface),
batch({0.2f, 1.0f, 0.3f, 0.5f}, GL_LINES, m_gauge.line.edges),
batch({1.0f, 0.9f, 0.1f, 1.0f}, GL_LINES, marker),
batch({0.2f, 0.9f, 0.3f, 0.12f}, GL_TRIANGLES, m_gauge.spot.surface),
batch({0.2f, 1.0f, 0.3f, 0.5f}, GL_LINES, m_gauge.spot.edges)};
}
void editor_mode::gauge_focus(int const Index)
{
auto const &hits{gauge_hits()};
if (hits.empty())
return;
m_gauge.current = (Index % static_cast<int>(hits.size()) + static_cast<int>(hits.size())) % static_cast<int>(hits.size());
m_gauge.published = false;
gauge_spot(hits[m_gauge.current]);
auto const &hit{hits[m_gauge.current]};
// along the track, tangent to the gauge at the height of the place, from the end nearer to the camera
glm::dvec3 look{hit.direction.x, 0.0, hit.direction.y};
look = glm::length(look) > 1e-6 ? glm::normalize(look) : glm::dvec3(0.0, 0.0, 1.0);
if (glm::dot(Camera.Pos - hit.point, look) > 0.0)
look = -look;
m_focus_start_pos = Camera.Pos;
m_focus_start_angle = Camera.Angle;
m_focus_target_pos = hit.point - look * gauge_view_distance;
m_focus_target_angle = glm::vec3(static_cast<float>(std::asin(std::clamp(look.y, -1.0, 1.0))), static_cast<float>(std::atan2(-look.x, -look.z)), 0.0f);
m_focus_active = true;
m_focus_time = 0.0;
m_focus_duration = 0.6;
}
void editor_mode::gauge_spot(gauge_hit const &Hit)
{
m_gauge.spot = {};
auto const &paths{simulation::Paths.sequence()};
// the hit outlives the scan, the path may be gone since
if (Hit.track == nullptr || std::find(paths.begin(), paths.end(), Hit.track) == paths.end())
return;
auto *track{const_cast<TTrack *>(Hit.track)};
editor_track::route route;
if (Hit.path == 0)
route = editor_track::run_route(*track, gauge_spot_reach);
else
route.spans.push_back({track, Hit.path, true});
if (auto const line{track_line(gauge_choice(), *track, route)})
build_tunnel(*line, m_gauge.spot.surface, m_gauge.spot.edges);
}
void editor_mode::render_gauge_window()
{
if (false == m_gauge.open)
return;
ImGui::SetNextWindowSize(ImVec2(380.0f, 420.0f), ImGuiCond_FirstUseEver);
if (false == ImGui::Begin("Structure gauge", &m_gauge.open))
{
ImGui::End();
return;
}
if (ImGui::Checkbox("Show along the selected path", &m_gauge.enabled))
m_gauge.pending = true;
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Translucent tunnel of the gauge 500 m each way along the line of the selected path.\nWidened in the curves under 250 m and tilted on the cant, from 20 m (inner side)\nand 26 m (outer side) ahead of the curve, as in the PKP PLK standard, volume II.");
gauge_load();
bool changed{render_gauge_choice("Railway", m_gauge.profile, false)};
if (ImGui::IsItemHovered())
ImGui::SetTooltip("GPL: unified structure gauge, widened only in the curves under 250 m.\nG1, G2, GA, GB, GC: limit (GSZ) and nominal (NSZ) installation gauges, widened by 3750/R in all curves.");
changed |= render_gauge_choice("Road", m_gauge.road, true);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Clearance over the roads, from 0.5 m beyond the edges of the carriageway");
if (m_gauge.scan)
{
auto const label{format("Scanning the map %.0f%%, %zu found", m_gauge.scan->progress * 100.f, m_gauge.map.hits.size())};
ImGui::ProgressBar(m_gauge.scan->progress, ImVec2(-70.0f, 0.0f), label.c_str());
ImGui::SameLine();
if (ImGui::SmallButton("Cancel"))
finish_gauge_map();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Stops the scan and keeps the models found so far");
}
else if (ImGui::Button("Find the violations on the whole map"))
scan_gauge_map();
else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Checks the models along all tracks of the scenery against the chosen gauge\nand lists them, the deepest first.\nRuns in the background, the list fills up as it goes");
if (m_gauge.map.scanned && false == static_cast<bool>(m_gauge.scan))
{
ImGui::SameLine();
if (ImGui::SmallButton("Clear"))
{
m_gauge.map = {};
m_gauge.current = -1;
m_gauge.published = false;
}
else if (m_gauge.map.history != m_history.size())
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "The scenery changed since the scan (%s)", m_gauge.map.profile.c_str());
}
render_gauge_hits();
changed |= render_gauge_outline(m_gauge.profile);
changed |= render_gauge_outline(m_gauge.road);
if (changed)
m_gauge.pending = true;
ImGui::End();
}
void editor_mode::render_gauge_hits()
{
auto const &hits{gauge_hits()};
if (false == m_gauge.map.scanned && (false == m_gauge.enabled || m_gauge.track == nullptr))
return;
if (hits.empty())
{
ImGui::TextDisabled(m_gauge.map.scanned ? "No model enters the gauge on the map" : "The gauge is clear along this line");
return;
}
ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.3f, 1.0f), "%zu models enter the gauge%s", hits.size(), m_gauge.map.scanned ? " on the map" : "");
if (ImGui::ArrowButton("##gaugeprevious", ImGuiDir_Left))
gauge_focus(m_gauge.current - 1);
ImGui::SameLine();
if (ImGui::ArrowButton("##gaugenext", ImGuiDir_Right))
gauge_focus(m_gauge.current + 1);
ImGui::SameLine();
ImGui::Text("%d / %zu", m_gauge.current + 1, hits.size());
ImGui::BeginChild("gaugehits", ImVec2(0.0f, std::min(160.0f, ImGui::GetTextLineHeightWithSpacing() * hits.size() + 8.0f)), true);
ImGuiListClipper clipper(static_cast<int>(hits.size()));
while (clipper.Step())
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
{
auto const &hit{hits[i]};
auto const label{(hit.model.empty() ? std::string{"(unnamed)"} : hit.model) + " " + std::to_string(static_cast<int>(std::lround(hit.depth * 100.0))) + " cm##" + std::to_string(i)};
if (ImGui::Selectable(label.c_str(), m_gauge.current == i))
gauge_focus(i);
}
ImGui::EndChild();
}
bool editor_mode::render_gauge_choice(char const *Label, int &Index, bool const Road)
{
bool changed{false};
ImGui::SetNextItemWidth(-60.0f);
if (ImGui::BeginCombo(Label, Index >= 0 ? m_gauge.profiles[Index].name.c_str() : "none"))
{
for (int i = 0; i < static_cast<int>(m_gauge.profiles.size()); ++i)
if ((m_gauge.profiles[i].kind == gauge::kind::road) == Road && ImGui::Selectable(m_gauge.profiles[i].name.c_str(), i == Index))
{
changed = i != Index;
Index = i;
}
ImGui::EndCombo();
}
return changed;
}
bool editor_mode::render_gauge_outline(int const Index)
{
if (Index < 0)
return false;
auto &profile{m_gauge.profiles[Index]};
if (false == ImGui::TreeNode(&profile, "Outline of %s (right half)", profile.name.c_str()))
return false;
ImGui::TextDisabled(profile.kind == gauge::kind::road ? "Metres from the edge of the carriageway and above the road, bottom up."
: "PKP PLK standard, volume II; below 1170 mm the limit installation gauge.\nMetres from the track axis and above the rail top, bottom up.");
bool edited{edit_points(profile.outline)};
if (ImGui::SmallButton("Restore the starting outlines"))
{
m_gauge.profiles = gauge::default_profiles();
gauge_load();
edited = true;
}
if (edited)
gauge::save_profiles(gauge_file, m_gauge.profiles);
ImGui::TreePop();
return edited;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
/*
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/editormode.h"
#include "rendering/renderer.h"
#include "utilities/Globals.h"
#include "imgui/imgui.h"
#include "imgui/ImGuizmo.h"
#include <limits>
namespace overlay_color
{
ImU32 constexpr marked{IM_COL32(255, 210, 60, 255)};
ImU32 constexpr grip{IM_COL32(60, 230, 90, 255)};
ImU32 constexpr selected{IM_COL32(40, 220, 255, 255)};
ImU32 constexpr invalid{IM_COL32(255, 60, 60, 255)};
ImU32 constexpr highlight{IM_COL32(255, 255, 255, 255)};
} // namespace overlay_color
// world to screen projection of the current camera, for overlays drawn with ImGui
class screen_projection
{
public:
screen_projection()
{
ImGuiIO const &io = ImGui::GetIO();
m_size = io.DisplaySize;
m_camera = GfxRenderer->Camera_Position();
float const aspect = m_size.y > 0.0f ? m_size.x / m_size.y : 1.0f;
m_viewprojection = editor_mode::projection_matrix(aspect) * GfxRenderer->Camera_View_Matrix();
}
glm::vec4 clip(glm::dvec3 const &Point) const
{
return m_viewprojection * glm::vec4(glm::vec3(Point - m_camera), 1.0f);
}
ImVec2 screen(glm::vec4 const &Clip) const
{
return ImVec2((Clip.x / Clip.w * 0.5f + 0.5f) * m_size.x, (0.5f - Clip.y / Clip.w * 0.5f) * m_size.y);
}
bool project(glm::dvec3 const &Point, ImVec2 &Screen) const
{
auto const c = clip(Point);
if (c.w < kNear)
return false;
Screen = screen(c);
return true;
}
void line(ImDrawList *Drawlist, glm::dvec3 const &A, glm::dvec3 const &B, ImU32 const Color, float const Thickness) const
{
glm::vec4 a = clip(A);
glm::vec4 b = clip(B);
if (a.w < kNear && b.w < kNear)
return;
if (a.w < kNear)
a = glm::mix(a, b, (kNear - a.w) / (b.w - a.w));
else if (b.w < kNear)
b = glm::mix(b, a, (kNear - b.w) / (a.w - b.w));
Drawlist->AddLine(screen(a), screen(b), Color, Thickness);
}
// squared distance on the screen from the mouse cursor to the point, the largest float for points behind the camera
float mouse_distance2(glm::dvec3 const &Point) const
{
ImVec2 position;
if (false == project(Point, position))
return std::numeric_limits<float>::max();
auto const &mouse{ImGui::GetIO().MousePos};
return (position.x - mouse.x) * (position.x - mouse.x) + (position.y - mouse.y) * (position.y - mouse.y);
}
private:
static constexpr float kNear{0.1f};
ImVec2 m_size;
glm::dvec3 m_camera;
glm::mat4 m_viewprojection;
};
// starts a gizmo frame over the whole display, the view and the projection are those of the current camera
struct gizmo_frame
{
glm::mat4 view;
glm::mat4 projection;
glm::dvec3 camera;
gizmo_frame()
{
ImGuizmo::BeginFrame();
ImGuizmo::SetOrthographic(Global.EditorOrtho);
ImGuiIO const &io = ImGui::GetIO();
ImGuizmo::SetRect(0.0f, 0.0f, io.DisplaySize.x, io.DisplaySize.y);
view = GfxRenderer->Camera_View_Matrix();
camera = GfxRenderer->Camera_Position();
projection = editor_mode::projection_matrix(io.DisplaySize.y > 0.0f ? io.DisplaySize.x / io.DisplaySize.y : 1.0f);
}
// moves the gizmo, placed at the point given in world space, with snapping to the step when ctrl is held
bool manipulate(ImGuizmo::OPERATION const Operation, glm::mat4 &Gizmo, float const Snap, glm::mat4 *Delta = nullptr) const
{
glm::vec3 snapvalue(Snap);
float const *snap = Global.ctrlState && Snap > 0.0f ? &snapvalue.x : nullptr;
return ImGuizmo::Manipulate(&view[0][0], &projection[0][0], Operation, ImGuizmo::WORLD, &Gizmo[0][0], Delta != nullptr ? &(*Delta)[0][0] : nullptr, snap);
}
};

View File

@@ -86,6 +86,18 @@ void editor_ui::render_mode_options(nodebank_panel::edit_mode const Mode)
m_filloptions();
break;
case nodebank_panel::TRACK:
if (m_gaugewindow != nullptr)
{
auto const open{*m_gaugewindow};
if (open)
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));
if (ImGui::Button(STR_C("Structure gauge"), ImVec2(-1.0f, 0.0f)))
*m_gaugewindow = false == *m_gaugewindow;
if (open)
ImGui::PopStyleColor();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Checks which models enter the structure gauge of the tracks and the clearance over the roads (skrajnia budowli)"));
}
if (m_trackoptions)
m_trackoptions();
break;

View File

@@ -64,6 +64,11 @@ class editor_ui : public ui_layer
{
m_gizmooptions = std::move(Renderer);
}
// switch of the structure gauge window in the menu bar (the window lives in the editor mode)
void set_gauge_window(bool *Open)
{
m_gaugewindow = Open;
}
// actions of the file menu, performed by the editor mode
void set_file_actions(std::function<void()> Save, std::function<void()> Export);
// shows outcome of an operation in the layers window
@@ -90,4 +95,5 @@ class editor_ui : public ui_layer
std::function<void()> m_menuoptions;
std::function<void()> m_gizmooptions;
scene::basic_node *m_node{nullptr}; // currently bound scene node, if any
bool *m_gaugewindow{nullptr};
};