mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 05:09:20 +02:00
feat(roads): level crossings, spawn and removal points for road vehicles, deleting points of a road
This commit is contained in:
@@ -96,6 +96,7 @@ set(SOURCES
|
||||
"utilities/Timer.cpp"
|
||||
"world/Track.cpp"
|
||||
"world/Road.cpp"
|
||||
"world/RoadPoint.cpp"
|
||||
"world/Traction.cpp"
|
||||
"world/TractionPower.cpp"
|
||||
"vehicle/Train.cpp"
|
||||
|
||||
@@ -500,9 +500,10 @@ class editor_mode : public application_mode
|
||||
{
|
||||
bool window{false}; // the road window is open, the left mouse button belongs to its tools
|
||||
bool lanes{true}; // lanes of the roads are drawn as trajectories
|
||||
int tool{0}; // 0: select, 1: build
|
||||
int tool{0}; // 0: select, 1: build, 2: place
|
||||
road_node *selected{nullptr};
|
||||
junction_node *junction{nullptr}; // selected junction
|
||||
roadpoint_node *marker{nullptr}; // selected level crossing or traffic point
|
||||
std::vector<glm::dvec3> points; // selected points where the pieces meet or end
|
||||
road_node::state settings; // layout shown in the window: of the selected piece, or of the pieces about to be built
|
||||
bool whole{true}; // layout changes go to every piece of the road the selected one belongs to
|
||||
@@ -522,6 +523,17 @@ class editor_mode : public application_mode
|
||||
bool follow{true}; // built roads are led over the ground between the clicked points
|
||||
float crossingspeed{30.f}; // speed limit on the junctions being made
|
||||
float heightstep{0.1f}; // how much the selected points are raised or lowered at a time
|
||||
// level crossings and traffic points
|
||||
int placekind{0}; // what the place tool puts down: 0: level crossing, 1: spawn point, 2: removal point
|
||||
roadpoint_node::state placing[3]; // what the next point of each kind is going to be like
|
||||
float stopmargin{5.f}; // distance from the outermost track of a new crossing to the places the vehicles stop at
|
||||
roadpoint_node::state target; // the point a click of the place tool would make at the moment
|
||||
bool hastarget{false};
|
||||
glm::dvec3 targetdirection{0.0}; // the way the traffic goes there
|
||||
std::string targetnote; // what's worth knowing about that point, or why there's none
|
||||
glm::dvec3 aimed{0.0}; // where the cursor was when the target was worked out, and for what kind of point
|
||||
int aimedkind{-1};
|
||||
char vehicles[4096]{}; // vehicles to add to the set of a spawn point, written the way they're put in a scenery
|
||||
road_plan plan; // what a click would make at the moment, worked out once a frame for display
|
||||
glm::dvec3 hover{0.0}; // point of a road or a junction under the cursor a road can be led out of
|
||||
bool hashover{false};
|
||||
@@ -550,6 +562,16 @@ class editor_mode : public application_mode
|
||||
void road_point_width(float const Width);
|
||||
void road_point_heights(std::vector<double> const &Heights);
|
||||
bool road_points_apply(std::vector<std::pair<road_node *, road_node::state>> const &Changes, std::vector<std::pair<junction_node *, junction_node::state>> const &Junctions);
|
||||
// takes the selected points out of their roads
|
||||
bool road_points_delete();
|
||||
// level crossings and traffic points
|
||||
void roadpoint_select(roadpoint_node *Point);
|
||||
// works out the point a click of the place tool would make. Fresh: even if the cursor is where it was the last time
|
||||
void roadpoint_aim(bool const Fresh);
|
||||
void roadpoint_place();
|
||||
void roadpoint_apply(roadpoint_node &Point, roadpoint_node::state const &State);
|
||||
// Point: the point the controls are for, nullptr for the one about to be made
|
||||
bool render_roadpoint_layout(roadpoint_node::state &State, roadpoint_node const *Point);
|
||||
// Fresh: the ground which wasn't looked up yet is gathered on the spot, instead of a bit of it each frame
|
||||
road_plan road_preview(bool const Fresh);
|
||||
// Firstgrade, Lastgrade: slopes the road has to start and end with, nullptr for none
|
||||
|
||||
@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/Road.h"
|
||||
#include "world/RoadPoint.h"
|
||||
#include "simulation/simulation.h"
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
@@ -133,6 +134,10 @@ double const kSnapRadius{4.0}; // an end of a road this close to the cursor is w
|
||||
double const kPointRadius{2.5}; // a point where pieces meet this close to the click is what gets selected, instead of the piece
|
||||
double const kPointRange{400.0}; // the points are drawn for the roads this close to the camera
|
||||
double const kSideMargin{1.0}; // how far past the edge of a road the cursor still counts as being on it
|
||||
double const kMarkerRadius{2.0}; // a level crossing or a traffic point this close to the click is what gets selected
|
||||
double const kRailReach{40.0}; // rails crossing the road this close to the cursor are taken for a single level crossing
|
||||
double const kLaneReach{2.5}; // a lane with its middle this close to the cursor is the one a traffic point is put on
|
||||
double const kFullTurn{6.283185307179586};
|
||||
double const kJoinClearance{15.0}; // a road isn't led into the side of another this close to the point it's led from
|
||||
double const kReach{3000.0}; // clicks landing further than this are taken for misses
|
||||
double const kLaneRange{600.0}; // lanes are drawn for the roads this close to the camera
|
||||
@@ -151,6 +156,19 @@ ImU32 const kSelectedColor{IM_COL32(40, 220, 255, 230)};
|
||||
ImU32 const kPreviewColor{IM_COL32(255, 210, 60, 255)};
|
||||
ImU32 const kRefusedColor{IM_COL32(240, 60, 60, 255)};
|
||||
ImU32 const kSnapColor{IM_COL32(255, 60, 255, 255)};
|
||||
ImU32 const kCrossingColor{IM_COL32(255, 220, 60, 230)};
|
||||
ImU32 const kSpawnColor{IM_COL32(90, 230, 120, 230)};
|
||||
ImU32 const kDespawnColor{IM_COL32(255, 110, 110, 230)};
|
||||
|
||||
roadpoint_node::kind_type point_kind(int const Index)
|
||||
{
|
||||
return Index == 1 ? roadpoint_node::kind_type::spawn : Index == 2 ? roadpoint_node::kind_type::despawn : roadpoint_node::kind_type::crossing;
|
||||
}
|
||||
|
||||
char const *point_label(roadpoint_node::kind_type const Kind)
|
||||
{
|
||||
return Kind == roadpoint_node::kind_type::spawn ? "spawn point" : Kind == roadpoint_node::kind_type::despawn ? "removal point" : "level crossing";
|
||||
}
|
||||
|
||||
// gathers new definitions for the pieces which meet or end at provided points.
|
||||
// Change: receives a definition, which end of it is at the point, and the number of the point
|
||||
@@ -209,6 +227,8 @@ void editor_mode::update_road_tool()
|
||||
tool.selected = nullptr;
|
||||
if (tool.junction != nullptr && tool.junction->m_editorremoved)
|
||||
tool.junction = nullptr;
|
||||
if (tool.marker != nullptr && tool.marker->m_editorremoved)
|
||||
tool.marker = nullptr;
|
||||
if ((tool.branchroad != nullptr && tool.branchroad->m_editorremoved) || (tool.branchjunction != nullptr && tool.branchjunction->m_editorremoved))
|
||||
{
|
||||
// what the road was to be led out of is gone
|
||||
@@ -220,6 +240,11 @@ void editor_mode::update_road_tool()
|
||||
tool.points.erase(std::remove_if(tool.points.begin(), tool.points.end(), [](glm::dvec3 const &Point) { return editor_road::ends_at(Point).empty(); }), tool.points.end());
|
||||
tool.plan = road_plan{};
|
||||
tool.hashover = false;
|
||||
if (false == tool.window || tool.tool != 2)
|
||||
{
|
||||
tool.hastarget = false;
|
||||
tool.aimedkind = -1;
|
||||
}
|
||||
if (false == tool.window)
|
||||
{
|
||||
tool.chain = false;
|
||||
@@ -230,6 +255,14 @@ void editor_mode::update_road_tool()
|
||||
if (glm::distance(tool.mouse, camera) > kReach)
|
||||
{
|
||||
tool.plan.error = "The cursor isn't over anything near enough";
|
||||
tool.hastarget = false;
|
||||
tool.aimedkind = -1;
|
||||
return;
|
||||
}
|
||||
if (tool.tool == 2)
|
||||
{
|
||||
// the point a click would make at the moment, to be drawn over the view
|
||||
roadpoint_aim(false);
|
||||
return;
|
||||
}
|
||||
if (tool.tool != 1)
|
||||
@@ -289,6 +322,280 @@ void editor_mode::junction_select(junction_node *Junction)
|
||||
copy_text(tool.sides[0], sizeof(tool.sides[0]), Junction->definition().side.material);
|
||||
}
|
||||
|
||||
void editor_mode::roadpoint_select(roadpoint_node *Point)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
tool.marker = Point;
|
||||
if (Point == nullptr)
|
||||
return;
|
||||
std::string reason;
|
||||
tool.status = "Selected: " + Point->name();
|
||||
if (false == editor_road::can_edit(*Point, &reason))
|
||||
tool.status += ". It can't be changed: " + reason;
|
||||
}
|
||||
|
||||
// works out the point a click of the place tool would make with the cursor where it is
|
||||
void editor_mode::roadpoint_aim(bool const Fresh)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
if (false == Fresh && tool.aimedkind == tool.placekind && glm::distance(tool.aimed, tool.mouse) < 0.1)
|
||||
{
|
||||
// the cursor didn't move, so neither did what it points at
|
||||
return;
|
||||
}
|
||||
tool.aimed = tool.mouse;
|
||||
tool.aimedkind = tool.placekind;
|
||||
auto const kind{point_kind(tool.placekind)};
|
||||
tool.target = tool.placing[tool.placekind];
|
||||
tool.target.kind = kind;
|
||||
tool.hastarget = false;
|
||||
tool.targetnote.clear();
|
||||
tool.targetdirection = glm::dvec3{0.0};
|
||||
if (kind == roadpoint_node::kind_type::crossing)
|
||||
{
|
||||
auto const *road{editor_road::nearest(tool.mouse, kSideMargin)};
|
||||
if (road == nullptr)
|
||||
{
|
||||
tool.targetnote = "Point at the place a road crosses the rails";
|
||||
return;
|
||||
}
|
||||
glm::dvec3 middle{0.0};
|
||||
double halfspan{0.0};
|
||||
if (editor_road::rails_across(*road, tool.mouse, kRailReach, middle, halfspan))
|
||||
{
|
||||
// the crossing goes halfway between the outermost tracks. the distance is rounded up, so it reads well in the scenery file
|
||||
tool.target.position = middle;
|
||||
tool.target.clearance = static_cast<float>(std::ceil((halfspan + tool.stopmargin) * 2.0) * 0.5);
|
||||
}
|
||||
else
|
||||
{
|
||||
tool.target.position = road->definition().point(editor_road::nearest_parameter(road->definition(), tool.mouse));
|
||||
tool.target.clearance = std::max(tool.stopmargin, 2.0f);
|
||||
tool.targetnote = "No rails cross the road here; a crossing put here won't close until some do";
|
||||
}
|
||||
tool.hastarget = true;
|
||||
return;
|
||||
}
|
||||
if (false == editor_road::nearest_lane(tool.mouse, kLaneReach, tool.target.position, tool.targetdirection))
|
||||
{
|
||||
tool.targetnote = "Point at a lane of a road";
|
||||
return;
|
||||
}
|
||||
tool.hastarget = true;
|
||||
}
|
||||
|
||||
// puts down the point the place tool is set for, as a step which can be taken back
|
||||
void editor_mode::roadpoint_place()
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
roadpoint_aim(true);
|
||||
if (false == tool.hastarget)
|
||||
{
|
||||
tool.status = tool.targetnote;
|
||||
return;
|
||||
}
|
||||
auto const created{editor_road::create(std::vector<roadpoint_node::state>{tool.target})};
|
||||
if (created.empty())
|
||||
{
|
||||
tool.status = "No scenery is loaded";
|
||||
return;
|
||||
}
|
||||
auto const *point{created.front()};
|
||||
auto const &state{point->definition()};
|
||||
editor_road::record record;
|
||||
record.points_created = created;
|
||||
push_road_snapshot(std::move(record));
|
||||
char text[64];
|
||||
switch (state.kind)
|
||||
{
|
||||
case roadpoint_node::kind_type::crossing:
|
||||
{
|
||||
std::snprintf(text, sizeof(text), "%.1f", state.clearance);
|
||||
tool.status = "Level crossing made: " + std::to_string(point->stops().size()) + " lanes stop " + text + " m from its middle, ";
|
||||
tool.status += std::to_string(point->rails()) + " tracks are watched for trains.";
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::spawn:
|
||||
{
|
||||
tool.status = (state.vehicles.empty() ? std::string{"Spawn point made, with no vehicles: nothing appears there until it's given some (Select, click the point)."} :
|
||||
"Spawn point made, with " + std::to_string(state.vehicles.size() * state.copies) + " vehicles. They appear while the simulation runs.");
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::despawn:
|
||||
{
|
||||
tool.status = "Removal point made, it takes vehicles from " + std::to_string(point->lanes().size()) + (point->lanes().size() == 1 ? " lane." : " lanes.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == tool.targetnote.empty())
|
||||
tool.status += " " + tool.targetnote + ".";
|
||||
}
|
||||
|
||||
// replaces the definition of a level crossing or a traffic point, as a step which can be taken back
|
||||
void editor_mode::roadpoint_apply(roadpoint_node &Point, roadpoint_node::state const &State)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
std::string reason;
|
||||
if (false == editor_road::can_edit(Point, &reason))
|
||||
{
|
||||
tool.status = "\"" + Point.name() + "\" can't be changed: " + reason;
|
||||
return;
|
||||
}
|
||||
editor_road::record record;
|
||||
record.points.emplace_back(&Point, Point.definition());
|
||||
editor_road::apply(std::vector<std::pair<roadpoint_node *, roadpoint_node::state>>{{&Point, State}});
|
||||
push_road_snapshot(std::move(record));
|
||||
tool.status = "Changed";
|
||||
}
|
||||
|
||||
// draws controls for a level crossing or a traffic point. returns: true if something was changed
|
||||
bool editor_mode::render_roadpoint_layout(roadpoint_node::state &State, roadpoint_node const *Point)
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
bool changed{false};
|
||||
// values typed for a point which is there already are taken when confirmed, so the point isn't made anew with each digit
|
||||
auto const flags{Point != nullptr ? ImGuiInputTextFlags_EnterReturnsTrue : ImGuiInputTextFlags_None};
|
||||
auto const number = [flags](char const *Label, float &Value, float const Step, char const *Format) {
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
return ImGui::InputFloat(Label, &Value, Step, Step * 4.0f, Format, flags);
|
||||
};
|
||||
ImVec4 const warning{1.0f, 0.6f, 0.3f, 1.0f};
|
||||
switch (State.kind)
|
||||
{
|
||||
case roadpoint_node::kind_type::crossing:
|
||||
{
|
||||
if (Point != nullptr)
|
||||
{
|
||||
ImGui::Text("%d lanes stop here, %d tracks are watched for trains", static_cast<int>(Point->stops().size()), static_cast<int>(Point->rails()));
|
||||
if (Point->rails() == 0)
|
||||
ImGui::TextColored(warning, "No rails pass through it, so it never closes");
|
||||
// the crossing is tied to what's there when it's made or changed, and when the roads change
|
||||
if (ImGui::Button("Look for the rails again"))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "For when the tracks were laid or changed after the crossing was made.");
|
||||
if (number("Stop this far from the middle [m]", State.clearance, 0.5f, "%.1f"))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "Where the vehicles stop, measured along the road from the middle of the crossing.\n"
|
||||
"The tracks passing within that distance from the middle are the ones the crossing is closed for.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (number("Stop this far from the outermost track [m]", tool.stopmargin, 0.5f, "%.1f"))
|
||||
tool.stopmargin = std::clamp(tool.stopmargin, 2.0f, 50.0f);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "Where the vehicles stop, measured along the road from the axis of the first track they'd get to.");
|
||||
}
|
||||
if (number("Closes with a train this far away [m]", State.warning, 50.0f, "%.0f"))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "Measured along the tracks, both ways, with both ways of each switch counted.\n"
|
||||
"A rail vehicle within that distance which gets nearer closes the crossing, and so does one right at it.\n"
|
||||
"It opens a few seconds after the last one is gone.");
|
||||
if (ImGui::Checkbox("Paint the stop lines", &State.stoplines))
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::spawn:
|
||||
{
|
||||
if (Point != nullptr)
|
||||
{
|
||||
if (Point->lanes().empty())
|
||||
ImGui::TextColored(warning, "It isn't on a lane, so nothing appears here");
|
||||
else if (Point->vehicles() == 0)
|
||||
ImGui::TextDisabled("The vehicles are made when the simulation runs");
|
||||
else
|
||||
ImGui::Text("%d vehicles made, %d of them wait for their turn", static_cast<int>(Point->vehicles()), static_cast<int>(Point->waiting()));
|
||||
}
|
||||
if (number("A vehicle every [s]", State.interval, 1.0f, "%.0f"))
|
||||
changed = true;
|
||||
if (number("Uneven by [0-1]", State.variation, 0.1f, "%.2f"))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "Part of the time between vehicles which is drawn at random each time.\n"
|
||||
"0: they come like clockwork, 1: anything from one right after another to twice the time.");
|
||||
if (number("Speed [km/h]", State.velocity, 5.0f, "%.0f"))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "The speed the vehicles appear with. From there on the drivers go by the speed limits of the lanes.");
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
if (ImGui::InputInt("Copies of each vehicle", &State.copies, 1, 1, flags))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "How many of each vehicle of the set are made. A vehicle can't be in two places at once,\n"
|
||||
"so this is how many of a kind can be on the road together. All of them are loaded along with the scenery,\n"
|
||||
"and a vehicle taken by a removal point goes back to wait for its next turn.");
|
||||
// the set
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Vehicles of the set: %d", static_cast<int>(State.vehicles.size()));
|
||||
int removed{-1};
|
||||
for (std::size_t index = 0; index < State.vehicles.size(); ++index)
|
||||
{
|
||||
auto const &vehicle{State.vehicles[index]};
|
||||
ImGui::PushID(static_cast<int>(index));
|
||||
if (ImGui::SmallButton("x"))
|
||||
removed = static_cast<int>(index);
|
||||
ImGui::SameLine();
|
||||
if (vehicle.load > 0)
|
||||
ImGui::Text("%s / %s, %s, %d %s", vehicle.folder.c_str(), vehicle.type.c_str(), vehicle.skin.c_str(), vehicle.load, vehicle.loadtype.c_str());
|
||||
else
|
||||
ImGui::Text("%s / %s, %s", vehicle.folder.c_str(), vehicle.type.c_str(), vehicle.skin.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (removed >= 0)
|
||||
{
|
||||
State.vehicles.erase(State.vehicles.begin() + removed);
|
||||
changed = true;
|
||||
}
|
||||
if (State.vehicles.empty())
|
||||
ImGui::TextColored(warning, "No vehicles, so nothing appears here");
|
||||
ImGui::InputTextMultiline("##vehicles", tool.vehicles, sizeof(tool.vehicles), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 5.0f));
|
||||
if (ImGui::Button("Add to the set"))
|
||||
{
|
||||
auto const parsed{roadpoint_node::parse_vehicles(tool.vehicles)};
|
||||
if (parsed.empty())
|
||||
{
|
||||
tool.status = "No vehicle found in the text";
|
||||
}
|
||||
else
|
||||
{
|
||||
State.vehicles.insert(State.vehicles.end(), parsed.begin(), parsed.end());
|
||||
tool.vehicles[0] = 0;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Empty the set") && false == State.vehicles.empty())
|
||||
{
|
||||
State.vehicles.clear();
|
||||
changed = true;
|
||||
}
|
||||
ImGui::TextDisabled("Paste the vehicles the way they're put in a scenery:");
|
||||
ImGui::TextDisabled("node -1 0 none dynamic <folder> <skin> <type> <path>");
|
||||
ImGui::TextDisabled(" <offset> <driver> <velocity> <load> enddynamic");
|
||||
ImGui::TextDisabled("or as <folder> <skin> <type>, one vehicle a line.");
|
||||
ImGui::TextDisabled("The path, the offset, the driver and the velocity don't matter.");
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::despawn:
|
||||
{
|
||||
if (Point != nullptr)
|
||||
ImGui::Text("Takes vehicles from %d %s", static_cast<int>(Point->lanes().size()), Point->lanes().size() == 1 ? "lane" : "lanes");
|
||||
if (number("Takes vehicles this close [m]", State.radius, 0.5f, "%.1f"))
|
||||
changed = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "Road vehicles on the lanes passing within this distance from the point are taken off the road when they get here,\n"
|
||||
"except for the one the user drives. Vehicles which came from a spawn point go back to wait for their next turn.\n"
|
||||
"The distance it starts with takes in a single lane.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (changed && Point != nullptr)
|
||||
State.normalize();
|
||||
return changed;
|
||||
}
|
||||
|
||||
// replaces the definition of a junction, as a step which can be taken back
|
||||
void editor_mode::junction_apply(junction_node &Junction, junction_node::state const &State)
|
||||
{
|
||||
@@ -636,8 +943,23 @@ void editor_mode::road_click()
|
||||
return;
|
||||
}
|
||||
tool.mouse = ground;
|
||||
if (tool.tool == 2)
|
||||
{
|
||||
roadpoint_place();
|
||||
return;
|
||||
}
|
||||
if (tool.tool == 0)
|
||||
{
|
||||
if (auto *marker{editor_road::nearest_point(ground, kMarkerRadius)}; marker != nullptr)
|
||||
{
|
||||
// a level crossing or a traffic point
|
||||
tool.points.clear();
|
||||
tool.junction = nullptr;
|
||||
road_select(nullptr);
|
||||
roadpoint_select(marker);
|
||||
return;
|
||||
}
|
||||
tool.marker = nullptr;
|
||||
glm::dvec3 joint;
|
||||
if (editor_road::nearest_joint(ground, kPointRadius, joint))
|
||||
{
|
||||
@@ -883,13 +1205,29 @@ void editor_mode::road_apply()
|
||||
bool editor_mode::road_delete()
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
if (false == tool.window || (tool.selected == nullptr && tool.junction == nullptr))
|
||||
{
|
||||
// with points selected the key isn't meant for whatever else is selected in the editor
|
||||
return tool.window && false == tool.points.empty();
|
||||
}
|
||||
if (false == tool.window)
|
||||
return false;
|
||||
std::string reason;
|
||||
editor_road::record record;
|
||||
if (tool.marker != nullptr)
|
||||
{
|
||||
auto *point{tool.marker};
|
||||
if (false == editor_road::can_edit(*point, &reason))
|
||||
{
|
||||
tool.status = "Can't delete: " + reason;
|
||||
return true;
|
||||
}
|
||||
editor_road::remove(std::vector<roadpoint_node *>{point});
|
||||
record.points_removed.emplace_back(point);
|
||||
push_road_snapshot(std::move(record));
|
||||
tool.marker = nullptr;
|
||||
tool.status = "\"" + point->name() + "\" deleted, Ctrl+Z brings it back";
|
||||
return true;
|
||||
}
|
||||
if (false == tool.points.empty())
|
||||
return road_points_delete();
|
||||
if (tool.selected == nullptr && tool.junction == nullptr)
|
||||
return false;
|
||||
if (tool.junction != nullptr)
|
||||
{
|
||||
auto *junction{tool.junction};
|
||||
@@ -919,6 +1257,30 @@ bool editor_mode::road_delete()
|
||||
return true;
|
||||
}
|
||||
|
||||
// takes the selected points out of their roads
|
||||
bool editor_mode::road_points_delete()
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
editor_road::record record;
|
||||
std::string notes;
|
||||
int taken{0};
|
||||
for (auto const &point : tool.points)
|
||||
{
|
||||
std::string error;
|
||||
if (editor_road::dissolve(point, record, error))
|
||||
++taken;
|
||||
else if (notes.find(error) == std::string::npos)
|
||||
notes += " A point was left: " + error + ".";
|
||||
}
|
||||
push_road_snapshot(std::move(record));
|
||||
// the points which stayed are still selected, so it's clear which ones these are
|
||||
tool.points.erase(std::remove_if(tool.points.begin(), tool.points.end(), [](glm::dvec3 const &Point) { return editor_road::ends_at(Point).empty(); }), tool.points.end());
|
||||
tool.status = (taken == 0 ? std::string{"Nothing deleted."} : taken == 1 ? std::string{"Point deleted, Ctrl+Z brings it back."} :
|
||||
std::to_string(taken) + " points deleted, Ctrl+Z brings them back.");
|
||||
tool.status += notes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_mode::road_split()
|
||||
{
|
||||
auto &tool{m_roadtool};
|
||||
@@ -1049,8 +1411,14 @@ void editor_mode::push_road_snapshot(editor_road::record Record)
|
||||
node = Record.junctions.front().first;
|
||||
else if (false == Record.junctions_created.empty())
|
||||
node = Record.junctions_created.front();
|
||||
else
|
||||
else if (false == Record.junctions_removed.empty())
|
||||
node = Record.junctions_removed.front();
|
||||
else if (false == Record.points.empty())
|
||||
node = Record.points.front().first;
|
||||
else if (false == Record.points_created.empty())
|
||||
node = Record.points_created.front();
|
||||
else
|
||||
node = Record.points_removed.front();
|
||||
EditorSnapshot snap;
|
||||
snap.action = EditorSnapshot::Action::RoadEdit;
|
||||
snap.node_name = node->name();
|
||||
@@ -1072,6 +1440,8 @@ void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vec
|
||||
entry.second = entry.first->definition();
|
||||
for (auto &entry : current.roads.junctions)
|
||||
entry.second = entry.first->definition();
|
||||
for (auto &entry : current.roads.points)
|
||||
entry.second = entry.first->definition();
|
||||
Opposite.push_back(std::move(current));
|
||||
|
||||
// things are taken out before the rest is changed and put in after it, so nothing gets joined with what's on its way out
|
||||
@@ -1079,23 +1449,28 @@ void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vec
|
||||
{
|
||||
editor_road::remove(record.roads_created);
|
||||
editor_road::remove(record.junctions_created);
|
||||
editor_road::remove(record.points_created);
|
||||
}
|
||||
else
|
||||
{
|
||||
editor_road::remove(record.roads_removed);
|
||||
editor_road::remove(record.junctions_removed);
|
||||
editor_road::remove(record.points_removed);
|
||||
}
|
||||
editor_road::apply(record.roads);
|
||||
editor_road::apply(record.junctions);
|
||||
editor_road::apply(record.points);
|
||||
if (Undo)
|
||||
{
|
||||
editor_road::revive(record.roads_removed);
|
||||
editor_road::revive(record.junctions_removed);
|
||||
editor_road::revive(record.points_removed);
|
||||
}
|
||||
else
|
||||
{
|
||||
editor_road::revive(record.roads_created);
|
||||
editor_road::revive(record.junctions_created);
|
||||
editor_road::revive(record.points_created);
|
||||
}
|
||||
|
||||
auto &tool{m_roadtool};
|
||||
@@ -1104,6 +1479,9 @@ void editor_mode::restore_road_snapshot(EditorSnapshot const &Snapshot, std::vec
|
||||
road_select(tool.selected->m_editorremoved ? nullptr : tool.selected);
|
||||
if (tool.junction != nullptr)
|
||||
junction_select(tool.junction->m_editorremoved ? nullptr : tool.junction);
|
||||
if (tool.marker != nullptr && tool.marker->m_editorremoved)
|
||||
tool.marker = nullptr;
|
||||
tool.aimedkind = -1;
|
||||
}
|
||||
|
||||
// a field for the name of a material, with a list of the ones in the texture folder to pick from. returns: true if the material was changed
|
||||
@@ -1333,6 +1711,10 @@ void editor_mode::render_road_window()
|
||||
ImGui::RadioButton("Select", &tool.tool, 0);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Build", &tool.tool, 1);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Place", &tool.tool, 2);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "Level crossings, and the points where vehicles appear on the roads or are taken off them");
|
||||
if (tool.tool != previoustool)
|
||||
{
|
||||
road_cancel();
|
||||
@@ -1340,6 +1722,7 @@ void editor_mode::render_road_window()
|
||||
{
|
||||
tool.junction = nullptr;
|
||||
tool.points.clear();
|
||||
tool.marker = nullptr;
|
||||
// the fields for the names of materials show the layout for the roads to be built again
|
||||
road_select(nullptr);
|
||||
}
|
||||
@@ -1379,6 +1762,45 @@ void editor_mode::render_road_window()
|
||||
// what's set here is what the next pieces get
|
||||
render_road_layout(tool.settings);
|
||||
}
|
||||
else if (tool.tool == 2)
|
||||
{
|
||||
ImGui::RadioButton("Level crossing", &tool.placekind, 0);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Spawn point", &tool.placekind, 1);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Removal point", &tool.placekind, 2);
|
||||
tool.placekind = std::clamp(tool.placekind, 0, 2);
|
||||
switch (tool.placekind)
|
||||
{
|
||||
case 0:
|
||||
ImGui::TextDisabled("LMB on a road where the rails cross it. The vehicles stop ahead");
|
||||
ImGui::TextDisabled("of the rails for as long as a train is coming.");
|
||||
break;
|
||||
case 1:
|
||||
ImGui::TextDisabled("LMB on a lane. The vehicles of the set appear there one by one");
|
||||
ImGui::TextDisabled("and drive on the way the lane goes.");
|
||||
break;
|
||||
default:
|
||||
ImGui::TextDisabled("LMB on a lane. The vehicles which get there are taken off the road;");
|
||||
ImGui::TextDisabled("put one where the road leaves the scenery.");
|
||||
break;
|
||||
}
|
||||
ImGui::Separator();
|
||||
// what's set here is what the next points get
|
||||
auto &placing{tool.placing[tool.placekind]};
|
||||
placing.kind = point_kind(tool.placekind);
|
||||
render_roadpoint_layout(placing, nullptr);
|
||||
}
|
||||
else if (tool.marker != nullptr)
|
||||
{
|
||||
auto &point{*tool.marker};
|
||||
auto state{point.definition()};
|
||||
ImGui::Text("%s, %s", point.name().c_str(), point_label(state.kind));
|
||||
if (render_roadpoint_layout(state, &point))
|
||||
roadpoint_apply(point, state);
|
||||
if (ImGui::Button("Delete (Del)"))
|
||||
road_delete();
|
||||
}
|
||||
else if (false == tool.points.empty())
|
||||
{
|
||||
// the road at the selected points
|
||||
@@ -1428,6 +1850,12 @@ void editor_mode::render_road_window()
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Puts each point %.2f m over the ground under it; the height above the ground is set in the Build tool.", tool.offset);
|
||||
}
|
||||
if (ImGui::Button(tool.points.size() == 1 ? "Delete the point (Del)" : "Delete the points (Del)"))
|
||||
road_points_delete();
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", "Takes the point out of the road: the pieces on its two sides become one piece,\n"
|
||||
"led from the far end of one to the far end of the other the way the road goes there.\n"
|
||||
"A point the road ends at takes the last piece of the road with it.");
|
||||
}
|
||||
else if (tool.junction != nullptr)
|
||||
{
|
||||
@@ -1456,7 +1884,8 @@ void editor_mode::render_road_window()
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextDisabled("LMB: select a road piece, a junction, or a point where pieces meet");
|
||||
ImGui::TextDisabled("LMB: select a road piece, a junction, a point where pieces meet,");
|
||||
ImGui::TextDisabled("a level crossing or a traffic point");
|
||||
}
|
||||
if (false == tool.status.empty())
|
||||
{
|
||||
@@ -1587,6 +2016,77 @@ void editor_mode::draw_road_overlay() const
|
||||
drawpoint(arm.position, Color, 5.0f, true);
|
||||
};
|
||||
|
||||
// a circle lying on the ground
|
||||
auto const drawring = [&](glm::dvec3 const &Centre, double const Radius, ImU32 const Color) {
|
||||
int const pieces{24};
|
||||
glm::dvec3 previous{Centre + glm::dvec3{Radius, 0.0, 0.0}};
|
||||
for (int i = 1; i <= pieces; ++i)
|
||||
{
|
||||
auto const angle{kFullTurn * i / pieces};
|
||||
glm::dvec3 const next{Centre + glm::dvec3{Radius * std::cos(angle), 0.0, Radius * std::sin(angle)}};
|
||||
projection.line(drawlist, previous, next, Color, 2.0f);
|
||||
previous = next;
|
||||
}
|
||||
};
|
||||
auto const drawlabel = [&](glm::dvec3 const &Point, ImU32 const Color, char const *Text) {
|
||||
ImVec2 screen;
|
||||
if (false == projection.room(drawlist) || false == projection.project(Point, screen) || projection.outside(screen, screen))
|
||||
return;
|
||||
drawlist->AddText(ImVec2(screen.x + 14.0f, screen.y - 8.0f), Color, Text);
|
||||
};
|
||||
// a level crossing or a traffic point. Point: the node it's drawn for, nullptr for one which isn't made yet
|
||||
auto const drawmarker = [&](roadpoint_node::state const &State, roadpoint_node const *Point, ImU32 const Color) {
|
||||
drawpoint(State.position, Color, 6.0f, true);
|
||||
auto const labelled{glm::distance(State.position, camera) < 200.0};
|
||||
switch (State.kind)
|
||||
{
|
||||
case roadpoint_node::kind_type::crossing:
|
||||
{
|
||||
// the tracks within the circle are the ones it's closed for, the lines are where the vehicles stop
|
||||
drawring(State.position, State.clearance, Color);
|
||||
auto const closed{Point != nullptr && Point->closed()};
|
||||
if (Point != nullptr)
|
||||
{
|
||||
for (auto const &stop : Point->stops())
|
||||
{
|
||||
glm::dvec3 across{stop.direction.z, 0.0, -stop.direction.x};
|
||||
if (glm::length(across) < 1e-6)
|
||||
continue;
|
||||
across = glm::normalize(across) * (0.5 * stop.width);
|
||||
projection.line(drawlist, stop.position - across, stop.position + across, closed ? kRefusedColor : Color, 4.0f);
|
||||
}
|
||||
}
|
||||
if (labelled)
|
||||
drawlabel(State.position, Color, closed ? "crossing, closed" : "crossing");
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::spawn:
|
||||
{
|
||||
drawpoint(State.position, Color, 11.0f, false);
|
||||
if (labelled)
|
||||
drawlabel(State.position, Color, "spawn");
|
||||
break;
|
||||
}
|
||||
case roadpoint_node::kind_type::despawn:
|
||||
{
|
||||
drawring(State.position, State.radius, Color);
|
||||
if (labelled)
|
||||
drawlabel(State.position, Color, "removal");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
for (auto const *point : simulation::Roadpoints.sequence())
|
||||
{
|
||||
if (point == nullptr || point->m_editorremoved || glm::distance(point->location(), camera) > kPointRange)
|
||||
continue;
|
||||
auto const kind{point->definition().kind};
|
||||
drawmarker(point->definition(), point,
|
||||
point == tool.marker ? kSelectedColor : kind == roadpoint_node::kind_type::spawn ? kSpawnColor : kind == roadpoint_node::kind_type::despawn ? kDespawnColor : kCrossingColor);
|
||||
if (point == tool.marker)
|
||||
drawpoint(point->definition().position, kSelectedColor, 15.0f, false);
|
||||
}
|
||||
|
||||
if (tool.selected != nullptr && false == tool.selected->m_editorremoved)
|
||||
{
|
||||
auto const &state{tool.selected->definition()};
|
||||
@@ -1649,6 +2149,30 @@ void editor_mode::draw_road_overlay() const
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tool.tool == 2)
|
||||
{
|
||||
if (tool.hastarget)
|
||||
{
|
||||
drawmarker(tool.target, nullptr, kPreviewColor);
|
||||
if (glm::length(tool.targetdirection) > 1e-6)
|
||||
{
|
||||
// the way the traffic goes on the lane
|
||||
auto const direction{glm::normalize(tool.targetdirection)};
|
||||
glm::dvec3 const across{direction.z, 0.0, -direction.x};
|
||||
auto const tip{tool.target.position + direction * 5.0};
|
||||
projection.line(drawlist, tool.target.position, tip, kPreviewColor, 2.0f);
|
||||
projection.line(drawlist, tip, tip - direction * 1.2 + across * 0.8, kPreviewColor, 2.0f);
|
||||
projection.line(drawlist, tip, tip - direction * 1.2 - across * 0.8, kPreviewColor, 2.0f);
|
||||
}
|
||||
hint = std::string{"LMB: put a "} + point_label(tool.target.kind) + " here";
|
||||
if (false == tool.targetnote.empty())
|
||||
hint += " " + tool.targetnote;
|
||||
}
|
||||
else
|
||||
{
|
||||
hint = (tool.targetnote.empty() ? std::string{"LMB: put a "} + point_label(point_kind(tool.placekind)) + " on a road" : tool.targetnote);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// points where the pieces meet or end, to pick from
|
||||
@@ -1664,10 +2188,11 @@ void editor_mode::draw_road_overlay() const
|
||||
drawpoint(point, kSelectedColor, 6.0f, true);
|
||||
drawpoint(point, kSelectedColor, 11.0f, false);
|
||||
}
|
||||
hint = false == tool.points.empty() ? "LMB: select a point Shift+LMB: add a point or take it out Ctrl+Z: undo" :
|
||||
tool.selected != nullptr ? "LMB: select a piece, a junction or a point (white dot) K: split at the cursor Del: delete Ctrl+Z: undo" :
|
||||
tool.junction != nullptr ? "LMB: select a piece, a junction or a point (white dot) Del: delete Ctrl+Z: undo" :
|
||||
"LMB: select a piece, a junction or a point (white dot)";
|
||||
hint = tool.marker != nullptr ? "LMB: select a piece, a junction, a point or a marker Del: delete Ctrl+Z: undo" :
|
||||
false == tool.points.empty() ? "LMB: select a point Shift+LMB: add a point or take it out Del: delete the points Ctrl+Z: undo" :
|
||||
tool.selected != nullptr ? "LMB: select a piece, a junction or a point (white dot) K: split at the cursor Del: delete Ctrl+Z: undo" :
|
||||
tool.junction != nullptr ? "LMB: select a piece, a junction or a point (white dot) Del: delete Ctrl+Z: undo" :
|
||||
"LMB: select a piece, a junction, a point (white dot) or a marker";
|
||||
}
|
||||
// shown above the place the track tools put their hints at
|
||||
ImGuiIO const &io = ImGui::GetIO();
|
||||
|
||||
@@ -317,6 +317,66 @@ void retire(std::vector<TTrack *> const &Tracks)
|
||||
editor_track::retire(*track);
|
||||
}
|
||||
|
||||
// value of the curve parameter for the point of a curve nearest to specified point, seen from above
|
||||
double nearest_on(std::array<glm::dvec3, 4> const &Points, glm::dvec3 const &Point)
|
||||
{
|
||||
auto const distance = [&](double const T) {
|
||||
auto const position{curve_point(Points, T)};
|
||||
return glm::distance(glm::dvec2{position.x, position.z}, glm::dvec2{Point.x, Point.z});
|
||||
};
|
||||
double best{0.0};
|
||||
for (int i = 1; i <= 64; ++i)
|
||||
if (distance(i / 64.0) < distance(best))
|
||||
best = i / 64.0;
|
||||
double step{1.0 / 64.0};
|
||||
for (int i = 0; i < 24; ++i)
|
||||
{
|
||||
step *= 0.5;
|
||||
if (best - step >= 0.0 && distance(best - step) < distance(best))
|
||||
best -= step;
|
||||
else if (best + step <= 1.0 && distance(best + step) < distance(best))
|
||||
best += step;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
double planar_distance(glm::dvec3 const &Left, glm::dvec3 const &Right)
|
||||
{
|
||||
return glm::distance(glm::dvec2{Left.x, Left.z}, glm::dvec2{Right.x, Right.z});
|
||||
}
|
||||
|
||||
// true if two lines cross when seen from above. Along, Otheralong: receive how far along each of them the crossing is, 0 to 1
|
||||
bool lines_cross(glm::dvec3 const &Start, glm::dvec3 const &End, glm::dvec3 const &Otherstart, glm::dvec3 const &Otherend, double &Along, double &Otheralong)
|
||||
{
|
||||
glm::dvec2 const line{End.x - Start.x, End.z - Start.z};
|
||||
glm::dvec2 const other{Otherend.x - Otherstart.x, Otherend.z - Otherstart.z};
|
||||
auto const denominator{line.x * other.y - line.y * other.x};
|
||||
if (std::abs(denominator) < 1e-12)
|
||||
return false;
|
||||
glm::dvec2 const offset{Otherstart.x - Start.x, Otherstart.z - Start.z};
|
||||
Along = (offset.x * other.y - offset.y * other.x) / denominator;
|
||||
Otheralong = (offset.x * line.y - offset.y * line.x) / denominator;
|
||||
return Along >= 0.0 && Along <= 1.0 && Otheralong >= 0.0 && Otheralong <= 1.0;
|
||||
}
|
||||
|
||||
// the same piece of a road laid out from its other end: what it looks like and where its lanes lead stays as it was
|
||||
road_node::state flipped(road_node::state const &State)
|
||||
{
|
||||
auto result{State};
|
||||
std::swap(result.axis.points[segment_data::point::start], result.axis.points[segment_data::point::end]);
|
||||
std::swap(result.axis.points[segment_data::point::control1], result.axis.points[segment_data::point::control2]);
|
||||
result.axis.rolls = {-State.axis.rolls[1], -State.axis.rolls[0]};
|
||||
std::swap(result.forward, result.backward);
|
||||
// the lanes are listed left to right for someone facing along the axis, who's now looking the other way
|
||||
std::reverse(result.lanes.begin(), result.lanes.end());
|
||||
std::reverse(result.changes.begin(), result.changes.end());
|
||||
for (auto &change : result.changes)
|
||||
change = ((change & road_node::change_toright) != 0 ? road_node::change_toleft : 0) | ((change & road_node::change_toleft) != 0 ? road_node::change_toright : 0);
|
||||
std::swap(result.sides[0], result.sides[1]);
|
||||
std::swap(result.taper[0], result.taper[1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
// smallest radius of the bend made by a bezier curve, seen from above; 0 if the curve is practically straight
|
||||
double tightest_radius(segment_data const &Path)
|
||||
{
|
||||
@@ -468,6 +528,8 @@ void editor_road::refresh(std::vector<road_node *> const &Roads, std::vector<jun
|
||||
// this takes down the paths made for the joints, so they can be made again once the change is done
|
||||
std::vector<road_node *> affected;
|
||||
std::vector<junction_node *> crossings;
|
||||
// level crossings and traffic points hold on to the lanes, which are about to be replaced
|
||||
simulation::Roadpoints.unbind();
|
||||
auto const gather = [&]() {
|
||||
std::vector<road_node *> roads{affected};
|
||||
std::vector<junction_node *> junctions{crossings};
|
||||
@@ -541,6 +603,7 @@ void editor_road::refresh(std::vector<road_node *> const &Roads, std::vector<jun
|
||||
for (auto *road : affected)
|
||||
if (false == road->m_editorremoved)
|
||||
road->close_ends();
|
||||
simulation::Roadpoints.bind();
|
||||
}
|
||||
|
||||
std::vector<road_node *> editor_road::create(std::vector<road_node::state> const &States)
|
||||
@@ -702,6 +765,288 @@ void editor_road::revive(std::vector<junction_node *> const &Junctions)
|
||||
});
|
||||
}
|
||||
|
||||
bool editor_road::can_edit(roadpoint_node const &Point, std::string *Reason)
|
||||
{
|
||||
std::string reason;
|
||||
auto editable{true};
|
||||
if (simulation::Region == nullptr)
|
||||
{
|
||||
reason = "No scenery is loaded";
|
||||
editable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
editable = scene::Layers.editable(&Point, &reason);
|
||||
}
|
||||
if (false == editable && Reason != nullptr)
|
||||
*Reason = reason;
|
||||
return editable;
|
||||
}
|
||||
|
||||
std::vector<roadpoint_node *> editor_road::create(std::vector<roadpoint_node::state> const &States)
|
||||
{
|
||||
static int counter{0};
|
||||
std::vector<roadpoint_node *> created;
|
||||
if (simulation::Region == nullptr)
|
||||
return created;
|
||||
for (auto const &state : States)
|
||||
{
|
||||
scene::node_data data;
|
||||
data.type = roadpoint_node::keyword(state.kind);
|
||||
data.range_max = -1.0;
|
||||
data.layer = scene::Layers.active(); // null_handle unless the scenery was opened for editing
|
||||
// the vehicles of a spawn point are named after it, so each point needs a name of its own
|
||||
do
|
||||
{
|
||||
data.name = data.type + "_" + std::to_string(++counter);
|
||||
} while (simulation::Roadpoints.find(data.name) != nullptr);
|
||||
auto *point{new roadpoint_node(data)};
|
||||
point->define(state);
|
||||
point->mark_dirty();
|
||||
simulation::Roadpoints.insert(point);
|
||||
scene::Layers.count(point->layer(), scene::layer_item::track);
|
||||
point->bind();
|
||||
point->show();
|
||||
created.emplace_back(point);
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
void editor_road::apply(std::vector<std::pair<roadpoint_node *, roadpoint_node::state>> const &Changes)
|
||||
{
|
||||
for (auto const &change : Changes)
|
||||
{
|
||||
auto *point{change.first};
|
||||
if (point == nullptr)
|
||||
continue;
|
||||
point->hide();
|
||||
point->unbind();
|
||||
point->define(change.second);
|
||||
point->mark_dirty();
|
||||
point->bind();
|
||||
point->show();
|
||||
}
|
||||
}
|
||||
|
||||
void editor_road::remove(std::vector<roadpoint_node *> const &Points)
|
||||
{
|
||||
for (auto *point : Points)
|
||||
{
|
||||
if (point == nullptr || point->m_editorremoved)
|
||||
continue;
|
||||
point->hide();
|
||||
// a crossing which goes lets the vehicles it was holding drive on
|
||||
point->unbind();
|
||||
point->m_editorremoved = true;
|
||||
scene::Layers.count(point->layer(), scene::layer_item::track, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void editor_road::revive(std::vector<roadpoint_node *> const &Points)
|
||||
{
|
||||
for (auto *point : Points)
|
||||
{
|
||||
if (point == nullptr || false == point->m_editorremoved)
|
||||
continue;
|
||||
point->m_editorremoved = false;
|
||||
point->mark_dirty();
|
||||
scene::Layers.count(point->layer(), scene::layer_item::track);
|
||||
point->bind();
|
||||
point->show();
|
||||
}
|
||||
}
|
||||
|
||||
roadpoint_node *editor_road::nearest_point(glm::dvec3 const &Point, double const Radius)
|
||||
{
|
||||
roadpoint_node *result{nullptr};
|
||||
auto best{Radius};
|
||||
for (auto *point : simulation::Roadpoints.sequence())
|
||||
{
|
||||
if (point == nullptr || point->m_editorremoved)
|
||||
continue;
|
||||
auto const &position{point->definition().position};
|
||||
auto const distance{planar_distance(position, Point)};
|
||||
if (distance > best || std::abs(position.y - Point.y) > 3.0)
|
||||
continue;
|
||||
best = distance;
|
||||
result = point;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool editor_road::nearest_lane(glm::dvec3 const &Point, double const Reach, glm::dvec3 &Position, glm::dvec3 &Direction)
|
||||
{
|
||||
auto best{Reach};
|
||||
auto found{false};
|
||||
for (auto const *road : simulation::Roads.sequence())
|
||||
{
|
||||
if (road == nullptr || road->m_editorremoved)
|
||||
continue;
|
||||
auto const &state{road->definition()};
|
||||
auto const axis{curve_points(state.axis)};
|
||||
// the axis is no longer than the lines joining its points, which is good enough to tell the road is nowhere near
|
||||
if (glm::distance(road->location(), Point) >
|
||||
0.5 * (glm::distance(axis[0], axis[1]) + glm::distance(axis[1], axis[2]) + glm::distance(axis[2], axis[3])) + 0.5 * state.width() * std::max(state.taper[0], state.taper[1]) + Reach + 10.0)
|
||||
continue;
|
||||
for (auto const *lane : road->tracks())
|
||||
{
|
||||
if (lane == nullptr || lane->m_paths.empty())
|
||||
continue;
|
||||
auto const points{curve_points(lane->m_paths.front())};
|
||||
auto const t{nearest_on(points, Point)};
|
||||
auto const position{curve_point(points, t)};
|
||||
auto const distance{planar_distance(position, Point)};
|
||||
// lanes passing over or under the point don't count
|
||||
if (distance > best || std::abs(position.y - Point.y) > 3.0)
|
||||
continue;
|
||||
best = distance;
|
||||
Position = position;
|
||||
// the lanes are laid out the way the traffic goes
|
||||
Direction = curve_point(points, std::min(1.0, t + 0.01)) - curve_point(points, std::max(0.0, t - 0.01));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool editor_road::rails_across(road_node const &Road, glm::dvec3 const &Near, double const Reach, glm::dvec3 &Middle, double &Halfspan)
|
||||
{
|
||||
int const pieces{32}; // lines the axis is taken for
|
||||
int const railpieces{16}; // and each of the rails
|
||||
auto const axis{curve_points(Road.definition().axis)};
|
||||
std::vector<glm::dvec3> line;
|
||||
for (int i = 0; i <= pieces; ++i)
|
||||
line.emplace_back(curve_point(axis, static_cast<double>(i) / pieces));
|
||||
// values of the curve parameter of the axis the rails cross it at
|
||||
auto first{2.0};
|
||||
auto last{-1.0};
|
||||
for (auto const *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || (track->iCategoryFlag & 1) == 0)
|
||||
continue;
|
||||
for (auto const &path : track->m_paths)
|
||||
{
|
||||
auto const rail{curve_points(path)};
|
||||
// the path is no longer than the lines joining its points, which is good enough to tell it's nowhere near
|
||||
if (planar_distance(rail[0], Near) > glm::distance(rail[0], rail[1]) + glm::distance(rail[1], rail[2]) + glm::distance(rail[2], rail[3]) + Reach)
|
||||
continue;
|
||||
auto previous{rail[0]};
|
||||
for (int j = 1; j <= railpieces; ++j)
|
||||
{
|
||||
auto const next{curve_point(rail, static_cast<double>(j) / railpieces)};
|
||||
for (int i = 0; i < pieces; ++i)
|
||||
{
|
||||
double along{0.0};
|
||||
double railalong{0.0};
|
||||
if (false == lines_cross(line[i], line[i + 1], previous, next, along, railalong))
|
||||
continue;
|
||||
auto const point{glm::mix(line[i], line[i + 1], along)};
|
||||
// rails passing over or under the road don't make a level crossing
|
||||
if (planar_distance(point, Near) > Reach || std::abs(glm::mix(previous.y, next.y, railalong) - point.y) > 3.0)
|
||||
continue;
|
||||
auto const t{(i + along) / pieces};
|
||||
first = std::min(first, t);
|
||||
last = std::max(last, t);
|
||||
}
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (last < first)
|
||||
return false;
|
||||
Middle = curve_point(axis, 0.5 * (first + last));
|
||||
Halfspan = std::max(planar_distance(curve_point(axis, first), Middle), planar_distance(curve_point(axis, last), Middle));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_road::dissolve(glm::dvec3 const &Joint, record &Record, std::string &Error)
|
||||
{
|
||||
if (false == junctions_at(Joint).empty())
|
||||
{
|
||||
Error = "the point is where a road meets a junction. Delete the junction or the road instead";
|
||||
return false;
|
||||
}
|
||||
auto const ends{ends_at(Joint)};
|
||||
if (ends.empty())
|
||||
{
|
||||
Error = "there's no point of a road there";
|
||||
return false;
|
||||
}
|
||||
if (ends.size() > 2 || (ends.size() == 2 && ends[0].road == ends[1].road))
|
||||
{
|
||||
Error = "more than two ends of roads meet at the point";
|
||||
return false;
|
||||
}
|
||||
std::string reason;
|
||||
for (auto const &end : ends)
|
||||
{
|
||||
if (false == can_edit(*end.road, &reason))
|
||||
{
|
||||
Error = "\"" + end.road->name() + "\" can't be changed: " + reason;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (ends.size() == 1)
|
||||
{
|
||||
// the point a road ends at: the last piece goes
|
||||
remove(std::vector<road_node *>{ends.front().road});
|
||||
Record.roads_removed.emplace_back(ends.front().road);
|
||||
return true;
|
||||
}
|
||||
// the piece which leads to the point stays, and takes the place of the two
|
||||
auto kept{ends[0]};
|
||||
auto dropped{ends[1]};
|
||||
if (false == kept.atend && dropped.atend)
|
||||
std::swap(kept, dropped);
|
||||
auto before{kept.road->definition()};
|
||||
auto after{dropped.road->definition()};
|
||||
if (false == kept.atend)
|
||||
before = flipped(before);
|
||||
if (dropped.atend)
|
||||
after = flipped(after);
|
||||
if (before.forward != after.forward || before.backward != after.backward)
|
||||
{
|
||||
Error = "the road has other lanes on each side of the point. Give both sides the same lanes first";
|
||||
return false;
|
||||
}
|
||||
auto const lengths{std::make_pair(before.length(), after.length())};
|
||||
auto const whole{lengths.first + lengths.second};
|
||||
if (lengths.first < 0.01 || lengths.second < 0.01 || before.width() <= 0.0)
|
||||
{
|
||||
Error = "the pieces at the point are too short";
|
||||
return false;
|
||||
}
|
||||
auto const start{curve_points(before.axis)};
|
||||
auto const end{curve_points(after.axis)};
|
||||
auto merged{before};
|
||||
merged.axis.points[segment_data::point::end] = end[3];
|
||||
merged.axis.rolls[1] = after.axis.rolls[1];
|
||||
// the far ends keep the way they go, with their control points moved out to make up for the ones which go with the point.
|
||||
// a piece cut in two has the control points at the cut as far from it as the parts of the piece each of the two took,
|
||||
// so going by these brings such a piece back to what it was
|
||||
auto const reach{std::make_pair(glm::distance(start[2], start[3]), glm::distance(end[0], end[1]))};
|
||||
auto const share{std::clamp(reach.first + reach.second > 1e-6 ? reach.first / (reach.first + reach.second) : lengths.first / whole, 0.05, 0.95)};
|
||||
auto control1{(start[1] - start[0]) / share};
|
||||
auto control2{(end[2] - end[3]) / (1.0 - share)};
|
||||
auto const straight{end[3] - start[0]};
|
||||
auto const along = [&straight](glm::dvec3 const &Direction) { return glm::length(glm::cross(glm::normalize(Direction), glm::normalize(straight))) < 1e-4; };
|
||||
if (glm::length(straight) > 0.01 && along(control1) && along(-control2) && glm::dot(control1, straight) > 0.0 && glm::dot(control2, straight) < 0.0)
|
||||
{
|
||||
// two pieces of a straight make a straight
|
||||
control1 = glm::dvec3{0.0};
|
||||
control2 = glm::dvec3{0.0};
|
||||
}
|
||||
merged.axis.points[segment_data::point::control1] = control1;
|
||||
merged.axis.points[segment_data::point::control2] = control2;
|
||||
// the road is as wide at its far ends as it was
|
||||
merged.taper[1] = static_cast<float>(after.taper[1] * after.width() / before.width());
|
||||
remember(Record, kept.road);
|
||||
remove(std::vector<road_node *>{dropped.road});
|
||||
Record.roads_removed.emplace_back(dropped.road);
|
||||
apply(std::vector<std::pair<road_node *, road_node::state>>{{kept.road, merged}});
|
||||
return true;
|
||||
}
|
||||
|
||||
road_node *editor_road::split(road_node &Road, double const T)
|
||||
{
|
||||
if (T <= 0.001 || T >= 0.999)
|
||||
@@ -764,25 +1109,7 @@ std::vector<road_node *> editor_road::chain(road_node &Road)
|
||||
|
||||
double editor_road::nearest_parameter(road_node::state const &State, glm::dvec3 const &Point)
|
||||
{
|
||||
auto const points{curve_points(State.axis)};
|
||||
auto const distance = [&](double const T) {
|
||||
auto const position{curve_point(points, T)};
|
||||
return glm::distance(glm::dvec2{position.x, position.z}, glm::dvec2{Point.x, Point.z});
|
||||
};
|
||||
double best{0.0};
|
||||
for (int i = 1; i <= 64; ++i)
|
||||
if (distance(i / 64.0) < distance(best))
|
||||
best = i / 64.0;
|
||||
double step{1.0 / 64.0};
|
||||
for (int i = 0; i < 24; ++i)
|
||||
{
|
||||
step *= 0.5;
|
||||
if (best - step >= 0.0 && distance(best - step) < distance(best))
|
||||
best -= step;
|
||||
else if (best + step <= 1.0 && distance(best + step) < distance(best))
|
||||
best += step;
|
||||
}
|
||||
return best;
|
||||
return nearest_on(curve_points(State.axis), Point);
|
||||
}
|
||||
|
||||
road_node *editor_road::nearest(glm::dvec3 const &Point, double const Margin)
|
||||
|
||||
@@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "world/Road.h"
|
||||
#include "world/RoadPoint.h"
|
||||
|
||||
// editor operations on the roads. a road in the scenery is a string of pieces joined end to end,
|
||||
// each piece being a road node with lanes and geometry of its own; roads meet at junctions
|
||||
@@ -54,10 +55,14 @@ class editor_road
|
||||
std::vector<std::pair<junction_node *, junction_node::state>> junctions; // junctions with what they were like before
|
||||
std::vector<junction_node *> junctions_created;
|
||||
std::vector<junction_node *> junctions_removed;
|
||||
std::vector<std::pair<roadpoint_node *, roadpoint_node::state>> points; // crossings and traffic points with what they were like before
|
||||
std::vector<roadpoint_node *> points_created;
|
||||
std::vector<roadpoint_node *> points_removed;
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return roads.empty() && roads_created.empty() && roads_removed.empty() && junctions.empty() && junctions_created.empty() && junctions_removed.empty();
|
||||
return roads.empty() && roads_created.empty() && roads_removed.empty() && junctions.empty() && junctions_created.empty() && junctions_removed.empty() && points.empty() &&
|
||||
points_created.empty() && points_removed.empty();
|
||||
}
|
||||
};
|
||||
// a road led out of the side of another road, or out of a junction between its arms: the junction this takes,
|
||||
@@ -91,6 +96,25 @@ class editor_road
|
||||
static std::vector<junction_node *> create(std::vector<junction_node::state> const &States);
|
||||
static void remove(std::vector<junction_node *> const &Junctions);
|
||||
static void revive(std::vector<junction_node *> const &Junctions);
|
||||
// level crossings, and the points the vehicles appear at or are taken away at. these go by the lanes and the rails they find
|
||||
// around them, so unlike the roads they can be changed with vehicles around
|
||||
static bool can_edit(roadpoint_node const &Point, std::string *Reason = nullptr);
|
||||
static std::vector<roadpoint_node *> create(std::vector<roadpoint_node::state> const &States);
|
||||
static void apply(std::vector<std::pair<roadpoint_node *, roadpoint_node::state>> const &Changes);
|
||||
static void remove(std::vector<roadpoint_node *> const &Points);
|
||||
static void revive(std::vector<roadpoint_node *> const &Points);
|
||||
// the crossing or traffic point nearest to specified point, within specified distance from it
|
||||
static roadpoint_node *nearest_point(glm::dvec3 const &Point, double const Radius);
|
||||
// place on the middle of a lane nearest to specified point, within specified distance from it.
|
||||
// Direction: receives the way the traffic goes there. returns: false if there's no lane that close
|
||||
static bool nearest_lane(glm::dvec3 const &Point, double const Reach, glm::dvec3 &Position, glm::dvec3 &Direction);
|
||||
// rails crossing specified piece within specified distance from a point. Middle: receives the point of the axis halfway between
|
||||
// the outermost of them, Halfspan: how far along the road these are from it. returns: false if no rails cross the piece there
|
||||
static bool rails_across(road_node const &Road, glm::dvec3 const &Near, double const Reach, glm::dvec3 &Middle, double &Halfspan);
|
||||
// takes a point out of a road. the two pieces which meet at it are made one, led from the far end of one to the far end
|
||||
// of the other the way these ends go; a point a road ends at takes the last piece of the road with it.
|
||||
// what gets done is added to Record. returns: false if it can't be done, with the reason in Error
|
||||
static bool dissolve(glm::dvec3 const &Joint, record &Record, std::string &Error);
|
||||
// cuts the piece in two at specified point of its axis. returns: the new piece, holding the part past the cut
|
||||
static road_node *split(road_node &Road, double const T);
|
||||
// the pieces which make a single road together with specified one, in no particular order
|
||||
|
||||
@@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "scene/scenelayers.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/Road.h"
|
||||
#include "world/RoadPoint.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "model/AnimModel.h"
|
||||
@@ -923,6 +924,68 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
state.patches[source.layer].edits.emplace_back(std::move(edit));
|
||||
rewritten.emplace(junction);
|
||||
}
|
||||
// level crossings and traffic points of the roads
|
||||
std::vector<roadpoint_node *> savedroadpoints;
|
||||
std::vector<roadpoint_node const *> droppedroadpoints;
|
||||
auto const roadpoint_text = [](roadpoint_node &Point, glm::dvec3 const &Offset) {
|
||||
auto const definition{Point.definition()};
|
||||
auto local{definition};
|
||||
local.position -= Offset;
|
||||
Point.define(local);
|
||||
std::string text;
|
||||
Point.export_as_text(text);
|
||||
Point.define(definition);
|
||||
text.erase(text.find_last_not_of(" \t\r\n") + 1);
|
||||
return text;
|
||||
};
|
||||
for (auto *point : simulation::Roadpoints.sequence())
|
||||
{
|
||||
if (point == nullptr || point->from_template())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const nodelayer{resolve(point->layer())};
|
||||
if (false == is_output(nodelayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const lookup{m_sources.find(point)};
|
||||
if (lookup == m_sources.end())
|
||||
{
|
||||
if (point->m_editorremoved || false == point->dirty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
created[nodelayer].emplace_back(point, roadpoint_text(*point, layer(nodelayer).context_insert().offset));
|
||||
savedroadpoints.push_back(point);
|
||||
continue;
|
||||
}
|
||||
auto const &source{lookup->second};
|
||||
if ((false == point->m_editorremoved && false == point->dirty()) || false == writable(source.layer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (false == load(source.layer))
|
||||
{
|
||||
return fail(state.error);
|
||||
}
|
||||
file_patch::edit edit;
|
||||
edit.begin = source.span.begin;
|
||||
edit.end = source.span.end;
|
||||
if (point->m_editorremoved)
|
||||
{
|
||||
droppedroadpoints.push_back(point);
|
||||
}
|
||||
else
|
||||
{
|
||||
edit.text = roadpoint_text(*point, source.context.offset);
|
||||
edit.length = edit.text.size();
|
||||
edit.node = point;
|
||||
savedroadpoints.push_back(point);
|
||||
}
|
||||
state.patches[source.layer].edits.emplace_back(std::move(edit));
|
||||
rewritten.emplace(point);
|
||||
}
|
||||
for (auto const &erased : m_erased)
|
||||
{
|
||||
file_patch::edit edit;
|
||||
@@ -1382,6 +1445,14 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
{
|
||||
m_sources.erase(junction);
|
||||
}
|
||||
for (auto *point : savedroadpoints)
|
||||
{
|
||||
point->m_dirty = false;
|
||||
}
|
||||
for (auto const *point : droppedroadpoints)
|
||||
{
|
||||
m_sources.erase(point);
|
||||
}
|
||||
if (removedterrain)
|
||||
{
|
||||
// binary terrain file still holds geometry of the dropped layer, have it rebuilt on the next load
|
||||
|
||||
@@ -17,6 +17,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/Road.h"
|
||||
#include "world/RoadPoint.h"
|
||||
#include "world/Traction.h"
|
||||
#include "world/TractionPower.h"
|
||||
#include "audio/sound.h"
|
||||
@@ -39,6 +40,7 @@ memory_table Memory;
|
||||
path_table Paths;
|
||||
road_table Roads;
|
||||
junction_table Junctions;
|
||||
roadpoint_table Roadpoints;
|
||||
traction_table Traction;
|
||||
powergridsource_table Powergrid;
|
||||
instance_table Instances;
|
||||
@@ -117,6 +119,8 @@ state_manager::update( double const Deltatime, int Iterationcount ) {
|
||||
TAnimModel::AnimUpdate( totaltime ); // wykonanie zakolejkowanych animacji
|
||||
|
||||
simulation::Powergrid.update( totaltime );
|
||||
// level crossings and the points road vehicles appear at; ahead of the vehicles, so the drivers act on what these decide
|
||||
simulation::Roadpoints.update( totaltime );
|
||||
simulation::Vehicles.update( Deltatime, Iterationcount );
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ extern memory_table Memory;
|
||||
extern path_table Paths;
|
||||
extern road_table Roads;
|
||||
extern junction_table Junctions;
|
||||
extern roadpoint_table Roadpoints;
|
||||
extern traction_table Traction;
|
||||
extern powergridsource_table Powergrid;
|
||||
extern instance_table Instances;
|
||||
|
||||
@@ -26,6 +26,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/lightarray.h"
|
||||
#include "world/TractionPower.h"
|
||||
#include "world/Road.h"
|
||||
#include "world/RoadPoint.h"
|
||||
#include "application/application.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "utilities/Logs.h"
|
||||
@@ -182,6 +183,7 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
|
||||
// so it's inserted in the region only after that file had its chance to be written
|
||||
simulation::Roads.create_geometry( Scratchpad );
|
||||
simulation::Junctions.create_geometry( Scratchpad );
|
||||
simulation::Roadpoints.create_geometry();
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -402,6 +404,8 @@ state_serializer::deserialize_firstinit( cParser &Input, scene::scratch_data &Sc
|
||||
// the junctions tie the lanes of the roads together, what's left loose after that gets closed by the roads
|
||||
simulation::Junctions.InitJunctions();
|
||||
simulation::Roads.InitRoads();
|
||||
// crossings and traffic points go by the lanes, which are complete at this point
|
||||
simulation::Roadpoints.InitRoadpoints();
|
||||
simulation::Traction.InitTraction();
|
||||
simulation::Events.InitEvents();
|
||||
simulation::Events.InitLaunchers();
|
||||
@@ -538,6 +542,20 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
|
||||
scene::Groups.insert( scene::Groups.handle(), junction );
|
||||
scene::Layers.track( junction, { sourcebegin, Input.TokenEnd() } );
|
||||
}
|
||||
else if( roadpoint_node::is_keyword( nodedata.type ) ) {
|
||||
// level crossing, or a point where road vehicles appear or are taken away
|
||||
auto *point { new roadpoint_node( nodedata ) };
|
||||
point->import(
|
||||
Input,
|
||||
( Scratchpad.location.offset.empty() ?
|
||||
glm::dvec3 { 0.0 } :
|
||||
glm::dvec3 { Scratchpad.location.offset.top() } ) );
|
||||
if( false == simulation::Roadpoints.insert( point ) ) {
|
||||
ErrorLog( "Bad scenario: duplicate road point name \"" + point->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" );
|
||||
}
|
||||
scene::Groups.insert( scene::Groups.handle(), point );
|
||||
scene::Layers.track( point, { sourcebegin, Input.TokenEnd() } );
|
||||
}
|
||||
else if( nodedata.type == "traction" ) {
|
||||
|
||||
auto *traction { deserialize_traction( Input, Scratchpad, nodedata ) };
|
||||
@@ -713,6 +731,9 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
|
||||
{ "track", scene::layer_item::track },
|
||||
{ "road", scene::layer_item::track },
|
||||
{ "junction", scene::layer_item::track },
|
||||
{ "crossing", scene::layer_item::track },
|
||||
{ "spawn", scene::layer_item::track },
|
||||
{ "despawn", scene::layer_item::track },
|
||||
{ "traction", scene::layer_item::traction },
|
||||
{ "tractionpowersource", scene::layer_item::powersource },
|
||||
{ "model", scene::layer_item::model },
|
||||
@@ -1427,6 +1448,11 @@ state_serializer::export_nodes_to_stream(std::ostream &scmfile, bool Dirty) cons
|
||||
junction->export_as_text( scmfile );
|
||||
}
|
||||
}
|
||||
for( auto const *point : Roadpoints.sequence() ) {
|
||||
if( point != nullptr && false == point->m_editorremoved && point->dirty() == Dirty && point->group() == null_handle ) {
|
||||
point->export_as_text( scmfile );
|
||||
}
|
||||
}
|
||||
// traction
|
||||
scmfile << "// traction\n";
|
||||
for( auto const *traction : Traction.sequence() ) {
|
||||
@@ -1482,7 +1508,8 @@ TAnimModel *state_serializer::create_model(const std::string &src, const std::st
|
||||
std::pair<int, int> state_serializer::preview_include(std::string const &Directive, scene::layer_context const &Context, scene::layer_handle Layer, scene::instance_handle Instance) {
|
||||
// statements which take more than a single token, with the tokens ending them
|
||||
static std::unordered_map<std::string, std::string> const nodeends {
|
||||
{ "dynamic", "enddynamic" }, { "track", "endtrack" }, { "road", "endroad" }, { "junction", "endjunction" }, { "traction", "endtraction" }, { "tractionpowersource", "end" }, { "model", "endmodel" },
|
||||
{ "dynamic", "enddynamic" }, { "track", "endtrack" }, { "road", "endroad" }, { "junction", "endjunction" }, { "crossing", "endcrossing" }, { "spawn", "endspawn" }, { "despawn", "enddespawn" },
|
||||
{ "traction", "endtraction" }, { "tractionpowersource", "end" }, { "model", "endmodel" },
|
||||
{ "triangles", "endtri" }, { "triangle_strip", "endtri" }, { "triangle_fan", "endtri" }, { "lines", "endline" }, { "line_strip", "endline" }, { "line_loop", "endline" },
|
||||
{ "memcell", "endmemcell" }, { "eventlauncher", "end" }, { "sound", "endsound" } };
|
||||
static std::unordered_map<std::string, std::string> const statementends {
|
||||
|
||||
@@ -47,6 +47,8 @@ class road_node; // kawałek drogi, z którego powstają pasy ruchu
|
||||
class road_table;
|
||||
class junction_node;
|
||||
class junction_table;
|
||||
class roadpoint_node; // przejazd kolejowy albo miejsce, w którym pojazdy drogowe się pojawiają lub znikają
|
||||
class roadpoint_table;
|
||||
struct dictionary_source;
|
||||
class trainset_desc;
|
||||
class scenery_desc;
|
||||
|
||||
1125
world/RoadPoint.cpp
Normal file
1125
world/RoadPoint.cpp
Normal file
File diff suppressed because it is too large
Load Diff
207
world/RoadPoint.h
Normal file
207
world/RoadPoint.h
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
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 <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "utilities/Classes.h"
|
||||
#include "utilities/Names.h"
|
||||
#include "scene/scenenode.h"
|
||||
#include "world/Road.h"
|
||||
|
||||
class TDynamicObject;
|
||||
class basic_event;
|
||||
|
||||
// a place on a road where something happens to the traffic. three kinds of it:
|
||||
// - a level crossing: vehicles are stopped ahead of the rails for as long as a train is near,
|
||||
// - a point where vehicles appear, drawn from a set made ready while the scenery is loaded,
|
||||
// - a point where vehicles are taken away. the ones which came from a point of the previous kind go back to its set.
|
||||
// scenery entries:
|
||||
// node <max> <min> <name> crossing <position> [clearance <m>] [warning <m>] [stoplines <yes|no>] endcrossing
|
||||
// node <max> <min> <name> spawn <position> [interval <s>] [variation <0..1>] [velocity <km/h>] [copies <n>]
|
||||
// [node <max> <min> <name> dynamic <folder> <skin> <type> <path> <offset> <driver> <velocity> <load> [<load type>] enddynamic]... endspawn
|
||||
// node <max> <min> <name> despawn <position> [radius <m>] enddespawn
|
||||
// the vehicles of a spawn point are given the way vehicles are put in a scenery; where they're put and how they're driven
|
||||
// is up to the point, so the path, the offset, the driver and the velocity of these entries don't matter
|
||||
class roadpoint_node : public scene::basic_node
|
||||
{
|
||||
|
||||
public:
|
||||
// types
|
||||
enum class kind_type
|
||||
{
|
||||
crossing,
|
||||
spawn,
|
||||
despawn
|
||||
};
|
||||
struct vehicle_data
|
||||
{
|
||||
std::string folder; // where the vehicle is defined, in the folder of the vehicles
|
||||
std::string skin;
|
||||
std::string type; // name of its definition files
|
||||
int load{0};
|
||||
std::string loadtype;
|
||||
|
||||
bool operator==(vehicle_data const &Other) const
|
||||
{
|
||||
return folder == Other.folder && skin == Other.skin && type == Other.type && load == Other.load && loadtype == Other.loadtype;
|
||||
}
|
||||
};
|
||||
struct state
|
||||
{
|
||||
kind_type kind{kind_type::crossing};
|
||||
glm::dvec3 position{0.0};
|
||||
// level crossing
|
||||
float clearance{6.f}; // distance along the road from the point to the places the vehicles stop at
|
||||
float warning{800.f}; // how far along the rails a train coming closes the crossing
|
||||
bool stoplines{true}; // lines are painted where the vehicles stop
|
||||
// point where vehicles appear
|
||||
float interval{20.f}; // seconds from a vehicle to the next
|
||||
float variation{0.5f}; // part of the interval which is drawn at random
|
||||
float velocity{50.f}; // speed the vehicles appear with, and are told to keep, km/h
|
||||
int copies{2}; // number of each vehicle made ready
|
||||
std::vector<vehicle_data> vehicles;
|
||||
// point where vehicles are taken away
|
||||
float radius{2.f}; // vehicles on lanes this close to the point are taken
|
||||
|
||||
// brings the content to a usable form
|
||||
void normalize();
|
||||
};
|
||||
// place the vehicles of a lane stop at while a crossing is closed
|
||||
struct stop_data
|
||||
{
|
||||
TTrack *track{nullptr}; // the lane it's on
|
||||
glm::dvec3 position{0.0};
|
||||
glm::dvec3 direction{0.0, 0.0, 1.0}; // the way the lane goes there
|
||||
float width{3.5f}; // of the lane
|
||||
};
|
||||
// constructors
|
||||
explicit roadpoint_node(scene::node_data const &Nodedata);
|
||||
// methods
|
||||
// restores content of the node from provided input stream; reads up to and including the closing statement
|
||||
void import(cParser &Input, glm::dvec3 const &Offset);
|
||||
state const &definition() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
// replaces what the node knows about itself. what it's tied to stays, it's up to the caller to bind it again.
|
||||
// a spawn point given another set of vehicles makes them anew; the ones it had stay on the road until they're taken away
|
||||
void define(state const &State);
|
||||
// scenery keyword for specified kind of point, and the statement closing its entry
|
||||
static std::string keyword(kind_type const Kind);
|
||||
static bool is_keyword(std::string const &Type);
|
||||
// reads vehicles out of a text. each is given the way vehicles are put in a scenery, or, if there's no such entry
|
||||
// in the text, as "<folder> <skin> <type> [<load> <load type>]" on a line of its own
|
||||
static std::vector<vehicle_data> parse_vehicles(std::string const &Text);
|
||||
// ties the point to the lanes and the rails as they are at the moment, or lets go of them
|
||||
void bind();
|
||||
void unbind();
|
||||
// makes the vehicles of a spawn point, once. it loads their models, which takes a while
|
||||
void prepare();
|
||||
// to be called with each step of the simulation
|
||||
void update(double const Deltatime);
|
||||
// state for display
|
||||
bool closed() const
|
||||
{
|
||||
return m_closed;
|
||||
}
|
||||
std::vector<stop_data> const &stops() const
|
||||
{
|
||||
return m_stops;
|
||||
}
|
||||
std::vector<TTrack *> const &lanes() const
|
||||
{
|
||||
return m_lanes;
|
||||
}
|
||||
std::size_t rails() const
|
||||
{
|
||||
return m_rails.size();
|
||||
}
|
||||
// vehicles made for a spawn point, and how many of them wait for their turn
|
||||
std::size_t vehicles() const
|
||||
{
|
||||
return m_pool.size();
|
||||
}
|
||||
std::size_t waiting() const;
|
||||
// generates geometry of the stop lines
|
||||
std::vector<scene::shape_node> create_shapes() const;
|
||||
// puts geometry of the point in the scene, or takes it back
|
||||
void show();
|
||||
void hide();
|
||||
bool shown() const
|
||||
{
|
||||
return m_shapes.shown();
|
||||
}
|
||||
// members
|
||||
bool m_editorremoved{false}; // removed in the editor; kept around so the removal can be undone
|
||||
|
||||
private:
|
||||
// types
|
||||
struct pooled_vehicle
|
||||
{
|
||||
TDynamicObject *vehicle{nullptr};
|
||||
double velocity{0.0}; // what its speed was when it was made, to be given back each time it's put on the road
|
||||
};
|
||||
// methods
|
||||
float radius_() override;
|
||||
void serialize_(std::ostream &Output) const override;
|
||||
void deserialize_(std::istream &Input) override;
|
||||
void export_as_text_(std::ostream &Output) const override;
|
||||
void bind_crossing();
|
||||
void update_crossing(double const Deltatime);
|
||||
void update_spawn(double const Deltatime);
|
||||
void update_despawn();
|
||||
void close(bool const Closed);
|
||||
// members
|
||||
state m_state;
|
||||
bool m_bound{false};
|
||||
// level crossing
|
||||
std::vector<stop_data> m_stops;
|
||||
std::vector<basic_event *> m_events; // orders for the drivers, one for each stop. these are never freed, as a driver can hold on to one
|
||||
std::vector<TTrack *> m_rails; // rails a train closes the crossing from
|
||||
std::map<TDynamicObject const *, double> m_seen; // rail vehicles near, with how far each was the last time
|
||||
bool m_closed{false};
|
||||
double m_hold{0.0}; // time left for the crossing to stay closed after the last train was seen
|
||||
double m_scan{0.0}; // time left to the next look at the rails
|
||||
// spawn and despawn
|
||||
std::vector<TTrack *> m_lanes; // spawn: the lane the vehicles are put on. despawn: lanes the vehicles are taken from
|
||||
double m_offset{0.0}; // spawn: distance of the point from the start of that lane
|
||||
std::vector<pooled_vehicle> m_pool;
|
||||
bool m_prepared{false};
|
||||
int m_made{0}; // number of vehicles made so far, for their names
|
||||
double m_timer{0.0}; // time left to the next vehicle
|
||||
owned_shapes m_shapes;
|
||||
};
|
||||
|
||||
// collection of the road points present in the scene
|
||||
class roadpoint_table : public basic_table<roadpoint_node>
|
||||
{
|
||||
|
||||
public:
|
||||
// legacy style initialization, to be performed when the lanes of the roads are tied together
|
||||
void InitRoadpoints();
|
||||
// lets go of the lanes and the rails, ahead of a change of the roads, and ties all points anew when it's done
|
||||
void unbind();
|
||||
void bind();
|
||||
// puts geometry of the points in the scene
|
||||
void create_geometry();
|
||||
// to be called with each step of the simulation
|
||||
void update(double const Deltatime);
|
||||
|
||||
private:
|
||||
bool m_geometry{false}; // the scene is ready to take geometry of the points
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user