#include "rail.h" #include "game/gamestate.h" #include "network.h" #include // IWYU pragma: keep #include #include #include template class NetworkOf; constexpr auto RAIL_CROSSSECTION_VERTICES {5U}; constexpr Size3D RAIL_HEIGHT {0, 0, 50.F}; RailLinks::RailLinks() : NetworkOf {"rails.jpg"} { } void RailLinks::tick(TickDuration) { } std::shared_ptr RailLinks::addLinksBetween(GlobalPosition3D start, GlobalPosition3D end) { auto node1ins = newNodeAt(start), node2ins = newNodeAt(end); if (node1ins.second == NodeIs::NotInNetwork && node2ins.second == NodeIs::NotInNetwork) { // Both nodes are new, direct link, easy return addLink(start, end); } if (node1ins.second == NodeIs::NotInNetwork && node2ins.second == NodeIs::InNetwork) { // node1 is new, node2 exists, but we build from existing outwards std::swap(node1ins, node2ins); std::swap(start, end); } // Find start link/end - opposite entry dir to existing link; so pi +... const Angle dir = pi + findNodeDirection(node1ins.first); if (dir == vector_yaw(difference(end, start))) { return addLink(start, end); } const auto flatStart {start.xy()}, flatEnd {end.xy()}; if (node2ins.second == NodeIs::InNetwork) { auto midheight = [&](auto mid) { const auto startToMid = ::distance<2>(flatStart, mid); const auto endToMid = ::distance<2>(flatEnd, mid); return start.z + GlobalDistance(RelativeDistance(end.z - start.z) * (startToMid / (startToMid + endToMid))); }; const float dir2 = pi + findNodeDirection(node2ins.first); const auto radii = find_arcs_radius(flatStart, dir, flatEnd, dir2); if (radii.first < radii.second) { const auto radius = radii.first; const auto centre1 = flatStart + (sincos(dir + half_pi) * radius); const auto centre2 = flatEnd + (sincos(dir2 + half_pi) * radius); const auto mid = (centre1 + centre2) / 2; const auto midh = mid || midheight(mid); addLink(start, midh, centre1); return addLink(end, midh, centre2); } const auto radius = radii.second; const auto centre1 = flatStart + (sincos(dir - half_pi) * radius); const auto centre2 = flatEnd + (sincos(dir2 - half_pi) * radius); const auto mid = (centre1 + centre2) / 2; const auto midh = mid || midheight(mid); addLink(midh, start, centre1); return addLink(midh, end, centre2); } const auto diff = difference(end, start); const auto yaw = vector_yaw(diff); const auto n2ed = (yaw * 2) - dir - pi; const auto centre = find_arc_centre(flatStart, dir, flatEnd, n2ed); if (centre.second) { // right hand arc std::swap(start, end); } return addLink(start, end, centre.first); } namespace { constexpr const std::array RAIL_CROSS_SECTION {{ {-1330.F, 0.F, 0}, {-608.F, 0.F, RAIL_HEIGHT.z}, {0, 0.F, RAIL_HEIGHT.z / 2}, {608.F, 0.F, RAIL_HEIGHT.z}, {1330.F, 0.F, 0}, }}; constexpr const std::array RAIL_TEXTURE_POS { 0.15F, .34F, .5F, .66F, 0.85F, }; template constexpr T SLEEPERS_PER_TEXTURE {5}; template constexpr T TEXTURE_LENGTH {2'000}; template constexpr T SLEEPER_LENGTH {T {1} / SLEEPERS_PER_TEXTURE}; template constexpr auto roundSleepers(const T length) { return round_frac(length / TEXTURE_LENGTH, SLEEPER_LENGTH); } } RailLinkStraight::RailLinkStraight(NetworkLinkHolder & instances, const Node::Ptr & nodeA, const Node::Ptr & nodeB) : RailLinkStraight(instances, nodeA, nodeB, nodeB->pos - nodeA->pos) { } RailLinkStraight::RailLinkStraight(NetworkLinkHolder & instances, Node::Ptr nodeA, Node::Ptr nodeB, const RelativePosition3D & diff) : Link({.node = std::move(nodeA), .dir = vector_yaw(diff)}, {.node = std::move(nodeB), .dir = vector_yaw(-diff)}, glm::length(diff)), instance {instances.vertices.acquire( ends[0].node->pos, ends[1].node->pos, flat_orientation(diff), roundSleepers(length))} { } RailLinkCurve::RailLinkCurve(NetworkLinkHolder & instances, const Node::Ptr & nodeA, const Node::Ptr & nodeB, GlobalPosition2D centre) : RailLinkCurve(instances, nodeA, nodeB, centre || nodeA->pos.z, ::distance<2>(nodeA->pos.xy(), centre), {centre, nodeA->pos, nodeB->pos}) { } RailLinkCurve::RailLinkCurve(NetworkLinkHolder & instances, const Node::Ptr & nodeA, const Node::Ptr & nodeB, GlobalPosition3D centre, RelativeDistance radius, const Arc arc) : Link({.node = nodeA, .dir = normalize(arc.first + half_pi)}, {.node = nodeB, .dir = normalize(arc.second - half_pi)}, glm::length(RelativePosition2D {radius * arc.length(), difference(nodeA->pos, nodeB->pos).z})), LinkCurve {centre, radius, arc}, instance {instances.vertices.acquire(ends[0].node->pos, ends[1].node->pos, centre, roundSleepers(length), half_pi - arc.first, half_pi - arc.second, radius)} { } RelativePosition3D RailLink::vehiclePositionOffset() const { return RAIL_HEIGHT; } template<> NetworkLinkHolder::NetworkLinkHolder() { VertexArrayObject {vao} .addAttribs( vertices.bufferName()); } template<> NetworkLinkHolder::NetworkLinkHolder() { VertexArrayObject {vao} .addAttribs(vertices.bufferName()); } namespace { template void renderType(const NetworkLinkHolder & networkLinks, auto & shader) { if (auto count = networkLinks.vertices.size()) { shader.use(RAIL_CROSS_SECTION, RAIL_TEXTURE_POS); glBindVertexArray(networkLinks.vao); glDrawArrays(GL_POINTS, 0, static_cast(count)); } }; } void RailLinks::render(const SceneShader & shader, const Frustum &) const { if (!links.empty()) { texture->bind(); glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(-1, 0); renderType(*this, shader.networkStraight); renderType(*this, shader.networkCurve); glDisable(GL_POLYGON_OFFSET_FILL); glBindVertexArray(0); } } const Surface * RailLinks::getBaseSurface() const { return gameState->assets.at("terrain.surface.gravel").dynamicCast().get(); } RelativeDistance RailLinks::getBaseWidth() const { static constexpr auto BASE_WIDTH = 5'700; return BASE_WIDTH; }