mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 23:23:30 +02:00
Labels: - every button, field, tooltip and message of the editor reread for a beginner: short nouns for the fields with the sentence in the tooltip, the less used options under More settings, one glossary (field of work, template, track, grade), sides told from directions, degrees and per mille as signs, messages built from one format string - plural forms through STR_P; the last entry of a po file is read too, and the Polish plural rule is in the header - lang/editor_pl.po: the code pass entries merged, 391 entries no code uses and 3 duplicates dropped - roads: the notes and errors naming a road translated; the build options and the layout fold the texture length, kerb sizes, traffic share, roundabout part, height over the ground and junction speed limit under More settings, the lanes start folded Grade lines fitted to the terrain model (NMT), a line or the whole scenery at once: - fit_ground: a robust fit on nodes 5 m apart; the ground parting from the line (a bridge, a viaduct, a wall) is gone past rather than followed, the bends keep off the switches as far as their curves reach, a run of bends the same way is one point of intersection, a curve with no room is as sharp as the room lets it be - the ends of the line and the other paths of its switches leading into lines fitted already bound the line by what the tracks between can climb; ends meeting in the plan, laid apart in height by hand, are joined before the run - apply_profile: the paths are split where split_path takes the parameter (a straight was cut in the wrong place, into pieces of a few centimetres the game takes for zero length), no piece shorter than the margin; the paths off the ends of the route are brought to its height - Bytom: 1600 lines, grade breaks at the joints 89 -> 20; Tarnowskie Gory: 848 lines, 223 -> 3; no step over 2 cm
493 lines
20 KiB
C++
493 lines
20 KiB
C++
/*
|
|
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 "application/editorprojection.h"
|
|
#include "application/editormodeshared.h"
|
|
#include "application/application.h"
|
|
#include "editor/editorFormat.hpp"
|
|
#include "editor/editorGeometry.hpp"
|
|
#include "launcher/textures_scanner.h"
|
|
|
|
#include "simulation/simulation.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "vehicle/DynObj.h"
|
|
#include "world/Track.h"
|
|
|
|
#include "imgui/imgui.h"
|
|
#include "utilities/translation.h"
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <optional>
|
|
#include <limits>
|
|
|
|
namespace
|
|
{
|
|
|
|
using geometry::bezier;
|
|
|
|
std::size_t const kVehicleList{300};
|
|
double const kHalfVehicle{8.0}; // m, the vehicle is placed about centred on the point
|
|
|
|
std::string lower(std::string Text)
|
|
{
|
|
std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char const Character) { return static_cast<char>(std::tolower(Character)); });
|
|
return Text;
|
|
}
|
|
|
|
std::string skin_name(ui::skin_set const &Skin)
|
|
{
|
|
return Skin.meta != nullptr && false == Skin.meta->name.empty() ? Skin.meta->name : Skin.skin;
|
|
}
|
|
|
|
// side view of the vehicle from textures/mini, 0 while there's none; reading one takes a moment, a few of them go in a frame
|
|
ImTextureID vehicle_mini(ui::skin_set &Skin, std::set<ui::skin_set const *> &Asked, int &Loads)
|
|
{
|
|
if (false == static_cast<bool>(Skin.mini))
|
|
return 0;
|
|
if (Asked.count(&Skin) == 0)
|
|
{
|
|
if (Loads >= 4)
|
|
return 0;
|
|
++Loads;
|
|
Asked.insert(&Skin);
|
|
}
|
|
auto const id{Skin.mini.get()};
|
|
return id == deferred_image::invalid_id ? 0 : static_cast<ImTextureID>(id);
|
|
}
|
|
|
|
// the side view fitted into the box, kept in proportion and standing on its bottom; mirrored for a vehicle turned around
|
|
void draw_mini(ImDrawList &Draw, ImTextureID const Image, glm::ivec2 const Size, ImVec2 const Low, ImVec2 const High, bool const Turned)
|
|
{
|
|
if (Image == 0 || Size.x <= 0 || Size.y <= 0)
|
|
{
|
|
Draw.AddRect(Low, High, ImGui::GetColorU32(ImGuiCol_Border), 3.0f);
|
|
return;
|
|
}
|
|
auto const scale{std::min((High.x - Low.x) / Size.x, (High.y - Low.y) / Size.y)};
|
|
auto const width{std::round(Size.x * scale)};
|
|
auto const height{std::round(Size.y * scale)};
|
|
ImVec2 const low{std::round(Low.x + (High.x - Low.x - width) * 0.5f), High.y - height};
|
|
Draw.AddImage(Image, low, ImVec2(low.x + width, High.y), ImVec2(Turned ? 1.0f : 0.0f, 1.0f), ImVec2(Turned ? 0.0f : 1.0f, 0.0f));
|
|
}
|
|
|
|
std::string skin_label(ui::vehicle_desc const &Vehicle, ui::skin_set const &Skin)
|
|
{
|
|
return skin_name(Skin) + " (" + Vehicle.path.generic_string() + ")";
|
|
}
|
|
|
|
int path_under(TTrack const &Track, glm::dvec3 const &Point)
|
|
{
|
|
int result{0};
|
|
auto best{std::numeric_limits<double>::max()};
|
|
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
|
|
{
|
|
bezier const curve{Track.m_paths[i]};
|
|
for (int k = 0; k <= 64; ++k)
|
|
{
|
|
auto const distance{geometry::plan_distance(curve.point(k / 64.0), Point)};
|
|
if (distance < best)
|
|
{
|
|
best = distance;
|
|
result = i;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double nearest_on(TTrack const &Track, int const Path, glm::dvec3 const &Point)
|
|
{
|
|
bezier const curve{Track.m_paths[Path]};
|
|
auto parameter{0.0};
|
|
auto best{std::numeric_limits<double>::max()};
|
|
for (int k = 0; k <= 256; ++k)
|
|
{
|
|
auto const distance{geometry::plan_distance(curve.point(k / 256.0), Point)};
|
|
if (distance < best)
|
|
{
|
|
best = distance;
|
|
parameter = k / 256.0;
|
|
}
|
|
}
|
|
return parameter;
|
|
}
|
|
|
|
glm::dvec2 heading_at(TTrack const &Track, int const Path, glm::dvec3 const &Point)
|
|
{
|
|
auto const tangent{bezier{Track.m_paths[Path]}.first(nearest_on(Track, Path, Point))};
|
|
glm::dvec2 const planar{tangent.x, tangent.z};
|
|
return glm::length(planar) > 1e-9 ? glm::normalize(planar) : glm::dvec2{0.0, 1.0};
|
|
}
|
|
|
|
// distance along the path of the track from its start to the point nearest to the one given
|
|
double distance_along(TTrack const &Track, int const Path, glm::dvec3 const &Point)
|
|
{
|
|
bezier const curve{Track.m_paths[Path]};
|
|
auto const parameter{nearest_on(Track, Path, Point)};
|
|
int const count{64};
|
|
double length{0.0}, total{0.0};
|
|
auto previous{curve.point(0.0)};
|
|
for (int k = 1; k <= count; ++k)
|
|
{
|
|
auto const t{static_cast<double>(k) / count};
|
|
auto const next{curve.point(t)};
|
|
auto const step{glm::distance(previous, next)};
|
|
total += step;
|
|
if (t <= parameter)
|
|
length += step;
|
|
else if (t - 1.0 / count < parameter)
|
|
length += step * (parameter - (t - 1.0 / count)) * count;
|
|
previous = next;
|
|
}
|
|
return std::clamp(length * Track.Length() / std::max(total, 1e-6), 0.0, Track.Length());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void editor_mode::vehicle_start(TTrack &Track, glm::dvec3 const &Point)
|
|
{
|
|
m_vehicle.track = &Track;
|
|
m_vehicle.point = Point;
|
|
m_vehicle.expand = true;
|
|
m_vehicle.status.clear();
|
|
auto const heading{heading_at(Track, Track.eType == tt_Switch ? path_under(Track, Point) : 0, Point)};
|
|
glm::dvec2 const away{Point.x - Global.pCamera.Pos.x, Point.z - Global.pCamera.Pos.z};
|
|
m_vehicle.facing = glm::dot(heading, away) >= 0.0 ? 1 : -1;
|
|
}
|
|
|
|
bool editor_mode::vehicle_place()
|
|
{
|
|
auto &tool{m_vehicle};
|
|
if (tool.track == nullptr || tool.track->m_editorremoved)
|
|
return false;
|
|
auto train{tool.consist};
|
|
if (train.empty())
|
|
{
|
|
if (tool.vehicle == nullptr || tool.skin == nullptr)
|
|
return false;
|
|
train.push_back({tool.vehicle, tool.skin, false});
|
|
}
|
|
auto const path{tool.track->eType == tt_Switch ? path_under(*tool.track, tool.point) : 0};
|
|
if (tool.track->eType == tt_Switch && tool.track->GetSwitchState() != path)
|
|
tool.track->Switch(path);
|
|
auto const along{distance_along(*tool.track, path, tool.point)};
|
|
auto const against{tool.facing < 0};
|
|
std::vector<std::string> names;
|
|
std::string nodes;
|
|
std::string head;
|
|
for (std::size_t i = 0; i < train.size(); ++i)
|
|
{
|
|
auto const &item{train[i]};
|
|
auto const stem{item.vehicle->path.stem().generic_string()};
|
|
std::string name;
|
|
for (int index = 1; index < 10000; ++index)
|
|
{
|
|
name = lower("editor_" + stem + "_" + std::to_string(index));
|
|
if (simulation::Vehicles.find(name) == nullptr && std::find(names.begin(), names.end(), name) == names.end())
|
|
break;
|
|
}
|
|
names.push_back(name);
|
|
auto const leads{i == 0};
|
|
if (leads)
|
|
head = name;
|
|
nodes += "node -1 0 " + name + " dynamic " + item.vehicle->path.parent_path().generic_string() + " " + item.skin->skin + " " + stem + " " + (item.turned ? "-1" : "0") + " " + (leads && tool.driver == 0 ? "headdriver" : "nobody") + " 3 0 enddynamic\n";
|
|
}
|
|
auto const offset{against ? along - kHalfVehicle : along + kHalfVehicle};
|
|
auto const vehicles{simulation::State.insert_trainset(head, tool.track, offset, nodes, against)};
|
|
if (vehicles.empty())
|
|
{
|
|
tool.status = STR_C("The vehicle couldn't be placed, the log says why");
|
|
return false;
|
|
}
|
|
tool.placed = head;
|
|
first_step_done(first_step::drive);
|
|
auto const trackname{tool.track->name().empty() ? std::string{STR("(no name)")} : tool.track->name()};
|
|
auto const behind{static_cast<int>(vehicles.size()) - 1};
|
|
tool.status = behind > 0 ? format(STR_P("%s and %d vehicle behind it stand on %s", "%s and %d vehicles behind it stand on %s", behind), head.c_str(), behind, trackname.c_str()) : format(STR_C("%s stands on %s"), head.c_str(), trackname.c_str());
|
|
if (tool.write && tool.track->name().empty())
|
|
tool.status += ". " + std::string{STR_C("The track has no name, the trainset can't be written to the scenery")};
|
|
else if (tool.write)
|
|
{
|
|
std::string timetable{tool.timetable};
|
|
timetable.erase(std::remove_if(timetable.begin(), timetable.end(), [](unsigned char const Character) { return std::isspace(Character) != 0; }), timetable.end());
|
|
vehicle_tool::trainset entry{head, tool.track->name(), {}};
|
|
entry.lines.push_back("trainset " + (timetable.empty() ? std::string{"none"} : timetable) + " " + tool.track->name() + " " + to_string(offset, 2) + " " + to_string(tool.velocity, 0));
|
|
if (against)
|
|
entry.lines.push_back("reversed");
|
|
std::istringstream lines{nodes};
|
|
for (std::string line; std::getline(lines, line);)
|
|
if (false == line.empty())
|
|
entry.lines.push_back(line);
|
|
entry.lines.push_back("endtrainset");
|
|
tool.trainsets.push_back(std::move(entry));
|
|
tool.status += ". " + std::string{STR_C("Written to the scenery as a trainset on save (Ctrl+S)")};
|
|
}
|
|
else
|
|
tool.status += ". " + std::string{STR_C("Only to drive, it isn't written to the scenery")};
|
|
WriteLog("Editor: " + tool.status, logtype::generic);
|
|
return true;
|
|
}
|
|
|
|
void editor_mode::render_vehicle_ui()
|
|
{
|
|
auto &tool{m_vehicle};
|
|
auto *selected{selected_track()};
|
|
if (selected != nullptr && selected == tool.track && m_hover.track == selected && ImGui::IsMouseClicked(0) && false == ImGui::GetIO().WantCaptureMouse)
|
|
vehicle_start(*selected, m_hover.point);
|
|
else if (selected != nullptr && selected != tool.track)
|
|
vehicle_start(*selected, m_hover.track == selected ? m_hover.point : bezier{selected->m_paths.front()}.point(0.5));
|
|
if (tool.track == nullptr)
|
|
{
|
|
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", STR_C("LMB on a track: where the vehicle stands"));
|
|
return;
|
|
}
|
|
if (tool.bank == nullptr)
|
|
{
|
|
tool.bank = std::make_shared<ui::vehicles_bank>();
|
|
tool.bank->scan_textures();
|
|
}
|
|
ImGui::SetNextItemWidth(-1.0f);
|
|
ImGui::InputTextWithHint("##vehiclesearch", STR_C("search: name, series, skin"), tool.search, sizeof(tool.search));
|
|
ImGui::Checkbox(STR_C("Only the ones with a cab"), &tool.controllable_only);
|
|
std::string const text{lower(tool.search)};
|
|
std::vector<std::pair<std::shared_ptr<ui::vehicle_desc>, std::shared_ptr<ui::skin_set>>> listed;
|
|
std::size_t total{0};
|
|
for (auto const &[path, vehicle] : tool.bank->vehicles)
|
|
{
|
|
if (vehicle == nullptr || (tool.controllable_only && false == vehicle->controllable))
|
|
continue;
|
|
for (auto const &skin : vehicle->matching_skinsets)
|
|
{
|
|
if (skin == nullptr)
|
|
continue;
|
|
if (false == text.empty() && lower(skin_label(*vehicle, *skin)).find(text) == std::string::npos && (skin->meta == nullptr || skin->meta->search_lowered.find(text) == std::string::npos))
|
|
continue;
|
|
++total;
|
|
if (listed.size() < kVehicleList)
|
|
listed.emplace_back(vehicle, skin);
|
|
}
|
|
}
|
|
int loads{0};
|
|
auto const line{ImGui::GetTextLineHeight()};
|
|
auto const box{ImVec2(std::round(ImGui::GetFontSize() * 7.0f), std::round(line * 2.0f))};
|
|
ImGui::BeginChild("##vehicles", ImVec2(0.0f, (box.y + ImGui::GetStyle().ItemSpacing.y) * 5.5f + 6.0f), ImGuiChildFlags_Borders);
|
|
for (std::size_t i = 0; i < listed.size(); ++i)
|
|
{
|
|
auto const &[vehicle, skin] = listed[i];
|
|
ImGui::PushID(static_cast<int>(i));
|
|
auto const low{ImGui::GetCursorScreenPos()};
|
|
if (ImGui::Selectable("##vehicle", tool.skin == skin, ImGuiSelectableFlags_AllowDoubleClick, ImVec2(0.0f, box.y)))
|
|
{
|
|
tool.vehicle = vehicle;
|
|
tool.skin = skin;
|
|
if (ImGui::IsMouseDoubleClicked(0))
|
|
tool.consist.push_back({vehicle, skin, false});
|
|
}
|
|
if (ImGui::IsItemVisible())
|
|
{
|
|
auto &draw{*ImGui::GetWindowDrawList()};
|
|
draw_mini(draw, vehicle_mini(*skin, tool.minis, loads), skin->mini.size(), ImVec2(low.x + 2.0f, low.y + 1.0f), ImVec2(low.x + box.x, low.y + box.y - 1.0f), false);
|
|
auto const left{low.x + box.x + ImGui::GetStyle().ItemSpacing.x};
|
|
auto const right{ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x};
|
|
draw.PushClipRect(ImVec2(left, low.y), ImVec2(right, low.y + box.y), true);
|
|
draw.AddText(ImVec2(left, low.y), ImGui::GetColorU32(ImGuiCol_Text), skin_name(*skin).c_str());
|
|
draw.AddText(ImVec2(left, low.y + line), ImGui::GetColorU32(ImGuiCol_TextDisabled), vehicle->path.generic_string().c_str());
|
|
draw.PopClipRect();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndChild();
|
|
if (total > listed.size())
|
|
ImGui::TextDisabled(STR_C("%zu found, the first %zu listed"), total, listed.size());
|
|
if (tool.vehicle != nullptr && tool.skin != nullptr)
|
|
{
|
|
if (ImGui::Button(format(STR_C("Add %s to the train"), skin_name(*tool.skin).c_str()).c_str(), ImVec2(-1.0f, 0.0f)))
|
|
tool.consist.push_back({tool.vehicle, tool.skin, false});
|
|
item_tooltip("A double click on the list adds it too");
|
|
}
|
|
|
|
// the train as a picture, the head on the left; a click on a vehicle shows its buttons
|
|
ImGui::SeparatorText(STR_C("Train"));
|
|
if (tool.consist.empty())
|
|
ImGui::TextDisabled("%s", STR_C("No vehicles yet: pick one from the list and add it"));
|
|
else
|
|
{
|
|
auto const height{std::round(line * 1.8f)};
|
|
auto const width_of = [&](ui::skin_set const &Skin) {
|
|
auto const size{Skin.mini.size()};
|
|
return size.y > 0 ? std::round(height * size.x / size.y) : height * 3.0f;
|
|
};
|
|
float total_width{0.0f};
|
|
for (auto const &item : tool.consist)
|
|
total_width += width_of(*item.skin) + 2.0f;
|
|
auto const scrolled{total_width > ImGui::GetContentRegionAvail().x};
|
|
ImGui::BeginChild("##train", ImVec2(0.0f, height + 6.0f + (scrolled ? ImGui::GetStyle().ScrollbarSize : 0.0f)), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar);
|
|
auto &draw{*ImGui::GetWindowDrawList()};
|
|
for (std::size_t i = 0; i < tool.consist.size(); ++i)
|
|
{
|
|
auto &item{tool.consist[i]};
|
|
auto const width{width_of(*item.skin)};
|
|
if (i > 0)
|
|
ImGui::SameLine(0.0f, 2.0f);
|
|
ImGui::PushID(static_cast<int>(i));
|
|
auto const low{ImGui::GetCursorScreenPos()};
|
|
if (ImGui::InvisibleButton("##car", ImVec2(width, height + 4.0f)))
|
|
tool.picked = tool.picked == static_cast<int>(i) ? -1 : static_cast<int>(i);
|
|
if (ImGui::IsItemHovered())
|
|
{
|
|
auto const vehicle{item.skin->vehicle.lock()};
|
|
ImGui::SetTooltip("%d. %s", static_cast<int>(i + 1), vehicle != nullptr ? skin_label(*vehicle, *item.skin).c_str() : item.skin->skin.c_str());
|
|
}
|
|
if (tool.picked == static_cast<int>(i) || ImGui::IsItemHovered())
|
|
draw.AddRectFilled(low, ImVec2(low.x + width, low.y + height + 4.0f), tool.picked == static_cast<int>(i) ? family_colour(0, 0.45f) : ImGui::GetColorU32(ImGuiCol_FrameBgHovered), 3.0f);
|
|
draw_mini(draw, vehicle_mini(*item.skin, tool.minis, loads), item.skin->mini.size(), ImVec2(low.x, low.y + 2.0f), ImVec2(low.x + width, low.y + height + 2.0f), item.turned);
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndChild();
|
|
if (tool.picked >= static_cast<int>(tool.consist.size()))
|
|
tool.picked = -1;
|
|
if (tool.picked >= 0)
|
|
{
|
|
auto const i{static_cast<std::size_t>(tool.picked)};
|
|
auto &item{tool.consist[i]};
|
|
ImGui::Text("%d. %s", tool.picked + 1, skin_name(*item.skin).c_str());
|
|
auto const button = [](char const *Label, bool const Enabled, char const *Tooltip) {
|
|
ImGui::BeginDisabled(false == Enabled);
|
|
auto const pressed{ImGui::Button(STR_C(Label))};
|
|
ImGui::EndDisabled();
|
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
|
ImGui::SetTooltip("%s", STR_C(Tooltip));
|
|
ImGui::SameLine();
|
|
return pressed;
|
|
};
|
|
auto const forward{button("Forward", i > 0, "Moves it one place towards the head")};
|
|
auto const back{button("Back", i + 1 < tool.consist.size(), "Moves it one place towards the end")};
|
|
auto const turn{button("Turn around", true, "Turned around in the train")};
|
|
auto const remove{button("Remove", true, "Takes it out of the train")};
|
|
ImGui::NewLine();
|
|
if (forward)
|
|
{
|
|
std::swap(tool.consist[i], tool.consist[i - 1]);
|
|
--tool.picked;
|
|
}
|
|
else if (back)
|
|
{
|
|
std::swap(tool.consist[i], tool.consist[i + 1]);
|
|
++tool.picked;
|
|
}
|
|
else if (turn)
|
|
item.turned = !item.turned;
|
|
else if (remove)
|
|
{
|
|
tool.consist.erase(tool.consist.begin() + static_cast<std::ptrdiff_t>(i));
|
|
tool.picked = -1;
|
|
}
|
|
}
|
|
else
|
|
ImGui::TextDisabled(STR_P("%d vehicle, the head on the left: click it to move it, turn it or remove it", "%d vehicles, the head on the left: click one to move it, turn it or remove it", static_cast<long long>(tool.consist.size())), static_cast<int>(tool.consist.size()));
|
|
if (ImGui::SmallButton(STR_C("Empty the train")))
|
|
{
|
|
tool.consist.clear();
|
|
tool.picked = -1;
|
|
}
|
|
}
|
|
|
|
ImGui::SeparatorText(STR_C("On the track"));
|
|
if (ImGui::Button(STR_C("Turn the train around")))
|
|
tool.facing = -tool.facing;
|
|
item_tooltip("The head goes where the arrow at the marker points");
|
|
segmented("driver", tool.driver,
|
|
{{0, STR_C("With a driver, to drive"), "The train can be driven or run by the AI at once"}, {1, STR_C("Nobody, parked"), "The vehicles stand braked, without a crew"}});
|
|
toggle_chips("vehiclewrite", {{STR_C("Write to the scenery as a trainset"), &tool.write, "On: the trainset is written to the scenery on save (Ctrl+S). Off: only to drive now, gone when the scenery is loaded again"}});
|
|
if (tool.write && ImGui::TreeNode(STR_C("More settings")))
|
|
{
|
|
if (begin_fields("##vehiclefields", 0.45f))
|
|
{
|
|
field_row("Timetable##trainset");
|
|
ImGui::InputText("##vehicletimetable", tool.timetable, sizeof(tool.timetable));
|
|
item_tooltip("The timetable the train runs by; none: no timetable, it waits for orders");
|
|
field_row("Speed at the start (km/h)");
|
|
ImGui::DragScalar("##vehiclevelocity", ImGuiDataType_Double, &tool.velocity, 1.0f, nullptr, nullptr, "%.0f");
|
|
tool.velocity = std::clamp(tool.velocity, 0.0, 400.0);
|
|
ImGui::EndTable();
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
if (false == tool.consist.empty() || (tool.vehicle != nullptr && tool.skin != nullptr))
|
|
{
|
|
auto const half{std::floor((ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f)};
|
|
auto const tall{std::round(ImGui::GetFrameHeight() * 1.3f)};
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.55f, 0.25f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.65f, 0.3f, 1.0f));
|
|
if (ImGui::Button(STR_C("Place"), ImVec2(half, tall)))
|
|
vehicle_place();
|
|
item_tooltip("A trainset on the track, placed where the track was clicked, or in the middle of the path");
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(STR_C("Place and drive"), ImVec2(-1.0f, tall)) && vehicle_place())
|
|
{
|
|
Global.editor_enter_vehicle = tool.placed;
|
|
tool.leave = true;
|
|
}
|
|
}
|
|
if (false == tool.status.empty())
|
|
ImGui::TextWrapped("%s", tool.status.c_str());
|
|
if (tool.trainsets.empty())
|
|
return;
|
|
ImGui::Separator();
|
|
ImGui::TextDisabled(STR_P("%d trainset to write on save:", "%d trainsets to write on save:", static_cast<long long>(tool.trainsets.size())), static_cast<int>(tool.trainsets.size()));
|
|
std::optional<std::size_t> drop;
|
|
for (std::size_t i = 0; i < tool.trainsets.size(); ++i)
|
|
{
|
|
ImGui::PushID(static_cast<int>(i));
|
|
if (ImGui::SmallButton("x"))
|
|
drop = i;
|
|
item_tooltip("Not written to the scenery; the vehicles stay until the scenery is loaded again");
|
|
ImGui::SameLine();
|
|
ImGui::Text("%s (%s)", tool.trainsets[i].head.c_str(), tool.trainsets[i].track.c_str());
|
|
ImGui::PopID();
|
|
}
|
|
if (drop)
|
|
tool.trainsets.erase(tool.trainsets.begin() + static_cast<std::ptrdiff_t>(*drop));
|
|
}
|
|
|
|
void editor_mode::draw_vehicle_marker() const
|
|
{
|
|
auto const &tool{m_vehicle};
|
|
if (false == tool.open || tool.track == nullptr)
|
|
return;
|
|
screen_projection const projection;
|
|
ImVec2 screen;
|
|
if (false == projection.project(tool.point, screen))
|
|
return;
|
|
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
|
auto const colour{IM_COL32(255, 220, 60, 235)};
|
|
drawlist->AddCircle(screen, 9.0f, colour, 16, 2.5f);
|
|
if (tool.track->m_editorremoved || tool.track->m_paths.empty())
|
|
return;
|
|
auto const heading{heading_at(*tool.track, tool.track->eType == tt_Switch ? path_under(*tool.track, tool.point) : 0, tool.point) * static_cast<double>(tool.facing)};
|
|
glm::dvec3 const tip{tool.point.x + heading.x * 8.0, tool.point.y, tool.point.z + heading.y * 8.0};
|
|
ImVec2 end;
|
|
if (false == projection.project(tip, end))
|
|
return;
|
|
drawlist->AddLine(screen, end, colour, 3.0f);
|
|
auto const dx{end.x - screen.x};
|
|
auto const dy{end.y - screen.y};
|
|
auto const size{std::sqrt(dx * dx + dy * dy)};
|
|
if (size < 1.0f)
|
|
return;
|
|
ImVec2 const unit{dx / size, dy / size};
|
|
ImVec2 const side{-unit.y, unit.x};
|
|
drawlist->AddTriangleFilled(ImVec2(end.x + unit.x * 10.0f, end.y + unit.y * 10.0f), ImVec2(end.x + side.x * 7.0f, end.y + side.y * 7.0f), ImVec2(end.x - side.x * 7.0f, end.y - side.y * 7.0f), colour);
|
|
}
|