17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-05 05:14:27 +01:00

feat(roads): right of way and lane turns at junctions, road weights, kerbs, medians and forking roads, banks to the ground, roundabouts, pedestrian crossings, spawn sets by count; fix(editor): model skin export, placement on the ground under the orthophoto

This commit is contained in:
2026-10-04 01:55:35 +02:00
parent 3ce9102da0
commit 27a94509c4
17 changed files with 2198 additions and 244 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,7 @@ namespace scene
struct scratch_data;
class basic_section;
}
class road_order;
// geometry a node puts in the scene as shapes of its own, apart from the shapes of the scenery, so it can take it back
class owned_shapes
@@ -54,6 +55,9 @@ class owned_shapes
// deal with a road without knowing about it.
// scenery entry:
// node <max> <min> <name> road <axis, laid out like a path of a track> [<property> <values>]... endroad
// properties, past the ones of the lanes and the look: median <gap|painted|island> <width at the start> <width at the end> <material>,
// kerbs <left|right|both> <width> <material>, bank <left|right|both> <width at the start> <drop at the start> <width at the end> <drop at the end>,
// bankmaterial <material>, weight <value>, roundabout
class road_node : public scene::basic_node
{
@@ -90,6 +94,26 @@ class road_node : public scene::basic_node
float width{0.f};
std::string material;
};
// what keeps the two directions of a road apart, where they're led away from each other
enum class median_type
{
gap, // nothing: the ground shows between two roadways
painted, // the surface carries on, marked as closed to the traffic
island // raised on a kerb
};
struct median_data
{
median_type type{median_type::painted};
std::array<float, 2> width{0.f, 0.f}; // at the start and at the end of the axis
std::string material{"none"}; // what an island is covered with
};
// slope leading from the edge of the road down, or up, to the ground
struct bank_data
{
bool set{false}; // false: a shoulder gets the bank the road gives to all its shoulders, anything else gets none
std::array<float, 2> width{0.f, 0.f}; // at the start and at the end of the axis
std::array<float, 2> drop{0.f, 0.f}; // how far below the edge of the road it ends; negative if it rises
};
// everything the scenery says about the road. the lanes go left to right when facing along the axis:
// the ones going against it, outermost first, then the ones going along it
struct state
@@ -112,6 +136,14 @@ class road_node : public scene::basic_node
float sounddistance{25.f};
int quality{15};
std::string environment{"flat"};
float weight{100.f}; // how willing the drivers are to take the road when leaving a junction, next to the other roads they can take there
median_data median;
std::array<bool, 2> kerbs{false, false}; // left, right: a kerb runs along the edge of the surface, as high as the kerb of a sidewalk
float kerbwidth{0.15f};
std::string kerbmaterial{"none"};
std::array<bank_data, 2> banks; // left, right
std::string bankmaterial{"none"}; // what a bank is covered with, other than one of a shoulder, which goes with the shoulder
bool roundabout{false}; // the piece is a part of a roundabout: traffic on it goes ahead of the traffic joining it
// brings the content to a usable form: values within their limits, an entry for each lane and for each pair of neighbours
void normalize();
@@ -127,6 +159,14 @@ class road_node : public scene::basic_node
double width() const;
// what the widths of the lanes are multiplied by at specified value of the curve parameter
double scale(double const T) const;
// true if the two directions of the road are kept apart anywhere along it
bool divided() const;
// how far apart the two directions are at specified value of the curve parameter
double median_width(double const T) const;
// distance from the axis of the line the two directions meet at, on a road which doesn't keep them apart
double divide() const;
// width of the whole roadway at specified value of the curve parameter: the lanes and what's between the directions
double span(double const T) const;
// shape of the middle of a lane, laid out in the direction of travel
segment_data lane_path(std::size_t const Lane) const;
// length of the axis
@@ -211,13 +251,32 @@ class road_node : public scene::basic_node
// scenery entry:
// node <max> <min> <name> junction <centre> arm <position> <direction x z> <lanes in> <lanes out> <lane width> ... [<property> <values>]... endjunction
// properties: surface <material>, texlength <m>, markings <white|orange|none>, side <none|shoulder|sidewalk> <width> <material>, kerb <height>,
// slope <width> <drop>, stopline <number of an arm, counted from 1>, velocity <km/h>, friction <value>, environment <name>
// the arms don't have to be level with each other: the surface is spanned between their ends and the centre
// slope <width> <drop>, stopline <number of an arm, counted from 1>, velocity <km/h>, friction <value>, environment <name>,
// priority <arm> <none|main|yield|stop>, turns <arm> <lane leading in, counted from the middle of the road> <letters out of l, s, r>,
// armmedian <arm> <width of what keeps the two directions of the road apart there>, kerbs <width> <material>
// the arms don't have to be level with each other: the surface is spanned between their ends and the centre.
// the junction also tells the vehicles when to wait: the ones which have to give way wait for the ones with the right of way,
// and where neither has it, for the ones coming from their right, or from the opposite side when turning left across their way
class junction_node : public scene::basic_node
{
public:
// types
// what the vehicles coming by a road are to do about the ones coming by the other roads
enum class right_of_way
{
none, // the rule of the right hand
priority, // the others give way
yield, // gives way
stop // stops, then gives way
};
// ways a lane can be left by
enum turn_flags : int
{
turn_left = 1,
turn_straight = 2,
turn_right = 4
};
struct arm_data
{
glm::dvec3 position{0.0}; // middle of the end of the road
@@ -226,6 +285,46 @@ class junction_node : public scene::basic_node
int outgoing{1}; // lanes leading out of it
float width{3.5f}; // width of a lane
bool stopline{false}; // a line is painted across the lanes leading into the junction
float median{0.f}; // how far apart the two directions of the road are there
right_of_way priority{right_of_way::none};
// ways each lane leading into the junction can be left by, a sum of turn_flags; the lanes are counted from the middle of the road.
// a lane with no entry, or with 0, gets what the junction works out: the inner lane takes the left turns, the outer one the right turns
std::vector<int> turns;
};
// a way through the junction from a lane leading into it
struct way_data
{
std::size_t exit{0}; // arm it leaves by
int kind{turn_straight}; // one of turn_flags
int code{0}; // what a driver calls it, once they picked it
TTrack *track{nullptr}; // lane it leads to
segment_data path;
std::vector<std::pair<std::size_t, std::size_t>> conflicts; // ways it can't be taken along with: lane leading in, and a way from that lane
};
// a lane leading into the junction, with what its traffic is told
struct gate_data
{
std::size_t arm{0};
int lane{1};
TTrack *entry{nullptr}; // the lane
TTrack *link{nullptr}; // path, or crossroads of paths, leading on from it
std::vector<way_data> ways;
std::vector<TTrack *> approach; // lanes the vehicles heading for the junction are looked for on, nearest first
glm::dvec3 line{0.0}; // where the lane ends
road_order *order{nullptr};
// what's going on at the moment
TDynamicObject *first{nullptr}; // vehicle nearest to the junction
double distance{0.0}; // of that vehicle from the end of the lane
double speed{0.0};
double halflength{0.0};
unsigned taken{0}; // ways that vehicle may take, a bit for each
unsigned inside{0}; // ways with a vehicle on them
bool active{false}; // the vehicle is near enough to matter
bool blocked{false}; // it's told to wait
bool committed{false}; // it was let through, the others wait for it
bool stopped{false}; // it made its stop at the stop sign
double waited{0.0}; // how long it's been standing there
double idle{0.0}; // how long it's been standing there since it was let through
};
struct state
{
@@ -235,6 +334,9 @@ class junction_node : public scene::basic_node
float texturelength{4.f};
road_node::marking_colour markings{road_node::marking_colour::white};
road_node::side_data side; // what the corners between the roads are lined with, the way the sides of a road are
bool kerbs{false}; // a kerb runs around the corners, the way it runs along the edges of a road
float kerbwidth{0.15f};
std::string kerbmaterial{"none"};
float kerbheight{0.12f};
glm::vec2 slope{1.f, 0.4f}; // width and drop of the bank which closes a shoulder
float velocity{30.f}; // speed limit on the way through
@@ -280,6 +382,13 @@ class junction_node : public scene::basic_node
}
// true if there's a vehicle on any path of the junction
bool occupied() const;
// tells the vehicles heading for the junction whether they can drive on. to be called with each step of the simulation
void update(double const Deltatime);
// lanes leading into the junction, with what their traffic is told at the moment
std::vector<gate_data> const &gates() const
{
return m_gates;
}
// generates geometry of the surface
std::vector<scene::shape_node> create_shapes() const;
// puts geometry of the junction in the scene as shapes of its own, or takes it back
@@ -304,10 +413,16 @@ class junction_node : public scene::basic_node
void export_as_text_(std::ostream &Output) const override;
// creates a path through the junction from provided definition and registers it with the simulation
TTrack *create_track(std::string const &Definition, std::size_t const Index);
// sets up what the traffic of each lane leading in is told, once the ways through are made
void create_gates();
// members
state m_state;
std::vector<TTrack *> m_links;
std::vector<segment_data> m_movements;
std::vector<gate_data> m_gates;
std::vector<road_order *> m_orders; // orders for the drivers, used again each time the gates are made. never freed, as a driver can hold on to one
double m_scan{0.0}; // time left to the next look at the traffic
bool m_quiet{false}; // there was no traffic the last time
bool m_merged{false};
owned_shapes m_shapes;
};
@@ -330,6 +445,8 @@ class junction_table : public basic_table<junction_node>
public:
// legacy style initialization, to be performed when the tracks are joined, ahead of the roads
void InitJunctions();
// to be called with each step of the simulation
void update(double const Deltatime);
// generates geometry of the junctions and puts it in the scene
void create_geometry(scene::scratch_data &Scratchpad);
};

60
world/RoadOrder.h Normal file
View File

@@ -0,0 +1,60 @@
/*
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 <string>
#include <glm/glm.hpp>
#include "world/Event.h"
// an order for the drivers of road vehicles heading for a place they may have to wait at: a closed level crossing,
// a junction they have to give way at. to them it's what a signal is to the driver of a train. it doesn't go to
// the event manager and never runs: the drivers find it on the lane they scan, and ask it what speed it allows
class road_order : public basic_event
{
public:
road_order()
{
m_passive = true;
}
void init() override {}
std::string input_text() const override
{
return "SetVelocity";
}
TCommandType input_command() const override
{
return TCommandType::cm_SetVelocity;
}
double input_value(int const Index) const override
{
return m_velocity;
}
glm::dvec3 input_location() const override
{
return m_location;
}
// members
double m_velocity{-1.0}; // 0: stop, -1: no limit
glm::dvec3 m_location{0.0};
private:
std::string type() const override
{
return "roadorder";
}
void deserialize_(cParser &Input, scene::scratch_data &Scratchpad) override {}
void run_() override {}
void export_as_text_(std::ostream &Output) const override {}
};
//---------------------------------------------------------------------------

View File

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include "world/Track.h"
#include "world/Event.h"
#include "world/RoadOrder.h"
#include "vehicle/DynObj.h"
#include "vehicle/Driver.h"
#include "vehicle/Train.h"
@@ -37,6 +38,11 @@ constexpr double near_zone{30.0}; // a rail vehicle this close to the stop lines
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
@@ -154,46 +160,34 @@ bool out_of_reach(segment_data const &Path, glm::dvec3 const &Point, double cons
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; to them it's what a signal showing stop is to the driver of a train.
// it doesn't go to the event manager and never runs: the drivers find it on the lane they scan, and ask it what speed it allows
class stop_event : public basic_event
// 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)
{
public:
stop_event()
road_node const *result{nullptr};
auto best{std::numeric_limits<double>::max()};
for (auto const *road : simulation::Roads.sequence())
{
m_passive = true;
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;
}
void init() override {}
std::string input_text() const override
{
return "SetVelocity";
}
TCommandType input_command() const override
{
return TCommandType::cm_SetVelocity;
}
double input_value(int const Index) const override
{
return m_velocity;
}
glm::dvec3 input_location() const override
{
return m_location;
}
// members
double m_velocity{-1.0}; // 0: stop, -1: no limit
glm::dvec3 m_location{0.0};
private:
std::string type() const override
{
return "roadstop";
}
void deserialize_(cParser &Input, scene::scratch_data &Scratchpad) override {}
void run_() override {}
void export_as_text_(std::ostream &Output) const override {}
};
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)
@@ -242,24 +236,25 @@ void roadpoint_node::state::normalize()
interval = std::clamp(interval, 1.f, 3600.f);
variation = std::clamp(variation, 0.f, 1.f);
velocity = std::clamp(velocity, 5.f, 200.f);
copies = std::clamp(copies, 1, 16);
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 : kind_type::crossing);
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" : "crossing";
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";
return Type == "crossing" || Type == "spawn" || Type == "despawn" || Type == "crosswalk";
}
// reads vehicles out of a text
@@ -341,6 +336,8 @@ void roadpoint_node::import(cParser &Input, glm::dvec3 const &Offset)
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)
@@ -374,10 +371,21 @@ void roadpoint_node::import(cParser &Input, glm::dvec3 const &Offset)
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 >> m_state.copies;
Input >> copies;
}
else if (token == "length")
{
Input.getTokens();
Input >> m_state.length;
}
else if (token == "radius")
{
@@ -441,6 +449,10 @@ void roadpoint_node::import(cParser &Input, glm::dvec3 const &Offset)
}
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);
}
@@ -449,10 +461,10 @@ void roadpoint_node::import(cParser &Input, glm::dvec3 const &Offset)
void roadpoint_node::define(state const &State)
{
auto const vehicles{m_state.vehicles};
auto const copies{m_state.copies};
auto const count{m_state.count};
m_state = State;
m_state.normalize();
if (m_prepared && (m_state.vehicles != vehicles || m_state.copies != copies))
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
@@ -492,6 +504,24 @@ void roadpoint_node::bind()
}
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
@@ -525,25 +555,8 @@ void roadpoint_node::bind()
void roadpoint_node::bind_crossing()
{
// the road the crossing is on: the one whose axis passes nearest
road_node const *crossed{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, m_state.position, 50.0))
{
continue;
}
auto const &layout{road->definition()};
path_curve const axis{layout.axis};
double distance{0.0};
auto const t{axis.nearest(m_state.position, distance)};
if (distance > 0.5 * layout.width() * layout.scale(t) + 1.0 || std::abs(axis.point(t).y - m_state.position.y) > 3.0 || distance >= best)
{
continue;
}
best = distance;
crossed = road;
}
double where{0.0};
auto const *crossed{road_under(m_state.position, where)};
if (crossed != nullptr)
{
auto const &lanes{crossed->tracks()};
@@ -659,6 +672,24 @@ void roadpoint_node::bind_crossing()
{
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);
}
@@ -683,8 +714,10 @@ void roadpoint_node::unbind()
}
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
@@ -709,47 +742,48 @@ void roadpoint_node::prepare()
// 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};
for (auto const &definition : m_state.vehicles)
// 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)
{
for (int copy = 0; copy < m_state.copies; ++copy)
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)
{
// 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
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);
vehicle->MyTrack = nullptr;
}
vehicle->bEnabled = false;
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())
{
@@ -757,6 +791,12 @@ void roadpoint_node::prepare()
}
}
// 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; }));
@@ -790,6 +830,11 @@ void roadpoint_node::update(double const Deltatime)
update_despawn();
break;
}
case kind_type::crosswalk:
{
// paint only
break;
}
}
}
@@ -846,6 +891,36 @@ void roadpoint_node::update_spawn(double const Deltatime)
// 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;
@@ -933,15 +1008,41 @@ void roadpoint_node::update_despawn()
std::vector<scene::shape_node> roadpoint_node::create_shapes() const
{
std::vector<scene::shape_node> shapes;
if (m_state.kind != kind_type::crossing || false == m_state.stoplines || m_stops.empty())
{
return 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)
@@ -993,7 +1094,7 @@ void roadpoint_node::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.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
@@ -1025,7 +1126,7 @@ void roadpoint_node::export_as_text_(std::ostream &Output) const
}
case kind_type::spawn:
{
Output << "interval " << m_state.interval << ' ' << "variation " << m_state.variation << ' ' << "velocity " << m_state.velocity << ' ' << "copies " << m_state.copies << ' ';
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
@@ -1043,6 +1144,11 @@ void roadpoint_node::export_as_text_(std::ostream &Output) const
Output << "radius " << m_state.radius << ' ';
break;
}
case kind_type::crosswalk:
{
Output << "length " << m_state.length << ' ';
break;
}
}
Output << "end" << type << "\n";
}
@@ -1110,6 +1216,12 @@ void roadpoint_table::create_geometry()
}
}
// 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)
{

View File

@@ -24,17 +24,20 @@ http://mozilla.org/MPL/2.0/.
class TDynamicObject;
class basic_event;
// a place on a road where something happens to the traffic. three kinds of it:
// a place on a road where something happens to the traffic, or is painted for 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.
// - a point where vehicles are taken away. the ones which came from a point of the previous kind go back to its set,
// - a pedestrian crossing: stripes painted across the road.
// 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> spawn <position> [interval <s>] [variation <0..1>] [velocity <km/h>] [count <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
// node <max> <min> <name> crosswalk <position> [length <m>] endcrosswalk
// 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
// is up to the point, so the path, the offset, the driver and the velocity of these entries don't matter. the listed vehicles
// are what the point draws from to have <count> vehicles of its own on the roads; one which gets stuck is taken off the road
class roadpoint_node : public scene::basic_node
{
@@ -44,7 +47,8 @@ class roadpoint_node : public scene::basic_node
{
crossing,
spawn,
despawn
despawn,
crosswalk
};
struct vehicle_data
{
@@ -71,10 +75,12 @@ class roadpoint_node : public scene::basic_node
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
int count{6}; // how many vehicles of the point can be on the roads at a time. that many are made ready, drawn from the listed ones
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
// pedestrian crossing
float length{4.f}; // how much of the road the stripes take along it
// brings the content to a usable form
void normalize();
@@ -135,6 +141,8 @@ class roadpoint_node : public scene::basic_node
return m_pool.size();
}
std::size_t waiting() const;
// true if specified vehicle stands in the line of vehicles held by the crossing, which is closed
bool holds(TDynamicObject const &Vehicle) 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
@@ -153,6 +161,7 @@ class roadpoint_node : public scene::basic_node
{
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
double standing{0.0}; // how long it's been standing still on the road
};
// methods
float radius_() override;
@@ -171,6 +180,7 @@ class roadpoint_node : public scene::basic_node
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::vector<TTrack *> m_queue; // lanes the vehicles held by the crossing wait on
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
@@ -182,6 +192,10 @@ class roadpoint_node : public scene::basic_node
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
// pedestrian crossing: the road at the point
bool m_onroad{false};
glm::dvec3 m_along{0.0, 0.0, 1.0}; // the way the road goes
double m_halfwidth{0.0}; // of the road
owned_shapes m_shapes;
};
@@ -199,6 +213,8 @@ class roadpoint_table : public basic_table<roadpoint_node>
void create_geometry();
// to be called with each step of the simulation
void update(double const Deltatime);
// true if specified vehicle stands in the line of vehicles held by a closed level crossing
bool holds(TDynamicObject const &Vehicle) const;
private:
bool m_geometry{false}; // the scene is ready to take geometry of the points

View File

@@ -1942,6 +1942,29 @@ bool TTrack::SwitchForced(int i, TDynamicObject *o)
return true;
};
// picks one of the routes through a crossroads for a driver heading for it
int TTrack::RouteDraw() const {
auto const count { std::min( RouteCount(), static_cast<int>( m_routeweights.size() ) ) };
auto total { 0.0 };
for( int route = 0; route < count; ++route ) {
total += std::max( 0.f, m_routeweights[ route ] );
}
if( total <= 0.0 ) {
// even odds, the way it's always been for the crossroads of the scenery
return 1 + static_cast<int>( std::floor( Random( static_cast<double>( RouteCount() ) - 0.001 ) ) );
}
auto draw { Random( total ) };
auto last { 1 };
for( int route = 0; route < count; ++route ) {
if( m_routeweights[ route ] <= 0.f ) { continue; }
last = route + 1;
draw -= m_routeweights[ route ];
if( draw < 0.0 ) { break; }
}
return last;
}
int TTrack::CrossSegment(int from, int into)
{ // ustawienie wskaźnika na segement w pożądanym kierunku (into) od strony (from)
// zwraca kod segmentu, z kierunkiem jazdy jako znakiem ±

View File

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include <string>
#include <vector>
#include <deque>
#include <array>
#include <glm/glm.hpp>
#include <glm/mat4x4.hpp>
@@ -187,6 +188,7 @@ public:
std::vector<segment_data> m_paths; // source data for owned paths
bool m_editorremoved { false };
scene::basic_node *m_road { nullptr }; // road or road junction which generated this path. such path is one-way, and isn't saved nor edited on its own
std::array<float, 3> m_routeweights { 0.f, 0.f, 0.f }; // odds of each route through a crossroads being picked by a driver, by the number of the route. all zero: even odds
int iterate_stamp = 0;
// sleepermodel optional parameter -------------------------------------------------
@@ -280,6 +282,8 @@ public:
int
RouteCount() const {
return SwitchExtension != nullptr ? SwitchExtension->iRoads - 1 : 1; }
// picks one of the routes through a crossroads for a driver heading for it. the routes are numbered from 1
int RouteDraw() const;
void Load(cParser *parser, glm::dvec3 const &pOrigin);
void init_segments( bool const Initial );
void update_location();