From 4512ed4e0b3f3c648a25158be5e110b1dce5cfff Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 21 Oct 2026 08:42:29 +0200 Subject: [PATCH] fix(editor): guard the places where the editor could crash outright Found with an address and undefined behaviour sanitizer build, random monkey sessions on the test sceneries and broken inputs: - A deleted model left its pointer in the pick list of the renderer, the bound node of the panels, the bindings of the infrastructure and the selection: the next pick or panel frame read freed memory. The renderer forgets it, and one erase_model does the cleaning for all the places which deleted a model by hand. - The plan band of a route read the vertex before the first one in the frame a drag was let go. - A scenery which can't be opened goes back to the launcher with the reason in the log, a track of an unknown type or a tributary loads as a normal track or a river with a message, and a spline with no finite length or step gets a sane number of segments. - Typed numbers which aren't (NaN, inf, ends 0 m or 1000 km apart, a vector of 1e30, a diverging track part of 5 km, a curve threshold of 0, a zero history size, radius norms below zero) are refused or clamped, where they made the geometry run away or divide by zero. - Folders which can't be read and file names which aren't valid in the locale no longer throw out of the scans of sceneries, textures, sweeps, roads and signal plates; the scans skip them. - The launcher: Ctrl+Del with no trainset selected, a dropped vehicle whose skin is gone and a text with a percent sign. - A terrain mesh made for another resolution of its chunk isn't put into the chunk, and a slot is reused only for a mesh of the same size. - Vehicles on a track changed by the editor are moved over a copy of the list they leave. - The table of the keyboard shortcuts gives its stretched column a weight, else ImGui divides 0 by 0 in the first frame of the window. --- application/editormode.cpp | 85 ++++++++++++++--------- application/editormode.h | 2 + application/editormodeconnectioncurve.cpp | 4 +- application/editormodeinfra.cpp | 16 +++++ application/editormodeprofile.cpp | 2 + application/editormoderoute.cpp | 6 +- application/editormodesignal.cpp | 13 +++- application/editormodestraights.cpp | 10 +++ application/editormodesweep.cpp | 42 +++++++---- application/editormodetextures.cpp | 20 ++++-- application/editormodetrack.cpp | 13 ++-- application/editormodetrackprops.cpp | 31 +++++---- application/editormodetrackux.cpp | 2 +- application/editoruilayer.h | 4 ++ application/scenarioloadermode.cpp | 12 +++- editor/editorAlignment.cpp | 2 +- editor/editorNmt.cpp | 2 +- editor/editorProfile.cpp | 2 +- editor/editorRoad.cpp | 13 +++- editor/editorSwitchDesign.cpp | 10 +-- editor/editorSwitchStore.cpp | 10 ++- editor/editorTerrainStreamer.cpp | 7 +- editor/editorTerrainStreamer.hpp | 1 + editor/editorTrack.cpp | 5 +- lang/editor_pl.po | 6 ++ launcher/scenery_list.cpp | 28 ++++---- launcher/scenery_scanner.cpp | 16 +++-- launcher/textures_scanner.cpp | 9 +-- rendering/opengl33renderer.cpp | 7 ++ rendering/opengl33renderer.h | 2 + rendering/openglrenderer.cpp | 9 +++ rendering/openglrenderer.h | 2 + rendering/renderer.h | 1 + simulation/simulation.cpp | 4 ++ world/Segment.cpp | 9 ++- world/Track.cpp | 15 +++- 36 files changed, 311 insertions(+), 111 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index cd665ef9..0b2ee811 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -200,6 +200,12 @@ namespace triangle_grid(std::vector Triangles, glm::dvec2 const &Min, glm::dvec2 const &Max) : m_triangles(std::move(Triangles)), m_min(Min) { glm::dvec2 const extent = glm::max(Max - Min, glm::dvec2(1.0)); + if (false == std::isfinite(extent.x) || false == std::isfinite(extent.y)) + { + m_triangles.clear(); + m_cells.resize(1); + return; + } // ~8 m cells, coarser for big areas so large terrain triangles don't flood the grid m_cellsize = std::max(8.0, std::max(extent.x, extent.y) / 128.0); m_columns = static_cast(std::ceil(extent.x / m_cellsize)) + 1; @@ -241,11 +247,13 @@ namespace private: int column(double const X) const { - return static_cast(std::clamp(std::floor((X - m_min.x) / m_cellsize), 0.0, static_cast(m_columns - 1))); + double const cell = std::floor((X - m_min.x) / m_cellsize); + return cell > 0.0 ? static_cast(std::min(cell, static_cast(m_columns - 1))) : 0; } int row(double const Z) const { - return static_cast(std::clamp(std::floor((Z - m_min.y) / m_cellsize), 0.0, static_cast(m_rows - 1))); + double const cell = std::floor((Z - m_min.y) / m_cellsize); + return cell > 0.0 ? static_cast(std::min(cell, static_cast(m_rows - 1))) : 0; } std::vector m_triangles; @@ -862,6 +870,10 @@ void editor_mode::nullify_history_pointers(scene::basic_node *node) if (!node) return; + if (ui()->node() == node) + ui()->set_node(nullptr); + infra_forget(node); + for (auto &s : m_history) { if (s.node_ptr == node) @@ -1076,9 +1088,7 @@ void editor_mode::undo_one() if (auto *model = dynamic_cast(target)) { - nullify_history_pointers(model); - remove_from_hierarchy(model); - simulation::State.delete_model(model); + erase_model(model); m_node = nullptr; ui()->set_node(nullptr); } @@ -1204,9 +1214,7 @@ void editor_mode::redo_one() { if (auto *model = dynamic_cast(target)) { - nullify_history_pointers(model); - remove_from_hierarchy(model); - simulation::State.delete_model(model); + erase_model(model); m_node = nullptr; ui()->set_node(nullptr); } @@ -3061,7 +3069,10 @@ void editor_mode::set_include_directive(scene::instance_handle const Instance, s simulation::State.rebuild_include(Instance, retired); // the pick of the renderer is resolved a few frames after it's requested, and can still deliver one of these for (auto *model : retired) + { + nullify_history_pointers(model); m_retired.emplace_back(model, 120); + } } void editor_mode::apply_include() @@ -3332,12 +3343,10 @@ void editor_mode::capture_terrain() std::string as_text; model->export_as_text(as_text); push_snapshot(model, EditorSnapshot::Action::Delete, as_text); - nullify_history_pointers(model); - remove_from_hierarchy(model); m_node = nullptr; m_dragging = false; ui()->set_node(nullptr); - simulation::State.delete_model(model); + erase_model(model); } // the ground under a point the cursor landed on. the orthophoto laid over the ground catches the cursor like any @@ -3453,13 +3462,21 @@ void editor_mode::read_georeference() std::error_code error; if (false == std::filesystem::exists(file, error)) { - for (auto const &entry : std::filesystem::directory_iterator(Global.asCurrentSceneryPath, error)) + for (std::filesystem::directory_iterator entry{Global.asCurrentSceneryPath, error}, end; entry != end; entry.increment(error)) { - auto name{entry.path().filename().string()}; + std::string name; + try + { + name = entry->path().filename().string(); + } + catch (std::exception const &) + { + continue; + } std::transform(name.begin(), name.end(), name.begin(), [](unsigned char const Character) { return static_cast(std::tolower(Character)); }); if (name == Global.SceneryFile) { - file = entry.path(); + file = entry->path(); break; } } @@ -3696,10 +3713,19 @@ void editor_mode::render_open_scenery_popup() m_openscenery_list.clear(); m_openscenery_choice.clear(); std::error_code error; - for (auto const &entry : std::filesystem::directory_iterator(Global.asCurrentSceneryPath, error)) + for (std::filesystem::directory_iterator entry{Global.asCurrentSceneryPath, error}, end; entry != end; entry.increment(error)) { - auto const name{entry.path().filename().string()}; - if (entry.is_regular_file(error) && name.size() > 4 && lower(name.substr(name.size() - 4)) == ".scn" && name.front() != '$') + std::string name; + try + { + name = entry->path().filename().string(); + } + catch (std::exception const &) + { + continue; + } + std::error_code status; + if (entry->is_regular_file(status) && name.size() > 4 && lower(name.substr(name.size() - 4)) == ".scn" && name.front() != '$') m_openscenery_list.emplace_back(name); } std::sort(m_openscenery_list.begin(), m_openscenery_list.end(), [&](std::string const &A, std::string const &B) { return lower(A) < lower(B); }); @@ -3807,9 +3833,7 @@ void editor_mode::undo_last_area_fill() m_node = nullptr; ui()->set_node(nullptr); } - nullify_history_pointers(model); - remove_from_hierarchy(model); - simulation::State.delete_model(model); + erase_model(model); } m_fill_status = format(STR_C("Removed %zu objects of the last fill"), created.size()); } @@ -4230,9 +4254,7 @@ void editor_mode::shape_array() copies.pop_back(); if (model == nullptr) continue; - nullify_history_pointers(model); - remove_from_hierarchy(model); - simulation::State.delete_model(model); + erase_model(model); } std::string definition; @@ -4317,9 +4339,7 @@ void editor_mode::restore_array_snapshot(EditorSnapshot Snapshot, std::vectorset_node(nullptr); } - nullify_history_pointers(model); - remove_from_hierarchy(model); - simulation::State.delete_model(model); + erase_model(model); } else { @@ -5488,7 +5508,7 @@ void editor_mode::render_shortcuts_window() if (false == ImGui::BeginTable(entry.title, 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) continue; ImGui::TableSetupColumn("##keys", ImGuiTableColumnFlags_WidthFixed, 250.0f * Global.ui_scale); - ImGui::TableSetupColumn("##action", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##action", ImGuiTableColumnFlags_WidthStretch, 1.0f); for (auto const &item : entry.shortcuts) { if (false == matches(item)) @@ -6123,15 +6143,17 @@ void editor_mode::delete_model(TAnimModel *model, bool const Joined) m_history.back().joined = Joined; WriteLog(debug, logtype::generic); - // clear history pointers referencing this model before actually deleting it + erase_model(model); +} + +void editor_mode::erase_model(TAnimModel *model) +{ nullify_history_pointers(model); remove_from_hierarchy(model); - if (m_node == model) { m_node = nullptr; m_dragging = false; - ui()->set_node(nullptr); } simulation::State.delete_model(model); } @@ -6561,6 +6583,7 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const auto *const previous{m_node}; m_node = nullptr; select_include(0); + ui()->set_node(additive ? previous : nullptr); // in a scenery opened for editing selection has its limits. nodes of locked layers are off limits. // nodes defined by a scenery template can't be rewritten one by one on save, so a click on one @@ -6722,7 +6745,7 @@ void editor_mode::render_change_history(){ int maxsize = m_max_history_size; if (ImGui::InputInt(STR_C("Max history size"), &maxsize)) { - m_max_history_size = std::max(0, maxsize); + m_max_history_size = std::max(1, maxsize); if ((int)m_history.size() > m_max_history_size && m_max_history_size >= 0) { auto remove_count = (int)m_history.size() - m_max_history_size; diff --git a/application/editormode.h b/application/editormode.h index 4ef8dac9..05f2ec4e 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -639,6 +639,7 @@ class editor_mode : public application_mode, private editor_track::observer // hierarchy management void add_to_hierarchy(scene::basic_node *node); void remove_from_hierarchy(scene::basic_node *node); + void erase_model(TAnimModel *model); 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); @@ -2496,6 +2497,7 @@ class editor_mode : public application_mode, private editor_track::observer bool m_infra_suspended{false}; void infra_load(); void infra_store(); + void infra_forget(scene::basic_node const *Node); std::vector infra_scope() const; void infra_recognize(); void infra_bind_chosen(); diff --git a/application/editormodeconnectioncurve.cpp b/application/editormodeconnectioncurve.cpp index 20e81fdb..d1900c06 100644 --- a/application/editormodeconnectioncurve.cpp +++ b/application/editormodeconnectioncurve.cpp @@ -333,7 +333,7 @@ void editor_mode::insert_curved_connection() // the top of the ramps where the switch on the outer track ends: no short paths left between the two if (c1 > c2) std::swap(c1, c2); - auto const ramp{std::abs(lift) / (kRampShare * m_profile.line.norms.curve_threshold / 1000.0)}; + auto const ramp{std::abs(lift) / (std::max(0.5, kRampShare * m_profile.line.norms.curve_threshold) / 1000.0)}; flatfrom = editor_track::sampled_position(samples, c1); flatto = editor_track::sampled_position(samples, c2); ramplength = ramp; @@ -545,7 +545,7 @@ void editor_mode::insert_curved_connection() } tool.status = format(STR_C("Crossover laid along the curve, switches %s, the tracks %.2f m apart"), names.c_str(), std::abs(offset)); if (std::abs(lift) > 0.002) - tool.status += format(STR_C("\nThe outer track went up %.3f m onto the plane of the cant of the inner one, with ramps of %.1f per mille"), std::abs(lift), kRampShare * m_profile.line.norms.curve_threshold); + tool.status += format(STR_C("\nThe outer track went up %.3f m onto the plane of the cant of the inner one, with ramps of %.1f per mille"), std::abs(lift), std::max(0.5, kRampShare * m_profile.line.norms.curve_threshold)); tool.status += "\n" + connection_readout(plan); ui()->set_status(tool.status, false); } diff --git a/application/editormodeinfra.cpp b/application/editormodeinfra.cpp index db6e4f3b..2644d07c 100644 --- a/application/editormodeinfra.cpp +++ b/application/editormodeinfra.cpp @@ -97,6 +97,11 @@ class live_nodes { return Node != nullptr && m_nodes.count(Node) > 0; } + void forget(scene::basic_node const *Node) + { + m_nodes.erase(Node); + m_valid = false; + } private: std::unordered_set m_nodes; @@ -778,6 +783,17 @@ void editor_mode::infra_load() logtype::generic); } +void editor_mode::infra_forget(scene::basic_node const *Node) +{ + Live.forget(Node); + auto const held = [Node](infra::binding const &Binding) { return Binding.type != infra::kind::include && Binding.node == Node; }; + m_bindings.erase(std::remove_if(m_bindings.begin(), m_bindings.end(), held), m_bindings.end()); + m_infra_buffer.erase(std::remove_if(m_infra_buffer.begin(), m_infra_buffer.end(), [&](infra::object_state const &State) { return held(State.saved); }), m_infra_buffer.end()); + for (auto &candidate : m_infra.candidates) + if (held(candidate.binding)) + candidate.binding.node = nullptr; +} + void editor_mode::infra_store() { if (false == m_bindings_loaded || scene::Layers.empty()) diff --git a/application/editormodeprofile.cpp b/application/editormodeprofile.cpp index c49b12b7..cc076c1e 100644 --- a/application/editormodeprofile.cpp +++ b/application/editormodeprofile.cpp @@ -1912,6 +1912,8 @@ bool editor_mode::render_profile_parameters() ImGui::EndTable(); } norms.grade_switch_max = std::max(0.0, norms.grade_switch_max); + norms.radius_factor = std::max(0.0, norms.radius_factor); + norms.radius_min = std::max(10.0, norms.radius_min); if (ImGui::SmallButton(STR_C("Automatic R at all points"))) { for (auto &point : line.points) diff --git a/application/editormoderoute.cpp b/application/editormoderoute.cpp index a176a4b8..fc46f264 100644 --- a/application/editormoderoute.cpp +++ b/application/editormoderoute.cpp @@ -279,7 +279,7 @@ void editor_mode::render_route_stored() ImGui::EndTable(); } auto const &records{alignment_store::records()}; - if (route.departed < 0 || route.departed >= static_cast(records.size())) + if (route.departed < 0 || route.departed >= static_cast(records.size()) || false == scene::Layers.valid(records[route.departed].layer)) return; auto const &layer{scene::Layers.layer(records[route.departed].layer).name}; ImGui::PushTextWrapPos(0.0f); @@ -404,7 +404,7 @@ int editor_mode::digitize_parts(editor_track::chain const &Chain, std::string *R bool editor_mode::digitize_chain(editor_track::chain const &Chain, std::string *Reason, TTrack **Culprit) { - if (Chain.tracks.empty()) + if (Chain.tracks.empty() || scene::Layers.empty()) return false; auto const name = [](TTrack const *Track) { return Track == nullptr || Track->name().empty() ? std::string{STR_C("(no name)")} : Track->name(); }; auto const nearest = [&](glm::dvec2 const &Point) { @@ -1423,7 +1423,7 @@ bool editor_mode::render_route_band() if (false == dragging) list->AddLine(ImVec2(mouse.x, plot_top), ImVec2(mouse.x, plot_bottom), IM_COL32(255, 255, 255, 60)); } - if (dragging && band.vertex < static_cast(design.vertices.size())) + if (dragging && band.vertex >= 0 && band.vertex < static_cast(design.vertices.size())) { auto const &vertex{design.vertices[band.vertex]}; auto const text{band.part == 1 ? format("R %.0f m", vertex.radius) : format("%.0f m", band.part == 2 ? vertex.transition_in : vertex.transition_out)}; diff --git a/application/editormodesignal.cpp b/application/editormodesignal.cpp index 1fb56425..0156c914 100644 --- a/application/editormodesignal.cpp +++ b/application/editormodesignal.cpp @@ -156,7 +156,15 @@ std::vector const &plate_textures() std::error_code error; for (std::filesystem::directory_iterator file{"textures/tabl", error}, end; file != end; file.increment(error)) { - auto const stem{lower(file->path().stem().string())}; + std::string stem; + try + { + stem = lower(file->path().stem().string()); + } + catch (std::exception const &) + { + continue; + } if (std::find(plates.begin(), plates.end(), stem) == plates.end()) plates.push_back(stem); } @@ -578,8 +586,9 @@ void editor_mode::signal_release() } tool.status = found->read.empty() ? format(STR_C("Signal %s moved, Ctrl+Z takes it back"), found->name.c_str()) : format(STR_C("Signal %s moved, read by %s %s of %s, Ctrl+Z takes it back"), found->name.c_str(), editor_track::event_keyword(spot.list), found->read.c_str(), spot.track->name().empty() ? STR_C("(no name)") : spot.track->name().c_str()); + auto const instance{found->include.instance}; signal_refresh(true); - signal_select(found->include.instance); + signal_select(instance); } std::string editor_mode::signal_variant(signal_template const &Template, signal_spot const &Spot) const diff --git a/application/editormodestraights.cpp b/application/editormodestraights.cpp index 992bc0a4..2a22da38 100644 --- a/application/editormodestraights.cpp +++ b/application/editormodestraights.cpp @@ -347,6 +347,8 @@ void editor_mode::render_straights_ui() for (int i = 0; i < static_cast(state.found.size()); ++i) { auto const &line{state.found[i]}; + if (line.tracks.empty()) + continue; if (state.filter[0] != '\0' && std::none_of(line.tracks.begin(), line.tracks.end(), [&](TTrack const *Track) { return Track->name().find(state.filter) != std::string::npos; })) continue; auto const label{std::to_string(i + 1) + ". " + describe(line) + "##straight" + std::to_string(i)}; @@ -562,6 +564,14 @@ void editor_mode::straights_apply(std::vector const &Lin { auto &state{m_straights}; state.status.clear(); + auto const finite = [](glm::dvec3 const &Point) { return std::isfinite(Point.x) && std::isfinite(Point.y) && std::isfinite(Point.z); }; + if (std::any_of(Ends.begin(), Ends.end(), [&](std::pair const &End) { + return false == finite(End.first) || false == finite(End.second) || false == (glm::distance(End.first, End.second) >= 0.1 && glm::distance(End.first, End.second) <= 100000.0); + })) + { + state.status = STR_C("The ends have to be numbers, from 0.1 m to 100 km apart"); + return; + } auto const tracks{editor_track::straight_affected(Lines)}; if (false == tracks_editable(tracks, state.status)) return; diff --git a/application/editormodesweep.cpp b/application/editormodesweep.cpp index f34f6c01..5fe783a6 100644 --- a/application/editormodesweep.cpp +++ b/application/editormodesweep.cpp @@ -144,14 +144,26 @@ void editor_mode::sweep_scan_models() tool.scanned = true; changed = true; std::error_code error; - for (auto const &entry : std::filesystem::recursive_directory_iterator("models", std::filesystem::directory_options::skip_permission_denied, error)) + for (std::filesystem::recursive_directory_iterator entry{"models", std::filesystem::directory_options::skip_permission_denied, error}, end; entry != end; entry.increment(error)) { - if (false == entry.is_regular_file(error)) + std::error_code status; + if (false == entry->is_regular_file(status)) continue; - auto const extension{lower(entry.path().extension().string())}; - if (extension != ".e3d" && extension != ".t3d") + std::string extension; + std::string name; + try + { + extension = lower(entry->path().extension().string()); + if (extension != ".e3d" && extension != ".t3d") + continue; + name = std::filesystem::relative(entry->path(), "models", status).generic_string(); + } + catch (std::exception const &) + { + continue; + } + if (name.size() < extension.size()) continue; - auto name{std::filesystem::relative(entry.path(), "models", error).generic_string()}; name.erase(name.size() - extension.size()); tool.models.push_back(name); } @@ -799,9 +811,17 @@ void editor_mode::render_sweep_ui() auto const start{prefix.substr(slash + 1)}; std::set variants; std::error_code error; - for (auto const &entry : std::filesystem::directory_iterator(folder, error)) + for (std::filesystem::directory_iterator entry{folder, error}, end; entry != end; entry.increment(error)) { - auto const stem{lower(entry.path().stem().string())}; + std::string stem; + try + { + stem = lower(entry->path().stem().string()); + } + catch (std::exception const &) + { + continue; + } if (stem.size() > start.size() + suffix.size() && stem.compare(0, start.size(), start) == 0 && (suffix.empty() || stem.compare(stem.size() - suffix.size(), suffix.size(), lower(suffix)) == 0)) variants.insert(stem.substr(start.size(), stem.size() - start.size() - suffix.size())); } @@ -1405,12 +1425,10 @@ void editor_mode::bend_selection() snap.rotation = model->Angles(); snap.scale = model->Scale(); snap.uuid = model->uuid; - nullify_history_pointers(model); - remove_from_hierarchy(model); m_node = nullptr; m_dragging = false; ui()->set_node(nullptr); - simulation::State.delete_model(model); + erase_model(model); } else { @@ -1463,9 +1481,7 @@ void editor_mode::restore_bend(EditorSnapshot &Snapshot, bool const Undo) } else if (auto *model{dynamic_cast(find_node_by_any(Snapshot.node_ptr, Snapshot.uuid.to_string(), Snapshot.node_name))}) { - nullify_history_pointers(model); - remove_from_hierarchy(model); - simulation::State.delete_model(model); + erase_model(model); Snapshot.node_ptr = nullptr; m_node = nullptr; ui()->set_node(nullptr); diff --git a/application/editormodetextures.cpp b/application/editormodetextures.cpp index f18dff0a..c80e5032 100644 --- a/application/editormodetextures.cpp +++ b/application/editormodetextures.cpp @@ -121,16 +121,28 @@ void editor_mode::render_texture_browser() for (std::filesystem::directory_iterator entry{std::string{paths::textures} + browser.folder, error}, end; false == static_cast(error) && entry != end; entry.increment(error)) { auto const path{entry->path()}; - if (entry->is_directory(error)) + std::string name; + std::string extension; + std::string stem; + try + { + name = path.filename().string(); + extension = lowercase(path.extension().string()); + stem = path.stem().string(); + } + catch (std::exception const &) { - browser.folders.push_back(path.filename().string()); continue; } - auto const extension{lowercase(path.extension().string())}; + std::error_code status; + if (entry->is_directory(status)) + { + browser.folders.push_back(name); + continue; + } auto const material{extension == ".mat"}; if (false == material && false == texture_file(extension)) continue; - auto const stem{path.stem().string()}; auto &item{items[stem]}; item.name = browser.folder + stem; item.label = stem; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index a41f544c..73ade052 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1345,6 +1345,7 @@ void editor_mode::render_path_ui() } }; + auto const finite = [](glm::dvec3 const &Point) { return std::isfinite(Point.x) && std::isfinite(Point.y) && std::isfinite(Point.z); }; if (ImGui::CollapsingHeader(track->eType == tt_Switch ? STR_C("Geometry of the paths") : STR_C("Geometry"))) { for (int i = 0; i < static_cast(track->m_paths.size()); ++i) @@ -1381,7 +1382,7 @@ void editor_mode::render_path_ui() if (editor_track::is_end(kind)) { auto position = path.points[kind == editor_track::point_kind::start ? segment_data::point::start : segment_data::point::end]; - if (ImGui::DragScalarN("x, y, z##point", ImGuiDataType_Double, glm::value_ptr(position), 3, 0.01f, nullptr, nullptr, "%.3f")) + if (ImGui::DragScalarN("x, y, z##point", ImGuiDataType_Double, glm::value_ptr(position), 3, 0.01f, nullptr, nullptr, "%.3f") && finite(position)) editor_track::move_point(*track, m_track_point, position); item_tooltip("Drag to move, Ctrl+click to type the value"); finish_edit(rebuild::geometry); @@ -1389,7 +1390,9 @@ void editor_mode::render_path_ui() else { auto &vector{path.points[kind == editor_track::point_kind::control1 ? segment_data::point::control1 : segment_data::point::control2]}; - ImGui::DragScalarN(STR_C("vector##point"), ImGuiDataType_Double, glm::value_ptr(vector), 3, 0.01f, nullptr, nullptr, "%.3f"); + auto typed{vector}; + if (ImGui::DragScalarN(STR_C("vector##point"), ImGuiDataType_Double, glm::value_ptr(typed), 3, 0.01f, nullptr, nullptr, "%.3f") && finite(typed) && glm::length(typed) < 1e5) + vector = typed; item_tooltip("Control vector, relative to its end; zero for a straight"); finish_edit(rebuild::geometry); } @@ -1399,11 +1402,13 @@ void editor_mode::render_path_ui() if (begin_fields("##cantradius", 0.3f)) { field_row("Roll (deg)"); - ImGui::DragFloat2("##roll", path.rolls.data(), 0.01f, -15.0f, 15.0f, "%.2f"); + if (auto rolls{path.rolls}; ImGui::DragFloat2("##roll", rolls.data(), 0.01f, -15.0f, 15.0f, "%.2f") && std::isfinite(rolls[0]) && std::isfinite(rolls[1])) + path.rolls = rolls; item_tooltip("Cant at the start and at the end as the roll angle of the track"); finish_edit(rebuild::geometry); field_row("Radius"); - ImGui::InputFloat("##radius", &path.radius, 0.0f, 0.0f, "%.1f"); + if (auto radius{path.radius}; ImGui::InputFloat("##radius", &radius, 0.0f, 0.0f, "%.1f") && std::isfinite(radius)) + path.radius = radius; item_tooltip("Radius stored with the path in the scenery; the shape itself comes from the control vectors"); finish_edit(rebuild::geometry); ImGui::EndTable(); diff --git a/application/editormodetrackprops.cpp b/application/editormodetrackprops.cpp index 574c2cda..4d507023 100644 --- a/application/editormodetrackprops.cpp +++ b/application/editormodetrackprops.cpp @@ -875,21 +875,28 @@ void editor_mode::track_textures_index() for (std::filesystem::recursive_directory_iterator entry{root, options, error}, end; false == static_cast(error) && entry != end; entry.increment(error)) { std::error_code ignored; - if (entry->is_directory(ignored)) + try + { + if (entry->is_directory(ignored)) + { + // the normal maps go with their textures, not on a track by themselves + if (lowered(entry->path().filename().string()) == "normals") + entry.disable_recursion_pending(); + continue; + } + auto const extension{lowered(entry->path().extension().string())}; + auto const material{extension == ".mat"}; + if (false == material && false == texture_extension(extension)) + continue; + auto path{entry->path().lexically_relative(root)}; + path.replace_extension(); + auto &known{found[path.generic_string()]}; + known = known || material; + } + catch (std::exception const &) { - // the normal maps go with their textures, not on a track by themselves - if (lowered(entry->path().filename().string()) == "normals") - entry.disable_recursion_pending(); continue; } - auto const extension{lowered(entry->path().extension().string())}; - auto const material{extension == ".mat"}; - if (false == material && false == texture_extension(extension)) - continue; - auto path{entry->path().lexically_relative(root)}; - path.replace_extension(); - auto &known{found[path.generic_string()]}; - known = known || material; } m_track_texture_index.clear(); m_track_texture_index.reserve(found.size()); diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index 96d5d9ac..9518c5ca 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -962,7 +962,7 @@ bool editor_mode::typed_key(int const Key) glm::dvec3 editor_mode::typed_lay(glm::dvec3 const &Mouse) const { auto const values{typed_values()}; - if (values.empty() || m_lay.points.empty() || values[0] <= 0.0) + if (values.empty() || m_lay.points.empty() || false == (values[0] > 0.0 && values[0] <= 50000.0)) return Mouse; auto const &last{m_lay.points.back()}; glm::dvec2 heading{Mouse.x - last.x, Mouse.z - last.z}; diff --git a/application/editoruilayer.h b/application/editoruilayer.h index f405bad5..b6c8a934 100644 --- a/application/editoruilayer.h +++ b/application/editoruilayer.h @@ -32,6 +32,10 @@ class editor_ui : public ui_layer // updates state of UI elements void update() override; void set_node(scene::basic_node *Node); + scene::basic_node *node() const + { + return m_node; + } // include of a scenery template to show in the node properties in place of a node; nullptr if there's none void set_include(include_selection *Include); void add_node_template(const std::string &desc); diff --git a/application/scenarioloadermode.cpp b/application/scenarioloadermode.cpp index 8409e762..7afe0a44 100644 --- a/application/scenarioloadermode.cpp +++ b/application/scenarioloadermode.cpp @@ -49,7 +49,17 @@ bool scenarioloader_mode::update() { WriteLog( "\nLoading scenario \"" + Global.SceneryFile + "\"..." ); timestart = std::chrono::system_clock::now(); - state = simulation::State.deserialize_begin(Global.SceneryFile); + try { + state = simulation::State.deserialize_begin(Global.SceneryFile); + } + catch (std::exception const &e) { + ErrorLog("Bad init: scenario \"" + Global.SceneryFile + "\" couldn't be opened (" + e.what() + ")"); + state.reset(); + Global.SceneryFile.clear(); + Application.pop_mode(); + Application.push_mode(eu07_application::mode::launcher); + return true; + } } try { diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index ce4db650..2b0d9d59 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -276,7 +276,7 @@ recommendation recommend(double const Speed, double const Radius, limits const & result.cant_equilibrium = factor * Speed * Speed / Radius; result.cant_min = std::max(0.0, result.cant_equilibrium - allowance); result.cant = std::min(Limits.cant_max, ceil_to(std::max(result.cant_min, result.cant_equilibrium * 2.0 / 3.0), 5.0)); - result.transition = transition_length(Speed, Radius, result.cant, Limits); + result.transition = std::min(5000.0, transition_length(Speed, Radius, result.cant, Limits)); return result; } diff --git a/editor/editorNmt.cpp b/editor/editorNmt.cpp index 64f12308..ce29f607 100644 --- a/editor/editorNmt.cpp +++ b/editor/editorNmt.cpp @@ -117,7 +117,7 @@ bool read_geotiff(std::vector const &Data, Square &Out) }; auto const width{static_cast(number(256, 0))}; auto const height{static_cast(number(257, 0))}; - if (width < 2 || height < 2 || number(258, 0) != 32 || number(339, 0) != 3 || std::max(1u, number(259, 0)) != 1 || false == entries.contains(273) || false == entries.contains(33550) || + if (width < 2 || height < 2 || width > 8192 || height > 8192 || number(258, 0) != 32 || number(339, 0) != 3 || std::max(1u, number(259, 0)) != 1 || false == entries.contains(273) || false == entries.contains(33550) || false == entries.contains(33922)) return false; auto const rows{std::max(1u, number(278, 0))}; diff --git a/editor/editorProfile.cpp b/editor/editorProfile.cpp index 6e610958..b838d13d 100644 --- a/editor/editorProfile.cpp +++ b/editor/editorProfile.cpp @@ -56,7 +56,7 @@ double grade_after(line const &Line, std::size_t const Index) double required_radius(line const &Line) { - return std::max(Line.norms.radius_min, Line.norms.radius_factor * Line.speed * Line.speed); + return std::max({10.0, Line.norms.radius_min, Line.norms.radius_factor * Line.speed * Line.speed}); } void assign_radii(line &Line) diff --git a/editor/editorRoad.cpp b/editor/editorRoad.cpp index 136f607e..b32a68b6 100644 --- a/editor/editorRoad.cpp +++ b/editor/editorRoad.cpp @@ -2069,11 +2069,20 @@ std::vector const &editor_road::materials() { if (error || false == file->is_regular_file(error)) continue; - auto extension{file->path().extension().string()}; + std::string extension; + std::string name; + try + { + extension = file->path().extension().string(); + name = file->path().stem().string(); + } + catch (std::exception const &) + { + continue; + } std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); if (extension != ".mat" && extension != ".dds" && extension != ".tga" && extension != ".png" && extension != ".bmp" && extension != ".jpg" && extension != ".ktx" && extension != ".tex") continue; - auto name{file->path().stem().string()}; std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); // names with spaces can't be put in a scenery file if (name.empty() || name.find(' ') != std::string::npos) diff --git a/editor/editorSwitchDesign.cpp b/editor/editorSwitchDesign.cpp index d4ef0a4f..16fb37b2 100644 --- a/editor/editorSwitchDesign.cpp +++ b/editor/editorSwitchDesign.cpp @@ -160,10 +160,12 @@ std::string checked(std::vector const &Parts) return "The diverging track has no parts"; for (auto const &part : Parts) { - if (part.length < 0.05) + if (false == (part.length >= 0.05)) return "A part of the diverging track is shorter than 5 cm"; + if (part.length > 1000.0) + return "A part of the diverging track is longer than 1000 m"; for (auto const radius : {part.radius1, part.radius2}) - if (radius != 0.0 && std::abs(radius) < kMinRadius) + if (std::isnan(radius) || (radius != 0.0 && std::abs(radius) < kMinRadius)) return "A radius of the diverging track is under 10 m"; } return {}; @@ -362,9 +364,9 @@ shape build(design const &Design, std::string &Error) bool const mirrored{Design.type == "Rps"}; if (Error.empty() && twofold && false == mirrored && false == Design.parts2.empty()) Error = checked(Design.parts2); - if (Error.empty() && (Design.lead < 0.0 || Design.lead > 30.0)) + if (Error.empty() && false == (Design.lead >= 0.0 && Design.lead <= 30.0)) Error = "The pre-blade stretch has to be between 0 and 30 m"; - if (Error.empty() && (Design.entry < 0.0 || Design.entry > 5.0)) + if (Error.empty() && false == (Design.entry >= 0.0 && Design.entry <= 5.0)) Error = "The angle of the blade has to be between 0 and 5 degrees"; if (false == Error.empty()) return result; diff --git a/editor/editorSwitchStore.cpp b/editor/editorSwitchStore.cpp index 0851a87f..02a04a3b 100644 --- a/editor/editorSwitchStore.cpp +++ b/editor/editorSwitchStore.cpp @@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/. #include #include +#include #include #include @@ -167,7 +168,7 @@ void load() if (entry.kind != "switch" && entry.kind != "slip" && entry.kind != "crossing" && entry.kind != "double" && entry.kind != "connection") continue; line >> entry.name >> entry.type >> entry.rail >> entry.radius >> entry.ratio >> hand >> entry.a >> entry.b >> entry.main_radius >> keyword >> count; - if (line.fail() || keyword != "parts" || count > 64) + if (line.fail() || keyword != "parts" || count == 0 || count > 64) continue; entry.hand = hand.empty() ? '-' : hand.front(); entry.parts.resize(count); @@ -202,7 +203,12 @@ void load() entry.direction = plan_of(ahead - entry.start); entry.direction = glm::length(entry.direction) > 1e-12 ? glm::normalize(entry.direction) : glm::dvec2{0.0, 1.0}; if (km != "-") - entry.km = std::stod(km) * 1000.0; + { + char *end{nullptr}; + auto const value{std::strtod(km.c_str(), &end)}; + if (end != km.c_str() && *end == '\0' && std::isfinite(value)) + entry.km = value * 1000.0; + } g_records.push_back(std::move(entry)); } } diff --git a/editor/editorTerrainStreamer.cpp b/editor/editorTerrainStreamer.cpp index 8f799c46..91067226 100644 --- a/editor/editorTerrainStreamer.cpp +++ b/editor/editorTerrainStreamer.cpp @@ -260,6 +260,7 @@ void terrain_streamer::worker() } case job::kind::mesh: { + done.cells = task.data->cells; auto const &neighbours{task.neighbours}; heightmap::build_mesh(*task.data, task.stride, task.origin, [&neighbours](double const X, double const Z, double &Height) { @@ -332,7 +333,7 @@ void terrain_streamer::process(result &Result) break; auto &chunk{*lookup->second}; chunk.m_building = false; - if (Result.version < chunk.m_builtversion) + if (Result.version < chunk.m_builtversion || Result.cells != chunk.cells()) break; upload(chunk, Result.mesh, Result.stride); chunk.m_builtversion = Result.version; @@ -386,7 +387,9 @@ void terrain_streamer::upload(editor_terrain &Chunk, heightmap::mesh &Mesh, int gfx::userdata_array nouserdata; // the mesh goes to a geometry slot made for its grid; a slot of the same kind is refilled in place - auto fits = [&](geometry_slot const &Slot) { return Slot.cells == cells && Slot.stride == Stride; }; + auto fits = [&](geometry_slot const &Slot) { + return Slot.cells == cells && Slot.stride == Stride && GfxRenderer->Indices(Slot.geometry).size() == Mesh.indices.size() && GfxRenderer->Vertices(Slot.geometry).size() == Mesh.vertices.size(); + }; if (Chunk.m_slot < 0 || false == fits(m_slots[Chunk.m_slot])) { if (Chunk.m_slot >= 0) diff --git a/editor/editorTerrainStreamer.hpp b/editor/editorTerrainStreamer.hpp index beff9746..c5c5d381 100644 --- a/editor/editorTerrainStreamer.hpp +++ b/editor/editorTerrainStreamer.hpp @@ -209,6 +209,7 @@ class terrain_streamer std::shared_ptr data; heightmap::mesh mesh; int stride{1}; + int cells{0}; std::uint64_t version{0}; std::string error; }; diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 4f30765c..a2c6595a 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -472,8 +472,11 @@ void editor_track::commit(std::vector const &Tracks) for (auto *track : Tracks) track->mark_dirty(); for (auto *track : affected) - for (auto *vehicle : track->Dynamics) + { + auto const vehicles{track->Dynamics}; + for (auto *vehicle : vehicles) vehicle->Move(0.000001); + } if (s_observer != nullptr) s_observer->tracks_committed(Tracks); } diff --git a/lang/editor_pl.po b/lang/editor_pl.po index ec07525b..bd6fef78 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -5118,6 +5118,9 @@ msgstr "Tor zwrotny nie ma części" msgid "A part of the diverging track is shorter than 5 cm" msgstr "Część toru zwrotnego jest krótsza niż 5 cm" +msgid "A part of the diverging track is longer than 1000 m" +msgstr "Część toru zwrotnego jest dłuższa niż 1000 m" + msgid "A radius of the diverging track is under 10 m" msgstr "Promień toru zwrotnego jest mniejszy niż 10 m" @@ -9949,3 +9952,6 @@ msgstr "Dobudowuj teren przy układaniu" msgid "Where a track is laid past the edge of the terrain, the terrain model is made around the cursor as it goes" msgstr "Gdy tor wychodzi poza teren, NMT dobudowuje się wokół kursora na bieżąco" + +msgid "The ends have to be numbers, from 0.1 m to 100 km apart" +msgstr "Końce muszą być liczbami, od 0,1 m do 100 km od siebie" diff --git a/launcher/scenery_list.cpp b/launcher/scenery_list.cpp index d95c962b..195b0e2c 100644 --- a/launcher/scenery_list.cpp +++ b/launcher/scenery_list.cpp @@ -23,7 +23,7 @@ bool ui::scenerylist_panel::on_key(int key, int action) if (it == keyboard.keytonamemap.end()) return false; - if (Global.ctrlState && it->second == "delete") + if (Global.ctrlState && it->second == "delete" && selected_trainset != nullptr) purge_selected_trainset(); return true; @@ -117,7 +117,7 @@ void ui::scenerylist_panel::draw_launch_box() { ImGui::NextColumn(); - ImGui::TextWrapped(selected_trainset->description.c_str()); + ImGui::TextWrapped("%s", selected_trainset->description.c_str()); ImGui::Checkbox(STR_C("Trainset turned around"), &selected_trainset->reversed); if (ImGui::IsItemHovered()) @@ -283,7 +283,8 @@ void ui::scenerylist_panel::draw_trainset(trainset_desc &trainset) draw_droptarget(trainset, position++); ImGui::SameLine(15.0f); - for (auto &dyn_desc : trainset.vehicles) { + for (std::size_t index = 0; index < trainset.vehicles.size(); ++index) { + auto &dyn_desc = trainset.vehicles[index]; deferred_image *mini = nullptr; if (dyn_desc.skin && dyn_desc.skin->mini.get() != deferred_image::invalid_id) @@ -345,16 +346,19 @@ void ui::scenerylist_panel::draw_droptarget(trainset_desc &trainset, int positio if (payload) { skin_set *ptr = *reinterpret_cast(payload->Data); std::shared_ptr skin; - for (auto &s : ptr->vehicle.lock()->matching_skinsets) - if (s.get() == ptr) - skin = s; + if (auto const vehicle = ptr->vehicle.lock()) + for (auto &s : vehicle->matching_skinsets) + if (s.get() == ptr) + skin = s; - trainset.vehicles.emplace(trainset.vehicles.begin() + position); - dynamic_desc &desc = trainset.vehicles[position]; + if (skin) { + trainset.vehicles.emplace(trainset.vehicles.begin() + position); + dynamic_desc &desc = trainset.vehicles[position]; - desc.name = skin->skin + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); - desc.vehicle = skin->vehicle.lock(); - desc.skin = skin; + desc.name = skin->skin + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); + desc.vehicle = skin->vehicle.lock(); + desc.skin = skin; + } } payload = ImGui::AcceptDragDropPayload("vehicle_set"); @@ -368,7 +372,7 @@ void ui::scenerylist_panel::draw_droptarget(trainset_desc &trainset, int positio if (moved->position < position) offset = -1; } else { - desc_copy.name = desc_copy.skin->skin + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); + desc_copy.name = (desc_copy.skin ? desc_copy.skin->skin : desc_copy.name) + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); } trainset.vehicles.insert(trainset.vehicles.begin() + position + offset, desc_copy); diff --git a/launcher/scenery_scanner.cpp b/launcher/scenery_scanner.cpp index bcd18ac9..5f6a6d2f 100644 --- a/launcher/scenery_scanner.cpp +++ b/launcher/scenery_scanner.cpp @@ -10,13 +10,21 @@ scenery_scanner::scenery_scanner(ui::vehicles_bank &bank) void scenery_scanner::scan() { - for (auto &f : std::filesystem::directory_iterator("scenery")) { - std::filesystem::path path(std::filesystem::relative(f.path(), "scenery/")); + std::error_code error; + for (std::filesystem::directory_iterator f{"scenery", std::filesystem::directory_options::skip_permission_denied, error}, end; f != end; f.increment(error)) { + std::filesystem::path path(std::filesystem::relative(f->path(), "scenery/", error)); + std::string name; + try { + name = path.string(); + } + catch (std::exception const &) { + continue; + } - if (*path.filename().string().begin() == '$') + if (name.empty() || path.filename().string().front() == '$') continue; - if (path.string().ends_with(".scn")) + if (name.ends_with(".scn")) scan_scn(path); } diff --git a/launcher/textures_scanner.cpp b/launcher/textures_scanner.cpp index 524a23ca..741f978c 100644 --- a/launcher/textures_scanner.cpp +++ b/launcher/textures_scanner.cpp @@ -4,12 +4,13 @@ void ui::vehicles_bank::scan_textures() { - for (auto &f : std::filesystem::recursive_directory_iterator("dynamic")) { - if (f.path().filename() != "textures.txt") + std::error_code error; + for (std::filesystem::recursive_directory_iterator f{"dynamic", std::filesystem::directory_options::skip_permission_denied, error}, end; f != end; f.increment(error)) { + if (f->path().filename() != "textures.txt") continue; - std::fstream stream(f.path(), std::ios_base::binary | std::ios_base::in); - ctx_path = std::filesystem::relative(f.path().parent_path(), "dynamic/"); + std::fstream stream(f->path(), std::ios_base::binary | std::ios_base::in); + ctx_path = std::filesystem::relative(f->path().parent_path(), "dynamic/", error); std::string line; while (std::getline(stream, line)) diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index b1f9e56d..4d699b52 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -5638,6 +5638,13 @@ void opengl33_renderer::Pick_Node_Callback(std::functionis_busy()) diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index d7adfb9e..46dd8001 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -116,6 +116,8 @@ class opengl33_renderer : public gfx_renderer { Pick_Control() const override { return m_pickcontrolitem; } scene::basic_node const * Pick_Node() const override { return m_picksceneryitem; } + void + Forget_Node( scene::basic_node const *Node ) override; glm::dvec3 Mouse_Position() const override { return m_worldmousecoordinates; } bool diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index d20626e3..3ce07491 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -1902,6 +1902,15 @@ opengl_renderer::Pick_Node_Callback( std::function m_node_pick_requests.emplace_back( Callback ); } +void +opengl_renderer::Forget_Node( scene::basic_node const *Node ) { + + if( m_picksceneryitem == Node ) { + m_picksceneryitem = nullptr; + } + std::replace_if( m_picksceneryitems.begin(), m_picksceneryitems.end(), [Node]( scene::basic_node const *Item ) { return Item == Node; }, nullptr ); +} + void opengl_renderer::Render( scene::basic_region *Region ) { diff --git a/rendering/openglrenderer.h b/rendering/openglrenderer.h index da3faad0..9779388c 100644 --- a/rendering/openglrenderer.h +++ b/rendering/openglrenderer.h @@ -104,6 +104,8 @@ public: Pick_Control() const override { return m_pickcontrolitem; } scene::basic_node const * Pick_Node() const override { return m_picksceneryitem; } + void + Forget_Node( scene::basic_node const *Node ) override; glm::dvec3 Mouse_Position() const override { return m_worldmousecoordinates; } glm::mat4 diff --git a/rendering/renderer.h b/rendering/renderer.h index 2c302a75..c87a8aad 100644 --- a/rendering/renderer.h +++ b/rendering/renderer.h @@ -96,6 +96,7 @@ public: virtual void Pick_Node_Callback( std::function Callback ) = 0; virtual auto Pick_Control() const -> TSubModel const * = 0; virtual auto Pick_Node() const -> scene::basic_node const * = 0; + virtual void Forget_Node( scene::basic_node const *Node ) {} virtual auto Mouse_Position() const -> glm::dvec3 = 0; virtual auto Mouse_Hit() const -> bool { return true; } diff --git a/simulation/simulation.cpp b/simulation/simulation.cpp index 70f9e307..ea217717 100644 --- a/simulation/simulation.cpp +++ b/simulation/simulation.cpp @@ -30,6 +30,7 @@ http://mozilla.org/MPL/2.0/. #include "scene/scenelayers.h" #include "vehicle/Train.h" #include "application/application.h" +#include "rendering/renderer.h" #include "utilities/Logs.h" #include "vehicle/Driver.h" @@ -561,6 +562,9 @@ std::pair state_manager::rebuild_include(scene::instance_handle const } void state_manager::delete_model(TAnimModel *model) { + if (GfxRenderer) { + GfxRenderer->Forget_Node(model); + } scene::Layers.count(model->layer(), scene::layer_item::model, -1); scene::Layers.forget(model); Region->erase(model); diff --git a/world/Segment.cpp b/world/Segment.cpp index 448d421f..2abdf075 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -121,6 +121,11 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N else { fLength = glm::length(Point1 - Point2); } + if( false == std::isfinite( fLength ) ) { + + ErrorLog( "Bad track: spline \"" + pOwner->name() + "\" with no finite length (location: " + to_string( glm::dvec3{ Point1 } ) + ")" ); + fLength = 0.01; + } if (fLength <= 0) { ErrorLog( "Bad track: zero length spline \"" + pOwner->name() + "\" (location: " + to_string( glm::dvec3{ Point1 } ) + ")" ); @@ -129,7 +134,7 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N fStoop = std::atan2(Point2.y - Point1.y, fLength); // pochylenie toru prostego, żeby nie liczyć wielokrotnie - fStep = fNewStep; + fStep = ( fNewStep > 0.0 && std::isfinite( fNewStep ) ) ? fNewStep : fLength; // NOTE: optionally replace this part with the commented version, after solving geometry issues with double switches if( pOwner->eType == tt_Switch && fStep * (3.0 * Global.SplineFidelity) > fLength ) { @@ -137,7 +142,7 @@ bool TSegment::Init(glm::dvec3 &NewPoint1, glm::dvec3 NewCPointOut, glm::dvec3 N fStep = fLength / ( 3.0 * Global.SplineFidelity ); } // iSegCount = static_cast( std::ceil( fLength / fStep ) ); // potrzebne do VBO - iSegCount = pOwner->eType == tt_Switch ? static_cast(6 * Global.SplineFidelity) : static_cast(std::ceil(fLength / fStep)); // potrzebne do VBO + iSegCount = pOwner->eType == tt_Switch ? static_cast(6 * Global.SplineFidelity) : static_cast(std::clamp(std::ceil(fLength / fStep), 1.0, 100000.0)); // potrzebne do VBO fStep = fLength / iSegCount; // update step to equalize size of individual pieces diff --git a/world/Track.cpp b/world/Track.cpp index ae071220..19a9d140 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -426,6 +426,7 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) std::string str; size_t i; //,state; //Ra: teraz już nie ma początkowego stanu zwrotnicy we wpisie std::string token; + bool tributary{false}; parser->getTokens(); *parser >> str; // typ toru @@ -467,11 +468,17 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) } else if (str == "tributary") { - eType = tt_Tributary; + ErrorLog("Bad track: type \"tributary\" of track \"" + m_name + "\" in file \"" + parser->Name() + "\" (line " + std::to_string(parser->Line()) + ") isn't supported, loaded as a river with its first path"); + eType = tt_Normal; iCategoryFlag = 4; + tributary = true; } else - eType = tt_Unknown; + { + ErrorLog("Bad track: unknown type \"" + str + "\" of track \"" + m_name + "\" in file \"" + parser->Name() + "\" (line " + std::to_string(parser->Line()) + "), loaded as a normal track"); + eType = tt_Normal; + iCategoryFlag = 1; + } if (Global.iWriteLogEnabled & 4) WriteLog(str); parser->getTokens(4); @@ -544,6 +551,10 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) // all subtypes contain at least one path m_paths.emplace_back(); m_paths.back().deserialize( *parser, pOrigin ); + if( tributary ) { + segment_data ignored; + ignored.deserialize( *parser, pOrigin ); + } switch( eType ) { case tt_Switch: case tt_Cross: