mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-29 04:37:30 +01:00
feat(roads): junctions, height above the ground and rounded profile following the terrain
This commit is contained in:
@@ -63,6 +63,72 @@ std::vector<road_node *> roads_at(glm::dvec3 const &Point, road_node const *Excl
|
||||
return result;
|
||||
}
|
||||
|
||||
// junctions with an arm at specified point
|
||||
std::vector<junction_node *> junctions_at(glm::dvec3 const &Point)
|
||||
{
|
||||
std::vector<junction_node *> result;
|
||||
for (auto *junction : simulation::Junctions.sequence())
|
||||
{
|
||||
if (junction == nullptr || junction->m_editorremoved)
|
||||
continue;
|
||||
for (auto const &arm : junction->definition().arms)
|
||||
{
|
||||
if (glm::distance(arm.position, Point) < kSamePoint)
|
||||
{
|
||||
result.emplace_back(junction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_unique(std::vector<junction_node *> &Junctions, junction_node *Junction)
|
||||
{
|
||||
if (Junction != nullptr && std::find(Junctions.begin(), Junctions.end(), Junction) == Junctions.end())
|
||||
Junctions.emplace_back(Junction);
|
||||
}
|
||||
|
||||
// part of a path between two values of its curve parameter
|
||||
segment_data subpath(segment_data const &Path, double const From, double const To)
|
||||
{
|
||||
segment_data part{Path};
|
||||
auto const &p0{Path.points[segment_data::point::start]};
|
||||
auto const &p3{Path.points[segment_data::point::end]};
|
||||
part.rolls = {static_cast<float>(Path.rolls[0] + (Path.rolls[1] - Path.rolls[0]) * From), static_cast<float>(Path.rolls[0] + (Path.rolls[1] - Path.rolls[0]) * To)};
|
||||
if (Path.points[segment_data::point::control1] == glm::dvec3{0.0} && Path.points[segment_data::point::control2] == glm::dvec3{0.0})
|
||||
{
|
||||
part.points[segment_data::point::start] = glm::mix(p0, p3, From);
|
||||
part.points[segment_data::point::end] = glm::mix(p0, p3, To);
|
||||
return part;
|
||||
}
|
||||
auto const p1{p0 + Path.points[segment_data::point::control1]};
|
||||
auto const p2{p3 + Path.points[segment_data::point::control2]};
|
||||
auto const point = [&](double const T) {
|
||||
auto const u{1.0 - T};
|
||||
return u * u * u * p0 + 3.0 * u * u * T * p1 + 3.0 * u * T * T * p2 + T * T * T * p3;
|
||||
};
|
||||
auto const derivative = [&](double const T) {
|
||||
auto const u{1.0 - T};
|
||||
return 3.0 * u * u * (p1 - p0) + 6.0 * u * T * (p2 - p1) + 3.0 * T * T * (p3 - p2);
|
||||
};
|
||||
// a part of a cubic curve is a cubic curve, with the tangents scaled to the part of the parameter range it takes
|
||||
part.points[segment_data::point::start] = point(From);
|
||||
part.points[segment_data::point::end] = point(To);
|
||||
part.points[segment_data::point::control1] = derivative(From) * ((To - From) / 3.0);
|
||||
part.points[segment_data::point::control2] = derivative(To) * (-(To - From) / 3.0);
|
||||
return part;
|
||||
}
|
||||
|
||||
// true if there's a vehicle on any of the paths turning the traffic back at the loose ends of the road
|
||||
bool turning(road_node const &Road)
|
||||
{
|
||||
for (auto const *turn : Road.turns())
|
||||
if (turn != nullptr && false == turn->Dynamics.empty())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void retire(std::vector<TTrack *> const &Tracks)
|
||||
{
|
||||
for (auto *track : Tracks)
|
||||
@@ -138,6 +204,66 @@ bool editor_road::can_edit(road_node const &Road, std::string *Reason)
|
||||
if (turn != nullptr && false == turn->Dynamics.empty())
|
||||
reason = "there are vehicles turning back next to it";
|
||||
}
|
||||
if (reason.empty())
|
||||
{
|
||||
// so do the ways through the junctions it leads to
|
||||
for (auto const atend : {false, true})
|
||||
for (auto const *junction : junctions_at(axis_end(Road.definition(), atend)))
|
||||
if (junction->occupied())
|
||||
reason = "there are vehicles on the junction it leads to";
|
||||
}
|
||||
if (false == reason.empty() && Reason != nullptr)
|
||||
*Reason = reason;
|
||||
return reason.empty();
|
||||
}
|
||||
|
||||
bool editor_road::can_edit(junction_node const &Junction, std::string *Reason)
|
||||
{
|
||||
std::string reason;
|
||||
if (false == available(&reason))
|
||||
{
|
||||
// reason was filled by the call
|
||||
}
|
||||
else if (Junction.merged())
|
||||
{
|
||||
reason = "the scenery wasn't opened for editing (-edit), geometry of the junctions it came with can't be taken apart";
|
||||
}
|
||||
else if (false == scene::Layers.editable(&Junction, &reason))
|
||||
{
|
||||
// reason was filled by the call
|
||||
}
|
||||
else if (Junction.occupied())
|
||||
{
|
||||
reason = "there are vehicles on it";
|
||||
}
|
||||
else
|
||||
{
|
||||
// the roads attached to it get their loose ends closed anew
|
||||
for (auto const &arm : Junction.definition().arms)
|
||||
for (auto const *road : roads_at(arm.position, nullptr))
|
||||
if (turning(*road))
|
||||
reason = "there are vehicles turning back on the roads it leads to";
|
||||
}
|
||||
if (false == reason.empty() && Reason != nullptr)
|
||||
*Reason = reason;
|
||||
return reason.empty();
|
||||
}
|
||||
|
||||
bool editor_road::can_join(loose_end const &End, std::string *Reason)
|
||||
{
|
||||
// what gets attached takes the place of the paths made for the end as it was
|
||||
std::string reason;
|
||||
if (End.road != nullptr && turning(*End.road))
|
||||
reason = "there are vehicles turning back on the road";
|
||||
if (End.junction != nullptr)
|
||||
{
|
||||
if (End.junction->occupied())
|
||||
reason = "there are vehicles on the junction";
|
||||
for (auto const &arm : End.junction->definition().arms)
|
||||
for (auto const *road : roads_at(arm.position, nullptr))
|
||||
if (turning(*road))
|
||||
reason = "there are vehicles turning back on the roads the junction leads to";
|
||||
}
|
||||
if (false == reason.empty() && Reason != nullptr)
|
||||
*Reason = reason;
|
||||
return reason.empty();
|
||||
@@ -155,38 +281,64 @@ std::vector<road_node *> editor_road::neighbours(road_node const &Road)
|
||||
}
|
||||
|
||||
template <class Change_>
|
||||
void editor_road::refresh(std::vector<road_node *> const &Roads, Change_ Change)
|
||||
void editor_road::refresh(std::vector<road_node *> const &Roads, std::vector<junction_node *> const &Junctions, Change_ Change)
|
||||
{
|
||||
// the pieces next to the changed ones keep their shape, but what they're joined with changes
|
||||
// the pieces and junctions next to the changed ones keep their shape, but what they're joined with changes.
|
||||
// this takes down the paths made for the joints, so they can be made again once the change is done
|
||||
std::vector<road_node *> affected;
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
add_unique(affected, road);
|
||||
for (auto *neighbour : neighbours(*road))
|
||||
add_unique(affected, neighbour);
|
||||
}
|
||||
for (auto *road : affected)
|
||||
retire(road->release_turns());
|
||||
std::vector<junction_node *> crossings;
|
||||
auto const gather = [&]() {
|
||||
std::vector<road_node *> roads{affected};
|
||||
std::vector<junction_node *> junctions{crossings};
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
add_unique(roads, road);
|
||||
if (road->m_editorremoved)
|
||||
continue;
|
||||
for (auto *neighbour : neighbours(*road))
|
||||
add_unique(roads, neighbour);
|
||||
for (auto const atend : {false, true})
|
||||
for (auto *junction : junctions_at(axis_end(road->definition(), atend)))
|
||||
add_unique(junctions, junction);
|
||||
}
|
||||
for (auto *junction : Junctions)
|
||||
add_unique(junctions, junction);
|
||||
// a junction changes what the roads attached to it do at their ends
|
||||
for (auto *junction : junctions)
|
||||
if (false == junction->m_editorremoved)
|
||||
for (auto const &arm : junction->definition().arms)
|
||||
for (auto *road : roads_at(arm.position, nullptr))
|
||||
add_unique(roads, road);
|
||||
for (auto *road : roads)
|
||||
{
|
||||
if (std::find(affected.begin(), affected.end(), road) == affected.end())
|
||||
{
|
||||
retire(road->release_turns());
|
||||
affected.emplace_back(road);
|
||||
}
|
||||
}
|
||||
for (auto *junction : junctions)
|
||||
{
|
||||
if (std::find(crossings.begin(), crossings.end(), junction) == crossings.end())
|
||||
{
|
||||
retire(junction->release_links());
|
||||
crossings.emplace_back(junction);
|
||||
}
|
||||
}
|
||||
};
|
||||
gather();
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
road->hide();
|
||||
retire(road->release_lanes());
|
||||
}
|
||||
for (auto *junction : Junctions)
|
||||
junction->hide();
|
||||
|
||||
Change();
|
||||
|
||||
// the change could have moved the pieces next to other ones
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
for (auto *neighbour : neighbours(*road))
|
||||
{
|
||||
if (std::find(affected.begin(), affected.end(), neighbour) == affected.end())
|
||||
{
|
||||
retire(neighbour->release_turns());
|
||||
affected.emplace_back(neighbour);
|
||||
}
|
||||
}
|
||||
}
|
||||
// the change could have moved things next to other ones
|
||||
gather();
|
||||
std::vector<TTrack *> lanes;
|
||||
for (auto *road : Roads)
|
||||
{
|
||||
@@ -198,6 +350,13 @@ void editor_road::refresh(std::vector<road_node *> const &Roads, Change_ Change)
|
||||
}
|
||||
// joins the lanes with the lanes of the neighbours, by the ends they share
|
||||
editor_track::commit(lanes);
|
||||
for (auto *junction : Junctions)
|
||||
if (false == junction->m_editorremoved)
|
||||
junction->show();
|
||||
// the junctions take the lanes which end at them, what's left loose after that is closed by the roads
|
||||
for (auto *junction : crossings)
|
||||
if (false == junction->m_editorremoved)
|
||||
junction->create_links();
|
||||
for (auto *road : affected)
|
||||
if (false == road->m_editorremoved)
|
||||
road->close_ends();
|
||||
@@ -227,7 +386,7 @@ std::vector<road_node *> editor_road::create(std::vector<road_node::state> const
|
||||
scene::Layers.count(road->layer(), scene::layer_item::track);
|
||||
created.emplace_back(road);
|
||||
}
|
||||
refresh(created, []() {});
|
||||
refresh(created, {}, []() {});
|
||||
return created;
|
||||
}
|
||||
|
||||
@@ -238,7 +397,7 @@ void editor_road::apply(std::vector<std::pair<road_node *, road_node::state>> co
|
||||
add_unique(roads, change.first);
|
||||
if (roads.empty())
|
||||
return;
|
||||
refresh(roads, [&]() {
|
||||
refresh(roads, {}, [&]() {
|
||||
for (auto const &change : Changes)
|
||||
{
|
||||
change.first->define(change.second);
|
||||
@@ -255,7 +414,7 @@ void editor_road::remove(std::vector<road_node *> const &Roads)
|
||||
add_unique(roads, road);
|
||||
if (roads.empty())
|
||||
return;
|
||||
refresh(roads, [&]() {
|
||||
refresh(roads, {}, [&]() {
|
||||
for (auto *road : roads)
|
||||
{
|
||||
road->m_editorremoved = true;
|
||||
@@ -272,7 +431,7 @@ void editor_road::revive(std::vector<road_node *> const &Roads)
|
||||
add_unique(roads, road);
|
||||
if (roads.empty())
|
||||
return;
|
||||
refresh(roads, [&]() {
|
||||
refresh(roads, {}, [&]() {
|
||||
for (auto *road : roads)
|
||||
{
|
||||
road->m_editorremoved = false;
|
||||
@@ -282,6 +441,68 @@ void editor_road::revive(std::vector<road_node *> const &Roads)
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<junction_node *> editor_road::create(std::vector<junction_node::state> const &States)
|
||||
{
|
||||
static int counter{0};
|
||||
std::vector<junction_node *> created;
|
||||
if (false == available())
|
||||
return created;
|
||||
for (auto const &state : States)
|
||||
{
|
||||
scene::node_data data;
|
||||
data.type = "junction";
|
||||
data.range_max = -1.0;
|
||||
data.layer = scene::Layers.active(); // null_handle unless the scenery was opened for editing
|
||||
do
|
||||
{
|
||||
data.name = "junction_" + std::to_string(++counter);
|
||||
} while (simulation::Junctions.find(data.name) != nullptr);
|
||||
auto *junction{new junction_node(data)};
|
||||
junction->define(state);
|
||||
junction->mark_dirty();
|
||||
simulation::Junctions.insert(junction);
|
||||
scene::Layers.count(junction->layer(), scene::layer_item::track);
|
||||
created.emplace_back(junction);
|
||||
}
|
||||
refresh({}, created, []() {});
|
||||
return created;
|
||||
}
|
||||
|
||||
void editor_road::remove(std::vector<junction_node *> const &Junctions)
|
||||
{
|
||||
std::vector<junction_node *> junctions;
|
||||
for (auto *junction : Junctions)
|
||||
if (junction != nullptr && false == junction->m_editorremoved)
|
||||
add_unique(junctions, junction);
|
||||
if (junctions.empty())
|
||||
return;
|
||||
refresh({}, junctions, [&]() {
|
||||
for (auto *junction : junctions)
|
||||
{
|
||||
junction->m_editorremoved = true;
|
||||
scene::Layers.count(junction->layer(), scene::layer_item::track, -1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void editor_road::revive(std::vector<junction_node *> const &Junctions)
|
||||
{
|
||||
std::vector<junction_node *> junctions;
|
||||
for (auto *junction : Junctions)
|
||||
if (junction != nullptr && junction->m_editorremoved)
|
||||
add_unique(junctions, junction);
|
||||
if (junctions.empty())
|
||||
return;
|
||||
refresh({}, junctions, [&]() {
|
||||
for (auto *junction : junctions)
|
||||
{
|
||||
junction->m_editorremoved = false;
|
||||
junction->mark_dirty();
|
||||
scene::Layers.count(junction->layer(), scene::layer_item::track);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
road_node *editor_road::split(road_node &Road, double const T)
|
||||
{
|
||||
if (T <= 0.001 || T >= 0.999)
|
||||
@@ -386,7 +607,29 @@ road_node *editor_road::nearest(glm::dvec3 const &Point, double const Margin)
|
||||
return result;
|
||||
}
|
||||
|
||||
editor_road::loose_end editor_road::find_end(glm::dvec3 const &Point, double const Radius)
|
||||
junction_node *editor_road::nearest_junction(glm::dvec3 const &Point)
|
||||
{
|
||||
junction_node *result{nullptr};
|
||||
double best{std::numeric_limits<double>::max()};
|
||||
for (auto *junction : simulation::Junctions.sequence())
|
||||
{
|
||||
if (junction == nullptr || junction->m_editorremoved)
|
||||
continue;
|
||||
auto const &state{junction->definition()};
|
||||
// the junction takes the ground up to the ends of the roads it's made for
|
||||
double reach{0.0};
|
||||
for (auto const &arm : state.arms)
|
||||
reach = std::max(reach, glm::distance(glm::dvec2{arm.position.x, arm.position.z}, glm::dvec2{state.centre.x, state.centre.z}));
|
||||
auto const distance{glm::distance(glm::dvec2{state.centre.x, state.centre.z}, glm::dvec2{Point.x, Point.z})};
|
||||
if (distance > reach || std::abs(state.centre.y - Point.y) > 3.0 || distance > best)
|
||||
continue;
|
||||
best = distance;
|
||||
result = junction;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
editor_road::loose_end editor_road::find_end(glm::dvec3 const &Point, double const Radius, bool const Junctions)
|
||||
{
|
||||
loose_end result;
|
||||
double best{Radius};
|
||||
@@ -399,15 +642,38 @@ editor_road::loose_end editor_road::find_end(glm::dvec3 const &Point, double con
|
||||
{
|
||||
auto const position{axis_end(state, 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 || false == roads_at(position, road).empty())
|
||||
if (distance > best || std::abs(position.y - Point.y) > 3.0 || false == roads_at(position, road).empty() || false == junctions_at(position).empty())
|
||||
continue;
|
||||
best = distance;
|
||||
result = loose_end{};
|
||||
result.road = road;
|
||||
result.atend = atend;
|
||||
result.position = position;
|
||||
result.outwards = (atend ? state.tangent(1.0) : -state.tangent(0.0));
|
||||
}
|
||||
}
|
||||
if (false == Junctions)
|
||||
return result;
|
||||
for (auto *junction : simulation::Junctions.sequence())
|
||||
{
|
||||
if (junction == nullptr || junction->m_editorremoved)
|
||||
continue;
|
||||
for (auto const &arm : junction->definition().arms)
|
||||
{
|
||||
auto const distance{glm::distance(glm::dvec2{arm.position.x, arm.position.z}, glm::dvec2{Point.x, Point.z})};
|
||||
if (distance > best || std::abs(arm.position.y - Point.y) > 3.0 || false == roads_at(arm.position, nullptr).empty())
|
||||
continue;
|
||||
best = distance;
|
||||
result = loose_end{};
|
||||
result.junction = junction;
|
||||
result.position = arm.position;
|
||||
result.outwards = glm::dvec3{arm.direction.x, 0.0, arm.direction.y};
|
||||
// a road leading away from the junction has the lanes going out of it as the ones going along its axis
|
||||
result.forward = arm.outgoing;
|
||||
result.backward = arm.incoming;
|
||||
result.width = arm.width;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -535,6 +801,147 @@ std::vector<segment_data> editor_road::plan(glm::dvec3 const &Start, glm::dvec2
|
||||
return pieces;
|
||||
}
|
||||
|
||||
double editor_road::planar_length(segment_data const &Path)
|
||||
{
|
||||
auto const whole{subpath(Path, 0.0, 1.0)};
|
||||
auto const &p0{whole.points[segment_data::point::start]};
|
||||
auto const &p3{whole.points[segment_data::point::end]};
|
||||
if (whole.points[segment_data::point::control1] == glm::dvec3{0.0} && whole.points[segment_data::point::control2] == glm::dvec3{0.0})
|
||||
return glm::distance(glm::dvec2{p0.x, p0.z}, glm::dvec2{p3.x, p3.z});
|
||||
auto const p1{p0 + whole.points[segment_data::point::control1]};
|
||||
auto const p2{p3 + whole.points[segment_data::point::control2]};
|
||||
double length{0.0};
|
||||
glm::dvec2 previous{p0.x, p0.z};
|
||||
for (int i = 1; i <= 32; ++i)
|
||||
{
|
||||
auto const t{i / 32.0};
|
||||
auto const u{1.0 - t};
|
||||
auto const point{u * u * u * p0 + 3.0 * u * u * t * p1 + 3.0 * u * t * t * p2 + t * t * t * p3};
|
||||
glm::dvec2 const current{point.x, point.z};
|
||||
length += glm::distance(previous, current);
|
||||
previous = current;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
double editor_road::end_grade(segment_data const &Path)
|
||||
{
|
||||
auto const &control{Path.points[segment_data::point::control2]};
|
||||
if (control != glm::dvec3{0.0})
|
||||
{
|
||||
auto const run{glm::length(glm::dvec2{control.x, control.z})};
|
||||
return run > 1e-6 ? -control.y / run : 0.0;
|
||||
}
|
||||
auto const length{planar_length(Path)};
|
||||
return length > 1e-6 ? (Path.points[segment_data::point::end].y - Path.points[segment_data::point::start].y) / length : 0.0;
|
||||
}
|
||||
|
||||
std::vector<segment_data> editor_road::refine(std::vector<segment_data> const &Pieces, double const Length)
|
||||
{
|
||||
std::vector<segment_data> result;
|
||||
for (auto const &piece : Pieces)
|
||||
{
|
||||
auto const count{std::max(1, static_cast<int>(std::ceil(planar_length(piece) / std::max(1.0, Length) - 0.01)))};
|
||||
for (int i = 0; i < count; ++i)
|
||||
result.emplace_back(count == 1 ? piece : subpath(piece, static_cast<double>(i) / count, static_cast<double>(i + 1) / count));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void editor_road::profile(std::vector<segment_data> &Pieces, std::vector<double> Heights, double const *Startgrade, double const *Endgrade)
|
||||
{
|
||||
auto const count{Pieces.size()};
|
||||
if (count == 0 || Heights.size() != count + 1)
|
||||
return;
|
||||
std::vector<double> lengths(count);
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
lengths[i] = std::max(0.01, planar_length(Pieces[i]));
|
||||
// the heights in between are evened out, so the road doesn't ride every dip. it's never lowered though,
|
||||
// as that would let the ground the heights were taken from come through the surface
|
||||
{
|
||||
auto const sampled{Heights};
|
||||
for (std::size_t i = 1; i < count; ++i)
|
||||
Heights[i] = std::max(sampled[i], 0.25 * sampled[i - 1] + 0.5 * sampled[i] + 0.25 * sampled[i + 1]);
|
||||
}
|
||||
std::vector<double> slopes(count);
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
slopes[i] = (Heights[i + 1] - Heights[i]) / lengths[i];
|
||||
// slope at each joint is shared by the pieces on both its sides, which is what rounds the profile
|
||||
std::vector<double> grades(count + 1);
|
||||
grades.front() = (Startgrade != nullptr ? *Startgrade : slopes.front());
|
||||
grades.back() = (Endgrade != nullptr ? *Endgrade : slopes.back());
|
||||
for (std::size_t i = 1; i < count; ++i)
|
||||
{
|
||||
auto const before{slopes[i - 1]};
|
||||
auto const after{slopes[i]};
|
||||
if (before * after <= 0.0)
|
||||
{
|
||||
// a crest or a bottom
|
||||
grades[i] = 0.0;
|
||||
continue;
|
||||
}
|
||||
auto grade{(before * lengths[i] + after * lengths[i - 1]) / (lengths[i - 1] + lengths[i])};
|
||||
// kept from overshooting the heights the road is led through
|
||||
auto const limit{3.0 * std::min(std::abs(before), std::abs(after))};
|
||||
grades[i] = std::clamp(grade, -limit, limit);
|
||||
}
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
auto &piece{Pieces[i]};
|
||||
auto &start{piece.points[segment_data::point::start]};
|
||||
auto &end{piece.points[segment_data::point::end]};
|
||||
auto &control1{piece.points[segment_data::point::control1]};
|
||||
auto &control2{piece.points[segment_data::point::control2]};
|
||||
start.y = Heights[i];
|
||||
end.y = Heights[i + 1];
|
||||
if (control1 == glm::dvec3{0.0} && control2 == glm::dvec3{0.0})
|
||||
{
|
||||
if (std::abs(grades[i] - slopes[i]) < 1e-4 && std::abs(grades[i + 1] - slopes[i]) < 1e-4)
|
||||
continue; // even slope, the piece stays a plain straight
|
||||
// a straight needs its control points spelled out to bend up or down
|
||||
control1 = (end - start) / 3.0;
|
||||
control2 = (start - end) / 3.0;
|
||||
}
|
||||
control1.y = grades[i] * glm::length(glm::dvec2{control1.x, control1.z});
|
||||
control2.y = -grades[i + 1] * glm::length(glm::dvec2{control2.x, control2.z});
|
||||
}
|
||||
}
|
||||
|
||||
double editor_road::junction_reach(int const Lanes, float const Width)
|
||||
{
|
||||
// half of the road, and room for the corners
|
||||
return 0.5 * std::max(1, Lanes) * Width + 6.0;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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<std::string> const &editor_road::materials()
|
||||
{
|
||||
static std::vector<std::string> names;
|
||||
|
||||
@@ -17,22 +17,42 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "world/Road.h"
|
||||
|
||||
// editor operations on the roads. a road in the scenery is a string of pieces joined end to end,
|
||||
// each piece being a road node with lanes and geometry of its own
|
||||
// each piece being a road node with lanes and geometry of its own; roads meet at junctions
|
||||
class editor_road
|
||||
{
|
||||
public:
|
||||
// an end of a road piece nothing is attached to
|
||||
// an end of a road piece, or an arm of a junction, nothing is attached to
|
||||
struct loose_end
|
||||
{
|
||||
road_node *road{nullptr};
|
||||
bool atend{false};
|
||||
junction_node *junction{nullptr};
|
||||
bool atend{false}; // of a road: the end of its axis rather than the start
|
||||
glm::dvec3 position{0.0};
|
||||
glm::dvec3 outwards{0.0}; // direction the road would continue in
|
||||
glm::dvec3 outwards{0.0}; // direction a road would continue in
|
||||
// of a junction arm: lanes of a road leading away from it
|
||||
int forward{0};
|
||||
int backward{0};
|
||||
float width{0.f};
|
||||
|
||||
bool valid() const
|
||||
{
|
||||
return road != nullptr || junction != nullptr;
|
||||
}
|
||||
};
|
||||
enum class junction_kind
|
||||
{
|
||||
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
|
||||
};
|
||||
// true if the roads can be built and changed at all. optionally explains why they can't
|
||||
static bool available(std::string *Reason = nullptr);
|
||||
// true if specified road piece can be changed or removed. optionally explains why it can't
|
||||
// true if specified road piece or junction can be changed or removed. optionally explains why it can't
|
||||
static bool can_edit(road_node const &Road, std::string *Reason = nullptr);
|
||||
static bool can_edit(junction_node const &Junction, std::string *Reason = nullptr);
|
||||
// true if a road or a junction can be attached to specified loose end. optionally explains why it can't
|
||||
static bool can_join(loose_end const &End, std::string *Reason = nullptr);
|
||||
// makes road pieces out of provided definitions, with their lanes joined to the neighbours and geometry in the scene
|
||||
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
|
||||
@@ -40,26 +60,48 @@ class editor_road
|
||||
// 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);
|
||||
// the same for the junctions
|
||||
static std::vector<junction_node *> create(std::vector<junction_node::state> const &States);
|
||||
static void remove(std::vector<junction_node *> const &Junctions);
|
||||
static void revive(std::vector<junction_node *> const &Junctions);
|
||||
// cuts the piece in two at specified point of its axis. returns: the new piece, holding the part past the cut
|
||||
static road_node *split(road_node &Road, double const T);
|
||||
// the pieces which make a single road together with specified one, in no particular order
|
||||
static std::vector<road_node *> chain(road_node &Road);
|
||||
// the piece under specified point, or within specified distance from it
|
||||
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);
|
||||
// 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 piece with nothing attached, within specified distance from specified point
|
||||
static loose_end find_end(glm::dvec3 const &Point, double const Radius);
|
||||
// end of a road with nothing attached, within specified distance from specified point; optionally arms of junctions too
|
||||
static loose_end find_end(glm::dvec3 const &Point, double const Radius, bool const Junctions = true);
|
||||
// lays out axes of the pieces for a road leading from a point to another. the directions are optional requirements
|
||||
// for the road at its ends; without them it starts straight at the target, with them it bends to meet them.
|
||||
// returns: the axes in order, or nothing if there's no reasonable road to make, with the reason in Error
|
||||
static std::vector<segment_data> plan(glm::dvec3 const &Start, glm::dvec2 const *Direction, glm::dvec3 const &Target, glm::dvec2 const *Enddirection, bool const Straight, std::string &Error);
|
||||
// cuts the pieces so none is longer than specified
|
||||
static std::vector<segment_data> refine(std::vector<segment_data> const &Pieces, double const Length);
|
||||
// gives a string of pieces a smooth profile running through provided heights.
|
||||
// Heights: one for the start of each piece and one for the end of the last. these of the ends are kept, the others are evened out:
|
||||
// the dips are filled while the tops stay, so the ground the heights were taken from doesn't come through the road.
|
||||
// Startgrade, Endgrade: slopes to keep at the ends, to carry on what the road joins; nullptr for none
|
||||
static void profile(std::vector<segment_data> &Pieces, std::vector<double> Heights, double const *Startgrade, double const *Endgrade);
|
||||
// length of the path seen from above
|
||||
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();
|
||||
|
||||
private:
|
||||
// takes down what depends on shape of the listed pieces, lets the caller change them, then builds it all again
|
||||
// takes down what depends on the listed pieces and junctions, lets the caller change them, then builds it all again
|
||||
template <class Change_>
|
||||
static void refresh(std::vector<road_node *> const &Roads, Change_ Change);
|
||||
static void refresh(std::vector<road_node *> const &Roads, std::vector<junction_node *> const &Junctions, Change_ Change);
|
||||
static std::vector<road_node *> neighbours(road_node const &Road);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user