mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 02:49:19 +02:00
feat(roads): junctions made by leading a road out of another, width and height set at road points
This commit is contained in:
@@ -107,11 +107,7 @@ class editor_mode : public application_mode
|
||||
std::vector<std::pair<TTrack *, editor_track::state>> tracks;
|
||||
std::vector<TTrack *> created;
|
||||
std::vector<TTrack *> removed;
|
||||
std::vector<std::pair<road_node *, road_node::state>> roads; // road pieces with what they were like before the change
|
||||
std::vector<road_node *> roads_created;
|
||||
std::vector<road_node *> roads_removed;
|
||||
std::vector<junction_node *> junctions_created;
|
||||
std::vector<junction_node *> junctions_removed;
|
||||
editor_road::record roads; // what a change of the roads comes to
|
||||
|
||||
};
|
||||
void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string());
|
||||
@@ -489,13 +485,25 @@ class editor_mode : public application_mode
|
||||
bool m_track_drag_connected{true};
|
||||
|
||||
// roads: a menu and a window of their own, independent from the edit modes of the toolset
|
||||
// what a click of the build tool would make
|
||||
struct road_plan
|
||||
{
|
||||
std::vector<segment_data> pieces; // axes of the pieces, in the order they're laid
|
||||
editor_road::loose_end snap; // loose end of a road or arm of a junction the road is led to
|
||||
editor_road::branch start; // junction made, or given another arm, for a road led out of the side of a road or out of a junction
|
||||
bool hasstart{false};
|
||||
editor_road::branch end; // the same for a road led into the side of a road or into a junction
|
||||
bool hasend{false};
|
||||
std::string error; // why there's nothing to make
|
||||
};
|
||||
struct road_tool
|
||||
{
|
||||
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, 2: junction
|
||||
int tool{0}; // 0: select, 1: build
|
||||
road_node *selected{nullptr};
|
||||
junction_node *junction{nullptr}; // selected junction
|
||||
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
|
||||
bool chain{false}; // the point to build from is set
|
||||
@@ -505,16 +513,17 @@ class editor_mode : public application_mode
|
||||
bool reversed{false}; // the pieces are laid from their ends, to carry on a road from its start
|
||||
double grade{0.0}; // slope the next piece has to start with, to carry on what it's attached to
|
||||
bool hasgrade{false};
|
||||
float offset{0.1f}; // height above the ground the roads and junctions are built at
|
||||
// the road being built leaves the side of a road, or a junction between its roads. the point to build from is the centre
|
||||
// of the junction, which gets made, or gets another arm, along with the first piece
|
||||
road_node *branchroad{nullptr};
|
||||
double branchat{0.0}; // value of the curve parameter of the axis of that road
|
||||
junction_node *branchjunction{nullptr};
|
||||
float offset{0.1f}; // height above the ground the roads are built at
|
||||
bool follow{true}; // built roads are led over the ground between the clicked points
|
||||
int kind{0}; // kind of the junctions being placed, an editor_road::junction_kind
|
||||
float crossingspeed{30.f}; // speed limit on the junctions being placed
|
||||
// what a click would make at the moment, worked out once a frame for display
|
||||
std::vector<segment_data> preview;
|
||||
junction_node::state previewjunction;
|
||||
bool hasjunction{false};
|
||||
editor_road::loose_end previewsnap;
|
||||
std::string previewerror;
|
||||
float crossingspeed{30.f}; // speed limit on the junctions being made
|
||||
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};
|
||||
glm::dvec3 mouse{0.0};
|
||||
std::string status;
|
||||
char surface[128]{};
|
||||
@@ -529,19 +538,21 @@ class editor_mode : public application_mode
|
||||
void road_click();
|
||||
void road_select(road_node *Road);
|
||||
void junction_select(junction_node *Junction);
|
||||
void junction_click();
|
||||
bool junction_preview(junction_node::state &State, editor_road::loose_end &Snap, std::string &Error) const;
|
||||
void road_apply();
|
||||
bool road_delete();
|
||||
bool road_split();
|
||||
void road_cancel();
|
||||
// sets the width of the road, or its height, at the selected points
|
||||
void road_point_width(float const Width);
|
||||
void road_point_height(double const Height);
|
||||
bool road_points_apply(std::vector<std::pair<road_node *, road_node::state>> const &Changes, std::size_t const Skipped);
|
||||
// Fresh: the ground is looked up anew instead of going by what was gathered for the previous frames
|
||||
std::vector<segment_data> road_preview(editor_road::loose_end &Snap, std::string &Error, bool const Fresh);
|
||||
void road_profile(std::vector<segment_data> &Pieces, editor_road::loose_end const &Snap, bool const Fresh);
|
||||
road_plan road_preview(bool const Fresh);
|
||||
// Firstgrade, Lastgrade: slopes the road has to start and end with, nullptr for none
|
||||
void road_profile(std::vector<segment_data> &Pieces, double const *Firstgrade, double const *Lastgrade, bool const Fresh);
|
||||
// height of the ground under each of the points, with the roads not taken for the ground. a point with nothing under it keeps its height
|
||||
std::vector<double> ground_heights(std::vector<glm::dvec3> const &Points, bool const Fresh);
|
||||
void push_road_snapshot(std::vector<std::pair<road_node *, road_node::state>> States, std::vector<road_node *> Created, std::vector<road_node *> Removed, std::vector<junction_node *> Junctionscreated = {},
|
||||
std::vector<junction_node *> Junctionsremoved = {});
|
||||
void push_road_snapshot(editor_road::record Record);
|
||||
void restore_road_snapshot(EditorSnapshot const &Snapshot, std::vector<EditorSnapshot> &Opposite, bool const Undo);
|
||||
road_tool m_roadtool;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,9 @@ double const kPieceLength{50.0}; // longest piece the planner makes
|
||||
double const kPieceTurn{glm::radians(30.0)}; // largest turn of a single piece
|
||||
double const kShortestRoad{2.0};
|
||||
double const kTightestRadius{6.0};
|
||||
double const kCornerRoom{4.0}; // length of an arm of a junction past the point where its edge meets the edge of the next one, taken by the rounded corner
|
||||
double const kSharpestBranch{glm::radians(30.0)}; // smallest angle between two roads leaving a junction
|
||||
double const kWidestCorner{glm::radians(120.0)}; // arms further apart than this don't have to make room for each other
|
||||
|
||||
void add_unique(std::vector<road_node *> &Roads, road_node *Road)
|
||||
{
|
||||
@@ -129,6 +132,135 @@ bool turning(road_node const &Road)
|
||||
return false;
|
||||
}
|
||||
|
||||
// definition of a part of a piece, between two values of the curve parameter of its axis
|
||||
road_node::state part(road_node::state const &State, double const From, double const To)
|
||||
{
|
||||
auto result{State};
|
||||
result.axis = subpath(State.axis, From, To);
|
||||
result.taper = {static_cast<float>(State.scale(From)), static_cast<float>(State.scale(To))};
|
||||
return result;
|
||||
}
|
||||
|
||||
int const kStations{64};
|
||||
|
||||
// distances along a path for evenly spaced values of its curve parameter
|
||||
std::vector<double> stations(road_node::state const &State)
|
||||
{
|
||||
std::vector<double> result(kStations + 1, 0.0);
|
||||
auto previous{State.point(0.0)};
|
||||
for (int i = 1; i <= kStations; ++i)
|
||||
{
|
||||
auto const current{State.point(static_cast<double>(i) / kStations)};
|
||||
result[i] = result[i - 1] + glm::distance(previous, current);
|
||||
previous = current;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// distance along a path for specified value of its curve parameter, and the other way around
|
||||
double station_at(std::vector<double> const &Stations, double const T)
|
||||
{
|
||||
auto const position{std::clamp(T, 0.0, 1.0) * kStations};
|
||||
auto const index{std::min(kStations - 1, static_cast<int>(position))};
|
||||
return glm::mix(Stations[index], Stations[index + 1], position - index);
|
||||
}
|
||||
|
||||
double parameter_at(std::vector<double> const &Stations, double const Station)
|
||||
{
|
||||
if (Station <= 0.0)
|
||||
return 0.0;
|
||||
if (Station >= Stations.back())
|
||||
return 1.0;
|
||||
auto const index{static_cast<int>(std::distance(Stations.begin(), std::upper_bound(Stations.begin(), Stations.end(), Station)))};
|
||||
auto const span{Stations[index] - Stations[index - 1]};
|
||||
return (index - 1 + (span > 0.0 ? (Station - Stations[index - 1]) / span : 0.0)) / kStations;
|
||||
}
|
||||
|
||||
// direction seen from above
|
||||
glm::dvec2 planar(glm::dvec3 const &Direction)
|
||||
{
|
||||
glm::dvec2 const result{Direction.x, Direction.z};
|
||||
return glm::length(result) > 1e-9 ? glm::normalize(result) : glm::dvec2{0.0, 1.0};
|
||||
}
|
||||
|
||||
// where a junction made in the middle of a road ends on one of its sides
|
||||
struct road_cut
|
||||
{
|
||||
road_node *road{nullptr};
|
||||
double t{0.0};
|
||||
bool keepstart{false}; // what stays of the piece: from its start up to the cut, or from the cut to its end
|
||||
};
|
||||
|
||||
// follows a road from a point of a piece for specified distance, to find where a junction made at that point ends.
|
||||
// Consumed: receives the pieces past the first one which lie within the distance whole
|
||||
bool follow(road_node &Road, double const T, bool const Forward, double const Distance, road_cut &Cut, std::vector<road_node *> &Consumed, std::string &Error)
|
||||
{
|
||||
auto *piece{&Road};
|
||||
auto from{T};
|
||||
auto forward{Forward};
|
||||
auto remaining{Distance};
|
||||
for (int guard = 0; guard < 64; ++guard)
|
||||
{
|
||||
auto const &state{piece->definition()};
|
||||
auto const lengths{stations(state)};
|
||||
auto const here{station_at(lengths, from)};
|
||||
auto const available{forward ? lengths.back() - here : here};
|
||||
if (remaining <= 0.0 || available >= remaining + kShortestRoad)
|
||||
{
|
||||
// the cut is put here; with nothing left to go the piece is kept whole
|
||||
Cut.road = piece;
|
||||
Cut.t = (remaining <= 0.0 ? from : parameter_at(lengths, forward ? here + remaining : here - remaining));
|
||||
Cut.keepstart = (false == forward);
|
||||
return true;
|
||||
}
|
||||
// the junction takes this piece up to its end, as what would be left of it is too short to be a road
|
||||
remaining = std::max(0.0, remaining - available);
|
||||
if (piece != &Road)
|
||||
Consumed.emplace_back(piece);
|
||||
auto const end{axis_end(state, forward)};
|
||||
if (false == junctions_at(end).empty())
|
||||
{
|
||||
Error = "Too close to a junction";
|
||||
return false;
|
||||
}
|
||||
auto const next{roads_at(end, piece)};
|
||||
if (next.size() != 1)
|
||||
{
|
||||
Error = (next.empty() ? "Too close to the end of the road" : "Too close to a place where roads meet");
|
||||
return false;
|
||||
}
|
||||
if (next.front() == &Road || std::find(Consumed.begin(), Consumed.end(), next.front()) != Consumed.end())
|
||||
{
|
||||
Error = "The road is too short for a junction";
|
||||
return false;
|
||||
}
|
||||
piece = next.front();
|
||||
forward = (glm::distance(axis_end(piece->definition(), false), end) < kSamePoint);
|
||||
from = (forward ? 0.0 : 1.0);
|
||||
}
|
||||
Error = "The road is cut into too many pieces here";
|
||||
return false;
|
||||
}
|
||||
|
||||
// how far from the centre of a junction the edges of two of its roads meet, measured along the first one.
|
||||
// Halfwidth, Otherhalfwidth: of the two roads, Angle: between the ways they leave
|
||||
double edge_reach(double const Halfwidth, double const Otherhalfwidth, double const Angle)
|
||||
{
|
||||
return (Otherhalfwidth + Halfwidth * std::cos(Angle)) / std::sin(Angle);
|
||||
}
|
||||
|
||||
// the look of a junction goes after the road it's made on
|
||||
void dress(junction_node::state &Junction, road_node::state const &Road)
|
||||
{
|
||||
Junction.surface = Road.surface;
|
||||
Junction.texturelength = Road.texturelength;
|
||||
Junction.markings = Road.markings;
|
||||
Junction.friction = Road.friction;
|
||||
Junction.sounddistance = Road.sounddistance;
|
||||
Junction.quality = Road.quality;
|
||||
Junction.environment = Road.environment;
|
||||
}
|
||||
|
||||
void retire(std::vector<TTrack *> const &Tracks)
|
||||
{
|
||||
for (auto *track : Tracks)
|
||||
@@ -441,6 +573,24 @@ void editor_road::revive(std::vector<road_node *> const &Roads)
|
||||
});
|
||||
}
|
||||
|
||||
void editor_road::apply(std::vector<std::pair<junction_node *, junction_node::state>> const &Changes)
|
||||
{
|
||||
std::vector<junction_node *> junctions;
|
||||
for (auto const &change : Changes)
|
||||
add_unique(junctions, change.first);
|
||||
if (junctions.empty())
|
||||
return;
|
||||
refresh({}, junctions, [&]() {
|
||||
for (auto const &change : Changes)
|
||||
{
|
||||
if (change.first == nullptr)
|
||||
continue;
|
||||
change.first->define(change.second);
|
||||
change.first->mark_dirty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<junction_node *> editor_road::create(std::vector<junction_node::state> const &States)
|
||||
{
|
||||
static int counter{0};
|
||||
@@ -538,8 +688,12 @@ road_node *editor_road::split(road_node &Road, double const T)
|
||||
auto const roll{static_cast<float>(path.rolls[0] + (path.rolls[1] - path.rolls[0]) * T)};
|
||||
first.axis.rolls[1] = roll;
|
||||
second.axis.rolls[0] = roll;
|
||||
apply({{&Road, first}});
|
||||
auto const created{create({second})};
|
||||
// the width at the cut is what it was there
|
||||
auto const scale{static_cast<float>(Road.definition().scale(T))};
|
||||
first.taper[1] = scale;
|
||||
second.taper[0] = scale;
|
||||
apply(std::vector<std::pair<road_node *, road_node::state>>{{&Road, first}});
|
||||
auto const created{create(std::vector<road_node::state>{second})};
|
||||
return created.empty() ? nullptr : created.front();
|
||||
}
|
||||
|
||||
@@ -907,39 +1061,238 @@ void editor_road::profile(std::vector<segment_data> &Pieces, std::vector<double>
|
||||
}
|
||||
}
|
||||
|
||||
double editor_road::junction_reach(int const Lanes, float const Width)
|
||||
bool editor_road::nearest_joint(glm::dvec3 const &Point, double const Radius, glm::dvec3 &Joint)
|
||||
{
|
||||
// half of the road, and room for the corners
|
||||
return 0.5 * std::max(1, Lanes) * Width + 6.0;
|
||||
auto best{Radius};
|
||||
bool found{false};
|
||||
for (auto const *road : simulation::Roads.sequence())
|
||||
{
|
||||
if (road == nullptr || road->m_editorremoved)
|
||||
continue;
|
||||
for (auto const atend : {false, true})
|
||||
{
|
||||
auto const position{axis_end(road->definition(), atend)};
|
||||
auto const distance{glm::distance(glm::dvec2{position.x, position.z}, glm::dvec2{Point.x, Point.z})};
|
||||
if (distance > best || std::abs(position.y - Point.y) > 3.0)
|
||||
continue;
|
||||
best = distance;
|
||||
Joint = position;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
junction_node::state editor_road::junction(junction_kind const Kind, glm::dvec3 const &Entry, glm::dvec2 const &Heading, int const Incoming, int const Outgoing, float const Width)
|
||||
std::vector<editor_road::piece_end> editor_road::ends_at(glm::dvec3 const &Point)
|
||||
{
|
||||
junction_node::state state;
|
||||
auto const heading{glm::length(Heading) > 1e-6 ? glm::normalize(Heading) : glm::dvec2{0.0, 1.0}};
|
||||
glm::dvec2 const left{heading.y, -heading.x};
|
||||
auto const reach{junction_reach(Incoming + Outgoing, Width)};
|
||||
auto const spot = [&](glm::dvec2 const &Direction, double const Distance) { return glm::dvec3{Entry.x + Direction.x * Distance, Entry.y, Entry.z + Direction.y * Distance}; };
|
||||
state.centre = spot(heading, reach);
|
||||
auto const arm = [&](glm::dvec2 const &Direction, int const In, int const Out) {
|
||||
junction_node::arm_data data;
|
||||
data.position = glm::dvec3{state.centre.x + Direction.x * reach, Entry.y, state.centre.z + Direction.y * reach};
|
||||
data.direction = Direction;
|
||||
data.incoming = In;
|
||||
data.outgoing = Out;
|
||||
data.width = Width;
|
||||
state.arms.emplace_back(data);
|
||||
};
|
||||
// the road the junction is entered by, then the others: what comes in by one leaves by them
|
||||
arm(-heading, Incoming, Outgoing);
|
||||
if (Kind == junction_kind::cross || Kind == junction_kind::tee_left || Kind == junction_kind::tee_right)
|
||||
arm(heading, Outgoing, Incoming);
|
||||
if (Kind == junction_kind::cross || Kind == junction_kind::tee_sides || Kind == junction_kind::tee_left)
|
||||
arm(left, Outgoing, Incoming);
|
||||
if (Kind == junction_kind::cross || Kind == junction_kind::tee_sides || Kind == junction_kind::tee_right)
|
||||
arm(-left, Outgoing, Incoming);
|
||||
state.normalize();
|
||||
return state;
|
||||
std::vector<piece_end> result;
|
||||
for (auto *road : simulation::Roads.sequence())
|
||||
{
|
||||
if (road == nullptr || road->m_editorremoved)
|
||||
continue;
|
||||
for (auto const atend : {false, true})
|
||||
if (glm::distance(axis_end(road->definition(), atend), Point) < kSamePoint)
|
||||
result.push_back({road, atend});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool editor_road::at_junction(glm::dvec3 const &Point)
|
||||
{
|
||||
return false == junctions_at(Point).empty();
|
||||
}
|
||||
|
||||
bool editor_road::lane_width(road_node::state const &State, double const T, float &Width)
|
||||
{
|
||||
if (State.lanes.empty())
|
||||
return false;
|
||||
for (auto const &lane : State.lanes)
|
||||
if (std::abs(lane.width - State.lanes.front().width) > 0.005f)
|
||||
return false;
|
||||
Width = static_cast<float>(State.lanes.front().width * State.scale(T));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_road::branch_from(road_node &Road, double const T, glm::dvec2 const &Direction, int const Incoming, int const Outgoing, float const Width, branch &Result, std::string &Error)
|
||||
{
|
||||
Result = branch{};
|
||||
auto const &layout{Road.definition()};
|
||||
auto const centre{layout.point(T)};
|
||||
auto const along{planar(layout.tangent(T))};
|
||||
auto const direction{glm::length(Direction) > 1e-9 ? glm::normalize(Direction) : glm::dvec2{0.0, 1.0}};
|
||||
// the sharper the angle the road leaves at, the further from the centre the edges of the roads part
|
||||
auto const angle{std::acos(std::clamp(std::abs(glm::dot(direction, along)), 0.0, 1.0))};
|
||||
if (angle < kSharpestBranch)
|
||||
{
|
||||
Error = "The road would leave at too sharp an angle";
|
||||
return false;
|
||||
}
|
||||
float lanewidth{0.f};
|
||||
if (false == lane_width(layout, T, lanewidth))
|
||||
{
|
||||
Error = "Lanes of \"" + Road.name() + "\" differ in width, and a junction takes roads with lanes of the same width";
|
||||
return false;
|
||||
}
|
||||
auto const halfwidth{0.5 * (layout.forward + layout.backward) * lanewidth};
|
||||
auto const branchhalfwidth{0.5 * (Incoming + Outgoing) * Width};
|
||||
auto const reach{edge_reach(halfwidth, branchhalfwidth, angle) + kCornerRoom};
|
||||
auto const branchreach{edge_reach(branchhalfwidth, halfwidth, angle) + kCornerRoom};
|
||||
|
||||
// where the road is cut on both sides of the centre
|
||||
road_cut cuts[2];
|
||||
std::vector<road_node *> consumed;
|
||||
if (false == follow(Road, T, false, reach, cuts[0], consumed, Error) || false == follow(Road, T, true, reach, cuts[1], consumed, Error))
|
||||
return false;
|
||||
if (cuts[0].road == cuts[1].road && cuts[0].road != &Road)
|
||||
{
|
||||
Error = "The road is too short for a junction";
|
||||
return false;
|
||||
}
|
||||
// what's left of the pieces
|
||||
if (cuts[0].road == &Road && cuts[1].road == &Road)
|
||||
{
|
||||
Result.changes.emplace_back(&Road, part(layout, 0.0, cuts[0].t));
|
||||
Result.added.emplace_back(part(layout, cuts[1].t, 1.0));
|
||||
}
|
||||
else if (cuts[0].road != &Road && cuts[1].road != &Road)
|
||||
{
|
||||
Result.removed.emplace_back(&Road);
|
||||
}
|
||||
for (auto const &cut : cuts)
|
||||
{
|
||||
if (cut.road == &Road && cuts[0].road == cuts[1].road)
|
||||
continue;
|
||||
// a cut at the very end the piece is kept from leaves it whole
|
||||
if (cut.keepstart ? cut.t < 0.999 : cut.t > 0.001)
|
||||
Result.changes.emplace_back(cut.road, cut.keepstart ? part(cut.road->definition(), 0.0, cut.t) : part(cut.road->definition(), cut.t, 1.0));
|
||||
}
|
||||
for (auto *piece : consumed)
|
||||
Result.removed.emplace_back(piece);
|
||||
std::string reason;
|
||||
for (auto const &change : Result.changes)
|
||||
{
|
||||
if (false == can_edit(*change.first, &reason))
|
||||
{
|
||||
Error = "\"" + change.first->name() + "\" can't be changed: " + reason;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto const *piece : Result.removed)
|
||||
{
|
||||
if (false == can_edit(*piece, &reason))
|
||||
{
|
||||
Error = "\"" + piece->name() + "\" can't be changed: " + reason;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// the junction: the road on both its sides, and the one leaving
|
||||
auto &junction{Result.layout};
|
||||
junction.centre = centre;
|
||||
for (auto const &cut : cuts)
|
||||
{
|
||||
auto const &state{cut.road->definition()};
|
||||
junction_node::arm_data arm;
|
||||
arm.position = state.point(cut.t);
|
||||
// the way out of the junction is the way the kept part of the piece goes from the cut
|
||||
arm.direction = planar(state.tangent(cut.t)) * (cut.keepstart ? -1.0 : 1.0);
|
||||
// lanes heading for the cut lead into the junction
|
||||
arm.incoming = (cut.keepstart ? state.forward : state.backward);
|
||||
arm.outgoing = (cut.keepstart ? state.backward : state.forward);
|
||||
if (false == lane_width(state, cut.t, arm.width))
|
||||
{
|
||||
Error = "Lanes of \"" + cut.road->name() + "\" differ in width, and a junction takes roads with lanes of the same width";
|
||||
return false;
|
||||
}
|
||||
junction.arms.emplace_back(arm);
|
||||
}
|
||||
junction_node::arm_data arm;
|
||||
arm.position = centre + glm::dvec3{direction.x, 0.0, direction.y} * branchreach;
|
||||
arm.direction = direction;
|
||||
arm.incoming = Incoming;
|
||||
arm.outgoing = Outgoing;
|
||||
arm.width = Width;
|
||||
junction.arms.emplace_back(arm);
|
||||
Result.arm = junction.arms.size() - 1;
|
||||
dress(junction, layout);
|
||||
junction.normalize();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_road::branch_from(junction_node &Junction, glm::dvec2 const &Direction, int const Incoming, int const Outgoing, float const Width, branch &Result, std::string &Error)
|
||||
{
|
||||
Result = branch{};
|
||||
auto const &layout{Junction.definition()};
|
||||
if (layout.arms.size() >= 4)
|
||||
{
|
||||
Error = "The junction has four roads already";
|
||||
return false;
|
||||
}
|
||||
std::string reason;
|
||||
if (false == can_edit(Junction, &reason))
|
||||
{
|
||||
Error = "\"" + Junction.name() + "\" can't be changed: " + reason;
|
||||
return false;
|
||||
}
|
||||
auto const direction{glm::length(Direction) > 1e-9 ? glm::normalize(Direction) : glm::dvec2{0.0, 1.0}};
|
||||
auto const halfwidth{0.5 * (Incoming + Outgoing) * Width};
|
||||
// the roads which are there stay where they are, so the new one has to fit between them
|
||||
auto reach{kCornerRoom};
|
||||
for (std::size_t idx = 0; idx < layout.arms.size(); ++idx)
|
||||
{
|
||||
auto const &other{layout.arms[idx]};
|
||||
auto const angle{std::acos(std::clamp(glm::dot(direction, other.direction), -1.0, 1.0))};
|
||||
if (angle < kSharpestBranch)
|
||||
{
|
||||
Error = "The road would leave too close to another road of the junction";
|
||||
return false;
|
||||
}
|
||||
if (angle > kWidestCorner)
|
||||
continue;
|
||||
auto const otherhalfwidth{0.5 * layout.arm_width(idx)};
|
||||
auto const otherreach{glm::distance(glm::dvec2{other.position.x, other.position.z}, glm::dvec2{layout.centre.x, layout.centre.z})};
|
||||
if (edge_reach(otherhalfwidth, halfwidth, angle) > otherreach - 0.5)
|
||||
{
|
||||
Error = "There's no room for the road between the roads the junction has; lead it out at a wider angle to them";
|
||||
return false;
|
||||
}
|
||||
reach = std::max(reach, edge_reach(halfwidth, otherhalfwidth, angle) + kCornerRoom);
|
||||
}
|
||||
Result.junction = &Junction;
|
||||
Result.layout = layout;
|
||||
junction_node::arm_data arm;
|
||||
arm.position = layout.centre + glm::dvec3{direction.x, 0.0, direction.y} * reach;
|
||||
arm.direction = direction;
|
||||
arm.incoming = Incoming;
|
||||
arm.outgoing = Outgoing;
|
||||
arm.width = Width;
|
||||
Result.layout.arms.emplace_back(arm);
|
||||
Result.arm = Result.layout.arms.size() - 1;
|
||||
Result.layout.normalize();
|
||||
return true;
|
||||
}
|
||||
|
||||
junction_node *editor_road::carry_out(branch const &Branch, record &Record)
|
||||
{
|
||||
if (Branch.junction != nullptr)
|
||||
{
|
||||
Record.junctions.emplace_back(Branch.junction, Branch.junction->definition());
|
||||
apply(std::vector<std::pair<junction_node *, junction_node::state>>{{Branch.junction, Branch.layout}});
|
||||
return Branch.junction;
|
||||
}
|
||||
// room is made first, so nothing gets joined with what's about to go
|
||||
remove(Branch.removed);
|
||||
Record.roads_removed.insert(Record.roads_removed.end(), Branch.removed.begin(), Branch.removed.end());
|
||||
for (auto const &change : Branch.changes)
|
||||
Record.roads.emplace_back(change.first, change.first->definition());
|
||||
apply(Branch.changes);
|
||||
auto const added{create(Branch.added)};
|
||||
Record.roads_created.insert(Record.roads_created.end(), added.begin(), added.end());
|
||||
auto const junctions{create(std::vector<junction_node::state>{Branch.layout})};
|
||||
if (junctions.empty())
|
||||
return nullptr;
|
||||
Record.junctions_created.emplace_back(junctions.front());
|
||||
return junctions.front();
|
||||
}
|
||||
|
||||
std::vector<std::string> const &editor_road::materials()
|
||||
|
||||
@@ -39,12 +39,37 @@ class editor_road
|
||||
return road != nullptr || junction != nullptr;
|
||||
}
|
||||
};
|
||||
enum class junction_kind
|
||||
// an end of a road piece
|
||||
struct piece_end
|
||||
{
|
||||
cross, // four roads
|
||||
tee_sides, // the road ends, with roads to both sides
|
||||
tee_left, // the road goes on, with a road to the left
|
||||
tee_right // the road goes on, with a road to the right
|
||||
road_node *road{nullptr};
|
||||
bool atend{false}; // the end of its axis rather than the start
|
||||
};
|
||||
// what an operation changed, in the form needed to take it back
|
||||
struct record
|
||||
{
|
||||
std::vector<std::pair<road_node *, road_node::state>> roads; // pieces with what they were like before
|
||||
std::vector<road_node *> roads_created;
|
||||
std::vector<road_node *> roads_removed;
|
||||
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;
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return roads.empty() && roads_created.empty() && roads_removed.empty() && junctions.empty() && junctions_created.empty() && junctions_removed.empty();
|
||||
}
|
||||
};
|
||||
// a road led out of the side of another road, or out of a junction between its arms: the junction this takes,
|
||||
// and what has to be done with the roads to make room for it
|
||||
struct branch
|
||||
{
|
||||
junction_node *junction{nullptr}; // the junction which gets another arm, nullptr if a new one is made
|
||||
junction_node::state layout; // the junction as it's going to be
|
||||
std::size_t arm{0}; // the arm of it made for the road
|
||||
std::vector<std::pair<road_node *, road_node::state>> changes; // pieces cut short, with their new definitions
|
||||
std::vector<road_node::state> added; // part of a piece left past the junction, to be made a piece of its own
|
||||
std::vector<road_node *> removed; // pieces which lie within the junction
|
||||
};
|
||||
// true if the roads can be built and changed at all. optionally explains why they can't
|
||||
static bool available(std::string *Reason = nullptr);
|
||||
@@ -57,6 +82,8 @@ class editor_road
|
||||
static std::vector<road_node *> create(std::vector<road_node::state> const &States);
|
||||
// replaces definitions of the pieces and makes their lanes and geometry anew
|
||||
static void apply(std::vector<std::pair<road_node *, road_node::state>> const &Changes);
|
||||
// replaces definitions of the junctions, and makes their geometry and the ways through them anew
|
||||
static void apply(std::vector<std::pair<junction_node *, junction_node::state>> const &Changes);
|
||||
// takes the pieces out of the scenery. they're kept around, so they can be brought back
|
||||
static void remove(std::vector<road_node *> const &Roads);
|
||||
static void revive(std::vector<road_node *> const &Roads);
|
||||
@@ -72,6 +99,23 @@ class editor_road
|
||||
static road_node *nearest(glm::dvec3 const &Point, double const Margin);
|
||||
// the junction specified point lies on
|
||||
static junction_node *nearest_junction(glm::dvec3 const &Point);
|
||||
// the point where pieces meet or end which is nearest to specified one, within specified distance from it. returns: false if there's none
|
||||
static bool nearest_joint(glm::dvec3 const &Point, double const Radius, glm::dvec3 &Joint);
|
||||
// ends of the pieces which meet at specified point
|
||||
static std::vector<piece_end> ends_at(glm::dvec3 const &Point);
|
||||
// true if there's an arm of a junction at specified point
|
||||
static bool at_junction(glm::dvec3 const &Point);
|
||||
// width shared by all lanes of the road at specified value of the curve parameter of its axis. returns: false if the lanes differ in width
|
||||
static bool lane_width(road_node::state const &State, double const T, float &Width);
|
||||
// lays out a junction for a road leaving the side of specified piece at specified value of the curve parameter of its axis.
|
||||
// Direction: the way the road leaves; Incoming, Outgoing: lanes of that road leading into the junction and out of it.
|
||||
// nothing is changed until the result is carried out. returns: false if it can't be done, with the reason in Error
|
||||
static bool branch_from(road_node &Road, double const T, glm::dvec2 const &Direction, int const Incoming, int const Outgoing, float const Width, branch &Result, std::string &Error);
|
||||
// the same for a road leaving a junction between its arms, which gives the junction another arm
|
||||
static bool branch_from(junction_node &Junction, glm::dvec2 const &Direction, int const Incoming, int const Outgoing, float const Width, branch &Result, std::string &Error);
|
||||
// makes the junction laid out for a branch, or gives the existing one its new arm, and cuts the roads to fit. what it does is added to Record.
|
||||
// returns: the junction
|
||||
static junction_node *carry_out(branch const &Branch, record &Record);
|
||||
// value of the curve parameter for the point of the axis nearest to specified point
|
||||
static double nearest_parameter(road_node::state const &State, glm::dvec3 const &Point);
|
||||
// end of a road with nothing attached, within specified distance from specified point; optionally arms of junctions too
|
||||
@@ -91,11 +135,6 @@ class editor_road
|
||||
static double planar_length(segment_data const &Path);
|
||||
// slope of the path at its end, in the direction it's laid out
|
||||
static double end_grade(segment_data const &Path);
|
||||
// distance from the centre of a junction to the ends of the roads it's made for
|
||||
static double junction_reach(int const Lanes, float const Width);
|
||||
// lays out a junction entered by a road. Entry: middle of the end of that road, Heading: the way the road goes there,
|
||||
// Incoming, Outgoing: lanes of that road leading into the junction and out of it
|
||||
static junction_node::state junction(junction_kind const Kind, glm::dvec3 const &Entry, glm::dvec2 const &Heading, int const Incoming, int const Outgoing, float const Width);
|
||||
// names of materials and images present in the texture folder, to pick from
|
||||
static std::vector<std::string> const &materials();
|
||||
|
||||
|
||||
106
world/Road.cpp
106
world/Road.cpp
@@ -192,13 +192,24 @@ class road_axis
|
||||
// corner of the cross-section of something laid along the road
|
||||
struct profile_point
|
||||
{
|
||||
double offset; // distance from the axis, positive to the left
|
||||
double offset; // distance from the axis, positive to the left. this part of it follows the taper of the road
|
||||
double height; // above the surface
|
||||
float texture; // texture coordinate across the road
|
||||
double shift{0.0}; // further distance from the axis, which the taper leaves alone
|
||||
};
|
||||
|
||||
// how far the taper of a road has got at specified part of its length. it starts and ends level, so the pieces of a road
|
||||
// meet without a bend in their edges, and it's what a lane running from one width to another comes to on a straight
|
||||
double taper_progress(double const Fraction)
|
||||
{
|
||||
auto const t{std::clamp(Fraction, 0.0, 1.0)};
|
||||
return t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
|
||||
// stretches provided profile along a part of the axis. the profile points go left to right, each two neighbours make a band of triangles.
|
||||
void loft(std::vector<world_vertex> &Output, road_axis const &Axis, std::vector<profile_point> const &Profile, double const From, double const To, double const Step, double const Texturelength)
|
||||
// Taper: what the offsets of the profile are multiplied by at the start and at the end of the axis
|
||||
void loft(std::vector<world_vertex> &Output, road_axis const &Axis, std::vector<profile_point> const &Profile, double const From, double const To, double const Step, double const Texturelength,
|
||||
std::array<float, 2> const &Taper)
|
||||
{
|
||||
if (Profile.size() < 2 || To <= From || Step <= 0.0)
|
||||
{
|
||||
@@ -219,9 +230,10 @@ void loft(std::vector<world_vertex> &Output, road_axis const &Axis, std::vector<
|
||||
{
|
||||
auto const station{From + (To - From) * section / count};
|
||||
auto const frame{Axis.at(station)};
|
||||
auto const scale{Taper[0] == Taper[1] ? static_cast<double>(Taper[0]) : glm::mix(static_cast<double>(Taper[0]), static_cast<double>(Taper[1]), taper_progress(station / Axis.length()))};
|
||||
for (std::size_t idx = 0; idx < pointcount; ++idx)
|
||||
{
|
||||
current[idx] = frame.position + frame.left * Profile[idx].offset + up * Profile[idx].height;
|
||||
current[idx] = frame.position + frame.left * (Profile[idx].offset * scale + Profile[idx].shift) + up * Profile[idx].height;
|
||||
}
|
||||
if (section > 0)
|
||||
{
|
||||
@@ -279,6 +291,10 @@ void road_node::state::normalize()
|
||||
forward = 1;
|
||||
}
|
||||
lanewidth = std::max(1.f, lanewidth);
|
||||
for (auto &end : taper)
|
||||
{
|
||||
end = std::clamp(end, 0.25f, 4.f);
|
||||
}
|
||||
if (texturelength < 0.01f)
|
||||
{
|
||||
texturelength = 4.f;
|
||||
@@ -372,7 +388,7 @@ double road_node::state::lane_offset(std::size_t const Lane) const
|
||||
return Lane < lanes.size() ? offset - 0.5 * lanes[Lane].width : offset;
|
||||
}
|
||||
|
||||
// combined width of the lanes
|
||||
// combined width of the lanes, before the taper
|
||||
double road_node::state::width() const
|
||||
{
|
||||
double total{0.0};
|
||||
@@ -383,24 +399,33 @@ double road_node::state::width() const
|
||||
return total;
|
||||
}
|
||||
|
||||
// what the widths of the lanes are multiplied by at specified value of the curve parameter
|
||||
double road_node::state::scale(double const T) const
|
||||
{
|
||||
return glm::mix(static_cast<double>(taper[0]), static_cast<double>(taper[1]), taper_progress(T));
|
||||
}
|
||||
|
||||
// shape of the middle of a lane, laid out in the direction of travel
|
||||
segment_data road_node::state::lane_path(std::size_t const Lane) const
|
||||
{
|
||||
road_axis const centre{axis};
|
||||
auto const offset{lane_offset(Lane)};
|
||||
// on a road which gets wider or narrower the lane moves away from the axis or towards it along the way
|
||||
auto const beginoffset{lane_offset(Lane) * taper[0]};
|
||||
auto const endoffset{lane_offset(Lane) * taper[1]};
|
||||
auto const begin{centre.frame(0.0)};
|
||||
auto const end{centre.frame(1.0)};
|
||||
|
||||
segment_data path;
|
||||
path.points[segment_data::point::start] = begin.position + begin.left * offset;
|
||||
path.points[segment_data::point::end] = end.position + end.left * offset;
|
||||
if (centre.curved())
|
||||
path.points[segment_data::point::start] = begin.position + begin.left * beginoffset;
|
||||
path.points[segment_data::point::end] = end.position + end.left * endoffset;
|
||||
if (centre.curved() || beginoffset != endoffset)
|
||||
{
|
||||
// a line running beside a bend is shorter or longer than the bend itself, and so are its control vectors
|
||||
path.points[segment_data::point::control1] = centre.control(0) * std::max(0.05, 1.0 - offset * std::cos(begin.roll) * centre.curvature(0.0));
|
||||
path.points[segment_data::point::control2] = centre.control(1) * std::max(0.05, 1.0 - offset * std::cos(end.roll) * centre.curvature(1.0));
|
||||
// a line running beside a bend is shorter or longer than the bend itself, and so are its control vectors.
|
||||
// the control vectors go along the axis at both ends, which is what makes the lane meet the next piece without a bend
|
||||
path.points[segment_data::point::control1] = centre.control(0) * std::max(0.05, 1.0 - beginoffset * std::cos(begin.roll) * centre.curvature(0.0));
|
||||
path.points[segment_data::point::control2] = centre.control(1) * std::max(0.05, 1.0 - endoffset * std::cos(end.roll) * centre.curvature(1.0));
|
||||
// if the roll changes along the road its sides rise or fall relative to the axis
|
||||
auto const climb{offset * (std::sin(end.roll) - std::sin(begin.roll)) / 3.0};
|
||||
auto const climb{(endoffset * std::sin(end.roll) - beginoffset * std::sin(begin.roll)) / 3.0};
|
||||
path.points[segment_data::point::control1].y += climb;
|
||||
path.points[segment_data::point::control2].y -= climb;
|
||||
}
|
||||
@@ -409,7 +434,7 @@ segment_data road_node::state::lane_path(std::size_t const Lane) const
|
||||
{
|
||||
// the lanes on the inner side of the bend have it tighter
|
||||
auto const turn{centre.curvature(0.5) >= 0.0 ? 1.0 : -1.0};
|
||||
path.radius = static_cast<float>(std::max(1.0, std::abs(axis.radius) - turn * offset));
|
||||
path.radius = static_cast<float>(std::max(1.0, std::abs(axis.radius) - turn * 0.5 * (beginoffset + endoffset)));
|
||||
}
|
||||
if (Lane < static_cast<std::size_t>(backward))
|
||||
{
|
||||
@@ -490,6 +515,12 @@ void road_node::import(cParser &Input, glm::dvec3 const &Offset)
|
||||
Input.getTokens();
|
||||
Input >> m_state.velocity;
|
||||
}
|
||||
else if (token == "taper")
|
||||
{
|
||||
// taper <at the start> <at the end>
|
||||
Input.getTokens(2);
|
||||
Input >> m_state.taper[0] >> m_state.taper[1];
|
||||
}
|
||||
else if (token == "lane")
|
||||
{
|
||||
// lane <lane> <width|velocity> <value>
|
||||
@@ -779,7 +810,7 @@ std::vector<scene::shape_node> road_node::create_shapes() const
|
||||
if (road.surface != "none")
|
||||
{
|
||||
auto const tile{static_cast<double>(texture_ratio(road.surface)) * texturelength};
|
||||
loft(vertices(road.surface, lighting_data{}), axis, {{halfwidth, 0.0, static_cast<float>(0.5 + halfwidth / tile)}, {-halfwidth, 0.0, static_cast<float>(0.5 - halfwidth / tile)}}, 0.0, length, step, texturelength);
|
||||
loft(vertices(road.surface, lighting_data{}), axis, {{halfwidth, 0.0, static_cast<float>(0.5 + halfwidth / tile)}, {-halfwidth, 0.0, static_cast<float>(0.5 - halfwidth / tile)}}, 0.0, length, step, texturelength, road.taper);
|
||||
}
|
||||
|
||||
// sides
|
||||
@@ -797,10 +828,10 @@ std::vector<scene::shape_node> road_node::create_shapes() const
|
||||
{
|
||||
// the image is laid out like the one of the legacy roads: the edge of the surface on its right, the bank on its left half
|
||||
profile.push_back({direction * halfwidth, 0.0, 1.f});
|
||||
profile.push_back({direction * (halfwidth + data.width), 0.0, 0.5f});
|
||||
profile.push_back({direction * halfwidth, 0.0, 0.5f, direction * data.width});
|
||||
if (road.slope.x > 0.f)
|
||||
{
|
||||
profile.push_back({direction * (halfwidth + data.width + road.slope.x), -road.slope.y, 0.f});
|
||||
profile.push_back({direction * halfwidth, -road.slope.y, 0.f, direction * (data.width + road.slope.x)});
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -809,14 +840,14 @@ std::vector<scene::shape_node> road_node::create_shapes() const
|
||||
auto const tile{static_cast<double>(texture_ratio(data.material)) * texturelength};
|
||||
profile.push_back({direction * halfwidth, 0.0, 0.f});
|
||||
profile.push_back({direction * halfwidth, road.kerbheight, static_cast<float>(road.kerbheight / tile)});
|
||||
profile.push_back({direction * (halfwidth + data.width), road.kerbheight, static_cast<float>((road.kerbheight + data.width) / tile)});
|
||||
profile.push_back({direction * halfwidth, road.kerbheight, static_cast<float>((road.kerbheight + data.width) / tile), direction * data.width});
|
||||
}
|
||||
if (side == 0)
|
||||
{
|
||||
// the loft takes the corners left to right
|
||||
std::reverse(profile.begin(), profile.end());
|
||||
}
|
||||
loft(vertices(data.material, lighting_data{}), axis, profile, 0.0, length, step, texturelength);
|
||||
loft(vertices(data.material, lighting_data{}), axis, profile, 0.0, length, step, texturelength, road.taper);
|
||||
}
|
||||
|
||||
// markings. these are painted with plain colour instead of an image
|
||||
@@ -827,26 +858,27 @@ std::vector<scene::shape_node> road_node::create_shapes() const
|
||||
paint.ambient = paint.diffuse;
|
||||
auto &lines{vertices("colored", paint)};
|
||||
|
||||
auto const solid = [&](double const Offset) {
|
||||
loft(lines, axis, {{Offset + 0.5 * line_width, line_lift, 0.f}, {Offset - 0.5 * line_width, line_lift, 1.f}}, 0.0, length, std::min(step, dash_step), texturelength);
|
||||
// Offset: where the line runs on a road as wide as its lanes say, Shift: how far from there it's kept whatever the width
|
||||
auto const solid = [&](double const Offset, double const Shift) {
|
||||
loft(lines, axis, {{Offset, line_lift, 0.f, Shift + 0.5 * line_width}, {Offset, line_lift, 1.f, Shift - 0.5 * line_width}}, 0.0, length, std::min(step, dash_step), texturelength, road.taper);
|
||||
};
|
||||
// the dashes are fitted so the gaps at both ends of the road are a half of the regular one, which makes them match the next piece
|
||||
auto const dashed = [&](double const Offset) {
|
||||
auto const dashed = [&](double const Offset, double const Shift) {
|
||||
auto const count{std::max(1, static_cast<int>(std::lround(length / dash_period)))};
|
||||
auto const period{length / count};
|
||||
for (int dash = 0; dash < count; ++dash)
|
||||
{
|
||||
auto const from{(dash + 1.0 / 3.0) * period};
|
||||
loft(lines, axis, {{Offset + 0.5 * line_width, line_lift, 0.f}, {Offset - 0.5 * line_width, line_lift, 1.f}}, from, from + period / 3.0, dash_step, texturelength);
|
||||
loft(lines, axis, {{Offset, line_lift, 0.f, Shift + 0.5 * line_width}, {Offset, line_lift, 1.f, Shift - 0.5 * line_width}}, from, from + period / 3.0, dash_step, texturelength, road.taper);
|
||||
}
|
||||
};
|
||||
|
||||
// edges of the surface
|
||||
auto const edge{halfwidth - edge_inset - 0.5 * line_width};
|
||||
if (edge > 0.5)
|
||||
auto const inset{edge_inset + 0.5 * line_width};
|
||||
if (halfwidth * std::min(road.taper[0], road.taper[1]) - inset > 0.5)
|
||||
{
|
||||
solid(edge);
|
||||
solid(-edge);
|
||||
solid(halfwidth, -inset);
|
||||
solid(-halfwidth, inset);
|
||||
}
|
||||
// lines between the lanes. a lane can be left across a dashed line, or across a pair of lines if the dashed one is on its side
|
||||
auto offset{halfwidth};
|
||||
@@ -859,19 +891,19 @@ std::vector<scene::shape_node> road_node::create_shapes() const
|
||||
{
|
||||
case change_both:
|
||||
{
|
||||
dashed(offset);
|
||||
dashed(offset, 0.0);
|
||||
break;
|
||||
}
|
||||
case change_toright:
|
||||
{
|
||||
dashed(offset + pair);
|
||||
solid(offset - pair);
|
||||
dashed(offset, pair);
|
||||
solid(offset, -pair);
|
||||
break;
|
||||
}
|
||||
case change_toleft:
|
||||
{
|
||||
solid(offset + pair);
|
||||
dashed(offset - pair);
|
||||
solid(offset, pair);
|
||||
dashed(offset, -pair);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -879,12 +911,12 @@ std::vector<scene::shape_node> road_node::create_shapes() const
|
||||
if (opposite)
|
||||
{
|
||||
// traffic going opposite ways is kept apart with a double line
|
||||
solid(offset + pair);
|
||||
solid(offset - pair);
|
||||
solid(offset, pair);
|
||||
solid(offset, -pair);
|
||||
}
|
||||
else
|
||||
{
|
||||
solid(offset);
|
||||
solid(offset, 0.0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1671,7 +1703,7 @@ TTrack *road_node::create_track(std::string const &Name, segment_data const &Pat
|
||||
// radius() subclass details, calculates node's bounding radius
|
||||
float road_node::radius_()
|
||||
{
|
||||
return static_cast<float>(0.5 * m_state.length() + 0.5 * m_state.width() + std::max(m_state.sides[0].width, m_state.sides[1].width) + m_state.slope.x);
|
||||
return static_cast<float>(0.5 * m_state.length() + 0.5 * m_state.width() * std::max(m_state.taper[0], m_state.taper[1]) + std::max(m_state.sides[0].width, m_state.sides[1].width) + m_state.slope.x);
|
||||
}
|
||||
|
||||
// serialize() subclass details, sends content of the subclass to provided stream
|
||||
@@ -1701,6 +1733,10 @@ void road_node::export_as_text_(std::ostream &Output) const
|
||||
Output.precision(precision);
|
||||
// lanes
|
||||
Output << "lanes " << road.forward << ' ' << road.backward << ' ' << "width " << road.lanewidth << ' ';
|
||||
if (road.taper[0] != 1.f || road.taper[1] != 1.f)
|
||||
{
|
||||
Output << "taper " << road.taper[0] << ' ' << road.taper[1] << ' ';
|
||||
}
|
||||
if (road.velocity > 0.f)
|
||||
{
|
||||
Output << "velocity " << road.velocity << ' ';
|
||||
|
||||
@@ -47,7 +47,8 @@ class owned_shapes
|
||||
scene::basic_section *m_section{nullptr};
|
||||
};
|
||||
|
||||
// a piece of a road for wheeled traffic: an axis, and a cross-section which stays the same along it.
|
||||
// a piece of a road for wheeled traffic: an axis, and a cross-section which stays the same along it, save for getting
|
||||
// evenly wider or narrower if it's told to.
|
||||
// the node itself is neither driven on nor drawn. it produces the lanes, which are ordinary invisible one-way tracks,
|
||||
// and the surface, the sides and the markings, which are ordinary shapes. this way the vehicles, the ai and the renderers
|
||||
// deal with a road without knowing about it.
|
||||
@@ -100,6 +101,7 @@ class road_node : public scene::basic_node
|
||||
float velocity{-1.f};
|
||||
std::vector<lane_data> lanes;
|
||||
std::vector<int> changes; // permissions to change the lane, one for each pair of neighbouring lanes
|
||||
std::array<float, 2> taper{1.f, 1.f}; // what the widths of the lanes are multiplied by at the start and at the end of the axis
|
||||
std::string surface{"asphaltdark1"};
|
||||
float texturelength{4.f};
|
||||
std::array<side_data, 2> sides; // left, right
|
||||
@@ -121,8 +123,10 @@ class road_node : public scene::basic_node
|
||||
int default_change(std::size_t const Boundary) const;
|
||||
// distance of the middle of a lane from the axis, positive to the left when facing along the axis
|
||||
double lane_offset(std::size_t const Lane) const;
|
||||
// combined width of the lanes
|
||||
// combined width of the lanes, before the taper
|
||||
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;
|
||||
// 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
|
||||
@@ -206,6 +210,7 @@ class road_node : public scene::basic_node
|
||||
// gets a legacy crossroads path, which is what the vehicles and their drivers already know how to pick a way through.
|
||||
// scenery entry:
|
||||
// node <max> <min> <name> junction <centre> arm <position> <direction x z> <lanes in> <lanes out> <lane width> ... [<property> <values>]... endjunction
|
||||
// the arms don't have to be level with each other: the surface is spanned between their ends and the centre
|
||||
class junction_node : public scene::basic_node
|
||||
{
|
||||
|
||||
|
||||
Reference in New Issue
Block a user