mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 00:29:19 +02:00
1267 lines
40 KiB
C++
1267 lines
40 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 "world/RoadPoint.h"
|
|
|
|
#include "world/Track.h"
|
|
#include "world/Event.h"
|
|
#include "world/RoadOrder.h"
|
|
#include "vehicle/DynObj.h"
|
|
#include "vehicle/Driver.h"
|
|
#include "vehicle/Train.h"
|
|
#include "model/Model3d.h"
|
|
#include "simulation/simulation.h"
|
|
#include "rendering/particles.h"
|
|
#include "rendering/renderer.h"
|
|
#include "scene/scene.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "utilities/parser.h"
|
|
#include "utilities/utilities.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
constexpr double stopline_width{0.5};
|
|
constexpr double stopline_inset{0.15}; // gap between the ends of a stop line and the edges of its lane
|
|
constexpr double line_lift{0.02}; // the line is raised over the surface, so the two don't fight for depth
|
|
constexpr double scan_period{0.2}; // seconds between looks at the rails of a crossing
|
|
constexpr double hold_time{4.0}; // seconds a crossing stays closed after the last train was seen
|
|
constexpr double near_zone{30.0}; // a rail vehicle this close to the stop lines keeps the crossing closed whichever way it goes
|
|
constexpr double clear_zone{20.0}; // a vehicle isn't put on the road with another one this close
|
|
constexpr double retry_period{1.0}; // seconds to the next try, when a vehicle can't be put on the road
|
|
constexpr double front_room{2.5}; // half the length a vehicle is presumed to have before it's made
|
|
constexpr double queue_reach{250.0}; // how far back from a closed crossing the vehicles standing on the lanes are taken to wait for it
|
|
constexpr double stuck_time{15.0}; // seconds a vehicle of a spawn point can stand still before it's taken off the road
|
|
constexpr double stuck_speed{0.1}; // m/s, slower than this is standing still
|
|
constexpr double reverse_speed{0.5}; // m/s, a vehicle going backwards faster than this is taken off the road
|
|
constexpr double stripe_width{0.5}; // of a pedestrian crossing; the gaps are as wide
|
|
|
|
// a path as a curve: points along it, and distances along it
|
|
class path_curve
|
|
{
|
|
|
|
public:
|
|
explicit path_curve(segment_data const &Path)
|
|
{
|
|
auto const &start{Path.points[segment_data::point::start]};
|
|
auto const &end{Path.points[segment_data::point::end]};
|
|
auto const &control1{Path.points[segment_data::point::control1]};
|
|
auto const &control2{Path.points[segment_data::point::control2]};
|
|
m_points[0] = start;
|
|
m_points[3] = end;
|
|
if (control1 == glm::dvec3{0.0} && control2 == glm::dvec3{0.0})
|
|
{
|
|
m_points[1] = start + (end - start) / 3.0;
|
|
m_points[2] = end - (end - start) / 3.0;
|
|
}
|
|
else
|
|
{
|
|
m_points[1] = start + control1;
|
|
m_points[2] = end + control2;
|
|
}
|
|
m_stations[0] = 0.0;
|
|
auto previous{m_points[0]};
|
|
for (int idx = 1; idx <= samples; ++idx)
|
|
{
|
|
auto const current{point(static_cast<double>(idx) / samples)};
|
|
m_stations[idx] = m_stations[idx - 1] + glm::distance(previous, current);
|
|
previous = current;
|
|
}
|
|
}
|
|
double length() const
|
|
{
|
|
return m_stations[samples];
|
|
}
|
|
glm::dvec3 point(double const T) const
|
|
{
|
|
auto const u{1.0 - T};
|
|
return u * u * u * m_points[0] + 3.0 * u * u * T * m_points[1] + 3.0 * u * T * T * m_points[2] + T * T * T * m_points[3];
|
|
}
|
|
glm::dvec3 direction(double const T) const
|
|
{
|
|
auto const u{1.0 - T};
|
|
auto const result{3.0 * u * u * (m_points[1] - m_points[0]) + 6.0 * u * T * (m_points[2] - m_points[1]) + 3.0 * T * T * (m_points[3] - m_points[2])};
|
|
return glm::length2(result) > 1e-12 ? glm::normalize(result) : glm::dvec3{0.0, 0.0, 1.0};
|
|
}
|
|
// value of the curve parameter for specified distance from the start, and the other way around
|
|
double parameter(double const Station) const
|
|
{
|
|
if (Station <= 0.0)
|
|
{
|
|
return 0.0;
|
|
}
|
|
if (Station >= length())
|
|
{
|
|
return 1.0;
|
|
}
|
|
auto const idx{std::distance(m_stations.begin(), std::upper_bound(m_stations.begin(), m_stations.end(), Station))};
|
|
auto const span{m_stations[idx] - m_stations[idx - 1]};
|
|
return (static_cast<double>(idx - 1) + (span > 0.0 ? (Station - m_stations[idx - 1]) / span : 0.0)) / samples;
|
|
}
|
|
double station(double const T) const
|
|
{
|
|
auto const position{std::clamp(T, 0.0, 1.0) * samples};
|
|
auto const idx{std::min(samples - 1, static_cast<int>(position))};
|
|
return glm::mix(m_stations[idx], m_stations[idx + 1], position - idx);
|
|
}
|
|
// value of the curve parameter for the point nearest to specified one, seen from above; Distance receives how far it is
|
|
double nearest(glm::dvec3 const &Point, double &Distance) const
|
|
{
|
|
auto const distance = [&](double const T) {
|
|
auto const position{point(T)};
|
|
return glm::distance(glm::dvec2{position.x, position.z}, glm::dvec2{Point.x, Point.z});
|
|
};
|
|
double best{0.0};
|
|
for (int idx = 1; idx <= samples; ++idx)
|
|
{
|
|
if (distance(static_cast<double>(idx) / samples) < distance(best))
|
|
{
|
|
best = static_cast<double>(idx) / samples;
|
|
}
|
|
}
|
|
auto step{1.0 / samples};
|
|
for (int pass = 0; pass < 16; ++pass)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
Distance = distance(best);
|
|
return best;
|
|
}
|
|
|
|
private:
|
|
static constexpr int samples{32};
|
|
std::array<glm::dvec3, 4> m_points;
|
|
std::array<double, samples + 1> m_stations{};
|
|
};
|
|
|
|
// true if the path can't have a point within specified distance from specified one; a cheap way to tell most of the scenery apart
|
|
bool out_of_reach(segment_data const &Path, glm::dvec3 const &Point, double const Reach)
|
|
{
|
|
auto const &start{Path.points[segment_data::point::start]};
|
|
auto const &end{Path.points[segment_data::point::end]};
|
|
// the curve lies within its control points, which are no further from the start than this
|
|
auto const span{glm::length(Path.points[segment_data::point::control1]) + glm::distance(start, end) + glm::length(Path.points[segment_data::point::control2])};
|
|
return glm::distance(glm::dvec2{start.x, start.z}, glm::dvec2{Point.x, Point.z}) > span + Reach;
|
|
}
|
|
|
|
// an order for the drivers heading for a closed crossing
|
|
using stop_event = road_order;
|
|
|
|
// the road passing under specified point: the one whose axis is nearest. T: receives the value of the curve parameter of its axis there
|
|
road_node const *road_under(glm::dvec3 const &Point, double &T)
|
|
{
|
|
road_node const *result{nullptr};
|
|
auto best{std::numeric_limits<double>::max()};
|
|
for (auto const *road : simulation::Roads.sequence())
|
|
{
|
|
if (road == nullptr || road->m_editorremoved || out_of_reach(road->definition().axis, Point, 50.0))
|
|
{
|
|
continue;
|
|
}
|
|
auto const &layout{road->definition()};
|
|
path_curve const axis{layout.axis};
|
|
double distance{0.0};
|
|
auto const t{axis.nearest(Point, distance)};
|
|
if (distance > 0.5 * layout.span(t) + 1.0 || std::abs(axis.point(t).y - Point.y) > 3.0 || distance >= best)
|
|
{
|
|
continue;
|
|
}
|
|
best = distance;
|
|
result = road;
|
|
T = t;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// lane of a road with the point nearest to specified one, within specified distance from it. Station: receives the distance of that point from the start of the lane
|
|
TTrack *nearest_lane(glm::dvec3 const &Point, double const Reach, double &Station)
|
|
{
|
|
TTrack *result{nullptr};
|
|
auto best{Reach};
|
|
for (auto const *road : simulation::Roads.sequence())
|
|
{
|
|
if (road == nullptr || road->m_editorremoved)
|
|
{
|
|
continue;
|
|
}
|
|
for (auto *lane : road->tracks())
|
|
{
|
|
if (lane == nullptr || lane->m_paths.empty() || out_of_reach(lane->m_paths.front(), Point, Reach))
|
|
{
|
|
continue;
|
|
}
|
|
path_curve const curve{lane->m_paths.front()};
|
|
double distance{0.0};
|
|
auto const t{curve.nearest(Point, distance)};
|
|
if (distance >= best || std::abs(curve.point(t).y - Point.y) > 3.0)
|
|
{
|
|
continue;
|
|
}
|
|
best = distance;
|
|
result = lane;
|
|
Station = curve.station(t);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
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});
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// brings the content to a usable form
|
|
void roadpoint_node::state::normalize()
|
|
{
|
|
clearance = std::clamp(clearance, 1.f, 100.f);
|
|
warning = std::clamp(warning, 50.f, 5000.f);
|
|
interval = std::clamp(interval, 1.f, 3600.f);
|
|
variation = std::clamp(variation, 0.f, 1.f);
|
|
velocity = std::clamp(velocity, 5.f, 200.f);
|
|
count = std::clamp(count, 1, 64);
|
|
radius = std::clamp(radius, 0.5f, 50.f);
|
|
length = std::clamp(length, 1.f, 20.f);
|
|
}
|
|
|
|
roadpoint_node::roadpoint_node(scene::node_data const &Nodedata) : basic_node(Nodedata)
|
|
{
|
|
m_state.kind = (Nodedata.type == "spawn" ? kind_type::spawn : Nodedata.type == "despawn" ? kind_type::despawn : Nodedata.type == "crosswalk" ? kind_type::crosswalk : kind_type::crossing);
|
|
}
|
|
|
|
// scenery keyword for specified kind of point
|
|
std::string roadpoint_node::keyword(kind_type const Kind)
|
|
{
|
|
return Kind == kind_type::spawn ? "spawn" : Kind == kind_type::despawn ? "despawn" : Kind == kind_type::crosswalk ? "crosswalk" : "crossing";
|
|
}
|
|
|
|
bool roadpoint_node::is_keyword(std::string const &Type)
|
|
{
|
|
return Type == "crossing" || Type == "spawn" || Type == "despawn" || Type == "crosswalk";
|
|
}
|
|
|
|
// reads vehicles out of a text
|
|
std::vector<roadpoint_node::vehicle_data> roadpoint_node::parse_vehicles(std::string const &Text)
|
|
{
|
|
std::vector<vehicle_data> result;
|
|
auto const add = [&result](std::vector<std::string> const &Tokens, std::size_t const First, std::string const &Load, std::string const &Loadtype) {
|
|
vehicle_data vehicle;
|
|
vehicle.folder = Tokens[First];
|
|
vehicle.skin = Tokens[First + 1];
|
|
vehicle.type = Tokens[First + 2];
|
|
replace_slashes(vehicle.folder);
|
|
replace_slashes(vehicle.skin);
|
|
replace_slashes(vehicle.type);
|
|
vehicle.load = std::max(0, std::atoi(Load.c_str()));
|
|
if (vehicle.load > 0)
|
|
{
|
|
vehicle.loadtype = Loadtype;
|
|
}
|
|
result.emplace_back(vehicle);
|
|
};
|
|
std::vector<std::string> tokens;
|
|
{
|
|
std::istringstream stream{Text};
|
|
std::string token;
|
|
while (stream >> token)
|
|
{
|
|
tokens.emplace_back(token);
|
|
}
|
|
}
|
|
if (std::find(tokens.begin(), tokens.end(), "dynamic") != tokens.end())
|
|
{
|
|
// node <max> <min> <name> dynamic <folder> <skin> <type> <path> <offset> <driver> <velocity> <load> [<load type>] enddynamic
|
|
for (std::size_t idx = 0; idx < tokens.size(); ++idx)
|
|
{
|
|
if (tokens[idx] != "dynamic" || idx + 3 >= tokens.size())
|
|
{
|
|
continue;
|
|
}
|
|
auto last{idx + 4};
|
|
while (last < tokens.size() && tokens[last] != "enddynamic")
|
|
{
|
|
++last;
|
|
}
|
|
// past the vehicle itself come the path, the offset, the driver and the velocity, then the load
|
|
auto const load{idx + 8};
|
|
add(tokens, idx + 1, load < last ? tokens[load] : std::string{}, load + 1 < last ? tokens[load + 1] : std::string{});
|
|
idx = last;
|
|
}
|
|
return result;
|
|
}
|
|
std::istringstream lines{Text};
|
|
std::string line;
|
|
while (std::getline(lines, line))
|
|
{
|
|
std::istringstream stream{line};
|
|
std::vector<std::string> parts;
|
|
std::string part;
|
|
while (stream >> part)
|
|
{
|
|
parts.emplace_back(part);
|
|
}
|
|
if (parts.size() >= 3)
|
|
{
|
|
add(parts, 0, parts.size() > 3 ? parts[3] : std::string{}, parts.size() > 4 ? parts[4] : std::string{});
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// restores content of the node from provided input stream; reads up to and including the closing statement
|
|
void roadpoint_node::import(cParser &Input, glm::dvec3 const &Offset)
|
|
{
|
|
auto const type{keyword(m_state.kind)};
|
|
auto const label{type + " \"" + m_name + "\""};
|
|
auto const kind{m_state.kind};
|
|
m_state = state{};
|
|
m_state.kind = kind;
|
|
Input.getTokens(3);
|
|
Input >> m_state.position.x >> m_state.position.y >> m_state.position.z;
|
|
m_state.position += Offset;
|
|
int copies{0}; // of each vehicle, the way the first sceneries with spawn points gave the size of the set
|
|
auto counted{false};
|
|
|
|
auto token{Input.getToken<std::string>()};
|
|
while (false == token.empty() && token != "end" + type)
|
|
{
|
|
if (token == "clearance")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.clearance;
|
|
}
|
|
else if (token == "warning")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.warning;
|
|
}
|
|
else if (token == "stoplines")
|
|
{
|
|
m_state.stoplines = (Input.getToken<std::string>() != "no");
|
|
}
|
|
else if (token == "interval")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.interval;
|
|
}
|
|
else if (token == "variation")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.variation;
|
|
}
|
|
else if (token == "velocity")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.velocity;
|
|
}
|
|
else if (token == "count")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.count;
|
|
counted = true;
|
|
}
|
|
else if (token == "copies")
|
|
{
|
|
Input.getTokens();
|
|
Input >> copies;
|
|
}
|
|
else if (token == "length")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.length;
|
|
}
|
|
else if (token == "radius")
|
|
{
|
|
Input.getTokens();
|
|
Input >> m_state.radius;
|
|
}
|
|
else if (token == "node")
|
|
{
|
|
// a vehicle, given the way vehicles are put in a scenery:
|
|
// node <max> <min> <name> dynamic <folder> <skin> <type> <path> <offset> <driver> <velocity> <load> [<load type>] enddynamic
|
|
std::string discard;
|
|
std::string nodetype;
|
|
Input.getTokens(4);
|
|
Input >> discard >> discard >> discard >> nodetype;
|
|
vehicle_data vehicle;
|
|
if (nodetype == "dynamic")
|
|
{
|
|
vehicle.folder = Input.getToken<std::string>();
|
|
vehicle.skin = Input.getToken<std::string>();
|
|
vehicle.type = Input.getToken<std::string>();
|
|
replace_slashes(vehicle.folder);
|
|
replace_slashes(vehicle.skin);
|
|
replace_slashes(vehicle.type);
|
|
}
|
|
else
|
|
{
|
|
ErrorLog("Bad road point: " + label + " lists a \"" + nodetype + "\" where it takes vehicles only");
|
|
}
|
|
// where the vehicle is put and how it's driven is up to the point, what's left to take is its load
|
|
std::vector<std::string> rest;
|
|
auto part{Input.getToken<std::string>()};
|
|
while (false == part.empty() && part != "enddynamic" && part != "end" + type)
|
|
{
|
|
rest.emplace_back(part);
|
|
part = Input.getToken<std::string>();
|
|
}
|
|
if (rest.size() > 4)
|
|
{
|
|
vehicle.load = std::atoi(rest[4].c_str());
|
|
}
|
|
if (rest.size() > 5 && vehicle.load > 0)
|
|
{
|
|
vehicle.loadtype = rest[5];
|
|
}
|
|
if (false == vehicle.type.empty())
|
|
{
|
|
m_state.vehicles.emplace_back(vehicle);
|
|
}
|
|
if (part != "enddynamic")
|
|
{
|
|
ErrorLog("Bad road point: a vehicle of " + label + " isn't closed with \"enddynamic\"");
|
|
if (part == "end" + type)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrorLog("Bad road point: unknown property: \"" + token + "\" defined for " + label);
|
|
}
|
|
token = Input.getToken<std::string>();
|
|
}
|
|
if (false == counted && copies > 0)
|
|
{
|
|
m_state.count = copies * static_cast<int>(std::max<std::size_t>(1, m_state.vehicles.size()));
|
|
}
|
|
m_state.normalize();
|
|
location(m_state.position);
|
|
}
|
|
|
|
// replaces what the node knows about itself
|
|
void roadpoint_node::define(state const &State)
|
|
{
|
|
auto const vehicles{m_state.vehicles};
|
|
auto const count{m_state.count};
|
|
m_state = State;
|
|
m_state.normalize();
|
|
if (m_prepared && (m_state.vehicles != vehicles || m_state.count != count))
|
|
{
|
|
// vehicles can't be unmade, there's too much holding on to them. the old ones are left alone:
|
|
// those which wait stay where they are, those on the road drive on until something takes them away
|
|
m_pool.clear();
|
|
m_prepared = false;
|
|
}
|
|
location(m_state.position);
|
|
m_area.radius = -1.f;
|
|
}
|
|
|
|
// ties the point to the lanes and the rails as they are at the moment
|
|
void roadpoint_node::bind()
|
|
{
|
|
unbind();
|
|
m_bound = true;
|
|
if (m_editorremoved)
|
|
{
|
|
return;
|
|
}
|
|
switch (m_state.kind)
|
|
{
|
|
case kind_type::crossing:
|
|
{
|
|
bind_crossing();
|
|
break;
|
|
}
|
|
case kind_type::spawn:
|
|
{
|
|
// the vehicles are put on the lane the point is on
|
|
double station{0.0};
|
|
auto *lane{nearest_lane(m_state.position, 3.0, station)};
|
|
if (lane != nullptr)
|
|
{
|
|
m_lanes.emplace_back(lane);
|
|
// at the very start of a lane which leads from nowhere the vehicle would hang over its end
|
|
m_offset = (lane->trPrev == nullptr ? std::max(station, 2.0 * front_room) : station);
|
|
}
|
|
break;
|
|
}
|
|
case kind_type::crosswalk:
|
|
{
|
|
// the stripes go across the road the point is on
|
|
double t{0.0};
|
|
auto const *road{road_under(m_state.position, t)};
|
|
if (road != nullptr)
|
|
{
|
|
auto const &layout{road->definition()};
|
|
path_curve const axis{layout.axis};
|
|
m_onroad = true;
|
|
m_along = axis.direction(t);
|
|
m_halfwidth = 0.5 * layout.span(t);
|
|
// the point itself may be off the axis, the stripes aren't
|
|
auto const centre{axis.point(t)};
|
|
m_state.position = centre;
|
|
}
|
|
break;
|
|
}
|
|
case kind_type::despawn:
|
|
{
|
|
// the vehicles are taken from every lane which passes close enough
|
|
for (auto const *road : simulation::Roads.sequence())
|
|
{
|
|
if (road == nullptr || road->m_editorremoved)
|
|
{
|
|
continue;
|
|
}
|
|
for (auto *lane : road->tracks())
|
|
{
|
|
if (lane == nullptr || lane->m_paths.empty() || out_of_reach(lane->m_paths.front(), m_state.position, m_state.radius))
|
|
{
|
|
continue;
|
|
}
|
|
path_curve const curve{lane->m_paths.front()};
|
|
double distance{0.0};
|
|
auto const t{curve.nearest(m_state.position, distance)};
|
|
if (distance <= m_state.radius && std::abs(curve.point(t).y - m_state.position.y) <= 3.0)
|
|
{
|
|
m_lanes.emplace_back(lane);
|
|
}
|
|
}
|
|
// and from the paths which lead the lanes of a piece which gains or loses them from one of its ends to the other
|
|
for (auto *link : road->links())
|
|
{
|
|
if (link == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
for (auto const &way : link->m_paths)
|
|
{
|
|
if (out_of_reach(way, m_state.position, m_state.radius))
|
|
{
|
|
continue;
|
|
}
|
|
path_curve const curve{way};
|
|
double distance{0.0};
|
|
auto const t{curve.nearest(m_state.position, distance)};
|
|
if (distance <= m_state.radius && std::abs(curve.point(t).y - m_state.position.y) <= 3.0)
|
|
{
|
|
m_lanes.emplace_back(link);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// finds the rails of a crossing, and the places the vehicles of each lane are to stop at
|
|
void roadpoint_node::bind_crossing()
|
|
{
|
|
// the road the crossing is on: the one whose axis passes nearest
|
|
double where{0.0};
|
|
auto const *crossed{road_under(m_state.position, where)};
|
|
if (crossed != nullptr)
|
|
{
|
|
auto const &layout{crossed->definition()};
|
|
if (layout.changing())
|
|
{
|
|
// NOTE: on such a piece a lane is a string of paths, and may fork; the vehicles are stopped ahead of all that
|
|
ErrorLog("Bad road point: crossing \"" + m_name + "\" lies on a piece of road which gains or loses lanes, the vehicles are stopped before that piece");
|
|
}
|
|
for (std::size_t idx = 0; idx < layout.lanes.size(); ++idx)
|
|
{
|
|
// the path the traffic of the lane gets on the piece by; on a piece which keeps its lanes it's the lane itself
|
|
auto *lane{crossed->lane_track(idx, idx >= static_cast<std::size_t>(layout.backward) ? 0 : 1)};
|
|
if (lane == nullptr || lane->m_paths.empty())
|
|
{
|
|
continue;
|
|
}
|
|
// the lanes are laid out the way the traffic goes, so the place to stop at is back along the lane from the crossing
|
|
double distance{0.0};
|
|
path_curve curve{lane->m_paths.front()};
|
|
auto station{curve.station(curve.nearest(m_state.position, distance))};
|
|
double remaining{m_state.clearance};
|
|
for (int guard = 0; guard < 16; ++guard)
|
|
{
|
|
if (station >= remaining)
|
|
{
|
|
station -= remaining;
|
|
break;
|
|
}
|
|
// it's on the piece before this one, if there's any: the same lane of the road, not a way through a junction
|
|
auto *previous{lane->trPrev};
|
|
auto const *owner{previous != nullptr ? dynamic_cast<road_node const *>(previous->m_road) : nullptr};
|
|
if (owner == nullptr || previous->m_paths.empty() || previous->trNext != lane || std::find(owner->tracks().begin(), owner->tracks().end(), previous) == owner->tracks().end())
|
|
{
|
|
station = 0.0;
|
|
break;
|
|
}
|
|
remaining -= station;
|
|
lane = previous;
|
|
curve = path_curve{lane->m_paths.front()};
|
|
station = curve.length();
|
|
}
|
|
auto const t{curve.parameter(station)};
|
|
stop_data stop;
|
|
stop.track = lane;
|
|
stop.position = curve.point(t);
|
|
stop.direction = curve.direction(t);
|
|
stop.width = (idx < crossed->definition().lanes.size() ? crossed->definition().lanes[idx].width : crossed->definition().lanewidth);
|
|
m_stops.emplace_back(stop);
|
|
}
|
|
}
|
|
// orders for the drivers. the ones made before are used again, as a driver may still hold on to one
|
|
while (m_events.size() < m_stops.size())
|
|
{
|
|
auto *event{new stop_event()};
|
|
event->m_name = m_name + ":stop" + std::to_string(m_events.size() + 1);
|
|
m_events.emplace_back(event);
|
|
}
|
|
for (std::size_t idx = 0; idx < m_stops.size(); ++idx)
|
|
{
|
|
auto *event{static_cast<stop_event *>(m_events[idx])};
|
|
event->m_location = m_stops[idx].position;
|
|
// lanes are driven from their first point to the second, which is the way events of the second kind are for
|
|
m_stops[idx].track->m_events2.emplace_back(event->m_name, event);
|
|
}
|
|
|
|
// rails: the tracks passing between the stop lines, and what leads to them within the warning distance
|
|
std::vector<std::pair<TTrack *, double>> reached;
|
|
auto const known = [&reached](TTrack const *Track) { return std::any_of(reached.begin(), reached.end(), [Track](std::pair<TTrack *, double> const &Entry) { return Entry.first == Track; }); };
|
|
for (auto *track : simulation::Paths.sequence())
|
|
{
|
|
if (track == nullptr || track->m_editorremoved || (track->iCategoryFlag & 1) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
for (auto const &path : track->m_paths)
|
|
{
|
|
if (out_of_reach(path, m_state.position, m_state.clearance))
|
|
{
|
|
continue;
|
|
}
|
|
path_curve const curve{path};
|
|
double distance{0.0};
|
|
auto const t{curve.nearest(m_state.position, distance)};
|
|
if (distance <= m_state.clearance && std::abs(curve.point(t).y - m_state.position.y) <= 3.0)
|
|
{
|
|
reached.emplace_back(track, 0.0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (std::size_t idx = 0; idx < reached.size(); ++idx)
|
|
{
|
|
auto *track{reached[idx].first};
|
|
double length{0.0};
|
|
for (auto const &path : track->m_paths)
|
|
{
|
|
length = std::max(length, path_curve{path}.length());
|
|
}
|
|
auto const travelled{reached[idx].second + length};
|
|
if (travelled > m_state.warning)
|
|
{
|
|
continue;
|
|
}
|
|
std::vector<TTrack *> neighbours{track->trPrev, track->trNext};
|
|
if (track->SwitchExtension != nullptr)
|
|
{
|
|
// both ways of a switch count, as the train can come by either
|
|
neighbours.insert(neighbours.end(), {track->SwitchExtension->pPrevs[0], track->SwitchExtension->pPrevs[1], track->SwitchExtension->pNexts[0], track->SwitchExtension->pNexts[1]});
|
|
}
|
|
for (auto *neighbour : neighbours)
|
|
{
|
|
if (neighbour != nullptr && (neighbour->iCategoryFlag & 1) != 0 && false == known(neighbour))
|
|
{
|
|
reached.emplace_back(neighbour, travelled);
|
|
}
|
|
}
|
|
}
|
|
for (auto const &entry : reached)
|
|
{
|
|
m_rails.emplace_back(entry.first);
|
|
}
|
|
// lanes the vehicles wait on while the crossing is closed: the ones with the stops, and what leads to them
|
|
for (auto const &stop : m_stops)
|
|
{
|
|
auto *lane{stop.track};
|
|
double reach{0.0};
|
|
for (int guard = 0; lane != nullptr && guard < 64 && reach < queue_reach; ++guard)
|
|
{
|
|
if (std::find(m_queue.begin(), m_queue.end(), lane) == m_queue.end())
|
|
{
|
|
m_queue.emplace_back(lane);
|
|
}
|
|
if (false == lane->m_paths.empty())
|
|
{
|
|
reach += glm::distance(lane->m_paths.front().points[segment_data::point::start], lane->m_paths.front().points[segment_data::point::end]);
|
|
}
|
|
lane = lane->trPrev;
|
|
}
|
|
}
|
|
close(m_closed);
|
|
}
|
|
|
|
// lets go of the lanes and the rails
|
|
void roadpoint_node::unbind()
|
|
{
|
|
if (false == m_bound)
|
|
{
|
|
return;
|
|
}
|
|
m_bound = false;
|
|
for (std::size_t idx = 0; idx < m_stops.size() && idx < m_events.size(); ++idx)
|
|
{
|
|
auto &events{m_stops[idx].track->m_events2};
|
|
auto const *event{m_events[idx]};
|
|
events.erase(std::remove_if(events.begin(), events.end(), [event](std::pair<std::string, basic_event *> const &Entry) { return Entry.second == event; }), events.end());
|
|
}
|
|
// a driver who has one of the orders in sight keeps it; it's not to hold anyone anymore
|
|
for (auto *event : m_events)
|
|
{
|
|
static_cast<stop_event *>(event)->m_velocity = -1.0;
|
|
}
|
|
m_stops.clear();
|
|
m_rails.clear();
|
|
m_queue.clear();
|
|
m_lanes.clear();
|
|
m_seen.clear();
|
|
m_onroad = false;
|
|
}
|
|
|
|
// makes the vehicles of a spawn point
|
|
void roadpoint_node::prepare()
|
|
{
|
|
if (m_prepared || m_state.kind != kind_type::spawn || m_editorremoved)
|
|
{
|
|
return;
|
|
}
|
|
if (false == m_bound)
|
|
{
|
|
bind();
|
|
}
|
|
if (m_lanes.empty())
|
|
{
|
|
ErrorLog("Bad road point: spawn \"" + m_name + "\" isn't on a lane of a road, there's nowhere to put its vehicles");
|
|
return;
|
|
}
|
|
m_prepared = true;
|
|
// the first vehicle shows up after a part of the interval, so points with the same interval don't all start at once
|
|
m_timer = LocalRandom(0.0, m_state.interval);
|
|
// vehicles are known by their names, so a point without one makes something up for them
|
|
static int nameless{0};
|
|
auto const basename{m_name.empty() ? "roadspawn" + std::to_string(++nameless) : m_name};
|
|
// the listed vehicles are gone through as many times as it takes, from a random one on, so the set gets each kind in turn
|
|
auto const kinds{m_state.vehicles.size()};
|
|
auto const start{kinds > 0 ? std::min(kinds - 1, static_cast<std::size_t>(LocalRandom(0.0, static_cast<double>(kinds)))) : 0};
|
|
for (int index = 0; index < m_state.count && kinds > 0; ++index)
|
|
{
|
|
auto const &definition{m_state.vehicles[(start + static_cast<std::size_t>(index)) % kinds]};
|
|
// a vehicle like any other of the scenery, made where the point is and driven at the speed of the point...
|
|
auto *vehicle{new TDynamicObject()};
|
|
auto const length{vehicle->Init(basename + ":" + std::to_string(++m_made), definition.folder, definition.skin, definition.type, m_lanes.front(), m_offset + front_room, "headdriver",
|
|
m_state.velocity, "", static_cast<float>(definition.load), definition.loadtype, false, "")};
|
|
if (length == 0.0)
|
|
{
|
|
ErrorLog("Bad road point: vehicle \"" + definition.folder + "/" + definition.type + "\" of spawn \"" + m_name + "\" couldn't be made");
|
|
if (vehicle->MyTrack != nullptr)
|
|
{
|
|
vehicle->MyTrack->RemoveDynamicObject(vehicle);
|
|
}
|
|
delete vehicle;
|
|
continue;
|
|
}
|
|
if (vehicle->mdModel != nullptr)
|
|
{
|
|
for (auto const &smokesource : vehicle->mdModel->smoke_sources())
|
|
{
|
|
simulation::Particles.insert(smokesource.first, vehicle, smokesource.second);
|
|
}
|
|
}
|
|
if (false == simulation::Vehicles.insert(vehicle))
|
|
{
|
|
ErrorLog("Bad road point: vehicle name \"" + vehicle->name() + "\" made for spawn \"" + m_name + "\" is taken already");
|
|
}
|
|
pooled_vehicle entry;
|
|
entry.vehicle = vehicle;
|
|
entry.velocity = vehicle->MoverParameters->V;
|
|
m_pool.emplace_back(entry);
|
|
// ...which is taken off the road right away, the way the vehicles leaving the scenery are, to wait for its turn
|
|
if (vehicle->MyTrack != nullptr)
|
|
{
|
|
vehicle->MyTrack->RemoveDynamicObject(vehicle);
|
|
vehicle->MyTrack = nullptr;
|
|
}
|
|
vehicle->bEnabled = false;
|
|
}
|
|
if (m_pool.empty())
|
|
{
|
|
ErrorLog("Bad road point: spawn \"" + m_name + "\" has no vehicles to put on the road");
|
|
}
|
|
}
|
|
|
|
// true if specified vehicle stands in the line of vehicles held by the crossing
|
|
bool roadpoint_node::holds(TDynamicObject const &Vehicle) const
|
|
{
|
|
return m_state.kind == kind_type::crossing && m_closed && false == m_editorremoved && std::find(m_queue.begin(), m_queue.end(), Vehicle.MyTrack) != m_queue.end();
|
|
}
|
|
|
|
std::size_t roadpoint_node::waiting() const
|
|
{
|
|
return static_cast<std::size_t>(std::count_if(m_pool.begin(), m_pool.end(), [](pooled_vehicle const &Entry) { return false == Entry.vehicle->bEnabled && Entry.vehicle->MyTrack == nullptr; }));
|
|
}
|
|
|
|
// to be called with each step of the simulation
|
|
void roadpoint_node::update(double const Deltatime)
|
|
{
|
|
if (m_editorremoved)
|
|
{
|
|
return;
|
|
}
|
|
if (false == m_bound)
|
|
{
|
|
bind();
|
|
}
|
|
switch (m_state.kind)
|
|
{
|
|
case kind_type::crossing:
|
|
{
|
|
update_crossing(Deltatime);
|
|
break;
|
|
}
|
|
case kind_type::spawn:
|
|
{
|
|
update_spawn(Deltatime);
|
|
break;
|
|
}
|
|
case kind_type::despawn:
|
|
{
|
|
update_despawn();
|
|
break;
|
|
}
|
|
case kind_type::crosswalk:
|
|
{
|
|
// paint only
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// closes the crossing while there's a rail vehicle coming, or standing right at it
|
|
void roadpoint_node::update_crossing(double const Deltatime)
|
|
{
|
|
m_scan -= Deltatime;
|
|
m_hold -= Deltatime;
|
|
if (m_scan <= 0.0)
|
|
{
|
|
m_scan = scan_period;
|
|
std::map<TDynamicObject const *, double> seen;
|
|
for (auto const *track : m_rails)
|
|
{
|
|
for (auto const *vehicle : track->Dynamics)
|
|
{
|
|
if (vehicle == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
auto const distance{planar_distance(vehicle->GetPosition(), m_state.position)};
|
|
seen.emplace(vehicle, distance);
|
|
auto const before{m_seen.find(vehicle)};
|
|
// a vehicle getting closer is on its way here; one which stands still or goes away isn't, unless it's at the crossing itself
|
|
if (distance < m_state.clearance + near_zone || (before != m_seen.end() && distance < before->second - 0.01))
|
|
{
|
|
m_hold = hold_time;
|
|
}
|
|
}
|
|
}
|
|
m_seen.swap(seen);
|
|
}
|
|
if ((m_hold > 0.0) != m_closed)
|
|
{
|
|
close(m_hold > 0.0);
|
|
}
|
|
}
|
|
|
|
// tells the drivers whether they can go
|
|
void roadpoint_node::close(bool const Closed)
|
|
{
|
|
m_closed = Closed;
|
|
for (std::size_t idx = 0; idx < m_stops.size() && idx < m_events.size(); ++idx)
|
|
{
|
|
static_cast<stop_event *>(m_events[idx])->m_velocity = (Closed ? 0.0 : -1.0);
|
|
}
|
|
}
|
|
|
|
// puts a waiting vehicle on the road when its time comes
|
|
void roadpoint_node::update_spawn(double const Deltatime)
|
|
{
|
|
if (false == m_prepared)
|
|
{
|
|
// the point was made in the editor, or the scenery was loaded for editing
|
|
prepare();
|
|
}
|
|
// vehicles of the point which got stuck are taken off the road, to wait for their next turn: the ones standing still
|
|
// for too long, unless it's a closed level crossing they wait at, and the ones which started to go backwards
|
|
for (auto &entry : m_pool)
|
|
{
|
|
auto *vehicle{entry.vehicle};
|
|
if (false == vehicle->bEnabled || vehicle->MyTrack == nullptr || (simulation::Train != nullptr && simulation::Train->Dynamic() == vehicle))
|
|
{
|
|
// not on the road, or taken over by the user
|
|
entry.standing = 0.0;
|
|
continue;
|
|
}
|
|
auto const speed{vehicle->MoverParameters->V};
|
|
// forward is the way it went when it was put on the road
|
|
auto const backwards{speed * entry.velocity < 0.0 && std::abs(speed) > reverse_speed};
|
|
if (std::abs(speed) > stuck_speed || simulation::Roadpoints.holds(*vehicle))
|
|
{
|
|
entry.standing = 0.0;
|
|
}
|
|
else
|
|
{
|
|
entry.standing += Deltatime;
|
|
}
|
|
if (backwards || entry.standing > stuck_time)
|
|
{
|
|
WriteLog("Road traffic: vehicle \"" + vehicle->name() + "\" taken off the road, " + (backwards ? "it was going backwards" : "it stood still for too long"));
|
|
entry.standing = 0.0;
|
|
vehicle->bEnabled = false;
|
|
TDynamicObject::bDynamicRemove = true;
|
|
}
|
|
}
|
|
if (m_pool.empty() || m_lanes.empty())
|
|
{
|
|
return;
|
|
}
|
|
m_timer -= Deltatime;
|
|
if (m_timer > 0.0)
|
|
{
|
|
return;
|
|
}
|
|
// not with something in the way
|
|
auto const *lane{m_lanes.front()};
|
|
for (auto const *track : {lane, static_cast<TTrack const *>(lane->trPrev), static_cast<TTrack const *>(lane->trNext)})
|
|
{
|
|
if (track == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
for (auto const *other : track->Dynamics)
|
|
{
|
|
if (other != nullptr && planar_distance(other->GetPosition(), m_state.position) < clear_zone)
|
|
{
|
|
m_timer = retry_period;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// one of the vehicles which wait, picked at random
|
|
std::vector<pooled_vehicle const *> ready;
|
|
for (auto const &entry : m_pool)
|
|
{
|
|
if (false == entry.vehicle->bEnabled && entry.vehicle->MyTrack == nullptr)
|
|
{
|
|
ready.emplace_back(&entry);
|
|
}
|
|
}
|
|
if (ready.empty())
|
|
{
|
|
m_timer = retry_period;
|
|
return;
|
|
}
|
|
auto const &entry{*ready[std::min(ready.size() - 1, static_cast<std::size_t>(LocalRandom(0.0, static_cast<double>(ready.size()))))]};
|
|
auto *vehicle{entry.vehicle};
|
|
// it comes back the way it was made: whole, at speed, with the driver told to drive on
|
|
vehicle->bEnabled = true;
|
|
vehicle->MoverParameters->DamageFlag = 0;
|
|
vehicle->MoverParameters->EngDmgFlag = 0;
|
|
vehicle->MoverParameters->V = entry.velocity;
|
|
vehicle->place_on_track(m_lanes.front(), m_offset + 0.5 * vehicle->MoverParameters->Dim.L, false);
|
|
if (vehicle->Mechanik != nullptr)
|
|
{
|
|
// the same order a vehicle put in the scenery gets; it has the driver forget the road they knew
|
|
vehicle->Mechanik->PutCommand("Timetable:", m_state.velocity, 0, nullptr);
|
|
}
|
|
m_timer = m_state.interval * (1.0 + m_state.variation * LocalRandom(-1.0, 1.0));
|
|
}
|
|
|
|
// takes away the road vehicles which got to the point
|
|
void roadpoint_node::update_despawn()
|
|
{
|
|
for (auto const *lane : m_lanes)
|
|
{
|
|
for (auto *vehicle : lane->Dynamics)
|
|
{
|
|
if (vehicle == nullptr || false == vehicle->bEnabled || vehicle->MoverParameters->CategoryFlag != 2)
|
|
{
|
|
continue;
|
|
}
|
|
if (simulation::Train != nullptr && simulation::Train->Dynamic() == vehicle)
|
|
{
|
|
// not the one the user sits in
|
|
continue;
|
|
}
|
|
if (planar_distance(vehicle->GetPosition(), m_state.position) > m_state.radius + 0.5 * vehicle->MoverParameters->Dim.L)
|
|
{
|
|
continue;
|
|
}
|
|
// the way vehicles reaching the end of the scenery go: marked here, taken off the road once all vehicles made their move
|
|
vehicle->bEnabled = false;
|
|
TDynamicObject::bDynamicRemove = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// generates geometry of the stop lines
|
|
std::vector<scene::shape_node> roadpoint_node::create_shapes() const
|
|
{
|
|
std::vector<scene::shape_node> shapes;
|
|
std::vector<world_vertex> vertices;
|
|
glm::dvec3 const lift{0.0, line_lift, 0.0};
|
|
glm::vec3 const upwards{0.f, 1.f, 0.f};
|
|
if (m_state.kind == kind_type::crosswalk && m_onroad)
|
|
{
|
|
// stripes laid along the road, side by side from one edge of it to the other
|
|
glm::dvec3 across{m_along.z, 0.0, -m_along.x};
|
|
if (glm::length2(across) > 1e-12)
|
|
{
|
|
across = glm::normalize(across);
|
|
auto const reach{m_halfwidth - stopline_inset};
|
|
auto const stripes{std::max(1, static_cast<int>(std::floor((2.0 * reach + stripe_width) / (2.0 * stripe_width))))};
|
|
// the stripes are spread evenly over the width, with one at each edge
|
|
auto const first{-0.5 * (stripes * 2 - 1) * stripe_width};
|
|
auto const back{-m_along * static_cast<double>(m_state.length)};
|
|
for (int stripe = 0; stripe < stripes; ++stripe)
|
|
{
|
|
auto const offset{first + stripe * 2.0 * stripe_width};
|
|
auto const right{m_state.position + across * offset + m_along * (0.5 * m_state.length) + lift};
|
|
auto const left{right + across * stripe_width};
|
|
vertices.push_back({right + back, upwards, {0.f, 0.f}});
|
|
vertices.push_back({right, upwards, {1.f, 0.f}});
|
|
vertices.push_back({left + back, upwards, {0.f, 1.f}});
|
|
vertices.push_back({right, upwards, {1.f, 0.f}});
|
|
vertices.push_back({left, upwards, {1.f, 1.f}});
|
|
vertices.push_back({left + back, upwards, {0.f, 1.f}});
|
|
}
|
|
}
|
|
}
|
|
for (auto const &stop : m_stops)
|
|
{
|
|
if (m_state.kind != kind_type::crossing || false == m_state.stoplines)
|
|
{
|
|
break;
|
|
}
|
|
// a line across the lane, ending where the vehicles are to stop
|
|
glm::dvec3 across{stop.direction.z, 0.0, -stop.direction.x};
|
|
if (glm::length2(across) < 1e-12)
|
|
{
|
|
continue;
|
|
}
|
|
across = glm::normalize(across);
|
|
glm::dvec3 back{-stop.direction.x, 0.0, -stop.direction.z};
|
|
back = glm::normalize(back) * stopline_width;
|
|
auto const reach{std::max(0.25, 0.5 * stop.width - stopline_inset)};
|
|
auto const right{stop.position - across * reach + lift};
|
|
auto const left{stop.position + across * reach + lift};
|
|
vertices.push_back({right + back, upwards, {0.f, 0.f}});
|
|
vertices.push_back({right, upwards, {1.f, 0.f}});
|
|
vertices.push_back({left + back, upwards, {0.f, 1.f}});
|
|
vertices.push_back({right, upwards, {1.f, 0.f}});
|
|
vertices.push_back({left, upwards, {1.f, 1.f}});
|
|
vertices.push_back({left + back, upwards, {0.f, 1.f}});
|
|
}
|
|
if (false == vertices.empty())
|
|
{
|
|
lighting_data paint;
|
|
paint.diffuse = glm::vec4{0.92f, 0.92f, 0.92f, 1.f};
|
|
paint.ambient = paint.diffuse;
|
|
scene::shape_node shape;
|
|
shape.make_terrain(GfxRenderer->Fetch_Material("colored"), std::move(vertices), glm::dvec3{0.0});
|
|
shape.lighting(paint);
|
|
shapes.emplace_back(std::move(shape));
|
|
}
|
|
return shapes;
|
|
}
|
|
|
|
// puts geometry of the point in the scene as shapes of its own, which can be taken back
|
|
void roadpoint_node::show()
|
|
{
|
|
if (m_shapes.shown() || m_editorremoved || simulation::Region == nullptr || Global.NvRenderer)
|
|
{
|
|
// NOTE: the experimental renderer draws only what the scenery had when it was loaded
|
|
return;
|
|
}
|
|
m_shapes.show(create_shapes(), m_state.position);
|
|
}
|
|
|
|
void roadpoint_node::hide()
|
|
{
|
|
m_shapes.hide();
|
|
}
|
|
|
|
// radius() subclass details, calculates node's bounding radius
|
|
float roadpoint_node::radius_()
|
|
{
|
|
return m_state.kind == kind_type::crossing ? m_state.clearance + 5.f : m_state.kind == kind_type::crosswalk ? static_cast<float>(m_halfwidth) + m_state.length : m_state.radius;
|
|
}
|
|
|
|
// serialize() subclass details, sends content of the subclass to provided stream
|
|
void roadpoint_node::serialize_(std::ostream &Output) const
|
|
{
|
|
// TODO: implement
|
|
}
|
|
|
|
// deserialize() subclass details, restores content of the subclass from provided stream
|
|
void roadpoint_node::deserialize_(std::istream &Input)
|
|
{
|
|
// TODO: implement
|
|
}
|
|
|
|
// export() subclass details, sends basic content of the class in legacy (text) format to provided stream
|
|
void roadpoint_node::export_as_text_(std::ostream &Output) const
|
|
{
|
|
auto const type{keyword(m_state.kind)};
|
|
Output << type << ' ';
|
|
auto const precision{Output.precision(std::numeric_limits<double>::digits10)};
|
|
Output << m_state.position.x << ' ' << m_state.position.y << ' ' << m_state.position.z << ' ';
|
|
Output.precision(precision);
|
|
switch (m_state.kind)
|
|
{
|
|
case kind_type::crossing:
|
|
{
|
|
Output << "clearance " << m_state.clearance << ' ' << "warning " << m_state.warning << ' ' << "stoplines " << (m_state.stoplines ? "yes" : "no") << ' ';
|
|
break;
|
|
}
|
|
case kind_type::spawn:
|
|
{
|
|
Output << "interval " << m_state.interval << ' ' << "variation " << m_state.variation << ' ' << "velocity " << m_state.velocity << ' ' << "count " << m_state.count << ' ';
|
|
for (auto const &vehicle : m_state.vehicles)
|
|
{
|
|
// the way vehicles are put in a scenery. the path and the offset are there to keep that form, the point doesn't use them
|
|
Output << "node -1 0 none dynamic " << vehicle.folder << ' ' << vehicle.skin << ' ' << vehicle.type << " none 0 headdriver " << m_state.velocity << ' ' << vehicle.load << ' ';
|
|
if (vehicle.load > 0 && false == vehicle.loadtype.empty())
|
|
{
|
|
Output << vehicle.loadtype << ' ';
|
|
}
|
|
Output << "enddynamic ";
|
|
}
|
|
break;
|
|
}
|
|
case kind_type::despawn:
|
|
{
|
|
Output << "radius " << m_state.radius << ' ';
|
|
break;
|
|
}
|
|
case kind_type::crosswalk:
|
|
{
|
|
Output << "length " << m_state.length << ' ';
|
|
break;
|
|
}
|
|
}
|
|
Output << "end" << type << "\n";
|
|
}
|
|
|
|
// legacy style initialization, to be performed when the lanes of the roads are tied together
|
|
void roadpoint_table::InitRoadpoints()
|
|
{
|
|
for (auto *point : m_items)
|
|
{
|
|
if (point == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
point->bind();
|
|
if (false == Global.editor_session)
|
|
{
|
|
// the vehicles are made now, so they don't hold the simulation up later. a scenery opened for editing
|
|
// does without them until the simulation is run
|
|
point->prepare();
|
|
}
|
|
}
|
|
}
|
|
|
|
// lets go of the lanes and the rails, ahead of a change of the roads
|
|
void roadpoint_table::unbind()
|
|
{
|
|
for (auto *point : m_items)
|
|
{
|
|
if (point != nullptr)
|
|
{
|
|
point->unbind();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ties all points anew, after the roads were changed
|
|
void roadpoint_table::bind()
|
|
{
|
|
for (auto *point : m_items)
|
|
{
|
|
if (point == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
// the stop lines go where the lanes are now
|
|
point->hide();
|
|
point->bind();
|
|
if (m_geometry)
|
|
{
|
|
point->show();
|
|
}
|
|
}
|
|
}
|
|
|
|
// puts geometry of the points in the scene
|
|
void roadpoint_table::create_geometry()
|
|
{
|
|
m_geometry = true;
|
|
for (auto *point : m_items)
|
|
{
|
|
if (point != nullptr)
|
|
{
|
|
point->show();
|
|
}
|
|
}
|
|
}
|
|
|
|
// true if specified vehicle stands in the line of vehicles held by a closed level crossing
|
|
bool roadpoint_table::holds(TDynamicObject const &Vehicle) const
|
|
{
|
|
return std::any_of(m_items.begin(), m_items.end(), [&Vehicle](roadpoint_node const *Point) { return Point != nullptr && Point->holds(Vehicle); });
|
|
}
|
|
|
|
// to be called with each step of the simulation
|
|
void roadpoint_table::update(double const Deltatime)
|
|
{
|
|
for (auto *point : m_items)
|
|
{
|
|
if (point != nullptr)
|
|
{
|
|
point->update(Deltatime);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|